Message ID | 1598113021-4149-4-git-send-email-mkshah@codeaurora.org |
---|---|
State | New |
Headers | show |
Series | irqchip: qcom: pdc: Introduce irq_set_wake call | expand |
Hi, On Tue, Sep 1, 2020 at 2:51 AM Thomas Gleixner <tglx@linutronix.de> wrote: > > On Mon, Aug 31 2020 at 08:12, Doug Anderson wrote: > > On Wed, Aug 26, 2020 at 3:15 AM Thomas Gleixner <tglx@linutronix.de> wrote: > >> There are two reasonable choices here: > >> > >> 1) Do the symmetric thing > >> > >> 2) Let the drivers call a new function disable_wakeup_irq_for_suspend() > >> which marks the interrupt to be enabled from the core on suspend and > >> remove the enable call on the resume callback of the driver. > >> > >> Then you don't need the resume part in the core and state still is > >> consistent. > >> > >> I'm leaning towards #2 because that makes a lot of sense. > > > > IIUC, #2 requires that we change existing drivers that are currently > > using disable_irq() + enable_irq_wake(), right? Presumably, if we're > > going to do #2, we should declare that what drivers used to do is now > > considered illegal, right? Perhaps we could detect that and throw a > > warning so that they know that they need to change to use the new > > disable_wakeup_irq_for_suspend() API. Otherwise these drivers will > > work fine on some systems (like they always have) but will fail in > > weird corner cases for systems that are relying on drivers to call > > disable_wakeup_irq_for_suspend(). That doesn't sound super great to > > me... > > Hmm. With disable_irq() + enable_irq_wake() in the driver suspend path > the driver already makes an implicit assumption about the underlying irq > chip functionality, i.e. it expects that even with the interrupt > disabled the irq chip can wake up the system. > > Now with the new flag magic and #1 we are just working around the driver > assumptions at the interrupt chip level. > > That's inconsistent at best. Sure, though I will say that it works on all Chromebooks we've shipped over the last ~9 years since the main cros_ec (EC = embedded controller) driver does this. Of course, it's easy to just change that driver. I just don't want everything else breaking too. > How many drivers are doing that sequence? I remember looking this up before but can't find it. It's gonna be hard to get an exact count without fancier searching, but we should be able to find a few... I'll just do the simple: git grep -C10 enable_irq_wake | grep -C10 'disable_irq(' That might miss people but it'll catch quite a few. Ones that are clearly using something like this: drivers/input/keyboard/adp5588-keys.c drivers/input/keyboard/adp5589-keys.c drivers/input/mouse/elan_i2c_core.c drivers/input/rmi4/rmi_driver.c drivers/input/touchscreen/elants_i2c.c drivers/input/touchscreen/raydium_i2c_ts.c drivers/mfd/as3722.c drivers/mfd/max14577.c (*) drivers/mfd/max77693.c drivers/mfd/max77843.c drivers/mfd/sec-core.c (*) drivers/mfd/twl6030-irq.c drivers/platform/chrome/cros_ec.c drivers/power/supply/max17042_battery.c drivers/rtc/rtc-st-lpc.c (*) Even has a comment explaining why! Input is perhaps over-represented but presumably that's because input is often the thing that wakes devices up. ;-) > And the more important > question is why are they calling disable_irq() in the first place if > they want to be woken up by that interrupt. I tried to put my thoughts back in: https://lore.kernel.org/r/CAD=FV=WN4R1tS47ZzdZa_hsbvLifwnv6rgETVaiea0+QSZmiOw@mail.gmail.com/ ...but that was a long thread. Copied the relevant bits here. Basically a driver that calls disablre_irq() together with enable_irq_wake() is trying to say: * Don't call the interrupt handler for this interrupt until I call enable_irq() but keep tracking it (either in hardware or in software). Specifically it's a requirement that if the interrupt fires one or more times while masked the interrupt handler should be called as soon as enable_irq() is called. * If this interrupt fires while the system is suspended then please wake the system up. Specifically I think it gets back to the idea that, from a device driver's point of view, there isn't a separate concept of disabling an IRQ (turn it off and stop tracking it) and masking an IRQ (keep track of it but don't call my handler until I unmask). As I understand it drivers expect that the disable_irq() call is actually a mask and that an IRQ is never fully disabled unless released by the driver. It is a little unfortunate (IMO) that the function is called disable_irq() but as far as I understand that's historical. > The point is that the core suspend code disables all interrupts which > are not marked as wakeup enabled automatically and reenables them after > resume. So why would any driver invoke disable_irq() in the suspend > function at all? Historical raisins? One case I can imagine: pretend that there are two power rails controlling a device. One power rail controls the communication channel between the CPU and the peripheral and the other power rail controls whether the peripheral is on. At suspend time we want to keep the peripheral on but we can shut down the power to the communication channel. One way you could do this is at suspend time: disable_irq() turn_off_comm_power() enable_irq_wake() You'd do the disable_irq() (AKA mask your interrupt) because you'd really want to make sure that your handler isn't called after you turned off the communication power. You want to leave the interrupt pending/masked until you are able to turn the communications channel back on and then you can query why the wakeup happened. Now, admittedly, you could redesign the above driver to work any number of different ways. Maybe you could use the "noirq" suspend to turn off your comm power or maybe you could come up with another solution. However, since the above has always worked and is quite simple I guess that's what drivers use? -Doug
On Wed, Sep 02 2020 at 13:26, Doug Anderson wrote: > Specifically I think it gets back to the idea that, from a device > driver's point of view, there isn't a separate concept of disabling an > IRQ (turn it off and stop tracking it) and masking an IRQ (keep track > of it but don't call my handler until I unmask). As I understand it > drivers expect that the disable_irq() call is actually a mask and that > an IRQ is never fully disabled unless released by the driver. It is a > little unfortunate (IMO) that the function is called disable_irq() but > as far as I understand that's historical. Yes, the naming is historical but it always meant: Don't invoke an interrupt handler. Whether that's achieved by actually masking it at the interrupt chip level in hardware or by software state in the core does not matter from the driver perspective. >> The point is that the core suspend code disables all interrupts which >> are not marked as wakeup enabled automatically and reenables them after >> resume. So why would any driver invoke disable_irq() in the suspend >> function at all? Historical raisins? > > One case I can imagine: pretend that there are two power rails > controlling a device. One power rail controls the communication > channel between the CPU and the peripheral and the other power rail > controls whether the peripheral is on. At suspend time we want to > keep the peripheral on but we can shut down the power to the > communication channel. > > One way you could do this is at suspend time: > disable_irq() > turn_off_comm_power() > enable_irq_wake() > > You'd do the disable_irq() (AKA mask your interrupt) because you'd > really want to make sure that your handler isn't called after you > turned off the communication power. You want to leave the interrupt > pending/masked until you are able to turn the communications channel > back on and then you can query why the wakeup happened. Ok. > Now, admittedly, you could redesign the above driver to work any > number of different ways. Maybe you could use the "noirq" suspend to > turn off your comm power or maybe you could come up with another > solution. However, since the above has always worked and is quite > simple I guess that's what drivers use? That comm power case is a reasonable argument for having that sequence. So we need to make sure that the underlying interrupt chips do the right thing. We have the following two cases: 1) irq chip does not have a irq_disable() callback and does not have IRQ_DISABLE_UNLAZY set In that case the interrupt is not masked at the hardware level. It's just software state. If the interrupt fires while disabled it is marked pending and actually masked at the hardware level. Actually there is a race condition which is not handled: disable_irq() ... interrupt fires mask and mark pending .... suspend_device_irq() if (wakeup source) { set_state(WAKEUP ARMED); return; } That pending interrupt will not prevent the machine from going into suspend and if it's an edge interrupt then an unmask in suspend_device_irq() won't help. Edge interrupts are not resent in hardware. They are fire and forget from the POV of the device hardware. 2) irq chip has a irq_disable() callback or has IRQ_DISABLE_UNLAZY set In that case disable_irq() will mask it at the hardware level and it stays that way until enable_irq() is invoked. #1 kinda works and the gap is reasonably trivial to fix in suspend_device_irq() by checking the pending state and telling the PM core that there is a wakeup pending. #2 Needs an indication from the chip flags that an interrupt which is masked has to be unmasked when it is a enabled wakeup source. I assume your problem is #2, right? If it's #1 then UNMASK_IF_WAKEUP is the wrong answer. Thanks, tglx
diff --git a/include/linux/irq.h b/include/linux/irq.h index 1b7f4df..752eb9a 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -545,27 +545,30 @@ struct irq_chip { /* * irq_chip specific flags * - * IRQCHIP_SET_TYPE_MASKED: Mask before calling chip.irq_set_type() - * IRQCHIP_EOI_IF_HANDLED: Only issue irq_eoi() when irq was handled - * IRQCHIP_MASK_ON_SUSPEND: Mask non wake irqs in the suspend path - * IRQCHIP_ONOFFLINE_ENABLED: Only call irq_on/off_line callbacks - * when irq enabled - * IRQCHIP_SKIP_SET_WAKE: Skip chip.irq_set_wake(), for this irq chip - * IRQCHIP_ONESHOT_SAFE: One shot does not require mask/unmask - * IRQCHIP_EOI_THREADED: Chip requires eoi() on unmask in threaded mode - * IRQCHIP_SUPPORTS_LEVEL_MSI Chip can provide two doorbells for Level MSIs - * IRQCHIP_SUPPORTS_NMI: Chip can deliver NMIs, only for root irqchips + * IRQCHIP_SET_TYPE_MASKED: Mask before calling chip.irq_set_type() + * IRQCHIP_EOI_IF_HANDLED: Only issue irq_eoi() when irq was handled + * IRQCHIP_MASK_ON_SUSPEND: Mask non wake irqs in the suspend path + * IRQCHIP_ONOFFLINE_ENABLED: Only call irq_on/off_line callbacks + * when irq enabled + * IRQCHIP_SKIP_SET_WAKE: Skip chip.irq_set_wake(), for this irq chip + * IRQCHIP_ONESHOT_SAFE: One shot does not require mask/unmask + * IRQCHIP_EOI_THREADED: Chip requires eoi() on unmask in threaded mode + * IRQCHIP_SUPPORTS_LEVEL_MSI: Chip can provide two doorbells for Level MSIs + * IRQCHIP_SUPPORTS_NMI: Chip can deliver NMIs, only for root irqchips + * IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND: Invoke .irq_enable/.irq_unmask for wake irqs + * in the suspend path */ enum { - IRQCHIP_SET_TYPE_MASKED = (1 << 0), - IRQCHIP_EOI_IF_HANDLED = (1 << 1), - IRQCHIP_MASK_ON_SUSPEND = (1 << 2), - IRQCHIP_ONOFFLINE_ENABLED = (1 << 3), - IRQCHIP_SKIP_SET_WAKE = (1 << 4), - IRQCHIP_ONESHOT_SAFE = (1 << 5), - IRQCHIP_EOI_THREADED = (1 << 6), - IRQCHIP_SUPPORTS_LEVEL_MSI = (1 << 7), - IRQCHIP_SUPPORTS_NMI = (1 << 8), + IRQCHIP_SET_TYPE_MASKED = (1 << 0), + IRQCHIP_EOI_IF_HANDLED = (1 << 1), + IRQCHIP_MASK_ON_SUSPEND = (1 << 2), + IRQCHIP_ONOFFLINE_ENABLED = (1 << 3), + IRQCHIP_SKIP_SET_WAKE = (1 << 4), + IRQCHIP_ONESHOT_SAFE = (1 << 5), + IRQCHIP_EOI_THREADED = (1 << 6), + IRQCHIP_SUPPORTS_LEVEL_MSI = (1 << 7), + IRQCHIP_SUPPORTS_NMI = (1 << 8), + IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND = (1 << 9), }; #include <linux/irqdesc.h> diff --git a/kernel/irq/debugfs.c b/kernel/irq/debugfs.c index b95ff5d..ab4f637 100644 --- a/kernel/irq/debugfs.c +++ b/kernel/irq/debugfs.c @@ -57,6 +57,7 @@ static const struct irq_bit_descr irqchip_flags[] = { BIT_MASK_DESCR(IRQCHIP_EOI_THREADED), BIT_MASK_DESCR(IRQCHIP_SUPPORTS_LEVEL_MSI), BIT_MASK_DESCR(IRQCHIP_SUPPORTS_NMI), + BIT_MASK_DESCR(IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND), }; static void diff --git a/kernel/irq/pm.c b/kernel/irq/pm.c index c6c7e18..2cc800b 100644 --- a/kernel/irq/pm.c +++ b/kernel/irq/pm.c @@ -69,12 +69,17 @@ void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) static bool suspend_device_irq(struct irq_desc *desc) { + unsigned long chipflags = irq_desc_get_chip(desc)->flags; + if (!desc->action || irq_desc_is_chained(desc) || desc->no_suspend_depth) return false; if (irqd_is_wakeup_set(&desc->irq_data)) { irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); + + if (chipflags & IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND) + irq_enable(desc); /* * We return true here to force the caller to issue * synchronize_irq(). We need to make sure that the @@ -93,7 +98,7 @@ static bool suspend_device_irq(struct irq_desc *desc) * chip level. The chip implementation indicates that with * IRQCHIP_MASK_ON_SUSPEND. */ - if (irq_desc_get_chip(desc)->flags & IRQCHIP_MASK_ON_SUSPEND) + if (chipflags & IRQCHIP_MASK_ON_SUSPEND) mask_irq(desc); return true; }
An interrupt that is disabled/masked but set for wakeup still needs to be able to wake up the system from sleep states like "suspend to RAM". Introduce IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND flag. If this flag is set wake irqs will get enabled/unmasked on suspend entry by invoking .irq_enable/.irq_unmask callback of irqchip. Suggested-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Maulik Shah <mkshah@codeaurora.org> --- include/linux/irq.h | 41 ++++++++++++++++++++++------------------- kernel/irq/debugfs.c | 1 + kernel/irq/pm.c | 7 ++++++- 3 files changed, 29 insertions(+), 20 deletions(-)