diff mbox

input: matrix_keypad: Prevent screaming interrupts on close or suspend

Message ID 20180312225147.okm6w3gzaq7yt6ur@xylophone.i.decadent.org.uk (mailing list archive)
State New, archived
Headers show

Commit Message

Ben Hutchings March 12, 2018, 10:51 p.m. UTC
matrix_keypad_stop() now serialises with matrix_keypad_interrupt()
using spin_lock_irq() while setting the "stopped" flag, but it drops
the lock before disabling the interrupt sources.  If an interrupt is
asserted at this point, matrix_keypad_interrupt() will ignore it
because stopped is true.  The interrupt will remain asserted and it
may be called repeatedly, potentially leading to a hard lockup on a UP
system.

Instead, matrix_keypad_stop() should disable the interrupt sources
(asynchronously) while still holding the lock.  However, we have to
take care because at this point they may already be disabled due to a
pending scan.  To avoid mismatched enable/disable calls, we should
maintain the invariant that they are enabled if and only if
!(scan_pending || stopped).  Therefore:

- Set the scan_pending flag in matrix_keypad_start(), since it does
  schedule a scan and doesn't enable interrupts
- Only disable interrupts in matrix_keypad_stop() if scan_pending is
  false
- Only enable interrupts in matrix_keypad_scan() if stopped is false

Cc: stable@vger.kernel.org
Signed-off-by: Ben Hutchings <ben.hutchings@codethink.co.uk>
---
This is untested and completely based on code review.  It's possible
that the problem I identified can't really happen for reasons that
aren't obvious to me.

Ben.

 drivers/input/keyboard/matrix_keypad.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

Comments

Dmitry Torokhov March 13, 2018, 6:09 p.m. UTC | #1
On Mon, Mar 12, 2018 at 10:51:48PM +0000, Ben Hutchings wrote:
> matrix_keypad_stop() now serialises with matrix_keypad_interrupt()
> using spin_lock_irq() while setting the "stopped" flag, but it drops
> the lock before disabling the interrupt sources.  If an interrupt is
> asserted at this point, matrix_keypad_interrupt() will ignore it
> because stopped is true.  The interrupt will remain asserted and it
> may be called repeatedly, potentially leading to a hard lockup on a UP
> system.

No, it will not. We acknowledge interrupt (return IRQ_HANDLED) even when
keypad is stopped, and because we are dealing with a bunch of GPIOs (and
not some controller that has internal state and would not deassert the
attention line until we read or otherwise clear the state) the
interrupts will not be re-assered (unless there is a keypress, but even
then we'll simply re-acknowledge interrupt again).

> 
> Instead, matrix_keypad_stop() should disable the interrupt sources
> (asynchronously) while still holding the lock.  However, we have to
> take care because at this point they may already be disabled due to a
> pending scan.  To avoid mismatched enable/disable calls, we should
> maintain the invariant that they are enabled if and only if
> !(scan_pending || stopped).  Therefore:
> 
> - Set the scan_pending flag in matrix_keypad_start(), since it does
>   schedule a scan and doesn't enable interrupts
> - Only disable interrupts in matrix_keypad_stop() if scan_pending is
>   false
> - Only enable interrupts in matrix_keypad_scan() if stopped is false
> 
> Cc: stable@vger.kernel.org
> Signed-off-by: Ben Hutchings <ben.hutchings@codethink.co.uk>
> ---
> This is untested and completely based on code review.  It's possible
> that the problem I identified can't really happen for reasons that
> aren't obvious to me.

In this case I'd appreciate if the patch was titled RFC instead of
preemptively tagging it for stable.

Thanks.
Ben Hutchings March 14, 2018, 4:17 p.m. UTC | #2
On Tue, 2018-03-13 at 11:09 -0700, Dmitry Torokhov wrote:
> On Mon, Mar 12, 2018 at 10:51:48PM +0000, Ben Hutchings wrote:
> > matrix_keypad_stop() now serialises with matrix_keypad_interrupt()
> > using spin_lock_irq() while setting the "stopped" flag, but it drops
> > the lock before disabling the interrupt sources.  If an interrupt is
> > asserted at this point, matrix_keypad_interrupt() will ignore it
> > because stopped is true.  The interrupt will remain asserted and it
> > may be called repeatedly, potentially leading to a hard lockup on a UP
> > system.
> 
> No, it will not. We acknowledge interrupt (return IRQ_HANDLED) even when
> keypad is stopped, and because we are dealing with a bunch of GPIOs (and
> not some controller that has internal state and would not deassert the
> attention line until we read or otherwise clear the state) the
> interrupts will not be re-assered (unless there is a keypress, but even
> then we'll simply re-acknowledge interrupt again).
[...]

Returning IRQ_HANDLED has no effect on the hardware; it's mostly used
for spurious interrupt detection.  But I think what you're saying that
these interrupts will always be edge-triggered, not level-triggered -
is that right?  In which case I agree that screaming interrupts won't
be a problem.

Ben.
Dmitry Torokhov March 14, 2018, 5:23 p.m. UTC | #3
On Wed, Mar 14, 2018 at 04:17:33PM +0000, Ben Hutchings wrote:
> On Tue, 2018-03-13 at 11:09 -0700, Dmitry Torokhov wrote:
> > On Mon, Mar 12, 2018 at 10:51:48PM +0000, Ben Hutchings wrote:
> > > matrix_keypad_stop() now serialises with matrix_keypad_interrupt()
> > > using spin_lock_irq() while setting the "stopped" flag, but it drops
> > > the lock before disabling the interrupt sources.  If an interrupt is
> > > asserted at this point, matrix_keypad_interrupt() will ignore it
> > > because stopped is true.  The interrupt will remain asserted and it
> > > may be called repeatedly, potentially leading to a hard lockup on a UP
> > > system.
> > 
> > No, it will not. We acknowledge interrupt (return IRQ_HANDLED) even when
> > keypad is stopped, and because we are dealing with a bunch of GPIOs (and
> > not some controller that has internal state and would not deassert the
> > attention line until we read or otherwise clear the state) the
> > interrupts will not be re-assered (unless there is a keypress, but even
> > then we'll simply re-acknowledge interrupt again).
> [...]
> 
> Returning IRQ_HANDLED has no effect on the hardware; it's mostly used
> for spurious interrupt detection.

Yes. You still need to acknowledge that interrupt was handled. I.e. if
the keypad driver was returning 0 (IRQ_NONE) while it is "stopped", that
would be wrong and the system could consider interrupt spurious one.

>  But I think what you're saying that
> these interrupts will always be edge-triggered, not level-triggered -
> is that right?  In which case I agree that screaming interrupts won't
> be a problem.

Yes, level-triggered interrupts are not usable whenever something is
based on GPIOs as while that "something" is active you'd have interrupt
constantly retriggered as soon as you done processing it.

We explicitly request interrupts as IRQG_TRIGGER_FALLING |
IRQF_TRIGGER_RISING in matrix-keypad driver.

Thanks.
Ben Hutchings March 14, 2018, 8:18 p.m. UTC | #4
On Wed, 2018-03-14 at 10:23 -0700, Dmitry Torokhov wrote:
[...]
> >  But I think what you're saying that
> > these interrupts will always be edge-triggered, not level-triggered -
> > is that right?  In which case I agree that screaming interrupts won't
> > be a problem.
> 
> Yes, level-triggered interrupts are not usable whenever something is
> based on GPIOs as while that "something" is active you'd have interrupt
> constantly retriggered as soon as you done processing it.
> 
> We explicitly request interrupts as IRQG_TRIGGER_FALLING |
> IRQF_TRIGGER_RISING in matrix-keypad driver.

This is not enforced in the clustered IRQ case.  Would it make sense
to combine those with the specified clustered_irq_flags, or to mention
this assumption in the struct matrix_keypad_platform_data doc-comment?

Ben.
diff mbox

Patch

diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c
index 41614c185918..e765950d3491 100644
--- a/drivers/input/keyboard/matrix_keypad.c
+++ b/drivers/input/keyboard/matrix_keypad.c
@@ -169,7 +169,8 @@  static void matrix_keypad_scan(struct work_struct *work)
 	/* Enable IRQs again */
 	spin_lock_irq(&keypad->lock);
 	keypad->scan_pending = false;
-	enable_row_irqs(keypad);
+	if (!keypad->stopped)
+		enable_row_irqs(keypad);
 	spin_unlock_irq(&keypad->lock);
 }
 
@@ -203,6 +204,7 @@  static int matrix_keypad_start(struct input_dev *dev)
 	struct matrix_keypad *keypad = input_get_drvdata(dev);
 
 	keypad->stopped = false;
+	keypad->scan_pending = true;
 	mb();
 
 	/*
@@ -220,14 +222,16 @@  static void matrix_keypad_stop(struct input_dev *dev)
 
 	spin_lock_irq(&keypad->lock);
 	keypad->stopped = true;
+	if (!keypad->scan_pending) {
+		/*
+		 * matrix_keypad_scan() will leave IRQs enabled;
+		 * we should disable them now.
+		 */
+		disable_row_irqs(keypad);
+	}
 	spin_unlock_irq(&keypad->lock);
 
 	flush_work(&keypad->work.work);
-	/*
-	 * matrix_keypad_scan() will leave IRQs enabled;
-	 * we should disable them now.
-	 */
-	disable_row_irqs(keypad);
 }
 
 #ifdef CONFIG_PM_SLEEP