Comment créer un pilote de périphérique Linux intégré (10)

10ème: créer un pilote de périphérique en utilisant I2C

À propos de cette série

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.

Le code source complet qui apparaît dans cet article

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

Contenu de cette époque

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

Préparation et confirmation à l'avance (pour la tarte à la râpe)

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

Histoire autour d'I2C

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.

Pilote de périphérique I2C simple

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 ()eststruct 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);

Remarque: simplifiez le traitement de routine

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);

Essayez de bouger

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.

Note 1

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.

Note 2

Utilisez la commande suivante pour supprimer le périphérique.

sudo bash -c 'echo  0x18 > /sys/bus/i2c/devices/i2c-1/delete_device'

Reconnaître les appareils I2C sur le code

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.

Interface utilisant sysfs

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

Interface utilisant / dev

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 ().

Connaissance préalable

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)

code

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 utilisantdevm_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 dansprivate_data dans la structure file` afin qu'il puisse être référencé au moment de la lecture / écriture.

Essayez de bouger

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

en conclusion

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

Comment créer un pilote de périphérique Linux intégré (11)
Comment créer un pilote de périphérique Linux intégré (8)
Comment créer un pilote de périphérique Linux intégré (4)
Comment créer un pilote de périphérique Linux intégré (2)
Comment créer un pilote de périphérique Linux intégré (3)
Comment créer un pilote de périphérique Linux intégré (6)
Comment créer un pilote de périphérique Linux intégré (5)
Comment créer un pilote de périphérique Linux intégré (10)
Comment créer un pilote de périphérique Linux intégré (9)
Comment créer un pilote de périphérique Linux intégré (12) (Terminé)
Comment faire reconnaître Yubico Yubikey par Manjaro Linux
Comment créer un outil CLI interactif avec Golang
Comment créer un serveur HTTPS avec Go / Gin
Comment créer un laboratoire de piratage - Kali Linux (2020.1) VirtualBox 64 bits Partie 2-
Comment créer un laboratoire de piratage - Kali Linux (2020.1) VirtualBox 64-bit edition -
Comment créer un package Python (écrit pour un stagiaire)
Comment créer un fichier ISO (image CD) sous Linux
Comment faire une traduction japonais-anglais
Comment créer un bot slack
Comment installer VMware-Tools sur Linux
Comment créer une fonction récursive
Comment installer MBDyn (Linux Ubuntu)
[Blender] Comment créer un plug-in Blender
[Blender] Comment rendre les scripts Blender multilingues
Comment créer un robot - Basic
Comment créer un pilote de langage MongoDB C
Comment vérifier la version du système d'exploitation Linux
Comment transformer une chaîne en tableau ou un tableau en chaîne en Python
Comment obtenir le pilote d'imprimante pour Oki Mac sous Linux
Comment rendre les caractères de Word Cloud monochromatiques
Comment rendre le sélénium aussi léger que possible
[Linux] Comment subdiviser des fichiers et des dossiers
Comment créer un bot LINE à intelligence artificielle avec l'API de messagerie Flask + LINE
Comment installer aws-session-manager-plugin sur Manajro Linux
[Python] Comment rendre une classe itérable
python3 Comment installer un module externe
Comment créer un environnement NVIDIA Docker
Comment convertir Python en fichier exe
[Linux] Comment utiliser la commande echo
Comment mettre à jour PHP sur Amazon Linux 2
Comment afficher des pictogrammes sur Manjaro Linux
Comment installer des packages sur Alpine Linux
[Cocos2d-x] Comment créer une liaison de script (partie 2)
Comment faire fonctionner Linux depuis la console
Comment mettre hors tension de Linux sur Ultra96-V2
Comment mettre à jour la sécurité sur CentOS Linux 8
Je veux faire un programme d'automatisation!
Comment installer php7.4 sur Linux (Ubuntu)
Comment créer une clé USB à démarrage multiple (compatible Windows 10)
Comment créer un indicateur personnalisé Backtrader
Comment créer un plan de site Pelican
[Cocos2d-x] Comment créer une liaison de script (partie 1)
Comment trouver des fichiers volumineux sous Linux
Comment utiliser le pilote JDBC avec Redash