http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3637782
Linux的RTC驅動相對還是比較簡單的,可以將它作為一個普通的字符型設備,或者一個misc設備,也可以是一個平台設備,這都沒有關系,主要還是對rtc_ops這個文件操作結構體中的成員填充,這里主要涉及到兩個方面比較重要: 1. 在Linux中有硬件時鍾與系統時鍾等兩種時鍾。硬件時鍾是指主機板上的時鍾設備,也就是通常可在BIOS畫面設定的時鍾。系統時鍾則是指kernel中的時鍾。當Linux啟動時,系統時鍾會去讀取硬件時鍾的設定,之后系統時鍾即獨立運作。所有Linux相關指令與函數都是讀取系統時鍾的設定。 系統時鍾的設定就是我們常用的date命令,而我們寫的RTC驅動就是為硬件時鍾服務的,它有屬於自己的命令hwclock,因此使用date命令是不可能調用到我們的驅動的(在這點上開始把我郁悶到了,寫完驅動之后,傻傻的用date指令來測試,當然結果是什么都沒有),我們可以通過hwclock的一些指令來實現更新rtc時鍾——也就是系統時鍾和硬件時鍾的交互。 hwclock –r 顯示硬件時鍾與日期 hwclock –s 將系統時鍾調整為與目前的硬件時鍾一致。 hwclock –w 將硬件時鍾調整為與目前的系統時鍾一致。 2. 第二點就是內核空間和用戶空間的交互,在系統啟動結束,我們實際是處在用戶態,因此我們使用指令輸入的內容也是在用戶態,而我們的驅動是在內核態的,內核態和用戶態是互相不可見的,因此我們需要特殊的函數來實現這兩種形態的交互,這就是以下兩個函數: copy_from_user(從用戶態到內核態) copy_to_user (從內核態到用戶態) 當然這兩個函數需要我們在內核驅動中實現。 RTC最基本的兩個命令就是設置時間,讀取時間。 設置時間——設置時間會調用系統默認的RTC_SET_TIME,很顯然就是處在用戶態的用戶將自己所要設置的時間信息傳遞給內核態, case RTC_SET_TIME: { struct rtc_time rtc_tm; if (copy_from_user(&rtc_tm, (struct rtc_time*)arg, sizeof(struct rtc_time))) return -EFAULT; sep4020_rtc_settime(&rtc_tm);//把用戶態得到的信息傳遞給設置時間這個函數 return 0; } 讀取時間——設置時間會調用系統默認的RTC_RD_TIME,很顯然就是需要通過內核態的驅動將芯片時鍾取出,並傳遞給用戶態 case RTC_RD_TIME: /* Read the time/date from RTC */ { sep4020_rtc_gettime(&septime);//通過驅動的讀函數讀取芯片時鍾 copy_to_user((void *)arg, &septime, sizeof septime);//傳遞給用戶態 } -------------------------------------------------------------------------------------------------------------------- 首先搞清楚RTC在kernel內的作用: linux系統有兩個時鍾:一個是由主板電池驅動的“Real Time Clock”也叫做RTC或者叫CMOS時鍾, 硬件時鍾。當操作系統關機的時候,用這個來記錄時間,但是對於運行的系統是不用這個時間的。 另一個時間是 “System clock”也叫內核時鍾或者軟件時鍾,是由軟件根據時間中斷來進行計數的, 內核時鍾在系統關機的情況下是不存在的,所以,當操作系統啟動的時候,內核時鍾是要讀取RTC時間 來進行時間同步。並且在系統關機的時候將系統時間寫回RTC中進行同步。 如前所述,Linux內核與RTC進行互操作的時機只有兩個: 1) 內核在啟動時從RTC中讀取啟動時的時間與日期; 2) 內核在需要時將時間與日期回寫到RTC中。 系統啟動時,內核通過讀取RTC來初始化內核時鍾,又叫牆上時間,該時間放在xtime變量中。 The current time of day (the wall time) is defined in kernel/timer.c: struct timespec xtime; The timespec data structure is defined in as: struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; 問題1:系統啟動時在哪讀取RTC的值並設置內核時鍾進行時間同步的呢? 最有可能讀取RTC設置內核時鍾的位置應該在arch/arm/kernel/time.c里的time_init函數內. time.c為系統的時鍾驅動部分.time_init函數會在系統初始化時,由init/main.c里的start_kernel函數內調用.X86架構就是在這里讀RTC值並初始化系統時鍾xtime的. ARM架構的time_init代碼如下: /* arch/arm/kernel/time.c */ void __init time_init(void) { if (system_timer->offset == NULL) system_timer->offset = dummy_gettimeoffset; system_timer->init(); #ifdef CONFIG_NO_IDLE_HZ if (system_timer->dyn_tick) system_timer->dyn_tick->lock = SPIN_LOCK_UNLOCKED; #endif } 上 面system_timer->init()實際執行的是時鍾驅動體系架構相關(具體平台)部分定義的init函數,若是s3c2410平台,則執 行的為arch/arm/mach-s3c2410/time.c里定義的s3c2410_timer_init函數.不過 s3c2410_timer_init()也沒有讀RTC的代碼.整個時鍾驅動初始化的過程大致就執行這些代碼. 既然在系統時鍾驅動初始化的過程中沒有讀RTC值並設置內核時鍾,那會在哪設置呢? 我搜了一下,發現內核好象只有在arch/cris/kernel/time.c里有RTC相關代碼,如下: /* arch/cris/kernel/time.c */ /* grab the time from the RTC chip */ //讀RTC的函數 unsigned long get_cmos_time(void) { unsigned int year, mon, day, hour, min, sec; sec = CMOS_READ(RTC_SECONDS); min = CMOS_READ(RTC_MINUTES); hour = CMOS_READ(RTC_HOURS); day = CMOS_READ(RTC_DAY_OF_MONTH); mon = CMOS_READ(RTC_MONTH); ………… return mktime(year, mon, day, hour, min, sec); } 這個函數會在update_xtime_from_cmos內被調用: void update_xtime_from_cmos(void) { if(have_rtc) { xtime.tv_sec = get_cmos_time(); xtime.tv_nsec = 0; } } 另外還有設置rtc的函數 int set_rtc_mmss(unsigned long nowtime); /* write time into RTC chip */ 不過我加了printk測試了一下,好象arch/cris/kernel/time.c這個文件和這兩個函數只是適用與X86? ARM平台啟動時並不走這邊.因此執行不到這些函數。 那ARM平台啟動時,系統是在哪讀RTC的值並對內核時鍾(WallTime)進行初始化的呢? 已解決: 嵌入式Linux內核(ARM)是在系統啟動時執行/etc/init.d/hwclock.sh腳本,這個腳本會調用hwclock小程序讀取RTC的值並設置系統時鍾。 (換句話說,這要取決於你制作的文件系統里是否有這樣的腳本) /* /etc/init.d/hwclock.sh */ DAEMON1=/sbin/hwclock start() { local RET ERROR= [ ! -f /etc/adjtime ] && echo "0.0 0 0.0" > /etc/adjtime log_status_msg "Setting the System Clock using the Hardware Clock as reference..." -n # Copies Hardware Clock time to System Clock using the correct # timezone for hardware clocks in local time, and sets kernel # timezone. DO NOT REMOVE. [ "$HWCLOCKACCESS" != no ] && $DAEMON1 --hctosys $GMT $BADYEAR # # Now that /usr/share/zoneinfo should be available, # announce the local time. # log_status_msg "System Clock set. Local time: `date`" log_status_msg "" return 0 } hwclock最先讀取的設備文件是 /dev/rtc ,busybox里面的hwclock是這樣實現的: static int xopen_rtc(int flags) { int rtc; if (!rtcname) { rtc = open("/dev/rtc", flags); if (rtc >= 0) return rtc; rtc = open("/dev/rtc0", flags); if (rtc >= 0) return rtc; rtcname = "/dev/misc/rtc"; } return xopen(rtcname, flags); } 2. 內核如何更新RTC時鍾? 通過set_rtc函數指針指向的函數,set_rtc在arch/arm/kernel/time.c內 /* arch/arm/kernel/time.c */ /* * hook for setting the RTC's idea of the current time. */ int (*set_rtc)(void); 但是set_rtc函數指針在哪初始化的呢?set_rtc應該是和RTC驅動相關的函數. 搜索kernel源碼后發現,好象內核其他地方並沒有對其初始化。待解決! set_rtc在do_set_rtc內調用 static inline void do_set_rtc(void) { …… if (set_rtc()) /* * rtc update failed. Try again in 60s */ next_rtc_update = xtime.tv_sec + 60; else next_rtc_update = xtime.tv_sec + 660; /* update every ~11 minutes by default*/ } do_set_rtc在timer_tick里調用 /* * Kernel system timer support. */ void timer_tick(struct pt_regs *regs) { profile_tick(CPU_PROFILING, regs); do_leds(); do_set_rtc(); do_timer(1); …… } timer_tick為Kernel提供的體系架構無關的時鍾中斷處理函數,通常會在體系架構相關的時鍾中斷處理函數內調用它。如s3c2410是這樣的: 在arch/arm/mach-s3c2410/time.c中 * IRQ handler for the timer */ static irqreturn_t s3c2410_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) { write_seqlock(&xtime_lock); timer_tick(regs); write_sequnlock(&xtime_lock); return IRQ_HANDLED; } *nix 下 timer機制 標准實現,一般是用 sigalarm + setitimer() 來實現的,但這樣就與 select/epoll 等邏輯有所沖突,我希望所有 event 的通知邏輯都從 select/epoll 中觸發。(FreeBSD 中 kqueue 默認就有 FILTER_TIMER,多好) ps. /dev/rtc 只能被 open() 一次,因此上面希望與 epoll 合並的想法基本不可能了~ 下面是通過 /dev/rtc (real-time clock) 硬件時鍾實現的 timer機制。:-) 其中 ioctl(fd, RTC_IRQP_SET, 4) 的第三個參數只能是 2, 4, 8, 16, 32 之一,表示 xx Hz。 ------------------------------------------------- #include <linux/rtc.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <time.h> #include <err.h> int main(void) { unsigned long i = 0; unsigned long data = 0; int fd = open("/dev/rtc", O_RDONLY); if ( fd < 0 ) errx(1, "open() fail"); /* set the freq as 4Hz */ if ( ioctl(fd, RTC_IRQP_SET, 4) < 0 ) errx(1, "ioctl(RTC_IRQP_SET) fail"); /* enable periodic interrupts */ if ( ioctl(fd, RTC_PIE_ON, 0) < 0 ) errx(1, "ioctl(RTC_PIE_ON)"); for ( i = 0; i < 100; i++ ) { if ( read(fd, &data, sizeof(data)) < 0 ) errx(1, "read() error"); printf("timer %d\n", time(NULL)); } /* enable periodic interrupts */ if ( ioctl(fd, RTC_PIE_OFF, 0) < 0 ) errx(1, "ioctl(RTC_PIE_OFF)"); close(fd); return 0; } -------------------------------------------------------------------------------------------------------------------- User mode test code: #include <stdio.h> #include <stdlib.h> #include <linux/rtc.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> int main(void) { int i, fd, retval, irqcount = 0; unsigned long tmp, data; struct rtc_time rtc_tm; fd = open ("/dev/rtc", O_RDONLY); if (fd == -1) { perror("/dev/rtc"); exit(1); } // Alarm example,10 mintues later alarm /* Read the RTC time/date */ retval = ioctl(fd, RTC_RD_TIME, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(1); } fprintf(stderr, "Current 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); // Setting alarm time rtc_tm.tm_min += 10; 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; // setting retval = ioctl(fd, RTC_ALM_SET, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(1); } /* Read the current alarm settings */ retval = ioctl(fd, RTC_ALM_READ, &rtc_tm); if (retval == -1) { perror("ioctl"); exit(1); } 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 after setting*/ retval = ioctl(fd, RTC_AIE_ON, 0); if (retval == -1) { perror("ioctl"); exit(1); } /* This blocks until the alarm ring causes an interrupt */ retval = read(fd, &data, sizeof(unsigned long)); if (retval == -1) { perror("read"); exit(1); } irqcount++; fprintf(stderr, " okay. Alarm rang.\n"); } ------------------------------------------------------------------------------------------------------------------------------------------------ S3C2410 RTC(Real Time Clock)簡介 實時時鍾(RTC)單元可以在系統電源關半閉的情況下依靠備用電池工作。RTC可以通過使用STRB/LDDRB這兩個ARM指令向CPU傳遞8位數據(BCD碼)。數據包括秒、分、小時、日期、天、月、和年。RTC單元依靠一個外部的32.768kHZ的石晶,也可以執行報警功能。 特性 BCD碼:秒、分、時、日期、天、月和年 潤年產生器 報警功能:報警中斷,或者從power-off狀態喚醒。 移除了2000年的問題 獨立的電源引角:RTCVDD 為RTOS內核時間Tick time支持毫秒Tick time中斷。 Round reset 功能。 RTC在power-off模式或者正常操作模式時可以在一指定的時間產生一個報警信號。在正常操作模式下,報警中斷(ALMINT)被激活,在power-off模式下,電源管理喚醒信號(PMWKUP)和ALMINT一起被激活。RTC報警寄存器(RTCALM)決定報警的enable/disable狀態和報警時間設定的條件。 RTC TICK TIME被用於中斷請求。TICNT寄存器有一個中斷使能位和中斷的計數值。當計數值到達0時TICK TIME中斷。所以中斷的周期如下: 周期= (n+1 ) /128 秒 n:Tick time計數值(1~127) 這個RTC time tick可以被用於實時操作系統(RTOS)內核 time tick。如果time tick通過RTC time tick產生,那么RTOS的時間相關的功能就需要總是與實時時間同步。 ROUND RESET 功能 Rund reset功能可以通過RTC round reset寄存器(RTCRST)來執行。 The round boundary (30, 40, or 50 sec.) of the second carry generation can be selected, and the second value is rounded to zero in the round reset. For example, when the current time is 23:37:47 and the round boundary is selected to 40 sec, the round reset changes the current time to 23:38:00. NOTE All RTC registers have to be accessed for each byte unit using the STRB and LDRB instructions or char type pointer. --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 在.../drivers/rtc/Makefile中與我們有關的項有 obj-$(CONFIG_RTC_LIB) += rtc-lib.o obj-$(CONFIG_RTC_HCTOSYS) += hctosys.o obj-$(CONFIG_RTC_CLASS) += rtc-core.o rtc-core-y := class.o interface.o rtc-core-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o rtc-core-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o 其中 rtc-lib.c :提供了一些時間格式相互轉化的函數。 hctosys.c:在啟動時初始化系統時間。 RTC核心文件: class.c interface.c rtc-dev.c:字符設備的注冊和用戶層文件操作函數接口。 rtc-proc.c rtc-sysfs.c rtc-s3c.o:S3C2410 RTC的芯片平台驅動。//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4> 在根文件系統的 做的動作, 把 pc linux上的 /etc/localtime 復制到 板子的 /etc/下面即可 5> mknod /dev/rtc c 254 0 下面的動作只需做一次 ,一旦寫入RTC chip后, chip就自己計時了,除非電池沒電了。 板子第一次啟動后, 假如設置系統時間為2007年10月2日,13:49分,可以這樣設置 1> date 100213492007 2> hwclock –w 如果沒有出錯, 就已經把2007年10月2日,13:49分 寫入RTC chip了, 測試: 反復執行hwclock ,看看是否時間在變化。 3> 重啟板子, 測試, 執行hwclock ,看看時間是否在流逝 。