參考資料:韋東山第二期經典視訊教程
1、rtc核心驅動分析
linux核心中的rtc驅動位于drivers/rtc下,裡面有許多開發平台的RTC驅動,我們這裡是以S3C24xx為主,是以它的RTC驅動為rtc-s3c.c
1.1 ./drivers/rtc/rtc-s3c.c
- 首先進入入口函數,裡面注冊了一個
的平台裝置驅動,當核心比對到s3c2410-rtc平台裝置,就會調用.probe函數s3c_rtc+probe:s3c2410-rtc
- 而
這個平台裝置是在s3c2410-rtc
裡定義的,但此時它僅僅是定義了,還沒有注冊到核心中,是以核心還看不到它:arch/arm/plat-s3c24xx/dev.c
- 接下來進入s3c2410_rtcdrv->probe函數中,看它的作用是什麼:
static int s3c_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc; //rtc裝置結構體
struct resource *res;
int ret;
s3c_rtc_tickno = platform_get_irq(pdev, 1); //擷取IRQ_TICK節拍中斷資源
s3c_rtc_alarmno = platform_get_irq(pdev, 0); //擷取IRQ_RTC鬧鐘中斷資源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //擷取記憶體資源
s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+1,pdev->name);//申請記憶體資源
s3c_rtc_base = ioremap(res->start, res->end - res->start + 1); //對記憶體進行重映射
s3c_rtc_enable(pdev, 1); //設定硬體相關設定,使能RTC寄存器
s3c_rtc_setfreq(s3c_rtc_freq); //設定TICONT寄存器,使能節拍中斷,設定節拍計數值
/*1.注冊RTC裝置*/
rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);
rtc->max_user_freq = 128;
platform_set_drvdata(pdev, rtc);
return 0;
}
顯然probe最終會調用
rtc_device_register()
函數來向核心注冊rtc_device裝置。注冊成功會傳回一個已注冊好的rtc_device。
而
s3c_rtcops
是一個rtc_class_ops結構體,裡面儲存了如何操作這個rtc裝置的函數,比如讀寫RTC時間、讀寫鬧鐘時間等。注冊成功後,會儲存在rtc_device->ops裡
該函數在drivers/rtc/Class.c檔案内被定義,Class.c檔案主要定義了RTC子系統。
而核心初始化,便會進入Class.c,通過執行rtc_init()->rtc_dev_init()來注冊字元裝置:
err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
- rtc_device_register()
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; //定義一個rtc_device結構體
... ...
rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL); //配置設定rtc_device結構體為全局變量
/*設定rtc_device*/
rtc->id = id;
rtc->ops = ops; //将s3c_rtcops儲存在rtc_device->ops裡
rtc->owner = owner;
rtc->max_user_freq = 64;
rtc->dev.parent = dev;
rtc->dev.class = rtc_class;
rtc->dev.release = rtc_device_release;
... ...
rtc_dev_prepare(rtc); //1.做提前準備,初始化cdev結構體
... ...
rtc_dev_add_device(rtc); //2.在/dev下建立rtc相關檔案,将cdev添加到系統中
rtc_sysfs_add_device(rtc); //在/sysfs下建立rtc相關檔案
rtc_proc_add_device(rtc); //在/proc下建立rtc相關檔案
... ...
return rtc;
}
上面的
rtc_dev_prepare(rtc)
和
rtc_dev_add_device(rtc)
主要做了以下兩個事情(位于./drivers/rtc/rtc-dev.c):
cdev_init(&rtc->char_dev, &rtc_dev_fops); //綁定file_operations
cdev_add(&rtc->char_dev, rtc->dev.devt, 1); //注冊rtc->char_dev字元裝置,添加一個從裝置到系統中
此處,和注冊字元裝置驅動的流程相似。
- 小結:是以“s3c2410-rtc”平台裝置驅動的.probe主要做了以下幾件事:
- 1.設定RTC相關寄存器
- 2.配置設定rtc_device結構體
- 3.設定rtc_device結構體
- -> 3.1 将struct rtc_class_ops s3c_rtcops放入rtc_device->ops,實作對RTC讀寫時間等操作
- 4 注冊rtc->char_dev字元裝置,且該字元裝置的操作結構體為: struct file_operations rtc_dev_fops
1.2 rtc_dev_fops
- 當我們應用層open(”/dev/rtcXX”)時,就會調用rtc_dev_fops-> rtc_dev_open():
static int rtc_dev_open(struct inode *inode, struct file *file)
{
struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);//擷取對應的rtc_device
const struct rtc_class_ops *ops = rtc->ops; //最終等于s3c_rtcops
file->private_data = rtc; //設定file結構體的私有成員等于rtc_device,再次執行ioctl等函數時,直接就可以提取file->private_data即可
err = ops->open ? ops->open(rtc->dev.parent) : 0; //調用s3c_rtcops->open
mutex_unlock(&rtc->char_lock);
return err;
}
顯然最終還是調用rtc_device下的s3c_rtcops->open:
而s3c_rtc_open()函數裡主要是申請了兩個中斷,一個鬧鐘中斷,一個計時中斷:
static int s3c_rtc_open(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
int ret;
ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,IRQF_DISABLED, "s3c2410-rtc alarm", rtc_dev); //申請鬧鐘中斷
if (ret) {
dev_err(dev, "IRQ%d error %d\n", s3c_rtc_alarmno, ret);
return ret;
}
ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,IRQF_DISABLED, "s3c2410-rtc tick", rtc_dev);//申請計時中斷
if (ret) {
dev_err(dev, "IRQ%d error %d\n", s3c_rtc_tickno, ret);
goto tick_err;
}
return ret;
tick_err:
free_irq(s3c_rtc_alarmno, rtc_dev);
return ret;
}
- 當我們應用層open後,使用 ioctl(int fd, unsigned long cmd, …)時,就會調用rtc_dev_fops-> rtc_dev_ioctl ():
static int rtc_dev_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
{
struct rtc_device *rtc = file->private_data; //提取rtc_device
void __user *uarg = (void __user *) arg;
... ...
switch (cmd) {
case RTC_EPOCH_SET:
case RTC_SET_TIME: //設定時間
if (!capable(CAP_SYS_TIME))
return -EACCES;
break;
case RTC_IRQP_SET: //改變中斷觸發速度
... ...}
... ...
switch (cmd) {
case RTC_ALM_READ: //讀鬧鐘時間
err = rtc_read_alarm(rtc, &alarm); //調用s3c_rtcops-> read_alarm
if (err < 0)
return err;
if (copy_to_user(uarg, &alarm.time, sizeof(tm))) //長傳時間資料
return -EFAULT;
break;
case RTC_ALM_SET: //設定鬧鐘時間 , 調用s3c_rtcops-> set_alarm
... ...
case RTC_RD_TIME: //讀RTC時間, 調用s3c_rtcops-> read_alarm
... ...
case RTC_SET_TIME: //寫RTC時間,調用s3c_rtcops-> set_time
... ...
case RTC_IRQP_SET: //改變中斷觸發頻率,調用s3c_rtcops-> irq_set_freq
... ...
}
最終還是調用s3c_rtcops下的成員函數,我們以s3c_rtcops-> read_alarm()函數為例,看看如何讀出時間的:
static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
unsigned int have_retried = 0;
void __iomem *base = s3c_rtc_base; //擷取RTC相關寄存器基位址
retry_get_time:
/*擷取年,月,日,時,分,秒寄存器*/
rtc_tm->tm_min = readb(base + S3C2410_RTCMIN);
rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);
rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);
rtc_tm->tm_mon = readb(base + S3C2410_RTCMON);
rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
rtc_tm->tm_sec = readb(base + S3C2410_RTCSEC);
/* 判斷秒寄存器中是0,則表示過去了一分鐘,那麼小時,天,月,等寄存器中的值都可能已經變化,需要重新讀取這些寄存器的值*/
if (rtc_tm->tm_sec == 0 && !have_retried) {
have_retried = 1;
goto retry_get_time;
}
/*将擷取的寄存器值,轉換為真正的時間資料*/
BCD_TO_BIN(rtc_tm->tm_sec);
BCD_TO_BIN(rtc_tm->tm_min);
BCD_TO_BIN(rtc_tm->tm_hour);
BCD_TO_BIN(rtc_tm->tm_mday);
BCD_TO_BIN(rtc_tm->tm_mon);
BCD_TO_BIN(rtc_tm->tm_year);
rtc_tm->tm_year += 100; //存儲器中存放的是從1900年開始的時間,是以加上100
rtc_tm->tm_mon -= 1;
return 0;
}
同樣, 在s3c_rtcops-> set_time()函數裡,也是向相關寄存器寫入RTC時間
是以,總結如下所示:
- rtc_device->char_dev: 字元裝置,與應用層、以及更底層的函數打交道
- rtc_device->ops: 更底層的操作函數,直接操作硬體相關的寄存器,被rtc_device->char_dev調用
2、修改核心,打開rtc裝置支援
我們單闆上使用
ls /dev/rtc*
找不到該字元裝置, 因為核心裡隻定義了s3c_device_rtc這個RTC平台裝置,沒有注冊,是以平台驅動沒有被比對上,接下來我們來修改核心裡的注冊數組:
- 進入
,核心版本2.6以後的進入arch/arm/plat-s3c24xx/Common-smdk.c
arch/arm/mach-s3c24xx/Common-smdk.c
- 在
裡,添加RTC的平台裝置即可(當核心啟動時,就會調用該數組,将裡面的platform_device統統注冊一遍)smdk_devs[]
- 重新編譯、加載新核心
3、測試
啟動後,使用
ls /dev/rtc*
,如果能看到rtc0這個字元裝置,就代表修改成功了
3.1 檢視時間
在linux裡有兩個時鐘:
硬體時鐘(2440裡寄存器的時鐘)、系統時鐘(核心中的時鐘)
是以對應有兩個不同的指令: date指令、hwclock指令
輸入
date
指令,就可以檢視系統時鐘:
# date
Wed Nov 3 12:23:20 UTC 2021
也可以指定格式顯示日期, date “+ %Y/%m/%d %H:%M:%S”
# date "+ %Y/%m/%d %H:%M:%S"
2021/11/03 12:23:20
3.2 設定時間
指令格式:date 月日時分年.秒
# date 081512302021.30
Sun Aug 15 12:30:30 UTC 2021
3.3 同步到硬體時間
指令:
hwclock
參數:
# hwclock -r
Wed Nov 3 12:23:20 2021 0.000000 seconds //同步前的時間
#
# date 081512302021.30
Sun Aug 15 12:30:30 UTC 2021 //系統目前時間
#
# hwclock -w //同步到硬體時間
#
# hwclock -r
Sun Aug 15 12:30:33 2021 0.000000 seconds //已經和系統時間同步的硬體時間
# hwclock -r
Wed Nov 3 12:23:20 2021 0.000000 seconds //同步前的時間
#
# date 081512302021.30
Sun Aug 15 12:30:30 UTC 2021 //系統目前時間
#
# hwclock -w //同步到硬體時間
#
# hwclock -r
Sun Aug 15 12:30:33 2021 0.000000 seconds //已經和系統時間同步的硬體時間