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/09_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/09_02
Dans le quatrième opus de cette série, j'ai implémenté un pilote de périphérique GPIO pour Raspeye. À ce moment-là, le contrôle était effectué en frappant directement le registre. L'adresse du registre et la valeur de consigne ont été définies en consultant la fiche technique du BCM2835. Ce sont des informations "dépendantes de la puce". Lorsque je crée un pilote de périphérique qui contrôle un périphérique externe tel qu'un capteur ou un moteur, je ne veux pas voir la fiche technique de chaque puce. Il existe une fonction pour le contrôle GPIO, alors utilisons-la.
En relation avec cela, je voudrais essayer d'appeler d'abord les fonctions définies dans d'autres modules du noyau.
Comme mentionné précédemment dans 4e: Implémentation en lecture / écriture et histoire de la mémoire, le noyau a un espace mémoire au total. Partager Cela inclut également les modules du noyau. Par conséquent, vous pouvez appeler les fonctions d'autres modules du noyau à partir du module du noyau que vous implémentez, ou vous pouvez appeler les fonctions qui sont statiquement intégrées dans le noyau lui-même.
Pour créer une fonction qui peut être appelée depuis d'autres modules, définissez simplement la fonction puis exportez-la avec ʻEXPORT_SYMBOL. ʻEXPORT_SYMBOL
enregistre la fonction dans la table des symboles du noyau afin qu'elle puisse être appelée par d'autres modules du noyau.
Créez un module qui définit uniquement les fonctions d'entrée pour le chargement (insmod) et le déchargement (rmmod) et la fonction (mydevicea_func ()
). Appelons cela MyDeviceDriverA. À l'origine, la déclaration de fonction doit être décrite dans l'en-tête, mais elle est gênante, elle est donc omise.
make
pour créer MyDeviceDriverA.ko.
myDeviceDriverA.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
/***Informations sur cet appareil***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDeviceA" /* /proc/Nom de l'appareil affiché sur les appareils, etc.*/
void mydevicea_func(void)
{
printk("This is a message in mydevicea_func\n");
}
/*Inscrivez-vous dans la table des symboles du noyau. Rendez-le appelable à partir d'autres modules du noyau*/
EXPORT_SYMBOL(mydevicea_func);
/*Route(insmod)Fonctions parfois appelées*/
static int mydevicea_init(void)
{
printk("[A]: mydevicea_init\n");
mydevicea_func();
return 0;
}
/*Décharger(rmmod)Fonctions parfois appelées*/
static void mydevicea_exit(void)
{
printk("[A]: mydevicea_exit\n");
}
module_init(mydevicea_init);
module_exit(mydevicea_exit);
Créez le module de noyau B qui appelle la fonction préparée précédemment. Vous pouvez l'appeler comme un langage C normal. Puisque l'en-tête de déclaration de fonction est omis cette fois, il est déclaré côté appelant avec extern. Je ne suis pas très sage. Au moment du chargement (insmod) de ce module B, essayez d'appeler la fonction précédente (mydevicea_func ()
).
make
pour créer MyDeviceDriverB.ko. L'état réel de mydevicea_func ()
n'est pas ici, mais il n'y a pas de problème lors de la création d'un module de noyau. Ceci est dû au fait que les modules make for kernel ne compilent et ne créent que des fichiers objets, pas des liens.
myDeviceDriverB.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
/***Informations sur cet appareil***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDeviceB" /* /proc/Nom de l'appareil affiché sur les appareils, etc.*/
/*Route(insmod)Fonctions parfois appelées*/
static int mydeviceb_init(void)
{
printk("[B]: mydeviceb_init\n");
extern void mydevicea_func(void);
mydevicea_func();
return 0;
}
/*Décharger(rmmod)Fonctions parfois appelées*/
static void mydeviceb_exit(void)
{
printk("[B]: mydeviceb_exit\n");
}
module_init(mydeviceb_init);
module_exit(mydeviceb_exit);
Commencez par charger MyDeviceDriverA.ko.
sudo insmod MyDeviceModuleA.ko
dmesg
[16909.979207] [A]: mydevicea_init
[16909.979223] This is a message in mydevicea_func
Après le chargement, si vous regardez le journal avec dmesg, vous pouvez voir que le processus d'initialisation du module A et la fonction ont été appelés dans init. Je ne pense pas que ce soit un problème.
Ensuite, chargez MyDeviceDriverB.ko.
sudo insmod MyDeviceModuleB.ko
dmesg
[17087.119434] [B]: mydeviceb_init
[17087.119449] This is a message in mydevicea_func
Ensuite, vous pouvez voir que le module B peut également appeler la fonction définie dans le module A de cette manière.
La dépendance est que le module B utilise le module A. Par conséquent, le module A doit être chargé avant de charger le module B. Sinon, une erreur se produira lors du chargement du module B. De même, le module B doit être déchargé avant que le module A puisse être déchargé. Si vous essayez de décharger le module A d'abord, vous obtiendrez une erreur.
Si vous implémentez correctement le contenu nécessaire et placez le module de noyau créé (.ko) à l'endroit approprié, vous pouvez utiliser mod probe
au lieu de ʻins mod` pour charger automatiquement les modules dépendants également. Il semble qu'ils le feront.
Comme mentionné au début, tant que vous créez des pilotes de périphériques pour les périphériques externes et les périphériques embarqués, je ne pense pas que vous ferez les paramètres de registre en regardant la fiche technique de la puce. Si vous êtes l'ingénieur d'un fabricant de SoC, que vous souhaitez étendre les fonctions ou si vous vous portez volontaire pour développer des dispositifs dépendant de la puce, vous en aurez besoin. (Cela ne peut donc pas être complètement hors de propos. Il peut y avoir des bugs.)
Lors du contrôle de GPIO à partir du pilote de périphérique, nous appelons les fonctions créées par ces personnes. Pour le moment, tout le monde ne l'implémente pas dans un format disjoint, mais l'implémente pour qu'il ait une interface comme celle de linux / gpio.h
. En conséquence, les utilisateurs (via les développeurs Devadora) peuvent utiliser les fonctions de linux / gpio.h
pour contrôler GPIO. Vous pouvez utiliser le même code sur une autre puce si vous utilisez les fonctions ici. (En regardant la documentation, il dit quelque chose comme "GPIO a un large éventail de fonctions, alors suivez linux / gpio.h
autant que possible." Par conséquent, cela peut différer selon la puce utilisée. Il y a.)
La puce pour laquelle le processus de contrôle GPIO est utilisé est déterminée par les paramètres au moment de la construction du noyau. Dans le cas de la tarte à la râpe, vous devriez utiliser le processus pour bcm2835. Le traitement GPIO pour BCM2835 était dans pinctrl-bcm2835.c
. Je ne l'ai pas suivi profondément, mais je pense qu'appeler la fonction dans linux / gpio.h
mènera finalement à chaque processus de pinctrl-bcm2835.c
. (En plus du contrôle de base du GPIO, il existe également un contrôle MUX en tant que broche fonctionnelle, donc cela semble assez compliqué.)
__ Quoi qu'il en soit, vous pouvez contrôler GPIO en utilisant les fonctions de linux / gpio.h
__
Cette fonction est requise pour le contrôle GPIO de base.
int gpio_direction_output(unsigned gpio, int value)
--Définissez GPIO sur la sortie. gpio est le numéro de broche. valeur est la valeur de sortie initiale (0 = faible, 1 = élevé)int gpio_direction_input(unsigned gpio)
--Définissez GPIO comme entrée. gpio est le numéro de brochevoid gpio_set_value(unsigned gpio, int value)
int gpio_to_irq(unsigned gpio)
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
--Enregistrez un gestionnaire d'interruption. Ce n'est pas une fonction uniquement GPIOComme il est difficile de mettre en œuvre la lecture / écriture, créez un pilote de périphérique (module noyau) avec les spécifications simples suivantes. En principe, on suppose que la LED est connectée au GPIO4 de Raspeye et que le bouton est connecté au GPIO17. La LED est connectée à 3,3 V via une résistance. Le bouton est connecté à GND et le côté GPIO17 est tiré vers le haut. Si cela pose problème, vous pouvez voir la sortie de GPIO4 avec un testeur, ou l'entrée de GPIO17 peut être directement connectée à 3,3V / GND.
Spécifications du module noyau à créer
--Lors du chargement d'un module (insmod)
Le code ressemble à ceci: Le gestionnaire d'interruption sera mydevice_gpio_intr ()
. Ceci est enregistré avec request_irq ()
au moment du chargement.
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/gpio.h>
#include <linux/interrupt.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.*/
#define GPIO_PIN_LED 4
#define GPIO_PIN_BTN 17
static irqreturn_t mydevice_gpio_intr(int irq, void *dev_id)
{
printk("mydevice_gpio_intr\n");
int btn;
btn = gpio_get_value(GPIO_PIN_BTN);
printk("button = %d\n", btn);
return IRQ_HANDLED;
}
/*Route(insmod)Fonctions parfois appelées*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
/*Sortie GPIO4 pour LED. La valeur initiale est 1(High) */
gpio_direction_output(GPIO_PIN_LED, 1);
/*0 à GPIO4 pour LED(Low)Pour sortir*/
gpio_set_value(GPIO_PIN_LED, 0);
/*Entrez GPIO17 pour le bouton*/
gpio_direction_input(GPIO_PIN_BTN);
/*Obtenez le numéro d'interruption GPIO17 pour le bouton*/
int irq = gpio_to_irq(GPIO_PIN_BTN);
printk("gpio_to_irq = %d\n", irq);
/*Enregistrer le gestionnaire d'interruption GPIO17 pour le bouton*/
if (request_irq(irq, (void*)mydevice_gpio_intr, IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "mydevice_gpio_intr", (void*)mydevice_gpio_intr) < 0) {
printk(KERN_ERR "request_irq\n");
return -1;
}
return 0;
}
/*Décharger(rmmod)Fonctions parfois appelées*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
int irq = gpio_to_irq(GPIO_PIN_BTN);
free_irq(irq, (void*)mydevice_gpio_intr);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Construisez et chargez comme suit.
make
sudo insmod MyDeviceModule.ko
La LED doit s'allumer. Après cela, essayez d'appuyer plusieurs fois sur le bouton ou de connecter GPIO17 à 3,3 V / GND.
dmesg
[19652.388837] mydevice_init
[19652.388873] gpio_to_irq = 183
[19654.100437] mydevice_gpio_intr
[19654.100457] button = 0
[19656.061705] mydevice_gpio_intr
[19656.061727] button = 1
Si vous regardez le journal avec dmesg, vous pouvez voir que le gestionnaire d'interruption enregistré est appelé et que la valeur d'entrée GPIO y est imprimée. En passant, vous pouvez vérifier l'état de l'interruption dans / proc / interrupts
.
cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
183: 7 0 0 0 pinctrl-bcm2835 17 Edge mydevice_gpio_intr
Il ne semble y avoir aucun moyen général de le définir à partir du code (https://raspberrypi.stackexchange.com/questions/44924/how-to-set-pull-up-down-resistors-in-a-kernel-module). Il semble être défini dans l'arborescence des périphériques. En regardant brcm, bcm2835-gpio.txt, il semble qu'il soit défini en utilisant brcm, pull
dans le fichier dts.
Recommended Posts