diff mbox series

[v2,6/9] Input: iqs5xx - prevent interrupt storm during removal

Message ID 20210313191236.4366-7-jeff@labundy.com (mailing list archive)
State New, archived
Headers show
Series Input: iqs5xx - more enhancements and optimizations | expand

Commit Message

Jeff LaBundy March 13, 2021, 7:12 p.m. UTC
Unsolicited I2C communication causes the device to assert an interrupt; as
such the IRQ is disabled before any registers are written in iqs5xx_open()
and iqs5xx_close().

After the driver is unloaded, however, i2c_device_remove() sets the IRQ to
zero before any handlers may call input_close_device() while the device is
unregistered. This keeps iqs5xx_close() from disabling the IRQ, leading to
an interrupt storm during removal.

Placing input_register_device() in front of devm_request_threaded_irq() to
free the IRQ before iqs5xx_close() is called does not cover the case where
firmware is updated at the factory and the input device is registered well
after the driver has already probed.

The solution, therefore, is to remove the open and close callbacks as they
do not buy much in the first place. The device already starts in an active
state, then drops into a low-power mode based on activity.

As an added benefit, this change allows the 250-ms delay in initialization
to be removed as iqs5xx_open() no longer follows immediately. Instead, the
delay is replaced with a mere 50-us delay which allows the interrupt to be
deasserted before the handler is registered.

Signed-off-by: Jeff LaBundy <jeff@labundy.com>
---
Changes in v2:
 - None

 drivers/input/touchscreen/iqs5xx.c | 25 +------------------------
 1 file changed, 1 insertion(+), 24 deletions(-)

--
2.17.1

Comments

Dmitry Torokhov March 14, 2021, 6:21 a.m. UTC | #1
Hi Jeff,

On Sat, Mar 13, 2021 at 01:12:33PM -0600, Jeff LaBundy wrote:
> Unsolicited I2C communication causes the device to assert an interrupt; as
> such the IRQ is disabled before any registers are written in iqs5xx_open()
> and iqs5xx_close().
> 
> After the driver is unloaded, however, i2c_device_remove() sets the IRQ to
> zero before any handlers may call input_close_device() while the device is
> unregistered. This keeps iqs5xx_close() from disabling the IRQ, leading to
> an interrupt storm during removal.
> 
> Placing input_register_device() in front of devm_request_threaded_irq() to
> free the IRQ before iqs5xx_close() is called does not cover the case where
> firmware is updated at the factory and the input device is registered well
> after the driver has already probed.
> 
> The solution, therefore, is to remove the open and close callbacks as they
> do not buy much in the first place. The device already starts in an active
> state, then drops into a low-power mode based on activity.

No, this is not the proper solution. We should rather fix i2c bus (and
really all the other buses with non-trivial probe and remove) so that it
is compatible with devres/devm. I wanted to do this for a while and I
guess we really need this. Could you please try the patch below and see
if it fixes your issue?

Thanks.
Jeff LaBundy March 15, 2021, 3:38 a.m. UTC | #2
Hi Dmitry,

On Sat, Mar 13, 2021 at 10:21:27PM -0800, Dmitry Torokhov wrote:
> Hi Jeff,
> 
> On Sat, Mar 13, 2021 at 01:12:33PM -0600, Jeff LaBundy wrote:
> > Unsolicited I2C communication causes the device to assert an interrupt; as
> > such the IRQ is disabled before any registers are written in iqs5xx_open()
> > and iqs5xx_close().
> > 
> > After the driver is unloaded, however, i2c_device_remove() sets the IRQ to
> > zero before any handlers may call input_close_device() while the device is
> > unregistered. This keeps iqs5xx_close() from disabling the IRQ, leading to
> > an interrupt storm during removal.
> > 
> > Placing input_register_device() in front of devm_request_threaded_irq() to
> > free the IRQ before iqs5xx_close() is called does not cover the case where
> > firmware is updated at the factory and the input device is registered well
> > after the driver has already probed.
> > 
> > The solution, therefore, is to remove the open and close callbacks as they
> > do not buy much in the first place. The device already starts in an active
> > state, then drops into a low-power mode based on activity.
> 
> No, this is not the proper solution. We should rather fix i2c bus (and
> really all the other buses with non-trivial probe and remove) so that it
> is compatible with devres/devm. I wanted to do this for a while and I
> guess we really need this. Could you please try the patch below and see
> if it fixes your issue?

Thank you for this suggestion; to be honest I had not considered how other
drivers may suffer a similar fate and I agree with your approach. I tested
your patch and it addresses my issue.

That being said, I would still advocate for this patch because of the other
reasons mentioned: the open/close callbacks do not happen to buy much since
the device effectively "opens" and (almost) "closes" automatically based on
touch events, and getting rid of the callbacks lets probe finish faster and
cleans up the code a bit.

Perhaps as a compromise, I can squash this and the next patch, and speak to
these points in a consolidated commit message?

> 
> Thanks.
> 
> -- 
> Dmitry

Kind regards,
Jeff LaBundy

> 
> 
> i2c: ensure timely release of driver-allocated resources
> 
> From: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> 
> More and more drivers rely on devres to manage their resources, however if
> bus' probe() and release() are not trivial and control some of resources as
> well (for example enable or disable clocks, or attach device to a power
> domain, we need to make sure that driver-allocated resources are released
> immediately after driver's remove() method returns, and not postponed until
> driver core gets around to releasing resources. To fix that we open a new
> devres group before calling driver's probe() and explicitly release it when
> we return from driver's remove().
> 
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Tested-by: Jeff LaBundy <jeff@labundy.com>

> ---
>  drivers/i2c/i2c-core-base.c |   19 ++++++++++++++++++-
>  include/linux/i2c.h         |    3 +++
>  2 files changed, 21 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
> index 63ebf722a424..b8a96db2c191 100644
> --- a/drivers/i2c/i2c-core-base.c
> +++ b/drivers/i2c/i2c-core-base.c
> @@ -518,6 +518,11 @@ static int i2c_device_probe(struct device *dev)
>  	if (status)
>  		goto err_clear_wakeup_irq;
>  
> +	client->devres_group_id = devres_open_group(&client->dev, NULL,
> +						    GFP_KERNEL);
> +	if (!client->devres_group_id)
> +		goto err_detach_pm_domain;
> +
>  	/*
>  	 * When there are no more users of probe(),
>  	 * rename probe_new to probe.
> @@ -530,11 +535,21 @@ static int i2c_device_probe(struct device *dev)
>  	else
>  		status = -EINVAL;
>  
> +	/*
> +	 * Note that we are not closing the devres group opened above so
> +	 * even resources that were attached to the device after probe is
> +	 * run are released when i2c_device_remove() is executed. This is
> +	 * needed as some drivers would allocate additional resources,
> +	 * for example when updating firmware.
> +	 */
> +
>  	if (status)
> -		goto err_detach_pm_domain;
> +		goto err_release_driver_resources;
>  
>  	return 0;
>  
> +err_release_driver_resources:
> +	devres_release_group(&client->dev, client->devres_group_id);
>  err_detach_pm_domain:
>  	dev_pm_domain_detach(&client->dev, true);
>  err_clear_wakeup_irq:
> @@ -563,6 +578,8 @@ static int i2c_device_remove(struct device *dev)
>  			dev_warn(dev, "remove failed (%pe), will be ignored\n", ERR_PTR(status));
>  	}
>  
> +	devres_release_group(&client->dev, client->devres_group_id);
> +
>  	dev_pm_domain_detach(&client->dev, true);
>  
>  	dev_pm_clear_wake_irq(&client->dev);
> diff --git a/include/linux/i2c.h b/include/linux/i2c.h
> index 56622658b215..5d1f11c0deaa 100644
> --- a/include/linux/i2c.h
> +++ b/include/linux/i2c.h
> @@ -306,6 +306,8 @@ struct i2c_driver {
>   *	userspace_devices list
>   * @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter
>   *	calls it to pass on slave events to the slave driver.
> + * @devres_group_id: id of the devres group that will be created for resources
> + *	acquired when probing this device.
>   *
>   * An i2c_client identifies a single device (i.e. chip) connected to an
>   * i2c bus. The behaviour exposed to Linux is defined by the driver
> @@ -334,6 +336,7 @@ struct i2c_client {
>  #if IS_ENABLED(CONFIG_I2C_SLAVE)
>  	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
>  #endif
> +	void *devres_group_id;		/* ID of probe devres group	*/
>  };
>  #define to_i2c_client(d) container_of(d, struct i2c_client, dev)
>
diff mbox series

Patch

diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c
index a990c176abf7..350466ff6bbd 100644
--- a/drivers/input/touchscreen/iqs5xx.c
+++ b/drivers/input/touchscreen/iqs5xx.c
@@ -468,20 +468,6 @@  static int iqs5xx_set_state(struct i2c_client *client, u8 state)
 	return error2;
 }

-static int iqs5xx_open(struct input_dev *input)
-{
-	struct iqs5xx_private *iqs5xx = input_get_drvdata(input);
-
-	return iqs5xx_set_state(iqs5xx->client, IQS5XX_RESUME);
-}
-
-static void iqs5xx_close(struct input_dev *input)
-{
-	struct iqs5xx_private *iqs5xx = input_get_drvdata(input);
-
-	iqs5xx_set_state(iqs5xx->client, IQS5XX_SUSPEND);
-}
-
 static int iqs5xx_axis_init(struct i2c_client *client)
 {
 	struct iqs5xx_private *iqs5xx = i2c_get_clientdata(client);
@@ -497,10 +483,7 @@  static int iqs5xx_axis_init(struct i2c_client *client)

 		input->name = client->name;
 		input->id.bustype = BUS_I2C;
-		input->open = iqs5xx_open;
-		input->close = iqs5xx_close;

-		input_set_drvdata(input, iqs5xx);
 		iqs5xx->input = input;
 	}

@@ -622,13 +605,7 @@  static int iqs5xx_dev_init(struct i2c_client *client)

 	iqs5xx->dev_id_info = *dev_id_info;

-	/*
-	 * The following delay allows ATI to complete before the open and close
-	 * callbacks are free to elicit I2C communication. Any attempts to read
-	 * from or write to the device during this time may face extended clock
-	 * stretching and prompt the I2C controller to report an error.
-	 */
-	msleep(250);
+	usleep_range(50, 100);

 	return 0;
 }