Consignes concernant les modules de fournisseurs

Suivez les consignes ci-dessous pour améliorer la robustesse et la fiabilité de vos modules fournisseurs. De nombreuses consignes, si elles sont suivies, peuvent vous aider à déterminer plus facilement l'ordre de chargement correct des modules et l'ordre dans lequel les pilotes doivent rechercher des appareils.

Un module peut être une bibliothèque ou un pilote.

  • Les modules de bibliothèque sont des bibliothèques qui fournissent des API à d'autres modules. Ces modules ne sont généralement pas spécifiques au matériel. Les exemples de modules de bibliothèque incluent un module de chiffrement AES, le framework remoteproc compilé en tant que module et un module de tampon de journalisation. Le code du module dans module_init() s'exécute pour configurer les structures de données, mais aucun autre code ne s'exécute, sauf s'il est déclenché par un module externe.

  • Les modules de pilotes sont des pilotes qui recherchent ou se lient à un type d'appareil spécifique. Ces modules sont spécifiques au matériel. Exemples de modules de pilotes : UART, PCIe et matériel d'encodeur vidéo. Les modules de pilotes ne s'activent que lorsque l'appareil associé est présent sur le système.

    • Si l'appareil n'est pas présent, le seul code de module qui s'exécute est le code module_init() qui enregistre le pilote avec le framework de noyau du pilote.

    • Si l'appareil est présent et que le pilote le sonde ou s'y lie correctement, d'autres codes de module peuvent s'exécuter.

Utiliser correctement l'initialisation et la sortie du module

Les modules de pilotes doivent enregistrer un pilote dans module_init() et annuler l'enregistrement d'un pilote dans module_exit(). Pour appliquer ces restrictions, vous pouvez utiliser des macros de wrapper, ce qui évite d'utiliser directement les macros module_init(), *_initcall() ou module_exit().

  • Pour les modules pouvant être désinstallés, utilisez module_subsystem_driver(). Exemples : module_platform_driver(), module_i2c_driver() et module_pci_driver().

  • Pour les modules qui ne peuvent pas être désinstallés, utilisez builtin_subsystem_driver(). Exemples : builtin_platform_driver(), builtin_i2c_driver() et builtin_pci_driver().

Certains modules de pilotes utilisent module_init() et module_exit(), car ils enregistrent plusieurs pilotes. Pour un module de pilote qui utilise module_init() et module_exit() pour enregistrer plusieurs pilotes, essayez de les combiner en un seul pilote. Par exemple, vous pouvez différencier les appareils à l'aide de la chaîne compatible ou des données auxiliaires de l'appareil au lieu d'enregistrer des pilotes distincts. Vous pouvez également diviser le module du pilote en deux modules.

Exceptions de fonction d'initialisation et de sortie

Les modules de bibliothèque n'enregistrent pas de pilotes et sont exemptés des restrictions concernant module_init() et module_exit(), car ils peuvent avoir besoin de ces fonctions pour configurer des structures de données, des files d'attente de travail ou des threads de noyau.

Utiliser la macro MODULE_DEVICE_TABLE

Les modules de pilotes doivent inclure la macro MODULE_DEVICE_TABLE, qui permet à l'espace utilisateur de déterminer les appareils compatibles avec un module de pilote avant de le charger. Android peut utiliser ces données pour optimiser le chargement de modules, par exemple pour éviter de charger des modules pour des appareils qui ne sont pas présents dans le système. Pour obtenir des exemples d'utilisation de la macro, consultez le code en amont.

Éviter les différences de CRC dues aux types de données déclarés à l'avance

N'incluez pas de fichiers d'en-tête pour obtenir une visibilité sur les types de données déclarés par avance. Certaines structures, unions et autres types de données définis dans un fichier d'en-tête (header-A.h) peuvent être déclarés de manière anticipée dans un autre fichier d'en-tête (header-B.h) qui utilise généralement des pointeurs vers ces types de données. Ce modèle de code signifie que le noyau tente intentionnellement de garder la structure de données privée pour les utilisateurs de header-B.h.

Les utilisateurs de header-B.h ne doivent pas inclure header-A.h pour accéder directement à l'intérieur de ces structures de données déclarées par avance. Cela entraîne des problèmes de non-concordance de CRC CONFIG_MODVERSIONS (qui génère des problèmes de conformité ABI) lorsqu'un autre kernel (tel que le kernel GKI) tente de charger le module.

Par exemple, struct fwnode_handle est défini dans include/linux/fwnode.h, mais est déclaré en avant comme struct fwnode_handle; dans include/linux/device.h, car le noyau tente de garder les détails de struct fwnode_handle privés des utilisateurs de include/linux/device.h. Dans ce scénario, n'ajoutez pas #include <linux/fwnode.h> dans un module pour accéder aux membres de struct fwnode_handle. Toute conception dans laquelle vous devez inclure de tels fichiers d'en-tête indique un mauvais modèle de conception.

Ne pas accéder directement aux structures de kernel de base

L'accès direct ou la modification des structures de données du noyau principal peut entraîner un comportement indésirable, y compris des fuites de mémoire, des plantages et une incompatibilité avec les futures versions du noyau. Une structure de données est une structure de données de noyau principale lorsqu'elle remplit l'une des conditions suivantes:

  • La structure de données est définie sous KERNEL-DIR/include/. Par exemple, struct device et struct dev_links_info. Les structures de données définies dans include/linux/soc sont exclues.

  • La structure de données est allouée ou initialisée par le module, mais est rendue visible par le noyau en étant transmise, indirectement (via un pointeur dans une struct) ou directement, en entrée dans une fonction exportée par le noyau. Par exemple, un module de pilote cpufreq initialise le struct cpufreq_driver, puis le transmet en entrée à cpufreq_register_driver(). Après ce point, le module de pilote cpufreq ne doit pas modifier struct cpufreq_driver directement, car l'appel de cpufreq_register_driver() rend struct cpufreq_driver visible par le noyau.

  • La structure de données n'est pas initialisée par votre module. Par exemple, struct regulator_dev renvoyé par regulator_register().

N'accédez aux structures de données du noyau principales que via des fonctions exportées par le noyau ou via des paramètres transmis explicitement en entrée aux hooks du fournisseur. Si vous ne disposez pas d'API ni de crochet de fournisseur pour modifier des parties d'une structure de données de noyau de base, il s'agit probablement d'une décision délibérée. Vous ne devez donc pas modifier la structure de données à partir de modules. Par exemple, ne modifiez aucun champ dans struct device ou struct device.links.

  • Pour modifier device.devres_head, utilisez une fonction devm_*() telle que devm_clk_get(), devm_regulator_get() ou devm_kzalloc().

  • Pour modifier les champs dans struct device.links, utilisez une API de liaison d'appareils telle que device_link_add() ou device_link_del().

Ne pas analyser les nœuds de l'arborescence des appareils avec la propriété compatible

Si un nœud d'arborescence d'appareils (DT) possède une propriété compatible, un struct device lui est alloué automatiquement ou lorsque of_platform_populate() est appelé sur le nœud DT parent (généralement par le pilote de l'appareil parent). L'attente par défaut (à l'exception de certains appareils initialisés tôt pour le planificateur) est qu'un nœud DT avec une propriété compatible possède un struct device et un pilote de périphérique correspondant. Toutes les autres exceptions sont déjà gérées par le code en amont.

De plus, fw_devlink (anciennement of_devlink) considère les nœuds DT avec la propriété compatible comme des appareils avec un struct device alloué qui est sondé par un pilote. Si un nœud DT possède une propriété compatible, mais que l'struct device allouée n'est pas sondée, fw_devlink peut empêcher ses appareils consommateurs de la sonder ou empêcher les appels sync_state() d'être appelés pour ses appareils fournisseurs.

Si votre pilote utilise une fonction of_find_*() (telle que of_find_node_by_name() ou of_find_compatible_node()) pour trouver directement un nœud DT doté d'une propriété compatible, puis analyser ce nœud DT, corrigez le module en écrivant un pilote de périphérique pouvant sonder l'appareil ou en supprimant la propriété compatible (uniquement si elle n'a pas été transmise en amont). Pour discuter d'autres options, contactez l'équipe du noyau Android à l'adresse kernel-team@android.com et soyez prêt à justifier vos cas d'utilisation.

Rechercher des fournisseurs à l'aide de phandles DT

Dans la mesure du possible, faites référence à un fournisseur à l'aide d'un phandle (une référence ou un pointeur vers un nœud DT). L'utilisation de liaisons DT et de phandles standards pour faire référence aux fournisseurs permet à fw_devlink (anciennement of_devlink) de déterminer automatiquement les dépendances inter-appareils en analysant le DT au moment de l'exécution. Le noyau peut ensuite interroger automatiquement les appareils dans l'ordre approprié, ce qui élimine le besoin d'un ordre de chargement de module ou d'un MODULE_SOFTDEP().

Scénario ancien (pas de prise en charge de DT dans le noyau ARM)

Auparavant, avant que la prise en charge de DT ne soit ajoutée aux noyaux ARM, les consommateurs tels que les appareils tactiles recherchaient des fournisseurs tels que des régulateurs à l'aide de chaînes uniques au niveau mondial. Par exemple, le pilote PMIC ACME peut enregistrer ou annoncer plusieurs régulateurs (par exemple, acme-pmic-ldo1 à acme-pmic-ldo10), et un pilote tactile peut rechercher un régulateur à l'aide de regulator_get(dev, "acme-pmic-ldo10"). Toutefois, sur une carte différente, le LDO8 peut alimenter l'appareil tactile, ce qui crée un système lourd où le même pilote tactile doit déterminer la chaîne de recherche correcte pour le régulateur pour chaque carte dans laquelle l'appareil tactile est utilisé.

Scénario actuel (compatibilité DT dans le noyau ARM)

Une fois la prise en charge de la DT ajoutée aux noyaux ARM, les consommateurs peuvent identifier les fournisseurs dans la DT en se référant au nœud de l'arborescence des appareils du fournisseur à l'aide d'un phandle. Les consommateurs peuvent également nommer la ressource en fonction de son utilisation plutôt que de son fournisseur. Par exemple, le pilote tactile de l'exemple précédent peut utiliser regulator_get(dev, "core") et regulator_get(dev, "sensor") pour obtenir les fournisseurs qui alimentent le cœur et le capteur de l'appareil tactile. La DT associée à un tel appareil ressemble à l'exemple de code suivant:

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 {
        ...
    };
};

Scénario du pire des deux mondes

Certains pilotes portés à partir d'anciens noyaux incluent un ancien comportement dans le DT qui prend la pire partie de l'ancien schéma et la force sur le nouveau schéma censé simplifier les choses. Dans ces pilotes, le pilote consommateur lit la chaîne à utiliser pour la recherche à l'aide d'une propriété DT spécifique à l'appareil. Le fournisseur utilise une autre propriété spécifique au fournisseur pour définir le nom à utiliser pour enregistrer la ressource du fournisseur. Le consommateur et le fournisseur continuent ensuite d'utiliser le même ancien schéma d'utilisation de chaînes pour rechercher le fournisseur. Dans ce scénario du pire des cas:

  • Le pilote tactile utilise un code semblable au code suivant:

    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);
    
  • Le DT utilise un code semblable à celui-ci:

    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"
      };
    };
    

Ne modifiez pas les erreurs d'API du framework

Les API de framework, telles que regulator, clocks, irq, gpio, phys et extcon, renvoient -EPROBE_DEFER en tant que valeur de retour d'erreur pour indiquer qu'un appareil tente de sonder, mais qu'il ne peut pas le faire pour le moment. Le noyau doit alors réessayer la sonde plus tard. Pour vous assurer que la fonction .probe() de votre appareil échoue comme prévu dans de tels cas, ne remplacez pas ni ne remappez pas la valeur d'erreur. Le remplacement ou le remappage de la valeur d'erreur peut entraîner l'abandon de -EPROBE_DEFER et empêcher la sonde de votre appareil.

Utiliser les variantes d'API devm_*()

Lorsque l'appareil acquiert une ressource à l'aide d'une API devm_*(), la ressource est automatiquement libérée par le noyau si l'appareil ne parvient pas à effectuer de sondage, ou si le sondage aboutit et qu'il est ensuite dissocié. Cette fonctionnalité rend le code de gestion des erreurs dans la fonction probe() plus propre, car il ne nécessite pas de sauts goto pour libérer les ressources acquises par devm_*() et simplifie les opérations de désassociation des pilotes.

Gérer la dissociation de l'appareil et du pilote

Déterminez de manière intentionnelle la dissociation des pilotes d'appareils et ne laissez pas la dissociation indéfinie, car l'état "undefined" n'implique pas qu'elle est interdite. Vous devez implémenter complètement la dissociation de l'appareil et du pilote ou désactiver explicitement la dissociation de l'appareil et du pilote.

Implémenter la dissociation de l'appareil et du pilote

Lorsque vous choisissez d'implémenter complètement la dissociation des pilotes d'appareils, dissociez-les de manière propre pour éviter les fuites de mémoire ou de ressources et les problèmes de sécurité. Vous pouvez associer un appareil à un pilote en appelant la fonction probe() d'un pilote et dissocier un appareil en appelant la fonction remove() du pilote. Si aucune fonction remove() n'existe, le noyau peut toujours dissocier l'appareil. Le noyau du pilote suppose qu'aucun travail de nettoyage n'est nécessaire par le pilote lorsqu'il se dissocie de l'appareil. Un pilote qui n'est pas associé à un appareil n'a pas besoin d'effectuer de nettoyage explicite lorsque les deux conditions suivantes sont remplies:

  • Toutes les ressources acquises par la fonction probe() d'un pilote sont via des API devm_*().

  • L'appareil matériel n'a pas besoin d'une séquence d'arrêt ou de mise en veille.

Dans ce cas, le noyau du pilote gère la libération de toutes les ressources acquises via les API devm_*(). Si l'une des déclarations précédentes est fausse, le pilote doit effectuer un nettoyage (libérer les ressources et arrêter ou mettre en veille le matériel) lorsqu'il se dissocie d'un appareil. Pour vous assurer qu'un appareil peut dissocier un module de pilote de manière propre, utilisez l'une des options suivantes:

  • Si le matériel n'a pas besoin d'une séquence d'arrêt ou de mise en veille, modifiez le module de l'appareil pour acquérir des ressources à l'aide des API devm_*().

  • Implémentez l'opération du pilote remove() dans la même structure que la fonction probe(), puis effectuez les étapes de nettoyage à l'aide de la fonction remove().

Désactiver explicitement le désassociation de l'appareil et du pilote (non recommandé)

Lorsque vous choisissez de désactiver explicitement le désassociation du pilote de périphérique, vous devez désactiver le désassociation et l'arrêt du module.

  • Pour interdire le désassociation, définissez l'indicateur suppress_bind_attrs sur true dans le fichier struct device_driver du pilote. Ce paramètre empêche les fichiers bind et unbind d'apparaître dans le répertoire sysfs du pilote. Le fichier unbind permet à l'espace utilisateur de déclencher le désassociation d'un pilote de son appareil.

  • Pour interdire le déchargement du module, assurez-vous que le module contient [permanent] dans lsmod. Si vous n'utilisez pas module_exit() ou module_XXX_driver(), le module est marqué comme [permanent].

Ne pas charger de micrologiciel depuis la fonction de sonde

Le pilote ne doit pas charger le micrologiciel à partir de la fonction .probe(), car il risque de ne pas y avoir accès si le pilote sonde avant le montage du système de fichiers basé sur le flash ou le stockage permanent. Dans ce cas, l'API request_firmware*() peut se bloquer pendant une longue période, puis échouer, ce qui peut ralentir inutilement le processus de démarrage. Déplacez plutôt le chargement du micrologiciel au moment où un client commence à utiliser l'appareil. Par exemple, un pilote d'affichage peut charger le micrologiciel lorsque l'appareil d'affichage est ouvert.

L'utilisation de .probe() pour charger le micrologiciel peut être acceptable dans certains cas, par exemple dans un pilote d'horloge qui a besoin d'un micrologiciel pour fonctionner, mais que l'appareil n'est pas exposé à l'espace utilisateur. D'autres cas d'utilisation appropriés sont possibles.

Implémenter l'analyse asynchrone

Assurez-vous que votre application est compatible avec la vérification asynchrone et utilisez-la pour profiter des futures améliorations qui pourraient être ajoutées à Android dans les prochaines versions, telles que le chargement de modules parallèles ou la vérification de l'appareil pour accélérer le temps de démarrage. Les modules de pilotes qui n'utilisent pas de sondage asynchrone peuvent réduire l'efficacité de ces optimisations.

Pour indiquer qu'un pilote accepte et préfère l'analyse asynchrone, définissez le champ probe_type dans le membre struct device_driver du pilote. L'exemple suivant montre cette prise en charge activée pour un pilote de plate-forme:

static struct platform_driver acme_driver = {
        .probe          = acme_probe,
        ...
        .driver         = {
                .name   = "acme",
                ...
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
};

Pour que le pilote fonctionne avec la sonde asynchrone, aucun code spécial n'est requis. Toutefois, tenez compte des points suivants lorsque vous ajoutez la prise en charge des sondages asynchrones.

  • Ne faites pas d'hypothèses sur les dépendances déjà analysées. Vérifiez directement ou indirectement (la plupart des appels de framework) et renvoyez -EPROBE_DEFER si un ou plusieurs fournisseurs ne sont pas encore prêts.

  • Si vous ajoutez des appareils enfants dans la fonction de sonde d'un appareil parent, ne partez pas du principe que les appareils enfants sont immédiatement sondés.

  • Si une sonde échoue, effectuez une gestion appropriée des erreurs et un nettoyage (voir Utiliser les variantes d'API de devm_*()).

N'utilisez pas MODULE_SOFTDEP pour commander des sondes d'appareil

La fonction MODULE_SOFTDEP() n'est pas une solution fiable pour garantir l'ordre des sondes de l'appareil et ne doit pas être utilisée pour les raisons suivantes.

  • Vérification différée. Lorsqu'un module se charge, la sonde de l'appareil peut être différée, car l'un de ses fournisseurs n'est pas prêt. Cela peut entraîner une incohérence entre l'ordre de chargement du module et l'ordre de la sonde de l'appareil.

  • Un pilote, plusieurs appareils Un module de pilote peut gérer un type d'appareil spécifique. Si le système inclut plusieurs instances d'un type d'appareil et que ces appareils ont chacun une exigence d'ordre de sonde différente, vous ne pouvez pas respecter ces exigences à l'aide de l'ordre de chargement du module.

  • Sondage asynchrone Les modules de pilotes qui effectuent une analyse asynchrone n'analysent pas immédiatement un appareil lorsque le module est chargé. À la place, un thread parallèle gère la sonde de l'appareil, ce qui peut entraîner un décalage entre l'ordre de chargement du module et l'ordre de la sonde de l'appareil. Par exemple, lorsqu'un module de pilote principal I2C effectue une analyse asynchrone et qu'un module de pilote tactile dépend du PMIC situé sur le bus I2C, même si le pilote tactile et le pilote PMIC se chargent dans l'ordre correct, la sonde du pilote tactile peut être tentée avant la sonde du pilote PMIC.

Si vous avez des modules de pilote qui utilisent la fonction MODULE_SOFTDEP(), corrigez-les pour qu'ils n'utilisent plus cette fonction. Pour vous aider, l'équipe Android a intégré des modifications qui permettent au noyau de gérer les problèmes de tri sans utiliser MODULE_SOFTDEP(). Plus précisément, vous pouvez utiliser fw_devlink pour vous assurer de l'ordre des sondes et (après que tous les consommateurs d'un appareil ont effectué une sonde) utiliser le rappel sync_state() pour effectuer les tâches nécessaires.

Utilisez #if IS_ENABLED() au lieu de #ifdef pour les configurations

Utilisez #if IS_ENABLED(CONFIG_XXX) au lieu de #ifdef CONFIG_XXX pour vous assurer que le code du bloc #if continue de se compiler si la configuration passe à une configuration trinaire à l'avenir. Les différences sont les suivantes:

  • #if IS_ENABLED(CONFIG_XXX) renvoie true lorsque CONFIG_XXX est défini sur module (=m) ou intégré (=y).

  • #ifdef CONFIG_XXX s'évalue à true lorsque CONFIG_XXX est défini sur intégré (=y) , mais pas lorsque CONFIG_XXX est défini sur module (=m). N'utilisez cette valeur que lorsque vous êtes certain de vouloir faire la même chose lorsque la configuration est définie sur module ou est désactivée.

Utiliser la macro appropriée pour les compilations conditionnelles

Si un CONFIG_XXX est défini sur module (=m), le système de compilation définit automatiquement CONFIG_XXX_MODULE. Si votre pilote est contrôlé par CONFIG_XXX et que vous souhaitez vérifier s'il est compilé en tant que module, suivez les consignes suivantes:

  • Dans le fichier C (ou tout fichier source qui n'est pas un fichier d'en-tête) de votre pilote, n'utilisez pas #ifdef CONFIG_XXX_MODULE, car il est inutilement restrictif et ne fonctionne pas si la configuration est renommée en CONFIG_XYZ. Pour tout fichier source sans en-tête compilé dans un module, le système de compilation définit automatiquement MODULE pour la portée de ce fichier. Par conséquent, pour vérifier si un fichier C (ou tout autre fichier source sans en-tête) est compilé dans le cadre d'un module, utilisez #ifdef MODULE (sans le préfixe CONFIG_).

  • Dans les fichiers d'en-tête, la même vérification est plus délicate, car les fichiers d'en-tête ne sont pas compilés directement dans un binaire, mais dans le cadre d'un fichier C (ou d'autres fichiers sources). Respectez les règles suivantes pour les fichiers d'en-tête:

    • Pour un fichier d'en-tête qui utilise #ifdef MODULE, le résultat change en fonction du fichier source qui l'utilise. Cela signifie que différentes parties du code du même fichier d'en-tête dans le même build peuvent être compilées pour différents fichiers sources (module par rapport à intégré ou désactivé). Cela peut être utile lorsque vous souhaitez définir une macro qui doit se développer d'une manière pour le code intégré et d'une autre pour un module.

    • Pour un fichier d'en-tête qui doit compiler un extrait de code lorsqu'un CONFIG_XXX spécifique est défini sur "module" (que le fichier source qui l'inclut soit un module ou non), le fichier d'en-tête doit utiliser #ifdef CONFIG_XXX_MODULE.