[笔记分享] [RTC] Alarm内核驱动分析
Platform: msm8x26
Overview
在rtc一节的框架图可以看出,Alarm依赖于rtc驱动框架,但它不是一个 rtc 驱动,主要还是实现定时闹钟的功能。
相关源代码在kernel/drivers/rtc/alarm.c和drivers/rtc/alarm_dev.c。
注意alarm接口是android系统专门添加的,linux并没有提供这样的功能。
其中alarm.c文件实现的是所有alarm设备的通用性操作,它创建了一个设备class,而alarm_dev.c则创建具体的alarm设备,注册到该设备class中。alarm.c还实现了与interface.c 的接口,即建立了与具体rtc驱动和rtc芯片的联系。alarm_dev.c在alarm.c基础包装了一层,主要是实现了标准的miscdevice接口,提供给应用层调用。
alarm.c实现的是机制和框架,alarm_dev.c则是实现符合这个框架的设备驱动,alarm_dev.c相当于在底层硬件rtc闹钟功能的基础上虚拟了多个软件闹钟。 有点类似于rtc的用法。
Struct
对于alarm,关键的数据结构有如下几个:
Alarm :
这个结构体代表alarm设备,所有的 alarm 设备按照它们过期时间的先后被组织成一个红黑树,alarm.node即红黑树的节点,alarm设备通过这个变量插入红黑树。alarm.type 是类型,android中一共定义了如下 5 种类型,在现在的系统中每种类型只有一个设备,也就是说同一时间一种类型只有一个alarm有效,这也可以从代码中看出,稍后再作讲解。
第一和第二种区别是第一种可以在android suspend的时候触发alarm,但第二种类型没有这个功能。同理,第三和第四也是如此。
另外,ANDROID_ALARM_RTC和ANDROID_ALARM_ELAPSED_REALTIME区别在于前者设置alarm时是基于UTC的,而后者是基于当前流逝的时间来设置alarm的,kernel会判断设置的类型,如果是ANDROID_ALARM_ELAPSED_REALTIME,它会加上当前时间,这样就和ANDROID_ALARM_RTC一样了。
alarm_queue:
这个结构体用于将前面的 struct alarm 表示的设备组织成红黑树。它是基于内核定时器来实现 alarm 的到期闹铃的。
alarm dev
下面我们来分析代码,先看alarm-dev.c,这部分代码比较少,用于和应用层交互。
先注册alarm_device设备,然后对各种类型的alarm初始化,其中alarm触发时的函数为alarm_trigger。关于alarm_wake_lock没有研究过。
这里对一些全局变量如alarm_enable、alarm_pending等作一下解释,看其各有什么意义。如果有不明白可以分析下在代码中的使用状况就能体会到所表达的意思了。
标志位,alarm设备是否被打开。
static int alarm_opened;
标志位,alarm设备是否就绪。所谓就绪是指该alarm设备的闹铃时间到达,但原本等待在该alarm设备上的进程还未唤醒,一旦唤醒,该标志清零。
static uint32_t alarm_pending;
标志位,表示alarm设备是否enabled,表示该设备设置了闹铃时间(并且闹铃时间还未到),一旦闹铃时间到了,该标志清零。
static uint32_t alarm_enabled;
标志位,表示原先等待该alarm的进程被唤醒了(它们等待的alarm到时了)。
static uint32_t wait_pending;
我们接着继续看open函数:
只有一条初始化private_data语句,将会在ioctl中用到。
Alarm_ioctl会对上层每个打开alarm device又调用ioctl来作判断。从代码看出,如果是get time那么任意进程都可以读取。Alarm_opened的作用就是为了保护当前只有一个进程可以操作alarm的set。再看具体ioctl调用。这部分是实现alarm的核心部分。
上面用来取消特定类型的alarm,那么所以和它相关未被处理的alarm也会被取消。
对于alarm set,核心函数就是alarm_start_range,如果一步步往下分析,可得知它是使用hrtimer来trigger的。Hrtimer和一般timer的区别就在于它精度比较高。
当设置好alarm之后,我们就开始等待,wait_event_interruptible使得进程会被阻塞,直到alarm触发位置,前面我们提到过,alarm_trigger会调用wake_up(&alarm_wait_queue);来wakeup等待的进程。
Alarm_pending会返回当前被触发的alarm type,然后作为返回值返回给用户进程,alarm_pending的设置可以从alarm_trigger的alarm_pending |= alarm_type_mask;这条语句反应出来。另外,当重新设置rtc的时候,也会改变alarm_pending的值,如下:
因此,当用户有进程等待alarm trigger而我们又重新设置了time的时候,alarm将会被触发,但并不是我们要wait的类型。
另外,目前android都是通过这个接口来设置时间的,可能是为了保护同一时间只有一个进程在set time吧。
再来看下get time接口。
ANDROID_ALARM_RTC/ ANDROID_ALARM_RTC_WAKEUP用于获取当前基于UTC时间,也就是经常说的real time或wall time.
ANDROID_ALARM_ELAPSED_REALTIME/ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP用于获取从现在启动到现在的流逝时间。
ANDROID_ALARM_SYSTEMTIME其实也是获取流逝时间,不过休眠的时间它是不会计入的
alarm
前面分析了alarm-dev.c,它主要是用来和应用程序来交互。具体实现其实是由alarm.c来完成的。下面我们看下flow。
这里主要初始化alarm里的各个hrtimer,类似于一般timer,它是用来定时的,只是hrtimer精度较高,alarm就是基于hrtimer来实现的。
另外,还初始化了hrtimer的触发函数为alarm_timer_triggered,这个函数很重要,调用alarm-dev就是靠它来实现的。
然后,alarm_late_init也会被调用到:
它主要是获取当前流逝的时间,然后将流逝时间和当前差值给ANDROID_ALARM_ELAPSED_REALTIME类型的alarm。Delta在获取流逝时间、流逝型闹钟设置alarm、重新设置rtc时会被用到。
前面有说到过在设置alarm的时候,alarm_ioctl中会call到alarm_start_range -> alarm_enqueue_locked:
这几条语句可以看出, 当同一种type的alarm再设置一次之后,第一次设置的alarm将会被删掉,因此每种type的alarm就只能保存一个了!!! 让人感到不解的是,这样为何还要rb tree?
接着就会调用update_timer_locked开启alarm。
base->timer._expires = ktime_add(base->delta, alarm->expires);
base->timer._softexpires = ktime_add(base->delta, alarm->softexpires);
这里加上delta是因为如果是elapse type的alarm设置下来的时候只是一个相对值,因此我们要加上delta,也就是在set之前走过了多少时间,而这个值在alarm_late_init被初始化了。得到的_softexpires会被用作hrtimer trigger的expired time, 也就是说会被加入到timer tree中,当timer trigger时会比较这个值,如果超时,就触发。这个比较可参考aaa@qq.com, 这里不对hrtimer进行说明了。
由于初始化制定了callback funciton为alarm_timer_triggered,在trigger的时候就会调用到:
这里在在查找哪个alarm先被trigger参考了hrtimer trigger的写法, while中先找到第一个被trigger的alarm type,然后call alarm->function(),也就是aaa@qq.com 。
好了,alarm的设置及trigger flow就如上面那样子。另外一点要注意的是set rtc时候对alarm的影响:
当重设rtc的时候,所以的alarm都会被取消。Elapse type alarm的delta会根据set rtc time来作调整,其实time往前或往后set都没关系,在alarm_get_elapsed_realtime的时候会重新减掉这个offset,不管正或者负,因此user space get到的elapse time永远为正的,不知道MMI他们说的会得到一个负数是什么原因造成的?
我们还看到call到了do_settimeofday和rtc_set_time,因此系统real time和硬件的rtc time都会被改变。
现在还剩suspend/resume。
之前我们说过ANDROID_ALARM_RTC_WAKEUP/ANDROID_ALARM_RTC的区别是前者可以在android sleep的时候唤醒系统。Suspend有开启ANDROID_ALARM_RTC_WAKEUP和ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,如果设置的话。
对于这部分,我也不是很懂为什么WAKEUP类型的就能唤醒,不过我看到的Log是ANDROID_ALARM_RTC_WAKEUP被trigger后调用了resume,然后ANDROID_ALARM_RTC 类型的就会被trigger。