Exynos4412 中斷驅動開發(一)—— 中斷基礎及中斷的註冊過程
一、中斷基礎概念
所謂中斷,指CPU在執行程式的過程中,出現了某些突發事件即待處理,CPU必須暫停當前的程式。轉去處理突發事件,處理完畢後CPU又返回原程式被中斷的位置並繼續執行。
1、中斷分類
a -- 內部中斷和外部中斷
根據中斷的的來源,中斷可以分為內部中斷和外部中斷:
內部中斷,其中斷源來自CPU內部(軟體中斷指令、溢位、除法錯誤等),例如,作業系統從使用者態切換到核心態需藉助CPU內部的軟中斷;
外部中斷,其中斷源來自CPU外部,由外設提出請求;
b -- 可遮蔽中斷與不遮蔽中斷
根據中斷是否可以遮蔽分為可遮蔽中斷與不遮蔽中斷:
可遮蔽中斷,其可以通過遮蔽字被遮蔽,遮蔽後,該中斷不再得到響應;
不遮蔽中斷,其不能被遮蔽;
c -- 向量中斷和非向量中斷
根據中斷入口跳轉方法的不同,分為向量中斷和非向量中斷:
向量中斷,採用向量中斷的CPU通常為不同的中斷分配不同的中斷號,當檢測到某中斷號的中斷到來後,就自動跳轉到與該中斷號對應的地址執行。不同的中斷號有不同的入口地址;
非向量中斷,其多箇中斷共享一個入口地址,進入該入口地址後再通過軟體判斷中斷標誌來標識具體是哪個中斷。
也就是說,向量中斷由硬體提供中斷服務程式入口地址,非向量中斷由軟體提供中斷服務入口地址。
2、中斷ID
a -- IRQ number
cpu給中斷的一個編號,一個IRQ number是一個虛擬的interrupt ID,和硬體無關;
b -- HW interrupt ID
對於中斷控制器而言,它收集了多個外設的irq request line,要向cpu傳遞,GIC要對外設進行編碼,GIC就用HW interrupt ID來標示外部中斷;
3、SMP情況下中斷兩種形態
1-Nmode :只有一個processor處理器
N-N :所有的processor都是獨立收到中斷的
GIC:SPI使用1-Nmode PPI 和 sgi使用N-Nmode
二、中斷程式設計
1、 申請IRQ
在linux核心中用於申請中斷的函式是request_irq(),函式原型在Kernel/irq/manage.c中定義:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
相關引數:
a -- irq是要申請的硬體中斷號。另外,這裡要思考的問題是,這個irq 是怎麼得到的?這裡我們在裝置樹中獲取,具體解析見:
b -- handler是向系統註冊的中斷處理函式,是一個回撥函式,中斷髮生時,系統呼叫這個函式,dev_id引數將被傳遞給它。
c -- irqflags是中斷處理的屬性,若設定了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版已經不支援了),則表示中斷處理程式是快速處理程式,快速處理程式被呼叫時遮蔽所有中斷,慢速處理程式不遮蔽;若設定了IRQF_SHARED (老版本中的SA_SHIRQ),則表示多個裝置共享中斷,若設定了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示對系統熵有貢獻,對系統獲取隨機數有好處。(這幾個flag是可以通過或的方式同時使用的)
d -- devname設定中斷名稱,在cat /proc/interrupts中可以看到此名稱。
e -- dev_id在中斷共享時會用到,一般設定為這個裝置的裝置結構體或者NULL。
request_irq()返回0表示成功,返回-INVAL表示中斷號無效或處理函式指標為NULL,返回-EBUSY表示中斷已經被佔用且不能共享。
頂半部 handler 的型別 irq_handler_t 定義為:
typedef irqreturn_t (*irq_handler_t)(int, void *);
引數1:中斷號
引數2 :引數
在Interrupt.h (e:\linux-3.14-fs4412\include\linux) 18323 2014/3/31中定義
IRQ_NONE 共享中斷,如果不是我的裝置產生的中斷,就返回該值
IRQ_HANDLED 中斷處理函式正確執行了就返回該值
IRQ_WAKE_THREAD = (1 << 1)
2、釋放IRQ
與request_irq()相對應的函式為 free_irq(),free_irq()的原型為:
void free_irq(unsigned int irq, void *dev_id)
free_irq()引數的定義與request_irq()相同。
三、中斷註冊過程分析
我們每次用中斷的時候就是註冊一箇中斷函式。request_irq首先生成一個irqaction結構,其次根據中斷號 找到irq_desc陣列項(還記得吧,核心中irq_desc是一個數組,沒一項對應一箇中斷號),然後將irqaction結構新增到 irq_desc中的action連結串列中。當然還做一些其他的工作,註冊完成後,中斷函式就可以發生並被處理了。
irq_desc 核心中記錄一個irq_desc的陣列,陣列的每一項對應一箇中斷或者一組中斷使用同一個中斷號,一句話irq_desc幾乎記錄所有中斷相關的東西,這個結構是中斷的核心。其中包括倆個重要的結構irq_chip 和irqaction 。
1、 irq_chip
irq_chip 裡面基本上是一些回撥函式,其中大多用於操作底層硬體,設定暫存器,其中包括設定GPIO為中斷輸入就是其中的一個回撥函式,分析一些原始碼
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq); //啟動中斷
void (*shutdown)(unsigned int irq); //關閉中斷
void (*enable)(unsigned int irq); // 使能中斷
void (*disable)(unsigned int irq); // 禁止中斷
void (*ack)(unsigned int irq); //中斷應答函式,就是清除中斷標識函式
void (*mask)(unsigned int irq); //中斷遮蔽函式
void (*mask_ack)(unsigned int irq); //遮蔽中斷應答函式,一般用於電平觸發方式,需要先遮蔽再應答
void (*unmask)(unsigned int irq); //開啟中斷
void (*eoi)(unsigned int irq);
void (*end)(unsigned int irq);
int (*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int (*retrigger)(unsigned int irq);
int (*set_type)(unsigned int irq, unsigned int flow_type); //設定中斷型別,其中包括設定GPIO口為中斷輸入
int (*set_wake)(unsigned int irq, unsigned int on);
void (*bus_lock)(unsigned int irq); //上鎖函式
void (*bus_sync_unlock)(unsigned int irq); //解鎖
/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char *typename;
};
我們可以看到這裡實現的是一個框架,需要我們進一步的填充裡面的函式。我們在分析另一個結構irqaction
2、irqaction
include/linux/interrupt.h
struct irqaction {
irq_handler_t handler; //使用者註冊的中斷處理函式
unsigned long flags; //中斷標識
const char *name; //使用者註冊的中斷名字,cat/proc/interrupts時可以看到
void *dev_id; //可以是使用者傳遞的引數或者用來區分共享中斷
struct irqaction *next; //irqaction結構鏈,一個共享中斷可以有多箇中斷處理函式
int irq; //中斷號
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};
我們用irq_request函式註冊中斷時,主要做倆個事情,根據中斷號生成一個irqaction結構並新增到irq_desc中的 action結構連結串列,另一發面做一些初始化的工作,其中包括設定中斷觸發方式,設定一些irq_chip結構中沒有初始化的函式為預設,開啟中斷,設定 GPIO口為中斷輸入模式(這裡後面有詳細流程分析)。
四、例項分析
1、driver.c
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
static int major = 250;
static wait_queue_head_t wq;
static int have_data = 0;
static int key;
static struct resource *res1;
static struct resource *res2;
static irqreturn_t key_handler(int irqno, void *dev)
{
// printk("key_handler irqno =%d \n",irqno);
if(irqno == res1->start)
{
key = 1;
}
if(irqno == res2->start)
{
key = 2;
}
have_data = 1;
wake_up_interruptible(&wq);
return IRQ_HANDLED;
}
static int key_open (struct inode *inod, struct file *filep)
{
return 0;
}
static ssize_t key_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
{
wait_event_interruptible(wq, have_data==1);
if(copy_to_user(buf,&key,sizeof(int)))
{
return -EFAULT;
}
have_data = 0;
return len;
}
static int key_release(struct inode *inode, struct file *filep)
{
return 0;
}
static struct file_operations key_ops =
{
.open = key_open,
.release = key_release,
.read = key_read,
};
static int hello_probe(struct platform_device *pdev)
{
int ret;
printk("match 0k \n");
res1 = platform_get_resource(pdev,IORESOURCE_IRQ, 0);
res2 = platform_get_resource(pdev,IORESOURCE_IRQ, 1);
ret = request_irq(res1->start,key_handler,IRQF_TRIGGER_FALLING|IRQF_DISABLED,"key1",NULL);
ret = request_irq(res2->start,key_handler,IRQF_TRIGGER_FALLING|IRQF_DISABLED,"key2",NULL);
register_chrdev( major, "key", &key_ops);
init_waitqueue_head(&wq);
return 0;
}
static int hello_remove(struct platform_device *pdev)
{
free_irq(res1->start,NULL);
free_irq(res2->start,NULL);
unregister_chrdev( major, "key");
return 0;
}
static struct of_device_id key_id[]=
{
{.compatible = "fs4412,key" },
};
static struct platform_driver hello_driver=
{
.probe = hello_probe,
.remove = hello_remove,
.driver ={
.name = "bigbang",
.of_match_table = key_id,
},
};
static int hello_init(void)
{
printk("hello_init");
return platform_driver_register(&hello_driver);
}
static void hello_exit(void)
{
platform_driver_unregister(&hello_driver);
printk("hello_exit \n");
return;
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
main()
{
int fd,len;
int key;
fd = open("/dev/hello",O_RDWR);
if(fd<0)
{
perror("open fail \n");
return ;
}
while(1)
{
read(fd,&key,4);
printf("============key%d==================\n",key);
}
close(fd);
}