GICv3驅動初始化

linux驅動支援GICv1, GICv2, GICv3, GICv4驅動,本節我們重點來描述下GICv3的驅動初始化,結合ARM-Cortex平台詳細描述

intc: interrupt-controller@666688888 {          compatible = "arm,gic-v3";          #interrupt-cells = <3>;          interrupt-controller;          #redistributor-regions = <1>;          redistributor-stride = <0x0 0x20000>;          reg = <0x666688888 0x10000>,     /* GICD */                <0x6666e8888 0x100000>;    /* GICR * 8 */          interrupts = <GIC_PPI 8 IRQ_TYPE_LEVEL_HIGH>;          interrupt-parent = <&intc>;  };

這裡面有幾個重點的欄位:可以參考內核文檔arm,gic-v3.txt文檔,

* ARM Generic Interrupt Controller, version 3    AArch64 SMP cores are often associated with a GICv3, providing Private  Peripheral Interrupts (PPI), Shared Peripheral Interrupts (SPI),  Software Generated Interrupts (SGI), and Locality-specific Peripheral  Interrupts (LPI).    Main node required properties:    - compatible : should at least contain  "arm,gic-v3".  - interrupt-controller : Identifies the node as an interrupt controller  - #interrupt-cells : Specifies the number of cells needed to encode an    interrupt source. Must be a single cell with a value of at least 3.    If the system requires describing PPI affinity, then the value must    be at least 4.      The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI    interrupts. Other values are reserved for future use.      The 2nd cell contains the interrupt number for the interrupt type.    SPI interrupts are in the range [0-987]. PPI interrupts are in the    range [0-15].      The 3rd cell is the flags, encoded as follows:  	bits[3:0] trigger type and level flags.  		1 = edge triggered  		4 = level triggered      The 4th cell is a phandle to a node describing a set of CPUs this    interrupt is affine to. The interrupt must be a PPI, and the node    pointed must be a subnode of the "ppi-partitions" subnode. For    interrupt types other than PPI or PPIs that are not partitionned,    this cell must be zero. See the "ppi-partitions" node description    below.      Cells 5 and beyond are reserved for future use and must have a value    of 0 if present.    - reg : Specifies base physical address(s) and size of the GIC    registers, in the following order:    - GIC Distributor interface (GICD)    - GIC Redistributors (GICR), one range per redistributor region    - GIC CPU interface (GICC)    - GIC Hypervisor interface (GICH)    - GIC Virtual CPU interface (GICV)      GICC, GICH and GICV are optional.    - interrupts : Interrupt source of the VGIC maintenance interrupt.
  • compatible: 用於和對應的驅動匹配,不再詳說
  • interrupt-cells用於描述一個中斷源的詳細資訊,此值等於3代表interrupts中有三個欄位
    • 第一個欄位代表中斷類型(GIC_PPI, GIC_SPI)
    • 第二個欄位物理中斷號,根據中斷類型中斷號的範圍不同。SPI(0-987)PPI(0-15)
    • 第三個欄位代表的中斷觸發方式
  • interrupt-controller: 描述此欄位是一個中斷控制器
  • interrupt-parent: 代表此中斷控制器是否是級聯的,如果沒有此欄位,則跟隨父欄位
  • reg描述的是中斷控制器中的涉及的暫存器
    • 0x666688888 代表的是Distributor的基址暫存器,GICD
    • 0x6666e8888 代表的是Redistributor的基址暫存器,GICR

了解了DTS,我們則繼續看下對應GICv3的驅動

IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);

大家可以把這個宏展開開下,展開之後如下,展開之後會有一個__irqchip_of_table的段

static const struct of_device_id __of_table_gic_v3      __used __section(__irqchip_of_table)       = {            .compatible = "arm,gic-v3",            .data = gic_of_init         }

這個段會在鏈接腳本中有詳細的描述,當開機的時候,會去從__irqchip_of_table去讀取此,然後做比較

void __init init_IRQ(void)  {      init_irq_stacks();      irqchip_init();      if (!handle_arch_irq)          panic("No interrupt controller found.");  }    void __init irqchip_init(void)  {      of_irq_init(__irqchip_of_table);      acpi_probe_device_table(irqchip);  }

最終在of_irq_init函數中根據dts來匹配到正確的中斷控制器。匹配到正確的中斷控制器後,會調用上面的data回調函數就是gic_of_init,也就是對中斷控制器做初始化操作

for_each_matching_node_and_match(np, matches, &match) {      if (!of_property_read_bool(np, "interrupt-controller") ||                //如果不是中斷控制器,則跳過              !of_device_is_available(np))          continue;        /*       * Here, we allocate and populate an of_intc_desc with the node       * pointer, interrupt-parent device_node etc.       */      desc = kzalloc(sizeof(*desc), GFP_KERNEL);      if (WARN_ON(!desc)) {          of_node_put(np);          goto err;      }        desc->irq_init_cb = match->data;      desc->dev = of_node_get(np);      desc->interrupt_parent = of_irq_find_parent(np);      if (desc->interrupt_parent == np)          desc->interrupt_parent = NULL;      list_add_tail(&desc->list, &intc_desc_list);  }
  • 找到存在interrupt-controller的欄位
  • 分配中斷控制器描述符,設置中斷控制器的irq_init_cb回調函數
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {      int ret;        if (desc->interrupt_parent != parent)          continue;        list_del(&desc->list);        of_node_set_flag(desc->dev, OF_POPULATED);        pr_debug("of_irq_init: init %pOF (%p), parent %pn",           desc->dev,           desc->dev, desc->interrupt_parent);      ret = desc->irq_init_cb(desc->dev,             //回調設置的中斷控制器的初始化處理函數                  desc->interrupt_parent);

這樣一來就調用到gic_of_init函數了

static int __init gic_of_init(struct device_node *node, struct device_node *parent)  {      void __iomem *dist_base;      struct redist_region *rdist_regs;      u64 redist_stride;      u32 nr_redist_regions;      int err, i;        dist_base = of_iomap(node, 0);        err = gic_validate_dist_version(dist_base);      if (err) {          pr_err("%pOF: no distributor detected, giving upn", node);          goto out_unmap_dist;      }        if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))          nr_redist_regions = 1;        rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs),                   GFP_KERNEL);        for (i = 0; i < nr_redist_regions; i++) {          struct resource res;          int ret;            ret = of_address_to_resource(node, 1 + i, &res);          rdist_regs[i].redist_base = of_iomap(node, 1 + i);          if (ret || !rdist_regs[i].redist_base) {              pr_err("%pOF: couldn't map region %dn", node, i);              err = -ENODEV;              goto out_unmap_rdist;          }          rdist_regs[i].phys_base = res.start;      }        if (of_property_read_u64(node, "redistributor-stride", &redist_stride))          redist_stride = 0;        err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,                   redist_stride, &node->fwnode);      return 0;  }
  • of_iomap獲取終端控制器distributor的基址
  • gic_validate_dist_version根據基址判斷當前是v3還是v4版本
  • 讀取redisttibutor的屬性,獲取對應暫存器的基址
  • 最終會調用到gic_init_bases函數中做相應的初始化
static int __init gic_init_bases(void __iomem *dist_base, struct redist_region *rdist_regs,  u32 nr_redist_regions,  u64 redist_stride,  struct fwnode_handle *handle)  {      u32 typer;      int gic_irqs;      int err;        if (!is_hyp_mode_available())          static_branch_disable(&supports_deactivate_key);        if (static_branch_likely(&supports_deactivate_key))          pr_info("GIC: Using split EOI/Deactivate moden");        gic_data.fwnode = handle;      gic_data.dist_base = dist_base;      gic_data.redist_regions = rdist_regs;      gic_data.nr_redist_regions = nr_redist_regions;      gic_data.redist_stride = redist_stride;        /*       * Find out how many interrupts are supported.       * The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI)       */      typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);      gic_data.rdists.gicd_typer = typer;      gic_irqs = GICD_TYPER_IRQS(typer);      if (gic_irqs > 1020)          gic_irqs = 1020;      gic_data.irq_nr = gic_irqs;        gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,                           &gic_data);      irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);      gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));      gic_data.rdists.has_vlpis = true;      gic_data.rdists.has_direct_lpi = true;        if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {          err = -ENOMEM;          goto out_free;      }        gic_data.has_rss = !!(typer & GICD_TYPER_RSS);      pr_info("Distributor has %sRange Selector supportn",          gic_data.has_rss ? "" : "no ");        if (typer & GICD_TYPER_MBIS) {          err = mbi_init(handle, gic_data.domain);          if (err)              pr_err("Failed to initialize MBIsn");      }        set_handle_irq(gic_handle_irq);        gic_update_vlpi_properties();        if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis() &&              !IS_ENABLED(CONFIG_ARM_GIC_V3_ACL))          its_init(handle, &gic_data.rdists, gic_data.domain);        gic_smp_init();      gic_dist_init();      gic_cpu_init();      gic_cpu_pm_init();        return 0;    out_free:      if (gic_data.domain)          irq_domain_remove(gic_data.domain);      free_percpu(gic_data.rdists.rdist);      return err;  }
  • is_hyp_mode_available判斷當前是否在Hyp虛擬化模式
  • 根據參數初始化gic_data結構
  • 通過讀取GICD_TYPER暫存器獲取到當前GIC支援的最大中斷數量。如果中斷數量超過1020則賦值最大值為1020.
  • irq_domain_create_tree通過此函數來創建一個irq domain,irq doamin就是對中斷的區域的管理,用於級聯
  • set_handle_irq(gic_handle_irq);重點中的重點,用於設置中斷處理的回調函數,當中斷處理時,首先會調用此函數的
  • gic_smp_init 軟中斷的初始化,設置軟中斷的回調
  • gic_dist_init distributor的初始化
  • gic_cpu_init cpu interface的初始化
  • gic_cpu_pm_init power相關的初始化

設置中斷回調函數

int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))  {      if (handle_arch_irq)          return -EBUSY;        handle_arch_irq = handle_irq;      return 0;  }

handle_arch_irq會在彙編中調用的,在中斷處理流程中詳細說明

static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)  {      u32 irqnr;        do {          irqnr = gic_read_iar();            if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {              int err;                uncached_logk(LOGK_IRQ, (void *)(uintptr_t)irqnr);              if (static_branch_likely(&supports_deactivate_key))                  gic_write_eoir(irqnr);              else                  isb();                err = handle_domain_irq(gic_data.domain, irqnr, regs);              if (err) {                  WARN_ONCE(true, "Unexpected interrupt received!n");                  if (static_branch_likely(&supports_deactivate_key)) {                      if (irqnr < 8192)                          gic_write_dir(irqnr);                  } else {                      gic_write_eoir(irqnr);                  }              }              continue;          }          if (irqnr < 16) {              uncached_logk(LOGK_IRQ, (void *)(uintptr_t)irqnr);              gic_write_eoir(irqnr);              if (static_branch_likely(&supports_deactivate_key))                  gic_write_dir(irqnr);  #ifdef CONFIG_SMP              /*               * Unlike GICv2, we don't need an smp_rmb() here.               * The control dependency from gic_read_iar to               * the ISB in gic_write_eoir is enough to ensure               * that any shared data read by handle_IPI will               * be read after the ACK.               */              handle_IPI(irqnr, regs);  #else              WARN_ONCE(true, "Unexpected SGI received!n");  #endif              continue;          }      } while (irqnr != ICC_IAR1_EL1_SPURIOUS);  }
  • 根據上一篇GIC-500的文章最後一小節中描述,先會去讀IAR暫存器確定中斷號的,軟體上是通過gic_read_iar實現的
  • 得到中斷號會去判斷當前是哪種中斷類型,當中斷號大於15小於1020的話,則此中斷屬於PPI或者SPI
  • 結合會根據irq domain去處理對應的中斷handle_domain_irq(gic_data.domain, irqnr, regs);
  • 如果中斷號小於16,則此中斷號是IPI中斷,是core之間用於通訊的中斷,則會調用handle_IPI(irqnr, regs);去處理對應的中斷

而linux中用一個irq chip結構體來描述一個中斷控制器,irq_chip稱為中斷控制器描述符

static struct irq_chip gic_chip = {      .name           = "GICv3",      .irq_mask       = gic_mask_irq,      .irq_unmask     = gic_unmask_irq,      .irq_eoi        = gic_eoi_irq,      .irq_set_type       = gic_set_type,      .irq_set_affinity   = gic_set_affinity,      .irq_get_irqchip_state  = gic_irq_get_irqchip_state,      .irq_set_irqchip_state  = gic_irq_set_irqchip_state,      .flags          = IRQCHIP_SET_TYPE_MASKED |                    IRQCHIP_SKIP_SET_WAKE |                    IRQCHIP_MASK_ON_SUSPEND,  };
  • irq_chip結構相對於是中斷控制器在軟體上的抽象
  • name,中斷控制器的名字,可以在/cat /proc/interrupter中查看
  • irq_mask: 用於屏蔽中斷源
  • irq_unmask: 用於取消屏蔽中斷源
  • irq_eoi: end of interrupter, 用於表明此中斷處理完畢
  • irq_set_type:設置中斷的觸發類型
  • irq_set_affinity: 設置中斷的親合性
  • 等等

當然了irq_chip提供了很多回調函數,大家可以去看irq_chip的定義

struct irq_chip {      struct device   *parent_device;      const char  *name;      unsigned int    (*irq_startup)(struct irq_data *data);      void        (*irq_shutdown)(struct irq_data *data);      void        (*irq_enable)(struct irq_data *data);      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);      void        (*irq_unmask)(struct irq_data *data);      void        (*irq_eoi)(struct irq_data *data);        int     (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);      int     (*irq_retrigger)(struct irq_data *data);      int     (*irq_set_type)(struct irq_data *data, unsigned int flow_type);      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;  };