Écrire un pilote Linux embarqué : du devicetree au sysfs, sans se tromper de couche
La plupart des pilotes Linux mal écrits ne le sont pas à cause d'une erreur de code, mais parce qu'ils ont été écrits dans la mauvaise couche. Avant la première ligne, une seule question compte : existe-t-il déjà un sous-système pour ce type de matériel.
Choisir la couche, avant tout
Linux fournit des sous-systèmes pour presque toutes les familles de périphériques. Écrire un pilote de capteur de température comme un simple périphérique caractère avec des ioctl maison, c'est se priver de tout ce que le noyau offre déjà et imposer une interface propriétaire à l'espace utilisateur.
| Type de matériel | Sous-système | Interface obtenue |
|---|---|---|
| Capteur, convertisseur analogique | IIO | /sys/bus/iio/, tampons, horodatage |
| LED, rétroéclairage | LED, backlight | /sys/class/leds/ |
| Bouton, clavier, tactile | Input | /dev/input/event* |
| Alimentation, batterie | Power supply | /sys/class/power_supply/ |
| Horloge temps réel | RTC | /dev/rtc0, hwclock |
| Chien de garde | Watchdog | /dev/watchdog |
| Contrôleur de bus | I2C, SPI adapter | bus standard |
| Mémoire non volatile | NVMEM | /sys/bus/nvmem/ |
Un pilote IIO de trente lignes vous donne l'horodatage, la mise en tampon, le déclenchement et une interface que tous les outils comprennent. Le même capteur en périphérique caractère, c'est trois cents lignes et personne d'autre ne saura s'en servir.
Le périphérique caractère reste légitime pour du matériel vraiment spécifique, sans famille existante.
L'appariement par devicetree
Sur une plateforme embarquée, le noyau associe un nœud du devicetree à un pilote par la propriété compatible.
Côté devicetree :
&i2c1 {
temp0: temp-sensor@48 {
compatible = "acme,temp42";
reg = <0x48>;
interrupt-parent = <&gpio2>;
interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
vdd-supply = <®_3v3>;
};
};
Côté pilote :
static const struct of_device_id temp42_of_match[] = {
{ .compatible = "acme,temp42" },
{ }
};
MODULE_DEVICE_TABLE(of, temp42_of_match);
Oublier MODULE_DEVICE_TABLE est l'erreur silencieuse la plus fréquente : le pilote compile, se charge à la main avec insmod, mais n'est jamais chargé automatiquement, parce que l'alias qui permet à udev de faire le lien n'existe pas.
probe et remove, la symétrie qui compte
probe() est appelée quand un périphérique correspondant apparaît. Elle acquiert les ressources, initialise le matériel et enregistre le périphérique auprès de son sous-système.
La règle absolue est la symétrie : tout ce qui est acquis dans probe doit être libéré dans remove, dans l'ordre inverse. Et surtout, chaque chemin d'erreur de probe doit défaire ce qui a déjà été fait.
C'est précisément ce que les fonctions devm_ suppriment comme source de bogues :
static int temp42_probe(struct i2c_client *client)
{
struct temp42 *st;
st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
if (!st)
return -ENOMEM;
st->vdd = devm_regulator_get(&client->dev, "vdd");
if (IS_ERR(st->vdd))
return dev_err_probe(&client->dev, PTR_ERR(st->vdd),
"regulateur vdd indisponible\n");
return devm_iio_device_register(&client->dev, indio_dev);
}
Trois choses à retenir de ce fragment. Les allocations devm_ sont libérées automatiquement quand le périphérique disparaît, ce qui rend remove souvent inutile. dev_err_probe gère proprement le cas -EPROBE_DEFER, sans polluer les journaux quand une dépendance n'est pas encore prête. Et le retour direct sur erreur, sans goto, devient possible et lisible.
Le report de sondage, à comprendre une fois pour toutes
-EPROBE_DEFER n'est pas une erreur. C'est le noyau qui dit « ce périphérique dépend d'une ressource pas encore disponible, je réessaierai plus tard ». Cela arrive constamment sur une plateforme embarquée, où l'ordre de sondage n'est pas déterministe : votre capteur a besoin d'un régulateur dont le pilote n'est pas encore chargé.
Le piège consiste à traiter ce code comme un échec et à journaliser une erreur. Le démarrage se remplit alors de messages alarmants alors que tout finit par fonctionner. dev_err_probe existe exactement pour ça : elle journalise en niveau debug pour ce cas et en erreur pour les autres.
Les erreurs de contexte, celles qui font paniquer
C'est le domaine où le noyau ne pardonne pas.
Dormir en contexte atomique. Dans un gestionnaire d'interruption, sous un spinlock, ou dans une tasklet, toute fonction susceptible de dormir est interdite : msleep, mutex_lock, kmalloc(GFP_KERNEL), regmap_read sur un bus I2C. La règle pratique : si vous ne savez pas si une fonction peut dormir, elle le peut probablement.
Le bon type d'allocation. GFP_KERNEL peut dormir, GFP_ATOMIC non mais puise dans une réserve limitée. Utiliser GFP_ATOMIC partout par prudence épuise cette réserve et déstabilise le système sous charge.
Les interruptions en deux temps. Un capteur sur I2C ne peut pas être lu depuis un gestionnaire d'interruption, puisque la lecture I2C dort. Le schéma correct est devm_request_threaded_irq avec un gestionnaire rapide qui ne fait qu'acquitter, et un gestionnaire threadé qui lit.
Activez CONFIG_DEBUG_ATOMIC_SLEEP pendant le développement : il transforme ces fautes en messages explicites plutôt qu'en blocages aléatoires.
Ce qu'il faut exposer à l'espace utilisateur
Le noyau a des conventions, et les respecter fait la différence entre un pilote acceptable et un pilote que personne ne peut utiliser.
Une valeur par fichier en sysfs, dans l'unité normalisée du sous-système. Pas de format maison. Les ioctl seulement quand aucune interface existante ne convient. Et jamais de format binaire non documenté dans un fichier sysfs.
Si votre pilote a vocation à remonter un jour dans le noyau principal, ces conventions ne sont pas négociables, et il vaut mieux les suivre dès le début que de tout réécrire.
Références
- Kernel.org, Driver Model,
probe,remove, appariement - Kernel.org, Devres, gestion automatique des ressources, fonctions
devm_ - Kernel.org, Industrial I/O
- Kernel.org, Devicetree usage model,
compatibleetMODULE_DEVICE_TABLE - Kernel.org, Unreliable guide to locking, contextes atomiques et
GFP_
Pour aller plus loin
Ces réflexes se construisent en écrivant un pilote complet sur du matériel réel, en le faisant échouer volontairement dans probe, et en observant ce que le noyau reproche. C'est ce que couvrent nos formations Drivers Linux et Drivers USB Linux.