[PATCH v7 18/22] hw/i3c: Add Mock target

Jamin Lin posted 22 patches 1 month, 2 weeks ago
Maintainers: Paolo Bonzini <pbonzini@redhat.com>, Peter Maydell <peter.maydell@linaro.org>, Joe Komlodi <komlodi@google.com>, "Cédric Le Goater" <clg@kaod.org>, Jamin Lin <jamin_lin@aspeedtech.com>, Nabih Estefan <nabihestefan@google.com>, Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>, Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>, "Marc-André Lureau" <marcandre.lureau@redhat.com>, "Daniel P. Berrangé" <berrange@redhat.com>, "Philippe Mathieu-Daudé" <philmd@linaro.org>
[PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Jamin Lin 1 month, 2 weeks ago
Adds a simple i3c device to be used for testing in lieu of a real
device.

The mock target supports the following features:
- A buffer that users can read and write to.
- CCC support for commonly used CCCs when probing devices on an I3C bus.
- IBI sending upon receiving a user-defined byte.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Titus Rwantare <titusr@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 include/hw/i3c/mock-i3c-target.h |  52 ++++++
 hw/i3c/mock-i3c-target.c         | 298 +++++++++++++++++++++++++++++++
 hw/i3c/Kconfig                   |  10 ++
 hw/i3c/meson.build               |   1 +
 hw/i3c/trace-events              |  10 ++
 5 files changed, 371 insertions(+)
 create mode 100644 include/hw/i3c/mock-i3c-target.h
 create mode 100644 hw/i3c/mock-i3c-target.c

diff --git a/include/hw/i3c/mock-i3c-target.h b/include/hw/i3c/mock-i3c-target.h
new file mode 100644
index 0000000000..8c6003ae8b
--- /dev/null
+++ b/include/hw/i3c/mock-i3c-target.h
@@ -0,0 +1,52 @@
+#ifndef MOCK_I3C_TARGET_H_
+#define MOCK_I3C_TARGET_H_
+
+/*
+ * Mock I3C Device
+ *
+ * Copyright (c) 2025 Google LLC
+ *
+ * The mock I3C device can be thought of as a simple EEPROM. It has a buffer,
+ * and the pointer in the buffer is reset to 0 on an I3C STOP.
+ * To write to the buffer, issue a private write and send data.
+ * To read from the buffer, issue a private read.
+ *
+ * The mock target also supports sending target interrupt IBIs.
+ * To issue an IBI, set the 'ibi-magic-num' property to a non-zero number, and
+ * send that number in a private transaction. The mock target will issue an IBI
+ * after 1 second.
+ *
+ * It also supports a handful of CCCs that are typically used when probing I3C
+ * devices.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "hw/i3c/i3c.h"
+
+#define TYPE_MOCK_I3C_TARGET "mock-i3c-target"
+OBJECT_DECLARE_SIMPLE_TYPE(MockI3cTargetState, MOCK_I3C_TARGET)
+
+struct MockI3cTargetState {
+    I3CTarget parent_obj;
+
+    /* General device state */
+    bool can_ibi;
+    QEMUTimer qtimer;
+    size_t p_buf;
+    uint8_t *buf;
+
+    /* For Handing CCCs. */
+    bool in_ccc;
+    I3CCCC curr_ccc;
+    uint8_t ccc_byte_offset;
+
+    struct {
+        uint32_t buf_size;
+        uint8_t ibi_magic;
+    } cfg;
+};
+
+#endif
diff --git a/hw/i3c/mock-i3c-target.c b/hw/i3c/mock-i3c-target.c
new file mode 100644
index 0000000000..875cd7c7d0
--- /dev/null
+++ b/hw/i3c/mock-i3c-target.c
@@ -0,0 +1,298 @@
+/*
+ * Mock I3C Device
+ *
+ * Copyright (c) 2025 Google LLC
+ *
+ * The mock I3C device can be thought of as a simple EEPROM. It has a buffer,
+ * and the pointer in the buffer is reset to 0 on an I3C STOP.
+ * To write to the buffer, issue a private write and send data.
+ * To read from the buffer, issue a private read.
+ *
+ * The mock target also supports sending target interrupt IBIs.
+ * To issue an IBI, set the 'ibi-magic-num' property to a non-zero number, and
+ * send that number in a private transaction. The mock target will issue an IBI
+ * after 1 second.
+ *
+ * It also supports a handful of CCCs that are typically used when probing I3C
+ * devices.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "hw/i3c/i3c.h"
+#include "hw/i3c/mock-i3c-target.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+
+#define IBI_DELAY_NS (1 * 1000 * 1000)
+
+static uint32_t mock_i3c_target_rx(I3CTarget *i3c, uint8_t *data,
+                                   uint32_t num_to_read)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
+    uint32_t i;
+
+    /* Bounds check. */
+    if (s->p_buf == s->cfg.buf_size) {
+        return 0;
+    }
+
+    for (i = 0; i < num_to_read; i++) {
+        data[i] = s->buf[s->p_buf];
+        trace_mock_i3c_target_rx(data[i]);
+        s->p_buf++;
+        if (s->p_buf == s->cfg.buf_size) {
+            break;
+        }
+    }
+
+    /* Return the number of bytes we're sending to the controller. */
+    return i;
+}
+
+static void mock_i3c_target_ibi_timer_start(MockI3cTargetState *s)
+{
+    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    timer_mod(&s->qtimer, now + IBI_DELAY_NS);
+}
+
+static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
+                              uint32_t num_to_send, uint32_t *num_sent)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
+    int ret;
+    uint32_t to_write;
+
+    if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) {
+        mock_i3c_target_ibi_timer_start(s);
+        return 0;
+    }
+
+    /* Bounds check. */
+    if (num_to_send + s->p_buf > s->cfg.buf_size) {
+        to_write = s->cfg.buf_size - s->p_buf;
+        ret = -1;
+    } else {
+        to_write = num_to_send;
+        ret = 0;
+    }
+    for (uint32_t i = 0; i < to_write; i++) {
+        trace_mock_i3c_target_tx(data[i]);
+        s->buf[s->p_buf] = data[i];
+        s->p_buf++;
+    }
+    return ret;
+}
+
+static int mock_i3c_target_event(I3CTarget *i3c, enum I3CEvent event)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
+
+    trace_mock_i3c_target_event(event);
+    if (event == I3C_STOP) {
+        s->in_ccc = false;
+        s->curr_ccc = 0;
+        s->ccc_byte_offset = 0;
+        s->p_buf = 0;
+    }
+
+    return 0;
+}
+
+static int mock_i3c_target_handle_ccc_read(I3CTarget *i3c, uint8_t *data,
+                                           uint32_t num_to_read,
+                                           uint32_t *num_read)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
+
+    switch (s->curr_ccc) {
+    case I3C_CCCD_GETMXDS:
+        /* Default data rate for I3C. */
+        while (s->ccc_byte_offset < num_to_read) {
+            if (s->ccc_byte_offset >= 2) {
+                break;
+            }
+            data[s->ccc_byte_offset] = 0;
+            *num_read = s->ccc_byte_offset;
+            s->ccc_byte_offset++;
+        }
+        break;
+    case I3C_CCCD_GETCAPS:
+        /* Support I3C version 1.1.x, no other features. */
+        while (s->ccc_byte_offset < num_to_read) {
+            if (s->ccc_byte_offset >= 2) {
+                break;
+            }
+            if (s->ccc_byte_offset == 0) {
+                data[s->ccc_byte_offset] = 0;
+            } else {
+                data[s->ccc_byte_offset] = 0x01;
+            }
+            *num_read = s->ccc_byte_offset;
+            s->ccc_byte_offset++;
+        }
+        break;
+    case I3C_CCCD_GETMWL:
+    case I3C_CCCD_GETMRL:
+        /* MWL/MRL is MSB first. */
+        while (s->ccc_byte_offset < num_to_read) {
+            if (s->ccc_byte_offset >= 2) {
+                break;
+            }
+            data[s->ccc_byte_offset] = (s->cfg.buf_size &
+                                        (0xff00 >> (s->ccc_byte_offset * 8))) >>
+                                        (8 - (s->ccc_byte_offset * 8));
+            s->ccc_byte_offset++;
+            *num_read = num_to_read;
+        }
+        break;
+    case I3C_CCC_ENTDAA:
+    case I3C_CCCD_GETPID:
+    case I3C_CCCD_GETBCR:
+    case I3C_CCCD_GETDCR:
+        /* Nothing to do. */
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc);
+        return -1;
+    }
+
+    trace_mock_i3c_target_handle_ccc_read(*num_read, num_to_read);
+    return 0;
+}
+
+static int mock_i3c_target_handle_ccc_write(I3CTarget *i3c, const uint8_t *data,
+                                            uint32_t num_to_send,
+                                            uint32_t *num_sent)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
+
+    if (!s->curr_ccc) {
+        s->in_ccc = true;
+        s->curr_ccc = *data;
+        trace_mock_i3c_target_new_ccc(s->curr_ccc);
+    }
+
+    *num_sent = 1;
+    switch (s->curr_ccc) {
+    case I3C_CCC_ENEC:
+    case I3C_CCCD_ENEC:
+        s->can_ibi = true;
+        break;
+    case I3C_CCC_DISEC:
+    case I3C_CCCD_DISEC:
+        s->can_ibi = false;
+        break;
+    case I3C_CCC_ENTDAA:
+    case I3C_CCC_SETAASA:
+    case I3C_CCC_RSTDAA:
+    case I3C_CCCD_SETDASA:
+    case I3C_CCCD_GETPID:
+    case I3C_CCCD_GETBCR:
+    case I3C_CCCD_GETDCR:
+    case I3C_CCCD_GETMWL:
+    case I3C_CCCD_GETMRL:
+    case I3C_CCCD_GETMXDS:
+    case I3C_CCCD_GETCAPS:
+        /* Nothing to do. */
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc);
+        return -1;
+    }
+
+    trace_mock_i3c_target_handle_ccc_write(*num_sent, num_to_send);
+    return 0;
+}
+
+static void mock_i3c_target_do_ibi(MockI3cTargetState *s)
+{
+    if (!s->can_ibi) {
+        return;
+    }
+
+    trace_mock_i3c_target_do_ibi(s->parent_obj.address, true);
+    int nack = i3c_target_send_ibi(&s->parent_obj, s->parent_obj.address,
+                                   /*is_recv=*/true);
+    /* Getting NACKed isn't necessarily an error, just print it out. */
+    if (nack) {
+        trace_mock_i3c_target_do_ibi_nack("sending");
+    }
+    nack = i3c_target_ibi_finish(&s->parent_obj, 0x00);
+    if (nack) {
+        trace_mock_i3c_target_do_ibi_nack("finishing");
+    }
+}
+
+static void mock_i3c_target_timer_elapsed(void *opaque)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(opaque);
+    timer_del(&s->qtimer);
+    mock_i3c_target_do_ibi(s);
+}
+
+static void mock_i3c_target_reset(I3CTarget *i3c)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
+    s->can_ibi = false;
+}
+
+static void mock_i3c_target_realize(DeviceState *dev, Error **errp)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(dev);
+    s->buf = g_new0(uint8_t, s->cfg.buf_size);
+    mock_i3c_target_reset(&s->parent_obj);
+}
+
+static void mock_i3c_target_init(Object *obj)
+{
+    MockI3cTargetState *s = MOCK_I3C_TARGET(obj);
+    s->can_ibi = false;
+
+    /* For IBIs. */
+    timer_init_ns(&s->qtimer, QEMU_CLOCK_VIRTUAL, mock_i3c_target_timer_elapsed,
+                  s);
+}
+
+static const Property remote_i3c_props[] = {
+    /* The size of the internal buffer. */
+    DEFINE_PROP_UINT32("buf-size", MockI3cTargetState, cfg.buf_size, 0x100),
+    /*
+     * If the mock target receives this number, it will issue an IBI after
+     * 1 second. Disabled if the IBI magic number is 0.
+     */
+    DEFINE_PROP_UINT8("ibi-magic-num", MockI3cTargetState, cfg.ibi_magic, 0x00),
+};
+
+static void mock_i3c_target_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    I3CTargetClass *k = I3C_TARGET_CLASS(klass);
+
+    dc->realize = mock_i3c_target_realize;
+    k->event = mock_i3c_target_event;
+    k->recv = mock_i3c_target_rx;
+    k->send = mock_i3c_target_tx;
+    k->handle_ccc_read = mock_i3c_target_handle_ccc_read;
+    k->handle_ccc_write = mock_i3c_target_handle_ccc_write;
+
+    device_class_set_props(dc, remote_i3c_props);
+}
+
+static const TypeInfo mock_i3c_target_types[] = {
+    {
+        .name          = TYPE_MOCK_I3C_TARGET,
+        .parent        = TYPE_I3C_TARGET,
+        .instance_size = sizeof(MockI3cTargetState),
+        .instance_init = mock_i3c_target_init,
+        .class_init    = mock_i3c_target_class_init,
+    },
+};
+
+DEFINE_TYPES(mock_i3c_target_types)
+
diff --git a/hw/i3c/Kconfig b/hw/i3c/Kconfig
index ecec77d6fc..d5c6d4049b 100644
--- a/hw/i3c/Kconfig
+++ b/hw/i3c/Kconfig
@@ -3,3 +3,13 @@ config I3C
 
 config DW_I3C
     bool
+
+config I3C_DEVICES
+    # Device group for i3c devices which can reasonably be user-plugged to any
+    # board's i3c bus.
+    bool
+
+config MOCK_I3C_TARGET
+    bool
+    select I3C
+    default y if I3C_DEVICES
diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build
index 83d75e7d5c..e614b18712 100644
--- a/hw/i3c/meson.build
+++ b/hw/i3c/meson.build
@@ -2,4 +2,5 @@ i3c_ss = ss.source_set()
 i3c_ss.add(when: 'CONFIG_I3C', if_true: files('core.c'))
 i3c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i3c.c'))
 i3c_ss.add(when: 'CONFIG_DW_I3C', if_true: files('dw-i3c.c'))
+i3c_ss.add(when: 'CONFIG_MOCK_I3C_TARGET', if_true: files('mock-i3c-target.c'))
 system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss)
diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
index 39f33d9a50..9e58edec99 100644
--- a/hw/i3c/trace-events
+++ b/hw/i3c/trace-events
@@ -36,3 +36,13 @@ legacy_i2c_recv(uint8_t byte) "Legacy I2C recv 0x%" PRIx8
 legacy_i2c_send(uint8_t byte) "Legacy I2C send 0x%" PRIx8
 legacy_i2c_start_transfer(uint8_t address, bool is_recv) "Legacy I2C START with address 0x%" PRIx8 " is_recv=%d"
 legacy_i2c_end_transfer(void) "Legacy I2C STOP"
+
+# mock-target.c
+mock_i3c_target_rx(uint8_t byte) "I3C mock target read 0x%" PRIx8
+mock_i3c_target_tx(uint8_t byte) "I3C mock target write 0x%" PRIx8
+mock_i3c_target_event(uint8_t event) "I3C mock target event 0x%" PRIx8
+mock_i3c_target_handle_ccc_read(uint32_t num_read, uint32_t num_to_read) "I3C mock target read %" PRId32 "/%" PRId32 " bytes"
+mock_i3c_target_new_ccc(uint8_t ccc) "I3C mock target handle CCC 0x%" PRIx8
+mock_i3c_target_handle_ccc_write(uint32_t num_sent, uint32_t num_to_send) "I3C mock target send %" PRId32 "/%" PRId32 " bytes"
+mock_i3c_target_do_ibi(uint8_t address, bool is_recv) "I3C mock target IBI with address 0x%" PRIx8 " RnW=%d"
+mock_i3c_target_do_ibi_nack(const char *reason) "NACKed from controller when %s target interrupt"
-- 
2.43.0
Re: [PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Jithu Joseph 1 month, 1 week ago
On 2/24/2026 6:12 PM, Jamin Lin wrote:
> Adds a simple i3c device to be used for testing in lieu of a real
> device.
> 

Reviewed-by: Jithu Joseph <jithu.joseph@oss.qualcomm.com>


Jithu
Re: [PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Cédric Le Goater 1 month, 1 week ago
On 2/25/26 03:12, Jamin Lin wrote:
> Adds a simple i3c device to be used for testing in lieu of a real
> device.
> 
> The mock target supports the following features:
> - A buffer that users can read and write to.
> - CCC support for commonly used CCCs when probing devices on an I3C bus.
> - IBI sending upon receiving a user-defined byte.
> 
> Signed-off-by: Joe Komlodi <komlodi@google.com>
> Reviewed-by: Titus Rwantare <titusr@google.com>
> Reviewed-by: Patrick Venture <venture@google.com>
> Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> ---
>   include/hw/i3c/mock-i3c-target.h |  52 ++++++
>   hw/i3c/mock-i3c-target.c         | 298 +++++++++++++++++++++++++++++++
>   hw/i3c/Kconfig                   |  10 ++
>   hw/i3c/meson.build               |   1 +
>   hw/i3c/trace-events              |  10 ++
>   5 files changed, 371 insertions(+)
>   create mode 100644 include/hw/i3c/mock-i3c-target.h
>   create mode 100644 hw/i3c/mock-i3c-target.c
> 
> diff --git a/include/hw/i3c/mock-i3c-target.h b/include/hw/i3c/mock-i3c-target.h
> new file mode 100644
> index 0000000000..8c6003ae8b
> --- /dev/null
> +++ b/include/hw/i3c/mock-i3c-target.h
> @@ -0,0 +1,52 @@
> +#ifndef MOCK_I3C_TARGET_H_
> +#define MOCK_I3C_TARGET_H_
> +
> +/*
> + * Mock I3C Device
> + *
> + * Copyright (c) 2025 Google LLC
> + *
> + * The mock I3C device can be thought of as a simple EEPROM. It has a buffer,
> + * and the pointer in the buffer is reset to 0 on an I3C STOP.
> + * To write to the buffer, issue a private write and send data.
> + * To read from the buffer, issue a private read.
> + *
> + * The mock target also supports sending target interrupt IBIs.
> + * To issue an IBI, set the 'ibi-magic-num' property to a non-zero number, and
> + * send that number in a private transaction. The mock target will issue an IBI
> + * after 1 second.
> + *
> + * It also supports a handful of CCCs that are typically used when probing I3C
> + * devices.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/timer.h"
> +#include "hw/i3c/i3c.h"
> +
> +#define TYPE_MOCK_I3C_TARGET "mock-i3c-target"
> +OBJECT_DECLARE_SIMPLE_TYPE(MockI3cTargetState, MOCK_I3C_TARGET)
> +
> +struct MockI3cTargetState {
> +    I3CTarget parent_obj;
> +
> +    /* General device state */
> +    bool can_ibi;
> +    QEMUTimer qtimer;
> +    size_t p_buf;
> +    uint8_t *buf;
> +
> +    /* For Handing CCCs. */
> +    bool in_ccc;
> +    I3CCCC curr_ccc;
> +    uint8_t ccc_byte_offset;
> +
> +    struct {
> +        uint32_t buf_size;
> +        uint8_t ibi_magic;
> +    } cfg;
> +};
> +
> +#endif
> diff --git a/hw/i3c/mock-i3c-target.c b/hw/i3c/mock-i3c-target.c
> new file mode 100644
> index 0000000000..875cd7c7d0
> --- /dev/null
> +++ b/hw/i3c/mock-i3c-target.c
> @@ -0,0 +1,298 @@
> +/*
> + * Mock I3C Device
> + *
> + * Copyright (c) 2025 Google LLC
> + *
> + * The mock I3C device can be thought of as a simple EEPROM. It has a buffer,
> + * and the pointer in the buffer is reset to 0 on an I3C STOP.
> + * To write to the buffer, issue a private write and send data.
> + * To read from the buffer, issue a private read.
> + *
> + * The mock target also supports sending target interrupt IBIs.
> + * To issue an IBI, set the 'ibi-magic-num' property to a non-zero number, and
> + * send that number in a private transaction. The mock target will issue an IBI
> + * after 1 second.
> + *
> + * It also supports a handful of CCCs that are typically used when probing I3C
> + * devices.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "trace.h"
> +#include "hw/i3c/i3c.h"
> +#include "hw/i3c/mock-i3c-target.h"
> +#include "hw/core/irq.h"
> +#include "hw/core/qdev-properties.h"
> +#include "qapi/error.h"
> +#include "qemu/module.h"
> +
> +#define IBI_DELAY_NS (1 * 1000 * 1000)
> +
> +static uint32_t mock_i3c_target_rx(I3CTarget *i3c, uint8_t *data,
> +                                   uint32_t num_to_read)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> +    uint32_t i;
> +
> +    /* Bounds check. */
> +    if (s->p_buf == s->cfg.buf_size) {
> +        return 0;
> +    }
> +
> +    for (i = 0; i < num_to_read; i++) {
> +        data[i] = s->buf[s->p_buf];
> +        trace_mock_i3c_target_rx(data[i]);
> +        s->p_buf++;
> +        if (s->p_buf == s->cfg.buf_size) {
> +            break;
> +        }
> +    }
> +
> +    /* Return the number of bytes we're sending to the controller. */
> +    return i;
> +}
> +
> +static void mock_i3c_target_ibi_timer_start(MockI3cTargetState *s)
> +{
> +    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +    timer_mod(&s->qtimer, now + IBI_DELAY_NS);
> +}
> +
> +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
> +                              uint32_t num_to_send, uint32_t *num_sent)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> +    int ret;
> +    uint32_t to_write;
> +
> +    if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) {
> +        mock_i3c_target_ibi_timer_start(s);
> +        return 0;
> +    }
> +
> +    /* Bounds check. */
> +    if (num_to_send + s->p_buf > s->cfg.buf_size) {
> +        to_write = s->cfg.buf_size - s->p_buf;
> +        ret = -1;
> +    } else {
> +        to_write = num_to_send;
> +        ret = 0;
> +    }
> +    for (uint32_t i = 0; i < to_write; i++) {
> +        trace_mock_i3c_target_tx(data[i]);
> +        s->buf[s->p_buf] = data[i];
> +        s->p_buf++;
> +    }
> +    return ret;
> +}
> +
> +static int mock_i3c_target_event(I3CTarget *i3c, enum I3CEvent event)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> +
> +    trace_mock_i3c_target_event(event);
> +    if (event == I3C_STOP) {
> +        s->in_ccc = false;
> +        s->curr_ccc = 0;
> +        s->ccc_byte_offset = 0;
> +        s->p_buf = 0;
> +    }
> +
> +    return 0;
> +}
> +
> +static int mock_i3c_target_handle_ccc_read(I3CTarget *i3c, uint8_t *data,
> +                                           uint32_t num_to_read,
> +                                           uint32_t *num_read)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> +
> +    switch (s->curr_ccc) {
> +    case I3C_CCCD_GETMXDS:
> +        /* Default data rate for I3C. */
> +        while (s->ccc_byte_offset < num_to_read) {
> +            if (s->ccc_byte_offset >= 2) {
> +                break;
> +            }
> +            data[s->ccc_byte_offset] = 0;
> +            *num_read = s->ccc_byte_offset;
> +            s->ccc_byte_offset++;
> +        }
> +        break;
> +    case I3C_CCCD_GETCAPS:
> +        /* Support I3C version 1.1.x, no other features. */
> +        while (s->ccc_byte_offset < num_to_read) {
> +            if (s->ccc_byte_offset >= 2) {
> +                break;
> +            }
> +            if (s->ccc_byte_offset == 0) {
> +                data[s->ccc_byte_offset] = 0;
> +            } else {
> +                data[s->ccc_byte_offset] = 0x01;
> +            }
> +            *num_read = s->ccc_byte_offset;
> +            s->ccc_byte_offset++;
> +        }
> +        break;
> +    case I3C_CCCD_GETMWL:
> +    case I3C_CCCD_GETMRL:
> +        /* MWL/MRL is MSB first. */
> +        while (s->ccc_byte_offset < num_to_read) {
> +            if (s->ccc_byte_offset >= 2) {
> +                break;
> +            }
> +            data[s->ccc_byte_offset] = (s->cfg.buf_size &
> +                                        (0xff00 >> (s->ccc_byte_offset * 8))) >>
> +                                        (8 - (s->ccc_byte_offset * 8));

This is difficult to read ...

C.



> +            s->ccc_byte_offset++;
> +            *num_read = num_to_read;
> +        }
> +        break;
> +    case I3C_CCC_ENTDAA:
> +    case I3C_CCCD_GETPID:
> +    case I3C_CCCD_GETBCR:
> +    case I3C_CCCD_GETDCR:
> +        /* Nothing to do. */
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc);
> +        return -1;
> +    }
> +
> +    trace_mock_i3c_target_handle_ccc_read(*num_read, num_to_read);
> +    return 0;
> +}
> +
> +static int mock_i3c_target_handle_ccc_write(I3CTarget *i3c, const uint8_t *data,
> +                                            uint32_t num_to_send,
> +                                            uint32_t *num_sent)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> +
> +    if (!s->curr_ccc) {
> +        s->in_ccc = true;
> +        s->curr_ccc = *data;
> +        trace_mock_i3c_target_new_ccc(s->curr_ccc);
> +    }
> +
> +    *num_sent = 1;
> +    switch (s->curr_ccc) {
> +    case I3C_CCC_ENEC:
> +    case I3C_CCCD_ENEC:
> +        s->can_ibi = true;
> +        break;
> +    case I3C_CCC_DISEC:
> +    case I3C_CCCD_DISEC:
> +        s->can_ibi = false;
> +        break;
> +    case I3C_CCC_ENTDAA:
> +    case I3C_CCC_SETAASA:
> +    case I3C_CCC_RSTDAA:
> +    case I3C_CCCD_SETDASA:
> +    case I3C_CCCD_GETPID:
> +    case I3C_CCCD_GETBCR:
> +    case I3C_CCCD_GETDCR:
> +    case I3C_CCCD_GETMWL:
> +    case I3C_CCCD_GETMRL:
> +    case I3C_CCCD_GETMXDS:
> +    case I3C_CCCD_GETCAPS:
> +        /* Nothing to do. */
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc);
> +        return -1;
> +    }
> +
> +    trace_mock_i3c_target_handle_ccc_write(*num_sent, num_to_send);
> +    return 0;
> +}
> +
> +static void mock_i3c_target_do_ibi(MockI3cTargetState *s)
> +{
> +    if (!s->can_ibi) {
> +        return;
> +    }
> +
> +    trace_mock_i3c_target_do_ibi(s->parent_obj.address, true);
> +    int nack = i3c_target_send_ibi(&s->parent_obj, s->parent_obj.address,
> +                                   /*is_recv=*/true);
> +    /* Getting NACKed isn't necessarily an error, just print it out. */
> +    if (nack) {
> +        trace_mock_i3c_target_do_ibi_nack("sending");
> +    }
> +    nack = i3c_target_ibi_finish(&s->parent_obj, 0x00);
> +    if (nack) {
> +        trace_mock_i3c_target_do_ibi_nack("finishing");
> +    }
> +}
> +
> +static void mock_i3c_target_timer_elapsed(void *opaque)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(opaque);
> +    timer_del(&s->qtimer);
> +    mock_i3c_target_do_ibi(s);
> +}
> +
> +static void mock_i3c_target_reset(I3CTarget *i3c)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> +    s->can_ibi = false;
> +}
> +
> +static void mock_i3c_target_realize(DeviceState *dev, Error **errp)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(dev);
> +    s->buf = g_new0(uint8_t, s->cfg.buf_size);
> +    mock_i3c_target_reset(&s->parent_obj);
> +}
> +
> +static void mock_i3c_target_init(Object *obj)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(obj);
> +    s->can_ibi = false;
> +
> +    /* For IBIs. */
> +    timer_init_ns(&s->qtimer, QEMU_CLOCK_VIRTUAL, mock_i3c_target_timer_elapsed,
> +                  s);
> +}
> +
> +static const Property remote_i3c_props[] = {
> +    /* The size of the internal buffer. */
> +    DEFINE_PROP_UINT32("buf-size", MockI3cTargetState, cfg.buf_size, 0x100),
> +    /*
> +     * If the mock target receives this number, it will issue an IBI after
> +     * 1 second. Disabled if the IBI magic number is 0.
> +     */
> +    DEFINE_PROP_UINT8("ibi-magic-num", MockI3cTargetState, cfg.ibi_magic, 0x00),
> +};
> +
> +static void mock_i3c_target_class_init(ObjectClass *klass, const void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    I3CTargetClass *k = I3C_TARGET_CLASS(klass);
> +
> +    dc->realize = mock_i3c_target_realize;
> +    k->event = mock_i3c_target_event;
> +    k->recv = mock_i3c_target_rx;
> +    k->send = mock_i3c_target_tx;
> +    k->handle_ccc_read = mock_i3c_target_handle_ccc_read;
> +    k->handle_ccc_write = mock_i3c_target_handle_ccc_write;
> +
> +    device_class_set_props(dc, remote_i3c_props);
> +}
> +
> +static const TypeInfo mock_i3c_target_types[] = {
> +    {
> +        .name          = TYPE_MOCK_I3C_TARGET,
> +        .parent        = TYPE_I3C_TARGET,
> +        .instance_size = sizeof(MockI3cTargetState),
> +        .instance_init = mock_i3c_target_init,
> +        .class_init    = mock_i3c_target_class_init,
> +    },
> +};
> +
> +DEFINE_TYPES(mock_i3c_target_types)
> +
> diff --git a/hw/i3c/Kconfig b/hw/i3c/Kconfig
> index ecec77d6fc..d5c6d4049b 100644
> --- a/hw/i3c/Kconfig
> +++ b/hw/i3c/Kconfig
> @@ -3,3 +3,13 @@ config I3C
>   
>   config DW_I3C
>       bool
> +
> +config I3C_DEVICES
> +    # Device group for i3c devices which can reasonably be user-plugged to any
> +    # board's i3c bus.
> +    bool
> +
> +config MOCK_I3C_TARGET
> +    bool
> +    select I3C
> +    default y if I3C_DEVICES
> diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build
> index 83d75e7d5c..e614b18712 100644
> --- a/hw/i3c/meson.build
> +++ b/hw/i3c/meson.build
> @@ -2,4 +2,5 @@ i3c_ss = ss.source_set()
>   i3c_ss.add(when: 'CONFIG_I3C', if_true: files('core.c'))
>   i3c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i3c.c'))
>   i3c_ss.add(when: 'CONFIG_DW_I3C', if_true: files('dw-i3c.c'))
> +i3c_ss.add(when: 'CONFIG_MOCK_I3C_TARGET', if_true: files('mock-i3c-target.c'))
>   system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss)
> diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
> index 39f33d9a50..9e58edec99 100644
> --- a/hw/i3c/trace-events
> +++ b/hw/i3c/trace-events
> @@ -36,3 +36,13 @@ legacy_i2c_recv(uint8_t byte) "Legacy I2C recv 0x%" PRIx8
>   legacy_i2c_send(uint8_t byte) "Legacy I2C send 0x%" PRIx8
>   legacy_i2c_start_transfer(uint8_t address, bool is_recv) "Legacy I2C START with address 0x%" PRIx8 " is_recv=%d"
>   legacy_i2c_end_transfer(void) "Legacy I2C STOP"
> +
> +# mock-target.c
> +mock_i3c_target_rx(uint8_t byte) "I3C mock target read 0x%" PRIx8
> +mock_i3c_target_tx(uint8_t byte) "I3C mock target write 0x%" PRIx8
> +mock_i3c_target_event(uint8_t event) "I3C mock target event 0x%" PRIx8
> +mock_i3c_target_handle_ccc_read(uint32_t num_read, uint32_t num_to_read) "I3C mock target read %" PRId32 "/%" PRId32 " bytes"
> +mock_i3c_target_new_ccc(uint8_t ccc) "I3C mock target handle CCC 0x%" PRIx8
> +mock_i3c_target_handle_ccc_write(uint32_t num_sent, uint32_t num_to_send) "I3C mock target send %" PRId32 "/%" PRId32 " bytes"
> +mock_i3c_target_do_ibi(uint8_t address, bool is_recv) "I3C mock target IBI with address 0x%" PRIx8 " RnW=%d"
> +mock_i3c_target_do_ibi_nack(const char *reason) "NACKed from controller when %s target interrupt"
RE: [PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Jamin Lin 1 month, 1 week ago
Hi Cédric,

> Subject: Re: [PATCH v7 18/22] hw/i3c: Add Mock target
> 
> On 2/25/26 03:12, Jamin Lin wrote:
> > Adds a simple i3c device to be used for testing in lieu of a real
> > device.
> >
> > The mock target supports the following features:
> > - A buffer that users can read and write to.
> > - CCC support for commonly used CCCs when probing devices on an I3C bus.
> > - IBI sending upon receiving a user-defined byte.
> >
> > Signed-off-by: Joe Komlodi <komlodi@google.com>
> > Reviewed-by: Titus Rwantare <titusr@google.com>
> > Reviewed-by: Patrick Venture <venture@google.com>
> > Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > ---
> >   include/hw/i3c/mock-i3c-target.h |  52 ++++++
> >   hw/i3c/mock-i3c-target.c         | 298
> +++++++++++++++++++++++++++++++
> >   hw/i3c/Kconfig                   |  10 ++
> >   hw/i3c/meson.build               |   1 +
> >   hw/i3c/trace-events              |  10 ++
> >   5 files changed, 371 insertions(+)
> >   create mode 100644 include/hw/i3c/mock-i3c-target.h
> >   create mode 100644 hw/i3c/mock-i3c-target.c
> >
> > diff --git a/include/hw/i3c/mock-i3c-target.h
> > b/include/hw/i3c/mock-i3c-target.h
> > new file mode 100644
> > index 0000000000..8c6003ae8b
> > --- /dev/null
> > +++ b/include/hw/i3c/mock-i3c-target.h
> > @@ -0,0 +1,52 @@
> > +#ifndef MOCK_I3C_TARGET_H_
> > +#define MOCK_I3C_TARGET_H_
> > +
> > +/*
> > + * Mock I3C Device
> > + *
> > + * Copyright (c) 2025 Google LLC
> > + *
> > + * The mock I3C device can be thought of as a simple EEPROM. It has a
> > +buffer,
> > + * and the pointer in the buffer is reset to 0 on an I3C STOP.
> > + * To write to the buffer, issue a private write and send data.
> > + * To read from the buffer, issue a private read.
> > + *
> > + * The mock target also supports sending target interrupt IBIs.
> > + * To issue an IBI, set the 'ibi-magic-num' property to a non-zero
> > +number, and
> > + * send that number in a private transaction. The mock target will
> > +issue an IBI
> > + * after 1 second.
> > + *
> > + * It also supports a handful of CCCs that are typically used when
> > +probing I3C
> > + * devices.
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later  */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qemu/timer.h"
> > +#include "hw/i3c/i3c.h"
> > +
> > +#define TYPE_MOCK_I3C_TARGET "mock-i3c-target"
> > +OBJECT_DECLARE_SIMPLE_TYPE(MockI3cTargetState, MOCK_I3C_TARGET)
> > +
> > +struct MockI3cTargetState {
> > +    I3CTarget parent_obj;
> > +
> > +    /* General device state */
> > +    bool can_ibi;
> > +    QEMUTimer qtimer;
> > +    size_t p_buf;
> > +    uint8_t *buf;
> > +
> > +    /* For Handing CCCs. */
> > +    bool in_ccc;
> > +    I3CCCC curr_ccc;
> > +    uint8_t ccc_byte_offset;
> > +
> > +    struct {
> > +        uint32_t buf_size;
> > +        uint8_t ibi_magic;
> > +    } cfg;
> > +};
> > +
> > +#endif
> > diff --git a/hw/i3c/mock-i3c-target.c b/hw/i3c/mock-i3c-target.c new
> > file mode 100644 index 0000000000..875cd7c7d0
> > --- /dev/null
> > +++ b/hw/i3c/mock-i3c-target.c
> > @@ -0,0 +1,298 @@
> > +/*
> > + * Mock I3C Device
> > + *
> > + * Copyright (c) 2025 Google LLC
> > + *
> > + * The mock I3C device can be thought of as a simple EEPROM. It has a
> > +buffer,
> > + * and the pointer in the buffer is reset to 0 on an I3C STOP.
> > + * To write to the buffer, issue a private write and send data.
> > + * To read from the buffer, issue a private read.
> > + *
> > + * The mock target also supports sending target interrupt IBIs.
> > + * To issue an IBI, set the 'ibi-magic-num' property to a non-zero
> > +number, and
> > + * send that number in a private transaction. The mock target will
> > +issue an IBI
> > + * after 1 second.
> > + *
> > + * It also supports a handful of CCCs that are typically used when
> > +probing I3C
> > + * devices.
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later  */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qemu/log.h"
> > +#include "trace.h"
> > +#include "hw/i3c/i3c.h"
> > +#include "hw/i3c/mock-i3c-target.h"
> > +#include "hw/core/irq.h"
> > +#include "hw/core/qdev-properties.h"
> > +#include "qapi/error.h"
> > +#include "qemu/module.h"
> > +
> > +#define IBI_DELAY_NS (1 * 1000 * 1000)
> > +
> > +static uint32_t mock_i3c_target_rx(I3CTarget *i3c, uint8_t *data,
> > +                                   uint32_t num_to_read) {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> > +    uint32_t i;
> > +
> > +    /* Bounds check. */
> > +    if (s->p_buf == s->cfg.buf_size) {
> > +        return 0;
> > +    }
> > +
> > +    for (i = 0; i < num_to_read; i++) {
> > +        data[i] = s->buf[s->p_buf];
> > +        trace_mock_i3c_target_rx(data[i]);
> > +        s->p_buf++;
> > +        if (s->p_buf == s->cfg.buf_size) {
> > +            break;
> > +        }
> > +    }
> > +
> > +    /* Return the number of bytes we're sending to the controller. */
> > +    return i;
> > +}
> > +
> > +static void mock_i3c_target_ibi_timer_start(MockI3cTargetState *s) {
> > +    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> > +    timer_mod(&s->qtimer, now + IBI_DELAY_NS); }
> > +
> > +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
> > +                              uint32_t num_to_send, uint32_t
> > +*num_sent) {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> > +    int ret;
> > +    uint32_t to_write;
> > +
> > +    if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic ==
> *data) {
> > +        mock_i3c_target_ibi_timer_start(s);
> > +        return 0;
> > +    }
> > +
> > +    /* Bounds check. */
> > +    if (num_to_send + s->p_buf > s->cfg.buf_size) {
> > +        to_write = s->cfg.buf_size - s->p_buf;
> > +        ret = -1;
> > +    } else {
> > +        to_write = num_to_send;
> > +        ret = 0;
> > +    }
> > +    for (uint32_t i = 0; i < to_write; i++) {
> > +        trace_mock_i3c_target_tx(data[i]);
> > +        s->buf[s->p_buf] = data[i];
> > +        s->p_buf++;
> > +    }
> > +    return ret;
> > +}
> > +
> > +static int mock_i3c_target_event(I3CTarget *i3c, enum I3CEvent event)
> > +{
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> > +
> > +    trace_mock_i3c_target_event(event);
> > +    if (event == I3C_STOP) {
> > +        s->in_ccc = false;
> > +        s->curr_ccc = 0;
> > +        s->ccc_byte_offset = 0;
> > +        s->p_buf = 0;
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +static int mock_i3c_target_handle_ccc_read(I3CTarget *i3c, uint8_t *data,
> > +                                           uint32_t num_to_read,
> > +                                           uint32_t *num_read) {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> > +
> > +    switch (s->curr_ccc) {
> > +    case I3C_CCCD_GETMXDS:
> > +        /* Default data rate for I3C. */
> > +        while (s->ccc_byte_offset < num_to_read) {
> > +            if (s->ccc_byte_offset >= 2) {
> > +                break;
> > +            }
> > +            data[s->ccc_byte_offset] = 0;
> > +            *num_read = s->ccc_byte_offset;
> > +            s->ccc_byte_offset++;
> > +        }
> > +        break;
> > +    case I3C_CCCD_GETCAPS:
> > +        /* Support I3C version 1.1.x, no other features. */
> > +        while (s->ccc_byte_offset < num_to_read) {
> > +            if (s->ccc_byte_offset >= 2) {
> > +                break;
> > +            }
> > +            if (s->ccc_byte_offset == 0) {
> > +                data[s->ccc_byte_offset] = 0;
> > +            } else {
> > +                data[s->ccc_byte_offset] = 0x01;
> > +            }
> > +            *num_read = s->ccc_byte_offset;
> > +            s->ccc_byte_offset++;
> > +        }
> > +        break;
> > +    case I3C_CCCD_GETMWL:
> > +    case I3C_CCCD_GETMRL:
> > +        /* MWL/MRL is MSB first. */
> > +        while (s->ccc_byte_offset < num_to_read) {
> > +            if (s->ccc_byte_offset >= 2) {
> > +                break;
> > +            }
> > +            data[s->ccc_byte_offset] = (s->cfg.buf_size &
> > +                                        (0xff00 >>
> (s->ccc_byte_offset * 8))) >>
> > +                                        (8 - (s->ccc_byte_offset *
> > + 8));
> 
> This is difficult to read ...
> 

Thanks for your review and suggestion.
I send v1 here, https://patchwork.kernel.org/project/qemu-devel/list/?series=1059757  and ready for review.

Thanks,
Jamin


> C.
> 
> 
> 
> > +            s->ccc_byte_offset++;
> > +            *num_read = num_to_read;
> > +        }
> > +        break;
> > +    case I3C_CCC_ENTDAA:
> > +    case I3C_CCCD_GETPID:
> > +    case I3C_CCCD_GETBCR:
> > +    case I3C_CCCD_GETDCR:
> > +        /* Nothing to do. */
> > +        break;
> > +    default:
> > +        qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n",
> s->curr_ccc);
> > +        return -1;
> > +    }
> > +
> > +    trace_mock_i3c_target_handle_ccc_read(*num_read, num_to_read);
> > +    return 0;
> > +}
> > +
> > +static int mock_i3c_target_handle_ccc_write(I3CTarget *i3c, const uint8_t
> *data,
> > +                                            uint32_t
> num_to_send,
> > +                                            uint32_t *num_sent)
> {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> > +
> > +    if (!s->curr_ccc) {
> > +        s->in_ccc = true;
> > +        s->curr_ccc = *data;
> > +        trace_mock_i3c_target_new_ccc(s->curr_ccc);
> > +    }
> > +
> > +    *num_sent = 1;
> > +    switch (s->curr_ccc) {
> > +    case I3C_CCC_ENEC:
> > +    case I3C_CCCD_ENEC:
> > +        s->can_ibi = true;
> > +        break;
> > +    case I3C_CCC_DISEC:
> > +    case I3C_CCCD_DISEC:
> > +        s->can_ibi = false;
> > +        break;
> > +    case I3C_CCC_ENTDAA:
> > +    case I3C_CCC_SETAASA:
> > +    case I3C_CCC_RSTDAA:
> > +    case I3C_CCCD_SETDASA:
> > +    case I3C_CCCD_GETPID:
> > +    case I3C_CCCD_GETBCR:
> > +    case I3C_CCCD_GETDCR:
> > +    case I3C_CCCD_GETMWL:
> > +    case I3C_CCCD_GETMRL:
> > +    case I3C_CCCD_GETMXDS:
> > +    case I3C_CCCD_GETCAPS:
> > +        /* Nothing to do. */
> > +        break;
> > +    default:
> > +        qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n",
> s->curr_ccc);
> > +        return -1;
> > +    }
> > +
> > +    trace_mock_i3c_target_handle_ccc_write(*num_sent, num_to_send);
> > +    return 0;
> > +}
> > +
> > +static void mock_i3c_target_do_ibi(MockI3cTargetState *s) {
> > +    if (!s->can_ibi) {
> > +        return;
> > +    }
> > +
> > +    trace_mock_i3c_target_do_ibi(s->parent_obj.address, true);
> > +    int nack = i3c_target_send_ibi(&s->parent_obj, s->parent_obj.address,
> > +                                   /*is_recv=*/true);
> > +    /* Getting NACKed isn't necessarily an error, just print it out. */
> > +    if (nack) {
> > +        trace_mock_i3c_target_do_ibi_nack("sending");
> > +    }
> > +    nack = i3c_target_ibi_finish(&s->parent_obj, 0x00);
> > +    if (nack) {
> > +        trace_mock_i3c_target_do_ibi_nack("finishing");
> > +    }
> > +}
> > +
> > +static void mock_i3c_target_timer_elapsed(void *opaque) {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(opaque);
> > +    timer_del(&s->qtimer);
> > +    mock_i3c_target_do_ibi(s);
> > +}
> > +
> > +static void mock_i3c_target_reset(I3CTarget *i3c) {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> > +    s->can_ibi = false;
> > +}
> > +
> > +static void mock_i3c_target_realize(DeviceState *dev, Error **errp) {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(dev);
> > +    s->buf = g_new0(uint8_t, s->cfg.buf_size);
> > +    mock_i3c_target_reset(&s->parent_obj);
> > +}
> > +
> > +static void mock_i3c_target_init(Object *obj) {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(obj);
> > +    s->can_ibi = false;
> > +
> > +    /* For IBIs. */
> > +    timer_init_ns(&s->qtimer, QEMU_CLOCK_VIRTUAL,
> mock_i3c_target_timer_elapsed,
> > +                  s);
> > +}
> > +
> > +static const Property remote_i3c_props[] = {
> > +    /* The size of the internal buffer. */
> > +    DEFINE_PROP_UINT32("buf-size", MockI3cTargetState, cfg.buf_size,
> 0x100),
> > +    /*
> > +     * If the mock target receives this number, it will issue an IBI after
> > +     * 1 second. Disabled if the IBI magic number is 0.
> > +     */
> > +    DEFINE_PROP_UINT8("ibi-magic-num", MockI3cTargetState,
> > +cfg.ibi_magic, 0x00), };
> > +
> > +static void mock_i3c_target_class_init(ObjectClass *klass, const void
> > +*data) {
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +    I3CTargetClass *k = I3C_TARGET_CLASS(klass);
> > +
> > +    dc->realize = mock_i3c_target_realize;
> > +    k->event = mock_i3c_target_event;
> > +    k->recv = mock_i3c_target_rx;
> > +    k->send = mock_i3c_target_tx;
> > +    k->handle_ccc_read = mock_i3c_target_handle_ccc_read;
> > +    k->handle_ccc_write = mock_i3c_target_handle_ccc_write;
> > +
> > +    device_class_set_props(dc, remote_i3c_props); }
> > +
> > +static const TypeInfo mock_i3c_target_types[] = {
> > +    {
> > +        .name          = TYPE_MOCK_I3C_TARGET,
> > +        .parent        = TYPE_I3C_TARGET,
> > +        .instance_size = sizeof(MockI3cTargetState),
> > +        .instance_init = mock_i3c_target_init,
> > +        .class_init    = mock_i3c_target_class_init,
> > +    },
> > +};
> > +
> > +DEFINE_TYPES(mock_i3c_target_types)
> > +
> > diff --git a/hw/i3c/Kconfig b/hw/i3c/Kconfig index
> > ecec77d6fc..d5c6d4049b 100644
> > --- a/hw/i3c/Kconfig
> > +++ b/hw/i3c/Kconfig
> > @@ -3,3 +3,13 @@ config I3C
> >
> >   config DW_I3C
> >       bool
> > +
> > +config I3C_DEVICES
> > +    # Device group for i3c devices which can reasonably be user-plugged
> to any
> > +    # board's i3c bus.
> > +    bool
> > +
> > +config MOCK_I3C_TARGET
> > +    bool
> > +    select I3C
> > +    default y if I3C_DEVICES
> > diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build index
> > 83d75e7d5c..e614b18712 100644
> > --- a/hw/i3c/meson.build
> > +++ b/hw/i3c/meson.build
> > @@ -2,4 +2,5 @@ i3c_ss = ss.source_set()
> >   i3c_ss.add(when: 'CONFIG_I3C', if_true: files('core.c'))
> >   i3c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i3c.c'))
> >   i3c_ss.add(when: 'CONFIG_DW_I3C', if_true: files('dw-i3c.c'))
> > +i3c_ss.add(when: 'CONFIG_MOCK_I3C_TARGET', if_true:
> > +files('mock-i3c-target.c'))
> >   system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss) diff --git
> > a/hw/i3c/trace-events b/hw/i3c/trace-events index
> > 39f33d9a50..9e58edec99 100644
> > --- a/hw/i3c/trace-events
> > +++ b/hw/i3c/trace-events
> > @@ -36,3 +36,13 @@ legacy_i2c_recv(uint8_t byte) "Legacy I2C recv 0x%"
> PRIx8
> >   legacy_i2c_send(uint8_t byte) "Legacy I2C send 0x%" PRIx8
> >   legacy_i2c_start_transfer(uint8_t address, bool is_recv) "Legacy I2C START
> with address 0x%" PRIx8 " is_recv=%d"
> >   legacy_i2c_end_transfer(void) "Legacy I2C STOP"
> > +
> > +# mock-target.c
> > +mock_i3c_target_rx(uint8_t byte) "I3C mock target read 0x%" PRIx8
> > +mock_i3c_target_tx(uint8_t byte) "I3C mock target write 0x%" PRIx8
> > +mock_i3c_target_event(uint8_t event) "I3C mock target event 0x%"
> > +PRIx8 mock_i3c_target_handle_ccc_read(uint32_t num_read, uint32_t
> num_to_read) "I3C mock target read %" PRId32 "/%" PRId32 " bytes"
> > +mock_i3c_target_new_ccc(uint8_t ccc) "I3C mock target handle CCC 0x%"
> > +PRIx8 mock_i3c_target_handle_ccc_write(uint32_t num_sent, uint32_t
> num_to_send) "I3C mock target send %" PRId32 "/%" PRId32 " bytes"
> > +mock_i3c_target_do_ibi(uint8_t address, bool is_recv) "I3C mock target IBI
> with address 0x%" PRIx8 " RnW=%d"
> > +mock_i3c_target_do_ibi_nack(const char *reason) "NACKed from controller
> when %s target interrupt"

Re: [PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Jithu Joseph 1 month, 1 week ago
Apologies for chiming in this late ... I only got a chance to test this last week
This really is a minor comment ... can be addressed subsequently

On 2/24/2026 6:12 PM, Jamin Lin wrote:
> Adds a simple i3c device to be used for testing in lieu of a real
> device.
> 
> The mock target supports the following features:
> - A buffer that users can read and write to.
> - CCC support for commonly used CCCs when probing devices on an I3C bus.
> - IBI sending upon receiving a user-defined byte.
> 
> Signed-off-by: Joe Komlodi <komlodi@google.com>
> Reviewed-by: Titus Rwantare <titusr@google.com>
> Reviewed-by: Patrick Venture <venture@google.com>
> Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> ---
>  include/hw/i3c/mock-i3c-target.h |  52 ++++++
>  hw/i3c/mock-i3c-target.c         | 298 +++++++++++++++++++++++++++++++
>  hw/i3c/Kconfig                   |  10 ++
>  hw/i3c/meson.build               |   1 +
>  hw/i3c/trace-events              |  10 ++
>  5 files changed, 371 insertions(+)
>  create mode 100644 include/hw/i3c/mock-i3c-target.h
>  create mode 100644 hw/i3c/mock-i3c-target.c
> 
> diff --git a/include/hw/i3c/mock-i3c-target.h b/include/hw/i3c/mock-i3c-target.h
> new file mode 100644

...

> +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
> +                              uint32_t num_to_send, uint32_t *num_sent)
> +{
> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> +    int ret;
> +    uint32_t to_write;
> +
> +    if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) {
> +        mock_i3c_target_ibi_timer_start(s);
> +        return 0;
> +    }
> +
> +    /* Bounds check. */
> +    if (num_to_send + s->p_buf > s->cfg.buf_size) {
> +        to_write = s->cfg.buf_size - s->p_buf;
> +        ret = -1;
> +    } else {
> +        to_write = num_to_send;
> +        ret = 0;
> +    }
> +    for (uint32_t i = 0; i < to_write; i++) {
> +        trace_mock_i3c_target_tx(data[i]);
> +        s->buf[s->p_buf] = data[i];
> +        s->p_buf++;
> +    }


num_sent is never updated prior to return, so the traces (from caller i3c_send) looked a bit confusing

<snip>
mock_i3c_target_tx I3C mock target write 0x12
i3c_send I3C send 0/1 bytes, ack=1   ---------------------> instead of 1/1 bytes
mock_i3c_target_tx I3C mock target write 0x34
i3c_send I3C send 0/1 bytes, ack=1
</snip>

Something like below is needed
+ *num_sent = to_write 
(might also needed in the ibi magic path above)


> +    return ret;
> +}
> +

Thanks
Jithu
RE: [PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Jamin Lin 1 month, 1 week ago
Hi Jithu Joseph

> Subject: Re: [PATCH v7 18/22] hw/i3c: Add Mock target
> 
> 
> Apologies for chiming in this late ... I only got a chance to test this last week
> This really is a minor comment ... can be addressed subsequently
> 
> On 2/24/2026 6:12 PM, Jamin Lin wrote:
> > Adds a simple i3c device to be used for testing in lieu of a real
> > device.
> >
> > The mock target supports the following features:
> > - A buffer that users can read and write to.
> > - CCC support for commonly used CCCs when probing devices on an I3C bus.
> > - IBI sending upon receiving a user-defined byte.
> >
> > Signed-off-by: Joe Komlodi <komlodi@google.com>
> > Reviewed-by: Titus Rwantare <titusr@google.com>
> > Reviewed-by: Patrick Venture <venture@google.com>
> > Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > ---
> >  include/hw/i3c/mock-i3c-target.h |  52 ++++++
> >  hw/i3c/mock-i3c-target.c         | 298
> +++++++++++++++++++++++++++++++
> >  hw/i3c/Kconfig                   |  10 ++
> >  hw/i3c/meson.build               |   1 +
> >  hw/i3c/trace-events              |  10 ++
> >  5 files changed, 371 insertions(+)
> >  create mode 100644 include/hw/i3c/mock-i3c-target.h  create mode
> > 100644 hw/i3c/mock-i3c-target.c
> >
> > diff --git a/include/hw/i3c/mock-i3c-target.h
> > b/include/hw/i3c/mock-i3c-target.h
> > new file mode 100644
> 
> ...
> 
> > +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
> > +                              uint32_t num_to_send, uint32_t
> > +*num_sent) {
> > +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
> > +    int ret;
> > +    uint32_t to_write;
> > +
> > +    if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic ==
> *data) {
> > +        mock_i3c_target_ibi_timer_start(s);
> > +        return 0;
> > +    }
> > +
> > +    /* Bounds check. */
> > +    if (num_to_send + s->p_buf > s->cfg.buf_size) {
> > +        to_write = s->cfg.buf_size - s->p_buf;
> > +        ret = -1;
> > +    } else {
> > +        to_write = num_to_send;
> > +        ret = 0;
> > +    }
> > +    for (uint32_t i = 0; i < to_write; i++) {
> > +        trace_mock_i3c_target_tx(data[i]);
> > +        s->buf[s->p_buf] = data[i];
> > +        s->p_buf++;
> > +    }
> 
> 
> num_sent is never updated prior to return, so the traces (from caller i3c_send)
> looked a bit confusing
> 
> <snip>
> mock_i3c_target_tx I3C mock target write 0x12
> i3c_send I3C send 0/1 bytes, ack=1   ---------------------> instead of 1/1 bytes
> mock_i3c_target_tx I3C mock target write 0x34 i3c_send I3C send 0/1 bytes,
> ack=1 </snip>
> 
> Something like below is needed
> + *num_sent = to_write
> (might also needed in the ibi magic path above)
> 
> 
> > +    return ret;
> > +}
> > +
> 

Thanks for your review and suggestion.
I send v1 here, https://patchwork.kernel.org/project/qemu-devel/list/?series=1059757 and ready for review.

Thanks,
Jamin

> Thanks
> Jithu

Re: [PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Cédric Le Goater 1 month, 1 week ago
On 2/27/26 02:43, Jithu Joseph wrote:
> 
> Apologies for chiming in this late ... I only got a chance to test this last week
> This really is a minor comment ... can be addressed subsequently

No problem. It is not merged yet and fixes are expected.


> 
> On 2/24/2026 6:12 PM, Jamin Lin wrote:
>> Adds a simple i3c device to be used for testing in lieu of a real
>> device.
>>
>> The mock target supports the following features:
>> - A buffer that users can read and write to.
>> - CCC support for commonly used CCCs when probing devices on an I3C bus.
>> - IBI sending upon receiving a user-defined byte.
>>
>> Signed-off-by: Joe Komlodi <komlodi@google.com>
>> Reviewed-by: Titus Rwantare <titusr@google.com>
>> Reviewed-by: Patrick Venture <venture@google.com>
>> Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
>> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
>> ---
>>   include/hw/i3c/mock-i3c-target.h |  52 ++++++
>>   hw/i3c/mock-i3c-target.c         | 298 +++++++++++++++++++++++++++++++
>>   hw/i3c/Kconfig                   |  10 ++
>>   hw/i3c/meson.build               |   1 +
>>   hw/i3c/trace-events              |  10 ++
>>   5 files changed, 371 insertions(+)
>>   create mode 100644 include/hw/i3c/mock-i3c-target.h
>>   create mode 100644 hw/i3c/mock-i3c-target.c
>>
>> diff --git a/include/hw/i3c/mock-i3c-target.h b/include/hw/i3c/mock-i3c-target.h
>> new file mode 100644
> 
> ...
> 
>> +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
>> +                              uint32_t num_to_send, uint32_t *num_sent)
>> +{
>> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
>> +    int ret;
>> +    uint32_t to_write;
>> +
>> +    if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) {
>> +        mock_i3c_target_ibi_timer_start(s);
>> +        return 0;
>> +    }
>> +
>> +    /* Bounds check. */
>> +    if (num_to_send + s->p_buf > s->cfg.buf_size) {
>> +        to_write = s->cfg.buf_size - s->p_buf;
>> +        ret = -1;
>> +    } else {
>> +        to_write = num_to_send;
>> +        ret = 0;
>> +    }
>> +    for (uint32_t i = 0; i < to_write; i++) {
>> +        trace_mock_i3c_target_tx(data[i]);
>> +        s->buf[s->p_buf] = data[i];
>> +        s->p_buf++;
>> +    }
> 
> 
> num_sent is never updated prior to return, so the traces (from caller i3c_send) looked a bit confusing
> 
> <snip>
> mock_i3c_target_tx I3C mock target write 0x12
> i3c_send I3C send 0/1 bytes, ack=1   ---------------------> instead of 1/1 bytes
> mock_i3c_target_tx I3C mock target write 0x34
> i3c_send I3C send 0/1 bytes, ack=1
> </snip>
> 
> Something like below is needed
> + *num_sent = to_write
> (might also needed in the ibi magic path above)


Please send a patch.

Thanks,

C.
Re: [PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Jithu Joseph 1 month, 1 week ago
On 2/26/2026 11:52 PM, Cédric Le Goater wrote:
> On 2/27/26 02:43, Jithu Joseph wrote:
>>
>> Apologies for chiming in this late ... I only got a chance to test this last week
>> This really is a minor comment ... can be addressed subsequently
> 
> No problem. It is not merged yet and fixes are expected.
> 

...

>>> +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
>>> +                              uint32_t num_to_send, uint32_t *num_sent)
>>> +{
>>> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
>>> +    int ret;
>>> +    uint32_t to_write;
>>> +
>>> +    if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) {
>>> +        mock_i3c_target_ibi_timer_start(s);
>>> +        return 0;
>>> +    }
>>> +
>>> +    /* Bounds check. */
>>> +    if (num_to_send + s->p_buf > s->cfg.buf_size) {
>>> +        to_write = s->cfg.buf_size - s->p_buf;
>>> +        ret = -1;
>>> +    } else {
>>> +        to_write = num_to_send;
>>> +        ret = 0;
>>> +    }
>>> +    for (uint32_t i = 0; i < to_write; i++) {
>>> +        trace_mock_i3c_target_tx(data[i]);
>>> +        s->buf[s->p_buf] = data[i];
>>> +        s->p_buf++;
>>> +    }
>>
>>
>> num_sent is never updated prior to return, so the traces (from caller i3c_send) looked a bit confusing
>>
>> <snip>
>> mock_i3c_target_tx I3C mock target write 0x12
>> i3c_send I3C send 0/1 bytes, ack=1   ---------------------> instead of 1/1 bytes
>> mock_i3c_target_tx I3C mock target write 0x34
>> i3c_send I3C send 0/1 bytes, ack=1
>> </snip>
>>
>> Something like below is needed
>> + *num_sent = to_write
>> (might also needed in the ibi magic path above)
> 
> 
> Please send a patch.

Would you like me to revise this patch with the changes, and send it in-reply-to v7 18/22 ?

or would you like me to first apply the whole series and create a new patch with just the delta suggested above ?


Jithu


Re: [PATCH v7 18/22] hw/i3c: Add Mock target
Posted by Cédric Le Goater 1 month, 1 week ago
On 2/27/26 20:49, Jithu Joseph wrote:
> On 2/26/2026 11:52 PM, Cédric Le Goater wrote:
>> On 2/27/26 02:43, Jithu Joseph wrote:
>>>
>>> Apologies for chiming in this late ... I only got a chance to test this last week
>>> This really is a minor comment ... can be addressed subsequently
>>
>> No problem. It is not merged yet and fixes are expected.
>>
> 
> ...
> 
>>>> +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
>>>> +                              uint32_t num_to_send, uint32_t *num_sent)
>>>> +{
>>>> +    MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
>>>> +    int ret;
>>>> +    uint32_t to_write;
>>>> +
>>>> +    if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) {
>>>> +        mock_i3c_target_ibi_timer_start(s);
>>>> +        return 0;
>>>> +    }
>>>> +
>>>> +    /* Bounds check. */
>>>> +    if (num_to_send + s->p_buf > s->cfg.buf_size) {
>>>> +        to_write = s->cfg.buf_size - s->p_buf;
>>>> +        ret = -1;
>>>> +    } else {
>>>> +        to_write = num_to_send;
>>>> +        ret = 0;
>>>> +    }
>>>> +    for (uint32_t i = 0; i < to_write; i++) {
>>>> +        trace_mock_i3c_target_tx(data[i]);
>>>> +        s->buf[s->p_buf] = data[i];
>>>> +        s->p_buf++;
>>>> +    }
>>>
>>>
>>> num_sent is never updated prior to return, so the traces (from caller i3c_send) looked a bit confusing
>>>
>>> <snip>
>>> mock_i3c_target_tx I3C mock target write 0x12
>>> i3c_send I3C send 0/1 bytes, ack=1   ---------------------> instead of 1/1 bytes
>>> mock_i3c_target_tx I3C mock target write 0x34
>>> i3c_send I3C send 0/1 bytes, ack=1
>>> </snip>
>>>
>>> Something like below is needed
>>> + *num_sent = to_write
>>> (might also needed in the ibi magic path above)
>>
>>
>> Please send a patch.
> 
> Would you like me to revise this patch with the changes, and send it in-reply-to v7 18/22 ?
> 
> or would you like me to first apply the whole series and create a new patch with just the delta suggested above ?

I rather have new patches on top of this series for now. I might merge
some if they are trivial.

Thanks,

C.