Patchwork [1/1] tmp105: Fix I2C protocol bug

login
register
mail settings
Submitter Alex Horn
Date Dec. 5, 2012, 7:48 p.m.
Message ID <1354736883-22417-1-git-send-email-alex.horn@cs.ox.ac.uk>
Download mbox | patch
Permalink /patch/203932/
State New
Headers show

Comments

Alex Horn - Dec. 5, 2012, 7:48 p.m.
The private buffer length field must only be incremented after the I2C
frame has been transmitted.

To expose this bug, assume the temperature in the TMP105 hardware model
is +0.125 C (e.g. snow slush). Note that eleven bit precision is required
to read this value; otherwise the reading is equal to zero centigrade (ice).

Continue by considering the following I2C protocol steps:

1) Start transfer with I2C_START_SEND
2) Send byte 0x01 (i.e. configuration register)
3) Send byte 0x40 (i.e. eleven bit precision)
4) End transfer with I2C_FINISH

5) Start transfer with I2C_START_SEND
6) Send byte 0x00 (i.e. temperature register)
7) End transfer I2C_FINISH

8) Start transfer with I2C_START_RECV
9) Receive high-order byte of temperature
   ...

In step (1), the function tmp105_tx() is called. By the conditional
check !s->len and the side effect with ++, s->len is equal to 1 when
step (2) begins. Thus, 0x40 is written to s->buf[1] in step (3).
By definition of tmp105_write(), s->config is set to zero in step (3).
Thus, when we read the higher-order byte in step (9), it is zero!

In other words, the TMP105 hardware model allows us to measure 0 C (ice)
even with eleven bit precision when, in fact, it should be 0.125 C (slush)!

Signed-off-by: Alex Horn <alex.horn@cs.ox.ac.uk>
---
 hw/tmp105.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
Blue Swirl - Dec. 6, 2012, 9:21 p.m.
On Wed, Dec 5, 2012 at 7:48 PM, Alex Horn <alex.horn@cs.ox.ac.uk> wrote:
> The private buffer length field must only be incremented after the I2C
> frame has been transmitted.
>
> To expose this bug, assume the temperature in the TMP105 hardware model
> is +0.125 C (e.g. snow slush). Note that eleven bit precision is required
> to read this value; otherwise the reading is equal to zero centigrade (ice).
>
> Continue by considering the following I2C protocol steps:
>
> 1) Start transfer with I2C_START_SEND
> 2) Send byte 0x01 (i.e. configuration register)
> 3) Send byte 0x40 (i.e. eleven bit precision)
> 4) End transfer with I2C_FINISH
>
> 5) Start transfer with I2C_START_SEND
> 6) Send byte 0x00 (i.e. temperature register)
> 7) End transfer I2C_FINISH
>
> 8) Start transfer with I2C_START_RECV
> 9) Receive high-order byte of temperature
>    ...
>
> In step (1), the function tmp105_tx() is called. By the conditional
> check !s->len and the side effect with ++, s->len is equal to 1 when
> step (2) begins. Thus, 0x40 is written to s->buf[1] in step (3).
> By definition of tmp105_write(), s->config is set to zero in step (3).
> Thus, when we read the higher-order byte in step (9), it is zero!
>
> In other words, the TMP105 hardware model allows us to measure 0 C (ice)
> even with eleven bit precision when, in fact, it should be 0.125 C (slush)!
>
> Signed-off-by: Alex Horn <alex.horn@cs.ox.ac.uk>
> ---
>  hw/tmp105.c |    3 ++-
>  1 files changed, 2 insertions(+), 1 deletions(-)
>
> diff --git a/hw/tmp105.c b/hw/tmp105.c
> index 8e8dbd9..5f41a3f 100644
> --- a/hw/tmp105.c
> +++ b/hw/tmp105.c
> @@ -152,7 +152,7 @@ static int tmp105_tx(I2CSlave *i2c, uint8_t data)
>  {
>      TMP105State *s = (TMP105State *) i2c;
>
> -    if (!s->len ++)
> +    if (s->len == 0)

Please fix coding style (add braces) while touching this line.

QEMU code is not yet consistent with our CODING_STYLE, but changes
should make progress towards that.

>          s->pointer = data;
>      else {
>          if (s->len <= 2)
> @@ -160,6 +160,7 @@ static int tmp105_tx(I2CSlave *i2c, uint8_t data)
>          tmp105_write(s);
>      }
>
> +    s->len ++;

Please remove the space between s->len and ++. However, I don't think
the change is entirely correct since the 'else' clause currently seems
to take the increment into account:
        if (s->len <= 2)
            s->buf[s->len - 1] = data;
        tmp105_write(s);

Shouldn't the '- 1'  in the middle line be removed?

>      return 0;
>  }
>
> --
> 1.7.6.5
>
>
Alex Horn - Dec. 7, 2012, 1:12 p.m.
> [T]he change is entirely correct since the 'else' clause currently seems
> to take the increment into account:
>         if (s->len <= 2)
>             s->buf[s->len - 1] = data;
>         tmp105_write(s);
>
> Shouldn't the '- 1'  in the middle line be removed?

Several clarifications follow.

The bug seems to occur because the else clause does not correctly
account for the increment (i.e. !s->len ++). If this increment
operation in the conditional statement should remain there, the
assignment "s->buf[s->len - 1] = data" would appear to have to be
changed to "s->buf[s->len - 2] = data". However, this offset
adjustment would only shadow the root cause of the bug.

To further clarify this bug, I have also created a standalone unit test:

   https://github.com/ahorn/benchmarks/blob/master/qemu-hw/tmp105/tmp105-test.c#L130

This test case would fail in the unmodified tmp105.c hardware model.
That is, you can produce the runtime assertion failure by reverting
the fix I have implemented in tmp105_tx() in the file

  https://github.com/ahorn/benchmarks/blob/master/qemu-hw/tmp105/tmp105.c#L140

There is also one correction about the bug description itself (see below).

 >> 8) Start transfer with I2C_START_RECV
>> 9) Receive high-order byte of temperature
>>    ...
>> Thus, when we read the higher-order byte in step (9), it is zero!

This description should read instead:

  [as before] ...

  8) Start transfer with I2C_START_RECV
  9) Receive high-order byte of temperature
10) Receive low-order byte of temperature

Thus, when we read the low-order byte in step (10), it is zero.

The rest of the argument remains the same.

I hope this correction and the unit test help in clarifying the source
of the error. I would greatly appreciate your thoughts on this.

With best regards,
Alex

On 6 December 2012 21:21, Blue Swirl <blauwirbel@gmail.com> wrote:
> On Wed, Dec 5, 2012 at 7:48 PM, Alex Horn <alex.horn@cs.ox.ac.uk> wrote:
>> The private buffer length field must only be incremented after the I2C
>> frame has been transmitted.
>>
>> To expose this bug, assume the temperature in the TMP105 hardware model
>> is +0.125 C (e.g. snow slush). Note that eleven bit precision is required
>> to read this value; otherwise the reading is equal to zero centigrade (ice).
>>
>> Continue by considering the following I2C protocol steps:
>>
>> 1) Start transfer with I2C_START_SEND
>> 2) Send byte 0x01 (i.e. configuration register)
>> 3) Send byte 0x40 (i.e. eleven bit precision)
>> 4) End transfer with I2C_FINISH
>>
>> 5) Start transfer with I2C_START_SEND
>> 6) Send byte 0x00 (i.e. temperature register)
>> 7) End transfer I2C_FINISH
>>
>> 8) Start transfer with I2C_START_RECV
>> 9) Receive high-order byte of temperature
>>    ...
>>
>> In step (1), the function tmp105_tx() is called. By the conditional
>> check !s->len and the side effect with ++, s->len is equal to 1 when
>> step (2) begins. Thus, 0x40 is written to s->buf[1] in step (3).
>> By definition of tmp105_write(), s->config is set to zero in step (3).
>> Thus, when we read the higher-order byte in step (9), it is zero!
>>
>> In other words, the TMP105 hardware model allows us to measure 0 C (ice)
>> even with eleven bit precision when, in fact, it should be 0.125 C (slush)!
>>
>> Signed-off-by: Alex Horn <alex.horn@cs.ox.ac.uk>
>> ---
>>  hw/tmp105.c |    3 ++-
>>  1 files changed, 2 insertions(+), 1 deletions(-)
>>
>> diff --git a/hw/tmp105.c b/hw/tmp105.c
>> index 8e8dbd9..5f41a3f 100644
>> --- a/hw/tmp105.c
>> +++ b/hw/tmp105.c
>> @@ -152,7 +152,7 @@ static int tmp105_tx(I2CSlave *i2c, uint8_t data)
>>  {
>>      TMP105State *s = (TMP105State *) i2c;
>>
>> -    if (!s->len ++)
>> +    if (s->len == 0)
>
> Please fix coding style (add braces) while touching this line.
>
> QEMU code is not yet consistent with our CODING_STYLE, but changes
> should make progress towards that.
>
>>          s->pointer = data;
>>      else {
>>          if (s->len <= 2)
>> @@ -160,6 +160,7 @@ static int tmp105_tx(I2CSlave *i2c, uint8_t data)
>>          tmp105_write(s);
>>      }
>>
>> +    s->len ++;
>
> Please remove the space between s->len and ++. However, I don't think
> the change is entirely correct since the 'else' clause currently seems
> to take the increment into account:
>         if (s->len <= 2)
>             s->buf[s->len - 1] = data;
>         tmp105_write(s);
>
> Shouldn't the '- 1'  in the middle line be removed?
>
>>      return 0;
>>  }
>>
>> --
>> 1.7.6.5
>>
>>

Patch

diff --git a/hw/tmp105.c b/hw/tmp105.c
index 8e8dbd9..5f41a3f 100644
--- a/hw/tmp105.c
+++ b/hw/tmp105.c
@@ -152,7 +152,7 @@  static int tmp105_tx(I2CSlave *i2c, uint8_t data)
 {
     TMP105State *s = (TMP105State *) i2c;
 
-    if (!s->len ++)
+    if (s->len == 0)
         s->pointer = data;
     else {
         if (s->len <= 2)
@@ -160,6 +160,7 @@  static int tmp105_tx(I2CSlave *i2c, uint8_t data)
         tmp105_write(s);
     }
 
+    s->len ++;
     return 0;
 }