0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

riscv在rt-smart中的板级初始化

RTThread物联网操作系统 来源:未知 2023-02-09 17:45 次阅读

本文章的代码来自于rt-smart中针对qemu-virt-riscv的bsp

仓库地址 https://gitee.com/rtthread/rt-thread/tree/rt-smart/

commit ID:d28249c08a152bcf0e1a076cf5b4b082c0a84add

qemu-virt-riscv介绍

简介

Virt板不对应于任何真实硬件的平台;它是为虚拟机设计的。如果你只是想运行Linux等客户机,而不关心重现真实世界硬件的特殊性和局限性,那么它是推荐的板卡类型。(摘自https://www.qemu.org/docs/master/system/riscv/virt.html)

内存空间布局(包括外设地址)

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},/*串口设备*/
[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针对virt board的ddr空间规划

参考链接脚本

bspqemu-virt64-riscvlink.lds

以及board.h中的相关定义

bspqemu-virt64-riscvdriveroard.h

得到ddr的空间规划如下

内容地址空间
代码段数据段栈空间以及bss段0x80200000 ~ __bss_end
堆空间__bss_end ~ __bss_end + 100M
页分配空间__bss_end + 100M ~ __bss_end + 200M

rt-smart针对virt board的初始化

整体初始化

rt_hw_board_init定义了与qemu-virt-riscv相关的板级初始化的全部内容,包括内存系统,plic中断子系统,定时器系统以及串口设备等。它由rtthread_startup调用,完整的调用路径如下。

(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算法管理了一部分内存区域,系统使用page_alloc来向buddy管理的内存区域申请内存资源,像linux一样每个page是4k的大小。rt-smart采用buddy算法将系统中部分可用的物理内存页面按照每1个页面、2个页面、4个页面等等划分为了不同的单元。详情可参考这篇文章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< total);

page_start=(structpage*)reg.start;
reg.start+=(mnr<< ARCH_PAGE_SHIFT);
page_addr=(void*)reg.start;
page_nr=(reg.end-reg.start)>>ARCH_PAGE_SHIFT;
}

这里rt-smart直接将一部分页表空间分配给struct page去使用,有可能会造成页面的浪费。例如当total=7,nr=5时,mnr=2,也就是俩个页表用于存储page,五个页表是真正可以被alloc_page申请的。但实际上五个页表只需要一个页表的空间就可以存放page结构体了,相当于浪费了一个页表。

rt_hw_mmu_map_init

#defineUSER_VADDR_START0x100000000UL
#defineIOREMAP_SIZE(1ul<< 30)
intrt_hw_mmu_map_init(rt_mmu_info*mmu_info,void*v_address,rt_size_tsize,rt_size_t*vtable,rt_size_tpv_off)
{
/*代码省略*/
mmu_info->vtable=vtable;
mmu_info->vstart=va_s;
mmu_info->vend=va_e;
mmu_info->pv_off=pv_off;

return0;
}

mmu_info是一个全局变量,在调用rt_hw_mmu_map_init后,(USER_VADDR_START - IOREMAP_SIZE) ~ USER_VADDR_START 这片虚拟地址空间将来专门提供给ioremap来使用。也就是ioremap返回的虚拟地址区间就是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的一比一映射,且只使用了一级页表。之后0x80000000之下的地址CPU都可以直接访问了。从页表的属性配置上看,这片区域是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;i< desc_nr; i++)
{
size_tattr;
switch(mdesc->attr)
{
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这片区域做了offset为0的线性映射,映射使用的是三级页表一页一页映射的,相当于page的区域也被映射好了。之后调用rt_hw_mmu_switch配置SATP配置MMU的地址翻译模式为SV39。STAP的mode被配置后,MMU就相当于开启了。

将rtconfig.h中的PV_OFFSET改为非0值后系统无法启动,对比bsp/qemu-vexpress-a9中board.c里关于页表的配置这块儿应该还是有问题的。

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中定义的一个64位的系统计时器,它被要求工作在常开的时钟域下。内核中使用以下指令可以读取mtime的值

staticuint64_tget_ticks()
{
__asm____volatile__(
"rdtime%0"
:"=r"(time_elapsed));
returntime_elapsed;
}

补充知识,在qemu中这个时钟的获取来源如下

staticinlineint64_tget_clock_realtime(void)
{
structtimevaltv;

gettimeofday(&tv,NULL);
returntv.tv_sec*1000000000LL+(tv.tv_usec*1000);
}

sbi_set_timer并不是设置timer本身的值,而是设置机器模式计时器比较值寄存器MTIMECMPH, MTIMECMPL的值,当系统计时器的值小于等于 {M/STIMECMPH[31:0],M/STIMECMPL[31:0]}的值时不产生中断;当系统计时器的值大于 {M/STIMECMPH[31:0],M/STIMECMPL[31:0]} 的值时 CLINT产生对应的计时器中断。它的配置过程为rt-smart将比较寄存器的配置按规则组织为sbi_call的指令,将指令类型指令参数等放入cpu的a0~a7的寄存器,然后调用ecall指令使cpu陷入M态。

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态后,opensbi会处理这个ecall产生的异常。获取内核放到寄存器中参数,把新的值赋值给比较值寄存器,并清除计时器中断

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中的内容了,感兴趣的读者可以自行查阅rt-thread官方的《RT-THREAD 编程指南》手册来学习。另外需要注意的点在plic_init中,plic的寄存器的基地址没有使用ioremap就直接使用了,这是因为上面描述的0x0 ~ 0x80000000的物理地址空间被做了offset为0的一比一映射。

rt-smart的ioremap实现

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实际上只分了俩种映射方式,分别是cache和nocache。在当前的qemu-virt64-riscv里,cache的属性没有配置到页表中,我也没有查qemu的页表支不支持配置cache,感兴趣的读者请参考C906的相关代码libcpu/risc-v/t-head/c906/riscv_mmu.h

/*C-SKYextend*/
#definePTE_SEC(1UL<< 59)   /*Security*/
#definePTE_SHARE(1UL<< 60)   /*Shareable*/
#definePTE_BUF(1UL<< 61)   /*Bufferable*/
#definePTE_CACHE(1UL<< 62)   /*Cacheable*/
#definePTE_SO(1UL<< 63)   /*StrongOrder*/
#defineMMU_MAP_K_DEVICE(PAGE_ATTR_RWX|PTE_V|PTE_G|PTE_SO|PTE_BUF|PTE_A|PTE_D)
#defineMMU_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中会记录页表要配置的属性然后调用rt_hw_mmu_map进行映射。之后会将映射得到的虚拟地址插入到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,所以这里先需要调用find_vaddr得到一个可用的虚拟地址。另一个传入find_vaddr的参数pages代表要要映射的物理内存区域需要多少个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< loop_pages; i++, va += ARCH_PAGE_SIZE) {
if(_rt_hw_mmu_v2p(mmu_info,(void*)va)){
n=0;
find_va=0;
continue;
}
if(!find_va){
find_va=va;
}
n++;
if(n>=pages){
returnfind_va;
}
}
return0;
}

这里会从mmu_info->vstart的虚拟地址开始找,这个地址就是最前面提到的0xC0000000。从0XC0000000开始一个page一个page的去找,看对应的虚拟地址有没有被映射。如果没有,那么将va赋值给find_va。之后会继续往后查找看能不能找到连续的虚拟内存空间大小可以满足ioremap需要的大小。如果满足大小最终就返回找到的虚拟地址。总结这个过程就是寻找一块连续的没有被映射的大小满足的虚拟地址空间。


———————End———————


你可以添加微信:rtthread2020 为好友,注明:公司+姓名,拉进RT-Thread官方微信交流群!



爱我就给我点在看

点击阅读原文进入官网


原文标题:riscv在rt-smart中的板级初始化

文章出处:【微信公众号:RTThread物联网操作系统】欢迎添加关注!文章转载请注明出处。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • RT-Thread
    +关注

    关注

    31

    文章

    1260

    浏览量

    39826
收藏 人收藏

    评论

    相关推荐

    基于旋转平移解耦框架的视觉惯性初始化方法

    精确和鲁棒的初始化对于视觉惯性里程计(VIO)至关重要,因为不良的初始化会严重降低姿态精度。
    的头像 发表于 11-01 10:16 114次阅读
    基于旋转平移解耦框架的视觉惯性<b class='flag-5'>初始化</b>方法

    TMS320C6000 McBSP初始化

    电子发烧友网站提供《TMS320C6000 McBSP初始化.pdf》资料免费下载
    发表于 10-26 10:10 0次下载
    TMS320C6000 McBSP<b class='flag-5'>初始化</b>

    如何在i.MX RT微控制器上初始化LWIP协议栈

    i.MX RT微控制器上初始化LWIP协议栈是一个复杂但有趣的过程,它涉及多个步骤和关键组件的配置.
    的头像 发表于 10-12 11:48 244次阅读
    如何在i.MX <b class='flag-5'>RT</b>微控制器上<b class='flag-5'>初始化</b>LWIP协议栈

    RK3568平台RT-smart系统跑不起来,为什么?

    RK3568平台RT-smart系统跑不起来
    发表于 09-13 07:28

    Keil变量不被初始化方法

    有些时候我们的应用过程要求变量有连续性,或者现场保留,例如Bootloader跳转,某种原因的复位过程我们有些关键变量不能被初始化
    的头像 发表于 08-30 11:47 462次阅读
    Keil<b class='flag-5'>中</b>变量不被<b class='flag-5'>初始化</b>方法

    初始化IO口为外部中断线的时候,最先初始化的会被后初始化的覆盖掉为什么?

    初始化IO口为外部中断线的时候,比如GPIOA6与GPIOB6先后初始化为外部中断,最先初始化的会被后初始化的覆盖掉,不知道是为什么?
    发表于 05-14 08:26

    字符型、指针型等变量等该如何初始化

     对于数值类型的变量往往初始化为0,但对于其他类型的变量,如字符型、指针型等变量等该如何初始化呢?
    的头像 发表于 03-18 11:02 1184次阅读

    MCU单片机GPIO初始化该按什么顺序配置?为什么初始化时有电平跳变?

    GPIO初始化时有时钟配置、模式配置、输出配置、复用配置,那么在编写初始化代码时,到底该按什么顺序执行呢?如果顺序不当那初始化过程可能会出现短暂的电平跳变。
    的头像 发表于 02-22 11:07 1382次阅读
    MCU单片机GPIO<b class='flag-5'>初始化</b>该按什么顺序配置?为什么<b class='flag-5'>初始化</b>时有电平跳变?

    串口初始化一般是初始化哪些内容

    串口初始化是指在使用串口进行数据通信之前,对串口进行一系列的设置和配置,以确保串口能够正常工作。串口初始化的内容主要包括以下几个方面: 串口硬件设置:首先,需要确定要使用的串口是哪一个,通常计算机
    的头像 发表于 01-04 09:39 2982次阅读

    labview运行后如何初始化

    LabVIEW是一款强大的图形编程软件,在运行之前通常需要进行一些初始化操作。本文将详细介绍LabVIEW运行前的初始化过程,并提供了一些常用的初始化方法。 创建新项目:
    的头像 发表于 12-28 17:24 2395次阅读

    自动初始化机制原理详解

    自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。这篇文章就来探索一下其中的奥秘, 简单理解其原理!
    的头像 发表于 12-16 09:33 943次阅读
    自动<b class='flag-5'>初始化</b>机制原理详解

    C语言编程时,各种类型的变量该如何初始化

    C语言编程时,各种类型的变量该如何初始化C语言中,每个变量都需要在使用之前进行初始化初始化是为变量分配内存空间并赋予初始值的过程。C
    的头像 发表于 12-07 13:53 1088次阅读

    secondary cpu初始化状态设置

    ,用于填写secondary cpu的入口地址。 uboot负责将这块内存的地址写入devicetree,当内核初始化完成,需要启动secondary cpu时,就将其内核入口地址写到那块内存
    的头像 发表于 12-05 15:27 957次阅读
    secondary cpu<b class='flag-5'>初始化</b>状态设置

    ubuntu20.04如何初始化rosdep

    初始化rosdep 输入以下的命令,先对其初始化一下 sudo rosdep init 如果出现以下界面,那么恭喜你,你可以省下一些麻烦了,直接跳到第7步就行了: 如果出现了以下几种错误,也不要担心
    的头像 发表于 12-01 16:22 832次阅读
    <b class='flag-5'>在</b>ubuntu20.04<b class='flag-5'>中</b>如何<b class='flag-5'>初始化</b>rosdep

    实战经验 | Keil、IAR、CubeIDE 变量不被初始化方法

    关键词:不被初始化,编译环境 目录预览 1、前言 2、IAR 实现变量不初始化方法 3、Keil 实现变量不被初始化方法 4、CubeIDE 实现变量不初始化方法 01 前言 有些时候
    的头像 发表于 11-24 18:05 3735次阅读