[PATCH v1 1/2] hw/misc: Implementating dummy AST2600 I3C model

Troy Lee leetroy at gmail.com
Tue Dec 28 20:15:03 AEDT 2021


Hi,
On Thu, Dec 23, 2021 at 9:48 PM Cédric Le Goater <clg at kaod.org> wrote:
>
>
> Hello,
>
> On 12/22/21 10:23, Troy Lee wrote:
> > Introduce a dummy AST2600 I3C model.
> >
> > Aspeed 2600 SDK enables I3C support by default.  The I3C driver will try
> > to reset the device controller and setup through device address table
> > register.  This dummy model response these register with default value
> > listed on ast2600v10 datasheet chapter 54.2.  If the device address
> > table register doesn't set correctly, it will cause guest machine kernel
> > panic due to reference to invalid address.
>
> Overall looks good. Some comments,
>
> >
> > Signed-off-by: Troy Lee <troy_lee at aspeedtech.com>
> > ---
> >   hw/misc/aspeed_i3c.c         | 258 +++++++++++++++++++++++++++++++++++
> >   hw/misc/meson.build          |   1 +
> >   include/hw/misc/aspeed_i3c.h |  30 ++++
> >   3 files changed, 289 insertions(+)
> >   create mode 100644 hw/misc/aspeed_i3c.c
> >   create mode 100644 include/hw/misc/aspeed_i3c.h
> >
> > diff --git a/hw/misc/aspeed_i3c.c b/hw/misc/aspeed_i3c.c
> > new file mode 100644
> > index 0000000000..9d2bda203e
> > --- /dev/null
> > +++ b/hw/misc/aspeed_i3c.c
> > @@ -0,0 +1,258 @@
> > +/*
> > + * ASPEED I3C Controller
> > + *
> > + * Copyright (C) 2021 ASPEED Technology Inc.
> > + *
> > + * This code is licensed under the GPL version 2 or later.  See
> > + * the COPYING file in the top-level directory.
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qemu/log.h"
> > +#include "qemu/error-report.h"
> > +#include "hw/misc/aspeed_i3c.h"
> > +#include "qapi/error.h"
> > +#include "migration/vmstate.h"
> > +
> > +/* I3C Controller Registers */
> > +#define R_I3CG_REG0(x)                  (((x * 0x10) + 0x10) / 4)
> > +#define  I3CG_REG0_SDA_PULLUP_EN_MASK   GENMASK(29, 28)
>
> GENMASK() is a macro defined in the FSI model which is not upstream.
> There are other ways to define bitfield masks in QEMU. Please take a
> look at include/hw/registerfields.h.
Got it. I found some reference code with registerfields, I'll improve
in next patch.

> > +#define  I3CG_REG0_SDA_PULLUP_EN_2K     BIT(28)
> > +#define  I3CG_REG0_SDA_PULLUP_EN_750    BIT(29)
> > +#define  I3CG_REG0_SDA_PULLUP_EN_545    (BIT(29) | BIT(28))
> > +
> > +#define R_I3CG_REG1(x)                  (((x * 0x10) + 0x14) / 4)
> > +#define  I3CG_REG1_I2C_MODE             BIT(0)
> > +#define  I3CG_REG1_TEST_MODE            BIT(1)
> > +#define  I3CG_REG1_ACT_MODE_MASK        GENMASK(3, 2)
> > +#define  I3CG_REG1_ACT_MODE(x)          (((x) << 2) & I3CG_REG1_ACT_MODE_MASK)
> > +#define  I3CG_REG1_PENDING_INT_MASK     GENMASK(7, 4)
> > +#define  I3CG_REG1_PENDING_INT(x)   (((x) << 4) & I3CG_REG1_PENDING_INT_MASK)
> > +#define  I3CG_REG1_SA_MASK              GENMASK(14, 8)
> > +#define  I3CG_REG1_SA(x)                (((x) << 8) & I3CG_REG1_SA_MASK)
> > +#define  I3CG_REG1_SA_EN                BIT(15)
> > +#define  I3CG_REG1_INST_ID_MASK         GENMASK(19, 16)
> > +#define  I3CG_REG1_INST_ID(x)           (((x) << 16) & I3CG_REG1_INST_ID_MASK)
> > +
> > +/* I3C Device Registers */
> > +#define R_DEVICE_CTRL                   (0x00 / 4)
> > +#define R_DEVICE_ADDR                   (0x04 / 4)
> > +#define R_HW_CAPABILITY                 (0x08 / 4)
> > +#define R_COMMAND_QUEUE_PORT            (0x0c / 4)
> > +#define R_RESPONSE_QUEUE_PORT           (0x10 / 4)
> > +#define R_RX_TX_DATA_PORT               (0x14 / 4)
> > +#define R_IBI_QUEUE_STATUS              (0x18 / 4)
> > +#define R_IBI_QUEUE_DATA                (0x18 / 4)
> > +#define R_QUEUE_THLD_CTRL               (0x1c / 4)
> > +#define R_DATA_BUFFER_THLD_CTRL         (0x20 / 4)
> > +#define R_IBI_QUEUE_CTRL                (0x24 / 4)
> > +#define R_IBI_MR_REQ_REJECT             (0x2c / 4)
> > +#define R_IBI_SIR_REQ_REJECT            (0x30 / 4)
> > +#define R_RESET_CTRL                    (0x34 / 4)
> > +#define R_SLV_EVENT_CTRL                (0x38 / 4)
> > +#define R_INTR_STATUS                   (0x3c / 4)
> > +#define R_INTR_STATUS_EN                (0x40 / 4)
> > +#define R_INTR_SIGNAL_EN                (0x44 / 4)
> > +#define R_INTR_FORCE                    (0x48 / 4)
> > +#define R_QUEUE_STATUS_LEVEL            (0x4c / 4)
> > +#define R_DATA_BUFFER_STATUS_LEVEL      (0x50 / 4)
> > +#define R_PRESENT_STATE                 (0x54 / 4)
> > +#define R_CCC_DEVICE_STATUS             (0x58 / 4)
> > +#define R_DEVICE_ADDR_TABLE_POINTER     (0x5c / 4)
> > +#define  DEVICE_ADDR_TABLE_DEPTH(x)     (((x) & GENMASK(31, 16)) >> 16)
> > +#define  DEVICE_ADDR_TABLE_ADDR(x)      ((x) & GENMASK(7, 0))
> > +#define R_DEV_CHAR_TABLE_POINTER        (0x60 / 4)
> > +#define R_VENDOR_SPECIFIC_REG_POINTER   (0x6c / 4)
> > +#define R_SLV_MIPI_PID_VALUE            (0x70 / 4)
> > +#define R_SLV_PID_VALUE                 (0x74 / 4)
> > +#define R_SLV_CHAR_CTRL                 (0x78 / 4)
> > +#define R_SLV_MAX_LEN                   (0x7c / 4)
> > +#define R_MAX_READ_TURNAROUND           (0x80 / 4)
> > +#define R_MAX_DATA_SPEED                (0x84 / 4)
> > +#define R_SLV_DEBUG_STATUS              (0x88 / 4)
> > +#define R_SLV_INTR_REQ                  (0x8c / 4)
> > +#define R_DEVICE_CTRL_EXTENDED          (0xb0 / 4)
> > +#define R_SCL_I3C_OD_TIMING             (0xb4 / 4)
> > +#define R_SCL_I3C_PP_TIMING             (0xb8 / 4)
> > +#define R_SCL_I2C_FM_TIMING             (0xbc / 4)
> > +#define R_SCL_I2C_FMP_TIMING            (0xc0 / 4)
> > +#define R_SCL_EXT_LCNT_TIMING           (0xc8 / 4)
> > +#define R_SCL_EXT_TERMN_LCNT_TIMING     (0xcc / 4)
> > +#define R_BUS_FREE_TIMING               (0xd4 / 4)
> > +#define R_BUS_IDLE_TIMING               (0xd8 / 4)
> > +#define R_I3C_VER_ID                    (0xe0 / 4)
> > +#define R_I3C_VER_TYPE                  (0xe4 / 4)
> > +#define R_EXTENDED_CAPABILITY           (0xe8 / 4)
> > +#define R_SLAVE_CONFIG                  (0xec / 4)
> > +
> > +static uint64_t aspeed_i3c_read(void *opaque,
> > +                                hwaddr addr,
> > +                                unsigned int size)
>
> You can avoid the new lines.
static uint64_t aspeed_i3c_read(void *opaque, hwaddr addr, unsigned int size)
Originally, I thought it would exceed 80 chars, but it is only 78 chars long.

> > +{
> > +    AspeedI3CState *s = ASPEED_I3C(opaque);
> > +    uint64_t val = 0;
> > +
> > +    addr >>= 2;
> > +
> > +    if (addr >= ASPEED_I3C_NR_REGS) {
>
> ASPEED_I3C_NR_REGS is a large value (0x8000 >> 2) which doesn't really
> represent the number of registers. I would do thing a bit differently.
> See the comment below in realize.
>
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
> > +                      __func__, addr << 2);
> > +    } else if (addr < 0x800) {
>
> Where does that value come from  ?
>
> > +        /* I3C controller register */
> > +        val = s->regs[addr];
> > +    } else {
> > +        /* I3C device register */
>
> hmm, I think we need one region per I3C device instead.
I'll split it into two levels, controller and devices, where the
controller could
be a memory region only.

> > +        switch (addr & 0xff) {
> > +        case R_DEVICE_ADDR_TABLE_POINTER:
> > +            val = DEVICE_ADDR_TABLE_DEPTH(0x8) | DEVICE_ADDR_TABLE_ADDR(0x280);
> > +            break;
> > +        case R_DEV_CHAR_TABLE_POINTER:
> > +            val = 0x00020200;
> > +            break;
> > +        case R_VENDOR_SPECIFIC_REG_POINTER:
> > +            val = 0x000000b0;
> > +            break;
> > +        default:
> > +            val = s->regs[addr];
> > +            break;
> > +        }
> > +    }
> > +
> > +    printf("I3C Read[%08lx] = [%08lx]\n", addr << 2, val);
>
> Please use trace-events. See trace_aspeed_scu_read/write for example.
Got it.

> > +
> > +    return val;
> > +}
> > +
> > +static void aspeed_i3c_write(void *opaque,
> > +                             hwaddr addr,
> > +                             uint64_t data,
> > +                             unsigned int size)
>
> same.
>
Got it.

> > +{
> > +    AspeedI3CState *s = ASPEED_I3C(opaque);
> > +
> > +    printf("I3C Write[%08lx] = [%08lx]\n", addr, data);
>
> same.
>
Got it.

> > +
> > +    addr >>= 2;
> > +
> > +    if (addr >= ASPEED_I3C_NR_REGS) {
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
> > +                      __func__, addr << 2);
> > +        return;
> > +    }
> > +
> > +    if (addr < 0x800) {
> > +        /* I3C controller register */
> > +        switch (addr) {
> > +        case R_I3CG_REG1(0):
> > +        case R_I3CG_REG1(1):
> > +        case R_I3CG_REG1(2):
> > +        case R_I3CG_REG1(3):
> > +            if (data & I3CG_REG1_I2C_MODE) {
> > +                qemu_log_mask(LOG_UNIMP,
> > +                              "%s: Not support I2C mode [%08lx]=%08lx",
> > +                              __func__, addr << 2, data);
> > +                break;
> > +            }
> > +            if (data & I3CG_REG1_SA_EN) {
> > +                qemu_log_mask(LOG_UNIMP,
> > +                              "%s: Not support slave mode [%08lx]=%08lx",
> > +                              __func__, addr << 2, data);
> > +                break;
> > +            }
> > +            s->regs[addr] = data;
> > +            break;
> > +        default:
> > +            s->regs[addr] = data;
> > +            break;
> > +        }
> > +    } else {
> > +        /* I3C device register */
> > +        switch (addr & 0xff) {
> > +        case R_RESET_CTRL:
> > +            s->regs[addr] = 0x00000000;
> > +            break;
> > +        default:
> > +            s->regs[addr] = data;
> > +            break;
> > +        }
> > +    }
> > +}
> > +
> > +static const MemoryRegionOps aspeed_i3c_ops = {
> > +    .read = aspeed_i3c_read,
> > +    .write = aspeed_i3c_write,
> > +    .endianness = DEVICE_LITTLE_ENDIAN,
> > +    .valid = {
> > +        .min_access_size = 1,
> > +        .max_access_size = 4,
> > +    }
> > +};
> > +
> > +static void aspeed_i3c_reset(DeviceState *dev)
> > +{
> > +    struct AspeedI3CState *s = ASPEED_I3C(dev);
> > +
> > +    memset(s->regs, 0, sizeof(s->regs));
> > +
> > +    /* Init I3C controller register */
> > +
> > +
> > +    /* Init I3C devices register */
> > +    for (int i = 0; i < 4; ++i) {
>
> there are 5 devices. no ?
Oops, there are 6 devices.

> > +        uint32_t dev_base = (0x2000 + i * 0x1000) >> 4;
> > +        s->regs[dev_base + R_HW_CAPABILITY] = 0x000e00bf;
> > +        s->regs[dev_base + R_QUEUE_THLD_CTRL] = 0x01000101;
> > +
> > +        s->regs[dev_base + R_I3C_VER_ID] = 0x3130302a;
> > +        s->regs[dev_base + R_I3C_VER_TYPE] = 0x6c633033;
>
> This is a sign that we need an AspeedI3CDevice model :)
>
> > +    }
> > +}
> > +
> > +static void aspeed_i3c_realize(DeviceState *dev, Error **errp)
> > +{
> > +    AspeedI3CState *s = ASPEED_I3C(dev);
> > +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> > +
> > +    sysbus_init_irq(sbd, &s->irq);
> > +
> > +    memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_i3c_ops, s,
> > +            TYPE_ASPEED_I3C, 0x8000);
>
> I would do things differently with an AspeedI3CDevice, something like :
>
>      struct AspeedI3CDevice {
>          SysBusDevice parent_obj;
>
>          MemoryRegion mr;
>
>          uint32_t regs[0x300 >> 2];
>      };
>
> for which you would define the realize and reset handlers like above
> but relative to the device only. The read/write ops of the device
> region would be simplified.
>
> You will then to introduce an array of AspeedI3CDevice under
> AspeedI3CState :
>
>        AspeedI3CDevice devices[ASPEED_I3C_NR_DEVICES];
>
> and initialize them in a instance_init handler of AspeedI3CState :
>
>      static void aspeed_i3c_instance_init(Object *obj)
>      {
>          AspeedI3CState *s = ASPEED_I3C(obj);
>          int i;
>
>          for (i = 0; i < ASPEED_I3C_NR_DEVICES; i++) {
>              object_initialize_child(obj, "device[*]", &s->devices[i],
>                                      TYPE_ASPEED_I3C_DEVICE);
>          }
>      }
>
> and realize the devices in aspeed_i3c_realize  :
>
>      for (i = 0; i < ASPEED_I3C_NR_DEVICES; i++) {
>          Object *obj = OBJECT(&s->devices[i]);
>
>          if (!sysbus_realize(SYS_BUS_DEVICE(obj), errp)) {
>              return;
>          }
>
>         /* Map the device MMIO window in the overall I3C MMIO */
>          memory_region_add_subregion(&s->iomem, 0x2000 + i * 0x1000;
>                                      &s->devices[i].mr);
>      }
This looks more straight forward.

> I don't think we need to handle the controller registers mapped at 0x0
> to start with. You could introduce a region to catch memory ops.
>
> At some point, we would have to consider modeling the I3C bus but that's
> beyond the scope of the initial model.
>
> Thanks,
>
> C.

Thanks for your suggestion, I'll update accordingly in the v2 patch.

Troy Lee

>
> > +
> > +    sysbus_init_mmio(sbd, &s->iomem);
> > +}
> > +
> > +static const VMStateDescription vmstate_aspeed_i3c = {
> > +    .name = TYPE_ASPEED_I3C,
> > +    .version_id = 1,
> > +    .minimum_version_id = 1,
> > +    .fields = (VMStateField[]) {
> > +        VMSTATE_UINT32_ARRAY(regs, AspeedI3CState, ASPEED_I3C_NR_REGS),
> > +        VMSTATE_END_OF_LIST(),
> > +    }
> > +};
> > +
> > +static void aspeed_i3c_class_init(ObjectClass *klass, void *data)
> > +{
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +
> > +    dc->realize = aspeed_i3c_realize;
> > +    dc->reset = aspeed_i3c_reset;
> > +    dc->desc = "Aspeed I3C Controller";
> > +    dc->vmsd = &vmstate_aspeed_i3c;
> > +}
> > +
> > +static const TypeInfo aspeed_i3c_info = {
> > +    .name = TYPE_ASPEED_I3C,
> > +    .parent = TYPE_SYS_BUS_DEVICE,
> > +    .instance_size = sizeof(AspeedI3CState),
> > +    .class_init = aspeed_i3c_class_init,
> > +};
> > +
> > +static void aspeed_i3c_register_types(void)
> > +{
> > +    type_register_static(&aspeed_i3c_info);
> > +}
> > +
> > +type_init(aspeed_i3c_register_types);
> > diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> > index 73a92fc459..9e570131c2 100644
> > --- a/hw/misc/meson.build
> > +++ b/hw/misc/meson.build
> > @@ -103,6 +103,7 @@ softmmu_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true: files('pvpanic-pci.c'))
> >   softmmu_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c'))
> >   softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
> >     'aspeed_hace.c',
> > +  'aspeed_i3c.c',
> >     'aspeed_lpc.c',
> >     'aspeed_pwm.c',
> >     'aspeed_scu.c',
> > diff --git a/include/hw/misc/aspeed_i3c.h b/include/hw/misc/aspeed_i3c.h
> > new file mode 100644
> > index 0000000000..c8f8ae3791
> > --- /dev/null
> > +++ b/include/hw/misc/aspeed_i3c.h
> > @@ -0,0 +1,30 @@
> > +/*
> > + * ASPEED I3C Controller
> > + *
> > + * Copyright (C) 2021 ASPEED Technology Inc.
> > + *
> > + * This code is licensed under the GPL version 2 or later.  See
> > + * the COPYING file in the top-level directory.
> > + */
> > +
> > +#ifndef ASPEED_I3C_H
> > +#define ASPEED_I3C_H
> > +
> > +#include "hw/sysbus.h"
> > +
> > +#define TYPE_ASPEED_I3C "aspeed.i3c"
> > +#define ASPEED_I3C(obj) OBJECT_CHECK(AspeedI3CState, (obj), TYPE_ASPEED_I3C)
> > +
> > +#define ASPEED_I3C_NR_REGS (0x8000 >> 2)
> > +
> > +typedef struct AspeedI3CState {
> > +    /* <private> */
> > +    SysBusDevice parent;
> > +
> > +    /* <public> */
> > +    MemoryRegion iomem;
> > +    qemu_irq irq;
> > +
> > +    uint32_t regs[ASPEED_I3C_NR_REGS];
> > +} AspeedI3CState;
> > +#endif /* ASPEED_I3C_H */
> >


More information about the openbmc mailing list