linux RTC子系统学习笔记
虽然目前的工作是在linux应用开发上,偏重于业务逻辑,但我还是希望自己能够更加明白操作系统本身的实现,计划之后在周末和闲暇时间抽出一定的时间来继续学习linux设备驱动,同时学习学习多媒体的知识。
今天来学习linux设备驱动中的RTC(Real-Time Clock)子系统,RTC设备就是一些提供系统时间的设备,它们一般自备电池,系统断电后还能计算时间,以便设备开机运行时提供给系统。此外,它们还能提供timer的功能,比如设置一个闹钟,设置周期性的闹钟等。因此,RTC设备从功能上来说,包括提供时间、设置时间、设置闹钟等。该部分的代码位于drivers/rtc/目录下,该目录下的文件如下图所示。类似其它子系统的结构,这些文件可以大致分为两类,一类是RTC子系统框架本身,另一类是针对具体平台或具体设备基于RTC驱动框架实现的驱动程序,前者是我们需要学习的部分,后者是工程中实现设备驱动的参考。
我们也可以从Makefile中看到这些文件的关系,如下图所示。不难看出实现RTC驱动框架的的是rtc-lib.c、class.c、interface.c、rtc-dev.c、rtc-proc.c、rtc-sysfs.c和hctosys.c,其余的都是具体的设备驱动文件,接下来就是逐个分析下实现框架的源代码就好了。
RTC驱动框架中如何表示一个RTC设备,也就是rtc设备的结构体定义,如下图所示。首先rtc设备肯定是struct device的子类;系统中有多个RTC设备的时候,每个设备要有自己的ID和name;接下来的rtc_class_ops是一个非常关键的变量,它包含了设置时间、读取时间、设置闹钟,读取闹钟等回调函数,设备驱动中就是通过这些接口屏蔽硬件差异的,这会是实现设备驱动的关键部分;其它还有跟timer相关的变量、同步与互斥用到的变量等等,这些等分析具体代码的时候再说。
说到底RTC设备是一种字符设备,我们就先看看在字符设备这方面,RTC子系统是如何处理的吧,这部分的逻辑在rtc-dev.c中实现,可以预见,这个文件中肯定有对rtc_device中cdev类型变量的处理。rtc-dev.c实现的功能主要包括两大类,第一类是RTC字符设备的设备号申请,第二类是实现file_operations中的各个接口。申请字符设备号的工作非常简单,直接调用alloc_chrdev_region()就可以了,这一步是在初始化函数rtc_dev_init()中实现的,如下图所示。
其中RTC_DEV_MAX是次设备号,这里被定义为16,一般来说,一个系统中的RTC设备不会达到这么多的。这里还是有个疑问的,字符设备的主设备号其实是一些“规定”的,大家约定好了各类字符设备的主设备号,比如RTC主设备号是10,但是目前这里使用的是alloc_chrdev_region(),去动态获取一个别人未用的主设备号,就不一定是“规定”好的了,当然这并不影响其它的内容分析。
void rtc_dev_add_device(struct rtc_device *rtc)其实就是调用了cdev_add(&rtc->chr_dev, rtc->dev.devt, 1),void rtc_dev_del_device(struct rtc_device *rtc)其实就是调用cdev_del(&rtc->chr_dev),到这里可以看到,rtc-dev.c确实是在处理字符设备的事情。这里看到cdev_add/del操作,但是cdev_init在哪里?在rtc_dev_prepare(struct rtc_device *rtc)中,当设备驱动准备号rtc_ops之后就需要调用rtc-dev.c中的这个接口,它做的事情也比较少,如下图所示,其实就是计算了设备号,调用了cdev_init()而已。虽然事情少,但是引出了rtc-dev.c中第二类工作,就是file_oerations结构中的各种接口,read、write、poll、ioctl等等,这部分内容较多,下面逐个展开。
rtc-dev.c中file_perations操作如下所示。
先看rtc_dev_open接口,代码如下所示,根据inode中的chr_dev指针,得到对应的rtc_device,这个rtc_device肯定是某个驱动程序注册的,然后将当前file的private_data设置为当前打开的rtc设备。作为框架代码,它不会去执行具体的逻辑,因此最后调用rtc_class_ops->open回调函数,这也是由对应的设备驱动程序提供的,看名字应该就是做一些初始化操作。
代码中涉及到两处bit互斥操作,其中test_and_set_bit的实现如下所示,流程很简单,通过spinlock实现互斥访问,读出原来的值,将对应bit置位后再返回原来的值,清除bit的操作是类似的。这里就是在open中互斥地将BUSY标志置位了,别人再打开相同的文件就会报错了。相应地,BUSY的标志会在release中被清除。
release回调函数如下所示,这部分代码看注释就差不多了,涉及到的ioctl接口后面会分析。注释中出现了几个英文缩写,UIE,PIE,AIE,开始看代码的时候也没看懂是什么意思,这里解析一下,三者间的详细区别还未搞清楚。
PIE - Periodical Interrupt Enable,用户设置的周期发生的事件
AIE - Alarm Interrupt Enable,一次性的闹钟
UIE - Update Interrupt Enable,update这个词太泛了,不能准确理解。
rtc_dev_read实现的并不是读取RTC时间,目前并没有RTC应用层开发的经验,到这里还不知道这个接口的具体用途,从程序中看是一个unsigned int的值,rtc->irq_data。这里的处理流程非常经典,其实这个流程也可以用在write中。
add_wait_queue();
do { ready = readable(); if (ready) { copy_to_user(); return n; } else { if (f_flags & O_NONBLOCK) { return -EAGAIN; } else { __set_current_state(TASK_INTERRUPTIBLE); schedule();
} } while (!ready);
set_current_state(TASK_RUNNING); remove_wait_queue(); copy_to_user(); return n;
在rtc_dev_read中readable其实就是irq_data这个值是否非零,访问它时要注意互斥保护。最后,代码中尝试调用read_callback,这个逻辑可以分析具体驱动的时候再看。
rtc_dev_poll主要是调用了poll_wait()具体的逻辑等之后再学习,这里需要留心的是,上层可读取的条件依然是检查irq_data。
rtc_dev_fasync()调用fasync_helper(),具体逻辑之后学习。
rtc_dev_ioctl()中处理的命令如下表所示,处理过程基本都是调用interface.c中实现的接口,这些接口的实现和功能,后面会分析。
命令 | 处理 |
RTC_ALM_READ | 调用rtc_read_alarm(),返回给调用者。 |
RTC_ALM_SET | 一番计算后最后调用rtc_set_alarm() |
RTC_RD_TIME | 调用rtc_read_time()获取RTC时间,这就是最基本的功能了,最后就调用到了具体驱动中注册的获取时间的函数。 |
RTC_SET_TIME | 调用rtc_set_time()设置RTC时间,属于基本功能。 |
RTC_PIE_ON | 调用rtc_irq_set_state(),使能 |
RTC_PIE_OFF | 调用rtc_irq_set_state(),禁止 |
RTC_AIE_ON | 调用rtc_alarm_irq_enable()使能闹钟中断 |
RTC_AIE_OFF | 调用rtc_alarm_irq_enable()关闭闹钟中断 |
RTC_UIE_ON | 调用rtc_update_irq_enable()使能update中断。 |
RTC_UIE_OFF | 调用rtc_update_irq_enable()关闭update中断。 |
RTC_IRQP_SET | 调用rtc_set_irq_freq()设置中断频率,是什么中断? |
RTC_IRQP_READ | 通过put_user()将irq_freq返回给调用者。 |
rtc-dev.c有关字符设备驱动相关的代码就基本分析完了,未得到解决的问题依赖于后续代码的分析。
class.c不仅初始化了rtc class,而且提供了rtc设备注册和注销的接口,其入口函数如下所示。调用class_create()创建一个类,因此文件系统中会存在文件夹/sys/class/rtc/。接着,还设置了suspend和resume函数,每个设备都有自己的suspend和resume方法,这里怎么在class中直接设置了呢?存疑。rtc_dev_init()我们已经分析过了,rtc_sysfs_init()属于rtc_sysfs.c中的内容。
向rtc class中注册设备的接口是rtc_device_register(),原型如下所示,调用者需要提供设备的名称,rtc_class_ops等信息,该函数内部的操作主要包括三个步骤:1. 初始化rtc_device。2 初始化锁、timer、queue、task。3. 向sysfs、procfs系统中注册设备。
初始化rtc_device的过程如下所示,注意dev.release方法是在class中提供的,工作就是根据struct device知道对应的rtc device,然后kfree掉。
初始化各种锁、timer的过程如下所示,ops_lock是用来对rtc设备的操作进行互斥保护的,比如说读rtc的时候不允许同时写,这里是用mutex来保护的;irq_lock用于保护rtc_device中的irq_data成员