嵌入式Linux中断驱动

用过STM32的大概都知道,基本每个GPIO管脚都支持中断模式,这样在检测外部插入一个硬件设备时,通过GPIO管脚电平中断就非常方便。那么AM3354的片子是否支持GPIO管脚电平中断呢?答案是肯定的,下面直接上源码解析:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <mach/irqs.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <asm/signal.h>

#define GPIO_TO_PIN(bank,gpio) ((32*(bank)) + (gpio))

#define IRQ_GPIO_TEST GPIO_TO_PIN(1,19)      //测试中断管脚
#define LED_GPIO_TEST GPIO_TO_PIN(1,20)     //LED灯管脚

#define NAME "gpio"                                             //驱动名称

int irq_num = 0;
static int major = 231;                                         //驱动主设备号

static irqreturn_t irq_proc(int irq,void *dev_id)   //中断处理程序
{
	printk("gpio interupt\n");
	gpio_set_value(LED_GPIO_TEST,1);	
	return IRQ_HANDLED;
}

int gpio_init(void)    //管脚的初始化
{
    // 检测该管脚是否被占用;如同占用直接返回错误;未被占用直接注册管脚
    // 第一个参数是管脚编号;第二个参数是其取一个名字
	int result = gpio_request(LED_GPIO_TEST,"led");  
	if(result != 0){
	    printk("gpio_request(LED_GPIO_TEST,led) failed!\n");	
	    return -1;
	}
			
	result = gpio_request(IRQ_GPIO_TEST,"irqtest");
	if(result != 0){
	    printk("gpio_request(IRQ_GPIO_TEST,irqtest) failed!\n");		
	    return -1;
	}
	
	// 设置管脚方向为输出,并拉低电平值
	result = gpio_direction_output(LED_GPIO_TEST,0); 
	if(result != 0){
	    printk("gpio_direction_output(LED_GPIO_TEST,0) failed\n");
	    return -1;
	}
		
	// 设置管脚方向为输入
	result = gpio_direction_input(IRQ_GPIO_TEST);
	if(result != 0){
	    printk("gpio_direction_input(IRQ_GPIO_TEST) failed\n");
	    return -1;
	}
	
	//获取中断号
	irq_num = gpio_to_irq(IRQ_GPIO_TEST);
	//通过中断号注册中断
	//第一个参数:申请的硬件中断号
	///第二个参数:向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它
	///第三个参数:IRQF_TRIGGER_RISING:上升沿触发
 				  IRQF_TRIGGER_FALLING:下降沿触发
				  IRQF_TRIGGER_HIGH:高电平触发
 				  IRQF_TRIGGER_LOW:低电平触发
 				  IRQF_SAMPLE_RANDOM:为系统随机发生器提供支持
 				  IRQF_SHARED:中断可在设备间共享
 				  IRQF_DISABLED:是否快速中断
	///第四个参数:设置中断名称,通常是设备驱动程序的名称  在cat /proc/interrupts中可以看到此名称
	///第五个参数:中断共享时会用到,一般设置为这个设备的设备结构体或者NULL
	
	result = request_irq(irq_num, irq_proc, IRQF_TRIGGER_LOW, "gpio_irq",NULL);
	printk("result === %d  irq_num == %d\n", result, irq_num);
	
	//使能中断号
	enable_irq(irq_num);
	
	return 0;
}

struct gpio_dev
{
	struct cdev cdev;
	unsigned char value;
};
struct gpio_dev *gpio_devp;

int gpio_release(struct inode *inode,struct file *filp)
{
	gpio_free(LED_GPIO_TEST);
	free_irq(irq_num,NULL);
	return 0;
}

int gpio_open(struct inode *inode,struct file *filp)
{
	struct gpio_dev *dev;
	dev = container_of(inode->i_cdev,struct gpio_dev,cdev);
	filp->private_data = dev;
	
	return 0;
}

long gpio_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
	struct gpio_dev *dev = filp->private_data;
	int ret=0;
	dev->value = arg;
	printk("cmd === %d\n",cmd);
	switch(cmd)
	{
		case 1:
			gpio_set_value(LED_GPIO_TEST,1);	
			break;
		case 2:
			gpio_set_value(LED_GPIO_TEST,0);	
			break;
		default:
		{	
			ret = -1;
			break;	
		}					
	}
	return ret;
}

// 添加file_operations 结构体,这个是字符设备驱动的核心结构,所有的应用层调用的函数最终都会调用这个结构下面定义的函数
struct file_operations gpio_fops =
{
	.owner = THIS_MODULE,
	.unlocked_ioctl = gpio_ioctl,
	.open = gpio_open,
	.release = gpio_release,
};

// __init 宏最常用的地方是驱动模块初始化函数的定义处,其目的是将驱动模块的初始化函数放入名叫.init.text的输入段。
// 当内核启动完毕后,这个段中的内存会被释放掉供其他使用。
// 执行insmod命令时就会调用这个函数 
static int __init gpio_start(void)
{
	int ret;
	gpio_init();
	printk(KERN_ALERT "gpio modules is install\n");
	// 注册字符设备,major参数如果等于0,则表示采用系统动态分配的主设备号
	// 
	ret = register_chrdev(major,NAME,&gpio_fops);
	if(ret < 0)
	{
		printk("unable to register gpio driver!\n");
		return -1;
	}
	printk("able to register gpio driver!\n");
	return 0;
}

// __exit,模块直接编译进内核或者不允许卸载,被标志为__exit的函数会被自动丢弃掉。
// 执行rmmod命令时就会调用这个函数 
static void __exit gpio_cleanup(void)
{
    gpio_free(LED_GPIO_TEST);
	free_irq(irq_num,NULL);
	// 注销字符设备
	unregister_chrdev(major,NAME);
}
module_init(gpio_start);    //指定初始化函数
module_exit(gpio_cleanup); 	//指定清除函数

MODULE_AUTHOR("chenhao");   //指定作者
MODULE_LICENSE("GPL");  	//指定代码使用的许可证
MODULE_VERSION("1.0");	    //指定代码修订号

注意,在AM3354中不是所有的管脚都支持中断,这个需要自己去查询;系统已经使用过的管脚无法重复使用;在Linux环境下不是编译好的驱动并查看结果,如下图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但此时在【/dev】目录下并未发现gpio的字符设备,经查阅资料得知,在【insmod】加载驱动后需先执行【mknod】命令才能够在【/dev】目录下出现该字符设备,具体命令如下:

mknod /dev/gpio c 231 1

mknod是创建设备文件,但在创建设备文件之前,一定要先写驱动程序。
1.在驱动程序中是要注册你自己的设备的,通过register将主次设备号注册进一个结构体中。
2.通过mknod命令创建的设备节点:是在/dev目录下创建相应的设备只是为了应用程序去使用它提供了途径,它们之间是通过设备号联系在一起的,应用程序触发中断后系统会去第一步中的那个结构体中寻找对应的设备进行操作。或者通过在第一步中使用一个classdev结构体【字符设备中】创建一个类,让系统自动为你创建设备节点。

运行命令成功后在【/dev】目录下发现该字符设备,如下图:
在这里插入图片描述