mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00
iio: adc: ad7173: prevent scan if too many setups requested
Add a check to ad7173_update_scan_mode() to ensure that we didn't exceed the maximum number of unique channel configurations. In the AD7173 family of chips, there are some chips that have 16 CHANNELx registers but only 8 setups (combination of CONFIGx, FILTERx, GAINx and OFFSETx registers). Since commit92c2472169
("iio: adc: ad7173: fix num_slots"), it is possible to have more than 8 channels enabled in a scan at the same time, so it is possible to get a bad configuration when more than 8 channels are using unique configurations. This happens because the algorithm to allocate the setup slots only takes into account which slot has been least recently used and doesn't know about the maximum number of slots available. Since the algorithm to allocate the setup slots is quite complex, it is simpler to check after the fact if the current state is valid or not. So this patch adds a check in ad7173_update_scan_mode() after setting up all of the configurations to make sure that the actual setup still matches the requested setup for each enabled channel. If not, we prevent the scan from being enabled and return an error. The setup comparison in ad7173_setup_equal() is refactored to a separate function since we need to call it in two places now. Fixes:92c2472169
("iio: adc: ad7173: fix num_slots") Signed-off-by: David Lechner <dlechner@baylibre.com> Link: https://patch.msgid.link/20250722-iio-adc-ad7173-fix-setup-use-limits-v2-1-8e96bdb72a9c@baylibre.com Cc: <Stable@vger.kernel.org> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
parent
de18e978d0
commit
1cfb22c277
@ -200,7 +200,7 @@ struct ad7173_channel_config {
|
|||||||
/*
|
/*
|
||||||
* Following fields are used to compare equality. If you
|
* Following fields are used to compare equality. If you
|
||||||
* make adaptations in it, you most likely also have to adapt
|
* make adaptations in it, you most likely also have to adapt
|
||||||
* ad7173_find_live_config(), too.
|
* ad7173_is_setup_equal(), too.
|
||||||
*/
|
*/
|
||||||
struct_group(config_props,
|
struct_group(config_props,
|
||||||
bool bipolar;
|
bool bipolar;
|
||||||
@ -561,12 +561,19 @@ static void ad7173_reset_usage_cnts(struct ad7173_state *st)
|
|||||||
st->config_usage_counter = 0;
|
st->config_usage_counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ad7173_channel_config *
|
/**
|
||||||
ad7173_find_live_config(struct ad7173_state *st, struct ad7173_channel_config *cfg)
|
* ad7173_is_setup_equal - Compare two channel setups
|
||||||
|
* @cfg1: First channel configuration
|
||||||
|
* @cfg2: Second channel configuration
|
||||||
|
*
|
||||||
|
* Compares all configuration options that affect the registers connected to
|
||||||
|
* SETUP_SEL, namely CONFIGx, FILTERx, GAINx and OFFSETx.
|
||||||
|
*
|
||||||
|
* Returns: true if the setups are identical, false otherwise
|
||||||
|
*/
|
||||||
|
static bool ad7173_is_setup_equal(const struct ad7173_channel_config *cfg1,
|
||||||
|
const struct ad7173_channel_config *cfg2)
|
||||||
{
|
{
|
||||||
struct ad7173_channel_config *cfg_aux;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is just to make sure that the comparison is adapted after
|
* This is just to make sure that the comparison is adapted after
|
||||||
* struct ad7173_channel_config was changed.
|
* struct ad7173_channel_config was changed.
|
||||||
@ -579,14 +586,22 @@ ad7173_find_live_config(struct ad7173_state *st, struct ad7173_channel_config *c
|
|||||||
u8 ref_sel;
|
u8 ref_sel;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
return cfg1->bipolar == cfg2->bipolar &&
|
||||||
|
cfg1->input_buf == cfg2->input_buf &&
|
||||||
|
cfg1->odr == cfg2->odr &&
|
||||||
|
cfg1->ref_sel == cfg2->ref_sel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ad7173_channel_config *
|
||||||
|
ad7173_find_live_config(struct ad7173_state *st, struct ad7173_channel_config *cfg)
|
||||||
|
{
|
||||||
|
struct ad7173_channel_config *cfg_aux;
|
||||||
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < st->num_channels; i++) {
|
for (i = 0; i < st->num_channels; i++) {
|
||||||
cfg_aux = &st->channels[i].cfg;
|
cfg_aux = &st->channels[i].cfg;
|
||||||
|
|
||||||
if (cfg_aux->live &&
|
if (cfg_aux->live && ad7173_is_setup_equal(cfg, cfg_aux))
|
||||||
cfg->bipolar == cfg_aux->bipolar &&
|
|
||||||
cfg->input_buf == cfg_aux->input_buf &&
|
|
||||||
cfg->odr == cfg_aux->odr &&
|
|
||||||
cfg->ref_sel == cfg_aux->ref_sel)
|
|
||||||
return cfg_aux;
|
return cfg_aux;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -1228,7 +1243,7 @@ static int ad7173_update_scan_mode(struct iio_dev *indio_dev,
|
|||||||
const unsigned long *scan_mask)
|
const unsigned long *scan_mask)
|
||||||
{
|
{
|
||||||
struct ad7173_state *st = iio_priv(indio_dev);
|
struct ad7173_state *st = iio_priv(indio_dev);
|
||||||
int i, ret;
|
int i, j, k, ret;
|
||||||
|
|
||||||
for (i = 0; i < indio_dev->num_channels; i++) {
|
for (i = 0; i < indio_dev->num_channels; i++) {
|
||||||
if (test_bit(i, scan_mask))
|
if (test_bit(i, scan_mask))
|
||||||
@ -1239,6 +1254,54 @@ static int ad7173_update_scan_mode(struct iio_dev *indio_dev,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On some chips, there are more channels that setups, so if there were
|
||||||
|
* more unique setups requested than the number of available slots,
|
||||||
|
* ad7173_set_channel() will have written over some of the slots. We
|
||||||
|
* can detect this by making sure each assigned cfg_slot matches the
|
||||||
|
* requested configuration. If it doesn't, we know that the slot was
|
||||||
|
* overwritten by a different channel.
|
||||||
|
*/
|
||||||
|
for_each_set_bit(i, scan_mask, indio_dev->num_channels) {
|
||||||
|
const struct ad7173_channel_config *cfg1, *cfg2;
|
||||||
|
|
||||||
|
cfg1 = &st->channels[i].cfg;
|
||||||
|
|
||||||
|
for_each_set_bit(j, scan_mask, indio_dev->num_channels) {
|
||||||
|
cfg2 = &st->channels[j].cfg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only compare configs that are assigned to the same
|
||||||
|
* SETUP_SEL slot and don't compare channel to itself.
|
||||||
|
*/
|
||||||
|
if (i == j || cfg1->cfg_slot != cfg2->cfg_slot)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we find two different configs trying to use the
|
||||||
|
* same SETUP_SEL slot, then we know that the that we
|
||||||
|
* have too many unique configurations requested for
|
||||||
|
* the available slots and at least one was overwritten.
|
||||||
|
*/
|
||||||
|
if (!ad7173_is_setup_equal(cfg1, cfg2)) {
|
||||||
|
/*
|
||||||
|
* At this point, there isn't a way to tell
|
||||||
|
* which setups are actually programmed in the
|
||||||
|
* ADC anymore, so we could read them back to
|
||||||
|
* see, but it is simpler to just turn off all
|
||||||
|
* of the live flags so that everything gets
|
||||||
|
* reprogramed on the next attempt read a sample.
|
||||||
|
*/
|
||||||
|
for (k = 0; k < st->num_channels; k++)
|
||||||
|
st->channels[k].cfg.live = false;
|
||||||
|
|
||||||
|
dev_err(&st->sd.spi->dev,
|
||||||
|
"Too many unique channel configurations requested for scan\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user