Étude du pilote du bouton ACPI dans le noyau 2.6

Gazette Linux n°106 — Septembre 2004

Pramode C.E.

Article paru dans le n°106 de la Gazette Linux de septembre 2004.

Traduction française par Joëlle Cornavin .

Relecture de la traduction française par Emmanuel Araman .

Article publié sous Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.


Table des matières
1. Premiers pas
2. Quelques expériences
3. Lecture du code
4. Compréhension de button.c
5. Ajout d'une interface de pilote caractère
6. Conclusion
7. Autres lectures et références

Un étudiant est venu me dire que son système Linux ne s'arrêtait pas automatiquement quand il appuyait sur le bouton d'alimentation. Il s'agissait d'un problème d'ACPI (Advanced Configuration and Power Interface), qui n'était pas configuré correctement sur sa machine. C'était une occasion d'en savoir un peu plus sur la gestion de l'alimentation et son implémentation sous Linux. Ce thème ne me passionnait pas particulièrement, mais la lecture de quelques fichiers dans drivers/acpi m'a convaincu que je pourrais le transformer en une démonstration intéressante sur la façon d'appréhender la lecture et le déchiffrement du code du noyau.


1. Premiers pas

La gestion de l'alimentation est un problème épineux dans les systèmes modernes — le système d'exploitation devrait être capable de décider des aspects comme le niveau de charge de la batterie, la température du processeur, etc. et de prendre des mesures intelligentes. L'ACPI est conçue dans le but de donner au système d'exploitation autant de souplesse que possible, en traitant les problèmes de gestion de l'alimentation. J'ai pu le comprendre en faisant une rapide recherche sur Google. Quand un article mentionnait que l'ensemble des spécifications ACPI dépassait largement 500 pages, j'en ai conclu que ce n'était pas ma tasse de thé. Je voulais juste savoir ce que le système d'exploitation fait pour arrêter et mettre hors tension mon système proprement quand j'appuie sur le bouton d'alimentation : inutile de lire 500 pages d'un manuel pour comprendre cela.


2. Quelques expériences

La première chose était de découvrir si mon noyau renfermait du code ACPI compilé. Un rapide



dmesg | grep ACPI

a résolu le problème — c'est le cas. Comme avec toutes choses complexes (USB par exemple), on peut à juste titre émettre l'hypothèse selon laquelle le noyau pourrait contenir du code de « bas niveau » (core) pour gérer tous les aspects vraiment ardus, comme communiquer avec le BIOS ACPI et déterminer tous les « événements » qui s'y sont produits (appuyer sur le commutateur d'alimentation est un événement). Le code qui « réagit » à ces événements devrait être beaucoup plus simple et serait la plupart du temps écrit sous forme de module. En gardant à l'esprit la philosophie de base d'Unix d'une politique du « pousser » autant que possible vers l'utilisateur, vous devinerez que cette « réaction » consisterait le plus souvent faire savoir à un processus utilisateur que quelque chose s'est produit. Maintenant, comment un pilote de périphérique fait-il savoir à un processus utilisateur que quelque chose s'est produit ? Deux des méthodes les plus simples consistent soit à employer un élément de fichier dans proc, soit à faire appel à un fichier de périphérique, l'élément de fichier dans proc étant la plus probable. Je suis assez sûr, en regardant dans /proc, d'avoir vu un répertoire acpi.

Le dernier maillon de la chaîne serait un programme au niveau utilisateur qui lit un fichier donné sous /proc/acpi et effectue quelques actions en fonction des informations d'événements qu'il obtient du noyau — par exemple un programme appelé acpid. Un ps ax m'a montré qu'un tel programme tournait. L'exécution d'un



strace -p <pid>

a affiché le programme effectuant un select sur les deux descripteurs de fichier 3 et 4. Tuer acpid et le lancer à nouveau comme suit :



strace acpid

a affiché la ligne suivante comme faisant partie de la sortie :


>
open("/proc/acpi/event", O_RDONLY);

Voilà ! Il suffit de lire /proc/acpi/event et vous saurez que tous les « événements » de gestion de l'alimentation se sont produits. J'ai tué acpid et lancé un



cat /proc/acpi/event

Comme prévu, le processus s'est bloqué. Appuyer sur le bouton d'alimentation a provoqué l'affichage de la ligne suivante :


button/power PWRF 00000080 00000001

La chaîne est presque complète. Comment le code core ACPI fait-il savoir qu'on a appuyé sur le bouton ? Le noyau ne peut pas continuer à utiliser un genre de boucle active (busy loop) — les interruptions sont les seules issues. Il est presque certain qu'un cat /proc/interrupts affichera :


           CPU0       CPU1       
  0:   11676819   11663518    IO-APIC-edge  timer
  1:       8998       7247    IO-APIC-edge  i8042
  2:          0          0          XT-PIC  cascade
  8:          1          0    IO-APIC-edge  rtc
  9:          3          1   IO-APIC-level  acpi
 12:         84          0    IO-APIC-edge  i8042

Note

Le reste a été supprimé.

Observons l'IRQ 9 — c'est ce à quoi nous nous intéressons. Appuyer sur le bouton a provoqué l'incrémentation du compteur d'interruption.

Voici la chaîne complète :


              Bouton d'alimentation
                   |
                   |
                 IRQ 9
                   |
                   |
             Code ACPI core
                   |
                   |
              Pilote du bouton
                   |
                   |
    Lecture de /proc/acpi/event via le programme utilisateur

3. Lecture du code

Un rapide coup d'½il dans /usr/src/linux-2.6.5/drivers montre un répertoire appelé acpi, contenant un fichier dénommé button.c. Il s'agit probablement du code du pilote plug-in qui a répondu aux événements bouton. Un autre fichier appelé event.c comporte des routines qui répondent aux requêtes d'ouverture/lecture dans l'espace utilisateur.

Une requête de « lecture » dans l'espace utilisateur sur le fichier /proc/acpi/events invoque acpi_system_read_event. À son tour, cette fonction invoque acpi_bus_receive_event qui bloque si le code du pilote du bouton n'a posté aucun événement. Les événements sont postés par une fonction appelée acpi_bus_generate_event définie dans le fichier bus.c.


4. Compréhension de button.c

Se faire une « haute idée » du fonctionnement du code du pilote du bouton est assez simple. Quand vous essayez d'aborder le code du pilote comme celui-ci, il serait bon de supprimer quelques lignes, de façon à pouvoir mieux vous concentrer sur l'essentiel. Les fonctions acpi_button_info_seq_show, acpi_button_info_open_fs, acpi_button_state_seq_show, acpi_button_state_open_fs, acpi_button_add_fs et acpi_button_remove_fs peuvent sans risque être retirées. Une simple printk au début des fonctions que nous trouvons intéressantes nous donnera une certaine idée du flot de contrôle de la commande. Voici la première version abrégée de button.c :

Listing 1

Voici ce que j'ai remarqué quand le module a été compilé séparément et chargé dans le noyau (2.6.5) :

Il est à nouveau temps de nous livrer à quelques conjectures. La fonction acpi_button_init appelle acpi_bus_register_driver avec l'adresse d'un objet de type struct acpi_driver :


static struct acpi_driver acpi_button_driver = {
        .name =         ACPI_BUTTON_DRIVER_NAME,
        .class =        ACPI_BUTTON_CLASS,
        .ids =          "ACPI_FPB,ACPI_FSB,PNP0C0D,PNP0C0C,PNP0C0E",
        .ops =          {
                                .add =          acpi_button_add,
                                .remove =       acpi_button_remove,
                        },
};

L'hypothèse est que acpi_button_add communiquerait avec le code core de plus bas niveau et assurerait l'invocation de quelques fonctions gestionnaires lorsque des événements comme l'activation d'un bouton d'alimentation ou de veille sont détectés (donc — un bon nombre d'imprécisions ici — le dialogue entre le code source et un bouton POWER et POWERF ainsi qu'un bouton SLEEP et SLEEPF. Est-ce que quelqu'un qui connaît mieux le code pourrait m'éclairer à propos de POWERF et SLEEPF ?). Puisque des gestionnaires doivent être invoqués pour plusieurs types de boutons, la fonction acpi_bus_register_driver devrait examiner le champ .ids de la structure et analyser un par un les noms qu'elle contient, en invoquant la fonction appelant la fonction acpi_button_add à chaque fois qu'elle rencontre un autre nom (ce ne sont que des conjectures — je n'ai pas vraiment regardé dans scan.c qui implémente la fonction acpi_bus_register_driver. L'ennui est que je ne comprends pas pourquoi add n'est appelée que deux fois).

Dans la révision suivante de button.c, je ne garde qu'un nom, ACPI_FPB dans le champ .ids et réduis acpi_button_add/acpi_button_remove pour traiter uniquement le cas du bouton d'alimentation. La ligne la plus importante à rechercher dans cette fonction est l'invocation de acpi_install_fixed_event_handler, qui déclare la fonction acpi_button_notify_fixed comme gestionnaire à invoquer quand le système ACPI détecte une activité du bouton.

Listing 2

Le gestionnaire (acpi_button_notify_fixed) appelle acpi_button_notify, qui à son tour appelle acpi_bus_generate_event. Souvenez-vous qu'une lecture sur /proc/acpi/events a été bloquée par acpi_bus_receive_event. La lecture (et le processus au niveau utilisateur) sort du bloc quand acpi_bus_generate_event est invoquée !


5. Ajout d'une interface de pilote caractère

Modifions le pilote de manière à ce qu'il utilise une interface différente pour communiquer avec le « pays magique des utilisateurs » : un pilote en mode caractère simple (bien sûr, c'est une idée plutôt stupide, mais nous devons effectuer un changement !). La méthode 'lecture' du pilote va s'endormir en appelant interruptible_sleep_on alors que la fonction acpi_button_notify_fixed la réveille.

Listing 3

Supposons que le pilote soit réservé à un nombre majeur de 254. Nous créons un fichier de périphérique :



mknod button c 254 0

et essayons un cat button. Le processus de lecture va s'endormir. Maintenant, nous appuyons sur le commutateur Marche et il sort immédiatement du bloc !


6. Conclusion

Un script shell simple (nous l'appellerons silly_acpid) :


#!/bin/sh
cat /home/pce/button
poweroff

Lancez cette commande en arrière-plan et appuyez sur le commutateur Marche — le système s'arrête et se met hors tension comme par magie !


7. Autres lectures et références

Le numéro d'octobre 2003 de la revue ACM Queue offre un article en préambule sur l'ACPI destiné aux débutants ; sa lecture est digne d'intérêt. Le projet Linux ACPI a sa propre page d'accueil ici. Vous pouvez télécharger le code source du programme acpi daemon ici. Le processus de compilation du module pour les noyaux de la série 2.6 est différent de celui de la série 2.4. Vous trouverez plus d'informations dans /usr/src/linux/Documentation/kbuild/.

Je suis formateur et travaille pour IC Software® à Kerala (Inde). J'aurais aimé travailler dans le domaine de la chimie organique, mais je donne le meilleur de moi-même à ma seconde passion, jouer avec Linux et enseigner la programmation !