media: dw2102: Don't translate i2c read into write
[ Upstream commit 0e148a522b8453115038193e19ec7bea71403e4a ] 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> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
parent
3fac5aecb5
commit
af19067bd5
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue