Linux RTC子系统概述
关键词:rtc、date、hwclock、Alarm、WakeAlarm、AIE、PIE、UIE等等。
1 rtc子系统框架
rtc子系统分为三部分:
- rtc core:负责rtc设备注册注销;对用户空间提供rtc字符设备文件,以及rtc类sysfs接口。
- rtc driver:将rtc设备注册到rtc子系统,提供针对rtc设备的底层操作函数集。
- 用户空间sysfs节点:/devrtcX字符设备文件,以及其他调试接口。
rtc子系统初始化,主要分配rtc_class类,以及rtc设备的rtc_devt:
rtc_init
->class_create--创建rtc_class类。
->rtc_dev_init
->alloc_chrdev_region--为rtc设备分配子设备号范围0~15。主设备号随机分配。最终结果放入rtc_devt。
系统启动时会将RTC时间设置到系统时间:
rtc_hctosys
->rtc_read_time
->rtc_tm_to_time64
->do_settimeofday64
AIE:Alarm Interrupt Enable。
UIE:Update Interrupt Enable。
PIE:Periodic Interrupt Enable。
WIE:Watchdog Interrupt Enable。
2 rtc配置
对rtc子系统的配置如下:
Device Drivers Real Time Clock
Set system time from RTC on startup and resume
(rtc0) RTC used to set the system time
Set the RTC time based on NTP synchronization
(rtc0) RTC used to synchronize NTP adjustment
RTC debug support
RTC non volatile storage support
/sys/class/rtc/rtcN (sysfs)
/proc/driver/rtc (procfs for rtcN)
/dev/rtcN (character devices)
STM32 RTC
rtc子系统相关文件如下:
drivers/rtc/
├── class.c--rtc_class创建,以及rtc设备的注册注销、分配释放、suspend/resume的实现。 ├── dev.c--rtc子系统初始化,和rtc字符设备文件操作函数集实现。 ├── hctosys.c--读取RTC时间设置到系统时间。HC是Hardware Clock的意思。 ├── interface.c--rtc子系统通用timer、对设备驱动rtc_class_ops函数调用接口等。 ├── lib.c--rtc关于时间和日期的通用函数。 ├── nvmem.c--NVRAM中存取RTC信息。 ├── proc.c--rtc相关的proc文件/proc/driver/rts的实现。 ├── rtc-stm32.c--STM32的RTC驱动。 ├── sysfs.c--rtc类设备属性sysfs实现。 └── systohc.c--将NTP时间设置到RTC中。
3 rtc数据结构和API
3.1 rtc数据结构
Linux将RTC设备抽象成struct rtc_device结构体:
struct rtc_device { struct device dev; struct module *owner; int id;--当前rtc设备在rtc子系统的子序号。 const struct rtc_class_ops *ops;--rtc设备底层操作函数。 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_class_ops是为RTC设备提供的底层操作函数集:
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); };
3.2 rtc分配注册函数
extern 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 *devm_rtc_allocate_device(struct device *dev); int __rtc_register_device(struct module *owner, struct rtc_device *rtc);
devm_rtc_device_register()首先分配一个带资源管理的struct rtc_device设备,然后将其注册到rtc子系统中。如果设备
devm_rtc_device_register
->devm_rtc_allocate_device
->rtc_device_get_id--获取rtc设备的序号。
->devres_alloc--分配一个指针,当device release时release函数devm_rtc_release_device()被调用。
->rtc_allocate_device
->kzalloc--分配一个struct rtc_device结构体。
->device_initialize--初始化rtc相关的struct device。
->初始化struct rtc_device结构体。
->rtc_timer_init--初始化rtc定时器。
->aie_timer--Alarm中断。
->rtc_aie_update_irq
->rtc_handle_legacy_irq
->设置irq_data。
->wake_up_interruptible--唤醒通过poll在/dev/rtcX等待的进程。对应于rtc_dev_poll()。
->kill_fasync--唤醒通过fasync在/dev/rtcX等待的进程,并发送SIGIO信号。对应rec_dev_fasync()。
->uie_rtctimer--1秒周期性RTC Alarm中断。
->rtc_uie_update_irq
->hrtimer_init
->pie_timer--周期性中断。
->rtc_pie_update_irq
->devres_add--将res和dev绑定。
->dev_set_name--设置设备名为rtcX。
->__rtc_register_device
->rtc_device_get_offset
->__rtc_read_alarm
->rtc_dev_prepare
->cdev_init--初始化rtc cdev操作函数集为rtc_dev_ops。
->rtc_initialize_alarm
->cdev_device_add--注册一个cdev,以及相关的struct device结构体。创建/dev/rtcX设备。
->rtc_proc_add_device--创建/proc/driver/rtc显示当前rtc硬件信息。
每创建一个rtc_class类型的设备都具有如下属性:
static struct attribute *rtc_attrs[] = { &dev_attr_name.attr, &dev_attr_date.attr, &dev_attr_time.attr, &dev_attr_since_epoch.attr, &dev_attr_max_user_freq.attr, &dev_attr_hctosys.attr, &dev_attr_wakealarm.attr, &dev_attr_offset.attr, &dev_attr_range.attr, NULL, };
rtc_dev_fops是每一个rtc cdev设备文件的操作函数集:
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, };
rtc_dev_ioctl对rtc设备进行设置和读取数据:
static long rtc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { ... switch (cmd) { case RTC_ALM_READ: mutex_unlock(&rtc->ops_lock); err = rtc_read_alarm(rtc, &alarm);--调用rtc底层设备获取struct rtc_wkalrm。 if (err < 0) return err; if (copy_to_user(uarg, &alarm.time, sizeof(tm))) err = -EFAULT; return err; case RTC_ALM_SET: mutex_unlock(&rtc->ops_lock); if (copy_from_user(&alarm.time, uarg, sizeof(tm))) return -EFAULT; ... return rtc_set_alarm(rtc, &alarm);--将Alarm时间设置到RTC。 case RTC_RD_TIME: mutex_unlock(&rtc->ops_lock); err = rtc_read_time(rtc, &tm);--从RTC读取时间返回给用户空间。 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设备。 ... default: /* Finally try the driver's ioctl interface */ if (ops->ioctl) {--底层rtc支持的自定义ioctl命令。 err = ops->ioctl(rtc->dev.parent, cmd, arg); if (err == -ENOIOCTLCMD) err = -ENOTTY; } else { err = -ENOTTY; } break; } done: mutex_unlock(&rtc->ops_lock); return err; }
用户层对/dev/rtcX调用poll进行等待,当Alarm中断产生后唤醒在irq_queue上的等待。
static __poll_t rtc_dev_poll(struct file *file, poll_table *wait) { struct rtc_device *rtc = file->private_data; unsigned long data; poll_wait(file, &rtc->irq_queue, wait);--等待Alarm中断唤醒irq_queue。
data = rtc->irq_data; return (data != 0) ? (EPOLLIN | EPOLLRDNORM) : 0; }--返回EPOLLIN标识。
当调用fasync对/dev/rtcX进行等待是,Alarm中断处理函数通过kill_fasync()唤醒等待:
static int rtc_dev_fasync(int fd, struct file *file, int on) { struct rtc_device *rtc = file->private_data; return fasync_helper(fd, file, on, &rtc->async_queue);--等待中断异步唤醒。 }
3.3 rtc设备操作API
对rtc设备的操作主要有:alarm读取和设置、rtc time读取和设置、中断配置。
extern int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm); extern int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm); extern int rtc_set_ntp_time(struct timespec64 now, unsigned long *target_nsec); int __rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm); extern int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alrm); extern int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alrm); extern int rtc_initialize_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alrm); extern void rtc_update_irq(struct rtc_device *rtc, unsigned long num, unsigned long events);extern int rtc_irq_set_state(struct rtc_device *rtc, int enabled); extern int rtc_irq_set_freq(struct rtc_device *rtc, int freq); extern int rtc_update_irq_enable(struct rtc_device *rtc, unsigned int enabled); extern int rtc_alarm_irq_enable(struct rtc_device *rtc, unsigned int enabled);
4 stm32 rtc驱动
4.1 stm32 rtc dts
stm32 rtc dts主要配置了寄存器地址、时钟、中断。
rtc: rtc@5c004000 { compatible = "st,stm32mp1-rtc"; reg = <0x5c004000 0x400>; clocks = <&scmi0_clk CK_SCMI0_RTCAPB>, <&scmi0_clk CK_SCMI0_RTC>; clock-names = "pclk", "rtc_ck"; interrupts-extended = <&exti 19 IRQ_TYPE_LEVEL_HIGH>; status = "okay"; };
4.2 stm32 rtc驱动
当dts中名称和stm32_rtc_of_match之一匹配后调用stm32_rtc_probe()进行初始化:
static const struct of_device_id stm32_rtc_of_match[] = { { .compatible = "st,stm32mp1-rtc", .data = &stm32mp1_data }, {} }; static const struct stm32_rtc_data stm32mp1_data = { .has_pclk = true, .need_dbp = false, .has_lsco = true, .regs = { .tr = 0x00, .dr = 0x04, .cr = 0x18, .isr = 0x0C, /* named RTC_ICSR on stm32mp1 */ .prer = 0x10, .alrmar = 0x40, .wpr = 0x24, .sr = 0x50, .scr = 0x5C, .cfgr = 0x60, .verr = 0x3F4, }, .events = { .alra = STM32_RTC_SR_ALRA, }, .clear_events = stm32mp1_rtc_clear_events, };
stm32_rtc_probe()获取时钟和中断,并打开时钟和注册中断;然后初始化rtc硬件;最后将rtc设备注册到rtc子系统中。
stm32_rtc_probe
devm_clk_get--获取pclk和rtc_ck。
clk_prepare_enable--使能pclk和rtc_ck。
stm32_rtc_init
stm32_rtc_wpr_unlock
stm32_rtc_enter_init_mode
stm32_rtc_exit_init_mode
stm32_rtc_wait_sync
stm32_rtc_wpr_lock
platform_get_irq--获取rtc alarm中断号。
device_init_wakeup--设置rtc设备具备唤醒功能。
dev_pm_set_wake_irq--设置rtc alarm中断具备唤醒功能。
devm_rtc_device_register--注册rtc设备,具体设备的操作函数集为stm32_rtc_ops。
devm_request_threaded_irq--注册rtc中断。
stm32_rtc_alarm_irq--alarm中断处理函数。
stm32_rtc_alarm_irq()是Alarm超时中断处理函数。
stm32_rtc_alarm_irq
->rtc_update_irq
->schedule_work--调度irqwork执行rtc_timer_do_work()函数。
rtc子系统对具体rtc设备的操作通过stm32_rtc_ops执行:
static const struct rtc_class_ops stm32_rtc_ops = { .read_time = stm32_rtc_read_time, .set_time = stm32_rtc_set_time, .read_alarm = stm32_rtc_read_alarm, .set_alarm = stm32_rtc_set_alarm, .alarm_irq_enable = stm32_rtc_alarm_irq_enable, };
5 rtc相关sysfs
/dev/rtcX:rtc字符设备,打开后通过ioctl进行配置。
/proc/driver/rtc:当前rtc硬件的详细信息。
/sys/class/rtc/rtcX:rtc类设备节点,可以通过目录下的sysfs对硬件进行配置。
6 rtc设置和测试工具
6.1 date和hwclock
date用户读取和设置系统时间。
date -s "2024-01-18 17:17:17"--设置当前系统时间。
hwclock用户去读和设置RTC:
hwclock -w--将当前系统时间设置到RTC硬件中。
hwclock -r--读取当前RTC硬件时间。
hwclock -s--将RTC时间设置到系统时间。
如果要设置RTC时间,先通过date设置系统时间,然后通过hwclock将系统时间设置到rtc硬件中。
date -s "2024-01-18 17:17:17" hwclock -s
6.2 rtc
Buildroot下rtc-tools配置:
Target packages
->Hardware handling
->rtc-tools
rtc命令用于读取设置rtc时间/Alarm/WakeAlarm:
rtc rd [rtc] rtc set YYYY-MM-DDThh:mm:ss [rtc] rtc wkalmrd [rtc] rtc wkalmset YYYY-MM-DDThh:mm:ss [rtc] rtc almread [rtc] rtc almset YYYY-MM-DDThh:mm:ss [rtc] rtc aieon [rtc] rtc aieoff [rtc]
如下是设置Alarm的流程:
rtc rd--读取时间。
rtc aieon--打开aie中断。
rtc almset 2024-01-18T19:30:50--设置超时时间。
在超时后内核会打印日志“rtc rtc0: Alarm occurred”。
其他命令还包括rtc-range/rtc-sync。