Re: [PATCH v2 12/24] staging:iio:cdc:ad7150: Rework interrupt handling.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, 31 Mar 2021 10:29:51 +0300
Alexandru Ardelean <ardeleanalex@xxxxxxxxx> wrote:

> On Sun, Mar 14, 2021 at 8:17 PM Jonathan Cameron <jic23@xxxxxxxxxx> wrote:
> >
> > From: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx>
> >
> > Note this doesn't support everything the chip can do as we ignore
> > window mode for now (in window / out of window).
> >
> > * Given the chip doesn't have any way of disabling the threshold
> >   pins, use disable_irq() etc to mask them except when we actually
> >   want them enabled (previously events were always enabled).
> >   Note there are race conditions, but using the current state from
> >   the status register and disabling interrupts across changes in
> >   type of event should mean those races result in interrupts,
> >   but no events to userspace.
> >
> > * Correctly reflect that there is one threshold line per channel.
> >
> > * Only take notice of rising edge. If anyone wants the other edge
> >   then they should set the other threshold (they are available for
> >   rising and falling directions).  This isn't perfect but it makes
> >   it a lot simpler.
> >
> > * If insufficient interrupts are specified in firnware, don't support
> >   events.
> >
> > * Adaptive events use the same pos/neg values of thrMD as non adaptive
> >   ones.
> >
> > Tested against qemu based emulation.
> >  
> 
> Overall looks ok.
> A few minor issues with the device-matching and OF.
> And maybe with the interrupt handling.

Hopefully answers below address those two.


> 
> 
> > Signed-off-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx>
> > Link: https://lore.kernel.org/r/20210207154623.433442-13-jic23@xxxxxxxxxx
> > ---
> >  drivers/staging/iio/cdc/ad7150.c | 258 ++++++++++++++++++-------------
> >  1 file changed, 153 insertions(+), 105 deletions(-)
> >
> > diff --git a/drivers/staging/iio/cdc/ad7150.c b/drivers/staging/iio/cdc/ad7150.c
> > index f8183c540d5c..24be97456c03 100644
> > --- a/drivers/staging/iio/cdc/ad7150.c
> > +++ b/drivers/staging/iio/cdc/ad7150.c
> > @@ -11,6 +11,7 @@
> >  #include <linux/kernel.h>
> >  #include <linux/slab.h>
> >  #include <linux/i2c.h>
> > +#include <linux/irq.h>
> >  #include <linux/module.h>
> >
> >  #include <linux/iio/iio.h>
> > @@ -60,8 +61,6 @@ enum {
> >  /**
> >   * struct ad7150_chip_info - instance specific chip data
> >   * @client: i2c client for this device
> > - * @current_event: device always has one type of event enabled.
> > - *     This element stores the event code of the current one.
> >   * @threshold: thresholds for simple capacitance value events
> >   * @thresh_sensitivity: threshold for simple capacitance offset
> >   *     from 'average' value.
> > @@ -70,21 +69,23 @@ enum {
> >   *     value jumps to current value.  Note made up of two fields,
> >   *      3:0 are for timeout receding - applies if below lower threshold
> >   *      7:4 are for timeout approaching - applies if above upper threshold
> > - * @old_state: store state from previous event, allowing confirmation
> > - *     of new condition.
> > - * @conversion_mode: the current conversion mode.
> >   * @state_lock: ensure consistent state of this structure wrt the
> >   *     hardware.
> > + * @interrupts: one or two interrupt numbers depending on device type.
> > + * @int_enabled: is a given interrupt currently enabled.
> > + * @type: threshold type
> > + * @dir: threshold direction
> >   */
> >  struct ad7150_chip_info {
> >         struct i2c_client *client;
> > -       u64 current_event;
> >         u16 threshold[2][2];
> >         u8 thresh_sensitivity[2][2];
> >         u8 thresh_timeout[2][2];
> > -       int old_state;
> > -       char *conversion_mode;
> >         struct mutex state_lock;
> > +       int interrupts[2];
> > +       bool int_enabled[2];
> > +       enum iio_event_type type;
> > +       enum iio_event_direction dir;
> >  };
> >
> >  /*
> > @@ -158,8 +159,8 @@ static int ad7150_read_event_config(struct iio_dev *indio_dev,
> >         switch (type) {
> >         case IIO_EV_TYPE_THRESH_ADAPTIVE:
> >                 if (dir == IIO_EV_DIR_RISING)
> > -                       return !thrfixed && (threshtype == 0x3);
> > -               return !thrfixed && (threshtype == 0x2);
> > +                       return !thrfixed && (threshtype == 0x1);
> > +               return !thrfixed && (threshtype == 0x0);
> >         case IIO_EV_TYPE_THRESH:
> >                 if (dir == IIO_EV_DIR_RISING)
> >                         return thrfixed && (threshtype == 0x1);
> > @@ -179,11 +180,9 @@ static int ad7150_write_event_params(struct iio_dev *indio_dev,
> >  {
> >         struct ad7150_chip_info *chip = iio_priv(indio_dev);
> >         int rising = (dir == IIO_EV_DIR_RISING);
> > -       u64 event_code;
> >
> > -       event_code = IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, chan, type, dir);
> > -
> > -       if (event_code != chip->current_event)
> > +       /* Only update value live, if parameter is in use */
> > +       if ((type != chip->type) || (dir != chip->dir))
> >                 return 0;
> >
> >         switch (type) {
> > @@ -231,52 +230,91 @@ static int ad7150_write_event_config(struct iio_dev *indio_dev,
> >         int ret;
> >         struct ad7150_chip_info *chip = iio_priv(indio_dev);
> >         int rising = (dir == IIO_EV_DIR_RISING);
> > -       u64 event_code;
> > -
> > -       /* Something must always be turned on */
> > -       if (!state)
> > -               return -EINVAL;
> >
> > -       event_code = IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
> > -       if (event_code == chip->current_event)
> > +       /*
> > +        * There is only a single shared control and no on chip
> > +        * interrupt disables for the two interrupt lines.
> > +        * So, enabling will switch the events configured to enable
> > +        * whatever was most recently requested and if necessary enable_irq()
> > +        * the interrupt and any disable will disable_irq() for that
> > +        * channels interrupt.
> > +        */
> > +       if (!state) {
> > +               if ((chip->int_enabled[chan->channel]) &&
> > +                   (type == chip->type) && (dir == chip->dir)) {
> > +                       disable_irq(chip->interrupts[chan->channel]);
> > +                       chip->int_enabled[chan->channel] = false;
> > +               }
> >                 return 0;
> > +       }
> > +
> >         mutex_lock(&chip->state_lock);
> > -       ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG);
> > -       if (ret < 0)
> > -               goto error_ret;
> > +       if ((type != chip->type) || (dir != chip->dir)) {
> >
> > -       cfg = ret & ~((0x03 << 5) | BIT(7));
> > +               /*
> > +                * Need to temporarily disable both interrupts if
> > +                * enabled - this is to avoid races around changing
> > +                * config and thresholds.
> > +                * Note enable/disable_irq() are reference counted so
> > +                * no need to check if already enabled.
> > +                */
> > +               disable_irq(chip->interrupts[0]);
> > +               disable_irq(chip->interrupts[1]);
> >
> > -       switch (type) {
> > -       case IIO_EV_TYPE_THRESH_ADAPTIVE:
> > -               adaptive = 1;
> > -               if (rising)
> > -                       thresh_type = 0x3;
> > -               else
> > -                       thresh_type = 0x2;
> > -               break;
> > -       case IIO_EV_TYPE_THRESH:
> > -               adaptive = 0;
> > -               if (rising)
> > -                       thresh_type = 0x1;
> > -               else
> > -                       thresh_type = 0x0;
> > -               break;
> > -       default:
> > -               ret = -EINVAL;
> > -               goto error_ret;
> > -       }
> > +               ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG);
> > +               if (ret < 0)
> > +                       goto error_ret;
> >
> > -       cfg |= (!adaptive << 7) | (thresh_type << 5);
> > +               cfg = ret & ~((0x03 << 5) | BIT(7));
> >
> > -       ret = i2c_smbus_write_byte_data(chip->client, AD7150_CFG, cfg);
> > -       if (ret < 0)
> > -               goto error_ret;
> > +               switch (type) {
> > +               case IIO_EV_TYPE_THRESH_ADAPTIVE:
> > +                       adaptive = 1;
> > +                       if (rising)
> > +                               thresh_type = 0x1;
> > +                       else
> > +                               thresh_type = 0x0;
> > +                       break;
> > +               case IIO_EV_TYPE_THRESH:
> > +                       adaptive = 0;
> > +                       if (rising)
> > +                               thresh_type = 0x1;
> > +                       else
> > +                               thresh_type = 0x0;  
> 
> this can be ignored; it's a minor nit;
> the if (rising) {} else {} block looks duplicate;
> it could be moved after the switch() block;

If you are happy with rest I'll just roll that in whilst applying.

> 
> > +                       break;
> > +               default:
> > +                       ret = -EINVAL;
> > +                       goto error_ret;
> > +               }
> >
> > -       chip->current_event = event_code;
> > +               cfg |= (!adaptive << 7) | (thresh_type << 5);
> > +
> > +               ret = i2c_smbus_write_byte_data(chip->client, AD7150_CFG, cfg);
> > +               if (ret < 0)
> > +                       goto error_ret;
> > +
> > +               /*
> > +                * There is a potential race condition here, but not easy
> > +                * to close given we can't disable the interrupt at the
> > +                * chip side of things. Rely on the status bit.
> > +                */
> > +               chip->type = type;
> > +               chip->dir = dir;
> > +
> > +               /* update control attributes */
> > +               ret = ad7150_write_event_params(indio_dev, chan->channel, type,
> > +                                               dir);
> > +               if (ret)
> > +                       goto error_ret;
> > +               /* reenable any irq's we disabled whilst changing mode */
> > +               enable_irq(chip->interrupts[0]);
> > +               enable_irq(chip->interrupts[1]);  
> 
> i'm wondering if we need to set   'chip->int_enabled[ for 0 & 1 ] = true;'
> tbh i am not sure if there is a chance of mismatch between
> 'int_enabled' and the actual enable_irq()
> 
> maybe it might make sense to add an  ad7150_{enable/disable}_irq()
> that checks the per-channel 'int_enabled' field;

Ah.  I'm playing a bit of a game here. (there is a comment at the irq_disable() calls
that was meant to indicate what was going on).

There is a difference between when chip->int_enabled[] is changed in conjunction
with enable_irq()/disable_irq() and when enable_irq()/disable_irq() are change on their own.

The intent is that int_enabled[] reflects non-transient state of whether
the interrupt is enabled - I don't want to update it during transient states.

We are fine to do the disable_irq() calls on irqs that aren't enabled because
disable_irq() calls can be safely nested.  Only when you have more enable_irq()
calls (including startup if it's auto enabled) than you have disable_irq() calls
will the interrupt be enabled.

https://elixir.bootlin.com/linux/latest/source/kernel/irq/manage.c#L771
https://elixir.bootlin.com/linux/latest/source/include/linux/irqdesc.h#L28
The depth field in irq_desc is used for this.

> 
> 
> > +       }
> > +       if (!chip->int_enabled[chan->channel]) {
> > +               enable_irq(chip->interrupts[chan->channel]);
> > +               chip->int_enabled[chan->channel] = true;
> > +       }
> >
> > -       /* update control attributes */
> > -       ret = ad7150_write_event_params(indio_dev, chan->channel, type, dir);
> >  error_ret:
> >         mutex_unlock(&chip->state_lock);
> >
> > @@ -434,59 +472,44 @@ static const struct iio_chan_spec ad7151_channels_no_irq[] = {
> >         AD7150_CAPACITANCE_CHAN_NO_IRQ(0),
> >  };
> >
> > -static irqreturn_t ad7150_event_handler(int irq, void *private)
> > +static irqreturn_t __ad7150_event_handler(void *private, u8 status_mask,
> > +                                         int channel)
> >  {
> >         struct iio_dev *indio_dev = private;
> >         struct ad7150_chip_info *chip = iio_priv(indio_dev);
> > -       u8 int_status;
> >         s64 timestamp = iio_get_time_ns(indio_dev);
> > -       int ret;
> > +       int int_status;
> >
> > -       ret = i2c_smbus_read_byte_data(chip->client, AD7150_STATUS);
> > -       if (ret < 0)
> > +       int_status = i2c_smbus_read_byte_data(chip->client, AD7150_STATUS);
> > +       if (int_status < 0)
> >                 return IRQ_HANDLED;
> >
> > -       int_status = ret;
> > -
> > -       if ((int_status & AD7150_STATUS_OUT1) &&
> > -           !(chip->old_state & AD7150_STATUS_OUT1))
> > -               iio_push_event(indio_dev,
> > -                              IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE,
> > -                                                   0,
> > -                                                   IIO_EV_TYPE_THRESH,
> > -                                                   IIO_EV_DIR_RISING),
> > -                               timestamp);
> > -       else if ((!(int_status & AD7150_STATUS_OUT1)) &&
> > -                (chip->old_state & AD7150_STATUS_OUT1))
> > -               iio_push_event(indio_dev,
> > -                              IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE,
> > -                                                   0,
> > -                                                   IIO_EV_TYPE_THRESH,
> > -                                                   IIO_EV_DIR_FALLING),
> > -                              timestamp);
> > -
> > -       if ((int_status & AD7150_STATUS_OUT2) &&
> > -           !(chip->old_state & AD7150_STATUS_OUT2))
> > -               iio_push_event(indio_dev,
> > -                              IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE,
> > -                                                   1,
> > -                                                   IIO_EV_TYPE_THRESH,
> > -                                                   IIO_EV_DIR_RISING),
> > -                              timestamp);
> > -       else if ((!(int_status & AD7150_STATUS_OUT2)) &&
> > -                (chip->old_state & AD7150_STATUS_OUT2))
> > -               iio_push_event(indio_dev,
> > -                              IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE,
> > -                                                   1,
> > -                                                   IIO_EV_TYPE_THRESH,
> > -                                                   IIO_EV_DIR_FALLING),
> > -                              timestamp);
> > -       /* store the status to avoid repushing same events */
> > -       chip->old_state = int_status;
> > +       /*
> > +        * There are race conditions around enabling and disabling that
> > +        * could easily land us here with a spurious interrupt.
> > +        * Just eat it if so.
> > +        */
> > +       if (!(int_status & status_mask))
> > +               return IRQ_HANDLED;
> > +
> > +       iio_push_event(indio_dev,
> > +                      IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, channel,
> > +                                           chip->type, chip->dir),
> > +                      timestamp);
> >
> >         return IRQ_HANDLED;
> >  }
> >
> > +static irqreturn_t ad7150_event_handler_ch1(int irq, void *private)
> > +{
> > +       return __ad7150_event_handler(private, AD7150_STATUS_OUT1, 0);
> > +}
> > +
> > +static irqreturn_t ad7150_event_handler_ch2(int irq, void *private)
> > +{
> > +       return __ad7150_event_handler(private, AD7150_STATUS_OUT2, 1);
> > +}
> > +
> >  static IIO_CONST_ATTR(in_capacitance_thresh_adaptive_timeout_available,
> >                       "[0 0.01 0.15]");
> >
> > @@ -533,12 +556,44 @@ static int ad7150_probe(struct i2c_client *client,
> >
> >         indio_dev->modes = INDIO_DIRECT_MODE;
> >
> > -       if (client->irq) {
> > +       chip->interrupts[0] = fwnode_irq_get(dev_fwnode(&client->dev), 0);
> > +       if (chip->interrupts[0] < 0)
> > +               return chip->interrupts[0];
> > +       if (id->driver_data == AD7150) {  
> 
> checking  'id->driver_data'  will be flaky after the of_match_table is added;

It's not, though the path is a bit obscure and only works if you don't use
probe_new() - if using probe_new() you to call i2c_match_id() directly.

I remember raising this in a review I did a few years back and chasing it down.
I just sanity checked and it still works ;)

So, even if we do an of_based registration the following happens

of_i2c_register_device()
-> of_i2c_get_board_info() // gets the of_node pointer
-> i2c_new_client_device(adap, &info); 
..  some time later the i2c_bus_type.probe() gets called for the devices we added
   and


-> i2c_device_probe()
  -> driver->probe(client, i2c_match_id(driver->id_table, client))
    -> i2c_match_id() //matches against id table and hence gets us the driver_data.



> either we defer the of_match_table patch, or we update it somehow;
> maybe a chip_info struct is an idea;
> otherwise we need to do void pointer to int conversions [which look nasty];
> 
> i'm also seeing that there is a 'type' field inside of_device_id that
> is underused, but looks useful for cases like this;
> i'm also a bit paranoid to use it; because it may not be compatible
> with acpi_device_id

We can refactor this at some point, but I don't think it is necessary to move
the driver out of staging as a lot of drivers are doing things the same
way we do it here.

> 
> > +               chip->interrupts[1] = fwnode_irq_get(dev_fwnode(&client->dev), 1);
> > +               if (chip->interrupts[1] < 0)
> > +                       return chip->interrupts[1];
> > +       }
> > +       if (chip->interrupts[0] &&
> > +           (id->driver_data == AD7151 || chip->interrupts[1])) {
> > +               irq_set_status_flags(chip->interrupts[0], IRQ_NOAUTOEN);
> > +               ret = devm_request_threaded_irq(&client->dev,
> > +                                               chip->interrupts[0],
> > +                                               NULL,
> > +                                               &ad7150_event_handler_ch1,
> > +                                               IRQF_TRIGGER_RISING |
> > +                                               IRQF_ONESHOT,
> > +                                               "ad7150_irq1",
> > +                                               indio_dev);
> > +               if (ret)
> > +                       return ret;
> > +
> >                 indio_dev->info = &ad7150_info;
> >                 switch (id->driver_data) {
> >                 case AD7150:
> >                         indio_dev->channels = ad7150_channels;
> >                         indio_dev->num_channels = ARRAY_SIZE(ad7150_channels);
> > +                       irq_set_status_flags(chip->interrupts[1], IRQ_NOAUTOEN);
> > +                       ret = devm_request_threaded_irq(&client->dev,
> > +                                                       chip->interrupts[1],
> > +                                                       NULL,
> > +                                                       &ad7150_event_handler_ch2,
> > +                                                       IRQF_TRIGGER_RISING |
> > +                                                       IRQF_ONESHOT,
> > +                                                       "ad7150_irq2",
> > +                                                       indio_dev);
> > +                       if (ret)
> > +                               return ret;
> >                         break;
> >                 case AD7151:
> >                         indio_dev->channels = ad7151_channels;
> > @@ -548,25 +603,18 @@ static int ad7150_probe(struct i2c_client *client,
> >                         return -EINVAL;
> >                 }
> >
> > -               ret = devm_request_threaded_irq(&client->dev, client->irq,
> > -                                               NULL,
> > -                                               &ad7150_event_handler,
> > -                                               IRQF_TRIGGER_RISING |
> > -                                               IRQF_ONESHOT,
> > -                                               "ad7150_irq1",
> > -                                               indio_dev);
> > -               if (ret)
> > -                       return ret;
> >         } else {
> >                 indio_dev->info = &ad7150_info_no_irq;
> >                 switch (id->driver_data) {
> >                 case AD7150:
> >                         indio_dev->channels = ad7150_channels_no_irq;
> > -                       indio_dev->num_channels = ARRAY_SIZE(ad7150_channels_no_irq);
> > +                       indio_dev->num_channels =
> > +                               ARRAY_SIZE(ad7150_channels_no_irq);
> >                         break;
> >                 case AD7151:
> >                         indio_dev->channels = ad7151_channels_no_irq;
> > -                       indio_dev->num_channels = ARRAY_SIZE(ad7151_channels_no_irq);
> > +                       indio_dev->num_channels =
> > +                               ARRAY_SIZE(ad7151_channels_no_irq);
> >                         break;
> >                 default:
> >                         return -EINVAL;
> > --
> > 2.30.2
> >  




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux