Linux中斷處理API介紹

Linux中斷處理API介紹

一、註冊中斷

  Linux內核提供註冊中斷的方法有requese_irq和request_threaded_irq兩個函數。

 1.1、request_threaded_irq函數內核源碼分析

/**
 *	request_threaded_irq - allocate an interrupt line
 *	@irq: Interrupt line to allocate//分配的中斷號
 *	@handler: Function to be called when the IRQ occurs.//中斷處理函數
 *		  Primary handler for threaded interrupts
 *		  If NULL and thread_fn != NULL the default
 *		  primary handler is installed
 *	@thread_fn: Function called from the irq handler thread
 *		    If NULL, no irq thread is created
 *      	//中斷線程處理函數,如果爲空則不創建中斷線程
 *	@irqflags: Interrupt type flags//中斷類型標誌
 *	@devname: An ascii name for the claiming device//設備的名稱
 *	@dev_id: A cookie passed back to the handler function//傳遞給中斷處理函數的參數
 *
 *	This call allocates interrupt resources and enables the
 *	interrupt line and IRQ handling. From the point this
 *	call is made your handler function may be invoked. Since
 *	your handler function must clear any interrupt the board
 *	raises, you must take care both to initialise your hardware
 *	and to set up the interrupt handler in the right order.
 *
 *	If you want to set up a threaded irq handler for your device
 *	then you need to supply @handler and @thread_fn. @handler is
 *	still called in hard interrupt context and has to check
 *	whether the interrupt originates from the device. If yes it
 *	needs to disable the interrupt on the device and return
 *	IRQ_WAKE_THREAD which will wake up the handler thread and run
 *	@thread_fn. This split handler design is necessary to support
 *	shared interrupts.
 *  如果您想爲您的設備設置一個線程的irq處理程序,那麼您需要提供@handler和
 *  @thread_fn。@handler仍然在硬中斷上下文中調用,必須檢查中斷是否來自設備。
 *  如果是,它需要禁用設備上的中斷並返回IRQ_WAKE_THREAD,它將喚醒處理程序
 *  線程並運行@thread_fn。這種分割處理程序設計對於支持共享中斷是必要的。
 *	Dev_id must be globally unique. Normally the address of the
 *	device data structure is used as the cookie. Since the handler
 *	receives this value it makes sense to use it.
 *
 *	If your interrupt is shared you must pass a non NULL dev_id
 *	as this is required when freeing the interrupt.
 *  如果您的中斷是共享的,那麼您必須傳遞一個非空的dev_id,因爲在釋放中斷時
 *  需要傳遞一個非空的dev_id。
 *	Flags:
 *
 *	IRQF_SHARED		    Interrupt is shared//共享中斷
 *	IRQF_TRIGGER_*		Specify active edge(s) or level//指定邊緣觸發或水平觸發
 *
 */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
			 irq_handler_t thread_fn, unsigned long irqflags,
			 const char *devname, void *dev_id)
{
	struct irqaction *action;
	struct irq_desc *desc;
	int retval;
	if (irq == IRQ_NOTCONNECTED)
		return -ENOTCONN;
	/*
	 * Sanity-check: shared interrupts must pass in a real dev-ID,
	 * otherwise we'll have trouble later trying to figure out
	 * which interrupt is which (messes up the interrupt freeing
	 * logic etc).
	 *
	 * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
	 * it cannot be set along with IRQF_NO_SUSPEND.
	 */
	if (((irqflags & IRQF_SHARED) && !dev_id) ||
	    (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
	    ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
		return -EINVAL;
	desc = irq_to_desc(irq);
	if (!desc)
		return -EINVAL;
	if (!irq_settings_can_request(desc) ||
	    WARN_ON(irq_settings_is_per_cpu_devid(desc)))
		return -EINVAL;
	if (!handler) {
		if (!thread_fn)
			return -EINVAL;
		handler = irq_default_primary_handler;
	}
	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;
	action->handler = handler;
	action->thread_fn = thread_fn;
	action->flags = irqflags;
	action->name = devname;
	action->dev_id = dev_id;
	retval = irq_chip_pm_get(&desc->irq_data);
	if (retval < 0) {
		kfree(action);
		return retval;
	}
	retval = __setup_irq(irq, desc, action);//在__setup_irq中創建中斷線程處理函數
	if (retval) {
		irq_chip_pm_put(&desc->irq_data);
		kfree(action->secondary);
		kfree(action);
	}
#ifdef CONFIG_DEBUG_SHIRQ_FIXME
	if (!retval && (irqflags & IRQF_SHARED)) {
		/*
		 * It's a shared IRQ -- the driver ought to be prepared for it
		 * to happen immediately, so let's make sure....
		 * We disable the irq to make sure that a 'real' IRQ doesn't
		 * run in parallel with our fake.
		 */
		unsigned long flags;
		disable_irq(irq);
		local_irq_save(flags);
		handler(irq, dev_id);
		local_irq_restore(flags);
		enable_irq(irq);
	}
#endif
	return retval;
}

  request_threaded_irq總結

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
			             irq_handler_t thread_fn, unsigned long irqflags,
			             const char *devname, void *dev_id)

  1、功能:當發生中斷號爲irq的中斷時候,會執行handler所指向的中斷處理函數。如果thread_fn不爲空,它將喚醒處理程序線程並運行thread_fn。
  2、函數參數
  irq:中斷號
  handler:中斷處理函數指針,中斷髮生時最先執行的代碼,類似於頂半,最後會return IRQ_WAKE_THREAD來喚醒中斷線程thread_fn。
  thread_fn:thread_fn,是要在線程裏執行的handler,非常類似於底半。當handler return IRQ_WAKE_THREAD會喚醒中斷線程thread_fn。
  devname:設備名稱(註冊後會出現在cat /proc/interrupts)
  dev_id:傳遞給中斷服務函數的參數。注意:如果爲共享中斷,那麼這個參數一定要有,因爲當註銷共享中斷的中其中一個時,就是通過這個來標識要註銷的是哪一個中斷。
  3、返回值
  0 表示成功
  -EINVAL(-22)表示中斷號無效
  -EBUSY(-16)表示中斷號已經被佔用

 1.2、request_irq函數內核源碼分析

static inline int __must_check  request_irq(unsigned int irq, irq_handler_t handler, 
                               unsigned long flags, const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

  從源碼中可以看出,request_irq其實是調用request_threaded_irq來實現的,只不過 irq_handler_t thread_fn=NULL,即不會創建中斷線程處理函數,用系統提供的默認函數處理。
 1.3、參數的具體分析

 1.3.1、irq中斷號

  中斷號一般定義在arch\架構(如arm)\mach-芯片類型\inchude\mach\Irqs.h中。
  外部中斷號的獲取一般使用gpio_to_irq來獲取。

int gpio_to_irq(unsigned int gpio)

 1.3.2、中斷服務函數指針handler、thread_fn

typedef irqreturn_t (*irq_handler_t)(int, void *);
//函數原型
irqreturn_t irq_handler_t(int irq, void *dev_id);

  irq爲中斷號,dev_id爲註冊中斷時傳遞給中斷服務函數的參數,一般在共享中斷時用來區分設備。返回值類型爲irqreturn_t,irqreturn_t介紹如下:

enum irqreturn {
	IRQ_NONE		= (0 << 0),//只在共享中斷中使用,如果不是本中斷則返回這個值
	IRQ_HANDLED		= (1 << 0),//正確執行中斷處理函數返回這個值
	IRQ_WAKE_THREAD		= (1 << 1),//表示去喚醒中斷處理線程
};
typedef enum irqreturn irqreturn_t;

 1.3.3、flags中斷標誌

#define IRQF_TRIGGER_NONE	    0x00000000//不設置觸發邊沿
#define IRQF_TRIGGER_RISING	    0x00000001//設置觸發邊沿爲上升沿
#define IRQF_TRIGGER_FALLING	0x00000002//設置觸發邊沿爲下降沿
#define IRQF_TRIGGER_HIGH   	0x00000004//設置觸發方式爲高電平
#define IRQF_TRIGGER_LOW	    0x00000008//設置觸發方式爲低電平
#define IRQF_TRIGGER_MASK	(IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
				 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE	    0x00000010
#define IRQF_SHARED	        	0x00000080//共享中斷
#define IRQF_ONESHOT		    0x00002000//在hardirq處理程序完成後,不會重新啓用中斷,保持irq線禁用,而是執行線程中斷處理,直到線程處理程序已經運行。

  補充:以上標誌只用於外部中斷,且可以組合使用,如:irq_flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT;
 1.3.4、devname設備名

  由編程者命名,用來給內核記錄這個中斷號已經被使用了。

cat /proc/interrupts

 1.3.5、dev_id

  傳給中斷函數的參數。如果flags指定爲共享中斷,則不能爲空,並且需要保持其唯一性,因爲在註銷某個中斷時,由於中斷號都一直,則通過這個參數來區分具體哪一個設備。如果爲獨享中斷,一般會傳遞一個有意義的指針,方便中斷函數中使用。

 1.3.6request_threaded_irq的優點

  在實際應用中,檢測耳機的插入一般是通過耳機插孔中機械變化導致一個gpio的電平的變化,在該gpio中斷裏進行耳機插入處理。但是耳機插入一般都有個抖動的過程,需要消抖處理。最簡單的辦法是在中斷髮生後,延時一段時間(例如200ms),然後再檢查GPIO狀態是否穩定來確定是否有效插入。如果用老的中斷方式,就需要借用workqueue等方式,你需要在頂半里激活一個delay 200ms的workqueue,然後在workqueue裏檢查。用線程化的處理方式,你僅僅需要在thread_fn裏sleep 200ms,然後在檢查即可。

二、註銷中斷

const void *free_irq(unsigned int irq, void *dev_id)
//irq:要註銷的中斷號   dev_id:和註冊時寫的一樣就可以

三、禁止中斷

void disable_irq_nosync(unsigned int irq)void disable_irq(unsigned int irq)

  區別:disable_irq_nosync是立刻返回,而disable_irq不會立刻返回,而是等待中斷程序執行完成才返回。
  注意:中斷服務程序中不能使用disable_irq函數,會導致死鎖。

四、使能中斷

void enable_irq(unsigned int irq)

  
  
  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章