|  | /* | 
|  | * Copyright (c) 2020 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | * | 
|  | * Routines for managing virtual address spaces | 
|  | */ | 
|  |  | 
|  | #include <stdint.h> | 
|  | #include <kernel_arch_interface.h> | 
|  | #include <zephyr/spinlock.h> | 
|  | #include <mmu.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <kernel_internal.h> | 
|  | #include <zephyr/internal/syscall_handler.h> | 
|  | #include <zephyr/toolchain.h> | 
|  | #include <zephyr/linker/linker-defs.h> | 
|  | #include <zephyr/sys/bitarray.h> | 
|  | #include <zephyr/sys/check.h> | 
|  | #include <zephyr/sys/math_extras.h> | 
|  | #include <zephyr/timing/timing.h> | 
|  | #include <zephyr/arch/common/init.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL); | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | #include <zephyr/kernel/mm/demand_paging.h> | 
|  | #endif /* CONFIG_DEMAND_PAGING */ | 
|  |  | 
|  | /* | 
|  | * General terminology: | 
|  | * - A page frame is a page-sized physical memory region in RAM. It is a | 
|  | *   container where a data page may be placed. It is always referred to by | 
|  | *   physical address. We have a convention of using uintptr_t for physical | 
|  | *   addresses. We instantiate a struct k_mem_page_frame to store metadata for | 
|  | *   every page frame. | 
|  | * | 
|  | * - A data page is a page-sized region of data. It may exist in a page frame, | 
|  | *   or be paged out to some backing store. Its location can always be looked | 
|  | *   up in the CPU's page tables (or equivalent) by virtual address. | 
|  | *   The data type will always be void * or in some cases uint8_t * when we | 
|  | *   want to do pointer arithmetic. | 
|  | */ | 
|  |  | 
|  | /* Spinlock to protect any globals in this file and serialize page table | 
|  | * updates in arch code | 
|  | */ | 
|  | struct k_spinlock z_mm_lock; | 
|  |  | 
|  | /* | 
|  | * General page frame management | 
|  | */ | 
|  |  | 
|  | /* Database of all RAM page frames */ | 
|  | struct k_mem_page_frame k_mem_page_frames[K_MEM_NUM_PAGE_FRAMES]; | 
|  |  | 
|  | #if __ASSERT_ON | 
|  | /* Indicator that k_mem_page_frames has been initialized, many of these APIs do | 
|  | * not work before POST_KERNEL | 
|  | */ | 
|  | static bool page_frames_initialized; | 
|  | #endif | 
|  |  | 
|  | /* Add colors to page table dumps to indicate mapping type */ | 
|  | #define COLOR_PAGE_FRAMES	1 | 
|  |  | 
|  | #if COLOR_PAGE_FRAMES | 
|  | #define ANSI_DEFAULT "\x1B" "[0m" | 
|  | #define ANSI_RED     "\x1B" "[1;31m" | 
|  | #define ANSI_GREEN   "\x1B" "[1;32m" | 
|  | #define ANSI_YELLOW  "\x1B" "[1;33m" | 
|  | #define ANSI_BLUE    "\x1B" "[1;34m" | 
|  | #define ANSI_MAGENTA "\x1B" "[1;35m" | 
|  | #define ANSI_CYAN    "\x1B" "[1;36m" | 
|  | #define ANSI_GREY    "\x1B" "[1;90m" | 
|  |  | 
|  | #define COLOR(x)	printk(_CONCAT(ANSI_, x)) | 
|  | #else | 
|  | #define COLOR(x)	do { } while (false) | 
|  | #endif /* COLOR_PAGE_FRAMES */ | 
|  |  | 
|  | /* LCOV_EXCL_START */ | 
|  | static void page_frame_dump(struct k_mem_page_frame *pf) | 
|  | { | 
|  | if (k_mem_page_frame_is_free(pf)) { | 
|  | COLOR(GREY); | 
|  | printk("-"); | 
|  | } else if (k_mem_page_frame_is_reserved(pf)) { | 
|  | COLOR(CYAN); | 
|  | printk("R"); | 
|  | } else if (k_mem_page_frame_is_busy(pf)) { | 
|  | COLOR(MAGENTA); | 
|  | printk("B"); | 
|  | } else if (k_mem_page_frame_is_pinned(pf)) { | 
|  | COLOR(YELLOW); | 
|  | printk("P"); | 
|  | } else if (k_mem_page_frame_is_available(pf)) { | 
|  | COLOR(GREY); | 
|  | printk("."); | 
|  | } else if (k_mem_page_frame_is_mapped(pf)) { | 
|  | COLOR(DEFAULT); | 
|  | printk("M"); | 
|  | } else { | 
|  | COLOR(RED); | 
|  | printk("?"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void k_mem_page_frames_dump(void) | 
|  | { | 
|  | int column = 0; | 
|  |  | 
|  | __ASSERT(page_frames_initialized, "%s called too early", __func__); | 
|  | printk("Physical memory from 0x%lx to 0x%lx\n", | 
|  | K_MEM_PHYS_RAM_START, K_MEM_PHYS_RAM_END); | 
|  |  | 
|  | for (int i = 0; i < K_MEM_NUM_PAGE_FRAMES; i++) { | 
|  | struct k_mem_page_frame *pf = &k_mem_page_frames[i]; | 
|  |  | 
|  | page_frame_dump(pf); | 
|  |  | 
|  | column++; | 
|  | if (column == 64) { | 
|  | column = 0; | 
|  | printk("\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | COLOR(DEFAULT); | 
|  | if (column != 0) { | 
|  | printk("\n"); | 
|  | } | 
|  | } | 
|  | /* LCOV_EXCL_STOP */ | 
|  |  | 
|  | #define VIRT_FOREACH(_base, _size, _pos) \ | 
|  | for ((_pos) = (_base); \ | 
|  | (_pos) < ((uint8_t *)(_base) + (_size)); (_pos) += CONFIG_MMU_PAGE_SIZE) | 
|  |  | 
|  | #define PHYS_FOREACH(_base, _size, _pos) \ | 
|  | for ((_pos) = (_base); \ | 
|  | (_pos) < ((uintptr_t)(_base) + (_size)); (_pos) += CONFIG_MMU_PAGE_SIZE) | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Virtual address space management | 
|  | * | 
|  | * Call all of these functions with z_mm_lock held. | 
|  | * | 
|  | * Overall virtual memory map: When the kernel starts, it resides in | 
|  | * virtual memory in the region K_MEM_KERNEL_VIRT_START to | 
|  | * K_MEM_KERNEL_VIRT_END. Unused virtual memory past this, up to the limit | 
|  | * noted by CONFIG_KERNEL_VM_SIZE may be used for runtime memory mappings. | 
|  | * | 
|  | * If CONFIG_ARCH_MAPS_ALL_RAM is set, we do not just map the kernel image, | 
|  | * but have a mapping for all RAM in place. This is for special architectural | 
|  | * purposes and does not otherwise affect page frame accounting or flags; | 
|  | * the only guarantee is that such RAM mapping outside of the Zephyr image | 
|  | * won't be disturbed by subsequent memory mapping calls. | 
|  | * | 
|  | * +--------------+ <- K_MEM_VIRT_RAM_START | 
|  | * | Undefined VM | <- May contain ancillary regions like x86_64's locore | 
|  | * +--------------+ <- K_MEM_KERNEL_VIRT_START (often == K_MEM_VIRT_RAM_START) | 
|  | * | Mapping for  | | 
|  | * | main kernel  | | 
|  | * | image        | | 
|  | * |		  | | 
|  | * |		  | | 
|  | * +--------------+ <- K_MEM_VM_FREE_START | 
|  | * |              | | 
|  | * | Unused,      | | 
|  | * | Available VM | | 
|  | * |              | | 
|  | * |..............| <- mapping_pos (grows downward as more mappings are made) | 
|  | * | Mapping      | | 
|  | * +--------------+ | 
|  | * | Mapping      | | 
|  | * +--------------+ | 
|  | * | ...          | | 
|  | * +--------------+ | 
|  | * | Mapping      | | 
|  | * +--------------+ <- mappings start here | 
|  | * | Reserved     | <- special purpose virtual page(s) of size K_MEM_VM_RESERVED | 
|  | * +--------------+ <- K_MEM_VIRT_RAM_END | 
|  | */ | 
|  |  | 
|  | /* Bitmap of virtual addresses where one bit corresponds to one page. | 
|  | * This is being used for virt_region_alloc() to figure out which | 
|  | * region of virtual addresses can be used for memory mapping. | 
|  | * | 
|  | * Note that bit #0 is the highest address so that allocation is | 
|  | * done in reverse from highest address. | 
|  | */ | 
|  | SYS_BITARRAY_DEFINE_STATIC(virt_region_bitmap, | 
|  | CONFIG_KERNEL_VM_SIZE / CONFIG_MMU_PAGE_SIZE); | 
|  |  | 
|  | static bool virt_region_inited; | 
|  |  | 
|  | #define Z_VIRT_REGION_START_ADDR	K_MEM_VM_FREE_START | 
|  | #define Z_VIRT_REGION_END_ADDR		(K_MEM_VIRT_RAM_END - K_MEM_VM_RESERVED) | 
|  |  | 
|  | static inline uintptr_t virt_from_bitmap_offset(size_t offset, size_t size) | 
|  | { | 
|  | return POINTER_TO_UINT(K_MEM_VIRT_RAM_END) | 
|  | - (offset * CONFIG_MMU_PAGE_SIZE) - size; | 
|  | } | 
|  |  | 
|  | static inline size_t virt_to_bitmap_offset(void *vaddr, size_t size) | 
|  | { | 
|  | return (POINTER_TO_UINT(K_MEM_VIRT_RAM_END) | 
|  | - POINTER_TO_UINT(vaddr) - size) / CONFIG_MMU_PAGE_SIZE; | 
|  | } | 
|  |  | 
|  | static void virt_region_init(void) | 
|  | { | 
|  | size_t offset, num_bits; | 
|  |  | 
|  | /* There are regions where we should never map via | 
|  | * k_mem_map() and k_mem_map_phys_bare(). Mark them as | 
|  | * already allocated so they will never be used. | 
|  | */ | 
|  |  | 
|  | if (K_MEM_VM_RESERVED > 0) { | 
|  | /* Mark reserved region at end of virtual address space */ | 
|  | num_bits = K_MEM_VM_RESERVED / CONFIG_MMU_PAGE_SIZE; | 
|  | (void)sys_bitarray_set_region(&virt_region_bitmap, | 
|  | num_bits, 0); | 
|  | } | 
|  |  | 
|  | /* Mark all bits up to Z_FREE_VM_START as allocated */ | 
|  | num_bits = POINTER_TO_UINT(K_MEM_VM_FREE_START) | 
|  | - POINTER_TO_UINT(K_MEM_VIRT_RAM_START); | 
|  | offset = virt_to_bitmap_offset(K_MEM_VIRT_RAM_START, num_bits); | 
|  | num_bits /= CONFIG_MMU_PAGE_SIZE; | 
|  | (void)sys_bitarray_set_region(&virt_region_bitmap, | 
|  | num_bits, offset); | 
|  |  | 
|  | virt_region_inited = true; | 
|  | } | 
|  |  | 
|  | static void virt_region_free(void *vaddr, size_t size) | 
|  | { | 
|  | size_t offset, num_bits; | 
|  | uint8_t *vaddr_u8 = (uint8_t *)vaddr; | 
|  |  | 
|  | if (unlikely(!virt_region_inited)) { | 
|  | virt_region_init(); | 
|  | } | 
|  |  | 
|  | #ifndef CONFIG_KERNEL_DIRECT_MAP | 
|  | /* Without the need to support K_MEM_DIRECT_MAP, the region must be | 
|  | * able to be represented in the bitmap. So this case is | 
|  | * simple. | 
|  | */ | 
|  |  | 
|  | __ASSERT((vaddr_u8 >= Z_VIRT_REGION_START_ADDR) | 
|  | && ((vaddr_u8 + size - 1) < Z_VIRT_REGION_END_ADDR), | 
|  | "invalid virtual address region %p (%zu)", vaddr_u8, size); | 
|  | if (!((vaddr_u8 >= Z_VIRT_REGION_START_ADDR) | 
|  | && ((vaddr_u8 + size - 1) < Z_VIRT_REGION_END_ADDR))) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | offset = virt_to_bitmap_offset(vaddr, size); | 
|  | num_bits = size / CONFIG_MMU_PAGE_SIZE; | 
|  | (void)sys_bitarray_free(&virt_region_bitmap, num_bits, offset); | 
|  | #else /* !CONFIG_KERNEL_DIRECT_MAP */ | 
|  | /* With K_MEM_DIRECT_MAP, the region can be outside of the virtual | 
|  | * memory space, wholly within it, or overlap partially. | 
|  | * So additional processing is needed to make sure we only | 
|  | * mark the pages within the bitmap. | 
|  | */ | 
|  | if (((vaddr_u8 >= Z_VIRT_REGION_START_ADDR) && | 
|  | (vaddr_u8 < Z_VIRT_REGION_END_ADDR)) || | 
|  | (((vaddr_u8 + size - 1) >= Z_VIRT_REGION_START_ADDR) && | 
|  | ((vaddr_u8 + size - 1) < Z_VIRT_REGION_END_ADDR))) { | 
|  | uint8_t *adjusted_start = MAX(vaddr_u8, Z_VIRT_REGION_START_ADDR); | 
|  | uint8_t *adjusted_end = MIN(vaddr_u8 + size, | 
|  | Z_VIRT_REGION_END_ADDR); | 
|  | size_t adjusted_sz = adjusted_end - adjusted_start; | 
|  |  | 
|  | offset = virt_to_bitmap_offset(adjusted_start, adjusted_sz); | 
|  | num_bits = adjusted_sz / CONFIG_MMU_PAGE_SIZE; | 
|  | (void)sys_bitarray_free(&virt_region_bitmap, num_bits, offset); | 
|  | } | 
|  | #endif /* !CONFIG_KERNEL_DIRECT_MAP */ | 
|  | } | 
|  |  | 
|  | static void *virt_region_alloc(size_t size, size_t align) | 
|  | { | 
|  | uintptr_t dest_addr; | 
|  | size_t alloc_size; | 
|  | size_t offset; | 
|  | size_t num_bits; | 
|  | int ret; | 
|  |  | 
|  | if (unlikely(!virt_region_inited)) { | 
|  | virt_region_init(); | 
|  | } | 
|  |  | 
|  | /* Possibly request more pages to ensure we can get an aligned virtual address */ | 
|  | num_bits = (size + align - CONFIG_MMU_PAGE_SIZE) / CONFIG_MMU_PAGE_SIZE; | 
|  | alloc_size = num_bits * CONFIG_MMU_PAGE_SIZE; | 
|  | ret = sys_bitarray_alloc(&virt_region_bitmap, num_bits, &offset); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("insufficient virtual address space (requested %zu)", | 
|  | size); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* Remember that bit #0 in bitmap corresponds to the highest | 
|  | * virtual address. So here we need to go downwards (backwards?) | 
|  | * to get the starting address of the allocated region. | 
|  | */ | 
|  | dest_addr = virt_from_bitmap_offset(offset, alloc_size); | 
|  |  | 
|  | if (alloc_size > size) { | 
|  | uintptr_t aligned_dest_addr = ROUND_UP(dest_addr, align); | 
|  |  | 
|  | /* Here is the memory organization when trying to get an aligned | 
|  | * virtual address: | 
|  | * | 
|  | * +--------------+ <- K_MEM_VIRT_RAM_START | 
|  | * | Undefined VM | | 
|  | * +--------------+ <- K_MEM_KERNEL_VIRT_START (often == K_MEM_VIRT_RAM_START) | 
|  | * | Mapping for  | | 
|  | * | main kernel  | | 
|  | * | image        | | 
|  | * |		  | | 
|  | * |		  | | 
|  | * +--------------+ <- K_MEM_VM_FREE_START | 
|  | * | ...          | | 
|  | * +==============+ <- dest_addr | 
|  | * | Unused       | | 
|  | * |..............| <- aligned_dest_addr | 
|  | * |              | | 
|  | * | Aligned      | | 
|  | * | Mapping      | | 
|  | * |              | | 
|  | * |..............| <- aligned_dest_addr + size | 
|  | * | Unused       | | 
|  | * +==============+ <- offset from K_MEM_VIRT_RAM_END == dest_addr + alloc_size | 
|  | * | ...          | | 
|  | * +--------------+ | 
|  | * | Mapping      | | 
|  | * +--------------+ | 
|  | * | Reserved     | | 
|  | * +--------------+ <- K_MEM_VIRT_RAM_END | 
|  | */ | 
|  |  | 
|  | /* Free the two unused regions */ | 
|  | virt_region_free(UINT_TO_POINTER(dest_addr), | 
|  | aligned_dest_addr - dest_addr); | 
|  | if (((dest_addr + alloc_size) - (aligned_dest_addr + size)) > 0) { | 
|  | virt_region_free(UINT_TO_POINTER(aligned_dest_addr + size), | 
|  | (dest_addr + alloc_size) - (aligned_dest_addr + size)); | 
|  | } | 
|  |  | 
|  | dest_addr = aligned_dest_addr; | 
|  | } | 
|  |  | 
|  | /* Need to make sure this does not step into kernel memory */ | 
|  | if (dest_addr < POINTER_TO_UINT(Z_VIRT_REGION_START_ADDR)) { | 
|  | (void)sys_bitarray_free(&virt_region_bitmap, num_bits, offset); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return UINT_TO_POINTER(dest_addr); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Free page frames management | 
|  | * | 
|  | * Call all of these functions with z_mm_lock held. | 
|  | */ | 
|  |  | 
|  | /* Linked list of unused and available page frames. | 
|  | * | 
|  | * TODO: This is very simple and treats all free page frames as being equal. | 
|  | * However, there are use-cases to consolidate free pages such that entire | 
|  | * SRAM banks can be switched off to save power, and so obtaining free pages | 
|  | * may require a more complex ontology which prefers page frames in RAM banks | 
|  | * which are still active. | 
|  | * | 
|  | * This implies in the future there may be multiple slists managing physical | 
|  | * pages. Each page frame will still just have one snode link. | 
|  | */ | 
|  | static sys_sflist_t free_page_frame_list; | 
|  |  | 
|  | /* Number of unused and available free page frames. | 
|  | * This information may go stale immediately. | 
|  | */ | 
|  | static size_t z_free_page_count; | 
|  |  | 
|  | #define PF_ASSERT(pf, expr, fmt, ...) \ | 
|  | __ASSERT(expr, "page frame 0x%lx: " fmt, k_mem_page_frame_to_phys(pf), \ | 
|  | ##__VA_ARGS__) | 
|  |  | 
|  | /* Get an unused page frame. don't care which one, or NULL if there are none */ | 
|  | static struct k_mem_page_frame *free_page_frame_list_get(void) | 
|  | { | 
|  | sys_sfnode_t *node; | 
|  | struct k_mem_page_frame *pf = NULL; | 
|  |  | 
|  | node = sys_sflist_get(&free_page_frame_list); | 
|  | if (node != NULL) { | 
|  | z_free_page_count--; | 
|  | pf = CONTAINER_OF(node, struct k_mem_page_frame, node); | 
|  | PF_ASSERT(pf, k_mem_page_frame_is_free(pf), | 
|  | "on free list but not free"); | 
|  | pf->va_and_flags = 0; | 
|  | } | 
|  |  | 
|  | return pf; | 
|  | } | 
|  |  | 
|  | /* Release a page frame back into the list of free pages */ | 
|  | static void free_page_frame_list_put(struct k_mem_page_frame *pf) | 
|  | { | 
|  | PF_ASSERT(pf, k_mem_page_frame_is_available(pf), | 
|  | "unavailable page put on free list"); | 
|  |  | 
|  | sys_sfnode_init(&pf->node, K_MEM_PAGE_FRAME_FREE); | 
|  | sys_sflist_append(&free_page_frame_list, &pf->node); | 
|  | z_free_page_count++; | 
|  | } | 
|  |  | 
|  | static void free_page_frame_list_init(void) | 
|  | { | 
|  | sys_sflist_init(&free_page_frame_list); | 
|  | } | 
|  |  | 
|  | static void page_frame_free_locked(struct k_mem_page_frame *pf) | 
|  | { | 
|  | pf->va_and_flags = 0; | 
|  | free_page_frame_list_put(pf); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Memory Mapping | 
|  | */ | 
|  |  | 
|  | /* Called after the frame is mapped in the arch layer, to update our | 
|  | * local ontology (and do some assertions while we're at it) | 
|  | */ | 
|  | static void frame_mapped_set(struct k_mem_page_frame *pf, void *addr) | 
|  | { | 
|  | PF_ASSERT(pf, !k_mem_page_frame_is_free(pf), | 
|  | "attempted to map a page frame on the free list"); | 
|  | PF_ASSERT(pf, !k_mem_page_frame_is_reserved(pf), | 
|  | "attempted to map a reserved page frame"); | 
|  |  | 
|  | /* We do allow multiple mappings for pinned page frames | 
|  | * since we will never need to reverse map them. | 
|  | * This is uncommon, use-cases are for things like the | 
|  | * Zephyr equivalent of VSDOs | 
|  | */ | 
|  | PF_ASSERT(pf, !k_mem_page_frame_is_mapped(pf) || k_mem_page_frame_is_pinned(pf), | 
|  | "non-pinned and already mapped to %p", | 
|  | k_mem_page_frame_to_virt(pf)); | 
|  |  | 
|  | uintptr_t flags_mask = CONFIG_MMU_PAGE_SIZE - 1; | 
|  | uintptr_t va = (uintptr_t)addr & ~flags_mask; | 
|  |  | 
|  | pf->va_and_flags &= flags_mask; | 
|  | pf->va_and_flags |= va | K_MEM_PAGE_FRAME_MAPPED; | 
|  | } | 
|  |  | 
|  | /* LCOV_EXCL_START */ | 
|  | /* Go through page frames to find the physical address mapped | 
|  | * by a virtual address. | 
|  | * | 
|  | * @param[in]  virt Virtual Address | 
|  | * @param[out] phys Physical address mapped to the input virtual address | 
|  | *                  if such mapping exists. | 
|  | * | 
|  | * @retval 0 if mapping is found and valid | 
|  | * @retval -EFAULT if virtual address is not mapped | 
|  | */ | 
|  | static int virt_to_page_frame(void *virt, uintptr_t *phys) | 
|  | { | 
|  | uintptr_t paddr; | 
|  | struct k_mem_page_frame *pf; | 
|  | int ret = -EFAULT; | 
|  |  | 
|  | K_MEM_PAGE_FRAME_FOREACH(paddr, pf) { | 
|  | if (k_mem_page_frame_is_mapped(pf)) { | 
|  | if (virt == k_mem_page_frame_to_virt(pf)) { | 
|  | ret = 0; | 
|  | if (phys != NULL) { | 
|  | *phys = k_mem_page_frame_to_phys(pf); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  | /* LCOV_EXCL_STOP */ | 
|  |  | 
|  | __weak FUNC_ALIAS(virt_to_page_frame, arch_page_phys_get, int); | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | static int page_frame_prepare_locked(struct k_mem_page_frame *pf, bool *dirty_ptr, | 
|  | bool page_in, uintptr_t *location_ptr); | 
|  |  | 
|  | static inline void do_backing_store_page_in(uintptr_t location); | 
|  | static inline void do_backing_store_page_out(uintptr_t location); | 
|  | #endif /* CONFIG_DEMAND_PAGING */ | 
|  |  | 
|  | /* 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. This will avoid expensive | 
|  | * page-ins as memory is mapped and physical RAM or backing store storage will | 
|  | * not be used if the mapped memory is unused. The cost is an empty physical | 
|  | * page of zeroes. | 
|  | */ | 
|  | static int map_anon_page(void *addr, uint32_t flags) | 
|  | { | 
|  | struct k_mem_page_frame *pf; | 
|  | uintptr_t phys; | 
|  | bool lock = (flags & K_MEM_MAP_LOCK) != 0U; | 
|  |  | 
|  | pf = free_page_frame_list_get(); | 
|  | if (pf == NULL) { | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | uintptr_t location; | 
|  | bool dirty; | 
|  | int ret; | 
|  |  | 
|  | pf = k_mem_paging_eviction_select(&dirty); | 
|  | __ASSERT(pf != NULL, "failed to get a page frame"); | 
|  | LOG_DBG("evicting %p at 0x%lx", | 
|  | k_mem_page_frame_to_virt(pf), | 
|  | k_mem_page_frame_to_phys(pf)); | 
|  | ret = page_frame_prepare_locked(pf, &dirty, false, &location); | 
|  | if (ret != 0) { | 
|  | return -ENOMEM; | 
|  | } | 
|  | if (dirty) { | 
|  | do_backing_store_page_out(location); | 
|  | } | 
|  | pf->va_and_flags = 0; | 
|  | #else | 
|  | return -ENOMEM; | 
|  | #endif /* CONFIG_DEMAND_PAGING */ | 
|  | } | 
|  |  | 
|  | phys = k_mem_page_frame_to_phys(pf); | 
|  | arch_mem_map(addr, phys, CONFIG_MMU_PAGE_SIZE, flags); | 
|  |  | 
|  | if (lock) { | 
|  | k_mem_page_frame_set(pf, K_MEM_PAGE_FRAME_PINNED); | 
|  | } | 
|  | frame_mapped_set(pf, addr); | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | if (IS_ENABLED(CONFIG_EVICTION_TRACKING) && (!lock)) { | 
|  | k_mem_paging_eviction_add(pf); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | LOG_DBG("memory mapping anon page %p -> 0x%lx", addr, phys); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void *k_mem_map_phys_guard(uintptr_t phys, size_t size, uint32_t flags, bool is_anon) | 
|  | { | 
|  | uint8_t *dst; | 
|  | size_t total_size; | 
|  | int ret; | 
|  | k_spinlock_key_t key; | 
|  | uint8_t *pos; | 
|  | bool uninit = (flags & K_MEM_MAP_UNINIT) != 0U; | 
|  |  | 
|  | __ASSERT(!is_anon || (is_anon && page_frames_initialized), | 
|  | "%s called too early", __func__); | 
|  | __ASSERT((flags & K_MEM_CACHE_MASK) == 0U, | 
|  | "%s does not support explicit cache settings", __func__); | 
|  |  | 
|  | if (((flags & K_MEM_PERM_USER) != 0U) && | 
|  | ((flags & K_MEM_MAP_UNINIT) != 0U)) { | 
|  | LOG_ERR("user access to anonymous uninitialized pages is forbidden"); | 
|  | return NULL; | 
|  | } | 
|  | if ((size % CONFIG_MMU_PAGE_SIZE) != 0U) { | 
|  | LOG_ERR("unaligned size %zu passed to %s", size, __func__); | 
|  | return NULL; | 
|  | } | 
|  | if (size == 0) { | 
|  | LOG_ERR("zero sized memory mapping"); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* Need extra for the guard pages (before and after) which we | 
|  | * won't map. | 
|  | */ | 
|  | if (size_add_overflow(size, CONFIG_MMU_PAGE_SIZE * 2, &total_size)) { | 
|  | LOG_ERR("too large size %zu passed to %s", size, __func__); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  |  | 
|  | dst = virt_region_alloc(total_size, CONFIG_MMU_PAGE_SIZE); | 
|  | if (dst == NULL) { | 
|  | /* Address space has no free region */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Unmap both guard pages to make sure accessing them | 
|  | * will generate fault. | 
|  | */ | 
|  | arch_mem_unmap(dst, CONFIG_MMU_PAGE_SIZE); | 
|  | arch_mem_unmap(dst + CONFIG_MMU_PAGE_SIZE + size, | 
|  | CONFIG_MMU_PAGE_SIZE); | 
|  |  | 
|  | /* Skip over the "before" guard page in returned address. */ | 
|  | dst += CONFIG_MMU_PAGE_SIZE; | 
|  |  | 
|  | if (is_anon) { | 
|  | /* Mapping from anonymous memory */ | 
|  | flags |= K_MEM_CACHE_WB; | 
|  | #ifdef CONFIG_DEMAND_MAPPING | 
|  | if ((flags & K_MEM_MAP_LOCK) == 0) { | 
|  | flags |= K_MEM_MAP_UNPAGED; | 
|  | VIRT_FOREACH(dst, size, pos) { | 
|  | arch_mem_map(pos, | 
|  | uninit ? ARCH_UNPAGED_ANON_UNINIT | 
|  | : ARCH_UNPAGED_ANON_ZERO, | 
|  | CONFIG_MMU_PAGE_SIZE, flags); | 
|  | } | 
|  | LOG_DBG("memory mapping anon pages %p to %p unpaged", dst, pos-1); | 
|  | /* skip the memset() below */ | 
|  | uninit = true; | 
|  | } else | 
|  | #endif | 
|  | { | 
|  | VIRT_FOREACH(dst, size, pos) { | 
|  | ret = map_anon_page(pos, flags); | 
|  |  | 
|  | if (ret != 0) { | 
|  | /* TODO: | 
|  | * call k_mem_unmap(dst, pos - dst) | 
|  | * when implemented in #28990 and | 
|  | * release any guard virtual page as well. | 
|  | */ | 
|  | dst = NULL; | 
|  | goto out; | 
|  | } | 
|  | } | 
|  | } | 
|  | } else { | 
|  | /* Mapping known physical memory. | 
|  | * | 
|  | * arch_mem_map() is a void function and does not return | 
|  | * anything. Arch code usually uses ASSERT() to catch | 
|  | * mapping errors. Assume this works correctly for now. | 
|  | */ | 
|  | arch_mem_map(dst, phys, size, flags); | 
|  | } | 
|  |  | 
|  | out: | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  |  | 
|  | if (dst != NULL && !uninit) { | 
|  | /* If we later implement mappings to a copy-on-write | 
|  | * zero page, won't need this step | 
|  | */ | 
|  | memset(dst, 0, size); | 
|  | } | 
|  |  | 
|  | return dst; | 
|  | } | 
|  |  | 
|  | void k_mem_unmap_phys_guard(void *addr, size_t size, bool is_anon) | 
|  | { | 
|  | uintptr_t phys; | 
|  | uint8_t *pos; | 
|  | struct k_mem_page_frame *pf; | 
|  | k_spinlock_key_t key; | 
|  | size_t total_size; | 
|  | int ret; | 
|  |  | 
|  | /* Need space for the "before" guard page */ | 
|  | __ASSERT_NO_MSG(POINTER_TO_UINT(addr) >= CONFIG_MMU_PAGE_SIZE); | 
|  |  | 
|  | /* Make sure address range is still valid after accounting | 
|  | * for two guard pages. | 
|  | */ | 
|  | pos = (uint8_t *)addr - CONFIG_MMU_PAGE_SIZE; | 
|  | k_mem_assert_virtual_region(pos, size + (CONFIG_MMU_PAGE_SIZE * 2)); | 
|  |  | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  |  | 
|  | /* Check if both guard pages are unmapped. | 
|  | * Bail if not, as this is probably a region not mapped | 
|  | * using k_mem_map(). | 
|  | */ | 
|  | pos = addr; | 
|  | ret = arch_page_phys_get(pos - CONFIG_MMU_PAGE_SIZE, NULL); | 
|  | if (ret == 0) { | 
|  | __ASSERT(ret == 0, | 
|  | "%s: cannot find preceding guard page for (%p, %zu)", | 
|  | __func__, addr, size); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ret = arch_page_phys_get(pos + size, NULL); | 
|  | if (ret == 0) { | 
|  | __ASSERT(ret == 0, | 
|  | "%s: cannot find succeeding guard page for (%p, %zu)", | 
|  | __func__, addr, size); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (is_anon) { | 
|  | /* Unmapping anonymous memory */ | 
|  | VIRT_FOREACH(addr, size, pos) { | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | enum arch_page_location status; | 
|  | uintptr_t location; | 
|  |  | 
|  | status = arch_page_location_get(pos, &location); | 
|  | switch (status) { | 
|  | case ARCH_PAGE_LOCATION_PAGED_OUT: | 
|  | /* | 
|  | * No pf is associated with this mapping. | 
|  | * Simply get rid of the MMU entry and free | 
|  | * corresponding backing store. | 
|  | */ | 
|  | arch_mem_unmap(pos, CONFIG_MMU_PAGE_SIZE); | 
|  | k_mem_paging_backing_store_location_free(location); | 
|  | continue; | 
|  | case ARCH_PAGE_LOCATION_PAGED_IN: | 
|  | /* | 
|  | * The page is in memory but it may not be | 
|  | * accessible in order to manage tracking | 
|  | * of the ARCH_DATA_PAGE_ACCESSED flag | 
|  | * meaning arch_page_phys_get() could fail. | 
|  | * Still, we know the actual phys address. | 
|  | */ | 
|  | phys = location; | 
|  | ret = 0; | 
|  | break; | 
|  | default: | 
|  | ret = arch_page_phys_get(pos, &phys); | 
|  | break; | 
|  | } | 
|  | #else | 
|  | ret = arch_page_phys_get(pos, &phys); | 
|  | #endif | 
|  | __ASSERT(ret == 0, | 
|  | "%s: cannot unmap an unmapped address %p", | 
|  | __func__, pos); | 
|  | if (ret != 0) { | 
|  | /* Found an address not mapped. Do not continue. */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | __ASSERT(k_mem_is_page_frame(phys), | 
|  | "%s: 0x%lx is not a page frame", __func__, phys); | 
|  | if (!k_mem_is_page_frame(phys)) { | 
|  | /* Physical address has no corresponding page frame | 
|  | * description in the page frame array. | 
|  | * This should not happen. Do not continue. | 
|  | */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Grab the corresponding page frame from physical address */ | 
|  | pf = k_mem_phys_to_page_frame(phys); | 
|  |  | 
|  | __ASSERT(k_mem_page_frame_is_mapped(pf), | 
|  | "%s: 0x%lx is not a mapped page frame", __func__, phys); | 
|  | if (!k_mem_page_frame_is_mapped(pf)) { | 
|  | /* Page frame is not marked mapped. | 
|  | * This should not happen. Do not continue. | 
|  | */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | arch_mem_unmap(pos, CONFIG_MMU_PAGE_SIZE); | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | if (IS_ENABLED(CONFIG_EVICTION_TRACKING) && | 
|  | (!k_mem_page_frame_is_pinned(pf))) { | 
|  | k_mem_paging_eviction_remove(pf); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Put the page frame back into free list */ | 
|  | page_frame_free_locked(pf); | 
|  | } | 
|  | } else { | 
|  | /* | 
|  | * Unmapping previous mapped memory with specific physical address. | 
|  | * | 
|  | * Note that we don't have to unmap the guard pages, as they should | 
|  | * have been unmapped. We just need to unmapped the in-between | 
|  | * region [addr, (addr + size)). | 
|  | */ | 
|  | arch_mem_unmap(addr, size); | 
|  | } | 
|  |  | 
|  | /* There are guard pages just before and after the mapped | 
|  | * region. So we also need to free them from the bitmap. | 
|  | */ | 
|  | pos = (uint8_t *)addr - CONFIG_MMU_PAGE_SIZE; | 
|  | total_size = size + (CONFIG_MMU_PAGE_SIZE * 2); | 
|  | virt_region_free(pos, total_size); | 
|  |  | 
|  | out: | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | } | 
|  |  | 
|  | int k_mem_update_flags(void *addr, size_t size, uint32_t flags) | 
|  | { | 
|  | uintptr_t phys; | 
|  | k_spinlock_key_t key; | 
|  | int ret; | 
|  |  | 
|  | k_mem_assert_virtual_region(addr, size); | 
|  |  | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  |  | 
|  | /* | 
|  | * We can achieve desired result without explicit architecture support | 
|  | * by unmapping and remapping the same physical memory using new flags. | 
|  | */ | 
|  |  | 
|  | ret = arch_page_phys_get(addr, &phys); | 
|  | if (ret < 0) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* TODO: detect and handle paged-out memory as well */ | 
|  |  | 
|  | arch_mem_unmap(addr, size); | 
|  | arch_mem_map(addr, phys, size, flags); | 
|  |  | 
|  | out: | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | size_t k_mem_free_get(void) | 
|  | { | 
|  | size_t ret; | 
|  | k_spinlock_key_t key; | 
|  |  | 
|  | __ASSERT(page_frames_initialized, "%s called too early", __func__); | 
|  |  | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | if (z_free_page_count > CONFIG_DEMAND_PAGING_PAGE_FRAMES_RESERVE) { | 
|  | ret = z_free_page_count - CONFIG_DEMAND_PAGING_PAGE_FRAMES_RESERVE; | 
|  | } else { | 
|  | ret = 0; | 
|  | } | 
|  | #else | 
|  | ret = z_free_page_count; | 
|  | #endif /* CONFIG_DEMAND_PAGING */ | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  |  | 
|  | return ret * (size_t)CONFIG_MMU_PAGE_SIZE; | 
|  | } | 
|  |  | 
|  | /* Get the default virtual region alignment, here the default MMU page size | 
|  | * | 
|  | * @param[in] phys Physical address of region to be mapped, aligned to MMU_PAGE_SIZE | 
|  | * @param[in] size Size of region to be mapped, aligned to MMU_PAGE_SIZE | 
|  | * | 
|  | * @retval alignment to apply on the virtual address of this region | 
|  | */ | 
|  | static size_t virt_region_align(uintptr_t phys, size_t size) | 
|  | { | 
|  | ARG_UNUSED(phys); | 
|  | ARG_UNUSED(size); | 
|  |  | 
|  | return CONFIG_MMU_PAGE_SIZE; | 
|  | } | 
|  |  | 
|  | __weak FUNC_ALIAS(virt_region_align, arch_virt_region_align, size_t); | 
|  |  | 
|  | /* 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 | 
|  | * initialization functions being called prior to work correctly. | 
|  | */ | 
|  | void k_mem_map_phys_bare(uint8_t **virt_ptr, uintptr_t phys, size_t size, uint32_t flags) | 
|  | { | 
|  | uintptr_t aligned_phys, addr_offset; | 
|  | size_t aligned_size, align_boundary; | 
|  | k_spinlock_key_t key; | 
|  | uint8_t *dest_addr; | 
|  | size_t num_bits; | 
|  | size_t offset; | 
|  |  | 
|  | #ifndef CONFIG_KERNEL_DIRECT_MAP | 
|  | __ASSERT(!(flags & K_MEM_DIRECT_MAP), "The direct-map is not enabled"); | 
|  | #endif /* CONFIG_KERNEL_DIRECT_MAP */ | 
|  | addr_offset = k_mem_region_align(&aligned_phys, &aligned_size, | 
|  | phys, size, | 
|  | CONFIG_MMU_PAGE_SIZE); | 
|  | __ASSERT(aligned_size != 0U, "0-length mapping at 0x%lx", aligned_phys); | 
|  | __ASSERT(aligned_phys < (aligned_phys + (aligned_size - 1)), | 
|  | "wraparound for physical address 0x%lx (size %zu)", | 
|  | aligned_phys, aligned_size); | 
|  |  | 
|  | align_boundary = arch_virt_region_align(aligned_phys, aligned_size); | 
|  |  | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_KERNEL_DIRECT_MAP) && | 
|  | (flags & K_MEM_DIRECT_MAP)) { | 
|  | dest_addr = (uint8_t *)aligned_phys; | 
|  |  | 
|  | /* Mark the region of virtual memory bitmap as used | 
|  | * if the region overlaps the virtual memory space. | 
|  | * | 
|  | * Basically if either end of region is within | 
|  | * virtual memory space, we need to mark the bits. | 
|  | */ | 
|  |  | 
|  | if (IN_RANGE(aligned_phys, | 
|  | (uintptr_t)K_MEM_VIRT_RAM_START, | 
|  | (uintptr_t)(K_MEM_VIRT_RAM_END - 1)) || | 
|  | IN_RANGE(aligned_phys + aligned_size - 1, | 
|  | (uintptr_t)K_MEM_VIRT_RAM_START, | 
|  | (uintptr_t)(K_MEM_VIRT_RAM_END - 1))) { | 
|  | uint8_t *adjusted_start = MAX(dest_addr, K_MEM_VIRT_RAM_START); | 
|  | uint8_t *adjusted_end = MIN(dest_addr + aligned_size, | 
|  | K_MEM_VIRT_RAM_END); | 
|  | size_t adjusted_sz = adjusted_end - adjusted_start; | 
|  |  | 
|  | num_bits = adjusted_sz / CONFIG_MMU_PAGE_SIZE; | 
|  | offset = virt_to_bitmap_offset(adjusted_start, adjusted_sz); | 
|  | if (sys_bitarray_test_and_set_region( | 
|  | &virt_region_bitmap, num_bits, offset, true)) { | 
|  | goto fail; | 
|  | } | 
|  | } | 
|  | } else { | 
|  | /* Obtain an appropriately sized chunk of virtual memory */ | 
|  | dest_addr = virt_region_alloc(aligned_size, align_boundary); | 
|  | if (!dest_addr) { | 
|  | goto fail; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* If this fails there's something amiss with virt_region_get */ | 
|  | __ASSERT((uintptr_t)dest_addr < | 
|  | ((uintptr_t)dest_addr + (size - 1)), | 
|  | "wraparound for virtual address %p (size %zu)", | 
|  | dest_addr, size); | 
|  |  | 
|  | LOG_DBG("arch_mem_map(%p, 0x%lx, %zu, %x) offset %lu", (void *)dest_addr, | 
|  | aligned_phys, aligned_size, flags, addr_offset); | 
|  |  | 
|  | arch_mem_map(dest_addr, aligned_phys, aligned_size, flags); | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  |  | 
|  | *virt_ptr = dest_addr + addr_offset; | 
|  | return; | 
|  | fail: | 
|  | /* May re-visit this in the future, but for now running out of | 
|  | * virtual address space or failing the arch_mem_map() call is | 
|  | * an unrecoverable situation. | 
|  | * | 
|  | * Other problems not related to resource exhaustion we leave as | 
|  | * assertions since they are clearly programming mistakes. | 
|  | */ | 
|  | LOG_ERR("memory mapping 0x%lx (size %zu, flags 0x%x) failed", | 
|  | phys, size, flags); | 
|  | k_panic(); | 
|  | } | 
|  |  | 
|  | void k_mem_unmap_phys_bare(uint8_t *virt, size_t size) | 
|  | { | 
|  | uintptr_t aligned_virt, addr_offset; | 
|  | size_t aligned_size; | 
|  | k_spinlock_key_t key; | 
|  |  | 
|  | addr_offset = k_mem_region_align(&aligned_virt, &aligned_size, | 
|  | POINTER_TO_UINT(virt), size, | 
|  | CONFIG_MMU_PAGE_SIZE); | 
|  | __ASSERT(aligned_size != 0U, "0-length mapping at 0x%lx", aligned_virt); | 
|  | __ASSERT(aligned_virt < (aligned_virt + (aligned_size - 1)), | 
|  | "wraparound for virtual address 0x%lx (size %zu)", | 
|  | aligned_virt, aligned_size); | 
|  |  | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  |  | 
|  | LOG_DBG("arch_mem_unmap(0x%lx, %zu) offset %lu", | 
|  | aligned_virt, aligned_size, addr_offset); | 
|  |  | 
|  | arch_mem_unmap(UINT_TO_POINTER(aligned_virt), aligned_size); | 
|  | virt_region_free(UINT_TO_POINTER(aligned_virt), aligned_size); | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Miscellaneous | 
|  | */ | 
|  |  | 
|  | size_t k_mem_region_align(uintptr_t *aligned_addr, size_t *aligned_size, | 
|  | uintptr_t addr, size_t size, size_t align) | 
|  | { | 
|  | size_t addr_offset; | 
|  |  | 
|  | /* The actual mapped region must be page-aligned. Round down the | 
|  | * physical address and pad the region size appropriately | 
|  | */ | 
|  | *aligned_addr = ROUND_DOWN(addr, align); | 
|  | addr_offset = addr - *aligned_addr; | 
|  | *aligned_size = ROUND_UP(size + addr_offset, align); | 
|  |  | 
|  | return addr_offset; | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_LINKER_USE_BOOT_SECTION) || defined(CONFIG_LINKER_USE_PINNED_SECTION) | 
|  | static void mark_linker_section_pinned(void *start_addr, void *end_addr, | 
|  | bool pin) | 
|  | { | 
|  | struct k_mem_page_frame *pf; | 
|  | uint8_t *addr; | 
|  |  | 
|  | uintptr_t pinned_start = ROUND_DOWN(POINTER_TO_UINT(start_addr), | 
|  | CONFIG_MMU_PAGE_SIZE); | 
|  | uintptr_t pinned_end = ROUND_UP(POINTER_TO_UINT(end_addr), | 
|  | CONFIG_MMU_PAGE_SIZE); | 
|  | size_t pinned_size = pinned_end - pinned_start; | 
|  |  | 
|  | VIRT_FOREACH(UINT_TO_POINTER(pinned_start), pinned_size, addr) | 
|  | { | 
|  | pf = k_mem_phys_to_page_frame(K_MEM_BOOT_VIRT_TO_PHYS(addr)); | 
|  | frame_mapped_set(pf, addr); | 
|  |  | 
|  | if (pin) { | 
|  | k_mem_page_frame_set(pf, K_MEM_PAGE_FRAME_PINNED); | 
|  | } else { | 
|  | k_mem_page_frame_clear(pf, K_MEM_PAGE_FRAME_PINNED); | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | if (IS_ENABLED(CONFIG_EVICTION_TRACKING) && | 
|  | k_mem_page_frame_is_evictable(pf)) { | 
|  | k_mem_paging_eviction_add(pf); | 
|  | } | 
|  | #endif | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif /* CONFIG_LINKER_USE_BOOT_SECTION) || CONFIG_LINKER_USE_PINNED_SECTION */ | 
|  |  | 
|  | #ifdef CONFIG_LINKER_USE_ONDEMAND_SECTION | 
|  | static void z_paging_ondemand_section_map(void) | 
|  | { | 
|  | uint8_t *addr; | 
|  | size_t size; | 
|  | uintptr_t location; | 
|  | uint32_t flags; | 
|  |  | 
|  | size = (uintptr_t)lnkr_ondemand_text_size; | 
|  | flags = K_MEM_MAP_UNPAGED | K_MEM_PERM_EXEC | K_MEM_CACHE_WB; | 
|  | VIRT_FOREACH(lnkr_ondemand_text_start, size, addr) { | 
|  | k_mem_paging_backing_store_location_query(addr, &location); | 
|  | arch_mem_map(addr, location, CONFIG_MMU_PAGE_SIZE, flags); | 
|  | sys_bitarray_set_region(&virt_region_bitmap, 1, | 
|  | virt_to_bitmap_offset(addr, CONFIG_MMU_PAGE_SIZE)); | 
|  | } | 
|  |  | 
|  | size = (uintptr_t)lnkr_ondemand_rodata_size; | 
|  | flags = K_MEM_MAP_UNPAGED | K_MEM_CACHE_WB; | 
|  | VIRT_FOREACH(lnkr_ondemand_rodata_start, size, addr) { | 
|  | k_mem_paging_backing_store_location_query(addr, &location); | 
|  | arch_mem_map(addr, location, CONFIG_MMU_PAGE_SIZE, flags); | 
|  | sys_bitarray_set_region(&virt_region_bitmap, 1, | 
|  | virt_to_bitmap_offset(addr, CONFIG_MMU_PAGE_SIZE)); | 
|  | } | 
|  | } | 
|  | #endif /* CONFIG_LINKER_USE_ONDEMAND_SECTION */ | 
|  |  | 
|  | void z_mem_manage_init(void) | 
|  | { | 
|  | uintptr_t phys; | 
|  | uint8_t *addr; | 
|  | struct k_mem_page_frame *pf; | 
|  | k_spinlock_key_t key = k_spin_lock(&z_mm_lock); | 
|  |  | 
|  | free_page_frame_list_init(); | 
|  |  | 
|  | ARG_UNUSED(addr); | 
|  |  | 
|  | #ifdef CONFIG_ARCH_HAS_RESERVED_PAGE_FRAMES | 
|  | /* If some page frames are unavailable for use as memory, arch | 
|  | * code will mark K_MEM_PAGE_FRAME_RESERVED in their flags | 
|  | */ | 
|  | arch_reserved_pages_update(); | 
|  | #endif /* CONFIG_ARCH_HAS_RESERVED_PAGE_FRAMES */ | 
|  |  | 
|  | #ifdef CONFIG_LINKER_GENERIC_SECTIONS_PRESENT_AT_BOOT | 
|  | /* All pages composing the Zephyr image are mapped at boot in a | 
|  | * predictable way. This can change at runtime. | 
|  | */ | 
|  | VIRT_FOREACH(K_MEM_KERNEL_VIRT_START, K_MEM_KERNEL_VIRT_SIZE, addr) | 
|  | { | 
|  | pf = k_mem_phys_to_page_frame(K_MEM_BOOT_VIRT_TO_PHYS(addr)); | 
|  | frame_mapped_set(pf, addr); | 
|  |  | 
|  | /* TODO: for now we pin the whole Zephyr image. Demand paging | 
|  | * currently tested with anonymously-mapped pages which are not | 
|  | * pinned. | 
|  | * | 
|  | * We will need to setup linker regions for a subset of kernel | 
|  | * code/data pages which are pinned in memory and | 
|  | * may not be evicted. This will contain critical CPU data | 
|  | * structures, and any code used to perform page fault | 
|  | * handling, page-ins, etc. | 
|  | */ | 
|  | k_mem_page_frame_set(pf, K_MEM_PAGE_FRAME_PINNED); | 
|  | } | 
|  | #endif /* CONFIG_LINKER_GENERIC_SECTIONS_PRESENT_AT_BOOT */ | 
|  |  | 
|  | #ifdef CONFIG_LINKER_USE_BOOT_SECTION | 
|  | /* Pin the boot section to prevent it from being swapped out during | 
|  | * boot process. Will be un-pinned once boot process completes. | 
|  | */ | 
|  | mark_linker_section_pinned(lnkr_boot_start, lnkr_boot_end, true); | 
|  | #endif /* CONFIG_LINKER_USE_BOOT_SECTION */ | 
|  |  | 
|  | #ifdef CONFIG_LINKER_USE_PINNED_SECTION | 
|  | /* Pin the page frames correspondng to the pinned symbols */ | 
|  | mark_linker_section_pinned(lnkr_pinned_start, lnkr_pinned_end, true); | 
|  | #endif /* CONFIG_LINKER_USE_PINNED_SECTION */ | 
|  |  | 
|  | /* Any remaining pages that aren't mapped, reserved, or pinned get | 
|  | * added to the free pages list | 
|  | */ | 
|  | K_MEM_PAGE_FRAME_FOREACH(phys, pf) { | 
|  | if (k_mem_page_frame_is_available(pf)) { | 
|  | free_page_frame_list_put(pf); | 
|  | } | 
|  | } | 
|  | LOG_DBG("free page frames: %zu", z_free_page_count); | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  | #ifdef CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM | 
|  | z_paging_histogram_init(); | 
|  | #endif /* CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM */ | 
|  | k_mem_paging_backing_store_init(); | 
|  | k_mem_paging_eviction_init(); | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_EVICTION_TRACKING)) { | 
|  | /* start tracking evictable page installed above if any */ | 
|  | K_MEM_PAGE_FRAME_FOREACH(phys, pf) { | 
|  | if (k_mem_page_frame_is_evictable(pf)) { | 
|  | k_mem_paging_eviction_add(pf); | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif /* CONFIG_DEMAND_PAGING */ | 
|  |  | 
|  | #ifdef CONFIG_LINKER_USE_ONDEMAND_SECTION | 
|  | z_paging_ondemand_section_map(); | 
|  | #endif | 
|  |  | 
|  | #if __ASSERT_ON | 
|  | page_frames_initialized = true; | 
|  | #endif | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  |  | 
|  | #ifndef CONFIG_LINKER_GENERIC_SECTIONS_PRESENT_AT_BOOT | 
|  | /* If BSS section is not present in memory at boot, | 
|  | * it would not have been cleared. This needs to be | 
|  | * done now since paging mechanism has been initialized | 
|  | * and the BSS pages can be brought into physical | 
|  | * memory to be cleared. | 
|  | */ | 
|  | arch_bss_zero(); | 
|  | #endif /* CONFIG_LINKER_GENERIC_SECTIONS_PRESENT_AT_BOOT */ | 
|  | } | 
|  |  | 
|  | void z_mem_manage_boot_finish(void) | 
|  | { | 
|  | #ifdef CONFIG_LINKER_USE_BOOT_SECTION | 
|  | /* At the end of boot process, unpin the boot sections | 
|  | * as they don't need to be in memory all the time anymore. | 
|  | */ | 
|  | mark_linker_section_pinned(lnkr_boot_start, lnkr_boot_end, false); | 
|  | #endif /* CONFIG_LINKER_USE_BOOT_SECTION */ | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS | 
|  | struct k_mem_paging_stats_t paging_stats; | 
|  | extern struct k_mem_paging_histogram_t z_paging_histogram_eviction; | 
|  | extern struct k_mem_paging_histogram_t z_paging_histogram_backing_store_page_in; | 
|  | extern struct k_mem_paging_histogram_t z_paging_histogram_backing_store_page_out; | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS */ | 
|  |  | 
|  | static inline void do_backing_store_page_in(uintptr_t location) | 
|  | { | 
|  | #ifdef CONFIG_DEMAND_MAPPING | 
|  | /* Check for special cases */ | 
|  | switch (location) { | 
|  | case ARCH_UNPAGED_ANON_ZERO: | 
|  | memset(K_MEM_SCRATCH_PAGE, 0, CONFIG_MMU_PAGE_SIZE); | 
|  | __fallthrough; | 
|  | case ARCH_UNPAGED_ANON_UNINIT: | 
|  | /* nothing else to do */ | 
|  | return; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | #endif /* CONFIG_DEMAND_MAPPING */ | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM | 
|  | uint32_t time_diff; | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS | 
|  | timing_t time_start, time_end; | 
|  |  | 
|  | time_start = timing_counter_get(); | 
|  | #else | 
|  | uint32_t time_start; | 
|  |  | 
|  | time_start = k_cycle_get_32(); | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS */ | 
|  | #endif /* CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM */ | 
|  |  | 
|  | k_mem_paging_backing_store_page_in(location); | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS | 
|  | time_end = timing_counter_get(); | 
|  | time_diff = (uint32_t)timing_cycles_get(&time_start, &time_end); | 
|  | #else | 
|  | time_diff = k_cycle_get_32() - time_start; | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS */ | 
|  |  | 
|  | z_paging_histogram_inc(&z_paging_histogram_backing_store_page_in, | 
|  | time_diff); | 
|  | #endif /* CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM */ | 
|  | } | 
|  |  | 
|  | static inline void do_backing_store_page_out(uintptr_t location) | 
|  | { | 
|  | #ifdef CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM | 
|  | uint32_t time_diff; | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS | 
|  | timing_t time_start, time_end; | 
|  |  | 
|  | time_start = timing_counter_get(); | 
|  | #else | 
|  | uint32_t time_start; | 
|  |  | 
|  | time_start = k_cycle_get_32(); | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS */ | 
|  | #endif /* CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM */ | 
|  |  | 
|  | k_mem_paging_backing_store_page_out(location); | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS | 
|  | time_end = timing_counter_get(); | 
|  | time_diff = (uint32_t)timing_cycles_get(&time_start, &time_end); | 
|  | #else | 
|  | time_diff = k_cycle_get_32() - time_start; | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS */ | 
|  |  | 
|  | z_paging_histogram_inc(&z_paging_histogram_backing_store_page_out, | 
|  | time_diff); | 
|  | #endif /* CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM */ | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_SMP) && defined(CONFIG_DEMAND_PAGING_ALLOW_IRQ) | 
|  | /* | 
|  | * SMP support is very simple. Some resources such as the scratch page could | 
|  | * be made per CPU, backing store driver execution be confined to the faulting | 
|  | * CPU, statistics be made to cope with access concurrency, etc. But in the | 
|  | * end we're dealing with memory transfer to/from some external storage which | 
|  | * is inherently slow and whose access is most likely serialized anyway. | 
|  | * So let's simply enforce global demand paging serialization across all CPUs | 
|  | * with a mutex as there is no real gain from added parallelism here. | 
|  | */ | 
|  | static K_MUTEX_DEFINE(z_mm_paging_lock); | 
|  | #endif | 
|  |  | 
|  | static void virt_region_foreach(void *addr, size_t size, | 
|  | void (*func)(void *)) | 
|  | { | 
|  | k_mem_assert_virtual_region(addr, size); | 
|  |  | 
|  | for (size_t offset = 0; offset < size; offset += CONFIG_MMU_PAGE_SIZE) { | 
|  | func((uint8_t *)addr + offset); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Perform some preparatory steps before paging out. The provided page frame | 
|  | * must be evicted to the backing store immediately after this is called | 
|  | * with a call to k_mem_paging_backing_store_page_out() if it contains | 
|  | * a data page. | 
|  | * | 
|  | * - Map page frame to scratch area if requested. This always is true if we're | 
|  | *   doing a page fault, but is only set on manual evictions if the page is | 
|  | *   dirty. | 
|  | * - If mapped: | 
|  | *    - obtain backing store location and populate location parameter | 
|  | *    - Update page tables with location | 
|  | * - Mark page frame as busy | 
|  | * | 
|  | * Returns -ENOMEM if the backing store is full | 
|  | */ | 
|  | static int page_frame_prepare_locked(struct k_mem_page_frame *pf, bool *dirty_ptr, | 
|  | bool page_fault, uintptr_t *location_ptr) | 
|  | { | 
|  | uintptr_t phys; | 
|  | int ret; | 
|  | bool dirty = *dirty_ptr; | 
|  |  | 
|  | phys = k_mem_page_frame_to_phys(pf); | 
|  | __ASSERT(!k_mem_page_frame_is_pinned(pf), "page frame 0x%lx is pinned", | 
|  | phys); | 
|  |  | 
|  | /* If the backing store doesn't have a copy of the page, even if it | 
|  | * wasn't modified, treat as dirty. This can happen for a few | 
|  | * reasons: | 
|  | * 1) Page has never been swapped out before, and the backing store | 
|  | *    wasn't pre-populated with this data page. | 
|  | * 2) Page was swapped out before, but the page contents were not | 
|  | *    preserved after swapping back in. | 
|  | * 3) Page contents were preserved when swapped back in, but were later | 
|  | *    evicted from the backing store to make room for other evicted | 
|  | *    pages. | 
|  | */ | 
|  | if (k_mem_page_frame_is_mapped(pf)) { | 
|  | dirty = dirty || !k_mem_page_frame_is_backed(pf); | 
|  | } | 
|  |  | 
|  | if (dirty || page_fault) { | 
|  | arch_mem_scratch(phys); | 
|  | } | 
|  |  | 
|  | if (k_mem_page_frame_is_mapped(pf)) { | 
|  | ret = k_mem_paging_backing_store_location_get(pf, location_ptr, | 
|  | page_fault); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("out of backing store memory"); | 
|  | return -ENOMEM; | 
|  | } | 
|  | arch_mem_page_out(k_mem_page_frame_to_virt(pf), *location_ptr); | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_EVICTION_TRACKING)) { | 
|  | k_mem_paging_eviction_remove(pf); | 
|  | } | 
|  | } else { | 
|  | /* Shouldn't happen unless this function is mis-used */ | 
|  | __ASSERT(!dirty, "un-mapped page determined to be dirty"); | 
|  | } | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | /* Mark as busy so that k_mem_page_frame_is_evictable() returns false */ | 
|  | __ASSERT(!k_mem_page_frame_is_busy(pf), "page frame 0x%lx is already busy", | 
|  | phys); | 
|  | k_mem_page_frame_set(pf, K_MEM_PAGE_FRAME_BUSY); | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | /* Update dirty parameter, since we set to true if it wasn't backed | 
|  | * even if otherwise clean | 
|  | */ | 
|  | *dirty_ptr = dirty; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int do_mem_evict(void *addr) | 
|  | { | 
|  | bool dirty; | 
|  | struct k_mem_page_frame *pf; | 
|  | uintptr_t location; | 
|  | k_spinlock_key_t key; | 
|  | uintptr_t flags, phys; | 
|  | int ret; | 
|  |  | 
|  | #if CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | __ASSERT(!k_is_in_isr(), | 
|  | "%s is unavailable in ISRs with CONFIG_DEMAND_PAGING_ALLOW_IRQ", | 
|  | __func__); | 
|  | #ifdef CONFIG_SMP | 
|  | k_mutex_lock(&z_mm_paging_lock, K_FOREVER); | 
|  | #else | 
|  | k_sched_lock(); | 
|  | #endif | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  | flags = arch_page_info_get(addr, &phys, false); | 
|  | __ASSERT((flags & ARCH_DATA_PAGE_NOT_MAPPED) == 0, | 
|  | "address %p isn't mapped", addr); | 
|  | if ((flags & ARCH_DATA_PAGE_LOADED) == 0) { | 
|  | /* Un-mapped or already evicted. Nothing to do */ | 
|  | ret = 0; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | dirty = (flags & ARCH_DATA_PAGE_DIRTY) != 0; | 
|  | pf = k_mem_phys_to_page_frame(phys); | 
|  | __ASSERT(k_mem_page_frame_to_virt(pf) == addr, "page frame address mismatch"); | 
|  | ret = page_frame_prepare_locked(pf, &dirty, false, &location); | 
|  | if (ret != 0) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | __ASSERT(ret == 0, "failed to prepare page frame"); | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | if (dirty) { | 
|  | do_backing_store_page_out(location); | 
|  | } | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | page_frame_free_locked(pf); | 
|  | out: | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | #ifdef CONFIG_SMP | 
|  | k_mutex_unlock(&z_mm_paging_lock); | 
|  | #else | 
|  | k_sched_unlock(); | 
|  | #endif | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int k_mem_page_out(void *addr, size_t size) | 
|  | { | 
|  | __ASSERT(page_frames_initialized, "%s called on %p too early", __func__, | 
|  | addr); | 
|  | k_mem_assert_virtual_region(addr, size); | 
|  |  | 
|  | for (size_t offset = 0; offset < size; offset += CONFIG_MMU_PAGE_SIZE) { | 
|  | void *pos = (uint8_t *)addr + offset; | 
|  | int ret; | 
|  |  | 
|  | ret = do_mem_evict(pos); | 
|  | if (ret != 0) { | 
|  | return ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int k_mem_page_frame_evict(uintptr_t phys) | 
|  | { | 
|  | k_spinlock_key_t key; | 
|  | struct k_mem_page_frame *pf; | 
|  | bool dirty; | 
|  | uintptr_t flags; | 
|  | uintptr_t location; | 
|  | int ret; | 
|  |  | 
|  | __ASSERT(page_frames_initialized, "%s called on 0x%lx too early", | 
|  | __func__, phys); | 
|  |  | 
|  | /* Implementation is similar to do_page_fault() except there is no | 
|  | * data page to page-in, see comments in that function. | 
|  | */ | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | __ASSERT(!k_is_in_isr(), | 
|  | "%s is unavailable in ISRs with CONFIG_DEMAND_PAGING_ALLOW_IRQ", | 
|  | __func__); | 
|  | #ifdef CONFIG_SMP | 
|  | k_mutex_lock(&z_mm_paging_lock, K_FOREVER); | 
|  | #else | 
|  | k_sched_lock(); | 
|  | #endif | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  | pf = k_mem_phys_to_page_frame(phys); | 
|  | if (!k_mem_page_frame_is_mapped(pf)) { | 
|  | /* Nothing to do, free page */ | 
|  | ret = 0; | 
|  | goto out; | 
|  | } | 
|  | flags = arch_page_info_get(k_mem_page_frame_to_virt(pf), NULL, false); | 
|  | /* Shouldn't ever happen */ | 
|  | __ASSERT((flags & ARCH_DATA_PAGE_LOADED) != 0, "data page not loaded"); | 
|  | dirty = (flags & ARCH_DATA_PAGE_DIRTY) != 0; | 
|  | ret = page_frame_prepare_locked(pf, &dirty, false, &location); | 
|  | if (ret != 0) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | if (dirty) { | 
|  | do_backing_store_page_out(location); | 
|  | } | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | page_frame_free_locked(pf); | 
|  | out: | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | #ifdef CONFIG_SMP | 
|  | k_mutex_unlock(&z_mm_paging_lock); | 
|  | #else | 
|  | k_sched_unlock(); | 
|  | #endif | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static inline void paging_stats_faults_inc(struct k_thread *faulting_thread, | 
|  | int key) | 
|  | { | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS | 
|  | bool is_irq_unlocked = arch_irq_unlocked(key); | 
|  |  | 
|  | paging_stats.pagefaults.cnt++; | 
|  |  | 
|  | if (is_irq_unlocked) { | 
|  | paging_stats.pagefaults.irq_unlocked++; | 
|  | } else { | 
|  | paging_stats.pagefaults.irq_locked++; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_THREAD_STATS | 
|  | faulting_thread->paging_stats.pagefaults.cnt++; | 
|  |  | 
|  | if (is_irq_unlocked) { | 
|  | faulting_thread->paging_stats.pagefaults.irq_unlocked++; | 
|  | } else { | 
|  | faulting_thread->paging_stats.pagefaults.irq_locked++; | 
|  | } | 
|  | #else | 
|  | ARG_UNUSED(faulting_thread); | 
|  | #endif /* CONFIG_DEMAND_PAGING_THREAD_STATS */ | 
|  |  | 
|  | #ifndef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | if (k_is_in_isr()) { | 
|  | paging_stats.pagefaults.in_isr++; | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_THREAD_STATS | 
|  | faulting_thread->paging_stats.pagefaults.in_isr++; | 
|  | #endif /* CONFIG_DEMAND_PAGING_THREAD_STATS */ | 
|  | } | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS */ | 
|  | } | 
|  |  | 
|  | static inline void paging_stats_eviction_inc(struct k_thread *faulting_thread, | 
|  | bool dirty) | 
|  | { | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS | 
|  | if (dirty) { | 
|  | paging_stats.eviction.dirty++; | 
|  | } else { | 
|  | paging_stats.eviction.clean++; | 
|  | } | 
|  | #ifdef CONFIG_DEMAND_PAGING_THREAD_STATS | 
|  | if (dirty) { | 
|  | faulting_thread->paging_stats.eviction.dirty++; | 
|  | } else { | 
|  | faulting_thread->paging_stats.eviction.clean++; | 
|  | } | 
|  | #else | 
|  | ARG_UNUSED(faulting_thread); | 
|  | #endif /* CONFIG_DEMAND_PAGING_THREAD_STATS */ | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS */ | 
|  | } | 
|  |  | 
|  | static inline struct k_mem_page_frame *do_eviction_select(bool *dirty) | 
|  | { | 
|  | struct k_mem_page_frame *pf; | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM | 
|  | uint32_t time_diff; | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS | 
|  | timing_t time_start, time_end; | 
|  |  | 
|  | time_start = timing_counter_get(); | 
|  | #else | 
|  | uint32_t time_start; | 
|  |  | 
|  | time_start = k_cycle_get_32(); | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS */ | 
|  | #endif /* CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM */ | 
|  |  | 
|  | pf = k_mem_paging_eviction_select(dirty); | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM | 
|  | #ifdef CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS | 
|  | time_end = timing_counter_get(); | 
|  | time_diff = (uint32_t)timing_cycles_get(&time_start, &time_end); | 
|  | #else | 
|  | time_diff = k_cycle_get_32() - time_start; | 
|  | #endif /* CONFIG_DEMAND_PAGING_STATS_USING_TIMING_FUNCTIONS */ | 
|  |  | 
|  | z_paging_histogram_inc(&z_paging_histogram_eviction, time_diff); | 
|  | #endif /* CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM */ | 
|  |  | 
|  | return pf; | 
|  | } | 
|  |  | 
|  | static bool do_page_fault(void *addr, bool pin) | 
|  | { | 
|  | struct k_mem_page_frame *pf; | 
|  | k_spinlock_key_t key; | 
|  | uintptr_t page_in_location, page_out_location; | 
|  | enum arch_page_location status; | 
|  | bool result; | 
|  | bool dirty = false; | 
|  | struct k_thread *faulting_thread; | 
|  | int ret; | 
|  |  | 
|  | __ASSERT(page_frames_initialized, "page fault at %p happened too early", | 
|  | addr); | 
|  |  | 
|  | LOG_DBG("page fault at %p", addr); | 
|  |  | 
|  | /* | 
|  | * TODO: Add performance accounting: | 
|  | * - k_mem_paging_eviction_select() metrics | 
|  | *   * periodic timer execution time histogram (if implemented) | 
|  | */ | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | /* | 
|  | * We do re-enable interrupts during the page-in/page-out operation | 
|  | * if and only if interrupts were enabled when the exception was | 
|  | * taken; in this configuration page faults in an ISR are a bug; all | 
|  | * their code/data must be pinned. | 
|  | * | 
|  | * If interrupts were disabled when the exception was taken, the | 
|  | * arch code is responsible for keeping them that way when entering | 
|  | * this function. | 
|  | * | 
|  | * If this is not enabled, then interrupts are always locked for the | 
|  | * entire operation. This is far worse for system interrupt latency | 
|  | * but requires less pinned pages and ISRs may also take page faults. | 
|  | * | 
|  | * On UP we lock the scheduler so that other threads are never | 
|  | * scheduled during the page-in/out operation. Support for | 
|  | * allowing k_mem_paging_backing_store_page_out() and | 
|  | * k_mem_paging_backing_store_page_in() to also sleep and allow | 
|  | * other threads to run (such as in the case where the transfer is | 
|  | * async DMA) is not supported on UP. Even if limited to thread | 
|  | * context, arbitrary memory access triggering exceptions that put | 
|  | * a thread to sleep on a contended page fault operation will break | 
|  | * scheduling assumptions of cooperative threads or threads that | 
|  | * implement critical sections with spinlocks or disabling IRQs. | 
|  | * | 
|  | * On SMP, though, exclusivity cannot be assumed solely from being | 
|  | * a cooperative thread. Another thread with any prio may be running | 
|  | * on another CPU so exclusion must already be enforced by other | 
|  | * means. Therefore trying to prevent scheduling on SMP is pointless, | 
|  | * and k_sched_lock()  is equivalent to a no-op on SMP anyway. | 
|  | * As a result, sleeping/rescheduling in the SMP case is fine. | 
|  | */ | 
|  | __ASSERT(!k_is_in_isr(), "ISR page faults are forbidden"); | 
|  | #ifdef CONFIG_SMP | 
|  | k_mutex_lock(&z_mm_paging_lock, K_FOREVER); | 
|  | #else | 
|  | k_sched_lock(); | 
|  | #endif | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  |  | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  | faulting_thread = _current; | 
|  |  | 
|  | status = arch_page_location_get(addr, &page_in_location); | 
|  | if (status == ARCH_PAGE_LOCATION_BAD) { | 
|  | /* Return false to treat as a fatal error */ | 
|  | result = false; | 
|  | goto out; | 
|  | } | 
|  | result = true; | 
|  |  | 
|  | if (status == ARCH_PAGE_LOCATION_PAGED_IN) { | 
|  | if (pin) { | 
|  | /* It's a physical memory address */ | 
|  | uintptr_t phys = page_in_location; | 
|  |  | 
|  | pf = k_mem_phys_to_page_frame(phys); | 
|  | if (!k_mem_page_frame_is_pinned(pf)) { | 
|  | if (IS_ENABLED(CONFIG_EVICTION_TRACKING)) { | 
|  | k_mem_paging_eviction_remove(pf); | 
|  | } | 
|  | k_mem_page_frame_set(pf, K_MEM_PAGE_FRAME_PINNED); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* This if-block is to pin the page if it is | 
|  | * already present in physical memory. There is | 
|  | * no need to go through the following code to | 
|  | * pull in the data pages. So skip to the end. | 
|  | */ | 
|  | goto out; | 
|  | } | 
|  | __ASSERT(status == ARCH_PAGE_LOCATION_PAGED_OUT, | 
|  | "unexpected status value %d", status); | 
|  |  | 
|  | paging_stats_faults_inc(faulting_thread, key.key); | 
|  |  | 
|  | pf = free_page_frame_list_get(); | 
|  | if (pf == NULL) { | 
|  | /* Need to evict a page frame */ | 
|  | pf = do_eviction_select(&dirty); | 
|  | __ASSERT(pf != NULL, "failed to get a page frame"); | 
|  | LOG_DBG("evicting %p at 0x%lx", | 
|  | k_mem_page_frame_to_virt(pf), | 
|  | k_mem_page_frame_to_phys(pf)); | 
|  |  | 
|  | paging_stats_eviction_inc(faulting_thread, dirty); | 
|  | } | 
|  | ret = page_frame_prepare_locked(pf, &dirty, true, &page_out_location); | 
|  | __ASSERT(ret == 0, "failed to prepare page frame"); | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | /* Interrupts are now unlocked if they were not locked when we entered | 
|  | * this function, and we may service ISRs. The scheduler is still | 
|  | * locked. | 
|  | */ | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | if (dirty) { | 
|  | do_backing_store_page_out(page_out_location); | 
|  | } | 
|  | do_backing_store_page_in(page_in_location); | 
|  |  | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  | k_mem_page_frame_clear(pf, K_MEM_PAGE_FRAME_BUSY); | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  | k_mem_page_frame_clear(pf, K_MEM_PAGE_FRAME_MAPPED); | 
|  | frame_mapped_set(pf, addr); | 
|  | if (pin) { | 
|  | k_mem_page_frame_set(pf, K_MEM_PAGE_FRAME_PINNED); | 
|  | } | 
|  |  | 
|  | arch_mem_page_in(addr, k_mem_page_frame_to_phys(pf)); | 
|  | k_mem_paging_backing_store_page_finalize(pf, page_in_location); | 
|  | if (IS_ENABLED(CONFIG_EVICTION_TRACKING) && (!pin)) { | 
|  | k_mem_paging_eviction_add(pf); | 
|  | } | 
|  | out: | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | #ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ | 
|  | #ifdef CONFIG_SMP | 
|  | k_mutex_unlock(&z_mm_paging_lock); | 
|  | #else | 
|  | k_sched_unlock(); | 
|  | #endif | 
|  | #endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */ | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static void do_page_in(void *addr) | 
|  | { | 
|  | bool ret; | 
|  |  | 
|  | ret = do_page_fault(addr, false); | 
|  | __ASSERT(ret, "unmapped memory address %p", addr); | 
|  | (void)ret; | 
|  | } | 
|  |  | 
|  | void k_mem_page_in(void *addr, size_t size) | 
|  | { | 
|  | __ASSERT(!IS_ENABLED(CONFIG_DEMAND_PAGING_ALLOW_IRQ) || !k_is_in_isr(), | 
|  | "%s may not be called in ISRs if CONFIG_DEMAND_PAGING_ALLOW_IRQ is enabled", | 
|  | __func__); | 
|  | virt_region_foreach(addr, size, do_page_in); | 
|  | } | 
|  |  | 
|  | static void do_mem_pin(void *addr) | 
|  | { | 
|  | bool ret; | 
|  |  | 
|  | ret = do_page_fault(addr, true); | 
|  | __ASSERT(ret, "unmapped memory address %p", addr); | 
|  | (void)ret; | 
|  | } | 
|  |  | 
|  | void k_mem_pin(void *addr, size_t size) | 
|  | { | 
|  | __ASSERT(!IS_ENABLED(CONFIG_DEMAND_PAGING_ALLOW_IRQ) || !k_is_in_isr(), | 
|  | "%s may not be called in ISRs if CONFIG_DEMAND_PAGING_ALLOW_IRQ is enabled", | 
|  | __func__); | 
|  | virt_region_foreach(addr, size, do_mem_pin); | 
|  | } | 
|  |  | 
|  | bool k_mem_page_fault(void *addr) | 
|  | { | 
|  | return do_page_fault(addr, false); | 
|  | } | 
|  |  | 
|  | static void do_mem_unpin(void *addr) | 
|  | { | 
|  | struct k_mem_page_frame *pf; | 
|  | k_spinlock_key_t key; | 
|  | uintptr_t flags, phys; | 
|  |  | 
|  | key = k_spin_lock(&z_mm_lock); | 
|  | flags = arch_page_info_get(addr, &phys, false); | 
|  | __ASSERT((flags & ARCH_DATA_PAGE_NOT_MAPPED) == 0, | 
|  | "invalid data page at %p", addr); | 
|  | if ((flags & ARCH_DATA_PAGE_LOADED) != 0) { | 
|  | pf = k_mem_phys_to_page_frame(phys); | 
|  | if (k_mem_page_frame_is_pinned(pf)) { | 
|  | k_mem_page_frame_clear(pf, K_MEM_PAGE_FRAME_PINNED); | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_EVICTION_TRACKING)) { | 
|  | k_mem_paging_eviction_add(pf); | 
|  | } | 
|  | } | 
|  | } | 
|  | k_spin_unlock(&z_mm_lock, key); | 
|  | } | 
|  |  | 
|  | void k_mem_unpin(void *addr, size_t size) | 
|  | { | 
|  | __ASSERT(page_frames_initialized, "%s called on %p too early", __func__, | 
|  | addr); | 
|  | virt_region_foreach(addr, size, do_mem_unpin); | 
|  | } | 
|  |  | 
|  | #endif /* CONFIG_DEMAND_PAGING */ |