在做中斷試驗時,發(fā)現(xiàn)中斷驅(qū)動總是insmod失敗,之后定位到 gpio_request 失敗,之后是想到使用的野火做好的系統(tǒng),在uEnv.txt中會加載大量設(shè)備樹插件,將key相關(guān)的設(shè)備樹插件屏蔽即可。
linux中斷API函數(shù)
中斷號
每個中斷都有一個中斷號,通過中斷號即可區(qū)分不同的中斷,在 Linux 內(nèi)核中使用一個 int 變量表示中斷號
request_irq函數(shù)
在 Linux 內(nèi)核中要想使用某個中斷是需要申請的, request_irq 函數(shù)用于申請中斷, request_irq函數(shù)可能會導(dǎo)致睡眠,因此不能在中斷上下文或者其他禁止睡眠的代碼段中使用 request_irq 函數(shù)。 request_irq 函數(shù)會激活(使能)中斷,所以不需要我們手動去使能中斷, request_irq 函數(shù)原型如下:
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)
irq:要申請中斷的中斷號。
handler:中斷處理函數(shù),當(dāng)中斷發(fā)生以后就會執(zhí)行此中斷處理函數(shù)。
flags:中斷標(biāo)志,可以在文件 include/linux/interrupt.h 里面查看所有的中斷標(biāo)志,這些標(biāo)志可以通過“|”來實現(xiàn)多種組合。
name:中斷名字,設(shè)置以后可以在/proc/interrupts 文件中看到對應(yīng)的中斷名字。
dev: 如果將 flags 設(shè)置為 IRQF_SHARED 的話, dev 用來區(qū)分不同的中斷,一般情況下將dev 設(shè)置為設(shè)備結(jié)構(gòu)體, dev 會傳遞給中斷處理函數(shù) irq_handler_t 的第二個參數(shù)。
free_irq函數(shù)
使用中斷的時候需要通過 request_irq 函數(shù)申請,使用完成以后就要通過 free_irq 函數(shù)釋放掉相應(yīng)的中斷。
void free_irq(unsigned int irq,void *dev)
irq: 要釋放的中斷。
dev:如果中斷設(shè)置為共享(IRQF_SHARED)的話,此參數(shù)用來區(qū)分具體的中斷。共享中斷只有在釋放最后中斷處理函數(shù)的時候才會被禁止掉。
中斷處理函數(shù)
使用 request_irq 函數(shù)申請中斷的時候需要設(shè)置中斷處理函數(shù),中斷處理函數(shù)格式如下所示:
irqreturn_t (*irq_handler_t) (int, void *)
第一個參數(shù)是要中斷處理函數(shù)要相應(yīng)的中斷號。第二個參數(shù)是一個指向 void 的指針,也就是個通用指針,需要與 request_irq 函數(shù)的 dev 參數(shù)保持一致。用于區(qū)分共享中斷的不同設(shè)備,dev 也可以指向設(shè)備數(shù)據(jù)結(jié)構(gòu)。中斷處理函數(shù)的返回值為 irqreturn_t 類型, irqreturn_t 類型定義
如下所示:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
可以看出 irqreturn_t 是個枚舉類型,一共有三種返回值。一般中斷服務(wù)函數(shù)返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED)
中斷使能和禁止函數(shù)
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
disable_irq函數(shù)要等到當(dāng)前正在執(zhí)行的中斷處理函數(shù)執(zhí)行完才返回,因此使用者需要保證不會產(chǎn)生新的中斷,并且確保所有已經(jīng)開始執(zhí)行的中斷處理程序已經(jīng)全部退出。在這種情況下,可以使用另外一個中斷禁止函數(shù):
void disable_irq_nosync(unsigned int irq)
關(guān)閉全局中斷可以使用如下函數(shù):
local_irq_save(flags) //禁止中斷
local_irq_restore(flags) //恢復(fù)中斷
上半部與下半部
上半部:上半部就是中斷處理函數(shù),那些處理過程比較快,不會占用很長時間的處理就可以放在上半部完成
下半部:如果中斷處理過程比較耗時,那么就將這些比較耗時的代碼提出來,交給下半部去執(zhí)行,這樣中斷處理函數(shù)就會快進(jìn)快出。
tasklet
tasklet 是利用軟中斷來實現(xiàn)的另外一種下半部機(jī)制,在軟中斷和 tasklet 之間,建議大家使用 tasklet。 Linux 內(nèi)核使用 tasklet_struct 結(jié)構(gòu)體來表示 tasklet:
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一個 tasklet */
unsigned long state; /* tasklet 狀態(tài) */
atomic_t count; /* 計數(shù)器,記錄對 tasklet 的引用數(shù) */
void (*func)(unsigned long); /* tasklet 執(zhí)行的函數(shù) */
unsigned long data; /* 函數(shù) func 的參數(shù) */
};
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
t:要初始化的 tasklet
func: tasklet 的處理函數(shù)。
data: 要傳遞給 func 函數(shù)的參數(shù)
也 可 以 使 用 宏 DECLARE_TASKLET 來 一 次 性 完 成 tasklet 的 定 義 和 初 始 化 ,DECLARE_TASKLET 定義在 include/linux/interrupt.h 文件中,定義如下:
DECLARE_TASKLET(name, func, data)
其中 name 為要定義的 tasklet 名字,這個名字就是一個 tasklet_struct 類型的時候變量, func就是 tasklet 的處理函數(shù), data 是傳遞給 func 函數(shù)的參數(shù)。
在上半部,也就是中斷處理函數(shù)中調(diào)用 tasklet_schedule 函數(shù)就能使 tasklet 在合適的時間運(yùn)行, tasklet_schedule 函數(shù)原型如下:
void tasklet_schedule(struct tasklet_struct *t)
t:要調(diào)度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。
關(guān)于 tasklet 的參考使用示例如下所示:
/* 定義 taselet */
struct tasklet_struct testtasklet;
/* tasklet 處理函數(shù) */
void testtasklet_func(unsigned long data)
{
/* tasklet 具體處理內(nèi)容 */
}
/* 中斷處理函數(shù) */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 調(diào)度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驅(qū)動入口函數(shù) */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注冊中斷處理函數(shù) */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
工作隊列
工作隊列是另外一種下半部執(zhí)行方式,工作隊列在進(jìn)程上下文執(zhí)行,工作隊列將要推后的工作交給一個內(nèi)核線程去執(zhí)行,因為工作隊列工作在進(jìn)程上下文,因此工作隊列允許睡眠或重新調(diào)度。因此如果你要推后的工作可以睡眠那么就可以選擇工作隊列,否則的話就只能選擇軟中斷或 tasklet。
Linux 內(nèi)核使用 work_struct 結(jié)構(gòu)體表示一個工作,內(nèi)容如下(省略掉條件編譯):
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作隊列處理函數(shù) */
};
簡單創(chuàng)建工作很簡單,直接定義一個 work_struct 結(jié)構(gòu)體
變量即可,然后使用 INIT_WORK 宏來初始化工作, INIT_WORK 宏定義如下:
#define INIT_WORK(_work, _func)
_work 表示要初始化的工作, _func 是工作對應(yīng)的處理函數(shù)。
也可以使用 DECLARE_WORK 宏一次性完成工作的創(chuàng)建和初始化,宏定義如下:
#define DECLARE_WORK(n, f)
n 表示定義的工作(work_struct), f 表示工作對應(yīng)的處理函數(shù)。
和 tasklet 一樣,工作也是需要調(diào)度才能運(yùn)行的,工作的調(diào)度函數(shù)為 schedule_work,函數(shù)原型如下所示:
bool schedule_work(struct work_struct *work)
函數(shù)參數(shù)和返回值含義如下:
work: 要調(diào)度的工作。
返回值: 0 成功,其他值 失敗。
/* 定義工作(work) */
struct work_struct testwork;
/* work 處理函數(shù) */
void testwork_func_t(struct work_struct *work);
{
/* work 具體處理內(nèi)容 */
}
/* 中斷處理函數(shù) */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 調(diào)度 work */
schedule_work(&testwork);
......
}
/* 驅(qū)動入口函數(shù) */
static int __init xxxx_init(void)
{
......
/* 初始化 work */
INIT_WORK(&testwork, testwork_func_t);
/* 注冊中斷處理函數(shù) */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
設(shè)備樹修改
設(shè)備樹修改如下:
key{
#address-cells = <1>;
#size-cells = <1>;
compatible = "fire,key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key_gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio5>;
interrupts = <1 IRQ_TYPE_EDGE_FALLING>; //1表示使用gpio5_io01
status = "okay";
};
IRQ_TYPE_EDGE_FALLING 定義在文件 include/linux/irq.h 中,定義如下:
enum {
IRQ_TYPE_NONE = 0x00000000,
IRQ_TYPE_EDGE_RISING = 0x00000001,
IRQ_TYPE_EDGE_FALLING = 0x00000002,
IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
IRQ_TYPE_LEVEL_HIGH = 0x00000004,
IRQ_TYPE_LEVEL_LOW = 0x00000008,
IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW |
IRQ_TYPE_LEVEL_HIGH),
......
}
獲取中斷號
編寫驅(qū)動的時候需要用到中斷號,我們用到中斷號,中斷信息已經(jīng)寫到了設(shè)備樹里面,因此可以通過 irq_of_parse_and_map 函數(shù)從 interupts 屬性中提取到對應(yīng)的設(shè)備號,函數(shù)原型如下:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
函數(shù)參數(shù)和返回值含義如下:
dev: 設(shè)備節(jié)點(diǎn)。
index:索引號, interrupts 屬性可能包含多條中斷信息,通過 index 指定要獲取的信息。
返回值:中斷號。
如果使用 GPIO 的話,可以使用 gpio_to_irq 函數(shù)來獲取 gpio 對應(yīng)的中斷號,函數(shù)原型如
下:文章來源:http://www.zghlxwxcb.cn/news/detail-667949.html
int gpio_to_irq(unsigned int gpio)
函數(shù)參數(shù)和返回值含義如下:
gpio: 要獲取的 GPIO 編號。
返回值: GPIO 對應(yīng)的中斷號。文章來源地址http://www.zghlxwxcb.cn/news/detail-667949.html
到了這里,關(guān)于linux驅(qū)動學(xué)習(xí)3-外部中斷的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!