Développer le code du noyau pour GKI

L'image de kernel générique (GKI) réduit la fragmentation du kernel en s'alignant étroitement sur le kernel Linux en amont. Toutefois, il existe des raisons valables pour lesquelles certains correctifs ne peuvent pas être acceptés en amont, et il existe des calendriers de produits qui doivent être respectés. Certains correctifs sont donc conservés dans les sources ACK (Common Kernel) d'Android à partir desquelles le GKI est créé.

Les développeurs doivent soumettre des modifications de code en amont en utilisant la liste de diffusion du noyau Linux (LKML) comme premier choix, et n'envoyer des modifications de code à la branche ACK android-mainline que lorsqu'il existe une bonne raison pour laquelle la diffusion en amont n'est pas viable. Vous trouverez ci-dessous des exemples de raisons valides et la manière de les gérer.

  • Le correctif a été envoyé à LKML, mais n'a pas été accepté à temps pour une version du produit. Pour gérer ce correctif:

    • Fournissez la preuve que le correctif a été envoyé à LKML et les commentaires reçus à son sujet, ou l'heure estimée à laquelle le correctif sera envoyé en amont.
    • Déterminez la procédure à suivre pour placer le correctif dans ACK, l'approuver en amont, puis le supprimer de ACK lorsque la version finale en amont est fusionnée dans ACK.
  • Le correctif définit EXPORT_SYMBOLS_GPL() pour un module fournisseur, mais n'a pas pu être envoyé en amont, car aucun module dans l'arborescence ne consomme ce symbole. Pour gérer ce correctif, indiquez pourquoi votre module ne peut pas être envoyé en amont et les alternatives que vous avez envisagées avant de faire cette demande.

  • Le correctif n'est pas assez générique pour l'amont et il n'y a pas le temps de le refactoriser avant la publication d'un produit. Pour gérer ce correctif, fournissez une estimation de la durée à laquelle un correctif refactorisé est envoyé en amont (le correctif ne sera pas accepté dans ACK sans plan d'envoi d'un correctif refactorisé en amont pour examen).

  • Impossible d'accepter le correctif en amont pour les raisons suivantes : <insert reason here>. Pour gérer ce correctif, contactez l'équipe du kernel Android et collaborez avec nous sur les options de refactorisation du correctif afin qu'il puisse être envoyé pour examen et accepté en amont.

Il existe de nombreuses autres justifications possibles. Lorsque vous envoyez votre bug ou votre correctif, incluez une justification valide et attendez-vous à des itérations et des discussions. Nous sommes conscients que l'ACK comporte des correctifs, en particulier dans les premières phases de GKI, alors que tout le monde apprend à travailler en amont, mais ne peut pas assouplir les calendriers des produits pour ce faire. Attendez-vous à ce que les exigences d'intégration en amont deviennent plus strictes au fil du temps.

Exigences concernant les correctifs

Les correctifs doivent respecter les normes de codage du kernel Linux décrites dans l'arborescence source Linux, qu'ils soient envoyés en amont ou à ACK. Le script scripts/checkpatch.pl est exécuté dans le cadre des tests de présoumission Gerrit. Exécutez-le à l'avance pour vous assurer qu'il réussit. Pour exécuter le script checkpatch avec la même configuration que les tests avant envoi, utilisez //build/kernel/static_analysis:checkpatch_presubmit. Pour en savoir plus, consultez build/kernel/kleaf/docs/checkpatch.md.

Correctifs ACK

Les correctifs envoyés à l'ACK doivent être conformes aux normes de codage du noyau Linux et aux consignes relatives aux contributions. Vous devez inclure une balise Change-Id dans le message de commit. Si vous envoyez le correctif à plusieurs branches (par exemple, android-mainline et android12-5.4), vous devez utiliser le même Change-Id pour toutes les instances du correctif.

Envoyez d'abord les correctifs à LKML pour un examen en amont. Si le correctif est:

  • Accepté en amont, il est fusionné automatiquement dans android-mainline.
  • Refusé en amont. Envoyez-le à android-mainline en indiquant une référence à l'envoi en amont ou en expliquant pourquoi il n'a pas été envoyé au LKML.

Une fois qu'un correctif est accepté en amont ou dans android-mainline, il peut être rétroporté vers l'ACK basé sur LTS approprié (par exemple, android12-5.4 et android11-5.4 pour les correctifs qui corrigent le code spécifique à Android). L'envoi à android-mainline permet d'effectuer des tests avec de nouvelles versions candidates en amont et garantit que le correctif se trouve dans le prochain ACK LTS. Les exceptions incluent les cas où un correctif en amont est rétroporté vers android12-5.4 (car le correctif est probablement déjà dans android-mainline).

Correctifs en amont

Comme spécifié dans les consignes de contribution, les correctifs en amont destinés aux noyaux ACK appartiennent aux groupes suivants (répertoriés par ordre de probabilité d'être acceptés).

  • UPSTREAM: : les correctifs sélectionnés dans "android-mainline" sont susceptibles d'être acceptés dans ACK s'il existe un cas d'utilisation raisonnable.
  • BACKPORT: : les correctifs en amont qui ne sont pas correctement sélectionnés et doivent être modifiés sont également susceptibles d'être acceptés s'il existe un cas d'utilisation raisonnable.
  • FROMGIT: : des correctifs sélectionnés par une branche du responsable de maintenance en vue de leur envoi au système principal Linux peuvent être acceptés en cas d'échéance à venir. Ces raisons doivent être justifiées aussi bien pour le contenu que pour le calendrier.
  • FROMLIST: : les correctifs envoyés à LKML, mais qui n'ont pas encore été acceptés dans une branche de responsable, sont peu susceptibles d'être acceptés, sauf si la justification est suffisamment convaincante pour que le correctif soit accepté, qu'il aboutisse ou non sous Linux en amont (nous partons du principe que non). Il doit y avoir un problème associé aux correctifs FROMLIST pour faciliter la discussion avec l'équipe du kernel Android.

Correctifs spécifiques à Android

Si vous ne parvenez pas à implémenter les modifications requises en amont, vous pouvez essayer d'envoyer directement des correctifs hors du dépôt à ACK. Pour envoyer des correctifs hors du dépôt, vous devez créer un problème dans l'IT qui cite le correctif et explique pourquoi il ne peut pas être envoyé en amont (voir la liste précédente pour obtenir des exemples). Toutefois, dans certains cas, le code ne peut pas être envoyé en amont. Ces cas sont couverts comme suit et doivent respecter les consignes de contribution pour les correctifs spécifiques à Android et être tagués avec le préfixe ANDROID: dans l'objet.

Modifications apportées à gki_defconfig

Toutes les modifications CONFIG apportées à gki_defconfig doivent être appliquées aux versions arm64 et x86, sauf si la CONFIG est spécifique à l'architecture. Pour demander la modification d'un paramètre CONFIG, créez un problème dans le service informatique afin de discuter de la modification. Toute modification de CONFIG qui affecte l'interface de module du noyau (KMI) après gelée est rejetée. Lorsque des partenaires demandent des paramètres contradictoires pour une seule configuration, nous les résolvons en discutant des bugs associés.

Code qui n'existe pas en amont

Les modifications du code déjà spécifique à Android ne peuvent pas être envoyées en amont. Par exemple, même si le pilote de liaison est géré en amont, les modifications apportées aux fonctionnalités d'héritage de priorité du pilote de liaison ne peuvent pas être envoyées en amont, car elles sont spécifiques à Android. Dans votre bug et votre correctif, indiquez clairement pourquoi le code ne peut pas être envoyé en amont. Si possible, divisez les correctifs en éléments pouvant être envoyés en amont et en éléments spécifiques à Android qui ne peuvent pas être envoyés en amont afin de réduire la quantité de code hors arborescence conservé dans l'ACK.

Les autres modifications de cette catégorie incluent les mises à jour des fichiers de représentation KMI, des listes de symboles KMI, de gki_defconfig, des scripts de compilation ou de configuration, ou d'autres scripts qui n'existent pas en amont.

Modules hors du répertoire

Linux en amont déconseille activement la création de modules hors arborescence. Cette position est raisonnable, car les responsables de Linux n'offrent aucune garantie sur la compatibilité binaire ou source du noyau, et ne souhaitent pas prendre en charge du code qui ne figure pas dans l'arborescence. Toutefois, le GKI fournit des garanties ABI pour les modules du fournisseur, ce qui garantit que les interfaces KMI sont stables pendant la durée de vie prise en charge d'un noyau. Par conséquent, il existe une classe de modifications pour prendre en charge les modules du fournisseur qui sont acceptables pour l'ACK, mais pas pour l'amont.

Prenons l'exemple d'un correctif qui ajoute des macros EXPORT_SYMBOL_GPL() où les modules qui utilisent l'exportation ne figurent pas dans l'arborescence source. Bien que vous deviez essayer de demander EXPORT_SYMBOL_GPL() en amont et fournir un module utilisant le symbole récemment exporté, vous pouvez envoyer le correctif à ACK s'il existe une justification valide pour laquelle le module n'est pas envoyé en amont. Vous devez inclure la justification pour laquelle le module ne peut pas être intégré dans le problème. (Ne demandez pas la variante non GPL, EXPORT_SYMBOL().)

Configurations masquées

Certains modules dans l'arborescence sélectionnent automatiquement des configurations masquées qui ne peuvent pas être spécifiées dans gki_defconfig. Par exemple, CONFIG_SND_SOC_TOPOLOGY est sélectionné automatiquement lorsque CONFIG_SND_SOC_SOF=y est configuré. Pour permettre la compilation de modules hors du répertoire, GKI inclut un mécanisme permettant d'activer les configurations masquées.

Pour activer une configuration masquée, ajoutez une instruction select dans init/Kconfig.gki afin qu'elle soit automatiquement sélectionnée en fonction de la configuration du kernel CONFIG_GKI_HACKS_TO_FIX, qui est activée dans gki_defconfig. N'utilisez ce mécanisme que pour les configurations masquées. Si la configuration n'est pas masquée, elle doit être spécifiée dans gki_defconfig, soit explicitement, soit en tant que dépendance.

Gouverneurs pouvant être chargés

Pour les frameworks de noyau (tels que cpufreq) compatibles avec les gouverneurs chargeables, vous pouvez ignorer le gouverneur par défaut (par exemple, le gouverneur schedutil de cpufreq). Pour les frameworks (tels que le framework thermique) qui ne sont pas compatibles avec les gouverneurs ou pilotes chargeables, mais qui nécessitent tout de même une implémentation spécifique au fournisseur, signalez un problème au service informatique et consultez l'équipe du noyau Android.

Nous collaborerons avec vous et les mainteneurs en amont pour ajouter la prise en charge nécessaire.

Hooks du fournisseur

Dans les versions précédentes, vous pouviez ajouter des modifications spécifiques au fournisseur directement dans le noyau principal. Cela n'est pas possible avec GKI 2.0, car le code spécifique au produit doit être implémenté dans des modules et n'est pas accepté dans les noyaux principaux en amont ou dans ACK. Pour activer les fonctionnalités à valeur ajoutée sur lesquelles les partenaires s'appuient avec un impact minimal sur le code du noyau principal, GKI accepte les hooks du fournisseur qui permettent d'appeler des modules à partir du code du noyau principal. De plus, les structures de données clés peuvent être complétées par des champs de données du fournisseur qui sont disponibles pour stocker des données spécifiques au fournisseur afin d'implémenter ces fonctionnalités.

Les hooks du fournisseur existent en deux variantes (normale et limitée) basées sur des points de trace (et non des événements de trace) auxquels les modules du fournisseur peuvent s'associer. Par exemple, au lieu d'ajouter une nouvelle fonction sched_exit() pour effectuer une comptabilité à la sortie de la tâche, les fournisseurs peuvent ajouter un crochet dans do_exit() auquel un module fournisseur peut s'attacher pour le traitement. Un exemple d'implémentation inclut les hooks du fournisseur suivants.

  • Les hooks de fournisseur normaux utilisent DECLARE_HOOK() pour créer une fonction de point de trace avec le nom trace_name, où name est l'identifiant unique de la trace. Par convention, les noms de crochets de fournisseur normaux commencent par android_vh. Le nom du crochet sched_exit() est donc android_vh_sched_exit.
  • Les hooks de fournisseurs restreints sont nécessaires dans les cas tels que les hooks de planificateur, où la fonction associée doit être appelée même si le processeur est hors connexion ou nécessite un contexte non atomique. Les hooks de fournisseurs restreints ne peuvent pas être dissociés. Par conséquent, les modules qui s'attachent à un hook restreint ne peuvent jamais être désinstallés. Les noms de crochets de fournisseur restreints commencent par android_rvh.

Pour ajouter un hook de fournisseur, signalez un problème au service informatique et envoyez des correctifs (comme pour tous les correctifs spécifiques à Android, un problème doit exister et vous devez fournir une justification). La prise en charge des hooks du fournisseur n'est disponible que dans ACK. Par conséquent, n'envoyez pas ces correctifs à Linux en amont.

Ajouter des champs de fournisseur aux structures

Vous pouvez associer des données de fournisseur à des structures de données clés en ajoutant des champs android_vendor_data à l'aide des macros ANDROID_VENDOR_DATA(). Par exemple, pour prendre en charge les fonctionnalités à valeur ajoutée, ajoutez des champs aux structures, comme illustré dans l'exemple de code suivant.

Pour éviter les conflits potentiels entre les champs requis par les fournisseurs et ceux requis par les OEM, ceux-ci ne doivent jamais utiliser de champs déclarés à l'aide de macros ANDROID_VENDOR_DATA(). Les OEM doivent plutôt utiliser ANDROID_OEM_DATA() pour déclarer des champs 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 */
}

Définir des hooks de fournisseur

Ajoutez des hooks du fournisseur au code du kernel en tant que points de trace en les déclarant à l'aide de DECLARE_HOOK() ou DECLARE_RESTRICTED_HOOK(), puis en les ajoutant au code en tant que point de trace. Par exemple, pour ajouter trace_android_vh_sched_exit() à la fonction de noyau do_exit() existante:

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

La fonction trace_android_vh_sched_exit() ne vérifie initialement que si quelque chose est associé. Toutefois, si un module fournisseur enregistre un gestionnaire à l'aide de register_trace_android_vh_sched_exit(), la fonction enregistrée est appelée. Le gestionnaire doit connaître le contexte concernant les verrous maintenus, l'état du RCS et d'autres facteurs. Le hook doit être défini dans un fichier d'en-tête du répertoire include/trace/hooks.

Par exemple, le code suivant fournit une déclaration possible pour trace_android_vh_sched_exit() dans le fichier 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>

Pour instancier les interfaces requises pour le hook du fournisseur, ajoutez le fichier d'en-tête avec la déclaration du hook à drivers/android/vendor_hooks.c et exportez les symboles. Par exemple, le code suivant complète la déclaration du 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);

REMARQUE: Les structures de données utilisées dans la déclaration de hook doivent être entièrement définies afin de garantir la stabilité de l'ABI. Sinon, il est dangereux de désadresser les pointeurs opaques ou d'utiliser la struct dans des contextes de taille. L'inclusion qui fournit la définition complète de ces structures de données doit figurer dans la section #ifndef __GENKSYMS__ de drivers/android/vendor_hooks.c. Les fichiers d'en-tête dans include/trace/hooks ne doivent pas inclure le fichier d'en-tête du noyau avec les définitions de type pour éviter les modifications CRC qui rompent le KMI. Déclarez plutôt les types de manière indirecte.

Associer aux hooks de fournisseur

Pour utiliser des hooks de fournisseur, le module du fournisseur doit enregistrer un gestionnaire pour le hook (généralement lors de l'initialisation du module). Par exemple, le code suivant affiche le gestionnaire de module foo.ko pour 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);
    ...
}

Utiliser des hooks de fournisseur à partir de fichiers d'en-tête

Pour utiliser des hooks de fournisseur à partir des fichiers d'en-tête, vous devrez peut-être mettre à jour le fichier d'en-tête de hook de fournisseur pour qu'il annule la définition de TRACE_INCLUDE_PATH afin d'éviter les erreurs de compilation indiquant qu'un fichier d'en-tête de point de trace est introuvable. Par exemple :

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.

Pour corriger ce type d'erreur de compilation, appliquez le correctif équivalent au fichier d'en-tête de hook de fournisseur que vous incluez. Pour en savoir plus, consultez la page 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

La définition de UNDEF_TRACE_INCLUDE_PATH indique à include/trace/define_trace.h de désdéfinir TRACE_INCLUDE_PATH après avoir créé les points de trace.

Fonctionnalités principales du noyau

Si aucune des techniques précédentes ne vous permet d'implémenter une fonctionnalité à partir d'un module, vous devez ajouter la fonctionnalité en tant que modification spécifique à Android au noyau principal. Créez un problème dans l'outil de suivi des problèmes (en informatique) pour engager la conversation.

Interface de programmation d'application utilisateur (UAPI)

  • Fichiers d'en-tête UAPI. Les modifications apportées aux fichiers d'en-tête de l'UAPI doivent se produire en amont, sauf si elles concernent des interfaces spécifiques à Android. Utilisez des fichiers d'en-tête spécifiques au fournisseur pour définir des interfaces entre les modules du fournisseur et le code de l'espace utilisateur du fournisseur.
  • Nœuds sysfs. N'ajoutez pas de nœuds sysfs au kernel GKI (ces ajouts ne sont valides que dans les modules du fournisseur). Les nœuds sysfs utilisés par les bibliothèques indépendantes du SoC et de l'appareil, ainsi que le code Java qui constitue le framework Android, ne peuvent être modifiés que de manière compatible et doivent être modifiés en amont s'ils ne sont pas des nœuds sysfs spécifiques à Android. Vous pouvez créer des nœuds sysfs spécifiques au fournisseur qui seront utilisés par l'espace utilisateur du fournisseur. Par défaut, l'accès aux nœuds sysfs par espace utilisateur est refusé à l'aide de SELinux. C'est au fournisseur d'ajouter les libellés SELinux appropriés pour autoriser l'accès par le logiciel du fournisseur autorisé.
  • Nœuds DebugFS. Les modules du fournisseur peuvent définir des nœuds dans debugfs uniquement à des fins de débogage (car debugfs n'est pas installé pendant le fonctionnement normal de l'appareil).