linux驅動工程面試必問知識點

linux內核原理面試必問(由易到難)php

簡單型html

1:linux中內核空間及用戶空間的區別?用戶空間與內核通訊方式有哪些?
2:linux中內存劃分及如何使用?虛擬地址及物理地址的概念及彼此之間的轉化,高端內存概念?
3:linux中中斷的實現機制,tasklet與workqueue的區別及底層實現區別?爲何要區分上半部和下半部?
java

4:linux中斷的響應執行流程?中斷的申請及什麼時候執行(什麼時候執行中斷處理函數)?
5:linux中的同步機制?spinlock與信號量的區別?
node

6:linux中RCU原理?
linux

7:  linux中軟中斷的實現原理?(2014.03.11)android

8:linux系統實現原子操做有哪些方法? (2014.03.22)面試

9:MIPS Cpu中空間地址是怎麼劃分的?如在uboot中如何操做設備的特定的寄存器? (2014.03.22)redis

 

複雜型:express

 

1:linux中netfilter的實現機制?是如何實現對特定數據包進行處理(如過濾,NAT之類的)及HOOK點的註冊?
2:linux中系統調用過程?如:應用程序中read()在linux中執行過程即從用戶空間到內核空間?
3:linux內核的啓動過程(源代碼級)?

編程

4:linux調度原理?

5:linux網絡子系統的認識?

三: 筆試

1:二分法查找

2:大小端轉化及判斷

3:  二維數組最外邊個元素之和?

4:特定比特位置0和1

5:字符串中的第一個和最後一個元素交換(字符串反轉)?

 

 

 

1:linux中內核空間及用戶空間的區別?用戶空間與內核通訊方式有哪些?

答:
-在32位架構cpu中,物理內存大小限制在4G。linux將4G內存分爲兩部分,0~1G爲kernel使用,1~4G爲用戶使用;進程運行在kernel,就是運行在0-1G,進程運行在用戶空間,就是運行在1-4G。
-用戶空間和內核空間通訊方式有那些?
1. 使用API:這是最常使用的一種方式了
A.get_user(x,ptr):在內核中被調用,獲取用戶空間指定地址的數值並保存到內核變量x中。
B.put_user(x,ptr):在內核中被調用,將內核空間的變量x的數值保存到到用戶空間指定地址處。
C.Copy_from_user()/copy_to_user():主要應用於設備驅動讀寫函數中,經過系統調用觸發。
2. 使用proc文件系統:和sysfs文件系統相似,也能夠做爲內核空間和用戶空間交互的手段。
3. netlink
4. 使用mmap系統調用
5. 信號
內核空間和用戶空間通訊方式

2:linux中內存劃分及如何使用?虛擬地址及物理地址的概念及彼此之間的轉化,高端內存概念?

1. 用戶虛擬地址
     這是在用戶空間進程所能看到的常規地址。每一個進程多有本身的虛擬地址,而且能夠使用大於物理內存大小的空間。
 2. 物理地址
     該地址在處理器和系統內存之間使用,對應與真是物理地址。
 3. 總線地址
     沒看懂,不說了。
 4. 內核邏輯地址
     內核邏輯地址組成了內核的常規地址空間。該地址映射了部分(或者所有)內存,並常常被視爲物理地址。
     邏輯地址使用硬件內建的指針大小,所以在安裝了大量內存的32位系統中,它沒法尋址所有的物理內存。
     邏輯地址一般保存在unsigned long或者void *這樣類型的變量中。kmalloc返回的內存就是內核邏輯地址。
     (上面這段話很重要,必定要理解,建議本身使用記號筆標紅)
 5. 內核虛擬地址
     內核虛擬地址與物理地址的映射沒必要是一對一的,而這是虛擬地址的特色。

     全部邏輯地址都是內核虛擬地址,可是許多內核虛擬地址不是邏輯地址。vmalloc分配的內存就是一個虛擬地址。
     能夠參考下面的地址:

 

內存詳解

內存結構圖

總結:高端內存的做用就是用於創建臨時地址映射,用於kernel申請user空間內存

3: linux中中斷的實現機制,tasklet與workqueue的區別及底層實現區別?爲何要區分上半部和下半部?

答:
tasklet和workqueue區別?
tasklet運行於中斷上下文,不容許阻塞 、休眠,而workqueue運行與進程上下文,能夠休眠和阻塞。
爲何要區分上半部和下半部?
中斷服務程序異步執行,可能會中斷其餘的重要代碼,包括其餘中斷服務程序。所以,爲了不被中斷的代碼延遲太長的時間,中斷服務程序須要儘快運行,並且執行的時間越短越好,因此中斷程序只做必須的工做,其餘工做推遲到之後處理。因此Linux把中斷處理切爲兩個部分:上半部和下半部。上半部就是中斷處理程序,它須要完成的工做越少越好,執行得越快越好,一旦接收到一箇中斷,它就當即開始執行。像對時間敏感、與硬件相關、要求保證不被其餘中斷打斷的任務每每放在中斷處理程序中執行;而剩下的與中斷有相關性可是能夠延後的任務,如對數據的操做處理,則推遲一點由下半部完成。下半部分延後執行且執行期間能夠相應全部中斷,這樣可以使系統處於中斷屏蔽狀態的時間儘量的短,提升了系統的響應能力。實現了程序運行快同時完成的工做量多的目標。

4:linux中斷的響應執行流程?中斷的申請及什麼時候執行(什麼時候執行中斷處理函數)?

中斷的響應流程:cpu接受終端->保存中斷上下文跳轉到中斷處理歷程->執行中斷上半部->執行中斷下半部->恢復中斷上下文。
中斷的申請request_irq的正確位置:應該是在第一次打開 、硬件被告知終端以前。

5:linux中的同步機制?spinlock與信號量的區別?

linux中的同步機制:自旋鎖/信號量/讀取所/循環緩衝區
spinlock在得不到鎖的時候,程序會循環訪問鎖,性能降低
信號量在得不到鎖的時候會休眠,等到能夠得到鎖的時候,繼續執行。

 

 

 

 

一、255.255.254.0網段最多能支持多少主機?(大概有5個備選項)

二、10M網卡傳輸過程當中物理層採用什麼編碼?(SNAP?)(大概有4個備選項)

三、棧與隊列的特色?(備選大概只有兩個,A爲FIFO,B爲LIFO)

四、Cache的工做方式劃分?(大概也有4個答案,大概是:write-none,write-all,write-through,write-back)。

五、什麼叫NMI中斷?(四個備選項)

六、RISC主要性能及特性?(大概有6個備選項)

七、在嵌入式系統中,所謂的北橋指的是什麼?

(2)簡答題:

一、說說輪巡任務調度與搶佔式任務調度的區別?(大概爲8分吧,記不清了)

二、什麼叫存儲器高速緩存技術,其主要目的?(大概6分)

三、畫出計算機組成的最小邏輯框圖。(哼,這道題居然10分)

四、談談Volatile與Register修飾符的做用?

 

 

一、linux驅動分類

a. 字符設備

b. 塊設備

c.網絡設備

 

字符設備指那些必須以串行順序依次進行訪問的設備,如觸摸屏、磁帶驅動器、鼠標等。

塊設備能夠用任意順序進行訪問,以塊爲單位進行操做,如硬盤、軟驅等。

字符設備不通過系統的快速緩衝,而塊設備通過系統的快速緩衝。可是,字符設備和塊設備並無明顯的界限,如對於Flash設備,符合塊設備的特色,可是咱們仍然能夠把它做爲一個字符設備來訪問。

網絡設備在Linux裏作專門的處理。Linux的網絡系統主要是基於BSD unix的socket 機制。在系統和驅動程序之間定義有專門的數據結構(sk_buff)進行數據的傳遞。系 統裏支持對發送數據和接收數據的緩存,提供流量控制機制,提供對多協議的支持。

 

二、信號量與自旋鎖

自旋鎖

  自旋鎖是專爲防止多處理器併發而引入的一種鎖,它應用於中斷處理等部分。對於單處理器來講,防止中斷處理中的併發可簡單採用關閉中斷的方式,不須要自旋鎖。

  自旋鎖最多隻能被一個內核任務持有,若是一個內核任務試圖請求一個已被爭用(已經被持有)的自旋鎖,那麼這個任務就會一直進行忙循環——旋轉——等待鎖從新可用。要是鎖未被爭用,請求它的內核任務便能馬上獲得它而且繼續進行。自旋鎖能夠在任什麼時候刻防止多於一個的內核任務同時進入臨界區,所以這種鎖可有效地避免多處理器上併發運行的內核任務競爭共享資源。

  事實上,自旋鎖的初衷就是:在短時間間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖從新可用的期間進行自旋(特別浪費處理器時間),因此自旋鎖不該該被持有時間過長。若是須要長時間鎖定的話, 最好使用信號量。可是自旋鎖節省了上下文切換的開銷。

自旋鎖的基本形式以下:

  spin_lock(&mr_lock);

  //臨界區

  spin_unlock(&mr_lock);

  由於自旋鎖在同一時刻只能被最多一個內核任務持有,因此一個時刻只有一個線程容許存在於臨界區中。這點很好地知足了對稱多處理機器須要的鎖定服務。在單處理器上,自旋鎖僅僅看成一個設置內核搶佔的開關。若是內核搶佔也不存在,那麼自旋鎖會在編譯時被徹底剔除出內核。

  簡單的說,自旋鎖在內核中主要用來防止多處理器中併發訪問臨界區,防止內核搶佔形成的競爭。另外自旋鎖不容許任務睡眠(持有自旋鎖的任務睡眠會形成自死鎖——由於睡眠有可能形成持有鎖的內核任務被從新調度,而再次申請本身已持有的鎖),它可以在中斷上下文中使用。

死鎖:假設有一個或多個內核任務和一個或多個資源,每一個內核都在等待其中的一個資源,但全部的資源都已經被佔用了。這便會發生全部內核任務都在相互等待,但它們永遠不會釋放已經佔有的資源,因而任何內核任務都沒法得到所須要的資源,沒法繼續運行,這便意味着死鎖發生了。自死瑣是說本身佔有了某個資源,而後本身又申請本身已佔有的資源,顯然不可能再得到該資源,所以就自縛手腳了。遞歸使用一個自旋鎖就會出現這種狀況。

 

信號量

信號量是一種睡眠鎖。若是有一個任務試圖得到一個已被持有的信號量時,信號量會將其推入等待隊列,而後讓其睡眠。這時處理器得到自由去執行其它代碼。當持有信號量的進程將信號量釋放後,在等待隊列中的一個任務將被喚醒,從而即可以得到這個信號量。

  信號量的睡眠特性,使得信號量適用於鎖會被長時間持有的狀況;只能在進程上下文中使用,由於中斷上下文中是不能被調度的;另外當代碼持有信號量時,不能夠再持有自旋鎖。

信號量基本使用形式爲:

  static DECLARE_MUTEX(mr_sem);//聲明互斥信號量

  if(down_interruptible(&mr_sem))

           //可被中斷的睡眠,當信號來到,睡眠的任務被喚醒

           //臨界區

       up(&mr_sem);  

 

信號量和自旋鎖區別

  從嚴格意義上講,信號量和自旋鎖屬於不一樣層次的互斥手段,前者的實現有賴於後者。

注意如下原則:

       若是代碼須要睡眠——這每每是發生在和用戶空間同步時——使用信號量是惟一的選擇。因爲不受睡眠的限制,使用信號量一般來講更加簡單一些。若是須要在自旋鎖和信號量中做選擇,應該取決於鎖被持有的時間長短。理想狀況是全部的鎖都應該儘量短的被持有,可是若是鎖的持有時間較長的話,使用信號量是更好的選擇。另外,信號量不一樣於自旋鎖,它不會關閉內核搶佔,因此持有信號量的代碼能夠被搶佔。這意味者信號量不會對影響調度反應時間帶來負面影響。

 

自旋鎖對信號量

 

需求              建議的加鎖方法

低開銷加鎖           優先使用自旋鎖

短時間鎖定            優先使用自旋鎖

長期加鎖            優先使用信號量

中斷上下文中加鎖        使用自旋鎖

持有鎖是須要睡眠、調度     使用信號量

 

三、platform總線設備及總線設備如何編寫

Linux設備模型(總線、設備、驅動程序和類)【轉】


文章的例子和實驗使用《LDD3》所配的lddbus模塊(稍做修改)。

提示:在學習這部份內容是必定要分析全部介紹的源代碼,知道他們與上一部份內容(kobject、kset、attribute等等)的關係,最好要分析一個實際的「flatform device」設備,否則會只學到表象,到後面會不知所云的。

總線

總線是處理器和一個或多個設備之間的通道,在設備模型中, 全部的設備都經過總線相連, 甚至是內部的虛擬"platform"總線。總線能夠相互插入。設備模型展現了總線和它們所控制的設備之間的實際鏈接。
在 Linux 設備模型中, 總線由 bus_type 結構表示, 定義在 <linux/device.h> :

struct bus_type {
    const char        * name;/*總線類型名稱*/
    struct module        * owner;/*指向模塊的指針(若是有), 此模塊負責操做這個總線*/

    struct kset        subsys;/*與該總線相關的子系統*/
    struct kset        drivers;/*總線驅動程序的kset*/
    struct kset        devices;/* 掛在該總線的全部設備的kset*/

    struct klist        klist_devices;/*與該總線相關的驅動程序鏈表*/
    struct klist        klist_drivers;/*掛接在該總線的設備鏈表*/

    struct blocking_notifier_head bus_notifier;

    struct bus_attribute    * bus_attrs; /*總線屬性*/
    struct device_attribute * dev_attrs; /*設備屬性,指向爲每一個加入總線的設備創建的默認屬性鏈表*/
    struct driver_attribute * drv_attrs; /*驅動程序屬性*/
    struct bus_attribute drivers_autoprobe_attr;/*驅動自動探測屬性*/
    struct bus_attribute drivers_probe_attr;/*驅動探測屬性*/

    int        (*match)(struct device * dev, struct device_driver * drv);
    int        (*uevent)(struct device *dev, char **envp,
                 int num_envp, char *buffer, int buffer_size);
    int        (*probe)(struct device * dev);
    int        (*remove)(struct device * dev);
    void        (*shutdown)(struct device * dev);

    int (*suspend)(struct device * dev, pm_message_t state);
    int (*suspend_late)(struct device * dev, pm_message_t state);
    int (*resume_early)(struct device * dev);
    nt (*resume)(struct device * dev);
/*處理熱插拔、電源管理、探測和移除等事件的方法*/
    unsigned int drivers_autoprobe:1;
};

 

在更新的內核裏,這個結構體變得更簡潔了,隱藏了無需驅動編程人員知道的一些成員:

 

 

/*in Linux 2.6.26.5*/

struct bus_type {
    const char        *name;
    struct bus_attribute    *bus_attrs;
    struct device_attribute    *dev_attrs;
    struct driver_attribute    *drv_attrs;

    int (*match)(struct device *devstruct device_driver *drv);
    int (*uevent)(struct device *devstruct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*suspend_late)(struct device *dev, pm_message_t state);
    int (*resume_early)(struct device *dev);
    int (*resume)(struct device *dev);

    struct bus_type_private *p;
};

struct bus_type_private {
    struct kset subsys;
    struct kset *drivers_kset;
    struct kset *devices_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;
};

 

總線的註冊和刪除

總線的主要註冊步驟:

(1)申明和初始化 bus_type 結構體。只有不多的 bus_type 成員須要初始化,大部分都由設備模型核心控制。但必須爲總線指定名字及一些必要的方法。例如:

struct bus_type ldd_bus_type = {
    .name = "ldd",
    .match = ldd_match,
    .uevent = ldd_uevent,
};

(2)調用bus_register函數註冊總線。

int bus_register(struct bus_type * bus)

調用可能失敗, 因此必須始終檢查返回值。若成功,新的總線子系統將被添加進系統,並可在 sysfs 的 /sys/bus 下看到。以後能夠向總線添加設備。
例如:

ret = bus_register(&ldd_bus_type);
if (ret)
 return ret;

  
當必須從系統中刪除一個總線時, 調用:

void bus_unregister(struct bus_type *bus);

總線方法

在 bus_type 結構中定義了許多方法,它們容許總線核心做爲設備核心和單獨的驅動程序之間提供服務的中介,主要介紹如下兩個方法:

int (*match)(struct device * dev, struct device_driver * drv);
/*當一個新設備或者驅動被添加到這個總線時,這個方法會被調用一次或屢次,若指定的驅動程序可以處理指定的設備,則返回非零值。必須在總線層使用這個函數, 由於那裏存在正確的邏輯,核心內核不知道如何爲每一個總線類型匹配設備和驅動程序*/

int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
/*在爲用戶空間產生熱插拔事件以前,這個方法容許總線添加環境變量(參數和 kset 的uevent方法相同)*/

lddbus的match和uevent方法:

static int ldd_match(struct device *dev, struct device_driver *driver)
{
 return !strncmp(dev->bus_id, driver->name, strlen(driver->name));
}/*僅簡單比較驅動和設備的名字*/
/*當涉及實際硬件時, match 函數經常對設備提供的硬件 ID 和驅動所支持的 ID 作比較*/

static int ldd_uevent(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)
{
 envp[0] = buffer;
 if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s",
 Version) >= buffer_size)
 return -ENOMEM;
 envp[1] = NULL;
 return 0;
}/*在環境變量中加入 lddbus 源碼的當前版本號*/

對設備和驅動的迭代

若要編寫總線層代碼, 可能不得不對全部已經註冊到總線的設備或驅動進行一些操做,這可能須要仔細研究嵌入到 bus_type 結構中的其餘數據結構, 但最好使用內核提供的輔助函數:

int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));

/*這兩個函數迭代總線上的每一個設備或驅動程序, 將關聯的 device 或 device_driver 傳遞給 fn, 同時傳遞 data 值。若 start 爲 NULL, 則從第一個設備開始; 不然從 start 以後的第一個設備開始。若 fn 返回非零值, 迭代中止而且那個值從 bus_for_each_dev 或bus_for_each_drv 返回。*/

總線屬性

幾乎 Linux 設備模型中的每一層都提供添加屬性的函數, 總線層也不例外。bus_attribute 類型定義在 <linux/device.h> 以下:

struct bus_attribute {
    struct attribute    attr;
    ssize_t (*show)(struct bus_type *, char * buf);
    ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};


能夠看出struct bus_attribute 和struct attribute 很類似,其實大部分在 kobject 級上的設備模型層都是以這種方式工做。

內核提供了一個宏在編譯時建立和初始化 bus_attribute 結構:

BUS_ATTR(_name,_mode,_show,_store)/*這個宏聲明一個結構, 將 bus_attr_ 做爲給定 _name 的前綴來建立總線的真正名稱*/

/*總線的屬性必須顯式調用 bus_create_file 來建立:*/
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);

/*刪除總線的屬性調用:*/
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);

例如建立一個包含源碼版本號簡單屬性文件方法以下:

static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
 return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}

static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);

/*在模塊加載時建立屬性文件:*/
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
 printk(KERN_NOTICE "Unable to create version attribute\n");

/*這個調用建立一個包含 lddbus 代碼的版本號的屬性文件(/sys/bus/ldd/version)*/


 

設備

在最底層, Linux 系統中的每一個設備由一個 struct device 表明:

struct device {
    struct klist        klist_children;
    struct klist_node    knode_parent;   /* node in sibling list */
    struct klist_node    knode_driver;
    struct klist_node    knode_bus;
    struct device        *parent;/* 設備的 "父" 設備,該設備所屬的設備,一般一個父設備是某種總線或者主控制器. 若是 parent 是 NULL, 則該設備是頂層設備,較少見 */

    struct kobject kobj;/*表明該設備並將其鏈接到結構體系中的 kobject; 注意:做爲通用的規則, device->kobj->parent 應等於 device->parent->kobj*/
    char    bus_id[BUS_ID_SIZE];/*在總線上惟一標識該設備的字符串;例如: PCI 設備使用標準的 PCI ID 格式, 包含:域, 總線, 設備, 和功能號.*/
    struct device_type    *type;
    unsigned        is_registered:1;
    unsigned        uevent_suppress:1;
    struct device_attribute uevent_attr;
    struct device_attribute *devt_attr;

    struct semaphore    sem;  /* semaphore to synchronize calls to its driver. */
    struct bus_type    * bus;     /*標識該設備鏈接在何種類型的總線上*/
    struct device_driver *driver;    /*管理該設備的驅動程序*/
    void        *driver_data;    /*該設備驅動使用的私有數據成員*/
    void        *platform_data;    /* Platform specific data, device core doesn't touch it */
    struct dev_pm_info    power;

#ifdef CONFIG_NUMA
    int        numa_node;   /* NUMA node this device is close to */
#endif
    u64        *dma_mask;    /* dma mask (if dma'able device) */
    u64        coherent_dma_mask;/* Like dma_mask, but for
                     alloc_coherent mappings as
                     not all hardware supports
                     64 bit addresses for consistent
                     allocations such descriptors. */

    struct list_head    dma_pools;    /* dma pools (if dma'ble) */

    struct dma_coherent_mem    *dma_mem; /* internal for coherent mem override */
    /* arch specific additions */
    struct dev_archdata    archdata;

    spinlock_t        devres_lock;
    struct list_head    devres_head;

    /* class_device migration path */
    struct list_head    node;
    struct class        *class;
    dev_t          devt;       /* dev_t, creates the sysfs "dev" */
    struct attribute_group    **groups;    /* optional groups */

    void    (*release)(struct device * dev);/*當這個設備的最後引用被刪除時,內核調用該方法; 它從被嵌入的 kobject 的 release 方法中調用。全部註冊到核心的設備結構必須有一個 release 方法, 不然內核將打印錯誤信息*/
};
/*在註冊 struct device 前,最少要設置parent, bus_id, bus, 和 release 成員*/

設備註冊

設備的註冊和註銷函數爲:

int device_register(struct device *dev);
void device_unregister(struct device *dev);

一個實際的總線也是一個設備,因此必須單獨註冊,如下爲 lddbus 在編譯時註冊它的虛擬總線設備源碼:

static void ldd_bus_release(struct device *dev)
{
 printk(KERN_DEBUG "lddbus release\n");
}

struct device ldd_bus = {
 .bus_id = "ldd0",
 .release = ldd_bus_release

}; /*這是頂層總線,parent 和 bus 成員爲 NULL*/

/*做爲第一個(而且惟一)總線, 它的名字爲 ldd0,這個總線設備的註冊代碼以下:*/
ret = device_register(&ldd_bus);
if (ret)
 printk(KERN_NOTICE "Unable to register ldd0\n");
/*一旦調用完成, 新總線會在 sysfs 中 /sys/devices 下顯示,任何掛到這個總線的設備會在 /sys/devices/ldd0 下顯示*/

設備屬性

sysfs 中的設備入口可有屬性,相關的結構是:

/* interface for exporting device attributes 這個結構體和《LDD3》中的不一樣,已經被更新過了,請特別注意!*/
struct device_attribute {
    struct attribute attr;
    ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
};

/*設備屬性結構可在編譯時創建, 使用如下宏:*/
DEVICE_ATTR(_name,_mode,_show,_store);
/*這個宏聲明一個結構, 將 dev_attr_ 做爲給定 _name 的前綴來命名設備屬性

/*屬性文件的實際處理使用如下函數:*/
 int device_create_file(struct device *device,    struct device_attribute * entry);
 void device_remove_file(struct device * dev, struct device_attribute * attr);

設備結構的嵌入

device 結構包含設備模型核心用來模擬系統的信息。但大部分子系統記錄了關於它們又擁有的設備的額外信息,因此不多單純用 device 結構表明設備,而是,一般將其嵌入一個設備的高層表示中。底層驅動幾乎不知道 struct device。

lddbus 驅動建立了它本身的 device 類型,並指望每一個設備驅動使用這個類型來註冊它們的設備:

struct ldd_device {
 char *name;
 struct ldd_driver *driver;
 struct device dev;
};
#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);

lddbus 導出的註冊和註銷接口以下:

/*
 * LDD devices.
 */

/*
 * For now, no references to LDDbus devices go out which are not
 * tracked via the module reference count, so we use a no-op
 * release function.
 */
static void ldd_dev_release(struct device *dev)
{ }

int register_ldd_device(struct ldd_device *ldddev)
{
    ldddev->dev.bus = &ldd_bus_type;
    ldddev->dev.parent = &ldd_bus;
    ldddev->dev.release = ldd_dev_release;
    strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);
    return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);

void unregister_ldd_device(struct ldd_device *ldddev)
{
    device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);

 

 sculld 驅動添加一個本身的屬性到它的設備入口,稱爲 dev, 僅包含關聯的設備號,源碼以下:

static ssize_t sculld_show_dev(struct device *ddev,struct device_attribute *attr, char *buf)
{
 struct sculld_dev *dev = ddev->driver_data;
 return print_dev_t(buf, dev->cdev.dev);
}

static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);

/*接着, 在初始化時間, 設備被註冊, 而且 dev 屬性經過下面的函數被建立:*/
static void sculld_register_dev(struct sculld_dev *dev, int index)
{
 sprintf(dev->devname, "sculld%d", index);
 dev->ldev.name = dev->devname;
 dev->ldev.driver = &sculld_driver;
 dev->ldev.dev.driver_data = dev;
 register_ldd_device(&dev->ldev);
 if (device_create_file(&dev->ldev.dev, &dev_attr_dev))
    printk( "Unable to create dev attribute ! \n"); 
} /*注意:程序使用 driver_data 成員來存儲指向咱們本身的內部的設備結構的指針。請檢查
device_create_file的返回值,不然編譯時會有警告。*/


 

設備驅動程序

設備模型跟蹤全部系統已知的驅動,主要目的是使驅動程序核心能協調驅動和新設備之間的關係。一旦驅動在系統中是已知的對象就可能完成大量的工做。驅動程序的結構體 device_driver 定義以下:

/*定義在<linux/device.h>*/
struct device_driver {
    const char        * name;/*驅動程序的名字( 在 sysfs 中出現 )*/
    struct bus_type        * bus;/*驅動程序所操做的總線類型*/

    struct kobject        kobj;/*內嵌的kobject對象*/
    struct klist        klist_devices;/*當前驅動程序能操做的設備鏈表*/
    struct klist_node    knode_bus;

    struct module        * owner;
    const char         * mod_name;    /* used for built-in modules */
    struct module_kobject    * mkobj;

    int    (*probe)    (struct device * dev);/*查詢一個特定設備是否存在及驅動是否能夠使用它的函數*/
    int    (*remove)    (struct device * dev);/*將設備從系統中刪除*/
    void    (*shutdown)    (struct device * dev);/*關閉設備*/
    int    (*suspend)    (struct device * dev, pm_message_t state);
    int    (*resume)    (struct device * dev);
};

/*註冊device_driver 結構的函數是:*/
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

/*driver的屬性結構在:*/
struct driver_attribute {
 struct attribute attr;
 ssize_t (*show)(struct device_driver *drv, char *buf);
 ssize_t (*store)(struct device_driver *drv, const char *buf, size_t count);
};
DRIVER_ATTR(_name,_mode,_show,_store)

/*屬性文件建立的方法:*/
int driver_create_file(struct device_driver * drv, struct driver_attribute * attr);
void driver_remove_file(struct device_driver * drv, struct driver_attribute * attr);

/*bus_type 結構含有一個成員( drv_attrs ) 指向一組爲屬於該總線的全部設備建立的默認屬性*/

 

在更新的內核裏,這個結構體變得更簡潔了,隱藏了無需驅動編程人員知道的一些成員:

/*in Linux 2.6.26.5*/

struct device_driver {
    const char        *name;
    struct bus_type        *bus;

    struct module        *owner;
    const char         *mod_name;    /* used for built-in modules */

    int (*probe(struct device *dev);
    int (*remove(struct device *dev);
    void (*shutdown(struct device *dev);
    int (*suspend(struct device *dev, pm_message_t state);
    int (*resume(struct device *dev);
    struct attribute_group **groups;

    struct driver_private *p;
};


struct driver_private {
    struct kobject kobj;
    struct klist klist_devices;
    struct klist_node knode_bus;
    struct module_kobject *mkobj;
    struct device_driver *driver;
};
#define to_driver(obj) container_of(objstruct driver_private, kobj)

 

驅動程序結構的嵌入

對大多數驅動程序核心結構, device_driver 結構一般被嵌入到一個更高層的、總線相關的結構中。

以lddbus 子系統爲例,它定義了ldd_driver 結構:

struct ldd_driver {
 char *version;
 struct module *module;
 struct device_driver driver;
 struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);

lddbus總線中相關的驅動註冊和註銷函數是:

/*
 * Crude driver interface.
 */
static ssize_t show_version(struct device_driver *driver, char *buf)
{
    struct ldd_driver *ldriver = to_ldd_driver(driver);
    sprintf(buf, "%s\n", ldriver->version);
    return strlen(buf);
}

int register_ldd_driver(struct ldd_driver *driver)
{
 int ret;
 driver->driver.bus = &ldd_bus_type;
 ret = driver_register(&driver->driver);/*註冊底層的 device_driver 結構到核心*/
 if (ret)
 return ret;
 driver->version_attr.attr.name = "version";/* driver_attribute 結構必須手工填充*/
 driver->version_attr.attr.owner = driver->module;/*注意:設定 version 屬性的擁有者爲驅動模塊, 不是 lddbus 模塊!由於 show_version 函數是使用驅動模塊所建立的 ldd_driver 結構,若 ldd_driver 結構在一個用戶空間進程試圖讀取版本號時已經註銷,就會出錯*/
 driver->version_attr.attr.mode = S_IRUGO;
 driver->version_attr.show = show_version;
 driver->version_attr.store = NULL;
 return driver_create_file(&driver->driver, &driver->version_attr);/*創建版本屬性,由於這個屬性在運行時被建立,因此不能使用 DRIVER_ATTR 宏*/
}

void unregister_ldd_driver(struct ldd_driver *driver)
{
    driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);

在sculld 中建立的 ldd_driver 結構以下:

/* Device model stuff */
static struct ldd_driver sculld_driver = {
    .version = "$Revision: 1.21 $",
    .module = THIS_MODULE,
    .driver = {
        .name = "sculld",
    },
};/*只要一個簡單的 register_ldd_driver 調用就可添加它到系統中。一旦完成初始化, 驅動信息可在 sysfs 中顯示*/


 

類 子系統

類是一個設備的高層視圖, 它抽象出了底層的實現細節,從而容許用戶空間使用設備所提供的功能, 而不用關心設備是如何鏈接和工做的。類成員一般由上層代碼所控制, 而無需驅動的明確支持。但有些狀況下驅動也須要直接處理類。

幾乎全部的類都顯示在 /sys/class 目錄中。出於歷史的緣由,有一個例外:塊設備顯示在 /sys/block目錄中。在許多狀況, 類子系統是向用戶空間導出信息的最好方法。當類子系統建立一個類時, 它將徹底擁有這個類,根本不用擔憂哪一個模塊擁有那些屬性,並且信息的表示也比較友好。

爲了管理類,驅動程序核心導出了一些接口,其目的之一是提供包含設備號的屬性以便自動建立設備節點,因此udev的使用離不開類。 類函數和結構與設備模型的其餘部分遵循相同的模式,因此真正嶄新的概念是不多的。

注意:class_simple 是老接口,在2.6.13中已被刪除,這裏再也不研究。

管理類的接口

類由 struct class 的結構體來定義:

/*
 * device classes
 */
struct class {
    const char        * name;/*每一個類須要一個惟一的名字, 它將顯示在 /sys/class 中*/
    struct module        * owner;

    struct kset        subsys;
    struct list_head    children;
    struct list_head    devices;
    struct list_head    interfaces;
    struct kset        class_dirs;
    struct semaphore    sem;    /* locks both the children and interfaces lists */

    struct class_attribute        * class_attrs;/* 指向類屬性的指針(以NULL結尾) */
    struct class_device_attribute    * class_dev_attrs;/* 指向類中每一個設備的一組默認屬性的指針 */
    struct device_attribute        * dev_attrs;

    int    (*uevent)(struct class_device *dev, char **envp,
             int num_envp, char *buffer, int buffer_size);/* 類熱插拔產生時添加環境變量的函數 */
    int    (*dev_uevent)(struct device *dev, char **envp, int num_envp,
                char *buffer, int buffer_size);/* 類中的設備熱插拔時添加環境變量的函數 */

    void    (*release)(struct class_device *dev);/* 把設備從類中刪除的函數 */
    void    (*class_release)(struct class *class);/* 刪除類自己的函數 */
    void    (*dev_release)(struct device *dev);

    int    (*suspend)(struct device *, pm_message_t state);
    int    (*resume)(struct device *);
};


/*類註冊函數:*/
int class_register(struct class *cls);
void class_unregister(struct class *cls);

/*類屬性的接口:*/
struct class_attribute {
 struct attribute attr;
 ssize_t (*show)(struct class *cls, char *buf);
 ssize_t (*store)(struct class *cls, const char *buf, size_t count);
};
CLASS_ATTR(_name,_mode,_show,_store);
int class_create_file(struct class *cls, const struct class_attribute *attr);void class_remove_file(struct class *cls, const struct class_attribute *attr);


在更新的內核裏,這個結構體變得簡潔了,刪除了一些成員:

/*in Linux 2.6.26.5*/

/*
 * device classes
 */
struct class {
    const char        *name;
    struct module        *owner;

    struct kset        subsys;
    struct list_head    devices;
    struct list_head    interfaces;
    struct kset        class_dirs;
    struct semaphore    sem/* locks children, devices, interfaces */
    struct class_attribute        *class_attrs;
    struct device_attribute        *dev_attrs;

    int (*dev_uevent)(struct device *devstruct kobj_uevent_env *env);

    void (*class_release)(struct class *class);
    void (*dev_release)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);
};

類設備(在新內核中已被刪除)

類存在的真正目的是給做爲類成員的各個設備提供一個容器,成員由 struct class_device 來表示:

struct class_device {
    struct list_head    node;/*for internal use by the driver core only*/
    struct kobject        kobj;/*for internal use by the driver core only*/
    struct class        * class;    /* 指向該設備所屬的類,必須*/
    dev_t            devt;        /* dev_t, creates the sysfs "dev" ,for internal use by the driver core only*/
    struct class_device_attribute *devt_attr;/*for internal use by the driver core only*/
    struct class_device_attribute uevent_attr;
    struct device        * dev;        /* 指向此設備相關的 device 結構體,可選。若不爲NULL,應是一個從類入口到/sys/devices 下相應入口的符號鏈接,以便用戶空間查找設備入口*/
    void            * class_data;    /* 私有數據指針 */
    struct class_device    *parent;    /* parent of this child device, if there is one */
    struct attribute_group ** groups;    /* optional groups */

    void    (*release)(struct class_device *dev);
    int    (*uevent)(struct class_device *dev, char **envp,
             int num_envp, char *buffer, int buffer_size);
    char    class_id[BUS_ID_SIZE];    /* 此類中的惟一的名字 */
};

/*類設備註冊函數:*/
int class_device_register(struct class_device *cd);
void class_device_unregister(struct class_device *cd);

/*重命名一個已經註冊的類設備入口:*/
int class_device_rename(struct class_device *cd, char *new_name);

/*類設備入口屬性:*/
struct class_device_attribute {
 struct attribute attr;
 ssize_t (*show)(struct class_device *cls, char *buf);
 ssize_t (*store)(struct class_device *cls, const char *buf,
 size_t count);
};

CLASS_DEVICE_ATTR(_name, _mode, _show, _store);

/*建立和刪除除struct class中設備默認屬性外的屬性*/
int class_device_create_file(struct class_device *cls, const struct class_device_attribute *attr);
void class_device_remove_file(struct class_device *cls, const struct class_device_attribute *attr);

類接口

類子系統有一個 Linux 設備模型的其餘部分找不到的附加概念,稱爲「接口」, 可將它理解爲一種設備加入或離開類時得到信息的觸發機制,結構體以下:

struct class_interface {
    struct list_head    node;
    struct class        *class;/* 指向該接口所屬的類*/

    int (*add(struct class_device *, struct class_interface *);

 /*當一個類設備被加入到在 class_interface 結構中指定的類時, 將調用接口的 add 函數,進行一些設備須要的額外設置,一般是添加更多屬性或其餘的一些工做*/
    void (*remove)    (struct class_device *, struct class_interface *);/*一個接口的功能是簡單明瞭的. 當設備從類中刪除, 將調用remove 方法來進行必要的清理*/
    int (*add_dev)     (struct device *, struct class_interface *);
    void (*remove_dev(struct device *, struct class_interface *);
};

/*註冊或註銷接口的函數:*/
int class_interface_register(struct class_interface *class_intf);
void class_interface_unregister(struct class_interface *class_intf);
/*一個類可註冊多個接口*/


 

Linux設備驅動模型之platform總線

1 平臺設備和驅動初識

platform是一個虛擬的地址總線,相比pci,usb,它主要用於描述SOC上的片上資源,好比s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的資源有一個共同點,就是在cpu的總線上直接取址。

平臺設備會分到一個名稱(用在驅動綁定中)以及一系列諸如地址和中斷請求號(IRQ)之類的資源.
struct platform_device {
    const char    * name;
    int        id;
    struct device    dev;
    u32        num_resources;
    struct resource    * resource;
};

平臺驅動遵循標準驅動模型的規範, 也就是說發現/列舉(discovery/enumeration)在驅動以外處理, 而
由驅動提供probe()和remove方法. 平臺驅動按標準規範對電源管理和關機通告提供支持
struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*suspend_late)(struct platform_device *, pm_message_t state);
    int (*resume_early)(struct platform_device *);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
};
probe()總應該覈實指定的設備硬件確實存在;平臺設置代碼有時不能肯定這一點. 枚舉(probing)能夠使用的設備資源包括時鐘及設備的platform_data.(譯註: platform_data定義在device.txt中的"基本設備結構體"中.)

平臺驅動經過普通的方法註冊自身
int platform_driver_register(struct platform_driver *drv);

或者, 更常見的狀況是已知設備不可熱插拔, probe()過程即可以駐留在一個初始化區域(init section)
中,以便減小驅動的運行時內存佔用(memory footprint)
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *));


設備列舉
按規定, 應由針對平臺(也適用於針對板)的設置代碼來註冊平臺設備
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev)

通常的規則是隻註冊那些實際存在的設備, 但也有例外. 例如, 某外部網卡未必會裝配在全部的板子上,
或者某集成控制器所在的板上可能沒掛任何外設, 而內核卻須要被配置來支持這些網卡和控制器


有些狀況下, 啓動固件(boot firmware)會導出一張裝配到板上的設備的描述表. 若是沒有這張表, 一般
就只能經過編譯針對目標板的內核來讓系統設置代碼安裝正確的設備了. 這種針對板的內核在嵌入式和自定
義的系統開發中是比較常見的.

多數狀況下, 分給平臺設備的內存和中斷請求號資源是不足以讓設備正常工做的. 板設置代碼一般會用設備
的platform_data域來存放附加信息, 並向外提供它們.


嵌入式系統時常須要爲平臺設備提供一個或多個時鐘信號. 除非被用到, 這些時鐘通常處於靜息狀態以節電.
系統設置代碼也負責爲設備提供這些時鐘, 以便設備能在它們須要是調用

clk_get(&pdev->dev, clock_name).

也能夠用以下函數來一次性完成分配空間和註冊設備的任務
struct platform_device *platform_device_register_simple( const char *name, int id, struct resource *res, unsigned int nres)

設備命名和驅動綁定
platform_device.dev.bus_id是設備的真名. 它由兩部分組成:

        *platform_device.name ... 這也被用來匹配驅動

        *platform_device.id ...        設備實例號, 或者用"-1"表示只有一個設備.

鏈接這兩項, 像"serial"/0就表示bus_id爲"serial.0", "serial"/3表示bus_id爲"serial.3";
上面二例都將使用名叫"serial"的平臺驅動. 而"my_rtc"/-1的bus_id爲"my_rtc"(無實例號), 它的
平臺驅動爲"my_rtc".



2 平臺總線
下面咱們看看與platform相關的操做
平臺總線的初始化
int __init platform_bus_init(void)
{
    int error;

    error = device_register(&platform_bus);
    if (error)
        return error;
    error =  bus_register(&platform_bus_type);
    if (error)
        device_unregister(&platform_bus);
    return error;
}

這段初始化代碼建立了一個platform設備,之後屬於platform類型的設備就會以此爲parent,增長的設備會出如今/sys/devices/platform目錄下

[root@wangping platform]# pwd
/sys/devices/platform
[root@wangping platform]# ls
bluetooth  floppy.0  i8042  pcspkr  power  serial8250  uevent  vesafb.0

緊接着註冊名爲platform的平臺總線
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_attrs    = platform_dev_attrs,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .suspend    = platform_suspend,
    .suspend_late    = platform_suspend_late,
    .resume_early    = platform_resume_early,
    .resume        = platform_resume,
};

int platform_device_add(struct platform_device *pdev)
{......
        pdev->dev.parent = &platform_bus;  //增長的platform設備之後都以platform_bus(platform設備)爲父節點
        pdev->dev.bus = &platform_bus_type; //platform類型設備都掛接在platform總線上 /sys/bus/platform/
......
}

3 platform device的註冊

struct platform_device {
    const char    * name;
    int        id;
    struct device    dev;
    u32        num_resources;
    struct resource    * resource;
};


1)動態分配一個名爲name的platform設備

struct platform_object {
    struct platform_device pdev;
    char name[1];
};

struct platform_device *platform_device_alloc(const char *name, int id)
{
    struct platform_object *pa;

    pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);//因爲 platform_object內name只有一個字節,因此須要多分配strlen(name)長度
    if (pa) {
        strcpy(pa->name, name);
        pa->pdev.name = pa->name;
        pa->pdev.id = id;
        device_initialize(&pa->pdev.dev);
        pa->pdev.dev.release = platform_device_release;
    }

    return pa ? &pa->pdev : NULL;
}

實際上就是分配一個platform_object 結構體(包含了一個platform device結構體)並初始化內部成員platform driver,而後返回platform driver結構體以完成動態分配一個platform設備
而後調用platform_add_devices()以追加一個platform 設備到platform bus上
int platform_device_add(struct platform_device *pdev)
{
    int i, ret = 0;

    if (!pdev)
        return -EINVAL;

    if (!pdev->dev.parent)
        pdev->dev.parent = &platform_bus; //初始化設備的父節點所屬類型爲platform device(platform_bus)

    pdev->dev.bus = &platform_bus_type;  //初始化設備的總線爲platform bus

    if (pdev->id != -1)
        snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
             pdev->id);
    else
        strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);

    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];

        if (r->name == NULL)
            r->name = pdev->dev.bus_id;

        p = r->parent;
        if (!p) {
            if (r->flags & IORESOURCE_MEM)
                p = &iomem_resource;
            else if (r->flags & IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p && insert_resource(p, r)) {                //插入資源到資源樹上
            printk(KERN_ERR
                   "%s: failed to claim resource %d\n",
                   pdev->dev.bus_id, i);
            ret = -EBUSY;
            goto failed;
        }
    }

    pr_debug("Registering platform device '%s'. Parent at %s\n",
         pdev->dev.bus_id, pdev->dev.parent->bus_id);

    ret = device_add(&pdev->dev);       //註冊特定的設備到platform bus上
    if (ret == 0)
        return ret;

 failed:
    while (--i >= 0)
        if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
            release_resource(&pdev->resource[i]);
    return ret;
}

上面的操做咱們看到另一個陌生的結構 設備資源(struct resource)
關於資源的操做(從上面已經瞭解,平臺設備會分到一系列諸如地址和中斷請求號(IRQ)之類的資源.
struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;// IORESOURCE_IO  IORESOURCE_MEM IORESOURCE_IRQ IORESOURCE_DMA
    struct resource *parent, *sibling, *child;
};

基於資源的分類(flags)有I/O端口、IRQ、DMA等等,而I/O端口又分爲2種類型, IORESOURCE_IO(I/O映射) IORESOURCE_MEM(內存映射)

這裏說一下關於I/O端口:
CPU對外設IO端口物理地址的編址方式有2種:一種是IO映射方式(IO-mapped), 另外一種是內存映射方式(Memory-mapped)。具體採用哪種方式則取決於CPU的體系結構。 像X86體系對外設就專門實現了一個單獨地址空間,而且有專門的I/O指令來訪問I/O端口,像ARM體系結構一般只是實現一個物理地址空間,I/O端口就被映射到CPU的單一物理地址空間中,而成爲內存的一部分,因此通常資源都採用(IORESOURCE_MEM)。

linux中對設備的資源按照資源樹的結構來組織(其實就是一個鏈表結構的插入、刪除、查找等操做),上面再添加設備(platform_device_add)的同時對相應的資源在資源樹上進行插入操做int insert_resource(struct resource *parent, struct resource *new)


關於platform resource有相關的函數進行對資源的操做。

struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
int platform_get_irq(struct platform_device *, unsigned int);
struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, char *);
int platform_get_irq_byname(struct platform_device *, char *);

例如s3c24210的watchdog資源分配實例:
watchdog寄存器的基地址爲0x5300000
#define S3C2410_PA_WATCHDOG (0x53000000)
#define S3C24XX_SZ_WATCHDOG SZ_1M

static struct resource s3c_wdt_resource[] = {
    [0] = {    
        .start = S3C24XX_PA_WATCHDOG,
        .end   = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
        .flags = IORESOURCE_MEM,    //內存映射
    },
    [1] = {    
        .start = IRQ_WDT,
        .end   = IRQ_WDT,
        .flags = IORESOURCE_IRQ,    //IRQ
    }

};


動態註冊platform device例:
/linux/drivers/serial/8250.c

static int __init serial8250_init(void)
{
    int ret, i;

    if (nr_uarts > UART_NR)
        nr_uarts = UART_NR;

    printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
        "%d ports, IRQ sharing %sabled\n", nr_uarts,
        share_irqs ? "en" : "dis");

    for (i = 0; i < NR_IRQS; i++)
        spin_lock_init(&irq_lists[i].lock);

    ret = uart_register_driver(&serial8250_reg);
    if (ret)
        goto out;

    serial8250_isa_devs = platform_device_alloc("serial8250",
                            PLAT8250_DEV_LEGACY);
    if (!serial8250_isa_devs) {
        ret = -ENOMEM;
        goto unreg_uart_drv;
    }

    ret = platform_device_add(serial8250_isa_devs);
    if (ret)
        goto put_dev;

    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

    ret = platform_driver_register(&serial8250_isa_driver);
    if (ret == 0)
        goto out;

    platform_device_del(serial8250_isa_devs);
 put_dev:
    platform_device_put(serial8250_isa_devs);
 unreg_uart_drv:
    uart_unregister_driver(&serial8250_reg);
 out:
    return ret;
}


也能夠在編譯的時候就肯定設備的相關信息,調用 int platform_device_register(struct platform_device *);

/linux/arch/arm/mach-smdk2410/mach-smdk2410.c

static struct platform_device *smdk2410_devices[] __initdata = {
    &s3c_device_usb,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c,
    &s3c_device_iis,
};

static void __init smdk2410_init(void)
{
    platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices)); //靜態增長一組soc設備,以便在加載驅動的時候匹配相關驅動
    smdk_machine_init();
}

int platform_add_devices(struct platform_device **devs, int num)
{
    int i, ret = 0;

    for (i = 0; i < num; i++) {
        ret = platform_device_register(devs[i]);  //其實是調用 platform_device_register追加platform device
        if (ret) {
            while (--i >= 0)
                platform_device_unregister(devs[i]);
            break;
        }
    }

    return ret;
}

int platform_device_register(struct platform_device * pdev)
{
    device_initialize(&pdev->dev);
    return platform_device_add(pdev);
}

從上面看出這和動態增長一個platform device所作的動做基本上是同樣的(device_initialize,platform_device_add)

例 watchdog設備定義:
struct platform_device s3c_device_wdt = {
    .name          = "s3c2410-wdt",
    .id          = -1,
    .num_resources      = ARRAY_SIZE(s3c_wdt_resource),
    .resource      = s3c_wdt_resource,
};


4 platform driver的註冊
先看結構體,裏面內嵌了一個
struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*suspend_late)(struct platform_device *, pm_message_t state);
    int (*resume_early)(struct platform_device *);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
};

int platform_driver_register(struct platform_driver *drv)
{
    drv->driver.bus = &platform_bus_type;
    if (drv->probe)
        drv->driver.probe = platform_drv_probe;
    if (drv->remove)
        drv->driver.remove = platform_drv_remove;
    if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;
    if (drv->suspend)
        drv->driver.suspend = platform_drv_suspend;
    if (drv->resume)
        drv->driver.resume = platform_drv_resume;
    return driver_register(&drv->driver);
}

指定platform device所屬總線,同時若是爲platform_driver中各項指定了接口,則爲struct device_driver中相應的接口賦值。
那麼是如何賦值的呢?

#define to_platform_driver(drv)    (container_of((drv), struct platform_driver, driver))

static int platform_drv_probe(struct device *_dev)
{
    struct platform_driver *drv = to_platform_driver(_dev->driver);
    struct platform_device *dev = to_platform_device(_dev);

    return drv->probe(dev);
}


從上面能夠看出,是將struct device轉換爲struct platform_device和struct platform_driver.而後調用platform_driver中的相應接口函數來實現,
最後調用 driver_register()將platform driver註冊到總線上。


/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
    ......
    serial8250_isa_devs = platform_device_alloc("serial8250",
                            PLAT8250_DEV_LEGACY);
    if (!serial8250_isa_devs) {
        ret = -ENOMEM;
        goto unreg_uart_drv;
    }

    ret = platform_device_add(serial8250_isa_devs);
    if (ret)
        goto put_dev;

    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

    ret = platform_driver_register(&serial8250_isa_driver);
    if (ret == 0)
        goto out;

    ......
}
在設備成功進行了註冊後,調用platform_driver_register()進行驅動註冊。

最後,總線上註冊有設備和相應的驅動,就會進行設備和驅動的匹配。
在找到一個設備和驅動的配對後, 驅動綁定是經過調用probe()由驅動核心自動完成的. 若是probe()成功,
驅動和設備就正常綁定了. 有三種不一樣的方法來進行配對:

-設備一被註冊, 就檢查對應總線下的各驅動, 看是否匹配. 平臺設備應在系統啓動過程的早期被註冊

-當驅動經過platform_driver_register()被註冊時, 就檢查對應總線上全部未綁定的設備.驅動一般在啓動過程的後期被註冊或經過裝載模塊來註冊.

-用platform_driver_probe()來註冊驅動的效果跟用platform_driver_register()幾乎相同, 不一樣點僅在於,若是再有設備註冊, 驅動就不會再被枚舉了. (這可有可無, 由於這種接口只用在不可熱插拔的設備上.)
驅動和設備的匹配僅僅是經過名稱來匹配的
static int platform_match(struct device * dev, struct device_driver * drv)
{
    struct platform_device *pdev = container_of(dev, struct platform_device, dev);

    return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

小結:本節總結平臺設備和驅動的模型,這部份知識能夠做爲咱們深刻了解具體平臺設備驅動的基礎。

 

 

BUS

在設備模型中,全部的device都是經過總線bus 鏈接,這裏的bus包括一般意義的總線如usbpci,也包括虛擬的platform總線。

[root@wangp bus]# pwd

/sys/bus

[root@wangp bus]# ls

ac97  acpi  bluetooth  gameport  i2c  ide  pci  pci_express  pcmcia  platform  pnp  scsi  serio  usb

[root@wangp platform]# pwd

/sys/bus/platform

[root@wangp platform]# ls

devices  drivers

 

struct bus_type {

       const char              * name;

       struct module         * owner;

 

       struct kset             subsys;

       struct kset             drivers;

       struct kset             devices;

       struct klist              klist_devices;

       struct klist              klist_drivers;

 

       struct blocking_notifier_head bus_notifier;

 

       struct bus_attribute * bus_attrs;

       struct device_attribute    * dev_attrs;

       struct driver_attribute     * drv_attrs;

 

       int           (*match)(struct device * dev, struct device_driver * drv);

       int           (*uevent)(struct device *dev, struct kobj_uevent_env *env);

       int           (*probe)(struct device * dev);

       int           (*remove)(struct device * dev);

       void         (*shutdown)(struct device * dev);

 

       int (*suspend)(struct device * dev, pm_message_t state);

       int (*suspend_late)(struct device * dev, pm_message_t state);

       int (*resume_early)(struct device * dev);

       int (*resume)(struct device * dev);

 

       unsigned int drivers_autoprobe:1;

};

 

name是總線的名字,每一個總線下都有本身的子系統,其中包含2ksetdeviecedriver,分別表明已知總線的驅動和插入總線的設備

platform總線的聲明以下:

 

struct bus_type platform_bus_type = {

       .name             = "platform",

       .dev_attrs       = platform_dev_attrs,

       .match            = platform_match,

       .uevent          = platform_uevent,

       .suspend  = platform_suspend,

       .suspend_late  = platform_suspend_late,

       .resume_early  = platform_resume_early,

       .resume          = platform_resume,

};

 

只有不多的bus_type成員須要初始化,大部分交給kernel來處理

 

 

關於總線的操做經常使用的以下:

int  bus_register(struct bus_type * bus);

void bus_unregister(struct bus_type * bus);

/* iterator helpers for buses */

 

列舉總線上從start以後的每一個設備,並進行fn操做,一般用途是對bus上的設備和驅動進行綁定

int bus_for_each_dev(struct bus_type * bus, struct device * start, void * data, int (*fn)(struct device *, void *));

 

int driver_attach(struct device_driver * drv)

{

       return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

}

 

static int __driver_attach(struct device * dev, void * data)

{

       struct device_driver * drv = data;

 

       /*

        * Lock device and try to bind to it. We drop the error

        * here and always return 0, because we need to keep trying

        * to bind to devices and some drivers will return an error

        * simply if it didn't support the device.

        *

        * driver_probe_device() will spit a warning if there

        * is an error.

        */

 

       if (dev->parent)      /* Needed for USB */

              down(&dev->parent->sem);

       down(&dev->sem);

       if (!dev->driver)

              driver_probe_device(drv, dev);

       up(&dev->sem);

       if (dev->parent)

              up(&dev->parent->sem);

 

       return 0;

}

 

幾乎linux設備模型的每一層都提供了添加屬性的函數,總線也不例外

struct bus_attribute {

       struct attribute       attr;

       ssize_t (*show)(struct bus_type *, char * buf); //顯示屬性

       ssize_t (*store)(struct bus_type *, const char * buf, size_t count); //設置屬性

};

建立屬於一個總線的屬性使用(在模塊的加載時間完成)

int  bus_create_file(struct bus_type *,struct bus_attribute *);

void bus_remove_file(struct bus_type *, struct bus_attribute *);

 

在說明下bussysfs裏面的結構,剛纔已經講過,bus_type中有2kset結構對應於devicedriver,也就是說每一個bus下面都會有devicedriver2個文件夾。

 

首先在總線上註冊的驅動會獲得一個文件夾driver,如platform驅動

[root@wangp platform]# pwd

/sys/bus/platform

[root@wangp platform]# ls

devices  drivers

[root@wangp drivers]# pwd

/sys/bus/platform/drivers

[root@wangp drivers]# ls

i8042  pcspkr  serial8250  vesafb

 

而任何在總線/sys/bus/xxx/上發現的設備會獲得一個symlink(符號連接)即/sys/bus/xxx/device指向/sys/device/xxx下面的文件夾

[root@wangp devices]# pwd

/sys/bus/platform/devices

[root@wangp devices]# ls -l

total 0

lrwxrwxrwx 1 root root 0 Jun  6 10:37 bluetooth -> ../../../devices/platform/bluetooth

lrwxrwxrwx 1 root root 0 Jun  6 10:37 floppy.0 -> ../../../devices/platform/floppy.0

lrwxrwxrwx 1 root root 0 Jun  6 10:37 i8042 -> ../../../devices/platform/i8042

lrwxrwxrwx 1 root root 0 Jun  6 10:37 pcspkr -> ../../../devices/platform/pcspkr

lrwxrwxrwx 1 root root 0 Jun  6 10:37 serial8250 -> ../../../devices/platform/serial8250

lrwxrwxrwx 1 root root 0 Jun  6 10:37 vesafb.0 -> ../../../devices/platform/vesafb.0

 

 

DEVICE

struct device {

       struct klist              klist_children;

       struct klist_node     knode_parent;        /* node in sibling list */

       struct klist_node     knode_driver;

       struct klist_node     knode_bus;

       struct device          *parent;  //該設備所屬的設備

 

       struct kobject kobj;

       char bus_id[BUS_ID_SIZE];     /* position on parent bus */

       struct device_type  *type;

       unsigned         is_registered:1;

       unsigned         uevent_suppress:1;

 

       struct semaphore    sem; /* semaphore to synchronize calls to

                                    * its driver.

                                    */

 

       struct bus_type      * bus;             /* type of bus device is on */

       struct device_driver *driver;    /* which driver has allocated this  device */

       void         *driver_data;   /* data private to the driver */

       void         *platform_data;      /* Platform specific data, device  core doesn't touch it */

       struct dev_pm_info power;

 

#ifdef CONFIG_NUMA

       int           numa_node;    /* NUMA node this device is close to */

#endif

       u64         *dma_mask;    /* dma mask (if dma'able device) */

       u64         coherent_dma_mask;/* Like dma_mask, but for

                                        alloc_coherent mappings as

                                        not all hardware supports

                                        64 bit addresses for consistent

                                        allocations such descriptors. */

 

       struct list_head       dma_pools;      /* dma pools (if dma'ble) */

 

       struct dma_coherent_mem     *dma_mem; /* internal for coherent mem   override */

       /* arch specific additions */

       struct dev_archdata archdata;

 

       spinlock_t        devres_lock;

       struct list_head       devres_head;

 

       /* class_device migration path */

       struct list_head       node;

       struct class             *class;

       dev_t                    devt;              /* dev_t, creates the sysfs "dev" */

       struct attribute_group    **groups;       /* optional groups */

 

       void  (*release)(struct device * dev);

};

這個結構至關於一個基類,對於基於特定總線的設備,會派生出特定的device結構(linux的驅動模型有不少結構均可以基於類來看待)

 

struct platform_device {

       const char       * name;

       int           id;

       struct device   dev;

       u32         num_resources;

       struct resource       * resource;

};

 

一個總線設備用以下函數註冊(註冊總線類型)

int device_register(struct device *dev)

{

       device_initialize(dev);

       return device_add(dev);

}

以完成parent name bus_id bus幾個成員的初始化,註冊後能夠在/sys/devices下面看到

void device_unregister(struct device *dev);

 

同時和其相關的屬性爲

struct device_attribute {

       struct attribute       attr;

       ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);

       ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);

};

int device_create_file(struct device *device,struct device_attribute * entry);

device_remove_file(struct device * dev, struct device_attribute * attr);

如下完成一個總線設備的註冊:

static void simple_bus_release(struct device *dev)

{

       printk("simple bus release\n");

}

struct device simple_bus = {

       .bus_id ="simple_bus",

       .release = simple_bus_release

}

 

ret = device_register(&simple_bus);

if(ret)

pritnk("unable to register simple_bus\n");

完成註冊後,simple_bus就能夠再sysfs/sys/devices下面看見,任何掛載這個bus上的device都會在/sys/devices/simple_bus下看到

 

DEVICE_DRIVER

 

struct device_driver {

       const char              * name;//sysfs下顯示

       struct bus_type             * bus;

 

       struct kobject         kobj;

       struct klist              klist_devices;

       struct klist_node     knode_bus;

 

       struct module         * owner;

       const char             * mod_name;  /* used for built-in modules */

       struct module_kobject    * mkobj;

 

       int    (*probe)  (struct device * dev);

       int    (*remove)       (struct device * dev);

       void  (*shutdown)   (struct device * dev);

       int    (*suspend)      (struct device * dev, pm_message_t state);

       int    (*resume)       (struct device * dev);

};

因爲大多數驅動都會帶有特有的針對某種特定總線的信息,所以通常都是基於device_driver來派生出本身

特有的驅動,如

struct platform_driver {

       int (*probe)(struct platform_device *);

       int (*remove)(struct platform_device *);

       void (*shutdown)(struct platform_device *);

       int (*suspend)(struct platform_device *, pm_message_t state);

       int (*suspend_late)(struct platform_device *, pm_message_t state);

       int (*resume_early)(struct platform_device *);

       int (*resume)(struct platform_device *);

       struct device_driver driver;

};

比較xxx_driver device_driver咱們能夠發現,結構體所帶的方法基本相同,在具體應該用的時候是能夠轉換的。

 

驅動的註冊

int driver_register(struct device_driver * drv);

void driver_unregister(struct device_driver * drv);

而大多數驅動會調用針對特定總線的諸如platform_driver_register,pci_driver_register之類的函數去註冊

 

總線(bus)能夠掛接一類設備(device)

驅動(driver)能夠驅動一類設備(device

所以和bus同樣,device_driver也有一個函數爲某個驅動來遍歷全部設備

int driver_for_each_dev(struct device_driver *drv, void *data,int (*callback)(struct device *dev,void *data);

全部device_driver完成註冊後,會在/sys/bus/xxx/driver目錄下看到驅動信息

 

同時相應的屬性內容

struct driver_attribute {

       struct attribute       attr;

       ssize_t (*show)(struct device_driver *, char * buf);

       ssize_t (*store)(struct device_driver *, const char * buf, size_t count);

};

int  driver_create_file(struct device_driver *,struct driver_attribute *);

void driver_remove_file(struct device_driver *, struct driver_attribute *);

 

說了這麼多,如今來理一理kobject kset,subsys,sysfs,bus之間的關係

 

< XMLNAMESPACE PREFIX ="V" /><!--[if !vml]-->
<!--[endif]-->

上圖反映了繼承體系的一個基本結構,kset是一組相同的kobject的集合,kernel能夠經過跟蹤kset來跟蹤所用的特定類型設備,platformpcii2c等,kset起到鏈接做用將設備模型和sysfs聯繫在一塊兒。每一個kset自身都包含一個kobject,這個kobject將做爲不少其餘的kobject的父類,從sys上看,某個kobject的父類是某個目錄,那麼它就是那個目錄的子目錄,parent指針能夠表明目錄層次,這樣典型的設備模型層次就創建起來了,從面向對象的觀點看,kset是頂層的容器類,kset繼承他本身的kobject,而且能夠當作kobject來處理

 

linux設備模型深探(2) - 784192422 - 嵌入式

如圖:kset把它的子類kobject放在鏈表裏面,kset子類鏈表裏面那些kobjectkset指針指向上面的ksetparent指向父類。

 

struct kobject {

       const char              * k_name;

       struct kref              kref;

       struct list_head       entry;

       struct kobject         * parent;

       struct kset             * kset;

       struct kobj_type     * ktype;

       struct sysfs_dirent   * sd;

};

 

struct kset {

       struct kobj_type     *ktype;

       struct list_head       list;

       spinlock_t        list_lock;

       struct kobject         kobj;

       struct kset_uevent_ops  *uevent_ops;

};

 

四、kmalloc和vmalloc的區別

kmalloc()
用於申請較小的、連續的物理內存
1. 以字節爲單位進行分配,在<linux/slab.h>中
2. void *kmalloc(size_t size, int flags) 分配的內存物理地址上連續,虛擬地址上天然連續
3. gfp_mask標誌
:何時使用哪一種標誌?以下:
———————————————————————————————-
情形 相應標誌
———————————————————————————————-
進程上下文,能夠睡眠 GFP_KERNEL
進程上下文,不能夠睡眠 GFP_ATOMIC
中斷處理程序 GFP_ATOMIC
軟中斷 GFP_ATOMIC
Tasklet GFP_ATOMIC
用於DMA的內存,能夠睡眠 GFP_DMA | GFP_KERNEL
用於DMA的內存,不能夠睡眠 GFP_DMA | GFP_ATOMIC
———————————————————————————————-
4. void kfree(const void *ptr)
釋放由kmalloc()分配出來的內存塊


vmalloc()
用於申請較大的內存空間,虛擬內存是連續的
1. 以字節爲單位進行分配,在<linux/vmalloc.h>中
2. void *vmalloc(unsigned long size) 分配的內存虛擬地址上連續,物理地址不連續
3. 通常狀況下,只有硬件設備才須要物理地址連續的內存,由於硬件設備每每存在於MMU以外,根本不瞭解虛擬地址;但爲了性能上的考慮,內核中通常使用 kmalloc(),而只有在須要得到大塊內存時才使用vmalloc(),例如當模塊被動態加載到內核當中時,就把模塊裝載到由vmalloc()分配 的內存上。
4.void vfree(void *addr),這個函數能夠睡眠,所以不能從中斷上下文調用。


malloc(), vmalloc()和kmalloc()區別
[*]kmalloc和vmalloc是分配的是內核的內存,malloc分配的是用戶的內存
[*]kmalloc保證分配的內存在物理上是連續的,vmalloc保證的是在虛擬地址空間上的連續,malloc不保證任何東西(這點是本身猜想的,不必定正確)
[*]kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相對較大
[*]內存只有在要被DMA訪問的時候才須要物理上連續
[*]vmalloc比kmalloc要慢

 

五、module_init的級別

 在Linux底下寫過driver模塊的對這個宏必定不會陌生。module_init宏在MODULE宏有沒有定義的狀況下展開的內容是不一樣的,若是這個宏沒有定義,基本上代表閣下的模塊是要編譯進內核的(obj-y)。
1.在MODULE沒有定義這種狀況下,module_init定義以下:
#define module_init(x) __initcall(x);
由於
#define __initcall(fn)                            device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
因此,module_init(x)最終展開爲:
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
更直白點,假設閣下driver所對應的模塊的初始化函數爲int gpio_init(void),那麼module_init(gpio_init)實際上等於:
static initcall_t  __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;
就是聲明一類型爲initcall_t(typedef int (*initcall_t)(void))函數指針類型的變量__initcall_gpio_init_6並將gpio_init賦值與它。
這裏的函數指針變量聲明比較特殊的地方在於,將這個變量放在了一名爲".initcall6.init"節中。接下來結合vmlinux.lds中的
.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {
   __initcall_start = .;
   *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
   __initcall_end = .;
   }
以及do_initcalls:
static void __init do_initcalls(void)
{
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
那麼就不難理解閣下模塊中的module_init中的初始化函數什麼時候被調用了:在系統啓動過程當中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。

 

在MODULE被定義的狀況下(大部分可動態加載的driver模塊都屬於此, obj-m),module_init定義以下:
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
這段宏定義關鍵點是後面一句,經過alias將initfn變名爲init_module。前面那個__inittest的定義實際上是種技巧,用來對initfn進行某種靜態的類型檢查,若是閣下將模塊初始化函數定義成,好比,void gpio_init(void)或者是int gpio_init(int),那麼在編譯時都會有相似下面的warning:
GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type
經過module_init將模塊初始化函數統一別名爲init_module,這樣之後insmod時候,在系統內部會調用sys_init_module()去找到init_module函數的入口地址。
若是objdump -t gpio.ko,就會發現init_module和gpio_init位於相同的地址偏移處。簡言之,這種狀況下模塊的初始化函數在insmod時候被調用。

 

六、添加驅動

靜態加載和動態加載:
靜態加載是系統啓動的時候由內核自動加載的,這個要事先將驅動編譯進內核才行;
動態加載,也就是模塊加載方式,這種方式下驅動以模塊的形式存放在文件系統中,須要時動態載入內核,這種主要用在調試的時候,比較方便靈活。insmod module.ko

 

七、IIC原理,總線框架,設備編寫方法,i2c_msg

首先來看一下原理圖:
IIC總線驅動基礎知識 - 小白 - 小白的博客
 
咱們看到它有兩根線:數據線和時鐘線,他們用於數據傳輸。
A0、A一、A2是設備地址,它是已經固化在硬件層的。
 
再來看看AT24c02與2440鏈接圖:
IIC總線驅動基礎知識 - 小白 - 小白的博客 
 咱們要知道2440與at24c02之間屬於主從鏈接,也就是說全部的會話必須由主設備發起,這裏的主設備天然是2440。那麼當2440的iic總線上掛載了多個存儲設備時,2440如何去找到本身想要訪問的存儲設備呢?這就須要設備地址,那麼當訪問到具體的設備後,想要讀寫特定的存儲空間,還須要知道存儲地址!那麼這些地址是如何組織的呢?咱們能夠看看下面這幅圖:
IIC總線驅動基礎知識 - 小白 - 小白的博客
 
因爲咱們用到的是at24c02,也就是2k的存儲設備,因此只須要看第一行:
這裏就是設備地址了,高4位是固化的,不用管,A二、A一、A0能夠根據硬件鏈接來設置。fl2440開發板原理圖可知 A二、A一、A0都是0,因此從設備地址是:1010000
咱們再來看看4k,8k,16k的狀況:咱們知道設備地址發出以後在發出的是存儲地址,而存儲地址都是8位的,那麼對 4k,8k,16k的狀況怎麼辦呢?他們可不是8位地址就能夠尋址的。8位地址只能尋址一頁數據,4k是2頁,8k是4頁,16k是8頁,咱們看到上面圖片p0能夠表明2頁,p一、p0能夠表明4頁,p二、p一、p0能夠表明8頁,這下明白了!
至於存儲地址,固然能夠根據本身的須要來設置了。
 
那麼經過iic總線與2440鏈接的存儲設備如何讀寫呢?咱們來看一看時序圖,不過由於讀寫都分爲好幾種,咱們分別只選其中一種來分析:
首先咱們來分析一下隨機讀時序:
IIC總線驅動基礎知識 - 小白 - 小白的博客
一開始SDA、SCL信號都是高電平,而後SDA出現一個降低沿,表示開始發送數據。而後會發出設備地址,就是咱們上面提到過的,它是7位的,第8位是讀寫標識位。因爲只有一根數據線,顯然數據是一位一位發送的。當SCL是低電平的時候,SDA上的數據能夠變化,當SCL爲高電平的時候,SDA上的數據被讀走。那麼要發送完這8位的數據,須要8個時鐘週期。在第九個時鐘週期,2440會交出總線的控制權,由從機來驅動,從機會向2440發送ack應答信號。
好下面咱們略去比較細節的東西來分析這個隨機讀時序:
首先發送設備地址並附帶寫信號,這樣就找到了設備並告訴設備下一次是2440向設備發送
2440向設備發送要讀的地址
發送設備地址並附帶讀信號,表示下一次是設備向2440發送數據
而後是設備向2440發送數據
 
再來看字節寫時序:
IIC總線驅動基礎知識 - 小白 - 小白的博客
首先發出地址信號並附加些標識,這樣就找到了要寫的設備並告訴設備下一次是2440向設備發送
而後發出要寫的存儲地址
以後開始寫入數據

 

Linux設備驅動之I2C架構分析

1、前言
       I2c是philips提出的外設總線.I2C只有兩條線,一條串行數據線:SDA,一條是時鐘線SCL.正由於這樣,它方便了工程人員的佈線.另外,I2C是一種多主機控制總線.它和USB總線不一樣,USB是基於master-slave機制,任何設備的通訊必須由主機發起才能夠.而 I2C 是基於multi master機制.一同總線上可容許多個master.關於I2C協議的知識,這裏再也不贅述.可自行下載spec閱讀便可.
2、I2C架構概述
       在linux中,I2C驅動架構以下所示:

Linux設備驅動之I2C架構分析【轉】 - 喧鬧的寂寞 - 喧鬧的寂寞

      如上圖所示,每一條I2C對應一個adapter.在kernel中,每個adapter提供了一個描述的結構(struct i2c_adapter),也定義了adapter支持的操做(struct i2c_adapter).再經過i2c core層將i2c設備與i2c adapter關聯起來.
     這個圖只是提供了一個大概的框架.在下面的代碼分析中,從下至上的來分析這個框架圖.如下的代碼分析是基於linux 2.6.26.分析的代碼基本位於: linux-2.6.26.3/drivers/i2c/位置.

3、adapter註冊
       在 kernel中提供了兩個adapter註冊接口,分別爲

       int i2c_add_adapter(struct i2c_adapter *adapter);

                                             和

       int i2c_add_numbered_adapter(struct i2c_adapter *adapter);

       因爲在系統中可能存在多個adapter,所以將每一條I2C總線對應一個編號(下文中稱爲 I2C總線號)。這個總線號的PCI中的總線號不一樣,它和硬件無關,只是軟件上便於區分而已 (這句很重要)
       i2c_add_adapter()而言,它使用的是動態總線號,即由系統給其分配一個總線號,而i2c_add_numbered_adapter()則是本身指定總線號,若是這個總線號非法或者是被佔用,就會註冊失敗.
分別來看一下這兩個函數的代碼:
int i2c_add_adapter(struct i2c_adapter *adapter)
{
    int id, res = 0;

retry:
    if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        return -ENOMEM;

    mutex_lock(&core_lock);
    /* "above" here means "above or equal to", sigh */
    res = idr_get_new_above(&i2c_adapter_idr, adapter, __i2c_first_dynamic_bus_num, &id);
    mutex_unlock(&core_lock);

    if (res < 0) {
        if (res == -EAGAIN)
            goto retry;
        return res;
    }

    adapter->nr = id;
    return i2c_register_adapter(adapter);
}
       在這裏涉及到一個idr結構.idr結構原本是爲了配合page cache中的radix tree而設計的.在這裏咱們只須要知道,它是一種高效的搜索樹,且這個樹預先存放了一些內存.避免在內存不夠的時候出現問題.所在,在往idr中插入結構的時候,首先要調用idr_pre_get()爲它預留足夠的空閒內存,而後再調用idr_get_new_above()將結構插入idr中,該函數以參數的形式返回一個id.之後憑這個id就能夠在idr中找到相對應的結構了.對這個數據結構操做不太理解的能夠查閱本站<< linux文件系統之文件的讀寫>>中有關radix tree的分析.
      注意一下 idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)的參數的含義,它是將adapter結構插入到i2c_adapter_idr中,存放位置的id必需要大於或者等於 __i2c_first_dynamic_bus_num,而後將對應的id號存放在adapter->nr中.調用i2c_register_adapter(adapter)對這個adapter進行進一步註冊.

看一下另一人註冊函數: i2c_add_numbered_adapter( ),以下所示:
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    int id;
    int status;

    if (adap->nr & ~MAX_ID_MASK)
        return -EINVAL;

retry:
    if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        return -ENOMEM;

    mutex_lock(&core_lock);
    /* "above" here means "above or equal to", sigh;
    * we need the "equal to" result to force the result
    */
    status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
    if (status == 0 && id != adap->nr) {
        status = -EBUSY;
        idr_remove(&i2c_adapter_idr, id);
    }
    mutex_unlock(&core_lock);
    if (status == -EAGAIN)
        goto retry;

    if (status == 0)
        status = i2c_register_adapter(adap);
    return status;
}
對比一下就知道差異了,在這裏它已經指定好了adapter->nr了.若是分配的id不和指定的相等,便返回錯誤.

過一步跟蹤i2c_register_adapter().代碼以下:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = 0, dummy;

    mutex_init(&adap->bus_lock);
    mutex_init(&adap->clist_lock);
    INIT_LIST_HEAD(&adap->clients);

    mutex_lock(&core_lock);

    /* Add the adapter to the driver core.
    * If the parent pointer is not set up,
    * we add this adapter to the host bus.
    */
    if (adap->dev.parent == NULL) {
        adap->dev.parent = &platform_bus;
        pr_debug("I2C adapter driver [%s] forgot to specify "
            "physical device/n", adap->name);
    }
    sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);
    adap->dev.release = &i2c_adapter_dev_release;
    adap->dev.class = &i2c_adapter_class;
    res = device_register(&adap->dev);
    if (res)
        goto out_list;

    dev_dbg(&adap->dev, "adapter [%s] registered/n", adap->name);

    /* create pre-declared device nodes for new-style drivers */
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);

    /* let legacy drivers scan this bus for matching devices */
    dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
                i2c_do_add_adapter);

out_unlock:
    mutex_unlock(&core_lock);
    return res;

out_list:
    idr_remove(&i2c_adapter_idr, adap->nr);
    goto out_unlock;
}
        首先對adapter和adapter中內嵌的struct device結構進行必須的初始化.以後將adapter內嵌的struct device註冊.
在這裏注意一下adapter->dev的初始化.它的類別爲i2c_adapter_class,若是沒有父結點,則將其父結點設爲
platform_bus.

        adapter->dev的名字爲i2c + 總線號.
測試一下:
[eric@mochow i2c]$ cd /sys/class/i2c-adapter/
[eric@mochow i2c-adapter]$ ls
i2c-0
能夠看到,在個人PC上,有一個I2C adapter,看下詳細信息:
[eric@mochow i2c-adapter]$ tree
.
`-- i2c-0
    |-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0
    |-- name
    |-- subsystem -> ../../../class/i2c-adapter
    `-- uevent

3 directories, 2 files
能夠看到,該adapter是一個PCI設備.
繼續往下看:
以後,在註釋中看到,有兩種類型的driver,一種是new-style drivers,另一種是legacy drivers
New-style drivers是在2.6近版的kernel加入的
.它們最主要的區別是在adapter和i2c driver的匹配上.

3.一、new-style 形式的adapter註冊
對於第一種,也就是new-style drivers,將相關代碼再次列出以下:
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);
若是adap->nr 小於__i2c_first_dynamic_bus_num的話,就會進入到i2c_scan_static_board_info().
結合咱們以前分析的adapter的兩種註冊分式: i2c_add_adapter()所分得的總線號會不會小於__i2c_first_dynamic_bus_num.只有i2c_add_numbered_adapter()纔有可能知足:(adap->nr < __i2c_first_dynamic_bus_num)。
       並且必需要調用i2c_register_board_info()將板子上的I2C設備信息預先註冊時纔會更改 __i2c_first_dynamic_bus_num的值.在x86上沒有使用i2c_register_board_info()。所以,x86平臺上的分析能夠忽略掉new-style driver的方式。不過,仍是詳細分析這種狀況下.
首先看一下i2c_register_board_info(),以下:
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
{
    int status;

    mutex_lock(&__i2c_board_lock);

    /* dynamic bus numbers will be assigned after the last static one */
    if (busnum >= __i2c_first_dynamic_bus_num)
        __i2c_first_dynamic_bus_num = busnum + 1;

    for (status = 0; len; len--, info++) {
        struct i2c_devinfo  *devinfo;

        devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
        if (!devinfo) {
            pr_debug("i2c-core: can't register boardinfo!/n");
            status = -ENOMEM;
            break;
        }

        devinfo->busnum = busnum;
        devinfo->board_info = *info;
        list_add_tail(&devinfo->list, &__i2c_board_list);
    }

    mutex_unlock(&__i2c_board_lock);

    return status;
}
      這個函數比較簡單, struct i2c_board_info用來表示I2C設備的一些狀況,好比所在的總線.名稱,地址,中斷號等.最後,這些信息會被存放到__i2c_board_list鏈表.

跟蹤i2c_scan_static_board_info():代碼以下:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    struct i2c_devinfo  *devinfo;

    mutex_lock(&__i2c_board_lock);
    list_for_each_entry(devinfo, &__i2c_board_list, list) {
        if (devinfo->busnum == adapter->nr
                && !i2c_new_device(adapter,
                        &devinfo->board_info))
            printk(KERN_ERR "i2c-core: can't create i2c%d-%04x/n",
                i2c_adapter_id(adapter),
                devinfo->board_info.addr);
    }
    mutex_unlock(&__i2c_board_lock);
}
        該函數遍歷掛在__i2c_board_list鏈表上面的i2c設備的信息,也就是咱們在啓動的時候指出的i2c設備的信息.
若是指定設備是位於adapter所在的I2C總線上,那麼,就調用i2c_new_device().代碼以下:
struct i2c_client  *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client  *client;
    int        status;

    client = kzalloc(sizeof *client, GFP_KERNEL);
    if (!client)
        return NULL;

    client->adapter = adap;

    client->dev.platform_data = info->platform_data;
    device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);

    client->flags = info->flags & ~I2C_CLIENT_WAKE;
    client->addr = info->addr;
    client->irq = info->irq;

    strlcpy(client->name, info->type, sizeof(client->name));

    /* a new style driver may be bound to this device when we
    * return from this function, or any later moment (e.g. maybe
    * hotplugging will load the driver module).  and the device
    * refcount model is the standard driver model one.
    */
    status = i2c_attach_client(client);
    if (status < 0) {
        kfree(client);
        client = NULL;
    }
    return client;
}
        咱們又遇到了一個新的結構:struct i2c_client,不要被這個結構嚇倒了,其實它就是一個嵌入struct device的I2C設備的封裝.它和咱們以前遇到的struct usb_device結構的做用是同樣的.
        首先,在clinet裏保存該設備的相關消息.特別的,
client->adapter指向了它所在的adapter.特別的,clinet->name爲info->name.也是指定好了的.
        一切初始化完成以後,便會調用i2c_attach_client( ).看這個函數的字面意思,是將clinet關聯起來.到底怎麼樣關聯呢?繼續往下看:
int i2c_attach_client(struct i2c_client *client)
{
    struct i2c_adapter *adapter = client->adapter;
    int res = 0;

    //初始化client內嵌的dev結構
    //父結點爲所在的adapter,所在bus爲i2c_bus_type
    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;

    //若是client已經指定了driver,將driver和內嵌的dev關聯起來 
    if (client->driver)
        client->dev.driver = &client->driver->driver;
    //指定了driver, 但不是newstyle的
    if (client->driver && !is_newstyle_driver(client->driver)) {
        client->dev.release = i2c_client_release;
        client->dev.uevent_suppress = 1;
    } else
        client->dev.release = i2c_client_dev_release;

    //clinet->dev的名稱
    snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),
        "%d-%04x", i2c_adapter_id(adapter), client->addr);
    //將內嵌的dev註冊
    res = device_register(&client->dev);
    if (res)
        goto out_err;

    //將clinet鏈到adapter->clients中
    mutex_lock(&adapter->clist_lock);
    list_add_tail(&client->list, &adapter->clients);
    mutex_unlock(&adapter->clist_lock);

    dev_dbg(&adapter->dev, "client [%s] registered with bus id %s/n",
        client->name, client->dev.bus_id);
    //若是adapter->cleinet_reqister存在,就調用它
    if (adapter->client_register)  {
        if (adapter->client_register(client)) {
            dev_dbg(&adapter->dev, "client_register "
                "failed for client [%s] at 0x%02x/n",
                client->name, client->addr);
        }
    }

    return 0;

out_err:
    dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x "
        "(%d)/n", client->name, client->addr, res);
    return res;
}
        參考上面添加的註釋,應該很容易理解這段代碼了,就不加詳細分析了.這個函數的名字不是i2c_attach_client()麼?怎麼沒看到它的關係過程呢?
        這是由於:在代碼中設置了client->dev所在的bus爲i2c_bus_type .覺得只須要有bus爲i2c_bus_type的driver註冊,就會產生probe了.這個過程呆後面分析i2c driver的時候再來詳細分析.

3.二、
legacy形式的adapter註冊
Legacy形式的adapter註冊代碼片斷以下:
    dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
                i2c_do_add_adapter);
這段代碼遍歷掛在i2c_bus_type上的驅動,而後對每個驅動和adapter調用i2c_do_add_adapter().
代碼以下:
static int i2c_do_add_adapter(struct device_driver *d, void *data)
{
    struct i2c_driver *driver = to_i2c_driver(d);
    struct i2c_adapter *adap = data;

    if (driver->attach_adapter) {
        /* We ignore the return code; if it fails, too bad */
        driver->attach_adapter(adap);
    }
    return 0;
}
該函數很簡單,就是調用driver的attach_adapter()接口.
到此爲止,adapter的註冊已經分析完了.

4、i2c driver註冊
在分析i2c driver的時候,有必要先分析一下i2c架構的初始化
代碼以下:
static int __init i2c_init(void)
{
    int retval;

   retval = bus_register(&i2c_bus_type);//註冊總線
    if (retval)
        return retval;
    retval = class_register(&i2c_adapter_class);//註冊類
    if (retval)
        goto bus_err;
    retval = i2c_add_driver(&dummy_driver);//添加驅動
    if (retval)
        goto class_err;
    return 0;

class_err:
    class_unregister(&i2c_adapter_class);
bus_err:
    bus_unregister(&i2c_bus_type);
    return retval;
}
subsys_initcall(i2c_init);
很明顯,i2c_init()會在系統初始化的時候被調用.
     在i2c_init中,先註冊了i2c_bus_type的bus,i2c_adapter_class的class.而後再調用i2c_add_driver()註冊了一個i2c driver.
I2c_bus_type結構以下:
static struct bus_type i2c_bus_type = {
    .name      = "i2c",
    .dev_attrs  = i2c_dev_attrs,
    .match      = i2c_device_match,
    .uevent    = i2c_device_uevent,
    .probe      = i2c_device_probe,
    .remove    = i2c_device_remove,
    .shutdown  = i2c_device_shutdown,
    .suspend    = i2c_device_suspend,
    .resume    = i2c_device_resume,
};

這個結構先放在這裏吧,之後還會用到裏面的信息的.
從上面的初始化函數裏也看到了,註冊i2c driver的接口爲i2c_add_driver().代碼以下:
static inline int i2c_add_driver(struct i2c_driver *driver)
{
    return i2c_register_driver(THIS_MODULE, driver);
}
繼續跟蹤:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    int res;

    /* new style driver methods can't mix with legacy ones */
    //若是是一個newstyle的driver.但又定義了attach_adapter/detach_adapter.非法
    if (is_newstyle_driver(driver)) {
        if (driver->attach_adapter || driver->detach_adapter
                || driver->detach_client) {
            printk(KERN_WARNING
                    "i2c-core: driver [%s] is confused/n",
                    driver->driver.name);
            return -EINVAL;
        }
    }

    /* add the driver to the list of i2c drivers in the driver core */
    //關聯到i2c_bus_types
    driver->driver.owner = owner;
    driver->driver.bus = &i2c_bus_type;

    /* for new style drivers, when registration returns the driver core
    * will have called probe() for all matching-but-unbound devices.
    */
    //註冊內嵌的driver
    res = driver_register(&driver->driver);
    if (res)
        return res;

    mutex_lock(&core_lock);

    pr_debug("i2c-core: driver [%s] registered/n", driver->driver.name);

    /* legacy drivers scan i2c busses directly */
    //遍歷全部的adapter,對其都調用driver->attach_adapter
    if (driver->attach_adapter) {
        struct i2c_adapter *adapter;

        down(&i2c_adapter_class.sem);
        list_for_each_entry(adapter, &i2c_adapter_class.devices,
                    dev.node) {
            driver->attach_adapter(adapter);
        }
        up(&i2c_adapter_class.sem);
    }

    mutex_unlock(&core_lock);
    return 0;
}
        這裏也有兩種形式的區分,對於第一種,只須要將內嵌的driver註冊就能夠了。對於legacy的狀況,對每個adapter都調用driver->attach_adapter().
如今,咱們能夠將adapter和i2c driver關聯起來考慮一下了:
1:若是是news style形式的,在註冊adapter的時候,將它上面的i2c 設備轉換成了struct client。struct client->dev->bus又指定了和i2c driver同一個bus.由於,它們能夠發生probe.
2:若是是legacy形式,就直接找到對應的對象,調用driver->attach_adapter().

5、i2c_bus_type的相關操做
        I2c_bus_type的操做主要存在於new-style形式的驅動中.接下來分析一下對應的probe過程:
5.1:match過程分析
        Match對應的操做函數爲i2c_device_match().代碼以下
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    struct i2c_client  *client = to_i2c_client(dev);
    struct i2c_driver  *driver = to_i2c_driver(drv);

    /* make legacy i2c drivers bypass driver model probing entirely;
    * such drivers scan each i2c adapter/bus themselves.
    */
    if (!is_newstyle_driver(driver))
        return 0;

    /* match on an id table if there is one */
    if (driver->id_table)
        return i2c_match_id(driver->id_table, client) != NULL;

    return 0;
}
若是該驅動不是一個new-style形式的.或者driver沒有定義匹配的id_table.都會匹配失敗.
繼續跟蹤進i2c_match_id():
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                        const struct i2c_client *client)
{
    while (id->name[0]) {
        if (strcmp(client->name, id->name) == 0)
            return id;
        id++;
    }
    return NULL;
}
因而可知.若是client的名字和driver->id_table[]中的名稱匹配即爲成功.

5.2:probe過程分析
Probe對應的函數爲: i2c_device_probe()
static int i2c_device_probe(struct device *dev)
{
    struct i2c_client  *client = to_i2c_client(dev);
    struct i2c_driver  *driver = to_i2c_driver(dev->driver);
    const struct i2c_device_id *id;
    int status;

    if (!driver->probe)
        return -ENODEV;
    client->driver = driver;
    dev_dbg(dev, "probe/n");

    if (driver->id_table)
        id = i2c_match_id(driver->id_table, client);
    else
        id = NULL;
    status = driver->probe(client, id);
    if (status)
        client->driver = NULL;
    return status;
}
這個函數也很簡單,就是將probe流程回溯到i2c driver的probe()

6、其它的擴展
        分析完adapter和i2c driver的註冊以後,好像整個架構也差很少了,其它,擴展的東西還有不少.
咱們舉一個legacy形式的例子,這個例子是在kernel中隨便搜索出來的:
在linux-2.6.26.3/drivers/hwmon/ad7418.c中,初始化函數爲:
static int __init ad7418_init(void)
{
    return i2c_add_driver(&ad7418_driver);
}
i2c_driver ad7418_driver結構以下:
static struct i2c_driver ad7418_driver = {
    .driver = {
        .name  = "ad7418",
    },
    .attach_adapter = ad7418_attach_adapter,
    .detach_client  = ad7418_detach_client,
};
該結構中沒有probe()函數,能夠判定是一個legacy形式的驅動
.這類驅動註冊的時候,會調用driver的attach_adapter函數.在這裏也就是ad7418_attach_adapter.
這個函數代碼以下:
static int ad7418_attach_adapter(struct i2c_adapter *adapter)
{
    if (!(adapter->class & I2C_CLASS_HWMON))
        return 0;
    return i2c_probe(adapter, &addr_data, ad7418_detect);
}
在這裏咱們又遇到了一個i2c-core中的函數,i2c_probe().在分析這個函數以前,先來看下addr_data是什麼?
#define I2C_CLIENT_MODULE_PARM(var,desc) /
  static unsigned short var[I2C_CLIENT_MAX_OPTS] = I2C_CLIENT_DEFAULTS; /
  static unsigned int var##_num; /
  module_param_array(var, short, &var##_num, 0); /
  MODULE_PARM_DESC(var,desc)

#define I2C_CLIENT_MODULE_PARM_FORCE(name)              /
I2C_CLIENT_MODULE_PARM(force_##name,                    /
              "List of adapter,address pairs which are "    /
              "unquestionably assumed to contain a `"      /
              # name "' chip")


#define I2C_CLIENT_INSMOD_COMMON                    /
I2C_CLIENT_MODULE_PARM(probe, "List of adapter,address pairs to scan "  /
              "additionally");                  /
I2C_CLIENT_MODULE_PARM(ignore, "List of adapter,address pairs not to "  /
              "scan");                      /
static const struct i2c_client_address_data addr_data = {      /
    .normal_i2c = normal_i2c,                  /
    .probe      = probe,                    /
    .ignore    = ignore,                  /
    .forces    = forces,                  /
}

#define I2C_CLIENT_FORCE_TEXT /
    "List of adapter,address pairs to boldly assume to be present"
由此可知道,addr_data中的三個成員都是模塊參數.在加載模塊的時候能夠用參數的方式對其賦值.三個模塊參數爲別爲probe,ignore,force.另外須要指出的是normal_i2c不能以模塊參數的方式對其賦值,只能在驅動內部靜態指定.
從模塊參數的模述看來, probe是指"List of adapter,address pairs to scan additionally"
Ignore是指"List of adapter,address pairs not to scan "
Force是指"List of adapter,address pairs to boldly assume to be present"
事實上,它們裏面的數據都是成對出現的.前面一部份表示所在的總線號,ANY_I2C_BUS表示任一總線.後一部份表示設備的地址.

addr_data是在 include/linux/i2c.h 中定義的或本身在本身驅動程序中定義的(注意,此處addr_data有兩種定義方式)一個i2c_client_address_data結構:
若本身不定義,則用i2c.h中的默認定義。
/* i2c_client_address_data is the struct for holding default client addresses for a driver and for the parameters supplied on the command line */
struct i2c_client_address_data {
    unsigned short *normal_i2c;
    unsigned short *probe;
    unsigned short *ignore;
    unsigned short **forces;
};
根據做者自行定義設備地址與否,有兩種情形:
a. 採用默認定義,通常是不會work,畢竟大多數i2c-core中是不可能提早知道所接設備地址的,這樣經過i2c_probe()探測確定不可能找到,也不可能創建二者之間的聯繫;何況,i2c_probe()屬於i2c-core中的函數,i2c-core中管理着全部註冊過的設備和驅動列表,i2c_probe()中也不能隨意傳入地址,不然容易致使系統混亂或有潛在的風險,因此i2c-core也不容許這麼作!
b. 做者自行定義地址結構
典型例子以下:
若自行定義,則參考以下:
/* Addresses to scan */
static unsigned short normal_i2c[] = {I2C_KS0127_ADDON>>1,
                       I2C_KS0127_ONBOARD>>1, I2C_CLIENT_END};/// 實際設備的地址List
static unsigned short probe[2] =    {I2C_CLIENT_END, I2C_CLIENT_END};
static unsigned short ignore[2] =     {I2C_CLIENT_END, I2C_CLIENT_END};
static struct i2c_client_address_data addr_data = {
    normal_i2c,
    probe,
    ignore,
};
        或者根本就不定義完整的i2c_client_address_data結構只根據須要定義normal_i2c[],probe[],ignore[],forces[][],而後調用i2c_probe(adapter,&addr_data, &my_probe) 便可
        在my_probe()中把實際的地址賦於i2c_client,調用i2c_set_clientdata()設置i2c_client->dev->drv_data,並調用i2c_attach_client(client)向系統註冊設備。
      最後,i2c_probe()中探測時的地址優先級:forces[X][ X], probe[X ], normal_i2c[ X](其中忽略ignore[]中的項)。
I2c設備在實際使用中比較普遍,sensor,rtc,audio, codec,etc. 因設備複雜性不一樣,Linux中有些驅動中對地址的定義不在同一文件,這時多數狀況都在arch中對設備做爲platform_device進行初始化並註冊的代碼中。


如今能夠來跟蹤i2c_probe()的代碼了.以下:
int i2c_probe(struct i2c_adapter *adapter, const struct i2c_client_address_data *address_data, int (*found_proc) (struct  i2c_adapter *, int, int))
{
    int i, err;
    int adap_id = i2c_adapter_id(adapter);

    /* Force entries are done first, and are not affected by ignore
      entries */
      //先掃描force裏面的信息,注意它是一個二級指針.ignore裏的信息對它是無效的
    if (address_data->forces) {
        const unsigned short * const *forces = address_data->forces;//forces爲常量指針,所指向的內容不能改變。
        int kind;

        for (kind = 0; forces[kind]; kind++) {
            for (i = 0; forces[kind] != I2C_CLIENT_END;
                i += 2) {
                if (forces[kind] == adap_id
                || forces[kind] == ANY_I2C_BUS) {
                    dev_dbg(&adapter->dev, "found force "
                        "parameter for adapter %d, "
                        "addr 0x%02x, kind %d/n",
                        adap_id, forces[kind][i + 1],
                        kind);
                    err = i2c_probe_address(adapter,
                        forces[kind][i + 1],
                        kind, found_proc);
                    if (err)
                        return err;
                }
            }
        }
    }

    /* Stop here if we can't use SMBUS_QUICK */
    //若是adapter不支持quick.不可以遍歷這個adapter上面的設備
    if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) {
        if (address_data->probe[0] == I2C_CLIENT_END
        && address_data->normal_i2c[0] == I2C_CLIENT_END)
            return 0;

        dev_warn(&adapter->dev, "SMBus Quick command not supported, "
            "can't probe for chips/n");
        return -1;
    }

    /* Probe entries are done second, and are not affected by ignore
      entries either */
      //遍歷probe上面的信息.ignore上的信息也對它是沒有影響的
    for (i = 0; address_data->probe != I2C_CLIENT_END; i += 2) {
        if (address_data->probe == adap_id
        || address_data->probe == ANY_I2C_BUS) {
            dev_dbg(&adapter->dev, "found probe parameter for "
                "adapter %d, addr 0x%02x/n", adap_id,
                address_data->probe[i + 1]);
            err = i2c_probe_address(adapter,
                        address_data->probe[i + 1],
                        -1, found_proc);
            if (err)
                return err;
        }
    }

    /* Normal entries are done last, unless shadowed by an ignore entry */
    //最後遍歷normal_i2c上面的信息.它上面的信息不能在ignore中.
    for (i = 0; address_data->normal_i2c != I2C_CLIENT_END; i += 1) {
        int j, ignore;

        ignore = 0;
        for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;
            j += 2) {
            if ((address_data->ignore[j] == adap_id ||
                address_data->ignore[j] == ANY_I2C_BUS)
            && address_data->ignore[j + 1]
                == address_data->normal_i2c) {
                dev_dbg(&adapter->dev, "found ignore "
                    "parameter for adapter %d, "
                    "addr 0x%02x/n", adap_id,
                    address_data->ignore[j + 1]);
                ignore = 1;
                break;
            }
        }
        if (ignore)
            continue;

        dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
            "addr 0x%02x/n", adap_id,
            address_data->normal_i2c);
        err = i2c_probe_address(adapter, address_data->normal_i2c,
                    -1, found_proc);
        if (err)
            return err;
    }

    return 0;
}
        這段代碼很簡單,結合代碼上面添加的註釋應該很好理解.若是匹配成功,則會調用i2c_probe_address ().這個函數代碼以下:
static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,
                int (*found_proc) (struct i2c_adapter *, int, int))
{
    int err;

    /* Make sure the address is valid */
    //地址小於0x03或者大於0x77都是不合法的
    if (addr < 0x03 || addr > 0x77) {
        dev_warn(&adapter->dev, "Invalid probe address 0x%02x/n",
            addr);
        return -EINVAL;
    }

    /* Skip if already in use */
    //adapter上已經有這個設備了
    if (i2c_check_addr(adapter, addr))
        return 0;

    /* Make sure there is something at this address, unless forced */
    //若是kind小於0.檢查adapter上是否有這個設備
    if (kind < 0) {
        if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,
                  I2C_SMBUS_QUICK, NULL) < 0)
            return 0;

        /* prevent 24RF08 corruption */
        if ((addr & ~0x0f) == 0x50)
            i2c_smbus_xfer(adapter, addr, 0, 0, 0,
                      I2C_SMBUS_QUICK, NULL);
    }

    /* Finally call the custom detection function */
    //調用回調函數
    err = found_proc(adapter, addr, kind);
    /* -ENODEV can be returned if there is a chip at the given address
      but it isn't supported by this chip driver. We catch it here as
      this isn't an error. */
    if (err == -ENODEV)
        err = 0;

    if (err)
        dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)/n",
            addr, err);
    return err;
}
        首先,對傳入的參數進行一系列的合法性檢查.另外,若是該adapter上已經有了這個地址的設備了.也會返回失敗.全部adapter下面的設備都是以 adapter->dev爲父結點的.所以只須要遍歷adapter->dev下面的子設備就能夠獲得當前地址是否是被佔用了.
若是kind < 0.還得要adapter檢查該總線是否有這個地址的設備.方法是向這個地址發送一個Read的Quick請求.若是該地址有應答,則說明這個地址上有這個設備.另外還有一種狀況是在24RF08設備的特例.
若是adapter上確實有這個設備,就會調用驅動調用時的回調函數.

在上面涉及到了IIC的傳輸方式,有疑問的能夠參考intel ICH5手冊的有關smbus部份.
跟蹤i2c_smbus_xfer().代碼以下:
s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,
                  char read_write, u8 command, int size,
                  union i2c_smbus_data * data)
{
    s32 res;

    flags &= I2C_M_TEN | I2C_CLIENT_PEC;

    if (adapter->algo->smbus_xfer) {
        mutex_lock(&adapter->bus_lock);
        res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,
                                        command,size,data);
        mutex_unlock(&adapter->bus_lock);
    } else
        res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,
                                          command,size,data);

    return res;
}
若是adapter有smbus_xfer()函數,則直接調用它發送,不然,也就是在adapter不支持smbus協議的狀況下,調用i2c_smbus_xfer_emulated()繼續處理.
跟進i2c_smbus_xfer_emulated().代碼以下:
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,
                                  unsigned short flags,
                                  char read_write, u8 command, int size,
                                  union i2c_smbus_data * data)
{
    /* So we need to generate a series of msgs. In the case of writing, we
      need to use only one message; when reading, we need two. We initialize
      most things with sane defaults, to keep the code below somewhat
      simpler. */
    //寫操做只會進行一次交互,而讀操做,有時會有兩次操做.
    //由於有時候讀操做要先寫command,再從總線上讀數據
    //在這裏爲了代碼的簡潔.使用了兩個緩存區,將兩種狀況統一塊兒來.
    unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
    unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
    //通常來講,讀操做要交互兩次.例外的狀況咱們在下面會接着分析
    int num = read_write == I2C_SMBUS_READ?2:1;
    //與設備交互的數據,通常在msg[0]存放寫入設備的信息,在msb[1]裏存放接收到的
    //信息.不過也有例外的
    //msg[2]的初始化,默認發送緩存區佔一個字節,無接收緩存
    struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
                              { addr, flags | I2C_M_RD, 0, msgbuf1 }
                            };
    int i;
    u8 partial_pec = 0;

    //將要發送的信息copy到發送緩存區的第一字節
    msgbuf0[0] = command;
    switch(size) {
        //quick類型的,其它並不傳輸有效數據,只是將地址寫到總線上,等待應答便可
        //因此將發送緩存區長度置爲0 .再根據讀/寫操做,調整msg[0]的標誌位
        //這類傳輸只須要一次總線交互
    case I2C_SMBUS_QUICK:
        msg[0].len = 0;
        /* Special case: The read/write field is used as data */
        msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;
        num = 1;
        break;
    case I2C_SMBUS_BYTE:
        //BYTE類型指一次寫和讀只有一個字節.這種狀況下,讀和寫都只會交互一次
        //這種類型的讀有例外,它讀取出來的數據不是放在msg[1]中的,而是存放在msg[0]
        if (read_write == I2C_SMBUS_READ) {
            /* Special case: only a read! */
            msg[0].flags = I2C_M_RD | flags;
            num = 1;
        }
        break;
    case I2C_SMBUS_BYTE_DATA:
        //Byte_Data是指命令+數據的傳輸形式.在這種狀況下,寫只須要一次交互,讀卻要兩次
        //第一次將command寫到總線上,第二次要轉換方向.要將設備地址和read標誌寫入總線.
        //應回答以後再進行read操做
        //寫操做佔兩字節,分別是command+data.讀操做的有效數據只有一個字節
        //交互次數用初始化值就能夠了
        if (read_write == I2C_SMBUS_READ)
            msg[1].len = 1;
        else {
            msg[0].len = 2;
            msgbuf0[1] = data->byte;
        }
        break;
    case I2C_SMBUS_WORD_DATA:
        //Word_Data是指命令+雙字節的形式.這種狀況跟Byte_Data的狀況相似
        //二者相比只是交互的數據大小不一樣
        if (read_write == I2C_SMBUS_READ)
            msg[1].len = 2;
        else {
            msg[0].len=3;
            msgbuf0[1] = data->word & 0xff;
            msgbuf0[2] = data->word >> 8;
        }
        break;
    case I2C_SMBUS_PROC_CALL:
        //Proc_Call的方式與write 的Word_Data類似,只不過寫完Word_Data以後,要等待它的應答
        //應該它須要交互兩次,一次寫一次讀
        num = 2; /* Special case */
        read_write = I2C_SMBUS_READ;
        msg[0].len = 3;
        msg[1].len = 2;
        msgbuf0[1] = data->word & 0xff;
        msgbuf0[2] = data->word >> 8;
        break;
    case I2C_SMBUS_BLOCK_DATA:
        //Block_Data:指command+N段數據的狀況.
        //若是是讀操做,它首先要寫command到總線,而後再讀N段數據.要寫的command已經
        //放在msg[0]了.如今只須要將msg[1]的標誌置I2C_M_RECV_LEN位,msg[1]有效長度爲1字節.由於
        //adapter驅動會處理好的.如今如今還不知道要傳多少段數據.

        //對於寫的狀況:msg[1]照例不須要.將要寫的數據所有都放到msb[0]中.相應的也要更新
        //msg[0]中的緩存區長度
        if (read_write == I2C_SMBUS_READ) {
            msg[1].flags |= I2C_M_RECV_LEN;
            msg[1].len = 1; /* block length will be added by
                      the underlying bus driver */
        } else {
            //data->block[0]表示後面有多少段數據.總長度要加2是由於command+count+N段數據
            msg[0].len = data->block[0] + 2;
            if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {
                dev_err(&adapter->dev, "smbus_access called with "
                      "invalid block write size (%d)/n",
                      data->block[0]);
                return -1;
            }
            for (i = 1; i < msg[0].len; i++)
                msgbuf0 = data->block[i-1];
        }
        break;
    case I2C_SMBUS_BLOCK_PROC_CALL:
        //Proc_Call:表示寫完Block_Data以後,要等它的應答消息它和Block_Data相比,只是多了一部份應答而已
        num = 2; /* Another special case */
        read_write = I2C_SMBUS_READ;
        if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
            dev_err(&adapter->dev, "%s called with invalid "
                "block proc call size (%d)/n", __func__,
                data->block[0]);
            return -1;
        }
        msg[0].len = data->block[0] + 2;
        for (i = 1; i < msg[0].len; i++)
            msgbuf0 = data->block[i-1];
        msg[1].flags |= I2C_M_RECV_LEN;
        msg[1].len = 1; /* block length will be added by
                  the underlying bus driver */
        break;
    case I2C_SMBUS_I2C_BLOCK_DATA:
        //I2c Block_Data與Block_Data類似,只不過read的時候,數據長度是預先定義好了的.另外
        //與Block_Data相比,中間不須要傳輸Count字段.(Count表示數據段數目)
        if (read_write == I2C_SMBUS_READ) {
            msg[1].len = data->block[0];
        } else {
            msg[0].len = data->block[0] + 1;
            if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) {
                dev_err(&adapter->dev, "i2c_smbus_xfer_emulated called with "
                      "invalid block write size (%d)/n",
                      data->block[0]);
                return -1;
            }
            for (i = 1; i <= data->block[0]; i++)
                msgbuf0 = data->block;
        }
        break;
    default:
        dev_err(&adapter->dev, "smbus_access called with invalid size (%d)/n",
              size);
        return -1;
    }

    //若是啓用了PEC.Quick和I2c Block_Data是不支持PEC的
    i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK
                      && size != I2C_SMBUS_I2C_BLOCK_DATA);
    if (i) {
        /* Compute PEC if first message is a write */
        //若是第一個操做是寫操做
        if (!(msg[0].flags & I2C_M_RD)) {
            //若是隻是寫操做
            if (num == 1) /* Write only */
                //若是隻有寫操做,寫緩存區要擴充一個字節,用來存放計算出來的PEC
                i2c_smbus_add_pec(&msg[0]);
            else /* Write followed by read */
                //若是後面還有讀操做,先計算前面寫部份的PEC(注意這種狀況下不須要
                //擴充寫緩存區,由於不須要發送PEC.只會接收到PEC)
                partial_pec = i2c_smbus_msg_pec(0, &msg[0]);
        }
        /* Ask for PEC if last message is a read */
        //若是最後一次是讀消息.還要接收到來自slave的PEC.因此接收緩存區要擴充一個字節
        if (msg[num-1].flags & I2C_M_RD)
            msg[num-1].len++;
    }

    if (i2c_transfer(adapter, msg, num) < 0)
        return -1;

    /* Check PEC if last message is a read */
    //操做完了以後,若是最後一個操做是PEC的讀操做.檢驗後面的PEC是否正確
    if (i && (msg[num-1].flags & I2C_M_RD)) {
        if (i2c_smbus_check_pec(partial_pec, &msg[num-1]) < 0)
            return -1;
    }

    //操做完了,如今能夠將數據放到data部份返回了.
    if (read_write == I2C_SMBUS_READ)
        switch(size) {
            case I2C_SMBUS_BYTE:
                data->byte = msgbuf0[0];
                break;
            case I2C_SMBUS_BYTE_DATA:
                data->byte = msgbuf1[0];
                break;
            case I2C_SMBUS_WORD_DATA:
            case I2C_SMBUS_PROC_CALL:
                data->word = msgbuf1[0] | (msgbuf1[1] << 8);
                break;
            case I2C_SMBUS_I2C_BLOCK_DATA:
                for (i = 0; i < data->block[0]; i++)
                    data->block[i+1] = msgbuf1;
                break;
            case I2C_SMBUS_BLOCK_DATA:
            case I2C_SMBUS_BLOCK_PROC_CALL:
                for (i = 0; i < msgbuf1[0] + 1; i++)
                    data->block = msgbuf1;
                break;
        }
    return 0;
}
在這個函數添上了很詳細的註釋,配和intel的datasheet,應該很容易看懂.在上面的交互過程當中,調用了子函數i2c_transfer().它的代碼以下所示:
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
{
    int ret;

    if (adap->algo->master_xfer) {
#ifdef DEBUG
        for (ret = 0; ret < num; ret++) {
            dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
                "len=%d%s/n", ret, (msgs[ret].flags & I2C_M_RD)
                ? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
                (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
        }
#endif

        if (in_atomic() || irqs_disabled()) {
            ret = mutex_trylock(&adap->bus_lock);
            if (!ret)
                /* I2C activity is ongoing. */
                return -EAGAIN;
        } else {
            mutex_lock_nested(&adap->bus_lock, adap->level);
        }

        ret = adap->algo->master_xfer(adap,msgs,num);
        mutex_unlock(&adap->bus_lock);

        return ret;
    } else {
        dev_dbg(&adap->dev, "I2C level transfers not supported/n");
        return -ENOSYS;
    }
}
由於在這裏的同步用的是mutex.首先判斷判斷是否充許睡眠,若是不容許,嘗試獲鎖.若是獲鎖失敗,則返回,這樣的操做是避免進入睡眠,咱們在後面也能夠看到,實際的傳輸工做交給了adap->algo->master_xfer()完成.

在這裏,咱們終於把i2c_probe_address()的執行分析完了,通過這個分析,咱們也知道了數據是怎麼樣傳輸的.咱們接着 i2c_probe()往下看.若是i2c_probe_address()成功.說明總線上確實有這樣的設備.那麼就會調用驅動中的回調函數.在 ad7148的驅動中,以下所示:
return i2c_probe(adapter, &addr_data, ad7418_detect);
也就是說,要調用的回調函數是ad7418_detect().這個函數中咱們只分析和i2c框架相關的部份.代碼片斷以下所示:
static int ad7418_detect(struct i2c_adapter *adapter, int address, int kind)
{
    struct i2c_client *client;
    ……
    ……
client->addr = address;
    client->adapter = adapter;
    client->driver = &ad7418_driver;

    i2c_set_clientdata(client, data);
    ……
    ……
if ((err = i2c_attach_client(client)))
        goto exit_free;
    ……
    ……
}
結合上面關於new-style形式的驅動分析.發現這裏走的是同一個套路,即初始化了client.而後調用 i2c_attach_client().後面的流程就跟上面分析的同樣了.只不過,不相同的是,這裏clinet已經指定了驅動爲 ad7418_driver.應該在註冊clinet->dev以後,就不會走bus->match和bus->probe的流程了.

七:i2c dev節點操做
如今來分析上面架構圖中的i2c-dev.c中的部份.這個部份爲用戶空間提供了操做adapter的接口.這部份代碼其實對應就晃一個模塊.它的初始化函數爲:
module_init(i2c_dev_init);
i2c_dev_init()代碼以下:
static int __init i2c_dev_init(void)
{
    int res;

    printk(KERN_INFO "i2c /dev entries driver/n");

    res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
    if (res)
        goto out;

    i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
    if (IS_ERR(i2c_dev_class))
        goto out_unreg_chrdev;

    res = i2c_add_driver(&i2cdev_driver);
    if (res)
        goto out_unreg_class;

    return 0;

out_unreg_class:
    class_destroy(i2c_dev_class);
out_unreg_chrdev:
    unregister_chrdev(I2C_MAJOR, "i2c");
out:
    printk(KERN_ERR "%s: Driver Initialisation failed/n", __FILE__);
    return res;
}
       首先爲主冊了一個主設備號爲I2C_MAJOR(89),操做集爲i2cdev_fops的字符設備.

       而後註冊了一個名爲」i2c-dev」的class.以後再註冊了一個i2c的driver.以下所示:

res = i2c_add_driver(&i2cdev_driver);
    if (res)
        goto out_unreg_class;
i2cdev_driver定義以下:
static struct i2c_driver i2cdev_driver = {
    .driver = {
        .name  = "dev_driver",
    },
    .id    = I2C_DRIVERID_I2CDEV,
    .attach_adapter = i2cdev_attach_adapter,
    .detach_adapter = i2cdev_detach_adapter,
    .detach_client  = i2cdev_detach_client,
};
也就是說,當它註冊或者有新的adapter註冊後,就會它的attach_adapter()函數.該函數代碼以下:
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
    struct i2c_dev *i2c_dev;
    int res;

    i2c_dev = get_free_i2c_dev(adap);
    if (IS_ERR(i2c_dev))
        return PTR_ERR(i2c_dev);

    /* register this i2c device with the driver core */
    i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
                    MKDEV(I2C_MAJOR, adap->nr),
                    "i2c-%d", adap->nr);
    if (IS_ERR(i2c_dev->dev)) {
        res = PTR_ERR(i2c_dev->dev);
        goto error;
    }
    res = device_create_file(i2c_dev->dev, &dev_attr_name);
    if (res)
        goto error_destroy;

    pr_debug("i2c-dev: adapter [%s] registered as minor %d/n",
        adap->name, adap->nr);
    return 0;
error_destroy:
    device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
    return_i2c_dev(i2c_dev);
    return res;
}
這個函數也很簡單,首先調用get_free_i2c_dev()分配並初始化了一個struct i2c_dev結構,使i2c_dev->adap指向操做的adapter.以後,該i2c_dev會被鏈入鏈表i2c_dev_list中。再分別以I2C_MAJOR, adap->nr爲主次設備號建立了一個device.若是此時系統配置了udev或者是hotplug,那麼就麼在/dev下自動建立相關的設備節點了.
剛纔咱們說過,全部主設備號爲I2C_MAJOR的設備節點的操做函數是i2cdev_fops.它的定義以下所示:
static const struct file_operations i2cdev_fops = {
    .owner      = THIS_MODULE,
    .llseek    = no_llseek,
    .read      = i2cdev_read,
    .write      = i2cdev_write,
    .ioctl      = i2cdev_ioctl,
    .open      = i2cdev_open,
    .release    = i2cdev_release,
};

7.1:i2c dev的open操做
Open操做對應的函數爲i2cdev_open().代碼以下:

static int i2cdev_open(struct inode *inode, struct file *file)
{
    unsigned int minor = iminor(inode);
    struct i2c_client *client;
    struct i2c_adapter *adap;
    struct i2c_dev *i2c_dev;

    //以次設備號從i2c_dev_list鏈表中取得i2c_dev
    i2c_dev = i2c_dev_get_by_minor(minor);
    if (!i2c_dev)
        return -ENODEV;

    //以apapter的總線號從i2c_adapter_idr中找到adapter
    adap = i2c_get_adapter(i2c_dev->adap->nr);
    if (!adap)
        return -ENODEV;

    /* This creates an anonymous i2c_client, which may later be
    * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
    *
    * This client is ** NEVER REGISTERED ** with the driver model
    * or I2C core code!!  It just holds private copies of addressing
    * information and maybe a PEC flag.
    */
    //分配並初始化一個i2c_client結構
    client = kzalloc(sizeof(*client), GFP_KERNEL);
    if (!client) {
        i2c_put_adapter(adap);
        return -ENOMEM;
    }
    snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
    client->driver = &i2cdev_driver;

    //clinet->adapter指向操做的adapter
    client->adapter = adap;
    //關聯到file
    file->private_data = client;

    return 0;
}
注意這裏分配並初始化了一個struct i2c_client結構.可是沒有註冊這個clinet.此外,這個函數中還有一個比較奇怪的操做.不是在前面已經將i2c_dev->adap 指向要操做的adapter麼?爲何還要以adapter->nr爲關鍵字從i2c_adapter_idr去找這個操做的adapter呢?注意了,調用i2c_get_adapter()從總線號nr找到操做的adapter的時候,還會增長module的引用計數.這樣能夠防止模塊意外被釋放掉.也許有人會有這樣的疑問,那 i2c_dev->adap->nr操做,若是i2c_dev->adap被釋放掉的話,不是同樣會引發系統崩潰麼?這裏由於,在 i2cdev_attach_adapter()間接的增長了一次adapter的一次引用計數.以下:
tatic int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
......
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
                    MKDEV(I2C_MAJOR, adap->nr),
                    "i2c-%d", adap->nr);
......
}
看到了麼,i2c_dev內嵌的device是以adap->dev爲父結點,在device_create()中會增次adap->dev的一次引用計數.
好了,open()操做到此就完成了.

7.2:read操做
Read操做對應的操做函數以下示:
static ssize_t i2cdev_read (struct file *file, char __user *buf, size_t count,
                            loff_t *offset)
{
    char *tmp;
    int ret;

    struct i2c_client *client = (struct i2c_client *)file->private_data;

    if (count > 8192)
        count = 8192;

    tmp = kmalloc(count,GFP_KERNEL);
    if (tmp==NULL)
        return -ENOMEM;

    pr_debug("i2c-dev: i2c-%d reading %zd bytes./n",
        iminor(file->f_path.dentry->d_inode), count);

    ret = i2c_master_recv(client,tmp,count);
    if (ret >= 0)
        ret = copy_to_user(buf,tmp,count)?-EFAULT:ret;
    kfree(tmp);
    return ret;
}
首先從file結構中取得struct i2c_clinet.而後在kernel同分配相同長度的緩存區,隨之調用i2c_master_recv()從設備中讀取數據.再將讀取出來的數據copy到用戶空間中.
I2c_master_recv()代碼以下:
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
{
    struct i2c_adapter *adap=client->adapter;
    struct i2c_msg msg;
    int ret;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.flags |= I2C_M_RD;
    msg.len = count;
    msg.buf = buf;

    ret = i2c_transfer(adap, &msg, 1);

    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
      transmitted, else error code. */
    return (ret == 1) ? count : ret;
}
看完前面的代碼以後,這個函數應該很簡單了,就是爲讀操做初始化了一個i2c_msg.而後調用i2c_tanster().代碼中的 client->flags & I2C_M_TEN表示adapter是否採用10位尋址的方式.在這裏就再也不詳細分析了.
另外,有人可能看出了一個問題.這裏clinet->addr是從哪來的呢?對,在read以前應該還要有一步操做來設置 clinet->addr的值.這個過程是ioctl的操做.ioctl能夠設置PEC標誌,重試次數,超時時間,和發送接收數據等,咱們在這裏只看一下clinet->addr的設置.代碼片斷以下示:
static int i2cdev_ioctl(struct inode *inode, struct file *file,
        unsigned int cmd, unsigned long arg)
{
    ......
    ......
    switch ( cmd ) {
    case I2C_SLAVE:
    case I2C_SLAVE_FORCE:
        /* NOTE:  devices set up to work with "new style" drivers
        * can't use I2C_SLAVE, even when the device node is not
        * bound to a driver.  Only I2C_SLAVE_FORCE will work.
        *
        * Setting the PEC flag here won't affect kernel drivers,
        * which will be using the i2c_client node registered with
        * the driver model core.  Likewise, when that client has
        * the PEC flag already set, the i2c-dev driver won't see
        * (or use) this setting.
        */
        if ((arg > 0x3ff) ||
            (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
            return -EINVAL;
        if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
            return -EBUSY;
        /* REVISIT: address could become busy later */
        client->addr = arg;
        return 0;
    ......
    ......
}
因而可知,調用I2C_SLAVE或者I2C_SLAVE_FORCE的Ioctl就會設置clinet->addr.另外,註釋中也說得很清楚了. 若是是I2C_SLAVE的話,還會調用其所長i2cdev_check_addr().進行地址檢查,若是adapter已經關聯到這個地址的設備,就會檢查失敗.

7.2:write操做
Write操做以下所示:
static ssize_t i2cdev_write (struct file *file, const char __user *buf, size_t count,
                            loff_t *offset)
{
    int ret;
    char *tmp;
    struct i2c_client *client = (struct i2c_client *)file->private_data;

    if (count > 8192)
        count = 8192;

    tmp = kmalloc(count,GFP_KERNEL);
    if (tmp==NULL)
        return -ENOMEM;
    if (copy_from_user(tmp,buf,count)) {
        kfree(tmp);
        return -EFAULT;
    }

    pr_debug("i2c-dev: i2c-%d writing %zd bytes./n",
        iminor(file->f_path.dentry->d_inode), count);

    ret = i2c_master_send(client,tmp,count);
    kfree(tmp);
    return ret;
}
該操做比較簡單,就是將用戶空間的數據發送到i2c 設備.

八:小結
在本節中,分析了i2c的框架設計.這個框架大致上沿用了Linux的設備驅動框架,不過之中又作了不少變通.在以後的分析中,會分別舉一個adapter和i2c device的例子來詳細描述一下有關i2c driver的設計.

 

 

IIC驅動程序分析

 

 

根據上一節課的分析,咱們來解讀這段代碼:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };
/* 地址值是7位 */

static struct i2c_client_address_data addr_data = {
 .normal_i2c = normal_addr,  /* 要發出S信號和設備地址並獲得ACK信號,才能肯定存在這個設備 */
 .probe  = ignore,
 .ignore  = ignore,
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
 printk("at24cxx_detect\n");
 return 0;
}

/*  i2c_add_driver以後會調用到這個函數 */

static int at24cxx_attach(struct i2c_adapter *adapter)
{
 return i2c_probe(adapter, &addr_data, at24cxx_detect);//這個函數最終會調用適配器的發送函數來發送設備地址
}

/* i2c_del_driver時會調用這個函數,但前提是調用在at24cxx_detect函數中調用了i2c_attach_client函數將 設備、驅動、適配器三者聯繫起來了*/

static int at24cxx_detach(struct i2c_client *client)
{
 printk("at24cxx_detach\n");
 return 0;
}


/* 1. 分配一個i2c_driver結構體 */
/* 2. 設置i2c_driver結構體 */

static struct i2c_driver at24cxx_driver = {
 .driver = {
  .name = "at24cxx",
 },
 .attach_adapter = at24cxx_attach,
 .detach_client  = at24cxx_detach,
};

static int at24cxx_init(void)
{
 i2c_add_driver(&at24cxx_driver);
 return 0;
}

static void at24cxx_exit(void)
{
 i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");

 

咱們來分析一下這個程序的流程:

首先分配一個i2c_driver結構體,而後設置並註冊這個結構體。設置的時候有來個函數比較重要:attach_adapter 和.detach_client ,前一個在註冊的時候調用,後一個在卸載的時候調用。註冊以後,這個結構體就被加入到總線驅動列表裏面,而後它會調用at24cxx_attach函數,at24cxx_attach函數又會調用i2c_probe(adapter, &addr_data, at24cxx_detect);函數,其中addr_data,表明設備地址,at24cxx_detect是找到設備後要執行的函數。i2c_probe函數最終會調用適配器裏面的真正發送信號的函數把設備地址發送給從設備,一旦發現了相符合的從設備,就會調用at24cxx_detect函數,這個函數裏面須要調用i2c_attach_client創建聯繫。這裏創建聯繫的工做沒有作,後來再來完善。

當調用 i2c_del_driver卸載i2c_driver這個結構體的時候,就會調用對應的驅動程序的 .detach_client = at24cxx_detach,函數來解除鏈接,可是前提是已經經過i2c_attach_client函數創建了聯繫。因爲這裏並無創建聯繫,因此。。。。。。

咱們能夠總結出編寫IIC總線驅動的大體流程:

1. 分配一個i2c_driver結構體
2. 設置
      attach_adapter // 它直接調用 i2c_probe(adap, 設備地址, 發現這個設備後要調用的函數);
      detach_client  // 卸載這個驅動後,若是以前發現可以支持的設備,則調用它來清理
     
3. 註冊:i2c_add_driver

 

IIC驅動程序分析(二)

在上一節的實驗中,咱們採用的是normal_i2c 的方式,即:要發出S信號和設備地址並獲得ACK信號,才能肯定存在這個設備。那麼若是自己不存在這個設備固然啊不會給出應答信號,這是就不會調用i2c_probe(adapter, &addr_data, at24cxx_detect)函數中的at24cxx_detect函數。若是咱們目前沒有接上這個設備,可是咱們從此打算把它安裝上去,因此咱們想要調用i2c_probe(adapter, &addr_data, at24cxx_detect)函數中的at24cxx_detect函數,那怎麼辦呢?這時就不能用normal_i2c方式,而應該採用forces方式。

 

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
                                      
  /* 改成0x60的話, 因爲不存在設備地址爲0x60的設備, 因此at24cxx_detect不被調用 */

/* 這裏ANY_I2C_BUS表示在任意一條總線上查找該設備,0x60表示要發出的設備地址 */

static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};

          
static struct i2c_client_address_data addr_data = {
 .normal_i2c = ignore,  /* 要發出S信號和設備地址並獲得ACK信號,才能肯定存在這個設備 */
 .probe  = ignore,
 .ignore  = ignore,
 .forces     = forces, /* 強制認爲存在這個設備 */
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
 printk("at24cxx_detect\n");
 return 0;
}

static int at24cxx_attach(struct i2c_adapter *adapter)
{
 return i2c_probe(adapter, &addr_data, at24cxx_detect);
}

static int at24cxx_detach(struct i2c_client *client)
{
 printk("at24cxx_detach\n");
 return 0;
}


static struct i2c_driver at24cxx_driver = {
 .driver = {
  .name = "at24cxx",
 },
 .attach_adapter = at24cxx_attach,
 .detach_client  = at24cxx_detach,
};

static int at24cxx_init(void)
{
 i2c_add_driver(&at24cxx_driver);
 return 0;
}

static void at24cxx_exit(void)
{
 i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");

 

在這個程序裏面,即使不存在設備地址爲0x60的設備,也會調用i2c_probe(adapter, &addr_data, at24cxx_detect)函數中的at24cxx_detect函數,由於這裏採用的是強制的方式。具體緣由等到之後在來研究,目前只是先有個輪廓!

 

IIC驅動程序分析(三)

上面兩個程序咱們主要實現了設備的識別,可是咱們發現當卸載驅動的時候並無相關的打印信息,這時怎麼回事兒呢?其實緣由咱們以前已經提到過了,那是由於咱們在i2c_probe(adapter, &addr_data, at24cxx_detect);的功能函數at24cxx_detect裏面並無創建設備、驅動、適配器的聯繫,由於沒有創建聯繫,因此卸載的時候固然不會解除聯繫了!那麼具體應該怎麼作呢?咱們來看代碼:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };

static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
          
static struct i2c_client_address_data addr_data = {
 .normal_i2c = normal_addr, 
 .probe  = ignore,
 .ignore  = ignore,
};

static struct i2c_driver at24cxx_driver;

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
 struct i2c_client *new_client;
 
 printk("at24cxx_detect\n");

 /* 構構一個i2c_client結構體: 之後收改數據時會用到它 */
 new_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);//分配
 new_client->addr    = address;//這是設備地址
 new_client->adapter = adapter;這時適配器
 new_client->driver  = &at24cxx_driver;//這是驅動
 strcpy(new_client->name, "at24cxx");//這是名字
 i2c_attach_client(new_client);//添加聯繫

 
 return 0;
}

static int at24cxx_attach(struct i2c_adapter *adapter)
{
 return i2c_probe(adapter, &addr_data, at24cxx_detect);
}

static int at24cxx_detach(struct i2c_client *client)
{
 printk("at24cxx_detach\n");
 i2c_detach_client(client);
 kfree(i2c_get_clientdata(client));

 return 0;
}

static struct i2c_driver at24cxx_driver = {
 .driver = {
  .name = "at24cxx",
 },
 .attach_adapter = at24cxx_attach,
 .detach_client  = at24cxx_detach,
};

static int at24cxx_init(void)
{
 i2c_add_driver(&at24cxx_driver);
 return 0;
}

static void at24cxx_exit(void)
{
 i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");

 

這樣的話卸載的時候就會有打印信息了!

 

IIC驅動程序之完善篇 

下面咱們來分析一個比較完整的IIC驅動程序:


#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
/* 改成0x60的話, 因爲不存在設備地址爲0x60的設備, 因此at24cxx_detect不被調用 */

static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};

static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr, /* 要發出S信號和設備地址並獲得ACK信號,才能肯定存在這個設備 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 強制認爲存在這個設備 */
};

static struct i2c_driver at24cxx_driver;


static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;

static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
int ret; /* address = buf[0] * data = buf[1] */ if (size != 1) return -EINVAL; copy_from_user(&address, buf, 1);/ *將內核用戶空間的數據(在buf中)拷貝到address裏面 */


/* 數據傳輸三要素: 源,目的,長度 */

/* AT24CXX時,要先把要讀的存儲空間的地址發給它 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = &address; /* */
msg[0].len = 1; /* 地址=1 byte */
msg[0].flags = 0; /* 表示寫 */

/* 而後啓動讀操做 */
msg[1].addr = at24cxx_client->addr; /* */
msg[1].buf = &data; /* 目的 */
msg[1].len = 1; /* 數據=1 byte */
msg[1].flags = I2C_M_RD; /* 表示讀 */


ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if (ret == 2)
{
copy_to_user(buf, &data, 1);
return 1;
}
else
return -EIO;
}

static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char val[2];
struct i2c_msg msg[1];
int ret;

/* address = buf[0]
* data = buf[1]
*/
if (size != 2)
return -EINVAL;

copy_from_user(val, buf, 2);

/* 數據傳輸三要素: 源,目的,長度 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = val; /* */
msg[0].len = 2; /* 地址+數據=2 byte */
msg[0].flags = 0; /* 表示寫 */

ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if (ret == 1)
return 2;
else
return -EIO;
}


static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
printk("at24cxx_detect\n");

/* 構構一個i2c_client結構體: 之後收改數據時會用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver = &at24cxx_driver;
strcpy(at24cxx_client->name, "at24cxx"); i2c_attach_client(at24cxx_client); major = register_chrdev(0, "at24cxx", &at24cxx_fops); cls = class_create(THIS_MODULE, "at24cxx"); class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */ return 0;}static int at24cxx_attach(struct i2c_adapter *adapter){ return i2c_probe(adapter, &addr_data, at24cxx_detect);}static int at24cxx_detach(struct i2c_client *client){ printk("at24cxx_detach\n"); class_device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "at24cxx"); i2c_detach_client(client); kfree(i2c_get_clientdata(client)); return 0;}/* 1. 分配一個i2c_driver結構體 *//* 2. 設置i2c_driver結構體 */static struct i2c_driver at24cxx_driver = { .driver = { .name = "at24cxx", }, .attach_adapter = at24cxx_attach, .detach_client = at24cxx_detach,};static int at24cxx_init(void){ i2c_add_driver(&at24cxx_driver); return 0;}static void at24cxx_exit(void){ i2c_del_driver(&at24cxx_driver);}module_init(at24cxx_init);module_exit(at24cxx_exit);MODULE_LICENSE("GPL");

以前咱們分析的IIC驅動程序主要是經過IIC總線機制完成了存儲設備的識別,可是咱們想對存儲設備進行操做怎麼辦呢?那就要用到字符設備驅動程序的概念了!其實 IIC總線機制就和平臺總線機制是一個性質的,要完成具體的操做,還須要字符設備的支持。下面咱們來詳細分析一下:
首先經過IIC總線機制完成了存儲設備的識別,接着註冊字符設備以及建立設備節點,同時定義了操做函數,那麼咱們這裏詳細要分析的就是操做函數了,咱們先來看看iic的消息傳輸函數:
i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
adap:適配器
msgs:消息,其格式以下:

 

struct i2c_msg {
__u16 addr; /* 設備地址 */
__u16 flags;    / * 讀寫標誌位* /
.....................................................................................................
__u16 len; /* 消息的長度,字節爲單位 */
__u8 *buf; /* 指向消息的指針 */
};
咱們重點要作的就是來設置這個消息,設置好以後只要用 i2c_transfer發送就能夠了。
num:要傳輸的消息的數目
 
讀函數:
讀數據的話,首先把地址寫進去,而後把數據讀出來
些函數:
些數據的話,首先把地址寫進去,而後把數據寫進去
 

測試程序以下:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


/* i2c_test r addr
* i2c_test w addr val
*/

void print_usage(char *file)
{
printf("%s r addr\n", file);
printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
int fd;
unsigned char buf[2];

if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}

fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/at24cxx\n");
return -1;
}

if (strcmp(argv[1], "r") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
read(fd, buf, 1);
printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
} else if (strcmp(argv[1], "w") == 0) { buf[0] = strtoul(argv[2], NULL, 0); buf[1] = strtoul(argv[3], NULL, 0); write(fd, buf, 2); } else { print_usage(argv[0]); return -1; } return 0;}

 

 

八、kernel panic

有兩種主要類型kernel panic:

1.hard panic(也就是Aieee信息輸出)
2.soft panic (也就是Oops信息輸出)

 

九、USB總線,USB傳輸種類,urb等

USB總線:

USB總線屬於一種輪詢式總線,主機控制端口初始化全部的數據傳輸。每一總線動做最多傳送三個數據包,包括令牌(Token)、數據(Data)、聯絡(HandShake)。按照傳輸前制定好的原則,在每次傳送開始時,主機送一個描述傳輸動做的種類、方向、USB設備地址和終端號的USB數據包,這個數據包一般被稱爲令牌包(TokenPacket)。USB設備從解碼後的數據包的適當位置取出屬於本身的數據。數據傳輸方向不是從主機到設備就是從設備到主機。在傳輸開始時,由標誌包來標誌數據的傳輸方向,而後發送端開始發送包含信息的數據包或代表沒有數據傳送。接收端也要相應發送一個握手的數據包代表是否傳送成功。發送端和接收端之間的USB數據傳輸,在主機和設備的端口之間,可視爲一個通道。USB中有一個特殊的通道一缺省控制通道,它屬於消息通道,設備一啓動即存在,從而爲設備的設置、狀態查詢和輸入控制信息提供一個入口。


USB總線的四種傳輸類型:

 

一、中斷傳輸:由OUT事務和IN事務構成,用於鍵盤、鼠標等HID設備的數據傳輸中 二、批量傳輸:由OUT事務和IN事務構成,用於大容量數據傳輸,沒有固定的傳輸速率,也不佔用帶寬,當總線忙時,USB會優先進行其餘類型的數據傳輸,而暫時中止批量轉輸。 三、同步傳輸:由OUT事務和IN事務構成,有兩個特別地方,第一,在同步傳輸的IN和OUT事務中是沒有返回包階段的;第二,在數據包階段任何的數據包都爲DATA0 四、控制傳輸:最重要的也是最複雜的傳輸,控制傳輸由三個階段構成(初始配置階段、可選數據階段、狀態信息步驟),每個階段可以當作一個的傳輸,也就是說控制傳輸實際上是由三個傳輸構成的,用來於USB設備初次加接到主機以後,主機經過控制傳輸來交換信息,設備地址和讀取設備的描述符,使得主機識別設備,並安裝相應的驅動程式,這是每個USB研發者都要關心的問題。
URB:
USB請求塊(USB request block,urb)是USB設備驅動中用來描述與USB設備通訊所用的基本載體和核心數據結構,很是相似於網絡設備驅動中的sk_buff結構體,是USB主機與設備通訊的「電波」。

 

20.3.2 USB請求塊(URB)

20.3.2  USB請求塊(URB)

1.urb結構體

USB請求塊(USB request block,urb)是USB設備驅動中用來描述與USB設備通訊所用的基本載體和核心數據結構,很是相似於網絡設備驅動中的sk_buff結構體,是USB主機與設備通訊的「電波」。

代碼清單20.13  urb結構體

1  struct urb
2  {
3    /* 私有的:只能由USB核心和主機控制器訪問的字段 */
4    struct kref kref; /*urb引用計數 */
5    spinlock_t lock; /* urb鎖 */
6    void *hcpriv; /* 主機控制器私有數據 */
7    int bandwidth; /* INT/ISO請求的帶寬 */
8    atomic_t use_count; /* 併發傳輸計數 */
9    u8 reject; /* 傳輸將失敗*/
10 
11   /* 公共的: 能夠被驅動使用的字段 */
12   struct list_head urb_list; /* 鏈表頭*/
13   struct usb_device *dev; /* 關聯的USB設備 */
14   unsigned int pipe; /* 管道信息 */
15   int status; /* URB的當前狀態 */
16   unsigned int transfer_flags; /* URB_SHORT_NOT_OK | ...*/
17   void *transfer_buffer; /* 發送數據到設備或從設備接收數據的緩衝區 */
18   dma_addr_t transfer_dma; /*用來以DMA方式向設備傳輸數據的緩衝區 */
19   int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向緩衝區的大小 */
20                       
21   int actual_length; /* URB結束後,發送或接收數據的實際長度 */
22   unsigned char *setup_packet; /* 指向控制URB的設置數據包的指針*/
23   dma_addr_t setup_dma; /*控制URB的設置數據包的DMA緩衝區*/
24   int start_frame; /*等時傳輸中用於設置或返回初始幀*/
25   int number_of_packets; /*等時傳輸中等時緩衝區數據 */
26   int interval; /* URB被輪詢到的時間間隔(對中斷和等時urb有效) */
27   int error_count;  /* 等時傳輸錯誤數量 */
28   void *context; /* completion函數上下文 */
29   usb_complete_t complete; /* 當URB被徹底傳輸或發生錯誤時,被調用 */
30   struct usb_iso_packet_descriptor iso_frame_desc[0];
31   /*單個URB一次可定義多個等時傳輸時,描述各個等時傳輸 */
32 };

當transfer_flags標誌中的URB_NO_TRANSFER_DMA_MAP被置位時,USB核心將使用transfer_dma指向的緩衝區而非transfer_buffer指向的緩衝區,意味着即將傳輸DMA緩衝區。

當transfer_flags標誌中的URB_NO_SETUP_DMA_MAP被置位時,對於有DMA緩衝區的控制urb而言,USB核心將使用setup_dma指向的緩衝區而非setup_packet指向的緩衝區。

2.urb處理流程

USB設備中的每一個端點都處理一個urb隊列,在隊列被清空以前,一個urb的典型生命週期以下:

(1)被一個 USB 設備驅動建立。

建立urb結構體的函數爲:

struct urb *usb_alloc_urb(int iso_packets, int mem_flags);

iso_packets是這個urb應當包含的等時數據包的數目,若爲0表示不建立等時數據包。 mem_flags參數是分配內存的標誌,和kmalloc()函數的分配標誌參數含義相同。若是分配成功,該函數返回一個urb結構體指針,不然返回0。

urb結構體在驅動中不能靜態建立,由於這可能破壞USB核心給urb使用的引用計數方法。

usb_alloc_urb()的「反函數」爲:

void usb_free_urb(struct urb *urb);

該函數用於釋放由usb_alloc_urb()分配的urb結構體。

(2)初始化,被安排給一個特定USB設備的特定端點。

對於中斷urb,使用usb_fill_int_urb()函數來初始化urb,以下所示:

void usb_fill_int_urb(struct urb *urb, struct usb_device *dev,
 unsigned int pipe, void *transfer_buffer,
 int buffer_length, usb_complete_t complete,
 void *context, int interval);

urb參數指向要被初始化的urb的指針;dev指向這個urb要被髮送到的USB設備;pipe是這個urb要被髮送到的USB設備的特定端點;transfer_buffer是指向發送數據或接收數據的緩衝區的指針,和urb同樣,它也不能是靜態緩衝區,必須使用kmalloc()來分配;buffer_length是transfer_buffer指針所指向緩衝區的大小;complete指針指向當這個 urb完成時被調用的完成處理函數;context是完成處理函數的「上下文」;interval是這個urb應當被調度的間隔。

上述函數參數中的pipe使用usb_sndintpipe()或usb_rcvintpipe()建立。

對於批量urb,使用usb_fill_bulk_urb()函數來初始化urb,以下所示:

void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,
 unsigned int pipe, void *transfer_buffer,
 int buffer_length, usb_complete_t complete,
 void *context);

除了沒有對應於調度間隔的interval參數之外,該函數的參數和usb_fill_int_urb()函數的參數含義相同。

上述函數參數中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函數來建立。

對於控制 urb,使用usb_fill_control_urb()函數來初始化urb,以下所示:

void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,
 unsigned int pipe, unsigned char *setup_packet,
 void *transfer_buffer, int buffer_length,
 usb_complete_t complete, void *context);

除了增長了新的setup_packet參數之外,該函數的參數和usb_fill_bulk_urb()函數的參數含義相同。setup_packet參數指向即將被髮送到端點的設置數據包。

上述函數參數中的pipe使用usb_sndctrlpipe()或usb_rcvictrlpipe()函數來建立。

等時urb沒有像中斷、控制和批量urb的初始化函數,咱們只能手動地初始化urb,然後才能提交給USB核心。代碼清單20.14給出了初始化等時urb的例子,它來自drivers/usb/media/usbvideo.c文件。

代碼清單20.14  初始化等時urb

1  for (i = 0; i < USBVIDEO_NUMSBUF; i++)
2  {
3    int j, k;
4    struct urb *urb = uvd->sbuf[i].urb;
5    urb->dev = dev;
6    urb->context = uvd;
7    urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp);/*端口*/
8    urb->interval = 1;
9    urb->transfer_flags = URB_ISO_ASAP; /*urb被調度*/
10   urb->transfer_buffer = uvd->sbuf[i].data;/*傳輸buffer*/
11   urb->complete = usbvideo_IsocIrq; /* 完成函數 */
12   urb->number_of_packets = FRAMES_PER_DESC; /*urb中的等時傳輸數量*/
13   urb->transfer_buffer_length = uvd->iso_packet_len *FRAMES_PER_DESC;
14   for (j = k = 0; j < FRAMES_PER_DESC; j++, k += uvd->iso_packet_len)
15   {
16     urb->iso_frame_desc[j].offset = k;
17     urb->iso_frame_desc[j].length = uvd->iso_packet_len;
18   }
19 }

(3)被USB設備驅動提交給USB 核心。

在完成第(1)、(2)步的建立和初始化urb後,urb即可以提交給USB核心,經過usb_submit_urb()函數來完成,以下所示:

int usb_submit_urb(struct urb *urb, int mem_flags);

urb參數是指向urb的指針,mem_flags參數與傳遞給kmalloc()函數參數的意義相同,它用於告知USB核心如何在此時分配內存緩衝區。

在提交urb到USB核心後,直到完成函數被調用以前,不要訪問urb中的任何成員。

usb_submit_urb()在原子上下文和進程上下文中均可以被調用,mem_flags變量需根據調用環境進行相應的設置,以下所示。

l GFP_ATOMIC:在中斷處理函數、底半部、tasklet、定時器處理函數以及urb完成函數中,在調用者持有自旋鎖或者讀寫鎖時以及當驅動將current->state修改成非 TASK_ RUNNING時,應使用此標誌。

l GFP_NOIO:在存儲設備的塊I/O和錯誤處理路徑中,應使用此標誌;

l GFP_KERNEL:若是沒有任何理由使用GFP_ATOMIC和GFP_NOIO,就使用GFP_ KERNEL。

若是usb_submit_urb()調用成功,即urb的控制權被移交給USB核心,該函數返回0;不然,返回錯誤號。

(4)提交由USB核心指定的USB主機控制器驅動。

(5)被USB主機控制器處理,進行一次到USB設備的傳送。

第(4)~(5)步由USB核心和主機控制器完成,不受USB設備驅動的控制。

(6)當urb完成,USB主機控制器驅動通知USB設備驅動。

在以下3種狀況下,urb將結束,urb完成函數將被調用。

l urb 被成功發送給設備,而且設備返回正確的確認。若是urb->status爲0,意味着對於一個輸出urb,數據被成功發送;對於一個輸入urb,請求的數據被成功收到。

l 若是發送數據到設備或從設備接收數據時發生了錯誤,urb->status將記錄錯誤值。

l urb 被從USB 核心「去除鏈接」,這發生在驅動經過usb_unlink_urb()或usb_kill_urb()函數取消urb,或urb雖已提交,而USB設備被拔出的狀況下。

usb_unlink_urb()和usb_kill_urb()這兩個函數用於取消已提交的urb,其參數爲要被取消的urb指針。對usb_unlink_urb()而言,若是urb結構體中的URB_ASYNC_UNLINK(即異步unlink)的標誌被置位,則對該urb的usb_unlink_urb()調用將當即返回,具體的unlink動做將在後臺進行。不然,此函數一直等到urb被解開連接或結束時才返回。usb_kill_urb()會完全終止urb的生命週期,它一般在設備的disconnect()函數中被調用。

當urb生命結束時(處理完成或被解除連接),經過urb結構體的status成員能夠獲知其緣由,如0表示傳輸成功,-ENOENT表示被usb_kill_urb()殺死,-ECONNRESET表示被usb_unlink_urb()殺死,-EPROTO表示傳輸中發生了bitstuff錯誤或者硬件未能及時收到響應數據包,-ENODEV表示USB設備已被移除,-EXDEV表示等時傳輸僅完成了一部分等。

對以上urb的處理步驟進行一個總結,圖20.5給出了一個urb的整個處理流程,虛線框的usb_unlink_urb()和usb_kill_urb()並不是必定會發生,它只是在urb正在被USB核心和主機控制器處理時,被驅動程序取消的狀況下才發生。

3.簡單的批量與控制URB

有時USB驅動程序只是從USB設備上接收或向USB設備發送一些簡單的數據,這時候,沒有必要將urb建立、初始化、提交、完成處理的整個流程走一遍,而能夠使用兩個更簡單的函數,以下所示。

(1)usb_bulk_msg()。

usb_bulk_msg()函數建立一個USB批量urb 並將它發送到特定設備,這個函數是同步的,它一直等待urb完成後才返回。usb_bulk_msg()函數的原型爲:

int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
                 void *data, int len, int *actual_length,
                 int timeout);
圖20.5 urb處理流程

usb_dev參數爲批量消息要發送的USB 設備的指針,pipe爲批量消息要發送到的USB設備的端點,data參數爲指向要發送或接收的數據緩衝區的指針,len參數爲data參數所指向的緩衝區的長度,actual_length用於返回實際發送或接收的字節數,timeout是發送超時,以jiffies爲單位,0意味着永遠等待。

若是函數調用成功,返回0;不然,返回1個負的錯誤值。

(2)usb_control_msg()函數。

usb_control_msg()函數與usb_bulk_msg()函數相似,不過它提供驅動發送和結束USB控制信息而非批量信息的能力,該函數的原型爲:

int usb_control_msg(struct usb_device *dev, unsigned int pipe, _ _u8 request, 
_ _u8 requesttype, _ _u16 value, _ _u16 index, void *data, _ _u16 size, int timeout); 

dev指向控制消息發往的USB設備,pipe是控制消息要發往的USB設備的端點,request是這個控制消息的USB請求值,requesttype是這個控制消息的USB請求類型,value是這個控制消息的USB消息值,index是這個控制消息的USB消息索引值,data指向要發送或接收的數據緩衝區,size是data參數所指向的緩衝區的大小,timeout是發送超時,以jiffies爲單位,0意味着永遠等待。

參數request、requesttype、value和index與USB規範中定義的USB控制消息直接對應。

若是函數調用成功,該函數返回發送到設備或從設備接收到的字節數;不然,返回一個負的錯誤值。

對usb_bulk_msg()和usb_control_msg()函數的使用要特別慎重,因爲它們是同步的,所以不能在中斷上下文和持有自旋鎖的狀況下使用。並且,該函數也不能被任何其餘函數取消,所以,務必要使得驅動程序的disconnect()函數掌握足夠的信息,以判斷和等待該調用的結束。

 

十、android boot 流程

android boot process from power on

1. Power on and boot ROM code execution      
    開機並執行 boot ROM 代碼

    

At power on the CPU will be in a state where no initializations have been done. Internal clocks are not set up and the only memory available is the internal RAM. 
When power supplies are stable the execution will start with the Boot ROM code. This is a small piece of code that is hardwired in the CPU ASIC. 

纔開機時,CPU 處於未初始化狀態,尚未設定內部時鐘,僅僅只有內部 RAM 可用。當電源穩定後會開始執行 BOOT ROM 代碼,這是一小塊代碼被硬編碼在 CPU ASIC 中。

  • A. The Boot ROM code will detect the boot media using a system register that maps to some physical balls on the asic. 
        This is to determine where to find the first stage of the boot loader.
        Boot ROM 代碼會引導 boot 媒介使用系統寄存器映射到 ASIC 中一些物理區域,這是爲了肯定哪裏能找到 boot loader 的第一階段

  • B. Once the boot media sequence is established the boot ROM will try to load the first stage boot loader to internal RAM. 
        Once the boot loader is in place the boot ROM code will perform a jump and execution continues in the boot loader.
        一旦 boot 媒介順序肯定,boot ROM 會試着裝載 boot loader 的第一階段到內部 RAM 中,一旦 boot loader 就位,boot ROM 代碼會跳到並執行 boot loader

 

2. The boot loader

The boot loader is a special program separate from the Linux kernel that is used to set up initial memories and load the kernel to RAM. 
On desktop systems the boot loaders are programs like GRUB and in embedded Linux uBoot is often the boot loader of choice. 
Device manufacturers often use their own proprietary boot loaders. 

boot loader 是一個特殊的獨立於 Linux 內核的程序,它用來初始化內存和裝載內核到 RAM 中,桌面系統的 boot loader 程序有 GRUB,嵌入式系統經常使用 uBoot,
設備製造商經常使用本身專有的 boot loader 程序。

 

  • A. The first boot loader stage will detect and set up external RAM.
        boot loader 第一階段會檢測和設置外部 RAM

  • B. Once external RAM is available and the system is ready the to run something more significant the first stage will load the main boot loader and place it in external RAM.
        一旦外部 RAM 可用,系統會準備裝載主 boot loader,把它放到外部 RAM 中

  • C. The second stage of the boot loader is the first major program that will run. This may contain code to set up file systems, additional memory, 
        network support and other things.On a mobile phone it may also be responsible for loading code for the modem CPU and setting up low level memory 
        protections and security options.

        boot loader 第二階段是運行的第一個重要程序,它包含了設置文件系統,內存,網絡支持和其餘的代碼。在一個移動電話上,
        也多是負責加載調制解調器的CPU代碼和設定低級別的內存保護和安全選項

  • D. Once the boot loader is done with any special tasks it will look for a Linux kernel to boot. It will load this from the boot media 
        (or some other source depending on system configuration) and place it in the RAM. 
        It will also place some boot parameters in memory for the kernel to read when it starts up.
        一旦 boot loader 完成這些特殊任務,開始尋找 linux 內核,它會從 boot 媒介上裝載 linux 內核(或者其餘地方,這取決於系統配置),把它放到 RAM 中,
       它也會在內存中爲內核替換一些在內核啓動時讀取的啓動參數

  • E. Once the boot loader is done it will perform a jump to the Linux kernel, usually some decompression routine, and the kernel assumes system responsibility.
        一旦 boot loader 完成會跳到 linux 內核,一般經過解壓程序解壓內核文件,內核將取得系統權限

     

3. The Linux kernel
  linux 內核

The Linux kernel starts up in a similar way on Android as on other systems. It will set up everything that is needed for the system to run. Initialize interrupt controllers,
set up memory protections, caches and scheduling.
linux 內核在 android 上跟在其餘系統上的啓動方式同樣,它將設置系統運行須要的一切,初始化中斷控制器,設定內存保護,高速緩存和調度

  • A. Once the memory management units and caches have been initialized the system will be able to use virtual memory and launch user space processes.
      一旦內存管理單元和高速緩存初始化完成,系統將能夠使用虛擬內存和啓動用戶空間進程

  • B. The kernel will look in the root file system for the init process (found under system/core/init in the Android open source tree) and launch it as the initial user space process.
      內核在根目錄尋找初始化程序(代碼對應 android source tree: /system/core/init ),啓動它做爲初始化用戶空間進程

     

4. The init process
  初始化進程

The init process is the "grandmother" of all system processes. Every other process in the system will be launched from this process or one of its descendants.
初始化進程是全部其餘系統進程的 「祖母 」,系統的每個其餘進程將從該進程中或者該進程的子進程中啓動

 

  • A. The init process in Android will look for a file called init.rc. This is a script that describes the system services, file system and other parameters that need to be set up. 
        The init.rc script is placed in system/core/rootdir in the Android open source project. 
        初始化進程會尋找 init.rc 文件,init.rc 腳本文件描述了系統服務,文件系統和其餘須要設定的參數,該文件在代碼:system/core/rootdir

  • B. The init process will parse the init script and launch the system service processes.
        初始化進程解析 init 腳本,啓動系統服務進程

     

5. Zygote and Dalvik

The Zygote is launched by the init process and will basically just start executing and and initialize the Dalvik VM.
Zygote 被初始化進程啓動,開始運行和初始化 dalvik 虛擬機

6. The system server
    系統服務
The system server is the first java component to run in the system. It will start all the Android services such as telephony manager and bluetooth. 
Start up of each service is currently written directly into the run method of the system server. The system server source can be found in the file :
frameworks/base/services/java/com/android/server/SystemServer.java in the open source project.

系統服務是在系統中運行的第一個 java 組件,它會啓動全部的 android 服務,好比:電話服務,藍牙服務,每一個服務的啓動被直接寫在 SystemServer.java 這個類的 run 方法裏面
代碼: frameworks/base/services/java/com/android/server/SystemServer.java


7. Boot completed
  啓動完成

 Once the System Server is up and running and the system boot has completed there is a standard broadcast action called ACTION_BOOT_COMPLETED. 
  一旦系統服務啓動並運行,android 系統啓動就完成了,同時發出 ACTION_BOOT_COMPLETED 廣播

 

android boot 代碼流程 1

以前這篇,從總體展現了 android 的整個啓動流程,爲了搞清楚 android 啓動到底在代碼層面上是如何調用的,將從源代碼角度去分析,另全部代碼基於 android 4.0 source tree

   all story begin with the init process startup 
      故事從 init 進程啓動開始

init 運行,代碼:system/core/init ,入口:system/core/init/init.c  main 函數:

複製代碼
 1 int main(int argc, char **argv){  2  3  ...  4  // 初始化文件系統  5 mkdir("/dev", 0755);  6 mkdir("/proc", 0755);  7 mkdir("/sys", 0755);  8  9 mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); 10 mkdir("/dev/pts", 0755); 11 mkdir("/dev/socket", 0755); 12 mount("devpts", "/dev/pts", "devpts", 0, NULL); 13 mount("proc", "/proc", "proc", 0, NULL); 14 mount("sysfs", "/sys", "sysfs", 0, NULL); 15 16  ...
17 // 解析 /init.rc 和 /init.$hardware.rc 腳本,其中 $hardware 參數從 /proc/cpuinfo 中讀取,模擬器默認是 goldfish 18 INFO("reading config file\n"); 19 init_parse_config_file("/init.rc"); 20 21 /* pull the kernel commandline and ramdisk properties file in */ 22 import_kernel_cmdline(0, import_kernel_nv); 23 /* don't expose the raw commandline to nonpriv processes */ 24 chmod("/proc/cmdline", 0440); 25 get_hardware_name(hardware, &revision); 26 snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware); 27 init_parse_config_file(tmp); 28 29 ... 30 }
複製代碼

解析 init.rc 文件,主要是由 /system/core/init/init_parser.c 來完成,截取 init.rc 的部份內容以下:(具體 init.rc 文件規範,可參考:/system/core/init/readme.txt)

複製代碼
on early-init
    start ueventd

# create mountpoints
    mkdir /mnt 0775 root system # setup the global environment export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin # Create cgroup mount points for process groups mkdir /dev/cpuctl mount cgroup none /dev/cpuctl cpu chown system system /dev/cpuctl chown system system /dev/cpuctl/tasks chmod 0777 /dev/cpuctl/tasks write /dev/cpuctl/cpu.shares 1024 service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main socket zygote stream 666 onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart media onrestart restart netd
複製代碼

 init.rc 使用的是 android init language 規範,它支持4種語句,Actions,Commands,Services,Options:

  •  Action 定義方式:
on <trigger>                        #以 on 開頭,後面是觸發器名字,觸發器有3種方式:
<command> #1.只是一個名字,如: on early-init; 
  <command>                #2.name=value 對,如:on property:vold.decrypt=trigger_reset_main;
<command> #3.系統自帶的,如:device-added-<path>,device-removed-<path>,service-exited-<name>
<command> #在觸發器下一行就是在觸發器觸發的時候須要執行的命令,如:start...,mkdir,...
  •  Command 就是系統支持的一系列命令,如:export,hostname,mkdir,mount,等等,其中一部分是 linux 命令,還有一些是 android 添加的,如:class_start <serviceclass>: 啓動服務,
     class_stop <serviceclass>:關閉服務,等等。 
  •  Service 定義方式: 
複製代碼
service <name> <pathname> [ <argument> ]*
   <option>
   <option>
   ...

#如:啓動 android 最重要的服務 zygote 
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main socket zygote stream 666 onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart media onrestart restart netd
複製代碼
  •  Option 是針對 Service 的選項,如: 
複製代碼
setenv <name> <value>                           在啓動服務時設置環境變量 user <username>                   運行服務以前切換用戶 oneshot                   若是服務已經存在,將再也不啓動 class <classname>                   爲服務設置名字,具備相同名字的服務將一塊兒啓動或者關閉
socket <name> <type> <perm> [ <user> [ <group> ] ]      建立以<name>命名的 socket,並將該 socket 的文件描述符返回給啓動的服務
onrestart <command>                        在服務從新啓動的時候執行<command>
複製代碼

 在對 init.rc 和 init.$hardware.rc 2個文件按照 android init language 規範進行解析完成之後,會將全部將要執行的命令放到一個大的 action_list 當中,而後緊跟上面解析 init 文件下面:

複製代碼
  // 尋找 early-init 觸發器,加到 action_queue 中
action_for_each_trigger("early-init", action_add_queue_tail);
// 添加系統的一些觸發器    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(property_init_action, "property_init"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); queue_builtin_action(set_init_properties_action, "set_init_properties"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */ if (strcmp(bootmode, "charger") != 0) { action_for_each_trigger("early-fs", action_add_queue_tail); action_for_each_trigger("fs", action_add_queue_tail); action_for_each_trigger("post-fs", action_add_queue_tail); action_for_each_trigger("post-fs-data", action_add_queue_tail); } queue_builtin_action(property_service_init_action, "property_service_init"); queue_builtin_action(signal_init_action, "signal_init"); queue_builtin_action(check_startup_action, "check_startup"); if (!strcmp(bootmode, "charger")) { action_for_each_trigger("charger", action_add_queue_tail); } else { action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail); } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");      ...
  
  // 按順序執行 action_queue 中的命令 for(;;) { int nr, i, timeout = -1; execute_one_command(); restart_processes(); ... }
複製代碼

 從 action_list 中找到制定觸發器,將觸發器須要執行的命令添加到 action_queue 中,最後按順序執行 action_queue 中的命令來完成初始化,初始化除了設置一些環境變量和建立文件夾之外,

 更多的是關心 Service 的啓動,init 文件裏面的服務有2種,1種是 class core,還有1種是 class main,對 init.rc 的 service 按照 class <name> 分類以下:

複製代碼
class core: service ueventd /sbin/ueventd service console /system/bin/sh service adbd /sbin/adbd service servicemanager /system/bin/servicemanager service vold /system/bin/vold class main: service netd /system/bin/netd service debuggerd /system/bin/debuggerd service ril-daemon /system/bin/rild service surfaceflinger /system/bin/surfaceflinger service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server service drm /system/bin/drmserver service media /system/bin/mediaserver service bootanim /system/bin/bootanimation service dbus /system/bin/dbus-daemon --system --nofork service bluetoothd /system/bin/bluetoothd -n service installd /system/bin/installd service flash_recovery /system/etc/install-recovery.sh service racoon /system/bin/racoon service mtpd /system/bin/mtpd service keystore /system/bin/keystore /data/misc/keystore service dumpstate /system/bin/dumpstate -s
複製代碼

 service ueventd:會讀取 /ueventd.rc 和 /ueventd.$hadrware.rc 文件,解析跟 init.rc 解析相似,主要是對文件系統的權限和用戶進行設置:(代碼:system/core/init/ueventd.c)

#目錄 權限 user group

/dev/null 0666 root root /dev/zero 0666 root root

 在 class core 服務啓動之後, class main 開始啓動,service zygote 是標誌進入 android 最重要的一個服務,服務名字 zygote,實際上啓動的是 app_process,(代碼:frameworks/base/cmds/app_process/app_main.cpp) 

複製代碼
int main(int argc, const char* const argv[]) { // These are global variables in ProcessState.cpp mArgC = argc; mArgV = argv; mArgLen = 0;

//讀取參數,傳遞的參數就是 init.rc 中啓動時傳入的: -Xzygote /system/bin --zygote --start-system-server for (int i=0; i<argc; i++) { mArgLen += strlen(argv[i]) + 1; } mArgLen--; AppRuntime runtime; const char* argv0 = argv[0]; //-Xzygote // Process command line arguments // ignore argv[0] argc--; //以前是 4, 如今是 3 argv++; //argv 指向 argv[1] // Everything up to '--' or first non '-' arg goes to the vm   
   // i = 0,代碼:frameworks/base/core/jni/AndroidRuntime.cpp int i = runtime.addVmArguments(argc, argv); // Parse runtime arguments. Stop at first unrecognized option. bool zygote = false; bool startSystemServer = false; bool application = false; const char* parentDir = NULL; const char* niceName = NULL; const char* className = NULL; while (i < argc) { const char* arg = argv[i++]; if (!parentDir) { parentDir = arg;               //parentDir = /system/bin } else if (strcmp(arg, "--zygote") == 0) { //當 i = 2,arg = argv[1] 時,即:--zygote zygote = true;                 niceName = "zygote"; } else if (strcmp(arg, "--start-system-server") == 0) { startSystemServer = true; } else if (strcmp(arg, "--application") == 0) { application = true; } else if (strncmp(arg, "--nice-name=", 12) == 0) { niceName = arg + 12; } else { className = arg; break; } } if (niceName && *niceName) { setArgv0(argv0, niceName); set_process_name(niceName); } runtime.mParentDir = parentDir; if (zygote) {
     // zygote = true,啓動 com.android.internal.os.ZygoteInit,參數:startSystemServer runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : ""); } else if (className) { // Remainder of args get passed to startup class main() runtime.mClassName = className; runtime.mArgC = argc - i; runtime.mArgV = argv + i; runtime.start("com.android.internal.os.RuntimeInit", application ? "application" : "tool"); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); return 10; } }
複製代碼

檢測傳入參數,將調用 : 

 runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : "");

 runtime 的代碼:frameworks/base/core/jni/AndroidRuntime.cpp,start 函數會啓動虛擬機, 執行 com.android.internal.os.ZygoteInit 該類的 main 函數,並傳入參數: start-system-server:

複製代碼
void AndroidRuntime::start(const char* className, const char* options) { LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n", className != NULL ? className : "(unknown)"); 
...
/* start the virtual machine */
//設置 dalvik 虛擬機參數,建立並啓動虛擬機 JNIEnv* env; if (startVm(&mJavaVM, &env) != 0) { return; } onVmCreated(env); /* * Register android functions. */ if (startReg(env) < 0) { LOGE("Unable to register all android natives\n"); return; } ...
/* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */
//將類名 com.xxx.xxx 轉換成 com/xxx/xxx char* slashClassName = toSlashClassName(className); jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { LOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else {
     // jni 調用 java 方法,獲取對應類名的 class,而後調用靜態 main 方法 jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { LOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { env->CallStaticVoidMethod(startClass, startMeth, strArray); } } free(slashClassName); LOGD("Shutting down VM\n"); if (mJavaVM->DetachCurrentThread() != JNI_OK) LOGW("Warning: unable to detach main thread\n"); if (mJavaVM->DestroyJavaVM() != 0) LOGW("Warning: VM did not shut down cleanly\n"); }
複製代碼

 ZygoteInit 類 main 函數:

複製代碼
 public static void main(String argv[]) { try { // Start profiling the zygote initialization.  SamplingProfilerIntegration.start();        
//註冊 socket server registerZygoteSocket(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis());

//預加載資源,有 preloadClasses() 和 preloadResources(),加載的開始和結束會被記錄在 /system/etc/event-log-tags 文件中 preload(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis());        
  // Finish profiling the zygote initialization. SamplingProfilerIntegration.writeZygoteSnapshot(); // Do an initial gc to clean up after startup gc(); // If requested, start system server directly from Zygote if (argv.length != 2) { throw new RuntimeException(argv[0] + USAGE_STRING); }    if (argv[1].equals("start-system-server")) {
          //在調用 Zygote 的 main 函數時,已經傳入 start-system-server,調用 startSystemServer() startSystemServer(); } else if (!argv[1].equals("")) { throw new RuntimeException(argv[0] + USAGE_STRING); } Log.i(TAG, "Accepting command socket connections"); if (ZYGOTE_FORK_MODE) { runForkMode(); } else {
         //上面經過 registerZygoteSocket() 函數調用註冊的 server scocket,會啓動,開始監聽 Zygote 鏈接 runSelectLoopMode(); }          closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } }
複製代碼

 

那 startSystemServer 到底又作了什麼東東呢,以及最後系統如何發出 ACTION_BOOT_COMPLETED 廣播的呢,且聽下回分解。 -):

 

android boot 代碼流程 2

上回 說到,開始調用 ZygoteInit  main 函數,main 函數:

  • registerZygoteServer:註冊一個 zygote server socket,全部來自客戶端的鏈接都經過 socket 方式鏈接;
  • preload:預加載系統的類庫和資源,這樣其餘程序啓動將再也不加載系統資源,只需加載本身程序的資源,這樣就達到系統資源在程序之間共享;
  • startSystemServer:
複製代碼
 private static boolean startSystemServer() throws MethodAndArgsCaller, RuntimeException { /* Hardcoded command line to start the system server */
     //命令行參數,包括:uid,gid,group,process_name,process class String args[] = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007", "--capabilities=130104352,130104352", "--runtime-init", "--nice-name=system_server", "com.android.server.SystemServer", }; ZygoteConnection.Arguments parsedArgs = null; int pid; try {
       //解析命令行參數 parsedArgs = new ZygoteConnection.Arguments(args); ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); /* Request to fork the system server process */
       //從 zygote 進程派生一個新的進程,fork 可參考:http://linux.die.net/man/2/fork ,不一樣的是該進程結束時,也會讓 zygote 進程結束
      //因此這裏,會返回2次,一次返回的是 zygote 進程的 pid ,值大於0;一次返回的是子進程 pid,值等於0
// fork 返回在 zygote 進程返回的子進程 pid,非0,在子進程中返回0
pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */
    //zygote 進程 pid 非0,直接返回,而子進程 pid = 0,對子進程進行設置 if (pid == 0) { handleSystemServerProcess(parsedArgs); } return true; }
複製代碼

而 handleSystemServerProcess 將啓動 com.android.server.SystemServer:

複製代碼
private static void handleSystemServerProcess( ZygoteConnection.Arguments parsedArgs) throws ZygoteInit.MethodAndArgsCaller {
//由於有 zygote 監聽 socket,因此 system server 不監聽 socket 鏈接,此處關閉 closeServerSocket(); // set umask to 0077 so new files and directories will default to owner-only permissions. FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);      
     //設置進程名字,即從命令行參數獲取的:system_server  if (parsedArgs.niceName != null) { Process.setArgV0(parsedArgs.niceName); } if (parsedArgs.invokeWith != null) { WrapperInit.execApplication(parsedArgs.invokeWith, parsedArgs.niceName, parsedArgs.targetSdkVersion, null, parsedArgs.remainingArgs); } else { /* * Pass the remaining arguments to SystemServer. */
       /* zygoteInit -> applicationInit:設置 sdktarget 版本 -> invokeStaticMain:獲得 com.android.server.SystemServer main 方法 -> ZygoteInit.MethodAndArgsCaller
        * ZygoteInit.MethodAndArgsCaller 方法拋出異常 MethodAndArgsCaller,跳過了在 startSystemServer 下面的代碼:
* if (ZYGOTE_FORK_MODE) {
        * runForkMode();
        * } else {
        * runSelectLoopMode();
        * }
        */
       RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs); } /* should never reach here */
     }
複製代碼

 在對 MethodAndArgsCaller 異常 catch 語句裏,直接調用了 com.android.server.SystemServer main 方法,而 zygote 進程由於 pid 不爲0,執行 runSelectLoopMode 方法:

複製代碼
 private static void runSelectLoopMode() throws MethodAndArgsCaller { ArrayList<FileDescriptor> fds = new ArrayList(); ArrayList<ZygoteConnection> peers = new ArrayList(); FileDescriptor[] fdArray = new FileDescriptor[4]; fds.add(sServerSocket.getFileDescriptor()); peers.add(null); int loopCount = GC_LOOP_COUNT;
     //一直循環 while (true) { int index; /* * Call gc() before we block in select(). * It's work that has to be done anyway, and it's better * to avoid making every child do it. It will also * madvise() any free memory as a side-effect. * * Don't call it every time, because walking the entire * heap is a lot of overhead to free a few hundred bytes. */ if (loopCount <= 0) { gc(); loopCount = GC_LOOP_COUNT; } else { loopCount--; }        //採用非阻塞方式,等待並取出 zygote 鏈接 try { fdArray = fds.toArray(fdArray); index = selectReadable(fdArray); } catch (IOException ex) { throw new RuntimeException("Error in select()", ex); }
//selectReadable 返回值小於0 ,有錯誤發生;值等於0,有新的鏈接,加到 list 中;值大於0,處理當前鏈接 if (index < 0) { throw new RuntimeException("Error in select()"); } else if (index == 0) { ZygoteConnection newPeer = acceptCommandPeer(); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); } else { boolean done; done = peers.get(index).runOnce(); if (done) { peers.remove(index); fds.remove(index); } } } }
複製代碼

 在 zygote 進程等待鏈接的同時,com.android.server.SystemServer 已經啓動:

複製代碼
    native public static void init1(String[] args); public static void main(String[] args) {      ...
     //加載 jni ,init1 是本地方法 System.loadLibrary("android_servers");
     // init1 -> frameworks/base/services/jni/com_android_server_SystemServer.cpp :: android_server_SystemServer_init1 ->
    // frameworks/base/cmds/system_server/library/system_init.cpp :: system_init init1(args); }
   // init1 將回調 init2 方法 public static final void init2() { Slog.i(TAG, "Entered the Android system server!"); Thread thr = new ServerThread(); thr.setName("android.server.ServerThread"); thr.start(); }
複製代碼

init1 方法最終調用的是 system_init 方法(代碼:frameworks/base/cmds/system_server/library/system_init.cpp)

複製代碼
extern "C" status_t system_init() { LOGI("Entered system_init()"); sp<ProcessState> proc(ProcessState::self());    sp<IServiceManager> sm = defaultServiceManager(); LOGI("ServiceManager: %p\n", sm.get()); sp<GrimReaper> grim = new GrimReaper(); sm->asBinder()->linkToDeath(grim, grim.get(), 0);   
 //初始化 SurfaceFlinger 和傳感器 char propBuf[PROPERTY_VALUE_MAX]; property_get("system_init.startsurfaceflinger", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { // Start the SurfaceFlinger SurfaceFlinger::instantiate(); } property_get("system_init.startsensorservice", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { // Start the sensor service SensorService::instantiate(); } // And now start the Android runtime. We have to do this bit // of nastiness because the Android runtime initialization requires // some of the core system services to already be started. // All other servers should just start the Android runtime at // the beginning of their processes's main(), before calling // the init function. LOGI("System server: starting Android runtime.\n"); AndroidRuntime* runtime = AndroidRuntime::getRuntime();   
   //回調 com.android.server.SystemServer init2 方法 LOGI("System server: starting Android services.\n"); JNIEnv* env = runtime->getJNIEnv(); if (env == NULL) { return UNKNOWN_ERROR; } jclass clazz = env->FindClass("com/android/server/SystemServer"); if (clazz == NULL) { return UNKNOWN_ERROR; } jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V"); if (methodId == NULL) { return UNKNOWN_ERROR; } env->CallStaticVoidMethod(clazz, methodId);   
  //啓動線程池,爲 binder 服務 LOGI("System server: entering thread pool.\n"); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); LOGI("System server: exiting thread pool.\n"); return NO_ERROR; }
複製代碼

 init2 啓動 ServerThread 線程,它會啓動 android 系統全部的服務:

複製代碼
 public void run() { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, SystemClock.uptimeMillis());      Looper.prepare(); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_FOREGROUND); BinderInternal.disableBackgroundScheduling(true); android.os.Process.setCanSelfBackground(false);  String factoryTestStr = SystemProperties.get("ro.factorytest"); int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF : Integer.parseInt(factoryTestStr);       
     //初始化服務,如:網絡服務,Wifi服務,藍牙,電源,等等,初始化完成之後,加到 ServiceManager 中,
//因此咱們用 Context.getSystemService (String name) 才獲取到相應的服務
LightsService lights = null; PowerManagerService power = null; BatteryService battery = null; AlarmManagerService alarm = null; NetworkManagementService networkManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; ConnectivityService connectivity = null; WifiP2pService wifiP2p = null; WifiService wifi = null; IPackageManager pm = null; Context context = null; WindowManagerService wm = null; BluetoothService bluetooth = null; BluetoothA2dpService bluetoothA2dp = null; DockObserver dock = null; UsbService usb = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; NetworkTimeUpdateService networkTimeUpdater = null; // Critical services... try { Slog.i(TAG, "Entropy Service"); ServiceManager.addService("entropy", new EntropyService());
Slog.i(TAG, "Package Manager"); // Only run "core" apps if we're encrypting the device.
......       
      //ActivityManagerService 是 android 系統最核心的服務之一 

//1.系統 context 的初始化,設置默認主題 android.R.style.Theme_Holo
//2.設置進程名字爲 system_process
//3.初始化 ActivityStack

context = ActivityManagerService.main(factoryTest);

 

//往 service manager 裏面添加一些服務,如:activity,meminfo,cupinfo,permission
ActivityManagerService.setSystemProcess();

 

//安裝系統 content provider
Slog.i(TAG, "System Content Providers");
ActivityManagerService.installSystemProviders();

 

//設置 windows manager

ActivityManagerService.self().setWindowManager(wm);                    

            ......
// We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state // where third party code can really run (but before it has actually // started launching the initial applications), for us to complete our // initialization.
    
     //代碼到這裏,代表系統已經就緒,能夠運行第3方代碼 ActivityManagerService.self().systemReady(new Runnable() { public void run() { Slog.i(TAG, "Making services ready");          // systemui 是 3.0 之後添加的,由於沒有物理鍵,提供虛擬鍵  startSystemUi(contextF);

         //諸多服務開始啓動 try { if (batteryF != null) batteryF.systemReady(); } catch (Throwable e) { reportWtf("making Battery Service ready", e); } try { if (networkManagementF != null) networkManagementF.systemReady(); } catch (Throwable e) { reportWtf("making Network Managment Service ready", e); } ...... } }); // For debug builds, log event loop stalls to dropbox for analysis. if (StrictMode.conditionallyEnableDebugLogging()) { Slog.i(TAG, "Enabled StrictMode for system server main thread."); } Looper.loop(); Slog.d(TAG, "System ServerThread is exiting!"); }
複製代碼

 而要執行 ActivityManagerService.self().systemReady(new Runnable() ...) 參數裏面 Runnable 的 run 方法,還必須等到 ActivityManagerService systemReady:

複製代碼
public void systemReady(final Runnable goingCallback) { synchronized(this) {
       //mSystemReady = false if (mSystemReady) { if (goingCallback != null) goingCallback.run(); return; } // Check to see if there are any update receivers to run. if (!mDidUpdate) { if (mWaitingUpdate) { return; }
         //檢測是否有 ACTION_PRE_BOOT_COMPLETED register,該廣播在 ACTION_BOOT_COMPLETED 前發出 Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); List<ResolveInfo> ris = null; try { ris = AppGlobals.getPackageManager().queryIntentReceivers( intent, null, 0); } catch (RemoteException e) { } if (ris != null) { for (int i=ris.size()-1; i>=0; i--) {
              //檢測廣播註冊是不是系統程序 if ((ris.get(i).activityInfo.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) == 0) { ris.remove(i); } } intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE); ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers(); final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>(); for (int i=0; i<ris.size(); i++) { ActivityInfo ai = ris.get(i).activityInfo; ComponentName comp = new ComponentName(ai.packageName, ai.name); if (lastDoneReceivers.contains(comp)) { ris.remove(i); i--; } } for (int i=0; i<ris.size(); i++) { ActivityInfo ai = ris.get(i).activityInfo; ComponentName comp = new ComponentName(ai.packageName, ai.name); doneReceivers.add(comp); intent.setComponent(comp); IIntentReceiver finisher = null; if (i == ris.size()-1) { finisher = new IIntentReceiver.Stub() { public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) { // The raw IIntentReceiver interface is called // with the AM lock held, so redispatch to // execute our code without the lock. mHandler.post(new Runnable() { public void run() { synchronized (ActivityManagerService.this) { mDidUpdate = true; } writeLastDonePreBootReceivers(doneReceivers); showBootMessage(mContext.getText( R.string.android_upgrading_complete), false);
                          //若是有 ACTION_PRE_BOOT_COMPLETED,在處理完廣播 receive 之後 ,還會再次走 systemRead(goingCallback) systemReady(goingCallback); } }); } }; } Slog.i(TAG, "Sending system update to: " + intent.getComponent()); broadcastIntentLocked(null, null, intent, null, finisher, 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID); if (finisher != null) { mWaitingUpdate = true; } } } if (mWaitingUpdate) { return; } mDidUpdate = true; } mSystemReady = true;
       //mStartRunning 已經在 ActivityManagerService.main(int factoryTest) 設置成 true if (!mStartRunning) { return; } }
     ......
retrieveSettings();     
  
     //開始執行 runnable 的 run 方法,執行完成之後,系統就緒 if (goingCallback != null) goingCallback.run(); synchronized (this) { if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { List apps = AppGlobals.getPackageManager(). getPersistentApplications(STOCK_PM_FLAGS); if (apps != null) { int N = apps.size(); int i; for (i=0; i<N; i++) { ApplicationInfo info = (ApplicationInfo)apps.get(i); if (info != null && !info.packageName.equals("android")) { addAppLocked(info); } } } } catch (RemoteException ex) { // pm is in same process, this will never happen. } } // Start up initial activity. mBooting = true; try { if (AppGlobals.getPackageManager().hasSystemUidErrors()) { Message msg = Message.obtain(); msg.what = SHOW_UID_ERROR_MSG; mHandler.sendMessage(msg); } } catch (RemoteException e) { }        
       //恢復 top activity,由於如今沒有任何啓動的 activity, 將會啓動 startHomeActivityLocked,啓動 HOME   mMainStack.resumeTopActivityLocked(null); } }
複製代碼

 HOME 啓動之後,ActivityManagerService 中 finishBooting 方法會發出 Intent.ACTION_BOOT_COMPLETED 廣播,調用該方法的地方有不少,resume activity 的時候或者出錯的時候,

 調用一次之後就再也不調用。

 

 至此 android 就完成了整個啓動工做,整個流程能夠用下圖簡潔表示:

 

十一、android init解析init.rc

Android初始化語言(Android Init Language

 

 

Android初始化腳本語言包含四種類型的語句:

  • 動做(Actions)
  • 指令(Commands)
  • 服務(Services)
  • 選項(Options)

該語言的語法包括下列約定:

  • 全部類型的語句都是基於行(line-oriented)的, 一個語句包含若干個tokens,token之間經過空格字符分隔. 若是一個token中須要包含空格字符,則須要經過C語言風格的反斜線('\')來轉義,或者使用雙引號把整個token引發來。反斜線還能夠出如今一行的末尾,表示下一行的內容仍然屬於當前語句。
  • 以'#'開始的行是註釋行。
  • 動做(Actions)和服務(Services)語句隱含表示一個新的段落(section)的開始。 全部的指令(commands)和選項(options)歸屬於上方最近的一個段落。在第一個段落以前的指令(commands)和選項(options)是無效的。
  • 動做(Actions)和服務(Services)擁有惟一性的名字。若是出現重名,那麼後出現的定義將被做爲錯誤忽略掉。

動做(Actions)

動做(Actions)是一個有名字的指令(commands)序列。每一個動做(Actions)都定義一個觸發條件(trigger),用於指示何時執行這個動做。當與動做的觸發器匹配的事件發生時,該動做將被添加到一個即將被執行的隊列的隊尾(除非它已經在隊列中)。

隊列中的每個動做被依次取出執行,動做中的每個指令也將依次執行。初始化程序(Init)在執行一個動做的各項指令的期間,還須要處理其它操做(好比,設備建立/銷燬,屬性設置,進程重啓)。

一個動做定義的形式以下:

on <trigger>
  <command>
  <command>
  <command>

服務(Services)

服務是初始化程序須要啓動的一些程序,初始化程序還有可能會在這些程序退出以後重啓它們。Services take 一個服務定義的形式以下:

  service <name> <pathname> [ <argument> ]*
  <option>
  <option>
  ...

選項(Options)

選項將影響控制初始化程序運行服務的時機和方法。可能的選項以下表。

選項 說明
disabled This service will not automatically start with its class. It must be explicitly started by name.
socket <name> <type>  <perm> [ <user> [ <group> ] ] Create a unix domain socket named /dev/socket/<name> and pass its fd to the launched process. Valid <type> values include dgram and streamuser and groupdefault to 0.
user <username> Change to username before exec'ing this service. Currently defaults to root.
group <groupname> [ <groupname> ]* Change to groupname before exec'ing this service.  Additional  groupnames beyond the first, which is required, are used to set additional groups of the process (withsetgroups()). Currently defaults to root.
capability [ <capability> ]+ Set linux capability before exec'ing this service
oneshot Do not restart the service when it exits.
class <name> Specify a class name for the service.  All services in a named class must start and stop together. A service is considered of class "default" if one is not specified via the class option.

 

觸發器(Triggers)

觸發器是一個字符串,用於匹配特定的事件,這些事件將觸發觸發器所屬動做(Actions)的執行。

觸發器 說明
boot This is the first trigger that occurs when init starts (after /init.conf is loaded).
<name>=<value> Triggers of this form occur when the property <name> is set to the specific value<value>.
device-added-<path>
device-removed-<path>
Triggers of these forms occur when a device node is added or removed.
service-exited-<name> Triggers of this form occur when the specified service exits.


指令(Commands)

Command Description
exec <path> [ <argument> ]* Fork and execute a program (<path>). This will block until the program completes execution. Try to avoid exec. Unlike the builtin commands, it runs the risk of getting init "stuck".
export <name> <value> Set the environment variable <name> equal to <value> in the global environment (which will be inherited by all processes started after this command is executed).
ifup <interface> Bring the network interface <interface> online.
import <filename> Parse an init config file, extending the current configuration.
hostname <name> Set the host name.
class_start <serviceclass> Start all services of the specified class if they are not already running.
class_stop <serviceclass> Stop all services of the specified class if they are currently running.
domainname <name> Set the domain name.
insmod <path> Install the module at <path>.
mkdir <path> Make a directory at <path>.
mount <type> <device> <dir> [ <mountoption> ]* Attempt to mount the named device at the directory <dir> <device>. This may be of the form mtd@name to specify a mtd block device by name.
setkey - currenlty undefined -
setprop <name> <value> Set system property <name> to <value>.
setrlimit <resource> <cur> <max> Set the rlimit for a resource.
start <service> Start a service running if it is not already running.
stop <service> Stop a service from running if it is currently running.
symlink <target> <path> Create a symbolic link at <path> with the value <target>.
write <path> <string> [ <string> ]* Open the file at <path> and write one or more strings to it with write(2).

 

屬性(Properties)

初始化程序(Init)能夠根據須要修改一些系統的屬性。 

 

屬性 說明
init.action Equal to the name of the action currently being executed or "" if none.
init.command Equal to the command being executed or "" if none.
init.svc.<name> State of a named service ("stopped", "running", or "restarting").

 

init.rc文件示例

on boot
  export PATH /sbin:/system/sbin:/system/bin
  export LD_LIBRARY_PATH /system/lib

  mkdir /dev
  mkdir /proc
  mkdir /sys


  mount tmpfs tmpfs /dev
  mkdir /dev/pts
  mkdir /dev/socket
  mount devpts devpts /dev/pts
  mount proc proc /proc
  mount sysfs sysfs /sys


  write /proc/cpu/alignment 4


  ifup lo


  hostname localhost
  domainname localhost


  mount yaffs2 mtd@system /system
  mount yaffs2 mtd@userdata /data


  import /system/etc/init.conf


  class_start default


service adbd /sbin/adbd
  user adb
  group adb


service usbd /system/bin/usbd -r
  user usbd
  group usbd
  socket usbd 666


service zygote /system/bin/app_process -Xzygote /system/bin --zygote
  socket zygote 666


service runtime /system/bin/runtime
  user system
  group system


on device-added-/dev/compass
  start akmd


on device-removed-/dev/compass
  stop akmd


service akmd /sbin/akmd
  disabled
  user akmd
  group akmd

 

十二、同步和互斥

同步和互斥

 

  相交進程之間的關係主要有兩種,同步與互斥。所謂互斥,是指散步在不一樣進程之間的若干程序片段,當某個進程運行其中一個程序片斷時,其它進程就不能運行它們之中的任一程序片斷,只能等到該進程運行完這個程序片斷後才能夠運行。所謂同步,是指散步在不一樣進程之間的若干程序片段,它們的運行必須嚴格按照規定的某種前後次序來運行,這種前後次序依賴於要完成的特定的任務。

 

  顯然,同步是一種更爲複雜的互斥,而互斥是一種特殊的同步。 

  也就是說互斥是兩個線程之間不能夠同時運行,他們會相互排斥,必須等待一個線程運行完畢,另外一個才能運行,而同步也是不能同時運行,但他是必需要安照某種次序來運行相應的線程(也是一種互斥)!

  總結:

  互斥:是指某一資源同時只容許一個訪問者對其進行訪問,具備惟一性和排它性。但互斥沒法限制訪問者對資源的訪問順序,即訪問是無序的。

  同步:是指在互斥的基礎上(大多數狀況),經過其它機制實現訪問者對資源的有序訪問。在大多數狀況下,同步已經實現了互斥,特別是全部寫入資源的狀況一定是互斥的。少數狀況是指能夠容許多個訪問者同時訪問資源
相關文章
相關標籤/搜索