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.流程分析
只讲重点,可以参考这个链接:linux RTC设备驱动_paomadi的专栏-CSDN博客
3.1结构体
主要还是看这几个结构体 rtc_time 、rtc_wkalrm、rtc_device、rtc_class_ops
头文件路径:include/uapi/linux/rtc.h include/linux/rtc.h
rtc_time,最基本的结构体,调用时间都要用到
struct rtc_time {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday; //没用到,初始化默认为0
int tm_isdst; //没用到,初始化默认为0
};
闹钟结构体,就是enabled、加上pending还有时间。
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 显示当前时间
设置当前时间,时间格式形如xxxx-xx-xx xx:xx:xx
date -s "2021-1-1 10:10:10"
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
查看对应的proc接口
cat /proc/driver/rtc
由于是虚拟机,空有这些接口,根本没有所谓irq和pending。
4.2.应用测试
一个简单的用例如下rtc_test.c
#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,在系统环境中,所以直接用gcc即可。
编译报错没有 <linux/rtc.h>对应的文件,需要构建对应的内核编译工具链。
tips:
1. RTC是硬件时钟,linux内核中RTC是UTC时间,系统时间是CST时间(北京时),原因是已经做了时区转换,CST = UTC + 8
2. linux内核中会进行时间转换,统一换成距epoch的绝对秒数。