Cómo desarrollar código de kernel para GKI

La imagen genérica del kernel (GKI) reduce la fragmentación del kernel porque se alinea estrechamente con el kernel de Linux ascendente. Sin embargo, existen motivos válidos por los que algunos parches no se pueden aceptar en upstream y hay cronogramas de productos que se deben cumplir, por lo que algunos parches se mantienen en las fuentes del kernel común de Android (ACK) a partir de las cuales se compila el GKI.

Los desarrolladores deben enviar los cambios de código upstream con la lista de distribución del kernel de Linux (LKML) como primera opción y enviar los cambios de código a la rama android-mainline de ACK solo cuando haya un motivo sólido por el que no sea viable hacerlo upstream. A continuación, se incluyen ejemplos de motivos válidos y cómo manejarlos.

  • El parche se envió a la LKML, pero no se aceptó a tiempo para el lanzamiento del producto. Para controlar este parche, haz lo siguiente:

    • Proporciona evidencia de que el parche se envió al LKML y que se recibieron comentarios para el parche, o una hora estimada en la que se envía el parche de forma ascendente.
    • Decide qué curso de acción seguir para implementar el parche en ACK, obtener su aprobación en la versión upstream y, luego, quitarlo de ACK cuando la versión final upstream se combine en ACK.
  • El parche define EXPORT_SYMBOLS_GPL() para un módulo de proveedor, pero no se pudo enviar upstream porque no hay módulos en el árbol que consuman ese símbolo. Para controlar este parche, proporciona detalles sobre por qué no se puede enviar tu módulo a la fuente y las alternativas que consideraste antes de realizar esta solicitud.

  • El parche no es lo suficientemente genérico para la versión anterior y no hay tiempo para refactorizarlo antes del lanzamiento de un producto. Para controlar este parche, proporciona un tiempo estimado en el que se enviará un parche refactorizado a la fuente (el parche no se aceptará en ACK sin un plan para enviar un parche refactorizado a la fuente para su revisión).

  • El parche no se puede aceptar en la parte superior porque… <ingresa el motivo aquí>. Para administrar este parche, comunícate con el equipo de kernel de Android y trabaja con nosotros en las opciones para refactorizar el parche de modo que pueda enviarse a revisión y aceptarse en sentido ascendente.

Hay muchas más justificaciones posibles. Cuando envíes el error o el parche, incluye una justificación válida y espera que se realice una iteración y un debate. Reconocemos que el ACK lleva algunos parches, especialmente en las primeras fases de GKI, mientras todos aprenden a trabajar en upstream, pero no pueden relajar los cronogramas de productos para hacerlo. Se espera que los requisitos de integración ascendente sean más estrictos con el tiempo.

Requisitos de parches

Los parches deben cumplir con los estándares de codificación del kernel de Linux que se describen en el árbol de origen de Linux, ya sea que se envíen upstream o a ACK. La secuencia de comandos scripts/checkpatch.pl se ejecuta como parte de las pruebas previas al envío de Gerrit, así que ejecútala con anticipación para asegurarte de que se apruebe. Para ejecutar la secuencia de comandos de checkpatch con la misma configuración que las pruebas previas al envío, usa //build/kernel/static_analysis:checkpatch_presubmit. Para obtener más información, consulta build/kernel/kleaf/docs/checkpatch.md.

Parches ACK

Los parches que se envían a ACK deben cumplir con los estándares de codificación del kernel de Linux y los lineamientos de contribución. Debes incluir una etiqueta Change-Id en el mensaje de confirmación. Si envías el parche a varias ramas (por ejemplo, android-mainline y android12-5.4), debes usar la misma Change-Id para todas las instancias del parche.

Primero, envía los parches a LKML para que se revisen en la fuente. Si el parche es lo siguiente:

  • Se acepta en upstream y se combina automáticamente en android-mainline.
  • No se aceptó en upstream, envíalo a android-mainline con una referencia al envío anterior o una explicación de por qué no se envió a LKML.

Una vez que se acepta un parche en upstream o en android-mainline, se puede llevar a la versión anterior al ACK basado en LTS correspondiente (como android12-5.4 y android11-5.4 para parches que corrigen el código específico de Android). El envío a android-mainline permite realizar pruebas con nuevas versiones candidatas upstream y garantiza que el parche esté en el próximo ACK basado en LTS. Las excepciones incluyen casos en los que un parche upstream se adapta a android12-5.4 (porque es probable que el parche ya esté en android-mainline).

Parches upstream

Como se especifica en los lineamientos de contribución, los parches upstream destinados a los kernels de ACK se dividen en los siguientes grupos (enumerados en orden de probabilidad de aceptación).

  • UPSTREAM:: Es probable que los parches seleccionados de "android-mainline" se acepten en ACK si hay un caso de uso razonable.
  • BACKPORT:: Es probable que también se acepten los parches de upstream que no seleccionen de forma clara y necesiten modificaciones si hay un caso de uso razonable.
  • FROMGIT:: Es posible que se acepten los parches seleccionados de una rama de mantenimiento para enviarlos a la línea principal de Linux si hay una fecha límite próxima. Estos deben justificarse tanto para el contenido como para la programación.
  • FROMLIST:: Es probable que no se acepten los parches que se enviaron a LKML, pero que aún no se aceptaron en una rama de mantenimiento, a menos que la justificación sea lo suficientemente convincente como para que se acepte el parche, independientemente de si llega o no a Linux upstream (suponemos que no). Debe haber un problema asociado con los parches de FROMLIST para facilitar el análisis con el equipo de kernel de Android.

Parches específicos de Android

Si no puedes implementar los cambios necesarios en la parte superior, puedes intentar enviar parches fuera del árbol directamente a ACK. Para enviar parches fuera del árbol, debes crear un problema en TI que cite el parche y el motivo por el que no se puede enviar upstream (consulta la lista anterior para ver ejemplos). Sin embargo, hay algunos casos en los que el código no se puede enviar a la fuente. Estos casos se describen a continuación y deben seguir los lineamientos de contribución para los parches específicos de Android y estar etiquetados con el prefijo ANDROID: en el asunto.

Cambios en gki_defconfig

Todos los cambios de CONFIG a gki_defconfig se deben aplicar a las versiones arm64 y x86, a menos que CONFIG sea específico de la arquitectura. Para solicitar un cambio en la configuración de CONFIG, crea un problema en el equipo de TI para analizar el cambio. Se rechaza cualquier cambio de CONFIG que afecte la interfaz del módulo del kernel (KMI) después de que se inmoviliza. En los casos en que los socios soliciten parámetros de configuración contradictorios para una sola configuración, resolvemos los conflictos mediante el análisis de los errores relacionados.

Código que no existe en la fuente

No se pueden enviar modificaciones al código que ya es específico de Android. Por ejemplo, aunque el controlador de Binder se mantiene en upstream, las modificaciones a las funciones de herencia de prioridad del controlador de Binder no se pueden enviar a upstream porque son específicas de Android. Explica de forma explícita en el error y el parche por qué no se puede enviar el código a la fuente. Si es posible, divide los parches en partes que se puedan enviar upstream y partes específicas de Android que no se puedan enviar upstream para minimizar la cantidad de código fuera del árbol que se mantiene en ACK.

Otros cambios en esta categoría son las actualizaciones de los archivos de representación de KMI, las listas de símbolos de KMI, gki_defconfig, las secuencias de comandos de compilación o configuración, y otras secuencias de comandos que no existen en upstream.

Módulos fuera del árbol

Upstream Linux desaconseja activamente la compatibilidad con la compilación de módulos fuera de árbol. Esta es una posición razonable, ya que los responsables de mantenimiento de Linux no garantizan la compatibilidad binaria o de origen en el kernel y no quieren admitir código que no esté en el árbol. Sin embargo, el GKI garantiza las ABI para módulos de proveedores, lo que garantiza que las interfaces de KMI sean estables durante la vida útil admitida de un kernel. Por lo tanto, hay una clase de cambios para admitir módulos de proveedores que son aceptables para ACK, pero no para upstream.

Por ejemplo, considera un parche que agrega macros EXPORT_SYMBOL_GPL() en el que los módulos que usan la exportación no están en el árbol de origen. Si bien debes intentar solicitar EXPORT_SYMBOL_GPL() upstream y proporcionar un módulo que use el símbolo exportado recientemente, si hay una justificación válida para el motivo por el que no se envía el módulo upstream, puedes enviar el parche a ACK. Debes incluir la justificación del motivo por el que el módulo no se puede transferir a la versión anterior en el problema. (No solicites la variante que no es de GPL, EXPORT_SYMBOL()).

Parámetros de configuración ocultos

Algunos módulos en el árbol seleccionan automáticamente parámetros de configuración ocultos que no se pueden especificar en gki_defconfig. Por ejemplo, CONFIG_SND_SOC_TOPOLOGY se selecciona automáticamente cuando se configura CONFIG_SND_SOC_SOF=y. Para admitir la compilación de módulos fuera del árbol, GKI incluye un mecanismo para habilitar configuraciones ocultas.

Para habilitar una configuración oculta, agrega una sentencia select en init/Kconfig.gki para que se seleccione automáticamente según la configuración del kernel CONFIG_GKI_HACKS_TO_FIX, que está habilitada en gki_defconfig. Usa este mecanismo solo para parámetros de configuración ocultos. Si la configuración no está oculta, se debe especificar en gki_defconfig, expresamente o como una dependencia.

Gobernadores cargables

Para los frameworks de kernel (como cpufreq) que admiten reguladores que se pueden cargar, puedes anular el control predeterminado (como el regulador schedutil de cpufreq). Para los frameworks (como el framework térmico) que no admiten controladores o gobernadores cargables, pero que aún requieren una implementación específica del proveedor, crea un problema en TI y consulta con el equipo del kernel de Android.

Trabajaremos contigo y con los responsables de la versión anterior para agregar la compatibilidad necesaria.

Hooks de proveedores

En versiones anteriores, podías agregar modificaciones específicas del proveedor directamente al kernel principal. Esto no es posible con GKI 2.0 porque el código específico del producto se debe implementar en módulos y no se aceptará en los kernels principales upstream ni en ACK. Para habilitar las funciones de valor agregado en las que los socios confían con un impacto mínimo en el código del kernel principal, GKI acepta hooks de proveedores que permiten invocar módulos desde el código del kernel principal. Además, las estructuras de datos clave se pueden rellenar con campos de datos del proveedor que están disponibles para almacenar datos específicos del proveedor para implementar estas funciones.

Los hooks de proveedores se presentan en dos variantes (normal y restringida) que se basan en puntos de seguimiento (no eventos de seguimiento) a los que se pueden adjuntar los módulos de proveedores. Por ejemplo, en lugar de agregar una nueva función sched_exit() para realizar una contabilización al salir de la tarea, los proveedores pueden agregar un hook en do_exit() al que un módulo de proveedor puede adjuntarse para su procesamiento. Una implementación de ejemplo incluye los siguientes hooks del proveedor.

  • Los hooks de proveedores normales usan DECLARE_HOOK() para crear una función de punto de seguimiento con el nombre trace_name, en el que name es el identificador único del seguimiento. Por convención, los nombres de hook del proveedor normales comienzan con android_vh, por lo que el nombre del hook sched_exit() sería android_vh_sched_exit.
  • Los hooks de proveedores restringidos son necesarios para casos como los hooks de programador, en los que se debe llamar a la función adjunta, incluso si la CPU está sin conexión o requiere un contexto no atómico. Los hooks de proveedores restringidos no se pueden desconectar, por lo que los módulos que se conectan a un hook restringido nunca se pueden descargar. Los nombres de hooks de proveedores restringidos comienzan con android_rvh.

Para agregar un hook de proveedor, informa un problema en TI y envía parches (como con todos los parches específicos de Android, debe existir un problema y debes proporcionar una justificación). La compatibilidad con los hooks de proveedores solo está en ACK, por lo que no envíes estos parches a Linux upstream.

Cómo agregar campos de proveedor a las estructuras

Para asociar los datos del proveedor con estructuras de datos clave, agrega campos android_vendor_data con las macros ANDROID_VENDOR_DATA(). Por ejemplo, para admitir funciones de valor agregado, agrega campos a las estructuras, como se muestra en la siguiente muestra de código.

Para evitar posibles conflictos entre los campos que necesitan los proveedores y los que necesitan los OEM, los OEM nunca deben usar campos declarados con macros ANDROID_VENDOR_DATA(). En su lugar, los OEM deben usar ANDROID_OEM_DATA() para declarar campos android_oem_data.

#include <linux/android_vendor.h>
...
struct important_kernel_data {
  [all the standard fields];
  /* Create vendor data for use by hook implementations. The
   * size of vendor data is based on vendor input. Vendor data
   * can be defined as single u64 fields like the following that
   * declares a single u64 field named "android_vendor_data1" :
   */
  ANDROID_VENDOR_DATA(1);

  /*
   * ...or an array can be declared. The following is equivalent to
   * u64 android_vendor_data2[20]:
   */
  ANDROID_VENDOR_DATA_ARRAY(2, 20);

  /*
   * SoC vendors must not use fields declared for OEMs and
   * OEMs must not use fields declared for SoC vendors.
   */
  ANDROID_OEM_DATA(1);

  /* no further fields */
}

Define los hooks de proveedores

Para agregar hooks de proveedor al código del kernel como puntos de seguimiento, decláralos con DECLARE_HOOK() o DECLARE_RESTRICTED_HOOK() y, luego, agrégalos al código como un punto de seguimiento. Por ejemplo, para agregar trace_android_vh_sched_exit() a la función del kernel do_exit() existente, haz lo siguiente:

#include <trace/hooks/exit.h>
void do_exit(long code)
{
    struct task_struct *tsk = current;
    ...
    trace_android_vh_sched_exit(tsk);
    ...
}

En un principio, la función trace_android_vh_sched_exit() solo verifica si hay algo conectado. Sin embargo, si un módulo de proveedor registra un controlador con register_trace_android_vh_sched_exit(), se llama a la función registrada. El controlador debe estar al tanto del contexto con respecto a los bloqueos retenidos, el estado de RCS y otros factores. El hook se debe definir en un archivo de encabezado en el directorio include/trace/hooks.

Por ejemplo, el siguiente código proporciona una posible declaración para trace_android_vh_sched_exit() en el archivo include/trace/hooks/exit.h.

/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched
#define TRACE_INCLUDE_PATH trace/hooks

#if !defined(_TRACE_HOOK_SCHED_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_HOOK_SCHED_H
#include <trace/hooks/vendor_hooks.h>
/*
 * Following tracepoints are not exported in tracefs and provide a
 * mechanism for vendor modules to hook and extend functionality
 */

struct task_struct;

DECLARE_HOOK(android_vh_sched_exit,
             TP_PROTO(struct task_struct *p),
             TP_ARGS(p));

#endif /* _TRACE_HOOK_SCHED_H */

/* This part must be outside protection */
#include <trace/define_trace.h>

Para crear instancias de las interfaces necesarias para el hook del proveedor, agrega el archivo de encabezado con la declaración del hook a drivers/android/vendor_hooks.c y exporta los símbolos. Por ejemplo, el siguiente código completa la declaración del hook android_vh_sched_exit().

#ifndef __GENKSYMS__
/* struct task_struct */
#include <linux/sched.h>
#endif

#define CREATE_TRACE_POINTS
#include <trace/hooks/vendor_hooks.h>
#include <trace/hooks/exit.h>
/*
 * Export tracepoints that act as a bare tracehook (i.e. have no trace
 * event associated with them) to allow external modules to probe
 * them.
 */
EXPORT_TRACEPOINT_SYMBOL_GPL(android_vh_sched_exit);

NOTA: Las estructuras de datos que se usan dentro de la declaración del hook deben definirse por completo para garantizar la estabilidad de la ABI. De lo contrario, no es seguro hacer la dereferencia de los punteros opacos ni usar la estructura en contextos de tamaño. La inclusión que proporciona la definición completa de esas estructuras de datos debe ir dentro de la sección #ifndef __GENKSYMS__ de drivers/android/vendor_hooks.c. Los archivos de encabezado en include/trace/hooks no deben incluir el archivo de encabezado del kernel con las definiciones de tipo para evitar cambios de CRC que rompan el KMI. En su lugar, declara los tipos de manera directa.

Cómo adjuntar a hooks de proveedores

Para usar hooks de proveedores, el módulo del proveedor debe registrar un controlador para el hook (por lo general, se hace durante la inicialización del módulo). Por ejemplo, el siguiente código muestra el controlador foo.ko del módulo para trace_android_vh_sched_exit().

#include <trace/hooks/sched.h>
...
static void foo_sched_exit_handler(void *data, struct task_struct *p)
{
    foo_do_exit_accounting(p);
}
...
static int foo_probe(..)
{
    ...
    rc = register_trace_android_vh_sched_exit(foo_sched_exit_handler, NULL);
    ...
}

Usa hooks de proveedores desde archivos de encabezado

Para usar hooks de proveedores desde archivos de encabezado, es posible que debas actualizar el archivo de encabezado del hook de proveedor para anular la definición de TRACE_INCLUDE_PATH y evitar errores de compilación que indiquen que no se pudo encontrar un archivo de encabezado de punto de seguimiento. Por ejemplo:

In file included from .../common/init/main.c:111:
In file included from .../common/include/trace/events/initcall.h:74:
.../common/include/trace/define_trace.h:95:10: fatal error: 'trace/hooks/initcall.h' file not found
   95 | #include TRACE_INCLUDE(TRACE_INCLUDE_FILE)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../common/include/trace/define_trace.h:90:32: note: expanded from macro 'TRACE_INCLUDE'
   90 | # define TRACE_INCLUDE(system) __TRACE_INCLUDE(system)
      |                                ^~~~~~~~~~~~~~~~~~~~~~~
.../common/include/trace/define_trace.h:87:34: note: expanded from macro '__TRACE_INCLUDE'
   87 | # define __TRACE_INCLUDE(system) __stringify(TRACE_INCLUDE_PATH/system.h)
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../common/include/linux/stringify.h:10:27: note: expanded from macro '__stringify'
   10 | #define __stringify(x...)       __stringify_1(x)
      |                                 ^~~~~~~~~~~~~~~~
.../common/include/linux/stringify.h:9:29: note: expanded from macro '__stringify_1'
    9 | #define __stringify_1(x...)     #x
      |                                 ^~
<scratch space>:14:1: note: expanded from here
   14 | "trace/hooks/initcall.h"
      | ^~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Para corregir este tipo de error de compilación, aplica la corrección equivalente al archivo de encabezado del hook del proveedor que estás incluyendo. Para obtener más información, consulta https://r.android.com/3066703.

diff --git a/include/trace/hooks/mm.h b/include/trace/hooks/mm.h
index bc6de7e53d66..039926f7701d 100644
--- a/include/trace/hooks/mm.h
+++ b/include/trace/hooks/mm.h
@@ -2,7 +2,10 @@
 #undef TRACE_SYSTEM
 #define TRACE_SYSTEM mm

+#ifdef CREATE_TRACE_POINTS
 #define TRACE_INCLUDE_PATH trace/hooks
+#define UNDEF_TRACE_INCLUDE_PATH
+#endif

Definir UNDEF_TRACE_INCLUDE_PATH le indica a include/trace/define_trace.h que des defina TRACE_INCLUDE_PATH después de crear los puntos de seguimiento.

Funciones principales del kernel

Si ninguna de las técnicas anteriores te permite implementar una función desde un módulo, debes agregarla como modificación específica de Android al kernel principal. Crea un problema en el sistema de seguimiento de problemas (ITS) para iniciar la conversación.

Interfaz de programación de aplicaciones del usuario (UAPI)

  • Archivos de encabezado de la UAPI Los cambios en los archivos de encabezado de UAPI deben realizarse en sentido ascendente, a menos que los cambios se realicen en interfaces específicas de Android. Usa archivos de encabezado específicos del proveedor para definir interfaces entre los módulos del proveedor y el código del espacio de usuario del proveedor.
  • Nodos sysfs. No agregues nodos sysfs nuevos al kernel de GKI (estas adiciones solo son válidas en los módulos de proveedores). Los nodos sysfs que usan las bibliotecas independientes del SoC y del dispositivo, y el código Java que comprende el framework de Android, solo se pueden cambiar de forma compatible y deben cambiarse upstream si no son nodos sysfs específicos de Android. Puedes crear nodos sysfs específicos del proveedor para que los use el espacio de usuario del proveedor. De forma predeterminada, se rechaza el acceso a los nodos sysfs por parte del espacio de usuario con SELinux. Depende del proveedor agregar las etiquetas de SELinux adecuadas para permitir el acceso del software autorizado del proveedor.
  • Nodos de DebugFS. Los módulos de proveedores pueden definir nodos en debugfs solo para la depuración (ya que debugfs no se activa durante el funcionamiento normal del dispositivo).