LINUX中斷--申請中斷和解除安裝中斷

duhui75發表於2020-10-23

inline int __must_check  request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)

unsigned int  irq:為要註冊中斷服務函式的中斷號,定義在mach/irqs.h

irq_handler_t  handler:為要註冊的中斷服務函式

unsigned long  irqflags: 觸發中斷的引數,比如邊沿觸發, 定義在linux/interrupt.h。         

const char  *devname:中斷程式的名字,使用cat /proc/interrupt 可以檢視中斷程式名字

void  *dev_id:傳入中斷處理程式的引數,註冊共享中斷時不能為NULL,因為解除安裝時需要這個做引數,避免解除安裝其它中斷服務函式

對於共享中斷,就是幾個中斷共享一箇中斷函式的情況。request_irq  函式最後一個引數void  *dev_id不能為NULL,中斷函式用這個引數來區分是哪個中斷在執行。

void free_irq(unsigned int irq, void *dev_id)

解除安裝中斷的時候,這個void  *dev_id 也不能為NULL,系統通過這個引數來判斷具體解除安裝哪一個中斷。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h> /*delay*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>	   /*kmalloc*/
#include <linux/vmalloc.h> /*vmalloc*/
#include <linux/types.h>   /*ssize_t*/
#include <linux/fs.h>	   /*file_operaiotns*/
#include <linux/gpio_keys.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm-generic/ioctl.h>
#include <asm-generic/errno-base.h>
#include <mach/iomux-mx6dl.h>

/**主裝置號和次裝置號**/
int device_major = 0;
int device_minor = 0;
static struct class *gpio_class;	/*在/sys目錄創造一個類*/
static struct cdev *gpio_class_dev; /*在這個類下,創造一個裝置節點*/

#define SABRESD_POWER_OFF IMX_GPIO_NR(3, 29) //MX6DL_PAD_EIM_D29__GPIO_3_29
#define SABRESD_VOLUME_UP IMX_GPIO_NR(1, 4)	 //MX6DL_PAD_GPIO_4__GPIO_1_4
#define SABRESD_VOLUME_DN IMX_GPIO_NR(1, 5)	 //MX6DL_PAD_GPIO_5__GPIO_1_5
//引腳描述符
struct pin_desc
{
	int pin;
	unsigned long long pinconfig;
	int intnumber;
	int key_val;
};

//引腳描述符,按引腳的不同定義不同的值,在中斷中進行處理
struct pin_desc pin_desc[3] =
	{
		{SABRESD_POWER_OFF, MX6DL_PAD_EIM_D29__GPIO_3_29, gpio_to_irq(SABRESD_POWER_OFF), 0x1},
		{SABRESD_VOLUME_UP, MX6DL_PAD_GPIO_4__GPIO_1_4, gpio_to_irq(SABRESD_VOLUME_UP), 0x2},
		{SABRESD_VOLUME_DN, MX6DL_PAD_GPIO_5__GPIO_1_5, gpio_to_irq(SABRESD_VOLUME_DN), 0x3},
};

static DECLARE_WAIT_QUEUE_HEAD(Key_Status_Read_Wait);
static int wait_flag = 0;

/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04 */
/* 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84 */
/* 主要是為了區分是按下還是鬆開 */
static unsigned char key_val = 0;
irqreturn_t button_irq(int irq, void *devid)
{

	int pin_val;
	struct pin_desc *pin_readed;

	pin_readed = (struct pin_desc *)devid;
	/* 1 獲取當前引腳的電平值 */
	pin_val = gpio_get_value(pin_readed->pin);
	/* 2 根據電平值判斷當前是按下還是鬆開 */
	if (pin_val) /* 鬆開為高電平,返回0x8x */
	{
		key_val = 0x80 | pin_readed->key_val;
	}
	else /* 按下為低電平,返回0x0x */
	{
		//把當前按鍵的鍵值給一個全域性靜態變數,在read函式裡給使用者
		key_val = pin_readed->key_val;
	}

	printk("%02x ", key_val);

	wake_up(&Key_Status_Read_Wait);
	wait_flag = 1;
	/* 3 標記中斷已經觸發 */
	return IRQ_RETVAL(IRQ_HANDLED);
}
/************************************************************************
中斷程式的返回值是一個特殊型別—irqreturn_t。
但是中斷程式的返回值卻只有兩個—IRQ_NONE和IRQ_HANDLED。
* IRQ_NONE means we didn't handle it.

* 中斷程式接收到中斷訊號後發現這並不是註冊時指定的中斷原發出的中斷訊號.

* IRQ_HANDLED means that we did have a valid interrupt and handled it.

* 接收到了準確的中斷訊號,並且作了相應正確的處理

也可以使用巨集IRQ_RETVAL(x),如果x非0值,那麼該巨集返回IRQ_HANDLED,否則,返回IRQ_NONE.
*************************************************************************/

/*open函式的實現*/
static int gpio_open(struct inode *inode, struct file *file)
{

	printk(KERN_ALERT "OPEN\n");
	return 0;
}

static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	printk(KERN_ALERT "ioctl\n");
	return 0;
}

/*release函式的實現*/
static int gpio_close(struct inode *inode, struct file *file)
{

	printk(KERN_ALERT "close\n");
	return 0;
}

ssize_t gpio_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{

	printk(KERN_ALERT "read\n");

	/* 將在中斷中儲存的資料返回給使用者,並將標誌位清零,表示已無資料 */
	int ret;

	wait_event_interruptible(Key_Status_Read_Wait, wait_flag);
	wait_flag = 0;
	//拷貝資料到使用者空間
	ret = copy_to_user(buf, &key_val, sizeof(key_val));
	key_val = 0;

	if (ret)
	{
		return 1;
	}
	//讀取資料完畢後需要將標誌位清零,表示暫時無資料可讀

	return 0;
}

/*具體的檔案操作集合*/
static const struct file_operations gpio_fops =
	{
		/*這是擁有者*/
		.owner = THIS_MODULE,
		.open = gpio_open,
		.unlocked_ioctl = gpio_ioctl,
		.release = gpio_close,
		.read = gpio_read,
};

/* 3.1 實現button_open函式 */
static int Button_Initial(void)
{
	/* 根據4個引腳的中斷號,註冊4個外部中斷(用來感知不同的引腳),並實現中斷處理函式 */
	int ret;

#if 1
	gpio_free(pin_desc[0].pin);
	mxc_iomux_v3_setup_pad(pin_desc[0].pinconfig);

	ret = gpio_request(pin_desc[0].pin, "S1");
	if (ret == 0)
	{
		printk("gpio_request S1 success\n");
	}
	else
	{
		printk("gpio_request S1 fail\n");
	}

	ret = gpio_direction_input(pin_desc[0].pin);
	if (ret == 0)
	{
		printk("gpio_direction_input S1 success\n");
	}
	else
	{
		printk("gpio_direction_input S1 fail\n");
	}

	ret = request_irq(pin_desc[0].intnumber, button_irq, IRQ_TYPE_EDGE_FALLING, "S1",
					  (void *)&pin_desc[0]);
	if (ret)
	{
		printk("request_irq S1 error\n");
		goto errout;
	}
	else
	{
		printk("request_irq S1 =%d\n", pin_desc[0].intnumber);
	}
#endif

	gpio_free(pin_desc[1].pin);
	mxc_iomux_v3_setup_pad(pin_desc[1].pinconfig);
	ret = gpio_request(pin_desc[1].pin, "S2");
	if (ret == 0)
	{
		printk("gpio_request S2 success\n");
	}
	else
	{
		printk("gpio_request S2 fail\n");
	}

	ret = gpio_direction_input(pin_desc[1].pin);
	if (ret == 0)
	{
		printk("gpio_direction_input S2 success\n");
	}
	else
	{
		printk("gpio_direction_input S2 fail\n");
	}

	ret = request_irq(pin_desc[1].intnumber, button_irq, IRQ_TYPE_EDGE_FALLING, "S2",
					  (void *)&pin_desc[1]);
	if (ret)
	{
		printk("request_irq S2 error\n");
		goto errout;
	}
	else
	{
		printk("request_irq S2 =%d\n", pin_desc[1].intnumber);
	}

	gpio_free(pin_desc[2].pin);
	mxc_iomux_v3_setup_pad(pin_desc[2].pinconfig);
	ret = gpio_request(pin_desc[2].pin, "S3");
	if (ret == 0)
	{
		printk("gpio_request S3 success\n");
	}
	else
	{
		printk("gpio_request S3 fail\n");
	}

	ret = gpio_direction_input(pin_desc[2].pin);
	if (ret == 0)
	{
		printk("gpio_direction_input S3 success\n");
	}
	else
	{
		printk("gpio_direction_input S3 fail\n");
	}

	ret = request_irq(pin_desc[2].intnumber, button_irq, IRQ_TYPE_EDGE_FALLING, "S3",
					  (void *)&pin_desc[2]);
	if (ret)
	{
		printk("request_irq S3 error\n");
		goto errout;
	}
	else
	{
		printk("request_irq S3 =%d\n", pin_desc[2].intnumber);
	}

	printk("button open\n");
	return 0;
errout:
	return ret;
}

/*驅動的初始化函式*/
static int ButtonDriver_Initial(void)
{
	int ret;
	dev_t gpio_dev_no; //裝置號

	if (device_major)
	{
		gpio_dev_no = MKDEV(device_major, device_minor);
		register_chrdev_region(gpio_dev_no, 1, "button-chardev");
	}
	else
	{
		ret = alloc_chrdev_region(&gpio_dev_no, 0, 1, "button-chardev");
		if (ret)
		{
			printk(KERN_ALERT "alloc_chrdev_region failed\n");
		}
		device_major = MAJOR(gpio_dev_no);
		device_minor = MINOR(gpio_dev_no);
		printk(KERN_ALERT "major=%d minor=%d\n", MAJOR(gpio_dev_no), MINOR(gpio_dev_no));
	}

	gpio_class_dev = cdev_alloc();												 //分配空間
	cdev_init(gpio_class_dev, &gpio_fops);										 /*字元裝置初始化,繫結相關操作到裝置*/
	gpio_class_dev->owner = THIS_MODULE;										 /*裝置的擁有者*/
	cdev_add(gpio_class_dev, gpio_dev_no, 1);									 /*新增裝置到核心*/
	gpio_class = class_create(THIS_MODULE, "button-chardev-class");				 /*建立裝置類,用於自動建立裝置檔案*/
	device_create(gpio_class, NULL, gpio_dev_no, NULL, "button-chardev-device"); /*依據以前建立的裝置類,建立裝置*/

	Button_Initial();
	return 0;
}
/************************************************************
在裝置驅動中cdev_add將struct file_operations和裝置號註冊到系統後,
為了能夠自動產生驅動對應的裝置檔案,需要呼叫class_create和device_create,
並通過uevent機制呼叫mdev(嵌入式linux由busybox提供)來呼叫mknod建立裝置檔案。
當然也可以不呼叫這兩個介面,那就手工通過命令列mknod來建立裝置檔案。

1. 在驅動通過cdev_add將struct file_operations介面集和裝置註冊到系統後,
即利用class_create介面來建立裝置類目錄檔案。
預設在/sysfs/建立class目錄。

1.利用class_create介面來建立裝置類目錄檔案後,再利用device_create
介面來建立具體裝置目錄和裝置屬性檔案。
代表預設在/sysfs/建立devices目錄

輪到mdev出場了,以上描述都是在sysfs檔案系統中建立目錄或者檔案,
而應用程式訪問的裝置檔案則需要建立在/dev/目錄下。該項工作由mdev完成。

*************************************************************/

/*退出函式*/
static void ButtonDriver_Exit(void)
{

	free_irq(pin_desc[0].intnumber, (void *)&pin_desc[0]);
	free_irq(pin_desc[1].intnumber, (void *)&pin_desc[1]);
	free_irq(pin_desc[2].intnumber, (void *)&pin_desc[2]);

	/*裝置解除安裝*/
	cdev_del(gpio_class_dev);										//登出裝置
	unregister_chrdev_region(MKDEV(device_major, device_minor), 1); //釋放裝置號
	device_destroy(gpio_class, MKDEV(device_major, device_minor));
	class_destroy(gpio_class);

	/* 釋放註冊的4箇中斷 */
	/*********************************************************
	free_irq(unsigned int irq, void *dev_id);
	引數說明:

	unsigned int  irq:要解除安裝的中斷號

	void  *dev_id:這個是要解除安裝的中斷action下的哪個服務函式,
	***********************************************************/
}

/*LICENSE資訊*/
MODULE_LICENSE("GPL");
/*解除安裝和載入*/
module_init(ButtonDriver_Initial);
module_exit(ButtonDriver_Exit);
/**************************************************************************


static inline int __must_check	request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

unsigned int  irq:為要註冊中斷服務函式的中斷號,定義在mach/irqs.h

irq_handler_t  handler:為要註冊的中斷服務函式

unsigned long  irqflags: 觸發中斷的引數,比如邊沿觸發, 定義在linux/interrupt.h。         

const char  *devname:中斷程式的名字,使用cat /proc/interrupt 可以檢視中斷程式名字

void  *dev_id:傳入中斷處理程式的引數,註冊共享中斷時不能為NULL,因為解除安裝時需要這個做引數,避免解除安裝其它中斷服務函式


void free_irq(unsigned int irq, void *dev_id)
{
	struct irq_desc *desc = irq_to_desc(irq);
	if (!desc)
		return;
	chip_bus_lock(irq, desc);
	kfree(__free_irq(irq, dev_id));
	chip_bus_sync_unlock(irq, desc);
}
unsigned int  irq:要解除安裝的中斷號

void  *dev_id:這個是要解除安裝的中斷action下的哪個服務函式

程式設計注意:

1.如果是採用非共享方式註冊中斷,則request_irq和free的最後一個引數都要為NULL。

2.如果採用共享中斷方式,所有使用request_irq註冊的中斷時flags都要加上IRQF_SHARED這個共享引數,表明其實共享中斷。

3.對於共享中斷,每一個申請共享的中斷,申請和釋放時都要給request_irq和free_irq的最後一個引數dev和id_dev傳遞一個指標,
將來來中斷的時候,將會傳遞這個指標到每個中斷函式中,而中斷函式就可以用來區分到底是不是它的中斷,是則執行,
不是則判斷後直接退出中斷處理函式即可。同時在free_irq時也會使用這個指標,查詢這個貢獻中斷連結串列上了所有註冊的irq,
只有在這個指標能對的上的時候,才會刪除它所在的連結串列節點(如果是最後一個節點還要釋放該中斷)。所在在編寫中斷處理
函式時該指標必須是唯一的,通常傳的這個指標是該裝置結構體的地址,這個每個裝置不一樣所以肯定是唯一的。

中斷申請函式request_irq()與中斷釋放函式free_irq()的最後一個引數(void *dev 裝置結構體)要保持一致,
必須是同一個指標,引數傳遞過來的都不算。
***************************************************************************/

 

相關文章