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

4ème: Implémentation en lecture / écriture et histoire de la mémoire

À propos de cette série

J'écrirai un HowTo sur la façon de créer un module de noyau (pilote de périphérique) pour Linux embarqué. Tout le contenu de cet article peut être exécuté sur Razpie.

Le code source complet qui apparaît dans cet article

https://github.com/take-iwiw/DeviceDriverLesson/tree/master/04_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/04_02

Contenu de cette époque

Jusqu'à la dernière fois, nous avons mis au point une méthode pour incorporer le pilote de périphérique créé dans le noyau en tant que module du noyau. Cette fois, j'échangerai des valeurs avec ce pilote de périphérique en lecture / écriture depuis le programme utilisateur.

Connaissances préalables

Concernant l'espace mémoire

Les ordinateurs ont généralement une mémoire telle que DDR. Dans le cas de Raspberry Pi 2, 1 Go de SDRAM est installé. Pour Raspeye, l'adresse physique de cette mémoire commence à 0x0000_0000. Cependant, si le programme que nous écrivons utilise directement l'adresse physique lors de l'accès aux données en mémoire, ce n'est pas le cas. La CPU a une fonction appelée MMU, et la MMU convertit l'adresse physique ⇔ l'adresse virtuelle. Le noyau et chaque processus s'exécutent dans des espaces d'adressage virtuels indépendants. Par exemple, si 50 processus sont en cours d'exécution, il y aura 51 espaces d'adressage virtuels (nombre de processus + noyau). Les adresses de chaque espace virtuel sont indépendantes. Par exemple, 0xAAAA_AAAA pour le processus A et 0xAAAA_AAAA pour le processus B sont différents. Cela empêche la mémoire d'être corrompue par d'autres processus. En outre, vous pouvez avoir plus d'espace mémoire que la capacité de mémoire physique réelle. Ce sont les avantages de l'utilisation de MMU.

Dans le cas de la tarte aux râpes

memorymap.jpg

Ce qui précède est la carte mémoire de Raspeye. Tout d'abord, le centre est l'adresse physique vue depuis le CPU (ARM). Si vous regardez cela, vous pouvez voir que la SDRAM est allouée à partir de 0x0000_0000. Le côté droit est la carte mémoire de l'adresse logique après avoir été traduite par la MMU ARM. 0x0000_0000 à 0xC000_0000 sont des adresses virtuelles pour les processus de l'espace utilisateur. Les programmes normaux s'exécutent dans cet espace de mémoire virtuelle. Des adresses virtuelles dupliquées se produisent entre différents processus. Cependant, le MMU le traduira afin qu'il ne chevauche pas l'adresse physique. À partir de 0xC000_0000, le niveau supérieur est l'espace de mémoire virtuelle pour l'espace noyau.

J'ai mentionné plus tôt que l'adresse physique de la SDRAM vue par l'ARM commence à 0x0000_0000, mais lorsque le processeur accède réellement à la mémoire, il passe par le bus (probablement AXI). De plus, il s'agit probablement de la propre configuration de Raspai, mais il semble qu'il soit connecté au bus via un processeur vidéo (VC (Video Core) sur le côté gauche de la figure). Par conséquent, la conversion de VC avec MMU est incluse. En regardant la figure, 0xC000_0000 est l'adresse de bus de la SDRAM (sans cache) à la fin. Par conséquent, il est nécessaire de définir cette adresse lors de l'utilisation de DMA, etc. Au fait, cela semble être 0x8000_0000 via le cache L2. Par exemple, lorsque la CPU crée des données à transférer par DMA, si vous écrivez à cette adresse, la cohérence ne sera pas conservée. L'incohérence des données se produit car le transfert DMA est effectué avant la réécriture depuis le cache vers la SDRAM. Vous devez le convertir en une adresse non mise en cache avant d'accéder à la mémoire.

Il est difficile de vérifier ces adresses une par une dans la carte mémoire, mais il semble que vous puissiez les obtenir avec la fonction suivante. La construction est gcc get_memory.c -L / opt / vc / lib -lbcm_host. Pour Raspberry Pi2, bcm_host_get_sdram_address a renvoyé C0000000 et bcm_host_get_peripheral_address a renvoyé 3F000000.

get_memory.c


#include <stdio.h>

int main()
{
	/* https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md */
	extern unsigned bcm_host_get_sdram_address(void);
	printf("%08X\n", bcm_host_get_sdram_address());
	extern unsigned bcm_host_get_peripheral_address(void);;
	printf("%08X\n", bcm_host_get_peripheral_address());

	return 0;
}

Veuillez consulter la Fiche de données: BCM2835-ARM-Peripherals.pdf pour plus de détails sur la carte mémoire de Raspeye. ..

La chose importante ici

C'est un peu en dehors des sentiers battus, mais ce qui est important cette fois, c'est que l'appelant de l'appel système (processus de l'espace utilisateur) et l'espace d'adressage virtuel dans le module du noyau sont différents. Passer un pointeur ne garantit pas qu'il pointe vers la même adresse.

Espace utilisateur - Copie de données sécurisée dans l'espace noyau

Dernière implémentation

La dernière fois, j'ai implémenté le gestionnaire d'appels système de lecture suivant dans l'implémentation pour le moment. Chaque fois que vous lisez, 1 octet de A est stocké dans buf. Ce buf est la zone réservée par l'appelant (espace utilisateur). Par conséquent, un accès hors zone doit avoir lieu. Cependant, cela fonctionnait bien. La valeur a également été stockée correctement. En effet, l'espace d'adressage virtuel est divisé en 1 Go pour le noyau et 3 Go pour l'utilisateur, comme indiqué dans la carte mémoire au début. Par conséquent, le noyau peut accéder à l'espace d'adressage virtuel des processus s'exécutant dans le même contexte (c'est-à-dire au processus qui a appelé l'appel système). Dans la direction opposée, je pense qu'une violation de la protection de la mémoire se produira. Cependant, cela fonctionne et je ne pense pas que cela fonctionnera si l'adresse cible est permutée, par exemple.

static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_read");
	buf[0] = 'A';
	return 1;
}

Copiez les données selon la manière

La méthode ci-dessus fonctionne, mais les manuels utilisent copy_to_user et copy_from_user lors de la copie de données dans l'espace utilisateur-noyau. Dans le code ci-dessous, la chaîne de caractères définie par l'utilisateur avec copy_from_user au moment de l'écriture est stockée dans la variable statique valeur_stockée. Le contenu conservé au moment de la lecture est retourné par copy_to_user.

#define NUM_BUFFER 256
static char stored_value[NUM_BUFFER];

/*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");
	if(count > NUM_BUFFER) count = NUM_BUFFER;
	if (copy_to_user(buf, stored_value, count) != 0) {
		return -EFAULT;
	}
	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");
	if (copy_from_user(stored_value, buf, count) != 0) {
		return -EFAULT;
	}
	printk("%s\n", stored_value);
	return count;
}

Gérer différemment pour chaque ouverture (descripteur de fichier)

Le code ci-dessus maintient les données statiques. Par conséquent, même si différents utilisateurs les ouvrent séparément, ils accéderont à la même variable. De plus, vous accéderez aux mêmes variables en utilisant un appareil différent. Réservez une zone de données lorsque vous l'ouvrez afin de pouvoir la gérer individuellement.

Traitement au moment de l'ouverture / de la fermeture

La structure du fichier est donnée dans l'argument open. Il y a un membre appelé private_data dans cette structure de fichier, et vous pouvez librement enregistrer le pointeur. Cette structure de fichier elle-même est enregistrée / gérée par l'utilisateur en tant que descripteur de fichier. Utilisez kmalloc pour allouer de la mémoire. Relâchez avec kfree une fois fermé. Cette fois, supposons que la structure _mydevice_file_data est le type qui contient les données.

/***Chaque dossier(Descripteur de fichier créé pour chaque ouverture)Informations associées à***/
#define NUM_BUFFER 256
struct _mydevice_file_data {
	unsigned char buffer[NUM_BUFFER];
};

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

	/*Réservez une zone pour stocker des données uniques à chaque fichier*/
	struct _mydevice_file_data *p = kmalloc(sizeof(struct _mydevice_file_data), GFP_KERNEL);
	if (p == NULL) {
		printk(KERN_ERR  "kmalloc\n");
		return -ENOMEM;
	}

	/*Initialiser les données spécifiques au fichier*/
	strlcat(p->buffer, "dummy", 5);
	
	/*Avoir le pointeur sécurisé tenu par fd côté utilisateur*/
	file->private_data = p;

	return 0;
}

/*Fonction appelée à la fermeture*/
static int mydevice_close(struct inode *inode, struct file *file)
{
	printk("mydevice_close");

	if (file->private_data) {
		/*Libérez la zone de données propre à chaque fichier réservé au moment de l'ouverture*/
		kfree(file->private_data);
		file->private_data = NULL;
	}

	return 0;
}

Traitement au moment de la lecture / écriture

Lorsque l'utilisateur lit ou écrit, le descripteur de fichier obtenu par open est défini comme argument. Dans l'implémentation côté pilote de périphérique, la zone de données allouée au moment de l'ouverture est accessible en se référant au membre private_data dans la structure file passé comme argument. Cela vous permet de gérer les données individuellement pour chaque ouverture (descripteur de fichier).

/*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");
	if(count > NUM_BUFFER) count = NUM_BUFFER;

	struct _mydevice_file_data *p = filp->private_data;
	if (copy_to_user(buf, p->buffer, count) != 0) {
		return -EFAULT;
	}
	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");

	struct _mydevice_file_data *p = filp->private_data;
	if (copy_from_user(p->buffer, buf, count) != 0) {
		return -EFAULT;
	}
	return count;
}

Essayez d'appeler depuis le programme utilisateur

À propos du code d'erreur

C'est un peu hors sujet, mais la valeur de retour du noyau est généralement 0. Le système de lecture / écriture est le nombre d'octets réellement traités. En cas d'erreur, une valeur négative est renvoyée.

Du côté du programme utilisateur, en incluant #include <errno.h>, le code d'erreur est stocké dans la variable ʻerrno. Vous pouvez le vérifier directement, mais en utilisant une fonction appelée perror, le code d'erreur sera converti en une phrase facile à comprendre. Par exemple, procédez comme suit: ʻIf ((fd = open ("/ dev / mydevice0", O_RDWR)) <0) perror ("open");

Code de test

test.c


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main()
{
	char buff[256];
	int fd0_A, fd0_B, fd1_A;

	printf("%08X\n", buff);

	if ((fd0_A = open("/dev/mydevice0", O_RDWR)) < 0) perror("open");
	if ((fd0_B = open("/dev/mydevice0", O_RDWR)) < 0) perror("open");
	if ((fd1_A = open("/dev/mydevice1", O_RDWR)) < 0) perror("open");

	if (write(fd0_A, "0_A", 4) < 0) perror("write");
	if (write(fd0_B, "0_B", 4) < 0) perror("write");
	if (write(fd1_A, "1_A", 4) < 0) perror("write");

	if (read(fd0_A, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	if (read(fd0_B, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	if (read(fd1_A, buff, 4) < 0) perror("read");
	printf("%s\n", buff);
	
	if (close(fd0_A) != 0) perror("close");
	if (close(fd0_B) != 0) perror("close");
	if (close(fd1_A) != 0) perror("close");

	return 0;
}

Compilez et exécutez le code de test comme ci-dessus.

gcc test.c
./a.out
0_A
0_B
1_A

Le résultat ressemble à ceci, et vous pouvez voir que même si vous accédez au même appareil, vous pouvez conserver / obtenir des valeurs différentes si vous les ouvrez séparément.

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
Comment créer un serveur HTTPS avec Go / Gin
[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 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 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
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 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
Je veux faire un programme d'automatisation!