| /* |
| * Copyright (c) 2011-2014 Wind River Systems, Inc. |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <kernel.h> |
| #include <mmustructs.h> |
| #include <linker/linker-defs.h> |
| |
| /* Common regions for all x86 processors. |
| * Peripheral I/O ranges configured at the SOC level |
| */ |
| |
| /* Mark text and rodata as read-only. |
| * Userspace may read all text and rodata. |
| */ |
| MMU_BOOT_REGION((u32_t)&_image_rom_start, (u32_t)&_image_rom_size, |
| MMU_ENTRY_READ | MMU_ENTRY_USER); |
| |
| #ifdef CONFIG_APPLICATION_MEMORY |
| /* User threads by default can read/write app-level memory. */ |
| MMU_BOOT_REGION((u32_t)&__app_ram_start, (u32_t)&__app_ram_size, |
| MMU_ENTRY_WRITE | MMU_ENTRY_USER | MMU_ENTRY_EXECUTE_DISABLE); |
| #endif |
| |
| /* __kernel_ram_size includes all unused memory, which is used for heaps. |
| * User threads cannot access this unless granted at runtime. This is done |
| * automatically for stacks. |
| */ |
| MMU_BOOT_REGION((u32_t)&__kernel_ram_start, (u32_t)&__kernel_ram_size, |
| MMU_ENTRY_WRITE | |
| MMU_ENTRY_RUNTIME_USER | |
| MMU_ENTRY_EXECUTE_DISABLE); |
| |
| |
| void _x86_mmu_get_flags(void *addr, |
| x86_page_entry_data_t *pde_flags, |
| x86_page_entry_data_t *pte_flags) |
| { |
| *pde_flags = (x86_page_entry_data_t)(X86_MMU_GET_PDE(addr)->value & |
| ~(x86_page_entry_data_t)MMU_PDE_PAGE_TABLE_MASK); |
| |
| if (*pde_flags & MMU_ENTRY_PRESENT) { |
| *pte_flags = (x86_page_entry_data_t) |
| (X86_MMU_GET_PTE(addr)->value & |
| ~(x86_page_entry_data_t)MMU_PTE_PAGE_MASK); |
| } else { |
| *pte_flags = 0; |
| } |
| } |
| |
| |
| int _arch_buffer_validate(void *addr, size_t size, int write) |
| { |
| u32_t start_pde_num; |
| u32_t end_pde_num; |
| u32_t starting_pte_num; |
| u32_t ending_pte_num; |
| u32_t pde; |
| u32_t pte; |
| #ifdef CONFIG_X86_PAE_MODE |
| union x86_mmu_pae_pte pte_value; |
| u32_t start_pdpte_num = MMU_PDPTE_NUM(addr); |
| u32_t end_pdpte_num = MMU_PDPTE_NUM((char *)addr + size - 1); |
| u32_t pdpte; |
| #else |
| union x86_mmu_pte pte_value; |
| #endif |
| struct x86_mmu_page_table *pte_address; |
| |
| |
| start_pde_num = MMU_PDE_NUM(addr); |
| end_pde_num = MMU_PDE_NUM((char *)addr + size - 1); |
| starting_pte_num = MMU_PAGE_NUM((char *)addr); |
| |
| #ifdef CONFIG_X86_PAE_MODE |
| for (pdpte = start_pdpte_num; pdpte <= end_pdpte_num; pdpte++) { |
| if (pdpte != start_pdpte_num) { |
| start_pde_num = 0; |
| } |
| |
| if (pdpte != end_pdpte_num) { |
| end_pde_num = 0; |
| } else { |
| end_pde_num = MMU_PDE_NUM((char *)addr + size - 1); |
| } |
| |
| struct x86_mmu_page_directory *pd_address = |
| X86_MMU_GET_PD_ADDR_INDEX(pdpte); |
| #endif |
| /* Iterate for all the pde's the buffer might take up. |
| * (depends on the size of the buffer and start address |
| * of the buff) |
| */ |
| for (pde = start_pde_num; pde <= end_pde_num; pde++) { |
| #ifdef CONFIG_X86_PAE_MODE |
| union x86_mmu_pae_pde pde_value = |
| pd_address->entry[pde]; |
| #else |
| union x86_mmu_pde_pt pde_value = |
| X86_MMU_PD->entry[pde].pt; |
| #endif |
| |
| if (!pde_value.p || |
| !pde_value.us || |
| (write && !pde_value.rw)) { |
| return -EPERM; |
| } |
| |
| pte_address = (struct x86_mmu_page_table *) |
| (pde_value.page_table << MMU_PAGE_SHIFT); |
| |
| /* loop over all the possible page tables for the |
| * required size. If the pde is not the last one |
| * then the last pte would be 1023. So each pde |
| * will be using all the page table entires except |
| * for the last pde. For the last pde, pte is |
| * calculated using the last memory address |
| * of the buffer. |
| */ |
| if (pde != end_pde_num) { |
| ending_pte_num = 1023; |
| } else { |
| ending_pte_num = |
| MMU_PAGE_NUM((char *)addr + size - 1); |
| } |
| |
| /* For all the pde's appart from the starting pde, |
| * will have the start pte number as zero. |
| */ |
| if (pde != start_pde_num) { |
| starting_pte_num = 0; |
| } |
| |
| pte_value.value = 0xFFFFFFFF; |
| |
| /* Bitwise AND all the pte values. |
| * An optimization done to make sure a compare is |
| * done only once. |
| */ |
| for (pte = starting_pte_num; |
| pte <= ending_pte_num; |
| pte++) { |
| pte_value.value &= |
| pte_address->entry[pte].value; |
| } |
| |
| if (!pte_value.p || |
| !pte_value.us || |
| (write && !pte_value.rw)) { |
| return -EPERM; |
| } |
| } |
| #ifdef CONFIG_X86_PAE_MODE |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static inline void tlb_flush_page(void *addr) |
| { |
| /* Invalidate TLB entries corresponding to the page containing the |
| * specified address |
| */ |
| char *page = (char *)addr; |
| |
| __asm__ ("invlpg %0" :: "m" (*page)); |
| } |
| |
| |
| void _x86_mmu_set_flags(void *ptr, |
| size_t size, |
| x86_page_entry_data_t flags, |
| x86_page_entry_data_t mask) |
| { |
| #ifdef CONFIG_X86_PAE_MODE |
| union x86_mmu_pae_pte *pte; |
| #else |
| union x86_mmu_pte *pte; |
| #endif |
| |
| u32_t addr = (u32_t)ptr; |
| |
| __ASSERT(!(addr & MMU_PAGE_MASK), "unaligned address provided"); |
| __ASSERT(!(size & MMU_PAGE_MASK), "unaligned size provided"); |
| |
| while (size) { |
| |
| #ifdef CONFIG_X86_PAE_MODE |
| /* TODO we're not generating 2MB entries at the moment */ |
| __ASSERT(X86_MMU_GET_PDE(addr)->ps != 1, "2MB PDE found"); |
| #else |
| /* TODO we're not generating 4MB entries at the moment */ |
| __ASSERT(X86_MMU_GET_4MB_PDE(addr)->ps != 1, "4MB PDE found"); |
| #endif |
| pte = X86_MMU_GET_PTE(addr); |
| |
| pte->value = (pte->value & ~mask) | flags; |
| tlb_flush_page((void *)addr); |
| |
| size -= MMU_PAGE_SIZE; |
| addr += MMU_PAGE_SIZE; |
| } |
| } |