天天看點

超詳細【Uboot驅動開發】(三)Uboot驅動模型

作者:嵌入式藝術

#頭條創作挑戰賽#

超詳細【Uboot驅動開發】(三)Uboot驅動模型

全文耗時一周,精心彙總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來檢視對應宏的編譯情況
超詳細【Uboot驅動開發】(三)Uboot驅動模型

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,這兩個代表什麼呢?往下看!

超詳細【Uboot驅動開發】(三)Uboot驅動模型
  • 相關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連結清單下,友善調用

大概可以了解為下面這樣:

超詳細【Uboot驅動開發】(三)Uboot驅動模型
  • 相關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,就可以查到程式中定義的所有驅動程式。

超詳細【Uboot驅動開發】(三)Uboot驅動模型

最終,所有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模型相關的資料結構介紹完畢,整體設計的架構如下:

超詳細【Uboot驅動開發】(三)Uboot驅動模型

正如紅線部分,如何實作driver和udevice的綁定、uclass、uclass_driver的綁定呢?

要想真正搞懂這些,我們不得不去深入到DM的初始化流程。

3.5、DM驅動模型之上帝視角

對于DM模型,我們站在上帝視角來觀察整套模型架構是如何的!

從對象設計的角度來看,Uboot的驅動模型可以分為靜态形式和動态形式。

  • 靜态模式:對象是離散的,和其他對象分隔開,減小對象複雜度,利于子產品化設計。
  • 動态模式:運作态表達形式的對象是把所有的靜态對象組合成層次視圖,有清晰的資料關聯視圖

在靜态模式下,驅動模型主要将對象分為udevice和driver,即裝置和驅動程式,兩個就像火車的兩條軌道,永遠也不會産生交集,驅動和裝置可以想注冊多少就注冊多少。

超詳細【Uboot驅動開發】(三)Uboot驅動模型

我們看一下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屬性來配對,綁定。

超詳細【Uboot驅動開發】(三)Uboot驅動模型

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`上!

整體結構如下:

超詳細【Uboot驅動開發】(三)Uboot驅動模型
超詳細【Uboot驅動開發】(三)Uboot驅動模型

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初始化的大體步驟如下:

超詳細【Uboot驅動開發】(三)Uboot驅動模型
如上程式執行流程圖,下面我們詳細講解幾個函數。

① 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驅動為例!其初始化流程如下:
超詳細【Uboot驅動開發】(三)Uboot驅動模型

詳細代碼在這裡就不展開來叙述了!

在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模型全部理清楚啦,耗時一個周,總感覺想要自己去講明白,真的不是一件容易的事情呢!

如果對你們有幫助,記得點個贊哦!

更多文章,可以關注我的公~号:【嵌入式藝術】哦,一起讨論嵌入式技術!

繼續閱讀