30.Linux-RTC驅動分析及使用

来源:http://www.cnblogs.com/lifexy/archive/2017/11/15/7839625.html
-Advertisement-
Play Games

linux中的rtc驅動位於drivers/rtc下,裡面包含了許多開發平臺的RTC驅動,我們這裡是以S3C24xx為主,所以它的RTC驅動為rtc-s3c.c 1.進入./drivers/rtc/rtc-s3c.c 還是首先進入入口函數,如下圖所示: 這裡註冊了一個“s3c2410-rtc”名稱的 ...


 linux中的rtc驅動位於drivers/rtc下,裡面包含了許多開發平臺的RTC驅動,我們這裡是以S3C24xx為主,所以它的RTC驅動為rtc-s3c.c


 

1.進入./drivers/rtc/rtc-s3c.c

還是首先進入入口函數,如下圖所示:

 

這裡註冊了一個“s3c2410-rtc”名稱的平臺設備驅動

而“s3c2410-rtc”的平臺設備,在./arch/arm/plat-s3c24xx/dev.c里定義了,只不過這裡沒有註冊,如下圖所示:

 

當內核匹配到有與它名稱同名的平臺設備,就會調用.probe函數,接下來我們便進入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; }

顯然最終會調用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_DEV_MAX=16,表示只註冊0~15個次設備號,設備編號保存在rtc_devt中

 

 

 

2.它與rtc_device_register()函數註冊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字元設備,添加一個從設備到系統中

顯然這裡的註冊字元設備,和我們上節講的http://www.cnblogs.com/lifexy/p/7827559.html一摸一樣的流程

 

所以“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 

 

3.上面的file_operations操作結構體rtc_dev_fops 的成員,如下圖所示:

 

3.1當我們應用層open(”/dev/rtcXX”)時,就會調用rtc_dev_fops-> rtc_dev_open(),我們來看看如何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;
}

 

3.2 當我們應用層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調用

 

4.修改內核

我們單板上使用ls /dev/rtc*,找不到該字元設備, 因為內核里只定義了s3c_device_rtc這個RTC平臺設備,沒有註冊,所以平臺驅動沒有被匹配上,接下來我們來修改內核里的註冊數組

4.1進入arch/arm/plat-s3c24xx/Common-smdk.c

如下圖所示,在smdk_devs[]里,添加RTC的平臺設備即可,當內核啟動時,就會調用該數組,將裡面的platform_device統統註冊一遍

 

然後將Common-smdk.c代替虛擬機的內核目錄下的Common-smdk.c,重新make uImage編譯內核即可

5.測試運行

啟動後,如下圖所示, 使用ls /dev/rtc*,就找到了rtc0這個字元設備

 

 

5.1接下來,便開始設置RTC時間

在linux里有兩個時鐘:

硬體時鐘(2440里寄存器的時鐘)、系統時鐘(內核中的時鐘)

所以有兩個不同的命令: date命令、hwclock命令

5.2 date命令使用:

輸入date查看系統時鐘:

 

如果覺得不方便也可以指定格式顯示日期,需要在字元串前面加”+”

如下圖所示,輸入了  date  "+ %Y/%m/%d %H:%M:%S"

 

  • %M:表示秒
  • %m:表示月
  • %Y:表示年,當只需要最後兩位數字,輸入%y即可

 

date命令設置時間格式如下:

date  月日時分年.秒

如下圖所示,輸入date 111515292017.20,即可設置好系統時鐘

 

 

5.3 hwclock命令使用:

常用參數如下所示

  -r, --show          讀取並列印硬體時鐘(read hardware clock and print result )
  -s, --hctosys      將硬體時鐘同步到系統時鐘(set the system time from the hardware clock )
  -w, --systohc     將系統時鐘同步到硬體時鐘(set the hardware clock to the current system time )

如下圖所示,使用hwclock -w,即可同步硬體時鐘

 

 

 

然後重啟後,使用date命令,看到時間正常

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 簡單總結下Oracle/MySql/SQL Sqlserver這三個資料庫的分頁查詢語句 ...
  • 一、深入學習 group by group by ,分組,顧名思義,把數據按什麼來分組,每一組都有什麼特點。 1、我們先從最簡單的開始: select count(*) from tb1 group by tb1.sex; 查詢所有數據的條數,按性別來分組。這樣查詢到的結果集只有一列count(*) ...
  • oracle資料庫,對於新手來說總會遇到這樣的問題: 相信大家都遇到了這樣的問題,說實話,我曾經就遇到過這樣的問題,但是不好意思問旁邊的技術大咖,都有點懷疑人生了,然後自己在網上去查找原因,結果發現,網上各種版本, 好不容易才找到原因。就在前不久,又有同學問我這個問題,所以我就統一為犯過這個錯誤的同 ...
  • 1.從官網下載MySQL 下載地址:http://dev.mysql.com/downloads/ 2.解壓到想要安裝的目錄(博主安裝到: d:\mysql下) 新增一個my.ini文件,寫入如下內容: 3.以管理員身份運行 win+x(再按a,回車) 一直cd到d:\mysql\bin目錄 輸入命 ...
  • 1》記憶體子系統 1>組件: slab allocator buddy system kswapd pdflush 2>虛擬化環境: PA:進程地址; HA:虛擬機地址; MA:機器地址; 虛擬機轉換:PA >HA GuestOS(來賓主機),OS(宿主主機); Shadow PT:影子列表 3>Me ...
  • ㈠ File類 這個類包裝了一個上傳文件的所有信息。通過它,可以得到上傳文件的文件名、文件大小、擴展名、文件數據等信息。 File類主要提供以下方法: 1、saveAs作用:將文件換名另存。 原型: public void saveAs(java.lang.String destFilePathNa ...
  • 實現基於Keepalived高可用集群網站架構 環境:隨著業務的發展,網站的訪問量越來越大,網站訪問量已經從原來的1000QPS,變為3000QPS,目前業務已經通過集群LVS架構可做到隨時拓展,後端節點已經通過集群技術保障了可用性,但對於前端負載均衡器來說,是個比較大的安全隱患,因為當前端負載均衡 ...
  • 1.1 cobbler簡介 Cobbler是一個Linux伺服器安裝的服務,可以通過網路啟動(PXE)的方式來快速安裝、重裝物理伺服器和虛擬機,同時還可以管理DHCP,DNS等。 Cobbler可以使用命令行方式管理,也提供了基於Web的界面管理工具(cobbler-web),還提供了API介面,可 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...