All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Stephan Costa Barros" <stephanbrrs8@gmail.com>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH 2/2] iio: adc: ti-ads1299: add driver for TI ADS1299
Date: Mon, 19 Oct 2026 00:31:29 +0000	[thread overview]
Message-ID: <sashiko-outbox-176464@kernel.org> (raw)
In-Reply-To: <20261019001247.483005-3-stephanbrrs8@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] iio: adc: ti-ads1299: unsynchronized async rdata_msg on stop and timeout
- [High] iio: adc: ti-ads1299: concurrent cmd_buffer access and shared DMA cacheline
- [Medium] iio: adc: ti-ads1299: incorrect power-up and reference start-up sequence in ads1299_probe()
- [Medium] iio: adc: ti-ads1299: unchecked spi_async() and rdata_msg.status errors
--

commit 3f1066cf1b7c3e1e9933416e0c037a37dcbe64ed
Author: Stephan Costa Barros <stephanbrrs8@gmail.com>

iio: adc: ti-ads1299: add driver for TI ADS1299

This patch adds an IIO ADC driver for the Texas Instruments ADS1299,
ADS1299-6, and ADS1299-4 24-bit biopotential analog front-ends over SPI.
It supports on-demand single-shot channel reads and buffered capture driven
by the DRDY interrupt.

> diff --git a/drivers/iio/adc/ti-ads1299.c b/drivers/iio/adc/ti-ads1299.c
> new file mode 100644
> index 0000000000000..f817a86cca5c9
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1299.c
> @@ -0,0 +1,897 @@

[ ... ]

> +struct ads1299_private {
> +	struct spi_device *spi;
> +	struct regmap *regmap;
> +	struct completion completion;
> +	struct spi_transfer rdata_xfer;
> +	struct spi_message rdata_msg;
> +	spinlock_t irq_busy_lock; /* Handshake between SPI and DRDY irqs */
> +	/*
> +	 * 0 = idle, 1 = transfer in progress, 2 = a sample became due during
> +	 * it; any more were lost.
> +	 */
> +	unsigned int rdata_xfer_busy;
> +	unsigned int num_adc_channels;
> +	int samp_freq_avail[ADS1299_NUM_DR];
> +	int scale_avail[ARRAY_SIZE(ads1299_pga_gain)][2];
> +
> +	struct {
> +		u32 chan[ADS1299_MAX_CHANNELS];
> +		aligned_s64 timestamp;
> +	} scan;
> +
> +	/* For synchronous SPI exchanges (read/write registers) */
> +	u8 cmd_buffer[ADS1299_SPI_CMD_BUFFER_SIZE] __aligned(IIO_DMA_MINALIGN);
> +
> +	u8 rx_buffer[ADS1299_SPI_RDATA_BUFFER_SIZE_MAX];
> +	/* Contains the RDATA command and zeroes to clock out */
> +	u8 tx_buffer[ADS1299_SPI_RDATA_BUFFER_SIZE_MAX];
> +};

[ ... ]

> +static int ads1299_write_cmd(struct ads1299_private *priv, u8 command)
> +{
> +	struct spi_transfer xfer = {
> +		.tx_buf = priv->cmd_buffer,
> +		.rx_buf = priv->cmd_buffer,
> +		.len = 1,
> +		.speed_hz = ADS1299_SPI_BUS_SPEED_SLOW,
> +		.delay = {
> +			.value = 2,
> +			.unit = SPI_DELAY_UNIT_USECS,
> +		},
> +	};
> +
> +	priv->cmd_buffer[0] = command;
> +
> +	return spi_sync_transfer(priv->spi, &xfer, 1);
> +}

[Severity: High]
Can priv->cmd_buffer be corrupted by concurrent accesses, or share a DMA
cacheline with priv->rx_buffer and priv->tx_buffer?

In ads1299_reg_read() and ads1299_reg_write(), priv->cmd_buffer is used as
both tx_buf and rx_buf under priv->regmap's internal lock. However,
ads1299_write_cmd() mutates and transfers priv->cmd_buffer under the IIO
device mode lock (via ads1299_read_one(), ads1299_buffer_postenable(), and
ads1299_buffer_predisable()) without acquiring the regmap lock.

Because ads1299_read_raw() (for IIO_CHAN_INFO_SCALE and
IIO_CHAN_INFO_SAMP_FREQ, where ADS1299_REG_CONFIG1 is uncached on first
read) and ads1299_reg_access() invoke regmap_read() and regmap_write()
without iio_device_claim_direct(), those paths can run concurrently with
ads1299_write_cmd() under disjoint locks and overwrite priv->cmd_buffer
during an active SPI transfer.

In addition, in struct ads1299_private, only cmd_buffer has
__aligned(IIO_DMA_MINALIGN), while rx_buffer and tx_buffer immediately
follow it in the same cacheline. On non-coherent platforms, can CPU or DMA
access to cmd_buffer (for example, during ads1299_buffer_predisable() or
sysfs/debugfs register reads) corrupt an in-flight rdata_msg DMA transfer
using rx_buffer and tx_buffer?

> +
> +static int ads1299_read_one(struct ads1299_private *priv, int chan_index)
> +{
> +	int ret;
> +
> +	/* Enable the channel */
> +	ret = regmap_update_bits(priv->regmap, ADS1299_REG_CHnSET(chan_index),
> +				 ADS1299_MASK_CH_PD, 0);
> +	if (ret)
> +		return ret;
> +
> +	/* Enable single-shot mode, so we don't need to send a STOP */
> +	ret = regmap_update_bits(priv->regmap, ADS1299_REG_CONFIG4,
> +				 ADS1299_MASK_CONFIG4_SINGLE_SHOT,
> +				 ADS1299_MASK_CONFIG4_SINGLE_SHOT);
> +	if (ret)
> +		return ret;
> +
> +	reinit_completion(&priv->completion);
> +
> +	ret = ads1299_write_cmd(priv, ADS1299_CMD_START);
> +	if (ret < 0) {
> +		dev_err(&priv->spi->dev, "CMD_START error: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* A conversion takes 4 ms at the lowest rate, plus bus latency */
> +	ret = wait_for_completion_timeout(&priv->completion, msecs_to_jiffies(100));
> +	if (!ret)
> +		return -ETIMEDOUT;
> +
> +	return 0;
> +}

[ ... ]

> +static void ads1299_rdata_release_busy_or_restart(struct ads1299_private *priv)
> +{
> +	guard(spinlock_irqsave)(&priv->irq_busy_lock);
> +
> +	if (priv->rdata_xfer_busy > 1) {
> +		/* A sample became due during the transfer: fetch it now */
> +		spi_async(priv->spi, &priv->rdata_msg);
> +		priv->rdata_xfer_busy = 1;
> +	} else {
> +		priv->rdata_xfer_busy = 0;
> +	}
> +}
> +
> +/* Called from SPI completion interrupt handler */
> +static void ads1299_rdata_complete(void *context)
> +{
> +	struct iio_dev *indio_dev = context;
> +	struct ads1299_private *priv = iio_priv(indio_dev);
> +	u32 *bounce = priv->scan.chan;
> +	int scan_index;
> +
> +	if (!iio_buffer_enabled(indio_dev)) {
> +		/*
> +		 * For a single transfer we are kept in direct mode until
> +		 * completion, avoiding a race with buffered IO.
> +		 */
> +		ads1299_rdata_unmark_busy(priv);
> +		complete(&priv->completion);
> +		return;
> +	}
> +
> +	iio_for_each_active_channel(indio_dev, scan_index) {
> +		const struct iio_chan_spec *scan_chan =
> +					&indio_dev->channels[scan_index];
> +
> +		if (scan_chan->type == IIO_TIMESTAMP)
> +			continue;
> +
> +		*bounce++ = get_unaligned_be24(priv->rx_buffer +
> +					       scan_chan->address);
> +	}
> +
> +	/* rx_buffer can be overwritten from this point on */
> +	ads1299_rdata_release_busy_or_restart(priv);
> +
> +	iio_push_to_buffers_with_ts(indio_dev, &priv->scan, sizeof(priv->scan),
> +				    iio_get_time_ns(indio_dev));
> +}
> +
> +static irqreturn_t ads1299_interrupt(int irq, void *dev_id)
> +{
> +	struct iio_dev *indio_dev = dev_id;
> +	struct ads1299_private *priv = iio_priv(indio_dev);
> +	unsigned int wasbusy;
> +
> +	guard(spinlock_irqsave)(&priv->irq_busy_lock);
> +
> +	wasbusy = priv->rdata_xfer_busy++;
> +	/* When no SPI transfer is in transit, start one now */
> +	if (!wasbusy)
> +		spi_async(priv->spi, &priv->rdata_msg);
> +
> +	return IRQ_HANDLED;
> +}

[Severity: Medium]
What happens if spi_async() fails in ads1299_interrupt() or
ads1299_rdata_release_busy_or_restart(), or if priv->rdata_msg completes
with an error?

In both functions, priv->rdata_xfer_busy is set to a non-zero value without
checking the return value of spi_async(). If spi_async() returns an error,
ads1299_rdata_complete() will never run, leaving priv->rdata_xfer_busy
non-zero so subsequent DRDY interrupts see wasbusy != 0 and stop starting
transfers.

Also, in ads1299_rdata_complete(), priv->rdata_msg.status is not checked
before completing priv->completion or pushing priv->rx_buffer data via
iio_push_to_buffers_with_ts(). Would a failed async SPI transfer report
stale or partial rx_buffer contents as valid ADC samples?

> +
> +static int ads1299_buffer_postenable(struct iio_dev *indio_dev)
> +{
> +	struct ads1299_private *priv = iio_priv(indio_dev);
> +	int ret;
> +
> +	/* Disable single-shot mode */
> +	ret = regmap_update_bits(priv->regmap, ADS1299_REG_CONFIG4,
> +				 ADS1299_MASK_CONFIG4_SINGLE_SHOT, 0);
> +	if (ret)
> +		return ret;
> +
> +	return ads1299_write_cmd(priv, ADS1299_CMD_START);
> +}
> +
> +static int ads1299_buffer_predisable(struct iio_dev *indio_dev)
> +{
> +	struct ads1299_private *priv = iio_priv(indio_dev);
> +
> +	return ads1299_write_cmd(priv, ADS1299_CMD_STOP);
> +}

[Severity: High]
Does ads1299_buffer_predisable() race with in-flight DRDY interrupts and
asynchronous priv->rdata_msg transfers?

When disabling the buffer, iio_disable_buffers() calls
ads1299_buffer_predisable(), which sends ADS1299_CMD_STOP and returns
immediately without synchronizing spi->irq, preventing
ads1299_rdata_release_busy_or_restart() from re-submitting priv->rdata_msg,
or waiting for in-flight priv->rdata_msg transfers to complete.

Right after predisable returns, iio_disable_buffers() frees
indio_dev->active_scan_mask and sets it to NULL before updating currentmode
to INDIO_DIRECT_MODE. If ads1299_rdata_complete() runs concurrently during
this window, iio_buffer_enabled(indio_dev) is still true while
iio_for_each_active_channel() dereferences indio_dev->active_scan_mask. If
it completes slightly later after currentmode becomes INDIO_DIRECT_MODE, it
will signal priv->completion and leave stale completion state for a
subsequent ads1299_read_one().

A similar issue exists if wait_for_completion_timeout() times out in
ads1299_read_one() while priv->rdata_msg is still in flight:
ads1299_read_raw() releases direct mode without waiting for priv->rdata_msg,
and a subsequent ads1299_update_scan_mode() calls
ads1299_rdata_unmark_busy() to reset priv->rdata_xfer_busy = 0, allowing the
next DRDY interrupt to pass an already-queued priv->rdata_msg to
spi_async(). Likewise, on driver unbind, devm tears down indio_dev without
quiescing the IRQ and flushing in-flight priv->rdata_msg transfers first.

[ ... ]

> +static int ads1299_init(struct iio_dev *indio_dev, bool internal_vref)
> +{

[ ... ]

> +	/* Without the reference buffer every channel reads zero */
> +	val = ADS1299_CONFIG3_RESERVED_VALUE;
> +	if (internal_vref)
> +		val |= ADS1299_MASK_CONFIG3_PWR_REFBUF;
> +	ret = regmap_write(priv->regmap, ADS1299_REG_CONFIG3, val);
> +	if (ret)
> +		return ret;
> +
> +	/* Out of reset the inputs are shorted: connect them */
> +	for (i = 0; i < priv->num_adc_channels; i++) {
> +		ret = regmap_update_bits(priv->regmap, ADS1299_REG_CHnSET(i),
> +					 ADS1299_MASK_CH_MUX,
> +					 FIELD_PREP(ADS1299_MASK_CH_MUX,
> +						    ADS1299_MUX_NORMAL));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}

[ ... ]

> +static int ads1299_probe(struct spi_device *spi)
> +{

[ ... ]

> +	/* Reset to be asserted before enabling clock and power */
> +	reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> +	if (IS_ERR(reset_gpio))
> +		return dev_err_probe(dev, PTR_ERR(reset_gpio),
> +				     "Cannot get reset GPIO\n");
> +
> +	powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown",
> +						 GPIOD_OUT_LOW);
> +	if (IS_ERR(powerdown_gpio))
> +		return dev_err_probe(dev, PTR_ERR(powerdown_gpio),
> +				     "Cannot get powerdown GPIO\n");
> +
> +	/* VREF can be supplied externally, otherwise use the internal 4.5 V */
> +	vref_uv = devm_regulator_get_enable_read_voltage(dev, "vref");
> +	if (vref_uv < 0 && vref_uv != -ENODEV)
> +		return dev_err_probe(dev, vref_uv, "Failed to get vref voltage\n");
> +
> +	internal_vref = vref_uv == -ENODEV;
> +	if (internal_vref)
> +		vref_uv = ADS1299_INTERNAL_VREF_UV;
> +
> +	clk = devm_clk_get_optional_enabled(dev, "clk");
> +	if (IS_ERR(clk))
> +		return dev_err_probe(dev, PTR_ERR(clk), "Failed to get clk\n");

[ ... ]

> +	ret = devm_regulator_get_enable(dev, "avdd");
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to enable avdd regulator\n");
> +
> +	ret = devm_regulator_get_enable(dev, "dvdd");
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to enable dvdd regulator\n");

[ ... ]

> +	/* Out of power-down: wait for the oscillator and the reference */
> +	if (powerdown_gpio)
> +		msleep(ADS1299_START_UP_MS);
> +
> +	if (reset_gpio) {
> +		/* Minimum reset pulse width is 2 clock cycles */
> +		fsleep(ADS1299_CLOCKS_TO_USECS(2));
> +		gpiod_set_value_cansleep(reset_gpio, 0);
> +	} else {
> +		ret = ads1299_write_cmd(priv, ADS1299_CMD_RESET);
> +		if (ret)
> +			return dev_err_probe(dev, ret, "RESET failed\n");
> +	}
> +	/* Wait 18 clock cycles for the reset to complete */
> +	fsleep(ADS1299_CLOCKS_TO_USECS(18));
> +
> +	ret = ads1299_init(indio_dev, internal_vref);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Init failed\n");

[Severity: Medium]
Is the power-up and reference start-up sequence in ads1299_probe() and
ads1299_init() ordered as intended?

First, powerdown_gpio is requested with GPIOD_OUT_LOW (deasserted) and vref
and clk are enabled before the avdd and dvdd regulators are enabled.

Second, msleep(ADS1299_START_UP_MS) is only executed when powerdown_gpio is
present. When the optional powerdown GPIO is omitted, the driver skips the
150 ms power-on start-up delay after enabling avdd and dvdd before releasing
reset or issuing ADS1299_CMD_RESET.

Third, out of reset, ADS1299_REG_CONFIG3 defaults to having the internal
reference buffer powered down, and ADS1299_MASK_CONFIG3_PWR_REFBUF is only
written later in ads1299_init(). Because msleep(ADS1299_START_UP_MS) runs
before reset is released (or before ADS1299_CMD_RESET) and before
ADS1299_MASK_CONFIG3_PWR_REFBUF is enabled, the internal reference buffer is
not given time to settle after being powered up.

[ ... ]

—
🤖 Generated by Linux Kernel Patch Reviewer (Model: gemini-3.1-pro-preview)
Prompt Version: 969354535492578f3c622ebd4ebddaf43de71bd4
Tools: git-2.53.0-1.fc43.x86_64
Date: 2026-10-19 06:58:26 UTC
Duration (ms): 229238
Tokens (in/out): 167213 / 11103
Prompts Used: build.md, callstack.md, cleanup.md, gpio.md, inline-template.md, kconfig.md, mm-alloc.md, of.md, subsystem/locking.md, technical-patterns.md

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019001247.483005-1-stephanbrrs8@gmail.com?part=2

  reply	other threads:[~2026-10-19  0:31 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19  0:12 [PATCH 0/2] iio: adc: add TI ADS1299 Stephan Costa Barros
2026-10-19  0:12 ` [PATCH 1/2] dt-bindings: " Stephan Costa Barros
2026-10-20  7:44   ` Krzysztof Kozlowski
2026-10-19  0:12 ` [PATCH 2/2] iio: adc: ti-ads1299: add driver for " Stephan Costa Barros
2026-10-19  0:31   ` sashiko-bot [this message]
2026-10-19  7:03   ` Uwe Kleine-König
2026-10-19 23:59     ` Stephan Costa Barros
2026-10-19 10:16 ` [PATCH 0/2] iio: adc: add " Andy Shevchenko
2026-10-20  0:00   ` Stephan Costa Barros

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-176464@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=stephanbrrs8@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.