欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux驱动之中断实验(基于设备树编程)

程序员文章站 2022-07-13 21:48:36
...

概述

Linux内核提供了完善的中断框架,我们只需要申请中断,然后注册中断处理函数即可,使用非常方便,不需要像单片机那样进行复杂的寄存器配置。

基础知识

(一) 中断号
每个中断都有一个中断号,通过中断号区分不同的中断。中断号可以在芯片datasheet中查找到,比如GPIO的中断号,一般会在GPIO章节或者interrupt章节会有介绍, 搜索” Interrupt Sources”,或许你就能找到。
Linux驱动之中断实验(基于设备树编程)
内部中断(定时器,IIC,串口等),有固定独立的中断号。
外部中断(GPIO),可能多个中断共用一个中断号,如PIOA的中断号6.
当然,也可以在SOC对应的设备树中找到中断号,例如sama5d34ek.dts开发板对应的SOC设备树sama5d3.dtsi中,可以看到GPIOA的中断号为6,与datasheet一致
Linux驱动之中断实验(基于设备树编程)
(二)request_irq、free_irq函数

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * devname, void * dev_id)
功能:request_irq向内核注册中断处理函数。

irq:中断号,中断号不能直接传硬件中断号,得从设备树获取,获取时核心层函数做了映射将中断号数组中对应的索引作为此函数的参数。可以通过irq_of_parse_and_map函数从设备树中获取按键IO对应的中断号,也可以使用gpio_to_irq将某个IO口设置为中断状态,并且返回其中断号。
handler:向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它
flags:中断标记。
IRQF_TRIGGER_RISING:上升沿触发
IRQF_TRIGGER_FALLING:下降沿触发
IRQF_TRIGGER_HIGH:高电平触发
IRQF_TRIGGER_LOW:低电平触发
IRQF_SAMPLE_RANDOM:为系统随机发生器提供支持
IRQF_SHARED:中断可在设备间共享
IRQF_DISABLED:是否快速中断

devname:设置中断名称,通常是设备驱动程序的名称 在cat /proc/interrupts中可以看到此名称
dev_id:中断共享时会用到,一般设置为这个设备的设备结构体或者NULL

void free_irq(unsigned int irq, void *dev_id)
功能:卸载中断处理函数。

irq:中断号,与request_irq中的irq一致,用于定位action链表;
dev_id:用于在action链表中找到要卸载的表项;同一个中断的不同中断处理函数必须使用不同的dev_id来区分,这就要求在注册共享中断时参数dev_id必须唯一。

程序实现

(一)增加设备树节点key。

key {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "atkalpha-key";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_key>;
        key-gpio = <&pioC 0 GPIO_ACTIVE_LOW>;      /* PC0*/
        interrupt-parent = <&pioC>;/*设置中断控制器,使用pioC作为中断控制器*/
        interrupts = <0 IRQ_TYPE_EDGE_BOTH>;/*中断信息,0表示GPIOC0,IRQ_TYPE_EDGE_BOTH双边沿触发,在linux/irq.h中有定义*/
        status = "okay";
}; 

编写完成过后,执行“make dtbs”重新编译设备树
(二) 编写驱动

#include <linux/fs.h>        /*包含file_operation结构体*/
#include <linux/init.h>      /* 包含module_init module_exit */
#include <linux/module.h>    /* 包含LICENSE的宏 */
#include <linux/miscdevice.h>/*包含miscdevice结构体*/
#include <linux/kernel.h>    /*包含printk等操作函数*/
#include <asm/uaccess.h>     /*包含copy_to_user操作函数*/
#include <linux/interrupt.h> /*包含request_irq操作函数*/
#include <linux/of.h>        /*设备树操作相关的函数*/
#include <linux/of_gpio.h>   /*of_get_named_gpio等函数*/
#include <linux/of_irq.h>
#include <linux/irq.h>

/* 中断IO描述结构体 */
struct irq_keydesc {
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
	struct device_node	*nd; /* 设备节点 */
};

struct irq_keydesc irqKeyDesc;	/* irq设备 */

/*按键中断处理函数*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	printk("key interrupt\r\n");
	return 0;
}

static int keyio_init(void)
{
	int res = 0;
	irqKeyDesc.nd = of_find_node_by_path("/key");
	if (irqKeyDesc.nd== NULL){
		printk("key node not find!\r\n");
		return -EINVAL;
	} 
	/* 提取GPIO */
	irqKeyDesc.gpio = of_get_named_gpio(irqKeyDesc.nd ,"key-gpio", 0);
	if (irqKeyDesc.gpio < 0) {
		printk("can't get key0\r\n");
		return -EFAULT;
	}
	
	/* 初始化key所使用的IO,并且设置成中断模式 */
	sprintf(irqKeyDesc.name, "KEY0");		/* 组合名字 */
	gpio_request(irqKeyDesc.gpio, irqKeyDesc.name);
	gpio_direction_input(irqKeyDesc.gpio);	
	irqKeyDesc.irqnum = irq_of_parse_and_map(irqKeyDesc.nd, 0);
	printk("key0:gpio=%d, irqnum=%d\r\n", irqKeyDesc.gpio,irqKeyDesc.irqnum);
	/* 申请中断 */
	irqKeyDesc.handler = key0_handler;
	res = request_irq(irqKeyDesc.irqnum, irqKeyDesc.handler, 
					 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, irqKeyDesc.name, NULL);
	if(res < 0){
		printk("irq %d request failed!\r\n", irqKeyDesc.irqnum);
		return -EFAULT;
	}
	return 0;
}

/* 定义一个打开设备的,read函数 */
ssize_t key_read(struct file *file, char __user *array, size_t size, loff_t *ppos)
{
	return -1;
}

/*字符设备驱动程序就是为具体硬件的file_operations结构编写各个函数*/
static const struct file_operations key_ctl={
         .owner          = THIS_MODULE,
		 .read           = key_read,
};

/*杂项设备,主设备号为10的字符设备,相对普通字符设备,使用更简单*/
static struct miscdevice key_miscdev = {
         .minor          = 255,
         .name           = "key_read",
         .fops           = &key_ctl,
};

static int __init key_init(void)
{
	/* 1、构建设备号 */
	char res;
	/*注册杂项设备驱动*/
	res = misc_register(&key_miscdev);
	printk(KERN_ALERT"key_init %d\n",res);
	
	keyio_init();
	return 0;
}

static void __exit key_exit(void)
{
	/* 释放中断 */
	free_irq(irqKeyDesc.irqnum, NULL);
	/*释放杂项设备*/
	misc_deregister(&key_miscdev);
	printk(KERN_ALERT"key_exit\r\n");
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("boyee");
MODULE_DESCRIPTION("key interrupt");

(三) 运行测试
Linux驱动之中断实验(基于设备树编程)