Skip to content

Commit

Permalink
irqchip/gic-v3: Refactor ISB + EOIR at ack time
Browse files Browse the repository at this point in the history
commit 6efb509 upstream.

There are cases where a context synchronization event is necessary
between an IRQ being raised and being handled, and there are races such
that we cannot rely upon the exception entry being subsequent to the
interrupt being raised. To fix this, we place an ISB between a read of
IAR and the subsequent invocation of an IRQ handler.

When EOI mode 1 is in use, we need to EOI an interrupt prior to invoking
its handler, and we have a write to EOIR for this. As this write to EOIR
requires an ISB, and this is provided by the gic_write_eoir() helper, we
omit the usual ISB in this case, with the logic being:

|	if (static_branch_likely(&supports_deactivate_key))
|		gic_write_eoir(irqnr);
|	else
|		isb();

This is somewhat opaque, and it would be a little clearer if there were
an unconditional ISB, with only the write to EOIR being conditional,
e.g.

|	if (static_branch_likely(&supports_deactivate_key))
|		write_gicreg(irqnr, ICC_EOIR1_EL1);
|
|	isb();

This patch rewrites the code that way, with this logic factored into a
new helper function with comments explaining what the ISB is for, as
were originally laid out in commit:

  39a06b6 ("irqchip/gic: Ensure we have an ISB between ack and ->handle_irq")

Note that since then, we removed the IAR polling in commit:

  342677d ("irqchip/gic-v3: Remove acknowledge loop")

... which removed one of the two race conditions.

For consistency, other portions of the driver are made to manipulate
EOIR using write_gicreg() and explcit ISBs, and the gic_write_eoir()
helper function is removed.

There should be no functional change as a result of this patch.

Signed-off-by: Mark Rutland <[email protected]>
Cc: Marc Zyngier <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Cc: Will Deacon <[email protected]>
Signed-off-by: Marc Zyngier <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Cc: Jon Hunter <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
Mark Rutland authored and gregkh committed Jul 12, 2022
1 parent b82dfac commit d74b09b
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 22 deletions.
7 changes: 1 addition & 6 deletions arch/arm/include/asm/arch_gicv3.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ static inline u32 read_ ## a64(void) \
return read_sysreg(a32); \
} \

CPUIF_MAP(ICC_EOIR1, ICC_EOIR1_EL1)
CPUIF_MAP(ICC_PMR, ICC_PMR_EL1)
CPUIF_MAP(ICC_AP0R0, ICC_AP0R0_EL1)
CPUIF_MAP(ICC_AP0R1, ICC_AP0R1_EL1)
Expand All @@ -63,12 +64,6 @@ CPUIF_MAP(ICC_AP1R3, ICC_AP1R3_EL1)

/* Low-level accessors */

static inline void gic_write_eoir(u32 irq)
{
write_sysreg(irq, ICC_EOIR1);
isb();
}

static inline void gic_write_dir(u32 val)
{
write_sysreg(val, ICC_DIR);
Expand Down
6 changes: 0 additions & 6 deletions arch/arm64/include/asm/arch_gicv3.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@
* sets the GP register's most significant bits to 0 with an explicit cast.
*/

static inline void gic_write_eoir(u32 irq)
{
write_sysreg_s(irq, SYS_ICC_EOIR1_EL1);
isb();
}

static __always_inline void gic_write_dir(u32 irq)
{
write_sysreg_s(irq, SYS_ICC_DIR_EL1);
Expand Down
43 changes: 33 additions & 10 deletions drivers/irqchip/irq-gic-v3.c
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,8 @@ static void gic_irq_nmi_teardown(struct irq_data *d)

static void gic_eoi_irq(struct irq_data *d)
{
gic_write_eoir(gic_irq(d));
write_gicreg(gic_irq(d), ICC_EOIR1_EL1);
isb();
}

static void gic_eoimode1_eoi_irq(struct irq_data *d)
Expand Down Expand Up @@ -640,10 +641,38 @@ static void gic_deactivate_unhandled(u32 irqnr)
if (irqnr < 8192)
gic_write_dir(irqnr);
} else {
gic_write_eoir(irqnr);
write_gicreg(irqnr, ICC_EOIR1_EL1);
isb();
}
}

/*
* Follow a read of the IAR with any HW maintenance that needs to happen prior
* to invoking the relevant IRQ handler. We must do two things:
*
* (1) Ensure instruction ordering between a read of IAR and subsequent
* instructions in the IRQ handler using an ISB.
*
* It is possible for the IAR to report an IRQ which was signalled *after*
* the CPU took an IRQ exception as multiple interrupts can race to be
* recognized by the GIC, earlier interrupts could be withdrawn, and/or
* later interrupts could be prioritized by the GIC.
*
* For devices which are tightly coupled to the CPU, such as PMUs, a
* context synchronization event is necessary to ensure that system
* register state is not stale, as these may have been indirectly written
* *after* exception entry.
*
* (2) Deactivate the interrupt when EOI mode 1 is in use.
*/
static inline void gic_complete_ack(u32 irqnr)
{
if (static_branch_likely(&supports_deactivate_key))
write_gicreg(irqnr, ICC_EOIR1_EL1);

isb();
}

static inline void gic_handle_nmi(u32 irqnr, struct pt_regs *regs)
{
bool irqs_enabled = interrupts_enabled(regs);
Expand All @@ -652,10 +681,7 @@ static inline void gic_handle_nmi(u32 irqnr, struct pt_regs *regs)
if (irqs_enabled)
nmi_enter();

if (static_branch_likely(&supports_deactivate_key))
gic_write_eoir(irqnr);
else
isb()
gic_complete_ack(irqnr);

/*
* Leave the PSR.I bit set to prevent other NMIs to be
Expand Down Expand Up @@ -726,10 +752,7 @@ static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs
gic_arch_enable_irqs();
}

if (static_branch_likely(&supports_deactivate_key))
gic_write_eoir(irqnr);
else
isb();
gic_complete_ack(irqnr);

if (handle_domain_irq(gic_data.domain, irqnr, regs)) {
WARN_ONCE(true, "Unexpected interrupt received!\n");
Expand Down

0 comments on commit d74b09b

Please sign in to comment.