【Linux驱动】将GPIO配置为输入,并注册为中断管脚,触发中断后通知应用层

9 篇文章 0 订阅
9 篇文章 1 订阅

1、driver

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/siginfo.h>
#include <linux/pid.h>
#include <linux/uaccess.h>
#include <linux/sched/signal.h>
#include <linux/pid_namespace.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/miscdevice.h>
#include <linux/atomic.h>
#include <linux/delay.h>
#include <dt-bindings/gpio/meson-t7-gpio.h>

// 设备名称
#define MYDEV_NAME			"ef1e_irq"

#define RFDM_IRQ_GPIO				467 //GPIOT_21
#define DHU_WAKEUP_IRQ_GPIO			493 //GPIOY_9

#define SET_PID						100
#define GET_DHU_WAKEUP_GPIO_VAL		200

// 设备类
static struct class *my_class;

// 用来保存设备
struct cdev my_cdev;

// 用来保存设备号
int mydev_major = 0;
int mydev_minor = 0;

// 中断号
int virq1 = 0;
int virq2 = 0;

// 用来保存向谁发送信号,应用程序通过 ioctl 把自己的进程 ID 设置进来。
static int g_pid = 0;

// 用来发送信号给应用程序
static void send_signal(int sig_no, int sig_code)
{
	int ret;
	struct siginfo info;
	struct task_struct *my_task = NULL;
	
	if (0 == g_pid)
	{
		// 说明应用程序没有设置自己的 PID
	    printk("pid[%d] is not valid!\n", g_pid);
	    return;
	}

	printk("send signal %d code %d to pid %d.\n", sig_no, sig_code, g_pid);

	// 构造信号结构体
	memset(&info, 0, sizeof(struct siginfo));
	info.si_signo = sig_no;
	info.si_errno = 0;
	info.si_code  = sig_code;

	// 获取自己的任务信息,使用的是 RCU 锁
	rcu_read_lock();
	my_task = pid_task(find_vpid(g_pid), PIDTYPE_PID);
	rcu_read_unlock();

	if (my_task == NULL)
	{
	    printk("get pid_task failed!\n");
	    return;
	}

	// 发送信号
	ret = send_sig_info(sig_no, (struct kernel_siginfo *)&info, my_task);
	if (ret < 0) 
	{
	       printk("send signal failed!\n");
	}

	printk("send signal succeed!\n");
	
}

//中断处理函数
static irqreturn_t rfdm_irq_handler(int irq, void * dev)
{	
	send_signal(SIGIO, 1);

	printk("rfdm_irq_handler send SIGIO 1.\n");

	return IRQ_HANDLED;
}

static irqreturn_t dhu_wakeup_irq_handler(int irq, void * dev)
{	
	send_signal(SIGIO, 2);

	printk("dhu_wakeup_irq_handler send SIGIO 2.\n");

	return IRQ_HANDLED;
}


// 当应用程序打开设备的时候被调用
static int mydev_open(struct inode *inode, struct file *file)
{
	
	printk("mydev_open is called.\n");
	return 0;
}

static long mydev_ioctl(struct file* file, unsigned int cmd, unsigned long arg)
{
	void __user *pArg;
	int val = -1;
	
	printk("mydev_ioctl is called. cmd = %d\n", cmd);
	
	if (SET_PID == cmd) // 设置进程号
	{
		pArg = (void *)arg;
		if (!access_ok(pArg, sizeof(int)))
		{
		    printk("access failed!\n");
		    return -EACCES;
		}

		// 把用户空间的数据复制到内核空间
		if (copy_from_user(&g_pid, pArg, sizeof(int)))
		{
		    printk("copy_from_user failed!\n");
		    return -EFAULT;
		}

		printk("mydev_ioctl pid = %d.\n", g_pid);
	}

	if(GET_DHU_WAKEUP_GPIO_VAL == cmd) // 读 DHU_WAKEUP_IRQ_GPIO 的值
	{
		val = gpio_get_value(DHU_WAKEUP_IRQ_GPIO);

		pArg = (void *)arg;
		if (!access_ok(pArg, sizeof(int)))
		{
		    printk("access failed!\n");
		    return -EACCES;
		}

		// 把内核空间的数据复制到用户空间
		if (copy_to_user(pArg, &val, sizeof(int)))
		{
		    printk("copy_to_user failed!\n");
		    return -EFAULT;
		}

		printk("mydev_ioctl val = %d.\n", val);
		
	}
	
	return 0;
}

static const struct file_operations mydev_ops={
	.owner = THIS_MODULE,
	.open  = mydev_open,
	//.unlocked_ioctl = mydev_ioctl
	.compat_ioctl = mydev_ioctl
};

static int __init mydev_driver_init(void)
{
	int devno, ret;
	dev_t num_dev;

	printk("mydev_driver_init is called. \n");
	
	gpio_direction_input(RFDM_IRQ_GPIO);
	printk("RFDM_IRQ_GPIO(GPIOT_21)      = %d\n", gpio_get_value(RFDM_IRQ_GPIO));
	gpio_direction_input(DHU_WAKEUP_IRQ_GPIO);
	printk("DHU_WAKEUP_IRQ_GPIO(GPIOY_9) = %d\n", gpio_get_value(DHU_WAKEUP_IRQ_GPIO));


	/*---------------- 注册RFDM irq -------------------*/
	ret = gpio_request(RFDM_IRQ_GPIO, "rfdm_irq");
	if (ret != 0) {
		pr_err("RFDM-IRQ: gpio_request failed! ret=%d\n", ret);
		return ret;
	}
	virq1 = gpio_to_irq(RFDM_IRQ_GPIO);
	if (virq1 < 0) {
		pr_err("RFDM-IRQ: gpio_to_irq failed! virq=%d\n", virq1);
		return -EINVAL;
	}
	ret = request_irq(virq1, rfdm_irq_handler, IRQF_TRIGGER_FALLING, "RFDM_IRQ_0", NULL);
	if (ret < 0) {
		pr_err("RFDM-IRQ: request_irq failed! ret=%d\n", ret);
		return -EINVAL;
	}
	pr_info("RFDM-IRQ: request_irq succeed!\n");

	/*---------------- 注册DHU wakeup irq -------------------*/
	ret = gpio_request(DHU_WAKEUP_IRQ_GPIO, "dhu_wakeup_irq");
	if (ret != 0) {
		pr_err("DHU-WAKEUP-IRQ: gpio_request failed! ret=%d\n", ret);
		return ret;
	}
	virq2 = gpio_to_irq(DHU_WAKEUP_IRQ_GPIO);
	if (virq2 < 0) {
		pr_err("DHU-WAKEUP-IRQ: gpio_to_irq failed! virq=%d\n", virq2);
		return -EINVAL;
	}
	ret = request_irq(virq2, dhu_wakeup_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "DHU_WAKEUP_IRQ_0", NULL);
	if (ret < 0) {
		pr_err("DHU-WAKEUP-IRQ: request_irq failed! ret=%d\n", ret);
		return -EINVAL;
	}
	pr_info("DHU-WAKEUP-IRQ: request_irq succeed!\n");
	

	// 动态申请设备号(严谨点的话,应该检查函数返回值)
	alloc_chrdev_region(&num_dev, mydev_minor, 1, MYDEV_NAME);

	// 获取主设备号
	mydev_major = MAJOR(num_dev);
	printk("mydev_major = %d. \n", mydev_major);

	// 创建设备类
	my_class = class_create(THIS_MODULE, MYDEV_NAME);

	// 创建设备节点
	devno = MKDEV(mydev_major, mydev_minor);
	
	// 初始化cdev结构
	cdev_init(&my_cdev, &mydev_ops);

	// 注册字符设备
	cdev_add(&my_cdev, devno, 1);

	// 创建设备节点
	device_create(my_class, NULL, devno, NULL, MYDEV_NAME);
	
	printk("mydev_driver_init done and succeed.\n");

	return 0;
}

static void __exit mydev_driver_exit(void)
{	
	printk("mydev_driver_exit is called. \n");

	// 删除设备节点
	cdev_del(&my_cdev);
	
	device_destroy(my_class, MKDEV(mydev_major, mydev_minor));

	// 释放设备类
	class_destroy(my_class);

	// 注销设备号
	unregister_chrdev_region(MKDEV(mydev_major, mydev_minor), 1);

	// 注销中断处理函数
	//free_irq(virq1, &mydev);
	//free_irq(virq2, &mydev);
	
}

MODULE_LICENSE("GPL");
module_init(mydev_driver_init);
module_exit(mydev_driver_exit);

2、app

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <errno.h>


char *dev_name = "/dev/ef1e_irq";

// 信号处理函数
static void signal_handler(int signum, siginfo_t *info, void *context)
{
	// 打印接收到的信号值
    printf("signal_handler: signum = %d \n", signum);
    printf("signo = %d, code = %d, errno = %d \n",
	         info->si_signo,
	         info->si_code, 
	         info->si_errno);
}

int main(int argc, char *argv[])
{
	int fd, count = 0;
	int pid = getpid();
	int val = -1;
	int ret;

	// 打开GPIO
	if((fd = open(dev_name, O_RDWR | O_NDELAY)) < 0){
		printf("open dev failed! \n");
		return -1;
	}

	printf("open dev success! \n");
	
	// 注册信号处理函数
	struct sigaction sa;
	sigemptyset(&sa.sa_mask);
	sa.sa_sigaction = &signal_handler;
	sa.sa_flags = SA_SIGINFO;
	
	sigaction(SIGIO, &sa, NULL);

	// set PID 
	printf("call ioctl 100. pid = %d \n", pid);
	ret = ioctl(fd, 100, &pid);
	if(ret != 0)
		printf("call ioctl 100 failed. errno = %d \n", errno);
	
	ret = ioctl(fd, 200, &val);
	printf("call ioctl 200. val = %d \n", val);
	if(ret != 0)
		printf("call ioctl 200 failed. errno = %d \n", errno);
	

	// 休眠1秒,等待接收信号
	while (1)
		sleep(1);

	// 关闭设备
	close(fd);
}

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Linux GPIO子系统是一个用于控制嵌入式系统通用输入/输出(GPIO)的软件子系统。它提供了一种标准的接口,使得应用程序可以通过文件系统接口来访问GPIO。这个子系统可以用于控制各种设备,例如LED、按钮、传感器等等。如果你需要更多的信息,可以查看Linux内核文档。 ### 回答2: Linux GPIO子系统是一种用于管理通用输入输出(GPIO)引脚的软件层。GPIO引脚是一种通用可编程引脚,可以在嵌入式系统用来通过读取输入或设置输出与外部设备进行通信。 Linux GPIO子系统负责将底层硬件 GPIO 引脚的操作抽象为文件系统的接口,使开发者可以通过读写文件的方式来访问和控制 GPIO 引脚。通过该子系统,可以实现对 GPIO 引脚的配置、读取和写入等操作,以满足不同应用下对 GPIO 的需求。 Linux GPIO子系统的核心是GPIO驱动程序,它与底层硬件层进行交互,完成对GPIO引脚的操作。驱动程序将GPIO引脚映射到内存,通过读写该内存地址即可对引脚进行操作。用户通过访问特定目录下的文件来和引脚进行交互,例如将引脚配置输入模式、输出模式,以及读取或写入引脚的状态。 通过Linux GPIO子系统,开发者可以方便地进行GPIO引脚的控制。可以根据不同的应用需求,灵活配置引脚的输入输出模式,监听引脚上的状态变化,并根据需要对其他外设进行控制。 总之,Linux GPIO子系统为开发者提供了便捷的接口,使得在嵌入式系统使用GPIO引脚更加简单和灵活。它允许开发者通过读写文件的方式访问和控制GPIO引脚,满足各种不同嵌入式应用GPIO的需求。 ### 回答3: LinuxGPIO(General Purpose Input/Output)子系统是通过软件对硬件上的通用输入/输出引脚进行控制的一种机制。它使得开发者可以利用这些GPIO引脚实现各种功能,比如控制LED灯、读取外部传感器的数据等。 LinuxGPIO子系统提供了许多功能和接口来管理和操作GPIO。首先,它使用sysfs文件系统来组织GPIO资源的目录树,并通过文件的方式来读取和写入GPIO的状态。在/sys/class/gpio目录下,每个GPIO引脚都会有一个对应的目录,在该目录的文件可以用于配置GPIO的方向(输入或输出)、读取和写入GPIO的电平状态。开发者可以使用命令行工具或者编程语言(如Python、C等)来操作这些文件,从而控制GPIO引脚的行为。 其次,LinuxGPIO子系统还提供了设备树(Device Tree)来描述硬件平台上的GPIO资源。设备树是一种描述硬件的数据结构,在启动时通过设备树绑定机制将设备树定义的GPIO资源与内核驱动程序关联起来。这样,开发者就可以通过调用相应的驱动程序来控制GPIO引脚,而不需要手动操作sysfs文件系统。 此外,LinuxGPIO子系统还支持中断机制,可以让GPIO引脚在特定事件发生时触发中断。通过注册中断处理函数,开发者可以实现对GPIO输入信号的快速响应,提高系统的实时性。 总之,LinuxGPIO子系统为开发者提供了一种方便且灵活的方式来控制硬件上的GPIO引脚。通过sysfs文件系统或设备树,开发者可以轻松地配置、读取和控制GPIO的状态,从而实现各种功能和应用

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值