Article HowTo pour développer des pilotes de périphériques Linux embarqués en tant que modules de noyau. Tout le contenu de cet article peut être exécuté sur Raspberry Pi.
https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_02 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_03 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_04
La dernière fois, j'ai créé un pilote de périphérique qui contrôle les LED / boutons à l'aide des fonctions de contrôle GPIO. Cette fois, nous allons créer un pilote de périphérique pour le périphérique connecté par I2C. Ce n'est pas le pilote de périphérique d'I2C lui-même. Semblable au GPIO précédent, le périphérique I2C lui-même est préparé par les fabricants de SoC. Et le pilote de périphérique peut être appelé par la fonction standard déclarée dans <linux / i2c.h>
. Par conséquent, vous devez l'utiliser de manière positive. Augmente la portabilité, la polyvalence et la fiabilité.
La carte cible est Raspberry Pi 2. Le dispositif I2C cible est décrit comme un capteur d'accélération (LIS3DH). Cependant, cela n'est pas particulièrement important car le but n'est pas de contrôler l'appareil lui-même. J'utilise un capteur d'accélération, mais cette fois je ne reçois que l'identifiant de l'appareil. C'est parce que je veux juste vérifier la communication. pas de signification particulière. S'il y a d'autres informations faciles à vérifier, c'est très bien. Veuillez corriger uniquement les informations suivantes à l'avance.
--Connectez I2C_1 de Raspeye et SCL, SDA de LIS3DH
Veuillez activer I2C avec raspi-config
. Après cela, vérifiez que la communication est possible avec la commande suivante. Vous pouvez vérifier les informations de tous les appareils connectés à I2C_1 avec ʻi2cdetect. ʻJ'obtiens l'ID de périphérique de LIS3DH avec i2cget
. Si 0x33 est émis, c'est OK. Sinon, quelque chose ne va pas et vous devriez vérifier la connexion, etc.
i2cdetect -y 1
i2cget -y 1 0x18 0x0f b
0x33
Comme GPIO, ce serait bien s'il pouvait être facilement contrôlé avec un seul appel de fonction, mais I2C est un peu compliqué. Certaines connaissances préalables sont requises.
Le contrôle du dispositif I2C est réalisé par des modules divisés en plusieurs couches. Commençons par le matériel. Tout d'abord, il y a I2C lui-même (bus I2C). Dans le cas du Raspberry Pi, il y a trois I2C au total, I2C_0, I2C_1 et I2C_2, respectivement, qui sont des bus I2C séparés. Des périphériques I2C sont connectés à chaque bus. Par exemple, si vous souhaitez connecter un capteur d'accélération (LIS3DH, adresse = 0x18) au bus de I2C_1, ce sera un client I2C.
Regardons le côté logiciel par le bas. Tout d'abord, il y a l'adaptateur I2C qui contrôle le bus I2C. Le contrôle de l'I2C lui-même est un processus dépendant du SoC. Par conséquent, il existe un code source distinct pour chaque SoC. Dans le cas de la tarte à la râpe, ce sera i2c-bcm2835.c. Il existe également i2c-algo, qui décrit les algorithmes utilisés lors de la communication. i2c-core.c est le processus principal et fournit des fonctions générales telles que ʻi2c_smbus_read_byte () . Les pilotes clients I2C contrôlent chaque périphérique I2C en utilisant son ʻi2c_smbus_read_byte () ʻetc. Il existe également i2c-dev.c pour préparer un fichier de périphérique i2c générique (
/ dev / i2c-1`). Ce sont tous du code côté noyau.
couche | Nom | La description | |
---|---|---|---|
SW | ↑ | I2C Client Drivers | Pilote de périphérique pour chaque périphérique I2C, i2c-dev |
| | i2c-core | Traitement principal de la communication I2C | |
| | i2c-algo-XXX | Algorithme de communication | |
↓ | I2C Adapters | Où contrôler l'I2C lui-même. Modification des registres et traitement dépendant de la puce | |
HW | - | I2C Bus | Par exemple, I2C_0、I2C_1 |
- | I2C Client | Chaque appareil I2C(Par exemple, capteur d'accélération(LIS3DH)) |
Cette fois, nous allons créer le pilote de périphérique pour le pilote client I2C dans le tableau ci-dessus, en particulier le capteur d'accélération connecté I2C (LIS3DH). Vous pourriez donc penser: "Vous appelez simplement la fonction i2c-core, n'est-ce pas?", Mais ce n'est pas si simple. Nous verrons ce qui se passera plus tard, mais pour l'instant, comprenez simplement les relations dans le tableau ci-dessus.
Le code de pilote de périphérique le plus basique pour les périphériques I2C est ci-dessous. Il y a diverses choses que je ne connais pas. Commençons par le bas du code. mydevice_init ()
et mydevice_exit ()
sont les processus lorsque ce pilote de périphérique est chargé / déchargé. Dans celui-ci, nous appelons ʻi2c_add_driver () et ʻi2c_del_driver ()
. D'une manière ou d'une autre, une table est passée en argument.
I2C est une interface qui se connecte avec "bus". Par conséquent, ce n'est pas parce que ce pilote de périphérique est chargé que le périphérique I2C cible (capteur d'accélération dans ce cas) est toujours connecté. C'est difficile à imaginer avec I2C, mais je pense que c'est facile à comprendre si vous imaginez USB. Par conséquent, un traitement est nécessaire lorsqu'un appareil est connecté / déconnecté du bus I2C. ʻI2c_add_driver () et ʻi2c_del_driver ()
enregistrent / annulent le traitement à ce moment-là. Le type de table à enregistrer est struct i2c_driver
. Enregistrez les informations de périphérique I2C prises en charge par ce pilote de périphérique dans .id_table
. Le contenu enregistré sera la struct i2c_device_id mydevice_i2c_idtable []
déclarée au début. Le premier membre est le nom de l'appareil correspondant. Le noyau recherche un périphérique correspondant avec ce nom (char name []) et est un membre très important (pas un numéro unique!). Le deuxième membre sera les données privées utilisées dans ce pilote de périphérique. Tout va bien, mais il semble généralement inclure un numéro d'identification. Enregistrez les fonctions que le noyau appelle lors de la connexion / déconnexion des périphériques I2C dans .probe
et .remove
. Enregistrez le nom de ce pilote de périphérique dans .driver
.
Dans mydevice_i2c_probe ()
, la fonction ʻi2c_smbus_read_byte_data () est appelée pour communiquer avec le périphérique I2C reconnu. Dans ce cas, il est supposé que LIS3DH est connecté, donc la valeur à l'adresse 0x0F (ID de périphérique) est en cours de lecture.
Le premier argument passé à i2c_smbus_read_byte_data ()est
struct i2c_client, qui stocke les informations du bus I2C (adaptateur I2C à utiliser) et l'adresse de l'esclave. Ces informations sont disponibles lorsque cette fonction
mydevice_i2c_probe ()` est appelée.
myDeviceDriver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>
/***Informations sur cet appareil***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice" /* /proc/Nom de l'appareil affiché sur les appareils, etc.*/
/*Enregistrer une table qui identifie les périphériques gérés par ce pilote de périphérique*/
/*L'important est le champ du prénom. Cela détermine le nom de l'appareil. Le dos est des données qui peuvent être utilisées librement avec ce pilote. Insérer des pointeurs et des numéros d'identification*/
static struct i2c_device_id mydevice_i2c_idtable[] = {
{"MyI2CDevice", 0},
{ }
};
MODULE_DEVICE_TABLE(i2c, mydevice_i2c_idtable);
static int mydevice_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("i2c_lcd_probe\n");
printk("id.name = %s, id.driver_data = %d", id->name, id->driver_data);
printk("slave address = 0x%02X\n", client->addr);
/*Habituellement ici pour vérifier si l'appareil est pris en charge par ce Devadora*/
int version;
version = i2c_smbus_read_byte_data(client, 0x0f);
printk("id = 0x%02X\n", version);
return 0;
}
static int mydevice_i2c_remove(struct i2c_client *client)
{
printk("mydevice_i2c_remove\n");
return 0;
}
static struct i2c_driver mydevice_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.id_table = mydevice_i2c_idtable, //Périphériques I2C pris en charge par ce pilote de périphérique
.probe = mydevice_i2c_probe, //Processus appelé lorsque le périphérique I2C cible est reconnu
.remove = mydevice_i2c_remove, //Processus appelé lorsque le périphérique I2C cible est supprimé
};
/*Route(insmod)Fonctions parfois appelées*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
/*Enregistrez ce pilote de périphérique en tant que pilote de périphérique utilisant le bus I2C*/
i2c_add_driver(&mydevice_driver);
return 0;
}
/*Décharger(rmmod)Fonctions parfois appelées*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
i2c_del_driver(&mydevice_driver);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Dans la plupart des pilotes de périphériques pour périphériques I2C, la seule chose que vous faites lors du chargement / déchargement est l'enregistrement de struct i2c_driver
. Par conséquent, il peut être simplifié comme suit.
module_i2c_driver(mydevice_driver);
Construisez, chargez et regardez le journal comme indiqué ci-dessous.
make && sudo insmod MyDeviceModule.ko
dmesg
[ 8589.545480] mydevice_init
En regardant le journal, mydevice_init ()
a juste été appelé, pas mydevice_i2c_probe ()
. C'est parce que, comme expliqué précédemment, mydevice_i2c_probe ()
est appelé lorsque le périphérique est reconnu, mais aucun périphérique I2C n'est connecté pour le moment. Si c'est USB, il sera probablement reconnu automatiquement, mais s'il s'agit d'I2C, vous devez le dire manuellement. Faites que le noyau reconnaisse le nouveau périphérique en écrivant une valeur dans / sys / bus / i2c / devices / i2c-1 / new_device
. Cette fois, nous voulons supposer que le périphérique pour ce pilote de périphérique est connecté, donc supposons que le périphérique nommé "MyI2CDevice" est connecté avec l'adresse esclave = 0x18. Les informations contenues dans cet ensemble sont la substance du «Client I2C» indiqué dans le tableau ci-dessus.
sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
dmesg
[ 9179.410883] mydevice_i2c_probe
[ 9179.410902] id.name = MyI2CDevice, id.driver_data = 0
[ 9179.410912] slave address = 0x18
[ 9179.411301] id = 0x33
[ 9179.412045] i2c i2c-1: new_device: Instantiated device MyI2CDevice at 0x18
Ensuite, comme indiqué dans le journal ci-dessus, mydevice_i2c_probe ()
est appelé. Vous pouvez également voir que le paramètre client
passé à ce moment-là contient les informations matérialisées du client I2C. Ensuite, en utilisant ces informations (cLient), vous pouvez voir que vous pouvez communiquer et obtenir l'ID correct.
Cette fois, j'ai décidé de "gérer un périphérique nommé" MyI2CDevice "". Par conséquent, j'ai défini le nom "MyI2CDevice" dans la table struct i2c_device_id
et l'ai enregistré dans le noyau. Ensuite, j'ai manuellement fait reconnaître au noyau le périphérique "MyI2CDevice", donc le pilote de périphérique mydevice_i2c_probe ()
a été appelé. Comme le noyau ne juge que par le nom, il peut essayer de contrôler le mauvais périphérique si le nom chevauche un autre périphérique. Veuillez noter que probe dans mydevice_i2c_probe ()
, mais ce que vous devez faire avec cette fonction est de vérifier si ce pilote de périphérique gère ce périphérique. S'il s'agit d'un appareil compatible, il renvoie 0, sinon il renvoie -1. Pour le confirmer, l'adresse de l'esclave est généralement confirmée ou la communication est effectuée pour obtenir l'ID de l'appareil et les informations de version. Cette fois, il est fixe et renvoie toujours 0.
Utilisez la commande suivante pour supprimer le périphérique.
sudo bash -c 'echo 0x18 > /sys/bus/i2c/devices/i2c-1/delete_device'
Dans l'exemple précédent, le périphérique a été reconnu manuellement à partir du script shell. Probablement pas recommandé, mais vous pouvez faire la même chose à partir de votre code comme suit:
myDeviceDriver.Partie de c
/*Route(insmod)Fonctions parfois appelées*/
static struct i2c_client *i2c_clie = NULL;
static int mydevice_init(void)
{
printk("mydevice_init\n");
/*Enregistrez ce pilote de périphérique en tant que pilote de périphérique utilisant le bus I2C*/
i2c_add_driver(&mydevice_driver);
/*Créer dynamiquement une entité d'appareil(https://www.kernel.org/doc/Documentation/i2c/instantiating-devices) */
/*Connecté à I2C1,"MyI2CDevice"Créez un appareil avec une adresse esclave de 0x18*/
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(1);
struct i2c_board_info i2c_board_info = {
I2C_BOARD_INFO("MyI2CDevice", 0x18)
};
i2c_clie = i2c_new_device(i2c_adap, &i2c_board_info);
i2c_put_adapter(i2c_adap);
return 0;
}
/*Décharger(rmmod)Fonctions parfois appelées*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
i2c_del_driver(&mydevice__driver);
if(i2c_clie) i2c_unregister_device(i2c_clie);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Cette méthode a également été décrite dans la documentation Linux (instanciation-devices), donc l'implémentation elle-même est correcte, mais l'emplacement d'implémentation n'est pas bon. Le type de périphérique connecté dépend des informations de la carte, il doit donc être écrit autour de bcm2835_init (void)
dans board_bcm2835.c. De plus, il semble préférable d'utiliser ʻi2c_register_board_info () `.
De plus, il semble y avoir un moyen de le mettre dans l'arborescence des périphériques (.dts), mais cela n'est pas encore bien compris.
Jusqu'à présent, la reconnaissance de l'appareil I2C et la communication initiale ont été effectuées. Ensuite, créez une interface avec l'utilisateur ou le programme dans l'espace utilisateur. Fondamentalement, comme d'habitude, il sera échangé à l'aide des fichiers de l'appareil. Tout d'abord, créez une interface facile à utiliser à l'aide de sysfs. J'ai brièvement abordé sysfs (7ème fois) à [https://qiita.com/take-iwiw/items/548444999d2dfdc06f46#%E3%83%8E%E3%83%BC%E3%83%882-sysfs]. .. À ce moment, il était traité comme un paramètre de module. Cette fois, il doit être traité comme un paramètre de l'équipement I2C.
Considérez une interface simple qui n'acquiert que l'ID de l'appareil (informations de version). La procédure est très simple: (1) définir la fonction du gestionnaire lors de sa lecture, (2) définir les attributs du fichier de périphérique à créer et (3) l'enregistrer dans le noyau. Pour ②, utilisez une macro d'assistance appelée DEVICE_ATTR
. Pour ③, appelez la fonction device_create_file ()
au moment de la sonde.
Voici le code qui crée un fichier appelé "version" et appelle la fonction get_version ()
lors de la lecture. Le premier argument de DEVICE_ATTR
est le nom du fichier à créer. Le deuxième argument est l'autorisation d'accès. Le troisième argument est la fonction de gestionnaire lors de la lecture et le quatrième argument est la fonction de gestionnaire lors de l'écriture. Puisque l'opération d'écriture n'est pas effectuée cette fois, NULL est spécifié. Avec get_version ()
défini dans la fonction de gestionnaire au moment de la lecture, comme auparavant, les informations d'ID de périphérique sont lues, emballées dans buf et renvoyées. Pour utiliser la fonction I2C, nous avons besoin des informations de struct i2c_client
, mais struct device
est passé à cette fonction. Cependant, comme struct i2c_client
est inclus dans ceci, il peut être facilement obtenu. Utilisez device_create_file ()
au moment de la sonde pour vous enregistrer. Le premier argument est également struct device
. Le deuxième argument est un ensemble d'attributs pour le fichier à enregistrer. Ceci est créé précédemment par la macro DEVICE_ATTR
. Passez le nom défini dans le premier argument de DEVICE_ATTR
avec le préfixe" dev_attr_ ".
myDeviceDriver.Partie de c
static ssize_t get_version(struct device *dev, struct device_attribute *dev_attr, char * buf)
{
printk("get_version\n");
struct i2c_client * client = to_i2c_client(dev);
int version;
version = i2c_smbus_read_byte_data(client, 0x0f);
return sprintf(buf, "id = 0x%02X\n", version);
}
static DEVICE_ATTR(version, S_IRUGO, get_version, NULL);
static int mydevice_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("mydevice_i2c_probe\n");
printk("id.name = %s, id.driver_data = %d", id->name, (int)(id->driver_data));
printk("slave address = 0x%02X\n", client->addr);
/*Habituellement ici pour vérifier si l'appareil est pris en charge par ce Devadora*/
/*Créez un fichier sysfs pour lire et écrire les attributs de ce pilote de périphérique*/
/* (/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0018/version)faire*/
device_create_file(&client->dev, &dev_attr_version);
return 0;
}
static int mydevice_i2c_remove(struct i2c_client *client)
{
printk("mydevice_i2c_remove\n");
device_remove_file(&client->dev, &dev_attr_version);
return 0;
}
Après cela, créez, chargez et reconnaissez l'appareil comme suit. Ensuite, le fichier / sys / devices / platform / soc / 3f804000.i2c / i2c-1 / 1-0018 / version
est créé, et si vous le lisez avec cat, vous pouvez voir que l'id a été obtenu.
make && sudo insmod MyDeviceModule.ko
sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
cat /sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0018/version
id = 0x33
Bien que sysfs soit simple et pratique, vous pouvez toujours utiliser l'habituel open / close / read / write / ioctl (+ select / poll / seek, etc.). Pour ce faire, vous devez créer le fichier de périphérique de la même manière qu'auparavant. Jusqu'à présent, le fichier de périphérique était créé au moment du chargement, mais cette fois, c'est le moment de la sonde. De plus, struct i2c_client
est requis pour utiliser la fonction de contrôle I2C. Il est nécessaire de garder quelque part la struct i2c_client
reçue au moment de la sonde et de s'y référer lors de la lecture ou de l'écriture. Cela peut être facilement fait en le tenant dans une variable statique, mais je vais le faire un peu mieux. Pour y parvenir, nous utilisons une macro d'assistance appelée container_of ()
.
container_of
container_of ()
est une macro d'aide définie ci-dessous qui est exécutée au moment de la compilation.
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
#endif
Entrez le nom de la structure dans type
et le nom du membre dans la structure dans membre
. Ensuite, placez le pointeur réel de ce membre dans ptr
. Ensuite, il renvoie l'adresse de début de la structure qui stocke le ptr
.
i2c_set_clientdata
ʻI2c_set_clientdata () est une fonction définie ci-dessous. Vous pouvez sauvegarder librement les informations associées à
struct i2c_client. Ces informations peuvent être n'importe quoi, mais elles contiennent généralement un pointeur vers la propre structure du pilote de périphérique qui a été allouée pendant la sonde. Utilisez ʻi2c_get_clientdata ()
pour le récupérer.
void i2c_set_clientdata(struct i2c_client *dev, void *data)
Ci-dessous, le code qui permet de se référer à la struct i2c_client
reçue au moment de la sonde au moment de l'ouverture / fermeture / lecture / écriture. Tout d'abord, je vais mettre le code rapidement.
myDeviceDriver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>
/***Informations sur cet appareil***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice" /* /proc/Nom de l'appareil affiché sur les appareils, etc.*/
static const unsigned int MINOR_BASE = 0; /*Numéro de départ et nombre de nombres mineurs utilisés dans ce pilote de périphérique(=Nombre d'appareils) */
static const unsigned int MINOR_NUM = 1; /*Le nombre mineur est 0*/
/***Informations de gestion des périphériques I2C***/
/*Périphériques I2C gérés par ce pilote de périphérique*/
enum mydevice_i2c_model {
MYDEVICE_MODEL_A = 0,
MYDEVICE_MODEL_NUM,
};
/*Enregistrer une table qui identifie les périphériques gérés par ce pilote de périphérique*/
/*L'important est le champ du prénom. Cela détermine le nom de l'appareil. Le dos est des données qui peuvent être utilisées librement avec ce pilote. Insérer des pointeurs et des numéros d'identification*/
static struct i2c_device_id mydevice_i2c_idtable[] = {
{"MyI2CDevice", MYDEVICE_MODEL_A},
{ }
};
MODULE_DEVICE_TABLE(i2c, mydevice_i2c_idtable);
/*Chaque appareil I2C(client)Informations associées à. Réglé au moment de la sonde i2c_set_Conserver les données client*/
struct mydevice_device_info {
struct cdev cdev; /*appareil I2C sondé(client)Requis pour associer cdev avec. conteneur ouvert_Recherche par de*/
unsigned int mydevice_major; /*Numéro majeur de ce pilote de périphérique(Décider dynamiquement) */
struct class *mydevice_class; /*Objet de classe de pilote de périphérique*/
struct i2c_client *client;
/*Ajoutez d'autres si nécessaire. mutex etc.*/
};
/*Fonction appelée à l'ouverture*/
static int mydevice_open(struct inode *inode, struct file *file)
{
printk("mydevice_open");
/*Cdev avec ce ouvert(inode->i_cdev)Avec mydevice_device_Trouver des informations*/
struct mydevice_device_info *dev_info;
dev_info = container_of(inode->i_cdev, struct mydevice_device_info, cdev);
if (dev_info == NULL || dev_info->client == NULL) {
printk(KERN_ERR "container_of\n");
return -EFAULT;
}
file->private_data = dev_info;
printk("i2c address = %02X\n",dev_info->client->addr);
return 0;
}
/*Fonction appelée à la fermeture*/
static int mydevice_close(struct inode *inode, struct file *file)
{
printk("mydevice_close");
return 0;
}
/*Fonction appelée lors de la lecture*/
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_read");
struct mydevice_device_info *dev_info = filp->private_data;
struct i2c_client * client = dev_info->client;
int version;
version = i2c_smbus_read_byte_data(client, 0x0f);
return sprintf(buf, "id = 0x%02X\n", version);
}
/*Fonction appelée au moment de l'écriture*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_write");
return count;
}
/*Table des gestionnaires pour divers appels système*/
struct file_operations s_mydevice_fops = {
.open = mydevice_open,
.release = mydevice_close,
.read = mydevice_read,
.write = mydevice_write,
};
static int mydevice_i2c_create_cdev(struct mydevice_device_info *dev_info)
{
int alloc_ret = 0;
int cdev_err = 0;
dev_t dev;
/*Sécurisez un numéro majeur gratuit*/
alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
if (alloc_ret != 0) {
printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret);
return -1;
}
/*Obtenu dev( =Numéro majeur+Numéro mineur)Obtenez le numéro de mesure et conservez-le*/
dev_info->mydevice_major = MAJOR(dev);
dev = MKDEV(dev_info->mydevice_major, MINOR_BASE); /*Inutile? */
/*Initialisation de la structure cdev et enregistrement de la table du gestionnaire d'appels système*/
cdev_init(&dev_info->cdev, &s_mydevice_fops);
dev_info->cdev.owner = THIS_MODULE;
/*Ce pilote de périphérique(cdev)Vers le noyau*/
cdev_err = cdev_add(&dev_info->cdev, dev, MINOR_NUM);
if (cdev_err != 0) {
printk(KERN_ERR "cdev_add = %d\n", alloc_ret);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
/*Enregistrer la classe de cet appareil(/sys/class/mydevice/faire) */
dev_info->mydevice_class = class_create(THIS_MODULE, "mydevice");
if (IS_ERR(dev_info->mydevice_class)) {
printk(KERN_ERR "class_create\n");
cdev_del(&dev_info->cdev);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
/* /sys/class/mydevice/mydevice*faire*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_create(dev_info->mydevice_class, NULL, MKDEV(dev_info->mydevice_major, minor), NULL, "mydevice%d", minor);
}
return 0;
}
static void mydevice_i2c_delete_cdev(struct mydevice_device_info *dev_info)
{
dev_t dev = MKDEV(dev_info->mydevice_major, MINOR_BASE);
/* /sys/class/mydevice/mydevice*Supprimer*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_destroy(dev_info->mydevice_class, MKDEV(dev_info->mydevice_major, minor));
}
/*Supprimer l'enregistrement de cours pour cet appareil(/sys/class/mydevice/Supprimer) */
class_destroy(dev_info->mydevice_class);
/*Ce pilote de périphérique(cdev)Depuis le noyau*/
cdev_del(&dev_info->cdev);
/*Supprimez l'enregistrement de numéro majeur utilisé par ce pilote de périphérique*/
unregister_chrdev_region(dev, MINOR_NUM);
}
static int mydevice_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("mydevice_i2c_probe\n");
printk("id.name = %s, id.driver_data = %d", id->name, (int)(id->driver_data));
printk("slave address = 0x%02X\n", client->addr);
/*Habituellement ici pour vérifier si l'appareil est pris en charge par ce Devadora*/
/* open/close/read/i2c même avec écriture_Le client est utilisé, alors gardez-le*/
struct mydevice_device_info *dev_info;
dev_info = (struct mydevice_device_info*)devm_kzalloc(&client->dev, sizeof(struct mydevice_device_info), GFP_KERNEL);
dev_info->client = client;
i2c_set_clientdata(client, dev_info);
/*Enregistrez ce pilote de périphérique comme type de caractère dans le noyau.(/sys/class/mydevice/mydevice*faire) */
if(mydevice_i2c_create_cdev(dev_info)) return -ENOMEM;
return 0;
}
static int mydevice_i2c_remove(struct i2c_client *client)
{
printk("mydevice_i2c_remove\n");
struct mydevice_device_info *dev_info;
dev_info = i2c_get_clientdata(client);
mydevice_i2c_delete_cdev(dev_info);
return 0;
}
static struct i2c_driver mydevice_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.id_table = mydevice_i2c_idtable, //Périphériques I2C pris en charge par ce pilote de périphérique
.probe = mydevice_i2c_probe, //Processus appelé lorsque le périphérique I2C cible est reconnu
.remove = mydevice_i2c_remove, //Processus appelé lorsque le périphérique I2C cible est supprimé
};
/*Enregistrez ce pilote de périphérique en tant que pilote de périphérique utilisant le bus I2C*/
module_i2c_driver(mydevice_driver);
Au début du code, nous définissons notre propre structure appelée struct mydevice_device_info
. Il stocke struct i2c_client
, c'est-à-dire les informations associées à chaque périphérique I2C (client). ("Associer" signifie ici que si struct i2c_client
est donné, les informations stockées ( struct mydevice_device_info
) peuvent être obtenues.)
Cette fois, je vais créer un nouveau fichier de périphérique (cdev) chaque fois qu'un périphérique I2C est connecté, c'est-à-dire au moment de la sonde. C'est exactement le même processus que vous faisiez lors du chargement. Le processus de création de fichier de périphérique est extrait dans la fonction mydevice_i2c_create_cdev ()
. Jusqu'à présent, struct cdev cdev;
, ʻunsigned int mydevice_major; et
struct class * mydevice_class; restaient statiques. Cette fois, placez-le dans la structure
struct mydevice_device_info. De plus, la zone de cette structure est allouée dynamiquement par
devm_kzallocau moment de la sonde. En utilisant
devm_kzalloc, cette mémoire sera automatiquement libérée lorsque le périphérique disparaîtra. De plus, dans la même structure `` struct mydevice_device_info``, enregistrez la
struct i2c_clientrequise pour le contrôle I2C. Ensuite, utilisez ʻi2c_set_clientdata ()
pour sauvegarder cette structure struct mydevice_device_info
dans la zone associée à ce client I2C (où ?? je pense que le noyau le fera bien). Tous ces processus sont exécutés lorsque le périphérique est connecté (mydevice_i2c_probe ()
).
Depuis que j'ai créé un fichier de périphérique de caractères (cdev) dans mydevice_i2c_probe ()
, je peux utiliser open / close / read / write. Tout d'abord, regardez mydevice_open ()
. Un pointeur vers cdev est stocké dans le premier argument struct inode * inode
de cette fonction. Vous pouvez le trouver à ʻinode-> i_cdev. Auparavant, lors de la création du fichier de périphérique au moment de la sonde, cdev l'avait enregistré dans sa propre structure
struct mydevice_device_info. Par conséquent, vous pouvez utiliser
container_ofpour trouver l'adresse de début de la structure dans laquelle ce cdev est stocké.
dev_info = container_of (inode-> i_cdev, struct mydevice_device_info, cdev);. Et comme les informations de
struct i2c_clienty étaient également incluses, le contrôle I2C est possible en utilisant cela. En fait, il est utilisé au moment de la lecture / écriture, donc je le mets dans
private_data dans la structure
file` afin qu'il puisse être référencé au moment de la lecture / écriture.
Créez et chargez le périphérique I2C comme indiqué ci-dessous. Ensuite, / dev / mydevice0
est créé. Et si vous lisez cet appareil avec cat, vous pouvez voir que la fonction mydevice_read ()
est appelée, dans laquelle la communication I2C est effectuée et la valeur de l'ID est retournée.
make && sudo insmod MyDeviceModule.ko
sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
ls /dev/mydevice0
/dev/mydevice0
cat /dev/mydevice0
id = 0x33
C'est un article un peu long, mais le code simple que j'ai mentionné au début est la forme de base, donc je pense que ce n'est pas grave si vous le supprimez d'abord. Je pense que la même idée peut être fondamentalement appliquée aux pilotes de périphériques (SPI, USB, PCI, etc.) qui utilisent d'autres bus. (Je pense qu'un traitement supplémentaire pour détecter et suspendre sera nécessaire)
En outre, dans l'article, il y avait des expressions redondantes telles que «pilote de périphérique pour les périphériques connectés par I2C», «pilote de périphérique pour les périphériques I2C» et «pilote de périphérique pour les périphériques I2C». C'est parce que je voulais le distinguer du «pilote de périphérique d'I2C lui-même». J'aurais aimé avoir défini le mot quelque part, mais j'ai oublié.
Recommended Posts