目录
一、主要函数接口
1. 设备获取与Uclass 遍历
/**
* uclass_get() - 根据ID获取设备类,如果不存在则创建
*
* 每个设备类都由一个ID标识(0到n-1的整数,n为设备类总数)。
* 此函数允许通过ID查找设备类。
*
* @key: 要查找的ID
* @ucp: 返回设备类指针(每个ID仅有一个)
* @return 成功返回0,失败返回负数错误码
*/
int uclass_get(enum uclass_id key, struct uclass **ucp);
/**
* uclass_get_name() - 获取设备类驱动的名称
*
* @id: 要查找的ID
* @returns 返回对应ID的设备类驱动名称,如果不存在则返回NULL
*/
const char *uclass_get_name(enum uclass_id id);
/**
* uclass_get_by_name() - 通过驱动名称查找设备类
*
* @name: 要查找的名称
* @returns 返回关联的设备类ID,如果未找到则返回UCLASS_INVALID
*/
enum uclass_id uclass_get_by_name(const char *name);
/**
* uclass_get_device() - 根据ID和索引获取设备类中的设备
*
* 设备将被探测以激活,准备使用。
*
* @id: 要查找的ID
* @index: 该设备类中的设备编号(0=第一个)
* @devp: 返回设备指针(每个ID仅有一个)
* @return 成功返回0,失败返回负数错误码
*/
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp);
/**
* uclass_get_device_by_name() - 通过名称获取设备类中的设备
*
* 在设备类中搜索具有完全匹配名称的设备。
*
* 设备将被探测以激活,准备使用。
*
* @id: 要查找的ID
* @name: 要获取的设备名称
* @devp: 返回设备指针(第一个匹配名称的设备)
* @return 成功返回0,失败返回负数错误码
*/
int uclass_get_device_by_name(enum uclass_id id, const char *name,
struct udevice **devp);
/**
* uclass_get_device_by_seq() - 根据ID和序列号获取设备类中的设备
*
* 如果存在活动设备具有此序列号,则返回该设备。如果没有这样的设备,
* 则检查请求此序列号的设备。
*
* 设备将被探测以激活,准备使用。
*
* @id: 要查找的ID
* @seq: 要查找的序列号(0=第一个)
* @devp: 返回设备指针(每个序列号仅有一个)
* @return 成功返回0,失败返回负数错误码
*/
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp);
/**
* uclass_get_device_by_of_offset() - 通过设备树节点偏移量获取设备类中的设备
*
* 在设备类中搜索附加到给定设备树节点的设备。
*
* 设备将被探测以激活,准备使用。
*
* @id: 要查找的ID
* @node: 要搜索的设备树偏移量(如果为负数则返回-ENODEV)
* @devp: 返回设备指针(每个节点仅有一个)
* @return 成功返回0,失败返回负数错误码
*/
int uclass_get_device_by_of_offset(enum uclass_id id, int node,
struct udevice **devp);
/**
* uclass_get_device_by_ofnode() - 通过设备树节点获取设备类中的设备
*
* 在设备类中搜索附加到给定设备树节点的设备。
*
* 设备将被探测以激活,准备使用。
*
* @id: 要查找的ID
* @np: 要搜索的设备树节点(如果为NULL则返回-ENODEV)
* @devp: 返回设备指针(每个节点仅有一个)
* @return 成功返回0,失败返回负数错误码
*/
int uclass_get_device_by_ofnode(enum uclass_id id, ofnode node,
struct udevice **devp);
/**
* uclass_get_device_by_phandle_id() - 通过phandle ID获取设备类中的设备
*
* 在设备类中搜索具有给定phandle ID的设备。
*
* 设备将被探测以激活,准备使用。
*
* @id: 要查找的设备类ID
* @phandle_id: 要查找的phandle ID
* @devp: 返回设备指针(每个节点仅有一个)
* @return 成功返回0,没有匹配的设备返回-ENODEV,其他错误返回负数错误码
*/
int uclass_get_device_by_phandle_id(enum uclass_id id, uint phandle_id,
struct udevice **devp);
/**
* uclass_get_device_by_phandle() - 通过phandle获取设备类中的设备
*
* 在设备类中搜索具有给定phandle的设备。
*
* 设备将被探测以激活,准备使用。
*
* @id: 要查找的设备类ID
* @parent: 包含phandle指针的父设备
* @name: 父设备节点中属性的名称
* @devp: 返回设备指针(每个节点仅有一个)
* @return 成功返回0,节点中不存在@name返回-ENOENT,其他错误返回负数错误码
*/
int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
const char *name, struct udevice **devp);
/**
* uclass_get_device_by_driver() - 获取使用特定驱动的设备类设备
*
* 在设备类中搜索使用给定驱动的设备。对@drv参数使用DM_GET_DRIVER(name),
* 其中'name'是驱动名称(如U_BOOT_DRIVER(name)中使用的名称)。
*
* 设备将被探测以激活,准备使用。
*
* @id: 要查找的ID
* @drv: 要查找的驱动
* @devp: 返回第一个使用该驱动的设备指针
* @return 成功返回0,失败返回负数错误码
*/
int uclass_get_device_by_driver(enum uclass_id id, const struct driver *drv,
struct udevice **devp);
/**
* uclass_first_device() - 获取设备类中的第一个设备
*
* 返回的设备将被探测(如果需要)并准备使用
* 探测失败的设备将被跳过
*
* 此函数可用于开始遍历正常工作且可探测的设备列表
*
* @id: 要查找的设备类ID
* @devp: 如果没有错误,返回该设备类中的第一个设备指针,
* 如果没有可用设备则返回NULL
*/
void uclass_first_device(enum uclass_id id, struct udevice **devp);
/**
* uclass_next_device() - 获取设备类中的下一个设备
*
* 返回的设备将被探测(如果需要)并准备使用
* 探测失败的设备将被跳过
*
* 此函数可用于开始遍历正常工作且可探测的设备列表
*
* @devp: 输入时,指向要查找的设备指针;输出时,返回设备类中的下一个设备指针,
* 如果没有下一个设备则返回NULL
*/
void uclass_next_device(struct udevice **devp);
/**
* uclass_first_device_err() - 获取设备类中的第一个设备(错误版本)
*
* 返回的设备将被探测(如果需要)并准备使用
*
* @id: 要查找的设备类ID
* @devp: 返回该设备类中的第一个设备指针,如果没有则返回NULL
* Return: 找到返回0,未找到返回-ENODEV,其他错误返回负数错误码
*/
int uclass_first_device_err(enum uclass_id id, struct udevice **devp);
/**
* uclass_next_device_err() - 获取设备类中的下一个设备(错误版本)
*
* 返回的设备将被探测(如果需要)并准备使用
*
* @devp: 输入时,指向要查找的设备指针;输出时,返回设备类中的下一个设备指针
* @return 找到返回0,未找到返回-ENODEV,其他错误返回负数错误码
*/
int uclass_next_device_err(struct udevice **devp);
/**
* uclass_first_device_check() - 获取设备类中的第一个设备(检查版本)
*
* 返回的设备将被探测(如果需要)并准备使用
*
* 此函数可用于开始遍历正常工作且可探测的设备列表
*
* @id: 要找到的设备类ID
* @devp: 返回该设备类中的第一个设备指针,如果没有第一个设备则返回NULL
* @return 成功返回0(找到或未找到),其他错误返回负数错误码。
* 即使发生错误,仍可能移动到下一个设备
*/
int uclass_first_device_check(enum uclass_id id, struct udevice **devp);
/**
* uclass_next_device_check() - 获取设备类中的下一个设备(检查版本)
*
* 返回的设备将被探测(如果需要)并准备使用
*
* 此函数可用于开始遍历正常工作且可探测的设备列表
*
* @devp: 输入时,指向要查找的设备指针;输出时,返回设备类中的下一个设备指针(如果有)
* @return 成功返回0(找到或未找到),其他错误返回负数错误码。
* 即使发生错误,仍可能移动到下一个设备
*/
int uclass_next_device_check(struct udevice **devp);
/**
* uclass_first_device_drvdata() - 查找具有给定驱动数据的第一个设备
*
* 在特定设备类中搜索具有给定驱动数据的设备
*
* @id: 要检查的设备类ID
* @driver_data: 要搜索的驱动数据
* @devp: 如果找到,返回该设备类中第一个匹配的设备指针
* @return 找到返回0,未找到返回-ENODEV,其他错误返回负数错误码
*/
int uclass_first_device_drvdata(enum uclass_id id, ulong driver_data,
struct udevice **devp);
/**
* uclass_resolve_seq() - 解析设备的序列号
*
* 输入时dev->seq为-1,dev->req_seq可能为-1(自动分配序列号)或>=0(选择特定编号)。
* 如果请求的序列号已被占用,则将为该设备分配另一个序列号。
*
* 注意:此函数不改变设备的seq值
*
* @dev: 要分配序列号的设备
* @return 分配的序列号,失败返回负数错误码
*/
int uclass_resolve_seq(struct udevice *dev);
2. 设备与驱动操作
/**
* device_probe() - 探测设备并激活
*
* 激活设备使其准备就绪使用。在探测设备前会先探测其所有父设备。
*
* @dev: 要探测的设备指针
* @return: 成功返回0,失败返回负错误码
*/
int device_probe(struct udevice *dev);
/**
* device_remove() - 移除设备并停用
*
* 停用设备使其不再可用。移除设备前会先停用其所有子设备。
*
* @dev: 要移除的设备指针
* @flags: 选择性设备移除标志 (DM_REMOVE_...)
* @return: 成功返回0,失败返回负错误码(此处出错通常表示严重问题)
*/
int device_remove(struct udevice *dev, uint flags);
/**
* device_bind() - 创建设备并绑定驱动
*
* 调用此函数创建并关联到驱动的设备。设备将包含平台数据(platdata),
* 或使用设备树节点创建平台数据。
*
* 绑定后设备存在但尚未激活,需调用device_probe()激活。
*
* @parent: 设备父设备指针,新设备将在此父设备下创建
* @drv: 设备的驱动指针
* @name: 设备名称(如设备树节点名)
* @platdata: 设备数据指针 - 结构体类型取决于设备,可能包含I/O地址等。
* 对于使用设备树的设备,此参数为NULL
* @of_offset: 设备树节点偏移量。对于不使用设备树的设备,此值为-1
* @devp: 非NULL时返回绑定设备的指针
* @return: 成功返回0,失败返回负错误码
*/
int device_bind(struct udevice *parent, const struct driver *drv,
const char *name, void *platdata, int of_offset,
struct udevice **devp);
/**
* device_unbind() - 解绑设备并销毁
*
* 解绑设备并释放其所有占用的内存资源
*
* @dev: 要解绑的设备指针
* @return: 成功返回0,失败返回负错误码
*/
int device_unbind(struct udevice *dev);
3. 设备树属性访问 (驱动中常用)
/**
* dev_read_addr() - 获取设备的 reg 属性值
*
* 从设备树中读取设备的 reg 属性(寄存器地址)
*
* @dev: 要读取的设备指针
*
* @return: 返回地址值,若未找到则返回 FDT_ADDR_T_NONE
*/
fdt_addr_t dev_read_addr(struct udevice *dev);
/**
* dev_read_addr_ptr() - 获取设备的 reg 属性指针
*
* 将设备的 reg 属性值转换为指针形式返回
*
* @dev: 要读取的设备指针
*
* @return: 返回地址指针,若未找到则返回 NULL
*/
void *dev_read_addr_ptr(struct udevice *dev);
/**
* dev_read_size() - 读取属性的字节大小
*
* 获取指定属性在设备树中的数据长度
*
* @dev: 要检查的设备
* @propname: 要检查的属性名称
* @return: 属性存在时返回其字节大小,不存在则返回 -EINVAL
*/
int dev_read_size(struct udevice *dev, const char *propname);
/**
* dev_read_bool() - 读取布尔属性值
*
* 检查设备树中是否存在指定属性(布尔值属性)
*
* @dev: 要读取的设备
* @propname: 属性名称
* @return: 属性存在返回 true,不存在返回 false
*/
bool dev_read_bool(struct udevice *dev, const char *propname);
/**
* dev_read_string() - 读取字符串属性值
*
* 从设备树中读取指定属性的字符串值
*
* @dev: 要读取的设备
* @propname: 属性名称
* @return: 返回字符串指针,属性不存在则返回 NULL
*/
const char *dev_read_string(struct udevice *dev, const char *propname);
/**
* dev_read_u32_array() - 读取32位整数数组
*
* 从设备树属性中读取指定数量的32位整数值
*
* 注意:只有当成功解码有效值时才会修改 out_values
*
* @dev: 要查找的设备
* @propname: 要读取的属性名称
* @out_values: 返回值指针(仅在返回值为0时被修改)
* @sz: 要读取的数组元素数量
* @return:
* 0 - 成功
* -EINVAL - 属性不存在
* -ENODATA - 属性无值
* -EOVERFLOW - 属性数据不足
*/
int dev_read_u32_array(struct udevice *dev, const char *propname,
u32 *out_values, size_t sz);
/**
* dev_read_phandle() - 获取设备的 phandle 值
*
* 从设备树中读取设备的 phandle 标识符
*
* @dev: 要检查的设备
* @return: 返回 phandle 值(≥1),若不存在或出错则返回 0
*/
int dev_read_phandle(struct udevice *dev);
二、实战:开发一个 DM 驱动
场景:实现一个简单的 LED 驱动
假设我们有一个 GPIO 控制的 LED,设备树描述如下:
leds {
compatible = "gpio-leds";
red {
label = "status:red";
gpios = <&gpio 12 GPIO_ACTIVE_HIGH>;
};
};
步骤 1:定义设备类
// 修改include/dm/uclass-id.h中的enum uclass_id
//末尾添加 UCLASS_GPIO_LED
步骤 2:实现驱动
// drivers/led/gpio-led.c
#include <dm.h>
#include <led.h>
#include <asm/gpio.h>
struct gpio_led_priv {
struct gpio_desc gpio; // GPIO描述符
const char *label; // LED标签
};
// 设备树匹配表
static const struct udevice_id gpio_led_ids[] = {
{ .compatible = "gpio-leds" },
{ }
};
// 操作函数
static int gpio_led_set_state(struct udevice *dev, enum led_state_t state)
{
struct gpio_led_priv *priv = dev_get_priv(dev);
switch (state) {
case LEDST_ON:
dm_gpio_set_value(&priv->gpio, 1);
break;
case LEDST_OFF:
dm_gpio_set_value(&priv->gpio, 0);
break;
// ...其他状态
}
return 0;
}
static const struct led_ops gpio_led_ops = {
.set_state = gpio_led_set_state,
};
// 探测函数
static int gpio_led_probe(struct udevice *dev)
{
struct gpio_led_priv *priv = dev_get_priv(dev);
int ret;
// 1. 从设备树获取GPIO
ret = gpio_request_by_name(dev, "gpios", 0, &priv->gpio, GPIOD_IS_OUT);
if (ret)
return ret;
// 2. 读取标签
priv->label = dev_read_string(dev, "label");
return 0;
}
// 驱动定义
U_BOOT_DRIVER(gpio_led_drv) = {
.name = "gpio_led",
.id = UCLASS_GPIO_LED,
.of_match = gpio_led_ids,
.probe = gpio_led_probe,
.priv_auto = sizeof(struct gpio_led_priv),
.ops = &gpio_led_ops,
};
步骤 3:测试驱动
# 在U-Boot命令行
=> dm tree
Class Index Probed Driver Name
-----------------------------------------------------------
root 0 Y root_driver root_bus
gpio 0 Y bcm2835_gpio gpio@7e200000
gpio_led 0 Y gpio_led leds
gpio_led 1 Y gpio_led red
三、DM 与 Linux 设备模型对比
特性 | U-Boot DM | Linux 设备模型 | 说明 |
---|---|---|---|
目标 | 引导阶段设备初始化 | 全功能设备管理 | U-Boot 更轻量 |
内存模型 | 静态分配(无动态内存) | 动态分配 | U-Boot 避免内存碎片 |
设备树使用 | 必需 | 必需(ARM)/可选(x86) | 两者深度依赖设备树 |
设备类 | 100+ 个预定义类 | 数百个 | U-Boot 聚焦引导关键设备 |
热插拔支持 | 有限 | 完整 | U-Boot 不需要完整热插拔 |
电源管理 | 基本支持 | 完整支持 | U-Boot 需求较简单 |
驱动加载 | 编译时静态链接 | 动态加载(模块) | U-Boot 无文件系统 |
✅ 优势:U-Boot DM 保留了 Linux 设备模型的核心思想,但针对资源受限的引导环境进行了优化,减少了约 40% 的内存占用。
四、高级技巧与最佳实践
1. 设备操作技巧
// 获取设备
struct udevice *dev;
uclass_get_device_by_name(UCLASS_I2C, "i2c@7e205000", &dev);
// 获取设备私有数据
struct i2c_priv *priv = dev_get_priv(dev);
// 调用设备操作
struct dm_i2c_ops *ops = i2c_get_ops(dev);
ops->xfer(dev, ...);
// 设备树属性读取
u32 reg;
dev_read_u32(dev, "reg", ®);
2. 调试技巧
- 查看设备树:
fdt addr $fdtcontroladdr; fdt print /
- 查看设备树:
dm tree -t
(显示设备树关系) - 查看设备详情:
dm uclass
- 跟踪初始化:
CONFIG_DEBUG_DEVRES=y
(显示设备资源分配)
3. 避免常见错误
错误 | 解决方案 |
---|---|
设备未初始化 | 检查设备树 compatible 属性是否匹配 |
父设备未就绪 | 确保父设备(如总线)先初始化 |
设备树属性缺失 | 使用 dev_read_*_default() 提供默认值 |
内存越界 | 通过 priv_auto 自动分配私有数据空间 |
重复定义设备 | 检查设备树节点是否唯一 |
五、DM 的演进与未来
1. 当前状态(U-Boot 2023+)
- 100% 主流设备支持:串口、MMC、网络、USB 等核心子系统。
- 设备树覆盖(Overlay):支持运行时修改设备树。
- 电源域管理:精细控制设备电源状态。
- 固件抽象层:通过
struct udevice *firmware
访问固件服务。
2. 未来发展方向
- 更细粒度的电源管理:针对低功耗场景优化。
- 安全启动集成:设备验证与安全初始化。
- 虚拟化支持:为 hypervisor 提供设备抽象。
- 性能优化:减少启动延迟,加速设备初始化。
六、学习资源推荐
1. 官方文档
- U-Boot Driver Model 文档:
uboot/doc/driver-model/
- 设备树绑定文档:
uboot/doc/device-tree-bindings
2. 代码示例
- 核心实现:
drivers/core/
目录 - 设备类定义:
include/dm/uclass-id.h
- 参考驱动:
- 串口:
drivers/serial/serial-uclass.c
- MMC:
drivers/mmc/mmc-uclass.c
- I2C:
drivers/i2c/i2c-uclass.c
- 串口:
3. 实践建议
- 从简单驱动开始(如 LED、GPIO)。
- 使用
dm tree
命令观察实际设备树。 - 尝试修改设备树并观察启动变化。
- 参与 U-Boot 社区,提交简单补丁。
4. 总结
U-Boot Driver Model 是嵌入式引导加载程序的一次重大架构革新,它通过:
- 设备树驱动:实现硬件描述与代码分离。
- 动态初始化:按需加载设备,减少启动时间。
- 标准化接口:提高代码复用率和可维护性。
- 层次化设计:清晰表达设备间关系。
掌握 DM 的关键:
- 理解
udriver
、udevice
、uclass
三者关系。 - 熟悉设备树与驱动的匹配机制。
- 掌握从设备树读取配置的方法。
- 学会使用
dm
命令进行调试。
💡 最后建议:最好的学习方式是动手实践!尝试在您的开发板上:
- 添加一个简单的 LED 设备树节点。
- 实现一个 DM 驱动。
- 通过 U-Boot 命令控制 LED。
您将很快掌握这一强大工具。如有问题,U-Boot 邮件列表和 IRC 频道(#u-boot on Libera)都是很好的求助渠道。祝您学习顺利!如需进一步探讨具体问题,欢迎随时提问。