From c67502d9b49b2d9bb8c404c9d88d96a07f00a7ae Mon Sep 17 00:00:00 2001 From: Tiago Medicci Serrano Date: Fri, 23 Feb 2024 10:33:24 -0300 Subject: [PATCH] riscv: Implement page-fault exception and on-demand paging When an application is being loaded `up_addrenv_create ` calls `create_region` to create the address environment. Only the first entry is mapped when the region is created. Virtual memory that is not mapped will trigger an exception when accessed. Other memory pages are allocated and mapped on-demand. This enables setting larger heap and stack for the process without compromising the overall system memory. --- arch/Kconfig | 10 ++ arch/risc-v/src/common/riscv_addrenv.c | 8 +- arch/risc-v/src/common/riscv_exception.c | 128 +++++++++++++++++- arch/risc-v/src/common/riscv_internal.h | 1 + .../rv-virt/configs/knsh32_paging/defconfig | 97 +++++++++++++ 5 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 boards/risc-v/qemu-rv/rv-virt/configs/knsh32_paging/defconfig diff --git a/arch/Kconfig b/arch/Kconfig index 73061ece40..74fe13e3c3 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -903,6 +903,16 @@ config ARCH_PGPOOL_SIZE endif # ARCH_PGPOOL_MAPPING endif # ARCH_ADDRENV && ARCH_NEED_ADDRENV_MAPPING +config PAGING + bool "On-demand paging" + default n + depends on BUILD_KERNEL && ARCH_USE_MMU && !ARCH_ROMPGTABLE && !LEGACY_PAGING + ---help--- + If set =y in your configation file, this setting will enable on-demand + paging, which relies on a MMU to enable larger virtual memory spaces + and map it to physical memory on-demand (usually during a page-fault + exception). + menuconfig LEGACY_PAGING bool "Legacy On-demand paging" default n diff --git a/arch/risc-v/src/common/riscv_addrenv.c b/arch/risc-v/src/common/riscv_addrenv.c index f26a5d11c2..a6524abe47 100644 --- a/arch/risc-v/src/common/riscv_addrenv.c +++ b/arch/risc-v/src/common/riscv_addrenv.c @@ -282,7 +282,13 @@ static int create_region(arch_addrenv_t *addrenv, uintptr_t vaddr, /* Then allocate memory for the region data */ - for (j = 0; j < ENTRIES_PER_PGT && nmapped < size; j++) + for (j = 0; +#ifdef CONFIG_PAGING + j < 1; +#else + j < ENTRIES_PER_PGT && nmapped < size; +#endif + j++) { paddr = mm_pgalloc(1); if (!paddr) diff --git a/arch/risc-v/src/common/riscv_exception.c b/arch/risc-v/src/common/riscv_exception.c index f560a60b0a..cb94c153a8 100644 --- a/arch/risc-v/src/common/riscv_exception.c +++ b/arch/risc-v/src/common/riscv_exception.c @@ -30,6 +30,14 @@ #include #include +#ifdef CONFIG_PAGING +# include +#endif + +#ifdef CONFIG_PAGING +# include "pgalloc.h" +# include "riscv_mmu.h" +#endif #include "riscv_internal.h" #include "chip.h" @@ -87,6 +95,117 @@ int riscv_exception(int mcause, void *regs, void *args) return 0; } +/**************************************************************************** + * Name: riscv_fillpage + * + * Description: + * This function is an exception handler for page faults in a RISC-V. + * It is invoked when a page fault exception occurs, which is typically + * when a process tries to access a page that is not currently in memory. + * + * The function takes as arguments the machine cause (mcause) which + * indicates the cause of the exception, a pointer to the register state + * at the time of the exception (regs), and a pointer to any additional + * arguments (args). + * + * The function should handle the exception appropriately, typically by + * loading the required page into memory and updating the page table. + * + * Input Parameters: + * mcause - The machine cause of the exception. + * regs - A pointer to the register state at the time of the exception. + * args - A pointer to any additional arguments. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +#ifdef CONFIG_PAGING +int riscv_fillpage(int mcause, void *regs, void *args) +{ + uintptr_t cause = mcause & RISCV_IRQ_MASK; + uintptr_t ptlast; + uintptr_t ptprev; + uintptr_t paddr; + uintptr_t vaddr; + uint32_t ptlevel; + uintptr_t satp; + uint32_t mmuflags; + + _info("EXCEPTION: %s. MCAUSE: %" PRIxREG ", EPC: %" PRIxREG + ", MTVAL: %" PRIxREG "\n", + mcause > RISCV_MAX_EXCEPTION ? "Unknown" : g_reasons_str[cause], + cause, READ_CSR(CSR_EPC), READ_CSR(CSR_TVAL)); + vaddr = MM_PGALIGNDOWN(READ_CSR(CSR_TVAL)); + if (vaddr >= CONFIG_ARCH_TEXT_VBASE && vaddr <= ARCH_TEXT_VEND) + { + mmuflags = MMU_UTEXT_FLAGS; + + /* Write access to .text region needs to be set according to + * https://github.com/apache/nuttx/pull/6193. + */ + + mmuflags |= PTE_W; + } + else if (vaddr >= CONFIG_ARCH_DATA_VBASE && vaddr <= ARCH_DATA_VEND) + { + mmuflags = MMU_UDATA_FLAGS; + } + else if (vaddr >= CONFIG_ARCH_HEAP_VBASE && vaddr <= ARCH_HEAP_VEND) + { + mmuflags = MMU_UDATA_FLAGS; + } + else + { + _alert("PANIC!!! virtual address not mappable: %" PRIxPTR "\n", vaddr); + up_irq_save(); + CURRENT_REGS = regs; + PANIC_WITH_REGS("panic", regs); + } + + satp = READ_CSR(CSR_SATP); + ptprev = riscv_pgvaddr(mmu_satp_to_paddr(satp)); + ptlevel = ARCH_SPGTS; + 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 */ + + riscv_pgwipe(paddr); + } + + ptlast = riscv_pgvaddr(paddr); + paddr = mm_pgalloc(1); + if (!paddr) + { + return -ENOMEM; + } + + /* Wipe the physical page memory */ + + riscv_pgwipe(paddr); + + /* Then map the virtual address to the physical address */ + + mmu_ln_setentry(ptlevel + 1, ptlast, paddr, vaddr, mmuflags); + + return 0; +} +#endif /* CONFIG_PAGING */ + /**************************************************************************** * Name: riscv_exception_attach * @@ -130,9 +249,16 @@ void riscv_exception_attach(void) #endif irq_attach(RISCV_IRQ_INSTRUCTIONPF, riscv_exception, NULL); + +#ifdef CONFIG_PAGING + irq_attach(RISCV_IRQ_LOADPF, riscv_fillpage, NULL); + irq_attach(RISCV_IRQ_STOREPF, riscv_fillpage, NULL); +#else irq_attach(RISCV_IRQ_LOADPF, riscv_exception, NULL); - irq_attach(RISCV_IRQ_RESERVED, riscv_exception, NULL); irq_attach(RISCV_IRQ_STOREPF, riscv_exception, NULL); +#endif + + irq_attach(RISCV_IRQ_RESERVED, riscv_exception, NULL); #ifdef CONFIG_SMP irq_attach(RISCV_IRQ_SOFT, riscv_pause_handler, NULL); diff --git a/arch/risc-v/src/common/riscv_internal.h b/arch/risc-v/src/common/riscv_internal.h index ac16c0bd87..100bcb79d6 100644 --- a/arch/risc-v/src/common/riscv_internal.h +++ b/arch/risc-v/src/common/riscv_internal.h @@ -312,6 +312,7 @@ void riscv_netinitialize(void); uintptr_t *riscv_doirq(int irq, uintptr_t *regs); int riscv_exception(int mcause, void *regs, void *args); +int riscv_fillpage(int mcause, void *regs, void *args); int riscv_misaligned(int irq, void *context, void *arg); /* Debug ********************************************************************/ diff --git a/boards/risc-v/qemu-rv/rv-virt/configs/knsh32_paging/defconfig b/boards/risc-v/qemu-rv/rv-virt/configs/knsh32_paging/defconfig new file mode 100644 index 0000000000..3c809f0c05 --- /dev/null +++ b/boards/risc-v/qemu-rv/rv-virt/configs/knsh32_paging/defconfig @@ -0,0 +1,97 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_ASSERTIONS_FILENAME is not set +# CONFIG_DISABLE_OS_API is not set +# CONFIG_NDEBUG is not set +# CONFIG_NSH_DISABLE_LOSMART is not set +CONFIG_16550_ADDRWIDTH=0 +CONFIG_16550_UART0=y +CONFIG_16550_UART0_BASE=0x10000000 +CONFIG_16550_UART0_CLOCK=3686400 +CONFIG_16550_UART0_IRQ=35 +CONFIG_16550_UART0_SERIAL_CONSOLE=y +CONFIG_16550_UART=y +CONFIG_ARCH="risc-v" +CONFIG_ARCH_ADDRENV=y +CONFIG_ARCH_BOARD="rv-virt" +CONFIG_ARCH_BOARD_QEMU_RV_VIRT=y +CONFIG_ARCH_CHIP="qemu-rv" +CONFIG_ARCH_CHIP_QEMU_RV32=y +CONFIG_ARCH_CHIP_QEMU_RV=y +CONFIG_ARCH_CHIP_QEMU_RV_ISA_A=y +CONFIG_ARCH_CHIP_QEMU_RV_ISA_C=y +CONFIG_ARCH_CHIP_QEMU_RV_ISA_M=y +CONFIG_ARCH_DATA_NPAGES=128 +CONFIG_ARCH_DATA_VBASE=0xC0100000 +CONFIG_ARCH_HEAP_NPAGES=2048 +CONFIG_ARCH_HEAP_VBASE=0xC0800000 +CONFIG_ARCH_INTERRUPTSTACK=2048 +CONFIG_ARCH_KERNEL_STACKSIZE=3072 +CONFIG_ARCH_PGPOOL_MAPPING=y +CONFIG_ARCH_PGPOOL_PBASE=0x80800000 +CONFIG_ARCH_PGPOOL_SIZE=4194304 +CONFIG_ARCH_PGPOOL_VBASE=0x80800000 +CONFIG_ARCH_RISCV=y +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARCH_TEXT_NPAGES=128 +CONFIG_ARCH_TEXT_VBASE=0xC0000000 +CONFIG_ARCH_USE_MMU=y +CONFIG_ARCH_USE_MPU=y +CONFIG_ARCH_USE_S_MODE=y +CONFIG_BINFMT_ELF_EXECUTABLE=y +CONFIG_BOARD_LATE_INITIALIZE=y +CONFIG_BOARD_LOOPSPERMSEC=6366 +CONFIG_BUILD_KERNEL=y +CONFIG_DEBUG_FEATURES=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DEV_ZERO=y +CONFIG_ELF=y +CONFIG_EXAMPLES_HELLO=m +CONFIG_EXAMPLES_HELLO_STACKSIZE=8192 +CONFIG_FS_PROCFS=y +CONFIG_FS_ROMFS=y +CONFIG_GRAN_INTR=y +CONFIG_IDLETHREAD_STACKSIZE=3072 +CONFIG_INIT_FILEPATH="/system/bin/init" +CONFIG_INIT_MOUNT=y +CONFIG_INIT_MOUNT_FLAGS=0x1 +CONFIG_INIT_MOUNT_TARGET="/system/bin" +CONFIG_INIT_STACKSIZE=3072 +CONFIG_INTELHEX_BINARY=y +CONFIG_LIBC_ENVPATH=y +CONFIG_LIBC_EXECFUNCS=y +CONFIG_LIBC_PERROR_STDOUT=y +CONFIG_LIBC_STRERROR=y +CONFIG_LIBM=y +CONFIG_MM_PGALLOC=y +CONFIG_NFILE_DESCRIPTORS_PER_BLOCK=6 +CONFIG_NSH_ARCHINIT=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_FILE_APPS=y +CONFIG_NSH_READLINE=y +CONFIG_PAGING=y +CONFIG_PATH_INITIAL="/system/bin" +CONFIG_POSIX_SPAWN_DEFAULT_STACKSIZE=1048576 +CONFIG_RAM_SIZE=4194304 +CONFIG_RAM_START=0x80400000 +CONFIG_READLINE_CMD_HISTORY=y +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_LPWORK=y +CONFIG_SCHED_WAITPID=y +CONFIG_SERIAL_UART_ARCH_MMIO=y +CONFIG_SIG_DEFAULT=y +CONFIG_START_MONTH=12 +CONFIG_START_YEAR=2021 +CONFIG_SYMTAB_ORDEREDBYNAME=y +CONFIG_SYSLOG_TIMESTAMP=y +CONFIG_SYSTEM_NSH=y +CONFIG_SYSTEM_NSH_PROGNAME="init" +CONFIG_TESTING_GETPRIME=y +CONFIG_TESTING_OSTEST=y +CONFIG_TLS_LOG2_MAXSTACK=20 +CONFIG_USEC_PER_TICK=1000