/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2020 Western Digital Corporation or its affiliates.
 *
 * Authors:
 *   Anup Patel <anup.patel@wdc.com>
 */

#ifndef __SBI_DOMAIN_H__
#define __SBI_DOMAIN_H__

#include <sbi/riscv_locks.h>
#include <sbi/sbi_types.h>
#include <sbi/sbi_hartmask.h>
#include <sbi/sbi_domain_context.h>

struct sbi_scratch;

/** Domain access types */
enum sbi_domain_access {
	SBI_DOMAIN_READ = (1UL << 0),
	SBI_DOMAIN_WRITE = (1UL << 1),
	SBI_DOMAIN_EXECUTE = (1UL << 2),
	SBI_DOMAIN_MMIO = (1UL << 3)
};

/** Representation of OpenSBI domain memory region */
struct sbi_domain_memregion {
	/**
	 * Size of memory region as power of 2
	 * It has to be minimum 3 and maximum __riscv_xlen
	 */
	unsigned long order;
	/**
	 * Base address of memory region
	 * It must be 2^order aligned address
	 */
	unsigned long base;
	/** Flags representing memory region attributes */
#define SBI_DOMAIN_MEMREGION_M_READABLE		(1UL << 0)
#define SBI_DOMAIN_MEMREGION_M_WRITABLE		(1UL << 1)
#define SBI_DOMAIN_MEMREGION_M_EXECUTABLE	(1UL << 2)
#define SBI_DOMAIN_MEMREGION_SU_READABLE	(1UL << 3)
#define SBI_DOMAIN_MEMREGION_SU_WRITABLE	(1UL << 4)
#define SBI_DOMAIN_MEMREGION_SU_EXECUTABLE	(1UL << 5)

#define SBI_DOMAIN_MEMREGION_ACCESS_MASK	(0x3fUL)
#define SBI_DOMAIN_MEMREGION_M_ACCESS_MASK	(0x7UL)
#define SBI_DOMAIN_MEMREGION_SU_ACCESS_MASK	(0x38UL)

#define SBI_DOMAIN_MEMREGION_SU_ACCESS_SHIFT	(3)

#define SBI_DOMAIN_MEMREGION_SHARED_RDONLY		\
		(SBI_DOMAIN_MEMREGION_M_READABLE |	\
		 SBI_DOMAIN_MEMREGION_SU_READABLE)

#define SBI_DOMAIN_MEMREGION_SHARED_SUX_MRX		\
		(SBI_DOMAIN_MEMREGION_M_READABLE   |	\
		 SBI_DOMAIN_MEMREGION_M_EXECUTABLE |	\
		 SBI_DOMAIN_MEMREGION_SU_EXECUTABLE)

#define SBI_DOMAIN_MEMREGION_SHARED_SUX_MX		\
		(SBI_DOMAIN_MEMREGION_M_EXECUTABLE |	\
		 SBI_DOMAIN_MEMREGION_SU_EXECUTABLE)

#define SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW		\
		(SBI_DOMAIN_MEMREGION_M_READABLE |	\
		 SBI_DOMAIN_MEMREGION_M_WRITABLE |	\
		 SBI_DOMAIN_MEMREGION_SU_READABLE|	\
		 SBI_DOMAIN_MEMREGION_SU_WRITABLE)

#define SBI_DOMAIN_MEMREGION_SHARED_SUR_MRW		\
		(SBI_DOMAIN_MEMREGION_M_READABLE |	\
		 SBI_DOMAIN_MEMREGION_M_WRITABLE |	\
		 SBI_DOMAIN_MEMREGION_SU_READABLE)

	/* Shared read-only region between M and SU mode */
#define SBI_DOMAIN_MEMREGION_IS_SUR_MR(__flags)			 \
		((__flags & SBI_DOMAIN_MEMREGION_ACCESS_MASK) == \
		 SBI_DOMAIN_MEMREGION_SHARED_RDONLY)

	/* Shared region: SU execute-only and M read/execute */
#define SBI_DOMAIN_MEMREGION_IS_SUX_MRX(__flags)		 \
		((__flags & SBI_DOMAIN_MEMREGION_ACCESS_MASK) == \
		 SBI_DOMAIN_MEMREGION_SHARED_SUX_MRX)

	/* Shared region: SU and M execute-only */
#define SBI_DOMAIN_MEMREGION_IS_SUX_MX(__flags)			 \
		((__flags & SBI_DOMAIN_MEMREGION_ACCESS_MASK) == \
		 SBI_DOMAIN_MEMREGION_SHARED_SUX_MX)

	/* Shared region: SU and M read/write */
#define SBI_DOMAIN_MEMREGION_IS_SURW_MRW(__flags)		 \
		((__flags & SBI_DOMAIN_MEMREGION_ACCESS_MASK) == \
		 SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW)

	/* Shared region: SU read-only and M read/write */
#define SBI_DOMAIN_MEMREGION_IS_SUR_MRW(__flags)		 \
		((__flags & SBI_DOMAIN_MEMREGION_ACCESS_MASK) == \
		 SBI_DOMAIN_MEMREGION_SHARED_SUR_MRW)

	/*
	 * Check if region flags match with any of the above
	 * mentioned shared region type
	 */
#define SBI_DOMAIN_MEMREGION_IS_SHARED(_flags)			\
		(SBI_DOMAIN_MEMREGION_IS_SUR_MR(_flags)  ||	\
		 SBI_DOMAIN_MEMREGION_IS_SUX_MRX(_flags) ||	\
		 SBI_DOMAIN_MEMREGION_IS_SUX_MX(_flags)  ||	\
		 SBI_DOMAIN_MEMREGION_IS_SURW_MRW(_flags)||	\
		 SBI_DOMAIN_MEMREGION_IS_SUR_MRW(_flags))

#define SBI_DOMAIN_MEMREGION_M_ONLY_ACCESS(__flags)			\
		((__flags & SBI_DOMAIN_MEMREGION_M_ACCESS_MASK) &&	\
		 !(__flags & SBI_DOMAIN_MEMREGION_SU_ACCESS_MASK))

#define SBI_DOMAIN_MEMREGION_SU_ONLY_ACCESS(__flags)			\
		((__flags & SBI_DOMAIN_MEMREGION_SU_ACCESS_MASK)  &&	\
		 !(__flags & SBI_DOMAIN_MEMREGION_M_ACCESS_MASK))

/** Bit to control if permissions are enforced on all modes */
#define SBI_DOMAIN_MEMREGION_ENF_PERMISSIONS	(1UL << 6)

#define SBI_DOMAIN_MEMREGION_M_RWX		\
				(SBI_DOMAIN_MEMREGION_M_READABLE | \
				 SBI_DOMAIN_MEMREGION_M_WRITABLE | \
				 SBI_DOMAIN_MEMREGION_M_EXECUTABLE)

#define SBI_DOMAIN_MEMREGION_SU_RWX		\
				(SBI_DOMAIN_MEMREGION_SU_READABLE | \
				 SBI_DOMAIN_MEMREGION_SU_WRITABLE | \
				 SBI_DOMAIN_MEMREGION_SU_EXECUTABLE)

/* Unrestricted M-mode accesses but enfoced on SU-mode */
#define SBI_DOMAIN_MEMREGION_READABLE		\
				(SBI_DOMAIN_MEMREGION_SU_READABLE | \
				 SBI_DOMAIN_MEMREGION_M_RWX)
#define SBI_DOMAIN_MEMREGION_WRITEABLE		\
				(SBI_DOMAIN_MEMREGION_SU_WRITABLE | \
				 SBI_DOMAIN_MEMREGION_M_RWX)
#define SBI_DOMAIN_MEMREGION_EXECUTABLE		\
				(SBI_DOMAIN_MEMREGION_SU_EXECUTABLE | \
				 SBI_DOMAIN_MEMREGION_M_RWX)

/* Enforced accesses across all modes */
#define SBI_DOMAIN_MEMREGION_ENF_READABLE	\
				(SBI_DOMAIN_MEMREGION_SU_READABLE | \
				 SBI_DOMAIN_MEMREGION_M_READABLE)
#define SBI_DOMAIN_MEMREGION_ENF_WRITABLE	\
				(SBI_DOMAIN_MEMREGION_SU_WRITABLE | \
				 SBI_DOMAIN_MEMREGION_M_WRITABLE)
#define SBI_DOMAIN_MEMREGION_ENF_EXECUTABLE	\
				(SBI_DOMAIN_MEMREGION_SU_EXECUTABLE | \
				 SBI_DOMAIN_MEMREGION_M_EXECUTABLE)

#define SBI_DOMAIN_MEMREGION_MMIO		(1UL << 31)
	unsigned long flags;
};

/** Maximum number of domains */
#define SBI_DOMAIN_MAX_INDEX			32

/** Representation of OpenSBI domain */
struct sbi_domain {
	/**
	 * Logical index of this domain
	 * Note: This set by sbi_domain_finalize() in the coldboot path
	 */
	u32 index;
	/**
	 * HARTs assigned to this domain
	 * Note: This set by sbi_domain_init() and sbi_domain_finalize()
	 * in the coldboot path
	 */
	struct sbi_hartmask assigned_harts;
	/** Spinlock for accessing assigned_harts */
	spinlock_t assigned_harts_lock;
	/** Name of this domain */
	char name[64];
	/** Possible HARTs in this domain */
	const struct sbi_hartmask *possible_harts;
	/** Contexts for possible HARTs indexed by hartindex */
	struct sbi_context *hartindex_to_context_table[SBI_HARTMASK_MAX_BITS];
	/** Array of memory regions terminated by a region with order zero */
	struct sbi_domain_memregion *regions;
	/** HART id of the HART booting this domain */
	u32 boot_hartid;
	/** Arg1 (or 'a1' register) of next booting stage for this domain */
	unsigned long next_arg1;
	/** Address of next booting stage for this domain */
	unsigned long next_addr;
	/** Privilege mode of next booting stage for this domain */
	unsigned long next_mode;
	/** Is domain allowed to reset the system */
	bool system_reset_allowed;
	/** Is domain allowed to suspend the system */
	bool system_suspend_allowed;
	/** Identifies whether to include the firmware region */
	bool fw_region_inited;
};

/** The root domain instance */
extern struct sbi_domain root;

/** Get pointer to sbi_domain from HART index */
struct sbi_domain *sbi_hartindex_to_domain(u32 hartindex);

/** Update HART local pointer to point to specified domain */
void sbi_update_hartindex_to_domain(u32 hartindex, struct sbi_domain *dom);

/** Get pointer to sbi_domain for current HART */
#define sbi_domain_thishart_ptr() \
	sbi_hartindex_to_domain(sbi_hartid_to_hartindex(current_hartid()))

/** Index to domain table */
extern struct sbi_domain *domidx_to_domain_table[];

/** Get pointer to sbi_domain from index */
#define sbi_index_to_domain(__index) \
	domidx_to_domain_table[__index]

/** Iterate over each domain */
#define sbi_domain_for_each(__i, __d) \
	for ((__i) = 0; ((__d) = sbi_index_to_domain(__i)); (__i)++)

/** Iterate over each memory region of a domain */
#define sbi_domain_for_each_memregion(__d, __r) \
	for ((__r) = (__d)->regions; (__r)->order; (__r)++)

/**
 * Check whether given HART is assigned to specified domain
 * @param dom pointer to domain
 * @param hartid the HART ID
 * @return true if HART is assigned to domain otherwise false
 */
bool sbi_domain_is_assigned_hart(const struct sbi_domain *dom, u32 hartid);

/**
 * Get ulong assigned HART mask for given domain and HART base ID
 * @param dom pointer to domain
 * @param hbase the HART base ID
 * @return ulong possible HART mask
 * Note: the return ulong mask will be set to zero on failure.
 */
ulong sbi_domain_get_assigned_hartmask(const struct sbi_domain *dom,
				       ulong hbase);

/**
 * Initialize a domain memory region based on it's physical
 * address and size.
 *
 * @param addr start physical address of memory region
 * @param size physical size of memory region
 * @param flags memory region flags
 * @param reg pointer to memory region being initialized
 */
void sbi_domain_memregion_init(unsigned long addr,
				unsigned long size,
				unsigned long flags,
				struct sbi_domain_memregion *reg);

/**
 * Check whether we can access specified address for given mode and
 * memory region flags under a domain
 * @param dom pointer to domain
 * @param addr the address to be checked
 * @param mode the privilege mode of access
 * @param access_flags bitmask of domain access types (enum sbi_domain_access)
 * @return true if access allowed otherwise false
 */
bool sbi_domain_check_addr(const struct sbi_domain *dom,
			   unsigned long addr, unsigned long mode,
			   unsigned long access_flags);

/**
 * Check whether we can access specified address range for given mode and
 * memory region flags under a domain
 * @param dom pointer to domain
 * @param addr the start of the address range to be checked
 * @param size the size of the address range to be checked
 * @param mode the privilege mode of access
 * @param access_flags bitmask of domain access types (enum sbi_domain_access)
 * @return TRUE if access allowed otherwise FALSE
 */
bool sbi_domain_check_addr_range(const struct sbi_domain *dom,
				 unsigned long addr, unsigned long size,
				 unsigned long mode,
				 unsigned long access_flags);

/** Dump domain details on the console */
void sbi_domain_dump(const struct sbi_domain *dom, const char *suffix);

/** Dump all domain details on the console */
void sbi_domain_dump_all(const char *suffix);

/**
 * Register a new domain
 * @param dom pointer to domain
 * @param assign_mask pointer to HART mask of HARTs assigned to the domain
 *
 * @return 0 on success and negative error code on failure
 */
int sbi_domain_register(struct sbi_domain *dom,
			const struct sbi_hartmask *assign_mask);

/**
 * Add a memory region to the root domain
 * @param reg pointer to the memory region to be added
 *
 * @return 0 on success
 * @return SBI_EALREADY if memory region conflicts with the existing one
 * @return SBI_EINVAL otherwise
 */
int sbi_domain_root_add_memregion(const struct sbi_domain_memregion *reg);

/**
 * Add a memory range with its flags to the root domain
 * @param addr start physical address of memory range
 * @param size physical size of memory range
 * @param align alignment of memory region
 * @param region_flags memory range flags
 *
 * @return 0 on success
 * @return SBI_EALREADY if memory region conflicts with the existing one
 * @return SBI_EINVAL otherwise
 */
int sbi_domain_root_add_memrange(unsigned long addr, unsigned long size,
			   unsigned long align, unsigned long region_flags);

/** Finalize domain tables and startup non-root domains */
int sbi_domain_finalize(struct sbi_scratch *scratch, u32 cold_hartid);

/** Initialize domains */
int sbi_domain_init(struct sbi_scratch *scratch, u32 cold_hartid);

#endif
