[uboot] (番外篇)uboot 驅動模型
建議先看《[uboot] (番外篇)uboot之fdt介紹》,瞭解一下uboot的fdt的功能,在驅動模型中會使用到。
==============================================================================================================
一、說明
1、uboot的驅動模型簡單介紹
uboot引入了驅動模型(driver model),這種驅動模型為驅動的定義和訪問介面提供了統一的方法。提高了驅動之間的相容性以及訪問的標準型。 uboot驅動模型和kernel中的裝置驅動模型類似,但是又有所區別。 在後續我們將驅動模型(driver model)簡稱為DM,其實在uboot裡面也是這樣簡稱的。
具體細節建議參考./doc/driver-model/README.txt
2、如何使能uboot的DM功能
(1)配置CONFIG_DM 在configs/tiny210_defconfig中定義瞭如下:
CONFIG_DM=y
- 1
(2)使能相應的uclass driver的config。 DM和uclass是息息相關的,如果我們希望在某個模組引入DM,那麼就需要使用相應模組的uclass driver來代替舊版的通用driver。 關於uclass我們會在後續繼續說明。 以serial為例,為了在serial中引入DM,我在configs/tiny210_defconfig0中打開了CONFIG_DM_SERIAL巨集,如下
CONFIG_DM_SERIAL=y
看driver/serial/Makefile
ifdef CONFIG_DM_SERIAL
obj-y += serial-uclass.o ## 引入dm的serial core驅動
else
obj-y += serial.o ## 通用的serial core驅動
endif
## 可以發現編譯出來的serial core的驅動程式碼是不一樣的。
(3)對應裝置驅動也要引入dm的功能 其裝置驅動主要是實現和底層互動,為uclass層提供介面。後續再具體說明。
__後續都以serial-uclass進行說明
二、uboot DM整體架構
1、DM的四個組成部分
uboot的DM主要有四個組成部分
- udevice 簡單就是指裝置物件,可以理解為kernel中的device。
- driver udevice的驅動,可以理解為kernel中的device_driver。和底層硬體裝置通訊,並且為裝置提供面向上層的介面。
- uclass 先看一下README.txt中關於uclass的說明:
Uclass - a group of devices which operate in the same way. A uclass provides
a way of accessing individual devices within the group, but always
using the same interface. For example a GPIO uclass provides
operations for get/set value. An I2C uclass may have 10 I2C ports,
4 with one driver, and 6 with another.
uclass,使用相同方式的操作集的device的組。相當於是一種抽象。uclass為那些使用相同介面的裝置提供了統一的介面。 例如,GPIO uclass提供了get/set介面。再例如,一個I2C uclass下可能有10個I2C埠,4個使用一個驅動,另外6個使用另外一個驅動。
- uclass_driver 對應uclass的驅動程式。主要提供uclass操作時,如繫結udevice時的一些操作。
2、呼叫關係框架圖
3、相互之間的關係
結合上圖來看:
- 上層介面都是和uclass的介面直接通訊。
- uclass可以理解為一些具有相同屬性的udevice對外操作的介面,uclass的驅動是uclass_driver,主要為上層提供介面。
- udevice的是指具體裝置的抽象,對應驅動是driver,driver主要負責和硬體通訊,為uclass提供實際的操作集。
- udevice找到對應的uclass的方式主要是通過:udevice對應的driver的id和uclass對應的uclass_driver的id是否匹配。
- udevice會和uclass繫結。driver會和udevice繫結。uclass_driver會和uclass繫結。
這裡先簡單介紹一下:uclass和udevice都是動態生成的。在解析fdt中的裝置的時候,會動態生成udevice。 然後找到udevice對應的driver,通過driver中的uclass id得到uclass_driver id。從uclass連結串列中查詢對應的uclass是否已經生成,沒有生成的話則動態生成uclass。
4、GD中和DM相關的部分
typedef struct global_data {
#ifdef CONFIG_DM
struct udevice *dm_root; /* Root instance for Driver Model */
// DM中的根裝置,也是uboot中第一個建立的udevice,也就對應了dts裡的根節點。
struct udevice *dm_root_f; /* Pre-relocation root instance */
// 在relocation之前DM中的根裝置
struct list_head uclass_root; /* Head of core tree */
// uclass連結串列,所有被udevice匹配的uclass都會被掛載到這個連結串列上
#endif
} gd_t;
三、DM四個主要組成部分詳細介紹
後續以資料結構、如何定義、存放位置、如何獲取四個部分進行說明進行說明
0、uclass id
每一種uclass都有自己對應的ID號。定義於其uclass_driver中。其附屬的udevice的driver中的uclass id必須與其一致。 所有uclass id定義於include/dm/uclass-id.h中 列出部分id如下
enum uclass_id {
/* These are used internally by driver model */
UCLASS_ROOT = 0,
UCLASS_DEMO,
UCLASS_CLK, /* Clock source, e.g. used by peripherals */
UCLASS_PINCTRL, /* Pinctrl (pin muxing/configuration) device */
UCLASS_SERIAL, /* Serial UART */
}
1、uclass
- (1)資料結構
struct uclass {
void *priv; // uclass的私有資料指標
struct uclass_driver *uc_drv; // 對應的uclass driver
struct list_head dev_head; // 連結串列頭,連線所屬的所有udevice
struct list_head sibling_node; // 連結串列節點,用於把uclass連線到uclass_root連結串列上
};
-
(2)如何定義 uclass是uboot自動生成。並且不是所有uclass都會生成,有對應uclass driver並且有被udevice匹配到的uclass才會生成。 具體參考後面的uboot DM初始化一節。或者參考uclass_add實現。
-
(3)存放位置 所有生成的uclass都會被掛載gd->uclass_root連結串列上。
-
(4)如何獲取、API 直接遍歷連結串列gd->uclass_root連結串列並且根據uclass id來獲取到相應的uclass。 具體uclass_get-》uclass_find實現了這個功能。 有如下API:
int uclass_get(enum uclass_id key, struct uclass **ucp);
// 從gd->uclass_root連結串列獲取對應的uclass
2、uclass_driver
- (1)資料結構 include/dm/uclass.h
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; // 標識為
};
- (2)如何定義 通過UCLASS_DRIVER來定義uclass_driver. 以serial-uclass為例
UCLASS_DRIVER(serial) = {
.id = UCLASS_SERIAL,
.name = "serial",
.flags = DM_UC_FLAG_SEQ_ALIAS,
.post_probe = serial_post_probe,
.pre_remove = serial_pre_remove,
.per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
};
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)))
關於ll_entry_declare我們在《[uboot] (第六章)uboot流程——命令列模式以及命令處理介紹》已經介紹過了
最終得到一個如下結構體
struct uclass_driver _u_boot_list_2_uclass_2_serial = {
.id = UCLASS_SERIAL, // 設定對應的uclass id
.name = "serial",
.flags = DM_UC_FLAG_SEQ_ALIAS,
.post_probe = serial_post_probe,
.pre_remove = serial_pre_remove,
.per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
}
並且存放在.u_boot_list_2_uclass_2_serial段中。
- (3)存放位置 通過上述,我們知道serial的uclass_driver結構體_u_boot_list_2_uclass_2_serial被存放到.u_boot_list_2_uclass_2_serial段中。 通過檢視u-boot.map得到如下
.u_boot_list_2_uclass_1
0x23e368e0 0x0 drivers/built-in.o
.u_boot_list_2_uclass_2_gpio
0x23e368e0 0x48 drivers/gpio/built-in.o
0x23e368e0 _u_boot_list_2_uclass_2_gpio // gpio uclass driver的符號
.u_boot_list_2_uclass_2_root
0x23e36928 0x48 drivers/built-in.o
0x23e36928 _u_boot_list_2_uclass_2_root // root uclass drvier的符號
.u_boot_list_2_uclass_2_serial
0x23e36970 0x48 drivers/serial/built-in.o
0x23e36970 _u_boot_list_2_uclass_2_serial // serial uclass driver的符號
.u_boot_list_2_uclass_2_simple_bus
0x23e369b8 0x48 drivers/built-in.o
0x23e369b8 _u_boot_list_2_uclass_2_simple_bus
.u_boot_list_2_uclass_3
0x23e36a00 0x0 drivers/built-in.o
0x23e36a00 . = ALIGN (0x4)
最終,所有uclass driver結構體以列表的形式被放在.u_boot_list_2_uclass_1和.u_boot_list_2_uclass_3的區間中。 這個列表簡稱uclass_driver table。
- (4)如何獲取、API 想要獲取uclass_driver需要先獲取uclass_driver table。 可以通過以下巨集來獲取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的長度
接著通過遍歷這個uclass_driver table,得到相應的uclass_driver。 有如下API
struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// 從uclass_driver table中獲取uclass id為id的uclass_driver。
3、udevice
- (1)資料結構 include/dm/device.h
struct udevice {
const struct driver *driver; // 該udevice對應的driver
const char *name; // 裝置名
void *platdata; // 該udevice的平臺數據
void *parent_platdata; // 提供給父裝置使用的平臺數據
void *uclass_platdata; // 提供給所屬uclass使用的平臺數據
int of_offset; // 該udevice的dtb節點偏移,代表了dtb裡面的這個節點node
ulong driver_data; // 驅動資料
struct udevice *parent; // 父裝置
void *priv; // 私有資料的指標
struct uclass *uclass; // 所屬uclass
void *uclass_priv; // 提供給所屬uclass使用的私有資料指標
void *parent_priv; // 提供給其父裝置使用的私有資料指標
struct list_head uclass_node; // 用於連線到其所屬uclass的連結串列上
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
};
-
(2)如何定義 在dtb存在的情況下,由uboot解析dtb後動態生成,後續在“uboot DM的初始化”一節中具體說明。
-
(3)存放位置
- 連線到對應uclass中 也就是會連線到uclass->dev_head中
- 連線到父裝置的子裝置連結串列中 也就是會連線到udevice->child_head中,並且最終的根裝置是gd->dm_root這個根裝置。
-
(4)如何獲取、API
- 從uclass中獲取udevice 遍歷uclass->dev_head,獲取對應的udevice。有如下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);
4、driver
和uclass_driver方式是相似的。
- (1)資料結構 include/dm/device.h
struct driver {
char *name; // 驅動名
enum uclass_id id; // 對應的uclass id
const struct udevice_id *of_match; // compatible字串的匹配表,用於和device tree裡面的裝置節點匹配
int (*bind)(struct udevice *dev); // 用於繫結目標裝置到該driver中
int (*probe)(struct udevice *dev); // 用於probe目標裝置,啟用
int (*remove)(struct udevice *dev); // 用於remove目標裝置。禁用
int (*unbind)(struct udevice *dev); // 用於解綁目標裝置到該driver中
int (*ofdata_to_platdata)(struct udevice *dev); // 在probe之前,解析對應udevice的dts節點,轉化成udevice的平臺數據
int (*child_post_bind)(struct udevice *dev); // 如果目標裝置的一個子裝置被繫結之後,呼叫
int (*child_pre_probe)(struct udevice *dev); // 在目標裝置的一個子裝置被probe之前,呼叫
int (*child_post_remove)(struct udevice *dev); // 在目標裝置的一個子裝置被remove之後,呼叫
int priv_auto_alloc_size; //需要分配多少空間作為其udevice的私有資料
int platdata_auto_alloc_size; //需要分配多少空間作為其udevice的平臺數據
int per_child_auto_alloc_size; // 對於目標裝置的每個子裝置需要分配多少空間作為父裝置的私有資料
int per_child_platdata_auto_alloc_size; // 對於目標裝置的每個子裝置需要分配多少空間作為父裝置的平臺數據
const void *ops; /* driver-specific operations */ // 操作集合的指標,提供給uclass使用,沒有規定操作集的格式,由具體uclass決定
uint32_t flags; // 一些標誌位
};
- (2)如何定義 通過U_BOOT_DRIVER來定義一個driver 以s5pv210為例: driver/serial/serial_s5p.c
U_BOOT_DRIVER(serial_s5p) = {
.name = "serial_s5p",
.id = UCLASS_SERIAL,
.of_match = s5p_serial_ids,
.ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
.probe = s5p_serial_probe,
.ops = &s5p_serial_ops,
.flags = DM_FLAG_PRE_RELOC,
};
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)))
關於ll_entry_declare我們在《[uboot] (第六章)uboot流程——命令列模式以及命令處理介紹》已經介紹過了
最終得到如下一個結構體
struct driver _u_boot_list_2_driver_2_serial_s5p= {
.name = "serial_s5p",
.id = UCLASS_SERIAL,
.of_match = s5p_serial_ids,
.ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
.probe = s5p_serial_probe,
.ops = &s5p_serial_ops,
.flags = DM_FLAG_PRE_RELOC,
};
並且存放在.u_boot_list_2_driver_2_serial_s5p段中
- (3)存放位置 通過上述,我們知道serial_s5p的driver結構體_u_boot_list_2_driver_2_serial_s5p被存放在.u_boot_list_2_driver_2_serial_s5p段中。 通過檢視u-boot.map得到如下
.u_boot_list_2_driver_1
0x23e36754 0x0 drivers/built-in.o
.u_boot_list_2_driver_2_gpio_exynos
0x23e36754 0x44 drivers/gpio/built-in.o
0x23e36754 _u_boot_list_2_driver_2_gpio_exynos
.u_boot_list_2_driver_2_root_driver
0x23e36798 0x44 drivers/built-in.o
0x23e36798 _u_boot_list_2_driver_2_root_driver
.u_boot_list_2_driver_2_serial_s5p
0x23e367dc 0x44 drivers/serial/built-in.o
0x23e367dc _u_boot_list_2_driver_2_serial_s5p
.u_boot_list_2_driver_2_simple_bus_drv
0x23e36820 0x44 drivers/built-in.o
0x23e36820 _u_boot_list_2_driver_2_simple_bus_drv
.u_boot_list_2_driver_3
0x23e36864 0x0 drivers/built-in.o
最終,所有driver結構體以列表的形式被放在.u_boot_list_2_driver_1和.u_boot_list_2_driver_3的區間中。 這個列表簡稱driver table。
- (4)如何獲取、API 想要獲取driver需要先獲取driver table。 可以通過以下巨集來獲取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);
// 獲得driver table的長度
接著通過遍歷這個driver table,得到相應的driver。
struct driver *lists_driver_lookup_name(const char *name)
// 從driver table中獲取名字為name的driver。
四、DM的一些API整理
先看一下前面一節理解一下。
1、uclass相關API
int uclass_get(enum uclass_id key, struct uclass **ucp);
// 從gd->uclass_root連結串列獲取對應的uclass
2、uclass_driver相關API
struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// 從uclass_driver table中獲取uclass id為id的uclass_driver。
3、udevice相關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 device_bind(struct udevice *parent, const struct driver *drv,
const char *name, void *platdata, int of_offset,
struct udevice **devp)
// 初始化一個udevice,並將其與其uclass、driver繫結。
int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
const struct driver_info *info, struct udevice **devp)
// 通過name獲取driver並且呼叫device_bind對udevice初始化,並將其與其uclass、driver繫結。
int uclass_bind_device(struct udevice *dev)
// 繫結udevice到其對應的uclass的裝置連結串列中
{
uc = dev->uclass;
list_add_tail(&dev->uclass_node, &uc->dev_head);
}
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp); // 通過索引從uclass中獲取udevice,注意,在獲取的過程中就會對裝置進行probe
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);
4、driver相關API
struct driver *lists_driver_lookup_name(const char *name)
// 從driver table中獲取名字為name的driver。
五、uboot 裝置的表達
1、說明
uboot中可以通過兩種方法來新增裝置
- 通過直接定義平臺裝置(這種方式基本上不使用)
- 通過在裝置樹新增裝置資訊
注意:這裡只是裝置的定義,最終還是會被uboot解析成udevice結構體的。
2、直接定義平臺裝置(這種方式除了根裝置外基本上不使用)
(1)通過U_BOOT_DEVICE巨集來進行定義或者直接定義struct driver_info結構體 (2)U_BOOT_DEVICE巨集以ns16550_serial為例
U_BOOT_DEVICE(overo_uart) = {
"ns16550_serial",
&overo_serial
};
U_BOOT_DEVICE實現如下: 和上述的U_BOOT_DRIVER類似,這裡不詳細說明了
#define U_BOOT_DEVICE(__name) \
ll_entry_declare(struct driver_info, __name, driver_info)
/* Declare a list of devices. The argument is a driver_info[] array */
#define U_BOOT_DEVICES(__name) \
ll_entry_declare_list(struct driver_info, __name, driver_info)
(3)直接定義struct driver_info結構體,以根裝置為例 uboot會建立一個根裝置root,作為所有裝置的祖裝置 root的定義如下:
static const struct driver_info root_info = {
.name = "root_driver",
};
3、在裝置樹新增裝置資訊
在對應的dts檔案中新增相應的裝置節點和資訊,以tiny210的serial為例: arch/arm/dts/s5pv210-tiny210.dts
/dts-v1/;
#include "skeleton.dtsi"
/{
aliases {
console = "/[email protected]";
};
[email protected] {
compatible = "samsung,exynos4210-uart";
reg = <0xe2900000 0x100>;
interrupts = <0 51 0>;
id = <0>;
};
};
dts的內容這裡不多說了。
六、uboot DM的初始化
關於下面可能使用到的一些FDT的API可以參考一下《[uboot] (番外篇)uboot之fdt介紹》。 關於下面可能使用到一些DM的API可以參考一下上述第四節。
1、主要工作
-
DM的初始化
- 建立根裝置root的udevice,存放在gd->dm_root中。 根裝置其實是一個虛擬裝置,主要是為uboot的其他裝置提供一個掛載點。
- 初始化uclass連結串列gd->uclass_root
-
DM中udevice和uclass的解析
- udevice的建立和uclass的建立
- udevice和uclass的繫結
- uclass_driver和uclass的繫結
- driver和udevice的繫結
- 部分driver函式的呼叫
2、入口說明
dm初始化的介面在dm_init_and_scan中。 可以發現在uboot relocate之前的initf_dm和之後的initr_dm都呼叫了這個函式。
static int initf_dm(void)
{
#if defined(CONFIG_DM) && defined(CONFIG_SYS_MALLOC_F_LEN)
int ret;
ret = dm_init_and_scan(true); // 呼叫dm_init_and_scan對DM進行初始化和裝置的解析
if (ret)
return ret;
#endif
return 0;
}
#ifdef CONFIG_DM
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; // 儲存relocate之前的根裝置
gd->dm_root = NULL;
ret = dm_init_and_scan(false); // 呼叫dm_init_and_scan對DM進行初始化和裝置的解析
if (ret)
return ret;
return 0;
}
#endif
主要區別在於引數。 首先說明一下dts節點中的“u-boot,dm-pre-reloc”屬性,當設定了這個屬性時,則表示這個裝置在relocate之前就需要使用。 當dm_init_and_scan的引數為true時,只會對帶有“u-boot,dm-pre-reloc”屬性的節點進行解析。而當引數為false的時候,則會對所有節點都進行解析。 由於“u-boot,dm-pre-reloc”的情況比較少,所以這裡只學習引數為false的情況。也就是initr_dm裡面的dm_init_and_scan(false);。
2、dm_init_and_scan說明
driver/core/root.c
int dm_init_and_scan(bool pre_reloc_only)
{
int ret;
ret = dm_init(); // DM的初始化
if (ret) {
debug("dm_init() failed: %d\n", ret);
return ret;
}
ret = dm_scan_platdata(pre_reloc_only); // 從平臺裝置中解析udevice和uclass
if (ret) {
debug("dm_scan_platdata() failed: %d\n", ret);
return ret;
}
if (CONFIG_IS_ENABLED(OF_CONTROL)) {
ret = dm_scan_fdt(gd->fdt_blob, pre_reloc_only); // 從dtb中解析udevice和uclass
if (ret) {
debug("dm_scan_fdt() failed: %d\n", ret);
return ret;
}
}
ret = dm_scan_other(pre_reloc_only);
if (ret)
return ret;
return 0;
}
3、DM的初始化——dm_init
對應程式碼如下: driver/core/root.c
#define DM_ROOT_NON_CONST (((gd_t *)gd)->dm_root) // 巨集定義根裝置指標gd->dm_root
#define DM_UCLASS_ROOT_NON_CONST (((gd_t *)gd)->uclass_root) // 巨集定義gd->uclass_root,uclass的連結串列
int dm_init(void)
{
int ret;
if (gd->dm_root) {
// 根裝置已經存在,說明DM已經初始化過了
dm_warn("Virtual root driver already exists!\n");
return -EINVAL;
}
INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);
// 初始化uclass連結串列
ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);
// DM_ROOT_NON_CONST是指根裝置udevice,root_info是表示根裝置的裝置資訊
// device_bind_by_name會查詢和裝置資訊匹配的driver,然後建立對應的udevice和uclass並進行繫結,最後放在DM_ROOT_NON_CONST中。
// device_bind_by_name後續我們會進行說明,這裡我們暫時只需要瞭解root根裝置的udevice以及對應的uclass都已經建立完成。
if (ret)
return ret;
#if CONFIG_IS_ENABLED(OF_CONTROL)
DM_ROOT_NON_CONST->of_offset = 0;
#endif
ret = device_probe(DM_ROOT_NON_CONST);
// 對根裝置執行probe操作,
// device_probe後續再進行說明
if (ret)
return ret;
return 0;
}
這裡就完成的DM的初始化了 (1)建立根裝置root的udevice,存放在gd->dm_root中。 (2)初始化uclass連結串列gd->uclass_root
4、從平臺裝置中解析udevice和uclass——dm_scan_platdata
跳過。
5、從dtb中解析udevice和uclass——dm_scan_fdt
關於fdt以及一些對應API請參考《[uboot] (番外篇)uboot之fdt介紹》。 對應程式碼如下(後續我們忽略pre_reloc_only=true的情況): driver/core/root.c
int dm_scan_fdt(const void *blob, bool pre_reloc_only)
// 此時傳進來的引數blob=gd->fdt_blob, pre_reloc_only=0
{
return dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);
// 直接呼叫dm_scan_fdt_node
}
int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset,
bool pre_reloc_only)
// 此時傳進來的引數
// parent=gd->dm_root,表示以root裝置作為父裝置開始解析
// blob=gd->fdt_blob,指定了對應的dtb
// offset=0,從偏移0的節點開始掃描
// pre_reloc_only=0,不只是解析relotion之前的裝置
{
int ret = 0, err;
/* 以下步驟相當於是遍歷每一個dts節點並且呼叫lists_bind_fdt對其進行解析 */
for (offset = fdt_first_subnode(blob, offset);
// 獲得blob裝置樹的offset偏移下的節點的第一個子節點
offset > 0;
offset = fdt_next_subnode(blob, offset)) {
// 迴圈查詢下一個子節點
if (!fdtdec_get_is_enabled(blob, offset)) {
// 判斷節點狀態是否是disable,如果是的話直接忽略
dm_dbg(" - ignoring disabled device\n");
continue;
}
err = lists_bind_fdt(parent, blob, offset, NULL);
// 解析繫結這個節點,dm_scan_fdt的核心,下面具體分析
if (err && !ret) {
ret = err;
debug("%s: ret=%d\n", fdt_get_name(blob, offset, NULL),
ret);
}
}
return ret;
}
lists_bind_fdt是從dtb中解析udevice和uclass的核心。 其具體實現如下: driver/core/lists.c
int lists_bind_fdt(struct udevice *parent, const void *blob, int offset,
struct udevice **devp)
// parent指定了父裝置,通過blob和offset可以獲得對應的裝置的dts節點,對應udevice結構通過devp返回
{
struct driver *driver = ll_entry_start(struct driver, driver);
// 獲取driver table地址
const int n_ents = ll_entry_count(struct driver, driver);
// 獲取driver table長度
const struct udevice_id *id;
struct driver *entry;
struct udevice *dev;
bool found = false;
const char *name;
int result = 0;
int ret = 0;
dm_dbg("bind node %s\n", fdt_get_name(blob, offset, NULL));
// 列印當前解析的節點的名稱
if (devp)
*devp = NULL;
for (entry = driver; entry != driver + n_ents; entry++) {
// 遍歷driver table中的所有driver,具體參考三、4一節
ret = driver_check_compatible(blob, offset, entry->of_match,
&id);
// 判斷driver中的compatibile欄位和dts節點是否匹配
name = fdt_get_name(blob, offset, NULL);
// 獲取節點名稱
if (ret == -ENOENT) {
continue;
} else if (ret == -ENODEV) {
dm_dbg("Device '%s' has no compatible string\n", name);
break;
} else if (ret) {
dm_warn("Device tree error at offset %d\n", offset);
result = ret;
break;
}
dm_dbg(" - found match at '%s'\n", entry->name);
ret = device_bind(parent, entry, name, NULL, offset, &dev);
// 找到對應的driver,呼叫device_bind進行繫結,會在這個函式中建立對應udevice和uclass並切進行繫結,後面繼續說明
if (ret) {
dm_warn("Error binding driver '%s': %d\n", entry->name,
ret);
return ret;
} else {
dev->driver_data = id->data;
found = true;
if (devp)
*devp = dev;
// 將udevice設定到devp指向的地方中,進行返回
}
break;
}
if (!found && !result && ret != -ENODEV) {
dm_dbg("No match for node '%s'\n",
fdt_get_name(blob, offset, NULL));
}
return result;
}
在device_bind中實現了udevice和uclass的建立和繫結以及一些初始化操作,這裡專門學習一下device_bind。 device_bind的實現如下(去除部分程式碼) driver/core/device.c
int device_bind(struct udevice *parent, const struct driver *drv,
const char *name, void *platdata, int of_offset,
struct udevice **devp)
// parent:父裝置
// drv:裝置對應的driver
// name:裝置名稱
// platdata:裝置的平臺數據指標
// of_offset:在dtb中的偏移,即代表了其dts節點
// devp:所建立的udevice的指標,用於返回
{
struct udevice *dev;
struct uclass *uc;
int size, ret = 0;
ret = uclass_get(drv->id, &uc);
// 獲取driver id對應的uclass,如果uclass原先並不存在,那麼會在這裡建立uclass並其uclass_driver進行繫結
dev = calloc(1, sizeof(struct udevice));
// 分配一個udevice
dev->platdata = platdata; // 設定udevice的平臺數據指標
dev->name = name; // 設定udevice的name
dev->of_offset = of_offset; // 設定udevice的dts節點偏移
dev->parent = parent; // 設定udevice的父裝置
dev->driver = drv; // 設定udevice的對應的driver,相當於driver和udevice的繫結
dev->uclass = uc; // 設定udevice的所屬uclass
dev->seq = -1;
dev->req_seq = -1;
if (CONFIG_IS_ENABLED(OF_CONTROL) && CONFIG_IS_ENABLED(DM_SEQ_ALIAS)) {
/*
* Some devices, such as a SPI bus, I2C bus and serial ports
* are numbered using aliases.
*
* This is just a 'requested' sequence, and will be
* resolved (and ->seq updated) when the device is probed.
*/
if (uc->uc_drv->flags & DM_UC_FLAG_SEQ_ALIAS) {
if (uc->uc_drv->name && of_offset != -1) {
fdtdec_get_alias_seq(gd->fdt_blob,
uc->uc_drv->name, of_offset,
&dev->req_seq);
}
// 設定udevice的alias請求序號
}
}
if (!dev->platdata && drv->platdata_auto_alloc_size) {
dev->flags |= DM_FLAG_ALLOC_PDATA;
dev->platdata = calloc(1, drv->platdata_auto_alloc_size);
// 為udevice分配平臺數據的空間,由driver中的platdata_auto_alloc_size決定
}
size = uc->uc_drv->per_device_platdata_auto_alloc_size;
if (size) {
dev->flags |= DM_FLAG_ALLOC_UCLASS_PDATA;
dev->uclass_platdata = calloc(1, size);
// 為udevice分配給其所屬uclass使用的平臺數據的空間,由所屬uclass的driver中的per_device_platdata_auto_alloc_size決定
}
/* put dev into parent's successor list */
if (parent)
list_add_tail(&dev->sibling_node, &parent->child_head);
// 新增到父裝置的子裝置連結串列中
ret = uclass_bind_device(dev);
// uclass和udevice進行繫結,主要是實現了將udevice連結到uclass的裝置連結串列中
/* if we fail to bind we remove device from successors and free it */
if (drv->bind) {
ret = drv->bind(dev);
// 執行udevice對應driver的bind函式
}
if (parent && parent->driver->child_post_bind) {
ret = parent->driver->child_post_bind(dev);
// 執行父裝置的driver的child_post_bind函式
}
if (uc->uc_drv->post_bind) {
ret = uc->uc_drv->post_bind(dev);
if (ret)
goto fail_uclass_post_bind;
// 執行所屬uclass的post_bind函式
}
if (devp)
*devp = dev;
// 將udevice進行返回
dev->flags |= DM_FLAG_BOUND;
// 設定已經繫結的標誌
// 後續可以通過dev->flags & DM_FLAG_ACTIVATED或者device_active巨集來判斷裝置是否已經被啟用
return 0;
上述就完成了dtb的解析,udevice和uclass的建立,以及各個組成部分的繫結關係。 注意,這裡只是繫結,即呼叫了driver的bind函式,但是裝置還沒有真正啟用,也就是還沒有執行裝置的probe函式。
七、DM工作流程
經過前面的DM初始化以及裝置解析之後,我們只是建立了udevice和uclass之間的繫結關係。但是此時udevice還沒有被probe,其對應裝置還沒有被啟用。 啟用一個裝置主要是通過device_probe函式,所以在介紹DM的工作流程前,先說明device_probe函式。
1、device_probe
driver/core/device.c
int device_probe(struct udevice *dev)
{
const struct driver *drv;
int size = 0;
int ret;
int seq;
if (dev->flags & DM_FLAG_ACTIVATED)
return 0;
// 表示這個裝置已經被激活了
drv = dev->driver;
assert(drv);
// 獲取這個裝置對應的driver
/* Allocate private data if requested and not reentered */
if (drv->priv_auto_alloc_size && !dev->priv) {
dev->priv = alloc_priv(drv->priv_auto_alloc_size, drv->flags);
// 為裝置分配私有資料
}
/* Allocate private data if requested and not reentered */
size = dev->uclass->uc_drv->per_device_auto_alloc_size;
if (size && !dev->uclass_priv) {
dev->uclass_priv = calloc(1, size);
// 為裝置所屬uclass分配私有資料
}
// 這裡過濾父裝置的probe
seq = uclass_resolve_seq(dev);
if (seq < 0) {
ret = seq;
goto fail;
}
dev->seq = seq;
dev->flags |= DM_FLAG_ACTIVATED;
// 設定udevice的啟用標誌
ret = uclass_pre_probe_device(dev);
// uclass在probe device之前的一些函式的呼叫
if (drv->ofdata_to_platdata && dev->of_offset >= 0) {
ret = drv->ofdata_to_platdata(dev);
// 呼叫driver中的ofdata_to_platdata將dts資訊轉化為裝置的平臺數據
}
if (drv->probe) {
ret = drv->probe(dev);
// 呼叫driver的probe函式,到這裡裝置才真正激活了
}
ret = uclass_post_probe_device(dev);
return ret;
}
主要工作歸納如下:
- 分配裝置的私有資料
- 對父裝置進行probe
- 執行probe device之前uclass需要呼叫的一些函式
- 呼叫driver的ofdata_to_platdata,將dts資訊轉化為裝置的平臺數據
- 呼叫driver的probe函式
- 執行probe device之後uclass需要呼叫的一些函式
2、通過uclass來獲取一個udevice並且進行probe
通過uclass來獲取一個udevice並且進行probe有如下介面 driver/core/uclass.c
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp) //通過索引從uclass的裝置連結串列中獲取udevice,並且進行probe
int uclass_get_device_by_name(enum uclass_id id, const char *name,
struct udevice **devp) //通過裝置名從uclass的裝置連結串列中獲取udevice,並且進行probe
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp) //通過序號從uclass的裝置連結串列中獲取udevice,並且進行probe
int uclass_get_device_by_of_offset(enum uclass_id id, int node,
struct udevice **devp) //通過dts節點的偏移從uclass的裝置連結串列中獲取udevice,並且進行probe
int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
const char *name, struct udevice **devp) //通過裝置的“phandle”屬性從uclass的裝置連結串列中獲取udevice,並且進行probe
int uclass_first_device(enum uclass_id id, struct udevice **devp) //從uclass的裝置連結串列中獲取第一個udevice,並且進行probe
int uclass_next_device(struct udevice **devp) //從uclass的裝置連結串列中獲取下一個udevice,並且進行probe
這些介面主要是獲取裝置的方法上有所區別,但是probe裝置的方法都是一樣的,都是通過呼叫uclass_get_device_tail->device_probe來probe裝置的。 以uclass_get_device為例
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp)
{
struct udevice *dev;
int ret;
*devp = NULL;
ret = uclass_find_device(id, index, &dev); //通過索引從uclass的裝置連結串列中獲取對應的udevice
return uclass_get_device_tail(dev, ret, devp); // 呼叫uclass_get_device_tail進行裝置的get,最終會呼叫device_probe來對裝置進行probe
}
int uclass_get_device_tail(struct udevice *dev, int ret,
struct udevice **devp)
{
ret = device_probe(dev);
// 呼叫device_probe對裝置進行probe,這個函式在前面說明過了
if (ret)
return ret;
*devp = dev;
return 0;
}
3、工作流程簡單說明
serial-uclass較為簡單,我們以serial-uclass為例
- (0)程式碼支援 < 1 > serial-uclass.c中定義一個uclass_driver
UCLASS_DRIVER(serial) = {
.id = UCLASS_SERIAL, //注意這裡的uclass id
.name = "serial",
.flags = DM_UC_FLAG_SEQ_ALIAS,
.post_probe = serial_post_probe,
.pre_remove = serial_pre_remove,
.per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
};
< 2 > 定義s5pv210的serial的dts節點
[email protected] {
compatible = "samsung,exynos4210-uart"; //注意這裡的compatible
reg = <0xe2900000 0x100>;
interrupts = <0 51 0>;
id = <0>;
};
< 3 > 定義裝置驅動
U_BOOT_DRIVER(serial_s5p) = {
.name = "serial_s5p",
.id = UCLASS_SERIAL, //注意這裡的uclass id
.of_match = s5p_serial_ids,
.ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
.probe = s5p_serial_probe,
.ops = &s5p_serial_ops,
.flags = DM_FLAG_PRE_RELOC,
};
static const struct udevice_id s5p_serial_ids[] = {
{ .compatible = "samsung,exynos4210-uart" }, //注意這裡的compatible
{ }
};
-
(1)udevice和對應uclass的建立 在DM初始化的過程中uboot自己建立對應的udevice和uclass。 具體參考“六、uboot DM的初始化”
-
(2)udevice和對應uclass的繫結 在DM初始化的過程中uboot自己實現將udevice繫結到對應的uclass中。 具體參考“六、uboot DM的初始化”
-
(3)對應udevice的probe 由模組自己實現。例如serial則需要在serial的初始化過程中,選擇需要的udevice進行probe。 serial-uclass只是操作作為console的serial,並不具有通用性,這裡簡單的瞭解下。 程式碼如下,過濾掉無關程式碼 driver/serial/serial-uclass.c
int serial_init(void)
{
serial_find_console_or_panic(); // 呼叫serial_find_console_or_panic進行作為console的serial的初始化
gd->flags |= GD_FLG_SERIAL_READY;
return 0;
}
static void serial_find_console_or_panic(void)
{
const void *blob = gd->fdt_blob;
struct udevice *dev;
int node;
if (CONFIG_IS_ENABLED(OF_CONTROL) && blob) {
/* Check for a chosen console */
// 這裡過濾掉獲取指定的serial的dts節點的程式碼
if (!uclass_get_device_by_of_offset(UCLASS_SERIAL, node,
&dev)) {
// 這裡呼叫uclass_get_device_by_of_offset,通過dts節點的偏移從uclass的裝置連結串列中獲取udevice,並且進行probe。
// 注意,是在這裡完成裝置的probe的!!!
gd->cur_serial_dev = dev;
// 將udevice儲存在gd->cur_serial_dev,後續uclass中可以直接通過gd->cur_serial_dev獲取到對應的裝置並且進行操作
// 但是注意,這種並不是通用做法!!!
return;
}
}
}
- (4)uclass的介面呼叫
- 可以通過先從root_uclass連結串列中提取對應的uclass,然後通過uclass->uclass_driver->ops來進行介面呼叫,這種方法比較具有通用性。
- 可以通過呼叫uclass直接expert的介面,不推薦,但是serial-uclass使用的是這種方式。 這部分應該屬於serial core,但是也放在了serial-uclass.c中實現。 以serial_putc呼叫為例,serial-uclass使用如下:
void serial_putc(char ch)
{
if (gd->cur_serial_dev)
_serial_putc(gd->cur_serial_dev, ch);// 將console對應的serial的udevice作為引數傳入
}
static void _serial_putc(struct udevice *dev, char ch)
{
struct dm_serial_ops *ops = serial_get_ops(dev);// 獲取裝置對應的driver函式的ops操作集
int err;
do {
err = ops->putc(dev, ch); // 以udevice為引數,呼叫ops中對應的操作函式
} while (err == -EAGAIN);
}
到此整個流程簡單介紹到這。
這裡幾乎都是紙上談兵,後續會來一篇gpio-uclass的使用實戰。