最新的非pc體系的硬件趨向於記秒數,比如time(2)系統調用的輸出,但是實時時鐘用公曆和24小時表示日期與時間,比如gmtime(3)的輸出。
linux提供兩類的rtc兼容性很高的用戶空間系統調用接口,如下所示:
(1) /dev/rtc ... 這個RTC適合pc體系的系統,而並不適合非x86體系的系統
(2) /dev/rtc0,/dev/rtc1 ... 他們依賴一種架構,這種架構在所有的系統上被RTC芯片廣泛的支持。
程序員必須知道,PC/AT的功能不總是有效,其他的系統可能會有另外的實現。這種情況下,如果在相同的系統結構上使用同樣的RTC API,那麼硬件會有不同的反應。例如,不是每一個RTC都提供IRQ,所以這些不能處理報警中斷;標準的PC系統RTC只能處理未來24小時以內的鬧鐘,而其他系統的RTC可能處理未來一個世紀的任何時間。
老的PC/AT驅動:/dev/rtc
所有基於PC的系統(甚至Alpha體系的機器)都有一個集成的實時時鐘。通常他們集成在計算機的芯片組內,但是也有一些系統是在主板上焊接着摩托羅拉MC146818(或者類似的芯片),他們給系統提供時間和日期,這個時間和日期在系統掉電後仍然會保存。
ACPT(高級配置與電源管理接口)對MC146818的功能進行了標準化,並且在一些方面進行了功能擴展(提供了更長的定時週期,睡眠喚醒功能)。然而這些擴展的功能不適合老的驅動程序。
這個RTC還可以產生頻率從 2HZ 到 8192HZ 的信號,以2的乘方增長。這些信號通過中斷信號線8報告給系統。這個RTC還可以用作定時限制爲24小時的鬧鐘,當定時時間到時產生8號中斷。這個鬧鐘可以設置成任意一個可編程值的子集,這意味着可以設置成任意小時的任意分鐘任意秒,例如,可以將這個時鐘設置成在每個clk產生中斷,從而產生1hz的信號。
這些中斷通過/dev/rtc報告給系統(主設備號10,次設備號135,只讀字符設備),中斷傳回一個無符號整數類型的數據。最低的位包含中斷的類型(更新,鬧鐘,或者期),其他的字節代表了最後一次讀到現在中斷髮生的次數。狀態信息由虛擬文件/proc/driver/rtc產生,前提條件是使能了/proc文件系統。驅動應該提供鎖機制,保證在同一時刻只有一個進程訪問/dev/rtc。
用戶進程通過系統調用read(2)或者select(2)讀取/dev/rtc來獲取這些中斷。當調用這兩個系統調用的時候,進程會阻塞或者退出直到下一個中斷到來。這個功能用在需要不頻繁的獲取數據而又不希望通過輪詢當前時間而佔用CPU時間的情況下。
在高頻率中斷或者高系統負載下,用戶進程應該檢查從上次讀取到現在發生中斷的次數以判斷是否有未處理的中斷。例如,一個典型的 486-33 對/dev/rtc以大於1024hz的頻率進行循環讀,偶爾會產生中斷積累(從上次讀取到現在發生大於一次的中斷)。鑑於此你應該檢查讀取數據的高字節,特別是在頻率高於普通定時器中斷--100hz的情況下。
中斷頻率是可編程的或可以讓他超過64hz,但是隻有root權限的用戶可以這樣做。這樣做可能有點保守,但是我們不希望有惡意的用戶在一個較慢的386sx-16機器上產生很多中斷,這樣會嚴重影響系統的性能。我們可以通過向/proc/sys/dev/rtc/max-user-freq寫入值來修改這個64hz的限制。但是注意你一定要這樣做,減少中斷處理程序的代碼纔會亡羊補牢,使對系統性能的影響降到最小。
如果內核時間是和外部時鐘源同步的,那麼內核將每隔11分鐘就會將時間寫回CMOS時鐘。在這個過程中,內核會關閉rtc週期中斷,如果你的程序在做一些關鍵的工作一定要注意到。如果你的內核不和外部時鐘源同步,那麼內核會一直處理rtc中斷,處理方式根據你具體的應用。
鬧鐘和中斷頻率可以通過系統調用ioctl(2)來設置,ioctl的命令定義在./include/linux/rtc.h。與其長篇大論的介紹怎麼樣使用這個系統調用,還不如寫一個實例程序來的方便,這個程序用來演示驅動的功能,對很多人來說用驅動程序提供的功能來進行應用編程他們會更感興趣。在這個文檔的最後有這段程序。
新接口 “RTC類” 驅動:/dev/rtcn
因爲linux支持許多非ACPI非PC平臺,其中一些平臺有不只一個RTC,所以需要更多可移植性的設計,而不是僅僅在每個系統都實現類似MC146818的接口。在這種情況下,新的“RTC類”構架產生了。他提供不同的用戶空間接口:
(1) /dev/rtcn 和老的接口一樣
(2)/dev/class/rtc/rtcn sysfs 屬性,一些屬性是隻讀的
(3) /dev/driver/rtc 第一個rtc會使用procfs接口。更多的信息會顯示在這裏而不是sysfs。
RTC類構架支持很多類型的RTC,從集成在嵌入式SOC處理器內的RTC到通過I2C,SPI和其他總線連接到CPU的芯片。這個架構甚至還支持PC系統的RTC,包括使用ACPI,PC的一些新特性。
新架構也打破了“每個系統只有一個RTC”的限制。例如,一個低功耗電池供電的RTC是一個分離的I2C接口的芯片,但是系統可能還集成了一個多功能的RTC。系統可能從分離的RTC讀取系統時鐘,但是對於其他任務用集成的RTC,因爲這個RTC提供更多的功能。
SYSFS 接口
--------------------
在/sys/class/rtc/rtcn下面的sysfs接口提供了操作rtc屬性的方法,而不用通過Ioclt系統調用。所有的日期時間都是牆鍾時間,而不是系統時間。
date: RTC提供的日期
hctosys: 如果在內核配置選項中配置了CONFIG_RTC_HCTOSYS,RTC會在系統啓動的時候提供系統時間,這種情況下這個位就是1,否則爲0
max_user_freq: 非特權用戶可以從RTC得到的最大中斷頻率
name: RTC的名字,與sysfs目錄相關
since_epoch: 從紀元開始所經歷的秒數
time: RTC提供的時間
wakealarm: 喚醒時間的時間事件。 這是一種單次的喚醒事件,所以如果還需要喚醒,在喚醒發生後必須復位。這個域的數據結構或者是從紀元開始經歷的妙數,或者是相對的秒數
IOCTL 接口
----------------------
/dev/rtc支持的Ioctl系統調用,RTC類構架也支持。然而,因爲芯片和系統沒有一個統一的標準,一些PC/AT功能可能沒有提供。以相同方式工作的一些新特性,--包括ACPI提供的,--在RTC類構架中表現出的,在老的驅動上不會工作。
(1) RTC_RD_TIME,RTC_SET_TIME .. 每一個RTC都至少支持讀時間這個命令,時間格式爲公曆和24小時制牆鍾時間。最有用的特性是,這個時間可以更新。
(2) RTC_ATE_ON,RTC_ATE_OFF,RTC_ALM_SET,RTC_ALM_READ ... 當RTC連接了一條IRQ線,他還能處理在未來24小時的報警中斷。
(3) RTC_WKALM_SET,RTC_WKALM_RD 。。。 RTCs 使用一個功能更強大的api,他可以處理超過24小時的報警時間。這個API支持設置更長的報警時間,支持單次請求的IRQ中斷。
(4) RTC_UIE_ON,RTC_UIE_OFF ... 如果RTC提供IRQ,他可能也提供每秒更新的IRQ中斷。如果需要,RTC結構可以模仿這個機制。
(5) RTC_PIE_ON,RTC_PIE_OFF,RTC_IRQP_SET,RTC_IRQP_READ ... 如果一個IRQ是週期中斷,那麼這個IRQ還有可設置頻率的特性(頻率通常是2的n次方)
很多情況下,RTC報警時鐘通常是一個系統喚醒事件,用於將Linux從低功耗睡眠模式喚醒到正常的工作模式。例如,系統會處於低功耗的模式下,直到時間到了去執行一些任務。注意這些ioctl的一些功能不必在你的驅動程序中實現。如果一個ioctl調用,你的驅動返回ENOIOCTLCMD,那麼這個Ioctl就由通用RTC設備接口處理。下面是一些通用的例子:
(6) RTC_RD_TIME, RTC_SET_TIME: read_time/set_time 函數會被調用。(7) RTC_ALM_SET, RTC_ALM_READ, RTC_WKALM_SET, RTC_WKALM_RD: set_alarm/read_alarm 函數將會被調用.
(8) RTC_IRQP_SET, RTC_IRQP_READ: irq_set_freq 函數將會調用,用來設置頻率,RTC類構架會處理讀請求,而頻率保存在RTC設備結構中的irq_freq域。你的驅動需要在模塊初始化的時候初始化irq_freq,你必須在irq_set_freq函數裏檢查設置的頻率是否在硬件允許的範圍。如果不是那麼驅動應該返回-EINVAL。如果你不需要改變這個頻率,那麼不要定義irq_set_freq這個函數。
(7) RTC_PIE_ON, RTC_PIE_OFF: irq_set_state 函數會被調用。
如果所有的ioctl都失敗了,用下面的rtc-test.c檢查一下你的驅動吧!
- /*
- * Real Time Clock Driver Test/Example Program
- *
- * Compile with:
- * gcc -s -Wall -Wstrict-prototypes rtctest.c -o rtctest
- *
- * Copyright (C) 1996, Paul Gortmaker.
- *
- * Released under the GNU General Public License, version 2,
- * included herein by reference.
- *
- */
- #include <stdio.h>
- #include <linux/rtc.h>
- #include <sys/ioctl.h>
- #include <sys/time.h>
- #include <sys/types.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <errno.h>
- /*
- * This expects the new RTC class driver framework, working with
- * clocks that will often not be clones of what the PC-AT had.
- * Use the command line to specify another RTC if you need one.
- */
- static const char default_rtc[] = "/dev/rtc0";
- int main(int argc, char **argv)
- {
- int i, fd, retval, irqcount = 0;
- unsigned long tmp, data;
- struct rtc_time rtc_tm;
- const char *rtc = default_rtc;
- switch (argc) {
- case 2:
- rtc = argv[1];
- /* FALLTHROUGH */
- case 1:
- break;
- default:
- fprintf(stderr, "usage: rtctest [rtcdev]\n");
- return 1;
- }
- fd = open(rtc, O_RDONLY);
- if (fd == -1) {
- perror(rtc);
- exit(errno);
- }
- fprintf(stderr, "\n\t\t\tRTC Driver Test Example.\n\n");
- /* Turn on update interrupts (one per second) */
- retval = ioctl(fd, RTC_UIE_ON, 0);
- if (retval == -1) {
- if (errno == ENOTTY) {
- fprintf(stderr,
- "\n...Update IRQs not supported.\n");
- goto test_READ;
- }
- perror("RTC_UIE_ON ioctl");
- exit(errno);
- }
- fprintf(stderr, "Counting 5 update (1/sec) interrupts from reading %s:",
- rtc);
- fflush(stderr);
- for (i=1; i<6; i++) {
- /* This read will block */
- retval = read(fd, &data, sizeof(unsigned long));
- if (retval == -1) {
- perror("read");
- exit(errno);
- }
- fprintf(stderr, " %d",i);
- fflush(stderr);
- irqcount++;
- }
- fprintf(stderr, "\nAgain, from using select(2) on /dev/rtc:");
- fflush(stderr);
- for (i=1; i<6; i++) {
- struct timeval tv = {5, 0}; /* 5 second timeout on select */
- fd_set readfds;
- FD_ZERO(&readfds);
- FD_SET(fd, &readfds);
- /* The select will wait until an RTC interrupt happens. */
- retval = select(fd+1, &readfds, NULL, NULL, &tv);
- if (retval == -1) {
- perror("select");
- exit(errno);
- }
- /* This read won't block unlike the select-less case above. */
- retval = read(fd, &data, sizeof(unsigned long));
- if (retval == -1) {
- perror("read");
- exit(errno);
- }
- fprintf(stderr, " %d",i);
- fflush(stderr);
- irqcount++;
- }
- /* Turn off update interrupts */
- retval = ioctl(fd, RTC_UIE_OFF, 0);
- if (retval == -1) {
- perror("RTC_UIE_OFF ioctl");
- exit(errno);
- }
- test_READ:
- /* Read the RTC time/date */
- retval = ioctl(fd, RTC_RD_TIME, &rtc_tm);
- if (retval == -1) {
- perror("RTC_RD_TIME ioctl");
- exit(errno);
- }
- fprintf(stderr, "\n\nCurrent RTC date/time is %d-%d-%d, %02d:%02d:%02d.\n",
- rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900,
- rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
- /* Set the alarm to 5 sec in the future, and check for rollover */
- rtc_tm.tm_sec += 5;
- if (rtc_tm.tm_sec >= 60) {
- rtc_tm.tm_sec %= 60;
- rtc_tm.tm_min++;
- }
- if (rtc_tm.tm_min == 60) {
- rtc_tm.tm_min = 0;
- rtc_tm.tm_hour++;
- }
- if (rtc_tm.tm_hour == 24)
- rtc_tm.tm_hour = 0;
- retval = ioctl(fd, RTC_ALM_SET, &rtc_tm);
- if (retval == -1) {
- if (errno == ENOTTY) {
- fprintf(stderr,
- "\n...Alarm IRQs not supported.\n");
- goto test_PIE;
- }
- perror("RTC_ALM_SET ioctl");
- exit(errno);
- }
- /* Read the current alarm settings */
- retval = ioctl(fd, RTC_ALM_READ, &rtc_tm);
- if (retval == -1) {
- perror("RTC_ALM_READ ioctl");
- exit(errno);
- }
- fprintf(stderr, "Alarm time now set to %02d:%02d:%02d.\n",
- rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
- /* Enable alarm interrupts */
- retval = ioctl(fd, RTC_AIE_ON, 0);
- if (retval == -1) {
- perror("RTC_AIE_ON ioctl");
- exit(errno);
- }
- fprintf(stderr, "Waiting 5 seconds for alarm...");
- fflush(stderr);
- /* This blocks until the alarm ring causes an interrupt */
- retval = read(fd, &data, sizeof(unsigned long));
- if (retval == -1) {
- perror("read");
- exit(errno);
- }
- irqcount++;
- fprintf(stderr, " okay. Alarm rang.\n");
- /* Disable alarm interrupts */
- retval = ioctl(fd, RTC_AIE_OFF, 0);
- if (retval == -1) {
- perror("RTC_AIE_OFF ioctl");
- exit(errno);
- }
- test_PIE:
- /* Read periodic IRQ rate */
- retval = ioctl(fd, RTC_IRQP_READ, &tmp);
- if (retval == -1) {
- /* not all RTCs support periodic IRQs */
- if (errno == ENOTTY) {
- fprintf(stderr, "\nNo periodic IRQ support\n");
- goto done;
- }
- perror("RTC_IRQP_READ ioctl");
- exit(errno);
- }
- fprintf(stderr, "\nPeriodic IRQ rate is %ldHz.\n", tmp);
- fprintf(stderr, "Counting 20 interrupts at:");
- fflush(stderr);
- /* The frequencies 128Hz, 256Hz, ... 8192Hz are only allowed for root. */
- for (tmp=2; tmp<=64; tmp*=2) {
- retval = ioctl(fd, RTC_IRQP_SET, tmp);
- if (retval == -1) {
- /* not all RTCs can change their periodic IRQ rate */
- if (errno == ENOTTY) {
- fprintf(stderr,
- "\n...Periodic IRQ rate is fixed\n");
- goto done;
- }
- perror("RTC_IRQP_SET ioctl");
- exit(errno);
- }
- fprintf(stderr, "\n%ldHz:\t", tmp);
- fflush(stderr);
- /* Enable periodic interrupts */
- retval = ioctl(fd, RTC_PIE_ON, 0);
- if (retval == -1) {
- perror("RTC_PIE_ON ioctl");
- exit(errno);
- }
- for (i=1; i<21; i++) {
- /* This blocks */
- retval = read(fd, &data, sizeof(unsigned long));
- if (retval == -1) {
- perror("read");
- exit(errno);
- }
- fprintf(stderr, " %d",i);
- fflush(stderr);
- irqcount++;
- }
- /* Disable periodic interrupts */
- retval = ioctl(fd, RTC_PIE_OFF, 0);
- if (retval == -1) {
- perror("RTC_PIE_OFF ioctl");
- exit(errno);
- }
- }
- done:
- fprintf(stderr, "\n\n\t\t\t *** Test complete ***\n");
- close(fd);
- return 0;
- }