Explication détaillée du cadre de pilote du sous-système GPIO du noyau Linux

Table des matières

1. Introduction

2 niveaux de sous-système GPIO

3 processus de pilote du sous-système gpio

4 Structure des données de médecine traditionnelle chinoise du sous-système gpio

5 Détails détaillés des appels de fonction du sous-système gpio

6 interface sysfs du sous-système GPIO

6.1 Quels sont les contrôleurs GPIO ?

6.2 Détails de chaque contrôleur gpio

6.3 Vérifier l'utilisation du GPIO

6.4 Utilisation des GPIO via SYSFS

6.4.1 Déterminer le numéro GPIO

6.4.2 Exporter, définir la direction, lire et écrire des valeurs

7 Méthode d'apprentissage Feynman : J'ai donc enregistré une vidéo d'apprentissage expliquant le sous-système gpio


1. Introduction

Lorsque nous voulons utiliser une certaine broche pour contrôler l'allumage et l'extinction de la lumière LED, nous devons généralement activer l'horloge, puis configurer la broche comme fonction GPIO, puis configurer les propriétés électriques, puis configurer le GPIO comme sortie, et enfin, contrôlez le GPIO en fonction du niveau de sortie, où la direction et le niveau du GPIO sont configurés, est effectué par le sous-système GPIO.

2 niveaux de sous-système GPIO

La figure ci-dessus est le diagramme de structure hiérarchique du sous-système gpio. Dans d'autres pilotes, nous pouvons directement utiliser la fonction gpiod_set_value pour définir la valeur de la broche. Cette fonction est définie dans la bibliothèque gpio, et la bibliothèque gpio sert de lien entre la fonction précédente et suivante., puis cette fonction gpiod_set_value appelle enfin la fonction chip->set(chip, gpio_chip_hwgpio(desc), value), où chip est la structure enregistrée dans le pilote gpio, et cette structure contient quelques paires gpio fonction de fonctionnement.

3 processus de pilote du sous-système gpio

L'image ci-dessus est un organigramme d'un pilote GPIO que j'ai dessiné sur la base du code source du noyau Linux. Les pilotes compatibles dans le nœud du contrôleur gpio dans l'arborescence des périphériques sont fsl, imx35-gpio, puis nous recherchons dans le code source du noyau pour trouver un pilote correspondant. Il s'agit de mxc_gpio_driver, et lorsque le périphérique correspond au pilote, il appellera la fonction de sonde dans le pilote, qui est la fonction mxc_gpio_probe, puis dans cette fonction mxc_gpio_probe, il effectue en fait les trois tâches suivantes

  • structure d'attribution
  • définir la structure
  • Structure d'inscription

Le travail spécifique de la fonction mxc_gpio_probe : Tout d'abord, la fonction mxc_gpio_get_hw est appelée. Cette fonction obtient l'adresse de décalage du groupe de registres gpio, puis une fonction platform_get_resource est utilisée. Cette fonction platform_get_resource obtient l'adresse de base du registre gpio, puis appelle devm_kzalloc pour allouer. Vous avez une structure mxc_gpio_port,

struct mxc_gpio_port {
    struct list_head node;
    struct clk *clk;
    void __iomem *base;
    int irq;
    int irq_high;
    struct irq_domain *domain;
    struct gpio_chip gc;
    u32 both_edges;
    int saved_reg[6];
    bool gpio_ranges;
};

Ensuite, il y a un membre important de la structure gpio_chip dans cette structure mxc_gpio_port.

struct gpio_chip {
    const char      *label;
    struct gpio_device  *gpiodev;
    struct device       *parent;
    struct module       *owner;
    ...省略一些...
    int         (*direction_input)(struct gpio_chip *chip,
                        unsigned offset);
    int         (*direction_output)(struct gpio_chip *chip,
                        unsigned offset, int value);
    int         (*get)(struct gpio_chip *chip,
                        unsigned offset);
    void            (*set)(struct gpio_chip *chip,
                        unsigned offset, int value);
     ...省略一些...
                        enum single_ended_mode mode);
    int         (*to_irq)(struct gpio_chip *chip,
                        unsigned offset);
    void            (*dbg_show)(struct seq_file *s,
                        struct gpio_chip *chip);
     ...省略一些...
};

Ce gpio_chip contient diverses fonctions opérationnelles.

Ensuite, la fonction suivante est appelée dans la fonction sonde

    err = bgpio_init(&port->gc, &pdev->dev, 4,
             port->base + GPIO_PSR,
             port->base + GPIO_DR, NULL,
             port->base + GPIO_GDIR, NULL,
             BGPIOF_READ_OUTPUT_REG_SET);
这里的参数    
port->base + GPIO_PSR,
port->base + GPIO_DR, 
port->base + GPIO_GDIR, 
就是寄存器地址
设置完结构体之后,这个结构体里面有寄存器值也有操作函数。

Dans cette fonction bgpio_init, les trois fonctions suivantes sont principalement appelées :

bgpio_setup_io(gc, dat, set, clr, flags);
bgpio_setup_accessors(dev, gc, flags & BGPIOF_BIG_ENDIAN,
                    flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER);
bgpio_setup_direction(gc, dirout, dirin, flags);

Ensuite, ces trois fonctions incluent diverses fonctions opérationnelles de gpio. Ensuite, la fonction err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port); est appelée. La structure gpio_device est allouée dans cette fonction, et le membre de puce dans la structure gpio_device est la structure gpio_chip allouée et définie précédemment.

4 Structure des données de médecine traditionnelle chinoise du sous-système gpio

Comme nous l'avons dit précédemment, nous devons allouer des paramètres pour enregistrer une structure gpio_chip, puis nous utilisons la fonction gpiochip_add_data(chip, data); pour enregistrer une structure gpio_device, puis cette structure gpio_device contient la structure gpio_chip, puis cette structure gpio_device. En plus du membre chip, il existe également un membre descs, qui est utilisé pour représenter la broche. Chaque broche a une structure descs, puis il y a un membre gdev dans la structure descs. Nous pouvons trouver la broche basée sur ce gdev membre. À quel contrôleur GPIO appartient la broche.

5 Détails détaillés des appels de fonction du sous-système gpio

L'image ci-dessus montre les détails spécifiques de l'appel de fonction du sous-système gpio. Un de nos contrôleurs gpio correspond à une structure gpio_device, et puis il y a

  • Le membre de base est le numéro de départ de la broche dans ce contrôleur GPIO.
  • ngpio est le nombre de broches,
  • Le membre descs est un tableau de structures, chaque élément qu'il contient est une structure gpio_desc, représentant une épingle.

Ajoutez-en ensuite un dans notre arborescence d'appareils

myled{
        compatible = "cumtchw"
        led-gpios = <&gpio1  10  GPIO_ACTIVE_LOW>        
};

Ensuite, lorsque nous appelons la fonction led_gpio = gpiod_get(&pdev->dev, "led", 0);, alors selon led-gpios = <&gpio1 10 GPIO_ACTIVE_LOW> nous pouvons obtenir le 10ème élément dans le contrôleur gpio1, puis led_gpio Juste pointez sur le 10ème élément du tableau descs, et alors notre desc peut réellement trouver le contrôleur correspondant à cette broche, puis lorsque nous utilisons gpiod_set_value(led_gpio, status); cette fonction pour définir la broche, nous trouverons la puce de contrôle- >set(chip, gpio_chip_hwgpio(desc), value) dans le contrôleur ; le deuxième paramètre de cette fonction fait référence à la broche du contrôleur qui est définie. Le deuxième paramètre ici est obtenu comme ceci

static int __maybe_unused gpio_chip_hwgpio(const struct gpio_desc *desc)
{
    return desc - &desc->gdev->descs[0];
}  这里就是10

6 interface sysfs du sous-système GPIO

Le pilote est `drivers\gpio\gpiolib-sysfs.c`,

6.1 Quels sont les contrôleurs GPIO ?

Tous les contrôleurs GPIO sont répertoriés dans le répertoire /sys/bus/gpio/devices` :

6.2 Détails de chaque contrôleur gpio

 Sous /sys/class/gpio/gpiochipXXX`, il y a ces informations :


base // Numéro GPIO de cette étiquette de périphérique  de contrôleur GPIO
// nom
ngpio // nombre de broches du sous-système
d'alimentation uevent

6.3 Vérifier l'utilisation du GPIO

 cat /sys/kernel/debug/gpio

6.4 Utilisation des GPIO via SYSFS

S'il s'agit simplement d'un simple contrôle de broche (comme une sortie, une valeur d'entrée de requête), vous n'avez pas besoin d'écrire un pilote.

Mais si des interruptions sont impliquées, un pilote doit être écrit.

6.4.1 Déterminer le numéro GPIO

Vérifiez l'étiquette dans chaque répertoire `/sys/class/gpio/gpiochipXXX` pour vous assurer qu'il s'agit bien du contrôleur GPIO que vous souhaitez utiliser, également appelé GPIO Bank.

D'après son nom gpiochipXXX, vous pouvez savoir que la valeur de base est XXX.

La valeur de base plus le décalage de la broche correspond au numéro de la broche.

6.4.2 Exporter, définir la direction, lire et écrire des valeurs

echo 509 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio509/direction
echo 1 > /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport

echo 509 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio509/direction
cat /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport

7 Méthode d'apprentissage Feynman : J'ai donc enregistré une vidéo d'apprentissage expliquant le sous-système gpio

Pilote de 8 LED basé sur le sous-système pinctrl et le sous-système gpio

8.1 Vérifiez le diagramme schématique pour déterminer les broches

Comme le montre le schéma, la LED est connectée à GPIO5_3, et lorsque la broche émet un niveau bas, la LED s'allume.

8.2 Modifier le fichier d'arborescence des appareils

8.2.1 Ajouter des informations pinctrl à l'arborescence des périphériques

Ici, l'outil Pins_Tool_for_i.MX_Processors_v6_x64.exe fourni par imx6ull est utilisé pour configurer les broches et générer des informations sur le nœud pinctrl. Installez Pins_Tool_for_i.MX_Processors_v6_x64.exe, puis ouvrez le fichier de configuration IMX6ULL "MCIMX6Y2xxx08.mex", puis recherchez-y gpio5_3, puis l'outil générera automatiquement des informations de configuration pour nous.

            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;

Mettez ces informations de configuration dans le fichier d'arborescence des périphériques,

        pinctrl_leds: ledgrp {
            fsl,pins = <
                  MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;
        };

8.2.2 Ajouter des informations sur le nœud LED à l'appareil

    myled {
        compatible = "cumtchw,leddrv";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_leds>;
        led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
    };

Ajoutez les informations de nœud ci-dessus sous le nœud racine de l'arborescence des périphériques, où cumtchw est mon nom.

8.3 Écriture du code

8.3.1 Pilote leddrv.c

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>


/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *led_class;
static struct gpio_desc *led_gpio;


/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	//struct inode *inode = file_inode(file);
	//int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	gpiod_set_value(led_gpio, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	//int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	gpiod_direction_output(led_gpio, 0);
	
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 从platform_device获得GPIO
 *    把file_operations结构体告诉内核:注册驱动程序
 */
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	//int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 4.1 设备树中定义有: led-gpios=<...>;	*/
    led_gpio = gpiod_get(&pdev->dev, "led", 0);
	if (IS_ERR(led_gpio)) {
		dev_err(&pdev->dev, "Failed to get GPIO for led\n");
		return PTR_ERR(led_gpio);
	}
    
	/* 4.2 注册file_operations 	*/
	major = register_chrdev(0, "cumtchw_led", &led_drv);  /* /dev/led */

	led_class = class_create(THIS_MODULE, "cumtchw_led_class");
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		gpiod_put(led_gpio);
		return PTR_ERR(led_class);
	}

	device_create(led_class, NULL, MKDEV(major, 0), NULL, "cumtchw_led%d", 0); /* /dev/cumtchw_led0 */
        
    return 0;
    
}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "cumtchw_led");
	gpiod_put(led_gpio);
    
    return 0;
}


static const struct of_device_id ask100_leds[] = {
    { .compatible = "cumtchw,leddrv" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "cumtchw_led",
        .of_match_table = ask100_leds,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init led_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&chip_demo_gpio_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&chip_demo_gpio_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");


8.3.2 Programme de test ledtest.c


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./ledtest /dev/cumtchw_led0 on
 * ./ledtest /dev/cumtchw_led0 off
 */
int main(int argc, char **argv)
{
	int fd;
	char status;
	
	/* 1. 判断参数 */
	if (argc != 3) 
	{
		printf("Usage: %s <dev> <on | off>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 写文件 */
	if (0 == strcmp(argv[2], "on"))
	{
		status = 1;
		write(fd, &status, 1);
	}
	else
	{
		status = 0;
		write(fd, &status, 1);
	}
	
	close(fd);
	
	return 0;
}


8.3.3 Makefile


# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /data/chw/imx6ull_20230512/bsp/100ask_imx6ull-sdk/Linux-4.9.88 # 板子所用内核源码的目录

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o ledtest ledtest.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f ledtest

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o



obj-m += leddrv.o

8.4 Expérience

8.4.1 Compiler les fichiers de l'arborescence des appareils

Exécuter dans le répertoire du noyau 

make dtbs

8.4.2 Compiler le pilote et le programme de test

make all

8.4.3 Remplacer l'arborescence des périphériques, le pilote et le programme de test

cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb /data/chw/imx6ull_20230512/nfs_rootfs/
cp leddrv.ko  ledtest /data/chw/imx6ull_20230512/nfs_rootfs/

Puis exécutez sur la carte de développement

Il y a deux erreurs dans la capture d'écran ci-dessus,

1. Le fichier de l'arborescence des périphériques est copié dans le répertoire de démarrage et non dans le répertoire ~/.

2. La carte de développement doit être redémarrée pour que le nouveau fichier d'arborescence des périphériques soit utilisé.

Après avoir corrigé les deux erreurs ci-dessus, insmod leddrv.ko a signalé l'erreur suivante :

insmod : ERREUR : impossible d'insérer le module leddrv.ko : paramètres invalides

J'ai remplacé à nouveau le fichier de l'arborescence des périphériques, puis j'ai redémarré la carte de développement et j'ai découvert que c'était encore pire.

 

Je suppose que tu aimes

Origine blog.csdn.net/u013171226/article/details/132686757
conseillé
Classement