Linux 驅動學習筆記 - 中斷管理(十六)

Linux 驅動學習筆記 - 中斷管理(十六)

本系列均爲正點原子 Linux 驅動的學習筆記, 以便加深筆者記憶。如讀者想進一步學習,可以到正點原子官網中下載資料進行學習。

中斷號

每個中斷都有一箇中斷號,通過中斷號即可區分不同的中斷,有的資料也把中斷號叫做中斷線。在 Linux 內核中使用一個 int 變量表示中斷號。

request_irq

在 Linux 內核中要想使用某個中斷是需要申請的,request_irq 函數用於申請中斷 request_irq 函數可能導致睡眠,因此不能在中斷上下文環境或者禁止睡眠的代碼段中使用 request_irq 函數。request_irq 函數會激活(使能)中斷,所以不需要我們手動去使能中斷,該函數原型如下:

int request_irq(unsigned int irq, irq_handle_t handler, unsigned long flags, const char *name, void *dev);
函數 描述
irq 要申請的中斷號
handler 中斷處理函數,當中斷髮生以後就會執行此中斷處理函數
flahs 中斷標誌,在 include/linux/interrupt.h 裏面可以查看所有的標誌,見下表
name 中斷名字,設置以後可以在 /proc/interrupts 文件中看到對應的中斷名字
dev 如果將 flahs 設置爲 IRQ_SHARED 的話,dev 用來區分不同的中斷,一般情況下 dev 設置爲設備結構體, dev 會傳遞給中斷處理函數 irq_handler_t 的第二個參數
返回值 描述
0 中斷申請成功
其它負值 中斷申請失敗
-EBUSY 表示中斷已經被申請了

各個中斷標誌的含義

標誌 含義
IRQF_SHARED 多個設備共享一箇中斷線,共享的所有中斷都必須指定此標誌。如果使用共享中斷的話,request_irq 函數的 dev 參數就是唯一區分他們的標誌。
IRQF_ONESHOT 單次中斷,中斷執行一次就結束
IRQF_TRIGGER_NONE 無觸發
IRQF_TRIGGER_RISING 上升沿觸發
IRQF_TRIGGER_FALLING 下降沿觸發
IRQF_TRIGGER_HIGH 高電平觸發
IRQF_TRIGGER_LOW 低電平觸發

free_irq

使用中斷的時候使用 request_irq 函數申請,使用完成以後需要使用 free_irq 函數釋放掉相應的中斷。如果中斷不是共享的,那麼 free_irq 會刪除中斷處理函數並禁止中斷。函數原型如下:

void free_irq(unsigned int irq, void *dev)
參數 描述
irq 要釋放的中斷
dev 如果中斷設置爲共享(IRQF_SHARED) 的話,此參數用來區分具體的中斷。共享中斷只有在釋放最後中斷處理函數的時候纔會被禁止掉

中斷處理函數

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

irqreturn_t (*irq_handler_t) (int, void *)

第一個參數中斷處理函數要處理的相應的中斷號。
第二個參數是一個指向 void 的指針,也就是個通用指針,需要與 request_irq 函數的 dev 參數保持一致。用於區分共享中斷的不同設備,dev 也可以指向設備數據結構。中斷處理函數的返回值爲 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 是個枚舉類型,一共有三種返回值。一般中斷服務函數返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)

中斷使能與禁止

常用的中斷使用和禁止函數如下所示:

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

enable_irq 和 disable_irq 用於使能和禁止指定的中斷,irq 就是要禁止的中斷號。

disable_irq 函數要等到當前正在執行的中斷處理函數執行完才返回,因此使用者需要保證不會產生新的中斷,並且確保所有已經開始執行的中斷處理程序已經全部退出。在這種情況下,可以使用另外一箇中斷禁止函數:

void disable_irq_nosync(unsigned int irq)

disable_irq_nosync 函數調用以後立即返回,不會等待當前中斷處理程序執行完畢。上面三個函數都是使能或者禁止某一箇中斷,有時候我們需要關閉當前處理器的整個中斷系統,也就是在學習 STM32 的時候常說的關閉全局中斷,這個時候可以使用如下兩個函數:

local_irq_enable()
local_irq_disable()

保存中斷現場的中斷使能和禁止函數

local_irq_save(flags)
local_irq_restore(flags)

這兩個函數是一對,local_irq_save 函數用於禁止中斷,並且將中斷狀態保存在 flags 中。local_irq_restore 用於恢復中斷,將中斷到 flags 狀態。

設備樹中斷信息節點

如果使用設備樹的話就需要在設備樹中設置好中斷屬性信息,Linux 內核通過讀取設備樹中的中斷屬性信息來配置中斷。對於中斷控制器而言,設備樹綁定信息參考文檔 Documentation/devicetree/bindings/arm/gic.txt。打開 imx6ull.dtsi 文件,其中的 intc 節點就是 I.MX6ULL 的中斷控制器節點,節點內容如下所示:

intc: interrupt-controller@00a01000 {
    compatible = "arm,cortex-a7-gic"; 
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x00a01000 0x1000>,
    <0x00a02000 0x100>;
};
  • compatible 屬性值爲 arm,cortex-a7-gic

  • #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 掩碼。
  • interrupt-controller 節點爲空,表示當前節點是中斷控制器

對於 gpio 來說,gpio 節點也可以作爲中斷控制器,比如 imx6ull.dtsi 文件中的 gpio5 節點內容如下所示:

gpio5: gpio@020ac000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x020ac000 0x4000>;
    interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
    <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller; 
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};
  • interrupts 描述中斷源信息

  • interrupt-controller 表明了 gpio5 節點也是個中斷控制器,用於控制 gpio5 所有 IO的中斷。

  • #interrupt-cells 修改爲 2

打開 imx6ull-alientek-emmc.dts 文件,找到如下所示內容:

fxls8471@1e {
    compatible = "fsl,fxls8471";
    reg = <0x1e>;
    position = <0>;
    interrupt-parent = <&gpio5>;
    interrupts = <0 8>;
};
  • interrupt-parent 屬性設置中斷控制器,這裏使用 gpio5 作爲中斷控制器

  • interrupts 設置中斷信息,0 表示 GPIO5_IO00,8 表示低電平觸發

獲取中斷號

編寫驅動的時候需要用到中斷號,我們用到中斷號,中斷信息已經寫到了設備樹裏面,因此可以通過 irq_of_parse_and_map 函數從 interupts 屬性中提取到對應的設備號,函數原型如下:

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
參數 描述
dev 設備節點
index 索引號,interrupts 屬性肯能包含多箇中斷信息,通過 index 指定要獲取的信息
返回值 描述
中斷號

如果使用 GPIO 的話,可以使用 gpio_to_irq 函數來獲取 gpio 對應的中斷號,函數原型如下

int gpio_to_irq(unsigned int gpio)
參數 描述
gpio 要獲取的 GPIO 的編號
返回值 描述
GPIO 對應的中斷號

實驗程序

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

#define IMX6UIRQ_CNT 1           /* 設備號個數 	*/
#define IMX6UIRQ_NAME "imx6uirq" /* 名字 		*/
#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 *); /* 中斷服務函數 */
};

/* imx6uirq設備結構體 */
struct imx6uirq_dev
{
    dev_t devid;                            /* 設備號 	 */
    struct cdev 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 imx6uirq; /* irq設備 */

/* @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;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定時 */
    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;

    num = dev->curkeynum;
    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 | keydesc->value);
        atomic_set(&dev->releasekey, 1); /* 標記鬆開按鍵,即完成一次完整的按鍵過程 */
    }
}

/*
 * @description	: 按鍵IO初始化
 * @param 		: 無
 * @return 		: 無
 */
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 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].gpio < 0)
        {
            printk("can't get key%d\r\n", i);
        }
    }

    /* 初始化key所使用的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_direction_input(imx6uirq.irqkeydesc[i].gpio);
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
		imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
        printk("key%d:gpio=%d, irqnum=%d\r\n", i, imx6uirq.irqkeydesc[i].gpio,
               imx6uirq.irqkeydesc[i].irqnum);
    }
    /* 申請中斷 */
    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,
                          IRQF_TRIGGER_FALLING | IRQF_TRIGGER_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;
}

/*
 * @description		: 打開設備
 * @param - inode 	: 傳遞給驅動的inode
 * @param - filp 	: 設備文件,file結構體有個叫做private_data的成員變量
 * 					  一般在open的時候將private_data指向設備結構體。
 * @return 			: 0 成功;其他 失敗
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq; /* 設置私有數據 */
    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;
            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 struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};

/*
 * @description	: 驅動入口函數
 * @param 		: 無
 * @return 		: 無
 */
static int __init imx6uirq_init(void)
{
    /* 1、構建設備號 */
    if (imx6uirq.major)
    {
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    }
    else
    {
        alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2、註冊字符設備 */
    cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
    cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

    /* 3、創建類 */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class))
    {
        return PTR_ERR(imx6uirq.class);
    }

    /* 4、創建設備 */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device))
    {
        return PTR_ERR(imx6uirq.device);
    }

    /* 5、初始化按鍵 */
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);
    keyio_init();
    return 0;
}

/*
 * @description	: 驅動出口函數
 * @param 		: 無
 * @return 		: 無
 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;
    /* 刪除定時器 */
    del_timer_sync(&imx6uirq.timer); /* 刪除定時器 */

    /* 釋放中斷 */
    for (i = 0; i < KEY_NUM; i++)
    {
        free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
    }
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("tyustli");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章