summaryrefslogtreecommitdiff
path: root/drivers/media/usb
diff options
context:
space:
mode:
authorMichael Bunk <micha@freedict.org>2022-01-16 14:22:36 +0300
committerMauro Carvalho Chehab <mchehab@kernel.org>2024-04-29 11:21:50 +0300
commit0e148a522b8453115038193e19ec7bea71403e4a (patch)
treedd77a45eb230c68a2b00461bc6cb06b64698aef8 /drivers/media/usb
parentc6ad2b9218d6d9538f60040ad12621ecb1274a0f (diff)
downloadlinux-0e148a522b8453115038193e19ec7bea71403e4a.tar.xz
media: dw2102: Don't translate i2c read into write
The code ignored the I2C_M_RD flag on I2C messages. Instead it assumed an i2c transaction with a single message must be a write operation and a transaction with two messages would be a read operation. Though this works for the driver code, it leads to problems once the i2c device is exposed to code not knowing this convention. For example, I did "insmod i2c-dev" and issued read requests from userspace, which were translated into write requests and destroyed the EEPROM of my device. So, just check and respect the I2C_M_READ flag, which indicates a read when set on a message. If it is absent, it is a write message. Incidentally, changing from the case statement to a while loop allows the code to lift the limitation to two i2c messages per transaction. There are 4 more *_i2c_transfer functions affected by the same behaviour and limitation that should be fixed in the same way. Link: https://lore.kernel.org/linux-media/20220116112238.74171-2-micha@freedict.org Signed-off-by: Michael Bunk <micha@freedict.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
Diffstat (limited to 'drivers/media/usb')
-rw-r--r--drivers/media/usb/dvb-usb/dw2102.c120
1 files changed, 73 insertions, 47 deletions
diff --git a/drivers/media/usb/dvb-usb/dw2102.c b/drivers/media/usb/dvb-usb/dw2102.c
index b3bb1805829a..10351308b0d0 100644
--- a/drivers/media/usb/dvb-usb/dw2102.c
+++ b/drivers/media/usb/dvb-usb/dw2102.c
@@ -716,6 +716,7 @@ static int su3000_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
{
struct dvb_usb_device *d = i2c_get_adapdata(adap);
struct dw2102_state *state;
+ int j;
if (!d)
return -ENODEV;
@@ -729,11 +730,11 @@ static int su3000_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
return -EAGAIN;
}
- switch (num) {
- case 1:
- switch (msg[0].addr) {
+ j = 0;
+ while (j < num) {
+ switch (msg[j].addr) {
case SU3000_STREAM_CTRL:
- state->data[0] = msg[0].buf[0] + 0x36;
+ state->data[0] = msg[j].buf[0] + 0x36;
state->data[1] = 3;
state->data[2] = 0;
if (dvb_usb_generic_rw(d, state->data, 3,
@@ -745,61 +746,86 @@ static int su3000_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
if (dvb_usb_generic_rw(d, state->data, 1,
state->data, 2, 0) < 0)
err("i2c transfer failed.");
- msg[0].buf[1] = state->data[0];
- msg[0].buf[0] = state->data[1];
+ msg[j].buf[1] = state->data[0];
+ msg[j].buf[0] = state->data[1];
break;
default:
- if (3 + msg[0].len > sizeof(state->data)) {
- warn("i2c wr: len=%d is too big!\n",
- msg[0].len);
+ /* if the current write msg is followed by a another
+ * read msg to/from the same address
+ */
+ if ((j+1 < num) && (msg[j+1].flags & I2C_M_RD) &&
+ (msg[j].addr == msg[j+1].addr)) {
+ /* join both i2c msgs to one usb read command */
+ if (4 + msg[j].len > sizeof(state->data)) {
+ warn("i2c combined wr/rd: write len=%d is too big!\n",
+ msg[j].len);
+ num = -EOPNOTSUPP;
+ break;
+ }
+ if (1 + msg[j+1].len > sizeof(state->data)) {
+ warn("i2c combined wr/rd: read len=%d is too big!\n",
+ msg[j+1].len);
+ num = -EOPNOTSUPP;
+ break;
+ }
+
+ state->data[0] = 0x09;
+ state->data[1] = msg[j].len;
+ state->data[2] = msg[j+1].len;
+ state->data[3] = msg[j].addr;
+ memcpy(&state->data[4], msg[j].buf, msg[j].len);
+
+ if (dvb_usb_generic_rw(d, state->data, msg[j].len + 4,
+ state->data, msg[j+1].len + 1, 0) < 0)
+ err("i2c transfer failed.");
+
+ memcpy(msg[j+1].buf, &state->data[1], msg[j+1].len);
+ j++;
+ break;
+ }
+
+ if (msg[j].flags & I2C_M_RD) {
+ /* single read */
+ if (1 + msg[j].len > sizeof(state->data)) {
+ warn("i2c rd: len=%d is too big!\n", msg[j].len);
+ num = -EOPNOTSUPP;
+ break;
+ }
+
+ state->data[0] = 0x09;
+ state->data[1] = 0;
+ state->data[2] = msg[j].len;
+ state->data[3] = msg[j].addr;
+ memcpy(&state->data[4], msg[j].buf, msg[j].len);
+
+ if (dvb_usb_generic_rw(d, state->data, 4,
+ state->data, msg[j].len + 1, 0) < 0)
+ err("i2c transfer failed.");
+
+ memcpy(msg[j].buf, &state->data[1], msg[j].len);
+ break;
+ }
+
+ /* single write */
+ if (3 + msg[j].len > sizeof(state->data)) {
+ warn("i2c wr: len=%d is too big!\n", msg[j].len);
num = -EOPNOTSUPP;
break;
}
- /* always i2c write*/
state->data[0] = 0x08;
- state->data[1] = msg[0].addr;
- state->data[2] = msg[0].len;
+ state->data[1] = msg[j].addr;
+ state->data[2] = msg[j].len;
- memcpy(&state->data[3], msg[0].buf, msg[0].len);
+ memcpy(&state->data[3], msg[j].buf, msg[j].len);
- if (dvb_usb_generic_rw(d, state->data, msg[0].len + 3,
+ if (dvb_usb_generic_rw(d, state->data, msg[j].len + 3,
state->data, 1, 0) < 0)
err("i2c transfer failed.");
+ } // switch
+ j++;
- }
- break;
- case 2:
- /* always i2c read */
- if (4 + msg[0].len > sizeof(state->data)) {
- warn("i2c rd: len=%d is too big!\n",
- msg[0].len);
- num = -EOPNOTSUPP;
- break;
- }
- if (1 + msg[1].len > sizeof(state->data)) {
- warn("i2c rd: len=%d is too big!\n",
- msg[1].len);
- num = -EOPNOTSUPP;
- break;
- }
-
- state->data[0] = 0x09;
- state->data[1] = msg[0].len;
- state->data[2] = msg[1].len;
- state->data[3] = msg[0].addr;
- memcpy(&state->data[4], msg[0].buf, msg[0].len);
-
- if (dvb_usb_generic_rw(d, state->data, msg[0].len + 4,
- state->data, msg[1].len + 1, 0) < 0)
- err("i2c transfer failed.");
-
- memcpy(msg[1].buf, &state->data[1], msg[1].len);
- break;
- default:
- warn("more than 2 i2c messages at a time is not handled yet.");
- break;
- }
+ } // while
mutex_unlock(&d->data_mutex);
mutex_unlock(&d->i2c_mutex);
return num;