Lineamientos del módulo de proveedores

Usa los siguientes lineamientos para aumentar la solidez y la confiabilidad de tus módulos del proveedor. Muchos lineamientos, cuando se siguen, pueden ayudar a determinar 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 remoteproc que se compila como un módulo y un módulo de logbuffer. 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 controlador son controladores que sondean o se vinculan a un tipo específico de dispositivo. Estos módulos son específicos del hardware. Algunos ejemplos de módulos de controladores son el hardware de UART, PCIe y 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 sondea o se vincula correctamente a ese dispositivo, es posible que se ejecute otro código del módulo.

Usa la inicialización y la 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 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() 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. En el caso de un módulo de controlador que usa module_init() y module_exit() para registrar varios controladores, intenta combinar los controladores en uno solo. Por ejemplo, podrías diferenciarte usando la cadena compatible o los datos auxiliares del dispositivo en lugar de registrar conductores independientes. Como alternativa, puedes dividir el módulo del controlador en dos módulos.

Excepciones de las funciones 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 del kernel.

Usa la macro MODULE_DEVICE_TABLE

Los módulos de controladores deben incluir la macro MODULE_DEVICE_TABLE, que permite que el espacio del usuario determine los dispositivos compatibles con un módulo de controlador antes de 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 del uso de la macro, consulta el código upstream.

Evita las discrepancias de CRC debido a los tipos de datos declarados por adelantado

No incluyas archivos de encabezado para obtener visibilidad de los tipos de datos declarados con anticipación. Algunas structs, uniones y otros tipos de datos definidos en un archivo de encabezado (header-A.h) se pueden declarar por adelantado 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 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 por adelantado. Si lo haces, se producen problemas de discrepancia del CRC de CONFIG_MODVERSIONS (que generan problemas de cumplimiento de la 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 por adelantado 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 acceder a los miembros de struct fwnode_handle. Cualquier diseño en el que debas incluir esos archivos de encabezado indica un mal patrón de diseño.

No accedas directamente a las estructuras principales del kernel

Acceder o modificar directamente las estructuras de datos principales del kernel puede generar un comportamiento no deseado, como pérdidas de memoria, fallas y compatibilidad interrumpida con futuras versiones del kernel. Una estructura de datos es una estructura de datos del kernel principal cuando cumple con 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.

  • El módulo asigna o inicializa la estructura de datos, pero la hace visible para el kernel pasándola, 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 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 los parámetros que se pasan de forma explícita como entrada a los hooks del proveedor. Si no tienes un gancho de API o de proveedor para modificar partes de una estructura de datos del kernel principal, probablemente sea intencional y no deberías modificar la estructura de datos desde 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 los campos dentro de struct device.links, usa una API de vinculación de dispositivos, como device_link_add() o device_link_del().

No se analizan los nodos de árbol de dispositivos 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 principal (por lo general, por el controlador 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. 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 de DT tiene una propiedad compatible, pero no se sondea el struct device asignado, fw_devlink podría impedir que sus dispositivos de consumo sondeen o podría impedir que se llamen las llamadas de sync_state() para sus dispositivos de proveedor.

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, analiza ese nodo DT, corrige el módulo escribiendo un controlador de dispositivo que pueda sondear el dispositivo o quitar la propiedad compatible (solo es posible si no se ha enviado a la versión upstream). Para analizar alternativas, comunícate con el equipo del kernel de Android a kernel-team@android.com y prepárate para justificar tus casos de uso.

Cómo usar identificadores 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 phandles estándar de DT para hacer referencia a los proveedores permite que fw_devlink (anteriormente 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().

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 los reguladores, con cadenas únicas a nivel global. Por ejemplo, el controlador 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 otra placa, el LDO8 podría suministrar el dispositivo táctil, lo que crearía un sistema engorroso en el que el mismo controlador táctil debería determinar la cadena de búsqueda correcta para el regulador de cada placa en la que se usa el dispositivo táctil.

Situación actual (compatibilidad 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 a los proveedores en el DT haciendo referencia al nodo del á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 un dispositivo de este tipo es similar a la siguiente muestra de código:

touch-device {
    compatible = "fizz,touch";
    ...
    core-supply = <&acme_pmic_ldo4>;
    sensor-supply = <&acme_pmic_ldo10>;
};

acme-pmic {
    compatible = "acme,super-pmic";
    ...
    acme_pmic_ldo4: ldo4 {
        ...
    };
    ...
    acme_pmic_ldo10: ldo10 {
        ...
    };
};

Situación en la que se combinan los peores aspectos de dos situaciones

Algunos controladores portados desde 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á destinado a facilitar las cosas. En estos controladores, el controlador del 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 siguen usando el mismo esquema anterior de cadenas para buscar al proveedor. En este caso, que es lo peor de ambos mundos, ocurre lo siguiente:

  • El controlador táctil usa 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 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 del framework, como regulator, clocks, irq, gpio, phys y extcon, devuelven -EPROBE_DEFER como valor de devolución de error para indicar que un dispositivo está intentando sondear, pero no puede hacerlo en este momento, y que el kernel debería 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. Reemplazar o reasignar el valor de error podría provocar que se descarte -EPROBE_DEFER y que nunca se sondee tu dispositivo.

Usa variantes de la API devm_*()

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

Cómo controlar la desvinculación de controladores de dispositivos

Sé intencional al desvincular los controladores de dispositivos y no dejes la desvinculación sin definir, ya que indefinido no implica prohibido. Debes implementar por completo la desvinculación del controlador del dispositivo o inhabilitar explícitamente la desvinculación del controlador del dispositivo.

Implementa la desvinculación del controlador del dispositivo

Cuando elijas implementar por completo la desvinculación del controlador del dispositivo, desvincula los controladores del dispositivo 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 ninguna función remove(), el kernel 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 no está vinculado a un dispositivo no necesita realizar ningún trabajo de limpieza explícito cuando se cumplen las siguientes condiciones:

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

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

En esta situación, el núcleo del controlador se encarga de liberar todos los recursos adquiridos a través de las APIs de devm_*(). Si alguna de las afirmaciones anteriores es falsa, el controlador debe realizar una limpieza (liberar recursos y apagar o detener 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 de 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 struct que la función probe() y, luego, realiza los pasos de limpieza con la función remove().

Cómo inhabilitar explícitamente la desvinculación del controlador de dispositivo (no recomendado)

Cuando elijas inhabilitar explícitamente la desvinculación de controladores de dispositivos, deberás rechazar la desvinculación y rechazar la descarga del módulo.

  • Para inhabilitar la desvinculación, configura la marca suppress_bind_attrs como true en el struct device_driver del controlador. Este parámetro de configuración impide 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 se usan module_exit() ni module_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 monte el sistema de archivos basado en el almacenamiento flash o 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 inicio de forma 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.

Usar .probe() para cargar el firmware puede ser aceptable 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 son posibles otros casos de uso adecuados.

Implementa la detección asíncrona

Admite y usa la detección asíncrona para aprovechar las mejoras futuras, como la carga de módulos paralela o la detección de dispositivos para acelerar el tiempo de arranque, que se podrían agregar a Android en versiones futuras. Los módulos de controladores que no usan la detección asíncrona podrían reducir la eficacia de estas optimizaciones.

Para marcar un controlador como compatible con la detección asíncrona y que la prefiere, establece el campo probe_type en el miembro struct device_driver del controlador. En el siguiente ejemplo, se muestra la compatibilidad habilitada para un controlador de plataforma:

static struct platform_driver acme_driver = {
        .probe          = acme_probe,
        ...
        .driver         = {
                .name   = "acme",
                ...
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
};

Para que un controlador funcione con la detección asíncrona, no se requiere código especial. Sin embargo, ten en cuenta lo siguiente cuando agregues compatibilidad con la detección asíncrona.

  • No hagas suposiciones sobre las dependencias que ya se sondearon. Verifica de forma directa o indirecta (la mayoría de las llamadas del framework) y devuelve -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 supongas que se sondean de inmediato.

  • Si falla una sonda, realiza el control de errores y la limpieza adecuados (consulta Usa variantes de la API de devm_*()).

No uses MODULE_SOFTDEP para ordenar las 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 se debe usar por los siguientes motivos.

  • Sondeo diferido. Cuando se carga un módulo, es posible que se posponga la sonda 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 cumplir con esos requisitos usando el orden de carga del módulo.

  • Sondeo asíncrono. Los módulos de controlador que realizan sondeos asíncronos no sondean un dispositivo de inmediato cuando se carga el módulo. En su lugar, un subproceso paralelo controla la detección del dispositivo, 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 de I2C realiza una detección asíncrona y un módulo de controlador táctil depende del PMIC que se encuentra en el bus I2C, incluso si el controlador táctil y el controlador de PMIC se cargan en el orden correcto, es posible que se intente la detección del controlador táctil antes de la detección del controlador de PMIC.

Si tienes módulos de controladores que usan la función MODULE_SOFTDEP(), corrígelos para que no la usen. Para ayudarte, el equipo de Android incorporó cambios que permiten que el kernel controle los problemas de ordenamiento sin usar MODULE_SOFTDEP(). Específicamente, puedes usar fw_devlink para garantizar el orden de las sondas y (después de que todos los consumidores de un dispositivo hayan realizado la sondeo) 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 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 en módulo (=m) o integrado (=y).

  • #ifdef CONFIG_XXX se evalúa como true cuando CONFIG_XXX se establece en integrado (=y) , pero no cuando CONFIG_XXX se establece en módulo (=m). Usa esto solo cuando tengas la certeza de que quieres hacer lo mismo cuando el parámetro de configuración se establece en módulo o se inhabilita.

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 tu controlador está controlado por CONFIG_XXX 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) de 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 fuente que no sea de encabezado y 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 compila 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 difícil porque 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 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 frente a integrado o inhabilitado). Esto puede ser útil cuando deseas definir una macro que debe expandirse de una manera para el código integrado 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 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.