/*
 * Copyright (c) 2021 Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Memory Management Driver APIs
 *
 * This contains APIs for a system-wide memory management
 * driver. Only one instance is permitted on the system.
 */

#ifndef ZEPHYR_INCLUDE_DRIVERS_SYSTEM_MM_H_
#define ZEPHYR_INCLUDE_DRIVERS_SYSTEM_MM_H_

#include <zephyr/types.h>

#ifndef _ASMLANGUAGE

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Memory Management Driver APIs
 * @defgroup mm_drv_apis Memory Management Driver APIs
 *
 * This contains APIs for a system-wide memory management
 * driver. Only one instance is permitted on the system.
 *
 * @ingroup memory_management
 * @{
 */

/**
 * @name Caching mode definitions.
 *
 * These are mutually exclusive.
 *
 * @{
 */

/** No caching */
#define SYS_MM_MEM_CACHE_NONE		2

/** Write-through caching */
#define SYS_MM_MEM_CACHE_WT		1

/** Full write-back caching */
#define SYS_MM_MEM_CACHE_WB		0

/** Reserved bits for cache modes */
#define SYS_MM_MEM_CACHE_MASK		(BIT(3) - 1)

/**
 * @}
 */

/**
 * @name Region permission attributes.
 *
 * Default should be read-only, no user, no exec.
 *
 * @{
 */

/** Region will have read/write access (and not read-only) */
#define SYS_MM_MEM_PERM_RW		BIT(3)

/** Region will be executable (normally forbidden) */
#define SYS_MM_MEM_PERM_EXEC		BIT(4)

/** Region will be accessible to user mode (normally supervisor-only) */
#define SYS_MM_MEM_PERM_USER		BIT(5)

/**
 * @}
 */

/**
 * @name Memory Mapping and Unmapping
 *
 * On mapping and unmapping of memory.
 *
 * @{
 */

/**
 * @brief Map one physical page into the virtual address space
 *
 * This maps one physical page into the virtual address space.
 * Behavior when providing unaligned address is undefined, this
 * is assumed to be page aligned.
 *
 * The memory range itself is never accessed by this operation.
 *
 * This API must be safe to call in ISRs or exception handlers. Calls
 * to this API are assumed to be serialized.
 *
 * @param virt Page-aligned destination virtual address to map
 * @param phys Page-aligned source physical address to map
 * @param flags Caching, access and control flags, see SYS_MM_MEM_* macros
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if virtual address has already been mapped
 */
int sys_mm_drv_map_page(void *virt, uintptr_t phys, uint32_t flags);

/**
 * @brief Map a region of physical memory into the virtual address space
 *
 * This maps a region of physical memory into the virtual address space.
 * Behavior when providing unaligned addresses/sizes is undefined, these
 * are assumed to be page aligned.
 *
 * The memory range itself is never accessed by this operation.
 *
 * This API must be safe to call in ISRs or exception handlers. Calls
 * to this API are assumed to be serialized.
 *
 * @param virt Page-aligned destination virtual address to map
 * @param phys Page-aligned source physical address to map
 * @param size Page-aligned size of the mapped memory region in bytes
 * @param flags Caching, access and control flags, see SYS_MM_MEM_* macros
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if any virtual addresses have already been mapped
 */
int sys_mm_drv_map_region(void *virt, uintptr_t phys,
			  size_t size, uint32_t flags);

/**
 * @brief Map an array of physical memory into the virtual address space
 *
 * This maps an array of physical pages into a continuous virtual address
 * space. Behavior when providing unaligned addresses is undefined, these
 * are assumed to be page aligned.
 *
 * The physical memory pages are never accessed by this operation.
 *
 * This API must be safe to call in ISRs or exception handlers. Calls
 * to this API are assumed to be serialized.
 *
 * @param virt Page-aligned destination virtual address to map
 * @param phys Array of pge-aligned source physical address to map
 * @param cnt Number of elements in the physical page array
 * @param flags Caching, access and control flags, see SYS_MM_MEM_* macros
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if any virtual addresses have already been mapped
 */
int sys_mm_drv_map_array(void *virt, uintptr_t *phys,
			 size_t cnt, uint32_t flags);

/**
 * @brief Remove mapping for one page of the provided virtual address
 *
 * This unmaps one page from the virtual address space.
 *
 * When this completes, the relevant translation table entries will be
 * updated as if no mapping was ever made for that memory page. No previous
 * context needs to be preserved. This function must update mapping in
 * all active translation tables.
 *
 * Behavior when providing unaligned address is undefined, this
 * is assumed to be page aligned.
 *
 * Implementations must invalidate translation caching as necessary.
 *
 * @param virt Page-aligned virtual address to un-map
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if virtual address is not mapped
 */
int sys_mm_drv_unmap_page(void *virt);

/**
 * @brief Remove mappings for a provided virtual address range
 *
 * This unmaps pages in the provided virtual address range.
 *
 * When this completes, the relevant translation table entries will be
 * updated as if no mapping was ever made for that memory range. No previous
 * context needs to be preserved. This function must update mappings in
 * all active translation tables.
 *
 * Behavior when providing unaligned address is undefined, this
 * is assumed to be page aligned.
 *
 * Implementations must invalidate translation caching as necessary.
 *
 * @param virt Page-aligned base virtual address to un-map
 * @param size Page-aligned region size
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if virtual address is not mapped
 */
int sys_mm_drv_unmap_region(void *virt, size_t size);

/**
 * @brief Remap virtual pages into new address
 *
 * This remaps a virtual memory region starting at @p virt_old
 * of size @p size into a new virtual memory region starting at
 * @p virt_new. In other words, physical memory at @p virt_old is
 * remapped to appear at @p virt_new. Both addresses must be page
 * aligned and valid.
 *
 * Note that the virtual memory at both the old and new addresses
 * must be unmapped in the memory domains of any runnable Zephyr
 * thread as this does not deal with memory domains.
 *
 * Note that overlapping of old and new virtual memory regions
 * is usually not supported for simpler implementation. Refer to
 * the actual driver to make sure if overlapping is allowed.
 *
 * @param virt_old Page-aligned base virtual address of existing memory
 * @param size Page-aligned size of the mapped memory region in bytes
 * @param virt_new Page-aligned base virtual address to which to remap
 *                 the memory
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if old virtual addresses are not all mapped or
 *                 new virtual addresses are not all unmapped
 */
int sys_mm_drv_remap_region(void *virt_old, size_t size, void *virt_new);

/**
 * @}
 */

/**
 * @name Memory Moving
 *
 * On moving already mapped memory.
 *
 * @{
 */

/**
 * @brief Physically move memory, with copy
 *
 * This maps a region of physical memory into the new virtual address space
 * (@p virt_new), and copy region of size @p size from the old virtual
 * address space (@p virt_old). The new virtual memory region is mapped
 * from physical memory starting at @p phys_new of size @p size.
 *
 * Behavior when providing unaligned addresses/sizes is undefined, these
 * are assumed to be page aligned.
 *
 * Note that the virtual memory at both the old and new addresses
 * must be unmapped in the memory domains of any runnable Zephyr
 * thread as this does not deal with memory domains.
 *
 * Note that overlapping of old and new virtual memory regions
 * is usually not supported for simpler implementation. Refer to
 * the actual driver to make sure if overlapping is allowed.
 *
 * @param virt_old Page-aligned base virtual address of existing memory
 * @param size Page-aligned size of the mapped memory region in bytes
 * @param virt_new Page-aligned base virtual address to which to map
 *                 new physical pages
 * @param phys_new Page-aligned base physical address to contain
 *                 the moved memory
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if old virtual addresses are not all mapped or
 *                 new virtual addresses are not all unmapped
 */
int sys_mm_drv_move_region(void *virt_old, size_t size, void *virt_new,
			   uintptr_t phys_new);

/**
 * @brief Physically move memory, with copy
 *
 * This maps a region of physical memory into the new virtual address space
 * (@p virt_new), and copy region of size @p size from the old virtual
 * address space (@p virt_old). The new virtual memory region is mapped
 * from an array of physical pages.
 *
 * Behavior when providing unaligned addresses/sizes is undefined, these
 * are assumed to be page aligned.
 *
 * Note that the virtual memory at both the old and new addresses
 * must be unmapped in the memory domains of any runnable Zephyr
 * thread as this does not deal with memory domains.
 *
 * Note that overlapping of old and new virtual memory regions
 * is usually not supported for simpler implementation. Refer to
 * the actual driver to make sure if overlapping is allowed.
 *
 * @param virt_old Page-aligned base virtual address of existing memory
 * @param size Page-aligned size of the mapped memory region in bytes
 * @param virt_new Page-aligned base virtual address to which to map
 *                 new physical pages
 * @param phys_new Array of page-aligned physical address to contain
 *                 the moved memory
 * @param phys_cnt Number of elements in the physical page array
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if old virtual addresses are not all mapped or
 *                 new virtual addresses are not all unmapped
 */
int sys_mm_drv_move_array(void *virt_old, size_t size, void *virt_new,
			  uintptr_t *phys_new, size_t phys_cnt);

/**
 * @}
 */

/**
 * @name Memory Mapping Attributes
 *
 * On manipulating attributes of already mapped memory.
 *
 * @{
 */

/**
 * @brief Update memory page flags
 *
 * This changes the attributes of physical memory page which is already
 * mapped to a virtual address. This is useful when use case of
 * specific memory region  changes.
 * E.g. when the library/module code is copied to the memory then
 * it needs to be read-write and after it has already
 * been copied and library/module code is ready to be executed then
 * attributes need to be changed to read-only/executable.
 * Calling this API must not cause losing memory contents.
 *
 * @param virt Page-aligned virtual address to be updated
 * @param flags Caching, access and control flags, see SYS_MM_MEM_* macros
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if virtual addresses is not mapped
 */

int sys_mm_drv_update_page_flags(void *virt, uint32_t flags);

/**
 * @brief Update memory region flags
 *
 * This changes the attributes of physical memory which is already
 * mapped to a virtual address. This is useful when use case of
 * specific memory region  changes.
 * E.g. when the library/module code is copied to the memory then
 * it needs to be read-write and after it has already
 * been copied and library/module code is ready to be executed then
 * attributes need to be changed to read-only/executable.
 * Calling this API must not cause losing memory contents.
 *
 * @param virt Page-aligned virtual address to be updated
 * @param size Page-aligned size of the mapped memory region in bytes
 * @param flags Caching, access and control flags, see SYS_MM_MEM_* macros
 *
 * @retval 0 if successful
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if virtual addresses is not mapped
 */

int sys_mm_drv_update_region_flags(void *virt, size_t size, uint32_t flags);

/**
 * @}
 */

/**
 * @name Memory Mappings Query
 *
 * On querying information on memory mappings.
 *
 * @{
 */

/**
 * @brief Get the mapped physical memory address from virtual address.
 *
 * The function queries the translation tables to find the physical
 * memory address of a mapped virtual address.
 *
 * Behavior when providing unaligned address is undefined, this
 * is assumed to be page aligned.
 *
 * @param      virt Page-aligned virtual address
 * @param[out] phys Mapped physical address (can be NULL if only checking
 *                  if virtual address is mapped)
 *
 * @retval 0 if mapping is found and valid
 * @retval -EINVAL if invalid arguments are provided
 * @retval -EFAULT if virtual address is not mapped
 */
int sys_mm_drv_page_phys_get(void *virt, uintptr_t *phys);

/**
 * @brief Represents an available memory region.
 *
 * A memory region that can be used by allocators. Driver defined
 * attributes can be used to guide the proper usage of each region.
 */
struct sys_mm_drv_region {
	void *addr; /**< @brief Address of the memory region */
	size_t size; /**< @brief Size of the memory region */
	uint32_t attr; /**< @brief Driver defined attributes of the memory region */
};

/* TODO is it safe to assume no valid region has size == 0? */
/**
 * @brief Iterates over an array of regions returned by #sys_mm_drv_query_memory_regions
 *
 * Note that a sentinel item marking the end of the array is expected for
 * this macro to work.
 */
#define SYS_MM_DRV_MEMORY_REGION_FOREACH(regions, iter) \
	for (iter = regions; iter->size; iter++)

/**
 * @brief Query available memory regions
 *
 * Returns an array of available memory regions. One can iterate over
 * the array using #SYS_MM_DRV_MEMORY_REGION_FOREACH. Note that the last
 * item of the array is a sentinel marking the end, and it's identified
 * by it's size attribute, which is zero.
 *
 * @retval regions A possibly empty array - i.e. containing only the sentinel
 *         marking at the end - of memory regions.
 */
const struct sys_mm_drv_region *sys_mm_drv_query_memory_regions(void);

/**
 * @brief Free the memory array returned by #sys_mm_drv_query_memory_regions
 *
 * The driver may have dynamically allocated the memory for the array of
 * regions returned by #sys_mm_drv_query_memory_regions. This method provides
 * it the opportunity to free any related resources.
 *
 * @param regions Array of regions previously returned by
 *                #sys_mm_drv_query_memory_regions
 */
void sys_mm_drv_query_memory_regions_free(const struct sys_mm_drv_region *regions);

/**
 * @}
 */

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* _ASMLANGUAGE */

#endif /* ZEPHYR_INCLUDE_DRIVERS_SYSTEM_MM_H_ */
