kernel: add k_mem_map() interface
Allows applications to increase the data space available to Zephyr
via anonymous memory mappings. Loosely based on mmap().
Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
diff --git a/include/sys/mem_manage.h b/include/sys/mem_manage.h
index fdc2793..ee93f87 100644
--- a/include/sys/mem_manage.h
+++ b/include/sys/mem_manage.h
@@ -14,13 +14,13 @@
*/
/** No caching. Most drivers want this. */
-#define K_MEM_CACHE_NONE 0
+#define K_MEM_CACHE_NONE 2
/** Write-through caching. Used by certain drivers. */
#define K_MEM_CACHE_WT 1
/** Full write-back caching. Any RAM mapped wants this. */
-#define K_MEM_CACHE_WB 2
+#define K_MEM_CACHE_WB 0
/** Reserved bits for cache modes in k_map() flags argument */
#define K_MEM_CACHE_MASK (BIT(3) - 1)
@@ -94,6 +94,87 @@
void z_phys_map(uint8_t **virt_ptr, uintptr_t phys, size_t size,
uint32_t flags);
+/*
+ * k_mem_map() control flags
+ */
+
+/**
+ * @def K_MEM_MAP_UNINIT
+ *
+ * @brief The mapped region is not guaranteed to be zeroed.
+ *
+ * This may improve performance. The associated page frames may contain
+ * indeterminate data, zeroes, or even sensitive information.
+ *
+ * This may not be used with K_MEM_PERM_USER as there are no circumstances
+ * where this is safe.
+ */
+#define K_MEM_MAP_UNINIT BIT(16)
+
+/**
+ * @def K_MEM_MAP_LOCK
+ *
+ * Region will be pinned in memory and never paged
+ *
+ * Such memory is guaranteed to never produce a page fault due to page-outs
+ * or copy-on-write once the mapping call has returned. Physical page frames
+ * will be pre-fetched as necessary and pinned.
+ */
+#define K_MEM_MAP_LOCK BIT(17)
+
+/**
+ * @def K_MEM_MAP_GUARD
+ *
+ * A un-mapped virtual guard page will be placed in memory immediately preceding
+ * the mapped region. This page will still be noted as being used by the
+ * virtual memory manager. The total size of the allocation will be the
+ * requested size plus the size of this guard page. The returned address
+ * pointer will not include the guard page immediately below it. The typical
+ * use-case is downward-growing thread stacks.
+ *
+ * Zephyr treats page faults on this guard page as a fatal K_ERR_STACK_CHK_FAIL
+ * if it determines it immediately precedes a stack buffer, this is
+ * implemented in the architecture layer.
+ */
+#define K_MEM_MAP_GUARD BIT(18)
+
+/**
+ * Map anonymous memory into Zephyr's address space
+ *
+ * This function effectively increases the data space available to Zephyr.
+ * The kernel will choose a base virtual address and return it to the caller.
+ * The memory will have access permissions for all contexts set per the
+ * provided flags argument.
+ *
+ * If user thread access control needs to be managed in any way, do not enable
+ * K_MEM_PERM_USER flags here; instead manage the region's permissions
+ * with memory domain APIs after the mapping has been established. Setting
+ * K_MEM_PERM_USER here will allow all user threads to access this memory
+ * which is usually undesirable.
+ *
+ * Unless K_MEM_MAP_UNINIT is used, the returned memory will be zeroed.
+ *
+ * The mapped region is not guaranteed to be physically contiguous in memory.
+ * Physically contiguous buffers should be allocated statically and pinned
+ * at build time.
+ *
+ * Pages mapped in this way have write-back cache settings.
+ *
+ * The returned virtual memory pointer will be page-aligned. The size
+ * parameter, and any base address for re-mapping purposes must be page-
+ * aligned.
+ *
+ * Many K_MEM_MAP_* flags have been implemented to alter the behavior of this
+ * function, with details in the documentation for these flags.
+ *
+ * @param size Size of the memory mapping. This must be page-aligned.
+ * @param flags K_MEM_PERM_*, K_MEM_MAP_* control flags.
+ * @return The mapped memory location, or NULL if insufficient virtual address
+ * space, insufficient physical memory to establish the mapping,
+ * or insufficient memory for paging structures.
+ */
+void *k_mem_map(size_t size, uint32_t flags);
+
/**
* Given an arbitrary region, provide a aligned region that covers it
*
diff --git a/kernel/mmu.c b/kernel/mmu.c
index 200ae42..9dece53 100644
--- a/kernel/mmu.c
+++ b/kernel/mmu.c
@@ -252,7 +252,7 @@
* local ontology (and do some assertions while we're at it)
*/
static void frame_mapped_set(struct z_page_frame *pf, void *addr)
-{
+{
PF_ASSERT(pf, !z_page_frame_is_reserved(pf),
"attempted to map a reserved page frame");
@@ -266,9 +266,102 @@
pf->flags |= Z_PAGE_FRAME_MAPPED;
pf->addr = addr;
- pf->refcount++;
}
+/* Allocate a free page frame, and map it to a specified virtual address
+ *
+ * TODO: Add optional support for copy-on-write mappings to a zero page instead
+ * of allocating, in which case page frames will be allocated lazily as
+ * the mappings to the zero page get touched.
+ */
+static int map_anon_page(void *addr, uint32_t flags)
+{
+ int ret;
+ struct z_page_frame *pf;
+ uintptr_t phys;
+ bool lock = (flags & K_MEM_MAP_LOCK) != 0;
+
+ pf = free_page_frame_list_get();
+ if (pf == NULL) {
+ return -ENOMEM;
+ }
+ phys = z_page_frame_to_phys(pf);
+
+ ret = arch_mem_map(addr, phys, CONFIG_MMU_PAGE_SIZE,
+ flags | K_MEM_CACHE_WB);
+ if (ret != 0) {
+ free_page_frame_list_put(pf);
+ return -ENOMEM;
+ }
+ if (lock) {
+ pf->flags |= Z_PAGE_FRAME_PINNED;
+ }
+ frame_mapped_set(pf, addr);
+
+ return 0;
+}
+
+void *k_mem_map(size_t size, uint32_t flags)
+{;
+ uint8_t *dst;
+ size_t total_size = size;
+ int ret;
+ k_spinlock_key_t key;
+ bool uninit = (flags & K_MEM_MAP_UNINIT) != 0;
+ bool guard = (flags & K_MEM_MAP_GUARD) != 0;
+ uint8_t *pos;
+
+ __ASSERT(!(((flags & K_MEM_PERM_USER) != 0) && uninit),
+ "user access to anonymous uninitialized pages is forbidden");
+ __ASSERT(size % CONFIG_MMU_PAGE_SIZE == 0,
+ "unaligned size %zu passed to %s", size, __func__);
+ __ASSERT(size != 0, "zero sized memory mapping");
+ __ASSERT(page_frames_initialized, "%s called too early", __func__);
+ __ASSERT((flags & K_MEM_CACHE_MASK) == 0,
+ "%s does not support explicit cache settings", __func__);
+
+ key = k_spin_lock(&z_mm_lock);
+
+ if (guard) {
+ /* Need extra virtual page for the guard which we
+ * won't map
+ */
+ total_size += CONFIG_MMU_PAGE_SIZE;
+ }
+
+ dst = virt_region_get(total_size);
+ if (dst == NULL) {
+ /* Address space has no free region */
+ goto out;
+ }
+ if (guard) {
+ /* Skip over the guard page in returned address. */
+ dst += CONFIG_MMU_PAGE_SIZE;
+ }
+
+ VIRT_FOREACH(dst, size, pos) {
+ ret = map_anon_page(pos, flags);
+
+ if (ret != 0) {
+ /* TODO: call k_mem_unmap(dst, pos - dst) when
+ * implmented in #28990 and release any guard virtual
+ * page as well.
+ */
+ dst = NULL;
+ goto out;
+ }
+ }
+
+ if (!uninit) {
+ /* If we later implement mappings to a copy-on-write zero
+ * page, won't need this step
+ */
+ memset(dst, 0, size);
+ }
+out:
+ k_spin_unlock(&z_mm_lock, key);
+ return dst;
+}
/* This may be called from arch early boot code before z_cstart() is invoked.
* Data will be copied and BSS zeroed, but this must not rely on any