我是靠谱客的博主 眯眯眼野狼,最近开发中收集的这篇文章主要介绍Linux RTC简析及使用背景简述RTC HW driverRTC subsystemRTC application & other driverhctosys,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

背景

对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所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部