diff mbox series

gpio: siox: indicate exclusive support of threaded IRQs

Message ID 20200804091603.541-1-a.fatoum@pengutronix.de
State New
Headers show
Series gpio: siox: indicate exclusive support of threaded IRQs | expand

Commit Message

Ahmad Fatoum Aug. 4, 2020, 9:16 a.m. UTC
Generic GPIO consumers like gpio-keys use request_any_context_irq()
to request a threaded handler if irq_settings_is_nested_thread() ==
true or a hardirq handler otherwise.

Drivers using handle_nested_irq() must be sure that the nested
IRQs were requested with threaded handlers, because the IRQ
is handled by calling action->thread_fn().

The gpio-siox driver dispatches IRQs via handle_nested_irq,
but has irq_settings_is_nested_thread() == false.

Set gpio_irq_chip::threaded to remedy this.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
I am writing a driver similar to gpio-siox and I ran into a null pointer
dereference, because ->threaded wasn't set. I didn't test this on actual
SIOX hardware.

This patch doesn't fix the case were are driver explicitly calls
request_irq and is combined with a driver that does handle_nested_irq.

Is there a flag, such drivers should additionally set or should we
check action->thread_fn before calling it inside handle_nested_irq?
---
 drivers/gpio/gpio-siox.c | 1 +
 1 file changed, 1 insertion(+)

Comments

Uwe Kleine-König Aug. 5, 2020, 6:17 a.m. UTC | #1
Hello,

[adding tglx for irq expertise to Cc]

On Tue, Aug 04, 2020 at 11:16:03AM +0200, Ahmad Fatoum wrote:
> Generic GPIO consumers like gpio-keys use request_any_context_irq()
> to request a threaded handler if irq_settings_is_nested_thread() ==
> true or a hardirq handler otherwise.
> 
> Drivers using handle_nested_irq() must be sure that the nested
> IRQs were requested with threaded handlers, because the IRQ
> is handled by calling action->thread_fn().
> 
> The gpio-siox driver dispatches IRQs via handle_nested_irq,
> but has irq_settings_is_nested_thread() == false.
> 
> Set gpio_irq_chip::threaded to remedy this.

Sounds reasonable, but I have to keep this for others to judge if this
is indeed how the irq stuff works.

> ---
> I am writing a driver similar to gpio-siox and I ran into a null pointer
> dereference, because ->threaded wasn't set. I didn't test this on actual
> SIOX hardware.
> 
> This patch doesn't fix the case were are driver explicitly calls
> request_irq and is combined with a driver that does handle_nested_irq.
> 
> Is there a flag, such drivers should additionally set or should we
> check action->thread_fn before calling it inside handle_nested_irq?

If gpio_irq_chip::threaded being set means that this problem happens
IMHO the driver is fine and something in the generic gpio code should do
the right thing here.

Best regards and thanks for caring,
Uwe

> ---
>  drivers/gpio/gpio-siox.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/gpio/gpio-siox.c b/drivers/gpio/gpio-siox.c
> index 26e1fe092304..f8c5e9fc4bac 100644
> --- a/drivers/gpio/gpio-siox.c
> +++ b/drivers/gpio/gpio-siox.c
> @@ -245,6 +245,7 @@ static int gpio_siox_probe(struct siox_device *sdevice)
>  	girq->chip = &ddata->ichip;
>  	girq->default_type = IRQ_TYPE_NONE;
>  	girq->handler = handle_level_irq;
> +	girq->threaded = true;
>  
>  	ret = devm_gpiochip_add_data(dev, &ddata->gchip, NULL);
>  	if (ret)
> -- 
> 2.28.0
> 
> 
>
Thomas Gleixner Aug. 6, 2020, 10:20 a.m. UTC | #2
Uwe Kleine-König <u.kleine-koenig@pengutronix.de> writes:
> On Tue, Aug 04, 2020 at 11:16:03AM +0200, Ahmad Fatoum wrote:
>> Generic GPIO consumers like gpio-keys use request_any_context_irq()
>> to request a threaded handler if irq_settings_is_nested_thread() ==
>> true or a hardirq handler otherwise.
>> 
>> Drivers using handle_nested_irq() must be sure that the nested
>> IRQs were requested with threaded handlers, because the IRQ
>> is handled by calling action->thread_fn().
>> 
>> The gpio-siox driver dispatches IRQs via handle_nested_irq,
>> but has irq_settings_is_nested_thread() == false.
>> 
>> Set gpio_irq_chip::threaded to remedy this.
>
> Sounds reasonable, but I have to keep this for others to judge if this
> is indeed how the irq stuff works.

handle_nested_irq() documentation clearly says: "Handle a nested irq
from a irq thread". As a consequence the caller of this function must
run in an interrupt thread. This is an optimization to spare tons of
interrupt threads and context switches.

So the solution for this driver is either to make the dispatch handler
threaded or use the hard interrupt variant of dispatching the
demultiplexed GPIO interrupts.

Thanks,

        tglx
Linus Walleij Aug. 6, 2020, 1:32 p.m. UTC | #3
On Thu, Aug 6, 2020 at 12:20 PM Thomas Gleixner <tglx@linutronix.de> wrote:

> So the solution for this driver is either to make the dispatch handler
> threaded or use the hard interrupt variant of dispatching the
> demultiplexed GPIO interrupts.

The struct gpio_irq_chip .threaded bool that the patch
sets just instructs the gpio core to issue
irq_set_nested_thread(irq, 1) on the child IRQ.

This is a driver of type "struct siox_driver" handling the
IRQ through the special .get_data callback supplied in the
driver struct and it calls handle_nested_irq(irq) so with
this fix it percolated up to the parent as intended.

So far so good. So I think the patch should be applied.

But what is behind this .get_data() callback for siox drivers?

The siox driver framework in drivers/siox dispatches calls
to .get_data() from a polling thread which is just some ordinary
kthread. It looks like this because the SIOX (I think) needs
to do polled I/O. (drivers/siox/siox-core.c)

So this is a thread but it is not an irq thread from the irq core,
however it is treated like such by the driver, and in a way what
happens is events, just polled by a thread.

So when we call handle_nested_irq() ... we are not really
calling that from an irq handler.

I am just very confused :D

But Uwe must have designed this thread to mimic IRQs
specifically? (Uwe?)

I don't know if the IRQ core even sees a difference between which
thread it gets interfaced with. I suppose it does? :/

Yours,
Linus Walleij
Uwe Kleine-König Aug. 6, 2020, 3:07 p.m. UTC | #4
On Thu, Aug 06, 2020 at 12:20:52PM +0200, Thomas Gleixner wrote:
> Uwe Kleine-König <u.kleine-koenig@pengutronix.de> writes:
> > On Tue, Aug 04, 2020 at 11:16:03AM +0200, Ahmad Fatoum wrote:
> >> Generic GPIO consumers like gpio-keys use request_any_context_irq()
> >> to request a threaded handler if irq_settings_is_nested_thread() ==
> >> true or a hardirq handler otherwise.
> >> 
> >> Drivers using handle_nested_irq() must be sure that the nested
> >> IRQs were requested with threaded handlers, because the IRQ
> >> is handled by calling action->thread_fn().
> >> 
> >> The gpio-siox driver dispatches IRQs via handle_nested_irq,
> >> but has irq_settings_is_nested_thread() == false.
> >> 
> >> Set gpio_irq_chip::threaded to remedy this.
> >
> > Sounds reasonable, but I have to keep this for others to judge if this
> > is indeed how the irq stuff works.
> 
> handle_nested_irq() documentation clearly says: "Handle a nested irq
> from a irq thread". As a consequence the caller of this function must
> run in an interrupt thread. This is an optimization to spare tons of
> interrupt threads and context switches.
> 
> So the solution for this driver is either to make the dispatch handler
> threaded or use the hard interrupt variant of dispatching the
> demultiplexed GPIO interrupts.

The action item isn't entirely clear for me. There is no "parent" irq, I
have for siox a kthread that does some IO and looks that the resulting
data which effectively reports the current state of the GPIO line. And
if this GPIO is configured to trigger an irq and the matching transition
(or state) is seen, I want to trigger the irq action. So the caller has
neither hard nor threaded irq context.

Best regards
Uwe
Thomas Gleixner Aug. 6, 2020, 6:50 p.m. UTC | #5
Linus Walleij <linus.walleij@linaro.org> writes:
> On Thu, Aug 6, 2020 at 12:20 PM Thomas Gleixner <tglx@linutronix.de> wrote:
>
>> So the solution for this driver is either to make the dispatch handler
>> threaded or use the hard interrupt variant of dispatching the
>> demultiplexed GPIO interrupts.
>
> The struct gpio_irq_chip .threaded bool that the patch
> sets just instructs the gpio core to issue
> irq_set_nested_thread(irq, 1) on the child IRQ.
>
> This is a driver of type "struct siox_driver" handling the
> IRQ through the special .get_data callback supplied in the
> driver struct and it calls handle_nested_irq(irq) so with
> this fix it percolated up to the parent as intended.
>
> So far so good. So I think the patch should be applied.
>
> But what is behind this .get_data() callback for siox drivers?
>
> The siox driver framework in drivers/siox dispatches calls
> to .get_data() from a polling thread which is just some ordinary
> kthread. It looks like this because the SIOX (I think) needs
> to do polled I/O. (drivers/siox/siox-core.c)
>
> So this is a thread but it is not an irq thread from the irq core,
> however it is treated like such by the driver, and in a way what
> happens is events, just polled by a thread.

As Uwe just explained.

> So when we call handle_nested_irq() ... we are not really
> calling that from an irq handler.
>
> I don't know if the IRQ core even sees a difference between which
> thread it gets interfaced with. I suppose it does? :/

handle_nested_irq() does not care. It cares about thread context,
external reentrancy protection for the same nested interrupt and that
the nested interrupt has a thread handler.

The latter is what goes belly up because w/o that threaded bit set the
GPIO core fails to set nested thread. So if a consumer requests an
interrupt with request_any_context_irq() then that fails to select
thread mode which means the threaded handler is not set causing
handle_nested_irq() to fail.

The polling kthread is a slight but clever abomination, but it just
works because it provides thread context and cannot run concurrently.

So Ahmad's patch is correct, just the changelog needs polishing.

Thanks,

        tglx
Uwe Kleine-König Aug. 6, 2020, 7:46 p.m. UTC | #6
On Thu, Aug 06, 2020 at 08:50:45PM +0200, Thomas Gleixner wrote:
> Linus Walleij <linus.walleij@linaro.org> writes:
> > On Thu, Aug 6, 2020 at 12:20 PM Thomas Gleixner <tglx@linutronix.de> wrote:
> >
> >> So the solution for this driver is either to make the dispatch handler
> >> threaded or use the hard interrupt variant of dispatching the
> >> demultiplexed GPIO interrupts.
> >
> > The struct gpio_irq_chip .threaded bool that the patch
> > sets just instructs the gpio core to issue
> > irq_set_nested_thread(irq, 1) on the child IRQ.
> >
> > This is a driver of type "struct siox_driver" handling the
> > IRQ through the special .get_data callback supplied in the
> > driver struct and it calls handle_nested_irq(irq) so with
> > this fix it percolated up to the parent as intended.
> >
> > So far so good. So I think the patch should be applied.
> >
> > But what is behind this .get_data() callback for siox drivers?
> >
> > The siox driver framework in drivers/siox dispatches calls
> > to .get_data() from a polling thread which is just some ordinary
> > kthread. It looks like this because the SIOX (I think) needs
> > to do polled I/O. (drivers/siox/siox-core.c)
> >
> > So this is a thread but it is not an irq thread from the irq core,
> > however it is treated like such by the driver, and in a way what
> > happens is events, just polled by a thread.
> 
> As Uwe just explained.
> 
> > So when we call handle_nested_irq() ... we are not really
> > calling that from an irq handler.
> >
> > I don't know if the IRQ core even sees a difference between which
> > thread it gets interfaced with. I suppose it does? :/
> 
> handle_nested_irq() does not care. It cares about thread context,
> external reentrancy protection for the same nested interrupt and that
> the nested interrupt has a thread handler.
> 
> The latter is what goes belly up because w/o that threaded bit set the
> GPIO core fails to set nested thread. So if a consumer requests an
> interrupt with request_any_context_irq() then that fails to select
> thread mode which means the threaded handler is not set causing
> handle_nested_irq() to fail.

For a caller of request_threaded_irq() that passes a relevant hardirq
handler the hardirq handler is never called but request_threaded_irq()
doesn't fail. The handler is just replaced by irq_nested_primary_handler
in __setup_irq(). Is that a bug? (I didn't test, just read the code, so I
might have missed something.) 

> The polling kthread is a slight but clever abomination, but it just
> works because it provides thread context and cannot run concurrently.

I think this is the first time you called any of my code "clever" :-)
 
> So Ahmad's patch is correct, just the changelog needs polishing.

Trying to be constructive, here is my suggested changelog:

	gpio: siox: explicitly only support threaded irqs

	The gpio-siox driver uses handle_nested_irq() to implement its
	interrupt support. This is only capable to handle threaded irq
	actions. For a hardirq action it triggers a NULL pointer oops.
	(It calls action->thread_fn which is NULL then.)

	So prevent registration of a hardirq action by setting
	gpio_irq_chip::threaded to true.

Does this address all your concerns?

Is this bad enough to justify sending this patch to stable?

Best regards
Uwe
Thomas Gleixner Aug. 6, 2020, 8:33 p.m. UTC | #7
Uwe Kleine-König <u.kleine-koenig@pengutronix.de> writes:
> On Thu, Aug 06, 2020 at 08:50:45PM +0200, Thomas Gleixner wrote:
>> handle_nested_irq() does not care. It cares about thread context,
>> external reentrancy protection for the same nested interrupt and that
>> the nested interrupt has a thread handler.
>> 
>> The latter is what goes belly up because w/o that threaded bit set the
>> GPIO core fails to set nested thread. So if a consumer requests an
>> interrupt with request_any_context_irq() then that fails to select
>> thread mode which means the threaded handler is not set causing
>> handle_nested_irq() to fail.
>
> For a caller of request_threaded_irq() that passes a relevant hardirq
> handler the hardirq handler is never called but request_threaded_irq()
> doesn't fail. The handler is just replaced by irq_nested_primary_handler
> in __setup_irq(). Is that a bug? (I didn't test, just read the code, so I
> might have missed something.)

Depends on what the threaded handler expects what the primary handler
has done. It might just work or not :)

> Trying to be constructive, here is my suggested changelog:
>
> 	gpio: siox: explicitly only support threaded irqs
>
> 	The gpio-siox driver uses handle_nested_irq() to implement its
> 	interrupt support. This is only capable to handle threaded irq
> 	actions. For a hardirq action it triggers a NULL pointer oops.
> 	(It calls action->thread_fn which is NULL then.)
>
> 	So prevent registration of a hardirq action by setting
> 	gpio_irq_chip::threaded to true.
>
> Does this address all your concerns?

LGTM

> Is this bad enough to justify sending this patch to stable?

Yes, a Cc: stable and a Fixes: tag is justified.

Thanks,

        tglx
Uwe Kleine-König Aug. 6, 2020, 9:07 p.m. UTC | #8
Hello Thomas,

On Thu, Aug 06, 2020 at 10:33:06PM +0200, Thomas Gleixner wrote:
> Uwe Kleine-König <u.kleine-koenig@pengutronix.de> writes:
> > On Thu, Aug 06, 2020 at 08:50:45PM +0200, Thomas Gleixner wrote:
> >> handle_nested_irq() does not care. It cares about thread context,
> >> external reentrancy protection for the same nested interrupt and that
> >> the nested interrupt has a thread handler.
> >> 
> >> The latter is what goes belly up because w/o that threaded bit set the
> >> GPIO core fails to set nested thread. So if a consumer requests an
> >> interrupt with request_any_context_irq() then that fails to select
> >> thread mode which means the threaded handler is not set causing
> >> handle_nested_irq() to fail.
> >
> > For a caller of request_threaded_irq() that passes a relevant hardirq
> > handler the hardirq handler is never called but request_threaded_irq()
> > doesn't fail. The handler is just replaced by irq_nested_primary_handler
> > in __setup_irq(). Is that a bug? (I didn't test, just read the code, so I
> > might have missed something.)
> 
> Depends on what the threaded handler expects what the primary handler
> has done. It might just work or not :)

So we need something like:

diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 48c38e09c673..31777a0b79df 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -1393,12 +1393,18 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
 			ret = -EINVAL;
 			goto out_mput;
 		}
-		/*
-		 * Replace the primary handler which was provided from
-		 * the driver for non nested interrupt handling by the
-		 * dummy function which warns when called.
-		 */
-		new->handler = irq_nested_primary_handler;
+
+		if (new->handler == NULL) {
+			/* Scream loud if the primary handler gets called */
+			new->handler = irq_nested_primary_handler;
+		} else {
+			/*
+			 * The handler won't be called as the requestor expects,
+			 * so refuse to install the handler
+			 */
+			ret = -EINVAL;
+			goto out_mput;
+		}
 	} else {
 		if (irq_settings_can_thread(desc)) {
 			ret = irq_setup_forced_threading(new);

? Do we need to care for other allowed values of new->handler? Maybe
irq_default_primary_handler?

> > Is this bad enough to justify sending this patch to stable?
> 
> Yes, a Cc: stable and a Fixes: tag is justified.

That would be

Fixes: be8c8facc707 ("gpio: new driver to work with a 8x12 siox")

Best regards
Uwe
Uwe Kleine-König Aug. 10, 2020, 1:33 p.m. UTC | #9
On Thu, Aug 06, 2020 at 11:07:09PM +0200, Uwe Kleine-König wrote:
> Hello Thomas,
> 
> On Thu, Aug 06, 2020 at 10:33:06PM +0200, Thomas Gleixner wrote:
> > Uwe Kleine-König <u.kleine-koenig@pengutronix.de> writes:
> > > On Thu, Aug 06, 2020 at 08:50:45PM +0200, Thomas Gleixner wrote:
> > >> handle_nested_irq() does not care. It cares about thread context,
> > >> external reentrancy protection for the same nested interrupt and that
> > >> the nested interrupt has a thread handler.
> > >> 
> > >> The latter is what goes belly up because w/o that threaded bit set the
> > >> GPIO core fails to set nested thread. So if a consumer requests an
> > >> interrupt with request_any_context_irq() then that fails to select
> > >> thread mode which means the threaded handler is not set causing
> > >> handle_nested_irq() to fail.
> > >
> > > For a caller of request_threaded_irq() that passes a relevant hardirq
> > > handler the hardirq handler is never called but request_threaded_irq()
> > > doesn't fail. The handler is just replaced by irq_nested_primary_handler
> > > in __setup_irq(). Is that a bug? (I didn't test, just read the code, so I
> > > might have missed something.)
> > 
> > Depends on what the threaded handler expects what the primary handler
> > has done. It might just work or not :)
> 
> So we need something like:
> 
> diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
> index 48c38e09c673..31777a0b79df 100644
> --- a/kernel/irq/manage.c
> +++ b/kernel/irq/manage.c
> @@ -1393,12 +1393,18 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
>  			ret = -EINVAL;
>  			goto out_mput;
>  		}
> -		/*
> -		 * Replace the primary handler which was provided from
> -		 * the driver for non nested interrupt handling by the
> -		 * dummy function which warns when called.
> -		 */
> -		new->handler = irq_nested_primary_handler;
> +
> +		if (new->handler == NULL) {
> +			/* Scream loud if the primary handler gets called */
> +			new->handler = irq_nested_primary_handler;
> +		} else {
> +			/*
> +			 * The handler won't be called as the requestor expects,
> +			 * so refuse to install the handler
> +			 */
> +			ret = -EINVAL;
> +			goto out_mput;
> +		}
>  	} else {
>  		if (irq_settings_can_thread(desc)) {
>  			ret = irq_setup_forced_threading(new);
> 

The siox stuff is used at Eckelmann (i.e. probably the only siox user)
via /dev/gpiochip%d. The code providing this device uses
request_threaded_irq(), so that's why we didn't run into the oops. That
the primary handler might not run was noticed already and cared for in
commit 1033be58992f ("gpiolib: fix line event timestamps for nested
irqs").

I grepped around a bit and I think most other drivers depend on their
primary handler being called. (Some primary handlers disable and/or mask
the irq[1], this is wrong, isn't it?)

So I really think request_threaded_irq should not silently rop the
primary handler on the floor.

Best regards
Uwe

[1] I saw:
 - arch/mips/alchemy/devboards/db1200.c
 - drivers/crypto/hisilicon/sec/sec_drv.c
 - drivers/crypto/stm32/stm32-hash.c
 - drivers/dma/idxd/init.c
Linus Walleij Aug. 27, 2020, 10:55 p.m. UTC | #10
Hi Ahmad,

On Tue, Aug 4, 2020 at 11:18 AM Ahmad Fatoum <a.fatoum@pengutronix.de> wrote:

> Generic GPIO consumers like gpio-keys use request_any_context_irq()
> to request a threaded handler if irq_settings_is_nested_thread() ==
> true or a hardirq handler otherwise.
>
> Drivers using handle_nested_irq() must be sure that the nested
> IRQs were requested with threaded handlers, because the IRQ
> is handled by calling action->thread_fn().
>
> The gpio-siox driver dispatches IRQs via handle_nested_irq,
> but has irq_settings_is_nested_thread() == false.
>
> Set gpio_irq_chip::threaded to remedy this.
>
> Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>

I think we concluded we want to apply this patch, but with
a fixed commit text, can you send a V2? (Or ask Uwe if he wants
to pick it up and write the text.)

Yours,
Linus Walleij
Ahmad Fatoum Sept. 7, 2020, 3:33 p.m. UTC | #11
Hello Linus,

On 8/28/20 12:55 AM, Linus Walleij wrote:
> Hi Ahmad,
> 
> On Tue, Aug 4, 2020 at 11:18 AM Ahmad Fatoum <a.fatoum@pengutronix.de> wrote:
> 
>> Generic GPIO consumers like gpio-keys use request_any_context_irq()
>> to request a threaded handler if irq_settings_is_nested_thread() ==
>> true or a hardirq handler otherwise.
>>
>> Drivers using handle_nested_irq() must be sure that the nested
>> IRQs were requested with threaded handlers, because the IRQ
>> is handled by calling action->thread_fn().
>>
>> The gpio-siox driver dispatches IRQs via handle_nested_irq,
>> but has irq_settings_is_nested_thread() == false.
>>
>> Set gpio_irq_chip::threaded to remedy this.
>>
>> Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
> 
> I think we concluded we want to apply this patch, but with
> a fixed commit text, can you send a V2? (Or ask Uwe if he wants
> to pick it up and write the text.)

Thanks for the reminder. This slipped through.
I just sent out v2 (<20200907153135.3307-1-a.fatoum@pengutronix.de>)

Cheers,
Ahmad

> 
> Yours,
> Linus Walleij
>
diff mbox series

Patch

diff --git a/drivers/gpio/gpio-siox.c b/drivers/gpio/gpio-siox.c
index 26e1fe092304..f8c5e9fc4bac 100644
--- a/drivers/gpio/gpio-siox.c
+++ b/drivers/gpio/gpio-siox.c
@@ -245,6 +245,7 @@  static int gpio_siox_probe(struct siox_device *sdevice)
 	girq->chip = &ddata->ichip;
 	girq->default_type = IRQ_TYPE_NONE;
 	girq->handler = handle_level_irq;
+	girq->threaded = true;
 
 	ret = devm_gpiochip_add_data(dev, &ddata->gchip, NULL);
 	if (ret)