Usa los siguientes lineamientos para aumentar la solidez y la confiabilidad de tus módulos de proveedores. Si se siguen muchos lineamientos, se puede determinar más fácilmente el orden de carga correcto de los módulos 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 APIs para que las usen otros módulos. Por lo general, estos módulos no son específicos del hardware. Entre los ejemplos de módulos de biblioteca, se incluyen un módulo de encriptación AES, el framework
remoteprocque se compila como un módulo y un módulo de logbuffer. El código del módulo enmodule_init()se ejecuta para configurar las 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. Estos módulos son específicos del hardware. Entre los ejemplos de módulos de controlador, se incluyen el hardware de UART, PCIe y el 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 framework principal del controlador.Si el dispositivo está presente y el controlador lo sondea o se vincula a él correctamente, es posible que se ejecute otro código de módulo.
Usa la inicialización y la salida del módulo correctamente
Los módulos de controlador deben registrar un controlador en module_init() y cancelar el registro en module_exit(). Una forma de aplicar estas restricciones es usar macros de wrapper, lo que evita el uso directo de las macros module_init(), *_initcall() o 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 registran más de un controlador. Para un módulo de controlador que usa module_init() y module_exit() para registrar varios controladores, intenta combinarlos en un solo controlador. Por ejemplo, puedes diferenciar con la cadena compatible o los datos auxiliares del dispositivo en lugar de registrar controladores separados.
Como alternativa, puedes dividir el módulo de controlador en dos módulos.
Excepciones de la función de inicialización 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 es posible que necesiten estas funciones para configurar estructuras de datos, colas de trabajo o subprocesos de kernel.
Usa 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 compatibles con 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 la carga de módulos para dispositivos que no están presentes en el sistema. Para obtener ejemplos sobre el uso de la macro, consulta el código ascendente.
Evita las discrepancias de CRC debido a los tipos de datos declarados de forma anticipada
No incluyas archivos de encabezado para obtener visibilidad en los tipos de datos declarados de forma anticipada.
Algunas estructuras, uniones y otros tipos de datos definidos en un archivo de encabezado (header-A.h) se pueden declarar de forma anticipada en un archivo de encabezado diferente (header-B.h) que suele usar punteros a esos tipos de datos. Este patrón de código significa que el kernel intenta intencionalmente 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 elementos internos de estas estructuras de datos declaradas de forma anticipada. Si lo haces, se producen problemas de discrepancia de CRC CONFIG_MODVERSIONS (que generan problemas de cumplimiento de ABI) cuando 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 declara de forma anticipada como struct fwnode_handle; en include/linux/device.h
porque el kernel intenta mantener los detalles de struct fwnode_handle
privados de los usuarios de include/linux/device.h. En este caso, 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 debas incluir esos archivos de encabezado indica un patrón de diseño incorrecto.
No accedas directamente a las estructuras principales del kernel
El acceso o la modificación directos de las estructuras de datos principales del kernel pueden generar un comportamiento no deseado, como pérdidas de memoria, fallas y compatibilidad interrumpida con versiones futuras del kernel. Una estructura de datos es una estructura de datos principal del kernel cuando cumple con cualquiera de las siguientes condiciones:
La estructura de datos se define en
KERNEL-DIR/include/. Por ejemplo,struct deviceystruct dev_links_info. Las estructuras de datos definidas eninclude/linux/socestán exentas.El módulo asigna o inicializa la estructura de datos, pero se hace visible para el kernel pasando, de forma indirecta (a través de un puntero en una estructura) o directa, como entrada en una función exportada por el kernel. Por ejemplo, un módulo de controlador
cpufreqinicializa elstruct cpufreq_drivery luego lo pasa como entrada acpufreq_register_driver(). Después de este punto, elcpufreqmódulo de controlador no debe modificarstruct cpufreq_driverdirectamente porque llamar acpufreq_register_driver()hace questruct cpufreq_driversea visible para el kernel.Tu módulo no inicializa la estructura de datos. Por ejemplo,
struct regulator_devque muestraregulator_register().
Accede a las estructuras de datos principales del kernel solo a través de funciones exportadas por el kernel o a través de parámetros que se pasan explícitamente como entrada a los hooks de proveedores. Si no tienes una API o un hook de proveedor para modificar partes de una estructura de datos principal del kernel, es probable que sea intencional y no debas modificar la estructura de datos de los módulos. Por ejemplo, no modifiques ningún campo dentro de struct device o
struct device.links.
Para modificar
device.devres_head, usa una funcióndevm_*(), comodevm_clk_get(),devm_regulator_get()odevm_kzalloc().Para modificar los campos dentro de
struct device.links, usa una API de vínculo de dispositivo, comodevice_link_add()odevice_link_del().
No analices los nodos de devicetree con la propiedad compatible
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 superior (por lo general, por el controlador de dispositivos del dispositivo superior). La expectativa predeterminada (excepto para algunos dispositivos inicializados de forma temprana para el programador) es que un nodo DT con una propiedad compatible tenga un struct device y un controlador de dispositivos coincidente. El código ascendente ya controla todas las demás excepciones.
Además, fw_devlink (antes llamado of_devlink) considera que los nodos DT con la propiedad compatible son dispositivos con un struct device asignado que son sondeados por un controlador. Si un nodo DT tiene una propiedad compatible, pero no se sondea el struct device asignado, fw_devlink podría impedir que sus dispositivos consumidores sondeen o que se llamen las llamadas sync_state() para sus dispositivos 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 dispositivos
que pueda sondear el dispositivo o quitar la propiedad compatible
(solo es posible si no se ha ascendido). Para analizar alternativas, comunícate
con el equipo de kernel de Android a kernel-team@android.com y prepárate para
justificar tus casos de uso.
Usa phandles de DT para buscar proveedores
Haz referencia a un proveedor con un phandle (una referencia o un puntero a un nodo DT) en DT siempre que sea posible. El uso de vinculaciones y phandles de DT estándar para hacer referencia a los proveedores permite que fw_devlink (antes of_devlink) determine automáticamente las dependencias entre dispositivos analizando el 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 ordenar la carga de módulos o MODULE_SOFTDEP().
Caso heredado (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 ACME PMIC podía registrar o anunciar varios
reguladores (como acme-pmic-ldo1 a acme-pmic-ldo10), y un controlador táctil
podí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 crea 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.
Caso actual (compatibilidad con DT en el kernel de ARM)
Después de que se agregó la compatibilidad con DT a los kernels de ARM, los consumidores pueden identificar proveedores en el DT haciendo referencia al nodo de árbol de dispositivos del proveedor con un phandle.
Los consumidores también pueden nombrar el recurso según para qué se usa 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 los
proveedores que alimentan el núcleo y el sensor del dispositivo táctil. El DT asociado para ese dispositivo 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 {
...
};
};
Caso de lo peor de ambos 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 la fuerza en el esquema más nuevo que está diseñado para facilitar las cosas. En esos controladores, el controlador consumidor 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 y, luego, el consumidor y el proveedor continúan usando el mismo esquema antiguo de usar cadenas para buscar el proveedor. En este caso de lo peor de ambos mundos, 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);El DT usa 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 de framework
Las APIs de framework, como regulator, clocks, irq, gpio, phys y extcon, muestran -EPROBE_DEFER como un valor de devolución de error para indicar que un dispositivo intenta sondear, pero no puede hacerlo en este momento, y el kernel debe volver a intentar el sondeo más tarde. Para asegurarte de que la función .probe() de tu dispositivo falle como se espera en esos casos, no reemplaces ni reasignes el valor de error.
Si reemplazas o reasignas el valor de error, es posible que se quite -EPROBE_DEFER y que el dispositivo nunca se sondee.
Usa variantes de la API de devm_*()
Cuando el dispositivo adquiere un recurso con una API de devm_*(), el kernel libera automáticamente el recurso si el dispositivo no puede sondear o si sondea correctamente y, luego, se desvincula. Esta capacidad 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 del controlador.
Controla la desvinculación del controlador de dispositivos
Sé intencional sobre la desvinculación de los controladores de dispositivos y no dejes la desvinculación sin definir, ya que no implica que no esté permitida. Debes implementar por completo la desvinculación del controlador de dispositivos o inhabilitarla de forma explícita.
Implementa la desvinculación del controlador de dispositivos
Cuando elijas implementar por completo la desvinculación del controlador de dispositivos, desvincula los controladores de dispositivos de forma limpia para evitar pérdidas de memoria o recursos y problemas de seguridad. Puedes 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 una función remove(), el kernel aún puede desvincular el dispositivo; el núcleo del controlador supone que el controlador no necesita ningún trabajo de limpieza cuando se desvincula del dispositivo. Un controlador que está desvinculado de un dispositivo no necesita realizar ningún trabajo de limpieza explícito cuando se cumplen las siguientes condiciones:
Todos los recursos adquiridos por la función
probe()de un controlador se realizan a través de las APIs dedevm_*().El dispositivo de hardware no necesita una secuencia de apagado o 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 instrucciones anteriores es falsa, el controlador debe realizar una limpieza (liberar recursos y apagar o desactivar el hardware) cuando se desvincula de un dispositivo. Para asegurarte de que un dispositivo pueda desvincular un módulo de controlador de forma limpia, usa una de las siguientes opciones:
Si el hardware no necesita una secuencia de apagado o inactividad, cambia el módulo del dispositivo para adquirir recursos con las APIs de
devm_*().Implementa la operación del controlador
remove()en la misma estructura que la funciónprobe()y, luego, realiza los pasos de limpieza con la funciónremove().
Inhabilita explícitamente la desvinculación del controlador de dispositivos (no recomendado)
Cuando elijas inhabilitar explícitamente la desvinculación del controlador de dispositivos, debes inhabilitar la desvinculación y la descarga del módulo.
Para inhabilitar la desvinculación, establece la marca
suppress_bind_attrsentrueen elstruct device_driverdel controlador. Este parámetro de configuración impide que los archivosbindyunbindse muestren en el directoriosysfsdel controlador. El archivounbindes lo que permite que el espacio del usuario active la desvinculación de un controlador de su dispositivo.Para inhabilitar 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 sondeo
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 sondea antes de que se active el sistema de archivos basado en flash o almacenamiento permanente. En esos casos, la API de request_firmware*() podría bloquearse durante un tiempo prolongado y, luego, fallar, lo que puede ralentizar el proceso de arranque de forma innecesaria. En su lugar, difiere la carga del firmware hasta que un cliente comience 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 el firmware puede ser correcto en algunos casos, como en un controlador de reloj que necesita firmware para funcionar, pero el dispositivo no está expuesto al espacio del usuario. Es posible que haya 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 paralelos o el sondeo de dispositivos para acelerar el tiempo de arranque, que se podrían agregar a Android en versiones futuras. Los módulos de controlador que no usan el sondeo asíncrono podrían reducir la eficacia de esas optimizaciones.
Para marcar un controlador como compatible y preferir el sondeo asíncrono, establece el campo probe_type en el miembro struct device_driver del controlador. En el siguiente ejemplo, se muestra esa 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 el sondeo asíncrono, no se requiere un código especial. Sin embargo, ten en cuenta lo siguiente cuando agregues compatibilidad con el sondeo asíncrono.
No hagas suposiciones sobre las dependencias sondeadas anteriormente. Verifica de forma directa o indirecta (la mayoría de las llamadas de framework) y muestra
-EPROBE_DEFERsi uno o más proveedores aún no están listos.Si agregas dispositivos secundarios en la función de sondeo de un dispositivo superior, no supongas que los dispositivos secundarios se sondean de inmediato.
Si falla un sondeo, realiza un control de errores y una limpieza adecuados (consulta Usa variantes de la API de devm_*()).
No uses MODULE_SOFTDEP para ordenar los sondeos de dispositivos
La función MODULE_SOFTDEP() no es una solución confiable para garantizar el orden de los sondeos de dispositivos y no se debe usar por los siguientes motivos.
Sondeo diferido. Cuando se carga un módulo, es posible que se difiera el sondeo del dispositivo porque uno de sus proveedores no está listo. Esto puede generar 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 sondeo diferente, no puedes respetar esos requisitos con el orden de carga del módulo.
Sondeo asíncrono. Los módulos de controlador que realizan el sondeo asíncrono no sondean de inmediato un dispositivo cuando se carga el módulo. En su lugar, un subproceso paralelo controla el sondeo del dispositivo, lo que puede generar una discrepancia entre el orden de carga del módulo y el orden de sondeo del dispositivo. Por ejemplo, cuando un módulo de controlador principal de 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 el sondeo del controlador táctil antes del sondeo del controlador PMIC.
Si tienes módulos de controlador que usan la función MODULE_SOFTDEP(), corrígelos para que no usen esa función. Para ayudarte, el equipo de Android ascendió los cambios que permiten que el kernel controle los problemas de orden sin usar MODULE_SOFTDEP(). En particular, puedes usar fw_devlink para garantizar el orden de sondeo y (después de que se hayan sondeado todos los consumidores de un dispositivo) 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 dentro del bloque #if continúe compilándose si la configuración cambia a una
configuración de tres estados en el futuro. Las diferencias se muestran a continuación:
#if IS_ENABLED(CONFIG_XXX)se evalúa comotruecuandoCONFIG_XXXse establece en módulo (=m) o integrado (=y).#ifdef CONFIG_XXXse evalúa comotruecuandoCONFIG_XXXse establece en integrado (=y) , pero no cuandoCONFIG_XXXse establece en módulo (=m). Usa esto solo cuando estés seguro de que deseas hacer lo mismo cuando la configuración se establece en módulo o está inhabilitada.
Usa la macro correcta para las compilaciones condicionales
Si un CONFIG_XXX se establece en módulo (=m), el sistema de compilación automáticamente
define CONFIG_XXX_MODULE. Si tu controlador está controlado por CONFIG_XXX y deseas verificar si se está compilando como un módulo, usa 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 el nombre de la configuración cambia 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áticamenteMODULEpara 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, usa#ifdef MODULE(sin el prefijoCONFIG_).En los archivos de encabezado, la misma verificación es más complicada porque los archivos de encabezado no se compilan directamente en un objeto binario, sino que se compilan como parte de un archivo C (o de otros archivos fuente). Usa las siguientes reglas para los archivos de encabezado:
Para un archivo de encabezado que usa
#ifdef MODULE, el resultado cambia según el archivo fuente que lo use. 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 en comparación con integrado o inhabilitado). Esto puede ser útil cuando deseas definir una macro que necesita expandirse de una manera para el código integrado y de una manera diferente para un módulo.Para un archivo de encabezado que necesita compilarse en un fragmento de código cuando un
CONFIG_XXXespecífico se establece en 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.