Linux 实时时钟驱动程序

来源:互联网 发布:java静态网页 编辑:程序博客网 时间:2024/04/30 16:50

当Linux开发者谈论一个实时时钟,他们通常指的是某种能记录墙上时间,并且有备用电池,以至于在系统关机的时候仍然可以工作的器件。这样的时钟通常不会追踪本地时区和夏令时间。除非他们和MS-Windows 双启动,但是会被替换设置成UTC时间。
最新的非PC硬件倾向于仅仅记录秒数,像time(2)系统调用返回的一样,但是RTC一般表示的是格里历(阳历),24小时制的时间。

Linux 有两个系列广泛兼容的用户空间RTC API你可能需要知道:
• /dev/rtc : PC机及兼容机系统提供的RTC接口,这种接口对于非x86系统来说不是很轻便。
• /dev/rtc0,/dev/rtc1 :是各种系统上,被各种RTC芯片所支持的框架的一部分。
程序员需要明白的是那些PC/AT机的功能不是任何时候都是可用的,一些系统可以支持的功能更多。也就是说,所有的实时时钟使用相同的API来向支持上述两种RTC的框架发出请求,但是硬件可能不支持同样的功能。例如,不是所有的RTC都会连接在一个中断上的,所以他们不是都能发出闹钟的,并且将来那些标准的PC上的RTC只能够发出24小时内的闹钟,其他的硬件可能安排即将到来一个世纪的任何一个时间的闹钟。

老的PC/AT机兼容的驱动:/dev/rtc

所有的PC机(包括Alpha机)都有包含一个实时时钟。通常他们在计算机的芯片组中,有些实际可能板载一个摩托罗拉MC146818(或克隆版)。这个就是在你的电脑关闭的时候保持日期和时间的时钟。
ACPI(高级配置和电源接口)标准化了MC14618的功能,并在一些方面做了扩展(更长的闹钟定时,从休眠中唤醒)。这些功能在老的驱动中没有被暴露出来。
然而RTC也可以用来产生信号,从比较慢的2Hz以2的倍数增长,到相对快的8192Hz。这些信号通过8号中断报告(这就是8号中断的作用)。它也可以作为24小时的闹钟,当闹钟到来时候,拉高IRQ8。闹钟可以被编程为只检查三个可编程值的任意组合,这意味着闹钟的响铃可以被设置在每个小时的第30分钟30秒。实时时钟也可以设置为每更新一个时间产生一个中断,因此可以产生一个1Hz的信号。
中断通过/dev/rtc(主设备号10,次设备号135,只读字符设备)以unsigned long的形式报告。低字节包含中断的类型(update-done, alarm-rang, 或periodic),剩余的字节保存从上次读取开始到本次读取的中断数量。如果使能了/proc文件系统,状态信息可以通过/proc/driver/rtc报告出来。RTC的驱动程序内部有锁,所以同一时刻只允许一个进程打开/dev/rtc接口。

用户进程可以通过在/dev/rtc上使用read(2)或select(2)来监控这些中断——在下一个中断到来之前,这两个接口将会阻塞用户进程。例如,对于那些以相当高的频率获取数据,但是又不想通过轮询gettimeofday使CPU的占用率达到100%的操作,这种方式是比较有用的。
在高频率下,或高负载下,用户进程应该检查从上次读取以来的中断数量,用来判断是否有中断累积。以下例子仅供参考,一个在/dev/rtc上运行紧密循环读取的486-33,在频率大于1024Hz的时候,将要开始偶尔遭遇中断累积。所以你真的需要检查你读取数值的高字节,特别是在频率大于普通定时器中断的时候,比如100Hz。

只有root用户才允许编程使RTC的中断频率大于64Hz。这可能有些保守,但是我不想让一些邪恶的用户在慢的386sx-16上产生太多的中断,这可能会对机器的性能造成负面影响。同过写不同的值到/proc/sys/dev/rtc/max-user-freq可以改变64Hz的限制。需要指出的是,中断处理函数只有很少的代码,以至于可以最小的影响性能。

当然,如果内核时间和外部时钟源同步,内核会每11分钟将时间写回到CMOS时钟。在内核将时间写入CMOS的过程中,内核会短暂地关闭RTC的periodic中断,所以必须要注意这一点,如果你正在做重要的工作。如果你不将内核和外部时间源的同步(通过ntp或其他方式),内核将不会触及RTC,这可以允许你的应用专享这个设备。

闹钟和中断频率被编程到RTC通过./include/linux/rtc.h中列出来的各种ioctl(2)调用。比起写50页来描述这些ioctl,可能用一个小的测试程序来演示如何使用这些接口更有用,同时这也可以演示驱动的功能。这个可能对于那些对写一个用这个驱动的应用的人来说更有用。演示代码在本文当的最后。
(原本的/dev/rtc 驱动是Paul Gortmaker 写的)

新的可移植的 “RTC Class” 驱动:/dev/rtcN
由于Linux 支持很多非 ACPI和非PC的平台,这些平台中,有些平台有多个实时时钟,所以它需要一个更方便更灵活的方案。因此一个新的”RTC Class” 框架就诞生了。它提供了三种用户空间的接口:
• /dev/rtcN 和老的/dev/rtc接口一致
• /sys/class/rtc/rtcN sysfs属性,支持只读访问一些RTC的属性
• /proc/driver/rtc 系统时钟的RTC可能在procfs暴露自己的接口。如果系统时钟没有RTC,默认使用rtc0。这里(procfs)展示的信息比sysfs更详细。

这个实时时钟类框架支持各种RTC,它支持从集成在芯片内部的实时时钟到使用i2c,spi或使用其他总线和主处理器通信的独立芯片的实时时钟。它甚至支持PC风格的实时时钟,包括比较新的PC上使用APCI的实时时钟。
新的实时时钟框架同样移除了一个系统上只能有一个实时时钟的限制。例如,使用备用电池的低功耗实时时钟可能是一个I2C接口的独立芯片,但是一个高级RTC被集成在SOC内部。这种系统可能从外部独立实时时钟芯片中读取系统时间,但是使用内部集成的去完成其他任务,因为内部的集成的实时时钟功能更加强大。

SYSFS 接口

/sys/class/rtc/rtcN下的sysfs接口提供了不需要ioctl调用来访问各种rtc属性的接口。所有的日期和时间是在RTC的时域内的,而不是系统时间。
date:RTC提供的日期
hctosys:如果通过内核配置选项CONFIG_RTC_HCTOSYS,使RTC在系统启动时提供系统时间,hctosys值为1,否则hctosys值为0。
max_user_freq::非特权用户可以向该RTC请求中断的最大比率。
name:这个sysfs对应RTC 的名字。
since_epoch:根据RTC,从上次纪元到现在的秒数。
time:RTC提供的时间。
wakealarm:时钟将要产生系统唤醒事件的时间。这是一个一次性事件,如果需要每天唤醒,唤醒后必须复位。格式要么是上次纪元以来的秒数,或者,如果有一个“+”在前面,代表未来的秒数。

IOCTL 接口

/dev/rtcN所支持的ioctl()调用,RTC 类框架也同样支持。然而, 由于一些芯片和系统不是标准的,一些PC/AT的功能可能没有提供。同样,包括那些ACPI支持的新功能被RTC框架暴露出来,但是这些功能是老的驱动不能支持的。
• RTC_RD_TIME,RTC_SET_TIME——任何一个RTC至少支持读取时间,返回格里历日期和24小时制的墙上时间。这个时间可能被更新,这个会很有用。
• RTC_AIE_ON,RTC_AIE_OFF,RTC_ALM_SET,RTC_ALM_READ——当RTC被连接到一个中断上,RTC可以经常发送一个未来长达24小时的闹钟中断请求(最好使用RTC_WKALM*调用)。
• RTC_WKALM_SET,RTC_WKALM_RD——那些可以在未来24小时之内,用一个轻量且有效的API发送闹钟的RTC,它支持使用一个请求来设置更长时间的闹钟时间和使能它的中断(和EFI固件使用相同的设计)。
• RTC_UIE_ON,RTC_UIE_OFF——如果RTC提供中断,RTC框架将会模拟这个机制。
• RTC_PIE_ON,RTC_PIE_OFF,RTC_IRQP_SET,RTC_IRQ_READ——这些ioctls调用通过内核的hrtimer来模拟。

在很多情况下,RTC闹钟可能是一个系统唤醒事件,通常用来使Linux从低功耗睡眠状态(或休眠状态)返回一个完全可使用的状态。例如,系统可能进入一个深度节能状态,直到是时候执行一些被调度的任务。
值得说明的是,这些ioctl调用通过通用的rtc-dev接口来处理。一些通用的例子:
• RTC_SET_TIME,RTC_READ_TIME:read_time/set_time函数将被使用一个适当的值来调用。
• RTC_ALM_SET,RTC_ALM_READ,RTC_WKALM_SET,RTC_WKALM_RD:获取或设置闹钟定时器。可能调用set_alarm驱动函数。
• RTC_IRQP_SET,RTC_IRQP_READ:这些被框架代码仿真。
• RTC_PIE_ON,RTC_PIE_OFF:这些也被框架代码所仿真。
如果这些都失败了,检查rtc-test.c驱动。

/* *      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 <stdio.h>#include <linux/rtc.h>#include <sys/ioctl.h>#include <sys/time.h>#include <sys/types.h>#include <fcntl.h>#include <unistd.h>#include <stdlib.h>#include <errno.h>/* * 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;}
0 0
原创粉丝点击