2. Linux RTC 子系统架构

Linux RTC 子系统采用经典的分层架构设计,将硬件操作、核心逻辑与用户空间接口解耦。理解这一架构是进行驱动开发、调试与性能调优的基础。整个子系统自下而上分为三层:RTC 类驱动、RTC 核心层以及字符设备与 sysfs 接口层。

架构核心思想: 类驱动负责与硬件寄存器直接交互;核心层提供统一的注册、注销、中断处理和通用 ioctl 逻辑;接口层则向用户空间暴露标准化的访问入口。
Linux RTC 子系统三层架构 用户空间接口层 字符设备节点 /dev/rtc0 sysfs 接口 /sys/class/rtc/rtc0/ RTC 核心层 (rtc-core) rtc_device 结构体管理 通用 ioctl 处理 (RTC_RD_TIME, RTC_SET_TIME, RTC_ALM_READ 等) 中断处理与回调分发 RTC 类驱动层 (rtc-class) 实现 struct rtc_class_ops 回调函数 直接操作硬件寄存器 (读/写时间、闹钟、频率) 注册到核心层:rtc_device_register()

2.1 RTC 核心层 (rtc-core)

核心层是子系统的中枢,负责管理所有 RTC 设备实例。它维护一个全局链表,记录每个注册的 rtc_device 结构体。核心层实现了大部分与硬件无关的逻辑,包括:

  • 设备生命周期管理: 分配、初始化、注册与注销 rtc_device。
  • 通用 ioctl 处理: 如 RTC_RD_TIME、RTC_SET_TIME、RTC_ALM_READ、RTC_IRQP_SET 等标准命令。
  • 中断分发: 将硬件中断转换为内核事件,并通知等待队列中的用户进程。
  • 系统时间同步: 提供 rtc_set_ntp_time() 和 sync_hw_clock() 接口,用于 NTP 或系统关机时同步硬件时间。
调试提示: 核心层导出了 /proc/driver/rtc(如果启用)和 /sys/class/rtc/,这是快速查看当前 RTC 状态的首选入口。

2.2 RTC 类驱动 (rtc-class)

类驱动是直接与硬件打交道的部分。每个 RTC 硬件驱动都需要实现 struct rtc_class_ops 结构体中的回调函数,并将其注册到核心层。关键回调函数如下表所示:

回调函数 功能描述 性能/调试关注点
read_time 从硬件寄存器读取当前时间 I2C/SPI 总线延迟;寄存器读取顺序
set_time 将时间写入硬件寄存器 写入完成等待时间;写保护位处理
read_alarm 读取当前闹钟设置 闹钟寄存器有效性校验
set_alarm 设置硬件闹钟 闹钟使能位操作;中断触发条件
alarm_irq_enable 使能/禁用闹钟中断 中断共享与竞争条件
proc 输出驱动私有信息到 /proc/driver/rtc 调试时用于输出寄存器原始值

类驱动通常通过 I2C、SPI 或内存映射 I/O 与硬件通信。驱动开发者需要确保这些回调函数是原子安全的,并且尽可能快地返回,以避免阻塞核心层或其他设备。

2.3 字符设备接口与 sysfs 接口

用户空间通过两种标准接口与 RTC 子系统交互:

2.3.1 字符设备接口

每个注册的 RTC 设备对应一个主设备号 254 的字符设备,次设备号从 0 开始递增。设备节点通常为 /dev/rtc0、/dev/rtc1 等。用户程序通过 open()、ioctl()、read() 和 close() 系统调用进行操作。

  • ioctl 命令: 核心层处理 RTC_RD_TIME、RTC_SET_TIME、RTC_ALM_READ、RTC_ALM_SET、RTC_IRQP_READ、RTC_IRQP_SET、RTC_PIE_ON/OFF 等标准命令。
  • read 操作: 当 RTC 中断使能时,read() 会阻塞等待,直到发生闹钟或周期性中断,并返回 struct rtc_time 数据。
  • 阻塞与非阻塞模式: 支持通过 O_NONBLOCK 标志实现非阻塞读取。
// 用户空间读取 RTC 时间示例
#include <linux/rtc.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>

int main() {
    int fd = open("/dev/rtc0", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    struct rtc_time rtc_tm;
    if (ioctl(fd, RTC_RD_TIME, &rtc_tm) == -1) {
        perror("ioctl");
        close(fd);
        return 1;
    }
    printf("RTC time: %04d-%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);
    close(fd);
    return 0;
}

2.3.2 sysfs 接口

sysfs 接口位于 /sys/class/rtc/rtcX/ 目录下,提供属性文件用于读取和配置 RTC 设备,无需编写 C 代码即可操作。常用属性文件包括:

属性文件 类型 说明
time 只读 当前 RTC 时间,格式 YYYY-MM-DD HH:MM:SS
date 只读 当前 RTC 日期,格式 YYYY-MM-DD
since_epoch 只读 从 Epoch 到当前 RTC 时间的秒数
max_user_freq 只读 支持的最大周期性中断频率 (Hz)
name 只读 RTC 设备名称 (通常来自设备树 compatible 属性)
wakealarm 读写 设置或读取系统唤醒闹钟时间 (写入 Unix 时间戳)
offset 读写 RTC 偏移量校正 (单位 ppb),用于精细调优
调试注意: 通过 sysfs 写入 wakealarm 时,务必确认硬件支持唤醒功能,并且系统电源管理配置正确。否则写入可能静默失败。
用户空间接口与内核交互流程 用户空间 ioctl() / read() / sysfs VFS / sysfs 层 文件操作分发 RTC 核心层 ioctl 处理 / 回调 RTC 类驱动 硬件寄存器操作 硬件设备 I2C/SPI/MMIO

2.4 关键数据结构

理解 struct rtc_device 和 struct rtc_class_ops 是驱动开发的核心。以下列出关键字段:

// include/linux/rtc.h
struct rtc_device {
    struct device       dev;            // 内核设备结构体
    struct module       *owner;         // 驱动模块所有者
    int                 id;             // RTC 设备编号 (0, 1, 2...)
    char                name[RTC_DEVICE_NAME_SIZE]; // 设备名称
    const struct rtc_class_ops *ops;    // 硬件操作回调
    struct mutex        ops_lock;       // 操作互斥锁
    struct cdev         char_dev;       // 字符设备
    unsigned long       features;       // 设备特性标志位
    // ... 其他字段
};

struct rtc_class_ops {
    int (*read_time)(struct device *dev, struct rtc_time *tm);
    int (*set_time)(struct device *dev, struct rtc_time *tm);
    int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alarm);
    int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alarm);
    int (*alarm_irq_enable)(struct device *dev, unsigned int enabled);
    int (*read_offset)(struct device *dev, long *offset);
    int (*set_offset)(struct device *dev, long offset);
    // ... 其他可选回调
};
性能调优要点: 在 read_time 和 set_time 回调中,应避免不必要的延时或轮询。对于 I2C/SPI 设备,考虑使用异步传输或批量寄存器读写来减少总线开销。同时,合理设置 max_user_freq 可以防止用户空间设置过高的周期性中断频率导致系统负载过高。