RTC(real time clock)實時時鐘,主要作用是給Linux系統提供時間。RTC因為是電池供電的,是以掉電後時間不丢失。Linux核心把RTC用作“離線”的時間與日期維護器。當Linux核心啟動時,它從RTC中讀取時間與日期,作為基準值。在運作期間核心完全抛開RTC,以軟體的形式維護系統的目前時間與日期,并在需要時将時間回寫RTC晶片。另外如果RTC提供了IRQ中斷并且可以定時,那麼RTC還可以作為核心睡眠時喚醒核心的鬧鐘。應用程式可以用RTC提供的周期中斷做一些周期的任務。 linux有兩種rtc驅動的接口,一個是老的接口,專門用在PC機上的。另外一鐘新接口是基于linux裝置驅動程式的。這個新的接口建立了一個RTC驅動模型,實作了RTC的大部分基本功能。而底層驅動無須考慮一些功能的實作,隻需将自己注冊的RTC核心中,其他工作由RTC核心來完成。下面分析RTC新接口的驅動模型。
一. 驅動模型結構
與RTC核心有關的檔案有:
/drivers/rtc/class.c 這個檔案向linux裝置模型核心注冊了一個類RTC,然後向驅動程式提供了注冊/登出接口
/drivers/rtc/rtc-dev.c 這個檔案定義了基本的裝置檔案操作函數,如:open,read等
/drivers/rtc/interface.c 顧名思義,這個檔案主要提供了使用者程式與RTC驅動的接口函數,使用者程式一般通過ioctl與RTC驅動互動,這裡定義了每個ioctl指令需要調用的函數
/drivers/rtc/rtc-sysfs.c 與sysfs有關
/drivers/rtc/rtc-proc.c 與proc檔案系統有關
/include/linux/rtc.h 定義了與RTC有關的資料結構
RTC驅動模型結構如下圖:
二. 基本資料結構
1. struct rtc_device 結構
struct rtc_device
{
struct device dev;
struct module *owner;
int id;
char name[RTC_DEVICE_NAME_SIZE];
const struct rtc_class_ops *ops;
struct mutex ops_lock;
struct cdev char_dev;
unsigned long flags;
unsigned long irq_data;
spinlock_t irq_lock;
wait_queue_head_t irq_queue;
struct fasync_struct *async_queue;
struct rtc_task *irq_task;
spinlock_t irq_task_lock;
int irq_freq;
int max_user_freq;
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
struct work_struct uie_task;
struct timer_list uie_timer;
/* Those fields are protected by rtc->irq_lock */
unsigned int oldsecs;
unsigned int uie_irq_active:1;
unsigned int stop_uie_polling:1;
unsigned int uie_task_active:1;
unsigned int uie_timer_active:1;
#endif
};
這個結構是RTC驅動程式的基本資料結構,但是他不像其他核心的基本結構一樣,驅動程式以他為參數調用注冊函數注冊到核心。這個結構是由注冊函數傳回給驅動程式的。
2. struct rtc_class_ops 結構
struct rtc_class_ops {
int (*open)(struct device *);
void (*release)(struct device *);
int (*ioctl)(struct device *, unsigned int, unsigned long);
int (*read_time)(struct device *, struct rtc_time *);
int (*set_time)(struct device *, struct rtc_time *);
int (*read_alarm)(struct device *, struct rtc_wkalrm *);
int (*set_alarm)(struct device *, struct rtc_wkalrm *);
int (*proc)(struct device *, struct seq_file *);
int (*set_mmss)(struct device *, unsigned long secs);
int (*irq_set_state)(struct device *, int enabled);
int (*irq_set_freq)(struct device *, int freq);
int (*read_callback)(struct device *, int data);
int (*alarm_irq_enable)(struct device *, unsigned int enabled);
int (*update_irq_enable)(struct device *, unsigned int enabled);
這個結構是RTC驅動程式要實作的基本操作函數,注意這裡的操作不是檔案操作。驅動程式通過初始化這樣一個結構,将自己實作的函數與RTC核心聯系起來。這裡面的大部分函數都要驅動程式來實作。而且這些函數都是操作底層硬體的,屬于最底層的函數。
3. struct rtc_time 結構
struct rtc_time {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
代表了時間與日期,從RTC裝置讀回的時間和日期就儲存在這個結構體中
三. class.c
1. 子產品初始化函數:rtc_init
static int __init rtc_init(void)
rtc_class = class_create(THIS_MODULE, "rtc");
if (IS_ERR(rtc_class)) {
printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
return PTR_ERR(rtc_class);
}
rtc_class->suspend = rtc_suspend;
rtc_class->resume = rtc_resume;
rtc_dev_init();
rtc_sysfs_init(rtc_class);
return 0;
}
rtc_init首先調用class_create建立了一個類--rtc。我們知道類是一個裝置的高層視圖,他抽象出了底層的實作細節。類的作用就是向使用者空間提供裝置的資訊,驅動程式不需要直接處理類。然後初始化類結構的相應成員,rtc_suspend,rtc_resume這兩個函數也是在class.c中實作的。接下來調用rtc_dev_init(),這個函數為RTC裝置動态配置設定裝置号,儲存在rtc_devt中。最後調用rtc_sysfs_init,初始化rtc_class的屬性。
2. 為底層驅動提供接口:rtc_device_register,rtc_device_unregister
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner)
struct rtc_device *rtc;
int id, err;
if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) {
err = -ENOMEM;
goto exit;
mutex_lock(&idr_lock);
err = idr_get_new(&rtc_idr, NULL, &id);
mutex_unlock(&idr_lock);
/*--------------------(1)---------------------*/
if (err < 0)
id = id & MAX_ID_MASK;
rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
if (rtc == NULL) {
goto exit_idr;
rtc->id = id;
rtc->ops = ops;
rtc->owner = owner;
rtc->max_user_freq = 64;
rtc->dev.parent = dev;
rtc->dev.class = rtc_class;
rtc->dev.release = rtc_device_release;
mutex_init(&rtc->ops_lock);
spin_lock_init(&rtc->irq_lock);
spin_lock_init(&rtc->irq_task_lock);
init_waitqueue_head(&rtc->irq_queue);
strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
dev_set_name(&rtc->dev, "rtc%d", id);
/*-------------------(2)--------------------*/
rtc_dev_prepare(rtc);
err = device_register(&rtc->dev);
if (err)
goto exit_kfree;
/*-------------------(3)--------------------*/
rtc_dev_add_device(rtc);
rtc_sysfs_add_device(rtc);
rtc_proc_add_device(rtc);
dev_info(dev, "rtc core: registered %s as %s\n",
rtc->name, dev_name(&rtc->dev));
/*-------------------(4)--------------------*/
return rtc;
exit_kfree:
kfree(rtc);
exit_idr:
idr_remove(&rtc_idr, id);
exit:
dev_err(dev, "rtc core: unable to register %s, err = %d\n",
name, err);
return ERR_PTR(err);
(1):處理一個idr的結構,idr在linux核心中指的就是整數ID管理機制,從本質上來說,idr是一種将整數ID号和特定指針關聯在一起的機制。這個機制最早是在2003年2月加入核心的,當時是作為POSIX定時器的一個更新檔。現在,在核心的很多地方都可以找到idr的身影。詳細實作請參照相關核心代碼。這裡從核心中擷取一個idr結構,并與id相關聯。
(2):配置設定了一個rtc_device的結構--rtc,并且初始化了相關的成員:id, rtc_class_ops等等。
(3):首先調用rtc_dev_prepare(在rtc-dev.c中定義)。因為RTC裝置本質來講還是字元裝置,是以這裡初始化了字元裝置相關的結構:裝置号以及檔案操作。然後調用device_register将裝置注冊到linux裝置模型核心。這樣在子產品加載的時候,udev daemon就會自動為我們建立裝置檔案rtc(n)。
(4):先後調用rtc_dev_add_device,rtc_sysfs_add_device,rtc_proc_add_device三個函數。rtc_dev_add_device注冊字元裝置,rtc_sysfs_add_device隻是為裝置添加了一個鬧鐘屬性,rtc_proc_add_device 建立proc檔案系統接口。
四. rtc-dev.c
rtc-dev.c 初始化了一個file_operations結構--rtc_dev_fops,并定義了這些操作函數。
1. rtc_dev_fops rtc基本的檔案操作
static const struct file_operations rtc_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = rtc_dev_read,
.poll = rtc_dev_poll,
.unlocked_ioctl = rtc_dev_ioctl,
.open = rtc_dev_open,
.release = rtc_dev_release,
.fasync = rtc_dev_fasync,
;
2. 函數的實作(以rtc_dev_read為例)
rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
struct rtc_device *rtc = file->private_data;
DECLARE_WAITQUEUE(wait, current);
unsigned long data;
ssize_t ret;
if (count != sizeof(unsigned int) && count < sizeof(unsigned long))
return -EINVAL;
add_wait_queue(&rtc->irq_queue, &wait);
do {
__set_current_state(TASK_INTERRUPTIBLE);
spin_lock_irq(&rtc->irq_lock);
data = rtc->irq_data;
rtc->irq_data = 0;
spin_unlock_irq(&rtc->irq_lock);
if (data != 0) {
ret = 0;
break;
}
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
if (signal_pending(current)) {
ret = -ERESTARTSYS;
schedule();
} while (1);
set_current_state(TASK_RUNNING);
remove_wait_queue(&rtc->irq_queue, &wait);
if (ret == 0) {
/* Check for any data updates */
if (rtc->ops->read_callback)
data = rtc->ops->read_callback(rtc->dev.parent,
data);
if (sizeof(int) != sizeof(long) &&
count == sizeof(unsigned int))
ret = put_user(data, (unsigned int __user *)buf) ?:
sizeof(unsigned int);
else
ret = put_user(data, (unsigned long __user *)buf) ?:
sizeof(unsigned long);
return ret;
這裡的read不是應用程式用來擷取時間的,而是有其他的作用,他幫助應用程式周期性的完成一些工作。如果要使用這個功能,應用程式首先保證RTC驅動程式提供這樣的功能。這個功能是這樣實作的:程序讀取/dev/rtc(n),程序睡眠直到RTC中斷将他喚醒。我們可以發現,這裡的睡眠是ldd3中提到的手工睡眠。這個函數的手工休眠過程如下:首先調用DECLARE_WAITQUEUE(wait, current),聲明一個等待隊列入口,然後調用add_wait_queue将這個入口加入到RTC的irq等待隊列裡,然後進入循環。在循環裡首先把程序的狀态改成TASK_INTERRUPTIBLE,這樣程序就不能再被排程運作。但是現在程序還在運作,沒有進入睡眠狀态。程式然後讀取RTC裡面的irq_data,如果不是零,那麼程式跳出這個循環,程序不會睡眠。因為這個irq_data在rtc的中斷處理程式會被指派,而讀過之後就會清零,是以如果資料不是零的話說明發生過一次中斷。如果是零那麼沒有發生中斷,調用schedule,程序會被排程出可運作隊列,進而讓出處理器,真正進入睡眠。跳出循環代表被喚醒,然後将程序狀态改變為可運作,移除等待隊列入口。最後将讀回的資料傳給使用者空間。
五. interface.c
interface.c裡的所有函數的實作都對應于rtc-dev.c 中ioctl相應的指令。對應關系如下:
RTC_ALM_READ rtc_read_alarm 讀取鬧鐘時間
RTC_ALM_SET rtc_set_alarm 設定鬧鐘時間
RTC_RD_TIME rtc_read_time 讀取時間與日期
RTC_SET_TIME rtc_set_time 設定時間與日期
RTC_PIE_ON RTC_PIE_OFF rtc_irq_set_state 開關RTC全局中斷的函數
RTC_AIE_ON RTC_AIE_OFF rtc_alarm_irq_enable 使能禁止RTC鬧鐘中斷
RTC_UIE_OFF RTC_UIE_ON rtc_update_irq_enable 使能禁止RTC更新中斷
RTC_IRQP_SET rtc_irq_set_freq 設定中斷的頻率
以上就是所有ioctl的指令與實作的對應關系。其中如果不涉及中斷的話,有兩個指令需要我們特别關心一下,就是RTC_RD_TIME與RTC_SET_TIME。因為RTC最基本的功能就是提供時間與日期。這兩個指令恰恰是擷取時間和設定時間。下面分析一下這兩個指令的實作,也就是rtc_set_alarm與rtc_read_time函數的實作:
1. rtc_read_time 函數
int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
int err;
err = mutex_lock_interruptible(&rtc->ops_lock);
return err;
if (!rtc->ops)
err = -ENODEV;
else if (!rtc->ops->read_time)
err = -EINVAL;
else {
memset(tm, 0, sizeof(struct rtc_time));
err = rtc->ops->read_time(rtc->dev.parent, tm);
mutex_unlock(&rtc->ops_lock);
return err;
這個函數用了一個信号來保證在同一時刻隻有一個程序可以擷取時間。鎖定了這個信号量後,調用rtc->ops裡面read函數,這個函數是由具體的驅動程式實作的,操作底層硬體。讀回的時間存放在rtc_time結構裡面的。
2. rtc_set_time 函數
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
err = rtc_valid_tm(tm);
if (err != 0)
else if (rtc->ops->set_time)
err = rtc->ops->set_time(rtc->dev.parent, tm);
else if (rtc->ops->set_mmss) {
unsigned long secs;
err = rtc_tm_to_time(tm, &secs);
if (err == 0)
err = rtc->ops->set_mmss(rtc->dev.parent, secs);
} else
這個函數其實和rtc_read_time函數差不多,同樣是鎖定信号量,同樣是調用底層驅動函數。但是這裡的設定時間提供了兩個調用:一個是set_time,一個是set_mmss。因為有的RTC硬體隻計算秒數,不關心牆鐘時間,是以如果是這樣的RTC,必須實作set_mmss來設定時間。
六. rtc-sysfs.c 部分
這個部分主要是有關sysfs的操作。rtc-sysfs.c中定義了這樣一個裝置屬性組,如下:
static struct device_attribute rtc_attrs[] = {
__ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),
__ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),
__ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),
__ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),
__ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,
rtc_sysfs_set_max_user_freq),
__ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),
{ },
這個屬性組是在class.c的子產品初始化函數中,由rtc_sysfs_init函數指派給rtc_class->dev_attrs的,以後屬于這個類的裝置都會有這些屬性。但是我們知道要想一個裝置結構擁有一種屬性,必須調用device_create_file,這樣才會使這個屬性出現在sysfs相關裝置目錄裡。但是在這裡的代碼中隻是給這個類的dev_attrs域指派了這個屬性組指針,而沒有調用device_create_file。我原來以為是在rtc_device_resgister函數中,由rtc_sysfs_add_device完成這個工作,但是這個函數隻是給裝置添加了鬧鐘屬性,并沒有處理這個屬性組。最後發現這個工作是由device_register來完成的。這裡的調用關系有點複雜:
device_register調用device_add
device_add調用 device_add_attrs
device_add_attrs調用device_add_attributes
device_add_attributes調用device_create_file來完成裝置的屬性設定的。
設定完屬性後,在/sys/class/rtc/rtc(n)的目錄下就會出現name,date,time等檔案,使用者讀這些檔案的時候就會調用相應的函數。如讀取name檔案,就會調用rtc_sysfs_show_name函數,這個函數也是在rtc-sysfs.c中實作的,作用是讀取并顯示時間。
七. rtc-proc.c
這個檔案提供RTC的proc檔案系統接口。proc檔案系統是軟體建立的檔案系統,核心通過他向外界導出資訊,下面的每一個檔案都綁定一個函數,當使用者讀取這個檔案的時候,這個函數會向檔案寫入資訊。rtc-proc.c中初始化了一個檔案操作:
static const struct file_operations rtc_proc_fops = {
.open = rtc_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = rtc_proc_release,
RTC驅動在向RTC核心注冊自己的時候,由注冊函數rtc_device_resgister調用rtc_proc_add_device來實作proc接口的初始化,這個函數如下定義:
void rtc_proc_add_device(struct rtc_device *rtc)
if (rtc->id == 0)
proc_create_data("driver/rtc", 0, NULL, &rtc_proc_fops, rtc);
他主要調用了proc_create_data。proc_create_data完成建立檔案節點的作用,并将檔案的操作函數與節點聯系起來。調用這個函數後,在/proc/driver目錄下就會有一個檔案rtc,應用程式打開這個檔案就會調用rtc_proc_open函數,這個函數如下定義:
static int rtc_proc_open(struct inode *inode, struct file *file)
struct rtc_device *rtc = PDE(inode)->data;
if (!try_module_get(THIS_MODULE))
return -ENODEV;
return single_open(file, rtc_proc_show, rtc);
我們知道一個proc的檔案必須與一個操作函數組成一個proc入口項,這個檔案才能正常工作。這個函數最主要作用就是調用single_open,建立一個proc檔案入口項,使其操作函數是rtc_proc_show,并初始化seq_file接口。rtc_proc_show函數如下定義:
static int rtc_proc_show(struct seq_file *seq, void *offset)
struct rtc_device *rtc = seq->private;
const struct rtc_class_ops *ops = rtc->ops;
struct rtc_wkalrm alrm;
struct rtc_time tm;
err = rtc_read_time(rtc, &tm);
if (err == 0) {
seq_printf(seq,
"rtc_time\t: %02d:%02d:%02d\n"
"rtc_date\t: %04d-%02d-%02d\n",
tm.tm_hour, tm.tm_min, tm.tm_sec,
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
err = rtc_read_alarm(rtc, &alrm);
seq_printf(seq, "alrm_time\t: ");
if ((unsigned int)alrm.time.tm_hour <= 24)
seq_printf(seq, "%02d:", alrm.time.tm_hour);
seq_printf(seq, "**:");
if ((unsigned int)alrm.time.tm_min <= 59)
seq_printf(seq, "%02d:", alrm.time.tm_min);
if ((unsigned int)alrm.time.tm_sec <= 59)
seq_printf(seq, "%02d\n", alrm.time.tm_sec);
seq_printf(seq, "**\n");
seq_printf(seq, "alrm_date\t: ");
if ((unsigned int)alrm.time.tm_year <= 200)
seq_printf(seq, "%04d-", alrm.time.tm_year + 1900);
seq_printf(seq, "****-");
if ((unsigned int)alrm.time.tm_mon <= 11)
seq_printf(seq, "%02d-", alrm.time.tm_mon + 1);
seq_printf(seq, "**-");
if (alrm.time.tm_mday && (unsigned int)alrm.time.tm_mday <= 31)
seq_printf(seq, "%02d\n", alrm.time.tm_mday);
seq_printf(seq, "alarm_IRQ\t: %s\n",
alrm.enabled ? "yes" : "no");
seq_printf(seq, "alrm_pending\t: %s\n",
alrm.pending ? "yes" : "no");
seq_printf(seq, "24hr\t\t: yes\n");
if (ops->proc)
ops->proc(rtc->dev.parent, seq);
這個函數就是最後給使用者顯示資訊的函數了,可以看出他通過調用rtc_deivce中的操作函數,讀取時間,日期和一些其他的資訊顯示給使用者。
六. 總結
RTC核心使底層硬體對使用者來說是透明的,并且減少了編寫驅動程式的工作量。RTC新的驅動接口提供了更多的功能,使系統可以同時存在多個RTC。/dev,sysfs,proc這三種機制的實作使得應用程式能靈活的使用RTC,RTC核心雖然表面上看上去很簡單,但是還是涉及到很多知識,有些東西書上講的還是不夠詳細,還需要通過分析代碼加深了解。 另外RTC核心代碼的組織方式也值得學習,不同功能的代碼放在不同的檔案中,簡單明了。
【新浪微網誌】 張昺華--sky
【twitter】 @sky2030_
【facebook】 張昺華 zhangbinghua
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.