[PATCH v2] media: solo6x10: Propagate I2C read errors

Ruoyu Wang posted 1 patch an hour ago
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(-)
[PATCH v2] media: solo6x10: Propagate I2C read errors
Posted by Ruoyu Wang an hour ago
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@gmail.com>
---
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@gmail.com/

 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