Lineamientos del módulo para proveedores

Usa los siguientes lineamientos para aumentar la solidez y la confiabilidad de los módulos de proveedores. Si se siguen muchos lineamientos, pueden ayudar a facilitar la determinación del orden de carga correcto 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 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 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 no se ejecuta ningún otro código, a menos que lo active un módulo externo.

  • Los módulos de controladores son controladores que buscan o se vinculan a un tipo específico de dispositivo. Estos módulos son específicos del hardware. Algunos ejemplos de módulos de controlador incluyen UART, PCIe y hardware de codificador de video. Los módulos de controladores solo se activan cuando su dispositivo asociado está presente en el sistema.

    • Si el dispositivo no está presente, el único código del 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 la inicialización y salida del módulo correctamente

Los módulos de controladores 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 de wrapper, lo que evita el uso directo de 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() y module_pci_driver().

  • Para los módulos que no se pueden descargar, usa builtin_subsystem_driver(). Ejemplos: builtin_platform_driver(), builtin_i2c_driver() y builtin_pci_driver().

Algunos módulos de controladores 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 uno solo. 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 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 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 cargar módulos para dispositivos que no están presentes en el sistema. Para ver ejemplos sobre el uso de la macro, consulta el código upstream.

Evita las discrepancias de CRC debido a los tipos de datos declarados previamente

No incluyas archivos de encabezado para obtener visibilidad de los tipos de datos declarados previamente. 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, por lo general, usa punteros a 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 las partes internas de estas estructuras de datos declaradas previamente. Si lo haces, se generarán problemas de discrepancia de la CRC CONFIG_MODVERSIONS (lo que genera problemas de cumplimiento de ABI) cuando un kernel diferente (como el de GKI) intente cargar el módulo.

Por ejemplo, struct fwnode_handle se define en include/linux/fwnode.h, pero se declara como struct fwnode_handle; en include/linux/device.h porque el kernel intenta mantener los detalles de struct fwnode_handle privados para los usuarios de include/linux/device.h. En este caso, no agregues #include <linux/fwnode.h> en un módulo para obtener acceso a 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 del kernel principal.

El acceso directo a las estructuras de datos del kernel principal o su modificación puede generar un comportamiento no deseado, como fugas de memoria, fallas y compatibilidad con versiones futuras del kernel. Una estructura de datos es una estructura de datos del kernel principal cuando cumple con 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 la hace visible al kernel pasándola, de forma indirecta (a través de un puntero en una estructura) o directa, como entrada en una función que exporta el kernel. Por ejemplo, un módulo de controlador cpufreq inicializa el struct cpufreq_driver y, luego, lo pasa como entrada a cpufreq_register_driver(). Después de este punto, el módulo del controlador cpufreq no debe modificar struct cpufreq_driver directamente, ya que llamar a cpufreq_register_driver() hace que struct cpufreq_driver sea visible para el kernel.

  • Tu módulo no inicializa la estructura de datos. Por ejemplo, struct regulator_dev que muestra regulator_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 de proveedores. Si no tienes una API o un hook de proveedor para modificar partes de la estructura de datos de un 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 o struct device.links.

  • Para modificar device.devres_head, usa una función devm_*(), como devm_clk_get(), devm_regulator_get() o devm_kzalloc().

  • Para modificar campos dentro de struct device.links, usa una API de vinculación de dispositivos, como device_link_add() o device_link_del().

No analiza los nodos de devicetree con la propiedad compatible.

Si un nodo del á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, el controlador de dispositivo del dispositivo superior). La expectativa predeterminada (excepto para algunos dispositivos inicializados con anticipación para el programador) es que un nodo DT con una propiedad compatible tenga un 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 que los nodos DT con la propiedad compatible son dispositivos con un struct device asignado que sondea un controlador. Si un nodo 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 el controlador usa una función of_find_*() (como of_find_node_by_name() o of_find_compatible_node()) para encontrar directamente un nodo de DT que tenga una propiedad compatible y, luego, analizar ese nodo, escribe un controlador de dispositivo que pueda sondear el dispositivo o quitar la propiedad compatible (solo es posible si no se subió un archivo upstream). 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 los identificadores de phandle de DT para buscar proveedores

Siempre que sea posible, haz referencia a un proveedor con un phandle (una referencia o un puntero a un nodo de DT) en DT. El uso de vinculaciones y controladores de objetos de datos estándar para hacer referencia a proveedores permite que fw_devlink (anteriormente of_devlink) determine automáticamente las dependencias entre dispositivos a través del análisis del DT en el tiempo de ejecución. Luego, el kernel puede probar los dispositivos automáticamente en el orden correcto, lo que quita la necesidad de ordenar la carga de módulos 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 proporcionar 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 para 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 que se agregó compatibilidad con la DT a los kernels de ARM, los consumidores pueden identificar a los proveedores en la DT haciendo referencia al nodo del árbol de dispositivos del proveedor con un phandle. Los consumidores también pueden nombrar el recurso en función del 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 los proveedores que alimentan el núcleo y el sensor del dispositivo táctil. La DT asociada 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 {
        ...
    };
};

Situación del peor de los casos

Algunos controladores transferidos de kernels más antiguos incluyen el comportamiento heredado en el DT que toma la peor parte del esquema heredado y la fuerza en el esquema más nuevo, 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 y, 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);
    
  • 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 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 sondeo más adelante. 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. Reemplazar o reasignar el valor de error puede provocar que se descarte -EPROBE_DEFER y que nunca se pruebe el dispositivo.

Usa variantes de la API de devm_*()

Cuando el dispositivo adquiere un recurso mediante una API de devm_*(), el kernel libera automáticamente el recurso si el dispositivo no puede sondear o si lo hace correctamente y, luego, se desvincula. Esta función hace que el código de manejo de errores de la función probe() sea más limpio, ya que no requiere saltos goto para liberar los recursos que adquirió devm_*() y simplifica las operaciones de desvinculación del controlador.

Cómo controlar la desvinculación de controladores 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 controlador de dispositivo

Cuando elijas implementar por completo la desvinculación de controladores de dispositivos, hazlo de forma ordenada para evitar fugas de memoria o recursos y problemas de seguridad. Para vincular un dispositivo a un controlador, llama a la función probe() del controlador y, para desvincular un dispositivo, llama a la función remove() del controlador. Si no existe una función remove(), el kernel puede desvincular el dispositivo de todos modos; el núcleo del controlador supone que el controlador no necesita realizar tareas de limpieza cuando se desvincula del dispositivo. Un controlador que no está vinculado a un dispositivo no necesita realizar ninguna tarea de limpieza explícita cuando se cumplen las siguientes condiciones:

  • Todos los recursos que adquiere la función probe() de un controlador son a través de las APIs de devm_*().

  • 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 no es verdadera, el controlador debe realizar una limpieza (liberar recursos y apagar o suspender 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 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ón probe() y, luego, realiza los pasos de limpieza con la función remove().

Inhabilita de forma explícita 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 como true en el struct device_driver del controlador. Este parámetro de 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úrate de que el módulo tenga [permanent] en lsmod. Si no usas module_exit() ni module_XXX_driver(), el módulo se marca como [permanent].

No cargues el 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 sondea antes de que se monte el sistema de archivos basado en almacenamiento flash o permanente. En esos casos, la API de request_firmware*() podría bloquearse durante mucho tiempo y, luego, fallar, lo que puede ralentizar el proceso de inicio de manera innecesaria. En su lugar, aplaza 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 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 se pueden aplicar 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. Los módulos de controlador que no usan sondeo asíncrono podrían reducir la eficacia de esas optimizaciones.

Para marcar un controlador como compatible y que prefiere el sondeo asíncrono, configura 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,
        },
};

Hacer que un controlador funcione con sondeos asíncronos no requiere 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 indirectamente (la mayoría de las llamadas al framework) y muestra -EPROBE_DEFER si uno o más proveedores aún no están listos.

  • Si agregas dispositivos secundarios en la función de sondeo de un dispositivo principal, no des por sentado que los dispositivos secundarios se sondean de inmediato.

  • Si falla un sondeo, realiza un manejo de errores adecuado y realiza una limpieza (consulta Cómo usar 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 entre el orden de carga del módulo y el orden del 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. Los módulos de controladores que realizan sondeos asíncronos no sondean un dispositivo de inmediato cuando se carga el módulo. En su lugar, un subproceso en paralelo controla la detección de dispositivos, lo que puede generar una discrepancia entre el orden de carga del módulo y el orden de detección del dispositivo. Por ejemplo, cuando un módulo de controlador principal 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, el sondeo del controlador táctil podría intentarse antes que el 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 envió cambios upstream que permiten que el kernel controle los problemas de orden sin usar MODULE_SOFTDEP(). Específicamente, puedes usar fw_devlink para garantizar el pedido de sondeo y (después de que todos los consumidores de un dispositivo hayan sondeado) 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 siga 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 como true cuando CONFIG_XXX se establece 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 en el módulo (=m). Usa esta opción solo si estás seguro de que quieres hacer lo mismo cuando la configuración está establecida en el 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 define automáticamente CONFIG_XXX_MODULE. Si CONFIG_XXX controla tu controlador y quieres 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 se cambia el nombre de la configuración a CONFIG_XYZ. Para cualquier archivo de origen que no sea de encabezado que se compile en un módulo, el sistema de compilación definirá automáticamente MODULE para el alcance de ese archivo. Por lo tanto, para verificar si se está compilando un archivo C (o cualquier archivo fuente sin encabezado) como parte de un módulo, usa #ifdef MODULE (sin el prefijo CONFIG_).

  • En los archivos de encabezado, la misma verificación es más complicada porque los archivos de encabezado no se compilan directamente en un archivo binario, sino como parte de un archivo C (o de 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 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 se debe expandir de una manera para el código incorporado y de otra manera para un módulo.

    • En el caso de un archivo de encabezado que debe compilarse 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.