Chinaunix首页 | 论坛 | 博客
  • 博客访问: 819830
  • 博文数量: 117
  • 博客积分: 2583
  • 博客等级: 少校
  • 技术积分: 1953
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-06 22:58
个人简介

Coder

文章分类
文章存档

2013年(1)

2012年(10)

2011年(12)

2010年(77)

2009年(13)

2008年(4)

分类: 嵌入式

2010-04-18 10:59:22

Linux 实时时钟(RTC)驱动
=======================================
 
Linux开发者在谈论“实时时钟”的时候,他们指的是某种可以跟踪墙上时间,
并且有备用电池所以在系统关闭的时候仍然可以运行的东西。这种时钟通常不跟
踪本地时区夏时制时间——除非他们有MS-Windows双系统——而将被设置为世界
标准时间(Coordinated Universal Time UTC, 以前的格林尼治标准时间
"Greenwich Mean Time")
 
最新的非PC硬件倾向于只计数秒,像time()系统调用报告的那样,但是,RTCs
常也会显示公历日历和24小时时间,像gmtime()呈现的那样。
 
Linux有两个大的兼容的用户空间RTC API族你也许需要知道的:
    *  /dev/rtc ... PC兼容系统提供的RTC,所以他是不能够移植到non-x86
统的
    * /dev/rtc0, /dev/rtc1 ... 是一个在所有的系统上被许多RTC所支持的框架
的一部分。
程序员应该明白的是PC/AT功能并不总是可用的,有些系统可以做的多得多。这即
是说,RTCs在所有的框架下(当然使用不同的文件名)使用相同的API去发出请
求,但硬件可能提供不同的功能。比如并不是每一个RTC都接通一个IRQ,所以它
们并不都能够产生报警,而且能产生报警的PC RTCs也只能产生在未来24小时这内
的报警,而其他硬件则可以在即将到来的一个世纪的任何时间产生一个报警。
 
老式PC/AT兼容驱动:/dev/rtc
--------------------------------------
所有的PC(即使是Alpha机)都内建有一个实时时钟。通常它们嵌在计算机的芯片
组里,而有些可能在板子上有一个Motorola MC146818 (或其仿制品)芯片。这是
一个在你的计算机关闭的时候可以保持日期和时间的时钟。
 
ACPI已经使MC146818功能成为了标准,并且在某些方面扩展了他(允许更长的报
警周期,从休眠中唤醒等)。老的驱动是没有这种功能的。
 
然而,它也被用来可以产生一个信号,信号的频率范围从较慢的2Hz到相对较快的
8192Hz,。这些信号通过中断号8来报告。(Oh! So *that* is what IRQ 8 is
for...),它也可以作为一个24小时警报器来运行,当警报器熄灭的时候产生一
个。警报器也可以被编程而只检查三个可编程值中的一个子集,也就是,比如,
它可以被设定为在每一个小时的第30分钟的第30秒钟响铃。始终也可以被设定为
一更新就产生一个中断,这样就产生一个1Hz的信号
However it can also be used to generate signals from a slow 2Hz to a
relatively fast 8192Hz, in increments of powers of two. 
 
中断以一个unsigned long的形式通过/dev/rtc major 10, minor 135, 只读的
字符设备)。两个字节包含产生的中断的类型(update-done, alarm-rang, or
periodic)
,剩余的字节包含自从上一次读取以来,中断的数量。如果/proc可用,
那么状态信息通过pseudo-file /proc/driver/rtc来报告。驱动要内建锁定,以
使在同一个时间,只有一个进程被允许打开/dev/rtc接口。
 
一个用户进程可以通过在/dev/rtc上调用一个read(2)或一个select(2)监视来监
视这些中断——每一个都要阻塞,直到收到下一个中断。这对于像高频率的数据
采集这样的情况是很有用的,因为并不想要因为轮询gettimeofday而将CPU烧到
100%etc
 
在高频率下,或者在高负载下,用户进程应该检查自从上次读取以来接收到的中
断的数量,如果已经有中断"pileup(堆积)"则发出声响。仅仅作为一个参考,
一个典型的486-33/dev/rtc运行一个紧凑的读循环将开始遭受频率在1024Hz
上的偶尔的中断堆积(即自从上一次读取以来 > 1 IRQ)所以你应该检查你读取
的值的高字节,尤其是频率高于一般的100Hz时钟中断的情况下。
 
应该只允许root用户编程和或使能中断频率在64Hz以上。这可能有点守旧,但
是我们不希望一个居心不良的用户在一个慢速的386sx-16上产生大量的IRQs,因
为这将对性能产生负面影响。这个64Hz的限制可以通过向
/sys/dev/rtc/max-user-freq中写入一个不同的值来改变。注意,中断处理程序应
该只有几行代码,从而来最小化产生这种效应的任何可能性。
 
如果内核时间用一个外部的源来同步,内核将每隔11分钟将时间写回CMOS一次。
在做这个的过程中,内核短暂的关闭RTC周期中断,所以当你在做重要的事情的
时候要意识到这一点。如果你不用外部的源(通过ntp或其他什么)来同步内核
时间,那么内核将使它的触手远离RTC,从而允许你的应用独占的访问设备。
 
报警或中断的频率通过include/linux/rtc.h列出的各种ioctl(2) 调用来写入
RTC。包含一个小的测试程序来演示如何使用ioctl命令,并演示驱动的特性,
可能比写50页来描述ioctl更加有用。这对于那些将要写使用驱动的应用程序的
人来说,可能更加有用的多。参考本文档下面的代码。
 
(最初的/dev/rtc驱动是由Paul Gortmaker写的。)
 
 
新的可移植的"RTC Class" 驱动:  /dev/rtcN
--------------------------------------------
由于Linux支持许多non-ACPI  non-PC平台,有些平台支持许多个RTC类型的时
钟,所以需要一个更具可移植性的解决方案而不是期待每一个系统上都有一个备
用电池型的MC146818仿制品。因此一个新的"RTC Class"框架被定义。它提供三
种不同的用户空间接口:
* /dev/rtcN ... 和老式的/dev/rtc接口大致相同
    * /sys/class/rtc/rtcN ... sysfs 属性支持只读的对某些RTC属性的访问
    * /proc/driver/rtc ... 第一个RTC (rtc0)可以使用一个procfs接口来显露
自己。 更多的信息(目前)在这里显示而不是通过sysfs
 
RTC Class框架支持支持各种的RTC,范围从那些集成到可嵌入SoC处理器的RTC
通过I2C, SPI,或其他什么总线来和主CPU通信的RTC。甚至支持PC-style 的通过
ACPI
而在较新的PCs上具有的特性RTCs
 
新的框架也去除了"one RTC per system"的限制。比如,也许有一个低功耗的使
用备用电池的分离的I2C RTC芯片,同时有一个高功能的RTC集成在SOC中。那样的
系统可能通过分离的RTC来读取系统时钟,而由于集成的RTC的更好的功能而使它
来做其他的事情。
 
系统接口
---------------
 
/sys/class/rtc/rtcNsysfs接口提供了不需要通过ioctls命令而访问rtc属性的
能力。所有的日期和时间是RTC的时区的时间,而不是系统时间。
 
date:            RTC-provided date
hctosys:        1 if the RTC provided the system time at boot via the
                CONFIG_RTC_HCTOSYS kernel option, 0 otherwise
max_user_freq:  The maximum interrupt rate an unprivileged user may 
request from this RTC.
name:           The name of the RTC corresponding to this sysfs 
directory
since_epoch:    The number of seconds since the epoch according to the 
RTC
time:           RTC-provided time
wakealarm:      The time at which the clock will generate a system wakeup
                event. This is a one shot wakeup event, so must be reset
after wake if a daily wakeup is required. Format is 
either  seconds since the epoch or, if there's a
leading +,seconds in the future.
 
IOCTL INTERFACE
---------------
 
不仅/dev/rtc 支持ioctl()调用,RTC class framework同样支持。然而,由于芯
片和系统不是标准化的,一些PC/AT功能可能不提供。同样的,一些通过
RTC class framework而支持更新的特性——包含在ACPI可用的系统中——却不能
被老的驱动所支持。
* RTC_RD_TIME, RTC_SET_TIME ... 每一个 RTC最少要支持读取时间,以一个
公历日历来返回结果和24小时墙钟时间,物尽其用,这个时间也许也可以被
更新。
 
* RTC_AIE_ON, RTC_AIE_OFF, RTC_ALM_SET, RTC_ALM_READ ... RTC被连接
到了一条IRQ线,那么它可以产生一个24小时内的报警IRQ
(
参考 RTC_WKALM_* )   
 
    * RTC_WKALM_SET, RTC_WKALM_RD ... 可以支持超过24小时的报警的RTCs 使
用一个功能略微强一些的API,这些API支持通过一个请求设定更长的报警
时间和使能中断(使用同 EFI firmware一样的模型)
 
    * RTC_UIE_ON, RTC_UIE_OFF ... 如果RTC提供IRQs,它可能也提供了在
"
秒(seconds"计数器改变的时候更新IRQs的能力。如果需要,RTC
framework
可以仿真这种机制。
 
* RTC_PIE_ON, RTC_PIE_OFF, RTC_IRQP_SET, RTC_IRQP_READ ... 另一个
IRQ线是一个可设置频率(通常为2^N Hz)周期性的IRQ的时候可以访问
的特性。
 
许多情况下,RTC警报可能是一个系统唤醒事件,被用来使Linux退出低功耗睡眠
状态(或休眠)而回到全功能状态。比如,一个系统可以在执行某些任务调度的
时间到来之前进入深度节能状态。
 
注意,这些ioctls中的许多并不需要实际在你的驱动中实现。如果你的驱动返回
ENOIOCTLCMD,则通用的rtc-dev接口,将会非常好的来处理这些ioctls
一些通用的例子:
 
    * RTC_RD_TIME, RTC_SET_TIME: the read_time/set_time functions will 
be called with appropriate values.
 
    * RTC_ALM_SET, RTC_ALM_READ, RTC_WKALM_SET, RTC_WKALM_RD: the 
set_alarm/read_alarm functions will be called.
 
    * RTC_IRQP_SET, RTC_IRQP_READ: the irq_set_freq function will be 
called to set the frequency while the framework will handle the
read for you since the frequency is stored in the irq_freq member
 of the rtc_device structure.  Your driver needs to initialize
the irq_freq member during init.  Make sure you check the
requested frequency is in range of your hardware in the
 irq_set_freq function.  If it isn't, return -EINVAL.  If you
cannot actually change the frequency, do not define irq_set_freq.
 
    * RTC_PIE_ON, RTC_PIE_OFF: the irq_set_state function will be called.
If all else fails, check out the rtc-test.c driver!
 
-------------------- 8< ---------------- 8< -----------------------------
 
/*
 *      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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
 
/*
 * 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;
}
说明:翻译的内核文档中RTC-API部分,原文在Documentation/rtc.txt
阅读(7148) | 评论(12) | 转发(4) |
0

上一篇:Linux混杂设备驱动

下一篇:S3C2440 RTC驱动

给主人留下些什么吧!~~