1.基础知识
RTC(real time clock) ,实时时钟。在linux内核中即为外部时钟源,由32.768kHz晶振产生;内部时钟源是系芯片自带24Mhz时钟分频而来。
RTC优点如下:
1)消耗功率低(需要辅助电源,一般是纽扣电池)
2)让主系统处理更需时效性的工作
3)有时会比其他方式的输出要更准确
linux代码路径:drivers/rtc,如下图所示
可以看到主要RTC芯片:PCF系列、RX系列和DS系列,这些都需要外挂到I2C总线下,也就是说会用到I2C的接口。有些RTC芯片没有挂在I2C总线下,直接封装到内部IC里,可以直接读写对应的寄存器进行操作,具体看硬件电路设计。
2.基本框架
下图是一个RTC基本框架图
3.流程分析
只讲重点,可以参考这个链接:https://blog.****.net/orz415678659/article/details/8309837
3.1结构体
主要还是看这几个结构体 rtc_time 、rtc_wkalrm、rtc_device、rtc_class_ops
头文件路径:include/uapi/linux/rtc.h include/linux/rtc.h
rtc_time,最基本的结构体,调用时间都要用到
struct rtc_wkalrm { unsigned char enabled; /* 0 = alarm disabled, 1 = alarm enabled */ unsigned char pending; /* 0 = alarm not pending, 1 = alarm pending */ struct rtc_time time; /* time the alarm is set to */ };
rtc_class_ops,里面都是是桩函数,也叫钩子函数,具体的驱动需要实现对应的读写接口。
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 (*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); int (*read_offset)(struct device *, long *offset); int (*set_offset)(struct device *, long offset); };
rtc_device,具体rtc驱动用到
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; /* Some hardware can\'t support UIE mode */ int uie_unsupported; /* Number of nsec it takes to set the RTC clock. This influences when * the set ops are called. An offset: * - of 0.5 s will call RTC set for wall clock time 10.0 s at 9.5 s * - of 1.5 s will call RTC set for wall clock time 10.0 s at 8.5 s * - of -0.5 s will call RTC set for wall clock time 10.0 s at 10.5 s */ long set_offset_nsec; bool registered; struct nvmem_device *nvmem; /* Old ABI support */ bool nvram_old_abi; struct bin_attribute *nvram; 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 };
3.2函数调用
主要看对应的probe函数,以rtc-s3c.c为例。
具体注册流程看devm_rtc_device_register里面的rtc_device_register实现。主要流程如下图
4.测试
4.1.命令测试
先看内核打印驱动开起来了没有
dmesg | grep -i rtc
显示当前时间
date
设置当前时间
date -s "2021-1-1 10:10:10" 时间格式形如xxxx-xx-xx xx:xx:xx root@zzz-VirtualBox:/# date -s "2021-1-1 10:10:10" 2021年 01月 01日 星期五 10:10:10 CST
同步系统时间到rtc时间
hwclock -w
设置闹钟为当前rtc时间的20s后
echo +20 /sys/class/rtc/rtc0/wakealarm
查看RTC属性
cat /proc/driver/rtc
由于是虚拟机,空有这些接口,根本没有所谓irq和pending。
4.2.应用测试
一个简单的应用测试用例如下
#include <stdio.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/types.h> #include <linux/rtc.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <time.h> struct rtc_time test_time[] = { {1, 1, 1, 1, 1, 1969}, {1, 1, 1, 1, 1, 1970}, {1, 1, 1, 1, 1, 1971}, {1, 1, 1, 1, 1, 2099}, {1, 1, 1, 1, 1, 2100}, {1, 1, 1, 1, 1, 2101}, {1, 1, 1, 1, 0, 2020}, {1, 1, 1, 1, 1, 2020}, {1, 1, 1, 1, 2, 2020}, {1, 1, 1, 1, 11, 2020}, {1, 1, 1, 1, 12, 2020}, {1, 1, 1, 1, 13, 2020}, {1, 1, 1, 0, 1, 2020}, {1, 1, 1, 2, 1, 2020}, {1, 1, 1, 30, 1, 2020}, {1, 1, 1, 31, 1, 2020}, {1, 1, 1, 32, 1, 2020}, {1, 1, 1, 0, 28, 2020}, {1, 1, 1, 1, 29, 2020}, {1, 1, 1, 2, 30, 2020}, {1, 1, 1, 0, 27, 2019}, {1, 1, 1, 1, 28, 2019}, {1, 1, 1, 2, 29, 2019}, {1, 1, 1, 29, 4, 2020}, {1, 1, 1, 30, 4, 2020}, {1, 1, 1, 31, 4, 2020}, {1, 1, -1, 1, 1, 2020}, {1, 1, 0, 1, 1, 2020}, {1, 1, 58, 1, 1, 2020}, {1, 1, 59, 1, 1, 2020}, {1, 1, 60, 1, 1, 2020}, {1, -1, 1, 1, 1, 2020}, {1, 0, 1, 1, 1, 2020}, {1, 58, 1, 1, 1, 2020}, {1, 59, 1, 1, 1, 2020}, {1, 60, 1, 1, 1, 2020}, {-1, 1, 1, 1, 1, 2020}, {0, 1, 1, 1, 1, 2020}, {58, 1, 1, 1, 1, 2020}, {59, 1, 1, 1, 1, 2020}, {60, 1, 1, 1, 1, 2020}, }; struct rtc_time rtc_tm ={ .tm_sec = 59, .tm_min = 59, .tm_hour = 59, .tm_mday = 1, .tm_mon = 1, .tm_year = 2020, }; struct rtc_wkalrm alrm = { .time.tm_sec = 59, .time.tm_min = 59, .time.tm_hour = 59, .time.tm_mday = 1, .time.tm_mon = 1, .time.tm_year = 2020, }; void print_time(struct rtc_time *tm) { printf("rtc time is %d/%d/%d %02d:%02d:%02d\n", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } void print_alarm(struct rtc_wkalrm *alrm) { printf("rtc alarm is %d/%d/%d %02d:%02d:%02d\n", alrm->time.tm_year + 1900, alrm->time.tm_mon + 1, alrm->time.tm_mday, alrm->time.tm_hour, alrm->time.tm_min, alrm->time.tm_sec); } void transfer_time(struct rtc_time *tm) { tm->tm_mon = tm->tm_mon - 1; tm->tm_year = tm->tm_year - 1900; } int read_rtc_time_test(struct rtc_time *tm){ int fd, ret; fd = open("/dev/rtc0", O_RDONLY); if (fd == -1) { printf("open /dev/rtc0 error\n"); close(fd); return -1; } ret = ioctl(fd, RTC_RD_TIME, tm); close(fd); return ret; } int set_rtc_time_test(struct rtc_time *tm){ int fd, ret; fd = open("/dev/rtc0", O_RDWR); if (fd == -1) { printf("open /dev/rtc0 error\n"); close(fd); return -1; } ret = ioctl(fd, RTC_SET_TIME, &tm); close(fd); return ret; } int main(void) { int cnt, ret; int test_num = sizeof(test_time)/sizeof(test_time[0]); struct rtc_time test; printf("-----rtc test-----\n"); read_rtc_time_test(&test); print_time(&test); /* set rtc time test */ for(cnt = 0; cnt < test_num; cnt++) { test = test_time[cnt]; transfer_time(&test); ret = set_rtc_time_test(&test); if (ret == 0) printf("-----test case pass-----\n"); else { printf("-----test case fail, case is %d-----\n", cnt); print_time(&test); } printf("\n"); } printf("-----end-----\n"); }
使用makefile进行编译
CC = gcc CFLAGS = -Wall -O -g rtc_test: rtc_test.o $(CC) -o rtc_test rtc_test.o rtc_test.o : rtc_test.c $(CC) $(CFLAGS) rtc_test.c clean: rm -rf rtc_test rtc_test.o
CC这里去要根据对应的驱动编译工具链去指定,由于本地环境已经装了gcc,在系统环境中,所以直接用。
编译报错没有 <linux/rtc.h>对应的文件,需要构建对应的内核编译工具链。
tips:
1. RTC是硬件时钟,linux内核中RTC是UTC时间,系统时间是CST时间(北京时),原因是已经做了时区转换,CST = UTC + 8
2. linux内核中会进行时间转换,统一换成距epoch的绝对秒数。
5.参考
http://www.wowotech.net/timer_subsystem/time_concept.html