上一篇文章我们给大家讲解了基于瑞芯微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);
......
}
late_initcall(rtc_hctosys);

late_initcall说明系统在启动流程的后面才会调用该函数去同步时间。

2)瑞芯微时间操作

在瑞芯微的系统中,安卓部分程序其实最终也是依赖/sys/class/rtc/rtc0 下的文件节点实现时间管理功能的。

安卓程序会通过AlarmImpl::getTime、AlarmImpl::setTime()方法来获得和设置RTC时间:

frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp
122 int AlarmImpl::getTime(int type, struct itimerspec *spec)
123 {
124 if (static_cast<size_t>(type) > ANDROID_ALARM_TYPE_COUNT) {
125 errno = EINVAL;
126 return -1;
127 }
128
129 return timerfd_gettime(fds[type], spec);
130 }
131
132 int AlarmImpl::setTime(struct timeval *tv)
133 {
134 struct rtc_time rtc;
135 struct tm tm, *gmtime_res;
136 int fd;
137 int res;
138
139 res = settimeofday(tv, NULL);
140 if (res < 0) {
141 ALOGV("settimeofday() failed: %s\n", strerror(errno));
142 return -1;
143 }
144
145 if (rtc_id < 0) {
146 ALOGV("Not setting RTC because wall clock RTC was not found");
147 errno = ENODEV;
148 return -1;
149 }
150
151 android::String8 rtc_dev = String8::format("/dev/rtc%d", rtc_id);
152 fd = open(rtc_dev.string(), O_RDWR);
153 if (fd < 0) {
154 ALOGV("Unable to open %s: %s\n", rtc_dev.string(), strerror(errno));
155 return res;
156 }
157
158 gmtime_res = gmtime_r(&tv->tv_sec, &tm);
159 if (!gmtime_res) {
160 ALOGV("gmtime_r() failed: %s\n", strerror(errno));
161 res = -1;
162 goto done;
163 }
164
165 memset(&rtc, 0, sizeof(rtc));
166 rtc.tm_sec = tm.tm_sec;
167 rtc.tm_min = tm.tm_min;
168 rtc.tm_hour = tm.tm_hour;
169 rtc.tm_mday = tm.tm_mday;
170 rtc.tm_mon = tm.tm_mon;
171 rtc.tm_year = tm.tm_year;
172 rtc.tm_wday = tm.tm_wday;
173 rtc.tm_yday = tm.tm_yday;
174 rtc.tm_isdst = tm.tm_isdst;
175 res = ioctl(fd, RTC_SET_TIME, &rtc);
176 if (res < 0)
177 ALOGV("RTC_SET_TIME ioctl failed: %s\n", strerror(errno));
178 done:
179 close(fd);
180 return res;
181 }

系统上电后,会先读取文件hctosys中的值,来决定是否将RTC时间写入到wall time:

255 static const char rtc_sysfs[] = "/sys/class/rtc";
256
257 static bool rtc_is_hctosys(unsigned int rtc_id)
258 {
259 android::String8 hctosys_path = String8::format("%s/rtc%u/hctosys",
260 rtc_sysfs, rtc_id);
261 FILE *file = fopen(hctosys_path.string(), "re");
262 if (!file) {
263 ALOGE("failed to open %s: %s", hctosys_path.string(), strerror(errno));
264 return false;
265 }
266
267 unsigned int hctosys;
268 bool ret = false;
269 int err = fscanf(file, "%u", &hctosys);
270 if (err == EOF)
271 ALOGE("failed to read from %s: %s", hctosys_path.string(),
272 strerror(errno));
273 else if (err == 0)
274 ALOGE("%s did not have expected contents", hctosys_path.string());
275 else
276 ret = hctosys;
277
278 fclose(file);
279 return ret;
280 }

269行,就是读取文件hctosys中的值,值为1则允许rtc时间写入到wall time,为0或者其他错误则不允许。

因为rtc只要有纽扣电池供电,就会有计时功能,

这是就是为什么,我们的设备关机并重启后,仍然能够显示正确的时间的原因。

【注意目录/sys/class/rtc/下文件是需要有访问权限的】

瑞芯微对文件权限的控制由以下文件提供:

device/rockchip/common/sepolicy/vendor/genfs_contexts



只需要按照对应的格式增加对应文件信息即可。

四、欢迎交流

一口君建立了瑞芯微的技术交流群,

大家工作中用到瑞芯微系列soc的,可以一起交流,

加群后台留言即可。

Linux驱动| Linux内核 RTC时间架构的更多相关文章

  1. Linux驱动之内核自带的S3C2440的LCD驱动分析

    先来看一下应用程序是怎么操作屏幕的:Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户 ...

  2. Linux驱动之内核加载模块过程分析

    Linux内核支持动态的加载模块运行:比如insmod first_drv.ko,这样就可以将模块加载到内核所在空间供应用程序调用.现在简单描述下insmod first_drv.ko的过程 1.in ...

  3. Linux 驱动之内核定时器

    1.定时器 之前说过两类跟时间相关的内核结构. 1.延时:通过忙等待或者睡眠机制实现延时. 2.tasklet和工作队列,通过某种机制使工作推后运行,但不知道运行的详细时间. 接下来要介绍的定时器,可 ...

  4. Linux驱动中获取系统时间

    最近在做VoIP方面的驱动,总共有16个FXS口和FXO口依次初始化,耗用的时间较多.准备将其改为多线程,首先需要确定哪个环节消耗的时间多,这就需要获取系统时间. #include <linux ...

  5. 【Linux驱动】内核等待队列

    在Linux中, 一个等待队列由一个"等待队列头"来管理,等待队列是双向链表结构. 应用场合:将等待同一资源的进程挂在同一个等待队列中. 数据结构 在include/linux/w ...

  6. Linux驱动:内核等待队列

    在Linux中, 一个等待队列由一个"等待队列头"来管理,等待队列是双向链表结构. 应用场合:将等待同一资源的进程挂在同一个等待队列中. 数据结构 在include/linux/w ...

  7. linux驱动之内核多线程(四)

    本文摘自 http://www.cnblogs.com/zhuyp1015/archive/2012/06/13/2548494.html 自己创建的内核线程,当把模块加载到内核之后,可以通过:ps ...

  8. linux驱动之内核多线程(二)

    本文摘自http://www.cnblogs.com/zhuyp1015/archive/2012/06/11/2545702.html 内核多线程是在项目中使用到,自己也不熟悉,遇到一个很囧的问题, ...

  9. linux驱动之内核多线程(一)

    本文摘自http://www.cnblogs.com/zhuyp1015/archive/2012/06/11/2545624.html Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进 ...

  10. linux驱动之内核多线程(三)

    本文摘自 http://www.cnblogs.com/zhuyp1015/archive/2012/06/13/2548458.html 接上 一篇文章 ,这里介绍另一种线程间通信的方式:compl ...

随机推荐

  1. Caffe样例中mnist的文件之间逻辑分析

    约定一下,Caffe运行样例时在终端中使用的所有命令,同时终端中的目录已经cd到Caffe之中(别告诉我一个Github项目你还没有make all就直接拿过来用了) sudo sh data/mni ...

  2. 06-Linux用户和组管理

    关于用户和组的知识 家目录 用户都有家目录:root用户家目录为/root.其他用户的家目录在/home/,如user1的家目录为/home/user1 当我们创建用户时,系统就会自动创建该用户的家目 ...

  3. cv2 判断图片是冷还是暖

    把图片的颜色空间转为HSV H表示色调(下图横轴), 图片的平均H值可用于区分冷暖

  4. python3 安装pymssql失败 pip3 install pymssql

    python3 安装pymssql失败 报错信息: AttributeError: module 'platform' has no attribute 'linux_distribution' 解决 ...

  5. debian11 简单搭建go环境

    简单环境,目前仅支持单版本go,后续可以考虑直接把go环境放到docker中或podman中,这样每个容器都是一套go版本. 新建文件夹目录 # 我直接用的root账户 cd /root mkdir ...

  6. MySql 数据库、数据表操作

    数据库操作 创建数据库 语法 语法一:create database 数据库名 语法二:create database 数据库名 character set 字符集; 查看数据库 语法 查看数据库服务 ...

  7. Java的TimeStamp

    Java的TimeStamp 很简单,我们可以这样声明 Timestamp ts=new Timestamp(new Date().getTime());这样我们就可以得到时间比较具体的一个类型转换! ...

  8. [oeasy]python0125_汉字打印机_点阵式打字机_汉字字形码

    汉字字形码 回忆上次内容 IBM 将 ASCII 扩展之后 规定了 一个字节的字符集 并制作了 相应的字形库   ​   添加图片注释,不超过 140 字(可选)   这种显示模式和字符大小之下 中文 ...

  9. SQL Server 帐号权限管理及C#编程应用(图解)

    昨晚在群里讲解这部分内容,因为好久没操作过了,差点翻车...今天把它整理一下发出来,方便没听明白的小伙伴学习和理解. 我们平时学习数据库时,要么使用sa帐号,要么用windows默认帐号登录,总之都拥 ...

  10. 顺序表之单链表(C实现)

    // Code file created by C Code Develop #include "ccd.h"#include "stdio.h"#includ ...