概要
上一篇文章,介绍了多核异构的方案,RPmsg-lite多核通信框架的内容。--《多核异构通信框架(RPMsg-Lite)》
本篇文章我们主要来讲讲RK3568上的多核加载流程,实验的板子:风火轮科技的YY3568开发板。
YY3568主板基于 Rockchip RK3568 芯片平台,四核 64位 Cortex-A55 核,主频最高达 2GHz,集成双核心架构GPU以及高效能NPU,芯片性能优异。开发板功能接口丰富,多媒体性能强悍、可在物联网、工业控制、智慧交通、轻量级人工智能等领域发挥独特优势。
板载有 2路DSI、1路HDMI 和 1路edp显示接口。支持双屏异显输出和4K分辨率。强大的显示性能,并且适配了自研的7寸mipi屏和edp屏。在多屏广告机、电子站牌、自助服务机、工业HMI等领域可发挥强大优势以及更低的成本。
板载 2 路千兆 以太网,可通过双网口访问和传输内外网的数据。拥有WIFI/BT,PCIE 3.0接口及 SIM 座,可接 4G 通信模块,提高网络传输效率。满足NVR、工业网关等多网口产品需求。
板载 5路 串口,能够大大降低通信成本。2 路IIC,可接多个IIC设备。1路CAN,能够满足汽车电子领域需求。
板载PCIE3.0和SATA接口,支持固态硬盘M.2,SATA硬盘,可扩展大容量硬盘。
YY3568多核启动方案
瑞芯微官方提供了4种多核软件方案。但是启动流程的大致方案是一致的。其方案为:
方案 | 说明 |
---|---|
3kernel + 1hal | 0~2核心:Linux(SMP),3核心:裸机 |
3kernel + 1RT-Thread | 0~2核心:Linux(SMP),3核心:RT-Thread(RTOS) |
1kernel + 3hal | 0核心:Linux,1~3核心:裸机(每一个核心跑一个裸机) |
2kernel + 3RT-Thread | 0核心:Linux,1~3核心:RT-Thread(每一个核心跑一个RTOS) |
YY3568多核启动分析
我们在风火轮科技的YY3568开发板上验证的方案:3kernel(SMP) + 1RT-Thread。
启动配置
多核启动配置文件路径:device/rockchip/rk3568/rk3568_amp_linux.its。
rk3568_amp_linux.its配置文件格式,其以设备树的格式存在。所以操作其内容的方法可以通过操作设备树一样。
多核启动主要分为两个核心节点:
①conf节点描述:描述需要启动那些子核心(节点:loadables),linux内核启动参数(节点:linux)。
②images节点描述:描述需要启动子核心的参数,如:架构,指令集,分区首地址,核启动延迟等。
/ { description = "FIT source file for rockchip AMP"; #address-cells = <1>; images { amp3 { description = "bare-mental-core3"; data = /incbin/("cpu3.bin"); // 打包前的固件位置,一般不需要 type = "firmware"; compression = "none"; arch = "arm"; // 固件的指令架构,当前只支持arm cpu = <0x300>; // mpidr thumb = <0>; // 0: arm or thumb2; 1: thumb hyp = <0>; // 0: el1/svc; 1: el2/hyp load = <0x02800000>; // 内存分区起始地址 udelay = <10000>; // 启动下一个核心的延迟时间 hash { algo = "sha256"; }; }; }; configurations { default = "conf"; conf { description = "Rockchip AMP images"; rollback-index = <0x0>; loadables = "amp3"; signature { algo = "sha256,rsa2048"; padding = "pss"; key-name-hint = "dev"; sign-images = "loadables"; }; /* - run linux on cpu0 * - it is brought up by amp(that run on U-Boot) * - it is boot entry depends on U-Boot */ linux { description = "linux-os"; arch = "arm64"; cpu = <0x000>; thumb = <0>; hyp = <0>; udelay = <0>; }; }; }; };
内存分区
描述了每个核心的内存起始地址以及内存分区大小。
我们采用的方案:3kernel(SMP) + 1RT-Thread,所以RT-Thread的内存位置为:CPU3_MEM_BASE=0x02800000。
# Linux + HAL/RTT形式的内存资源分区示例: CPU0_MEM_BASE=0x03000000 CPU1_MEM_BASE=0x01800000 CPU2_MEM_BASE=0x02000000 CPU3_MEM_BASE=0x02800000 CPU0_MEM_SIZE=0x00800000 CPU1_MEM_SIZE=0x00800000 CPU2_MEM_SIZE=0x00800000 CPU3_MEM_SIZE=0x00800000
amp固件打包
RK3568的amp固件,其包含的内容:启动配置信息(rk3568_amp_linux.its) + 从核的代码。
它是通过mkimage将两者打包一起的,工具路径:device/rockchip/common/mkimage。
打包命令:mkimage -f amp.its -E -p 0xe00 amp.img,其中:
0xe00:它是its在固件的大小,如果its文件大小不足对应大小,则补0。
从核的代码追加在其后面。
源码分析
内核启动流程--准备工作
RK3568的多核启动是由uboot来管理的,所以我们主要剖析uboot的源码。
多核启动流程的代码路径:uboot/drivers/cpu/rockchip_amp.c。
多核启动的函数入口:int amp_cpus_on(void)。
启动核心的程序,需要提前准备4个动作:
获取设备的启动设备,我们YY3568目前采用的是EMMC,所以这里描述的就是EMMC设备;然后从启动设备获取AMP分区。
申请存放头信息的空间,从AMP分区中获取头信息(即多核启动配置信息:rk3568_amp_linux.its);检测its的合法性,并获取其大小。
申请固件的内存,从AMP分区获取从核心的内容。
通过头部信息,解析可加载项。然后调用brought_up_all_amp()启动所有核心。
int amp_cpus_on(void) { ....省略 dev_desc = rockchip_get_bootdev(); ....省略 if (part_get_info_by_name(dev_desc, AMP_PART, &part) < 0) // ① ....省略 hdr = memalign(ARCH_DMA_MINALIGN, FIT_HEADER_SIZE); ....省略 /* get totalsize */ offset = part.start; cnt = DIV_ROUND_UP(FIT_HEADER_SIZE, part.blksz); if (blk_dread(dev_desc, offset, cnt, hdr) != cnt) { ....省略 if (fdt_check_header(hdr)) { ....省略 if (fit_get_totalsize(hdr, &totalsize)) { // ② ....省略 /* load image */ fit = memalign(ARCH_DMA_MINALIGN, ALIGN(totalsize, part.blksz)); ....省略 offset += cnt; cnt = DIV_ROUND_UP(totalsize, part.blksz) - cnt; if (blk_dread(dev_desc, offset, cnt, fit + FIT_HEADER_SIZE) != cnt) { // ③ ....省略 ret = parse_os_amp_dispatcher(); ....省略 /* Load loadables */ memset(&images, 0, sizeof(images)); images.fit_uname_cfg = "conf"; images.fit_hdr_os = fit; images.verify = 1; ret = boot_get_loadable(0, NULL, &images, IH_ARCH_DEFAULT, NULL, NULL); // ④ ....省略 /* Wakeup */ ret = brought_up_all_amp(images.fit_hdr_os, images.fit_uname_cfg); //⑤ ....省略 }
内核启动流程--加载项获取
核心启动分为两部分:Linux内核启动部分 + 非Linux内核(RT-Thread)启动部分。
Linux内核启动部分:从rk3568_amp_linux.its配置中获取Linux节点。然后调用brought_up_amp()启动内核。
非Linux内核(RT-Thread)启动部分:从rk3568_amp_linux.its配置中获取loadables节点,遍历节点成员,获取对应的加载项的配置信息,然后调用调用brought_up_amp()启动内核。
static int brought_up_all_amp(void *fit, const char *fit_uname_cfg) { ....省略 g_bootcpu.boot_on = 1; linux_noffset = fdt_subnode_offset(fit, conf_noffset, "linux"); // ① if (linux_noffset > 0) { ret = brought_up_amp(fit, linux_noffset, &g_bootcpu, 1); if (ret) return ret; } for (loadables_index = 0; // ② uname = fdt_stringlist_get(fit, conf_noffset, FIT_LOADABLE_PROP, loadables_index, NULL), uname; loadables_index++) { cpu_noffset = fit_image_get_node(fit, uname); if (cpu_noffset < 0) return cpu_noffset; ret = brought_up_amp(fit, cpu_noffset, &g_bootcpu, 0); if (ret) return ret; } ....省略 return 0; }
内核启动流程--核心配置参数获取
我们获取了加载项节点之后,从节点中获取其加载参数,获取方式跟设备树树获取一致。
通过smc_cpu_on()启动核心
static int brought_up_amp(void *fit, int noffset, boot_cpu_t *bootcpu, int is_linux) { ....省略 desc = fdt_getprop(fit, noffset, "description", NULL); cpu = fit_get_u32_default(fit, noffset, "cpu", -ENODATA); hyp = fit_get_u32_default(fit, noffset, "hyp", 0); thumb = fit_get_u32_default(fit, noffset, "thumb", 0); entry = load = fit_get_u32_default(fit, noffset, "load", -ENODATA); us = fit_get_u32_default(fit, noffset, "udelay", 0); boot_on = fit_get_u32_default(fit, noffset, "boot-on", 1); fit_image_get_arch(fit, noffset, &arch); fit_image_get_type(fit, noffset, &type); fit_image_get_data_size(fit, noffset, &data_size); memset(&args, 0, sizeof(args)); ....省略 /* boot now */ ret = smc_cpu_on(cpu, pe_state, entry, &args, is_linux); if (ret) return ret; exit: if (us) udelay(us); return 0; }
内核启动流程--内核启动
检测pe状态,如果目标pe状态是默认的arch状态,则直接给cpu通电
如果非Linux系统则跳转到finish下,直接启动启动内核。
如果是Linux系统需要设置启动参数,然后再启动内核。
static int smc_cpu_on(u32 cpu, u32 pe_state, u32 entry, boot_args_t *args, bool is_linux) { ....省略 /* if target pe state is default arch state, power up cpu directly */ if (is_default_pe_state(pe_state)) goto finish; ret = sip_smc_amp_cfg(AMP_PE_STATE, cpu, pe_state, 0); if (ret) { AMP_E("smc pe-state, ret=%dn", ret); return ret; } /* only linux needs boot args */ if (!is_linux) goto finish; ret = sip_smc_amp_cfg(AMP_BOOT_ARG01, cpu, args->arg0, args->arg1); if (ret) { AMP_E("smc boot arg01, ret=%dn", ret); return ret; } ret = sip_smc_amp_cfg(AMP_BOOT_ARG23, cpu, args->arg2, args->arg3); if (ret) { AMP_E("smc boot arg23, ret=%dn", ret); return ret; } finish: ret = psci_cpu_on(cpu, entry); if (ret) { printf("cpu up failed, ret=%dn", ret); return ret; } printf("OKn"); return 0; }
多核启动效果
我们多核的方案:3kernel(SMP) + 1RT-Thread,
我们需要准备两个串口,一个为Linux端的终端信息打印(UART2),一个为RT-Thread端的终端信息打印(UART4)
YY3568已经将所有的串口引出,所以我们调试很方便,接线图如下:
运行效果:
5. 视频演示
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !