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)
- goto exit;
- id = id & MAX_ID_MASK;
- rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
- if (rtc == NULL) {
- err = -ENOMEM;
- 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:
- mutex_lock(&idr_lock);
- idr_remove(&rtc_idr, id);
- mutex_unlock(&idr_lock);
- 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文件系統接口。