天天看點

Linux核心rtc驅動分析

參考資料:韋東山第二期經典視訊教程

1、rtc核心驅動分析

linux核心中的rtc驅動位于drivers/rtc下,裡面有許多開發平台的RTC驅動,我們這裡是以S3C24xx為主,是以它的RTC驅動為rtc-s3c.c

1.1 ./drivers/rtc/rtc-s3c.c

  • 首先進入入口函數,裡面注冊了一個​

    ​s3c2410-rtc​

    ​的平台裝置驅動,當核心比對到s3c2410-rtc平台裝置,就會調用.probe函數s3c_rtc+probe:
Linux核心rtc驅動分析
  • 而​

    ​s3c2410-rtc​

    ​​這個平台裝置是在​

    ​arch/arm/plat-s3c24xx/dev.c​

    ​裡定義的,但此時它僅僅是定義了,還沒有注冊到核心中,是以核心還看不到它:
Linux核心rtc驅動分析
  • 接下來進入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

Linux核心rtc驅動分析
  • 當我們應用層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:

Linux核心rtc驅動分析

而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平台裝置,沒有注冊,是以平台驅動沒有被比對上,接下來我們來修改核心裡的注冊數組:

  • 進入​

    ​arch/arm/plat-s3c24xx/Common-smdk.c​

    ​​,核心版本2.6以後的進入​

    ​arch/arm/mach-s3c24xx/Common-smdk.c​

  • 在​

    ​smdk_devs[]​

    ​裡,添加RTC的平台裝置即可(當核心啟動時,就會調用該數組,将裡面的platform_device統統注冊一遍)
Linux核心rtc驅動分析
  • 重新編譯、加載新核心

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  //已經和系統時間同步的硬體時間