目录
简介:
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 内核里面。
/* 路径: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 设备自己实现。
/* 路径: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已经做好了,不需要我们实现。
/* 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 用来动态分配号,调用过程如下:
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 注册到内核中。
/* 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()
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 操作集提供给应用层调用:
/* 路径: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 设备的读写操作。
/* 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()
/* 路径: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时间设置到系统时间:
rtc_hctosys
--->rtc_read_time
--->rtc_tm_to_time64
--->do_settimeofday64
二、ds1302 驱动分析
Linux中自带的linux/drivers/rtc/rtc-ds1302.c驱动代码如下:
#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