Lineamientos del módulo de proveedores

Usa los siguientes lineamientos para aumentar la solidez y confiabilidad de tus módulos de proveedores. Muchas pautas, cuando se siguen, pueden ayudar a que sea más fácil determinar el orden de carga correcto de los módulos y el orden en el que los controladores sondeo de dispositivos.

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

  • Los módulos de bibliotecas son bibliotecas que proporcionan APIs para que las usen otros módulos. Por lo general, estos módulos no son específicos del hardware. Ejemplos de módulos de biblioteca incluir un módulo de encriptación AES, el framework remoteproc que se compila como módulo y 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 controlador son controladores que sondean o se vinculan a un tipo específico de dispositivo. Esos módulos son específicos de hardware. Algunos ejemplos de módulos de controladores son UART, PCIe y hardware de codificador de video. Los módulos de controlador se activan solo cuando que 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 correctamente a él, es posible que se ejecute otro código de módulo.

Usa los comandos init y exit del módulo 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 de aplicar estas restricciones es usar macros wrapper, lo que evita el uso directo de module_init(), *_initcall(), o las macros 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 registrar más de un controlador. Para un módulo de controlador que usa module_init() y module_exit() para registrar varios conductores; intenta combinarlos en una con un solo controlador. 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 las 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 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 cargarlo. 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 ejemplos sobre el uso de la macro, consulta el código upstream.

Evita las discrepancias en la CRC debido a los tipos de datos declarados en forma directa

No incluyas archivos de encabezado para obtener visibilidad sobre los tipos de datos declarados con posterioridad. Algunos structs, uniones y otros tipos de datos definidos en un archivo de encabezado (header-A.h) se puede reenviar de manera declarada en una archivo de encabezado (header-B.h) que generalmente usa punteros para 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 los componentes internos de estas declaradas con posterioridad. Si lo haces, se genera la CRC CONFIG_MODVERSIONS. problemas de discrepancia (lo que genera problemas de cumplimiento de ABI) cuando se usa 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 declaró como struct fwnode_handle; en include/linux/device.h ya que el kernel intenta mantener los detalles de struct fwnode_handle. privada de los usuarios de include/linux/device.h. En esta situación, 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 tengas que incluir ese encabezado indica un mal patrón de diseño.

No accedes directamente a las estructuras principales del kernel.

Acceder directamente o modificar las estructuras de datos del kernel principal puede provocar comportamiento no deseado, incluidas fugas de memoria, fallas y errores en la compatibilidad con futuras versiones de kernel. Una estructura de datos es una estructura de datos central del kernel cuando cumple alguna de las siguientes condiciones:

  • La estructura de datos se define en KERNEL-DIR/include/. Por ejemplo, struct device y struct dev_links_info. Las estructuras de datos definidas en include/linux/soc están exentas.

  • El módulo asigna o inicializa la estructura de datos, pero se hace visibles para el kernel si se pasan, de forma indirecta, (a través de un puntero en un struct) o directamente como entrada en una función exportada por el kernel. Para Por ejemplo, un módulo de controlador cpufreq inicializa struct cpufreq_driver y 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 del proveedor. Si No tienen una API ni un hook de proveedor para modificar partes de los datos del 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 ni 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 analices los nodos de devicetree con una propiedad compatible

Si un nodo del árbol de dispositivos (DT) tiene una propiedad compatible, se aplica una struct device se le asigna automáticamente o cuando se llama a of_platform_populate() en el nodo DT superior (por lo general, mediante el controlador del dispositivo superior). El esperada predeterminada (excepto por algunos dispositivos que se inicializaron antes para el programador) es que un nodo DT con una propiedad compatible tiene un valor 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 nodos de DT. con la propiedad compatible para que sean 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 bloquear la sondeo de sus dispositivos de consumo o bloquear las llamadas a sync_state() para sus dispositivos de 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 dispositivo que pueda sondear el dispositivo o quitar la propiedad compatible (solo es posible si no se transfirió a upstream). Para analizar las alternativas, comunícate con el equipo del kernel de Android a través de kernel-team@android.com y prepárate para justificar tus casos de uso.

Usa DT phandles para buscar proveedores

Haz referencia a un proveedor con un phandle (una referencia o un puntero a un nodo de DT) en DT siempre que sea posible. 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 dependencias entre dispositivos a través del análisis del 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 usar pedido de carga 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 suministrar el dispositivo táctil, lo que crearía 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.

Situación actual (compatibilidad de DT en el kernel de ARM)

Después de agregar la compatibilidad con DT a los kernels de ARM, los consumidores pueden identificar a los proveedores consultando el nodo del árbol de dispositivos del proveedor con un phandle. Los consumidores también pueden nombrar el recurso según el 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 la 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 {
        ...
    };
};

El peor de los dos 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 lo aplica al esquema más nuevo que está 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. 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);
    
  • La DT usa 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 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 sonda más adelante. Para asegurarte de que la función .probe() de tu dispositivo falla como se espera en esos casos, no reemplaces ni reasignes el valor del error. Reemplazar o reasignar el valor de error puede hacer que se descarte -EPROBE_DEFER y, como resultado, nunca se sondea el dispositivo.

Usa variantes de API de devm_*()

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

Controla la desvinculación del controlador 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 dispositivo y el controlador

Cuando elijas implementar por completo la desvinculación de controladores de dispositivos, debes desvincularlos. de forma limpia para evitar fugas de memoria o recursos y problemas de seguridad. Puedes vincular un desde un dispositivo a un controlador llamando a la función probe() de un controlador y desvinculando 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 realizar ningún trabajo de limpieza cuando se desvincula del dispositivo. Un conductor desvinculada de un dispositivo no necesita realizar ningún trabajo de limpieza explícito cuando las siguientes afirmaciones son verdaderas:

  • 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 es falsa, el controlador necesita realizar una limpieza (liberar recursos y apagar o desactiva 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 la módulo del dispositivo para adquirir recursos con las APIs de devm_*().

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

Inhabilita explícitamente 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 en true en el struct device_driver del conductor; este parámetro de configuración evita que bind y unbind archivos en el directorio sysfs del controlador. El archivo unbind tiene 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() o module_XXX_driver(), el módulo se marca como [permanent].

No cargues el firmware desde la función de sonda.

El controlador no debería cargar el firmware desde la función .probe(), como debería hacerlo. no tienen acceso al firmware si el controlador realiza un sondeo antes de la instalación el sistema de archivos basado en almacenamiento permanente esté activado. En esos casos, es posible que la API de request_firmware*() se bloquee durante mucho tiempo y, luego, falle, lo que puede ralentizar el proceso de inicio de forma innecesaria. En cambio, se debe diferir la carga del firmware cuando un cliente comienza 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 hay 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. Módulos de controlador que no usan el sondeo asíncrono podría reducir la eficacia de esas optimizaciones.

Para marcar un controlador como compatible y que prefiere el sondeo asíncrono, configura la Campo probe_type en el miembro struct device_driver del conductor Lo siguiente En el 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 sondeo asíncrono no se requiere un 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 de forma indirecta (la mayoría de las llamadas al framework) y mostrar -EPROBE_DEFER si hay una o más los proveedores aún no están listos.

  • Si agregas dispositivos secundarios a la función de sondeo de un dispositivo superior, no des por sentado que del dispositivo secundario se sondea de inmediato.

  • Si falla un sondeo, realiza un manejo adecuado de errores y realiza una limpieza (consulta Uso 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 el orden de carga del módulo y el orden de 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. Módulos de controlador que realizan sondeos asíncronos no sondeen de inmediato un dispositivo cuando el módulo está cargado. En cambio, un subproceso paralelo controla el sondeo del dispositivo, lo que puede generar una discrepancia el orden de carga del módulo y el orden de sondeo del dispositivo. Por ejemplo, cuando un cable I2C el módulo del controlador principal 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 de PMIC se carga en el orden correcto, es posible que el sondeo del controlador táctil esté antes del sondeo del controlador de PMIC.

Si tienes módulos de controlador que usan la función MODULE_SOFTDEP(), corrígelos de modo que no usan esa función. Para ayudarte, el equipo de Android subió cambios que permiten que el kernel maneje los problemas de ordenamiento sin usar MODULE_SOFTDEP() Específicamente, puedes usar fw_devlink para asegurarte de que el sondeo tenga el orden correcto 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.

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 del bloque #if continúa compilándose si la configuración cambia a un config. de triestado 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 cuando CONFIG_XXX está configurado como módulo (=m). Úsalo solo cuando tengas la certeza de que quieres hacer lo mismo cuando la configuración esté configurada como módulo o esté inhabilitada.

Usa 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 CONFIG_XXX y CONFIG_XXX controla tu conductor quieres comprobar si tu controlador se compila como un módulo, usa el comando 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 fuente que no sea de 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 comprobar si un archivo C (o cualquier un archivo de origen 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 complicada, ya que estos no son se compilan directamente en un objeto binario, pero se compilan como parte de un archivo C (o 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 puede compilar distintas partes de su código para distintas fuentes (módulo en comparación con integrado o inhabilitado). Esto puede ser útil cuando quieres para definir una macro que necesita expandirse de una manera para el código integrado de una manera diferente para un módulo.

    • Para un archivo de encabezado que se debe compilar 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.