本文章详细介绍了,在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
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #include
- #include
- #include
- #include
- #include
-
- 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目录下面有对应的设备节点产生。
4. 应用程序测试
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #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对应的中断处理函数就会执行。