analogdevicesinc / linux

Linux kernel variant from Analog Devices; see README.md for details

Home Page:https://github.com/analogdevicesinc/linux

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AXI SPI Engine driver modifies SPI xfer length.

dlech opened this issue · comments

30707b0 introduced a function spi_engine_update_xfer_len() that modifies the len field of each xfer in an SPI message. However, modifying the struct spi_transfer is problematic.

Since spi_engine_compile_message() actually gets called twice per message (once is a "dry" run to calculate the size, then again to actually write the commands to memory), spi_engine_update_xfer_len() is getting called twice and therefore modifies the length twice. This probably has gone unnoticed since the first call usually sets the length to 1 and the second call can't make it any smaller.

Modifying the members of struct spi_transfer in the SPI controller doesn't seem like a good idea in general since it doesn't own that memory. For example, a client driver may be reusing the struct spi_transfer and not write the length again before each transaction.

(The commit in question is not in the mainline kernel, but it fixes a potential issue that may still exist in the mainline kernel.)

Fixing this is likely going to break things that depend on this buggy behavoir. For example, in the ad_pulsar driver.

static int ad_pulsar_read_channel(struct ad_pulsar_adc *adc, unsigned int reg,
unsigned int *val)
{
struct spi_transfer xfer = {
.bits_per_word = adc->info->resolution,
.speed_hz = adc->info->sclk_rate,
.len = 4,
};
int ret;
adc->cfg = reg;
put_unaligned_be16(reg << 2, adc->spi_tx_data);
if (adc->info->cfg_register)
xfer.tx_buf = adc->spi_tx_data;
xfer.rx_buf = adc->spi_rx_data;
ret = spi_sync_transfer(adc->spi, &xfer, 1);
if (ret)
return ret;
*val = get_unaligned_le32(adc->spi_rx_data);
return ret;
}

For a single conversion (reading the _raw sysfs attribute), the xfer len is always 4 even when the ADC is 16 bits or less. Due to the double evaluation of spi_engine_update_xfer_len(), the len gets modified to 1 word which is correct for those ADCs. After fixing, this will instruct it to read two word instead when it only needs to read one.

I guess my comments in #2300 can also be applied in here