科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Linux操作系统内核对RTC的编程详解 (5)

Linux操作系统内核对RTC的编程详解 (5)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

(2)从第二个for循环退出后,RTC的Update Cycle已经结束。此时我们就已经把当前时间逻辑定准在RTC的当前一秒时间间隔内。也就是说,这是我们就可以开始从RTC寄存器中读取当前时间值。

作者:赛迪网技术社区 来源:赛迪网技术社区 2007年10月26日

关键字: RTC 内核 Linux 编程

  • 评论
  • 分享微博
  • 分享邮件
 

(2)从第二个for循环退出后,RTC的Update Cycle已经结束。此时我们就已经把当前时间逻辑定准在RTC的当前一秒时间间隔内。也就是说,这是我们就可以开始从RTC寄存器中读取当前时间值。但是要注意,读操作应该保证在244us内完成(准确地说,读操作要在RTC的下一个更新周期开始之前完成,244us的限制是过分偏执的:-)。所以,get_cmos_time()函数接下来通过CMOS_READ()宏从RTC中依次读取秒、分钟、小时、日期、月份和年分。这里的do{}while(sec!=CMOS_READ(RTC_SECOND))循环就是用来确保上述6个读操作必须在下一个Update Cycle开始之前完成。

(3)接下来判定时间的数据格式,PC机中一般总是使用BCD格式的时间,因此需要通过BCD_TO_BIN()宏把BCD格式转换为二进制格式。

(4)接下来对年分进行修正,以将年份转换为“19XX”的格式,如果是1970以前的年份,则将其加上100。

(5)最后调用mktime()函数将当前时间与日期转换为相对于1970-01-01 00:00:00的秒数值,并将其作为函数返回值返回。

函数mktime()定义在include/linux/time.h头文件中,它用来根据Gauss算法将以year/mon/day/hour/min/sec(如1980-12-31 23:59:59)格式表示的时间转换为相对于1970-01-01 00:00:00这个UNIX时间基准以来的相对秒数。其源码如下:

static inline unsigned long 
mktime (unsigned int year, unsigned int mon, 
unsigned int day, unsigned int hour, 
unsigned int min, unsigned int sec) 
{ 
if (0 >= (int) (mon -= 2)) { /* 1..12 -> 11,12,1..10 */ 
mon += 12; /* Puts Feb last since it has leap day */ 
year -= 1; 
} 

return ((( 
(unsigned long) (year/4 - year/100 + year/400 + 367*mon/12 + day) + 
year*365 - 719499 
)*24 + hour /* now have hours */ 
)*60 + min /* now have minutes */ 
)*60 + sec; /* finally seconds */ 
}

(3)set_rtc_mmss()函数

该函数用来更新RTC中的时间,它仅有一个参数nowtime,是以秒数表示的当前时间,其源码如下:

static int set_rtc_mmss(unsigned long nowtime) 
{ 
int retval = 0; 
int real_seconds, real_minutes, cmos_minutes; 
unsigned char save_control, save_freq_select; 

/* gets recalled with irq locally disabled */ 
spin_lock(&rtc_lock); 
save_control = CMOS_READ(RTC_CONTROL); /* tell the clock it's being set */ 
CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL); 

save_freq_select = CMOS_READ(RTC_FREQ_SELECT); /* stop and reset prescaler */ 
CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT); 

cmos_minutes = CMOS_READ(RTC_MINUTES); 
if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) 
BCD_TO_BIN(cmos_minutes); 

/* 
* since we're only adjusting minutes and seconds, 
* don't interfere with hour overflow. This avoids 
* messing with unknown time zones but requires your 
* RTC not to be off by more than 15 minutes 
*/ 
real_seconds = nowtime % 60; 
real_minutes = nowtime / 60; 
if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1) 
real_minutes += 30; /* correct for half hour time zone */ 
real_minutes %= 60; 

if (abs(real_minutes - cmos_minutes) < 30) { 
if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { 
BIN_TO_BCD(real_seconds); 
BIN_TO_BCD(real_minutes); 
} 
CMOS_WRITE(real_seconds,RTC_SECONDS); 
CMOS_WRITE(real_minutes,RTC_MINUTES); 
} else { 
printk(KERN_WARNING 
"set_rtc_mmss: can't update from %d to %d\n", 
cmos_minutes, real_minutes); 
retval = -1; 
} 

/* The following flags have to be released exactly in this order, 
* otherwise the DS12887 (popular MC146818A clone with integrated 
* battery and quartz) will not reset the oscillator and will not 
* update precisely 500 ms later. You won't find this mentioned in 
* the Dallas Semiconductor data sheets, but who believes data 
* sheets anyway ... -- Markus Kuhn 
*/ 
CMOS_WRITE(save_control, RTC_CONTROL); 
CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT); 
spin_unlock(&rtc_lock); 

return retval; 
}

对该函数的注释如下:

(1)首先对自旋锁rtc_lock进行加锁。定义在arch/i386/kernel/time.c文件中的全局自旋锁rtc_lock用来串行化所有CPU对RTC的操作。

(2)接下来,在RTC控制寄存器中设置SET标志位,以便通知RTC软件程序随后马上将要更新它的时间与日期。为此先把RTC_CONTROL寄存器的当前值读到变量save_control中,然后再把值(save_control | RTC_SET)回写到寄存器RTC_CONTROL中。

(3)然后,通过RTC_FREQ_SELECT寄存器中bit[6:4]重启RTC芯片内部的除法器。为此,类似地先把RTC_FREQ_SELECT寄存器的当前值读到变量save_freq_select中,然后再把值(save_freq_select | RTC_DIV_RESET2)回写到RTC_FREQ_SELECT寄存器中。

(4)接着将RTC_MINUTES寄存器的当前值读到变量cmos_minutes中,并根据需要将它从BCD格式转化为二进制格式。

(5)从nowtime参数中得到当前时间的秒数和分钟数。分别保存到real_seconds和real_minutes变量。注意,这里对于半小时区的情况要修正分钟数real_minutes的值。

(6)然后,在real_minutes与RTC_MINUTES寄存器的原值cmos_minutes二者相差不超过30分钟的情况下,将real_seconds和real_minutes所表示的时间值写到RTC的秒寄存器和分钟寄存器中。当然,在回写之前要记得把二进制转换为BCD格式。

(7)最后,恢复RTC_CONTROL寄存器和RTC_FREQ_SELECT寄存器原来的值。这二者的先后次序是:先恢复RTC_CONTROL寄存器,再恢复RTC_FREQ_SELECT寄存器。然后在解除自旋锁rtc_lock后就可以返回了。

最后,需要说明的一点是,set_rtc_mmss()函数尽可能在靠近一秒时间间隔的中间位置(也即500ms处)左右被调用。此外,Linux内核对每一次成功的更新RTC时间都留下时间轨迹,它用一个系统全局变量last_rtc_update来表示内核最近一次成功地对RTC进行更新的时间(单位是秒数)。该变量定义在arch/i386/kernel/time.c文件中:

/* last time the cmos clock got updated */ 
static long last_rtc_update;

每一次成功地调用set_rtc_mmss()函数后,内核都会马上将last_rtc_update更新为当前时间。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章