Linux中斷(interrupt)子系統之四:驅動程式介面層 & 中斷通用邏輯層

來源:互聯網
上載者:User

在本系列文章的第一篇:Linux中斷(interrupt)子系統之一:中斷系統基本原理,我把通用中斷子系統分為了4個層次,其中的驅動程式介面層和中斷通用邏輯層的界限實際上不是很明確,因為中斷通用邏輯層的很多介面,既可以被驅動程式使用,也可以被硬體封裝層使用,所以我把這兩部分的內容放在一起進行討論。

本章我將會討論這兩層對外提供的標準介面和內部實現機制,幾乎所有的介面都是圍繞著irq_desc和irq_chip這兩個結構體進行的,對這兩個結構體不熟悉的讀者可以現讀一下前面幾篇文章。

/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/

1.  irq的開啟和關閉

中斷子系統為我們提供了一系列用於irq的開啟和關閉的函數介面,其中最基本的一對是:

  • disable_irq(unsigned int irq);
  • enable_irq(unsigned int irq);
這兩個API應該配對使用,disable_irq可以被多次嵌套調用,要想重新開啟irq,enable_irq必須也要被調用同樣的次數,為此,irq_desc結構中的depth欄位專門用於這兩個API嵌套深度的管理。當某個irq首次被驅動程式申請時,預設情況下,設定depth的初始值是0,對應的irq處於開啟狀態。我們看看disable_irq的調用過程:

                                                                             圖1.1  disable_irq的調用過程

函數的開始使用非同步方式的內建函式__disable_irq_nosync(),所謂非同步方式就是不理會當前該irq是否正在被處理(有handler在運行或者有中斷線程尚未結束)。有些中斷控制器可能掛在某個慢速的匯流排上,所以在進一步處理前,先通過irq_get_desc_buslock獲得匯流排鎖(最終會調用chip->irq_bus_lock),然後進入內建函式__disable_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();

中斷子系統開啟irq的的API是:

  • enable_irq();

開啟irq無需提供同步的版本,因為irq開啟前,沒有handler和線程在運行,我們關注一下他對depth的處理,他在內建函式__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);

        irq  需要申請的irq編號,對於ARM體系,irq編號通常在平台級的代碼中事先定義好,有時候也可以動態申請。

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

進入__setup_irq函數,如果參數flag中設定了IRQF_SAMPLE_RANDOM標誌,它會調用rand_initialize_irq,以便對隨機數的產生產生影響。如果申請的不是一個線程嵌套中斷(關於線程嵌套中斷,請參閱Linux中斷(interrupt)子系統之三:中斷流控處理層中的handle_nested_irq一節),而且提供了thread_fn參數,它將建立一個核心線程:

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結構中斷action鏈表不為空白,說明這個irq已經被其它裝置申請過,也就是說,這是一個共用中斷,所以接下來會判斷這個新申請的中斷與已經申請的舊中斷的以下幾個標誌是否一致:

  • 一定要設定了IRQF_SHARED標誌
  • 電氣觸發方式要完全一樣(IRQF_TRIGGER_XXXX)
  • IRQF_PERCPU要一致
  • IRQF_ONESHOT要一致

檢查這些條件都是因為多個裝置試圖共用一根中斷線,試想一下,如果一個裝置要求上升沿中斷,一個裝置要求電平中斷,當中斷到達時,核心將不知如何選擇合適的流控操作。完成檢查後,函數找出action鏈表中最後一個irqaction執行個體的指標。

/* 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被申請後,各個資料結構之間的關係:

                                                     圖3.1  irq各個資料結構之間的關係

4.  動態擴充irq編號在ARM體系的行動裝置中,irq的編號通常在平台級或板級代碼中事先根據硬體的串連定義好,最大的irq數目也用NR_IRQS常量指定。幾種情況下,我們希望能夠動態地增加系統中irq的數量:
  • 配置了CONFIG_SPARSE_IRQ核心配置項,使用基數樹動態管理irq_desc結構。
  • 針對多功能複合裝置,內部具備多個中斷源,但中斷觸發引腳只有一個,為了實現驅動程式的跨平台,不希望這些中斷源的irq被寫入程式碼在板級代碼中。

中斷子系統為我們提供了以下幾個api,用於動態申請/擴充irq編號:

         irq_alloc_desc(node)  申請一個irq,node是對應記憶體節點的編號;         irq_alloc_desc_at(at, node)  在指定位置申請一個irq,如果指定位置已經被佔用,則申請失敗;         irq_alloc_desc_from(from, node)  從指定位置開始搜尋,申請一個irq;         irq_alloc_descs(irq, from, cnt, node)  申請多個連續的irq編號,從from位置開始搜尋;         irq_free_descs(irq, cnt)  釋放irq資源;以上這些申請函數(宏),會為我們申請相應的irq_desc結構並初始化為預設狀態,要想這些irq能夠正常工作,我們還要使用第二節提到的api,對必要的欄位進行設定,例如:
  • irq_set_chip_and_handler_name
  • irq_set_handler_data
  • irq_set_chip_data

對於沒有配置CONFIG_SPARSE_IRQ核心配置項的核心,irq_desc是一個數組,根本不可能做到動態擴充,但是很多驅動又確實使用到了上述api,尤其是mfd驅動,這些驅動並沒有我們一定要配置CONFIG_SPARSE_IRQ選項,要想不對這些驅動做出修改,你只能妥協一下,在你的板級代碼中把NR_IRQS定義得大一些,留出足夠的保留數量

5.  多功能複合裝置的中斷處理

在行動裝置系統中,存在著大量的多功能複合裝置,最常見的是一個晶片中,內部整合了多個功能組件,或者是一個模組單元內部整合了功能組件,這些內部功能組件可以各自產生插斷要求,但是晶片或者硬體模組對外只有一個插斷要求引腳,我們可以使用多種方式處理這些裝置的插斷要求,以下我們逐一討論這些方法。

5.1  單一中斷模式 

       對於這種複合裝置,通常裝置中會提供某種方式,以便讓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;}

5.2  共用中斷模式

共用中斷模式充分利用了通用中斷子系統的特性,經過前面的討論,我們知道,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將會被執行;
5.3  中斷控制器級聯模式多數多功能複合裝置內部提供了基本的中斷控制器功能,例如可以單獨地控制某個子中斷的開啟和關閉,並且可以方便地獲得子中斷源,對於這種裝置,我們可以把裝置內的中斷控制器實現為一個子控制器,然後使用中斷控制器級聯模式。這種模式下,各個子裝置擁有各自獨立的irq編號,中斷服務通過父中斷進行分發。對於父中斷,具體的實現步驟如下:
  • 首先,父中斷的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編號,按照正常流程申請中斷服務即可。
5.4  中斷線程嵌套模式該模式與中斷控制器級聯模式大體相似,只不過級聯模式時,父中斷無需通過request_threaded_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編號,按照正常流程申請中斷服務即可。

應為子裝置irq的線程嵌套特性被開啟,使用request_threaded_irq申請子裝置的中斷服務時,即是是提供了handler參數,中斷子系統也不會使用它,同時也不會為它建立中斷線程,子裝置的thread_fn回調是在父中斷的中斷線程中,通過handle_nested_irq調用的,也就是說,儘管子中斷有自己獨立的irq編號,但是它們沒有獨立的中斷線程,只是共用了父中斷的中斷服務線程。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.