Devicetree
 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox