Utilice las siguientes pautas para aumentar la solidez y confiabilidad de sus módulos de proveedor. Muchas pautas, cuando se siguen, pueden ayudar a que sea más fácil determinar el orden correcto de carga del módulo y el orden en el que los controladores deben sondear los dispositivos.
Un módulo puede ser una biblioteca o un controlador .
Los módulos de biblioteca son bibliotecas que proporcionan API para que las usen otros módulos. Dichos módulos normalmente no son específicos del hardware. Los ejemplos de módulos de biblioteca incluyen un módulo de cifrado AES, el marco de trabajo
remoteproc
que se compila como un módulo y un módulo de búfer de registro. El código del módulo enmodule_init()
se ejecuta para configurar estructuras de datos, pero ningún otro código se ejecuta a menos que lo active un módulo externo.Los módulos de controlador son controladores que sondean o se unen a un tipo específico de dispositivo. Dichos módulos son específicos del hardware. Los ejemplos de módulos de controlador incluyen UART, PCIe y hardware de codificador de video. Los módulos de controlador se activan solo cuando su dispositivo asociado está presente en el sistema.
Si el dispositivo no está presente, el único código de módulo que se ejecuta es el código
module_init()
que registra el controlador con el marco del núcleo del controlador.Si el dispositivo está presente y el controlador busca o se vincula con éxito a ese dispositivo, es posible que se ejecute otro código de módulo.
Usar módulo init/exit correctamente
Los módulos de controlador deben registrar un controlador en module_init()
y anular el registro de un controlador en module_exit()
. Una forma sencilla de hacer cumplir estas restricciones es usar macros contenedoras, lo que evita el uso directo de las module_init()
, *_initcall()
o module_exit()
.
Para los módulos que se pueden descargar, use
module_ subsystem _driver()
. Ejemplos:module_platform_driver()
,module_i2c_driver()
ymodule_pci_driver()
.Para los módulos que no se pueden descargar, use
builtin_ subsystem _driver()
Ejemplos:builtin_platform_driver()
,builtin_i2c_driver()
ybuiltin_pci_driver()
.
Algunos módulos de controlador usan module_init()
y module_exit()
porque registran más de un controlador. Para un módulo de controlador que usa module_init()
y module_exit()
para registrar múltiples controladores, intente combinar los controladores en un solo controlador. Por ejemplo, podría diferenciar usando la cadena compatible
o los datos auxiliares del dispositivo en lugar de registrar controladores separados. Alternativamente, puede dividir el módulo del controlador en dos módulos.
Excepciones de funciones de inicio y salida
Los módulos de biblioteca no registran controladores y están exentos de restricciones en module_init()
y module_exit()
, ya que podrían necesitar estas funciones para configurar estructuras de datos, colas de trabajo o subprocesos del kernel.
Utilice la macro MODULE_DEVICE_TABLE
Los módulos de controlador deben incluir la macro MODULE_DEVICE_TABLE
, que permite que el espacio del usuario determine los dispositivos admitidos por un módulo de controlador antes de cargar el módulo. Android puede usar estos datos para optimizar la carga de módulos, por ejemplo, para evitar cargar módulos para dispositivos que no están presentes en el sistema. Para ver ejemplos sobre el uso de la macro, consulte el código original.
Evite las discrepancias de CRC debido a los tipos de datos declarados hacia adelante
No incluya archivos de encabezado para obtener visibilidad de los tipos de datos declarados hacia adelante. Algunas estructuras, uniones y otros tipos de datos definidos en un archivo de encabezado ( header-Ah
) se pueden declarar hacia adelante en un archivo de encabezado diferente ( header-Bh
) que normalmente usa punteros a esos tipos de datos. Este patrón de código significa que el kernel está tratando intencionalmente de mantener la estructura de datos privada para los usuarios de header-Bh
.
Los usuarios de header-Bh
no deben incluir header-Ah
para acceder directamente a las partes internas de estas estructuras de datos declaradas hacia adelante. Hacerlo causa problemas de discrepancia de CONFIG_MODVERSIONS
CRC (lo que genera problemas de cumplimiento de ABI) cuando un kernel diferente (como el kernel GKI) intenta cargar el módulo.
Por ejemplo, struct fwnode_handle
se define en include/linux/fwnode.h
, pero se declara hacia adelante como struct fwnode_handle;
en include/linux/device.h
porque el núcleo está tratando de mantener los detalles de struct fwnode_handle
privados de los usuarios de include/linux/device.h
. En este escenario, no agregue #include <linux/fwnode.h>
en un módulo para obtener acceso a los miembros de struct fwnode_handle
. Cualquier diseño en el que tenga que incluir dichos archivos de encabezado indica un patrón de diseño incorrecto.
No acceda directamente a las estructuras centrales del kernel
El acceso directo o la modificación de las estructuras de datos del kernel central puede provocar un comportamiento no deseado, incluidas pérdidas de memoria, bloqueos y compatibilidad rota con futuras versiones del kernel. Una estructura de datos es una estructura de datos del kernel central cuando cumple alguna de las siguientes condiciones:
La estructura de datos se define en
KERNEL-DIR /include/
. Por ejemplo,struct device
ystruct dev_links_info
. Las estructuras de datos definidas eninclude/linux/soc
están exentas.El módulo asigna o inicializa la estructura de datos, pero se hace visible para el kernel al pasarla, indirectamente (a través de un puntero en una estructura) o directamente, como entrada en una función exportada por el kernel. Por ejemplo, un módulo de controlador
cpufreq
inicializa lastruct cpufreq_driver
y luego la pasa como entrada acpufreq_register_driver()
. Después de este punto, el módulo del controladorcpufreq
no debería modificarstruct cpufreq_driver
directamente porque llamar acpufreq_register_driver()
hace questruct cpufreq_driver
visible para el kernel.Su módulo no inicializa la estructura de datos. Por ejemplo,
struct regulator_dev
devuelto porregulator_register()
.
Acceda a las estructuras de datos del kernel central solo a través de funciones exportadas por el kernel o mediante parámetros pasados explícitamente como entrada a los ganchos del proveedor. Si no tiene una API o un enlace de proveedor para modificar partes de una estructura de datos del kernel central, probablemente sea intencional y no debe modificar la estructura de datos de los módulos. Por ejemplo, no modifique ningún campo dentro struct device
o struct device.links
.
Para modificar
device.devres_head
, use unadevm_*()
comodevm_clk_get()
,devm_regulator_get()
odevm_kzalloc()
.Para modificar campos dentro
struct device.links
, use una API de vínculo de dispositivo comodevice_link_add()
odevice_link_del()
.
No analice los nodos del árbol de dispositivos con propiedades compatibles
Si un nodo de árbol de dispositivos (DT) tiene una propiedad compatible
, se le asigna un struct device
automáticamente o cuando se llama a of_platform_populate()
en el nodo DT principal (normalmente por el controlador de dispositivo del dispositivo principal). La expectativa predeterminada (excepto para algunos dispositivos inicializados antes para el programador) es que un nodo DT con una propiedad compatible
tenga un struct device
y un controlador de dispositivo coincidente. Todas las demás excepciones ya son manejadas por el código ascendente.
Además, fw_devlink
(anteriormente llamado of_devlink
) considera que los nodos DT con la propiedad compatible
son dispositivos con un struct device
asignado que es probado por un controlador. Si un nodo DT tiene una propiedad compatible
, pero el struct device
asignado no se prueba, fw_devlink
podría bloquear el sondeo de sus dispositivos de consumo o podría bloquear las llamadas de sync_state()
para los dispositivos de su proveedor.
Si su controlador usa una of_find_*()
(como of_find_node_by_name()
o of_find_compatible_node()
) para buscar directamente un nodo DT que tenga una propiedad compatible
y luego analizar ese nodo DT, arregle el módulo escribiendo un controlador de dispositivo que pueda sondear el dispositivo o elimine la propiedad compatible
(es posible solo si no se ha actualizado). Para analizar alternativas, comuníquese con el equipo de kernel de Android en kernel-team@android.com y prepárese para justificar sus casos de uso.
Utilice DT phandles para buscar proveedores
Refiérase a un proveedor utilizando un fandle (una referencia/puntero a un nodo DT) en DT siempre que sea posible. El uso de enlaces y phandles de DT estándar para hacer referencia a los proveedores permite que fw_devlink
(anteriormente of_devlink
) determine automáticamente las dependencias entre dispositivos mediante el análisis del DT en tiempo de ejecución. Luego, el kernel puede sondear automáticamente los dispositivos en el orden correcto, eliminando la necesidad de ordenar la carga de módulos o MODULE_SOFTDEP()
.
Escenario heredado (sin compatibilidad con DT en el kernel ARM)
Anteriormente, antes de que se agregara la compatibilidad con DT a los núcleos ARM, los consumidores, como los dispositivos táctiles, buscaban proveedores, como los reguladores, que usaban cadenas únicas a nivel mundial. Por ejemplo, el controlador ACME PMIC podría registrar o anunciar varios reguladores (como acme-pmic-ldo1
a acme-pmic-ldo10
) y un controlador táctil podría buscar un regulador mediante regulator_get(dev, "acme-pmic-ldo10")
. Sin embargo, en una placa diferente, el LDO8 podría suministrar el dispositivo táctil, creando un sistema engorroso en el que el mismo controlador táctil necesita determinar la cadena de búsqueda correcta para el regulador para cada placa en la que se utiliza el dispositivo táctil.
Escenario actual (compatibilidad con DT en kernel ARM)
Después de agregar la compatibilidad con DT a los kernels ARM, los consumidores pueden identificar proveedores en el DT consultando el nodo del árbol de dispositivos del proveedor mediante un phandle . Los consumidores también pueden nombrar el recurso en función de para qué se utiliza en lugar de quién lo suministra. Por ejemplo, el controlador táctil del ejemplo anterior podría usar regulator_get(dev, "core")
y regulator_get(dev, "sensor")
para obtener los proveedores que alimentan el núcleo y el sensor del dispositivo táctil. El IME asociado para dicho dispositivo es similar al siguiente ejemplo de código:
touch-device {
compatible = "fizz,touch";
...
core-supply = <&acme_pmic_ldo4>;
sensor-supply = <&acme_pmic_ldo10>;
};
acme-pmic {
compatible = "acme,super-pmic";
...
acme_pmic_ldo4: ldo4 {
...
};
...
acme_pmic_ldo10: ldo10 {
...
};
};
Escenario del peor de ambos mundos
Algunos controladores portados desde kernels más antiguos incluyen un comportamiento heredado en el DT que toma la peor parte del esquema heredado y lo fuerza en el esquema más nuevo que está destinado a facilitar las cosas. En tales controladores, el controlador del consumidor lee la cadena para usarla en la búsqueda usando una propiedad de DT específica del dispositivo, el proveedor usa otra propiedad específica del proveedor para definir el nombre que se usará para registrar el recurso del proveedor, luego el consumidor y el proveedor continúan usando el mismo viejo esquema de usar cadenas para buscar el proveedor. En este peor escenario de ambos mundos:
El controlador táctil utiliza un código similar al código siguiente:
str = of_property_read(np, "fizz,core-regulator"); core_reg = regulator_get(dev, str); str = of_property_read(np, "fizz,sensor-regulator"); sensor_reg = regulator_get(dev, str);
El DT utiliza un código similar al siguiente:
touch-device { compatible = "fizz,touch"; ... fizz,core-regulator = "acme-pmic-ldo4"; fizz,sensor-regulator = "acme-pmic-ldo4"; }; acme-pmic { compatible = "acme,super-pmic"; ... ldo4 { regulator-name = "acme-pmic-ldo4" ... }; ... acme_pmic_ldo10: ldo10 { ... regulator-name = "acme-pmic-ldo10" }; };
No modifique los errores de la API del marco
Las API del marco, como regulator
, clocks
, irq
, gpio
, phys
y extcon
, devuelven -EPROBE_DEFER
como un valor de retorno de error para indicar que un dispositivo está intentando sondear pero no puede en este momento, y el kernel debe volver a intentar la sonda luego. Para asegurarse de que la función .probe()
de su dispositivo falle como se esperaba en tales casos, no reemplace ni reasigne el valor del error. Reemplazar o reasignar el valor de error puede hacer que se -EPROBE_DEFER
y que su dispositivo nunca sea probado.
Usar variantes de la API devm_*()
Cuando el dispositivo adquiere un recurso mediante una devm_*()
, el kernel libera automáticamente el recurso si el dispositivo falla al sondear o si lo hace con éxito y luego se desvincula. Esta funcionalidad hace que el código de manejo de errores en la función probe()
sea más limpio porque no requiere saltos goto
para liberar los recursos adquiridos por devm_*()
y simplifica las operaciones de desvinculación de controladores.
Manejar la desvinculación del controlador de dispositivo
Sea intencional al desvincular los controladores de dispositivos y no deje la desvinculación sin definir porque indefinido no implica que no esté permitido. Debe implementar por completo la desvinculación de controladores de dispositivos o deshabilitar explícitamente la desvinculación de controladores de dispositivos.
Implementación de la desvinculación de controladores de dispositivos
Al elegir implementar completamente la desvinculación de controladores de dispositivos, desvincule los controladores de dispositivos de forma limpia para evitar fugas de memoria o recursos y problemas de seguridad. Puede vincular un dispositivo a un controlador llamando a la función probe()
del controlador y desvincular un dispositivo llamando a la función remove()
del controlador. Si no existe la función remove()
, el núcleo aún puede desvincular el dispositivo; el núcleo del controlador asume que el controlador no necesita ningún trabajo de limpieza cuando se desvincula del dispositivo. Un controlador que no está vinculado a un dispositivo no necesita realizar ningún trabajo de limpieza explícito cuando se cumplen las dos condiciones siguientes:
Todos los recursos adquiridos por la función
probe()
de un controlador se realizan a travésdevm_*()
.El dispositivo de hardware no necesita una secuencia de apagado o inactividad.
En esta situación, el núcleo del controlador maneja la liberación de todos los recursos adquiridos a través devm_*()
. Si alguna de las afirmaciones anteriores no es cierta, el controlador debe realizar una limpieza (liberar recursos y apagar o poner en modo inactivo el hardware) cuando se desvincula de un dispositivo. Para asegurarse de que un dispositivo pueda desvincular un módulo de controlador sin problemas, use una de las siguientes opciones:
Si el hardware no necesita una secuencia de apagado o inactividad, cambie el módulo del dispositivo para adquirir recursos mediante
devm_*()
.Implemente la operación del controlador
remove()
en la misma estructura que la funciónprobe()
, luego realice los pasos de limpieza usando la funciónremove()
.
Deshabilitar explícitamente la desvinculación de controladores de dispositivos (no recomendado)
Al elegir deshabilitar explícitamente la desvinculación de controladores de dispositivos, debe deshabilitar la desvinculación y la descarga de módulos.
Para no permitir la desvinculación, establezca el indicador
suppress_bind_attrs
entrue
en la estructura del controladorstruct device_driver
; esta configuración evita que los archivosbind
yunbind
se muestren en el directoriosysfs
del controlador. El archivo deunbind
es lo que permite que el espacio del usuario active la desvinculación de un controlador de su dispositivo.Para no permitir la descarga del módulo, asegúrese de que el módulo tenga
[permanent]
enlsmod
. Al no usarmodule_exit()
omodule_XXX_driver()
, el módulo se marca como[permanent]
.
No cargue el firmware desde dentro de la función de sonda
El controlador no debe cargar el firmware desde la función .probe()
, ya que es posible que no tenga acceso al firmware si el controlador prueba antes de que se monte el sistema de archivos basado en almacenamiento permanente o flash. En tales casos, la API request_firmware*()
puede bloquearse durante mucho tiempo y luego fallar, lo que puede ralentizar el proceso de arranque innecesariamente. En su lugar, posponga la carga del firmware para cuando un cliente comience a usar el dispositivo. Por ejemplo, un controlador de pantalla podría cargar el firmware cuando se abre el dispositivo de visualización.
El uso .probe()
para cargar firmware puede estar bien en algunos casos, como en un controlador de reloj que necesita firmware para funcionar, pero el dispositivo no está expuesto al espacio del usuario. Son posibles otros casos de uso apropiados.
Implementar sondeo asíncrono
Admita y use el sondeo asíncrono para aprovechar las mejoras futuras, como la carga de módulos en paralelo o el sondeo de dispositivos para acelerar el tiempo de arranque, que podrían agregarse a Android en futuras versiones. Los módulos de controlador que no usan sondeo asíncrono podrían reducir la efectividad de dichas optimizaciones.
Para marcar un controlador como compatible y que prefiere el sondeo asíncrono, configure el campo probe_type
en el miembro struct device_driver
del controlador. El siguiente ejemplo muestra dicha compatibilidad habilitada para un controlador de plataforma:
static struct platform_driver acme_driver = {
.probe = acme_probe,
...
.driver = {
.name = "acme",
...
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
Hacer que un controlador funcione con sondeo asíncrono no requiere un código especial. Sin embargo, tenga en cuenta lo siguiente al agregar soporte de sondeo asíncrono.
No haga suposiciones sobre las dependencias probadas previamente. Verifique directa o indirectamente (la mayoría de las llamadas de marco) y devuelva
-EPROBE_DEFER
si uno o más proveedores aún no están listos.Si agrega dispositivos secundarios en la función de sondeo de un dispositivo principal, no asuma que los dispositivos secundarios se sondean inmediatamente.
Si una sonda falla, realice el manejo de errores y la limpieza adecuados (consulte Uso de variantes de la API devm_*() ).
No use MODULE_SOFTDEP para ordenar sondas de dispositivos
La función MODULE_SOFTDEP()
no es una solución confiable para garantizar el orden de las sondas del dispositivo y no debe usarse por las siguientes razones.
Sonda diferida. Cuando se carga un módulo, la sonda del dispositivo puede posponerse porque uno de sus proveedores no está listo. Esto puede provocar una discrepancia entre el orden de carga del módulo y el orden de sondeo del dispositivo.
Un controlador, muchos dispositivos. Un módulo de controlador puede administrar un tipo de dispositivo específico. Si el sistema incluye más de una instancia de un tipo de dispositivo y cada uno de esos dispositivos tiene un requisito de orden de sonda diferente, no puede respetar esos requisitos mediante el orden de carga de módulos.
Sondeo asíncrono. Los módulos de controlador que realizan un sondeo asíncrono no sondean inmediatamente un dispositivo cuando se carga el módulo. En cambio, un subproceso paralelo maneja el sondeo del dispositivo, lo que puede generar una discrepancia entre el orden de carga del módulo y el orden del sondeo del dispositivo. Por ejemplo, cuando un módulo de controlador maestro I2C realiza un sondeo asíncrono y un módulo de controlador táctil depende del PMIC que está en el bus I2C, incluso si el controlador táctil y el controlador PMIC se cargan en el orden correcto, es posible que se intente realizar el sondeo del controlador táctil antes. la sonda del controlador PMIC.
Si tiene módulos de controlador que usan la función MODULE_SOFTDEP()
, corríjalos para que no usen esa función. Para ayudarlo, el equipo de Android ha actualizado los cambios que permiten que el kernel maneje los problemas de pedidos sin usar MODULE_SOFTDEP()
. Específicamente, puede usar fw_devlink
para garantizar el orden de sondeo y (después de que todos los consumidores de un dispositivo hayan sondeado) use la devolución de llamada sync_state()
para realizar las tareas necesarias.
Use #if IS_ENABLED() en lugar de #ifdef para configuraciones
Use #if IS_ENABLED(CONFIG_XXX)
en lugar de #ifdef CONFIG_XXX
para asegurarse de que el código dentro del bloque #if
continúe compilando si la configuración cambia a una configuración tristate en el futuro. Las diferencias son las siguientes:
#if IS_ENABLED(CONFIG_XXX)
evalúa comotrue
cuandoCONFIG_XXX
se establece en módulo (=m
) o integrado (=y
).#ifdef CONFIG_XXX
evalúa comotrue
cuandoCONFIG_XXX
está configurado como integrado (=y
) , pero no cuandoCONFIG_XXX
está configurado como módulo (=m
). Use esto solo cuando esté seguro de que quiere hacer lo mismo cuando la configuración está configurada en módulo o está deshabilitada.
Use la macro correcta para compilaciones condicionales
Si CONFIG_XXX
se establece en módulo ( =m
), el sistema de compilación define automáticamente CONFIG_XXX_MODULE
. Si su controlador está controlado por CONFIG_XXX
y desea verificar si su controlador se está compilando como un módulo, use las siguientes pautas:
En el archivo C (o cualquier archivo de origen que no sea un archivo de encabezado) para su controlador, no use
#ifdef CONFIG_XXX_MODULE
ya que es innecesariamente restrictivo y se rompe si la configuración cambia de nombre aCONFIG_XYZ
. Para cualquier archivo fuente que no sea de encabezado que se compile en un módulo, el sistema de compilación define automáticamenteMODULE
para el alcance de ese archivo. Por lo tanto, para verificar si un archivo C (o cualquier archivo fuente que no sea de encabezado) se está compilando como parte de un módulo, use#ifdef MODULE
(sin el prefijoCONFIG_
).En los archivos de encabezado, la misma verificación es engañosa porque los archivos de encabezado no se compilan directamente en un binario, sino que se compilan como parte de un archivo C (u otros archivos fuente). Utilice las siguientes reglas para los archivos de encabezado:
Para un archivo de encabezado que usa
#ifdef MODULE
, el resultado cambia según el archivo de origen que lo esté usando. Esto significa que el mismo archivo de encabezado en la misma compilación puede tener diferentes partes de su código compiladas para diferentes archivos fuente (módulo versus incorporado o deshabilitado). Esto puede ser útil cuando desea definir una macro que necesita expandirse de una manera para el código integrado y expandirse de una manera diferente para un módulo.Para un archivo de encabezado que necesita compilarse en una pieza de código cuando un
CONFIG_XXX
específico se configura como módulo (independientemente de si el archivo de origen que lo incluye es un módulo), el archivo de encabezado debe usar#ifdef CONFIG_XXX_MODULE
.