MTK定时器消息机制分析
1. 数据结构
(1). stack_timer_struct
定时器类型的信息结构( 其主要作用似乎是用以装载待发送的定时器消息数据 )
(2). TIMERTABLE
定时器队列节点结构( 其由主要元素mmi_frm_timer_type结构及链表指针两个元素组成 )
(3). event_scheduler
队列信息结构
(4). mmi_frm_timer_type
定时器信息结构
2. L4定时器初始化
(1). 步骤
...-> 创建MMI Task -> 设置MMI Task初始化函数 -> 在该函数中调用 L4InitTimer
(2). 作用
初始化定时器队列并设置基本定时器1,2
3. 发送定时器消息
(1). 步骤
StartTimer -> L4StartTimer
(2). 两种类型的定时器
MTK中有两种类型的定时器
a. NO_ALIGNMENT
非队列式的,即要求立即执行的定时器,时间到了就自动被reset.
b. ALIGNMENT
队列式的, 即可以通过队列操作,有一定的延时容忍的定时器 .
其基本执行流程: 执行定时器 --> 超时? --> 保存timer id,event id -- timer stop || no event ?----> END ; |
----------------------------------------------------------
c. 除了触摸屏和手写,其他情况下的定时器一般都是队列式的.
(3). L4StartTimer的作用
判断将要发送的定时器ID,根据是否是队列类型传递给不同的队列结构(event_sheduler1/event_sheduler2) ;
(4). TimerExpiry
这是作为参数传递给L4StartTimer的回调函数,由于MTK做了一定的封装,因此其内部具体回调触发过程
无法得知,但根据猜测,应该是在定时时间一到,以中断的方式发出消息(MSG_ID_TIMER_EXPIRY),并将其写到
MMI的循环队列.
该函数可能是在L4CallBackTimer中调用的,L4CallBackTimer的作用如下:
a. 重置当前定时器信息结构(mmi_frm_timer_type) ;
b. 执行定时器到点后的执行函数(TimerExpiry) ;
c. 讲Timer消息写到MMI循环队列中 .
4. 与StartTimer对应的StopTimer
(1). 具体实现通过调用L4StopTimer操作.
(2). 作用: 找出指定要停止的定时器ID在队列中的位置,然后使用evshed_cancel_event将指定定时器节点从队列中删除.
5. 定时器消息的处理
(1). 步骤
...-> 创建MMI Task -> 设置MMI Task入口函数 -> 调用 EvshedMMITimerHandler
(2). evshed_timer_handler( ) -> 处理具体的定时器事件
6. 小结
简单分析MTK定时器消息事件,由于只是简单的分析,谬误定然甚多,忘包涵包涵并不吝指正.
MTK定时器消息处理机制
一、基本概念及Neclus内核定时器初始化
expires:指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数)。当一个定时器的expires值小于或等于jiffies变量时,我们就说这个定时器已经超时或到期了。在初始化一个定时器后,通常把它的expires域设置成当前expires变量的当前值加上某个时间间隔值(以时钟滴答次数计。
typedef struct timertable
{ /* store the timer_id. MSB(Most Significant Bit) is align_timer_mask */
U16 timer_id[SIMULTANEOUS_TIMER_NUM];
/* store the event_id that returns from evshed_set_event() */
eventid event_id[SIMULTANEOUS_TIMER_NUM];
/* store the timer_expiry_func */
oslTimerFuncPtr callback_func[SIMULTANEOUS_TIMER_NUM];
/* point to the next TIMERTABLE data */
struct timertable *next;
} TIMERTABLE;
typedef lcd_dll_node *eventid;
struct lcd_dll_node {
void *data;
lcd_dll_node *prev;
lcd_dll_node *next;
};
(1)timer_id:定时器id最多同时12个。
(2)双向链表元素event_id:用来将多个定时器调度动作连接成一条双向循环队列。
(3)函数指针callback_func:指向一个可执行函数。当定时器到期时,内核就执行function所指定的函数,产生expires 消息。
//L4 init the timer
/*****************************************************************************
* FUNCTION
* L4InitTimer
* DESCRIPTION
* This function is to init the timer while task create.
*
* PARAMETERS
* a IN void
* RETURNS
* VOID.
* GLOBALS AFFECTED
* external_global
*****************************************************************************/
void L4InitTimer(void)
{
/*----------------------------------------------------------------*/
/* Local Variables */
/*----------------------------------------------------------------*/
TIMERTABLE *p;
TIMERTABLE *pp;
/*----------------------------------------------------------------*/
/* Code Body */
/*----------------------------------------------------------------*/
/* Try to free TIMERTABLE list exclude g_timer_table */
p = g_timer_table.next;
pp = NULL;
do
{
if (p != NULL)
{
pp = p->next;
OslMfree(p);
}
p = pp;
} while (p != NULL);
/* reset g_timer_talbe */
memset(&g_timer_table, 0, sizeof(TIMERTABLE));
g_timer_table_size = SIMULTANEOUS_TIMER_NUM;
g_timer_table_used = 0;
/* Initiate the clock time callback function. */
get_clocktime_callback_func = NULL;
set_clocktime_callback_func = NULL;
/* Initate the no alignment stack timer */
stack_init_timer (&base_timer1, "MMI_Base_Timer1", MOD_MMI);
/* Create a no alignment timer schedule */
event_scheduler1_ptr = new_evshed(&base_timer1,
L4StartBaseTimer, L4StopBaseTimer,
0 , kal_evshed_get_mem, kal_evshed_free_mem, 0);
/* Initate the alignment stack timer */
stack_init_timer (&base_timer2, "MMI_Base_Timer2", MOD_MMI);
/* Create an alignment timer schedule */
event_scheduler2_ptr = new_evshed(&base_timer2,
L4StartBaseTimer, L4StopBaseTimer,
0, kal_evshed_get_mem, kal_evshed_free_mem, 255);
}
typedef struct stack_timer_struct_t {
module_type dest_mod_id;
kal_timerid kal_timer_id;
kal_uint16 timer_indx;
stack_timer_status_type timer_status;
kal_uint8 invalid_time_out_count;
} stack_timer_struct;
/*************************************************************************
* Exported Function Prototypes
*************************************************************************/
/*
* Important:
* Current implementation max_delay_ticks _disibledevent="text-indent: 24pt; line-height: 150%" align="left">
二、Linux动态内核定时器机制的原理
Linux是怎样为其内核定时器机制提供动态扩展能力的呢?其关键就在于“定时器向量”的概念。所谓“定时器向量”就是指这样一条双向循环定时器队列(对列中的每一个元素都是一个timer_list结构):对列中的所有定时器都在同一个时刻到期,也即对列中的每一个timer_list结构都具有相同的expires值。显然,可以用一个timer_list结构类型的指针来表示一个定时器向量。
显然,定时器expires成员的值与jiffies变量的差值决定了一个定时器将在多长时间后到期。在32位系统中,这个时间差值的最大值应该是0xffffffff。因此如果是基于“定时器向量”基本定义,内核将至少要维护0xffffffff个timer_list结构类型的指针,这显然是不现实的。
另一方面,从内核本身这个角度看,它所关心的定时器显然不是那些已经过期而被执行过的定时器(这些定时器完全可以被丢弃),也不是那些要经过很长时间才会到期的定时器,而是那些当前已经到期或者马上就要到期的定时器(注意!时间间隔是以滴答次数为计数单位的)。
基于上述考虑,并假定一个定时器要经过interval个时钟滴答后才到期(interval=expires-jiffies),则Linux采用了下列思想来实现其动态内核定时器机制:对于那些0≤interval≤255的定时器,Linux严格按照定时器向量的基本语义来组织这些定时器,也即Linux内核最关心那些在接下来的255个时钟节拍内就要到期的定时器,因此将它们按照各自不同的expires值组织成256个定时器向量。而对于那些256≤interval≤0xffffffff的定时器,由于他们离到期还有一段时间,因此内核并不关心他们,而是将它们以一种扩展的定时器向量语义(或称为“松散的定时器向量语义”)进行组织。所谓“松散的定时器向量语义”就是指:各定时器的expires值可以互不相同的一个定时器队列。
三、MTK Linux动态内核定时器机制的原理
MTK内核只需要同时维护12个类型的定时器,而且限定了其interval≤255,列为最关心处理的定时器,那么我们只需要为每个类型的定时器分配不同的expires值,因为它们按照不同的expires值组织成定时器向量。MTK内核里还分为两种不同类型的定时器,一种允许delay的,一种不允许,其他这些在初始化调度函数指针时就已经指定好了,内核对两者的处理是一样的。一般使用的是允许delay的定时器,防止一些突发性错误。
四、MTK具体代码级分析
MMITask.c中:
void MMI_task(oslEntryType * entry_param)
{
……
switch (Message.msg_id)
{
case MSG_ID_TIMER_EXPIRY:
{
kal_uint16 msg_len;
EvshedMMITimerHandler(get_local_para_ptr(Message.oslDataPtr, &msg_len));
}
break;
……
}
}
在这里或者我们在trace信息的时候经常可能看到一类比较特殊的消息,MSG_ID_TIMER_EXPIRY.(定时器时间到的消息ID)。Expiry是满,终结的意思。那么这个消息应该是我们在设置定时器的时候发给L4层的,然后再转交到MMI或者其他的模块的,被MMITASK所获取读到消息队列里的,然后针对其进行时间上的再分配和调度。
在MTK里面,存在两种性质定时时间,一种是很精确的(no alignment timer),一种允许调整校准(delay)(allow alignment timer).而timer发出的消息不类似其他任务或进程发出的消息,并不是信号量,信号量的话一般发出来被其他进程得到(处理完后)可能就消亡了,而timer 的动作则需要重复的产生和调度,对两者的处理不一样。()/*
* NoteXXX:
* In evshed_timer_handler(), system would execute event regisited timeout callback function.
* Caller should reset saved event id in regisited timeout callback function,
* or cause potential bug to cancel wrong timer event.
*/
见以下代码:
(MMI Timer Event Scheduler)
//MMI定时器动作调度函数
void EvshedMMITimerHandler(void *dataPtr)
{
/*----------------------------------------------------------------*/
/* Local Variables */
/*----------------------------------------------------------------*/
stack_timer_struct* stack_timer_ptr;
stack_timer_ptr = (stack_timer_struct*)dataPtr;
/*----------------------------------------------------------------*/
/* Code Body */
/*----------------------------------------------------------------*/
if (stack_timer_ptr == &base_timer1)
{
if (stack_is_time_out_valid(&base_timer1))
{
evshed_timer_handler(event_scheduler1_ptr);
}
stack_process_time_out(&base_timer1);
}
else if (stack_timer_ptr == &base_timer2)
{
if (stack_is_time_out_valid(&base_timer2))
{
evshed_timer_handler(event_scheduler2_ptr);
}
stack_process_time_out(&base_timer2);
}
}
上面讲的是如何对消息进行处理,那么设置定时器后这消息是如何发出去呢?这个过程还没有讲。
见代码:
void StartTimer(U16 timerid, U32 delay, FuncPtr funcPtr)
{
StartMyTimer(timerid, delay, (oslTimerFuncPtr)funcPtr);
}
再往下:
#define StartMyTimer(nTimerId,nTimeDuration,TimerExpiryFunction)/
StartMyTimerInt(nTimerId,nTimeDuration,TimerExpiryFunction,0)
再往下:
U16 StartMyTimerInt(U16 nTimerId,U32 nTimeDuration,oslTimerFuncPtr TimerExpiry, U8 alignment)
{
if(TimerExist[nTimerId])
{
OslStopSoftTimer(nTimerId);
}
OslStartSoftTimer(nTimerId,TimerExpiry, nTimerId, nTimeDuration,alignment);
TimerExist[nTimerId]=1;
return TRUE;
}
L4 timer://将定时器消息由L4发送给MMI
#define OslStartSoftTimer(nTimerId,TimerExpiry, nTimerId1, nTimeDuration,alignment)/
L4StartTimer(nTimerId,TimerExpiry,(void*)nTimerId1,nTimeDuration,alignment)
void L4StartTimer(unsigned short nTimerId, oslTimerFuncPtr TimerExpiry, void * funcArg, unsigned long nTimeDurationInTicks, unsigned char alignment)
{
/*----------------------------------------------------------------*/
/* Local Variables */
/*----------------------------------------------------------------*/
TIMERTABLE *pTable = NULL;
U16 i = 0;
U32 temp;
/*----------------------------------------------------------------*/
/* Code Body */
/*----------------------------------------------------------------*/
if (TimerExpiry == NULL)
{ /* If TimerExpiry is NULL, we don't start the timer */
MMI_ASSERT(0);
return ;
}
MMI_ASSERT(nTimerId<MAX_TIMERS);
if (L4TimerUsePreciseTick(nTimerId))
{
temp = ( nTimeDurationInTicks * KAL_TICKS_10_MSEC ) / 10;
if (temp == 0)
{
temp = KAL_TICKS_10_MSEC;
}
alignment = TIMER_IS_NO_ALIGNMENT;//非对列的,需要紧急处理,处理完立即reset.
}
else
{
if (nTimeDurationInTicks == 1000)
{
temp = KAL_TICKS_1_SEC-4;
}
else
{
temp = (nTimeDurationInTicks / 100) * KAL_TICKS_100_MSEC;
}
if (temp==0)
{
temp = KAL_TICKS_100_MSEC;
}
} /* if (L4TimerUsePreciseTick(nTimerId)) */
/*
* Because the handset doesn’t camp _disibledevent="text-indent: 21.6pt">* it influences MMI alignment timer are inaccurate.
* We change all of MMI timer to non-alignment timer in flight mode.
*/
if ( mmi_bootup_get_active_flight_mode() == 1 )
{
alignment = TIMER_IS_NO_ALIGNMENT;
}
MMI_TRACE((MMI_TRACE_G1_FRM, MI_FRM_INFO_L4DRV_STARTTIMER_HDLR, nTimerId, TimerExpiry, temp, alignment));
pTable = &g_timer_table;
if (g_timer_table_used >= g_timer_table_size)
{
/*
* TIMERTABLE list doesn’t have enough space, allocate the memory
* * If we need to allocate the memeory, it means that MMI may have
* such many timers run simultaneously. We won’t free the memory
* after we allocate more memory in TIMERTABLE list.
*/
do
{
if (pTable->next == NULL)
{
pTable->next = OslMalloc(sizeof(TIMERTABLE));
memset(pTable->next, 0, sizeof(TIMERTABLE));
g_timer_table_size += SIMULTANEOUS_TIMER_NUM;
pTable = pTable->next;
i = 0;
break;
}
pTable = pTable-> next;
} while (pTable != NULL);
}
else
{
/* find the empty record in g_timer_table list */
i = 0;
do
{
if (pTable->event_id[i] == NULL)
{ /* find the empty space */
break;
}
i++;
if (i >= SIMULTANEOUS_TIMER_NUM )
{
pTable = pTable->next;
i = 0;
}
} while (pTable != NULL);
if (pTable == NULL)
{
/* Can’t find the empty space in TIMERTABLE list, assert!!! */
MMI_ASSERT(0);
}
} /* if (g_timer_table_used >= g_timer_table_size) */
/*
* already find the empty record, and then start timer
* event_sheduler1 = NO alignment scherulder
* event_sheduler2 = alignment scherulder (low power)
*/
if (alignment == TIMER_IS_NO_ALIGNMENT)
{
/* MSB(Most Significant Bit) is align_timer_mask */
pTable->timer_id[i] = nTimerId | NO_ALIGNMENT_TIMER_MASK;
pTable->event_id[i] = evshed_set_event (event_scheduler1_ptr,
(kal_timer_func_ptr)L4CallBackTimer, (void *)nTimerId, temp);
pTable->callback_func[i] = TimerExpiry;
g_timer_table_used ++;
}
else if (alignment == TIMER_IS_ALIGNMENT)
{
/* MSB(Most Significant Bit) is align_timer_mask */
pTable->timer_id[i] = nTimerId | ALIGNMENT_TIMER_MASK;
pTable->event_id[i] = evshed_set_event (event_scheduler2_ptr,
(kal_timer_func_ptr)L4CallBackTimer, (void *)nTimerId,temp );
pTable->callback_func[i] = TimerExpiry;
g_timer_table_used ++;
}
}
TimerExpiry 类型为void (*oslTimerFuncPtr)(void*),根据我个人猜测消息应该在回调函数TimerExpiry里(定时时间一到,而且很有可能是中断的方式)发出MSG_ID_TIMER_EXPIRY。
Evshed_set_event()这个函数有点类似SetProtocolevent()设置协议动作函数,类似就象设置异步串口时只要相应消息一到就会跳到相应的处理函数里处理。在定时器timer里面,用的是L4CallBackTimer这个函数,在这个函数里面:
// 将使用过的TimerID和EventID给复位(reset).
*****************************************************************************/
void L4CallBackTimer(void *p)
{
U32 nTimerId = (U32)p;
TIMERTABLE *pTable = &g_timer_table;
U32 i = 0;
oslTimerFuncPtr pTimerExpiry = NULL;
MMI_ASSERT(nTimerId < MAX_TIMERS);
/* find the nTimerId in TIMERTABLE list */
do
{
/* MSB(Most Significant Bit) of timer_id[i] is align_timer_mask */
if ((pTable->timer_id[i] & (~NO_ALIGNMENT_TIMER_MASK)) == (U16)nTimerId)
{
/* find out nTimerId */
if (pTable->callback_func[i] != NULL)
{
pTimerExpiry = pTable->callback_func[i];
MMI_TRACE((MMI_TRACE_G1_FRM, MMI_FRM_INFO_L4DRV_CBTIMER_HDLR, nTimerId, pTimerExpiry));
g_timer_table_used --;
pTable->event_id[i] = 0;
pTable->timer_id[i] = 0;
pTable->callback_func[i] = NULL;
/*
* we process g_timer_table_used, event_id and timer_id … first
* because the user may call stoptimer() in the timer_expiry_func
*/
pTimerExpiry(p);//此定时nTimerId已经执行完
}
break;
}
i++;
if (i >= SIMULTANEOUS_TIMER_NUM )
{
pTable = pTable->next;
i = 0;
}
} while (pTable != NULL);
/* can't find nTimerId, do nothing */
mmi_frm_fetch_msg_from_extQ_to_circularQ();
/*
* Because we modify MMI process protocol events and key events mechanism,
* we don't need to process key events here.
*/
}
mmi_frm_fetch_msg_from_extQ_to_circularQ();将对所有由MOD_TIMER(receive the message from MMI queue and put in circular queue)发送给MMI的消息放进循环队列……,而这个函数在MMI_task()函数while(1)循环一开始就用于获取内部队列消息到循环队列中(fetch the message from external queue and put in the circular queue)。
另外我们可以在Stoptimer()里面存在evshed_cancel_event()函数,这是与Starttimer()函数相对应。
这样一来对TIMER 消息的处理流程就清楚了许多。当然整个理解过程中可能还有一些缺陷,希望大家指出。
/*
* Important
* System will allocate memory for event id, and return to caller.
* If caller need to save event id, please be careful to reset when
* cancel the event or the event expired.
*/
extern eventid evshed_set_event(event_scheduler *es,
kal_timer_func_ptr event_hf,
void *event_hf_param,kal_uint32 elapse_time);
/*
* Important
* System would reset *eid to NULL before return, however, caller
* should pay attention to saved event id.
*/
extern kal_int32 evshed_cancel_event(event_scheduler *es, eventid *eid);
二、总结分析
//定时器消息一般分为两类:
(1) 队列型,这类消息一旦有事件处理它就会一直执行下去,就会出现超时现象(产生MSG_ID_TIMER_EXPIRY),接下去保存timer id和event id 继续再执行下去。直到定时器被停止或事件处理完,才会被reset.
// (2)非队列型:这类消息一发出去,而且一般时间很紧急,时间到了就自动被reset.
void L4CallBackTimer(void *p)函数。
基于这个思路:
作为MTK平台下一般是对列型定时器,队列定时器一般是允许调整校准的时间(根据EXPIRYS值来调整),一旦定时器开启后,一直在执行过程中,实际时间将超时(不超时可能会导致某些问题),系统计数时间到将产生MSG_ID_TIMER_EXPIRY消息,然后In evshed_timer_handler()函数处理,注册一个超时回调函数并复位定时器/保存event id。这样这个定时器一旦开启如何不被Stop将一直执行下去。
* NoteXXX:
* In evshed_timer_handler(), system would execute event regisited timeout callback function.
* Caller should reset saved event id in regisited timeout callback function,
* or cause potential bug to cancel wrong timer event.
*/
四、个人总结
关于队列与非队列型以及宽松队列型timer可以总结如下:
队列型:相当于执行正常的定时器,只允许稍微有偏差影响不大,比如延时500ms,实际超个1ms是没有问题。
非队列型:一般是立即执行,时间很短,MTK上为10ms,这时间相差个1ms问题很大。
宽松队列:只存在linux(软实时内核),延时1分钟,相差个10ms问题不大。
操作系统一般会对于这些定时器timer的紧急程度,分配不同的expires值进行调整的,以达到目的。宽松队列和队列型一般放在队列中进行处理,有一个等待的过程,而非对列型处理可能要紧急。
MTK(6225)定时器源代分析
yanzhong.lee
http://blog.163.com/lyzaily@126/blog/static/424388372009290535834/
最近在网上看了一些MTK定时器的有关博文,但是没有结合源代分析所以一下子可能看的不是太明白,我现在结合MTK6225源代码进行简单的说明,以便于大家交流!
(一)首先我想给出几个有关定时器的重要全局变量:
1:
static TIMERTABLE g_timer_table = {0};
说明:
g_timer_table 是系统维护的一个定时器链表,每一个TIMERTABLE变量可以储存12(或者24)个定时器;
TIMERTABLE的结构是:
typedef struct timertable
{
mmi_frm_timer_type tm[SIMULTANEOUS_TIMER_NUM];
struct timertable *next;
} TIMERTABLE;
其中mmi_frm_timer_type结构是:
typedef struct {
U16 timer_info;
eventid event_id;
oslTimerFuncPtr callback_func;
void* arg;
} mmi_frm_timer_type;
SIMULTANEOUS_TIMER_NUM==12 or 24,这两个值是系统推荐的值。
mmi_frm_timer_type中的成员timer_info最高BIT位表示了是队列定时器(0)还是非队列定时器(1);
event_id是个指针便利,由evshed_set_event()分配并返回的;
evshed_set_event()函数是源代码 MTK没有给出,只给出了简单的接口说明 如下英文所示:
这段话是什么意思:
就是说每个定时器(each timer item)中的成员变量event_id所指向的内存空间都是由函数evshed_set_event()分配的,并且将指向给内存的指针返回给调用者(也就是将event_id保存到全局变量
TIMERTABLE.tm[xxxx].event_id中);如果上层应用将该event id保存到了TIMERTABLE.tm[xxxx].event_id,则当事件取消函数执行后(evshed_cancel_event(event_scheduler *es, eventid *eid))或者定时事件结束后,上层应用(caller)必须将TIMERTABLE.tm[xxxx].event_id置城NULL。
2:
static U16 g_timer_table_size = 0;
表示全局变量g_timer_table中最多能储存多少个定时器;系统刚启动时对多能存储12个或24个,究竟多少个要取决于你系统中 SIMULTANEOUS_TIMER_NUM宏的值;当系统运行一段时间以后定时器个数已经超过了SIMULTANEOUS_TIMER_NUM宏所能表示范围;则需要系统分配一个TIMERTABLE大小的变量空间并将该空间的指针赋给全局变量g_timer_table 中的成员next指针;
请参阅L4StartTimer中的部分代码:
.....
.....
pTable = &g_timer_table;
if (g_timer_table_used >= g_timer_table_size) //表示当前已经使用的定时器数超过了一个TIMERTABLE变量所
//能存储的定时器个数
{
do
{
if (pTable->next == NULL)
{
pTable->next = OslMalloc(sizeof(TIMERTABLE));
memset(pTable->next, 0, sizeof(TIMERTABLE));
g_timer_table_size += SIMULTANEOUS_TIMER_NUM;
pTable = pTable->next;
i = 0;
break;
}
pTable = pTable->next;
} while (pTable != NULL);
}
else
....
....
[粉红色英文是什么意思?]
如果TIMERTABLE列表没有足够的空间存储定时器时,说明MMI在某个时刻需要更多的定时器同时运行,我们可以给系统分配更多的内存空间;但是我们不需要去释放刚才分配给TIMERTABEL列表的空间。那么这个空间在哪里释放呢?是在系统启动时释放;系统刚启动时在TIMERTABLE列表中只维护一个节点,只有当系统运行之后所需的定时器超过了12或24个时才为下一个列表节点分配空间;当系统重新启动后多余的节点空间就被释放。如下代码所示, 在L4InitTimer()中 。
mmi_create()->MMI_Init()->L4InitTimer()
...
...
TIMERTABLE *p;
TIMERTABLE *pp;
p = g_timer_table.next;
pp = NULL;
do
{
if (p != NULL)
{
pp = p->next;
OslMfree(p);
}
p = pp;
} while (p != NULL);
...
...
3:
static U16 g_timer_table_used = 0;
表示当前系统中已经使用了多少个定时器。
(二)定时器代码执行流程分析
要认识MTK定时器的工作机制,必须要分析如下几个函数的执行过程:
1:L4InitTimer()
2:StartTimer()
3:StopTimer()
4:L4CallBackTimer()
(一)L4InitTimer()已经在上面分析过,主要的工作就是释放TIMERTABLE列表中除了g_timer_table全局变量占有的所有内存空间 ;初始化全局变量;初始化系统默认的队列定时器、非队列定时器(使用函数static_init_timer() 和new_evshen()) .
(二)StartTimer()
首先在全局变量g_timer_table寻找是否有空间,如果有则找到存储定时器信息空间;否则分配一个TIMERTABLE类型变量的空间,并添加到g_timer_table中供系统使用。
有了存储定时器的空间后,主要做了以下工作:
判断是队列定时器还是非队列定时器,并添加到对应的定时器调度队列中。
if (alignment == TIMER_IS_NO_ALIGNMENT)
{
pTable->tm[i].timer_info = nTimerId | NO_ALIGNMENT_TIMER_MASK;
pTable->tm[i].event_id = evshed_set_event(
event_scheduler1_ptr, //非队列定时器调度队列
(kal_timer_func_ptr) L4CallBackTimer,
(void*)&(pTable->tm[i]),
temp);
pTable->tm[i].arg = funcArg;
pTable->tm[i].callback_func = TimerExpiry;
g_timer_table_used++;
}
else if (alignment == TIMER_IS_ALIGNMENT)
{
pTable->tm[i].timer_info = nTimerId | ALIGNMENT_TIMER_MASK;
pTable->tm[i].event_id = evshed_set_event(
event_scheduler2_ptr, //队列定时器 调度队列
(kal_timer_func_ptr) L4CallBackTimer,
(void*)&(pTable->tm[i]),
temp);
pTable->tm[i].arg = funcArg;
pTable->tm[i].callback_func = TimerExpiry;
g_timer_table_used++;
}
(三)L4CallBackTimer()
分析如下代码,当定时时间到时,会执行该函数;执行用户为定时器制订的定时器到期时要执行的任务;并且将外部消息队列中的消息取出并存储到MMI的循环消息队列中。
static void L4CallBackTimer(void *p)
{
mmi_frm_timer_type *pTimer = (mmi_frm_timer_type *)p;
U32 nTimerId = pTimer->timer_info & (~NO_ALIGNMENT_TIMER_MASK);
oslTimerFuncPtr pTimerExpiry = pTimer->callback_func;
void * arg = pTimer->arg;
MMI_ASSERT(nTimerId < MAX_TIMERS);
MMI_TRACE(MMI_FW_TRC_G1_FRM, MMI_FRM_INFO_L4DRV_CBTIMER_HDLR, nTimerId, pTimerExpiry);
g_timer_table_used--;
memset( pTimer, 0, sizeof(mmi_frm_timer_type));
if (pTimerExpiry) //用户在L4StartTimer()函数中设置的定时器结束时的回调函数
{
pTimerExpiry((void *)arg);
}
mmi_frm_fetch_msg_from_extQ_to_circularQ();
}
(四)L4stopTimer 就不用介绍了,很简单。
(五) 我们在(三)中已经知道,当定时器到期时会通过 L4CallBackTimer()函数调用我们APP层所制订的定时器到期回调函数;那么究竟 L4CallBackTimer()是如何被系统调用的呢????由于MTK对系统中的定时器作了封装无法看到源代码,所以我这里只是猜测,如果有错请各位看官指正,在此先谢谢各位!
在MMI_task()中有如下代码:
switch (Message.msg_id)
{
...
...
case MSG_ID_TIMER_EXPIRY:
{
kal_uint16 msg_len;
EvshedMMITimerHandler(get_local_para_ptr(Message.oslDataPtr, &msg_len));
}
break;
...
...
}
而函数EvshedMMITimerHandler()代码如下:
void EvshedMMITimerHandler(void *dataPtr)
{
stack_timer_struct *stack_timer_ptr;
stack_timer_ptr = (stack_timer_struct*) dataPtr;
if (stack_timer_ptr == &base_timer1)
{
if (stack_is_time_out_valid(&base_timer1))
{
evshed_timer_handler(event_scheduler1_ptr);
}
stack_process_time_out(&base_timer1);
}
else if (stack_timer_ptr == &base_timer2)
{
if (stack_is_time_out_valid(&base_timer2))
{
evshed_timer_handler(event_scheduler2_ptr);
}
stack_process_time_out(&base_timer2);
}
}
并且在L4CallBackTimer中调用了一个函数mmi_frm_fetch_msg_from_extQ_to_circularQ();而该函数的功能是将外部消息队列中的消息取出来,放入MMI的循环消息队列中。我们来看看 mmi_frm_fetch_msg_from_extQ_to_circularQ()中的一个代码片断;就会发现该函数放入MMI循环消息队列中的消息就是MMI_task()接下来就要处理的消息。
mmi_frm_fetch_msg_from_extQ_to_circularQ()代码片断如下:
if (mmi_frm_check_is_valid_msg(&checkBaseNode, &Message) == 1)
{
ilm_ptr.src_mod_id = Message.src_mod_id;
ilm_ptr.dest_mod_id = Message.dest_mod_id;
ilm_ptr.msg_id = Message.msg_id;
ilm_ptr.sap_id = Message.sap_id;
ilm_ptr.local_para_ptr = Message.local_para_ptr;
ilm_ptr.peer_buff_ptr = Message.peer_buff_ptr;
flag = OslWriteCircularQ(&ilm_ptr);
MMI_ASSERT(flag == 1);
if (Message.src_mod_id != MOD_TIMER)
{
hold_local_para(ilm_ptr.local_para_ptr);
hold_peer_buff(ilm_ptr.peer_buff_ptr);
}
}
而在MMI_task()中变量Message的类型是MYQUEUE,而MYQUEUE就是一个宏,定义如下:
#define MYQUEUE ilm_struct
由此可以看出,MMI_task()中处理的消息,就是由函数mmi_frm_fetch_msg_from_extQ_to_circularQ()从外部队列出去并封装成MMI循环队列的消息放入循环队列,并由MMI_task()来处理。
[总结]
app 通过StartTimer启动一个定时器;定时器到期时会发一个消息给MMI_task(),MMI_task()调用EvshedMMITimerHandler()通过该函数会去调用定时器到期时L4层的回调函数L4CallBackTimer();L4CallBackTimer()去回调由app定制的定时器到期回调函数;同时L4CallBackTimer()会将外部消息队列中的下一个消息取出并封装成MMI循环队列中的消息结构传入MMI循环队列,等候MMI_task()处理。
MTK之定时篇
★ 驱动层
例子:在接收到新短信时将GPIO24拉高0.3秒
kal_uint8 GPIO_timer;//新短信拉高GPIO定时用的Handle
kal_uint8 Message_Notify_GPIO = 24;
#define NEW_MESSAGE_KEEP_LEVEL_TIME 30 //Unit 10 ms
#define LEVEL_HIGH 1
#define LEVEL_LOW 0
void New_Message_Pull_Up_GPIO(……)
{
……
GPTI_GetHandle(&GPIO_timer);//获取定时Handle
GPIO_WriteIO(LEVEL_HIGH, Message_Notify_GPIO);//拉高GPIO24
GPTI_StartItem(GPIO_timer,
NEW_MESSAGE_KEEP_LEVEL_TIME, //定时时间
NEW_MESSAGE_GPIO_TIMER, //定时中断函数
NULL);
}
//定时中断函数
void NEW_MESSAGE_GPIO_TIMER(void *parameter)
{
GPTI_StopItem(GPIO_timer);//关定时中断
GPTI_ReleaseHandle(&GPIO_timer);
GPIO_WriteIO(LEVEL_LOW, Message_Notify_GPIO);//拉低GPIO24
}
★ MMI层
StartTimer(TIMER_ID, time, callback_function);
◆ TIMER_ID在plutommi/mmi/inc/TimerEvents.h中定义。
◆ time 定时时间,unit ms
◆ callback_function 回调函数