Inicialización del controlador GICv3

El controlador de Linux es compatible con los controladores GICv1, GICv2, GICv3 y GICv4. En esta sección, nos centramos en describir la inicialización del controlador GICv3, combinada con una descripción detallada de la plataforma 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>;
};

Hay varios campos clave: puede consultar el brazo de documentos del núcleo , 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: se utiliza para coincidir con el controlador correspondiente, no hay más detalles
  • interrupt-cells se usa para describir la información detallada de una fuente de interrupción, este valor es igual a 3 significa que hay tres campos en las interrupciones
    • El primer campo representa el tipo de interrupción ( GIC_PPI, GIC_SPI )
    • El segundo campo es el número de interrupción física, que varía según el tipo de interrupción. SPI (0-987) PPI (0-15)
    • El modo de activación de interrupción representado por el tercer campo
  • controlador de interrupción: Descripción Este campo es un controlador de interrupción
  • interrupt-parent: representa si este controlador de interrupciones está en cascada, si no hay este campo, siga el campo padre
  • reg describe los registros involucrados en el controlador de interrupción
    • 0x666688888 representa el registro base del Distribuidor, GICD
    • 0x6666e8888 representa el registro de dirección base del Redistribuidor, GICR

 

Conociendo DTS, continuaremos mirando el controlador para GICv3

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

Puede expandir esta macro, la expansión es la siguiente, habrá una sección __irqchip_of_table después de la expansión

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

 Esta sección se describirá en detalle en el script de enlace, al arrancar, irá a leer esto desde __irqchip_of_table y luego comparará

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

Finalmente, el controlador de interrupción correcto se corresponde de acuerdo con dts en la función of_irq_init. Después de hacer coincidir el controlador de interrupción correcto, la función de devolución de llamada de datos anterior se llama gic_of_init, que es para inicializar el controlador de interrupción

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);
}
  • Encuentre el campo donde existe el controlador de interrupción
  • Asigne el descriptor del controlador de interrupción y configure la función de devolución de llamada irq_init_cb del controlador de interrupción
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 %p\n",
         desc->dev,
         desc->dev, desc->interrupt_parent);
    ret = desc->irq_init_cb(desc->dev,             //回调设置的中断控制器的初始化处理函数
                desc->interrupt_parent);

De esta manera, la función gic_of_init se llama

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 up\n", 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 %d\n", 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 obtiene la dirección base del distribuidor del controlador de terminal
  • gic_validate_dist_version determina si la versión actual es v3 o v4 según la dirección base
  • Lea los atributos de redisttibutor y obtenga la dirección base del registro correspondiente
  • Eventualmente llamará a la función gic_init_bases para hacer la inicialización correspondiente
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 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;
 
    /*
     * 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 support\n",
        gic_data.has_rss ? "" : "no ");
 
    if (typer & GICD_TYPER_MBIS) {
        err = mbi_init(handle, gic_data.domain);
        if (err)
            pr_err("Failed to initialize MBIs\n");
    }
 
    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 determina si está actualmente en modo de virtualización Hyp
  • Inicialice la estructura gic_data de acuerdo con los parámetros.
  • El número máximo de interrupciones admitidas por el GIC actual se obtiene leyendo el registro GICD_TYPER. Si el número de interrupciones excede 1020, el valor máximo asignado es 1020.
  • irq_domain_create_tree utiliza esta función para crear un dominio irq, irq doamin es la gestión del área interrumpida, utilizada para la conexión en cascada
  • set_handle_irq (gic_handle_irq); el punto clave de la tecla se usa para establecer la función de devolución de llamada para el procesamiento de interrupción . Cuando se procesa la interrupción, esta función se llamará primero
  • gic_smp_init Inicialización de interrupción suave, establecer devolución de llamada de interrupción suave
  • inicialización del distribuidor gic_dist_init
  • gic_cpu_init inicialización de la interfaz de la CPU
  • gic_cpu_pm_init inicialización relacionada con la energía

Establecer la función de devolución de llamada de interrupción

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

Se llamará a handle_arch_irq en el ensamblado, detallado en el flujo de procesamiento de interrupción

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);
}
  • De acuerdo con la descripción en la última sección del último artículo del GIC-500, primero leeré el registro IAR para determinar el número de interrupción. El software se implementa a través de gic_read_iar
  • Obtenga el número de interrupción para determinar qué tipo de interrupción es actualmente, cuando el número de interrupción es mayor que 15 y menor que 1020, la interrupción pertenece a PPI o SPI
  • La combinación procesará la interrupción correspondiente handle_domain_irq (gic_data.domain, irqnr, regs) de acuerdo con el dominio irq;
  • Si el número de interrupción es menor que 16, el número de interrupción es una interrupción IPI, que es una interrupción utilizada para la comunicación entre núcleos, y se llamará a handle_IPI (irqnr, regs); para manejar la interrupción correspondiente

 

En Linux, una estructura de chip irq se usa para describir un controlador de interrupción, y irq_chip se denomina descriptor de controlador de interrupción.

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,
};
  • La estructura irq_chip es relativa a la abstracción de software del controlador de interrupción
  • nombre, el nombre del controlador de interrupción, se puede ver en / cat / proc / interrupter
  • irq_mask: se usa para enmascarar la fuente de interrupción
  • irq_unmask: se usa para desenmascarar la fuente de interrupción
  • irq_eoi: fin del interruptor, usado para indicar que esta interrupción ha sido procesada
  • irq_set_type: establece el tipo de activación de interrupción
  • irq_set_affinity: establece la afinidad interrumpida
  • Espera

 

Por supuesto, irq_chip proporciona muchas funciones de devolución de llamada, puede ver la definición de 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;
};

 

 

 

 

 

 

187 artículos originales publicados · ganó 108 · 370,000 visitas

Supongo que te gusta

Origin blog.csdn.net/longwang155069/article/details/105275286
Recomendado
Clasificación