Linux内核设备驱动之GIC驱动

时间:2022-06-25 09:49:10
对于可提供中断这种功能的IC来说,具体中断IC应该具有哪些功能参见IC描述,下面我们着重
讲解GIC,并且是GIC-V3版本的IC。
对于中断控制器来说,内核对其抽象,用数据结构struct irq_chip来对其描述。对于GIC来说,实现下面对象:
 

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,
};

static struct irq_chip gic_eoimode1_chip = {
 .name   = "GICv3",
 .irq_mask  = gic_eoimode1_mask_irq,
 .irq_unmask  = gic_unmask_irq,
 .irq_eoi  = gic_eoimode1_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,
 .irq_set_vcpu_affinity = gic_irq_set_vcpu_affinity,
 .flags   = IRQCHIP_SET_TYPE_MASKED,
};

当然,这里设置的函数都是对GIC控制器进行的操作。

对于中断控制来说,Linux内核定义了一个宏,用于在系统启动时候可以对GIC提供的初始化函数调用:

IRQCHIP_DECLARE

所以,这里我们定义GIC初始化如下:

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

这样的话,在内核启动时,会对这些定义的IRQCHIP_DECLARE宏下的回调函数调用,以对中断控制器初始化。

这里我们就是调用函数gic_of_init。

 

函数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);
 if (!dist_base) {
  pr_err("%s: unable to map gic dist registers\n",
   node->full_name);
  return -ENXIO;
 }

 err = gic_validate_dist_version(dist_base);
 if (err) {
  pr_err("%s: no distributor detected, giving up\n",
   node->full_name);
  goto out_unmap_dist;
 }

 if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
  nr_redist_regions = 1;

 rdist_regs = kzalloc(sizeof(*rdist_regs) * nr_redist_regions, GFP_KERNEL);
 if (!rdist_regs) {
  err = -ENOMEM;
  goto out_unmap_dist;
 }

 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("%s: couldn't map region %d\n",
          node->full_name, 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);

 if (err)
  goto out_unmap_rdist;

 gic_populate_ppi_partitions(node);
 gic_of_setup_kvm_info(node);

 return 0;

out_unmap_rdist:
 for (i = 0; i < nr_redist_regions; i++)
  if (rdist_regs[i].redist_base)
   iounmap(rdist_regs[i].redist_base);
 kfree(rdist_regs);
out_unmap_dist:
 iounmap(dist_base);
 return err;
}

这里,我们需要了解GIC控制器提供的基础寄存器,然后对其配置。


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_key_slow_dec(&supports_deactivate);

 if (static_key_true(&supports_deactivate))
  pr_info("GIC: Using split EOI/Deactivate mode\n");

 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;

 gicv3_enable_quirks();

 /*
  * 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.id_bits = GICD_TYPER_ID_BITS(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);

 gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));

 if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {
  err = -ENOMEM;
  goto out_free;
 }

 set_handle_irq(gic_handle_irq);

 if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis())
  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;
}

这里最为重要的是关注函数set_handle_irq(),其设置了GIC控制器中断发生时,中断调用的处理函数,这里调用函数

gic_handle_irq()

此中断处理程序会根据GIC中断控制器状态,获取中断号,然后调用不同的中断处理程序。


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;

   if (static_key_true(&supports_deactivate))
    gic_write_eoir(irqnr);

   err = handle_domain_irq(gic_data.domain, irqnr, regs);
   if (err) {
    WARN_ONCE(true, "Unexpected interrupt received!\n");
    if (static_key_true(&supports_deactivate)) {
     if (irqnr < 8192)
      gic_write_dir(irqnr);
    } else {
     gic_write_eoir(irqnr);
    }
   }
   continue;
  }


  if (irqnr < 16) {
   gic_write_eoir(irqnr);
   if (static_key_true(&supports_deactivate))
    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);
}