這篇文章主要介紹“l(fā)inux內(nèi)核有沒有中斷函數(shù)”,在日常操作中,相信很多人在linux內(nèi)核有沒有中斷函數(shù)問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”linux內(nèi)核有沒有中斷函數(shù)”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習吧!
為涇縣等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及涇縣網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站制作、成都網(wǎng)站制作、涇縣網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達到每一位用戶的要求,就會得到認可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!
linux內(nèi)核有中斷函數(shù)。在Linux內(nèi)核中要想使用某個中斷是需要申請的,而request_irq()函數(shù)用于申請中斷,中斷使用完成以后就要通過free_irq()函數(shù)釋放掉相應(yīng)的中斷;還有enable_irq()和disable_irq(),它們用于使能和禁止指定的中斷。
1.Linux中斷
1.1 Linux中斷API函數(shù)
request_irq函數(shù)
在 Linux 內(nèi)核中要想使用某個中斷是需要申請的,request_irq 函數(shù)用于申請中斷,request_irq函數(shù)可能會導(dǎo)致睡眠,因此不能在中斷上下文或者其他禁止睡眠的代碼段中使用 request_irq 函數(shù)。request_irq 函數(shù)會激活(使能)中斷,所以不需要我們手動去使能中斷,request_irq 函數(shù)原型如下:
irq:要申請中斷的中斷號。
handler:中斷處理函數(shù),當中斷發(fā)生以后就會執(zhí)行此中斷處理函數(shù)。
flags:中斷標志,可以在文件 include/linux/interrupt.h 里面查看所有的中斷標志
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ù)。
返回值:0 中斷申請成功,其他負值 中斷申請失敗,如果返回-EBUSY 的話表示中斷已經(jīng)被申請了。
free_irq
使用中斷的時候需要通過 request_irq 函數(shù)申請,使用完成以后就要通過 free_irq 函數(shù)釋放掉相應(yīng)的中斷。如果中斷不是共享的,那么 free_irq 會刪除中斷處理函數(shù)并且禁止中斷。free_irq函數(shù)原型如下所示:
函數(shù)參數(shù)和返回值含義如下:
irq:要釋放的中斷。
dev:如果中斷設(shè)置為共享(IRQF_SHARED)的話,此參數(shù)用來區(qū)分具體的中斷。共享中斷只有在釋放最后中斷處理函數(shù)的時候才會被禁止掉。
返回值:無。
中斷處理函數(shù)
使用 request_irq 函數(shù)申請中斷的時候需要設(shè)置中斷處理函數(shù),中斷處理函數(shù)格式如下所示:
中斷使能和禁止函數(shù)
常用的中斷使用和禁止函數(shù)如下所示:
enable_irq 和 disable_irq 用于使能和禁止指定的中斷,irq 就是要禁止的中斷號。disable_irq函數(shù)要等到當前正在執(zhí)行的中斷處理函數(shù)執(zhí)行完才返回,因此使用者需要保證不會產(chǎn)生新的中斷,并且確保所有已經(jīng)開始執(zhí)行的中斷處理程序已經(jīng)全部退出。在這種情況下,可以使用另外一個中斷禁止函數(shù):
disable_irq_nosync 函數(shù)調(diào)用以后立即返回,不會等待當前中斷處理程序執(zhí)行完畢。上面三個函數(shù)都是使能或者禁止某一個中斷,有時候我們需要關(guān)閉當前處理器的整個中斷系統(tǒng),也就是在學(xué)習 STM32 的時候常說的關(guān)閉全局中斷,這個時候可以使用如下兩個函數(shù):
local_irq_enable 用于使能當前處理器中斷系統(tǒng),local_irq_disable 用于禁止當前處理器中斷系統(tǒng)。假如 A 任務(wù)調(diào)用 local_irq_disable 關(guān)閉全局中斷 10S,當關(guān)閉了 2S 的時候 B 任務(wù)開始運行,B 任務(wù)也調(diào)用 local_irq_disable 關(guān)閉全局中斷 3S,3 秒以后 B 任務(wù)調(diào)用 local_irq_enable 函數(shù)將全局中斷打開了。此時才過去 2+3=5 秒的時間,然后全局中斷就被打開了,此時 A 任務(wù)要關(guān)閉 10S 全局中斷的愿望就破滅了,然后 A 任務(wù)就“生氣了”,結(jié)果很嚴重,可能系統(tǒng)都要被A 任務(wù)整崩潰。為了解決這個問題,B 任務(wù)不能直接簡單粗暴的通過 local_irq_enable 函數(shù)來打開全局中斷,而是將中斷狀態(tài)恢復(fù)到以前的狀態(tài),要考慮到別的任務(wù)的感受,此時就要用到下面兩個函數(shù):
1.2 上半部和下半部
在有些資料中也將上半部和下半部稱為頂半部和底半部,都是一個意思。我們在使用request_irq 申請中斷的時候注冊的中斷服務(wù)函數(shù)屬于中斷處理的上半部,只要中斷觸發(fā),那么中斷處理函數(shù)就會執(zhí)行。我們都知道中斷處理函數(shù)一定要快點執(zhí)行完畢,越短越好,但是現(xiàn)實往往是殘酷的,有些中斷處理過程就是比較費時間,我們必須要對其進行處理,縮小中斷處理函數(shù)的執(zhí)行時間。比如電容觸摸屏通過中斷通知 SOC 有觸摸事件發(fā)生,SOC 響應(yīng)中斷,然后通過 IIC 接口讀取觸摸坐標值并將其上報給系統(tǒng)。但是我們都知道 IIC 的速度最高也只有400Kbit/S,所以在中斷中通過 IIC 讀取數(shù)據(jù)就會浪費時間。我們可以將通過 IIC 讀取觸摸數(shù)據(jù)的操作暫后執(zhí)行,中斷處理函數(shù)僅僅相應(yīng)中斷,然后清除中斷標志位即可。這個時候中斷處理過程就分為了兩部分:
上半部:上半部就是中斷處理函數(shù),那些處理過程比較快,不會占用很長時間的處理就可以放在上半部完成。
下半部:如果中斷處理過程比較耗時,那么就將這些比較耗時的代碼提出來,交給下半部去執(zhí)行,這樣中斷處理函數(shù)就會快進快出。
因此,Linux 內(nèi)核將中斷分為上半部和下半部的主要目的就是實現(xiàn)中斷處理函數(shù)的快進快出,那些對時間敏感、執(zhí)行速度快的操作可以放到中斷處理函數(shù)中,也就是上半部。剩下的所有工作都可以放到下半部去執(zhí)行,比如在上半部將數(shù)據(jù)拷貝到內(nèi)存中,關(guān)于數(shù)據(jù)的具體處理就可以放到下半部去執(zhí)行。至于哪些代碼屬于上半部,哪些代碼屬于下半部并沒有明確的規(guī)定,一切根據(jù)實際使用情況去判斷,這個就很考驗驅(qū)動編寫人員的功底了。這里有一些可以借鑒的參考點:
①、如果要處理的內(nèi)容不希望被其他中斷打斷,那么可以放到上半部。
②、如果要處理的任務(wù)對時間敏感,可以放到上半部。
③、如果要處理的任務(wù)與硬件有關(guān),可以放到上半部
④、除了上述三點以外的其他任務(wù),優(yōu)先考慮放到下半部。
下半部機制:
軟中斷
一開始 Linux 內(nèi)核提供了“bottom half”機制來實現(xiàn)下半部,簡稱“BH”。后面引入了軟中斷和 tasklet 來替代“BH”機制,完全可以使用軟中斷和 tasklet 來替代 BH,從 2.5 版本的 Linux內(nèi)核開始 BH 已經(jīng)被拋棄了。Linux 內(nèi)核使用結(jié)構(gòu)體 softirq_action 表示軟中斷, softirq_action結(jié)構(gòu)體定義在文件 include/linux/interrupt.h 中,內(nèi)容如下:
在 kernel/softirq.c 文件中一共定義了 10 個軟中斷,如下所示:
NR_SOFTIRQS 是枚舉類型,定義在文件 include/linux/interrupt.h 中,定義如下:
可以看出,一共有 10 個軟中斷,因此 NR_SOFTIRQS 為 10,因此數(shù)組softirq_vec 有 10 個元素。softirq_action 結(jié)構(gòu)體中的 action 成員變量就是軟中斷的服務(wù)函數(shù),數(shù)組 softirq_vec 是個全局數(shù)組,因此所有的 CPU(對于 SMP 系統(tǒng)而言)都可以訪問到,每個 CPU 都有自己的觸發(fā)和控制機制,并且只執(zhí)行自己所觸發(fā)的軟中斷。但是各個 CPU 所執(zhí)行的軟中斷服務(wù)函數(shù)確是相同的,都是數(shù)組 softirq_vec 中定義的 action 函數(shù)。要使用軟中斷,必須先使用 open_softirq 函數(shù)注冊對應(yīng)的軟中斷處理函數(shù),open_softirq 函數(shù)原型如下:
nr:要開啟的軟中斷,在示例代碼 51.1.2.3 中選擇一個。
action:軟中斷對應(yīng)的處理函數(shù)。
返回值:沒有返回值。
注冊好軟中斷以后需要通過 raise_softirq 函數(shù)觸發(fā),raise_softirq 函數(shù)原型如下:
軟中斷必須在編譯的時候靜態(tài)注冊!Linux 內(nèi)核使用 softirq_init 函數(shù)初始化軟中斷,softirq_init 函數(shù)定義在 kernel/softirq.c 文件里面,函數(shù)內(nèi)容如下:
tasklet
tasklet 是利用軟中斷來實現(xiàn)的另外一種下半部機制,在軟中斷和 tasklet 之間,建議大家使用 tasklet。Linux 內(nèi)核使用結(jié)構(gòu)體
第 489 行的 func 函數(shù)就是 tasklet 要執(zhí)行的處理函數(shù),用戶定義函數(shù)內(nèi)容,相當于中斷處理函數(shù)。如果要使用 tasklet,必須先定義一個 tasklet,然后使用 tasklet_init 函數(shù)初始化 tasklet,taskled_init 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
t:要初始化的 tasklet
func:tasklet 的處理函數(shù)。
data:要傳遞給 func 函數(shù)的參數(shù)
返回值:沒有返回值。
也可以使用宏DECLARE_TASKLET來一次性完成tasklet的定義和初始化DECLARE_TASKLET 定義在 include/linux/interrupt.h 文件中,定義如下:
其中 name 為要定義的 tasklet 名字,這個名字就是一個 tasklet_struct 類型的時候變量,func就是 tasklet 的處理函數(shù),data 是傳遞給 func 函數(shù)的參數(shù)。
在上半部,也就是中斷處理函數(shù)中調(diào)用 tasklet_schedule 函數(shù)就能使 tasklet 在合適的時間運行,tasklet_schedule 函數(shù)原型如下:
關(guān)于 tasklet 的參考使用示例如下所示:
工作隊列
工作隊列是另外一種下半部執(zhí)行方式,工作隊列在進程上下文執(zhí)行,工作隊列將要推后的工作交給一個內(nèi)核線程去執(zhí)行,因為工作隊列工作在進程上下文,因此工作隊列允許睡眠或重新調(diào)度。因此如果你要推后的工作可以睡眠那么就可以選擇工作隊列,否則的話就只能選擇軟中斷或 tasklet。
Linux 內(nèi)核使用 work_struct 結(jié)構(gòu)體表示一個工作,內(nèi)容如下(省略掉條件編譯):
這些工作組織成工作隊列,工作隊列使用 workqueue_struct 結(jié)構(gòu)體表示,內(nèi)容如下(省略掉條件編譯):
Linux 內(nèi)核使用工作者線程(worker thread)來處理工作隊列中的各個工作,Linux 內(nèi)核使用worker 結(jié)構(gòu)體表示工作者線程,worker 結(jié)構(gòu)體內(nèi)容如下:
從示例代碼 51.1.2.10 可以看出,每個 worker 都有一個工作隊列,工作者線程處理自己工作隊列中的所有工作。在實際的驅(qū)動開發(fā)中,我們只需要定義工作(work_struct)即可,關(guān)于工作隊列和工作者線程我們基本不用去管。簡單創(chuàng)建工作很簡單,直接定義一個 work_struct 結(jié)構(gòu)體變量即可,然后使用 INIT_WORK 宏來初始化工作,INIT_WORK 宏定義如下:
1.3 設(shè)備樹中斷信息節(jié)點
如果使用設(shè)備樹的話就需要在設(shè)備樹中設(shè)置好中斷屬性信息,Linux 內(nèi)核通過讀取設(shè)備樹中的中斷屬性信息來配置中斷。對于中斷控制器而言,設(shè)備樹綁定信息參考文檔Documentation/devicetree/bindings/arm/gic.txt。打開 imx6ull.dtsi 文件,其中的 intc 節(jié)點就是I.MX6ULL 的中斷控制器節(jié)點,節(jié)點內(nèi)容如下所示:
第 2 行,compatible 屬性值為“arm,cortex-a7-gic”在 Linux 內(nèi)核源碼中搜索“arm,cortex-a7-gic”即可找到 GIC 中斷控制器驅(qū)動文件。
第 3 行,#interrupt-cells 和#address-cells、#size-cells 一樣。表示此中斷控制器下設(shè)備的 cells大小,對于設(shè)備而言,會使用 interrupts 屬性描述中斷信息,#interrupt-cells 描述了 interrupts 屬性的 cells 大小,也就是一條信息有幾個 cells。每個 cells 都是 32 位整形值,對于 ARM 處理的GIC 來說,一共有 3 個 cells,這三個 cells 的含義如下:
第一個 cells:中斷類型,0 表示 SPI 中斷,1 表示 PPI 中斷。
第二個 cells:中斷號,對于 SPI 中斷來說中斷號的范圍為 0~987,對于 PPI 中斷來說中斷號的范圍為 0~15。
第三個 cells:標志,bit[3:0]表示中斷觸發(fā)類型,為 1 的時候表示上升沿觸發(fā),為 2 的時候表示下降沿觸發(fā),為 4 的時候表示高電平觸發(fā),為 8 的時候表示低電平觸發(fā)。bit[15:8]為 PPI 中斷的 CPU 掩碼。
第 4 行,interrupt-controller 節(jié)點為空,表示當前節(jié)點是中斷控制器。
對于 gpio 來說,gpio 節(jié)點也可以作為中斷控制器,比如 imx6ull.dtsi 文件中的 gpio5 節(jié)點內(nèi)容如下所示:
第 4 行,interrupts 描述中斷源信息,對于 gpio5 來說一共有兩條信息,中斷類型都是 SPI,觸發(fā)電平都是 IRQ_TYPE_LEVEL_HIGH。不同之處在于中斷源,一個是 74,一個是 75,打開可以打開《IMX6ULL 參考手冊》的“Chapter 3 Interrupts and DMA Events”章節(jié),找到表 3-1,有如圖 50.1.3.1 所示的內(nèi)容:
從圖 50.1.3.1 可以看出,GPIO5 一共用了 2 個中斷號,一個是 74,一個是75。其中 74 對 應(yīng) GPIO5_IO00~GPIO5_IO15 這低 16 個 IO,75 對應(yīng)GPIO5_IO16~GPIOI5_IO31 這高 16 位 IO。 第 8 行,interrupt-controller 表明了 gpio5 節(jié)點也是個中斷控制器,用于控制 gpio5 所有 IO
的中斷。
第 9 行,將#interrupt-cells 修改為 2。
打開 imx6ull-alientek-emmc.dts 文件,找到如下所示內(nèi)容:
1.4 獲取中斷號
編寫驅(qū)動的時候需要用到中斷號,我們用到中斷號,中斷信息已經(jīng)寫到了設(shè)備樹里面,因此可以通過 irq_of_parse_and_map 函數(shù)從 interupts 屬性中提取到對應(yīng)的設(shè)備號,函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
dev:設(shè)備節(jié)點。
index:索引號,interrupts 屬性可能包含多條中斷信息,通過 index 指定要獲取的信息。
返回值:中斷號。
如果使用 GPIO 的話,可以使用 gpio_to_irq 函數(shù)來獲取 gpio 對應(yīng)的中斷號,函數(shù)原型如下:
2.驅(qū)動代碼
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define IMX6UIRQ_CNT 1 /* 設(shè)備號個數(shù) */
#define IMX6UIRQ_NAME "irqDev" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0 按鍵值 */
#define INVAKEY 0XFF /* 無效的按鍵值 */
#define KEY_NUM 1 /* 按鍵數(shù)量 */
/* 可能會有好多按鍵,通過結(jié)構(gòu)體數(shù)組來描述按鍵 */
/* 中斷 IO 描述結(jié)構(gòu)體 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中斷號 */
unsigned char value; /* 按鍵對應(yīng)的鍵值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中斷服務(wù)函數(shù) */
};
/* irq設(shè)備結(jié)構(gòu)體 */
struct imx6uirq_dev {
dev_t devid; /* 設(shè)備號 */
struct cdev cdev; /* 字符設(shè)備 */
struct class *class; /* 類 */
struct device *device; /* 設(shè)備 */
int major; /* 注設(shè)備號 */
int minor; /* 次設(shè)備號 */
struct device_node *nd; /* 設(shè)備節(jié)點 */
atomic_t keyvalue; /* 有效的按鍵鍵值 */
atomic_t releasekey; /* 標記是否完成一次完成的按鍵*/
struct timer_list timer; /* 定義一個定時器*/
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按鍵描述數(shù)組 */
unsigned char curkeynum; /* 當前的按鍵號 */
};
struct imx6uirq_dev irqDev; /* 定義LED結(jié)構(gòu)體 */
/* @description : 中斷服務(wù)函數(shù),開啟定時器,延時 10ms,
* 定時器用于按鍵消抖。
* 兩個參數(shù)是中斷處理函數(shù)的必須寫法
* @param - irq : 中斷號
* @param - dev_id : 設(shè)備結(jié)構(gòu)。
* @return : 中斷執(zhí)行結(jié)果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
/* 采用定時器削抖,如果再定時器時間內(nèi)還是這個值,說明是真的按下了,在定時器中斷中處理 */
/* 這里設(shè)置為0是簡易處理,因為只有一個按鍵 */
/* 有其他按鍵要再建一個中斷處理函數(shù),并把curkeynum改成相應(yīng)的按鍵值 */
/* 注意不能所有按鍵用一個中斷函數(shù),第一是一起按的時候會出錯 */
/* 第二,無法用curkeynum判斷使用的是第幾個按鍵 */
dev->curkeynum = 0;
/* 傳遞給定時器的參數(shù),注意要強轉(zhuǎn),在中斷處理函數(shù)里面再轉(zhuǎn)回來 */
dev->timer.data = (volatile long)dev_id;
/* mod_timer會啟動定時器,第二個參數(shù)是要修改的超時時間 */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description : 定時器服務(wù)函數(shù),用于按鍵消抖,定時器到了以后
* 再次讀取按鍵值,如果按鍵還是處于按下狀態(tài)就表示按鍵有效。
* @param – arg : 設(shè)備結(jié)構(gòu)變量
* @return : 無
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
/* 因為只有一個按鍵,這里是0 */
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); /* 讀取 IO 值 */
if(value == 0){ /* 按下按鍵 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按鍵松開 */
/* 這種情況是按下再松開的松開,使用keyValue加上releaseKey */
/* 沒按下的話, releasekey一直為0*/
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 標記松開按鍵 */
}
}
/*
* @description : 按鍵 IO 初始化
* @param : 無
* @return : 無
*/
static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;
/* 1.獲取key節(jié)點 */
irqDev.nd = of_find_node_by_path("/key");
if (irqDev.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 對每個按鍵都提取 GPIO */
for (i = 0; i < KEY_NUM; i++) {
irqDev.irqkeydesc[i].gpio = of_get_named_gpio(irqDev.nd, "key-gpios", i);
if (irqDev.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化 key 所使用的 IO,并且設(shè)置成中斷模式 */
for (i = 0; i < KEY_NUM; i++) {
/* 先對每一個IO命名 */
/* 先對命名清0 */
memset(irqDev.irqkeydesc[i].name, 0, sizeof(irqDev.irqkeydesc[i].name));
/* 給IO命名 */
sprintf(irqDev.irqkeydesc[i].name, "KEY%d", i);
/* 請求GPIO */
gpio_request(irqDev.irqkeydesc[i].gpio, irqDev.irqkeydesc[i].name);
/* 設(shè)置GPIO為輸入 */
gpio_direction_input(irqDev.irqkeydesc[i].gpio);
/* 獲取中斷號,以下為兩個方法,都可以獲取到 */
/* 從interrupts屬性里面獲取 */
/* 注意i是根據(jù)設(shè)備樹里面設(shè)置了多少個就是多少個,都會獲取到 */
/* 下面的方法是通用的獲取中斷號的函數(shù) */
irqDev.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqDev.nd, i);
#if 0
/* 此方法是gpio獲取中斷號的方法 */
irqDev.irqkeydesc[i].irqnum = gpio_to_irq(irqDev.irqkeydesc[i].gpio);
#endif
printk("key%d:gpio=%d, irqnum=%d\r\n", i, irqDev.irqkeydesc[i].gpio,
irqDev.irqkeydesc[i].irqnum);
}
/* 2. 按鍵中斷初始化 */
/* 設(shè)置中斷處理函數(shù)和按鍵初始值 */
/* 因為只有一個key0.,所以這里也沒用循環(huán) */
irqDev.irqkeydesc[0].handler = key0_handler;
irqDev.irqkeydesc[0].value = KEY0VALUE;
/* 申請中斷 */
for (i = 0; i < KEY_NUM; i++) {
/* request_irq參數(shù)
* 中斷號,中斷函數(shù),中斷觸發(fā)類型,中斷名字,傳遞給中斷處理函數(shù)的參數(shù)(第二個),這里傳的結(jié)構(gòu)體
* */
ret = request_irq(irqDev.irqkeydesc[i].irqnum, irqDev.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
irqDev.irqkeydesc[i].name, &irqDev);
if(ret < 0){
printk("irq %d request failed!\r\n", irqDev.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 3. 創(chuàng)建定時器 */
init_timer(&irqDev.timer);
irqDev.timer.function = timer_function;
/* 注意下面不能讓定時器運行,因為要按下按鍵之后再運行 */
/* 啟動定時器通過mod_timer啟動,通常在初始化階段的定時器用的是add_timer */
return 0;
}
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &irqDev;
return 0;
}
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
//struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
return 0;
}
/*
* @description : 從設(shè)備讀取數(shù)據(jù)
* @param – filp : 要打開的設(shè)備文件(文件描述符)
* @param – buf : 返回給用戶空間的數(shù)據(jù)緩沖區(qū)
* @param - cnt : 要讀取的數(shù)據(jù)長度
* @param – offt : 相對于文件首地址的偏移
* @return : 讀取的字節(jié)數(shù),如果為負值,表示讀取失敗
*/
static ssize_t 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; /* 因為中斷中或了一個0x80,這里面去掉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;
}
/* 字符設(shè)備操作集 */
static const struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.release = imx6uirq_release,
.read = imx6uirq_read
};
/* 模塊入口函數(shù) */
static int __init imx6uirq_init(void)
{
/* 定義一些所需變量 */
int ret = 0;
/* 1. 注冊字符設(shè)備驅(qū)動 */
irqDev.major = 0;
if(irqDev.major) {
irqDev.devid = MKDEV(irqDev.major, 0);
ret = register_chrdev_region(irqDev.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME );
} else {
alloc_chrdev_region(&irqDev.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME );
irqDev.major = MAJOR(irqDev.devid);
irqDev.minor = MINOR(irqDev.devid);
}
if(ret < 0){
goto fail_devid;
}
printk("Make devid success! \r\n");
printk("major = %d, minor = %d \r\n", irqDev.major, irqDev.minor);
/* 2. 初始化cdev */
irqDev.cdev.owner = THIS_MODULE;
cdev_init(&irqDev.cdev, &imx6uirq_fops);
ret = cdev_add(&irqDev.cdev, irqDev.devid, IMX6UIRQ_CNT);
if (ret < 0){
goto fail_cdev;
} else {
printk("Cdev add sucess! \r\n");
}
/* 3. 自動創(chuàng)建設(shè)備節(jié)點 */
irqDev.class = class_create(THIS_MODULE, IMX6UIRQ_NAME );
if(IS_ERR(irqDev.class)) {
ret = PTR_ERR(irqDev.class);
goto fail_class;
} else {
printk("Class create sucess! \r\n");
}
irqDev.device = device_create(irqDev.class, NULL, irqDev.devid, NULL, IMX6UIRQ_NAME );
if(IS_ERR(irqDev.device)) {
ret = PTR_ERR(irqDev.device);
goto fail_device;
} else {
printk("Device create sucess! \r\n");
}
/* 4.初始化按鍵 */
atomic_set(&irqDev.keyvalue, INVAKEY);
atomic_set(&irqDev.releasekey, 0);
keyio_init();
printk("irqDev init! \r\n");
return 0;
/* 錯誤處理 */
fail_device:
class_destroy(irqDev.class);
fail_class:
cdev_del(&irqDev.cdev);
fail_cdev:
unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}
/* 模塊出口函數(shù) */
static void __exit imx6uirq_exit(void)
{
unsigned int i = 0;
/* 刪除定時器 */
del_timer_sync(&irqDev.timer);
/* 釋放中斷 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(irqDev.irqkeydesc[i].irqnum, &irqDev);
}
/* 1. 釋放設(shè)備號 */
cdev_del(&irqDev.cdev);
/* 2. 注銷設(shè)備號 */
unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
/* 3. 摧毀設(shè)備 */
device_destroy(irqDev.class, irqDev.devid);
/* 4.摧毀類 */
class_destroy(irqDev.class);
printk("irqDev exit! \r\n");
}
/* 模塊入口和出口注冊 */
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shao Zheming");
3.應(yīng)用代碼
#include
#include
#include
#include
#include
#include
#include
#include "linux/ioctl.h"
/*
* argc: 應(yīng)用程序參數(shù)個數(shù)
* argv[]: 參數(shù)是什么,具體的參數(shù),說明參數(shù)是字符串的形式
* .chrdevbaseApp <0:1> 0表示關(guān)燈,1表示開燈
* .chrdevbaseApp /dev/led 0 關(guān)燈
* .chrdevbaseApp /dev/led 1 開燈
* */
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
int fd, ret;
char *filename;
unsigned char data;
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("file %s open failed! \r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) { /* 數(shù)據(jù)讀取錯誤或者無效 */
} else { /* 數(shù)據(jù)讀取正確 */
if (data) /* 讀取到數(shù)據(jù) */
printf("key value = %#X\r\n", data);
}
}
close(fd);
return 0;
}
4.使用tasklet處理中斷下半部
#include
#include
#include
#include
#include
#include
#include
#include "linux/ioctl.h"
/*
* argc: 應(yīng)用程序參數(shù)個數(shù)
* argv[]: 參數(shù)是什么,具體的參數(shù),說明參數(shù)是字符串的形式
* .chrdevbaseApp <0:1> 0表示關(guān)燈,1表示開燈
* .chrdevbaseApp /dev/led 0 關(guān)燈
* .chrdevbaseApp /dev/led 1 開燈
* */
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
int fd, ret;
char *filename;
unsigned char data;
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("file %s open failed! \r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) { /* 數(shù)據(jù)讀取錯誤或者無效 */
} else { /* 數(shù)據(jù)讀取正確 */
if (data) /* 讀取到數(shù)據(jù) */
printf("key value = %#X\r\n", data);
}
}
close(fd);
return 0;
}
5. 工作隊列處理下半部
開發(fā)方式同tasklet
注意work是可以推導(dǎo)出設(shè)備dev結(jié)構(gòu)體的,所以一般將work放在dev結(jié)構(gòu)體里
到此,關(guān)于“l(fā)inux內(nèi)核有沒有中斷函數(shù)”的學(xué)習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習,快去試試吧!若想繼續(xù)學(xué)習更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
網(wǎng)頁標題:linux內(nèi)核有沒有中斷函數(shù)
文章出自:http://weahome.cn/article/gegcej.html