ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

【转载】Linux RTC简析及使用

2022-07-20 10:33:57  阅读:143  来源: 互联网

标签:RTC rtc dev 简析 Linux device class struct


转载原文地址: https://blog.csdn.net/spongebob1912/article/details/111174475

 

背景

对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: %d\n", 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 %s\n",
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 class\n");
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 clock\n");
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

 

标签:RTC,rtc,dev,简析,Linux,device,class,struct
来源: https://www.cnblogs.com/cxt-janson/p/16496939.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有