設備樹下的中斷

一、上半部和下半部(頂半部和底半部)

上半部:我們在使用request_irq申請中斷時註冊的中斷服務函數屬於中斷處理的上半部。適合耗時不長的程序。
下半部:適合處理過程耗時的代碼。

①、 如果要處理的內容不希望被其他中斷打斷,可以放到上半部。
②、如果要處理的任務對時間敏感,可以放到上半部。
③、如果要處理的任務與硬件有關,可以放到上半部

1、軟中斷
Linux 內核使用結構體 softirq_action 表示軟中斷, softirq_action
結構體定義在文件 include/linux/interrupt.h 中

2、tasklet
tasklet 是利用軟中斷來實現的另外一種下半部機制,在軟中斷和tasklet 之間,建議大家使用 tasklet。Linux 內核使用結構體

 struct tasklet_struct
{
struct tasklet_struct *next; /* 下一個 tasklet */
unsigned long state; /* tasklet 狀態 */
atomic_t count; /* 計數器,記錄對 tasklet 的引用數 */
void (*func)(unsigned long); /* tasklet 執行的函數 */
unsigned long data; /* 函數 func 的參數 */
};

使用示例

/* 定義 taselet */
struct tasklet_struct testtasklet;
/* tasklet 處理函數 */
void testtasklet_func(unsigned long data) {
 /* tasklet 具體處理內容 */
}
/* 中斷處理函數 */
irqreturn_t test_handler(int irq, void *dev_id) {
 ......
 /* 調度 tasklet */
 tasklet_schedule(&testtasklet);
 ......
}
/* 驅動入口函數 */
static int __init xxxx_init(void) {
 ......
 /* 初始化 tasklet */
 tasklet_init(&testtasklet, testtasklet_func, data);
 /* 註冊中斷處理函數 */
 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
 ......
}

3、工作隊列

工作隊列是另外一種下半部執行方式,工作隊列在進程上下文執行,工作隊列將要推後的工作交給一個內核線程去執行,因爲工作隊列工作在進程上下文,因此工作隊列允許睡眠或重新調度。因此如果你要推後的工作可以睡眠那麼就可以選擇工作隊列,否則的話就只能選擇軟中斷或 tasklet。
使用示例

/* 定義工作(work) */
struct work_struct testwork;
/* work 處理函數 */
void testwork_func_t(struct work_struct *work);
{
 /* work 具體處理內容 */
}
/* 中斷處理函數 */
irqreturn_t test_handler(int irq, void *dev_id) {
 ......
 /* 調度 work */
 schedule_work(&testwork);
 ......
}
/* 驅動入口函數 */
static int __init xxxx_init(void) {
 ......
 /* 初始化 work */
 INIT_WORK(&testwork, testwork_func_t);
 /* 註冊中斷處理函數 */
 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
 ......
}

4、中斷示例

(1)設備樹修改
在這裏插入圖片描述(2)主程序
(2.1)open函數

static int imx6uirq_open(struct inode *inode, struct file *filp)
{
	filp->private_data = imx6uirq;
	return 0;
}

在OPEN函數裏添加中斷結構體爲私有數據
(2.2)read函數

static int imx6uirq_read(struct file *filp, char __user *buf, size_t cnt loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

	//加鎖讀值
	keyvalue = atomic_read(&dev->keyvalue);
	releasekey = atomic_read(&dev->releasekey);
	if(releasekey){
		if(keyvalue & 0x80){
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));//傳入用戶層
		}else{
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);
	}else{
		goto data_error;
	}
	return 0;
data_error:
	return -EINVAL;

}

加原子鎖讀取按鍵值並複製到用戶空間
(2.3)IO初始化
從設備樹中提取GPIO然後設置成中斷模式,根據中斷處理函數中斷號申請中斷。中斷服務函數裏負責處理定時器,再進入定時器函數裏讀取IO值,延時的目的爲了消除按鍵抖動帶來的誤差。

static int keyio_init(void)
{
	unsigned char i = 0;
	char name[10];
	int ret = 0;
	imx6uirq.nd = of_find_node_by_path("key");
	if(imx6uirq.nd == NULL){
		printk("key node can not find!|r\n");
		return -EINVAL;
	}
	//提取GPIO
	for(i = 0; i< KEY_NUM; i++){
		imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", i);
		if(imx6uirq.irqkeydesc[i]<0){
			printk("can not get key%d\r\n", i);
		}
	}

	//初始化IO ,設置中斷模式
	for(i = 0; i < KEY_NUM; i++){
		memset(imx6uirq.irqkeydesc[i].name,0, sizeof(name));
		sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);
		gpio_request(imx6uirq.irqkeydesc[i].gpio, name);//獲取gpio
		gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);//設置爲輸出
		imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);//獲取中斷號並設置

		//申請中斷
		imx6uirq.irqkeydesc[0].handler = key0_handler;
		imx6uirq.irqkeydesc[0].value = KEY0VALUE;

		for(i=0; i<KEY_NUM; i++){
			ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
							imx6uirq.irqkeydesc[i].handler,
							IRQ_TRIGGER_EDGE_FALLING|IRQ_TRIGGER_EDGE_RISING,
							imx6uirq.irqkeydesc[i].name, &imx6uirq);
			if(ret < 0){
				printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
				return -EFAULT;
			}			
		}
		//創建定時器
		init_timer(&imx6uirq.timer);
		imx6uirq.timer.function = timer_function;
		return 0;
		
	}
	
}

void timer_function(unsigned long arg)
{
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *keydesc;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

	num = dev->cur_keynum;
	keydesc = &dev->irqkeydesc[num];

	//
	value = gpio_get_value(keydesc->gpio);	//讀取IO值
	if(value == 0){
		atomic_set(&dev->keyvalue, keydesc->value);	//按下按鍵
	}else{
		atomic_set(&dev->keyvalue, 0x80);			//按鍵鬆開
		atomic_set(&dev->releasekey, 1);			//標記鬆開
	}
}

總結中斷的使用:驅動入口函數里正常註冊字符設備後,進行按鍵IO的初始化,進入IO初始化函數裏,獲取設備樹IO並設置中斷模式、中斷的處理函數然後申請中斷、創建定時器讀取按鍵值。

更多文章請關注嵌入式機器人公衆號
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章