![]() |
M2 SETI B4 / MS SE SE758 |
L’objectif de ce TP est d’écrire la première partie d’un pilote pour un périphérique I2C :
et de le tester pour vérifier que tout fonctionne. Les prochains TP auront pour but de compléter ce pilote (communication avec l’espace utilisateur, interruptions, etc.).
Nous allons reprendre le répertoire créé lors des TP précédents.
$ export TPROOT=xxx # À adapter
$ cd $TPROOT
Pensez à adapter les commandes ci-dessus en fonction du nom donné au répertoire lors du premier TP.
Vous devez avoir gardé du TP précédent :
Vous trouverez ci-dessous le squelette d’un pilote pour un périphérique I2C.
Notez que le nom des fonctions et des structures commence par
foo
. Par convention, on remplacera foo
par le
nom du périphérique (par exemple, pour un pilote pour le périphérique
ADXL345, on nommera les fonctions adxl345_probe
,
adxl345_remove
, etc. et les structures
adxl345_idtable
, adxl345_of_match
, etc.).
Le contenu et le rôle de chaque partie de ce squelette a normalement été vu en cours.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/i2c.h>
static int foo_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* ... */
}
static int foo_remove(struct i2c_client *client)
{
/* ... */
}
/* La liste suivante permet l'association entre un périphérique et son
pilote dans le cas d'une initialisation statique sans utilisation de
device tree.
Chaque entrée contient une chaîne de caractère utilisée pour
faire l'association et un entier qui peut être utilisé par le
pilote pour effectuer des traitements différents en fonction
du périphérique physique détecté (cas d'un pilote pouvant gérer
différents modèles de périphérique).
*/
static struct i2c_device_id foo_idtable[] = {
{ "foo", 0 },
{ }
};
(i2c, foo_idtable);
MODULE_DEVICE_TABLE
#ifdef CONFIG_OF
/* Si le support des device trees est disponible, la liste suivante
permet de faire l'association à l'aide du device tree.
Chaque entrée contient une structure de type of_device_id. Le champ
compatible est une chaîne qui est utilisée pour faire l'association
avec les champs compatible dans le device tree. Le champ data est
un pointeur void* qui peut être utilisé par le pilote pour
effectuer des traitements différents en fonction du périphérique
physique détecté.
*/
static const struct of_device_id foo_of_match[] = {
{ .compatible = "vendor,foo",
.data = NULL },
{}
};
(of, foo_of_match);
MODULE_DEVICE_TABLE#endif
static struct i2c_driver foo_driver = {
.driver = {
/* Le champ name doit correspondre au nom du module
et ne doit pas contenir d'espace */
.name = "foo",
.of_match_table = of_match_ptr(foo_of_match),
},
.id_table = foo_idtable,
.probe = foo_probe,
.remove = foo_remove,
};
(foo_driver);
module_i2c_driver
("GPL");
MODULE_LICENSE("Foo driver");
MODULE_DESCRIPTION("Me"); MODULE_AUTHOR
Pour rappel (voir le TP sur l’écriture du premier module), voici le
fichier Makefile
nécessaire pour compiler le module (en
supposant que le fichier source est nommé foo.c
) :
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m := foo.o
else
# normal makefile
KDIR ?= /lib/modules/`uname -r`/build
default:
$(MAKE) -C $(KDIR) M=$$PWD
endif
Attention ! La ligne $(MAKE) -C...
qui
indique la commande à exécuter pour construire la cible
default
doit être indentée à l’aide d’une tabulation et non
d’un ou plusieurs espaces.
La carte de TP que nous aurions du utiliser est équipée d’un
accéléromètre 3 axes (ADXL345 d’Analog Devices) connecté au
contrôleur I2C0
via un bus I2C. L’accéléromètre
dispose également d’une sortie d’interruption, reliée directement à une
des entrées du FPGA.
À défaut de pouvoir réaliser en présentiel ce TP, la version spéciale de QEMU que vous avez récupérée lors du TP précédent contient un modèle (une simulation) de cet accéléromètre.
Ce modèle se comporte de façon très proche de l’accéléromètre physique, à quelques détails près :
BW_RATE
est ignoré)DATA_FORMAT
est ignoré (les données sont toujours
représentées sur 16 bits en complément à 2)INT_MAP
est ignoré)Voici comment sont générées les données. À chaque mesure, un compteur
c
(initialisé à 0) est incrémenté. Les valeurs produites
sur les différents axes sont :
c % 2 == 0
, X = c * 4
sinon
X = -c * 4
c % 2 == 0
, Y = c * 4 + 1
sinon
Y = -(c * 4 + 1)
c % 2 == 0
, Z = c * 4 + 2
sinon
Z = -(c * 4 + 2)
Les premiers échantillons sont donc :
(0,1,2), (-4,-5,-6), (8,9,10)
…
Cette information vous permettra de vérifier, dans la suite des TP, que vous récupérez bien toutes les données échantillonnées sans en perdre.
Vous allez progressivement écrire un pilote de périphérique pour cet accéléromètre simulé.
Commencez par regarder rapidement la documentation du composant réel :
Ce périphérique ADXL345 est déclaré dans l’arbre des périphériques que vous avez récupéré lors du TP précédent.
En voici l’extrait intéressant :
&v2m_i2c_dvi {
adxl345: adxl345@53 {
compatible = "qemu,adxl345";
reg = <0x53>;
interrupt-parent = <&gic>;
interrupts = <0 50 4>;
};
};
Ne considérez pas pour le moment les lignes commençant par
interrupt
.
&v2m_i2c_dvi
pointe vers le contrôleur I2C auquel
est virtuellement connecté l’accéléromètre. L’accéléromètre est placé à
l’adresse 0x53
sur ce bus dans QEMU.
Créez un répertoire de travail pour votre premier module :
$ cd $TPROOT
$ mkdir pilote_i2c
$ cd pilote_i2c
Copiez le squelette ci-dessus (en le nommant
adxl345.c
) ainsi que le Makefile
Adaptez le squelette fourni pour :
foo
par le
nom du périphérique dans le nom des fonctions et des structures, mettez
des valeurs correctes dans les macros MODULE_xxx
…)compatible
)Adaptez également le Makefile fourni pour que le module puisse compiler.
Compilez votre module
$ make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm KDIR=../linux-5.10.19/build/
Votre module est maintenant dans le fichier
adxl345.ko
.
Démarrez QEMU :
$ cd ..
$ ./qemu-system-arm -nographic -machine vexpress-a9 -kernel linux-5.10.19/build/arch/arm/boot/zImage \
-initrd rootfs.cpio.gz \
-dtb linux-5.10.19/build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -device virtio-9p-device,fsdev=mnt,mount_tag=mnt -fsdev local,path=pilote_i2c,security_model=mapped,id=mnt
Après vous être loggué en tant que root
, montez le
partage avec la machine hôte en tapant, dans la console émulée par QEMU
:
$ mount -t 9p -o trans=virtio mnt /mnt -oversion=9p2000.L,msize=10240
Si cette commande s’exécute correctement, vous aurez alors accès,
dans le répertoire /mnt
de la machine émulée par QEMU, au
contenu du répertoire pilote_i2c
de votre machine (et donc
au module que vous venez de compiler).
Chargez votre module (depuis QEMU) :
$ insmod /mnt/adxl345.ko
Vérifiez que votre module a le comportement attendu (affichage de votre message de découverte du périphérique)
Déchargez votre module (rmmod adxl345
) et vérifiez
qu’il a bien le comportement attendu (affichage de votre message de
retrait du périphérique)
Vous pouvez arrêter QEMU (ctrl-a c
puis
quit
)
Pour le moment votre pilote ne fait pas grand chose. Dans cette
deuxième étape nous allons essayer de lire le registre
DEVID
de l’accéléromètre. Ce registre contient une valeur
fixe (0xe5
). Si nous récupérons cette valeur, cela montrera
que l’on est capable de faire des échanges avec ce périphérique en
utilisant l’API I2C du noyau.
Modifiez la fonction probe
de votre module afin de
récupérer la valeur de ce registre depuis le périphérique physique, et
affichez la.
Indice : vous aurez besoin de faire une écriture suivie d’une lecture
I2C. Les fonctions d’écriture (i2c_master_send
) et de
lecture (i2c_master_recv
) ont été détaillées en cours.
Regardez également le schéma en bas de la page 18/40 (19 dans le PDF) de
la datasheet. Si vous ne voyez toujours pas comment faire, demandez à
votre encadrant.
Pour recompiler et recharger votre module, référez-vous à l’étape précédente.
Nous allons maintenant modifier notre pilote pour qu’il configure correctement le périphérique physique.
Dans la fonction probe
, configurez les différents
registres de l’accéléromètre. Nous utiliserons la configuration
suivante, même si certaines valeurs ne sont pas implémentées dans la
simulation (voir la documentation de l’accéléromètre) :
BW_RATE
)INT_ENABLE
)DATA_FORMAT
)FIFO_CTL
)POWER_CTL
)Dans la fonction remove
, éteignez l’accéléromètre :
POWER_CTL
)© Copyright 2020 Guillaume Duc. Le contenu de cette page est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International (à l'exception des exemples de code tirés du noyau Linux et qui sont distribués sous leurs licences d'origine).