在本系列文章的第一篇:Linux中斷(interrupt)子系統(tǒng)之一:中斷系統(tǒng)基本原理,我把通用中斷子系統(tǒng)分為了4個(gè)層次,其中的驅(qū)動(dòng)程序接口層和中斷通用邏輯層的界限實(shí)際上不是很明確,因?yàn)橹袛嗤ㄓ眠壿媽拥暮芏嘟涌?,既可以被?qū)動(dòng)程序使用,也可以被硬件封裝層使用,所以我把這兩部分的內(nèi)容放在一起進(jìn)行討論。
本章我將會(huì)討論這兩層對(duì)外提供的標(biāo)準(zhǔn)接口和內(nèi)部實(shí)現(xiàn)機(jī)制,幾乎所有的接口都是圍繞著irq_desc和irq_chip這兩個(gè)結(jié)構(gòu)體進(jìn)行的,對(duì)這兩個(gè)結(jié)構(gòu)體不熟悉的讀者可以現(xiàn)讀一下前面幾篇文章。
1. ?irq的打開和關(guān)閉
中斷子系統(tǒng)為我們提供了一系列用于irq的打開和關(guān)閉的函數(shù)接口,其中最基本的一對(duì)是:
disable_irq(unsigned int irq);
enable_irq(unsigned int irq);
這兩個(gè)API應(yīng)該配對(duì)使用,disable_irq可以被多次嵌套調(diào)用,要想重新打開irq,enable_irq必須也要被調(diào)用同樣的次數(shù),為此,irq_desc結(jié)構(gòu)中的depth字段專門用于這兩個(gè)API嵌套深度的管理。當(dāng)某個(gè)irq首次被驅(qū)動(dòng)程序申請(qǐng)時(shí),默認(rèn)情況下,設(shè)置depth的初始值是0,對(duì)應(yīng)的irq處于打開狀態(tài)。我們看看disable_irq的調(diào)用過程:
圖1.1 ?disable_irq的調(diào)用過程
函數(shù)的開始使用異步方式的內(nèi)部函數(shù)__disable_irq_nosync(),所謂異步方式就是不理會(huì)當(dāng)前該irq是否正在被處理(有handler在運(yùn)行或者有中斷線程尚未結(jié)束)。有些中斷控制器可能掛在某個(gè)慢速的總線上,所以在進(jìn)一步處理前,先通過irq_get_desc_buslock獲得總線鎖(最終會(huì)調(diào)用chip->irq_bus_lock),然后進(jìn)入內(nèi)部函數(shù)__disable_irq:
[cpp]?view plain?copy
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);??
}??
前面幾句是對(duì)suspend的處理,最后兩句,只有之前的depth為0,才會(huì)通過irq_disable函數(shù),調(diào)用中斷控制器的回調(diào)chip->irq_mask,否則只是簡(jiǎn)單地把depth的值加1。irq_disable函數(shù)還會(huì)通過irq_state_set_disabled和irq_state_set_masked,設(shè)置irq_data.flag的IRQD_IRQ_DISABLED和IRQD_IRQ_MASK標(biāo)志。
disable_irq的最后,調(diào)用了synchronize_irq,該函數(shù)通過IRQ_INPROGRESS標(biāo)志,確保action鏈表中所有的handler都已經(jīng)處理完畢,然后還要通過wait_event等待該irq所有的中斷線程退出。正因?yàn)檫@樣,在中斷上下文中,不應(yīng)該使用該API來關(guān)閉irq,同時(shí)要確保調(diào)用該API的函數(shù)不能擁有該irq處理函數(shù)或線程的資源,否則就會(huì)發(fā)生死鎖!!如果一定要在這兩種情況下關(guān)閉irq,中斷子系統(tǒng)為我們提供了另外一個(gè)API,它不會(huì)做出任何等待動(dòng)作:
disable_irq_nosync();
中斷子系統(tǒng)打開irq的的API是:
enable_irq();
打開irq無需提供同步的版本,因?yàn)閕rq打開前,沒有handler和線程在運(yùn)行,我們關(guān)注一下他對(duì)depth的處理,他在內(nèi)部函數(shù)__enable_irq中處理:
[cpp]?view plain?copy
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--;??
}??
}??
當(dāng)depth的值為1時(shí),才真正地調(diào)用irq_enable(),它最終通過chip->unmask或chip->enable回調(diào)開啟中斷控制器中相應(yīng)的中斷線,如果depth不是1,只是簡(jiǎn)單地減去1。如果已經(jīng)是0,驅(qū)動(dòng)還要調(diào)用enable_irq,說明驅(qū)動(dòng)程序處理不當(dāng),造成enable與disable不平衡,內(nèi)核會(huì)打印一句警告信息:Unbalanced enable for IRQ xxx。
?
2. ?中斷子系統(tǒng)內(nèi)部數(shù)據(jù)結(jié)構(gòu)訪問接口
我們知道,中斷子系統(tǒng)內(nèi)部定義了幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu),例如:irq_desc,irq_chip,irq_data等等,這些數(shù)據(jù)結(jié)構(gòu)的各個(gè)字段控制或影響著中斷子系統(tǒng)和各個(gè)irq的行為和實(shí)現(xiàn)方式。通常,驅(qū)動(dòng)程序不應(yīng)該直接訪問這些數(shù)據(jù)結(jié)構(gòu),直接訪問會(huì)破會(huì)中斷子系統(tǒng)的封裝性,為此,中斷子系統(tǒng)為我們提供了一系列的訪問接口函數(shù),用于訪問這些數(shù)據(jù)結(jié)構(gòu)。
存取irq_data結(jié)構(gòu)相關(guān)字段的API:
irq_set_chip(irq, *chip) /?irq_get_chip(irq)? 通過irq編號(hào),設(shè)置、獲取irq_cip結(jié)構(gòu)指針;
irq_set_handler_data(irq, *data) /?irq_get_handler_data(irq)? 通過irq編號(hào),設(shè)置、獲取irq_desc.irq_data.handler_data字段,該字段是每個(gè)irq的私有數(shù)據(jù),通常用于硬件封裝層,例如中斷控制器級(jí)聯(lián)時(shí),父irq用該字段保存子irq的起始編號(hào)。
irq_set_chip_data(irq, *data) /?irq_get_chip_data(irq)? 通過irq編號(hào),設(shè)置、獲取irq_desc.irq_data.chip_data字段,該字段是每個(gè)中斷控制器的私有數(shù)據(jù),通常用于硬件封裝層。
irq_set_irq_type(irq, type)? 用于設(shè)置中斷的電氣類型,可選的類型有:
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編號(hào),獲取irq_data結(jié)構(gòu)指針;
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字段;
設(shè)置中斷流控處理回調(diào)API:
irq_set_handler(irq, handle)? 設(shè)置中斷流控回調(diào)字段:irq_desc.handle_irq,參數(shù)handle的類型是irq_flow_handler_t。
irq_set_chip_and_handler(irq, *chip, handle)? 同時(shí)設(shè)置中斷流控回調(diào)字段和irq_chip指針:irq_desc.handle_irq和irq_desc.irq_data.chip。
irq_set_chip_and_handler_name(irq, *chip, handle, *name)??同時(shí)設(shè)置中斷流控回調(diào)字段和irq_chip指針以及irq名字:irq_desc.handle_irq、irq_desc.irq_data.chip、irq_desc.name。
irq_set_chained_handler(irq, *chip, handle)??設(shè)置中斷流控回調(diào)字段:irq_desc.handle_irq,同時(shí)設(shè)置標(biāo)志:IRQ_NOREQUEST、IRQ_NOPROBE、IRQ_NOTHREAD,該api通常用于中斷控制器的級(jí)聯(lián),父控制器通過該api設(shè)置流控回調(diào)后,同時(shí)設(shè)置上述三個(gè)標(biāo)志位,使得父控制器的中斷線不允許被驅(qū)動(dòng)程序申請(qǐng)。
3. ?在驅(qū)動(dòng)程序中申請(qǐng)中斷
系統(tǒng)啟動(dòng)階段,中斷子系統(tǒng)完成了必要的初始化工作,為驅(qū)動(dòng)程序申請(qǐng)中斷服務(wù)做好了準(zhǔn)備,通常,我們用一下API申請(qǐng)中斷服務(wù):
[cpp]?view plain?copy
request_threaded_irq(unsigned?int?irq,?irq_handler_t?handler,??
irq_handler_t?thread_fn,??
unsigned?long?flags,?const?char?*name,?void?*dev);??
irq? 需要申請(qǐng)的irq編號(hào),對(duì)于ARM體系,irq編號(hào)通常在平臺(tái)級(jí)的代碼中事先定義好,有時(shí)候也可以動(dòng)態(tài)申請(qǐng)。
handler? 中斷服務(wù)回調(diào)函數(shù),該回調(diào)運(yùn)行在中斷上下文中,并且cpu的本地中斷處于關(guān)閉狀態(tài),所以該回調(diào)函數(shù)應(yīng)該只是執(zhí)行需要快速響應(yīng)的操作,執(zhí)行時(shí)間應(yīng)該盡可能短小,耗時(shí)的工作最好留給下面的thread_fn回調(diào)處理。
thread_fn? 如果該參數(shù)不為NULL,內(nèi)核會(huì)為該irq創(chuàng)建一個(gè)內(nèi)核線程,當(dāng)中斷發(fā)生時(shí),如果handler回調(diào)返回值是IRQ_WAKE_THREAD,內(nèi)核將會(huì)激活中斷線程,在中斷線程中,該回調(diào)函數(shù)將被調(diào)用,所以,該回調(diào)函數(shù)運(yùn)行在進(jìn)程上下文中,允許進(jìn)行阻塞操作。
flags? 控制中斷行為的位標(biāo)志,IRQF_XXXX,例如:IRQF_TRIGGER_RISING,IRQF_TRIGGER_LOW,IRQF_SHARED等,在include/linux/interrupt.h中定義。
name? 申請(qǐng)本中斷服務(wù)的設(shè)備名稱,同時(shí)也作為中斷線程的名稱,該名稱可以在/proc/interrupts文件中顯示。
dev? 當(dāng)多個(gè)設(shè)備的中斷線共享同一個(gè)irq時(shí),它會(huì)作為handler的參數(shù),用于區(qū)分不同的設(shè)備。
下面我們分析一下request_threaded_irq的工作流程。函數(shù)先是根據(jù)irq編號(hào)取出對(duì)應(yīng)的irq_desc實(shí)例的指針,然后分配了一個(gè)irqaction結(jié)構(gòu),用參數(shù)handler,thread_fn,irqflags,devname,dev_id初始化irqaction結(jié)構(gòu)的各字段,同時(shí)做了一些必要的條件判斷:該irq是否禁止申請(qǐng)?handler和thread_fn不允許同時(shí)為NULL,最后把大部分工作委托給__setup_irq函數(shù):
[cpp]?view plain?copy
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);??
進(jìn)入__setup_irq函數(shù),如果參數(shù)flag中設(shè)置了IRQF_SAMPLE_RANDOM標(biāo)志,它會(huì)調(diào)用rand_initialize_irq,以便對(duì)隨機(jī)數(shù)的生成產(chǎn)生影響。如果申請(qǐng)的不是一個(gè)線程嵌套中斷(關(guān)于線程嵌套中斷,請(qǐng)參閱Linux中斷(interrupt)子系統(tǒng)之三:中斷流控處理層中的handle_nested_irq一節(jié)),而且提供了thread_fn參數(shù),它將創(chuàng)建一個(gè)內(nèi)核線程:
[cpp]?view plain?copy
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;??
}??
如果irq_desc結(jié)構(gòu)中斷action鏈表不為空,說明這個(gè)irq已經(jīng)被其它設(shè)備申請(qǐng)過,也就是說,這是一個(gè)共享中斷,所以接下來會(huì)判斷這個(gè)新申請(qǐng)的中斷與已經(jīng)申請(qǐng)的舊中斷的以下幾個(gè)標(biāo)志是否一致:
一定要設(shè)置了IRQF_SHARED標(biāo)志
電氣觸發(fā)方式要完全一樣(IRQF_TRIGGER_XXXX)
IRQF_PERCPU要一致
IRQF_ONESHOT要一致
檢查這些條件都是因?yàn)槎鄠€(gè)設(shè)備試圖共享一根中斷線,試想一下,如果一個(gè)設(shè)備要求上升沿中斷,一個(gè)設(shè)備要求電平中斷,當(dāng)中斷到達(dá)時(shí),內(nèi)核將不知如何選擇合適的流控操作。完成檢查后,函數(shù)找出action鏈表中最后一個(gè)irqaction實(shí)例的指針。
[cpp]?view plain?copy
/*?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;??
如果這不是一個(gè)共享中斷,或者是共享中斷的第一次申請(qǐng),函數(shù)將初始化irq_desc結(jié)構(gòu)中斷線程等待結(jié)構(gòu):wait_for_threads,disable_irq函數(shù)會(huì)使用該字段等待所有irq線程的結(jié)束。接下來設(shè)置中斷控制器的電氣觸發(fā)類型,然后處理一些必要的IRQF_XXXX標(biāo)志位。如果沒有設(shè)置IRQF_NOAUTOEN標(biāo)志,則調(diào)用irq_startup()打開該irq,在irq_startup()函數(shù)中irq_desc中的enable_irq/disable_irq嵌套深度字段depth設(shè)置為0,代表該irq已經(jīng)打開,如果在沒有任何disable_irq被調(diào)用的情況下,enable_irq將會(huì)打印一個(gè)警告信息。
[cpp]?view plain?copy
if?(irq_settings_can_autoenable(desc))??
irq_startup(desc);??
else??
/*?Undo?nested?disables:?*/??
desc->depth?=?1;??
接著,設(shè)置cpu和irq的親緣關(guān)系:
[cpp]?view plain?copy
/*?Set?default?affinity?mask?once?everything?is?setup?*/??
setup_affinity(irq,?desc,?mask);??
然后,把新的irqaction實(shí)例鏈接到action鏈表的最后:
[cpp]?view plain?copy
new->irq?=?irq;??
*old_ptr?=?new;??
最后,喚醒中斷線程,注冊(cè)相關(guān)的/proc文件節(jié)點(diǎn):
[cpp]?view plain?copy
if?(new->thread)??
wake_up_process(new->thread);??
register_irq_proc(irq,?desc);??
new->dir?=?NULL;??
register_handler_proc(irq,?new);??
至此,irq的申請(qǐng)宣告完畢,當(dāng)中斷發(fā)生時(shí),處理的路徑將會(huì)沿著:irq_desc.handle_irq,irqaction.handler,irqaction.thread_fn(irqaction.handler的返回值是IRQ_WAKE_THREAD)這個(gè)過程進(jìn)行處理。下圖表明了某個(gè)irq被申請(qǐng)后,各個(gè)數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系:
圖3.1 ?irq各個(gè)數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系
4. ?動(dòng)態(tài)擴(kuò)展irq編號(hào)
在ARM體系的移動(dòng)設(shè)備中,irq的編號(hào)通常在平臺(tái)級(jí)或板級(jí)代碼中事先根據(jù)硬件的連接定義好,最大的irq數(shù)目也用NR_IRQS常量指定。幾種情況下,我們希望能夠動(dòng)態(tài)地增加系統(tǒng)中irq的數(shù)量:
配置了CONFIG_SPARSE_IRQ內(nèi)核配置項(xiàng),使用基數(shù)樹動(dòng)態(tài)管理irq_desc結(jié)構(gòu)。
針對(duì)多功能復(fù)合設(shè)備,內(nèi)部具備多個(gè)中斷源,但中斷觸發(fā)引腳只有一個(gè),為了實(shí)現(xiàn)驅(qū)動(dòng)程序的跨平臺(tái),不希望這些中斷源的irq被硬編碼在板級(jí)代碼中。
中斷子系統(tǒng)為我們提供了以下幾個(gè)api,用于動(dòng)態(tài)申請(qǐng)/擴(kuò)展irq編號(hào):
irq_alloc_desc(node)? 申請(qǐng)一個(gè)irq,node是對(duì)應(yīng)內(nèi)存節(jié)點(diǎn)的編號(hào);
irq_alloc_desc_at(at, node)? 在指定位置申請(qǐng)一個(gè)irq,如果指定位置已經(jīng)被占用,則申請(qǐng)失?。?/p>
irq_alloc_desc_from(from, node)? 從指定位置開始搜索,申請(qǐng)一個(gè)irq;
irq_alloc_descs(irq, from, cnt, node)? 申請(qǐng)多個(gè)連續(xù)的irq編號(hào),從from位置開始搜索;
irq_free_descs(irq, cnt)? 釋放irq資源;
以上這些申請(qǐng)函數(shù)(宏),會(huì)為我們申請(qǐng)相應(yīng)的irq_desc結(jié)構(gòu)并初始化為默認(rèn)狀態(tài),要想這些irq能夠正常工作,我們還要使用第二節(jié)提到的api,對(duì)必要的字段進(jìn)行設(shè)置,例如:
irq_set_chip_and_handler_name
irq_set_handler_data
irq_set_chip_data
對(duì)于沒有配置CONFIG_SPARSE_IRQ內(nèi)核配置項(xiàng)的內(nèi)核,irq_desc是一個(gè)數(shù)組,根本不可能做到動(dòng)態(tài)擴(kuò)展,但是很多驅(qū)動(dòng)又確實(shí)使用到了上述api,尤其是mfd驅(qū)動(dòng),這些驅(qū)動(dòng)并沒有我們一定要配置CONFIG_SPARSE_IRQ選項(xiàng),要想不對(duì)這些驅(qū)動(dòng)做出修改,你只能妥協(xié)一下,在你的板級(jí)代碼中把NR_IRQS定義得大一些,留出足夠的保留數(shù)量
5. ?多功能復(fù)合設(shè)備的中斷處理
在移動(dòng)設(shè)備系統(tǒng)中,存在著大量的多功能復(fù)合設(shè)備,最常見的是一個(gè)芯片中,內(nèi)部集成了多個(gè)功能部件,或者是一個(gè)模塊單元內(nèi)部集成了功能部件,這些內(nèi)部功能部件可以各自產(chǎn)生中斷請(qǐng)求,但是芯片或者硬件模塊對(duì)外只有一個(gè)中斷請(qǐng)求引腳,我們可以使用多種方式處理這些設(shè)備的中斷請(qǐng)求,以下我們逐一討論這些方法。
5.1 ?單一中斷模式?
對(duì)于這種復(fù)合設(shè)備,通常設(shè)備中會(huì)提供某種方式,以便讓CPU獲取真正的中斷來源, 方式可以是一個(gè)內(nèi)部寄存器,gpio的狀態(tài)等等。單一中斷模式是指驅(qū)動(dòng)程序只申請(qǐng)一個(gè)irq,然后在中斷處理程序中通過讀取設(shè)備的內(nèi)部寄存器,獲取中斷源,然后根據(jù)不同的中斷源做出不同的處理,以下是一個(gè)簡(jiǎn)化后的代碼:
[cpp]?view plain?copy
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;??
}??
5.2 ?共享中斷模式
共享中斷模式充分利用了通用中斷子系統(tǒng)的特性,經(jīng)過前面的討論,我們知道,irq對(duì)應(yīng)的irq_desc結(jié)構(gòu)中的action字段,本質(zhì)上是一個(gè)鏈表,這給我們實(shí)現(xiàn)中斷共享提供了必要的基礎(chǔ),只要我們以相同的irq編號(hào)多次申請(qǐng)中斷服務(wù),那么,action鏈表上就會(huì)有多個(gè)irqaction實(shí)例,當(dāng)中斷發(fā)生時(shí),中斷子系統(tǒng)會(huì)遍歷action鏈表,逐個(gè)執(zhí)行irqaction實(shí)例中的handler回調(diào),根據(jù)handler回調(diào)的返回值不同,決定是否喚醒中斷線程。需要注意到是,申請(qǐng)多個(gè)中斷時(shí),irq編號(hào)要保持一致,flag參數(shù)最好也能保持一致,并且都要設(shè)上IRQF_SHARED標(biāo)志。在使用共享中斷時(shí),最好handler和thread_fn都要提供,在各自的中斷處理回調(diào)handler中,做出以下處理:
判斷中斷是否來自本設(shè)備;
如果不是來自本設(shè)備:
直接返回IRQ_NONE;
如果是來自本設(shè)備:
關(guān)閉irq;
返回IRQ_WAKE_THREAD,喚醒中斷線程,thread_fn將會(huì)被執(zhí)行;
5.3 ?中斷控制器級(jí)聯(lián)模式
多數(shù)多功能復(fù)合設(shè)備內(nèi)部提供了基本的中斷控制器功能,例如可以單獨(dú)地控制某個(gè)子中斷的打開和關(guān)閉,并且可以方便地獲得子中斷源,對(duì)于這種設(shè)備,我們可以把設(shè)備內(nèi)的中斷控制器實(shí)現(xiàn)為一個(gè)子控制器,然后使用中斷控制器級(jí)聯(lián)模式。這種模式下,各個(gè)子設(shè)備擁有各自獨(dú)立的irq編號(hào),中斷服務(wù)通過父中斷進(jìn)行分發(fā)。
對(duì)于父中斷,具體的實(shí)現(xiàn)步驟如下:
首先,父中斷的irq編號(hào)可以從板級(jí)代碼的預(yù)定義中獲得,或者通過device的platform_data字段獲得;
使用父中斷的irq編號(hào),利用irq_set_chained_handler函數(shù)修改父中斷的流控函數(shù);
使用父中斷的irq編號(hào),利用irq_set_handler_data設(shè)置流控函數(shù)的參數(shù),該參數(shù)要能夠用于判別子控制器的中斷來源;
實(shí)現(xiàn)父中斷的流控函數(shù),其中只需獲得并計(jì)算子設(shè)備的irq編號(hào),然后調(diào)用generic_handle_irq即可;
對(duì)于子設(shè)備,具體的實(shí)現(xiàn)步驟如下
為設(shè)備內(nèi)的中斷控制器實(shí)現(xiàn)一個(gè)irq_chip結(jié)構(gòu),實(shí)現(xiàn)其中必要的回調(diào),例如irq_mask,irq_unmask,irq_ack等;
循環(huán)每一個(gè)子設(shè)備,做以下動(dòng)作:
為每個(gè)子設(shè)備,使用irq_alloc_descs函數(shù)申請(qǐng)irq編號(hào);
使用irq_set_chip_data設(shè)置必要的cookie數(shù)據(jù);
使用irq_set_chip_and_handler設(shè)置子控制器的irq_chip實(shí)例和子irq的流控處理程序,通常使用標(biāo)準(zhǔn)的流控函數(shù),例如handle_edge_irq;
子設(shè)備的驅(qū)動(dòng)程序使用自身申請(qǐng)到的irq編號(hào),按照正常流程申請(qǐng)中斷服務(wù)即可。
5.4 ?中斷線程嵌套模式
該模式與中斷控制器級(jí)聯(lián)模式大體相似,只不過級(jí)聯(lián)模式時(shí),父中斷無需通過request_threaded_irq申請(qǐng)中斷服務(wù),而是直接更換了父中斷的流控回調(diào),在父中斷的流控回調(diào)中實(shí)現(xiàn)子中斷的二次分發(fā)。但是這在有些情況下會(huì)給我們帶來不便,因?yàn)榱骺鼗卣{(diào)要獲取子控制器的中斷源,而流控回調(diào)運(yùn)行在中斷上下文中,對(duì)于那些子控制器需要通過慢速總線訪問的設(shè)備,在中斷上下文中訪問顯然不太合適,這時(shí)我們可以把子中斷分發(fā)放在父中斷的中斷線程中進(jìn)行,這就是我所說的所謂中斷線程嵌套模式。下面是大概的實(shí)現(xiàn)過程:
對(duì)于父中斷,具體的實(shí)現(xiàn)步驟如下:
首先,父中斷的irq編號(hào)可以從板級(jí)代碼的預(yù)定義中獲得,或者通過device的platform_data字段獲得;
使用父中斷的irq編號(hào),利用request_threaded_irq函數(shù)申請(qǐng)中斷服務(wù),需要提供thread_fn參數(shù)和dev_id參數(shù);
dev_id參數(shù)要能夠用于判別子控制器的中斷來源;
實(shí)現(xiàn)父中斷的thread_fn函數(shù),其中只需獲得并計(jì)算子設(shè)備的irq編號(hào),然后調(diào)用handle_nested_irq即可;
對(duì)于子設(shè)備,具體的實(shí)現(xiàn)步驟如下
為設(shè)備內(nèi)的中斷控制器實(shí)現(xiàn)一個(gè)irq_chip結(jié)構(gòu),實(shí)現(xiàn)其中必要的回調(diào),例如irq_mask,irq_unmask,irq_ack等;
循環(huán)每一個(gè)子設(shè)備,做以下動(dòng)作:
為每個(gè)子設(shè)備,使用irq_alloc_descs函數(shù)申請(qǐng)irq編號(hào);
使用irq_set_chip_data設(shè)置必要的cookie數(shù)據(jù);
使用irq_set_chip_and_handler設(shè)置子控制器的irq_chip實(shí)例和子irq的流控處理程序,通常使用標(biāo)準(zhǔn)的流控函數(shù),例如handle_edge_irq;
使用irq_set_nested_thread函數(shù),把子設(shè)備irq的線程嵌套特性打開;
子設(shè)備的驅(qū)動(dòng)程序使用自身申請(qǐng)到的irq編號(hào),按照正常流程申請(qǐng)中斷服務(wù)即可。
應(yīng)為子設(shè)備irq的線程嵌套特性被打開,使用request_threaded_irq申請(qǐng)子設(shè)備的中斷服務(wù)時(shí),即是是提供了handler參數(shù),中斷子系統(tǒng)也不會(huì)使用它,同時(shí)也不會(huì)為它創(chuàng)建中斷線程,子設(shè)備的thread_fn回調(diào)是在父中斷的中斷線程中,通過handle_nested_irq調(diào)用的,也就是說,盡管子中斷有自己獨(dú)立的irq編號(hào),但是它們沒有獨(dú)立的中斷線程,只是共享了父中斷的中斷服務(wù)線程。
?
評(píng)論
查看更多