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 = rtc; //将rtc放入到private_data变量中
err = ops->open ? ops->open(rtc->dev.parent) : 0; //如果驱动实现open函数,就调用驱动的open,如果没有实现返回0
if (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_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 = 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出来的,然后将进程状态设置为running
remove_wait_queue(&rtc->irq_queue, &wait); //从等待队列移除wait
if (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);
else
ret = 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 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的分析。