Consignes du module pour les fournisseurs

Utilisez les consignes suivantes pour accroître la robustesse et la fiabilité de vos modules de fournisseurs. Lorsqu'elles sont suivies, de nombreuses consignes peuvent vous aider à déterminer plus facilement l'ordre de chargement des modules et l'ordre dans lequel les pilotes doivent vérifier les appareils.

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

  • Les modules de bibliothèque sont des bibliothèques qui fournissent des API pour d'autres modules à utiliser. De tels 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 logbuffer. 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 pilotes sont des pilotes qui vérifient la présence d'un type d'appareil spécifique ou s'y lien. 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 recherche 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échargés, utilisez des exemples builtin_subsystem_driver() : 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 incohérences CRC dues à des types de données déclarés par transfert

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 schéma de conception.

N'accédez pas directement aux structures du 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 compatibilité endommagée avec les futures versions du noyau. Une structure de données est une structure de données de kernel 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 exemptées.

  • La structure de données est allouée ou initialisée par le module, mais elle est rendue visible par le noyau via sa transmission, indirectement (via un pointeur dans une structure) ou directement, en tant qu'entrée dans une fonction exportée par le noyau. Par exemple, un module de pilote cpufreq initialise 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 le struct device alloué n'est pas sondé, fw_devlink peut empêcher ses appareils consommateurs de 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 kernel 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 entre les 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 autre carte, le LDO8 peut fournir l'appareil tactile, créant ainsi un système fastidieux dans lequel 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 que la prise en charge du transfert de données a été ajoutée aux noyaux ARM, les consommateurs peuvent identifier les fournisseurs dans le transfert de données en se référant au nœud d'arborescence de l'appareil du fournisseur à l'aide d'un phandle. Les clients peuvent également nommer la ressource en fonction de son utilisation plutôt que de la personne qui la fournit. 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. Le DT associé à un tel appareil est semblable à 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 le pire des deux scénarios:

  • 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. Dans ce cas, pour vous assurer que la fonction .probe() de votre appareil échoue comme prévu, ne remplacez pas et n'associez pas à nouveau la valeur d'erreur. Le remplacement ou le remappage de la valeur d'erreur peut entraîner la suppression de -EPROBE_DEFER et faire en sorte que votre appareil ne soit jamais vérifié.

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é facilite le nettoyage du code de gestion des erreurs dans la fonction probe(), car elle ne nécessite pas de sauts goto pour libérer les ressources acquises par devm_*() et simplifie les opérations de dissociation du pilote.

Gérer la dissociation du pilote de l'appareil

Déterminez de manière intentionnelle le désassociation des pilotes d'appareils et ne laissez pas la désassociation indéfinie, car un état indéfini n'implique pas qu'il est interdit. Vous devez soit implémenter complètement la dissociation de pilotes d'appareil, soit désactiver explicitement la dissociation de pilotes d'appareil.

Implémenter la dissociation du pilote d'appareil

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 de 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 annuler la liaison d'un module de pilote proprement dit, 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 l'annulation de la liaison du pilote de l'appareil (non recommandé)

Lorsque vous choisissez de désactiver explicitement le désassociation du pilote de l'appareil, vous devez interdire 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 d'un module, assurez-vous que [permanent] est associé à ce module dans lsmod. Si vous n'utilisez pas module_exit() ou module_XXX_driver(), le module est marqué comme [permanent].

Ne pas charger de micrologiciel à partir de la fonction de sonde

Le pilote ne doit pas charger le micrologiciel à partir de la fonction .probe(), car il pourrait ne pas y avoir accès si le pilote effectue une vérification avant l'installation du système de fichiers basé sur la mémoire 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 si un pilote d'horloge 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

Acceptez et utilisez l'analyse asynchrone pour profiter des futures améliorations, telles que le chargement parallèle de modules ou l'analyse de l'appareil pour accélérer le temps de démarrage, qui pourraient être ajoutées à Android dans les prochaines versions. 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 l'analyse asynchrone, aucun code spécial n'est requis. Toutefois, tenez compte des points suivants lorsque vous ajoutez la prise en charge de la vérification asynchrone.

  • Ne faites pas d'hypothèses sur les dépendances précédemment 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 des modules.

  • Sondage asynchrone Les modules de pilote qui effectuent une analyse asynchrone n'analysent pas immédiatement un appareil lors du chargement du module. À la place, un thread parallèle gère l'analyse de l'appareil, ce qui peut entraîner un décalage entre l'ordre de chargement du module et l'ordre d'analyse 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 que l'ordre des vérifications est effectué et (une fois que tous les consommateurs d'un appareil ont vérifié) 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 instructions ci-dessous:

  • Dans le fichier C (ou dans tout fichier source autre qu'un fichier d'en-tête) de votre pilote, n'utilisez pas #ifdef CONFIG_XXX_MODULE, car il est inutilement restrictif et ne fonctionne plus si la configuration est renommée 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_).

  • Pour 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 plutôt dans 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.