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的分析。

Linux RTC驱动模型分析相关推荐

  1. linux RTC 驱动模型分析

    linux RTC 驱动模型分析 RTC(real time clock)实时时钟,主要作用是给Linux系统提供时间.RTC因为是电池供电的,所以掉电后时间不丢失.Linux内核把RTC用作&quo ...

  2. Linux RTC驱动模型分析之rtc-sysfs.c【转】

    转自:https://blog.csdn.net/longwang155069/article/details/52353408 版权声明:本文为博主原创文章,未经博主允许不得转载. https:// ...

  3. linux MISC 驱动模型分析

    linux MISC 驱动模型分析 阅读led驱动程序的代码的时候,没有发现ldd3中提到的各种字符设备注册函数,而是发现了一个misc_register函数,这说明led设备是作为杂项设备出现在内核 ...

  4. LINUX设备驱动模型分析之三 驱动(DRIVER)接口分析

    上一章我们分析了bus-driver-device模型中bus接口部分,本章我们将分析driver接口,在bus-driver-device模型中,driver接口是依附于bus上,而不像device ...

  5. linux platform 驱动模型分析

    一. 概述     platform设备和驱动与linux设备模型密切相关.platform在linux设备模型中,其实就是一种虚拟总线没有对应的硬件结构.它的主要作用就是管理系统的外设资源,比如io ...

  6. linux i2c adapter 增加设备_LINUX设备驱动模型分析之四 设备模块相关(DEVICE)接口分析...

    本系列前几篇文章链接如下: <LINUX设备驱动模型分析之一 总体概念说明> <LINUX设备驱动模型分析之二 总线(BUS)接口分析> <LINUX设备驱动模型分析之三 ...

  7. linux下camera驱动分析_LINUX设备驱动模型分析之三 驱动模块相关(DRIVER)接口分析...

    本系列前几篇文章链接如下: <LINUX设备驱动模型分析之一 总体概念说明> <LINUX设备驱动模型分析之二 总线(BUS)接口分析> 上一章我们分析了bus-driver- ...

  8. linux 内核驱动模型,linux设备驱动模型架构分析 一

    linux设备驱动模型架构分析 一 发布时间:2018-07-04 15:14, 浏览次数:584 , 标签: linux 概述 LDD3中说:"Linux内核需要一个对系统结构的一般性描述 ...

  9. char添加一个字符_LINUX字符设备驱动模型分析(起始篇)

    在前面几个模块的介绍中,我们主要以vfs为起始,完成了sysfs.设备-总线-驱动模型.platform设备驱动模型.i2c设备驱动模型.spi设备驱动模型的分析.在对这些模块进行分析的时候,我们或多 ...

  10. 请教几个Linux设备驱动模型的问题

    http://bbs.chinaunix.net/thread-3691002-1-1.html 1.Linux设备驱动模型是如何将bus.device和driver关联起来的?它们之间的数据结构关系 ...

最新文章

  1. 3.5.3 CSMA协议
  2. 404 Not Found: Requested route ('jerrylist.cfapps.eu10.hana.ondemand.com') does not exist
  3. mysql 行列转换 动态_mysql 行列动态转换的实现(列联表,交叉表)
  4. 演练 类的定义 java 1615134691
  5. 二叉树——二叉树问题(洛谷 P3884)
  6. 安卓学习笔记23:常用控件 - 网格视图与图像切换器
  7. 《深入理解 Spring Cloud 与微服务构建》第十五章 微服务监控 Spring Boot Admin
  8. 敏友的【敏捷个人】有感(6): 我的改变从执行力分享开始
  9. Get_HD_Serial() 获得磁盘驱动器序列号
  10. java分布性_java大型分布系统性能优化实战教程
  11. WPF自定义动画控件 风机
  12. 关于embedding的理解,2020-7-30
  13. java web play_玩转Java Web应用开发:Play框架
  14. 雷军在北京大学的演讲
  15. 技术总结--android篇(四)--工具类总结
  16. 训练赛1_E_Lawnmower
  17. 如何实现实时音视频聊天功能
  18. react 使用qs
  19. (七)集成学习中-投票法Voting
  20. windows访问共享文件夹 登录失败: 禁用当前的账户 解决方法

热门文章

  1. Java项目导出为jar包+导出第三方jar包+使用命令行调用+传参
  2. Linux 基础——ls 命令
  3. react-native学习小结
  4. 你的灯还亮着吗阅读笔记之二
  5. java折半查找(递归版)
  6. python自动写工作日志_python自动化执行重复工作
  7. 编译器GCC的Windows版本 : MinGW-w64安装教程
  8. 自定义音乐播放器的歌词显示view
  9. ACM-ICPC 2018 沈阳赛区网络预赛 B Call of Accepted(表达式求值)
  10. WinForm 使用 HttpUtility