Re: [PATCH v2] media: solo6x10: Propagate I2C read errors
From: Ismael Luceno
Date: Fri Aug 14 2026 - 13:09:47 EST
On 14/Aug/2026 21:41, Ruoyu Wang wrote:
> solo_i2c_readbyte() ignores the number of messages completed by
> i2c_transfer() and returns the read byte even when the transfer did not
> complete. A short transfer can therefore expose an uninitialized stack
> byte to chip detection, input-status queries, and ALSA gain controls.
>
> Return status separately from the output byte and map short transfers to
> -EIO. Propagate failures where callers provide an error channel. At
> input-status and write-and-verify sites, avoid consuming the output after
> a failed read while retaining the existing ioctl and best-effort retry
> behavior.
>
> This issue was found by a static analysis checker and confirmed by manual
> source review.
>
> Fixes: faa4fd2a0951 ("Staging: solo6x10: New driver (staging) for Softlogic 6x10")
> Signed-off-by: Ruoyu Wang <ruoyuw560@xxxxxxxxx>
> ---
> Changes in v2:
> - replace the zero-initialization fallback with an explicit error channel;
> - update every active caller without making ENUMINPUT fail on status-read
> errors;
> - preserve the existing best-effort write-and-verify policy and leave disabled
> code untouched;
> - rebase onto media-committers next at 4900cad020c0.
>
> v1: https://lore.kernel.org/r/20260813153120.3952770-1-ruoyuw560@xxxxxxxxx/
>
> drivers/media/pci/solo6x10/solo6x10-g723.c | 17 ++-
> drivers/media/pci/solo6x10/solo6x10-i2c.c | 15 ++-
> drivers/media/pci/solo6x10/solo6x10-tw28.c | 114 ++++++++++++------
> drivers/media/pci/solo6x10/solo6x10-tw28.h | 4 +-
> .../media/pci/solo6x10/solo6x10-v4l2-enc.c | 4 +-
> drivers/media/pci/solo6x10/solo6x10-v4l2.c | 5 +-
> drivers/media/pci/solo6x10/solo6x10.h | 3 +-
> 7 files changed, 108 insertions(+), 54 deletions(-)
>
> diff --git a/drivers/media/pci/solo6x10/solo6x10-g723.c b/drivers/media/pci/solo6x10/solo6x10-g723.c
> index e41b8d90a30ecc..5138a6ec55df61 100644
> --- a/drivers/media/pci/solo6x10/solo6x10-g723.c
> +++ b/drivers/media/pci/solo6x10/solo6x10-g723.c
> @@ -257,8 +257,13 @@ static int snd_solo_capture_volume_get(struct snd_kcontrol *kcontrol,
> {
> struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol);
> u8 ch = value->id.numid - 1;
> + u8 gain;
> + int ret;
>
> - value->value.integer.value[0] = tw28_get_audio_gain(solo_dev, ch);
> + ret = tw28_get_audio_gain(solo_dev, ch, &gain);
> + if (ret)
> + return ret;
> + value->value.integer.value[0] = gain;
>
> return 0;
> }
> @@ -269,14 +274,16 @@ static int snd_solo_capture_volume_put(struct snd_kcontrol *kcontrol,
> struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol);
> u8 ch = value->id.numid - 1;
> u8 old_val;
> + int ret;
>
> - old_val = tw28_get_audio_gain(solo_dev, ch);
> + ret = tw28_get_audio_gain(solo_dev, ch, &old_val);
> + if (ret)
> + return ret;
> if (old_val == value->value.integer.value[0])
> return 0;
>
> - tw28_set_audio_gain(solo_dev, ch, value->value.integer.value[0]);
> -
> - return 1;
> + ret = tw28_set_audio_gain(solo_dev, ch, value->value.integer.value[0]);
> + return ret ? ret : 1;
> }
>
> static const struct snd_kcontrol_new snd_solo_capture_volume = {
> diff --git a/drivers/media/pci/solo6x10/solo6x10-i2c.c b/drivers/media/pci/solo6x10/solo6x10-i2c.c
> index 7db785e9c99791..52f8a95c370d72 100644
> --- a/drivers/media/pci/solo6x10/solo6x10-i2c.c
> +++ b/drivers/media/pci/solo6x10/solo6x10-i2c.c
> @@ -22,10 +22,11 @@
>
> #include "solo6x10.h"
>
> -u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off)
> +int solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off,
> + u8 *data)
> {
> struct i2c_msg msgs[2];
> - u8 data;
> + int ret;
>
> msgs[0].flags = 0;
> msgs[0].addr = addr;
> @@ -35,11 +36,15 @@ u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off)
> msgs[1].flags = I2C_M_RD;
> msgs[1].addr = addr;
> msgs[1].len = 1;
> - msgs[1].buf = &data;
> + msgs[1].buf = data;
>
> - i2c_transfer(&solo_dev->i2c_adap[id], msgs, 2);
> + ret = i2c_transfer(&solo_dev->i2c_adap[id], msgs, ARRAY_SIZE(msgs));
> + if (ret == ARRAY_SIZE(msgs))
> + return 0;
> + if (ret < 0)
> + return ret;
>
> - return data;
> + return -EIO;
> }
>
> void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr,
> diff --git a/drivers/media/pci/solo6x10/solo6x10-tw28.c b/drivers/media/pci/solo6x10/solo6x10-tw28.c
> index 8f53946c67928f..66a9fd1e04ee63 100644
> --- a/drivers/media/pci/solo6x10/solo6x10-tw28.c
> +++ b/drivers/media/pci/solo6x10/solo6x10-tw28.c
> @@ -168,17 +168,17 @@ static const u8 tbl_tw2865_pal_template[] = {
>
> #define is_tw286x(__solo, __id) (!((__solo)->tw2815 & (1U << (__id))))
>
> -static u8 tw_readbyte(struct solo_dev *solo_dev, int chip_id, u8 tw6x_off,
> - u8 tw_off)
> +static int tw_readbyte(struct solo_dev *solo_dev, int chip_id, u8 tw6x_off,
> + u8 tw_off, u8 *val)
> {
> if (is_tw286x(solo_dev, chip_id))
> return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> TW_CHIP_OFFSET_ADDR(chip_id),
> - tw6x_off);
> + tw6x_off, val);
> else
> return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> TW_CHIP_OFFSET_ADDR(chip_id),
> - tw_off);
> + tw_off, val);
> }
>
> static void tw_writebyte(struct solo_dev *solo_dev, int chip_id,
> @@ -200,9 +200,10 @@ static void tw_write_and_verify(struct solo_dev *solo_dev, u8 addr, u8 off,
> int i;
>
> for (i = 0; i < 5; i++) {
> - u8 rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, addr, off);
> + u8 rval;
>
> - if (rval == val)
> + if (!solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, addr, off,
> + &rval) && rval == val)
> return;
>
> solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, addr, off, val);
> @@ -582,14 +583,17 @@ static void saa712x_setup(struct solo_dev *dev)
> int solo_tw28_init(struct solo_dev *solo_dev)
> {
> int i;
> + int ret;
> u8 value;
>
> solo_dev->tw28_cnt = 0;
>
> /* Detect techwell chip type(s) */
> for (i = 0; i < solo_dev->nr_chans / 4; i++) {
> - value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> - TW_CHIP_OFFSET_ADDR(i), 0xFF);
> + ret = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> + TW_CHIP_OFFSET_ADDR(i), 0xFF, &value);
> + if (ret)
> + return ret;
>
> switch (value >> 3) {
> case 0x18:
> @@ -602,9 +606,11 @@ int solo_tw28_init(struct solo_dev *solo_dev)
> solo_dev->tw28_cnt++;
> break;
> default:
> - value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> - TW_CHIP_OFFSET_ADDR(i),
> - 0x59);
> + ret = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> + TW_CHIP_OFFSET_ADDR(i), 0x59,
> + &value);
> + if (ret)
> + return ret;
> if ((value >> 3) == 0x04) {
> solo_dev->tw2815 |= 1 << i;
> solo_dev->tw28_cnt++;
> @@ -641,13 +647,17 @@ int solo_tw28_init(struct solo_dev *solo_dev)
> int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch)
> {
> u8 val, chip_num;
> + int ret;
>
> /* Get the right chip and on-chip channel */
> chip_num = ch / 4;
> ch %= 4;
>
> - val = tw_readbyte(solo_dev, chip_num, TW286x_AV_STAT_ADDR,
> - TW_AV_STAT_ADDR) & 0x0f;
> + ret = tw_readbyte(solo_dev, chip_num, TW286x_AV_STAT_ADDR,
> + TW_AV_STAT_ADDR, &val);
> + if (ret)
> + return ret;
> + val &= 0x0f;
>
> return val & (1 << ch) ? 1 : 0;
> }
> @@ -681,6 +691,7 @@ int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch,
> {
> char sval;
> u8 chip_num;
> + int ret;
>
> /* Get the right chip and on-chip channel */
> chip_num = ch / 4;
> @@ -696,9 +707,13 @@ int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch,
> case V4L2_CID_SHARPNESS:
> /* Only 286x has sharpness */
> if (is_tw286x(solo_dev, chip_num)) {
> - u8 v = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> - TW_CHIP_OFFSET_ADDR(chip_num),
> - TW286x_SHARPNESS(chip_num));
> + u8 v;
> +
> + ret = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> + TW_CHIP_OFFSET_ADDR(chip_num),
> + TW286x_SHARPNESS(chip_num), &v);
> + if (ret)
> + return ret;
> v &= 0xf0;
> v |= val;
> solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
> @@ -756,6 +771,7 @@ int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch,
> s32 *val)
> {
> u8 rval, chip_num;
> + int ret;
>
> /* Get the right chip and on-chip channel */
> chip_num = ch / 4;
> @@ -768,35 +784,48 @@ int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch,
> case V4L2_CID_SHARPNESS:
> /* Only 286x has sharpness */
> if (is_tw286x(solo_dev, chip_num)) {
> - rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> - TW_CHIP_OFFSET_ADDR(chip_num),
> - TW286x_SHARPNESS(chip_num));
> + ret = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
> + TW_CHIP_OFFSET_ADDR(chip_num),
> + TW286x_SHARPNESS(chip_num),
> + &rval);
> + if (ret)
> + return ret;
> *val = rval & 0x0f;
> } else
> *val = 0;
> break;
> case V4L2_CID_HUE:
> - rval = tw_readbyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch),
> - TW_HUE_ADDR(ch));
> + ret = tw_readbyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch),
> + TW_HUE_ADDR(ch), &rval);
> + if (ret)
> + return ret;
> if (is_tw286x(solo_dev, chip_num))
> *val = (s32)((char)rval) + 128;
> else
> *val = rval;
> break;
> case V4L2_CID_SATURATION:
> - *val = tw_readbyte(solo_dev, chip_num,
> - TW286x_SATURATIONU_ADDR(ch),
> - TW_SATURATION_ADDR(ch));
> + ret = tw_readbyte(solo_dev, chip_num,
> + TW286x_SATURATIONU_ADDR(ch),
> + TW_SATURATION_ADDR(ch), &rval);
> + if (ret)
> + return ret;
> + *val = rval;
> break;
> case V4L2_CID_CONTRAST:
> - *val = tw_readbyte(solo_dev, chip_num,
> - TW286x_CONTRAST_ADDR(ch),
> - TW_CONTRAST_ADDR(ch));
> + ret = tw_readbyte(solo_dev, chip_num,
> + TW286x_CONTRAST_ADDR(ch),
> + TW_CONTRAST_ADDR(ch), &rval);
> + if (ret)
> + return ret;
> + *val = rval;
> break;
> case V4L2_CID_BRIGHTNESS:
> - rval = tw_readbyte(solo_dev, chip_num,
> - TW286x_BRIGHTNESS_ADDR(ch),
> - TW_BRIGHTNESS_ADDR(ch));
> + ret = tw_readbyte(solo_dev, chip_num,
> + TW286x_BRIGHTNESS_ADDR(ch),
> + TW_BRIGHTNESS_ADDR(ch), &rval);
> + if (ret)
> + return ret;
> if (is_tw286x(solo_dev, chip_num))
> *val = (s32)((char)rval) + 128;
> else
> @@ -832,38 +861,45 @@ void tw2815_Set_AudioOutVol(struct solo_dev *solo_dev, unsigned int u_val)
> }
> #endif
>
> -u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch)
> +int tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 *val)
> {
> - u8 val;
> u8 chip_num;
> + int ret;
>
> /* Get the right chip and on-chip channel */
> chip_num = ch / 4;
> ch %= 4;
>
> - val = tw_readbyte(solo_dev, chip_num,
> + ret = tw_readbyte(solo_dev, chip_num,
> TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
> - TW_AUDIO_INPUT_GAIN_ADDR(ch));
> + TW_AUDIO_INPUT_GAIN_ADDR(ch), val);
> + if (ret)
> + return ret;
>
> - return (ch % 2) ? (val >> 4) : (val & 0x0f);
> + *val = (ch % 2) ? (*val >> 4) : (*val & 0x0f);
> + return 0;
> }
>
> -void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val)
> +int tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val)
> {
> u8 old_val;
> u8 chip_num;
> + int ret;
>
> /* Get the right chip and on-chip channel */
> chip_num = ch / 4;
> ch %= 4;
>
> - old_val = tw_readbyte(solo_dev, chip_num,
> - TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
> - TW_AUDIO_INPUT_GAIN_ADDR(ch));
> + ret = tw_readbyte(solo_dev, chip_num,
> + TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
> + TW_AUDIO_INPUT_GAIN_ADDR(ch), &old_val);
> + if (ret)
> + return ret;
>
> val = (old_val & ((ch % 2) ? 0x0f : 0xf0)) |
> ((ch % 2) ? (val << 4) : val);
>
> tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
> TW_AUDIO_INPUT_GAIN_ADDR(ch), val);
> + return 0;
> }
> diff --git a/drivers/media/pci/solo6x10/solo6x10-tw28.h b/drivers/media/pci/solo6x10/solo6x10-tw28.h
> index 4a8ede3139a856..a0feed7cf3ddee 100644
> --- a/drivers/media/pci/solo6x10/solo6x10-tw28.h
> +++ b/drivers/media/pci/solo6x10/solo6x10-tw28.h
> @@ -44,8 +44,8 @@ int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 val);
> int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 *val);
> bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch);
>
> -u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch);
> -void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val);
> +int tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 *val);
> +int tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val);
> int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch);
>
> #if 0
> diff --git a/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c b/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c
> index 91b5c416193036..dc79f88d17567f 100644
> --- a/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c
> +++ b/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c
> @@ -774,6 +774,7 @@ static int solo_enc_enum_input(struct file *file, void *priv,
> {
> struct solo_enc_dev *solo_enc = video_drvdata(file);
> struct solo_dev *solo_dev = solo_enc->solo_dev;
> + int ret;
>
> if (input->index)
> return -EINVAL;
> @@ -783,7 +784,8 @@ static int solo_enc_enum_input(struct file *file, void *priv,
> input->type = V4L2_INPUT_TYPE_CAMERA;
> input->std = solo_enc->vfd->tvnorms;
>
> - if (!tw28_get_video_status(solo_dev, solo_enc->ch))
> + ret = tw28_get_video_status(solo_dev, solo_enc->ch);
> + if (ret <= 0)
> input->status = V4L2_IN_ST_NO_SIGNAL;
>
> return 0;
> diff --git a/drivers/media/pci/solo6x10/solo6x10-v4l2.c b/drivers/media/pci/solo6x10/solo6x10-v4l2.c
> index 35715b21dbdffc..78cd07a800818a 100644
> --- a/drivers/media/pci/solo6x10/solo6x10-v4l2.c
> +++ b/drivers/media/pci/solo6x10/solo6x10-v4l2.c
> @@ -410,11 +410,14 @@ static int solo_enum_input(struct file *file, void *priv,
> if (ret < 0)
> return ret;
> } else {
> + int ret;
> +
> snprintf(input->name, sizeof(input->name), "Camera %d",
> input->index + 1);
>
> /* We can only check this for normal inputs */
> - if (!tw28_get_video_status(solo_dev, input->index))
> + ret = tw28_get_video_status(solo_dev, input->index);
> + if (ret <= 0)
> input->status = V4L2_IN_ST_NO_SIGNAL;
> }
>
> diff --git a/drivers/media/pci/solo6x10/solo6x10.h b/drivers/media/pci/solo6x10/solo6x10.h
> index 126f6fb7b755db..baacf99141aba9 100644
> --- a/drivers/media/pci/solo6x10/solo6x10.h
> +++ b/drivers/media/pci/solo6x10/solo6x10.h
> @@ -333,7 +333,8 @@ void solo_motion_isr(struct solo_dev *solo_dev);
> void solo_video_in_isr(struct solo_dev *solo_dev);
>
> /* i2c read/write */
> -u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off);
> +int solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off,
> + u8 *data);
> void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off,
> u8 data);
>
>
> base-commit: 4900cad020c0580dfb1be27776ff10a4ef110cfa
> --
> 2.51.0
Reviewed-by: Ismael Luceno <ismael@xxxxxxxxxxx>