Linux系統時間與RTC時間【轉】

http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3637782php

 

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計數值(1127)

這個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 ,看看時間是否在流逝 。
相關文章
相關標籤/搜索