qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [Qemu-ppc] [PATCH v5 1/4] ppc4xx_i2c: Rewrite to model


From: Cédric Le Goater
Subject: Re: [Qemu-devel] [Qemu-ppc] [PATCH v5 1/4] ppc4xx_i2c: Rewrite to model hardware more closely
Date: Mon, 25 Jun 2018 11:27:41 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.5.2

On 06/24/2018 10:37 PM, BALATON Zoltan wrote:
> Hello,
> 
> On Sun, 24 Jun 2018, Cédric Le Goater wrote:
>> On 06/24/2018 01:20 PM, BALATON Zoltan wrote:
>>> Rewrite to make it closer to how real device works so that guest OS
>>> drivers can access I2C devices. Previously this was only a hack to
>>> allow U-Boot to get past accessing SPD EEPROMs but to support other
>>> I2C devices and allow guests to access them we need to model real
>>> device more properly.
>>
>> ppc4xx support was dropped from u-boot but there is some work being done
>> to re-add at least ppc-460x. These models should be of interest to emulate
>> BMC like boards and, in some near future, they could even run OpenBMC.
>>
>> I understand that you are adding extended status support, multi transfer
>> support, better interrupt support. Having some comments on the bit
>> definitions and register names would help a lot.
>>
>> Is there a public datasheet for the I2C controller of the Sam460ex board ?
>> and a simple boot image we could use to test ?
> 
> I don't have the manual of this SoC but some of the devices including this 
> i2c controller is similar to the 440EP for which there's a manual online. 
> That's the best reference I know of even if not always applicable for 460ex.

I could not find one. Can you provide an URL ? I would to take a look 
at the register and bit definitions of the I2C controller of the SoC.

Thanks,

C. 
 
> 
>> Some comments below,
>>
>>>
>>> Signed-off-by: BALATON Zoltan <address@hidden>
>>> ---
>>>  hw/i2c/ppc4xx_i2c.c         | 222 
>>> +++++++++++++++++++++-----------------------
>>>  include/hw/i2c/ppc4xx_i2c.h |   3 +-
>>>  2 files changed, 110 insertions(+), 115 deletions(-)
>>>
>>> diff --git a/hw/i2c/ppc4xx_i2c.c b/hw/i2c/ppc4xx_i2c.c
>>> index fca80d6..3ebce17 100644
>>> --- a/hw/i2c/ppc4xx_i2c.c
>>> +++ b/hw/i2c/ppc4xx_i2c.c
>>> @@ -38,13 +38,26 @@
>>>  #define IIC_CNTL_READ       (1 << 1)
>>>  #define IIC_CNTL_CHT        (1 << 2)
>>>  #define IIC_CNTL_RPST       (1 << 3)
>>> +#define IIC_CNTL_AMD        (1 << 6)
>>> +#define IIC_CNTL_HMT        (1 << 7)
>>> +
>>> +#define IIC_MDCNTL_EINT     (1 << 2)
>>> +#define IIC_MDCNTL_ESM      (1 << 3)
>>> +#define IIC_MDCNTL_FMDB     (1 << 6)
>>>
>>>  #define IIC_STS_PT          (1 << 0)
>>> +#define IIC_STS_IRQA        (1 << 1)
>>>  #define IIC_STS_ERR         (1 << 2)
>>> +#define IIC_STS_MDBF        (1 << 4)
>>>  #define IIC_STS_MDBS        (1 << 5)
>>>
>>>  #define IIC_EXTSTS_XFRA     (1 << 0)
>>>
>>> +#define IIC_INTRMSK_EIMTC   (1 << 0)
>>> +#define IIC_INTRMSK_EITA    (1 << 1)
>>> +#define IIC_INTRMSK_EIIC    (1 << 2)
>>> +#define IIC_INTRMSK_EIHE    (1 << 3)
>>> +
>>>  #define IIC_XTCNTLSS_SRST   (1 << 0)
>>>
>>>  #define IIC_DIRECTCNTL_SDAC (1 << 3)
>>> @@ -56,21 +69,13 @@ static void ppc4xx_i2c_reset(DeviceState *s)
>>>  {
>>>      PPC4xxI2CState *i2c = PPC4xx_I2C(s);
>>>
>>> -    /* FIXME: Should also reset bus?
>>> -     *if (s->address != ADDR_RESET) {
>>> -     *    i2c_end_transfer(s->bus);
>>> -     *}
>>> -     */> -
>>> -    i2c->mdata = 0;
>>> -    i2c->lmadr = 0;
>>> -    i2c->hmadr = 0;
>>> +    i2c->mdidx = -1;
>>> +    memset(i2c->mdata, 0, ARRAY_SIZE(i2c->mdata));
>>> +    /* [hl][ms]addr are not affected by reset */
>>>      i2c->cntl = 0;
>>>      i2c->mdcntl = 0;
>>>      i2c->sts = 0;
>>> -    i2c->extsts = 0x8f;
>>> -    i2c->lsadr = 0;
>>> -    i2c->hsadr = 0;
>>> +    i2c->extsts = (1 << 6);
>>
>> #define   EXTSTS_BCS_FREE  0x40 ?
> 
> I guess this could be defined but I did not bother as this is not fully 
> emulated. If you think it's worth to add it without the other states I can do 
> in next version.
> 
>>>      i2c->clkdiv = 0;
>>>      i2c->intrmsk = 0;
>>>      i2c->xfrcnt = 0;
>>> @@ -78,69 +83,29 @@ static void ppc4xx_i2c_reset(DeviceState *s)
>>>      i2c->directcntl = 0xf;
>>>  }
>>>
>>> -static inline bool ppc4xx_i2c_is_master(PPC4xxI2CState *i2c)
>>> -{
>>> -    return true;
>>> -}
>>> -
>>>  static uint64_t ppc4xx_i2c_readb(void *opaque, hwaddr addr, unsigned int 
>>> size)
>>>  {
>>>      PPC4xxI2CState *i2c = PPC4xx_I2C(opaque);
>>>      uint64_t ret;
>>> +    int i;
>>>
>>>      switch (addr) {
>>>      case 0:
>>> -        ret = i2c->mdata;
>>> -        if (ppc4xx_i2c_is_master(i2c)) {
>>> +        if (i2c->mdidx < 0) {
>>>              ret = 0xff;
>>> -
>>> -            if (!(i2c->sts & IIC_STS_MDBS)) {
>>> -                qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to read "
>>> -                              "without starting transfer\n",
>>> -                              TYPE_PPC4xx_I2C, __func__);
>>> -            } else {
>>> -                int pending = (i2c->cntl >> 4) & 3;
>>> -
>>> -                /* get the next byte */
>>> -                int byte = i2c_recv(i2c->bus);
>>> -
>>> -                if (byte < 0) {
>>> -                    qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: read failed "
>>> -                                  "for device 0x%02x\n", TYPE_PPC4xx_I2C,
>>> -                                  __func__, i2c->lmadr);
>>> -                    ret = 0xff;
>>> -                } else {
>>> -                    ret = byte;
>>> -                    /* Raise interrupt if enabled */
>>> -                    /*ppc4xx_i2c_raise_interrupt(i2c)*/;
>>> -                }
>>> -
>>> -                if (!pending) {
>>> -                    i2c->sts &= ~IIC_STS_MDBS;
>>> -                    /*i2c_end_transfer(i2c->bus);*/
>>> -                /*} else if (i2c->cntl & (IIC_CNTL_RPST | IIC_CNTL_CHT)) 
>>> {*/
>>> -                } else if (pending) {
>>> -                    /* current smbus implementation doesn't like
>>> -                       multibyte xfer repeated start */
>>> -                    i2c_end_transfer(i2c->bus);
>>> -                    if (i2c_start_transfer(i2c->bus, i2c->lmadr >> 1, 1)) {
>>> -                        /* if non zero is returned, the adress is not 
>>> valid */
>>> -                        i2c->sts &= ~IIC_STS_PT;
>>> -                        i2c->sts |= IIC_STS_ERR;
>>> -                        i2c->extsts |= IIC_EXTSTS_XFRA;
>>> -                    } else {
>>> -                        /*i2c->sts |= IIC_STS_PT;*/
>>> -                        i2c->sts |= IIC_STS_MDBS;
>>> -                        i2c->sts &= ~IIC_STS_ERR;
>>> -                        i2c->extsts = 0;
>>> -                    }
>>> -                }
>>> -                pending--;
>>> -                i2c->cntl = (i2c->cntl & 0xcf) | (pending << 4);
>>> -            }
>>> -        } else {
>>> -            qemu_log_mask(LOG_UNIMP, "[%s]%s: slave mode not 
>>> implemented\n",
>>> -                          TYPE_PPC4xx_I2C, __func__);
>>> +            break;
>>> +        }
>>> +        ret = i2c->mdata[0];
>>> +        if (i2c->mdidx == 3) {
>>> +            i2c->sts &= ~IIC_STS_MDBF;
>>> +        } else if (i2c->mdidx == 0) {
>>> +            i2c->sts &= ~IIC_STS_MDBS;
>>> +        }
>>> +        for (i = 0; i < i2c->mdidx; i++) {
>>> +            i2c->mdata[i] = i2c->mdata[i + 1];
>>> +        }
>>> +        if (i2c->mdidx >= 0) {
>>> +            i2c->mdidx--;
>>>          }
>>>          break;
>>>      case 4:
>>> @@ -160,6 +125,7 @@ static uint64_t ppc4xx_i2c_readb(void *opaque, hwaddr 
>>> addr, unsigned int size)
>>>          break;
>>>      case 9:
>>>          ret = i2c->extsts;
>>> +        ret |= !!i2c_bus_busy(i2c->bus) << 4;
>>
>> Don't we have a bit definition for this ?
> 
> No, like I said above not all states in extsts are emulated, just the bits 
> needed for the guests I've tested, because I don't know how real hardware 
> works. So extsts is mostly a placeholder if it ever needs to be emulated more 
> closely at which points appropriate defines could also be added.
> 
>>>          break;
>>>      case 10:
>>>          ret = i2c->lsadr;
>>> @@ -203,70 +169,98 @@ static void ppc4xx_i2c_writeb(void *opaque, hwaddr 
>>> addr, uint64_t value,
>>>
>>>      switch (addr) {
>>>      case 0:
>>> -        i2c->mdata = value;
>>> -        if (!i2c_bus_busy(i2c->bus)) {
>>> -            /* assume we start a write transfer */
>>> -            if (i2c_start_transfer(i2c->bus, i2c->lmadr >> 1, 0)) {
>>> -                /* if non zero is returned, the adress is not valid */
>>> -                i2c->sts &= ~IIC_STS_PT;
>>> -                i2c->sts |= IIC_STS_ERR;
>>> -                i2c->extsts |= IIC_EXTSTS_XFRA;
>>> -            } else {
>>> -                i2c->sts |= IIC_STS_PT;
>>> -                i2c->sts &= ~IIC_STS_ERR;
>>> -                i2c->extsts = 0;
>>> -            }
>>> +        if (i2c->mdidx >= 3) {
>>
>> can mdidx go above 3 ?
> 
> No, it shouldn't, the > is just to make sure no buffer overrun is possible.
> 
>>> +            break;
>>>          }
>>> -        if (i2c_bus_busy(i2c->bus)) {
>>> -            if (i2c_send(i2c->bus, i2c->mdata)) {
>>> -                /* if the target return non zero then end the transfer */
>>> -                i2c->sts &= ~IIC_STS_PT;
>>> -                i2c->sts |= IIC_STS_ERR;
>>> -                i2c->extsts |= IIC_EXTSTS_XFRA;
>>> -                i2c_end_transfer(i2c->bus);
>>> -            }
>>> +        i2c->mdata[++i2c->mdidx] = value;
>>> +        if (i2c->mdidx == 3) {
>>> +            i2c->sts |= IIC_STS_MDBF;
>>
>> MDBF sounds like a 'M ... Data Buffer Full' status
> 
> Master Data Buffer Full
> 
>>> +        } else if (i2c->mdidx == 0) {
>>> +            i2c->sts |= IIC_STS_MDBS;
>>
>> what about MDBS ?
> 
> Master Data Buffer Status, shows if buffer has data or not.
> 
>>>          }
>>>          break;
>>>      case 4:
>>>          i2c->lmadr = value;
>>> -        if (i2c_bus_busy(i2c->bus)) {
>>> -            i2c_end_transfer(i2c->bus);
>>> -        }
>>>          break;
>>>      case 5:
>>>          i2c->hmadr = value;
>>>          break;
>>>      case 6:
>>> -        i2c->cntl = value;
>>> -        if (i2c->cntl & IIC_CNTL_PT) {
>>> -            if (i2c->cntl & IIC_CNTL_READ) {
>>> -                if (i2c_bus_busy(i2c->bus)) {
>>> -                    /* end previous transfer */
>>> -                    i2c->sts &= ~IIC_STS_PT;
>>> -                    i2c_end_transfer(i2c->bus);
>>> +        i2c->cntl = value & 0xfe;
>>> +        if (value & IIC_CNTL_AMD) {
>>> +            qemu_log_mask(LOG_UNIMP, "%s: only 7 bit addresses 
>>> supported\n",
>>> +                          __func__);
>>> +        }
>>> +        if (value & IIC_CNTL_HMT && i2c_bus_busy(i2c->bus)) {
>>
>> That's an abort ? correct ?
> 
> HMT: Halt Master Transfer, issue stop to halt master transfer.
> 
>>> +            i2c_end_transfer(i2c->bus);
>>> +            if (i2c->mdcntl & IIC_MDCNTL_EINT &&
>>> +                i2c->intrmsk & IIC_INTRMSK_EIHE) {
>>> +                    i2c->sts |= IIC_STS_IRQA;
>>> +                    qemu_irq_raise(i2c->irq);
>>> +            }
>>> +        } else if (value & IIC_CNTL_PT) {
>>> +            int recv = (value & IIC_CNTL_READ) >> 1;
>>> +            int tct = value >> 4 & 3;
>>> +            int i;
>>> +
>>> +            if (recv && (i2c->lmadr >> 1) >= 0x50 && (i2c->lmadr >> 1) < 
>>> 0x58) {
>>> +                /* smbus emulation does not like multi byte reads w/o 
>>> restart */
>>> +                value |= IIC_CNTL_RPST;
>>> +            }
>>> +
>>> +            for (i = 0; i <= tct; i++) {
>>
>> ok. i is used for mdata, but the tct definition should not exceed 3
> 
> TCT: Transfer Count, mdata FIFO is 4 bytes so max index is 3, TCT has 2 bits 
> so it should also not be higher than 3.
> 
>>> +                if (!i2c_bus_busy(i2c->bus)) {
>>> +                    i2c->extsts = (1 << 6);
>>
>> please add a definition for this bit.
> 
> See above, this basically resets extsts to initial value which may not match 
> what real hardware does but enough for tested guests.
> 
>>> +                    if (i2c_start_transfer(i2c->bus, i2c->lmadr >> 1, 
>>> recv)) {
>>> +                        i2c->sts |= IIC_STS_ERR;
>>> +                        i2c->extsts |= IIC_EXTSTS_XFRA;
>>> +                        break;
>>> +                    } else {
>>> +                        i2c->sts &= ~IIC_STS_ERR;
>>> +                    }
>>> +                }
>>
>> do we need to start the transfer in the loop ? The device address
>> does not change if I am correct. We would not need to test sts against
>> IIC_STS_ERR.
> 
> I have no idea. This works with all guests I tested, that's U-Boot, AROS, 
> AmigaOS, Linux. Don't know if it matches what real hardware does though. But 
> there are some modes where repeated start is used so I think this needs to be 
> within the loop to allow that.
> 
>>> +                if (!(i2c->sts & IIC_STS_ERR) &&
>>> +                    i2c_send_recv(i2c->bus, &i2c->mdata[i], !recv)) {
>>> +                        i2c->sts |= IIC_STS_ERR;
>>> +                        i2c->extsts |= IIC_EXTSTS_XFRA;
>>> +                        break;
>>>                  }
>>> -                if (i2c_start_transfer(i2c->bus, i2c->lmadr >> 1, 1)) {
>>> -                    /* if non zero is returned, the adress is not valid */
>>> -                    i2c->sts &= ~IIC_STS_PT;
>>> -                    i2c->sts |= IIC_STS_ERR;
>>> -                    i2c->extsts |= IIC_EXTSTS_XFRA;
>>> -                } else {
>>> -                    /*i2c->sts |= IIC_STS_PT;*/
>>> -                    i2c->sts |= IIC_STS_MDBS;
>>> -                    i2c->sts &= ~IIC_STS_ERR;
>>> -                    i2c->extsts = 0;
>>> +                if (value & IIC_CNTL_RPST || !(value & IIC_CNTL_CHT)) {
>>> +                    i2c_end_transfer(i2c->bus);
>>>                  }
>>> -            } else {
>>> -                /* we actually already did the write transfer... */
>>> -                i2c->sts &= ~IIC_STS_PT;
>>> +            }
>>
>> That's the end of the loop I suppose ?
>>
>>> +            i2c->xfrcnt = i;
>>
>> what is that xfrcnt field/reg for ?
> 
> Transfer count, number of bytes transmitted.
> 
>>> +            i2c->mdidx = i - 1;
>>> +            if (recv && i2c->mdidx >= 0) {
>>> +                i2c->sts |= IIC_STS_MDBS;
>>> +            }
>>> +            if (recv && i2c->mdidx == 3) {
>>> +                i2c->sts |= IIC_STS_MDBF;
>>> +            }
>>> +            if (i && i2c->mdcntl & IIC_MDCNTL_EINT &&
>>> +                i2c->intrmsk & IIC_INTRMSK_EIMTC) {
>>> +                i2c->sts |= IIC_STS_IRQA;
>>> +                qemu_irq_raise(i2c->irq);
>>>              }
>>>          }
>>>          break;
>>>      case 7:
>>
>> So that seems to be 'control' register of the  I2C controller.
> 
> Yes.
> 
>>> -        i2c->mdcntl = value & 0xdf;
>>> +        i2c->mdcntl = value & 0x3d;
>>
>> Could we use a mask built from the bits instead of raw hex value ?
> 
> Probably, I don't remember the details any more.
> 
>>> +        if (value & IIC_MDCNTL_ESM) {
>>> +            qemu_log_mask(LOG_UNIMP, "%s: slave mode not implemented\n",
>>> +                          __func__);
>>> +        }
>>> +        if (value & IIC_MDCNTL_FMDB) {
>>
>> that's a flush ?
> 
> FMDB: Flush Master Data Buffer
> 
> All these names are in the documentation, you should consult that instead of 
> using this emulation as documentation which it's not just approximate 
> emulation of hardware to satisfy guests.
> 
> Do we need a new version with more constants added or is this acceptable now?
> 
> Thanks you,
> BALATON Zoltan
> 
>>> +            i2c->mdidx = -1;
>>> +            memset(i2c->mdata, 0, ARRAY_SIZE(i2c->mdata));
>>> +            i2c->sts &= ~(IIC_STS_MDBF | IIC_STS_MDBS);
>>> +        }
>>>          break;
>>>      case 8:
>>> -        i2c->sts &= ~(value & 0xa);
>>> +        i2c->sts &= ~(value & 0x0a);
>>
>> ditto for the mask.
>>
>> Thanks,
>>
>> C.
>>
>>> +        if (value & IIC_STS_IRQA && i2c->mdcntl & IIC_MDCNTL_EINT) {
>>> +            qemu_irq_lower(i2c->irq);
>>> +        }
>>>          break;
>>>      case 9:
>>>          i2c->extsts &= ~(value & 0x8f);
>>> @@ -287,12 +281,12 @@ static void ppc4xx_i2c_writeb(void *opaque, hwaddr 
>>> addr, uint64_t value,
>>>          i2c->xfrcnt = value & 0x77;
>>>          break;
>>>      case 15:
>>> +        i2c->xtcntlss &= ~(value & 0xf0);
>>>          if (value & IIC_XTCNTLSS_SRST) {
>>>              /* Is it actually a full reset? U-Boot sets some regs before */
>>>              ppc4xx_i2c_reset(DEVICE(i2c));
>>>              break;
>>>          }
>>> -        i2c->xtcntlss = value;
>>>          break;
>>>      case 16:
>>>          i2c->directcntl = value & (IIC_DIRECTCNTL_SDAC & 
>>> IIC_DIRECTCNTL_SCLC);
>>> diff --git a/include/hw/i2c/ppc4xx_i2c.h b/include/hw/i2c/ppc4xx_i2c.h
>>> index ea6c8e1..0891a9c 100644
>>> --- a/include/hw/i2c/ppc4xx_i2c.h
>>> +++ b/include/hw/i2c/ppc4xx_i2c.h
>>> @@ -46,7 +46,8 @@ typedef struct PPC4xxI2CState {
>>>      qemu_irq irq;
>>>      MemoryRegion iomem;
>>>      bitbang_i2c_interface *bitbang;
>>> -    uint8_t mdata;
>>> +    int mdidx;
>>> +    uint8_t mdata[4];
>>>      uint8_t lmadr;
>>>      uint8_t hmadr;
>>>      uint8_t cntl;
>>>
>>
>>
>>




reply via email to

[Prev in Thread] Current Thread [Next in Thread]