一、註冊中斷
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)