Re: [PATCH v4 3/3] iio: adc: add support for pac1921

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

 



On Tue, 06 Aug 2024 11:53:12 +0200
Matteo Martelli <matteomartelli3@xxxxxxxxx> wrote:

> Jonathan Cameron wrote:
> > > > > +
> > > > > +/*
> > > > > + * Emit on sysfs the list of available scales contained in scales_tbl
> > > > > + *
> > > > > + * TODO:: this function can be replaced with iio_format_avail_list() if the
> > > > > + * latter will ever be exported.    
> > > > 
> > > > You could just have added a precursor patch doing that.
> > > > If you have time I'd certainly consider a patch that does export that function
> > > > and uses it here.
> > > >    
> > > I wasn't sure that one usage was enough to justify the export. I could
> > > definitely do it, I am assuming it would now go to a new patch series since
> > > this has already been merged into testing, right?  
> > The requirements for justifying exporting an existing function is less
> > than it would be to add a new one.  As such I think it makes sense.
> > 
> > As you note, needs a separate patch on top of the tree.
> >   
> I will try to address this more generally by adding a new
> read_avail_release_resource() iio_info function, see below. If that goes
> through, exporting the iio_format_avail_list() would not be necessary since the
> driver could directly use the read_avail iio_info function.
> 
> > >   
> > > > > + *
> > > > > + * Must be called with lock held if the scales_tbl can change runtime (e.g. for
> > > > > + * the current scales table)
> > > > > + */
> > > > > +static ssize_t pac1921_format_scale_avail(const int (*const scales_tbl)[2],
> > > > > +					  size_t size, char *buf)
> > > > > +{
> > > > > +	ssize_t len = 0;
> > > > > +
> > > > > +	for (unsigned int i = 0; i < size; i++) {
> > > > > +		if (i != 0) {
> > > > > +			len += sysfs_emit_at(buf, len, " ");
> > > > > +			if (len >= PAGE_SIZE)
> > > > > +				return -EFBIG;
> > > > > +		}
> > > > > +		len += sysfs_emit_at(buf, len, "%d.%09d", scales_tbl[i][0],
> > > > > +				     scales_tbl[i][1]);
> > > > > +		if (len >= PAGE_SIZE)
> > > > > +			return -EFBIG;
> > > > > +	}
> > > > > +
> > > > > +	len += sysfs_emit_at(buf, len, "\n");
> > > > > +	return len;
> > > > > +}
> > > > > +
> > > > > +/*
> > > > > + * Read available scales for a specific channel
> > > > > + *
> > > > > + * NOTE: using extended info insted of iio.read_avail() because access to
> > > > > + * current scales must be locked as they depend on shunt resistor which may
> > > > > + * change runtime. Caller of iio.read_avail() would access the table unlocked
> > > > > + * instead.    
> > > > 
> > > > That's a corner case we should think about closing. Would require an indicator
> > > > to read_avail that the buffer it has been passed is a snapshot that it should
> > > > free on completion of the string building.  I don't like passing ownership
> > > > of data around like that, but it is fiddly to do anything else given
> > > > any simple double buffering is subject to race conditions.
> > > >    
> > > If I understand your suggestion the driver would allocate a new table and copy
> > > the values into it at each read_avail() call. Then
> > > iio_read_channel_info_avail() would free the buffer if some sort of
> > > free-after-use indicator flag is set. I guess such indicator might be set via an
> > > additional read_avail function argument (would be an extensive API change) or
> > > maybe via a new iio_chan_spec attribute.  
> > 
> > Probably needs to be in read_avail() as otherwise we end up with yet more masks.
> > However, doesn't need to be global.  read_avail_ext() could be added that
> > is used in preference to read_avail() if it is supplied.  That new one can
> > be used only be drivers that need to handle the allocation and free.
> > However I prefer the explicit resource free option as we can in theory
> > at least do much cleverer things than simply freeing the buffer.
> >   
> > >   
> > > > An alternative would use a key of sometype to associate individual read_avail
> > > > calls with new ones to read_avail_release_resource. That might be cleaner.
> > > >     
> > > Are you referring to introduce a new read_avail_realease_resource callback that
> > > would be called at the end of iio_read_channel_info_avail() if set? Similarly
> > > to the previous point the driver would allocate a new table and copy the values
> > > into it at each read_avail() call, but the driver would also define a release
> > > callback to free such table. If otherwise you are referring to something less
> > > trivial, is there a similar API in the kernel that can be referred to for
> > > clarity?  
> > 
> > Indeed what you suggest. Key is it puts the burden on the driver to do it's
> > own management. That avoids handing ownership of the buffer to the core
> > which is a pattern I'm not that keen on if we can avoid it.
> > 
> > The new callback would take the buffer pointer that came back from read_avail()
> > and pass that back to the driver.  In simple case the driver could just
> > free the buffer.  However, it could also do some cleverer stuff to keep
> > it around if a write hasn't raced with this code.  That might make sense if
> > it's a big table and calculating the values is expensive.
> >  
> I am trying to achieve this and it looks pretty straightforward for the case we
> considered, iio would be extended like the following:
> 
> diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
> index e6fad8a6a1fc..fe6ad8e9722f 100644
> --- a/drivers/iio/industrialio-core.c
> +++ b/drivers/iio/industrialio-core.c
> @@ -860,12 +860,20 @@ static ssize_t iio_read_channel_info_avail(struct device *dev,
>                 return ret;
>         switch (ret) {
>         case IIO_AVAIL_LIST:
> -               return iio_format_avail_list(buf, vals, type, length);
> +               ret = iio_format_avail_list(buf, vals, type, length);
> +               break;
>         case IIO_AVAIL_RANGE:
> -               return iio_format_avail_range(buf, vals, type);
> +               ret = iio_format_avail_range(buf, vals, type);
> +               break;
>         default:
> -               return -EINVAL;
> +               ret = -EINVAL;
>         }
> +
> +       if (indio_dev->info->read_avail_release_resource)
> +               indio_dev->info->read_avail_release_resource(
> +                       indio_dev, this_attr->c, vals, this_attr->address);
> +
> +       return ret;
>  }
> 
>  /**
> diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h
> index f6c0499853bb..0ab08b94bad0 100644
> --- a/include/linux/iio/iio.h
> +++ b/include/linux/iio/iio.h
> @@ -491,6 +491,10 @@ struct iio_info {
>                           int *length,
>                           long mask);
> 
> +       void (*read_avail_release_resource)(struct iio_dev *indio_dev,
> +                                           struct iio_chan_spec const *chan,
> +                                           const int *vals, long mask);
> +
>         int (*write_raw)(struct iio_dev *indio_dev,
>                          struct iio_chan_spec const *chan,
>                          int val, 
> 
> And with the following usage example for the pac1921 driver:
> 
> static int pac1921_read_avail(struct iio_dev *indio_dev,
> 			      struct iio_chan_spec const *chan,
> 			      const int **vals, int *type, int *length,
> 			      long mask)
> {
> 	switch (mask) {
> 	//...
> 	case IIO_CHAN_INFO_SCALE:
> 		switch (chan->channel) {
> 		//...
> 		case PAC1921_CHAN_CURRENT: {
> 			struct pac1921_priv *priv = iio_priv(indio_dev);
> 			size_t len;
> 			int *buf;
> 
> 			len = ARRAY_SIZE(priv->current_scales) * 2;
> 			buf = kmalloc_array(len, sizeof(int), GFP_KERNEL);
> 			if (!buf)
> 				return -ENOMEM;
> 
> 			for (unsigned int i = 0; i < len; i++)
> 				buf[i] = ((int *)priv->current_scales)[i];
> 
> 			*vals = buf;
> 			*length = (int)len;
> 			*type = IIO_VAL_INT_PLUS_NANO;
> 			return IIO_AVAIL_LIST;
> 		}
> 		default:
> 			return -EINVAL;
> 		}
> 	default:
> 		return -EINVAL;
> 	}
> }
> 
> static void pac1921_read_avail_release_res(struct iio_dev *indio_dev,
> 					   struct iio_chan_spec const *chan,
> 					   const int *vals, long mask)
> {
> 	if (mask == IIO_CHAN_INFO_SCALE &&
> 	    chan->channel == PAC1921_CHAN_CURRENT)
> 		kfree(vals);
> }
> 
> static const struct iio_info pac1921_iio = {
> 	//...
> 	.read_avail = pac1921_read_avail,
> 	.read_avail_release_resource = pac1921_read_avail_release_res,
> };
> 
> However I noticed that some consumer drivers also expose the producer's
> available lists through the following functions:
> - iio_read_avail_channel_attribute()
> - iio_read_avail_channel_raw()
> - iio_channel_read_max()
> - iio_channel_read_min()
> 
> While addressing the read_max()/read_min() is trivial since the
> release_resource() can be called at the end of those function, I think the
> first twos should be tracked as well for later release by the consumer drivers.

We can mostly avoid this by taking a copy in the consumers that use these interfaces then
immediately calling the release. 

> So for example the consumer driver would also expose a
> iio_read_avail_channel_attribute_release_resource() (any suggestion for shorter
> function names?) mapped to the read_avail_release_resource() iio_info function.

> However the fact that iio_read_avail_channel_attribute() locks on
> info_exist_lock, makes me think that the driver could be unregistered between a
> read_avail() and a read_avail_release_resource() and in that case an allocated
> list would be leaked, right? Any suggestion on how best handle this case? My
> guess is to let iio destroy the list at some point during device release, that
> would be done if the list allocation was done through devm_kmalloc (or similar)
> but I think it would result in double frees during usual case, so maybe there
> should be a way to let it free the list only if not already freed? Or maybe a
> complete different approach?

Locking is a bit of a pain. I don't want to reference count for something
as trivial as this.

Perhaps the original idea of a release callback isn't best solution for these
in kernel interfaces and we should just 'always' make a copy of the data to
avoid the lifetime issue.  I don't want to do that for the IIO core case
because it's a big waste of memory and we don't have the lifetime issues,
but for the in kernel consumer interfaces copying sounds fine.

> 
> > >   
> > > > oh well, a cleanup job for another day.   I suspect we have drivers today
> > > > that are subject to tearing of their available lists.
> > > >     
> > > I've just taken a quick look at the other drivers and the following twos seem
> > > to have the race condition issue since they are updating an available table
> > > during a write_raw() call and also exposing it during a read_avail() call:
> > > * drivers/iio/light/as73211.c: see int_time_avail table
> > > * drivers/iio/adc/ad7192.c: see filter_freq_avail table
> > > 
> > > There might be others, I've only looked into those that seemed likely to have
> > > this issue after some trivial greps.
> > > 
> > > Is there already a common way for iio to keep track of open issues (e.g. Issue
> > > tracker/TODO lists/etc)?  
> > 
> > Not really.  Email to the list tends to be the most we do for tracking.
> > I have had various todo lists public over the years, but they tend to rot.
> > 
> > Fix stuff before we forget about it! :(
> >   
> I could try to provide fix patches for those two drivers as well, but I could
> not test them on the real HW. I am wondering whether to add them to the same
> release_resource() patch series or into a separate series since those fixes
> could be sit for a while waiting for additional tests.
Either is fine.  I don't necessarily have to pick the whole series up in one
go.  Just put those other drivers towards the end.

Jonathan

> 
> Thanks,
> Matteo Martelli
> 





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux