Directrices del módulo de proveedores

Utilice las siguientes pautas para aumentar la solidez y confiabilidad de los módulos de su proveedor. Muchas pautas, cuando se siguen, pueden ayudar a que sea más fácil determinar el orden correcto de carga de los módulos y el orden en el que los controladores deben buscar dispositivos.

Un módulo puede ser una biblioteca o un controlador .

  • Los módulos de biblioteca son bibliotecas que proporcionan API para que las utilicen otros módulos. Estos módulos normalmente no son específicos de hardware. Ejemplos de módulos de biblioteca incluyen un módulo de cifrado AES, el marco de 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 buscan o se vinculan a un tipo específico de dispositivo. Estos módulos son específicos del hardware. Ejemplos de módulos de controlador incluyen UART, PCIe y hardware 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 central 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.

Utilice el módulo de inicio/salida 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 sencilla de hacer cumplir estas restricciones es utilizar macros contenedoras, lo que evita el uso directo de las macros module_init() , *_initcall() o module_exit() .

  • Para los módulos que se pueden descargar, utilice 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() builtin_pci_driver() .

Algunos módulos de controladores utilizan module_init() y module_exit() porque registran más de un controlador. Para un módulo de controlador que utiliza module_init() y module_exit() para registrar varios controladores, intente combinar los controladores en uno solo. Por ejemplo, podrías 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 la biblioteca no registran controladores y están exentos de restricciones sobre 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 al usuario determinar 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 ascendente.

Evite discrepancias de CRC debido a 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 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 intenta intencionalmente mantener la estructura de datos privada para los usuarios de header-Bh .

Los usuarios de header-Bh no deberían incluir header-Ah para acceder directamente a las partes internas de estas estructuras de datos declaradas hacia adelante. Hacerlo causa problemas de discrepancia 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 kernel 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 deba 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 centrales del kernel puede provocar comportamientos no deseados, incluidas pérdidas de memoria, fallas y compatibilidad rota con futuras versiones del kernel. Una estructura de datos es una estructura de datos del núcleo central cuando cumple cualquiera 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.

  • La estructura de datos es asignada o inicializada por el módulo, pero se hace visible para el núcleo al pasarla, indirectamente (a través de un puntero en una estructura) o directamente, como entrada en una función exportada por el núcleo. 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 cpufreq_register_driver() hace que struct cpufreq_driver sea visible para el kernel.

  • La estructura de datos no es inicializada por su módulo. Por ejemplo, struct regulator_dev devuelta por regulator_register() .

Acceda a las estructuras de datos centrales del kernel solo a través de funciones exportadas por el kernel o mediante parámetros pasados ​​explícitamente como entrada a los enlaces del proveedor. Si no tiene una API o un enlace de proveedor para modificar partes de una estructura de datos del núcleo central, probablemente sea intencional y no debería 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 , utilice una función devm_*() como devm_clk_get() , devm_regulator_get() o devm_kzalloc() .

  • Para modificar campos dentro de struct device.links , utilice una API de enlace 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 of_platform_populate() en el nodo DT principal (normalmente mediante el controlador de dispositivo del dispositivo principal). La expectativa predeterminada (excepto para algunos dispositivos inicializados anticipadamente 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 consumidores o podría bloquear las llamadas sync_state() para sus dispositivos proveedores.

Si su 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, arregle el módulo escribiendo un controlador de dispositivo que pueda sondear el dispositivo o eliminar la propiedad compatible (solo es posible si no se ha actualizado). Para analizar alternativas, comuníquese con el equipo del kernel de Android en kernel-team@android.com y prepárese para justificar sus casos de uso.

Utilice DT phandles para buscar proveedores

Consulte a un proveedor que utilice un phandle (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 proveedores permite que fw_devlink (anteriormente of_devlink ) determine automáticamente las dependencias entre dispositivos analizando el 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 del módulo o MODULE_SOFTDEP() .

Escenario heredado (sin soporte DT en el kernel ARM)

Anteriormente, antes de que se agregara el soporte DT a los núcleos ARM, los consumidores, como los dispositivos táctiles, buscaban proveedores, como los reguladores, que utilizaban cadenas únicas a nivel mundial. Por ejemplo, el controlador ACME PMIC podría registrar o anunciar múltiples reguladores (como acme-pmic-ldo1 a acme-pmic-ldo10 ) y un controlador táctil podría buscar un regulador usando 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 (soporte DT en el kernel ARM)

Después de que se agregó el soporte de 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 DT 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 {
        ...
    };
};

El peor escenario de ambos mundos

Algunos controladores trasladados desde núcleos 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 destinado a facilitar las cosas. En dichos controladores, el controlador del consumidor lee la cadena que se usará para la búsqueda usando 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 continúan usando el mismo viejo esquema de usar cadenas para buscar al proveedor. En este escenario del peor de ambos mundos:

  • El controlador táctil utiliza un 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 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 de framework, como regulator , clocks , irq , gpio , phys y extcon , devuelven -EPROBE_DEFER como 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 intentarlo más tarde. Para asegurarse de que la función .probe() de su dispositivo falle como se espera en tales casos, no reemplace ni reasigne el valor de error. Reemplazar o reasignar el valor de error puede causar que -EPROBE_DEFER se elimine y que su dispositivo nunca sea sondeado.

Utilice variantes de API devm_*()

Cuando el dispositivo adquiere un recurso mediante una API devm_*() , el kernel libera automáticamente el recurso si el dispositivo no realiza la prueba o si la prueba tiene é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 del controlador.

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 no definido no implica que no esté permitido. Debe implementar completamente la desvinculación del controlador de dispositivo o deshabilitar explícitamente la desvinculación del controlador de dispositivo.

Implementación de la desvinculación del controlador de dispositivo

Al optar por implementar completamente la desvinculación de controladores de dispositivos, desvincule los controladores de dispositivos limpiamente para evitar pérdidas 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 kernel aún puede desvincular el dispositivo; el núcleo del controlador asume que no es necesario realizar ningún trabajo de limpieza cuando el controlador 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 de las API devm_*() .

  • El dispositivo de hardware no necesita una secuencia de apagado o inactividad.

En esta situación, el núcleo del controlador se encarga de liberar todos los recursos adquiridos a través de las API devm_*() . Si alguna de las afirmaciones anteriores no es cierta, el controlador debe realizar una limpieza (liberar recursos y apagar o inmovilizar el hardware) cuando se desvincula de un dispositivo. Para garantizar que un dispositivo pueda desvincular un módulo de controlador limpiamente, utilice 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 las API 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 del controlador de dispositivo (no recomendado)

Al elegir deshabilitar explícitamente la desvinculación del controlador de dispositivo, debe no permitir la desvinculación y no permitir la descarga del módulo.

  • Para no permitir la desvinculación, establezca el indicador suppress_bind_attrs en true en la struct device_driver del controlador; esta configuración evita que los archivos bind y unbind se muestren en el directorio sysfs del controlador. El archivo 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 utilizar module_exit() o module_XXX_driver() , el módulo se marca como [permanent] .

No cargue firmware desde 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 flash o permanente. 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 hasta que un cliente comience a utilizar el dispositivo. Por ejemplo, un controlador de pantalla podría cargar el firmware cuando se abre el dispositivo de pantalla.

Usar .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 asincrónico

Admite y utiliza el sondeo asincrónico para aprovechar mejoras futuras, como la carga de módulos paralelos 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 utilizan sondeo asincrónico podrían reducir la eficacia de dichas optimizaciones.

Para marcar un controlador como compatible y que prefiere el sondeo asincrónico, establezca 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 asincrónico no requiere código especial. Sin embargo, tenga en cuenta lo siguiente al agregar soporte de sondeo asincrónico.

  • No haga suposiciones sobre dependencias previamente investigadas. 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 falla una sonda, realice una gestión de errores y una limpieza adecuadas (consulte Uso de variantes de API devm_*() ).

No utilice MODULE_SOFTDEP para solicitar 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.

  • Sondeo diferido. Cuando se carga un módulo, la prueba del dispositivo podría 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 sonda del dispositivo.

  • Un controlador, muchos dispositivos. Un módulo de controlador puede gestionar 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 asincrónico. Los módulos de controlador que realizan sondeos asíncronos no sondean inmediatamente un dispositivo cuando se carga el módulo. En cambio, un subproceso paralelo maneja el sondeo del dispositivo, lo que puede provocar una falta de coincidencia entre el orden de carga del módulo y el orden de sondeo del dispositivo. Por ejemplo, cuando un módulo controlador principal I2C realiza un sondeo asíncrono y un módulo 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 probar el controlador táctil antes. la sonda del controlador PMIC.

Si tiene módulos de controlador que usan la función MODULE_SOFTDEP() , arréglelos para que no usen esa función. Para ayudarlo, el equipo de Android ha actualizado cambios que permiten que el kernel maneje problemas de pedidos sin usar MODULE_SOFTDEP() . Específicamente, puede usar fw_devlink para garantizar el pedido de sondeo 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.

Utilice #if IS_ENABLED() en lugar de #ifdef para las configuraciones

Utilice #if IS_ENABLED(CONFIG_XXX) en lugar de #ifdef CONFIG_XXX para garantizar que el código dentro del bloque #if continúe compilándose si la configuración cambia a una configuración triestado en el futuro. Las diferencias son las siguientes:

  • #if IS_ENABLED(CONFIG_XXX) se evalúa como true cuando CONFIG_XXX está configurado como módulo ( =m ) o integrado ( =y ).

  • #ifdef CONFIG_XXX se evalúa como true cuando CONFIG_XXX está configurado como integrado ( =y ), pero no lo hace cuando CONFIG_XXX está configurado como módulo ( =m ). Use esto solo cuando esté seguro de querer hacer lo mismo cuando la configuración esté configurada en módulo o esté deshabilitada.

Utilice 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 su controlador está controlado por CONFIG_XXX y desea verificar si su controlador se está compilando como un módulo, utilice las siguientes pautas:

  • En el archivo C (o cualquier archivo fuente que no sea un archivo de encabezado) de su controlador, no use #ifdef CONFIG_XXX_MODULE ya que es innecesariamente restrictivo y se rompe si se cambia el nombre de la configuración a CONFIG_XYZ . Para cualquier archivo fuente sin encabezado que se compila 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 sin 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 complicada 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 archivos de encabezado:

    • Para un archivo de encabezado que usa #ifdef MODULE , el resultado cambia según el archivo fuente 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 integrado o deshabilitado). Esto puede resultar ú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 un fragmento de código cuando un CONFIG_XXX específico está configurado 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 .