Commit acd26bcf authored by Thomas Gleixner's avatar Thomas Gleixner
Browse files

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: default avatarThomas Gleixner <tglx@linutronix.de>
Tested-by: default avatarKuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Reviewed-by: default avatarKuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Acked-by: default avatarMarc Zyngier <maz@kernel.org>
Link: https://lkml.kernel.org/r/20200306130623.990928309@linutronix.de
parent da90921a
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -248,6 +248,8 @@ extern void enable_percpu_nmi(unsigned int irq, unsigned int type);
extern int prepare_percpu_nmi(unsigned int irq);
extern int prepare_percpu_nmi(unsigned int irq);
extern void teardown_percpu_nmi(unsigned int irq);
extern void teardown_percpu_nmi(unsigned int irq);


extern int irq_inject_interrupt(unsigned int irq);

/* The following three functions are for the core kernel use only. */
/* The following three functions are for the core kernel use only. */
extern void suspend_device_irqs(void);
extern void suspend_device_irqs(void);
extern void resume_device_irqs(void);
extern void resume_device_irqs(void);
+5 −0
Original line number Original line Diff line number Diff line
@@ -43,6 +43,10 @@ config GENERIC_IRQ_MIGRATION
config AUTO_IRQ_AFFINITY
config AUTO_IRQ_AFFINITY
       bool
       bool


# Interrupt injection mechanism
config GENERIC_IRQ_INJECTION
	bool

# Tasklet based software resend for pending interrupts on enable_irq()
# Tasklet based software resend for pending interrupts on enable_irq()
config HARDIRQS_SW_RESEND
config HARDIRQS_SW_RESEND
       bool
       bool
@@ -127,6 +131,7 @@ config SPARSE_IRQ
config GENERIC_IRQ_DEBUGFS
config GENERIC_IRQ_DEBUGFS
	bool "Expose irq internals in debugfs"
	bool "Expose irq internals in debugfs"
	depends on DEBUG_FS
	depends on DEBUG_FS
	select GENERIC_IRQ_INJECTION
	default n
	default n
	---help---
	---help---


+1 −1
Original line number Original line Diff line number Diff line
@@ -278,7 +278,7 @@ int irq_startup(struct irq_desc *desc, bool resend, bool force)
		}
		}
	}
	}
	if (resend)
	if (resend)
		check_irq_resend(desc);
		check_irq_resend(desc, false);


	return ret;
	return ret;
}
}
+1 −33
Original line number Original line Diff line number Diff line
@@ -190,39 +190,7 @@ static ssize_t irq_debug_write(struct file *file, const char __user *user_buf,
		return -EFAULT;
		return -EFAULT;


	if (!strncmp(buf, "trigger", size)) {
	if (!strncmp(buf, "trigger", size)) {
		unsigned long flags;
		int err = irq_inject_interrupt(irq_desc_get_irq(desc));
		int err;

		/* Try the HW interface first */
		err = irq_set_irqchip_state(irq_desc_get_irq(desc),
					    IRQCHIP_STATE_PENDING, true);
		if (!err)
			return count;

		/*
		 * Otherwise, try to inject via the resend interface,
		 * which may or may not succeed.
		 */
		chip_bus_lock(desc);
		raw_spin_lock_irqsave(&desc->lock, flags);

		/*
		 * Don't allow injection when the interrupt is:
		 *  - Level or NMI type
		 *  - not activated
		 *  - replaying already
		 */
		if (irq_settings_is_level(desc) ||
		    !irqd_is_activated(&desc->irq_data) ||
		    (desc->istate & (IRQS_NMI | IRQS_REPLAY))) {
			err = -EINVAL;
		} else {
			desc->istate |= IRQS_PENDING;
			err = check_irq_resend(desc);
		}

		raw_spin_unlock_irqrestore(&desc->lock, flags);
		chip_bus_sync_unlock(desc);


		return err ? err : count;
		return err ? err : count;
	}
	}
+1 −1
Original line number Original line Diff line number Diff line
@@ -108,7 +108,7 @@ irqreturn_t handle_irq_event_percpu(struct irq_desc *desc);
irqreturn_t handle_irq_event(struct irq_desc *desc);
irqreturn_t handle_irq_event(struct irq_desc *desc);


/* Resending of interrupts :*/
/* Resending of interrupts :*/
int check_irq_resend(struct irq_desc *desc);
int check_irq_resend(struct irq_desc *desc, bool inject);
bool irq_wait_for_poll(struct irq_desc *desc);
bool irq_wait_for_poll(struct irq_desc *desc);
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action);
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action);


Loading