linux核心有中斷函數嗎

2022-11-11 18:02:22

linux核心有中斷函數。在Linux核心中要想使用某個中斷是需要申請的,而request_irq()函數用於申請中斷,中斷使用完成以後就要通過free_irq()函數釋放掉相應的中斷;還有enable_irq()和disable_irq(),它們用於使能和禁止指定的中斷。

程式設計師必備介面測試偵錯工具:

本教學操作環境:linux7.3系統、Dell G3電腦。

1.Linux中斷

1.1 Linux中斷API函數

request_irq函數

在 Linux 核心中要想使用某個中斷是需要申請的,request_irq 函數用於申請中斷,request_irq函數可能會導致睡眠,因此不能在中斷上下文或者其他禁止睡眠的程式碼段中使用 request_irq 函數。request_irq 函數會啟用(使能)中斷,所以不需要我們手動去使能中斷,request_irq 函數原型如下:
image
irq:要申請中斷的中斷號。
handler:中斷處理常式,當中斷髮生以後就會執行此中斷處理常式。
flags:中斷標誌,可以在檔案 include/linux/interrupt.h 裡面檢視所有的中斷標誌
image
name:中斷名字,設定以後可以在/proc/interrupts 檔案中看到對應的中斷名字。
dev:如果將 flags 設定為 IRQF_SHARED 的話,dev 用來區分不同的中斷,一般情況下將dev 設定為裝置結構體,dev 會傳遞給中斷處理常式 irq_handler_t 的第二個引數。
返回值:0 中斷申請成功,其他負值 中斷申請失敗,如果返回-EBUSY 的話表示中斷已經被申請了。

free_irq

使用中斷的時候需要通過 request_irq 函數申請,使用完成以後就要通過 free_irq 函數釋放掉相應的中斷。如果中斷不是共用的,那麼 free_irq 會刪除中斷處理常式並且禁止中斷。free_irq函數原型如下所示:
image
函數引數和返回值含義如下:
irq:要釋放的中斷。
dev:如果中斷設定為共用(IRQF_SHARED)的話,此引數用來區分具體的中斷。共用中斷只有在釋放最後中斷處理常式的時候才會被禁止掉。
返回值:無。

中斷處理常式

使用 request_irq 函數申請中斷的時候需要設定中斷處理常式,中斷處理常式格式如下所示:
image
image

中斷使能和禁止函數

常用的中斷使用和禁止函數如下所示:
image
enable_irq 和 disable_irq 用於使能和禁止指定的中斷,irq 就是要禁止的中斷號。disable_irq函數要等到當前正在執行的中斷處理常式執行完才返回,因此使用者需要保證不會產生新的中斷,並且確保所有已經開始執行的中斷處理程式已經全部退出。在這種情況下,可以使用另外一箇中斷禁止函數:
image
disable_irq_nosync 函數呼叫以後立即返回,不會等待當前中斷處理程式執行完畢。上面三個函數都是使能或者禁止某一箇中斷,有時候我們需要關閉當前處理器的整個中斷系統,也就是在學習 STM32 的時候常說的關閉全域性中斷,這個時候可以使用如下兩個函數:
image
local_irq_enable 用於使能當前處理器中斷系統,local_irq_disable 用於禁止當前處理器中斷系統。假如 A 任務呼叫 local_irq_disable 關閉全域性中斷 10S,當關閉了 2S 的時候 B 任務開始執行,B 任務也呼叫 local_irq_disable 關閉全域性中斷 3S,3 秒以後 B 任務呼叫 local_irq_enable 函數將全域性中斷開啟了。此時才過去 2+3=5 秒的時間,然後全域性中斷就被開啟了,此時 A 任務要關閉 10S 全域性中斷的願望就破滅了,然後 A 任務就「生氣了」,結果很嚴重,可能系統都要被A 任務整崩潰。為了解決這個問題,B 任務不能直接簡單粗暴的通過 local_irq_enable 函數來開啟全域性中斷,而是將中斷狀態恢復到以前的狀態,要考慮到別的任務的感受,此時就要用到下面兩個函數:
image

1.2 上半部和下半部

在有些資料中也將上半部和下半部稱為頂半部和底半部,都是一個意思。我們在使用request_irq 申請中斷的時候註冊的中斷服務函數屬於中斷處理的上半部,只要中斷觸發,那麼中斷處理常式就會執行。我們都知道中斷處理常式一定要快點執行完畢,越短越好,但是現實往往是殘酷的,有些中斷處理過程就是比較費時間,我們必須要對其進行處理,縮小中斷處理常式的執行時間。比如電容觸控式螢幕通過中斷通知 SOC 有觸控事件發生,SOC 響應中斷,然後通過 IIC 介面讀取觸控座標值並將其上報給系統。但是我們都知道 IIC 的速度最高也只有400Kbit/S,所以在中斷中通過 IIC 讀取資料就會浪費時間。我們可以將通過 IIC 讀取觸控資料的操作暫後執行,中斷處理常式僅僅相應中斷,然後清除中斷標誌位即可。這個時候中斷處理過程就分為了兩部分:
上半部:上半部就是中斷處理常式,那些處理過程比較快,不會佔用很長時間的處理就可以放在上半部完成。
下半部:如果中斷處理過程比較耗時,那麼就將這些比較耗時的程式碼提出來,交給下半部去執行,這樣中斷處理常式就會快進快出。
因此,Linux 核心將中斷分為上半部和下半部的主要目的就是實現中斷處理常式的快進快出,那些對時間敏感、執行速度快的操作可以放到中斷處理常式中,也就是上半部。剩下的所有工作都可以放到下半部去執行,比如在上半部將資料拷貝到記憶體中,關於資料的具體處理就可以放到下半部去執行。至於哪些程式碼屬於上半部,哪些程式碼屬於下半部並沒有明確的規定,一切根據實際使用情況去判斷,這個就很考驗驅動編寫人員的功底了。這裡有一些可以借鑑的參考點:
①、如果要處理的內容不希望被其他中斷打斷,那麼可以放到上半部。
②、如果要處理的任務對時間敏感,可以放到上半部。
③、如果要處理的任務與硬體有關,可以放到上半部
④、除了上述三點以外的其他任務,優先考慮放到下半部。
下半部機制:

軟中斷

一開始 Linux 核心提供了「bottom half」機制來實現下半部,簡稱「BH」。後面引入了軟中斷和 tasklet 來替代「BH」機制,完全可以使用軟中斷和 tasklet 來替代 BH,從 2.5 版本的 Linux核心開始 BH 已經被拋棄了。Linux 核心使用結構體 softirq_action 表示軟中斷, softirq_action結構體定義在檔案 include/linux/interrupt.h 中,內容如下:
image
在 kernel/softirq.c 檔案中一共定義了 10 個軟中斷,如下所示:
image
NR_SOFTIRQS 是列舉型別,定義在檔案 include/linux/interrupt.h 中,定義如下:
image
可以看出,一共有 10 個軟中斷,因此 NR_SOFTIRQS 為 10,因此陣列softirq_vec 有 10 個元素。softirq_action 結構體中的 action 成員變數就是軟中斷的服務函數,陣列 softirq_vec 是個全域性陣列,因此所有的 CPU(對於 SMP 系統而言)都可以存取到,每個 CPU 都有自己的觸發和控制機制,並且只執行自己所觸發的軟中斷。但是各個 CPU 所執行的軟中斷服務函數確是相同的,都是陣列 softirq_vec 中定義的 action 函數。要使用軟中斷,必須先使用 open_softirq 函數註冊對應的軟中斷處理常式,open_softirq 函數原型如下:
image
nr:要開啟的軟中斷,在範例程式碼 51.1.2.3 中選擇一個。
action:軟中斷對應的處理常式。
返回值:沒有返回值。
註冊好軟中斷以後需要通過 raise_softirq 函數觸發,raise_softirq 函數原型如下:
image
軟中斷必須在編譯的時候靜態註冊!Linux 核心使用 softirq_init 函數初始化軟中斷,softirq_init 函數定義在 kernel/softirq.c 檔案裡面,函數內容如下:
image

tasklet

tasklet 是利用軟中斷來實現的另外一種下半部機制,在軟中斷和 tasklet 之間,建議大家使用 tasklet。Linux 核心使用結構體
image
第 489 行的 func 函數就是 tasklet 要執行的處理常式,使用者定義函數內容,相當於中斷處理常式。如果要使用 tasklet,必須先定義一個 tasklet,然後使用 tasklet_init 函數初始化 tasklet,taskled_init 函數原型如下:
image
image
函數引數和返回值含義如下:
t:要初始化的 tasklet
func:tasklet 的處理常式。
data:要傳遞給 func 函數的引數
返回值:沒有返回值。
也可以使用宏DECLARE_TASKLET來一次性完成tasklet的定義和初始化DECLARE_TASKLET 定義在 include/linux/interrupt.h 檔案中,定義如下:
image
其中 name 為要定義的 tasklet 名字,這個名字就是一個 tasklet_struct 型別的時候變數,func就是 tasklet 的處理常式,data 是傳遞給 func 函數的引數。
在上半部,也就是中斷處理常式中呼叫 tasklet_schedule 函數就能使 tasklet 在合適的時間執行,tasklet_schedule 函數原型如下:
image
關於 tasklet 的參考使用範例如下所示:
image
image

工作佇列

工作佇列是另外一種下半部執行方式,工作佇列在程序上下文執行,工作佇列將要推後的工作交給一個核心執行緒去執行,因為工作佇列工作在程序上下文,因此工作佇列允許睡眠或重新排程。因此如果你要推後的工作可以睡眠那麼就可以選擇工作佇列,否則的話就只能選擇軟中斷或 tasklet。
Linux 核心使用 work_struct 結構體表示一個工作,內容如下(省略掉條件編譯):
image
這些工作組織成工作佇列,工作佇列使用 workqueue_struct 結構體表示,內容如下(省略掉條件編譯):
image
Linux 核心使用工作者執行緒(worker thread)來處理工作佇列中的各個工作,Linux 核心使用worker 結構體表示工作者執行緒,worker 結構體內容如下:
image
從範例程式碼 51.1.2.10 可以看出,每個 worker 都有一個工作佇列,工作者執行緒處理自己工作佇列中的所有工作。在實際的驅動開發中,我們只需要定義工作(work_struct)即可,關於工作佇列和工作者執行緒我們基本不用去管。簡單建立工作很簡單,直接定義一個 work_struct 結構體變數即可,然後使用 INIT_WORK 宏來初始化工作,INIT_WORK 宏定義如下:
image
image
image

1.3 裝置樹中斷資訊節點

如果使用裝置樹的話就需要在裝置樹中設定好中斷屬性資訊,Linux 核心通過讀取裝置樹中的中斷屬性資訊來設定中斷。對於中斷控制器而言,裝置樹繫結資訊參考檔案Documentation/devicetree/bindings/arm/gic.txt。開啟 imx6ull.dtsi 檔案,其中的 intc 節點就是I.MX6ULL 的中斷控制器節點,節點內容如下所示:
image
第 2 行,compatible 屬性值為「arm,cortex-a7-gic」在 Linux 核心原始碼中搜尋「arm,cortex-a7-gic」即可找到 GIC 中斷控制器驅動檔案。
第 3 行,#interrupt-cells 和#address-cells、#size-cells 一樣。表示此中斷控制器下裝置的 cells大小,對於裝置而言,會使用 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]表示中斷觸發型別,為 1 的時候表示上升沿觸發,為 2 的時候表示下降沿觸發,為 4 的時候表示高電平觸發,為 8 的時候表示低電平觸發。bit[15:8]為 PPI 中斷的 CPU 掩碼。
第 4 行,interrupt-controller 節點為空,表示當前節點是中斷控制器。
對於 gpio 來說,gpio 節點也可以作為中斷控制器,比如 imx6ull.dtsi 檔案中的 gpio5 節點內容如下所示:
image
第 4 行,interrupts 描述中斷源資訊,對於 gpio5 來說一共有兩條資訊,中斷型別都是 SPI,觸發電平都是 IRQ_TYPE_LEVEL_HIGH。不同之處在於中斷源,一個是 74,一個是 75,開啟可以開啟《IMX6ULL 參考手冊》的「Chapter 3 Interrupts and DMA Events」章節,找到表 3-1,有如圖 50.1.3.1 所示的內容:
image
從圖 50.1.3.1 可以看出,GPIO5 一共用了 2 箇中斷號,一個是 74,一個是75。其中 74 對 應 GPIO5_IO00~GPIO5_IO15 這低 16 個 IO,75 對應GPIO5_IO16~GPIOI5_IO31 這高 16 位 IO。 第 8 行,interrupt-controller 表明了 gpio5 節點也是個中斷控制器,用於控制 gpio5 所有 IO
的中斷。
第 9 行,將#interrupt-cells 修改為 2。
開啟 imx6ull-alientek-emmc.dts 檔案,找到如下所示內容:
image
image
image

1.4 獲取中斷號

編寫驅動的時候需要用到中斷號,我們用到中斷號,中斷資訊已經寫到了裝置樹裡面,因此可以通過 irq_of_parse_and_map 函數從 interupts 屬性中提取到對應的裝置號,函數原型如下:
image
函數引數和返回值含義如下:
dev:裝置節點。
index:索引號,interrupts 屬性可能包含多條中斷資訊,通過 index 指定要獲取的資訊。
返回值:中斷號。
如果使用 GPIO 的話,可以使用 gpio_to_irq 函數來獲取 gpio 對應的中斷號,函數原型如下:
image

2.驅動程式碼

#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/errno.h> 
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

#define IMX6UIRQ_CNT            1               /* 裝置號個數 */
#define IMX6UIRQ_NAME           "irqDev"        /* 名字 */
#define KEY0VALUE               0X01            /* KEY0 按鍵值 */
#define INVAKEY                 0XFF            /* 無效的按鍵值 */
#define KEY_NUM                 1               /* 按鍵數量 */

/* 可能會有好多按鍵,通過結構體陣列來描述按鍵 */
/* 中斷 IO 描述結構體 */
struct irq_keydesc {
    int gpio;                               /* gpio */
    int irqnum;                             /* 中斷號 */
    unsigned char value;                    /* 按鍵對應的鍵值 */
    char name[10];                          /* 名字 */
    irqreturn_t (*handler)(int, void *);    /* 中斷服務函數 */
};

/* irq裝置結構體 */
struct imx6uirq_dev {
    dev_t               devid;          /* 裝置號 */
    struct cdev         cdev;           /* 字元裝置 */
    struct class        *class;         /* 類 */
    struct device       *device;        /* 裝置 */
    int                 major;          /* 注裝置號 */
    int                 minor;          /* 次裝置號 */
    struct device_node  *nd;            /* 裝置節點 */

    atomic_t            keyvalue;       /* 有效的按鍵鍵值 */
    atomic_t            releasekey;     /* 標記是否完成一次完成的按鍵*/
    struct timer_list   timer;          /* 定義一個定時器*/
    struct irq_keydesc  irqkeydesc[KEY_NUM]; /* 按鍵描述陣列 */
    unsigned char       curkeynum;      /* 當前的按鍵號 */
};

struct imx6uirq_dev  irqDev; /* 定義LED結構體 */

/* @description : 中斷服務函數,開啟定時器,延時 10ms,
* 定時器用於按鍵消抖。
* 兩個引數是中斷處理常式的必須寫法
* @param - irq : 中斷號
* @param - dev_id : 裝置結構。
* @return : 中斷執行結果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    /* 採用定時器削抖,如果再定時器時間內還是這個值,說明是真的按下了,在定時器中斷中處理 */
    /* 這裡設定為0是簡易處理,因為只有一個按鍵 */
    /* 有其他按鍵要再建一箇中斷處理常式,並把curkeynum改成相應的按鍵值 */
    /* 注意不能所有按鍵用一箇中斷函數,第一是一起按的時候會出錯 */
    /* 第二,無法用curkeynum判斷使用的是第幾個按鍵 */
    dev->curkeynum = 0;
    /* 傳遞給定時器的引數,注意要強轉,在中斷處理常式裡面再轉回來 */
    dev->timer.data = (volatile long)dev_id;
    /* mod_timer會啟動定時器,第二個引數是要修改的超時時間 */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
    return IRQ_RETVAL(IRQ_HANDLED);
}

 /* @description : 定時器服務函數,用於按鍵消抖,定時器到了以後
* 再次讀取按鍵值,如果按鍵還是處於按下狀態就表示按鍵有效。
* @param – arg : 裝置結構變數
* @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節點 */
    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,並且設定成中斷模式 */
    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);
        
        /* 設定GPIO為輸入 */
        gpio_direction_input(irqDev.irqkeydesc[i].gpio); 

        /* 獲取中斷號,以下為兩個方法,都可以獲取到 */
        /* 從interrupts屬性裡面獲取 */
        /* 注意i是根據裝置樹裡面設定了多少個就是多少個,都會獲取到 */
        /* 下面的方法是通用的獲取中斷號的函數 */
        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. 按鍵中斷初始化 */
    /* 設定中斷處理常式和按鍵初始值 */
    /* 因為只有一個key0.,所以這裡也沒用迴圈 */
    irqDev.irqkeydesc[0].handler = key0_handler;
    irqDev.irqkeydesc[0].value = KEY0VALUE;
     /* 申請中斷 */
    for (i = 0; i < KEY_NUM; i++) {
        /* request_irq引數
         * 中斷號,中斷函數,中斷觸發型別,中斷名字,傳遞給中斷處理常式的引數(第二個),這裡傳的結構體
         * */
        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. 建立定時器 */
    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 : 從裝置讀取資料
* @param – filp : 要開啟的裝置檔案(檔案描述符)
* @param – buf : 返回給使用者空間的資料緩衝區
* @param - cnt : 要讀取的資料長度
* @param – offt : 相對於檔案首地址的偏移
* @return : 讀取的位元組數,如果為負值,表示讀取失敗
*/
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;
}

/* 字元裝置操作集 */
static const struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .release = imx6uirq_release,
    .read = imx6uirq_read
};

/* 模組入口函數 */
static int __init imx6uirq_init(void)
{
    /* 定義一些所需變數 */
    int ret = 0;

    /* 1. 註冊字元裝置驅動 */
    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. 自動建立裝置節點 */
    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;
}

/* 模組出口函數 */
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. 釋放裝置號 */
    cdev_del(&irqDev.cdev);
    /* 2. 登出裝置號 */
    unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
    /* 3. 摧毀裝置 */
    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.應用程式碼

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"

/* 
 * argc: 應用程式引數個數
 * argv[]: 引數是什麼,具體的引數,說明引數是字串的形式
 * .chrdevbaseApp <filename> <0:1> 0表示關燈,1表示開燈
 * .chrdevbaseApp /dev/led 0 關燈
 * .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) { /* 資料讀取錯誤或者無效 */
        
        } else { /* 資料讀取正確 */
            if (data) /* 讀取到資料 */
                printf("key value = %#X\r\n", data);
        }
    }

    close(fd);
    return 0;
}
登入後複製
登入後複製

4.使用tasklet處理中斷下半部

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"

/* 
 * argc: 應用程式引數個數
 * argv[]: 引數是什麼,具體的引數,說明引數是字串的形式
 * .chrdevbaseApp <filename> <0:1> 0表示關燈,1表示開燈
 * .chrdevbaseApp /dev/led 0 關燈
 * .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) { /* 資料讀取錯誤或者無效 */
        
        } else { /* 資料讀取正確 */
            if (data) /* 讀取到資料 */
                printf("key value = %#X\r\n", data);
        }
    }

    close(fd);
    return 0;
}
登入後複製
登入後複製

5. 工作佇列處理下半部

開發方式同tasklet
注意work是可以推匯出裝置dev結構體的,所以一般將work放在dev結構體裡

相關推薦:《Linux視訊教學

以上就是linux核心有中斷函數嗎的詳細內容,更多請關注TW511.COM其它相關文章!