Linux RTC驱动分析及应用
一、RTC简介
RTC(real-time clock)简称实时时钟,主要是用来计时,产生闹钟等。RTC一般有个备份电池,所以即使设备关机掉电,RTC也能在备份电池的供电下继续正常计时,这样在每次系统开机上电时就可以从RTC设备中读取到准确的时间。RTC时间在每次系统启动的时候会使用,在需要的时候也可以由系统将要设置的时间写入到RTC设备。Linux系统中,RTC可以使用周期性的中断来产生闹钟,也可以在系统suspend的时候作为系统的唤醒源使用。内核为RTC设备设计了一套驱动模型,如果驱动工程师想增加某一种新RTC硬件的驱动,只需要去编写芯片相关的代码,然后调用内核提供函数注册到RTC核心层即可。
二、RTC设备描述
在介绍内核RTC驱动结构之前,先来看RTC设备在内核中是如何描述的
rtc_device结构体
struct rtc_device { struct device dev; //代表是一个设备 struct module *owner; int id; //rtc设备编号 char name[RTC_DEVICE_NAME_SIZE]; //rtc设备的名称 const struct rtc_class_ops *ops; //rtc操作函数集,由驱动程序实现 struct mutex ops_lock; //操作函数集的互斥锁 struct cdev char_dev; //cdev结构体,代表rtc是字符设备 unsigned long flags; //rtc的状态标志,例如RTC_DEV_BUSY unsigned long irq_data; //rtc中断数据 spinlock_t irq_lock; //互斥访问数据 wait_queue_head_t irq_queue; //数据查询中用到的rtc队列 struct fasync_struct *async_queue; //异步队列 struct rtc_task *irq_task; //在中断中使用task传输数据 spinlock_t irq_task_lock; //task传输互斥 int irq_freq; //rtc的中断频率 int max_user_freq; //rtc的最大中断频率 struct timerqueue_head timerqueue; //定时器队列 struct rtc_timer aie_timer; //报警中断定时器 struct rtc_timer uie_rtctimer; //更新中断定时器 struct hrtimer pie_timer; //周期中断高精度定时器 int pie_enabled; //周期中断使能标志 struct work_struct irqwork; /* Some hardware can't support UIE mode */ int uie_unsupported; ... };
RTC设备在内核中是以字符设备形式存在的,内核中使用rtc_device结构体来抽象一个rtc设备。rtc_device结构体屏蔽了不同RTC硬件之间的差异,通过rtc_class_ops结构体为上层提供了访问硬件设备的统一接口,该结构体中包含了对硬件操作的相关函数。
rtc_class_ops结构体
struct rtc_class_ops { int (*open)(struct device *); void (*release)(struct device *); 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_mmss)(struct device *, unsigned long secs); int (*read_callback)(struct device *, int data); int (*alarm_irq_enable)(struct device *, unsigned int enabled); };
rtc_class_ops结构体中的操作函数需要驱动程序实现,比如open, read_time, set_time等。这些函数大多数都是和RTC芯片的操作有关,上层通过这些函数进行对硬件设备的操作,比如调用set_time设置时间、read_time获取时间等等。
三、内核源代码分析
好了,有了上面的知识作为铺垫,现在我们开始来从源码角度去详细分析内核中RTC驱动程序的框架。这里以s3c24xx芯片外设RTC为例,一步步推出内核中RTC设备驱动是如何实现的。
RTC设备驱动程序存放在内核源码树的drivers/rtc目录,s3c24xx芯片的RTC驱动程序对应的是rtc-s3c.c文件。下面我们就从这个文件中的入口函数进行分析
入口函数
module_platform_driver(s3c_rtc_driver);
static struct platform_driver s3c_rtc_driver = {
.probe = s3c_rtc_probe,
.remove = __devexit_p(s3c_rtc_remove),
.suspend = s3c_rtc_suspend,
.resume = s3c_rtc_resume,
.id_table = s3c_rtc_driver_ids,
.driver = {
.name = "s3c-rtc",
.owner = THIS_MODULE,
.of_match_table = s3c_rtc_dt_match,
},
};
modules_platform_driver是一个宏,这个宏在include\linux\device.h文件中定义,内容如下:
#define module_platform_driver(__platform_driver) \ module_driver(__platform_driver, platform_driver_register, \ platform_driver_unregister) #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);
根据上述宏定义,将module_platform_device(s3c_rtc_driver)展开,展开内容如下所示:
static int __init s3c_rtc_driver_init(void) { return platform_driver_register(&(s3c_rtc_driver)); } module_init(s3c_rtc_driver_init); static void __exit s3c_rtc_driver_exit(void) { platform_driver_unregister(&(s3c_rtc_driver)); } module_exit(s3c_rtc_driver_exit);
通过上面的展开操作,我们可以看出module_platform_driver(s3c_rtc_driver)即实现了注册、卸载一个platform驱动程序的入口和出口函数。其实和我们平时编写驱动程序一样,不过这里使用的是内核定义好的宏,看上去更加简介。
我们知道platform驱动在注册时,会遍历platform总线上所有的挂载的platform设备,并调用platform总线的match函数进行比较,如果匹配成功将调用platform驱动的probe函数。
下面来看s3c_rtc_driver的probe函数s3c_rtc_probe
static int __devinit s3c_rtc_probe(struct platform_device *pdev) { ... res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ... s3c_rtc_mem = request_mem_region(res->start, resource_size(res), pdev->name); ... s3c_rtc_base = ioremap(res->start, resource_size(res)); ... rtc_clk = clk_get(&pdev->dev, "rtc"); ... clk_enable(rtc_clk); s3c_rtc_enable(pdev, 1); ... device_init_wakeup(&pdev->dev, 1); rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops, THIS_MODULE); ... }
s3c_rtc_probe函数实现的基本上是硬件相关的操作,首先获取与之匹配的platform_device中描述的硬件相关的资源,如s3c24xx芯片RTC外设寄存器地址,完成寄存器物理地址到虚拟地址映射、使能RTC外设时钟、初始化RTC外设寄存器,然后调用rtc_device_register函数向内核注册一个rtc设备。
rtc_device_register函数是驱动程序找到匹配的设备后,向内核注册一个rtc_device,来详细分析这个函数
rtc_device_register函数
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 = ida_simple_get(&rtc_ida, 0, 0, GFP_KERNEL); ------------------------->① ... rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);--------------------->② ... rtc->id = id; rtc->ops = ops; ... mutex_init(&rtc->ops_lock); spin_lock_init(&rtc->irq_lock); spin_lock_init(&rtc->irq_task_lock); init_waitqueue_head(&rtc->irq_queue); /* Init timerqueue */ timerqueue_init_head(&rtc->timerqueue); INIT_WORK(&rtc->irqwork, rtc_timer_do_work); ... /* Check to see if there is an ALARM already set in hw */ err = __rtc_read_alarm(rtc, &alrm); ... strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE); dev_set_name(&rtc->dev, "rtc%d", id); rtc_dev_prepare(rtc);------------------------------------------------>③ err = device_register(&rtc->dev); ... rtc_dev_add_device(rtc);-------------------------------------------->④ ... }
① 给注册的rtc_device分配一个新的设备id,创建的字符设备的设备节点会使用到这个id, /dev/rtc(id)
② 分配一个rtc_device结构体,初始化rtc_device结构体中的成员变量,比如使用到的锁,初始化定时器等等
注:rtc_device结构体中的rtc设备操作函数指针rtc_class_ops指针指向s3c_rtcops,这部分内容在后面再进行展开分析
③ 调用rtc_dev_prepare初始化cdev结构体
void rtc_dev_prepare(struct rtc_device *rtc) { ... rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); ... cdev_init(&rtc->char_dev, &rtc_dev_fops); rtc->char_dev.owner = rtc->owner; }
④ 调用ret_dev_add_device向内核添加一个字符设备
void rtc_dev_add_device(struct rtc_device *rtc) { if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1)) ... }
rtc_device_register函数动态分配了一个rtc_device结构体,并初始化分配的rtc_device结构体成员。最后向内核注册了一个字符设备,注册的字符设备的file_operations结构体指针指向rtc_dev_fops结构体。当系统访问RTC设备时,对RTC设备的操作是通过注册的字符设备的file_operations结构体的成员函数实现的。
注册的RTC设备使用的file_operations结构体
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, };
以open函数进行分析,rtc_dev_open
static int rtc_dev_open(struct inode *inode, struct file *file) { int err; struct rtc_device *rtc = container_of(inode->i_cdev, struct rtc_device, char_dev); const struct rtc_class_ops *ops = rtc->ops; ... file->private_data = rtc; err = ops->open ? ops->open(rtc->dev.parent) : 0; ... }
rtc_dev_open函数在打开rtc设备节点时会被调用,首先根据inode->i_cdev使用container_of获取对应的rtc_device结构体(inode->i_cdev = &rtc->char_dev)。
获取rtc_device结构体后,找到其成员rtc_class_ops结构体指针,调用rtc_class_ops结构体指针指向对象的open函数。
由此可见,访问设备的file_operations函数最终会调用到对应的rtc_device结构体中对RTC硬件进行操作的函数。
前面讲过,rtc_device结构体中的RTC设备操作函数指针rtc_class_ops指针指向s3c_rtcops,我们来看下s3c_rtcops结构体内容
static const struct rtc_class_ops s3c_rtcops = { .read_time = s3c_rtc_gettime, .set_time = s3c_rtc_settime, .read_alarm = s3c_rtc_getalarm, .set_alarm = s3c_rtc_setalarm, .proc = s3c_rtc_proc, .alarm_irq_enable = s3c_rtc_setaie, };
s3c_rtcops结构体中实现了.read_time、.set_time等成员函数,这些成员函数在系统调用使用ioctl读取时间或设置时间时被调用,我们进一步分析看它是如何返回时间信息的
static long rtc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct rtc_device *rtc = file->private_data; const struct rtc_class_ops *ops = rtc->ops; struct rtc_time tm; ... switch (cmd) { ... case RTC_RD_TIME: mutex_unlock(&rtc->ops_lock); err = rtc_read_time(rtc, &tm); |-->__rtc_read_time(rtc, tm); |-->memset(tm, 0, sizeof(struct rtc_time)); |-->err = rtc->ops->read_time(rtc->dev.parent, tm); if (copy_to_user(uarg, &tm, sizeof(tm))) err = -EFAULT; return err; ... }
应用程序使用ioctl函数执行获取设备时间操作,最终会调用到rtc_dev_ioctl,执行rtc_read_time获取当前时间,将获取时间存放到rtc_time结构体中,最后使用copy_to_user拷贝到用户空间。
rtc_read_time函数最终会调用到s3c_rtcops.read_time,即最终调用到s3c_rtc_gettime函数
s3c_rtc_gettime函数
static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) { ... rtc_tm->tm_min = readb(base + S3C2410_RTCMIN); rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR); rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE); rtc_tm->tm_mon = readb(base + S3C2410_RTCMON); rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR); rtc_tm->tm_sec = readb(base + S3C2410_RTCSEC); ... }
s3c_rtc_gettime函数通过读取s3c24xx芯片RTC外设当前时间寄存器值来获取时间,将读取时间信息填充到一个rtc_time结构体中.
由以上的分析,我们知道了加载一个RTC驱动程序,需要去初始化RTC硬件设备,并调用rtc_device_register函数构建一个rtc设备。RTC驱动程序中需要实例化一个rtc_class_op类型成员对象,该成员对象包含了获取时间信息、设置时间、闹钟时间等硬件相关的操作函数。
调用rtc_device_register函数构造的rtc_device结构体时会将其rtc_class_op类型指针指向实例化的rtc_class_op类型成员对象,最后初始化、创建一个字符设备。应用程序通过注册的/dev/rtc(n)设备节点对RTC设备进行访问,对字符设备的操作最后会调用到注册字符设备的file_operations成员函数。RTC设备的file_operation成员函数是访问所有注册的RTC设备的统一入口,file_operation成员函数函数中再根据打开字符设备节点,找到对应的rtc_device,最终调用到rtc_device中的rtc_class_op类型指针指向内容的成员函数。
前面看到了使用rtc_dev_prepare函数调用cdev_init去初始化一个cdev结构体,使用rtc_dev_add_device函数调用cdev_add去向内核添加一个字符设备,但注册的RTC字符设备主设备号和可用此设备号区域是在哪里分配的呢?我们还需要继续来看
/drivers/rtc/class.c的入口函数中分配了注册的RTC字符设备主设备号和可用此设备号区域
static int __init rtc_init(void) { rtc_class = class_create(THIS_MODULE, "rtc"); ... rtc_dev_init(); rtc_sysfs_init(rtc_class); return 0; }
void __init rtc_dev_init(void) { ... err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc"); ... }
rtc_init函数中创建了一个rtc_class,最后调用rtc_dev_init函数,rtc_dev_init函数调用alloc_chrdev_region为rtc设备动态分配了主设备号区域。创建一个rtc_class,在驱动程序中匹配到设备时去这rtc_class下创建设备,这样mdev机制获取rtc_class类下设备信息,自动创建/dev/rtc(n)设备节点。
至此,我们的内核RTC设备驱动程序框架分析完毕。