Linux内核 RTC时间架构

Posted 一口Linux

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核 RTC时间架构相关的知识,希望对你有一定的参考价值。

上一篇文章我们给大家讲解了基于瑞芯微rk3568平台芯片hym8563驱动的移植,本文给大家详细讲解Linux内核的时间子系统。

Linux驱动|rtc-hym8563移植笔记

一、Linux 时间操作命令 :date、hwclock

Linux时间有两个: 系统时间(Wall Time), RTC时间

1)系统时间(WT):

由Linux系统软件维持的时间,通过Linux命令date查看:

rk3568_r:/ # date
Wed Sep 21 03:05:21 GMT 2022

获取到的就是系统时间。

2)RTC时间:

这个时间来自我们设备上的RTC芯片,通过Linux命令hwclock 可以读取:

rk3568_r:/ # hwclock
Wed Sep 21 03:05:24 2022  0.000000 seconds

我们通过man查看date和hwclock的介绍:

命令说明

1)date

DESCRIPTION
       Display the current time in the given FORMAT, or set the system date.

2)hwclock

DESCRIPTION
       hwclock  is  a tool for accessing the Hardware Clock.  It can: display the Hardware
       Clock time; set the Hardware Clock to a specified time; set the Hardware Clock from
       the  System  Clock;  set  the  System Clock from the Hardware Clock; compensate for
       Hardware Clock drift; correct the System Clock timescale; set  the  kernel's  time‐
       zone,  NTP  timescale,  and  epoch  (Alpha  only);  compare the System and Hardware
       Clocks; and predict future Hardware Clock values based on its drift rate.

       Since v2.26 important changes were made to the --hctosys function and the  --direc‐
       tisa  option,  and  a  new  option  --update-drift was added.  See their respective
       descriptions below.

接下来,通过代码看下两者的关系。

二、RTC时间框架

框架如图:

从该架构可得:

Hardware:提供时间信息(time&alarm),通过一定的接口(比如I2C)和RTC Driver进行交互
Driver:  完成硬件的访问功能,提供访问接口,以驱动的形式驻留在系统
class.c:驱动注册方式由class.c:文件提供。驱动注册成功后会构建rtc_device结构体表征的rtc设备,并把rtc芯片的操作方式存放到rtc设备的ops成员中
interface.c:文件屏蔽硬件相关的细节,向上提供统一的获取/设置时间或Alarm的接口
rtc-lib.c:文件提供通用的时间操作函数,如rtc_time_to_tm、rtc_valid_tm等
rtc-dev.c:文件在/dev/目录下创建设备节点供应用层访问,如open、read、ioctl等,访问方式填充到file_operations结构体中
hctosys.c/rtc-sys.c/rtc-proc.c:将硬件时钟写给 wall time

下面我们从底层往上层来一步步分析。

1、rtc_class_ops 填充

驱动主要工作是填充 rtc_class_ops结构体,结构体描述了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_mmss)(struct device *, unsigned long secs);
    int (*read_callback)(struct device *, int data);
    int (*alarm_irq_enable)(struct device *, unsigned int enabled);
;

实现:

static const struct rtc_class_ops hym8563_rtc_ops = 
	.read_time		= hym8563_rtc_read_time,
	.set_time		= hym8563_rtc_set_time,
	.alarm_irq_enable	= hym8563_rtc_alarm_irq_enable,
	.read_alarm		= hym8563_rtc_read_alarm,
	.set_alarm		= hym8563_rtc_set_alarm,
;

注册:

	hym8563->rtc = devm_rtc_device_register(&client->dev, client->name,
						&hym8563_rtc_ops, THIS_MODULE);

成功的话log:

[    0.758774] hym8563_probe()---565----
[    0.760651] rtc-hym8563 5-0051: rtc information is invalid
[    0.761666] hym8563_rtc_read_time()---115----1--
[    0.761681] hym8563_rtc_set_time()---129----1--
[    0.764235] hym8563_rtc_read_time()---115----1--
[    0.766425] hym8563_rtc_read_time()---115----1--
[    0.767439] hym8563_rtc_read_time()---115----1--
[    0.767619] rtc-hym8563 5-0051: rtc core: registered hym8563 as rtc0
[    0.768634] hym8563_rtc_read_time()---115----1--
[    0.768661] rtc-hym8563 5-0051: setting system clock to 2021-01-01 12:00:00 UTC (1609502400)

从log可得
5-0051: 5表示I2C通道5,0051表示从设备地址
rtc0 :注册的rtc设备为rtc0

2、class.c和RTC驱动注册

class.c文件在RTC驱动注册之前开始得到运行:

static int __init rtc_init(void)

    rtc_class = class_create(THIS_MODULE, "rtc");
    rtc_class->suspend = rtc_suspend;
    rtc_class->resume = rtc_resume;
    rtc_dev_init();
    rtc_sysfs_init(rtc_class);
    return 0;

subsys_initcall(rtc_init);

函数功能:

  • 1、创建名为rtc的class
  • 2、提供PM相关接口suspend/resume
  • 3、rtc_dev_init():动态申请/dev/rtcN的设备号
  • 4、rtc_sysfs_init():rtc类具有的device_attribute属性

3、RTC驱动注册函数devm_rtc_device_register():

drivers/class.c
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 **ptr, *rtc;

	ptr = devres_alloc(devm_rtc_device_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return ERR_PTR(-ENOMEM);

	rtc = rtc_device_register(name, dev, ops, owner);
	if (!IS_ERR(rtc)) 
		*ptr = rtc;
		devres_add(dev, ptr);
	 else 
		devres_free(ptr);
	

	return rtc;

rtc_device_register()定义如下

struct rtc_device *rtc_device_register(const char *name, struct device *dev,
                    const struct rtc_class_ops *ops,
                    struct module *owner)

    struct rtc_device *rtc;
    struct rtc_wkalrm alrm;
    int id, err;
    
    // 1、Linux支持多个RTC设备,所以需要为每一个设备分配一个ID
    // 对应与/dev/rtc0,/dev/rtc1,,,/dev/rtcN
    id = ida_simple_get(&rtc_ida, 0, 0, GFP_KERNEL);
 
    // 2、创建rtc_device设备(对象)并执行初始化
    rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
    rtc->id = id;
    rtc->ops = ops; // 2.1 对应RTC驱动填充的test_rtc_ops
    rtc->owner = owner;
    rtc->irq_freq = 1;
    rtc->max_user_freq = 64;
    rtc->dev.parent = dev;
    rtc->dev.class = rtc_class;// 2.2 rtc_init()创建的rtc_class
    rtc->dev.release = rtc_device_release;
 
    // 2.3 rtc设备中相关锁、等待队列的初始化
    mutex_init(&rtc->ops_lock);
    spin_lock_init(&rtc->irq_lock);
    spin_lock_init(&rtc->irq_task_lock);
    init_waitqueue_head(&rtc->irq_queue);
 
    // 2.4 Init timerqueue:我们都知道,手机等都是可以设置多个闹钟的
    timerqueue_init_head(&rtc->timerqueue);
    INIT_WORK(&rtc->irqwork, rtc_timer_do_work);
    // 2.5 Init aie timer:alarm interrupt enable,RTC闹钟中断
    rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc);
    // 2.6 Init uie timer:update interrupt,RTC更新中断
    rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc);
    /* Init pie timer:periodic interrupt,RTC周期性中断 */
    hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    rtc->pie_timer.function = rtc_pie_update_irq;
    rtc->pie_enabled = 0;
 
    /* Check to see if there is an ALARM already set in hw */
    err = __rtc_read_alarm(rtc, &alrm);
 
    // 3、如果RTC芯片中设置了有效的Alarm,则初始化:加入到rtc->timerqueue队列中
    if (!err && !rtc_valid_tm(&alrm.time))
        rtc_initialize_alarm(rtc, &alrm);
 
    // 4、根据name参数设置rtc的name域
    strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
    // 5、设置rtc的dev成员中的name域
    dev_set_name(&rtc->dev, "rtc%d", id);
 
    // 6、/dev/rtc0的rtc作为字符设备进行初始化
    // rtc_dev_prepare-->cdev_init(&rtc->char_dev, &rtc_dev_fops);
    rtc_dev_prepare(rtc);
 
    // 7、添加rtc设备到系统
    err = device_register(&rtc->dev);
 
    // 8、rtc设备作为字符设备添加到系统
    // rtc_dev_add_devicec-->dev_add(&rtc->char_dev, rtc->dev.devt, 1)
    // 然后就存在/dev/rtc0了
    rtc_dev_add_device(rtc);
    rtc_sysfs_add_device(rtc);
    // 9、/proc/rtc
    rtc_proc_add_device(rtc);
 
    dev_info(dev, "rtc core: registered %s as %s\\n",
            rtc->name, dev_name(&rtc->dev));
 
    return rtc;

有了 /dev/rtc0后,应用层就可以通过 open/read/ioctl操作RTC设备了,对应与内核的file_operations:

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

4、硬件抽象层interface.c

硬件抽象,即屏蔽具体的硬件细节,为上层用户提供统一的调用接口,使用者无需关心这些接口是怎么实现的。
以RTC访问为例,抽象的实现位于interface.c文件,其实现基于class.c中创建的rtc_device设备。
实现原理,以rtc_set_time为例:

drivers/interface.c
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)

    int err;
    // 1、参数检测
    err = rtc_valid_tm(tm);
 
    err = mutex_lock_interruptible(&rtc->ops_lock);
    if (err)
        return err;
 
    // 2、调用rtc_device中ops结构体的函数指针
    // ops结构体的函数指针已经在RTC驱动中被赋值
    if (!rtc->ops)
        err = -ENODEV;
    else if (rtc->ops->set_time)
        err = rtc->ops->set_time(rtc->dev.parent, tm);
    mutex_unlock(&rtc->ops_lock);
    /* A timer might have just expired */
    schedule_work(&rtc->irqwork);
    return err;

5、rtc在sysfs文件系统中的呈现

之前曾建立过名为rtc的class:

rtc_class = class_create(THIS_MODULE, "rtc");

查看之:

# ls /sys/class/rtc/                                    
rtc0
# ls -l /sys/class/rtc/                                 
lrwxrwxrwx 1 root root 0 2021-01-01 12:00 rtc0 -> ../../devices/platform/fe5e0000.i2c/i2c-5/5-0051/rtc/rtc0

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

同时发现rtc0文件为指向/sys/devices/platform/fe5e0000.i2c/i2c-5/5-0051/rtc/rtc0的符号链接,

RTC芯片是I2C接口,所以rtc0挂载在I2C的总线上,总线控制器地址fe5e0000,控制器编号为5,RTC芯片作为slave端地址为0x51。

rtc0 设备属性:

drivers/rtc-sysfs.c
void __init rtc_sysfs_init(struct class *rtc_class)

	rtc_class->dev_attrs = rtc_attrs;

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,
	NULL,
;

对应文件系统中的文件节点:

rk3568_r:/sys/class/rtc # cd rtc0/
rk3568_r:/sys/class/rtc/rtc0 # ls -l
total 0
-r--r--r-- 1 root root 4096 2022-09-21 03:56 date
-r--r--r-- 1 root root 4096 2022-09-21 03:56 dev
lrwxrwxrwx 1 root root    0 2022-09-21 03:56 device -> ../../../5-0051
-r--r--r-- 1 root root 4096 2021-01-01 12:00 hctosys
-rw-r--r-- 1 root root 4096 2022-09-21 03:56 max_user_freq
-r--r--r-- 1 root root 4096 2022-09-21 03:56 name
drwxr-xr-x 2 root root    0 2021-01-01 12:00 power
-r--r--r-- 1 root root 4096 2022-09-21 03:56 since_epoch
lrwxrwxrwx 1 root root    0 2022-09-21 03:56 subsystem -> ../../../../../../../class/rtc
-r--r--r-- 1 root root 4096 2022-09-21 03:56 time
-rw-r--r-- 1 root root 4096 2021-01-01 12:00 uevent
-rw-r--r-- 1 root root 4096 2022-09-21 03:56 wakealarm
drwxr-xr-x 2 root root    0 2021-01-01 12:00 wakeup8

6、rtc在proc文件系统中的呈现

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

drivers/class.c
rtc_device_register()

	--->rtc_proc_add_device(rtc);

void rtc_proc_add_device(struct rtc_device *rtc)

	if (is_rtc_hctosys(rtc))
		proc_create_data("driver/rtc", 0, NULL, &rtc_proc_fops, rtc);

查看之:

# cat /proc/driver/rtc                                       
rtc_time        : 03:59:11
rtc_date        : 2022-09-21
alrm_time       : 12:00:00
alrm_date       : 2021-01-02
alarm_IRQ       : no
alrm_pending    : no
update IRQ enabled      : no
periodic IRQ enabled    : no
periodic IRQ frequency  : 1
max user IRQ frequency  : 64
24hr            : yes

信息来源:

rtc_proc_fops
	-->rtc_proc_open
		-->rtc_proc_show

三、WT时间和RTC时间同步问题

1)

WT时间来自于RTC时间,流程是:

上电-->RTC驱动加载-->从RTC同步时间到WT时间

对应驱动代码:

hctosys.c (drivers\\rtc)
static int __init rtc_hctosys(void)

	......
    struct timespec tv = 
        .tv_nsec = NSEC_PER_SEC >> 1,
    ;
    
    err = rtc_read_time(rtc, &tm);
    err = do_settimeofday(&tv);
    dev_info(rtc->dev.parent,
        "setting system clock to "
        "%d-%02d-%02d %02d:%02d:%02d UTC (%u)\\n",
        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
        tm.tm_hour, tm.tm_min, tm.tm_sec,
        (unsigned int) tv.tv_sec);
    ......

以上是关于Linux内核 RTC时间架构的主要内容,如果未能解决你的问题,请参考以下文章

RK3399驱动开发 | 15 - RTC实时时钟芯片HYM8563S调试(基于linux5.4.32内核)

linux 实时时钟(RTC)驱动

RK3399平台开发系列讲解(内核调试篇)9.18通过/proc/driver/rtc节点读取时间

i.MX6ULL驱动开发 | 26 - Linux内核的RTC驱动

i.MX6ULL驱动开发 | 26 - Linux内核的RTC驱动

Linux RTC 驱动实验