Usa los siguientes lineamientos para aumentar la solidez y confiabilidad de tus módulos de proveedores. Muchas pautas, cuando se siguen, pueden ayudar a que sea más fácil determinar el orden de carga correcto de los módulos y el orden en el que los controladores sondeo de dispositivos.
Un módulo puede ser una biblioteca o un controlador.
Los módulos de bibliotecas son bibliotecas que proporcionan APIs para que las usen otros módulos. Por lo general, estos módulos no son específicos del hardware. Ejemplos de módulos de biblioteca incluir un módulo de encriptación AES, el framework
remoteproc
que se compila como módulo y módulo de búfer de registro. El código del módulo enmodule_init()
se ejecuta para configurar estructuras de datos, pero no se ejecuta ningún otro código, a menos que lo active un módulo externo.Los módulos de controlador son controladores que sondean o se vinculan a un tipo específico de dispositivo. Esos módulos son específicos de hardware. Algunos ejemplos de módulos de controladores son UART, PCIe y hardware de codificador de video. Los módulos de controlador se activan solo cuando que 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 framework principal del controlador.Si el dispositivo está presente y el controlador lo sondea o se vincula correctamente a él, es posible que se ejecute otro código de módulo.
Usa los comandos init y exit del módulo correctamente
Los módulos de controlador deben registrar un controlador en module_init()
y cancelar el registro de un
controlador en module_exit()
. Una forma de aplicar estas restricciones es usar
macros wrapper, lo que evita el uso directo de module_init()
, *_initcall()
,
o las macros module_exit()
.
Para los módulos que se pueden descargar, usa
module_subsystem_driver()
. Ejemplos:module_platform_driver()
,module_i2c_driver()
ymodule_pci_driver()
.Para los módulos que no se pueden descargar, usa
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
registrar más de un controlador. Para un módulo de controlador que usa module_init()
y
module_exit()
para registrar varios conductores; intenta combinarlos en una
con un solo controlador. Por ejemplo, puedes diferenciar usando la cadena compatible
o los datos auxiliares del dispositivo en lugar de registrar controladores independientes.
Como alternativa, puedes dividir el módulo del controlador en dos.
Excepciones de las funciones de inicio y salida
Los módulos de biblioteca no registran controladores y están exentos de las restricciones en
module_init()
y module_exit()
, ya que podrían necesitar estas funciones para configurar
estructuras de datos, colas de trabajo o subprocesos de kernel.
Usa la macro MODULE_DEVICE_TABLE
Los módulos de controladores deben incluir la macro MODULE_DEVICE_TABLE
, que permite que el espacio del usuario determine los dispositivos compatibles con un módulo de controlador antes de cargarlo. 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
ejemplos sobre el uso de la macro, consulta el código upstream.
Evita las discrepancias en la CRC debido a los tipos de datos declarados en forma directa
No incluyas archivos de encabezado para obtener visibilidad sobre los tipos de datos declarados con posterioridad.
Algunos structs, uniones y otros tipos de datos definidos en un archivo de encabezado
(header-A.h
) se puede reenviar de manera declarada en una
archivo de encabezado (header-B.h
) que generalmente usa punteros para
esos tipos de datos. Este patrón de código significa que el kernel intenta, de forma intencional, mantener la estructura de datos privada para los usuarios de header-B.h
.
Los usuarios de header-B.h
no deben incluir
header-A.h
para acceder directamente a los componentes internos de estas
declaradas con posterioridad. Si lo haces, se genera la CRC CONFIG_MODVERSIONS
.
problemas de discrepancia (lo que genera problemas de cumplimiento de ABI) cuando se usa un kernel diferente.
(como el kernel de GKI) intenta cargar el módulo.
Por ejemplo, struct fwnode_handle
se define en include/linux/fwnode.h
, pero
se declaró como struct fwnode_handle;
en include/linux/device.h
ya que el kernel intenta mantener los detalles de struct fwnode_handle
.
privada de los usuarios de include/linux/device.h
. En esta situación, no agregues #include <linux/fwnode.h>
en un módulo para obtener acceso a los miembros de struct fwnode_handle
. Cualquier diseño en el que tengas que incluir ese encabezado
indica un mal patrón de diseño.
No accedes directamente a las estructuras principales del kernel.
Acceder directamente o modificar las estructuras de datos del kernel principal puede provocar comportamiento no deseado, incluidas fugas de memoria, fallas y errores en la compatibilidad con futuras versiones de kernel. Una estructura de datos es una estructura de datos central del kernel 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 visibles para el kernel si se pasan, de forma indirecta, (a través de un puntero en un struct) o directamente como entrada en una función exportada por el kernel. Para Por ejemplo, un módulo de controlador
cpufreq
inicializastruct cpufreq_driver
y y, luego, lo pasa como entrada acpufreq_register_driver()
. Después de este punto, el módulo del controladorcpufreq
no debe modificarstruct cpufreq_driver
directamente, ya que llamar acpufreq_register_driver()
hace questruct cpufreq_driver
sea visible para el kernel.Tu módulo no inicializa la estructura de datos. Por ejemplo,
struct regulator_dev
que muestraregulator_register()
.
Accede a las estructuras de datos principales del kernel solo a través de las funciones que exporta el kernel o a través de parámetros que se pasan de forma explícita como entrada a los hooks del proveedor. Si
No tienen una API ni un hook de proveedor para modificar partes de los datos del kernel principal.
es probable que sea intencional, y no deberías modificar la estructura de datos
de los módulos. Por ejemplo, no modifiques ningún campo dentro de struct device
ni
struct device.links
Para modificar
device.devres_head
, usa una funcióndevm_*()
, comodevm_clk_get()
,devm_regulator_get()
odevm_kzalloc()
.Para modificar campos dentro de
struct device.links
, usa una API de vinculación de dispositivos, comodevice_link_add()
odevice_link_del()
.
No analices los nodos de devicetree con una propiedad compatible
Si un nodo del árbol de dispositivos (DT) tiene una propiedad compatible
, se aplica una struct device
se le asigna automáticamente o cuando se llama a of_platform_populate()
en
el nodo DT superior (por lo general, mediante el controlador del dispositivo superior). El
esperada predeterminada (excepto por algunos dispositivos que se inicializaron antes para el
programador) es que un nodo DT con una propiedad compatible
tiene un valor struct device
y un controlador de dispositivo coincidente. El código upstream ya controla todas las demás excepciones.
Además, fw_devlink
(antes llamado of_devlink
) considera nodos de DT.
con la propiedad compatible
para que sean dispositivos con un struct device
asignado
que sondea un controlador. Si un nodo de DT tiene una propiedad compatible
, pero no se sondea el struct device
asignado, fw_devlink
podría bloquear la sondeo de sus dispositivos de consumo o bloquear las llamadas a sync_state()
para sus dispositivos de proveedores.
Si tu controlador usa una función of_find_*()
(como of_find_node_by_name()
o of_find_compatible_node()
) para encontrar directamente un nodo DT que tenga una propiedad compatible
y, luego, analizar ese nodo DT, corrige el módulo escribiendo un controlador de dispositivo que pueda sondear el dispositivo o quitar la propiedad compatible
(solo es posible si no se transfirió a upstream). Para analizar las alternativas, comunícate con el equipo del kernel de Android a través de kernel-team@android.com y prepárate para justificar tus casos de uso.
Usa DT phandles para buscar proveedores
Haz referencia a un proveedor con un phandle (una referencia o un puntero a un nodo de DT) en DT
siempre que sea posible. Uso de vinculaciones y phandles estándar de DT para hacer referencia a los proveedores
permite que fw_devlink
(anteriormente of_devlink
) determine automáticamente
dependencias entre dispositivos a través del análisis del DT en el tiempo de ejecución. Luego, el kernel puede
sondear automáticamente los dispositivos en el orden correcto, lo que elimina la necesidad de usar
pedido de carga o MODULE_SOFTDEP()
.
Situación heredada (sin compatibilidad con DT en el kernel de ARM)
Anteriormente, antes de que se agregara la compatibilidad con DT a los kernels de ARM, los consumidores, como los dispositivos táctiles, buscaban proveedores, como reguladores, con cadenas únicas a nivel global.
Por ejemplo, el controlador de PMIC de ACME 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 con regulator_get(dev, "acme-pmic-ldo10")
.
Sin embargo, en una placa diferente, el LDO8 podría suministrar el dispositivo táctil, lo que crearía un sistema engorroso en el que el mismo controlador táctil debe determinar la cadena de búsqueda correcta para el regulador de cada placa en la que se usa el dispositivo táctil.
Situación actual (compatibilidad de DT en el kernel de ARM)
Después de agregar la compatibilidad con DT a los kernels de ARM, los consumidores pueden identificar a los proveedores
consultando el nodo del árbol de dispositivos del proveedor con un phandle.
Los consumidores también pueden nombrar el recurso según el uso que se le da en lugar de quién
lo proporciona. Por ejemplo, el controlador táctil del ejemplo anterior podría usar
regulator_get(dev, "core")
y regulator_get(dev, "sensor")
para obtener la
que alimentan el núcleo y el sensor
del dispositivo táctil. El DT asociado para un dispositivo de este tipo es similar a la siguiente muestra 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 {
...
};
};
El peor de los dos mundos
Algunos controladores portados de kernels más antiguos incluyen un comportamiento heredado en el DT que toma la peor parte del esquema heredado y lo aplica al esquema más nuevo que está diseñado para facilitar las cosas. En esos controladores, el controlador de consumo lee la cadena que se usará para la búsqueda con una propiedad 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 siguen usando el mismo esquema anterior de usar cadenas para buscar el proveedor. En esta situación, ocurre lo siguiente:
El controlador táctil usa código similar al 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);
La DT usa 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 modifiques los errores de la API del framework
Las APIs de Framework, como regulator
, clocks
, irq
, gpio
, phys
y extcon
, muestran -EPROBE_DEFER
como un valor devuelto de error para indicar que un dispositivo intenta sondear, pero no puede hacerlo en este momento, y el kernel debe volver a intentar la sonda más adelante. Para asegurarte de que la función .probe()
de tu dispositivo
falla como se espera en esos casos, no reemplaces ni reasignes el valor del error.
Reemplazar o reasignar el valor de error puede hacer que se descarte -EPROBE_DEFER
y, como resultado, nunca se sondea el dispositivo.
Usa variantes de API de devm_*()
Cuando el dispositivo adquiere un recurso mediante una API de devm_*()
, el recurso se
el kernel automáticamente si el dispositivo no puede sondear o si
con éxito y, luego, se desvincula. Esta función hace que el manejo de errores
código en la función probe()
más limpiadora porque no requiere saltos de goto
para liberar los recursos adquiridos por devm_*()
y simplifica la desvinculación del controlador.
las operaciones.
Controla la desvinculación del controlador de dispositivos
Ten cuidado cuando desvincules los controladores de dispositivos y no dejes la desvinculación sin definir, ya que no definirla no implica que no se permita. Debes implementar por completo la desvinculación del controlador de dispositivo o inhabilitarla de forma explícita.
Implementa la desvinculación del dispositivo y el controlador
Cuando elijas implementar por completo la desvinculación de controladores de dispositivos, debes desvincularlos.
de forma limpia para evitar fugas de memoria o recursos y problemas de seguridad. Puedes vincular un
desde un dispositivo a un controlador llamando a la función probe()
de un controlador y desvinculando un dispositivo.
llamando a la función remove()
del controlador. Si no existe una función remove()
, el kernel aún puede desvincular el dispositivo. El núcleo del controlador supone que el controlador no necesita realizar ningún trabajo de limpieza cuando se desvincula del dispositivo. Un conductor
desvinculada de un dispositivo no necesita realizar ningún trabajo de limpieza explícito cuando
las siguientes afirmaciones son verdaderas:
Todos los recursos que adquiere la función
probe()
de un controlador son a través de las APIs dedevm_*()
.El dispositivo de hardware no necesita una secuencia de apagado ni inactividad.
En esta situación, el núcleo del controlador controla la liberación de todos los recursos adquiridos a través de las APIs de devm_*()
. Si alguna de las afirmaciones anteriores es falsa,
el controlador necesita realizar una limpieza (liberar recursos y apagar o
desactiva el hardware) cuando se desvincula de un dispositivo. Para asegurarte de que un dispositivo pueda desvincular un módulo de controlador de forma correcta, usa una de las siguientes opciones:
Si el hardware no necesita una secuencia de apagado o inactividad, cambia la módulo del dispositivo para adquirir recursos con las APIs de
devm_*()
.Implementa la operación del controlador
remove()
en la misma struct queprobe()
. y, luego, realiza los pasos de limpieza con la funciónremove()
.
Inhabilita explícitamente la desvinculación del controlador de dispositivo (no se recomienda)
Cuando elijas inhabilitar de forma explícita la desvinculación del controlador de dispositivos, debes prohibir la desvinculación y la descarga del módulo.
Para no permitir la desvinculación, establece la marca
suppress_bind_attrs
entrue
en elstruct device_driver
del conductor; este parámetro de configuración evita quebind
yunbind
archivos en el directoriosysfs
del controlador. El archivounbind
tiene 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úrate de que el módulo tenga
[permanent]
enlsmod
. Si no usasmodule_exit()
omodule_XXX_driver()
, el módulo se marca como[permanent]
.
No cargues el firmware desde la función de sonda.
El controlador no debería cargar el firmware desde la función .probe()
, como debería hacerlo.
no tienen acceso al firmware si el controlador realiza un sondeo antes de la instalación
el sistema de archivos basado en
almacenamiento permanente esté activado. En esos casos, es posible que la API de request_firmware*()
se bloquee durante mucho tiempo y, luego, falle, lo que puede ralentizar el proceso de inicio de forma innecesaria. En cambio, se debe diferir la carga del firmware
cuando un cliente comienza a usar el dispositivo. Por ejemplo, un controlador de pantalla podría cargar el firmware cuando se abre el dispositivo de pantalla.
El uso de .probe()
para cargar firmware puede ser adecuado en algunos casos, como en un controlador de reloj que necesita firmware para funcionar, pero el dispositivo no está expuesto al espacio del usuario. También hay otros casos de uso adecuados.
Implementa el sondeo asíncrono
Admite y usa 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 inicio, que se podrían agregar a Android en versiones futuras. Módulos de controlador que no usan el sondeo asíncrono podría reducir la eficacia de esas optimizaciones.
Para marcar un controlador como compatible y que prefiere el sondeo asíncrono, configura la
Campo probe_type
en el miembro struct device_driver
del conductor Lo siguiente
En el ejemplo, se muestra la compatibilidad habilitada para un controlador de plataforma:
static struct platform_driver acme_driver = {
.probe = acme_probe,
...
.driver = {
.name = "acme",
...
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
Para que un controlador funcione con sondeo asíncrono no se requiere un código especial. Sin embargo, ten en cuenta lo siguiente cuando agregues compatibilidad con la sondeo asíncrono.
No hagas suposiciones sobre las dependencias probadas con anterioridad. Verifica directamente o de forma indirecta (la mayoría de las llamadas al framework) y mostrar
-EPROBE_DEFER
si hay una o más los proveedores aún no están listos.Si agregas dispositivos secundarios a la función de sondeo de un dispositivo superior, no des por sentado que del dispositivo secundario se sondea de inmediato.
Si falla un sondeo, realiza un manejo adecuado de errores y realiza una limpieza (consulta Uso variantes de API de devm_*()).
No uses MODULE_SOFTDEP para solicitar sondeos de dispositivos
La función MODULE_SOFTDEP()
no es una solución confiable para garantizar el orden de las sondas de dispositivos y no debe usarse por los siguientes motivos.
Sondeo diferido. Cuando se carga un módulo, es posible que se aplace la prueba del dispositivo porque uno de sus proveedores no está listo. Esto puede generar una discrepancia el orden de carga del módulo y el orden de sondeo del dispositivo.
Un controlador para 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 sondeo diferente, no puedes respetar esos requisitos con el orden de carga de módulos.
Sondeo asíncrono. Módulos de controlador que realizan sondeos asíncronos no sondeen de inmediato un dispositivo cuando el módulo está cargado. En cambio, un subproceso paralelo controla el sondeo del dispositivo, lo que puede generar una discrepancia el orden de carga del módulo y el orden de sondeo del dispositivo. Por ejemplo, cuando un cable I2C el módulo del controlador principal 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 de PMIC se carga en el orden correcto, es posible que el sondeo del controlador táctil esté antes del sondeo del controlador de PMIC.
Si tienes módulos de controlador que usan la función MODULE_SOFTDEP()
, corrígelos de modo que
no usan esa función. Para ayudarte, el equipo de Android subió
cambios que permiten que el kernel maneje los problemas de ordenamiento sin usar
MODULE_SOFTDEP()
Específicamente, puedes usar fw_devlink
para asegurarte de que el sondeo
tenga el orden correcto y (después de que todos los consumidores de un dispositivo hayan sondeado) usar la
devolución de llamada sync_state()
para realizar las tareas necesarias.
Usa #if IS_ENABLED() en lugar de #ifdef para las configuraciones
Usa #if IS_ENABLED(CONFIG_XXX)
en lugar de #ifdef CONFIG_XXX
para asegurarte de que
el código del bloque #if
continúa compilándose si la configuración cambia a un
config. de triestado en el futuro. Las diferencias se muestran a continuación:
#if IS_ENABLED(CONFIG_XXX)
se evalúa comotrue
cuandoCONFIG_XXX
se establece como módulo (=m
) o integrado (=y
).#ifdef CONFIG_XXX
se evalúa comotrue
cuandoCONFIG_XXX
está configurado como integrado (=y
), pero no cuandoCONFIG_XXX
está configurado como módulo (=m
). Úsalo solo cuando tengas la certeza de que quieres hacer lo mismo cuando la configuración esté configurada como módulo o esté inhabilitada.
Usa la macro correcta para compilaciones condicionales
Si un CONFIG_XXX
se establece en módulo (=m
), el sistema de compilación define automáticamente CONFIG_XXX_MODULE
. Si CONFIG_XXX
y CONFIG_XXX
controla tu conductor
quieres comprobar si tu controlador se compila como un módulo, usa el comando
los siguientes lineamientos:
En el archivo C (o cualquier archivo fuente que no sea un archivo de encabezado) para tu controlador, no uses
#ifdef CONFIG_XXX_MODULE
, ya que es innecesariamente restrictivo y se interrumpe si se cambia el nombre de la configuración aCONFIG_XYZ
. Para cualquier archivo fuente que no sea de encabezado que se compila en un módulo, el sistema de compilación define automáticamenteMODULE
para el alcance de ese archivo. Por lo tanto, para comprobar si un archivo C (o cualquier un archivo de origen que no sea de encabezado) se compila como parte de un módulo, usa#ifdef MODULE
(sin el prefijoCONFIG_
).En los archivos de encabezado, la misma verificación es complicada, ya que estos no son se compilan directamente en un objeto binario, pero se compilan como parte de un archivo C (o otros archivos fuente). Usa las siguientes reglas para los archivos de encabezado:
En el caso de un archivo de encabezado que usa
#ifdef MODULE
, el resultado cambia según el archivo de origen que lo usa. Esto significa que el mismo archivo de encabezado en la misma puede compilar distintas partes de su código para distintas fuentes (módulo en comparación con integrado o inhabilitado). Esto puede ser útil cuando quieres para definir una macro que necesita expandirse de una manera para el código integrado de una manera diferente para un módulo.Para un archivo de encabezado que se debe compilar en un fragmento de código cuando un
CONFIG_XXX
específico se establece como módulo (independientemente de si el archivo fuente que lo incluye es un módulo), el archivo de encabezado debe usar#ifdef CONFIG_XXX_MODULE
.