加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 一、Linux 時間操作命令 :date、hwclock
    • 二、RTC時間框架
    • 三、WT時間和RTC時間同步問題
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

Linux驅(qū)動 | Linux內(nèi)核 RTC時間架構(gòu)

2022/10/09
1970
閱讀需 38 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

上一篇文章我們給大家講解了基于瑞芯微rk3568平臺芯片hym8563驅(qū)動的移植,本文給大家詳細(xì)講解Linux內(nèi)核的時間子系統(tǒng)。

《Linux驅(qū)動|rtc-hym8563移植筆記》

一、Linux 時間操作命令 :date、hwclock

Linux時間有兩個: 系統(tǒng)時間(Wall Time), RTC時間

1)系統(tǒng)時間(WT):

由Linux系統(tǒng)軟件維持的時間,通過Linux命令date查看:

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

獲取到的就是系統(tǒng)時間。

2)RTC時間:

這個時間來自我們設(shè)備上的RTC芯片,通過Linux命令hwclock 可以讀?。?/p>

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.

接下來,通過代碼看下兩者的關(guān)系。

二、RTC時間框架

框架如圖:

從該架構(gòu)可得:

Hardware:提供時間信息(time&alarm),通過一定的接口(比如I2C)和RTC Driver進行交互
Driver:  完成硬件的訪問功能,提供訪問接口,以驅(qū)動的形式駐留在系統(tǒng)
class.c:驅(qū)動注冊方式由class.c:文件提供。驅(qū)動注冊成功后會構(gòu)建rtc_device結(jié)構(gòu)體表征的rtc設(shè)備,并把rtc芯片的操作方式存放到rtc設(shè)備的ops成員中
interface.c:文件屏蔽硬件相關(guān)的細(xì)節(jié),向上提供統(tǒng)一的獲取/設(shè)置時間或Alarm的接口
rtc-lib.c:文件提供通用的時間操作函數(shù),如rtc_time_to_tm、rtc_valid_tm等
rtc-dev.c:文件在/dev/目錄下創(chuàng)建設(shè)備節(jié)點供應(yīng)用層訪問,如open、read、ioctl等,訪問方式填充到file_operations結(jié)構(gòu)體中
hctosys.c/rtc-sys.c/rtc-proc.c:將硬件時鐘寫給 wall time

下面我們從底層往上層來一步步分析。

1、rtc_class_ops 填充

驅(qū)動主要工作是填充 rtc_class_ops結(jié)構(gòu)體,結(jié)構(gòu)體描述了RTC芯片能夠提供的所有操作方式:

struct rtc_class_ops {
    int (*open)(struct device *);
    void (*release)(struct device *);
    int (*ioctl)(struct device *, unsigned intunsigned 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);
};

實現(xiàn):

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表示從設(shè)備地址 rtc0   :注冊的rtc設(shè)備為rtc0

2、class.c和RTC驅(qū)動注冊

class.c文件在RTC驅(qū)動注冊之前開始得到運行:

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

函數(shù)功能:

1、創(chuàng)建名為rtc的class
2、提供PM相關(guān)接口suspend/resume
3、rtc_dev_init():動態(tài)申請/dev/rtcN的設(shè)備號

4、rtc_sysfs_init():rtc類具有的device_attribute屬性

3、RTC驅(qū)動注冊函數(shù)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設(shè)備,所以需要為每一個設(shè)備分配一個ID
    // 對應(yīng)與/dev/rtc0,/dev/rtc1,,,/dev/rtcN
    id = ida_simple_get(&rtc_ida, 00, GFP_KERNEL);
 
    // 2、創(chuàng)建rtc_device設(shè)備(對象)并執(zhí)行初始化
    rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
    rtc->id = id;
    rtc->ops = ops; // 2.1 對應(yīng)RTC驅(qū)動填充的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()創(chuàng)建的rtc_class
    rtc->dev.release = rtc_device_release;
 
    // 2.3 rtc設(shè)備中相關(guān)鎖、等待隊列的初始化
    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:我們都知道,手機等都是可以設(shè)置多個鬧鐘的
    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芯片中設(shè)置了有效的Alarm,則初始化:加入到rtc->timerqueue隊列中
    if (!err && !rtc_valid_tm(&alrm.time))
        rtc_initialize_alarm(rtc, &alrm);
 
    // 4、根據(jù)name參數(shù)設(shè)置rtc的name域
    strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
    // 5、設(shè)置rtc的dev成員中的name域
    dev_set_name(&rtc->dev, "rtc%d", id);
 
    // 6、/dev/rtc0的rtc作為字符設(shè)備進行初始化
    // rtc_dev_prepare-->cdev_init(&rtc->char_dev, &rtc_dev_fops);
    rtc_dev_prepare(rtc);
 
    // 7、添加rtc設(shè)備到系統(tǒng)
    err = device_register(&rtc->dev);
 
    // 8、rtc設(shè)備作為字符設(shè)備添加到系統(tǒng)
    // 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 %sn",
            rtc->name, dev_name(&rtc->dev));
 
    return rtc;
}

有了 /dev/rtc0后,應(yīng)用層就可以通過 open/read/ioctl操作RTC設(shè)備了,對應(yīng)與內(nèi)核的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

硬件抽象,即屏蔽具體的硬件細(xì)節(jié),為上層用戶提供統(tǒng)一的調(diào)用接口,使用者無需關(guān)心這些接口是怎么實現(xiàn)的。 以RTC訪問為例,抽象的實現(xiàn)位于interface.c文件,其實現(xiàn)基于class.c中創(chuàng)建的rtc_device設(shè)備。 實現(xiàn)原理,以rtc_set_time為例:

drivers/interface.c
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
{
    int err;
    // 1、參數(shù)檢測
    err = rtc_valid_tm(tm);
 
    err = mutex_lock_interruptible(&rtc->ops_lock);
    if (err)
        return err;
 
    // 2、調(diào)用rtc_device中ops結(jié)構(gòu)體的函數(shù)指針
    // ops結(jié)構(gòu)體的函數(shù)指針已經(jīng)在RTC驅(qū)動中被賦值
    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文件系統(tǒng)中的呈現(xiàn)

之前曾建立過名為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

我們系統(tǒng)中只有一個RTC,所以編號為rtc0。

同時發(fā)現(xiàn)rtc0文件為指向/sys/devices/platform/fe5e0000.i2c/i2c-5/5-0051/rtc/rtc0的符號鏈接,

RTC芯片是I2C接口,所以rtc0掛載在I2C的總線上,總線控制器地址fe5e0000,控制器編號為5,RTC芯片作為slave端地址為0x51。

rtc0 設(shè)備屬性:

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

對應(yīng)文件系統(tǒng)中的文件節(jié)點:

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文件系統(tǒng)中的呈現(xiàn)

之前曾rtc0設(shè)備加入到了/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"0NULL, &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驅(qū)動加載-->從RTC同步時間到WT時間

對應(yīng)驅(qū)動代碼:

hctosys.c (driversrtc)
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說明系統(tǒng)在啟動流程的后面才會調(diào)用該函數(shù)去同步時間。

2)瑞芯微時間操作

在瑞芯微的系統(tǒng)中,安卓部分程序其實最終也是依賴**/sys/class/rtc/rtc0** 下的文件節(jié)點實現(xiàn)時間管理功能的。

安卓程序會通過AlarmImpl::getTime、AlarmImpl::setTime()方法來獲得和設(shè)置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: %sn", 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: %sn", 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: %sn", 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: %sn", strerror(errno));
178 done:
179     close(fd);
180     return res;
181 }

系統(tǒng)上電后,會先讀取文件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只要有紐扣電池供電,就會有計時功能,這是就是為什么,我們的設(shè)備關(guān)機并重啟后,仍然能夠顯示正確的時間的原因。

【注意目錄/sys/class/rtc/下文件是需要有訪問權(quán)限的】

瑞芯微對文件權(quán)限的控制由以下文件提供:

device/rockchip/common/sepolicy/vendor/genfs_contexts

只需要按照對應(yīng)的格式增加對應(yīng)文件信息即可。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

公眾號『一口Linux』號主彭老師,擁有15年嵌入式開發(fā)經(jīng)驗和培訓(xùn)經(jīng)驗。曾任職ZTE,某研究所,華清遠(yuǎn)見教學(xué)總監(jiān)。擁有多篇網(wǎng)絡(luò)協(xié)議相關(guān)專利和軟件著作。精通計算機網(wǎng)絡(luò)、Linux系統(tǒng)編程、ARM、Linux驅(qū)動、龍芯、物聯(lián)網(wǎng)。原創(chuàng)內(nèi)容基本從實際項目出發(fā),保持原理+實踐風(fēng)格,適合Linux驅(qū)動新手入門和技術(shù)進階。