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");