* [GIT pull] irq/drivers for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
@ 2026-10-21 9:51 ` Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/clocksource " Thomas Gleixner
` (4 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:51 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest irq/drivers branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq-drivers-2026-10-20
up to: 9376df349675: irqchip/al-fic: Support per-child affinity via interrupt redirection
Updates for interrupt chip drivers:
- Remove the futile attempts to cleanup resources when the probing of the
main interrupt chip fails for SiFIVE/PLIC devices. If that probe fails
the system will stall immediately afterwards in the boot process
without reaching a state where the error could be diagnosed. The lack
of resource cleanup led to a frenzy of AI detected/assisted "fixes"
which just make the code pretend to be correct for no value.
Remove the existing half baked cleanup and immediately panic the
machine when the probe fails. That's far more useful than letting it
proceed and stall out a millisecond later with no information about the
root cause.
- A series of fixes for the ARM GIC-V3 driver addressing a potential
double free, a bitmask overrun on 32-bit ARM and a OF node leak.
- Automatically set the IRQ_DOMAIN_FLAG_DESTROY_GC when a interrupt
domain is created that also allocates the generic interrupt chip during
domain creation. That removes the requirement for manually setting the
flag in the domain creation info structure when it contains a valid
entry for allocating the generic interrupt chip. Remove the therefore
redundant setters of IRQ_DOMAIN_FLAG_DESTROY_GC in the existing
drivers.
- Add support for Annapurna Labs FIC version 2 and 3 and implement
affinity control for the demultiplexed interrupts by utilizing the
recently merged interrupt redirection infrastructure.
- Convert the ACPI device ID structures to named initializers
- Consolidate the IPI related constants for RISC-V interrupt controllers
- Add support for Qualcomm Purwa SoC interrupt chip and consolidate the
workaround for the previous version in Hamoa SoCs.
- The usual small fixes and improvements all over the place
Thanks,
tglx
------------------>
Adrian Ng Ho Yin (1):
irqchip/gic-v3: Add Altera SoCFPGA Agilex5 GIC600 DMA32 erratum workaround
Andrew Davis (2):
irqchip/pruss-intc: Use match data directly
irqchip/pruss-intc: Use scoped lock guard and devm_mutex_init
Anirudha Sarangi (2):
dt-bindings: interrupt-controller: Add Xilinx Versal NET SMMU CSR
irqchip: Add Xilinx Versal NET SMMU CSR interrupt controller driver
Arnd Bergmann (1):
irqchip/qcom-pdc: Fix linking against QCOM_SCM firmware
Au Dang (1):
irqchip/renesas-rzv2h: Preserve TIEN state in rzv2h_tint_set_type()
Benoît Monin (3):
irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic()
irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity()
irqchip/mips-gic: Transfer interrupt mask state across clusters
Eliav Farber (9):
irqchip/al-fic: Fix coding style issues
irqchip/al-fic: Use %pe to print init error
irqchip/al-fic: Use %pOF and raise init log level
irqchip/al-fic: Keep the device_node instead of a cached name string
irqchip/al-fic: Switch to shared parent interrupt
dt-bindings: interrupt-controller: amazon,al-fic: Add mask selection
irqchip/al-fic: Support error and fatal outputs and FIC v2
irqchip/al-fic: Add support for FIC v3
irqchip/al-fic: Support per-child affinity via interrupt redirection
Fuad Tabba (6):
irqchip/gic-v3: Release the partition node in gic_irq_get_fwspec_info()
irqchip/gic-v3-its: Don't clamp nvecs to zero when id_bits is 32
irqchip/gic-v3-its: Clear vpt_page after freeing the pending table
irqchip/gic-v3-its: Don't free a vPE table shared with another ITS
irqchip/gic-v3-its: Don't free the tables of an enabled ITS
irqchip/gic-v3: Don't register a redistributor that was never counted
GUO Ren (XuanTie) (5):
riscv: smp: Move enum ipi_message_type to asm/smp.h
riscv: sbi: Use IPI_MAX for SBI IPI muxing
clocksource: clint: Use IPI_MAX for IPI muxing
irqchip/aclint-sswi: Use IPI_MAX for IPI muxing
irqchip/imsic: Use IPI_MAX instead of IMSIC_NR_IPI
Guangshuo Li (1):
irqchip/imx-intmux: Fix runtime PM teardown on remove
Hemanth Selam (1):
irqchip: Fix typos and repeated words in comments
Huacai Chen (1):
irqchip/loongson-eiointc: Use pa_to_nid() rather than hard-code
Igor Velkov (1):
irqchip/meson-gpio: Allow the GPIO interrupts to wake the system
Jiangshan Yi (1):
irqchip/gic-v5: Install root IRQ handler last in gicv5_init_common()
Karl Mehltretter (3):
irqchip/gic-v3-its: Don't WARN on LPI free allocation failure
irqchip/atmel-aic[5]: Disable interrupts in the irq_pm_shutdown() callbacks
irqchip/jcore-aic: Fix lost IPIs on SMP
Krzysztof Kozlowski (3):
irqchip/qcom: Enable compile testing for QCOM_PDC and QCOM_MPM
irqchip/qcom: Add defaults for desired SoC drivers
arm64: defconfig: Drop redundant Qualcomm irqchip entries
Lorenzo Pieralisi (2):
dt-bindings: interrupt-controller: gicv5: Relax IRS properties constraints
irqchip/gic-v5: Fix standalone IRSes OF probe
Marc Zyngier (1):
irqchip/gic: Simplify Maintenance Interrupt ACPI parsing
Maulik Shah (3):
dt-bindings: interrupt-controller: qcom,pdc: Document Purwa PDC
irqchip/qcom-pdc: Add purwa compatible for PDC secondary mode
arm64: dts: qcom: purwa: Drop the Hamoa workaround for PDC
Nam Cao (1):
irqchip/sifive-plic: Panic if initialization fails
Pawel Zalewski (1):
irqchip: Use named initializers for acpi_device_id
Pengpeng Hou (4):
irqchip/riscv-imsic: Use GENMASK for base address masks
irqchip/mst-intc: Validate the DT interrupt range
irqchip/sifive-plic: Avoid signed shift in interrupt enable mask
irqchip/riscv-imsic: Reject overlapping group index fields
Prajwal N (1):
irqchip/riscv-imsic: Expose the fwnode via the global config
Qingshuang Fu (3):
irqdomain: Set IRQ_DOMAIN_FLAG_DESTROY_GC in __irq_domain_instantiate()
irqchip/lan966x-oic: Drop redundant IRQ_DOMAIN_FLAG_DESTROY_GC
soc/fsl/qe: qe_ports_ic: Drop redundant IRQ_DOMAIN_FLAG_DESTROY_GC
Triet Hoang (1):
irqchip: st: Convert to DEFINE_SIMPLE_DEV_PM_OPS()
Vladimir Murzin (1):
irqchip/gic-v3: Ensure change to DS is visible
Wentao Liang (1):
irqchip/mchp-eic: Fix clock reference leak in mchp_eic_probe()
Zhu Ling (1):
irqchip/gic: Fix UBSAN shift-out-of-bounds in GIC helpers
Documentation/arch/arm64/silicon-errata.rst | 3 +
.../interrupt-controller/amazon,al-fic.yaml | 16 +-
.../bindings/interrupt-controller/arm,gic-v5.yaml | 9 +-
.../bindings/interrupt-controller/qcom,pdc.yaml | 1 +
.../xlnx,versal-net-smmu-csr.yaml | 75 ++++++
MAINTAINERS | 10 +
arch/arm64/Kconfig | 9 +
arch/arm64/Kconfig.platforms | 1 +
arch/arm64/boot/dts/qcom/purwa.dtsi | 5 +
arch/arm64/configs/defconfig | 2 -
arch/riscv/include/asm/smp.h | 12 +
arch/riscv/kernel/sbi-ipi.c | 4 +-
arch/riscv/kernel/smp.c | 12 -
drivers/clocksource/timer-clint.c | 4 +-
drivers/irqchip/Kconfig | 16 +-
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-aclint-sswi.c | 4 +-
drivers/irqchip/irq-al-fic.c | 255 +++++++++++++++------
drivers/irqchip/irq-atmel-aic.c | 2 +-
drivers/irqchip/irq-atmel-aic5.c | 2 +-
drivers/irqchip/irq-gic-common.c | 2 +-
drivers/irqchip/irq-gic-v3-its.c | 25 +-
drivers/irqchip/irq-gic-v3.c | 36 +--
drivers/irqchip/irq-gic-v5-irs.c | 4 +
drivers/irqchip/irq-gic-v5-iwb.c | 4 +-
drivers/irqchip/irq-gic-v5.c | 9 +-
drivers/irqchip/irq-gic.c | 12 +-
drivers/irqchip/irq-imx-intmux.c | 15 +-
drivers/irqchip/irq-jcore-aic.c | 4 +-
drivers/irqchip/irq-lan966x-oic.c | 1 -
drivers/irqchip/irq-loongson-eiointc.c | 2 +-
drivers/irqchip/irq-mbigen.c | 4 +-
drivers/irqchip/irq-mchp-eic.c | 4 +-
drivers/irqchip/irq-meson-gpio.c | 2 +-
drivers/irqchip/irq-mips-gic.c | 59 +++--
drivers/irqchip/irq-mst-intc.c | 3 +
drivers/irqchip/irq-mtk-cirq.c | 2 +-
drivers/irqchip/irq-pruss-intc.c | 38 ++-
drivers/irqchip/irq-renesas-rzv2h.c | 6 +-
drivers/irqchip/irq-riscv-aplic-main.c | 4 +-
drivers/irqchip/irq-riscv-imsic-early.c | 6 +-
drivers/irqchip/irq-riscv-imsic-platform.c | 18 +-
drivers/irqchip/irq-riscv-imsic-state.c | 23 +-
drivers/irqchip/irq-riscv-imsic-state.h | 4 -
drivers/irqchip/irq-riscv-rpmi-sysmsi.c | 4 +-
drivers/irqchip/irq-sifive-plic.c | 64 ++----
drivers/irqchip/irq-sni-exiu.c | 4 +-
drivers/irqchip/irq-st.c | 6 +-
drivers/irqchip/irq-vic.c | 2 +-
drivers/irqchip/irq-xilinx-smmu-csr.c | 244 ++++++++++++++++++++
drivers/irqchip/qcom-irq-combiner.c | 2 +-
drivers/irqchip/qcom-pdc.c | 3 +
drivers/soc/fsl/qe/qe_ports_ic.c | 1 -
include/linux/irqchip/riscv-imsic.h | 3 +
kernel/irq/irqdomain.c | 1 +
55 files changed, 797 insertions(+), 267 deletions(-)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
create mode 100644 drivers/irqchip/irq-xilinx-smmu-csr.c
diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index ac3248b9f2f3..25afa79266a4 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -53,6 +53,9 @@ stable kernels.
| Allwinner | A64/R18 | UNKNOWN1 | SUN50I_ERRATUM_UNKNOWN1 |
+----------------+-----------------+-----------------+-----------------------------+
+----------------+-----------------+-----------------+-----------------------------+
+| Altera | SoCFPGA Agilex5 | 2.1.23 |ALTERA_ERRATUM_AGILEX5_2_1_23|
++----------------+-----------------+-----------------+-----------------------------+
++----------------+-----------------+-----------------+-----------------------------+
| Ampere | AmpereOne | AC03_CPU_38 | AMPERE_ERRATUM_AC03_CPU_38 |
+----------------+-----------------+-----------------+-----------------------------+
| Ampere | AmpereOne | AC03_CPU_57 | N/A |
diff --git a/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml b/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml
index 26bc05dee0bc..1ebf9a427f2f 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml
@@ -24,6 +24,19 @@ properties:
interrupts:
maxItems: 1
+ amazon,al-fic-mask:
+ $ref: /schemas/types.yaml#/definitions/string
+ description:
+ Selects which of the controller's severity outputs this group drives,
+ and thus which mask register the driver programs. A group has one cause
+ register and three severity outputs - info, error and fatal - each
+ gated by its own mask register. Each output is aggregated into a
+ separate interrupt tree, and a group belongs to exactly one of them.
+ The error and fatal outputs exist only on controller revisions that
+ implement them; earlier revisions provide the info output only.
+ enum: [info, error, fatal]
+ default: info
+
required:
- compatible
- reg
@@ -39,8 +52,9 @@ examples:
interrupt-controller@fd8a8500 {
compatible = "amazon,al-fic";
- reg = <0xfd8a8500 0x1000>;
+ reg = <0xfd8a8500 0x40>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <GIC_SPI 0x0 IRQ_TYPE_LEVEL_HIGH>;
+ amazon,al-fic-mask = "error";
};
diff --git a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
index 86ca7f3ac281..b901e4288816 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
@@ -120,13 +120,14 @@ patternProperties:
cpus:
description:
- CPUs managed by the IRS.
+ CPUs managed by the IRS. Omit it if the IRS has no associated CPUs.
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.
+ Omit it if the IRS has no associated CPUs.
patternProperties:
"^its@[0-9a-f]+$":
@@ -210,12 +211,14 @@ patternProperties:
- reg
- reg-names
+ dependencies:
+ cpus: [ "arm,iaffids" ]
+ arm,iaffids: [ cpus ]
+
required:
- compatible
- reg
- reg-names
- - cpus
- - arm,iaffids
additionalProperties: false
diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml
index 8162a49d49a6..b3d19c86a240 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml
@@ -58,6 +58,7 @@ properties:
- qcom,sm8650-pdc
- qcom,sm8750-pdc
- qcom,x1e80100-pdc
+ - qcom,x1p42100-pdc
- const: qcom,pdc
reg:
diff --git a/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml b/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
new file mode 100644
index 000000000000..38b63e51b732
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/xlnx,versal-net-smmu-csr.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Xilinx Versal NET SMMU CSR Interrupt Controller
+
+maintainers:
+ - Anirudha Sarangi <anirudha.sarangi@amd.com>
+ - Tushar Nimkar <tushar.nimkar@amd.com>
+
+description: |
+ The Versal NET platform includes a Xilinx-specific SMMU CSR (Control and
+ Status Register) block that acts as an intermediate interrupt controller
+ for ARM SMMUv3.
+
+ The block provides registers to enable, disable and clear the
+ standard SMMUv3 interrupt sources (EVENTQ, CMDQ_SYNC, GERROR and PRIQ)
+ before forwarding them to the parent interrupt controller,
+ typically a GIC.
+
+ The controller receives one parent interrupt and demultiplexes CSR
+ status bits into the following child interrupt identifiers
+ 0 - EVENTQ
+ 1 - CMDQ_SYNC
+ 2 - GERROR (GLOBAL)
+ 3 - PRIQ
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+properties:
+ compatible:
+ const: xlnx,versal-net-smmu-csr
+
+ reg:
+ maxItems: 1
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 1
+ description: |
+ Single cell containing the child interrupt identifier
+ 0 - EVENTQ
+ 1 - CMDQ_SYNC
+ 2 - GERROR (GLOBAL)
+ 3 - PRIQ
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupt-controller
+ - "#interrupt-cells"
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ interrupt-controller@eca10000 {
+ compatible = "xlnx,versal-net-smmu-csr";
+ reg = <0xeca10000 0x1000>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ interrupt-parent = <&gic>;
+ interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_HIGH>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 3a19da74d00c..f6007e77445f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29913,6 +29913,16 @@ F: Documentation/misc-devices/xilinx_sdfec.rst
F: drivers/misc/xilinx_sdfec.c
F: include/uapi/misc/xilinx_sdfec.h
+XILINX SMMU CSR IRQ DRIVER
+M: Anirudha Sarangi <anirudha.sarangi@amd.com>
+M: Tushar Nimkar <tushar.nimkar@amd.com>
+L: git@amd.com
+L: linux-arm-kernel@lists.infradead.org
+L: linux-kernel@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
+F: drivers/irqchip/irq-xilinx-smmu-csr.c
+
XILINX TRNG DRIVER
M: Mounika Botcha <mounika.botcha@amd.com>
M: Harsh Jain <h.jain@amd.com>
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b5a51b0ef944..d25c77a15276 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1427,6 +1427,15 @@ config NVIDIA_OLYMPUS_1027_ERRATUM
If unsure, say Y.
+config ALTERA_ERRATUM_AGILEX5_2_1_23
+ bool
+ help
+ The Altera SoCFPGA Agilex5 GIC600 SoC integration has ACE-lite
+ addressing limited to the first 32bit of physical address space,
+ so the ITS cannot access memory above 4GB.
+
+ Selected by ARCH_INTEL_SOCFPGA, as all Agilex5 devices are affected.
+
config RENESAS_ERRATUM_GEN4GICITS1
bool "Renesas R-Car Gen4: GIC600 can not access physical addresses above 4 GiB"
default y
diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index d2acfac73003..07127f7291db 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -361,6 +361,7 @@ config ARCH_SEATTLE
config ARCH_INTEL_SOCFPGA
bool "Intel's SoCFPGA ARMv8 Families"
+ select ALTERA_ERRATUM_AGILEX5_2_1_23
help
This enables support for Intel's SoCFPGA ARMv8 families:
Stratix 10 (ex. Altera), Stratix10 Software Virtual Platform,
diff --git a/arch/arm64/boot/dts/qcom/purwa.dtsi b/arch/arm64/boot/dts/qcom/purwa.dtsi
index c698e6cb2543..4348dd3d1dc5 100644
--- a/arch/arm64/boot/dts/qcom/purwa.dtsi
+++ b/arch/arm64/boot/dts/qcom/purwa.dtsi
@@ -224,6 +224,11 @@ &pcie6a_phy {
compatible = "qcom,x1p42100-qmp-gen4x4-pcie-phy";
};
+/* X1P42100 PDC is same as X1E80100, but without hardware register bug */
+&pdc {
+ compatible = "qcom,x1p42100-pdc", "qcom,pdc";
+};
+
&qfprom {
gpu_speed_bin: gpu-speed-bin@119 {
reg = <0x119 0x2>;
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 6ec8d3a69c98..978e00891dca 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -1743,8 +1743,6 @@ CONFIG_PWM_VISCONTI=m
CONFIG_PWM_XILINX=m
CONFIG_SL28CPLD_INTC=y
CONFIG_XILINX_INTC=y
-CONFIG_QCOM_PDC=y
-CONFIG_QCOM_MPM=y
CONFIG_TI_SCI_INTR_IRQCHIP=y
CONFIG_TI_SCI_INTA_IRQCHIP=y
CONFIG_RESET_GPIO=m
diff --git a/arch/riscv/include/asm/smp.h b/arch/riscv/include/asm/smp.h
index 0ecc67641b09..bed39fff1f8a 100644
--- a/arch/riscv/include/asm/smp.h
+++ b/arch/riscv/include/asm/smp.h
@@ -15,6 +15,18 @@
struct seq_file;
extern unsigned long boot_cpu_hartid;
+enum ipi_message_type {
+ IPI_RESCHEDULE,
+ IPI_CALL_FUNC,
+ IPI_CPU_STOP,
+ IPI_CPU_CRASH_STOP,
+ IPI_IRQ_WORK,
+ IPI_TIMER,
+ IPI_CPU_BACKTRACE,
+ IPI_KGDB_ROUNDUP,
+ IPI_MAX
+};
+
#ifdef CONFIG_SMP
#include <linux/jump_label.h>
diff --git a/arch/riscv/kernel/sbi-ipi.c b/arch/riscv/kernel/sbi-ipi.c
index 0cc5559c08d8..eeec178a9b95 100644
--- a/arch/riscv/kernel/sbi-ipi.c
+++ b/arch/riscv/kernel/sbi-ipi.c
@@ -57,7 +57,7 @@ void __init sbi_ipi_init(void)
return;
}
- virq = ipi_mux_create(BITS_PER_BYTE, sbi_send_ipi);
+ virq = ipi_mux_create(IPI_MAX, sbi_send_ipi);
if (virq <= 0) {
pr_err("unable to create muxed IPIs\n");
irq_dispose_mapping(sbi_ipi_virq);
@@ -75,7 +75,7 @@ void __init sbi_ipi_init(void)
"irqchip/sbi-ipi:starting",
sbi_ipi_starting_cpu, NULL);
- riscv_ipi_set_virq_range(virq, BITS_PER_BYTE);
+ riscv_ipi_set_virq_range(virq, IPI_MAX);
pr_info("providing IPIs using SBI IPI extension\n");
/*
diff --git a/arch/riscv/kernel/smp.c b/arch/riscv/kernel/smp.c
index fa66f9c97d74..8930b62b15e7 100644
--- a/arch/riscv/kernel/smp.c
+++ b/arch/riscv/kernel/smp.c
@@ -28,18 +28,6 @@
#include <asm/cacheflush.h>
#include <asm/cpu_ops.h>
-enum ipi_message_type {
- IPI_RESCHEDULE,
- IPI_CALL_FUNC,
- IPI_CPU_STOP,
- IPI_CPU_CRASH_STOP,
- IPI_IRQ_WORK,
- IPI_TIMER,
- IPI_CPU_BACKTRACE,
- IPI_KGDB_ROUNDUP,
- IPI_MAX
-};
-
static const char * const ipi_names[] = {
[IPI_RESCHEDULE] = "Rescheduling interrupts",
[IPI_CALL_FUNC] = "Function call interrupts",
diff --git a/drivers/clocksource/timer-clint.c b/drivers/clocksource/timer-clint.c
index 0bdd9d7ec545..e56eee7e3781 100644
--- a/drivers/clocksource/timer-clint.c
+++ b/drivers/clocksource/timer-clint.c
@@ -243,7 +243,7 @@ static int __init clint_timer_init_dt(struct device_node *np)
}
#ifdef CONFIG_SMP
- rc = ipi_mux_create(BITS_PER_BYTE, clint_send_ipi);
+ rc = ipi_mux_create(IPI_MAX, clint_send_ipi);
if (rc <= 0) {
pr_err("unable to create muxed IPIs\n");
rc = (rc < 0) ? rc : -ENODEV;
@@ -251,7 +251,7 @@ static int __init clint_timer_init_dt(struct device_node *np)
}
irq_set_chained_handler(clint_ipi_irq, clint_ipi_interrupt);
- riscv_ipi_set_virq_range(rc, BITS_PER_BYTE);
+ riscv_ipi_set_virq_range(rc, IPI_MAX);
clint_clear_ipi();
#endif
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 20b77fbc51ee..136bab41f801 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -522,17 +522,19 @@ config GOLDFISH_PIC
config QCOM_PDC
tristate "Qualcomm PDC"
- depends on ARCH_QCOM
+ depends on QCOM_SCM
select IRQ_DOMAIN_HIERARCHY
+ default ARCH_QCOM
help
Power Domain Controller driver to manage and configure wakeup
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
config QCOM_MPM
tristate "Qualcomm MPM"
- depends on ARCH_QCOM
+ depends on ARCH_QCOM || COMPILE_TEST
depends on MAILBOX
select IRQ_DOMAIN_HIERARCHY
+ default ARCH_QCOM if ARM64
help
MSM Power Manager driver to manage and configure wakeup
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
@@ -840,4 +842,14 @@ config SUNPLUS_SP7021_INTC
chained controller, routing all interrupt source in P-Chip to
the primary controller on C-Chip.
+config IRQCHIP_XILINX_SMMU_CSR
+ bool "Xilinx SMMU CSR Interrupt Driver"
+ depends on ARCH_ZYNQMP
+ select IRQ_DOMAIN
+ help
+ This driver does the initial handling of SMMU interrupts before
+ forwarding them to the ARM SMMU v3 driver. It is intended for
+ ZynqMP-class platforms where a vendor CSR block mediates SMMU
+ interrupts.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index ab33cccd8471..16a5fd126c9a 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -135,3 +135,4 @@ obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o
obj-$(CONFIG_MCHP_EIC) += irq-mchp-eic.o
obj-$(CONFIG_SOPHGO_SG2042_MSI) += irq-sg2042-msi.o
obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o
+obj-$(CONFIG_IRQCHIP_XILINX_SMMU_CSR) += irq-xilinx-smmu-csr.o
diff --git a/drivers/irqchip/irq-aclint-sswi.c b/drivers/irqchip/irq-aclint-sswi.c
index ca06efd86fa1..5e010fc401f7 100644
--- a/drivers/irqchip/irq-aclint-sswi.c
+++ b/drivers/irqchip/irq-aclint-sswi.c
@@ -138,7 +138,7 @@ static int __init aclint_sswi_probe(struct fwnode_handle *fwnode)
}
/* Register SSWI irq and handler */
- virq = ipi_mux_create(BITS_PER_BYTE, aclint_sswi_ipi_send);
+ virq = ipi_mux_create(IPI_MAX, aclint_sswi_ipi_send);
if (virq <= 0) {
pr_err("unable to create muxed IPIs\n");
irq_dispose_mapping(sswi_ipi_virq);
@@ -152,7 +152,7 @@ static int __init aclint_sswi_probe(struct fwnode_handle *fwnode)
aclint_sswi_starting_cpu,
aclint_sswi_dying_cpu);
- riscv_ipi_set_virq_range(virq, BITS_PER_BYTE);
+ riscv_ipi_set_virq_range(virq, IPI_MAX);
return 0;
}
diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c
index d10ac9b63c99..e6068e0d0660 100644
--- a/drivers/irqchip/irq-al-fic.c
+++ b/drivers/irqchip/irq-al-fic.c
@@ -4,9 +4,9 @@
*/
#include <linux/bitfield.h>
+#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
-#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -16,11 +16,14 @@
/* FIC Registers */
#define AL_FIC_CAUSE 0x00
#define AL_FIC_SET_CAUSE 0x08
-#define AL_FIC_MASK 0x10
+#define AL_FIC_INFO_MASK 0x10
#define AL_FIC_CONTROL 0x28
+#define AL_FIC_ERROR_MASK 0x2c
+#define AL_FIC_FATAL_MASK 0x34
#define CONTROL_TRIGGER_RISING BIT(3)
#define CONTROL_MASK_MSI_X BIT(5)
+#define CONTROL_VERSION_ID GENMASK(29, 28)
#define NR_FIC_IRQS 32
@@ -33,20 +36,51 @@ enum al_fic_state {
AL_FIC_CONFIGURED_RISING_EDGE,
};
+/*
+ * FIC hardware revision, as reported by the CONTROL register version field
+ * (CONTROL_VERSION_ID, bits 29-28). These are this driver's names for that
+ * field's encoding.
+ */
+enum al_fic_version {
+ AL_FIC_VERSION_V1,
+ AL_FIC_VERSION_V2,
+ AL_FIC_VERSION_V3,
+};
+
+enum al_fic_id {
+ AL_FIC_ID_INFO,
+ AL_FIC_ID_ERROR,
+ AL_FIC_ID_FATAL,
+ AL_FIC_ID_MAX,
+};
+
+/* Mask register offset for each interrupt group */
+static const unsigned int al_fic_mask_offset[AL_FIC_ID_MAX] = {
+ [AL_FIC_ID_INFO] = AL_FIC_INFO_MASK,
+ [AL_FIC_ID_ERROR] = AL_FIC_ERROR_MASK,
+ [AL_FIC_ID_FATAL] = AL_FIC_FATAL_MASK,
+};
+
+/* amazon,al-fic-mask property value for each interrupt group */
+static const char * const al_fic_mask_name[AL_FIC_ID_MAX] = {
+ [AL_FIC_ID_INFO] = "info",
+ [AL_FIC_ID_ERROR] = "error",
+ [AL_FIC_ID_FATAL] = "fatal",
+};
+
struct al_fic {
- void __iomem *base;
- struct irq_domain *domain;
- const char *name;
- unsigned int parent_irq;
- enum al_fic_state state;
+ void __iomem *base;
+ struct irq_domain *domain;
+ struct device_node *node;
+ unsigned int parent_irq;
+ enum al_fic_state state;
};
-static void al_fic_set_trigger(struct al_fic *fic,
- struct irq_chip_generic *gc,
+static void al_fic_set_trigger(struct al_fic *fic, struct irq_chip_generic *gc,
enum al_fic_state new_state)
{
- irq_flow_handler_t handler;
u32 control = readl_relaxed(fic->base + AL_FIC_CONTROL);
+ irq_flow_handler_t handler;
if (new_state == AL_FIC_CONFIGURED_LEVEL) {
handler = handle_level_irq;
@@ -89,30 +123,34 @@ static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type)
if (fic->state == AL_FIC_UNCONFIGURED) {
al_fic_set_trigger(fic, gc, new_state);
} else if (fic->state != new_state) {
- pr_debug("fic %s state already configured to %d\n", fic->name, fic->state);
+ pr_debug("fic %pOF state already configured to %d\n", fic->node, fic->state);
return -EINVAL;
}
return 0;
}
-static void al_fic_irq_handler(struct irq_desc *desc)
+static irqreturn_t al_fic_irq_handler(int irq, void *data)
{
- struct al_fic *fic = irq_desc_get_handler_data(desc);
+ struct al_fic *fic = data;
struct irq_domain *domain = fic->domain;
- struct irq_chip *irqchip = irq_desc_get_chip(desc);
struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
unsigned long pending;
u32 hwirq;
- chained_irq_enter(irqchip, desc);
-
pending = readl_relaxed(fic->base + AL_FIC_CAUSE);
pending &= ~gc->mask_cache;
- for_each_set_bit(hwirq, &pending, NR_FIC_IRQS)
- generic_handle_domain_irq(domain, hwirq);
+ for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) {
+ /*
+ * Redirect a child whose affinity excludes this CPU to its
+ * target CPU via irq_work. The ack moves to
+ * al_fic_irq_pre_redirect(), because the whole flow handler is
+ * deferred when that happens.
+ */
+ generic_handle_demux_domain_irq(domain, hwirq);
+ }
- chained_irq_exit(irqchip, desc);
+ return pending ? IRQ_HANDLED : IRQ_NONE;
}
static int al_fic_irq_retrigger(struct irq_data *data)
@@ -125,26 +163,53 @@ static int al_fic_irq_retrigger(struct irq_data *data)
return 1;
}
-static int al_fic_register(struct device_node *node,
- struct al_fic *fic)
+#ifdef CONFIG_SMP
+/* The ack happens in al_fic_irq_pre_redirect() on both paths instead. */
+static void al_fic_ack_noop(struct irq_data *data) { }
+
+/*
+ * Runs in the parent interrupt context on every dispatch, redirected or not.
+ * Clear the cause bit here: once the flow handler is deferred, the FIC keeps
+ * driving its output until the irq_work runs, and a level parent SPI re-fires.
+ *
+ * A level configured group is masked too, because its cause bit re-latches
+ * while the input stays asserted; the deferred handle_level_irq() unmasks
+ * when the handler is done. An edge configured group is only acked, since
+ * handle_edge_irq() never unmasks unconditionally and the mask would stay.
+ */
+static void al_fic_irq_pre_redirect(struct irq_data *data)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
+ struct al_fic *fic = gc->private;
+
+ if (fic->state == AL_FIC_CONFIGURED_LEVEL)
+ irq_gc_mask_set_bit(data);
+
+ irq_gc_ack_clr_bit(data);
+}
+#endif /* CONFIG_SMP */
+
+static int al_fic_register(struct device_node *node, struct al_fic *fic, enum al_fic_id fic_id)
{
struct irq_chip_generic *gc;
int ret;
- fic->domain = irq_domain_create_linear(of_fwnode_handle(node),
- NR_FIC_IRQS,
- &irq_generic_chip_ops,
- fic);
+ fic->domain = irq_domain_create_linear(of_fwnode_handle(node), NR_FIC_IRQS,
+ &irq_generic_chip_ops, fic);
if (!fic->domain) {
pr_err("fail to add irq domain\n");
return -ENOMEM;
}
- ret = irq_alloc_domain_generic_chips(fic->domain,
- NR_FIC_IRQS,
- 1, fic->name,
- handle_level_irq,
- 0, 0, IRQ_GC_INIT_MASK_CACHE);
+ /*
+ * Let irq_domain_remove() free the generic chips on either error path
+ * below, instead of calling irq_domain_remove_generic_chips() by hand.
+ */
+ fic->domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC;
+
+ ret = irq_alloc_domain_generic_chips(fic->domain, NR_FIC_IRQS,
+ 1, of_node_full_name(fic->node), handle_level_irq,
+ 0, 0, 0);
if (ret) {
pr_err("fail to allocate generic chip (%d)\n", ret);
goto err_domain_remove;
@@ -152,47 +217,67 @@ static int al_fic_register(struct device_node *node,
gc = irq_get_domain_generic_chip(fic->domain, 0);
gc->reg_base = fic->base;
- gc->chip_types->regs.mask = AL_FIC_MASK;
+ gc->chip_types->regs.mask = al_fic_mask_offset[fic_id];
gc->chip_types->regs.ack = AL_FIC_CAUSE;
gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit;
gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit;
- gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit;
gc->chip_types->chip.irq_set_type = al_fic_irq_set_type;
gc->chip_types->chip.irq_retrigger = al_fic_irq_retrigger;
+#ifdef CONFIG_SMP
+ /*
+ * The FIC has no hardware affinity control, so give each child real
+ * affinity by redirecting its handler to the target CPU.
+ */
+ gc->chip_types->chip.irq_ack = al_fic_ack_noop;
+ gc->chip_types->chip.irq_pre_redirect = al_fic_irq_pre_redirect;
+ gc->chip_types->chip.irq_set_affinity = irq_chip_redirect_set_affinity;
+#else
+ gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit;
+#endif
gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE;
gc->private = fic;
- irq_set_chained_handler_and_data(fic->parent_irq,
- al_fic_irq_handler,
- fic);
+ /*
+ * Seed the mask cache with the value al_fic_wire_init() programmed,
+ * rather than having the generic chip read the register back on the
+ * first child mapping: that is later than the parent is requested, and
+ * never happens at all for a group with no consumer in the devicetree.
+ */
+ gc->mask_cache = ~0U;
+
+ ret = request_irq(fic->parent_irq, al_fic_irq_handler, IRQF_NO_THREAD | IRQF_SHARED,
+ of_node_full_name(fic->node), fic);
+ if (ret) {
+ pr_err("fail to request irq (%d)\n", ret);
+ goto err_domain_remove;
+ }
+
return 0;
err_domain_remove:
irq_domain_remove(fic->domain);
-
return ret;
}
/*
* al_fic_wire_init() - initialize and configure fic in wire mode
- * @of_node: optional pointer to interrupt controller's device tree node.
+ * @node: pointer to the interrupt controller's device tree node
* @base: mmio to fic register
- * @name: name of the fic
* @parent_irq: interrupt of parent
+ * @fic_id: which of the controller's outputs (info, error or fatal) this
+ * group drives
*
- * This API will configure the fic hardware to to work in wire mode.
+ * This API will configure the fic hardware to work in wire mode.
* In wire mode, fic hardware is generating a wire ("wired") interrupt.
* Interrupt can be generated based on positive edge or level - configuration is
* to be determined based on connected hardware to this fic.
*/
-static struct al_fic *al_fic_wire_init(struct device_node *node,
- void __iomem *base,
- const char *name,
- unsigned int parent_irq)
+static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *base,
+ unsigned int parent_irq, enum al_fic_id fic_id)
{
+ u32 control, version_id;
struct al_fic *fic;
int ret;
- u32 control = CONTROL_MASK_MSI_X;
fic = kzalloc_obj(*fic);
if (!fic)
@@ -200,67 +285,110 @@ static struct al_fic *al_fic_wire_init(struct device_node *node,
fic->base = base;
fic->parent_irq = parent_irq;
- fic->name = name;
+ fic->node = of_node_get(node);
+
+ control = readl_relaxed(fic->base + AL_FIC_CONTROL);
+ version_id = FIELD_GET(CONTROL_VERSION_ID, control);
+ if (version_id == AL_FIC_VERSION_V1 && fic_id != AL_FIC_ID_INFO) {
+ pr_err("%pOF: amazon,al-fic-mask = \"%s\" not available on FIC v1\n",
+ node, al_fic_mask_name[fic_id]);
+ ret = -EINVAL;
+ goto err_free;
+ }
/* mask out all interrupts */
- writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK);
+ writel_relaxed(~0U, fic->base + AL_FIC_INFO_MASK);
+ if (version_id > AL_FIC_VERSION_V1) {
+ writel_relaxed(~0U, fic->base + AL_FIC_ERROR_MASK);
+ writel_relaxed(~0U, fic->base + AL_FIC_FATAL_MASK);
+ }
/* clear any pending interrupt */
writel_relaxed(0, fic->base + AL_FIC_CAUSE);
+ /* make sure the controller works in non msi_x mode */
+ control |= CONTROL_MASK_MSI_X;
writel_relaxed(control, fic->base + AL_FIC_CONTROL);
- ret = al_fic_register(node, fic);
+ ret = al_fic_register(node, fic, fic_id);
if (ret) {
pr_err("fail to register irqchip\n");
goto err_free;
}
- pr_debug("%s initialized successfully in Legacy mode (parent-irq=%u)\n",
- fic->name, parent_irq);
+ pr_info("%pOF initialized successfully (mask=%s parent-irq=%u)\n",
+ node, al_fic_mask_name[fic_id], parent_irq);
return fic;
err_free:
+ of_node_put(fic->node);
kfree(fic);
return ERR_PTR(ret);
}
-static int __init al_fic_init_dt(struct device_node *node,
- struct device_node *parent)
+/*
+ * Parse the amazon,al-fic-mask property into an enum al_fic_id, selecting
+ * which of the controller's outputs this group drives. The property is
+ * optional; an absent property means the info output.
+ */
+static int al_fic_parse_mask(struct device_node *node, enum al_fic_id *fic_id)
{
+ const char *mask;
int ret;
- void __iomem *base;
+
+ ret = of_property_read_string(node, "amazon,al-fic-mask", &mask);
+ if (ret == -EINVAL) {
+ *fic_id = AL_FIC_ID_INFO;
+ return 0;
+ }
+ if (ret)
+ return ret;
+
+ ret = match_string(al_fic_mask_name, AL_FIC_ID_MAX, mask);
+ if (ret < 0)
+ return ret;
+
+ *fic_id = ret;
+ return 0;
+}
+
+static int __init al_fic_init_dt(struct device_node *node, struct device_node *parent)
+{
unsigned int parent_irq;
+ enum al_fic_id fic_id;
struct al_fic *fic;
+ void __iomem *base;
+ int ret;
if (!parent) {
- pr_err("%s: unsupported - device require a parent\n",
- node->name);
+ pr_err("%pOF: unsupported - device requires a parent\n", node);
return -EINVAL;
}
+ ret = al_fic_parse_mask(node, &fic_id);
+ if (ret) {
+ pr_err("%pOF: invalid amazon,al-fic-mask\n", node);
+ return ret;
+ }
+
base = of_iomap(node, 0);
if (!base) {
- pr_err("%s: fail to map memory\n", node->name);
+ pr_err("%pOF: fail to map memory\n", node);
return -ENOMEM;
}
parent_irq = irq_of_parse_and_map(node, 0);
if (!parent_irq) {
- pr_err("%s: fail to map irq\n", node->name);
+ pr_err("%pOF: fail to map irq\n", node);
ret = -EINVAL;
goto err_unmap;
}
- fic = al_fic_wire_init(node,
- base,
- node->name,
- parent_irq);
+ fic = al_fic_wire_init(node, base, parent_irq, fic_id);
+
if (IS_ERR(fic)) {
- pr_err("%s: fail to initialize irqchip (%lu)\n",
- node->name,
- PTR_ERR(fic));
+ pr_err("%pOF: fail to initialize irqchip (%pe)\n", node, fic);
ret = PTR_ERR(fic);
goto err_irq_dispose;
}
@@ -271,7 +399,6 @@ static int __init al_fic_init_dt(struct device_node *node,
irq_dispose_mapping(parent_irq);
err_unmap:
iounmap(base);
-
return ret;
}
diff --git a/drivers/irqchip/irq-atmel-aic.c b/drivers/irqchip/irq-atmel-aic.c
index 1dcc52760eca..4a7935e75e4d 100644
--- a/drivers/irqchip/irq-atmel-aic.c
+++ b/drivers/irqchip/irq-atmel-aic.c
@@ -123,7 +123,7 @@ static void aic_pm_shutdown(struct irq_data *d)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
- guard(raw_spinlock)(&gc->lock);
+ guard(raw_spinlock_irqsave)(&gc->lock);
irq_reg_writel(gc, 0xffffffff, AT91_AIC_IDCR);
irq_reg_writel(gc, 0xffffffff, AT91_AIC_ICCR);
}
diff --git a/drivers/irqchip/irq-atmel-aic5.c b/drivers/irqchip/irq-atmel-aic5.c
index 1f14b401f71d..07a317ed06ef 100644
--- a/drivers/irqchip/irq-atmel-aic5.c
+++ b/drivers/irqchip/irq-atmel-aic5.c
@@ -217,7 +217,7 @@ static void aic5_pm_shutdown(struct irq_data *d)
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
int i;
- guard(raw_spinlock)(&bgc->lock);
+ guard(raw_spinlock_irqsave)(&bgc->lock);
for (i = 0; i < dgc->irqs_per_chip; i++) {
irq_reg_writel(bgc, i + gc->irq_base, AT91_AIC5_SSR);
irq_reg_writel(bgc, 1, AT91_AIC5_IDCR);
diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c
index c776f9142610..8bd1eaa54295 100644
--- a/drivers/irqchip/irq-gic-common.c
+++ b/drivers/irqchip/irq-gic-common.c
@@ -48,7 +48,7 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
int gic_configure_irq(unsigned int irq, unsigned int type,
void __iomem *base)
{
- u32 confmask = 0x2 << ((irq % 16) * 2);
+ u32 confmask = BIT(((irq % 16) * 2) + 1);
u32 confoff = (irq / 16) * 4;
u32 val, oldval;
int ret = 0;
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index e9807af23537..282e4bafb084 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -87,6 +87,8 @@ struct its_baser {
u64 val;
u32 order;
u32 psz;
+ /* Inherited from a sibling ITS, not freed here */
+ bool shared;
};
struct its_device;
@@ -2248,7 +2250,8 @@ static unsigned long *its_lpi_alloc(int nr_irqs, u32 *base, int *nr_ids)
static void its_lpi_free(unsigned long *bitmap, u32 base, u32 nr_ids)
{
- WARN_ON(free_lpi_range(base, nr_ids));
+ if (free_lpi_range(base, nr_ids))
+ pr_err_ratelimited("ITS: failed to free LPI range %u:%u\n", base, nr_ids);
bitmap_free(bitmap);
}
@@ -2604,7 +2607,8 @@ static void its_free_tables(struct its_node *its)
for (i = 0; i < GITS_BASER_NR_REGS; i++) {
if (its->tables[i].base) {
- its_free_pages(its->tables[i].base, its->tables[i].order);
+ if (!its->tables[i].shared)
+ its_free_pages(its->tables[i].base, its->tables[i].order);
its->tables[i].base = NULL;
}
}
@@ -2703,6 +2707,7 @@ static int its_alloc_tables(struct its_node *its)
WARN_ON(i != 2);
if ((sibling = find_sibling_its(its))) {
*baser = sibling->tables[2];
+ baser->shared = true;
its_write_baser(its, baser, baser->val);
continue;
}
@@ -3484,7 +3489,7 @@ static struct its_device *its_create_device(struct its_node *its, u32 dev_id,
* Also honor the ITS's own EID limit.
*/
id_bits = FIELD_GET(GITS_TYPER_IDBITS, its->typer) + 1;
- nvecs = min_t(unsigned int, nvecs, BIT(id_bits));
+ nvecs = min_t(u64, nvecs, BIT_ULL(id_bits));
nr_ites = max(2, nvecs);
sz = nr_ites * (FIELD_GET(GITS_TYPER_ITT_ENTRY_SIZE, its->typer) + 1);
sz = max(sz, ITS_ITT_ALIGN);
@@ -4599,6 +4604,7 @@ static void its_vpe_teardown(struct its_vpe *vpe)
its_vpe_db_proxy_unmap(vpe);
its_vpe_id_free(vpe->vpe_id);
its_free_pending_table(vpe->vpt_page);
+ vpe->vpt_page = NULL;
}
static void its_vpe_irq_domain_free(struct irq_domain *domain,
@@ -4895,6 +4901,9 @@ static bool __maybe_unused its_enable_quirk_hip09_162100801(void *data)
}
static const char * const dma_32bit_impaired_platforms[] = {
+#ifdef CONFIG_ALTERA_ERRATUM_AGILEX5_2_1_23
+ "intel,socfpga-agilex5",
+#endif
#ifdef CONFIG_RENESAS_ERRATUM_GEN4GICITS1
"renesas,r8a779f0",
"renesas,r8a779g0",
@@ -5328,16 +5337,18 @@ static int __init its_probe_one(struct its_node *its)
}
gits_write_cwriter(0, its->base + GITS_CWRITER);
+
+ err = its_init_domain(its);
+ if (err)
+ goto out_free_collection;
+
+ /* Enable last: the unwind frees memory an enabled ITS may access */
ctlr = readl_relaxed(its->base + GITS_CTLR);
ctlr |= GITS_CTLR_ENABLE;
if (is_v4(its))
ctlr |= GITS_CTLR_ImDe;
writel_relaxed(ctlr, its->base + GITS_CTLR);
- err = its_init_domain(its);
- if (err)
- goto out_free_collection;
-
raw_spin_lock(&its_lock);
list_add(&its->entry, &its_nodes);
raw_spin_unlock(&its_lock);
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
index 6e1fa5b247fc..94873646d46f 100644
--- a/drivers/irqchip/irq-gic-v3.c
+++ b/drivers/irqchip/irq-gic-v3.c
@@ -164,6 +164,7 @@ static inline bool gic_dist_security_disabled(void)
static bool cpus_have_security_disabled __ro_after_init;
static bool cpus_have_group0 __ro_after_init;
+static void gic_dist_wait_for_rwp(void);
static void __init gic_prio_init(void)
{
@@ -179,6 +180,7 @@ static void __init gic_prio_init(void)
val = readl_relaxed(gic_data.dist_base + GICD_CTLR);
val |= GICD_CTLR_DS;
writel_relaxed(val, gic_data.dist_base + GICD_CTLR);
+ gic_dist_wait_for_rwp();
ds = gic_dist_security_disabled();
if (ds)
@@ -457,7 +459,7 @@ static int gic_peek_irq(struct irq_data *d, u32 offset)
u32 index, mask;
offset = convert_offset_index(d, offset, &index);
- mask = 1 << (index % 32);
+ mask = BIT(index % 32);
if (gic_irq_in_rdist(d))
base = gic_data_rdist_sgi_base();
@@ -473,7 +475,7 @@ static void gic_poke_irq(struct irq_data *d, u32 offset)
u32 index, mask;
offset = convert_offset_index(d, offset, &index);
- mask = 1 << (index % 32);
+ mask = BIT(index % 32);
if (gic_irq_in_rdist(d))
base = gic_data_rdist_sgi_base();
@@ -687,7 +689,7 @@ static void gic_eoimode1_eoi_irq(struct irq_data *d)
{
/*
* No need to deactivate an LPI, or an interrupt that
- * is is getting forwarded to a vcpu.
+ * is getting forwarded to a vcpu.
*/
if (irqd_to_hwirq(d) >= 8192 || irqd_is_forwarded_to_vcpu(d))
return;
@@ -1728,8 +1730,6 @@ static int gic_irq_get_fwspec_info(struct irq_fwspec *fwspec, struct irq_fwspec_
/* If the specifier provides an affinity, use it */
if (fwspec->param_count == 4 && fwspec->param[3]) {
- struct fwnode_handle *fw;
-
switch (fwspec->param[0]) {
case 1: /* PPI */
case 3: /* EPPI */
@@ -1738,7 +1738,10 @@ static int gic_irq_get_fwspec_info(struct irq_fwspec *fwspec, struct irq_fwspec_
return 0;
}
- fw = of_fwnode_handle(of_find_node_by_phandle(fwspec->param[3]));
+ struct device_node *np __free(device_node) =
+ of_find_node_by_phandle(fwspec->param[3]);
+ struct fwnode_handle *fw = of_fwnode_handle(np);
+
if (!fw)
return -ENOENT;
@@ -2276,7 +2279,6 @@ static struct
bool single_redist;
int enabled_rdists;
u32 maint_irq;
- int maint_irq_mode;
phys_addr_t vcpu_base;
} acpi_data __initdata;
@@ -2344,6 +2346,17 @@ gic_acpi_parse_madt_gicc(union acpi_subtable_headers *header,
return 0;
}
+ /* Not counted by gic_acpi_match_gicc(), so there is no slot for it */
+ if (!gicc->gicr_base_address) {
+ int cpu = get_cpu_for_acpi_id(gicc->uid);
+
+ pr_warn(FW_BUG "GICC entry with ACPI UID %u has no GICR base address, CPU kept offline\n",
+ gicc->uid);
+ if (cpu >= 0)
+ cpumask_set_cpu(cpu, &broken_rdists);
+ return 0;
+ }
+
redist_base = ioremap(gicc->gicr_base_address, size);
if (!redist_base)
return -ENOMEM;
@@ -2454,21 +2467,19 @@ static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *hea
{
struct acpi_madt_generic_interrupt *gicc =
(struct acpi_madt_generic_interrupt *)header;
- int maint_irq_mode;
static int first_madt = true;
if (!(gicc->flags &
(ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE)))
return 0;
- maint_irq_mode = (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE) ?
- ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE;
+ if (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE)
+ pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n");
if (first_madt) {
first_madt = false;
acpi_data.maint_irq = gicc->vgic_interrupt;
- acpi_data.maint_irq_mode = maint_irq_mode;
acpi_data.vcpu_base = gicc->gicv_base_address;
return 0;
@@ -2478,7 +2489,6 @@ static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *hea
* The maintenance interrupt and GICV should be the same for every CPU
*/
if ((acpi_data.maint_irq != gicc->vgic_interrupt) ||
- (acpi_data.maint_irq_mode != maint_irq_mode) ||
(acpi_data.vcpu_base != gicc->gicv_base_address))
return -EINVAL;
@@ -2511,7 +2521,7 @@ static void __init gic_acpi_setup_kvm_info(void)
gic_v3_kvm_info.type = GIC_V3;
irq = acpi_register_gsi(NULL, acpi_data.maint_irq,
- acpi_data.maint_irq_mode,
+ ACPI_LEVEL_SENSITIVE,
ACPI_ACTIVE_HIGH);
if (irq <= 0)
return;
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index b3feb6340b59..53639e6f85bd 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -609,6 +609,10 @@ static int __init gicv5_irs_of_init_affinity(struct device_node *node,
u16 iaffid_mask = GENMASK(iaffid_bits - 1, 0);
int ret, i, ncpus, niaffids;
+ /* An IRS is architecturally allowed to have no cpus associated with it */
+ if (!of_property_present(node, "cpus") && !of_property_present(node, "arm,iaffids"))
+ return 0;
+
ncpus = of_count_phandle_with_args(node, "cpus", NULL);
if (ncpus < 0)
return -EINVAL;
diff --git a/drivers/irqchip/irq-gic-v5-iwb.c b/drivers/irqchip/irq-gic-v5-iwb.c
index 442c20614dbe..34afa036c34d 100644
--- a/drivers/irqchip/irq-gic-v5-iwb.c
+++ b/drivers/irqchip/irq-gic-v5-iwb.c
@@ -282,8 +282,8 @@ MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id iwb_acpi_match[] = {
- { "ARMH0003", 0 },
- {}
+ { .id = "ARMH0003" },
+ { }
};
#endif
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index ac2d423b1723..c365f8150300 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1166,21 +1166,18 @@ static int __init gicv5_init_common(struct fwnode_handle *parent_domain)
if (ret)
goto out_int;
- ret = set_handle_irq(gicv5_handle_irq);
+ ret = gicv5_irs_enable();
if (ret)
goto out_int;
- ret = gicv5_irs_enable();
- if (ret)
- goto out_handle;
+ if (set_handle_irq(gicv5_handle_irq))
+ panic("GICv5: unable to install root IRQ handler\n");
gicv5_smp_init();
gicv5_irs_its_probe();
return 0;
-out_handle:
- set_handle_irq(NULL);
out_int:
gicv5_cpu_disable_interrupts();
gicv5_free_domains();
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c
index f6bc29f515fb..a5ca04e9be64 100644
--- a/drivers/irqchip/irq-gic.c
+++ b/drivers/irqchip/irq-gic.c
@@ -178,14 +178,14 @@ static inline bool cascading_gic_irq(struct irq_data *d)
*/
static void gic_poke_irq(struct irq_data *d, u32 offset)
{
- u32 mask = 1 << (irqd_to_hwirq(d) % 32);
+ u32 mask = BIT(irqd_to_hwirq(d) % 32);
writel_relaxed(mask, gic_dist_base(d) + offset + (irqd_to_hwirq(d) / 32) * 4);
}
static int gic_peek_irq(struct irq_data *d, u32 offset)
{
- u32 mask = 1 << (irqd_to_hwirq(d) % 32);
+ u32 mask = BIT(irqd_to_hwirq(d) % 32);
return !!(readl_relaxed(gic_dist_base(d) + offset + (irqd_to_hwirq(d) / 32) * 4) & mask);
}
@@ -1527,7 +1527,6 @@ static struct
{
phys_addr_t cpu_phys_base;
u32 maint_irq;
- int maint_irq_mode;
phys_addr_t vctrl_base;
phys_addr_t vcpu_base;
} acpi_data __initdata;
@@ -1553,10 +1552,11 @@ gic_acpi_parse_madt_cpu(union acpi_subtable_headers *header,
if (cpu_base_assigned && gic_cpu_base != acpi_data.cpu_phys_base)
return -EINVAL;
+ if (processor->flags & ACPI_MADT_VGIC_IRQ_MODE)
+ pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n");
+
acpi_data.cpu_phys_base = gic_cpu_base;
acpi_data.maint_irq = processor->vgic_interrupt;
- acpi_data.maint_irq_mode = (processor->flags & ACPI_MADT_VGIC_IRQ_MODE) ?
- ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE;
acpi_data.vctrl_base = processor->gich_base_address;
acpi_data.vcpu_base = processor->gicv_base_address;
@@ -1616,7 +1616,7 @@ static void __init gic_acpi_setup_kvm_info(void)
vcpu_res->end = vcpu_res->start + ACPI_GICV2_VCPU_MEM_SIZE - 1;
irq = acpi_register_gsi(NULL, acpi_data.maint_irq,
- acpi_data.maint_irq_mode,
+ ACPI_LEVEL_SENSITIVE,
ACPI_ACTIVE_HIGH);
if (irq <= 0)
return;
diff --git a/drivers/irqchip/irq-imx-intmux.c b/drivers/irqchip/irq-imx-intmux.c
index 47c2681d138a..de2a8f98f625 100644
--- a/drivers/irqchip/irq-imx-intmux.c
+++ b/drivers/irqchip/irq-imx-intmux.c
@@ -288,12 +288,16 @@ static int imx_intmux_probe(struct platform_device *pdev)
static void imx_intmux_remove(struct platform_device *pdev)
{
struct intmux_data *data = platform_get_drvdata(pdev);
- int i;
+ int i, ret;
+
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "failed to resume device: %d\n", ret);
for (i = 0; i < data->channum; i++) {
/* disable all interrupt sources of this channel */
- writel_relaxed(0, data->regs + CHANIER(i));
-
+ if (ret >= 0)
+ writel_relaxed(0, data->regs + CHANIER(i));
irq_set_chained_handler_and_data(data->irqchip_data[i].irq,
NULL, NULL);
@@ -301,6 +305,11 @@ static void imx_intmux_remove(struct platform_device *pdev)
}
pm_runtime_disable(&pdev->dev);
+ if (ret >= 0) {
+ pm_runtime_put_noidle(&pdev->dev);
+ clk_disable_unprepare(data->ipg_clk);
+ pm_runtime_set_suspended(&pdev->dev);
+ }
}
#ifdef CONFIG_PM
diff --git a/drivers/irqchip/irq-jcore-aic.c b/drivers/irqchip/irq-jcore-aic.c
index 94c05cf974be..b4fee02997ee 100644
--- a/drivers/irqchip/irq-jcore-aic.c
+++ b/drivers/irqchip/irq-jcore-aic.c
@@ -37,8 +37,10 @@ static struct irq_chip jcore_aic;
static void handle_jcore_irq(struct irq_desc *desc)
{
- if (irqd_is_per_cpu(irq_desc_get_irq_data(desc)))
+ if (irq_is_percpu_devid(irq_desc_get_irq(desc)))
handle_percpu_devid_irq(desc);
+ else if (irqd_is_per_cpu(irq_desc_get_irq_data(desc)))
+ handle_percpu_irq(desc);
else
handle_simple_irq(desc);
}
diff --git a/drivers/irqchip/irq-lan966x-oic.c b/drivers/irqchip/irq-lan966x-oic.c
index 8af08d0e4182..5122f3f1b353 100644
--- a/drivers/irqchip/irq-lan966x-oic.c
+++ b/drivers/irqchip/irq-lan966x-oic.c
@@ -220,7 +220,6 @@ static int lan966x_oic_probe(struct platform_device *pdev)
};
struct irq_domain_info d_info = {
.fwnode = of_fwnode_handle(pdev->dev.of_node),
- .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC,
.size = LAN966X_OIC_NR_IRQ,
.hwirq_max = LAN966X_OIC_NR_IRQ,
.ops = &irq_generic_chip_ops,
diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c
index a9e8b481d31d..ba6e8480e383 100644
--- a/drivers/irqchip/irq-loongson-eiointc.c
+++ b/drivers/irqchip/irq-loongson-eiointc.c
@@ -454,7 +454,7 @@ static int __init pch_pic_parse_madt(union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_bio_pic *pchpic_entry = (struct acpi_madt_bio_pic *)header;
- unsigned int node = (pchpic_entry->address >> 44) & 0xf;
+ unsigned int node = pa_to_nid(pchpic_entry->address) & 0xf;
struct irq_domain *parent = acpi_get_vec_parent(node, pch_group);
if (parent)
diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c
index 6f69f4e5dbac..3c54f2402aeb 100644
--- a/drivers/irqchip/irq-mbigen.c
+++ b/drivers/irqchip/irq-mbigen.c
@@ -259,8 +259,8 @@ static int mbigen_of_create_domain(struct platform_device *pdev,
#ifdef CONFIG_ACPI
static const struct acpi_device_id mbigen_acpi_match[] = {
- { "HISI0152", 0 },
- {}
+ { .id = "HISI0152" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match);
diff --git a/drivers/irqchip/irq-mchp-eic.c b/drivers/irqchip/irq-mchp-eic.c
index be007d325e67..5250e6baff63 100644
--- a/drivers/irqchip/irq-mchp-eic.c
+++ b/drivers/irqchip/irq-mchp-eic.c
@@ -233,7 +233,7 @@ static int mchp_eic_probe(struct platform_device *pdev, struct device_node *pare
ret = clk_prepare_enable(eic->clk);
if (ret)
- goto unmap;
+ goto clk_put;
for (i = 0; i < MCHP_EIC_NIRQ; i++) {
struct of_phandle_args irq;
@@ -270,6 +270,8 @@ static int mchp_eic_probe(struct platform_device *pdev, struct device_node *pare
clk_unprepare:
clk_disable_unprepare(eic->clk);
+clk_put:
+ clk_put(eic->clk);
unmap:
iounmap(eic->base);
free:
diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c
index 91a9c337fe6d..e0329ef5e4a2 100644
--- a/drivers/irqchip/irq-meson-gpio.c
+++ b/drivers/irqchip/irq-meson-gpio.c
@@ -538,7 +538,7 @@ static struct irq_chip meson_gpio_irq_chip = {
#ifdef CONFIG_SMP
.irq_set_affinity = irq_chip_set_affinity_parent,
#endif
- .flags = IRQCHIP_SET_TYPE_MASKED,
+ .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE,
};
static int meson_gpio_irq_domain_translate(struct irq_domain *domain,
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 19a57c5e2b2e..be38989d8e73 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -70,6 +70,10 @@ static int __gic_with_next_online_cpu(int prev)
{
unsigned int cpu;
+ /* Release the redirect/other region lock to the previous CPU, if any. */
+ if (prev >= 0)
+ mips_cm_unlock_other();
+
/* Discover the next online CPU */
cpu = cpumask_next(prev, cpu_online_mask);
@@ -77,23 +81,12 @@ static int __gic_with_next_online_cpu(int prev)
if (cpu >= nr_cpu_ids)
return cpu;
- /*
- * Move the access lock to the next CPU's GIC local register block.
- *
- * Set GIC_VL_OTHER. Since the caller holds gic_lock nothing can
- * clobber the written value.
- */
- write_gic_vl_other(mips_cm_vp_id(cpu));
+ /* Lock access to redirect/other region to the next CPU */
+ mips_cm_lock_other_cpu(cpu, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
return cpu;
}
-static inline void gic_unlock_cluster(void)
-{
- if (mips_cps_multicluster_cpus())
- mips_cm_unlock_other();
-}
-
/**
* for_each_online_cpu_gic() - Iterate over online CPUs, access local registers
* @cpu: An integer variable to hold the current CPU number
@@ -108,7 +101,6 @@ static inline void gic_unlock_cluster(void)
guard(raw_spinlock_irqsave)(gic_lock); \
for ((cpu) = __gic_with_next_online_cpu(-1); \
(cpu) < nr_cpu_ids; \
- gic_unlock_cluster(), \
(cpu) = __gic_with_next_online_cpu(cpu))
/**
@@ -305,14 +297,14 @@ static void gic_ack_irq(struct irq_data *d)
}
}
-static int gic_set_type(struct irq_data *d, unsigned int type)
+static int gic_set_type_locked(struct irq_data *d, unsigned int type)
{
unsigned int irq, pol, trig, dual;
- unsigned long flags;
+
+ lockdep_assert_held(&gic_lock);
irq = GIC_HWIRQ_TO_SHARED(d->hwirq);
- raw_spin_lock_irqsave(&gic_lock, flags);
switch (type & IRQ_TYPE_SENSE_MASK) {
case IRQ_TYPE_EDGE_FALLING:
pol = GIC_POL_FALLING_EDGE;
@@ -359,11 +351,16 @@ static int gic_set_type(struct irq_data *d, unsigned int type)
else
irq_set_chip_handler_name_locked(d, &gic_level_irq_controller,
handle_level_irq, NULL);
- raw_spin_unlock_irqrestore(&gic_lock, flags);
return 0;
}
+static int gic_set_type(struct irq_data *d, unsigned int type)
+{
+ guard(raw_spinlock_irqsave)(&gic_lock);
+ return gic_set_type_locked(d, type);
+}
+
#ifdef CONFIG_SMP
static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
bool force)
@@ -371,6 +368,7 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq);
unsigned int cpu, cl, old_cpu, old_cl;
unsigned long flags;
+ bool enabled;
/*
* The GIC specifies that we can only route an interrupt to one VP(E),
@@ -392,15 +390,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
raw_spin_lock_irqsave(&gic_lock, flags);
/*
- * If we're moving affinity between clusters, stop routing the
- * interrupt to any VP(E) in the old cluster.
+ * If we're moving affinity between clusters, save the interrupt's
+ * mask state, stop routing it to any VP(E) in the old cluster and
+ * disable it there so it is no longer delivered.
*/
if (cl != old_cl) {
if (gic_irq_lock_cluster(d)) {
+ enabled = read_gic_redir_mask(irq);
write_gic_redir_map_vp(irq, 0);
+ write_gic_redir_rmask(irq);
mips_cm_unlock_other();
} else {
+ enabled = read_gic_mask(irq);
write_gic_map_vp(irq, 0);
+ write_gic_rmask(irq);
}
}
@@ -412,10 +415,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
/*
* If we're moving affinity between clusters, configure the interrupt
- * trigger type in the new cluster.
+ * trigger type and, if it was enabled in the old cluster, enable it
+ * in the new one.
*/
- if (cl != old_cl)
- gic_set_type(d, irqd_get_trigger_type(d));
+ if (cl != old_cl) {
+ gic_set_type_locked(d, irqd_get_trigger_type(d));
+ if (enabled) {
+ if (gic_irq_lock_cluster(d)) {
+ write_gic_redir_smask(irq);
+ mips_cm_unlock_other();
+ } else {
+ write_gic_smask(irq);
+ }
+ }
+ }
/* Route the interrupt to its new VP(E) */
if (gic_irq_lock_cluster(d)) {
diff --git a/drivers/irqchip/irq-mst-intc.c b/drivers/irqchip/irq-mst-intc.c
index b5335f6fd6d6..1070de5de8cc 100644
--- a/drivers/irqchip/irq-mst-intc.c
+++ b/drivers/irqchip/irq-mst-intc.c
@@ -263,6 +263,9 @@ static int __init mst_intc_of_init(struct device_node *dn,
of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end))
return -EINVAL;
+ if (irq_end < irq_start || irq_end - irq_start >= MST_INTC_MAX_IRQS)
+ return -EINVAL;
+
cd = kzalloc_obj(*cd);
if (!cd)
return -ENOMEM;
diff --git a/drivers/irqchip/irq-mtk-cirq.c b/drivers/irqchip/irq-mtk-cirq.c
index 914d1d639fe3..d30c34ce0f56 100644
--- a/drivers/irqchip/irq-mtk-cirq.c
+++ b/drivers/irqchip/irq-mtk-cirq.c
@@ -247,7 +247,7 @@ static int mtk_cirq_suspend(void *data)
writel_relaxed(mask, reg);
}
- /* set edge_only mode, record edge-triggerd interrupts */
+ /* set edge_only mode, record edge-triggered interrupts */
/* enable cirq */
reg = mtk_cirq_reg(cirq_data, CIRQ_CONTROL);
value = readl_relaxed(reg);
diff --git a/drivers/irqchip/irq-pruss-intc.c b/drivers/irqchip/irq-pruss-intc.c
index 81078d56f38d..5a3e9e5bccbe 100644
--- a/drivers/irqchip/irq-pruss-intc.c
+++ b/drivers/irqchip/irq-pruss-intc.c
@@ -181,7 +181,7 @@ static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq)
u8 ch, host, reg_idx;
u32 val;
- mutex_lock(&intc->lock);
+ guard(mutex)(&intc->lock);
intc->event_channel[hwirq].ref_count++;
@@ -206,8 +206,6 @@ static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq)
dev_dbg(dev, "mapped system_event = %lu channel = %d host = %d",
hwirq, ch, host);
-
- mutex_unlock(&intc->lock);
}
/**
@@ -224,7 +222,7 @@ static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq)
u8 ch, host, reg_idx;
u32 val;
- mutex_lock(&intc->lock);
+ guard(mutex)(&intc->lock);
ch = intc->event_channel[hwirq].value;
host = intc->channel_host[ch].value;
@@ -251,8 +249,6 @@ static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq)
dev_dbg(intc->dev, "unmapped system_event = %lu channel = %d host = %d\n",
hwirq, ch, host);
-
- mutex_unlock(&intc->lock);
}
static void pruss_intc_init(struct pruss_intc *intc)
@@ -376,17 +372,15 @@ static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event,
int channel, int host)
{
struct device *dev = intc->dev;
- int ret = 0;
- mutex_lock(&intc->lock);
+ guard(mutex)(&intc->lock);
/* check if sysevent already assigned */
if (intc->event_channel[event].ref_count > 0 &&
intc->event_channel[event].value != channel) {
dev_err(dev, "event %d (req. ch %d) already assigned to channel %d\n",
event, channel, intc->event_channel[event].value);
- ret = -EBUSY;
- goto unlock;
+ return -EBUSY;
}
/* check if channel already assigned */
@@ -394,16 +388,13 @@ static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event,
intc->channel_host[channel].value != host) {
dev_err(dev, "channel %d (req. host %d) already assigned to host %d\n",
channel, host, intc->channel_host[channel].value);
- ret = -EBUSY;
- goto unlock;
+ return -EBUSY;
}
intc->event_channel[event].value = channel;
intc->channel_host[channel].value = host;
-unlock:
- mutex_unlock(&intc->lock);
- return ret;
+ return 0;
}
static int
@@ -516,24 +507,21 @@ static const char * const irq_names[MAX_NUM_HOST_IRQS] = {
static int pruss_intc_probe(struct platform_device *pdev)
{
- const struct pruss_intc_match_data *data;
struct device *dev = &pdev->dev;
struct pruss_intc *intc;
struct pruss_host_irq_data *host_data;
int i, irq, ret;
u8 max_system_events, irqs_reserved = 0;
- data = of_device_get_match_data(dev);
- if (!data)
- return -ENODEV;
-
- max_system_events = data->num_system_events;
-
intc = devm_kzalloc(dev, sizeof(*intc), GFP_KERNEL);
if (!intc)
return -ENOMEM;
- intc->soc_config = data;
+ intc->soc_config = of_device_get_match_data(dev);
+ if (!intc->soc_config)
+ return -ENODEV;
+ max_system_events = intc->soc_config->num_system_events;
+
intc->dev = dev;
platform_set_drvdata(pdev, intc);
@@ -553,7 +541,9 @@ static int pruss_intc_probe(struct platform_device *pdev)
pruss_intc_init(intc);
- mutex_init(&intc->lock);
+ ret = devm_mutex_init(dev, &intc->lock);
+ if (ret)
+ return ret;
intc->domain = irq_domain_create_linear(dev_fwnode(dev), max_system_events,
&pruss_intc_irq_domain_ops, intc);
diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c
index 1a4703a353e9..485c3eea7632 100644
--- a/drivers/irqchip/irq-renesas-rzv2h.c
+++ b/drivers/irqchip/irq-renesas-rzv2h.c
@@ -420,9 +420,9 @@ static void rzv2h_clear_tint_int(struct rzv2h_icu_priv *priv, unsigned int hwirq
static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type)
{
u32 titsr, titsr_k, titsel_n, tien;
+ u32 titsr_cur, tssr_cur, tien_cur;
struct rzv2h_icu_priv *priv;
u32 tssr, tssr_k, tssel_n;
- u32 titsr_cur, tssr_cur;
unsigned int hwirq;
u32 tint, sense;
int tint_nr;
@@ -477,6 +477,8 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type)
if (tssr_cur == tint && titsr_cur == sense)
goto set_parent_type;
+ tien_cur = tssr & tien;
+
tssr &= ~(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width) | tien);
tssr |= ICU_TSSR_TSSEL_PREP(tint, tssel_n, priv->info->field_width);
@@ -489,7 +491,7 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type)
rzv2h_clear_tint_int(priv, hwirq);
- writel_relaxed(tssr | tien, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
+ writel_relaxed(tssr | tien_cur, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
}
set_parent_type:
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
diff --git a/drivers/irqchip/irq-riscv-aplic-main.c b/drivers/irqchip/irq-riscv-aplic-main.c
index d9afb6ae98cf..1676d64e6e4a 100644
--- a/drivers/irqchip/irq-riscv-aplic-main.c
+++ b/drivers/irqchip/irq-riscv-aplic-main.c
@@ -305,8 +305,8 @@ static void aplic_init_hw_irqs(struct aplic_priv *priv)
#ifdef CONFIG_ACPI
static const struct acpi_device_id aplic_acpi_match[] = {
- { "RSCV0002", 0 },
- {}
+ { .id = "RSCV0002" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, aplic_acpi_match);
diff --git a/drivers/irqchip/irq-riscv-imsic-early.c b/drivers/irqchip/irq-riscv-imsic-early.c
index 12efd241ce88..06726a062b75 100644
--- a/drivers/irqchip/irq-riscv-imsic-early.c
+++ b/drivers/irqchip/irq-riscv-imsic-early.c
@@ -67,15 +67,15 @@ static int __init imsic_ipi_domain_init(void)
return 0;
/* Create IMSIC IPI multiplexing */
- virq = ipi_mux_create(IMSIC_NR_IPI, imsic_ipi_send);
+ virq = ipi_mux_create(IPI_MAX, imsic_ipi_send);
if (virq <= 0)
return virq < 0 ? virq : -ENOMEM;
/* Set vIRQ range */
- riscv_ipi_set_virq_range(virq, IMSIC_NR_IPI);
+ riscv_ipi_set_virq_range(virq, IPI_MAX);
/* Announce that IMSIC is providing IPIs */
- pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->fwnode, IMSIC_IPI_ID);
+ pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->global.fwnode, IMSIC_IPI_ID);
return 0;
}
diff --git a/drivers/irqchip/irq-riscv-imsic-platform.c b/drivers/irqchip/irq-riscv-imsic-platform.c
index 643c8e459611..95ff76e63a69 100644
--- a/drivers/irqchip/irq-riscv-imsic-platform.c
+++ b/drivers/irqchip/irq-riscv-imsic-platform.c
@@ -313,40 +313,40 @@ int imsic_irqdomain_init(void)
};
struct imsic_global_config *global;
- if (!imsic || !imsic->fwnode) {
+ if (!imsic || !imsic->global.fwnode) {
pr_err("early driver not probed\n");
return -ENODEV;
}
if (imsic->base_domain) {
- pr_err("%pfwP: irq domain already created\n", imsic->fwnode);
+ pr_err("%pfwP: irq domain already created\n", imsic->global.fwnode);
return -ENODEV;
}
/* Create Base IRQ domain */
- info.fwnode = imsic->fwnode,
+ info.fwnode = imsic->global.fwnode,
imsic->base_domain = msi_create_parent_irq_domain(&info, &imsic_msi_parent_ops);
if (!imsic->base_domain) {
- pr_err("%pfwP: failed to create IMSIC base domain\n", imsic->fwnode);
+ pr_err("%pfwP: failed to create IMSIC base domain\n", imsic->global.fwnode);
return -ENOMEM;
}
global = &imsic->global;
pr_info("%pfwP: hart-index-bits: %d, guest-index-bits: %d\n",
- imsic->fwnode, global->hart_index_bits, global->guest_index_bits);
+ imsic->global.fwnode, global->hart_index_bits, global->guest_index_bits);
pr_info("%pfwP: group-index-bits: %d, group-index-shift: %d\n",
- imsic->fwnode, global->group_index_bits, global->group_index_shift);
+ imsic->global.fwnode, global->group_index_bits, global->group_index_shift);
pr_info("%pfwP: per-CPU IDs %d at base address %pa\n",
- imsic->fwnode, global->nr_ids, &global->base_addr);
+ imsic->global.fwnode, global->nr_ids, &global->base_addr);
pr_info("%pfwP: total %d interrupts available\n",
- imsic->fwnode, num_possible_cpus() * (global->nr_ids - 1));
+ imsic->global.fwnode, num_possible_cpus() * (global->nr_ids - 1));
return 0;
}
static int imsic_platform_probe_common(struct fwnode_handle *fwnode)
{
- if (imsic && imsic->fwnode != fwnode) {
+ if (imsic && imsic->global.fwnode != fwnode) {
pr_err("%pfwP: fwnode mismatch\n", fwnode);
return -ENODEV;
}
diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c
index b8d1bbbf42f7..01620373b43a 100644
--- a/drivers/irqchip/irq-riscv-imsic-state.c
+++ b/drivers/irqchip/irq-riscv-imsic-state.c
@@ -7,6 +7,7 @@
#define pr_fmt(fmt) "riscv-imsic: " fmt
#include <linux/acpi.h>
#include <linux/cpu.h>
+#include <linux/bits.h>
#include <linux/bitmap.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@@ -739,6 +740,14 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode,
return -EINVAL;
}
+ /* Group index bits must not overlap guest and HART index bits. */
+ i = IMSIC_MMIO_PAGE_SHIFT + global->guest_index_bits +
+ global->hart_index_bits;
+ if (global->group_index_bits && global->group_index_shift < i) {
+ pr_err("%pfwP: group index shift too small\n", fwnode);
+ return -EINVAL;
+ }
+
/* Sanity check group index shift */
i = global->group_index_bits + global->group_index_shift - 1;
if (i >= BITS_PER_LONG) {
@@ -769,9 +778,9 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode,
return -EINVAL;
}
global->base_addr = res.start;
- global->base_addr &= ~(BIT(global->guest_index_bits +
- global->hart_index_bits +
- IMSIC_MMIO_PAGE_SHIFT) - 1);
+ global->base_addr &= ~GENMASK(global->guest_index_bits +
+ global->hart_index_bits +
+ IMSIC_MMIO_PAGE_SHIFT - 1, 0);
global->base_addr &= ~((BIT(global->group_index_bits) - 1) <<
global->group_index_shift);
@@ -813,9 +822,9 @@ int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque)
imsic = kzalloc_obj(*imsic);
if (!imsic)
return -ENOMEM;
- imsic->fwnode = fwnode;
global = &imsic->global;
+ global->fwnode = fwnode;
global->local = alloc_percpu(typeof(*global->local));
if (!global->local) {
rc = -ENOMEM;
@@ -850,9 +859,9 @@ int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque)
}
base_addr = mmios[i].start;
- base_addr &= ~(BIT(global->guest_index_bits +
- global->hart_index_bits +
- IMSIC_MMIO_PAGE_SHIFT) - 1);
+ base_addr &= ~GENMASK(global->guest_index_bits +
+ global->hart_index_bits +
+ IMSIC_MMIO_PAGE_SHIFT - 1, 0);
base_addr &= ~((BIT(global->group_index_bits) - 1) <<
global->group_index_shift);
if (base_addr != global->base_addr) {
diff --git a/drivers/irqchip/irq-riscv-imsic-state.h b/drivers/irqchip/irq-riscv-imsic-state.h
index c42ee180b305..189829b8673a 100644
--- a/drivers/irqchip/irq-riscv-imsic-state.h
+++ b/drivers/irqchip/irq-riscv-imsic-state.h
@@ -13,7 +13,6 @@
#include <linux/timer.h>
#define IMSIC_IPI_ID 1
-#define IMSIC_NR_IPI 8
struct imsic_vector {
/* Fixed details of the vector */
@@ -44,9 +43,6 @@ struct imsic_local_priv {
};
struct imsic_priv {
- /* Device details */
- struct fwnode_handle *fwnode;
-
/* Global configuration common for all HARTs */
struct imsic_global_config global;
diff --git a/drivers/irqchip/irq-riscv-rpmi-sysmsi.c b/drivers/irqchip/irq-riscv-rpmi-sysmsi.c
index 612f3972f7af..bc80c38bb864 100644
--- a/drivers/irqchip/irq-riscv-rpmi-sysmsi.c
+++ b/drivers/irqchip/irq-riscv-rpmi-sysmsi.c
@@ -313,8 +313,8 @@ static const struct of_device_id rpmi_sysmsi_match[] = {
};
static const struct acpi_device_id acpi_rpmi_sysmsi_match[] = {
- { "RSCV0006" },
- {}
+ { .id = "RSCV0006" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, acpi_rpmi_sysmsi_match);
diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c
index 5b0dac104814..55d94c49f083 100644
--- a/drivers/irqchip/irq-sifive-plic.c
+++ b/drivers/irqchip/irq-sifive-plic.c
@@ -108,7 +108,7 @@ static int plic_irq_set_type(struct irq_data *d, unsigned int type);
static void __plic_toggle(struct plic_handler *handler, int hwirq, int enable)
{
u32 __iomem *base = handler->enable_base;
- u32 hwirq_mask = 1 << (hwirq % 32);
+ u32 hwirq_mask = BIT(hwirq % 32);
int group = hwirq / 32;
u32 value;
@@ -452,7 +452,7 @@ static irq_hw_number_t cp100_get_hwirq(struct plic_handler *handler, void __iome
return 0;
/*
- * Interrupts delievered to hardware still become pending, but only
+ * Interrupts delivered to hardware still become pending, but only
* interrupts that are both pending and enabled can be claimed.
* Clearing the enable bit for all interrupts but the first pending
* one avoids a hardware bug that occurs during read from the claim
@@ -538,8 +538,8 @@ static const struct of_device_id plic_match[] = {
#ifdef CONFIG_ACPI
static const struct acpi_device_id plic_acpi_match[] = {
- { "RSCV0001", 0 },
- {}
+ { .id = "RSCV0001" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, plic_acpi_match);
@@ -643,13 +643,11 @@ static int plic_probe(struct fwnode_handle *fwnode)
error = plic_parse_nr_irqs_and_contexts(fwnode, &nr_irqs, &nr_contexts, &gsi_base, &id);
if (error)
- goto fail_free_regs;
+ return error;
priv = kzalloc_obj(*priv);
- if (!priv) {
- error = -ENOMEM;
- goto fail_free_regs;
- }
+ if (!priv)
+ return -ENOMEM;
priv->fwnode = fwnode;
priv->plic_quirks = plic_quirks;
@@ -668,10 +666,8 @@ static int plic_probe(struct fwnode_handle *fwnode)
priv->acpi_plic_id = id;
priv->prio_save = bitmap_zalloc(nr_irqs, GFP_KERNEL);
- if (!priv->prio_save) {
- error = -ENOMEM;
- goto fail_free_priv;
- }
+ if (!priv->prio_save)
+ return -ENOMEM;
for (i = 0; i < nr_contexts; i++) {
error = plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu,
@@ -735,10 +731,8 @@ static int plic_probe(struct fwnode_handle *fwnode)
handler->enable_save = kcalloc(priv->irq_groups, sizeof(*handler->enable_save),
GFP_KERNEL);
- if (!handler->enable_save) {
- error = -ENOMEM;
- goto fail_cleanup_contexts;
- }
+ if (!handler->enable_save)
+ return -ENOMEM;
done:
for_each_device_irq(hwirq, priv) {
plic_toggle(handler, hwirq, 0);
@@ -748,10 +742,8 @@ static int plic_probe(struct fwnode_handle *fwnode)
}
priv->irqdomain = irq_domain_create_linear(fwnode, nr_irqs, &plic_irqdomain_ops, priv);
- if (WARN_ON(!priv->irqdomain)) {
- error = -ENOMEM;
- goto fail_cleanup_contexts;
- }
+ if (!priv->irqdomain)
+ return -ENOMEM;
/*
* We can have multiple PLIC instances so setup global state
@@ -799,33 +791,13 @@ static int plic_probe(struct fwnode_handle *fwnode)
pr_info("%pfwP: mapped %d interrupts with %d handlers for %d contexts.\n",
fwnode, nr_irqs, nr_handlers, nr_contexts);
return 0;
-
-fail_cleanup_contexts:
- for (i = 0; i < nr_contexts; i++) {
- if (plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu, priv->acpi_plic_id))
- continue;
- if (parent_hwirq != RV_IRQ_EXT || cpu < 0)
- continue;
-
- handler = per_cpu_ptr(&plic_handlers, cpu);
- handler->present = false;
- handler->hart_base = NULL;
- handler->enable_base = NULL;
- kfree(handler->enable_save);
- handler->enable_save = NULL;
- handler->priv = NULL;
- }
- bitmap_free(priv->prio_save);
-fail_free_priv:
- kfree(priv);
-fail_free_regs:
- iounmap(regs);
- return error;
}
static int plic_platform_probe(struct platform_device *pdev)
{
- return plic_probe(pdev->dev.fwnode);
+ if (plic_probe(pdev->dev.fwnode))
+ panic("%pfwP: Failed to initialize\n", pdev->dev.fwnode);
+ return 0;
}
static struct platform_driver plic_driver = {
@@ -842,7 +814,9 @@ builtin_platform_driver(plic_driver);
static int __init plic_early_probe(struct device_node *node,
struct device_node *parent)
{
- return plic_probe(&node->fwnode);
+ if (plic_probe(&node->fwnode))
+ panic("%pfwP: Failed to initialize\n", &node->fwnode);
+ return 0;
}
IRQCHIP_DECLARE(riscv, "allwinner,sun20i-d1-plic", plic_early_probe);
diff --git a/drivers/irqchip/irq-sni-exiu.c b/drivers/irqchip/irq-sni-exiu.c
index bfd2adedf8f3..108d7f2e59c4 100644
--- a/drivers/irqchip/irq-sni-exiu.c
+++ b/drivers/irqchip/irq-sni-exiu.c
@@ -307,8 +307,8 @@ static int exiu_acpi_probe(struct platform_device *pdev)
}
static const struct acpi_device_id exiu_acpi_ids[] = {
- { "SCX0008" },
- { /* sentinel */ }
+ { .id = "SCX0008" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids);
diff --git a/drivers/irqchip/irq-st.c b/drivers/irqchip/irq-st.c
index de71bb350d57..fef0521831aa 100644
--- a/drivers/irqchip/irq-st.c
+++ b/drivers/irqchip/irq-st.c
@@ -157,7 +157,7 @@ static int st_irq_syscfg_probe(struct platform_device *pdev)
return st_irq_syscfg_enable(pdev);
}
-static int __maybe_unused st_irq_syscfg_resume(struct device *dev)
+static int st_irq_syscfg_resume(struct device *dev)
{
struct st_irq_syscfg *ddata = dev_get_drvdata(dev);
@@ -165,12 +165,12 @@ static int __maybe_unused st_irq_syscfg_resume(struct device *dev)
ST_A9_IRQ_MASK, ddata->config);
}
-static SIMPLE_DEV_PM_OPS(st_irq_syscfg_pm_ops, NULL, st_irq_syscfg_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(st_irq_syscfg_pm_ops, NULL, st_irq_syscfg_resume);
static struct platform_driver st_irq_syscfg_driver = {
.driver = {
.name = "st_irq_syscfg",
- .pm = &st_irq_syscfg_pm_ops,
+ .pm = pm_sleep_ptr(&st_irq_syscfg_pm_ops),
.of_match_table = st_irq_syscfg_match,
},
.probe = st_irq_syscfg_probe,
diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c
index e38104c5064e..607e3284f700 100644
--- a/drivers/irqchip/irq-vic.c
+++ b/drivers/irqchip/irq-vic.c
@@ -478,7 +478,7 @@ static void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
/**
* vic_init() - initialise a vectored interrupt controller
* @base: iomem base address
- * @irq_start: starting interrupt number, must be muliple of 32
+ * @irq_start: starting interrupt number, must be multiple of 32
* @vic_sources: bitmask of interrupt sources to allow
* @resume_sources: bitmask of interrupt sources to allow for resume
*/
diff --git a/drivers/irqchip/irq-xilinx-smmu-csr.c b/drivers/irqchip/irq-xilinx-smmu-csr.c
new file mode 100644
index 000000000000..9bb625598107
--- /dev/null
+++ b/drivers/irqchip/irq-xilinx-smmu-csr.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx Versal Net SMMU CSR interrupt controller driver
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ *
+ * Vendor CSR block that gates the ARM SMMUv3 wired interrupts. Chains
+ * off the single GIC parent line and demultiplexes the CSR status into
+ * three children (eventq, gerror, priq), each enabled/acked
+ * via SMMU_CSR_IER/IDR/ISR that the ARM SMMUv3 driver requests individually.
+ */
+
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+/* Interrupt types */
+#define SMMU_INTR_EVENT BIT(0)
+#define SMMU_INTR_GLOBAL BIT(2)
+#define SMMU_INTR_PRI BIT(3)
+
+/* Mask for the SMMU interrupts this driver demultiplexes */
+#define SMMU_INTR_ALL (SMMU_INTR_EVENT | SMMU_INTR_GLOBAL | SMMU_INTR_PRI)
+
+/* Every interrupt source the CSR block implements (incl. unsupported CMD_SYNC) */
+#define SMMU_INTR_HW_ALL GENMASK(3, 0)
+
+#define SMMU_CSR_ISR 0x24 /* Interrupt Status (raw, W1C) */
+#define SMMU_CSR_MASK 0x28 /* Interrupt Mask (RO): 1 = masked */
+#define SMMU_CSR_IER 0x2c /* Interrupt Enable (W1 to unmask) */
+#define SMMU_CSR_IDR 0x30 /* Interrupt Disable (W1 to mask) */
+
+/**
+ * struct xilinx_smmu_csr - SMMU CSR interrupt controller context
+ * @base: MMIO base address of the CSR registers
+ * @domain: Interrupt domain for the child interrupts
+ * @parent_irq: parent (GIC) interrupt this block is chained to
+ * @lock: protects the SMMU_CSR_IER/IDR/ISR read and writes
+ */
+struct xilinx_smmu_csr {
+ void __iomem *base;
+ struct irq_domain *domain;
+ int parent_irq;
+ raw_spinlock_t lock;
+};
+
+enum xilinx_smmu_csr_irq {
+ SMMU_CSR_IRQ_EVENTQ = 0,
+ SMMU_CSR_IRQ_GERROR = 2,
+ SMMU_CSR_IRQ_PRIQ = 3,
+ SMMU_CSR_IRQ_NR,
+};
+
+static u32 xilinx_smmu_csr_hwirq_mask(irq_hw_number_t hwirq)
+{
+ if (hwirq >= SMMU_CSR_IRQ_NR)
+ return 0;
+
+ return BIT(hwirq) & SMMU_INTR_ALL;
+}
+
+static void xilinx_smmu_csr_irq_mask(struct irq_data *d)
+{
+ struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+ u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+ if (!mask)
+ return;
+
+ guard(raw_spinlock)(&csr->lock);
+ writel(mask, csr->base + SMMU_CSR_IDR);
+}
+
+static void xilinx_smmu_csr_irq_unmask(struct irq_data *d)
+{
+ struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+ u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+ if (!mask)
+ return;
+
+ guard(raw_spinlock)(&csr->lock);
+ writel(mask, csr->base + SMMU_CSR_IER);
+}
+
+static void xilinx_smmu_csr_irq_eoi(struct irq_data *d)
+{
+ struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+ u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+ if (!mask)
+ return;
+
+ guard(raw_spinlock)(&csr->lock);
+ writel(mask, csr->base + SMMU_CSR_ISR);
+}
+
+static struct irq_chip xilinx_smmu_csr_chip = {
+ .name = "xlnx-smmu-csr",
+ .irq_mask = xilinx_smmu_csr_irq_mask,
+ .irq_unmask = xilinx_smmu_csr_irq_unmask,
+ .irq_eoi = xilinx_smmu_csr_irq_eoi,
+ /* Defer sticky ISR clear until the threaded SMMU handler drains it. */
+ .flags = IRQCHIP_EOI_THREADED,
+};
+
+static void xilinx_smmu_csr_irq_handler(struct irq_desc *desc)
+{
+ struct xilinx_smmu_csr *csr = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ u32 status, mask, active, pending, spurious;
+
+ chained_irq_enter(chip, desc);
+
+ /*
+ * SMMU_CSR_ISR latches raw, pre-mask status, so gate it with the mask
+ * register to only act on sources that are actually enabled.
+ */
+ status = readl(csr->base + SMMU_CSR_ISR);
+ mask = readl(csr->base + SMMU_CSR_MASK);
+ active = status & ~mask;
+
+ /*
+ * Defend against any enabled source this driver does not demultiplex
+ * (e.g. CMD_SYNC). It would never be acked and would livelock the
+ * parent line, so mask and clear it here.
+ */
+ spurious = active & ~SMMU_INTR_ALL;
+ if (spurious) {
+ writel(spurious, csr->base + SMMU_CSR_IDR);
+ writel(spurious, csr->base + SMMU_CSR_ISR);
+ }
+
+ pending = active & SMMU_INTR_ALL;
+
+ while (pending) {
+ irq_hw_number_t hwirq = __ffs(pending);
+
+ if (generic_handle_domain_irq(csr->domain, hwirq)) {
+ /*
+ * No mapped consumer: mask and clear so an unexpected
+ * source cannot spin the parent line.
+ */
+ writel(BIT(hwirq), csr->base + SMMU_CSR_IDR);
+ writel(BIT(hwirq), csr->base + SMMU_CSR_ISR);
+ }
+
+ pending &= ~BIT(hwirq);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static int xilinx_smmu_csr_domain_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hwirq)
+{
+ struct xilinx_smmu_csr *csr = d->host_data;
+
+ if (!xilinx_smmu_csr_hwirq_mask(hwirq))
+ return -EINVAL;
+
+ irq_set_chip_and_handler(virq, &xilinx_smmu_csr_chip, handle_fasteoi_irq);
+ irq_set_chip_data(virq, csr);
+ irq_set_status_flags(virq, IRQ_LEVEL);
+
+ return 0;
+}
+
+static const struct irq_domain_ops xilinx_smmu_csr_domain_ops = {
+ .map = xilinx_smmu_csr_domain_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int __init xilinx_smmu_csr_init(struct device_node *node, struct device_node *parent)
+{
+ struct xilinx_smmu_csr *csr;
+ int ret;
+
+ if (WARN_ON_ONCE(!parent))
+ return -EINVAL;
+
+ if (irq_find_matching_fwnode(of_fwnode_handle(node), DOMAIN_BUS_ANY))
+ return -ENODEV;
+
+ csr = kzalloc(sizeof(*csr), GFP_KERNEL);
+ if (!csr)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&csr->lock);
+
+ csr->base = of_iomap(node, 0);
+ if (!csr->base) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
+ /*
+ * Start from a known state: mask and clear *every* source the block
+ * implements, including CMD_SYNC (bit 1) which this driver does not
+ * service. The CSR ORs (status & ~mask) onto a single parent line, so
+ * a source left unmasked and latched by a prior boot stage (firmware,
+ * kexec) would assert the line forever with no one to ack it. Masking
+ * the whole set here makes that impossible; the ARM SMMUv3 driver
+ * unmasks the sources it needs via the child irq_unmask() callbacks.
+ */
+ writel(SMMU_INTR_HW_ALL, csr->base + SMMU_CSR_IDR);
+ writel(SMMU_INTR_HW_ALL, csr->base + SMMU_CSR_ISR);
+
+ csr->domain = irq_domain_create_linear(of_fwnode_handle(node), SMMU_CSR_IRQ_NR,
+ &xilinx_smmu_csr_domain_ops, csr);
+ if (!csr->domain) {
+ pr_err("%pOF: failed to create irq domain\n", node);
+ ret = -ENOMEM;
+ goto unmap;
+ }
+
+ csr->parent_irq = irq_of_parse_and_map(node, 0);
+ if (!csr->parent_irq) {
+ pr_err("%pOF: failed to map parent irq\n", node);
+ ret = -EINVAL;
+ goto remove_domain;
+ }
+
+ irq_set_chained_handler_and_data(csr->parent_irq, xilinx_smmu_csr_irq_handler, csr);
+
+ pr_debug("%pOF: Xilinx SMMU CSR interrupt controller registered\n", node);
+
+ return 0;
+
+remove_domain:
+ irq_domain_remove(csr->domain);
+unmap:
+ iounmap(csr->base);
+free:
+ kfree(csr);
+ return ret;
+}
+
+IRQCHIP_DECLARE(xilinx_smmu_csr, "xlnx,versal-net-smmu-csr", xilinx_smmu_csr_init);
diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c
index 09819007d08e..6d83e1e4a7ea 100644
--- a/drivers/irqchip/qcom-irq-combiner.c
+++ b/drivers/irqchip/qcom-irq-combiner.c
@@ -262,7 +262,7 @@ static int combiner_probe(struct platform_device *pdev)
}
static const struct acpi_device_id qcom_irq_combiner_ids[] = {
- { "QCOM80B1", },
+ { .id = "QCOM80B1" },
{ }
};
diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c
index ce6d80c7f17a..29025a212ece 100644
--- a/drivers/irqchip/qcom-pdc.c
+++ b/drivers/irqchip/qcom-pdc.c
@@ -715,7 +715,10 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
}
pdc->x1e_quirk = true;
+ }
+ if (of_device_is_compatible(node, "qcom,x1e80100-pdc") ||
+ of_device_is_compatible(node, "qcom,x1p42100-pdc")) {
if (!qcom_scm_is_available())
return -EPROBE_DEFER;
diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c
index 7375f92f528b..fb3b92039547 100644
--- a/drivers/soc/fsl/qe/qe_ports_ic.c
+++ b/drivers/soc/fsl/qe/qe_ports_ic.c
@@ -140,7 +140,6 @@ static int qepic_probe(struct platform_device *pdev)
};
struct irq_domain_info d_info = {
.fwnode = of_fwnode_handle(pdev->dev.of_node),
- .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC,
.size = 32,
.hwirq_max = 32,
.ops = &irq_generic_chip_ops,
diff --git a/include/linux/irqchip/riscv-imsic.h b/include/linux/irqchip/riscv-imsic.h
index 61af3a5bea09..b7be1aad7774 100644
--- a/include/linux/irqchip/riscv-imsic.h
+++ b/include/linux/irqchip/riscv-imsic.h
@@ -46,6 +46,9 @@ struct imsic_local_config {
};
struct imsic_global_config {
+ /* Device details */
+ struct fwnode_handle *fwnode;
+
/*
* MSI Target Address Scheme
*
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
index 57c819da30c2..4fdcb6df5306 100644
--- a/kernel/irq/irqdomain.c
+++ b/kernel/irq/irqdomain.c
@@ -344,6 +344,7 @@ static struct irq_domain *__irq_domain_instantiate(const struct irq_domain_info
err = irq_domain_alloc_generic_chips(domain, info->dgc_info);
if (err)
goto err_domain_free;
+ domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC;
}
if (info->init) {
^ permalink raw reply [flat|nested] 12+ messages in thread* [GIT pull] timers/clocksource for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
2026-10-21 9:51 ` [GIT pull] irq/drivers " Thomas Gleixner
@ 2026-10-21 9:51 ` Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/core " Thomas Gleixner
` (3 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:51 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest timers/clocksource branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-clocksource-2026-10-20
up to: 5bbd49a96e04: Merge tag 'timers-v7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/daniel.lezcano/linux into timers/clocksource
Updates for clocksource and clockevent drivers:
- Support for the Renesas RZ/N1 timers
- Rework of the per CPU and per cluster MIPS GIC timers to improve
performance, sched clock precision and to add VDSO support.
- The usual assorted pile of small fixes and improvements all over the
place.
Thanks,
tglx
------------------>
Benoît Monin (2):
clocksource/drivers/mips-gic-timer: Simplify gic_next_event() for per-cpu timer
clocksource/drivers/mips-gic-timer: Use local counter on synced multi-cluster systems
Herve Codina (Schneider Electric) (3):
dt-bindings: timer: Add the Renesas RZ/N1 timer
clocksource/drivers: Add support for the Renesas RZ/N1 timers
MAINTAINERS: Add the Renesas RZ/N1 timers driver entry
Jiale Yao (1):
clocksource/drivers/timer-ti-dm: Handle runtime resume failure
Luca Leonardo Scorcia (3):
clocksource/drivers/timer-mediatek: Fix random crash at boot
clocksource/drivers/timer-mediatek: Convert to OF probing
clocksource/drivers/timer-mediatek: Request and enable the bus clock
Markus Schneider-Pargmann (TI) (1):
clocksource/drivers/timer-ti-dm: Register clocksource/event only for am654
Slavin Liu (1):
clocksource/drivers/dbx500-prcmu: Check timer register mapping
Wentao Liang (3):
clocksource/drivers/fttmr010: Fix clk reference leak in error paths
clocksource/drivers/digicolor: Fix clk reference leak on request_irq failure
clocksource/drivers/timer-econet-en751221: Fix clock reference leak
.../bindings/timer/renesas,rzn1-timer.yaml | 74 ++++
MAINTAINERS | 7 +
drivers/clocksource/Kconfig | 11 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/clksrc-dbx500-prcmu.c | 2 +
drivers/clocksource/mips-gic-timer.c | 138 +++++-
drivers/clocksource/timer-digicolor.c | 2 +
drivers/clocksource/timer-econet-en751221.c | 1 +
drivers/clocksource/timer-fttmr010.c | 2 +
drivers/clocksource/timer-mediatek.c | 47 +-
drivers/clocksource/timer-rzn1.c | 483 +++++++++++++++++++++
drivers/clocksource/timer-ti-dm.c | 10 +-
include/linux/platform_data/dmtimer-omap.h | 2 +
13 files changed, 753 insertions(+), 27 deletions(-)
create mode 100644 Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml
create mode 100644 drivers/clocksource/timer-rzn1.c
diff --git a/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml b/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml
new file mode 100644
index 000000000000..7218c0c2671d
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/timer/renesas,rzn1-timer.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas RZ/N1 timers
+
+maintainers:
+ - Herve Codina <herve.codina@bootlin.com>
+
+description: |
+ The Renesas RZ/N1 SoCs timers block controller is composed of 8 independent
+ timers.
+ - 6 are 16-bit timers
+ - 2 are 32-bit timers
+
+ Each timer has its own interrupt line and can work in either one-shot or
+ periodic mode.
+
+properties:
+ compatible:
+ items:
+ - const: renesas,r9a06g032-timer # RZ/N1D
+ - const: renesas,rzn1-timer
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: APB internal bus clock
+
+ clock-names:
+ items:
+ - const: pclk
+
+ power-domains:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 8
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - power-domains
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/r9a06g032-sysctrl.h>
+
+ timer@51001000 {
+ compatible = "renesas,r9a06g032-timer", "renesas,rzn1-timer";
+ reg = <0x51001000 0x200>;
+ clocks = <&sysctrl R9A06G032_HCLK_TIMER0>;
+ clock-names = "pclk";
+ power-domains = <&sysctrl>;
+ interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 72294ddfa5b7..7bdacc8feeed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23295,6 +23295,13 @@ S: Maintained
F: Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml
F: drivers/rtc/rtc-rzn1.c
+RENESAS RZ/N1 TIMERS DRIVER
+M: Herve Codina <herve.codina@bootlin.com>
+L: linux-renesas-soc@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml
+F: drivers/clocksource/timer-rzn1.c
+
RENESAS RZ/N1 USBF CONTROLLER DRIVER
M: Herve Codina <herve.codina@bootlin.com>
L: linux-renesas-soc@vger.kernel.org
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index d9c76dd443f8..da83483296d5 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -152,6 +152,17 @@ config REALTEK_OTTO_TIMER
RT8391, RTL8392, RTL8393 and RTL8396 and chips of the RTL930x series
such as RTL9301, RTL9302 or RTL9303.
+config RZN1_TIMER
+ bool "Renesas RZ/N1 Timer"
+ depends on HAS_IOMEM && COMMON_CLK
+ depends on ARCH_RZN1 || COMPILE_TEST
+ help
+ Enables support for RZ/N1 SoC timers.
+ A timers block in RZ/N1 SoCs is composed of 8 timers
+ - 6 16-bit timers
+ - 2 32-bit timers
+ Two timers blocks are available in RZ/N1 SoCs.
+
config SUN4I_TIMER
bool "Sun4i timer driver" if COMPILE_TEST
depends on HAS_IOMEM
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index b46376af6b49..464ec6690ab6 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -96,3 +96,4 @@ obj-$(CONFIG_EP93XX_TIMER) += timer-ep93xx.o
obj-$(CONFIG_RALINK_TIMER) += timer-ralink.o
obj-$(CONFIG_NXP_STM_TIMER) += timer-nxp-stm.o
obj-$(CONFIG_RTK_SYSTIMER) += timer-realtek.o
+obj-$(CONFIG_RZN1_TIMER) += timer-rzn1.o
diff --git a/drivers/clocksource/clksrc-dbx500-prcmu.c b/drivers/clocksource/clksrc-dbx500-prcmu.c
index 2fc93e46cea3..1af223b0e896 100644
--- a/drivers/clocksource/clksrc-dbx500-prcmu.c
+++ b/drivers/clocksource/clksrc-dbx500-prcmu.c
@@ -52,6 +52,8 @@ static struct clocksource clocksource_dbx500_prcmu = {
static int __init clksrc_dbx500_prcmu_init(struct device_node *node)
{
clksrc_dbx500_timer_base = of_iomap(node, 0);
+ if (!clksrc_dbx500_timer_base)
+ return -ENOMEM;
/*
* The A9 sub system expects the timer to be configured as
diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index 1501c7db9a8e..fc97b31f0adb 100644
--- a/drivers/clocksource/mips-gic-timer.c
+++ b/drivers/clocksource/mips-gic-timer.c
@@ -6,6 +6,7 @@
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/cpu.h>
+#include <linux/delay.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/notifier.h>
@@ -21,6 +22,9 @@ static int gic_timer_irq;
static unsigned int gic_frequency;
static unsigned int gic_count_width;
static bool __read_mostly gic_clock_unstable;
+static bool __read_mostly gic_use_local_counter = true;
+static unsigned long *gic_synced_cl_map;
+static struct work_struct gic_promote_work;
static void gic_clocksource_unstable(char *reason);
@@ -52,18 +56,13 @@ static u64 notrace gic_read_count(void)
static int gic_next_event(unsigned long delta, struct clock_event_device *evt)
{
- int cpu = cpumask_first(evt->cpumask);
u64 cnt;
int res;
cnt = gic_read_count();
cnt += (u64)delta;
- if (cpu == raw_smp_processor_id()) {
- write_gic_vl_compare(cnt);
- } else {
- write_gic_vl_other(mips_cm_vp_id(cpu));
- write_gic_vo_compare(cnt);
- }
+ write_gic_vl_compare(cnt);
+
res = ((int)(gic_read_count() - cnt) >= 0) ? -ETIME : 0;
return res;
}
@@ -82,6 +81,7 @@ static void gic_clockevent_cpu_init(unsigned int cpu,
{
cd->name = "MIPS GIC";
cd->features = CLOCK_EVT_FEAT_ONESHOT |
+ CLOCK_EVT_FEAT_PERCPU |
CLOCK_EVT_FEAT_C3STOP;
cd->rating = 350;
@@ -106,10 +106,71 @@ static void gic_update_frequency(void *data)
clockevents_update_freq(this_cpu_ptr(&gic_clockevent_device), rate);
}
+/* Number of iterations to synchronize the local GIC counter */
+#define GIC_SYNC_ITERATIONS 4
+
+/* Delay in us to check if the local GIC counter is still in sync with cluster 0 */
+#define GIC_SYNC_CHECK_DELAY 100
+
+static void gic_sync_counter_64(unsigned int cluster)
+{
+ unsigned int config = read_gic_config();
+ u64 t0, t1, t2;
+ s64 offset = 0;
+
+ mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+
+ for (int i = 0; i < GIC_SYNC_ITERATIONS; i++) {
+ write_gic_config(config | GIC_CONFIG_COUNTSTOP);
+ write_gic_counter(read_gic_redir_counter() + offset);
+ write_gic_config(config & ~GIC_CONFIG_COUNTSTOP);
+
+ t0 = read_gic_counter();
+ t1 = read_gic_redir_counter();
+ t2 = read_gic_counter();
+
+ if (time_in_range64(t1, t0, t2))
+ break;
+
+ /*
+ * Compute the offset to apply to the local counter
+ * so that (t1 - t0) equals (t2 - t1).
+ */
+ offset += (s64)(2 * t1 - t0 - t2) / 2;
+ }
+
+ mips_cm_unlock_other();
+
+ if (!time_in_range64(t1, t0, t2))
+ return;
+
+ udelay(GIC_SYNC_CHECK_DELAY);
+
+ mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+ t0 = read_gic_counter();
+ t1 = read_gic_redir_counter();
+ t2 = read_gic_counter();
+ mips_cm_unlock_other();
+
+ /* If so, mark the cluster as synchronized */
+ if (time_in_range64(t1, t0, t2) && gic_synced_cl_map)
+ bitmap_set(gic_synced_cl_map, cluster, 1);
+}
+
static int gic_starting_cpu(unsigned int cpu)
{
- /* Ensure the GIC counter is running */
- clear_gic_config(GIC_CONFIG_COUNTSTOP);
+ unsigned int cluster = cpu_cluster(&cpu_data[cpu]);
+
+ if (read_gic_config() & GIC_CONFIG_COUNTSTOP) {
+ clear_gic_config(GIC_CONFIG_COUNTSTOP);
+
+ if (cluster && mips_cm_is64 && !gic_clock_unstable)
+ gic_sync_counter_64(cluster);
+
+ if (gic_synced_cl_map &&
+ bitmap_full(gic_synced_cl_map, mips_cps_numclusters()))
+ schedule_work(&gic_promote_work);
+ }
gic_clockevent_cpu_init(cpu, this_cpu_ptr(&gic_clockevent_device));
return 0;
@@ -158,11 +219,6 @@ static int gic_clockevent_init(void)
return 0;
}
-static u64 gic_hpt_read(struct clocksource *cs)
-{
- return gic_read_count();
-}
-
static u64 gic_hpt_read_multicluster(struct clocksource *cs)
{
unsigned int hi, hi2, lo;
@@ -194,6 +250,14 @@ static u64 gic_hpt_read_multicluster(struct clocksource *cs)
return count;
}
+static u64 gic_hpt_read(struct clocksource *cs)
+{
+ if (unlikely(!READ_ONCE(gic_use_local_counter)))
+ return gic_hpt_read_multicluster(cs);
+
+ return gic_read_count();
+}
+
static struct clocksource gic_clocksource = {
.name = "GIC",
.read = gic_hpt_read,
@@ -213,8 +277,28 @@ static void gic_clocksource_unstable(char *reason)
clocksource_mark_unstable(&gic_clocksource);
}
+static void gic_clocksource_promote(struct work_struct *work)
+{
+ if (gic_clock_unstable || READ_ONCE(gic_use_local_counter))
+ return;
+
+ WRITE_ONCE(gic_use_local_counter, true);
+
+#ifdef CONFIG_GENERIC_GETTIMEOFDAY
+ gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_GIC;
+#endif
+
+ if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) {
+ sched_clock_register(mips_cm_is64 ?
+ gic_read_count_64 : gic_read_count_2x32,
+ gic_count_width, gic_frequency);
+ }
+}
+
static int __init __gic_clocksource_init(void)
{
+ unsigned int numclusters;
+ bool synced = false;
int ret;
/* Set clocksource mask. */
@@ -226,14 +310,32 @@ static int __init __gic_clocksource_init(void)
/* Calculate a somewhat reasonable rating value. */
if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ))
- gic_clocksource.rating = 300; /* Good when frequecy is stable */
+ gic_clocksource.rating = 300; /* Good when frequency is stable */
else
gic_clocksource.rating = 200;
gic_clocksource.rating += clamp(gic_frequency / 10000000, 0, 99);
- if (mips_cps_multicluster_cpus()) {
- gic_clocksource.read = &gic_hpt_read_multicluster;
+ numclusters = mips_cps_numclusters();
+ if (numclusters > 1)
+ gic_synced_cl_map = bitmap_zalloc(numclusters, GFP_KERNEL);
+
+ /*
+ * Mark cluster 0 as synchronized (with itself), and all clusters
+ * without cores since there is no local GIC counter access on those.
+ */
+ if (gic_synced_cl_map) {
+ bitmap_set(gic_synced_cl_map, 0, 1);
+ for (unsigned int cl = 0; cl < numclusters; cl++) {
+ if (!mips_cps_numcores(cl))
+ bitmap_set(gic_synced_cl_map, cl, 1);
+ }
+ synced = bitmap_full(gic_synced_cl_map, numclusters);
+ }
+
+ if (numclusters > 1 && !synced) {
+ WRITE_ONCE(gic_use_local_counter, false);
gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE;
+ INIT_WORK(&gic_promote_work, gic_clocksource_promote);
}
ret = clocksource_register_hz(&gic_clocksource, gic_frequency);
@@ -292,7 +394,7 @@ static int __init gic_clocksource_of_init(struct device_node *node)
* change performed by the CPC core clocks divider.
*/
if ((mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) &&
- !mips_cps_multicluster_cpus()) {
+ READ_ONCE(gic_use_local_counter)) {
sched_clock_register(mips_cm_is64 ?
gic_read_count_64 : gic_read_count_2x32,
gic_count_width, gic_frequency);
diff --git a/drivers/clocksource/timer-digicolor.c b/drivers/clocksource/timer-digicolor.c
index 559aa96089c3..46e0c329957a 100644
--- a/drivers/clocksource/timer-digicolor.c
+++ b/drivers/clocksource/timer-digicolor.c
@@ -190,6 +190,8 @@ static int __init digicolor_timer_init(struct device_node *node)
&dc_timer_dev.ce);
if (ret) {
pr_warn("request of timer irq %d failed (%d)\n", irq, ret);
+ clk_disable_unprepare(clk);
+ clk_put(clk);
return ret;
}
diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 4008076b1a21..1859335345b5 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -181,6 +181,7 @@ static int __init timer_init(struct device_node *np)
}
econet_timer.freq_hz = clk_get_rate(clk);
+ clk_put(clk);
for (int i = 0; i < num_blocks; i++) {
econet_timer.membase[i] = of_iomap(np, i);
diff --git a/drivers/clocksource/timer-fttmr010.c b/drivers/clocksource/timer-fttmr010.c
index 308f94bfbaed..c2553851e8d6 100644
--- a/drivers/clocksource/timer-fttmr010.c
+++ b/drivers/clocksource/timer-fttmr010.c
@@ -292,6 +292,7 @@ static int __init fttmr010_common_init(struct device_node *np,
ret = clk_prepare_enable(clk);
if (ret) {
pr_err("failed to enable PCLK\n");
+ clk_put(clk);
return ret;
}
@@ -432,6 +433,7 @@ static int __init fttmr010_common_init(struct device_node *np,
kfree(fttmr010);
out_disable_clock:
clk_disable_unprepare(clk);
+ clk_put(clk);
return ret;
}
diff --git a/drivers/clocksource/timer-mediatek.c b/drivers/clocksource/timer-mediatek.c
index 7bcb4a3f26fb..842d6ee4259e 100644
--- a/drivers/clocksource/timer-mediatek.c
+++ b/drivers/clocksource/timer-mediatek.c
@@ -9,10 +9,14 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
#include <linux/sched_clock.h>
#include <linux/slab.h>
#include "timer-of.h"
@@ -215,8 +219,7 @@ static irqreturn_t mtk_gpt_interrupt(int irq, void *dev_id)
return IRQ_HANDLED;
}
-static void
-__init mtk_gpt_setup(struct timer_of *to, u8 timer, u8 option)
+static void mtk_gpt_setup(struct timer_of *to, u8 timer, u8 option)
{
writel(GPT_CTRL_CLEAR | GPT_CTRL_DISABLE,
timer_of_base(to) + GPT_CTRL_REG(timer));
@@ -277,12 +280,13 @@ static struct timer_of to = {
},
.of_irq = {
- .flags = IRQF_TIMER | IRQF_IRQPOLL,
+ .flags = IRQF_TIMER | IRQF_IRQPOLL | IRQF_NO_AUTOEN,
},
};
-static int __init mtk_syst_init(struct device_node *node)
+static int mtk_syst_init(struct platform_device *pdev)
{
+ struct device_node *node = pdev->dev.of_node;
int ret;
to.clkevt.features = CLOCK_EVT_FEAT_DYNIRQ | CLOCK_EVT_FEAT_ONESHOT;
@@ -298,14 +302,19 @@ static int __init mtk_syst_init(struct device_node *node)
clockevents_config_and_register(&to.clkevt, timer_of_rate(&to),
TIMER_SYNC_TICKS, 0xffffffff);
+ enable_irq(timer_of_irq(&to));
return 0;
}
-static int __init mtk_gpt_init(struct device_node *node)
+static int mtk_gpt_init(struct platform_device *pdev)
{
+ struct device_node *node = pdev->dev.of_node;
+ struct clk *clk_bus;
int ret;
+ clk_bus = devm_clk_get_optional_enabled(&pdev->dev, "bus");
+
to.clkevt.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
to.clkevt.set_state_shutdown = mtk_gpt_clkevt_shutdown;
to.clkevt.set_state_periodic = mtk_gpt_clkevt_set_periodic;
@@ -334,8 +343,32 @@ static int __init mtk_gpt_init(struct device_node *node)
TIMER_SYNC_TICKS, 0xffffffff);
mtk_gpt_enable_irq(&to, TIMER_CLK_EVT);
+ enable_irq(timer_of_irq(&to));
return 0;
}
-TIMER_OF_DECLARE(mtk_mt6577, "mediatek,mt6577-timer", mtk_gpt_init);
-TIMER_OF_DECLARE(mtk_mt6765, "mediatek,mt6765-timer", mtk_syst_init);
+
+static int mtk_timer_probe(struct platform_device *pdev)
+{
+ int (*timer_init)(struct platform_device *pdev);
+
+ timer_init = of_device_get_match_data(&pdev->dev);
+ return timer_init(pdev);
+}
+
+static const struct of_device_id mtk_timer_match_table[] = {
+ { .compatible = "mediatek,mt6577-timer", .data = mtk_gpt_init },
+ { .compatible = "mediatek,mt6765-timer", .data = mtk_syst_init },
+ { /* sentinel */ }
+};
+
+static struct platform_driver mtk_timer_driver = {
+ .probe = mtk_timer_probe,
+ .driver = {
+ .name = "mediatek-timer",
+ .of_match_table = mtk_timer_match_table,
+ },
+};
+module_platform_driver(mtk_timer_driver);
+
+MODULE_DESCRIPTION("MediaTek Timer driver");
diff --git a/drivers/clocksource/timer-rzn1.c b/drivers/clocksource/timer-rzn1.c
new file mode 100644
index 000000000000..25ebebff3c63
--- /dev/null
+++ b/drivers/clocksource/timer-rzn1.c
@@ -0,0 +1,483 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RZ/N1 timers driver
+ *
+ * Copyright (C) 2026 Schneider-Electric
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/cpuhotplug.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/sched_clock.h>
+
+/* Per-timer register offsets */
+#define RZN1_TIMER_LOAD_COUNT_REG 0x00
+#define RZN1_TIMER_CURRENT_COUNT_REG 0x04
+#define RZN1_TIMER_CONTROL_REG 0x08
+#define RZN1_TIMER_CONTROL_INT_UNMASK BIT(3)
+#define RZN1_TIMER_CONTROL_ENABLE BIT(2)
+#define RZN1_TIMER_CONTROL_AUTORELOAD BIT(1)
+#define RZN1_TIMER_CONTROL_PRESCALER_DIV25 BIT(0)
+#define RZN1_TIMER_CLEAR_INT_REG 0x0c
+#define RZN1_TIMER_STATUS_INT0_REG 0x10
+#define RZN1_TIMER_STATUS_INT1_REG 0x14
+
+/*
+ * 8 timers are available. Among those 8 timers, the first 6 timers are 16-bit
+ * timers and the last two ones are 32-bit timers.
+ */
+#define RZN1_TIMER_BASE_INDEX_16BIT_TIMERS 0
+#define RZN1_TIMER_NB_16BIT_TIMERS 6
+#define RZN1_TIMER_LAST_INDEX_16BIT_TIMERS (RZN1_TIMER_BASE_INDEX_16BIT_TIMERS + \
+ RZN1_TIMER_NB_16BIT_TIMERS - 1)
+
+#define RZN1_TIMER_BASE_INDEX_32BIT_TIMERS 6
+#define RZN1_TIMER_NB_32BIT_TIMERS 2
+#define RZN1_TIMER_LAST_INDEX_32BIT_TIMERS (RZN1_TIMER_BASE_INDEX_32BIT_TIMERS + \
+ RZN1_TIMER_NB_32BIT_TIMERS - 1)
+
+#define RZN1_TIMER_IS_16BIT_TIMER(_i) ((_i) < RZN1_TIMER_BASE_INDEX_32BIT_TIMERS)
+
+/* Total numbers of timers */
+#define RZN1_TIMER_NB_TIMERS (RZN1_TIMER_NB_16BIT_TIMERS + \
+ RZN1_TIMER_NB_32BIT_TIMERS)
+
+/* Offset of registers related to the timer number _i in the timers regs area */
+#define RZN1_TIMER_REGS_OFFSET(_i) (0x20 * (_i))
+
+struct rzn1_timer {
+ void __iomem *base;
+ unsigned int width;
+ unsigned long rate;
+ unsigned long hz_period;
+ struct clock_event_device ced;
+};
+
+static int rzn1_timer_config(struct rzn1_timer *timer, u32 period, bool is_periodic)
+{
+ u32 ctrl;
+
+ ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG);
+
+ /* Disable the timer */
+ writel(ctrl & ~RZN1_TIMER_CONTROL_ENABLE,
+ timer->base + RZN1_TIMER_CONTROL_REG);
+
+ /* Set the new period */
+ writel(period, timer->base + RZN1_TIMER_LOAD_COUNT_REG);
+
+ /* Prepare the timer mode */
+ if (is_periodic)
+ ctrl |= RZN1_TIMER_CONTROL_AUTORELOAD;
+ else
+ ctrl &= ~RZN1_TIMER_CONTROL_AUTORELOAD;
+
+ /*
+ * Enable the timer.
+ *
+ * This automatically resets the CURRENT_COUNT register.
+ */
+ ctrl |= RZN1_TIMER_CONTROL_ENABLE;
+ writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG);
+
+ return 0;
+}
+
+static void rzn1_timer_disable(struct rzn1_timer *timer)
+{
+ u32 ctrl;
+
+ ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG);
+ ctrl &= ~RZN1_TIMER_CONTROL_ENABLE;
+ writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG);
+}
+
+static void rzn1_timer_int_enable(struct rzn1_timer *timer)
+{
+ u32 ctrl;
+
+ ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG);
+ ctrl |= RZN1_TIMER_CONTROL_INT_UNMASK;
+ writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG);
+}
+
+static irqreturn_t rzn1_timer_interrupt(int irq, void *dev_id)
+{
+ struct rzn1_timer *timer = dev_id;
+
+ /* Ack the interrupt */
+ readl(timer->base + RZN1_TIMER_CLEAR_INT_REG);
+
+ if (timer->ced.event_handler)
+ timer->ced.event_handler(&timer->ced);
+
+ return IRQ_HANDLED;
+}
+
+static int rzn1_clkevt_set_next_event(unsigned long cycles,
+ struct clock_event_device *evt)
+{
+ struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced);
+
+ return rzn1_timer_config(timer, cycles, false);
+}
+
+static int rzn1_clkevt_shutdown(struct clock_event_device *evt)
+{
+ struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced);
+
+ rzn1_timer_disable(timer);
+ return 0;
+}
+
+static int rzn1_clkevt_set_periodic(struct clock_event_device *evt)
+{
+ struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced);
+
+ rzn1_timer_config(timer, timer->hz_period, true);
+ return 0;
+}
+
+/*
+ * This global lock is used to prevent race conditions during global timers
+ * test and set operation in case the driver is using the async probe option.
+ */
+static DEFINE_MUTEX(rzn1_global_timers_lock);
+
+static int rzn1_timer_instance;
+
+/* scheduler and clocksource */
+static struct rzn1_timer *rzn1_sched_clock;
+
+static u64 notrace rzn1_sched_read(void)
+{
+ return readl_relaxed(rzn1_sched_clock->base + RZN1_TIMER_CURRENT_COUNT_REG);
+}
+
+static u64 rzn1_clksrc_read(struct clocksource *cs)
+{
+ return readl_relaxed(rzn1_sched_clock->base + RZN1_TIMER_CURRENT_COUNT_REG);
+}
+
+static struct clocksource rzn1_clocksource = {
+ .name = "rzn1_timer_clocksource",
+ .rating = 200,
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+ .read = rzn1_clksrc_read,
+};
+
+/* Timers table where 16-bit timers will be assigned per-cpu */
+static struct rzn1_timer *rzn1_tab_timers;
+
+static int rzn1_local_timer_starting_cpu(unsigned int cpu)
+{
+ struct rzn1_timer *timer;
+
+ /* We assign the first and second 16-bit timer as per-cpu timers. */
+ if (cpu >= 2)
+ return 0;
+
+ timer = &rzn1_tab_timers[cpu + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS];
+
+ timer->ced.cpumask = cpumask_of(cpu);
+ irq_force_affinity(timer->ced.irq, cpumask_of(cpu));
+
+ clockevents_config_and_register(&timer->ced, timer->rate, 1,
+ GENMASK(timer->width - 1, 0));
+ /*
+ * Time to enable the interrupt even if no interrupt can be fired yet.
+ * Indeed, the timer is still globally disabled.
+ * It will be enabled when rzn1_timer_config() is called
+ */
+ enable_irq(timer->ced.irq);
+ return 0;
+}
+
+static int rzn1_local_timer_dying_cpu(unsigned int cpu)
+{
+ struct rzn1_timer *timer;
+
+ if (cpu >= 2)
+ return 0;
+
+ timer = &rzn1_tab_timers[cpu + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS];
+
+ rzn1_timer_disable(timer);
+ disable_irq_nosync(timer->ced.irq);
+ return 0;
+}
+
+static void rzn1_timer_init(struct rzn1_timer *timer, unsigned int index,
+ void __iomem *timers_base, unsigned long clock_rate)
+{
+ timer->base = timers_base + RZN1_TIMER_REGS_OFFSET(index);
+
+ /* Disable the timer */
+ writel(0, timer->base + RZN1_TIMER_CONTROL_REG);
+
+ /* Clear potential interrupts */
+ readl(timer->base + RZN1_TIMER_CLEAR_INT_REG);
+
+ if (RZN1_TIMER_IS_16BIT_TIMER(index)) {
+ timer->width = 16;
+ /*
+ * Enable pre-scaler for 16-bit timers.
+ *
+ * Timers are fed by a 25MHz clock. With this pre-scaler (1/25)
+ * enabled, the 16-bit timers resolution is 1 microsecond.
+ */
+ writel(RZN1_TIMER_CONTROL_PRESCALER_DIV25,
+ timer->base + RZN1_TIMER_CONTROL_REG);
+ timer->rate = clock_rate / 25;
+ } else {
+ timer->width = 32;
+ timer->rate = clock_rate;
+ }
+ timer->hz_period = DIV_ROUND_UP(timer->rate, HZ);
+}
+
+static void rzn1_timer_clkevt_init_ced(struct rzn1_timer *timer, const char *name,
+ int irq)
+{
+ timer->ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ timer->ced.set_next_event = rzn1_clkevt_set_next_event;
+ timer->ced.set_state_shutdown = rzn1_clkevt_shutdown;
+ timer->ced.set_state_periodic = rzn1_clkevt_set_periodic;
+ timer->ced.cpumask = cpu_possible_mask;
+
+ /*
+ * 32-bit timers are fed with a 25 MHz clock directly derived from the
+ * main PLL (1 GHz). The arch timer, ARMv7 CP15 in RZ/N1 SoCs, is fed
+ * with to a 6.25 MHz clock.
+ *
+ * The clock of the RZ/N1 timers block is a stable one and the RZ/N1
+ * timers have a higher resolution than the ARM CP15 one.
+ *
+ * Use a high rating for the 32-bit RZ/N1 timers.
+ */
+ timer->ced.rating = timer->width == 16 ? 200 : 460;
+ timer->ced.owner = THIS_MODULE;
+ timer->ced.name = name;
+ timer->ced.irq = irq;
+}
+
+static int rzn1_timer_setup_timers(struct platform_device *pdev,
+ struct rzn1_timer *tab_timers,
+ unsigned int first_timer_index,
+ unsigned int last_timer_index,
+ void __iomem *base, unsigned long clock_rate)
+{
+ struct device *dev = &pdev->dev;
+ struct rzn1_timer *timer;
+ unsigned int i;
+ char *name;
+ int irq;
+ int ret;
+
+ for (i = first_timer_index; i <= last_timer_index; i++) {
+ timer = &tab_timers[i];
+
+ rzn1_timer_init(timer, i, base, clock_rate);
+
+ irq = platform_get_irq(pdev, i);
+ if (irq < 0)
+ return irq;
+
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s-%u", dev_name(dev), i);
+ if (!name)
+ return -ENOMEM;
+
+ rzn1_timer_clkevt_init_ced(timer, name, irq);
+
+ ret = devm_request_irq(dev, timer->ced.irq, rzn1_timer_interrupt,
+ IRQF_TIMER | IRQF_NO_AUTOEN,
+ timer->ced.name, timer);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "timer%u: Failed to request IRQ\n", i);
+
+ rzn1_timer_int_enable(timer);
+ }
+ return 0;
+}
+
+static void rzn1_timer_register_clkevt(struct rzn1_timer *tab_timers,
+ unsigned int first_timer_index,
+ unsigned int last_timer_index)
+{
+ struct rzn1_timer *timer;
+ unsigned int i;
+
+ for (i = first_timer_index; i <= last_timer_index; i++) {
+ timer = &tab_timers[i];
+ clockevents_config_and_register(&timer->ced, timer->rate,
+ 1, GENMASK(timer->width - 1, 0));
+ /*
+ * Time to enable the interrupt even if no interrupt can be
+ * fired yet. Indeed, the timer is still globally disabled.
+ * It will be enabled when rzn1_timer_config() is called
+ */
+ enable_irq(timer->ced.irq);
+ }
+}
+
+static int rzn1_timer_probe_first(struct platform_device *pdev,
+ struct rzn1_timer *tab_timers,
+ void __iomem *base, unsigned long clock_rate)
+{
+ struct device *dev = &pdev->dev;
+ struct rzn1_timer *timer;
+ int ret;
+
+ /*
+ * Each timer instance has 6 x 16-bit timers and 2 x 32-bit timers.
+ *
+ * Probe the first instance. In that case, timers are assigned as
+ * follow:
+ * - First and second 16-bit timers: clock events per CPU
+ * - Last (second one) 32-bit timer: clocksource and sched_clock
+ * - Other timers: clock events for all possible CPUs
+ *
+ * First step, perform all operation that could fail without calling
+ * clockevents_config_and_register(), sched_clock_register() nor
+ * cpuhp_setup_state(). Those operation don't have unregister nor
+ * teardown counterparts and so, once called, we cannot remove the
+ * related resource.
+ */
+
+ /* First step for 16-bit timers and the first 32-bit timers. */
+ ret = rzn1_timer_setup_timers(pdev, tab_timers,
+ RZN1_TIMER_BASE_INDEX_16BIT_TIMERS,
+ RZN1_TIMER_BASE_INDEX_32BIT_TIMERS,
+ base, clock_rate);
+ if (ret)
+ return ret;
+
+ /*
+ * Second step, almost all operations that can fail have been called.
+ * Timers are ready to work. Start with the last operation that can fail,
+ * installing and invoking hotplug callbacks
+ */
+ rzn1_tab_timers = tab_timers;
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+ "clockevents/rzn1/global_timer:starting",
+ rzn1_local_timer_starting_cpu,
+ rzn1_local_timer_dying_cpu);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Setup CPU hotplug state failed\n");
+
+ /*
+ * At this point, no more operations can fail. Perform remaining
+ * operations. Starting by handling the last (second one) 32-bit timer
+ */
+ timer = &tab_timers[RZN1_TIMER_LAST_INDEX_32BIT_TIMERS];
+
+ rzn1_timer_init(timer, RZN1_TIMER_LAST_INDEX_32BIT_TIMERS, base, clock_rate);
+ rzn1_timer_config(timer, GENMASK(timer->width - 1, 0), true);
+
+ rzn1_sched_clock = timer;
+
+ sched_clock_register(rzn1_sched_read, rzn1_sched_clock->width, rzn1_sched_clock->rate);
+
+ rzn1_clocksource.mask = CLOCKSOURCE_MASK(rzn1_sched_clock->width);
+ clocksource_register_hz(&rzn1_clocksource, rzn1_sched_clock->rate);
+
+ /*
+ * Register clockevents for 16-bit timers (except the first and second
+ * ones) and for the first 32-bit timer. The first and second 16-bit
+ * timers clockevents are registered by CPU hotplug startup function set
+ * previously by the cpuhp_setup_state() call.
+ */
+ rzn1_timer_register_clkevt(tab_timers,
+ RZN1_TIMER_BASE_INDEX_16BIT_TIMERS + 2,
+ RZN1_TIMER_BASE_INDEX_32BIT_TIMERS);
+
+ return 0;
+}
+
+static int rzn1_timer_probe_other(struct platform_device *pdev,
+ struct rzn1_timer *tab_timers,
+ void __iomem *base, unsigned long clock_rate)
+{
+ int ret;
+
+ /*
+ * Probe other instance(s), i.e. not the first one. In that case,
+ * all timers are used as clock events and available for all possible
+ * CPUs
+ *
+ * First step, perform all operation that could fail without calling
+ * clockevents_config_and_register(). Unregister counterpart does not
+ * exist and so, once called, we cannot remove the related resource.
+ */
+ ret = rzn1_timer_setup_timers(pdev, tab_timers,
+ RZN1_TIMER_BASE_INDEX_16BIT_TIMERS,
+ RZN1_TIMER_LAST_INDEX_32BIT_TIMERS,
+ base, clock_rate);
+ if (ret)
+ return ret;
+
+ /*
+ * Second step, all operation that can fail have been called. We can
+ * register our timers.
+ */
+ rzn1_timer_register_clkevt(tab_timers,
+ RZN1_TIMER_BASE_INDEX_16BIT_TIMERS,
+ RZN1_TIMER_LAST_INDEX_32BIT_TIMERS);
+ return 0;
+}
+
+static int rzn1_timer_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rzn1_timer *tab_timers;
+ unsigned long clock_rate;
+ void __iomem *base;
+ struct clk *clk;
+ int ret;
+
+ tab_timers = devm_kcalloc(dev, RZN1_TIMER_NB_TIMERS, sizeof(*tab_timers),
+ GFP_KERNEL);
+ if (!tab_timers)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ clk = devm_clk_get_enabled(dev, "pclk");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get pclk\n");
+
+ clock_rate = clk_get_rate(clk);
+
+ scoped_guard(mutex, &rzn1_global_timers_lock) {
+ ret = !rzn1_timer_instance ?
+ rzn1_timer_probe_first(pdev, tab_timers, base, clock_rate) :
+ rzn1_timer_probe_other(pdev, tab_timers, base, clock_rate);
+ if (!ret)
+ rzn1_timer_instance++;
+ }
+
+ return ret;
+}
+
+static const struct of_device_id rzn1_timer_of_match[] = {
+ { .compatible = "renesas,rzn1-timer", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver rzn1_timer_driver = {
+ .driver = {
+ .name = "rzn1_timer",
+ .of_match_table = rzn1_timer_of_match,
+ .suppress_bind_attrs = true,
+ },
+};
+builtin_platform_driver_probe(rzn1_timer_driver, rzn1_timer_probe);
diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c
index 6787acac9a43..8012d3f3e1a3 100644
--- a/drivers/clocksource/timer-ti-dm.c
+++ b/drivers/clocksource/timer-ti-dm.c
@@ -1095,6 +1095,7 @@ static int omap_dm_timer_write_counter(struct omap_dm_timer *cookie, unsigned in
{
struct dmtimer *timer;
struct device *dev;
+ int ret;
timer = to_dmtimer(cookie);
if (unlikely(!timer)) {
@@ -1104,7 +1105,10 @@ static int omap_dm_timer_write_counter(struct omap_dm_timer *cookie, unsigned in
dev = &timer->pdev->dev;
- pm_runtime_resume_and_get(dev);
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, value);
pm_runtime_put_sync(dev);
@@ -1475,7 +1479,8 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
timer->pdev = pdev;
- if (timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) {
+ if (pdata->system_timer_capable &&
+ timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) {
if (!omap_dm_timer_sched_clock_counter) {
ret = omap_dm_timer_setup_clocksource(timer);
if (ret)
@@ -1585,6 +1590,7 @@ static const struct dmtimer_platform_data omap3plus_pdata = {
static const struct dmtimer_platform_data am6_pdata = {
.timer_ops = &dmtimer_ops,
+ .system_timer_capable = true,
};
static const struct of_device_id omap_timer_match[] = {
diff --git a/include/linux/platform_data/dmtimer-omap.h b/include/linux/platform_data/dmtimer-omap.h
index 726d89143842..b78ee4676b98 100644
--- a/include/linux/platform_data/dmtimer-omap.h
+++ b/include/linux/platform_data/dmtimer-omap.h
@@ -57,6 +57,8 @@ struct dmtimer_platform_data {
u32 timer_errata;
int (*get_context_loss_count)(struct device *);
const struct omap_dm_timer_ops *timer_ops;
+ /* always-on timers can be used for clocksource/clockevent */
+ bool system_timer_capable;
};
#endif /* __PLATFORM_DATA_DMTIMER_OMAP_H__ */
^ permalink raw reply [flat|nested] 12+ messages in thread* [GIT pull] timers/core for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
2026-10-21 9:51 ` [GIT pull] irq/drivers " Thomas Gleixner
2026-10-21 9:51 ` [GIT pull] timers/clocksource " Thomas Gleixner
@ 2026-10-21 9:51 ` Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/nohz " Thomas Gleixner
` (2 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:51 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest timers/core branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-core-2026-10-20
up to: 348f54c435bf: selftests/timers: clocksource-switch: Fix unchecked open()/read()
Updates for the core timekeeping and timers subsystem:
- Futher cleanups for the POSIX timer exit/exec related code to simplify
the code flow and to make it more robust against future changes.
- Prevent tearing of timekeeper::ktime_sec which can be accessed without the
sequence count protection.
The timekeeper is updated by memcpy() with an uneven sequence count to
force readers into the retry path. On 64-bit machine the ktime_sec
member can be accessed atomically without the sequence count
protection. That is only correct when the update side memcpy()
guarantees atomicity at the 64-bit granularity. That's not guaranteed
due to debug options, compilers and instruction availability. If the
memcpy() does not provide the atomicity then the unprotected readers
can observe store tearing.
To prevent this write the ktime_sec member before the memcpy() so that
a non-atomic memcpy() will write the same data. A similar fix has been
done for the xtime_sec member, but the ktime_sec equivalent was missed.
- Prevent time64_to_tm() day truncation on 32-bit kernels
The function receives a 64-bit seconds value but stores the quotient of
the computation in a long, which truncates the result for large input
values. Cure this by using div_s64_rem() which stores the quotient in
s64 and only leaves the seconds-within-day reminder in a long, which is
guaranteed to fit. This fix comes with a corresponding kunit test to
prevent further mishaps.
- Mark data-racy accesses to the hrtimer_sleeper::task member.
The recent updates to KCSAN flagged these benign races. Annotate them
with new helper functions, convert all existing usage sites and mark
the task member private after that so that future users which fail to
use the helpers can be detected automatically.
- Address similar benign KCSAN flagged issues in the hrtimer, timer and
timer migration code.
- Fixes for a few selftest issues
Thanks,
tglx
------------------>
Eva Kurchatova (2):
selftests: timers: Count what tick is worth in the drift estimate
selftests: timers: Measure the CPU timers on the clock they count
Karl Mehltretter (2):
time: Prevent time64_to_tm() day truncation on 32-bit
time/kunit: Add time64_to_tm() case beyond 32-bit day count
Paul E. McKenney (14):
hrtimer: Mark data-racy accesses to hrtimer_sleeper ->task field
aio: Use accessor for hrtimer_sleeper ->task field
wait: Use accessor for hrtimer_sleeper ->task field
io-uring/rw: Use accessor for hrtimer_sleeper ->task field
futex: Use accessor for hrtimer_sleeper ->task field in waitwake.c
timers: Use accessor for hrtimer_sleeper ->task field in sleep_timeout.c
net: pktgen: Use accessor for hrtimer_sleeper ->task field
rtmutex: Use accessor for hrtimer_sleeper ->task field
futex: Use accessor for hrtimer_sleeper ->task field in requeue
hrtimer: Update hrtimer_resolution only if value changes
hrtimer: Mark the hrtimer_sleeper structure's ->task field __private
hrtimer: Apply READ_ONCE() to lockless base->running loads
timers: Mark racy updates to hlist_node::pprev field
timers/migration: Mark racy updates to tmigr_event::ignore field
Qingshuang Fu (1):
selftests/timers: clocksource-switch: Fix unchecked open()/read()
Thomas Gleixner (6):
posix-timers: Move posixtimer_exec_cleanup() out of exec.c
posix-timers: Move POSIX timer group exit related code out of do_exit()
posix-cpu-timers: Move inlines out of public header
posix-cpu-timers: Use PF_EXITING to indicate exit
posix-cpu-timers: Prevent enqueueing when PF_EXITING is set
posix-timers: Handle exit in do_exit() completely
Thomas Weißschuh (1):
timekeeping: Use READ_ONCE/WRITE_ONCE() for ktime_sec to prevent tearing
fs/aio.c | 2 +-
fs/exec.c | 13 +--
include/linux/hrtimer.h | 10 +-
include/linux/list.h | 6 +-
include/linux/posix-timers.h | 39 +-------
include/linux/sched/task.h | 1 -
include/linux/wait.h | 2 +-
io_uring/rw.c | 2 +-
kernel/exit.c | 13 +--
kernel/futex/requeue.c | 2 +-
kernel/futex/waitwake.c | 8 +-
kernel/locking/rtmutex.c | 2 +-
kernel/time/hrtimer.c | 23 ++---
kernel/time/posix-cpu-timers.c | 103 +++++++++++++++++++--
kernel/time/posix-timers.c | 26 ++++--
kernel/time/posix-timers.h | 3 +
kernel/time/sleep_timeout.c | 4 +-
kernel/time/time_test.c | 16 ++++
kernel/time/timeconv.c | 6 +-
kernel/time/timekeeping.c | 4 +-
kernel/time/timer.c | 2 +-
kernel/time/timer_migration.c | 6 +-
net/core/pktgen.c | 4 +-
.../testing/selftests/timers/clocksource-switch.c | 23 ++++-
tools/testing/selftests/timers/posix_timers.c | 7 +-
tools/testing/selftests/timers/raw_skew.c | 12 +++
26 files changed, 227 insertions(+), 112 deletions(-)
diff --git a/fs/aio.c b/fs/aio.c
index d78acc69f487..8130d9e637ea 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -1402,7 +1402,7 @@ static long read_events(struct kioctx *ctx, long min_nr, long nr,
w.min_nr = min_nr - ret;
ret2 = prepare_to_wait_event(&ctx->wait, &w.w, TASK_INTERRUPTIBLE);
- if (!ret2 && !t.task)
+ if (!ret2 && !hrtimer_sleeper_task_get(&t))
ret2 = -ETIME;
if (aio_read_events(ctx, min_nr, nr, event, &ret) || ret2)
diff --git a/fs/exec.c b/fs/exec.c
index 819643408e6d..1fc567155f34 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1115,17 +1115,6 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm)
return bprm->file;
}
-static void posixtimer_exec(struct task_struct *me)
-{
-#ifdef CONFIG_POSIX_TIMERS
- spin_lock_irq(&me->sighand->siglock);
- posix_cpu_timers_exit(me);
- spin_unlock_irq(&me->sighand->siglock);
- exit_itimers(me);
- flush_itimer_signals();
-#endif
-}
-
/*
* Calling this is the point of no return. None of the failures will be
* seen by userspace since either the process is already taking a fatal
@@ -1171,7 +1160,7 @@ int begin_new_exec(struct linux_binprm * bprm)
* timer would not remove an enqueued timer because the TID lookup
* of the old TID fails.
*/
- posixtimer_exec(me);
+ posixtimer_exec();
/* see the comment in check_unsafe_exec() */
current->fs->in_exec = 0;
diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h
index 29072d89e5cb..cad8482337cb 100644
--- a/include/linux/hrtimer.h
+++ b/include/linux/hrtimer.h
@@ -72,7 +72,7 @@ enum hrtimer_mode {
*/
struct hrtimer_sleeper {
struct hrtimer timer;
- struct task_struct *task;
+ struct task_struct *__private task;
};
static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time)
@@ -320,6 +320,14 @@ extern int schedule_hrtimeout_range_clock(ktime_t *expires,
const enum hrtimer_mode mode,
clockid_t clock_id);
extern int schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode);
+static inline struct task_struct *hrtimer_sleeper_task_get(struct hrtimer_sleeper *sl)
+{
+ return READ_ONCE(ACCESS_PRIVATE(sl, task));
+}
+static inline void hrtimer_sleeper_task_set(struct hrtimer_sleeper *sl, struct task_struct *t)
+{
+ WRITE_ONCE(ACCESS_PRIVATE(sl, task), t);
+}
/* Soft interrupt function to run the hrtimer queues: */
extern void hrtimer_run_queues(void);
diff --git a/include/linux/list.h b/include/linux/list.h
index 77fb62f79928..e3753695e76c 100644
--- a/include/linux/list.h
+++ b/include/linux/list.h
@@ -1172,7 +1172,7 @@ static inline void hlist_move_list(struct hlist_head *old,
{
new->first = old->first;
if (new->first)
- new->first->pprev = &new->first;
+ WRITE_ONCE(new->first->pprev, &new->first);
old->first = NULL;
}
@@ -1189,10 +1189,10 @@ static inline void hlist_splice_init(struct hlist_head *from,
struct hlist_head *to)
{
if (to->first)
- to->first->pprev = &last->next;
+ WRITE_ONCE(to->first->pprev, &last->next);
last->next = to->first;
to->first = from->first;
- from->first->pprev = &to->first;
+ WRITE_ONCE(from->first->pprev, &to->first);
from->first = NULL;
}
diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index 9a1a0c61361c..00767acbc111 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -66,38 +66,6 @@ struct cpu_timer {
struct task_struct __rcu *handling;
};
-static inline bool cpu_timer_enqueue(struct timerqueue_head *head,
- struct cpu_timer *ctmr)
-{
- ctmr->head = head;
- return timerqueue_add(head, &ctmr->node);
-}
-
-static inline bool cpu_timer_queued(struct cpu_timer *ctmr)
-{
- return !!ctmr->head;
-}
-
-static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr)
-{
- if (cpu_timer_queued(ctmr)) {
- timerqueue_del(ctmr->head, &ctmr->node);
- ctmr->head = NULL;
- return true;
- }
- return false;
-}
-
-static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr)
-{
- return ctmr->node.expires;
-}
-
-static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp)
-{
- ctmr->node.expires = exp;
-}
-
static inline void posix_cputimers_init(struct posix_cputimers *pct)
{
memset(pct, 0, sizeof(*pct));
@@ -224,14 +192,15 @@ struct k_itimer {
} ____cacheline_aligned_in_smp;
void run_posix_cpu_timers(void);
-void posix_cpu_timers_exit(struct task_struct *task);
-void posix_cpu_timers_exit_group(struct task_struct *task);
void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx,
u64 *newval, u64 *oldval);
int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new);
#ifdef CONFIG_POSIX_TIMERS
+void posixtimer_exec(void);
+void posixtimer_exit(bool group_dead);
+
static inline void posixtimer_putref(struct k_itimer *tmr)
{
if (rcuref_put(&tmr->rcuref))
@@ -259,6 +228,8 @@ static inline bool posixtimer_valid(const struct k_itimer *timer)
return !(val & 0x1UL);
}
#else /* CONFIG_POSIX_TIMERS */
+static inline void posixtimer_exec(void) { }
+static inline void posixtimer_exit(bool group_dead) { }
static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { }
static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { }
#endif /* !CONFIG_POSIX_TIMERS */
diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h
index e0c1ca8c6a18..90ed5bc3c7af 100644
--- a/include/linux/sched/task.h
+++ b/include/linux/sched/task.h
@@ -94,7 +94,6 @@ static inline void exit_thread(struct task_struct *tsk)
extern __noreturn void do_group_exit(int);
extern void exit_files(struct task_struct *);
-extern void exit_itimers(struct task_struct *);
extern pid_t kernel_clone(struct kernel_clone_args *kargs);
struct task_struct *copy_process(struct pid *pid, int trace, int node,
diff --git a/include/linux/wait.h b/include/linux/wait.h
index 7e215330199c..c2af98b0074d 100644
--- a/include/linux/wait.h
+++ b/include/linux/wait.h
@@ -556,7 +556,7 @@ do { \
} \
\
__ret = ___wait_event(wq_head, condition, state, 0, 0, \
- if (!__t.task) { \
+ if (!hrtimer_sleeper_task_get(&__t)) { \
__ret = -ETIME; \
break; \
} \
diff --git a/io_uring/rw.c b/io_uring/rw.c
index 432820f86251..d9b1e096f07b 100644
--- a/io_uring/rw.c
+++ b/io_uring/rw.c
@@ -1295,7 +1295,7 @@ static u64 io_hybrid_iopoll_delay(struct io_ring_ctx *ctx, struct io_kiocb *req)
set_current_state(TASK_INTERRUPTIBLE);
hrtimer_sleeper_start_expires(&timer, mode);
- if (timer.task)
+ if (hrtimer_sleeper_task_get(&timer))
io_schedule();
hrtimer_cancel(&timer.timer);
diff --git a/kernel/exit.c b/kernel/exit.c
index 424c44a42a4d..06a1eb1fdfcd 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -167,12 +167,6 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts
lockdep_tasklist_lock_is_held());
spin_lock(&sighand->siglock);
-#ifdef CONFIG_POSIX_TIMERS
- posix_cpu_timers_exit(tsk);
- if (group_dead)
- posix_cpu_timers_exit_group(tsk);
-#endif
-
if (group_dead) {
tty = sig->tty;
sig->tty = NULL;
@@ -966,13 +960,12 @@ void __noreturn do_exit(long code)
panic("Attempted to kill init! exitcode=0x%08x\n",
tsk->signal->group_exit_code ?: (int)code);
-#ifdef CONFIG_POSIX_TIMERS
- hrtimer_cancel(&tsk->signal->real_timer);
- exit_itimers(tsk);
-#endif
if (tsk->mm)
setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm);
}
+
+ posixtimer_exit(group_dead);
+
acct_collect(code, group_dead);
if (group_dead)
tty_audit_exit();
diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c
index b3f4a4bccb12..842d852302dd 100644
--- a/kernel/futex/requeue.c
+++ b/kernel/futex/requeue.c
@@ -744,7 +744,7 @@ int handle_early_requeue_pi_wakeup(struct futex_hash_bucket *hb,
/* Handle spurious wakeups gracefully */
ret = -EWOULDBLOCK;
- if (timeout && !timeout->task)
+ if (timeout && !hrtimer_sleeper_task_get(timeout))
ret = -ETIMEDOUT;
else if (signal_pending(current))
ret = -ERESTARTNOINTR;
diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c
index d4483d15d30a..cf18309e5770 100644
--- a/kernel/futex/waitwake.c
+++ b/kernel/futex/waitwake.c
@@ -383,7 +383,7 @@ void futex_do_wait(struct futex_q *q, struct hrtimer_sleeper *timeout)
* flagged for rescheduling. Only call schedule if there
* is no timeout, or if it has yet to expire.
*/
- if (!timeout || timeout->task)
+ if (!timeout || hrtimer_sleeper_task_get(timeout))
schedule();
}
__set_current_state(TASK_RUNNING);
@@ -539,7 +539,7 @@ int futex_wait_multiple_setup(struct futex_vector *vs, int count, int *woken)
static void futex_sleep_multiple(struct futex_vector *vs, unsigned int count,
struct hrtimer_sleeper *to)
{
- if (to && !to->task)
+ if (to && !hrtimer_sleeper_task_get(to))
return;
for (; count; count--, vs++) {
@@ -590,7 +590,7 @@ int futex_wait_multiple(struct futex_vector *vs, unsigned int count,
if (ret >= 0)
return ret;
- if (to && !to->task)
+ if (to && !hrtimer_sleeper_task_get(to))
return -ETIMEDOUT;
else if (signal_pending(current))
return -ERESTARTSYS;
@@ -725,7 +725,7 @@ int __futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
if (!futex_unqueue(&q))
return 0;
- if (to && !to->task)
+ if (to && !hrtimer_sleeper_task_get(to))
return -ETIMEDOUT;
/*
diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c
index 4728631ae719..5a9534c715b8 100644
--- a/kernel/locking/rtmutex.c
+++ b/kernel/locking/rtmutex.c
@@ -1644,7 +1644,7 @@ static int __sched rt_mutex_slowlock_block(struct rt_mutex_base *lock,
break;
}
- if (timeout && !timeout->task) {
+ if (timeout && !hrtimer_sleeper_task_get(timeout)) {
ret = -ETIMEDOUT;
break;
}
diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c
index cbf1693c86b3..17dd38a6cee7 100644
--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -780,7 +780,8 @@ static void hrtimer_switch_to_hres(void)
return;
}
base->hres_active = true;
- hrtimer_resolution = HIGH_RES_NSEC;
+ if (hrtimer_resolution != HIGH_RES_NSEC)
+ hrtimer_resolution = HIGH_RES_NSEC;
tick_setup_sched_timer(true);
/* "Retrigger" the interrupt to get things going */
@@ -2003,7 +2004,7 @@ bool hrtimer_active(const struct hrtimer *timer)
base = READ_ONCE(timer->base);
seq = raw_read_seqcount_begin(&base->seq);
- if (timer->is_queued || base->running == timer)
+ if (timer->is_queued || READ_ONCE(base->running) == timer)
return true;
} while (read_seqcount_retry(&base->seq, seq) || base != READ_ONCE(timer->base));
@@ -2040,7 +2041,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc
lockdep_assert_held(&cpu_base->lock);
debug_hrtimer_deactivate(timer);
- base->running = timer;
+ WRITE_ONCE(base->running, timer);
/*
* Separate the ->running assignment from the ->is_queued assignment.
@@ -2099,7 +2100,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc
raw_write_seqcount_barrier(&base->seq);
WARN_ON_ONCE(base->running != timer);
- base->running = NULL;
+ WRITE_ONCE(base->running, NULL);
}
static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now,
@@ -2323,9 +2324,9 @@ void hrtimer_run_queues(void)
static enum hrtimer_restart hrtimer_wakeup(struct hrtimer *timer)
{
struct hrtimer_sleeper *t = container_of(timer, struct hrtimer_sleeper, timer);
- struct task_struct *task = t->task;
+ struct task_struct *task = hrtimer_sleeper_task_get(t);
- t->task = NULL;
+ hrtimer_sleeper_task_set(t, NULL);
if (task)
wake_up_process(task);
@@ -2354,7 +2355,7 @@ void hrtimer_sleeper_start_expires(struct hrtimer_sleeper *sl, enum hrtimer_mode
/* If already expired, clear the task pointer and set current state to running */
if (!hrtimer_start_expires_user(&sl->timer, mode)) {
- sl->task = NULL;
+ hrtimer_sleeper_task_set(sl, NULL);
__set_current_state(TASK_RUNNING);
}
}
@@ -2388,7 +2389,7 @@ static void __hrtimer_setup_sleeper(struct hrtimer_sleeper *sl, clockid_t clock_
}
__hrtimer_setup(&sl->timer, hrtimer_wakeup, clock_id, mode);
- sl->task = current;
+ hrtimer_sleeper_task_set(sl, current);
}
/**
@@ -2432,17 +2433,17 @@ static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mod
set_current_state(TASK_INTERRUPTIBLE|TASK_FREEZABLE);
hrtimer_sleeper_start_expires(t, mode);
- if (likely(t->task))
+ if (likely(hrtimer_sleeper_task_get(t)))
schedule();
hrtimer_cancel(&t->timer);
mode = HRTIMER_MODE_ABS;
- } while (t->task && !signal_pending(current));
+ } while (hrtimer_sleeper_task_get(t) && !signal_pending(current));
__set_current_state(TASK_RUNNING);
- if (!t->task)
+ if (!hrtimer_sleeper_task_get(t))
return 0;
restart = ¤t->restart_block;
diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c
index d73d31c7994f..9f90500894dc 100644
--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -438,6 +438,38 @@ static void trigger_base_recalc_expires(struct k_itimer *timer,
base->nextevt = 0;
}
+static inline bool cpu_timer_enqueue(struct timerqueue_head *head,
+ struct cpu_timer *ctmr)
+{
+ ctmr->head = head;
+ return timerqueue_add(head, &ctmr->node);
+}
+
+static inline bool cpu_timer_queued(struct cpu_timer *ctmr)
+{
+ return !!ctmr->head;
+}
+
+static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr)
+{
+ if (cpu_timer_queued(ctmr)) {
+ timerqueue_del(ctmr->head, &ctmr->node);
+ ctmr->head = NULL;
+ return true;
+ }
+ return false;
+}
+
+static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr)
+{
+ return ctmr->node.expires;
+}
+
+static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp)
+{
+ ctmr->node.expires = exp;
+}
+
/*
* Dequeue the timer and reset the base if it was its earliest expiration.
* It makes sure the next tick recalculates the base next expiration so we
@@ -596,6 +628,7 @@ static int posix_cpu_timer_del(struct k_itimer *timer)
}
if (!ret) {
+ WARN_ON_ONCE(cpu_timer_queued(&timer->it.cpu));
put_pid(timer->it.cpu.pid);
timer->it_status = POSIX_TIMER_DISARMED;
}
@@ -628,18 +661,50 @@ static void cleanup_timers(struct posix_cputimers *pct)
cleanup_timerqueue(&pct->bases[CPUCLOCK_SCHED].tqhead);
}
+static inline void posix_cpu_timers_exit_work(void);
+
/*
- * These are both called with the siglock held, when the current thread
- * is being reaped. When the final (leader) thread in the group is reaped,
- * posix_cpu_timers_exit_group will be called after posix_cpu_timers_exit.
+ * Invoked from posixtimer_exit_task() after PF_EXITING was set in tsk::flags or
+ * from posixtimer_exec_cleanup().
*/
-void posix_cpu_timers_exit(struct task_struct *tsk)
+void posix_cpu_timers_exit_task(void)
{
- cleanup_timers(&tsk->posix_cputimers);
+ posix_cpu_timers_exit_work();
+
+ guard(spinlock_irq)(¤t->sighand->siglock);
+ cleanup_timers(¤t->posix_cputimers);
}
-void posix_cpu_timers_exit_group(struct task_struct *tsk)
+
+/*
+ * Invoked from posixtimer_exit_group() after PF_EXITING was set in tsk::flags.
+ */
+void posix_cpu_timers_exit_group(void)
{
- cleanup_timers(&tsk->signal->posix_cputimers);
+ posix_cpu_timers_exit_task();
+
+ guard(spinlock_irq)(¤t->sighand->siglock);
+ cleanup_timers(¤t->signal->posix_cputimers);
+}
+
+/*
+ * This function validates that POSIX CPU timers can be safely enqueued on the
+ * target task.
+ *
+ * Enqueue is allowed when PF_EXITING is not set. If set then it is only allowed
+ * for process shared timers (type = PIDTYPE_TGID) as long as tsk::signal::flags
+ * does not have SIGNAL_GROUP_EXIT set. PIDTYPE_PID targets are not allowed at
+ * all when the task has PF_EXITING set.
+ *
+ * This guarantees that after the POSIX timer cleanup in posixtimer_exit() no
+ * POSIX CPU timers are queued on the task or in case of a group exit on the
+ * process.
+ */
+static inline bool task_can_enqueue_timer(struct task_struct *tsk, enum pid_type type)
+{
+ if (likely(!(tsk->flags & PF_EXITING)))
+ return true;
+
+ return type == PIDTYPE_TGID && !(tsk->signal->flags & SIGNAL_GROUP_EXIT);
}
/*
@@ -652,7 +717,13 @@ static void arm_timer(struct k_itimer *timer, struct task_struct *p)
struct cpu_timer *ctmr = &timer->it.cpu;
u64 newexp = cpu_timer_getexpires(ctmr);
+ lockdep_assert_held(&p->sighand->siglock);
+
timer->it_status = POSIX_TIMER_ARMED;
+
+ if (unlikely(!task_can_enqueue_timer(p, clock_pid_type(timer->it_clock))))
+ return;
+
if (!cpu_timer_enqueue(&base->tqhead, ctmr))
return;
@@ -1197,6 +1268,20 @@ static void posix_cpu_timers_work(struct callback_head *work)
mutex_unlock(&cw->mutex);
}
+static inline void posix_cpu_timers_exit_work(void)
+{
+ /* Canceling the work is only valid for exit() but not for exec() */
+ if (!(current->flags & PF_EXITING))
+ return;
+ /*
+ * current->flags has PF_EXITING set so this can be done lockless and
+ * with interrupts enabled as PF_EXITING prevents the interrupt from
+ * scheduling the work.
+ */
+ if (current->posix_cputimers_work.scheduled)
+ task_work_cancel(current, ¤t->posix_cputimers_work.work);
+}
+
/*
* Invoked from the posix-timer core when a cancel operation failed because
* the timer is marked firing. The caller holds rcu_read_lock(), which
@@ -1327,6 +1412,8 @@ static inline void __run_posix_cpu_timers(struct task_struct *tsk)
lockdep_posixtimer_exit();
}
+static inline void posix_cpu_timers_exit_work(void) { }
+
static void posix_cpu_timer_wait_running(struct k_itimer *timr)
{
cpu_relax();
@@ -1473,7 +1560,7 @@ void run_posix_cpu_timers(void)
* posix_cpu_timer_del() may fail to lock_task_sighand(tsk) and
* miss timer->it.cpu.firing != 0.
*/
- if (tsk->exit_state)
+ if (tsk->flags & PF_EXITING)
return;
/*
diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c
index 436ba794cc0b..188dbedbffca 100644
--- a/kernel/time/posix-timers.c
+++ b/kernel/time/posix-timers.c
@@ -1077,13 +1077,9 @@ SYSCALL_DEFINE1(timer_delete, timer_t, timer_id)
return 0;
}
-/*
- * Invoked from do_exit() when the last thread of a thread group exits.
- * At that point no other task can access the timers of the dying
- * task anymore.
- */
-void exit_itimers(struct task_struct *tsk)
+static void posixtimer_delete_timers(void)
{
+ struct task_struct *tsk = current;
struct hlist_head timers;
struct hlist_node *next;
struct k_itimer *timer;
@@ -1120,6 +1116,24 @@ void exit_itimers(struct task_struct *tsk)
}
}
+void posixtimer_exit(bool group_dead)
+{
+ if (group_dead) {
+ hrtimer_cancel(¤t->signal->real_timer);
+ posix_cpu_timers_exit_group();
+ posixtimer_delete_timers();
+ } else {
+ posix_cpu_timers_exit_task();
+ }
+}
+
+void posixtimer_exec(void)
+{
+ posix_cpu_timers_exit_task();
+ posixtimer_delete_timers();
+ flush_itimer_signals();
+}
+
SYSCALL_DEFINE2(clock_settime, const clockid_t, which_clock,
const struct __kernel_timespec __user *, tp)
{
diff --git a/kernel/time/posix-timers.h b/kernel/time/posix-timers.h
index 4ea9611dd716..79fd7ea71046 100644
--- a/kernel/time/posix-timers.h
+++ b/kernel/time/posix-timers.h
@@ -51,3 +51,6 @@ int common_timer_set(struct k_itimer *timr, int flags,
struct itimerspec64 *old_setting);
void posix_timer_set_common(struct k_itimer *timer, struct itimerspec64 *new_setting);
int common_timer_del(struct k_itimer *timer);
+
+void posix_cpu_timers_exit_task(void);
+void posix_cpu_timers_exit_group(void);
diff --git a/kernel/time/sleep_timeout.c b/kernel/time/sleep_timeout.c
index 3c90574bd904..ad8c415851ae 100644
--- a/kernel/time/sleep_timeout.c
+++ b/kernel/time/sleep_timeout.c
@@ -212,7 +212,7 @@ int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta,
hrtimer_set_expires_range_ns(&t.timer, *expires, delta);
hrtimer_sleeper_start_expires(&t, mode);
- if (likely(t.task))
+ if (likely(hrtimer_sleeper_task_get(&t)))
schedule();
hrtimer_cancel(&t.timer);
@@ -220,7 +220,7 @@ int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta,
__set_current_state(TASK_RUNNING);
- return !t.task ? 0 : -EINTR;
+ return !hrtimer_sleeper_task_get(&t) ? 0 : -EINTR;
}
EXPORT_SYMBOL_GPL(schedule_hrtimeout_range_clock);
diff --git a/kernel/time/time_test.c b/kernel/time/time_test.c
index 1b99180da288..8b718767b3ba 100644
--- a/kernel/time/time_test.c
+++ b/kernel/time/time_test.c
@@ -87,8 +87,24 @@ static void time64_to_tm_test_date_range(struct kunit *test)
}
}
+static void time64_to_tm_test_wide_day_count(struct kunit *test)
+{
+ /* 2^31 days: the first count that does not fit in a 32-bit long. */
+ time64_t timestamp = (1LL << 31) * 86400;
+ struct tm result;
+
+ time64_to_tm(timestamp, 0, &result);
+
+ KUNIT_EXPECT_EQ(test, result.tm_year, 5879680);
+ KUNIT_EXPECT_EQ(test, result.tm_mon, 6);
+ KUNIT_EXPECT_EQ(test, result.tm_mday, 12);
+ KUNIT_EXPECT_EQ(test, result.tm_yday, 193);
+ KUNIT_EXPECT_EQ(test, result.tm_wday, 6);
+}
+
static struct kunit_case time_test_cases[] = {
KUNIT_CASE_SLOW(time64_to_tm_test_date_range),
+ KUNIT_CASE(time64_to_tm_test_wide_day_count),
{}
};
diff --git a/kernel/time/timeconv.c b/kernel/time/timeconv.c
index 59b922c826e7..aed3af950fa0 100644
--- a/kernel/time/timeconv.c
+++ b/kernel/time/timeconv.c
@@ -49,8 +49,9 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
u32 u32tmp, day_of_century, year_of_century, day_of_year, month, day;
u64 u64tmp, udays, century, year;
bool is_Jan_or_Feb, is_leap_year;
- long days, rem;
int remainder;
+ long rem;
+ s64 days;
days = div_s64_rem(totalsecs, SECS_PER_DAY, &remainder);
rem = remainder;
@@ -70,7 +71,8 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
result->tm_sec = rem % 60;
/* January 1, 1970 was a Thursday. */
- result->tm_wday = (4 + days) % 7;
+ div_s64_rem(days + 4, 7, &remainder);
+ result->tm_wday = remainder;
if (result->tm_wday < 0)
result->tm_wday += 7;
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index ea2e6e55f37b..d54c4d303db6 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -861,8 +861,10 @@ static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int act
*
* Write xtime_sec first so that even if the memcpy() tears the store
* data integrity is provided for ktime_get_real_seconds().
+ * The same goes for ktime_sec and ktime_get_seconds().
*/
WRITE_ONCE(tkd->timekeeper.xtime_sec, tk->xtime_sec);
+ WRITE_ONCE(tkd->timekeeper.ktime_sec, tk->ktime_sec);
memcpy(&tkd->timekeeper, tk, sizeof(*tk));
write_seqcount_end(&tkd->seq);
}
@@ -1169,7 +1171,7 @@ time64_t ktime_get_seconds(void)
struct timekeeper *tk = &tk_core.timekeeper;
WARN_ON(timekeeping_suspended);
- return tk->ktime_sec;
+ return READ_ONCE(tk->ktime_sec);
}
EXPORT_SYMBOL_GPL(ktime_get_seconds);
diff --git a/kernel/time/timer.c b/kernel/time/timer.c
index ae9abf14688e..42afdcb229d8 100644
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -890,7 +890,7 @@ static inline void detach_timer(struct timer_list *timer, bool clear_pending)
__hlist_del(entry);
if (clear_pending)
- entry->pprev = NULL;
+ WRITE_ONCE(entry->pprev, NULL);
entry->next = LIST_POISON2;
}
diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c
index 059d43355e65..f920e73fff51 100644
--- a/kernel/time/timer_migration.c
+++ b/kernel/time/timer_migration.c
@@ -715,7 +715,7 @@ static void __tmigr_cpu_activate(struct tmigr_cpu *tmc)
trace_tmigr_cpu_active(tmc);
- tmc->cpuevt.ignore = true;
+ WRITE_ONCE(tmc->cpuevt.ignore, true);
WRITE_ONCE(tmc->wakeup, KTIME_MAX);
walk_groups(&tmigr_active_up, &data, tmc);
@@ -1258,7 +1258,7 @@ u64 tmigr_cpu_new_timer(u64 nextexp)
ret = READ_ONCE(tmc->wakeup);
if (nextexp != KTIME_MAX) {
if (nextexp != tmc->cpuevt.nextevt.expires ||
- tmc->cpuevt.ignore) {
+ READ_ONCE(tmc->cpuevt.ignore)) {
ret = tmigr_new_timer(tmc, nextexp);
/*
* Make sure the reevaluation of timers in idle path
@@ -1362,7 +1362,7 @@ static u64 __tmigr_cpu_deactivate(struct tmigr_cpu *tmc, u64 nextexp)
* or CPU goes offline.
*/
if (nextexp != KTIME_MAX)
- tmc->cpuevt.ignore = false;
+ WRITE_ONCE(tmc->cpuevt.ignore, false);
walk_groups(&tmigr_inactive_up, &data, tmc);
return data.firstexp;
diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index 7f81aed46672..784d8398fb7a 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -2346,11 +2346,11 @@ static void spin(struct pktgen_dev *pkt_dev, ktime_t spin_until)
set_current_state(TASK_INTERRUPTIBLE);
hrtimer_sleeper_start_expires(&t, HRTIMER_MODE_ABS);
- if (likely(t.task))
+ if (likely(hrtimer_sleeper_task_get(&t)))
schedule();
hrtimer_cancel(&t.timer);
- } while (t.task && pkt_dev->running && !signal_pending(current));
+ } while (hrtimer_sleeper_task_get(&t) && pkt_dev->running && !signal_pending(current));
__set_current_state(TASK_RUNNING);
end_time = ktime_get();
}
diff --git a/tools/testing/selftests/timers/clocksource-switch.c b/tools/testing/selftests/timers/clocksource-switch.c
index db62a764c29e..2e86f56d953e 100644
--- a/tools/testing/selftests/timers/clocksource-switch.c
+++ b/tools/testing/selftests/timers/clocksource-switch.c
@@ -40,16 +40,23 @@
int get_clocksources(char list[][30])
{
int fd, i;
- size_t size;
+ ssize_t size;
char buf[512];
char *head, *tmp;
fd = open("/sys/devices/system/clocksource/clocksource0/available_clocksource", O_RDONLY);
+ if (fd < 0)
+ return 0;
- size = read(fd, buf, 512);
+ size = read(fd, buf, sizeof(buf) - 1);
close(fd);
+ if (size <= 0)
+ return 0;
+
+ buf[size] = '\0';
+
for (i = 0; i < 10; i++)
list[i][0] = '\0';
@@ -74,11 +81,21 @@ int get_clocksources(char list[][30])
int get_cur_clocksource(char *buf, size_t size)
{
+ ssize_t len;
int fd;
fd = open("/sys/devices/system/clocksource/clocksource0/current_clocksource", O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ len = read(fd, buf, size - 1);
+
+ close(fd);
+
+ if (len <= 0)
+ return -1;
- size = read(fd, buf, size);
+ buf[len] = '\0';
return 0;
}
diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c
index a92d4b957747..52b7289e1450 100644
--- a/tools/testing/selftests/timers/posix_timers.c
+++ b/tools/testing/selftests/timers/posix_timers.c
@@ -113,9 +113,10 @@ static void check_itimer(int which, const char *name)
done = 0;
- if (which == ITIMER_VIRTUAL)
+ if (which == ITIMER_VIRTUAL) {
+ clock_id = CLOCK_THREAD_CPUTIME_ID;
signal(SIGVTALRM, sig_handler);
- else if (which == ITIMER_PROF) {
+ } else if (which == ITIMER_PROF) {
clock_id = CLOCK_THREAD_CPUTIME_ID;
signal(SIGPROF, sig_handler);
}
@@ -148,7 +149,7 @@ static void check_timer_create(int which)
struct itimerspec val = {
.it_value.tv_sec = DELAY,
};
- int clock_id = CLOCK_REALTIME;
+ int clock_id = which;
timer_t id;
done = 0;
diff --git a/tools/testing/selftests/timers/raw_skew.c b/tools/testing/selftests/timers/raw_skew.c
index 0c87a8fb0d7f..dfca3e2d50d2 100644
--- a/tools/testing/selftests/timers/raw_skew.c
+++ b/tools/testing/selftests/timers/raw_skew.c
@@ -91,6 +91,7 @@ int main(int argc, char **argv)
{
struct timespec mon, raw, start, end;
long long delta1, delta2, interval, eppm, ppm;
+ long tick_nom;
struct timex tx1, tx2;
setbuf(stdout, NULL);
@@ -129,6 +130,17 @@ int main(int argc, char **argv)
/* Avg the two actual freq samples adjtimex gave us */
ppm = (long long)(tx1.freq + tx2.freq) * 1000 / 2;
ppm = shift_right(ppm, 16);
+
+ /*
+ * The tick value holds the coarse part of the adjustment, a
+ * microsecond of the tick length per unit, and time sync daemons
+ * do put part of their correction there. What it is worth has to
+ * be counted in as well, or a clock disciplined through it looks
+ * off by a hundred ppm a unit against what the two clocks show.
+ */
+ tick_nom = USEC_PER_SEC / sysconf(_SC_CLK_TCK);
+ ppm += (long long)(tx1.tick + tx2.tick - 2 * tick_nom) *
+ (USEC_PER_SEC / tick_nom) * 1000 / 2;
printf(" %lld.%i(act)", ppm/1000, abs((int)(ppm%1000)));
if (llabs(eppm - ppm) > 1000) {
^ permalink raw reply [flat|nested] 12+ messages in thread