diff --git a/arch/risc-v/Kconfig b/arch/risc-v/Kconfig index 961bb51def3..8c4e0d516da 100644 --- a/arch/risc-v/Kconfig +++ b/arch/risc-v/Kconfig @@ -111,6 +111,8 @@ config ARCH_CHIP_MPFS select ARCH_HAVE_MPU select ARCH_HAVE_MMU select ARCH_MMU_TYPE_SV39 + select ARCH_HAVE_ADDRENV + select ARCH_NEED_ADDRENV_MAPPING select ARCH_HAVE_RESET select ARCH_HAVE_SPI_CS_CONTROL select ARCH_HAVE_PWM_MULTICHAN diff --git a/arch/risc-v/include/arch.h b/arch/risc-v/include/arch.h index ed9745605ba..19e9e28155d 100644 --- a/arch/risc-v/include/arch.h +++ b/arch/risc-v/include/arch.h @@ -33,6 +33,7 @@ #ifndef __ASSEMBLY__ # include +# include #endif /**************************************************************************** @@ -70,6 +71,18 @@ #define STACK_ALIGNMENT 16 #define STACK_FRAME_SIZE __XSTR(STACK_ALIGNMENT) +/* Provide the maximum amount of page table levels per MMU type */ + +#ifdef CONFIG_ARCH_MMU_TYPE_SV39 +# define ARCH_PGT_MAX_LEVELS (3) +#endif + +/* Amount of static page tables allocated for an address environment */ + +#ifdef CONFIG_ARCH_ADDRENV +# define ARCH_SPGTS (ARCH_PGT_MAX_LEVELS - 1) +#endif + /**************************************************************************** * Inline functions ****************************************************************************/ @@ -97,6 +110,60 @@ static inline uintptr_t up_getsp(void) * Public Types ****************************************************************************/ +#ifdef CONFIG_ARCH_ADDRENV +#ifndef __ASSEMBLY__ + +/* A task group must have its L1 table in memory always, and the rest can + * be dynamically committed to memory (and even swapped). + * + * In this implementation every level tables besides the final level N are + * kept in memory always, while the level N tables are dynamically allocated. + * + * The implications ? They depend on the MMU type. + * + * For Sv39 this means that: + * - A task can not have more than 1GB of memory allocated. This should be + * plenty enough... + * - The minimum amount of memory needed for page tables per task is 12K, + * which gives access to 2MB of memory. This is plenty for many tasks. + */ + +struct group_addrenv_s +{ + /* Pointers to MAX_LEVELS-1 tables here, one of each are allocated for the + * task when it is created. + */ + + uintptr_t spgtables[ARCH_SPGTS]; + + /* For convenience store the text base here */ + + uintptr_t textvbase; + + /* For convenience store the data base here */ + + uintptr_t datavbase; + + /* For convenience store the heap base and initial size here */ + + uintptr_t heapvbase; + size_t heapsize; + + /* For convenience store the satp value here */ + + uintptr_t satp; +}; + +typedef struct group_addrenv_s group_addrenv_t; + +/* If an address environment needs to be saved, saving the satp register + * will suffice. The register width is architecture dependent + */ + +typedef uintptr_t save_addrenv_t; +#endif /* __ASSEMBLY__ */ +#endif /* CONFIG_ARCH_ADDRENV */ + /**************************************************************************** * Public Function Prototypes ****************************************************************************/ diff --git a/arch/risc-v/src/common/riscv_addrenv.c b/arch/risc-v/src/common/riscv_addrenv.c new file mode 100644 index 00000000000..4f1485a6a47 --- /dev/null +++ b/arch/risc-v/src/common/riscv_addrenv.c @@ -0,0 +1,756 @@ +/**************************************************************************** + * arch/risc-v/src/common/riscv_addrenv.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Address Environment Interfaces + * + * Low-level interfaces used in binfmt/ to instantiate tasks with address + * environments. These interfaces all operate on type group_addrenv_t which + * is an abstract representation of a task group's address environment and + * must be defined in arch/arch.h if CONFIG_ARCH_ADDRENV is defined. + * + * up_addrenv_create - Create an address environment + * up_addrenv_destroy - Destroy an address environment. + * up_addrenv_vtext - Returns the virtual base address of the .text + * address environment + * up_addrenv_vdata - Returns the virtual base address of the .bss/.data + * address environment + * up_addrenv_heapsize - Returns the size of the initial heap allocation. + * up_addrenv_select - Instantiate an address environment + * up_addrenv_restore - Restore an address environment + * up_addrenv_clone - Copy an address environment from one location to + * another. + * + * Higher-level interfaces used by the tasking logic. These interfaces are + * used by the functions in sched/ and all operate on the thread which whose + * group been assigned an address environment by up_addrenv_clone(). + * + * up_addrenv_attach - Clone the address environment assigned to one TCB + * to another. This operation is done when a pthread + * is created that share's the same address + * environment. + * up_addrenv_detach - Release the threads reference to an address + * environment when a task/thread exits. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "riscv_mmu.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Only CONFIG_BUILD_KERNEL is supported (i.e. tested) */ + +#ifndef CONFIG_BUILD_KERNEL +# error "This module is intended to be used with CONFIG_BUILD_KERNEL" +#endif + +/* Entries per PGT */ + +#define ENTRIES_PER_PGT (RV_MMU_PAGE_ENTRIES) + +/* Base address for address environment */ + +#define ADDRENV_VBASE (CONFIG_ARCH_DATA_VBASE) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: wipe_page + * + * Description: + * Wipe a page of physical memory, first mapping it into virtual memory. + * + * Input Parameters: + * paddr - Physical address of page + * + ****************************************************************************/ + +static inline void wipe_page(uintptr_t paddr) +{ + uintptr_t vaddr = paddr; + memset((void *)vaddr, 0, MM_PGSIZE); +} + +/**************************************************************************** + * Name: map_spgtables + * + * Description: + * Map vaddr to the static page tables. + * + * Input Parameters: + * addrenv - Describes the address environment + * + ****************************************************************************/ + +static void map_spgtables(group_addrenv_t *addrenv, uintptr_t vaddr) +{ + int i; + uintptr_t prev; + + /* Start from L1, and connect until max level - 1 */ + + prev = addrenv->spgtables[0]; + + /* Check if the mapping already exists */ + + if (mmu_ln_getentry(1, prev, vaddr) != 0) + { + return; + } + + /* No mapping yet, create it */ + + for (i = 0; i < (ARCH_SPGTS - 1); i++) + { + uintptr_t next = addrenv->spgtables[i + 1]; + mmu_ln_setentry(i + 1, prev, next, vaddr, MMU_UPGT_FLAGS); + prev = next; + } +} + +/**************************************************************************** + * Name: create_spgtables + * + * Description: + * Create the static page tables. Allocate memory for them and connect them + * together. + * + * Input Parameters: + * addrenv - Describes the address environment + * + * Returned value: + * Amount of pages created on success; a negated errno value on failure + * + ****************************************************************************/ + +static int create_spgtables(group_addrenv_t *addrenv) +{ + int i; + uintptr_t paddr; + + for (i = 0; i < ARCH_SPGTS; i++) + { + paddr = mm_pgalloc(1); + if (!paddr) + { + return -ENOMEM; + } + + /* Wipe the memory and assign it */ + + wipe_page(paddr); + addrenv->spgtables[i] = paddr; + } + + /* Flush the data cache, so the changes are committed to memory */ + + __DMB(); + + return i; +} + +/**************************************************************************** + * Name: create_region + * + * Description: + * Map a single region of memory to MMU. Assumes that the static page + * tables exist. Allocates the final level page tables and commits the + * region memory to physical memory. + * + * Input Parameters: + * addrenv - Describes the address environment + * vaddr - Base virtual address for the mapping + * size - Size of the region in bytes + * mmuflags - MMU flags to use + * + * Returned value: + * Amount of pages created on success; a negated errno value on failure + * + ****************************************************************************/ + +static int create_region(group_addrenv_t *addrenv, uintptr_t vaddr, + size_t size, uint32_t mmuflags) +{ + uintptr_t ptlast; + uintptr_t ptprev; + uintptr_t paddr; + uint32_t ptlevel; + int npages; + int nmapped; + int i; + int j; + + nmapped = 0; + npages = MM_NPAGES(size); + ptprev = addrenv->spgtables[ARCH_SPGTS - 1]; + ptlevel = ARCH_SPGTS; + + /* Create mappings for the lower level tables */ + + map_spgtables(addrenv, vaddr); + + /* Begin allocating memory for the page tables */ + + for (i = 0; i < npages; i += ENTRIES_PER_PGT) + { + /* Get the current final level entry corresponding to this vaddr */ + + paddr = mmu_pte_to_paddr(mmu_ln_getentry(ptlevel, ptprev, vaddr)); + + if (!paddr) + { + /* Nothing yet, allocate one page for final level page table */ + + paddr = mm_pgalloc(1); + if (!paddr) + { + return -ENOMEM; + } + + /* Map the page table to the prior level */ + + mmu_ln_setentry(ptlevel, ptprev, paddr, vaddr, MMU_UPGT_FLAGS); + + /* This is then used to map the final level */ + + wipe_page(paddr); + } + + ptlast = paddr; + + /* Then allocate memory for the region data */ + + for (j = 0; j < ENTRIES_PER_PGT && nmapped < size; j++) + { + paddr = mm_pgalloc(1); + if (!paddr) + { + return -ENOMEM; + } + + /* Wipe the physical page memory */ + + wipe_page(paddr); + + /* Then map the virtual address to the physical address */ + + mmu_ln_setentry(ptlevel + 1, ptlast, paddr, vaddr, mmuflags); + nmapped += MM_PGSIZE; + vaddr += MM_PGSIZE; + } + } + + /* Flush the data cache, so the changes are committed to memory */ + + __DMB(); + + return npages; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_addrenv_create + * + * Description: + * This function is called when a new task is created in order to + * instantiate an address environment for the new task group. + * up_addrenv_create() is essentially the allocator of the physical + * memory for the new task. + * + * Input Parameters: + * textsize - The size (in bytes) of the .text address environment needed + * by the task. This region may be read/execute only. + * datasize - The size (in bytes) of the .data/.bss address environment + * needed by the task. This region may be read/write only. NOTE: The + * actual size of the data region that is allocated will include a + * OS private reserved region at the beginning. The size of the + * private, reserved region is give by ARCH_DATA_RESERVE_SIZE. + * heapsize - The initial size (in bytes) of the heap address environment + * needed by the task. This region may be read/write only. + * addrenv - The location to return the representation of the task address + * environment. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_create(size_t textsize, size_t datasize, size_t heapsize, + group_addrenv_t *addrenv) +{ + int ret; + uintptr_t resvbase; + uintptr_t resvsize; + uintptr_t textbase; + uintptr_t database; + uintptr_t heapbase; + + DEBUGASSERT(addrenv); + DEBUGASSERT(MM_ISALIGNED(ADDRENV_VBASE)); + + /* Make sure the address environment is wiped before doing anything */ + + memset(addrenv, 0, sizeof(group_addrenv_t)); + + /* Create the static page tables */ + + ret = create_spgtables(addrenv); + + if (ret < 0) + { + serr("ERROR: Failed to create static page tables\n"); + goto errout; + } + + /* Calculate the base addresses for convenience */ + + resvbase = ADDRENV_VBASE; + resvsize = ARCH_DATA_RESERVE_SIZE; + textbase = resvbase + MM_PGALIGNUP(resvsize); + database = textbase + MM_PGALIGNUP(textsize); + heapbase = database + MM_PGALIGNUP(datasize); + + /* Allocate 1 extra page for heap, temporary fix for #5811 */ + + heapsize = heapsize + MM_NPAGES(1); + + /* Map the reserved area */ + + ret = create_region(addrenv, resvbase, resvsize, MMU_UDATA_FLAGS); + + if (ret < 0) + { + berr("ERROR: Failed to create reserved region: %d\n", ret); + goto errout; + } + + /* Map each region in turn FIXME: Remove W-flag after .elf is loaded */ + + ret = create_region(addrenv, textbase, textsize, MMU_UTEXT_FLAGS | PTE_W); + + if (ret < 0) + { + berr("ERROR: Failed to create .text region: %d\n", ret); + goto errout; + } + + ret = create_region(addrenv, database, datasize, MMU_UDATA_FLAGS); + + if (ret < 0) + { + berr("ERROR: Failed to create .bss/.data region: %d\n", ret); + goto errout; + } + + ret = create_region(addrenv, heapbase, heapsize, MMU_UDATA_FLAGS); + + if (ret < 0) + { + berr("ERROR: Failed to create heap region: %d\n", ret); + goto errout; + } + + /* Save the heap base and initial size allocated. These will be needed when + * the heap data structures are initialized. + */ + + addrenv->heapvbase = heapbase; + addrenv->heapsize = (size_t)ret << MM_PGSHIFT; + + /* Save the text base */ + + addrenv->textvbase = textbase; + + /* Save the data base */ + + addrenv->datavbase = database; + + /* Provide the satp value for context switch */ + + addrenv->satp = mmu_satp_reg(addrenv->spgtables[0], 0); + + /* When all is set and done, flush the data caches */ + + __ISB(); + __DMB(); + + return OK; + +errout: + up_addrenv_destroy(addrenv); + return ret; +} + +/**************************************************************************** + * Name: up_addrenv_destroy + * + * Description: + * This function is called when a final thread leaves the task group and + * the task group is destroyed. This function then destroys the defunct + * address environment, releasing the underlying physical memory. + * + * Input Parameters: + * addrenv - The address environment to be destroyed. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_destroy(group_addrenv_t *addrenv) +{ + /* Recursively destroy it all, need to table walk */ + + uintptr_t *ptprev; + uintptr_t *ptlast; + uintptr_t paddr; + int i; + int j; + + DEBUGASSERT(addrenv); + + /* Make sure the caches are flushed before doing this */ + + __ISB(); + __DMB(); + + /* First destroy the allocated memory and the final level page table */ + + ptprev = (uintptr_t *)addrenv->spgtables[ARCH_SPGTS - 1]; + if (ptprev) + { + for (i = 0; i < ENTRIES_PER_PGT; i++) + { + ptlast = (uintptr_t *)mmu_pte_to_paddr(ptprev[i]); + if (ptlast) + { + /* Page table allocated, free any allocated memory */ + + for (j = 0; j < ENTRIES_PER_PGT; j++) + { + paddr = mmu_pte_to_paddr(ptlast[j]); + if (paddr) + { + mm_pgfree(paddr, 1); + } + } + + /* Then free the page table itself */ + + mm_pgfree((uintptr_t)ptlast, 1); + } + } + } + + /* Then destroy the static tables */ + + for (i = 0; i < ARCH_SPGTS; i++) + { + paddr = addrenv->spgtables[i]; + if (paddr) + { + mm_pgfree(paddr, 1); + } + } + + /* When all is set and done, flush the caches */ + + __ISB(); + __DMB(); + + memset(addrenv, 0, sizeof(group_addrenv_t)); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_vtext + * + * Description: + * Return the virtual address associated with the newly create .text + * address environment. This function is used by the binary loaders in + * order get an address that can be used to initialize the new task. + * + * Input Parameters: + * addrenv - The representation of the task address environment previously + * returned by up_addrenv_create. + * vtext - The location to return the virtual address. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_vtext(group_addrenv_t *addrenv, void **vtext) +{ + DEBUGASSERT(addrenv && vtext); + *vtext = (void *)addrenv->textvbase; + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_vdata + * + * Description: + * Return the virtual address associated with the newly create .text + * address environment. This function is used by the binary loaders in + * order get an address that can be used to initialize the new task. + * + * Input Parameters: + * addrenv - The representation of the task address environment previously + * returned by up_addrenv_create. + * textsize - For some implementations, the text and data will be saved + * in the same memory region (read/write/execute) and, in this case, + * the virtual address of the data just lies at this offset into the + * common region. + * vdata - The location to return the virtual address. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_vdata(group_addrenv_t *addrenv, uintptr_t textsize, + void **vdata) +{ + DEBUGASSERT(addrenv && vdata); + *vdata = (void *)addrenv->datavbase; + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_heapsize + * + * Description: + * Return the initial heap allocation size. That is the amount of memory + * allocated by up_addrenv_create() when the heap memory region was first + * created. This may or may not differ from the heapsize parameter that + * was passed to up_addrenv_create() + * + * Input Parameters: + * addrenv - The representation of the task address environment previously + * returned by up_addrenv_create. + * + * Returned Value: + * The initial heap size allocated is returned on success; a negated + * errno value on failure. + * + ****************************************************************************/ + +#ifdef CONFIG_BUILD_KERNEL +ssize_t up_addrenv_heapsize(const group_addrenv_t *addrenv) +{ + DEBUGASSERT(addrenv); + return (ssize_t)addrenv->heapsize; +} +#endif + +/**************************************************************************** + * Name: up_addrenv_select + * + * Description: + * After an address environment has been established for a task (via + * up_addrenv_create()), this function may be called to instantiate + * that address environment in the virtual address space. This might be + * necessary, for example, to load the code for the task from a file or + * to access address environment private data. + * + * Input Parameters: + * addrenv - The representation of the task address environment previously + * returned by up_addrenv_create. + * oldenv + * The address environment that was in place before up_addrenv_select(). + * This may be used with up_addrenv_restore() to restore the original + * address environment that was in place before up_addrenv_select() was + * called. Note that this may be a task agnostic, hardware + * representation that is different from group_addrenv_t. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_select(const group_addrenv_t *addrenv, + save_addrenv_t *oldenv) +{ + DEBUGASSERT(addrenv); + if (oldenv) + { + /* Save the old environment */ + + uintptr_t satp_reg = mmu_read_satp(); + *oldenv = (save_addrenv_t)satp_reg; + } + + mmu_write_satp(addrenv->satp); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_restore + * + * Description: + * After an address environment has been temporarily instantiated by + * up_addrenv_select, this function may be called to restore the + * original address environment. + * + * Input Parameters: + * oldenv - The hardware representation of the address environment + * previously returned by up_addrenv_select. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_restore(const save_addrenv_t *oldenv) +{ + DEBUGASSERT(oldenv); + mmu_write_satp((uintptr_t)*oldenv); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_coherent + * + * Description: + * Flush D-Cache and invalidate I-Cache in preparation for a change in + * address environments. This should immediately precede a call to + * up_addrenv_select(); + * + * Input Parameters: + * addrenv - Describes the address environment to be made coherent. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_coherent(const group_addrenv_t *addrenv) +{ + /* Flush the instruction and data caches */ + + __ISB(); + __DMB(); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_clone + * + * Description: + * Duplicate an address environment. This does not copy the underlying + * memory, only the representation that can be used to instantiate that + * memory as an address environment. + * + * Input Parameters: + * src - The address environment to be copied. + * dest - The location to receive the copied address environment. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_clone(const group_addrenv_t *src, + group_addrenv_t *dest) +{ + DEBUGASSERT(src && dest); + memcpy(dest, src, sizeof(group_addrenv_t)); + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_attach + * + * Description: + * This function is called from the core scheduler logic when a thread + * is created that needs to share the address environment of its task + * group. + * + * Input Parameters: + * group - The task group to which the new thread belongs. + * tcb - The tcb of the thread needing the address environment. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_attach(struct task_group_s *group, struct tcb_s *tcb) +{ + /* There is nothing that needs to be done */ + + return OK; +} + +/**************************************************************************** + * Name: up_addrenv_detach + * + * Description: + * This function is called when a task or thread exits in order to release + * its reference to an address environment. The address environment, + * however, should persist until up_addrenv_destroy() is called when the + * task group is itself destroyed. Any resources unique to this thread + * may be destroyed now. + * + * NOTE: In some platforms, nothing will need to be done in this case. + * Simply being a member of the group that has the address environment + * may be sufficient. + * + * Input Parameters: + * group - The group to which the thread belonged. + * tcb - The TCB of the task or thread whose the address environment will + * be released. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int up_addrenv_detach(struct task_group_s *group, struct tcb_s *tcb) +{ + /* There is nothing that needs to be done */ + + return OK; +} diff --git a/arch/risc-v/src/common/riscv_mmu.h b/arch/risc-v/src/common/riscv_mmu.h index 461837f73a6..b02a1a72a8e 100644 --- a/arch/risc-v/src/common/riscv_mmu.h +++ b/arch/risc-v/src/common/riscv_mmu.h @@ -26,6 +26,10 @@ #define RV_MMU_PAGE_SHIFT (12) #define RV_MMU_PAGE_SIZE (1 << RV_MMU_PAGE_SHIFT) /* 4K pages */ +/* Entries per PGT */ + +#define RV_MMU_PAGE_ENTRIES (RV_MMU_PAGE_SIZE / sizeof(uintptr_t)) + /* Supervisor Address Translation and Protection (satp) */ #define SATP_PPN_SHIFT (0) @@ -90,10 +94,10 @@ #ifdef CONFIG_ARCH_MMU_TYPE_SV39 #define RV_MMU_PTE_PADDR_SHIFT (10) -#define RV_MMU_PTE_PPN_MASK ((1 << RV_MMU_PTE_PADDR_SHIFT) - 1) +#define RV_MMU_PTE_PPN_MASK (((1ul << 44) - 1) << RV_MMU_PTE_PADDR_SHIFT) #define RV_MMU_PTE_PPN_SHIFT (2) #define RV_MMU_VPN_WIDTH (9) -#define RV_MMU_VPN_MASK ((1 << RV_MMU_VPN_WIDTH) - 1) +#define RV_MMU_VPN_MASK ((1ul << RV_MMU_VPN_WIDTH) - 1) #define RV_MMU_PT_LEVELS (3) #define RV_MMU_VADDR_SHIFT(_n) (RV_MMU_PAGE_SHIFT + RV_MMU_VPN_WIDTH * \ (RV_MMU_PT_LEVELS - (_n))) @@ -244,6 +248,28 @@ static inline void mmu_enable(uintptr_t pgbase, uint16_t asid) mmu_write_satp(reg); } +/**************************************************************************** + * Name: mmu_pte_to_paddr + * + * Description: + * Extract physical address from PTE + * + * Input Parameters: + * pte - Page table entry + * + * Returned Value: + * Physical address from PTE + * + ****************************************************************************/ + +static inline uintptr_t mmu_pte_to_paddr(uintptr_t pte) +{ + uintptr_t paddr = pte; + paddr &= RV_MMU_PTE_PPN_MASK; /* Remove flags */ + paddr <<= RV_MMU_PTE_PPN_SHIFT; /* Move to correct position */ + return paddr; +} + /**************************************************************************** * Name: mmu_ln_setentry * @@ -279,6 +305,7 @@ void mmu_ln_setentry(uint32_t ptlevel, uintptr_t lnvaddr, uintptr_t paddr, * level n * vaddr - The virtual address to get pte for. Must be aligned to a PPN * address boundary which is dependent on the level of the entry + * ****************************************************************************/ uintptr_t mmu_ln_getentry(uint32_t ptlevel, uintptr_t lnvaddr, diff --git a/arch/risc-v/src/common/riscv_pgalloc.c b/arch/risc-v/src/common/riscv_pgalloc.c new file mode 100644 index 00000000000..0542746530a --- /dev/null +++ b/arch/risc-v/src/common/riscv_pgalloc.c @@ -0,0 +1,222 @@ +/**************************************************************************** + * arch/risc-v/src/common/riscv_pgalloc.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "riscv_mmu.h" + +#ifdef CONFIG_BUILD_KERNEL + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Last PGT level */ + +#define PGT_LAST (RV_MMU_PT_LEVELS) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: wipe_page + * + * Description: + * Wipe a page of physical memory, first mapping it into virtual memory. + * + * Input Parameters: + * paddr - Physical address of page + * + ****************************************************************************/ + +static inline void wipe_page(uintptr_t paddr) +{ + uintptr_t vaddr = paddr; + memset((void *)vaddr, 0, MM_PGSIZE); +} + +/**************************************************************************** + * Name: get_pgtable + * + * Description: + * Get the physical address of the last page table level corresponding to + * 'vaddr' + * + ****************************************************************************/ + +static uintptr_t get_pgtable(group_addrenv_t *addrenv, uintptr_t vaddr) +{ + uintptr_t paddr; + uintptr_t ptprev; + uint32_t ptlevel; + + /* Get the current level MAX_LEVELS-1 entry corresponding to this vaddr */ + + ptlevel = ARCH_SPGTS; + ptprev = addrenv->spgtables[ARCH_SPGTS - 1]; + paddr = mmu_pte_to_paddr(mmu_ln_getentry(ptlevel, ptprev, vaddr)); + + if (!paddr) + { + /* No page table has been allocated... allocate one now */ + + paddr = mm_pgalloc(1); + if (paddr) + { + /* Wipe the page and assign it */ + + wipe_page(paddr); + mmu_ln_setentry(ptlevel, ptprev, paddr, vaddr, MMU_UPGT_FLAGS); + } + } + + /* Flush the data cache, so the changes are committed to memory */ + + __DMB(); + + return paddr; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pgalloc + * + * Description: + * If there is a page allocator in the configuration and if and MMU is + * available to map physical addresses to virtual address, then function + * must be provided by the platform-specific code. This is part of the + * implementation of sbrk(). This function will allocate the requested + * number of pages using the page allocator and map them into consecutive + * virtual addresses beginning with 'brkaddr' + * + * NOTE: This function does not use the up_ naming standard because it + * is indirectly callable from user-space code via a system trap. + * Therefore, it is a system interface and follows a different naming + * convention. + * + * Input Parameters: + * brkaddr - The heap break address. The next page will be allocated and + * mapped to this address. Must be page aligned. If the memory manager + * has not yet been initialized and this is the first block requested for + * the heap, then brkaddr should be zero. pgalloc will then assigned the + * well-known virtual address of the beginning of the heap. + * npages - The number of pages to allocate and map. Mapping of pages + * will be contiguous beginning beginning at 'brkaddr' + * + * Returned Value: + * The (virtual) base address of the mapped page will returned on success. + * Normally this will be the same as the 'brkaddr' input. However, if + * the 'brkaddr' input was zero, this will be the virtual address of the + * beginning of the heap. Zero is returned on any failure. + * + ****************************************************************************/ + +uintptr_t pgalloc(uintptr_t brkaddr, unsigned int npages) +{ + struct tcb_s *tcb = nxsched_self(); + struct task_group_s *group; + uintptr_t ptlast; + uintptr_t paddr; + uintptr_t vaddr; + + DEBUGASSERT(tcb && tcb->group); + group = tcb->group; + + /* The current implementation only supports extending the user heap + * region as part of the implementation of user sbrk(). This function + * needs to be expanded to also handle (1) extending the user stack + * space and (2) extending the kernel memory regions as well. + */ + + DEBUGASSERT((group->tg_flags & GROUP_FLAG_ADDRENV) != 0); + + /* brkaddr = 0 means that no heap has yet been allocated */ + + if (!brkaddr) + { + brkaddr = group->tg_addrenv.heapvbase; + } + + /* Start mapping from the old heap break address */ + + vaddr = brkaddr; + + /* Sanity checks */ + + DEBUGASSERT(brkaddr >= group->tg_addrenv.heapvbase); + DEBUGASSERT(MM_ISALIGNED(brkaddr)); + + for (; npages > 0; npages--) + { + /* Get the address of the last level page table */ + + ptlast = get_pgtable(&group->tg_addrenv, vaddr); + if (!ptlast) + { + return 0; + } + + /* Allocate physical memory for the new heap */ + + paddr = mm_pgalloc(1); + if (!paddr) + { + return 0; + } + + /* Wipe the memory */ + + wipe_page(paddr); + + /* Then add the reference */ + + mmu_ln_setentry(PGT_LAST, ptlast, paddr, vaddr, MMU_UDATA_FLAGS); + vaddr += MM_PGSIZE; + } + + /* Flush the data cache, so the changes are committed to memory */ + + __DMB(); + + return brkaddr; +} + +#endif /* CONFIG_BUILD_KERNEL */ diff --git a/arch/risc-v/src/common/riscv_swint.c b/arch/risc-v/src/common/riscv_swint.c index 8a117d66332..6f279766e22 100644 --- a/arch/risc-v/src/common/riscv_swint.c +++ b/arch/risc-v/src/common/riscv_swint.c @@ -32,6 +32,7 @@ #include #include +#include #include #include