Lineamientos del módulo de proveedores

Usa los siguientes lineamientos para aumentar la solidez y la confiabilidad de tus módulos de proveedores. Si se siguen muchos lineamientos, se puede determinar más fácilmente el orden de carga correcto de los módulos y el orden en el que los controladores deben sondear los dispositivos.

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

  • Los módulos de biblioteca son bibliotecas que proporcionan APIs para que las usen otros módulos. Por lo general, estos módulos no son específicos del hardware. Entre los ejemplos de módulos de biblioteca, se incluyen un módulo de encriptación AES, el framework 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 las estructuras de datos, pero no se ejecuta ningún otro código, a menos que lo active un módulo externo.

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

    • Si el dispositivo no está presente, el único código de módulo que se ejecuta es el código module_init() que registra el controlador con el framework principal del controlador.

    • Si el dispositivo está presente y el controlador lo sondea o se vincula a él correctamente, es posible que se ejecute otro código de módulo.

Usa la inicialización y la salida del módulo correctamente

Los módulos de controlador deben registrar un controlador en module_init() y cancelar el registro en module_exit(). Una forma de aplicar estas restricciones es usar macros de wrapper, lo que evita el uso directo de las macros module_init(), *_initcall() o module_exit().

  • Para los módulos que se pueden descargar, usa module_subsystem_driver(). Ejemplos: module_platform_driver(), module_i2c_driver() 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 controlador usan module_init() y module_exit() porque registran más de un controlador. Para un módulo de controlador que usa module_init() y module_exit() para registrar varios controladores, intenta combinarlos en un solo controlador. Por ejemplo, puedes diferenciar con la cadena compatible o los datos auxiliares del dispositivo en lugar de registrar controladores separados. Como alternativa, puedes dividir el módulo de controlador en dos módulos.

Excepciones de la función de inicialización y salida

Los módulos de biblioteca no registran controladores y están exentos de las restricciones en module_init() y module_exit(), ya que es posible que necesiten estas funciones para configurar estructuras de datos, colas de trabajo o subprocesos de kernel.

Usa la macro MODULE_DEVICE_TABLE

Los módulos de controlador deben incluir la macro MODULE_DEVICE_TABLE, que permite que el espacio del usuario determine los dispositivos compatibles con un módulo de controlador antes de cargar el módulo. Android puede usar estos datos para optimizar la carga de módulos, por ejemplo, para evitar la carga de módulos para dispositivos que no están presentes en el sistema. Para obtener ejemplos sobre el uso de la macro, consulta el código ascendente.

Evita las discrepancias de CRC debido a los tipos de datos declarados de forma anticipada

No incluyas archivos de encabezado para obtener visibilidad en los tipos de datos declarados de forma anticipada. Algunas estructuras, uniones y otros tipos de datos definidos en un archivo de encabezado (header-A.h) se pueden declarar de forma anticipada en un archivo de encabezado diferente (header-B.h) que suele usar punteros a esos tipos de datos. Este patrón de código significa que el kernel intenta intencionalmente mantener la estructura de datos privada para los usuarios de header-B.h.

Los usuarios de header-B.h no deben incluir header-A.h para acceder directamente a los elementos internos de estas estructuras de datos declaradas de forma anticipada. Si lo haces, se producen problemas de discrepancia de CRC CONFIG_MODVERSIONS (que generan problemas de cumplimiento de ABI) cuando un kernel diferente (como el kernel de GKI) intenta cargar el módulo.

Por ejemplo, struct fwnode_handle se define en include/linux/fwnode.h, pero se declara de forma anticipada como struct fwnode_handle; en include/linux/device.h porque el kernel intenta mantener los detalles de struct fwnode_handle privados de los usuarios de include/linux/device.h. En este caso, no agregues #include <linux/fwnode.h> en un módulo para obtener acceso a los miembros de struct fwnode_handle. Cualquier diseño en el que debas incluir esos archivos de encabezado indica un patrón de diseño incorrecto.

No accedas directamente a las estructuras principales del kernel

El acceso o la modificación directos de las estructuras de datos principales del kernel pueden generar un comportamiento no deseado, como pérdidas de memoria, fallas y compatibilidad interrumpida con versiones futuras del kernel. Una estructura de datos es una estructura de datos principal del kernel cuando cumple con cualquiera de las siguientes condiciones:

  • La estructura de datos se define en KERNEL-DIR/include/. Por ejemplo, struct 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 pasando, de forma indirecta (a través de un puntero en una estructura) o directa, como entrada en una función exportada por el kernel. Por ejemplo, un módulo de controlador cpufreq inicializa el struct cpufreq_driver y luego lo pasa como entrada a cpufreq_register_driver(). Después de este punto, el cpufreq módulo de controlador no debe modificar struct cpufreq_driver directamente porque 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 funciones exportadas por el kernel o a través de parámetros que se pasan explícitamente como entrada a los hooks de proveedores. Si no tienes una API o un hook de proveedor para modificar partes de una estructura de datos principal del kernel, es probable que sea intencional y no debas modificar la estructura de datos de los módulos. Por ejemplo, no modifiques ningún campo dentro de struct device o struct device.links.

  • Para modificar device.devres_head, usa una funció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 vínculo de dispositivo, como device_link_add() o device_link_del().

No analices los nodos de devicetree con la propiedad compatible

Si un nodo de árbol de dispositivos (DT) tiene una propiedad compatible, se le asigna un struct device automáticamente o cuando se llama a of_platform_populate() en el nodo DT superior (por lo general, por el controlador de dispositivos del dispositivo superior). La expectativa predeterminada (excepto para algunos dispositivos inicializados de forma temprana para el programador) es que un nodo DT con una propiedad compatible tenga un struct device y un controlador de dispositivos coincidente. El código ascendente ya controla todas las demás excepciones.

Además, fw_devlink (antes llamado of_devlink) considera que los nodos DT con la propiedad compatible son dispositivos con un struct device asignado que son sondeados por un controlador. Si un nodo DT tiene una propiedad compatible, pero no se sondea el struct device asignado, fw_devlink podría impedir que sus dispositivos consumidores sondeen o que se llamen las llamadas sync_state() para sus dispositivos proveedores.

Si tu controlador usa una función of_find_*() (como of_find_node_by_name() o of_find_compatible_node()) para encontrar directamente un nodo DT que tenga una propiedad compatible y, luego, analizar ese nodo DT, corrige el módulo escribiendo un controlador de dispositivos que pueda sondear el dispositivo o quitar la propiedad compatible (solo es posible si no se ha ascendido). Para analizar alternativas, comunícate con el equipo de kernel de Android a kernel-team@android.com y prepárate para justificar tus casos de uso.

Usa phandles de DT para buscar proveedores

Haz referencia a un proveedor con un phandle (una referencia o un puntero a un nodo DT) en DT siempre que sea posible. El uso de vinculaciones y phandles de DT estándar para hacer referencia a los proveedores permite que fw_devlink (antes of_devlink) determine automáticamente las dependencias entre dispositivos analizando el DT en el tiempo de ejecución. Luego, el kernel puede sondear automáticamente los dispositivos en el orden correcto, lo que elimina la necesidad de ordenar la carga de módulos o MODULE_SOFTDEP().

Caso heredado (sin compatibilidad con DT en el kernel de ARM)

Anteriormente, antes de que se agregara la compatibilidad con DT a los kernels de ARM, los consumidores, como los dispositivos táctiles, buscaban proveedores, como reguladores, con cadenas únicas a nivel global. Por ejemplo, el controlador ACME PMIC podía registrar o anunciar varios reguladores (como acme-pmic-ldo1 a acme-pmic-ldo10), y un controlador táctil podía buscar un regulador con regulator_get(dev, "acme-pmic-ldo10"). Sin embargo, en una placa diferente, el LDO8 podría suministrar el dispositivo táctil, lo que crea un sistema engorroso en el que el mismo controlador táctil debe determinar la cadena de búsqueda correcta para el regulador de cada placa en la que se usa el dispositivo táctil.

Caso actual (compatibilidad con DT en el kernel de ARM)

Después de que se agregó la compatibilidad con DT a los kernels de ARM, los consumidores pueden identificar proveedores en el DT haciendo referencia al nodo de árbol de dispositivos del proveedor con un phandle. Los consumidores también pueden nombrar el recurso según para qué se usa en lugar de quién lo proporciona. Por ejemplo, el controlador táctil del ejemplo anterior podría usar regulator_get(dev, "core") y regulator_get(dev, "sensor") para obtener los proveedores que alimentan el núcleo y el sensor del dispositivo táctil. El DT asociado para ese dispositivo es similar a la siguiente muestra de código:

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

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

Caso de lo peor de ambos mundos

Algunos controladores portados de kernels más antiguos incluyen un comportamiento heredado en el DT que toma la peor parte del esquema heredado y la fuerza en el esquema más nuevo que está diseñado para facilitar las cosas. En esos controladores, el controlador consumidor lee la cadena que se usará para la búsqueda con una propiedad DT específica del dispositivo, el proveedor usa otra propiedad específica del proveedor para definir el nombre que se usará para registrar el recurso del proveedor y, luego, el consumidor y el proveedor continúan usando el mismo esquema antiguo de usar cadenas para buscar el proveedor. En este caso de lo peor de ambos mundos, ocurre lo siguiente:

  • El controlador táctil usa código similar al siguiente:

    str = of_property_read(np, "fizz,core-regulator");
    core_reg = regulator_get(dev, str);
    str = of_property_read(np, "fizz,sensor-regulator");
    sensor_reg = regulator_get(dev, str);
    
  • El DT usa código similar al siguiente:

    touch-device {
      compatible = "fizz,touch";
      ...
      fizz,core-regulator = "acme-pmic-ldo4";
      fizz,sensor-regulator = "acme-pmic-ldo4";
    };
    acme-pmic {
      compatible = "acme,super-pmic";
      ...
      ldo4 {
        regulator-name = "acme-pmic-ldo4"
        ...
      };
      ...
      acme_pmic_ldo10: ldo10 {
        ...
        regulator-name = "acme-pmic-ldo10"
      };
    };
    

No modifiques los errores de la API de framework

Las APIs de framework, como regulator, clocks, irq, gpio, phys y extcon, muestran -EPROBE_DEFER como un valor de devolución de error para indicar que un dispositivo intenta sondear, pero no puede hacerlo en este momento, y el kernel debe volver a intentar el sondeo más tarde. Para asegurarte de que la función .probe() de tu dispositivo falle como se espera en esos casos, no reemplaces ni reasignes el valor de error. Si reemplazas o reasignas el valor de error, es posible que se quite -EPROBE_DEFER y que el dispositivo nunca se sondee.

Usa variantes de la API de devm_*()

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

Controla la desvinculación del controlador de dispositivos

Sé intencional sobre la desvinculación de los controladores de dispositivos y no dejes la desvinculación sin definir, ya que no implica que no esté permitida. Debes implementar por completo la desvinculación del controlador de dispositivos o inhabilitarla de forma explícita.

Implementa la desvinculación del controlador de dispositivos

Cuando elijas implementar por completo la desvinculación del controlador de dispositivos, desvincula los controladores de dispositivos de forma limpia para evitar pérdidas de memoria o recursos y problemas de seguridad. Puedes vincular un dispositivo a un controlador llamando a la función probe() del controlador y desvincular un dispositivo llamando a la función remove() del controlador. Si no existe una función remove(), el kernel aún puede desvincular el dispositivo; el núcleo del controlador supone que el controlador no necesita ningún trabajo de limpieza cuando se desvincula del dispositivo. Un controlador que está desvinculado de un dispositivo no necesita realizar ningún trabajo de limpieza explícito cuando se cumplen las siguientes condiciones:

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

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

En esta situación, el núcleo del controlador controla la liberación de todos los recursos adquiridos a través de las APIs de devm_*(). Si alguna de las instrucciones anteriores es falsa, el controlador debe realizar una limpieza (liberar recursos y apagar o desactivar el hardware) cuando se desvincula de un dispositivo. Para asegurarte de que un dispositivo pueda desvincular un módulo de controlador de forma limpia, usa una de las siguientes opciones:

  • Si el hardware no necesita una secuencia de apagado o inactividad, cambia el módulo del dispositivo para adquirir recursos con las APIs de devm_*().

  • Implementa la operación del controlador remove() en la misma estructura que la función probe() y, luego, realiza los pasos de limpieza con la función remove().

Inhabilita explícitamente la desvinculación del controlador de dispositivos (no recomendado)

Cuando elijas inhabilitar explícitamente la desvinculación del controlador de dispositivos, debes inhabilitar la desvinculación y la descarga del módulo.

  • Para inhabilitar la desvinculación, establece la marca suppress_bind_attrs en 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 inhabilitar la descarga del módulo, asegúrate de que el módulo tenga [permanent] en lsmod. Si no usas module_exit() o 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 active el sistema de archivos basado en flash o almacenamiento permanente. En esos casos, la API de request_firmware*() podría bloquearse durante un tiempo prolongado y, luego, fallar, lo que puede ralentizar el proceso de arranque de forma innecesaria. En su lugar, difiere la carga del firmware hasta que un cliente comience a usar el dispositivo. Por ejemplo, un controlador de pantalla podría cargar el firmware cuando se abre el dispositivo de pantalla.

El uso de .probe() para cargar el firmware puede ser correcto en algunos casos, como en un controlador de reloj que necesita firmware para funcionar, pero el dispositivo no está expuesto al espacio del usuario. Es posible que haya otros casos de uso adecuados.

Implementa el sondeo asíncrono

Admite y usa el sondeo asíncrono para aprovechar las mejoras futuras, como la carga de módulos paralelos o el sondeo de dispositivos para acelerar el tiempo de arranque, que se podrían agregar a Android en versiones futuras. Los módulos de controlador que no usan el sondeo asíncrono podrían reducir la eficacia de esas optimizaciones.

Para marcar un controlador como compatible y preferir el sondeo asíncrono, establece el campo probe_type en el miembro struct device_driver del controlador. En el siguiente ejemplo, se muestra esa compatibilidad habilitada para un controlador de plataforma:

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

Para que un controlador funcione con el sondeo asíncrono, no se requiere un código especial. Sin embargo, ten en cuenta lo siguiente cuando agregues compatibilidad con el sondeo asíncrono.

  • No hagas suposiciones sobre las dependencias sondeadas anteriormente. Verifica de forma directa o indirecta (la mayoría de las llamadas de framework) y muestra -EPROBE_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 superior, no supongas que los dispositivos secundarios se sondean de inmediato.

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

No uses MODULE_SOFTDEP para ordenar los sondeos de dispositivos

La función MODULE_SOFTDEP() no es una solución confiable para garantizar el orden de los sondeos de dispositivos y no se debe usar por los siguientes motivos.

  • Sondeo diferido. Cuando se carga un módulo, es posible que se difiera el sondeo del dispositivo porque uno de sus proveedores no está listo. Esto puede generar una discrepancia entre el orden de carga del módulo y el orden de sondeo del dispositivo.

  • Un controlador, muchos dispositivos. Un módulo de controlador puede administrar un tipo de dispositivo específico. Si el sistema incluye más de una instancia de un tipo de dispositivo y cada uno de esos dispositivos tiene un requisito de orden de sondeo diferente, no puedes respetar esos requisitos con el orden de carga del módulo.

  • Sondeo asíncrono. Los módulos de controlador que realizan el sondeo asíncrono no sondean de inmediato un dispositivo cuando se carga el módulo. En su lugar, un subproceso paralelo controla el sondeo del dispositivo, lo que puede generar una discrepancia entre el orden de carga del módulo y el orden de sondeo del dispositivo. Por ejemplo, cuando un módulo de controlador principal de I2C realiza un sondeo asíncrono y un módulo de controlador táctil depende del PMIC que está en el bus I2C, incluso si el controlador táctil y el controlador PMIC se cargan en el orden correcto, es posible que se intente el sondeo del controlador táctil antes del sondeo del controlador PMIC.

Si tienes módulos de controlador que usan la función MODULE_SOFTDEP(), corrígelos para que no usen esa función. Para ayudarte, el equipo de Android ascendió los cambios que permiten que el kernel controle los problemas de orden sin usar MODULE_SOFTDEP(). En particular, puedes usar fw_devlink para garantizar el orden de sondeo y (después de que se hayan sondeado todos los consumidores de un dispositivo) usar la devolución de llamada sync_state() para realizar las tareas necesarias.

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

Usa #if IS_ENABLED(CONFIG_XXX) en lugar de #ifdef CONFIG_XXX para asegurarte de que el código dentro del bloque #if continúe compilándose si la configuración cambia a una configuración de tres estados en el futuro. Las diferencias se muestran a continuación:

  • #if IS_ENABLED(CONFIG_XXX) se evalúa 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 estés seguro de que deseas hacer lo mismo cuando la configuración se establece en módulo o está inhabilitada.

Usa la macro correcta para las compilaciones condicionales

Si un CONFIG_XXX se establece en módulo (=m), el sistema de compilación automáticamente define CONFIG_XXX_MODULE. Si tu controlador está controlado por CONFIG_XXX y deseas verificar si se está compilando como un módulo, usa los siguientes lineamientos:

  • En el archivo C (o cualquier archivo fuente que no sea un archivo de encabezado) para tu controlador, no uses #ifdef CONFIG_XXX_MODULE, ya que es innecesariamente restrictivo y se interrumpe si el nombre de la configuración cambia 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, 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 objeto binario, sino que se compilan como parte de un archivo C (o de otros archivos fuente). Usa las siguientes reglas para los archivos de encabezado:

    • Para un archivo de encabezado que usa #ifdef MODULE, el resultado cambia según el archivo fuente que lo use. Esto significa que el mismo archivo de encabezado en la misma compilación puede tener diferentes partes de su código compiladas para diferentes archivos fuente (módulo en comparación con integrado o inhabilitado). Esto puede ser útil cuando deseas definir una macro que necesita expandirse de una manera para el código integrado y de una manera diferente para un módulo.

    • Para un archivo de encabezado que necesita compilarse en un fragmento de código cuando un CONFIG_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.