在本系列文章的第一篇:Linux中斷(interrupt)子系統之一:中斷系統基本原理,我把通用中斷子系統分爲了4個層次,其中的驅動程序接口層和中斷通用邏輯層的界限實際上不是很明確,因爲中斷通用邏輯層的很多接口,既可以被驅動程序使用,也可以被硬件封裝層使用,所以我把這兩部分的內容放在一起進行討論。
本章我將會討論這兩層對外提供的標準接口和內部實現機制,幾乎所有的接口都是圍繞着irq_desc和irq_chip這兩個結構體進行的,對這兩個結構體不熟悉的讀者可以現讀一下前面幾篇文章。
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/
中斷子系統爲我們提供了一系列用於irq的打開和關閉的函數接口,其中最基本的一對是:
-
disable_irq(unsigned int irq);
-
enable_irq(unsigned int irq);
點擊(此處)摺疊或打開
-
void __disable_irq(struct irq_desc *desc, unsigned int irq, bool
suspend)
-
{
-
if (suspend) {
-
if (!desc->action || (desc->action->flags & IRQF_NO_SUSPEND))
-
return;
-
desc->istate |= IRQS_SUSPENDED;
-
}
-
-
if (!desc->depth++)
-
irq_disable(desc);
- }
前面幾句是對suspend的處理,最後兩句,只有之前的depth爲0,纔會通過irq_disable函數,調用中斷控制器的回調chip->irq_mask,否則只是簡單地把depth的值加1。irq_disable函數還會通過irq_state_set_disabled和irq_state_set_masked,設置irq_data.flag的IRQD_IRQ_DISABLED和IRQD_IRQ_MASK標誌。
disable_irq的最後,調用了synchronize_irq,該函數通過IRQ_INPROGRESS標誌,確保action鏈表中所有的handler都已經處理完畢,然後還要通過wait_event等待該irq所有的中斷線程退出。正因爲這樣,在中斷上下文中,不應該使用該API來關閉irq,同時要確保調用該API的函數不能擁有該irq處理函數或線程的資源,否則就會發生死鎖!!如果一定要在這兩種情況下關閉irq,中斷子系統爲我們提供了另外一個API,它不會做出任何等待動作:
-
disable_irq_nosync();
- enable_irq();
點擊(此處)摺疊或打開
-
void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume)
-
{
-
if (resume) {
-
......
-
}
-
-
switch (desc->depth) {
-
case 0:
-
err_out:
-
WARN(1, KERN_WARNING "Unbalanced enable for IRQ %d\n", irq);
-
break;
-
case 1: {
-
......
-
irq_enable(desc);
-
......
-
}
-
default:
-
desc->depth--;
-
}
- }
當depth的值爲1時,才真正地調用irq_enable(),它最終通過chip->unmask或chip->enable回調開啓中斷控制器中相應的中斷線,如果depth不是1,只是簡單地減去1。如果已經是0,驅動還要調用enable_irq,說明驅動程序處理不當,造成enable與disable不平衡,內核會打印一句警告信息:Unbalanced enable for IRQ xxx。
2. 中斷子系統內部數據結構訪問接口
我們知道,中斷子系統內部定義了幾個重要的數據結構,例如:irq_desc,irq_chip,irq_data等等,這些數據結構的各個字段控制或影響着中斷子系統和各個irq的行爲和實現方式。通常,驅動程序不應該直接訪問這些數據結構,直接訪問會破會中斷子系統的封裝性,爲此,中斷子系統爲我們提供了一系列的訪問接口函數,用於訪問這些數據結構。
存取irq_data結構相關字段的API:
irq_set_chip(irq, *chip) / irq_get_chip(irq) 通過irq編號,設置、獲取irq_cip結構指針;
irq_set_handler_data(irq, *data) / irq_get_handler_data(irq) 通過irq編號,設置、獲取irq_desc.irq_data.handler_data字段,該字段是每個irq的私有數據,通常用於硬件封裝層,例如中斷控制器級聯時,父irq用該字段保存子irq的起始編號。
irq_set_chip_data(irq, *data) / irq_get_chip_data(irq) 通過irq編號,設置、獲取irq_desc.irq_data.chip_data字段,該字段是每個中斷控制器的私有數據,通常用於硬件封裝層。
irq_set_irq_type(irq, type) 用於設置中斷的電氣類型,可選的類型有:
- IRQ_TYPE_EDGE_RISING
- IRQ_TYPE_EDGE_FALLING
- IRQ_TYPE_EDGE_BOTH
- IRQ_TYPE_LEVEL_HIGH
- IRQ_TYPE_LEVEL_LOW
irq_get_irq_data(irq) 通過irq編號,獲取irq_data結構指針;
irq_data_get_irq_chip(irq_data *d) 通過irq_data指針,獲取irq_chip字段;
irq_data_get_irq_chip_data(irq_data *d) 通過irq_data指針,獲取chip_data字段;
irq_data_get_irq_handler_data(irq_data *d) 通過irq_data指針,獲取handler_data字段;
設置中斷流控處理回調API:
irq_set_handler(irq, handle) 設置中斷流控回調字段:irq_desc.handle_irq,參數handle的類型是irq_flow_handler_t。
irq_set_chip_and_handler(irq, *chip, handle) 同時設置中斷流控回調字段和irq_chip指針:irq_desc.handle_irq和irq_desc.irq_data.chip。
irq_set_chip_and_handler_name(irq, *chip, handle, *name) 同時設置中斷流控回調字段和irq_chip指針以及irq名字:irq_desc.handle_irq、irq_desc.irq_data.chip、irq_desc.name。
irq_set_chained_handler(irq, *chip, handle) 設置中斷流控回調字段:irq_desc.handle_irq,同時設置標誌:IRQ_NOREQUEST、IRQ_NOPROBE、IRQ_NOTHREAD,該api通常用於中斷控制器的級聯,父控制器通過該api設置流控回調後,同時設置上述三個標誌位,使得父控制器的中斷線不允許被驅動程序申請。
3. 在驅動程序中申請中斷
系統啓動階段,中斷子系統完成了必要的初始化工作,爲驅動程序申請中斷服務做好了準備,通常,我們用一下API申請中斷服務:
點擊(此處)摺疊或打開
-
request_threaded_irq(unsigned int irq, irq_handler_t
handler,
-
irq_handler_t thread_fn,
- unsigned long flags, const char *name, void *dev);
handler 中斷服務回調函數,該回調運行在中斷上下文中,並且cpu的本地中斷處於關閉狀態,所以該回調函數應該只是執行需要快速響應的操作,執行時間應該儘可能短小,耗時的工作最好留給下面的thread_fn回調處理。
thread_fn 如果該參數不爲NULL,內核會爲該irq創建一個內核線程,當中斷髮生時,如果handler回調返回值是IRQ_WAKE_THREAD,內核將會激活中斷線程,在中斷線程中,該回調函數將被調用,所以,該回調函數運行在進程上下文中,允許進行阻塞操作。
flags 控制中斷行爲的位標誌,IRQF_XXXX,例如:IRQF_TRIGGER_RISING,IRQF_TRIGGER_LOW,IRQF_SHARED等,在include/linux/interrupt.h中定義。
name 申請本中斷服務的設備名稱,同時也作爲中斷線程的名稱,該名稱可以在/proc/interrupts文件中顯示。
dev 當多個設備的中斷線共享同一個irq時,它會作爲handler的參數,用於區分不同的設備。
下面我們分析一下request_threaded_irq的工作流程。函數先是根據irq編號取出對應的irq_desc實例的指針,然後分配了一個irqaction結構,用參數handler,thread_fn,irqflags,devname,dev_id初始化irqaction結構的各字段,同時做了一些必要的條件判斷:該irq是否禁止申請?handler和thread_fn不允許同時爲NULL,最後把大部分工作委託給__setup_irq函數:
點擊(此處)摺疊或打開
-
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;
-
-
chip_bus_lock(desc);
-
retval = __setup_irq(irq, desc, action);
- chip_bus_sync_unlock(desc);
點擊(此處)摺疊或打開
-
if (new->thread_fn && !nested) {
-
struct task_struct *t;
-
-
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
-
new->name);
-
if (IS_ERR(t)) {
-
ret = PTR_ERR(t);
-
goto out_mput;
-
}
-
/*
-
* We keep the reference to the task struct even if
-
* the thread dies to avoid that the interrupt code
-
* references an already freed task_struct.
-
*/
-
get_task_struct(t);
-
new->thread = t;
- }
- 一定要設置了IRQF_SHARED標誌
- 電氣觸發方式要完全一樣(IRQF_TRIGGER_XXXX)
-
IRQF_PERCPU要一致
-
IRQF_ONESHOT要一致
點擊(此處)摺疊或打開
-
/* add new interrupt at end of
irq queue */
-
do {
-
thread_mask |= old->thread_mask;
-
old_ptr = &old->next;
-
old = *old_ptr;
-
} while (old);
- shared = 1;
如果這不是一個共享中斷,或者是共享中斷的第一次申請,函數將初始化irq_desc結構中斷線程等待結構:wait_for_threads,disable_irq函數會使用該字段等待所有irq線程的結束。接下來設置中斷控制器的電氣觸發類型,然後處理一些必要的IRQF_XXXX標誌位。如果沒有設置IRQF_NOAUTOEN標誌,則調用irq_startup()打開該irq,在irq_startup()函數中irq_desc中的enable_irq/disable_irq嵌套深度字段depth設置爲0,代表該irq已經打開,如果在沒有任何disable_irq被調用的情況下,enable_irq將會打印一個警告信息。
點擊(此處)摺疊或打開
-
if (irq_settings_can_autoenable(desc))
-
irq_startup(desc);
-
else
-
/* Undo nested disables: */
- desc->depth = 1;
接着,設置cpu和irq的親緣關係:
點擊(此處)摺疊或打開
-
/* Set default affinity
mask once everything is setup */
- setup_affinity(irq, desc, mask);
然後,把新的irqaction實例鏈接到action鏈表的最後:
點擊(此處)摺疊或打開
-
new->irq = irq;
- *old_ptr = new;
最後,喚醒中斷線程,註冊相關的/proc文件節點:
點擊(此處)摺疊或打開
-
if (new->thread)
-
wake_up_process(new->thread);
-
-
register_irq_proc(irq, desc);
-
new->dir = NULL;
- register_handler_proc(irq, new);
至此,irq的申請宣告完畢,當中斷髮生時,處理的路徑將會沿着:irq_desc.handle_irq,irqaction.handler,irqaction.thread_fn(irqaction.handler的返回值是IRQ_WAKE_THREAD)這個過程進行處理。下圖表明瞭某個irq被申請後,各個數據結構之間的關係:
- 配置了CONFIG_SPARSE_IRQ內核配置項,使用基數樹動態管理irq_desc結構。
- 針對多功能複合設備,內部具備多箇中斷源,但中斷觸發引腳只有一個,爲了實現驅動程序的跨平臺,不希望這些中斷源的irq被硬編碼在板級代碼中。
- irq_set_chip_and_handler_name
-
irq_set_handler_data
-
irq_set_chip_data
在移動設備系統中,存在着大量的多功能複合設備,最常見的是一個芯片中,內部集成了多個功能部件,或者是一個模塊單元內部集成了功能部件,這些內部功能部件可以各自產生中斷請求,但是芯片或者硬件模塊對外只有一箇中斷請求引腳,我們可以使用多種方式處理這些設備的中斷請求,以下我們逐一討論這些方法。
對於這種複合設備,通常設備中會提供某種方式,以便讓CPU獲取真正的中斷來源, 方式可以是一個內部寄存器,gpio的狀態等等。單一中斷模式是指驅動程序只申請一個irq,然後在中斷處理程序中通過讀取設備的內部寄存器,獲取中斷源,然後根據不同的中斷源做出不同的處理,以下是一個簡化後的代碼:
點擊(此處)摺疊或打開
-
static int xxx_probe(device *dev)
-
{
-
......
-
irq = get_irq_from_dev(dev);
-
-
ret = request_threaded_irq(irq, NULL, xxx_irq_thread,
-
IRQF_TRIGGER_RISING,
-
"xxx_dev", NULL);
-
......
-
return 0;
-
}
-
-
static irqreturn_t xxx_irq_thread(int irq, void *data)
-
{
-
......
-
irq_src = read_device_irq();
-
switch (irq_src) {
-
case IRQ_SUB_DEV0:
-
ret = handle_sub_dev0_irq();
-
break;
-
case IRQ_SUB_DEV1:
-
ret = handle_sub_dev1_irq();
-
break;
-
......
-
default:
-
ret = IRQ_NONE;
-
break;
-
}
-
......
-
return ret;
- }
共享中斷模式充分利用了通用中斷子系統的特性,經過前面的討論,我們知道,irq對應的irq_desc結構中的action字段,本質上是一個鏈表,這給我們實現中斷共享提供了必要的基礎,只要我們以相同的irq編號多次申請中斷服務,那麼,action鏈表上就會有多個irqaction實例,當中斷髮生時,中斷子系統會遍歷action鏈表,逐個執行irqaction實例中的handler回調,根據handler回調的返回值不同,決定是否喚醒中斷線程。需要注意到是,申請多箇中斷時,irq編號要保持一致,flag參數最好也能保持一致,並且都要設上IRQF_SHARED標誌。在使用共享中斷時,最好handler和thread_fn都要提供,在各自的中斷處理回調handler中,做出以下處理:
- 判斷中斷是否來自本設備;
-
如果不是來自本設備:
- 直接返回IRQ_NONE;
-
如果是來自本設備:
- 關閉irq;
- 返回IRQ_WAKE_THREAD,喚醒中斷線程,thread_fn將會被執行;
- 首先,父中斷的irq編號可以從板級代碼的預定義中獲得,或者通過device的platform_data字段獲得;
- 使用父中斷的irq編號,利用irq_set_chained_handler函數修改父中斷的流控函數;
-
使用父中斷的irq編號,利用irq_set_handler_data設置流控函數的參數,該參數要能夠用於判別子控制器的中斷來源;
- 實現父中斷的流控函數,其中只需獲得並計算子設備的irq編號,然後調用generic_handle_irq即可;
- 爲設備內的中斷控制器實現一個irq_chip結構,實現其中必要的回調,例如irq_mask,irq_unmask,irq_ack等;
-
循環每一個子設備,做以下動作:
- 爲每個子設備,使用irq_alloc_descs函數申請irq編號;
- 使用irq_set_chip_data設置必要的cookie數據;
- 使用irq_set_chip_and_handler設置子控制器的irq_chip實例和子irq的流控處理程序,通常使用標準的流控函數,例如handle_edge_irq;
- 子設備的驅動程序使用自身申請到的irq編號,按照正常流程申請中斷服務即可。
- 首先,父中斷的irq編號可以從板級代碼的預定義中獲得,或者通過device的platform_data字段獲得;
- 使用父中斷的irq編號,利用request_threaded_irq函數申請中斷服務,需要提供thread_fn參數和dev_id參數;
-
dev_id參數要能夠用於判別子控制器的中斷來源;
- 實現父中斷的thread_fn函數,其中只需獲得並計算子設備的irq編號,然後調用handle_nested_irq即可;
- 爲設備內的中斷控制器實現一個irq_chip結構,實現其中必要的回調,例如irq_mask,irq_unmask,irq_ack等;
-
循環每一個子設備,做以下動作:
- 爲每個子設備,使用irq_alloc_descs函數申請irq編號;
- 使用irq_set_chip_data設置必要的cookie數據;
- 使用irq_set_chip_and_handler設置子控制器的irq_chip實例和子irq的流控處理程序,通常使用標準的流控函數,例如handle_edge_irq;
-
使用irq_set_nested_thread函數,把子設備irq的線程嵌套特性打開;
- 子設備的驅動程序使用自身申請到的irq編號,按照正常流程申請中斷服務即可。