Linux驱动编程 - RTC子系统

目录

简介:

一、代码分析

1、RTC子系统初始化

2、注册RTC设备驱动

[2.1 rtc_dev_prepare(rtc)](#2.1 rtc_dev_prepare(rtc))

3、总结

[二、ds1302 驱动分析](#二、ds1302 驱动分析)

三、rtc设置和测试工具

1、date读/写系统时间

2、hwclock读/写RTC


简介:


Linux中RTC设备驱动是一个标准的字符设备驱动,应用程序通过 open、 release、 read、 write 和 ioctl 等函数完成对 RTC 设备的操作。

rtc子系统分为三部分:

  • rtc core:负责rtc设备注册注销;对用户空间提供rtc字符设备文件,以及rtc类sysfs接口;
  • rtc driver:将rtc设备注册到rtc子系统,提供针对rtc设备的底层操作函数集;
  • 用户空间sysfs节点:/dev/rtcX字符设备文件,以及其他调试接口;

图中,RTC Core已经在kernel中实现了,它初始化RTC子系统并向用户空间提供 file_operations 操作集(open、 read、 write 和 ioctl 等)。我们只需实现 RTC Driver(RTC 设备驱动) 和 设备树中对RTC的配置,RTC Driver中实现对RTC芯片的底层操作集。

一、代码分析


Linux 内核将 RTC 设备抽象为 rtc_device 结构体,因此 RTC 设备驱动就是申请并初始化 rtc_device,最后将 rtc_device 注册到 Linux 内核里面。

cpp 复制代码
/* 路径:include/linux/rtc.h */
struct rtc_device
{
	struct device dev;
	struct module *owner;

	int id;                            //当前rtc设备在rtc子系统的子序号
	char name[RTC_DEVICE_NAME_SIZE];

	const struct rtc_class_ops *ops;
	struct mutex ops_lock;

	struct cdev char_dev;             //rtc设备对应的字符设备
	unsigned long flags;

	unsigned long irq_data;
	spinlock_t irq_lock;
	wait_queue_head_t irq_queue;       //和用户空间同步的poll调用所使用的等待队列,由中断唤醒
	struct fasync_struct *async_queue; //和用户空间同步基于文件的fasync调用,由中断触发

	struct rtc_task *irq_task;
	spinlock_t irq_task_lock;
	int irq_freq;
	int max_user_freq;

	struct timerqueue_head timerqueue;
	struct rtc_timer aie_timer;
	struct rtc_timer uie_rtctimer;
	struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */
	int pie_enabled;
	struct work_struct irqwork;
	/* Some hardware can't support UIE mode */
	int uie_unsupported;

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
	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
};

重点关注 struct rtc_class_ops *ops 成员变量,rtc_class_ops 为 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 RTC 设备写入新的时间等。因此,rtc_class_ops 操作集需要用户根据所使用的 RTC 设备自己实现。

cpp 复制代码
/* 路径:include/linux/rtc.h */
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_mmss64)(struct device *, time64_t secs);
	int (*set_mmss)(struct device *, unsigned long secs);
	int (*read_callback)(struct device *, int data);
	int (*alarm_irq_enable)(struct device *, unsigned int enabled);
};

注意:rtc_class_ops 中的这些函数只是最底层对 RTC 设备的操作函数,并不是提供给应用层的file_operations 函数操作集。Linux 内核提供了一个 RTC 通用字符设备驱动文件 drivers/rtc/rtc-dev.c,rtc-dev.c 文件提供了所有 RTC 设备共用的 file_operations 函数操作集。

1、RTC子系统初始化


drivers/rtc/class.c 中运行 rtc_init() 函数,实现对RTC子系统的初始化。此部分Linux已经做好了,不需要我们实现。

cpp 复制代码
/* linux/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();
	return 0;
}
subsys_initcall(rtc_init);

RTC子系统初始化,主要分配rtc_class类,以及rtc设备的rtc_devt。alloc_chrdev_region 用来动态分配号,调用过程如下:

cpp 复制代码
rtc_init
--->class_create(THIS_MODULE, "rtc")         //创建rtc_class类
--->rtc_dev_init()
--->--->alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc")    //为rtc设备分配子设备号范围0~15。主设备号随机分配。最终结果放入rtc_devt

2、注册RTC设备驱动


devm_rtc_device_register() 函数用来注册RTC设备驱动。编写RTC设备驱动时,我们需要先实现 struct rtc_class_ops 结构体,它是对RTC设备最底层的操作函数集合。然后调用 devm_rtc_device_register() 将 rtc_class_ops 注册到内核中。

cpp 复制代码
/* linux/drivers/rtc/class.c */
/*
参数:
    dev: 设备
    name:设备名字
    ops: RTC 底层驱动函数集
    owner:驱动模块拥有者
*/
struct rtc_device *devm_rtc_device_register(struct device *dev,
					    const char *name,
					    const struct rtc_class_ops *ops,
					    struct module *owner)
{
	struct rtc_device *rtc;
	int err;

	rtc = devm_rtc_allocate_device(dev);    //分配 struct rtc_device 结构
	if (IS_ERR(rtc))
		return rtc;

	rtc->ops = ops;        //设置 rtc_class_ops 底层操作

	err = __rtc_register_device(owner, rtc);    //注册rtc设备
	if (err)
		return ERR_PTR(err);

	return rtc;
}

rtc->ops = ops 设置 rtc_class_ops 底层操作集。主要分析下 __rtc_register_device()

cpp 复制代码
int __rtc_register_device(struct module *owner, struct rtc_device *rtc)
{
    /* ... ... */
    dev_set_name(&rtc->dev, "rtc%d", id);    //设置device名字

	rtc_dev_prepare(rtc);					//初始化cdev结构体,file_operations

	err = cdev_device_add(&rtc->char_dev, &rtc->dev);	//添加设备到内核 cdev_add、device_add 注册设备
	/* ... ... */

	rtc_proc_add_device(rtc);

    /* ... ... */

#ifdef CONFIG_RTC_HCTOSYS_DEVICE
	if (!strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE))
		rtc_hctosys();
#endif

	return 0;
}

rtc_dev_prepare(rtc) 和 cdev_device_add 其实就是实现字符设备那套固定流程,初始化cdev并添加到kernel,注册device等。

2.1 rtc_dev_prepare(rtc)

rtc_dev_prepare先分配设备号,调用cdev_init初始化cdev并添加file_operations操作集。file_operations 操作集提供给应用层调用:

cpp 复制代码
/* 路径:linux/drivers/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)
{
	if (!rtc_devt)
		return;

	if (rtc->id >= RTC_DEV_MAX) {
		dev_dbg(&rtc->dev, "too many RTC devices\n");
		return;
	}

	rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
	INIT_WORK(&rtc->uie_task, rtc_uie_task);
	timer_setup(&rtc->uie_timer, rtc_uie_timer, 0);
#endif

	cdev_init(&rtc->char_dev, &rtc_dev_fops);
	rtc->char_dev.owner = rtc->owner;
}

应用程序可以通过 ioctl 函数来设置/读取时间、设置/读取闹钟等操作,那么对应的 rtc_dev_ioctl 函数就会执行, rtc_dev_ioctl 最终会通过操作 rtc_class_ops 底层操作集中的 read_time、 set_time 等函数来对具体 RTC 设备的读写操作。

cpp 复制代码
/* linux/drivers/rtc/dev.c */
static long rtc_dev_ioctl(struct file *file,
			  unsigned int cmd, unsigned long arg)
{
	/* ... ... */
	case RTC_RD_TIME:
		mutex_unlock(&rtc->ops_lock);

		err = rtc_read_time(rtc, &tm);    //最终会调用 rtc->ops->read_time
		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);
    /* ... ... */
}

以读RTC为例,rtc_read_time会调用到 __rtc_read_time()

cpp 复制代码
/* 路径:linux/drivers/rtc/interface.c */
static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{
	... ...
    /* devm_rtc_device_register() 中将 rtc->ops 赋值为我们实现的 rtc_class_ops */
	err = rtc->ops->read_time(rtc->dev.parent, tm);
	... ...
}

__rtc_read_time 函数会通过调用 rtc_class_ops 中的read_time 来从 RTC 设备中获取当前时间。 rtc_dev_ioctl 函数对其他的命令处理都是类似的。

3、结论

3.1 编写RTC驱动时,我们只用实现 rtc_class_ops ,然后调用devm_rtc_device_register() 将其注册到 Linux 内核中即可

3.2 Linux 内核中 RTC 驱动调用流程如图

注意,系统启动时注册RTC设备调用 rtc_hctosys,会将RTC时间设置到系统时间:

cpp 复制代码
rtc_hctosys
--->rtc_read_time
--->rtc_tm_to_time64
--->do_settimeofday64

二、ds1302 驱动分析


Linux中自带的linux/drivers/rtc/rtc-ds1302.c驱动代码如下:

cpp 复制代码
#include <linux/bcd.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/rtc.h>
#include <linux/spi/spi.h>

#define DRV_NAME	"rtc-ds1302"

#define	RTC_CMD_READ	0x81		/* Read command */
#define	RTC_CMD_WRITE	0x80		/* Write command */

#define	RTC_CMD_WRITE_ENABLE	0x00		/* Write enable */
#define	RTC_CMD_WRITE_DISABLE	0x80		/* Write disable */

#define RTC_ADDR_RAM0	0x20		/* Address of RAM0 */
#define RTC_ADDR_TCR	0x08		/* Address of trickle charge register */
#define RTC_CLCK_BURST	0x1F		/* Address of clock burst */
#define	RTC_CLCK_LEN	0x08		/* Size of clock burst */
#define	RTC_ADDR_CTRL	0x07		/* Address of control register */
#define	RTC_ADDR_YEAR	0x06		/* Address of year register */
#define	RTC_ADDR_DAY	0x05		/* Address of day of week register */
#define	RTC_ADDR_MON	0x04		/* Address of month register */
#define	RTC_ADDR_DATE	0x03		/* Address of day of month register */
#define	RTC_ADDR_HOUR	0x02		/* Address of hour register */
#define	RTC_ADDR_MIN	0x01		/* Address of minute register */
#define	RTC_ADDR_SEC	0x00		/* Address of second register */

static int ds1302_rtc_set_time(struct device *dev, struct rtc_time *time)
{
	struct spi_device	*spi = dev_get_drvdata(dev);
	u8		buf[1 + RTC_CLCK_LEN];
	u8		*bp;
	int		status;

	/* spi将时间写入ds1302 */
	/* Enable writing */
	bp = buf;
	*bp++ = RTC_ADDR_CTRL << 1 | RTC_CMD_WRITE;
	*bp++ = RTC_CMD_WRITE_ENABLE;

	status = spi_write_then_read(spi, buf, 2,
			NULL, 0);
	if (status)
		return status;

	/* Write registers starting at the first time/date address. */
	bp = buf;
	*bp++ = RTC_CLCK_BURST << 1 | RTC_CMD_WRITE;

	*bp++ = bin2bcd(time->tm_sec);
	*bp++ = bin2bcd(time->tm_min);
	*bp++ = bin2bcd(time->tm_hour);
	*bp++ = bin2bcd(time->tm_mday);
	*bp++ = bin2bcd(time->tm_mon + 1);
	*bp++ = time->tm_wday + 1;
	*bp++ = bin2bcd(time->tm_year % 100);
	*bp++ = RTC_CMD_WRITE_DISABLE;

	/* use write-then-read since dma from stack is nonportable */
	return spi_write_then_read(spi, buf, sizeof(buf),
			NULL, 0);
}

static int ds1302_rtc_get_time(struct device *dev, struct rtc_time *time)
{
	struct spi_device	*spi = dev_get_drvdata(dev);
	u8		addr = RTC_CLCK_BURST << 1 | RTC_CMD_READ;
	u8		buf[RTC_CLCK_LEN - 1];
	int		status;

	/* spi从ds1302读取时间数据 */
	status = spi_write_then_read(spi, &addr, sizeof(addr),
			buf, sizeof(buf));
	if (status < 0)
		return status;

	/* Decode the registers */
	time->tm_sec = bcd2bin(buf[RTC_ADDR_SEC]);
	time->tm_min = bcd2bin(buf[RTC_ADDR_MIN]);
	time->tm_hour = bcd2bin(buf[RTC_ADDR_HOUR]);
	time->tm_wday = buf[RTC_ADDR_DAY] - 1;
	time->tm_mday = bcd2bin(buf[RTC_ADDR_DATE]);
	time->tm_mon = bcd2bin(buf[RTC_ADDR_MON]) - 1;
	time->tm_year = bcd2bin(buf[RTC_ADDR_YEAR]) + 100;

	return 0;
}

static const struct rtc_class_ops ds1302_rtc_ops = {
	.read_time	= ds1302_rtc_get_time,
	.set_time	= ds1302_rtc_set_time,
};

static int ds1302_probe(struct spi_device *spi)
{
	struct rtc_device	*rtc;
	u8		addr;
	u8		buf[4];
	u8		*bp;
	int		status;

	/* spi初始化ds1302 */
	... ...

	spi_set_drvdata(spi, spi);

	rtc = devm_rtc_device_register(&spi->dev, "ds1302",
			&ds1302_rtc_ops, THIS_MODULE);
	if (IS_ERR(rtc)) {
		status = PTR_ERR(rtc);
		dev_err(&spi->dev, "error %d registering rtc\n", status);
		return status;
	}

	return 0;
}

static int ds1302_remove(struct spi_device *spi)
{
	spi_set_drvdata(spi, NULL);
	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id ds1302_dt_ids[] = {
	{ .compatible = "maxim,ds1302", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ds1302_dt_ids);
#endif

static struct spi_driver ds1302_driver = {
	.driver.name	= "rtc-ds1302",
	.driver.of_match_table = of_match_ptr(ds1302_dt_ids),
	.probe		= ds1302_probe,
	.remove		= ds1302_remove,
};

module_spi_driver(ds1302_driver);

MODULE_DESCRIPTION("Dallas DS1302 RTC driver");
MODULE_AUTHOR("Paul Mundt, David McCullough");
MODULE_LICENSE("GPL v2");

rtc_class_ops结构体中实现了 ds1302_rtc_get_time 和 ds1302_rtc_set_time 分别读取/设置时间。读取/设置时间就是通过spi总线读写ds1302的寄存器。最终,ds1302_probe() 函数调用 devm_rtc_device_register() 将 rtc_class_ops 注册到RTC子系统中。我们可以参照此代码来添加自己的RTC设备驱动。

三、rtc设置和测试工具


1、date读/写系统时间

$ date -s "2024-10-14 10:10:10" #设置当前系统时间

" date -s"命令仅仅是将当前系统时间设置了,此时间还没有写入到RTC设备中

2、hwclock读/写RTC

$ hwclock -w #将当前系统时间设置到RTC硬件中。

$ hwclock -r #读取当前RTC硬件时间

$ hwclock -s #将RTC时间设置到系统时间。

如果要设置RTC时间,先通过date设置系统时间,然后通过hwclock将系统时间设置到rtc硬件中。

$ date -s "2024-10-14 10:10:10"

$ hwclock -s


相关推荐
加载中loading...10 分钟前
Linux线程安全(二)条件变量实现线程同步
linux·运维·服务器·c语言·1024程序员节
well_fly3 小时前
Ubuntu特殊目录
linux·ubuntu
大熊程序猿3 小时前
ubuntu 安装k3s
linux·运维·ubuntu
luoqice3 小时前
CentOS 自启动某个应用
linux·运维·服务器
泠山3 小时前
ubuntu增加swap交换空间
linux·运维·ubuntu
hero_th4 小时前
[Ubuntu] 文件/目录权限更改
linux·ubuntu
花花少年4 小时前
pip在ubuntu下换源
linux·ubuntu·pip
y0ungsheep5 小时前
[GXYCTF 2019]Ping Ping Ping 题解(多种解题方式)
linux·web安全·网络安全·php
海绵波波1075 小时前
Webserver(1.6)Linux系统IO函数
linux·运维·服务器