分类: LINUX
2016-02-18 14:16:07
原文地址:用户空间访问RTC 作者:formycuteboy
RTC是“RealTimeClock”的简称。RTC时钟一般由板载电池供电,系统掉电后仍可以照常运行,系统启动的时候从RTC读取时间作为系统时间的初始值,系统启动以后内核会根据系统中断不停的更新系统时间,并每过11分钟将内核维护的系统时间写入RTC一次。系统掉电之后,RTC的时间不会丢失,而且会根据输入的震荡时钟信号不停的更新自己的时间。
除了内核,用户空间也可以通过设备节点访问RTC。用户空间可以读取或者设置RTC的时间值,也可以在RTC的设备节点上睡眠,并设置RTC何时将自己唤醒,这对于需要定时或者周期性的唤醒的用户进程特别有用。
本章介绍用户空间通过设备节点使用RTC的方法,不涉及内核空间使用RTC的知识。用户需要注意的是,虽然RTC设备节点的接口是固定的(所有的IOCTL命令在所有Linux系统上也都是一致的),用户自己系统(或开发板)上的RTC设备节点也许并不支持某些功能,这依赖于实际使用的RTC芯片和RTC设备驱动。因此这里介绍的某些功能,用户或许无法在自己的系统(或开发板)上使用。
对RTC的设备节点使用ioctl()接口的时候需要用到rtc_time结构体,它的定义如程序清单 1 .1所示。
程序清单1.1 rtc_time
structrtc_time {
inttm_sec; /* 秒,0~60(60是闰秒的需要)*/
inttm_min; /* 分钟,0~59*/
inttm_hour; /* 小时,0~23 */
inttm_mday; /* 本月中的第几天,1~31 */
inttm_mon; /* 自一月以来的第几个月,0~11*/
inttm_year; /* 自1900年以来的年数*/
inttm_wday; /* 本周的第几天,0~6,星期天是0 */
inttm_yday; /* 一年当中的第几天,0~365*/
inttm_isdst; /* 夏令时标志*/
};
对于最后一个成员变量tm_isdst,当它为正数的时候意味着正处在夏令时中,当是0的时候表示夏令时已经结束,负数表示不使用夏令时。1992年后中国大陆地区不再使用夏令时,因此这个成员应该总是负数。夏令时的意思是“DaylightSaving Time”,是在太阳升起较早的季节(通常是夏季)人为将时钟拨快一小时,让人们充分利用日光以节约能源。
使用ioctl()设置wakeupalarm中断的时候需要用到rtc_wkalrm结构体做参数,它的定义如程序清单 1 .2所示。
程序清单1.2 rtc_wkalrm
structrtc_wkalrm {
unsignedchar enabled; /* 0 = 禁止alarm,1 = 使能alarm */
unsignedchar pending; /* 0 = alarm未挂起,1 = alarm挂起(已发生)*/
structrtc_time time; /* 设置的alarm中断发生的时刻 */
};
当设置alarm中断的时间的时候,第一个成员变量enabled影响设置完时间之后是否使能alarm中断。如果enabled为0,alarm中断的时间正常设置,但是中断功能将被禁止(即使设置之前是开启的);而如果enabled为1的话,RTC的alarm功能将被开启。设置时间的时候第二个成员pending无效。
当使用ioctl读取alarm时间的时候,第一个成员enabled的值反应当前alarm中断的开启与否,第二个成员pending反应当前是否已有alarm中断挂起(发生)。
第三个成员是上面已经介绍过的structrtc_time结构体,用来传递要设置或者已设置的alarm中断时间。
对于alarm和wakeupalarm中断,将不会使用中文译名,因为无论定时中断、闹钟中断还是报警中断都不太合适。这两个中断在内核中其实是一回事,仅有的差别就是alarm中断只能设定为24小时内的某个时刻发生,而wakeupalarm中断可是设置为将来任意时刻发生。后面讲解的时候将会把它们两个做为一类中断来讲解。
在RTC设备节点上使用read()函数将会使进程进入睡眠,直到有一个中断发生。中断发生后从RTC设备节点只能读取一个unsignedlong类型的变量,无论发生了几次中断,一次只能读取一个unsignedlong。读取到的这个unsignedlong变量的最低一字节反应已经发生的中断类型,其余的字节反应自上次读取以来发生中断的次数。注意,如果自上次读取以来已发生了多钟类型的中断,并且每种类型的中断都发生了多次,根本没有办法判断每种中断类型各发生了几次。
RTC共支持三种类型的中断:更新中断、周期中断和alarm/wakeupalarm中断。更新中断是在RTC的时间变化的时候发生的中断,由于RTC的最小单位是秒,所以更新中断的频率是1HZ;周期中断是频率高于1HZ的中断(必须高于1HZ),中断频率必须是2^N(N>=1,整数)HZ,并且只有Root用户能使用64HZ以上的频率;alarm/wakeupalarm中断是在未来某个时刻发生的中断,它只能发生一次,发生过后需要重新设置才能再次发生,其中alarm中断只能将中断发生时刻设定在24小时之内,wakeupalarm中断可以设定为未来的任意时刻。
从RTC设备节点读出的最低字节使用掩码表示发生的中断类型。掩码的定义如程序清单 1 .3所示。
程序清单1.3 中断类型掩码
#defineRTC_IRQF 0x80 /* 任意中断发生它都会置位*/
#defineRTC_PF 0x40 /* 周期中断发生后该位置位*/
#defineRTC_AF 0x20 /* alarm/wakeup alarm中断 */
#defineRTC_UF 0x10 /* 更新中断*/
当有多种类型的中断发生后而用户空间有没来得及读取的时候,会有多个位被置1。高字节中的中断次数是所有中断次数之和。
现在需要澄清一下RTC中断的含义。这里的中断是说在RTC设备节点上使用read或select休眠的进程被唤醒,每次中断都发生一次唤醒操作。即使没有进程在设备节点上休眠,中断状态也不会丢失,而是在下一次读取的时候被读出。这里中断的概念和内核中的中断不是一回事。
如果系统中有udev模块的话,系统启动以后会在/dev下生成RTC的设备节点。一般RTC设备节点分为两类:/dev/rtc和/dev/rtcN(N>=0)。其中/dev/rtc是PC机上使用的接口,因为最早的时候系统中只有一个RTC设备,设备节点的名字就是rtc,现在为了兼容以前的程序,仍然会建立这个设备节点,但是现在的/dev/rtc往往是一个指向其它设备节点的链接。随着嵌入式平台的兴起,Linux开始支持多个RTC设备了,因此必须用新的命名规则对不同的设备进行区分,所以产生了/dev/rtcN(N>=0)设备节点,/dev/rtc往往就是指向它们中某一个的链接。
上面说Linux已经支持多个RTC了,那么系统启动的时候从哪个RTC读取时间呢?答案取决于编译内核之前配置内核的选项。
假设现在某个开发板上有两个RTC设备,那么在/dev下的设备节点如下:
[root@zlg/]# ls /dev/rtc*
/dev/rtc /dev/rtc0 /dev/rtc1
[root@zlg/]# ls -l /dev/rtc*
lrwxrwxrwx 1 root root 4 Nov 25 17:32 /dev/rtc -> rtc0
crw-r--r-- 1 root root 254, 0 Nov 25 17:32 /dev/rtc0
crw-rw---- 1 root root 254, 1 Nov 25 17:32 /dev/rtc1
[root@zlg/]#
可以看到,rtc是指向rtc0的一个链接。据此可以推断系统使用rtc0来读取系统时间。
在这三个节点上可以使用open、close、read、ioctl和select等函数。有关这些函数的使用将会在后面介绍。
如果系统中使用了procfs,那么第一个RTC设备(rtc0)可以在/proc/driver/rtc中暴露自己的信息。
读取这个文件会得到类似下面的信息:
[root@zlg/]# cat /proc/driver/rtc
rtc_time : 18:45:50
rtc_date : 2011-01-03
24hr : yes
在/sys/class/下会生成一个名为rtc的目录,这个目录中会为每一个注册的rtc设备生成一个子目录,名为rtcN(N>=0),每个子目录下的内容都是一致的,如下:
[root@zlg/]# ls /sys/class/rtc/
rtc0 rtc1
[root@zlg/]# ls /sys/class/rtc/rtc0
date device name since_epoch time
dev max_user_freq power subsystem uevent
[root@zlg/]# ls /sys/class/rtc/rtc1
date device name since_epoch time
dev max_user_freq power subsystem uevent
[root@zlg/]#
现在以rtc0为例访问用户比较关心的几个节点,它们的含义如下:
[root@zlg/]# cat /sys/class/rtc/rtc0/date /* 日期 */
2040-01-03
[root@zlg/]# cat /sys/class/rtc/rtc0/name /* 设备名 */
rtc-pcf8563
[root@zlg/]# cat /sys/class/rtc/rtc0/since_epoch /* 自1970年1月1日00:00:00以来的秒数*/
2209231116
[root@zlg/]# cat /sys/class/rtc/rtc0/time /* 时间*/
19:18:50
[root@zlg/]# cat /sys/class/rtc/rtc0/dev /* 设备节点号 */
254:0
[root@zlg/]# cat /sys/class/rtc/rtc0/max_user_freq /* 非Root用户可使用的最大周期中断频率*/
64
[root@zlg/]#
对于RTC设备在/dev下的设备节点可以使用ioctl()函数进行访问。这一小节介绍一下需要用到的IOCTL命令。
RTC_ALM_SET
设置alarm中断的触发时刻,使用的方式如下:
ioctl(fd,RTC_ALM_SET, &rtc_tm);
其中第三个参数是一个structrtc_time结构体,这个结构体的成员请参考程序清单 1 .1。注意,alarm中断的触发时间只能是24小时内的一个时刻,所以只有时、分、秒的部分是有效的,structrtc_time的年、月、日部分会被忽略。为了保证正确性,structrtc_time的tm_isdst成员应该设为-1。
如当前时间是13:00:00,而传入的时刻是14:00:00,则意味着alarm中断将在一小时后发生。而如果传入的是12:00:00,则意味着明天中午发生中断。总之,alarm中断不能超过24小时。
RTC_ALM_READ
读取已经设置的alarm中断时刻,使用的方式如下:
ioctl(fd,RTC_ALM_READ, &rtc_tm);
RTC_WKALM_SET
设置wakeupalarm中断的触发时刻,它和alarm中断的唯一不同之处就是wakeupalarm中断的触发时刻可以在未来的任意时刻。它的使用方式如下:
ioctl(fd,RTC_WKALM_SET, &alarm);
第三个参数是一个structrtc_wkalrm结构体,请参考程序清单 1 .2。structrtc_wkalrm的第一个成员enabled为1的话可以使ioctl()返回的时候中断功能就已经开启了。如果这里里没有开启中断功能,可以使用RTC_AIE_ON命令来开启,这个命令的用法在下面。
由于alarm和wakeupalarm在内核中表现为一种中断,因此同一时刻只有一个是有效的。也就是说不可以使用alarm中断的同时使用wakeupalarm中断。只有最后一次设置的alarm或wakeupalarm中断触发时刻是有效的。
RTC_WKALM_RD
读取wakeupalarm中断的触发时刻。它的使用方式如下:
ioctl(fd,RTC_WKALM_RD, &alarm);
RTC_AIE_ON
开启alarm/wakeupalarm中断。使用上面的命令设置了中断的时刻还不能启用中断,必须使用这个命令来开启。
RTC_AIE_OFF
关闭alarm/wakeupalarm中断。但是不会改变已经设置的中断触发时刻。
RTC_UIE_ON
开启RTC更新中断。更新中断每秒钟触发一次,因此频率为1HZ。有些RTC芯片或者RTC驱动不支持更新中断,可以使用软件来模拟,但前提是编译内核前配置内核时选择了软件模拟的功能,选项的路径如下:
DeviceDriver->RealTime Clock->RTC UIE emulation on dev interface
开启和关闭更新中断都不需要传入参数。
RTC_UIE_OFF
关闭RTC更新中断。
RTC_IRQP_SET
设置周期中断的频率。使用的方法如下:
ioctl(fd,RTC_IRQP_SET, tmp);
tmp是一个unsignedlong类型的变量,它的值必须是2的幂,也就是2^N(N>=1)。非Root用户无法使用64HZ以上的周期中断。
RTC_IRQP_READ
读取周期中断的频率。使用方式如下:
ioctl(fd,RTC_IRQP_READ, &tmp);
tmp是一个unsignedlong类型的变量,它保存返回的周期中断频率。
RTC_PIE_ON
开启周期中断功能。周期中断的频率设置好之后还不能立即使用,必须使用此命令来开启。
RTC_PIE_OFF
关闭周期中断功能。之前设置的周期中断频率依然有效。
RTC_SET_TIME
设置RTC的时间。这个命令和中断无关,用于更新RTC芯片的当前时间。使用的方式如下:
ioctl(fd,RTC_SET_TIME, &rtc_tm)
第三个参数以一个structrtc_time变量,各个成员的含义请参考程序清单 1 .1。
RTC_RD_TIME
读取RTC硬件中的当前时刻。使用的方式如下:
ioctl(fd,RTC_RD_TIME, &rtc_tm);
用户空间使用RTC设备的方法就是设置好中断的参数然后使用read或者select在设备节点上睡眠,等待中断将自己唤醒后读取设备节点的数据来判断中断类型和次数。
本文撰写的依据是2.6.27.8版本的内核。在内核的/driver/documentation/rtc.txt中有一个用户空间访问RTC设备节点的例程
1.6 3250开发板不支持的RTC功能
3250开发板上有两个RTC设备,一个片外的pcf8563和一个片内的RTC。其中片pcf8563仅仅支持读取和设置时间,不支持硬件的更新中断和alarm/wakeupalarm中断。片内的RTC不支持更新中断,但是支持alarm/wakeupalarm中断。在sys/class/rtc和/dev下,pcf8563对应rtc0,片内RTC对应rtc1。内核的系统时间是从rtc0读取的,并不使用rtc1。
虽然3250开发板的两个RTC硬件上并不支持更新中断,但是可以开启软件模拟更新中断的功能,开启的方法是在编译内核前使用“makemenuconfig”命令来配置内核,使内核支持软件模拟更新中断的功能。选择这个功能的路径如下:
DeviceDriver->Real Time Clock->RTC UIE emulation on dev interface
选择之后重新编译内核并下载到开发板就可以支持软件模拟的更新中断了,但是对用户空间来说,和硬件更新中断是没有区别的。