• 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入口,位于(zephyrarcharmcorecortex_m eset.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语言环境后的准备工作,位于(zephyrarcharmcorecortex_mPrep_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文件位于(zephyrincludearcharmcortex_mscripts):

    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语言环境,开始操作系统初始化工作,位于(zephyrkernelinit.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钩子函数(oszephyrsamplesxxxsrcmain.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
    ");
    		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,位于(zephyrarcharmcorecortex_mvector_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地址,位于projectprj_XXX empIsr_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,同样位于projectprj_XXX empIsr_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
  • 相关阅读:
    自定义tabbar
    数据存储: sqlite,coredata plist 归档
    分享(微信,微博,人人)
    OAuth协议与第三方登录:(QQ,百度,微信,微博)
    修改后台来测试APP的方法
    mysql 数据库优化之路
    tcp协议在定位中的应用(2)
    tcp协议在定位中的应用
    计算机术语中一些歧义
    网络常见问题背后的原因
  • 原文地址:https://www.cnblogs.com/DF11G/p/9774437.html
Copyright © 2020-2023  润新知