一、用户程序示例
以qihang开发板gpio_led程序为例,为何单板上电后LedTask()会自动运行,SYS_RUN宏在背后是如何起作用的?
static void LedTask(void)
{
while (1)
{
IoTGpioSetOutputVal(LED_TASK_GPIO2,1);
usleep(500*1000);
IoTGpioSetOutputVal(LED_TASK_GPIO2,0);
usleep(500*1000);
}
}
static void LedExampleEntry(void)
{
osThreadAttr_t attr;
IoTGpioInit(LED_TASK_GPIO2);
IoTGpioSetDir(LED_TASK_GPIO2,IOT_GPIO_DIR_OUT);
attr.name = "LedTask";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = LED_TASK_STACK_SIZE;
attr.priority = LED_TASK_PRIO;
if (osThreadNew((osThreadFunc_t)LedTask1, NULL, &attr) == NULL) {
printf("Falied to create LedTask!\n");
}
}
SYS_RUN(LedExampleEntry);
二、第二阶段(应用)启动流程
在上一篇文章中"OpenHarmony南向设备开发建构编译分析",有提到qihang板产品配置文件中相关子系统及组件如下:
vendor/isoftstone/qihang/config.json。
......
{
"subsystem": "distributedschedule", #分布式任务调度子系统
"components": [
{ "component": "samgr_lite", "features":[] }
]
},
{
"subsystem": "startup",
"components": [
{ "component": "bootstrap_lite", "features":[] }, #bootstrap启动引导
{ "component": "syspara_lite", "features": #提供系统属性读写接口
[
"enable_ohos_startup_syspara_lite_use_thirdparty_mbedtls = false"
]
}
]
},
......
可以看到,qihang板使用startup子系统中的bootstrap_lite组件和syspara_lite组件。重点看一下bootstrap_lite组件,位于SDK的base/startup/bootstrap_lite目录。引用该组件的readme文件说明如下:
bootstrap启动引导组件,提供了各服务和功能的启动入口标识。在SAMGR启动时,会调用boostrap标识的入口函数,并启动系统服务。
samgr_lite组件是针对Hi3861这类硬件资源有限的轻量化系统服务框架,代码位于foundation/distributedschedule/samgr_lite目录。该组件的功能引用如下:
系统服务框架基于面向服务的架构,提供了服务开发、服务的子功能开发、对外接口的开发、以及多服务共进程的开发框架。
device/soc/hisilicon/hi3861v100/sdk_liteos/app/wifiiot_app/src/app_main.c
hi_void app_main(hi_void)
{
hi_flash_partition_table *ptable = HI_NULL;
peripheral_init();
peripheral_init_no_sleep();
hi_u32 ret = hi_factory_nv_init(HI_FNV_DEFAULT_ADDR, HI_NV_DEFAULT_TOTAL_SIZE, HI_NV_DEFAULT_BLOCK_SIZE);
hi_flash_partition_init();
ptable = hi_get_partition_table();
hi_nv_init(ptable->table[HI_FLASH_PARTITON_NORMAL_NV].addr, ptable->table[HI_FLASH_PARTITON_NORMAL_NV].size,
HI_NV_DEFAULT_BLOCK_SIZE);
hi_fs_init();
(hi_void)hi_event_init(APP_INIT_EVENT_NUM, HI_NULL);
hi_sal_init();
hi_syserr_watchdog_debug(HI_FALSE);
hi_syserr_record_crash_info(HI_TRUE);
hi_lpc_init();
hi_lpc_register_hw_handler(config_before_sleep, config_after_sleep);
hi_at_init();
tcpip_init(NULL, NULL);
hi_wifi_init(APP_INIT_VAP_NUM, APP_INIT_USR_NUM);
app_demo_task_release_mem();
hilink_main();
OHOS_Main();
}
device/soc/hisilicon/hi3861v100/sdk_liteos/app/wifiiot_app/src/ohos_main.c。
void OHOS_Main()
{
OHOS_SystemInit();
}
base/startup/bootstrap_lite/services/source/system_init.c。
void OHOS_SystemInit(void)
{
MODULE_INIT(bsp);
MODULE_INIT(device);
MODULE_INIT(core);
SYS_INIT(service);
SYS_INIT(feature);
MODULE_INIT(run);
SAMGR_Bootstrap();
}
#define SYS_INIT(name) \
do { \
SYS_CALL(name, 0); \
} while (0)
#define SYS_CALL(name, step) \
do { \
InitCall *initcall = (InitCall *)(SYS_BEGIN(name, step)); \
InitCall *initend = (InitCall *)(SYS_END(name, step)); \
for (; initcall < initend; initcall++) { \
(*initcall)(); \
} \
} while (0)
#define SYS_BEGIN(name, step) \
({ extern InitCall __zinitcall_sys_##name##_start; \
InitCall *initCall = &__zinitcall_sys_##name##_start; \
(initCall); \
})
#define SYS_END(name, step) \
({ extern InitCall __zinitcall_sys_##name##_end; \
InitCall *initCall = &__zinitcall_sys_##name##_end; \
(initCall); \
})
#define MODULE_INIT(name) \
do { \
MODULE_CALL(name, 0); \
} while (0)
#define MODULE_CALL(name, step) \
do { \
InitCall *initcall = (InitCall *)(MODULE_BEGIN(name, step)); \
InitCall *initend = (InitCall *)(MODULE_END(name, step)); \
for (; initcall < initend; initcall++) { \
(*initcall)(); \
} \
} while (0)
#define MODULE_BEGIN(name, step) \
({ extern InitCall __zinitcall_##name##_start; \
InitCall *initCall = &__zinitcall_##name##_start; \
(initCall); \
})
#define MODULE_END(name, step) \
({ extern InitCall __zinitcall_##name##_end; \
InitCall *initCall = &__zinitcall_##name##_end; \
(initCall); \
})
foundation/distributedschedule/samgr_lite/samgr/source/samgr_lite.c。
void SAMGR_Bootstrap(void)
{
SamgrLiteImpl *samgr = GetImplement();
WDT_Reset(WDG_SVC_BOOT_TIME);
Vector initServices = VECTOR_Make(NULL, NULL);
MUTEX_Lock(samgr->mutex);
samgr->status = TO_NEXT_STATUS(samgr->status);
int16 size = VECTOR_Size(&(samgr->services));
int16 i;
for (i = 0; i < size; ++i) {
ServiceImpl *serviceImpl = (ServiceImpl *)VECTOR_At(&(samgr->services), i);
VECTOR_Add(&initServices, serviceImpl);
}
MUTEX_Unlock(samgr->mutex);
InitializeAllServices(&initServices);
VECTOR_Clear(&initServices);
InitCompleted();
}
在用户应用程序组件的代码中,会包含下述声明:
APP_FEATURE_INIT(MQTTDemo);
SYS_RUN(LedExampleEntry);
上述宏的说明引用如下:
#define APP_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, app_service, “app.service”)
总结以上分析,程序第二阶段启动流程如下图图所示:
三、第一阶段(上电)启动流程
请参考本文最后延申阅读第二篇文章的具体介绍,程序加载由3个boot程序前后配合完成:
romboot:
- 芯片内部自带的上电引导程序,引导loaderboot。
loaderboot (device/soc/hisilicon/hi3861v100/sdk_liteos/boot/loaderboot):
- 与HiBurn通讯,下载镜像到flash。
- 烧写EFUSE(芯片配置信息)。
- 校验并引导flashboot。
flashboot (device/soc/hisilicon/hi3861v100/sdk_liteos/boot/flashboot):
- 升级固件。
- 校验并引导固件(主程序)。
loaderboot/common/cmd_loop.c 定义了从hiburn接收并处理的操作:
const loader_cmd g_loader_cmdtable[LOADER_CMD_MAX] = {
{ CMD_DL_IMAGE, loader_download_image },
{ CMD_BURN_EFUSE, loader_burn_efuse },
{ CMD_UL_DATA, loader_upload_data },
{ CMD_READ_EFUSE, loader_read_efuse },
{ CMD_FLASH_PROTECT, loader_flash_protect },
{ CMD_RESET, loader_reset },
{ CMD_FACTORY_IMAGE, loader_download_image },
{ CMD_VERSION, loader_burn_version},
};
其中:
loader_download_image就是接收hiburn传来的升级文件,并烧录到flash中。
flashboot/startup目录下有两个重要文件:
- riscv_init_flshboot.S 汇编语言格式,RISC-V启动代码。
- main.c。
#define KERNEL_START_ADDR 0x40D3C0
boot_kernel(KERNEL_START_ADDR);
global_reset();
hi_void boot_kernel(uintptr_t kaddr)
{
__asm__ __volatile__("ecall");
hi_void (*entry)(hi_void) = (hi_void*)(kaddr);
entry();
}
在最后build应用生成的map文件,可看到内存布局如下:
Name Origin Length Attributes
BIN 0x000000000040d3c0 0x0000000000200000 xr
ROM_TEXT 0x00000000003b8000 0x00000000000457e0 xr
ROM_DATA0 0x000000000011d7c0 0x0000000000000020 xrw
ROM_DATA1 0x000000000011d7e0 0x00000000000006e8 xrw
ROM_BSS 0x000000000011a9c0 0x0000000000002e00 xrw
STACK 0x00000000001185c0 0x0000000000002400 rw
CHECK_INFO 0x000000000011dfc0 0x0000000000000040 rw
FLASH 0x000000000040d3c0 0x00000000001f2c40 xrw
PATCH_BSS 0x00000000000d8000 0x0000000000000400 xrw
RAM 0x00000000000d8400 0x00000000000401c0 xrw
EXTERN_ROM_DATA1_BSS 0x000000000011dec8 0x00000000000000f8 xrw
*default* 0x0000000000000000 0xffffffffffffffff
.entry.text 0x000000000040d3c0 0x4 build/libs/hi3861/release/no_mesh/liblitekernel_flash.a(los_startup.o)
0x000000000040d3c0 _start
0x000000000040d3e0 . = ALIGN (0x20)
对比可以看到,KERNEL_START_ADDR与应用程序的起始地址一致,基本可推断flashboot最后操作为调用应用程序。通过ecall指令,实现RISC-V处理器( Hi3861使用 )从User Mode( 禁止不可信代码执行特权指令 )切换为Machine Mode( 最高特权模式 )。
四、小结
本文采用倒序的方式,初步梳理了从Hi3861芯片上电到OpenHarmony应用程序启动运行的流程。还有很多内容都没有涉及,包括芯片安全启动,Flash的存储分布等,boot部分说明也比较粗浅。