Linux RTC驱动模型分析

来源:互联网 发布:夏普lcd70tx8008a知乎 编辑:程序博客网 时间:2024/06/05 00:15

RTC简介

RTC(real-time clock)简称实时时钟,主要作用是用来记时,产生闹钟等。RTC因为有备份电池,所以即使计算机关机掉电,也不会影响RTC记时。而RTC和系统时间(主要靠软件模拟)的区别在于,RTC会在掉电后数据不丢失,在下次启动依旧可以重新设置当前时间给计算机。而系统时间主要靠软件模拟产生,在掉电之后会丢失,需要在下次计算机重新启动之后重新模拟产生。RTC时间在每次系统启动的时候会使用,在以后需要的时候会将设置的时间写入到RTC中,别的时候获取时间都通过软件可以获得。 RTC可以使用周期性的中断来产生闹钟,也可以在系统suspend的时候作为系统的唤醒源使用。Linux系统提供了两套RTC接口,/dev/rtc是为pc机器提供,另一种/dev/rtc0, /dev/rtc1支持所有的系统,具体可参考rtc.txt文档。linux为新的接口设计一套驱动模型,如果驱动工程师想增加某一个驱动,只需要将芯片相关的代码编写,然后注册到rtc核心层中即可。

RTC驱动框架

RTC涉及的代码如下:
driver/rtc/class.c:         此文件向linux内核驱动模型注册了一个类RTC, 同时为底层的RTC驱动提供了注册/注销RTC接口。同时实现了RTC相关的PM操作。
driver/rtc/rtc-dev.c:      将各种各样的RTC设备抽象成一个字符设备,同时提供文件操作函数集。
driver/rtc/rtc-sysfs.c:    用户可以通过sysfs文件系统方便快捷的操作rtc设备。
driver/rtc/rtc-proc.c:     可以通过proc文件系统获得rtc的相关信息,比如rtc_time, rtc_data等信息。
driver/rtc/interface.c:    提供应用程序和驱动的接口函数,主要是为rtc提供相关的调用接口。
driver/rtc/rtc-lib.c:         提供了一个rtc和data以及time之间的转换函数
driver/rtc/hctosys.c:     用于开机启动的时候获取rtc的值。
driver/rtc/rtc-xxx.c:       各式各样的rtc驱动。
RTC的模型图如下:
通过上图可以清晰的看出class.c为各种各异的驱动提供了注册接口。同样用户可以操作设备节点/dev/rtc0,也可以通过sysfs或者proc文件系统最终通过interface操作到实际的驱动代码中。rtc-dev.c是对各式各样的rtc驱动的一个抽象,所以下一步先分析rtc-dev.c。

基本数据结构

在分析代码之前需要了解一些必要的数据结构

1. struct  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;                                //代表rtc字符设备,因为rtc就是个字符设备unsigned long flags;                                 //rtc的状态标志,例如RTC_DEV_BUSYunsigned long irq_data;                              //rtc中断数据spinlock_t irq_lock;                                 //访问数据是要互斥,需要spin_lockwait_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;                          //aie(alaram interrupt enable)定时器struct rtc_timer uie_rtctimer;                       //uie(update interrupt enable)定时器struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */ //pie(periodic interrupt enable)定时器int pie_enabled;                                     //pie使能标志struct work_struct irqwork;                          /* Some hardware can't support UIE mode */int uie_unsupported;                                  //uie使能标志#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL                           //RTC UIE emulation on dev interface配置项,目前没有开启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};
这个结构是rtc驱动的核心结构,当驱动程序使用rtc_device_register函数传递正确的参数,然后就返回struct rtc_deivce给驱动程序。而在这个结构中rtc_class_ops函数需要驱动程序实现。

2.  struct 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);};
这些函数中大部分需要驱动程序实现,比如open, read_time, set_time等。这些函数大多数都是和rtc芯片的操作有关。

rtc-dev.c代码分析

rtc-dev.c是对形形色色的rtc设备进行抽象,实现一些公共的功能,然后将此抽象rtc设备注册为字符设备。
void __init rtc_dev_init(void){int err;err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");if (err < 0)pr_err("failed to allocate char dev region\n");}
动态分配一个次设备号为0,相同设备的最大个数为16的字符设备。该函数会在rtc_init函数中被调用。
输出参数设备号rtc_devt, 由主设备号和次设备号组成。 
void rtc_dev_prepare(struct rtc_device *rtc){if (!rtc_devt)return;if (rtc->id >= RTC_DEV_MAX) {                                         //合法性判断,如果id大于16个,说明rtc设备个数太多dev_dbg(&rtc->dev, "%s: too many RTC devices\n", rtc->name);return;}rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);            #ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL                                          //UIE模拟配置相关,不做过多介绍INIT_WORK(&rtc->uie_task, rtc_uie_task);setup_timer(&rtc->uie_timer, rtc_uie_timer, (unsigned long)rtc);#endifcdev_init(&rtc->char_dev, &rtc_dev_fops);                        //字符设备初始化,以及文件操作函数集合初始化rtc->char_dev.owner = rtc->owner;}
该函数主要是初始化字符设备,设置rtc相关的file operation函数集合。
void rtc_dev_add_device(struct rtc_device *rtc){if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n",rtc->name, MAJOR(rtc_devt), rtc->id);elsedev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name,MAJOR(rtc_devt), rtc->id);}
调用cdev_add函数将rtc字符设备加入到内核中。这样以来rtc字符设备已经加入到系统中,就等待应用程序的调用。应用程序操作之前还需要实现rtc_dev_fops:
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,};
以上就是rtc字符设备驱动对应的file operation操作函数集合。接下来一个一个分析。
当应用程序打开/dev/rtc设备的时候就会走到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;                             //获得驱动的rtc opsif (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags))                  //检测rtc是否现在在使用,如果没有使用即可open           return -EBUSY;file->private_data = rtc;                                        //将rtc放入到private_data变量中err = ops->open ? ops->open(rtc->dev.parent) : 0;                //如果驱动实现open函数,就调用驱动的open,如果没有实现返回0if (err == 0) {spin_lock_irq(&rtc->irq_lock);rtc->irq_data = 0;spin_unlock_irq(&rtc->irq_lock);return 0;}/* something has gone wrong */clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);                 //离开的时候将rtc设备为不忙return err;}
以上操作就是rtc的open操作,多么简单,多么熟悉的套路。
/** * test_and_set_bit_lock - Set a bit and return its old value, for lock * @nr: Bit to set * @addr: Address to count from * * This operation is atomic and provides acquire barrier semantics. * It can be used to implement bit locks. */#define test_and_set_bit_lock(nr, addr)test_and_set_bit(nr, addr)
设置一个bit然后返回以前的值, 用于检测是设备是否在使用。

接下来分析read函数的执行过程。
static ssize_t rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){struct rtc_device *rtc = file->private_data;                    //从private_data域取出rtc数据,在open中设置的private_dataDECLARE_WAITQUEUE(wait, current);                             //声明一个等待队列waitunsigned long data;ssize_t ret;if (count != sizeof(unsigned int) && count < sizeof(unsigned long))   return -EINVAL;add_wait_queue(&rtc->irq_queue, &wait);                       //将等待队列加入到rtc的等待队列do {__set_current_state(TASK_INTERRUPTIBLE);              //设置当前进程的状态为可中断类型spin_lock_irq(&rtc->irq_lock);data = rtc->irq_data;                                  //读取irq_date的数据,在中断中有数据的时候会设置irq_date的值rtc->irq_data = 0;spin_unlock_irq(&rtc->irq_lock);if (data != 0) {                                       //data不等于0,说明有数据,跳出while循环ret = 0;break;}if (file->f_flags & O_NONBLOCK) {                       //如果读取数据是非阻塞的方式,直接返回ret = -EAGAIN;break;}if (signal_pending(current)) {                         //收到信号中断,退出ret = -ERESTARTSYS;break;}schedule();                                            //调度出去,睡眠} while (1);set_current_state(TASK_RUNNING);                                //执行到这里说明是从上述3种情况break出来的,然后将进程状态设置为runningremove_wait_queue(&rtc->irq_queue, &wait);                      //从等待队列移除waitif (ret == 0) {                                                 //ret等于0,说明是rtc中断触发导致退出while循环/* Check for any data updates */if (rtc->ops->read_callback)                            //驱动程序是否实现read_callback, 一般驱动程序没有实现该回调函数data = rtc->ops->read_callback(rtc->dev.parent,       data);if (sizeof(int) != sizeof(long) &&    count == sizeof(unsigned int))ret = put_user(data, (unsigned int __user *)buf) ?:    //返回出去给用户sizeof(unsigned int);elseret = put_user(data, (unsigned long __user *)buf) ?:sizeof(unsigned long);}return ret;}
该函数一般可以用来判断是否有rtc中断发生,如果有read读就不会blocked。 而此read不是用来读取具体时间的函数。 

接下来分析poll函数。
static unsigned int rtc_dev_poll(struct file *file, poll_table *wait){struct rtc_device *rtc = file->private_data;unsigned long data;poll_wait(file, &rtc->irq_queue, wait);          //使用poll系统调用,一直等待有数据是否到来data = rtc->irq_data;return (data != 0) ? (POLLIN | POLLRDNORM) : 0;  //返回结果(有数据可读|有普通数据可读)}
接下来分析rtc的重点函数ioctl调用。
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;
       //以下几个都是合法性检测,检测调用者是否有权限执行操作。switch (cmd) {case RTC_EPOCH_SET:case RTC_SET_TIME:if (!capable(CAP_SYS_TIME))err = -EACCES;break;case RTC_IRQP_SET:if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))err = -EACCES;break;case RTC_PIE_ON:if (rtc->irq_freq > rtc->max_user_freq &&!capable(CAP_SYS_RESOURCE))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:                                                     //设置闹钟时间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;/* RTC_ALM_SET alarms may be up to 24 hours in the future. * Rather than expecting every RTC to implement "don't care" * for day/month/year fields, just force the alarm to have * the right values for those fields. * * RTC_WKALM_SET should be used instead.  Not only does it * eliminate the need for a separate RTC_AIE_ON call, it * doesn't have the "alarm 23:59:59 in the future" race. * * NOTE:  some legacy code may have used invalid fields as * wildcards, exposing hardware "periodic alarm" capabilities. * Not supported here. */{unsigned long now, then;err = rtc_read_time(rtc, &tm);if (err < 0)return err;rtc_tm_to_time(&tm, &now);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;rtc_tm_to_time(&alarm.time, &then);/* alarm may need to wrap into tomorrow */if (then < now) {rtc_time_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);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);case RTC_PIE_ON:                                                //Enable the periodic interrupterr = rtc_irq_set_state(rtc, NULL, 1);break;case RTC_PIE_OFF:                                               //Disable the periodic interrupterr = rtc_irq_set_state(rtc, NULL, 0);break;case RTC_AIE_ON:                                                //Enable the alarm interruptmutex_unlock(&rtc->ops_lock);return rtc_alarm_irq_enable(rtc, 1);case RTC_AIE_OFF:                                                 //Disable the alarm interruptmutex_unlock(&rtc->ops_lock);return rtc_alarm_irq_enable(rtc, 0);case RTC_UIE_ON:                                                  //Enable the interrupt on every clock updatemutex_unlock(&rtc->ops_lock);return rtc_update_irq_enable(rtc, 1);case RTC_UIE_OFF:                                                 //Disable the interrupt on every clock updatemutex_unlock(&rtc->ops_lock);return rtc_update_irq_enable(rtc, 0);case RTC_IRQP_SET:                                                //Set IRQ rateerr = rtc_irq_set_freq(rtc, NULL, arg);break;case RTC_IRQP_READ:                                                 //Read IRQ rate err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);break;case RTC_WKALM_SET:                                                     //Set wakeup alarmmutex_unlock(&rtc->ops_lock);if (copy_from_user(&alarm, uarg, sizeof(alarm)))return -EFAULT;return rtc_set_alarm(rtc, &alarm);case RTC_WKALM_RD:                                                      //Get wakeup alarmmutex_unlock(&rtc->ops_lock);err = rtc_read_alarm(rtc, &alarm);if (err < 0)return err;if (copy_to_user(uarg, &alarm, sizeof(alarm)))err = -EFAULT;return err;default:                                                                 //默认操作,如果驱动不实现上述操作,可以实现自己的命令,然后走这里分支。/* Finally try the driver's ioctl interface */if (ops->ioctl) {err = ops->ioctl(rtc->dev.parent, cmd, arg);if (err == -ENOIOCTLCMD)err = -ENOTTY;} elseerr = -ENOTTY;break;}done:mutex_unlock(&rtc->ops_lock);return err;}
以上就是全部ioctl的操作,大多数rtc的功能都在这个函数中的case当中被调用。
接下来是rtc的关闭函数。
static int rtc_dev_release(struct inode *inode, struct file *file){struct rtc_device *rtc = file->private_data;/* Keep ioctl until all drivers are converted */rtc_dev_ioctl(file, RTC_UIE_OFF, 0);                            //关闭rtc的uie中断rtc_update_irq_enable(rtc, 0);                                  //disable  rtc中断rtc_irq_set_state(rtc, NULL, 0);if (rtc->ops->release)rtc->ops->release(rtc->dev.parent);                      //调用驱动的release函数clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);                  //将rtc的状态设置为空闲,也就是不忙。return 0;}

以上就是全部的rtc-dev.c的分析。










0 0
原创粉丝点击