Avatar

以data abort 为例

Data_Abort_ISR
MOV      a1,#DATA_EXCEPTION_TYPE         ; Set type DATA_ABORT (4)
MOV      a2,lr

saveException
MRS     a3,CPSR                          ; Pickup current CPSR
BIC     a3,a3,#MODE_MASK                 ; Clear the mode bits
ORR     a3,a3,#SUP_MODE                  ; Prepare to switch to supervisor mode (SVC)
MSR     CPSR_cxsf,a3                     ; Switch to supervisor mode (SVC)

LDR     a4, SYSTEM_FATAL_ERROR
BX      a4

SYSTEM_FATAL_ERROR实际是函数stack_system_error的地址
在stack_system_error函数中,首先会切换sp到一个专门为异常处理预留的内存地址上,然后根据异常传入的类型填充必要的参数后调用fatal_error_handler。

kal_fatal_error_handler(kal_char *error_message_ptr, kal_uint32 error_code, kal_uint32 os_error_code)
{
DisableIRQ();
error_param_g.param1 = (kal_uint32)error_message_ptr;
error_param_g.param2 = (kal_uint32)error_code;
error_param_g.param3 = (kal_uint32)os_error_code;
fatal_error_handler((kal_uint8 *)error_param_g.param1, error_param_g.param2, error_param_g.param3);

}
在fatal_error_handler中
{

/* lockout all interrupts */
DisableIRQ();

/* mask all interrupts */
IRQDirectMaskAll();

/*
* NoteXXX: To avoid system hang in the exception handler,
*          watchdog remains active while handling exception (if it is enabled).
*/
WDT_Restart2();

/* increase the coutner */
INT_Exception_Enter++;

/* determine if multi-level exception*/
if (INT_Exception_Enter >= 2) {

tst_sysfatal_trace((kal_uint8 *)"Caution: Possibly Endless Nested Exceptions!");

ex_reboot();
}

//以下开始准备得到reset时的系统状态。
/*
* I. Initialize the exception log.
*/
/* reset hardware */
ex_reset_hw();//dma_recover_all,L1Audio_ResetDevice,L1D_PauseDSP
/* re-start WDT again */
WDT_Restart2();

然后得到当前task,并将其优先级设为0,最高
/* get the current thread id */
current_thread = kal_get_current_thread_ID();

/* determine if the current executed task is a TASK, not a HISR */
if ((current_thread != NULL) && (kal_if_hisr() == KAL_FALSE)) {

/* raise the task priority level to the highest */
kal_change_priority((NU_TASK *)current_thread, 0);
}

/* setup param */
param.ext = KAL_FALSE;
param.e1 = param.e2 = param.e3 = 0;
param.dump_param = NULL;

/* initialize the exception log */

/* determine if ARM pre-defined exceptions */
if (err_code < ASSERT_FAIL_EXCEPTION) {

param.type = (exception_type)err_code;

param.code1 = (kal_uint32 *)&os_err_code;

param.code2 = 0;

ex_init_log(&param);

} else
//在ex_init_log中会取得当前版本信息,当前时间,当前系统堆栈信息,当前task状态,hisr状态

然后
/* output the exception log */
ex_output_log();
最后重启系统
/* reboot silently */
ex_reboot();  //使用watchdog来重启系统。

}

在系统重启后有如下一段代码
;  /* Check if abnormal reset */
LDR   a1,ABN_RST_PTR
BL    INT_SystemReset_Check
在INT_SystemReset_Check中会读取地址为0×80040018 的寄存器的值并且同0×0ffa进行比较不相等就返回否则进入kal_fatal_error_handler

关于0×80040018的这个寄存器,datasheet上如下描述Watchdog Timer Reset Signal Duration Register。This register indicates the reset duration
When Watchdog timer times out

关于这段代码,猜测是因为有可能是系统因为挂起而没有reset watchdog寄存器导致重启,这里就可以进行log的抓取。

Tagged with: .
Avatar

在code已经优化的情况下的做法,其他系统类似也应该可用。

1.code放到internal ram中去执行

2.code中使用的全局变量或动态分配的内存放到internal ram中。

3.执行该code时将系统堆栈设置到internal ram中。

Tagged with: .
Avatar

该项目平台是MTK6225,ARM7处理器,nor boot,128+32
DM技术方案提供商为Red Bend公司。

在加入DM方案以后,系统的划分如下

ROM1 0×00000000 0×00040000
ROM2 0×00040000 0×00d00000
ROM3 0×00d40000 0×000C0000

ROM1用来放FOTA更新程序和Red Bend 提供的lib部分。
ROM2为原来MTK程序,也是版本需要更新部分。
ROM3用来存放差分包,最后两个block一个用来做临时备份使用,一个用来做标志(安全但是浪费啊)。
注意:ARM的中断向量当然要放到ROM1中,*.obj的LEADING_PART部分也是需要的,否则可能无法boot。

片外RAM部分
FOTA部分将会使用很小的一部分,原来的MTK程序依次向后就可以了。FOTA部分程序当然会使用到MTK程序部分的内存,这个是因为FOTA需要很大的heap,并且这样做是没有问题的,因为MTK程序部分后面会再次初始化一次。

片内RAM部分
在这个项目的实现中,我将FLASH驱动代码放在了片内RAM中去执行。MTK程序依次向后就可以了。

进入FOTA的bootloader后,依然是先切换到svc模式,设置系统堆栈,设置EMI和DPLL.配置需要的GPIO,UART,手动将INTSRAM_CODE部分由FLASH copy到对应的RAM中,将FOTA必须的FLASH驱动copy到片内RAM中,禁掉watchdog timer,完成必须的lcd的初始化,然后

int i=0;
char *a=(char *)BACK_BLOCK_ADDR1;char *b=(char *)BACK_BLOCK_ADDR2;
char *backbuferr[2];
backbuferr[0]=a;backbuferr[1]=b;
i = RB_ImageUpdate((unsigned long int)UPDATE_ADDRESS_BEGIN,(unsigned long int)UPDATE_ADDRESS_END,(unsigned char*)FOTA_MEMORY_BEGIN,(unsigned long int)FOTA_MEMORY_SIZE,(unsigned char*)0,(unsigned long int)0,
(unsigned long int *)backbuferr,(unsigned long int)2,(unsigned long int)0);
if(i==S_RB_SUCCESS)
{
PW_Set_FOTA_Flag();
set_successed_flag();
clear_need_update_flag();
return;
}

Done.
Redbend提供给我们的RB_flash.c和RB_ImageUpdate.c是我们必须要完成接口函数,当我们将对应的驱动实现后,这些函数还是很容易完成的。

Tagged with: , .
Avatar

FOTA在中国移动终端管理中指的是将手机软件由旧版本升级到新版本的实现部分。其原理是首先根据特殊的算法将新旧版本之间的差别做成一个差分包,然后手机从网络上下载到手机中,然后重新启动。在手机重启时通过对应的算法读取差分包中的信息,然后通过直接擦写flash的方式完成版本的更新。

版本更新时运行的程序当然是放在bootloader 那部分的,这部分代码一般来说是不会被更新的,因为如果这部分flash在更新时被擦除,代码被擦除后就无法继续进行了。当然如果一定要对这部分进行更新,那么就必须将这部分代码放到ram中去执行。

Flash擦写是以block为单位的,这是flash的驱动决定的。FOTA更新和恢复现场需要对版本进行校验,是通过对每个flash block中数据的checksum来进行的,以此来决定该block是否已经进行了更新,以及整个版本是不是对应的版本。

更新的区域当然不能包括用户使用的区域,因为这里保存的用户个人的数据。

同时我们手机的flash除了版本外当然必须还有空余的空间用手存放升级的差分包,用于临时使用的备份的flash block,以及为掉电后恢复现场的标志。

只有完全明白FOTA的实现原理,系统工程师才能对系统部分进行重新划分,这其实也是实现FOTA的核心所在。

有无FOTA时对应flash的使用分布情况
Bootloader和Update Agent 部分主要的功能就是实现了通过对差分包信息的提取然后直接擦写flash来完成版本的更新。这里是FOTA的核心程序所在。这部分包括系统的bootloader,FOTA运行时所需要的FLASH,LCD,TRACE等驱动,FOTA运行控制的Update Agent部分。

更新时临时备份区域主要是用来备份flash block 上的数据的,以block为单位。具体需要的空间大小根据update agent需要分配。

标志区域主要是用来标识是否需要FOTA升级,是否升级成功。

关于RAM划分。在FOTA的过程中,除掉FOTA必须的RAM(FOTA部分的全局变量)其余部分都是可用的。也就是说FOTA可以复用版本所使用的RAM,因为FOTA是在bootloader部分,FOTA完成后的正常系统流程会再一次对全局变量进行清零等操作。

需要注意的地方:
Flash的擦写函数需要放到RAM中去执行,因为同一块bank不能同时读写。Flash的驱动是放在bootloader部分的,因为该段使用较小,不可能独占一个bank。

各个段的起始地址一定要以flash的block开始。

Bootloader部分注意不要调用标准的实时库函数,例如memcpy之类,类似copy这样的函数可以自己实现。实时库函数在升级过程中会被完全擦除掉。

Bootloader和Update Agent部分必须完全独立。检验方法一个是从生成的符号表文件中查看该区域是否有其他函数,另一个方法是将除该部分的代码外将其余部分全部擦除,然后开机看能否正常运行。

Tagged with: , .
Avatar

scatter-loading机制允许我们通过scatter file将image中的各个区域指定到特定的地址上。其实对于一个特定的系统,这个也是必须的,因为每个系统的片选地址是不一样的。

以下这个简单的例子中,flash的开始地址是0×0000,其长度为0×8000,用于存放code和各种Read-Olny数据。RAM的起始地址是0×10000,其可用大小为0×6000,用于存放Read-Write数据。

Load和Run时的情况如下

我们也可以通过类似bootarm.obj (C$$code,+First)的格式,来手动指定特定c文件中特定部分(使用预处理指令命名,例子中为C$$code)出现在该段的最前面(+First)或最后面(+Last)。
当然scatter file支持更加复杂的应用,比如多个load rom,内存共用,代码在ram中执行等等…

使用scatter file会对应生成一些与之相对应的符号(symbols)。这些符号是可以在我们的代码中进行引用的。生成的符号及其意义如下:
Load$$region_name$$Base                 Load address of the region.
Image$$region_name$$Base               Execution address of the region.
Image$$region_name$$Length            Execution region length in bytes (multiple of 4).
Image$$region_name$$Limit               Address of the byte beyond the end of the execution region.
Image$$region_name$$ZI$$Base         Execution address of the ZI output section in this region.
Image$$region_name$$ZI$$Length      Length of the ZI output section in bytes (multiple of 4).
Image$$region_name$$ZI$$Limit         Address of the byte beyond the end of the ZI output section in the execution region.
在我们的代码中使用如下例子。
IMPORT ||Image$$region_name$$ZI$$Limit||
__user_initial_stackheap
LDR r0, =||Image$$region_name$$ZI$$Limit||
MOV pc, lr

Tagged with: .
Avatar

一般一个ARM系统的boot过程是这样的。首先ARM在上电后直接跳转到0地址,在这个地址上存放的是系统的启动代码。系统开始执行时一般会先设置DPLL和clock,然后进行片选,接着将需要放到ram中代码手动copy到ram中,设置EMI等系统必要参数,设置ARM各个模式的系统堆栈,切回SVC模式进行操作系统初始化。
以下是一个实际的基于ARM平台的一个bootloader示例。
编译环境:ADSv1.2
Read the rest of this post »

Tagged with: , .
Avatar

在Nucleus中timer的创建函数是NU_Create_Timer,其定义是

#define         NU_Create_Timer                 TMS_Create_Timer

通过其参数我们就可以看出如何设置一个timer。

STATUS  TMS_Create_Timer(NU_TIMER *timer_ptr, CHAR *name,
VOID (*expiration_routine)(UNSIGNED), UNSIGNED id,
UNSIGNED initial_time, UNSIGNED reschedule_time, OPTION enable)

timer -> tm_actual_timer.tm_timer_type =    TM_APPL_TIMER;
CSC_Place_On_List(&TMD_Created_Timers_List, &(timer -> tm_created));
TMD_Total_Timers++;

创建一个timer是很容易看懂的,我们主要分析timer时间到了之后的系统流程。

在TI平台中使用一个硬件晶振产生中断来作为系统的定时器,每次中断间隔时间作为操作系统的一个tick,也就是一个时间片。当中断发生时也就是产生了一个irq,在以前的irq流程中分析中我们可以知道每个irq都有一个自己的处理函数。在ti的平台的这个函数中我们可以发现如下代码

TMT_Timer_Interrupt();

这个函数就是Nucleus timer的接口函数,在这个函数中会检查是否有timer的时间到了,如果有timer active需要进行对应的操作,该函数会激活timer的HISR,这个HISR是在Nucleus初始化时TMI_Initialize函数中创建的。

status =  TCCE_Create_HISR((NU_HISR *) &TMD_HISR, "SYSTEM H",
TMC_Timer_HISR, (OPTION) TMD_HISR_Priority,
TMD_HISR_Stack_Ptr, TMD_HISR_Stack_Size);

在这个HISR的处理函数中,使用timer函数创建的timer的最主要的代码是

/* Determine if the task timer has expired.  */
if (TMD_Timer_State == TM_EXPIRED)

/* Resume the timer task.  */
TMC_Timer_Expiration();

在TMC_Timer_Expiration函数中

id =                  app_timer -> tm_expiration_id;
expiration_routine =  app_timer -> tm_expiration_routine;
//app_timer的tm_expiration_id和tm_expiration_routine就是创建函数TMS_Create_Timer传入的参数。
if (!done)
{

/* Determine which type of timer has expired.  */
if (type == TM_APPL_TIMER)
//类型为TM_APPL_TIMER,也就是task调用timer函数创建的类型。
//调用创建传入的timer超时时需要调用的函数。
/* Call application timer's expiration function.  */
(*(expiration_routine)) (id);
else
//这里是Nucleus进行task的时间片处理的部分。
/* Call the task timeout function in the thread control
function.  */
TCC_Task_Timeout((NU_TASK *) pointer);
}

基本上Nucleus的timer主要流程就是这些了。

Avatar

在Nucleus的等待事件函数EVC_Retrieve_Events中有这么一段函数

if (suspend)
{

/* Suspension is selected.  */

/* Increment the number of tasks waiting.  */
event_group -> ev_tasks_waiting++;

/* Setup the suspend block and suspend the calling task.  */
suspend_ptr =  &suspend_block;
suspend_ptr -> ev_event_group =              event_group;
suspend_ptr -> ev_suspend_link.cs_next =     NU_NULL;
suspend_ptr -> ev_suspend_link.cs_previous = NU_NULL;
task =                            (TC_TCB *) TCT_Current_Thread();
suspend_ptr -> ev_suspended_task =           task;
suspend_ptr -> ev_requested_events =         requested_events;
suspend_ptr -> ev_operation =                operation;

/* Link the suspend block into the list of suspended tasks on this
event group.  */
CSC_Place_On_List((CS_NODE **)
&(event_group -> ev_suspension_list),
&(suspend_ptr -> ev_suspend_link));

/* Finally, suspend the calling task. Note that the suspension call
automatically clears the protection on the event group.  */
TCC_Suspend_Task((NU_TASK *) task, NU_EVENT_SUSPEND,
EVC_Cleanup, suspend_ptr, suspend);

/* Pickup the return status and the actual retrieved events.  */
status =             suspend_ptr -> ev_return_status;
*retrieved_events =  suspend_ptr -> ev_actual_events;

}
else
..........

我们看到当task因为该事件而阻塞的代码

/* Finally, suspend the calling task. Note that the suspension call
automatically clears the protection on the event group.  */
TCC_Suspend_Task((NU_TASK *) task, NU_EVENT_SUSPEND,
EVC_Cleanup, suspend_ptr, suspend);

执行后紧接着的就是将等待事件的状态和接受到消息的变量进行了赋值,这是为什么呢,这样正确吗?
原来是这样的。当TCC_Suspend_Task被调用后当前task被挂起,这个函数以后的代码已经不会再接着执行了。等到这个task恢复时说明这个事件已经等到了,这个事件的值是在EVC_Set_Events函数中被设置的。

if (compare)
{

/* Decrement the number of tasks waiting counter.  */
event_group -> ev_tasks_waiting--;

/* Determine if consumption is requested.  */
if (suspend_ptr -> ev_operation & EV_CONSUME)

/* Keep track of the event flags to consume.  */
consume =  consume | suspend_ptr -> ev_requested_events;

/* Remove the first suspended block from the list.  */
CSC_Remove_From_List((CS_NODE **)
&(event_group -> ev_suspension_list),
&(suspend_ptr -> ev_suspend_link));

/* Setup the appropriate return value.  */
suspend_ptr -> ev_return_status =  NU_SUCCESS;
suspend_ptr -> ev_actual_events =
event_group -> ev_current_events;

/* Resume the suspended task.  */
preempt = preempt |
TCC_Resume_Task((NU_TASK *) suspend_ptr -> ev_suspended_task,
NU_EVENT_SUSPEND);

}

可以看到设置的返回值就是EVC_Retrieve_Events函数中的返回值。