Add simple MMU driver for RISC-V (Sv39)

Sv39 is the only mode supported for now. However, it should be trivial
to extend the driver to support the other modes (including Sv32) as well.

The driver is tested with mpfs only, but it should work with any riscv
implementation.
This commit is contained in:
Ville Juven 2022-01-19 10:14:28 +02:00 committed by Xiang Xiao
parent 2b63b811e0
commit 926a19217e
5 changed files with 336 additions and 2 deletions

View File

@ -96,6 +96,8 @@ config ARCH_CHIP_MPFS
select ARCH_HAVE_FPU
select ARCH_HAVE_DPFPU
select ARCH_HAVE_MPU
select ARCH_HAVE_MMU
select ARCH_MMU_TYPE_SV39
select ARCH_HAVE_RESET
select ARCH_HAVE_SPI_CS_CONTROL
select ARCH_HAVE_PWM_MULTICHAN
@ -182,6 +184,10 @@ config ARCH_RISCV_INTXCPT_EXTREGS
endif
config ARCH_MMU_TYPE_SV39
bool
default n
source "arch/risc-v/src/opensbi/Kconfig"
source "arch/risc-v/src/common/Kconfig"

View File

@ -0,0 +1,107 @@
/****************************************************************************
* arch/risc-v/src/common/riscv_mmu.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 <assert.h>
#include <nuttx/config.h>
#include <nuttx/arch.h>
#include <arch/csr.h>
#include "riscv_internal.h"
#include "riscv_mmu.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Functions
****************************************************************************/
void mmu_ln_setentry(uint32_t ptlevel, uintptr_t lnvaddr, uintptr_t paddr,
uintptr_t vaddr, uint32_t mmuflags)
{
uintptr_t *lntable = (uintptr_t *)lnvaddr;
uint32_t index;
DEBUGASSERT(ptlevel <= RV_MMU_PT_LEVELS);
/* Test if this is a leaf PTE, if it is, set A+D even if they are not used
* by the implementation.
*
* If not, clear A+D+U because the spec. says:
* For non-leaf PTEs, the D, A, and U bits are reserved for future use and
* must be cleared by software for forward compatibility.
*/
if (mmuflags & PTE_LEAF_MASK)
{
mmuflags |= (PTE_A | PTE_D);
}
else
{
mmuflags &= ~(PTE_A | PTE_D | PTE_U);
}
/* Make sure the entry is valid */
mmuflags |= PTE_VALID;
/* Calculate index for lntable */
index = (vaddr >> RV_MMU_VADDR_SHIFT(ptlevel)) & RV_MMU_VPN_MASK;
/* Move PPN to correct position */
paddr >>= RV_MMU_PTE_PPN_SHIFT;
/* Save it */
lntable[index] = (paddr | mmuflags);
/* Update with memory by flushing the cache(s) */
mmu_invalidate_tlb_by_vaddr(vaddr);
}
void mmu_ln_map_region(uint32_t ptlevel, uintptr_t lnvaddr, uintptr_t paddr,
uintptr_t vaddr, size_t size, uint32_t mmuflags)
{
uintptr_t end_paddr = paddr + size;
while (paddr < end_paddr)
{
mmu_ln_setentry(ptlevel, lnvaddr, paddr, vaddr, mmuflags);
paddr += RV_MMU_PAGE_SIZE;
vaddr += RV_MMU_PAGE_SIZE;
}
}

View File

@ -0,0 +1,206 @@
/****************************************************************************
* arch/risc-v/src/common/riscv_mmu.h
*
* 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.
*
****************************************************************************/
#ifndef ___ARCH_RISC_V_SRC_COMMON_RISCV_MMU_H_
#define ___ARCH_RISC_V_SRC_COMMON_RISCV_MMU_H_
/* RV32/64 page size */
#define RV_MMU_PAGE_SHIFT (12)
#define RV_MMU_PAGE_SIZE (1 << RV_MMU_PAGE_SHIFT) /* 4K pages */
/* Supervisor Address Translation and Protection (satp) */
#define SATP_PPN_SHIFT (0)
#define SATP_PPN_MASK (((1ul << 44) - 1) << SATP_PPN_SHIFT)
#define SATP_ASID_SHIFT (44)
#define SATP_ASID_MASK (((1ul << 16) - 1) << SATP_ASID_SHIFT)
#define SATP_MODE_SHIFT (60)
#define SATP_MODE_MASK (((1ul << 4) - 1) << SATP_MODE_SHIFT)
/* Modes, for RV32 only 1 is valid, for RV64 1-7 and 10-15 are reserved */
#define SATP_MODE_BARE (0ul)
#define SATP_MODE_SV32 (1ul)
#define SATP_MODE_SV39 (8ul)
#define SATP_MODE_SV48 (9ul)
/* satp address to PPN translation */
#define SATP_ADDR_TO_PPN(_addr) ((_addr) >> RV_MMU_PAGE_SHIFT)
/* Common Page Table Entry (PTE) bits */
#define PTE_VALID (1 << 0) /* PTE is valid */
#define PTE_R (1 << 1) /* Page is readable */
#define PTE_W (1 << 2) /* Page is writable */
#define PTE_X (1 << 3) /* Page is executable */
#define PTE_U (1 << 4) /* Page is a user mode page */
#define PTE_G (1 << 5) /* Page is a global mapping */
#define PTE_A (1 << 6) /* Page has been accessed */
#define PTE_D (1 << 7) /* Page is dirty */
/* Check if leaf PTE entry or not (if X/W/R are set it is) */
#define PTE_LEAF_MASK (7 << 1)
/* SvX definitions, only Sv39 is currently supported, but it should be
* trivial to extend the driver to support other SvX implementations
*
* Sv39 has:
* - 4K page size
* - 3 page table levels
* - 9-bit VPN width
*/
#ifdef CONFIG_ARCH_MMU_TYPE_SV39
#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_PT_LEVELS (3)
#define RV_MMU_VADDR_SHIFT(_n) (RV_MMU_PAGE_SHIFT + RV_MMU_VPN_WIDTH * \
(RV_MMU_PT_LEVELS - (_n)))
#define RV_MMU_SATP_MODE SATP_MODE_SV39
#else
#error "Unsupported RISC-V MMU implementation selected"
#endif /* CONFIG_ARCH_MMU_TYPE_SV39 */
/****************************************************************************
* Name: mmu_enable
*
* Description:
* Enable MMU and set the base page table address
*
* Input Parameters:
* pgbase - The physical base address of the translation table base
* asid - Address space identifier. This can be used to identify different
* address spaces. It is not necessary to use this, nor is it necessary
* for the RISC-V implementation to implement such bits. This means in
* practice that the value should not be used in this generic driver.
*
****************************************************************************/
static inline void mmu_enable(uintptr_t pgbase, uint16_t asid)
{
uintptr_t reg;
reg = ((RV_MMU_SATP_MODE << SATP_MODE_SHIFT) & SATP_MODE_MASK);
reg |= (((uintptr_t)asid << SATP_ASID_SHIFT) & SATP_ASID_MASK);
reg |= ((SATP_ADDR_TO_PPN(pgbase) << SATP_PPN_SHIFT) & SATP_PPN_MASK);
/* Commit to satp and synchronize */
__asm__ __volatile__
(
"csrw satp, %0\n"
"sfence.vma x0, x0\n"
"fence iorw, iorw\n"
:
: "rK" (reg)
: "memory"
);
}
/****************************************************************************
* Name: mmu_invalidate_tlb_by_vaddr
*
* Description:
* Flush the TLB for vaddr entry
*
* Input Parameters:
* vaddr - The virtual address to flush
*
****************************************************************************/
static inline void mmu_invalidate_tlb_by_vaddr(uintptr_t vaddr)
{
__asm__ __volatile__
(
"sfence.vma x0, %0\n"
:
: "rK" (vaddr)
: "memory"
);
}
/****************************************************************************
* Name: mmu_invalidate_tlbs
*
* Description:
* Flush the entire TLB
*
****************************************************************************/
static inline void mmu_invalidate_tlbs(void)
{
__asm__ __volatile__
(
"sfence.vma x0, x0\n"
:
:
: "memory"
);
}
/****************************************************************************
* Name: mmu_ln_setentry
*
* Description:
* Set a level n translation table entry.
*
* Input Parameters:
* ptlevel - The translation table level, amount of levels is
* MMU implementation specific
* lnvaddr - The virtual address of the beginning of the page table at
* level n
* paddr - The physical address to be mapped. Must be aligned to a PPN
* address boundary which is dependent on the level of the entry
* vaddr - Must be aligned to a PPN
* address boundary which is dependent on the level of the entry
* mmuflags - The MMU flags to use in the mapping.
*
****************************************************************************/
void mmu_ln_setentry(uint32_t ptlevel, uintptr_t lnvaddr, uintptr_t paddr,
uintptr_t vaddr, uint32_t mmuflags);
/****************************************************************************
* Name: mmu_ln_map_region
*
* Description:
* Set a translation table region for level n
*
* Input Parameters:
* ptlevel - The translation table level, amount of levels is
* MMU implementation specific
* lnvaddr - The virtual address of the beginning of the page table at
* level n
* paddr - The physical address to be mapped. Must be aligned to a PPN
* address boundary which is dependent on the level of the entry
* vaddr - Must be aligned to a PPN
* address boundary which is dependent on the level of the entry
* size - The size of the region in bytes
* mmuflags - The MMU flags to use in the mapping.
*
****************************************************************************/
void mmu_ln_map_region(uint32_t ptlevel, uintptr_t lnvaddr, uintptr_t paddr,
uintptr_t vaddr, size_t size, uint32_t mmuflags);
#endif /* ___ARCH_RISC_V_SRC_COMMON_RISCV_MMU_H_ */

View File

@ -60,13 +60,23 @@ CHIP_CSRCS += mpfs_dma.c
endif
ifeq ($(CONFIG_BUILD_PROTECTED),y)
CMN_CSRCS += riscv_task_start.c riscv_pthread_start.c
CMN_CSRCS += riscv_signal_dispatch.c riscv_pmp.c
CMN_CSRCS += riscv_task_start.c
CMN_CSRCS += riscv_pthread_start.c riscv_pthread_exit.c
CMN_CSRCS += riscv_signal_dispatch.c
CMN_UASRCS += riscv_signal_handler.S
CHIP_CSRCS += mpfs_userspace.c
endif
ifeq ($(CONFIG_ARCH_USE_MPU),y)
CMN_CSRCS += riscv_pmp.c
endif
ifeq ($(CONFIG_ARCH_USE_MMU),y)
CMN_CSRCS += riscv_mmu.c
endif
ifeq ($(CONFIG_SPI),y)
CHIP_CSRCS += mpfs_spi.c
endif

View File

@ -108,6 +108,11 @@ __start:
csrw mideleg, 0
csrw medeleg, 0
/* Disable MMU if not done already */
csrw satp, zero
fence
/* invalid all MMU TLB Entry */
sfence.vma x0, x0