本文章的代碼來自于rt-smart中針對(duì)qemu-virt-riscv的bsp
倉庫地址 https://gitee.com/rtthread/rt-thread/tree/rt-smart/
commit ID:d28249c08a152bcf0e1a076cf5b4b082c0a84add
qemu-virt-riscv介紹
簡(jiǎn)介
Virt板不對(duì)應(yīng)于任何真實(shí)硬件的平臺(tái);它是為虛擬機(jī)設(shè)計(jì)的。如果你只是想運(yùn)行Linux等客戶機(jī),而不關(guān)心重現(xiàn)真實(shí)世界硬件的特殊性和局限性,那么它是推薦的板卡類型。
內(nèi)存空間布局(包括外設(shè)地址)
staticconstMemMapEntryvirt_memmap[]={ [VIRT_DEBUG]={0x0,0x100}, [VIRT_MROM]={0x1000,0xf000}, [VIRT_TEST]={0x100000,0x1000}, [VIRT_RTC]={0x101000,0x1000}, [VIRT_CLINT]={0x2000000,0x10000}, [VIRT_ACLINT_SSWI]={0x2F00000,0x4000}, [VIRT_PCIE_PIO]={0x3000000,0x10000}, [VIRT_PLIC]={0xc000000,VIRT_PLIC_SIZE(VIRT_CPUS_MAX*2)}, [VIRT_APLIC_M]={0xc000000,APLIC_SIZE(VIRT_CPUS_MAX)}, [VIRT_APLIC_S]={0xd000000,APLIC_SIZE(VIRT_CPUS_MAX)}, [VIRT_UART0]={0x10000000,0x100},/*串口設(shè)備*/ [VIRT_VIRTIO]={0x10001000,0x1000}, [VIRT_FW_CFG]={0x10100000,0x18}, [VIRT_FLASH]={0x20000000,0x4000000}, [VIRT_IMSIC_M]={0x24000000,VIRT_IMSIC_MAX_SIZE}, [VIRT_IMSIC_S]={0x28000000,VIRT_IMSIC_MAX_SIZE}, [VIRT_PCIE_ECAM]={0x30000000,0x10000000}, [VIRT_PCIE_MMIO]={0x40000000,0x40000000}, [VIRT_DRAM]={0x80000000,0x0},/*DDR空間*/ };
rt-smart針對(duì)virt board的ddr空間規(guī)劃
參考鏈接腳本
bspqemu-virt64-riscvlink.lds
以及board.h中的相關(guān)定義
bspqemu-virt64-riscvdriveroard.h
得到ddr的空間規(guī)劃如下
內(nèi)容 | 地址空間 |
---|---|
代碼段數(shù)據(jù)段??臻g以及bss段 | 0x80200000 ~ __bss_end |
堆空間 | __bss_end ~ __bss_end + 100M |
頁分配空間 | __bss_end + 100M ~ __bss_end + 200M |
rt-smart針對(duì)virt board的初始化
整體初始化
rt_hw_board_init定義了與qemu-virt-riscv相關(guān)的板級(jí)初始化的全部?jī)?nèi)容,包括內(nèi)存系統(tǒng),plic中斷子系統(tǒng),定時(shí)器系統(tǒng)以及串口設(shè)備等。它由rtthread_startup調(diào)用,完整的調(diào)用路徑如下。
(libcpu isc-vvirt64startup_gcc.S)_start->primary_cpu_entry->entry->rtthread_startup->rt_hw_board_init
源碼如下
voidrt_hw_board_init(void) { #ifdefRT_USING_USERSPACE rt_page_init(init_page_region); /*initmmu_infostructure*/ rt_hw_mmu_map_init(&mmu_info,(void*)(USER_VADDR_START-IOREMAP_SIZE),IOREMAP_SIZE,(rt_size_t*)MMUTable,0); //thisAPIisreservedcurrentlysincePLICetchadnotbeenportingcompletelytoMMUversion rt_hw_mmu_kernel_map_init(&mmu_info,0x00000000UL,0x80000000); /*setupregion,andenableMMU*/ rt_hw_mmu_setup(&mmu_info,platform_mem_desc,NUM_MEM_DESC); #endif #ifdefRT_USING_HEAP /*initializememorysystem*/ rt_system_heap_init(RT_HW_HEAP_BEGIN,RT_HW_HEAP_END); #endif plic_init(); rt_hw_interrupt_init(); rt_hw_uart_init(); #ifdefRT_USING_CONSOLE /*setconsoledevice*/ rt_console_set_device(RT_CONSOLE_DEVICE_NAME); #endif/*RT_USING_CONSOLE*/ rt_hw_tick_init(); #ifdefRT_USING_COMPONENTS_INIT rt_components_board_init(); #endif #ifdefRT_USING_HEAP rt_kprintf("heap:[0x%08x-0x%08x] ",(rt_ubase_t)RT_HW_HEAP_BEGIN,(rt_ubase_t)RT_HW_HEAP_END); #endif/*RT_USING_HEAP*/ }
rt_page_init
rt-smart中使用了buddy算法管理了一部分內(nèi)存區(qū)域,系統(tǒng)使用page_alloc來向buddy管理的內(nèi)存區(qū)域申請(qǐng)內(nèi)存資源,像linux一樣每個(gè)page是4k的大小。rt-smart采用buddy算法將系統(tǒng)中部分可用的物理內(nèi)存頁面按照每1個(gè)頁面、2個(gè)頁面、4個(gè)頁面等等劃分為了不同的單元。詳情可參考這篇文章https://club.rt-thread.org/ask/article/3e3a9a0b6d3e2105.html
voidrt_page_init(rt_region_treg) { inti; LOG_D("split0x%08x0x%08x ",reg.start,reg.end); reg.start+=ARCH_PAGE_MASK; reg.start&=~ARCH_PAGE_MASK; reg.end&=~ARCH_PAGE_MASK; { intnr=ARCH_PAGE_SIZE/sizeof(structpage); inttotal=(reg.end-reg.start)>>ARCH_PAGE_SHIFT; intmnr=(total+nr)/(nr+1); LOG_D("nr=0x%08x ",nr); LOG_D("total=0x%08x ",total); LOG_D("mnr=0x%08x ",mnr); RT_ASSERT(mnr>ARCH_PAGE_SHIFT; }
這里rt-smart直接將一部分頁表空間分配給struct page去使用,有可能會(huì)造成頁面的浪費(fèi)。例如當(dāng)total=7,nr=5時(shí),mnr=2,也就是倆個(gè)頁表用于存儲(chǔ)page,五個(gè)頁表是真正可以被alloc_page申請(qǐng)的。但實(shí)際上五個(gè)頁表只需要一個(gè)頁表的空間就可以存放page結(jié)構(gòu)體了,相當(dāng)于浪費(fèi)了一個(gè)頁表。
rt_hw_mmu_map_init
#defineUSER_VADDR_START0x100000000UL #defineIOREMAP_SIZE(1ul<30) 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) { ???/*代碼省略*/ ????mmu_info->vtable=vtable; mmu_info->vstart=va_s; mmu_info->vend=va_e; mmu_info->pv_off=pv_off; return0; }
mmu_info是一個(gè)全局變量,在調(diào)用rt_hw_mmu_map_init后,(USER_VADDR_START - IOREMAP_SIZE) ~ USER_VADDR_START 這片虛擬地址空間將來專門提供給ioremap來使用。也就是ioremap返回的虛擬地址區(qū)間就是0xc0000000 ~ 0xFFFFFFFF
rt_hw_mmu_kernel_map_init
voidrt_hw_mmu_kernel_map_init(rt_mmu_info*mmu_info,rt_size_tvaddr_start,rt_size_tsize) { rt_size_tpaddr_start=__UMASKVALUE(VPN_TO_PPN(vaddr_start,mmu_info->pv_off),PAGE_OFFSET_MASK); rt_size_tva_s=GET_L1(vaddr_start); rt_size_tva_e=GET_L1(vaddr_start+size-1); rt_size_ti; for(i=va_s;i<=?va_e;?i++) ????{ ????????mmu_info->vtable[i]=COMBINEPTE(paddr_start,PAGE_ATTR_RWX|PTE_G|PTE_V); paddr_start+=L1_PAGE_SIZE; } rt_hw_cpu_tlb_invalidate(); }
這里將0x0 ~ 0x80000000的物理地址空間做了offset為0的一比一映射,且只使用了一級(jí)頁表。之后0x80000000之下的地址CPU都可以直接訪問了。從頁表的屬性配置上看,這片區(qū)域是nocache的。
rt_hw_mmu_setup
#defineKERNEL_VADDR_START0x80000000 #definePV_OFFSET0 structmem_descplatform_mem_desc[]={ {KERNEL_VADDR_START,KERNEL_VADDR_START+0x10000000-1,KERNEL_VADDR_START+PV_OFFSET,NORMAL_MEM}, }; voidrt_hw_mmu_setup(rt_mmu_info*mmu_info,structmem_desc*mdesc,intdesc_nr) { void*err; for(size_ti=0;iattr) { caseNORMAL_MEM: attr=MMU_MAP_K_RWCB; break; caseNORMAL_NOCACHE_MEM: attr=MMU_MAP_K_RWCB; break; caseDEVICE_MEM: attr=MMU_MAP_K_DEVICE; break; default: attr=MMU_MAP_K_DEVICE; } rt_kprintf("vaddrstart:%lxpaddr_start:%lx ",mdesc->vaddr_start,mdesc->paddr_start); err=_rt_hw_mmu_map(mmu_info,(void*)mdesc->vaddr_start,(void*)mdesc->paddr_start, mdesc->vaddr_end-mdesc->vaddr_start+1,attr); mdesc++; } rt_hw_mmu_switch((void*)MMUTable); }
這里首先將0x80000000 ~ 0x90000000這片區(qū)域做了offset為0的線性映射,映射使用的是三級(jí)頁表一頁一頁映射的,相當(dāng)于page的區(qū)域也被映射好了。之后調(diào)用rt_hw_mmu_switch配置SATP配置MMU的地址翻譯模式為SV39。STAP的mode被配置后,MMU就相當(dāng)于開啟了。
將rtconfig.h中的PV_OFFSET改為非0值后系統(tǒng)無法啟動(dòng),對(duì)比bsp/qemu-vexpress-a9中board.c里關(guān)于頁表的配置這塊兒應(yīng)該還是有問題的。
rt_hw_tick_init
intrt_hw_tick_init(void) { /*Readcoreid*/ //unsignedlongcore_id=current_coreid(); unsignedlonginterval=1000/RT_TICK_PER_SECOND; /*CleartheSupervisor-TimerbitinSIE*/ clear_csr(sie,SIP_STIP); /*calculatethetickcycles*/ //tick_cycles=interval*sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/CLINT_CLOCK_DIV/1000ULL-1; tick_cycles=40000; /*Settimer*/ sbi_set_timer(get_ticks()+tick_cycles); /*EnabletheSupervisor-TimerbitinSIE*/ set_csr(sie,SIP_STIP); return0; }
這里使用的是riscv中的mtime。mtime是riscv中定義的一個(gè)64位的系統(tǒng)計(jì)時(shí)器,它被要求工作在常開的時(shí)鐘域下。內(nèi)核中使用以下指令可以讀取mtime的值
staticuint64_tget_ticks() { __asm____volatile__( "rdtime%0" :"=r"(time_elapsed)); returntime_elapsed; }
補(bǔ)充知識(shí),在qemu中這個(gè)時(shí)鐘的獲取來源如下
staticinlineint64_tget_clock_realtime(void) { structtimevaltv; gettimeofday(&tv,NULL); returntv.tv_sec*1000000000LL+(tv.tv_usec*1000); }
sbi_set_timer并不是設(shè)置timer本身的值,而是設(shè)置機(jī)器模式計(jì)時(shí)器比較值寄存器MTIMECMPH, MTIMECMPL的值,當(dāng)系統(tǒng)計(jì)時(shí)器的值小于等于 {M/STIMECMPH[31:0],M/STIMECMPL[31:0]}的值時(shí)不產(chǎn)生中斷;當(dāng)系統(tǒng)計(jì)時(shí)器的值大于 {M/STIMECMPH[31:0],M/STIMECMPL[31:0]} 的值時(shí) CLINT產(chǎn)生對(duì)應(yīng)的計(jì)時(shí)器中斷。它的配置過程為rt-smart將比較寄存器的配置按規(guī)則組織為sbi_call的指令,將指令類型指令參數(shù)等放入cpu的a0~a7的寄存器,然后調(diào)用ecall指令使cpu陷入M態(tài)。
sbi_set_timer->SBI_CALL1(SBI_SET_TIMER, 0, val)->sbi_call
static__inlinestructsbi_ret sbi_call(uint64_targ7,uint64_targ6,uint64_targ0,uint64_targ1, uint64_targ2,uint64_targ3,uint64_targ4) { structsbi_retret; registeruintptr_ta0__asm("a0")=(uintptr_t)(arg0); registeruintptr_ta1__asm("a1")=(uintptr_t)(arg1); registeruintptr_ta2__asm("a2")=(uintptr_t)(arg2); registeruintptr_ta3__asm("a3")=(uintptr_t)(arg3); registeruintptr_ta4__asm("a4")=(uintptr_t)(arg4); registeruintptr_ta6__asm("a6")=(uintptr_t)(arg6); registeruintptr_ta7__asm("a7")=(uintptr_t)(arg7); __asm__volatile( "ecall" :"+r"(a0),"+r"(a1) :"r"(a2),"r"(a3),"r"(a4),"r"(a6),"r"(a7) :"memory"); ret.error=a0; ret.value=a1; return(ret); }
CPU陷入M態(tài)后,opensbi會(huì)處理這個(gè)ecall產(chǎn)生的異常。獲取內(nèi)核放到寄存器中參數(shù),把新的值賦值給比較值寄存器,并清除計(jì)時(shí)器中斷
voidsbi_timer_event_start(u64next_event) { if(timer_dev&&timer_dev->timer_event_start) timer_dev->timer_event_start(next_event); csr_clear(CSR_MIP,MIP_STIP); csr_set(CSR_MIE,MIP_MTIP); }
其他
之后的初始化都是原先rt-thread中的內(nèi)容了,感興趣的讀者可以自行查閱rt-thread官方的《RT-THREAD 編程指南》手冊(cè)來學(xué)習(xí)。另外需要注意的點(diǎn)在plic_init中,plic的寄存器的基地址沒有使用ioremap就直接使用了,這是因?yàn)樯厦婷枋龅?x0 ~ 0x80000000的物理地址空間被做了offset為0的一比一映射。
rt-smart的ioremap實(shí)現(xiàn)
void*rt_ioremap(void*paddr,size_tsize) { return_ioremap_type(paddr,size,MM_AREA_TYPE_PHY); } void*rt_ioremap_nocache(void*paddr,size_tsize) { return_ioremap_type(paddr,size,MM_AREA_TYPE_PHY); } void*rt_ioremap_cached(void*paddr,size_tsize) { return_ioremap_type(paddr,size,MM_AREA_TYPE_PHY_CACHED); }
rt-smart中的ioremap實(shí)際上只分了倆種映射方式,分別是cache和nocache。在當(dāng)前的qemu-virt64-riscv里,cache的屬性沒有配置到頁表中,我也沒有查qemu的頁表支不支持配置cache,感興趣的讀者請(qǐng)參考C906的相關(guān)代碼libcpu/risc-v/t-head/c906/riscv_mmu.h
/*C-SKYextend*/ #definePTE_SEC(1UL<59)???/*?Security?*/ #define?PTE_SHARE?(1UL?<60)???/*?Shareable?*/ #define?PTE_BUF???(1UL?<61)???/*?Bufferable?*/ #define?PTE_CACHE?(1UL?<62)???/*?Cacheable?*/ #define?PTE_SO????(1UL?<63)???/*?Strong?Order?*/ #define?MMU_MAP_K_DEVICE?(PAGE_ATTR_RWX?|?PTE_V?|?PTE_G?|?PTE_SO?|?PTE_BUF?|?PTE_A?|?PTE_D) #define?MMU_MAP_K_RWCB?(PAGE_ATTR_RWX?|?PTE_V?|?PTE_G?|?PTE_SHARE?|?PTE_BUF?|?PTE_CACHE?|?PTE_A?|?PTE_D)
staticvoid*_ioremap_type(void*paddr,size_tsize,inttype) { void*v_addr=NULL; size_tattr; switch(type) { caseMM_AREA_TYPE_PHY: attr=MMU_MAP_K_DEVICE; break; caseMM_AREA_TYPE_PHY_CACHED: attr=MMU_MAP_K_RWCB; break; default: returnv_addr; } rt_mm_lock(); v_addr=rt_hw_mmu_map(&mmu_info,0,paddr,size,attr); if(v_addr) { intret=lwp_map_area_insert(&k_map_area,(size_t)v_addr,size,type); if(ret!=0) { _iounmap_range(v_addr,size); v_addr=NULL; } } rt_mm_unlock(); returnv_addr; }
__ioremap_type中會(huì)記錄頁表要配置的屬性然后調(diào)用rt_hw_mmu_map進(jìn)行映射。之后會(huì)將映射得到的虛擬地址插入到k_map_area中。
void*_rt_hw_mmu_map(rt_mmu_info*mmu_info,void*v_addr,void*p_addr,rt_size_tsize,rt_size_tattr) { /*代碼省略*/ if(v_addr) { /*代碼省略*/ } 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)); } } return0; }
ioremap傳入的虛擬地址是0,所以這里先需要調(diào)用find_vaddr得到一個(gè)可用的虛擬地址。另一個(gè)傳入find_vaddr的參數(shù)pages代表要要映射的物理內(nèi)存區(qū)域需要多少個(gè)page(4K).
staticsize_tfind_vaddr(rt_mmu_info*mmu_info,intpages) { size_tloop_pages; size_tva; size_tfind_va=0; intn=0; size_ti; loop_pages=(mmu_info->vend-mmu_info->vstart)?(mmu_info->vend-mmu_info->vstart):1; loop_pages<<=?(ARCH_INDEX_WIDTH?*?2); ????va?=?mmu_info->vstart; va<<=?(ARCH_PAGE_SHIFT?+?ARCH_INDEX_WIDTH?*?2); ????for?(i?=?0;?i?=pages){ returnfind_va; } } return0; }
這里會(huì)從mmu_info->vstart的虛擬地址開始找,這個(gè)地址就是最前面提到的0xC0000000。從0XC0000000開始一個(gè)page一個(gè)page的去找,看對(duì)應(yīng)的虛擬地址有沒有被映射。如果沒有,那么將va賦值給find_va。之后會(huì)繼續(xù)往后查找看能不能找到連續(xù)的虛擬內(nèi)存空間大小可以滿足ioremap需要的大小。如果滿足大小最終就返回找到的虛擬地址??偨Y(jié)這個(gè)過程就是尋找一塊連續(xù)的沒有被映射的大小滿足的虛擬地址空間。
審核編輯:湯梓紅
-
Linux
+關(guān)注
關(guān)注
87文章
11420瀏覽量
212308 -
代碼
+關(guān)注
關(guān)注
30文章
4880瀏覽量
69987 -
虛擬機(jī)
+關(guān)注
關(guān)注
1文章
955瀏覽量
28875 -
初始化
+關(guān)注
關(guān)注
0文章
50瀏覽量
12028 -
qemu
+關(guān)注
關(guān)注
0文章
57瀏覽量
5585
原文標(biāo)題:riscv在rt-smart中的板級(jí)初始化
文章出處:【微信號(hào):RTThread,微信公眾號(hào):RTThread物聯(lián)網(wǎng)操作系統(tǒng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
Rt-Smart在riscv中的初始化流程

RT-Smart的資料合集
D1哪吒開發(fā)板rt-smart內(nèi)核固件的燒寫與運(yùn)行步驟
RT-Smart在riscv64上的系統(tǒng)初始化和異常處理的代碼注釋
rt-smart在riscv64上的系統(tǒng)初始化和異常處理的代碼注釋
Rt-Smart在riscv中的初始化流程
樹莓派上rt-smart的應(yīng)用編程入門

rt-smart移植分析:從樹莓派3b入手

RT-Thread自動(dòng)初始化機(jī)制

評(píng)論