1. 程式人生 > >Exynos4412 中斷驅動開發(一)—— 中斷基礎及中斷的註冊過程

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