#頭條創作挑戰賽#
全文耗時一周,精心彙總20000餘字,希望對大家有所幫助,感覺可以的點贊,關注,不迷路,後續還有更多幹貨!
看文章前,答應我,靜下心來,慢慢品!
文章目錄
- 3.1、什麼是Uboot驅動模型
- 3.2、為什麼要有驅動模型呢
- 3.3、如何使用uboot的DM模型
- ①:menuconfig配置全局DM模型
- ②:指定某個驅動的DM模型
- 3.4、DM模型資料結構
- ① global_data
- ② uclass
- ③ uclass_driver
- ④ uclass_id
- ⑤ udevice
- ⑥ driver
- 3.5、DM驅動模型之上帝視角
- 3.6、DM模型——Udevice與driver綁定
- ① dm_init
- ② lists_bind_fdt
- 3.7、DM模型——probe探測函數的執行
- 3.8、DM模型——uclass與uclass_driver綁定
- 3.9參考文檔
3.1、什麼是Uboot驅動模型
學過Linux的朋友基本都知道Linux的裝置驅動模型,Uboot根據Linux的驅動模型架構,也引入了Uboot的驅動模型(driver model :DM)。
這種驅動模型為驅動的定義和通路接口提供了統一的方法。提高了驅動之間的相容性以及通路的标準型,uboot驅動模型和kernel中的裝置驅動模型類似。
3.2、為什麼要有驅動模型呢
無論是Linux還是Uboot,一個新對象的産生必定有其要解決的問題,驅動模型也不例外!
- 提高代碼的可重用性:為了能夠使代碼在不同硬體平台,不同體系架構下運作,必須要最大限度的提高代碼的可重用性。
- 高内聚,低耦合:分層的思想也是為了達到這一目标,低耦合展現在對外提供統一的抽象通路接口,高内聚将相關度緊密的集中抽象實作。
- 便于管理:在不斷發展過程中,硬體裝置越來越多,驅動程式也越來越多,為了更好的管理驅動,也需要一套優秀的驅動架構!
3.3、如何使用uboot的DM模型
DM模型的使用,可以通過menuconfig來配置。
make menuconfig
①:menuconfig配置全局DM模型
Device Drivers -> Generic Driver Options -> Enable Driver Model
通過上面的路徑來打開Driver Model模型,最終配置在.config檔案中,CONFIG_DM=y
②:指定某個驅動的DM模型
全局的DM模型打開後,我們對于不通的驅動子產品,使能或者失能DM功能。如MMC驅動為例:
Device Drivers -> MMC Host controller Support -> Enable MMC controllers using Driver Model
最終反映在.config檔案中的CONFIG_DM_MMC=y
在對應的驅動中,可以看到判斷#if !CONFIG_IS_ENABLED(DM_MMC),來判斷是否打開DM驅動模型。
在管理驅動的Makefile檔案中,也能看到obj-$(CONFIG_$(SPL_)DM_MMC) += mmc-uclass.o,來判斷是否将驅動模型加入到編譯選項中。
總之,我們要打開DM模型,最後反映在幾個配置資訊上:
- CONFIG_DM=y,全局DM模型打開
- CONFIG_DM_XXX=y,某個驅動的DM模型的打開
- 可以通過Kconifg、Makefile來檢視對應宏的編譯情況
3.4、DM模型資料結構
要想了解DM模型整套驅動架構,我們必須先了解它的一磚一瓦!也就是組成驅動架構的各個資料結構。
① global_data
typedef struct global_data {
...
#ifdef CONFIG_DM
struct udevice *dm_root; /* Root instance for Driver Model */
struct udevice *dm_root_f; /* Pre-relocation root instance */
struct list_head uclass_root; /* Head of core tree */
#endif
...
}
global_data,管理着整個Uboot的全局變量,其中dm_root,dm_root_f,uclass_root用來管理整個DM模型。這幾個變量代表什麼意思呢?
- dm_root:DM模型的根裝置
- dm_root_f:重定向前的根裝置
- uclass_root:uclass連結清單的頭
這幾個變量,最終要的作用就是:管理整個模型中的udevice裝置資訊和uclass驅動類。
② uclass
我們首先看一下uclass這個結構體
/**
* struct uclass - a U-Boot drive class, collecting together similar drivers
*
* A uclass provides an interface to a particular function, which is
* implemented by one or more drivers. Every driver belongs to a uclass even
* if it is the only driver in that uclass. An example uclass is GPIO, which
* provides the ability to change read inputs, set and clear outputs, etc.
* There may be drivers for on-chip SoC GPIO banks, I2C GPIO expanders and
* PMIC IO lines, all made available in a unified way through the uclass.
*
* @priv: Private data for this uclass
* @uc_drv: The driver for the uclass itself, not to be confused with a
* 'struct driver'
* @dev_head: List of devices in this uclass (devices are attached to their
* uclass when their bind method is called)
* @sibling_node: Next uclass in the linked list of uclasses
*/
struct uclass {
void *priv; //uclass的私有資料
struct uclass_driver *uc_drv; //uclass類的操作函數集合
struct list_head dev_head; //該uclass的所有裝置
struct list_head sibling_node; //下一個uclass的節點
};
根據注釋,我們就可以了解到,uclass相當于老師,管理着對應某一個類别下的所有的udevice。
例如:一個IIC驅動程式,其驅動程式架構是一緻的,隻有一種,但是IIC驅動的裝置可以有很多,如EEPROM,MCU6050等;
所有在這裡呢,dev_head連結清單就是用來管理該驅動類下的所有的裝置。
總結:uclass,來管理該類型下的所有裝置,并且有對應的uclass_driver驅動。
- 定義
uclass是uboot自動生成的,并且不是所有uclass都會生成,有對應uclass_driver并且有被udevice比對到的uclass才會生成。
- 存放
所有生成的uclass都會被挂載gd->uclass_root連結清單上。
- 相關API
直接周遊連結清單gd->uclass_root連結清單并且根據uclass_id來擷取到相應的uclass。
int uclass_get(enum uclass_id key, struct uclass **ucp);
// 從gd->uclass_root連結清單擷取對應的ucla ss
③ uclass_driver
正如上面,我們看到了uclass類所包含uclass_driver結構體,uclass_driver正如其名,它就是uclass的驅動程式。其主要作用是:為uclass提供統一管理的接口,結構體如下:
/**
* struct uclass_driver - Driver for the uclass
*
* A uclass_driver provides a consistent interface to a set of related
* drivers.
*/
struct uclass_driver {
const char *name; // 該uclass_driver的指令
enum uclass_id id; // 對應的uclass id
/* 以下函數指針主要是調用時機的差別 */
int (*post_bind)(struct udevice *dev); // 在udevice被綁定到該uclass之後調用
int (*pre_unbind)(struct udevice *dev); // 在udevice被解綁出該uclass之前調用
int (*pre_probe)(struct udevice *dev); // 在該uclass的一個udevice進行probe之前調用
int (*post_probe)(struct udevice *dev); // 在該uclass的一個udevice進行probe之後調用
int (*pre_remove)(struct udevice *dev);// 在該uclass的一個udevice進行remove之前調用
int (*child_post_bind)(struct udevice *dev); // 在該uclass的一個udevice的一個子裝置被綁定到該udevice之後調用
int (*child_pre_probe)(struct udevice *dev); // 在該uclass的一個udevice的一個子裝置進行probe之前調用
int (*init)(struct uclass *class); // 安裝該uclass的時候調用
int (*destroy)(struct uclass *class); // 銷毀該uclass的時候調用
int priv_auto_alloc_size; // 需要為對應的uclass配置設定多少私有資料
int per_device_auto_alloc_size; //
int per_device_platdata_auto_alloc_size; //
int per_child_auto_alloc_size; //
int per_child_platdata_auto_alloc_size; //
const void *ops; //操作集合
uint32_t flags; // 辨別為
};
- 定義
uclass_driver主要通過UCLASS_DRIVER來定義,這裡就簡單說明一下底層代碼,耐心看哦!
下面以pinctrl為例
UCLASS_DRIVER(pinctrl) = {
.id = UCLASS_PINCTRL,
.post_bind = pinctrl_post_bind,
.flags = DM_UC_FLAG_SEQ_ALIAS,
.name = "pinctrl",
};
/* Declare a new uclass_driver */
#define UCLASS_DRIVER(__name) \
ll_entry_declare(struct uclass_driver, __name, uclass)
#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))
上面基本上就是我們的底層代碼了,稍微有點繞,但是也不難!我們隻需要将宏進行替換就行了!
通過上面的定義,我們替換掉宏之後,最終得到的定義如下:
struct uclass_driver _u_boot_list_2_uclass_2_pinctrl = {
.id = UCLASS_PINCTRL,
.post_bind = pinctrl_post_bind,
.flags = DM_UC_FLAG_SEQ_ALIAS,
.name = "pinctrl",
}
//同時存放在段._u_boot_list_2_uclass_2_pinctrl中,也就是section段的内容
- 存放
由上面結構體可得,其定義之後都被存放在了段._u_boot_list_2_uclass_2_pinctrl中,那麼去哪裡可以看到呢?
在u-boot.map檔案中搜尋,._u_boot_list_2_uclass_2_pinctrl,就可以查到程式中定義的所有驅動程式。
這裡相信大家會有疑問,為什麼是uclass_2呢?我們大概看一下,也會看到uclass_1和uclass_3,這兩個代表什麼呢?往下看!
- 相關API
想要擷取uclass_driver需要先擷取uclass_driver table。
struct uclass_driver *uclass =
ll_entry_start(struct uclass_driver, uclass);
// 會根據.u_boot_list_2_uclass_1的段位址來得到uclass_driver table的位址
const int n_ents = ll_entry_count(struct uclass_driver, uclass);
// 獲得uclass_driver table的長度
struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// 從uclass_driver table中擷取uclass id為id的uclass_driver。
正如注釋描述,上文中提到的uclass_1和uclass_3起到定位作用,用于計算uclass_2的長度!
上述的API,主要用于根據uclass_id來查找到對應的uclass_driver,進而操作對應的uclass下的udevice。
④ uclass_id
我們在uclass_driver中,看到一個uclass_id類型,這種類型與uclass有什麼關系呢?
我們知道,uclass代表驅動的一個類别,uclass_driver是uclass的驅動程式,為uclass提供統一操作接口。而對于不同類型的驅動,就需要uclass_id來區分了!
事實上,每一種類型的裝置``uclass都有唯一對應的uclass_id,貫穿裝置模型,也是udevice與uclass`相關聯的關鍵之處。
enum uclass_id {
/* These are used internally by driver model */
UCLASS_ROOT = 0,
UCLASS_DEMO,
UCLASS_TEST,
UCLASS_TEST_FDT,
UCLASS_TEST_BUS,
UCLASS_TEST_PROBE,
......
/* U-Boot uclasses start here - in alphabetical order */
UCLASS_ACPI_PMC, /* (x86) Power-management controller (PMC) */
UCLASS_ADC, /* Analog-to-digital converter */
UCLASS_AHCI, /* SATA disk controller */
UCLASS_AUDIO_CODEC, /* Audio codec with control and data path */
UCLASS_AXI, /* AXI bus */
UCLASS_BLK, /* Block device */
UCLASS_BOARD, /* Device information from hardware */
......
};
在這裡,我們就把他當作一個裝置識别的标志即可!
最後,壓軸的兩個結構體出來了,也是DM模型最終操作的對象。
⑤ udevice
/**
* struct udevice - An instance of a driver
*
* This holds information about a device, which is a driver bound to a
* particular port or peripheral (essentially a driver instance).
*
*/
struct udevice {
const struct driver *driver; //device 對應的driver
const char *name; //device 的名稱
void *platdata;
void *parent_platdata;
void *uclass_platdata;
ofnode node; //裝置樹節點
ulong driver_data;
struct udevice *parent; //父裝置
void *priv; // 私有資料的指針
struct uclass *uclass; //驅動所屬的uclass
void *uclass_priv;
void *parent_priv;
struct list_head uclass_node;
struct list_head child_head;
struct list_head sibling_node;
uint32_t flags;
int req_seq;
int seq;
#ifdef CONFIG_DEVRES
struct list_head devres_head;
#endif
};
- 定義
- **寫死:**代碼中調用U_BOOT_DEVICE宏來定義裝置資源,實際為一個裝置執行個體。
- **裝置樹:**将裝置描述資訊寫在對應的DTS檔案中,然後編譯成DTB,最終由uboot解析裝置樹後動态生成的。
- 傳參方式:通過指令行或者接口将裝置資源資訊傳遞進來,非常靈活。
-
存放
udevice是最基礎的一個裝置單元,我們把它作為一個獨立的個體,上層所有的操作,最終都與該結構體有關。
我們建立一個裝置後,為了服從統一的管理,該結構體會被連接配接到DM模型下,并入到機制中。那麼udevice會被連接配接到哪裡呢?
- 将udevice連接配接到對應的uclass中,uclass主要用來管理着同一類的驅動
- 除此之外,有父子關系的udevice,還會連接配接到udevice->child_head連結清單下,友善調用
大概可以了解為下面這樣:
- 相關API
#define uclass_foreach_dev(pos, uc) \
list_for_each_entry(pos, &uc->dev_head, uclass_node)
#define uclass_foreach_dev_safe(pos, next, uc) \
list_for_each_entry_safe(pos, next, &uc->dev_head, uclass_node)
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp); // 通過索引從uclass中擷取udevice
int uclass_get_device_by_name(enum uclass_id id, const char *name, // 通過裝置名從uclass中擷取udevice
struct udevice **devp);
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp);
int uclass_get_device_by_of_offset(enum uclass_id id, int node,
struct udevice **devp);
int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
const char *name, struct udevice **devp);
int uclass_first_device(enum uclass_id id, struct udevice **devp);
int uclass_first_device_err(enum uclass_id id, struct udevice **devp);
int uclass_next_device(struct udevice **devp);
int uclass_resolve_seq(struct udevice *dev);
這些相關的API,主要作用就是根據uclass_id,查找對應的uclass,然後根據索引值或者名稱,來查找到對應的udevice
⑥ driver
struct driver {
char *name; //驅動名稱
enum uclass_id id; //驅動所對應的uclass_id
const struct udevice_id *of_match; //比對函數
int (*bind)(struct udevice *dev); //綁定函數
int (*probe)(struct udevice *dev); //注冊函數
int (*remove)(struct udevice *dev);
int (*unbind)(struct udevice *dev);
int (*ofdata_to_platdata)(struct udevice *dev);
int (*child_post_bind)(struct udevice *dev);
int (*child_pre_probe)(struct udevice *dev);
int (*child_post_remove)(struct udevice *dev);
int priv_auto_alloc_size;
int platdata_auto_alloc_size;
int per_child_auto_alloc_size;
int per_child_platdata_auto_alloc_size;
const void *ops; /* driver-specific operations */
uint32_t flags;
#if CONFIG_IS_ENABLED(ACPIGEN)
struct acpi_ops *acpi_ops;
#endif
};
- 定義
driver對象,主要通過U_BOOT_DRIVER來定義
以pinctrl來舉例
U_BOOT_DRIVER(xxx_pinctrl) = {
.name = "xxx_pinctrl",
.id = UCLASS_PINCTRL,
.of_match = xxx_pinctrl_match,
.priv_auto_alloc_size = sizeof(struct xxx_pinctrl),
.ops = &xxx_pinctrl_ops,
.probe = xxx_v2s_pinctrl_probe,
.remove = xxx_v2s_pinctrl_remove,
};
/* Declare a new U-Boot driver */
#define U_BOOT_DRIVER(__name) \
ll_entry_declare(struct driver, __name, driver)
#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))
通過上面的定義,最終我們定義的結構體如下:
struct driver _u_boot_list_2_driver_2_xxx_pinctrl = {
.name = "xxx_pinctrl",
.id = UCLASS_PINCTRL,
.of_match = xxxx_pinctrl_match,
.priv_auto_alloc_size = sizeof(struct xxx_pinctrl),
.ops = &xxxx_pinctrl_ops,
.probe = xxxx_pinctrl_probe,
.remove = xxxx_pinctrl_remove,
}
//同時存放在段._u_boot_list_2_driver_2_xxx_pinctrl中
- 存放
由上面結構體可得,其定義之後都被存放在了段._u_boot_list_2_driver_2_xxx中,那麼去哪裡可以看到呢?
在u-boot.map檔案中搜尋,._u_boot_list_2_driver,就可以查到程式中定義的所有驅動程式。
最終,所有driver結構體以清單的形式被放在.u_boot_list_2_driver_1和.u_boot_list_2_driver_3的區間中。
- 相關API
/*先擷取driver table 表*/
struct driver *drv =
ll_entry_start(struct driver, driver); // 會根據.u_boot_list_2_driver_1的段位址來得到uclass_driver table的位址
const int n_ents = ll_entry_count(struct driver, driver); // 通過.u_boot_list_2_driver_3的段位址 減去 .u_boot_list_2_driver_1的段位址 獲得driver table的長度
/*周遊所有的driver*/
struct driver *lists_driver_lookup_name(const char *name) // 從driver table中擷取名字為name的driver。
正如注釋描述,上文中提到的driver_1和driver_3起到定位作用,用于計算driver_2的長度!
上述的API,主要用于根據name來查找到對應的driver驅動程式。
綜上,DM模型相關的資料結構介紹完畢,整體設計的架構如下:
正如紅線部分,如何實作driver和udevice的綁定、uclass、uclass_driver的綁定呢?
要想真正搞懂這些,我們不得不去深入到DM的初始化流程。
3.5、DM驅動模型之上帝視角
對于DM模型,我們站在上帝視角來觀察整套模型架構是如何的!
從對象設計的角度來看,Uboot的驅動模型可以分為靜态形式和動态形式。
- 靜态模式:對象是離散的,和其他對象分隔開,減小對象複雜度,利于子產品化設計。
- 動态模式:運作态表達形式的對象是把所有的靜态對象組合成層次視圖,有清晰的資料關聯視圖
在靜态模式下,驅動模型主要将對象分為udevice和driver,即裝置和驅動程式,兩個就像火車的兩條軌道,永遠也不會産生交集,驅動和裝置可以想注冊多少就注冊多少。
我們看一下udevice的描述:
/**
* struct udevice - An instance of a driver
*
* This holds information about a device, which is a driver bound to a
* particular port or peripheral (essentially a driver instance).
*
*/
udevice是driver的一個執行個體,兩個不相交的鐵軌,終歸也是想要發生愛情的。那麼如何讓其産生交集呢?這就是動态模式需要做的工作了!
**在動态模式下,**引入了uclass和uclass_driver兩個資料結構,實作了對udevice和driver的管理。
看一下uclass和uclass_driver兩個結構體的說明:
/**
* struct uclass - a U-Boot drive class, collecting together similar drivers
*
*/
/**
* struct uclass_driver - Driver for the uclass
*
* A uclass_driver provides a consistent interface to a set of related
* drivers.
*
*/
- uclass:裝置組公共屬性對象,作為udevice的一個屬性,主要用來管理某個驅動類的所有的裝置。
- uclass_driver:裝置組公共行為對象,uclass的驅動程式,主要将uclass管理的裝置和驅動實作綁定、注冊,移除等操作。
通過這兩個結構體的引入,可以将毫不相關的udevice是driver關聯起來!
udevice與driver的綁定:通過驅動的of_match和compatible屬性來配對,綁定。
udevice與uclass的綁定:udevice内的driver下的uclass_id,來與uclass對應的uclass_driver的uclass_id進行比對。
uclass與uclass_driver的綁定:已知udevice内的driver下的uclass_id,建立uclass的同時,通過``uclass_id找到對應的uclass_driver對象,然後将uclass_driver綁定到uclass`上!
整體結構如下:
3.6、DM模型——Udevice與driver綁定
相信站在上帝視角看完DM的整體架構,大家都對DM架構有一定了解,下面我們來看看具體的實作細節!
DM的初始化分為兩個部分,一個是在relocate重定向之前的初始化:initf_dm,一個是在relocate重定向之後的初始化:initr_dm。
我們對比這兩個函數:
static int initf_dm(void)
{
#if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN)
int ret;
bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, "dm_f");
ret = dm_init_and_scan(true); //這裡為true
bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F);
if (ret)
return ret;
#endif
#ifdef CONFIG_TIMER_EARLY
ret = dm_timer_init();
if (ret)
return ret;
#endif
return 0;
}
static int initr_dm(void)
{
int ret;
/* Save the pre-reloc driver model and start a new one */
gd->dm_root_f = gd->dm_root;
gd->dm_root = NULL;
#ifdef CONFIG_TIMER
gd->timer = NULL;
#endif
bootstage_start(BOOTSTAGE_ID_ACCUM_DM_R, "dm_r");
ret = dm_init_and_scan(false); //這裡為false
bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R);
if (ret)
return ret;
return 0;
}
兩個均調用了dm_init_and_scan這個接口,這兩個的關鍵差別在于參數的不同。
- 首先說明一下dts節點中的“u-boot,dm-pre-reloc”屬性,當設定了這個屬性時,則表示這個裝置在relocate之前就需要使用。
- 當dm_init_and_scan的參數為true時,隻會對帶有“u-boot,dm-pre-reloc”屬性的節點進行解析。而當參數為false的時候,則會對所有節點都進行解析。
DM初始化的大體步驟如下:
如上程式執行流程圖,下面我們詳細講解幾個函數。
① dm_init
int dm_init(bool of_live)
{
int ret;
if (gd->dm_root) {
dm_warn("Virtual root driver already exists!\n");
return -EINVAL;
}
INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
fix_drivers();
fix_uclass();
fix_devices();
#endif
ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST); //查找root_driver驅動,并綁定
if (ret)
return ret;
#if CONFIG_IS_ENABLED(OF_CONTROL)
# if CONFIG_IS_ENABLED(OF_LIVE)
if (of_live)
DM_ROOT_NON_CONST->node = np_to_ofnode(gd->of_root);
else
#endif
DM_ROOT_NON_CONST->node = offset_to_ofnode(0);
#endif
ret = device_probe(DM_ROOT_NON_CONST); //probe激活root_driver驅動
if (ret)
return ret;
return 0;
}
dm_init這個函數,名字起的容易讓人誤導,這個函數主要做的就是初始化了根裝置root_driver,根據這個跟裝置,初始化了global_data中的dm_root、uclass_root。
② lists_bind_fdt
我們通常會使用裝置樹來定義各種裝置,是以這個函數才是主角。
這個函數主要用來查找子裝置,并且根據查找到的子裝置,進而查找對應驅動進行綁定!即:實作了driver和device的綁定。
int lists_bind_fdt(struct udevice *parent, ofnode node, struct udevice **devp,
bool pre_reloc_only)
{
struct driver *driver = ll_entry_start(struct driver, driver); //獲得驅動清單的起始位址
const int n_ents = ll_entry_count(struct driver, driver); //獲得驅動清單的總數量
const struct udevice_id *id;
struct driver *entry;
struct udevice *dev;
bool found = false;
const char *name, *compat_list, *compat;
int compat_length, i;
int result = 0;
int ret = 0;
if (devp)
*devp = NULL;
name = ofnode_get_name(node);
log_debug("bind node %s\n", name);
compat_list = ofnode_get_property(node, "compatible", &compat_length); //得到compatible屬性,用于比對driver驅動
if (!compat_list) {
if (compat_length == -FDT_ERR_NOTFOUND) {
log_debug("Device '%s' has no compatible string\n",
name);
return 0;
}
dm_warn("Device tree error at node '%s'\n", name);
return compat_length;
}
/*
* Walk through the compatible string list, attempting to match each
* compatible string in order such that we match in order of priority
* from the first string to the last.
*/
for (i = 0; i < compat_length; i += strlen(compat) + 1) {
compat = compat_list + i;
log_debug(" - attempt to match compatible string '%s'\n",
compat);
for (entry = driver; entry != driver + n_ents; entry++) { //循環判斷所有驅動是否比對
ret = driver_check_compatible(entry->of_match, &id,
compat);
if (!ret)
break;
}
if (entry == driver + n_ents)
continue;
if (pre_reloc_only) {
if (!ofnode_pre_reloc(node) &&
!(entry->flags & DM_FLAG_PRE_RELOC)) {
log_debug("Skipping device pre-relocation\n");
return 0;
}
}
log_debug(" - found match at '%s': '%s' matches '%s'\n",
entry->name, entry->of_match->compatible,
id->compatible);
ret = device_bind_with_driver_data(parent, entry, name,
id->data, node, &dev); //該函數,用于建立udevice對象,并與查找到的driver綁定
if (ret == -ENODEV) {
log_debug("Driver '%s' refuses to bind\n", entry->name);
continue;
}
if (ret) {
dm_warn("Error binding driver '%s': %d\n", entry->name,
ret);
return ret;
} else {
found = true;
if (devp)
*devp = dev;
}
break;
}
if (!found && !result && ret != -ENODEV)
log_debug("No match for node '%s'\n", name);
return result;
}
lists_bind_fdt這個函數,主要用來掃描裝置樹中的各個節點;
根據掃描到的udevice裝置資訊,通過compatible來比對compatible相同的driver,比對成功後,就會建立對應的struct udevice結構體,它會同時指向裝置資源和driver,這樣裝置資源和driver就綁定在一起了。
3.7、DM模型——probe探測函數的執行
上述,完成了DM模型的初始化,但是我們隻是建立了driver和udevice的綁定關系,那麼何時調用到我們驅動中的probe探測函數呢?uclass與driver又何時比對的呢?
上文呢,dm_init隻是負責初始化并綁定了udevice和driver,那麼probe探測函數的執行,當然是在該驅動初始化的時候喽!
下文以mmc驅動為例!其初始化流程如下:
詳細代碼在這裡就不展開來叙述了!
在MMC驅動初始化後,有沒有注意到mmc_probe這個函數,該函數就是間接調用了我們驅動編寫的probe函數。
執行流程在上面已經很清楚了:根據uclass_id,調用``uclass_get_device_by_seq來得到udevice,進而調用device_probe來找到對應驅動的probe`。
int device_probe(struct udevice *dev)
{
const struct driver *drv;
int ret;
int seq;
if (!dev)
return -EINVAL;
if (dev->flags & DM_FLAG_ACTIVATED)
return 0;
drv = dev->driver; //擷取driver
assert(drv);
ret = device_ofdata_to_platdata(dev);
if (ret)
goto fail;
/* Ensure all parents are probed */
if (dev->parent) { //父裝置probe
ret = device_probe(dev->parent);
if (ret)
goto fail;
/*
* The device might have already been probed during
* the call to device_probe() on its parent device
* (e.g. PCI bridge devices). Test the flags again
* so that we don't mess up the device.
*/
if (dev->flags & DM_FLAG_ACTIVATED)
return 0;
}
seq = uclass_resolve_seq(dev);
if (seq < 0) {
ret = seq;
goto fail;
}
dev->seq = seq;
dev->flags |= DM_FLAG_ACTIVATED;
/*
* Process pinctrl for everything except the root device, and
* continue regardless of the result of pinctrl. Don't process pinctrl
* settings for pinctrl devices since the device may not yet be
* probed.
*/
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
pinctrl_select_state(dev, "default");
if (CONFIG_IS_ENABLED(POWER_DOMAIN) && dev->parent &&
(device_get_uclass_id(dev) != UCLASS_POWER_DOMAIN) &&
!(drv->flags & DM_FLAG_DEFAULT_PD_CTRL_OFF)) {
ret = dev_power_domain_on(dev);
if (ret)
goto fail;
}
ret = uclass_pre_probe_device(dev);
if (ret)
goto fail;
if (dev->parent && dev->parent->driver->child_pre_probe) {
ret = dev->parent->driver->child_pre_probe(dev);
if (ret)
goto fail;
}
/* Only handle devices that have a valid ofnode */
if (dev_of_valid(dev)) {
/*
* Process 'assigned-{clocks/clock-parents/clock-rates}'
* properties
*/
ret = clk_set_defaults(dev, 0);
if (ret)
goto fail;
}
if (drv->probe) {
ret = drv->probe(dev); //調用驅動的probe
if (ret)
goto fail;
}
ret = uclass_post_probe_device(dev);
if (ret)
goto fail_uclass;
if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL)
pinctrl_select_state(dev, "default");
return 0;
fail_uclass:
if (device_remove(dev, DM_REMOVE_NORMAL)) {
dm_warn("%s: Device '%s' failed to remove on error path\n",
__func__, dev->name);
}
fail:
dev->flags &= ~DM_FLAG_ACTIVATED;
dev->seq = -1;
device_free(dev);
return ret;
}
主要工作歸納如下:
- 根據udevice擷取driver
- 然後判斷是否父裝置被probe
- 對父裝置進行probe
- 調用driver的probe函數
3.8、DM模型——uclass與uclass_driver綁定
上述完成了driver的probe函數調用,基本底層都已經準備好了,uclass何時與uclass_driver綁定,給上層提供統一的API呢?
uclass與uclass_driver綁定,也是在驅動probe之後,確定該驅動存在,裝置存在,最後為該驅動綁定uclass與uclass_driver,為上層提供統一接口。
以根據MMC驅動為例
回到上文的驅動流程圖,看到mmc_do_preinit這個函數了嘛?裡面調用了ret = uclass_get(UCLASS_MMC, &uc);,該函數才是真正的将uclass與uclass_driver綁定。
int uclass_get(enum uclass_id id, struct uclass **ucp)
{
struct uclass *uc;
*ucp = NULL;
uc = uclass_find(id);
if (!uc)
return uclass_add(id, ucp);
*ucp = uc;
return 0;
}
uclass_get主要實作了:根據uclass_id查找對應的uclass是否被添加到global_data->uclass_root連結清單中,如果沒有添加到,就調用uclass_add函數,實作uclass與uclass_driver的綁定,并将其添加到global_data->uclass_root連結清單中。
static int uclass_add(enum uclass_id id, struct uclass **ucp)
{
struct uclass_driver *uc_drv;
struct uclass *uc;
int ret;
*ucp = NULL;
uc_drv = lists_uclass_lookup(id); //根據uclass_id查找到對應的driver
if (!uc_drv) {
debug("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n",
id);
/*
* Use a strange error to make this case easier to find. When
* a uclass is not available it can prevent driver model from
* starting up and this failure is otherwise hard to debug.
*/
return -EPFNOSUPPORT;
}
uc = calloc(1, sizeof(*uc));
if (!uc)
return -ENOMEM;
if (uc_drv->priv_auto_alloc_size) {
uc->priv = calloc(1, uc_drv->priv_auto_alloc_size);
if (!uc->priv) {
ret = -ENOMEM;
goto fail_mem;
}
}
uc->uc_drv = uc_drv; //uclass與uclass_driver綁定
INIT_LIST_HEAD(&uc->sibling_node);
INIT_LIST_HEAD(&uc->dev_head);
list_add(&uc->sibling_node, &DM_UCLASS_ROOT_NON_CONST); //添加到global_data->uclass_root連結清單中
if (uc_drv->init) {
ret = uc_drv->init(uc);
if (ret)
goto fail;
}
*ucp = uc;
return 0;
fail:
if (uc_drv->priv_auto_alloc_size) {
free(uc->priv);
uc->priv = NULL;
}
list_del(&uc->sibling_node);
fail_mem:
free(uc);
return ret;
}
好啦,到這裡基本就把Uboot的DM模型全部理清楚啦,耗時一個周,總感覺想要自己去講明白,真的不是一件容易的事情呢!
如果對你們有幫助,記得點個贊哦!
更多文章,可以關注我的公~号:【嵌入式藝術】哦,一起讨論嵌入式技術!