mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 08:38:45 +02:00 
			
		
		
		
	 b4706d8149
			
		
	
	
		b4706d8149
		
	
	
	
	
		
			
			During machine kexec, machine_kexec_mask_interrupts() is responsible for
disabling or masking all interrupts. While the irq_disable() is only
invoked when the interrupt is not yet disabled, it unconditionally invokes
the irq_mask() callback for every interrupt descriptor, even when the
interrupt is already masked or not even started up yet.
A specific issue was observed in the crash kernel flow after unbinding a
device (prior to kexec) that used a GPIO as an IRQ source. The warning was
triggered by the gpiochip_disable_irq() function, which attempts to clear
the FLAG_IRQ_IS_ENABLED flag when FLAG_USED_AS_IRQ was not set.
This issue surfaced after commit a8173820f4 ("gpio: gpiolib: Allow GPIO
IRQs to lazy disable") introduced lazy disablement for GPIO IRQs. It
replaced disable/enable hooks with mask/unmask hooks. Unlike the disable
hook, the mask hook doesn't handle already-masked IRQs.
When a GPIO-IRQ driver is unbound, the IRQ is released, triggering
__irq_disable() and irq_state_set_masked(). A subsequent call to
machine_kexec_mask_interrupts() re-invokes chip->irq_mask(). This results
in a call chain, including gpiochip_irq_mask() and gpiochip_disable_irq().
Since FLAG_USED_AS_IRQ was cleared earlier, the warning is triggered.
Replace the direct invocation of the irq_mask() and irq_disable() callbacks
invoking to irq_shutdown(), which handles the cases correct and avoid it
all together when the interrupt has never been started up.
Signed-off-by: Eliav Farber <farbere@amazon.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20241204142003.32859-3-farbere@amazon.com
		
	
			
		
			
				
	
	
		
			36 lines
		
	
	
	
		
			870 B
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			36 lines
		
	
	
	
		
			870 B
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| 
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/irq.h>
 | |
| #include <linux/irqdesc.h>
 | |
| #include <linux/irqnr.h>
 | |
| 
 | |
| #include "internals.h"
 | |
| 
 | |
| void machine_kexec_mask_interrupts(void)
 | |
| {
 | |
| 	struct irq_desc *desc;
 | |
| 	unsigned int i;
 | |
| 
 | |
| 	for_each_irq_desc(i, desc) {
 | |
| 		struct irq_chip *chip;
 | |
| 		int check_eoi = 1;
 | |
| 
 | |
| 		chip = irq_desc_get_chip(desc);
 | |
| 		if (!chip || !irqd_is_started(&desc->irq_data))
 | |
| 			continue;
 | |
| 
 | |
| 		if (IS_ENABLED(CONFIG_GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD)) {
 | |
| 			/*
 | |
| 			 * First try to remove the active state from an interrupt which is forwarded
 | |
| 			 * to a VM. If the interrupt is not forwarded, try to EOI the interrupt.
 | |
| 			 */
 | |
| 			check_eoi = irq_set_irqchip_state(i, IRQCHIP_STATE_ACTIVE, false);
 | |
| 		}
 | |
| 
 | |
| 		if (check_eoi && chip->irq_eoi && irqd_irq_inprogress(&desc->irq_data))
 | |
| 			chip->irq_eoi(&desc->irq_data);
 | |
| 
 | |
| 		irq_shutdown(desc);
 | |
| 	}
 | |
| }
 |