lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <3ebe506b-17d8-4639-94e2-16ef75a221a5@baylibre.com>
Date: Thu, 9 Jan 2025 15:53:50 -0600
From: David Lechner <dlechner@...libre.com>
To: Guillaume Ranquet <granquet@...libre.com>,
 Lars-Peter Clausen <lars@...afoo.de>,
 Michael Hennerich <Michael.Hennerich@...log.com>,
 Jonathan Cameron <jic23@...nel.org>
Cc: linux-iio@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: Re: [PATCH RFC v2 2/2] iio: adc: ad7173: add openwire detection
 support for single conversions

On 1/9/25 7:55 AM, Guillaume Ranquet wrote:
> Some chips of the ad7173 family supports open wire detection.
> 
> Generate a threshold event whenever an external source is disconnected

s/threshold/"openwire"/

> from the system input on single conversions.

And also an event when reconnected.

What about during buffered reads? Is the recommendation that we should do a
single conversion, check for event, do buffered read, do a single conversion,
check for event to ensure that a wire did not become disconnected while doing a
buffered read?

> 
> Signed-off-by: Guillaume Ranquet <granquet@...libre.com>
> ---
>  drivers/iio/adc/ad7173.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 164 insertions(+)
> 
> diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c
> index 11ff148cb5a315d32485acf04b8d6f7d0fb6e5fa..2f1b506c439249462b9740ff69e7553a37c45943 100644
> --- a/drivers/iio/adc/ad7173.c
> +++ b/drivers/iio/adc/ad7173.c
> @@ -35,6 +35,7 @@
>  #include <linux/units.h>
>  
>  #include <linux/iio/buffer.h>
> +#include <linux/iio/events.h>
>  #include <linux/iio/iio.h>
>  #include <linux/iio/trigger_consumer.h>
>  #include <linux/iio/triggered_buffer.h>
> @@ -102,6 +103,7 @@
>  
>  #define AD7173_GPIO_PDSW	BIT(14)
>  #define AD7173_GPIO_OP_EN2_3	BIT(13)
> +#define AD4111_GPIO_GP_OW_EN	BIT(12)
>  #define AD7173_GPIO_MUX_IO	BIT(12)
>  #define AD7173_GPIO_SYNC_EN	BIT(11)
>  #define AD7173_GPIO_ERR_EN	BIT(10)
> @@ -149,6 +151,7 @@
>  
>  #define AD7173_FILTER_ODR0_MASK		GENMASK(5, 0)
>  #define AD7173_MAX_CONFIGS		8
> +#define AD4111_OW_DET_THRSH_MV		300
>  
>  #define AD7173_MODE_CAL_INT_ZERO		0x4 /* Internal Zero-Scale Calibration */
>  #define AD7173_MODE_CAL_INT_FULL		0x5 /* Internal Full-Scale Calibration */
> @@ -181,11 +184,15 @@ struct ad7173_device_info {
>  	bool has_int_ref;
>  	bool has_ref2;
>  	bool has_internal_fs_calibration;
> +	bool has_openwire_det;
>  	bool higher_gpio_bits;
>  	u8 num_gpios;
>  };
>  
>  struct ad7173_channel_config {
> +	/* Openwire detection threshold */
> +	unsigned int openwire_thrsh_raw;
> +	int openwire_comp_chan;
>  	u8 cfg_slot;
>  	bool live;
>  
> @@ -202,6 +209,7 @@ struct ad7173_channel {
>  	unsigned int chan_reg;
>  	unsigned int ain;
>  	struct ad7173_channel_config cfg;
> +	bool openwire_det_en;
>  };
>  
>  struct ad7173_state {
> @@ -280,6 +288,7 @@ static const struct ad7173_device_info ad4111_device_info = {
>  	.has_current_inputs = true,
>  	.has_int_ref = true,
>  	.has_internal_fs_calibration = true,
> +	.has_openwire_det = true,
>  	.clock = 2 * HZ_PER_MHZ,
>  	.sinc5_data_rates = ad7173_sinc5_data_rates,
>  	.num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
> @@ -616,6 +625,69 @@ static int ad7173_calibrate_all(struct ad7173_state *st, struct iio_dev *indio_d
>  	return 0;
>  }
>  
> +/* Associative array of channel pairs for openwire detection
> + * The array is indexed by ain and gives the associated channel pair
> + * to perform the openwire detection with
> + * the channel pair [0] is for non differential and pair [1]
> + * is for differential inputs

Maybe just me, but it seem like this would be a bit easier to understand if this
was two separate tables even if the code that uses it gets a bit more verbose.

> + */
> +static int openwire_ain_to_channel_pair[][2][2] = {
> +	[0] = { {0, 15},  {1, 2}   },

Not sure if it makes a difference, but the order in the datasheet is 15, 0.

> +	[1] = { {1, 2},   {2, 1}   },
> +	[2] = { {3, 4},   {5, 6}   },
> +	[3] = { {5, 6},   {6, 5}   },
> +	[4] = { {7, 8},   {9, 10}  },
> +	[5] = { {9, 10},  {10, 9}  },
> +	[6] = { {11, 12}, {13, 14} },
> +	[7] = { {13, 14}, {14, 13} },
> +};
> +
> +/* Openwire detection on ad4111 works by running the same input measurement

s/Openwire/Open wire/

> + * on two different channels and compare if the difference between the two
> + * measurements exceeds a certain value (typical 300mV)
> + */
> +static void ad4111_openwire_event(struct iio_dev *indio_dev,
> +				  const struct iio_chan_spec *chan)
> +{
> +	struct ad7173_state *st = iio_priv(indio_dev);
> +	struct ad7173_channel *adchan = &st->channels[chan->address];
> +	struct ad7173_channel_config *cfg = &adchan->cfg;
> +	int ret, val1, val2;
> +
> +	ret = regmap_set_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, AD4111_GPIO_GP_OW_EN);
> +	if (ret)

Probably best to return errors to userspace. Or at least log errors instead of
silently ignoring them. Otherwise troubleshooting failures won't be possible
without modifying the kernel.

> +		return;
> +
> +	adchan->cfg.openwire_comp_chan =
> +		openwire_ain_to_channel_pair[chan->channel][chan->differential][0];
> +
> +	ret = ad_sigma_delta_single_conversion(indio_dev, chan, &val1);
> +	if (ret < 0)
> +		goto out;
> +
> +	adchan->cfg.openwire_comp_chan =
> +		openwire_ain_to_channel_pair[chan->channel][chan->differential][1];
> +
> +	ret = ad_sigma_delta_single_conversion(indio_dev, chan, &val2);
> +	if (ret < 0)
> +		goto out;
> +
> +	if (abs(val1 - val2) > cfg->openwire_thrsh_raw)
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, chan->address,
> +						    IIO_EV_TYPE_OPENWIRE, IIO_EV_DIR_RISING),
> +			       iio_get_time_ns(indio_dev));
> +	else
> +		iio_push_event(indio_dev,
> +			       IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, chan->address,
> +						    IIO_EV_TYPE_OPENWIRE, IIO_EV_DIR_FALLING),
> +			       iio_get_time_ns(indio_dev));

Hmm... rising and falling doesn't make so much sense for an openwire event. It
is either "open" or "closed" and doesn't really have a direction. (The threshold
is just an internal implementation detail of how it is detected.)

So maybe we need new IIO_EV_DIR_ members for this too?

I guess we could call it "opening" and "closing" to make it direction-like?

Also, this implementation makes it a level event instead of an edge event.
In other words, on every single conversion, we will get an event. Usually, we
would keep track of the previous value and only emit an event when the state
changes.

> +
> +out:
> +	adchan->cfg.openwire_comp_chan = -1;
> +	regmap_clear_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, AD4111_GPIO_GP_OW_EN);
> +}
> +
>  static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
>  			     unsigned int offset, unsigned int *reg,
>  			     unsigned int *mask)
> @@ -813,6 +885,9 @@ static int ad7173_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
>  	      FIELD_PREP(AD7173_CH_SETUP_SEL_MASK, st->channels[channel].cfg.cfg_slot) |
>  	      st->channels[channel].ain;
>  
> +	if (st->channels[channel].cfg.openwire_comp_chan >= 0)
> +		channel = st->channels[channel].cfg.openwire_comp_chan;
> +
>  	return ad_sd_write_reg(&st->sd, AD7173_REG_CH(channel), 2, val);
>  }
>  
> @@ -861,6 +936,11 @@ static int ad7173_disable_all(struct ad_sigma_delta *sd)
>  
>  static int ad7173_disable_one(struct ad_sigma_delta *sd, unsigned int chan)
>  {
> +	struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd);
> +
> +	if (st->channels[chan].cfg.openwire_comp_chan >= 0)
> +		chan = st->channels[chan].cfg.openwire_comp_chan;
> +
>  	return ad_sd_write_reg(sd, AD7173_REG_CH(chan), 2, 0);
>  }
>  
> @@ -968,6 +1048,9 @@ static int ad7173_read_raw(struct iio_dev *indio_dev,
>  		if (ret < 0)
>  			return ret;
>  
> +		if (ch->openwire_det_en)
> +			ad4111_openwire_event(indio_dev, chan);
> +
>  		return IIO_VAL_INT;
>  	case IIO_CHAN_INFO_SCALE:
>  
> @@ -1112,12 +1195,71 @@ static int ad7173_debug_reg_access(struct iio_dev *indio_dev, unsigned int reg,
>  	return ad_sd_write_reg(&st->sd, reg, reg_size, writeval);
>  }
>  
> +static int ad7173_write_event_config(struct iio_dev *indio_dev,
> +				     const struct iio_chan_spec *chan,
> +				     enum iio_event_type type,
> +				     enum iio_event_direction dir,
> +				     bool state)
> +{
> +	struct ad7173_state *st = iio_priv(indio_dev);
> +	struct ad7173_channel *adchan = &st->channels[chan->address];
> +
> +	adchan->openwire_det_en = state;
> +
> +	return 0;
> +}
> +
> +static int ad7173_write_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan,
> +				    enum iio_event_type type, enum iio_event_direction dir,
> +				    enum iio_event_info info, int val, int val2)
> +{
> +	struct ad7173_state *st = iio_priv(indio_dev);
> +	struct ad7173_channel *adchan = &st->channels[chan->address];
> +
> +	switch (info) {
> +	case IIO_EV_INFO_VALUE:
> +		adchan->cfg.openwire_thrsh_raw = val;

This difference threshold seems like an internal implementation detail of how
this specific chip handles open wire detection. I'm not sure that writing
this value to the events/in_voltageY_openwire_value attribute makes much sense
from an IIO ABI point of view.

It would be OK with me to just always use 300mV for now. And if we find an
application where that doesn't work, consider adding an adjustment somewhere.
For example, we might find that it makes more sense to put this in the
devictree because it depends on electrical properties of the application.

> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int ad7173_read_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan,
> +				   enum iio_event_type type, enum iio_event_direction dir,
> +				   enum iio_event_info info, int *val, int *val2)
> +{
> +	struct ad7173_state *st = iio_priv(indio_dev);
> +	struct ad7173_channel *adchan = &st->channels[chan->address];
> +
> +	switch (info) {
> +	case IIO_EV_INFO_VALUE:
> +		*val = adchan->cfg.openwire_thrsh_raw;
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static const struct iio_event_spec ad4111_events[] = {
> +	{
> +		.type = IIO_EV_TYPE_OPENWIRE,
> +		.dir = IIO_EV_DIR_EITHER,
> +		.mask_separate = BIT(IIO_EV_INFO_VALUE),
> +		.mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE),
> +	},
> +};
> +
>  static const struct iio_info ad7173_info = {
>  	.read_raw = &ad7173_read_raw,
>  	.write_raw = &ad7173_write_raw,
>  	.debugfs_reg_access = &ad7173_debug_reg_access,
>  	.validate_trigger = ad_sd_validate_trigger,
>  	.update_scan_mode = ad7173_update_scan_mode,

No read_event_config ?

> +	.write_event_config = ad7173_write_event_config,
> +	.write_event_value = ad7173_write_event_value,
> +	.read_event_value = ad7173_read_event_value,
>  };
>  
>  static const struct iio_scan_type ad4113_scan_type = {
> @@ -1321,6 +1463,15 @@ static int ad7173_validate_reference(struct ad7173_state *st, int ref_sel)
>  	return 0;
>  }
>  
> +static int ad7173_validate_openwire_ain_inputs(struct ad7173_state *st, bool differential,
> +					       unsigned int ain0, unsigned int ain1)
> +{
> +	if (differential)

A comment here explaining why this check would be helpful.

> +		return (ain0 % 2) ? (ain0 - 1) == ain1 : (ain0 + 1) == ain1;
> +
> +	return ain1 == AD4111_VINCOM_INPUT;
> +}
> +
>  static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
>  {
>  	struct ad7173_channel *chans_st_arr, *chan_st_priv;
> @@ -1375,6 +1526,7 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
>  		chan_st_priv->cfg.bipolar = false;
>  		chan_st_priv->cfg.input_buf = st->info->has_input_buf;
>  		chan_st_priv->cfg.ref_sel = AD7173_SETUP_REF_SEL_INT_REF;
> +		chan_st_priv->cfg.openwire_comp_chan = -1;
>  		st->adc_mode |= AD7173_ADC_MODE_REF_EN;
>  		if (st->info->data_reg_only_16bit)
>  			chan_arr[chan_index].scan_type = ad4113_scan_type;
> @@ -1442,6 +1594,7 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
>  		chan_st_priv->chan_reg = chan_index;
>  		chan_st_priv->cfg.input_buf = st->info->has_input_buf;
>  		chan_st_priv->cfg.odr = 0;
> +		chan_st_priv->cfg.openwire_comp_chan = -1;

Setting the same value twice?

>  
>  		chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar");
>  		if (chan_st_priv->cfg.bipolar)
> @@ -1456,6 +1609,17 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
>  			chan_st_priv->cfg.input_buf = st->info->has_input_buf;
>  			chan->channel2 = ain[1];
>  			chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]);
> +			if (st->info->has_openwire_det &&
> +			    ad7173_validate_openwire_ain_inputs(st, chan->differential, ain[0], ain[1])) {
> +				chan->event_spec = ad4111_events;
> +				chan->num_event_specs = ARRAY_SIZE(ad4111_events);
> +				chan_st_priv->cfg.openwire_thrsh_raw =
> +					BIT(chan->scan_type.realbits - !!(chan_st_priv->cfg.bipolar))
> +					* AD4111_OW_DET_THRSH_MV
> +					/ ad7173_get_ref_voltage_milli(st, chan_st_priv->cfg.ref_sel);
> +				if (chan->channel < st->info->num_voltage_in_div)
> +					chan_st_priv->cfg.openwire_thrsh_raw /= AD4111_DIVIDER_RATIO;
> +			}
>  		}
>  
>  		if (st->info->data_reg_only_16bit)
> 

Overall, I think the way this is implemented looks reasonable. We'll see how
much Jonathan agrees or disagrees my comments on the ABI stuff. :-)


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ