Directrices del módulo de proveedores

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 en module_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() y module_pci_driver() .

  • Para los módulos que no se pueden descargar, use builtin_ subsystem _driver() Ejemplos: builtin_platform_driver() , builtin_i2c_driver() y builtin_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 y struct dev_links_info . Las estructuras de datos definidas en include/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 la struct cpufreq_driver y luego la pasa como entrada a cpufreq_register_driver() . Después de este punto, el módulo del controlador cpufreq no debería modificar struct cpufreq_driver directamente porque llamar a cpufreq_register_driver() hace que struct cpufreq_driver visible para el kernel.

  • Su módulo no inicializa la estructura de datos. Por ejemplo, struct regulator_dev devuelto por regulator_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 una devm_*() como devm_clk_get() , devm_regulator_get() o devm_kzalloc() .

  • Para modificar campos dentro struct device.links , use una API de vínculo de dispositivo como device_link_add() o device_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és devm_*() .

  • 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ón probe() , luego realice los pasos de limpieza usando la función remove() .

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 en true en la estructura del controlador struct device_driver ; esta configuración evita que los archivos bind y unbind se muestren en el directorio sysfs del controlador. El archivo de unbind 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] en lsmod . Al no usar module_exit() o module_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 como true cuando CONFIG_XXX se establece en módulo ( =m ) o integrado ( =y ).

  • #ifdef CONFIG_XXX evalúa como true cuando CONFIG_XXX está configurado como integrado ( =y ) , pero no cuando CONFIG_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 a CONFIG_XYZ . Para cualquier archivo fuente que no sea de encabezado que se compile en un módulo, el sistema de compilación define automáticamente MODULE 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 prefijo CONFIG_ ).

  • 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 .