本文章详细介绍了,在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对应的中断处理函数就会执行。