From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "Paolo Bonzini" <pbonzini@redhat.com>,
"Peter Maydell" <peter.maydell@linaro.org>,
"Cédric Le Goater" <clg@kaod.org>,
"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>,
"open list:All patches CC here" <qemu-devel@nongnu.org>,
"open list:ARM TCG CPUs" <qemu-arm@nongnu.org>
Cc: Jamin Lin <jamin_lin@aspeedtech.com>,
Troy Lee <troy_lee@aspeedtech.com>,
Kane Chen <kane_chen@aspeedtech.com>,
"nabihestefan@google.com" <nabihestefan@google.com>,
"komlodi@google.com" <komlodi@google.com>,
Titus Rwantare <titusr@google.com>,
Patrick Venture <venture@google.com>
Subject: [PATCH v6 18/22] hw/i3c: Add Mock target
Date: Tue, 24 Feb 2026 05:36:40 +0000 [thread overview]
Message-ID: <20260224053613.589102-19-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260224053613.589102-1-jamin_lin@aspeedtech.com>
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
next prev parent reply other threads:[~2026-02-24 5:39 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-24 5:36 [PATCH v6 00/22] i3c: aspeed: Add I3C support Jamin Lin
2026-02-24 5:36 ` [PATCH v6 01/22] hw/misc/aspeed_i3c: Move to i3c directory Jamin Lin
2026-02-24 5:36 ` [PATCH v6 02/22] hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj naming Jamin Lin
2026-02-24 5:36 ` [PATCH v6 03/22] hw/i3c: Add bus support Jamin Lin
2026-02-24 5:36 ` [PATCH v6 04/22] hw/i3c: Split DesignWare I3C out of Aspeed I3C Jamin Lin
2026-02-24 5:36 ` [PATCH v6 05/22] hw/i3c/dw-i3c: Add more register fields Jamin Lin
2026-02-24 5:36 ` [PATCH v6 06/22] hw/i3c/aspeed_i3c: " Jamin Lin
2026-02-24 5:36 ` [PATCH v6 07/22] hw/i3c/dw-i3c: Add more reset values Jamin Lin
2026-02-24 5:36 ` [PATCH v6 08/22] hw/i3c/aspeed_i3c: Add register RO field masks Jamin Lin
2026-02-24 5:36 ` [PATCH v6 09/22] hw/i3c/dw-i3c: " Jamin Lin
2026-02-24 5:36 ` [PATCH v6 10/22] hw/i3c/dw-i3c: Treat more registers as read-as-zero Jamin Lin
2026-02-24 5:36 ` [PATCH v6 11/22] hw/i3c/dw-i3c: Use 32 bits on MMIO writes Jamin Lin
2026-02-24 5:36 ` [PATCH v6 12/22] hw/i3c/dw-i3c: Add IRQ MMIO behavior Jamin Lin
2026-02-24 5:36 ` [PATCH v6 13/22] hw/i3c/dw-i3c: Add data TX and RX Jamin Lin
2026-02-24 5:36 ` [PATCH v6 14/22] hw/i3c/dw-i3c: Add IBI handling Jamin Lin
2026-02-24 5:36 ` [PATCH v6 15/22] hw/i3c/dw-i3c: Add ctrl MMIO handling Jamin Lin
2026-02-24 5:36 ` [PATCH v6 16/22] hw/i3c/dw-i3c: Add controller resets Jamin Lin
2026-02-24 5:36 ` [PATCH v6 17/22] hw/i3c/aspeed: Add I3C bus get function Jamin Lin
2026-02-24 5:36 ` Jamin Lin [this message]
2026-02-24 5:36 ` [PATCH v6 19/22] hw/arm/aspeed: Build with I3C_DEVICES Jamin Lin
2026-02-24 5:36 ` [PATCH v6 20/22] hw/i3c: Add hotplug support Jamin Lin
2026-02-24 5:36 ` [PATCH v6 21/22] tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test Jamin Lin
2026-02-24 5:36 ` [PATCH v6 22/22] MAINTAINERS: Add I3C maintainers and reviewer Jamin Lin
2026-02-24 16:35 ` Nabih Estefan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260224053613.589102-19-jamin_lin@aspeedtech.com \
--to=jamin_lin@aspeedtech.com \
--cc=andrew@codeconstruct.com.au \
--cc=berrange@redhat.com \
--cc=clg@kaod.org \
--cc=joel@jms.id.au \
--cc=kane_chen@aspeedtech.com \
--cc=komlodi@google.com \
--cc=leetroy@gmail.com \
--cc=marcandre.lureau@redhat.com \
--cc=nabihestefan@google.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=steven_lee@aspeedtech.com \
--cc=titusr@google.com \
--cc=troy_lee@aspeedtech.com \
--cc=venture@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.