4.3.12. RTC 调试指南
4.3.12.1. RTC 概述
Linux RTC(Real-Time Clock)实时
在 X5 芯片
RTC 特点
时间与日期记录
RTC 最
当前
时间(小时、分钟、秒) 当前
日期(年、月、日) 星期
信息
闹钟与中断
许多 RTC 设备
周期性唤醒
RTC 可以
时间更新
RTC 允许hwclock)来
低功耗模式
为了
硬件接口
Linux RTC 框架
RTC 功能原理
RTC 通过
以下
时间
计数器 :RTC 内部
集成 有 一个计时 电路 ,通常由 一个高精度 晶振 提供时钟 信号。常见 的 晶振 频率 为32.768 kHz,它 的 频率 非常 稳定,适合 用于 长时间 的 时间 计量。 RTC 的
计时 电路 通过 晶振 生成 稳定 的 时钟 信号,通常 是 秒 脉冲(1Hz),然后 将 其 累加。每秒钟,RTC 会 增加 计数,直到 形成 分钟、小时、日期 等。
日期
和 :时间 的 维护 RTC 将
累加 的 秒、分钟、小时、日期 等 信息 存储 在 内部 的寄存器中。这些 寄存器 可以 通过 系统 与 RTC 通信 来 读取 和 设置 当前 时间 或 闹钟。 一些 RTC 还
具有 存储 年份、月份 和 星期 几 等 信息。
备用
电池 :为了
在 主 系统 断电 时 保持 时间,RTC 通常 配有 一个备用 电池 。当主电源 断开 时,备用 电池 为 RTC 提供 电力,确保 其 内部 时钟 仍然 继续 工作,而 不会 丢失 时间 信息。 在
这种 情况 下,RTC 会 继续 运行,但 主控 芯片 可能 处于 低功耗 或 关闭 状态。
闹钟
功能 :RTC 模块
通常 内置 闹钟 功能,允许 设置 特定 的 时间 点,当 计时 达到 设置 的 时间 时,RTC 会 触发 一个 中断 信号 或 发出 警告。 主控
芯片 可以 根据 这个 中断 信号 执行 相应 的 操作,如 唤醒 系统、启动 某个 任务 等。
RTC 典型应用
RTC 时间保持
Linux 的

流程
系统
关闭 :时 系统
在 关闭 前 将 当前 的 系统 时间 设置 到 RTC 中。 内核
通过 RTC 驱动 将 时间 写入 RTC 硬件。 RTC 硬件
确认 时间 写入 成功。 RTC 驱动
将 确认 信息 返回 给 内核。 内核
将 确认 信息 返回 给 系统。
系统
关闭 :后 系统
关闭 后,备用 电池 开始 供电 给 RTC 硬件。 RTC 硬件
在 备用 电池 供电 下 继续 运行,持续 计时。
系统
下次 :上电时 系统
重新 上电后,通过 内核 从 RTC 硬件 获取 时间。 内核
通过 RTC 驱动 读取 RTC 硬件 中 的 时间。 RTC 硬件
返回 当前 时间 给 RTC 驱动。 RTC 驱动
将 时间 返回 给 内核。 内核
将 时间 设置 为 系统 时间。
RTC 定时唤醒
RTC 的

流程
初始化
阶段: 主控芯片 首先 与 RTC 模块 进行 初始化,设置 当前 时间 和 闹钟(定时 提醒)。这 一步 通常 发生 在 系统 上电后,主控 芯片 通过 与 RTC 模块 的 通信 设置 时间 和 闹钟 参数。 切换
至 完成低功耗 模式: 初始化 后,RTC 模块 切换 至 低功耗 模式,依赖于 外接 电池 持续保持 时间 和 闹钟 信息。外接 电池 提供 必要 的 电源,以 确保 RTC 模块 在 主控 芯片 关闭 或 进入 低功耗 状态 时,依然 能 保持 运行。 持续
运行 RTC 模块阶段(Loop): 进入 一个 循环 模式,持续 地 进行 计时(计数器 累加)。该 过程 是 低功耗 的,RTC 模块 会 定期 更新 其 内部 计数器。 闹钟
触发 在事件: 特定 时间 点,RTC 模块 的 闹钟 触发 条件 被 满足 时,RTC 模块 会 向 主控 芯片 发送 中断 信号,通知 主控 芯片 该 时间 到达。 主控 芯片 接收 到 中断 信号 后,会 执行 预定 的 唤醒 任务,比如 唤醒 系统、处理 特定 任务 或 执行 特定 功能。
4.3.12.2. RTC 驱动代码
Linux RTC 驱动框架
在 Linux 中,RTC 设备
用户
空间 位于最 顶层,包含 用户 工具 和 内核 空间 的 接口。 内核
空间 位于中间层,分为 三个 部分: 接口
层 :与用户 空间 直接 交互。 RTC Core:管理 RTC 设备
的 核心 模块。 RTC 驱动
层 :与硬件 层 直接 交互。
硬件
层 位于最底层,表示 具体 的 RTC 硬件 设备。
Linux RTC 驱动

下面
RTC 用户
用户
用户
工具 :hwclock:硬件时钟 操作 工具。 date:系统时间 操作 工具。 测试工具:如
rtctest.c,用于测试 RTC 驱动 的 ioctl接口。
字符
设备 :接口 /dev/rtcN:字符设备 节点,支持 open、read、write和ioctl操作。
sysfs 接口:
/sys/class/rtc/rtcN:提供只读 属性,如 时间、闹钟 等,允许 用户 空间 访问 RTC 设备 的 某些 属性。
procfs 接口:
/proc/driver/rtc:提供系统 时钟 RTC 的 信息,如果 系统 没有 专用 的 RTC,则 默认 使用 rtc0。
RTC 内核
内核
接口
层(Interface Layer) :管理
字符 设备 接口。 管理 sysfs 和 procfs 属性。
RTC Core(核心层):
设备
管理 :设备
注册 :通过与 注销 register和unregister函数进行 RTC 设备 的 注册 和 注销。 字符
设备 :通过抽象 dev.c将 RTC 设备抽象 为 通用 的 字符 设备,提供 文件 操作 函数。 sysfs 和 procfs 管理:通过
sysfs.c和proc.c管理 RTC 设备的 sysfs 和 procfs 属性。
时间
转换 :通过lib.c提供 RTC 时间与 系统 时间 之间 的 转换。
RTC 驱动
层(Driver Layer) :硬件
抽象 :操作
函数 :通过集 rtc_class_ops结构体 定义 的 函数 集,提供 对 RTC 硬件 的 底层 操作,如 读取 时间、设置 时间、读取 和 设置 闹钟 等。 硬件
初始化 :初始化 RTC 硬件,配置时钟 源、中断 等。 中断
处理 :处理 RTC 产生的 中断,如 闹钟 中断 和 周期性 中断。
数据结构:
struct rtc_device:描述 RTC 设备。struct rtc_class_ops:定义底层 操作 函数。
RTC 硬件
RTC 硬件:
硬件
时钟 芯片(如 PCF8563) 晶振
外部
电池
RTC 驱动代码说明
本
rtc driver Layer:将 RTC 设备
注册 到 RTC 子系统,并 提供 针对 RTC 设备 的 底层 操作 函数 集。 rtc core:负责 RTC 设备
的 注册 与 注销,向 用户 空间 提供 RTC 字符 设备 文件,并 实现 RTC 类 的 sysfs 等 接口。 用户
空间 :包括接口 ioctl在内的 接口。
RTC driver Layer 代码说明
RTC 驱动rtc_device结构rtc_device。
Linux 内核
// kernel/include/linux/rtc.h
struct rtc_device {
struct device dev;
struct module *owner;
int id;
const struct rtc_class_ops *ops;
struct mutex ops_lock;
struct cdev char_dev;
unsigned long flags;
unsigned long irq_data;
spinlock_t irq_lock;
wait_queue_head_t irq_queue;
struct fasync_struct *async_queue;
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;
/*
* This offset specifies the update timing of the RTC.
*
* tsched t1 write(t2.tv_sec - 1sec)) t2 RTC increments seconds
*
* The offset defines how tsched is computed so that the write to
* the RTC (t2.tv_sec - 1sec) is correct versus the time required
* for the transport of the write and the time which the RTC needs
* to increment seconds the first time after the write (t2).
*
* For direct accessible RTCs tsched ~= t1 because the write time
* is negligible. For RTCs behind slow busses the transport time is
* significant and has to be taken into account.
*
* The time between the write (t1) and the first increment after
* the write (t2) is RTC specific. For a MC146818 RTC it's 500ms,
* for many others it's exactly 1 second. Consult the datasheet.
*
* The value of this offset is also used to calculate the to be
* written value (t2.tv_sec - 1sec) at tsched.
*
* The default value for this is NSEC_PER_SEC + 10 msec default
* transport time. The offset can be adjusted by drivers so the
* calculation for the to be written value at tsched becomes
* correct:
*
* newval = tsched + set_offset_nsec - NSEC_PER_SEC
* and (tsched + set_offset_nsec) % NSEC_PER_SEC == 0
*/
unsigned long set_offset_nsec;
unsigned long features[BITS_TO_LONGS(RTC_FEATURE_CNT)];
time64_t range_min;
timeu64_t range_max;
time64_t start_secs;
time64_t offset_secs;
bool set_start_time;
#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
};
RTC 硬件ops函数rtc_device结构struct rtc_class_ops *ops成员rtc_class_ops是 RTC 设备
// kernel/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
*/
struct rtc_class_ops {
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 (*alarm_irq_enable)(struct device *, unsigned int enabled);
int (*read_offset)(struct device *, long *offset);
int (*set_offset)(struct device *, long offset);
int (*param_get)(struct device *, struct rtc_param *param);
int (*param_set)(struct device *, struct rtc_param *param);
};
通过rtc_class_ops具体
以 BSP 源码
// kernel/drivers/rtc/rtc-pcf8563.c
static const struct rtc_class_ops pcf8563_rtc_ops = {
.ioctl = pcf8563_rtc_ioctl,
.read_time = pcf8563_rtc_read_time,
.set_time = pcf8563_rtc_set_time,
.read_alarm = pcf8563_rtc_read_alarm,
.set_alarm = pcf8563_rtc_set_alarm,
.alarm_irq_enable = pcf8563_irq_enable,
};
这些rtc_class_ops结构
注意:rtc_class_ops中file_operations操作drivers/rtc/rtc-dev.c,该file_operations操作
rtc_init 函数
// kernel/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_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 中。
RTC core 代码说明
RTC core 层
RTC 驱动rtc_class_ops 结构devm_rtc_device_register 向 Linux 内核
相关
//kernel/drivers/rtc/class.c
/**
* devm_rtc_device_register - resource managed rtc_device_register()
* @dev: the device to register
* @name: the name of the device (unused)
* @ops: the rtc operations structure
* @owner: the module owner
*
* @return a struct rtc on success, or an ERR_PTR on error
*
* Managed rtc_device_register(). The rtc_device returned from this function
* are automatically freed on driver detach.
* This function is deprecated, use devm_rtc_allocate_device and
* rtc_register_device instead
*/
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);
if (IS_ERR(rtc))
return rtc;
rtc->ops = ops;
err = __devm_rtc_register_device(owner, rtc);
if (err)
return ERR_PTR(err);
return rtc;
}
EXPORT_SYMBOL_GPL(devm_rtc_device_register);
这里rtc->ops = ops 即rtc_class_ops 底层
下面__devm_rtc_register_device,该
//kernel/drivers/rtc/class.c
int __devm_rtc_register_device(struct module *owner, struct rtc_device *rtc)
{
struct rtc_wkalrm alrm;
int err;
if (!rtc->ops) {
dev_dbg(&rtc->dev, "no ops set\n");
return -EINVAL;
}
if (!rtc->ops->set_alarm)
clear_bit(RTC_FEATURE_ALARM, rtc->features);
if (rtc->ops->set_offset)
set_bit(RTC_FEATURE_CORRECTION, rtc->features);
rtc->owner = owner;
rtc_device_get_offset(rtc);
/* Check to see if there is an ALARM already set in hw */
err = __rtc_read_alarm(rtc, &alrm);
if (!err && !rtc_valid_tm(&alrm.time))
rtc_initialize_alarm(rtc, &alrm);
rtc_dev_prepare(rtc);
err = cdev_device_add(&rtc->char_dev, &rtc->dev); // 将 RTC 设备添加为字符设备
if (err) {
set_bit(RTC_NO_CDEV, &rtc->flags);
dev_warn(rtc->dev.parent, "failed to add char device %d:%d\n",
MAJOR(rtc->dev.devt), rtc->id);
} else {
dev_dbg(rtc->dev.parent, "char device (%d:%d)\n",
MAJOR(rtc->dev.devt), rtc->id);
}
rtc_proc_add_device(rtc); // 添加 RTC 设备到 proc 文件系统
dev_info(rtc->dev.parent, "registered as %s\n",
dev_name(&rtc->dev));
#ifdef CONFIG_RTC_HCTOSYS_DEVICE
if (!strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE))
rtc_hctosys(rtc);
#endif
return devm_add_action_or_reset(rtc->dev.parent,
devm_rtc_unregister_device, rtc);
}
EXPORT_SYMBOL_GPL(__devm_rtc_register_device);
其中rtc_dev_prepare函数
// kernel/drivers/rtc/dev.c
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;
}
rtc_dev_prepare 函数
初始化
设备 :号 为 RTC 设备
分配 一个 唯一 的 设备 号( devt),这是内核 用来 识别 设备 的 一个 标识符。设备 号 由主 设备 号 和 次 设备 号 组成,其中 主 设备 号 通常 用于 标识 设备 类型,次 设备 号 用于 区分 同一 类型 的 多个 设备 实例。
初始化
字符 :设备 结构 初始化 RTC 设备
的 字符 设备 结构( cdev),这个结构 包含 了 文件 操作 函数( file_operations),这些函数 定义 了 用户 空间 如何 与 设备 文件 交互。例如,当 用户 空间 程序 打开、读取、写入 或 执行 I/O 控制 操作(ioctl)时,内核 会 调用 这些 函数。
设置
文件 :操作 将
rtc_dev_fops(一个file_operations结构体)与 RTC 设备 的 字符 设备 结构 关联 起来。这样,当 用户 空间 程序 对 设备 文件 进行 操作 时,内核 就 会 调用 这些 预定 义 的 函数 来 执行 相应 的 硬件 操作。
注册
设备 :通过
调用 cdev_device_add函数将 RTC 设备 的 字符 设备 添加 到 系统 中,这样 用户 空间 程序 就 可以 通过 设备 文件(如 /dev/rtcN)来访问 RTC 设备 了。
初始化
其他 :功能 根据
需要 初始化 其他 功能,如 定时器,一般 用于 处理 特定 的 RTC 功能。
用户层接口代码说明
procfs 接口函数:
调用
rtc_proc_add_device函数将 RTC 设备 添加 到 proc 文件系统。 // kernel/drivers/rtc/proc.c void rtc_proc_add_device(struct rtc_device *rtc) { if (is_rtc_hctosys(rtc)) proc_create_single_data("driver/rtc", 0, NULL, rtc_proc_show, rtc); }
rtc_proc_add_device函数的 主要 作用 是 将 RTC 设备 的 信息 暴露 给 用户 空间,通过 /proc文件系统提供 一个 接口,使得 用户 空间 程序 可以 方便 地 读取 RTC 设备 的 状态 和 配置 信息。这样,用户 空间 程序 就 能 以 一种 标准化 的 方式 获取 内核 中 设备 的 信息,而 无需 直接 访问 设备 驱动 的 内部 数据结构。 rtc_proc_add_device函数执行 成功 后 会 在 /proc目录下 创建 一个 名为 driver/rtc的文件,这个 文件 是 与 rtc 设备 关联 的,其中 内容 一般 如下: root@buildroot:~# cat /proc/driver/rtc rtc_time : 00:01:00 rtc_date : 1970-01-01 alrm_time : 00:00:00 alrm_date : 1970-01-01 alarm_IRQ : no alrm_pending : no update IRQ enabled : no periodic IRQ enabled : no periodic IRQ frequency : 1 max user IRQ frequency : 64 24hr : yes
/proc/driver/rtc文件提供 了 关于 RTC 设备 状态 的 详细信息,该 文件 中 信息 解释 如下: 查看 RTC 时间
文件
中 显示 的 rtc_time和rtc_date分别表示 RTC 设备 当前 的 小时、分钟、秒 和 年、月、日。这 对于 验证 RTC 设备 是否 正确 运行 非常 有用。
检查
闹钟 设置 alrm_time和alrm_date显示了 RTC 设备 的 闹钟 设置。如果 这些 值 不是 预期 的 值,可能 需要 重新配置 闹钟。 alarm_IRQ表示是否 有 中断请求(IRQ)与 闹钟 相关联。如果 设置 为 yes,则表示 RTC 设备 支持 闹钟 中断。 alrm_pending表示是否 有待 处理 的 闹钟 事件。
监控
中断 状态 update IRQ enabled和periodic IRQ enabled显示了 RTC 设备 是否 启用 了 更新 中断 和 周期性 中断。这些 中断 可以 用于 定时 任务 或 事件 触发。 periodic IRQ frequency和max user IRQ frequency提供了 关于 周期性 中断 频率 的 信息,这 对于 需要 精确 定时 的 应用 非常 重要。
时间
格式 24hr指示 RTC 设备是否 使用24小时 制。这 对于 需要 在12小时 制 和24小时 制 之间 转换 的 应用 来说 是 一个 重要 的 信息。
/proc/driver/rtc文件的 常见 使用 场景 如下: 系统监控:用户
可以 使用 这个 文件 来 监控 RTC 设备 的 状态,确保 时间 同步 和 闹钟 功能 正常 工作。 故障
排除 :如果 RTC 设备出现 问题,如 时间 不 准确 或 闹钟 不 触发,这个 文件 可以 提供 故障 排除 的 线索。 配置
验证 :用户可以 在 配置 RTC 设备 后 检查 这个 文件,以 验证 配置 是否 正确 应用。 应用
开发 :用户在 开发 需要 与 RTC 设备 交互 的 应用 时,可以 参考 这个 文件 来 获取 设备 的 状态 和 能力 信息。
ioctl 接口函数:
调用
cdev_init函数初始化 RTC 设备 的 字符 设备 结构( rtc->char_dev),并将 其 文件 操作 函数指针 设置 为 rtc_dev_fops,这是一个 结构 体,包含 了 RTC 设备 的 文件 操作 函数,当 用户 态 的 程序 打开、读写 或 执行 其他 操作 于 与 设备 相关联 的 文件 时,内核 会 调用 这些 函数。 // kernel/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, #ifdef CONFIG_COMPAT .compat_ioctl = rtc_dev_compat_ioctl, #endif .open = rtc_dev_open, .release = rtc_dev_release, .fasync = rtc_dev_fasync, };
rtc_dev_fops是在 用户 态下 使用 的。在 Linux 内核 中, struct file_operations(通常通过 指向 该 结构 的 指针 fops引用)定义了 一系列 文件 操作 函数,这些 函数 实现 了 对 设备 文件 的 操作。当 用户 态 的 程序 通过 ioctl函数打开、读写 或 执行 其他 操作 于 与 设备 相关联 的 文件 时,内核 会 调用 这些 函数。 rtc_dev_fops结构体 定义 了 一组 与 RTC 设备 交互 的 文件 操作,包括: .owner:指示哪个 模块 拥有 这些 文件 操作。通常 设置 为 THIS_MODULE,表示当前 模块。 .llseek:文件定位 操作, no_llseek表示该 设备 不 支持 常规 的 文件 定位 操作。 .read:从 RTC 设备读取数据 的 函数,用户 态 程序 调用 read()系统调用 时会 使用。 .poll:轮询函数,用于 非 阻塞 I/O 操作,例如,当 使用 select()或poll()系统调用 时。 .unlocked_ioctl:执行设备 特定 操作 的 函数,如 获取 或 设置 RTC 时间。 ioctl()系统调用 会 使用 此 函数。 .compat_ioctl:兼容模式 下 的 ioctl函数,用于支持32位 程序 在64位 系统 上 的 运行。 .open:打开 RTC 设备文件 时 调用 的 函数。 .release:关闭 RTC 设备文件 时 调用 的 函数。 .fasync:用于异步 I/O 通知 的 函数。 这些
操作 函数 在 用户 态 程序 通过 文件系统 与 RTC 设备 交互 时 被 内核 调用。例如,当 用户程序 打开 /dev/rtcN设备文件 时,内核 会 调用 rtc_dev_open函数;当用户程序 读取 该 文件 时,内核 会 调用 rtc_dev_read函数。这里
需要 说明 rtc_dev_ioctl函数,它是 RTC 驱动 中 处理 I/O 控制 操作 的 核心 函数,主要 负责 根据 传入 的 命令 和 参数 执行 相应 的 RTC 操作: kernel/drivers/rtc/dev.c static long rtc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; struct rtc_device *rtc = file->private_data; const struct rtc_class_ops *ops = rtc->ops; struct rtc_time tm; struct rtc_wkalrm alarm; struct rtc_param param; void __user *uarg = (void __user *)arg; err = mutex_lock_interruptible(&rtc->ops_lock); if (err) return err; /* check that the calling task has appropriate permissions * for certain ioctls. doing this check here is useful * to avoid duplicate code in each driver. */ switch (cmd) { case RTC_EPOCH_SET: case RTC_SET_TIME: case RTC_PARAM_SET: if (!capable(CAP_SYS_TIME)) err = -EACCES; break; case RTC_IRQP_SET: if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE)) err = -EACCES; break; case RTC_PIE_ON: if (rtc->irq_freq > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE)) err = -EACCES; break; } …… }当
应用程序 通过 ioctl函数进行 时间 设置/读取、闹钟 设置/读取 等 操作 时, rtc_dev_ioctl函数将 被 调用。 rtc_dev_ioctl最终会 通过 调用 rtc_class_ops底层操作 集中 的 read_time、set_time等函数,执行 对 具体 RTC 设备 的 读写操作。
将

具体
用户
态 :程序 发起
ioctl()系统调用,请求 对 RTC 设备 进行 操作。
RTC 用户
接口 ( :rtc_dev_fops)接收
到 ioctl()调用后,调用 rtc_dev_ioctl()函数处理 具体 的 命令。 RTC 用户
接口 是 用户 态 与 内核 之间 的 桥梁。
RTC core:
如果
是 首次 使用 RTC 设备,调用 __devm_rtc_register_device()进行设备 注册。 调用
rtc_dev_prepare()准备设备 资源。 初始化
rtc_class_ops,设置底层 操作 函数 集。 根据
用户 态 程序 的 命令,调用 对应 的 底层 操作 函数(如 read_time、set_time、set_alarm等)。
RTC 驱动:
提供
底层 操作 函数 集( rtc_class_ops),直接与 RTC 硬件 交互。 执行
具体 的 硬件 操作,并 将 结果 返回 给 RTC core。
返回
结果 :操作
结果 逐层 返回 到 用户 态 程序,用户 态 程序 根据 返回 结果 继续执行。
4.3.12.3. 内核配置
通过 ./bd.sh boot menuconfig 命令
Device Drivers --->
Real Time Clock --->
然后
CONFIG_RTC_LIB=y
CONFIG_RTC_CLASS=y
CONFIG_RTC_HCTOSYS=y
CONFIG_RTC_HCTOSYS_DEVICE="rtc0"
CONFIG_RTC_SYSTOHC=y
CONFIG_RTC_SYSTOHC_DEVICE="rtc0"
CONFIG_RTC_DEBUG=y
CONFIG_RTC_NVMEM=y
CONFIG_RTC_INTF_SYSFS=y
CONFIG_RTC_INTF_PROC=y
CONFIG_RTC_INTF_DEV=y
这些
核心
CONFIG_RTC_LIB=y启用 RTC 库
支持。该 选项 启用 RTC 的 基本操作 函数库,供 其他 模块 或 驱动 使用。
CONFIG_RTC_CLASS=y启用 RTC 类。此
选项 创建 一个 RTC 子系统,使得 内核 能够 管理 多个 RTC 设备,并 向 用户 提供 相关 的 接口。
CONFIG_RTC_HCTOSYS=y启用
系统 从 RTC 设置 时间 的 功能。内核 在 启动 时会 从 指定 的 RTC 设备(由 CONFIG_RTC_HCTOSYS_DEVICE配置指定)读取 时间,并 将 其 用作 系统 时间。
CONFIG_RTC_HCTOSYS_DEVICE="rtc0"设置
从 哪个 RTC 设备 读取 时间。此 选项 指定 内核 启动 时 将 rtc0设备的 时间 作为 系统 时间。
CONFIG_RTC_SYSTOHC=y启用
系统 时间 写回 RTC 的 功能。该 选项 会 让 内核 将 当前 系统 时间 保存 回 指定 的 RTC 设备。
CONFIG_RTC_SYSTOHC_DEVICE="rtc0"设置
哪个 RTC 设备 用于 保存 系统 时间。该 选项 指定 将 系统 时间 写回 rtc0设备。
CONFIG_RTC_DEBUG=y启用 RTC 的
调试信息 输出。开启 后,会 输出 更 多 的 调试信息,有助于 调试 RTC 相关 问题。
CONFIG_RTC_NVMEM=y启用 RTC 的
非易 失性 存储 支持。该 选项 使 RTC 能够 提供 一个 用于 存储配置 信息 的 非易 失性 存储 区域。
RTC 接口
CONFIG_RTC_INTF_SYSFS=y启用 RTC 的 sysfs 接口。这会
在 /sys/class/rtc/目录下 创建 RTC 设备 的 相关 信息,允许 用户 通过 sysfs 接口 与 RTC 设备 交互。
CONFIG_RTC_INTF_PROC=y启用 RTC 的 proc 接口。内核
会 创建 与 RTC 相关 的 /proc/rtc文件,允许通过 proc 文件系统 与 RTC 设备 进行 交互。
CONFIG_RTC_INTF_DEV=y启用 RTC 的
字符 设备 接口。该 选项 允许 RTC 设备 出现 在 /dev/rtcN中,供用户 空间 应用程序(如 hwclock)进行读写操作。
另外, X5 芯片
CONFIG_RTC_DRV_DWAPB=y # /dev/rtc0
CONFIG_RTC_DRV_PCF8563=m # /dev/rtc1
这
CONFIG_RTC_DRV_DWAPB=y启用 DWAPB 驱动,该
驱动 支持 X5 芯片 内置 的 RTC 模块。此 选项 会 使 内核 识别 并 驱动 /dev/rtc0设备,使得 X5 芯片内置 RTC 可以 作为 系统 时间 源 使用。
CONFIG_RTC_DRV_PCF8563=m启用 PCF8563 RTC 驱动,并
将 其 作为 模块 加载( m表示模块)。该 驱动 支持 外置 的 PCF8563 RTC 芯片(X5 EVB 中 通过 I2C 接口 连接)。通过 加载 这个 模块,外置 的 PCF8563 RTC 可以 被 识别 并 作为 /dev/rtc1设备使用。
4.3.12.4. RTC 使用简介
RTC 测试方式
驱动/dev/rtcN 设备
root@buildroot:~# ls /dev/rtc*
/dev/rtc /dev/rtc0 /dev/rtc1
可以/dev/rtc0 和 /dev/rtc1,分别
root@buildroot:~# dmesg | grep rtc
[ 0.577125] dw-rtc 31010000.rtc: prescaler set to: 24000000
[ 0.577462] dw-rtc 31010000.rtc: char device (253:0)
[ 0.577474] dw-rtc 31010000.rtc: registered as rtc0
[ 0.577491] dw-rtc 31010000.rtc: setting system clock to 1970-01-01T00:00:00 UTC (0)
[ 1.306790] rtc-pcf8563 4-0051: pcf8563_probe
[ 1.308632] rtc-pcf8563 4-0051: low voltage detected, date/time is not reliable.
[ 1.309614] rtc rtc1: read_time: fail to read: -22
[ 1.309838] rtc-pcf8563 4-0051: char device (253:1)
[ 1.309854] rtc-pcf8563 4-0051: registered as rtc1
所以,/dev/rtc0 即/dev/rtc1 即
系统/dev/rtc0 作为/dev/rtc:
root@buildroot:~# ls -l /dev/rtc
lrwxrwxrwx 1 root root 4 Jan 1 00:00 /dev/rtc -> rtc0
这里
# 测试命令
date -s "2024/01/01 17:00:00" # 设置系统时间
hwclock -w # 将系统时间写入 RTC
hwclock -r # 读取 RTC 时间,确认时间是否写入成功
date # 读取系统时间
此时/proc 接口来
root@buildroot:~# cat /proc/driver/rtc
rtc_time : 00:15:09
rtc_date : 1970-01-01
alrm_time : 00:00:00
alrm_date : 1970-01-01
alarm_IRQ : no
alrm_pending : no
update IRQ enabled : no
periodic IRQ enabled : no
periodic IRQ frequency : 1
max user IRQ frequency : 64
24hr : yes
root@buildroot:~# date -s "2024/01/01 17:00:00"
Mon Jan 1 17:00:00 UTC 2024
root@buildroot:~# hwclock -w
root@buildroot:~# hwclock -r
Mon Jan 1 17:00:11 2024 0.000000 seconds
root@buildroot:~# date
Mon Jan 1 17:00:14 UTC 2024
root@buildroot:~# cat /proc/driver/rtc
rtc_time : 17:00:20
rtc_date : 2024-01-01
alrm_time : 00:00:00
alrm_date : 1970-01-01
alarm_IRQ : no
alrm_pending : no
update IRQ enabled : no
periodic IRQ enabled : no
periodic IRQ frequency : 1
max user IRQ frequency : 64
24hr : yes
可以rtc_time 已经
外置 RTC 模块 PCF8563 的
RTC 测试接口
下面
set_rtc_time 函数
功能:设置 RTC 的
时间。 参数:
int fd,文件描述符; struct rtc_time rtc_tm,包含要 设置 的 时间。 实现:通过
ioctl系统调用 使用 RTC_SET_TIME命令将 时间 写入 RTC。 错误处理:如果
ioctl调用失败,输出 错误信息 并 关闭 文件 描述符。 代码
示例 :int set_rtc_time(int fd, struct rtc_time rtc_tm) { int ret; ret = ioctl(fd, RTC_SET_TIME, &rtc_tm); if (ret < 0) { printf("<%s %d> ERR: set rtc time failed!\n", __func__, __LINE__); close(fd); return -1; } return 0; }
read_rtc_time 函数
功能:读取 RTC 的
当前 时间。 参数:
int fd,文件描述符; struct rtc_time *rtc_tm,存储读取 到 的 时间。 实现:通过
ioctl系统调用 使用 RTC_RD_TIME命令读取 RTC 的 时间,并 调用 print_rtc_time输出该 时间。 错误处理:如果
ioctl调用失败,输出 错误信息 并 关闭 文件 描述符。 代码
示例 :int read_rtc_time(int fd, struct rtc_time *rtc_tm) { int ret; ret = ioctl(fd, RTC_RD_TIME, rtc_tm); if (ret < 0) { printf("<%s %d> ERR: read rtc time failed!\n", __func__, __LINE__); close(fd); return -1; } print_rtc_time(rtc_tm); return 0; }
alm_set_rtc 函数
功能:设置 RTC 的
闹钟 时间。 参数:
int fd,文件描述符; struct rtc_time rtc_tm,要设置 的 闹钟 时间。 实现:通过
ioctl系统调用 使用 RTC_ALM_SET命令设置 闹钟 时间。 错误处理:如果
ioctl调用失败,输出 错误信息 并 关闭 文件 描述符。 代码
示例 :int alm_set_rtc(int fd, struct rtc_time rtc_tm) { int ret; ret = ioctl(fd, RTC_ALM_SET, &rtc_tm); if (ret < 0) { printf("<%s %d> ERR: set alarm failed!\n", __func__, __LINE__); close(fd); return -1; } return 0; }
alm_read_rtc 函数
功能:读取 RTC 的
闹钟 时间。 参数:
int fd,文件描述符; struct rtc_time *rtc_tm,存储读取 到 的 闹钟 时间。 实现:通过
ioctl系统调用 使用 RTC_ALM_READ命令读取 闹钟 时间,并 调用 print_rtc_time输出该 时间。 错误处理:如果
ioctl调用失败,输出 错误信息 并 关闭 文件 描述符。 代码
示例 :int alm_read_rtc(int fd, struct rtc_time *rtc_tm) { int ret; ret = ioctl(fd, RTC_ALM_READ, rtc_tm); if (ret < 0) { printf("<%s %d> ERR: read alarm failed!\n", __func__, __LINE__); close(fd); return -1; } print_rtc_time(rtc_tm); return 0; }
alm_rtc_enable 函数
功能:启用 RTC 闹钟
中断。 参数:
int fd,文件描述符。 实现:通过
ioctl系统调用 使用 RTC_AIE_ON命令启用 闹钟 中断。 错误处理:如果
ioctl调用失败,输出 错误信息 并 关闭 文件 描述符。 代码
示例 :int alm_rtc_enable(int fd) { int ret; ret = ioctl(fd, RTC_AIE_ON, 0); if (ret < 0) { printf("<%s %d> ERR: enable alarm failed!\n", __func__, __LINE__); close(fd); return -1; } return 0; }
alm_rtc_disable 函数
功能:禁用 RTC 闹钟
中断。 参数:
int fd,文件描述符。 实现:通过
ioctl系统调用 使用 RTC_AIE_OFF命令禁用 闹钟 中断。 错误处理:如果
ioctl调用失败,输出 错误信息 并 关闭 文件 描述符。 代码
示例 :int alm_rtc_disable(int fd) { int ret; ret = ioctl(fd, RTC_AIE_OFF, 0); if (ret < 0) { printf("<%s %d> ERR: disable alarm failed!\n", __func__, __LINE__); close(fd); return -1; } return 0; }
上述RTC_SET_TIME、RTC_RD_TIME、RTC_AIE_OFF 等)都
RTC 测试用例
下面
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <time.h>
#include <linux/rtc.h>
#define RTC_DEVICE "/dev/rtc0"
int set_rtc_time(int fd, struct rtc_time rtc_tm)
{
int ret;
ret = ioctl(fd, RTC_SET_TIME, &rtc_tm);
if (ret < 0) {
printf("<%s %d> ERR: set rtc time failed!\n", __func__, __LINE__);
close(fd);
return -1;
}
return 0;
}
int read_rtc_time(int fd, struct rtc_time *rtc_tm)
{
int ret;
ret = ioctl(fd, RTC_RD_TIME, rtc_tm);
if (ret < 0) {
printf("<%s %d> ERR: read rtc time failed!\n", __func__, __LINE__);
close(fd);
return -1;
}
return 0;
}
int alm_set_rtc(int fd, struct rtc_time rtc_tm)
{
int ret;
ret = ioctl(fd, RTC_ALM_SET, &rtc_tm);
if (ret < 0) {
printf("<%s %d> ERR: set alarm failed!\n", __func__, __LINE__);
close(fd);
return -1;
}
return 0;
}
int alm_read_rtc(int fd, struct rtc_time *rtc_tm)
{
int ret;
ret = ioctl(fd, RTC_ALM_READ, rtc_tm);
if (ret < 0) {
printf("<%s %d> ERR: read alarm failed!\n", __func__, __LINE__);
close(fd);
return -1;
}
return (0);
}
int alm_rtc_enable(int fd)
{
int ret;
ret = ioctl(fd, RTC_AIE_ON, 0);
if (ret < 0) {
printf("<%s %d> ERR: enable alarm failed!\n", __func__, __LINE__);
close(fd);
return -1;
}
return 0;
}
int alm_rtc_disable(int fd)
{
int ret;
ret = ioctl(fd, RTC_AIE_OFF, 0);
if (ret < 0) {
printf("<%s %d> ERR: disable alarm failed!\n", __func__, __LINE__);
close(fd);
return -1;
}
return (0);
}
int main() {
int fd;
struct rtc_time rtc_tm;
struct rtc_wkalrm alarm_tm;
int ret;
int choice;
// 打开 RTC 设备文件,通常是"/dev/rtc0"
printf("Opening RTC device...\n");
fd = open(RTC_DEVICE, O_RDWR);
if (fd == -1) {
perror("Failed to open RTC device");
return -1;
}
printf("RTC device opened successfully.\n");
// 用户选择操作
while (1) {
printf("\nPlease choose an option:\n");
printf("1. Set RTC time\n");
printf("2. Read RTC time\n");
printf("3. Set Alarm time\n");
printf("4. Read Alarm time\n");
printf("5. Enable Alarm\n");
printf("6. Disable Alarm\n");
printf("7. Exit\n");
printf("Enter your choice: ");
scanf("%d", &choice);
switch (choice) {
case 1:
// 设置 RTC 时间
printf("Enter year (e.g., 2025): ");
scanf("%d", &rtc_tm.tm_year);
rtc_tm.tm_year -= 1900; // 年份需要减去1900
printf("Enter month (1-12): ");
scanf("%d", &rtc_tm.tm_mon);
rtc_tm.tm_mon -= 1; // 月份是0-11的范围
printf("Enter day (1-31): ");
scanf("%d", &rtc_tm.tm_mday);
printf("Enter hour (0-23): ");
scanf("%d", &rtc_tm.tm_hour);
printf("Enter minute (0-59): ");
scanf("%d", &rtc_tm.tm_min);
printf("Enter second (0-59): ");
scanf("%d", &rtc_tm.tm_sec);
printf("Setting RTC time to: %d-%02d-%02d %02d:%02d:%02d\n",
rtc_tm.tm_year + 1900, rtc_tm.tm_mon + 1, rtc_tm.tm_mday,
rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
ret = set_rtc_time(fd, rtc_tm);
if (ret < 0) {
printf("Failed to set RTC time.\n");
} else {
printf("RTC time set successfully.\n");
}
break;
case 2:
// 读取 RTC 时间
printf("Reading RTC time...\n");
ret = read_rtc_time(fd, &rtc_tm);
if (ret < 0) {
printf("Failed to read RTC time.\n");
} else {
printf("RTC time is: %d-%02d-%02d %02d:%02d:%02d\n",
rtc_tm.tm_year + 1900, rtc_tm.tm_mon + 1, rtc_tm.tm_mday,
rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
}
break;
case 3:
// 设置闹钟时间
printf("Enter alarm year (e.g., 2025): ");
scanf("%d", &alarm_tm.time.tm_year);
alarm_tm.time.tm_year -= 1900; // 年份需要减去1900
printf("Enter alarm month (1-12): ");
scanf("%d", &alarm_tm.time.tm_mon);
alarm_tm.time.tm_mon -= 1; // 月份是0-11的范围
printf("Enter alarm day (1-31): ");
scanf("%d", &alarm_tm.time.tm_mday);
printf("Enter alarm hour (0-23): ");
scanf("%d", &alarm_tm.time.tm_hour);
printf("Enter alarm minute (0-59): ");
scanf("%d", &alarm_tm.time.tm_min);
printf("Enter alarm second (0-59): ");
scanf("%d", &alarm_tm.time.tm_sec);
printf("Setting alarm time to: %d-%02d-%02d %02d:%02d:%02d\n",
alarm_tm.time.tm_year + 1900, alarm_tm.time.tm_mon + 1, alarm_tm.time.tm_mday,
alarm_tm.time.tm_hour, alarm_tm.time.tm_min, alarm_tm.time.tm_sec);
ret = alm_set_rtc(fd, alarm_tm.time);
if (ret < 0) {
printf("Failed to set alarm time.\n");
} else {
printf("Alarm time set successfully.\n");
}
break;
case 4:
// 读取闹钟时间
printf("Reading alarm time...\n");
ret = alm_read_rtc(fd, &rtc_tm);
if (ret < 0) {
printf("Failed to read alarm time.\n");
} else {
printf("Alarm time is: %d-%02d-%02d %02d:%02d:%02d\n",
rtc_tm.tm_year + 1900, rtc_tm.tm_mon + 1, rtc_tm.tm_mday,
rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
}
break;
case 5:
// 启用闹钟
printf("Enabling RTC alarm...\n");
ret = alm_rtc_enable(fd);
if (ret < 0) {
printf("Failed to enable RTC alarm.\n");
} else {
printf("RTC alarm enabled successfully.\n");
}
break;
case 6:
// 禁用闹钟
printf("Disabling RTC alarm...\n");
ret = alm_rtc_disable(fd);
if (ret < 0) {
printf("Failed to disable RTC alarm.\n");
} else {
printf("RTC alarm disabled successfully.\n");
}
break;
case 7:
// 退出程序
printf("Exiting program...\n");
close(fd);
return 0;
default:
printf("Invalid choice. Please try again.\n");
break;
}
}
return 0;
}
以
root@buildroot:/userdata# chmod +x rtc_test
root@buildroot:/userdata# ./rtc_test
Opening RTC device...
RTC device opened successfully.
Please choose an option:
1. Set RTC time
2. Read RTC time
3. Set Alarm time
4. Read Alarm time
5. Enable Alarm
6. Disable Alarm
7. Exit
Enter your choice: 1
Enter year (e.g., 2025): 2025
Enter month (1-12): 3
Enter day (1-31): 21
Enter hour (0-23): 15
Enter minute (0-59): 46
Enter second (0-59): 0
Setting RTC time to: 2025-03-21 15:46:00
RTC time set successfully.
Please choose an option:
1. Set RTC time
2. Read RTC time
3. Set Alarm time
4. Read Alarm time
5. Enable Alarm
6. Disable Alarm
7. Exit
Enter your choice: 2
Reading RTC time...
RTC time is: 2025-03-21 15:46:03
Please choose an option:
1. Set RTC time
2. Read RTC time
3. Set Alarm time
4. Read Alarm time
5. Enable Alarm
6. Disable Alarm
7. Exit
Enter your choice: 7
Exiting program...
root@buildroot:/userdata# cat /proc/driver/rtc
rtc_time : 15:46:23
rtc_date : 2025-03-21
alrm_time : 00:00:00
alrm_date : 1970-01-01
alarm_IRQ : no
alrm_pending : no
update IRQ enabled : no
periodic IRQ enabled : no
periodic IRQ frequency : 1
max user IRQ frequency : 64
24hr : yes
可以
4.3.12.5. RTC 常见问题
在
RTC 时间
丢失 :现象:重启
后,RTC 时间 变成1970-01-01 00:00:00。 原因
分析:RTC 需要 持续 的 供电 才能 保持 时间 信息。可能 的 原因 包括 电源线 断开、未 安装 电池 或 电池电量 不足。可以 通过 内核 启动 日志 看到 相关 内容: root@buildroot:/userdata# dmesg | grep rtc [ 0.577125] dw-rtc 31010000.rtc: prescaler set to: 24000000 [ 0.577462] dw-rtc 31010000.rtc: char device (253:0) [ 0.577474] dw-rtc 31010000.rtc: registered as rtc0 [ 0.577491] dw-rtc 31010000.rtc: setting system clock to 1970-01-01T00:00:00 UTC (0) [ 1.306790] rtc-pcf8563 4-0051: pcf8563_probe [ 1.308632] rtc-pcf8563 4-0051: low voltage detected, date/time is not reliable. [ 1.309614] rtc rtc1: read_time: fail to read: -22 [ 1.309838] rtc-pcf8563 4-0051: char device (253:1) [ 1.309854] rtc-pcf8563 4-0051: registered as rtc1
注意:这里
可以 看到 rtc-pcf8563 4-0051: low voltage detected, date/time is not reliable.,一般出现 这样 的 日志 就 说明 没有 正确 接入 外部 电池 或者 电池电量 不足。 解决
方法:确认 电源线 没有 断开 过,检查 是否 安装 有 电池 以及 电池电量 是否 充足。
访问 RTC 设备
时 :遇到 权限 问题 现象:用户
空间 应用程序 可能 没有 足够 的 权限 来 访问 RTC 设备 文件。 解决
方法:确保 应用程序 以 具有 适当 权限 的 用户 身份 运行。
RTC 闹钟
设置 :失败 现象:尝试
设置 RTC 闹钟 时 操作 失败。 解决
方法:检查 闹钟 时间 是否 在 RTC 支持 的 范围 内,确认 闹钟 功能 是否 被 硬件 支持,以及 是否 有 足够 的 权限 来 设置 闹钟。
系统
没有 启用 RTC 驱动 现象:Linux 系统
中 可能 没有 启用 RTC 驱动,导致 无法 使用 RTC 设备。 解决
方法:确保 内核 配置文件 中 启用 了 RTC 支持。如果 没有 启用,可以 重新 编译 内核 并 启用 RTC 驱动。如果 RTC 驱动 未 加载,可以 手动 加载。