Linux: IO中断驱动开发教程

本文章详细介绍了,在Linux内核开发IO中断驱动的流程。主要分为四部分:配置设备树DTS、驱动模块编写、配置Linux内核、应用程序测试。

1. 配置设备树DTS

(1)屏蔽原按键设备树IO配置

打开stm32mp15xx-ya157c.dtsi文件(/arch/arm/boot/dts/stm32mp15xx-ya157c.dtsi),屏蔽如下代码。屏蔽下面的button,是为了方便测试。button按下改成了打印我们的消息。

图 1. 屏蔽原按键设备树IO配置 

(2)增加外部中断按键配置

在上一个按键驱动实验的基础上修改即可

gpio_XXXX {
	compatible = "gpio, XXXX";
	status = "okay";
	label = "XXXX_notifier";
	gpio_XXXX-gpios = <&gpioi 11 (GPIO_ACTIVE_LOW )>;
	interrupt-parent = <&gpioi>;
	interrupts = <11 IRQ_TYPE_EDGE_RISING>;
};

编译和更新DTB文件以后,运行arm板卡,可以发现设备文件,在默认目录下产生。

  2. 驱动模块编写

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Driver for XXXX on GPIO line capable of generating interrupt.
 *
 * Copyright 2022 Allen Sun
 */
/*---- Kernel includes ----*/
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>

#include <linux/sched.h>
#include <asm/siginfo.h>
#include <linux/pid_namespace.h>
#include <linux/pid.h>
#include <linux/sched/signal.h>

dev_t dev = 0;
static struct class *dev_class;
static struct cdev cdev;

struct gpio_desc *gpio_XXXX;
static int gpio_XXXX_pin_number = 0;
static int gpio_XXXX_irq_number = 0;

#define SIGETX 44
static struct task_struct *task = NULL;

// application pid, application use ioctl method to set this value
static int app_pid = 0;

typedef enum{
        IOCTL_SET_APP_PID = 0x111,
} MODULE_CMD;

static int gpio_XXXX_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "XXXX_NOTIFIER: gpio_XXXX_open\n");

    return 0;
}

static ssize_t gpio_XXXX_read(struct file *file, char *userbuf, size_t count, loff_t * ppos)
{
    unsigned char temp = gpio_get_value(gpio_XXXX_pin_number);

    if(copy_to_user(userbuf, &temp, 1))
    {
        printk(KERN_ALERT "gpio_XXXX_read copy_to_user failed\n");
        return -EINVAL;
    }

    return count;
}

static long gpio_XXXX_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    printk(KERN_INFO "gpio_XXXX_ioctl:%s cmd=0x%x,arg=0x%lx\n", __func__, cmd, arg);

    if(cmd == IOCTL_SET_APP_PID)
    {
            printk(KERN_INFO "%s, gpio_XXXX_ioctl: owner pid at 0x%lx\n", __func__, arg);

            if(copy_from_user(&app_pid, (int *)arg, sizeof(int)))
            {
                printk(KERN_ALERT "gpio_XXXX_ioctl:copy from user failed\n");
                return -EFAULT;
            }

            printk(KERN_INFO "gpio_XXXX_ioctl: app_pid is %d\n", app_pid);
    }

    return 0;
}

static struct file_operations gpio_XXXX_fops = {
    .owner = THIS_MODULE,
    .read = gpio_XXXX_read,
    .open = gpio_XXXX_open,
    .unlocked_ioctl = gpio_XXXX_ioctl,
    .compat_ioctl = gpio_XXXX_ioctl,
};

// Create the interrupt handler
static irqreturn_t gpio_XXXX_handler(int irq, void * ident)
{
    struct kernel_siginfo info;

    if (app_pid == 0)
    {
        printk(KERN_ALERT "XXXX_NOTIFIER: gpio_XXXX_handler not set user pid\n");
        return IRQ_HANDLED;
    }

    //Sending signal to app
    memset(&info, 0, sizeof(struct kernel_siginfo));
    info.si_signo = SIGETX;
    info.si_code = 0;
    info.si_int = 1234;

    printk(KERN_INFO "gpio_XXXX_handler Interrupt received from GPIO XXXX pin\n");

    rcu_read_lock();
    task = pid_task(find_vpid(app_pid), PIDTYPE_PID);
    rcu_read_unlock();

    if (task == NULL) {
        printk(KERN_ALERT "XXXX_NOTIFIER: get_current failed\n");
        return -EINVAL;
    }
    else
    {
        printk(KERN_INFO "Sending signal to app\n");
        if(send_sig_info(SIGETX, &info, task) < 0) {
            printk(KERN_ALERT "gpio_XXXX_handler Unable to send signal\n");
        }
    }

    return IRQ_HANDLED;
}

static const struct of_device_id gpio_XXXX_of_match[] = {
	// Used to probe, match the DTS file parameter: compatible = "gpio, XXXX";
    { .compatible = "gpio, XXXX", },
    { },
};
MODULE_DEVICE_TABLE(of, gpio_XXXX_of_match);

static int gpio_XXXX_probe(struct platform_device *pdev)
{
    // "gpio_XXXX" match the DTS file parameter name: gpio_XXXX-gpios = <&gpioi 11 (GPIO_ACTIVE_LOW )>;
    gpio_XXXX = devm_gpiod_get(&pdev->dev, "gpio_XXXX", GPIOD_ASIS);

    if (IS_ERR(gpio_XXXX))
    {
        dev_err(&pdev->dev, "failed to get gpio_XXXX GPIO\n");
        return PTR_ERR(gpio_XXXX);
    }

    gpio_XXXX_pin_number = desc_to_gpio(gpio_XXXX);
    gpio_XXXX_irq_number = gpiod_to_irq(gpio_XXXX);

    if(alloc_chrdev_region(&dev, 0, 1, "gpio_XXXX_drv") < 0)
    {
        printk(KERN_ALERT "gpio_XXXX_probe alloc_chrdev_region failed\n");
        return -1;
    }

    printk(KERN_INFO "XXXX_NOTIFIER Major = %d Minor = %d \n", MAJOR(dev), MINOR(dev));

    dev_class = class_create(THIS_MODULE, "gpio_XXXX_chr_class");

    if(dev_class == NULL)
    {
        printk(KERN_ALERT "gpio_XXXX_probe class_create failed\n");
        unregister_chrdev_region(dev, 1);
        return -1;
    }

    if(device_create(dev_class, NULL, dev, NULL, "gpio_XXXX_chr_device") == NULL)
    {
        printk(KERN_ALERT "gpio_XXXX_probe device_create failed\n");
        class_destroy(dev_class);
        unregister_chrdev_region(dev, 1);
        return -1;
    }

    cdev_init(&cdev, &gpio_XXXX_fops);

    if(cdev_add(&cdev, dev, 1) < 0)
    {
        printk(KERN_ALERT "gpio_XXXX_probe cdev_add failed\n");
        device_destroy(dev_class, dev);
        class_destroy(dev_class);
        unregister_chrdev_region(dev, 1);
        return -1;
    }

    if(request_irq(gpio_XXXX_irq_number, gpio_XXXX_handler, IRQF_TRIGGER_RISING, "gpio_XXXX", NULL) != 0)
    {
        printk(KERN_INFO "could not request irq: %d\n", gpio_XXXX_irq_number);
        gpio_free(gpio_XXXX_pin_number);

        return -1;
    }

    printk(KERN_INFO "Waiting for interrupts ... \n");

    return 0;
}

static void gpio_XXXX_shutdown(struct platform_device *pdev)
{
    free_irq(gpio_XXXX_irq_number, THIS_MODULE->name);
    gpio_free(gpio_XXXX_pin_number);
}

static struct platform_driver gpio_XXXX_device_driver = {
    .probe      = gpio_XXXX_probe,
    .shutdown   = gpio_XXXX_shutdown,
    .driver     = {
        .name   = "XXXX_notifier",
        .of_match_table = gpio_XXXX_of_match,
    }
};

static int __init gpio_XXXX_init (void)
{
    printk("XXXX_NOTIFIER: gpio_XXXX_init \n");
    return platform_driver_register(&gpio_XXXX_device_driver);
}

static void __exit gpio_XXXX_exit(void)
{
    platform_driver_unregister(&gpio_XXXX_device_driver);
}

module_init(gpio_XXXX_init);
module_exit(gpio_XXXX_exit);

MODULE_AUTHOR("Allen Sun");
MODULE_DESCRIPTION("GPIO interrupt test module for embedded Linux");
MODULE_LICENSE("GPL");

/*
 * End of file
 */

 Kconfig

# SPDX-License-Identifier: GPL-2.0-only
#
# Input core configuration
#
menuconfig INPUT_XXXX_NOTIFIER
	bool "XXXXNotifier"
	default y
	help
	  Say Y here, and a list of supported IpccNotifier will be displayed.
	  This option doesn't affect the kernel.

	  If unsure, say Y.

if INPUT_XXXX_NOTIFIER

config XXXX_NOTIFIER_GPIO
	tristate "XXXX NOTIFIER GPIO"
	depends on GPIOLIB
	default m
	help
	  This driver implements support for XXXX connected
	  to GPIO pins of various CPUs (and some other chips).

	  Say Y here if your device has XXXX connected
	  directly to such GPIO pins.  Your board-specific
	  setup logic must also provide a platform device,
	  with configuration data saying which GPIOs are used.

	  To compile this driver as a module, choose M here: the
	  module will be called xxxx_notifier.

endif

 Makefile

# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the input core drivers.
#

# Each configuration option enables a list of files.

obj-$(CONFIG_XXXX_NOTIFIER_GPIO)		+= xxxx_notifier.o

3. 配置Linux内核

 make menuconfig:设置编译为模块。M:生成.ko Y:编译进内核,不生成.ko文件

 将编译好的驱动文件和内核拷贝到arm开发板上面。重启运行,会看到/dev目录下面有对应的设备节点产生。

查看中断信号:

uas@localhost:~/bin$ cat /proc/interrupts

​​​​​​​

 4. 应用程序测试

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/gpio.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>

#define SIGETX 44
int check = 0;

typedef enum{
        IOCTL_SET_APP_PID = 0x111,
} APP_CMD;

void sig_event_handler(int n, siginfo_t *info, void *unused)
{
    printf ("sig_event_handler Received signal from kernel");

    if (n == SIGETX) {
        check = info->si_int;
        printf ("Received signal from kernel : Value =  %u\n", check);
    }
}

int main(int argc, char **argv)
{
	int fd = -1;
	int count = 1;
	int app_pid;
	unsigned char pin_level;
	struct sigaction act;

	/* install custom signal handler */
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_NODEFER;
	act.sa_sigaction = sig_event_handler;
	sigaction(SIGETX, &act, NULL);

	printf("Installed signal handler for SIGETX = %d\n", SIGETX);

	fd = open("/dev/gpio_XXXX_chr_device", O_RDWR);

	if (fd == -1) 
	{
		perror("XXXX GPIO Open\n");
		return -1;
	}

	count = read(fd, &pin_level, 1);
	
	if(count == -1)
	{
		perror("XXXX GPIO read\n");
	}
	else
	{
		printf("XXXX GPIO level is %d\n", pin_level);
	}

	app_pid = getpid();
	ioctl(fd, IOCTL_SET_APP_PID, &app_pid);

	while(1)
	{
		printf("XXXX notifier heartbeat..\n");
		sleep(10);
	}

	if (close(fd) == -1)
	{
		perror("Failed to close GPIO character device file");
		return -1;		
	}

	return 0;
}

测试结果:按下用户按钮,app对应的中断处理函数就会执行。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AllenSun-1990

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值