Cómo desarrollar código de kernel para GKI

La imagen genérica del kernel (GKI) reduce la fragmentación del kernel, ya que se alinea estrechamente con el kernel de Linux upstream. Sin embargo, existen motivos válidos por los que no se pueden aceptar algunos parches en el 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 de forma ascendente a través de la lista de distribución de correo electrónico 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 la opción ascendente no sea viable. A continuación, se indican ejemplos de motivos válidos y cómo controlarlos.

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

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

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

  • Upstream no puede aceptar el parche porque… <insert reason here>. Para controlar este parche, comunícate con el equipo del kernel de Android y trabaja con nosotros en las opciones para refactorizar el parche de modo que se pueda enviar para su revisión y aceptación en la versión upstream.

Hay muchas más justificaciones posibles. Cuando envíes tu error o parche, incluye una justificación válida y prepárate para algunas iteraciones y debates. Reconocemos que el ACK incluye algunos parches, especialmente en las primeras fases del GKI, mientras todos aprenden a trabajar en el upstream, pero no pueden relajar los cronogramas de los productos para hacerlo. Se espera que los requisitos de upstreaming se vuelvan 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 de forma ascendente o a ACK. La secuencia de comandos scripts/checkpatch.pl se ejecuta como parte de las pruebas previas al envío de Gerrit, por lo que debes ejecutarla con anticipación para asegurarte de que se apruebe. Para ejecutar la secuencia de comandos checkpatch con la misma configuración que las pruebas previas a la confirmación, usa //build/kernel/static_analysis:checkpatch_presubmit. Para obtener más detalles, consulta build/kernel/kleaf/docs/checkpatch.md.

Parches de ACK

Los parches enviados 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 el mismo Change-Id para todas las instancias del parche.

Primero, envía parches a LKML para que se realice una revisión upstream. Si el parche es:

  • Si se acepta en la versión upstream, se combinará automáticamente en android-mainline.
  • No se aceptó en la versión upstream. Envíala a android-mainline con una referencia al envío upstream o una explicación de por qué no se envió a LKML.

Una vez que se acepta un parche en el upstream o en android-mainline, se puede transferir a la versión anterior del ACK basada en LTS adecuada (como android12-5.4 y android11-5.4 para los parches que corrigen código específico de Android). Enviar a android-mainline permite realizar pruebas con los nuevos candidatos a lanzamiento upstream y garantiza que el parche se incluya en el próximo ACK basado en LTS. Entre las excepciones, se incluyen los casos en los que un parche upstream se porta a android12-5.4 (porque es probable que el parche ya esté en android-mainline).

Parches ascendentes

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 se acepten en ACK los parches seleccionados de "android-mainline" si hay un caso de uso razonable.
  • BACKPORT:: También es probable que se acepten parches de upstream que no se puedan aplicar de forma limpia y que necesiten modificaciones si hay un caso de uso razonable.
  • FROMGIT:: Es posible que se acepten los parches seleccionados de una rama de mantenedor en preparación para enviarlos al kernel principal de Linux si hay una fecha límite próxima. Estas deben justificarse tanto para el contenido como para la programación.
  • FROMLIST:: Es poco probable que se acepten los parches que se enviaron a LKML, pero que aún no se aceptaron en una rama de mantenedor, a menos que la justificación sea lo suficientemente convincente como para que se acepte el parche, independientemente de si se incluye en la versión upstream de Linux (suponemos que no se incluirá). Debe haber un problema asociado con los parches de FROMLIST para facilitar el debate con el equipo del kernel de Android.

Parches específicos de Android

Si no puedes aplicar los cambios necesarios en la versión upstream, puedes intentar enviar parches fuera del árbol directamente a ACK. Para enviar parches fuera del árbol, debes crear un problema en el sistema de TI que cite el parche y la justificación de por qué no se puede enviar el parche de forma ascendente (consulta la lista anterior para ver ejemplos). Sin embargo, hay algunos casos en los que el código no se puede enviar de forma ascendente. Estos casos se cubren de la siguiente manera y deben seguir los lineamientos de contribución para los parches específicos de Android y etiquetarse con el prefijo ANDROID: en el asunto.

Cambios en gki_defconfig

Todos los cambios de CONFIG en 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 un parámetro de configuración de CONFIG, crea un problema en el equipo de TI para analizar el cambio. Se rechaza cualquier cambio en CONFIG que afecte la interfaz del módulo del kernel (KMI) después de que se congela. En los casos en que los socios solicitan parámetros de configuración contradictorios para una sola configuración, resolvemos los conflictos a través de debates sobre los errores relacionados.

Código que no existe en el upstream

Las modificaciones al código que ya es específico para Android no se pueden enviar de forma ascendente. Por ejemplo, aunque el controlador de Binder se mantiene en la versión upstream, las modificaciones en las funciones de herencia de prioridad del controlador de Binder no se pueden enviar a la versión upstream porque son específicas de Android. Sé explícito en tu error y parche sobre por qué el código no se puede enviar upstream. Si es posible, divide los parches en partes que se puedan enviar de forma ascendente y partes específicas de Android que no se puedan enviar de forma ascendente 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 la KMI, las listas de símbolos de la KMI, gki_defconfig, las secuencias de comandos de compilación o la configuración, o bien otras secuencias de comandos que no existen en la versión upstream.

Módulos fuera del árbol

El kernel de Linux upstream desaconseja activamente la compatibilidad con la compilación de módulos fuera del árbol. Esta es una postura razonable, ya que los mantenedores de Linux no garantizan la compatibilidad binaria o de código fuente en el kernel y no quieren admitir código que no esté en el árbol. Sin embargo, el GKI garantiza la ABI para los módulos del proveedor, 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 del proveedor 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() de forma upstream y proporcionar un módulo que use el símbolo exportado recientemente, si hay una justificación válida por la que no se envía el módulo de forma upstream, puedes enviar el parche a ACK. En el problema, debes incluir la justificación de por qué no se puede incorporar el módulo al upstream. (No solicites la variante que no es de GPL, EXPORT_SYMBOL()).

Parámetros de configuración ocultos

Algunos módulos integrados 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 las configuraciones ocultas.

Para habilitar una configuración oculta, agrega una instrucción 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 las configuraciones ocultas. Si la configuración no está oculta, debe especificarse en gki_defconfig de forma explícita o como dependencia.

Cargadores de gobernador

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

Trabajaremos contigo y con los mantenedores upstream para agregar la compatibilidad necesaria.

Hooks del proveedor

En versiones anteriores, podías agregar modificaciones específicas del proveedor directamente al kernel principal. Esto no es posible con el GKI 2.0 porque el código específico del producto debe implementarse en módulos y no se aceptará en los kernels principales ascendentes ni en ACK. Para habilitar 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 del proveedor que permiten invocar módulos desde el código del kernel principal. Además, las estructuras de datos clave se pueden completar con campos de datos del proveedor que están disponibles para almacenar datos específicos del proveedor y, así, implementar estas funciones.

Los hooks del proveedor vienen en dos variantes (normal y restringida) que se basan en puntos de registro (no en eventos de registro) a los que se pueden adjuntar los módulos del proveedor. 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 gancho en do_exit() al que se pueda adjuntar un módulo del proveedor para su procesamiento. Un ejemplo de implementación incluye los siguientes hooks del proveedor.

  • Los hooks normales del proveedor usan DECLARE_HOOK() para crear una función de punto de seguimiento con el nombre trace_name, en la que name es el identificador único del seguimiento. Por convención, los nombres de los enlaces normales del proveedor comienzan con android_vh, por lo que el nombre del enlace sched_exit() sería android_vh_sched_exit.
  • Los hooks de proveedores restringidos son necesarios en casos como los hooks del 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 restringidos del proveedor no se pueden separar, por lo que los módulos que se adjuntan a un hook restringido nunca se pueden descargar. Los nombres de los hooks de proveedores restringidos comienzan con android_rvh.

Para agregar un gancho del proveedor, envía un problema a 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 del proveedor solo se encuentra en ACK, por lo que no envíes estos parches al kernel de Linux upstream.

Agrega campos del proveedor a las estructuras

Puedes asociar datos del proveedor con estructuras de datos clave agregando campos android_vendor_data con las macros ANDROID_VENDOR_DATA(). Por ejemplo, para admitir funciones agregadas por valor, agrega campos a las estructuras como se muestra en el siguiente ejemplo de código.

Para evitar posibles conflictos entre los campos que necesitan los proveedores y los que necesitan los OEM, estos últimos nunca deben usar los 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 hooks del proveedor

Agrega hooks del proveedor al código del kernel como puntos de seguimiento declarándolos 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);
    ...
}

Inicialmente, la función trace_android_vh_sched_exit() solo verifica si hay algo adjunto. Sin embargo, si un módulo del proveedor registra un controlador con register_trace_android_vh_sched_exit(), se llama a la función registrada. El controlador debe conocer el contexto con respecto a los bloqueos retenidos, el estado de RCS y otros factores. El gancho 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 requeridas para el gancho del proveedor, agrega el archivo de encabezado con la declaración del gancho a drivers/android/vendor_hooks.c y exporta los símbolos. Por ejemplo, el siguiente código completa la declaración del gancho 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 en la declaración del gancho deben definirse por completo para garantizar la estabilidad de la ABI. De lo contrario, no es seguro desreferenciar los punteros opacos ni usar la struct en contextos con tamaño. La inclusión que proporciona la definición completa de tales 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 en el CRC que interrumpan la KMI. En su lugar, declara los tipos por adelantado.

Cómo adjuntar a los hooks del proveedor

Para usar los hooks del proveedor, el módulo del proveedor debe registrar un controlador para el hook (por lo general, esto se hace durante la inicialización del módulo). Por ejemplo, el siguiente código muestra el controlador del módulo foo.ko 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);
    ...
}

Cómo usar hooks de proveedores desde archivos de encabezado

Para usar los enlaces del proveedor desde los archivos de encabezado, es posible que debas actualizar el archivo de encabezado del enlace del 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 de vinculación del proveedor que incluyes. 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 anule la definición de 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 agregar la función como una modificación específica de Android al kernel principal. Crea un problema en el registro de problemas (IT) 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 la UAPI deben realizarse de forma ascendente, a menos que se trate de cambios 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 del usuario del proveedor.
  • Nodos sysfs. No agregues nodos sysfs nuevos al kernel del GKI (estas adiciones solo son válidas en los módulos del proveedor). 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 formas compatibles y se deben cambiar de forma upstream si no son nodos sysfs específicos de Android. Puedes crear nodos sysfs específicos del proveedor para que los use el espacio del usuario del proveedor. De forma predeterminada, SELinux deniega el acceso a los nodos de sysfs por parte del espacio del usuario. Depende del proveedor agregar las etiquetas de SELinux adecuadas para permitir el acceso del software autorizado del proveedor.
  • Nodos de DebugFS. Los módulos del proveedor pueden definir nodos en debugfs solo para la depuración (ya que debugfs no se activa durante el funcionamiento normal del dispositivo).