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

5ème: Implémentation de GPIO Devadora pour Raspeye

À 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/05_01

Contenu de cette époque

Jusqu'à la dernière fois, vous savez comment implémenter des appels système (ouvrir, fermer, lire, écrire) qui effectuent des opérations de base avec le pilote de périphérique. Cette fois, nous utiliserons le contenu jusqu'à présent pour implémenter le pilote de périphérique GPIO pour Raspai.

Puisque le but n'est pas de créer un périphérique GPIO solide, par souci de simplicité, nous ciblerons uniquement GPIO4 et utiliserons toujours le mode de sortie. High / Low est émis par écriture. Read renvoie le niveau de sortie actuel sous la forme d'une chaîne de "1" / "0".

Connaissances préalables

Histoire de mémoire (encore)

La dernière fois, j'ai expliqué la mémoire de Raspeye. Dans Raspberry Pi 2, l'adresse physique du registre périphérique vu depuis ARM (CPU) est 0x3F000000. Cela semble avoir été 0x20000000 pour le Raspberry Pi original. En fait, en regardant la carte mémoire de la BCM2835 Datasheet, I / O Peripherals indique 0x20000000. Ça a été. Comme présenté la dernière fois, vous pouvez obtenir cette adresse avec bcm_host_get_peripheral_address. Cependant, par souci de simplicité, nous allons le corriger à 0x3F000000 cette fois. Si Razzpie 4 ou 5 sort dans le futur, cette adresse peut changer.

Accès au registre du matériel Raspeye

Comme mentionné ci-dessus, nous savons que l'adresse physique du registre périphérique commence à 0x3F000000. Voir Fiche technique BCM2835 pour savoir quel registre frapper spécifiquement pour le contrôle GPIO. Il est répertorié. Pour le moment, il y a une mise en garde sur la façon de lire cette fiche technique. Cette fiche technique contient une description de chaque registre, ainsi qu'une adresse pour chacun. Cependant, cette adresse sera une adresse de bus commençant par 0x7E000000. Ce dont nous avons besoin, c'est de l'adresse physique vue par le CPU. Nous savons déjà que l'adresse de début est 0x3F000000, alors assurez-vous de ne regarder que le décalage. Par exemple, GPFSEL0 (GPIO Function Select 0) est 0x7E200000 dans la fiche technique, mais l'adresse physique vue depuis le CPU est 0x3F200000 (dans le cas de Raspberry Pi 2).

A propos, les registres utilisés cette fois sont les suivants.

Mise en œuvre d'essai avec le programme d'espace utilisateur

J'ai créé des modules de noyau jusqu'à présent, mais en fait, vous ne pouvez accéder qu'aux registres depuis l'espace utilisateur. Cette fois, je viens de frapper le registre de contrôle GPIO, donc je vais l'implémenter avec un programme sur l'espace utilisateur pour le moment. Si cela fonctionne, nous commencerons à implémenter le pilote de périphérique du côté du module du noyau plus tard. L'essayer au préalable dans l'espace utilisateur présente l'avantage d'être facile à déboguer.

Pour accéder à l'adresse physique depuis un programme d'espace utilisateur, ouvrez d'abord / dev / mem. Utilisez ensuite ce descripteur de fichier (fd) pour mmap. Je pense qu'il est normal d'utiliser uniquement la taille, mais il semble qu'il existe de nombreux cas où la taille de page (4 Ko) est sécurisée. De plus, ʻO_SYNC` est spécifié lors de l'ouverture. Cela invalide le cache et fournit un accès immédiat au registre. (En premier lieu, je pense que le cache n'est pas utilisé car c'est un registre, mais juste au cas où)

Le code ressemble à ceci: Vous pouvez accéder au registre (0x3F000000) en lisant et en écrivant à l'adresse virtuelle acquise par mmap. L'explication des valeurs définies dans chaque registre est omise. Veuillez consulter la fiche technique. Fondamentalement, il définit et n'entre / sort que les valeurs de GPIO4.

userGpio.c


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

/*Adresse physique du registre périphérique(D'après les spécifications de BCM2835) */
#define REG_ADDR_BASE        (0x3F000000)		/* bcm_host_get_peripheral_address()Est mieux*/
#define REG_ADDR_GPIO_BASE   (REG_ADDR_BASE + 0x00200000)
#define REG_ADDR_GPIO_LENGTH 4096
#define REG_ADDR_GPIO_GPFSEL_0     0x0000
#define REG_ADDR_GPIO_OUTPUT_SET_0 0x001C
#define REG_ADDR_GPIO_OUTPUT_CLR_0 0x0028
#define REG_ADDR_GPIO_LEVEL_0      0x0034

#define REG(addr) (*((volatile unsigned int*)(addr)))
#define DUMP_REG(addr) printf("%08X\n", REG(addr));

int main()
{
	int address;	/*Adresse virtuelle au registre GPIO(Espace utilisateur) */
	int fd;

	/*Ouvrir le fichier de l'appareil pour accéder à la mémoire*/
	if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
		perror("open");
		return -1;
	}

	/* ARM(CPU)Adresse physique vue de → Mappage vers une adresse virtuelle*/
	address = (int)mmap(NULL, REG_ADDR_GPIO_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, REG_ADDR_GPIO_BASE);
	if (address == MAP_FAILED) {
		perror("mmap");
		close(fd);
		return -1;
	}

	/*Réglez GPIO4 sur la sortie*/
	REG(address + REG_ADDR_GPIO_GPFSEL_0) = 1 << 12;

	/*Haut rendement de GPIO4*/
	REG(address + REG_ADDR_GPIO_OUTPUT_SET_0) = 1 << 4;
	DUMP_REG(address + REG_ADDR_GPIO_LEVEL_0);

	/*Faible sortie de GPIO4*/
	REG(address + REG_ADDR_GPIO_OUTPUT_CLR_0) = 1 << 4;
	DUMP_REG(address + REG_ADDR_GPIO_LEVEL_0);

	/*Libérer les ressources utilisées*/
	munmap((void*)address, REG_ADDR_GPIO_LENGTH);
	close(fd);

	return 0;
}

Essayez de bouger

Connectez la broche GPIO4 (la broche à droite du SCL) à 3,3 V via la LED et la résistance. Construisez et exécutez avec la commande suivante.

gcc userGpio.c
sudo ./a.out

Comme vous pouvez le voir à partir du code, la LED s'allume car GPIO4 est une sortie faible à la fin. Si vous commentez cette ligne, GPIO4 sera émis High et la LED s'éteindra.

Implémenter le pilote de périphérique en tant que module du noyau

spécification

Enfin, créez un vrai pilote de périphérique. Comme mentionné au début, cette fois, nous utiliserons une spécification très simple par souci de simplicité.

la mise en oeuvre

Dans l'espace noyau, utilisez ʻioremap_nocache` pour traduire les adresses physiques en adresses virtuelles (non mises en cache). En ce qui concerne cette conversion d'adresse, je voulais convertir une grande zone (par exemple, 4 Ko) à la fois lors du chargement ou de l'ouverture du module noyau, mais l'espace noyau n'a qu'un seul espace d'adressage virtuel dans son ensemble. Alors, je me suis demandé si un seul module pouvait occuper autant, j'ai donc décidé de convertir 4 octets à chaque fois que j'accédais au registre. Bien sûr, c'est désavantageux en termes de vitesse. Que faites-vous habituellement? Si vous avez des connaissances, faites-le moi savoir. (Dans le cas de l'espace noyau, je pense que les adresses virtuelles ne sont pas attribuées, mais uniquement traduites à partir d'adresses physiques, donc ce n'est peut-être pas un gaspillage d'espace d'adressage.)

Après la conversion d'adresse, vous pouvez accéder aux registres de la même manière qu'un programme d'espace utilisateur. Le code ressemble à ceci: Le traitement dans mydevice_open, mydevice_read, mydevice_write est important. A part cela, init et exit sont les mêmes que la dernière fois. J'ai pensé que je l'omettrais, mais comme c'est un article séparé, je vais tout poster.

Concernant l'échange de données avec l'espace utilisateur, j'ai utilisé la dernière fois copy_to_user et copy_from_user. Cette fois, seulement 1 octet de données est échangé, j'ai donc essayé d'utiliser put_user et get_user.

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/slab.h>
#include <asm/current.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/*Adresse physique du registre périphérique(D'après les spécifications de BCM2835) */
#define REG_ADDR_BASE        (0x3F000000)		/* bcm_host_get_peripheral_address()Est mieux*/
#define REG_ADDR_GPIO_BASE   (REG_ADDR_BASE + 0x00200000)
#define REG_ADDR_GPIO_GPFSEL_0     0x0000
#define REG_ADDR_GPIO_OUTPUT_SET_0 0x001C
#define REG_ADDR_GPIO_OUTPUT_CLR_0 0x0028
#define REG_ADDR_GPIO_LEVEL_0      0x0034

#define REG(addr) (*((volatile unsigned int*)(addr)))
#define DUMP_REG(addr) printk("%08X\n", REG(addr));

/***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 seulement*/
static unsigned int mydevice_major;			/*Numéro majeur de ce pilote de périphérique(Décider dynamiquement) */
static struct cdev mydevice_cdev;			/*Objet périphérique de caractère*/
static struct class *mydevice_class = NULL;	/*Objet de classe de pilote de périphérique*/

/*Fonction appelée à l'ouverture*/
static int mydevice_open(struct inode *inode, struct file *file)
{
	printk("mydevice_open");

	/* ARM(CPU)Adresse physique vue de → Adresse virtuelle(Espace noyau)Mappage vers*/
	int address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_GPFSEL_0, 4);

	/*Réglez GPIO4 sur la sortie*/
	REG(address) = 1 << 12;

	iounmap((void*)address);

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

	/* ARM(CPU)Adresse physique vue de → Adresse virtuelle(Espace noyau)Mappage vers*/
	int address = address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_LEVEL_0, 4);
	int val = (REG(address) & (1 << 4)) != 0;	/*0 si GPIO4 est 0,Définir sur 1*/

	/*Renvoie la valeur de sortie GPIO à l'utilisateur sous forme de caractère*/
	put_user(val + '0', &buf[0]);

	iounmap((void*)address);

	return count;
}

/*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");

	int address;
	char outValue;

	/*Obtenir la valeur de sortie de GPIO définie par l'utilisateur*/
	get_user(outValue, &buf[0]);

	/* ARM(CPU)Adresse physique vue de → Adresse virtuelle(Espace noyau)Mappage vers*/
	if(outValue == '1') {
		/* '1'Puis SET*/
		address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_OUTPUT_SET_0, 4);
	} else {
		/* '0'Puis CLR*/
		address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_OUTPUT_CLR_0, 4);
	}
	REG(address) = 1 << 4;
	iounmap((void*)address);
	
	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,
};

/*Route(insmod)Fonctions parfois appelées*/
static int mydevice_init(void)
{
	printk("mydevice_init\n");

	int alloc_ret = 0;
	int cdev_err = 0;
	dev_t dev;

	/* 1.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;
	}

	/* 2.Obtenu dev( =Numéro majeur+Numéro mineur)Obtenez le numéro de mesure et conservez-le*/
	mydevice_major = MAJOR(dev);
	dev = MKDEV(mydevice_major, MINOR_BASE);	/*Inutile? */

	/* 3.Initialisation de la structure cdev et enregistrement de la table du gestionnaire d'appels système*/
	cdev_init(&mydevice_cdev, &s_mydevice_fops);
	mydevice_cdev.owner = THIS_MODULE;

	/* 4.Ce pilote de périphérique(cdev)Vers le noyau*/
	cdev_err = cdev_add(&mydevice_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;
	}

	/* 5.Enregistrer la classe de cet appareil(/sys/class/mydevice/faire) */
	mydevice_class = class_create(THIS_MODULE, "mydevice");
	if (IS_ERR(mydevice_class)) {
		printk(KERN_ERR  "class_create\n");
		cdev_del(&mydevice_cdev);
		unregister_chrdev_region(dev, MINOR_NUM);
		return -1;
	}

	/* 6. /sys/class/mydevice/mydevice*faire*/
	for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
		device_create(mydevice_class, NULL, MKDEV(mydevice_major, minor), NULL, "mydevice%d", minor);
	}

	return 0;
}

/*Décharger(rmmod)Fonctions parfois appelées*/
static void mydevice_exit(void)
{
	printk("mydevice_exit\n");

	dev_t dev = MKDEV(mydevice_major, MINOR_BASE);
	
	/* 7. /sys/class/mydevice/mydevice*Supprimer*/
	for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
		device_destroy(mydevice_class, MKDEV(mydevice_major, minor));
	}

	/* 8.Supprimer l'enregistrement de cours pour cet appareil(/sys/class/mydevice/Supprimer) */
	class_destroy(mydevice_class);

	/* 9.Ce pilote de périphérique(cdev)À partir du noyau*/
	cdev_del(&mydevice_cdev);

	/* 10.Supprimez l'enregistrement de numéro majeur utilisé par ce pilote de périphérique*/
	unregister_chrdev_region(dev, MINOR_NUM);
}

module_init(mydevice_init);
module_exit(mydevice_exit);

Contrôle de fonctionnement

Construisez et incluez dans le noyau comme suit:

make
sudo insmod MyDeviceModule.ko
echo "0" > /dev/mydevice0
echo "1" > /dev/mydevice0
cat /dev/mydevice0
1111111111111111^C

Ecrire "0" ou "1" avec écho devrait changer la sortie de GPIO4 et faire scintiller la LED. De plus, lire avec chat affichera le niveau de sortie à ce moment-là. Cependant, il renvoie toujours une valeur, vous devez donc l'arrêter avec Ctrl-c.

prime

Il semble que vous puissiez vérifier la carte de mémoire physique avec la commande suivante. Si vous regardez cela, vous pouvez voir qu'il a le même contenu que celui que nous avons vu jusqu'à présent. La SDRAM est située à partir de l'adresse physique 0. Dans la carte mémoire ci-dessous, il s'agit de 00000000-3b3fffff. 0x3b3fffff = 994050047 = Environ 1 Go, il semble donc que nous nous réunissions. L'adresse de registre de Peri est également située à 0x3fXXXXXX.

sudo cat /proc/iomem
00000000-3b3fffff : System RAM
  00008000-00afffff : Kernel code
  00c00000-00d3da63 : Kernel data
3f006000-3f006fff : dwc_otg
3f007000-3f007eff : /soc/dma@7e007000
3f00b840-3f00b84e : /soc/vchiq
3f00b880-3f00b8bf : /soc/mailbox@7e00b880
3f101000-3f102fff : /soc/cprman@7e101000
3f200000-3f2000b3 : /soc/gpio@7e200000
3f201000-3f201fff : /soc/serial@7e201000
  3f201000-3f201fff : /soc/serial@7e201000
3f202000-3f2020ff : /soc/sdhost@7e202000
3f215000-3f215007 : /soc/aux@0x7e215000
3f980000-3f98ffff : dwc_otg

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é (1)
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é (7)
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
[Python] Comment créer une matrice de contiguïté / liste de contiguïté [Théorie des graphes]
Comment créer un laboratoire de piratage - Kali Linux (2020.1) VirtualBox 64 bits Partie 2-
Comment créer un package Python (écrit pour un stagiaire)
Comment créer un fichier ISO (image CD) sous Linux
Comment créer un bot slack
Comment installer VMware-Tools sur Linux
Comment créer un robot - Avancé
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 créer mon propre serveur Linux
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
Je veux savoir comment fonctionne LINUX!
[Linux] Comment utiliser la commande echo
Comment mettre à jour PHP sur Amazon Linux 2
Comment afficher des pictogrammes sur Manjaro Linux
[Cocos2d-x] Comment créer une liaison de script (partie 2)
Comment faire fonctionner Linux depuis la console
Comment installer le sous-système Windows pour Linux
Comment mettre hors tension de Linux sur Ultra96-V2
Comment mettre à jour la sécurité sur CentOS Linux 8
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