一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Linux中斷子系統(tǒng)(一)-中斷控制器及驅(qū)動分析

 myallmy 2021-11-19

 背景

  • Read the fucking source code!  --By 魯迅
  • A picture is worth a thousand words. --By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

從這篇文章開始,來聊一聊中斷子系統(tǒng)。中斷是處理器用于異步處理外圍設(shè)備請求的一種機制,可以說中斷處理是操作系統(tǒng)管理外圍設(shè)備的基石,此外系統(tǒng)調(diào)度、核間交互等都離不開中斷,它的重要性不言而喻。

來一張概要的分層圖:

圖片


  • 硬件層:最下層為硬件連接層,對應(yīng)的是具體的外設(shè)與SoC的物理連接,中斷信號是從外設(shè)到中斷控制器,由中斷控制器統(tǒng)一管理,再路由到處理器上;
  • 硬件相關(guān)層:這個層包括兩部分代碼,一部分是架構(gòu)相關(guān)的,比如ARM64處理器處理中斷相關(guān),另一部分是中斷控制器的驅(qū)動代碼;
  • 通用層:這部分也可以認(rèn)為是框架層,是硬件無關(guān)層,這部分代碼在所有硬件平臺上是通用的;
  • 用戶層:這部分也就是中斷的使用者了,主要是各類設(shè)備驅(qū)動,通過中斷相關(guān)接口來進行申請和注冊,最終在外設(shè)觸發(fā)中斷時,進行相應(yīng)的回調(diào)處理;

中斷子系統(tǒng)系列文章,會包括硬件相關(guān)、中斷框架層、上半部與下半部、Softirq、Workqueue等機制的介紹,本文會先介紹硬件相關(guān)的原理及驅(qū)動,前戲結(jié)束,直奔主題。

2. GIC硬件原理

  • ARM公司提供了一個通用的中斷控制器GIC(Generic Interrupt Controller),GIC的版本包括V1 ~ V4,由于本人使用的SoC中的中斷控制器是V2版本,本文將圍繞GIC-V2來展開介紹;

來一張功能版的框圖:

圖片


  • GIC-V2從功能上說,除了常用的中斷使能、中斷屏蔽、優(yōu)先級管理等功能外,還支持安全擴展、虛擬化等;
  • GIC-V2從組成上說,主要分為DistributorCPU Interface兩個模塊,Distributor主要負(fù)責(zé)中斷源的管理,包括優(yōu)先級的處理,屏蔽、搶占等,并將最高優(yōu)先級的中斷分發(fā)給CPU Interface,CPU Interface主要用于連接處理器,與處理器進行交互;
  • Virtual DistributorVirtual CPU Interface都與虛擬化相關(guān),本文不深入分析;

再來一張細(xì)節(jié)圖看看DistributorCPU Interface的功能:

圖片


  • GIC-V2支持三種類型的中斷:

    1. SGI(software-generated interrupts):軟件產(chǎn)生的中斷,主要用于核間交互,內(nèi)核中的IPI:inter-processor interrupts就是基于SGI,中斷號ID0 - ID15用于SGI;
    2. PPI(Private Peripheral Interrupt):私有外設(shè)中斷,每個CPU都有自己的私有中斷,典型的應(yīng)用有local timer,中斷號ID16 - ID31用于PPI;
    3. SPI(Shared Peripheral Interrupt):共享外設(shè)中斷,中斷產(chǎn)生后,可以分發(fā)到某一個CPU上,中斷號ID32 - ID1019用于SPI,ID1020 - ID1023保留用于特殊用途;
  • Distributor功能:

    1. 全局開關(guān)控制Distributor分發(fā)到CPU Interface
    2. 打開或關(guān)閉每個中斷;
    3. 設(shè)置每個中斷的優(yōu)先級;
    4. 設(shè)置每個中斷將路由的CPU列表;
    5. 設(shè)置每個外設(shè)中斷的觸發(fā)方式:電平觸發(fā)、邊緣觸發(fā);
    6. 設(shè)置每個中斷的Group:Group0或Group1,其中Group0用于安全中斷,支持FIQ和IRQ,Group1用于非安全中斷,只支持IRQ;
    7. SGI中斷分發(fā)到目標(biāo)CPU上;
    8. 每個中斷的狀態(tài)可見;
    9. 提供軟件機制來設(shè)置和清除外設(shè)中斷的pending狀態(tài);
  • CPU Interface功能:

    1. 使能中斷請求信號到CPU上;
    2. 中斷的確認(rèn);
    3. 標(biāo)識中斷處理的完成;
    4. 為處理器設(shè)置中斷優(yōu)先級掩碼;
    5. 設(shè)置處理器的中斷搶占策略;
    6. 確定處理器的最高優(yōu)先級pending中斷;

中斷處理的狀態(tài)機如下圖:

圖片


  • Inactive:無中斷狀態(tài);
  • Pending:硬件或軟件觸發(fā)了中斷,但尚未傳遞到目標(biāo)CPU,在電平觸發(fā)模式下,產(chǎn)生中斷的同時保持pending狀態(tài);
  • Active:發(fā)生了中斷并將其傳遞給目標(biāo)CPU,并且目標(biāo)CPU可以處理該中斷;
  • Active and pending:發(fā)生了中斷并將其傳遞給目標(biāo)CPU,同時發(fā)生了相同的中斷并且該中斷正在等待處理;

GIC檢測中斷流程如下:

  1. GIC捕獲中斷信號,中斷信號assert,標(biāo)記為pending狀態(tài);
  2. Distributor確定好目標(biāo)CPU后,將中斷信號發(fā)送到目標(biāo)CPU上,同時,對于每個CPU,Distributor會從pending信號中選擇最高優(yōu)先級中斷發(fā)送至CPU Interface;
  3. CPU Interface來決定是否將中斷信號發(fā)送至目標(biāo)CPU;
  4. CPU完成中斷處理后,發(fā)送一個完成信號EOI(End of Interrupt)給GIC;

3. GIC驅(qū)動分析

3.1 設(shè)備信息添加

ARM平臺的設(shè)備信息,都是通過Device Tree設(shè)備樹來添加,設(shè)備樹信息放置在arch/arm64/boot/dts/

下圖就是一個中斷控制器的設(shè)備樹信息:

圖片


  • compatible字段:用于與具體的驅(qū)動來進行匹配,比如圖片中arm, gic-400,可以根據(jù)這個名字去匹配對應(yīng)的驅(qū)動程序;
  • interrupt-cells字段:用于指定編碼一個中斷源所需要的單元個數(shù),這個值為3。比如在外設(shè)在設(shè)備樹中添加中斷信號時,通常能看到類似interrupts = <0 23 4>;的信息,第一個單元0,表示的是中斷類型(1:PPI,0:SPI),第二個單元23表示的是中斷號,第三個單元4表示的是中斷觸發(fā)的類型;
  • reg字段:描述中斷控制器的地址信息以及地址范圍,比如圖片中分別制定了GIC Distributor(GICD)GIC CPU Interface(GICC)的地址信息;
  • interrupt-controller字段:表示該設(shè)備是一個中斷控制器,外設(shè)可以連接在該中斷控制器上;
  • 關(guān)于設(shè)備數(shù)的各個字段含義,詳細(xì)可以參考Documentation/devicetree/bindings下的對應(yīng)信息;

設(shè)備樹的信息,是怎么添加到系統(tǒng)中的呢?Device Tree最終會編譯成dtb文件,并通過Uboot傳遞給內(nèi)核,在內(nèi)核啟動后會將dtb文件解析成device_node結(jié)構(gòu)。關(guān)于設(shè)備樹的相關(guān)知識,本文先不展開,后續(xù)再找機會補充。來一張圖,先簡要介紹下關(guān)鍵路徑:

圖片


  • 設(shè)備樹的節(jié)點信息,最終會變成device_node結(jié)構(gòu),在內(nèi)存中維持一個樹狀結(jié)構(gòu);
  • 設(shè)備與驅(qū)動,會根據(jù)compatible字段進行匹配;

3.2 驅(qū)動流程分析

GIC驅(qū)動的執(zhí)行流程如下圖所示:

圖片


  • 首先需要了解一下鏈接腳本vmlinux.lds,腳本中定義了一個__irqchip_of_table段,該段用于存放中斷控制器信息,用于最終來匹配設(shè)備;
  • 在GIC驅(qū)動程序中,使用IRQCHIP_DECLARE宏來聲明結(jié)構(gòu)信息,包括compatible字段和回調(diào)函數(shù),該宏會將這個結(jié)構(gòu)放置到__irqchip_of_table字段中;
  • 在內(nèi)核啟動初始化中斷的函數(shù)中,of_irq_init函數(shù)會去查找設(shè)備節(jié)點信息,該函數(shù)的傳入?yún)?shù)就是__irqchip_of_table段,由于IRQCHIP_DECLARE已經(jīng)將信息填充好了,of_irq_init函數(shù)會根據(jù)arm,gic-400去查找對應(yīng)的設(shè)備節(jié)點,并獲取設(shè)備的信息。中斷控制器也存在級聯(lián)的情況,of_irq_init函數(shù)中也處理了這種情況;
  • or_irq_init函數(shù)中,最終會回調(diào)IRQCHIP_DECLARE聲明的回調(diào)函數(shù),也就是gic_of_init,而這個函數(shù)就是GIC驅(qū)動的初始化入口函數(shù)了;
  • GIC的工作,本質(zhì)上是由中斷信號來驅(qū)動,因此驅(qū)動本身的工作就是完成各類信息的初始化,注冊好相應(yīng)的回調(diào)函數(shù),以便能在信號到來之時去執(zhí)行;
  • set_smp_process_call設(shè)置__smp_cross_call函數(shù)指向gic_raise_softirq,本質(zhì)上就是通過軟件來觸發(fā)GIC的SGI中斷,用于核間交互;
  • cpuhp_setup_state_nocalls函數(shù),設(shè)置好CPU進行熱插拔時GIC的回調(diào)函數(shù),以便在CPU熱插拔時做相應(yīng)處理;
  • set_handle_irq函數(shù)的設(shè)置很關(guān)鍵,它將全局函數(shù)指針handle_arch_irq指向了gic_handle_irq,而處理器在進入中斷異常時,會跳轉(zhuǎn)到handle_arch_irq執(zhí)行,所以,可以認(rèn)為它就是中斷處理的入口函數(shù)了;
  • 驅(qū)動中完成了各類函數(shù)的注冊,此外還完成了irq_chip, irq_domain等結(jié)構(gòu)體的初始化,這些結(jié)構(gòu)在下文會進一步分析;
  • 最后,完成GIC硬件模塊的初始化設(shè)置,以及電源管理相關(guān)的注冊等工作;

3.3 數(shù)據(jù)結(jié)構(gòu)分析

先來張圖:

圖片


  • GIC驅(qū)動中,使用struct gic_chip_data結(jié)構(gòu)體來描述GIC控制器的信息,整個驅(qū)動都是圍繞著該結(jié)構(gòu)體的初始化,驅(qū)動中將函數(shù)指針都初始化好,實際的工作是由中斷信號觸發(fā),也就是在中斷來臨的時候去進行回調(diào);
  • struct irq_chip結(jié)構(gòu),描述的是中斷控制器的底層操作函數(shù)集,這些函數(shù)集最終完成對控制器硬件的操作;
  • struct irq_domain結(jié)構(gòu),用于硬件中斷號和Linux IRQ中斷號(virq,虛擬中斷號)之間的映射;

還是上一下具體的數(shù)據(jù)結(jié)構(gòu)代碼吧,關(guān)鍵注釋如下:

struct irq_chip { struct device *parent_device; //指向父設(shè)備 const char *name; // /proc/interrupts中顯示的名字 unsigned int (*irq_startup)(struct irq_data *data); //啟動中斷,如果設(shè)置成NULL,則默認(rèn)為enable void (*irq_shutdown)(struct irq_data *data); //關(guān)閉中斷,如果設(shè)置成NULL,則默認(rèn)為disable void (*irq_enable)(struct irq_data *data); //中斷使能,如果設(shè)置成NULL,則默認(rèn)為chip->unmask void (*irq_disable)(struct irq_data *data); //中斷禁止
void (*irq_ack)(struct irq_data *data); //開始新的中斷 void (*irq_mask)(struct irq_data *data); //中斷源屏蔽 void (*irq_mask_ack)(struct irq_data *data); //應(yīng)答并屏蔽中斷 void (*irq_unmask)(struct irq_data *data); //解除中斷屏蔽 void (*irq_eoi)(struct irq_data *data); //中斷處理結(jié)束后調(diào)用
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force); //在SMP中設(shè)置CPU親和力 int (*irq_retrigger)(struct irq_data *data); //重新發(fā)送中斷到CPU int (*irq_set_type)(struct irq_data *data, unsigned int flow_type); //設(shè)置中斷觸發(fā)類型 int (*irq_set_wake)(struct irq_data *data, unsigned int on); //使能/禁止電源管理中的喚醒功能
void (*irq_bus_lock)(struct irq_data *data); //慢速芯片總線上的鎖 void (*irq_bus_sync_unlock)(struct irq_data *data); //同步釋放慢速總線芯片的鎖
void (*irq_cpu_online)(struct irq_data *data); void (*irq_cpu_offline)(struct irq_data *data);
void (*irq_suspend)(struct irq_data *data); void (*irq_resume)(struct irq_data *data); void (*irq_pm_shutdown)(struct irq_data *data);
void (*irq_calc_mask)(struct irq_data *data);
void (*irq_print_chip)(struct irq_data *data, struct seq_file *p); int (*irq_request_resources)(struct irq_data *data); void (*irq_release_resources)(struct irq_data *data);
void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg); void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);
int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state); int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);
int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
void (*ipi_send_single)(struct irq_data *data, unsigned int cpu); void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);
unsigned long flags;};
struct irq_domain { struct list_head link; //用于添加到全局鏈表irq_domain_list中 const char *name; //IRQ domain的名字 const struct irq_domain_ops *ops; //IRQ domain映射操作函數(shù)集 void *host_data; //在GIC驅(qū)動中,指向了irq_gic_data unsigned int flags; unsigned int mapcount; //映射中斷的個數(shù)
/* Optional data */ struct fwnode_handle *fwnode; enum irq_domain_bus_token bus_token; struct irq_domain_chip_generic *gc;#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY struct irq_domain *parent; //支持級聯(lián)的話,指向父設(shè)備#endif#ifdef CONFIG_GENERIC_IRQ_DEBUGFS struct dentry *debugfs_file;#endif
/* reverse map data. The linear map gets appended to the irq_domain */ irq_hw_number_t hwirq_max; //IRQ domain支持中斷數(shù)量的最大值 unsigned int revmap_direct_max_irq; unsigned int revmap_size; //線性映射的大小 struct radix_tree_root revmap_tree; //Radix Tree映射的根節(jié)點 unsigned int linear_revmap[]; //線性映射用到的查找表};
struct irq_domain_ops { int (*match)(struct irq_domain *d, struct device_node *node, enum irq_domain_bus_token bus_token); // 用于中斷控制器設(shè)備與IRQ domain的匹配 int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec, enum irq_domain_bus_token bus_token); int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw); //用于硬件中斷號與Linux中斷號的映射 void (*unmap)(struct irq_domain *d, unsigned int virq); int (*xlate)(struct irq_domain *d, struct device_node *node, const u32 *intspec, unsigned int intsize, unsigned long *out_hwirq, unsigned int *out_type); //通過device_node,解析硬件中斷號和觸發(fā)方式
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY /* extended V2 interfaces to support hierarchy irq_domains */ int (*alloc)(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs, void *arg); void (*free)(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs); void (*activate)(struct irq_domain *d, struct irq_data *irq_data); void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data); int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *out_hwirq, unsigned int *out_type);#endif};

3.3.1 IRQ domain

IRQ domain用于將硬件的中斷號,轉(zhuǎn)換成Linux系統(tǒng)中的中斷號(virtual irq, virq),來張圖:

圖片


  • 每個中斷控制器都對應(yīng)一個IRQ Domain;
  • 中斷控制器驅(qū)動通過irq_domain_add_*()接口來創(chuàng)建IRQ Domain;
  • IRQ Domain支持三種映射方式:linear map(線性映射),tree map(樹映射),no map(不映射);
    1. linear map:維護固定大小的表,索引是硬件中斷號,如果硬件中斷最大數(shù)量固定,并且數(shù)值不大,可以選擇線性映射;
    2. tree map:硬件中斷號可能很大,可以選擇樹映射;
    3. no map:硬件中斷號直接就是Linux的中斷號;

三種映射的方式如下圖:

圖片


  • 圖中描述了三個中斷控制器,對應(yīng)到三種不同的映射方式;
  • 各個控制器的硬件中斷號可以一樣,最終在Linux內(nèi)核中映射的中斷號是唯一的;

4. Arch-speicific代碼分析

  • 中斷也是異常模式的一種,當(dāng)外設(shè)觸發(fā)中斷時,處理器會切換到特定的異常模式進行處理,而這部分代碼都是架構(gòu)相關(guān)的;ARM64的代碼位于arch/arm64/kernel/entry.S。
  • ARM64處理器有四個異常級別Exception Level:0~3,EL0級對應(yīng)用戶態(tài)程序,EL1級對應(yīng)操作系統(tǒng)內(nèi)核態(tài),EL2級對應(yīng)Hypervisor,EL3級對應(yīng)Secure Monitor;
  • 異常觸發(fā)時,處理器進行切換,并且跳轉(zhuǎn)到異常向量表開始執(zhí)行,針對中斷異常,最終會跳轉(zhuǎn)到irq_handler中;

代碼比較簡單,如下:

/* * Interrupt handling. */	.macro	irq_handler	ldr_l	x1, handle_arch_irq	mov	x0, sp	irq_stack_entry	blr	x1	irq_stack_exit	.endm

來張圖:

圖片


  • 中斷觸發(fā),處理器去異常向量表找到對應(yīng)的入口,比如EL0的中斷跳轉(zhuǎn)到el0_irq處,EL1則跳轉(zhuǎn)到el1_irq處;
  • 在GIC驅(qū)動中,會調(diào)用set_handle_irq接口來設(shè)置handle_arch_irq的函數(shù)指針,讓它指向gic_handle_irq,因此中斷觸發(fā)的時候會跳轉(zhuǎn)到gic_handle_irq處執(zhí)行;
  • gic_handle_irq函數(shù)處理時,分為兩種情況,一種是外設(shè)觸發(fā)的中斷,硬件中斷號在16 ~ 1020之間,一種是軟件觸發(fā)的中斷,用于處理器之間的交互,硬件中斷號在16以內(nèi);
  • 外設(shè)觸發(fā)中斷后,根據(jù)irq domain去查找對應(yīng)的Linux IRQ中斷號,進而得到中斷描述符irq_desc,最終也就能調(diào)用到外設(shè)的中斷處理函數(shù)了;

GIC和Arch相關(guān)的介紹就此打住,下一篇文章會接著介紹通用的中斷處理框架,敬請期待。

參考

ARM Generic Interrupt Controller Architecture version 2.0

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    精品国产91亚洲一区二区三区| 亚洲永久一区二区三区在线| 韩国日本欧美国产三级| 中文字幕日韩一区二区不卡| 国产一级不卡视频在线观看| 精品国模一区二区三区欧美| 在线日韩中文字幕一区| 国产a天堂一区二区专区| 欧美日韩欧美国产另类| 欧洲偷拍视频中文字幕| 亚洲一区二区三区四区性色av | 少妇一区二区三区精品| 欧美日韩亚洲国产av| 国产三级不卡在线观看视频| 久热人妻中文字幕一区二区| 国产日韩熟女中文字幕| 国产免费成人激情视频| 免费播放一区二区三区四区| 激情五月激情婷婷丁香| 亚洲视频偷拍福利来袭| 好吊色免费在线观看视频| 日韩av生活片一区二区三区| 国产在线视频好看不卡| 国产欧美日韩视频91| 国产精品白丝一区二区| 亚洲一区二区福利在线| 插进她的身体里在线观看骚| 欧美熟妇喷浆一区二区| 99久久人妻中文字幕| 欧美日韩亚洲国产综合网 | 日本加勒比中文在线观看| 国产又粗又猛又大爽又黄| 亚洲成人黄色一级大片| 中日韩美女黄色一级片| 日本加勒比中文在线观看| 亚洲欧美日韩在线中文字幕| 日本一本不卡免费视频| 久久亚洲精品中文字幕| 69老司机精品视频在线观看| 五月婷日韩中文字幕四虎| 午夜精品一区二区三区国产|