U-BOOT DM驱动模型

U-BOOT DM驱动模型

Before driver model

  • U-Boot has 10 useful design principles (e.g. small, fast, simple, portable)
    • Huge community, over 1000 boards supported by the end of 2011
    • But Ad-hoc driver model started to bite
  • Drivers were invoked through direct C calls
    • i2c_read() is implemented by whichever driver is compiled in
    • CONFIG option select which I2C driver to use, clock speed, bus number, etc.
  • Hard to scale
    • Multiple I2C drivers must be munged into a single driver
    • Or an ad-hoc framework created to handle this requirements
  • Configuration becoming unwieldy
    • 6000 CONFIG options at its peak
    • Kconfig conversion helps, but that’s still a lot of options

dm-u-boot.pdf

U-Boot Mini Summit talk on driver model at ELCE 2014

Why driver model?

  • Device init and access is ad-hoc
    – scsi_init(), mmc_init(), nand_init()
  • Many subsystems only allow one driver
    – But I have USB2 and USB3!
  • Communication between subsystems is tricky
    – How does an I2C expander or PMIC provide a GPIO?
  • Hard to answer simple questions
    – How many GPIOs? What is my serial console?
  • Board file functions provide the glue
    – What GPIO provides my MMC card detect?

Architecture 体系结构

DM (Driver Model)是 U-Boot 中的驱动框架。

  • udevice 描述具体的某一个硬件设备。

    • Instance of a driver
    • Created from some platform-specific information bound to a driver
  • driver 是与这个设备匹配的驱动。

    • Code to talk to a peripheral type (e.g. Ethernet switch, I2C controller, LCD)
  • uclass 是同一类设备的抽象,提供管理同一类设备的抽象接口。

    • A way of grouping devices which operate the same way
  • Device tree and hierarchy

    • 设备树和层次结构
  • Memory allocation

  • Sequence numbers

    • 序列号

像 Kernel 中的驱动三要素 device 、bus 、driver 一样,DM 也有自己的三要素:udevice、uclass、driver。以 serial 驱动为例:

Device tree configuration

Device sequence

Automatic memory allocation

Architecture 2

  • Binding and probing
    • Bing creates the device but does not touch hardware
    • Probing activates the device ready for use
  • Avoid private data structures
    • Everything out in the open
  • SPL support
    • fdtgrep
    • Simple malloc()
    • Drop device removal code, warnings, etc.

Driver model benefits (en)

  • Consistent view of devices regardless of their type
  • Multiple driver can be used with the same subsystem
    • Drivers can be created which use others driver for their transport layer
  • The lifecycle of a device is clear and consistent
  • Devices can be bound automatically
    • Then probed automatically when used
  • Supports device tree for configuration
    • Thus sharing this with Linux and potentially other projects
    • Avoids recreating the same information again in a different format

Driver model benefits (zh)

  • 一致的设备视图,无论其类型如何
  • 同一个子系统可以使用多个驱动程序
    • 可以创建使用其他驱动程序作为传输层的驱动程序
  • 设备生命周期清晰一致
  • 设备可自动绑定
    • 然后使用时自动探测
  • 支持设备树配置
    • 因此与 Linux 和潜在的其他项目共享这个
    • 避免以不同的格式重新创建相同的信息

DM Limitations

  • A driver can be in only one uclass
    • Multi-function devices must use separate child devices
  • Uses flattened device tree (FDT,即 device tree, dts)
    • Driver model uses the device tree offset
    • Overlays and other mutations are not supported

Comparisons with Linux

  • Classes

  • Buses

  • Binding and probing

  • Memory allocation

  • Relocation and SPL //重定位 & SPL

  • device visibility

  • Locking

  • Data structure size

    • Core structure sizes are moderate
      • struct uclass
      • struct udevice
      • struct driver
  • automatic memory allocation

A few examples

Example 1: Requesting GPIOs

Example 2: Enabling power

重要数据结构

udevice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 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).
*
* A device will come into existence through a 'bind' call, either due to
* a U_BOOT_DEVICE() macro (in which case platdata is non-NULL) or a node
* in the device tree (in which case of_offset is >= 0). In the latter case
* we translate the device tree information into platdata in a function
* implemented by the driver ofdata_to_platdata method (called just before the
* probe method if the device has a device tree node.
*
* All three of platdata, priv and uclass_priv can be allocated by the
* driver, or you can use the auto_alloc_size members of struct driver and
* struct uclass_driver to have driver model do this automatically.
*
* @driver: The driver used by this device
* @name: Name of device, typically the FDT node name
* @platdata: Configuration data for this device
* @parent_platdata: The parent bus's configuration data for this device
* @uclass_platdata: The uclass's configuration data for this device
* @node: Reference to device tree node for this device
* @driver_data: Driver data word for the entry that matched this device with
* its driver
* @parent: Parent of this device, or NULL for the top level device
* @priv: Private data for this device
* @uclass: Pointer to uclass for this device
* @uclass_priv: The uclass's private data for this device
* @parent_priv: The parent's private data for this device
* @uclass_node: Used by uclass to link its devices
* @child_head: List of children of this device
* @sibling_node: Next device in list of all devices
* @flags: Flags for this device DM_FLAG_...
* @req_seq: Requested sequence number for this device (-1 = any)
* @seq: Allocated sequence number for this device (-1 = none). This is set up
* when the device is probed and will be unique within the device's uclass.
* @devres_head: List of memory allocations associated with this device.
* When CONFIG_DEVRES is enabled, devm_kmalloc() and friends will
* add to this list. Memory so-allocated will be freed
* automatically when the device is removed / unbound
*/
struct udevice {
const struct driver *driver;
const char *name;
void *platdata;
void *parent_platdata;
void *uclass_platdata;
ofnode node;
ulong driver_data;
struct udevice *parent;
void *priv;
struct 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
};

有三种途径生成一个 udevice:

  1. dts 设备节点

  2. UBOOTDEVICE(__name) 宏申明

  3. 调用 ‘bind’ API, device_bind_xxx

Device Model Start-up sequence

DM 启动顺序, core/root.c

  • dm_init_and_scan():
  • dm_init()
    • Creates an empty list of devices and uclasses
    • Binds and probes a root device
  • dm_scan_platdata()
    • Scans available platform data looking devices to be created
    • Platform data may only be used when memory constrains prohibit device tree
  • dm_scan_fdt()
    • Scan device tree and bind driver to nodes to create devices

根据当前 U-Boot 的编程哲学,基本大部分设备都是通过 dts 来描述,还有少部分设备因为特殊原因,可以通过 U_BOOT_DEVICE(_name) 宏申明。

匹配过程

在UBoot DM 初始化阶段(initfdm 和 initrdm),通过调用 dm_init_and_scan(boolpre_reloc_only) 根据名称 (UBOOT_DEVICE 中和 driver 的 name,或者 dts 和 driver 的 compatible) 匹配到对应的 driver,

然后调用 device_bind_common 函数生成 udevice,udevice 会和 driver 绑定,

并根据 driver 中的uclass id 找到对应的 uclass driver,并生成相应的 uclass, 并把该设备挂到 uclass 的设备节点之下。

最后调用 driver 的 bind 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| -> dm_init_and_scan
| -> dm_init
| -> INIT_LIST_HEAD //创建 root 节点
| -> device_bind_by_name()
| -> lists_driver_lookup_name //lists_driver_lookup_name("root_driver"), This function returns a pointer a driver given its name.
| -> ll_entry_start //Point to first entry of linker-generated array
| -> ll_entry_count //Return the number of elements in linker-generated array
| -> for { strcmp } //通过名字来遍历,得到匹配的 driver
| -> device_bind_common
| -> uclass_get
| -> uclass_find //Find uclass by uclass_id
| -> list_add_tail //put dev into parent's successor list
| -> uclass_bind_device
| -> device_probe
| ->
| -> dm_scan_platdata
| -> dm_extended_scan_fdt
| -> dm_scan_other

还有部分特殊的驱动,他们并不存在实际意义上的设备,比如 MMC 子系统中的 mmcblk 驱动,

该驱动主要是把所有的 mmc 设备注册到更上一层的 blk 子系统中,向 blk 层提供操作 mmc 设备的 blkops,向下通过mmc uclass 提供的统一接口控制 mmc 设备。显然,这个驱动位于抽象层,它不和具体的硬件设备直接交互,并不适合用一个 dts(dts 是用来描述具体的硬件信息的) 节点或者 UBOOTDEVICE(_name) 宏来为这个驱动显示的申明设备。这种情形下一般通过主动调用 device_bind_xxx 系列 API 来完成驱动和设备已经更上一层 uclass 之间的 bind。

A:生成 udevice。
B:绑定 udevice 和 driver。
C:把设备挂到 uclass 的dev_head 链表下。
D:调用设备驱动的 bind 接口。

uclass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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;
struct uclass_driver *uc_drv;
struct list_head dev_head;
struct list_head sibling_node;
};

这里主要的成员是 uclassdriver 和 devhead 链表。

  • dev_head 是一个链表头, 用来链接该类下的所有设备。可以通过 uclass_foreach_dev(dev,uc) 遍历该class 下的所有设备。
  • uclass_driver 是针对某一类设备提供的通用操作接口,然后通过 udevice->driver->ops 操作到具体的硬件设备。
    uclassdriver 通过 UCLASSDRIVER(name) 宏申明, 在 device_bind_common 中根据 设备对应的驱动 driver 中的 uclass id 找到 uclassdriver,并生成相应的 uclass, 并把设备挂到该 uclass 的设备节点 dev_head 下。

以 pwm backlight 为例:

通过 UBOOTDRIVER 的 id 可以看出,该设备(pwm backlight)驱动属于 UCLASSPANELBACKLIGHT 类。

这里定义了 backlight 的 UCLASS_DRIVER。该 uclass driver 提供了 backlight_enable(struct udevice*dev) 和 backlight_set_brightness(struct udevice*dev,intpercent) 两个通用的 API 供应用调用,可以看到他们都需要传递对应设备的 udevice ,然后通过 backlight_get_ops(dev) 拿到对该设备的操作接口。

#define backlight_get_ops(dev) ((struct backlight_ops *)(dev)->driver->ops)

driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* struct driver - A driver for a feature or peripheral
*
* This holds methods for setting up a new device, and also removing it.
* The device needs information to set itself up - this is provided either
* by platdata or a device tree node (which we find by looking up
* matching compatible strings with of_match).
*
* Drivers all belong to a uclass, representing a class of devices of the
* same type. Common elements of the drivers can be implemented in the uclass,
* or the uclass can provide a consistent interface to the drivers within
* it.
*
* @name: Device name
* @id: Identifies the uclass we belong to
* @of_match: List of compatible strings to match, and any identifying data
* for each.
* @bind: Called to bind a device to its driver
* @probe: Called to probe a device, i.e. activate it
* @remove: Called to remove a device, i.e. de-activate it
* @unbind: Called to unbind a device from its driver
* @ofdata_to_platdata: Called before probe to decode device tree data
* @child_post_bind: Called after a new child has been bound
* @child_pre_probe: Called before a child device is probed. The device has
* memory allocated but it has not yet been probed.
* @child_post_remove: Called after a child device is removed. The device
* has memory allocated but its device_remove() method has been called.
* @priv_auto_alloc_size: If non-zero this is the size of the private data
* to be allocated in the device's ->priv pointer. If zero, then the driver
* is responsible for allocating any data required.
* @platdata_auto_alloc_size: If non-zero this is the size of the
* platform data to be allocated in the device's ->platdata pointer.
* This is typically only useful for device-tree-aware drivers (those with
* an of_match), since drivers which use platdata will have the data
* provided in the U_BOOT_DEVICE() instantiation.
* @per_child_auto_alloc_size: Each device can hold private data owned by
* its parent. If required this will be automatically allocated if this
* value is non-zero.
* @per_child_platdata_auto_alloc_size: A bus likes to store information about
* its children. If non-zero this is the size of this data, to be allocated
* in the child's parent_platdata pointer.
* @ops: Driver-specific operations. This is typically a list of function
* pointers defined by the driver, to implement driver functions required by
* the uclass.
* @flags: driver flags - see DM_FLAGS_...
*/
struct driver {
char *name;
enum uclass_id 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;
};

通过 UBOOTDRIVER(__name) 宏声明。如果 driver 实现了 bind 接口,该bind 将在 device_bind_common 中 device 和 driver 匹配上后被调用, 而且在 device_bind_common 中会完成 udevice 和 driver 的绑定。

driver 一般都有对应的 probe 接口,通过 device_probe(structudevice*dev) 调用,需要注意的是driver 的 bind 接口调用的比 probe 接口早, 大部分在 dm_init_and_scan 中就被调用了。

driver 一般会提供 ops 操作接口,供上一层调用。

需要说明的是,driver 一般都不需要把自己注册到 uclass 中,而是在 device_bind _common 阶段实现driver 、uclass、device 三者的对接,然后 uclass 层通过 udevice->driver->ops 获取对应 driver 的操作接口。

设备驱动的使用

一般应用层的代码要使用某个设备的时候,首先需要通过 uclass_get_device_xxx 系列 API 拿到该设备的 udevice, 然后通过该设备的 uclass 提供的 API 操作该设备。

uclass_get_device_xxx 拿到该设备的 udevice 后会调用该设备的 probe 接口。

以前面提到的 pwm backlight 为例:

1
2
3
4
5
6
7
/**
* drivers/video/simple_panel.c
*/
struct udevice *bldev;
uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev, "backlight", &bldev);
backlight_enable(bldev);
backlight_set_brightness(bldev, percent);