ARM GIC V3 initialization of Linux interrupt management

ARM GIC V3 initialization of Linux interrupt management

1. Introduction to ARM GIC V3 Interrupt Controller

GIC (Generic Interrupt Controller) is a general interrupt controller, used to receive hardware interrupt signals, and after certain processing, distribute them to the corresponding CPU for processing. GIC V3 is one of the versions, and the supported interrupt types are as follows:

Type of interrupt

Interrupt numberdescription
SGI (Software Generated Interrupt)0-15

Software triggered interrupts are usually used for communication between multiple cores, and are usually used as IPI (inter

-process interrupts) interrupt, and send it to the designated CPU of the system, support up to 16 SGI interrupts, interrupt number 0-15

PPI (Private Peripheral Interrupt)16-31The private peripheral interrupt of each processor supports up to 16 PPI interrupts, the interrupt number is 16-31, PPI is usually sent to the designated CPU
SPI (Shared Peripheral Interrupt)32-1019System shared peripheral interrupt

LPI (Locality-specific Peripheral

Interrupt)

8192-MAXLPI is a message-based interrupt, and their configuration is stored in a table instead of a register

The components of the GIC V3 interrupt controller include: distributor, redistributor, cpu interface, ITS, the relationship diagram between the GIC V3 interrupt controller and the processor core is as follows:

View Image

SPI interrupt detection process :

  • Peripheral initiates SPI interrupt, GIC detects this interrupt and marks it as pending
  • The distributor determines the target CPU for all interrupts in the pending state
  • For each CPU, the distributor will select the high priority interrupt from many pending interrupts and send it to the corresponding redistributor
  • The redistributor sends the interrupt to the CPU Interface of the target CPU
  • CPU Interface sends interrupts that meet the requirements to the CPU
  • After the CPU enters the interrupt exception, the kernel interrupt handler reads the GICC_IAR register to respond to the interrupt, and the register returns the hardware interrupt number
  • After the CPU has processed the interrupt service, it sends an EOI completion signal to the GIC by writing to the GICC_EOIR register

PPI and SGI interrupt detection process :

  • GIC detects PPI or SGI interruption and marks it as pending
  • The redistributor selects the interrupt with high priority and sends it to the corresponding CPU Interface
  • CPU Interface sends interrupts that meet the requirements to the CPU
  • After the CPU enters the interrupt exception, the kernel interrupt handler reads the GICC_IAR register to respond to the interrupt, and the register returns the hardware interrupt number
  • After the CPU has processed the interrupt service, it sends an EOI completion signal to the GIC by writing to the GICC_EOIR register

LPI interrupt detection process :

1. The forwarding method is realized by the following registers

  • GICR_SERLPIR: Set the specified LPI interrupt to the pending state
  • GICR_INVLPIR: Interrupt the specified LPI and clear the pending state. The register content is consistent with GICR_SERLPIR
  • GICR_INVLPIR: Invalidate the specified LPI cache in the cache, so that GIC reloads the LPI configuration from memory
  • GICR_INVALLR: Invalidate all LPI caches in the cache, so that GIC can reload the LPI interrupt configuration from the memory
  • GICR_SYNCR: Whether the operation of redistributor is complete

2. Use ITS method

  • The peripheral writes the GITS_TRANSLATER register to provide the ITS with the interrupt event type EventID and the DeviceID of the peripheral that initiated the interrupt
  • ITS uses DeviceID to get the location of the interrupt mapping table by checking the device table
  • ITS uses EventID to obtain the LPI interrupt number and the collection number to which the interrupt belongs by checking the interrupt mapping table
  • Use the collection number, from the redistributor mapping information in the collection table
  • ITS sends the interrupt information to the corresponding redistributor
  • The redistributor sends the interrupt information to the CPU Interface
  • CPU Interface sends interrupts that meet the requirements to the CPU

2 GIC V3 initialization in ARM Linux system

All the following kernel source code comes from Linux 5.5.8

2.1 gic-v3 DTS definition and device node in Linux system

     Device Tree is used to describe the data structure of hardware. DTS is the source code of Device Tree Source. The file name suffix corresponding to DTS in Linux source code is .dts. DTC is a tool to compile .dts into .dtb, .dtb is the device tree description in binary format after .dts is compiled by DTC, which can be parsed by the Linux kernel. Bootloader will inform the kernel of the .dtb address when booting the Linux kernel. After that, the kernel will expand the Device Tree and create and register related devices.

 2.1.1 Definition of gic-v3 DTS in Linux system

gic: interrupt-controller@2c010000 { compatible = "arm,gic-v3"; #interrupt-cells = <3>; #address-cells = <2>; #size-cells = <2>; ranges; interrupt-controller; redistributor-stride = <0x0 0x40000>; //256kB stride #redistributor-regions = <2>; reg = <0x0 0x2c010000 0 0x10000>, //GICD <0x0 0x2d000000 0 0x800000>, //GICR 1: CPUs 0-31 <0x0 0x2e000000 0 0x800000>; //GICR 2: CPUs 32-63 <0x0 0x2c040000 0 0x2000>, //GICC <0x0 0x2c060000 0 0x2000>, //GICH <0x0 0x2c080000 0 0x2000>; //GICV interrupts = <1 9 4>; gic-its@2c200000 { compatible = "arm,gic-v3-its"; msi-controller; #msi-cells = <1>; reg = <0x0 0x2c200000 0 0x200000>; }; gic-its@2c400000 { compatible = "arm,gic-v3-its"; msi-controller; #msi-cells = <1>; reg = <0x0 0x2c400000 0 0x200000>; }; };

1) Required attributes in gic-v3 device node:

  • interrupt-controller : The device node is an interrupt controller
  • compatible : must contain the string "arm,gic-v3", indicating that the device is an interrupt controller arm gic-v3
  • #interrupt-cells : Specify the number of cells required to encode the interrupt source, at least 3 cells are required. The detailed description of the cells is as follows
   Cell                                     description
        1The first cell is the interrupt type: 0 SPI, 1 PPI, the others are reserved values
        2The value in the second cell is the interrupt number
        3The value in the third cell is some interrupt flags, 1 is edge trigger, 4 is level trigger
  • reg : Specifies the physical base address of the GIC register. These registers include: Distributor (GICD), Redistributors (GICR), CPU interface (GICC), Hypervisor interface (GICH), Virtual CPU interface (GICV), among which GICC, GICH, GICV Is optional.
  • interrupts : interrupt source

2) Optional attributes in gic-v3 device node:

  • redistributor-stride : Specify the step size of redistributor, which must be a multiple of 64KB
  • #redistributor-regions : If there are multiple redistributors in the system, this attribute is needed to describe multiple redistributor regions

3) ITS (Interrupt Translation Services) sub-device node in the gic-v3 device node: There are one or more ITS devices in a GIC. The ITS device is used to route the message signal interrupt (MSI) to the cpu, and the description of the ITS device node as follows:

  • compatible: must contain the string "arm,gic-v3-its", indicating that this is an ITS device
  • msi-controller: Identifies the device node as an MSI controller
  • #msi-cells: Must be 1, which is the DeviceID of the MSI device
  • reg: the physical base address of the ITS register

2.1.2 Gic-v3 device node in Linux system

      When the kernel creates a device node, the main data structure is struct device_node, which is obtained by parsing .dtb. The following is the source code and comments of struct device_node:

struct property { /*The name of the property*/ char *name; /*The length of the property*/ int length; /*The value of the property*/ void *value; /*The next property connected to this property*/ struct property *next; #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC) unsigned long _flags; #endif #if defined(CONFIG_OF_PROMTREE) unsigned int unique_id; #endif #if defined(CONFIG_OF_KOBJ) struct bin_attribute attr; #endif }; #if defined(CONFIG_SPARC) struct of_irq_controller; #endif struct device_node { /*Device node name*/ const char *name; phandle phandle; /*Device node name and address*/ const char *full_name; /*Some callback functions for operating the device node*/ struct fwnode_handle fwnode; /*Each set of data in the node (such as compatible = "arm,cortex-a9-gic") is represented by the structure property, * property->next points to another set of data. */ struct property *properties; /*The property removed from the node*/ struct property *deadprops;/* removed properties */ /*The parent device node of the device node*/ struct device_node *parent; /*The child device node of the device node*/ struct device_node *child; /*The sibling device node of the device node*/ struct device_node *sibling; #if defined(CONFIG_OF_KOBJ) struct kobject kobj; #endif unsigned long _flags; void *data; #if defined(CONFIG_SPARC) unsigned int unique_id; struct of_irq_controller *irq_trans; #endif };

The struct device_node obtained by the system after analyzing the gic-v3 DTS in 2.1.1 is as follows:

  • device_node->name = "interrupt-controller"
  • device_node->full_name = "interrupt-controller@2c010000"
  • device_node->properties->name = "compatible"
  • device_node->properties->value = "arm,gic-v3"
  • device_node->properties->length = 10 (the length of the string "arm,gic-v3")
  • device_node->properties->next->name = "redistributor-stride"
  • device_node->properties->next->value = {0x0, 0x0, 0x0, 0x0, 0x0, 0x04, 0x0, 0x0}
  • device_node->properties->next->length = 4 (a field occupies 4 bytes)
  • device_node->properties->next->next->name = "reg"

                                 ......

  • device_node->child->name = "gic-its"
  • device_node->child->full_name = "gic-itss@2c200000"

                                 ......

  • device_node->child->next->name = "gic-its"
  • device_node->child->next->full_name = "gic-itss@2c400000"

                                 ......

2.2 Initialization of gic-v3 in Linux system

2.2.1 struct of_device_id structure

struct of_device_id { char name[32]; char type[32]; char compatible[128]; const void *data; }; /* * This special of_device_id is the sentinel at the end of the * of_device_id[] array of all irqchips. It is automatically placed at * the end of the array by the linker, thanks to being part of a * special section. */ static const struct of_device_id irqchip_of_match_end __used __section(__irqchip_of_table_end); extern struct of_device_id __irqchip_of_table[];

      1) struct of_device_id structure

  • name: device name
  • type: Device type
  • compatible: Compatible corresponding to the device node in the DTS file, used to match a suitable device node
  • data: For GIC, the data field is the address of the function that initializes GIC

      2) __irqchip_of_table: a system global array. Each object in the array is a global struct of_device_id static constant. The IRQCHIP_DECLARE macro is used to initialize a static constant of struct of_device_id and place it in __irqchip_of_table.

2.2.2 Macro IRQCHIP_DECLARE

/* * This macro must be used by the different irqchip drivers to declare * the association between their DT compatible string and their * initialization function. * * @name: name that must be unique across all IRQCHIP_DECLARE of the * same file. * @compstr: compatible string of the irqchip driver * @fn: initialization function */ #define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn) #define OF_DECLARE_2(table, name, compat, fn)/ _OF_DECLARE(table, name, compat, fn, of_init_fn_2) #define _OF_DECLARE(table, name, compat, fn, fn_type) \ static const struct of_device_id __of_table_##name \ __used __section(__##table##_of_table) \ = { .compatible = compat, \ .data = (fn == (fn_type)NULL) ? fn : fn }

The above is the definition of the macro IRQCHIP_DECLARE in the Linux system. This macro initializes a static constant of struct of_device_id and places it in __irqchip_of_table.

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

As shown in the code above, IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init) declares a __of_table_gic_v3 struct of_device_id structure, which is placed in __irqchip_of_table. __of_table_gic_v3.compatible is "arm,gic-v3", __of_table_gic_v3.data points to the address of the function gic_of_init().

2.2.3 gic_of_init() function analysis

The function calling sequence of initializing GIC V3 after Linux system startup is init_IRQ()->irqchip_init()->of_irq_init()->gic_of_init(). The following is the relevant code and comments of rqchip_init() and of_irq_init():

extern struct of_device_id __irqchip_of_table[]; void __init irqchip_init(void) { of_irq_init(__irqchip_of_table); acpi_probe_device_table(irqchip); } /** * of_irq_init-scan and initialize matching interrupt controller in DT * @matches: A piece of data of all device nodes of the interrupt controller used to scan and match, namely __irqchip_of_table * * This function scans the device tree to find matching interrupt controller nodes and executes their initialization function. */ void __init of_irq_init(const struct of_device_id *matches) { const struct of_device_id *match; struct device_node *np, *parent = NULL; struct of_intc_desc *desc, *temp_desc; struct list_head intc_desc_list, intc_parent_list; INIT_LIST_HEAD(&intc_desc_list); INIT_LIST_HEAD(&intc_parent_list); /* Traverse the interrupt controller device information obtained by __irqchip_of_table, and control each interrupt *The controller device allocates a struct of_intc_desc structure */ for_each_matching_node_and_match(np, matches, &match) { if (!of_property_read_bool(np, "interrupt-controller") || !of_device_is_available(np)) continue; if (WARN(!match->data, "of_irq_init: no init function for %s\n", match->compatible)) 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 (!desc) { of_node_put(np); goto err; } /* The content in match->data is the address of the gic_of_init() function, * Here set the irq_init_cb callback function of of_intc_desc to gic_of_init() */ 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; /*Put of_intc_desc in the intc_desc_list list*/ list_add_tail(&desc->list, &intc_desc_list); } /* * The root irq controller is the one without an interrupt-parent. * That one goes first, followed by the controllers that reference it, * followed by the ones that reference the 2nd level controllers, etc. */ while (!list_empty(&intc_desc_list)) { /* * Process all controllers with the current'parent'. * First pass will be looking for NULL as the parent. * The assumption is that NULL parent means a root controller. */ 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); /*Execute the callback function gic_of_init() of the initialization interrupt controller device*/ ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent); if (ret) { of_node_clear_flag(desc->dev, OF_POPULATED); kfree(desc); continue; } /* * This one is now set up; add it to the parent list so * its children can get processed in a subsequent pass. */ list_add_tail(&desc->list, &intc_parent_list); } /* Get the next pending parent that might have children */ desc = list_first_entry_or_null(&intc_parent_list, typeof(*desc), list); if (!desc) { pr_err("of_irq_init: children remain, but no parents\n"); break; } list_del(&desc->list); parent = desc->dev; kfree(desc); } list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) { list_del(&desc->list); kfree(desc); } err: list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { list_del(&desc->list); of_node_put(desc->dev); kfree(desc); } }

The following is the code and comments of the gic_of_init() function. In gic_of_init(), gic_init_bases() is called to handle the core work of GIC V3 initialization:

struct redist_region { /*Point to the address of the Redistributor domain memory area*/ void __iomem *redist_base; /*Redistributor domain physical address*/ phys_addr_t phys_base; /*Is there only one Redistributor domain*/ bool single_redist; }; /*GIC V3 hardware device related data structure*/ struct gic_chip_data { /*Some callback methods for operating the device*/ struct fwnode_handle *fwnode; /*Point to the address of the Distributor memory area*/ void __iomem *dist_base; /*The information of all Redistributor domains in this gic*/ struct redist_region *redist_regions; /*All Redistributor information in this gic*/ struct rdists rdists; /*The irq_domain corresponding to the GIC, each GIC in the Linux system corresponds to an irq_domain, * Used to parse hardware interrupt number */ struct irq_domain *domain; /*The step length between Redistributor domains, a multiple of 64KB*/ u64 redist_stride; /*Number of Redistributor domains*/ u32 nr_redist_regions; u64 flags; bool has_rss; unsigned int ppi_nr; struct partition_desc **ppi_descs; }; static struct gic_chip_data gic_data __read_mostly; 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; /* is the node mapping memory area, the dist_base returned here is the address of the Distributor*/ dist_base = of_iomap(node, 0); if (!dist_base) { pr_err("%pOF: unable to map gic dist registers\n", node); return -ENXIO; } /*Check whether the GIC hardware version is V3 or V4*/ err = gic_validate_dist_version(dist_base); if (err) { pr_err("%pOF: no distributor detected, giving up\n", node); goto out_unmap_dist; } /*Read the number of redistributors in the device node, if there is no "#redistributor-regions" field in the device DT, * Then there is only one redistributor */ if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions)) nr_redist_regions = 1; /*Allocate the redistributor array according to the number of redistributors*/ rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs), 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); /*Each redistributor area that has been allocated maps the memory area*/ 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; } /*Read the step length between each redistributor area in the device node, if the device DT * There is no "#redistributor-stride" field, redist_stride size is 0 */ if (of_property_read_u64(node, "redistributor-stride", &redist_stride)) redist_stride = 0; gic_enable_of_quirks(node, gic_quirks, &gic_data); /*Start to initialize GIC V3 device officially*/ 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); if (static_branch_likely(&supports_deactivate_key)) 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; } /*@dist_base:Distributor address, *@rdist_regs: Address information of all Redistributors in the GIC device *@nr_redist_regions: the number of Redistributors *@redist_stride: the step length between Redistributor areas, *@handle: is the fwnode in the data structure struct device_node corresponding to the device node, * Pointer to the firmware node associated with irq_domain */ 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 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 is a static global variable, here are some initializations*/ 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. */ typer = readl_relaxed(gic_data.dist_base + GICD_TYPER); gic_data.rdists.gicd_typer = typer; gic_enable_quirks(readl_relaxed(gic_data.dist_base + GICD_IIDR), gic_quirks, &gic_data); pr_info("%d SPIs implemented\n", GIC_LINE_NR-32); pr_info("%d Extended SPIs implemented\n", GIC_ESPI_NR); /*Register an irq domain data structure for GIC V3 in the system. The main function of irq_domain is to map the hardware interrupt number*/ gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops, &gic_data); /*There may be multiple irq_domains in a device node, these irq_domains are used for different purposes, DOMAIN_BUS_WIRED * Indicates that the irq_domain is used for wired IRQS */ 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; } /*rss represents the range of SGI interrupt affinity, GICD_TYPER_RSS value is bit[26] of GICD_TYPER register, * 0 means Interrupt Routing (IRI) supports SGI of affinity 0-15 * 1 means that SGI of affinity 0-255 is supported */ 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 the interrupt callback function gic_handle_irq, when an interrupt occurs, * The system will execute gic_handle_irq to handle the interrupt */ set_handle_irq(gic_handle_irq); /*Update Redistributor related attributes*/ gic_update_rdist_properties(); /*1. Set the communication between cores, 2. Set the callback function gic_cpu_init to dynamically register gic when the CPU is started*/ gic_smp_init(); /*Initialize Distributor*/ gic_dist_init(); /*Initialize CPU Interface*/ gic_cpu_init(); /*Initialize GIC power management*/ gic_cpu_pm_init(); if (gic_dist_supports_lpis()) { /*Initialize ITS*/ its_init(handle, &gic_data.rdists, gic_data.domain); its_cpu_init(); } else { if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) gicv2m_init(handle, gic_data.domain); } gic_enable_nmi_support(); return 0; out_free: if (gic_data.domain) irq_domain_remove(gic_data.domain); free_percpu(gic_data.rdists.rdist); return err; }err; }

 

 


Reference : https://blog.csdn.net/wyy4045/article/details/104827111