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/05_01
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".
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.
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.
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;
}
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.
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é.
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);
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.
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