首页 > 代码库 > Linux RTC驱动模型分析
Linux RTC驱动模型分析
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_BUSY unsigned long irq_data; //rtc中断数据 spinlock_t irq_lock; //访问数据是要互斥,需要spin_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; //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); #endif cdev_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); else dev_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 ops if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags)) //检测rtc是否现在在使用,如果没有使用即可open return -EBUSY; file->private_data = http://www.mamicode.com/rtc; //将rtc放入到private_data变量中>以上操作就是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_data DECLARE_WAITQUEUE(wait, current); //声明一个等待队列wait unsigned 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 = http://www.mamicode.com/rtc->irq_data; //读取irq_date的数据,在中断中有数据的时候会设置irq_date的值>该函数一般可以用来判断是否有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 = http://www.mamicode.com/rtc->irq_data;>接下来分析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 interrupt err = rtc_irq_set_state(rtc, NULL, 1); break; case RTC_PIE_OFF: //Disable the periodic interrupt err = rtc_irq_set_state(rtc, NULL, 0); break; case RTC_AIE_ON: //Enable the alarm interrupt mutex_unlock(&rtc->ops_lock); return rtc_alarm_irq_enable(rtc, 1); case RTC_AIE_OFF: //Disable the alarm interrupt mutex_unlock(&rtc->ops_lock); return rtc_alarm_irq_enable(rtc, 0); case RTC_UIE_ON: //Enable the interrupt on every clock update mutex_unlock(&rtc->ops_lock); return rtc_update_irq_enable(rtc, 1); case RTC_UIE_OFF: //Disable the interrupt on every clock update mutex_unlock(&rtc->ops_lock); return rtc_update_irq_enable(rtc, 0); case RTC_IRQP_SET: //Set IRQ rate err = 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 alarm mutex_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 alarm mutex_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; } else err = -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的分析。
Linux RTC驱动模型分析
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。