比起2.6内核,3.2内核在rtc框架上加了一层class,变为了三层。
VFS 虚拟文件系统
rtc-dev.c 核心层
向总线层提供 rtc_dev_add_device、rtc_dev_del_device等接口
kernel/driver/rtc/class.c 总线层
向具体设备驱动提供 rtc_device_register 等接口
rtc-ds1307.c rtc-ds1742.c等具体驱动
从class.c源码中rtc_device_register->rtc_sysfs_add_device和rtc_proc_add_device
可知,rtc不仅在/dev下创建设备节点,还在虚拟文件系统中有节点
ls /sys/class/rtc -l
lrwxrwxrwx 1 root root 0 Jul 27 2012 rtc0 -> ../../devices/platform/omap/omap_i2c.1/i2c-1/1-0068/rtc/rtc0
/sys/下节点实际上是I2C下软链接,RTC一般是挂载I2C总线上的。
从内核文档可知,新的框架移除了所谓的“一系统一RTC”的理念,比如说 AM335X 上有两个RTC芯片,其中一个是外挂在I2C总线上的(低功耗),另一个是集成在SOC上(高功耗,高性能)的,系统刚启动时候会从I2C上的RTC读取时间,但是一旦上电运行后,就以SOC上的芯片来提供时钟脉冲来工作,为的是利用它的高性能。
IOCTL INTERFACE
The ioctl() calls supported by /dev/rtc are also supported by the RTC class framework.
RTC_RD_TIME, RTC_SET_TIME--提供读写RTC的接口
RTC_ALM_SET, RTC_ALM_READ--当RTC和中断IRQ做了连接,那么会每隔一定时间产生中断。
...还有其他一些不常用接口。
举个例子,RTC alarm可以用作系统唤醒源,可以把系统从低功耗的睡眠或者挂起状态唤醒到正常运行状态。这就给我们思路来做电源管理这块了。
内核提供了一个很好的示例 rtc-test.c 来说明这块,可以参考。
下面就从 rtc-test.c 来分析整个框架(PS:这个驱动也是学习platform平台的好例子)。
test_probe
struct rtc_device *rtc = rtc_device_register("test", &plat_dev->dev, &test_rtc_ops, THIS_MODULE);
rtc_device_register //class.c
struct rtc_device *rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
rtc->id = ida_simple_get(&rtc_ida, 0, 0, GFP_KERNEL); //get a new id,下面会用到,这个函数没有具体分析
rtc->ops = ops; //就是上面的 test_rtc_ops
rtc->dev.class = rtc_class; //rtc总线,在rtc_init函数中通过rtc_class = class_create(THIS_MODULE, "rtc")注册
rtc_dev_prepare(rtc); //这个函数很重要,下面我们会单独分析
//分别在 /dev、/sys、/proc下注册节点
rtc_dev_add_device(rtc);
rtc_sysfs_add_device(rtc);
rtc_proc_add_device(rtc);
我们来看下核心层都做了什么
rtc_dev_init //rtc-dev.c 核心层
alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc"); //RTC_DEV_MAX = 16 动态申请设备号,并存入rtc_devt中
回到上面的 rtc_dev_add_device
rtc_dev_add_device(struct rtc_device *rtc)
cdev_add(&rtc->char_dev, rtc->dev.devt, 1); //到这里才真正注册设备节点
那么问题就来了,上面申请的rtc_devt到哪里用了?rtc->dev.devt是多少,而字符设备的ops在哪?这就需要到上面我们说的rtc_dev_prepare中看了
rtc_dev_prepare //rtc-dev.c
rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); //也就说用的是上面动态申请的主设备号,但是次设备号用的是class.c中分配的
cdev_init(&rtc->char_dev, &rtc_dev_fops); //ops出现了
注意区分 rtc_dev_fops 和上面的 test_rtc_ops 不是一个概念
rtc_dev_fops :是file_operations结构,供设备节点的read/write使用
test_rtc_ops :为rtc设备抽象出的rtc_class_ops结构,用于rtc设备具体读写实现
到这里,我们就能就能梳理清楚 /dev/rtc*是如何注册使用的了。
当应用层调用ioctl(“/dev/rtc*”, RTC_RD_TIME, &rtc_tm),实际上调用的是 rtc_dev_fops 中的相关接口,我们来跟下代码
--------------------------------------------------------
ioctl("/dev/rtc*", RTC_RD_TIME, &rtc_tm) 应用层
--------------------------------------------------------
rtc_dev_fops rtc-dev.c 核心层
rtc_dev_ioctl //就是上面的rtc_dev_fops->unlocked_ioctl
const struct rtc_class_ops *ops = rtc->ops; //取到具体驱动中的ops,这里就是rtc-test.c中的 test_rtc_ops
mutex_lock_interruptible(&rtc->ops_lock); //加个锁,防止多个进程同时控制
case RTC_RD_TIME:
rtc_read_time(rtc, &tm);
copy_to_user(uarg, &tm, sizeof(tm))
---------------------------------------------------------
接下来具体分析 rtc_read_alarm 的实现 rtc/interface.c
rtc_read_time
__rtc_read_time(rtc, tm);
memset(tm, 0, sizeof(struct rtc_time));
rtc->ops->read_time(rtc->dev.parent, tm); //最终调用的是驱动里边的ops->read_time
----------------------------------------------------------
rtc->ops->read_time = test_rtc_read_time 具体驱动层rtc-test.c
rtc_time_to_tm(get_seconds(), tm); //这里具体怎么读,怎么写就取决于具体的硬件设备了
总结下,从上面分析可知,如果我们要编写一个具体的RTC设备驱动程序,只需要做两步即可:
1.调用 rtc_device_register 接口注册设备
2.实现 rtc_class_ops 需要的接口 //这个需要根据具体硬件设备来编程
我们具体来分析一个真正的设备实现,以3.2.0中rtc-ds1307.c来分析,这个文件实际上是以I2C体系来实现rtc驱动(大多数RTC芯片都是挂在I2C上的)
ds1307_init
i2c_add_driver(&ds1307_driver);
ds1307_probe //当BSP文件出现设备端时候调用这个函数
ds1307->read_block_data = i2c_smbus_read_i2c_block_data; //最后读数据还是通过I2C总线来读的
ds1307->rtc = rtc_device_register(client->name, &client->dev, &ds13xx_rtc_ops, THIS_MODULE); //出现了,注册RTC设备
//接下来主要任务就是实现这些ops接口啦,走一个 ds1307_get_time
static const struct rtc_class_ops ds13xx_rtc_ops = {
.read_time = ds1307_get_time,
.set_time = ds1307_set_time,
.read_alarm = ds1337_read_alarm,
.set_alarm = ds1337_set_alarm,
.alarm_irq_enable = ds1307_alarm_irq_enable,
};
ds1307_get_time
ds1307->read_block_data(ds1307->client, ds1307->offset, 7, ds1307->regs); //实际上调用就是上面的 i2c_smbus_read_i2c_block_data
剩下的我们就不再继续分析了,直接借助I2C总线接口就很容易实现编程了
至于 /sys 和 /proc相关的代码,不再分析,这又牵扯到两个很大的体系,后边我们会说道