01-驅動中的中斷處理

本系列文章主要講Linux中的中斷和時間管理,文章機構如下:
01 - 驅動中的中斷處理
02 - 中斷下半部 tasklet
03 - 中斷的下半部 workqueue
04 - Linux中的延時操作函數
05 - Linux硬件定時 jiffies
06 - Linux 低分辨率定時器
07 - Linux高分辨率定時器

1. 驅動中定義的中斷處理相關的API

1.1 註冊中斷函數

 驅動中註冊中斷處理函數的原型如下:

原    型: static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
功    能: 向內核中註冊中斷處理函數
@param1: 設備上所用中斷號,這個中斷號不是硬件手冊上查到的號,而是內核中的中斷號,這個號將會用於決定構造的 struct irqaction 對象被插入到哪個鏈表,並用於初始化 struct irqaction 對象中的irq成員
@param2: 指向中斷處理函數的指針
@param3: 與中斷相關的標誌,用於初始化 struct irqaction 對象中的flags成員,這些中斷標誌可以用位或的方式來設置多個,常用的標誌如下:
					#define IRQF_TRIGGER_RISING		0x00000001		// 上升沿觸發		 在 include\linux\interrupt.h 中定義
					#define IRQF_TRIGGER_FALLING	0x00000002		// 下降沿觸發
					#define IRQF_TRIGGER_HIGH		0x00000004		// 高電平觸發
					#define IRQF_TRIGGER_LOW		0x00000008		// 低電平觸發
					#define IRQF_SHARED				0x00000080		// 共享中斷標誌,多個設備共享一箇中斷線,共享的所有中斷都必須指定此標誌。如果使用共享中斷的話,request_irq 函數的 dev 參數就是唯一區分他們的標誌。
					#define IRQF_TIMER				(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)	// 定時器專用中斷標誌
					#define __IRQF_TIMER			0x00000200
					#define IRQF_NO_SUSPEND			0x00004000
					#define IRQF_NO_THREAD			0x00010000
@param4: 該中斷在 /proc 中的名字,用於初始化 struct irqaction 對象中的name成員
@param5: 區別不同設備所對應的 struct irqaction 對象,在 struct irqaction 對象從鏈表中移除時需要,dev用於初始化 struct irqaction 對象中的dev_id成員。共享中斷必須傳遞一個非NULL實參,非共享中斷可以傳NULL。中斷髮生後調用中斷處理函數時也會把該參數傳遞給中斷處理函數。
@retval: 成功返回0,失敗返回負的錯誤碼。

 需要注意的是:request_irq 函數根據傳入的參數構造好一個 struct irqaction 對象並加入到對應的鏈表後,還將對應的中斷使能了,因此我們不需要再使能中斷。

 其中 struct irqaction 結構體的定義如下:

struct irqaction {
	irq_handler_t		handler;			// 中斷處理函數的指針
	void				*dev_id;			// 區別共享中斷中不同設備的ID
	void __percpu		*percpu_dev_id;
	struct irqaction	*next;				// 將共享同一IRQ號的 struct irqaction 對象鏈接在一起的指針
	irq_handler_t		thread_fn;
	struct task_struct	*thread;
	struct irqaction	*secondary;
	unsigned int		irq;				// IRQ號
	unsigned int		flags;
	unsigned long		thread_flags;		// 以IRQF_爲開頭的一組標誌
	unsigned long		thread_mask;
	const char		*name;
	struct proc_dir_entry	*dir;
} ____cacheline_internodealigned_in_smp;

1.2 中斷處理函數

 中斷處理函數原型如下:

原    型: irqreturn_t (*irq_handler_t)(int, void *); include\linux\irqreturn.h
功    能: 中斷處理函數,中斷髮生後中斷處理函數會自動調用
@param1: IRQ號
@param2: 對應的設備ID,也是 struct irqaction 對象中的dev_id成員
@retval: 返回值是一個枚舉類型,在 include\linux\irqreturn.h 中定義,詳細信息如下			
			enum irqreturn {
				IRQ_NONE		= (0 << 0),		// 不是驅動所管理的設備產生的中斷,用於共享中斷
				IRQ_HANDLED		= (1 << 0),		// interrupt was handled by this device
				IRQ_WAKE_THREAD	= (1 << 1),		// handler requests to wake the handler thread
			};

 一般中斷處理函數返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)

 IRQ_RETVAL宏的定義如下:

#define IRQ_RETVAL(x)	((x) ? IRQ_HANDLED : IRQ_NONE)

1.3 註銷中斷的函數

 註銷中斷處理函數的原型如下:

原    型: void free_irq(unsigned int, void *) 	
功    能: 註銷一箇中斷處理函數
@param1: IRQ號
@param2: 對應的設備ID,也是 struct irqaction 對象中的dev_id成員,共享中斷必須要傳遞一個非NULL的實參,和request_irq中的dev_id保持一致
@retval: 無返回值

1.4 使能及禁止使能相關函數

 除此之外,還有一些中斷常用的使能和禁止的函數如下:

void enable_irq(unsigned int irq)		// 使能指定的中斷
void disable_irq(unsigned int irq)		// 禁止指定的中斷

 disable_irq函數要等到當前正在執行的中斷處理函數執行完才返回,因此使用者需要保證不會產生新的中斷,並且確保所有已經開始執行的中斷處理程序已經全部退出。在這種情況下,可以使用另外一箇中斷禁止函數:

void disable_irq_nosync(unsigned int irq)

 disable_irq_nosync 函數調用以後立即返回,不會等待當前中斷處理程序執行完畢。

 上面三個函數都是使能或者禁止某一箇中斷,有時候我們需要關閉當前處理器的整個中斷系統,這個時候可以使用如下兩個函數:

local_irq_enable()		// 使能當前處理器中斷系統
local_irq_disable()		// 禁止當前處理器中斷系統
// 函數原型如下
#define local_irq_enable() do { } while (0)
#define local_irq_disable() do { } while (0)

local_irq_save(flags) 		// 用於禁止中斷,並且將中斷狀態保存在 flags 中
local_irq_restore(flags)	// 用於恢復中斷,將中斷恢復到 flags 狀態

 中斷處理函數應該快速完成,不能消耗太長時間。因爲處理進入中斷後相應的中斷被屏蔽,在之後的代碼中也沒有重新開啓,因此整個中斷處理過程中中斷是禁止的,如果中斷處理函數執行的過長,其他的中斷將會被掛起,從而對其他中斷的相應造成嚴重的影響。
 必須記住的一條準則是:在中斷處理函數中一定不能調用可能引起進程切換的函數,因爲一旦中斷處理程序被切換將不能再次被調度,這是內核對終端處理的一個嚴格限制。學過的函數有 copy_from_user 、copy_to_user

2. 示例代碼

2.1 demo.c

 在代碼的112行添加了註冊中斷的函數,中斷處理函數是my_irq_handler,因爲定義了共享中斷所以必須傳遞dev_id。產生中斷事件之後會自動的調用50行的中斷處理函數,在裏面可以做中斷相關的內容,用宏 IRQ_RETVAL(IRQ_HANDLED) 作爲中斷處理函數的返回值。
 本實例中只是搭建了中斷處理的框架,並沒有進行實際的操作,可以利用按鍵來模擬外部中斷。

#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <linux/slab.h>				// kzalloc和kfree的頭文件
#include <linux/interrupt.h>		// 中斷相關的頭文件

typedef struct 
{
	dev_t dev_no;
	char devname[20];
	char classname[20];
	struct cdev demo_cdev;
	struct class *cls;
	struct device *dev;
	struct mutex my_mutex;			// 定義一個互斥體,在init函數中進行初始化
}demo_struct;
demo_struct *my_demo_dev = NULL;	// 定義一個設備結構體指針,指向NULL

static int demo_open(struct inode *inode, struct file *filp)
{
	/* 首先判斷設備是否可用 */
	if( mutex_lock_interruptible(&my_demo_dev->my_mutex) )		// 訪問共享資源之前獲取互斥體,成功獲取返回0
	{
		return -ERESTARTSYS;		// 不能獲取返回錯誤碼
	}
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	return 0;

}

static int demo_release(struct inode *inode, struct file *filp)
{
	/* 釋放互斥量 */
	mutex_unlock(&my_demo_dev->my_mutex);

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	
	return 0;
}

struct file_operations demo_ops = {
	.open = demo_open,
	.release=  demo_release,
};

irqreturn_t my_irq_handler(int irq, void *dev_id)
{
	/*
		中斷處理函數執行內容
	*/
	
	return IRQ_RETVAL(IRQ_HANDLED);		// 中斷處理函數常用的返回值
}


static int __init demo_init(void)
{
	int ret;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	/* 開闢空間 */
	my_demo_dev =  kzalloc(sizeof(demo_struct), GFP_KERNEL);
	if ( IS_ERR(my_demo_dev) )
	{
		printk("kzalloc failed.\n");
		ret = PTR_ERR(my_demo_dev);
		goto kzalloc_err;
	}
	strcpy(my_demo_dev->devname, "demo_chrdev");	// 給設備名字賦值
	strcpy(my_demo_dev->classname, "demo_class");	// 給設備類的名字賦值

	mutex_init(&my_demo_dev->my_mutex);				// 初始化互斥體
	
	ret = alloc_chrdev_region(&my_demo_dev->dev_no, 0, 0, my_demo_dev->devname);
	if (ret)
	{
		printk("alloc_chrdev_region failed.\n");
		goto region_err;
	}

	cdev_init(&my_demo_dev->demo_cdev, &demo_ops);

	ret = cdev_add(&my_demo_dev->demo_cdev, my_demo_dev->dev_no, 1);
	if (ret < 0)
	{
		printk("cdev_add failed.\n");
		goto add_err;
	}

	my_demo_dev->cls = class_create(THIS_MODULE, my_demo_dev->classname);		/* 在目錄/sys/class/.. */
	if ( IS_ERR(my_demo_dev->cls) )
	{
		ret = PTR_ERR(my_demo_dev->cls);
		printk("class_create failed.\n");
		goto cls_err;
	}

	my_demo_dev->dev = device_create(my_demo_dev->cls, NULL, my_demo_dev->dev_no, NULL, "chrdev%d", 0);	/* 在目錄/dev/.. */
	if ( IS_ERR(my_demo_dev->dev) )
	{
		ret = PTR_ERR(my_demo_dev->dev);
		printk("device_create failed.\n");
		goto dev_err;
	}

	// 註冊中斷,因爲是共享中斷所以必須傳遞dev_id
	ret = request_irq(123, my_irq_handler, IRQF_TRIGGER_RISING | IRQF_SHARED, "irq_test", my_demo_dev);	
	if ( ret )	// 成功返回0,失敗返回錯誤碼
	{	
		printk("request_irq failed.\n");
		goto irq_err;
	}
	return 0;

irq_err:
	device_destroy(my_demo_dev->cls, my_demo_dev->dev_no);
dev_err:
	class_destroy(my_demo_dev->cls);	
cls_err:
	cdev_del(&my_demo_dev->demo_cdev);
add_err:
	unregister_chrdev_region(my_demo_dev->dev_no, 1);
region_err:
	kfree(my_demo_dev);		// 釋放空間,避免內存泄漏
kzalloc_err:
	return ret;
}


static void __exit demo_exit(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	free_irq(123, my_demo_dev);		// 註銷中斷
	device_destroy(my_demo_dev->cls, my_demo_dev->dev_no);
	class_destroy(my_demo_dev->cls);
	cdev_del(&my_demo_dev->demo_cdev);
	unregister_chrdev_region(my_demo_dev->dev_no, 1);
	kfree(my_demo_dev);				// 釋放空間,避免內存泄漏
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

2.2 test.c

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	int fd;
	int i, count = 1;

	fd = open("/dev/chrdev0", O_RDWR, 0666);
	if (fd < 0)
	{
		perror("open");
		return -1;
	}

	while(1);

	close(fd);

	return 0;
}

2.3 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	arm-linux-gnueabihf-gcc test.c -o app
install:
	sudo cp *.ko  app /tftpboot
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o
發佈了57 篇原創文章 · 獲贊 65 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章