[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