linux rtc杂谈

1概念:

        RTC(real time clock)实时时钟,主要作用是给Linux系统提供时间。RTC因为是电池供电的,所以掉电后时间不丢失。Linux内核把RTC用作“离线”的时间与日期维护器

总结rtc的特性只包含两部分:

  1. 可以将时间写入和读出,因为里面有独立的电池,所以系统在掉电的时候可以准确的读取出时间。
  2. 毫秒级别的报警器,可以作为定时中断的发生器,作为手机等产品的关机闹钟。

2 特点

RTC 具备以下特点:以海思hi3516EV200为例: 

  1. 内部具有 1 个 16bit 的天计数器,5bit 的小时计数器,6bit 的分计数器,6bit 的秒计数器和 7bit 的 10ms 计数器。
  2. 计数时钟 100Hz
  3. 计数初值可配置
  4. 计数比较值可配置
  5. 支持超时中断产生
  6. 支持软复位
  7. 支持分频参数可配置
  8. 64bit 用户寄存器,供用户保存数据

功能描述:

        由寄存器 RTC_LR_10MS、RTC_LR_S、RTC_LR_M、RTC_LR_H、RTC_LR_D_L、RTC_LR_D_H 载入。在当计数值递加到寄存器与 RTC_MR_10MS、RTC_MR_S、RTC_MR_M、RTC_MR_H、RTC_MR_D_L、RTC_MR_D_H 寄存器值相等时,RTC 将产生一个中断,然后在下一个计数时钟上升沿,计数器继续递加计数。

        根据实际应用需要,可通过配置 RTC_IMSC 使能或者禁止 RTC 产生中断信号。此时,存在以下两种情况:        

(1)当禁止产生中断时,RTC 计数器继续递加计数,将不会对外产生中断,在RTC_MSC_INT 中显示屏蔽后中断的状态,在 RTC_RAW_INT 中显示原始中断状态。

(2)当重新开启中断时,RTC 计数器仍然继续递加计数,当计数值递加到与

RTC_MR_10MS、RTC_MR_S、RTC_MR_M、RTC_MR_H、RTC_MR_D_L、RTC_MR_D_H 寄存器值相等时,RTC 将产生一个中断。

        RTC 的计数时钟采用的是 100Hz 时钟,同时提供 16bit 的天计数,便于通过天计数值转换为具体的年、月、日。

寄存器功能描述:

       由于rtc有计时与定时的功能,其寄存器也有计数类的寄存器和定时类寄存器

        计数或者定时寄存器的值可通过更新设置寄存器的值,然后设置RTC设置值使能加载寄存器为0x01。如果使能中断,当计数值递加到与RTC_MR_10MS、RTC_MR_S、RTC_MR_M、RTC_MR_H、RTC_MR_D_L、RTC_MR_D_H 寄存器值相等时,RTC 将产生一个中断。

3 RTC驱动框架

        Linux 内核将 RTC 设备抽象为 rtc_device 结构体,因此 RTC 设备驱动就是申请并初始化rtc_device,最后将 rtc_device 注册到 Linux 内核里面,这样 Linux 内核就有一个 RTC 设的。include/linux/rtc.h

struct rtc_device

 {

 struct device dev; /* 设备 */

 struct module *owner;



 int id; /* ID */

 char name[RTC_DEVICE_NAME_SIZE]; /* 名字 */



 const struct rtc_class_ops *ops; /* RTC 设备底层操作函数 */

 struct mutex ops_lock;



 struct cdev char_dev; /* 字符设备 */

 unsigned long flags;

......

}

        重点关注的是 ops 成员变量,这是一个 rtc_class_ops 类型的指针变量,rtc_class_ops为 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 RTC 设备写入新的时间值等。

 struct rtc_class_ops {

 int (*open)(struct device *);

 void (*release)(struct device *);

 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);

 };

        rtc_class_ops 中的这些函数只是最底层的 RTC 设备操作函数,并不是提供给应用层的file_operations 函数操作集。RTC 是个字符设备,那么肯定有字符设备的 file_operations 函数操作集,Linux 内核提供了一个 RTC 通用字符设备驱动文件,文件名为 drivers/rtc/rtc-dev.c 

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,

 };

hctosys

不得不提到的一个驱动就是hctosys(hardware clock to system),顾名思义是将hw clock中的时间向系统同步,此处hw clock指的就是RTC,调用时机为late_initcall,路径/linux-4.4/drivers/rtc/hctosys.c;

static int __init rtc_hctosys(void)

{

    int err = -ENODEV;

    struct rtc_time tm;

    struct timespec64 tv64 = {

        .tv_nsec = NSEC_PER_SEC >> 1,

    };

    struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);



    if (rtc == NULL) {

        pr_info("unable to open rtc device (%s)\n",

            CONFIG_RTC_HCTOSYS_DEVICE);

        goto err_open;

    }



    err = rtc_read_time(rtc, &tm);

    if (err) {

        dev_err(rtc->dev.parent,

            "hctosys: unable to read the hardware clock\n");

        goto err_read;



    }



    tv64.tv_sec = rtc_tm_to_time64(&tm);



    err = do_settimeofday64(&tv64);



    dev_info(rtc->dev.parent,

        "setting system clock to "

        "%d-%02d-%02d %02d:%02d:%02d UTC (%lld)\n",

        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,

        tm.tm_hour, tm.tm_min, tm.tm_sec,

        (long long) tv64.tv_sec);



err_read:

    rtc_class_close(rtc);



err_open:

    rtc_hctosys_ret = err;



    return err;

}



late_initcall(rtc_hctosys);

1 通过interface中接口rtc_class_open()获取系统中注册好的RTC设备,参数的部分是通过编译config配置得到的

$ grep -nr CONFIG_RTC_HCTOSYS_DEVICE .config

3217:CONFIG_RTC_HCTOSYS_DEVICE="rtc0"

2 成功获取得到RTC设备后,调用rtc_read_time获取RTC时间;

3 将得到的时间进行转换,struct rtc_time tm->struct timespec64,最终通过do_settimeofday64函数将时间同步给系统,也就是上文提到的wall time;

4 打印日志,关闭设备。

4  rtc在文件系统中的呈现

4.1、rtc在sysfs

之前曾建立过名为rtcclass

rtc_class = class_create(THIS_MODULE, "rtc");

查看之:

# ls /sys/class/rtc/                                   

rtc0

# ls -l /sys/class/rtc/                                

lrwxrwxrwx root     root              2014-01-02 16:51 rtc0 -> ../../devices/ff660000.i2c/i2c-2/2-0051/rtc/rtc0

系统中只有一个RTC,所以编号为rtc0。

同时发现rtc0文件为指向/sys/devices/ff660000.i2c/i2c-2/2-0051/rtc/rtc0的符号链接,RTC芯片是I2C接口(SOC外接rtc),所以rtc0挂载在I2C的总线上,总线控制器地址ff660000,控制器编号为2,RTC芯片作为slave端地址为0x51。

rtc0 设备属性:

void __init rtc_sysfs_init(struct class *rtc_class)

{

    rtc_class->dev_attrs = rtc_attrs;

}

static struct device_attribute rtc_attrs[] = {

    __ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),

    __ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),

    __ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),

    __ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),

    __ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,

            rtc_sysfs_set_max_user_freq),

    __ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),

    { },

};
ls -l /sys/class/rtc/rtc0/                           

-r--r--r-- root     root         4096 2014-01-02 16:51 date

-r--r--r-- root     root         4096 2014-01-02 16:51 dev

lrwxrwxrwx root     root              2014-01-02 16:51 device -> ../../../2-0051

-r--r--r-- root     root         4096 2014-01-02 16:51 hctosys

-rw-r--r-- root     root         4096 2014-01-02 16:51 max_user_freq

-r--r--r-- root     root         4096 2014-01-02 16:51 name

drwxr-xr-x root     root              2014-01-02 16:48 power

-r--r--r-- root     root         4096 2014-01-02 16:51 since_epoch

lrwxrwxrwx root     root              2014-01-02 16:51 subsystem -> ../../../../../../class/rtc

-r--r--r-- root     root         4096 2014-01-02 16:51 time

-rw-r--r-- root     root         4096 2014-01-02 16:48 uevent

-rw-r--r-- root     root         4096 2014-01-02 16:51 wakealarm
4.2、rtc在proc

之前曾rtc0设备加入到了/proc               

rtc_proc_fops

        -->rtc_proc_open

                -->rtc_proc_show

# cat /proc/driver/rtc                                      

rtc_time    : 17:19:53

rtc_date    : 2014-01-02

24hr        : yes

5  rtc相关命令(date hwclockclock)

Hwclock同clock命令:

Hwclock –r 显示硬件时钟时间

              -w 从系统时间设置硬件时钟

              -s 从硬件时钟设置系统时间

date -s                         # 设置当前时间,只有root权限才能设置,其他只能查看

date -s 20120523                # 设置成20120523,这样会把具体时间设置成00:00:00

date -s 01:01:01                # 设置具体时间,不会对日期做更改

date -s "01:01:01 2012-05-23"   # 这样可以设置全部时间

date -s "01:01:01 20120523"     # 这样可以设置全部时间

date -s "2012-05-23 01:01:01"   # 这样可以设置全部时间

date -s "20120523 01:01:01"     # 这样可以设置全部时间

Linux中的date日期命令详解_date linux-CSDN博客

6 应用代码案例

#include <stdio.h>                    /*标准输入输出定义*/

#include <stdlib.h>            /*标准函数库定义*/

#include <unistd.h>           /*Unix 标准函数定义*/

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>                      /*文件控制定义*/

#include <linux/rtc.h>               /*RTC支持的CMD*/

#include <linux/ioctl.h>    

#include <errno.h>                    /*错误号定义*/

#include <string.h>



/*2023 04 09  qxl*/



#define RTC_DEVICE_NAME "/dev/rtc0"





int set_rtc_timer(int fd)

{

       struct rtc_time rtc_tm = {0};

       struct rtc_time rtc_tm_temp = {0};



       rtc_tm.tm_year = 2020 - 1900;   /* 需要设置的年份,需要减1900 */

       rtc_tm.tm_mon = 11 - 1;           /* 需要设置的月份,需要确保在0-11范围*/

       rtc_tm.tm_mday = 21;                      /* 需要设置的日期*/

       rtc_tm.tm_hour = 10;                /* 需要设置的时间*/

       rtc_tm.tm_min = 12;                  /* 需要设置的分钟时间*/

       rtc_tm.tm_sec = 30;                  /* 需要设置的秒数*/



       /* 设置RTC时间*/

       if (ioctl(fd, RTC_SET_TIME, &rtc_tm) < 0) {

              printf("RTC_SET_TIME failed\n");

              return -1;

       }



       /* 获取RTC时间*/

       if (ioctl(fd, RTC_RD_TIME, &rtc_tm_temp) < 0) {

              printf("RTC_RD_TIME failed\n");

              return -1;

       }

       printf("RTC_RD_TIME return %04d-%02d-%02d %02d:%02d:%02d\n",

       rtc_tm_temp.tm_year + 1900, rtc_tm_temp.tm_mon + 1, rtc_tm_temp.tm_mday,

       rtc_tm_temp.tm_hour, rtc_tm_temp.tm_min, rtc_tm_temp.tm_sec);

       return 0;

}

/* ssd202 rtc没有定时功能 */

#if 0

int set_rtc_alarm(int fd)

{

       struct rtc_time rtc_tm = {0};

       struct rtc_time rtc_tm_temp = {0};



       rtc_tm.tm_year = 0;                   /* 闹钟忽略年设置*/

       rtc_tm.tm_mon = 0;                          /* 闹钟忽略月设置*/

       rtc_tm.tm_mday = 0;                 /* 闹钟忽略日期设置*/

       rtc_tm.tm_hour = 10;                /* 需要设置的时间*/

       rtc_tm.tm_min = 12;                  /* 需要设置的分钟时间*/

       rtc_tm.tm_sec = 30;                  /* 需要设置的秒数*/



       /* set alarm time */

       if (ioctl(fd, RTC_ALM_SET, &rtc_tm) < 0) {

              printf("RTC_ALM_SET failed\n");

              return -1;

       }



       if (ioctl(fd, RTC_AIE_ON) < 0) {

              printf("RTC_AIE_ON failed!\n");

              return -1;

       }



       if (ioctl(fd, RTC_ALM_READ, &rtc_tm_temp) < 0) {

              printf("RTC_ALM_READ failed\n");

              return -1;

       }



       printf("RTC_ALM_READ return %04d-%02d-%02d %02d:%02d:%02d\n",

       rtc_tm_temp.tm_year + 1900, rtc_tm_temp.tm_mon + 1, rtc_tm_temp.tm_mday,

       rtc_tm_temp.tm_hour, rtc_tm_temp.tm_min, rtc_tm_temp.tm_sec);

       return 0;

}

#endif

int main(int argc, char *argv[])

{

       int fd;

       int ret;



       /* open rtc device */

       fd = open(RTC_DEVICE_NAME, O_RDWR);

       if (fd < 0) {

              printf("open rtc device %s failed\n", RTC_DEVICE_NAME);

              return -ENODEV;

       }



       /* 设置RTC时间*/

       ret = set_rtc_timer(fd);

       if (ret < 0) {

              printf("set rtc timer error\n");

              close(fd);

              return -EINVAL;

       }

#if 0

       /* 设置闹钟*/

       ret = set_rtc_alarm(fd);

       if (ret < 0) {

              printf("set rtc alarm error\n");

              close(fd);

              return -EINVAL;

        }

#endif

       close(fd);

       return 0;

 }

文章引用:

https://blog.csdn.net/u013686019/article/details/57126940

https://blog.csdn.net/spongebob1912/article/details/111174475

Linux RTC 开发指南 - 知乎 (zhihu.com)

Linux中的date日期命令详解_date linux-CSDN博客

正点原子驱动开发指南

海思datesheet

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值