![]() |
Pilotes de périphériques pour le noyau Linux |
Le noyau Linux étant conçu pour fonctionner sur un grand nombre de plates-formes matérielles différentes, il a fallu rationaliser le fonctionnement des pilotes de périphériques. Cela a été fait au niveau de la communication entre le pilote et le matériel (device model) et entre le logiciel et le pilote (framework).
Physiquement, un périphérique est connecté au système au travers d’un bus (soit des bus sophistiqués permettant l’énumération des périphériques comme USB, PCI, soit des bus plus simples comme AXI…). Au niveau du noyau, cette relation est représentée par un pilote qui va gérer le bus (exemple : USB), un pilote qui va gérer le contrôleur de ce bus (exemple : contrôleur de bus USB) et un pilote qui va gérer un périphérique connecté à ce bus.
Au niveau de l’interface avec les applications, de nombreux périphériques différents offrent des fonctionnalités similaires. Plutôt que d’avoir une interface différente par périphérique avec l’espace utilisateur, des frameworks offrent une interface commune (exemple : port série, framebuffer…).
La suite de cette page va décrire de façon plus détaillée le modèle de périphérique (device model). La partie framework sera vue ultérieurement.
Le modèle de périphérique du noyau se base sur trois structures de données définies dans include/linux/device.h :
struct bus_type
qui représente un bus. Un bus est un canal entre le processeur et un ou plusieurs périphériques. Pour les besoins du modèle, tous les périphériques sont connectés par l’intermédiaire d’un bus même s’il s’agit d’un bus interne virtuel (appelé platform bus). Les bus peuvent être connectés les uns aux autres. Par exemple, un contrôleur USB (qui gère un bus USB) est souvent lui-même un périphérique PCI, connecté au bus PCI. Le modèle de périphérique représente les connexions entre les bus et les périphériques qu’ils contrôlent. La structure struct bus_type
, qui représente un bus, contient son nom, ses attributs par défaut, les méthodes de gestion du bus, les opérations de gestion de l’alimentation, et les données privées du pilote du bus.1
struct device_driver
qui représente un pilote capable de gérer certains périphériques sur un bus donné. Tous ces pilotes sont enregistrés par le bus afin que lorsqu’un périphérique est connecté au bus, ce dernier puisse en informer le pilote approprié.
struct device
qui représente un périphérique connecté à un bus. Chaque périphérique est représenté par une instance de cette structure.
Ces structures sont spécialisées (par héritage) en fonction du type de bus. Par exemple, en ce qui concerne le bus USB, struct device_driver
est spécialisée en struct usb_driver
.
À chaque type de bus (PCI, USB…) est associé un pilote de bus. Il assure plusieurs fonctions :
struct bus_type
qui représente ce busLe pilote du bus USB est situé dans drivers/usb/core. La structure struct bus_type
représentant le bus USB est définie dans drivers/usb/core/driver.c :
et elle est enregistrée auprès du noyau dans la fonction usb_init
dans drivers/usb/core/usb.c :
Ce pilote du bus USB va interagir avec les pilotes des contrôleurs dans drivers/usb/host et avec les pilotes des périphériques (partout ailleurs dans drivers/usb).
Pour voir l’interaction avec le bus, nous allons étudier un pilote de périphérique USB assez simple (drivers/usb/misc/usbled.c2) qui pilote des LED branchées sur le port USB.
La première chose que doit faire le pilote de périphérique est déclarer quels sont les périphériques matériels que le pilote sait gérer. Sur le bus USB, les périphériques sont principalement identifiés par deux nombres : l’identifiant du fabriquant (idVendor) et l’identifiant du produit (idProduct), tous les deux sur 16 bits et qui suffisent généralement à faire la relation entre le périphérique matériel et le pilote correspondant.
/* table of devices that work with this driver */
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x0fc5, 0x1223), .driver_info = DELCOM_VISUAL_SIGNAL_INDICATOR },
{ USB_DEVICE(0x1d34, 0x0004), .driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER },
{ USB_DEVICE(0x1d34, 0x000a), .driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER },
{ USB_DEVICE(0x1294, 0x1320), .driver_info = RISO_KAGAKU_LED },
{ },
};
MODULE_DEVICE_TABLE(usb, id_table);
La structure struct usb_device_id
est définie dans include/linux/mod_devicetable.h et permet d’indiquer sur quels champs de l’USB (idVendor
, idProduct
ou autre) la comparaison doit être effectuée. La macro USB_DEVICE
définie dans include/linux/usb.h permet de faire une comparaison sur idVendor
et idProduct
. Le champ driver_info
est un entier spécifique au pilote et qui peut lui servir à savoir facilement quel type de périphérique correspond. Ici, le pilote sait gérer 4 périphériques matériels différents.
La macro MODULE_DEVICE_TABLE
permet, à la compilation du module, d’extraire les informations de correspondance entre les périphériques et les pilotes pour que le module puisse être chargé automatiquement par udev
lorsqu’un périphérique supporté est détecté.
Lorsque le pilote est chargé (au démarrage du noyau s’il est compilé en dur ou lors de son chargement s’il est compilé sous forme de module) il doit s’enregistrer auprès du pilote du bus USB à l’aide de la fonction usb_register
. De même, lorsque le pilote est déchargé, il doit se désenregistrer à l’aide de la fonction usb_deregister
. Ces appels sont cachés par la macro :
qui génère le code suivant :
static int __init led_driver_init(void)
{
return usb_register(&led_driver);
}
static void __exit led_driver_exit(void)
{
usb_deregister(&led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
La structure led_driver
de type struct usb_driver
(voir sa définition dans include/linux/usb.h) est définie ainsi dans ce pilote :
static struct usb_driver led_driver = {
.name = "usbled",
.probe = led_probe,
.disconnect = led_disconnect,
.id_table = id_table,
};
Le champ name
contient le nom du pilote qui doit être unique parmi les pilotes enregistrés pour le bus USB. Le champ id_table
contient la table vue précédemment pour assurer la détection des périphériques utilisables avec ce pilote. Le champ probe
indique la fonction a appeler lorsqu’un périphérique correspondant à ce pilote est détecté sur le bus USB et le champ disconnect
la fonction a appeler lorsque le périphérique est débranché du bus.
Lorsqu’un périphérique est branché sur le bus, les opérations suivantes se produisent :
probe
de la structure struct usb_driver
pour le pilote correspondantprobe
reçoit en argument une ou plusieurs structures décrivant le périphérique. Elle doit :
Dans notre exemple, la fonction led_probe
crée des fichiers particuliers dans sysfs
afin de pouvoir piloter la LED.
Lorsque le périphérique est débranché, le même processus se répète cette fois-ci en appelant la fonction disconnect
du pilote de périphérique.
Tous les périphériques ne sont pas connectés à des bus qui permettent de faire de l’énumération (identifier quels périphériques y sont connectés). C’est souvent le cas des bus utilisés dans les systèmes embarqués (par exemple les bus I2C ou SPI) et dans les System-on-Chip (par exemple le bus AXI). Néanmoins, il est utile de conserver le même modèle de périphérique.
Dans ces cas, au lieu d’un système dynamique, la configuration de ces pilotes de périphérique doit être décrite de façon statique, soit directement dans le code source (sur la carte X, le périphérique Y est présent à l’adresse Z), soit, dans le cas de certaines architectures (PowerPC et ARM notamment), dans une structure de données particulière : le Device Tree.
Le bus platform est un bus virtuel dans le noyau Linux pour connecter tous les périphériques faisant partie du SoC (contrôleur ethernet, UART, contrôleur SPI…). Il fonctionne de la même façon que les autres bus du système à l’exception du fait que les périphériques ne sont pas identifiés dynamiquement mais statiquement.
Nous prendrons comme exemple le pilote drivers/leds/leds-gpio.c qui permet de piloter des LED connectés à des GPIO (General Purpose Inputs Outputs).
L’enregistrement en temps que pilote d’un périphérique sur le bus platform se fait ainsi :
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.remove = gpio_led_remove,
.driver = {
.name = "leds-gpio",
.of_match_table = of_gpio_leds_match,
},
};
module_platform_driver(gpio_led_driver);
Tout comme la macro module_usb_driver
, la macro module_platform_driver
génère le code suivant :
static int __init gpio_led_driver_init(void)
{
return platform_driver_register(&gpio_led_driver);
}
static void __exit gpio_led_driver_exit(void)
{
platform_driver_unregister(&gpio_led_driver);
}
module_init(gpio_led_driver_init);
module_exit(gpio_led_driver_exit);
Les méthodes platform_driver_register
et platform_driver_unregister
permettent respectivement d’enregistrer le pilote de périphérique auprès du pilote du bus platform et de supprimer le pilote.
Le pilote fournit au bus platform les informations permettant de faire le lien entre un périphérique et ce pilote par l’intermédiaire de la structure of_gpio_leds_match
.
Cette structure est instanciée ainsi :
Par définition, le bus platform ne dispose pas de mécanisme pour énumérer (détecter) les périphériques qui y sont connectés et donc de faire le lien entre un périphérique et son pilote. La liste des périphériques présents et leur configuration doit donc être fournie de façon statique au noyau. C’est également le cas pour d’autres bus ne permettant pas l’énumération (en tout cas pas de manière fiable) comme par exemple le bus I2C.
La méthode la plus courante, notamment pour les SoC à base de processeur ARM, est d’utiliser un Device Tree.
Un Device Tree est une structure de données arborescente servant à décrire une configuration matérielle. Elle provient d’Open Firmware, un standard de système de démarrage utilisé par de nombreuses machines à base de processeurs autres que x86 et notamment les machines Sun à base de processeurs SPARC, les machines à base de PowerPC (et notamment les anciennes machines Apple), etc. La spécification exacte de la syntaxe du Device Tree est disponible dans le document Power.org(TM) Standard for Embedded Power Architecture(TM) Platform Requirements (ePAPR) v1.1. Un résumé de la syntaxe, plus digeste et lisible est disponible sur le projet GitHub de DeviceTree.org.
De nombreux Device Tree sont fournis dans les sources du noyau. Par exemple, celui utilisé pour la carte de TP est dans arch/arm/boot/dts/socfpga_cyclone5_de1soc.dts
. Ce fichier dts
(Device Tree Source) inclut socfpga_cyclone5
qui inclut socfpga.dtsi
qui lui-même inclut skeleton.dtsi
.
Le Device Tree Source (fichier .dts
) est compilé par l’outil dtc
(Device Tree Compiler) pour donner un fichier binaire dtb
(Device Tree Blob) qui est chargé en même temps que le noyau par le bootloader (U-Boot dans notre cas).
Il est possible d’appeler l’outil dtc
manuellement (il fait partie des sources du noyau et est situé dans votre cas dans ~/se758/linux-socfpga/build/scripts/dtc/dtc
). Il est également possible de compiler un fichier dts
à partir du Makefile
à la racine des sources du noyau :
Voici un extrait simplifié du Device Tree utilisé pour la carte de TP :
/ {
#address-cells = <1>;
#size-cells = <1>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
device_type = "cpu";
reg = <0>;
next-level-cache = <&L2>;
};
cpu@1 {
compatible = "arm,cortex-a9";
device_type = "cpu";
reg = <1>;
next-level-cache = <&L2>;
};
};
intc: intc@fffed000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xfffed000 0x1000>,
<0xfffec100 0x100>;
};
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
device_type = "soc";
interrupt-parent = <&intc>;
ranges;
gmac0: ethernet@ff700000 {
compatible = "altr,socfpga-stmmac", "snps,dwmac-3.70a", "snps,dwmac";
reg = <0xff700000 0x2000>;
interrupts = <0 115 4>;
interrupt-names = "macirq";
mac-address = [00 00 00 00 00 00];/* Filled in by U-Boot */
clocks = <&emac0_clk>;
clock-names = "stmmaceth";
reset-names = "stmmaceth";
snps,multicast-filter-bins = <256>;
snps,perfect-filter-entries = <128>;
max-frame-size = <3800>;
tx-fifo-depth = <4096>;
rx-fifo-depth = <4096>;
status = "okay";
};
};
};
Dans le Device Tree, on trouve les informations de configuration (adresses, numéro d’interruption…) et de topologie. Dans l’exemple ci-dessus, le nœud ethernet@ff700000
(auquel est attribuée l’étiquette gmac0
pour s’y référer plus facilement ailleurs dans l’arbre) est un périphérique, connecté au bus soc
(le nœud parent). Les registres de ce périphérique (ici le contrôleur MAC ethernet), sont mappés en mémoire à partir de l’adresse 0xff700000
pour une longueur de 0x2000
(propriété reg
).
On trouve également des informations permettant de faire le lien entre un périphérique et son pilote. C’est la propriété compatible
dont la valeur est de la forme vendor,model
. Ici, le contrôleur ethernet est compatible avec les pilotes supportant altr,socfpga-stmmac
, snps,dwmac-3.70a
ou snps,dwmac
.
Ce sont les pilotes qui, en s’enregistrant auprès du bus platform, indiquent avec quels types de périphériques ils sont compatibles. Le bus platform fait alors le lien entre la valeur de la propriété compatible
dans le Device Tree et les valeurs fournies par les pilotes lors de leur enregistrement.
La documentation dans Documentation/devicetree/bindings/ décrit, pour chaque valeur du champ compatible
, la liste des propriétés obligatoires et facultatives dans le nœud correspondant et leur signification.
Pour les systèmes n’utilisant pas de Device Tree, les périphériques sont instanciés statiquement.
Exemple pour la carte SH-2007 (architecture SH), dans arch/sh/boards/board-sh2007.c :
static int __init sh2007_io_init(void)
{
/* ... */
platform_add_devices(sh2007_devices, ARRAY_SIZE(sh2007_devices));
return 0;
}
static struct platform_device *sh2007_devices[] __initdata = {
&smsc9118_0_device,
&smsc9118_1_device,
&cf_device,
};
static struct platform_device cf_device = {
.name = "pata_platform",
.id = 0,
.num_resources = ARRAY_SIZE(cf_resources),
.resource = cf_resources,
};
static struct resource cf_resources[] = {
[0] = {
.start = CF_BASE + CF_OFFSET,
.end = CF_BASE + CF_OFFSET + 0x0f,
.flags = IORESOURCE_MEM,
},
/* ... */
[2] = {
.start = evt2irq(0x2c0),
.end = evt2irq(0x2c0),
.flags = IORESOURCE_IRQ,
},
};
Lors du démarrage de la carte, la fonction sh2007_io_init
ajoute les périphériques listés dans le tableau sh2007_devices
. Chaque élément de ce tableau pointe vers une structure struct platform_device
qui indique le nom du périphérique (qui sera utilisé pour faire la correspondance avec le pilote, un peu à la manière du champ compatible
du Device Tree) et un lien vers la liste des ressources utilisées (struct resource
), ici, une zone mémoire IO (équivalent de la propriété req
du Device Tree) et une IRQ (équivalent de la propriété interrupts
du Device Tree).
Pour information, le pilote correspondant à pata_platform
est drivers/ata/pata_platform.c qui est décrit par la structure :
static struct platform_driver pata_platform_driver = {
.probe = pata_platform_probe,
.remove = ata_platform_remove_one,
.driver = {
.name = "pata_platform",
},
};
probe
Pour reprendre notre exemple précédent de pilote de LED, la structure of_gpio_leds_match
indique que le pilote est compatible avec gpio-leds
. Donc si dans le Device Tree, un périphérique a pour propriété compatible = "gpio-leds"
, le bus platform appellera la fonction gpio_led_probe
pour indiquer que le périphérique correspondant a été détecté.
Cette fonction peut récupérer les informations stockées dans le Device Tree ou initialisées statiquement via une struct resource
:
reg
du Device Tree) : platform_get_resource(pdev, IORESOURCE_MEM, 0)
interrupts
du Device Tree) : platform_get_irq(pdev, 0)
La carte de TP dispose 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. Dans un premier temps, nous n’utiliserons pas ce signal d’interruption.
Vous allez progressivement écrire un pilote de périphérique pour cet accéléromètre.
Commencez par lire rapidement la documentation du composant :
I2C est un bus, inventé par Philips Semiconductor (maintenant NXP), utilisé principalement pour relier des périphériques lents à un microprocesseur ou à un microcontrôleur. Il utilise seulement deux fils pour la communication : SDA
(pour les données), et SCL
pour l’horloge. Le débit standard du bus I2C est de 100 kbit/s (des versions plus rapides ont été développées par la suite).
Plusieurs entités peuvent être connectées sur le bus : un ou plusieurs maîtres et un ou plusieurs esclaves. Toutes les communications sont initiées par un maître qui fournit l’horloge de transmission sur le fil SCL
. Les esclaves ne font que répondre aux requêtes des maîtres et se basent sur l’horloge fournie.
Chaque esclave dispose d’une adresse, généralement sur 7 bits et souvent codée en dur dans le composant. Chaque transmission émise par un maître commence par l’adresse de l’esclave concerné, ce qui permet à ce dernier de savoir que la transmission le concerne.
Les spécifications complètes du bus I2C sont disponibles sur le site de NXP : http://www.nxp.com/documents/user_manual/UM10204.pdf.
Le bus I2C est modélisé dans le noyau Linux de la même manière que les autres bus vus précédemment.
Les pilotes des contrôleurs du bus I2C sont situés dans drivers/i2c/busses/ et les pilotes des périphériques connectés au bus sont répartis dans des sous-répertoires de drivers
en fonction de leur type.
Les pilotes des périphériques I2C sont représentés par la structure struct i2c_driver
définie dans include/linux/i2c.h. Cette structure hérite de struct device_driver
.
Les pilotes s’enregistrent auprès du pilote du bus I2C grâce à la fonction i2c_add_driver
et se désenregistrent grâce à la fonction i2c_del_driver
. Ces deux fonctions sont traditionnellement appelées dans la fonction d’initialisation du module (xxx_init
) et dans la fonction de nettoyage du module (xxx_exit
).
Comme vu précédemment pour module_usb_driver()
et module_platform_driver()
, s’il n’y a rien besoin de faire d’autre dans les fonctions d’initialisation et de nettoyage, la macro module_i2c_driver
permet de générer automatiquement ces fonctions.
Le noyau fournit un mécanisme permettant, dans certains cas, de détecter dynamiquement les périphériques branchés sur le bus. Nous n’utiliserons pas ce mécanisme par la suite mais la documentation décrit son fonctionnement pour les personnes curieuses.
Voici le squelette d’un pilote de périphérique I2C (plus de détails dans Documentation/i2c/writing-clients) :
/* includes ... */
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 },
{ }
};
MODULE_DEVICE_TABLE(i2c, foo_idtable);
#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 },
{}
};
MODULE_DEVICE_TABLE(of, foo_of_match);
#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,
};
module_i2c_driver(foo_driver);
/* MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION... */
Pour instancier le périphérique sur une plate-forme n’utilisant pas de Device Tree, la structure struct i2c_board_info
permet de déclarer la liste des périphériques connectés au bus I2C et leurs adresses. Cette structure est passée à la fonction i2c_register_board_info
lors du démarrage du noyau sur la carte.
static struct i2c_board_info myboard_i2c_devices[] __initdata = {
{
I2C_BOARD_INFO("foo", 0x42),
}
};
void board_init(void)
{
/* ... */
i2c_register_board_info(0, myboard_i2c_devices, ARRAY_SIZE(myboard_i2c_devices));
/* ... */
}
Le contrôleur de bus I2C utilisé sur la carte de TP est décrit dans le Device Tree de la façon suivante (arch/arm/boot/dts/socfpga.dtsi
, la propriété status
provenant de arch/arm/boot/dts/socfpga_cyclone5_de1soc.dts
) :
i2c0: i2c@ffc04000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "snps,designware-i2c";
reg = <0xffc04000 0x1000>;
interrupts = <0 158 4>;
clocks = <&l4_sp_clk>;
speed-mode = <0>;
status = "okay";
};
Les périphériques I2C connectés à ce contrôleur doivent être décrit comme des fils de ce nœud :
i2c0: i2c@ffc04000 {
status = "okay";
foo: foo@42 {
compatible = "vendor,foo";
reg = <0x42>;
};
};
La propriété reg
indique l’adresse du périphérique sur le bus I2C.
probe
et remove
La fonction probe
a pour prototype :
Elle est appelée lorsque le bus détecte le périphérique (ici la détection étant statique, elle est appelée au démarrage du pilote si le périphérique est listé dans le device tree ou dans la configuration statique de la carte).
Ses arguments sont :
client
: Un pointeur vers une structure struct i2c_client
qui représente le périphérique I2C. Cette structure hérite de la structure struct device
.id
: Un pointeur vers l’entrée du tableau foo_idtable
qui a matché avec le périphérique (si un device tree n’est pas utilisé).La fonction probe
est chargée d’initialiser le périphérique et de préparer l’utilisation du périphérique par l’espace utilisateur (le plus souvent en s’enregistrant auprès du framework approprié).
Si le pilote supporte plusieurs modèles de périphériques (donc si les tables foo_idtable
et foo_of_match
contiennent plus d’une entrée), il est possible de récupérer l’entrée précise qui a déclenché l’appel à la fonction probe
en utilisant le code suivant (inspiré de drivers/mfd/mc13xxx-i2c.c
if (client->dev.of_node) {
/* Le device tree a été utilisé */
const struct of_device_id *of_id =
of_match_device(foo_of_match, &client->dev);
variant = of_id->data; /* Champ data de of_device_id */
} else {
/* La structure foo_idtable a été utilisée */
variant = id->driver_data; /* Second champ de i2c_device_id */
}
La fonction remove
a pour prototype :
Elle est responsable du désenregistrement du périphérique du framework et de son extinction.
Un même pilote peut gérer plusieurs périphériques connectés au bus. Des données propres à la gestion de chacun de ces périphériques peuvent être stockés directement dans le champ data
de la structure struct i2c_client
pour pouvoir être utilisées facilement par la suite.
Deux fonctions sont définies à ces fins :
void i2c_set_clientdata(struct i2c_client *client, void *data);
void *i2c_get_clientdata(const struct i2c_client *client);
Deux fonctions de base sont disponibles pour envoyer et recevoir des données sur le bus I2C :
Cette fonction envoie count
octets depuis le tampon buf
au périphérique client
(l’adresse de ce périphérique est contenue dans cette structure). Elle retourne le nombre d’octets réellement envoyés.
Cette fonction reçoit count
octets depuis le périphérique client
et les stocke dans le tampon buf
. Elle retourne le nombre d’octets réellement reçus.
Dans le cas de l’I2C, il faut faire attention au protocole de communication avec le périphérique. Par exemple, pour l’accéléromètre, lire le contenu d’un registre implique l’envoi (écriture) du numéro du registre demandé (send
d’un octet représentant le numéro du registre demandé) puis d’une réception (lecture) du contenu (recv
d’un ou plusieurs octets). Voir la documentation de l’accéléromètre page 17 pour plus de détails.
Une fonction de plus haut niveau est disponible pour transmettre une série de messages, chacun de ces messages pouvant être une lecture ou une écriture.
Ses arguments sont :
adap
: la valeur de ce champ peut être obtenue depuis la structure client
: client->adapter
msg
: pointeur vers un tableau de struct i2c_msg
décrivant les messages à transmettrenum
: nombre de messages à transmettre (nombre d’éléments du tableau pointé par msg
)La structure struct i2c_msg
est définie dans include/uapi/linux/i2c.h :
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
Le champ addr
peut être rempli par client->addr
. La signification des différents drapeaux (champ flags
) est décrite en détails dans Documentation/i2c/i2c-protocol.
~/se758/linux-socfpga/arch/arm/boot/dts/socfpga_cyclone5_de1soc.dts
, ajoutez-le en tant que fils de &i2c0
(en fin de fichier)compatible
(exemple : ad,adxl345
). Elle doit être identique dans le Device Tree et dans votre pilote. Indiquez aussi l’adresse du périphérique sur le bus I2C : 0x53
~/se758/linux-socfpga
), tapez : make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm O=build socfpga_cyclone5_de1soc.dtb
cp ~/se758/linux-socfpga/build/arch/arm/boot/dts/socfpga_cyclone5_de1soc.dtb ~/se758/board
sudo mount -t vfat /dev/mmcblk0p1 /mnt
sudo cp ~/socfpga_cyclone5_de1soc.dtb /mnt/socfpga.dtb
sudo umount /mnt
probe
(affichez un message)probe
, lisez et affichez l’identifiant (DEVID) de l’accéléromètreprobe
, configurez les différents registres de l’accéléromètre. Nous utiliserons la configuration suivante (voir la documentation de l’accéléromètre) :
remove
, configurez l’accéléromètre avec une configuration appropriée :
Pour le moment, le pilote n’offre aucune interface pour les applications. Ce point (important) sera traité dans la suite du cours.
Texte issu de la traduction de la documentation de la structure struct bus_type
dans <linux/device.h>
.↩︎
Depuis la version 4.8, la gestion de ces LEDs USB a été déplacée vers le module HID (Human Interface Device) dans drivers/hid/hid-led.c.↩︎
© 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).