Développer le code noyau pour GKI

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

Les développeurs doivent soumettre les modifications de code en amont en utilisant la liste de diffusion du noyau Linux (LKML) comme premier choix, et soumettre les modifications de code à la branche android-mainline ACK uniquement lorsqu'il existe une bonne raison pour laquelle l'amont n'est pas viable. Des exemples de raisons valables et comment les traiter sont répertoriés ci-dessous.

  • Le correctif a été soumis à LKML, mais n'a pas été accepté à temps pour la sortie du produit. Pour gérer ce patch :

    • Fournissez la preuve que le correctif a été soumis à LKML et les commentaires reçus pour le correctif, ou une heure estimée à laquelle le correctif sera soumis en amont.
    • Décidez d'un plan d'action pour placer le correctif dans ACK, faites-le approuver en amont, puis retirez-le d'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 soumis en amont car aucun module dans l'arborescence ne consomme ce symbole. Pour gérer ce correctif, fournissez des détails sur les raisons pour lesquelles votre module ne peut pas être soumis 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 sortie d'un produit. Pour gérer ce correctif, fournissez une estimation du délai avant lequel un correctif refactorisé sera soumis en amont (le correctif ne sera pas accepté dans ACK sans un plan de soumission d'un correctif refactorisé en amont pour examen).

  • Le correctif ne peut pas être accepté en amont car... <insérer la raison ici> . Pour gérer ce correctif, contactez l'équipe du noyau Android et travaillez avec nous sur les options permettant de refactoriser le correctif afin qu'il puisse être soumis pour examen et accepté en amont.

Il existe de nombreuses autres justifications potentielles. Lorsque vous soumettez votre bug ou votre correctif, incluez une justification valide et attendez-vous à des itérations et des discussions. Nous reconnaissons que l'ACK comportera certains 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 plannings des produits pour ce faire. Attendez-vous à ce que les exigences en amont deviennent plus strictes au fil du temps.

Exigences relatives aux correctifs

Les correctifs doivent être conformes aux normes de codage du noyau Linux décrites dans l' arborescence des sources Linux , qu'ils soient soumis en amont ou à ACK. Le script scripts/checkpatch.pl est exécuté dans le cadre des tests de pré-soumission de Gerrit, alors exécutez-le à l'avance pour vous assurer qu'il réussit. Pour exécuter le script checkpatch avec la même configuration que le test de pré-soumission, utilisez //build/kernel/static_analysis:checkpatch_presubmit . Pour plus de détails, consultez build/kernel/kleaf/docs/checkpatch.md .

Correctifs ACK

Les correctifs soumis à ACK doivent être conformes aux normes de codage du noyau Linux et aux directives de contribution . Vous devez inclure une balise Change-Id dans le message de validation ; si vous soumettez 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.

Soumettez d’abord les correctifs à LKML pour un examen en amont. Si le patch est :

  • Accepté en amont, il est automatiquement fusionné dans android-mainline .
  • Non accepté en amont, soumettez-le à android-mainline avec une référence à la soumission en amont ou une explication de la raison pour laquelle il n'a pas été soumis à 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é (tel que android12-5.4 et android11-5.4 pour les correctifs qui corrigent le code spécifique à Android). La soumission à android-mainline permet de tester avec de nouvelles versions candidates en amont et garantit que le correctif se trouve dans le prochain ACK basé sur 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 directives 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 sur « Android-mainline » sont susceptibles d'être acceptés dans ACK s'il existe un cas d'utilisation raisonnable.
  • BACKPORT: - Les correctifs provenant d'amont qui ne sont pas sélectionnés proprement et qui nécessitent des modifications sont également susceptibles d'être acceptés s'il existe un cas d'utilisation raisonnable.
  • FROMGIT: - Les correctifs sélectionnés dans une branche du responsable en vue de leur soumission à la ligne principale Linux peuvent être acceptés s'il y a une date limite à venir. Celles-ci doivent être justifiées tant sur le contenu que sur le calendrier.
  • FROMLIST: - Les correctifs qui ont été soumis à LKML mais qui n'ont pas encore été acceptés dans une branche du responsable ne seront probablement pas acceptés, à moins que la justification ne soit suffisamment convaincante pour que le correctif soit accepté, qu'il atterrisse ou non dans Linux en amont (nous supposons que ce ne sera pas le cas). Il doit y avoir un problème associé aux correctifs FROMLIST pour faciliter la discussion avec l'équipe du noyau Android.

Correctifs spécifiques à Android

Si vous ne parvenez pas à apporter les modifications requises en amont, vous pouvez tenter de soumettre directement les correctifs hors arborescence à ACK. La soumission de correctifs hors arborescence nécessite que vous créiez un problème dans le service informatique qui cite le correctif et explique pourquoi le correctif ne peut pas être soumis en amont (voir la liste précédente pour des exemples). Il existe cependant quelques cas où le code ne peut pas être soumis en amont. Ces cas sont traités comme suit et doivent suivre les directives de contribution pour les correctifs spécifiques à Android et être étiquetés avec le préfixe ANDROID: dans le sujet.

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 une modification d'un paramètre CONFIG , créez un problème dans le service informatique pour discuter de la modification. Toute modification CONFIG qui affecte l'interface du module noyau (KMI) après son gel est rejetée. Dans les cas où les partenaires demandent des paramètres contradictoires pour une seule configuration, nous résolvons les conflits par le biais de discussions sur les bogues associés.

Code qui n'existe pas en amont

Les modifications apportées au code déjà spécifique à Android ne peuvent pas être envoyées en amont. Par exemple, même si le pilote de classeur est conservé en amont, les modifications apportées aux fonctionnalités d’héritage prioritaire du pilote de classeur ne peuvent pas être envoyées en amont car elles sont spécifiques à Android. Soyez explicite dans votre bug et corrigez pourquoi le code ne peut pas être envoyé en amont. Si possible, divisez les correctifs en parties pouvant être soumises en amont et en parties spécifiques à Android qui ne peuvent pas être soumises en amont afin de minimiser la quantité de code hors arborescence conservé dans ACK.

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

Modules hors arborescence

Linux en amont décourage activement la prise en charge de la création de modules hors arborescence. C'est une position raisonnable étant donné que les responsables de Linux ne garantissent pas la compatibilité des sources ou des binaires dans le noyau et ne souhaitent pas prendre en charge du code qui n'est pas dans l'arborescence. Cependant, le GKI offre des garanties ABI pour les modules du fournisseur, garantissant ainsi 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 ACK mais ne sont pas acceptables pour l'amont.

Par exemple, considérons un correctif qui ajoute des macros EXPORT_SYMBOL_GPL() là où les modules qui utilisent l'exportation ne sont pas dans l'arborescence source. Bien que vous deviez tenter de demander EXPORT_SYMBOL_GPL() en amont et fournir un module qui utilise le symbole nouvellement exporté, s'il existe une justification valable pour laquelle le module n'est pas soumis en amont, vous pouvez plutôt soumettre le correctif à ACK. Vous devez inclure la justification de la raison pour laquelle le module ne peut pas être intégré en amont dans le problème. (Ne demandez pas la variante non GPL, EXPORT_SYMBOL() .)

Configurations cachées

Certains modules dans l'arborescence sélectionnent automatiquement les configurations caché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 création de modules hors arborescence, GKI inclut un mécanisme permettant d'activer les configurations cachées.

Pour activer une configuration cachée, ajoutez une instruction select dans init/Kconfig.gki afin qu'elle soit automatiquement sélectionnée en fonction de la configuration du noyau CONFIG_GKI_HACKS_TO_FIX , qui est activée dans gki_defconfig . Utilisez ce mécanisme uniquement pour les configurations caché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 chargeables

Pour les frameworks de noyau (tels que cpufreq ) qui prennent en charge les gouverneurs chargeables, vous pouvez remplacer le gouverneur par défaut (tel que le gouverneur schedutil de cpufreq . Pour les frameworks (tels que le framework thermal) qui ne prennent pas en charge les gouverneurs ou pilotes chargeables mais nécessitent toujours un implémentation spécifique au fournisseur, créez un problème au sein du service informatique et consultez l' équipe du noyau Android .

Nous travaillerons avec vous et les responsables en amont pour ajouter le support nécessaire.

Crochets du vendeur

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 les modules et ne sera 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 principal du noyau, GKI accepte les hooks des fournisseurs qui permettent d'invoquer des modules à partir du code principal du noyau. De plus, les structures de données clés peuvent être complétées par des champs de données de fournisseur 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 (normales et restreintes) qui sont basées sur des points de trace (et non sur des événements de trace) auxquels les modules du fournisseur peuvent s'attacher. 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 hook dans do_exit() auquel un module fournisseur peut s'attacher pour le traitement. Un exemple d’implémentation inclut les hooks de fournisseur suivants.

  • Les hooks de fournisseur normaux utilisent DECLARE_HOOK() pour créer une fonction tracepoint avec le nom trace_ namename est l'identifiant unique de la trace. Par convention, les noms de hooks normaux des fournisseurs commencent par android_vh , donc le nom du hook sched_exit() serait android_vh_sched_exit .
  • Des hooks de fournisseur restreints sont nécessaires pour des cas tels que les hooks de planificateur où la fonction attachée doit être appelée même si le processeur est hors ligne ou nécessite un contexte non atomique. Les hooks de fournisseurs restreints ne peuvent pas être détachés, de sorte que les modules attachés à un hook restreint ne peuvent jamais être déchargés. Les noms de hooks de fournisseurs restreints commencent par android_rvh .

Pour ajouter un hook de fournisseur, signalez un problème au service informatique et soumettez les 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 de fournisseur est uniquement dans ACK, vous n'envoyez donc pas ces correctifs à Linux en amont.

Ajouter des champs de fournisseur aux structures

Vous pouvez associer les données du fournisseur aux 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 indiqué dans l’exemple de code suivant.

Pour éviter les conflits potentiels entre les champs nécessaires aux fournisseurs et les champs nécessaires aux OEM, les OEM ne doivent jamais utiliser les champs déclarés à l'aide des macros ANDROID_VENDOR_DATA() . Au lieu de cela, les OEM doivent utiliser ANDROID_OEM_DATA() pour déclarer les 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 les hooks du fournisseur

Ajoutez des hooks de fournisseur au code du noyau 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 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() vérifie initialement uniquement si quelque chose est joint. Cependant, si un module fournisseur enregistre un gestionnaire à l'aide register_trace_android_vh_sched_exit() , la fonction enregistrée est appelée. Le gestionnaire doit être conscient du contexte en ce qui concerne les verrous détenus, l'état du RCS et d'autres facteurs. Le hook doit être défini dans un fichier d'en-tête dans le répertoire include/trace/hooks .

Par exemple, le code suivant donne 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éréférencer les pointeurs opaques ou d'utiliser la structure dans des contextes dimensionnés. L'inclusion qui fournit la définition complète de ces structures de données doit se trouver 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 brisent le KMI. Au lieu de cela, déclarez les types en avant.

Attacher aux crochets du fournisseur

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

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 alors ajouter la fonctionnalité en tant que modification spécifique à Android au noyau principal. Créez un problème dans le système de suivi des problèmes (IT) pour démarrer 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 UAPI doivent être effectuées en amont, sauf si les modifications concernent des interfaces spécifiques à Android. Utilisez des fichiers d'en-tête spécifiques au fournisseur pour définir les interfaces entre les modules du fournisseur et le code de l'espace utilisateur du fournisseur.
  • nœuds sysfs. N'ajoutez pas de nouveaux nœuds sysfs au noyau GKI (ces ajouts ne sont valables que dans les modules du fournisseur). Les nœuds sysfs utilisés par les bibliothèques indépendantes du SoC et des appareils et le code Java qui comprend le framework Android ne peuvent être modifiés que de manière compatible et doivent être modifiés en amont s'il ne s'agit pas de nœuds sysfs spécifiques à Android. Vous pouvez créer des nœuds sysfs spécifiques au fournisseur à utiliser par l'espace utilisateur du fournisseur. Par défaut, l'accès aux nœuds sysfs par l'espace utilisateur est refusé à l'aide de SELinux. Il appartient au fournisseur d'ajouter les étiquettes SELinux appropriées pour permettre l'accès aux logiciels du fournisseur autorisé.
  • Nœuds DebugFS. Les modules du fournisseur peuvent définir des nœuds dans debugfs pour le débogage uniquement (car debugfs n'est pas monté pendant le fonctionnement normal du périphérique).