En abril de 2014, U-Boot se refirió al diseño del modelo de controlador Linux Kernel e introdujo su propia arquitectura de controlador Modelo de controlador (oficialmente denominado DM). Este modelo de controlador (DM) proporciona un método unificado para la definición del controlador y la interfaz de acceso, lo que mejora la compatibilidad entre los controladores y la estandarización del acceso.
Todos los códigos involucrados en este artículo están colocados en mi Github personal: https://github.com/ZCShou/BOARD-STM32F769I-EVAL, puede usarlo directamente para aprender y verificar, para evitar la miopía y la falta de mano. El código fuente involucrado en este artículo se usa principalmente U-Boot-v2022.10
, ¡y el código fuente de diferentes versiones puede ser bastante diferente! ! !
configuración
La arquitectura DM debe CONFIG_DM=y
habilitarse , y los controladores periféricos reales correspondientes deben CONFIG_DM_xxx
habilitarse a través de habilitar. Entre ellos, xxx indica un dispositivo periférico específico. Por ejemplo, habilitar CONFIG_DM_SERIAL
agregará automáticamente el archivo de código fuente correspondiente en el Makefile:
En la actualidad, los controladores de la mayoría de los dispositivos se han migrado completamente a la arquitectura DM. Por lo tanto, en el código fuente real , A menudo podemos ver que CONFIG_DM_xxx
la interfaz del controlador correspondiente está implementada, pero la versión anterior no está implementada o no tiene la interfaz del controlador anterior (algunos controladores todavía están en el modo de controlador anterior).
opciones de enlace
El controlador DM se almacenará uniformemente en el archivo de imagen final después de la compilación, y el controlador de arquitectura DM de cada dispositivo se colocará en una sección separada durante la compilación. Cuando compilamos U-Boot, todos estos controladores __u_boot_list_2_"#_list"_2_"#_name
usarán el área de la sección como nombre. Además, estas áreas de la sección se envolverán__u_boot_list_2_"#_list"_1
con y , para que se pueda calcular el tamaño de todos. Esta información se puede ver :__u_boot_list_2_"#_list"_3
__u_boot_list_2_*
u-boot.map
De hecho, no solo el controlador, sino también otras partes (por ejemplo, cmd) se manejan de esta manera.
La clave para la implementación en el código se encuentra en los valores de macro de UCLASS_DRIVER(__name)
, U_BOOT_DRIVER(__name)
etc. Estos valores de macro eventualmente se referirán a las macros relevantes ./include/linker_lists.h
en ll_entry_declare
, que es la clave para la implementación.
Durante el proceso de inicialización, U-Boot atravesará las secciones anteriores, luego realizará la coincidencia de contenido y creará varios dispositivos y la UCLASS correspondiente a su vez. El siguiente es el método para encontrar el controlador especificado según el nombre del controlador:
Árbol de dispositivos/datos de plataforma
El controlador debe conocer la información básica del hardware.U-Boot admite dos métodos para proporcionar información básica de configuración del hardware, Datos de la plataforma (datos de la plataforma, a menudo denominados plat
o platdata
) y Árbol de dispositivos aplanado (árbol de dispositivos, al que a menudo se hace referencia en código ). fdt
Entre ellos, los datos de la plataforma son la forma antigua y el árbol de dispositivos es la forma estándar.
Datos de la plataforma
Los datos de la plataforma pasan información de configuración específica de la plataforma (dirección de registro, velocidad del bus, etc.) al conductor a través de una estructura C, y las definiciones relevantes se encuentran en ./include/dm/platdata.h
el archivo Después de inicializar el DM (en realidad, después de la sonda), la información del dispositivo finalmente se almacena en la memoria udevice ->plat_
a la que apunta y el conductor puede dev->plat_
acceder . El funcionario señaló que a menos que exista una razón necesaria, no utilice el método de datos de la plataforma, sino el método del árbol de dispositivos.
static const struct dm_demo_pdata red_square = {
.colour = "red",
.sides = 4.
};
/* 直接定义(不推荐) */
static const struct driver_info info[] = {
{
.name = "demo_shape_drv",
.plat = &red_square,
},
};
demo1 = driver_bind(root, &info[0]);
/* 使用 U_BOOT_DRVINFO 宏(推荐) */
U_BOOT_DRVINFO(demo0) = {
.name = "demo_shape_drv",
.plat = &red_square,
};
La recomendación oficial es usar Platform Data solo cuando las limitaciones de memoria no permitan el uso de un árbol de dispositivos. Además, U-Boot proporciona un método para convertir automáticamente el árbol de dispositivos en datos de plataforma, es decir, of-platdata
características . Sin embargo, of-platdata
solo está disponible en SPL/TPL
la fase .
Árbol de dispositivos
El árbol de dispositivos proporciona un método más flexible para proporcionar datos de dispositivos y la recomendación oficial es utilizar el método de árbol de dispositivos. U-Boot analizará automáticamente el árbol de dispositivos para obtener información relevante del dispositivo. Estos datos de información se denominan ofdata
(abreviatura de datos de Open-Firmware) y la estructura relacionada con el árbol de dispositivos (parte del código) se ./include/dm/of.h
define of
en el archivo que comienza con etc. Después de inicializar el DM (en realidad, después de la sonda), la información del dispositivo finalmente se almacena en la memoria udevice ->plat_
a la que apunta (específicamente a través driver
de los miembrosplat_auto
y en ). El árbol de dispositivos de U-Boot proviene del Kernel de Linux. De acuerdo con las instrucciones oficiales, para mantener la compatibilidad sin realizar cambios adicionales en los archivos fuente del árbol del dispositivo original, U-Boot presenta un archivo especial de U-Boot llamado .of_to_plat
*-u-boot.dtsi
Ningún otro archivo hará referencia directamente a los recién *-u-boot.dtsi
agregados , ya que estos archivos se introducen directamente en el Makefile. Solo de esta manera U-Boot puede evitar por completo la modificación de los archivos del árbol del dispositivo desde el Kernel de Linux. La prioridad incluida de mayor a menor es la siguiente:*.dts
*-u-boot.dtsi
<orig_filename>-u-boot.dtsi
# <orig_filename> es el nombre correspondiente al .dts a compilar<CONFIG_SYS_SOC>-u-boot.dtsi
<CONFIG_SYS_CPU>-u-boot.dtsi
<CONFIG_SYS_VENDOR>-u-boot.dtsi
u-boot.dtsi
marco
El DM de U-Boot usa uclass
y udevice
estas dos clases abstractas para administrar todos los controladores de dispositivos, y estas dos clases abstractas corresponden a uclass_driver
y driver
. udevice
se crea driver
dinámicamente ; uclass
se uclass_driver
crea . Pero sólo cuando se cree udevice
encontrará el correspondiente uclass
, por lo que al final sólo se creará si driver
existe uclass
.
Lo realmente útil es un independiente uclass_driver
y driver
, uclass
se udevice
gestionan a través de y respectivamente. En términos de implementación de código, uclass
en udevice
realidad nodos de una lista doblemente enlazada, y todos los controladores se administran a través de una lista doblemente enlazada. Y finalmente, la posición de la lista enlazada se indica mediante la variable relevante global_data
en . Un diagrama de bloques simple es el siguiente:
El funcionario proporciona una DEMOSTRACIÓN del controlador ( drivers/demo
), al abrir CONFIG_DM=y
, CONFIG_CMD_DEMO=y
, puede agregar esta DEMO a nuestra compilación y luego realizar una prueba de aprendizaje.CONFIG_DM_DEMO=y
CONFIG_DM_DEMO_IMX6ULL=y
global_data
./include/asm-generic/global_data.h
struct global_data
La estructura en el archivo administra las variables globales de todo el U-Boot Después de que definamos CONFIG_DM=y
, global_data
habrá algunos campos relacionados con DM en el archivo para guardar información relacionada con DM. Consulte los comentarios del código a continuación para obtener más detalles:
struct global_data {
/* ... 略 ... */
#ifdef CONFIG_DM
struct udevice *dm_root; /* 指向 DM 的根设备 */
struct udevice *dm_root_f; /* 指向重定向前的 DM 的根设备 */
struct list_head uclass_root_s; /* 非只读性内存中的 UCLASS 链表表头 */
struct list_head *uclass_root; /* UCLASS 链表表头指针,非只读内存中他就指向上面的 uclass_root_s */
/* ... 略 ... */
#endif
/* ... 略 ... */
uclass y uclass_driver
uclass
y uclass_driver
se definen en ./include/dm/uclass.h
el archivo, entre ellos, uclass
es una clase abstracta que divide los dispositivos del mismo tipo en un grupo para la gestión de la clasificación; uclass_driver
proporciona una interfaz coherente para un grupo de controladores de dispositivos relacionados. Hayuclass
una correspondencia uno a uno con . uclass_driver
En la expresión, generalmente la usamos uclass_id
para representar una uclass, por ejemplo, UCLASS_ROOT representa la propia ROOT UCLASS, y su controlador correspondiente se llama uclass_driver_root
. Los valores disponibles del ID de uclass se definen en ./include/dm/uclass-id.h
el archivo enum uclass_id
, cabe señalar que el ID se usa realmente en struct uclass_driver
el archivo .
estructura uclase
uclass
Agrupe los dispositivos del mismo tipo en un grupo para la gestión clasificada. Tenga en cuenta que uclass
U-Boot lo genera automáticamente durante el proceso de inicialización, y uclass_id
no uclass
se generarán todos los correspondientes, solo se generarán los correspondientes uclass_driver
y coincidentesudevice
(detalles en la sección de inicialización a continuación).uclass
struct uclass {
void *priv_; /* uclass 本身使用的私有数据指针。不对外使用。*/
struct uclass_driver *uc_drv; /* 一个 UCLASS 对一个 uclass_driver,这个指针指向对应的 uclass_driver */
struct list_head dev_head; /* 本 UCLASS 下对应的 udevice 链表 */
struct list_head sibling_node; /* 本 UCLASS 节点本身的前后节点(用于串联 uclass 链表) */
};
En términos de implementación de código, struct uclass
esta estructura es en realidad un nodo de lista enlazada.Después de inicializar DM, todos los dispositivos existentes uclass
formarán una gd->uclass_root
lista doblemente enlazada con el encabezado de la lista enlazada. Esta lista enlazada se encadena a través sibling_node
de sus miembros.
Cuando se inicializa DM, recorrerá todo automáticamente uclass_driver
, y cada vez que encuentre un , uclass_driver
comprobará uclass
si existe el ID correspondiente (la condición de juicio es que ya existe uclass->uc_drv->id 是否等于当前 uclass_driver->id
), y si no existe, creará un nuevo con el ID uclass_driver
en uclass
, y luego asócielo uclass_driver
con . Es decir, uclass
se crea uclass_driver
dinámicamente .
estructura uclass_driver
uclass_driver
Proporciona una interfaz coherente para un conjunto de controladores relacionados, uno uclass
para uclass_driver
. En términos de implementación de código, struct uclass
el uc_drv
in apunta a la uclass actual uclass_driver
.
struct uclass_driver {
const char *name; /* uclass driver 的名称,在定义 uclass_driver 时,填写的一个字符串 */
enum uclass_id id; /* uclass 的 ID 号,取值见 ./include/dm/uclass-id.h 文件中的 enum uclass_id 定义 */
int (*post_bind)(struct udevice *dev); /* 在一个新设备绑定到这个 uclass 后被调用 */
int (*pre_unbind)(struct udevice *dev); /* 在一个设备从该 uclass 解绑定之前调用 */
int (*pre_probe)(struct udevice *dev); /* 在 probe 一个新设备之前调用 */
int (*post_probe)(struct udevice *dev); /* 在 probe 一个新设备之后调用 */
int (*pre_remove)(struct udevice *dev); /* 在移除设备之前调用 */
int (*child_post_bind)(struct udevice *dev); /* 在这个 uclass 的 child 绑定到一个设备之后被调用 */
int (*child_pre_probe)(struct udevice *dev); /* 在这个 uclass 的 child 被 probed 之前被调用 */
int (*child_post_probe)(struct udevice *dev); /* 在这个 uclass 的 child 被 probed 之后被调用 */
int (*init)(struct uclass *class); /* 在创建一个新的 uclass 时被调用 */
int (*destroy)(struct uclass *class); /* 在 uclass 被销毁时被调用 */
int priv_auto; /* 如果非零,它就是 uclass->priv_ 指针中分配的私有数据的大小。如果为 0,则 uclass driver 负责分配所需私有数据的空间 */
int per_device_auto; /* 每个 device 都可以将 uclass 拥有的私有数据保存在自己的 dev->uclass_priv_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间 */
int per_device_plat_auto; /* 每个 device 都可以将 uclass 拥有的平台数据保存在自己的 dev->uclass_plat_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间 */
int per_child_auto; /* 每个子设备可以保存它的 parent 私有数据到 dev->parent_priv_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间。在 udevice 对应的 driver 中,也存在该变量,只有 udevice 对应的 driver 中该值为 0 时,才会使用该值 */
int per_child_plat_auto; /* 每个子设备可以保存它的 parent 平台数据到 dev->parent_plat_ 中。如果该值是非零,将在 device 初始化时自动分配该值大小的空间。在 udevice 对应的 driver 中,也存在该变量,只有 udevice 对应的 driver 中该值为 0 时,才会使用该值 */
uint32_t flags; /* 这个 uclass 的标志(DM_UC_…) */
};
En la implementación del código, uclass_driver
se deben usar macros UCLASS_DRIVER(__name)
para la definición. Además de usar la variable struct uclass_driver
de definición __name
también definirá una sección con el mismo nombre y __name
la colocará en la sección con el mismo nombre. Tome serial
como ejemplo , como se muestra a continuación:
uclass_driver
es administrado por una lista vinculada struct uclass
como un nodo, y uclass_driver
cada uno debe estar asociado con un struct uclass
especificado (en un nodo de lista vinculada). Cuando no hay un correspondienteuclass_driver
, se creará uno automáticamente y luego se asociará .struct uclass
struct uclass
dispositivos y controladores
udevice
y se driver
definen en ./include/dm/device.h
el archivo, donde udevice
se utiliza una clase abstracta para representar un dispositivo (una instancia del controlador); driver
es el controlador real correspondiente a un dispositivo. udevice
La relación con driver
puede ser de muchos a uno (varios dispositivos pueden compartir el mismo driver
).
A diferencia de UCLASS, ambosudevice
tienen miembros para identificarse y los dos nombres pueden ser diferentes o iguales. Por ejemplo, el nombre del dispositivo ROOT y el nombre del controlador ROOT son ambos . El nombre del dispositivo definido en el árbol de dispositivos suele ser un nombre de nodo y el nombre del controlador correspondiente es una cadena con el significado exacto en el código.driver
name
root_driver
dispositivo de estructura
udevice
Contiene información sobre el dispositivo, que es esencialmente una instancia de controlador que debe vincularse a un puerto específico o controlador periférico ( udevice
debe asociarse const struct driver *driver
con driver
). udevice
No se puede asociar con UCLASS en sí mismo y struct driver
debe id
asociarse con el UCLASS en el que se encuentra de acuerdo con el atributo de su asociación.
struct udevice {
const struct driver *driver;/* 此设备使用的驱动程序 */
const char *name; /* 设备名称,通常为 FDT 节点名称 */
void *plat_; /* 此设备的配置数据(DM 之外不能访问),这通常由驱动程序制定大小,并且由驱动程序负责填充内容 */
void *parent_plat_; /* 该设备的父总线配置数据(DM 之外不能访问) */
void *uclass_plat_; /* 此设备对应的 uclass 的配置数据(DM 之外不能访问) */
ulong driver_data; /* 驱动程序数据字,用于将此设备与其驱动程序相匹配的条目 */
struct udevice *parent; /* 该设备的父设备,顶级设备(例如,ROOT DEVICE)的 parent 为 NULL */
void *priv_; /* 此设备的私有数据(DM 之外不能访问) */
struct uclass *uclass; /* 指向该设备对应的 uclass 的指针 */
void *uclass_priv_; /* 此设备对应的 uclass 的私有数据(DM 之外不能访问) */
void *parent_priv_; /* 此设备的父设备的私有数据 */
struct list_head uclass_node; /* 由此设备对应的 uclass 用于连接它的设备 */
struct list_head child_head; /* 此设备的子设备列表 */
struct list_head sibling_node; /* 所有设备列表中的下一个设备 */
#if !CONFIG_IS_ENABLED(OF_PLATDATA_RT)
u32 flags_; /* 此设备的标志 DM_FLAG_xx */
#endif
int seq_; /* 为该设备分配的序列号(-1 表示没有序列号)。这是在设备绑定时设置的,在设备对应的 uclass 中是唯一的。如果设备在设备树中有别名,则别名用于设置序列号。否则,使用下一个可用号码。序列号用于某些需要对设备进行编号的命令(例如 mmc dev)(DM 之外不能访问)*/
#if CONFIG_IS_ENABLED(OF_REAL)
ofnode node_; /* 此设备的设备树节点的引用 */
#endif
#if CONFIG_IS_ENABLED(DEVRES)
struct list_head devres_head; /* 与此设备关联的内存分配列表。当 CONFIG_DEVRES 被启用时,devm_kmalloc()和 friends 会添加到这个列表中。这样分配的内存将在移除或解绑设备时自动释放 */
#endif
#if CONFIG_IS_ENABLED(DM_DMA)
ulong dma_offset; /* (CPU 的)物理地址空间和设备总线地址空间之间的偏移量 */
#endif
};
En términos de implementación de código, struct udevice
esta estructura es en realidad un nodo de lista enlazada. Después de inicializar el DM, todos udevice
formarán una lista doblemente enlazada con el encabezado de la lista enlazada. Esta lista enlazada está conectada gd->dm_root
por el child_head
y este miembro. A diferencia desibling_node
, también admite la concatenación . Es decir, a estará en múltiples listas enlazadas al mismo tiempo. Cuando se inicializa DM, recorrerá todos los , y cada vez que encuentre uno, comprobará si existe el correspondiente, y si no existe, creará un nuevo dispositivo con el nombre de . Es decir, se crea dinámicamente .uclass
udevice
uclass_node
struct udevice
driver
driver
udevice
driver
udevice
driver
uclass
A diferencia deudevice
, contiene la relación jerárquica de uno a muchos entre el dispositivo principal y el dispositivo secundario. Esta relación jerárquica corresponde a un nodo en el árbol de dispositivos y múltiples nodos secundarios.
Se creará un dispositivo mediante una llamada 'bind', ya sea debido a U_BOOT_DRVINFO()
la macro (en este caso, plat
no nula) o debido a un nodo en el árbol de dispositivos (en este caso, of_offset
>= 0). En el último caso, traduciremos la información del árbol de dispositivos en el of_to_plat
método plat
(llamado antes del método de sondeo si el dispositivo tiene un nodo de árbol de dispositivos).
La secuencia para hacer funcionar el dispositivo es bind ➜ of_to_plat(如果使用设备树)➜ probe
. En comparación con el kernel de Linux, el modelo de controlador de U-Boot tiene un paso de sondeo/eliminación independiente del enlace/desenlace. Esto se debe a que muchos dispositivos no se usarán en U-Boot y el costo de Probe no vale la pena, así que cuando sea necesario Solo entonces el dispositivo Probe.
Acerca de los números de secuencia de dispositivos
En la mayoría de los casos, U-Boot comienza a numerar dispositivos desde 0. Este número identifica de manera única un dispositivo dentro de su UCLASS, por lo que dos dispositivos dentro de una UCLASS en particular no pueden tener el mismo número de serie. Los números de serie comienzan en 0, pero se permiten espacios en blanco. Por ejemplo, una placa de desarrollo puede tener I2C1, I2C4, I2C5, pero no I2C0, I2C2, I2C3. La elección de cómo se numeran los dispositivos depende de la placa de desarrollo particular y, en algunos casos, puede ser establecida por el SoC.
El número de serie del dispositivo se resuelve cuando el dispositivo está vinculado, almacenado en udevice->seq_
la variable miembro y no cambiará durante la vida útil del dispositivo.
La ubicación donde el dispositivo obtiene el número de serie está controlada por DM_SEQ_ALIAS
un elemento de configuración , y esta opción puede tener diferentes valores en el propio U-Boot y en SPL. Si esta opción no está configurada, los alias se ignoran. Incluso si está habilitada CONFIG_DM_SEQ_ALIAS
, una uclass aún debe tener DM_UC_FLAG_SEQ_ALIAS
el indicador para que sus dispositivos se clasifiquen por alias.
Cuando se configuran estas opciones, los dispositivos con un alias (por ejemplo, "serial2") obtendrán este número de serie 2. Otros dispositivos obtienen el siguiente número disponible después de todos los alias y todos los números existentes. Esto significa que si solo hay un alias "serial2", los dispositivos serie sin alias se asignarán 3 y más allá, 0 y 1 no se utilizan.
Si CONFIG_DM_SEQ_ALIAS
o DM_UC_FLAG_SEQ_ALIAS
, todos los dispositivos obtendrán números de serie en orden simple a partir de 0. Para encontrar el siguiente número a asignar, el modelo de controlador busca el número existente más grande y luego usa el siguiente. No trata de llenar los vacíos.
controlador de estructura
driver
Contiene métodos para crear nuevos dispositivos y eliminar dispositivos, los dispositivos se configuran con información proporcionada por platdata o nodos de árbol de dispositivos (emparejados al encontrar cadenas compatibles que coincidan con of_match).
struct driver {
char *name; /* 设备名字。在定义 driver 时指定的一个字符串 */
enum uclass_id id; /* 标记此驱动属于哪个 uclass 的 id,取值是 ./include/dm/uclass-id.h 中定义的 enum uclass_id */
const struct udevice_id *of_match; /* 要匹配的 compatible 字符串列表 */
int (*bind)(struct udevice *dev); /* 绑定 device 到它的 driver 时被调用 */
int (*probe)(struct udevice *dev); /* 被调用来探测一个设备,即激活设备 */
int (*remove)(struct udevice *dev); /* 被调用来移除一个设备 */
int (*unbind)(struct udevice *dev); /* 调用来解除设备与其驱动程序的绑定 */
int (*of_to_plat)(struct udevice *dev); /* 在 probe 之前,解析对应 udevice 的 dts 节点,转化成 udevice 的平台数据(存放于 udevice->plat_ 中) */
int (*child_post_bind)(struct udevice *dev); /* 在一个新的 child 设备被绑定之后调用 */
int (*child_pre_probe)(struct udevice *dev); /* 在探测子设备之前调用。设备已分配内存,但尚未被探测。. */
int (*child_post_remove)(struct udevice *dev); /* 在移除子设备后调用。设备已经分配了内存,但是它的 device_remove() 方法已经被调用 */
int priv_auto; /* 如果非零,这是在 udevice->priv_ 指针中分配的私有数据的大小。如果为零,则驱动程序负责分配所需的任何数据。 */
int plat_auto; /* 如果非零,这是要分配到 udevice->plat_ 指针中的平台数据的大小。这通常只对支持设备树的驱动程序(使用 of_match 的驱动程序)有用,因为使用 platform data 的驱动程序将拥有 U_BOOT_DRVINFO() 实例化中提供的数据 */
int per_child_auto; /* 每个设备都可以保存其父设备拥有的私有数据。如果需要,如果该值非零,将自动分配到 udevice->parent_priv_ 指针中。 */
int per_child_plat_auto; /* 总线喜欢存储关于其子节点的信息。如果非零,这是该数据的大小,将分配到子对象的 udevice->parent_plat_ 指针中 */
const void *ops; /* driver 的具体操作,这通常是一个由 driver 定义的函数指针列表,用于实现 uclass 所需的驱动程序函数。 */
uint32_t flags; /* 驱动程序标志-参见' DM_FLAGS_…' */
#if CONFIG_IS_ENABLED(ACPIGEN)
struct acpi_ops *acpi_ops; /* 高级配置和电源接口(ACPI)操作,允许设备向传递给Linux的ACPI表中添加东西 */
#endif
};
struct driver
Ambos pertenecen a UCLASS, que representan una clase de dispositivos del mismo tipo. Los elementos comunes de los controladores se pueden implementar en UCLASS, o UCLASS puede proporcionar una interfaz coherente para los controladores dentro de ella. Seudevice
asocia con su UCLASS según el atributo en .struct driver
id
En la implementación del código, driver
se deben usar macros U_BOOT_DRIVER(__name)
para la definición. Además de usar la variable struct uclass_driver
de definición __name
también definirá una sección con el mismo nombre y __name
la colocará en la sección con el mismo nombre. Tome serial
como ejemplo , como se muestra a continuación:
driver
es administrado por una lista vinculada struct udevice
como un nodo, y driver
cada uno debe estar asociado con un struct udevice
especificado (en un nodo de lista vinculada). Cuando no hay un correspondientedriver
, se creará uno automáticamente y luego se asociará .struct udevice
struct udevice
estructura udevice_id
struct udevice_id
es para controladores específicos para enumerar las cadenas de compatibilidad admitidas por el controlador. El controlador y el dispositivo en el árbol de dispositivos struct udevice_id
específico compatible
coinciden en.
struct udevice_id {
const char *compatible; /* 一个字符串 */
ulong data; /* 兼容字符串对应的数据,具体使用方式由驱动决定 */
};
struct udevice_id
La función de es facilitar la definición de varias cadenas compatibles a la vez y, finalmente, asignarlas struct driver
a of_match
los miembros en . Entre ellos, el miembro compatible
se utiliza para compatible
hacer coincidir en el árbol de dispositivos; data
se pasará a struct udevice
in driver_data
(consulte el proceso de inicialización más adelante).
Comando MD
U-Boot proporciona comandos relacionados con DM en ./cmd/dm.c
el archivo y puede ver información relacionada con DM en la interfaz de comandos de U-Boot. Después de ingresar al modo de línea de comandos de U-Boot, ingrese help
o ?
, y luego presione Enter para ver todos los comandos compatibles actualmente con U-Boot de manera predeterminada. También puede ingresar help 命令名
o ? 命令名
para ver el uso detallado del comando, por ejemplo, help dm
se imprimirá la introducción detallada de dm
este .
dm compat
dm compat
Se utiliza para mostrar las cadenas de compatibilidad asociadas con cada controlador (puede encontrar estas cadenas en el archivo de árbol de dispositivos de cada placa), una por línea si hay más de una.
El significado de cada columna es el siguiente:
nombre de la columna | significado |
---|---|
Conductor | El nombre del conductor, el valor driver->name de |
Compatible | Cadena de compatibilidad del controlador, el valor driver->of_match de . Si el Compatible árbol coincide aquí, significa que el dispositivo del nodo del árbol de dispositivos utiliza este controlador |
dm devres
dm devres
Se utiliza para mostrar una lista de registros de devres (recursos de dispositivo) para un dispositivo. Algunos controladores usan la API de devres para asignar memoria de modo que cuando se quita el dispositivo, la memoria se libera automáticamente (no se requiere código en los remove()
métodos ).
Esta función debe definirse
CONFIG DEVRES
para habilitarla.
dm drivers
dm drivers
Se utiliza para mostrar todos los controladores disponibles, la UCLASS correspondiente al controlador y la lista de dispositivos que utilizan el controlador (un dispositivo por línea cuando hay varios dispositivos) y un controlador por línea. Si el controlador no tiene un dispositivo correspondiente, el dispositivo se muestra como none
.
El significado de cada columna es el siguiente:
nombre de la columna | significado |
---|---|
Conductor | El nombre del conductor, el valor driver->name de |
fluido | UID es el valor correspondiente enum uclass_id en |
uclase | UCLASS nombre, el valor uclass_driver->name de |
Dispositivos | Nombre del dispositivo, el valor udevice->name de |
dm static
dm static
Se utiliza para mostrar los dispositivos vinculados a los datos de la plataforma, es decir, no del árbol de dispositivos. Por lo general, no están disponibles, pero algunas placas pueden usar dispositivos estáticos por razones de espacio.
nombre de la columna | significado |
---|---|
Conductor | driver->name El nombre del controlador definido en |
DIRECCIÓN | dirección de memoria de la unidad |
dm tree
dm tree
Se utiliza para mostrar el árbol completo de dispositivos.
El significado de cada columna es el siguiente:
nombre de la columna | significado |
---|---|
Clase | El nombre UCLASS del dispositivo, es decir, el valor uclass_driver->name de |
Índice | El número de índice del dispositivo en UCLASS. Tenga en cuenta que no es el número de secuencia. |
probado | Si el dispositivo está activo, muestra+ |
Conductor | El nombre del controlador utilizado por este dispositivo, el valor driver->name de |
Nombre | Mostrar el nombre del dispositivo (es decir, el valor udevice->name de |
dm uclass
dm uclass
Se utiliza para mostrar cada clase y una lista de dispositivos en esa clase.
container_of
En DM, los dispositivos se administran a través de una lista vinculada y la administración de la lista vinculada scripts/kconfig/list.h
utiliza container_of
la macro definida en . El de U-Boot container_of
está tomado de Linux, el diseño de esta macro es bastante interesante, por lo que debemos centrarnos en analizarlo. A primera vista, esta macro no es complicada, solo un bloque de código ( {}
) y dos declaraciones independientes ( ;
).
const typeof( ((type *)0)->member ) *__mptr = (ptr);
typeof
Es una palabra clave para obtener el tipo de miembro. Por lo tanto, la primera mitad de la oración es const typeof( ((type *)0)->member )
en realidad para obtener member
el tipo de , y toda la oración es member
para definir la variable de puntero _mptr
con el tipo de y asignarle un valor ptr
, ptr
que en realidad es un puntero member
a .
(type *)( (char *)__mptr - ((size_t) &((type *)0)->member) );
(char *)__mptr
Convierte de forma coercitiva el tipo de miembro achar *
, lo que requiere que la dirección se agregue o reste en bytesoffsetof
Se utiliza para obtener el desplazamiento del miembro de la estructura. Este es un uso inteligente Sabemos que la dirección del miembro de la estructura menos la dirección base de la estructura es el desplazamiento. Y si la dirección base es 0, entonces obtener directamente la dirección del miembro es el desplazamiento.(char *)__mptr - ((size_t) &((type *)0)->member)
Es para obtener la primera dirección detype
la variable de estructura , pero el tipo eschar*
, y finalmente se usa(type *)
para convertirtype
al puntero de tipo.
en conclusión
container_of
El propósito final es devolver la dirección base de la estructura member
donde se encuentra . En pocas palabras, container_of
la función es obtener la dirección base de la estructura según los miembros de la estructura. Y es const typeof( ((type *)0)->member ) *__mptr = (ptr);
solo un estado intermedio, sin esta oración, container_of
no se puede lograr la universalidad de (la alternativa es usar el tipo coerción, pero solo se puede usar en un tipo específico).
proceso de inicialización
Como se mencionó anteriormente uclass
, udevice
se crean dinámicamente, en función de los dispositivos definidos por Device Tree y Platform Data en el controlador U-Boot. El proceso de inicialización atravesará los dispositivos creados por Platform Data y Device Tree, y creará y asociará udevice, uclass y controladores relacionados.
- De acuerdo con la información del dispositivo encontrado (dispositivo de datos de plataforma
name
o árbol de dispositivoscompatible
) para atravesardriver
para que coincida con sus miembroschar *name;
oconst struct udevice_id *of_match;
, si no hay un controlador, simplemente abandone driver
Después de encontrar , continúe buscando eldriver
miembro correspondiente según el miembro . Si existe , devuelva el encontrado , de lo contrario, continúe recorriendo los miembros para que coincida con sus miembros , y abandone si no hayenum uclass_id id
uclass
uclass
uclass
driver
enum uclass_id id
uclass driver
enum uclass_id id;
uclass driver
Una vez que se encuentra a , se crea auclass
,uclass
y el miembro destruct uclass_driver *uc_drv;
apunta al encontradouclass driver
, y se devuelve el recién creadouclass
.- Finalmente, cree uno
udevice
con miembrosconst struct driver *driver;
que apunten a lo encontradodriver
y miembrosstruct uclass *uclass;
que apunten a lo obtenido anteriormenteuclass
.
Cabe señalar aquí que el dispositivo es una estructura de árbol que contiene relaciones jerárquicas.Sin embargo, la inicialización de DM solo es responsable de atravesar todos los dispositivos de primer nivel, y los dispositivos de siguiente nivel son atravesados por el método correspondiente uclass
al .post_bind
La uclass
mayoría .post_bind
llamará directa o indirectamente dm_scan_fdt_dev
para atravesar los subdispositivos del dispositivo actual. Algunos nodos no están habilitados y los nodos que no están habilitados no tendrán los dispositivos correspondientes.
La interfaz para la inicialización de DM está dm_init_and_scan
en./common/board_f.c
en el archivo llamado antes de la reubicación static int initf_dm(void)
y./common/board_r.c
en el archivostatic int initr_dm(void)
. La inicialización después de la reubicación no es muy diferente de antes de la reubicación.
U-Boot proporciona la función de etapa de arranque (./common/bootstage.c
) para registrar información como el tiempo de ejecución de cada etapa, y esta información registrada se puede informar al usuario y pasar al sistema operativo para el registro/análisis adicional. bootstage
No está habilitado de forma predeterminadaLo que realmente está relacionado con la inicialización de DM esdm_init_and_scan
que nos centraremos en esta función a continuación.
dm_init_and_scan
dm_init_and_scan
Definido drivers/core/root.c
en pre_reloc_only
, cuando el parámetro de entrada es verdadero, significa que solo se analizarán los nodos antes de la reubicación (solo u-boot,dm-pre-reloc
se DM_FLAG_PRE_RELOC
analizarán los nodos con el atributo en el árbol de dispositivos o los dispositivos con la bandera); pre_reloc_only
si es falso, todos los nodos se analizarán parse.
Se puede ver a partir de los parámetros de entrada dm_init_and_scan
de que los parámetros de entrada son verdaderos antes de la reubicación, por lo que se analizarán menos nodos. Además, OF_LIVE
es un árbol dinámico, que no está habilitado por defecto, porque of-platdata
solo SPL/TPL
disponible en el escenario, por lo que todos los códigos of-platdata
relacionados . DM_EVENT
No lo habilito por defecto aquí, solo ignóralo.
dm_init
dm_init
Definido drivers/core/root.c
en y utilizado principalmente para inicializar ./drivers/core/root.c
el dispositivo raíz ( ) definido en U_BOOT_DRIVER(root_driver)
. El dispositivo raíz no se define mediante el árbol de dispositivos, sino que se define directamente en el código (es decir, el método de datos de plataforma).
Todos los dispositivos son hijos del dispositivo raíz. La única función del dispositivo raíz es administrar otros dispositivos
dm_init
El parámetro de entrada of_live
indica si Live Device Tree está habilitado y el código fuente muestra que este parámetro de entrada no se ha utilizado. Live Device Tree es un concepto que corresponde a Flattened Device Tree. Se utiliza principalmente para acelerar el tiempo de escaneo de inicio, pero solo se puede usar después de la reubicación.
of-platdata
La función no está habilitada, por lo que el código en else se ejecuta directamente, gd->uclass_root
señalando gd->uclass_root_s
y luego inicializando los miembros gd->uclass_root
en : gd->uclass_root.next = gd->uclass_root
y gd->uclass_root.prev = gd->uclass_root
.
device_bind_by_name
device_bind_by_name
Se drivers/core/device.c
define y se utiliza principalmente para enlazar dispositivos que no están definidos mediante el árbol de dispositivos. Esta interfaz se utiliza para crear un dispositivo y vincularlo a un controlador. Para la inicialización de DM, el dispositivo ROOT se creará aquí ./drivers/core/root.c
y U_BOOT_DRIVER(root_driver)
se vinculará al dispositivo definido en .
lists_driver_lookup_name
lists_driver_lookup_name
Definido drivers/core/device.c
en , atravesará todas struct driver
las secciones correspondientes y coincidirá con el nombre del controlador especificado (parámetro de entrada). Para la inicialización del dispositivo raíz aquí, el parámetro de entrada name
es el valor del nombre ./drivers/core/root.c
definido U_BOOT_DRIVER(root_driver)
en root_driver
y eventualmente devolverá la dirección base 0x804b438
.
device_bind_common
device_bind_common
Definida drivers/core/device.c/
en , la función es vincular el controlador de dispositivo, el dispositivo y UCLASS ( root_driver
el dispositivo inicializado por DM y U_BOOT_DRIVER(root_driver)
) UCLASS_ROOT
, de acuerdo con el proceso de inicialización anterior, solo cuando hay un controlador de dispositivo, se creará el dispositivo correspondiente.
- Llame para encontrar el UCLASS correspondiente
uclass_get
según la identificación del dispositivo. Esto incluye las operaciones relacionadas con el procesamiento de UCLASS, que se describirán en detalle más adelante. dev = calloc(1, sizeof(struct udevice));
Solicite una memoriaudevice
de nodo , es decir, cree un dispositivo (durante la inicialización de DM, se creará aquíROOT DEVICE
) y luego inicialice los nodos de la lista vinculada en él.
INIT_LIST_HEAD
Se usa para apuntar cada nodo de la lista vinculada a sí mismodev_set_plat
eso esdev->plat_ = plat;
- Algunos dispositivos, como el bus SPI, el bus I2C y los puertos serie, se numeran mediante alias. Por lo tanto, debe extraerse del nodo del árbol de dispositivos y colocarse
dev->seq_
en .
- Inicialice parte de la memoria utilizada por el dispositivo actual, como el archivo
plat_
. Tenga en cuenta que el espacio de memoria solo se asigna aquí y el contenido lo completa el controlador específico./* Check if we need to allocate plat */ if (drv->plat_auto) { /* 当驱动中 将 plat_auto 设置为实际的 platform data 的大小 */ bool alloc = !plat; /* plat 是入参,如果入参指定了,则就不会在分配驱动中 drv->plat_auto 的内存空间 */ /* * For of-platdata, we try use the existing data, but if * plat_auto is larger, we must allocate a new space */ if (CONFIG_IS_ENABLED(OF_PLATDATA)) { if (of_plat_size) dev_or_flags(dev, DM_FLAG_OF_PLATDATA); if (of_plat_size < drv->plat_auto) alloc = true; } if (alloc) { dev_or_flags(dev, DM_FLAG_ALLOC_PDATA); ptr = calloc(1, drv->plat_auto); /* 分配内存 */ if (!ptr) { ret = -ENOMEM; goto fail_alloc1; } /* * For of-platdata, copy the old plat into the new * space */ if (CONFIG_IS_ENABLED(OF_PLATDATA) && plat) memcpy(ptr, plat, of_plat_size); dev_set_plat(dev, ptr); /* dev->plat_ = plat; */ } }
- El dispositivo actual puede optar por guardar algunos datos de la plataforma en el UCLASS correspondiente
uclass_plat_
a . Si el correspondienteuclass->per_device_plat_auto
no es 0, solicite la memoria y llame adev_set_uclass_plat(dev, ptr);
la asignacióndev->uclass_plat_ = uclass_plat;
para apuntar a la memoria solicitada. Tenga en cuenta que el espacio de memoria solo se asigna aquí y el contenido lo completa el controlador específico.size = uc->uc_drv->per_device_plat_auto; if (size) { dev_or_flags(dev, DM_FLAG_ALLOC_UCLASS_PDATA); ptr = calloc(1, size); if (!ptr) { ret = -ENOMEM; goto fail_alloc2; } dev_set_uclass_plat(dev, ptr); }
- Si el dispositivo actual tiene un dispositivo de nodo principal, inicialice el dispositivo de nodo principal
per_child_plat_auto
y luego llamelist_add_tail
para agregar un nuevo dispositivo a su dispositivo de nodo principal. Para nuestro dispositivo ROOT, su padre es NULL, por lo que no es necesario agregarlo. Cuando parent no es NULL, el dispositivo se conectará a Parent a travéschild_head
de estos dos miembros, y habrá diagramas detallados más adelante.sibling_node
uclass_bind_device
Llamar Agregue el dispositivo creado anteriormente a UCLASS y, al mismo tiempo, si el dispositivo tiene un dispositivo principal, debe llamarchild_post_bind
al método del dispositivo principal (para la inicialización de DM aquí, el dispositivo raíz no tiene dispositivo principal). Directamente en la imagen:
- Llame al
bind
método para completar el enlace entre el dispositivo y el controlador correspondiente, luego llame al dispositivo principal correspondiente al dispositivo actualchild_post_bind
(en realidad es lo mismo queuclass_bind_device
en ), y finalmente llame al métodouclass
correspondiente al actual dispositivo (uclass_driver correspondiente) Por lo general, en El método atravesará los subdispositivos del dispositivo actual por turnos (es decir, los subnodos del nodo actual en el árbol de dispositivos correspondiente) y vinculará todos los subdispositivos al dispositivo principal actual. El método se llama directa o indirectamente y luego enlaza .post_bind
uclass
post_bind
dm_scan_fdt_dev ➜ dm_scan_fdt_node
lists_bind_fdt
uclass_get
El dispositivo debe pertenecer a UCLASS, uclass_get
que se define drivers/core/uclass.c
en gd->uclass_root
, para recorrer la lista vinculada de uclass a la que apunta el id de uclass en udevice y devolver la dirección de uclass encontrada; si no se encuentra, crear una nueva uclass y devolver la dirección de uclass recién creada .
- Llame para
uclass_find
recorrergd->uclass_root
la lista enlazada de uclass a la que apunta para encontrar la uclass con la identificación especificada. Para la inicialización de DM, dado quegd->dm_root
es NULL, en realidad no se ejecutalis_for_each_entry
; de lo contrario, la expansión es la siguiente:
- Cuando no se puede encontrar la uclass con la identificación especificada, llame para
uclass_add
crear una nueva uclass- Llame a
lists_uclass_lookup
finduclass_driver
, devuelva lauclass_driver
dirección , de lo contrario devuelva un error
- Cree una nueva uclass, luego realice una serie de inicializaciones y finalmente devuelva la uclass recién creada.
uc = calloc(1, sizeof(*uc));
Solicitar una memoria de nodo UCLASS- Juicio
uclass_driver->priv_auto
Solicitaruclaas->priv_
espacio de memoria (la interfazuclass_set_priv
es una declaración de asignación simpleuc->priv_ = priv;
). INIT_LIST_HEAD()
Se utilizadev_head
parasibling_node
señalar punteros en y hacia sí mismos.list_add
Responsable de serializar la memoria de nodo UCLASS solicitada agd->uclass_root
la lista enlazada- Determine y
uclass
llame a la interfazuclass_driver
correspondiente a la actual:init
uc_drv->init
- Llame a
dev_set_ofnode
OF_CONTROL
Indica si el árbol de dispositivos está habilitado, que está habilitado de forma predeterminada, por lo que continuará llamando al drivers/core/device.c
definido dev_set_ofnode
y node_
apuntando nodo raíz.
device_probe
Para ahorrar recursos, los dispositivos en U-Boot se prueban tarde. Muchos dispositivos nunca se usan durante la ejecución de U-Boot, probarlos lleva tiempo, requiere memoria, puede aumentar la demora del bucle principal, etc., por lo que U-Boot no probará dispositivos de manera proactiva a menos que sea necesario. De acuerdo con el código, el dispositivo raíz se inicializa y device_probe
se llama .
- Los dispositivos deben ser probados por código uclass o código de dispositivo genérico (por ejemplo
device_find_global_by_ofnode()
). - Para activar un dispositivo, U-Boot primero lee los datos de información del dispositivo (
of_to_plat
), si hay un dispositivo principal, el dispositivo principal debe activarse primero
device_probe
Definido drivers/core/device.c
en , se utiliza para activar un dispositivo para que pueda ser utilizado en cualquier momento.Para ahorrar recursos, el dispositivo en U-Boot se detectará con un retraso. Si el dispositivo ya está activado, vuelve directamente. Para la inicialización de DM, aquí es donde se prueba y activa el dispositivo raíz.
device_of_to_plat
Llamada Convierte la información en dts a los datos de la plataforma del dispositivo, para proporcionar la información necesaria para operaciones como la detección del dispositivo. Esto puede hacer que se prueben otros dispositivos de los que depende el dispositivo actual, por ejemplo, una línea GPIO hará que se pruebe un dispositivo GPIO.- Si el dispositivo actual tiene un dispositivo principal, ejecute recursivamente el dispositivo principal.
device_of_to_plat
- Llamado para
device_alloc_priv
asignar la memoria utilizada por varios datos en poder de este dispositivo. Esto es solo para asignar espacio en la memoria, y el contenido real lo completan los controladores específicos.static int device_alloc_priv(struct udevice *dev) { const struct driver *drv; void *ptr; int size; drv = dev->driver; assert(drv); /* Allocate private data if requested and not reentered */ if (drv->priv_auto && !dev_get_priv(dev)) { ptr = alloc_priv(drv->priv_auto, drv->flags); if (!ptr) return -ENOMEM; dev_set_priv(dev, ptr); } /* Allocate private data if requested and not reentered */ size = dev->uclass->uc_drv->per_device_auto; if (size && !dev_get_uclass_priv(dev)) { ptr = alloc_priv(size, dev->uclass->uc_drv->flags); if (!ptr) return -ENOMEM; dev_set_uclass_priv(dev, ptr); } /* Allocate parent data for this child */ if (dev->parent) { size = dev->parent->driver->per_child_auto; if (!size) size = dev->parent->uclass->uc_drv->per_child_auto; if (size && !dev_get_parent_priv(dev)) { ptr = alloc_priv(size, drv->flags); if (!ptr) return -ENOMEM; dev_set_parent_priv(dev, ptr); } } return 0; }
if (drv->priv_auto && !dev_get_priv(dev))
Se basa endrv->priv_auto
la asignacióndev->priv_
de espacio de memoria. ¡Estos son los datos privados del dispositivo!if (size && !dev_get_uclass_priv(dev))
Se basa endev->uclass->uc_drv->per_device_auto
eldev->uclass_priv_
espacio asignado, que se utiliza para guardar los datos privados de su UCLASS propiedad del dispositivo.if (size && !dev_get_parent_priv(dev))
Se utiliza para guardar los datos de su dispositivo principal propiedad del dispositivo actual de acuerdo condev->parent->driver->per_child_auto
el espacio asignado . Tenga en cuenta que solo se puede seleccionar uno de los datos privados del dispositivo principal y los datos privados de la uclass del dispositivo principal, y solo los datos privados del dispositivo principal se guardan de manera predeterminada.dev->parent_priv_
- Llame al controlador correspondiente al propio dispositivo
of_to_plat
y convierta la información del dispositivo descrita en el árbol del dispositivo en datos de la plataforma (almacenados en la memoriaudevice ->plat_
a la que apunta ), y el controlador subsiguiente obtendrá los recursos relevantes de los datos de la plataforma al usar el hardware.
- Si el dispositivo actual tiene un dispositivo principal, ejecute recursivamente el dispositivo principal.
- Si el dispositivo tiene un dispositivo principal, pruebe primero el dispositivo principal para asegurarse de que se prueben todos los dispositivos principales.
device_probe
implementación recursiva directa
- Aquí
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
hay que configurar el PINCTRL correspondiente de este dispositivo, pero debe tenerse en cuenta que debido a que el dispositivo PINCTRL puede no haber sido Probe, este es solo el estado predeterminado. - Llame para
device_get_dma_constraints
llenar las restricciones de DMA del dispositivo. Obtenga las restricciones de DMA del dispositivo del firmware. El controlador luego usa esta información para traducir la dirección física al espacio de direcciones del bus del dispositivo. Actualmente, solo se admite el árbol de dispositivos. - Llame a la interfaz que debe ejecutarse en uclass (uclass_driver correspondiente) correspondiente al dispositivo actual antes de
uclass_pre_probe_device
ejecutar el dispositivo de detección.pre_probe()
El método en uclass (uclass_driver correspondiente) correspondiente al dispositivo actual
child_pre_probe()
El método en uclass (uclass_driver correspondiente) correspondiente al dispositivo principal del dispositivo actual
- Se llama el dispositivo principal del dispositivo actual
child_pre_probe
. - La llamada
dev_has_ofnode
solo procesa el reloj predeterminado para dispositivos con un ofnode válido (la frecuencia de reloj definida en el nodo del dispositivo) - Ejecute la función de sondeo del controlador del dispositivo para activar realmente el dispositivo.
- Llame a la interfaz que debe ejecutarse en uclass (uclass_driver correspondiente) después de
uclass_post_probe_device
ejecutar el dispositivo de detección. Esto incluyepost_probe()
métodos ychild_post_probe()
métodos de uclass padre (el uclass_driver correspondiente).child_post_probe()
El método en uclass (uclass_driver correspondiente) correspondiente al dispositivo principal del dispositivo actual
post_probe()
El método en uclass (uclass_driver correspondiente) correspondiente al dispositivo actual
- Si actualmente es un dispositivo PINCTRL, entonces para los nodos con
pinctrl-names = "default";
atributos , PINCTRL se configurará automáticamente aquí
Tenga en cuenta que aquí solo estoy explicando la llamada en el nodo raíz.Otros device_probe
dispositivos no lo llamarán activamente cuando se inicialice, pero lo llamarán cuando realmente se necesite. Vea dm_scan
el análisis .
dm_scan
dm_scan
Se define drivers/core/root.c
en es responsable de inicializar todos los dispositivos excepto el dispositivo raíz. Como se mencionó anteriormente, U-Boot admite dos formas básicas de configuración del controlador, datos de plataforma (a menudo denominado plat en el código) y árbol de dispositivos plano (a menudo denominado fdt en el código). Por lo tanto, los dispositivos definidos por estos dos controladores deben tratarse aquí.
dm_scan_plat
dm_scan_plat
Definido drivers/core/root.c
en , busca y vincula U_BOOT_DRVINFO(__name)
dispositivos definidos directamente mediante . U_BOOT_DRVINFO(__name)
Definido ./include/dm/platdata.h
en , U_BOOT_DRIVER(__name)
similar a , U_BOOT_DRVINFO(__name)
excepto que se usa struct driver_info
para definir variables __name
, y también define una sección con el mismo nombre, y __name
la coloca en esta sección.
dm_scan_plat
De hecho, es recorrer driver_info
la tabla ( hasta__u_boot_list_2_driver_info_1
) y luego usar los miembros para encontrar los controladores definidos por . El establecimiento del controlador, el establecimiento de UCLASS y el enlace de uclass_driver son los mismos que el dispositivo raíz anterior, y las acciones internas finalmente se llaman para completar esta serie de acciones (los parámetros son diferentes cuando se llama). Tenga en cuenta que todos los nodos están relacionados ! ! !__u_boot_list_2_driver_info_3
driver_info
name
U_BOOT_DRIVER(__name)
udevice
device_bind_by_name
device_bind_by_name
gd->dm_root
dm_extended_scan
dm_extended_scan
Definido drivers/core/root.c
en , maneja los dispositivos definidos en el árbol de dispositivos. De acuerdo con el código fuente, dm_extended_scan
incluye principalmente dos partes: primero, dm_scan_fdt
procesar los nodos del dispositivo en el árbol de dispositivos a través de ; segundo, debido a que algunos nodos ( /chosen
, /clocks
, /firmware
) no son dispositivos en sí mismos, pueden contener algunos dispositivos. En este momento, dm_scan_fdt_ofnode_path
use para procesar los dispositivos en estos nodos uno por uno.
ofnode_root
Es el nodo raíz ( of_offset=0
), y ofnode_path
esto es para encontrar el nodo del árbol de dispositivos especificado de acuerdo con la ruta completa.Estas dos partes finalmente se proporcionan dm_scan_fdt_node
para procesar el dispositivo del nodo, por lo que solo debemos centrarnos dm_scan_fdt_node
en .
dm_scan_fdt_node
dm_scan_fdt_node
Definido drivers/core/root.c
en , implementa la exploración del árbol de dispositivos y la vinculación de controladores para nodos. Creará un nuevo udevice para el nodo del árbol de dispositivos enlazados y utilizará el parámetro de entrada principal como su dispositivo principal (el parámetro de entrada se fija para que sea gd->dm_root
el dispositivo raíz).
dm_scan_fdt_node
Comenzará desde el nodo raíz y atravesará todos los nodos secundarios a su vez. Dado que el dispositivo raíz se ha inicializado por separado antes, el dispositivo que se encuentra aquí es el primer dispositivo debajo del nodo raíz y luego se usa para vincular los nodos lists_bind_fdt
uno por uno enlace aquí es para conectar el nodo con el correspondiente udevice, controlador, uclass , uclass_driver vinculado).
for (node = ofnode_first_subnode(parent_node); /* 以根节点开始,获取第一个子节点 */
ofnode_valid(node);
node = ofnode_next_subnode(node)) {
/* 当前节点的子节点 */
const char *node_name = ofnode_get_name(node); /* 节点名字 */
if (!ofnode_is_enabled(node)) {
pr_debug(" - ignoring disabled device\n");
continue;
}
err = lists_bind_fdt(parent, node, NULL, NULL, pre_reloc_only); /* 绑定 udevice、driver、uclass */
if (err && !ret) {
ret = err;
debug("%s: ret=%d\n", node_name, ret);
}
}
lists_bind_fdt
lists_bind_fdt
node
Se creará un udevice internamente, y luego el udevice se vinculará al nodo actual (parámetro de entrada ). Por supuesto, al crear udevice, se vinculará su controlador correspondiente, uclass, uclass_driver (si no hay uclass, se creará uno nuevo).
- Obtenga
compatible
el contenido (dirección base + longitud), si no hay un nodo de dispositivocompatible
, este nodo se ignorará.compat_list = ofnode_get_property(node, "compatible", &compat_length); 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; }
- Traverse
compatible
, y luego ir adriver
la tabla ( to__u_boot_list_2_driver_1
) para comparar uno por uno (el nodo coincide con el controlador ). El código es muy simple, es un bucle de dos capas.__u_boot_list_2_driver_3
compatible
of_match
Siempre que haya una coincidencia, llame afor (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++) { if (drv) { if (drv != entry) continue; if (!entry->of_match) break; } /* 这里就会返回 匹配的 struct udevice_id */ ret = driver_check_compatible(entry->of_match, &id, compat); if (!ret) break; }
device_bind_with_driver_data
➜completar eldevice_bind_common
establecimiento del controlador, el establecimiento de UCLASS, el enlace de uclass_driver ( los parámetros son diferentes al llamar). Entre ellos está el partido, que se asigna al interior . Tenga en cuenta que todos los nodos están relacionados ! ! ! De esta forma, todos los nodos estarán conectados por padre. Además, el parámetro de entrada plat es NULL, es decir, los datos de plat del dispositivo del árbol de dispositivos se extraen del árbol de re-dispositivo subsiguiente.udevice
device_bind_common
id->data
struct udevice_id
device_bind_common
struct udevice
driver_data
gd->dm_root
device_bind_common
dm_scan_other
dm_scan_other
Definido drivers/core/root.c
en , se usa para buscar y vincular dispositivos especiales que no son visibles para el DM. Esta función es una __WEAK
función sin sustancia y debe implementarse si es necesario.
Hay una implementación predeterminada de esta interfaz en ./lib/efi/efi_app.c
y ./boot/bootstd-uclass.c
, la primera es para buscar dispositivos UEFI disponibles en U-Boot (crear un dispositivo de bloque en U-Boot), y la última se usa para vincular dispositivos con método de boor. No uso efi para comenzar aquí, por lo que se ejecutará ./boot/bootstd-uclass.c
de forma dm_scan_other
.
Podríamos definir manualmente todos los dispositivos requeridos en el árbol de dispositivos, pero esto no es obligatorio. ./boot/bootstd-uclass.c
El dm_scan_other
in bootdev
creará automáticamente el dispositivo bootstd si no se encuentra en el árbol de dispositivos. Si no se encuentran dispositivos bootmeth en absoluto, crea uno para cada controlador bootmeth disponible.
Para obtener detalles sobre esta parte, consulte Bota estándar U-Boot
dm_probe_devices
dm_probe_devices
Definido drivers/core/root.c
en , realice el recorrido gd->dm_root
de todos los dispositivos debajo y luego active el dispositivo. Como se mencionó anteriormente, todos los dispositivos U-Boot se inicializan con un retraso, y todos los dispositivos con DM_FLAG_PROBE_AFTER_BIND
la bandera ; de lo contrario, el dispositivo se probará solo cuando se use más tarde.
static int dm_probe_devices(struct udevice *dev, bool pre_reloc_only)
{
u32 mask = DM_FLAG_PROBE_AFTER_BIND;
u32 flags = dev_get_flags(dev);
struct udevice *child;
int ret;
if (pre_reloc_only)
mask |= DM_FLAG_PRE_RELOC;
if ((flags & mask) == mask) {
ret = device_probe(dev);
if (ret)
return ret;
}
list_for_each_entry(child, &dev->child_head, sibling_node)
dm_probe_devices(child, pre_reloc_only);
return 0;
}
-
Si el dispositivo tiene
DM_FLAG_PROBE_AFTER_BIND
el indicador ,device_probe
la sonda de llamada activa el dispositivo actual. Mencioné anteriormente que el dispositivo ROOT se inicializa por separado y la sonda. -
Llame
list_for_each_entry(child, &dev->child_head, sibling_node)
a la variable todos los nodos secundarios del dispositivo actual. Como se mencionó anteriormente, está conectadoudevice
en seriechild_head
con dos miembros, por lo que esta interfaz se atraviesa .sibling_node
list_for_each_entry
child_head
sibling_node
list_entry()
La función de es obtener el puntero de toda la estructura a través del puntero conocido al subelemento miembro.prefetch()
Dígale a la CPU los elementos que se utilizarán a continuación y búsquelos previamente para aumentar la velocidad&pos->member !=(head)
es la condición finalfor
del ciclo
La siguiente es la forma básica después de expandirse completamente
A lo que debemos prestar atención es a la llamada recursiva aquí dm_probe_devices
Para cada subdispositivo, la llamada recursiva aquí atravesará el subdispositivo del subdispositivo, y así sucesivamente. La condición final es volver a la capa anterior hasta que no quede ningún subequipo. El siguiente dm_probe_devices
es el recorrido de cada dispositivo.
referencia
- https://blog.csdn.net/ZHONGCAI0901/article/details/117781158
- https://zhuanlan.zhihu.com/p/460754843
- https://www.cnblogs.com/YYFaGe/p/16672483.html
- https://blog.csdn.net/weixin_41028621/article/details/90643550
- https://blog.csdn.net/ooonebook/article/details/53234020
- https://u-boot.readthedocs.io/en/latest/develop/driver-model/design.html