MTK timer

MTK timer有很多种,从最低层的KAL(kernel adpat layer)到MMI,都有timer的身影。
第一种timer即KAL timer,KAL timer是非常底层的timer,它的实现是直接封装MTK的RTOS(nuleus)的timer,实现方式是由HISR,从而这种timer具有很高的优先级,也就是说,当这个timer超时时,就会触发一个HISR(高级中断 High Level ISR (Interrupt Service Routine)),这个中断会回调注册的回调函数。所以这种timer 使用时,要比较小心,它的优先级很高,在回调函数运行时,其他事件是得不到处理的。
kal_timerid kal_create_timer(kal_char* timer_name_ptr); //创建一个timer,参数是timer的名字
void kal_set_timer(kal_timerid timer_id,
                   kal_timer_func_ptr handler_func_ptr,
                   void* handler_param_ptr,
                   kal_uint32 delay,
                   kal_uint32 reschedule_time);   
功能:设置timer 超时时间;
参数:timer_id 是create返回值;
          handler_func_ptr 是回调函数;
          handler_param_ptr 是回调函数返回的参数(一般回调函数都是这么设置,这样很灵活);
          delay 是超时时间,注意这个参数的单位是ticks 不是ms;
          reschedule_time 表示是否循环使用timer,0表示timer超时一次就停止,1表示自动循环启动timer。

第二种timer是stack timer,这种timer与KAL timer最大的区别是:stack timer 超时后,发送一个超时消息到相应task的消息队列,由task的消息队列来处理这个消息,而不像KAL timer,直接在中断状态回调注册函数。从时间的精确性来说,stack timer 可能没有KAL timer 精确(发送消息,task 切换等都要花费时间),但是 stack 更加的安全(在 task 里处理这个消息),提高并发性(stack timer 到期后只是发送了一个消息,具体并不处理)。当然stack timer 底层具体实现还是依靠KAL timer。有一点需要注意的是,当stack timer 超时后,向消息队列发送了消息,但是这个消息还没有来的处理,这个时候取消了这个timer,需要特别处理,虽然这种情况发生的概率很小。
void stack_init_timer(stack_timer_struct *stack_timer, kal_char *timer_name, module_type module_id);
// stack timer初始化module_id 是要接受消息的mdoule,也就是当stack timer 超时,会像改model 发送消息。
kal_bool stack_is_time_out_valid(stack_timer_struct *stack_timer);
//判断这个消息是否继续有效,也就是是否需要处理,这个函数就是用于方式上面提到那种情况的发生。
void stack_process_time_out(stack_timer_struct *stack_timer);
//这个函数与上面的函数成对使用,具体看例子。
void stack_start_timer(stack_timer_struct *stack_timer, kal_uint16 timer_indx, kal_uint32 init_time);
//启动定时期,timer_index timer索引,超时时间。

第三种timer,一般在task要管理一组timer,用stack timer加上event scheduler;
event scheduler处理的timer,从时间精确性上来说,相对不是那么精确,但对于上层app应用来说,是必不可少的。MMI层的timer(StartTimer 系列函数)就是用event scheduler + stack timer来实现的。
extern event_scheduler *new_evshed(void *timer_id,
                                   void (*start_timer)(void *, unsigned int),
                                   void (*stop_timer)(void *),
                                   kal_uint32 fuzz,
                                   malloc_fp_t alloc_fn_p,
                                   free_fp_t free_fn_p,
                                   kal_uint8 max_delay_ticks);
功能:创建一个 event scheduler;
参数:timer_id是stack timer创建的timer id,一般称为base timer;
         start_timer 启动这个base timer;
         stop_timer 停止这个base timer;
         fuzz 校正timer 时间;
         alloc_fn_p 内存分配函数;
         free_fn_p 内存释放函数;
         max_delay_ticks timer 最大可延迟时间,这个表示timer的准确度,这个参数的作用主要是用于节省电池。

extern eventid evshed_set_event(event_scheduler *es,
                                kal_timer_func_ptr event_hf,
                                void *event_hf_param,
                                kal_uint32 elapse_time);
功能:设置一个timer;
参数:es 用new_evshed创建的;
         event_hf, timer 超时后的回调函数;
         event_hf_param 回调函数传入的参数;
         elapse_time timer的时间。

extern void evshed_timer_handler(event_scheduler *es);
timer超时后,统一处理超时回调函数。也就是说,当stack timer向相应的mod发送MSG_ID_TIMER_EXPIRY后,需要调用该函数,该函数会处理相应的回调函数。

MTK 的这套机制让人感觉很别扭,像evshed_timer_handler这样的函数都要手动去调用,仔细想想,也只能这么来用,event scheduler依赖于 stack timer,而stack timer又只能往相应的

mod里发送消息,而这个mod的消息处理又是自己手动写的,如果不开放evshed_timer_handler这个函数,那么超时了,event scheduler也不知道。不过还是别扭阿。 
下面举了个例子,就上面这么一说,估计也是晕晕的,我当时看了N遍代码,才慢慢明白些,也没有个资料可以参考。就看MMI timer的实现,平时开发应用的时候,这个是用的最多的。

源文件:MMITimer.c
void L4InitTimer(void)
{
    /* Try to free TIMERTABLE list exclude g_timer_table */
    //释放g_timer_table的内存
    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);

    /* 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 timer 1(即base_timer1) 
    stack_init_timer(&base_timer1, "MMI_Base_Timer1", MOD_MMI);

    /* Create a no alignment timer schedule */
    //根据 stack timer 1 ,创建 event scheduler 1(即event_scheduler1_ptr)
    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 timer 2(即base_timer2)
    stack_init_timer(&base_timer2, "MMI_Base_Timer2", MOD_MMI);

    /* Create an alignment timer schedule */
    //根据 stack timer 2 ,创建 event scheduler 2(即event_scheduler2_ptr)
    event_scheduler2_ptr = new_evshed(
                            &base_timer2,
                            L4StartBaseTimer,
                            L4StopBaseTimer,
                            0,
                            kal_evshed_get_mem,
                            kal_evshed_free_mem,
                            254);
}

说明:MMI共有两种timer,一种是no alignment timer一种叫alignment timer。
两者的区别有两点:
1,前者不会延迟,也就是说相对于后者来说,精确很多,当然后者有可能延迟,也就是用后者创建了一个100ms timer,也许过了150ms才被回调,甚至300ms。
2,前者在手机休眠时不会被关起,而后会被挂起,也就是如果用后者创建了一个timer,还没有到期,这个时候手机休眠了,那么这个timer就不会被触发了,直到手机再次唤醒。
在MMI timer里面,这两种timer分别对应 event_scheduler1_ptr和event_scheduler2_ptr。
在MMI实现timer里面用了一种table,用来存放每一个timer的信息,这种table是用一种链表加上数组的实现的,这样实现其可以省去一些内存的频繁申请和释放。


在MMI层,启动一个timer,最终都会调用到L4StartTimer这个函数。具体来分析一下这个函数:
参数:nTimerId 是要自己定义一个timer id,在 timerEvents.h 里的 MMI_TIMER_IDS 定义,用来区分timer
         TimerExpiry 超时的回调函数;
         funcArg 回调函数回传的参数;
         nTimeDuration 超时时间,单位ms;
         alignment alignment或者non-alignment,在 MTK timer小结2说明过;

static void L4StartTimer(
        unsigned short nTimerId,
        oslTimerFuncPtr TimerExpiry,
        void *funcArg,
        unsigned long nTimeDuration,
        unsigned char alignment)
{
    TIMERTABLE *pTable = NULL;
    U16 i = 0;
    U32 temp;

    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))
    {
        alignment = TIMER_IS_NO_ALIGNMENT;
    }
    // 把ticks转化为ms
    // mtk底层的timer的单位都是ticks
    if (nTimeDuration == 1000)
    {
        temp = KAL_TICKS_1_SEC - 4;
    }
    else
    {
        temp = (U32)((nTimeDuration / 5) * MMI_TICKS_5_MSEC);
    }

    if (temp == 0)
    {
        /* Cause by by rounding. If expire immediately, MoDIS boot-up failure because MMI keeps running and block NVRAM task */
        temp = (U32)MMI_TICKS_5_MSEC;
    }
    // 取得存放所有MMI timer 的table(上文提到这个table是list + array的组合)
    // 如果只用存数组的话,初始化大小不好确定,而且要array relloc. 如果考虑内存回收还容易引起数组震荡
    // 如果只用list的话,一个timer如果很频繁的话,会不停的malloc 和 free
    pTable = &g_timer_table;
    // 判断timer是否已经满了,如果满了,那么需要在timer table后面增加一个timer node
    if (g_timer_table_used >= g_timer_table_size)
    {
        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
    {
        //寻找空的 timer node
        i = 0;
        do
        {
            if (pTable->tm[i].event_id == 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) */

    // 根据 algigment 属性,分别创建一个 event scheduler timer
    // 把 timer 的信息保存到 timer node 里面
    if (alignment == TIMER_IS_NO_ALIGNMENT)
    {
        /* MSB(Most Significant Bit) is align_timer_mask */
        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)
    {
        /* MSB(Most Significant Bit) is align_timer_mask */
        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++;
    }
}
再说一句timer table,其实也可以用timer pool来实现,把超时或者stop的timer node,放入到free pool中,需要时再拿出来,也可以避免重复的malloc和free。在设置event scheduler timer中,设置了一个回调函数L4CallBackTimer,这个回调函数就是在stack timer超时需要回调的函数。那具体在哪里回调?上文提到,event scheduler依赖于stack timer,当stack timer超时后要向相应的mod发送消息。在初始化L4InitTimer函数中,stack timer初始化(stack_init_timer(&base_timer1, "MMI_Base_Timer1", MOD_MMI))时mod是 MOD_MMI,那么超时后,就会向mmi task发送超时消息。MMI层在MMI_task函数中处理所有的消息,while消息处理中,可以看到:
case MSG_ID_TIMER_EXPIRY:
{
    kal_uint16 msg_len;
    EvshedMMITimerHandler(get_local_para_ptr(Message.oslDataPtr, &msg_len));
}
break;
这个就是处理stack timer地方。也就是stack timer超时后,回回调EvshedMMITimerHandler函数。
void EvshedMMITimerHandler(void *dataPtr)
{
    stack_timer_struct *stack_timer_ptr;
    stack_timer_ptr = (stack_timer_struct*) dataPtr;
    // 判断是哪个 stack timer
    if (stack_timer_ptr == &base_timer1)
    {
        // 这里需要判断 这 stack timer 是否还是有效,也就是否被stop
        // 这种情况出现环境在 MTK timer 小结 1 中介绍过
        // 如果无效,就不要触发这个timer
        if (stack_is_time_out_valid(&base_timer1))
        {
            //调用这个函数,处罚注册的 event scheduler timer
            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);
    }
}
这个函数在调用evshed_timer_handler函数是,就会回调由evshed_set_event注册的timer回调函数L4CallBackTimer。
static void L4CallBackTimer(void *p)
{
    // 在evshed_set_event 第三个参数中传得timer noder
    // 这里转换这个指针
    mmi_frm_timer_type *pTimer = (mmi_frm_timer_type *)p;
    // 得到timer id
    U32 nTimerId = pTimer->timer_info & (~NO_ALIGNMENT_TIMER_MASK);
    // 得到回调函数
    oslTimerFuncPtr pTimerExpiry = pTimer->callback_func;
    // 得到回调函数参数
    void * arg = pTimer->arg;
    // timer 个数减少
    g_timer_table_used--;
    // 清空这个 timer node
    memset( pTimer, 0, sizeof(mmi_frm_timer_type));   
    // 回调 timer 的回调函数
    // 注意:现在这个回调函数是在 MMI task环境下执行的
    if (pTimerExpiry)
    {
        pTimerExpiry((void *)arg);
    }
    // 这个的作用是 把 mmi task 外部循环队列的消息 放入到内部消息循环
    // 具体在下次 分析 MMI task 时解释
    // 这里真么做主要是 让消息及时得到处理
    mmi_frm_fetch_msg_from_extQ_to_circularQ();

}
这样整个MMI timer梳理通了,从初始化timer到设置timer到最后的触发timer。还有几个需要注意的地方,上文说到, MMI timer 有两种,其中alignment timer 在手机休眠时会被挂起。具体函数如下:

void mmi_frm_suspend_timers(U32 type)
{
    event_scheduler *ev = NULL;
    // 判断是哪一种 timer,实际中,只使用 alignment
    switch(type)
    {
    case TIMER_IS_NO_ALIGNMENT:

        ev = event_scheduler1_ptr;
        break;
       
    case TIMER_IS_ALIGNMENT:
        ev = event_scheduler2_ptr;
        break;

    default:
        /* undefined type */
        MMI_ASSERT(0);
        break;
    }
    // 挂起所有 alignment timer
    evshed_suspend_all_events(ev);
}
具体调用改函数的地方在 BacklightTimerHdlr 里面,这个函数具体实现以后分析主要作用就是控制屏不背光灯,当屏幕背光灯关闭时,关闭alignment timer.有挂起,就由恢复,函数 mmi_frm_resume_timers 调用地方在 TurnOnBacklight 等几个函数里,具体就不在分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值