Linux中斷基礎概念
中斷上下文
Linux內核的中斷回調可以有兩部分,即上下文。當中斷比較簡單時,可以只有上文。
一般中斷上文是指由中斷產生的回調函數直接執行的部分;中斷下文在上文中啟用調度,再由內核調度。
中斷上文:處理盡可能少的任務,特點是響應速度快
中斷下文:處理耗時任務,可以被新的中斷打斷
中斷嵌套
Linux中斷現在不能嵌套,之前可以
中斷相關的函數及命令
獲取中斷號
如果是有設備樹的內核,一般通過節點的interrupt-parent和interrupt屬性來描述中斷
對GPIO來說,GPIO的節點可以作為中斷控制器,一般由BSP廠家編寫
<linux/of_irq.h>
//從設備樹的設備節點中獲取中斷號
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
//參數:dev設備節點,index索引(節點中interrupts屬性可能包含多條中斷信息,通過index確認)
//返回值:中斷號
//如果是GPIO的話,可以不從設備樹中獲取
int gpio_to_irq(unsigned int gpio);
//參數:gpio的編號
//返回值:gpio對應的中斷號
申請中斷
申請中斷的函數
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
//參數:
//irq:要申請中斷的中斷號
//handler:中斷處理函數
//flags:中斷標志
//name:中斷名字,可在/proc/interrupts文件中看到對應的名字
//dev:flags為IRQF_SHARED時,dev用來區分不同的中斷。一般將dev設置為設備結構體,傳遞給irq_handler_t的第二個參數
//返回值:0申請成功,其他負值申請失敗;如果返回-EBUSY標識已經被申請
中斷標志(申請中斷函數的flags參數)定義在 include/linux/interrupt.h中
常見的中斷標志:
標志 | 功能 |
---|---|
IRQF_SHARED | 多個設備共享一個中斷線,申請中斷函數的dev參數是區分它們的唯一標志 |
IRQF_ONESHOT | 單次中斷,中斷執行一次就結束 |
IRQF_TRIGGER_NONE | 無觸發 |
IRQF_TRIGGER_RISING | 上升沿觸發 |
IRQF_TRIGGER_FALLING | 下降沿觸發 |
IRQF_TRIGGER_HIGH | 高電平觸發 |
IRQF_TRIGGER_LOW | 低電平觸發 |
中斷處理函數
使用request_irq申請中斷的時候需要中斷處理函數irq_handler_t來做參數,
這里的irq_handler_t函數可以理解為中斷上文的回調函數,發生中斷時內核會調用處理函數。
irqretuen_t (*irq_handler_t)(int, void*)
//參數:int型中斷號,void*型需要和中斷申請函數的dev參數保持一致
//返回值:irqretuen_t類型
enum irqreturn {
IRQ_NONE = (0<<0), //不處理
IRQ_HANDLED = (1<<0), //正常處理
IRQ_WAKE_THREAD = (1<<1), //使用中斷下文處理
};
typedef enum irqreturn irqreturn_t;
//irqretuen_t是一個枚舉類型,一般中斷處理函數會返回第二個數,格式如下
//return ITQ_RETVAL(IRQ_HANDLED)
釋放中斷
void free_irq(unsigned int irq, void *dev);
//參數:irq要釋放的中斷號,dev如果設置為IRQ_SHARED的話,此參數來區分具體中斷
查看中斷是否存在
#查看中斷是否存在
cat /proc/interrupts
#查看中斷執行次數
cat /proc/irq/xx/spurious
#xx指中斷號
中斷實驗-以GPIO按鍵中斷為例
實驗的gpio中斷內容很少,所以只使用中斷上文
使用訊為的itop-4412開發板,home鍵,按鍵按下時進入中斷執行打印
設備樹修改
home鍵的gpio是gpx1_1,將之前home鍵的描述注釋掉,添加自己的節點
home_inter {
compatible = "taxue,home_key";
status = "okay";
inter_gpio = <&gpx1 1 GPIO_ACTIVE_LOW>;
};
驅動部分的代碼步驟
以平台總線driver的驅動作為框架,在probe函數里執行以下步驟
- 從設備樹獲取描述GPIO的節點
- 通過節點中的gpio參數,獲取gpio編號
- 申請gpio
- 設置gpio方向
- 獲取gpio對應的中斷號
- 申請中斷(中斷處理函數)
編寫中斷處理函數
platform_driver的of_match_table參數要和設備樹節點中的compatible參數一致,保證可以匹配
在remove函數里,釋放中斷、釋放GPIO
代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
//用於和設備匹配
#define DEVICE_NAME "taxue,home_key"
int home_gpio_idx; //home鍵的gpio編號
int home_inter_idx; //home鍵gpio對應的中斷號
static irqreturn_t home_interrupt(int irq, void *dev_id) {
printk("%s(%d)\n", __FUNCTION__, __LINE__); //__LINE__,代碼所在的行數
printk("HOME KEY HIGH TO LOW!\n");
return IRQ_HANDLED; //返回值,表示正常執行
}
int drProbe(struct platform_device *dev){
int ret;
struct device_node *inter_node;
//根據設備樹路徑獲取節點
inter_node = of_find_node_by_path("/home_inter");
if(inter_node == NULL){
printk("find node failed\n");
return -1;
}
//根據節點的inter_gpio屬性,獲取gpio編號
home_gpio_idx = of_get_named_gpio( inter_node, "inter_gpio", 0);
//申請gpio
ret = gpio_request(home_gpio_idx,"home");
if( ret != 0){
printk("gpio request failed\n");
return -1;
}
//將gpio設置為輸入
gpio_direction_input(home_gpio_idx);
//獲取中斷號
home_inter_idx = gpio_to_irq(home_gpio_idx);
//申請中斷,下降沿觸發
ret = request_irq(home_inter_idx, home_interrupt, IRQF_TRIGGER_FALLING, "home_key", dev);
if(ret < 0){
printk("request interrupt failed, IRQ=%d,ret=%d\n", home_inter_idx, ret);
return -1;
}
return 0;
}
int drRemove(struct platform_device *dev){
free_irq(home_inter_idx, dev); //釋放中斷
gpio_free(home_gpio_idx); //釋放gpio
printk("driver remove\n");
return 0;
}
static const struct of_device_id of_interr_match[] = {
{.compatible = DEVICE_NAME},
{},
};
static struct platform_driver pdrv = {
.probe = drProbe,
.remove = drRemove,
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
.of_match_table = of_interr_match,
}
};
//模塊入口
static int driver_init_interr(void){
int ret=0;
ret = platform_driver_register(&pdrv); //注冊平台driver
if(ret < 0){
printk("platform driver regist failed\n");
return -1;
}
return 0;
}
//模塊出口
static void driver_exit_interr(void){
platform_driver_unregister(&pdrv); //卸載平台driver
printk("platform driver exit!\n");
}
module_init(driver_init_interr);
module_exit(driver_exit_interr);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TAXUE");