This is an automatic generated email to let you know that the following patch 
were queued:

Subject: media: dw2102: Don't translate i2c read into write
Author:  Michael Bunk <mi...@freedict.org>
Date:    Sun Jan 16 11:22:36 2022 +0000

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-mi...@freedict.org
Signed-off-by: Michael Bunk <mi...@freedict.org>
Signed-off-by: Mauro Carvalho Chehab <mche...@kernel.org>

 drivers/media/usb/dvb-usb/dw2102.c | 120 ++++++++++++++++++++++---------------
 1 file 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;

Reply via email to