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