/* * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2021-01-30 lizhirui first version */ #include #include #include #include "page.h" #include #include #include #include "riscv.h" #include "riscv_mmu.h" #include "mmu.h" void *current_mmu_table = RT_NULL; static void rt_hw_cpu_tlb_invalidate() { rt_size_t satpv = read_csr(satp); write_csr(satp,satpv); mmu_flush_tlb(); } void *rt_hw_mmu_tbl_get() { return current_mmu_table; } void rt_hw_mmu_switch(void *mmu_table) { current_mmu_table = mmu_table; RT_ASSERT(__CHECKALIGN(mmu_table,PAGE_OFFSET_BIT)); mmu_set_pagetable((rt_ubase_t)mmu_table); rt_hw_cpu_dcache_clean_all(); rt_hw_cpu_icache_invalidate_all(); } int rt_hw_mmu_map_init(rt_mmu_info *mmu_info,void *v_address,rt_size_t size,rt_size_t *vtable,rt_size_t pv_off) { size_t l1_off,va_s,va_e; rt_base_t level; if((!mmu_info) || (!vtable)) { return -1; } va_s = (rt_size_t)v_address; va_e = ((rt_size_t)v_address) + size - 1; if(va_e < va_s) { return -1; } //convert address to level 1 page frame id va_s = GET_L1(va_s); va_e = GET_L1(va_e); if(va_s == 0) { return -1; } level = rt_hw_interrupt_disable(); //vtable initialization check for(l1_off = va_s;l1_off <= va_e;l1_off++) { size_t v = vtable[l1_off]; if(v) { rt_hw_interrupt_enable(level); return 0; } } mmu_info -> vtable = vtable; mmu_info -> vstart = va_s; mmu_info -> vend = va_e; mmu_info -> pv_off = pv_off; rt_hw_interrupt_enable(level); return 0; } void rt_hw_mmu_kernel_map_init(rt_mmu_info *mmu_info,rt_size_t vaddr_start,rt_size_t size) { rt_size_t paddr_start = __UMASKVALUE(VPN_TO_PPN(vaddr_start,mmu_info -> pv_off),PAGE_OFFSET_MASK); rt_size_t va_s = GET_L1(vaddr_start); rt_size_t va_e = GET_L1(vaddr_start + size - 1); rt_size_t i; for(i = va_s;i <= va_e;i++) { mmu_info -> vtable[i] = COMBINEPTE(paddr_start,PAGE_ATTR_RWX | PTE_G | PTE_V | PTE_CACHE | PTE_SHARE | PTE_BUF | PTE_A | PTE_D); paddr_start += L1_PAGE_SIZE; } rt_hw_cpu_tlb_invalidate(); } //find a range of free virtual address specified by pages static rt_size_t find_vaddr(rt_mmu_info *mmu_info,rt_size_t pages) { rt_size_t l1_off,l2_off,l3_off; rt_size_t *mmu_l1,*mmu_l2,*mmu_l3; rt_size_t find_l1 = 0,find_l2 = 0,find_l3 = 0; rt_size_t n = 0; if(!pages) { return 0; } if(!mmu_info) { return 0; } for(l1_off = mmu_info -> vstart;l1_off <= mmu_info -> vend;l1_off++) { mmu_l1 = ((rt_size_t *)mmu_info -> vtable) + l1_off; if(PTE_USED(*mmu_l1)) { RT_ASSERT(!PAGE_IS_LEAF(*mmu_l1)); mmu_l2 = (rt_size_t *)PPN_TO_VPN(GET_PADDR(*mmu_l1),mmu_info -> pv_off); for(l2_off = 0;l2_off < __SIZE(VPN1_BIT);l2_off++) { if(PTE_USED(*(mmu_l2 + l2_off))) { RT_ASSERT(!PAGE_IS_LEAF(*(mmu_l2 + l2_off))); mmu_l3 = (rt_size_t *)PPN_TO_VPN(GET_PADDR(*(mmu_l2 + l2_off)),mmu_info -> pv_off); for(l3_off = 0;l3_off < __SIZE(VPN0_BIT);l3_off++) { if(PTE_USED(*(mmu_l3 + l3_off))) { RT_ASSERT(PAGE_IS_LEAF(*(mmu_l3 + l3_off))); n = 0;//in use } else { if(!n) { find_l1 = l1_off; find_l2 = l2_off; find_l3 = l3_off; } n++; if(n >= pages) { return COMBINEVADDR(find_l1,find_l2,find_l3); } } } } else { if(!n) { find_l1 = l1_off; find_l2 = l2_off; find_l3 = 0; } n += __SIZE(VPN0_BIT); if(n >= pages) { return COMBINEVADDR(find_l1,find_l2,find_l3); } } } } else { if(!n) { find_l1 = l1_off; find_l2 = 0; find_l3 = 0; } n += __SIZE(VPN1_BIT); if(n >= pages) { return COMBINEVADDR(find_l1,find_l2,find_l3); } } } return 0; } //check whether the range of virtual address are free static int check_vaddr(rt_mmu_info *mmu_info,void *va,rt_size_t pages) { rt_size_t loop_va = __UMASKVALUE((rt_size_t)va,PAGE_OFFSET_MASK); rt_size_t l1_off,l2_off,l3_off; rt_size_t *mmu_l1,*mmu_l2,*mmu_l3; if(!pages) { return -1; } if(!mmu_info) { return -1; } while(pages--) { l1_off = GET_L1(loop_va); l2_off = GET_L2(loop_va); l3_off = GET_L3(loop_va); mmu_l1 = ((rt_size_t *)mmu_info -> vtable) + l1_off; if(PTE_USED(*mmu_l1)) { RT_ASSERT(!PAGE_IS_LEAF(*mmu_l1)); mmu_l2 = (rt_size_t *)PPN_TO_VPN(GET_PADDR(*mmu_l1),mmu_info -> pv_off) + l2_off; if(PTE_USED(*mmu_l2)) { RT_ASSERT(!PAGE_IS_LEAF(*mmu_l2)); mmu_l3 = (rt_size_t *)PPN_TO_VPN(GET_PADDR(*mmu_l2),mmu_info -> pv_off) + l3_off; if(PTE_USED(*mmu_l3)) { RT_ASSERT(PAGE_IS_LEAF(*mmu_l3)); return -1; } } } loop_va += PAGE_SIZE; } return 0; } static void __rt_hw_mmu_unmap(rt_mmu_info *mmu_info,void *v_addr,rt_size_t npages) { rt_size_t loop_va = __UMASKVALUE((rt_size_t)v_addr,PAGE_OFFSET_MASK); rt_size_t l1_off,l2_off,l3_off; rt_size_t *mmu_l1,*mmu_l2,*mmu_l3; rt_size_t *ref_cnt; RT_ASSERT(mmu_info); while(npages--) { l1_off = (rt_size_t)GET_L1(loop_va); RT_ASSERT((l1_off >= mmu_info -> vstart) && (l1_off <= mmu_info -> vend)); l2_off = (rt_size_t)GET_L2(loop_va); l3_off = (rt_size_t)GET_L3(loop_va); mmu_l1 = ((rt_size_t *)mmu_info -> vtable) + l1_off; RT_ASSERT(PTE_USED(*mmu_l1)) RT_ASSERT(!PAGE_IS_LEAF(*mmu_l1)); mmu_l2 = ((rt_size_t *)PPN_TO_VPN(GET_PADDR(*mmu_l1),mmu_info -> pv_off)) + l2_off; RT_ASSERT(PTE_USED(*mmu_l2)); RT_ASSERT(!PAGE_IS_LEAF(*mmu_l2)); mmu_l3 = ((rt_size_t *)PPN_TO_VPN(GET_PADDR(*mmu_l2),mmu_info -> pv_off)) + l3_off; RT_ASSERT(PTE_USED(*mmu_l3)); RT_ASSERT(PAGE_IS_LEAF(*(mmu_l3))); *mmu_l3 = 0; rt_hw_cpu_dcache_clean(mmu_l3,sizeof(*mmu_l3)); mmu_l3 -= l3_off; ref_cnt = mmu_l3 + __SIZE(VPN0_BIT); (*ref_cnt)--; if(!*ref_cnt) { //release level 3 page rt_pages_free(mmu_l3,1);//entry page and ref_cnt page *mmu_l2 = 0; rt_hw_cpu_dcache_clean(mmu_l2,sizeof(*mmu_l2)); mmu_l2 -= l2_off; ref_cnt = mmu_l2 + __SIZE(VPN1_BIT); (*ref_cnt)--; if(!*ref_cnt) { //release level 2 page rt_pages_free(mmu_l2,1);//entry page and ref_cnt page *mmu_l1 = 0; rt_hw_cpu_dcache_clean(mmu_l1,sizeof(*mmu_l1)); } } loop_va += PAGE_SIZE; } } static int __rt_hw_mmu_map(rt_mmu_info *mmu_info,void *v_addr,void *p_addr,rt_size_t npages,rt_size_t attr) { rt_size_t loop_va = __UMASKVALUE((rt_size_t)v_addr,PAGE_OFFSET_MASK); rt_size_t loop_pa = __UMASKVALUE((rt_size_t)p_addr,PAGE_OFFSET_MASK); rt_size_t l1_off,l2_off,l3_off; rt_size_t *mmu_l1,*mmu_l2,*mmu_l3; rt_size_t *ref_cnt; //rt_kprintf("v_addr = 0x%p,p_addr = 0x%p,npages = %lu\n",v_addr,p_addr,npages); if(!mmu_info) { return -1; } while(npages--) { l1_off = GET_L1(loop_va); l2_off = GET_L2(loop_va); l3_off = GET_L3(loop_va); mmu_l1 = ((rt_size_t *)mmu_info -> vtable) + l1_off; if(PTE_USED(*mmu_l1)) { RT_ASSERT(!PAGE_IS_LEAF(*mmu_l1)); mmu_l2 = (rt_size_t *)PPN_TO_VPN(GET_PADDR(*mmu_l1),mmu_info -> pv_off); } else { mmu_l2 = (rt_size_t *)rt_pages_alloc(1); if(mmu_l2) { rt_memset(mmu_l2,0,PAGE_SIZE * 2); rt_hw_cpu_dcache_clean(mmu_l2,PAGE_SIZE * 2); *mmu_l1 = COMBINEPTE((rt_size_t)VPN_TO_PPN(mmu_l2,mmu_info -> pv_off),PAGE_DEFAULT_ATTR_NEXT); rt_hw_cpu_dcache_clean(mmu_l1,sizeof(*mmu_l1)); } else { __rt_hw_mmu_unmap(mmu_info,v_addr,npages); return -1; } } if(PTE_USED(*(mmu_l2 + l2_off))) { RT_ASSERT(!PAGE_IS_LEAF(*(mmu_l2 + l2_off))); mmu_l3 = (rt_size_t *)PPN_TO_VPN(GET_PADDR(*(mmu_l2 + l2_off)),mmu_info -> pv_off); } else { mmu_l3 = (rt_size_t *)rt_pages_alloc(1); if(mmu_l3) { rt_memset(mmu_l3,0,PAGE_SIZE * 2); rt_hw_cpu_dcache_clean(mmu_l3,PAGE_SIZE * 2); *(mmu_l2 + l2_off) = COMBINEPTE((rt_size_t)VPN_TO_PPN(mmu_l3,mmu_info -> pv_off),PAGE_DEFAULT_ATTR_NEXT); rt_hw_cpu_dcache_clean(mmu_l2,sizeof(*mmu_l2)); ref_cnt = mmu_l2 + __SIZE(VPN1_BIT); (*ref_cnt)++; } else { __rt_hw_mmu_unmap(mmu_info,v_addr,npages); return -1; } } RT_ASSERT(!PTE_USED(*(mmu_l3 + l3_off))); ref_cnt = mmu_l3 + __SIZE(VPN0_BIT); (*ref_cnt)++; *(mmu_l3 + l3_off) = COMBINEPTE((rt_size_t)loop_pa,PAGE_DEFAULT_ATTR_LEAF); rt_hw_cpu_dcache_clean(mmu_l3 + l3_off,sizeof(*(mmu_l3 + l3_off))); loop_va += PAGE_SIZE; loop_pa += PAGE_SIZE; } return 0; } void *_rt_hw_mmu_map(rt_mmu_info *mmu_info,void *v_addr,void *p_addr,rt_size_t size,rt_size_t attr) { rt_size_t pa_s,pa_e; rt_size_t vaddr; rt_size_t pages; int ret; if(!size) { return 0; } pa_s = (rt_size_t)p_addr; pa_e = ((rt_size_t)p_addr) + size - 1; pa_s = GET_PF_ID(pa_s); pa_e = GET_PF_ID(pa_e); pages = pa_e - pa_s + 1; if(v_addr) { vaddr = (rt_size_t)v_addr; pa_s = (rt_size_t)p_addr; if(GET_PF_OFFSET(vaddr) != GET_PF_OFFSET(pa_s)) { return 0; } vaddr = __UMASKVALUE(vaddr,PAGE_OFFSET_MASK); if(check_vaddr(mmu_info,(void *)vaddr,pages) != 0) { return 0; } } else { vaddr = find_vaddr(mmu_info,pages); } if(vaddr) { ret = __rt_hw_mmu_map(mmu_info,(void *)vaddr,p_addr,pages,attr); if(ret == 0) { rt_hw_cpu_tlb_invalidate(); return (void *)(vaddr | GET_PF_OFFSET((rt_size_t)p_addr)); } } return 0; } static int __rt_hw_mmu_map_auto(rt_mmu_info *mmu_info,void *v_addr,rt_size_t npages,rt_size_t attr) { rt_size_t loop_va = __UMASKVALUE((rt_size_t)v_addr,PAGE_OFFSET_MASK); rt_size_t loop_pa; rt_size_t l1_off,l2_off,l3_off; rt_size_t *mmu_l1,*mmu_l2,*mmu_l3; rt_size_t *ref_cnt; rt_size_t i; void *va,*pa; if(!mmu_info) { return -1; } while(npages--) { loop_pa = (rt_size_t)rt_pages_alloc(0); if(!loop_pa) { goto err; } if(__rt_hw_mmu_map(mmu_info,(void *)loop_va,(void *)loop_pa,1,attr) < 0) { goto err; } loop_va += PAGE_SIZE; } return 0; err: va = (void *)__UMASKVALUE((rt_size_t)v_addr,PAGE_OFFSET_MASK); for(i = 0;i < npages;i++) { pa = rt_hw_mmu_v2p(mmu_info,va); if(pa) { rt_pages_free((void *)PPN_TO_VPN(pa,mmu_info -> pv_off),0); } va = (void *)((rt_uint8_t *)va + PAGE_SIZE); } __rt_hw_mmu_unmap(mmu_info,v_addr,npages); return -1; } void *_rt_hw_mmu_map_auto(rt_mmu_info *mmu_info,void *v_addr,rt_size_t size,rt_size_t attr) { rt_size_t vaddr; rt_size_t offset; rt_size_t pages; int ret; if(!size) { return 0; } offset = GET_PF_OFFSET((rt_size_t)v_addr); size += (offset + PAGE_SIZE - 1); pages = size >> PAGE_OFFSET_BIT; if(v_addr) { vaddr = __UMASKVALUE((rt_size_t)v_addr,PAGE_OFFSET_MASK); if(check_vaddr(mmu_info,(void *)vaddr,pages) != 0) { return 0; } } else { vaddr = find_vaddr(mmu_info,pages); } if(vaddr) { ret = __rt_hw_mmu_map_auto(mmu_info,(void *)vaddr,pages,attr); if(ret == 0) { rt_hw_cpu_tlb_invalidate(); return (void *)(vaddr | offset); } } return 0; } void _rt_hw_mmu_unmap(rt_mmu_info *mmu_info,void *v_addr,rt_size_t size) { rt_size_t va_s,va_e; rt_size_t pages; va_s = ((rt_size_t)v_addr) >> PAGE_OFFSET_BIT; va_e = (((rt_size_t)v_addr) + size - 1) >> PAGE_OFFSET_BIT; pages = va_e - va_s + 1; __rt_hw_mmu_unmap(mmu_info,v_addr,pages); rt_hw_cpu_tlb_invalidate(); } void *rt_hw_mmu_map(rt_mmu_info *mmu_info,void *v_addr,void *p_addr,rt_size_t size,rt_size_t attr) { void *ret; rt_base_t level; level = rt_hw_interrupt_disable(); ret = _rt_hw_mmu_map(mmu_info,v_addr,p_addr,size,attr); rt_hw_interrupt_enable(level); return ret; } void *rt_hw_mmu_map_auto(rt_mmu_info *mmu_info,void *v_addr,rt_size_t size,rt_size_t attr) { void *ret; rt_base_t level; level = rt_hw_interrupt_disable(); ret = _rt_hw_mmu_map_auto(mmu_info,v_addr,size,attr); rt_hw_interrupt_enable(level); return ret; } void rt_hw_mmu_unmap(rt_mmu_info *mmu_info,void *v_addr,rt_size_t size) { rt_base_t level; level = rt_hw_interrupt_disable(); _rt_hw_mmu_unmap(mmu_info,v_addr,size); rt_hw_interrupt_enable(level); } void *_rt_hw_mmu_v2p(rt_mmu_info *mmu_info,void *v_addr) { rt_size_t l1_off,l2_off,l3_off; rt_size_t *mmu_l1,*mmu_l2,*mmu_l3; rt_size_t pa; l1_off = GET_L1((rt_size_t)v_addr); l2_off = GET_L2((rt_size_t)v_addr); l3_off = GET_L3((rt_size_t)v_addr); if(!mmu_info) { return RT_NULL; } mmu_l1 = ((rt_size_t *)mmu_info -> vtable) + l1_off; if(PTE_USED(*mmu_l1)) { RT_ASSERT(!PAGE_IS_LEAF(*mmu_l1)); mmu_l2 = (rt_size_t *)PPN_TO_VPN(GET_PADDR(*mmu_l1),mmu_info -> pv_off); if(PTE_USED(*(mmu_l2 + l2_off))) { RT_ASSERT(!PAGE_IS_LEAF(*(mmu_l2 + l2_off))); mmu_l3 = (rt_size_t *)PPN_TO_VPN(GET_PADDR(*(mmu_l2 + l2_off)),mmu_info -> pv_off); if(PTE_USED(*(mmu_l3 + l3_off))) { RT_ASSERT(PAGE_IS_LEAF(*(mmu_l3 + l3_off))); return (void *)(GET_PADDR(*(mmu_l3 + l3_off)) | GET_PF_OFFSET((rt_size_t)v_addr)); } } } return RT_NULL; } void *rt_hw_mmu_v2p(rt_mmu_info *mmu_info,void *v_addr) { void *ret; rt_base_t level; level = rt_hw_interrupt_disable(); ret = _rt_hw_mmu_v2p(mmu_info,v_addr); rt_hw_interrupt_enable(level); return ret; }