* [PATCH v2 00/22] Arm GICv5: Host driver implementation
@ 2025-04-24 10:25 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 01/22] dt-bindings: interrupt-controller: Add Arm GICv5 Lorenzo Pieralisi
` (21 more replies)
0 siblings, 22 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Implement the irqchip kernel driver for the Arm GICv5 architecture,
as described in the GICv5 beta0 specification, available at:
https://developer.arm.com/documentation/aes0070
The GICv5 architecture is composed of multiple components:
- one or more IRS (Interrupt Routing Service)
- zero or more ITS (Interrupt Translation Service)
- zero or more IWB (Interrupt Wire Bridge)
The GICv5 host kernel driver is split into units corresponding
to GICv5 components.
The GICv5 architecture defines the following interrupt types:
- PPI (PE-Private Peripheral Interrupt)
- SPI (Shared Peripheral Interrupt)
- LPI (Logical Peripheral Interrupt)
This series adds sysreg entries required to automatically generate
GICv5 registers handling code, one patch per-register.
The GICv5 driver is split into patches matching *logical* entities,
that make the respective drivers functional.
Logical entities:
- PPI, IRS/SPI, LPI/IPI
- ITS
- IWB
The salient points of the driver are summarized below.
=============
1. Testing
=============
Patchset tested with an architecturally compliant FVP model with
the following setup:
- 1 IRS
- 1 and 2 ITSes
- 1 and 2 IWBs
configured with different parameters that vary the IRS(IST) and
ITS(DT/ITT) table levels and INTID/DEVICEID/EVENTID bits.
A Trusted-Firmware (TF-A) prototype was used for device tree
bindings and component initializations.
================
2. Driver design
================
=====================
2.1 GICv5 DT bindings
=====================
The DT bindings attempt to map directly to the GICv5 component
hierarchy, with a top level node corresponding to the GICv5 "system",
having IRS child nodes, that have in turn ITS child nodes.
The IWB is defined in a separate schema; its relationship with the ITS
is explicit through the msi-parent property required to define the IWB
deviceID.
===================
2.2 GICv5 top level
===================
The top-level GICv5 irqchip driver implements separate IRQ
domains - one for each interrupt type, PPI (PE-Private Peripheral
Interrupt), SPI (Shared Peripheral Interrupt) and LPI (Logical
Peripheral Interrupt).
The top-level exception handler routes the IRQ to the relevant IRQ
domain for handling according to the interrupt type detected when the
IRQ is acknowledged.
All IRQs are set to the same priority value.
The driver assumes that the GICv5 components implement enough
physical address bits to address the full system RAM, as required
by the architecture; it does not check whether the physical address
ranges of memory allocated for IRS/ITS tables are within the GICv5
physical address range.
Components are probed by relying on the early DT irqchip probing
scheme. The probing is carried out hierarchically, starting from
the top level.
The IWB driver is not implemented using the wired-to-MSI interface
(i.e. with bus_token == DOMAIN_BUS_WIRED_TO_MSI mechanism), for two
specific reasons:
- An IWB is tightly coupled with a GICv5 ITS and its eventIDs
are fixed and cannot be allocated by the GICv5 ITS as it does
for other devices. An IWB is plugged directly into the ITS
driver for this specific reason.
- The DOMAIN_BUS_WIRED_TO_MSI interface requires the IWBs to be
platform devices - fiddling with platform device creation and
DT nodes is doable but a bit hacky.
Having said that, an alternative IWB driver with DOMAIN_BUS_WIRED_TO_MSI
bus token was implemented and can be shared for subsequent patchsets
versions depending on feedback, ref: 2.5 GICv5 IWB section below.
=============
2.3 GICv5 IRS
=============
The GICv5 IRS driver probes and manages SPI interrupts by detecting their
presence and by providing the top-level driver the information required
to set up the SPI interrupt domain.
The GICv5 IRS driver also parses from firmware Interrupt AFFinity ID
(IAFFID) IDs identifying cores and sets up IRS IRQ routing.
The GICv5 IRS driver allocates memory to handle the IRS tables.
The IRS LPI interrupts state is kept in an Interrupt State Table (IST)
and it is managed through CPU instructions.
The IRS driver allocates the IST table that, depending on available HW
features can be either 1- or 2-level.
If the IST is 2-level, memory for the level-2 table entries
is allocated on demand (ie when LPIs are requested), using an IRS
mechanism to make level-1 entry valid on demand after the IST
has already been enabled.
Chunks of memory allocated for IST entries can be smaller or larger than
PAGE_SIZE and are required to be physically contiguous within an IST level
(i.e. a linear IST is a single memory block, a 2-level IST is made up of a
block of memory for the L1 table, whose entries point at different L2 tables
that are in turn allocated as memory chunks).
LPI INTIDs are allocated in software using an IDA. IDA does not support
allocating ranges, which is a bit cumbersome because this forces us
to allocate IDs one by one where the LPIs could actually be allocated
in chunks.
An IDA was chosen because basically it is a dynamic bitmap, which
carries out memory allocation automatically.
Other drivers/subsystems made different choices to allocate ranges,
an IDA was chosen since it is part of the core kernel and an IDA
range API is in the making.
IPIs are implemented using LPIs and a hierarchical domain is created
specifically for IPIs using the LPI domain as a parent.
arm64 IPI management core code is augmented with a new API to handle
IPIs that are not per-cpu interrupts and force the affinity of the LPI
backing an IPI to a specific and immutable value.
=============
2.4 GICv5 ITS
=============
The ITS driver reuses the existing GICv3/v4 MSI-parent infrastructure
and on top builds an IRQ domain needed to enable message based IRQs.
ITS tables - DT (device table) and ITT (Interrupt Translation Table) are
allocated according to the number of required deviceIDs and eventIDs on
a per device basis. The ITS driver relies on the kmalloc() interface
because memory pages must be physically contiguous within a table level
and can be < or > than PAGE_SIZE.
=============
2.5 GICv5 IWB
=============
The IWB is a wire to message translator; its driver implements the
IRQ domain needed to carry out this translation and passes the
interrupt to its parent ITS domain.
An IWB is connected to an ITS and it has its own deviceID for all
interrupt wires that it manages; the IWB input wire number is
exposed to the ITS as an eventID. This eventID is not programmable
and therefore requires special handling in the ITS driver.
The current driver is tied to the ITS code and basically creates
a wired domain that allocates eventIDs into the ITS driver directly.
The ITS device allocation for the IWB is implemented directly in
the IWB driver at probe time (after all the IWB is a wired interrupt
controller with fixed events). An IWB IRQ domain is assigned to
the IWB wired IRQs, with the corresponding ITS domain as its
parent. Upon IRQ allocation (triggered by firmware parsing
the devices IRQ entries) the domain hierarchy maps the requested IWB
wire to an ITS {device/event}ID fixed pair.
An alternative IWB driver, based on the DOMAIN_BUS_WIRED_TO_MSI bus
token, was already developed and tested but the authors preferred
to post the current version to get feedback on it even though it
makes the IWB driver tied to the ITS driver requiring boilerplate
code that is not really needed (e.g. platform device deviceID parsing).
In particular, the fixed eventID nature of the IWB requires ITS
{device/event}ID allocation code to be IWB aware. To make this
work for the DOMAIN_BUS_WIRED_TO_MSI bus token approach,
the IWB should be made to work as a platform device and
most importantly, ITS code should add special handling for the
IWB (probably using an OF compatible string match or a new MSI
alloc flag). In a way, that's what the current IWB driver does.
Feedback is welcome on the matter.
===================
3. Acknowledgements
===================
The patchset was co-developed with T.Hayes and S.Bischoff from
Arm - thank you so much for your help.
A big thank you to M.Zyngier for his fundamental help/advice.
If you have some time to help us review this series and get it into
shape, thank you very much.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
---
Changes in v2:
- Squashed patches [18-21] into a single logical entity
- Replaced maple tree with IDA for LPI IDs allocation
- Changed coding style to tip-maintainer guidelines
- Tried to consolidate poll wait mechanism into fewer functions
- Added comments related to _relaxed accessors, barriers and kmalloc
limitations
- Removed IPI affinity check hotplug callback
- Applied DT schema changes requested, moved IWB into a separate schema
- Fixed DT examples
- Fixed guard() usage
- Link to v1: https://lore.kernel.org/r/20250408-gicv5-host-v1-0-1f26db465f8d@kernel.org
---
Lorenzo Pieralisi (21):
dt-bindings: interrupt-controller: Add Arm GICv5
arm64/sysreg: Add GCIE field to ID_AA64PFR2_EL1
arm64/sysreg: Add ICC_PPI_PRIORITY<n>_EL1
arm64/sysreg: Add ICC_ICSR_EL1
arm64/sysreg: Add ICC_PPI_HMR<n>_EL1
arm64/sysreg: Add ICC_PPI_ENABLER<n>_EL1
arm64/sysreg: Add ICC_PPI_{C/S}ACTIVER<n>_EL1
arm64/sysreg: Add ICC_PPI_{C/S}PENDR<n>_EL1
arm64/sysreg: Add ICC_CR0_EL1
arm64/sysreg: Add ICC_PCR_EL1
arm64/sysreg: Add ICC_IDR0_EL1
arm64/sysreg: Add ICH_HFGRTR_EL2
arm64/sysreg: Add ICH_HFGWTR_EL2
arm64/sysreg: Add ICH_HFGITR_EL2
arm64: Disable GICv5 read/write/instruction traps
arm64: cpucaps: Rename GICv3 CPU interface capability
arm64: cpucaps: Add GICv5 CPU interface (GCIE) capability
irqchip/gic-v5: Add GICv5 CPU interface/IRS support
irqchip/gic-v5: Add GICv5 ITS support
irqchip/gic-v5: Add GICv5 IWB support
arm64: Kconfig: Enable GICv5
Marc Zyngier (1):
arm64: smp: Support non-SGIs for IPIs
.../interrupt-controller/arm,gic-v5-iwb.yaml | 76 ++
.../bindings/interrupt-controller/arm,gic-v5.yaml | 196 +++
MAINTAINERS | 10 +
arch/arm64/Kconfig | 1 +
arch/arm64/include/asm/arch_gicv5.h | 91 ++
arch/arm64/include/asm/el2_setup.h | 45 +
arch/arm64/include/asm/smp.h | 24 +-
arch/arm64/kernel/cpufeature.c | 17 +-
arch/arm64/kernel/smp.c | 156 ++-
arch/arm64/tools/cpucaps | 3 +-
arch/arm64/tools/sysreg | 495 +++++++-
drivers/irqchip/Kconfig | 16 +
drivers/irqchip/Makefile | 5 +-
drivers/irqchip/irq-gic-common.h | 2 -
...3-its-msi-parent.c => irq-gic-its-msi-parent.c} | 3 +-
drivers/irqchip/irq-gic-its-msi-parent.h | 13 +
drivers/irqchip/irq-gic-v3-its.c | 3 +-
drivers/irqchip/irq-gic-v5-irs.c | 851 +++++++++++++
drivers/irqchip/irq-gic-v5-its.c | 1325 ++++++++++++++++++++
drivers/irqchip/irq-gic-v5-iwb.c | 356 ++++++
drivers/irqchip/irq-gic-v5.c | 1062 ++++++++++++++++
drivers/irqchip/irq-gic-v5.h | 388 ++++++
drivers/irqchip/irq-gic.c | 2 +-
23 files changed, 5074 insertions(+), 66 deletions(-)
---
base-commit: 0af2f6be1b4281385b618cb86ad946eded089ac8
change-id: 20250408-gicv5-host-749f316afe84
Best regards,
--
Lorenzo Pieralisi <lpieralisi@kernel.org>
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v2 01/22] dt-bindings: interrupt-controller: Add Arm GICv5
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 02/22] arm64/sysreg: Add GCIE field to ID_AA64PFR2_EL1 Lorenzo Pieralisi
` (20 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
The GICv5 interrupt controller architecture is composed of:
- one or more Interrupt Routing Service (IRS)
- zero or more Interrupt Translation Service (ITS)
- zero or more Interrupt Wire Bridge (IWB)
Describe a GICv5 implementation by specifying a top level node
corresponding to the GICv5 system component.
IRS nodes are added as GICv5 system component children.
An ITS is associated with an IRS so ITS nodes are described
as IRS children - use the hierarchy explicitly in the device
tree to define the association.
IWB nodes are described as a separate schema.
An IWB is connected to a single ITS, the connection is made explicit
through the msi-parent property and therefore is not required to be
explicit through a parent-child relationship in the device tree.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Rob Herring <robh@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Marc Zyngier <maz@kernel.org>
---
.../interrupt-controller/arm,gic-v5-iwb.yaml | 76 ++++++++
.../bindings/interrupt-controller/arm,gic-v5.yaml | 196 +++++++++++++++++++++
MAINTAINERS | 7 +
3 files changed, 279 insertions(+)
diff --git a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5-iwb.yaml b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5-iwb.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b3eb89567b3457e91b93588d7db1cef69b6b9813
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5-iwb.yaml
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/arm,gic-v5-iwb.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ARM Generic Interrupt Controller, version 5 Interrupt Wire Bridge (IWB)
+
+maintainers:
+ - Lorenzo Pieralisi <lpieralisi@kernel.org>
+ - Marc Zyngier <maz@kernel.org>
+
+description: |
+ The GICv5 architecture defines the guidelines to implement GICv5
+ compliant interrupt controllers for AArch64 systems.
+
+ The GICv5 specification can be found at
+ https://developer.arm.com/documentation/aes0070
+
+ GICv5 has zero or more Interrupt Wire Bridges (IWB) that are responsible
+ for translating wire signals into interrupt messages to the GICv5 ITS.
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+properties:
+ compatible:
+ const: arm,gic-v5-iwb
+
+ interrupt-controller: true
+
+ "#address-cells":
+ const: 0
+
+ "#interrupt-cells":
+ description: |
+ The 1st cell corresponds to the IWB wire.
+
+ The 2nd cell is the flags, encoded as follows:
+ bits[3:0] trigger type and level flags.
+
+ 1 = low-to-high edge triggered
+ 2 = high-to-low edge triggered
+ 4 = active high level-sensitive
+ 8 = active low level-sensitive
+
+ const: 2
+
+ reg:
+ items:
+ - description: IWB control frame
+
+ msi-parent:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - msi-parent
+
+additionalProperties: false
+
+examples:
+ - |
+ interrupt-controller@2f000000 {
+ compatible = "arm,gic-v5-iwb";
+ #address-cells = <0>;
+
+ interrupt-controller;
+ #interrupt-cells = <2>;
+
+ reg = <0x2f000000 0x10000>;
+
+ msi-parent = <&its0 64>;
+ };
+...
diff --git a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1ba0a2088e6d15bacae22c9fc9eebc4ce5c51b0b
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
@@ -0,0 +1,196 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/arm,gic-v5.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ARM Generic Interrupt Controller, version 5
+
+maintainers:
+ - Lorenzo Pieralisi <lpieralisi@kernel.org>
+ - Marc Zyngier <maz@kernel.org>
+
+description: |
+ The GICv5 architecture defines the guidelines to implement GICv5
+ compliant interrupt controllers for AArch64 systems.
+
+ The GICv5 specification can be found at
+ https://developer.arm.com/documentation/aes0070
+
+ The GICv5 architecture is composed of multiple components:
+ - one or more IRS (Interrupt Routing Service)
+ - zero or more ITS (Interrupt Translation Service)
+
+ The architecture defines:
+ - PE-Private Peripheral Interrupts (PPI)
+ - Shared Peripheral Interrupts (SPI)
+ - Logical Peripheral Interrupts (LPI)
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+properties:
+ compatible:
+ const: arm,gic-v5
+
+ interrupt-controller: true
+
+ "#address-cells":
+ enum: [ 1, 2 ]
+
+ "#size-cells":
+ enum: [ 1, 2 ]
+
+ ranges: true
+
+ "#interrupt-cells":
+ description: |
+ The 1st cell corresponds to the INTID.Type field in the INTID; 1 for PPI,
+ 3 for SPI. LPI interrupts must not be described in the bindings since
+ they are allocated dynamically by the software component managing them.
+
+ The 2nd cell contains the interrupt INTID.ID field.
+
+ The 3rd cell is the flags, encoded as follows:
+ bits[3:0] trigger type and level flags.
+
+ 1 = low-to-high edge triggered
+ 2 = high-to-low edge triggered
+ 4 = active high level-sensitive
+ 8 = active low level-sensitive
+
+ const: 3
+
+ interrupts:
+ description:
+ The VGIC maintenance interrupt.
+ maxItems: 1
+
+required:
+ - compatible
+
+patternProperties:
+ "^irs@[0-9a-f]+$":
+ type: object
+ description:
+ GICv5 has one or more Interrupt Routing Services (IRS) that are
+ responsible for handling IRQ state and routing.
+
+ additionalProperties: false
+
+ properties:
+ compatible:
+ const: arm,gic-v5-irs
+
+ "#address-cells":
+ enum: [ 1, 2 ]
+
+ "#size-cells":
+ enum: [ 1, 2 ]
+
+ ranges: true
+
+ dma-noncoherent:
+ description:
+ Present if the GIC IRS permits programming shareability and
+ cacheability attributes but is connected to a non-coherent
+ downstream interconnect.
+
+ reg:
+ minItems: 1
+ items:
+ - description: IRS control frame
+ - description: IRS setlpi frame
+
+ cpus:
+ description:
+ CPUs managed by the IRS.
+
+ arm,iaffids:
+ $ref: /schemas/types.yaml#/definitions/uint16-array
+ description:
+ Interrupt AFFinity ID (IAFFID) associated with the CPU whose
+ CPU node phandle is at the same index in the cpus array.
+
+ patternProperties:
+ "^msi-controller@[0-9a-f]+$":
+ type: object
+ description:
+ GICv5 has zero or more Interrupt Translation Services (ITS) that are
+ used to route Message Signalled Interrupts (MSI) to the CPUs. Each
+ ITS is connected to an IRS.
+ additionalProperties: false
+
+ properties:
+ compatible:
+ const: arm,gic-v5-its
+
+ dma-noncoherent:
+ description:
+ Present if the GIC ITS permits programming shareability and
+ cacheability attributes but is connected to a non-coherent
+ downstream interconnect.
+
+ msi-controller: true
+
+ "#msi-cells":
+ description:
+ The single msi-cell is the DeviceID of the device which will
+ generate the MSI.
+ const: 1
+
+ reg:
+ items:
+ - description: ITS control frame
+ - description: ITS translate frame
+
+ required:
+ - compatible
+ - msi-controller
+ - "#msi-cells"
+ - reg
+
+ required:
+ - compatible
+ - reg
+ - cpus
+ - arm,iaffids
+
+additionalProperties: false
+
+examples:
+ - |
+ interrupt-controller {
+ compatible = "arm,gic-v5";
+ #interrupt-cells = <3>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ interrupt-controller;
+
+ interrupts = <1 25 4>;
+
+ irs@2f1a0000 {
+ compatible = "arm,gic-v5-irs";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ reg = <0x2f1a0000 0x10000>; // IRS_CONFIG_FRAME for NS
+
+ arm,iaffids = /bits/ 16 <0 1 2 3 4 5 6 7>;
+ cpus = <&cpu0>, <&cpu1>, <&cpu2>, <&cpu3>, <&cpu4>, <&cpu5>, <&cpu6>, <&cpu7>;
+
+ msi-controller@2f120000 {
+ compatible = "arm,gic-v5-its";
+
+ msi-controller;
+ #msi-cells = <1>;
+
+ reg = <0x2f120000 0x10000 // ITS_CONFIG_FRAME for NS
+ 0x2f130000 0x10000>; // ITS_TRANSLATE_FRAME
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 96b82704950184bd71623ff41fc4df31e4c7fe87..f3ed84466da19906953b5396a5f4b50e878c376e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1901,6 +1901,13 @@ F: drivers/irqchip/irq-gic*.[ch]
F: include/linux/irqchip/arm-gic*.h
F: include/linux/irqchip/arm-vgic-info.h
+ARM GENERIC INTERRUPT CONTROLLER V5 DRIVERS
+M: Lorenzo Pieralisi <lpieralisi@kernel.org>
+M: Marc Zyngier <maz@kernel.org>
+L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+S: Maintained
+F: Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
+
ARM HDLCD DRM DRIVER
M: Liviu Dudau <liviu.dudau@arm.com>
S: Supported
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 02/22] arm64/sysreg: Add GCIE field to ID_AA64PFR2_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 01/22] dt-bindings: interrupt-controller: Add Arm GICv5 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 03/22] arm64/sysreg: Add ICC_PPI_PRIORITY<n>_EL1 Lorenzo Pieralisi
` (19 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add field reporting the GCIE feature to ID_AA64PFR2_EL1 sysreg.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index f9476848a2edfad53bb4af7f68bc05cb2a4af9ce..06e1fb5e126b41b7e41fffa0a00553d73197ac3c 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -1023,7 +1023,10 @@ UnsignedEnum 19:16 UINJ
0b0000 NI
0b0001 IMP
EndEnum
-Res0 15:12
+UnsignedEnum 15:12 GCIE
+ 0b0000 NI
+ 0b0001 IMP
+EndEnum
UnsignedEnum 11:8 MTEFAR
0b0000 NI
0b0001 IMP
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 03/22] arm64/sysreg: Add ICC_PPI_PRIORITY<n>_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 01/22] dt-bindings: interrupt-controller: Add Arm GICv5 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 02/22] arm64/sysreg: Add GCIE field to ID_AA64PFR2_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 04/22] arm64/sysreg: Add ICC_ICSR_EL1 Lorenzo Pieralisi
` (18 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_PPI_PRIORITY<n>_EL1 sysreg description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 83 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 06e1fb5e126b41b7e41fffa0a00553d73197ac3c..0cc1268c0bfad8266da47b441e80c603e46c00ae 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2310,6 +2310,89 @@ Field 31 C
Field 30:0 P
EndSysreg
+SysregFields ICC_PPI_PRIORITYRx_EL1
+Res0 63:61
+Field 60:56 Priority7
+Res0 55:53
+Field 52:48 Priority6
+Res0 47:45
+Field 44:40 Priority5
+Res0 39:37
+Field 36:32 Priority4
+Res0 31:29
+Field 28:24 Priority3
+Res0 23:21
+Field 20:16 Priority2
+Res0 15:13
+Field 12:8 Priority1
+Res0 7:5
+Field 4:0 Priority0
+EndSysregFields
+
+Sysreg ICC_PPI_PRIORITYR0_EL1 3 0 12 14 0
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR1_EL1 3 0 12 14 1
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR2_EL1 3 0 12 14 2
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR3_EL1 3 0 12 14 3
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR4_EL1 3 0 12 14 4
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR5_EL1 3 0 12 14 5
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR6_EL1 3 0 12 14 6
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR7_EL1 3 0 12 14 7
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR8_EL1 3 0 12 15 0
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR9_EL1 3 0 12 15 1
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR10_EL1 3 0 12 15 2
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR11_EL1 3 0 12 15 3
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR12_EL1 3 0 12 15 4
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR13_EL1 3 0 12 15 5
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR14_EL1 3 0 12 15 6
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_PRIORITYR15_EL1 3 0 12 15 7
+Fields ICC_PPI_PRIORITYRx_EL1
+EndSysreg
+
Sysreg PMSELR_EL0 3 3 9 12 5
Res0 63:5
Field 4:0 SEL
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 04/22] arm64/sysreg: Add ICC_ICSR_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (2 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 03/22] arm64/sysreg: Add ICC_PPI_PRIORITY<n>_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 05/22] arm64/sysreg: Add ICC_PPI_HMR<n>_EL1 Lorenzo Pieralisi
` (17 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_ICSR_EL1 register sysreg description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 0cc1268c0bfad8266da47b441e80c603e46c00ae..985f2cdb67cfec6df335a3951ecb63f128f6da55 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2310,6 +2310,20 @@ Field 31 C
Field 30:0 P
EndSysreg
+Sysreg ICC_ICSR_EL1 3 0 12 10 4
+Res0 63:48
+Field 47:32 IAFFID
+Res0 31:16
+Field 15:11 Priority
+Res0 10:6
+Field 5 HM
+Field 4 Active
+Field 3 IRM
+Field 2 Pending
+Field 1 Enabled
+Field 0 F
+EndSysreg
+
SysregFields ICC_PPI_PRIORITYRx_EL1
Res0 63:61
Field 60:56 Priority7
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 05/22] arm64/sysreg: Add ICC_PPI_HMR<n>_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (3 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 04/22] arm64/sysreg: Add ICC_ICSR_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 06/22] arm64/sysreg: Add ICC_PPI_ENABLER<n>_EL1 Lorenzo Pieralisi
` (16 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_PPI_HMR<n>_EL1 registers sysreg description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 75 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 985f2cdb67cfec6df335a3951ecb63f128f6da55..d046d719d4f69801aeef51b5b9437a0eaa04134e 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2324,6 +2324,81 @@ Field 1 Enabled
Field 0 F
EndSysreg
+SysregFields ICC_PPI_HMRx_EL1
+Field 63 HM63
+Field 62 HM62
+Field 61 HM61
+Field 60 HM60
+Field 59 HM59
+Field 58 HM58
+Field 57 HM57
+Field 56 HM56
+Field 55 HM55
+Field 54 HM54
+Field 53 HM53
+Field 52 HM52
+Field 51 HM51
+Field 50 HM50
+Field 49 HM49
+Field 48 HM48
+Field 47 HM47
+Field 46 HM46
+Field 45 HM45
+Field 44 HM44
+Field 43 HM43
+Field 42 HM42
+Field 41 HM41
+Field 40 HM40
+Field 39 HM39
+Field 38 HM38
+Field 37 HM37
+Field 36 HM36
+Field 35 HM35
+Field 34 HM34
+Field 33 HM33
+Field 32 HM32
+Field 31 HM31
+Field 30 HM30
+Field 29 HM29
+Field 28 HM28
+Field 27 HM27
+Field 26 HM26
+Field 25 HM25
+Field 24 HM24
+Field 23 HM23
+Field 22 HM22
+Field 21 HM21
+Field 20 HM20
+Field 19 HM19
+Field 18 HM18
+Field 17 HM17
+Field 16 HM16
+Field 15 HM15
+Field 14 HM14
+Field 13 HM13
+Field 12 HM12
+Field 11 HM11
+Field 10 HM10
+Field 9 HM9
+Field 8 HM8
+Field 7 HM7
+Field 6 HM6
+Field 5 HM5
+Field 4 HM4
+Field 3 HM3
+Field 2 HM2
+Field 1 HM1
+Field 0 HM0
+EndSysregFields
+
+Sysreg ICC_PPI_HMR0_EL1 3 0 12 10 0
+Fields ICC_PPI_HMRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_HMR1_EL1 3 0 12 10 1
+Fields ICC_PPI_HMRx_EL1
+EndSysreg
+
SysregFields ICC_PPI_PRIORITYRx_EL1
Res0 63:61
Field 60:56 Priority7
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 06/22] arm64/sysreg: Add ICC_PPI_ENABLER<n>_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (4 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 05/22] arm64/sysreg: Add ICC_PPI_HMR<n>_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 07/22] arm64/sysreg: Add ICC_PPI_{C/S}ACTIVER<n>_EL1 Lorenzo Pieralisi
` (15 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_PPI_ENABLER<n>_EL1 registers sysreg description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 75 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index d046d719d4f69801aeef51b5b9437a0eaa04134e..6c5552707ad88c145adc8b7ceb3f63da401191ea 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2399,6 +2399,81 @@ Sysreg ICC_PPI_HMR1_EL1 3 0 12 10 1
Fields ICC_PPI_HMRx_EL1
EndSysreg
+SysregFields ICC_PPI_ENABLERx_EL1
+Field 63 EN63
+Field 62 EN62
+Field 61 EN61
+Field 60 EN60
+Field 59 EN59
+Field 58 EN58
+Field 57 EN57
+Field 56 EN56
+Field 55 EN55
+Field 54 EN54
+Field 53 EN53
+Field 52 EN52
+Field 51 EN51
+Field 50 EN50
+Field 49 EN49
+Field 48 EN48
+Field 47 EN47
+Field 46 EN46
+Field 45 EN45
+Field 44 EN44
+Field 43 EN43
+Field 42 EN42
+Field 41 EN41
+Field 40 EN40
+Field 39 EN39
+Field 38 EN38
+Field 37 EN37
+Field 36 EN36
+Field 35 EN35
+Field 34 EN34
+Field 33 EN33
+Field 32 EN32
+Field 31 EN31
+Field 30 EN30
+Field 29 EN29
+Field 28 EN28
+Field 27 EN27
+Field 26 EN26
+Field 25 EN25
+Field 24 EN24
+Field 23 EN23
+Field 22 EN22
+Field 21 EN21
+Field 20 EN20
+Field 19 EN19
+Field 18 EN18
+Field 17 EN17
+Field 16 EN16
+Field 15 EN15
+Field 14 EN14
+Field 13 EN13
+Field 12 EN12
+Field 11 EN11
+Field 10 EN10
+Field 9 EN9
+Field 8 EN8
+Field 7 EN7
+Field 6 EN6
+Field 5 EN5
+Field 4 EN4
+Field 3 EN3
+Field 2 EN2
+Field 1 EN1
+Field 0 EN0
+EndSysregFields
+
+Sysreg ICC_PPI_ENABLER0_EL1 3 0 12 10 6
+Fields ICC_PPI_ENABLERx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_ENABLER1_EL1 3 0 12 10 7
+Fields ICC_PPI_ENABLERx_EL1
+EndSysreg
+
SysregFields ICC_PPI_PRIORITYRx_EL1
Res0 63:61
Field 60:56 Priority7
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 07/22] arm64/sysreg: Add ICC_PPI_{C/S}ACTIVER<n>_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (5 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 06/22] arm64/sysreg: Add ICC_PPI_ENABLER<n>_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 08/22] arm64/sysreg: Add ICC_PPI_{C/S}PENDR<n>_EL1 Lorenzo Pieralisi
` (14 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_PPI_{C/S}ACTIVER<n>_EL1 registers description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 83 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 6c5552707ad88c145adc8b7ceb3f63da401191ea..0485721e1575c9ed158210c6f02fb9af2828f2d5 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2474,6 +2474,89 @@ Sysreg ICC_PPI_ENABLER1_EL1 3 0 12 10 7
Fields ICC_PPI_ENABLERx_EL1
EndSysreg
+SysregFields ICC_PPI_ACTIVERx_EL1
+Field 63 Active63
+Field 62 Active62
+Field 61 Active61
+Field 60 Active60
+Field 59 Active59
+Field 58 Active58
+Field 57 Active57
+Field 56 Active56
+Field 55 Active55
+Field 54 Active54
+Field 53 Active53
+Field 52 Active52
+Field 51 Active51
+Field 50 Active50
+Field 49 Active49
+Field 48 Active48
+Field 47 Active47
+Field 46 Active46
+Field 45 Active45
+Field 44 Active44
+Field 43 Active43
+Field 42 Active42
+Field 41 Active41
+Field 40 Active40
+Field 39 Active39
+Field 38 Active38
+Field 37 Active37
+Field 36 Active36
+Field 35 Active35
+Field 34 Active34
+Field 33 Active33
+Field 32 Active32
+Field 31 Active31
+Field 30 Active30
+Field 29 Active29
+Field 28 Active28
+Field 27 Active27
+Field 26 Active26
+Field 25 Active25
+Field 24 Active24
+Field 23 Active23
+Field 22 Active22
+Field 21 Active21
+Field 20 Active20
+Field 19 Active19
+Field 18 Active18
+Field 17 Active17
+Field 16 Active16
+Field 15 Active15
+Field 14 Active14
+Field 13 Active13
+Field 12 Active12
+Field 11 Active11
+Field 10 Active10
+Field 9 Active9
+Field 8 Active8
+Field 7 Active7
+Field 6 Active6
+Field 5 Active5
+Field 4 Active4
+Field 3 Active3
+Field 2 Active2
+Field 1 Active1
+Field 0 Active0
+EndSysregFields
+
+Sysreg ICC_PPI_CACTIVER0_EL1 3 0 12 13 0
+Fields ICC_PPI_ACTIVERx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_CACTIVER1_EL1 3 0 12 13 1
+Fields ICC_PPI_ACTIVERx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_SACTIVER0_EL1 3 0 12 13 2
+Fields ICC_PPI_ACTIVERx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_SACTIVER1_EL1 3 0 12 13 3
+Fields ICC_PPI_ACTIVERx_EL1
+EndSysreg
+
SysregFields ICC_PPI_PRIORITYRx_EL1
Res0 63:61
Field 60:56 Priority7
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 08/22] arm64/sysreg: Add ICC_PPI_{C/S}PENDR<n>_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (6 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 07/22] arm64/sysreg: Add ICC_PPI_{C/S}ACTIVER<n>_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 09/22] arm64/sysreg: Add ICC_CR0_EL1 Lorenzo Pieralisi
` (13 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_PPI_{C/S}PENDR<n>_EL1 registers description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 83 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 0485721e1575c9ed158210c6f02fb9af2828f2d5..7acad93718c56729ce2a333ed007243ec554dbc9 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2557,6 +2557,89 @@ Sysreg ICC_PPI_SACTIVER1_EL1 3 0 12 13 3
Fields ICC_PPI_ACTIVERx_EL1
EndSysreg
+SysregFields ICC_PPI_PENDRx_EL1
+Field 63 Pend63
+Field 62 Pend62
+Field 61 Pend61
+Field 60 Pend60
+Field 59 Pend59
+Field 58 Pend58
+Field 57 Pend57
+Field 56 Pend56
+Field 55 Pend55
+Field 54 Pend54
+Field 53 Pend53
+Field 52 Pend52
+Field 51 Pend51
+Field 50 Pend50
+Field 49 Pend49
+Field 48 Pend48
+Field 47 Pend47
+Field 46 Pend46
+Field 45 Pend45
+Field 44 Pend44
+Field 43 Pend43
+Field 42 Pend42
+Field 41 Pend41
+Field 40 Pend40
+Field 39 Pend39
+Field 38 Pend38
+Field 37 Pend37
+Field 36 Pend36
+Field 35 Pend35
+Field 34 Pend34
+Field 33 Pend33
+Field 32 Pend32
+Field 31 Pend31
+Field 30 Pend30
+Field 29 Pend29
+Field 28 Pend28
+Field 27 Pend27
+Field 26 Pend26
+Field 25 Pend25
+Field 24 Pend24
+Field 23 Pend23
+Field 22 Pend22
+Field 21 Pend21
+Field 20 Pend20
+Field 19 Pend19
+Field 18 Pend18
+Field 17 Pend17
+Field 16 Pend16
+Field 15 Pend15
+Field 14 Pend14
+Field 13 Pend13
+Field 12 Pend12
+Field 11 Pend11
+Field 10 Pend10
+Field 9 Pend9
+Field 8 Pend8
+Field 7 Pend7
+Field 6 Pend6
+Field 5 Pend5
+Field 4 Pend4
+Field 3 Pend3
+Field 2 Pend2
+Field 1 Pend1
+Field 0 Pend0
+EndSysregFields
+
+Sysreg ICC_PPI_CPENDR0_EL1 3 0 12 13 4
+Fields ICC_PPI_PENDRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_CPENDR1_EL1 3 0 12 13 5
+Fields ICC_PPI_PENDRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_SPENDR0_EL1 3 0 12 13 6
+Fields ICC_PPI_PENDRx_EL1
+EndSysreg
+
+Sysreg ICC_PPI_SPENDR1_EL1 3 0 12 13 7
+Fields ICC_PPI_PENDRx_EL1
+EndSysreg
+
SysregFields ICC_PPI_PRIORITYRx_EL1
Res0 63:61
Field 60:56 Priority7
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 09/22] arm64/sysreg: Add ICC_CR0_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (7 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 08/22] arm64/sysreg: Add ICC_PPI_{C/S}PENDR<n>_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 10/22] arm64/sysreg: Add ICC_PCR_EL1 Lorenzo Pieralisi
` (12 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_CR0_EL1 register description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 7acad93718c56729ce2a333ed007243ec554dbc9..c96243505031ea680c04a693fee2c96ad19e30ea 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2798,6 +2798,14 @@ Res0 14:12
Field 11:0 AFFINITY
EndSysreg
+Sysreg ICC_CR0_EL1 3 1 12 0 1
+Res0 63:39
+Field 38 PID
+Field 37:32 IPPT
+Res0 31:1
+Field 0 EN
+EndSysreg
+
Sysreg CSSELR_EL1 3 2 0 0 0
Res0 63:5
Field 4 TnD
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 10/22] arm64/sysreg: Add ICC_PCR_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (8 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 09/22] arm64/sysreg: Add ICC_CR0_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 11/22] arm64/sysreg: Add ICC_IDR0_EL1 Lorenzo Pieralisi
` (11 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_PCR_EL1 register description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index c96243505031ea680c04a693fee2c96ad19e30ea..9a2ddab8661c85586b0e91f7eaabd5a6b3409c67 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2806,6 +2806,11 @@ Res0 31:1
Field 0 EN
EndSysreg
+Sysreg ICC_PCR_EL1 3 1 12 0 2
+Res0 63:5
+Field 4:0 PRIORITY
+EndSysreg
+
Sysreg CSSELR_EL1 3 2 0 0 0
Res0 63:5
Field 4 TnD
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 11/22] arm64/sysreg: Add ICC_IDR0_EL1
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (9 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 10/22] arm64/sysreg: Add ICC_PCR_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 12/22] arm64/sysreg: Add ICH_HFGRTR_EL2 Lorenzo Pieralisi
` (10 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICC_IDR0_EL1 register description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 9a2ddab8661c85586b0e91f7eaabd5a6b3409c67..1ec8113df713dfea6d38e39c42eba1e3dca5eea5 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2399,6 +2399,22 @@ Sysreg ICC_PPI_HMR1_EL1 3 0 12 10 1
Fields ICC_PPI_HMRx_EL1
EndSysreg
+Sysreg ICC_IDR0_EL1 3 0 12 10 2
+Res0 63:12
+UnsignedEnum 11:8 GCIE_LEGACY
+ 0b0000 NI
+ 0b0001 IMP
+EndEnum
+UnsignedEnum 7:4 PRI_BITS
+ 0b0011 4BITS
+ 0b0100 5BITS
+EndEnum
+UnsignedEnum 3:0 ID_BITS
+ 0b0000 16BITS
+ 0b0001 24BITS
+EndEnum
+EndSysreg
+
SysregFields ICC_PPI_ENABLERx_EL1
Field 63 EN63
Field 62 EN62
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 12/22] arm64/sysreg: Add ICH_HFGRTR_EL2
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (10 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 11/22] arm64/sysreg: Add ICC_IDR0_EL1 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 13/22] arm64/sysreg: Add ICH_HFGWTR_EL2 Lorenzo Pieralisi
` (9 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICH_HFGRTR_EL2 register description.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 1ec8113df713dfea6d38e39c42eba1e3dca5eea5..0c0e805481c84a14ae62d199466171d97d54ef90 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -3583,6 +3583,24 @@ Field 31:16 PhyPARTID29
Field 15:0 PhyPARTID28
EndSysreg
+Sysreg ICH_HFGRTR_EL2 3 4 12 9 4
+Res0 63:21
+Field 20 ICC_PPI_ACTIVERn_EL1
+Field 19 ICC_PPI_PRIORITYRn_EL1
+Field 18 ICC_PPI_PENDRn_EL1
+Field 17 ICC_PPI_ENABLERn_EL1
+Field 16 ICC_PPI_HMRn_EL1
+Res0 15:8
+Field 7 ICC_IAFFIDR_EL1
+Field 6 ICC_ICSR_EL1
+Field 5 ICC_PCR_EL1
+Field 4 ICC_HPPIR_EL1
+Field 3 ICC_HAPR_EL1
+Field 2 ICC_CR0_EL1
+Field 1 ICC_IDRn_EL1
+Field 0 ICC_APR_EL1
+EndSysreg
+
Sysreg ICH_HCR_EL2 3 4 12 11 0
Res0 63:32
Field 31:27 EOIcount
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 13/22] arm64/sysreg: Add ICH_HFGWTR_EL2
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (11 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 12/22] arm64/sysreg: Add ICH_HFGRTR_EL2 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 14/22] arm64/sysreg: Add ICH_HFGITR_EL2 Lorenzo Pieralisi
` (8 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICH_HFGWTR_EL2 register description to sysreg.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 0c0e805481c84a14ae62d199466171d97d54ef90..1b519e35000be328acfe26d51e098059f9cf9ef2 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -3601,6 +3601,21 @@ Field 1 ICC_IDRn_EL1
Field 0 ICC_APR_EL1
EndSysreg
+Sysreg ICH_HFGWTR_EL2 3 4 12 9 6
+Res0 63:21
+Field 20 ICC_PPI_ACTIVERn_EL1
+Field 19 ICC_PPI_PRIORITYRn_EL1
+Field 18 ICC_PPI_PENDRn_EL1
+Field 17 ICC_PPI_ENABLERn_EL1
+Res0 16:7
+Field 6 ICC_ICSR_EL1
+Field 5 ICC_PCR_EL1
+Res0 4:3
+Field 2 ICC_CR0_EL1
+Res0 1
+Field 0 ICC_APR_EL1
+EndSysreg
+
Sysreg ICH_HCR_EL2 3 4 12 11 0
Res0 63:32
Field 31:27 EOIcount
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 14/22] arm64/sysreg: Add ICH_HFGITR_EL2
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (12 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 13/22] arm64/sysreg: Add ICH_HFGWTR_EL2 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 15/22] arm64: Disable GICv5 read/write/instruction traps Lorenzo Pieralisi
` (7 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Add ICH_HFGITR_EL2 register description to sysreg.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/tools/sysreg | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 1b519e35000be328acfe26d51e098059f9cf9ef2..5af0dea6e775ea680686dbe4bc836b5f5b69fbc7 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -3616,6 +3616,21 @@ Res0 1
Field 0 ICC_APR_EL1
EndSysreg
+Sysreg ICH_HFGITR_EL2 3 4 12 9 7
+Res0 63:11
+Field 10 GICRCDNMIA
+Field 9 GICRCDIA
+Field 8 GICCDDI
+Field 7 GICCDEOI
+Field 6 GICCDHM
+Field 5 GICCRDRCFG
+Field 4 GICCDPEND
+Field 3 GICCDAFF
+Field 2 GICCDPRI
+Field 1 GICCDDIS
+Field 0 GICCDEN
+EndSysreg
+
Sysreg ICH_HCR_EL2 3 4 12 11 0
Res0 63:32
Field 31:27 EOIcount
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 15/22] arm64: Disable GICv5 read/write/instruction traps
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (13 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 14/22] arm64/sysreg: Add ICH_HFGITR_EL2 Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-05-01 14:32 ` Marc Zyngier
2025-04-24 10:25 ` [PATCH v2 16/22] arm64: cpucaps: Rename GICv3 CPU interface capability Lorenzo Pieralisi
` (6 subsequent siblings)
21 siblings, 1 reply; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
GICv5 trap configuration registers value is UNKNOWN at reset.
Initialize GICv5 EL2 trap configuration registers to prevent
trapping GICv5 instruction/register access upon entering the
kernel.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/include/asm/el2_setup.h | 45 ++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h
index ebceaae3c749b84395c9c5eccf0caf874697ad11..1e362bb3b042d51fff15a7c2abc73842930b275a 100644
--- a/arch/arm64/include/asm/el2_setup.h
+++ b/arch/arm64/include/asm/el2_setup.h
@@ -165,6 +165,50 @@
.Lskip_gicv3_\@:
.endm
+/* GICv5 system register access */
+.macro __init_el2_gicv5
+ mrs_s x0, SYS_ID_AA64PFR2_EL1
+ ubfx x0, x0, #ID_AA64PFR2_EL1_GCIE_SHIFT, #4
+ cbz x0, .Lskip_gicv5_\@
+
+ mov x0, #(1 << ICH_HFGITR_EL2_GICRCDNMIA_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICRCDIA_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCDDI_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCDEOI_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCDHM_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCRDRCFG_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCDPEND_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCDAFF_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCDPRI_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCDDIS_SHIFT | \
+ 1 << ICH_HFGITR_EL2_GICCDEN_SHIFT)
+ msr_s SYS_ICH_HFGITR_EL2, x0 // Disable instruction traps
+ mov_q x0, (1 << ICH_HFGRTR_EL2_ICC_PPI_ACTIVERn_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_PPI_PRIORITYRn_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_PPI_PENDRn_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_PPI_ENABLERn_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_PPI_HMRn_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_ICSR_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_PCR_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_HPPIR_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_HAPR_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_CR0_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_IDRn_EL1_SHIFT | \
+ 1 << ICH_HFGRTR_EL2_ICC_APR_EL1_SHIFT)
+ msr_s SYS_ICH_HFGRTR_EL2, x0 // Disable reg read traps
+ mov_q x0, (1 << ICH_HFGWTR_EL2_ICC_PPI_ACTIVERn_EL1_SHIFT | \
+ 1 << ICH_HFGWTR_EL2_ICC_PPI_PRIORITYRn_EL1_SHIFT | \
+ 1 << ICH_HFGWTR_EL2_ICC_PPI_PENDRn_EL1_SHIFT | \
+ 1 << ICH_HFGWTR_EL2_ICC_PPI_ENABLERn_EL1_SHIFT | \
+ 1 << ICH_HFGWTR_EL2_ICC_ICSR_EL1_SHIFT | \
+ 1 << ICH_HFGWTR_EL2_ICC_PCR_EL1_SHIFT | \
+ 1 << ICH_HFGWTR_EL2_ICC_CR0_EL1_SHIFT | \
+ 1 << ICH_HFGWTR_EL2_ICC_APR_EL1_SHIFT)
+ msr_s SYS_ICH_HFGWTR_EL2, x0 // Disable reg write traps
+.Lskip_gicv5_\@:
+.endm
+
.macro __init_el2_hstr
msr hstr_el2, xzr // Disable CP15 traps to EL2
.endm
@@ -323,6 +367,7 @@
__init_el2_lor
__init_el2_stage2
__init_el2_gicv3
+ __init_el2_gicv5
__init_el2_hstr
__init_el2_mpam
__init_el2_nvhe_idregs
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 16/22] arm64: cpucaps: Rename GICv3 CPU interface capability
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (14 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 15/22] arm64: Disable GICv5 read/write/instruction traps Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 17/22] arm64: cpucaps: Add GICv5 CPU interface (GCIE) capability Lorenzo Pieralisi
` (5 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
In preparation for adding a GICv5 CPU interface capability,
rework the existing GICv3 CPUIF capability - change its name and
description so that the subsequent GICv5 CPUIF capability
can be added with a more consistent naming on top.
Suggested-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/kernel/cpufeature.c | 10 +++++-----
arch/arm64/tools/cpucaps | 2 +-
drivers/irqchip/irq-gic.c | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 9c4d6d552b25cb3a31d1fb267bd73d3f82513e69..cbb49de451f45fbee3100ea01e77b06352bd55ac 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -2283,11 +2283,11 @@ static bool can_use_gic_priorities(const struct arm64_cpu_capabilities *entry,
int scope)
{
/*
- * ARM64_HAS_GIC_CPUIF_SYSREGS has a lower index, and is a boot CPU
+ * ARM64_HAS_GICV3_CPUIF has a lower index, and is a boot CPU
* feature, so will be detected earlier.
*/
- BUILD_BUG_ON(ARM64_HAS_GIC_PRIO_MASKING <= ARM64_HAS_GIC_CPUIF_SYSREGS);
- if (!cpus_have_cap(ARM64_HAS_GIC_CPUIF_SYSREGS))
+ BUILD_BUG_ON(ARM64_HAS_GIC_PRIO_MASKING <= ARM64_HAS_GICV3_CPUIF);
+ if (!cpus_have_cap(ARM64_HAS_GICV3_CPUIF))
return false;
return enable_pseudo_nmi;
@@ -2483,8 +2483,8 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
.matches = has_always,
},
{
- .desc = "GIC system register CPU interface",
- .capability = ARM64_HAS_GIC_CPUIF_SYSREGS,
+ .desc = "GICv3 CPU interface",
+ .capability = ARM64_HAS_GICV3_CPUIF,
.type = ARM64_CPUCAP_STRICT_BOOT_CPU_FEATURE,
.matches = has_useable_gicv3_cpuif,
ARM64_CPUID_FIELDS(ID_AA64PFR0_EL1, GIC, IMP)
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 772c1b008e437ed34cedb1c0f663c4dcea8f6759..860ec49cc0530885c138b7dc7f67d58cd69b2593 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -34,7 +34,7 @@ HAS_GENERIC_AUTH
HAS_GENERIC_AUTH_ARCH_QARMA3
HAS_GENERIC_AUTH_ARCH_QARMA5
HAS_GENERIC_AUTH_IMP_DEF
-HAS_GIC_CPUIF_SYSREGS
+HAS_GICV3_CPUIF
HAS_GIC_PRIO_MASKING
HAS_GIC_PRIO_RELAXED_SYNC
HAS_HCR_NV1
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c
index 6503573557fdf295bc543b16b64e3e7dd6841321..1269ab8eb726afbb80849fd062612861680cb4d1 100644
--- a/drivers/irqchip/irq-gic.c
+++ b/drivers/irqchip/irq-gic.c
@@ -54,7 +54,7 @@
static void gic_check_cpu_features(void)
{
- WARN_TAINT_ONCE(this_cpu_has_cap(ARM64_HAS_GIC_CPUIF_SYSREGS),
+ WARN_TAINT_ONCE(this_cpu_has_cap(ARM64_HAS_GICV3_CPUIF),
TAINT_CPU_OUT_OF_SPEC,
"GICv3 system registers enabled, broken firmware!\n");
}
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 17/22] arm64: cpucaps: Add GICv5 CPU interface (GCIE) capability
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (15 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 16/22] arm64: cpucaps: Rename GICv3 CPU interface capability Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 18/22] arm64: smp: Support non-SGIs for IPIs Lorenzo Pieralisi
` (4 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Implement the GCIE capability as a strict boot cpu capability to
detect whether architectural GICv5 support is available in HW.
Plug it in with a naming consistent with the existing GICv3
CPU interface capability.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/kernel/cpufeature.c | 7 +++++++
arch/arm64/tools/cpucaps | 1 +
2 files changed, 8 insertions(+)
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index cbb49de451f45fbee3100ea01e77b06352bd55ac..4d5163a20ee0fb09380ea5f1f2d37afb7257edfb 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -3041,6 +3041,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
.matches = has_pmuv3,
},
#endif
+ {
+ .desc = "GICv5 CPU interface",
+ .type = ARM64_CPUCAP_STRICT_BOOT_CPU_FEATURE,
+ .capability = ARM64_HAS_GICV5_CPUIF,
+ .matches = has_cpuid_feature,
+ ARM64_CPUID_FIELDS(ID_AA64PFR2_EL1, GCIE, IMP)
+ },
{},
};
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 860ec49cc0530885c138b7dc7f67d58cd69b2593..c36f4165e2bb460abde81baf453199f62dd265b0 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -35,6 +35,7 @@ HAS_GENERIC_AUTH_ARCH_QARMA3
HAS_GENERIC_AUTH_ARCH_QARMA5
HAS_GENERIC_AUTH_IMP_DEF
HAS_GICV3_CPUIF
+HAS_GICV5_CPUIF
HAS_GIC_PRIO_MASKING
HAS_GIC_PRIO_RELAXED_SYNC
HAS_HCR_NV1
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 18/22] arm64: smp: Support non-SGIs for IPIs
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (16 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 17/22] arm64: cpucaps: Add GICv5 CPU interface (GCIE) capability Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support Lorenzo Pieralisi
` (3 subsequent siblings)
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
From: Marc Zyngier <maz@kernel.org>
The arm64 arch has relied so far on GIC architectural software
generated interrupt (SGIs) to handle IPIs. Those are per-cpu
software generated interrupts.
arm64 architecture code that allocates the IPIs virtual IRQs and
IRQ descriptors was written accordingly.
On GICv5 systems, IPIs are implemented using LPIs that are not
per-cpu interrupts - they are just normal routable IRQs.
Add arch code to set-up IPIs on systems where they are handled
using normal routable IRQs.
For those systems, force the IRQ affinity (and make it immutable)
to the cpu a given IRQ was assigned to.
Signed-off-by: Marc Zyngier <maz@kernel.org>
[timothy.hayes@arm.com: fixed ipi/irq conversion, irq flags]
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
[lpieralisi: changed affinity set-up, log]
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
---
arch/arm64/include/asm/smp.h | 7 ++-
arch/arm64/kernel/smp.c | 139 ++++++++++++++++++++++++++++++++-----------
2 files changed, 111 insertions(+), 35 deletions(-)
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index 2510eec026f7e3d6f0ecf1197c3a81b183ddd216..d6fd6efb66a673ae33825971e4aa07e791c02ee5 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -53,7 +53,12 @@ extern void smp_init_cpus(void);
/*
* Register IPI interrupts with the arch SMP code
*/
-extern void set_smp_ipi_range(int ipi_base, int nr_ipi);
+extern void set_smp_ipi_range_percpu(int ipi_base, int nr_ipi, int ncpus);
+
+static inline void set_smp_ipi_range(int ipi_base, int n)
+{
+ set_smp_ipi_range_percpu(ipi_base, n, 0);
+}
/*
* Called from the secondary holding pen, this is the secondary CPU entry point.
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 3b3f6b56e733039cad7ff5b8995db16a68f3c762..3f3712e47c94c62836fb89cd4bfb3595fbb41557 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -83,7 +83,26 @@ enum ipi_msg_type {
static int ipi_irq_base __ro_after_init;
static int nr_ipi __ro_after_init = NR_IPI;
-static struct irq_desc *ipi_desc[MAX_IPI] __ro_after_init;
+
+struct ipi_descs {
+ struct irq_desc *descs[MAX_IPI];
+};
+
+static DEFINE_PER_CPU(struct ipi_descs, pcpu_ipi_desc);
+
+#define get_ipi_desc(__cpu, __ipi) (per_cpu_ptr(&pcpu_ipi_desc, __cpu)->descs[__ipi])
+
+static bool percpu_ipi_descs __ro_after_init;
+
+static int ipi_to_irq(int ipi, int cpu)
+{
+ return ipi_irq_base + (cpu * nr_ipi) + ipi;
+}
+
+static int irq_to_ipi(int irq)
+{
+ return (irq - ipi_irq_base) % nr_ipi;
+}
static bool crash_stop;
@@ -844,7 +863,7 @@ int arch_show_interrupts(struct seq_file *p, int prec)
seq_printf(p, "%*s%u:%s", prec - 1, "IPI", i,
prec >= 4 ? " " : "");
for_each_online_cpu(cpu)
- seq_printf(p, "%10u ", irq_desc_kstat_cpu(ipi_desc[i], cpu));
+ seq_printf(p, "%10u ", irq_desc_kstat_cpu(get_ipi_desc(cpu, i), cpu));
seq_printf(p, " %s\n", ipi_types[i]);
}
@@ -919,7 +938,13 @@ static void __noreturn ipi_cpu_crash_stop(unsigned int cpu, struct pt_regs *regs
static void arm64_backtrace_ipi(cpumask_t *mask)
{
- __ipi_send_mask(ipi_desc[IPI_CPU_BACKTRACE], mask);
+ unsigned int cpu;
+
+ if (!percpu_ipi_descs)
+ __ipi_send_mask(get_ipi_desc(0, IPI_CPU_BACKTRACE), mask);
+ else
+ for_each_cpu(cpu, mask)
+ __ipi_send_single(get_ipi_desc(cpu, IPI_CPU_BACKTRACE), cpu);
}
void arch_trigger_cpumask_backtrace(const cpumask_t *mask, int exclude_cpu)
@@ -944,7 +969,7 @@ void kgdb_roundup_cpus(void)
if (cpu == this_cpu)
continue;
- __ipi_send_single(ipi_desc[IPI_KGDB_ROUNDUP], cpu);
+ __ipi_send_single(get_ipi_desc(cpu, IPI_KGDB_ROUNDUP), cpu);
}
}
#endif
@@ -1013,14 +1038,21 @@ static void do_handle_IPI(int ipinr)
static irqreturn_t ipi_handler(int irq, void *data)
{
- do_handle_IPI(irq - ipi_irq_base);
+ do_handle_IPI(irq_to_ipi(irq));
return IRQ_HANDLED;
}
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr)
{
+ unsigned int cpu;
+
trace_ipi_raise(target, ipi_types[ipinr]);
- __ipi_send_mask(ipi_desc[ipinr], target);
+
+ if (!percpu_ipi_descs)
+ __ipi_send_mask(get_ipi_desc(0, ipinr), target);
+ else
+ for_each_cpu(cpu, target)
+ __ipi_send_single(get_ipi_desc(cpu, ipinr), cpu);
}
static bool ipi_should_be_nmi(enum ipi_msg_type ipi)
@@ -1046,11 +1078,15 @@ static void ipi_setup(int cpu)
return;
for (i = 0; i < nr_ipi; i++) {
- if (ipi_should_be_nmi(i)) {
- prepare_percpu_nmi(ipi_irq_base + i);
- enable_percpu_nmi(ipi_irq_base + i, 0);
+ if (!percpu_ipi_descs) {
+ if (ipi_should_be_nmi(i)) {
+ prepare_percpu_nmi(ipi_irq_base + i);
+ enable_percpu_nmi(ipi_irq_base + i, 0);
+ } else {
+ enable_percpu_irq(ipi_irq_base + i, 0);
+ }
} else {
- enable_percpu_irq(ipi_irq_base + i, 0);
+ enable_irq(irq_desc_get_irq(get_ipi_desc(cpu, i)));
}
}
}
@@ -1064,44 +1100,79 @@ static void ipi_teardown(int cpu)
return;
for (i = 0; i < nr_ipi; i++) {
- if (ipi_should_be_nmi(i)) {
- disable_percpu_nmi(ipi_irq_base + i);
- teardown_percpu_nmi(ipi_irq_base + i);
+ if (!percpu_ipi_descs) {
+ if (ipi_should_be_nmi(i)) {
+ disable_percpu_nmi(ipi_irq_base + i);
+ teardown_percpu_nmi(ipi_irq_base + i);
+ } else {
+ disable_percpu_irq(ipi_irq_base + i);
+ }
} else {
- disable_percpu_irq(ipi_irq_base + i);
+ disable_irq(irq_desc_get_irq(get_ipi_desc(cpu, i)));
}
}
}
#endif
-void __init set_smp_ipi_range(int ipi_base, int n)
+static void ipi_setup_ppi(int ipi)
+{
+ int err, irq, cpu;
+
+ irq = ipi_irq_base + ipi;
+
+ if (ipi_should_be_nmi(irq)) {
+ err = request_percpu_nmi(irq, ipi_handler, "IPI", &irq_stat);
+ WARN(err, "Could not request IRQ %d as NMI, err=%d\n", irq, err);
+ } else {
+ err = request_percpu_irq(irq, ipi_handler, "IPI", &irq_stat);
+ WARN(err, "Could not request IRQ %d as IRQ, err=%d\n", irq, err);
+ }
+
+ for_each_possible_cpu(cpu)
+ get_ipi_desc(cpu, ipi) = irq_to_desc(irq);
+
+ irq_set_status_flags(irq, IRQ_HIDDEN);
+}
+
+static void ipi_setup_lpi(int ipi, int ncpus)
+{
+ for (int cpu = 0; cpu < ncpus; cpu++) {
+ int err, irq;
+
+ irq = ipi_to_irq(ipi, cpu);
+
+ err = irq_force_affinity(irq, cpumask_of(cpu));
+
+ WARN(err, "Could not force affinity IRQ %d, err=%d\n", irq, err);
+
+ err = request_irq(irq, ipi_handler, IRQF_NO_AUTOEN, "IPI",
+ &irq_stat);
+
+ WARN(err, "Could not request IRQ %d, err=%d\n", irq, err);
+
+ irq_set_status_flags(irq, (IRQ_HIDDEN | IRQ_NO_BALANCING_MASK));
+
+ get_ipi_desc(cpu, ipi) = irq_to_desc(irq);
+ }
+}
+
+void __init set_smp_ipi_range_percpu(int ipi_base, int n, int ncpus)
{
int i;
WARN_ON(n < MAX_IPI);
nr_ipi = min(n, MAX_IPI);
- for (i = 0; i < nr_ipi; i++) {
- int err;
-
- if (ipi_should_be_nmi(i)) {
- err = request_percpu_nmi(ipi_base + i, ipi_handler,
- "IPI", &irq_stat);
- WARN(err, "Could not request IPI %d as NMI, err=%d\n",
- i, err);
- } else {
- err = request_percpu_irq(ipi_base + i, ipi_handler,
- "IPI", &irq_stat);
- WARN(err, "Could not request IPI %d as IRQ, err=%d\n",
- i, err);
- }
-
- ipi_desc[i] = irq_to_desc(ipi_base + i);
- irq_set_status_flags(ipi_base + i, IRQ_HIDDEN);
- }
-
+ percpu_ipi_descs = !!ncpus;
ipi_irq_base = ipi_base;
+ for (i = 0; i < nr_ipi; i++) {
+ if (!percpu_ipi_descs)
+ ipi_setup_ppi(i);
+ else
+ ipi_setup_lpi(i, ncpus);
+ }
+
/* Setup the boot CPU immediately */
ipi_setup(smp_processor_id());
}
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (17 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 18/22] arm64: smp: Support non-SGIs for IPIs Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-28 15:49 ` Marc Zyngier
2025-04-24 10:25 ` [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support Lorenzo Pieralisi
` (2 subsequent siblings)
21 siblings, 1 reply; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Implement GICv5 CPU interface and IRS support, to manage interrupt
state, priority and routing for all GICv5 interrupt types.
The GICv5 CPU interface implements support for PE-Private Peripheral
Interrupts (PPI), that are handled (enabled/prioritized/delivered)
entirely within the CPU interface hardware.
To enable PPI interrupts, implement the baseline GICv5 host kernel
driver infrastructure required to handle interrupts on a GICv5 system.
Add the exception handling code path and definitions for GICv5
instructions.
Add GICv5 PPI handling code as a specific IRQ domain to:
- Set-up PPI priority
- Manage PPI configuration and state
- Manage IRQ flow handler
- IRQs allocation/free
- Hook-up a PPI specific IRQchip to provide the relevant methods
PPI IRQ priority is chosen as the minimum allowed priority by the
system design (after probing the number of priority bits implemented
by the CPU interface).
The GICv5 Interrupt Routing Service (IRS) component implements
interrupt management and routing in the GICv5 architecture.
A GICv5 system comprises one or more IRSes, that together
handle the interrupt routing and state for the system.
An IRS supports Shared Peripheral Interrupts (SPIs), that are
interrupt sources directly connected to the IRS; they do not
rely on memory for storage. The number of supported SPIs is
fixed for a given implementation and can be probed through IRS
IDR registers.
SPI interrupt state and routing are managed through GICv5
instructions.
Each core (PE in GICv5 terms) in a GICv5 system is identified with
an Interrupt AFFinity ID (IAFFID).
An IRS manages a set of cores that are connected to it.
Firmware provides a topology description that the driver uses
to detect to which IRS a CPU (ie an IAFFID) is associated with.
Use probeable information and firmware description to initialize
the IRSes and implement GICv5 IRS SPIs support through an
SPI-specific IRQ domain.
The GICv5 IRS driver:
- Probes IRSes in the system to detect SPI ranges
- Associates an IRS with a set of cores connected to it
- Adds an IRQchip structure for SPI handling
SPIs priority is set to a value corresponding to the lowest
permissible priority in the system (taking into account the
implemented priority bits of the IRS and CPU interface).
Since all IRQs are set to the same priority value, the value
itself does not matter as long as it is a valid one.
An IRS supports Logical Peripheral Interrupts (LPIs) and implement
Linux IPIs on top of it.
LPIs are used for interrupt signals that are translated by a
GICv5 ITS (Interrupt Translation Service) but also for software
generated IRQs - namely interrupts that are not driven by a HW
signal, ie IPIs.
LPIs rely on memory storage for interrupt routing and state.
Memory storage is handled by the IRS - that is configured
at probe time by the driver with the required memory.
LPIs state and routing information is kept in the Interrupt
State Table (IST).
IRSes provide support for 1- or 2-level IST tables configured
to support a maximum number of interrupts that depend on the
OS configuration and the HW capabilities.
On systems that provide 2-level IST support, always allow
the maximum number of LPIs; On systems with only 1-level
support, limit the number of LPIs to 2^12 to prevent
wasting memory (presumably a system that supports a 1-level
only IST is not expecting a large number of interrupts).
On a 2-level IST system, L2 entries are allocated on
demand.
The IST table memory is allocated using the kmalloc() interface;
the allocation required may be smaller than a page and must be
made up of contiguous physical pages if larger than a page.
On systems where the IRS is not cache-coherent with the CPUs,
cache mainteinance operations are executed to clean and
invalidate the allocated memory to the point of coherency
making it visible to the IRS components.
Add an LPI IRQ domain to:
- Manage LPI state and routing
- Add LPI IRQchip structure and callbacks
- LPI domain allocation/de-allocation
On GICv5 systems, IPIs are implemented using LPIs.
Implement an IPI-specific IRQ domain created as a child/subdomain
of the LPI domain to allocate the required number of LPIs needed
to implement the IPIs.
Move the arm64 IPI enum declaration to a header file so that the
GICv5 driver code can detect how many IPIs are required by arch code.
IPIs are backed by LPIs, add LPIs allocation/de-allocation
functions.
The LPI INTID namespace is managed using an IDA to alloc/free LPI
INTIDs.
Associate an IPI irqchip with IPI IRQ descriptors to provide
core code with the irqchip.ipi_send_single() method required
to raise an IPI.
Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
MAINTAINERS | 2 +
arch/arm64/include/asm/arch_gicv5.h | 91 +++
arch/arm64/include/asm/smp.h | 17 +
arch/arm64/kernel/smp.c | 17 -
drivers/irqchip/Kconfig | 5 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-gic-v5-irs.c | 841 ++++++++++++++++++++++++++++
drivers/irqchip/irq-gic-v5.c | 1058 +++++++++++++++++++++++++++++++++++
drivers/irqchip/irq-gic-v5.h | 184 ++++++
9 files changed, 2199 insertions(+), 17 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index f3ed84466da19906953b5396a5f4b50e878c376e..cdeceb6782355a4a18609135bf7f03249d8b0bb5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1907,6 +1907,8 @@ M: Marc Zyngier <maz@kernel.org>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
F: Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
+F: arch/arm64/include/asm/arch_gicv5.h
+F: drivers/irqchip/irq-gic-v5*.[ch]
ARM HDLCD DRM DRIVER
M: Liviu Dudau <liviu.dudau@arm.com>
diff --git a/arch/arm64/include/asm/arch_gicv5.h b/arch/arm64/include/asm/arch_gicv5.h
new file mode 100644
index 0000000000000000000000000000000000000000..75557fdad611fa51d7136126eb80cb861be98a8d
--- /dev/null
+++ b/arch/arm64/include/asm/arch_gicv5.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 ARM Ltd.
+ */
+#ifndef __ASM_ARCH_GICV5_H
+#define __ASM_ARCH_GICV5_H
+
+#include <asm/cacheflush.h>
+#include <asm/sysreg.h>
+
+#ifndef __ASSEMBLY__
+
+#define GICV5_OP_GIC_CDAFF sys_insn(1, 0, 12, 1, 3)
+#define GICV5_OP_GIC_CDDI sys_insn(1, 0, 12, 2, 0)
+#define GICV5_OP_GIC_CDDIS sys_insn(1, 0, 12, 1, 0)
+#define GICV5_OP_GIC_CDEN sys_insn(1, 0, 12, 1, 1)
+#define GICV5_OP_GIC_CDEOI sys_insn(1, 0, 12, 1, 7)
+#define GICV5_OP_GIC_CDPEND sys_insn(1, 0, 12, 1, 4)
+#define GICV5_OP_GIC_CDPRI sys_insn(1, 0, 12, 1, 2)
+#define GICV5_OP_GIC_CDRCFG sys_insn(1, 0, 12, 1, 5)
+#define GICV5_OP_GICR_CDIA sys_insn(1, 0, 12, 3, 0)
+
+#define gicr_insn(insn) read_sysreg_s(insn)
+#define gic_insn(v, insn) write_sysreg_s(v, insn)
+
+#define GSB_ACK __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 1) | 31)
+#define GSB_SYS __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 0) | 31)
+
+#define gsb_ack() asm volatile(GSB_ACK : : : "memory")
+#define gsb_sys() asm volatile(GSB_SYS : : : "memory")
+
+/* Shift and mask definitions for GIC CDAFF */
+#define GICV5_GIC_CDAFF_IAFFID_MASK GENMASK_ULL(47, 32)
+#define GICV5_GIC_CDAFF_IAFFID(r) FIELD_GET(GICV5_GIC_CDAFF_IAFFID_MASK, r)
+#define GICV5_GIC_CDAFF_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDAFF_TYPE(r) FIELD_GET(GICV5_GIC_CDAFF_TYPE_MASK, r)
+#define GICV5_GIC_CDAFF_IRM_MASK BIT_ULL(28)
+#define GICV5_GIC_CDAFF_IRM(r) FIELD_GET(GICV5_GIC_CDAFF_IRM_MASK, r)
+#define GICV5_GIC_CDAFF_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDAFF_ID(r) FIELD_GET(GICV5_GIC_CDAFF_ID_MASK, r)
+
+/* Shift and mask definitions for GIC CDDI */
+#define GICV5_GIC_CDDI_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDDI_TYPE(r) FIELD_GET(GICV5_GIC_CDDI_TYPE_MASK, r)
+#define GICV5_GIC_CDDI_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDDI_ID(r) FIELD_GET(GICV5_GIC_CDDI_ID_MASK, r)
+
+/* Shift and mask definitions for GIC CDDIS */
+#define GICV5_GIC_CDDIS_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDDIS_TYPE(r) FIELD_GET(GICV5_GIC_CDDIS_TYPE_MASK, r)
+#define GICV5_GIC_CDDIS_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDDIS_ID(r) FIELD_GET(GICV5_GIC_CDDIS_ID_MASK, r)
+
+/* Shift and mask definitions for GIC CDEN */
+#define GICV5_GIC_CDEN_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDEN_TYPE(r) FIELD_GET(GICV5_GIC_CDEN_TYPE_MASK, r)
+#define GICV5_GIC_CDEN_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDEN_ID(r) FIELD_GET(GICV5_GIC_CDEN_ID_MASK, r)
+
+/* Shift and mask definitions for GIC CDPEND */
+#define GICV5_GIC_CDPEND_PENDING_MASK BIT_ULL(32)
+#define GICV5_GIC_CDPEND_PENDING(r) FIELD_GET(GICV5_GIC_CDPEND_PENDING_MASK, r)
+#define GICV5_GIC_CDPEND_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDPEND_TYPE(r) FIELD_GET(GICV5_GIC_CDPEND_TYPE_MASK, r)
+#define GICV5_GIC_CDPEND_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDPEND_ID(r) FIELD_GET(GICV5_GIC_CDPEND_ID_MASK, r)
+
+/* Shift and mask definitions for GIC CDPRI */
+#define GICV5_GIC_CDPRI_PRIORITY_MASK GENMASK_ULL(39, 35)
+#define GICV5_GIC_CDPRI_PRIORITY(r) FIELD_GET(GICV5_GIC_CDPRI_PRIORITY_MASK, r)
+#define GICV5_GIC_CDPRI_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDPRI_TYPE(r) FIELD_GET(GICV5_GIC_CDPRI_TYPE_MASK, r)
+#define GICV5_GIC_CDPRI_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDPRI_ID(r) FIELD_GET(GICV5_GIC_CDPRI_ID_MASK, r)
+
+/* Shift and mask definitions for GIC CDRCFG */
+#define GICV5_GIC_CDRCFG_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDRCFG_TYPE(r) FIELD_GET(GICV5_GIC_CDRCFG_TYPE_MASK, r)
+#define GICV5_GIC_CDRCFG_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDRCFG_ID(r) FIELD_GET(GICV5_GIC_CDRCFG_ID_MASK, r)
+
+/* Shift and mask definitions for GICR CDIA */
+#define GICV5_GIC_CDIA_VALID_MASK BIT_ULL(32)
+#define GICV5_GIC_CDIA_VALID(r) FIELD_GET(GICV5_GIC_CDIA_VALID_MASK, r)
+#define GICV5_GIC_CDIA_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDIA_TYPE(r) FIELD_GET(GICV5_GIC_CDIA_TYPE_MASK, r)
+#define GICV5_GIC_CDIA_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDIA_ID(r) FIELD_GET(GICV5_GIC_CDIA_ID_MASK, r)
+
+#endif /* __ASSEMBLY__ */
+#endif /* __ASM_ARCH_GICV5_H */
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index d6fd6efb66a673ae33825971e4aa07e791c02ee5..d48ef6d5abcc77d1c06ad8e91e72006acf662078 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -50,6 +50,23 @@ struct seq_file;
*/
extern void smp_init_cpus(void);
+enum ipi_msg_type {
+ IPI_RESCHEDULE,
+ IPI_CALL_FUNC,
+ IPI_CPU_STOP,
+ IPI_CPU_STOP_NMI,
+ IPI_TIMER,
+ IPI_IRQ_WORK,
+ NR_IPI,
+ /*
+ * Any enum >= NR_IPI and < MAX_IPI is special and not tracable
+ * with trace_ipi_*
+ */
+ IPI_CPU_BACKTRACE = NR_IPI,
+ IPI_KGDB_ROUNDUP,
+ MAX_IPI
+};
+
/*
* Register IPI interrupts with the arch SMP code
*/
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 3f3712e47c94c62836fb89cd4bfb3595fbb41557..148145979d83f67469075df1c8b5e366ffe9d907 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -64,23 +64,6 @@ struct secondary_data secondary_data;
/* Number of CPUs which aren't online, but looping in kernel text. */
static int cpus_stuck_in_kernel;
-enum ipi_msg_type {
- IPI_RESCHEDULE,
- IPI_CALL_FUNC,
- IPI_CPU_STOP,
- IPI_CPU_STOP_NMI,
- IPI_TIMER,
- IPI_IRQ_WORK,
- NR_IPI,
- /*
- * Any enum >= NR_IPI and < MAX_IPI is special and not tracable
- * with trace_ipi_*
- */
- IPI_CPU_BACKTRACE = NR_IPI,
- IPI_KGDB_ROUNDUP,
- MAX_IPI
-};
-
static int ipi_irq_base __ro_after_init;
static int nr_ipi __ro_after_init = NR_IPI;
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index cec05e443083b8982b3e72f4212d808a22883914..160a4761d5d85f6dbf36f3142fd619c114733e36 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -54,6 +54,11 @@ config ARM_GIC_V3_ITS_FSL_MC
depends on FSL_MC_BUS
default ARM_GIC_V3_ITS
+config ARM_GIC_V5
+ bool
+ select IRQ_DOMAIN_HIERARCHY
+ select GENERIC_IRQ_EFFECTIVE_AFF_MASK if SMP
+
config ARM_NVIC
bool
select IRQ_DOMAIN_HIERARCHY
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 365bcea9a61ff89e2cb41034125b3fc8cd494d81..3d9c47fa3fdf40b7452c059d84fe8ac24c91bc0f 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o
obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o
obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
+obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
obj-$(CONFIG_ARM_VIC) += irq-vic.o
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
new file mode 100644
index 0000000000000000000000000000000000000000..7bd60e6d56b77c0c19a1bd9bee9685d9b6ffc959
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -0,0 +1,841 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) "GICv5 IRS: " fmt
+
+#include <linux/iopoll.h>
+#include <linux/irqchip.h>
+#include <linux/log2.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include "irq-gic-v5.h"
+
+#define LPI_ID_BITS_LINEAR 12
+
+#define IRS_FLAGS_NON_COHERENT BIT(0)
+
+static DEFINE_PER_CPU(struct gicv5_irs_chip_data *, per_cpu_irs_data);
+static LIST_HEAD(irs_nodes);
+
+static u32 irs_readl_relaxed(struct gicv5_irs_chip_data *irs_data,
+ const u64 reg_offset)
+{
+ return readl_relaxed(irs_data->irs_base + reg_offset);
+}
+
+static void irs_writel_relaxed(struct gicv5_irs_chip_data *irs_data,
+ const u32 val, const u64 reg_offset)
+{
+ writel_relaxed(val, irs_data->irs_base + reg_offset);
+}
+
+static u64 irs_readq_relaxed(struct gicv5_irs_chip_data *irs_data,
+ const u64 reg_offset)
+{
+ return readq_relaxed(irs_data->irs_base + reg_offset);
+}
+
+static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
+ const u64 val, const u64 reg_offset)
+{
+ writeq_relaxed(val, irs_data->irs_base + reg_offset);
+}
+
+static int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask, u32 *val)
+{
+ void __iomem *reg = addr + offset;
+ u32 tmp;
+ int ret;
+
+ ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
+
+ if (val)
+ *val = tmp;
+
+ return ret;
+}
+
+#define gicv5_irs_wait_for_op(base, reg, mask) \
+ ({ \
+ int ret; \
+ \
+ ret = gicv5_wait_for_op(base, reg, mask, NULL); \
+ if (unlikely(ret == -ETIMEDOUT)) \
+ pr_err_ratelimited(#reg" timeout...\n"); \
+ ret; \
+ })
+
+/* Wait for completion of an IST change */
+static int gicv5_irs_ist_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
+{
+ return gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_IST_STATUSR,
+ GICV5_IRS_IST_STATUSR_IDLE);
+}
+
+static int __init gicv5_irs_init_ist_linear(struct gicv5_irs_chip_data *irs_data,
+ unsigned int lpi_id_bits,
+ unsigned int istsz)
+{
+ size_t l2istsz;
+ u32 n, cfgr;
+ void *ist;
+ u64 baser;
+ int ret;
+
+ /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */
+ n = max(5, lpi_id_bits + 1 + istsz);
+
+ l2istsz = BIT(n + 1);
+ /*
+ * Check memory requirements. For a linear IST we cap the
+ * number of ID bits to a value that should never exceed
+ * kmalloc interface memory allocation limits, so this
+ * check is really belt and braces.
+ */
+ if (l2istsz > KMALLOC_MAX_SIZE) {
+ u8 lpi_id_cap = ilog2(KMALLOC_MAX_SIZE) - 2 + istsz;
+
+ pr_warn("Limiting LPI ID bits from %u to %u\n",
+ lpi_id_bits, lpi_id_cap);
+ lpi_id_bits = lpi_id_cap;
+ l2istsz = KMALLOC_MAX_SIZE;
+ }
+
+ ist = kzalloc(l2istsz, GFP_KERNEL);
+ if (!ist)
+ return -ENOMEM;
+
+ if (irs_data->flags & IRS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)ist,
+ (unsigned long)ist + l2istsz);
+ else
+ dsb(ishst);
+
+ cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE,
+ GICV5_IRS_IST_CFGR_STRUCTURE_LINEAR) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ,
+ GICV5_IRS_IST_CFGR_L2SZ_4K) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits);
+ irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR);
+
+ gicv5_global_data.ist.l2 = false;
+
+ baser = (virt_to_phys(ist) & GICV5_IRS_IST_BASER_ADDR_MASK) |
+ FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1);
+ irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER);
+
+ /*
+ * The polling wait (in gicv5_wait_for_op()) on a GIC register
+ * provides the memory barriers (through MMIO accessors)
+ * required to synchronize CPU and GIC access to IST memory.
+ */
+ ret = gicv5_irs_ist_wait_for_idle(irs_data);
+ if (ret) {
+ kfree(ist);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __init gicv5_irs_init_ist_two_level(struct gicv5_irs_chip_data *irs_data,
+ unsigned int lpi_id_bits,
+ unsigned int istsz,
+ unsigned int l2sz)
+{
+ __le64 *l1ist;
+ u32 cfgr, n;
+ size_t l1sz;
+ u64 baser;
+ int ret;
+
+ /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */
+ n = max(5, lpi_id_bits - ((10 - istsz) + (2 * l2sz)) + 2);
+
+ l1sz = BIT(n + 1);
+
+ l1ist = kzalloc(l1sz, GFP_KERNEL);
+ if (!l1ist)
+ return -ENOMEM;
+
+ if (irs_data->flags & IRS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)l1ist,
+ (unsigned long)l1ist + l1sz);
+ else
+ dsb(ishst);
+
+ cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE,
+ GICV5_IRS_IST_CFGR_STRUCTURE_TWO_LEVEL) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ, l2sz) |
+ FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits);
+ irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR);
+
+ /*
+ * The L2SZ determine bits required at L2 level. Number of bytes
+ * required by metadata is reported through istsz - the number of bits
+ * covered by L2 entries scales accordingly.
+ */
+ gicv5_global_data.ist.l2_size = BIT(11 + (2 * l2sz) + 1);
+ gicv5_global_data.ist.l2_bits = (10 - istsz) + (2 * l2sz);
+ gicv5_global_data.ist.l1ist_addr = l1ist;
+ gicv5_global_data.ist.l2 = true;
+
+ baser = (virt_to_phys(l1ist) & GICV5_IRS_IST_BASER_ADDR_MASK) |
+ FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1);
+ irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER);
+
+ /*
+ * The polling wait (in gicv5_wait_for_op()) on a GIC register
+ * provides the memory barriers (through MMIO accessors)
+ * required to synchronize CPU and GIC access to IST memory.
+ */
+ ret = gicv5_irs_ist_wait_for_idle(irs_data);
+ if (ret) {
+ kfree(l1ist);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Alloc L2 IST entries on demand.
+ *
+ * Locking/serialization is guaranteed by irqdomain core code by
+ * taking the hierarchical domain struct irq_domain.root->mutex.
+ */
+int gicv5_irs_iste_alloc(const u32 lpi)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ unsigned int index;
+ u32 l2istr, l2bits;
+ __le64 *l1ist;
+ size_t l2size;
+ void *l2ist;
+ int ret;
+
+ if (!gicv5_global_data.ist.l2)
+ return 0;
+
+ irs_data = per_cpu(per_cpu_irs_data, smp_processor_id());
+ if (!irs_data)
+ return -ENOENT;
+
+ l2size = gicv5_global_data.ist.l2_size;
+ l2bits = gicv5_global_data.ist.l2_bits;
+
+ l1ist = gicv5_global_data.ist.l1ist_addr;
+
+ index = lpi >> l2bits;
+
+ if (FIELD_GET(GICV5_ISTL1E_VALID, le64_to_cpu(l1ist[index])))
+ return 0;
+
+ l2ist = kzalloc(l2size, GFP_KERNEL);
+ if (!l2ist)
+ return -ENOMEM;
+
+ l1ist[index] = cpu_to_le64(virt_to_phys(l2ist) & GICV5_ISTL1E_L2_ADDR_MASK);
+
+ if (irs_data->flags & IRS_FLAGS_NON_COHERENT) {
+ dcache_clean_inval_poc((unsigned long)l2ist,
+ (unsigned long)l2ist + l2size);
+ dcache_clean_poc((unsigned long)(l1ist + index),
+ (unsigned long)(l1ist + index) + sizeof(*l1ist));
+ } else {
+ dsb(ishst);
+ }
+
+ l2istr = FIELD_PREP(GICV5_IRS_MAP_L2_ISTR_ID, lpi);
+ irs_writel_relaxed(irs_data, l2istr, GICV5_IRS_MAP_L2_ISTR);
+
+ /*
+ * The polling wait (in gicv5_wait_for_op()) on a GIC register
+ * provides the memory barriers (through MMIO accessors)
+ * required to synchronize CPU and GIC access to IST memory.
+ */
+ ret = gicv5_irs_ist_wait_for_idle(irs_data);
+ if (ret) {
+ l1ist[index] = 0;
+ kfree(l2ist);
+ return ret;
+ }
+
+ /*
+ * Make sure we invalidate the cache line pulled before the IRS
+ * had a chance to update the L1 entry and mark it valid.
+ */
+ if (irs_data->flags & IRS_FLAGS_NON_COHERENT) {
+ /*
+ * gicv5_irs_ist_wait_for_idle() includes memory
+ * barriers (MMIO accessors) required to guarantee that the
+ * following dcache invalidation is not executed before the
+ * IST mapping operation has completed.
+ */
+ dcache_inval_poc((unsigned long)(l1ist + index),
+ (unsigned long)(l1ist + index) + sizeof(*l1ist));
+ }
+
+ return 0;
+}
+
+/*
+ * Try to match the L2 IST size to the pagesize, and if this is not possible
+ * pick the smallest supported L2 size in order to minimise the requirement for
+ * physically contiguous blocks of memory as page-sized allocations are
+ * guaranteed to be physically contiguous, and are by definition the easiest to
+ * find.
+ *
+ * Fall back to the smallest supported size (in the event that the pagesize
+ * itself is not supported) again serves to make it easier to find physically
+ * contiguous blocks of memory.
+ */
+static int gicv5_irs_l2_sz(u32 idr2)
+{
+ switch (PAGE_SIZE) {
+ case SZ_64K:
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_64K;
+ fallthrough;
+ case SZ_16K:
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_16K;
+ fallthrough;
+ case SZ_4K:
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_4K;
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_16K;
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
+ return GICV5_IRS_IST_CFGR_L2SZ_64K;
+ break;
+ }
+
+ return -ENODEV;
+}
+
+static int __init gicv5_irs_init_ist(struct gicv5_irs_chip_data *irs_data)
+{
+ u32 lpi_id_bits, idr2_id_bits, idr2_min_lpi_id_bits,
+ l2_iste_sz, l2sz, l2_iste_sz_split, idr2;
+ bool two_levels, istmd;
+ u64 baser;
+ int ret;
+
+ baser = irs_readq_relaxed(irs_data, GICV5_IRS_IST_BASER);
+ if (FIELD_GET(GICV5_IRS_IST_BASER_VALID, baser)) {
+ pr_err("IST is marked as valid already; cannot allocate\n");
+ return -EPERM;
+ }
+
+ idr2 = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2);
+
+ two_levels = !!FIELD_GET(GICV5_IRS_IDR2_IST_LEVELS, idr2);
+
+ idr2_id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, idr2);
+ idr2_min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, idr2);
+
+ /*
+ * For two level tables we are always allocating the maximum allowed
+ * number of IDs.
+ *
+ * For 1-level tables, we should allocate a number of bits that
+ * is >= min_lpi_id_bits but cap it to LPI_ID_BITS_LINEAR lest
+ * the level 1-table gets too large and its memory allocation
+ * may fail.
+ */
+ if (two_levels) {
+ lpi_id_bits = idr2_id_bits;
+ } else {
+ lpi_id_bits = max(LPI_ID_BITS_LINEAR, idr2_min_lpi_id_bits);
+ lpi_id_bits = min(lpi_id_bits, idr2_id_bits);
+ }
+
+ /*
+ * Cap the ID bits according to the CPUIF supported ID bits
+ */
+ lpi_id_bits = min(lpi_id_bits, gicv5_global_data.cpuif_id_bits);
+
+ if (two_levels) {
+ l2sz = gicv5_irs_l2_sz(idr2);
+ if (l2sz < 0)
+ return l2sz;
+ }
+
+ istmd = !!FIELD_GET(GICV5_IRS_IDR2_ISTMD, idr2);
+
+ l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_4;
+
+ // Only supported if IRS_IDR2.ISTMD is 1
+ if (istmd) {
+ l2_iste_sz_split = FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, idr2);
+
+ if (lpi_id_bits < l2_iste_sz_split)
+ l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_8;
+ else
+ l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_16;
+ }
+
+ /*
+ * Follow GICv5 specification recommendation to opt in for two
+ * level tables
+ */
+ two_levels = two_levels && (lpi_id_bits > ((10 - l2_iste_sz) + (2 * l2sz)));
+
+ if (two_levels)
+ ret = gicv5_irs_init_ist_two_level(irs_data, lpi_id_bits,
+ l2_iste_sz, l2sz);
+ else
+ ret = gicv5_irs_init_ist_linear(irs_data, lpi_id_bits,
+ l2_iste_sz);
+ if (ret)
+ return ret;
+
+ gicv5_init_lpis(BIT(lpi_id_bits));
+
+ return 0;
+}
+
+struct iaffid_entry {
+ u16 iaffid;
+ bool valid;
+};
+
+static DEFINE_PER_CPU(struct iaffid_entry, cpu_iaffid);
+
+int gicv5_irs_cpu_to_iaffid(int cpuid, u16 *iaffid)
+{
+ if (!per_cpu(cpu_iaffid, cpuid).valid) {
+ pr_err("IAFFID for CPU %d has not been initialised\n", cpuid);
+ return -ENODEV;
+ }
+
+ *iaffid = per_cpu(cpu_iaffid, cpuid).iaffid;
+
+ return 0;
+}
+
+struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ u32 min, max;
+
+ list_for_each_entry(irs_data, &irs_nodes, entry) {
+ if (!irs_data->spi_range)
+ continue;
+
+ min = irs_data->spi_min;
+ max = irs_data->spi_min + irs_data->spi_range - 1;
+ if (spi_id >= min && spi_id <= max)
+ return irs_data;
+ }
+
+ return NULL;
+}
+
+static int gicv5_irs_wait_for_spi_op(struct gicv5_irs_chip_data *irs_data)
+{
+ u32 statusr;
+ int ret;
+
+ ret = gicv5_wait_for_op(irs_data->irs_base, GICV5_IRS_SPI_STATUSR,
+ GICV5_IRS_SPI_STATUSR_IDLE, &statusr);
+ if (unlikely(ret == -ETIMEDOUT)) {
+ pr_err_ratelimited("IRS_SPI_STATUSR timeout\n");
+ return ret;
+ }
+
+ return !!FIELD_GET(GICV5_IRS_SPI_STATUSR_V, statusr) ? 0 : -ENXIO;
+}
+
+static int gicv5_irs_wait_for_irs_pe(struct gicv5_irs_chip_data *irs_data,
+ bool selr)
+{
+ u32 statusr;
+ bool valid;
+ int ret;
+
+ ret = gicv5_wait_for_op(irs_data->irs_base, GICV5_IRS_PE_STATUSR,
+ GICV5_IRS_PE_STATUSR_IDLE, &statusr);
+
+ if (unlikely(ret == -ETIMEDOUT)) {
+ pr_err_ratelimited("IRS_PE_STATUSR timeout after %s\n",
+ selr ? "IRS_PE_SELR" : "IRS_PE_CR0");
+ return ret;
+ }
+
+ if (selr) {
+ valid = !!FIELD_GET(GICV5_IRS_PE_STATUSR_V, statusr);
+ return valid ? 0 : -ENXIO;
+ }
+
+ return 0;
+}
+
+static int gicv5_irs_wait_for_pe_selr(struct gicv5_irs_chip_data *irs_data)
+{
+ return gicv5_irs_wait_for_irs_pe(irs_data, true);
+}
+
+static int gicv5_irs_wait_for_pe_cr0(struct gicv5_irs_chip_data *irs_data)
+{
+ return gicv5_irs_wait_for_irs_pe(irs_data, false);
+}
+
+int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gicv5_irs_chip_data *irs_data = d->chip_data;
+ u32 selr, cfgr;
+ bool level;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ level = false;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ case IRQ_TYPE_LEVEL_LOW:
+ level = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(raw_spinlock)(&irs_data->spi_config_lock);
+
+ selr = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, d->hwirq);
+ irs_writel_relaxed(irs_data, selr, GICV5_IRS_SPI_SELR);
+ if (gicv5_irs_wait_for_spi_op(irs_data))
+ return -EIO;
+
+ cfgr = FIELD_PREP(GICV5_IRS_SPI_CFGR_TM, level);
+
+ irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_SPI_CFGR);
+ if (gicv5_irs_wait_for_spi_op(irs_data))
+ return -EPERM;
+
+ return 0;
+}
+
+static int gicv5_irs_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
+{
+ return gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_CR0,
+ GICV5_IRS_CR0_IDLE);
+}
+
+int gicv5_irs_register_cpu(int cpuid)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ u32 selr, cr0;
+ u16 iaffid;
+ int ret;
+
+ ret = gicv5_irs_cpu_to_iaffid(cpuid, &iaffid);
+ if (ret) {
+ pr_err("IAFFID for CPU %d has not been initialised\n", cpuid);
+ return ret;
+ }
+
+ irs_data = per_cpu(per_cpu_irs_data, cpuid);
+ if (!irs_data) {
+ pr_err("No IRS associated with CPU %u\n", cpuid);
+ return -ENXIO;
+ }
+
+ selr = FIELD_PREP(GICV5_IRS_PE_SELR_IAFFID, iaffid);
+ irs_writel_relaxed(irs_data, selr, GICV5_IRS_PE_SELR);
+
+ ret = gicv5_irs_wait_for_pe_selr(irs_data);
+ if (ret) {
+ pr_err("IAFFID 0x%x used in IRS_PE_SELR is invalid\n", iaffid);
+ return -ENXIO;
+ }
+
+ cr0 = FIELD_PREP(GICV5_IRS_PE_CR0_DPS, 0x1);
+ irs_writel_relaxed(irs_data, cr0, GICV5_IRS_PE_CR0);
+
+ ret = gicv5_irs_wait_for_pe_cr0(irs_data);
+ if (ret)
+ return ret;
+
+ pr_debug("CPU%d enabled PE IAFFID 0x%x\n", cpuid, iaffid);
+
+ return 0;
+}
+
+static int __init gicv5_irs_init_bases(struct gicv5_irs_chip_data *irs_data,
+ void __iomem *irs_base,
+ struct fwnode_handle *handle)
+{
+ struct device_node *np = to_of_node(handle);
+ u32 cr0, cr1;
+
+ irs_data->fwnode = handle;
+ irs_data->irs_base = irs_base;
+
+ if (of_property_read_bool(np, "dma-noncoherent")) {
+ /*
+ * A non-coherent IRS implies that some cache levels cannot be
+ * used coherently by the cores and GIC. Our only option is to mark
+ * memory attributes for the GIC as non-cacheable; by default,
+ * non-cacheable memory attributes imply outer-shareable
+ * shareability, the value written into IRS_CR1_SH is ignored.
+ */
+ cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_NO_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_NO_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_NO_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_NON_CACHE) |
+ FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_NON_CACHE);
+ irs_data->flags |= IRS_FLAGS_NON_COHERENT;
+ } else {
+ cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_WRITE_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_WB_CACHE) |
+ FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_WB_CACHE) |
+ FIELD_PREP(GICV5_IRS_CR1_SH, GICV5_INNER_SHARE);
+ }
+
+ irs_writel_relaxed(irs_data, cr1, GICV5_IRS_CR1);
+
+ cr0 = FIELD_PREP(GICV5_IRS_CR0_IRSEN, 0x1);
+ irs_writel_relaxed(irs_data, cr0, GICV5_IRS_CR0);
+ gicv5_irs_wait_for_idle(irs_data);
+
+ return 0;
+}
+
+static int __init gicv5_irs_of_init_affinity(struct device_node *node,
+ struct gicv5_irs_chip_data *irs_data,
+ u8 iaffid_bits)
+{
+ /*
+ * Detect IAFFID<->CPU mappings from the device tree and
+ * record IRS<->CPU topology information.
+ */
+ u16 iaffid_mask = GENMASK(iaffid_bits - 1, 0);
+ u16 *iaffids __free(kfree) = NULL;
+ int ret, i, ncpus, niaffids;
+
+ ncpus = of_property_count_elems_of_size(node, "cpus", sizeof(u32));
+ if (ncpus < 0)
+ return -EINVAL;
+
+ niaffids = of_property_count_elems_of_size(node, "arm,iaffids",
+ sizeof(u16));
+ if (niaffids != ncpus)
+ return -EINVAL;
+
+ iaffids = kcalloc(niaffids, sizeof(*iaffids), GFP_KERNEL);
+ if (!iaffids)
+ return -ENOMEM;
+
+ ret = of_property_read_u16_array(node, "arm,iaffids", iaffids, niaffids);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ncpus; i++) {
+ struct device_node *cpu_node;
+ u32 cpu_phandle;
+ int cpu;
+
+ if (of_property_read_u32_index(node, "cpus", i, &cpu_phandle))
+ continue;
+
+ cpu_node = of_find_node_by_phandle(cpu_phandle);
+ if (WARN_ON(!cpu_node))
+ continue;
+
+ cpu = of_cpu_node_to_id(cpu_node);
+ of_node_put(cpu_node);
+ if (WARN_ON(cpu < 0))
+ continue;
+
+ if (iaffids[i] & ~iaffid_mask) {
+ pr_warn("CPU %d iaffid 0x%x exceeds IRS iaffid bits\n",
+ cpu, iaffids[i]);
+ continue;
+ }
+
+ per_cpu(cpu_iaffid, cpu).iaffid = iaffids[i];
+ per_cpu(cpu_iaffid, cpu).valid = true;
+
+ // We also know that the CPU is connected to this IRS
+ per_cpu(per_cpu_irs_data, cpu) = irs_data;
+ }
+
+ return ret;
+}
+
+static void irs_setup_pri_bits(u32 idr1)
+{
+ switch (FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1)) {
+ case GICV5_IRS_IDR1_PRIORITY_BITS_1BITS:
+ gicv5_global_data.irs_pri_bits = 1;
+ break;
+ case GICV5_IRS_IDR1_PRIORITY_BITS_2BITS:
+ gicv5_global_data.irs_pri_bits = 2;
+ break;
+ case GICV5_IRS_IDR1_PRIORITY_BITS_3BITS:
+ gicv5_global_data.irs_pri_bits = 3;
+ break;
+ case GICV5_IRS_IDR1_PRIORITY_BITS_4BITS:
+ gicv5_global_data.irs_pri_bits = 4;
+ break;
+ case GICV5_IRS_IDR1_PRIORITY_BITS_5BITS:
+ gicv5_global_data.irs_pri_bits = 5;
+ break;
+ default:
+ pr_warn("Detected wrong IDR priority bits value 0x%lx\n",
+ FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1));
+ break;
+ }
+}
+
+static int __init gicv5_irs_init(struct device_node *node)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ void __iomem *irs_base;
+ u8 iaffid_bits;
+ int ret;
+ u32 idr;
+
+ irs_data = kzalloc(sizeof(*irs_data), GFP_KERNEL);
+ if (!irs_data)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&irs_data->spi_config_lock);
+
+ irs_base = of_io_request_and_map(node, 0, "IRS");
+ if (IS_ERR(irs_base)) {
+ pr_err("%pOF: unable to map GICv5 IRS registers\n", node);
+ ret = PTR_ERR(irs_base);
+ goto out_err;
+ }
+
+ gicv5_irs_init_bases(irs_data, irs_base, &node->fwnode);
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
+ iaffid_bits = FIELD_GET(GICV5_IRS_IDR1_IAFFID_BITS, idr) + 1;
+
+ ret = gicv5_irs_of_init_affinity(node, irs_data, iaffid_bits);
+ if (ret) {
+ pr_err("Failed to parse CPU IAFFIDs from the device tree!\n");
+ goto out_iomem;
+ }
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2);
+ if (WARN(!FIELD_GET(GICV5_IRS_IDR2_LPI, idr),
+ "LPI support not available - no IPIs, can't proceed\n")) {
+ ret = -ENODEV;
+ goto out_iomem;
+ }
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR7);
+ irs_data->spi_min = FIELD_GET(GICV5_IRS_IDR7_SPI_BASE, idr);
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR6);
+ irs_data->spi_range = FIELD_GET(GICV5_IRS_IDR6_SPI_IRS_RANGE, idr);
+
+ if (irs_data->spi_range) {
+ pr_info("%s detected SPI range [%u-%u]\n",
+ of_node_full_name(node),
+ irs_data->spi_min,
+ irs_data->spi_min +
+ irs_data->spi_range - 1);
+ }
+
+ /*
+ * Do the global setting only on the first IRS.
+ * Global properties (iaffid_bits, global spi count) are guaranteed to
+ * be consistent across IRSes by the architecture.
+ */
+ if (list_empty(&irs_nodes)) {
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
+ irs_setup_pri_bits(idr);
+
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR5);
+ gicv5_global_data.global_spi_count =
+ FIELD_GET(GICV5_IRS_IDR5_SPI_RANGE, idr);
+
+ gicv5_init_lpi_domain();
+
+ pr_debug("Detected %u SPIs globally\n",
+ gicv5_global_data.global_spi_count);
+ }
+
+ list_add_tail(&irs_data->entry, &irs_nodes);
+
+ return 0;
+out_iomem:
+ iounmap(irs_base);
+out_err:
+ kfree(irs_data);
+ return ret;
+}
+
+void __init gicv5_irs_remove(void)
+{
+ struct gicv5_irs_chip_data *irs_data, *tmp_data;
+
+ gicv5_free_lpi_domain();
+ gicv5_deinit_lpis();
+
+ list_for_each_entry_safe(irs_data, tmp_data, &irs_nodes, entry) {
+ iounmap(irs_data->irs_base);
+ list_del(&irs_data->entry);
+ kfree(irs_data);
+ }
+}
+
+int __init gicv5_irs_enable(void)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ int ret;
+
+ irs_data = list_first_entry_or_null(&irs_nodes,
+ struct gicv5_irs_chip_data, entry);
+ if (!irs_data)
+ return -ENODEV;
+
+ ret = gicv5_irs_init_ist(irs_data);
+ if (ret) {
+ pr_err("Failed to init IST\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int __init gicv5_irs_of_probe(struct device_node *parent)
+{
+ struct device_node *np;
+ int ret;
+
+ for_each_available_child_of_node(parent, np) {
+ if (!of_device_is_compatible(np, "arm,gic-v5-irs"))
+ continue;
+
+ ret = gicv5_irs_init(np);
+ if (ret)
+ pr_err("Failed to init IRS %s\n", np->full_name);
+ }
+
+ return list_empty(&irs_nodes) ? -ENODEV : 0;
+}
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
new file mode 100644
index 0000000000000000000000000000000000000000..c4d4e85382f672fa4ae334db1a4e4c7c4f46b9fe
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -0,0 +1,1058 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) "GICv5: " fmt
+
+#include <linux/cpuhotplug.h>
+#include <linux/idr.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.h>
+#include <linux/slab.h>
+#include <linux/wordpart.h>
+
+#include <asm/cpufeature.h>
+#include <asm/exception.h>
+
+#include "irq-gic-v5.h"
+
+static u8 pri_bits = 5;
+#define GICV5_IRQ_PRI_MASK 0x1f
+#define GICV5_IRQ_PRI_MI \
+ (GICV5_IRQ_PRI_MASK & GENMASK(4, 5 - pri_bits))
+
+static bool gicv5_cpuif_has_gcie(void)
+{
+ return this_cpu_has_cap(ARM64_HAS_GICV5_CPUIF);
+}
+
+struct gicv5_chip_data gicv5_global_data __read_mostly;
+
+static DEFINE_IDA(lpi_ida);
+static u32 num_lpis;
+
+void __init gicv5_init_lpis(u32 lpis)
+{
+ num_lpis = lpis;
+}
+
+void __init gicv5_deinit_lpis(void)
+{
+ num_lpis = 0;
+}
+
+static int alloc_lpi(void)
+{
+ if (!num_lpis)
+ return -ENOSPC;
+
+ return ida_alloc_max(&lpi_ida, num_lpis - 1, GFP_KERNEL);
+}
+
+static void release_lpi(u32 lpi)
+{
+ ida_free(&lpi_ida, lpi);
+}
+
+static int gicv5_alloc_lpi(void)
+{
+ return alloc_lpi();
+}
+
+static void gicv5_free_lpi(u32 lpi)
+{
+ release_lpi(lpi);
+}
+
+static void gicv5_ppi_priority_init(void)
+{
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR0_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR1_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR2_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR3_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR4_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR5_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR6_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR7_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR8_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR9_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR10_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR11_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR12_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR13_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR14_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR15_EL1);
+
+ /*
+ * Context syncronization required to mare system
+ * register writes effects are synchronized
+ */
+ isb();
+}
+
+static void gicv5_hwirq_init(irq_hw_number_t hwirq, u8 priority, u8 hwirq_type)
+{
+ u64 cdpri, cdaff;
+ u16 iaffid;
+ int ret;
+
+ if (hwirq_type == GICV5_HWIRQ_TYPE_LPI || hwirq_type == GICV5_HWIRQ_TYPE_SPI) {
+ cdpri = FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK, priority) |
+ FIELD_PREP(GICV5_GIC_CDPRI_TYPE_MASK, hwirq_type) |
+ FIELD_PREP(GICV5_GIC_CDPRI_ID_MASK, hwirq);
+ gic_insn(cdpri, GICV5_OP_GIC_CDPRI);
+
+ ret = gicv5_irs_cpu_to_iaffid(smp_processor_id(), &iaffid);
+
+ if (WARN_ON_ONCE(ret))
+ return;
+
+ cdaff = FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, iaffid) |
+ FIELD_PREP(GICV5_GIC_CDAFF_TYPE_MASK, hwirq_type) |
+ FIELD_PREP(GICV5_GIC_CDAFF_ID_MASK, hwirq);
+ gic_insn(cdaff, GICV5_OP_GIC_CDAFF);
+ }
+}
+
+static void gicv5_ppi_irq_mask(struct irq_data *d)
+{
+ u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
+
+ if (d->hwirq < 64)
+ sysreg_clear_set_s(SYS_ICC_PPI_ENABLER0_EL1, hwirq_id_bit, 0);
+ else
+ sysreg_clear_set_s(SYS_ICC_PPI_ENABLER1_EL1, hwirq_id_bit, 0);
+
+ /*
+ * Ensure that the disable takes effect
+ */
+ isb();
+}
+
+static void gicv5_iri_irq_mask(struct irq_data *d, u8 hwirq_type)
+{
+ u64 cddis = d->hwirq | FIELD_PREP(GICV5_GIC_CDDIS_TYPE_MASK, hwirq_type);
+
+ gic_insn(cddis, GICV5_OP_GIC_CDDIS);
+ /*
+ * We must make sure that GIC CDDIS write effects are propagated
+ */
+ gsb_sys();
+}
+
+static void gicv5_spi_irq_mask(struct irq_data *d)
+{
+ gicv5_iri_irq_mask(d, GICV5_HWIRQ_TYPE_SPI);
+}
+
+static void gicv5_lpi_irq_mask(struct irq_data *d)
+{
+ gicv5_iri_irq_mask(d, GICV5_HWIRQ_TYPE_LPI);
+}
+
+static void gicv5_ppi_irq_unmask(struct irq_data *d)
+{
+ u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
+
+ if (d->hwirq < 64)
+ sysreg_clear_set_s(SYS_ICC_PPI_ENABLER0_EL1, 0, hwirq_id_bit);
+ else
+ sysreg_clear_set_s(SYS_ICC_PPI_ENABLER1_EL1, 0, hwirq_id_bit);
+}
+
+static void gicv5_iri_irq_unmask(struct irq_data *d, u8 hwirq_type)
+{
+ u64 cden = d->hwirq | FIELD_PREP(GICV5_GIC_CDEN_TYPE_MASK, hwirq_type);
+
+ gic_insn(cden, GICV5_OP_GIC_CDEN);
+}
+
+static void gicv5_spi_irq_unmask(struct irq_data *d)
+{
+ gicv5_iri_irq_unmask(d, GICV5_HWIRQ_TYPE_SPI);
+}
+
+static void gicv5_lpi_irq_unmask(struct irq_data *d)
+{
+ gicv5_iri_irq_unmask(d, GICV5_HWIRQ_TYPE_LPI);
+}
+
+static void gicv5_hwirq_eoi(u32 hwirq_id, u8 hwirq_type)
+{
+ u64 cddi = hwirq_id | FIELD_PREP(GICV5_GIC_CDDI_TYPE_MASK, hwirq_type);
+
+ gic_insn(cddi, GICV5_OP_GIC_CDDI);
+
+ gic_insn(0, GICV5_OP_GIC_CDEOI);
+}
+
+static void gicv5_ppi_irq_eoi(struct irq_data *d)
+{
+ gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_PPI);
+}
+
+static void gicv5_spi_irq_eoi(struct irq_data *d)
+{
+ gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_SPI);
+}
+
+static void gicv5_lpi_irq_eoi(struct irq_data *d)
+{
+ gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_LPI);
+}
+
+static int gicv5_iri_irq_set_affinity(struct irq_data *d,
+ const struct cpumask *mask_val,
+ bool force, u8 hwirq_type)
+{
+ int ret, cpuid;
+ u16 iaffid;
+ u64 cdaff;
+
+ if (force)
+ cpuid = cpumask_first(mask_val);
+ else
+ cpuid = cpumask_any_and(mask_val, cpu_online_mask);
+
+ ret = gicv5_irs_cpu_to_iaffid(cpuid, &iaffid);
+ if (ret)
+ return ret;
+
+ cdaff = FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, iaffid) |
+ FIELD_PREP(GICV5_GIC_CDAFF_TYPE_MASK, hwirq_type) |
+ FIELD_PREP(GICV5_GIC_CDAFF_ID_MASK, d->hwirq);
+ gic_insn(cdaff, GICV5_OP_GIC_CDAFF);
+
+ irq_data_update_effective_affinity(d, cpumask_of(cpuid));
+
+ return IRQ_SET_MASK_OK_DONE;
+}
+
+static int gicv5_spi_irq_set_affinity(struct irq_data *d,
+ const struct cpumask *mask_val,
+ bool force)
+{
+ return gicv5_iri_irq_set_affinity(d, mask_val, force,
+ GICV5_HWIRQ_TYPE_SPI);
+}
+
+static int gicv5_lpi_irq_set_affinity(struct irq_data *d,
+ const struct cpumask *mask_val,
+ bool force)
+{
+ return gicv5_iri_irq_set_affinity(d, mask_val, force,
+ GICV5_HWIRQ_TYPE_LPI);
+}
+
+#define READ_PPI_REG(irq, reg) \
+ ({ \
+ u64 val; \
+ \
+ if (irq < 64) \
+ val = read_sysreg_s(SYS_ICC_PPI_##reg##R0_EL1); \
+ else \
+ val = read_sysreg_s(SYS_ICC_PPI_##reg##R1_EL1); \
+ val; \
+ })
+
+#define WRITE_PPI_REG(set, irq, bit, reg) \
+ do { \
+ if (set) { \
+ if (irq < 64) \
+ write_sysreg_s(bit, SYS_ICC_PPI_S##reg##R0_EL1);\
+ else \
+ write_sysreg_s(bit, SYS_ICC_PPI_S##reg##R1_EL1);\
+ } else { \
+ if (irq < 64) \
+ write_sysreg_s(bit, SYS_ICC_PPI_C##reg##R0_EL1);\
+ else \
+ write_sysreg_s(bit, SYS_ICC_PPI_C##reg##R1_EL1);\
+ } \
+ } while (0)
+
+static int gicv5_ppi_set_type(struct irq_data *d, unsigned int type)
+{
+ /*
+ * The PPI trigger mode is not configurable at runtime,
+ * therefore this function simply confirms that the `type`
+ * parameter matches what is present.
+ */
+ u64 hmr = READ_PPI_REG(d->hwirq, HM);
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ case IRQ_TYPE_LEVEL_LOW:
+ if (((hmr >> (d->hwirq % 64)) & 0x1) != GICV5_PPI_HM_LEVEL)
+ return -EINVAL;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ if (((hmr >> (d->hwirq % 64)) & 0x1) != GICV5_PPI_HM_EDGE)
+ return -EINVAL;
+ break;
+ default:
+ pr_debug("Unexpected PPI trigger mode");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gicv5_ppi_irq_get_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool *val)
+{
+ u64 pendr, activer, enabler, hwirq_id_bit = BIT_ULL(d->hwirq % 64);
+
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ pendr = READ_PPI_REG(d->hwirq, SPEND);
+
+ *val = !!(pendr & hwirq_id_bit);
+
+ return 0;
+ case IRQCHIP_STATE_ACTIVE:
+ activer = READ_PPI_REG(d->hwirq, SACTIVE);
+
+ *val = !!(activer & hwirq_id_bit);
+
+ return 0;
+ case IRQCHIP_STATE_MASKED:
+ enabler = READ_PPI_REG(d->hwirq, ENABLE);
+
+ *val = !(enabler & hwirq_id_bit);
+
+ return 0;
+ default:
+ pr_debug("Unexpected PPI irqchip state\n");
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int gicv5_iri_irq_get_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool *val, u8 hwirq_type)
+{
+ u64 icsr, cdrcfg;
+
+ cdrcfg = d->hwirq | FIELD_PREP(GICV5_GIC_CDRCFG_TYPE_MASK, hwirq_type);
+
+ gic_insn(cdrcfg, GICV5_OP_GIC_CDRCFG);
+ isb();
+ icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
+
+ if (FIELD_GET(ICC_ICSR_EL1_F, icsr)) {
+ pr_err("ICSR_EL1 is invalid\n");
+ return -EINVAL;
+ }
+
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ *val = !!(FIELD_GET(ICC_ICSR_EL1_Pending, icsr));
+ return 0;
+
+ case IRQCHIP_STATE_ACTIVE:
+ *val = !!(FIELD_GET(ICC_ICSR_EL1_Active, icsr));
+ return 0;
+
+ case IRQCHIP_STATE_MASKED:
+ *val = !(FIELD_GET(ICC_ICSR_EL1_Enabled, icsr));
+ return 0;
+
+ default:
+ pr_debug("Unexpected irqchip_irq_state\n");
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int gicv5_spi_irq_get_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool *val)
+{
+ return gicv5_iri_irq_get_irqchip_state(d, which, val,
+ GICV5_HWIRQ_TYPE_SPI);
+}
+
+static int gicv5_lpi_irq_get_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool *val)
+{
+ return gicv5_iri_irq_get_irqchip_state(d, which, val,
+ GICV5_HWIRQ_TYPE_LPI);
+}
+
+static int gicv5_ppi_irq_set_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool val)
+{
+ u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
+
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ WRITE_PPI_REG(val, d->hwirq, hwirq_id_bit, PEND);
+ return 0;
+ case IRQCHIP_STATE_ACTIVE:
+ WRITE_PPI_REG(val, d->hwirq, hwirq_id_bit, ACTIVE);
+ return 0;
+ case IRQCHIP_STATE_MASKED:
+ if (val)
+ gicv5_ppi_irq_mask(d);
+ else
+ gicv5_ppi_irq_unmask(d);
+ return 0;
+ default:
+ pr_debug("Unexpected PPI irqchip state\n");
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static void gicv5_iri_irq_write_pending_state(struct irq_data *d, bool val,
+ u8 hwirq_type)
+{
+ u64 cdpend;
+
+ cdpend = FIELD_PREP(GICV5_GIC_CDPEND_TYPE_MASK, hwirq_type) |
+ FIELD_PREP(GICV5_GIC_CDPEND_ID_MASK, d->hwirq) |
+ FIELD_PREP(GICV5_GIC_CDPEND_PENDING_MASK, val);
+
+ gic_insn(cdpend, GICV5_OP_GIC_CDPEND);
+}
+
+static void gicv5_spi_irq_write_pending_state(struct irq_data *d, bool val)
+{
+ gicv5_iri_irq_write_pending_state(d, val, GICV5_HWIRQ_TYPE_SPI);
+}
+
+static void gicv5_lpi_irq_write_pending_state(struct irq_data *d, bool val)
+{
+ gicv5_iri_irq_write_pending_state(d, val, GICV5_HWIRQ_TYPE_LPI);
+}
+
+static int gicv5_spi_irq_set_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool val)
+{
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ gicv5_spi_irq_write_pending_state(d, val);
+ break;
+ case IRQCHIP_STATE_MASKED:
+ if (val)
+ gicv5_spi_irq_mask(d);
+ else
+ gicv5_spi_irq_unmask(d);
+ break;
+ default:
+ pr_debug("Unexpected irqchip_irq_state\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gicv5_lpi_irq_set_irqchip_state(struct irq_data *d,
+ enum irqchip_irq_state which,
+ bool val)
+{
+ switch (which) {
+ case IRQCHIP_STATE_PENDING:
+ gicv5_lpi_irq_write_pending_state(d, val);
+ break;
+ case IRQCHIP_STATE_MASKED:
+ if (val)
+ gicv5_lpi_irq_mask(d);
+ else
+ gicv5_lpi_irq_unmask(d);
+ break;
+
+ default:
+ pr_debug("Unexpected irqchip_irq_state\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int gicv5_spi_irq_retrigger(struct irq_data *data)
+{
+ return !gicv5_spi_irq_set_irqchip_state(data, IRQCHIP_STATE_PENDING,
+ true);
+}
+
+static int gicv5_lpi_irq_retrigger(struct irq_data *data)
+{
+ return !gicv5_lpi_irq_set_irqchip_state(data, IRQCHIP_STATE_PENDING,
+ true);
+}
+
+static void gicv5_ipi_send_single(struct irq_data *d, unsigned int cpu)
+{
+ /* Mark the LPI pending */
+ irq_chip_retrigger_hierarchy(d);
+}
+
+static const struct irq_chip gicv5_ppi_irq_chip = {
+ .name = "GICv5-PPI",
+ .irq_mask = gicv5_ppi_irq_mask,
+ .irq_unmask = gicv5_ppi_irq_unmask,
+ .irq_eoi = gicv5_ppi_irq_eoi,
+ .irq_set_type = gicv5_ppi_set_type,
+ .irq_get_irqchip_state = gicv5_ppi_irq_get_irqchip_state,
+ .irq_set_irqchip_state = gicv5_ppi_irq_set_irqchip_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND
+};
+
+static const struct irq_chip gicv5_spi_irq_chip = {
+ .name = "GICv5-SPI",
+ .irq_mask = gicv5_spi_irq_mask,
+ .irq_unmask = gicv5_spi_irq_unmask,
+ .irq_eoi = gicv5_spi_irq_eoi,
+ .irq_set_type = gicv5_spi_irq_set_type,
+ .irq_set_affinity = gicv5_spi_irq_set_affinity,
+ .irq_retrigger = gicv5_spi_irq_retrigger,
+ .irq_get_irqchip_state = gicv5_spi_irq_get_irqchip_state,
+ .irq_set_irqchip_state = gicv5_spi_irq_set_irqchip_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND
+};
+
+static int gicv5_irq_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ irq_hw_number_t *hwirq,
+ unsigned int *type,
+ u8 hwirq_type)
+{
+ if (!is_of_node(fwspec->fwnode))
+ return -EINVAL;
+
+ if (fwspec->param_count < 3)
+ return -EINVAL;
+
+ if (fwspec->param[0] != hwirq_type)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[1];
+ *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
+
+ return 0;
+}
+
+static int gicv5_irq_ppi_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ irq_hw_number_t *hwirq,
+ unsigned int *type)
+{
+ return gicv5_irq_domain_translate(d, fwspec, hwirq, type,
+ GICV5_HWIRQ_TYPE_PPI);
+}
+
+static int gicv5_irq_ppi_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ unsigned int type = IRQ_TYPE_NONE;
+ struct irq_fwspec *fwspec = arg;
+ irq_hw_number_t hwirq;
+ int ret;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return -EINVAL;
+
+ ret = gicv5_irq_ppi_domain_translate(domain, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ irq_set_percpu_devid(virq);
+ irq_domain_set_info(domain, virq, hwirq, &gicv5_ppi_irq_chip, NULL,
+ handle_percpu_devid_irq, NULL, NULL);
+
+ return 0;
+}
+
+static void gicv5_irq_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return;
+
+ d = irq_domain_get_irq_data(domain, virq);
+
+ irq_set_handler(virq, NULL);
+ irq_domain_reset_irq_data(d);
+}
+
+static int gicv5_irq_ppi_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
+ enum irq_domain_bus_token bus_token)
+{
+ if (fwspec->fwnode != d->fwnode)
+ return 0;
+
+ if (fwspec->param[0] != GICV5_HWIRQ_TYPE_PPI)
+ return 0;
+
+ return (d == gicv5_global_data.ppi_domain);
+}
+
+static const struct irq_domain_ops gicv5_irq_ppi_domain_ops = {
+ .translate = gicv5_irq_ppi_domain_translate,
+ .alloc = gicv5_irq_ppi_domain_alloc,
+ .free = gicv5_irq_domain_free,
+ .select = gicv5_irq_ppi_domain_select
+};
+
+static int gicv5_irq_spi_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ irq_hw_number_t *hwirq,
+ unsigned int *type)
+{
+ return gicv5_irq_domain_translate(d, fwspec, hwirq, type,
+ GICV5_HWIRQ_TYPE_SPI);
+}
+
+static int gicv5_irq_spi_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs,
+ void *arg)
+{
+ struct gicv5_irs_chip_data *chip_data;
+ unsigned int type = IRQ_TYPE_NONE;
+ struct irq_fwspec *fwspec = arg;
+ struct irq_data *irqd;
+ irq_hw_number_t hwirq;
+ int ret;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return -EINVAL;
+
+ ret = gicv5_irq_spi_domain_translate(domain, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ irqd = irq_desc_get_irq_data(irq_to_desc(virq));
+ chip_data = gicv5_irs_lookup_by_spi_id(hwirq);
+
+ irq_domain_set_info(domain, virq, hwirq, &gicv5_spi_irq_chip, chip_data,
+ handle_fasteoi_irq, NULL, NULL);
+ irq_set_probe(virq);
+ irqd_set_single_target(irqd);
+
+ gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_SPI);
+
+ return 0;
+}
+
+static int gicv5_irq_spi_domain_select(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ enum irq_domain_bus_token bus_token)
+{
+ if (fwspec->fwnode != d->fwnode)
+ return 0;
+
+ if (fwspec->param[0] != GICV5_HWIRQ_TYPE_SPI)
+ return 0;
+
+ return (d == gicv5_global_data.spi_domain);
+}
+
+static const struct irq_domain_ops gicv5_irq_spi_domain_ops = {
+ .translate = gicv5_irq_spi_domain_translate,
+ .alloc = gicv5_irq_spi_domain_alloc,
+ .free = gicv5_irq_domain_free,
+ .select = gicv5_irq_spi_domain_select
+};
+
+static const struct irq_chip gicv5_lpi_irq_chip = {
+ .name = "GICv5-LPI",
+ .irq_mask = gicv5_lpi_irq_mask,
+ .irq_unmask = gicv5_lpi_irq_unmask,
+ .irq_eoi = gicv5_lpi_irq_eoi,
+ .irq_set_affinity = gicv5_lpi_irq_set_affinity,
+ .irq_retrigger = gicv5_lpi_irq_retrigger,
+ .irq_get_irqchip_state = gicv5_lpi_irq_get_irqchip_state,
+ .irq_set_irqchip_state = gicv5_lpi_irq_set_irqchip_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND
+};
+
+static const struct irq_chip gicv5_ipi_irq_chip = {
+ .name = "GICv5-IPI",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ // We only handle this one in the IPI domain - the rest go to parent
+ .ipi_send_single = gicv5_ipi_send_single,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND
+};
+
+static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ irq_hw_number_t hwirq;
+ struct irq_data *irqd;
+ u32 *lpi = arg;
+ int ret;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return -EINVAL;
+
+ hwirq = *lpi;
+
+ irqd = irq_domain_get_irq_data(domain, virq);
+
+ irq_domain_set_info(domain, virq, hwirq, &gicv5_lpi_irq_chip, NULL,
+ handle_fasteoi_irq, NULL, NULL);
+ irqd_set_single_target(irqd);
+
+ ret = gicv5_irs_iste_alloc(hwirq);
+ if (ret < 0)
+ return ret;
+
+ gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_LPI);
+
+ return 0;
+}
+
+static void gicv5_irq_lpi_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d;
+ int i;
+
+ WARN_ON_ONCE(nr_irqs != 1);
+
+ for (i = 0; i < nr_irqs; i++) {
+ d = irq_domain_get_irq_data(domain, virq + i);
+
+ irq_set_handler(virq + i, NULL);
+ irq_domain_reset_irq_data(d);
+ }
+}
+
+static const struct irq_domain_ops gicv5_irq_lpi_domain_ops = {
+ .alloc = gicv5_irq_lpi_domain_alloc,
+ .free = gicv5_irq_lpi_domain_free,
+};
+
+void __init gicv5_init_lpi_domain(void)
+{
+ struct irq_domain *d;
+
+ d = irq_domain_create_tree(NULL, &gicv5_irq_lpi_domain_ops, NULL);
+ gicv5_global_data.lpi_domain = d;
+}
+
+void __init gicv5_free_lpi_domain(void)
+{
+ irq_domain_remove(gicv5_global_data.lpi_domain);
+}
+
+static int gicv5_irq_ipi_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct irq_data *irqd;
+ int ret, i;
+ u32 lpi;
+
+ for (i = 0; i < nr_irqs; i++) {
+ ret = gicv5_alloc_lpi();
+ if (ret < 0)
+ return ret;
+
+ lpi = ret;
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
+ if (ret) {
+ gicv5_free_lpi(lpi);
+ return ret;
+ }
+
+ irqd = irq_domain_get_irq_data(domain, virq + i);
+
+ irq_domain_set_hwirq_and_chip(domain, virq + i, i,
+ &gicv5_ipi_irq_chip, NULL);
+
+ irqd_set_single_target(irqd);
+
+ irq_set_handler(virq + i, handle_percpu_irq);
+ }
+
+ return 0;
+}
+
+static void gicv5_irq_ipi_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d;
+ unsigned int i;
+
+ for (i = 0; i < nr_irqs; i++) {
+ d = irq_domain_get_irq_data(domain, virq + i);
+
+ if (!d)
+ return;
+
+ gicv5_free_lpi(d->parent_data->hwirq);
+
+ irq_set_handler(virq + i, NULL);
+ irq_domain_reset_irq_data(d);
+ irq_domain_free_irqs_parent(domain, virq + i, 1);
+ }
+}
+
+static const struct irq_domain_ops gicv5_irq_ipi_domain_ops = {
+ .alloc = gicv5_irq_ipi_domain_alloc,
+ .free = gicv5_irq_ipi_domain_free,
+};
+
+static inline void handle_irq_per_domain(u32 hwirq)
+{
+ u8 hwirq_type = FIELD_GET(GICV5_HWIRQ_TYPE, hwirq);
+ u32 hwirq_id = FIELD_GET(GICV5_HWIRQ_ID, hwirq);
+ struct irq_domain *domain = NULL;
+
+ if (hwirq_type == GICV5_HWIRQ_TYPE_PPI)
+ domain = gicv5_global_data.ppi_domain;
+ else if (hwirq_type == GICV5_HWIRQ_TYPE_SPI)
+ domain = gicv5_global_data.spi_domain;
+ else if (hwirq_type == GICV5_HWIRQ_TYPE_LPI)
+ domain = gicv5_global_data.lpi_domain;
+
+ if (generic_handle_domain_irq(domain, hwirq_id)) {
+ pr_err_once("Could not handle, hwirq = 0x%x", hwirq_id);
+ gicv5_hwirq_eoi(hwirq_id, hwirq_type);
+ }
+}
+
+static void __exception_irq_entry gicv5_handle_irq(struct pt_regs *regs)
+{
+ bool valid;
+ u32 hwirq;
+ u64 ia;
+
+ ia = gicr_insn(GICV5_OP_GICR_CDIA);
+ valid = GICV5_GIC_CDIA_VALID(ia);
+
+ if (!valid)
+ return;
+
+ /*
+ * Ensure that the CDIA instruction effects (ie IRQ activation) are
+ * completed before handling the interrupt.
+ */
+ gsb_ack();
+
+ /*
+ * Ensure instruction ordering between an acknowledgment and subsequent
+ * instructions in the IRQ handler using an ISB.
+ */
+ isb();
+
+ hwirq = FIELD_GET(GICV5_HWIRQ_INTID, ia);
+
+ handle_irq_per_domain(hwirq);
+}
+
+static void gicv5_cpu_disable_interrupts(void)
+{
+ u64 cr0;
+
+ cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0);
+ write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
+}
+
+static void gicv5_cpu_enable_interrupts(void)
+{
+ u64 cr0, pcr;
+
+ write_sysreg_s(0, SYS_ICC_PPI_ENABLER0_EL1);
+ write_sysreg_s(0, SYS_ICC_PPI_ENABLER1_EL1);
+
+ gicv5_ppi_priority_init();
+
+ pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, GICV5_IRQ_PRI_MI);
+ write_sysreg_s(pcr, SYS_ICC_PCR_EL1);
+
+ cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 1);
+ write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
+}
+
+static int base_ipi_virq;
+
+static int gicv5_starting_cpu(unsigned int cpu)
+{
+ if (WARN(!gicv5_cpuif_has_gcie(),
+ "GICv5 system components present but CPU does not have FEAT_GCIE"))
+ return -ENODEV;
+
+ gicv5_cpu_enable_interrupts();
+
+ return gicv5_irs_register_cpu(cpu);
+}
+
+static void __init gicv5_smp_init(void)
+{
+ unsigned int num_ipis = GICV5_IPIS_PER_CPU * nr_cpu_ids;
+
+ cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
+ "irqchip/arm/gicv5:starting",
+ gicv5_starting_cpu, NULL);
+
+ base_ipi_virq = irq_domain_alloc_irqs(gicv5_global_data.ipi_domain,
+ num_ipis, NUMA_NO_NODE, NULL);
+ if (WARN(base_ipi_virq <= 0, "IPI IRQ allocation was not successful"))
+ return;
+
+ set_smp_ipi_range_percpu(base_ipi_virq, GICV5_IPIS_PER_CPU, nr_cpu_ids);
+}
+
+static void __init gicv5_free_domains(void)
+{
+ if (gicv5_global_data.ppi_domain)
+ irq_domain_remove(gicv5_global_data.ppi_domain);
+ if (gicv5_global_data.spi_domain)
+ irq_domain_remove(gicv5_global_data.spi_domain);
+ if (gicv5_global_data.ipi_domain)
+ irq_domain_remove(gicv5_global_data.ipi_domain);
+}
+
+static int __init gicv5_init_domains(struct fwnode_handle *handle)
+{
+ u32 spi_count = gicv5_global_data.global_spi_count;
+ struct irq_domain *d;
+
+ d = irq_domain_create_linear(handle, 128, &gicv5_irq_ppi_domain_ops,
+ NULL);
+ if (!d)
+ return -ENOMEM;
+
+ irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED);
+ gicv5_global_data.ppi_domain = d;
+
+ if (spi_count) {
+ d = irq_domain_create_linear(handle, spi_count,
+ &gicv5_irq_spi_domain_ops, NULL);
+
+ if (!d) {
+ gicv5_free_domains();
+ return -ENOMEM;
+ }
+
+ gicv5_global_data.spi_domain = d;
+ irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED);
+ }
+
+ if (!WARN(!gicv5_global_data.lpi_domain,
+ "LPI domain uninitialized, can't set up IPIs")) {
+ d = irq_domain_create_hierarchy(gicv5_global_data.lpi_domain,
+ 0, GICV5_IPIS_PER_CPU * nr_cpu_ids,
+ NULL, &gicv5_irq_ipi_domain_ops,
+ NULL);
+
+ if (!d) {
+ gicv5_free_domains();
+ return -ENOMEM;
+ }
+ gicv5_global_data.ipi_domain = d;
+ }
+ gicv5_global_data.fwnode = handle;
+
+ return 0;
+}
+
+static void gicv5_set_cpuif_pribits(void)
+{
+ u64 icc_idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
+
+ switch (FIELD_GET(ICC_IDR0_EL1_PRI_BITS, icc_idr0)) {
+ case ICC_IDR0_EL1_PRI_BITS_4BITS:
+ gicv5_global_data.cpuif_pri_bits = 4;
+ break;
+ case ICC_IDR0_EL1_PRI_BITS_5BITS:
+ gicv5_global_data.cpuif_pri_bits = 5;
+ break;
+ default:
+ pr_err("Unexpected ICC_IDR0_EL1_PRI_BITS value, default to 4");
+ gicv5_global_data.cpuif_pri_bits = 4;
+ break;
+ }
+}
+
+static void gicv5_set_cpuif_idbits(void)
+{
+ u32 icc_idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
+
+ switch (FIELD_GET(ICC_IDR0_EL1_ID_BITS, icc_idr0)) {
+ case ICC_IDR0_EL1_ID_BITS_16BITS:
+ gicv5_global_data.cpuif_id_bits = 16;
+ break;
+ case ICC_IDR0_EL1_ID_BITS_24BITS:
+ gicv5_global_data.cpuif_id_bits = 24;
+ break;
+ default:
+ pr_err("Unexpected ICC_IDR0_EL1_ID_BITS value, default to 16");
+ gicv5_global_data.cpuif_id_bits = 16;
+ break;
+ }
+}
+
+
+static int __init gicv5_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ int ret;
+
+ ret = gicv5_irs_of_probe(node);
+ if (ret)
+ return ret;
+
+ ret = gicv5_init_domains(&node->fwnode);
+ if (ret)
+ goto out_irs;
+
+ gicv5_set_cpuif_pribits();
+ gicv5_set_cpuif_idbits();
+
+ pri_bits = min_not_zero(gicv5_global_data.cpuif_pri_bits,
+ gicv5_global_data.irs_pri_bits);
+
+ ret = gicv5_starting_cpu(smp_processor_id());
+ if (ret)
+ goto out_dom;
+
+ ret = set_handle_irq(gicv5_handle_irq);
+ if (ret)
+ goto out_int;
+
+ ret = gicv5_irs_enable();
+ if (ret)
+ goto out_int;
+
+ gicv5_smp_init();
+
+ return 0;
+out_int:
+ gicv5_cpu_disable_interrupts();
+out_dom:
+ gicv5_free_domains();
+out_irs:
+ gicv5_irs_remove();
+
+ return ret;
+}
+IRQCHIP_DECLARE(gic_v5, "arm,gic-v5", gicv5_of_init);
diff --git a/drivers/irqchip/irq-gic-v5.h b/drivers/irqchip/irq-gic-v5.h
new file mode 100644
index 0000000000000000000000000000000000000000..19569639153a084760c3b5b7f0fa84791ba0195c
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 ARM Limited, All Rights Reserved.
+ */
+#ifndef __LINUX_IRQCHIP_GIC_V5_H
+#define __LINUX_IRQCHIP_GIC_V5_H
+
+#include <asm/arch_gicv5.h>
+#include <asm/smp.h>
+
+#define GICV5_IPIS_PER_CPU MAX_IPI
+
+#define GICV5_HWIRQ_ID GENMASK(23, 0)
+#define GICV5_HWIRQ_TYPE GENMASK(31, 29)
+#define GICV5_HWIRQ_INTID GENMASK_ULL(31, 0)
+
+#define GICV5_HWIRQ_TYPE_PPI UL(0x1)
+#define GICV5_HWIRQ_TYPE_LPI UL(0x2)
+#define GICV5_HWIRQ_TYPE_SPI UL(0x3)
+
+#define GICV5_PPI_HM_EDGE UL(0x0)
+#define GICV5_PPI_HM_LEVEL UL(0x1)
+
+#define GICV5_NO_READ_ALLOC 0b0
+#define GICV5_READ_ALLOC 0b1
+#define GICV5_NO_WRITE_ALLOC 0b0
+#define GICV5_WRITE_ALLOC 0b1
+
+#define GICV5_NON_CACHE 0b00
+#define GICV5_WB_CACHE 0b01
+#define GICV5_WT_CACHE 0b10
+
+#define GICV5_NON_SHARE 0b00
+#define GICV5_OUTER_SHARE 0b10
+#define GICV5_INNER_SHARE 0b11
+
+#define GICV5_IRS_IDR1 0x0004
+#define GICV5_IRS_IDR2 0x0008
+#define GICV5_IRS_IDR5 0x0014
+#define GICV5_IRS_IDR6 0x0018
+#define GICV5_IRS_IDR7 0x001c
+#define GICV5_IRS_CR0 0x0080
+#define GICV5_IRS_CR1 0x0084
+#define GICV5_IRS_SPI_SELR 0x0108
+#define GICV5_IRS_SPI_CFGR 0x0114
+#define GICV5_IRS_SPI_STATUSR 0x0118
+#define GICV5_IRS_PE_SELR 0x0140
+#define GICV5_IRS_PE_STATUSR 0x0144
+#define GICV5_IRS_PE_CR0 0x0148
+#define GICV5_IRS_IST_BASER 0x0180
+#define GICV5_IRS_IST_CFGR 0x0190
+#define GICV5_IRS_IST_STATUSR 0x0194
+#define GICV5_IRS_MAP_L2_ISTR 0x01c0
+
+#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
+#define GICV5_IRS_IDR1_IAFFID_BITS GENMASK(19, 16)
+
+#define GICV5_IRS_IDR1_PRIORITY_BITS_1BITS 0b000
+#define GICV5_IRS_IDR1_PRIORITY_BITS_2BITS 0b001
+#define GICV5_IRS_IDR1_PRIORITY_BITS_3BITS 0b010
+#define GICV5_IRS_IDR1_PRIORITY_BITS_4BITS 0b011
+#define GICV5_IRS_IDR1_PRIORITY_BITS_5BITS 0b100
+
+#define GICV5_IRS_IDR2_ISTMD_SZ GENMASK(19, 15)
+#define GICV5_IRS_IDR2_ISTMD BIT(14)
+#define GICV5_IRS_IDR2_IST_L2SZ GENMASK(13, 11)
+#define GICV5_IRS_IDR2_IST_LEVELS BIT(10)
+#define GICV5_IRS_IDR2_MIN_LPI_ID_BITS GENMASK(9, 6)
+#define GICV5_IRS_IDR2_LPI BIT(5)
+#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
+
+#define GICV5_IRS_IDR5_SPI_RANGE GENMASK(24, 0)
+#define GICV5_IRS_IDR6_SPI_IRS_RANGE GENMASK(24, 0)
+#define GICV5_IRS_IDR7_SPI_BASE GENMASK(23, 0)
+
+#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
+
+#define GICV5_IRS_CR0_IDLE BIT(1)
+#define GICV5_IRS_CR0_IRSEN BIT(0)
+
+#define GICV5_IRS_CR1_VPED_WA BIT(15)
+#define GICV5_IRS_CR1_VPED_RA BIT(14)
+#define GICV5_IRS_CR1_VMD_WA BIT(13)
+#define GICV5_IRS_CR1_VMD_RA BIT(12)
+#define GICV5_IRS_CR1_VPET_WA BIT(11)
+#define GICV5_IRS_CR1_VPET_RA BIT(10)
+#define GICV5_IRS_CR1_VMT_WA BIT(9)
+#define GICV5_IRS_CR1_VMT_RA BIT(8)
+#define GICV5_IRS_CR1_IST_WA BIT(7)
+#define GICV5_IRS_CR1_IST_RA BIT(6)
+#define GICV5_IRS_CR1_IC GENMASK(5, 4)
+#define GICV5_IRS_CR1_OC GENMASK(3, 2)
+#define GICV5_IRS_CR1_SH GENMASK(1, 0)
+
+#define GICV5_IRS_SPI_STATUSR_V BIT(1)
+#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_SPI_SELR_ID GENMASK(23, 0)
+
+#define GICV5_IRS_SPI_CFGR_TM BIT(0)
+
+#define GICV5_IRS_PE_SELR_IAFFID GENMASK(15, 0)
+
+#define GICV5_IRS_PE_STATUSR_V BIT(1)
+#define GICV5_IRS_PE_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_PE_CR0_DPS BIT(0)
+
+#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_IST_CFGR_STRUCTURE BIT(16)
+#define GICV5_IRS_IST_CFGR_ISTSZ GENMASK(8, 7)
+#define GICV5_IRS_IST_CFGR_L2SZ GENMASK(6, 5)
+#define GICV5_IRS_IST_CFGR_LPI_ID_BITS GENMASK(4, 0)
+
+#define GICV5_IRS_IST_CFGR_STRUCTURE_LINEAR 0b0
+#define GICV5_IRS_IST_CFGR_STRUCTURE_TWO_LEVEL 0b1
+
+#define GICV5_IRS_IST_CFGR_ISTSZ_4 0b00
+#define GICV5_IRS_IST_CFGR_ISTSZ_8 0b01
+#define GICV5_IRS_IST_CFGR_ISTSZ_16 0b10
+
+#define GICV5_IRS_IST_CFGR_L2SZ_4K 0b00
+#define GICV5_IRS_IST_CFGR_L2SZ_16K 0b01
+#define GICV5_IRS_IST_CFGR_L2SZ_64K 0b10
+
+#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
+#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
+
+#define GICV5_IRS_MAP_L2_ISTR_ID GENMASK(23, 0)
+
+#define GICV5_ISTL1E_VALID BIT_ULL(0)
+
+#define GICV5_ISTL1E_L2_ADDR_MASK GENMASK_ULL(55, 12)
+
+struct gicv5_chip_data {
+ struct fwnode_handle *fwnode;
+ struct irq_domain *ppi_domain;
+ struct irq_domain *spi_domain;
+ struct irq_domain *lpi_domain;
+ struct irq_domain *ipi_domain;
+ u32 global_spi_count;
+ u8 cpuif_pri_bits;
+ u8 cpuif_id_bits;
+ u8 irs_pri_bits;
+ u8 irs_iaffid_bits;
+ struct {
+ __le64 *l1ist_addr;
+ u32 l2_size;
+ u8 l2_bits;
+ bool l2;
+ } ist;
+};
+
+extern struct gicv5_chip_data gicv5_global_data __read_mostly;
+
+struct gicv5_irs_chip_data {
+ struct list_head entry;
+ struct fwnode_handle *fwnode;
+ void __iomem *irs_base;
+ u32 flags;
+ u32 spi_min;
+ u32 spi_range;
+ raw_spinlock_t spi_config_lock;
+};
+
+void __init gicv5_init_lpi_domain(void);
+void __init gicv5_free_lpi_domain(void);
+
+int gicv5_irs_of_probe(struct device_node *parent);
+void gicv5_irs_remove(void);
+int gicv5_irs_enable(void);
+int gicv5_irs_register_cpu(int cpuid);
+int gicv5_irs_cpu_to_iaffid(int cpu_id, u16 *iaffid);
+struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id);
+int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
+int gicv5_spi_set_type(struct irq_data *d, unsigned int type);
+int gicv5_irs_iste_alloc(u32 lpi);
+
+void gicv5_init_lpis(u32 max);
+void gicv5_deinit_lpis(void);
+#endif
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (18 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-30 7:28 ` Jiri Slaby
2025-04-30 9:12 ` Marc Zyngier
2025-04-24 10:25 ` [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 22/22] arm64: Kconfig: Enable GICv5 Lorenzo Pieralisi
21 siblings, 2 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
The GICv5 architecture implements Interrupt Translation Service
(ITS) components in order to translate events coming from peripherals
into interrupt events delivered to the connected IRSes.
Events (ie MSI memory writes to ITS translate frame), are translated
by the ITS using tables kept in memory.
ITS translation tables for peripherals is kept in memory storage
(device table [DT] and Interrupt Translation Table [ITT]) that
is allocated by the driver on boot.
Both tables can be 1- or 2-level; the structure is chosen by the
driver after probing the ITS HW parameters and checking the
allowed table splits and supported {device/event}_IDbits.
DT table entries are allocated on demand (ie when a device is
probed); the DT table is sized using the number of supported
deviceID bits in that that's a system design decision (ie the
number of deviceID bits implemented should reflect the number
of devices expected in a system) therefore it makes sense to
allocate a DT table that can cater for the maximum number of
devices.
DT and ITT tables are allocated using the kmalloc interface;
the allocation size may be smaller than a page or larger,
and must provide contiguous memory pages.
LPIs INTIDs backing the device events are allocated one-by-one
and only upon Linux IRQ allocation; this to avoid preallocating
a large number of LPIs to cover the HW device MSI vector
size whereas few MSI entries are actually enabled by a device.
ITS cacheability/shareability attributes are programmed
according to the provided firmware ITS description.
The GICv5 ITS reuses the GICv3 MSI parent infrastructure,
there is no need to duplicate it, make it common.
Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Marc Zyngier <maz@kernel.org>
---
MAINTAINERS | 1 +
drivers/irqchip/Kconfig | 11 +
drivers/irqchip/Makefile | 4 +-
drivers/irqchip/irq-gic-common.h | 2 -
...3-its-msi-parent.c => irq-gic-its-msi-parent.c} | 3 +-
drivers/irqchip/irq-gic-its-msi-parent.h | 13 +
drivers/irqchip/irq-gic-v3-its.c | 3 +-
drivers/irqchip/irq-gic-v5-irs.c | 40 +-
drivers/irqchip/irq-gic-v5-its.c | 1293 ++++++++++++++++++++
drivers/irqchip/irq-gic-v5.c | 6 +-
drivers/irqchip/irq-gic-v5.h | 176 +++
11 files changed, 1529 insertions(+), 23 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index cdeceb6782355a4a18609135bf7f03249d8b0bb5..d231077c024deba42153663ac66b6c05f7673f03 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1908,6 +1908,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
F: Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
F: arch/arm64/include/asm/arch_gicv5.h
+F: drivers/irqchip/irq-gic-its-msi-parent.[ch]
F: drivers/irqchip/irq-gic-v5*.[ch]
ARM HDLCD DRM DRIVER
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 160a4761d5d85f6dbf36f3142fd619c114733e36..6c348d421b05af0e4f4909877e02ac8ef19178ff 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -41,10 +41,14 @@ config ARM_GIC_V3
select HAVE_ARM_SMCCC_DISCOVERY
select IRQ_MSI_IOMMU
+config ARM_GIC_ITS_PARENT
+ bool
+
config ARM_GIC_V3_ITS
bool
select GENERIC_MSI_IRQ
select IRQ_MSI_LIB
+ select ARM_GIC_ITS_PARENT
default ARM_GIC_V3
select IRQ_MSI_IOMMU
@@ -59,6 +63,13 @@ config ARM_GIC_V5
select IRQ_DOMAIN_HIERARCHY
select GENERIC_IRQ_EFFECTIVE_AFF_MASK if SMP
+config ARM_GIC_V5_ITS
+ bool
+ select GENERIC_MSI_IRQ
+ select IRQ_MSI_LIB
+ select ARM_GIC_ITS_PARENT
+ default ARM_GIC_V5
+
config ARM_NVIC
bool
select IRQ_DOMAIN_HIERARCHY
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 3d9c47fa3fdf40b7452c059d84fe8ac24c91bc0f..4280395e3bdff7858102f0b4eaaea1121cace52f 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -32,10 +32,12 @@ obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o
obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o
obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o
obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o
-obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o
+obj-$(CONFIG_ARM_GIC_ITS_PARENT) += irq-gic-its-msi-parent.o
+obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
+obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
obj-$(CONFIG_ARM_VIC) += irq-vic.o
diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h
index 020ecdf16901c9720e5746aec4d0b5b39d3625ed..710cab61d9195a0bd64d57e03c60852c4cd6ff8e 100644
--- a/drivers/irqchip/irq-gic-common.h
+++ b/drivers/irqchip/irq-gic-common.h
@@ -29,8 +29,6 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
void gic_enable_of_quirks(const struct device_node *np,
const struct gic_quirk *quirks, void *data);
-extern const struct msi_parent_ops gic_v3_its_msi_parent_ops;
-
#define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0)
#define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1)
#define RDIST_FLAGS_FORCE_NON_SHAREABLE (1 << 2)
diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c
similarity index 98%
rename from drivers/irqchip/irq-gic-v3-its-msi-parent.c
rename to drivers/irqchip/irq-gic-its-msi-parent.c
index bdb04c8081480de468fb217b68c6933a8e1e2bd7..71edcdb2defdfd5b892d86354039d2e46b832ea5 100644
--- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c
+++ b/drivers/irqchip/irq-gic-its-msi-parent.c
@@ -7,7 +7,6 @@
#include <linux/acpi_iort.h>
#include <linux/pci.h>
-#include "irq-gic-common.h"
#include "irq-msi-lib.h"
#define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
@@ -200,7 +199,7 @@ static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
return true;
}
-const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
+const struct msi_parent_ops gic_its_msi_parent_ops = {
.supported_flags = ITS_MSI_FLAGS_SUPPORTED,
.required_flags = ITS_MSI_FLAGS_REQUIRED,
.chip_flags = MSI_CHIP_FLAG_SET_EOI | MSI_CHIP_FLAG_SET_ACK,
diff --git a/drivers/irqchip/irq-gic-its-msi-parent.h b/drivers/irqchip/irq-gic-its-msi-parent.h
new file mode 100644
index 0000000000000000000000000000000000000000..e7bb7f3862eef379e5b85fe7bd5eb72f3586d3b7
--- /dev/null
+++ b/drivers/irqchip/irq-gic-its-msi-parent.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 ARM Limited, All Rights Reserved.
+ */
+
+#ifndef _IRQ_GIC_ITS_MSI_PARENT_H
+#define _IRQ_GIC_ITS_MSI_PARENT_H
+
+#include <linux/msi.h>
+
+extern const struct msi_parent_ops gic_its_msi_parent_ops;
+
+#endif /* _IRQ_GIC_ITS_MSI_PARENT_H */
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index 0115ad6c82593de511c285d99437996919bfa308..6c51bf4e34a38103d612c74476d640cd4126e8b6 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -41,6 +41,7 @@
#include <asm/exception.h>
#include "irq-gic-common.h"
+#include "irq-gic-its-msi-parent.h"
#include "irq-msi-lib.h"
#define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0)
@@ -5139,7 +5140,7 @@ static int its_init_domain(struct its_node *its)
irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);
- inner_domain->msi_parent_ops = &gic_v3_its_msi_parent_ops;
+ inner_domain->msi_parent_ops = &gic_its_msi_parent_ops;
inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
return 0;
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index 7bd60e6d56b77c0c19a1bd9bee9685d9b6ffc959..ff9de8fe175f511b2e81f712fa2e69b96f3e66fb 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -5,7 +5,6 @@
#define pr_fmt(fmt) "GICv5 IRS: " fmt
-#include <linux/iopoll.h>
#include <linux/irqchip.h>
#include <linux/log2.h>
#include <linux/of.h>
@@ -44,20 +43,6 @@ static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
writeq_relaxed(val, irs_data->irs_base + reg_offset);
}
-static int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask, u32 *val)
-{
- void __iomem *reg = addr + offset;
- u32 tmp;
- int ret;
-
- ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
-
- if (val)
- *val = tmp;
-
- return ret;
-}
-
#define gicv5_irs_wait_for_op(base, reg, mask) \
({ \
int ret; \
@@ -528,6 +513,23 @@ static int gicv5_irs_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
GICV5_IRS_CR0_IDLE);
}
+void gicv5_irs_syncr(void)
+{
+ struct gicv5_irs_chip_data *irs_data;
+ u32 syncr;
+
+ irs_data = list_first_entry_or_null(&irs_nodes,
+ struct gicv5_irs_chip_data, entry);
+ if (WARN_ON(!irs_data))
+ return;
+
+ syncr = FIELD_PREP(GICV5_IRS_SYNCR_SYNC, 1);
+ irs_writel_relaxed(irs_data, syncr, GICV5_IRS_SYNCR);
+
+ gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_SYNC_STATUSR,
+ GICV5_IRS_SYNC_STATUSR_IDLE);
+}
+
int gicv5_irs_register_cpu(int cpuid)
{
struct gicv5_irs_chip_data *irs_data;
@@ -823,6 +825,14 @@ int __init gicv5_irs_enable(void)
return 0;
}
+void __init gicv5_irs_its_probe(void)
+{
+ struct gicv5_irs_chip_data *irs_data;
+
+ list_for_each_entry(irs_data, &irs_nodes, entry)
+ gicv5_its_of_probe(to_of_node(irs_data->fwnode));
+}
+
int __init gicv5_irs_of_probe(struct device_node *parent)
{
struct device_node *np;
diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
new file mode 100644
index 0000000000000000000000000000000000000000..da349b4709cc5ec8978859237838f039389ca4a1
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5-its.c
@@ -0,0 +1,1293 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) "GICv5 ITS: " fmt
+
+#include <linux/bitmap.h>
+#include <linux/iommu.h>
+#include <linux/init.h>
+#include <linux/irqchip.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/msi.h>
+
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+
+#include "irq-gic-v5.h"
+#include "irq-gic-its-msi-parent.h"
+#include "irq-msi-lib.h"
+
+#define ITS_FLAGS_NON_COHERENT BIT(0)
+
+static LIST_HEAD(its_nodes);
+
+static u32 its_readl_relaxed(struct gicv5_its_chip_data *its_node,
+ const u64 reg_offset)
+{
+ return readl_relaxed(its_node->its_base + reg_offset);
+}
+
+static void its_writel_relaxed(struct gicv5_its_chip_data *its_node,
+ const u32 val, const u64 reg_offset)
+{
+ writel_relaxed(val, its_node->its_base + reg_offset);
+}
+
+static void its_writeq_relaxed(struct gicv5_its_chip_data *its_node,
+ const u64 val, const u64 reg_offset)
+{
+ writeq_relaxed(val, its_node->its_base + reg_offset);
+}
+
+static void its_write_table_entry(struct gicv5_its_chip_data *its,
+ __le64 *entry, u64 val)
+{
+ WRITE_ONCE(*entry, val);
+ if (its->flags & ITS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)entry,
+ (unsigned long)entry + sizeof(*entry));
+ else
+ dsb(ishst);
+}
+
+#define gicv5_its_wait_for_op(base, reg, mask) \
+ ({ \
+ int ret; \
+ \
+ ret = gicv5_wait_for_op(base, reg, mask, NULL); \
+ if (unlikely(ret == -ETIMEDOUT)) \
+ pr_err_ratelimited(#reg" timeout...\n"); \
+ ret; \
+ })
+
+static int gicv5_its_wait_for_invalidation(struct gicv5_its_chip_data *its)
+{
+ return gicv5_its_wait_for_op(its->its_base, GICV5_ITS_STATUSR,
+ GICV5_ITS_STATUSR_IDLE);
+}
+
+static void gicv5_its_syncr(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev)
+{
+ u64 syncr;
+
+ syncr = FIELD_PREP(GICV5_ITS_SYNCR_SYNC, 1) |
+ FIELD_PREP(GICV5_ITS_SYNCR_DEVICEID, its_dev->device_id);
+
+ its_writeq_relaxed(its, syncr, GICV5_ITS_SYNCR);
+
+ gicv5_its_wait_for_op(its->its_base, GICV5_ITS_SYNC_STATUSR,
+ GICV5_ITS_SYNC_STATUSR_IDLE);
+}
+
+static int gicv5_its_l2sz_to_l2_bits(unsigned int sz)
+{
+ switch (sz) {
+ case GICV5_ITS_DT_ITT_CFGR_L2SZ_4k:
+ return 9;
+ case GICV5_ITS_DT_ITT_CFGR_L2SZ_16k:
+ return 11;
+ case GICV5_ITS_DT_ITT_CFGR_L2SZ_64k:
+ return 13;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int gicv5_its_itt_cache_inv(struct gicv5_its_chip_data *its,
+ u32 device_id, u16 event_id)
+{
+ u32 eventr, eidr;
+ u64 didr;
+
+ didr = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
+ eidr = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id);
+ eventr = FIELD_PREP(GICV5_ITS_INV_EVENTR_I, 0x1);
+
+ its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
+ its_writel_relaxed(its, eidr, GICV5_ITS_EIDR);
+ its_writel_relaxed(its, eventr, GICV5_ITS_INV_EVENTR);
+
+ return gicv5_its_wait_for_invalidation(its);
+}
+
+static void gicv5_its_free_itt_linear(struct gicv5_its_dev *its_dev)
+{
+ kfree(its_dev->itt_cfg.linear.itt);
+}
+
+static void gicv5_its_free_itt_two_level(struct gicv5_its_dev *its_dev)
+{
+ unsigned int i, num_ents = its_dev->itt_cfg.l2.num_l1_ents;
+
+ for (i = 0; i < num_ents; i++)
+ kfree(its_dev->itt_cfg.l2.l2ptrs[i]);
+
+ kfree(its_dev->itt_cfg.l2.l2ptrs);
+ kfree(its_dev->itt_cfg.l2.l1itt);
+}
+
+static void gicv5_its_free_itt(struct gicv5_its_dev *its_dev)
+{
+ if (!its_dev->itt_cfg.l2itt)
+ gicv5_its_free_itt_linear(its_dev);
+ else
+ gicv5_its_free_itt_two_level(its_dev);
+}
+
+static int gicv5_its_create_itt_linear(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev,
+ unsigned int event_id_bits)
+{
+ unsigned int num_ents = BIT(event_id_bits);
+ __le64 *itt;
+
+ itt = kcalloc(num_ents, sizeof(*itt), GFP_KERNEL);
+ if (!itt)
+ return -ENOMEM;
+
+ its_dev->itt_cfg.linear.itt = itt;
+ its_dev->itt_cfg.linear.num_ents = num_ents;
+ its_dev->itt_cfg.l2itt = false;
+ its_dev->itt_cfg.event_id_bits = event_id_bits;
+
+ if (its->flags & ITS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)itt,
+ (unsigned long)itt + num_ents * sizeof(*itt));
+ else
+ dsb(ishst);
+
+ return 0;
+}
+
+/*
+ * Allocate a two-level ITT. All ITT entries are allocated in one go, unlike
+ * with the device table. Span may be used to limit the second level table
+ * size, where possible.
+ */
+static int gicv5_its_create_itt_two_level(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev,
+ unsigned int event_id_bits,
+ unsigned int itt_l2sz,
+ unsigned int num_events)
+{
+ unsigned int l1_bits, l2_bits, span, events_per_l2_table,
+ complete_tables, final_span, num_ents;
+ __le64 *itt_l1, *itt_l2, **l2ptrs;
+ size_t l1sz;
+ int ret, i;
+ u64 val;
+
+ ret = gicv5_its_l2sz_to_l2_bits(itt_l2sz);
+ if (ret < 0 || ret >= event_id_bits) {
+ pr_debug("Incorrect l2sz (0x%x) for %u EventID bits. Cannot allocate ITT\n",
+ itt_l2sz, event_id_bits);
+ return -EINVAL;
+ }
+
+ l2_bits = ret;
+
+ l1_bits = event_id_bits - l2_bits;
+
+ num_ents = BIT(l1_bits);
+
+ itt_l1 = kcalloc(num_ents, sizeof(*itt_l1), GFP_KERNEL);
+ if (!itt_l1)
+ return -ENOMEM;
+
+ l2ptrs = kcalloc(num_ents, sizeof(*l2ptrs), GFP_KERNEL);
+ if (!l2ptrs) {
+ kfree(itt_l1);
+ return -ENOMEM;
+ }
+
+ its_dev->itt_cfg.l2.l2ptrs = l2ptrs;
+
+ its_dev->itt_cfg.l2.l2sz = itt_l2sz;
+ its_dev->itt_cfg.l2.l1itt = itt_l1;
+ its_dev->itt_cfg.l2.num_l1_ents = num_ents;
+ its_dev->itt_cfg.l2itt = true;
+ its_dev->itt_cfg.event_id_bits = event_id_bits;
+
+ /*
+ * Need to determine how many entries there are per L2 - this is based
+ * on the number of bits in the table.
+ */
+ events_per_l2_table = BIT(l2_bits);
+ complete_tables = num_events / events_per_l2_table;
+ final_span = order_base_2(num_events % events_per_l2_table);
+
+ for (i = 0; i < num_ents; i++) {
+ size_t l2sz;
+
+ span = i == complete_tables ? final_span : l2_bits;
+
+ itt_l2 = kcalloc(BIT(span), sizeof(*itt_l2), GFP_KERNEL);
+ if (!itt_l2) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ its_dev->itt_cfg.l2.l2ptrs[i] = itt_l2;
+
+ l2sz = BIT(span) * sizeof(*itt_l2);
+
+ if (its->flags & ITS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)itt_l2,
+ (unsigned long)itt_l2 + l2sz);
+
+ val = (virt_to_phys(itt_l2) & GICV5_ITTL1E_L2_ADDR_MASK) |
+ FIELD_PREP(GICV5_ITTL1E_SPAN, span) |
+ FIELD_PREP(GICV5_ITTL1E_VALID, 0x1);
+
+ WRITE_ONCE(itt_l1[i], cpu_to_le64(val));
+ }
+
+ if (its->flags & ITS_FLAGS_NON_COHERENT) {
+ l1sz = num_ents * sizeof(*itt_l1);
+ dcache_clean_inval_poc((unsigned long)itt_l1,
+ (unsigned long)itt_l1 + l1sz);
+ } else {
+ dsb(ishst);
+ }
+
+ return 0;
+out_free:
+ for (i = i - 1; i >= 0; i--)
+ kfree(its_dev->itt_cfg.l2.l2ptrs[i]);
+
+ kfree(its_dev->itt_cfg.l2.l2ptrs);
+ kfree(itt_l1);
+ return ret;
+}
+
+/*
+ * Function to check whether the device table or ITT table support
+ * a two-level table and if so depending on the number of id_bits
+ * requested, determine whether a two-level table is required.
+ *
+ * Return the 2-level size value if a two level table is deemed
+ * necessary.
+ */
+static bool gicv5_its_l2sz_two_level(bool devtab, u32 its_idr1, u8 id_bits,
+ u8 *sz)
+{
+ int l2_bits, l2_sz = -EINVAL;
+
+ if (devtab && !FIELD_GET(GICV5_ITS_IDR1_DT_LEVELS, its_idr1))
+ return false;
+
+ if (!devtab && !FIELD_GET(GICV5_ITS_IDR1_ITT_LEVELS, its_idr1))
+ return false;
+
+ /*
+ * Pick an L2 size that matches the pagesize; if a match
+ * is not found, go for the smallest supported l2 size granule.
+ *
+ * This ensures that we will always be able to allocate
+ * contiguous memory at L2.
+ */
+ switch (PAGE_SIZE) {
+ case SZ_64K:
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_64k;
+ break;
+ }
+ fallthrough;
+ case SZ_16K:
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_16k;
+ break;
+ }
+ fallthrough;
+ case SZ_4K:
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_4k;
+ break;
+ }
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_16k;
+ break;
+ }
+ if (GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(its_idr1)) {
+ l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_64k;
+ break;
+ }
+ break;
+ }
+
+ l2_bits = gicv5_its_l2sz_to_l2_bits(l2_sz);
+
+ if (l2_bits < 0 || l2_bits > id_bits)
+ return false;
+
+ *sz = l2_sz;
+
+ return true;
+}
+
+static int gicv5_its_device_get_itte_ref(struct gicv5_its_dev *its_dev,
+ __le64 **itte, u16 event_id)
+{
+ if (!its_dev->itt_cfg.l2itt) {
+ __le64 *itt = its_dev->itt_cfg.linear.itt;
+ *itte = &itt[event_id];
+ } else {
+ __le64 *l2_itt, *l1_itt = its_dev->itt_cfg.l2.l1itt;
+ unsigned int l1_idx, l2_idx, l2_size, l2_bits;
+ int ret;
+
+ ret = gicv5_its_l2sz_to_l2_bits(its_dev->itt_cfg.l2.l2sz);
+ if (ret < 0)
+ return ret;
+ l2_bits = ret;
+
+ l1_idx = event_id >> l2_bits;
+
+ if (!FIELD_GET(GICV5_ITTL1E_VALID,
+ le64_to_cpu(l1_itt[l1_idx]))) {
+ pr_debug("L1 ITT entry is not valid.\n");
+ return -EINVAL;
+ }
+
+ l2_idx = event_id & GENMASK(l2_bits - 1, 0);
+
+ l2_size = BIT(FIELD_GET(GICV5_ITTL1E_SPAN,
+ le64_to_cpu(l1_itt[l1_idx])));
+
+ // Sanity check our indexing
+ if (l2_idx >= l2_size) {
+ pr_debug("L2 ITT index (%u) exceeds L2 table size (%u)!\n",
+ l2_idx, l2_size);
+ return -EINVAL;
+ }
+ l2_itt = its_dev->itt_cfg.l2.l2ptrs[l1_idx];
+ *itte = &l2_itt[l2_idx];
+ }
+
+ return 0;
+}
+
+static int gicv5_its_device_cache_inv(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev)
+{
+ u32 devicer;
+ u64 didr;
+
+ didr = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, its_dev->device_id);
+ devicer = FIELD_PREP(GICV5_ITS_INV_DEVICER_I, 0x1) |
+ FIELD_PREP(GICV5_ITS_INV_DEVICER_EVENTID_BITS,
+ its_dev->itt_cfg.event_id_bits) |
+ FIELD_PREP(GICV5_ITS_INV_DEVICER_L1, 0x0);
+ its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
+ its_writel_relaxed(its, devicer, GICV5_ITS_INV_DEVICER);
+
+ return gicv5_its_wait_for_invalidation(its);
+}
+
+/*
+ * Allocate a level 2 device table entry, update L1 parent to reference it.
+ * Only used for 2-level device tables, and it is called on demand.
+ */
+static int gicv5_its_alloc_l2_devtab(struct gicv5_its_chip_data *its,
+ unsigned int l1_index)
+{
+ __le64 *l2devtab, *l1devtab = its->devtab_cfgr.l2.l1devtab;
+ u8 span, l2sz, l2_bits;
+ u64 l1dte;
+ int ret;
+
+ if (FIELD_GET(GICV5_DTL1E_VALID, le64_to_cpu(l1devtab[l1_index])))
+ return 0;
+
+ span = FIELD_GET(GICV5_DTL1E_SPAN, le64_to_cpu(l1devtab[l1_index]));
+ l2sz = FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, its->devtab_cfgr.cfgr);
+
+ ret = gicv5_its_l2sz_to_l2_bits(l2sz);
+ if (ret < 0)
+ return ret;
+
+ l2_bits = ret;
+
+ /*
+ * Span allows us to create a smaller L2 device table.
+ * If it is too large, use the number of allowed L2 bits.
+ */
+ if (span > l2_bits)
+ span = l2_bits;
+
+ l2devtab = kcalloc(BIT(span), sizeof(*l2devtab), GFP_KERNEL);
+ if (!l2devtab)
+ return -ENOMEM;
+
+ its->devtab_cfgr.l2.l2ptrs[l1_index] = l2devtab;
+
+ l1dte = FIELD_PREP(GICV5_DTL1E_SPAN, span) |
+ (virt_to_phys(l2devtab) & GICV5_DTL1E_L2_ADDR_MASK) |
+ FIELD_PREP(GICV5_DTL1E_VALID, 0x1);
+ its_write_table_entry(its, &l1devtab[l1_index], cpu_to_le64(l1dte));
+
+ return 0;
+}
+
+static int gicv5_its_devtab_get_dte_ref(struct gicv5_its_chip_data *its,
+ __le64 **dte, u32 device_id,
+ bool alloc)
+{
+ u8 str = FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, its->devtab_cfgr.cfgr);
+ unsigned int l2sz, l2_bits, l1_idx, l2_idx;
+ __le64 *l1devtab, *l2devtab;
+ int ret;
+
+ if (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) {
+ l2devtab = its->devtab_cfgr.linear.devtab;
+ *dte = &l2devtab[device_id];
+ } else {
+ l2sz = FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, its->devtab_cfgr.cfgr);
+ l1devtab = its->devtab_cfgr.l2.l1devtab;
+
+ ret = gicv5_its_l2sz_to_l2_bits(l2sz);
+ if (ret < 0)
+ return -EINVAL;
+
+ l2_bits = ret;
+ l1_idx = device_id >> l2_bits;
+ l2_idx = device_id & GENMASK(l2_bits - 1, 0);
+
+ if (alloc) {
+ /*
+ * Allocate a new L2 device table here before
+ * continuing. We make the assumption that the span in
+ * the L1 table has been set correctly, and blindly use
+ * that value.
+ */
+ ret = gicv5_its_alloc_l2_devtab(its, l1_idx);
+ if (ret)
+ return ret;
+ } else {
+ if (!FIELD_GET(GICV5_DTL1E_VALID,
+ le64_to_cpu(l1devtab[l1_idx])))
+ return -EINVAL;
+ }
+
+ l2devtab = its->devtab_cfgr.l2.l2ptrs[l1_idx];
+ *dte = &l2devtab[l2_idx];
+ }
+
+ return 0;
+}
+
+/*
+ * Register a new device in the device table. Allocate an ITT and
+ * program the L2DTE entry according to the ITT structure that
+ * was chosen.
+ */
+static int gicv5_its_device_register(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev)
+{
+ u8 event_id_bits, device_id_bits, itt_struct, itt_l2sz;
+ phys_addr_t itt_phys_base;
+ bool two_level_itt;
+ u32 idr1, idr2;
+ __le64 *dte;
+ u64 val;
+ int ret;
+
+ device_id_bits = FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS,
+ its->devtab_cfgr.cfgr);
+
+ if (its_dev->device_id >= BIT(device_id_bits)) {
+ pr_err("Supplied DeviceID (%u) outside of Device Table range (%u)!",
+ its_dev->device_id, (u32)GENMASK(device_id_bits - 1, 0));
+ return -EINVAL;
+ }
+
+ ret = gicv5_its_devtab_get_dte_ref(its, &dte, its_dev->device_id, true);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(GICV5_DTL2E_VALID, le64_to_cpu(*dte)))
+ return -EBUSY;
+
+ /*
+ * Determine how many bits we need, validate those against the max.
+ * Based on these, determine if we should go for a 1- or 2-level ITT.
+ */
+ event_id_bits = order_base_2(its_dev->num_events);
+
+ idr2 = its_readl_relaxed(its, GICV5_ITS_IDR2);
+
+ if (event_id_bits > FIELD_GET(GICV5_ITS_IDR2_EVENTID_BITS, idr2)) {
+ pr_err("Required EventID bits (%u) larger than supported bits (%u)!",
+ event_id_bits,
+ (u8)FIELD_GET(GICV5_ITS_IDR2_EVENTID_BITS, idr2));
+ return -EINVAL;
+ }
+
+ idr1 = its_readl_relaxed(its, GICV5_ITS_IDR1);
+
+ /*
+ * L2 ITT size is programmed into the L2DTE regardless of
+ * whether a two-level or linear ITT is built, init it.
+ */
+ itt_l2sz = 0;
+
+ two_level_itt = gicv5_its_l2sz_two_level(false, idr1, event_id_bits,
+ &itt_l2sz);
+ if (two_level_itt)
+ ret = gicv5_its_create_itt_two_level(its, its_dev, event_id_bits,
+ itt_l2sz,
+ its_dev->num_events);
+ else
+ ret = gicv5_its_create_itt_linear(its, its_dev, event_id_bits);
+ if (ret)
+ return ret;
+
+ itt_phys_base = two_level_itt ? virt_to_phys(its_dev->itt_cfg.l2.l1itt) :
+ virt_to_phys(its_dev->itt_cfg.linear.itt);
+
+ itt_struct = two_level_itt ? GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL :
+ GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR;
+
+ val = FIELD_PREP(GICV5_DTL2E_EVENT_ID_BITS, event_id_bits) |
+ FIELD_PREP(GICV5_DTL2E_ITT_STRUCTURE, itt_struct) |
+ (itt_phys_base & GICV5_DTL2E_ITT_ADDR_MASK) |
+ FIELD_PREP(GICV5_DTL2E_ITT_L2SZ, itt_l2sz) |
+ FIELD_PREP(GICV5_DTL2E_VALID, 0x1);
+
+ its_write_table_entry(its, dte, cpu_to_le64(val));
+
+ ret = gicv5_its_device_cache_inv(its, its_dev);
+ if (ret) {
+ gicv5_its_free_itt(its_dev);
+ its_write_table_entry(its, dte, 0);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Unregister a device in the device table. Lookup the device by ID, free the
+ * corresponding ITT, mark the device as invalid in the device table.
+ */
+static int gicv5_its_device_unregister(struct gicv5_its_chip_data *its,
+ struct gicv5_its_dev *its_dev)
+{
+ __le64 *dte;
+ int ret;
+
+ ret = gicv5_its_devtab_get_dte_ref(its, &dte, its_dev->device_id, false);
+ if (ret) {
+ pr_debug("Failed to find DTE for DeviceID 0x%x\n", its_dev->device_id);
+ return -EINVAL;
+ }
+
+ if (!FIELD_GET(GICV5_DTL2E_VALID, le64_to_cpu(*dte))) {
+ pr_debug("Device table entry for DeviceID 0x%x is not valid. Nothing to clean up!",
+ its_dev->device_id);
+ return -EINVAL;
+ }
+
+ gicv5_its_free_itt(its_dev);
+
+ /* Zero everything - make it clear that this is an invalid entry */
+ its_write_table_entry(its, dte, 0);
+
+ return gicv5_its_device_cache_inv(its, its_dev);
+}
+
+/*
+ * Allocate a 1-level device table. All entries are allocated, but marked
+ * invalid.
+ */
+static int gicv5_its_alloc_devtab_linear(struct gicv5_its_chip_data *its,
+ u8 device_id_bits)
+{
+ __le64 *devtab;
+ size_t sz;
+ u64 baser;
+ u32 cfgr;
+
+ /*
+ * We expect a GICv5 implementation requiring a large number of
+ * deviceID bits to support a 2-level device table. If that's not
+ * the case, cap the number of deviceIDs supported according to the
+ * kmalloc limits so that the system can chug along with a linear
+ * device table.
+ */
+ sz = BIT_ULL(device_id_bits) * sizeof(*devtab);
+ if (sz > KMALLOC_MAX_SIZE) {
+ u8 device_id_cap = ilog2(KMALLOC_MAX_SIZE/sizeof(*devtab));
+
+ pr_warn("Limiting device ID bits from %u to %u\n",
+ device_id_bits, device_id_cap);
+ device_id_bits = device_id_cap;
+ }
+
+ devtab = kcalloc(BIT(device_id_bits), sizeof(*devtab), GFP_KERNEL);
+ if (!devtab)
+ return -ENOMEM;
+
+ if (its->flags & ITS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)devtab,
+ (unsigned long)devtab + sz);
+ else
+ dsb(ishst);
+
+ cfgr = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
+ GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) |
+ FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, 0) |
+ FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, device_id_bits);
+ its_writel_relaxed(its, cfgr, GICV5_ITS_DT_CFGR);
+
+ baser = virt_to_phys(devtab) & GICV5_ITS_DT_BASER_ADDR_MASK;
+ its_writeq_relaxed(its, baser, GICV5_ITS_DT_BASER);
+
+ its->devtab_cfgr.cfgr = cfgr;
+ its->devtab_cfgr.linear.devtab = devtab;
+
+ return 0;
+}
+
+/*
+ * Allocate a 2-level device table. L2 entries are not allocated,
+ * they are allocated on-demand.
+ */
+static int gicv5_its_alloc_devtab_two_level(struct gicv5_its_chip_data *its,
+ u8 device_id_bits,
+ u8 devtab_l2sz)
+{
+ unsigned int l1_bits, l2_bits, i;
+ __le64 *l1devtab, **l2ptrs;
+ size_t l1_sz;
+ u64 baser;
+ u32 cfgr;
+ int ret;
+
+ ret = gicv5_its_l2sz_to_l2_bits(devtab_l2sz);
+ if (ret < 0)
+ return ret;
+
+ l2_bits = ret;
+
+ l1_bits = device_id_bits - l2_bits;
+ l1_sz = BIT(l1_bits) * sizeof(*l1devtab);
+ /*
+ * With 2-level device table support it is highly unlikely
+ * that we are not able to allocate the required amount of
+ * device table memory to cover deviceID space; cap the
+ * deviceID space if we encounter such set-up.
+ * If this ever becomes a problem we could revisit the policy
+ * behind level 2 size selection to reduce level-1 deviceID bits.
+ */
+ if (l1_sz > KMALLOC_MAX_SIZE) {
+ l1_bits = ilog2(KMALLOC_MAX_SIZE/sizeof(*l1devtab));
+
+ pr_warn("Limiting device ID bits from %u to %u\n",
+ device_id_bits, l1_bits + l2_bits);
+ device_id_bits = l1_bits + l2_bits;
+ l1_sz = KMALLOC_MAX_SIZE;
+ }
+
+ l1devtab = kcalloc(BIT(l1_bits), sizeof(*l1devtab), GFP_KERNEL);
+ if (!l1devtab)
+ return -ENOMEM;
+
+ l2ptrs = kcalloc(BIT(l1_bits), sizeof(*l2ptrs), GFP_KERNEL);
+ if (!l2ptrs) {
+ kfree(l1devtab);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < BIT(l1_bits); i++)
+ l1devtab[i] = cpu_to_le64(FIELD_PREP(GICV5_DTL1E_SPAN, l2_bits));
+
+ if (its->flags & ITS_FLAGS_NON_COHERENT)
+ dcache_clean_inval_poc((unsigned long)l1devtab,
+ (unsigned long)l1devtab + l1_sz);
+ else
+ dsb(ishst);
+
+ cfgr = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
+ GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL) |
+ FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, devtab_l2sz) |
+ FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, device_id_bits);
+ its_writel_relaxed(its, cfgr, GICV5_ITS_DT_CFGR);
+
+ baser = virt_to_phys(l1devtab) & GICV5_ITS_DT_BASER_ADDR_MASK;
+ its_writeq_relaxed(its, baser, GICV5_ITS_DT_BASER);
+
+ its->devtab_cfgr.cfgr = cfgr;
+ its->devtab_cfgr.l2.l1devtab = l1devtab;
+ its->devtab_cfgr.l2.l2ptrs = l2ptrs;
+
+ return 0;
+}
+
+/*
+ * Initialise the device table as either 1- or 2-level depending on what is
+ * supported by the hardware.
+ */
+static int gicv5_its_init_devtab(struct gicv5_its_chip_data *its)
+{
+ u8 device_id_bits, devtab_l2sz;
+ bool two_level_devtab;
+ u32 idr1;
+
+ idr1 = its_readl_relaxed(its, GICV5_ITS_IDR1);
+
+ device_id_bits = FIELD_GET(GICV5_ITS_IDR1_DEVICEID_BITS, idr1);
+ two_level_devtab = gicv5_its_l2sz_two_level(true, idr1, device_id_bits,
+ &devtab_l2sz);
+ if (two_level_devtab)
+ return gicv5_its_alloc_devtab_two_level(its, device_id_bits,
+ devtab_l2sz);
+ else
+ return gicv5_its_alloc_devtab_linear(its, device_id_bits);
+}
+
+static void gicv5_its_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
+{
+ struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
+ struct gicv5_its_chip_data *its = its_dev->its_node;
+ u64 addr;
+
+ addr = its->its_trans_phys_base;
+
+ msg->data = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+ msi_msg_set_addr(irq_data_get_msi_desc(d), msg, addr);
+}
+
+static const struct irq_chip gicv5_its_irq_chip = {
+ .name = "GICv5-ITS-MSI",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_compose_msi_msg = gicv5_its_compose_msi_msg,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND
+};
+
+static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *its,
+ u32 device_id)
+{
+ struct gicv5_its_dev *dev = xa_load(&its->its_devices, device_id);
+
+ return dev ? dev : ERR_PTR(-ENODEV);
+}
+
+static struct gicv5_its_dev *gicv5_its_alloc_device(
+ struct gicv5_its_chip_data *its, int nvec,
+ u32 dev_id)
+{
+ struct gicv5_its_dev *its_dev;
+ int ret;
+
+ its_dev = gicv5_its_find_device(its, dev_id);
+ if (!IS_ERR(its_dev)) {
+ pr_debug("A device with this DeviceID (0x%x) has already been registered.\n",
+ dev_id);
+
+ if (nvec > its_dev->num_events) {
+ pr_debug("Requesting more ITT entries than allocated\n");
+ return ERR_PTR(-ENXIO);
+ }
+
+ its_dev->shared = true;
+
+ return its_dev;
+ }
+
+ its_dev = kzalloc(sizeof(*its_dev), GFP_KERNEL);
+ if (!its_dev)
+ return ERR_PTR(-ENOMEM);
+
+ its_dev->device_id = dev_id;
+ its_dev->num_events = nvec;
+ its_dev->num_mapped_events = 0;
+
+ ret = gicv5_its_device_register(its, its_dev);
+ if (ret) {
+ pr_debug("Failed to register the device\n");
+ kfree(its_dev);
+ return ERR_PTR(ret);
+ }
+
+ gicv5_its_device_cache_inv(its, its_dev);
+
+ /*
+ * This is the first time we have seen this device. Hence, it is not
+ * shared.
+ */
+ its_dev->shared = false;
+
+ its_dev->its_node = its;
+
+ its_dev->event_map =
+ (unsigned long *)bitmap_zalloc(its_dev->num_events, GFP_KERNEL);
+ if (!its_dev->event_map) {
+ gicv5_its_device_unregister(its, its_dev);
+ kfree(its_dev);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ xa_store(&its->its_devices, dev_id, its_dev, GFP_KERNEL);
+
+ return its_dev;
+}
+
+static int gicv5_its_msi_prepare(struct irq_domain *domain, struct device *dev,
+ int nvec, msi_alloc_info_t *info)
+{
+ u32 dev_id = info->scratchpad[0].ul;
+ struct msi_domain_info *msi_info;
+ struct gicv5_its_chip_data *its;
+ struct gicv5_its_dev *its_dev;
+
+ msi_info = msi_get_domain_info(domain);
+ its = msi_info->data;
+
+ guard(mutex)(&its->dev_alloc_lock);
+
+ its_dev = gicv5_its_alloc_device(its, nvec, dev_id);
+ if (IS_ERR(its_dev))
+ return PTR_ERR(its_dev);
+
+ if (info->flags & MSI_ALLOC_FLAGS_PROXY_DEVICE)
+ its_dev->shared = true;
+
+ return 0;
+}
+
+static struct msi_domain_ops its_msi_domain_ops = {
+ .msi_prepare = gicv5_its_msi_prepare,
+};
+
+static int gicv5_its_alloc_event(struct gicv5_its_dev *its_dev, u16 event_id,
+ u32 lpi)
+{
+ struct gicv5_its_chip_data *its = its_dev->its_node;
+ u64 itt_entry;
+ __le64 *itte;
+ int ret;
+
+ if (event_id >= its_dev->num_events) {
+ pr_debug("EventID 0x%x outside of ITT range (0x%x)\n", event_id,
+ its_dev->num_events);
+ return -EINVAL;
+ }
+
+ if (WARN(its_dev->num_mapped_events == its_dev->num_events,
+ "Reached maximum number of events\n"))
+ return -EINVAL;
+
+ ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(GICV5_ITTL2E_VALID, *itte))
+ return -EEXIST;
+
+ itt_entry = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) |
+ FIELD_PREP(GICV5_ITTL2E_VALID, 0x1);
+
+ its_write_table_entry(its, itte, cpu_to_le64(itt_entry));
+
+ gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
+
+ its_dev->num_mapped_events += 1;
+
+ return 0;
+}
+
+static void gicv5_its_free_event(struct gicv5_its_dev *its_dev, u16 event_id)
+{
+ struct gicv5_its_chip_data *its = its_dev->its_node;
+ u64 itte_val;
+ __le64 *itte;
+ int ret;
+
+ if (WARN(!its_dev->num_mapped_events, "No mapped events\n"))
+ return;
+
+ ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
+ if (ret) {
+ pr_debug("Failed to get the ITTE!\n");
+ return;
+ }
+
+ itte_val = le64_to_cpu(*itte);
+ itte_val &= ~GICV5_ITTL2E_VALID;
+
+ its_write_table_entry(its, itte, cpu_to_le64(itte_val));
+
+ gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
+
+ its_dev->num_mapped_events -= 1;
+}
+
+static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev,
+ unsigned int nr_irqs, u32 *eventid)
+{
+ int ret;
+
+ ret = bitmap_find_free_region(its_dev->event_map,
+ its_dev->num_events,
+ get_count_order(nr_irqs));
+
+ if (ret < 0)
+ return ret;
+
+ *eventid = ret;
+
+ return 0;
+}
+
+static void gicv5_its_free_eventid(struct gicv5_its_dev *its_dev,
+ u32 event_id_base,
+ unsigned int nr_irqs)
+{
+ bitmap_release_region(its_dev->event_map, event_id_base,
+ get_count_order(nr_irqs));
+}
+
+static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ u32 device_id, event_id_base, lpi;
+ struct msi_domain_info *msi_info;
+ struct gicv5_its_chip_data *its;
+ struct gicv5_its_dev *its_dev;
+ msi_alloc_info_t *info = arg;
+ irq_hw_number_t hwirq;
+ struct irq_data *irqd;
+ int ret, i;
+
+ device_id = info->scratchpad[0].ul;
+
+ msi_info = msi_get_domain_info(domain);
+ its = msi_info->data;
+
+ mutex_lock(&its->dev_alloc_lock);
+
+ its_dev = gicv5_its_find_device(its, device_id);
+ if (IS_ERR(its_dev)) {
+ mutex_unlock(&its->dev_alloc_lock);
+ return PTR_ERR(its_dev);
+ }
+
+ ret = gicv5_its_alloc_eventid(its_dev, nr_irqs, &event_id_base);
+ if (ret) {
+ mutex_unlock(&its->dev_alloc_lock);
+ return ret;
+ }
+
+ mutex_unlock(&its->dev_alloc_lock);
+
+ ret = iommu_dma_prepare_msi(info->desc, its->its_trans_phys_base);
+ if (ret)
+ goto out_eventid;
+
+ for (i = 0; i < nr_irqs; i++) {
+ lpi = gicv5_alloc_lpi();
+ if (ret < 0) {
+ pr_debug("Failed to find free LPI!\n");
+ goto out_eventid;
+ }
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
+ if (ret)
+ goto out_free_lpi;
+
+ /*
+ * Store eventid and deviceid into the hwirq for later use.
+ *
+ * hwirq = event_id << 32 | device_id
+ */
+ hwirq = FIELD_PREP(GICV5_ITS_HWIRQ_DEVICE_ID, device_id) |
+ FIELD_PREP(GICV5_ITS_HWIRQ_EVENT_ID, (u64)event_id_base + i);
+ irq_domain_set_info(domain, virq + i, hwirq,
+ &gicv5_its_irq_chip, its_dev,
+ handle_fasteoi_irq, NULL, NULL);
+
+ irqd = irq_get_irq_data(virq + i);
+ irqd_set_single_target(irqd);
+ irqd_set_affinity_on_activate(irqd);
+ irqd_set_resend_when_in_progress(irqd);
+ }
+
+ return 0;
+out_free_lpi:
+ gicv5_free_lpi(lpi);
+out_eventid:
+ gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs);
+
+ return ret;
+}
+
+static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct msi_domain_info *msi_info;
+ struct gicv5_its_chip_data *its;
+ struct gicv5_its_dev *its_dev;
+ struct irq_data *d;
+ u16 event_id_base;
+ bool free_device;
+ u32 device_id;
+ int i;
+
+ msi_info = msi_get_domain_info(domain);
+ its = msi_info->data;
+
+ d = irq_domain_get_irq_data(domain, virq);
+ device_id = FIELD_GET(GICV5_ITS_HWIRQ_DEVICE_ID, d->hwirq);
+ event_id_base = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+
+ guard(mutex)(&its->dev_alloc_lock);
+
+ its_dev = gicv5_its_find_device(its, device_id);
+ if (IS_ERR(its_dev)) {
+ pr_debug("Couldn't find the ITS device!\n");
+ return;
+ }
+
+ bitmap_release_region(its_dev->event_map, event_id_base,
+ get_count_order(nr_irqs));
+
+ free_device = !its_dev->shared && bitmap_empty(its_dev->event_map,
+ its_dev->num_events);
+
+ /* Hierarchically free irq data */
+ for (i = 0; i < nr_irqs; i++) {
+ d = irq_domain_get_irq_data(domain, virq + i);
+
+ gicv5_free_lpi(d->parent_data->hwirq);
+ irq_domain_reset_irq_data(d);
+ }
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+
+ gicv5_its_syncr(its, its_dev);
+ gicv5_irs_syncr();
+
+ if (free_device) {
+ gicv5_its_device_unregister(its, its_dev);
+ bitmap_free(its_dev->event_map);
+ xa_erase(&its->its_devices, device_id);
+ kfree(its_dev);
+ }
+}
+
+static int gicv5_its_irq_domain_activate(struct irq_domain *domain,
+ struct irq_data *d, bool reserve)
+{
+ struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
+ u16 event_id;
+ u32 lpi;
+
+ event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+ lpi = d->parent_data->hwirq;
+
+ return gicv5_its_alloc_event(its_dev, event_id, lpi);
+}
+
+static void gicv5_its_irq_domain_deactivate(struct irq_domain *domain,
+ struct irq_data *d)
+{
+ struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
+ u16 event_id;
+
+ event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
+
+ gicv5_its_free_event(its_dev, event_id);
+}
+static const struct irq_domain_ops gicv5_its_irq_domain_ops = {
+ .alloc = gicv5_its_irq_domain_alloc,
+ .free = gicv5_its_irq_domain_free,
+ .activate = gicv5_its_irq_domain_activate,
+ .deactivate = gicv5_its_irq_domain_deactivate,
+ .select = msi_lib_irq_domain_select,
+};
+
+static int gicv5_its_wait_for_cr0(struct gicv5_its_chip_data *its)
+{
+ return gicv5_its_wait_for_op(its->its_base, GICV5_ITS_CR0,
+ GICV5_ITS_CR0_IDLE);
+}
+
+static void gicv5_its_print_info(struct gicv5_its_chip_data *its_node)
+{
+ bool devtab_linear;
+ u8 device_id_bits;
+ u8 str;
+
+ device_id_bits = FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS,
+ its_node->devtab_cfgr.cfgr);
+
+ str = FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, its_node->devtab_cfgr.cfgr);
+ devtab_linear = (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR);
+
+ pr_info("ITS %s enabled using %s device table device_id_bits %u\n",
+ fwnode_get_name(its_node->fwnode),
+ devtab_linear ? "linear" : "2-level",
+ device_id_bits);
+}
+
+static int __init gicv5_its_init_bases(phys_addr_t its_trans_base,
+ void __iomem *its_base,
+ struct fwnode_handle *handle,
+ struct irq_domain *parent_domain)
+{
+ struct device_node *np = to_of_node(handle);
+ struct gicv5_its_chip_data *its_node;
+ struct msi_domain_info *info;
+ struct irq_domain *d;
+ u32 cr0, cr1;
+ bool enabled;
+ int ret;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ its_node = kzalloc(sizeof(*its_node), GFP_KERNEL);
+ if (!its_node) {
+ kfree(info);
+ return -ENOMEM;
+ }
+
+ info->ops = &its_msi_domain_ops;
+ info->data = its_node;
+
+ mutex_init(&its_node->dev_alloc_lock);
+ xa_init(&its_node->its_devices);
+ its_node->fwnode = handle;
+ its_node->its_base = its_base;
+ its_node->its_trans_phys_base = its_trans_base;
+
+ d = irq_domain_create_hierarchy(parent_domain, IRQ_DOMAIN_FLAG_ISOLATED_MSI,
+ 0, handle, &gicv5_its_irq_domain_ops, info);
+ its_node->domain = d;
+ irq_domain_update_bus_token(its_node->domain, DOMAIN_BUS_NEXUS);
+
+ its_node->domain->msi_parent_ops = &gic_its_msi_parent_ops;
+ its_node->domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
+
+ cr0 = its_readl_relaxed(its_node, GICV5_ITS_CR0);
+ enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, cr0);
+ if (WARN(enabled, "ITS %s enabled, disabling it before proceeding\n",
+ np->full_name)) {
+ cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x0);
+ its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
+ ret = gicv5_its_wait_for_cr0(its_node);
+ if (ret) {
+ irq_domain_remove(its_node->domain);
+ kfree(info);
+ kfree(its_node);
+ return ret;
+ }
+ }
+
+ if (of_property_read_bool(np, "dma-noncoherent")) {
+ /*
+ * A non-coherent ITS implies that some cache levels cannot be
+ * used coherently by the cores and GIC. Our only option is to mark
+ * memory attributes for the GIC as non-cacheable; by default,
+ * non-cacheable memory attributes imply outer-shareable
+ * shareability, the value written into ITS_CR1_SH is ignored.
+ */
+ cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_NO_READ_ALLOC) |
+ FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_NON_CACHE) |
+ FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_NON_CACHE);
+ its_node->flags |= ITS_FLAGS_NON_COHERENT;
+ } else {
+ cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_READ_ALLOC) |
+ FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_WB_CACHE) |
+ FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_WB_CACHE) |
+ FIELD_PREP(GICV5_ITS_CR1_SH, GICV5_INNER_SHARE);
+ }
+
+ its_writel_relaxed(its_node, cr1, GICV5_ITS_CR1);
+
+ ret = gicv5_its_init_devtab(its_node);
+ if (ret) {
+ irq_domain_remove(its_node->domain);
+ kfree(info);
+ kfree(its_node);
+ return ret;
+ }
+
+ cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x1);
+ its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
+
+ ret = gicv5_its_wait_for_cr0(its_node);
+ if (ret) {
+ irq_domain_remove(its_node->domain);
+ kfree(info);
+ kfree(its_node);
+ return ret;
+ }
+
+ list_add(&its_node->entry, &its_nodes);
+
+ gicv5_its_print_info(its_node);
+
+ return 0;
+}
+
+static int __init gicv5_its_init(struct device_node *node)
+{
+ void __iomem *its_base;
+ struct resource res;
+ int ret;
+
+ its_base = of_io_request_and_map(node, 0, "ITS");
+ if (IS_ERR(its_base)) {
+ pr_err("%pOF: unable to map GICv5 ITS_CONFIG_FRAME\n", node);
+ return PTR_ERR(its_base);
+ }
+
+ /*
+ * The ITS_TRANSLATE_FRAME is the second reg entry, (first is the
+ * ITS_CONFIG_FRAME) - extract it and use it to init ITS data
+ * structures.
+ */
+ ret = of_address_to_resource(node, 1, &res);
+ if (ret)
+ goto out_unmap;
+
+ ret = gicv5_its_init_bases(res.start, its_base, &node->fwnode,
+ gicv5_global_data.lpi_domain);
+ if (ret)
+ goto out_unmap;
+
+ return 0;
+out_unmap:
+ iounmap(its_base);
+ return ret;
+}
+
+void __init gicv5_its_of_probe(struct device_node *parent)
+{
+ struct device_node *np;
+
+ for_each_available_child_of_node(parent, np) {
+ if (!of_device_is_compatible(np, "arm,gic-v5-its"))
+ continue;
+
+ if (gicv5_its_init(np))
+ pr_err("Failed to init ITS %s\n", np->full_name);
+ }
+}
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index c4d4e85382f672fa4ae334db1a4e4c7c4f46b9fe..e483d0774936035b5cf2407da9a65d776bad3138 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -55,12 +55,12 @@ static void release_lpi(u32 lpi)
ida_free(&lpi_ida, lpi);
}
-static int gicv5_alloc_lpi(void)
+int gicv5_alloc_lpi(void)
{
return alloc_lpi();
}
-static void gicv5_free_lpi(u32 lpi)
+void gicv5_free_lpi(u32 lpi)
{
release_lpi(lpi);
}
@@ -1045,6 +1045,8 @@ static int __init gicv5_of_init(struct device_node *node,
gicv5_smp_init();
+ gicv5_irs_its_probe();
+
return 0;
out_int:
gicv5_cpu_disable_interrupts();
diff --git a/drivers/irqchip/irq-gic-v5.h b/drivers/irqchip/irq-gic-v5.h
index 19569639153a084760c3b5b7f0fa84791ba0195c..f5a453599493020b36d9c7f18c08171c51ba8669 100644
--- a/drivers/irqchip/irq-gic-v5.h
+++ b/drivers/irqchip/irq-gic-v5.h
@@ -5,6 +5,8 @@
#ifndef __LINUX_IRQCHIP_GIC_V5_H
#define __LINUX_IRQCHIP_GIC_V5_H
+#include <linux/iopoll.h>
+
#include <asm/arch_gicv5.h>
#include <asm/smp.h>
@@ -41,6 +43,8 @@
#define GICV5_IRS_IDR7 0x001c
#define GICV5_IRS_CR0 0x0080
#define GICV5_IRS_CR1 0x0084
+#define GICV5_IRS_SYNCR 0x00c0
+#define GICV5_IRS_SYNC_STATUSR 0x00c4
#define GICV5_IRS_SPI_SELR 0x0108
#define GICV5_IRS_SPI_CFGR 0x0114
#define GICV5_IRS_SPI_STATUSR 0x0118
@@ -94,6 +98,10 @@
#define GICV5_IRS_CR1_OC GENMASK(3, 2)
#define GICV5_IRS_CR1_SH GENMASK(1, 0)
+#define GICV5_IRS_SYNCR_SYNC BIT(31)
+
+#define GICV5_IRS_SYNC_STATUSR_IDLE BIT(0)
+
#define GICV5_IRS_SPI_STATUSR_V BIT(1)
#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
@@ -135,6 +143,101 @@
#define GICV5_ISTL1E_L2_ADDR_MASK GENMASK_ULL(55, 12)
+#define GICV5_ITS_IDR1 0x0004
+#define GICV5_ITS_IDR2 0x0008
+#define GICV5_ITS_CR0 0x0080
+#define GICV5_ITS_CR1 0x0084
+#define GICV5_ITS_DT_BASER 0x00c0
+#define GICV5_ITS_DT_CFGR 0x00d0
+#define GICV5_ITS_DIDR 0x0100
+#define GICV5_ITS_EIDR 0x0108
+#define GICV5_ITS_INV_EVENTR 0x010c
+#define GICV5_ITS_INV_DEVICER 0x0110
+#define GICV5_ITS_STATUSR 0x0120
+#define GICV5_ITS_SYNCR 0x0140
+#define GICV5_ITS_SYNC_STATUSR 0x0148
+
+#define GICV5_ITS_IDR1_L2SZ GENMASK(10, 8)
+#define GICV5_ITS_IDR1_ITT_LEVELS BIT(7)
+#define GICV5_ITS_IDR1_DT_LEVELS BIT(6)
+#define GICV5_ITS_IDR1_DEVICEID_BITS GENMASK(5, 0)
+
+#define GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(8), (r))
+#define GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(9), (r))
+#define GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(10), (r))
+
+#define GICV5_ITS_IDR2_XDMN_EVENTs GENMASK(6, 5)
+#define GICV5_ITS_IDR2_EVENTID_BITS GENMASK(4, 0)
+
+#define GICV5_ITS_CR0_IDLE BIT(1)
+#define GICV5_ITS_CR0_ITSEN BIT(0)
+
+#define GICV5_ITS_CR1_ITT_RA BIT(7)
+#define GICV5_ITS_CR1_DT_RA BIT(6)
+#define GICV5_ITS_CR1_IC GENMASK(5, 4)
+#define GICV5_ITS_CR1_OC GENMASK(3, 2)
+#define GICV5_ITS_CR1_SH GENMASK(1, 0)
+
+#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16)
+#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6)
+#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0)
+
+#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3)
+
+#define GICV5_ITS_INV_DEVICER_I BIT(31)
+#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1)
+#define GICV5_ITS_INV_DEVICER_L1 BIT(0)
+
+#define GICV5_ITS_DIDR_DEVICEID GENMASK_ULL(31, 0)
+
+#define GICV5_ITS_EIDR_EVENTID GENMASK(15, 0)
+
+#define GICV5_ITS_INV_EVENTR_I BIT(31)
+#define GICV5_ITS_INV_EVENTR_ITT_L2SZ GENMASK(2, 1)
+#define GICV5_ITS_INV_EVENTR_L1 BIT(0)
+
+#define GICV5_ITS_STATUSR_IDLE BIT(0)
+
+#define GICV5_ITS_SYNCR_SYNC BIT_ULL(63)
+#define GICV5_ITS_SYNCR_SYNCALL BIT_ULL(32)
+#define GICV5_ITS_SYNCR_DEVICEID GENMASK_ULL(31, 0)
+
+#define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0)
+
+#define GICV5_DTL1E_VALID BIT_ULL(0)
+// Note that there is no shift for the address by design
+#define GICV5_DTL1E_L2_ADDR_MASK GENMASK_ULL(55, 3)
+#define GICV5_DTL1E_SPAN GENMASK_ULL(63, 60)
+
+#define GICV5_DTL2E_VALID BIT_ULL(0)
+#define GICV5_DTL2E_ITT_L2SZ GENMASK_ULL(2, 1)
+// Note that there is no shift for the address by design
+#define GICV5_DTL2E_ITT_ADDR_MASK GENMASK_ULL(55, 3)
+#define GICV5_DTL2E_ITT_DSWE BIT_ULL(57)
+#define GICV5_DTL2E_ITT_STRUCTURE BIT_ULL(58)
+#define GICV5_DTL2E_EVENT_ID_BITS GENMASK_ULL(63, 59)
+
+#define GICV5_ITTL1E_VALID BIT_ULL(0)
+// Note that there is no shift for the address by design
+#define GICV5_ITTL1E_L2_ADDR_MASK GENMASK_ULL(55, 3)
+#define GICV5_ITTL1E_SPAN GENMASK_ULL(63, 60)
+
+#define GICV5_ITTL2E_LPI_ID GENMASK_ULL(23, 0)
+#define GICV5_ITTL2E_DAC GENMASK_ULL(29, 28)
+#define GICV5_ITTL2E_VIRTUAL BIT_ULL(30)
+#define GICV5_ITTL2E_VALID BIT_ULL(31)
+#define GICV5_ITTL2E_VM_ID GENMASK_ULL(47, 32)
+
+#define GICV5_ITS_DT_ITT_CFGR_L2SZ_4k 0b00
+#define GICV5_ITS_DT_ITT_CFGR_L2SZ_16k 0b01
+#define GICV5_ITS_DT_ITT_CFGR_L2SZ_64k 0b10
+
+#define GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR 0
+#define GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL 1
+
+#define GICV5_ITS_HWIRQ_DEVICE_ID GENMASK_ULL(31, 0)
+#define GICV5_ITS_HWIRQ_EVENT_ID GENMASK_ULL(63, 32)
+
struct gicv5_chip_data {
struct fwnode_handle *fwnode;
struct irq_domain *ppi_domain;
@@ -168,17 +271,90 @@ struct gicv5_irs_chip_data {
void __init gicv5_init_lpi_domain(void);
void __init gicv5_free_lpi_domain(void);
+static inline int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask,
+ u32 *val)
+{
+ void __iomem *reg = addr + offset;
+ u32 tmp;
+ int ret;
+
+ ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
+
+ if (val)
+ *val = tmp;
+
+ return ret;
+}
int gicv5_irs_of_probe(struct device_node *parent);
void gicv5_irs_remove(void);
int gicv5_irs_enable(void);
+void gicv5_irs_its_probe(void);
int gicv5_irs_register_cpu(int cpuid);
int gicv5_irs_cpu_to_iaffid(int cpu_id, u16 *iaffid);
struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id);
int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
int gicv5_spi_set_type(struct irq_data *d, unsigned int type);
int gicv5_irs_iste_alloc(u32 lpi);
+void gicv5_irs_syncr(void);
+
+struct gicv5_its_devtab_cfg {
+ union {
+ struct {
+ __le64 *devtab;
+ } linear;
+ struct {
+ __le64 *l1devtab;
+ __le64 **l2ptrs;
+ } l2;
+ };
+ u32 cfgr;
+};
+
+struct gicv5_its_itt_cfg {
+ union {
+ struct {
+ __le64 *itt;
+ unsigned int num_ents;
+ } linear;
+ struct {
+ __le64 *l1itt;
+ __le64 **l2ptrs;
+ unsigned int num_l1_ents;
+ u8 l2sz;
+ } l2;
+ };
+ u8 event_id_bits;
+ bool l2itt;
+};
+
+struct gicv5_its_chip_data {
+ struct list_head entry;
+ struct xarray its_devices;
+ struct mutex dev_alloc_lock;
+ struct fwnode_handle *fwnode;
+ struct gicv5_its_devtab_cfg devtab_cfgr;
+ struct irq_domain *domain;
+ void __iomem *its_base;
+ phys_addr_t its_trans_phys_base;
+ u32 flags;
+};
+
+struct gicv5_its_dev {
+ struct gicv5_its_chip_data *its_node;
+ struct gicv5_its_itt_cfg itt_cfg;
+ unsigned long *event_map;
+ u32 device_id;
+ u32 num_events;
+ u32 num_mapped_events;
+ bool shared;
+};
void gicv5_init_lpis(u32 max);
void gicv5_deinit_lpis(void);
+
+int gicv5_alloc_lpi(void);
+void gicv5_free_lpi(u32 lpi);
+
+void __init gicv5_its_of_probe(struct device_node *parent);
#endif
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (19 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
2025-04-30 11:57 ` Marc Zyngier
2025-04-24 10:25 ` [PATCH v2 22/22] arm64: Kconfig: Enable GICv5 Lorenzo Pieralisi
21 siblings, 1 reply; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
The GICv5 architecture implements the Interrupt Wire Bridge (IWB) in
order to support wired interrupts that cannot be connected directly
to an IRS and instead uses the ITS to translate a wire event into
an IRQ signal.
An IWB is a special ITS device with its own deviceID; upon probe,
an IWB calls into the ITS driver to allocate DT/ITT tables for its
events (ie wires).
An IWB is always associated with a single ITS in the system.
An IWB is connected to an ITS and it has its own deviceID for all
interrupt wires that it manages; the IWB input wire number is
exposed to the ITS as an eventID. This eventID is not programmable
and therefore requires special handling in the ITS driver.
Add an IWB driver in order to:
- Probe IWBs in the system and allocate ITS tables
- Manage IWB IRQ domains
- Handle IWB input wires state (enable/disable)
- Add the required IWB IRQchip representation
- Handle firmware representation to Linux IRQ translation
Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Marc Zyngier <maz@kernel.org>
---
drivers/irqchip/Makefile | 2 +-
drivers/irqchip/irq-gic-v5-its.c | 68 ++++++--
drivers/irqchip/irq-gic-v5-iwb.c | 356 +++++++++++++++++++++++++++++++++++++++
drivers/irqchip/irq-gic-v5.c | 2 +
drivers/irqchip/irq-gic-v5.h | 28 +++
5 files changed, 437 insertions(+), 19 deletions(-)
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 4280395e3bdff7858102f0b4eaaea1121cace52f..7bfb2369fbe494a64b72308d95ae33de93c6b8c6 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -37,7 +37,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
-obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
+obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o irq-gic-v5-iwb.o
obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
obj-$(CONFIG_ARM_VIC) += irq-vic.o
diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
index da349b4709cc5ec8978859237838f039389ca4a1..b5eb4dbfe2296dc6620889eb9291b542cae4aeb6 100644
--- a/drivers/irqchip/irq-gic-v5-its.c
+++ b/drivers/irqchip/irq-gic-v5-its.c
@@ -786,9 +786,8 @@ static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *i
return dev ? dev : ERR_PTR(-ENODEV);
}
-static struct gicv5_its_dev *gicv5_its_alloc_device(
- struct gicv5_its_chip_data *its, int nvec,
- u32 dev_id)
+struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
+ int nvec, u32 dev_id, bool is_iwb)
{
struct gicv5_its_dev *its_dev;
int ret;
@@ -815,6 +814,7 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
its_dev->device_id = dev_id;
its_dev->num_events = nvec;
its_dev->num_mapped_events = 0;
+ its_dev->is_iwb = is_iwb;
ret = gicv5_its_device_register(its, its_dev);
if (ret) {
@@ -827,9 +827,11 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
/*
* This is the first time we have seen this device. Hence, it is not
- * shared.
+ * shared, unless it is an IWB that is a shared ITS device by
+ * definition, its eventids are hardcoded and never change - we allocate
+ * it once for all and never free it.
*/
- its_dev->shared = false;
+ its_dev->shared = is_iwb;
its_dev->its_node = its;
@@ -859,7 +861,7 @@ static int gicv5_its_msi_prepare(struct irq_domain *domain, struct device *dev,
guard(mutex)(&its->dev_alloc_lock);
- its_dev = gicv5_its_alloc_device(its, nvec, dev_id);
+ its_dev = gicv5_its_alloc_device(its, nvec, dev_id, false);
if (IS_ERR(its_dev))
return PTR_ERR(its_dev);
@@ -937,28 +939,55 @@ static void gicv5_its_free_event(struct gicv5_its_dev *its_dev, u16 event_id)
}
static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev,
+ msi_alloc_info_t *info,
unsigned int nr_irqs, u32 *eventid)
{
- int ret;
+ int event_id_base;
- ret = bitmap_find_free_region(its_dev->event_map,
- its_dev->num_events,
- get_count_order(nr_irqs));
+ if (!its_dev->is_iwb) {
- if (ret < 0)
- return ret;
+ event_id_base = bitmap_find_free_region(
+ its_dev->event_map, its_dev->num_events,
+ get_count_order(nr_irqs));
+ if (event_id_base < 0)
+ return event_id_base;
+ } else {
+ /*
+ * We want to have a fixed EventID mapped for the IWB.
+ */
+ if (WARN(nr_irqs != 1, "IWB requesting nr_irqs != 1\n"))
+ return -EINVAL;
- *eventid = ret;
+ event_id_base = info->scratchpad[1].ul;
+
+ if (event_id_base >= its_dev->num_events) {
+ pr_err("EventID ouside of ITT range; cannot allocate an ITT entry!\n");
+
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(event_id_base, its_dev->event_map)) {
+ pr_warn("Can't reserve event_id bitmap\n");
+ return -EINVAL;
+
+ }
+ }
+
+ *eventid = event_id_base;
return 0;
+
}
static void gicv5_its_free_eventid(struct gicv5_its_dev *its_dev,
u32 event_id_base,
unsigned int nr_irqs)
{
- bitmap_release_region(its_dev->event_map, event_id_base,
+ if (!its_dev->is_iwb)
+ bitmap_release_region(its_dev->event_map, event_id_base,
get_count_order(nr_irqs));
+ else
+ clear_bit(event_id_base, its_dev->event_map);
}
static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
@@ -986,7 +1015,7 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi
return PTR_ERR(its_dev);
}
- ret = gicv5_its_alloc_eventid(its_dev, nr_irqs, &event_id_base);
+ ret = gicv5_its_alloc_eventid(its_dev, info, nr_irqs, &event_id_base);
if (ret) {
mutex_unlock(&its->dev_alloc_lock);
return ret;
@@ -994,9 +1023,12 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi
mutex_unlock(&its->dev_alloc_lock);
- ret = iommu_dma_prepare_msi(info->desc, its->its_trans_phys_base);
- if (ret)
- goto out_eventid;
+ /* IWBs are never upstream an IOMMU */
+ if (!its_dev->is_iwb) {
+ ret = iommu_dma_prepare_msi(info->desc, its->its_trans_phys_base);
+ if (ret)
+ goto out_eventid;
+ }
for (i = 0; i < nr_irqs; i++) {
lpi = gicv5_alloc_lpi();
diff --git a/drivers/irqchip/irq-gic-v5-iwb.c b/drivers/irqchip/irq-gic-v5-iwb.c
new file mode 100644
index 0000000000000000000000000000000000000000..a0ff1467f15e6f5cf969ada3309775fdc6a67d2b
--- /dev/null
+++ b/drivers/irqchip/irq-gic-v5-iwb.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
+ */
+#define pr_fmt(fmt) "GICv5 IWB: " fmt
+
+#include <linux/init.h>
+#include <linux/irqchip.h>
+#include <linux/kernel.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include "irq-gic-v5.h"
+
+static u32 iwb_readl_relaxed(struct gicv5_iwb_chip_data *iwb_node,
+ const u64 reg_offset)
+{
+ return readl_relaxed(iwb_node->iwb_base + reg_offset);
+}
+
+static void iwb_writel_relaxed(struct gicv5_iwb_chip_data *iwb_node,
+ const u32 val, const u64 reg_offset)
+{
+ writel_relaxed(val, iwb_node->iwb_base + reg_offset);
+}
+
+static int gicv5_iwb_wait_for_wenabler(struct gicv5_iwb_chip_data *iwb_node)
+{
+ int ret;
+
+ ret = gicv5_wait_for_op(iwb_node->iwb_base, GICV5_IWB_WENABLE_STATUSR,
+ GICV5_IWB_WENABLE_STATUSR_IDLE, NULL);
+ if (unlikely(ret == -ETIMEDOUT))
+ pr_err_ratelimited("IWB_WENABLE_STATUSR timeout\n");
+
+ return ret;
+}
+
+static int __gicv5_iwb_set_wire_enable(struct gicv5_iwb_chip_data *iwb_node,
+ u32 iwb_wire, bool enable)
+{
+ u32 n = iwb_wire / 32;
+ u8 i = iwb_wire % 32;
+ u32 val;
+
+ if (n >= iwb_node->nr_regs) {
+ pr_err("IWB_WENABLER<n> is invalid for n=%u\n", n);
+ return -EINVAL;
+ }
+
+ /*
+ * Enable IWB wire/pin at this point
+ * Note: This is not the same as enabling the interrupt
+ */
+ val = iwb_readl_relaxed(iwb_node, GICV5_IWB_WENABLER + (4 * n));
+ if (enable)
+ val |= BIT(i);
+ else
+ val &= ~BIT(i);
+ iwb_writel_relaxed(iwb_node, val, GICV5_IWB_WENABLER + (4 * n));
+
+ return gicv5_iwb_wait_for_wenabler(iwb_node);
+}
+
+static int gicv5_iwb_enable_wire(struct gicv5_iwb_chip_data *iwb_node,
+ u32 iwb_wire)
+{
+ return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, true);
+}
+
+static int gicv5_iwb_disable_wire(struct gicv5_iwb_chip_data *iwb_node,
+ u32 iwb_wire)
+{
+ return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, false);
+}
+
+static int gicv5_iwb_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
+ u32 iwb_wire, n, wtmr;
+ u8 i;
+
+ iwb_wire = d->hwirq;
+
+ i = iwb_wire % 32;
+ n = iwb_wire / 32;
+
+ if (n >= iwb_node->nr_regs) {
+ pr_err_once("reg %u out of range\n", n);
+ return -EINVAL;
+ }
+
+ wtmr = iwb_readl_relaxed(iwb_node, GICV5_IWB_WTMR + (4 * n));
+
+ switch (type) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ case IRQ_TYPE_LEVEL_LOW:
+ wtmr |= BIT(i);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_EDGE_FALLING:
+ wtmr &= ~BIT(i);
+ break;
+ default:
+ pr_debug("unexpected wire trigger mode");
+ return -EINVAL;
+ }
+
+ iwb_writel_relaxed(iwb_node, wtmr, GICV5_IWB_WTMR + (4 * n));
+
+ return 0;
+}
+
+static const struct irq_chip gicv5_iwb_chip = {
+ .name = "GICv5-IWB",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_type = gicv5_iwb_set_type,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND
+};
+
+static int gicv5_iwb_irq_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ irq_hw_number_t *hwirq,
+ unsigned int *type)
+{
+ if (!is_of_node(fwspec->fwnode))
+ return -EINVAL;
+
+ if (fwspec->param_count < 2)
+ return -EINVAL;
+
+ /*
+ * param[0] is be the wire
+ * param[1] is the interrupt type
+ */
+ *hwirq = fwspec->param[0];
+ *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+
+ return 0;
+}
+
+static void gicv5_iwb_irq_domain_free(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs)
+{
+ /* Free the local data, and then go up the hierarchy doing the same */
+ struct gicv5_iwb_chip_data *iwb_node = domain->host_data;
+ struct irq_data *data;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return;
+
+ data = irq_domain_get_irq_data(domain, virq);
+ gicv5_iwb_disable_wire(iwb_node, data->hwirq);
+
+ irq_domain_reset_irq_data(data);
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+}
+
+/*
+ * Our parent is the ITS, which expects MSI devices with programmable
+ * event IDs. IWB event IDs are hardcoded.
+ *
+ * Use the msi_alloc_info_t structure to convey both our DeviceID
+ * (scratchpad[0]), and the wire that we are attempting to map to an LPI in
+ * the ITT (scratchpad[1]).
+ */
+static int iwb_alloc_lpi_irq_parent(struct irq_domain *domain,
+ unsigned int virq, irq_hw_number_t hwirq)
+{
+ struct gicv5_iwb_chip_data *iwb_node = domain->host_data;
+ msi_alloc_info_t info;
+
+ info.scratchpad[0].ul = iwb_node->device_id;
+ info.scratchpad[1].ul = hwirq;
+ info.hwirq = hwirq;
+
+ return irq_domain_alloc_irqs_parent(domain, virq, 1, &info);
+}
+
+static int gicv5_iwb_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct gicv5_iwb_chip_data *iwb_node;
+ unsigned int type = IRQ_TYPE_NONE;
+ struct irq_fwspec *fwspec = arg;
+ irq_hw_number_t hwirq;
+ struct irq_data *irqd;
+ int ret;
+
+ if (WARN_ON_ONCE(nr_irqs != 1))
+ return -EINVAL;
+
+ ret = gicv5_iwb_irq_domain_translate(domain, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ if (iwb_alloc_lpi_irq_parent(domain, virq, hwirq))
+ return -EINVAL;
+
+ irqd = irq_get_irq_data(virq);
+ iwb_node = domain->host_data;
+
+ gicv5_iwb_enable_wire(iwb_node, hwirq);
+
+ irq_domain_set_info(domain, virq, hwirq, &gicv5_iwb_chip, iwb_node,
+ handle_fasteoi_irq, NULL, NULL);
+ irq_set_probe(virq);
+ irqd_set_single_target(irqd);
+
+ return 0;
+}
+
+static const struct irq_domain_ops gicv5_iwb_irq_domain_ops = {
+ .translate = gicv5_iwb_irq_domain_translate,
+ .alloc = gicv5_iwb_irq_domain_alloc,
+ .free = gicv5_iwb_irq_domain_free,
+};
+
+static struct gicv5_iwb_chip_data *
+__init gicv5_iwb_init_bases(void __iomem *iwb_base,
+ struct fwnode_handle *handle,
+ struct irq_domain *parent_domain, u32 device_id)
+{
+ struct gicv5_iwb_chip_data *iwb_node;
+ struct msi_domain_info *msi_info;
+ struct gicv5_its_chip_data *its;
+ struct gicv5_its_dev *its_dev;
+ u32 nr_wires, idr0, cr0;
+ int ret;
+
+ msi_info = msi_get_domain_info(parent_domain);
+ its = msi_info->data;
+ if (!its) {
+ pr_warn("IWB %pOF can't find parent ITS, bailing\n",
+ to_of_node(handle));
+ return ERR_PTR(-ENODEV);
+ }
+
+ iwb_node = kzalloc(sizeof(*iwb_node), GFP_KERNEL);
+ if (!iwb_node)
+ return ERR_PTR(-ENOMEM);
+
+ iwb_node->iwb_base = iwb_base;
+ iwb_node->device_id = device_id;
+
+ idr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_IDR0);
+ nr_wires = (FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1) * 32;
+
+ iwb_node->domain = irq_domain_create_hierarchy(parent_domain, 0,
+ nr_wires, handle, &gicv5_iwb_irq_domain_ops,
+ iwb_node);
+ irq_domain_update_bus_token(iwb_node->domain, DOMAIN_BUS_WIRED);
+
+ cr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_CR0);
+ if (!FIELD_GET(GICV5_IWB_CR0_IWBEN, cr0)) {
+ pr_err("IWB %s must be enabled in firmware\n",
+ fwnode_get_name(handle));
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ iwb_node->nr_regs = FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1;
+
+ for (unsigned int n = 0; n < iwb_node->nr_regs; n++)
+ iwb_writel_relaxed(iwb_node, 0, GICV5_IWB_WENABLER + (sizeof(u32) * n));
+
+ ret = gicv5_iwb_wait_for_wenabler(iwb_node);
+ if (ret)
+ goto out_free;
+
+ mutex_lock(&its->dev_alloc_lock);
+ its_dev = gicv5_its_alloc_device(its, roundup_pow_of_two(nr_wires),
+ device_id, true);
+ mutex_unlock(&its->dev_alloc_lock);
+ if (IS_ERR(its_dev)) {
+ ret = -ENODEV;
+ goto out_free;
+ }
+
+ return iwb_node;
+out_free:
+ irq_domain_remove(iwb_node->domain);
+ kfree(iwb_node);
+
+ return ERR_PTR(ret);
+}
+
+static int __init gicv5_iwb_of_init(struct device_node *node)
+{
+ struct gicv5_iwb_chip_data *iwb_node;
+ struct irq_domain *parent_domain;
+ struct device_node *parent_its;
+ struct of_phandle_args args;
+ void __iomem *iwb_base;
+ u32 device_id;
+ int ret;
+
+ iwb_base = of_io_request_and_map(node, 0, "IWB");
+ if (IS_ERR(iwb_base)) {
+ pr_err("%pOF: unable to map GICv5 IWB registers\n", node);
+ return PTR_ERR(iwb_base);
+ }
+
+ ret = of_parse_phandle_with_args(node, "msi-parent", "#msi-cells", 0,
+ &args);
+ if (ret) {
+ pr_err("%pOF: Can't retrieve deviceID\n", node);
+ goto out_unmap;
+ }
+
+ parent_its = args.np;
+ parent_domain = irq_find_matching_host(parent_its, DOMAIN_BUS_NEXUS);
+ if (!parent_domain) {
+ pr_err("Unable to find the parent ITS domain for %pOF!\n", node);
+ ret = -ENXIO;
+ goto out_put;
+ }
+
+ device_id = args.args[0];
+ pr_debug("IWB deviceID: 0x%x\n", device_id);
+
+ iwb_node = gicv5_iwb_init_bases(iwb_base, &node->fwnode, parent_domain,
+ device_id);
+ if (IS_ERR(iwb_node)) {
+ ret = PTR_ERR(iwb_node);
+ goto out_put;
+ }
+
+ return 0;
+out_put:
+ of_node_put(parent_its);
+out_unmap:
+ iounmap(iwb_base);
+ return ret;
+}
+
+void __init gicv5_iwb_of_probe(void)
+{
+ struct device_node *np;
+ int ret;
+
+ for_each_compatible_node(np, NULL, "arm,gic-v5-iwb") {
+ ret = gicv5_iwb_of_init(np);
+ if (ret)
+ pr_err("Failed to init IWB %s\n", np->full_name);
+ }
+}
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index e483d0774936035b5cf2407da9a65d776bad3138..0eaf1f40073d2659b60b3fa5ac06c66a9e4f2e2f 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1047,6 +1047,8 @@ static int __init gicv5_of_init(struct device_node *node,
gicv5_irs_its_probe();
+ gicv5_iwb_of_probe();
+
return 0;
out_int:
gicv5_cpu_disable_interrupts();
diff --git a/drivers/irqchip/irq-gic-v5.h b/drivers/irqchip/irq-gic-v5.h
index f5a453599493020b36d9c7f18c08171c51ba8669..a71504cbee2d08c8ff54fb409be72373945bc65a 100644
--- a/drivers/irqchip/irq-gic-v5.h
+++ b/drivers/irqchip/irq-gic-v5.h
@@ -238,6 +238,20 @@
#define GICV5_ITS_HWIRQ_DEVICE_ID GENMASK_ULL(31, 0)
#define GICV5_ITS_HWIRQ_EVENT_ID GENMASK_ULL(63, 32)
+#define GICV5_IWB_IDR0 0x0000
+#define GICV5_IWB_CR0 0x0080
+#define GICV5_IWB_WENABLE_STATUSR 0x00c0
+#define GICV5_IWB_WENABLER 0x2000
+#define GICV5_IWB_WTMR 0x4000
+
+#define GICV5_IWB_IDR0_INT_DOMS GENMASK(14, 11)
+#define GICV5_IWB_IDR0_IW_RANGE GENMASK(10, 0)
+
+#define GICV5_IWB_CR0_IDLE BIT(1)
+#define GICV5_IWB_CR0_IWBEN BIT(0)
+
+#define GICV5_IWB_WENABLE_STATUSR_IDLE BIT(0)
+
struct gicv5_chip_data {
struct fwnode_handle *fwnode;
struct irq_domain *ppi_domain;
@@ -348,6 +362,7 @@ struct gicv5_its_dev {
u32 num_events;
u32 num_mapped_events;
bool shared;
+ bool is_iwb;
};
void gicv5_init_lpis(u32 max);
@@ -357,4 +372,17 @@ int gicv5_alloc_lpi(void);
void gicv5_free_lpi(u32 lpi);
void __init gicv5_its_of_probe(struct device_node *parent);
+struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
+ int nvec, u32 dev_id, bool is_iwb);
+
+struct gicv5_iwb_chip_data {
+ void __iomem *iwb_base;
+ struct irq_domain *domain;
+ u64 flags;
+ u32 device_id;
+ u16 nr_regs;
+};
+
+void gicv5_iwb_of_probe(void);
+
#endif
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 22/22] arm64: Kconfig: Enable GICv5
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
` (20 preceding siblings ...)
2025-04-24 10:25 ` [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support Lorenzo Pieralisi
@ 2025-04-24 10:25 ` Lorenzo Pieralisi
21 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-24 10:25 UTC (permalink / raw)
To: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree,
Lorenzo Pieralisi
Enable GICv5 driver code for the ARM64 architecture.
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
---
arch/arm64/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index a182295e6f08bfa0f3e6f630dc4adfe797a4d273..f1b3c695b376717979ae864865238ae12ad65ca2 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -128,6 +128,7 @@ config ARM64
select ARM_GIC_V2M if PCI
select ARM_GIC_V3
select ARM_GIC_V3_ITS if PCI
+ select ARM_GIC_V5
select ARM_PSCI_FW
select BUILDTIME_TABLE_SORT
select CLONE_BACKWARDS
--
2.48.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support
2025-04-24 10:25 ` [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support Lorenzo Pieralisi
@ 2025-04-28 15:49 ` Marc Zyngier
2025-04-29 14:54 ` Lorenzo Pieralisi
0 siblings, 1 reply; 43+ messages in thread
From: Marc Zyngier @ 2025-04-28 15:49 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Thu, 24 Apr 2025 11:25:30 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> Implement GICv5 CPU interface and IRS support, to manage interrupt
> state, priority and routing for all GICv5 interrupt types.
>
> The GICv5 CPU interface implements support for PE-Private Peripheral
> Interrupts (PPI), that are handled (enabled/prioritized/delivered)
> entirely within the CPU interface hardware.
>
> To enable PPI interrupts, implement the baseline GICv5 host kernel
> driver infrastructure required to handle interrupts on a GICv5 system.
>
> Add the exception handling code path and definitions for GICv5
> instructions.
>
> Add GICv5 PPI handling code as a specific IRQ domain to:
>
> - Set-up PPI priority
> - Manage PPI configuration and state
> - Manage IRQ flow handler
> - IRQs allocation/free
> - Hook-up a PPI specific IRQchip to provide the relevant methods
>
> PPI IRQ priority is chosen as the minimum allowed priority by the
> system design (after probing the number of priority bits implemented
> by the CPU interface).
>
> The GICv5 Interrupt Routing Service (IRS) component implements
> interrupt management and routing in the GICv5 architecture.
>
> A GICv5 system comprises one or more IRSes, that together
> handle the interrupt routing and state for the system.
>
> An IRS supports Shared Peripheral Interrupts (SPIs), that are
> interrupt sources directly connected to the IRS; they do not
> rely on memory for storage. The number of supported SPIs is
> fixed for a given implementation and can be probed through IRS
> IDR registers.
>
> SPI interrupt state and routing are managed through GICv5
> instructions.
>
> Each core (PE in GICv5 terms) in a GICv5 system is identified with
> an Interrupt AFFinity ID (IAFFID).
>
> An IRS manages a set of cores that are connected to it.
>
> Firmware provides a topology description that the driver uses
> to detect to which IRS a CPU (ie an IAFFID) is associated with.
>
> Use probeable information and firmware description to initialize
> the IRSes and implement GICv5 IRS SPIs support through an
> SPI-specific IRQ domain.
>
> The GICv5 IRS driver:
>
> - Probes IRSes in the system to detect SPI ranges
> - Associates an IRS with a set of cores connected to it
> - Adds an IRQchip structure for SPI handling
>
> SPIs priority is set to a value corresponding to the lowest
> permissible priority in the system (taking into account the
> implemented priority bits of the IRS and CPU interface).
>
> Since all IRQs are set to the same priority value, the value
> itself does not matter as long as it is a valid one.
>
> An IRS supports Logical Peripheral Interrupts (LPIs) and implement
> Linux IPIs on top of it.
>
> LPIs are used for interrupt signals that are translated by a
> GICv5 ITS (Interrupt Translation Service) but also for software
> generated IRQs - namely interrupts that are not driven by a HW
> signal, ie IPIs.
>
> LPIs rely on memory storage for interrupt routing and state.
>
> Memory storage is handled by the IRS - that is configured
> at probe time by the driver with the required memory.
>
> LPIs state and routing information is kept in the Interrupt
> State Table (IST).
>
> IRSes provide support for 1- or 2-level IST tables configured
> to support a maximum number of interrupts that depend on the
> OS configuration and the HW capabilities.
>
> On systems that provide 2-level IST support, always allow
> the maximum number of LPIs; On systems with only 1-level
> support, limit the number of LPIs to 2^12 to prevent
> wasting memory (presumably a system that supports a 1-level
> only IST is not expecting a large number of interrupts).
>
> On a 2-level IST system, L2 entries are allocated on
> demand.
>
> The IST table memory is allocated using the kmalloc() interface;
> the allocation required may be smaller than a page and must be
> made up of contiguous physical pages if larger than a page.
>
> On systems where the IRS is not cache-coherent with the CPUs,
> cache mainteinance operations are executed to clean and
> invalidate the allocated memory to the point of coherency
> making it visible to the IRS components.
>
> Add an LPI IRQ domain to:
>
> - Manage LPI state and routing
> - Add LPI IRQchip structure and callbacks
> - LPI domain allocation/de-allocation
>
> On GICv5 systems, IPIs are implemented using LPIs.
>
> Implement an IPI-specific IRQ domain created as a child/subdomain
> of the LPI domain to allocate the required number of LPIs needed
> to implement the IPIs.
>
> Move the arm64 IPI enum declaration to a header file so that the
> GICv5 driver code can detect how many IPIs are required by arch code.
>
> IPIs are backed by LPIs, add LPIs allocation/de-allocation
> functions.
>
> The LPI INTID namespace is managed using an IDA to alloc/free LPI
> INTIDs.
>
> Associate an IPI irqchip with IPI IRQ descriptors to provide
> core code with the irqchip.ipi_send_single() method required
> to raise an IPI.
>
> Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> Cc: Will Deacon <will@kernel.org>
> Cc: Thomas Gleixner <tglx@linutronix.de>
> Cc: Catalin Marinas <catalin.marinas@arm.com>
> Cc: Marc Zyngier <maz@kernel.org>
> ---
> MAINTAINERS | 2 +
> arch/arm64/include/asm/arch_gicv5.h | 91 +++
> arch/arm64/include/asm/smp.h | 17 +
> arch/arm64/kernel/smp.c | 17 -
> drivers/irqchip/Kconfig | 5 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-gic-v5-irs.c | 841 ++++++++++++++++++++++++++++
> drivers/irqchip/irq-gic-v5.c | 1058 +++++++++++++++++++++++++++++++++++
> drivers/irqchip/irq-gic-v5.h | 184 ++++++
Nit: the split between include/asm/arch_gicv5.h and
drivers/irqchip/irq-gic-v5.h is pretty pointless (this is obviously
inherited from the GICv3 on 32bit setup). Given that GICv5 is strictly
arm64, this split is no longer necessary.
> 9 files changed, 2199 insertions(+), 17 deletions(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f3ed84466da19906953b5396a5f4b50e878c376e..cdeceb6782355a4a18609135bf7f03249d8b0bb5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1907,6 +1907,8 @@ M: Marc Zyngier <maz@kernel.org>
> L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> S: Maintained
> F: Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
> +F: arch/arm64/include/asm/arch_gicv5.h
> +F: drivers/irqchip/irq-gic-v5*.[ch]
>
> ARM HDLCD DRM DRIVER
> M: Liviu Dudau <liviu.dudau@arm.com>
> diff --git a/arch/arm64/include/asm/arch_gicv5.h b/arch/arm64/include/asm/arch_gicv5.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..75557fdad611fa51d7136126eb80cb861be98a8d
> --- /dev/null
> +++ b/arch/arm64/include/asm/arch_gicv5.h
> @@ -0,0 +1,91 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 ARM Ltd.
> + */
> +#ifndef __ASM_ARCH_GICV5_H
> +#define __ASM_ARCH_GICV5_H
> +
> +#include <asm/cacheflush.h>
> +#include <asm/sysreg.h>
> +
> +#ifndef __ASSEMBLY__
> +
> +#define GICV5_OP_GIC_CDAFF sys_insn(1, 0, 12, 1, 3)
> +#define GICV5_OP_GIC_CDDI sys_insn(1, 0, 12, 2, 0)
> +#define GICV5_OP_GIC_CDDIS sys_insn(1, 0, 12, 1, 0)
> +#define GICV5_OP_GIC_CDEN sys_insn(1, 0, 12, 1, 1)
> +#define GICV5_OP_GIC_CDEOI sys_insn(1, 0, 12, 1, 7)
> +#define GICV5_OP_GIC_CDPEND sys_insn(1, 0, 12, 1, 4)
> +#define GICV5_OP_GIC_CDPRI sys_insn(1, 0, 12, 1, 2)
> +#define GICV5_OP_GIC_CDRCFG sys_insn(1, 0, 12, 1, 5)
> +#define GICV5_OP_GICR_CDIA sys_insn(1, 0, 12, 3, 0)
> +
> +#define gicr_insn(insn) read_sysreg_s(insn)
> +#define gic_insn(v, insn) write_sysreg_s(v, insn)
If you are providing this sort of helpers, use them to shorten the
amount of boilerplate stuff that you need to read or write.
For example:
#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_ ## insn)
allows you to write:
ia = gicr_insn(CDIA);
> +
> +#define GSB_ACK __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 1) | 31)
> +#define GSB_SYS __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 0) | 31)
Can you please express this in terms of __SYS_BARRIER_INSN(), with a
slight rework of the SB definition? It would limit the propagation of
the 0xd5 constant and make it clear what 31 stands for.
> +
> +#define gsb_ack() asm volatile(GSB_ACK : : : "memory")
> +#define gsb_sys() asm volatile(GSB_SYS : : : "memory")
> +
> +/* Shift and mask definitions for GIC CDAFF */
> +#define GICV5_GIC_CDAFF_IAFFID_MASK GENMASK_ULL(47, 32)
> +#define GICV5_GIC_CDAFF_IAFFID(r) FIELD_GET(GICV5_GIC_CDAFF_IAFFID_MASK, r)
> +#define GICV5_GIC_CDAFF_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDAFF_TYPE(r) FIELD_GET(GICV5_GIC_CDAFF_TYPE_MASK, r)
> +#define GICV5_GIC_CDAFF_IRM_MASK BIT_ULL(28)
> +#define GICV5_GIC_CDAFF_IRM(r) FIELD_GET(GICV5_GIC_CDAFF_IRM_MASK, r)
> +#define GICV5_GIC_CDAFF_ID_MASK GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDAFF_ID(r) FIELD_GET(GICV5_GIC_CDAFF_ID_MASK, r)
> +
> +/* Shift and mask definitions for GIC CDDI */
> +#define GICV5_GIC_CDDI_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDDI_TYPE(r) FIELD_GET(GICV5_GIC_CDDI_TYPE_MASK, r)
> +#define GICV5_GIC_CDDI_ID_MASK GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDDI_ID(r) FIELD_GET(GICV5_GIC_CDDI_ID_MASK, r)
> +
> +/* Shift and mask definitions for GIC CDDIS */
> +#define GICV5_GIC_CDDIS_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDDIS_TYPE(r) FIELD_GET(GICV5_GIC_CDDIS_TYPE_MASK, r)
> +#define GICV5_GIC_CDDIS_ID_MASK GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDDIS_ID(r) FIELD_GET(GICV5_GIC_CDDIS_ID_MASK, r)
> +
> +/* Shift and mask definitions for GIC CDEN */
> +#define GICV5_GIC_CDEN_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDEN_TYPE(r) FIELD_GET(GICV5_GIC_CDEN_TYPE_MASK, r)
> +#define GICV5_GIC_CDEN_ID_MASK GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDEN_ID(r) FIELD_GET(GICV5_GIC_CDEN_ID_MASK, r)
> +
> +/* Shift and mask definitions for GIC CDPEND */
> +#define GICV5_GIC_CDPEND_PENDING_MASK BIT_ULL(32)
> +#define GICV5_GIC_CDPEND_PENDING(r) FIELD_GET(GICV5_GIC_CDPEND_PENDING_MASK, r)
> +#define GICV5_GIC_CDPEND_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDPEND_TYPE(r) FIELD_GET(GICV5_GIC_CDPEND_TYPE_MASK, r)
> +#define GICV5_GIC_CDPEND_ID_MASK GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDPEND_ID(r) FIELD_GET(GICV5_GIC_CDPEND_ID_MASK, r)
> +
> +/* Shift and mask definitions for GIC CDPRI */
> +#define GICV5_GIC_CDPRI_PRIORITY_MASK GENMASK_ULL(39, 35)
> +#define GICV5_GIC_CDPRI_PRIORITY(r) FIELD_GET(GICV5_GIC_CDPRI_PRIORITY_MASK, r)
> +#define GICV5_GIC_CDPRI_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDPRI_TYPE(r) FIELD_GET(GICV5_GIC_CDPRI_TYPE_MASK, r)
> +#define GICV5_GIC_CDPRI_ID_MASK GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDPRI_ID(r) FIELD_GET(GICV5_GIC_CDPRI_ID_MASK, r)
> +
> +/* Shift and mask definitions for GIC CDRCFG */
> +#define GICV5_GIC_CDRCFG_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDRCFG_TYPE(r) FIELD_GET(GICV5_GIC_CDRCFG_TYPE_MASK, r)
> +#define GICV5_GIC_CDRCFG_ID_MASK GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDRCFG_ID(r) FIELD_GET(GICV5_GIC_CDRCFG_ID_MASK, r)
> +
> +/* Shift and mask definitions for GICR CDIA */
> +#define GICV5_GIC_CDIA_VALID_MASK BIT_ULL(32)
> +#define GICV5_GIC_CDIA_VALID(r) FIELD_GET(GICV5_GIC_CDIA_VALID_MASK, r)
> +#define GICV5_GIC_CDIA_TYPE_MASK GENMASK_ULL(31, 29)
> +#define GICV5_GIC_CDIA_TYPE(r) FIELD_GET(GICV5_GIC_CDIA_TYPE_MASK, r)
> +#define GICV5_GIC_CDIA_ID_MASK GENMASK_ULL(23, 0)
> +#define GICV5_GIC_CDIA_ID(r) FIELD_GET(GICV5_GIC_CDIA_ID_MASK, r)
> +
> +#endif /* __ASSEMBLY__ */
> +#endif /* __ASM_ARCH_GICV5_H */
> diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
> index d6fd6efb66a673ae33825971e4aa07e791c02ee5..d48ef6d5abcc77d1c06ad8e91e72006acf662078 100644
> --- a/arch/arm64/include/asm/smp.h
> +++ b/arch/arm64/include/asm/smp.h
> @@ -50,6 +50,23 @@ struct seq_file;
> */
> extern void smp_init_cpus(void);
>
> +enum ipi_msg_type {
> + IPI_RESCHEDULE,
> + IPI_CALL_FUNC,
> + IPI_CPU_STOP,
> + IPI_CPU_STOP_NMI,
> + IPI_TIMER,
> + IPI_IRQ_WORK,
> + NR_IPI,
> + /*
> + * Any enum >= NR_IPI and < MAX_IPI is special and not tracable
> + * with trace_ipi_*
> + */
> + IPI_CPU_BACKTRACE = NR_IPI,
> + IPI_KGDB_ROUNDUP,
> + MAX_IPI
> +};
> +
> /*
> * Register IPI interrupts with the arch SMP code
> */
> diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
> index 3f3712e47c94c62836fb89cd4bfb3595fbb41557..148145979d83f67469075df1c8b5e366ffe9d907 100644
> --- a/arch/arm64/kernel/smp.c
> +++ b/arch/arm64/kernel/smp.c
> @@ -64,23 +64,6 @@ struct secondary_data secondary_data;
> /* Number of CPUs which aren't online, but looping in kernel text. */
> static int cpus_stuck_in_kernel;
>
> -enum ipi_msg_type {
> - IPI_RESCHEDULE,
> - IPI_CALL_FUNC,
> - IPI_CPU_STOP,
> - IPI_CPU_STOP_NMI,
> - IPI_TIMER,
> - IPI_IRQ_WORK,
> - NR_IPI,
> - /*
> - * Any enum >= NR_IPI and < MAX_IPI is special and not tracable
> - * with trace_ipi_*
> - */
> - IPI_CPU_BACKTRACE = NR_IPI,
> - IPI_KGDB_ROUNDUP,
> - MAX_IPI
> -};
> -
> static int ipi_irq_base __ro_after_init;
> static int nr_ipi __ro_after_init = NR_IPI;
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index cec05e443083b8982b3e72f4212d808a22883914..160a4761d5d85f6dbf36f3142fd619c114733e36 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -54,6 +54,11 @@ config ARM_GIC_V3_ITS_FSL_MC
> depends on FSL_MC_BUS
> default ARM_GIC_V3_ITS
>
> +config ARM_GIC_V5
> + bool
> + select IRQ_DOMAIN_HIERARCHY
> + select GENERIC_IRQ_EFFECTIVE_AFF_MASK if SMP
> +
Drop the 'if SMP', as arm64 is always SMP.
> config ARM_NVIC
> bool
> select IRQ_DOMAIN_HIERARCHY
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 365bcea9a61ff89e2cb41034125b3fc8cd494d81..3d9c47fa3fdf40b7452c059d84fe8ac24c91bc0f 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o
> obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o
> obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> +obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> obj-$(CONFIG_ARM_VIC) += irq-vic.o
> diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..7bd60e6d56b77c0c19a1bd9bee9685d9b6ffc959
> --- /dev/null
> +++ b/drivers/irqchip/irq-gic-v5-irs.c
> @@ -0,0 +1,841 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
> + */
> +
> +#define pr_fmt(fmt) "GICv5 IRS: " fmt
> +
> +#include <linux/iopoll.h>
> +#include <linux/irqchip.h>
> +#include <linux/log2.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +
> +#include "irq-gic-v5.h"
Why the ""? The normal include path (linux/irqchip) should work.
> +
> +#define LPI_ID_BITS_LINEAR 12
> +
> +#define IRS_FLAGS_NON_COHERENT BIT(0)
> +
> +static DEFINE_PER_CPU(struct gicv5_irs_chip_data *, per_cpu_irs_data);
> +static LIST_HEAD(irs_nodes);
> +
> +static u32 irs_readl_relaxed(struct gicv5_irs_chip_data *irs_data,
> + const u64 reg_offset)
> +{
> + return readl_relaxed(irs_data->irs_base + reg_offset);
> +}
> +
> +static void irs_writel_relaxed(struct gicv5_irs_chip_data *irs_data,
> + const u32 val, const u64 reg_offset)
> +{
> + writel_relaxed(val, irs_data->irs_base + reg_offset);
> +}
> +
> +static u64 irs_readq_relaxed(struct gicv5_irs_chip_data *irs_data,
> + const u64 reg_offset)
> +{
> + return readq_relaxed(irs_data->irs_base + reg_offset);
> +}
> +
> +static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
> + const u64 val, const u64 reg_offset)
> +{
> + writeq_relaxed(val, irs_data->irs_base + reg_offset);
> +}
> +
> +static int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask, u32 *val)
> +{
> + void __iomem *reg = addr + offset;
> + u32 tmp;
> + int ret;
> +
> + ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
> +
> + if (val)
> + *val = tmp;
Updating the result value on error is rather odd. Do you rely on this
anywhere? If not, consider avoiding the write-back on timeout.
> +
> + return ret;
> +}
> +
> +#define gicv5_irs_wait_for_op(base, reg, mask) \
> + ({ \
> + int ret; \
> + \
> + ret = gicv5_wait_for_op(base, reg, mask, NULL); \
> + if (unlikely(ret == -ETIMEDOUT)) \
> + pr_err_ratelimited(#reg" timeout...\n"); \
> + ret; \
> + })
> +
> +/* Wait for completion of an IST change */
> +static int gicv5_irs_ist_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
> +{
> + return gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_IST_STATUSR,
> + GICV5_IRS_IST_STATUSR_IDLE);
> +}
> +
> +static int __init gicv5_irs_init_ist_linear(struct gicv5_irs_chip_data *irs_data,
> + unsigned int lpi_id_bits,
> + unsigned int istsz)
> +{
> + size_t l2istsz;
> + u32 n, cfgr;
> + void *ist;
> + u64 baser;
> + int ret;
> +
> + /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */
> + n = max(5, lpi_id_bits + 1 + istsz);
> +
> + l2istsz = BIT(n + 1);
> + /*
> + * Check memory requirements. For a linear IST we cap the
> + * number of ID bits to a value that should never exceed
> + * kmalloc interface memory allocation limits, so this
> + * check is really belt and braces.
> + */
> + if (l2istsz > KMALLOC_MAX_SIZE) {
> + u8 lpi_id_cap = ilog2(KMALLOC_MAX_SIZE) - 2 + istsz;
> +
> + pr_warn("Limiting LPI ID bits from %u to %u\n",
> + lpi_id_bits, lpi_id_cap);
> + lpi_id_bits = lpi_id_cap;
> + l2istsz = KMALLOC_MAX_SIZE;
> + }
> +
> + ist = kzalloc(l2istsz, GFP_KERNEL);
> + if (!ist)
> + return -ENOMEM;
> +
> + if (irs_data->flags & IRS_FLAGS_NON_COHERENT)
> + dcache_clean_inval_poc((unsigned long)ist,
> + (unsigned long)ist + l2istsz);
> + else
> + dsb(ishst);
> +
> + cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE,
> + GICV5_IRS_IST_CFGR_STRUCTURE_LINEAR) |
> + FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) |
> + FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ,
> + GICV5_IRS_IST_CFGR_L2SZ_4K) |
> + FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits);
> + irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR);
> +
> + gicv5_global_data.ist.l2 = false;
> +
> + baser = (virt_to_phys(ist) & GICV5_IRS_IST_BASER_ADDR_MASK) |
> + FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1);
> + irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER);
> +
> + /*
> + * The polling wait (in gicv5_wait_for_op()) on a GIC register
> + * provides the memory barriers (through MMIO accessors)
> + * required to synchronize CPU and GIC access to IST memory.
> + */
This comment would be better placed with the helper itself, and avoid
the repeats that I can see in the rest of the code.
> + ret = gicv5_irs_ist_wait_for_idle(irs_data);
> + if (ret) {
> + kfree(ist);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int __init gicv5_irs_init_ist_two_level(struct gicv5_irs_chip_data *irs_data,
> + unsigned int lpi_id_bits,
> + unsigned int istsz,
> + unsigned int l2sz)
> +{
> + __le64 *l1ist;
> + u32 cfgr, n;
> + size_t l1sz;
> + u64 baser;
> + int ret;
> +
> + /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */
> + n = max(5, lpi_id_bits - ((10 - istsz) + (2 * l2sz)) + 2);
> +
> + l1sz = BIT(n + 1);
> +
> + l1ist = kzalloc(l1sz, GFP_KERNEL);
> + if (!l1ist)
> + return -ENOMEM;
> +
> + if (irs_data->flags & IRS_FLAGS_NON_COHERENT)
> + dcache_clean_inval_poc((unsigned long)l1ist,
> + (unsigned long)l1ist + l1sz);
> + else
> + dsb(ishst);
> +
> + cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE,
> + GICV5_IRS_IST_CFGR_STRUCTURE_TWO_LEVEL) |
> + FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) |
> + FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ, l2sz) |
> + FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits);
> + irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR);
> +
> + /*
> + * The L2SZ determine bits required at L2 level. Number of bytes
> + * required by metadata is reported through istsz - the number of bits
> + * covered by L2 entries scales accordingly.
> + */
> + gicv5_global_data.ist.l2_size = BIT(11 + (2 * l2sz) + 1);
> + gicv5_global_data.ist.l2_bits = (10 - istsz) + (2 * l2sz);
> + gicv5_global_data.ist.l1ist_addr = l1ist;
> + gicv5_global_data.ist.l2 = true;
> +
> + baser = (virt_to_phys(l1ist) & GICV5_IRS_IST_BASER_ADDR_MASK) |
> + FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1);
> + irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER);
> +
> + /*
> + * The polling wait (in gicv5_wait_for_op()) on a GIC register
> + * provides the memory barriers (through MMIO accessors)
> + * required to synchronize CPU and GIC access to IST memory.
> + */
> + ret = gicv5_irs_ist_wait_for_idle(irs_data);
> + if (ret) {
> + kfree(l1ist);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Alloc L2 IST entries on demand.
> + *
> + * Locking/serialization is guaranteed by irqdomain core code by
> + * taking the hierarchical domain struct irq_domain.root->mutex.
> + */
> +int gicv5_irs_iste_alloc(const u32 lpi)
> +{
> + struct gicv5_irs_chip_data *irs_data;
> + unsigned int index;
> + u32 l2istr, l2bits;
> + __le64 *l1ist;
> + size_t l2size;
> + void *l2ist;
> + int ret;
> +
> + if (!gicv5_global_data.ist.l2)
> + return 0;
> +
> + irs_data = per_cpu(per_cpu_irs_data, smp_processor_id());
> + if (!irs_data)
> + return -ENOENT;
> +
> + l2size = gicv5_global_data.ist.l2_size;
> + l2bits = gicv5_global_data.ist.l2_bits;
> +
> + l1ist = gicv5_global_data.ist.l1ist_addr;
> +
> + index = lpi >> l2bits;
> +
> + if (FIELD_GET(GICV5_ISTL1E_VALID, le64_to_cpu(l1ist[index])))
> + return 0;
> +
> + l2ist = kzalloc(l2size, GFP_KERNEL);
> + if (!l2ist)
> + return -ENOMEM;
> +
> + l1ist[index] = cpu_to_le64(virt_to_phys(l2ist) & GICV5_ISTL1E_L2_ADDR_MASK);
> +
> + if (irs_data->flags & IRS_FLAGS_NON_COHERENT) {
> + dcache_clean_inval_poc((unsigned long)l2ist,
> + (unsigned long)l2ist + l2size);
> + dcache_clean_poc((unsigned long)(l1ist + index),
> + (unsigned long)(l1ist + index) + sizeof(*l1ist));
> + } else {
> + dsb(ishst);
> + }
> +
> + l2istr = FIELD_PREP(GICV5_IRS_MAP_L2_ISTR_ID, lpi);
> + irs_writel_relaxed(irs_data, l2istr, GICV5_IRS_MAP_L2_ISTR);
> +
> + /*
> + * The polling wait (in gicv5_wait_for_op()) on a GIC register
> + * provides the memory barriers (through MMIO accessors)
> + * required to synchronize CPU and GIC access to IST memory.
> + */
> + ret = gicv5_irs_ist_wait_for_idle(irs_data);
> + if (ret) {
> + l1ist[index] = 0;
> + kfree(l2ist);
> + return ret;
> + }
> +
> + /*
> + * Make sure we invalidate the cache line pulled before the IRS
> + * had a chance to update the L1 entry and mark it valid.
> + */
> + if (irs_data->flags & IRS_FLAGS_NON_COHERENT) {
> + /*
> + * gicv5_irs_ist_wait_for_idle() includes memory
> + * barriers (MMIO accessors) required to guarantee that the
> + * following dcache invalidation is not executed before the
> + * IST mapping operation has completed.
> + */
> + dcache_inval_poc((unsigned long)(l1ist + index),
> + (unsigned long)(l1ist + index) + sizeof(*l1ist));
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Try to match the L2 IST size to the pagesize, and if this is not possible
> + * pick the smallest supported L2 size in order to minimise the requirement for
> + * physically contiguous blocks of memory as page-sized allocations are
> + * guaranteed to be physically contiguous, and are by definition the easiest to
> + * find.
> + *
> + * Fall back to the smallest supported size (in the event that the pagesize
> + * itself is not supported) again serves to make it easier to find physically
> + * contiguous blocks of memory.
> + */
> +static int gicv5_irs_l2_sz(u32 idr2)
> +{
> + switch (PAGE_SIZE) {
> + case SZ_64K:
> + if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
> + return GICV5_IRS_IST_CFGR_L2SZ_64K;
> + fallthrough;
> + case SZ_16K:
> + if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
> + return GICV5_IRS_IST_CFGR_L2SZ_16K;
> + fallthrough;
> + case SZ_4K:
> + if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
> + return GICV5_IRS_IST_CFGR_L2SZ_4K;
> + if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
> + return GICV5_IRS_IST_CFGR_L2SZ_16K;
> + if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
> + return GICV5_IRS_IST_CFGR_L2SZ_64K;
> + break;
> + }
> +
> + return -ENODEV;
> +}
Really, it shouldn't be possible to return an error here. I think this
should be rewritten as:
static unsigned int gicv5_irs_l2_sz(u32 idr2)
{
switch (PAGE_SIZE) {
case SZ_64K:
if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
return GICV5_IRS_IST_CFGR_L2SZ_64K;
fallthrough;
case SZ_16K:
if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
return GICV5_IRS_IST_CFGR_L2SZ_16K;
fallthrough;
case SZ_4K:
if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
return GICV5_IRS_IST_CFGR_L2SZ_4K;
break;
}
if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
return GICV5_IRS_IST_CFGR_L2SZ_16K;
return GICV5_IRS_IST_CFGR_L2SZ_64K;
}
and fix the sole call site accordingly.
> +
> +static int __init gicv5_irs_init_ist(struct gicv5_irs_chip_data *irs_data)
> +{
> + u32 lpi_id_bits, idr2_id_bits, idr2_min_lpi_id_bits,
> + l2_iste_sz, l2sz, l2_iste_sz_split, idr2;
> + bool two_levels, istmd;
> + u64 baser;
> + int ret;
> +
> + baser = irs_readq_relaxed(irs_data, GICV5_IRS_IST_BASER);
> + if (FIELD_GET(GICV5_IRS_IST_BASER_VALID, baser)) {
> + pr_err("IST is marked as valid already; cannot allocate\n");
> + return -EPERM;
> + }
> +
> + idr2 = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2);
> +
> + two_levels = !!FIELD_GET(GICV5_IRS_IDR2_IST_LEVELS, idr2);
> +
> + idr2_id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, idr2);
> + idr2_min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, idr2);
> +
> + /*
> + * For two level tables we are always allocating the maximum allowed
> + * number of IDs.
nit: isn't it the maximum number of L1 entries, rather than the
maximum number if IDs?
> + *
> + * For 1-level tables, we should allocate a number of bits that
> + * is >= min_lpi_id_bits but cap it to LPI_ID_BITS_LINEAR lest
> + * the level 1-table gets too large and its memory allocation
> + * may fail.
> + */
> + if (two_levels) {
> + lpi_id_bits = idr2_id_bits;
> + } else {
> + lpi_id_bits = max(LPI_ID_BITS_LINEAR, idr2_min_lpi_id_bits);
> + lpi_id_bits = min(lpi_id_bits, idr2_id_bits);
> + }
> +
> + /*
> + * Cap the ID bits according to the CPUIF supported ID bits
> + */
> + lpi_id_bits = min(lpi_id_bits, gicv5_global_data.cpuif_id_bits);
> +
> + if (two_levels) {
> + l2sz = gicv5_irs_l2_sz(idr2);
> + if (l2sz < 0)
> + return l2sz;
> + }
> +
> + istmd = !!FIELD_GET(GICV5_IRS_IDR2_ISTMD, idr2);
> +
> + l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_4;
> +
> + // Only supported if IRS_IDR2.ISTMD is 1
> + if (istmd) {
> + l2_iste_sz_split = FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, idr2);
> +
> + if (lpi_id_bits < l2_iste_sz_split)
> + l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_8;
> + else
> + l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_16;
> + }
> +
> + /*
> + * Follow GICv5 specification recommendation to opt in for two
> + * level tables
> + */
> + two_levels = two_levels && (lpi_id_bits > ((10 - l2_iste_sz) + (2 * l2sz)));
> +
> + if (two_levels)
This is slightly confusing, as you are reusing the two_levels variable
to mean something rather different (you go from "is 2 level supported"
to "is 2 level a good idea").
Consider simply expanding the condition in the if () statement.
> + ret = gicv5_irs_init_ist_two_level(irs_data, lpi_id_bits,
> + l2_iste_sz, l2sz);
> + else
> + ret = gicv5_irs_init_ist_linear(irs_data, lpi_id_bits,
> + l2_iste_sz);
> + if (ret)
> + return ret;
> +
> + gicv5_init_lpis(BIT(lpi_id_bits));
> +
> + return 0;
> +}
> +
> +struct iaffid_entry {
> + u16 iaffid;
> + bool valid;
> +};
> +
> +static DEFINE_PER_CPU(struct iaffid_entry, cpu_iaffid);
> +
> +int gicv5_irs_cpu_to_iaffid(int cpuid, u16 *iaffid)
> +{
> + if (!per_cpu(cpu_iaffid, cpuid).valid) {
> + pr_err("IAFFID for CPU %d has not been initialised\n", cpuid);
> + return -ENODEV;
> + }
> +
> + *iaffid = per_cpu(cpu_iaffid, cpuid).iaffid;
> +
> + return 0;
> +}
> +
> +struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id)
> +{
> + struct gicv5_irs_chip_data *irs_data;
> + u32 min, max;
> +
> + list_for_each_entry(irs_data, &irs_nodes, entry) {
> + if (!irs_data->spi_range)
> + continue;
> +
> + min = irs_data->spi_min;
> + max = irs_data->spi_min + irs_data->spi_range - 1;
> + if (spi_id >= min && spi_id <= max)
> + return irs_data;
> + }
Funnily enough, this is exactly the sort of iterative searches the
maple tree could have avoided, by storing INTID (and range) specific
data. Never mind.
> +
> + return NULL;
> +}
> +
> +static int gicv5_irs_wait_for_spi_op(struct gicv5_irs_chip_data *irs_data)
> +{
> + u32 statusr;
> + int ret;
> +
> + ret = gicv5_wait_for_op(irs_data->irs_base, GICV5_IRS_SPI_STATUSR,
> + GICV5_IRS_SPI_STATUSR_IDLE, &statusr);
> + if (unlikely(ret == -ETIMEDOUT)) {
> + pr_err_ratelimited("IRS_SPI_STATUSR timeout\n");
You could simply have a helper similar to gicv5_irs_wait_for_op() that
deals with the printing stuff. This would result in more homogeneous
messages and less boilerplate code.
> + return ret;
> + }
> +
> + return !!FIELD_GET(GICV5_IRS_SPI_STATUSR_V, statusr) ? 0 : -ENXIO;
> +}
> +
> +static int gicv5_irs_wait_for_irs_pe(struct gicv5_irs_chip_data *irs_data,
> + bool selr)
> +{
> + u32 statusr;
> + bool valid;
> + int ret;
> +
> + ret = gicv5_wait_for_op(irs_data->irs_base, GICV5_IRS_PE_STATUSR,
> + GICV5_IRS_PE_STATUSR_IDLE, &statusr);
> +
> + if (unlikely(ret == -ETIMEDOUT)) {
> + pr_err_ratelimited("IRS_PE_STATUSR timeout after %s\n",
> + selr ? "IRS_PE_SELR" : "IRS_PE_CR0");
> + return ret;
Is there a real value in specialising this based on the point we're
coming from?
> + }
> +
> + if (selr) {
> + valid = !!FIELD_GET(GICV5_IRS_PE_STATUSR_V, statusr);
> + return valid ? 0 : -ENXIO;
> + }
> +
> + return 0;
nit: maybe simplify as:
bool valid == true;
[...]
if (selr)
valid = !!FIELD_GET(GICV5_IRS_PE_STATUSR_V, statusr);
return valid ? 0 : -ENXIO;
> +}
> +
> +static int gicv5_irs_wait_for_pe_selr(struct gicv5_irs_chip_data *irs_data)
> +{
> + return gicv5_irs_wait_for_irs_pe(irs_data, true);
> +}
> +
> +static int gicv5_irs_wait_for_pe_cr0(struct gicv5_irs_chip_data *irs_data)
> +{
> + return gicv5_irs_wait_for_irs_pe(irs_data, false);
> +}
> +
> +int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type)
> +{
> + struct gicv5_irs_chip_data *irs_data = d->chip_data;
> + u32 selr, cfgr;
> + bool level;
> +
> + switch (type) {
> + case IRQ_TYPE_EDGE_RISING:
> + case IRQ_TYPE_EDGE_FALLING:
> + level = false;
> + break;
> + case IRQ_TYPE_LEVEL_HIGH:
> + case IRQ_TYPE_LEVEL_LOW:
> + level = true;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + guard(raw_spinlock)(&irs_data->spi_config_lock);
> +
> + selr = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, d->hwirq);
> + irs_writel_relaxed(irs_data, selr, GICV5_IRS_SPI_SELR);
> + if (gicv5_irs_wait_for_spi_op(irs_data))
> + return -EIO;
> +
> + cfgr = FIELD_PREP(GICV5_IRS_SPI_CFGR_TM, level);
> +
> + irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_SPI_CFGR);
> + if (gicv5_irs_wait_for_spi_op(irs_data))
> + return -EPERM;
-EPERM is an odd return code. Is the expectation that the update
operation can fail for another reason than "the HW has deadlocked"?
If that's not the case, then something similar to the write to
SPI_SELR is probably best.
If there is a genuine "permission check" aspect, then we should be
able to distinguish between the two.
> +
> + return 0;
> +}
> +
> +static int gicv5_irs_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
> +{
> + return gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_CR0,
> + GICV5_IRS_CR0_IDLE);
> +}
> +
> +int gicv5_irs_register_cpu(int cpuid)
> +{
> + struct gicv5_irs_chip_data *irs_data;
> + u32 selr, cr0;
> + u16 iaffid;
> + int ret;
> +
> + ret = gicv5_irs_cpu_to_iaffid(cpuid, &iaffid);
> + if (ret) {
> + pr_err("IAFFID for CPU %d has not been initialised\n", cpuid);
> + return ret;
> + }
> +
> + irs_data = per_cpu(per_cpu_irs_data, cpuid);
> + if (!irs_data) {
> + pr_err("No IRS associated with CPU %u\n", cpuid);
> + return -ENXIO;
> + }
> +
> + selr = FIELD_PREP(GICV5_IRS_PE_SELR_IAFFID, iaffid);
> + irs_writel_relaxed(irs_data, selr, GICV5_IRS_PE_SELR);
> +
> + ret = gicv5_irs_wait_for_pe_selr(irs_data);
> + if (ret) {
> + pr_err("IAFFID 0x%x used in IRS_PE_SELR is invalid\n", iaffid);
> + return -ENXIO;
> + }
> +
> + cr0 = FIELD_PREP(GICV5_IRS_PE_CR0_DPS, 0x1);
> + irs_writel_relaxed(irs_data, cr0, GICV5_IRS_PE_CR0);
> +
> + ret = gicv5_irs_wait_for_pe_cr0(irs_data);
> + if (ret)
> + return ret;
> +
> + pr_debug("CPU%d enabled PE IAFFID 0x%x\n", cpuid, iaffid);
> +
> + return 0;
> +}
> +
> +static int __init gicv5_irs_init_bases(struct gicv5_irs_chip_data *irs_data,
> + void __iomem *irs_base,
> + struct fwnode_handle *handle)
> +{
> + struct device_node *np = to_of_node(handle);
> + u32 cr0, cr1;
> +
> + irs_data->fwnode = handle;
> + irs_data->irs_base = irs_base;
> +
> + if (of_property_read_bool(np, "dma-noncoherent")) {
> + /*
> + * A non-coherent IRS implies that some cache levels cannot be
> + * used coherently by the cores and GIC. Our only option is to mark
> + * memory attributes for the GIC as non-cacheable; by default,
> + * non-cacheable memory attributes imply outer-shareable
> + * shareability, the value written into IRS_CR1_SH is ignored.
> + */
> + cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_NO_WRITE_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_NO_WRITE_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_NO_WRITE_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_NON_CACHE) |
> + FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_NON_CACHE);
> + irs_data->flags |= IRS_FLAGS_NON_COHERENT;
> + } else {
> + cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_WRITE_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_WRITE_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_WRITE_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_WB_CACHE) |
> + FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_WB_CACHE) |
> + FIELD_PREP(GICV5_IRS_CR1_SH, GICV5_INNER_SHARE);
> + }
> +
> + irs_writel_relaxed(irs_data, cr1, GICV5_IRS_CR1);
> +
> + cr0 = FIELD_PREP(GICV5_IRS_CR0_IRSEN, 0x1);
> + irs_writel_relaxed(irs_data, cr0, GICV5_IRS_CR0);
> + gicv5_irs_wait_for_idle(irs_data);
> +
> + return 0;
> +}
> +
> +static int __init gicv5_irs_of_init_affinity(struct device_node *node,
> + struct gicv5_irs_chip_data *irs_data,
> + u8 iaffid_bits)
> +{
> + /*
> + * Detect IAFFID<->CPU mappings from the device tree and
> + * record IRS<->CPU topology information.
> + */
> + u16 iaffid_mask = GENMASK(iaffid_bits - 1, 0);
> + u16 *iaffids __free(kfree) = NULL;
> + int ret, i, ncpus, niaffids;
> +
> + ncpus = of_property_count_elems_of_size(node, "cpus", sizeof(u32));
> + if (ncpus < 0)
> + return -EINVAL;
> +
> + niaffids = of_property_count_elems_of_size(node, "arm,iaffids",
> + sizeof(u16));
> + if (niaffids != ncpus)
> + return -EINVAL;
> +
> + iaffids = kcalloc(niaffids, sizeof(*iaffids), GFP_KERNEL);
> + if (!iaffids)
> + return -ENOMEM;
> +
> + ret = of_property_read_u16_array(node, "arm,iaffids", iaffids, niaffids);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < ncpus; i++) {
> + struct device_node *cpu_node;
> + u32 cpu_phandle;
> + int cpu;
> +
> + if (of_property_read_u32_index(node, "cpus", i, &cpu_phandle))
> + continue;
> +
> + cpu_node = of_find_node_by_phandle(cpu_phandle);
> + if (WARN_ON(!cpu_node))
> + continue;
> +
> + cpu = of_cpu_node_to_id(cpu_node);
> + of_node_put(cpu_node);
> + if (WARN_ON(cpu < 0))
> + continue;
> +
> + if (iaffids[i] & ~iaffid_mask) {
> + pr_warn("CPU %d iaffid 0x%x exceeds IRS iaffid bits\n",
> + cpu, iaffids[i]);
> + continue;
> + }
> +
> + per_cpu(cpu_iaffid, cpu).iaffid = iaffids[i];
> + per_cpu(cpu_iaffid, cpu).valid = true;
> +
> + // We also know that the CPU is connected to this IRS
> + per_cpu(per_cpu_irs_data, cpu) = irs_data;
> + }
> +
> + return ret;
> +}
> +
> +static void irs_setup_pri_bits(u32 idr1)
> +{
> + switch (FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1)) {
> + case GICV5_IRS_IDR1_PRIORITY_BITS_1BITS:
> + gicv5_global_data.irs_pri_bits = 1;
> + break;
> + case GICV5_IRS_IDR1_PRIORITY_BITS_2BITS:
> + gicv5_global_data.irs_pri_bits = 2;
> + break;
> + case GICV5_IRS_IDR1_PRIORITY_BITS_3BITS:
> + gicv5_global_data.irs_pri_bits = 3;
> + break;
> + case GICV5_IRS_IDR1_PRIORITY_BITS_4BITS:
> + gicv5_global_data.irs_pri_bits = 4;
> + break;
> + case GICV5_IRS_IDR1_PRIORITY_BITS_5BITS:
> + gicv5_global_data.irs_pri_bits = 5;
> + break;
> + default:
> + pr_warn("Detected wrong IDR priority bits value 0x%lx\n",
> + FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1));
Please assign a default value. I don't feel confident leaving this
uninitialised.
> + break;
> + }
> +}
> +
> +static int __init gicv5_irs_init(struct device_node *node)
> +{
> + struct gicv5_irs_chip_data *irs_data;
> + void __iomem *irs_base;
> + u8 iaffid_bits;
> + int ret;
> + u32 idr;
> +
> + irs_data = kzalloc(sizeof(*irs_data), GFP_KERNEL);
> + if (!irs_data)
> + return -ENOMEM;
> +
> + raw_spin_lock_init(&irs_data->spi_config_lock);
> +
> + irs_base = of_io_request_and_map(node, 0, "IRS");
> + if (IS_ERR(irs_base)) {
> + pr_err("%pOF: unable to map GICv5 IRS registers\n", node);
> + ret = PTR_ERR(irs_base);
> + goto out_err;
> + }
> +
> + gicv5_irs_init_bases(irs_data, irs_base, &node->fwnode);
Make this function return void, since it never fails and its return
value is never checked either.
> +
> + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
> + iaffid_bits = FIELD_GET(GICV5_IRS_IDR1_IAFFID_BITS, idr) + 1;
> +
> + ret = gicv5_irs_of_init_affinity(node, irs_data, iaffid_bits);
> + if (ret) {
> + pr_err("Failed to parse CPU IAFFIDs from the device tree!\n");
> + goto out_iomem;
> + }
> +
> + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2);
> + if (WARN(!FIELD_GET(GICV5_IRS_IDR2_LPI, idr),
> + "LPI support not available - no IPIs, can't proceed\n")) {
> + ret = -ENODEV;
> + goto out_iomem;
> + }
> +
> + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR7);
> + irs_data->spi_min = FIELD_GET(GICV5_IRS_IDR7_SPI_BASE, idr);
> +
> + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR6);
> + irs_data->spi_range = FIELD_GET(GICV5_IRS_IDR6_SPI_IRS_RANGE, idr);
> +
> + if (irs_data->spi_range) {
> + pr_info("%s detected SPI range [%u-%u]\n",
> + of_node_full_name(node),
> + irs_data->spi_min,
> + irs_data->spi_min +
> + irs_data->spi_range - 1);
> + }
> +
> + /*
> + * Do the global setting only on the first IRS.
> + * Global properties (iaffid_bits, global spi count) are guaranteed to
> + * be consistent across IRSes by the architecture.
> + */
> + if (list_empty(&irs_nodes)) {
> +
> + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
> + irs_setup_pri_bits(idr);
> +
> + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR5);
> + gicv5_global_data.global_spi_count =
> + FIELD_GET(GICV5_IRS_IDR5_SPI_RANGE, idr);
> +
> + gicv5_init_lpi_domain();
> +
> + pr_debug("Detected %u SPIs globally\n",
> + gicv5_global_data.global_spi_count);
> + }
> +
> + list_add_tail(&irs_data->entry, &irs_nodes);
> +
> + return 0;
> +out_iomem:
> + iounmap(irs_base);
> +out_err:
> + kfree(irs_data);
> + return ret;
> +}
> +
> +void __init gicv5_irs_remove(void)
> +{
> + struct gicv5_irs_chip_data *irs_data, *tmp_data;
> +
> + gicv5_free_lpi_domain();
> + gicv5_deinit_lpis();
> +
> + list_for_each_entry_safe(irs_data, tmp_data, &irs_nodes, entry) {
> + iounmap(irs_data->irs_base);
> + list_del(&irs_data->entry);
> + kfree(irs_data);
> + }
> +}
> +
> +int __init gicv5_irs_enable(void)
> +{
> + struct gicv5_irs_chip_data *irs_data;
> + int ret;
> +
> + irs_data = list_first_entry_or_null(&irs_nodes,
> + struct gicv5_irs_chip_data, entry);
> + if (!irs_data)
> + return -ENODEV;
> +
> + ret = gicv5_irs_init_ist(irs_data);
> + if (ret) {
> + pr_err("Failed to init IST\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +int __init gicv5_irs_of_probe(struct device_node *parent)
> +{
> + struct device_node *np;
> + int ret;
> +
> + for_each_available_child_of_node(parent, np) {
> + if (!of_device_is_compatible(np, "arm,gic-v5-irs"))
> + continue;
> +
> + ret = gicv5_irs_init(np);
> + if (ret)
> + pr_err("Failed to init IRS %s\n", np->full_name);
> + }
> +
> + return list_empty(&irs_nodes) ? -ENODEV : 0;
> +}
Just a passing comment: consider splitting this patch in two (IRS on
one side, CPUif on the other). I'm only half-way through, and it's
quite tiring... :-/
> diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..c4d4e85382f672fa4ae334db1a4e4c7c4f46b9fe
> --- /dev/null
> +++ b/drivers/irqchip/irq-gic-v5.c
> @@ -0,0 +1,1058 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
> + */
> +
> +#define pr_fmt(fmt) "GICv5: " fmt
> +
> +#include <linux/cpuhotplug.h>
> +#include <linux/idr.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqdomain.h>
> +#include <linux/slab.h>
> +#include <linux/wordpart.h>
> +
> +#include <asm/cpufeature.h>
> +#include <asm/exception.h>
> +
> +#include "irq-gic-v5.h"
> +
> +static u8 pri_bits = 5;
> +#define GICV5_IRQ_PRI_MASK 0x1f
> +#define GICV5_IRQ_PRI_MI \
> + (GICV5_IRQ_PRI_MASK & GENMASK(4, 5 - pri_bits))
> +
> +static bool gicv5_cpuif_has_gcie(void)
> +{
> + return this_cpu_has_cap(ARM64_HAS_GICV5_CPUIF);
> +}
> +
> +struct gicv5_chip_data gicv5_global_data __read_mostly;
> +
> +static DEFINE_IDA(lpi_ida);
> +static u32 num_lpis;
> +
> +void __init gicv5_init_lpis(u32 lpis)
> +{
> + num_lpis = lpis;
> +}
> +
> +void __init gicv5_deinit_lpis(void)
> +{
> + num_lpis = 0;
> +}
> +
> +static int alloc_lpi(void)
> +{
> + if (!num_lpis)
> + return -ENOSPC;
> +
> + return ida_alloc_max(&lpi_ida, num_lpis - 1, GFP_KERNEL);
> +}
> +
> +static void release_lpi(u32 lpi)
> +{
> + ida_free(&lpi_ida, lpi);
> +}
> +
> +static int gicv5_alloc_lpi(void)
> +{
> + return alloc_lpi();
> +}
> +
> +static void gicv5_free_lpi(u32 lpi)
> +{
> + release_lpi(lpi);
> +}
> +
> +static void gicv5_ppi_priority_init(void)
> +{
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR0_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR1_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR2_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR3_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR4_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR5_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR6_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR7_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR8_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR9_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR10_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR11_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR12_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR13_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR14_EL1);
> + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR15_EL1);
> +
> + /*
> + * Context syncronization required to mare system
> + * register writes effects are synchronized
synchroni[sz]ation? Not sure what "mare" is supposed to be here, but
I can't see any horse.
But I don't think there is much point in saying that sysreg accesses
need synchronisation, if that's what this is meant to say.
> + */
> + isb();
> +}
> +
> +static void gicv5_hwirq_init(irq_hw_number_t hwirq, u8 priority, u8 hwirq_type)
> +{
> + u64 cdpri, cdaff;
> + u16 iaffid;
> + int ret;
> +
> + if (hwirq_type == GICV5_HWIRQ_TYPE_LPI || hwirq_type == GICV5_HWIRQ_TYPE_SPI) {
> + cdpri = FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK, priority) |
> + FIELD_PREP(GICV5_GIC_CDPRI_TYPE_MASK, hwirq_type) |
> + FIELD_PREP(GICV5_GIC_CDPRI_ID_MASK, hwirq);
> + gic_insn(cdpri, GICV5_OP_GIC_CDPRI);
> +
> + ret = gicv5_irs_cpu_to_iaffid(smp_processor_id(), &iaffid);
> +
> + if (WARN_ON_ONCE(ret))
> + return;
> +
> + cdaff = FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, iaffid) |
> + FIELD_PREP(GICV5_GIC_CDAFF_TYPE_MASK, hwirq_type) |
> + FIELD_PREP(GICV5_GIC_CDAFF_ID_MASK, hwirq);
> + gic_insn(cdaff, GICV5_OP_GIC_CDAFF);
> + }
> +}
> +
> +static void gicv5_ppi_irq_mask(struct irq_data *d)
> +{
> + u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> +
> + if (d->hwirq < 64)
> + sysreg_clear_set_s(SYS_ICC_PPI_ENABLER0_EL1, hwirq_id_bit, 0);
> + else
> + sysreg_clear_set_s(SYS_ICC_PPI_ENABLER1_EL1, hwirq_id_bit, 0);
> +
> + /*
> + * Ensure that the disable takes effect
> + */
It would be more interesting if you indicated *why* we want immediate
effect on mask, while we don't have the same requirement on unmask.
> + isb();
> +}
> +
> +static void gicv5_iri_irq_mask(struct irq_data *d, u8 hwirq_type)
> +{
> + u64 cddis = d->hwirq | FIELD_PREP(GICV5_GIC_CDDIS_TYPE_MASK, hwirq_type);
> +
> + gic_insn(cddis, GICV5_OP_GIC_CDDIS);
> + /*
> + * We must make sure that GIC CDDIS write effects are propagated
> + */
> + gsb_sys();
> +}
> +
> +static void gicv5_spi_irq_mask(struct irq_data *d)
> +{
> + gicv5_iri_irq_mask(d, GICV5_HWIRQ_TYPE_SPI);
> +}
> +
> +static void gicv5_lpi_irq_mask(struct irq_data *d)
> +{
> + gicv5_iri_irq_mask(d, GICV5_HWIRQ_TYPE_LPI);
> +}
> +
> +static void gicv5_ppi_irq_unmask(struct irq_data *d)
> +{
> + u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> +
> + if (d->hwirq < 64)
> + sysreg_clear_set_s(SYS_ICC_PPI_ENABLER0_EL1, 0, hwirq_id_bit);
> + else
> + sysreg_clear_set_s(SYS_ICC_PPI_ENABLER1_EL1, 0, hwirq_id_bit);
> +}
> +
> +static void gicv5_iri_irq_unmask(struct irq_data *d, u8 hwirq_type)
> +{
> + u64 cden = d->hwirq | FIELD_PREP(GICV5_GIC_CDEN_TYPE_MASK, hwirq_type);
> +
> + gic_insn(cden, GICV5_OP_GIC_CDEN);
> +}
> +
> +static void gicv5_spi_irq_unmask(struct irq_data *d)
> +{
> + gicv5_iri_irq_unmask(d, GICV5_HWIRQ_TYPE_SPI);
> +}
> +
> +static void gicv5_lpi_irq_unmask(struct irq_data *d)
> +{
> + gicv5_iri_irq_unmask(d, GICV5_HWIRQ_TYPE_LPI);
> +}
> +
> +static void gicv5_hwirq_eoi(u32 hwirq_id, u8 hwirq_type)
> +{
> + u64 cddi = hwirq_id | FIELD_PREP(GICV5_GIC_CDDI_TYPE_MASK, hwirq_type);
> +
> + gic_insn(cddi, GICV5_OP_GIC_CDDI);
> +
> + gic_insn(0, GICV5_OP_GIC_CDEOI);
> +}
> +
> +static void gicv5_ppi_irq_eoi(struct irq_data *d)
> +{
> + gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_PPI);
> +}
> +
> +static void gicv5_spi_irq_eoi(struct irq_data *d)
> +{
> + gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_SPI);
> +}
> +
> +static void gicv5_lpi_irq_eoi(struct irq_data *d)
> +{
> + gicv5_hwirq_eoi(d->hwirq, GICV5_HWIRQ_TYPE_LPI);
> +}
> +
> +static int gicv5_iri_irq_set_affinity(struct irq_data *d,
> + const struct cpumask *mask_val,
> + bool force, u8 hwirq_type)
> +{
> + int ret, cpuid;
> + u16 iaffid;
> + u64 cdaff;
> +
> + if (force)
> + cpuid = cpumask_first(mask_val);
> + else
> + cpuid = cpumask_any_and(mask_val, cpu_online_mask);
> +
> + ret = gicv5_irs_cpu_to_iaffid(cpuid, &iaffid);
> + if (ret)
> + return ret;
> +
> + cdaff = FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, iaffid) |
> + FIELD_PREP(GICV5_GIC_CDAFF_TYPE_MASK, hwirq_type) |
> + FIELD_PREP(GICV5_GIC_CDAFF_ID_MASK, d->hwirq);
> + gic_insn(cdaff, GICV5_OP_GIC_CDAFF);
> +
> + irq_data_update_effective_affinity(d, cpumask_of(cpuid));
> +
> + return IRQ_SET_MASK_OK_DONE;
> +}
> +
> +static int gicv5_spi_irq_set_affinity(struct irq_data *d,
> + const struct cpumask *mask_val,
> + bool force)
> +{
> + return gicv5_iri_irq_set_affinity(d, mask_val, force,
> + GICV5_HWIRQ_TYPE_SPI);
> +}
> +
> +static int gicv5_lpi_irq_set_affinity(struct irq_data *d,
> + const struct cpumask *mask_val,
> + bool force)
> +{
> + return gicv5_iri_irq_set_affinity(d, mask_val, force,
> + GICV5_HWIRQ_TYPE_LPI);
> +}
> +
> +#define READ_PPI_REG(irq, reg) \
> + ({ \
> + u64 val; \
> + \
> + if (irq < 64) \
> + val = read_sysreg_s(SYS_ICC_PPI_##reg##R0_EL1); \
> + else \
> + val = read_sysreg_s(SYS_ICC_PPI_##reg##R1_EL1); \
> + val; \
> + })
> +
> +#define WRITE_PPI_REG(set, irq, bit, reg) \
> + do { \
> + if (set) { \
> + if (irq < 64) \
> + write_sysreg_s(bit, SYS_ICC_PPI_S##reg##R0_EL1);\
> + else \
> + write_sysreg_s(bit, SYS_ICC_PPI_S##reg##R1_EL1);\
> + } else { \
> + if (irq < 64) \
> + write_sysreg_s(bit, SYS_ICC_PPI_C##reg##R0_EL1);\
> + else \
> + write_sysreg_s(bit, SYS_ICC_PPI_C##reg##R1_EL1);\
> + } \
> + } while (0)
> +
> +static int gicv5_ppi_set_type(struct irq_data *d, unsigned int type)
> +{
> + /*
> + * The PPI trigger mode is not configurable at runtime,
> + * therefore this function simply confirms that the `type`
> + * parameter matches what is present.
> + */
> + u64 hmr = READ_PPI_REG(d->hwirq, HM);
> +
> + switch (type) {
> + case IRQ_TYPE_LEVEL_HIGH:
> + case IRQ_TYPE_LEVEL_LOW:
> + if (((hmr >> (d->hwirq % 64)) & 0x1) != GICV5_PPI_HM_LEVEL)
> + return -EINVAL;
> + break;
> + case IRQ_TYPE_EDGE_RISING:
> + case IRQ_TYPE_EDGE_FALLING:
> + if (((hmr >> (d->hwirq % 64)) & 0x1) != GICV5_PPI_HM_EDGE)
> + return -EINVAL;
> + break;
> + default:
> + pr_debug("Unexpected PPI trigger mode");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int gicv5_ppi_irq_get_irqchip_state(struct irq_data *d,
> + enum irqchip_irq_state which,
> + bool *val)
> +{
> + u64 pendr, activer, enabler, hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> +
> + switch (which) {
> + case IRQCHIP_STATE_PENDING:
> + pendr = READ_PPI_REG(d->hwirq, SPEND);
> +
> + *val = !!(pendr & hwirq_id_bit);
> +
> + return 0;
> + case IRQCHIP_STATE_ACTIVE:
> + activer = READ_PPI_REG(d->hwirq, SACTIVE);
> +
> + *val = !!(activer & hwirq_id_bit);
> +
> + return 0;
> + case IRQCHIP_STATE_MASKED:
> + enabler = READ_PPI_REG(d->hwirq, ENABLE);
> +
> + *val = !(enabler & hwirq_id_bit);
> +
> + return 0;
Please drop this IRQCHIP_STATE_MASKED. It was solely introduced to
paper over a terrible suspend/resume bug in some other interrupt
controller, and I'd rather not let this stupidity propagate any
further. This also applies to the set_irqchip_state() callbacks.
> + default:
> + pr_debug("Unexpected PPI irqchip state\n");
> + return -EINVAL;
Useless return.
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int gicv5_iri_irq_get_irqchip_state(struct irq_data *d,
> + enum irqchip_irq_state which,
> + bool *val, u8 hwirq_type)
> +{
> + u64 icsr, cdrcfg;
> +
> + cdrcfg = d->hwirq | FIELD_PREP(GICV5_GIC_CDRCFG_TYPE_MASK, hwirq_type);
> +
> + gic_insn(cdrcfg, GICV5_OP_GIC_CDRCFG);
> + isb();
> + icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
> +
> + if (FIELD_GET(ICC_ICSR_EL1_F, icsr)) {
> + pr_err("ICSR_EL1 is invalid\n");
> + return -EINVAL;
> + }
> +
> + switch (which) {
> + case IRQCHIP_STATE_PENDING:
> + *val = !!(FIELD_GET(ICC_ICSR_EL1_Pending, icsr));
> + return 0;
> +
> + case IRQCHIP_STATE_ACTIVE:
> + *val = !!(FIELD_GET(ICC_ICSR_EL1_Active, icsr));
> + return 0;
> +
> + case IRQCHIP_STATE_MASKED:
> + *val = !(FIELD_GET(ICC_ICSR_EL1_Enabled, icsr));
> + return 0;
> +
> + default:
> + pr_debug("Unexpected irqchip_irq_state\n");
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int gicv5_spi_irq_get_irqchip_state(struct irq_data *d,
> + enum irqchip_irq_state which,
> + bool *val)
> +{
> + return gicv5_iri_irq_get_irqchip_state(d, which, val,
> + GICV5_HWIRQ_TYPE_SPI);
> +}
> +
> +static int gicv5_lpi_irq_get_irqchip_state(struct irq_data *d,
> + enum irqchip_irq_state which,
> + bool *val)
> +{
> + return gicv5_iri_irq_get_irqchip_state(d, which, val,
> + GICV5_HWIRQ_TYPE_LPI);
> +}
> +
> +static int gicv5_ppi_irq_set_irqchip_state(struct irq_data *d,
> + enum irqchip_irq_state which,
> + bool val)
> +{
> + u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> +
> + switch (which) {
> + case IRQCHIP_STATE_PENDING:
> + WRITE_PPI_REG(val, d->hwirq, hwirq_id_bit, PEND);
> + return 0;
> + case IRQCHIP_STATE_ACTIVE:
> + WRITE_PPI_REG(val, d->hwirq, hwirq_id_bit, ACTIVE);
> + return 0;
> + case IRQCHIP_STATE_MASKED:
> + if (val)
> + gicv5_ppi_irq_mask(d);
> + else
> + gicv5_ppi_irq_unmask(d);
> + return 0;
Same thing.
> + default:
> + pr_debug("Unexpected PPI irqchip state\n");
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static void gicv5_iri_irq_write_pending_state(struct irq_data *d, bool val,
> + u8 hwirq_type)
> +{
> + u64 cdpend;
> +
> + cdpend = FIELD_PREP(GICV5_GIC_CDPEND_TYPE_MASK, hwirq_type) |
> + FIELD_PREP(GICV5_GIC_CDPEND_ID_MASK, d->hwirq) |
> + FIELD_PREP(GICV5_GIC_CDPEND_PENDING_MASK, val);
> +
> + gic_insn(cdpend, GICV5_OP_GIC_CDPEND);
> +}
> +
> +static void gicv5_spi_irq_write_pending_state(struct irq_data *d, bool val)
> +{
> + gicv5_iri_irq_write_pending_state(d, val, GICV5_HWIRQ_TYPE_SPI);
> +}
> +
> +static void gicv5_lpi_irq_write_pending_state(struct irq_data *d, bool val)
> +{
> + gicv5_iri_irq_write_pending_state(d, val, GICV5_HWIRQ_TYPE_LPI);
> +}
> +
> +static int gicv5_spi_irq_set_irqchip_state(struct irq_data *d,
> + enum irqchip_irq_state which,
> + bool val)
> +{
> + switch (which) {
> + case IRQCHIP_STATE_PENDING:
> + gicv5_spi_irq_write_pending_state(d, val);
> + break;
> + case IRQCHIP_STATE_MASKED:
> + if (val)
> + gicv5_spi_irq_mask(d);
> + else
> + gicv5_spi_irq_unmask(d);
> + break;
> + default:
> + pr_debug("Unexpected irqchip_irq_state\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int gicv5_lpi_irq_set_irqchip_state(struct irq_data *d,
> + enum irqchip_irq_state which,
> + bool val)
> +{
> + switch (which) {
> + case IRQCHIP_STATE_PENDING:
> + gicv5_lpi_irq_write_pending_state(d, val);
> + break;
> + case IRQCHIP_STATE_MASKED:
> + if (val)
> + gicv5_lpi_irq_mask(d);
> + else
> + gicv5_lpi_irq_unmask(d);
> + break;
> +
> + default:
> + pr_debug("Unexpected irqchip_irq_state\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int gicv5_spi_irq_retrigger(struct irq_data *data)
> +{
> + return !gicv5_spi_irq_set_irqchip_state(data, IRQCHIP_STATE_PENDING,
> + true);
> +}
> +
> +static int gicv5_lpi_irq_retrigger(struct irq_data *data)
> +{
> + return !gicv5_lpi_irq_set_irqchip_state(data, IRQCHIP_STATE_PENDING,
> + true);
> +}
> +
> +static void gicv5_ipi_send_single(struct irq_data *d, unsigned int cpu)
> +{
> + /* Mark the LPI pending */
> + irq_chip_retrigger_hierarchy(d);
> +}
> +
> +static const struct irq_chip gicv5_ppi_irq_chip = {
> + .name = "GICv5-PPI",
> + .irq_mask = gicv5_ppi_irq_mask,
> + .irq_unmask = gicv5_ppi_irq_unmask,
> + .irq_eoi = gicv5_ppi_irq_eoi,
> + .irq_set_type = gicv5_ppi_set_type,
> + .irq_get_irqchip_state = gicv5_ppi_irq_get_irqchip_state,
> + .irq_set_irqchip_state = gicv5_ppi_irq_set_irqchip_state,
> + .flags = IRQCHIP_SET_TYPE_MASKED |
> + IRQCHIP_SKIP_SET_WAKE |
> + IRQCHIP_MASK_ON_SUSPEND
> +};
> +
> +static const struct irq_chip gicv5_spi_irq_chip = {
> + .name = "GICv5-SPI",
> + .irq_mask = gicv5_spi_irq_mask,
> + .irq_unmask = gicv5_spi_irq_unmask,
> + .irq_eoi = gicv5_spi_irq_eoi,
> + .irq_set_type = gicv5_spi_irq_set_type,
> + .irq_set_affinity = gicv5_spi_irq_set_affinity,
> + .irq_retrigger = gicv5_spi_irq_retrigger,
> + .irq_get_irqchip_state = gicv5_spi_irq_get_irqchip_state,
> + .irq_set_irqchip_state = gicv5_spi_irq_set_irqchip_state,
> + .flags = IRQCHIP_SET_TYPE_MASKED |
> + IRQCHIP_SKIP_SET_WAKE |
> + IRQCHIP_MASK_ON_SUSPEND
> +};
> +
> +static int gicv5_irq_domain_translate(struct irq_domain *d,
> + struct irq_fwspec *fwspec,
> + irq_hw_number_t *hwirq,
> + unsigned int *type,
> + u8 hwirq_type)
> +{
> + if (!is_of_node(fwspec->fwnode))
> + return -EINVAL;
> +
> + if (fwspec->param_count < 3)
> + return -EINVAL;
> +
> + if (fwspec->param[0] != hwirq_type)
> + return -EINVAL;
> +
> + *hwirq = fwspec->param[1];
> + *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
> +
> + return 0;
> +}
> +
> +static int gicv5_irq_ppi_domain_translate(struct irq_domain *d,
> + struct irq_fwspec *fwspec,
> + irq_hw_number_t *hwirq,
> + unsigned int *type)
> +{
> + return gicv5_irq_domain_translate(d, fwspec, hwirq, type,
> + GICV5_HWIRQ_TYPE_PPI);
> +}
> +
> +static int gicv5_irq_ppi_domain_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + unsigned int type = IRQ_TYPE_NONE;
> + struct irq_fwspec *fwspec = arg;
> + irq_hw_number_t hwirq;
> + int ret;
> +
> + if (WARN_ON_ONCE(nr_irqs != 1))
> + return -EINVAL;
> +
> + ret = gicv5_irq_ppi_domain_translate(domain, fwspec, &hwirq, &type);
> + if (ret)
> + return ret;
> +
> + irq_set_percpu_devid(virq);
> + irq_domain_set_info(domain, virq, hwirq, &gicv5_ppi_irq_chip, NULL,
> + handle_percpu_devid_irq, NULL, NULL);
> +
> + return 0;
> +}
> +
> +static void gicv5_irq_domain_free(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs)
> +{
> + struct irq_data *d;
> +
> + if (WARN_ON_ONCE(nr_irqs != 1))
> + return;
> +
> + d = irq_domain_get_irq_data(domain, virq);
> +
> + irq_set_handler(virq, NULL);
> + irq_domain_reset_irq_data(d);
> +}
> +
> +static int gicv5_irq_ppi_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec,
> + enum irq_domain_bus_token bus_token)
> +{
> + if (fwspec->fwnode != d->fwnode)
> + return 0;
> +
> + if (fwspec->param[0] != GICV5_HWIRQ_TYPE_PPI)
> + return 0;
> +
> + return (d == gicv5_global_data.ppi_domain);
> +}
> +
> +static const struct irq_domain_ops gicv5_irq_ppi_domain_ops = {
> + .translate = gicv5_irq_ppi_domain_translate,
> + .alloc = gicv5_irq_ppi_domain_alloc,
> + .free = gicv5_irq_domain_free,
> + .select = gicv5_irq_ppi_domain_select
> +};
> +
> +static int gicv5_irq_spi_domain_translate(struct irq_domain *d,
> + struct irq_fwspec *fwspec,
> + irq_hw_number_t *hwirq,
> + unsigned int *type)
> +{
> + return gicv5_irq_domain_translate(d, fwspec, hwirq, type,
> + GICV5_HWIRQ_TYPE_SPI);
> +}
> +
> +static int gicv5_irq_spi_domain_alloc(struct irq_domain *domain,
> + unsigned int virq, unsigned int nr_irqs,
> + void *arg)
> +{
> + struct gicv5_irs_chip_data *chip_data;
> + unsigned int type = IRQ_TYPE_NONE;
> + struct irq_fwspec *fwspec = arg;
> + struct irq_data *irqd;
> + irq_hw_number_t hwirq;
> + int ret;
> +
> + if (WARN_ON_ONCE(nr_irqs != 1))
> + return -EINVAL;
> +
> + ret = gicv5_irq_spi_domain_translate(domain, fwspec, &hwirq, &type);
> + if (ret)
> + return ret;
> +
> + irqd = irq_desc_get_irq_data(irq_to_desc(virq));
> + chip_data = gicv5_irs_lookup_by_spi_id(hwirq);
> +
> + irq_domain_set_info(domain, virq, hwirq, &gicv5_spi_irq_chip, chip_data,
> + handle_fasteoi_irq, NULL, NULL);
> + irq_set_probe(virq);
> + irqd_set_single_target(irqd);
> +
> + gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_SPI);
> +
> + return 0;
> +}
> +
> +static int gicv5_irq_spi_domain_select(struct irq_domain *d,
> + struct irq_fwspec *fwspec,
> + enum irq_domain_bus_token bus_token)
> +{
> + if (fwspec->fwnode != d->fwnode)
> + return 0;
> +
> + if (fwspec->param[0] != GICV5_HWIRQ_TYPE_SPI)
> + return 0;
> +
> + return (d == gicv5_global_data.spi_domain);
> +}
> +
> +static const struct irq_domain_ops gicv5_irq_spi_domain_ops = {
> + .translate = gicv5_irq_spi_domain_translate,
> + .alloc = gicv5_irq_spi_domain_alloc,
> + .free = gicv5_irq_domain_free,
> + .select = gicv5_irq_spi_domain_select
> +};
> +
> +static const struct irq_chip gicv5_lpi_irq_chip = {
> + .name = "GICv5-LPI",
> + .irq_mask = gicv5_lpi_irq_mask,
> + .irq_unmask = gicv5_lpi_irq_unmask,
> + .irq_eoi = gicv5_lpi_irq_eoi,
> + .irq_set_affinity = gicv5_lpi_irq_set_affinity,
> + .irq_retrigger = gicv5_lpi_irq_retrigger,
> + .irq_get_irqchip_state = gicv5_lpi_irq_get_irqchip_state,
> + .irq_set_irqchip_state = gicv5_lpi_irq_set_irqchip_state,
> + .flags = IRQCHIP_SET_TYPE_MASKED |
> + IRQCHIP_SKIP_SET_WAKE |
> + IRQCHIP_MASK_ON_SUSPEND
> +};
> +
> +static const struct irq_chip gicv5_ipi_irq_chip = {
> + .name = "GICv5-IPI",
> + .irq_mask = irq_chip_mask_parent,
> + .irq_unmask = irq_chip_unmask_parent,
> + .irq_eoi = irq_chip_eoi_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> + .irq_get_irqchip_state = irq_chip_get_parent_state,
> + .irq_set_irqchip_state = irq_chip_set_parent_state,
> + // We only handle this one in the IPI domain - the rest go to parent
> + .ipi_send_single = gicv5_ipi_send_single,
> + .flags = IRQCHIP_SET_TYPE_MASKED |
> + IRQCHIP_SKIP_SET_WAKE |
> + IRQCHIP_MASK_ON_SUSPEND
> +};
> +
> +static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + irq_hw_number_t hwirq;
> + struct irq_data *irqd;
> + u32 *lpi = arg;
> + int ret;
> +
> + if (WARN_ON_ONCE(nr_irqs != 1))
> + return -EINVAL;
> +
> + hwirq = *lpi;
> +
> + irqd = irq_domain_get_irq_data(domain, virq);
> +
> + irq_domain_set_info(domain, virq, hwirq, &gicv5_lpi_irq_chip, NULL,
> + handle_fasteoi_irq, NULL, NULL);
> + irqd_set_single_target(irqd);
> +
> + ret = gicv5_irs_iste_alloc(hwirq);
> + if (ret < 0)
> + return ret;
> +
> + gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_LPI);
> +
> + return 0;
> +}
> +
> +static void gicv5_irq_lpi_domain_free(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs)
> +{
> + struct irq_data *d;
> + int i;
> +
> + WARN_ON_ONCE(nr_irqs != 1);
> +
> + for (i = 0; i < nr_irqs; i++) {
> + d = irq_domain_get_irq_data(domain, virq + i);
> +
> + irq_set_handler(virq + i, NULL);
> + irq_domain_reset_irq_data(d);
> + }
> +}
> +
> +static const struct irq_domain_ops gicv5_irq_lpi_domain_ops = {
> + .alloc = gicv5_irq_lpi_domain_alloc,
> + .free = gicv5_irq_lpi_domain_free,
> +};
> +
> +void __init gicv5_init_lpi_domain(void)
> +{
> + struct irq_domain *d;
> +
> + d = irq_domain_create_tree(NULL, &gicv5_irq_lpi_domain_ops, NULL);
> + gicv5_global_data.lpi_domain = d;
> +}
> +
> +void __init gicv5_free_lpi_domain(void)
> +{
> + irq_domain_remove(gicv5_global_data.lpi_domain);
> +}
> +
> +static int gicv5_irq_ipi_domain_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + struct irq_data *irqd;
> + int ret, i;
> + u32 lpi;
> +
> + for (i = 0; i < nr_irqs; i++) {
> + ret = gicv5_alloc_lpi();
> + if (ret < 0)
> + return ret;
> +
> + lpi = ret;
> +
> + ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
> + if (ret) {
> + gicv5_free_lpi(lpi);
> + return ret;
> + }
> +
> + irqd = irq_domain_get_irq_data(domain, virq + i);
> +
> + irq_domain_set_hwirq_and_chip(domain, virq + i, i,
> + &gicv5_ipi_irq_chip, NULL);
> +
> + irqd_set_single_target(irqd);
> +
> + irq_set_handler(virq + i, handle_percpu_irq);
> + }
> +
> + return 0;
> +}
> +
> +static void gicv5_irq_ipi_domain_free(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs)
> +{
> + struct irq_data *d;
> + unsigned int i;
> +
> + for (i = 0; i < nr_irqs; i++) {
> + d = irq_domain_get_irq_data(domain, virq + i);
> +
> + if (!d)
> + return;
> +
> + gicv5_free_lpi(d->parent_data->hwirq);
> +
> + irq_set_handler(virq + i, NULL);
> + irq_domain_reset_irq_data(d);
> + irq_domain_free_irqs_parent(domain, virq + i, 1);
> + }
> +}
> +
> +static const struct irq_domain_ops gicv5_irq_ipi_domain_ops = {
> + .alloc = gicv5_irq_ipi_domain_alloc,
> + .free = gicv5_irq_ipi_domain_free,
> +};
> +
> +static inline void handle_irq_per_domain(u32 hwirq)
Drop the inline.
> +{
> + u8 hwirq_type = FIELD_GET(GICV5_HWIRQ_TYPE, hwirq);
> + u32 hwirq_id = FIELD_GET(GICV5_HWIRQ_ID, hwirq);
> + struct irq_domain *domain = NULL;
> +
> + if (hwirq_type == GICV5_HWIRQ_TYPE_PPI)
> + domain = gicv5_global_data.ppi_domain;
> + else if (hwirq_type == GICV5_HWIRQ_TYPE_SPI)
> + domain = gicv5_global_data.spi_domain;
> + else if (hwirq_type == GICV5_HWIRQ_TYPE_LPI)
> + domain = gicv5_global_data.lpi_domain;
This could be more clearly written as a switch/case statement, and
avoid calling into generic_handle_domain_irq() with a NULL pointer,
should this ever happen.
> +
> + if (generic_handle_domain_irq(domain, hwirq_id)) {
> + pr_err_once("Could not handle, hwirq = 0x%x", hwirq_id);
> + gicv5_hwirq_eoi(hwirq_id, hwirq_type);
> + }
> +}
> +
> +static void __exception_irq_entry gicv5_handle_irq(struct pt_regs *regs)
> +{
> + bool valid;
> + u32 hwirq;
> + u64 ia;
> +
> + ia = gicr_insn(GICV5_OP_GICR_CDIA);
> + valid = GICV5_GIC_CDIA_VALID(ia);
> +
> + if (!valid)
> + return;
> +
> + /*
> + * Ensure that the CDIA instruction effects (ie IRQ activation) are
> + * completed before handling the interrupt.
> + */
> + gsb_ack();
> +
> + /*
> + * Ensure instruction ordering between an acknowledgment and subsequent
> + * instructions in the IRQ handler using an ISB.
> + */
> + isb();
> +
> + hwirq = FIELD_GET(GICV5_HWIRQ_INTID, ia);
> +
> + handle_irq_per_domain(hwirq);
> +}
> +
> +static void gicv5_cpu_disable_interrupts(void)
> +{
> + u64 cr0;
> +
> + cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0);
> + write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
> +}
> +
> +static void gicv5_cpu_enable_interrupts(void)
> +{
> + u64 cr0, pcr;
> +
> + write_sysreg_s(0, SYS_ICC_PPI_ENABLER0_EL1);
> + write_sysreg_s(0, SYS_ICC_PPI_ENABLER1_EL1);
> +
> + gicv5_ppi_priority_init();
> +
> + pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, GICV5_IRQ_PRI_MI);
> + write_sysreg_s(pcr, SYS_ICC_PCR_EL1);
> +
> + cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 1);
> + write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
> +}
> +
> +static int base_ipi_virq;
> +
> +static int gicv5_starting_cpu(unsigned int cpu)
> +{
> + if (WARN(!gicv5_cpuif_has_gcie(),
> + "GICv5 system components present but CPU does not have FEAT_GCIE"))
> + return -ENODEV;
> +
> + gicv5_cpu_enable_interrupts();
> +
> + return gicv5_irs_register_cpu(cpu);
> +}
> +
> +static void __init gicv5_smp_init(void)
> +{
> + unsigned int num_ipis = GICV5_IPIS_PER_CPU * nr_cpu_ids;
> +
> + cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
> + "irqchip/arm/gicv5:starting",
> + gicv5_starting_cpu, NULL);
> +
> + base_ipi_virq = irq_domain_alloc_irqs(gicv5_global_data.ipi_domain,
> + num_ipis, NUMA_NO_NODE, NULL);
> + if (WARN(base_ipi_virq <= 0, "IPI IRQ allocation was not successful"))
> + return;
> +
> + set_smp_ipi_range_percpu(base_ipi_virq, GICV5_IPIS_PER_CPU, nr_cpu_ids);
> +}
> +
> +static void __init gicv5_free_domains(void)
> +{
> + if (gicv5_global_data.ppi_domain)
> + irq_domain_remove(gicv5_global_data.ppi_domain);
> + if (gicv5_global_data.spi_domain)
> + irq_domain_remove(gicv5_global_data.spi_domain);
> + if (gicv5_global_data.ipi_domain)
> + irq_domain_remove(gicv5_global_data.ipi_domain);
Make these pointers NULL once you have dropped the irqdomains.
> +}
> +
> +static int __init gicv5_init_domains(struct fwnode_handle *handle)
> +{
> + u32 spi_count = gicv5_global_data.global_spi_count;
> + struct irq_domain *d;
> +
> + d = irq_domain_create_linear(handle, 128, &gicv5_irq_ppi_domain_ops,
> + NULL);
Consider having a named constant for the number of PPIs.
> + if (!d)
> + return -ENOMEM;
> +
> + irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED);
> + gicv5_global_data.ppi_domain = d;
> +
> + if (spi_count) {
> + d = irq_domain_create_linear(handle, spi_count,
> + &gicv5_irq_spi_domain_ops, NULL);
> +
> + if (!d) {
> + gicv5_free_domains();
> + return -ENOMEM;
> + }
> +
> + gicv5_global_data.spi_domain = d;
> + irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED);
> + }
> +
> + if (!WARN(!gicv5_global_data.lpi_domain,
> + "LPI domain uninitialized, can't set up IPIs")) {
> + d = irq_domain_create_hierarchy(gicv5_global_data.lpi_domain,
> + 0, GICV5_IPIS_PER_CPU * nr_cpu_ids,
> + NULL, &gicv5_irq_ipi_domain_ops,
> + NULL);
> +
> + if (!d) {
> + gicv5_free_domains();
> + return -ENOMEM;
> + }
> + gicv5_global_data.ipi_domain = d;
> + }
> + gicv5_global_data.fwnode = handle;
> +
> + return 0;
> +}
> +
> +static void gicv5_set_cpuif_pribits(void)
> +{
> + u64 icc_idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
> +
> + switch (FIELD_GET(ICC_IDR0_EL1_PRI_BITS, icc_idr0)) {
> + case ICC_IDR0_EL1_PRI_BITS_4BITS:
> + gicv5_global_data.cpuif_pri_bits = 4;
> + break;
> + case ICC_IDR0_EL1_PRI_BITS_5BITS:
> + gicv5_global_data.cpuif_pri_bits = 5;
> + break;
> + default:
> + pr_err("Unexpected ICC_IDR0_EL1_PRI_BITS value, default to 4");
> + gicv5_global_data.cpuif_pri_bits = 4;
> + break;
> + }
> +}
> +
> +static void gicv5_set_cpuif_idbits(void)
> +{
> + u32 icc_idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
> +
> + switch (FIELD_GET(ICC_IDR0_EL1_ID_BITS, icc_idr0)) {
> + case ICC_IDR0_EL1_ID_BITS_16BITS:
> + gicv5_global_data.cpuif_id_bits = 16;
> + break;
> + case ICC_IDR0_EL1_ID_BITS_24BITS:
> + gicv5_global_data.cpuif_id_bits = 24;
> + break;
> + default:
> + pr_err("Unexpected ICC_IDR0_EL1_ID_BITS value, default to 16");
> + gicv5_global_data.cpuif_id_bits = 16;
> + break;
> + }
> +}
> +
> +
> +static int __init gicv5_of_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + int ret;
> +
> + ret = gicv5_irs_of_probe(node);
> + if (ret)
> + return ret;
> +
> + ret = gicv5_init_domains(&node->fwnode);
> + if (ret)
> + goto out_irs;
> +
> + gicv5_set_cpuif_pribits();
> + gicv5_set_cpuif_idbits();
> +
> + pri_bits = min_not_zero(gicv5_global_data.cpuif_pri_bits,
> + gicv5_global_data.irs_pri_bits);
> +
> + ret = gicv5_starting_cpu(smp_processor_id());
> + if (ret)
> + goto out_dom;
> +
> + ret = set_handle_irq(gicv5_handle_irq);
> + if (ret)
> + goto out_int;
> +
> + ret = gicv5_irs_enable();
> + if (ret)
> + goto out_int;
> +
> + gicv5_smp_init();
> +
> + return 0;
> +out_int:
> + gicv5_cpu_disable_interrupts();
> +out_dom:
> + gicv5_free_domains();
> +out_irs:
> + gicv5_irs_remove();
> +
> + return ret;
> +}
> +IRQCHIP_DECLARE(gic_v5, "arm,gic-v5", gicv5_of_init);
> diff --git a/drivers/irqchip/irq-gic-v5.h b/drivers/irqchip/irq-gic-v5.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..19569639153a084760c3b5b7f0fa84791ba0195c
> --- /dev/null
> +++ b/drivers/irqchip/irq-gic-v5.h
> @@ -0,0 +1,184 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 ARM Limited, All Rights Reserved.
> + */
> +#ifndef __LINUX_IRQCHIP_GIC_V5_H
> +#define __LINUX_IRQCHIP_GIC_V5_H
> +
> +#include <asm/arch_gicv5.h>
> +#include <asm/smp.h>
> +
> +#define GICV5_IPIS_PER_CPU MAX_IPI
> +
> +#define GICV5_HWIRQ_ID GENMASK(23, 0)
> +#define GICV5_HWIRQ_TYPE GENMASK(31, 29)
> +#define GICV5_HWIRQ_INTID GENMASK_ULL(31, 0)
> +
> +#define GICV5_HWIRQ_TYPE_PPI UL(0x1)
> +#define GICV5_HWIRQ_TYPE_LPI UL(0x2)
> +#define GICV5_HWIRQ_TYPE_SPI UL(0x3)
> +
> +#define GICV5_PPI_HM_EDGE UL(0x0)
> +#define GICV5_PPI_HM_LEVEL UL(0x1)
> +
> +#define GICV5_NO_READ_ALLOC 0b0
> +#define GICV5_READ_ALLOC 0b1
> +#define GICV5_NO_WRITE_ALLOC 0b0
> +#define GICV5_WRITE_ALLOC 0b1
> +
> +#define GICV5_NON_CACHE 0b00
> +#define GICV5_WB_CACHE 0b01
> +#define GICV5_WT_CACHE 0b10
> +
> +#define GICV5_NON_SHARE 0b00
> +#define GICV5_OUTER_SHARE 0b10
> +#define GICV5_INNER_SHARE 0b11
> +
> +#define GICV5_IRS_IDR1 0x0004
> +#define GICV5_IRS_IDR2 0x0008
> +#define GICV5_IRS_IDR5 0x0014
> +#define GICV5_IRS_IDR6 0x0018
> +#define GICV5_IRS_IDR7 0x001c
> +#define GICV5_IRS_CR0 0x0080
> +#define GICV5_IRS_CR1 0x0084
> +#define GICV5_IRS_SPI_SELR 0x0108
> +#define GICV5_IRS_SPI_CFGR 0x0114
> +#define GICV5_IRS_SPI_STATUSR 0x0118
> +#define GICV5_IRS_PE_SELR 0x0140
> +#define GICV5_IRS_PE_STATUSR 0x0144
> +#define GICV5_IRS_PE_CR0 0x0148
> +#define GICV5_IRS_IST_BASER 0x0180
> +#define GICV5_IRS_IST_CFGR 0x0190
> +#define GICV5_IRS_IST_STATUSR 0x0194
> +#define GICV5_IRS_MAP_L2_ISTR 0x01c0
> +
> +#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
> +#define GICV5_IRS_IDR1_IAFFID_BITS GENMASK(19, 16)
> +
> +#define GICV5_IRS_IDR1_PRIORITY_BITS_1BITS 0b000
> +#define GICV5_IRS_IDR1_PRIORITY_BITS_2BITS 0b001
> +#define GICV5_IRS_IDR1_PRIORITY_BITS_3BITS 0b010
> +#define GICV5_IRS_IDR1_PRIORITY_BITS_4BITS 0b011
> +#define GICV5_IRS_IDR1_PRIORITY_BITS_5BITS 0b100
> +
> +#define GICV5_IRS_IDR2_ISTMD_SZ GENMASK(19, 15)
> +#define GICV5_IRS_IDR2_ISTMD BIT(14)
> +#define GICV5_IRS_IDR2_IST_L2SZ GENMASK(13, 11)
> +#define GICV5_IRS_IDR2_IST_LEVELS BIT(10)
> +#define GICV5_IRS_IDR2_MIN_LPI_ID_BITS GENMASK(9, 6)
> +#define GICV5_IRS_IDR2_LPI BIT(5)
> +#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
> +
> +#define GICV5_IRS_IDR5_SPI_RANGE GENMASK(24, 0)
> +#define GICV5_IRS_IDR6_SPI_IRS_RANGE GENMASK(24, 0)
> +#define GICV5_IRS_IDR7_SPI_BASE GENMASK(23, 0)
> +
> +#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
> +#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
> +#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
> +
> +#define GICV5_IRS_CR0_IDLE BIT(1)
> +#define GICV5_IRS_CR0_IRSEN BIT(0)
> +
> +#define GICV5_IRS_CR1_VPED_WA BIT(15)
> +#define GICV5_IRS_CR1_VPED_RA BIT(14)
> +#define GICV5_IRS_CR1_VMD_WA BIT(13)
> +#define GICV5_IRS_CR1_VMD_RA BIT(12)
> +#define GICV5_IRS_CR1_VPET_WA BIT(11)
> +#define GICV5_IRS_CR1_VPET_RA BIT(10)
> +#define GICV5_IRS_CR1_VMT_WA BIT(9)
> +#define GICV5_IRS_CR1_VMT_RA BIT(8)
> +#define GICV5_IRS_CR1_IST_WA BIT(7)
> +#define GICV5_IRS_CR1_IST_RA BIT(6)
> +#define GICV5_IRS_CR1_IC GENMASK(5, 4)
> +#define GICV5_IRS_CR1_OC GENMASK(3, 2)
> +#define GICV5_IRS_CR1_SH GENMASK(1, 0)
> +
> +#define GICV5_IRS_SPI_STATUSR_V BIT(1)
> +#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
> +
> +#define GICV5_IRS_SPI_SELR_ID GENMASK(23, 0)
> +
> +#define GICV5_IRS_SPI_CFGR_TM BIT(0)
> +
> +#define GICV5_IRS_PE_SELR_IAFFID GENMASK(15, 0)
> +
> +#define GICV5_IRS_PE_STATUSR_V BIT(1)
> +#define GICV5_IRS_PE_STATUSR_IDLE BIT(0)
> +
> +#define GICV5_IRS_PE_CR0_DPS BIT(0)
> +
> +#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
> +
> +#define GICV5_IRS_IST_CFGR_STRUCTURE BIT(16)
> +#define GICV5_IRS_IST_CFGR_ISTSZ GENMASK(8, 7)
> +#define GICV5_IRS_IST_CFGR_L2SZ GENMASK(6, 5)
> +#define GICV5_IRS_IST_CFGR_LPI_ID_BITS GENMASK(4, 0)
> +
> +#define GICV5_IRS_IST_CFGR_STRUCTURE_LINEAR 0b0
> +#define GICV5_IRS_IST_CFGR_STRUCTURE_TWO_LEVEL 0b1
> +
> +#define GICV5_IRS_IST_CFGR_ISTSZ_4 0b00
> +#define GICV5_IRS_IST_CFGR_ISTSZ_8 0b01
> +#define GICV5_IRS_IST_CFGR_ISTSZ_16 0b10
> +
> +#define GICV5_IRS_IST_CFGR_L2SZ_4K 0b00
> +#define GICV5_IRS_IST_CFGR_L2SZ_16K 0b01
> +#define GICV5_IRS_IST_CFGR_L2SZ_64K 0b10
> +
> +#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
> +#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
> +
> +#define GICV5_IRS_MAP_L2_ISTR_ID GENMASK(23, 0)
> +
> +#define GICV5_ISTL1E_VALID BIT_ULL(0)
> +
> +#define GICV5_ISTL1E_L2_ADDR_MASK GENMASK_ULL(55, 12)
> +
> +struct gicv5_chip_data {
> + struct fwnode_handle *fwnode;
> + struct irq_domain *ppi_domain;
> + struct irq_domain *spi_domain;
> + struct irq_domain *lpi_domain;
> + struct irq_domain *ipi_domain;
> + u32 global_spi_count;
> + u8 cpuif_pri_bits;
> + u8 cpuif_id_bits;
> + u8 irs_pri_bits;
> + u8 irs_iaffid_bits;
> + struct {
> + __le64 *l1ist_addr;
> + u32 l2_size;
> + u8 l2_bits;
> + bool l2;
> + } ist;
> +};
> +
> +extern struct gicv5_chip_data gicv5_global_data __read_mostly;
> +
> +struct gicv5_irs_chip_data {
> + struct list_head entry;
> + struct fwnode_handle *fwnode;
> + void __iomem *irs_base;
> + u32 flags;
> + u32 spi_min;
> + u32 spi_range;
> + raw_spinlock_t spi_config_lock;
> +};
> +
> +void __init gicv5_init_lpi_domain(void);
> +void __init gicv5_free_lpi_domain(void);
> +
> +int gicv5_irs_of_probe(struct device_node *parent);
> +void gicv5_irs_remove(void);
> +int gicv5_irs_enable(void);
> +int gicv5_irs_register_cpu(int cpuid);
> +int gicv5_irs_cpu_to_iaffid(int cpu_id, u16 *iaffid);
> +struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id);
> +int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
> +int gicv5_spi_set_type(struct irq_data *d, unsigned int type);
> +int gicv5_irs_iste_alloc(u32 lpi);
> +
> +void gicv5_init_lpis(u32 max);
> +void gicv5_deinit_lpis(void);
> +#endif
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support
2025-04-28 15:49 ` Marc Zyngier
@ 2025-04-29 14:54 ` Lorenzo Pieralisi
2025-04-29 15:38 ` Marc Zyngier
0 siblings, 1 reply; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-29 14:54 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Mon, Apr 28, 2025 at 04:49:17PM +0100, Marc Zyngier wrote:
> On Thu, 24 Apr 2025 11:25:30 +0100,
> Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> >
> > Implement GICv5 CPU interface and IRS support, to manage interrupt
> > state, priority and routing for all GICv5 interrupt types.
> >
> > The GICv5 CPU interface implements support for PE-Private Peripheral
> > Interrupts (PPI), that are handled (enabled/prioritized/delivered)
> > entirely within the CPU interface hardware.
> >
> > To enable PPI interrupts, implement the baseline GICv5 host kernel
> > driver infrastructure required to handle interrupts on a GICv5 system.
> >
> > Add the exception handling code path and definitions for GICv5
> > instructions.
> >
> > Add GICv5 PPI handling code as a specific IRQ domain to:
> >
> > - Set-up PPI priority
> > - Manage PPI configuration and state
> > - Manage IRQ flow handler
> > - IRQs allocation/free
> > - Hook-up a PPI specific IRQchip to provide the relevant methods
> >
> > PPI IRQ priority is chosen as the minimum allowed priority by the
> > system design (after probing the number of priority bits implemented
> > by the CPU interface).
> >
> > The GICv5 Interrupt Routing Service (IRS) component implements
> > interrupt management and routing in the GICv5 architecture.
> >
> > A GICv5 system comprises one or more IRSes, that together
> > handle the interrupt routing and state for the system.
> >
> > An IRS supports Shared Peripheral Interrupts (SPIs), that are
> > interrupt sources directly connected to the IRS; they do not
> > rely on memory for storage. The number of supported SPIs is
> > fixed for a given implementation and can be probed through IRS
> > IDR registers.
> >
> > SPI interrupt state and routing are managed through GICv5
> > instructions.
> >
> > Each core (PE in GICv5 terms) in a GICv5 system is identified with
> > an Interrupt AFFinity ID (IAFFID).
> >
> > An IRS manages a set of cores that are connected to it.
> >
> > Firmware provides a topology description that the driver uses
> > to detect to which IRS a CPU (ie an IAFFID) is associated with.
> >
> > Use probeable information and firmware description to initialize
> > the IRSes and implement GICv5 IRS SPIs support through an
> > SPI-specific IRQ domain.
> >
> > The GICv5 IRS driver:
> >
> > - Probes IRSes in the system to detect SPI ranges
> > - Associates an IRS with a set of cores connected to it
> > - Adds an IRQchip structure for SPI handling
> >
> > SPIs priority is set to a value corresponding to the lowest
> > permissible priority in the system (taking into account the
> > implemented priority bits of the IRS and CPU interface).
> >
> > Since all IRQs are set to the same priority value, the value
> > itself does not matter as long as it is a valid one.
> >
> > An IRS supports Logical Peripheral Interrupts (LPIs) and implement
> > Linux IPIs on top of it.
> >
> > LPIs are used for interrupt signals that are translated by a
> > GICv5 ITS (Interrupt Translation Service) but also for software
> > generated IRQs - namely interrupts that are not driven by a HW
> > signal, ie IPIs.
> >
> > LPIs rely on memory storage for interrupt routing and state.
> >
> > Memory storage is handled by the IRS - that is configured
> > at probe time by the driver with the required memory.
> >
> > LPIs state and routing information is kept in the Interrupt
> > State Table (IST).
> >
> > IRSes provide support for 1- or 2-level IST tables configured
> > to support a maximum number of interrupts that depend on the
> > OS configuration and the HW capabilities.
> >
> > On systems that provide 2-level IST support, always allow
> > the maximum number of LPIs; On systems with only 1-level
> > support, limit the number of LPIs to 2^12 to prevent
> > wasting memory (presumably a system that supports a 1-level
> > only IST is not expecting a large number of interrupts).
> >
> > On a 2-level IST system, L2 entries are allocated on
> > demand.
> >
> > The IST table memory is allocated using the kmalloc() interface;
> > the allocation required may be smaller than a page and must be
> > made up of contiguous physical pages if larger than a page.
> >
> > On systems where the IRS is not cache-coherent with the CPUs,
> > cache mainteinance operations are executed to clean and
> > invalidate the allocated memory to the point of coherency
> > making it visible to the IRS components.
> >
> > Add an LPI IRQ domain to:
> >
> > - Manage LPI state and routing
> > - Add LPI IRQchip structure and callbacks
> > - LPI domain allocation/de-allocation
> >
> > On GICv5 systems, IPIs are implemented using LPIs.
> >
> > Implement an IPI-specific IRQ domain created as a child/subdomain
> > of the LPI domain to allocate the required number of LPIs needed
> > to implement the IPIs.
> >
> > Move the arm64 IPI enum declaration to a header file so that the
> > GICv5 driver code can detect how many IPIs are required by arch code.
> >
> > IPIs are backed by LPIs, add LPIs allocation/de-allocation
> > functions.
> >
> > The LPI INTID namespace is managed using an IDA to alloc/free LPI
> > INTIDs.
> >
> > Associate an IPI irqchip with IPI IRQ descriptors to provide
> > core code with the irqchip.ipi_send_single() method required
> > to raise an IPI.
> >
> > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > Cc: Will Deacon <will@kernel.org>
> > Cc: Thomas Gleixner <tglx@linutronix.de>
> > Cc: Catalin Marinas <catalin.marinas@arm.com>
> > Cc: Marc Zyngier <maz@kernel.org>
> > ---
> > MAINTAINERS | 2 +
> > arch/arm64/include/asm/arch_gicv5.h | 91 +++
> > arch/arm64/include/asm/smp.h | 17 +
> > arch/arm64/kernel/smp.c | 17 -
> > drivers/irqchip/Kconfig | 5 +
> > drivers/irqchip/Makefile | 1 +
> > drivers/irqchip/irq-gic-v5-irs.c | 841 ++++++++++++++++++++++++++++
> > drivers/irqchip/irq-gic-v5.c | 1058 +++++++++++++++++++++++++++++++++++
> > drivers/irqchip/irq-gic-v5.h | 184 ++++++
>
> Nit: the split between include/asm/arch_gicv5.h and
> drivers/irqchip/irq-gic-v5.h is pretty pointless (this is obviously
> inherited from the GICv3 on 32bit setup). Given that GICv5 is strictly
> arm64, this split is no longer necessary.
That's true but I thought I would leave sys instructions and barriers
in arm64 arch code headers rather than moving them out. I am up for
whatever you folks think it is best.
[...]
> > diff --git a/arch/arm64/include/asm/arch_gicv5.h b/arch/arm64/include/asm/arch_gicv5.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..75557fdad611fa51d7136126eb80cb861be98a8d
> > --- /dev/null
> > +++ b/arch/arm64/include/asm/arch_gicv5.h
> > @@ -0,0 +1,91 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Copyright (C) 2025 ARM Ltd.
> > + */
> > +#ifndef __ASM_ARCH_GICV5_H
> > +#define __ASM_ARCH_GICV5_H
> > +
> > +#include <asm/cacheflush.h>
> > +#include <asm/sysreg.h>
> > +
> > +#ifndef __ASSEMBLY__
> > +
> > +#define GICV5_OP_GIC_CDAFF sys_insn(1, 0, 12, 1, 3)
> > +#define GICV5_OP_GIC_CDDI sys_insn(1, 0, 12, 2, 0)
> > +#define GICV5_OP_GIC_CDDIS sys_insn(1, 0, 12, 1, 0)
> > +#define GICV5_OP_GIC_CDEN sys_insn(1, 0, 12, 1, 1)
> > +#define GICV5_OP_GIC_CDEOI sys_insn(1, 0, 12, 1, 7)
> > +#define GICV5_OP_GIC_CDPEND sys_insn(1, 0, 12, 1, 4)
> > +#define GICV5_OP_GIC_CDPRI sys_insn(1, 0, 12, 1, 2)
> > +#define GICV5_OP_GIC_CDRCFG sys_insn(1, 0, 12, 1, 5)
> > +#define GICV5_OP_GICR_CDIA sys_insn(1, 0, 12, 3, 0)
> > +
> > +#define gicr_insn(insn) read_sysreg_s(insn)
> > +#define gic_insn(v, insn) write_sysreg_s(v, insn)
>
> If you are providing this sort of helpers, use them to shorten the
> amount of boilerplate stuff that you need to read or write.
>
> For example:
>
> #define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_ ## insn)
>
> allows you to write:
>
> ia = gicr_insn(CDIA);
Done.
> > +
> > +#define GSB_ACK __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 1) | 31)
> > +#define GSB_SYS __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 0) | 31)
>
> Can you please express this in terms of __SYS_BARRIER_INSN(), with a
> slight rework of the SB definition? It would limit the propagation of
> the 0xd5 constant and make it clear what 31 stands for.
I tried but the __SYS_BARRIER_INSN() is a different class (GICv5
barriers are sys instructions the SB barrier has a slightly different
encoding), so maybe something like this ?
#define GSB_ACK __msr_s(sys_insn(1, 0, 12, 0, 1), "xzr")
>
> > +
> > +#define gsb_ack() asm volatile(GSB_ACK : : : "memory")
> > +#define gsb_sys() asm volatile(GSB_SYS : : : "memory")
[...]
> > diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> > index cec05e443083b8982b3e72f4212d808a22883914..160a4761d5d85f6dbf36f3142fd619c114733e36 100644
> > --- a/drivers/irqchip/Kconfig
> > +++ b/drivers/irqchip/Kconfig
> > @@ -54,6 +54,11 @@ config ARM_GIC_V3_ITS_FSL_MC
> > depends on FSL_MC_BUS
> > default ARM_GIC_V3_ITS
> >
> > +config ARM_GIC_V5
> > + bool
> > + select IRQ_DOMAIN_HIERARCHY
> > + select GENERIC_IRQ_EFFECTIVE_AFF_MASK if SMP
> > +
>
> Drop the 'if SMP', as arm64 is always SMP.
Done.
>
> > config ARM_NVIC
> > bool
> > select IRQ_DOMAIN_HIERARCHY
> > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > index 365bcea9a61ff89e2cb41034125b3fc8cd494d81..3d9c47fa3fdf40b7452c059d84fe8ac24c91bc0f 100644
> > --- a/drivers/irqchip/Makefile
> > +++ b/drivers/irqchip/Makefile
> > @@ -35,6 +35,7 @@ obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o
> > obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o
> > obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> > obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> > +obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> > obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> > obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> > obj-$(CONFIG_ARM_VIC) += irq-vic.o
> > diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..7bd60e6d56b77c0c19a1bd9bee9685d9b6ffc959
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-gic-v5-irs.c
> > @@ -0,0 +1,841 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
> > + */
> > +
> > +#define pr_fmt(fmt) "GICv5 IRS: " fmt
> > +
> > +#include <linux/iopoll.h>
> > +#include <linux/irqchip.h>
> > +#include <linux/log2.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +
> > +#include "irq-gic-v5.h"
>
> Why the ""? The normal include path (linux/irqchip) should work.
irq-gic-v5.h lives in drivers/irqchip, should I move it into
include/linux/irqchip (or I am missing something, likely) ?
I did not do it because it is only consumed by files in that directory.
> > +
> > +#define LPI_ID_BITS_LINEAR 12
> > +
> > +#define IRS_FLAGS_NON_COHERENT BIT(0)
> > +
> > +static DEFINE_PER_CPU(struct gicv5_irs_chip_data *, per_cpu_irs_data);
> > +static LIST_HEAD(irs_nodes);
> > +
> > +static u32 irs_readl_relaxed(struct gicv5_irs_chip_data *irs_data,
> > + const u64 reg_offset)
> > +{
> > + return readl_relaxed(irs_data->irs_base + reg_offset);
> > +}
> > +
> > +static void irs_writel_relaxed(struct gicv5_irs_chip_data *irs_data,
> > + const u32 val, const u64 reg_offset)
> > +{
> > + writel_relaxed(val, irs_data->irs_base + reg_offset);
> > +}
> > +
> > +static u64 irs_readq_relaxed(struct gicv5_irs_chip_data *irs_data,
> > + const u64 reg_offset)
> > +{
> > + return readq_relaxed(irs_data->irs_base + reg_offset);
> > +}
> > +
> > +static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
> > + const u64 val, const u64 reg_offset)
> > +{
> > + writeq_relaxed(val, irs_data->irs_base + reg_offset);
> > +}
> > +
> > +static int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask, u32 *val)
> > +{
> > + void __iomem *reg = addr + offset;
> > + u32 tmp;
> > + int ret;
> > +
> > + ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
> > +
> > + if (val)
> > + *val = tmp;
>
> Updating the result value on error is rather odd. Do you rely on this
> anywhere? If not, consider avoiding the write-back on timeout.
I do rely on it to avoid reading the register once again on return if
the wait succeeded (but we don't know unless for some registers we
check another bit in the returned value).
Yes it is odd and cumbersome, these poll loops are annoying.
> > +
> > + return ret;
> > +}
> > +
> > +#define gicv5_irs_wait_for_op(base, reg, mask) \
> > + ({ \
> > + int ret; \
> > + \
> > + ret = gicv5_wait_for_op(base, reg, mask, NULL); \
> > + if (unlikely(ret == -ETIMEDOUT)) \
> > + pr_err_ratelimited(#reg" timeout...\n"); \
> > + ret; \
> > + })
> > +
> > +/* Wait for completion of an IST change */
> > +static int gicv5_irs_ist_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
> > +{
> > + return gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_IST_STATUSR,
> > + GICV5_IRS_IST_STATUSR_IDLE);
> > +}
> > +
> > +static int __init gicv5_irs_init_ist_linear(struct gicv5_irs_chip_data *irs_data,
> > + unsigned int lpi_id_bits,
> > + unsigned int istsz)
> > +{
> > + size_t l2istsz;
> > + u32 n, cfgr;
> > + void *ist;
> > + u64 baser;
> > + int ret;
> > +
> > + /* Taken from GICv5 specifications 10.2.1.13 IRS_IST_BASER */
> > + n = max(5, lpi_id_bits + 1 + istsz);
> > +
> > + l2istsz = BIT(n + 1);
> > + /*
> > + * Check memory requirements. For a linear IST we cap the
> > + * number of ID bits to a value that should never exceed
> > + * kmalloc interface memory allocation limits, so this
> > + * check is really belt and braces.
> > + */
> > + if (l2istsz > KMALLOC_MAX_SIZE) {
> > + u8 lpi_id_cap = ilog2(KMALLOC_MAX_SIZE) - 2 + istsz;
> > +
> > + pr_warn("Limiting LPI ID bits from %u to %u\n",
> > + lpi_id_bits, lpi_id_cap);
> > + lpi_id_bits = lpi_id_cap;
> > + l2istsz = KMALLOC_MAX_SIZE;
> > + }
> > +
> > + ist = kzalloc(l2istsz, GFP_KERNEL);
> > + if (!ist)
> > + return -ENOMEM;
> > +
> > + if (irs_data->flags & IRS_FLAGS_NON_COHERENT)
> > + dcache_clean_inval_poc((unsigned long)ist,
> > + (unsigned long)ist + l2istsz);
> > + else
> > + dsb(ishst);
> > +
> > + cfgr = FIELD_PREP(GICV5_IRS_IST_CFGR_STRUCTURE,
> > + GICV5_IRS_IST_CFGR_STRUCTURE_LINEAR) |
> > + FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, istsz) |
> > + FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ,
> > + GICV5_IRS_IST_CFGR_L2SZ_4K) |
> > + FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, lpi_id_bits);
> > + irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_IST_CFGR);
> > +
> > + gicv5_global_data.ist.l2 = false;
> > +
> > + baser = (virt_to_phys(ist) & GICV5_IRS_IST_BASER_ADDR_MASK) |
> > + FIELD_PREP(GICV5_IRS_IST_BASER_VALID, 0x1);
> > + irs_writeq_relaxed(irs_data, baser, GICV5_IRS_IST_BASER);
> > +
> > + /*
> > + * The polling wait (in gicv5_wait_for_op()) on a GIC register
> > + * provides the memory barriers (through MMIO accessors)
> > + * required to synchronize CPU and GIC access to IST memory.
> > + */
>
> This comment would be better placed with the helper itself, and avoid
> the repeats that I can see in the rest of the code.
On this, I thought about that. The problem is, the comment would be
buried in a helper function whereas it is in this function that we
are actually handing over memory to the GIC so in a way I thought
it was more appropriate to add it where the explanation is needed
and possibly clearer.
[...]
> > +/*
> > + * Try to match the L2 IST size to the pagesize, and if this is not possible
> > + * pick the smallest supported L2 size in order to minimise the requirement for
> > + * physically contiguous blocks of memory as page-sized allocations are
> > + * guaranteed to be physically contiguous, and are by definition the easiest to
> > + * find.
> > + *
> > + * Fall back to the smallest supported size (in the event that the pagesize
> > + * itself is not supported) again serves to make it easier to find physically
> > + * contiguous blocks of memory.
> > + */
> > +static int gicv5_irs_l2_sz(u32 idr2)
> > +{
> > + switch (PAGE_SIZE) {
> > + case SZ_64K:
> > + if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
> > + return GICV5_IRS_IST_CFGR_L2SZ_64K;
> > + fallthrough;
> > + case SZ_16K:
> > + if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
> > + return GICV5_IRS_IST_CFGR_L2SZ_16K;
> > + fallthrough;
> > + case SZ_4K:
> > + if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
> > + return GICV5_IRS_IST_CFGR_L2SZ_4K;
> > + if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
> > + return GICV5_IRS_IST_CFGR_L2SZ_16K;
> > + if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
> > + return GICV5_IRS_IST_CFGR_L2SZ_64K;
> > + break;
> > + }
> > +
> > + return -ENODEV;
> > +}
>
> Really, it shouldn't be possible to return an error here. I think this
> should be rewritten as:
>
> static unsigned int gicv5_irs_l2_sz(u32 idr2)
> {
> switch (PAGE_SIZE) {
> case SZ_64K:
> if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
> return GICV5_IRS_IST_CFGR_L2SZ_64K;
> fallthrough;
> case SZ_16K:
> if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
> return GICV5_IRS_IST_CFGR_L2SZ_16K;
> fallthrough;
> case SZ_4K:
> if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
> return GICV5_IRS_IST_CFGR_L2SZ_4K;
> break;
> }
>
> if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
> return GICV5_IRS_IST_CFGR_L2SZ_16K;
>
> return GICV5_IRS_IST_CFGR_L2SZ_64K;
Done.
> }
>
> and fix the sole call site accordingly.
Done.
> > +
> > +static int __init gicv5_irs_init_ist(struct gicv5_irs_chip_data *irs_data)
> > +{
> > + u32 lpi_id_bits, idr2_id_bits, idr2_min_lpi_id_bits,
> > + l2_iste_sz, l2sz, l2_iste_sz_split, idr2;
> > + bool two_levels, istmd;
> > + u64 baser;
> > + int ret;
> > +
> > + baser = irs_readq_relaxed(irs_data, GICV5_IRS_IST_BASER);
> > + if (FIELD_GET(GICV5_IRS_IST_BASER_VALID, baser)) {
> > + pr_err("IST is marked as valid already; cannot allocate\n");
> > + return -EPERM;
> > + }
> > +
> > + idr2 = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2);
> > +
> > + two_levels = !!FIELD_GET(GICV5_IRS_IDR2_IST_LEVELS, idr2);
> > +
> > + idr2_id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, idr2);
> > + idr2_min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, idr2);
> > +
> > + /*
> > + * For two level tables we are always allocating the maximum allowed
> > + * number of IDs.
>
> nit: isn't it the maximum number of L1 entries, rather than the
> maximum number if IDs?
Did:
s/allocating/supporting
> > + *
> > + * For 1-level tables, we should allocate a number of bits that
> > + * is >= min_lpi_id_bits but cap it to LPI_ID_BITS_LINEAR lest
> > + * the level 1-table gets too large and its memory allocation
> > + * may fail.
> > + */
> > + if (two_levels) {
> > + lpi_id_bits = idr2_id_bits;
> > + } else {
> > + lpi_id_bits = max(LPI_ID_BITS_LINEAR, idr2_min_lpi_id_bits);
> > + lpi_id_bits = min(lpi_id_bits, idr2_id_bits);
> > + }
> > +
> > + /*
> > + * Cap the ID bits according to the CPUIF supported ID bits
> > + */
> > + lpi_id_bits = min(lpi_id_bits, gicv5_global_data.cpuif_id_bits);
> > +
> > + if (two_levels) {
> > + l2sz = gicv5_irs_l2_sz(idr2);
> > + if (l2sz < 0)
> > + return l2sz;
> > + }
> > +
> > + istmd = !!FIELD_GET(GICV5_IRS_IDR2_ISTMD, idr2);
> > +
> > + l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_4;
> > +
> > + // Only supported if IRS_IDR2.ISTMD is 1
> > + if (istmd) {
> > + l2_iste_sz_split = FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, idr2);
> > +
> > + if (lpi_id_bits < l2_iste_sz_split)
> > + l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_8;
> > + else
> > + l2_iste_sz = GICV5_IRS_IST_CFGR_ISTSZ_16;
> > + }
> > +
> > + /*
> > + * Follow GICv5 specification recommendation to opt in for two
> > + * level tables
> > + */
> > + two_levels = two_levels && (lpi_id_bits > ((10 - l2_iste_sz) + (2 * l2sz)));
> > +
> > + if (two_levels)
>
> This is slightly confusing, as you are reusing the two_levels variable
> to mean something rather different (you go from "is 2 level supported"
> to "is 2 level a good idea").
>
> Consider simply expanding the condition in the if () statement.
Done.
> > + ret = gicv5_irs_init_ist_two_level(irs_data, lpi_id_bits,
> > + l2_iste_sz, l2sz);
> > + else
> > + ret = gicv5_irs_init_ist_linear(irs_data, lpi_id_bits,
> > + l2_iste_sz);
> > + if (ret)
> > + return ret;
> > +
> > + gicv5_init_lpis(BIT(lpi_id_bits));
> > +
> > + return 0;
> > +}
> > +
> > +struct iaffid_entry {
> > + u16 iaffid;
> > + bool valid;
> > +};
> > +
> > +static DEFINE_PER_CPU(struct iaffid_entry, cpu_iaffid);
> > +
> > +int gicv5_irs_cpu_to_iaffid(int cpuid, u16 *iaffid)
> > +{
> > + if (!per_cpu(cpu_iaffid, cpuid).valid) {
> > + pr_err("IAFFID for CPU %d has not been initialised\n", cpuid);
> > + return -ENODEV;
> > + }
> > +
> > + *iaffid = per_cpu(cpu_iaffid, cpuid).iaffid;
> > +
> > + return 0;
> > +}
> > +
> > +struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id)
> > +{
> > + struct gicv5_irs_chip_data *irs_data;
> > + u32 min, max;
> > +
> > + list_for_each_entry(irs_data, &irs_nodes, entry) {
> > + if (!irs_data->spi_range)
> > + continue;
> > +
> > + min = irs_data->spi_min;
> > + max = irs_data->spi_min + irs_data->spi_range - 1;
> > + if (spi_id >= min && spi_id <= max)
> > + return irs_data;
> > + }
>
> Funnily enough, this is exactly the sort of iterative searches the
> maple tree could have avoided, by storing INTID (and range) specific
> data. Never mind.
I did it with a range store with an Xarray but then removed it because
I thought it was overkill, one of those things I am not sure what's
best.
> > +
> > + return NULL;
> > +}
> > +
> > +static int gicv5_irs_wait_for_spi_op(struct gicv5_irs_chip_data *irs_data)
> > +{
> > + u32 statusr;
> > + int ret;
> > +
> > + ret = gicv5_wait_for_op(irs_data->irs_base, GICV5_IRS_SPI_STATUSR,
> > + GICV5_IRS_SPI_STATUSR_IDLE, &statusr);
> > + if (unlikely(ret == -ETIMEDOUT)) {
> > + pr_err_ratelimited("IRS_SPI_STATUSR timeout\n");
>
> You could simply have a helper similar to gicv5_irs_wait_for_op() that
> deals with the printing stuff. This would result in more homogeneous
> messages and less boilerplate code.
Yes, these poll loops are getting under my skin.
> > + return ret;
> > + }
> > +
> > + return !!FIELD_GET(GICV5_IRS_SPI_STATUSR_V, statusr) ? 0 : -ENXIO;
> > +}
> > +
> > +static int gicv5_irs_wait_for_irs_pe(struct gicv5_irs_chip_data *irs_data,
> > + bool selr)
> > +{
> > + u32 statusr;
> > + bool valid;
> > + int ret;
> > +
> > + ret = gicv5_wait_for_op(irs_data->irs_base, GICV5_IRS_PE_STATUSR,
> > + GICV5_IRS_PE_STATUSR_IDLE, &statusr);
> > +
> > + if (unlikely(ret == -ETIMEDOUT)) {
> > + pr_err_ratelimited("IRS_PE_STATUSR timeout after %s\n",
> > + selr ? "IRS_PE_SELR" : "IRS_PE_CR0");
> > + return ret;
>
> Is there a real value in specialising this based on the point we're
> coming from?
Probably not, good point.
> > + }
> > +
> > + if (selr) {
> > + valid = !!FIELD_GET(GICV5_IRS_PE_STATUSR_V, statusr);
> > + return valid ? 0 : -ENXIO;
> > + }
> > +
> > + return 0;
>
> nit: maybe simplify as:
>
> bool valid == true;
bool valid = true;
> [...]
> if (selr)
> valid = !!FIELD_GET(GICV5_IRS_PE_STATUSR_V, statusr);
>
> return valid ? 0 : -ENXIO;
Done.
>
> > +}
> > +
> > +static int gicv5_irs_wait_for_pe_selr(struct gicv5_irs_chip_data *irs_data)
> > +{
> > + return gicv5_irs_wait_for_irs_pe(irs_data, true);
> > +}
> > +
> > +static int gicv5_irs_wait_for_pe_cr0(struct gicv5_irs_chip_data *irs_data)
> > +{
> > + return gicv5_irs_wait_for_irs_pe(irs_data, false);
> > +}
> > +
> > +int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type)
> > +{
> > + struct gicv5_irs_chip_data *irs_data = d->chip_data;
> > + u32 selr, cfgr;
> > + bool level;
> > +
> > + switch (type) {
> > + case IRQ_TYPE_EDGE_RISING:
> > + case IRQ_TYPE_EDGE_FALLING:
> > + level = false;
> > + break;
> > + case IRQ_TYPE_LEVEL_HIGH:
> > + case IRQ_TYPE_LEVEL_LOW:
> > + level = true;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + guard(raw_spinlock)(&irs_data->spi_config_lock);
> > +
> > + selr = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, d->hwirq);
> > + irs_writel_relaxed(irs_data, selr, GICV5_IRS_SPI_SELR);
> > + if (gicv5_irs_wait_for_spi_op(irs_data))
> > + return -EIO;
> > +
> > + cfgr = FIELD_PREP(GICV5_IRS_SPI_CFGR_TM, level);
> > +
> > + irs_writel_relaxed(irs_data, cfgr, GICV5_IRS_SPI_CFGR);
> > + if (gicv5_irs_wait_for_spi_op(irs_data))
> > + return -EPERM;
>
> -EPERM is an odd return code. Is the expectation that the update
> operation can fail for another reason than "the HW has deadlocked"?
> If that's not the case, then something similar to the write to
> SPI_SELR is probably best.
>
> If there is a genuine "permission check" aspect, then we should be
> able to distinguish between the two.
No, -EIO is more appropriate. Changed.
> > +
> > + return 0;
> > +}
> > +
> > +static int gicv5_irs_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
> > +{
> > + return gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_CR0,
> > + GICV5_IRS_CR0_IDLE);
> > +}
> > +
> > +int gicv5_irs_register_cpu(int cpuid)
> > +{
> > + struct gicv5_irs_chip_data *irs_data;
> > + u32 selr, cr0;
> > + u16 iaffid;
> > + int ret;
> > +
> > + ret = gicv5_irs_cpu_to_iaffid(cpuid, &iaffid);
> > + if (ret) {
> > + pr_err("IAFFID for CPU %d has not been initialised\n", cpuid);
> > + return ret;
> > + }
> > +
> > + irs_data = per_cpu(per_cpu_irs_data, cpuid);
> > + if (!irs_data) {
> > + pr_err("No IRS associated with CPU %u\n", cpuid);
> > + return -ENXIO;
> > + }
> > +
> > + selr = FIELD_PREP(GICV5_IRS_PE_SELR_IAFFID, iaffid);
> > + irs_writel_relaxed(irs_data, selr, GICV5_IRS_PE_SELR);
> > +
> > + ret = gicv5_irs_wait_for_pe_selr(irs_data);
> > + if (ret) {
> > + pr_err("IAFFID 0x%x used in IRS_PE_SELR is invalid\n", iaffid);
> > + return -ENXIO;
> > + }
> > +
> > + cr0 = FIELD_PREP(GICV5_IRS_PE_CR0_DPS, 0x1);
> > + irs_writel_relaxed(irs_data, cr0, GICV5_IRS_PE_CR0);
> > +
> > + ret = gicv5_irs_wait_for_pe_cr0(irs_data);
> > + if (ret)
> > + return ret;
> > +
> > + pr_debug("CPU%d enabled PE IAFFID 0x%x\n", cpuid, iaffid);
> > +
> > + return 0;
> > +}
> > +
> > +static int __init gicv5_irs_init_bases(struct gicv5_irs_chip_data *irs_data,
> > + void __iomem *irs_base,
> > + struct fwnode_handle *handle)
> > +{
> > + struct device_node *np = to_of_node(handle);
> > + u32 cr0, cr1;
> > +
> > + irs_data->fwnode = handle;
> > + irs_data->irs_base = irs_base;
> > +
> > + if (of_property_read_bool(np, "dma-noncoherent")) {
> > + /*
> > + * A non-coherent IRS implies that some cache levels cannot be
> > + * used coherently by the cores and GIC. Our only option is to mark
> > + * memory attributes for the GIC as non-cacheable; by default,
> > + * non-cacheable memory attributes imply outer-shareable
> > + * shareability, the value written into IRS_CR1_SH is ignored.
> > + */
> > + cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_NO_WRITE_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_NO_WRITE_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_NO_WRITE_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_NON_CACHE) |
> > + FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_NON_CACHE);
> > + irs_data->flags |= IRS_FLAGS_NON_COHERENT;
> > + } else {
> > + cr1 = FIELD_PREP(GICV5_IRS_CR1_VPED_WA, GICV5_WRITE_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VPED_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VMD_WA, GICV5_WRITE_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VMD_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VPET_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_VMT_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_IST_WA, GICV5_WRITE_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_IST_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_IRS_CR1_IC, GICV5_WB_CACHE) |
> > + FIELD_PREP(GICV5_IRS_CR1_OC, GICV5_WB_CACHE) |
> > + FIELD_PREP(GICV5_IRS_CR1_SH, GICV5_INNER_SHARE);
> > + }
> > +
> > + irs_writel_relaxed(irs_data, cr1, GICV5_IRS_CR1);
> > +
> > + cr0 = FIELD_PREP(GICV5_IRS_CR0_IRSEN, 0x1);
> > + irs_writel_relaxed(irs_data, cr0, GICV5_IRS_CR0);
> > + gicv5_irs_wait_for_idle(irs_data);
> > +
> > + return 0;
> > +}
> > +
> > +static int __init gicv5_irs_of_init_affinity(struct device_node *node,
> > + struct gicv5_irs_chip_data *irs_data,
> > + u8 iaffid_bits)
> > +{
> > + /*
> > + * Detect IAFFID<->CPU mappings from the device tree and
> > + * record IRS<->CPU topology information.
> > + */
> > + u16 iaffid_mask = GENMASK(iaffid_bits - 1, 0);
> > + u16 *iaffids __free(kfree) = NULL;
> > + int ret, i, ncpus, niaffids;
> > +
> > + ncpus = of_property_count_elems_of_size(node, "cpus", sizeof(u32));
> > + if (ncpus < 0)
> > + return -EINVAL;
> > +
> > + niaffids = of_property_count_elems_of_size(node, "arm,iaffids",
> > + sizeof(u16));
> > + if (niaffids != ncpus)
> > + return -EINVAL;
> > +
> > + iaffids = kcalloc(niaffids, sizeof(*iaffids), GFP_KERNEL);
> > + if (!iaffids)
> > + return -ENOMEM;
> > +
> > + ret = of_property_read_u16_array(node, "arm,iaffids", iaffids, niaffids);
> > + if (ret)
> > + return ret;
> > +
> > + for (i = 0; i < ncpus; i++) {
> > + struct device_node *cpu_node;
> > + u32 cpu_phandle;
> > + int cpu;
> > +
> > + if (of_property_read_u32_index(node, "cpus", i, &cpu_phandle))
> > + continue;
> > +
> > + cpu_node = of_find_node_by_phandle(cpu_phandle);
> > + if (WARN_ON(!cpu_node))
> > + continue;
> > +
> > + cpu = of_cpu_node_to_id(cpu_node);
> > + of_node_put(cpu_node);
> > + if (WARN_ON(cpu < 0))
> > + continue;
> > +
> > + if (iaffids[i] & ~iaffid_mask) {
> > + pr_warn("CPU %d iaffid 0x%x exceeds IRS iaffid bits\n",
> > + cpu, iaffids[i]);
> > + continue;
> > + }
> > +
> > + per_cpu(cpu_iaffid, cpu).iaffid = iaffids[i];
> > + per_cpu(cpu_iaffid, cpu).valid = true;
> > +
> > + // We also know that the CPU is connected to this IRS
> > + per_cpu(per_cpu_irs_data, cpu) = irs_data;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static void irs_setup_pri_bits(u32 idr1)
> > +{
> > + switch (FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1)) {
> > + case GICV5_IRS_IDR1_PRIORITY_BITS_1BITS:
> > + gicv5_global_data.irs_pri_bits = 1;
> > + break;
> > + case GICV5_IRS_IDR1_PRIORITY_BITS_2BITS:
> > + gicv5_global_data.irs_pri_bits = 2;
> > + break;
> > + case GICV5_IRS_IDR1_PRIORITY_BITS_3BITS:
> > + gicv5_global_data.irs_pri_bits = 3;
> > + break;
> > + case GICV5_IRS_IDR1_PRIORITY_BITS_4BITS:
> > + gicv5_global_data.irs_pri_bits = 4;
> > + break;
> > + case GICV5_IRS_IDR1_PRIORITY_BITS_5BITS:
> > + gicv5_global_data.irs_pri_bits = 5;
> > + break;
> > + default:
> > + pr_warn("Detected wrong IDR priority bits value 0x%lx\n",
> > + FIELD_GET(GICV5_IRS_IDR1_PRIORITY_BITS, idr1));
>
> Please assign a default value. I don't feel confident leaving this
> uninitialised.
Done.
> > + break;
> > + }
> > +}
> > +
> > +static int __init gicv5_irs_init(struct device_node *node)
> > +{
> > + struct gicv5_irs_chip_data *irs_data;
> > + void __iomem *irs_base;
> > + u8 iaffid_bits;
> > + int ret;
> > + u32 idr;
> > +
> > + irs_data = kzalloc(sizeof(*irs_data), GFP_KERNEL);
> > + if (!irs_data)
> > + return -ENOMEM;
> > +
> > + raw_spin_lock_init(&irs_data->spi_config_lock);
> > +
> > + irs_base = of_io_request_and_map(node, 0, "IRS");
> > + if (IS_ERR(irs_base)) {
> > + pr_err("%pOF: unable to map GICv5 IRS registers\n", node);
> > + ret = PTR_ERR(irs_base);
> > + goto out_err;
> > + }
> > +
> > + gicv5_irs_init_bases(irs_data, irs_base, &node->fwnode);
>
> Make this function return void, since it never fails and its return
> value is never checked either.
Done.
> > +
> > + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
> > + iaffid_bits = FIELD_GET(GICV5_IRS_IDR1_IAFFID_BITS, idr) + 1;
> > +
> > + ret = gicv5_irs_of_init_affinity(node, irs_data, iaffid_bits);
> > + if (ret) {
> > + pr_err("Failed to parse CPU IAFFIDs from the device tree!\n");
> > + goto out_iomem;
> > + }
> > +
> > + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR2);
> > + if (WARN(!FIELD_GET(GICV5_IRS_IDR2_LPI, idr),
> > + "LPI support not available - no IPIs, can't proceed\n")) {
> > + ret = -ENODEV;
> > + goto out_iomem;
> > + }
> > +
> > + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR7);
> > + irs_data->spi_min = FIELD_GET(GICV5_IRS_IDR7_SPI_BASE, idr);
> > +
> > + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR6);
> > + irs_data->spi_range = FIELD_GET(GICV5_IRS_IDR6_SPI_IRS_RANGE, idr);
> > +
> > + if (irs_data->spi_range) {
> > + pr_info("%s detected SPI range [%u-%u]\n",
> > + of_node_full_name(node),
> > + irs_data->spi_min,
> > + irs_data->spi_min +
> > + irs_data->spi_range - 1);
> > + }
> > +
> > + /*
> > + * Do the global setting only on the first IRS.
> > + * Global properties (iaffid_bits, global spi count) are guaranteed to
> > + * be consistent across IRSes by the architecture.
> > + */
> > + if (list_empty(&irs_nodes)) {
> > +
> > + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
> > + irs_setup_pri_bits(idr);
> > +
> > + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR5);
> > + gicv5_global_data.global_spi_count =
> > + FIELD_GET(GICV5_IRS_IDR5_SPI_RANGE, idr);
> > +
> > + gicv5_init_lpi_domain();
> > +
> > + pr_debug("Detected %u SPIs globally\n",
> > + gicv5_global_data.global_spi_count);
> > + }
> > +
> > + list_add_tail(&irs_data->entry, &irs_nodes);
> > +
> > + return 0;
> > +out_iomem:
> > + iounmap(irs_base);
> > +out_err:
> > + kfree(irs_data);
> > + return ret;
> > +}
> > +
> > +void __init gicv5_irs_remove(void)
> > +{
> > + struct gicv5_irs_chip_data *irs_data, *tmp_data;
> > +
> > + gicv5_free_lpi_domain();
> > + gicv5_deinit_lpis();
> > +
> > + list_for_each_entry_safe(irs_data, tmp_data, &irs_nodes, entry) {
> > + iounmap(irs_data->irs_base);
> > + list_del(&irs_data->entry);
> > + kfree(irs_data);
> > + }
> > +}
> > +
> > +int __init gicv5_irs_enable(void)
> > +{
> > + struct gicv5_irs_chip_data *irs_data;
> > + int ret;
> > +
> > + irs_data = list_first_entry_or_null(&irs_nodes,
> > + struct gicv5_irs_chip_data, entry);
> > + if (!irs_data)
> > + return -ENODEV;
> > +
> > + ret = gicv5_irs_init_ist(irs_data);
> > + if (ret) {
> > + pr_err("Failed to init IST\n");
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +int __init gicv5_irs_of_probe(struct device_node *parent)
> > +{
> > + struct device_node *np;
> > + int ret;
> > +
> > + for_each_available_child_of_node(parent, np) {
> > + if (!of_device_is_compatible(np, "arm,gic-v5-irs"))
> > + continue;
> > +
> > + ret = gicv5_irs_init(np);
> > + if (ret)
> > + pr_err("Failed to init IRS %s\n", np->full_name);
> > + }
> > +
> > + return list_empty(&irs_nodes) ? -ENODEV : 0;
> > +}
>
> Just a passing comment: consider splitting this patch in two (IRS on
> one side, CPUif on the other). I'm only half-way through, and it's
> quite tiring... :-/
Well, on this I am having a hard time as I replied to Thomas as well.
We do need CPUIF + IRS (LPI so IPIs) to make this a functional patch.
If I split per component, this might simplify review (and that's what
I did in v1 but Thomas complained that could not find SMP
initialization).
What I could do to reduce the number of lines is moving SPI support
in a separate patch but I don't think there is a way to split CPUIF
and IRS and have a patch series that is bisectable and works at every
stage (by the way the last patch should be merged with this one because
technically with this patch we have a working GICv5).
I will try to think more.
I understand that it is tiring and I appreciate a lot all reviews
received so far.
> > diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..c4d4e85382f672fa4ae334db1a4e4c7c4f46b9fe
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-gic-v5.c
> > @@ -0,0 +1,1058 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
> > + */
> > +
> > +#define pr_fmt(fmt) "GICv5: " fmt
> > +
> > +#include <linux/cpuhotplug.h>
> > +#include <linux/idr.h>
> > +#include <linux/irqchip.h>
> > +#include <linux/irqdomain.h>
> > +#include <linux/slab.h>
> > +#include <linux/wordpart.h>
> > +
> > +#include <asm/cpufeature.h>
> > +#include <asm/exception.h>
> > +
> > +#include "irq-gic-v5.h"
> > +
> > +static u8 pri_bits = 5;
> > +#define GICV5_IRQ_PRI_MASK 0x1f
> > +#define GICV5_IRQ_PRI_MI \
> > + (GICV5_IRQ_PRI_MASK & GENMASK(4, 5 - pri_bits))
> > +
> > +static bool gicv5_cpuif_has_gcie(void)
> > +{
> > + return this_cpu_has_cap(ARM64_HAS_GICV5_CPUIF);
> > +}
> > +
> > +struct gicv5_chip_data gicv5_global_data __read_mostly;
> > +
> > +static DEFINE_IDA(lpi_ida);
> > +static u32 num_lpis;
> > +
> > +void __init gicv5_init_lpis(u32 lpis)
> > +{
> > + num_lpis = lpis;
> > +}
> > +
> > +void __init gicv5_deinit_lpis(void)
> > +{
> > + num_lpis = 0;
> > +}
> > +
> > +static int alloc_lpi(void)
> > +{
> > + if (!num_lpis)
> > + return -ENOSPC;
> > +
> > + return ida_alloc_max(&lpi_ida, num_lpis - 1, GFP_KERNEL);
> > +}
> > +
> > +static void release_lpi(u32 lpi)
> > +{
> > + ida_free(&lpi_ida, lpi);
> > +}
> > +
> > +static int gicv5_alloc_lpi(void)
> > +{
> > + return alloc_lpi();
> > +}
> > +
> > +static void gicv5_free_lpi(u32 lpi)
> > +{
> > + release_lpi(lpi);
> > +}
> > +
> > +static void gicv5_ppi_priority_init(void)
> > +{
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR0_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR1_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR2_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR3_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR4_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR5_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR6_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR7_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR8_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR9_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR10_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR11_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR12_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR13_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR14_EL1);
> > + write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR15_EL1);
> > +
> > + /*
> > + * Context syncronization required to mare system
> > + * register writes effects are synchronized
>
> synchroni[sz]ation? Not sure what "mare" is supposed to be here, but
> I can't see any horse.
It is "mare" as used in "night-mare". Joking, no idea how I managed to
get it there, probably a text substitution turned wrong that even a
spell checker could not spot. Apologies.
> But I don't think there is much point in saying that sysreg accesses
> need synchronisation, if that's what this is meant to say.
It is, updated.
> > + */
> > + isb();
> > +}
> > +
> > +static void gicv5_hwirq_init(irq_hw_number_t hwirq, u8 priority, u8 hwirq_type)
> > +{
> > + u64 cdpri, cdaff;
> > + u16 iaffid;
> > + int ret;
> > +
> > + if (hwirq_type == GICV5_HWIRQ_TYPE_LPI || hwirq_type == GICV5_HWIRQ_TYPE_SPI) {
> > + cdpri = FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK, priority) |
> > + FIELD_PREP(GICV5_GIC_CDPRI_TYPE_MASK, hwirq_type) |
> > + FIELD_PREP(GICV5_GIC_CDPRI_ID_MASK, hwirq);
> > + gic_insn(cdpri, GICV5_OP_GIC_CDPRI);
> > +
> > + ret = gicv5_irs_cpu_to_iaffid(smp_processor_id(), &iaffid);
> > +
> > + if (WARN_ON_ONCE(ret))
> > + return;
> > +
> > + cdaff = FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, iaffid) |
> > + FIELD_PREP(GICV5_GIC_CDAFF_TYPE_MASK, hwirq_type) |
> > + FIELD_PREP(GICV5_GIC_CDAFF_ID_MASK, hwirq);
> > + gic_insn(cdaff, GICV5_OP_GIC_CDAFF);
> > + }
> > +}
> > +
> > +static void gicv5_ppi_irq_mask(struct irq_data *d)
> > +{
> > + u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> > +
> > + if (d->hwirq < 64)
> > + sysreg_clear_set_s(SYS_ICC_PPI_ENABLER0_EL1, hwirq_id_bit, 0);
> > + else
> > + sysreg_clear_set_s(SYS_ICC_PPI_ENABLER1_EL1, hwirq_id_bit, 0);
> > +
> > + /*
> > + * Ensure that the disable takes effect
> > + */
>
> It would be more interesting if you indicated *why* we want immediate
> effect on mask, while we don't have the same requirement on unmask.
That's a very good point, working on it.
> > + isb();
> > +}
> > +
[...]
> > +static int gicv5_ppi_irq_get_irqchip_state(struct irq_data *d,
> > + enum irqchip_irq_state which,
> > + bool *val)
> > +{
> > + u64 pendr, activer, enabler, hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> > +
> > + switch (which) {
> > + case IRQCHIP_STATE_PENDING:
> > + pendr = READ_PPI_REG(d->hwirq, SPEND);
> > +
> > + *val = !!(pendr & hwirq_id_bit);
> > +
> > + return 0;
> > + case IRQCHIP_STATE_ACTIVE:
> > + activer = READ_PPI_REG(d->hwirq, SACTIVE);
> > +
> > + *val = !!(activer & hwirq_id_bit);
> > +
> > + return 0;
> > + case IRQCHIP_STATE_MASKED:
> > + enabler = READ_PPI_REG(d->hwirq, ENABLE);
> > +
> > + *val = !(enabler & hwirq_id_bit);
> > +
> > + return 0;
>
> Please drop this IRQCHIP_STATE_MASKED. It was solely introduced to
> paper over a terrible suspend/resume bug in some other interrupt
> controller, and I'd rather not let this stupidity propagate any
> further. This also applies to the set_irqchip_state() callbacks.
Right, removed for all interrupt types (don't think it is PPI specific).
> > + default:
> > + pr_debug("Unexpected PPI irqchip state\n");
> > + return -EINVAL;
>
> Useless return.
Gone.
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int gicv5_iri_irq_get_irqchip_state(struct irq_data *d,
> > + enum irqchip_irq_state which,
> > + bool *val, u8 hwirq_type)
> > +{
> > + u64 icsr, cdrcfg;
> > +
> > + cdrcfg = d->hwirq | FIELD_PREP(GICV5_GIC_CDRCFG_TYPE_MASK, hwirq_type);
> > +
> > + gic_insn(cdrcfg, GICV5_OP_GIC_CDRCFG);
> > + isb();
> > + icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
> > +
> > + if (FIELD_GET(ICC_ICSR_EL1_F, icsr)) {
> > + pr_err("ICSR_EL1 is invalid\n");
> > + return -EINVAL;
> > + }
> > +
> > + switch (which) {
> > + case IRQCHIP_STATE_PENDING:
> > + *val = !!(FIELD_GET(ICC_ICSR_EL1_Pending, icsr));
> > + return 0;
> > +
> > + case IRQCHIP_STATE_ACTIVE:
> > + *val = !!(FIELD_GET(ICC_ICSR_EL1_Active, icsr));
> > + return 0;
> > +
> > + case IRQCHIP_STATE_MASKED:
> > + *val = !(FIELD_GET(ICC_ICSR_EL1_Enabled, icsr));
> > + return 0;
Removed this hunk too, see above.
> > +
> > + default:
> > + pr_debug("Unexpected irqchip_irq_state\n");
> > + return -EINVAL;
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int gicv5_spi_irq_get_irqchip_state(struct irq_data *d,
> > + enum irqchip_irq_state which,
> > + bool *val)
> > +{
> > + return gicv5_iri_irq_get_irqchip_state(d, which, val,
> > + GICV5_HWIRQ_TYPE_SPI);
> > +}
> > +
> > +static int gicv5_lpi_irq_get_irqchip_state(struct irq_data *d,
> > + enum irqchip_irq_state which,
> > + bool *val)
> > +{
> > + return gicv5_iri_irq_get_irqchip_state(d, which, val,
> > + GICV5_HWIRQ_TYPE_LPI);
> > +}
> > +
> > +static int gicv5_ppi_irq_set_irqchip_state(struct irq_data *d,
> > + enum irqchip_irq_state which,
> > + bool val)
> > +{
> > + u64 hwirq_id_bit = BIT_ULL(d->hwirq % 64);
> > +
> > + switch (which) {
> > + case IRQCHIP_STATE_PENDING:
> > + WRITE_PPI_REG(val, d->hwirq, hwirq_id_bit, PEND);
> > + return 0;
> > + case IRQCHIP_STATE_ACTIVE:
> > + WRITE_PPI_REG(val, d->hwirq, hwirq_id_bit, ACTIVE);
> > + return 0;
> > + case IRQCHIP_STATE_MASKED:
> > + if (val)
> > + gicv5_ppi_irq_mask(d);
> > + else
> > + gicv5_ppi_irq_unmask(d);
> > + return 0;
>
> Same thing.
>
> > + default:
> > + pr_debug("Unexpected PPI irqchip state\n");
> > + return -EINVAL;
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static void gicv5_iri_irq_write_pending_state(struct irq_data *d, bool val,
> > + u8 hwirq_type)
> > +{
> > + u64 cdpend;
> > +
> > + cdpend = FIELD_PREP(GICV5_GIC_CDPEND_TYPE_MASK, hwirq_type) |
> > + FIELD_PREP(GICV5_GIC_CDPEND_ID_MASK, d->hwirq) |
> > + FIELD_PREP(GICV5_GIC_CDPEND_PENDING_MASK, val);
> > +
> > + gic_insn(cdpend, GICV5_OP_GIC_CDPEND);
> > +}
> > +
> > +static void gicv5_spi_irq_write_pending_state(struct irq_data *d, bool val)
> > +{
> > + gicv5_iri_irq_write_pending_state(d, val, GICV5_HWIRQ_TYPE_SPI);
> > +}
> > +
> > +static void gicv5_lpi_irq_write_pending_state(struct irq_data *d, bool val)
> > +{
> > + gicv5_iri_irq_write_pending_state(d, val, GICV5_HWIRQ_TYPE_LPI);
> > +}
> > +
> > +static int gicv5_spi_irq_set_irqchip_state(struct irq_data *d,
> > + enum irqchip_irq_state which,
> > + bool val)
> > +{
> > + switch (which) {
> > + case IRQCHIP_STATE_PENDING:
> > + gicv5_spi_irq_write_pending_state(d, val);
> > + break;
> > + case IRQCHIP_STATE_MASKED:
> > + if (val)
> > + gicv5_spi_irq_mask(d);
> > + else
> > + gicv5_spi_irq_unmask(d);
> > + break;
Removed this hunk too.
> > + default:
> > + pr_debug("Unexpected irqchip_irq_state\n");
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
[...]
> > +static inline void handle_irq_per_domain(u32 hwirq)
>
> Drop the inline.
Done.
> > +{
> > + u8 hwirq_type = FIELD_GET(GICV5_HWIRQ_TYPE, hwirq);
> > + u32 hwirq_id = FIELD_GET(GICV5_HWIRQ_ID, hwirq);
> > + struct irq_domain *domain = NULL;
> > +
> > + if (hwirq_type == GICV5_HWIRQ_TYPE_PPI)
> > + domain = gicv5_global_data.ppi_domain;
> > + else if (hwirq_type == GICV5_HWIRQ_TYPE_SPI)
> > + domain = gicv5_global_data.spi_domain;
> > + else if (hwirq_type == GICV5_HWIRQ_TYPE_LPI)
> > + domain = gicv5_global_data.lpi_domain;
>
> This could be more clearly written as a switch/case statement, and
> avoid calling into generic_handle_domain_irq() with a NULL pointer,
> should this ever happen.
Done.
> > +
> > + if (generic_handle_domain_irq(domain, hwirq_id)) {
> > + pr_err_once("Could not handle, hwirq = 0x%x", hwirq_id);
> > + gicv5_hwirq_eoi(hwirq_id, hwirq_type);
> > + }
> > +}
> > +
> > +static void __exception_irq_entry gicv5_handle_irq(struct pt_regs *regs)
> > +{
> > + bool valid;
> > + u32 hwirq;
> > + u64 ia;
> > +
> > + ia = gicr_insn(GICV5_OP_GICR_CDIA);
> > + valid = GICV5_GIC_CDIA_VALID(ia);
> > +
> > + if (!valid)
> > + return;
> > +
> > + /*
> > + * Ensure that the CDIA instruction effects (ie IRQ activation) are
> > + * completed before handling the interrupt.
> > + */
> > + gsb_ack();
> > +
> > + /*
> > + * Ensure instruction ordering between an acknowledgment and subsequent
> > + * instructions in the IRQ handler using an ISB.
> > + */
> > + isb();
> > +
> > + hwirq = FIELD_GET(GICV5_HWIRQ_INTID, ia);
> > +
> > + handle_irq_per_domain(hwirq);
> > +}
> > +
> > +static void gicv5_cpu_disable_interrupts(void)
> > +{
> > + u64 cr0;
> > +
> > + cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0);
> > + write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
> > +}
> > +
> > +static void gicv5_cpu_enable_interrupts(void)
> > +{
> > + u64 cr0, pcr;
> > +
> > + write_sysreg_s(0, SYS_ICC_PPI_ENABLER0_EL1);
> > + write_sysreg_s(0, SYS_ICC_PPI_ENABLER1_EL1);
> > +
> > + gicv5_ppi_priority_init();
> > +
> > + pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, GICV5_IRQ_PRI_MI);
> > + write_sysreg_s(pcr, SYS_ICC_PCR_EL1);
> > +
> > + cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 1);
> > + write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
> > +}
> > +
> > +static int base_ipi_virq;
> > +
> > +static int gicv5_starting_cpu(unsigned int cpu)
> > +{
> > + if (WARN(!gicv5_cpuif_has_gcie(),
> > + "GICv5 system components present but CPU does not have FEAT_GCIE"))
> > + return -ENODEV;
> > +
> > + gicv5_cpu_enable_interrupts();
> > +
> > + return gicv5_irs_register_cpu(cpu);
> > +}
> > +
> > +static void __init gicv5_smp_init(void)
> > +{
> > + unsigned int num_ipis = GICV5_IPIS_PER_CPU * nr_cpu_ids;
> > +
> > + cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
> > + "irqchip/arm/gicv5:starting",
> > + gicv5_starting_cpu, NULL);
> > +
> > + base_ipi_virq = irq_domain_alloc_irqs(gicv5_global_data.ipi_domain,
> > + num_ipis, NUMA_NO_NODE, NULL);
> > + if (WARN(base_ipi_virq <= 0, "IPI IRQ allocation was not successful"))
> > + return;
> > +
> > + set_smp_ipi_range_percpu(base_ipi_virq, GICV5_IPIS_PER_CPU, nr_cpu_ids);
> > +}
> > +
> > +static void __init gicv5_free_domains(void)
> > +{
> > + if (gicv5_global_data.ppi_domain)
> > + irq_domain_remove(gicv5_global_data.ppi_domain);
> > + if (gicv5_global_data.spi_domain)
> > + irq_domain_remove(gicv5_global_data.spi_domain);
> > + if (gicv5_global_data.ipi_domain)
> > + irq_domain_remove(gicv5_global_data.ipi_domain);
>
> Make these pointers NULL once you have dropped the irqdomains.
Done.
> > +}
> > +
> > +static int __init gicv5_init_domains(struct fwnode_handle *handle)
> > +{
> > + u32 spi_count = gicv5_global_data.global_spi_count;
> > + struct irq_domain *d;
> > +
> > + d = irq_domain_create_linear(handle, 128, &gicv5_irq_ppi_domain_ops,
> > + NULL);
>
> Consider having a named constant for the number of PPIs.
Done.
> > + if (!d)
> > + return -ENOMEM;
> > +
> > + irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED);
> > + gicv5_global_data.ppi_domain = d;
> > +
> > + if (spi_count) {
> > + d = irq_domain_create_linear(handle, spi_count,
> > + &gicv5_irq_spi_domain_ops, NULL);
> > +
> > + if (!d) {
> > + gicv5_free_domains();
> > + return -ENOMEM;
> > + }
> > +
> > + gicv5_global_data.spi_domain = d;
> > + irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED);
> > + }
> > +
> > + if (!WARN(!gicv5_global_data.lpi_domain,
> > + "LPI domain uninitialized, can't set up IPIs")) {
> > + d = irq_domain_create_hierarchy(gicv5_global_data.lpi_domain,
> > + 0, GICV5_IPIS_PER_CPU * nr_cpu_ids,
> > + NULL, &gicv5_irq_ipi_domain_ops,
> > + NULL);
> > +
> > + if (!d) {
> > + gicv5_free_domains();
> > + return -ENOMEM;
> > + }
> > + gicv5_global_data.ipi_domain = d;
> > + }
> > + gicv5_global_data.fwnode = handle;
> > +
> > + return 0;
> > +}
> > +
> > +static void gicv5_set_cpuif_pribits(void)
> > +{
> > + u64 icc_idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
> > +
> > + switch (FIELD_GET(ICC_IDR0_EL1_PRI_BITS, icc_idr0)) {
> > + case ICC_IDR0_EL1_PRI_BITS_4BITS:
> > + gicv5_global_data.cpuif_pri_bits = 4;
> > + break;
> > + case ICC_IDR0_EL1_PRI_BITS_5BITS:
> > + gicv5_global_data.cpuif_pri_bits = 5;
> > + break;
> > + default:
> > + pr_err("Unexpected ICC_IDR0_EL1_PRI_BITS value, default to 4");
> > + gicv5_global_data.cpuif_pri_bits = 4;
> > + break;
> > + }
> > +}
> > +
> > +static void gicv5_set_cpuif_idbits(void)
> > +{
> > + u32 icc_idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
> > +
> > + switch (FIELD_GET(ICC_IDR0_EL1_ID_BITS, icc_idr0)) {
> > + case ICC_IDR0_EL1_ID_BITS_16BITS:
> > + gicv5_global_data.cpuif_id_bits = 16;
> > + break;
> > + case ICC_IDR0_EL1_ID_BITS_24BITS:
> > + gicv5_global_data.cpuif_id_bits = 24;
> > + break;
> > + default:
> > + pr_err("Unexpected ICC_IDR0_EL1_ID_BITS value, default to 16");
> > + gicv5_global_data.cpuif_id_bits = 16;
> > + break;
> > + }
> > +}
> > +
> > +
> > +static int __init gicv5_of_init(struct device_node *node,
> > + struct device_node *parent)
> > +{
> > + int ret;
> > +
> > + ret = gicv5_irs_of_probe(node);
> > + if (ret)
> > + return ret;
> > +
> > + ret = gicv5_init_domains(&node->fwnode);
> > + if (ret)
> > + goto out_irs;
> > +
> > + gicv5_set_cpuif_pribits();
> > + gicv5_set_cpuif_idbits();
> > +
> > + pri_bits = min_not_zero(gicv5_global_data.cpuif_pri_bits,
> > + gicv5_global_data.irs_pri_bits);
> > +
> > + ret = gicv5_starting_cpu(smp_processor_id());
> > + if (ret)
> > + goto out_dom;
> > +
> > + ret = set_handle_irq(gicv5_handle_irq);
> > + if (ret)
> > + goto out_int;
> > +
> > + ret = gicv5_irs_enable();
> > + if (ret)
> > + goto out_int;
> > +
> > + gicv5_smp_init();
> > +
> > + return 0;
> > +out_int:
> > + gicv5_cpu_disable_interrupts();
> > +out_dom:
> > + gicv5_free_domains();
> > +out_irs:
> > + gicv5_irs_remove();
> > +
> > + return ret;
> > +}
> > +IRQCHIP_DECLARE(gic_v5, "arm,gic-v5", gicv5_of_init);
> > diff --git a/drivers/irqchip/irq-gic-v5.h b/drivers/irqchip/irq-gic-v5.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..19569639153a084760c3b5b7f0fa84791ba0195c
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-gic-v5.h
> > @@ -0,0 +1,184 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Copyright (C) 2025 ARM Limited, All Rights Reserved.
> > + */
> > +#ifndef __LINUX_IRQCHIP_GIC_V5_H
> > +#define __LINUX_IRQCHIP_GIC_V5_H
> > +
> > +#include <asm/arch_gicv5.h>
> > +#include <asm/smp.h>
> > +
> > +#define GICV5_IPIS_PER_CPU MAX_IPI
> > +
> > +#define GICV5_HWIRQ_ID GENMASK(23, 0)
> > +#define GICV5_HWIRQ_TYPE GENMASK(31, 29)
> > +#define GICV5_HWIRQ_INTID GENMASK_ULL(31, 0)
> > +
> > +#define GICV5_HWIRQ_TYPE_PPI UL(0x1)
> > +#define GICV5_HWIRQ_TYPE_LPI UL(0x2)
> > +#define GICV5_HWIRQ_TYPE_SPI UL(0x3)
> > +
> > +#define GICV5_PPI_HM_EDGE UL(0x0)
> > +#define GICV5_PPI_HM_LEVEL UL(0x1)
> > +
> > +#define GICV5_NO_READ_ALLOC 0b0
> > +#define GICV5_READ_ALLOC 0b1
> > +#define GICV5_NO_WRITE_ALLOC 0b0
> > +#define GICV5_WRITE_ALLOC 0b1
> > +
> > +#define GICV5_NON_CACHE 0b00
> > +#define GICV5_WB_CACHE 0b01
> > +#define GICV5_WT_CACHE 0b10
> > +
> > +#define GICV5_NON_SHARE 0b00
> > +#define GICV5_OUTER_SHARE 0b10
> > +#define GICV5_INNER_SHARE 0b11
> > +
> > +#define GICV5_IRS_IDR1 0x0004
> > +#define GICV5_IRS_IDR2 0x0008
> > +#define GICV5_IRS_IDR5 0x0014
> > +#define GICV5_IRS_IDR6 0x0018
> > +#define GICV5_IRS_IDR7 0x001c
> > +#define GICV5_IRS_CR0 0x0080
> > +#define GICV5_IRS_CR1 0x0084
> > +#define GICV5_IRS_SPI_SELR 0x0108
> > +#define GICV5_IRS_SPI_CFGR 0x0114
> > +#define GICV5_IRS_SPI_STATUSR 0x0118
> > +#define GICV5_IRS_PE_SELR 0x0140
> > +#define GICV5_IRS_PE_STATUSR 0x0144
> > +#define GICV5_IRS_PE_CR0 0x0148
> > +#define GICV5_IRS_IST_BASER 0x0180
> > +#define GICV5_IRS_IST_CFGR 0x0190
> > +#define GICV5_IRS_IST_STATUSR 0x0194
> > +#define GICV5_IRS_MAP_L2_ISTR 0x01c0
> > +
> > +#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
> > +#define GICV5_IRS_IDR1_IAFFID_BITS GENMASK(19, 16)
> > +
> > +#define GICV5_IRS_IDR1_PRIORITY_BITS_1BITS 0b000
> > +#define GICV5_IRS_IDR1_PRIORITY_BITS_2BITS 0b001
> > +#define GICV5_IRS_IDR1_PRIORITY_BITS_3BITS 0b010
> > +#define GICV5_IRS_IDR1_PRIORITY_BITS_4BITS 0b011
> > +#define GICV5_IRS_IDR1_PRIORITY_BITS_5BITS 0b100
> > +
> > +#define GICV5_IRS_IDR2_ISTMD_SZ GENMASK(19, 15)
> > +#define GICV5_IRS_IDR2_ISTMD BIT(14)
> > +#define GICV5_IRS_IDR2_IST_L2SZ GENMASK(13, 11)
> > +#define GICV5_IRS_IDR2_IST_LEVELS BIT(10)
> > +#define GICV5_IRS_IDR2_MIN_LPI_ID_BITS GENMASK(9, 6)
> > +#define GICV5_IRS_IDR2_LPI BIT(5)
> > +#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
> > +
> > +#define GICV5_IRS_IDR5_SPI_RANGE GENMASK(24, 0)
> > +#define GICV5_IRS_IDR6_SPI_IRS_RANGE GENMASK(24, 0)
> > +#define GICV5_IRS_IDR7_SPI_BASE GENMASK(23, 0)
> > +
> > +#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
> > +#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
> > +#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
> > +
> > +#define GICV5_IRS_CR0_IDLE BIT(1)
> > +#define GICV5_IRS_CR0_IRSEN BIT(0)
> > +
> > +#define GICV5_IRS_CR1_VPED_WA BIT(15)
> > +#define GICV5_IRS_CR1_VPED_RA BIT(14)
> > +#define GICV5_IRS_CR1_VMD_WA BIT(13)
> > +#define GICV5_IRS_CR1_VMD_RA BIT(12)
> > +#define GICV5_IRS_CR1_VPET_WA BIT(11)
> > +#define GICV5_IRS_CR1_VPET_RA BIT(10)
> > +#define GICV5_IRS_CR1_VMT_WA BIT(9)
> > +#define GICV5_IRS_CR1_VMT_RA BIT(8)
> > +#define GICV5_IRS_CR1_IST_WA BIT(7)
> > +#define GICV5_IRS_CR1_IST_RA BIT(6)
> > +#define GICV5_IRS_CR1_IC GENMASK(5, 4)
> > +#define GICV5_IRS_CR1_OC GENMASK(3, 2)
> > +#define GICV5_IRS_CR1_SH GENMASK(1, 0)
> > +
> > +#define GICV5_IRS_SPI_STATUSR_V BIT(1)
> > +#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
> > +
> > +#define GICV5_IRS_SPI_SELR_ID GENMASK(23, 0)
> > +
> > +#define GICV5_IRS_SPI_CFGR_TM BIT(0)
> > +
> > +#define GICV5_IRS_PE_SELR_IAFFID GENMASK(15, 0)
> > +
> > +#define GICV5_IRS_PE_STATUSR_V BIT(1)
> > +#define GICV5_IRS_PE_STATUSR_IDLE BIT(0)
> > +
> > +#define GICV5_IRS_PE_CR0_DPS BIT(0)
> > +
> > +#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
> > +
> > +#define GICV5_IRS_IST_CFGR_STRUCTURE BIT(16)
> > +#define GICV5_IRS_IST_CFGR_ISTSZ GENMASK(8, 7)
> > +#define GICV5_IRS_IST_CFGR_L2SZ GENMASK(6, 5)
> > +#define GICV5_IRS_IST_CFGR_LPI_ID_BITS GENMASK(4, 0)
> > +
> > +#define GICV5_IRS_IST_CFGR_STRUCTURE_LINEAR 0b0
> > +#define GICV5_IRS_IST_CFGR_STRUCTURE_TWO_LEVEL 0b1
> > +
> > +#define GICV5_IRS_IST_CFGR_ISTSZ_4 0b00
> > +#define GICV5_IRS_IST_CFGR_ISTSZ_8 0b01
> > +#define GICV5_IRS_IST_CFGR_ISTSZ_16 0b10
> > +
> > +#define GICV5_IRS_IST_CFGR_L2SZ_4K 0b00
> > +#define GICV5_IRS_IST_CFGR_L2SZ_16K 0b01
> > +#define GICV5_IRS_IST_CFGR_L2SZ_64K 0b10
> > +
> > +#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
> > +#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
> > +
> > +#define GICV5_IRS_MAP_L2_ISTR_ID GENMASK(23, 0)
> > +
> > +#define GICV5_ISTL1E_VALID BIT_ULL(0)
> > +
> > +#define GICV5_ISTL1E_L2_ADDR_MASK GENMASK_ULL(55, 12)
> > +
> > +struct gicv5_chip_data {
> > + struct fwnode_handle *fwnode;
> > + struct irq_domain *ppi_domain;
> > + struct irq_domain *spi_domain;
> > + struct irq_domain *lpi_domain;
> > + struct irq_domain *ipi_domain;
> > + u32 global_spi_count;
> > + u8 cpuif_pri_bits;
> > + u8 cpuif_id_bits;
> > + u8 irs_pri_bits;
> > + u8 irs_iaffid_bits;
> > + struct {
> > + __le64 *l1ist_addr;
> > + u32 l2_size;
> > + u8 l2_bits;
> > + bool l2;
> > + } ist;
> > +};
> > +
> > +extern struct gicv5_chip_data gicv5_global_data __read_mostly;
> > +
> > +struct gicv5_irs_chip_data {
> > + struct list_head entry;
> > + struct fwnode_handle *fwnode;
> > + void __iomem *irs_base;
> > + u32 flags;
> > + u32 spi_min;
> > + u32 spi_range;
> > + raw_spinlock_t spi_config_lock;
> > +};
> > +
> > +void __init gicv5_init_lpi_domain(void);
> > +void __init gicv5_free_lpi_domain(void);
> > +
> > +int gicv5_irs_of_probe(struct device_node *parent);
> > +void gicv5_irs_remove(void);
> > +int gicv5_irs_enable(void);
> > +int gicv5_irs_register_cpu(int cpuid);
> > +int gicv5_irs_cpu_to_iaffid(int cpu_id, u16 *iaffid);
> > +struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id);
> > +int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
> > +int gicv5_spi_set_type(struct irq_data *d, unsigned int type);
> > +int gicv5_irs_iste_alloc(u32 lpi);
> > +
> > +void gicv5_init_lpis(u32 max);
> > +void gicv5_deinit_lpis(void);
> > +#endif
>
> Thanks,
Thanks a lot Marc !
Lorenzo
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support
2025-04-29 14:54 ` Lorenzo Pieralisi
@ 2025-04-29 15:38 ` Marc Zyngier
2025-04-29 16:02 ` Lorenzo Pieralisi
0 siblings, 1 reply; 43+ messages in thread
From: Marc Zyngier @ 2025-04-29 15:38 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Tue, 29 Apr 2025 15:54:07 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> On Mon, Apr 28, 2025 at 04:49:17PM +0100, Marc Zyngier wrote:
> > On Thu, 24 Apr 2025 11:25:30 +0100,
> > Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> > >
> > > Implement GICv5 CPU interface and IRS support, to manage interrupt
> > > state, priority and routing for all GICv5 interrupt types.
> > >
> > > The GICv5 CPU interface implements support for PE-Private Peripheral
> > > Interrupts (PPI), that are handled (enabled/prioritized/delivered)
> > > entirely within the CPU interface hardware.
> > >
> > > To enable PPI interrupts, implement the baseline GICv5 host kernel
> > > driver infrastructure required to handle interrupts on a GICv5 system.
> > >
> > > Add the exception handling code path and definitions for GICv5
> > > instructions.
> > >
> > > Add GICv5 PPI handling code as a specific IRQ domain to:
> > >
> > > - Set-up PPI priority
> > > - Manage PPI configuration and state
> > > - Manage IRQ flow handler
> > > - IRQs allocation/free
> > > - Hook-up a PPI specific IRQchip to provide the relevant methods
> > >
> > > PPI IRQ priority is chosen as the minimum allowed priority by the
> > > system design (after probing the number of priority bits implemented
> > > by the CPU interface).
> > >
> > > The GICv5 Interrupt Routing Service (IRS) component implements
> > > interrupt management and routing in the GICv5 architecture.
> > >
> > > A GICv5 system comprises one or more IRSes, that together
> > > handle the interrupt routing and state for the system.
> > >
> > > An IRS supports Shared Peripheral Interrupts (SPIs), that are
> > > interrupt sources directly connected to the IRS; they do not
> > > rely on memory for storage. The number of supported SPIs is
> > > fixed for a given implementation and can be probed through IRS
> > > IDR registers.
> > >
> > > SPI interrupt state and routing are managed through GICv5
> > > instructions.
> > >
> > > Each core (PE in GICv5 terms) in a GICv5 system is identified with
> > > an Interrupt AFFinity ID (IAFFID).
> > >
> > > An IRS manages a set of cores that are connected to it.
> > >
> > > Firmware provides a topology description that the driver uses
> > > to detect to which IRS a CPU (ie an IAFFID) is associated with.
> > >
> > > Use probeable information and firmware description to initialize
> > > the IRSes and implement GICv5 IRS SPIs support through an
> > > SPI-specific IRQ domain.
> > >
> > > The GICv5 IRS driver:
> > >
> > > - Probes IRSes in the system to detect SPI ranges
> > > - Associates an IRS with a set of cores connected to it
> > > - Adds an IRQchip structure for SPI handling
> > >
> > > SPIs priority is set to a value corresponding to the lowest
> > > permissible priority in the system (taking into account the
> > > implemented priority bits of the IRS and CPU interface).
> > >
> > > Since all IRQs are set to the same priority value, the value
> > > itself does not matter as long as it is a valid one.
> > >
> > > An IRS supports Logical Peripheral Interrupts (LPIs) and implement
> > > Linux IPIs on top of it.
> > >
> > > LPIs are used for interrupt signals that are translated by a
> > > GICv5 ITS (Interrupt Translation Service) but also for software
> > > generated IRQs - namely interrupts that are not driven by a HW
> > > signal, ie IPIs.
> > >
> > > LPIs rely on memory storage for interrupt routing and state.
> > >
> > > Memory storage is handled by the IRS - that is configured
> > > at probe time by the driver with the required memory.
> > >
> > > LPIs state and routing information is kept in the Interrupt
> > > State Table (IST).
> > >
> > > IRSes provide support for 1- or 2-level IST tables configured
> > > to support a maximum number of interrupts that depend on the
> > > OS configuration and the HW capabilities.
> > >
> > > On systems that provide 2-level IST support, always allow
> > > the maximum number of LPIs; On systems with only 1-level
> > > support, limit the number of LPIs to 2^12 to prevent
> > > wasting memory (presumably a system that supports a 1-level
> > > only IST is not expecting a large number of interrupts).
> > >
> > > On a 2-level IST system, L2 entries are allocated on
> > > demand.
> > >
> > > The IST table memory is allocated using the kmalloc() interface;
> > > the allocation required may be smaller than a page and must be
> > > made up of contiguous physical pages if larger than a page.
> > >
> > > On systems where the IRS is not cache-coherent with the CPUs,
> > > cache mainteinance operations are executed to clean and
> > > invalidate the allocated memory to the point of coherency
> > > making it visible to the IRS components.
> > >
> > > Add an LPI IRQ domain to:
> > >
> > > - Manage LPI state and routing
> > > - Add LPI IRQchip structure and callbacks
> > > - LPI domain allocation/de-allocation
> > >
> > > On GICv5 systems, IPIs are implemented using LPIs.
> > >
> > > Implement an IPI-specific IRQ domain created as a child/subdomain
> > > of the LPI domain to allocate the required number of LPIs needed
> > > to implement the IPIs.
> > >
> > > Move the arm64 IPI enum declaration to a header file so that the
> > > GICv5 driver code can detect how many IPIs are required by arch code.
> > >
> > > IPIs are backed by LPIs, add LPIs allocation/de-allocation
> > > functions.
> > >
> > > The LPI INTID namespace is managed using an IDA to alloc/free LPI
> > > INTIDs.
> > >
> > > Associate an IPI irqchip with IPI IRQ descriptors to provide
> > > core code with the irqchip.ipi_send_single() method required
> > > to raise an IPI.
> > >
> > > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > > Cc: Will Deacon <will@kernel.org>
> > > Cc: Thomas Gleixner <tglx@linutronix.de>
> > > Cc: Catalin Marinas <catalin.marinas@arm.com>
> > > Cc: Marc Zyngier <maz@kernel.org>
> > > ---
> > > MAINTAINERS | 2 +
> > > arch/arm64/include/asm/arch_gicv5.h | 91 +++
> > > arch/arm64/include/asm/smp.h | 17 +
> > > arch/arm64/kernel/smp.c | 17 -
> > > drivers/irqchip/Kconfig | 5 +
> > > drivers/irqchip/Makefile | 1 +
> > > drivers/irqchip/irq-gic-v5-irs.c | 841 ++++++++++++++++++++++++++++
> > > drivers/irqchip/irq-gic-v5.c | 1058 +++++++++++++++++++++++++++++++++++
> > > drivers/irqchip/irq-gic-v5.h | 184 ++++++
> >
> > Nit: the split between include/asm/arch_gicv5.h and
> > drivers/irqchip/irq-gic-v5.h is pretty pointless (this is obviously
> > inherited from the GICv3 on 32bit setup). Given that GICv5 is strictly
> > arm64, this split is no longer necessary.
>
> That's true but I thought I would leave sys instructions and barriers
> in arm64 arch code headers rather than moving them out. I am up for
> whatever you folks think it is best.
I can see two options:
- either you unify the two and place the result in
include/linux/irqchip/arm-gic-v5.h
- or you move the system stuff and the barriers to
arch/arm64/include/asm/sysreg.h together with the rest of the pile,
*and* move the other include file as above
> > > +#define GSB_ACK __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 1) | 31)
> > > +#define GSB_SYS __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 0) | 31)
> >
> > Can you please express this in terms of __SYS_BARRIER_INSN(), with a
> > slight rework of the SB definition? It would limit the propagation of
> > the 0xd5 constant and make it clear what 31 stands for.
>
> I tried but the __SYS_BARRIER_INSN() is a different class (GICv5
> barriers are sys instructions the SB barrier has a slightly different
> encoding), so maybe something like this ?
>
> #define GSB_ACK __msr_s(sys_insn(1, 0, 12, 0, 1), "xzr")
Why a different encoding? It looks completely similar to me. What I
was expecting to see is something along the lines of (completely
untested):
diff --git a/arch/arm64/include/asm/barrier.h b/arch/arm64/include/asm/barrier.h
index 1ca947d5c9396..7a00781d1d788 100644
--- a/arch/arm64/include/asm/barrier.h
+++ b/arch/arm64/include/asm/barrier.h
@@ -44,6 +44,8 @@
SB_BARRIER_INSN"nop\n", \
ARM64_HAS_SB))
+#define gsb_ack() asm volatile("GSC_ACK_BARRIER_INSN\n" : : : "memory")
+
#ifdef CONFIG_ARM64_PSEUDO_NMI
#define pmr_sync() \
do { \
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index 690b6ebd118f4..a84993c3d117b 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -112,10 +112,13 @@
/* Register-based PAN access, for save/restore purposes */
#define SYS_PSTATE_PAN sys_reg(3, 0, 4, 2, 3)
-#define __SYS_BARRIER_INSN(CRm, op2, Rt) \
- __emit_inst(0xd5000000 | sys_insn(0, 3, 3, (CRm), (op2)) | ((Rt) & 0x1f))
+#define __SYS_BARRIER_INSN(op0, op1, CRn, CRm, op2, Rt) \
+ __emit_inst(0xd5000000 | \
+ sys_insn((op0), (op1), (CRn), (CRm), (op2)) | \
+ ((Rt) & 0x1f))
-#define SB_BARRIER_INSN __SYS_BARRIER_INSN(0, 7, 31)
+#define SB_BARRIER_INSN __SYS_BARRIER_INSN(0, 3, 3, 0, 7, 31)
+#define GSB_ACK_BARRIER_INSN __SYS_BARRIER_INSN(1, 0, 12, 0, 1, 31)
/* Data cache zero operations */
#define SYS_DC_ISW sys_insn(1, 0, 7, 6, 2)
and the same thing for GSB SYS.
> > > +#include "irq-gic-v5.h"
> >
> > Why the ""? The normal include path (linux/irqchip) should work.
>
> irq-gic-v5.h lives in drivers/irqchip, should I move it into
> include/linux/irqchip (or I am missing something, likely) ?
>
> I did not do it because it is only consumed by files in that directory.
I full intend for KVM to consume all of this pretty much directly, and
the KVM code will definitely not live in drivers/irqchip.
> > > + ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
> > > +
> > > + if (val)
> > > + *val = tmp;
> >
> > Updating the result value on error is rather odd. Do you rely on this
> > anywhere? If not, consider avoiding the write-back on timeout.
>
> I do rely on it to avoid reading the register once again on return if
> the wait succeeded (but we don't know unless for some registers we
> check another bit in the returned value).
That's a bit annoying. But hey...
> > > + /*
> > > + * The polling wait (in gicv5_wait_for_op()) on a GIC register
> > > + * provides the memory barriers (through MMIO accessors)
> > > + * required to synchronize CPU and GIC access to IST memory.
> > > + */
> >
> > This comment would be better placed with the helper itself, and avoid
> > the repeats that I can see in the rest of the code.
>
> On this, I thought about that. The problem is, the comment would be
> buried in a helper function whereas it is in this function that we
> are actually handing over memory to the GIC so in a way I thought
> it was more appropriate to add it where the explanation is needed
> and possibly clearer.
It's not clearer. If you want it to be clearer, name the helper in a
way that makes it actions explicit (gicv5_irs_ist_synchronise() ?),
and let the comment explain how the synchronisation is enforced next
to the helper.
Using explicit names is far better than duplicating comments, IMHO.
> > > + list_for_each_entry(irs_data, &irs_nodes, entry) {
> > > + if (!irs_data->spi_range)
> > > + continue;
> > > +
> > > + min = irs_data->spi_min;
> > > + max = irs_data->spi_min + irs_data->spi_range - 1;
> > > + if (spi_id >= min && spi_id <= max)
> > > + return irs_data;
> > > + }
> >
> > Funnily enough, this is exactly the sort of iterative searches the
> > maple tree could have avoided, by storing INTID (and range) specific
> > data. Never mind.
>
> I did it with a range store with an Xarray but then removed it because
> I thought it was overkill, one of those things I am not sure what's
> best.
Probably not an issue for now. Time will tell as we get larger
machines with multiple IRSes.
> > Just a passing comment: consider splitting this patch in two (IRS on
> > one side, CPUif on the other). I'm only half-way through, and it's
> > quite tiring... :-/
>
> Well, on this I am having a hard time as I replied to Thomas as well.
>
> We do need CPUIF + IRS (LPI so IPIs) to make this a functional
> patch.
We don't need things to be functional. We need things to not break the
build. So as long as things are split in a logical way and, this
should be fine.
> If I split per component, this might simplify review (and that's what
> I did in v1 but Thomas complained that could not find SMP
> initialization).
Well, you could have one patch clearly labelled as "SMP init", which
would both satisfy tglx's requirements *and* my tired brain.
>
> What I could do to reduce the number of lines is moving SPI support
> in a separate patch but I don't think there is a way to split CPUIF
> and IRS and have a patch series that is bisectable and works at every
> stage (by the way the last patch should be merged with this one because
> technically with this patch we have a working GICv5).
The trivial way to make it bisectable is to avoid compiling it until
it is sufficiently feature-complete. Any sizeable amount of new code
relies on this to a degree or another.
Also, think of the commit messages: what you have here is a massive
wall of text that would be more appropriate for a cover letter, and
could be much smaller on a per-patch basis without losing any
meaningful content.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support
2025-04-29 15:38 ` Marc Zyngier
@ 2025-04-29 16:02 ` Lorenzo Pieralisi
0 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-29 16:02 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Tue, Apr 29, 2025 at 04:38:02PM +0100, Marc Zyngier wrote:
> On Tue, 29 Apr 2025 15:54:07 +0100,
> Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> >
> > On Mon, Apr 28, 2025 at 04:49:17PM +0100, Marc Zyngier wrote:
> > > On Thu, 24 Apr 2025 11:25:30 +0100,
> > > Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> > > >
> > > > Implement GICv5 CPU interface and IRS support, to manage interrupt
> > > > state, priority and routing for all GICv5 interrupt types.
> > > >
> > > > The GICv5 CPU interface implements support for PE-Private Peripheral
> > > > Interrupts (PPI), that are handled (enabled/prioritized/delivered)
> > > > entirely within the CPU interface hardware.
> > > >
> > > > To enable PPI interrupts, implement the baseline GICv5 host kernel
> > > > driver infrastructure required to handle interrupts on a GICv5 system.
> > > >
> > > > Add the exception handling code path and definitions for GICv5
> > > > instructions.
> > > >
> > > > Add GICv5 PPI handling code as a specific IRQ domain to:
> > > >
> > > > - Set-up PPI priority
> > > > - Manage PPI configuration and state
> > > > - Manage IRQ flow handler
> > > > - IRQs allocation/free
> > > > - Hook-up a PPI specific IRQchip to provide the relevant methods
> > > >
> > > > PPI IRQ priority is chosen as the minimum allowed priority by the
> > > > system design (after probing the number of priority bits implemented
> > > > by the CPU interface).
> > > >
> > > > The GICv5 Interrupt Routing Service (IRS) component implements
> > > > interrupt management and routing in the GICv5 architecture.
> > > >
> > > > A GICv5 system comprises one or more IRSes, that together
> > > > handle the interrupt routing and state for the system.
> > > >
> > > > An IRS supports Shared Peripheral Interrupts (SPIs), that are
> > > > interrupt sources directly connected to the IRS; they do not
> > > > rely on memory for storage. The number of supported SPIs is
> > > > fixed for a given implementation and can be probed through IRS
> > > > IDR registers.
> > > >
> > > > SPI interrupt state and routing are managed through GICv5
> > > > instructions.
> > > >
> > > > Each core (PE in GICv5 terms) in a GICv5 system is identified with
> > > > an Interrupt AFFinity ID (IAFFID).
> > > >
> > > > An IRS manages a set of cores that are connected to it.
> > > >
> > > > Firmware provides a topology description that the driver uses
> > > > to detect to which IRS a CPU (ie an IAFFID) is associated with.
> > > >
> > > > Use probeable information and firmware description to initialize
> > > > the IRSes and implement GICv5 IRS SPIs support through an
> > > > SPI-specific IRQ domain.
> > > >
> > > > The GICv5 IRS driver:
> > > >
> > > > - Probes IRSes in the system to detect SPI ranges
> > > > - Associates an IRS with a set of cores connected to it
> > > > - Adds an IRQchip structure for SPI handling
> > > >
> > > > SPIs priority is set to a value corresponding to the lowest
> > > > permissible priority in the system (taking into account the
> > > > implemented priority bits of the IRS and CPU interface).
> > > >
> > > > Since all IRQs are set to the same priority value, the value
> > > > itself does not matter as long as it is a valid one.
> > > >
> > > > An IRS supports Logical Peripheral Interrupts (LPIs) and implement
> > > > Linux IPIs on top of it.
> > > >
> > > > LPIs are used for interrupt signals that are translated by a
> > > > GICv5 ITS (Interrupt Translation Service) but also for software
> > > > generated IRQs - namely interrupts that are not driven by a HW
> > > > signal, ie IPIs.
> > > >
> > > > LPIs rely on memory storage for interrupt routing and state.
> > > >
> > > > Memory storage is handled by the IRS - that is configured
> > > > at probe time by the driver with the required memory.
> > > >
> > > > LPIs state and routing information is kept in the Interrupt
> > > > State Table (IST).
> > > >
> > > > IRSes provide support for 1- or 2-level IST tables configured
> > > > to support a maximum number of interrupts that depend on the
> > > > OS configuration and the HW capabilities.
> > > >
> > > > On systems that provide 2-level IST support, always allow
> > > > the maximum number of LPIs; On systems with only 1-level
> > > > support, limit the number of LPIs to 2^12 to prevent
> > > > wasting memory (presumably a system that supports a 1-level
> > > > only IST is not expecting a large number of interrupts).
> > > >
> > > > On a 2-level IST system, L2 entries are allocated on
> > > > demand.
> > > >
> > > > The IST table memory is allocated using the kmalloc() interface;
> > > > the allocation required may be smaller than a page and must be
> > > > made up of contiguous physical pages if larger than a page.
> > > >
> > > > On systems where the IRS is not cache-coherent with the CPUs,
> > > > cache mainteinance operations are executed to clean and
> > > > invalidate the allocated memory to the point of coherency
> > > > making it visible to the IRS components.
> > > >
> > > > Add an LPI IRQ domain to:
> > > >
> > > > - Manage LPI state and routing
> > > > - Add LPI IRQchip structure and callbacks
> > > > - LPI domain allocation/de-allocation
> > > >
> > > > On GICv5 systems, IPIs are implemented using LPIs.
> > > >
> > > > Implement an IPI-specific IRQ domain created as a child/subdomain
> > > > of the LPI domain to allocate the required number of LPIs needed
> > > > to implement the IPIs.
> > > >
> > > > Move the arm64 IPI enum declaration to a header file so that the
> > > > GICv5 driver code can detect how many IPIs are required by arch code.
> > > >
> > > > IPIs are backed by LPIs, add LPIs allocation/de-allocation
> > > > functions.
> > > >
> > > > The LPI INTID namespace is managed using an IDA to alloc/free LPI
> > > > INTIDs.
> > > >
> > > > Associate an IPI irqchip with IPI IRQ descriptors to provide
> > > > core code with the irqchip.ipi_send_single() method required
> > > > to raise an IPI.
> > > >
> > > > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > > > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > > > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > > > Cc: Will Deacon <will@kernel.org>
> > > > Cc: Thomas Gleixner <tglx@linutronix.de>
> > > > Cc: Catalin Marinas <catalin.marinas@arm.com>
> > > > Cc: Marc Zyngier <maz@kernel.org>
> > > > ---
> > > > MAINTAINERS | 2 +
> > > > arch/arm64/include/asm/arch_gicv5.h | 91 +++
> > > > arch/arm64/include/asm/smp.h | 17 +
> > > > arch/arm64/kernel/smp.c | 17 -
> > > > drivers/irqchip/Kconfig | 5 +
> > > > drivers/irqchip/Makefile | 1 +
> > > > drivers/irqchip/irq-gic-v5-irs.c | 841 ++++++++++++++++++++++++++++
> > > > drivers/irqchip/irq-gic-v5.c | 1058 +++++++++++++++++++++++++++++++++++
> > > > drivers/irqchip/irq-gic-v5.h | 184 ++++++
> > >
> > > Nit: the split between include/asm/arch_gicv5.h and
> > > drivers/irqchip/irq-gic-v5.h is pretty pointless (this is obviously
> > > inherited from the GICv3 on 32bit setup). Given that GICv5 is strictly
> > > arm64, this split is no longer necessary.
> >
> > That's true but I thought I would leave sys instructions and barriers
> > in arm64 arch code headers rather than moving them out. I am up for
> > whatever you folks think it is best.
>
> I can see two options:
>
> - either you unify the two and place the result in
> include/linux/irqchip/arm-gic-v5.h
>
> - or you move the system stuff and the barriers to
> arch/arm64/include/asm/sysreg.h together with the rest of the pile,
> *and* move the other include file as above
Probably go for the second option.
> > > > +#define GSB_ACK __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 1) | 31)
> > > > +#define GSB_SYS __emit_inst(0xd5000000 | sys_insn(1, 0, 12, 0, 0) | 31)
> > >
> > > Can you please express this in terms of __SYS_BARRIER_INSN(), with a
> > > slight rework of the SB definition? It would limit the propagation of
> > > the 0xd5 constant and make it clear what 31 stands for.
> >
> > I tried but the __SYS_BARRIER_INSN() is a different class (GICv5
> > barriers are sys instructions the SB barrier has a slightly different
> > encoding), so maybe something like this ?
> >
> > #define GSB_ACK __msr_s(sys_insn(1, 0, 12, 0, 1), "xzr")
>
> Why a different encoding? It looks completely similar to me. What I
> was expecting to see is something along the lines of (completely
> untested):
>
> diff --git a/arch/arm64/include/asm/barrier.h b/arch/arm64/include/asm/barrier.h
> index 1ca947d5c9396..7a00781d1d788 100644
> --- a/arch/arm64/include/asm/barrier.h
> +++ b/arch/arm64/include/asm/barrier.h
> @@ -44,6 +44,8 @@
> SB_BARRIER_INSN"nop\n", \
> ARM64_HAS_SB))
>
> +#define gsb_ack() asm volatile("GSC_ACK_BARRIER_INSN\n" : : : "memory")
> +
> #ifdef CONFIG_ARM64_PSEUDO_NMI
> #define pmr_sync() \
> do { \
> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
> index 690b6ebd118f4..a84993c3d117b 100644
> --- a/arch/arm64/include/asm/sysreg.h
> +++ b/arch/arm64/include/asm/sysreg.h
> @@ -112,10 +112,13 @@
> /* Register-based PAN access, for save/restore purposes */
> #define SYS_PSTATE_PAN sys_reg(3, 0, 4, 2, 3)
>
> -#define __SYS_BARRIER_INSN(CRm, op2, Rt) \
> - __emit_inst(0xd5000000 | sys_insn(0, 3, 3, (CRm), (op2)) | ((Rt) & 0x1f))
> +#define __SYS_BARRIER_INSN(op0, op1, CRn, CRm, op2, Rt) \
> + __emit_inst(0xd5000000 | \
> + sys_insn((op0), (op1), (CRn), (CRm), (op2)) | \
> + ((Rt) & 0x1f))
>
> -#define SB_BARRIER_INSN __SYS_BARRIER_INSN(0, 7, 31)
> +#define SB_BARRIER_INSN __SYS_BARRIER_INSN(0, 3, 3, 0, 7, 31)
> +#define GSB_ACK_BARRIER_INSN __SYS_BARRIER_INSN(1, 0, 12, 0, 1, 31)
Fine by me if that's fine by you, Catalin and Will.
>
> /* Data cache zero operations */
> #define SYS_DC_ISW sys_insn(1, 0, 7, 6, 2)
>
> and the same thing for GSB SYS.
>
> > > > +#include "irq-gic-v5.h"
> > >
> > > Why the ""? The normal include path (linux/irqchip) should work.
> >
> > irq-gic-v5.h lives in drivers/irqchip, should I move it into
> > include/linux/irqchip (or I am missing something, likely) ?
> >
> > I did not do it because it is only consumed by files in that directory.
>
> I full intend for KVM to consume all of this pretty much directly, and
> the KVM code will definitely not live in drivers/irqchip.
Understood but KVM code is not part of this series, we could move it
out of drivers/irqchip when it is needed.
Anyway, this does not matter, I will move it to include/linux/irqchip.
> > > > + ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
> > > > +
> > > > + if (val)
> > > > + *val = tmp;
> > >
> > > Updating the result value on error is rather odd. Do you rely on this
> > > anywhere? If not, consider avoiding the write-back on timeout.
> >
> > I do rely on it to avoid reading the register once again on return if
> > the wait succeeded (but we don't know unless for some registers we
> > check another bit in the returned value).
>
> That's a bit annoying. But hey...
>
> > > > + /*
> > > > + * The polling wait (in gicv5_wait_for_op()) on a GIC register
> > > > + * provides the memory barriers (through MMIO accessors)
> > > > + * required to synchronize CPU and GIC access to IST memory.
> > > > + */
> > >
> > > This comment would be better placed with the helper itself, and avoid
> > > the repeats that I can see in the rest of the code.
> >
> > On this, I thought about that. The problem is, the comment would be
> > buried in a helper function whereas it is in this function that we
> > are actually handing over memory to the GIC so in a way I thought
> > it was more appropriate to add it where the explanation is needed
> > and possibly clearer.
>
> It's not clearer. If you want it to be clearer, name the helper in a
> way that makes it actions explicit (gicv5_irs_ist_synchronise() ?),
> and let the comment explain how the synchronisation is enforced next
> to the helper.
>
> Using explicit names is far better than duplicating comments, IMHO.
OK.
> > > > + list_for_each_entry(irs_data, &irs_nodes, entry) {
> > > > + if (!irs_data->spi_range)
> > > > + continue;
> > > > +
> > > > + min = irs_data->spi_min;
> > > > + max = irs_data->spi_min + irs_data->spi_range - 1;
> > > > + if (spi_id >= min && spi_id <= max)
> > > > + return irs_data;
> > > > + }
> > >
> > > Funnily enough, this is exactly the sort of iterative searches the
> > > maple tree could have avoided, by storing INTID (and range) specific
> > > data. Never mind.
> >
> > I did it with a range store with an Xarray but then removed it because
> > I thought it was overkill, one of those things I am not sure what's
> > best.
>
> Probably not an issue for now. Time will tell as we get larger
> machines with multiple IRSes.
+1
> > > Just a passing comment: consider splitting this patch in two (IRS on
> > > one side, CPUif on the other). I'm only half-way through, and it's
> > > quite tiring... :-/
> >
> > Well, on this I am having a hard time as I replied to Thomas as well.
> >
> > We do need CPUIF + IRS (LPI so IPIs) to make this a functional
> > patch.
>
> We don't need things to be functional. We need things to not break the
> build. So as long as things are split in a logical way and, this
> should be fine.
That's what I did in v1.
> > If I split per component, this might simplify review (and that's what
> > I did in v1 but Thomas complained that could not find SMP
> > initialization).
>
> Well, you could have one patch clearly labelled as "SMP init", which
> would both satisfy tglx's requirements *and* my tired brain.
https://lore.kernel.org/lkml/20250408-gicv5-host-v1-21-1f26db465f8d@kernel.org
> > What I could do to reduce the number of lines is moving SPI support
> > in a separate patch but I don't think there is a way to split CPUIF
> > and IRS and have a patch series that is bisectable and works at every
> > stage (by the way the last patch should be merged with this one because
> > technically with this patch we have a working GICv5).
>
> The trivial way to make it bisectable is to avoid compiling it until
> it is sufficiently feature-complete. Any sizeable amount of new code
> relies on this to a degree or another.
>
> Also, think of the commit messages: what you have here is a massive
> wall of text that would be more appropriate for a cover letter, and
> could be much smaller on a per-patch basis without losing any
> meaningful content.
Sounds like v1, back to the splitting board.
Thanks,
Lorenzo
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support
2025-04-24 10:25 ` [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support Lorenzo Pieralisi
@ 2025-04-30 7:28 ` Jiri Slaby
2025-04-30 12:55 ` Lorenzo Pieralisi
2025-04-30 9:12 ` Marc Zyngier
1 sibling, 1 reply; 43+ messages in thread
From: Jiri Slaby @ 2025-04-30 7:28 UTC (permalink / raw)
To: Lorenzo Pieralisi, Marc Zyngier, Thomas Gleixner, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Catalin Marinas, Will Deacon
Cc: Arnd Bergmann, Sascha Bischoff, Timothy Hayes, Liam R. Howlett,
Mark Rutland, linux-arm-kernel, linux-kernel, devicetree
On 24. 04. 25, 12:25, Lorenzo Pieralisi wrote:
> --- /dev/null
> +++ b/drivers/irqchip/irq-gic-v5-its.c
> @@ -0,0 +1,1293 @@
...
> +static u32 its_readl_relaxed(struct gicv5_its_chip_data *its_node,
> + const u64 reg_offset)
I wonder -- can the offset be u64 at all?
> +{
> + return readl_relaxed(its_node->its_base + reg_offset);
> +}
> +
> +static void its_writel_relaxed(struct gicv5_its_chip_data *its_node,
> + const u32 val, const u64 reg_offset)
> +{
> + writel_relaxed(val, its_node->its_base + reg_offset);
> +}
> +
> +static void its_writeq_relaxed(struct gicv5_its_chip_data *its_node,
> + const u64 val, const u64 reg_offset)
> +{
> + writeq_relaxed(val, its_node->its_base + reg_offset);
> +}
> +
> +static void its_write_table_entry(struct gicv5_its_chip_data *its,
> + __le64 *entry, u64 val)
> +{
> + WRITE_ONCE(*entry, val);
This triggers a warning with the le/be checker enabled, right? You
likely need cpu_to_le64() or __force.
> + if (its->flags & ITS_FLAGS_NON_COHERENT)
> + dcache_clean_inval_poc((unsigned long)entry,
> + (unsigned long)entry + sizeof(*entry));
> + else
> + dsb(ishst);
> +}
> +
> +#define gicv5_its_wait_for_op(base, reg, mask) \
What's the purpose of this not being an inline?
> + ({ \
> + int ret; \
> + \
> + ret = gicv5_wait_for_op(base, reg, mask, NULL); \
> + if (unlikely(ret == -ETIMEDOUT)) \
> + pr_err_ratelimited(#reg" timeout...\n"); \
Ah, this. Is it worth it? At least you should not clobber variables like
"ret". Also grepping sources for "GICV5_ITS_STATUSR timeout..." would be
clueless anyway. Yeah, at least there would be a driver prefix.
> + ret; \
> + })
...
> +static int gicv5_its_device_get_itte_ref(struct gicv5_its_dev *its_dev,
> + __le64 **itte, u16 event_id)
> +{
> + if (!its_dev->itt_cfg.l2itt) {
> + __le64 *itt = its_dev->itt_cfg.linear.itt;
> + *itte = &itt[event_id];
Can you return 0 here and dedent the whole } else { block?
> + } else {
> + __le64 *l2_itt, *l1_itt = its_dev->itt_cfg.l2.l1itt;
> + unsigned int l1_idx, l2_idx, l2_size, l2_bits;
> + int ret;
> +
> + ret = gicv5_its_l2sz_to_l2_bits(its_dev->itt_cfg.l2.l2sz);
> + if (ret < 0)
> + return ret;
> + l2_bits = ret;
> +
> + l1_idx = event_id >> l2_bits;
> +
> + if (!FIELD_GET(GICV5_ITTL1E_VALID,
> + le64_to_cpu(l1_itt[l1_idx]))) {
> + pr_debug("L1 ITT entry is not valid.\n");
> + return -EINVAL;
> + }
> +
> + l2_idx = event_id & GENMASK(l2_bits - 1, 0);
> +
> + l2_size = BIT(FIELD_GET(GICV5_ITTL1E_SPAN,
> + le64_to_cpu(l1_itt[l1_idx])));
> +
> + // Sanity check our indexing
> + if (l2_idx >= l2_size) {
> + pr_debug("L2 ITT index (%u) exceeds L2 table size (%u)!\n",
> + l2_idx, l2_size);
> + return -EINVAL;
> + }
> + l2_itt = its_dev->itt_cfg.l2.l2ptrs[l1_idx];
> + *itte = &l2_itt[l2_idx];
> + }
> +
> + return 0;
> +}
...
> +static struct gicv5_its_dev *gicv5_its_alloc_device(
> + struct gicv5_its_chip_data *its, int nvec,
> + u32 dev_id)
> +{
> + struct gicv5_its_dev *its_dev;
> + int ret;
> +
> + its_dev = gicv5_its_find_device(its, dev_id);
> + if (!IS_ERR(its_dev)) {
> + pr_debug("A device with this DeviceID (0x%x) has already been registered.\n",
> + dev_id);
> +
> + if (nvec > its_dev->num_events) {
> + pr_debug("Requesting more ITT entries than allocated\n");
Why only _debug()?
> + return ERR_PTR(-ENXIO);
> + }
> +
> + its_dev->shared = true;
> +
> + return its_dev;
> + }
> +
> + its_dev = kzalloc(sizeof(*its_dev), GFP_KERNEL);
> + if (!its_dev)
> + return ERR_PTR(-ENOMEM);
> +
> + its_dev->device_id = dev_id;
> + its_dev->num_events = nvec;
> + its_dev->num_mapped_events = 0;
> +
> + ret = gicv5_its_device_register(its, its_dev);
> + if (ret) {
> + pr_debug("Failed to register the device\n");
And here.
> + kfree(its_dev);
Can you use __free() and return_ptr() instead?
> + return ERR_PTR(ret);
> + }
> +
> + gicv5_its_device_cache_inv(its, its_dev);
> +
> + /*
> + * This is the first time we have seen this device. Hence, it is not
> + * shared.
> + */
> + its_dev->shared = false;
> +
> + its_dev->its_node = its;
> +
> + its_dev->event_map =
> + (unsigned long *)bitmap_zalloc(its_dev->num_events, GFP_KERNEL);
> + if (!its_dev->event_map) {
> + gicv5_its_device_unregister(its, its_dev);
> + kfree(its_dev);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + xa_store(&its->its_devices, dev_id, its_dev, GFP_KERNEL);
This can fail.
> +
> + return its_dev;
> +}
...
> +static int gicv5_its_alloc_event(struct gicv5_its_dev *its_dev, u16 event_id,
> + u32 lpi)
> +{
> + struct gicv5_its_chip_data *its = its_dev->its_node;
> + u64 itt_entry;
> + __le64 *itte;
> + int ret;
> +
> + if (event_id >= its_dev->num_events) {
> + pr_debug("EventID 0x%x outside of ITT range (0x%x)\n", event_id,
> + its_dev->num_events);
Again, is this so often to be _debug()?
> + return -EINVAL;
> + }
> +
> + if (WARN(its_dev->num_mapped_events == its_dev->num_events,
> + "Reached maximum number of events\n"))
Weird indent level.
> + return -EINVAL;
> +
> + ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
> + if (ret)
> + return ret;
> +
> + if (FIELD_GET(GICV5_ITTL2E_VALID, *itte))
> + return -EEXIST;
> +
> + itt_entry = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) |
> + FIELD_PREP(GICV5_ITTL2E_VALID, 0x1);
> +
> + its_write_table_entry(its, itte, cpu_to_le64(itt_entry));
> +
> + gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
> +
> + its_dev->num_mapped_events += 1;
This is not python, we have ++ :).
> +
> + return 0;
> +}
> +
> +static void gicv5_its_free_event(struct gicv5_its_dev *its_dev, u16 event_id)
> +{
> + struct gicv5_its_chip_data *its = its_dev->its_node;
> + u64 itte_val;
> + __le64 *itte;
> + int ret;
> +
> + if (WARN(!its_dev->num_mapped_events, "No mapped events\n"))
> + return;
> +
> + ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
> + if (ret) {
> + pr_debug("Failed to get the ITTE!\n");
> + return;
> + }
> +
> + itte_val = le64_to_cpu(*itte);
> + itte_val &= ~GICV5_ITTL2E_VALID;
> +
> + its_write_table_entry(its, itte, cpu_to_le64(itte_val));
> +
> + gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
> +
> + its_dev->num_mapped_events -= 1;
And --.
> +}
> +
> +static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev,
> + unsigned int nr_irqs, u32 *eventid)
> +{
> + int ret;
> +
> + ret = bitmap_find_free_region(its_dev->event_map,
> + its_dev->num_events,
> + get_count_order(nr_irqs));
> +
> + if (ret < 0)
> + return ret;
> +
> + *eventid = ret;
> +
> + return 0;
> +}
...
> +static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + u32 device_id, event_id_base, lpi;
> + struct msi_domain_info *msi_info;
> + struct gicv5_its_chip_data *its;
> + struct gicv5_its_dev *its_dev;
> + msi_alloc_info_t *info = arg;
> + irq_hw_number_t hwirq;
> + struct irq_data *irqd;
> + int ret, i;
Why is i not unsigned too?
> +
> + device_id = info->scratchpad[0].ul;
> +
> + msi_info = msi_get_domain_info(domain);
> + its = msi_info->data;
> +
> + mutex_lock(&its->dev_alloc_lock);
> +
> + its_dev = gicv5_its_find_device(its, device_id);
> + if (IS_ERR(its_dev)) {
> + mutex_unlock(&its->dev_alloc_lock);
scope_guard() would make much sense here.
> + return PTR_ERR(its_dev);
> + }
> +
> + ret = gicv5_its_alloc_eventid(its_dev, nr_irqs, &event_id_base);
> + if (ret) {
> + mutex_unlock(&its->dev_alloc_lock);
> + return ret;
> + }
> +
> + mutex_unlock(&its->dev_alloc_lock);
> +
> + ret = iommu_dma_prepare_msi(info->desc, its->its_trans_phys_base);
> + if (ret)
> + goto out_eventid;
> +
> + for (i = 0; i < nr_irqs; i++) {
> + lpi = gicv5_alloc_lpi();
> + if (ret < 0) {
> + pr_debug("Failed to find free LPI!\n");
> + goto out_eventid;
> + }
> +
> + ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
> + if (ret)
> + goto out_free_lpi;
> +
> + /*
> + * Store eventid and deviceid into the hwirq for later use.
> + *
> + * hwirq = event_id << 32 | device_id
> + */
> + hwirq = FIELD_PREP(GICV5_ITS_HWIRQ_DEVICE_ID, device_id) |
> + FIELD_PREP(GICV5_ITS_HWIRQ_EVENT_ID, (u64)event_id_base + i);
> + irq_domain_set_info(domain, virq + i, hwirq,
> + &gicv5_its_irq_chip, its_dev,
> + handle_fasteoi_irq, NULL, NULL);
> +
> + irqd = irq_get_irq_data(virq + i);
> + irqd_set_single_target(irqd);
> + irqd_set_affinity_on_activate(irqd);
> + irqd_set_resend_when_in_progress(irqd);
> + }
> +
> + return 0;
> +out_free_lpi:
> + gicv5_free_lpi(lpi);
> +out_eventid:
> + gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs);
> +
> + return ret;
> +}
> +
> +static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs)
> +{
> + struct msi_domain_info *msi_info;
> + struct gicv5_its_chip_data *its;
> + struct gicv5_its_dev *its_dev;
> + struct irq_data *d;
> + u16 event_id_base;
> + bool free_device;
> + u32 device_id;
> + int i;
> +
> + msi_info = msi_get_domain_info(domain);
> + its = msi_info->data;
> +
> + d = irq_domain_get_irq_data(domain, virq);
> + device_id = FIELD_GET(GICV5_ITS_HWIRQ_DEVICE_ID, d->hwirq);
> + event_id_base = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> +
> + guard(mutex)(&its->dev_alloc_lock);
> +
> + its_dev = gicv5_its_find_device(its, device_id);
> + if (IS_ERR(its_dev)) {
> + pr_debug("Couldn't find the ITS device!\n");
This is serious, not debug, IMO. Either we leak memory or even allow out
of bounds accesses somewhere.
> + return;
> + }
> +
> + bitmap_release_region(its_dev->event_map, event_id_base,
> + get_count_order(nr_irqs));
> +
> + free_device = !its_dev->shared && bitmap_empty(its_dev->event_map,
> + its_dev->num_events);
> +
> + /* Hierarchically free irq data */
> + for (i = 0; i < nr_irqs; i++) {
> + d = irq_domain_get_irq_data(domain, virq + i);
> +
> + gicv5_free_lpi(d->parent_data->hwirq);
> + irq_domain_reset_irq_data(d);
> + }
> + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> +
> + gicv5_its_syncr(its, its_dev);
> + gicv5_irs_syncr();
> +
> + if (free_device) {
> + gicv5_its_device_unregister(its, its_dev);
> + bitmap_free(its_dev->event_map);
> + xa_erase(&its->its_devices, device_id);
> + kfree(its_dev);
> + }
> +}
...
> +static int __init gicv5_its_init_bases(phys_addr_t its_trans_base,
> + void __iomem *its_base,
> + struct fwnode_handle *handle,
> + struct irq_domain *parent_domain)
> +{
> + struct device_node *np = to_of_node(handle);
> + struct gicv5_its_chip_data *its_node;
> + struct msi_domain_info *info;
> + struct irq_domain *d;
> + u32 cr0, cr1;
> + bool enabled;
> + int ret;
> +
> + info = kzalloc(sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + its_node = kzalloc(sizeof(*its_node), GFP_KERNEL);
> + if (!its_node) {
> + kfree(info);
> + return -ENOMEM;
> + }
> +
> + info->ops = &its_msi_domain_ops;
> + info->data = its_node;
> +
> + mutex_init(&its_node->dev_alloc_lock);
> + xa_init(&its_node->its_devices);
> + its_node->fwnode = handle;
> + its_node->its_base = its_base;
> + its_node->its_trans_phys_base = its_trans_base;
> +
> + d = irq_domain_create_hierarchy(parent_domain, IRQ_DOMAIN_FLAG_ISOLATED_MSI,
> + 0, handle, &gicv5_its_irq_domain_ops, info);
> + its_node->domain = d;
> + irq_domain_update_bus_token(its_node->domain, DOMAIN_BUS_NEXUS);
> +
> + its_node->domain->msi_parent_ops = &gic_its_msi_parent_ops;
> + its_node->domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
> +
> + cr0 = its_readl_relaxed(its_node, GICV5_ITS_CR0);
> + enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, cr0);
> + if (WARN(enabled, "ITS %s enabled, disabling it before proceeding\n",
> + np->full_name)) {
> + cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x0);
> + its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
> + ret = gicv5_its_wait_for_cr0(its_node);
> + if (ret) {
> + irq_domain_remove(its_node->domain);
> + kfree(info);
> + kfree(its_node);
> + return ret;
> + }
> + }
> +
> + if (of_property_read_bool(np, "dma-noncoherent")) {
> + /*
> + * A non-coherent ITS implies that some cache levels cannot be
> + * used coherently by the cores and GIC. Our only option is to mark
> + * memory attributes for the GIC as non-cacheable; by default,
> + * non-cacheable memory attributes imply outer-shareable
> + * shareability, the value written into ITS_CR1_SH is ignored.
> + */
> + cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_NON_CACHE) |
> + FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_NON_CACHE);
> + its_node->flags |= ITS_FLAGS_NON_COHERENT;
> + } else {
> + cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_WB_CACHE) |
> + FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_WB_CACHE) |
> + FIELD_PREP(GICV5_ITS_CR1_SH, GICV5_INNER_SHARE);
> + }
> +
> + its_writel_relaxed(its_node, cr1, GICV5_ITS_CR1);
> +
> + ret = gicv5_its_init_devtab(its_node);
> + if (ret) {
> + irq_domain_remove(its_node->domain);
> + kfree(info);
> + kfree(its_node);
> + return ret;
> + }
> +
> + cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x1);
> + its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
> +
> + ret = gicv5_its_wait_for_cr0(its_node);
> + if (ret) {
> + irq_domain_remove(its_node->domain);
> + kfree(info);
> + kfree(its_node);
Either convert to cleanup.h or do this in a common error label(s).
> + return ret;
> + }
> +
> + list_add(&its_node->entry, &its_nodes);
> +
> + gicv5_its_print_info(its_node);
> +
> + return 0;
> +}
...
> diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> index c4d4e85382f672fa4ae334db1a4e4c7c4f46b9fe..e483d0774936035b5cf2407da9a65d776bad3138 100644
> --- a/drivers/irqchip/irq-gic-v5.c
> +++ b/drivers/irqchip/irq-gic-v5.c
...
> @@ -168,17 +271,90 @@ struct gicv5_irs_chip_data {
>
> void __init gicv5_init_lpi_domain(void);
> void __init gicv5_free_lpi_domain(void);
> +static inline int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask,
> + u32 *val)
> +{
> + void __iomem *reg = addr + offset;
> + u32 tmp;
> + int ret;
> +
> + ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
Does this have to be atomic? The call chain is complex, I haven't
managed to check...
> +
> + if (val)
> + *val = tmp;
Do you really want to write val in case of timeout? Sounds unexpected.
> + return ret;
> +}
thanks,
--
js
suse labs
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support
2025-04-24 10:25 ` [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support Lorenzo Pieralisi
2025-04-30 7:28 ` Jiri Slaby
@ 2025-04-30 9:12 ` Marc Zyngier
2025-04-30 13:21 ` Lorenzo Pieralisi
1 sibling, 1 reply; 43+ messages in thread
From: Marc Zyngier @ 2025-04-30 9:12 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Thu, 24 Apr 2025 11:25:31 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> The GICv5 architecture implements Interrupt Translation Service
> (ITS) components in order to translate events coming from peripherals
> into interrupt events delivered to the connected IRSes.
>
> Events (ie MSI memory writes to ITS translate frame), are translated
> by the ITS using tables kept in memory.
>
> ITS translation tables for peripherals is kept in memory storage
> (device table [DT] and Interrupt Translation Table [ITT]) that
> is allocated by the driver on boot.
>
> Both tables can be 1- or 2-level; the structure is chosen by the
> driver after probing the ITS HW parameters and checking the
> allowed table splits and supported {device/event}_IDbits.
>
> DT table entries are allocated on demand (ie when a device is
> probed); the DT table is sized using the number of supported
> deviceID bits in that that's a system design decision (ie the
> number of deviceID bits implemented should reflect the number
> of devices expected in a system) therefore it makes sense to
> allocate a DT table that can cater for the maximum number of
> devices.
>
> DT and ITT tables are allocated using the kmalloc interface;
> the allocation size may be smaller than a page or larger,
> and must provide contiguous memory pages.
>
> LPIs INTIDs backing the device events are allocated one-by-one
> and only upon Linux IRQ allocation; this to avoid preallocating
> a large number of LPIs to cover the HW device MSI vector
> size whereas few MSI entries are actually enabled by a device.
>
> ITS cacheability/shareability attributes are programmed
> according to the provided firmware ITS description.
>
> The GICv5 ITS reuses the GICv3 MSI parent infrastructure,
> there is no need to duplicate it, make it common.
>
> Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> Cc: Thomas Gleixner <tglx@linutronix.de>
> Cc: Marc Zyngier <maz@kernel.org>
> ---
> MAINTAINERS | 1 +
> drivers/irqchip/Kconfig | 11 +
> drivers/irqchip/Makefile | 4 +-
> drivers/irqchip/irq-gic-common.h | 2 -
> ...3-its-msi-parent.c => irq-gic-its-msi-parent.c} | 3 +-
> drivers/irqchip/irq-gic-its-msi-parent.h | 13 +
> drivers/irqchip/irq-gic-v3-its.c | 3 +-
> drivers/irqchip/irq-gic-v5-irs.c | 40 +-
> drivers/irqchip/irq-gic-v5-its.c | 1293 ++++++++++++++++++++
> drivers/irqchip/irq-gic-v5.c | 6 +-
> drivers/irqchip/irq-gic-v5.h | 176 +++
> 11 files changed, 1529 insertions(+), 23 deletions(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index cdeceb6782355a4a18609135bf7f03249d8b0bb5..d231077c024deba42153663ac66b6c05f7673f03 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1908,6 +1908,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> S: Maintained
> F: Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
> F: arch/arm64/include/asm/arch_gicv5.h
> +F: drivers/irqchip/irq-gic-its-msi-parent.[ch]
> F: drivers/irqchip/irq-gic-v5*.[ch]
>
> ARM HDLCD DRM DRIVER
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 160a4761d5d85f6dbf36f3142fd619c114733e36..6c348d421b05af0e4f4909877e02ac8ef19178ff 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -41,10 +41,14 @@ config ARM_GIC_V3
> select HAVE_ARM_SMCCC_DISCOVERY
> select IRQ_MSI_IOMMU
>
> +config ARM_GIC_ITS_PARENT
> + bool
> +
> config ARM_GIC_V3_ITS
> bool
> select GENERIC_MSI_IRQ
> select IRQ_MSI_LIB
> + select ARM_GIC_ITS_PARENT
> default ARM_GIC_V3
> select IRQ_MSI_IOMMU
>
> @@ -59,6 +63,13 @@ config ARM_GIC_V5
> select IRQ_DOMAIN_HIERARCHY
> select GENERIC_IRQ_EFFECTIVE_AFF_MASK if SMP
>
> +config ARM_GIC_V5_ITS
> + bool
> + select GENERIC_MSI_IRQ
> + select IRQ_MSI_LIB
> + select ARM_GIC_ITS_PARENT
> + default ARM_GIC_V5
> +
I don't think you should be mimicking GICv3 here. It was never
possible to not compile the ITS code anyway, and you are better off
just having one config symbol that drags the whole thing.
> config ARM_NVIC
> bool
> select IRQ_DOMAIN_HIERARCHY
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 3d9c47fa3fdf40b7452c059d84fe8ac24c91bc0f..4280395e3bdff7858102f0b4eaaea1121cace52f 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -32,10 +32,12 @@ obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o
> obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o
> obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o
> obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o
> -obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o
> +obj-$(CONFIG_ARM_GIC_ITS_PARENT) += irq-gic-its-msi-parent.o
> +obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
> obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> +obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
> obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> obj-$(CONFIG_ARM_VIC) += irq-vic.o
> diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h
> index 020ecdf16901c9720e5746aec4d0b5b39d3625ed..710cab61d9195a0bd64d57e03c60852c4cd6ff8e 100644
> --- a/drivers/irqchip/irq-gic-common.h
> +++ b/drivers/irqchip/irq-gic-common.h
> @@ -29,8 +29,6 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
> void gic_enable_of_quirks(const struct device_node *np,
> const struct gic_quirk *quirks, void *data);
>
> -extern const struct msi_parent_ops gic_v3_its_msi_parent_ops;
> -
> #define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0)
> #define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1)
> #define RDIST_FLAGS_FORCE_NON_SHAREABLE (1 << 2)
> diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c
> similarity index 98%
> rename from drivers/irqchip/irq-gic-v3-its-msi-parent.c
> rename to drivers/irqchip/irq-gic-its-msi-parent.c
> index bdb04c8081480de468fb217b68c6933a8e1e2bd7..71edcdb2defdfd5b892d86354039d2e46b832ea5 100644
> --- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c
> +++ b/drivers/irqchip/irq-gic-its-msi-parent.c
> @@ -7,7 +7,6 @@
> #include <linux/acpi_iort.h>
> #include <linux/pci.h>
>
> -#include "irq-gic-common.h"
> #include "irq-msi-lib.h"
>
> #define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
> @@ -200,7 +199,7 @@ static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
> return true;
> }
>
> -const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
> +const struct msi_parent_ops gic_its_msi_parent_ops = {
> .supported_flags = ITS_MSI_FLAGS_SUPPORTED,
> .required_flags = ITS_MSI_FLAGS_REQUIRED,
> .chip_flags = MSI_CHIP_FLAG_SET_EOI | MSI_CHIP_FLAG_SET_ACK,
> diff --git a/drivers/irqchip/irq-gic-its-msi-parent.h b/drivers/irqchip/irq-gic-its-msi-parent.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..e7bb7f3862eef379e5b85fe7bd5eb72f3586d3b7
> --- /dev/null
> +++ b/drivers/irqchip/irq-gic-its-msi-parent.h
> @@ -0,0 +1,13 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2024 ARM Limited, All Rights Reserved.
> + */
> +
> +#ifndef _IRQ_GIC_ITS_MSI_PARENT_H
> +#define _IRQ_GIC_ITS_MSI_PARENT_H
> +
> +#include <linux/msi.h>
> +
> +extern const struct msi_parent_ops gic_its_msi_parent_ops;
> +
> +#endif /* _IRQ_GIC_ITS_MSI_PARENT_H */
> diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
> index 0115ad6c82593de511c285d99437996919bfa308..6c51bf4e34a38103d612c74476d640cd4126e8b6 100644
> --- a/drivers/irqchip/irq-gic-v3-its.c
> +++ b/drivers/irqchip/irq-gic-v3-its.c
> @@ -41,6 +41,7 @@
> #include <asm/exception.h>
>
> #include "irq-gic-common.h"
> +#include "irq-gic-its-msi-parent.h"
> #include "irq-msi-lib.h"
>
> #define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0)
> @@ -5139,7 +5140,7 @@ static int its_init_domain(struct its_node *its)
>
> irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);
>
> - inner_domain->msi_parent_ops = &gic_v3_its_msi_parent_ops;
> + inner_domain->msi_parent_ops = &gic_its_msi_parent_ops;
> inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
>
> return 0;
> diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
> index 7bd60e6d56b77c0c19a1bd9bee9685d9b6ffc959..ff9de8fe175f511b2e81f712fa2e69b96f3e66fb 100644
> --- a/drivers/irqchip/irq-gic-v5-irs.c
> +++ b/drivers/irqchip/irq-gic-v5-irs.c
> @@ -5,7 +5,6 @@
>
> #define pr_fmt(fmt) "GICv5 IRS: " fmt
>
> -#include <linux/iopoll.h>
> #include <linux/irqchip.h>
> #include <linux/log2.h>
> #include <linux/of.h>
> @@ -44,20 +43,6 @@ static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
> writeq_relaxed(val, irs_data->irs_base + reg_offset);
> }
>
> -static int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask, u32 *val)
> -{
> - void __iomem *reg = addr + offset;
> - u32 tmp;
> - int ret;
> -
> - ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
> -
> - if (val)
> - *val = tmp;
> -
> - return ret;
> -}
> -
Since you are moving this helper into the include file, put it there
from the beginning and avoid spurious code movement.
> #define gicv5_irs_wait_for_op(base, reg, mask) \
> ({ \
> int ret; \
> @@ -528,6 +513,23 @@ static int gicv5_irs_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
> GICV5_IRS_CR0_IDLE);
> }
>
> +void gicv5_irs_syncr(void)
> +{
> + struct gicv5_irs_chip_data *irs_data;
> + u32 syncr;
> +
> + irs_data = list_first_entry_or_null(&irs_nodes,
> + struct gicv5_irs_chip_data, entry);
> + if (WARN_ON(!irs_data))
> + return;
> +
> + syncr = FIELD_PREP(GICV5_IRS_SYNCR_SYNC, 1);
> + irs_writel_relaxed(irs_data, syncr, GICV5_IRS_SYNCR);
> +
> + gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_SYNC_STATUSR,
> + GICV5_IRS_SYNC_STATUSR_IDLE);
> +}
> +
Only the ITS code is using this function. Why isn't it in the ITS code
as a static helper?
> int gicv5_irs_register_cpu(int cpuid)
> {
> struct gicv5_irs_chip_data *irs_data;
> @@ -823,6 +825,14 @@ int __init gicv5_irs_enable(void)
> return 0;
> }
>
> +void __init gicv5_irs_its_probe(void)
> +{
> + struct gicv5_irs_chip_data *irs_data;
> +
> + list_for_each_entry(irs_data, &irs_nodes, entry)
> + gicv5_its_of_probe(to_of_node(irs_data->fwnode));
> +}
> +
> int __init gicv5_irs_of_probe(struct device_node *parent)
> {
> struct device_node *np;
> diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..da349b4709cc5ec8978859237838f039389ca4a1
> --- /dev/null
> +++ b/drivers/irqchip/irq-gic-v5-its.c
> @@ -0,0 +1,1293 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
> + */
> +
> +#define pr_fmt(fmt) "GICv5 ITS: " fmt
> +
> +#include <linux/bitmap.h>
> +#include <linux/iommu.h>
> +#include <linux/init.h>
> +#include <linux/irqchip.h>
> +#include <linux/kernel.h>
> +#include <linux/list.h>
> +#include <linux/msi.h>
> +
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/slab.h>
> +
> +#include "irq-gic-v5.h"
> +#include "irq-gic-its-msi-parent.h"
> +#include "irq-msi-lib.h"
> +
> +#define ITS_FLAGS_NON_COHERENT BIT(0)
> +
> +static LIST_HEAD(its_nodes);
> +
> +static u32 its_readl_relaxed(struct gicv5_its_chip_data *its_node,
> + const u64 reg_offset)
> +{
> + return readl_relaxed(its_node->its_base + reg_offset);
> +}
> +
> +static void its_writel_relaxed(struct gicv5_its_chip_data *its_node,
> + const u32 val, const u64 reg_offset)
> +{
> + writel_relaxed(val, its_node->its_base + reg_offset);
> +}
> +
> +static void its_writeq_relaxed(struct gicv5_its_chip_data *its_node,
> + const u64 val, const u64 reg_offset)
> +{
> + writeq_relaxed(val, its_node->its_base + reg_offset);
> +}
> +
> +static void its_write_table_entry(struct gicv5_its_chip_data *its,
> + __le64 *entry, u64 val)
> +{
> + WRITE_ONCE(*entry, val);
Do yourself a favour and move all the cpu_to_le64() nested calls
here. At least that will make the function signature correct, and
sparse won't be shouting at you.
> + if (its->flags & ITS_FLAGS_NON_COHERENT)
> + dcache_clean_inval_poc((unsigned long)entry,
> + (unsigned long)entry + sizeof(*entry));
> + else
> + dsb(ishst);
> +}
> +
> +#define gicv5_its_wait_for_op(base, reg, mask) \
Make this live up to its name and take an its as the first
parameter. The helper can happily to and get its_base.
> + ({ \
> + int ret; \
> + \
> + ret = gicv5_wait_for_op(base, reg, mask, NULL); \
> + if (unlikely(ret == -ETIMEDOUT)) \
> + pr_err_ratelimited(#reg" timeout...\n"); \
> + ret; \
> + })
> +
> +static int gicv5_its_wait_for_invalidation(struct gicv5_its_chip_data *its)
> +{
> + return gicv5_its_wait_for_op(its->its_base, GICV5_ITS_STATUSR,
> + GICV5_ITS_STATUSR_IDLE);
> +}
> +
> +static void gicv5_its_syncr(struct gicv5_its_chip_data *its,
> + struct gicv5_its_dev *its_dev)
> +{
> + u64 syncr;
> +
> + syncr = FIELD_PREP(GICV5_ITS_SYNCR_SYNC, 1) |
> + FIELD_PREP(GICV5_ITS_SYNCR_DEVICEID, its_dev->device_id);
> +
> + its_writeq_relaxed(its, syncr, GICV5_ITS_SYNCR);
> +
> + gicv5_its_wait_for_op(its->its_base, GICV5_ITS_SYNC_STATUSR,
> + GICV5_ITS_SYNC_STATUSR_IDLE);
> +}
> +
> +static int gicv5_its_l2sz_to_l2_bits(unsigned int sz)
> +{
> + switch (sz) {
> + case GICV5_ITS_DT_ITT_CFGR_L2SZ_4k:
> + return 9;
> + case GICV5_ITS_DT_ITT_CFGR_L2SZ_16k:
> + return 11;
> + case GICV5_ITS_DT_ITT_CFGR_L2SZ_64k:
> + return 13;
> + default:
> + return -EINVAL;
> + }
Similar to my earlier remarks: these sort of helpers should never be
able to return an error.
> +}
> +
> +static int gicv5_its_itt_cache_inv(struct gicv5_its_chip_data *its,
> + u32 device_id, u16 event_id)
> +{
> + u32 eventr, eidr;
> + u64 didr;
> +
> + didr = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
> + eidr = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id);
> + eventr = FIELD_PREP(GICV5_ITS_INV_EVENTR_I, 0x1);
> +
> + its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
> + its_writel_relaxed(its, eidr, GICV5_ITS_EIDR);
> + its_writel_relaxed(its, eventr, GICV5_ITS_INV_EVENTR);
> +
> + return gicv5_its_wait_for_invalidation(its);
You're not waiting for an invalidation. You're waiting for its
completion. Just have a single helper that synchronises everything
related to the ITS, irrespective of what you're waiting for
(gicv5_its_synchronise(), or something similar).
> +}
> +
> +static void gicv5_its_free_itt_linear(struct gicv5_its_dev *its_dev)
> +{
> + kfree(its_dev->itt_cfg.linear.itt);
> +}
> +
> +static void gicv5_its_free_itt_two_level(struct gicv5_its_dev *its_dev)
> +{
> + unsigned int i, num_ents = its_dev->itt_cfg.l2.num_l1_ents;
> +
> + for (i = 0; i < num_ents; i++)
> + kfree(its_dev->itt_cfg.l2.l2ptrs[i]);
> +
> + kfree(its_dev->itt_cfg.l2.l2ptrs);
> + kfree(its_dev->itt_cfg.l2.l1itt);
> +}
> +
> +static void gicv5_its_free_itt(struct gicv5_its_dev *its_dev)
> +{
> + if (!its_dev->itt_cfg.l2itt)
> + gicv5_its_free_itt_linear(its_dev);
> + else
> + gicv5_its_free_itt_two_level(its_dev);
> +}
> +
> +static int gicv5_its_create_itt_linear(struct gicv5_its_chip_data *its,
> + struct gicv5_its_dev *its_dev,
> + unsigned int event_id_bits)
> +{
> + unsigned int num_ents = BIT(event_id_bits);
> + __le64 *itt;
> +
> + itt = kcalloc(num_ents, sizeof(*itt), GFP_KERNEL);
> + if (!itt)
> + return -ENOMEM;
> +
> + its_dev->itt_cfg.linear.itt = itt;
> + its_dev->itt_cfg.linear.num_ents = num_ents;
> + its_dev->itt_cfg.l2itt = false;
> + its_dev->itt_cfg.event_id_bits = event_id_bits;
> +
> + if (its->flags & ITS_FLAGS_NON_COHERENT)
> + dcache_clean_inval_poc((unsigned long)itt,
> + (unsigned long)itt + num_ents * sizeof(*itt));
> + else
> + dsb(ishst);
We have this very pattern 4 or 5 times in this single file.
Consider a helper such as:
static void gicv5_its_dcache_clean(struct gicv5_its_chip_data *its,
void *start, size_t sz)
{
void *end = start + sz;
if (its->flags & ITS_FLAGS_NON_COHERENT)
dcache_clean_inval_poc((unsigned long)start, (unsigned long)end);
else
dsb(ishst);
}
and use it everywhere. Yes, this adds extra DSBs on the L2 ITT
path. But none of that is ever performance critical, and mostly
happens once per device in the lifetime of the system.
> +
> + return 0;
> +}
> +
> +/*
> + * Allocate a two-level ITT. All ITT entries are allocated in one go, unlike
> + * with the device table. Span may be used to limit the second level table
> + * size, where possible.
> + */
> +static int gicv5_its_create_itt_two_level(struct gicv5_its_chip_data *its,
> + struct gicv5_its_dev *its_dev,
> + unsigned int event_id_bits,
> + unsigned int itt_l2sz,
> + unsigned int num_events)
> +{
> + unsigned int l1_bits, l2_bits, span, events_per_l2_table,
> + complete_tables, final_span, num_ents;
> + __le64 *itt_l1, *itt_l2, **l2ptrs;
> + size_t l1sz;
> + int ret, i;
> + u64 val;
> +
> + ret = gicv5_its_l2sz_to_l2_bits(itt_l2sz);
> + if (ret < 0 || ret >= event_id_bits) {
> + pr_debug("Incorrect l2sz (0x%x) for %u EventID bits. Cannot allocate ITT\n",
> + itt_l2sz, event_id_bits);
> + return -EINVAL;
> + }
Honestly, I fail to see how this can happen.
> +
> + l2_bits = ret;
> +
> + l1_bits = event_id_bits - l2_bits;
> +
> + num_ents = BIT(l1_bits);
> +
> + itt_l1 = kcalloc(num_ents, sizeof(*itt_l1), GFP_KERNEL);
> + if (!itt_l1)
> + return -ENOMEM;
> +
> + l2ptrs = kcalloc(num_ents, sizeof(*l2ptrs), GFP_KERNEL);
> + if (!l2ptrs) {
> + kfree(itt_l1);
> + return -ENOMEM;
> + }
> +
> + its_dev->itt_cfg.l2.l2ptrs = l2ptrs;
> +
> + its_dev->itt_cfg.l2.l2sz = itt_l2sz;
> + its_dev->itt_cfg.l2.l1itt = itt_l1;
> + its_dev->itt_cfg.l2.num_l1_ents = num_ents;
> + its_dev->itt_cfg.l2itt = true;
> + its_dev->itt_cfg.event_id_bits = event_id_bits;
> +
> + /*
> + * Need to determine how many entries there are per L2 - this is based
> + * on the number of bits in the table.
> + */
> + events_per_l2_table = BIT(l2_bits);
> + complete_tables = num_events / events_per_l2_table;
> + final_span = order_base_2(num_events % events_per_l2_table);
> +
> + for (i = 0; i < num_ents; i++) {
> + size_t l2sz;
> +
> + span = i == complete_tables ? final_span : l2_bits;
> +
> + itt_l2 = kcalloc(BIT(span), sizeof(*itt_l2), GFP_KERNEL);
> + if (!itt_l2) {
> + ret = -ENOMEM;
> + goto out_free;
> + }
You are allocating a bunch of 64bit pointers. So the alignment is
BIT(span + 3) or ARCH_KMALLOC_MINALIGN, whichever is the largest.
> +
> + its_dev->itt_cfg.l2.l2ptrs[i] = itt_l2;
> +
> + l2sz = BIT(span) * sizeof(*itt_l2);
> +
> + if (its->flags & ITS_FLAGS_NON_COHERENT)
> + dcache_clean_inval_poc((unsigned long)itt_l2,
> + (unsigned long)itt_l2 + l2sz);
> +
> + val = (virt_to_phys(itt_l2) & GICV5_ITTL1E_L2_ADDR_MASK) |
> + FIELD_PREP(GICV5_ITTL1E_SPAN, span) |
> + FIELD_PREP(GICV5_ITTL1E_VALID, 0x1);
GICV5_ITTL1E_L2_ADDR_MASK starts at bit 12. What guarantees that span
is at least 9 so that you don't lose some significant address bits? I
think is works as a consequence of gicv5_its_l2sz_to_l2_bits()
returning at least 9, but some comment would be appreciated.
> +
> + WRITE_ONCE(itt_l1[i], cpu_to_le64(val));
> + }
> +
> + if (its->flags & ITS_FLAGS_NON_COHERENT) {
> + l1sz = num_ents * sizeof(*itt_l1);
> + dcache_clean_inval_poc((unsigned long)itt_l1,
> + (unsigned long)itt_l1 + l1sz);
> + } else {
> + dsb(ishst);
> + }
> +
> + return 0;
> +out_free:
> + for (i = i - 1; i >= 0; i--)
> + kfree(its_dev->itt_cfg.l2.l2ptrs[i]);
> +
> + kfree(its_dev->itt_cfg.l2.l2ptrs);
> + kfree(itt_l1);
> + return ret;
> +}
> +
> +/*
> + * Function to check whether the device table or ITT table support
> + * a two-level table and if so depending on the number of id_bits
> + * requested, determine whether a two-level table is required.
> + *
> + * Return the 2-level size value if a two level table is deemed
> + * necessary.
> + */
> +static bool gicv5_its_l2sz_two_level(bool devtab, u32 its_idr1, u8 id_bits,
> + u8 *sz)
> +{
> + int l2_bits, l2_sz = -EINVAL;
> +
> + if (devtab && !FIELD_GET(GICV5_ITS_IDR1_DT_LEVELS, its_idr1))
> + return false;
> +
> + if (!devtab && !FIELD_GET(GICV5_ITS_IDR1_ITT_LEVELS, its_idr1))
> + return false;
> +
> + /*
> + * Pick an L2 size that matches the pagesize; if a match
> + * is not found, go for the smallest supported l2 size granule.
> + *
> + * This ensures that we will always be able to allocate
> + * contiguous memory at L2.
> + */
> + switch (PAGE_SIZE) {
> + case SZ_64K:
> + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(its_idr1)) {
> + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_64k;
> + break;
> + }
> + fallthrough;
> + case SZ_16K:
> + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(its_idr1)) {
> + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_16k;
> + break;
> + }
> + fallthrough;
> + case SZ_4K:
> + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(its_idr1)) {
> + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_4k;
> + break;
> + }
> + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(its_idr1)) {
> + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_16k;
> + break;
> + }
> + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(its_idr1)) {
> + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_64k;
> + break;
> + }
> + break;
> + }
> +
> + l2_bits = gicv5_its_l2sz_to_l2_bits(l2_sz);
> +
> + if (l2_bits < 0 || l2_bits > id_bits)
> + return false;
I really cannot see how l2_bits can be an error. PAGE_SIZE is not an
arbitrary value, neither is the L2SZ configuration in IDR1.
If you are paranoid about broken implementations, put a BUG_ON() early
on to validate that your implementation isn't completely
braindead. But please don't litter the driver with these checks.
> +
> + *sz = l2_sz;
> +
> + return true;
> +}
> +
> +static int gicv5_its_device_get_itte_ref(struct gicv5_its_dev *its_dev,
> + __le64 **itte, u16 event_id)
> +{
> + if (!its_dev->itt_cfg.l2itt) {
> + __le64 *itt = its_dev->itt_cfg.linear.itt;
> + *itte = &itt[event_id];
> + } else {
> + __le64 *l2_itt, *l1_itt = its_dev->itt_cfg.l2.l1itt;
> + unsigned int l1_idx, l2_idx, l2_size, l2_bits;
> + int ret;
> +
> + ret = gicv5_its_l2sz_to_l2_bits(its_dev->itt_cfg.l2.l2sz);
> + if (ret < 0)
> + return ret;
> + l2_bits = ret;
> +
> + l1_idx = event_id >> l2_bits;
> +
> + if (!FIELD_GET(GICV5_ITTL1E_VALID,
> + le64_to_cpu(l1_itt[l1_idx]))) {
> + pr_debug("L1 ITT entry is not valid.\n");
> + return -EINVAL;
> + }
> +
> + l2_idx = event_id & GENMASK(l2_bits - 1, 0);
> +
> + l2_size = BIT(FIELD_GET(GICV5_ITTL1E_SPAN,
> + le64_to_cpu(l1_itt[l1_idx])));
> +
> + // Sanity check our indexing
> + if (l2_idx >= l2_size) {
> + pr_debug("L2 ITT index (%u) exceeds L2 table size (%u)!\n",
> + l2_idx, l2_size);
> + return -EINVAL;
> + }
That's another example: if your indexing is going in the weeds, your
tables are invalid, or some other state has gone rotten, you probably
are corrupting memory already, and returning an error only has two
effects:
- keep the bad state around
- add complexity to the code because of the error handling
I'm not a fan of BUG_ON(), but that's at least appropriate in this
case -- you are in a very bad situation that cannot be recovered from.
> + l2_itt = its_dev->itt_cfg.l2.l2ptrs[l1_idx];
> + *itte = &l2_itt[l2_idx];
> + }
> +
> + return 0;
And once you've simplified the error handling, you can simply return
the pointer instead of dealing with the **itte.
> +}
> +
> +static int gicv5_its_device_cache_inv(struct gicv5_its_chip_data *its,
> + struct gicv5_its_dev *its_dev)
> +{
> + u32 devicer;
> + u64 didr;
> +
> + didr = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, its_dev->device_id);
> + devicer = FIELD_PREP(GICV5_ITS_INV_DEVICER_I, 0x1) |
> + FIELD_PREP(GICV5_ITS_INV_DEVICER_EVENTID_BITS,
> + its_dev->itt_cfg.event_id_bits) |
> + FIELD_PREP(GICV5_ITS_INV_DEVICER_L1, 0x0);
> + its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
> + its_writel_relaxed(its, devicer, GICV5_ITS_INV_DEVICER);
> +
> + return gicv5_its_wait_for_invalidation(its);
> +}
> +
> +/*
> + * Allocate a level 2 device table entry, update L1 parent to reference it.
> + * Only used for 2-level device tables, and it is called on demand.
> + */
> +static int gicv5_its_alloc_l2_devtab(struct gicv5_its_chip_data *its,
> + unsigned int l1_index)
> +{
> + __le64 *l2devtab, *l1devtab = its->devtab_cfgr.l2.l1devtab;
> + u8 span, l2sz, l2_bits;
> + u64 l1dte;
> + int ret;
> +
> + if (FIELD_GET(GICV5_DTL1E_VALID, le64_to_cpu(l1devtab[l1_index])))
> + return 0;
> +
> + span = FIELD_GET(GICV5_DTL1E_SPAN, le64_to_cpu(l1devtab[l1_index]));
> + l2sz = FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, its->devtab_cfgr.cfgr);
nit: I see a bunch of these accessors. Maybe consider adding
convenience helpers such as:
#define devtab_cfgr_field(its, f) \
FIELD_GET(GICV5_ITS_DT_CFGR_##f, (its)->devtab_cfgr.cfgr)
l2sz = devtab_cfgr_field(its, L2SZ);
which makes it much more readable.
> +
> + ret = gicv5_its_l2sz_to_l2_bits(l2sz);
> + if (ret < 0)
> + return ret;
> +
> + l2_bits = ret;
> +
> + /*
> + * Span allows us to create a smaller L2 device table.
> + * If it is too large, use the number of allowed L2 bits.
> + */
> + if (span > l2_bits)
> + span = l2_bits;
> +
> + l2devtab = kcalloc(BIT(span), sizeof(*l2devtab), GFP_KERNEL);
> + if (!l2devtab)
> + return -ENOMEM;
> +
> + its->devtab_cfgr.l2.l2ptrs[l1_index] = l2devtab;
> +
> + l1dte = FIELD_PREP(GICV5_DTL1E_SPAN, span) |
> + (virt_to_phys(l2devtab) & GICV5_DTL1E_L2_ADDR_MASK) |
> + FIELD_PREP(GICV5_DTL1E_VALID, 0x1);
> + its_write_table_entry(its, &l1devtab[l1_index], cpu_to_le64(l1dte));
> +
> + return 0;
> +}
> +
> +static int gicv5_its_devtab_get_dte_ref(struct gicv5_its_chip_data *its,
> + __le64 **dte, u32 device_id,
> + bool alloc)
> +{
> + u8 str = FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, its->devtab_cfgr.cfgr);
> + unsigned int l2sz, l2_bits, l1_idx, l2_idx;
> + __le64 *l1devtab, *l2devtab;
> + int ret;
> +
> + if (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) {
> + l2devtab = its->devtab_cfgr.linear.devtab;
> + *dte = &l2devtab[device_id];
> + } else {
> + l2sz = FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, its->devtab_cfgr.cfgr);
> + l1devtab = its->devtab_cfgr.l2.l1devtab;
> +
> + ret = gicv5_its_l2sz_to_l2_bits(l2sz);
> + if (ret < 0)
> + return -EINVAL;
> +
> + l2_bits = ret;
nit: directly assign l2_bits.
> + l1_idx = device_id >> l2_bits;
> + l2_idx = device_id & GENMASK(l2_bits - 1, 0);
> +
> + if (alloc) {
> + /*
> + * Allocate a new L2 device table here before
> + * continuing. We make the assumption that the span in
> + * the L1 table has been set correctly, and blindly use
> + * that value.
> + */
> + ret = gicv5_its_alloc_l2_devtab(its, l1_idx);
> + if (ret)
> + return ret;
> + } else {
> + if (!FIELD_GET(GICV5_DTL1E_VALID,
> + le64_to_cpu(l1devtab[l1_idx])))
> + return -EINVAL;
> + }
> +
> + l2devtab = its->devtab_cfgr.l2.l2ptrs[l1_idx];
> + *dte = &l2devtab[l2_idx];
> + }
> +
> + return 0;
Same comments as the itte_ref equivalent.
> +}
> +
> +/*
> + * Register a new device in the device table. Allocate an ITT and
> + * program the L2DTE entry according to the ITT structure that
> + * was chosen.
> + */
> +static int gicv5_its_device_register(struct gicv5_its_chip_data *its,
> + struct gicv5_its_dev *its_dev)
> +{
> + u8 event_id_bits, device_id_bits, itt_struct, itt_l2sz;
> + phys_addr_t itt_phys_base;
> + bool two_level_itt;
> + u32 idr1, idr2;
> + __le64 *dte;
> + u64 val;
> + int ret;
> +
> + device_id_bits = FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS,
> + its->devtab_cfgr.cfgr);
> +
> + if (its_dev->device_id >= BIT(device_id_bits)) {
> + pr_err("Supplied DeviceID (%u) outside of Device Table range (%u)!",
> + its_dev->device_id, (u32)GENMASK(device_id_bits - 1, 0));
> + return -EINVAL;
> + }
> +
> + ret = gicv5_its_devtab_get_dte_ref(its, &dte, its_dev->device_id, true);
> + if (ret)
> + return ret;
> +
> + if (FIELD_GET(GICV5_DTL2E_VALID, le64_to_cpu(*dte)))
> + return -EBUSY;
> +
> + /*
> + * Determine how many bits we need, validate those against the max.
> + * Based on these, determine if we should go for a 1- or 2-level ITT.
> + */
> + event_id_bits = order_base_2(its_dev->num_events);
> +
> + idr2 = its_readl_relaxed(its, GICV5_ITS_IDR2);
> +
> + if (event_id_bits > FIELD_GET(GICV5_ITS_IDR2_EVENTID_BITS, idr2)) {
> + pr_err("Required EventID bits (%u) larger than supported bits (%u)!",
> + event_id_bits,
> + (u8)FIELD_GET(GICV5_ITS_IDR2_EVENTID_BITS, idr2));
> + return -EINVAL;
> + }
> +
> + idr1 = its_readl_relaxed(its, GICV5_ITS_IDR1);
> +
> + /*
> + * L2 ITT size is programmed into the L2DTE regardless of
> + * whether a two-level or linear ITT is built, init it.
> + */
> + itt_l2sz = 0;
> +
> + two_level_itt = gicv5_its_l2sz_two_level(false, idr1, event_id_bits,
> + &itt_l2sz);
> + if (two_level_itt)
> + ret = gicv5_its_create_itt_two_level(its, its_dev, event_id_bits,
> + itt_l2sz,
> + its_dev->num_events);
> + else
> + ret = gicv5_its_create_itt_linear(its, its_dev, event_id_bits);
> + if (ret)
> + return ret;
> +
> + itt_phys_base = two_level_itt ? virt_to_phys(its_dev->itt_cfg.l2.l1itt) :
> + virt_to_phys(its_dev->itt_cfg.linear.itt);
> +
> + itt_struct = two_level_itt ? GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL :
> + GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR;
> +
> + val = FIELD_PREP(GICV5_DTL2E_EVENT_ID_BITS, event_id_bits) |
> + FIELD_PREP(GICV5_DTL2E_ITT_STRUCTURE, itt_struct) |
> + (itt_phys_base & GICV5_DTL2E_ITT_ADDR_MASK) |
> + FIELD_PREP(GICV5_DTL2E_ITT_L2SZ, itt_l2sz) |
> + FIELD_PREP(GICV5_DTL2E_VALID, 0x1);
> +
> + its_write_table_entry(its, dte, cpu_to_le64(val));
> +
> + ret = gicv5_its_device_cache_inv(its, its_dev);
> + if (ret) {
> + gicv5_its_free_itt(its_dev);
> + its_write_table_entry(its, dte, 0);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Unregister a device in the device table. Lookup the device by ID, free the
> + * corresponding ITT, mark the device as invalid in the device table.
> + */
> +static int gicv5_its_device_unregister(struct gicv5_its_chip_data *its,
> + struct gicv5_its_dev *its_dev)
> +{
> + __le64 *dte;
> + int ret;
> +
> + ret = gicv5_its_devtab_get_dte_ref(its, &dte, its_dev->device_id, false);
> + if (ret) {
> + pr_debug("Failed to find DTE for DeviceID 0x%x\n", its_dev->device_id);
> + return -EINVAL;
> + }
> +
> + if (!FIELD_GET(GICV5_DTL2E_VALID, le64_to_cpu(*dte))) {
> + pr_debug("Device table entry for DeviceID 0x%x is not valid. Nothing to clean up!",
> + its_dev->device_id);
> + return -EINVAL;
> + }
> +
> + gicv5_its_free_itt(its_dev);
> +
> + /* Zero everything - make it clear that this is an invalid entry */
> + its_write_table_entry(its, dte, 0);
> +
> + return gicv5_its_device_cache_inv(its, its_dev);
> +}
> +
> +/*
> + * Allocate a 1-level device table. All entries are allocated, but marked
> + * invalid.
> + */
> +static int gicv5_its_alloc_devtab_linear(struct gicv5_its_chip_data *its,
> + u8 device_id_bits)
> +{
> + __le64 *devtab;
> + size_t sz;
> + u64 baser;
> + u32 cfgr;
> +
> + /*
> + * We expect a GICv5 implementation requiring a large number of
> + * deviceID bits to support a 2-level device table. If that's not
> + * the case, cap the number of deviceIDs supported according to the
> + * kmalloc limits so that the system can chug along with a linear
> + * device table.
> + */
> + sz = BIT_ULL(device_id_bits) * sizeof(*devtab);
> + if (sz > KMALLOC_MAX_SIZE) {
> + u8 device_id_cap = ilog2(KMALLOC_MAX_SIZE/sizeof(*devtab));
> +
> + pr_warn("Limiting device ID bits from %u to %u\n",
> + device_id_bits, device_id_cap);
> + device_id_bits = device_id_cap;
> + }
> +
> + devtab = kcalloc(BIT(device_id_bits), sizeof(*devtab), GFP_KERNEL);
> + if (!devtab)
> + return -ENOMEM;
> +
> + if (its->flags & ITS_FLAGS_NON_COHERENT)
> + dcache_clean_inval_poc((unsigned long)devtab,
> + (unsigned long)devtab + sz);
> + else
> + dsb(ishst);
> +
> + cfgr = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
> + GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) |
> + FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, 0) |
> + FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, device_id_bits);
> + its_writel_relaxed(its, cfgr, GICV5_ITS_DT_CFGR);
> +
> + baser = virt_to_phys(devtab) & GICV5_ITS_DT_BASER_ADDR_MASK;
> + its_writeq_relaxed(its, baser, GICV5_ITS_DT_BASER);
> +
> + its->devtab_cfgr.cfgr = cfgr;
> + its->devtab_cfgr.linear.devtab = devtab;
> +
> + return 0;
> +}
> +
> +/*
> + * Allocate a 2-level device table. L2 entries are not allocated,
> + * they are allocated on-demand.
> + */
> +static int gicv5_its_alloc_devtab_two_level(struct gicv5_its_chip_data *its,
> + u8 device_id_bits,
> + u8 devtab_l2sz)
> +{
> + unsigned int l1_bits, l2_bits, i;
> + __le64 *l1devtab, **l2ptrs;
> + size_t l1_sz;
> + u64 baser;
> + u32 cfgr;
> + int ret;
> +
> + ret = gicv5_its_l2sz_to_l2_bits(devtab_l2sz);
> + if (ret < 0)
> + return ret;
> +
> + l2_bits = ret;
This pattern should go.
> +
> + l1_bits = device_id_bits - l2_bits;
> + l1_sz = BIT(l1_bits) * sizeof(*l1devtab);
> + /*
> + * With 2-level device table support it is highly unlikely
> + * that we are not able to allocate the required amount of
> + * device table memory to cover deviceID space; cap the
> + * deviceID space if we encounter such set-up.
> + * If this ever becomes a problem we could revisit the policy
> + * behind level 2 size selection to reduce level-1 deviceID bits.
> + */
> + if (l1_sz > KMALLOC_MAX_SIZE) {
> + l1_bits = ilog2(KMALLOC_MAX_SIZE/sizeof(*l1devtab));
> +
> + pr_warn("Limiting device ID bits from %u to %u\n",
> + device_id_bits, l1_bits + l2_bits);
> + device_id_bits = l1_bits + l2_bits;
> + l1_sz = KMALLOC_MAX_SIZE;
> + }
> +
> + l1devtab = kcalloc(BIT(l1_bits), sizeof(*l1devtab), GFP_KERNEL);
> + if (!l1devtab)
> + return -ENOMEM;
> +
> + l2ptrs = kcalloc(BIT(l1_bits), sizeof(*l2ptrs), GFP_KERNEL);
> + if (!l2ptrs) {
> + kfree(l1devtab);
> + return -ENOMEM;
> + }
> +
> + for (i = 0; i < BIT(l1_bits); i++)
> + l1devtab[i] = cpu_to_le64(FIELD_PREP(GICV5_DTL1E_SPAN, l2_bits));
> +
> + if (its->flags & ITS_FLAGS_NON_COHERENT)
> + dcache_clean_inval_poc((unsigned long)l1devtab,
> + (unsigned long)l1devtab + l1_sz);
> + else
> + dsb(ishst);
> +
> + cfgr = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
> + GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL) |
> + FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, devtab_l2sz) |
> + FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, device_id_bits);
> + its_writel_relaxed(its, cfgr, GICV5_ITS_DT_CFGR);
> +
> + baser = virt_to_phys(l1devtab) & GICV5_ITS_DT_BASER_ADDR_MASK;
> + its_writeq_relaxed(its, baser, GICV5_ITS_DT_BASER);
> +
> + its->devtab_cfgr.cfgr = cfgr;
> + its->devtab_cfgr.l2.l1devtab = l1devtab;
> + its->devtab_cfgr.l2.l2ptrs = l2ptrs;
> +
> + return 0;
> +}
> +
> +/*
> + * Initialise the device table as either 1- or 2-level depending on what is
> + * supported by the hardware.
> + */
> +static int gicv5_its_init_devtab(struct gicv5_its_chip_data *its)
> +{
> + u8 device_id_bits, devtab_l2sz;
> + bool two_level_devtab;
> + u32 idr1;
> +
> + idr1 = its_readl_relaxed(its, GICV5_ITS_IDR1);
> +
> + device_id_bits = FIELD_GET(GICV5_ITS_IDR1_DEVICEID_BITS, idr1);
> + two_level_devtab = gicv5_its_l2sz_two_level(true, idr1, device_id_bits,
> + &devtab_l2sz);
> + if (two_level_devtab)
> + return gicv5_its_alloc_devtab_two_level(its, device_id_bits,
> + devtab_l2sz);
> + else
> + return gicv5_its_alloc_devtab_linear(its, device_id_bits);
> +}
> +
> +static void gicv5_its_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
> +{
> + struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
> + struct gicv5_its_chip_data *its = its_dev->its_node;
> + u64 addr;
> +
> + addr = its->its_trans_phys_base;
> +
> + msg->data = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> + msi_msg_set_addr(irq_data_get_msi_desc(d), msg, addr);
> +}
> +
> +static const struct irq_chip gicv5_its_irq_chip = {
> + .name = "GICv5-ITS-MSI",
> + .irq_mask = irq_chip_mask_parent,
> + .irq_unmask = irq_chip_unmask_parent,
> + .irq_eoi = irq_chip_eoi_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> + .irq_get_irqchip_state = irq_chip_get_parent_state,
> + .irq_set_irqchip_state = irq_chip_set_parent_state,
> + .irq_compose_msi_msg = gicv5_its_compose_msi_msg,
> + .flags = IRQCHIP_SET_TYPE_MASKED |
> + IRQCHIP_SKIP_SET_WAKE |
> + IRQCHIP_MASK_ON_SUSPEND
> +};
> +
> +static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *its,
> + u32 device_id)
> +{
> + struct gicv5_its_dev *dev = xa_load(&its->its_devices, device_id);
> +
> + return dev ? dev : ERR_PTR(-ENODEV);
> +}
> +
> +static struct gicv5_its_dev *gicv5_its_alloc_device(
> + struct gicv5_its_chip_data *its, int nvec,
> + u32 dev_id)
nit: please place the first parameter on the same line as the function
name.
> +{
> + struct gicv5_its_dev *its_dev;
> + int ret;
> +
> + its_dev = gicv5_its_find_device(its, dev_id);
> + if (!IS_ERR(its_dev)) {
> + pr_debug("A device with this DeviceID (0x%x) has already been registered.\n",
> + dev_id);
> +
> + if (nvec > its_dev->num_events) {
> + pr_debug("Requesting more ITT entries than allocated\n");
> + return ERR_PTR(-ENXIO);
> + }
> +
> + its_dev->shared = true;
> +
> + return its_dev;
I really think we shouldn't even consider the silliness of
non-transparent bridges this time around. That's a terrible system
design, and it leads to all sorts of lifetime madness -- the GICv3
driver is a testament to it. Modern systems with GICv5 should not have
to deal with this nonsense.
> + }
> +
> + its_dev = kzalloc(sizeof(*its_dev), GFP_KERNEL);
> + if (!its_dev)
> + return ERR_PTR(-ENOMEM);
> +
> + its_dev->device_id = dev_id;
> + its_dev->num_events = nvec;
> + its_dev->num_mapped_events = 0;
> +
> + ret = gicv5_its_device_register(its, its_dev);
> + if (ret) {
> + pr_debug("Failed to register the device\n");
> + kfree(its_dev);
> + return ERR_PTR(ret);
> + }
> +
> + gicv5_its_device_cache_inv(its, its_dev);
> +
> + /*
> + * This is the first time we have seen this device. Hence, it is not
> + * shared.
> + */
> + its_dev->shared = false;
> +
> + its_dev->its_node = its;
> +
> + its_dev->event_map =
> + (unsigned long *)bitmap_zalloc(its_dev->num_events, GFP_KERNEL);
> + if (!its_dev->event_map) {
> + gicv5_its_device_unregister(its, its_dev);
> + kfree(its_dev);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + xa_store(&its->its_devices, dev_id, its_dev, GFP_KERNEL);
> +
> + return its_dev;
> +}
> +
> +static int gicv5_its_msi_prepare(struct irq_domain *domain, struct device *dev,
> + int nvec, msi_alloc_info_t *info)
> +{
> + u32 dev_id = info->scratchpad[0].ul;
> + struct msi_domain_info *msi_info;
> + struct gicv5_its_chip_data *its;
> + struct gicv5_its_dev *its_dev;
> +
> + msi_info = msi_get_domain_info(domain);
> + its = msi_info->data;
> +
> + guard(mutex)(&its->dev_alloc_lock);
> +
> + its_dev = gicv5_its_alloc_device(its, nvec, dev_id);
> + if (IS_ERR(its_dev))
> + return PTR_ERR(its_dev);
> +
> + if (info->flags & MSI_ALLOC_FLAGS_PROXY_DEVICE)
> + its_dev->shared = true;
> +
> + return 0;
> +}
> +
> +static struct msi_domain_ops its_msi_domain_ops = {
> + .msi_prepare = gicv5_its_msi_prepare,
> +};
> +
> +static int gicv5_its_alloc_event(struct gicv5_its_dev *its_dev, u16 event_id,
> + u32 lpi)
> +{
> + struct gicv5_its_chip_data *its = its_dev->its_node;
> + u64 itt_entry;
> + __le64 *itte;
> + int ret;
> +
> + if (event_id >= its_dev->num_events) {
> + pr_debug("EventID 0x%x outside of ITT range (0x%x)\n", event_id,
> + its_dev->num_events);
> + return -EINVAL;
> + }
> +
> + if (WARN(its_dev->num_mapped_events == its_dev->num_events,
> + "Reached maximum number of events\n"))
> + return -EINVAL;
> +
> + ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
> + if (ret)
> + return ret;
> +
> + if (FIELD_GET(GICV5_ITTL2E_VALID, *itte))
> + return -EEXIST;
> +
> + itt_entry = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) |
> + FIELD_PREP(GICV5_ITTL2E_VALID, 0x1);
> +
> + its_write_table_entry(its, itte, cpu_to_le64(itt_entry));
> +
> + gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
> +
> + its_dev->num_mapped_events += 1;
> +
> + return 0;
> +}
> +
> +static void gicv5_its_free_event(struct gicv5_its_dev *its_dev, u16 event_id)
> +{
> + struct gicv5_its_chip_data *its = its_dev->its_node;
> + u64 itte_val;
> + __le64 *itte;
> + int ret;
> +
> + if (WARN(!its_dev->num_mapped_events, "No mapped events\n"))
> + return;
> +
> + ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
> + if (ret) {
> + pr_debug("Failed to get the ITTE!\n");
> + return;
> + }
> +
> + itte_val = le64_to_cpu(*itte);
> + itte_val &= ~GICV5_ITTL2E_VALID;
> +
> + its_write_table_entry(its, itte, cpu_to_le64(itte_val));
> +
> + gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
> +
> + its_dev->num_mapped_events -= 1;
> +}
> +
> +static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev,
> + unsigned int nr_irqs, u32 *eventid)
> +{
> + int ret;
> +
> + ret = bitmap_find_free_region(its_dev->event_map,
> + its_dev->num_events,
> + get_count_order(nr_irqs));
> +
> + if (ret < 0)
> + return ret;
> +
> + *eventid = ret;
> +
> + return 0;
> +}
> +
> +static void gicv5_its_free_eventid(struct gicv5_its_dev *its_dev,
> + u32 event_id_base,
> + unsigned int nr_irqs)
> +{
> + bitmap_release_region(its_dev->event_map, event_id_base,
> + get_count_order(nr_irqs));
> +}
> +
> +static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + u32 device_id, event_id_base, lpi;
> + struct msi_domain_info *msi_info;
> + struct gicv5_its_chip_data *its;
> + struct gicv5_its_dev *its_dev;
> + msi_alloc_info_t *info = arg;
> + irq_hw_number_t hwirq;
> + struct irq_data *irqd;
> + int ret, i;
> +
> + device_id = info->scratchpad[0].ul;
> +
> + msi_info = msi_get_domain_info(domain);
> + its = msi_info->data;
> +
> + mutex_lock(&its->dev_alloc_lock);
> +
> + its_dev = gicv5_its_find_device(its, device_id);
> + if (IS_ERR(its_dev)) {
> + mutex_unlock(&its->dev_alloc_lock);
> + return PTR_ERR(its_dev);
> + }
> +
> + ret = gicv5_its_alloc_eventid(its_dev, nr_irqs, &event_id_base);
> + if (ret) {
> + mutex_unlock(&its->dev_alloc_lock);
> + return ret;
> + }
> +
> + mutex_unlock(&its->dev_alloc_lock);
Turn this block into a scoped guard, which will avoid the
mutex_unlock()s altogether.
> +
> + ret = iommu_dma_prepare_msi(info->desc, its->its_trans_phys_base);
> + if (ret)
> + goto out_eventid;
> +
> + for (i = 0; i < nr_irqs; i++) {
> + lpi = gicv5_alloc_lpi();
> + if (ret < 0) {
> + pr_debug("Failed to find free LPI!\n");
> + goto out_eventid;
> + }
> +
> + ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
> + if (ret)
> + goto out_free_lpi;
> +
> + /*
> + * Store eventid and deviceid into the hwirq for later use.
> + *
> + * hwirq = event_id << 32 | device_id
> + */
> + hwirq = FIELD_PREP(GICV5_ITS_HWIRQ_DEVICE_ID, device_id) |
> + FIELD_PREP(GICV5_ITS_HWIRQ_EVENT_ID, (u64)event_id_base + i);
> + irq_domain_set_info(domain, virq + i, hwirq,
> + &gicv5_its_irq_chip, its_dev,
> + handle_fasteoi_irq, NULL, NULL);
> +
> + irqd = irq_get_irq_data(virq + i);
> + irqd_set_single_target(irqd);
> + irqd_set_affinity_on_activate(irqd);
> + irqd_set_resend_when_in_progress(irqd);
> + }
> +
> + return 0;
> +out_free_lpi:
> + gicv5_free_lpi(lpi);
> +out_eventid:
> + gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs);
> +
> + return ret;
> +}
> +
> +static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs)
> +{
> + struct msi_domain_info *msi_info;
> + struct gicv5_its_chip_data *its;
> + struct gicv5_its_dev *its_dev;
> + struct irq_data *d;
> + u16 event_id_base;
> + bool free_device;
> + u32 device_id;
> + int i;
> +
> + msi_info = msi_get_domain_info(domain);
> + its = msi_info->data;
> +
> + d = irq_domain_get_irq_data(domain, virq);
> + device_id = FIELD_GET(GICV5_ITS_HWIRQ_DEVICE_ID, d->hwirq);
> + event_id_base = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> +
> + guard(mutex)(&its->dev_alloc_lock);
> +
> + its_dev = gicv5_its_find_device(its, device_id);
> + if (IS_ERR(its_dev)) {
> + pr_debug("Couldn't find the ITS device!\n");
> + return;
> + }
> +
> + bitmap_release_region(its_dev->event_map, event_id_base,
> + get_count_order(nr_irqs));
> +
> + free_device = !its_dev->shared && bitmap_empty(its_dev->event_map,
> + its_dev->num_events);
> +
> + /* Hierarchically free irq data */
> + for (i = 0; i < nr_irqs; i++) {
> + d = irq_domain_get_irq_data(domain, virq + i);
> +
> + gicv5_free_lpi(d->parent_data->hwirq);
> + irq_domain_reset_irq_data(d);
> + }
> + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> +
> + gicv5_its_syncr(its, its_dev);
> + gicv5_irs_syncr();
> +
> + if (free_device) {
> + gicv5_its_device_unregister(its, its_dev);
> + bitmap_free(its_dev->event_map);
> + xa_erase(&its->its_devices, device_id);
> + kfree(its_dev);
> + }
> +}
> +
> +static int gicv5_its_irq_domain_activate(struct irq_domain *domain,
> + struct irq_data *d, bool reserve)
> +{
> + struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
> + u16 event_id;
> + u32 lpi;
> +
> + event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> + lpi = d->parent_data->hwirq;
> +
> + return gicv5_its_alloc_event(its_dev, event_id, lpi);
Huh. This looks wrong. Allocating the event really should happen at
alloc time, not at activate time, because the endpoint driver doesn't
really expect this to fail for any reason other than a gross bug.
activate should allow the translation to take place, but not rely on
allocating events. Compare with GICv3, which only issues the MAPTI
command at activate time.
> +}
> +
> +static void gicv5_its_irq_domain_deactivate(struct irq_domain *domain,
> + struct irq_data *d)
> +{
> + struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
> + u16 event_id;
> +
> + event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> +
> + gicv5_its_free_event(its_dev, event_id);
> +}
> +static const struct irq_domain_ops gicv5_its_irq_domain_ops = {
> + .alloc = gicv5_its_irq_domain_alloc,
> + .free = gicv5_its_irq_domain_free,
> + .activate = gicv5_its_irq_domain_activate,
> + .deactivate = gicv5_its_irq_domain_deactivate,
> + .select = msi_lib_irq_domain_select,
> +};
> +
> +static int gicv5_its_wait_for_cr0(struct gicv5_its_chip_data *its)
> +{
> + return gicv5_its_wait_for_op(its->its_base, GICV5_ITS_CR0,
> + GICV5_ITS_CR0_IDLE);
> +}
> +
> +static void gicv5_its_print_info(struct gicv5_its_chip_data *its_node)
> +{
> + bool devtab_linear;
> + u8 device_id_bits;
> + u8 str;
> +
> + device_id_bits = FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS,
> + its_node->devtab_cfgr.cfgr);
> +
> + str = FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, its_node->devtab_cfgr.cfgr);
> + devtab_linear = (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR);
> +
> + pr_info("ITS %s enabled using %s device table device_id_bits %u\n",
> + fwnode_get_name(its_node->fwnode),
> + devtab_linear ? "linear" : "2-level",
> + device_id_bits);
> +}
> +
> +static int __init gicv5_its_init_bases(phys_addr_t its_trans_base,
> + void __iomem *its_base,
> + struct fwnode_handle *handle,
> + struct irq_domain *parent_domain)
> +{
> + struct device_node *np = to_of_node(handle);
> + struct gicv5_its_chip_data *its_node;
> + struct msi_domain_info *info;
> + struct irq_domain *d;
> + u32 cr0, cr1;
> + bool enabled;
> + int ret;
> +
> + info = kzalloc(sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + its_node = kzalloc(sizeof(*its_node), GFP_KERNEL);
> + if (!its_node) {
> + kfree(info);
> + return -ENOMEM;
> + }
> +
> + info->ops = &its_msi_domain_ops;
> + info->data = its_node;
> +
> + mutex_init(&its_node->dev_alloc_lock);
> + xa_init(&its_node->its_devices);
> + its_node->fwnode = handle;
> + its_node->its_base = its_base;
> + its_node->its_trans_phys_base = its_trans_base;
> +
> + d = irq_domain_create_hierarchy(parent_domain, IRQ_DOMAIN_FLAG_ISOLATED_MSI,
> + 0, handle, &gicv5_its_irq_domain_ops, info);
> + its_node->domain = d;
> + irq_domain_update_bus_token(its_node->domain, DOMAIN_BUS_NEXUS);
> +
> + its_node->domain->msi_parent_ops = &gic_its_msi_parent_ops;
> + its_node->domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
> +
> + cr0 = its_readl_relaxed(its_node, GICV5_ITS_CR0);
> + enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, cr0);
> + if (WARN(enabled, "ITS %s enabled, disabling it before proceeding\n",
> + np->full_name)) {
> + cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x0);
> + its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
> + ret = gicv5_its_wait_for_cr0(its_node);
> + if (ret) {
> + irq_domain_remove(its_node->domain);
> + kfree(info);
> + kfree(its_node);
> + return ret;
> + }
> + }
> +
> + if (of_property_read_bool(np, "dma-noncoherent")) {
> + /*
> + * A non-coherent ITS implies that some cache levels cannot be
> + * used coherently by the cores and GIC. Our only option is to mark
> + * memory attributes for the GIC as non-cacheable; by default,
> + * non-cacheable memory attributes imply outer-shareable
> + * shareability, the value written into ITS_CR1_SH is ignored.
> + */
> + cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_NO_READ_ALLOC) |
> + FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_NON_CACHE) |
> + FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_NON_CACHE);
> + its_node->flags |= ITS_FLAGS_NON_COHERENT;
> + } else {
> + cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_READ_ALLOC) |
> + FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_WB_CACHE) |
> + FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_WB_CACHE) |
> + FIELD_PREP(GICV5_ITS_CR1_SH, GICV5_INNER_SHARE);
> + }
> +
> + its_writel_relaxed(its_node, cr1, GICV5_ITS_CR1);
> +
> + ret = gicv5_its_init_devtab(its_node);
> + if (ret) {
> + irq_domain_remove(its_node->domain);
> + kfree(info);
> + kfree(its_node);
> + return ret;
> + }
> +
> + cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x1);
> + its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
> +
> + ret = gicv5_its_wait_for_cr0(its_node);
> + if (ret) {
> + irq_domain_remove(its_node->domain);
> + kfree(info);
> + kfree(its_node);
> + return ret;
> + }
All this error handling is a bit cumbersome. Use common error paths or
whatever fancy new-age cleanup mechanism has been invented these days.
> +
> + list_add(&its_node->entry, &its_nodes);
> +
> + gicv5_its_print_info(its_node);
> +
> + return 0;
> +}
> +
> +static int __init gicv5_its_init(struct device_node *node)
> +{
> + void __iomem *its_base;
> + struct resource res;
> + int ret;
> +
> + its_base = of_io_request_and_map(node, 0, "ITS");
> + if (IS_ERR(its_base)) {
> + pr_err("%pOF: unable to map GICv5 ITS_CONFIG_FRAME\n", node);
> + return PTR_ERR(its_base);
> + }
> +
> + /*
> + * The ITS_TRANSLATE_FRAME is the second reg entry, (first is the
> + * ITS_CONFIG_FRAME) - extract it and use it to init ITS data
> + * structures.
> + */
> + ret = of_address_to_resource(node, 1, &res);
> + if (ret)
> + goto out_unmap;
> +
> + ret = gicv5_its_init_bases(res.start, its_base, &node->fwnode,
> + gicv5_global_data.lpi_domain);
> + if (ret)
> + goto out_unmap;
> +
> + return 0;
> +out_unmap:
> + iounmap(its_base);
> + return ret;
> +}
> +
> +void __init gicv5_its_of_probe(struct device_node *parent)
> +{
> + struct device_node *np;
> +
> + for_each_available_child_of_node(parent, np) {
> + if (!of_device_is_compatible(np, "arm,gic-v5-its"))
> + continue;
> +
> + if (gicv5_its_init(np))
> + pr_err("Failed to init ITS %s\n", np->full_name);
> + }
> +}
> diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> index c4d4e85382f672fa4ae334db1a4e4c7c4f46b9fe..e483d0774936035b5cf2407da9a65d776bad3138 100644
> --- a/drivers/irqchip/irq-gic-v5.c
> +++ b/drivers/irqchip/irq-gic-v5.c
> @@ -55,12 +55,12 @@ static void release_lpi(u32 lpi)
> ida_free(&lpi_ida, lpi);
> }
>
> -static int gicv5_alloc_lpi(void)
> +int gicv5_alloc_lpi(void)
> {
> return alloc_lpi();
> }
>
> -static void gicv5_free_lpi(u32 lpi)
> +void gicv5_free_lpi(u32 lpi)
> {
> release_lpi(lpi);
> }
> @@ -1045,6 +1045,8 @@ static int __init gicv5_of_init(struct device_node *node,
>
> gicv5_smp_init();
>
> + gicv5_irs_its_probe();
> +
> return 0;
> out_int:
> gicv5_cpu_disable_interrupts();
> diff --git a/drivers/irqchip/irq-gic-v5.h b/drivers/irqchip/irq-gic-v5.h
> index 19569639153a084760c3b5b7f0fa84791ba0195c..f5a453599493020b36d9c7f18c08171c51ba8669 100644
> --- a/drivers/irqchip/irq-gic-v5.h
> +++ b/drivers/irqchip/irq-gic-v5.h
> @@ -5,6 +5,8 @@
> #ifndef __LINUX_IRQCHIP_GIC_V5_H
> #define __LINUX_IRQCHIP_GIC_V5_H
>
> +#include <linux/iopoll.h>
> +
> #include <asm/arch_gicv5.h>
> #include <asm/smp.h>
>
> @@ -41,6 +43,8 @@
> #define GICV5_IRS_IDR7 0x001c
> #define GICV5_IRS_CR0 0x0080
> #define GICV5_IRS_CR1 0x0084
> +#define GICV5_IRS_SYNCR 0x00c0
> +#define GICV5_IRS_SYNC_STATUSR 0x00c4
> #define GICV5_IRS_SPI_SELR 0x0108
> #define GICV5_IRS_SPI_CFGR 0x0114
> #define GICV5_IRS_SPI_STATUSR 0x0118
> @@ -94,6 +98,10 @@
> #define GICV5_IRS_CR1_OC GENMASK(3, 2)
> #define GICV5_IRS_CR1_SH GENMASK(1, 0)
>
> +#define GICV5_IRS_SYNCR_SYNC BIT(31)
> +
> +#define GICV5_IRS_SYNC_STATUSR_IDLE BIT(0)
> +
> #define GICV5_IRS_SPI_STATUSR_V BIT(1)
> #define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
>
> @@ -135,6 +143,101 @@
>
> #define GICV5_ISTL1E_L2_ADDR_MASK GENMASK_ULL(55, 12)
>
> +#define GICV5_ITS_IDR1 0x0004
> +#define GICV5_ITS_IDR2 0x0008
> +#define GICV5_ITS_CR0 0x0080
> +#define GICV5_ITS_CR1 0x0084
> +#define GICV5_ITS_DT_BASER 0x00c0
> +#define GICV5_ITS_DT_CFGR 0x00d0
> +#define GICV5_ITS_DIDR 0x0100
> +#define GICV5_ITS_EIDR 0x0108
> +#define GICV5_ITS_INV_EVENTR 0x010c
> +#define GICV5_ITS_INV_DEVICER 0x0110
> +#define GICV5_ITS_STATUSR 0x0120
> +#define GICV5_ITS_SYNCR 0x0140
> +#define GICV5_ITS_SYNC_STATUSR 0x0148
> +
> +#define GICV5_ITS_IDR1_L2SZ GENMASK(10, 8)
> +#define GICV5_ITS_IDR1_ITT_LEVELS BIT(7)
> +#define GICV5_ITS_IDR1_DT_LEVELS BIT(6)
> +#define GICV5_ITS_IDR1_DEVICEID_BITS GENMASK(5, 0)
> +
> +#define GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(8), (r))
> +#define GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(9), (r))
> +#define GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(10), (r))
> +
> +#define GICV5_ITS_IDR2_XDMN_EVENTs GENMASK(6, 5)
> +#define GICV5_ITS_IDR2_EVENTID_BITS GENMASK(4, 0)
> +
> +#define GICV5_ITS_CR0_IDLE BIT(1)
> +#define GICV5_ITS_CR0_ITSEN BIT(0)
> +
> +#define GICV5_ITS_CR1_ITT_RA BIT(7)
> +#define GICV5_ITS_CR1_DT_RA BIT(6)
> +#define GICV5_ITS_CR1_IC GENMASK(5, 4)
> +#define GICV5_ITS_CR1_OC GENMASK(3, 2)
> +#define GICV5_ITS_CR1_SH GENMASK(1, 0)
> +
> +#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16)
> +#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6)
> +#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0)
> +
> +#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3)
> +
> +#define GICV5_ITS_INV_DEVICER_I BIT(31)
> +#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1)
> +#define GICV5_ITS_INV_DEVICER_L1 BIT(0)
> +
> +#define GICV5_ITS_DIDR_DEVICEID GENMASK_ULL(31, 0)
> +
> +#define GICV5_ITS_EIDR_EVENTID GENMASK(15, 0)
> +
> +#define GICV5_ITS_INV_EVENTR_I BIT(31)
> +#define GICV5_ITS_INV_EVENTR_ITT_L2SZ GENMASK(2, 1)
> +#define GICV5_ITS_INV_EVENTR_L1 BIT(0)
> +
> +#define GICV5_ITS_STATUSR_IDLE BIT(0)
> +
> +#define GICV5_ITS_SYNCR_SYNC BIT_ULL(63)
> +#define GICV5_ITS_SYNCR_SYNCALL BIT_ULL(32)
> +#define GICV5_ITS_SYNCR_DEVICEID GENMASK_ULL(31, 0)
> +
> +#define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0)
> +
> +#define GICV5_DTL1E_VALID BIT_ULL(0)
> +// Note that there is no shift for the address by design
> +#define GICV5_DTL1E_L2_ADDR_MASK GENMASK_ULL(55, 3)
> +#define GICV5_DTL1E_SPAN GENMASK_ULL(63, 60)
> +
> +#define GICV5_DTL2E_VALID BIT_ULL(0)
> +#define GICV5_DTL2E_ITT_L2SZ GENMASK_ULL(2, 1)
> +// Note that there is no shift for the address by design
> +#define GICV5_DTL2E_ITT_ADDR_MASK GENMASK_ULL(55, 3)
> +#define GICV5_DTL2E_ITT_DSWE BIT_ULL(57)
> +#define GICV5_DTL2E_ITT_STRUCTURE BIT_ULL(58)
> +#define GICV5_DTL2E_EVENT_ID_BITS GENMASK_ULL(63, 59)
> +
> +#define GICV5_ITTL1E_VALID BIT_ULL(0)
> +// Note that there is no shift for the address by design
> +#define GICV5_ITTL1E_L2_ADDR_MASK GENMASK_ULL(55, 3)
> +#define GICV5_ITTL1E_SPAN GENMASK_ULL(63, 60)
> +
> +#define GICV5_ITTL2E_LPI_ID GENMASK_ULL(23, 0)
> +#define GICV5_ITTL2E_DAC GENMASK_ULL(29, 28)
> +#define GICV5_ITTL2E_VIRTUAL BIT_ULL(30)
> +#define GICV5_ITTL2E_VALID BIT_ULL(31)
> +#define GICV5_ITTL2E_VM_ID GENMASK_ULL(47, 32)
> +
> +#define GICV5_ITS_DT_ITT_CFGR_L2SZ_4k 0b00
> +#define GICV5_ITS_DT_ITT_CFGR_L2SZ_16k 0b01
> +#define GICV5_ITS_DT_ITT_CFGR_L2SZ_64k 0b10
> +
> +#define GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR 0
> +#define GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL 1
> +
> +#define GICV5_ITS_HWIRQ_DEVICE_ID GENMASK_ULL(31, 0)
> +#define GICV5_ITS_HWIRQ_EVENT_ID GENMASK_ULL(63, 32)
> +
> struct gicv5_chip_data {
> struct fwnode_handle *fwnode;
> struct irq_domain *ppi_domain;
> @@ -168,17 +271,90 @@ struct gicv5_irs_chip_data {
>
> void __init gicv5_init_lpi_domain(void);
> void __init gicv5_free_lpi_domain(void);
> +static inline int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask,
> + u32 *val)
> +{
> + void __iomem *reg = addr + offset;
> + u32 tmp;
> + int ret;
> +
> + ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
> +
> + if (val)
> + *val = tmp;
> +
> + return ret;
> +}
>
> int gicv5_irs_of_probe(struct device_node *parent);
> void gicv5_irs_remove(void);
> int gicv5_irs_enable(void);
> +void gicv5_irs_its_probe(void);
> int gicv5_irs_register_cpu(int cpuid);
> int gicv5_irs_cpu_to_iaffid(int cpu_id, u16 *iaffid);
> struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id);
> int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
> int gicv5_spi_set_type(struct irq_data *d, unsigned int type);
> int gicv5_irs_iste_alloc(u32 lpi);
> +void gicv5_irs_syncr(void);
> +
> +struct gicv5_its_devtab_cfg {
> + union {
> + struct {
> + __le64 *devtab;
> + } linear;
> + struct {
> + __le64 *l1devtab;
> + __le64 **l2ptrs;
> + } l2;
> + };
> + u32 cfgr;
> +};
> +
> +struct gicv5_its_itt_cfg {
> + union {
> + struct {
> + __le64 *itt;
> + unsigned int num_ents;
> + } linear;
> + struct {
> + __le64 *l1itt;
> + __le64 **l2ptrs;
> + unsigned int num_l1_ents;
> + u8 l2sz;
> + } l2;
> + };
> + u8 event_id_bits;
> + bool l2itt;
> +};
> +
> +struct gicv5_its_chip_data {
> + struct list_head entry;
> + struct xarray its_devices;
> + struct mutex dev_alloc_lock;
> + struct fwnode_handle *fwnode;
> + struct gicv5_its_devtab_cfg devtab_cfgr;
> + struct irq_domain *domain;
> + void __iomem *its_base;
> + phys_addr_t its_trans_phys_base;
> + u32 flags;
> +};
> +
> +struct gicv5_its_dev {
> + struct gicv5_its_chip_data *its_node;
> + struct gicv5_its_itt_cfg itt_cfg;
> + unsigned long *event_map;
> + u32 device_id;
> + u32 num_events;
> + u32 num_mapped_events;
> + bool shared;
> +};
>
> void gicv5_init_lpis(u32 max);
> void gicv5_deinit_lpis(void);
> +
> +int gicv5_alloc_lpi(void);
> +void gicv5_free_lpi(u32 lpi);
> +
> +void __init gicv5_its_of_probe(struct device_node *parent);
> #endif
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-04-24 10:25 ` [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support Lorenzo Pieralisi
@ 2025-04-30 11:57 ` Marc Zyngier
2025-04-30 13:27 ` Lorenzo Pieralisi
2025-04-30 16:25 ` Lorenzo Pieralisi
0 siblings, 2 replies; 43+ messages in thread
From: Marc Zyngier @ 2025-04-30 11:57 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Thu, 24 Apr 2025 11:25:32 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> The GICv5 architecture implements the Interrupt Wire Bridge (IWB) in
> order to support wired interrupts that cannot be connected directly
> to an IRS and instead uses the ITS to translate a wire event into
> an IRQ signal.
>
> An IWB is a special ITS device with its own deviceID; upon probe,
> an IWB calls into the ITS driver to allocate DT/ITT tables for its
> events (ie wires).
>
> An IWB is always associated with a single ITS in the system.
>
> An IWB is connected to an ITS and it has its own deviceID for all
> interrupt wires that it manages; the IWB input wire number is
> exposed to the ITS as an eventID. This eventID is not programmable
> and therefore requires special handling in the ITS driver.
>
> Add an IWB driver in order to:
>
> - Probe IWBs in the system and allocate ITS tables
> - Manage IWB IRQ domains
> - Handle IWB input wires state (enable/disable)
> - Add the required IWB IRQchip representation
> - Handle firmware representation to Linux IRQ translation
>
> Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> Cc: Thomas Gleixner <tglx@linutronix.de>
> Cc: Marc Zyngier <maz@kernel.org>
> ---
> drivers/irqchip/Makefile | 2 +-
> drivers/irqchip/irq-gic-v5-its.c | 68 ++++++--
> drivers/irqchip/irq-gic-v5-iwb.c | 356 +++++++++++++++++++++++++++++++++++++++
> drivers/irqchip/irq-gic-v5.c | 2 +
> drivers/irqchip/irq-gic-v5.h | 28 +++
> 5 files changed, 437 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 4280395e3bdff7858102f0b4eaaea1121cace52f..7bfb2369fbe494a64b72308d95ae33de93c6b8c6 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -37,7 +37,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
> obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> -obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
> +obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o irq-gic-v5-iwb.o
> obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> obj-$(CONFIG_ARM_VIC) += irq-vic.o
> diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> index da349b4709cc5ec8978859237838f039389ca4a1..b5eb4dbfe2296dc6620889eb9291b542cae4aeb6 100644
> --- a/drivers/irqchip/irq-gic-v5-its.c
> +++ b/drivers/irqchip/irq-gic-v5-its.c
> @@ -786,9 +786,8 @@ static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *i
> return dev ? dev : ERR_PTR(-ENODEV);
> }
>
> -static struct gicv5_its_dev *gicv5_its_alloc_device(
> - struct gicv5_its_chip_data *its, int nvec,
> - u32 dev_id)
> +struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
> + int nvec, u32 dev_id, bool is_iwb)
> {
> struct gicv5_its_dev *its_dev;
> int ret;
> @@ -815,6 +814,7 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> its_dev->device_id = dev_id;
> its_dev->num_events = nvec;
> its_dev->num_mapped_events = 0;
> + its_dev->is_iwb = is_iwb;
>
> ret = gicv5_its_device_register(its, its_dev);
> if (ret) {
> @@ -827,9 +827,11 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
>
> /*
> * This is the first time we have seen this device. Hence, it is not
> - * shared.
> + * shared, unless it is an IWB that is a shared ITS device by
> + * definition, its eventids are hardcoded and never change - we allocate
> + * it once for all and never free it.
I'm not convinced the IWB should be treated differently from any other
device. Its lifetime is not tied to its inputs, so all that's needed
is to probe it, get a bunch of interrupts, and that's about it.
The other thing is that the IWB really is a standalone thing. It
shouldn't have its fingers in the ITS code, and should only rely on
the core infrastructure to get its interrupts.
As much as I dislike it, the MBIGEN actually provides a decent example
of how this could be structured.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support
2025-04-30 7:28 ` Jiri Slaby
@ 2025-04-30 12:55 ` Lorenzo Pieralisi
0 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-30 12:55 UTC (permalink / raw)
To: Jiri Slaby
Cc: Marc Zyngier, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Catalin Marinas, Will Deacon, Arnd Bergmann,
Sascha Bischoff, Timothy Hayes, Liam R. Howlett, Mark Rutland,
linux-arm-kernel, linux-kernel, devicetree
On Wed, Apr 30, 2025 at 09:28:08AM +0200, Jiri Slaby wrote:
> On 24. 04. 25, 12:25, Lorenzo Pieralisi wrote:
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-gic-v5-its.c
> > @@ -0,0 +1,1293 @@
> ...
> > +static u32 its_readl_relaxed(struct gicv5_its_chip_data *its_node,
> > + const u64 reg_offset)
>
> I wonder -- can the offset be u64 at all?
You have a point, changed it.
> > +{
> > + return readl_relaxed(its_node->its_base + reg_offset);
> > +}
> > +
> > +static void its_writel_relaxed(struct gicv5_its_chip_data *its_node,
> > + const u32 val, const u64 reg_offset)
> > +{
> > + writel_relaxed(val, its_node->its_base + reg_offset);
> > +}
> > +
> > +static void its_writeq_relaxed(struct gicv5_its_chip_data *its_node,
> > + const u64 val, const u64 reg_offset)
> > +{
> > + writeq_relaxed(val, its_node->its_base + reg_offset);
> > +}
> > +
> > +static void its_write_table_entry(struct gicv5_its_chip_data *its,
> > + __le64 *entry, u64 val)
> > +{
> > + WRITE_ONCE(*entry, val);
>
> This triggers a warning with the le/be checker enabled, right? You likely
> need cpu_to_le64() or __force.
Fixed, thanks.
> > + if (its->flags & ITS_FLAGS_NON_COHERENT)
> > + dcache_clean_inval_poc((unsigned long)entry,
> > + (unsigned long)entry + sizeof(*entry));
> > + else
> > + dsb(ishst);
> > +}
> > +
> > +#define gicv5_its_wait_for_op(base, reg, mask) \
>
> What's the purpose of this not being an inline?
>
> > + ({ \
> > + int ret; \
> > + \
> > + ret = gicv5_wait_for_op(base, reg, mask, NULL); \
> > + if (unlikely(ret == -ETIMEDOUT)) \
> > + pr_err_ratelimited(#reg" timeout...\n"); \
>
> Ah, this. Is it worth it? At least you should not clobber variables like
> "ret". Also grepping sources for "GICV5_ITS_STATUSR timeout..." would be
> clueless anyway. Yeah, at least there would be a driver prefix.
Is it worth it ? It depends on whom you ask.
> > + ret; \
> > + })
> ...
> > +static int gicv5_its_device_get_itte_ref(struct gicv5_its_dev *its_dev,
> > + __le64 **itte, u16 event_id)
> > +{
> > + if (!its_dev->itt_cfg.l2itt) {
> > + __le64 *itt = its_dev->itt_cfg.linear.itt;
> > + *itte = &itt[event_id];
>
> Can you return 0 here and dedent the whole } else { block?
Yep, good point.
> > + } else {
> > + __le64 *l2_itt, *l1_itt = its_dev->itt_cfg.l2.l1itt;
> > + unsigned int l1_idx, l2_idx, l2_size, l2_bits;
> > + int ret;
> > +
> > + ret = gicv5_its_l2sz_to_l2_bits(its_dev->itt_cfg.l2.l2sz);
> > + if (ret < 0)
> > + return ret;
> > + l2_bits = ret;
> > +
> > + l1_idx = event_id >> l2_bits;
> > +
> > + if (!FIELD_GET(GICV5_ITTL1E_VALID,
> > + le64_to_cpu(l1_itt[l1_idx]))) {
> > + pr_debug("L1 ITT entry is not valid.\n");
> > + return -EINVAL;
> > + }
> > +
> > + l2_idx = event_id & GENMASK(l2_bits - 1, 0);
> > +
> > + l2_size = BIT(FIELD_GET(GICV5_ITTL1E_SPAN,
> > + le64_to_cpu(l1_itt[l1_idx])));
> > +
> > + // Sanity check our indexing
> > + if (l2_idx >= l2_size) {
> > + pr_debug("L2 ITT index (%u) exceeds L2 table size (%u)!\n",
> > + l2_idx, l2_size);
> > + return -EINVAL;
> > + }
> > + l2_itt = its_dev->itt_cfg.l2.l2ptrs[l1_idx];
> > + *itte = &l2_itt[l2_idx];
> > + }
> > +
> > + return 0;
> > +}
>
> ...
> > +static struct gicv5_its_dev *gicv5_its_alloc_device(
> > + struct gicv5_its_chip_data *its, int nvec,
> > + u32 dev_id)
> > +{
> > + struct gicv5_its_dev *its_dev;
> > + int ret;
> > +
> > + its_dev = gicv5_its_find_device(its, dev_id);
> > + if (!IS_ERR(its_dev)) {
> > + pr_debug("A device with this DeviceID (0x%x) has already been registered.\n",
> > + dev_id);
> > +
> > + if (nvec > its_dev->num_events) {
> > + pr_debug("Requesting more ITT entries than allocated\n");
>
> Why only _debug()?
This code path will go anyway but thanks for chiming in.
> > + return ERR_PTR(-ENXIO);
> > + }
> > +
> > + its_dev->shared = true;
> > +
> > + return its_dev;
> > + }
> > +
> > + its_dev = kzalloc(sizeof(*its_dev), GFP_KERNEL);
> > + if (!its_dev)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + its_dev->device_id = dev_id;
> > + its_dev->num_events = nvec;
> > + its_dev->num_mapped_events = 0;
> > +
> > + ret = gicv5_its_device_register(its, its_dev);
> > + if (ret) {
> > + pr_debug("Failed to register the device\n");
>
> And here.
Yep, will check.
> > + kfree(its_dev);
>
> Can you use __free() and return_ptr() instead?
I tried but I was not sure it helps in this particular case, I will
check again.
> > + return ERR_PTR(ret);
> > + }
> > +
> > + gicv5_its_device_cache_inv(its, its_dev);
> > +
> > + /*
> > + * This is the first time we have seen this device. Hence, it is not
> > + * shared.
> > + */
> > + its_dev->shared = false;
> > +
> > + its_dev->its_node = its;
> > +
> > + its_dev->event_map =
> > + (unsigned long *)bitmap_zalloc(its_dev->num_events, GFP_KERNEL);
> > + if (!its_dev->event_map) {
> > + gicv5_its_device_unregister(its, its_dev);
> > + kfree(its_dev);
> > + return ERR_PTR(-ENOMEM);
> > + }
> > +
> > + xa_store(&its->its_devices, dev_id, its_dev, GFP_KERNEL);
>
> This can fail.
Noted.
> > +
> > + return its_dev;
> > +}
>
> ...
> > +static int gicv5_its_alloc_event(struct gicv5_its_dev *its_dev, u16 event_id,
> > + u32 lpi)
> > +{
> > + struct gicv5_its_chip_data *its = its_dev->its_node;
> > + u64 itt_entry;
> > + __le64 *itte;
> > + int ret;
> > +
> > + if (event_id >= its_dev->num_events) {
> > + pr_debug("EventID 0x%x outside of ITT range (0x%x)\n", event_id,
> > + its_dev->num_events);
>
> Again, is this so often to be _debug()?
I think this is a paranoia check and I will probably remove it.
> > + return -EINVAL;
> > + }
> > +
> > + if (WARN(its_dev->num_mapped_events == its_dev->num_events,
> > + "Reached maximum number of events\n"))
>
> Weird indent level.
Right.
>
> > + return -EINVAL;
> > +
> > + ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
> > + if (ret)
> > + return ret;
> > +
> > + if (FIELD_GET(GICV5_ITTL2E_VALID, *itte))
> > + return -EEXIST;
> > +
> > + itt_entry = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) |
> > + FIELD_PREP(GICV5_ITTL2E_VALID, 0x1);
> > +
> > + its_write_table_entry(its, itte, cpu_to_le64(itt_entry));
> > +
> > + gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
> > +
> > + its_dev->num_mapped_events += 1;
>
> This is not python, we have ++ :).
Ok.
> > +
> > + return 0;
> > +}
> > +
> > +static void gicv5_its_free_event(struct gicv5_its_dev *its_dev, u16 event_id)
> > +{
> > + struct gicv5_its_chip_data *its = its_dev->its_node;
> > + u64 itte_val;
> > + __le64 *itte;
> > + int ret;
> > +
> > + if (WARN(!its_dev->num_mapped_events, "No mapped events\n"))
> > + return;
> > +
> > + ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
> > + if (ret) {
> > + pr_debug("Failed to get the ITTE!\n");
> > + return;
> > + }
> > +
> > + itte_val = le64_to_cpu(*itte);
> > + itte_val &= ~GICV5_ITTL2E_VALID;
> > +
> > + its_write_table_entry(its, itte, cpu_to_le64(itte_val));
> > +
> > + gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
> > +
> > + its_dev->num_mapped_events -= 1;
>
> And --.
Ok.
> > +}
> > +
> > +static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev,
> > + unsigned int nr_irqs, u32 *eventid)
> > +{
> > + int ret;
> > +
> > + ret = bitmap_find_free_region(its_dev->event_map,
> > + its_dev->num_events,
> > + get_count_order(nr_irqs));
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + *eventid = ret;
> > +
> > + return 0;
> > +}
> ...
> > +static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> > + unsigned int nr_irqs, void *arg)
> > +{
> > + u32 device_id, event_id_base, lpi;
> > + struct msi_domain_info *msi_info;
> > + struct gicv5_its_chip_data *its;
> > + struct gicv5_its_dev *its_dev;
> > + msi_alloc_info_t *info = arg;
> > + irq_hw_number_t hwirq;
> > + struct irq_data *irqd;
> > + int ret, i;
>
> Why is i not unsigned too?
It could.
> > +
> > + device_id = info->scratchpad[0].ul;
> > +
> > + msi_info = msi_get_domain_info(domain);
> > + its = msi_info->data;
> > +
> > + mutex_lock(&its->dev_alloc_lock);
> > +
> > + its_dev = gicv5_its_find_device(its, device_id);
> > + if (IS_ERR(its_dev)) {
> > + mutex_unlock(&its->dev_alloc_lock);
>
> scope_guard() would make much sense here.
I did not add it because I still use gotos below and mixing them is
frowned upon, let me see how I can rework it.
> > + return PTR_ERR(its_dev);
> > + }
> > +
> > + ret = gicv5_its_alloc_eventid(its_dev, nr_irqs, &event_id_base);
> > + if (ret) {
> > + mutex_unlock(&its->dev_alloc_lock);
> > + return ret;
> > + }
> > +
> > + mutex_unlock(&its->dev_alloc_lock);
> > +
> > + ret = iommu_dma_prepare_msi(info->desc, its->its_trans_phys_base);
> > + if (ret)
> > + goto out_eventid;
> > +
> > + for (i = 0; i < nr_irqs; i++) {
> > + lpi = gicv5_alloc_lpi();
> > + if (ret < 0) {
> > + pr_debug("Failed to find free LPI!\n");
> > + goto out_eventid;
> > + }
> > +
> > + ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
> > + if (ret)
> > + goto out_free_lpi;
> > +
> > + /*
> > + * Store eventid and deviceid into the hwirq for later use.
> > + *
> > + * hwirq = event_id << 32 | device_id
> > + */
> > + hwirq = FIELD_PREP(GICV5_ITS_HWIRQ_DEVICE_ID, device_id) |
> > + FIELD_PREP(GICV5_ITS_HWIRQ_EVENT_ID, (u64)event_id_base + i);
> > + irq_domain_set_info(domain, virq + i, hwirq,
> > + &gicv5_its_irq_chip, its_dev,
> > + handle_fasteoi_irq, NULL, NULL);
> > +
> > + irqd = irq_get_irq_data(virq + i);
> > + irqd_set_single_target(irqd);
> > + irqd_set_affinity_on_activate(irqd);
> > + irqd_set_resend_when_in_progress(irqd);
> > + }
> > +
> > + return 0;
> > +out_free_lpi:
> > + gicv5_free_lpi(lpi);
> > +out_eventid:
> > + gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs);
> > +
> > + return ret;
> > +}
> > +
> > +static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
> > + unsigned int nr_irqs)
> > +{
> > + struct msi_domain_info *msi_info;
> > + struct gicv5_its_chip_data *its;
> > + struct gicv5_its_dev *its_dev;
> > + struct irq_data *d;
> > + u16 event_id_base;
> > + bool free_device;
> > + u32 device_id;
> > + int i;
> > +
> > + msi_info = msi_get_domain_info(domain);
> > + its = msi_info->data;
> > +
> > + d = irq_domain_get_irq_data(domain, virq);
> > + device_id = FIELD_GET(GICV5_ITS_HWIRQ_DEVICE_ID, d->hwirq);
> > + event_id_base = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> > +
> > + guard(mutex)(&its->dev_alloc_lock);
> > +
> > + its_dev = gicv5_its_find_device(its, device_id);
> > + if (IS_ERR(its_dev)) {
> > + pr_debug("Couldn't find the ITS device!\n");
>
> This is serious, not debug, IMO. Either we leak memory or even allow out of
> bounds accesses somewhere.
This again is serious but should really never happen either (well,
if there are bugs that can't happen).
Let me check this path again.
> > + return;
> > + }
> > +
> > + bitmap_release_region(its_dev->event_map, event_id_base,
> > + get_count_order(nr_irqs));
> > +
> > + free_device = !its_dev->shared && bitmap_empty(its_dev->event_map,
> > + its_dev->num_events);
> > +
> > + /* Hierarchically free irq data */
> > + for (i = 0; i < nr_irqs; i++) {
> > + d = irq_domain_get_irq_data(domain, virq + i);
> > +
> > + gicv5_free_lpi(d->parent_data->hwirq);
> > + irq_domain_reset_irq_data(d);
> > + }
> > + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> > +
> > + gicv5_its_syncr(its, its_dev);
> > + gicv5_irs_syncr();
> > +
> > + if (free_device) {
> > + gicv5_its_device_unregister(its, its_dev);
> > + bitmap_free(its_dev->event_map);
> > + xa_erase(&its->its_devices, device_id);
> > + kfree(its_dev);
> > + }
> > +}
>
> ...
> > +static int __init gicv5_its_init_bases(phys_addr_t its_trans_base,
> > + void __iomem *its_base,
> > + struct fwnode_handle *handle,
> > + struct irq_domain *parent_domain)
> > +{
> > + struct device_node *np = to_of_node(handle);
> > + struct gicv5_its_chip_data *its_node;
> > + struct msi_domain_info *info;
> > + struct irq_domain *d;
> > + u32 cr0, cr1;
> > + bool enabled;
> > + int ret;
> > +
> > + info = kzalloc(sizeof(*info), GFP_KERNEL);
> > + if (!info)
> > + return -ENOMEM;
> > +
> > + its_node = kzalloc(sizeof(*its_node), GFP_KERNEL);
> > + if (!its_node) {
> > + kfree(info);
> > + return -ENOMEM;
> > + }
> > +
> > + info->ops = &its_msi_domain_ops;
> > + info->data = its_node;
> > +
> > + mutex_init(&its_node->dev_alloc_lock);
> > + xa_init(&its_node->its_devices);
> > + its_node->fwnode = handle;
> > + its_node->its_base = its_base;
> > + its_node->its_trans_phys_base = its_trans_base;
> > +
> > + d = irq_domain_create_hierarchy(parent_domain, IRQ_DOMAIN_FLAG_ISOLATED_MSI,
> > + 0, handle, &gicv5_its_irq_domain_ops, info);
> > + its_node->domain = d;
> > + irq_domain_update_bus_token(its_node->domain, DOMAIN_BUS_NEXUS);
> > +
> > + its_node->domain->msi_parent_ops = &gic_its_msi_parent_ops;
> > + its_node->domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
> > +
> > + cr0 = its_readl_relaxed(its_node, GICV5_ITS_CR0);
> > + enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, cr0);
> > + if (WARN(enabled, "ITS %s enabled, disabling it before proceeding\n",
> > + np->full_name)) {
> > + cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x0);
> > + its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
> > + ret = gicv5_its_wait_for_cr0(its_node);
> > + if (ret) {
> > + irq_domain_remove(its_node->domain);
> > + kfree(info);
> > + kfree(its_node);
> > + return ret;
> > + }
> > + }
> > +
> > + if (of_property_read_bool(np, "dma-noncoherent")) {
> > + /*
> > + * A non-coherent ITS implies that some cache levels cannot be
> > + * used coherently by the cores and GIC. Our only option is to mark
> > + * memory attributes for the GIC as non-cacheable; by default,
> > + * non-cacheable memory attributes imply outer-shareable
> > + * shareability, the value written into ITS_CR1_SH is ignored.
> > + */
> > + cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_NON_CACHE) |
> > + FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_NON_CACHE);
> > + its_node->flags |= ITS_FLAGS_NON_COHERENT;
> > + } else {
> > + cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_WB_CACHE) |
> > + FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_WB_CACHE) |
> > + FIELD_PREP(GICV5_ITS_CR1_SH, GICV5_INNER_SHARE);
> > + }
> > +
> > + its_writel_relaxed(its_node, cr1, GICV5_ITS_CR1);
> > +
> > + ret = gicv5_its_init_devtab(its_node);
> > + if (ret) {
> > + irq_domain_remove(its_node->domain);
> > + kfree(info);
> > + kfree(its_node);
> > + return ret;
> > + }
> > +
> > + cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x1);
> > + its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
> > +
> > + ret = gicv5_its_wait_for_cr0(its_node);
> > + if (ret) {
> > + irq_domain_remove(its_node->domain);
> > + kfree(info);
> > + kfree(its_node);
>
> Either convert to cleanup.h or do this in a common error label(s).
Sure.
> > + return ret;
> > + }
> > +
> > + list_add(&its_node->entry, &its_nodes);
> > +
> > + gicv5_its_print_info(its_node);
> > +
> > + return 0;
> > +}
> ...
>
> > diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> > index c4d4e85382f672fa4ae334db1a4e4c7c4f46b9fe..e483d0774936035b5cf2407da9a65d776bad3138 100644
> > --- a/drivers/irqchip/irq-gic-v5.c
> > +++ b/drivers/irqchip/irq-gic-v5.c
> ...
> > @@ -168,17 +271,90 @@ struct gicv5_irs_chip_data {
> > void __init gicv5_init_lpi_domain(void);
> > void __init gicv5_free_lpi_domain(void);
> > +static inline int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask,
> > + u32 *val)
> > +{
> > + void __iomem *reg = addr + offset;
> > + u32 tmp;
> > + int ret;
> > +
> > + ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
>
> Does this have to be atomic? The call chain is complex, I haven't managed to
> check...
I will check again.
> > +
> > + if (val)
> > + *val = tmp;
>
> Do you really want to write val in case of timeout? Sounds unexpected.
Yep, on timeout I should not write back the return value (well, it
does not hurt either to be honest).
> > + return ret;
> > +}
>
> thanks,
> --
> js
> suse labs
Thank you very much for having a look.
Lorenzo
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support
2025-04-30 9:12 ` Marc Zyngier
@ 2025-04-30 13:21 ` Lorenzo Pieralisi
2025-05-01 9:01 ` Marc Zyngier
0 siblings, 1 reply; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-30 13:21 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Wed, Apr 30, 2025 at 10:12:58AM +0100, Marc Zyngier wrote:
> On Thu, 24 Apr 2025 11:25:31 +0100,
> Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> >
> > The GICv5 architecture implements Interrupt Translation Service
> > (ITS) components in order to translate events coming from peripherals
> > into interrupt events delivered to the connected IRSes.
> >
> > Events (ie MSI memory writes to ITS translate frame), are translated
> > by the ITS using tables kept in memory.
> >
> > ITS translation tables for peripherals is kept in memory storage
> > (device table [DT] and Interrupt Translation Table [ITT]) that
> > is allocated by the driver on boot.
> >
> > Both tables can be 1- or 2-level; the structure is chosen by the
> > driver after probing the ITS HW parameters and checking the
> > allowed table splits and supported {device/event}_IDbits.
> >
> > DT table entries are allocated on demand (ie when a device is
> > probed); the DT table is sized using the number of supported
> > deviceID bits in that that's a system design decision (ie the
> > number of deviceID bits implemented should reflect the number
> > of devices expected in a system) therefore it makes sense to
> > allocate a DT table that can cater for the maximum number of
> > devices.
> >
> > DT and ITT tables are allocated using the kmalloc interface;
> > the allocation size may be smaller than a page or larger,
> > and must provide contiguous memory pages.
> >
> > LPIs INTIDs backing the device events are allocated one-by-one
> > and only upon Linux IRQ allocation; this to avoid preallocating
> > a large number of LPIs to cover the HW device MSI vector
> > size whereas few MSI entries are actually enabled by a device.
> >
> > ITS cacheability/shareability attributes are programmed
> > according to the provided firmware ITS description.
> >
> > The GICv5 ITS reuses the GICv3 MSI parent infrastructure,
> > there is no need to duplicate it, make it common.
> >
> > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > Cc: Thomas Gleixner <tglx@linutronix.de>
> > Cc: Marc Zyngier <maz@kernel.org>
> > ---
> > MAINTAINERS | 1 +
> > drivers/irqchip/Kconfig | 11 +
> > drivers/irqchip/Makefile | 4 +-
> > drivers/irqchip/irq-gic-common.h | 2 -
> > ...3-its-msi-parent.c => irq-gic-its-msi-parent.c} | 3 +-
> > drivers/irqchip/irq-gic-its-msi-parent.h | 13 +
> > drivers/irqchip/irq-gic-v3-its.c | 3 +-
> > drivers/irqchip/irq-gic-v5-irs.c | 40 +-
> > drivers/irqchip/irq-gic-v5-its.c | 1293 ++++++++++++++++++++
> > drivers/irqchip/irq-gic-v5.c | 6 +-
> > drivers/irqchip/irq-gic-v5.h | 176 +++
> > 11 files changed, 1529 insertions(+), 23 deletions(-)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index cdeceb6782355a4a18609135bf7f03249d8b0bb5..d231077c024deba42153663ac66b6c05f7673f03 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -1908,6 +1908,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> > S: Maintained
> > F: Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
> > F: arch/arm64/include/asm/arch_gicv5.h
> > +F: drivers/irqchip/irq-gic-its-msi-parent.[ch]
> > F: drivers/irqchip/irq-gic-v5*.[ch]
> >
> > ARM HDLCD DRM DRIVER
> > diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> > index 160a4761d5d85f6dbf36f3142fd619c114733e36..6c348d421b05af0e4f4909877e02ac8ef19178ff 100644
> > --- a/drivers/irqchip/Kconfig
> > +++ b/drivers/irqchip/Kconfig
> > @@ -41,10 +41,14 @@ config ARM_GIC_V3
> > select HAVE_ARM_SMCCC_DISCOVERY
> > select IRQ_MSI_IOMMU
> >
> > +config ARM_GIC_ITS_PARENT
> > + bool
> > +
> > config ARM_GIC_V3_ITS
> > bool
> > select GENERIC_MSI_IRQ
> > select IRQ_MSI_LIB
> > + select ARM_GIC_ITS_PARENT
> > default ARM_GIC_V3
> > select IRQ_MSI_IOMMU
> >
> > @@ -59,6 +63,13 @@ config ARM_GIC_V5
> > select IRQ_DOMAIN_HIERARCHY
> > select GENERIC_IRQ_EFFECTIVE_AFF_MASK if SMP
> >
> > +config ARM_GIC_V5_ITS
> > + bool
> > + select GENERIC_MSI_IRQ
> > + select IRQ_MSI_LIB
> > + select ARM_GIC_ITS_PARENT
> > + default ARM_GIC_V5
> > +
>
> I don't think you should be mimicking GICv3 here. It was never
> possible to not compile the ITS code anyway, and you are better off
> just having one config symbol that drags the whole thing.
Changed it.
[...]
> > diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h
> > index 020ecdf16901c9720e5746aec4d0b5b39d3625ed..710cab61d9195a0bd64d57e03c60852c4cd6ff8e 100644
> > --- a/drivers/irqchip/irq-gic-common.h
> > +++ b/drivers/irqchip/irq-gic-common.h
> > @@ -29,8 +29,6 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
> > void gic_enable_of_quirks(const struct device_node *np,
> > const struct gic_quirk *quirks, void *data);
> >
> > -extern const struct msi_parent_ops gic_v3_its_msi_parent_ops;
> > -
> > #define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0)
> > #define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1)
> > #define RDIST_FLAGS_FORCE_NON_SHAREABLE (1 << 2)
> > diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c
> > similarity index 98%
> > rename from drivers/irqchip/irq-gic-v3-its-msi-parent.c
> > rename to drivers/irqchip/irq-gic-its-msi-parent.c
> > index bdb04c8081480de468fb217b68c6933a8e1e2bd7..71edcdb2defdfd5b892d86354039d2e46b832ea5 100644
> > --- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c
> > +++ b/drivers/irqchip/irq-gic-its-msi-parent.c
> > @@ -7,7 +7,6 @@
> > #include <linux/acpi_iort.h>
> > #include <linux/pci.h>
> >
> > -#include "irq-gic-common.h"
> > #include "irq-msi-lib.h"
> >
> > #define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
> > @@ -200,7 +199,7 @@ static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
> > return true;
> > }
> >
> > -const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
> > +const struct msi_parent_ops gic_its_msi_parent_ops = {
> > .supported_flags = ITS_MSI_FLAGS_SUPPORTED,
> > .required_flags = ITS_MSI_FLAGS_REQUIRED,
> > .chip_flags = MSI_CHIP_FLAG_SET_EOI | MSI_CHIP_FLAG_SET_ACK,
> > diff --git a/drivers/irqchip/irq-gic-its-msi-parent.h b/drivers/irqchip/irq-gic-its-msi-parent.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..e7bb7f3862eef379e5b85fe7bd5eb72f3586d3b7
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-gic-its-msi-parent.h
> > @@ -0,0 +1,13 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Copyright (C) 2024 ARM Limited, All Rights Reserved.
> > + */
> > +
> > +#ifndef _IRQ_GIC_ITS_MSI_PARENT_H
> > +#define _IRQ_GIC_ITS_MSI_PARENT_H
> > +
> > +#include <linux/msi.h>
> > +
> > +extern const struct msi_parent_ops gic_its_msi_parent_ops;
> > +
> > +#endif /* _IRQ_GIC_ITS_MSI_PARENT_H */
> > diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
> > index 0115ad6c82593de511c285d99437996919bfa308..6c51bf4e34a38103d612c74476d640cd4126e8b6 100644
> > --- a/drivers/irqchip/irq-gic-v3-its.c
> > +++ b/drivers/irqchip/irq-gic-v3-its.c
> > @@ -41,6 +41,7 @@
> > #include <asm/exception.h>
> >
> > #include "irq-gic-common.h"
> > +#include "irq-gic-its-msi-parent.h"
> > #include "irq-msi-lib.h"
> >
> > #define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0)
> > @@ -5139,7 +5140,7 @@ static int its_init_domain(struct its_node *its)
> >
> > irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);
> >
> > - inner_domain->msi_parent_ops = &gic_v3_its_msi_parent_ops;
> > + inner_domain->msi_parent_ops = &gic_its_msi_parent_ops;
> > inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
> >
> > return 0;
> > diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
> > index 7bd60e6d56b77c0c19a1bd9bee9685d9b6ffc959..ff9de8fe175f511b2e81f712fa2e69b96f3e66fb 100644
> > --- a/drivers/irqchip/irq-gic-v5-irs.c
> > +++ b/drivers/irqchip/irq-gic-v5-irs.c
> > @@ -5,7 +5,6 @@
> >
> > #define pr_fmt(fmt) "GICv5 IRS: " fmt
> >
> > -#include <linux/iopoll.h>
> > #include <linux/irqchip.h>
> > #include <linux/log2.h>
> > #include <linux/of.h>
> > @@ -44,20 +43,6 @@ static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
> > writeq_relaxed(val, irs_data->irs_base + reg_offset);
> > }
> >
> > -static int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask, u32 *val)
> > -{
> > - void __iomem *reg = addr + offset;
> > - u32 tmp;
> > - int ret;
> > -
> > - ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
> > -
> > - if (val)
> > - *val = tmp;
> > -
> > - return ret;
> > -}
> > -
>
> Since you are moving this helper into the include file, put it there
> from the beginning and avoid spurious code movement.
Ok.
> > #define gicv5_irs_wait_for_op(base, reg, mask) \
> > ({ \
> > int ret; \
> > @@ -528,6 +513,23 @@ static int gicv5_irs_wait_for_idle(struct gicv5_irs_chip_data *irs_data)
> > GICV5_IRS_CR0_IDLE);
> > }
> >
> > +void gicv5_irs_syncr(void)
> > +{
> > + struct gicv5_irs_chip_data *irs_data;
> > + u32 syncr;
> > +
> > + irs_data = list_first_entry_or_null(&irs_nodes,
> > + struct gicv5_irs_chip_data, entry);
> > + if (WARN_ON(!irs_data))
> > + return;
> > +
> > + syncr = FIELD_PREP(GICV5_IRS_SYNCR_SYNC, 1);
> > + irs_writel_relaxed(irs_data, syncr, GICV5_IRS_SYNCR);
> > +
> > + gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_SYNC_STATUSR,
> > + GICV5_IRS_SYNC_STATUSR_IDLE);
> > +}
> > +
>
> Only the ITS code is using this function. Why isn't it in the ITS code
> as a static helper?
I'd need to make irs_nodes global.
> > int gicv5_irs_register_cpu(int cpuid)
> > {
> > struct gicv5_irs_chip_data *irs_data;
> > @@ -823,6 +825,14 @@ int __init gicv5_irs_enable(void)
> > return 0;
> > }
> >
> > +void __init gicv5_irs_its_probe(void)
> > +{
> > + struct gicv5_irs_chip_data *irs_data;
> > +
> > + list_for_each_entry(irs_data, &irs_nodes, entry)
> > + gicv5_its_of_probe(to_of_node(irs_data->fwnode));
> > +}
> > +
> > int __init gicv5_irs_of_probe(struct device_node *parent)
> > {
> > struct device_node *np;
> > diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..da349b4709cc5ec8978859237838f039389ca4a1
> > --- /dev/null
> > +++ b/drivers/irqchip/irq-gic-v5-its.c
> > @@ -0,0 +1,1293 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2024-2025 ARM Limited, All Rights Reserved.
> > + */
> > +
> > +#define pr_fmt(fmt) "GICv5 ITS: " fmt
> > +
> > +#include <linux/bitmap.h>
> > +#include <linux/iommu.h>
> > +#include <linux/init.h>
> > +#include <linux/irqchip.h>
> > +#include <linux/kernel.h>
> > +#include <linux/list.h>
> > +#include <linux/msi.h>
> > +
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/slab.h>
> > +
> > +#include "irq-gic-v5.h"
> > +#include "irq-gic-its-msi-parent.h"
> > +#include "irq-msi-lib.h"
> > +
> > +#define ITS_FLAGS_NON_COHERENT BIT(0)
> > +
> > +static LIST_HEAD(its_nodes);
> > +
> > +static u32 its_readl_relaxed(struct gicv5_its_chip_data *its_node,
> > + const u64 reg_offset)
> > +{
> > + return readl_relaxed(its_node->its_base + reg_offset);
> > +}
> > +
> > +static void its_writel_relaxed(struct gicv5_its_chip_data *its_node,
> > + const u32 val, const u64 reg_offset)
> > +{
> > + writel_relaxed(val, its_node->its_base + reg_offset);
> > +}
> > +
> > +static void its_writeq_relaxed(struct gicv5_its_chip_data *its_node,
> > + const u64 val, const u64 reg_offset)
> > +{
> > + writeq_relaxed(val, its_node->its_base + reg_offset);
> > +}
> > +
> > +static void its_write_table_entry(struct gicv5_its_chip_data *its,
> > + __le64 *entry, u64 val)
> > +{
> > + WRITE_ONCE(*entry, val);
>
> Do yourself a favour and move all the cpu_to_le64() nested calls
> here. At least that will make the function signature correct, and
> sparse won't be shouting at you.
Done.
>
> > + if (its->flags & ITS_FLAGS_NON_COHERENT)
> > + dcache_clean_inval_poc((unsigned long)entry,
> > + (unsigned long)entry + sizeof(*entry));
> > + else
> > + dsb(ishst);
> > +}
> > +
> > +#define gicv5_its_wait_for_op(base, reg, mask) \
>
> Make this live up to its name and take an its as the first
> parameter. The helper can happily to and get its_base.
Done.
>
> > + ({ \
> > + int ret; \
> > + \
> > + ret = gicv5_wait_for_op(base, reg, mask, NULL); \
> > + if (unlikely(ret == -ETIMEDOUT)) \
> > + pr_err_ratelimited(#reg" timeout...\n"); \
> > + ret; \
> > + })
> > +
> > +static int gicv5_its_wait_for_invalidation(struct gicv5_its_chip_data *its)
> > +{
> > + return gicv5_its_wait_for_op(its->its_base, GICV5_ITS_STATUSR,
> > + GICV5_ITS_STATUSR_IDLE);
> > +}
> > +
> > +static void gicv5_its_syncr(struct gicv5_its_chip_data *its,
> > + struct gicv5_its_dev *its_dev)
> > +{
> > + u64 syncr;
> > +
> > + syncr = FIELD_PREP(GICV5_ITS_SYNCR_SYNC, 1) |
> > + FIELD_PREP(GICV5_ITS_SYNCR_DEVICEID, its_dev->device_id);
> > +
> > + its_writeq_relaxed(its, syncr, GICV5_ITS_SYNCR);
> > +
> > + gicv5_its_wait_for_op(its->its_base, GICV5_ITS_SYNC_STATUSR,
> > + GICV5_ITS_SYNC_STATUSR_IDLE);
> > +}
> > +
> > +static int gicv5_its_l2sz_to_l2_bits(unsigned int sz)
> > +{
> > + switch (sz) {
> > + case GICV5_ITS_DT_ITT_CFGR_L2SZ_4k:
> > + return 9;
> > + case GICV5_ITS_DT_ITT_CFGR_L2SZ_16k:
> > + return 11;
> > + case GICV5_ITS_DT_ITT_CFGR_L2SZ_64k:
> > + return 13;
> > + default:
> > + return -EINVAL;
> > + }
>
> Similar to my earlier remarks: these sort of helpers should never be
> able to return an error.
Changed it.
> > +}
> > +
> > +static int gicv5_its_itt_cache_inv(struct gicv5_its_chip_data *its,
> > + u32 device_id, u16 event_id)
> > +{
> > + u32 eventr, eidr;
> > + u64 didr;
> > +
> > + didr = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, device_id);
> > + eidr = FIELD_PREP(GICV5_ITS_EIDR_EVENTID, event_id);
> > + eventr = FIELD_PREP(GICV5_ITS_INV_EVENTR_I, 0x1);
> > +
> > + its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
> > + its_writel_relaxed(its, eidr, GICV5_ITS_EIDR);
> > + its_writel_relaxed(its, eventr, GICV5_ITS_INV_EVENTR);
> > +
> > + return gicv5_its_wait_for_invalidation(its);
>
> You're not waiting for an invalidation. You're waiting for its
> completion. Just have a single helper that synchronises everything
> related to the ITS, irrespective of what you're waiting for
> (gicv5_its_synchronise(), or something similar).
gicv5_its_cache_sync()
>
> > +}
> > +
> > +static void gicv5_its_free_itt_linear(struct gicv5_its_dev *its_dev)
> > +{
> > + kfree(its_dev->itt_cfg.linear.itt);
> > +}
> > +
> > +static void gicv5_its_free_itt_two_level(struct gicv5_its_dev *its_dev)
> > +{
> > + unsigned int i, num_ents = its_dev->itt_cfg.l2.num_l1_ents;
> > +
> > + for (i = 0; i < num_ents; i++)
> > + kfree(its_dev->itt_cfg.l2.l2ptrs[i]);
> > +
> > + kfree(its_dev->itt_cfg.l2.l2ptrs);
> > + kfree(its_dev->itt_cfg.l2.l1itt);
> > +}
> > +
> > +static void gicv5_its_free_itt(struct gicv5_its_dev *its_dev)
> > +{
> > + if (!its_dev->itt_cfg.l2itt)
> > + gicv5_its_free_itt_linear(its_dev);
> > + else
> > + gicv5_its_free_itt_two_level(its_dev);
> > +}
> > +
> > +static int gicv5_its_create_itt_linear(struct gicv5_its_chip_data *its,
> > + struct gicv5_its_dev *its_dev,
> > + unsigned int event_id_bits)
> > +{
> > + unsigned int num_ents = BIT(event_id_bits);
> > + __le64 *itt;
> > +
> > + itt = kcalloc(num_ents, sizeof(*itt), GFP_KERNEL);
> > + if (!itt)
> > + return -ENOMEM;
> > +
> > + its_dev->itt_cfg.linear.itt = itt;
> > + its_dev->itt_cfg.linear.num_ents = num_ents;
> > + its_dev->itt_cfg.l2itt = false;
> > + its_dev->itt_cfg.event_id_bits = event_id_bits;
> > +
> > + if (its->flags & ITS_FLAGS_NON_COHERENT)
> > + dcache_clean_inval_poc((unsigned long)itt,
> > + (unsigned long)itt + num_ents * sizeof(*itt));
> > + else
> > + dsb(ishst);
>
> We have this very pattern 4 or 5 times in this single file.
>
> Consider a helper such as:
>
> static void gicv5_its_dcache_clean(struct gicv5_its_chip_data *its,
> void *start, size_t sz)
> {
> void *end = start + sz;
>
> if (its->flags & ITS_FLAGS_NON_COHERENT)
> dcache_clean_inval_poc((unsigned long)start, (unsigned long)end);
> else
> dsb(ishst);
> }
>
> and use it everywhere. Yes, this adds extra DSBs on the L2 ITT
> path. But none of that is ever performance critical, and mostly
> happens once per device in the lifetime of the system.
Yes, it is nicer, changed it.
>
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * Allocate a two-level ITT. All ITT entries are allocated in one go, unlike
> > + * with the device table. Span may be used to limit the second level table
> > + * size, where possible.
> > + */
> > +static int gicv5_its_create_itt_two_level(struct gicv5_its_chip_data *its,
> > + struct gicv5_its_dev *its_dev,
> > + unsigned int event_id_bits,
> > + unsigned int itt_l2sz,
> > + unsigned int num_events)
> > +{
> > + unsigned int l1_bits, l2_bits, span, events_per_l2_table,
> > + complete_tables, final_span, num_ents;
> > + __le64 *itt_l1, *itt_l2, **l2ptrs;
> > + size_t l1sz;
> > + int ret, i;
> > + u64 val;
> > +
> > + ret = gicv5_its_l2sz_to_l2_bits(itt_l2sz);
> > + if (ret < 0 || ret >= event_id_bits) {
> > + pr_debug("Incorrect l2sz (0x%x) for %u EventID bits. Cannot allocate ITT\n",
> > + itt_l2sz, event_id_bits);
> > + return -EINVAL;
> > + }
>
> Honestly, I fail to see how this can happen.
I can remove it, certainly the ret < 0 path.
> > +
> > + l2_bits = ret;
> > +
> > + l1_bits = event_id_bits - l2_bits;
> > +
> > + num_ents = BIT(l1_bits);
> > +
> > + itt_l1 = kcalloc(num_ents, sizeof(*itt_l1), GFP_KERNEL);
> > + if (!itt_l1)
> > + return -ENOMEM;
> > +
> > + l2ptrs = kcalloc(num_ents, sizeof(*l2ptrs), GFP_KERNEL);
> > + if (!l2ptrs) {
> > + kfree(itt_l1);
> > + return -ENOMEM;
> > + }
> > +
> > + its_dev->itt_cfg.l2.l2ptrs = l2ptrs;
> > +
> > + its_dev->itt_cfg.l2.l2sz = itt_l2sz;
> > + its_dev->itt_cfg.l2.l1itt = itt_l1;
> > + its_dev->itt_cfg.l2.num_l1_ents = num_ents;
> > + its_dev->itt_cfg.l2itt = true;
> > + its_dev->itt_cfg.event_id_bits = event_id_bits;
> > +
> > + /*
> > + * Need to determine how many entries there are per L2 - this is based
> > + * on the number of bits in the table.
> > + */
> > + events_per_l2_table = BIT(l2_bits);
> > + complete_tables = num_events / events_per_l2_table;
> > + final_span = order_base_2(num_events % events_per_l2_table);
> > +
> > + for (i = 0; i < num_ents; i++) {
> > + size_t l2sz;
> > +
> > + span = i == complete_tables ? final_span : l2_bits;
> > +
> > + itt_l2 = kcalloc(BIT(span), sizeof(*itt_l2), GFP_KERNEL);
> > + if (!itt_l2) {
> > + ret = -ENOMEM;
> > + goto out_free;
> > + }
>
> You are allocating a bunch of 64bit pointers. So the alignment is
> BIT(span + 3) or ARCH_KMALLOC_MINALIGN, whichever is the largest.
Right, at least 8 bytes.
> > +
> > + its_dev->itt_cfg.l2.l2ptrs[i] = itt_l2;
> > +
> > + l2sz = BIT(span) * sizeof(*itt_l2);
> > +
> > + if (its->flags & ITS_FLAGS_NON_COHERENT)
> > + dcache_clean_inval_poc((unsigned long)itt_l2,
> > + (unsigned long)itt_l2 + l2sz);
> > +
> > + val = (virt_to_phys(itt_l2) & GICV5_ITTL1E_L2_ADDR_MASK) |
> > + FIELD_PREP(GICV5_ITTL1E_SPAN, span) |
> > + FIELD_PREP(GICV5_ITTL1E_VALID, 0x1);
>
> GICV5_ITTL1E_L2_ADDR_MASK starts at bit 12.
No, it starts at bit 3.
> What guarantees that span is at least 9 so that you don't lose some
> significant address bits? I think is works as a consequence of
> gicv5_its_l2sz_to_l2_bits() returning at least 9, but some comment
> would be appreciated.
>
> > +
> > + WRITE_ONCE(itt_l1[i], cpu_to_le64(val));
> > + }
> > +
> > + if (its->flags & ITS_FLAGS_NON_COHERENT) {
> > + l1sz = num_ents * sizeof(*itt_l1);
> > + dcache_clean_inval_poc((unsigned long)itt_l1,
> > + (unsigned long)itt_l1 + l1sz);
> > + } else {
> > + dsb(ishst);
> > + }
> > +
> > + return 0;
> > +out_free:
> > + for (i = i - 1; i >= 0; i--)
> > + kfree(its_dev->itt_cfg.l2.l2ptrs[i]);
> > +
> > + kfree(its_dev->itt_cfg.l2.l2ptrs);
> > + kfree(itt_l1);
> > + return ret;
> > +}
> > +
> > +/*
> > + * Function to check whether the device table or ITT table support
> > + * a two-level table and if so depending on the number of id_bits
> > + * requested, determine whether a two-level table is required.
> > + *
> > + * Return the 2-level size value if a two level table is deemed
> > + * necessary.
> > + */
> > +static bool gicv5_its_l2sz_two_level(bool devtab, u32 its_idr1, u8 id_bits,
> > + u8 *sz)
> > +{
> > + int l2_bits, l2_sz = -EINVAL;
> > +
> > + if (devtab && !FIELD_GET(GICV5_ITS_IDR1_DT_LEVELS, its_idr1))
> > + return false;
> > +
> > + if (!devtab && !FIELD_GET(GICV5_ITS_IDR1_ITT_LEVELS, its_idr1))
> > + return false;
> > +
> > + /*
> > + * Pick an L2 size that matches the pagesize; if a match
> > + * is not found, go for the smallest supported l2 size granule.
> > + *
> > + * This ensures that we will always be able to allocate
> > + * contiguous memory at L2.
> > + */
> > + switch (PAGE_SIZE) {
> > + case SZ_64K:
> > + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(its_idr1)) {
> > + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_64k;
> > + break;
> > + }
> > + fallthrough;
> > + case SZ_16K:
> > + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(its_idr1)) {
> > + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_16k;
> > + break;
> > + }
> > + fallthrough;
> > + case SZ_4K:
> > + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(its_idr1)) {
> > + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_4k;
> > + break;
> > + }
> > + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(its_idr1)) {
> > + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_16k;
> > + break;
> > + }
> > + if (GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(its_idr1)) {
> > + l2_sz = GICV5_ITS_DT_ITT_CFGR_L2SZ_64k;
> > + break;
> > + }
> > + break;
> > + }
> > +
> > + l2_bits = gicv5_its_l2sz_to_l2_bits(l2_sz);
> > +
> > + if (l2_bits < 0 || l2_bits > id_bits)
> > + return false;
>
> I really cannot see how l2_bits can be an error. PAGE_SIZE is not an
> arbitrary value, neither is the L2SZ configuration in IDR1.
>
> If you are paranoid about broken implementations, put a BUG_ON() early
> on to validate that your implementation isn't completely
> braindead. But please don't litter the driver with these checks.
Ok.
> > +
> > + *sz = l2_sz;
> > +
> > + return true;
> > +}
> > +
> > +static int gicv5_its_device_get_itte_ref(struct gicv5_its_dev *its_dev,
> > + __le64 **itte, u16 event_id)
> > +{
> > + if (!its_dev->itt_cfg.l2itt) {
> > + __le64 *itt = its_dev->itt_cfg.linear.itt;
> > + *itte = &itt[event_id];
> > + } else {
> > + __le64 *l2_itt, *l1_itt = its_dev->itt_cfg.l2.l1itt;
> > + unsigned int l1_idx, l2_idx, l2_size, l2_bits;
> > + int ret;
> > +
> > + ret = gicv5_its_l2sz_to_l2_bits(its_dev->itt_cfg.l2.l2sz);
> > + if (ret < 0)
> > + return ret;
> > + l2_bits = ret;
> > +
> > + l1_idx = event_id >> l2_bits;
> > +
> > + if (!FIELD_GET(GICV5_ITTL1E_VALID,
> > + le64_to_cpu(l1_itt[l1_idx]))) {
> > + pr_debug("L1 ITT entry is not valid.\n");
> > + return -EINVAL;
> > + }
> > +
> > + l2_idx = event_id & GENMASK(l2_bits - 1, 0);
> > +
> > + l2_size = BIT(FIELD_GET(GICV5_ITTL1E_SPAN,
> > + le64_to_cpu(l1_itt[l1_idx])));
> > +
> > + // Sanity check our indexing
> > + if (l2_idx >= l2_size) {
> > + pr_debug("L2 ITT index (%u) exceeds L2 table size (%u)!\n",
> > + l2_idx, l2_size);
> > + return -EINVAL;
> > + }
>
> That's another example: if your indexing is going in the weeds, your
> tables are invalid, or some other state has gone rotten, you probably
> are corrupting memory already, and returning an error only has two
> effects:
>
> - keep the bad state around
> - add complexity to the code because of the error handling
>
> I'm not a fan of BUG_ON(), but that's at least appropriate in this
> case -- you are in a very bad situation that cannot be recovered from.
Ok.
> > + l2_itt = its_dev->itt_cfg.l2.l2ptrs[l1_idx];
> > + *itte = &l2_itt[l2_idx];
> > + }
> > +
> > + return 0;
>
> And once you've simplified the error handling, you can simply return
> the pointer instead of dealing with the **itte.
Yep.
> > +}
> > +
> > +static int gicv5_its_device_cache_inv(struct gicv5_its_chip_data *its,
> > + struct gicv5_its_dev *its_dev)
> > +{
> > + u32 devicer;
> > + u64 didr;
> > +
> > + didr = FIELD_PREP(GICV5_ITS_DIDR_DEVICEID, its_dev->device_id);
> > + devicer = FIELD_PREP(GICV5_ITS_INV_DEVICER_I, 0x1) |
> > + FIELD_PREP(GICV5_ITS_INV_DEVICER_EVENTID_BITS,
> > + its_dev->itt_cfg.event_id_bits) |
> > + FIELD_PREP(GICV5_ITS_INV_DEVICER_L1, 0x0);
> > + its_writeq_relaxed(its, didr, GICV5_ITS_DIDR);
> > + its_writel_relaxed(its, devicer, GICV5_ITS_INV_DEVICER);
> > +
> > + return gicv5_its_wait_for_invalidation(its);
> > +}
> > +
> > +/*
> > + * Allocate a level 2 device table entry, update L1 parent to reference it.
> > + * Only used for 2-level device tables, and it is called on demand.
> > + */
> > +static int gicv5_its_alloc_l2_devtab(struct gicv5_its_chip_data *its,
> > + unsigned int l1_index)
> > +{
> > + __le64 *l2devtab, *l1devtab = its->devtab_cfgr.l2.l1devtab;
> > + u8 span, l2sz, l2_bits;
> > + u64 l1dte;
> > + int ret;
> > +
> > + if (FIELD_GET(GICV5_DTL1E_VALID, le64_to_cpu(l1devtab[l1_index])))
> > + return 0;
> > +
> > + span = FIELD_GET(GICV5_DTL1E_SPAN, le64_to_cpu(l1devtab[l1_index]));
> > + l2sz = FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, its->devtab_cfgr.cfgr);
>
> nit: I see a bunch of these accessors. Maybe consider adding
> convenience helpers such as:
>
> #define devtab_cfgr_field(its, f) \
> FIELD_GET(GICV5_ITS_DT_CFGR_##f, (its)->devtab_cfgr.cfgr)
>
> l2sz = devtab_cfgr_field(its, L2SZ);
>
> which makes it much more readable.
I think I will take the hint and apply it.
> > +
> > + ret = gicv5_its_l2sz_to_l2_bits(l2sz);
> > + if (ret < 0)
> > + return ret;
> > +
> > + l2_bits = ret;
> > +
> > + /*
> > + * Span allows us to create a smaller L2 device table.
> > + * If it is too large, use the number of allowed L2 bits.
> > + */
> > + if (span > l2_bits)
> > + span = l2_bits;
> > +
> > + l2devtab = kcalloc(BIT(span), sizeof(*l2devtab), GFP_KERNEL);
> > + if (!l2devtab)
> > + return -ENOMEM;
> > +
> > + its->devtab_cfgr.l2.l2ptrs[l1_index] = l2devtab;
> > +
> > + l1dte = FIELD_PREP(GICV5_DTL1E_SPAN, span) |
> > + (virt_to_phys(l2devtab) & GICV5_DTL1E_L2_ADDR_MASK) |
> > + FIELD_PREP(GICV5_DTL1E_VALID, 0x1);
> > + its_write_table_entry(its, &l1devtab[l1_index], cpu_to_le64(l1dte));
> > +
> > + return 0;
> > +}
> > +
> > +static int gicv5_its_devtab_get_dte_ref(struct gicv5_its_chip_data *its,
> > + __le64 **dte, u32 device_id,
> > + bool alloc)
> > +{
> > + u8 str = FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, its->devtab_cfgr.cfgr);
> > + unsigned int l2sz, l2_bits, l1_idx, l2_idx;
> > + __le64 *l1devtab, *l2devtab;
> > + int ret;
> > +
> > + if (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) {
> > + l2devtab = its->devtab_cfgr.linear.devtab;
> > + *dte = &l2devtab[device_id];
> > + } else {
> > + l2sz = FIELD_GET(GICV5_ITS_DT_CFGR_L2SZ, its->devtab_cfgr.cfgr);
> > + l1devtab = its->devtab_cfgr.l2.l1devtab;
> > +
> > + ret = gicv5_its_l2sz_to_l2_bits(l2sz);
> > + if (ret < 0)
> > + return -EINVAL;
> > +
> > + l2_bits = ret;
>
> nit: directly assign l2_bits.
Ok.
>
> > + l1_idx = device_id >> l2_bits;
> > + l2_idx = device_id & GENMASK(l2_bits - 1, 0);
> > +
> > + if (alloc) {
> > + /*
> > + * Allocate a new L2 device table here before
> > + * continuing. We make the assumption that the span in
> > + * the L1 table has been set correctly, and blindly use
> > + * that value.
> > + */
> > + ret = gicv5_its_alloc_l2_devtab(its, l1_idx);
> > + if (ret)
> > + return ret;
> > + } else {
> > + if (!FIELD_GET(GICV5_DTL1E_VALID,
> > + le64_to_cpu(l1devtab[l1_idx])))
> > + return -EINVAL;
> > + }
> > +
> > + l2devtab = its->devtab_cfgr.l2.l2ptrs[l1_idx];
> > + *dte = &l2devtab[l2_idx];
> > + }
> > +
> > + return 0;
>
> Same comments as the itte_ref equivalent.
Ok.
> > +}
> > +
> > +/*
> > + * Register a new device in the device table. Allocate an ITT and
> > + * program the L2DTE entry according to the ITT structure that
> > + * was chosen.
> > + */
> > +static int gicv5_its_device_register(struct gicv5_its_chip_data *its,
> > + struct gicv5_its_dev *its_dev)
> > +{
> > + u8 event_id_bits, device_id_bits, itt_struct, itt_l2sz;
> > + phys_addr_t itt_phys_base;
> > + bool two_level_itt;
> > + u32 idr1, idr2;
> > + __le64 *dte;
> > + u64 val;
> > + int ret;
> > +
> > + device_id_bits = FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS,
> > + its->devtab_cfgr.cfgr);
> > +
> > + if (its_dev->device_id >= BIT(device_id_bits)) {
> > + pr_err("Supplied DeviceID (%u) outside of Device Table range (%u)!",
> > + its_dev->device_id, (u32)GENMASK(device_id_bits - 1, 0));
> > + return -EINVAL;
> > + }
> > +
> > + ret = gicv5_its_devtab_get_dte_ref(its, &dte, its_dev->device_id, true);
> > + if (ret)
> > + return ret;
> > +
> > + if (FIELD_GET(GICV5_DTL2E_VALID, le64_to_cpu(*dte)))
> > + return -EBUSY;
> > +
> > + /*
> > + * Determine how many bits we need, validate those against the max.
> > + * Based on these, determine if we should go for a 1- or 2-level ITT.
> > + */
> > + event_id_bits = order_base_2(its_dev->num_events);
> > +
> > + idr2 = its_readl_relaxed(its, GICV5_ITS_IDR2);
> > +
> > + if (event_id_bits > FIELD_GET(GICV5_ITS_IDR2_EVENTID_BITS, idr2)) {
> > + pr_err("Required EventID bits (%u) larger than supported bits (%u)!",
> > + event_id_bits,
> > + (u8)FIELD_GET(GICV5_ITS_IDR2_EVENTID_BITS, idr2));
> > + return -EINVAL;
> > + }
> > +
> > + idr1 = its_readl_relaxed(its, GICV5_ITS_IDR1);
> > +
> > + /*
> > + * L2 ITT size is programmed into the L2DTE regardless of
> > + * whether a two-level or linear ITT is built, init it.
> > + */
> > + itt_l2sz = 0;
> > +
> > + two_level_itt = gicv5_its_l2sz_two_level(false, idr1, event_id_bits,
> > + &itt_l2sz);
> > + if (two_level_itt)
> > + ret = gicv5_its_create_itt_two_level(its, its_dev, event_id_bits,
> > + itt_l2sz,
> > + its_dev->num_events);
> > + else
> > + ret = gicv5_its_create_itt_linear(its, its_dev, event_id_bits);
> > + if (ret)
> > + return ret;
> > +
> > + itt_phys_base = two_level_itt ? virt_to_phys(its_dev->itt_cfg.l2.l1itt) :
> > + virt_to_phys(its_dev->itt_cfg.linear.itt);
> > +
> > + itt_struct = two_level_itt ? GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL :
> > + GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR;
> > +
> > + val = FIELD_PREP(GICV5_DTL2E_EVENT_ID_BITS, event_id_bits) |
> > + FIELD_PREP(GICV5_DTL2E_ITT_STRUCTURE, itt_struct) |
> > + (itt_phys_base & GICV5_DTL2E_ITT_ADDR_MASK) |
> > + FIELD_PREP(GICV5_DTL2E_ITT_L2SZ, itt_l2sz) |
> > + FIELD_PREP(GICV5_DTL2E_VALID, 0x1);
> > +
> > + its_write_table_entry(its, dte, cpu_to_le64(val));
> > +
> > + ret = gicv5_its_device_cache_inv(its, its_dev);
> > + if (ret) {
> > + gicv5_its_free_itt(its_dev);
> > + its_write_table_entry(its, dte, 0);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * Unregister a device in the device table. Lookup the device by ID, free the
> > + * corresponding ITT, mark the device as invalid in the device table.
> > + */
> > +static int gicv5_its_device_unregister(struct gicv5_its_chip_data *its,
> > + struct gicv5_its_dev *its_dev)
> > +{
> > + __le64 *dte;
> > + int ret;
> > +
> > + ret = gicv5_its_devtab_get_dte_ref(its, &dte, its_dev->device_id, false);
> > + if (ret) {
> > + pr_debug("Failed to find DTE for DeviceID 0x%x\n", its_dev->device_id);
> > + return -EINVAL;
> > + }
> > +
> > + if (!FIELD_GET(GICV5_DTL2E_VALID, le64_to_cpu(*dte))) {
> > + pr_debug("Device table entry for DeviceID 0x%x is not valid. Nothing to clean up!",
> > + its_dev->device_id);
> > + return -EINVAL;
> > + }
> > +
> > + gicv5_its_free_itt(its_dev);
> > +
> > + /* Zero everything - make it clear that this is an invalid entry */
> > + its_write_table_entry(its, dte, 0);
> > +
> > + return gicv5_its_device_cache_inv(its, its_dev);
> > +}
> > +
> > +/*
> > + * Allocate a 1-level device table. All entries are allocated, but marked
> > + * invalid.
> > + */
> > +static int gicv5_its_alloc_devtab_linear(struct gicv5_its_chip_data *its,
> > + u8 device_id_bits)
> > +{
> > + __le64 *devtab;
> > + size_t sz;
> > + u64 baser;
> > + u32 cfgr;
> > +
> > + /*
> > + * We expect a GICv5 implementation requiring a large number of
> > + * deviceID bits to support a 2-level device table. If that's not
> > + * the case, cap the number of deviceIDs supported according to the
> > + * kmalloc limits so that the system can chug along with a linear
> > + * device table.
> > + */
> > + sz = BIT_ULL(device_id_bits) * sizeof(*devtab);
> > + if (sz > KMALLOC_MAX_SIZE) {
> > + u8 device_id_cap = ilog2(KMALLOC_MAX_SIZE/sizeof(*devtab));
> > +
> > + pr_warn("Limiting device ID bits from %u to %u\n",
> > + device_id_bits, device_id_cap);
> > + device_id_bits = device_id_cap;
> > + }
> > +
> > + devtab = kcalloc(BIT(device_id_bits), sizeof(*devtab), GFP_KERNEL);
> > + if (!devtab)
> > + return -ENOMEM;
> > +
> > + if (its->flags & ITS_FLAGS_NON_COHERENT)
> > + dcache_clean_inval_poc((unsigned long)devtab,
> > + (unsigned long)devtab + sz);
> > + else
> > + dsb(ishst);
> > +
> > + cfgr = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
> > + GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR) |
> > + FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, 0) |
> > + FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, device_id_bits);
> > + its_writel_relaxed(its, cfgr, GICV5_ITS_DT_CFGR);
> > +
> > + baser = virt_to_phys(devtab) & GICV5_ITS_DT_BASER_ADDR_MASK;
> > + its_writeq_relaxed(its, baser, GICV5_ITS_DT_BASER);
> > +
> > + its->devtab_cfgr.cfgr = cfgr;
> > + its->devtab_cfgr.linear.devtab = devtab;
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * Allocate a 2-level device table. L2 entries are not allocated,
> > + * they are allocated on-demand.
> > + */
> > +static int gicv5_its_alloc_devtab_two_level(struct gicv5_its_chip_data *its,
> > + u8 device_id_bits,
> > + u8 devtab_l2sz)
> > +{
> > + unsigned int l1_bits, l2_bits, i;
> > + __le64 *l1devtab, **l2ptrs;
> > + size_t l1_sz;
> > + u64 baser;
> > + u32 cfgr;
> > + int ret;
> > +
> > + ret = gicv5_its_l2sz_to_l2_bits(devtab_l2sz);
> > + if (ret < 0)
> > + return ret;
> > +
> > + l2_bits = ret;
>
> This pattern should go.
I will remove it.
> > +
> > + l1_bits = device_id_bits - l2_bits;
> > + l1_sz = BIT(l1_bits) * sizeof(*l1devtab);
> > + /*
> > + * With 2-level device table support it is highly unlikely
> > + * that we are not able to allocate the required amount of
> > + * device table memory to cover deviceID space; cap the
> > + * deviceID space if we encounter such set-up.
> > + * If this ever becomes a problem we could revisit the policy
> > + * behind level 2 size selection to reduce level-1 deviceID bits.
> > + */
> > + if (l1_sz > KMALLOC_MAX_SIZE) {
> > + l1_bits = ilog2(KMALLOC_MAX_SIZE/sizeof(*l1devtab));
> > +
> > + pr_warn("Limiting device ID bits from %u to %u\n",
> > + device_id_bits, l1_bits + l2_bits);
> > + device_id_bits = l1_bits + l2_bits;
> > + l1_sz = KMALLOC_MAX_SIZE;
> > + }
> > +
> > + l1devtab = kcalloc(BIT(l1_bits), sizeof(*l1devtab), GFP_KERNEL);
> > + if (!l1devtab)
> > + return -ENOMEM;
> > +
> > + l2ptrs = kcalloc(BIT(l1_bits), sizeof(*l2ptrs), GFP_KERNEL);
> > + if (!l2ptrs) {
> > + kfree(l1devtab);
> > + return -ENOMEM;
> > + }
> > +
> > + for (i = 0; i < BIT(l1_bits); i++)
> > + l1devtab[i] = cpu_to_le64(FIELD_PREP(GICV5_DTL1E_SPAN, l2_bits));
> > +
> > + if (its->flags & ITS_FLAGS_NON_COHERENT)
> > + dcache_clean_inval_poc((unsigned long)l1devtab,
> > + (unsigned long)l1devtab + l1_sz);
> > + else
> > + dsb(ishst);
> > +
> > + cfgr = FIELD_PREP(GICV5_ITS_DT_CFGR_STRUCTURE,
> > + GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL) |
> > + FIELD_PREP(GICV5_ITS_DT_CFGR_L2SZ, devtab_l2sz) |
> > + FIELD_PREP(GICV5_ITS_DT_CFGR_DEVICEID_BITS, device_id_bits);
> > + its_writel_relaxed(its, cfgr, GICV5_ITS_DT_CFGR);
> > +
> > + baser = virt_to_phys(l1devtab) & GICV5_ITS_DT_BASER_ADDR_MASK;
> > + its_writeq_relaxed(its, baser, GICV5_ITS_DT_BASER);
> > +
> > + its->devtab_cfgr.cfgr = cfgr;
> > + its->devtab_cfgr.l2.l1devtab = l1devtab;
> > + its->devtab_cfgr.l2.l2ptrs = l2ptrs;
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * Initialise the device table as either 1- or 2-level depending on what is
> > + * supported by the hardware.
> > + */
> > +static int gicv5_its_init_devtab(struct gicv5_its_chip_data *its)
> > +{
> > + u8 device_id_bits, devtab_l2sz;
> > + bool two_level_devtab;
> > + u32 idr1;
> > +
> > + idr1 = its_readl_relaxed(its, GICV5_ITS_IDR1);
> > +
> > + device_id_bits = FIELD_GET(GICV5_ITS_IDR1_DEVICEID_BITS, idr1);
> > + two_level_devtab = gicv5_its_l2sz_two_level(true, idr1, device_id_bits,
> > + &devtab_l2sz);
> > + if (two_level_devtab)
> > + return gicv5_its_alloc_devtab_two_level(its, device_id_bits,
> > + devtab_l2sz);
> > + else
> > + return gicv5_its_alloc_devtab_linear(its, device_id_bits);
> > +}
> > +
> > +static void gicv5_its_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
> > +{
> > + struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
> > + struct gicv5_its_chip_data *its = its_dev->its_node;
> > + u64 addr;
> > +
> > + addr = its->its_trans_phys_base;
> > +
> > + msg->data = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> > + msi_msg_set_addr(irq_data_get_msi_desc(d), msg, addr);
> > +}
> > +
> > +static const struct irq_chip gicv5_its_irq_chip = {
> > + .name = "GICv5-ITS-MSI",
> > + .irq_mask = irq_chip_mask_parent,
> > + .irq_unmask = irq_chip_unmask_parent,
> > + .irq_eoi = irq_chip_eoi_parent,
> > + .irq_set_affinity = irq_chip_set_affinity_parent,
> > + .irq_get_irqchip_state = irq_chip_get_parent_state,
> > + .irq_set_irqchip_state = irq_chip_set_parent_state,
> > + .irq_compose_msi_msg = gicv5_its_compose_msi_msg,
> > + .flags = IRQCHIP_SET_TYPE_MASKED |
> > + IRQCHIP_SKIP_SET_WAKE |
> > + IRQCHIP_MASK_ON_SUSPEND
> > +};
> > +
> > +static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *its,
> > + u32 device_id)
> > +{
> > + struct gicv5_its_dev *dev = xa_load(&its->its_devices, device_id);
> > +
> > + return dev ? dev : ERR_PTR(-ENODEV);
> > +}
> > +
> > +static struct gicv5_its_dev *gicv5_its_alloc_device(
> > + struct gicv5_its_chip_data *its, int nvec,
> > + u32 dev_id)
>
> nit: please place the first parameter on the same line as the function
> name.
Sure.
> > +{
> > + struct gicv5_its_dev *its_dev;
> > + int ret;
> > +
> > + its_dev = gicv5_its_find_device(its, dev_id);
> > + if (!IS_ERR(its_dev)) {
> > + pr_debug("A device with this DeviceID (0x%x) has already been registered.\n",
> > + dev_id);
> > +
> > + if (nvec > its_dev->num_events) {
> > + pr_debug("Requesting more ITT entries than allocated\n");
> > + return ERR_PTR(-ENXIO);
> > + }
> > +
> > + its_dev->shared = true;
> > +
> > + return its_dev;
>
> I really think we shouldn't even consider the silliness of
> non-transparent bridges this time around. That's a terrible system
> design, and it leads to all sorts of lifetime madness -- the GICv3
> driver is a testament to it. Modern systems with GICv5 should not have
> to deal with this nonsense.
I am not sure we can remove this path for the IWB - even if we model it
as an MBIgen.
With Sascha and Tim we tested this code path, I am not sure it would
work if a driver with a wired IRQ connected to an IWB free an IRQ and
the ITS device representing the IWB is not shared.
Need to check the whole thing again.
> > + }
> > +
> > + its_dev = kzalloc(sizeof(*its_dev), GFP_KERNEL);
> > + if (!its_dev)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + its_dev->device_id = dev_id;
> > + its_dev->num_events = nvec;
> > + its_dev->num_mapped_events = 0;
> > +
> > + ret = gicv5_its_device_register(its, its_dev);
> > + if (ret) {
> > + pr_debug("Failed to register the device\n");
> > + kfree(its_dev);
> > + return ERR_PTR(ret);
> > + }
> > +
> > + gicv5_its_device_cache_inv(its, its_dev);
> > +
> > + /*
> > + * This is the first time we have seen this device. Hence, it is not
> > + * shared.
> > + */
> > + its_dev->shared = false;
> > +
> > + its_dev->its_node = its;
> > +
> > + its_dev->event_map =
> > + (unsigned long *)bitmap_zalloc(its_dev->num_events, GFP_KERNEL);
> > + if (!its_dev->event_map) {
> > + gicv5_its_device_unregister(its, its_dev);
> > + kfree(its_dev);
> > + return ERR_PTR(-ENOMEM);
> > + }
> > +
> > + xa_store(&its->its_devices, dev_id, its_dev, GFP_KERNEL);
> > +
> > + return its_dev;
> > +}
> > +
> > +static int gicv5_its_msi_prepare(struct irq_domain *domain, struct device *dev,
> > + int nvec, msi_alloc_info_t *info)
> > +{
> > + u32 dev_id = info->scratchpad[0].ul;
> > + struct msi_domain_info *msi_info;
> > + struct gicv5_its_chip_data *its;
> > + struct gicv5_its_dev *its_dev;
> > +
> > + msi_info = msi_get_domain_info(domain);
> > + its = msi_info->data;
> > +
> > + guard(mutex)(&its->dev_alloc_lock);
> > +
> > + its_dev = gicv5_its_alloc_device(its, nvec, dev_id);
> > + if (IS_ERR(its_dev))
> > + return PTR_ERR(its_dev);
> > +
> > + if (info->flags & MSI_ALLOC_FLAGS_PROXY_DEVICE)
> > + its_dev->shared = true;
> > +
> > + return 0;
> > +}
> > +
> > +static struct msi_domain_ops its_msi_domain_ops = {
> > + .msi_prepare = gicv5_its_msi_prepare,
> > +};
> > +
> > +static int gicv5_its_alloc_event(struct gicv5_its_dev *its_dev, u16 event_id,
> > + u32 lpi)
> > +{
> > + struct gicv5_its_chip_data *its = its_dev->its_node;
> > + u64 itt_entry;
> > + __le64 *itte;
> > + int ret;
> > +
> > + if (event_id >= its_dev->num_events) {
> > + pr_debug("EventID 0x%x outside of ITT range (0x%x)\n", event_id,
> > + its_dev->num_events);
> > + return -EINVAL;
> > + }
> > +
> > + if (WARN(its_dev->num_mapped_events == its_dev->num_events,
> > + "Reached maximum number of events\n"))
> > + return -EINVAL;
> > +
> > + ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
> > + if (ret)
> > + return ret;
> > +
> > + if (FIELD_GET(GICV5_ITTL2E_VALID, *itte))
> > + return -EEXIST;
> > +
> > + itt_entry = FIELD_PREP(GICV5_ITTL2E_LPI_ID, lpi) |
> > + FIELD_PREP(GICV5_ITTL2E_VALID, 0x1);
> > +
> > + its_write_table_entry(its, itte, cpu_to_le64(itt_entry));
> > +
> > + gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
> > +
> > + its_dev->num_mapped_events += 1;
> > +
> > + return 0;
> > +}
> > +
> > +static void gicv5_its_free_event(struct gicv5_its_dev *its_dev, u16 event_id)
> > +{
> > + struct gicv5_its_chip_data *its = its_dev->its_node;
> > + u64 itte_val;
> > + __le64 *itte;
> > + int ret;
> > +
> > + if (WARN(!its_dev->num_mapped_events, "No mapped events\n"))
> > + return;
> > +
> > + ret = gicv5_its_device_get_itte_ref(its_dev, &itte, event_id);
> > + if (ret) {
> > + pr_debug("Failed to get the ITTE!\n");
> > + return;
> > + }
> > +
> > + itte_val = le64_to_cpu(*itte);
> > + itte_val &= ~GICV5_ITTL2E_VALID;
> > +
> > + its_write_table_entry(its, itte, cpu_to_le64(itte_val));
> > +
> > + gicv5_its_itt_cache_inv(its, its_dev->device_id, event_id);
> > +
> > + its_dev->num_mapped_events -= 1;
> > +}
> > +
> > +static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev,
> > + unsigned int nr_irqs, u32 *eventid)
> > +{
> > + int ret;
> > +
> > + ret = bitmap_find_free_region(its_dev->event_map,
> > + its_dev->num_events,
> > + get_count_order(nr_irqs));
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + *eventid = ret;
> > +
> > + return 0;
> > +}
> > +
> > +static void gicv5_its_free_eventid(struct gicv5_its_dev *its_dev,
> > + u32 event_id_base,
> > + unsigned int nr_irqs)
> > +{
> > + bitmap_release_region(its_dev->event_map, event_id_base,
> > + get_count_order(nr_irqs));
> > +}
> > +
> > +static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> > + unsigned int nr_irqs, void *arg)
> > +{
> > + u32 device_id, event_id_base, lpi;
> > + struct msi_domain_info *msi_info;
> > + struct gicv5_its_chip_data *its;
> > + struct gicv5_its_dev *its_dev;
> > + msi_alloc_info_t *info = arg;
> > + irq_hw_number_t hwirq;
> > + struct irq_data *irqd;
> > + int ret, i;
> > +
> > + device_id = info->scratchpad[0].ul;
> > +
> > + msi_info = msi_get_domain_info(domain);
> > + its = msi_info->data;
> > +
> > + mutex_lock(&its->dev_alloc_lock);
> > +
> > + its_dev = gicv5_its_find_device(its, device_id);
> > + if (IS_ERR(its_dev)) {
> > + mutex_unlock(&its->dev_alloc_lock);
> > + return PTR_ERR(its_dev);
> > + }
> > +
> > + ret = gicv5_its_alloc_eventid(its_dev, nr_irqs, &event_id_base);
> > + if (ret) {
> > + mutex_unlock(&its->dev_alloc_lock);
> > + return ret;
> > + }
> > +
> > + mutex_unlock(&its->dev_alloc_lock);
>
> Turn this block into a scoped guard, which will avoid the
> mutex_unlock()s altogether.
Same reply as to Jiri, I still use gotos below, mixing cleanup
handlers and gotos is frowned upon, let me see what I can come up
with.
> > +
> > + ret = iommu_dma_prepare_msi(info->desc, its->its_trans_phys_base);
> > + if (ret)
> > + goto out_eventid;
> > +
> > + for (i = 0; i < nr_irqs; i++) {
> > + lpi = gicv5_alloc_lpi();
> > + if (ret < 0) {
> > + pr_debug("Failed to find free LPI!\n");
> > + goto out_eventid;
> > + }
> > +
> > + ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi);
> > + if (ret)
> > + goto out_free_lpi;
> > +
> > + /*
> > + * Store eventid and deviceid into the hwirq for later use.
> > + *
> > + * hwirq = event_id << 32 | device_id
> > + */
> > + hwirq = FIELD_PREP(GICV5_ITS_HWIRQ_DEVICE_ID, device_id) |
> > + FIELD_PREP(GICV5_ITS_HWIRQ_EVENT_ID, (u64)event_id_base + i);
> > + irq_domain_set_info(domain, virq + i, hwirq,
> > + &gicv5_its_irq_chip, its_dev,
> > + handle_fasteoi_irq, NULL, NULL);
> > +
> > + irqd = irq_get_irq_data(virq + i);
> > + irqd_set_single_target(irqd);
> > + irqd_set_affinity_on_activate(irqd);
> > + irqd_set_resend_when_in_progress(irqd);
> > + }
> > +
> > + return 0;
> > +out_free_lpi:
> > + gicv5_free_lpi(lpi);
> > +out_eventid:
> > + gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs);
> > +
> > + return ret;
> > +}
> > +
> > +static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
> > + unsigned int nr_irqs)
> > +{
> > + struct msi_domain_info *msi_info;
> > + struct gicv5_its_chip_data *its;
> > + struct gicv5_its_dev *its_dev;
> > + struct irq_data *d;
> > + u16 event_id_base;
> > + bool free_device;
> > + u32 device_id;
> > + int i;
> > +
> > + msi_info = msi_get_domain_info(domain);
> > + its = msi_info->data;
> > +
> > + d = irq_domain_get_irq_data(domain, virq);
> > + device_id = FIELD_GET(GICV5_ITS_HWIRQ_DEVICE_ID, d->hwirq);
> > + event_id_base = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> > +
> > + guard(mutex)(&its->dev_alloc_lock);
> > +
> > + its_dev = gicv5_its_find_device(its, device_id);
> > + if (IS_ERR(its_dev)) {
> > + pr_debug("Couldn't find the ITS device!\n");
> > + return;
> > + }
> > +
> > + bitmap_release_region(its_dev->event_map, event_id_base,
> > + get_count_order(nr_irqs));
> > +
> > + free_device = !its_dev->shared && bitmap_empty(its_dev->event_map,
> > + its_dev->num_events);
> > +
> > + /* Hierarchically free irq data */
> > + for (i = 0; i < nr_irqs; i++) {
> > + d = irq_domain_get_irq_data(domain, virq + i);
> > +
> > + gicv5_free_lpi(d->parent_data->hwirq);
> > + irq_domain_reset_irq_data(d);
> > + }
> > + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> > +
> > + gicv5_its_syncr(its, its_dev);
> > + gicv5_irs_syncr();
> > +
> > + if (free_device) {
> > + gicv5_its_device_unregister(its, its_dev);
> > + bitmap_free(its_dev->event_map);
> > + xa_erase(&its->its_devices, device_id);
> > + kfree(its_dev);
> > + }
> > +}
> > +
> > +static int gicv5_its_irq_domain_activate(struct irq_domain *domain,
> > + struct irq_data *d, bool reserve)
> > +{
> > + struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
> > + u16 event_id;
> > + u32 lpi;
> > +
> > + event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> > + lpi = d->parent_data->hwirq;
> > +
> > + return gicv5_its_alloc_event(its_dev, event_id, lpi);
>
> Huh. This looks wrong. Allocating the event really should happen at
> alloc time, not at activate time, because the endpoint driver doesn't
> really expect this to fail for any reason other than a gross bug.
>
> activate should allow the translation to take place, but not rely on
> allocating events. Compare with GICv3, which only issues the MAPTI
> command at activate time.
I am not "allocating an event" (well, then you would say "learn how to
name your functions" and you are right), I am writing the ITT table for
an eventid that was preallocated before, so basically, apart from
paranoia checks, this is the MAPTI equivalent.
> > +}
> > +
> > +static void gicv5_its_irq_domain_deactivate(struct irq_domain *domain,
> > + struct irq_data *d)
> > +{
> > + struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
> > + u16 event_id;
> > +
> > + event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> > +
> > + gicv5_its_free_event(its_dev, event_id);
> > +}
> > +static const struct irq_domain_ops gicv5_its_irq_domain_ops = {
> > + .alloc = gicv5_its_irq_domain_alloc,
> > + .free = gicv5_its_irq_domain_free,
> > + .activate = gicv5_its_irq_domain_activate,
> > + .deactivate = gicv5_its_irq_domain_deactivate,
> > + .select = msi_lib_irq_domain_select,
> > +};
> > +
> > +static int gicv5_its_wait_for_cr0(struct gicv5_its_chip_data *its)
> > +{
> > + return gicv5_its_wait_for_op(its->its_base, GICV5_ITS_CR0,
> > + GICV5_ITS_CR0_IDLE);
> > +}
> > +
> > +static void gicv5_its_print_info(struct gicv5_its_chip_data *its_node)
> > +{
> > + bool devtab_linear;
> > + u8 device_id_bits;
> > + u8 str;
> > +
> > + device_id_bits = FIELD_GET(GICV5_ITS_DT_CFGR_DEVICEID_BITS,
> > + its_node->devtab_cfgr.cfgr);
> > +
> > + str = FIELD_GET(GICV5_ITS_DT_CFGR_STRUCTURE, its_node->devtab_cfgr.cfgr);
> > + devtab_linear = (str == GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR);
> > +
> > + pr_info("ITS %s enabled using %s device table device_id_bits %u\n",
> > + fwnode_get_name(its_node->fwnode),
> > + devtab_linear ? "linear" : "2-level",
> > + device_id_bits);
> > +}
> > +
> > +static int __init gicv5_its_init_bases(phys_addr_t its_trans_base,
> > + void __iomem *its_base,
> > + struct fwnode_handle *handle,
> > + struct irq_domain *parent_domain)
> > +{
> > + struct device_node *np = to_of_node(handle);
> > + struct gicv5_its_chip_data *its_node;
> > + struct msi_domain_info *info;
> > + struct irq_domain *d;
> > + u32 cr0, cr1;
> > + bool enabled;
> > + int ret;
> > +
> > + info = kzalloc(sizeof(*info), GFP_KERNEL);
> > + if (!info)
> > + return -ENOMEM;
> > +
> > + its_node = kzalloc(sizeof(*its_node), GFP_KERNEL);
> > + if (!its_node) {
> > + kfree(info);
> > + return -ENOMEM;
> > + }
> > +
> > + info->ops = &its_msi_domain_ops;
> > + info->data = its_node;
> > +
> > + mutex_init(&its_node->dev_alloc_lock);
> > + xa_init(&its_node->its_devices);
> > + its_node->fwnode = handle;
> > + its_node->its_base = its_base;
> > + its_node->its_trans_phys_base = its_trans_base;
> > +
> > + d = irq_domain_create_hierarchy(parent_domain, IRQ_DOMAIN_FLAG_ISOLATED_MSI,
> > + 0, handle, &gicv5_its_irq_domain_ops, info);
> > + its_node->domain = d;
> > + irq_domain_update_bus_token(its_node->domain, DOMAIN_BUS_NEXUS);
> > +
> > + its_node->domain->msi_parent_ops = &gic_its_msi_parent_ops;
> > + its_node->domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
> > +
> > + cr0 = its_readl_relaxed(its_node, GICV5_ITS_CR0);
> > + enabled = FIELD_GET(GICV5_ITS_CR0_ITSEN, cr0);
> > + if (WARN(enabled, "ITS %s enabled, disabling it before proceeding\n",
> > + np->full_name)) {
> > + cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x0);
> > + its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
> > + ret = gicv5_its_wait_for_cr0(its_node);
> > + if (ret) {
> > + irq_domain_remove(its_node->domain);
> > + kfree(info);
> > + kfree(its_node);
> > + return ret;
> > + }
> > + }
> > +
> > + if (of_property_read_bool(np, "dma-noncoherent")) {
> > + /*
> > + * A non-coherent ITS implies that some cache levels cannot be
> > + * used coherently by the cores and GIC. Our only option is to mark
> > + * memory attributes for the GIC as non-cacheable; by default,
> > + * non-cacheable memory attributes imply outer-shareable
> > + * shareability, the value written into ITS_CR1_SH is ignored.
> > + */
> > + cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_NO_READ_ALLOC) |
> > + FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_NON_CACHE) |
> > + FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_NON_CACHE);
> > + its_node->flags |= ITS_FLAGS_NON_COHERENT;
> > + } else {
> > + cr1 = FIELD_PREP(GICV5_ITS_CR1_ITT_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_ITS_CR1_DT_RA, GICV5_READ_ALLOC) |
> > + FIELD_PREP(GICV5_ITS_CR1_IC, GICV5_WB_CACHE) |
> > + FIELD_PREP(GICV5_ITS_CR1_OC, GICV5_WB_CACHE) |
> > + FIELD_PREP(GICV5_ITS_CR1_SH, GICV5_INNER_SHARE);
> > + }
> > +
> > + its_writel_relaxed(its_node, cr1, GICV5_ITS_CR1);
> > +
> > + ret = gicv5_its_init_devtab(its_node);
> > + if (ret) {
> > + irq_domain_remove(its_node->domain);
> > + kfree(info);
> > + kfree(its_node);
> > + return ret;
> > + }
> > +
> > + cr0 = FIELD_PREP(GICV5_ITS_CR0_ITSEN, 0x1);
> > + its_writel_relaxed(its_node, cr0, GICV5_ITS_CR0);
> > +
> > + ret = gicv5_its_wait_for_cr0(its_node);
> > + if (ret) {
> > + irq_domain_remove(its_node->domain);
> > + kfree(info);
> > + kfree(its_node);
> > + return ret;
> > + }
>
> All this error handling is a bit cumbersome. Use common error paths or
> whatever fancy new-age cleanup mechanism has been invented these days.
Sure.
> > +
> > + list_add(&its_node->entry, &its_nodes);
> > +
> > + gicv5_its_print_info(its_node);
> > +
> > + return 0;
> > +}
> > +
> > +static int __init gicv5_its_init(struct device_node *node)
> > +{
> > + void __iomem *its_base;
> > + struct resource res;
> > + int ret;
> > +
> > + its_base = of_io_request_and_map(node, 0, "ITS");
> > + if (IS_ERR(its_base)) {
> > + pr_err("%pOF: unable to map GICv5 ITS_CONFIG_FRAME\n", node);
> > + return PTR_ERR(its_base);
> > + }
> > +
> > + /*
> > + * The ITS_TRANSLATE_FRAME is the second reg entry, (first is the
> > + * ITS_CONFIG_FRAME) - extract it and use it to init ITS data
> > + * structures.
> > + */
> > + ret = of_address_to_resource(node, 1, &res);
> > + if (ret)
> > + goto out_unmap;
> > +
> > + ret = gicv5_its_init_bases(res.start, its_base, &node->fwnode,
> > + gicv5_global_data.lpi_domain);
> > + if (ret)
> > + goto out_unmap;
> > +
> > + return 0;
> > +out_unmap:
> > + iounmap(its_base);
> > + return ret;
> > +}
> > +
> > +void __init gicv5_its_of_probe(struct device_node *parent)
> > +{
> > + struct device_node *np;
> > +
> > + for_each_available_child_of_node(parent, np) {
> > + if (!of_device_is_compatible(np, "arm,gic-v5-its"))
> > + continue;
> > +
> > + if (gicv5_its_init(np))
> > + pr_err("Failed to init ITS %s\n", np->full_name);
> > + }
> > +}
> > diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> > index c4d4e85382f672fa4ae334db1a4e4c7c4f46b9fe..e483d0774936035b5cf2407da9a65d776bad3138 100644
> > --- a/drivers/irqchip/irq-gic-v5.c
> > +++ b/drivers/irqchip/irq-gic-v5.c
> > @@ -55,12 +55,12 @@ static void release_lpi(u32 lpi)
> > ida_free(&lpi_ida, lpi);
> > }
> >
> > -static int gicv5_alloc_lpi(void)
> > +int gicv5_alloc_lpi(void)
> > {
> > return alloc_lpi();
> > }
> >
> > -static void gicv5_free_lpi(u32 lpi)
> > +void gicv5_free_lpi(u32 lpi)
> > {
> > release_lpi(lpi);
> > }
> > @@ -1045,6 +1045,8 @@ static int __init gicv5_of_init(struct device_node *node,
> >
> > gicv5_smp_init();
> >
> > + gicv5_irs_its_probe();
> > +
> > return 0;
> > out_int:
> > gicv5_cpu_disable_interrupts();
> > diff --git a/drivers/irqchip/irq-gic-v5.h b/drivers/irqchip/irq-gic-v5.h
> > index 19569639153a084760c3b5b7f0fa84791ba0195c..f5a453599493020b36d9c7f18c08171c51ba8669 100644
> > --- a/drivers/irqchip/irq-gic-v5.h
> > +++ b/drivers/irqchip/irq-gic-v5.h
> > @@ -5,6 +5,8 @@
> > #ifndef __LINUX_IRQCHIP_GIC_V5_H
> > #define __LINUX_IRQCHIP_GIC_V5_H
> >
> > +#include <linux/iopoll.h>
> > +
> > #include <asm/arch_gicv5.h>
> > #include <asm/smp.h>
> >
> > @@ -41,6 +43,8 @@
> > #define GICV5_IRS_IDR7 0x001c
> > #define GICV5_IRS_CR0 0x0080
> > #define GICV5_IRS_CR1 0x0084
> > +#define GICV5_IRS_SYNCR 0x00c0
> > +#define GICV5_IRS_SYNC_STATUSR 0x00c4
> > #define GICV5_IRS_SPI_SELR 0x0108
> > #define GICV5_IRS_SPI_CFGR 0x0114
> > #define GICV5_IRS_SPI_STATUSR 0x0118
> > @@ -94,6 +98,10 @@
> > #define GICV5_IRS_CR1_OC GENMASK(3, 2)
> > #define GICV5_IRS_CR1_SH GENMASK(1, 0)
> >
> > +#define GICV5_IRS_SYNCR_SYNC BIT(31)
> > +
> > +#define GICV5_IRS_SYNC_STATUSR_IDLE BIT(0)
> > +
> > #define GICV5_IRS_SPI_STATUSR_V BIT(1)
> > #define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
> >
> > @@ -135,6 +143,101 @@
> >
> > #define GICV5_ISTL1E_L2_ADDR_MASK GENMASK_ULL(55, 12)
> >
> > +#define GICV5_ITS_IDR1 0x0004
> > +#define GICV5_ITS_IDR2 0x0008
> > +#define GICV5_ITS_CR0 0x0080
> > +#define GICV5_ITS_CR1 0x0084
> > +#define GICV5_ITS_DT_BASER 0x00c0
> > +#define GICV5_ITS_DT_CFGR 0x00d0
> > +#define GICV5_ITS_DIDR 0x0100
> > +#define GICV5_ITS_EIDR 0x0108
> > +#define GICV5_ITS_INV_EVENTR 0x010c
> > +#define GICV5_ITS_INV_DEVICER 0x0110
> > +#define GICV5_ITS_STATUSR 0x0120
> > +#define GICV5_ITS_SYNCR 0x0140
> > +#define GICV5_ITS_SYNC_STATUSR 0x0148
> > +
> > +#define GICV5_ITS_IDR1_L2SZ GENMASK(10, 8)
> > +#define GICV5_ITS_IDR1_ITT_LEVELS BIT(7)
> > +#define GICV5_ITS_IDR1_DT_LEVELS BIT(6)
> > +#define GICV5_ITS_IDR1_DEVICEID_BITS GENMASK(5, 0)
> > +
> > +#define GICV5_ITS_IDR1_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(8), (r))
> > +#define GICV5_ITS_IDR1_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(9), (r))
> > +#define GICV5_ITS_IDR1_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(10), (r))
> > +
> > +#define GICV5_ITS_IDR2_XDMN_EVENTs GENMASK(6, 5)
> > +#define GICV5_ITS_IDR2_EVENTID_BITS GENMASK(4, 0)
> > +
> > +#define GICV5_ITS_CR0_IDLE BIT(1)
> > +#define GICV5_ITS_CR0_ITSEN BIT(0)
> > +
> > +#define GICV5_ITS_CR1_ITT_RA BIT(7)
> > +#define GICV5_ITS_CR1_DT_RA BIT(6)
> > +#define GICV5_ITS_CR1_IC GENMASK(5, 4)
> > +#define GICV5_ITS_CR1_OC GENMASK(3, 2)
> > +#define GICV5_ITS_CR1_SH GENMASK(1, 0)
> > +
> > +#define GICV5_ITS_DT_CFGR_STRUCTURE BIT(16)
> > +#define GICV5_ITS_DT_CFGR_L2SZ GENMASK(7, 6)
> > +#define GICV5_ITS_DT_CFGR_DEVICEID_BITS GENMASK(5, 0)
> > +
> > +#define GICV5_ITS_DT_BASER_ADDR_MASK GENMASK_ULL(55, 3)
> > +
> > +#define GICV5_ITS_INV_DEVICER_I BIT(31)
> > +#define GICV5_ITS_INV_DEVICER_EVENTID_BITS GENMASK(5, 1)
> > +#define GICV5_ITS_INV_DEVICER_L1 BIT(0)
> > +
> > +#define GICV5_ITS_DIDR_DEVICEID GENMASK_ULL(31, 0)
> > +
> > +#define GICV5_ITS_EIDR_EVENTID GENMASK(15, 0)
> > +
> > +#define GICV5_ITS_INV_EVENTR_I BIT(31)
> > +#define GICV5_ITS_INV_EVENTR_ITT_L2SZ GENMASK(2, 1)
> > +#define GICV5_ITS_INV_EVENTR_L1 BIT(0)
> > +
> > +#define GICV5_ITS_STATUSR_IDLE BIT(0)
> > +
> > +#define GICV5_ITS_SYNCR_SYNC BIT_ULL(63)
> > +#define GICV5_ITS_SYNCR_SYNCALL BIT_ULL(32)
> > +#define GICV5_ITS_SYNCR_DEVICEID GENMASK_ULL(31, 0)
> > +
> > +#define GICV5_ITS_SYNC_STATUSR_IDLE BIT(0)
> > +
> > +#define GICV5_DTL1E_VALID BIT_ULL(0)
> > +// Note that there is no shift for the address by design
> > +#define GICV5_DTL1E_L2_ADDR_MASK GENMASK_ULL(55, 3)
> > +#define GICV5_DTL1E_SPAN GENMASK_ULL(63, 60)
> > +
> > +#define GICV5_DTL2E_VALID BIT_ULL(0)
> > +#define GICV5_DTL2E_ITT_L2SZ GENMASK_ULL(2, 1)
> > +// Note that there is no shift for the address by design
> > +#define GICV5_DTL2E_ITT_ADDR_MASK GENMASK_ULL(55, 3)
> > +#define GICV5_DTL2E_ITT_DSWE BIT_ULL(57)
> > +#define GICV5_DTL2E_ITT_STRUCTURE BIT_ULL(58)
> > +#define GICV5_DTL2E_EVENT_ID_BITS GENMASK_ULL(63, 59)
> > +
> > +#define GICV5_ITTL1E_VALID BIT_ULL(0)
> > +// Note that there is no shift for the address by design
> > +#define GICV5_ITTL1E_L2_ADDR_MASK GENMASK_ULL(55, 3)
> > +#define GICV5_ITTL1E_SPAN GENMASK_ULL(63, 60)
> > +
> > +#define GICV5_ITTL2E_LPI_ID GENMASK_ULL(23, 0)
> > +#define GICV5_ITTL2E_DAC GENMASK_ULL(29, 28)
> > +#define GICV5_ITTL2E_VIRTUAL BIT_ULL(30)
> > +#define GICV5_ITTL2E_VALID BIT_ULL(31)
> > +#define GICV5_ITTL2E_VM_ID GENMASK_ULL(47, 32)
> > +
> > +#define GICV5_ITS_DT_ITT_CFGR_L2SZ_4k 0b00
> > +#define GICV5_ITS_DT_ITT_CFGR_L2SZ_16k 0b01
> > +#define GICV5_ITS_DT_ITT_CFGR_L2SZ_64k 0b10
> > +
> > +#define GICV5_ITS_DT_ITT_CFGR_STRUCTURE_LINEAR 0
> > +#define GICV5_ITS_DT_ITT_CFGR_STRUCTURE_TWO_LEVEL 1
> > +
> > +#define GICV5_ITS_HWIRQ_DEVICE_ID GENMASK_ULL(31, 0)
> > +#define GICV5_ITS_HWIRQ_EVENT_ID GENMASK_ULL(63, 32)
> > +
> > struct gicv5_chip_data {
> > struct fwnode_handle *fwnode;
> > struct irq_domain *ppi_domain;
> > @@ -168,17 +271,90 @@ struct gicv5_irs_chip_data {
> >
> > void __init gicv5_init_lpi_domain(void);
> > void __init gicv5_free_lpi_domain(void);
> > +static inline int gicv5_wait_for_op(void __iomem *addr, u32 offset, u32 mask,
> > + u32 *val)
> > +{
> > + void __iomem *reg = addr + offset;
> > + u32 tmp;
> > + int ret;
> > +
> > + ret = readl_poll_timeout_atomic(reg, tmp, tmp & mask, 1, 10 * USEC_PER_MSEC);
> > +
> > + if (val)
> > + *val = tmp;
> > +
> > + return ret;
> > +}
> >
> > int gicv5_irs_of_probe(struct device_node *parent);
> > void gicv5_irs_remove(void);
> > int gicv5_irs_enable(void);
> > +void gicv5_irs_its_probe(void);
> > int gicv5_irs_register_cpu(int cpuid);
> > int gicv5_irs_cpu_to_iaffid(int cpu_id, u16 *iaffid);
> > struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id);
> > int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
> > int gicv5_spi_set_type(struct irq_data *d, unsigned int type);
> > int gicv5_irs_iste_alloc(u32 lpi);
> > +void gicv5_irs_syncr(void);
> > +
> > +struct gicv5_its_devtab_cfg {
> > + union {
> > + struct {
> > + __le64 *devtab;
> > + } linear;
> > + struct {
> > + __le64 *l1devtab;
> > + __le64 **l2ptrs;
> > + } l2;
> > + };
> > + u32 cfgr;
> > +};
> > +
> > +struct gicv5_its_itt_cfg {
> > + union {
> > + struct {
> > + __le64 *itt;
> > + unsigned int num_ents;
> > + } linear;
> > + struct {
> > + __le64 *l1itt;
> > + __le64 **l2ptrs;
> > + unsigned int num_l1_ents;
> > + u8 l2sz;
> > + } l2;
> > + };
> > + u8 event_id_bits;
> > + bool l2itt;
> > +};
> > +
> > +struct gicv5_its_chip_data {
> > + struct list_head entry;
> > + struct xarray its_devices;
> > + struct mutex dev_alloc_lock;
> > + struct fwnode_handle *fwnode;
> > + struct gicv5_its_devtab_cfg devtab_cfgr;
> > + struct irq_domain *domain;
> > + void __iomem *its_base;
> > + phys_addr_t its_trans_phys_base;
> > + u32 flags;
> > +};
> > +
> > +struct gicv5_its_dev {
> > + struct gicv5_its_chip_data *its_node;
> > + struct gicv5_its_itt_cfg itt_cfg;
> > + unsigned long *event_map;
> > + u32 device_id;
> > + u32 num_events;
> > + u32 num_mapped_events;
> > + bool shared;
> > +};
> >
> > void gicv5_init_lpis(u32 max);
> > void gicv5_deinit_lpis(void);
> > +
> > +int gicv5_alloc_lpi(void);
> > +void gicv5_free_lpi(u32 lpi);
> > +
> > +void __init gicv5_its_of_probe(struct device_node *parent);
> > #endif
>
> Thanks,
>
> M.
Thank you very much for the review !
Lorenzo
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-04-30 11:57 ` Marc Zyngier
@ 2025-04-30 13:27 ` Lorenzo Pieralisi
2025-05-01 13:27 ` Marc Zyngier
2025-04-30 16:25 ` Lorenzo Pieralisi
1 sibling, 1 reply; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-30 13:27 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Wed, Apr 30, 2025 at 12:57:01PM +0100, Marc Zyngier wrote:
> On Thu, 24 Apr 2025 11:25:32 +0100,
> Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> >
> > The GICv5 architecture implements the Interrupt Wire Bridge (IWB) in
> > order to support wired interrupts that cannot be connected directly
> > to an IRS and instead uses the ITS to translate a wire event into
> > an IRQ signal.
> >
> > An IWB is a special ITS device with its own deviceID; upon probe,
> > an IWB calls into the ITS driver to allocate DT/ITT tables for its
> > events (ie wires).
> >
> > An IWB is always associated with a single ITS in the system.
> >
> > An IWB is connected to an ITS and it has its own deviceID for all
> > interrupt wires that it manages; the IWB input wire number is
> > exposed to the ITS as an eventID. This eventID is not programmable
> > and therefore requires special handling in the ITS driver.
> >
> > Add an IWB driver in order to:
> >
> > - Probe IWBs in the system and allocate ITS tables
> > - Manage IWB IRQ domains
> > - Handle IWB input wires state (enable/disable)
> > - Add the required IWB IRQchip representation
> > - Handle firmware representation to Linux IRQ translation
> >
> > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > Cc: Thomas Gleixner <tglx@linutronix.de>
> > Cc: Marc Zyngier <maz@kernel.org>
> > ---
> > drivers/irqchip/Makefile | 2 +-
> > drivers/irqchip/irq-gic-v5-its.c | 68 ++++++--
> > drivers/irqchip/irq-gic-v5-iwb.c | 356 +++++++++++++++++++++++++++++++++++++++
> > drivers/irqchip/irq-gic-v5.c | 2 +
> > drivers/irqchip/irq-gic-v5.h | 28 +++
> > 5 files changed, 437 insertions(+), 19 deletions(-)
> >
> > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > index 4280395e3bdff7858102f0b4eaaea1121cace52f..7bfb2369fbe494a64b72308d95ae33de93c6b8c6 100644
> > --- a/drivers/irqchip/Makefile
> > +++ b/drivers/irqchip/Makefile
> > @@ -37,7 +37,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
> > obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> > obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> > obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> > -obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
> > +obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o irq-gic-v5-iwb.o
> > obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> > obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> > obj-$(CONFIG_ARM_VIC) += irq-vic.o
> > diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> > index da349b4709cc5ec8978859237838f039389ca4a1..b5eb4dbfe2296dc6620889eb9291b542cae4aeb6 100644
> > --- a/drivers/irqchip/irq-gic-v5-its.c
> > +++ b/drivers/irqchip/irq-gic-v5-its.c
> > @@ -786,9 +786,8 @@ static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *i
> > return dev ? dev : ERR_PTR(-ENODEV);
> > }
> >
> > -static struct gicv5_its_dev *gicv5_its_alloc_device(
> > - struct gicv5_its_chip_data *its, int nvec,
> > - u32 dev_id)
> > +struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
> > + int nvec, u32 dev_id, bool is_iwb)
> > {
> > struct gicv5_its_dev *its_dev;
> > int ret;
> > @@ -815,6 +814,7 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> > its_dev->device_id = dev_id;
> > its_dev->num_events = nvec;
> > its_dev->num_mapped_events = 0;
> > + its_dev->is_iwb = is_iwb;
> >
> > ret = gicv5_its_device_register(its, its_dev);
> > if (ret) {
> > @@ -827,9 +827,11 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> >
> > /*
> > * This is the first time we have seen this device. Hence, it is not
> > - * shared.
> > + * shared, unless it is an IWB that is a shared ITS device by
> > + * definition, its eventids are hardcoded and never change - we allocate
> > + * it once for all and never free it.
>
> I'm not convinced the IWB should be treated differently from any other
> device. Its lifetime is not tied to its inputs, so all that's needed
> is to probe it, get a bunch of interrupts, and that's about it.
I need to check again how this works for devices requesting wires
from an IWB if we don't allow ITS device sharing.
> The other thing is that the IWB really is a standalone thing. It
> shouldn't have its fingers in the ITS code, and should only rely on
> the core infrastructure to get its interrupts.
>
> As much as I dislike it, the MBIGEN actually provides a decent example
> of how this could be structured.
We wrote that code already, I should have posted it. An MBIgen can
programme the eventids it sents to the ITS, an IWB can't. So yes,
I can make an IWB MBIgen like but the ITS code has to know it is
allocating an IRQ for an IWB - one way or another, the eventids
are not programmable.
I will try to post a v3 with the code in it so that I can get flamed
and find a solution to this niggle.
Thanks,
Lorenzo
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-04-30 11:57 ` Marc Zyngier
2025-04-30 13:27 ` Lorenzo Pieralisi
@ 2025-04-30 16:25 ` Lorenzo Pieralisi
2025-05-01 14:15 ` Marc Zyngier
1 sibling, 1 reply; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-04-30 16:25 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Wed, Apr 30, 2025 at 12:57:01PM +0100, Marc Zyngier wrote:
> On Thu, 24 Apr 2025 11:25:32 +0100,
> Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> >
> > The GICv5 architecture implements the Interrupt Wire Bridge (IWB) in
> > order to support wired interrupts that cannot be connected directly
> > to an IRS and instead uses the ITS to translate a wire event into
> > an IRQ signal.
> >
> > An IWB is a special ITS device with its own deviceID; upon probe,
> > an IWB calls into the ITS driver to allocate DT/ITT tables for its
> > events (ie wires).
> >
> > An IWB is always associated with a single ITS in the system.
> >
> > An IWB is connected to an ITS and it has its own deviceID for all
> > interrupt wires that it manages; the IWB input wire number is
> > exposed to the ITS as an eventID. This eventID is not programmable
> > and therefore requires special handling in the ITS driver.
> >
> > Add an IWB driver in order to:
> >
> > - Probe IWBs in the system and allocate ITS tables
> > - Manage IWB IRQ domains
> > - Handle IWB input wires state (enable/disable)
> > - Add the required IWB IRQchip representation
> > - Handle firmware representation to Linux IRQ translation
> >
> > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > Cc: Thomas Gleixner <tglx@linutronix.de>
> > Cc: Marc Zyngier <maz@kernel.org>
> > ---
> > drivers/irqchip/Makefile | 2 +-
> > drivers/irqchip/irq-gic-v5-its.c | 68 ++++++--
> > drivers/irqchip/irq-gic-v5-iwb.c | 356 +++++++++++++++++++++++++++++++++++++++
> > drivers/irqchip/irq-gic-v5.c | 2 +
> > drivers/irqchip/irq-gic-v5.h | 28 +++
> > 5 files changed, 437 insertions(+), 19 deletions(-)
> >
> > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > index 4280395e3bdff7858102f0b4eaaea1121cace52f..7bfb2369fbe494a64b72308d95ae33de93c6b8c6 100644
> > --- a/drivers/irqchip/Makefile
> > +++ b/drivers/irqchip/Makefile
> > @@ -37,7 +37,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
> > obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> > obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> > obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> > -obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
> > +obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o irq-gic-v5-iwb.o
> > obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> > obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> > obj-$(CONFIG_ARM_VIC) += irq-vic.o
> > diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> > index da349b4709cc5ec8978859237838f039389ca4a1..b5eb4dbfe2296dc6620889eb9291b542cae4aeb6 100644
> > --- a/drivers/irqchip/irq-gic-v5-its.c
> > +++ b/drivers/irqchip/irq-gic-v5-its.c
> > @@ -786,9 +786,8 @@ static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *i
> > return dev ? dev : ERR_PTR(-ENODEV);
> > }
> >
> > -static struct gicv5_its_dev *gicv5_its_alloc_device(
> > - struct gicv5_its_chip_data *its, int nvec,
> > - u32 dev_id)
> > +struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
> > + int nvec, u32 dev_id, bool is_iwb)
> > {
> > struct gicv5_its_dev *its_dev;
> > int ret;
> > @@ -815,6 +814,7 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> > its_dev->device_id = dev_id;
> > its_dev->num_events = nvec;
> > its_dev->num_mapped_events = 0;
> > + its_dev->is_iwb = is_iwb;
> >
> > ret = gicv5_its_device_register(its, its_dev);
> > if (ret) {
> > @@ -827,9 +827,11 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> >
> > /*
> > * This is the first time we have seen this device. Hence, it is not
> > - * shared.
> > + * shared, unless it is an IWB that is a shared ITS device by
> > + * definition, its eventids are hardcoded and never change - we allocate
> > + * it once for all and never free it.
>
> I'm not convinced the IWB should be treated differently from any other
> device. Its lifetime is not tied to its inputs, so all that's needed
> is to probe it, get a bunch of interrupts, and that's about it.
>
> The other thing is that the IWB really is a standalone thing. It
> shouldn't have its fingers in the ITS code, and should only rely on
> the core infrastructure to get its interrupts.
>
> As much as I dislike it, the MBIGEN actually provides a decent example
> of how this could be structured.
I wrote a diff against the heavily reworked series in progress I have,
(so it does not apply on v2 - headers moved) with what I came up with
for the IWB MBIgen like. It works - it removes lots of boilerplate code
but there is a hack we never really liked in:
gicv5_its_msi_prepare()
that is, using the OF compatible string to detect if we are an IWB or not.
If we are, we use the msi_alloc_info_t->hwirq to define the LPI eventid,
basically the IWB wire, if not we just allocate an eventid available from
the device bitmap.
Other than that (and being forced to provide an IWB irqchip.irq_write_msi_msg()
pointer even if the IWB can't write anything otherwise we dereference
NULL) this works.
Is there a better way to implement this ? I would post this code with
v3 but instead of waiting I thought I could inline it here, feel free
to ignore it (or flame me if it is a solved problem I failed to spot,
we need to find a way for the IWB driver to pass the "fixed event" info
to the ITS - IWB eventIDs are hardwired it is not like the MBIgen where
the irq_write_msi_msg() callback programs the wire-to-eventid
translation in HW).
Thanks,
Lorenzo
-- >8 --
diff --git i/drivers/irqchip/irq-gic-v5-its.c w/drivers/irqchip/irq-gic-v5-its.c
index 7d35f3fe4fd9..96e8e1df53f3 100644
--- i/drivers/irqchip/irq-gic-v5-its.c
+++ w/drivers/irqchip/irq-gic-v5-its.c
@@ -826,13 +826,14 @@ static int gicv5_its_msi_prepare(struct irq_domain *domain, struct device *dev,
struct msi_domain_info *msi_info;
struct gicv5_its_chip_data *its;
struct gicv5_its_dev *its_dev;
+ bool is_iwb = of_device_is_compatible(dev->of_node, "arm,gic-v5-iwb");
msi_info = msi_get_domain_info(domain);
its = msi_info->data;
guard(mutex)(&its->dev_alloc_lock);
- its_dev = gicv5_its_alloc_device(its, nvec, dev_id, false);
+ its_dev = gicv5_its_alloc_device(its, nvec, dev_id, is_iwb);
if (IS_ERR(its_dev))
return PTR_ERR(its_dev);
@@ -929,7 +930,7 @@ static int gicv5_its_alloc_eventid(struct gicv5_its_dev *its_dev,
if (WARN(nr_irqs != 1, "IWB requesting nr_irqs != 1\n"))
return -EINVAL;
- event_id_base = info->scratchpad[1].ul;
+ event_id_base = info->hwirq;
if (event_id_base >= its_dev->num_events) {
pr_err("EventID ouside of ITT range; cannot allocate an ITT entry!\n");
diff --git i/drivers/irqchip/irq-gic-v5-iwb.c w/drivers/irqchip/irq-gic-v5-iwb.c
index c8bbfe877eda..ef801ca31f0c 100644
--- i/drivers/irqchip/irq-gic-v5-iwb.c
+++ w/drivers/irqchip/irq-gic-v5-iwb.c
@@ -11,6 +11,12 @@
#include <linux/msi.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+struct gicv5_iwb_chip_data {
+ void __iomem *iwb_base;
+ u16 nr_regs;
+};
static u32 iwb_readl_relaxed(struct gicv5_iwb_chip_data *iwb_node,
const u64 reg_offset)
@@ -74,6 +80,22 @@ static int gicv5_iwb_disable_wire(struct gicv5_iwb_chip_data *iwb_node,
return __gicv5_iwb_set_wire_enable(iwb_node, iwb_wire, false);
}
+static void gicv5_iwb_irq_disable(struct irq_data *d)
+{
+ struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
+
+ gicv5_iwb_disable_wire(iwb_node, d->hwirq);
+ irq_chip_disable_parent(d);
+}
+
+static void gicv5_iwb_irq_enable(struct irq_data *d)
+{
+ struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
+
+ gicv5_iwb_enable_wire(iwb_node, d->hwirq);
+ irq_chip_enable_parent(d);
+}
+
static int gicv5_iwb_set_type(struct irq_data *d, unsigned int type)
{
struct gicv5_iwb_chip_data *iwb_node = irq_data_get_irq_chip_data(d);
@@ -111,19 +133,11 @@ static int gicv5_iwb_set_type(struct irq_data *d, unsigned int type)
return 0;
}
-static const struct irq_chip gicv5_iwb_chip = {
- .name = "GICv5-IWB",
- .irq_mask = irq_chip_mask_parent,
- .irq_unmask = irq_chip_unmask_parent,
- .irq_eoi = irq_chip_eoi_parent,
- .irq_set_type = gicv5_iwb_set_type,
- .irq_set_affinity = irq_chip_set_affinity_parent,
- .irq_get_irqchip_state = irq_chip_get_parent_state,
- .irq_set_irqchip_state = irq_chip_set_parent_state,
- .flags = IRQCHIP_SET_TYPE_MASKED |
- IRQCHIP_SKIP_SET_WAKE |
- IRQCHIP_MASK_ON_SUSPEND
-};
+static void gicv5_iwb_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
+{
+ arg->desc = desc;
+ arg->hwirq = (u32)desc->data.icookie.value;
+}
static int gicv5_iwb_irq_domain_translate(struct irq_domain *d,
struct irq_fwspec *fwspec,
@@ -146,123 +160,67 @@ static int gicv5_iwb_irq_domain_translate(struct irq_domain *d,
return 0;
}
-static void gicv5_iwb_irq_domain_free(struct irq_domain *domain,
- unsigned int virq, unsigned int nr_irqs)
-{
- /* Free the local data, and then go up the hierarchy doing the same */
- struct gicv5_iwb_chip_data *iwb_node = domain->host_data;
- struct irq_data *data;
+static void gicv5_iwb_write_msi_msg(struct irq_data *d, struct msi_msg *msg) {}
- if (WARN_ON_ONCE(nr_irqs != 1))
- return;
+static const struct msi_domain_template iwb_msi_template = {
+ .chip = {
+ .name = "GICv5-IWB",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_enable = gicv5_iwb_irq_enable,
+ .irq_disable = gicv5_iwb_irq_disable,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_type = gicv5_iwb_set_type,
+ .irq_write_msi_msg = gicv5_iwb_write_msi_msg,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED |
+ IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND
+ },
- data = irq_domain_get_irq_data(domain, virq);
- gicv5_iwb_disable_wire(iwb_node, data->hwirq);
+ .ops = {
+ .set_desc = gicv5_iwb_domain_set_desc,
+ .msi_translate = gicv5_iwb_irq_domain_translate,
+ },
- irq_domain_reset_irq_data(data);
-
- irq_domain_free_irqs_parent(domain, virq, nr_irqs);
-}
-
-/*
- * Our parent is the ITS, which expects MSI devices with programmable
- * event IDs. IWB event IDs are hardcoded.
- *
- * Use the msi_alloc_info_t structure to convey both our DeviceID
- * (scratchpad[0]), and the wire that we are attempting to map to an LPI in
- * the ITT (scratchpad[1]).
- */
-static int iwb_alloc_lpi_irq_parent(struct irq_domain *domain,
- unsigned int virq, irq_hw_number_t hwirq)
-{
- struct gicv5_iwb_chip_data *iwb_node = domain->host_data;
- msi_alloc_info_t info;
-
- info.scratchpad[0].ul = iwb_node->device_id;
- info.scratchpad[1].ul = hwirq;
- info.hwirq = hwirq;
-
- return irq_domain_alloc_irqs_parent(domain, virq, 1, &info);
-}
-
-static int gicv5_iwb_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
- unsigned int nr_irqs, void *arg)
-{
- struct gicv5_iwb_chip_data *iwb_node;
- unsigned int type = IRQ_TYPE_NONE;
- struct irq_fwspec *fwspec = arg;
- irq_hw_number_t hwirq;
- struct irq_data *irqd;
- int ret;
-
- if (WARN_ON_ONCE(nr_irqs != 1))
- return -EINVAL;
-
- ret = gicv5_iwb_irq_domain_translate(domain, fwspec, &hwirq, &type);
- if (ret)
- return ret;
-
- if (iwb_alloc_lpi_irq_parent(domain, virq, hwirq))
- return -EINVAL;
-
- irqd = irq_get_irq_data(virq);
- iwb_node = domain->host_data;
-
- gicv5_iwb_enable_wire(iwb_node, hwirq);
-
- irq_domain_set_info(domain, virq, hwirq, &gicv5_iwb_chip, iwb_node,
- handle_fasteoi_irq, NULL, NULL);
- irq_set_probe(virq);
- irqd_set_single_target(irqd);
-
- return 0;
-}
-
-static const struct irq_domain_ops gicv5_iwb_irq_domain_ops = {
- .translate = gicv5_iwb_irq_domain_translate,
- .alloc = gicv5_iwb_irq_domain_alloc,
- .free = gicv5_iwb_irq_domain_free,
+ .info = {
+ .bus_token = DOMAIN_BUS_WIRED_TO_MSI,
+ .flags = MSI_FLAG_USE_DEV_FWNODE,
+ },
};
+static bool gicv5_iwb_create_device_domain(struct device *dev, unsigned int size,
+ struct gicv5_iwb_chip_data *iwb_node)
+{
+ if (WARN_ON_ONCE(!dev->msi.domain))
+ return false;
+
+ return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN,
+ &iwb_msi_template, size,
+ NULL, iwb_node);
+}
+
static struct gicv5_iwb_chip_data *
-__init gicv5_iwb_init_bases(void __iomem *iwb_base,
- struct fwnode_handle *handle,
- struct irq_domain *parent_domain, u32 device_id)
+gicv5_iwb_init_bases(void __iomem *iwb_base, struct platform_device *pdev)
{
struct gicv5_iwb_chip_data *iwb_node;
- struct msi_domain_info *msi_info;
- struct gicv5_its_chip_data *its;
- struct gicv5_its_dev *its_dev;
u32 nr_wires, idr0, cr0;
int ret;
- msi_info = msi_get_domain_info(parent_domain);
- its = msi_info->data;
- if (!its) {
- pr_warn("IWB %pOF can't find parent ITS, bailing\n",
- to_of_node(handle));
- return ERR_PTR(-ENODEV);
- }
-
iwb_node = kzalloc(sizeof(*iwb_node), GFP_KERNEL);
if (!iwb_node)
return ERR_PTR(-ENOMEM);
iwb_node->iwb_base = iwb_base;
- iwb_node->device_id = device_id;
idr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_IDR0);
nr_wires = (FIELD_GET(GICV5_IWB_IDR0_IW_RANGE, idr0) + 1) * 32;
- iwb_node->domain = irq_domain_create_hierarchy(parent_domain, 0,
- nr_wires, handle, &gicv5_iwb_irq_domain_ops,
- iwb_node);
- irq_domain_update_bus_token(iwb_node->domain, DOMAIN_BUS_WIRED);
-
cr0 = iwb_readl_relaxed(iwb_node, GICV5_IWB_CR0);
if (!FIELD_GET(GICV5_IWB_CR0_IWBEN, cr0)) {
- pr_err("IWB %s must be enabled in firmware\n",
- fwnode_get_name(handle));
+ dev_err(&pdev->dev, "IWB must be enabled in firmware\n");
ret = -EINVAL;
goto out_free;
}
@@ -276,80 +234,60 @@ __init gicv5_iwb_init_bases(void __iomem *iwb_base,
if (ret)
goto out_free;
- mutex_lock(&its->dev_alloc_lock);
- its_dev = gicv5_its_alloc_device(its, roundup_pow_of_two(nr_wires),
- device_id, true);
- mutex_unlock(&its->dev_alloc_lock);
- if (IS_ERR(its_dev)) {
- ret = -ENODEV;
+ if (!gicv5_iwb_create_device_domain(&pdev->dev, nr_wires, iwb_node)) {
+ ret = -ENOMEM;
goto out_free;
}
return iwb_node;
out_free:
- irq_domain_remove(iwb_node->domain);
kfree(iwb_node);
return ERR_PTR(ret);
}
-static int __init gicv5_iwb_of_init(struct device_node *node)
+static int gicv5_iwb_device_probe(struct platform_device *pdev)
{
struct gicv5_iwb_chip_data *iwb_node;
- struct irq_domain *parent_domain;
- struct device_node *parent_its;
- struct of_phandle_args args;
void __iomem *iwb_base;
- u32 device_id;
+ struct resource *res;
int ret;
- iwb_base = of_io_request_and_map(node, 0, "IWB");
- if (IS_ERR(iwb_base)) {
- pr_err("%pOF: unable to map GICv5 IWB registers\n", node);
- return PTR_ERR(iwb_base);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ iwb_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (!iwb_base) {
+ dev_err(&pdev->dev, "failed to ioremap %pR\n", res);
+ return -ENOMEM;
}
- ret = of_parse_phandle_with_args(node, "msi-parent", "#msi-cells", 0,
- &args);
- if (ret) {
- pr_err("%pOF: Can't retrieve deviceID\n", node);
+ iwb_node = gicv5_iwb_init_bases(iwb_base, pdev);
+ if (IS_ERR(iwb_node)) {
+ ret = PTR_ERR(iwb_node);
goto out_unmap;
}
- parent_its = args.np;
- parent_domain = irq_find_matching_host(parent_its, DOMAIN_BUS_NEXUS);
- if (!parent_domain) {
- pr_err("Unable to find the parent ITS domain for %pOF!\n", node);
- ret = -ENXIO;
- goto out_put;
- }
-
- device_id = args.args[0];
- pr_debug("IWB deviceID: 0x%x\n", device_id);
-
- iwb_node = gicv5_iwb_init_bases(iwb_base, &node->fwnode, parent_domain,
- device_id);
- if (IS_ERR(iwb_node)) {
- ret = PTR_ERR(iwb_node);
- goto out_put;
- }
-
return 0;
-out_put:
- of_node_put(parent_its);
out_unmap:
iounmap(iwb_base);
return ret;
}
-void __init gicv5_iwb_of_probe(void)
-{
- struct device_node *np;
- int ret;
+static const struct of_device_id gicv5_iwb_of_match[] = {
+ { .compatible = "arm,gic-v5-iwb" },
+ { /* END */ }
+};
+MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match);
- for_each_compatible_node(np, NULL, "arm,gic-v5-iwb") {
- ret = gicv5_iwb_of_init(np);
- if (ret)
- pr_err("Failed to init IWB %s\n", np->full_name);
- }
-}
+static struct platform_driver gicv5_iwb_platform_driver = {
+ .driver = {
+ .name = "GICv5 IWB",
+ .of_match_table = gicv5_iwb_of_match,
+ .suppress_bind_attrs = true,
+ },
+ .probe = gicv5_iwb_device_probe,
+};
+
+module_platform_driver(gicv5_iwb_platform_driver);
diff --git i/drivers/irqchip/irq-gic-v5.c w/drivers/irqchip/irq-gic-v5.c
index 1d00ad1cd1fc..c1f26718c350 100644
--- i/drivers/irqchip/irq-gic-v5.c
+++ w/drivers/irqchip/irq-gic-v5.c
@@ -1030,8 +1030,6 @@ static int __init gicv5_of_init(struct device_node *node,
gicv5_irs_its_probe();
- gicv5_iwb_of_probe();
-
return 0;
out_int:
gicv5_cpu_disable_interrupts();
diff --git i/include/linux/irqchip/arm-gic-v5.h w/include/linux/irqchip/arm-gic-v5.h
index e28e08bccbb3..05e17223733d 100644
--- i/include/linux/irqchip/arm-gic-v5.h
+++ w/include/linux/irqchip/arm-gic-v5.h
@@ -372,17 +372,4 @@ int gicv5_alloc_lpi(void);
void gicv5_free_lpi(u32 lpi);
void __init gicv5_its_of_probe(struct device_node *parent);
-struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
- int nvec, u32 dev_id, bool is_iwb);
-
-struct gicv5_iwb_chip_data {
- void __iomem *iwb_base;
- struct irq_domain *domain;
- u64 flags;
- u32 device_id;
- u16 nr_regs;
-};
-
-void gicv5_iwb_of_probe(void);
-
#endif
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support
2025-04-30 13:21 ` Lorenzo Pieralisi
@ 2025-05-01 9:01 ` Marc Zyngier
0 siblings, 0 replies; 43+ messages in thread
From: Marc Zyngier @ 2025-05-01 9:01 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Wed, 30 Apr 2025 14:21:00 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> On Wed, Apr 30, 2025 at 10:12:58AM +0100, Marc Zyngier wrote:
> > On Thu, 24 Apr 2025 11:25:31 +0100,
> > Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
[...]
> > >
> > > +void gicv5_irs_syncr(void)
> > > +{
> > > + struct gicv5_irs_chip_data *irs_data;
> > > + u32 syncr;
> > > +
> > > + irs_data = list_first_entry_or_null(&irs_nodes,
> > > + struct gicv5_irs_chip_data, entry);
> > > + if (WARN_ON(!irs_data))
> > > + return;
> > > +
> > > + syncr = FIELD_PREP(GICV5_IRS_SYNCR_SYNC, 1);
> > > + irs_writel_relaxed(irs_data, syncr, GICV5_IRS_SYNCR);
> > > +
> > > + gicv5_irs_wait_for_op(irs_data->irs_base, GICV5_IRS_SYNC_STATUSR,
> > > + GICV5_IRS_SYNC_STATUSR_IDLE);
> > > +}
> > > +
> >
> > Only the ITS code is using this function. Why isn't it in the ITS code
> > as a static helper?
>
> I'd need to make irs_nodes global.
You could simply have a helper returning the first IRS node. Not a big
deal anyway.
[...]
> > > + /*
> > > + * Need to determine how many entries there are per L2 - this is based
> > > + * on the number of bits in the table.
> > > + */
> > > + events_per_l2_table = BIT(l2_bits);
> > > + complete_tables = num_events / events_per_l2_table;
> > > + final_span = order_base_2(num_events % events_per_l2_table);
> > > +
> > > + for (i = 0; i < num_ents; i++) {
> > > + size_t l2sz;
> > > +
> > > + span = i == complete_tables ? final_span : l2_bits;
> > > +
> > > + itt_l2 = kcalloc(BIT(span), sizeof(*itt_l2), GFP_KERNEL);
> > > + if (!itt_l2) {
> > > + ret = -ENOMEM;
> > > + goto out_free;
> > > + }
> >
> > You are allocating a bunch of 64bit pointers. So the alignment is
> > BIT(span + 3) or ARCH_KMALLOC_MINALIGN, whichever is the largest.
>
> Right, at least 8 bytes.
>
> > > +
> > > + its_dev->itt_cfg.l2.l2ptrs[i] = itt_l2;
> > > +
> > > + l2sz = BIT(span) * sizeof(*itt_l2);
> > > +
> > > + if (its->flags & ITS_FLAGS_NON_COHERENT)
> > > + dcache_clean_inval_poc((unsigned long)itt_l2,
> > > + (unsigned long)itt_l2 + l2sz);
> > > +
> > > + val = (virt_to_phys(itt_l2) & GICV5_ITTL1E_L2_ADDR_MASK) |
> > > + FIELD_PREP(GICV5_ITTL1E_SPAN, span) |
> > > + FIELD_PREP(GICV5_ITTL1E_VALID, 0x1);
> >
> > GICV5_ITTL1E_L2_ADDR_MASK starts at bit 12.
>
> No, it starts at bit 3.
Ah, you're absolutely right. I looked at the IST version...
[...]
> > > +{
> > > + struct gicv5_its_dev *its_dev;
> > > + int ret;
> > > +
> > > + its_dev = gicv5_its_find_device(its, dev_id);
> > > + if (!IS_ERR(its_dev)) {
> > > + pr_debug("A device with this DeviceID (0x%x) has already been registered.\n",
> > > + dev_id);
> > > +
> > > + if (nvec > its_dev->num_events) {
> > > + pr_debug("Requesting more ITT entries than allocated\n");
> > > + return ERR_PTR(-ENXIO);
> > > + }
> > > +
> > > + its_dev->shared = true;
> > > +
> > > + return its_dev;
> >
> > I really think we shouldn't even consider the silliness of
> > non-transparent bridges this time around. That's a terrible system
> > design, and it leads to all sorts of lifetime madness -- the GICv3
> > driver is a testament to it. Modern systems with GICv5 should not have
> > to deal with this nonsense.
>
> I am not sure we can remove this path for the IWB - even if we model it
> as an MBIgen.
Why? The IWB is (or rather should be) seen as a device. The fact that
it is itself an interrupt controller is am independent issue.
> With Sascha and Tim we tested this code path, I am not sure it would
> work if a driver with a wired IRQ connected to an IWB free an IRQ and
> the ITS device representing the IWB is not shared.
I don't think freeing the IRQ from the end-point perspective should
have any effect on the IWB. At probe time, the IWB should grab all the
LPIs it needs, publish them as part of the wired domain attached to
its fwnode, and be done with it
[...]
> > > +static int gicv5_its_irq_domain_activate(struct irq_domain *domain,
> > > + struct irq_data *d, bool reserve)
> > > +{
> > > + struct gicv5_its_dev *its_dev = irq_data_get_irq_chip_data(d);
> > > + u16 event_id;
> > > + u32 lpi;
> > > +
> > > + event_id = FIELD_GET(GICV5_ITS_HWIRQ_EVENT_ID, d->hwirq);
> > > + lpi = d->parent_data->hwirq;
> > > +
> > > + return gicv5_its_alloc_event(its_dev, event_id, lpi);
> >
> > Huh. This looks wrong. Allocating the event really should happen at
> > alloc time, not at activate time, because the endpoint driver doesn't
> > really expect this to fail for any reason other than a gross bug.
> >
> > activate should allow the translation to take place, but not rely on
> > allocating events. Compare with GICv3, which only issues the MAPTI
> > command at activate time.
>
> I am not "allocating an event" (well, then you would say "learn how to
> name your functions" and you are right), I am writing the ITT table for
> an eventid that was preallocated before, so basically, apart from
> paranoia checks, this is the MAPTI equivalent.
Feels like *a lot* of paranoia checks, most of which should not be
possible by construction. You can also get rid of num_mapped_events,
which is clearly some debug stuff.
And yes, this function can do with a bit of renaming.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-04-30 13:27 ` Lorenzo Pieralisi
@ 2025-05-01 13:27 ` Marc Zyngier
2025-05-02 7:59 ` Lorenzo Pieralisi
0 siblings, 1 reply; 43+ messages in thread
From: Marc Zyngier @ 2025-05-01 13:27 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Wed, 30 Apr 2025 14:27:22 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> On Wed, Apr 30, 2025 at 12:57:01PM +0100, Marc Zyngier wrote:
> > On Thu, 24 Apr 2025 11:25:32 +0100,
> > Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> > >
> > > The GICv5 architecture implements the Interrupt Wire Bridge (IWB) in
> > > order to support wired interrupts that cannot be connected directly
> > > to an IRS and instead uses the ITS to translate a wire event into
> > > an IRQ signal.
> > >
> > > An IWB is a special ITS device with its own deviceID; upon probe,
> > > an IWB calls into the ITS driver to allocate DT/ITT tables for its
> > > events (ie wires).
> > >
> > > An IWB is always associated with a single ITS in the system.
> > >
> > > An IWB is connected to an ITS and it has its own deviceID for all
> > > interrupt wires that it manages; the IWB input wire number is
> > > exposed to the ITS as an eventID. This eventID is not programmable
> > > and therefore requires special handling in the ITS driver.
> > >
> > > Add an IWB driver in order to:
> > >
> > > - Probe IWBs in the system and allocate ITS tables
> > > - Manage IWB IRQ domains
> > > - Handle IWB input wires state (enable/disable)
> > > - Add the required IWB IRQchip representation
> > > - Handle firmware representation to Linux IRQ translation
> > >
> > > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > > Cc: Thomas Gleixner <tglx@linutronix.de>
> > > Cc: Marc Zyngier <maz@kernel.org>
> > > ---
> > > drivers/irqchip/Makefile | 2 +-
> > > drivers/irqchip/irq-gic-v5-its.c | 68 ++++++--
> > > drivers/irqchip/irq-gic-v5-iwb.c | 356 +++++++++++++++++++++++++++++++++++++++
> > > drivers/irqchip/irq-gic-v5.c | 2 +
> > > drivers/irqchip/irq-gic-v5.h | 28 +++
> > > 5 files changed, 437 insertions(+), 19 deletions(-)
> > >
> > > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > > index 4280395e3bdff7858102f0b4eaaea1121cace52f..7bfb2369fbe494a64b72308d95ae33de93c6b8c6 100644
> > > --- a/drivers/irqchip/Makefile
> > > +++ b/drivers/irqchip/Makefile
> > > @@ -37,7 +37,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
> > > obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> > > obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> > > obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> > > -obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
> > > +obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o irq-gic-v5-iwb.o
> > > obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> > > obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> > > obj-$(CONFIG_ARM_VIC) += irq-vic.o
> > > diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> > > index da349b4709cc5ec8978859237838f039389ca4a1..b5eb4dbfe2296dc6620889eb9291b542cae4aeb6 100644
> > > --- a/drivers/irqchip/irq-gic-v5-its.c
> > > +++ b/drivers/irqchip/irq-gic-v5-its.c
> > > @@ -786,9 +786,8 @@ static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *i
> > > return dev ? dev : ERR_PTR(-ENODEV);
> > > }
> > >
> > > -static struct gicv5_its_dev *gicv5_its_alloc_device(
> > > - struct gicv5_its_chip_data *its, int nvec,
> > > - u32 dev_id)
> > > +struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
> > > + int nvec, u32 dev_id, bool is_iwb)
> > > {
> > > struct gicv5_its_dev *its_dev;
> > > int ret;
> > > @@ -815,6 +814,7 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> > > its_dev->device_id = dev_id;
> > > its_dev->num_events = nvec;
> > > its_dev->num_mapped_events = 0;
> > > + its_dev->is_iwb = is_iwb;
> > >
> > > ret = gicv5_its_device_register(its, its_dev);
> > > if (ret) {
> > > @@ -827,9 +827,11 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> > >
> > > /*
> > > * This is the first time we have seen this device. Hence, it is not
> > > - * shared.
> > > + * shared, unless it is an IWB that is a shared ITS device by
> > > + * definition, its eventids are hardcoded and never change - we allocate
> > > + * it once for all and never free it.
> >
> > I'm not convinced the IWB should be treated differently from any other
> > device. Its lifetime is not tied to its inputs, so all that's needed
> > is to probe it, get a bunch of interrupts, and that's about it.
>
> I need to check again how this works for devices requesting wires
> from an IWB if we don't allow ITS device sharing.
There is no sharing. Each IWB has its own devid, and the endpoint
drivers don't have to know about anything ITS related.
>
> > The other thing is that the IWB really is a standalone thing. It
> > shouldn't have its fingers in the ITS code, and should only rely on
> > the core infrastructure to get its interrupts.
> >
> > As much as I dislike it, the MBIGEN actually provides a decent example
> > of how this could be structured.
>
> We wrote that code already, I should have posted it. An MBIgen can
> programme the eventids it sents to the ITS, an IWB can't. So yes,
> I can make an IWB MBIgen like but the ITS code has to know it is
> allocating an IRQ for an IWB - one way or another, the eventids
> are not programmable.
They are not programmable on the MBIGEN either, despite what the code
does. Everything on this HW is hardcoded.
> I will try to post a v3 with the code in it so that I can get flamed
> and find a solution to this niggle.
Nobody is flaming you, Lorenzo. We're just trying to get to a point
where the code is in a good enough shape to be merged.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-04-30 16:25 ` Lorenzo Pieralisi
@ 2025-05-01 14:15 ` Marc Zyngier
2025-05-02 8:04 ` Lorenzo Pieralisi
0 siblings, 1 reply; 43+ messages in thread
From: Marc Zyngier @ 2025-05-01 14:15 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Wed, 30 Apr 2025 17:25:24 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> I wrote a diff against the heavily reworked series in progress I have,
> (so it does not apply on v2 - headers moved) with what I came up with
> for the IWB MBIgen like. It works - it removes lots of boilerplate code
> but there is a hack we never really liked in:
>
> gicv5_its_msi_prepare()
>
> that is, using the OF compatible string to detect if we are an IWB or not.
You shouldn't need that. The MSI_FLAG_USE_DEV_FWNODE should be a good
enough indication that this is something of interest, and that ends-up
in the .init_dev_msi_info() callback.
> If we are, we use the msi_alloc_info_t->hwirq to define the LPI eventid,
> basically the IWB wire, if not we just allocate an eventid available from
> the device bitmap.
>
> Other than that (and being forced to provide an IWB irqchip.irq_write_msi_msg()
> pointer even if the IWB can't write anything otherwise we dereference
> NULL) this works.
Not even MBIGEN allows you to change the event. If you really want to
ensure things are even tighter, invent a MSI_FLAG_HARDCODED_MSG flag,
and pass that down the prepare path.
> Is there a better way to implement this ? I would post this code with
> v3 but instead of waiting I thought I could inline it here, feel free
> to ignore it (or flame me if it is a solved problem I failed to spot,
> we need to find a way for the IWB driver to pass the "fixed event" info
> to the ITS - IWB eventIDs are hardwired it is not like the MBIgen where
> the irq_write_msi_msg() callback programs the wire-to-eventid
> translation in HW).
It's *exactly* the same. And see above for a potential explicit
solution. The empty irq_write_msi_msg() is not a problem. It's
actually pretty clean, given how the whole thing works.
Please fold this into your v3, and we'll take it from there.
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 15/22] arm64: Disable GICv5 read/write/instruction traps
2025-04-24 10:25 ` [PATCH v2 15/22] arm64: Disable GICv5 read/write/instruction traps Lorenzo Pieralisi
@ 2025-05-01 14:32 ` Marc Zyngier
0 siblings, 0 replies; 43+ messages in thread
From: Marc Zyngier @ 2025-05-01 14:32 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Thu, 24 Apr 2025 11:25:26 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> GICv5 trap configuration registers value is UNKNOWN at reset.
>
> Initialize GICv5 EL2 trap configuration registers to prevent
> trapping GICv5 instruction/register access upon entering the
> kernel.
>
> Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> Cc: Will Deacon <will@kernel.org>
> Cc: Catalin Marinas <catalin.marinas@arm.com>
> Cc: Marc Zyngier <maz@kernel.org>
> ---
> arch/arm64/include/asm/el2_setup.h | 45 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 45 insertions(+)
>
> diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h
> index ebceaae3c749b84395c9c5eccf0caf874697ad11..1e362bb3b042d51fff15a7c2abc73842930b275a 100644
> --- a/arch/arm64/include/asm/el2_setup.h
> +++ b/arch/arm64/include/asm/el2_setup.h
> @@ -165,6 +165,50 @@
> .Lskip_gicv3_\@:
> .endm
>
> +/* GICv5 system register access */
> +.macro __init_el2_gicv5
> + mrs_s x0, SYS_ID_AA64PFR2_EL1
> + ubfx x0, x0, #ID_AA64PFR2_EL1_GCIE_SHIFT, #4
> + cbz x0, .Lskip_gicv5_\@
> +
> + mov x0, #(1 << ICH_HFGITR_EL2_GICRCDNMIA_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICRCDIA_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCDDI_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCDEOI_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCDHM_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCRDRCFG_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCDPEND_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCDAFF_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCDPRI_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCDDIS_SHIFT | \
> + 1 << ICH_HFGITR_EL2_GICCDEN_SHIFT)
Please write this as:
mov x0, #(ICH_HFGITR_EL2_GICRCDNMIA | \
ICH_HFGITR_EL2_GICRCDIA | \
ICH_HFGITR_EL2_GICCDDI | \
ICH_HFGITR_EL2_GICCDEOI | \
ICH_HFGITR_EL2_GICCDHM | \
ICH_HFGITR_EL2_GICCRDRCFG | \
ICH_HFGITR_EL2_GICCDPEND | \
ICH_HFGITR_EL2_GICCDAFF | \
ICH_HFGITR_EL2_GICCDPRI | \
ICH_HFGITR_EL2_GICCDDIS | \
ICH_HFGITR_EL2_GICCDEN)
which has the exact same effect, and is consistent with other uses in
this file.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-05-01 13:27 ` Marc Zyngier
@ 2025-05-02 7:59 ` Lorenzo Pieralisi
2025-05-02 14:50 ` Marc Zyngier
2025-05-02 15:43 ` Marc Zyngier
0 siblings, 2 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-05-02 7:59 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Thu, May 01, 2025 at 02:27:26PM +0100, Marc Zyngier wrote:
> On Wed, 30 Apr 2025 14:27:22 +0100,
> Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> >
> > On Wed, Apr 30, 2025 at 12:57:01PM +0100, Marc Zyngier wrote:
> > > On Thu, 24 Apr 2025 11:25:32 +0100,
> > > Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> > > >
> > > > The GICv5 architecture implements the Interrupt Wire Bridge (IWB) in
> > > > order to support wired interrupts that cannot be connected directly
> > > > to an IRS and instead uses the ITS to translate a wire event into
> > > > an IRQ signal.
> > > >
> > > > An IWB is a special ITS device with its own deviceID; upon probe,
> > > > an IWB calls into the ITS driver to allocate DT/ITT tables for its
> > > > events (ie wires).
> > > >
> > > > An IWB is always associated with a single ITS in the system.
> > > >
> > > > An IWB is connected to an ITS and it has its own deviceID for all
> > > > interrupt wires that it manages; the IWB input wire number is
> > > > exposed to the ITS as an eventID. This eventID is not programmable
> > > > and therefore requires special handling in the ITS driver.
> > > >
> > > > Add an IWB driver in order to:
> > > >
> > > > - Probe IWBs in the system and allocate ITS tables
> > > > - Manage IWB IRQ domains
> > > > - Handle IWB input wires state (enable/disable)
> > > > - Add the required IWB IRQchip representation
> > > > - Handle firmware representation to Linux IRQ translation
> > > >
> > > > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > > > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > > > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > > > Cc: Thomas Gleixner <tglx@linutronix.de>
> > > > Cc: Marc Zyngier <maz@kernel.org>
> > > > ---
> > > > drivers/irqchip/Makefile | 2 +-
> > > > drivers/irqchip/irq-gic-v5-its.c | 68 ++++++--
> > > > drivers/irqchip/irq-gic-v5-iwb.c | 356 +++++++++++++++++++++++++++++++++++++++
> > > > drivers/irqchip/irq-gic-v5.c | 2 +
> > > > drivers/irqchip/irq-gic-v5.h | 28 +++
> > > > 5 files changed, 437 insertions(+), 19 deletions(-)
> > > >
> > > > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > > > index 4280395e3bdff7858102f0b4eaaea1121cace52f..7bfb2369fbe494a64b72308d95ae33de93c6b8c6 100644
> > > > --- a/drivers/irqchip/Makefile
> > > > +++ b/drivers/irqchip/Makefile
> > > > @@ -37,7 +37,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
> > > > obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> > > > obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> > > > obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> > > > -obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
> > > > +obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o irq-gic-v5-iwb.o
> > > > obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> > > > obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> > > > obj-$(CONFIG_ARM_VIC) += irq-vic.o
> > > > diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> > > > index da349b4709cc5ec8978859237838f039389ca4a1..b5eb4dbfe2296dc6620889eb9291b542cae4aeb6 100644
> > > > --- a/drivers/irqchip/irq-gic-v5-its.c
> > > > +++ b/drivers/irqchip/irq-gic-v5-its.c
> > > > @@ -786,9 +786,8 @@ static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *i
> > > > return dev ? dev : ERR_PTR(-ENODEV);
> > > > }
> > > >
> > > > -static struct gicv5_its_dev *gicv5_its_alloc_device(
> > > > - struct gicv5_its_chip_data *its, int nvec,
> > > > - u32 dev_id)
> > > > +struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
> > > > + int nvec, u32 dev_id, bool is_iwb)
> > > > {
> > > > struct gicv5_its_dev *its_dev;
> > > > int ret;
> > > > @@ -815,6 +814,7 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> > > > its_dev->device_id = dev_id;
> > > > its_dev->num_events = nvec;
> > > > its_dev->num_mapped_events = 0;
> > > > + its_dev->is_iwb = is_iwb;
> > > >
> > > > ret = gicv5_its_device_register(its, its_dev);
> > > > if (ret) {
> > > > @@ -827,9 +827,11 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> > > >
> > > > /*
> > > > * This is the first time we have seen this device. Hence, it is not
> > > > - * shared.
> > > > + * shared, unless it is an IWB that is a shared ITS device by
> > > > + * definition, its eventids are hardcoded and never change - we allocate
> > > > + * it once for all and never free it.
> > >
> > > I'm not convinced the IWB should be treated differently from any other
> > > device. Its lifetime is not tied to its inputs, so all that's needed
> > > is to probe it, get a bunch of interrupts, and that's about it.
> >
> > I need to check again how this works for devices requesting wires
> > from an IWB if we don't allow ITS device sharing.
>
> There is no sharing. Each IWB has its own devid, and the endpoint
> drivers don't have to know about anything ITS related.
I patched the IWB driver to work like an MBIgen.
It looks like the msi_prepare() ITS callback (ie where the its_device is
allocated) is called everytime an endpoint device driver requests a
wired IRQ through:
gicv5_its_msi_prepare+0x68c/0x6f8
its_pmsi_prepare+0x16c/0x1b8
__msi_domain_alloc_irqs+0x70/0x448
__msi_domain_alloc_irq_at+0xf8/0x194
msi_device_domain_alloc_wired+0x88/0x10c
irq_create_fwspec_mapping+0x3a0/0x4c0
irq_create_of_mapping+0xc0/0xe8
of_irq_get+0xa0/0xe4
platform_get_irq_optional+0x54/0x1c4
platform_get_irq+0x1c/0x50
so it becomes "shared" if multiple IWB wires are requested by endpoint
drivers.
I don't have an MBIgen enabled platform but I don't see how it could
behave differently but it could be that I have stared at this code
path for too long.
> > > The other thing is that the IWB really is a standalone thing. It
> > > shouldn't have its fingers in the ITS code, and should only rely on
> > > the core infrastructure to get its interrupts.
> > >
> > > As much as I dislike it, the MBIGEN actually provides a decent example
> > > of how this could be structured.
> >
> > We wrote that code already, I should have posted it. An MBIgen can
> > programme the eventids it sents to the ITS, an IWB can't. So yes,
> > I can make an IWB MBIgen like but the ITS code has to know it is
> > allocating an IRQ for an IWB - one way or another, the eventids
> > are not programmable.
>
> They are not programmable on the MBIGEN either, despite what the code
> does. Everything on this HW is hardcoded.
I don't understand then how in the GICv3 ITS we can guarantee that the
eventid we "allocate" for a wire matches the one sent on the MBIgen->ITS
bus. AFAICS, the ITS eventid is an offset from the LPI base that is
allocated dynamically.
Let's say an endpoint driver requires wire X. The ITS, in
its_alloc_device_irq() grabs a bit from the lpi_map bitmap that has
nothing to do with X.
I don't get how the two can be made to match unless we do something
like I am going to do with the IWB.
This, unless mbigen_write_msi_msg() does something with the
X<->{msg->data} translation (if that function does nothing it should be
removed because it is really misleading).
I am sorry to drone on about this but we have been raking our brains
over this and I would like to understand how this works once for all.
> > I will try to post a v3 with the code in it so that I can get flamed
> > and find a solution to this niggle.
>
> Nobody is flaming you, Lorenzo.
Absolutely, that's not what I meant, sorry, I am really really grateful
and honestly very happy about all the help provided.
I was referring to the disgusting OF compatible hack I posted, I really
don't like it (and good news, we don't need it either).
> We're just trying to get to a point where the code is in a good enough
> shape to be merged.
Absolutely, no questions, massive thanks.
Lorenzo
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-05-01 14:15 ` Marc Zyngier
@ 2025-05-02 8:04 ` Lorenzo Pieralisi
0 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-05-02 8:04 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Thu, May 01, 2025 at 03:15:46PM +0100, Marc Zyngier wrote:
[...]
> > If we are, we use the msi_alloc_info_t->hwirq to define the LPI eventid,
> > basically the IWB wire, if not we just allocate an eventid available from
> > the device bitmap.
> >
> > Other than that (and being forced to provide an IWB irqchip.irq_write_msi_msg()
> > pointer even if the IWB can't write anything otherwise we dereference
> > NULL) this works.
>
> Not even MBIGEN allows you to change the event. If you really want to
> ensure things are even tighter, invent a MSI_FLAG_HARDCODED_MSG flag,
> and pass that down the prepare path.
I tried to set a new alloc flag in the IWB msi_domain_template.ops.set_desc()
callback and it works.
It can be set in the IWB driver (and does not change anything else),
it works so happy days.
> > Is there a better way to implement this ? I would post this code with
> > v3 but instead of waiting I thought I could inline it here, feel free
> > to ignore it (or flame me if it is a solved problem I failed to spot,
> > we need to find a way for the IWB driver to pass the "fixed event" info
> > to the ITS - IWB eventIDs are hardwired it is not like the MBIgen where
> > the irq_write_msi_msg() callback programs the wire-to-eventid
> > translation in HW).
>
> It's *exactly* the same. And see above for a potential explicit
> solution. The empty irq_write_msi_msg() is not a problem. It's
> actually pretty clean, given how the whole thing works.
>
> Please fold this into your v3, and we'll take it from there.
I will with the new alloc flag above, thanks.
Lorenzo
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-05-02 7:59 ` Lorenzo Pieralisi
@ 2025-05-02 14:50 ` Marc Zyngier
2025-05-02 15:43 ` Marc Zyngier
1 sibling, 0 replies; 43+ messages in thread
From: Marc Zyngier @ 2025-05-02 14:50 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Fri, 02 May 2025 08:59:42 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> On Thu, May 01, 2025 at 02:27:26PM +0100, Marc Zyngier wrote:
> > On Wed, 30 Apr 2025 14:27:22 +0100,
> > Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> > >
> > > On Wed, Apr 30, 2025 at 12:57:01PM +0100, Marc Zyngier wrote:
> > > > On Thu, 24 Apr 2025 11:25:32 +0100,
> > > > Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> > > > >
> > > > > The GICv5 architecture implements the Interrupt Wire Bridge (IWB) in
> > > > > order to support wired interrupts that cannot be connected directly
> > > > > to an IRS and instead uses the ITS to translate a wire event into
> > > > > an IRQ signal.
> > > > >
> > > > > An IWB is a special ITS device with its own deviceID; upon probe,
> > > > > an IWB calls into the ITS driver to allocate DT/ITT tables for its
> > > > > events (ie wires).
> > > > >
> > > > > An IWB is always associated with a single ITS in the system.
> > > > >
> > > > > An IWB is connected to an ITS and it has its own deviceID for all
> > > > > interrupt wires that it manages; the IWB input wire number is
> > > > > exposed to the ITS as an eventID. This eventID is not programmable
> > > > > and therefore requires special handling in the ITS driver.
> > > > >
> > > > > Add an IWB driver in order to:
> > > > >
> > > > > - Probe IWBs in the system and allocate ITS tables
> > > > > - Manage IWB IRQ domains
> > > > > - Handle IWB input wires state (enable/disable)
> > > > > - Add the required IWB IRQchip representation
> > > > > - Handle firmware representation to Linux IRQ translation
> > > > >
> > > > > Co-developed-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > > > Co-developed-by: Timothy Hayes <timothy.hayes@arm.com>
> > > > > Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
> > > > > Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> > > > > Cc: Thomas Gleixner <tglx@linutronix.de>
> > > > > Cc: Marc Zyngier <maz@kernel.org>
> > > > > ---
> > > > > drivers/irqchip/Makefile | 2 +-
> > > > > drivers/irqchip/irq-gic-v5-its.c | 68 ++++++--
> > > > > drivers/irqchip/irq-gic-v5-iwb.c | 356 +++++++++++++++++++++++++++++++++++++++
> > > > > drivers/irqchip/irq-gic-v5.c | 2 +
> > > > > drivers/irqchip/irq-gic-v5.h | 28 +++
> > > > > 5 files changed, 437 insertions(+), 19 deletions(-)
> > > > >
> > > > > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> > > > > index 4280395e3bdff7858102f0b4eaaea1121cace52f..7bfb2369fbe494a64b72308d95ae33de93c6b8c6 100644
> > > > > --- a/drivers/irqchip/Makefile
> > > > > +++ b/drivers/irqchip/Makefile
> > > > > @@ -37,7 +37,7 @@ obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o
> > > > > obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o
> > > > > obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
> > > > > obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o
> > > > > -obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o
> > > > > +obj-$(CONFIG_ARM_GIC_V5_ITS) += irq-gic-v5-its.o irq-gic-v5-iwb.o
> > > > > obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
> > > > > obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
> > > > > obj-$(CONFIG_ARM_VIC) += irq-vic.o
> > > > > diff --git a/drivers/irqchip/irq-gic-v5-its.c b/drivers/irqchip/irq-gic-v5-its.c
> > > > > index da349b4709cc5ec8978859237838f039389ca4a1..b5eb4dbfe2296dc6620889eb9291b542cae4aeb6 100644
> > > > > --- a/drivers/irqchip/irq-gic-v5-its.c
> > > > > +++ b/drivers/irqchip/irq-gic-v5-its.c
> > > > > @@ -786,9 +786,8 @@ static struct gicv5_its_dev *gicv5_its_find_device(struct gicv5_its_chip_data *i
> > > > > return dev ? dev : ERR_PTR(-ENODEV);
> > > > > }
> > > > >
> > > > > -static struct gicv5_its_dev *gicv5_its_alloc_device(
> > > > > - struct gicv5_its_chip_data *its, int nvec,
> > > > > - u32 dev_id)
> > > > > +struct gicv5_its_dev *gicv5_its_alloc_device(struct gicv5_its_chip_data *its,
> > > > > + int nvec, u32 dev_id, bool is_iwb)
> > > > > {
> > > > > struct gicv5_its_dev *its_dev;
> > > > > int ret;
> > > > > @@ -815,6 +814,7 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> > > > > its_dev->device_id = dev_id;
> > > > > its_dev->num_events = nvec;
> > > > > its_dev->num_mapped_events = 0;
> > > > > + its_dev->is_iwb = is_iwb;
> > > > >
> > > > > ret = gicv5_its_device_register(its, its_dev);
> > > > > if (ret) {
> > > > > @@ -827,9 +827,11 @@ static struct gicv5_its_dev *gicv5_its_alloc_device(
> > > > >
> > > > > /*
> > > > > * This is the first time we have seen this device. Hence, it is not
> > > > > - * shared.
> > > > > + * shared, unless it is an IWB that is a shared ITS device by
> > > > > + * definition, its eventids are hardcoded and never change - we allocate
> > > > > + * it once for all and never free it.
> > > >
> > > > I'm not convinced the IWB should be treated differently from any other
> > > > device. Its lifetime is not tied to its inputs, so all that's needed
> > > > is to probe it, get a bunch of interrupts, and that's about it.
> > >
> > > I need to check again how this works for devices requesting wires
> > > from an IWB if we don't allow ITS device sharing.
> >
> > There is no sharing. Each IWB has its own devid, and the endpoint
> > drivers don't have to know about anything ITS related.
>
> I patched the IWB driver to work like an MBIgen.
>
> It looks like the msi_prepare() ITS callback (ie where the its_device is
> allocated) is called everytime an endpoint device driver requests a
> wired IRQ through:
>
> gicv5_its_msi_prepare+0x68c/0x6f8
> its_pmsi_prepare+0x16c/0x1b8
> __msi_domain_alloc_irqs+0x70/0x448
> __msi_domain_alloc_irq_at+0xf8/0x194
> msi_device_domain_alloc_wired+0x88/0x10c
> irq_create_fwspec_mapping+0x3a0/0x4c0
> irq_create_of_mapping+0xc0/0xe8
> of_irq_get+0xa0/0xe4
> platform_get_irq_optional+0x54/0x1c4
> platform_get_irq+0x1c/0x50
>
> so it becomes "shared" if multiple IWB wires are requested by endpoint
> drivers.
You shouldn't get into the allocation when the endpoint device probes.
The assumption is that all the interrupts should be allocated only
*once*.
If that's not the case, it probably means that something has been
subtly broken when this code was last massaged, and that it needs
fixing.
I'll resurrect my D05 shortly to collect some traces.
>
> I don't have an MBIgen enabled platform but I don't see how it could
> behave differently but it could be that I have stared at this code
> path for too long.
>
> > > > The other thing is that the IWB really is a standalone thing. It
> > > > shouldn't have its fingers in the ITS code, and should only rely on
> > > > the core infrastructure to get its interrupts.
> > > >
> > > > As much as I dislike it, the MBIGEN actually provides a decent example
> > > > of how this could be structured.
> > >
> > > We wrote that code already, I should have posted it. An MBIgen can
> > > programme the eventids it sents to the ITS, an IWB can't. So yes,
> > > I can make an IWB MBIgen like but the ITS code has to know it is
> > > allocating an IRQ for an IWB - one way or another, the eventids
> > > are not programmable.
> >
> > They are not programmable on the MBIGEN either, despite what the code
> > does. Everything on this HW is hardcoded.
>
> I don't understand then how in the GICv3 ITS we can guarantee that the
> eventid we "allocate" for a wire matches the one sent on the MBIgen->ITS
> bus. AFAICS, the ITS eventid is an offset from the LPI base that is
> allocated dynamically.
>
> Let's say an endpoint driver requires wire X. The ITS, in
> its_alloc_device_irq() grabs a bit from the lpi_map bitmap that has
> nothing to do with X.
>
> I don't get how the two can be made to match unless we do something
> like I am going to do with the IWB.
The driver allocates n LPIs, which are mapped as events 0 to n-1. Just
like with the IWB. When I say it is the same, it is *EXACTLY* the same.
> This, unless mbigen_write_msi_msg() does something with the
> X<->{msg->data} translation (if that function does nothing it should be
> removed because it is really misleading).
It doesn't do anything when it comes to the eventid. But misleading or
not has nothing to do with your problem.
M.
--
Jazz isn't dead. It just smells funny.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-05-02 7:59 ` Lorenzo Pieralisi
2025-05-02 14:50 ` Marc Zyngier
@ 2025-05-02 15:43 ` Marc Zyngier
2025-05-02 16:16 ` Lorenzo Pieralisi
1 sibling, 1 reply; 43+ messages in thread
From: Marc Zyngier @ 2025-05-02 15:43 UTC (permalink / raw)
To: Lorenzo Pieralisi
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Fri, 02 May 2025 08:59:42 +0100,
Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
>
> It looks like the msi_prepare() ITS callback (ie where the its_device is
> allocated) is called everytime an endpoint device driver requests a
> wired IRQ through:
>
> gicv5_its_msi_prepare+0x68c/0x6f8
> its_pmsi_prepare+0x16c/0x1b8
> __msi_domain_alloc_irqs+0x70/0x448
> __msi_domain_alloc_irq_at+0xf8/0x194
> msi_device_domain_alloc_wired+0x88/0x10c
> irq_create_fwspec_mapping+0x3a0/0x4c0
> irq_create_of_mapping+0xc0/0xe8
> of_irq_get+0xa0/0xe4
> platform_get_irq_optional+0x54/0x1c4
> platform_get_irq+0x1c/0x50
>
> so it becomes "shared" if multiple IWB wires are requested by endpoint
> drivers.
Right, I've reproduced on D05 with MBIGEN:
[ 5.505530] Reusing ITT for devID 40000
[ 5.505532] CPU: 36 UID: 0 PID: 557 Comm: (udev-worker) Not tainted 6.15.0-rc4-00079-geef147df4841-dirty #4403 PREEMPT
[ 5.505535] Hardware name: Huawei Taishan 2280 /D05, BIOS Hisilicon D05 IT21 Nemo 2.0 RC0 04/18/2018
[ 5.505536] Call trace:
[ 5.505537] show_stack+0x20/0x38 (C)
[ 5.505540] dump_stack_lvl+0x80/0xf8
[ 5.505543] dump_stack+0x18/0x28
[ 5.505546] its_msi_prepare+0xe4/0x1d0
[ 5.505549] its_pmsi_prepare+0x15c/0x1d0
[ 5.505552] __msi_domain_alloc_irqs+0x80/0x398
[ 5.505556] __msi_domain_alloc_irq_at+0x100/0x168
[ 5.505560] msi_device_domain_alloc_wired+0x9c/0x128
[ 5.505564] irq_create_fwspec_mapping+0x180/0x388
[ 5.505567] acpi_irq_get+0xac/0xe8
[ 5.505570] platform_get_irq_optional+0x1e8/0x208
[ 5.505574] devm_platform_get_irqs_affinity+0x58/0x298
[ 5.505578] hisi_sas_v2_interrupt_preinit+0x60/0xb0 [hisi_sas_v2_hw]
[ 5.505582] hisi_sas_probe+0x164/0x278 [hisi_sas_main]
[ 5.505588] hisi_sas_v2_probe+0x20/0x38 [hisi_sas_v2_hw]
[ 5.505591] platform_probe+0x70/0xd0
[ 5.505595] really_probe+0xc8/0x3a0
[ 5.505598] __driver_probe_device+0x84/0x170
[ 5.505600] driver_probe_device+0x44/0x120
[ 5.505603] __driver_attach+0xfc/0x210
[ 5.505606] bus_for_each_dev+0x7c/0xe8
[ 5.505608] driver_attach+0x2c/0x40
[ 5.505611] bus_add_driver+0x118/0x248
[ 5.505613] driver_register+0x68/0x138
[ 5.505616] __platform_driver_register+0x2c/0x40
[ 5.505619] hisi_sas_v2_driver_init+0x28/0xff8 [hisi_sas_v2_hw]
[ 5.505623] do_one_initcall+0x4c/0x2c0
[ 5.505626] do_init_module+0x60/0x230
[ 5.505629] load_module+0xa64/0xb30
[ 5.505631] init_module_from_file+0x8c/0xd8
[ 5.505634] idempotent_init_module+0x1c4/0x2b8
[ 5.505637] __arm64_sys_finit_module+0x74/0xe8
[ 5.505640] invoke_syscall+0x50/0x120
[ 5.505642] el0_svc_common.constprop.0+0x48/0xf0
[ 5.505644] do_el0_svc+0x24/0x38
[ 5.505646] el0_svc+0x34/0xf0
[ 5.505650] el0t_64_sync_handler+0x10c/0x138
[ 5.505654] el0t_64_sync+0x1ac/0x1b0
[ 5.505681] ID:78 pID:8382 vID:143
And that a few dozen times.
I'll have a think at how to unfsck this. This was previously avoided
by (IIRC) populating the domain upfront and letting the domain
matching code do its job. That behaviour seems to have been lost. On
the other hand, as long as you don't expect the ITT to *grow*, nothing
horrible should happen.
But I also get an interesting crash in msi_domain_debig_show(), so
there is more than just this corner case that is screwed.
M.
--
Jazz isn't dead. It just smells funny.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support
2025-05-02 15:43 ` Marc Zyngier
@ 2025-05-02 16:16 ` Lorenzo Pieralisi
0 siblings, 0 replies; 43+ messages in thread
From: Lorenzo Pieralisi @ 2025-05-02 16:16 UTC (permalink / raw)
To: Marc Zyngier
Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, Arnd Bergmann, Sascha Bischoff,
Timothy Hayes, Liam R. Howlett, Mark Rutland, linux-arm-kernel,
linux-kernel, devicetree
On Fri, May 02, 2025 at 04:43:57PM +0100, Marc Zyngier wrote:
> On Fri, 02 May 2025 08:59:42 +0100,
> Lorenzo Pieralisi <lpieralisi@kernel.org> wrote:
> >
> > It looks like the msi_prepare() ITS callback (ie where the its_device is
> > allocated) is called everytime an endpoint device driver requests a
> > wired IRQ through:
> >
> > gicv5_its_msi_prepare+0x68c/0x6f8
> > its_pmsi_prepare+0x16c/0x1b8
> > __msi_domain_alloc_irqs+0x70/0x448
> > __msi_domain_alloc_irq_at+0xf8/0x194
> > msi_device_domain_alloc_wired+0x88/0x10c
> > irq_create_fwspec_mapping+0x3a0/0x4c0
> > irq_create_of_mapping+0xc0/0xe8
> > of_irq_get+0xa0/0xe4
> > platform_get_irq_optional+0x54/0x1c4
> > platform_get_irq+0x1c/0x50
> >
> > so it becomes "shared" if multiple IWB wires are requested by endpoint
> > drivers.
>
> Right, I've reproduced on D05 with MBIGEN:
>
> [ 5.505530] Reusing ITT for devID 40000
> [ 5.505532] CPU: 36 UID: 0 PID: 557 Comm: (udev-worker) Not tainted 6.15.0-rc4-00079-geef147df4841-dirty #4403 PREEMPT
> [ 5.505535] Hardware name: Huawei Taishan 2280 /D05, BIOS Hisilicon D05 IT21 Nemo 2.0 RC0 04/18/2018
> [ 5.505536] Call trace:
> [ 5.505537] show_stack+0x20/0x38 (C)
> [ 5.505540] dump_stack_lvl+0x80/0xf8
> [ 5.505543] dump_stack+0x18/0x28
> [ 5.505546] its_msi_prepare+0xe4/0x1d0
> [ 5.505549] its_pmsi_prepare+0x15c/0x1d0
> [ 5.505552] __msi_domain_alloc_irqs+0x80/0x398
> [ 5.505556] __msi_domain_alloc_irq_at+0x100/0x168
> [ 5.505560] msi_device_domain_alloc_wired+0x9c/0x128
> [ 5.505564] irq_create_fwspec_mapping+0x180/0x388
> [ 5.505567] acpi_irq_get+0xac/0xe8
> [ 5.505570] platform_get_irq_optional+0x1e8/0x208
> [ 5.505574] devm_platform_get_irqs_affinity+0x58/0x298
> [ 5.505578] hisi_sas_v2_interrupt_preinit+0x60/0xb0 [hisi_sas_v2_hw]
> [ 5.505582] hisi_sas_probe+0x164/0x278 [hisi_sas_main]
> [ 5.505588] hisi_sas_v2_probe+0x20/0x38 [hisi_sas_v2_hw]
> [ 5.505591] platform_probe+0x70/0xd0
> [ 5.505595] really_probe+0xc8/0x3a0
> [ 5.505598] __driver_probe_device+0x84/0x170
> [ 5.505600] driver_probe_device+0x44/0x120
> [ 5.505603] __driver_attach+0xfc/0x210
> [ 5.505606] bus_for_each_dev+0x7c/0xe8
> [ 5.505608] driver_attach+0x2c/0x40
> [ 5.505611] bus_add_driver+0x118/0x248
> [ 5.505613] driver_register+0x68/0x138
> [ 5.505616] __platform_driver_register+0x2c/0x40
> [ 5.505619] hisi_sas_v2_driver_init+0x28/0xff8 [hisi_sas_v2_hw]
> [ 5.505623] do_one_initcall+0x4c/0x2c0
> [ 5.505626] do_init_module+0x60/0x230
> [ 5.505629] load_module+0xa64/0xb30
> [ 5.505631] init_module_from_file+0x8c/0xd8
> [ 5.505634] idempotent_init_module+0x1c4/0x2b8
> [ 5.505637] __arm64_sys_finit_module+0x74/0xe8
> [ 5.505640] invoke_syscall+0x50/0x120
> [ 5.505642] el0_svc_common.constprop.0+0x48/0xf0
> [ 5.505644] do_el0_svc+0x24/0x38
> [ 5.505646] el0_svc+0x34/0xf0
> [ 5.505650] el0t_64_sync_handler+0x10c/0x138
> [ 5.505654] el0t_64_sync+0x1ac/0x1b0
> [ 5.505681] ID:78 pID:8382 vID:143
>
> And that a few dozen times.
Yep that matches my expectations, thanks a lot for testing it.
> I'll have a think at how to unfsck this. This was previously avoided
> by (IIRC) populating the domain upfront and letting the domain
> matching code do its job. That behaviour seems to have been lost. On
> the other hand, as long as you don't expect the ITT to *grow*, nothing
> horrible should happen.
Yes - I can remove the "shared" ITS device flag but should keep the
logic preventing an ITS device with same deviceID to be allocated
if found.
> But I also get an interesting crash in msi_domain_debig_show(), so
> there is more than just this corner case that is screwed.
That I can try on my side too to try to help you untangle it.
Possibly this was introduced when the MBIgen switched to MSI parent
with 752e021f5b9b ? It is pure speculation at this stage just noticed
that's a point in time where the domain code changed.
Is MBIgen the only example of an IC relying on the ITS as MSI parent ?
Thanks,
Lorenzo
^ permalink raw reply [flat|nested] 43+ messages in thread
end of thread, other threads:[~2025-05-02 17:23 UTC | newest]
Thread overview: 43+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-24 10:25 [PATCH v2 00/22] Arm GICv5: Host driver implementation Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 01/22] dt-bindings: interrupt-controller: Add Arm GICv5 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 02/22] arm64/sysreg: Add GCIE field to ID_AA64PFR2_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 03/22] arm64/sysreg: Add ICC_PPI_PRIORITY<n>_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 04/22] arm64/sysreg: Add ICC_ICSR_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 05/22] arm64/sysreg: Add ICC_PPI_HMR<n>_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 06/22] arm64/sysreg: Add ICC_PPI_ENABLER<n>_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 07/22] arm64/sysreg: Add ICC_PPI_{C/S}ACTIVER<n>_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 08/22] arm64/sysreg: Add ICC_PPI_{C/S}PENDR<n>_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 09/22] arm64/sysreg: Add ICC_CR0_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 10/22] arm64/sysreg: Add ICC_PCR_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 11/22] arm64/sysreg: Add ICC_IDR0_EL1 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 12/22] arm64/sysreg: Add ICH_HFGRTR_EL2 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 13/22] arm64/sysreg: Add ICH_HFGWTR_EL2 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 14/22] arm64/sysreg: Add ICH_HFGITR_EL2 Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 15/22] arm64: Disable GICv5 read/write/instruction traps Lorenzo Pieralisi
2025-05-01 14:32 ` Marc Zyngier
2025-04-24 10:25 ` [PATCH v2 16/22] arm64: cpucaps: Rename GICv3 CPU interface capability Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 17/22] arm64: cpucaps: Add GICv5 CPU interface (GCIE) capability Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 18/22] arm64: smp: Support non-SGIs for IPIs Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 19/22] irqchip/gic-v5: Add GICv5 CPU interface/IRS support Lorenzo Pieralisi
2025-04-28 15:49 ` Marc Zyngier
2025-04-29 14:54 ` Lorenzo Pieralisi
2025-04-29 15:38 ` Marc Zyngier
2025-04-29 16:02 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 20/22] irqchip/gic-v5: Add GICv5 ITS support Lorenzo Pieralisi
2025-04-30 7:28 ` Jiri Slaby
2025-04-30 12:55 ` Lorenzo Pieralisi
2025-04-30 9:12 ` Marc Zyngier
2025-04-30 13:21 ` Lorenzo Pieralisi
2025-05-01 9:01 ` Marc Zyngier
2025-04-24 10:25 ` [PATCH v2 21/22] irqchip/gic-v5: Add GICv5 IWB support Lorenzo Pieralisi
2025-04-30 11:57 ` Marc Zyngier
2025-04-30 13:27 ` Lorenzo Pieralisi
2025-05-01 13:27 ` Marc Zyngier
2025-05-02 7:59 ` Lorenzo Pieralisi
2025-05-02 14:50 ` Marc Zyngier
2025-05-02 15:43 ` Marc Zyngier
2025-05-02 16:16 ` Lorenzo Pieralisi
2025-04-30 16:25 ` Lorenzo Pieralisi
2025-05-01 14:15 ` Marc Zyngier
2025-05-02 8:04 ` Lorenzo Pieralisi
2025-04-24 10:25 ` [PATCH v2 22/22] arm64: Kconfig: Enable GICv5 Lorenzo Pieralisi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).