Commit 0e148a52 authored by Michael Bunk's avatar Michael Bunk Committed by Mauro Carvalho Chehab

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.orgSigned-off-by: default avatarMichael Bunk <micha@freedict.org>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@kernel.org>
parent c6ad2b92
...@@ -716,6 +716,7 @@ static int su3000_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], ...@@ -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 dvb_usb_device *d = i2c_get_adapdata(adap);
struct dw2102_state *state; struct dw2102_state *state;
int j;
if (!d) if (!d)
return -ENODEV; return -ENODEV;
...@@ -729,11 +730,11 @@ static int su3000_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], ...@@ -729,11 +730,11 @@ static int su3000_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
return -EAGAIN; return -EAGAIN;
} }
switch (num) { j = 0;
case 1: while (j < num) {
switch (msg[0].addr) { switch (msg[j].addr) {
case SU3000_STREAM_CTRL: 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[1] = 3;
state->data[2] = 0; state->data[2] = 0;
if (dvb_usb_generic_rw(d, state->data, 3, 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[], ...@@ -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, if (dvb_usb_generic_rw(d, state->data, 1,
state->data, 2, 0) < 0) state->data, 2, 0) < 0)
err("i2c transfer failed."); err("i2c transfer failed.");
msg[0].buf[1] = state->data[0]; msg[j].buf[1] = state->data[0];
msg[0].buf[0] = state->data[1]; msg[j].buf[0] = state->data[1];
break; break;
default: default:
if (3 + msg[0].len > sizeof(state->data)) { /* if the current write msg is followed by a another
warn("i2c wr: len=%d is too big!\n", * read msg to/from the same address
msg[0].len); */
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; num = -EOPNOTSUPP;
break; break;
} }
/* always i2c write*/ state->data[0] = 0x09;
state->data[0] = 0x08; state->data[1] = msg[j].len;
state->data[1] = msg[0].addr; state->data[2] = msg[j+1].len;
state->data[2] = msg[0].len; state->data[3] = msg[j].addr;
memcpy(&state->data[4], msg[j].buf, msg[j].len);
memcpy(&state->data[3], msg[0].buf, msg[0].len);
if (dvb_usb_generic_rw(d, state->data, msg[0].len + 3, if (dvb_usb_generic_rw(d, state->data, msg[j].len + 4,
state->data, 1, 0) < 0) state->data, msg[j+1].len + 1, 0) < 0)
err("i2c transfer failed."); err("i2c transfer failed.");
} memcpy(msg[j+1].buf, &state->data[1], msg[j+1].len);
break; j++;
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; break;
} }
if (1 + msg[1].len > sizeof(state->data)) {
warn("i2c rd: len=%d is too big!\n", if (msg[j].flags & I2C_M_RD) {
msg[1].len); /* single read */
if (1 + msg[j].len > sizeof(state->data)) {
warn("i2c rd: len=%d is too big!\n", msg[j].len);
num = -EOPNOTSUPP; num = -EOPNOTSUPP;
break; break;
} }
state->data[0] = 0x09; state->data[0] = 0x09;
state->data[1] = msg[0].len; state->data[1] = 0;
state->data[2] = msg[1].len; state->data[2] = msg[j].len;
state->data[3] = msg[0].addr; state->data[3] = msg[j].addr;
memcpy(&state->data[4], msg[0].buf, msg[0].len); memcpy(&state->data[4], msg[j].buf, msg[j].len);
if (dvb_usb_generic_rw(d, state->data, msg[0].len + 4, if (dvb_usb_generic_rw(d, state->data, 4,
state->data, msg[1].len + 1, 0) < 0) state->data, msg[j].len + 1, 0) < 0)
err("i2c transfer failed."); err("i2c transfer failed.");
memcpy(msg[1].buf, &state->data[1], msg[1].len); memcpy(msg[j].buf, &state->data[1], msg[j].len);
break; break;
default: }
warn("more than 2 i2c messages at a time is not handled yet.");
/* 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; break;
} }
state->data[0] = 0x08;
state->data[1] = msg[j].addr;
state->data[2] = msg[j].len;
memcpy(&state->data[3], msg[j].buf, msg[j].len);
if (dvb_usb_generic_rw(d, state->data, msg[j].len + 3,
state->data, 1, 0) < 0)
err("i2c transfer failed.");
} // switch
j++;
} // while
mutex_unlock(&d->data_mutex); mutex_unlock(&d->data_mutex);
mutex_unlock(&d->i2c_mutex); mutex_unlock(&d->i2c_mutex);
return num; return num;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment