uboot的驱动模型-DM(二)

一、主要函数接口

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 DMLinux 设备模型说明
目标引导阶段设备初始化全功能设备管理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", &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. 未来发展方向

  1. 更细粒度的电源管理:针对低功耗场景优化。
  2. 安全启动集成:设备验证与安全初始化。
  3. 虚拟化支持:为 hypervisor 提供设备抽象。
  4. 性能优化:减少启动延迟,加速设备初始化。

六、学习资源推荐

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. 实践建议

  1. 从简单驱动开始(如 LED、GPIO)。
  2. 使用 dm tree 命令观察实际设备树。
  3. 尝试修改设备树并观察启动变化。
  4. 参与 U-Boot 社区,提交简单补丁。

4. 总结

U-Boot Driver Model 是嵌入式引导加载程序的一次重大架构革新,它通过:

  • 设备树驱动:实现硬件描述与代码分离。
  • 动态初始化:按需加载设备,减少启动时间。
  • 标准化接口:提高代码复用率和可维护性。
  • 层次化设计:清晰表达设备间关系。

掌握 DM 的关键:

  1. 理解 udriverudeviceuclass 三者关系。
  2. 熟悉设备树与驱动的匹配机制。
  3. 掌握从设备树读取配置的方法。
  4. 学会使用 dm 命令进行调试。

💡 最后建议:最好的学习方式是动手实践!尝试在您的开发板上:

  1. 添加一个简单的 LED 设备树节点。
  2. 实现一个 DM 驱动。
  3. 通过 U-Boot 命令控制 LED。

您将很快掌握这一强大工具。如有问题,U-Boot 邮件列表和 IRC 频道(#u-boot on Libera)都是很好的求助渠道。祝您学习顺利!如需进一步探讨具体问题,欢迎随时提问。

### U-Boot 中 DM-SPL 的实现原理 在 U-Boot 中,`dm-spl` 配置用于指定设备模型 (Device Model, DM) 在 Secondary Program Loader (SPL) 和 U-Boot pre-relocation 阶段的应用[^1]。此配置项确保了 SPL 能够访问并初始化必要的硬件资源,在早期启动过程中提供支持。 #### 设备树节点定义 为了使能 `dm-spl` 功能,需要在设备树源文件 (.dts) 中适当位置添加如下属性: ```dts chosen { bootargs = "console=ttyS0,115200"; u-boot,dm-spl; }; ``` 上述代码片段展示了如何通过设置 `u-boot,dm-spl` 属性来启用该功能。这使得平台能够在 SPL 阶段利用完整的设备管理框架进行驱动加载和资源配置。 #### 实现机制解析 当设置了 `u-boot,dm-spl` 之后,U-Boot 将会在 SPL 初始化期间调用特定函数以构建初始环境,并完成对关键外设的支持。具体来说: - **初始化阶段**:读取存储介质中的镜像数据至内存; - **设备枚举**:依据设备树描述自动探测连接在外围总线上的组件; - **驱动绑定**:为已识别的硬件分配相应的驱动程序实例; 这些操作共同构成了一个稳健可靠的低级运行时环境,从而保障后续更高层次软件模块可以顺利接管控制权。 #### 关键代码示例 以下是部分涉及 `dm-spl` 处理逻辑的关键 C 语言代码摘录: ```c // 文件路径: drivers/core/init.c int dm_init(void) { #ifdef CONFIG_SPL_BUILD /* 如果是在编译 SPL,则仅处理标记有 'spl' 或者 'tpl' 的节点 */ if (!IS_ENABLED(CONFIG_DM_TPL)) return dm_scan_fdt_spl(gd->fdt_blob); #endif } ``` 这段代码表明,在 SPL 构建环境下 (`CONFIG_SPL_BUILD`) 并且未开启 TPL 支持的情况下,会专门扫描带有 `spl` 或 `tpl` 标记的 FDT (Flattened Device Tree) 节点来进行有限度的初始化工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小嵌同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值