【转载】Linux RTC简析及使用
作者:互联网
转载原文地址: 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