版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134388342
目录
一、开发环境
linux 4.19
二、RTC框架的基本用法
在看正点原子的linux驱动开发教程的时候,因为正点说大部分厂商都把rtc驱动写好了,不用自己写,所以讲的比较粗略,大致说了下RTC框架的基本用法以及如果有必要自己写RTC驱动时,使用者要做的两件事:
2.1 编写RTC设备的最底层操作函数集合
编写RTC设备的最底层操作函数集合,也就是rtc_class_ops
结构体
在这里先看一下struct rtc_class_ops
的定义:
struct rtc_class_ops {
int (*ioctl)(struct device *, unsigned int, unsigned long);
int (*read_time)(struct device *, struct rtc_time *);
int (*set_time)(struct device *, struct rtc_time *);
int (*read_alarm)(struct device *, struct rtc_wkalrm *);
int (*set_alarm)(struct device *, struct rtc_wkalrm *);
int (*proc)(struct device *, struct seq_file *);
int (*set_mmss64)(struct device *, time64_t secs);
int (*set_mmss)(struct device *, unsigned long secs);
int (*read_callback)(struct device *, int data);
int (*alarm_irq_enable)(struct device *, unsigned int enabled);
int (*read_offset)(struct device *, long *offset);
int (*set_offset)(struct device *, long offset);
};
我们要做的就是创建一个rtc_class_ops
变量,并将里面的函数指针指向我们驱动程序中自己编写的函数,如下:
...
int my_read_time(struct device *, struct rtc_time *)
{
....
}
...
struct rtc_class_ops my_ops = {
.read_time = my_read_time,
....
}
2.2 使用rtc_device_register()
函数来向系统注册RTC设备
但是我并不清楚rtc_device_register()
函数做了什么事情,为什么向系统注册了设备之后,就可以控制设备了呢?在我另一篇纹章解析hwclock
命令的时候说过,hwclock
这个命令其实是打开了"/dev/rtc" ,"/dev/rtc0", "/dev/misc/rtc"
这三个设备的其中一个,然后用户层使用ioctl()
函数来下达命令,驱动程序根据传入的命令控制对应的寄存器实现相应的功能。所以RTC设备本质上是一个字符设备。字符设备肯定有字符设备操作集,但是我很好奇这个字符设备操作集在哪儿绑定的呢,所以就看了下rtc_device_register()
这个函数
三、rtc_device_register()
函数解析
先放上这个函数的源码,此函数位于drivers/rtc/class.c中。传入的四个参数及类型分别为:
- const char *name, 设备名字
- struct device *dev, 设备,在后面分析过程中,可以看到这个设备其实是要注册的RTC设备的父设备
- const struct rtc_class_ops *ops, 即RTC 底层驱动函数集。
- struct module *owner, 驱动模块拥有者,一般为THIS_MODULE
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
{
struct rtc_device *rtc;
struct rtc_wkalrm alrm;
int id, err;
id = rtc_device_get_id(dev);
if (id < 0) {
err = id;
goto exit;
}
rtc = rtc_allocate_device();
if (!rtc) {
err = -ENOMEM;
goto exit_ida;
}
rtc->id = id;
rtc->ops = ops;
rtc->owner = owner;
rtc->dev.parent = dev;
dev_set_name(&rtc->dev, "rtc%d", id);
rtc_device_get_offset(rtc);
/* Check to see if there is an ALARM already set in hw */
err = __rtc_read_alarm(rtc, &alrm);
if (!err && !rtc_valid_tm(&alrm.time))
rtc_initialize_alarm(rtc, &alrm);
rtc_dev_prepare(rtc); /* 这里执行cdev_init */
err = cdev_device_add(&rtc->char_dev, &rtc->dev);
if (err) {
dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n",
name, MAJOR(rtc->dev.devt), rtc->id);
/* This will free both memory and the ID */
put_device(&rtc->dev);
goto exit;
} else {
dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", name,
MAJOR(rtc->dev.devt), rtc->id);
}
rtc_proc_add_device(rtc);
dev_info(dev, "rtc core: registered %s as %s\n",
name, dev_name(&rtc->dev));
return rtc;
exit_ida:
ida_simple_remove(&rtc_ida, id);
exit:
dev_err(dev, "rtc core: unable to register %s, err = %d\n",
name, err);
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(rtc_device_register);
该函数创建了一个struct rtc_device
类型的指针,并且该函数最终返回的也是这个指针,这个结构体定义在include/linux/rtc.h中,其中成员变量众多,我只把跟rtc_device_register()
函数有关的一些变量贴上来::
struct rtc_device {
struct device dev;
struct module *owner;
int id;
const struct rtc_class_ops *ops;
struct mutex ops_lock;
struct cdev char_dev;
...
spinlock_t irq_lock;
...
};
根据该函数的源码中的这几行代码:
rtc->id = id;
rtc->ops = ops;
rtc->owner = owner;
rtc->dev.parent = dev;
通过rtc->ops = ops
可以看出把rtc的ops
绑定到了我们传入的ops
上,同时把rtc下的struct device
类型的成员变量dev
的父设备绑定到了我们传入的dev
上。这里知道了传入的dev其实只是为了给要注册的rtc设备绑定一个父设备,所以按道理随便传入一个无关紧要的父设备就可以了,但是后续我在编写BL5372这个rtc芯片的驱动程序的时候这里出了问题(BUG记录在我的另外一篇文章)。但是现在还是没有看懂用户层在打开字符设备后是如何调用到struct rtc_class_ops *ops
上的。
继续往下看,下面这段代码看命名貌似是与RTC的闹钟有关,暂时先不考虑(后续会回头再看的)
err = __rtc_read_alarm(rtc, &alrm);
if (!err && !rtc_valid_tm(&alrm.time))
rtc_initialize_alarm(rtc, &alrm);
接着重点看一下rtc_dev_prepare()
这个函数,这个函数是理解RTC框架的关键,这个函数定义在drivers/rtc/rtc-dev.c中,其源码如下:
void rtc_dev_prepare(struct rtc_device *rtc)
{
if (!rtc_devt)
return;
if (rtc->id >= RTC_DEV_MAX) {
dev_dbg(&rtc->dev, "too many RTC devices\n");
return;
}
rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
INIT_WORK(&rtc->uie_task, rtc_uie_task);
timer_setup(&rtc->uie_timer, rtc_uie_timer, 0);
#endif
cdev_init(&rtc->char_dev, &rtc_dev_fops);
rtc->char_dev.owner = rtc->owner;
}
从这个函数中可以看到注册字符设备很多熟悉的地方,比如MKDEV
创建设备号,cdev_init()
等等。从cdev_init()
可以看出,rtc->char_dev
这个字符设备的操作集其实是rtc_dev_fops
,这个操作集就在同一个文件下(rtc-dev.c):
static const struct file_operations rtc_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = rtc_dev_read,
.poll = rtc_dev_poll,
.unlocked_ioctl = rtc_dev_ioctl,
.open = rtc_dev_open,
.release = rtc_dev_release,
.fasync = rtc_dev_fasync,
};
到这里算是知道hwclock(以及其他用户层程序)是在哪一步通过系统调用来对底层驱动进行操作了,但是到这里还不知道与我们编写的编写RTC设备的最底层操作函数集合rtc_class_ops
有什么关系。在使用hwclock中,使用ioctl()
函数来传递指令,那我们就来看一看字符操作集中的.unlocked_ioctl = rtc_dev_ioctl
,rtc_dev_ioctl
这个函数也在同一个文件,其源码较长,有兴趣的可以自己去看一下,这里为了有针对性的解释如何调用我们自己写的RTC底层操作函数集,简单的摘录一部分如下:
static long rtc_dev_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
int err = 0;
struct rtc_device *rtc = file->private_data;
const struct rtc_class_ops *ops = rtc->ops;
struct rtc_time tm;
struct rtc_wkalrm alarm;
void __user *uarg = (void __user *) arg;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
/* check that the calling task has appropriate permissions
* for certain ioctls. doing this check here is useful
* to avoid duplicate code in each driver.
*/
switch (cmd) {
case RTC_EPOCH_SET:
case RTC_SET_TIME:
if (!capable(CAP_SYS_TIME))
err = -EACCES;
break;
...
}
if (err)
goto done;
switch (cmd) {
case RTC_ALM_READ:
mutex_unlock(&rtc->ops_lock);
err = rtc_read_alarm(rtc, &alarm);
if (err < 0)
return err;
if (copy_to_user(uarg, &alarm.time, sizeof(tm)))
err = -EFAULT;
return err;
case RTC_ALM_SET:
...
case RTC_RD_TIME:
mutex_unlock(&rtc->ops_lock);
err = rtc_read_time(rtc, &tm);
if (err < 0)
return err;
if (copy_to_user(uarg, &tm, sizeof(tm)))
err = -EFAULT;
return err;
case RTC_SET_TIME:
mutex_unlock(&rtc->ops_lock);
if (copy_from_user(&tm, uarg, sizeof(tm)))
return -EFAULT;
return rtc_set_time(rtc, &tm);
...
default:
/* Finally try the driver's ioctl interface */
if (ops->ioctl) {
err = ops->ioctl(rtc->dev.parent, cmd, arg);
if (err == -ENOIOCTLCMD)
err = -ENOTTY;
} else
err = -ENOTTY;
break;
}
done:
mutex_unlock(&rtc->ops_lock);
return err;
}
以RTC_RD_TIME为例:
case RTC_RD_TIME:
mutex_unlock(&rtc->ops_lock);
err = rtc_read_time(rtc, &tm);
if (err < 0)
return err;
if (copy_to_user(uarg, &tm, sizeof(tm)))
err = -EFAULT;
return err;
如果用户层调用ioctl传入了RTC_RD_TIME,会执行rtc_read_time()
函数,该函数在drivers/rtc/interface.c下,源码如下:
int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{
int err;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
err = __rtc_read_time(rtc, tm);
mutex_unlock(&rtc->ops_lock);
trace_rtc_read_time(rtc_tm_to_time64(tm), err);
return err;
}
该函数中调用了__rtc_read_time()函数,该函数位于同一文件下,其源码为:
static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{
int err;
if (!rtc->ops)
err = -ENODEV;
else if (!rtc->ops->read_time)
err = -EINVAL;
else {
memset(tm, 0, sizeof(struct rtc_time));
err = rtc->ops->read_time(rtc->dev.parent, tm);
if (err < 0) {
dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",
err);
return err;
}
rtc_add_offset(rtc, tm);
err = rtc_valid_tm(tm);
if (err < 0)
dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n");
}
return err;
}
可见,如果你在你自己的驱动程序中令rtc->ops->read_time
指向了一个函数后(!rtc->ops->read_time
,即该函数指针不为NULL)
,则会最终调用rtc->ops->read_time
指向的函数,也就是自己驱动程序中编写的readtime函数。而你需要做的就是在自己的readtime函数中对硬件rtc的寄存器进行操作即可。
其余函数如设置硬件rtc时间也大致如此,可以通过用户层使用ioctl()
函数传入来最终调用我们绑定的RTC设备的最底层操作函数。这就是rtc_device_register()
这个函数的部分作用。提一嘴的是,我看了看drivers/rtc/下的一些其他已经编写好的rtc芯片的驱动程序时,它们更常用的是devm_rtc_device_register()
这个函数。该函数源码如下:
struct rtc_device *devm_rtc_device_register(struct device *dev,
const char *name,
const struct rtc_class_ops *ops,
struct module *owner)
{
struct rtc_device **ptr, *rtc;
ptr = devres_alloc(devm_rtc_device_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
rtc = rtc_device_register(name, dev, ops, owner);
if (!IS_ERR(rtc)) {
*ptr = rtc;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return rtc;
}
可见该函数最终也是调用了rtc_device_register()
,而devres_alloc()
、devres_add()
等函数的作用这篇博客中做了详细的解释.。
但是有一些对于硬件RTC的功能的实现在Linux的RTC框架下更加复杂,比如设置闹钟和读取闹钟,所以最终我放弃了使用RTC框架,转而直接将这个rtc注册成了MISC设备。原因在下一节中进行了说明。
四、新的问题
其实看到上面那步已经可以实现读硬件RTC时间和写硬件RTC时间功能了,如果不需要其他功能的话就可以使用了。如果需要其他功能,我认为使用RTC框架不如直接注册成MISC设备或者普通的字符设备来读写相应的寄存器来的轻松,因为并不是所有的RTC硬件都能适配linux下的RTC框架,下面的笔记可以不用看了。
4.1 设置闹钟
我选用的RTC硬件需要启用闹钟功能,以便在CPU休眠时定时闹钟到时候唤醒CPU。所以继续看一下RTC框架是如何设置RTC硬件闹钟的,没想到学到的东西是越来越多。我们先看一下当ioctl()
传入RTC_ALM_SET
时执行的代码:
switch (cmd) {
...
case RTC_ALM_SET:
mutex_unlock(&rtc->ops_lock);
if (copy_from_user(&alarm.time, uarg, sizeof(tm)))
return -EFAULT;
alarm.enabled = 0;
alarm.pending = 0;
alarm.time.tm_wday = -1;
alarm.time.tm_yday = -1;
alarm.time.tm_isdst = -1;
{
time64_t now, then;
err = rtc_read_time(rtc, &tm);
if (err < 0)
return err;
now = rtc_tm_to_time64(&tm);
alarm.time.tm_mday = tm.tm_mday;
alarm.time.tm_mon = tm.tm_mon;
alarm.time.tm_year = tm.tm_year;
err = rtc_valid_tm(&alarm.time);
if (err < 0)
return err;
then = rtc_tm_to_time64(&alarm.time);
/* alarm may need to wrap into tomorrow */
if (then < now) {
rtc_time64_to_tm(now + 24 * 60 * 60, &tm);
alarm.time.tm_mday = tm.tm_mday;
alarm.time.tm_mon = tm.tm_mon;
alarm.time.tm_year = tm.tm_year;
}
}
return rtc_set_alarm(rtc, &alarm);
}
这里引出了一个新的结构体struct rtc_wkalrm
,即代码中alarm
的参数类型:
struct rtc_wkalrm {
unsigned char enabled; /* 0 = alarm disabled, 1 = alarm enabled */
unsigned char pending; /* 0 = alarm not pending, 1 = alarm pending */
struct rtc_time time; /* time the alarm is set to */
};
- enabled: 是否启用闹钟
- pending:闹钟是否处于活动状态
- time:要设置的闹钟时间
在进入这个case
之后,根据传入的参数uarg
对alram.time
进行了初始化。并对传入时间进行了规范性检查(检查传入的时间是否符合linux的时间设置要求)。如果检查无误,则调用rtc_set_alarm()
函数,该函数位于drivers/rtc/interface.c中,其源码如下:
int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
int err;
if (!rtc->ops)
return -ENODEV;
else if (!rtc->ops->set_alarm)
return -EINVAL;
err = rtc_valid_tm(&alarm->time); /* 这个并不会检查weekday,所以可以绕过这个 */
if (err != 0)
return err;
err = rtc_valid_range(rtc, &alarm->time);
if (err)
return err;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
if (rtc->aie_timer.enabled) /* rtc->aie_timer.enabled在rtc_init_timer的时候设为0了,但是会在rtc_initialize_alarm中再次开启 */
rtc_timer_remove(rtc, &rtc->aie_timer);
rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time);
rtc->aie_timer.period = 0;
if (alarm->enabled)
err = rtc_timer_enqueue(rtc, &rtc->aie_timer); /* 这里面会把aie_timer.enabled置1 */
mutex_unlock(&rtc->ops_lock);
return err;
}
这里涉及到两个重要的函数:rtc_timer_remove()
和rtc_timer_enqueue()
,这两个函数又牵扯出了linux下的另外一个子系统。但是到此为止没有回头路了,索性就继续看下了。首先这里需要知道struct rtc_device
(即我们使用rtc_device_register()
函数注册返回的rtc设备的类型)里面到底有哪些成员变量,结构体定义如下:
struct rtc_device {
struct device dev;
struct module *owner;
int id;
const struct rtc_class_ops *ops;
struct mutex ops_lock;
struct cdev char_dev;
unsigned long flags;
unsigned long irq_data;
spinlock_t irq_lock;
wait_queue_head_t irq_queue;
struct fasync_struct *async_queue;
int irq_freq;
int max_user_freq;
struct timerqueue_head timerqueue; /* 维护了一个hrtimer红黑树,给闹钟用的*/
struct rtc_timer aie_timer;
struct rtc_timer uie_rtctimer;
struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */
int pie_enabled;
struct work_struct irqwork;
/* Some hardware can't support UIE mode */
int uie_unsupported;
/* Number of nsec it takes to set the RTC clock. This influences when
* the set ops are called. An offset:
* - of 0.5 s will call RTC set for wall clock time 10.0 s at 9.5 s
* - of 1.5 s will call RTC set for wall clock time 10.0 s at 8.5 s
* - of -0.5 s will call RTC set for wall clock time 10.0 s at 10.5 s
*/
long set_offset_nsec;
bool registered;
struct nvmem_device *nvmem;
/* Old ABI support */
bool nvram_old_abi;
struct bin_attribute *nvram;
time64_t range_min;
timeu64_t range_max;
time64_t start_secs;
time64_t offset_secs;
bool set_start_time;
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
struct work_struct uie_task;
struct timer_list uie_timer;
/* Those fields are protected by rtc->irq_lock */
unsigned int oldsecs;
unsigned int uie_irq_active:1;
unsigned int stop_uie_polling:1;
unsigned int uie_task_active:1;
unsigned int uie_timer_active:1;
#endif
};
这篇文章对该结构体的部分变量进行了大致的解释,这里我转载过来:
- dev:设备驱动模型中的device,可以将rtc_device看做其子类;
- owner:模块的拥有者;
- id:rtc设备编号;
- ops:rtc设备操作函数集;
- ops_lock:互斥锁,解决对rtc设备并发访问问题;
- char_dev:rtc字符设备;
- flags:rtc的状态标志,比如RTC_DEV_BUSY;
- irq_data:irq中断数据;
- irq_lock:自旋锁,互斥访问irq中断数据;
- irq_queue:数据查询时用到的rtc等待队列头;头节点为struct
wait_queue_head_t类型,等待队列中的元素为struct wait_queue_t类型; - async_queue:异步队列;
- irq_freq:rtc中断频率,实际上就是时钟节拍中断的频率;
- max_user_freq:rtc时钟节拍中断最大频率;
- irqwork:该工作会在rtc中断函数中执行,工作函数会被设置为rtc_timer_do_work;
- timerqueue:定时器队列,使用最小堆算法实现,根节点保存的是最先触发的定时器;struct timerqueue_head类型;
- aie_timer:闹钟定时器,保存闹钟时间,闹钟时间到时执行定时器超时处理函数rtc_aie_update_irq;struct
rtc_timer类型; - uie_rtctimer:更新定时器,定时器超时处理函数被设置为rtc_uie_update_irq;struct
rtc_timer类型; - pie_timer:周期高精度定时器,定时器超时处理函数设置为rtc_pie_update_irq;struct hrtimer类型;
- pie_enabled:周期中断使能标志;
- range_int:最小秒数;用来描述rtc设备支持的最小时间;
- range_max:最大秒数;用来描述rtc设备支持的最大时间;
- start_secs:开始秒数;rtc设备设置的起始时间;
- offset_secs:时间偏移秒数;RTC读取到的时间+偏移秒数就是真实的时间;
- uie_task:该工作会在uie_timer定时器超时函数rtc_uie_timer中执行,工作函数会被设置为rtc_uie_task;
- uie_timer:更新定时器,定时器超时函数会被设置为rtc_uie_timer;struct timer_list类型;
其中rtc_set_alarm()
中出现的aie_timer
是个struct rtc_timer
类型,其定义如下:
struct rtc_timer {
struct timerqueue_node node; /* 而这个timerqueue_node结构体里面的expires就是硬超时时间 */
ktime_t period; /* 如果高精度定时器都是通用的话,这个就是软超时时间 */
void (*func)(void *private_data); /* 定时器到时的时候调用的回调函数 */
void *private_data;
int enabled; /* 是否使能定时器 */
};
这里就涉及到linux下的另外一个子系统:时间子系统的一些机制,我看了看CSDN和知乎上的一些相关文章,认为这几篇文章写的比较好,如果想研究的话值得一看:
但是想要深入研究我觉得还是应该挑一本讲解Linux内核的书看一看,更加系统的认识Linux内核。言归正传,从strcut rtc_timer
下的变量struct timerqueue_node node
以及struct rtc_device
下的变量struct timerqueue_head timerqueue
来看,其实rtc设备结构体下维护了一个高精度定时器队列,该队列使用红黑树来实现。继续看rtc_set_alarm()
的代码的这一段:
int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
...
if (rtc->aie_timer.enabled) /* rtc->aie_timer.enabled在rtc_init_timer的时候设为0了,但是会在rtc_initialize_alarm中再次开启 */
rtc_timer_remove(rtc, &rtc->aie_timer);
rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time);
rtc->aie_timer.period = 0;
if (alarm->enabled)
err = rtc_timer_enqueue(rtc, &rtc->aie_timer); /* 这里面会把aie_timer.enabled置1 */
...
}
第一个if判断,如果rtc->aie_timer.enabled
使能了,则会调用rtc_timer_remove()
函数,从定时器队列中删除上一个闹钟,也就是aie_timer
表示的那个闹钟。然后修改aie_timer
的expires
和period
的值(定时器中的概念,想了解的可以看看我列出来的那三篇文章)。后面第二个if判断,如果传入的alarm->enabled
使能了,则调用rtc_timer_enqueue()
函数将修改后的aie_timer
重新加入到定时器队列中。
到这里好像发现,对alarm的操作好像跟read_time和set_time不一样?甚至都跟我们自己定义的RTC底层设备操作集中的设置闹钟的函数没什么关联?别急,看一下rtc_timer_remove()
和rtc_timer_enqueue()
函数,这俩函数位于drivers/rtc/interface.c下,先来看一下先看rtc_timer_remove()
函数:
static void rtc_timer_remove(struct rtc_device *rtc, struct rtc_timer *timer)
{
struct timerqueue_node *next = timerqueue_getnext(&rtc->timerqueue);
timerqueue_del(&rtc->timerqueue, &timer->node);
trace_rtc_timer_dequeue(timer);
timer->enabled = 0;
if (next == &timer->node) { /* 设置下一个合适的闹钟 */
struct rtc_wkalrm alarm;
int err;
next = timerqueue_getnext(&rtc->timerqueue);
if (!next) {
rtc_alarm_disable(rtc);
return;
}
alarm.time = rtc_ktime_to_tm(next->expires);
alarm.enabled = 1;
err = __rtc_set_alarm(rtc, &alarm);
if (err == -ETIME) {
pm_stay_awake(rtc->dev.parent);
schedule_work(&rtc->irqwork);
}
}
}
其中timerqueue_getnext()
这个函数的作用是获取定时器队列最快要到时的node,而且因为高精度度定时器的定时器队列使用了红黑树,所以根据红黑树的特性可知,就是在获取红黑树的最左下方的节点,并保存到函数中的next变量。这个函数的具体实现我并不没有去深入了解(大致看了一下,rtc->timerqueue
的数据类型为timerqueue_head
结构体,该结构体下有一个叫rb_root_cached
结构体,这个结构体中有一个成员变量叫rb_leftmost
,看名字非常直白,最左边的,所以这个变量应该是实时维护的)。
接下来执行了timerqueue_del()
将当前的aie_timer
从定时器队列中删除了(想想也很合理,你要设置一个新的RTC闹钟,要把上一个设置的给删掉)。如果删除的节点正好是红黑树最左下角的节点,会同时更新红黑树,并找到新的rb_leftmost
。
如果你删掉的节点就是上一个next所指的节点的话(if判断的事情),就需要重新设置闹钟事件,再次调用timerqueue_getnext()
找到最快要到期的定时器,并调用__rtc_set_alarm()
函数,设置硬件rtc的闹钟为新的next所记录的时间。而这个函数,最终就调用到了我们在RTC底层函数操作集中绑定的我们自己写的set_alarm
函数来读写硬件rtc的闹钟寄存器,这个函数同样位于interface.c中,代码如下所示:
static int __rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
...
if (!rtc->ops)
err = -ENODEV;
else if (!rtc->ops->set_alarm)
err = -EINVAL;
else
err = rtc->ops->set_alarm(rtc->dev.parent, alarm);
...
return err;
}
可以看到,最终调用了rtc->ops->set_alarm()
。接下来看一下rtc_timer_enqueue()
函数:
static int rtc_timer_enqueue(struct rtc_device *rtc, struct rtc_timer *timer)
{
struct timerqueue_node *next = timerqueue_getnext(&rtc->timerqueue);
struct rtc_time tm;
ktime_t now;
timer->enabled = 1;
__rtc_read_time(rtc, &tm);
now = rtc_tm_to_ktime(tm);
/* Skip over expired timers */
while (next) {
if (next->expires >= now) /* 找到一个比现在的expires大的定时器,也就是还没过期的定时器 */
break;
next = timerqueue_iterate_next(next);
}
timerqueue_add(&rtc->timerqueue, &timer->node);
trace_rtc_timer_enqueue(timer);
if (!next || ktime_before(timer->node.expires, next->expires)) { /* 如果next为空,或者要设置的闹钟到期时间比next的到期时间早(此时的next表示的是一个比当前时间晚的定时器) */
struct rtc_wkalrm alarm;
int err;
alarm.time = rtc_ktime_to_tm(timer->node.expires);
alarm.enabled = 1;
err = __rtc_set_alarm(rtc, &alarm);
if (err == -ETIME) {
pm_stay_awake(rtc->dev.parent);
schedule_work(&rtc->irqwork);
} else if (err) {
timerqueue_del(&rtc->timerqueue, &timer->node);
trace_rtc_timer_dequeue(timer);
timer->enabled = 0;
return err;
}
}
return 0;
}
其实就是做了与remove函数相反的事,向定时器队列中添加了一个新的节点。但是这个if
判断就很怪,只有当next
为空(也就是说没找到晚于当前时间的定时器,意味着定时器队列中的定时器全部过期了),或者要设置的闹钟到期时间比next
的到期时间早(此时的next
表示的是比当前时间晚的所有定时器中最早的一个),才会调用__rtc_set_alarm()
来最终设置硬件rtc的闹钟寄存器。
那意思就是说,我虽然在rtc系统下的定时器队列中维护了你要设置的闹钟,但是我不一定把你设到rtc硬件寄存器中去(当要设置的闹钟晚于现在的next时就会出现这种情况)?这里我还没有想明白,后续花时间再梳理一下。到这里如何通过ioctl()
来最终调用到我们自己写的set_alram
就讲完了,而想调用到我们自己写的read_alram,则有另外一种路子。
4.2 读取闹钟
读取闹钟又做了什么事情呢?我们看一下当ioctl()
传入RTC_ALM_READ
后执行的代码:
switch (cmd) {
case RTC_ALM_READ:
mutex_unlock(&rtc->ops_lock);
err = rtc_read_alarm(rtc, &alarm);
if (err < 0)
return err;
if (copy_to_user(uarg, &alarm.time, sizeof(tm)))
err = -EFAULT;
return err;
...
}
接下来看一下rtc_read_alarm()
函数,该函数同样位于interface.c中:
int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
int err;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
if (rtc->ops == NULL)
err = -ENODEV;
else if (!rtc->ops->read_alarm)
err = -EINVAL;
else {
memset(alarm, 0, sizeof(struct rtc_wkalrm));
alarm->enabled = rtc->aie_timer.enabled;
alarm->time = rtc_ktime_to_tm(rtc->aie_timer.node.expires);
}
mutex_unlock(&rtc->ops_lock);
trace_rtc_read_alarm(rtc_tm_to_time64(&alarm->time), err);
return err;
}
直接将rtc->aie_timer.node.expires
(在rtc_set_alarm
时对expires
变量做了设置,使其表示上次设置的闹钟到时时间)转化了一下返回了…这好像根本没用到我们自己写的read_alam啊。于是在整个rtc文件夹下搜索了一下,总算找到了在哪里调用了我们自己写的read_alarm,其实他就在我们注册RTC设备时调用的rtc_device_register()
函数中:
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
{
...
rtc = rtc_allocate_device(); /* 这个函数做了很多东西,有必要看一下,基本上rtc下的所有变量都在这里进行了定义 */
...
/* Check to see if there is an ALARM already set in hw */
err = __rtc_read_alarm(rtc, &alrm); /* 这里关键,最终联系到咱们自己的read_alarm */
if (!err && !rtc_valid_tm(&alrm.time))
rtc_initialize_alarm(rtc, &alrm);
rtc_dev_prepare(rtc);
...
}
__rtc_read_alarm()
前面这俩横线就像那样,因为前面的什么__rtc_read_time
之类的带横线的最后都调用了我们自己写的函数。那就看一下__rtc_read_alarm
,这个函数比较长,但是大部分代码都是在做一些检验时间合理性,补充缺失值的工作,所以我只列出部分代码,完整源码同样位于interface.中:
int __rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
...
/* Get the "before" timestamp */
err = rtc_read_time(rtc, &before);
if (err < 0)
return err;
do {
if (!first_time)
memcpy(&before, &now, sizeof(struct rtc_time));
first_time = 0;
/* get the RTC alarm values, which may be incomplete */
err = rtc_read_alarm_internal(rtc, alarm); /* 这里面最终调用了我们自己的read_alarm */
if (err)
return err;
/* full-function RTCs won't have such missing fields */
if (rtc_valid_tm(&alarm->time) == 0) {
rtc_add_offset(rtc, &alarm->time);
return 0;
}
/* get the "after" timestamp, to detect wrapped fields */
err = rtc_read_time(rtc, &now); /* 双重检验,看是否执行代码的时候时间发生了跳变,如果发生了跳变则重新进入循环 */
if (err < 0)
return err;
/* note that tm_sec is a "don't care" value here: */
} while ( before.tm_min != now.tm_min
|| before.tm_hour != now.tm_hour
|| before.tm_mon != now.tm_mon
|| before.tm_year != now.tm_year);
...
...
}
其中的重点函数是rtc_read_alarm_internal()
,其代码如下:
static int rtc_read_alarm_internal(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
int err;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
if (rtc->ops == NULL)
err = -ENODEV;
else if (!rtc->ops->read_alarm)
err = -EINVAL;
else {
alarm->enabled = 0;
alarm->pending = 0;
alarm->time.tm_sec = -1;
alarm->time.tm_min = -1;
alarm->time.tm_hour = -1;
alarm->time.tm_mday = -1;
alarm->time.tm_mon = -1;
alarm->time.tm_year = -1;
alarm->time.tm_wday = -1;
alarm->time.tm_yday = -1;
alarm->time.tm_isdst = -1;
err = rtc->ops->read_alarm(rtc->dev.parent, alarm);
}
mutex_unlock(&rtc->ops_lock);
trace_rtc_read_alarm(rtc_tm_to_time64(&alarm->time), err);
return err;
}
可以看到,这里最终调用了rtc->ops->read_alram()
,也就是我们自己写的read_alram函数。
五、rtc_device_register()
其余函数内容解析
在rtc_set_alram()函数中,有两个if判断:
int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
...
if (rtc->aie_timer.enabled)
rtc_timer_remove(rtc, &rtc->aie_timer);
rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time);
rtc->aie_timer.period = 0;
if (alarm->enabled)
err = rtc_timer_enqueue(rtc, &rtc->aie_timer);
...
}
也就是说,当rtc->aie_timer.enabled
和alarm->enabled
都使能时,才会执行下面的rtc_timer_remove()
和rtc_timer_enqueue()
函数,那它们是在哪里使能的呢?再回头看一下rtc_device_register()
函数中的另外两个函数rtc_allocate_device()
和rtc_initialize_alarm()
,先看一下rtc_allocate_device()
函数:
static struct rtc_device *rtc_allocate_device(void)
{
struct rtc_device *rtc;
rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
if (!rtc)
return NULL;
device_initialize(&rtc->dev);
/* Drivers can revise this default after allocating the device. */
rtc->set_offset_nsec = NSEC_PER_SEC / 2;
rtc->irq_freq = 1;
rtc->max_user_freq = 64;
rtc->dev.class = rtc_class;
rtc->dev.groups = rtc_get_dev_attribute_groups();
rtc->dev.release = rtc_device_release;
mutex_init(&rtc->ops_lock);
spin_lock_init(&rtc->irq_lock);
init_waitqueue_head(&rtc->irq_queue);
/* Init timerqueue */
timerqueue_init_head(&rtc->timerqueue);
INIT_WORK(&rtc->irqwork, rtc_timer_do_work);
/* Init aie timer */
rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc); /* 给aie_timer绑定了到期之后的处理函数 */
/* Init uie timer */
rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc);
/* Init pie timer */
hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); /* pie_timer是个周期定时器 */
rtc->pie_timer.function = rtc_pie_update_irq; /* 给周期定时器绑定了处理函数 */
rtc->pie_enabled = 0;
return rtc;
}
这个函数做的工作看上去比较清晰:
- 给rtc结构体分配内存
- 初始化rtc下的dev设备结构体
- 初始化rtc下的定时器队列以及各种定时器,给它们绑定回调函数,也就是定时器到期时要执行的函数
所以说该函数就是做了一个初始化的工作,那么再看一下rtc_initialize_alarm()
函数:
int rtc_initialize_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
int err;
struct rtc_time now;
err = rtc_valid_tm(&alarm->time);
if (err != 0)
return err;
err = rtc_read_time(rtc, &now);
if (err)
return err;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time);
rtc->aie_timer.period = 0;
/* Alarm has to be enabled & in the future for us to enqueue it */
if (alarm->enabled && (rtc_tm_to_ktime(now) < /* 在前面已经使能了alarm->enabled(如果在自己的read_alarm中使能了的话) */
rtc->aie_timer.node.expires)) {
rtc->aie_timer.enabled = 1; /* 这里使能了aie_timer */
timerqueue_add(&rtc->timerqueue, &rtc->aie_timer.node);
trace_rtc_timer_enqueue(&rtc->aie_timer);
}
mutex_unlock(&rtc->ops_lock);
return err;
}
首先要知道,这个函数的调用是在__rtc_read_alarm()
函数之后,所以传入的alarm变量已经在你自己编写的read_alarm(__rtc_read_alarm()
最终会调用我们自己写的read_alarm)中做了修改。所以可以看到,如果你在我们自己写的read_alarm中对alarm->enabled
进行了使能的话,这个函数将会开启rtc->aie_timer.enabled
的使能,设置一次闹钟,并加入到了定时器队列中。至此,两个使能就都开启了,所以我们的read_alarm还是很重要的,要根据你自己的硬件rtc的控制寄存器判断是否开启闹钟,即是否使能alarm->enabled
。
rtc_device_register()
函数中还有一部分是与字符设备相关的代码,这一部分大家应该比较熟悉。但这里我遇到了一个BUG,并在解决过程中学习了linux设备模型的一些知识,BUG的解决过程在我的另一篇博客中(还在编写)。
题外话
我的RTC芯片有不止一个闹钟,可以设置很多个闹钟中断,而且还有很多个乱七八糟的寄存器,还有很多个看乱七八糟的寄存器,其实跟linux下的RTC框架不是完全适配的,我觉得用这个RTC框架实在是太麻烦了,所以最终决定放弃使用RTC框架了。不如直接写寄存器来的方便。而且RTC框架中与时间子系统也有关系,我看的比较快,可能会有理解出错的地方,希望老哥们能指点一下。