概述
背景
对Linux时间系统感兴趣不是一天两天了,今天这篇着重讲一下Linux时间系统中相对简单跟独立的部分——RTC。
简述
RTC全称为Real Time Clock,是一个专门用来记录时间的硬件设备,一般可以集成在soc内部,或者选择外挂,通过i2c与其通信。
那为什么会需要RTC,因为Linux的系统时间(也就是我们常说的wall time)只能在系统运行时使用,系统关机时间就丢了,而RTC可以在系统关闭后,依靠外部电池或其他supply继续工作,这才将时间保存下来。
一般在Linux系统启动后,会先读取RTC时间,将其同步给wall time,这部分逻辑关注后面的hctosys部分,之后如果有了网络,应用程序可以再将网络时间同步给wall time跟RTC,做一次校准。
那么RTC驱动结构是怎样的,如何通过RTC帮助我们保存时间?附上Linux RTC子系统框图,后面会详细说明。
RTC HW driver
我们由下至上,HW driver这部分是用来直接操作RTC芯片驱动,我们称之为HW层driver。
1.既然要操作芯片,自然少不了ops,Linux源码中已为大家统一了接口,基本结构如下,路径/linux-4.4/include/linux/rtc.h
/*
* For these RTC methods the device parameter is the physical device
* on whatever bus holds the hardware (I2C, Platform, SPI, etc), which
* was passed to rtc_device_register(). Its driver_data normally holds
* device state, including the rtc_device pointer for the RTC.
*
* Most of these methods are called with rtc_device.ops_lock held,
* through the rtc_*(struct rtc_device *, ...) calls.
*
* The (current) exceptions are mostly filesystem hooks:
* - the proc() hook for procfs
* - non-ioctl() chardev hooks: open(), release(), read_callback()
*
* REVISIT those periodic irq calls *do* have ops_lock when they're
* issued through ioctl() ...
*/
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);
};
通过函数名,我们可以很清除的阅读函数的意义,读取/设置时间,读取/设置alarm,alarm中断使能控制等等。
这里以Linux4.4源码中snvs为例,路径/linux-4.4/drivers/rtc/rtc-snvs.c
static const struct rtc_class_ops snvs_rtc_ops = {
.read_time = snvs_rtc_read_time,
.set_time = snvs_rtc_set_time,
.read_alarm = snvs_rtc_read_alarm,
.set_alarm = snvs_rtc_set_alarm,
.alarm_irq_enable = snvs_rtc_alarm_irq_enable,
};
2.备好ops后,通过接口rtc_device_register向系统注册rtc资源,附上函数原型及调用,路径/linux-4.4/drivers/rtc/class.c
/**
* rtc_device_register - register w/ RTC class
* @dev: the device to register
*
* rtc_device_unregister() must be called when the class device is no
* longer needed.
*
* Returns the pointer to the new struct class device.
*/
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
static int snvs_rtc_probe(struct platform_device *pdev)
{
...
data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
&snvs_rtc_ops, THIS_MODULE);
if (IS_ERR(data->rtc)) {
ret = PTR_ERR(data->rtc);
dev_err(&pdev->dev, "failed to register rtc: %dn", ret);
goto error_rtc_device_register;
}
...
}
RTC subsystem
1.RTC HW driver完成注册之后,rtc子系统会在/dev下自动创建新的rtc字符设备,附上rtc_device_register源码,路径/linux-4.4/drivers/rtc/class.c
/**
* rtc_device_register - register w/ RTC class
* @dev: the device to register
*
* rtc_device_unregister() must be called when the class device is no
* longer needed.
*
* Returns the pointer to the new struct class device.
*/
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
{
...
rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
if (rtc == NULL) {
err = -ENOMEM;
goto exit_ida;
}
rtc->id = id;
rtc->ops = ops;
rtc->owner = owner;
rtc->irq_freq = 1;
rtc->max_user_freq = 64;
rtc->dev.parent = dev;
rtc->dev.class = rtc_class;
rtc->dev.groups = rtc_get_dev_attribute_groups();
rtc->dev.release = rtc_device_release;
mutex_init(&rtc->ops_lock);
spin_lock_init(&rtc->irq_lock);
spin_lock_init(&rtc->irq_task_lock);
init_waitqueue_head(&rtc->irq_queue);
...
rtc_dev_prepare(rtc);
err = device_register(&rtc->dev);
if (err) {
/* This will free both memory and the ID */
put_device(&rtc->dev);
goto exit;
}
rtc_dev_add_device(rtc);
rtc_proc_add_device(rtc);
dev_info(dev, "rtc core: registered %s as %sn",
rtc->name, dev_name(&rtc->dev));
return rtc;
...
}
EXPORT_SYMBOL_GPL(rtc_device_register);
2.上一段代码中LINE 35 rtc_dev_prepare函数,准备字符设备ops及设备号,/linux-4.4/drivers/rtc/rtc-dev.c
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,
};
/* insertion/removal hooks */
void rtc_dev_prepare(struct rtc_device *rtc)
{
...
rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
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->char_dev.kobj.parent = &rtc->dev.kobj;
}
3.sysfs class下创建attribute file节点,附上函数rtc_init(),/linux-4.4/drivers/rtc/class.c
static int __init rtc_init(void)
{
rtc_class = class_create(THIS_MODULE, "rtc");
if (IS_ERR(rtc_class)) {
pr_err("couldn't create classn");
return PTR_ERR(rtc_class);
}
rtc_class->pm = RTC_CLASS_DEV_PM_OPS;
rtc_dev_init(); /* rtc-dev.c */
rtc_sysfs_init(rtc_class); /* rtc-sysfs.c */
return 0;
}
4.注册成功可通过串口看到如下文件
root@freescale:/sys/class/rtc/rtc0 # ls -al
-r--r--r-- root root 4096 2020-12-16 02:08 date
-r--r--r-- root root 4096 2020-12-16 02:08 dev
lrwxrwxrwx root root 2020-12-16 02:08 device -> ../../../20cc000.snvs:snvs-rtc-lp
-r--r--r-- root root 4096 2020-12-16 02:05 hctosys
-rw-r--r-- root root 4096 2020-12-16 02:08 max_user_freq
-r--r--r-- root root 4096 2020-12-16 02:08 name
drwxr-xr-x root root 2020-12-16 02:04 power
-r--r--r-- root root 4096 2020-12-16 02:08 since_epoch
lrwxrwxrwx root root 2020-12-16 02:08 subsystem -> ../../../../../../../../class/rtc
-r--r--r-- root root 4096 2020-12-16 02:08 time
-rw-r--r-- root root 4096 2020-12-16 02:04 uevent
-rw-r--r-- root root 4096 2020-12-16 02:08 wakealarm
root@freescale:/sys/class/rtc/rtc0 # ls -al /dev/rtc0
crw-r----- system system 254, 0 2020-12-16 02:04 rtc0
root@freescale:/sys/class/rtc/rtc0 #
RTC application & other driver
1.应用程序可通过设备节点/dev/rtc0访问rtc设备,通过ioctl获取跟设置rtc时间,/linux-4.4/drivers/rtc/rtc-dev.c
static long rtc_dev_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
...
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);
...
}
函数太长不全贴了,这里以set rtc time为例,指定cmd值为RTC_SET_TIME,即可进入内核,继而调用函数rtc_set_time()
2.函数rtc_set_time()的目的是设置rtc时间,该函数已在内核导出,在其他驱动中均可使用,/linux-4.4/drivers/rtc/interface.c
该文件内部同样导出了其他接口,供应用层跟其他驱动使用。
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
{
int err;
err = rtc_valid_tm(tm);
if (err != 0)
return err;
err = mutex_lock_interruptible(&rtc->ops_lock);
if (err)
return err;
if (!rtc->ops)
err = -ENODEV;
else if (rtc->ops->set_time)
err = rtc->ops->set_time(rtc->dev.parent, tm);
else if (rtc->ops->set_mmss64) {
time64_t secs64 = rtc_tm_to_time64(tm);
err = rtc->ops->set_mmss64(rtc->dev.parent, secs64);
} else if (rtc->ops->set_mmss) {
time64_t secs64 = rtc_tm_to_time64(tm);
err = rtc->ops->set_mmss(rtc->dev.parent, secs64);
} else
err = -EINVAL;
pm_stay_awake(rtc->dev.parent);
mutex_unlock(&rtc->ops_lock);
/* A timer might have just expired */
schedule_work(&rtc->irqwork);
return err;
}
EXPORT_SYMBOL_GPL(rtc_set_time);
进入函数内部,我们可以看到最终访问了rtc device的ops,这就是第一步RTC HW Driver中提供的ops,通过这个接口最终操作RTC。
hctosys
不得不提到的一个驱动就是hctosys(hardware clock to system),顾名思义是将hw clock中的时间向系统同步,此处hw clock指的就是RTC,调用时机为late_initcall,路径/linux-4.4/drivers/rtc/hctosys.c;
static int __init rtc_hctosys(void)
{
int err = -ENODEV;
struct rtc_time tm;
struct timespec64 tv64 = {
.tv_nsec = NSEC_PER_SEC >> 1,
};
struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
if (rtc == NULL) {
pr_info("unable to open rtc device (%s)n",
CONFIG_RTC_HCTOSYS_DEVICE);
goto err_open;
}
err = rtc_read_time(rtc, &tm);
if (err) {
dev_err(rtc->dev.parent,
"hctosys: unable to read the hardware clockn");
goto err_read;
}
tv64.tv_sec = rtc_tm_to_time64(&tm);
err = do_settimeofday64(&tv64);
dev_info(rtc->dev.parent,
"setting system clock to "
"%d-%02d-%02d %02d:%02d:%02d UTC (%lld)n",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec,
(long long) tv64.tv_sec);
err_read:
rtc_class_close(rtc);
err_open:
rtc_hctosys_ret = err;
return err;
}
late_initcall(rtc_hctosys);
1.通过interface中接口rtc_class_open()获取系统中注册好的RTC设备,参数的部分是通过编译config配置得到的
$ grep -nr CONFIG_RTC_HCTOSYS_DEVICE .config
3217:CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
2.成功获取得到RTC设备后,调用rtc_read_time获取RTC时间;
3.将得到的时间进行转换,struct rtc_time tm->struct timespec64,最终通过do_settimeofday64函数将时间同步给系统,也就是上文提到的wall time;
4.打印日志,关闭设备。
参考链接:https://blog.csdn.net/u013686019/article/details/57126940
最后
以上就是眯眯眼野狼为你收集整理的Linux RTC简析及使用背景简述RTC HW driverRTC subsystemRTC application & other driverhctosys的全部内容,希望文章能够帮你解决Linux RTC简析及使用背景简述RTC HW driverRTC subsystemRTC application & other driverhctosys所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复