Skip to content

Commit acd26bc

Browse files
committed
genirq: Provide interrupt injection mechanism
Error injection mechanisms need a half ways safe way to inject interrupts as invoking generic_handle_irq() or the actual device interrupt handler directly from e.g. a debugfs write is not guaranteed to be safe. On x86 generic_handle_irq() is unsafe due to the hardware trainwreck which is the base of x86 interrupt delivery and affinity management. Move the irq debugfs injection code into a separate function which can be used by error injection code as well. The implementation prevents at least that state is corrupted, but it cannot close a very tiny race window on x86 which might result in a stale and not serviced device interrupt under very unlikely circumstances. This is explicitly for debugging and testing and not for production use or abuse in random driver code. Signed-off-by: Thomas Gleixner <[email protected]> Tested-by: Kuppuswamy Sathyanarayanan <[email protected]> Reviewed-by: Kuppuswamy Sathyanarayanan <[email protected]> Acked-by: Marc Zyngier <[email protected]> Link: https://lkml.kernel.org/r/[email protected]
1 parent da90921 commit acd26bc

File tree

6 files changed

+61
-37
lines changed

6 files changed

+61
-37
lines changed

include/linux/interrupt.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ extern void enable_percpu_nmi(unsigned int irq, unsigned int type);
248248
extern int prepare_percpu_nmi(unsigned int irq);
249249
extern void teardown_percpu_nmi(unsigned int irq);
250250

251+
extern int irq_inject_interrupt(unsigned int irq);
252+
251253
/* The following three functions are for the core kernel use only. */
252254
extern void suspend_device_irqs(void);
253255
extern void resume_device_irqs(void);

kernel/irq/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ config GENERIC_IRQ_MIGRATION
4343
config AUTO_IRQ_AFFINITY
4444
bool
4545

46+
# Interrupt injection mechanism
47+
config GENERIC_IRQ_INJECTION
48+
bool
49+
4650
# Tasklet based software resend for pending interrupts on enable_irq()
4751
config HARDIRQS_SW_RESEND
4852
bool
@@ -127,6 +131,7 @@ config SPARSE_IRQ
127131
config GENERIC_IRQ_DEBUGFS
128132
bool "Expose irq internals in debugfs"
129133
depends on DEBUG_FS
134+
select GENERIC_IRQ_INJECTION
130135
default n
131136
---help---
132137

kernel/irq/chip.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ int irq_startup(struct irq_desc *desc, bool resend, bool force)
278278
}
279279
}
280280
if (resend)
281-
check_irq_resend(desc);
281+
check_irq_resend(desc, false);
282282

283283
return ret;
284284
}

kernel/irq/debugfs.c

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -190,39 +190,7 @@ static ssize_t irq_debug_write(struct file *file, const char __user *user_buf,
190190
return -EFAULT;
191191

192192
if (!strncmp(buf, "trigger", size)) {
193-
unsigned long flags;
194-
int err;
195-
196-
/* Try the HW interface first */
197-
err = irq_set_irqchip_state(irq_desc_get_irq(desc),
198-
IRQCHIP_STATE_PENDING, true);
199-
if (!err)
200-
return count;
201-
202-
/*
203-
* Otherwise, try to inject via the resend interface,
204-
* which may or may not succeed.
205-
*/
206-
chip_bus_lock(desc);
207-
raw_spin_lock_irqsave(&desc->lock, flags);
208-
209-
/*
210-
* Don't allow injection when the interrupt is:
211-
* - Level or NMI type
212-
* - not activated
213-
* - replaying already
214-
*/
215-
if (irq_settings_is_level(desc) ||
216-
!irqd_is_activated(&desc->irq_data) ||
217-
(desc->istate & (IRQS_NMI | IRQS_REPLAY))) {
218-
err = -EINVAL;
219-
} else {
220-
desc->istate |= IRQS_PENDING;
221-
err = check_irq_resend(desc);
222-
}
223-
224-
raw_spin_unlock_irqrestore(&desc->lock, flags);
225-
chip_bus_sync_unlock(desc);
193+
int err = irq_inject_interrupt(irq_desc_get_irq(desc));
226194

227195
return err ? err : count;
228196
}

kernel/irq/internals.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ irqreturn_t handle_irq_event_percpu(struct irq_desc *desc);
108108
irqreturn_t handle_irq_event(struct irq_desc *desc);
109109

110110
/* Resending of interrupts :*/
111-
int check_irq_resend(struct irq_desc *desc);
111+
int check_irq_resend(struct irq_desc *desc, bool inject);
112112
bool irq_wait_for_poll(struct irq_desc *desc);
113113
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action);
114114

kernel/irq/resend.c

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static int irq_sw_resend(struct irq_desc *desc)
9191
*
9292
* Is called with interrupts disabled and desc->lock held.
9393
*/
94-
int check_irq_resend(struct irq_desc *desc)
94+
int check_irq_resend(struct irq_desc *desc, bool inject)
9595
{
9696
int err = 0;
9797

@@ -108,7 +108,7 @@ int check_irq_resend(struct irq_desc *desc)
108108
if (desc->istate & IRQS_REPLAY)
109109
return -EBUSY;
110110

111-
if (!(desc->istate & IRQS_PENDING))
111+
if (!(desc->istate & IRQS_PENDING) && !inject)
112112
return 0;
113113

114114
desc->istate &= ~IRQS_PENDING;
@@ -122,3 +122,52 @@ int check_irq_resend(struct irq_desc *desc)
122122
desc->istate |= IRQS_REPLAY;
123123
return err;
124124
}
125+
126+
#ifdef CONFIG_GENERIC_IRQ_INJECTION
127+
/**
128+
* irq_inject_interrupt - Inject an interrupt for testing/error injection
129+
* @irq: The interrupt number
130+
*
131+
* This function must only be used for debug and testing purposes!
132+
*
133+
* Especially on x86 this can cause a premature completion of an interrupt
134+
* affinity change causing the interrupt line to become stale. Very
135+
* unlikely, but possible.
136+
*
137+
* The injection can fail for various reasons:
138+
* - Interrupt is not activated
139+
* - Interrupt is NMI type or currently replaying
140+
* - Interrupt is level type
141+
* - Interrupt does not support hardware retrigger and software resend is
142+
* either not enabled or not possible for the interrupt.
143+
*/
144+
int irq_inject_interrupt(unsigned int irq)
145+
{
146+
struct irq_desc *desc;
147+
unsigned long flags;
148+
int err;
149+
150+
/* Try the state injection hardware interface first */
151+
if (!irq_set_irqchip_state(irq, IRQCHIP_STATE_PENDING, true))
152+
return 0;
153+
154+
/* That failed, try via the resend mechanism */
155+
desc = irq_get_desc_buslock(irq, &flags, 0);
156+
if (!desc)
157+
return -EINVAL;
158+
159+
/*
160+
* Only try to inject when the interrupt is:
161+
* - not NMI type
162+
* - activated
163+
*/
164+
if ((desc->istate & IRQS_NMI) || !irqd_is_activated(&desc->irq_data))
165+
err = -EINVAL;
166+
else
167+
err = check_irq_resend(desc, true);
168+
169+
irq_put_desc_busunlock(desc, flags);
170+
return err;
171+
}
172+
EXPORT_SYMBOL_GPL(irq_inject_interrupt);
173+
#endif

0 commit comments

Comments
 (0)