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_tableIRQ_VECTOR_TABLESW_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

 

posted @ 2018-10-11 19:24  DF11G  阅读(1867)  评论(0编辑  收藏  举报