| From stable-bounces@linux.kernel.org Thu Sep 27 06:17:47 2007 |
| From: Jean Delvare <khali@linux-fr.org> |
| Date: Thu, 27 Sep 2007 15:17:25 +0200 |
| Subject: i2c-algo-bit: Read block data bugfix |
| To: stable@kernel.org |
| Cc: David Brownell <david-b@pacbell.net> |
| Message-ID: <20070927151725.6c33402b@hyperion.delvare> |
| |
| |
| From: David Brownell <david-b@pacbell.net> |
| |
| In Linus tree already: |
| http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=939bc4943d0483961edc45b63a7d27b4ffe547e3 |
| |
| This fixes a bug in the way i2c-algo-bit handles I2C_M_RECV_LEN, |
| used to implement i2c_smbus_read_block_data(). Previously, in the |
| absence of PEC (rarely used!) it would NAK the "length" byte: |
| |
| S addr Rd [A] [length] NA |
| |
| That prevents the subsequent data bytes from being read: |
| |
| S addr Rd [A] [length] { A [data] }* NA |
| |
| The primary fix just reorders two code blocks, so the length used |
| in the "should I NAK now?" check incorporates the data which it |
| just read from the slave device. |
| |
| However, that move also highlighted other fault handling glitches. |
| This fixes those by abstracting the RX path ack/nak logic, so it |
| can be used in more than one location. |
| |
| Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> |
| Signed-off-by: Jean Delvare <khali@linux-fr.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| |
| --- |
| drivers/i2c/algos/i2c-algo-bit.c | 52 ++++++++++++++++++++++++--------------- |
| 1 file changed, 32 insertions(+), 20 deletions(-) |
| |
| --- a/drivers/i2c/algos/i2c-algo-bit.c |
| +++ b/drivers/i2c/algos/i2c-algo-bit.c |
| @@ -357,13 +357,29 @@ static int sendbytes(struct i2c_adapter |
| return wrcount; |
| } |
| |
| +static int acknak(struct i2c_adapter *i2c_adap, int is_ack) |
| +{ |
| + struct i2c_algo_bit_data *adap = i2c_adap->algo_data; |
| + |
| + /* assert: sda is high */ |
| + if (is_ack) /* send ack */ |
| + setsda(adap, 0); |
| + udelay((adap->udelay + 1) / 2); |
| + if (sclhi(adap) < 0) { /* timeout */ |
| + dev_err(&i2c_adap->dev, "readbytes: ack/nak timeout\n"); |
| + return -ETIMEDOUT; |
| + } |
| + scllo(adap); |
| + return 0; |
| +} |
| + |
| static int readbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg) |
| { |
| int inval; |
| int rdcount=0; /* counts bytes read */ |
| - struct i2c_algo_bit_data *adap = i2c_adap->algo_data; |
| unsigned char *temp = msg->buf; |
| int count = msg->len; |
| + const unsigned flags = msg->flags; |
| |
| while (count > 0) { |
| inval = i2c_inb(i2c_adap); |
| @@ -377,28 +393,12 @@ static int readbytes(struct i2c_adapter |
| temp++; |
| count--; |
| |
| - if (msg->flags & I2C_M_NO_RD_ACK) { |
| - bit_dbg(2, &i2c_adap->dev, "i2c_inb: 0x%02x\n", |
| - inval); |
| - continue; |
| - } |
| - |
| - /* assert: sda is high */ |
| - if (count) /* send ack */ |
| - setsda(adap, 0); |
| - udelay((adap->udelay + 1) / 2); |
| - bit_dbg(2, &i2c_adap->dev, "i2c_inb: 0x%02x %s\n", inval, |
| - count ? "A" : "NA"); |
| - if (sclhi(adap)<0) { /* timeout */ |
| - dev_err(&i2c_adap->dev, "readbytes: timeout at ack\n"); |
| - return -ETIMEDOUT; |
| - }; |
| - scllo(adap); |
| - |
| /* Some SMBus transactions require that we receive the |
| transaction length as the first read byte. */ |
| - if (rdcount == 1 && (msg->flags & I2C_M_RECV_LEN)) { |
| + if (rdcount == 1 && (flags & I2C_M_RECV_LEN)) { |
| if (inval <= 0 || inval > I2C_SMBUS_BLOCK_MAX) { |
| + if (!(flags & I2C_M_NO_RD_ACK)) |
| + acknak(i2c_adap, 0); |
| dev_err(&i2c_adap->dev, "readbytes: invalid " |
| "block length (%d)\n", inval); |
| return -EREMOTEIO; |
| @@ -409,6 +409,18 @@ static int readbytes(struct i2c_adapter |
| count += inval; |
| msg->len += inval; |
| } |
| + |
| + bit_dbg(2, &i2c_adap->dev, "readbytes: 0x%02x %s\n", |
| + inval, |
| + (flags & I2C_M_NO_RD_ACK) |
| + ? "(no ack/nak)" |
| + : (count ? "A" : "NA")); |
| + |
| + if (!(flags & I2C_M_NO_RD_ACK)) { |
| + inval = acknak(i2c_adap, count); |
| + if (inval < 0) |
| + return inval; |
| + } |
| } |
| return rdcount; |
| } |