Zephyr启动过程与中断响应
// 本文部分内容来自网络
1. 启动过程
  一般嵌入式处理器启动方式分为两种:
      1. XIP 模式  (eXecute In Place), 在该模式下CPU直接从Nor Flash上读代码执行,执行速度慢 ;
      2. 非XIP模式,  在该模式下硬件先将代码从Flash上搬移到RAM上后,CPU才能从RAM上访问数据,执行速度快;
对于非XIP模式,启动前需要先借助boot把代码段从FLASH拷贝到RAM;
对于XIP模式,如果FLASH基地址映射到0地址,复位后可以直接启动;
如果FLASH基地址非0地址,启动之前需要先借助boot在映射的0地址(笔者项目的使用SOC通过bootlink将0地址映射到了0x20110000 :IRAM_AON)中写入MSP的初始值+Reset入口地址(针对CortexM处理器)。
下文以CortexM + Zephyr1.9.2 +XIP模式 为例进行相关阐述。
__reset
根据向量表找到的reset入口,位于(zephyr\arch\arm\core\cortex_m\reset.S)
reset入口完成工作:
- 初始化watchdog: _WdogInit
 - 将中断栈_interrupt_stack内容初始化为0xaa
 - 复位后CortexM处理器默认处于线程模式+特权访问+使用MSP主栈指针,这里切换为PSP指针并将PSP设为_interrupt_stack中断栈
 - 进入C语言准备_PrepC
 
SECTION_SUBSEC_FUNC(TEXT,_reset_section,__reset)
/*
 * The entry point is located at the __reset symbol, which
 * is fetched by a XIP image playing the role of a bootloader, which jumps to
 * it, not through the reset vector mechanism. Such bootloaders might want to
 * search for a __start symbol instead, so create that alias here.
 */
SECTION_SUBSEC_FUNC(TEXT,_reset_section,__start)
    /* lock interrupts: will get unlocked when switch to main task */
    cpsid i
#ifdef CONFIG_WDOG_INIT
    /* board-specific watchdog initialization is necessary */
    bl _WdogInit
#endif
#ifdef CONFIG_INIT_STACKS
    ldr r0, =_interrupt_stack
    ldr r1, =0xaa
    ldr r2, =CONFIG_ISR_STACK_SIZE
    bl memset
#endif
    /*
     * Set PSP and use it to boot without using MSP, so that it
     * gets set to _interrupt_stack during nanoInit().
     */
    ldr r0, =_interrupt_stack
    ldr r1, =CONFIG_ISR_STACK_SIZE
    adds r0, r0, r1
    msr PSP, r0
    movs.n r0, #2	/* switch to using PSP (bit1 of CONTROL reg) */
    msr CONTROL, r0
    b _PrepC
_PrepC
完成进入C语言环境后的准备工作,位于(zephyr\arch\arm\core\cortex_m\Prep_c.c)
_PrepC完成工作:
- 重定位向量表
 - 使能浮点计算(如果有相关特性)
 - 清零BSS段
 - 从ROM拷贝数据段到RAM(针对XIP模式)
 - 正式进入C环境执行
 
void _PrepC(void)
{
	relocate_vector_table();
	enable_floating_point();
	_bss_zero();
	_data_copy();
#ifdef CONFIG_BOOT_TIME_MEASUREMENT
	__start_time_stamp = 0;
#endif
	_Cstart();
	CODE_UNREACHABLE;
}
relocate_vector_table
根据CortexM处理器的特性,向量表固定于0地址,对于(XIP模式并且FLASH基地址非0)或者(非XIP模式并且RAM基地址非0)的情况,需要重新将向量表拷贝到0地址。
#define VECTOR_ADDRESS 0
static inline void relocate_vector_table(void)
{
#if defined(CONFIG_XIP) && (CONFIG_FLASH_BASE_ADDRESS != 0) || \
    !defined(CONFIG_XIP) && (CONFIG_SRAM_BASE_ADDRESS != 0)
	size_t vector_size = (size_t)_vector_end - (size_t)_vector_start;
	memcpy(VECTOR_ADDRESS, _vector_start, vector_size);
#endif
}
_vector_end/_vector_start两个符号的地址在linker.ld链接脚本里面有定义,linker文件位于(zephyr\include\arch\arm\cortex_m\scripts):
SECTION_PROLOGUE(_TEXT_SECTION_NAME,,)
	{
	. = CONFIG_TEXT_SECTION_OFFSET;
	KEEP(*(.os.start))
	_vector_start = .;
	KEEP(*(.exc_vector_table))
	KEEP(*(".exc_vector_table.*"))
	KEEP(*(IRQ_VECTOR_TABLE))
	KEEP(*(.openocd_dbg))
	KEEP(*(".openocd_dbg.*"))
	/* Kinetis has to write 16 bytes at 0x400 */
	SKIP_TO_KINETIS_FLASH_CONFIG
	KEEP(*(.kinetis_flash_config))
	KEEP(*(".kinetis_flash_config.*"))
#ifdef CONFIG_GEN_SW_ISR_TABLE
	KEEP(*(SW_ISR_TABLE))
#endif
	_vector_end = .;
	_image_text_start = .;
	*(.text)
	*(".text.*")
	*(.gnu.linkonce.t.*)
	} GROUP_LINK_IN(ROMABLE_REGION)
	_image_text_end = .;
_Cstart
正式进入C语言环境,开始操作系统初始化工作,位于(zephyr\kernel\init.c)
_Cstart完成工作:
- 多线程初始化
 - PRE_KERNEL_1、PRE_KERNEL_2级别设备初始化
 - 切换到主线程开始运行
 
FUNC_NORETURN void _Cstart(void)
{
#ifdef CONFIG_TIME_TRACK
	insert_pack_time_debug(PACK_AP_CSTART, NULL, -1);
#endif
#ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN
	struct k_thread *dummy_thread = NULL;
#else
	struct k_thread dummy_thread_memory;
	struct k_thread *dummy_thread = &dummy_thread_memory;
#endif
	/*
	 * Initialize kernel data structures. This step includes
	 * initializing the interrupt subsystem, which must be performed
	 * before the hardware initialization phase.
	 */
	prepare_multithreading(dummy_thread);
	/* perform basic hardware initialization */
	_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);
	_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);
	/* initialize stack canaries */
#ifdef CONFIG_STACK_CANARIES
	__stack_chk_guard = (void *)sys_rand32_get();
#endif
	/* display boot banner */
	switch_to_main_thread();
	/*
	 * Compiler can't tell that the above routines won't return and issues
	 * a warning unless we explicitly tell it that control never gets this
	 * far.
	 */
	CODE_UNREACHABLE;
}
prepare_multithreading
该函数完成多线程初始化,主要完成
- 中断(NVIC)初始化
 - 创建了两个线程,分别是_main_thread(使用_main_stack,即系统入口对应的MSP指针)和_idle_thread(使用_idle_stack)
 - 初始化_timeout_q超时队列
 - 架构相关的内核初始化,针对CortexM主要完成MSP主栈设置为_interrupt_stack,以及中断异常相关初始化
 
static void prepare_multithreading(struct k_thread *dummy_thread)
{
#ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN
	ARG_UNUSED(dummy_thread);
#else
	/*
	 * Initialize the current execution thread to permit a level of
	 * debugging output if an exception should happen during kernel
	 * initialization.  However, don't waste effort initializing the
	 * fields of the dummy thread beyond those needed to identify it as a
	 * dummy thread.
	 */
	_current = dummy_thread;
	dummy_thread->base.user_options = K_ESSENTIAL;
	dummy_thread->base.thread_state = _THREAD_DUMMY;
#endif
	/* _kernel.ready_q is all zeroes */
	/*
	 * The interrupt library needs to be initialized early since a series
	 * of handlers are installed into the interrupt table to catch
	 * spurious interrupts. This must be performed before other kernel
	 * subsystems install bonafide handlers, or before hardware device
	 * drivers are initialized.
	 */
	_IntLibInit();
	/* ready the init/main and idle threads */
	for (int ii = 0; ii < K_NUM_PRIORITIES; ii++) {
		sys_dlist_init(&_ready_q.q[ii]);
	}
	/*
	 * prime the cache with the main thread since:
	 *
	 * - the cache can never be NULL
	 * - the main thread will be the one to run first
	 * - no other thread is initialized yet and thus their priority fields
	 *   contain garbage, which would prevent the cache loading algorithm
	 *   to work as intended
	 */
	_ready_q.cache = _main_thread;
	_new_thread(_main_thread, _main_stack,
		    MAIN_STACK_SIZE, _main, NULL, NULL, NULL,
		    CONFIG_MAIN_THREAD_PRIORITY, K_ESSENTIAL);
	_mark_thread_as_started(_main_thread);
	_add_thread_to_ready_q(_main_thread);
#ifdef CONFIG_MULTITHREADING
	_new_thread(_idle_thread, _idle_stack,
		    IDLE_STACK_SIZE, idle, NULL, NULL, NULL,
		    K_LOWEST_THREAD_PRIO, K_ESSENTIAL);
	_mark_thread_as_started(_idle_thread);
	_add_thread_to_ready_q(_idle_thread);
#endif
	initialize_timeouts();
	/* perform any architecture-specific initialization */
	kernel_arch_init();
}
_sys_device_do_config_level
设备初始化,Zephyr定义了四个级别的初始化,分别为:
#define _SYS_INIT_LEVEL_PRE_KERNEL_1 0 #define _SYS_INIT_LEVEL_PRE_KERNEL_2 1 #define _SYS_INIT_LEVEL_POST_KERNEL 2 #define _SYS_INIT_LEVEL_APPLICATION 3
_sys_device_do_config_level函数实现:
void _sys_device_do_config_level(int level)
{
	struct device *info;
	for (info = config_levels[level]; info < config_levels[level+1];
								info++) {
		struct device_config *device = info->config;
		device->init(info);
#ifdef CONFIG_TIME_TRACK
		insert_pack_time_debug(ap_init_counter, device->init, level);
		ap_init_counter++;
#endif
	}
}
设备初始化接口定义:
SYS_INIT(netdog_work_q_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#define SYS_INIT(init_fn, level, prio) \
 DEVICE_INIT(_SYS_NAME(init_fn), "", init_fn, NULL, NULL, level, prio)
 
#define DEVICE_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio) \
 DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
       level, prio, NULL)
 
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
       level, prio, api) \
 DEVICE_DEFINE(dev_name, drv_name, init_fn, \
        device_pm_control_nop, data, cfg_info, level, \
        prio, api)
 
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \
        data, cfg_info, level, prio, api) \
 \
 static struct device_config _CONCAT(__config_, dev_name) __used \
 __attribute__((__section__(".devconfig.init"))) = { \
  .name = drv_name, .init = (init_fn), \
  .device_pm_control = (pm_control_fn), \
  .config_info = (cfg_info) \
 }; \
 static struct device _CONCAT(__device_, dev_name) __used \
 __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \
   .config = &_CONCAT(__config_, dev_name), \
   .driver_api = api, \
   .driver_data = data \
 }
switch_to_main_thread
切换到_main_thread开始运行:
static void switch_to_main_thread(void)
{
#ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN
	_arch_switch_to_main_thread(_main_thread, _main_stack, MAIN_STACK_SIZE,
				    _main);
#else
	/*
	 * Context switch to main task (entry function is _main()): the
	 * current fake thread is not on a wait queue or ready queue, so it
	 * will never be rescheduled in.
	 */
	_Swap(irq_lock());
#endif
}
_main_thread线程入口为_main,_main函数完成:
- 平台相关的初始化以及_SYS_INIT_LEVEL_POST_KERNEL、SYS_INIT_LEVEL_APPLICATION级别设备初始化,这些初始化会创建相关的平台与应用线程;
 - 完成后进入main钩子函数(\os\zephyr\samples\xxx\src\main.c),本平台该函数实现为空;
 - 该线程没有死循环入口,所以完成后自行退出,然后操作系统开始正式调度执行;
 
static void _main(void *unused1, void *unused2, void *unused3)
{
	ARG_UNUSED(unused1);
	ARG_UNUSED(unused2);
	ARG_UNUSED(unused3);
	oss_event_init();
	oss_icp_init();
	oss_nv_init();
	_reset_uart_termios();
	ramdump_init();
	amt_init();
	zcat_ap_init();
	_sys_device_do_config_level(_SYS_INIT_LEVEL_POST_KERNEL);
	/* Final init level before app starts */
	_sys_device_do_config_level(_SYS_INIT_LEVEL_APPLICATION);
#if defined(CONFIG_BOOT_DELAY) && CONFIG_BOOT_DELAY > 0
	if (boot_delay > 0) {
		printk("delaying boot\n");
		k_sleep(CONFIG_BOOT_DELAY);
	}
	PRINT_BOOT_BANNER();
#endif
	_init_static_threads();
#ifdef CONFIG_BOOT_TIME_MEASUREMENT
	/* record timestamp for kernel's _main() function */
	extern u64_t __main_time_stamp;
	__main_time_stamp = (u64_t)k_cycle_get_32();
#endif
	extern void main(void);
	k_thread_priority_set(_main_thread, CONFIG_MAIN_THREAD_PRIORITY);
	main();
	/* Terminate thread normally since it has no more work to do */
	_main_thread->base.user_options &= ~K_ESSENTIAL;
}
2. 中断响应
以CortexM0处理器为例,其异常列表如下:
异常类型 异常编号 描述 Reset 1 上电复位或系统复位 NMI 2 不可屏蔽中断 Hard fault 3 用于错误处理,系统检测到错误后被激活 SVCall 11 请求管理调用,在执行SVC指令被激活,主要用作操作系统 PendSV 14 可挂起服务(系统)调用 SysTick 15 系统节拍定时器异常,一般在OS种用作周期系统节拍异常 IRQ0-IRQ31 16-47 中断,可来自于外部,也可来自片上外设
查看linker.ld文件可以看到,Zephyr系统将上述异常对应的向量表分成了几部分,分别为exc_vector_table,IRQ_VECTOR_TABLE和SW_ISR_TABLE;(忽略openocd_dbg、kinetis_flash_config段,未使用):
        _vector_start = .;
	KEEP(*(.exc_vector_table))
	KEEP(*(".exc_vector_table.*"))
	KEEP(*(IRQ_VECTOR_TABLE))
	KEEP(*(.openocd_dbg))
	KEEP(*(".openocd_dbg.*"))
	/* Kinetis has to write 16 bytes at 0x400 */
	SKIP_TO_KINETIS_FLASH_CONFIG
	KEEP(*(.kinetis_flash_config))
	KEEP(*(".kinetis_flash_config.*"))
#ifdef CONFIG_GEN_SW_ISR_TABLE
	KEEP(*(SW_ISR_TABLE))
#endif
	_vector_end = .;
第一部分,异常向量表exc_vector_table,位于(zephyr\arch\arm\core\cortex_m\vector_table.S),定义了1-15号异常对应的跳转PC地址:
 SECTION_SUBSEC_FUNC(exc_vector_table,_vector_table_section,_vector_table)
    .word _main_stack + CONFIG_MAIN_STACK_SIZE
    .word __reset
    .word __nmi
    .word __hard_fault
    .word __reserved
    .word __reserved
    .word __reserved
    .word __reserved
    .word __reserved
    .word __reserved
    .word __reserved
    .word __svc
    .word __reserved
    .word __reserved
    .word __pendsv
    #if defined(CONFIG_CORTEX_M_SYSTICK)
    .word _timer_int_handler
    #else
    .word __reserved
    #endif
第二部分,中断向量表IRQ_VECTOR_TABLE,定义了16-47号中断跳转PC地址,位于project\prj_XXX\temp\Isr_tables.c,这是一个正式编译前由gen_isr_tables.py Python脚本生成的文件;该向量表所有项都对应同一个函数即所有中断总入口,函数名为_isr_wrapper:
#define __irq_vector_table _GENERIC_SECTION(IRQ_VECTOR_TABLE)
u32_t __irq_vector_table _irq_vector_table[34] = {
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
	0xffc88cd,
};
第三部分,具体中断实现结构体SW_ISR_TABLE,同样位于project\prj_XXX\temp\Isr_tables.c,也是由Python脚本生成,该结构体由两项成员,分别为传入参数arg和中断入口isr:
#define __sw_isr_table  _GENERIC_SECTION(SW_ISR_TABLE)
struct _isr_table_entry {  void *arg;  void (*isr)(void *); };
struct _isr_table_entry __sw_isr_table _sw_isr_table[34] = {
	{(void *)0x10724, (void *)0xffb8479},
	{(void *)0x10670, (void *)0xffb4b59},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x1067c, (void *)0xffb4b59},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffb9661},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x10730, (void *)0xffb8aa9},
	{(void *)0x10730, (void *)0xffb8a5d},
	{(void *)0x106c4, (void *)0xffb52c9},
	{(void *)0x2, (void *)0xffc6ad1},
	{(void *)0x0, (void *)0xffc6ad1},
	{(void *)0x1061c, (void *)0xffb66f5},
	{(void *)0x10610, (void *)0xffb66f5},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc69e9},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x10694, (void *)0xffb9b8b},
	{(void *)0x0, (void *)0xffc72e1},
	{(void *)0x106e8, (void *)0xffb6af3},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x10730, (void *)0xffb8959},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x106b8, (void *)0xffb52c9},
	{(void *)0x0, (void *)0xffc5e9d},
	{(void *)0x0, (void *)0xffc8759},
	{(void *)0x0, (void *)0xffc8759},
};
具体程序如何从_isr_wrapper总入口找到对应中断并进入对应中断入口,可以参考_isr_wrapper函数实现:
SECTION_FUNC(TEXT, _isr_wrapper)
	push {lr}		/* lr is now the first item on the stack */
	/*
	 * All interrupts are disabled when handling idle wakeup.  For tickless
	 * idle, this ensures that the calculation and programming of the device
	 * for the next timer deadline is not interrupted.  For non-tickless idle,
	 * this ensures that the clearing of the kernel idle state is not
	 * interrupted.  In each case, _sys_power_save_idle_exit is called with
	 * interrupts disabled.
	 */
	cpsid i  /* PRIMASK = 1 */
	/* is this a wakeup from idle ? */
	ldr r2, =_kernel
	/* requested idle duration, in ticks */
	ldr r0, [r2, #_kernel_offset_to_idle]
	cmp r0, #0
	beq _idle_state_cleared
	movs.n r1, #0
	/* clear kernel idle state */
	str r1, [r2, #_kernel_offset_to_idle]
	blx _sys_power_save_idle_exit
_idle_state_cleared:
	cpsie i		/* re-enable interrupts (PRIMASK = 0) */
	mrs r0, IPSR	/* get exception number */
	ldr r1, =16
	subs r0, r1	/* get IRQ number */
	lsls r0, #3	/* table is 8-byte wide */
	ldr r1, =_sw_isr_table
	add r1, r1, r0	/* table entry: ISRs must have their MSB set to stay
			 * in thumb mode */
	ldm r1!,{r0,r3}	/* arg in r0, ISR in r3 */
        blx r3		/* call ISR */
	pop {r3}
	mov lr, r3
	/* exception return is done in _IntExit() */
	b _IntExit
中断定义与处理
中断定义接口如下:
IRQ_CONNECT(SI_TIM0_IRQ, CONFIG_TIMER_0_IRQ_PRI, timer_si_isr, DEVICE_GET(timer_si_0), 0);
中断号:SI_TIM0_IRQ
中断处理函数:timer_si_isr
定义完成后通过脚本解析,timer_si_isr的地址会被写入sw_isr_table;下面的中断处理路径,通过timer中断实现了系统tick的处理:
timer_si_isr
timer_systick_callback
_sys_clock_final_tick_announce
_sys_clock_tick_announce
_nano_sys_clock_tick_announce
handle_timeouts
_handle_expired_timeouts
_handle_one_expired_timeout
work_timeout
                    
                
                
            
        
浙公网安备 33010602011771号