linux rtc驱动分析和测试

时间:2024-04-17 16:01:07

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_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   

设置当前时间

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

https://www.cnblogs.com/lifexy/p/7839625.html

https://blog.****.net/orz415678659/article/details/8309837