public inbox for qemu-arm@nongnu.org
 help / color / mirror / Atom feed
* [PATCH v7 00/22] i3c: aspeed: Add I3C support
@ 2026-02-25  2:11 Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 01/22] hw/misc/aspeed_i3c: Move to i3c directory Jamin Lin
                   ` (23 more replies)
  0 siblings, 24 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:11 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com

v1:
  The initial patch series was based on work by Joe Komlodi <komlodi@google.com>.

  This series adds I3C bus support to QEMU and adds more functionality to the
  Aspeed I3C controller.

  This implementation is a basic implementation that introduces IBIs
  (including hot-join), CCCs, and SDR data transfer. As-is, it doesn't support
  multi-controller buses or HDR transfers.

  First we add the I3C bus and controller model. With that added we extend
  the functionality of the Aspeed I3C controller so it can do transfers
  and handle IBIs.

  Next, we add a mock I3C target. It's intended to be a very simple target
  just to verify that I3C is working on the guest. Internally, we've used it
  on Linux to verify that i3C devices can be probed and can send/receive data
  and IBIs.
  This target is sort of like an EEPROM, and it can also send IBIs upon
  reception of a user-defined magic number.

  Lastly we add  hotplugging support. The hotplugging doesn't do anything too
  complicated, it just adds the device attempting to hotplug to the bus. It
  is the device's responsibility to hot-join and go through the DAA process
  to participate on the bus.

v2:
  Jamin Lin <jamin_lin@aspeedtech.com> has taken ownership of the I3C patch
  series for upstream submission.

  Changes in this version include:

  1. Added I3C functional tests.
  2. Updated patch 4 to refine register field definitions.
  3. Updated patch 7 to correct read-only register field masks.

v3:
  1. Add Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
  2. Fix a typo
  3. Fix ASPEED mail server issue

v4:
  1. Add Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> at the end of each patch.

v5:
  1. Address review feedback by updating the I3C bus and device to use the
   DEFINE_TYPES() macro instead of an explicit type registration function.
  2. Rename variables to `parent_obj` for the parent object and `parent_class`
   for the parent class to comply with QEMU QOM coding style guidelines.

v6:
  1. Add I3C maintainers and reviewer

v7:
  1.  Add I3C maintainers and reviewer

Jamin Lin (3):
  hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj
    naming
  tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test
  MAINTAINERS: Add I3C maintainers and reviewer

Joe Komlodi (19):
  hw/misc/aspeed_i3c: Move to i3c directory
  hw/i3c: Add bus support
  hw/i3c: Split DesignWare I3C out of Aspeed I3C
  hw/i3c/dw-i3c: Add more register fields
  hw/i3c/aspeed_i3c: Add more register fields
  hw/i3c/dw-i3c: Add more reset values
  hw/i3c/aspeed_i3c: Add register RO field masks
  hw/i3c/dw-i3c: Add register RO field masks
  hw/i3c/dw-i3c: Treat more registers as read-as-zero
  hw/i3c/dw-i3c: Use 32 bits on MMIO writes
  hw/i3c/dw-i3c: Add IRQ MMIO behavior
  hw/i3c/dw-i3c: Add data TX and RX
  hw/i3c/dw-i3c: Add IBI handling
  hw/i3c/dw-i3c: Add ctrl MMIO handling
  hw/i3c/dw-i3c: Add controller resets
  hw/i3c/aspeed: Add I3C bus get function
  hw/i3c: Add Mock target
  hw/arm/aspeed: Build with I3C_DEVICES
  hw/i3c: Add hotplug support

 MAINTAINERS                                   |   13 +
 meson.build                                   |    1 +
 hw/i3c/trace.h                                |    2 +
 include/hw/arm/aspeed_soc.h                   |    2 +-
 include/hw/{misc => i3c}/aspeed_i3c.h         |   31 +-
 include/hw/i3c/dw-i3c.h                       |  199 ++
 include/hw/i3c/i3c.h                          |  277 +++
 include/hw/i3c/mock-i3c-target.h              |   52 +
 hw/i3c/aspeed_i3c.c                           |  258 +++
 hw/i3c/core.c                                 |  664 ++++++
 hw/i3c/dw-i3c.c                               | 1862 +++++++++++++++++
 hw/i3c/mock-i3c-target.c                      |  298 +++
 hw/misc/aspeed_i3c.c                          |  383 ----
 hw/Kconfig                                    |    1 +
 hw/arm/Kconfig                                |    3 +
 hw/i3c/Kconfig                                |   15 +
 hw/i3c/meson.build                            |    6 +
 hw/i3c/trace-events                           |   48 +
 hw/meson.build                                |    1 +
 hw/misc/meson.build                           |    1 -
 hw/misc/trace-events                          |    6 -
 .../functional/arm/test_aspeed_ast2600_sdk.py |   12 +
 22 files changed, 3723 insertions(+), 412 deletions(-)
 create mode 100644 hw/i3c/trace.h
 rename include/hw/{misc => i3c}/aspeed_i3c.h (53%)
 create mode 100644 include/hw/i3c/dw-i3c.h
 create mode 100644 include/hw/i3c/i3c.h
 create mode 100644 include/hw/i3c/mock-i3c-target.h
 create mode 100644 hw/i3c/aspeed_i3c.c
 create mode 100644 hw/i3c/core.c
 create mode 100644 hw/i3c/dw-i3c.c
 create mode 100644 hw/i3c/mock-i3c-target.c
 delete mode 100644 hw/misc/aspeed_i3c.c
 create mode 100644 hw/i3c/Kconfig
 create mode 100644 hw/i3c/meson.build
 create mode 100644 hw/i3c/trace-events

-- 
2.43.0


^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH v7 01/22] hw/misc/aspeed_i3c: Move to i3c directory
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 02/22] hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj naming Jamin Lin
                   ` (22 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture, Titus Rwantare,
	Cédric Le Goater

Moves the Aspeed I3C model and traces into hw/i3c and creates I3C build
files.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Reviewed-by: Titus Rwantare <titusr@google.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 meson.build                           | 1 +
 hw/i3c/trace.h                        | 2 ++
 include/hw/arm/aspeed_soc.h           | 2 +-
 include/hw/{misc => i3c}/aspeed_i3c.h | 0
 hw/{misc => i3c}/aspeed_i3c.c         | 2 +-
 hw/Kconfig                            | 1 +
 hw/arm/Kconfig                        | 1 +
 hw/i3c/Kconfig                        | 2 ++
 hw/i3c/meson.build                    | 3 +++
 hw/i3c/trace-events                   | 7 +++++++
 hw/meson.build                        | 1 +
 hw/misc/meson.build                   | 1 -
 hw/misc/trace-events                  | 6 ------
 13 files changed, 20 insertions(+), 9 deletions(-)
 create mode 100644 hw/i3c/trace.h
 rename include/hw/{misc => i3c}/aspeed_i3c.h (100%)
 rename hw/{misc => i3c}/aspeed_i3c.c (99%)
 create mode 100644 hw/i3c/Kconfig
 create mode 100644 hw/i3c/meson.build
 create mode 100644 hw/i3c/trace-events

diff --git a/meson.build b/meson.build
index 414c8ea7e2..41c5e2ebea 100644
--- a/meson.build
+++ b/meson.build
@@ -3610,6 +3610,7 @@ if have_system
     'hw/fsi',
     'hw/hyperv',
     'hw/i2c',
+    'hw/i3c',
     'hw/i386',
     'hw/i386/xen',
     'hw/i386/kvm',
diff --git a/hw/i3c/trace.h b/hw/i3c/trace.h
new file mode 100644
index 0000000000..1e0c4eadf0
--- /dev/null
+++ b/hw/i3c/trace.h
@@ -0,0 +1,2 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include "trace/trace-hw_i3c.h"
diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index b185b04186..d7b3647ca1 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -23,7 +23,7 @@
 #include "hw/timer/aspeed_timer.h"
 #include "hw/rtc/aspeed_rtc.h"
 #include "hw/i2c/aspeed_i2c.h"
-#include "hw/misc/aspeed_i3c.h"
+#include "hw/i3c/aspeed_i3c.h"
 #include "hw/ssi/aspeed_smc.h"
 #include "hw/misc/aspeed_hace.h"
 #include "hw/misc/aspeed_sbc.h"
diff --git a/include/hw/misc/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
similarity index 100%
rename from include/hw/misc/aspeed_i3c.h
rename to include/hw/i3c/aspeed_i3c.h
diff --git a/hw/misc/aspeed_i3c.c b/hw/i3c/aspeed_i3c.c
similarity index 99%
rename from hw/misc/aspeed_i3c.c
rename to hw/i3c/aspeed_i3c.c
index ac6db214ee..fff259ff66 100644
--- a/hw/misc/aspeed_i3c.c
+++ b/hw/i3c/aspeed_i3c.c
@@ -10,7 +10,7 @@
 #include "qemu/osdep.h"
 #include "qemu/log.h"
 #include "qemu/error-report.h"
-#include "hw/misc/aspeed_i3c.h"
+#include "hw/i3c/aspeed_i3c.h"
 #include "hw/core/registerfields.h"
 #include "hw/core/qdev-properties.h"
 #include "qapi/error.h"
diff --git a/hw/Kconfig b/hw/Kconfig
index f8f92b5d03..1b17b7b446 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -13,6 +13,7 @@ source fsi/Kconfig
 source gpio/Kconfig
 source hyperv/Kconfig
 source i2c/Kconfig
+source i3c/Kconfig
 source ide/Kconfig
 source input/Kconfig
 source intc/Kconfig
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index c66c452737..8344b9769f 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -545,6 +545,7 @@ config ASPEED_SOC
     select DS1338
     select FTGMAC100
     select I2C
+    select I3C
     select DPS310
     select PCA9552
     select PCA9554
diff --git a/hw/i3c/Kconfig b/hw/i3c/Kconfig
new file mode 100644
index 0000000000..e07fe445c6
--- /dev/null
+++ b/hw/i3c/Kconfig
@@ -0,0 +1,2 @@
+config I3C
+    bool
diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build
new file mode 100644
index 0000000000..ebf20325cb
--- /dev/null
+++ b/hw/i3c/meson.build
@@ -0,0 +1,3 @@
+i3c_ss = ss.source_set()
+i3c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i3c.c'))
+system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss)
diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
new file mode 100644
index 0000000000..3ead84eb45
--- /dev/null
+++ b/hw/i3c/trace-events
@@ -0,0 +1,7 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# aspeed_i3c.c
+aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read: offset 0x%" PRIx64 " data 0x%" PRIx64
+aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64 " data 0x%" PRIx64
+aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
+aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
diff --git a/hw/meson.build b/hw/meson.build
index 66e46b8090..91552a646d 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -35,6 +35,7 @@ subdir('dma')
 subdir('gpio')
 subdir('hyperv')
 subdir('i2c')
+subdir('i3c')
 subdir('ide')
 subdir('input')
 subdir('intc')
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index d304a98498..96b6705b7d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -135,7 +135,6 @@ system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c'))
 system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c'))
 system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
   'aspeed_hace.c',
-  'aspeed_i3c.c',
   'aspeed_lpc.c',
   'aspeed_ltpi.c',
   'aspeed_pwm.c',
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index d6af2fcf85..b88accc437 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -293,12 +293,6 @@ armsse_mhu_write(uint64_t offset, uint64_t data, unsigned size) "SSE-200 MHU wri
 # aspeed_xdma.c
 aspeed_xdma_write(uint64_t offset, uint64_t data) "XDMA write: offset 0x%" PRIx64 " data 0x%" PRIx64
 
-# aspeed_i3c.c
-aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read: offset 0x%" PRIx64 " data 0x%" PRIx64
-aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64 " data 0x%" PRIx64
-aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
-aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
-
 # aspeed_pwm.c
 aspeed_pwm_read(uint64_t offset, uint64_t data) "read: offset 0x%" PRIx64 " data 0x%" PRIx64
 aspeed_pwm_write(uint64_t offset, uint64_t data) "write: offset 0x%" PRIx64 " data 0x%" PRIx64
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 02/22] hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj naming
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 01/22] hw/misc/aspeed_i3c: Move to i3c directory Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25 10:47   ` Cédric Le Goater
  2026-02-25  2:12 ` [PATCH v7 03/22] hw/i3c: Add bus support Jamin Lin
                   ` (21 subsequent siblings)
  23 siblings, 1 reply; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com

Following review feedback, update the Aspeed I3C device to use the
DEFINE_TYPES() macro instead of an explicit type registration function.

DEFINE_TYPES() is the currently recommended approach in QEMU for
registering multiple TypeInfo entries and avoids boilerplate
type_init() code.

Additionally, rename embedded SysBusDevice fields from "parent" to
"parent_obj" to comply with the QEMU coding style guidelines for QOM
objects.

No functional change.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 include/hw/i3c/aspeed_i3c.h | 16 ++++++----------
 hw/i3c/aspeed_i3c.c         | 35 +++++++++++++++--------------------
 2 files changed, 21 insertions(+), 30 deletions(-)

diff --git a/include/hw/i3c/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
index 7a984e1f01..bd0ffc84ea 100644
--- a/include/hw/i3c/aspeed_i3c.h
+++ b/include/hw/i3c/aspeed_i3c.h
@@ -21,28 +21,24 @@ OBJECT_DECLARE_TYPE(AspeedI3CState, AspeedI3CClass, ASPEED_I3C)
 #define ASPEED_I3C_NR_DEVICES 6
 
 OBJECT_DECLARE_SIMPLE_TYPE(AspeedI3CDevice, ASPEED_I3C_DEVICE)
-typedef struct AspeedI3CDevice {
-    /* <private> */
-    SysBusDevice parent;
+struct AspeedI3CDevice {
+    SysBusDevice parent_obj;
 
-    /* <public> */
     MemoryRegion mr;
     qemu_irq irq;
 
     uint8_t id;
     uint32_t regs[ASPEED_I3C_DEVICE_NR_REGS];
-} AspeedI3CDevice;
+};
 
-typedef struct AspeedI3CState {
-    /* <private> */
-    SysBusDevice parent;
+struct AspeedI3CState {
+    SysBusDevice parent_obj;
 
-    /* <public> */
     MemoryRegion iomem;
     MemoryRegion iomem_container;
     qemu_irq irq;
 
     uint32_t regs[ASPEED_I3C_NR_REGS];
     AspeedI3CDevice devices[ASPEED_I3C_NR_DEVICES];
-} AspeedI3CState;
+};
 #endif /* ASPEED_I3C_H */
diff --git a/hw/i3c/aspeed_i3c.c b/hw/i3c/aspeed_i3c.c
index fff259ff66..e7cdfbfdbd 100644
--- a/hw/i3c/aspeed_i3c.c
+++ b/hw/i3c/aspeed_i3c.c
@@ -337,13 +337,6 @@ static void aspeed_i3c_device_class_init(ObjectClass *klass, const void *data)
     device_class_set_props(dc, aspeed_i3c_device_properties);
 }
 
-static const TypeInfo aspeed_i3c_device_info = {
-    .name = TYPE_ASPEED_I3C_DEVICE,
-    .parent = TYPE_SYS_BUS_DEVICE,
-    .instance_size = sizeof(AspeedI3CDevice),
-    .class_init = aspeed_i3c_device_class_init,
-};
-
 static const VMStateDescription vmstate_aspeed_i3c = {
     .name = TYPE_ASPEED_I3C,
     .version_id = 1,
@@ -366,18 +359,20 @@ static void aspeed_i3c_class_init(ObjectClass *klass, const void *data)
     dc->vmsd = &vmstate_aspeed_i3c;
 }
 
-static const TypeInfo aspeed_i3c_info = {
-    .name = TYPE_ASPEED_I3C,
-    .parent = TYPE_SYS_BUS_DEVICE,
-    .instance_init = aspeed_i3c_instance_init,
-    .instance_size = sizeof(AspeedI3CState),
-    .class_init = aspeed_i3c_class_init,
+static const TypeInfo aspeed_i3c_types[] = {
+    {
+        .name = TYPE_ASPEED_I3C,
+        .parent = TYPE_SYS_BUS_DEVICE,
+        .instance_init = aspeed_i3c_instance_init,
+        .instance_size = sizeof(AspeedI3CState),
+        .class_init = aspeed_i3c_class_init,
+    },
+    {
+        .name = TYPE_ASPEED_I3C_DEVICE,
+        .parent = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(AspeedI3CDevice),
+        .class_init = aspeed_i3c_device_class_init,
+    },
 };
 
-static void aspeed_i3c_register_types(void)
-{
-    type_register_static(&aspeed_i3c_device_info);
-    type_register_static(&aspeed_i3c_info);
-}
-
-type_init(aspeed_i3c_register_types);
+DEFINE_TYPES(aspeed_i3c_types)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 03/22] hw/i3c: Add bus support
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 01/22] hw/misc/aspeed_i3c: Move to i3c directory Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 02/22] hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj naming Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-27  2:23   ` Jithu Joseph
                     ` (2 more replies)
  2026-02-25  2:12 ` [PATCH v7 04/22] hw/i3c: Split DesignWare I3C out of Aspeed I3C Jamin Lin
                   ` (20 subsequent siblings)
  23 siblings, 3 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture, Titus Rwantare

Adds an I3C bus and a target class.
The bus supports:
- I3C data transmission and reception
- CCCs (including ENTDAA)
- IBIs
- legacy I2C transactions

General usage of the bus is similar to I2C. Users are expected to
initialize a bus via i3c_init_bus, and use the bus returned from the
init function to do transactions on the bus.

In order to handle IBIs, the controller provides callbacks to handle
receiving an IBI from a target, receiving (optional) additional IBI
bytes from a target, and handling when a target is done with its IBI.

Similarly, target creation is done via i3c_target_create_simple and
users use the provided I3CTarget to handle transactions.
The target has functions provided that it can use to invoke an IBI and
send additional bytes.

Along with the send, recv, and event callbacks that are expected of an
I3C target, which are similar to I2C, there is a separate callback for
CCC handling.
This is to help encapsulate CCC handling and keep it separate from
target-specific read/write functionality.

To avoid repition for required CCCs among I3C targets, there is some
class-level CCC handling added. The CCC is then passed to the target in
case it needs to handle it in some way.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Reviewed-by: Titus Rwantare <titusr@google.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 include/hw/i3c/i3c.h | 277 ++++++++++++++++++
 hw/i3c/core.c        | 647 +++++++++++++++++++++++++++++++++++++++++++
 hw/i3c/meson.build   |   1 +
 hw/i3c/trace-events  |  16 ++
 4 files changed, 941 insertions(+)
 create mode 100644 include/hw/i3c/i3c.h
 create mode 100644 hw/i3c/core.c

diff --git a/include/hw/i3c/i3c.h b/include/hw/i3c/i3c.h
new file mode 100644
index 0000000000..6ba90793ad
--- /dev/null
+++ b/include/hw/i3c/i3c.h
@@ -0,0 +1,277 @@
+/*
+ * QEMU I3C bus interface.
+ *
+ * Copyright 2025 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef QEMU_INCLUDE_HW_I3C_I3C_H_
+#define QEMU_INCLUDE_HW_I3C_I3C_H_
+
+#include "hw/core/qdev.h"
+#include "qom/object.h"
+#include "hw/i2c/i2c.h"
+
+#define TYPE_I3C_TARGET "i3c-target"
+OBJECT_DECLARE_TYPE(I3CTarget, I3CTargetClass, I3C_TARGET)
+
+typedef enum I3CEvent {
+    I3C_START_RECV,
+    I3C_START_SEND,
+    I3C_STOP,
+    I3C_NACK,
+} I3CEvent;
+
+typedef enum I3CCCC {
+    /* Broadcast CCCs */
+    I3C_CCC_ENEC      = 0x00,
+    I3C_CCC_DISEC     = 0x01,
+    I3C_CCC_ENTAS0    = 0x02,
+    I3C_CCC_ENTAS1    = 0x03,
+    I3C_CCC_ENTAS2    = 0x04,
+    I3C_CCC_ENTAS3    = 0x05,
+    I3C_CCC_RSTDAA    = 0x06,
+    I3C_CCC_ENTDAA    = 0x07,
+    I3C_CCC_DEFTGTS   = 0x08,
+    I3C_CCC_SETMWL    = 0x09,
+    I3C_CCC_SETMRL    = 0x0a,
+    I3C_CCC_ENTTM     = 0x0b,
+    I3C_CCC_SETBUSCON = 0x0c,
+    I3C_CCC_ENDXFER   = 0x12,
+    I3C_CCC_ENTHDR0   = 0x20,
+    I3C_CCC_ENTHDR1   = 0x21,
+    I3C_CCC_ENTHDR2   = 0x22,
+    I3C_CCC_ENTHDR3   = 0x23,
+    I3C_CCC_ENTHDR4   = 0x24,
+    I3C_CCC_ENTHDR5   = 0x25,
+    I3C_CCC_ENTHDR6   = 0x26,
+    I3C_CCC_ENTHDR7   = 0x27,
+    I3C_CCC_SETXTIME  = 0x28,
+    I3C_CCC_SETAASA   = 0x29,
+    I3C_CCC_RSTACT    = 0x2a,
+    I3C_CCC_DEFGRPA   = 0x2b,
+    I3C_CCC_RSTGRPA   = 0x2c,
+    I3C_CCC_MLANE     = 0x2d,
+    /* Direct CCCs */
+    I3C_CCCD_ENEC       = 0x80,
+    I3C_CCCD_DISEC      = 0x81,
+    I3C_CCCD_ENTAS0     = 0x82,
+    I3C_CCCD_ENTAS1     = 0x83,
+    I3C_CCCD_ENTAS2     = 0x84,
+    I3C_CCCD_ENTAS3     = 0x85,
+    I3C_CCCD_SETDASA    = 0x87,
+    I3C_CCCD_SETNEWDA   = 0x88,
+    I3C_CCCD_SETMWL     = 0x89,
+    I3C_CCCD_SETMRL     = 0x8a,
+    I3C_CCCD_GETMWL     = 0x8b,
+    I3C_CCCD_GETMRL     = 0x8c,
+    I3C_CCCD_GETPID     = 0x8d,
+    I3C_CCCD_GETBCR     = 0x8e,
+    I3C_CCCD_GETDCR     = 0x8f,
+    I3C_CCCD_GETSTATUS  = 0x90,
+    I3C_CCCD_GETACCCR   = 0x91,
+    I3C_CCCD_ENDXFER    = 0x92,
+    I3C_CCCD_SETBRGTGT  = 0x93,
+    I3C_CCCD_GETMXDS    = 0x94,
+    I3C_CCCD_GETCAPS    = 0x95,
+    I3C_CCCD_SETROUTE   = 0x96,
+    I3C_CCCD_SETXTIME   = 0x98,
+    I3C_CCCD_GETXTIME   = 0x99,
+    I3C_CCCD_RSTACT     = 0x9a,
+    I3C_CCCD_SETGRPA    = 0x9b,
+    I3C_CCCD_RSTGRPA    = 0x9c,
+    I3C_CCCD_MLANE      = 0x9d,
+} I3CCCC;
+
+#define CCC_IS_DIRECT(_ccc) (_ccc & 0x80)
+
+#define I3C_BROADCAST 0x7e
+#define I3C_HJ_ADDR 0x02
+#define I3C_ENTDAA_SIZE 8
+
+struct I3CTargetClass {
+    DeviceClass parent_class;
+
+    /*
+     * Controller to target. Returns 0 for success, non-zero for NAK or other
+     * error.
+     */
+    int (*send)(I3CTarget *s, const uint8_t *data, uint32_t num_to_send,
+                uint32_t *num_sent);
+    /*
+     * Target to controller. I3C targets are able to terminate reads early, so
+     * this returns the number of bytes read from the target.
+     */
+    uint32_t (*recv)(I3CTarget *s, uint8_t *data, uint32_t num_to_read);
+    /* Notify the target of a bus state change. */
+    int (*event)(I3CTarget *s, enum I3CEvent event);
+    /*
+     * Handle a read CCC transmitted from a controller.
+     * CCCs are I3C commands that I3C targets support.
+     * The target can NACK the CCC if it does not support it.
+     */
+    int (*handle_ccc_read)(I3CTarget *s, uint8_t *data, uint32_t num_to_read,
+                           uint32_t *num_read);
+    /*
+     * Handle a write CCC transmitted from a controller.
+     * CCCs are I3C commands that I3C targets support.
+     * The target can NACK the CCC if it does not support it.
+     */
+    int (*handle_ccc_write)(I3CTarget *s, const uint8_t *data,
+                            uint32_t num_to_send, uint32_t *num_sent);
+
+    /*
+     * Matches and adds the candidate if the address matches the candidate's
+     * address.
+     * Returns true if the address matched, or if this was a broadcast, and
+     * updates the device list. Otherwise returns false.
+     */
+    bool (*target_match)(I3CTarget *candidate, uint8_t address, bool is_read,
+                         bool broadcast, bool in_entdaa);
+};
+
+struct I3CTarget {
+    DeviceState parent_obj;
+
+    uint8_t address;
+    uint8_t static_address;
+    uint8_t dcr;
+    uint8_t bcr;
+    uint64_t pid;
+
+    /* CCC State tracking. */
+    I3CCCC curr_ccc;
+    uint8_t ccc_byte_offset;
+    bool in_ccc;
+    bool in_test_mode;
+};
+
+struct I3CNode {
+    I3CTarget *target;
+    QLIST_ENTRY(I3CNode) next;
+};
+
+typedef struct I3CNode I3CNode;
+
+typedef QLIST_HEAD(I3CNodeList, I3CNode) I3CNodeList;
+
+#define TYPE_I3C_BUS "i3c-bus"
+OBJECT_DECLARE_TYPE(I3CBus, I3CBusClass, I3C_BUS)
+
+struct I3CBus {
+    BusState parent_obj;
+
+    /* Legacy I2C. */
+    I2CBus *i2c_bus;
+
+    I3CNodeList current_devs;
+    bool broadcast;
+    uint8_t ccc;
+    bool in_ccc;
+    bool in_entdaa;
+    uint8_t saved_address;
+};
+
+struct I3CBusClass {
+    BusClass parent_class;
+
+    /* Handle an incoming IBI request from a target */
+    int (*ibi_handle) (I3CBus *bus, uint8_t addr, bool is_recv);
+    /* Receive data from an IBI request */
+    int (*ibi_recv) (I3CBus *bus, uint8_t data);
+    /* Do anything that needs to be done, since the IBI is finished. */
+    int (*ibi_finish) (I3CBus *bus);
+};
+
+I3CBus *i3c_init_bus(DeviceState *parent, const char *name);
+I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent,
+                          const char *name);
+void i3c_set_target_address(I3CTarget *dev, uint8_t address);
+bool i3c_bus_busy(I3CBus *bus);
+
+/*
+ * Start a transfer on an I3C bus.
+ * If is_recv is known at compile-time (i.e. a device will always be sending or
+ * will always be receiving at a certain point), prefer to use i3c_start_recv or
+ * i3c_start_send instead.
+ *
+ * Returns 0 on success, non-zero on an error.
+ */
+int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv);
+
+/*
+ * Start a receive transfer on an I3C bus.
+ *
+ * Returns 0 on success, non-zero on an error
+ */
+int i3c_start_recv(I3CBus *bus, uint8_t address);
+
+/*
+ * Start a send transfer on an I3C bus.
+ *
+ * Returns 0 on success, non-zero on an error
+ */
+int i3c_start_send(I3CBus *bus, uint8_t address);
+
+void i3c_end_transfer(I3CBus *bus);
+void i3c_nack(I3CBus *bus);
+int i3c_send_byte(I3CBus *bus, uint8_t data);
+int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
+             uint32_t *num_sent);
+/*
+ * I3C receives can only NACK on a CCC. The target should NACK a CCC it does not
+ * support.
+ */
+int i3c_recv_byte(I3CBus *bus, uint8_t *data);
+int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read,
+             uint32_t *num_read);
+bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event);
+int i3c_do_entdaa(I3CBus *bus, uint8_t address, uint64_t *pid, uint8_t *bcr,
+                  uint8_t *dcr);
+int i3c_start_device_transfer(I3CTarget *dev, int send_length);
+bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address,
+                             enum I3CEvent event);
+int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv);
+int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data);
+int i3c_target_ibi_finish(I3CTarget *t, uint8_t data);
+
+/*
+ * Legacy I2C functions.
+ *
+ * These are wrapper for I2C functions that take in an I3C bus instead of an I2C
+ * bus. Internally they use the I2C bus (and devices attached to it) that's a
+ * part of the I3C bus
+ */
+void legacy_i2c_nack(I3CBus *bus);
+uint8_t legacy_i2c_recv(I3CBus *bus);
+int legacy_i2c_send(I3CBus *bus, uint8_t data);
+int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv);
+int legacy_i2c_start_recv(I3CBus *bus, uint8_t address);
+int legacy_i2c_start_send(I3CBus *bus, uint8_t address);
+void legacy_i2c_end_transfer(I3CBus *bus);
+I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name,
+                                          uint8_t addr);
+
+/**
+ * Create an I3C Target.
+ *
+ * The target returned from this function still needs to be realized.
+ */
+I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
+                          uint8_t bcr, uint64_t pid);
+
+/**
+ * Create and realize an I3C target.
+ *
+ * Create the target, initialize it, put it on the specified I3C bus, and
+ * realize it.
+ */
+I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name,
+                                    uint8_t addr, uint8_t dcr, uint8_t bcr,
+                                    uint64_t pid);
+
+/* Realize and drop the reference count on an I3C target. */
+bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp);
+
+#endif  /* QEMU_INCLUDE_HW_I3C_I3C_H_ */
diff --git a/hw/i3c/core.c b/hw/i3c/core.c
new file mode 100644
index 0000000000..22e6e226a7
--- /dev/null
+++ b/hw/i3c/core.c
@@ -0,0 +1,647 @@
+/*
+ * QEMU I3C bus interface.
+ *
+ * Copyright 2025 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/i3c/i3c.h"
+#include "hw/core/qdev-properties.h"
+
+/*
+ * In test mode (enabled by ENTTM CCC) we're supposed to send a random PID
+ * during ENTDAA, so we'll just send "QEMU".
+ */
+#define TEST_MODE_PROVISIONED_ID 0x0000554d4551ULL
+
+static const Property i3c_props[] = {
+    DEFINE_PROP_UINT8("static-address", struct I3CTarget, static_address, 0),
+    DEFINE_PROP_UINT8("dcr", struct I3CTarget, dcr, 0),
+    DEFINE_PROP_UINT8("bcr", struct I3CTarget, bcr, 0),
+    DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0),
+};
+
+I3CBus *i3c_init_bus(DeviceState *parent, const char *name)
+{
+    return i3c_init_bus_type(TYPE_I3C_BUS, parent, name);
+}
+
+I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent,
+                          const char *name)
+{
+    I3CBus *bus;
+
+    bus = I3C_BUS(qbus_new(type, parent, name));
+    QLIST_INIT(&bus->current_devs);
+    bus->broadcast = false;
+    bus->in_entdaa = false;
+    bus->in_ccc = false;
+
+    /* I2C init. */
+    g_autofree gchar *i2c_bus_name = g_strdup_printf("%s-legacy-i2c", name);
+    bus->i2c_bus = i2c_init_bus(parent, i2c_bus_name);
+
+    return bus;
+}
+
+bool i3c_bus_busy(I3CBus *bus)
+{
+    return !QLIST_EMPTY(&bus->current_devs);
+}
+
+static bool i3c_target_match(I3CTarget *candidate, uint8_t address,
+                             bool is_recv, bool broadcast, bool in_entdaa)
+{
+    /* Once a target has a dynamic address, it only responds to that. */
+    uint8_t targ_addr = candidate->address ? candidate->address :
+                                             candidate->static_address;
+
+    if (in_entdaa) {
+        if (address != I3C_BROADCAST) {
+            g_autofree char *path =
+                object_get_canonical_path(OBJECT(candidate));
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address 0x%.2x sent during "
+                          "ENTDAA instead of a broadcast address\n",
+                          path, address);
+            return false;
+        }
+
+        /*
+         * Targets should only ACK ENTDAA broadcasts if they have no dynamic
+         * address.
+         */
+        return candidate->address == 0;
+    }
+
+    /* Return if our addresses match, or if it's a broadcast. */
+    return targ_addr == address || broadcast;
+}
+
+bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address,
+                              enum I3CEvent event)
+{
+    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(target);
+    bool matched = tc->target_match(target, address, event == I3C_START_RECV,
+                                    bus->broadcast, bus->in_entdaa);
+
+    if (matched) {
+        I3CNode *node = g_new(struct I3CNode, 1);
+        node->target = target;
+        QLIST_INSERT_HEAD(&bus->current_devs, node, next);
+    }
+    return matched;
+}
+
+bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event)
+{
+    BusChild *child;
+    I3CNode *node, *next;
+
+    /* Clear out any devices from a previous (re-)START. */
+    QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
+        QLIST_REMOVE(node, next);
+        g_free(node);
+    }
+
+    QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) {
+        DeviceState *qdev = child->child;
+        I3CTarget *target = I3C_TARGET(qdev);
+
+        if (i3c_target_match_and_add(bus, target, address, event)) {
+            return true;
+        }
+    }
+
+    /* No one on the bus could respond. */
+    return false;
+}
+
+/* Class-level event handling, since we do some CCCs at the class level. */
+static int i3c_target_event(I3CTarget *t, enum I3CEvent event)
+{
+    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
+    trace_i3c_target_event(t->address, event);
+
+    if (event == I3C_STOP) {
+        t->curr_ccc = 0;
+        t->ccc_byte_offset = 0;
+        t->in_ccc = false;
+    }
+    return tc->event(t, event);
+}
+
+/*
+ * Sends a START or repeated START and the address for an I3C transaction.
+ *
+ * This function returns 0 if a device on the bus was able to respond to the
+ * address, and non-zero otherwise.
+ * A non-zero return represents a NACK.
+ */
+static int i3c_do_start_transfer(I3CBus *bus, uint8_t address,
+                                 enum I3CEvent event)
+{
+    I3CTargetClass *tc;
+    I3CNode *node;
+
+    if (address == I3C_BROADCAST) {
+        bus->broadcast = true;
+        /* If we're not in ENTDAA, a broadcast is the start of a new CCC. */
+        if (!bus->in_entdaa) {
+            bus->in_ccc = false;
+        }
+    } else {
+        bus->broadcast = false;
+    }
+
+    /* No one responded to the address, NACK it. */
+    if (!i3c_scan_bus(bus, address, event)) {
+        return -1;
+    }
+
+    QLIST_FOREACH(node, &bus->current_devs, next) {
+        I3CTarget *t = node->target;
+
+        tc = I3C_TARGET_GET_CLASS(t);
+        if (tc->event) {
+            int rv = i3c_target_event(t, event);
+            if (rv && !bus->broadcast) {
+                return rv;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv)
+{
+    trace_i3c_start_transfer(address, is_recv);
+    return i3c_do_start_transfer(bus, address, is_recv
+                                               ? I3C_START_RECV
+                                               : I3C_START_SEND);
+}
+
+int i3c_start_recv(I3CBus *bus, uint8_t address)
+{
+    trace_i3c_start_transfer(address, true);
+    return i3c_do_start_transfer(bus, address, I3C_START_RECV);
+}
+
+int i3c_start_send(I3CBus *bus, uint8_t address)
+{
+    trace_i3c_start_transfer(address, false);
+    return i3c_do_start_transfer(bus, address, I3C_START_SEND);
+}
+
+void i3c_end_transfer(I3CBus *bus)
+{
+    I3CTargetClass *tc;
+    I3CNode *node, *next;
+
+    trace_i3c_end_transfer();
+
+    /*
+     * If we're in ENTDAA, we need to notify all devices when ENTDAA is done.
+     * This is because everyone initially participates due to the broadcast,
+     * but gradually drops out as they get assigned addresses.
+     * Since the current_devs list only stores who's currently participating,
+     * and not everyone who previously participated, we send the STOP to all
+     * children.
+     */
+    if (bus->in_entdaa) {
+        BusChild *child;
+
+        QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) {
+            DeviceState *qdev = child->child;
+            I3CTarget *t = I3C_TARGET(qdev);
+            tc = I3C_TARGET_GET_CLASS(t);
+            if (tc->event) {
+                i3c_target_event(t, I3C_STOP);
+            }
+        }
+    } else {
+        QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
+            I3CTarget *t = node->target;
+            tc = I3C_TARGET_GET_CLASS(t);
+            if (tc->event) {
+                i3c_target_event(t, I3C_STOP);
+            }
+            QLIST_REMOVE(node, next);
+            g_free(node);
+        }
+    }
+    bus->broadcast = false;
+    bus->in_entdaa = false;
+    bus->in_ccc = false;
+}
+
+/*
+ * Any CCCs that are universal across all I3C devices should be handled here.
+ * Once they're handled, we pass the CCC up to the I3C target to do anything
+ * else it may want with the bytes.
+ */
+static int i3c_target_handle_ccc_write(I3CTarget *t, const uint8_t *data,
+                                       uint32_t num_to_send, uint32_t *num_sent)
+{
+    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
+    *num_sent = 0;
+
+    /* Is this the start of a new CCC? */
+    if (!t->in_ccc) {
+        t->curr_ccc = *data;
+        t->in_ccc = true;
+        *num_sent = 1;
+        trace_i3c_target_handle_ccc(t->address, t->curr_ccc);
+    }
+
+    switch (t->curr_ccc) {
+    case I3C_CCC_ENTDAA:
+        /*
+         * This is the last byte of ENTDAA, the controller is assigning us an
+         * address.
+         */
+        if (t->ccc_byte_offset == 8) {
+            t->address = *data;
+            t->in_ccc = false;
+            t->curr_ccc = 0;
+            t->ccc_byte_offset = 0;
+            *num_sent = 1;
+        }
+        break;
+    case I3C_CCCD_SETDASA:
+        t->address = t->static_address;
+        break;
+    case I3C_CCC_SETAASA:
+        t->address = t->static_address;
+        break;
+    case I3C_CCC_RSTDAA:
+        t->address = 0;
+        break;
+    case I3C_CCCD_SETNEWDA:
+        /* If this isn't the CCC byte, it's our new address. */
+        if (*num_sent == 0) {
+            t->address = *data;
+            *num_sent = 1;
+        }
+        break;
+    case I3C_CCC_ENTTM:
+        /*
+         * If there are still more to look at, the next byte is the test mode
+         * byte.
+         */
+        if (*num_sent != num_to_send) {
+            /* Enter test mode if the byte is non-zero. Otherwise exit. */
+            t->in_test_mode = !!data[*num_sent];
+            ++*num_sent;
+        }
+        break;
+    /* Ignore other CCCs it's better to handle on a device-by-device basis. */
+    default:
+        break;
+    }
+    return tc->handle_ccc_write(t, data, num_to_send, num_sent);
+}
+
+int i3c_send_byte(I3CBus *bus, uint8_t data)
+{
+    /*
+     * Ignored, the caller can determine how many were sent based on if this was
+     * ACKed/NACKed.
+     */
+    uint32_t num_sent;
+    return i3c_send(bus, &data, 1, &num_sent);
+}
+
+int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
+             uint32_t *num_sent)
+{
+    I3CTargetClass *tc;
+    I3CTarget *t;
+    I3CNode *node;
+    int ret = 0;
+
+    /* If this message is a broadcast and no CCC has been found, grab it. */
+    if (bus->broadcast && !bus->in_ccc) {
+        bus->ccc = *data;
+        bus->in_ccc = true;
+        /*
+         * We need to keep track if we're currently in ENTDAA.
+         * On any other CCC, the CCC is over on a RESTART or STOP, but ENTDAA
+         * is only over on a STOP.
+         */
+        if (bus->ccc == I3C_CCC_ENTDAA) {
+            bus->in_entdaa = true;
+        }
+    }
+
+    QLIST_FOREACH(node, &bus->current_devs, next) {
+        t = node->target;
+        tc = I3C_TARGET_GET_CLASS(t);
+        if (bus->in_ccc) {
+            if (!tc->handle_ccc_write) {
+                ret = -1;
+                continue;
+            }
+            ret = i3c_target_handle_ccc_write(t, data, num_to_send, num_sent);
+            /* Targets should only NACK on a direct CCC. */
+            if (ret && !CCC_IS_DIRECT(bus->ccc)) {
+                ret = 0;
+            }
+        } else {
+            if (tc->send) {
+                ret = ret || tc->send(t, data, num_to_send, num_sent);
+            } else {
+                ret = -1;
+            }
+        }
+    }
+
+    trace_i3c_send(*num_sent, num_to_send, ret == 0);
+
+    return ret ? -1 : 0;
+}
+
+static int i3c_target_handle_ccc_read(I3CTarget *t, uint8_t *data,
+                                      uint32_t num_to_read, uint32_t *num_read)
+{
+    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
+    uint8_t read_count = 0;
+    uint64_t pid;
+
+    switch (t->curr_ccc) {
+    case I3C_CCC_ENTDAA:
+        if (t->in_test_mode) {
+            pid = TEST_MODE_PROVISIONED_ID;
+        } else {
+            pid = t->pid;
+        }
+        /* Return the 6-byte PID, followed by BCR then DCR. */
+        while (t->ccc_byte_offset < 6) {
+            if (read_count >= num_to_read) {
+                break;
+            }
+            data[read_count] = (pid >> (t->ccc_byte_offset * 8)) & 0xff;
+            t->ccc_byte_offset++;
+            read_count++;
+        }
+        if (read_count < num_to_read) {
+            data[read_count] = t->bcr;
+            t->ccc_byte_offset++;
+            read_count++;
+        }
+        if (read_count < num_to_read) {
+            data[read_count] = t->dcr;
+            t->ccc_byte_offset++;
+            read_count++;
+        }
+        *num_read = read_count;
+        break;
+    case I3C_CCCD_GETPID:
+        while (t->ccc_byte_offset < 6) {
+            if (read_count >= num_to_read) {
+                break;
+            }
+            data[read_count] = (t->pid >> (t->ccc_byte_offset * 8)) & 0xff;
+            t->ccc_byte_offset++;
+            read_count++;
+        }
+        *num_read = read_count;
+        break;
+    case I3C_CCCD_GETBCR:
+        *data = t->bcr;
+        *num_read = 1;
+        break;
+    case I3C_CCCD_GETDCR:
+        *data = t->dcr;
+        *num_read = 1;
+        break;
+    default:
+        /* Unhandled on the I3CTarget class level. */
+        break;
+    }
+
+    return tc->handle_ccc_read(t, data, num_to_read, num_read);
+}
+
+int i3c_recv_byte(I3CBus *bus, uint8_t *data)
+{
+     /*
+      * Ignored, the caller can determine how many bytes were read based on if
+      * this is ACKed/NACKed.
+      */
+    uint32_t num_read;
+    return i3c_recv(bus, data, 1, &num_read);
+}
+
+int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read,
+             uint32_t *num_read)
+{
+    int ret = 0;
+    I3CTargetClass *tc;
+    I3CTarget *t;
+
+    *data = 0xff;
+    if (!QLIST_EMPTY(&bus->current_devs)) {
+        tc = I3C_TARGET_GET_CLASS(QLIST_FIRST(&bus->current_devs)->target);
+        t = QLIST_FIRST(&bus->current_devs)->target;
+        if (bus->in_ccc) {
+            if (!tc->handle_ccc_read) {
+                return -1;
+            }
+            ret = i3c_target_handle_ccc_read(t, data, num_to_read, num_read);
+        } else {
+            if (tc->recv) {
+                /*
+                 * Targets cannot NACK on a direct transfer, so the data
+                 * is returned directly.
+                 */
+                *num_read = tc->recv(t, data, num_to_read);
+            }
+        }
+    }
+
+    trace_i3c_recv(*num_read, num_to_read, ret == 0);
+
+    return ret;
+}
+
+void i3c_nack(I3CBus *bus)
+{
+    I3CTargetClass *tc;
+    I3CNode *node;
+
+    if (QLIST_EMPTY(&bus->current_devs)) {
+        return;
+    }
+
+    QLIST_FOREACH(node, &bus->current_devs, next) {
+        tc = I3C_TARGET_GET_CLASS(node->target);
+        if (tc->event) {
+            i3c_target_event(node->target, I3C_NACK);
+        }
+    }
+}
+
+int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv)
+{
+    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
+    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
+    trace_i3c_target_send_ibi(addr, is_recv);
+    return bc->ibi_handle(bus, addr, is_recv);
+}
+
+int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data)
+{
+    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
+    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
+    trace_i3c_target_send_ibi_bytes(data);
+    return bc->ibi_recv(bus, data);
+}
+
+int i3c_target_ibi_finish(I3CTarget *t, uint8_t data)
+{
+    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
+    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
+    trace_i3c_target_ibi_finish();
+    return bc->ibi_finish(bus);
+}
+
+static bool i3c_addr_is_rsvd(uint8_t addr)
+{
+    const bool is_rsvd[255] = {
+        [0x00] = true,
+        [0x01] = true,
+        [0x02] = true,
+        [0x3e] = true,
+        [0x5e] = true,
+        [0x6e] = true,
+        [0x76] = true,
+        [0x7a] = true,
+        [0x7c] = true,
+        [0x7e] = true,
+        [0x7f] = true,
+    };
+
+    return is_rsvd[addr];
+}
+
+I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
+                          uint8_t bcr, uint64_t pid)
+{
+    DeviceState *dev;
+
+    dev = qdev_new(name);
+    qdev_prop_set_uint8(dev, "static-address", addr);
+    qdev_prop_set_uint8(dev, "dcr", dcr);
+    qdev_prop_set_uint8(dev, "bcr", bcr);
+    qdev_prop_set_uint64(dev, "pid", pid);
+
+    if (i3c_addr_is_rsvd(addr)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(dev));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created with reserved "
+                      "address 0x%.2x\n", path, addr);
+    }
+    return I3C_TARGET(dev);
+}
+
+bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp)
+{
+    return qdev_realize_and_unref(&dev->parent_obj, &bus->parent_obj, errp);
+}
+
+I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, uint8_t addr,
+                                    uint8_t dcr, uint8_t bcr, uint64_t pid)
+{
+    I3CTarget *dev = i3c_target_new(name, addr, dcr, bcr, pid);
+    dev->address = 0;
+    i3c_target_realize_and_unref(dev, bus, &error_abort);
+
+    return dev;
+}
+
+/* Legacy I2C functions. */
+void legacy_i2c_nack(I3CBus *bus)
+{
+    trace_legacy_i2c_nack();
+    i2c_nack(bus->i2c_bus);
+}
+
+uint8_t legacy_i2c_recv(I3CBus *bus)
+{
+    uint8_t byte = i2c_recv(bus->i2c_bus);
+    trace_legacy_i2c_recv(byte);
+    return byte;
+}
+
+int legacy_i2c_send(I3CBus *bus, uint8_t data)
+{
+    trace_legacy_i2c_send(data);
+    return i2c_send(bus->i2c_bus, data);
+}
+
+int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv)
+{
+    trace_legacy_i2c_start_transfer(address, is_recv);
+    return i2c_start_transfer(bus->i2c_bus, address, is_recv);
+}
+
+int legacy_i2c_start_recv(I3CBus *bus, uint8_t address)
+{
+    trace_legacy_i2c_start_transfer(address, true);
+    return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/true);
+}
+
+int legacy_i2c_start_send(I3CBus *bus, uint8_t address)
+{
+    trace_legacy_i2c_start_transfer(address, false);
+    return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/false);
+}
+
+void legacy_i2c_end_transfer(I3CBus *bus)
+{
+    trace_legacy_i2c_end_transfer();
+    i2c_end_transfer(bus->i2c_bus);
+}
+
+I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name,
+                                          uint8_t addr)
+{
+    I2CSlave *dev = i2c_slave_new(name, addr);
+
+    i2c_slave_realize_and_unref(dev, bus->i2c_bus, &error_abort);
+    return dev;
+}
+
+static void i3c_target_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *k = DEVICE_CLASS(klass);
+    I3CTargetClass *sc = I3C_TARGET_CLASS(klass);
+    set_bit(DEVICE_CATEGORY_MISC, k->categories);
+    k->bus_type = TYPE_I3C_BUS;
+    device_class_set_props(k, i3c_props);
+    sc->target_match = i3c_target_match;
+}
+
+static const TypeInfo i3c_types[] = {
+    {
+        .name = TYPE_I3C_BUS,
+        .parent = TYPE_BUS,
+        .instance_size = sizeof(I3CBus),
+        .class_size = sizeof(I3CBusClass),
+    },
+    {
+        .name = TYPE_I3C_TARGET,
+        .parent = TYPE_DEVICE,
+        .instance_size = sizeof(I3CTarget),
+        .abstract = true,
+        .class_size = sizeof(I3CTargetClass),
+        .class_init = i3c_target_class_init,
+    },
+};
+
+DEFINE_TYPES(i3c_types)
diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build
index ebf20325cb..fb127613fe 100644
--- a/hw/i3c/meson.build
+++ b/hw/i3c/meson.build
@@ -1,3 +1,4 @@
 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'))
 system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss)
diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
index 3ead84eb45..cdf7cb07f6 100644
--- a/hw/i3c/trace-events
+++ b/hw/i3c/trace-events
@@ -5,3 +5,19 @@ aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read: offset 0x%" PRIx64 "
 aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64 " data 0x%" PRIx64
 aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
 aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
+
+# core.c
+i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " event 0x%" PRIx8
+i3c_target_handle_ccc(uint8_t address, uint8_t ccc) "I3C target 0x%" PRIx8 " handling CCC 0x%" PRIx8
+i3c_target_send_ibi(uint8_t address, bool is_recv) "I3C target IBI address 0x%" PRIx8 " RnW=%d"
+i3c_target_send_ibi_bytes(uint8_t byte) "I3C target IBI byte 0x%" PRIx8
+i3c_target_ibi_finish(void) "I3C target IBI finish"
+i3c_start_transfer(uint8_t address, bool is_recv) "I3C START with address 0x%" PRIx8 " is_recv=%d"
+i3c_end_transfer(void) "I3C transfer done"
+i3c_send(uint32_t num_sent, uint32_t num_to_send, bool ack) "I3C send %" PRId32 "/%" PRId32 " bytes, ack=%d"
+i3c_recv(uint32_t num_read, uint32_t num_to_read, bool ack) "I3C recv %" PRId32 "/%" PRId32 " bytes, ack=%d"
+legacy_i2c_nack(void) "Legacy I2C NACK"
+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"
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 04/22] hw/i3c: Split DesignWare I3C out of Aspeed I3C
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (2 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 03/22] hw/i3c: Add bus support Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 05/22] hw/i3c/dw-i3c: Add more register fields Jamin Lin
                   ` (19 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com

The Aspeed I3C IP block is technically an Aspeed IP block that manages
6 DW I3C controllers.

To help reflect this better and to make it easier for other SoCs to use
the DW I3C model, we'll split out the DW portion from the Aspeed
portion.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 include/hw/i3c/aspeed_i3c.h |  17 +--
 include/hw/i3c/dw-i3c.h     |  33 ++++++
 hw/i3c/aspeed_i3c.c         | 181 +-------------------------------
 hw/i3c/dw-i3c.c             | 202 ++++++++++++++++++++++++++++++++++++
 hw/arm/Kconfig              |   1 +
 hw/i3c/Kconfig              |   3 +
 hw/i3c/meson.build          |   1 +
 hw/i3c/trace-events         |   6 +-
 8 files changed, 250 insertions(+), 194 deletions(-)
 create mode 100644 include/hw/i3c/dw-i3c.h
 create mode 100644 hw/i3c/dw-i3c.c

diff --git a/include/hw/i3c/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
index bd0ffc84ea..ade5c42d39 100644
--- a/include/hw/i3c/aspeed_i3c.h
+++ b/include/hw/i3c/aspeed_i3c.h
@@ -10,27 +10,15 @@
 #ifndef ASPEED_I3C_H
 #define ASPEED_I3C_H
 
+#include "hw/i3c/dw-i3c.h"
 #include "hw/core/sysbus.h"
 
 #define TYPE_ASPEED_I3C "aspeed.i3c"
-#define TYPE_ASPEED_I3C_DEVICE "aspeed.i3c.device"
 OBJECT_DECLARE_TYPE(AspeedI3CState, AspeedI3CClass, ASPEED_I3C)
 
 #define ASPEED_I3C_NR_REGS (0x70 >> 2)
-#define ASPEED_I3C_DEVICE_NR_REGS (0x300 >> 2)
 #define ASPEED_I3C_NR_DEVICES 6
 
-OBJECT_DECLARE_SIMPLE_TYPE(AspeedI3CDevice, ASPEED_I3C_DEVICE)
-struct AspeedI3CDevice {
-    SysBusDevice parent_obj;
-
-    MemoryRegion mr;
-    qemu_irq irq;
-
-    uint8_t id;
-    uint32_t regs[ASPEED_I3C_DEVICE_NR_REGS];
-};
-
 struct AspeedI3CState {
     SysBusDevice parent_obj;
 
@@ -39,6 +27,7 @@ struct AspeedI3CState {
     qemu_irq irq;
 
     uint32_t regs[ASPEED_I3C_NR_REGS];
-    AspeedI3CDevice devices[ASPEED_I3C_NR_DEVICES];
+    DWI3C devices[ASPEED_I3C_NR_DEVICES];
+    uint8_t id;
 };
 #endif /* ASPEED_I3C_H */
diff --git a/include/hw/i3c/dw-i3c.h b/include/hw/i3c/dw-i3c.h
new file mode 100644
index 0000000000..7143e8ca7a
--- /dev/null
+++ b/include/hw/i3c/dw-i3c.h
@@ -0,0 +1,33 @@
+/*
+ * DesignWare I3C Controller
+ *
+ * Copyright (C) 2021 ASPEED Technology Inc.
+ * Copyright (C) 2025 Google, LLC.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef DW_I3C_H
+#define DW_I3C_H
+
+#include "hw/core/sysbus.h"
+
+#define TYPE_DW_I3C "dw.i3c"
+OBJECT_DECLARE_SIMPLE_TYPE(DWI3C, DW_I3C)
+
+#define DW_I3C_NR_REGS (0x300 >> 2)
+
+struct DWI3C {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mr;
+    qemu_irq irq;
+
+    uint8_t id;
+    uint32_t regs[DW_I3C_NR_REGS];
+};
+
+/* Extern for other controllers that use DesignWare I3C. */
+extern const VMStateDescription vmstate_dw_i3c;
+
+#endif /* DW_I3C_H */
diff --git a/hw/i3c/aspeed_i3c.c b/hw/i3c/aspeed_i3c.c
index e7cdfbfdbd..647074d181 100644
--- a/hw/i3c/aspeed_i3c.c
+++ b/hw/i3c/aspeed_i3c.c
@@ -2,6 +2,7 @@
  * ASPEED I3C Controller
  *
  * Copyright (C) 2021 ASPEED Technology Inc.
+ * Copyright (C) 2025 Google, LLC.
  *
  * This code is licensed under the GPL version 2 or later.  See
  * the COPYING file in the top-level directory.
@@ -43,162 +44,6 @@ REG32(I3C6_REG1, 0x64)
     FIELD(I3C6_REG1, I2C_MODE,  0,  1)
     FIELD(I3C6_REG1, SA_EN,     15, 1)
 
-/* I3C Device Registers */
-REG32(DEVICE_CTRL,                  0x00)
-REG32(DEVICE_ADDR,                  0x04)
-REG32(HW_CAPABILITY,                0x08)
-REG32(COMMAND_QUEUE_PORT,           0x0c)
-REG32(RESPONSE_QUEUE_PORT,          0x10)
-REG32(RX_TX_DATA_PORT,              0x14)
-REG32(IBI_QUEUE_STATUS,             0x18)
-REG32(IBI_QUEUE_DATA,               0x18)
-REG32(QUEUE_THLD_CTRL,              0x1c)
-REG32(DATA_BUFFER_THLD_CTRL,        0x20)
-REG32(IBI_QUEUE_CTRL,               0x24)
-REG32(IBI_MR_REQ_REJECT,            0x2c)
-REG32(IBI_SIR_REQ_REJECT,           0x30)
-REG32(RESET_CTRL,                   0x34)
-REG32(SLV_EVENT_CTRL,               0x38)
-REG32(INTR_STATUS,                  0x3c)
-REG32(INTR_STATUS_EN,               0x40)
-REG32(INTR_SIGNAL_EN,               0x44)
-REG32(INTR_FORCE,                   0x48)
-REG32(QUEUE_STATUS_LEVEL,           0x4c)
-REG32(DATA_BUFFER_STATUS_LEVEL,     0x50)
-REG32(PRESENT_STATE,                0x54)
-REG32(CCC_DEVICE_STATUS,            0x58)
-REG32(DEVICE_ADDR_TABLE_POINTER,    0x5c)
-    FIELD(DEVICE_ADDR_TABLE_POINTER, DEPTH, 16, 16)
-    FIELD(DEVICE_ADDR_TABLE_POINTER, ADDR,  0,  16)
-REG32(DEV_CHAR_TABLE_POINTER,       0x60)
-REG32(VENDOR_SPECIFIC_REG_POINTER,  0x6c)
-REG32(SLV_MIPI_PID_VALUE,           0x70)
-REG32(SLV_PID_VALUE,                0x74)
-REG32(SLV_CHAR_CTRL,                0x78)
-REG32(SLV_MAX_LEN,                  0x7c)
-REG32(MAX_READ_TURNAROUND,          0x80)
-REG32(MAX_DATA_SPEED,               0x84)
-REG32(SLV_DEBUG_STATUS,             0x88)
-REG32(SLV_INTR_REQ,                 0x8c)
-REG32(DEVICE_CTRL_EXTENDED,         0xb0)
-REG32(SCL_I3C_OD_TIMING,            0xb4)
-REG32(SCL_I3C_PP_TIMING,            0xb8)
-REG32(SCL_I2C_FM_TIMING,            0xbc)
-REG32(SCL_I2C_FMP_TIMING,           0xc0)
-REG32(SCL_EXT_LCNT_TIMING,          0xc8)
-REG32(SCL_EXT_TERMN_LCNT_TIMING,    0xcc)
-REG32(BUS_FREE_TIMING,              0xd4)
-REG32(BUS_IDLE_TIMING,              0xd8)
-REG32(I3C_VER_ID,                   0xe0)
-REG32(I3C_VER_TYPE,                 0xe4)
-REG32(EXTENDED_CAPABILITY,          0xe8)
-REG32(SLAVE_CONFIG,                 0xec)
-
-static const uint32_t ast2600_i3c_device_resets[ASPEED_I3C_DEVICE_NR_REGS] = {
-    [R_HW_CAPABILITY]               = 0x000e00bf,
-    [R_QUEUE_THLD_CTRL]             = 0x01000101,
-    [R_I3C_VER_ID]                  = 0x3130302a,
-    [R_I3C_VER_TYPE]                = 0x6c633033,
-    [R_DEVICE_ADDR_TABLE_POINTER]   = 0x00080280,
-    [R_DEV_CHAR_TABLE_POINTER]      = 0x00020200,
-    [A_VENDOR_SPECIFIC_REG_POINTER] = 0x000000b0,
-    [R_SLV_MAX_LEN]                 = 0x00ff00ff,
-};
-
-static uint64_t aspeed_i3c_device_read(void *opaque, hwaddr offset,
-                                       unsigned size)
-{
-    AspeedI3CDevice *s = ASPEED_I3C_DEVICE(opaque);
-    uint32_t addr = offset >> 2;
-    uint64_t value;
-
-    switch (addr) {
-    case R_COMMAND_QUEUE_PORT:
-        value = 0;
-        break;
-    default:
-        value = s->regs[addr];
-        break;
-    }
-
-    trace_aspeed_i3c_device_read(s->id, offset, value);
-
-    return value;
-}
-
-static void aspeed_i3c_device_write(void *opaque, hwaddr offset,
-                                    uint64_t value, unsigned size)
-{
-    AspeedI3CDevice *s = ASPEED_I3C_DEVICE(opaque);
-    uint32_t addr = offset >> 2;
-
-    trace_aspeed_i3c_device_write(s->id, offset, value);
-
-    switch (addr) {
-    case R_HW_CAPABILITY:
-    case R_RESPONSE_QUEUE_PORT:
-    case R_IBI_QUEUE_DATA:
-    case R_QUEUE_STATUS_LEVEL:
-    case R_PRESENT_STATE:
-    case R_CCC_DEVICE_STATUS:
-    case R_DEVICE_ADDR_TABLE_POINTER:
-    case R_VENDOR_SPECIFIC_REG_POINTER:
-    case R_SLV_CHAR_CTRL:
-    case R_SLV_MAX_LEN:
-    case R_MAX_READ_TURNAROUND:
-    case R_I3C_VER_ID:
-    case R_I3C_VER_TYPE:
-    case R_EXTENDED_CAPABILITY:
-        qemu_log_mask(LOG_GUEST_ERROR,
-                      "%s: write to readonly register[0x%02" HWADDR_PRIx
-                      "] = 0x%08" PRIx64 "\n",
-                      __func__, offset, value);
-        break;
-    case R_RX_TX_DATA_PORT:
-        break;
-    case R_RESET_CTRL:
-        break;
-    default:
-        s->regs[addr] = value;
-        break;
-    }
-}
-
-static const VMStateDescription aspeed_i3c_device_vmstate = {
-    .name = TYPE_ASPEED_I3C,
-    .version_id = 1,
-    .minimum_version_id = 1,
-    .fields = (const VMStateField[]){
-        VMSTATE_UINT32_ARRAY(regs, AspeedI3CDevice, ASPEED_I3C_DEVICE_NR_REGS),
-        VMSTATE_END_OF_LIST(),
-    }
-};
-
-static const MemoryRegionOps aspeed_i3c_device_ops = {
-    .read = aspeed_i3c_device_read,
-    .write = aspeed_i3c_device_write,
-    .endianness = DEVICE_LITTLE_ENDIAN,
-};
-
-static void aspeed_i3c_device_reset(DeviceState *dev)
-{
-    AspeedI3CDevice *s = ASPEED_I3C_DEVICE(dev);
-
-    memcpy(s->regs, ast2600_i3c_device_resets, sizeof(s->regs));
-}
-
-static void aspeed_i3c_device_realize(DeviceState *dev, Error **errp)
-{
-    AspeedI3CDevice *s = ASPEED_I3C_DEVICE(dev);
-    g_autofree char *name = g_strdup_printf(TYPE_ASPEED_I3C_DEVICE ".%d",
-                                            s->id);
-
-    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
-
-    memory_region_init_io(&s->mr, OBJECT(s), &aspeed_i3c_device_ops,
-                          s, name, ASPEED_I3C_DEVICE_NR_REGS << 2);
-}
-
 static uint64_t aspeed_i3c_read(void *opaque, hwaddr addr, unsigned int size)
 {
     AspeedI3CState *s = ASPEED_I3C(opaque);
@@ -275,7 +120,7 @@ static void aspeed_i3c_instance_init(Object *obj)
 
     for (i = 0; i < ASPEED_I3C_NR_DEVICES; ++i) {
         object_initialize_child(obj, "device[*]", &s->devices[i],
-                TYPE_ASPEED_I3C_DEVICE);
+                TYPE_DW_I3C);
     }
 }
 
@@ -323,20 +168,6 @@ static void aspeed_i3c_realize(DeviceState *dev, Error **errp)
 
 }
 
-static const Property aspeed_i3c_device_properties[] = {
-    DEFINE_PROP_UINT8("device-id", AspeedI3CDevice, id, 0),
-};
-
-static void aspeed_i3c_device_class_init(ObjectClass *klass, const void *data)
-{
-    DeviceClass *dc = DEVICE_CLASS(klass);
-
-    dc->desc = "Aspeed I3C Device";
-    dc->realize = aspeed_i3c_device_realize;
-    device_class_set_legacy_reset(dc, aspeed_i3c_device_reset);
-    device_class_set_props(dc, aspeed_i3c_device_properties);
-}
-
 static const VMStateDescription vmstate_aspeed_i3c = {
     .name = TYPE_ASPEED_I3C,
     .version_id = 1,
@@ -344,7 +175,7 @@ static const VMStateDescription vmstate_aspeed_i3c = {
     .fields = (const VMStateField[]) {
         VMSTATE_UINT32_ARRAY(regs, AspeedI3CState, ASPEED_I3C_NR_REGS),
         VMSTATE_STRUCT_ARRAY(devices, AspeedI3CState, ASPEED_I3C_NR_DEVICES, 1,
-                             aspeed_i3c_device_vmstate, AspeedI3CDevice),
+                             vmstate_dw_i3c, DWI3C),
         VMSTATE_END_OF_LIST(),
     }
 };
@@ -367,12 +198,6 @@ static const TypeInfo aspeed_i3c_types[] = {
         .instance_size = sizeof(AspeedI3CState),
         .class_init = aspeed_i3c_class_init,
     },
-    {
-        .name = TYPE_ASPEED_I3C_DEVICE,
-        .parent = TYPE_SYS_BUS_DEVICE,
-        .instance_size = sizeof(AspeedI3CDevice),
-        .class_init = aspeed_i3c_device_class_init,
-    },
 };
 
 DEFINE_TYPES(aspeed_i3c_types)
diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
new file mode 100644
index 0000000000..6cadc59191
--- /dev/null
+++ b/hw/i3c/dw-i3c.c
@@ -0,0 +1,202 @@
+/*
+ * DesignWare I3C Controller
+ *
+ * Copyright (C) 2021 ASPEED Technology Inc.
+ * Copyright (C) 2025 Google, LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "hw/i3c/i3c.h"
+#include "hw/i3c/dw-i3c.h"
+#include "hw/core/registerfields.h"
+#include "hw/core/qdev-properties.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+REG32(DEVICE_CTRL,                  0x00)
+REG32(DEVICE_ADDR,                  0x04)
+REG32(HW_CAPABILITY,                0x08)
+REG32(COMMAND_QUEUE_PORT,           0x0c)
+REG32(RESPONSE_QUEUE_PORT,          0x10)
+REG32(RX_TX_DATA_PORT,              0x14)
+REG32(IBI_QUEUE_STATUS,             0x18)
+REG32(IBI_QUEUE_DATA,               0x18)
+REG32(QUEUE_THLD_CTRL,              0x1c)
+REG32(DATA_BUFFER_THLD_CTRL,        0x20)
+REG32(IBI_QUEUE_CTRL,               0x24)
+REG32(IBI_MR_REQ_REJECT,            0x2c)
+REG32(IBI_SIR_REQ_REJECT,           0x30)
+REG32(RESET_CTRL,                   0x34)
+REG32(SLV_EVENT_CTRL,               0x38)
+REG32(INTR_STATUS,                  0x3c)
+REG32(INTR_STATUS_EN,               0x40)
+REG32(INTR_SIGNAL_EN,               0x44)
+REG32(INTR_FORCE,                   0x48)
+REG32(QUEUE_STATUS_LEVEL,           0x4c)
+REG32(DATA_BUFFER_STATUS_LEVEL,     0x50)
+REG32(PRESENT_STATE,                0x54)
+REG32(CCC_DEVICE_STATUS,            0x58)
+REG32(DEVICE_ADDR_TABLE_POINTER,    0x5c)
+    FIELD(DEVICE_ADDR_TABLE_POINTER, DEPTH, 16, 16)
+    FIELD(DEVICE_ADDR_TABLE_POINTER, ADDR,  0,  16)
+REG32(DEV_CHAR_TABLE_POINTER,       0x60)
+REG32(VENDOR_SPECIFIC_REG_POINTER,  0x6c)
+REG32(SLV_MIPI_PID_VALUE,           0x70)
+REG32(SLV_PID_VALUE,                0x74)
+REG32(SLV_CHAR_CTRL,                0x78)
+REG32(SLV_MAX_LEN,                  0x7c)
+REG32(MAX_READ_TURNAROUND,          0x80)
+REG32(MAX_DATA_SPEED,               0x84)
+REG32(SLV_DEBUG_STATUS,             0x88)
+REG32(SLV_INTR_REQ,                 0x8c)
+REG32(DEVICE_CTRL_EXTENDED,         0xb0)
+REG32(SCL_I3C_OD_TIMING,            0xb4)
+REG32(SCL_I3C_PP_TIMING,            0xb8)
+REG32(SCL_I2C_FM_TIMING,            0xbc)
+REG32(SCL_I2C_FMP_TIMING,           0xc0)
+REG32(SCL_EXT_LCNT_TIMING,          0xc8)
+REG32(SCL_EXT_TERMN_LCNT_TIMING,    0xcc)
+REG32(BUS_FREE_TIMING,              0xd4)
+REG32(BUS_IDLE_TIMING,              0xd8)
+REG32(I3C_VER_ID,                   0xe0)
+REG32(I3C_VER_TYPE,                 0xe4)
+REG32(EXTENDED_CAPABILITY,          0xe8)
+REG32(SLAVE_CONFIG,                 0xec)
+
+static const uint32_t dw_i3c_resets[DW_I3C_NR_REGS] = {
+    [R_HW_CAPABILITY]               = 0x000e00bf,
+    [R_QUEUE_THLD_CTRL]             = 0x01000101,
+    [R_I3C_VER_ID]                  = 0x3130302a,
+    [R_I3C_VER_TYPE]                = 0x6c633033,
+    [R_DEVICE_ADDR_TABLE_POINTER]   = 0x00080280,
+    [R_DEV_CHAR_TABLE_POINTER]      = 0x00020200,
+    [A_VENDOR_SPECIFIC_REG_POINTER] = 0x000000b0,
+    [R_SLV_MAX_LEN]                 = 0x00ff00ff,
+};
+
+static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
+{
+    DWI3C *s = DW_I3C(opaque);
+    uint32_t addr = offset >> 2;
+    uint64_t value;
+
+    switch (addr) {
+    case R_COMMAND_QUEUE_PORT:
+        value = 0;
+        break;
+    default:
+        value = s->regs[addr];
+        break;
+    }
+
+    trace_dw_i3c_read(s->id, offset, value);
+
+    return value;
+}
+
+static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
+                         unsigned size)
+{
+    DWI3C *s = DW_I3C(opaque);
+    uint32_t addr = offset >> 2;
+
+    trace_dw_i3c_write(s->id, offset, value);
+
+    switch (addr) {
+    case R_HW_CAPABILITY:
+    case R_RESPONSE_QUEUE_PORT:
+    case R_IBI_QUEUE_DATA:
+    case R_QUEUE_STATUS_LEVEL:
+    case R_PRESENT_STATE:
+    case R_CCC_DEVICE_STATUS:
+    case R_DEVICE_ADDR_TABLE_POINTER:
+    case R_VENDOR_SPECIFIC_REG_POINTER:
+    case R_SLV_CHAR_CTRL:
+    case R_SLV_MAX_LEN:
+    case R_MAX_READ_TURNAROUND:
+    case R_I3C_VER_ID:
+    case R_I3C_VER_TYPE:
+    case R_EXTENDED_CAPABILITY:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: write to readonly register[0x%02" HWADDR_PRIx
+                      "] = 0x%08" PRIx64 "\n",
+                      __func__, offset, value);
+        break;
+    case R_RX_TX_DATA_PORT:
+        break;
+    case R_RESET_CTRL:
+        break;
+    default:
+        s->regs[addr] = value;
+        break;
+    }
+}
+
+const VMStateDescription vmstate_dw_i3c = {
+    .name = TYPE_DW_I3C,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]){
+        VMSTATE_UINT32_ARRAY(regs, DWI3C, DW_I3C_NR_REGS),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static const MemoryRegionOps dw_i3c_ops = {
+    .read = dw_i3c_read,
+    .write = dw_i3c_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void dw_i3c_reset_enter(Object *obj, ResetType type)
+{
+    DWI3C *s = DW_I3C(obj);
+
+    memcpy(s->regs, dw_i3c_resets, sizeof(s->regs));
+}
+
+static void dw_i3c_realize(DeviceState *dev, Error **errp)
+{
+    DWI3C *s = DW_I3C(dev);
+    g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->id);
+
+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+
+    memory_region_init_io(&s->mr, OBJECT(s), &dw_i3c_ops, s, name,
+                          DW_I3C_NR_REGS << 2);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr);
+}
+
+static const Property dw_i3c_properties[] = {
+    DEFINE_PROP_UINT8("device-id", DWI3C, id, 0),
+};
+
+static void dw_i3c_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.enter = dw_i3c_reset_enter;
+
+    dc->desc = "DesignWare I3C Controller";
+    dc->realize = dw_i3c_realize;
+    dc->vmsd = &vmstate_dw_i3c;
+    device_class_set_props(dc, dw_i3c_properties);
+}
+
+static const TypeInfo dw_i3c_types[] = {
+    {
+        .name = TYPE_DW_I3C,
+        .parent = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(DWI3C),
+        .class_init = dw_i3c_class_init,
+    },
+};
+
+DEFINE_TYPES(dw_i3c_types)
+
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 8344b9769f..d545ecd712 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -546,6 +546,7 @@ config ASPEED_SOC
     select FTGMAC100
     select I2C
     select I3C
+    select DW_I3C
     select DPS310
     select PCA9552
     select PCA9554
diff --git a/hw/i3c/Kconfig b/hw/i3c/Kconfig
index e07fe445c6..ecec77d6fc 100644
--- a/hw/i3c/Kconfig
+++ b/hw/i3c/Kconfig
@@ -1,2 +1,5 @@
 config I3C
     bool
+
+config DW_I3C
+    bool
diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build
index fb127613fe..83d75e7d5c 100644
--- a/hw/i3c/meson.build
+++ b/hw/i3c/meson.build
@@ -1,4 +1,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'))
 system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss)
diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
index cdf7cb07f6..2d944387db 100644
--- a/hw/i3c/trace-events
+++ b/hw/i3c/trace-events
@@ -3,8 +3,10 @@
 # aspeed_i3c.c
 aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read: offset 0x%" PRIx64 " data 0x%" PRIx64
 aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64 " data 0x%" PRIx64
-aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
-aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
+
+# dw-i3c,c
+dw_i3c_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
+dw_i3c_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
 
 # core.c
 i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " event 0x%" PRIx8
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 05/22] hw/i3c/dw-i3c: Add more register fields
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (3 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 04/22] hw/i3c: Split DesignWare I3C out of Aspeed I3C Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 06/22] hw/i3c/aspeed_i3c: " Jamin Lin
                   ` (18 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture

Adds the rest of the Designware register fields.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/i3c/dw-i3c.c | 199 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 199 insertions(+)

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 6cadc59191..48dde008de 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -19,54 +19,253 @@
 #include "trace.h"
 
 REG32(DEVICE_CTRL,                  0x00)
+    FIELD(DEVICE_CTRL, I3C_BROADCAST_ADDR_INC,    0, 1)
+    FIELD(DEVICE_CTRL, I2C_SLAVE_PRESENT,         7, 1)
+    FIELD(DEVICE_CTRL, HOT_JOIN_ACK_NACK_CTRL,    8, 1)
+    FIELD(DEVICE_CTRL, IDLE_CNT_MULTIPLIER,       24, 2)
+    FIELD(DEVICE_CTRL, SLV_ADAPT_TO_I2C_I3C_MODE, 27, 1)
+    FIELD(DEVICE_CTRL, DMA_HANDSHAKE_EN,          28, 1)
+    FIELD(DEVICE_CTRL, I3C_ABORT,                 29, 1)
+    FIELD(DEVICE_CTRL, I3C_RESUME,                30, 1)
+    FIELD(DEVICE_CTRL, I3C_EN,                    31, 1)
 REG32(DEVICE_ADDR,                  0x04)
+    FIELD(DEVICE_ADDR, STATIC_ADDR,         0, 7)
+    FIELD(DEVICE_ADDR, STATIC_ADDR_VALID,   15, 1)
+    FIELD(DEVICE_ADDR, DYNAMIC_ADDR,        16, 7)
+    FIELD(DEVICE_ADDR, DYNAMIC_ADDR_VALID,  31, 1)
 REG32(HW_CAPABILITY,                0x08)
+    FIELD(HW_CAPABILITY, DEVICE_ROLE_CONFIG,  0, 3)
+    FIELD(HW_CAPABILITY, HDR_DDR, 3, 1)
+    FIELD(HW_CAPABILITY, HDR_TS,  4, 1)
 REG32(COMMAND_QUEUE_PORT,           0x0c)
+    FIELD(COMMAND_QUEUE_PORT, CMD_ATTR, 0, 3)
+    /* Transfer command structure */
+    FIELD(COMMAND_QUEUE_PORT, TID, 3, 4)
+    FIELD(COMMAND_QUEUE_PORT, CMD, 7, 8)
+    FIELD(COMMAND_QUEUE_PORT, CP, 15, 1)
+    FIELD(COMMAND_QUEUE_PORT, DEV_INDEX, 16, 5)
+    FIELD(COMMAND_QUEUE_PORT, SPEED, 21, 3)
+    FIELD(COMMAND_QUEUE_PORT, ROC, 26, 1)
+    FIELD(COMMAND_QUEUE_PORT, SDAP, 27, 1)
+    FIELD(COMMAND_QUEUE_PORT, RNW, 28, 1)
+    FIELD(COMMAND_QUEUE_PORT, TOC, 30, 1)
+    FIELD(COMMAND_QUEUE_PORT, PEC, 31, 1)
+    /* Transfer argument data structure */
+    FIELD(COMMAND_QUEUE_PORT, DB, 8, 8)
+    FIELD(COMMAND_QUEUE_PORT, DL, 16, 16)
+    /* Short data argument data structure */
+    FIELD(COMMAND_QUEUE_PORT, BYTE_STRB, 3, 3)
+    FIELD(COMMAND_QUEUE_PORT, BYTE0, 8, 8)
+    FIELD(COMMAND_QUEUE_PORT, BYTE1, 16, 8)
+    FIELD(COMMAND_QUEUE_PORT, BYTE2, 24, 8)
+    /* Address assignment command structure */
+    /*
+     * bits 3..21 and 26..31 are the same as the transfer command structure, or
+     * marked as reserved.
+     */
+    FIELD(COMMAND_QUEUE_PORT, DEV_COUNT, 21, 3)
 REG32(RESPONSE_QUEUE_PORT,          0x10)
+    FIELD(RESPONSE_QUEUE_PORT, DL, 0, 16)
+    FIELD(RESPONSE_QUEUE_PORT, CCCT, 16, 8)
+    FIELD(RESPONSE_QUEUE_PORT, TID, 24, 3)
+    FIELD(RESPONSE_QUEUE_PORT, ERR_STATUS, 28, 4)
 REG32(RX_TX_DATA_PORT,              0x14)
 REG32(IBI_QUEUE_STATUS,             0x18)
+    FIELD(IBI_QUEUE_STATUS, IBI_DATA_LEN,   0, 8)
+    FIELD(IBI_QUEUE_STATUS, IBI_ID,         8, 8)
+    FIELD(IBI_QUEUE_STATUS, LAST_STATUS,    24, 1)
+    FIELD(IBI_QUEUE_STATUS, ERROR,          30, 1)
+    FIELD(IBI_QUEUE_STATUS, IBI_STATUS,     31, 1)
 REG32(IBI_QUEUE_DATA,               0x18)
 REG32(QUEUE_THLD_CTRL,              0x1c)
+    FIELD(QUEUE_THLD_CTRL, CMD_BUF_EMPTY_THLD,  0, 8);
+    FIELD(QUEUE_THLD_CTRL, RESP_BUF_THLD, 8, 8);
+    FIELD(QUEUE_THLD_CTRL, IBI_DATA_THLD, 16, 5);
+    FIELD(QUEUE_THLD_CTRL, IBI_STATUS_THLD,     24, 8);
 REG32(DATA_BUFFER_THLD_CTRL,        0x20)
+    FIELD(DATA_BUFFER_THLD_CTRL, TX_BUF_THLD,   0, 3)
+    FIELD(DATA_BUFFER_THLD_CTRL, RX_BUF_THLD,   8, 3)
+    FIELD(DATA_BUFFER_THLD_CTRL, TX_START_THLD, 16, 3)
+    FIELD(DATA_BUFFER_THLD_CTRL, RX_START_THLD, 24, 3)
 REG32(IBI_QUEUE_CTRL,               0x24)
+    FIELD(IBI_QUEUE_CTRL, NOTIFY_REJECTED_HOT_JOIN,   0, 1)
+    FIELD(IBI_QUEUE_CTRL, NOTIFY_REJECTED_MASTER_REQ, 1, 1)
+    FIELD(IBI_QUEUE_CTRL, NOTIFY_REJECTED_SLAVE_IRQ,  3, 1)
 REG32(IBI_MR_REQ_REJECT,            0x2c)
 REG32(IBI_SIR_REQ_REJECT,           0x30)
 REG32(RESET_CTRL,                   0x34)
+    FIELD(RESET_CTRL, CORE_RESET,       0, 1)
+    FIELD(RESET_CTRL, CMD_QUEUE_RESET,  1, 1)
+    FIELD(RESET_CTRL, RESP_QUEUE_RESET, 2, 1)
+    FIELD(RESET_CTRL, TX_BUF_RESET,     3, 1)
+    FIELD(RESET_CTRL, RX_BUF_RESET,     4, 1)
+    FIELD(RESET_CTRL, IBI_QUEUE_RESET,  5, 1)
 REG32(SLV_EVENT_CTRL,               0x38)
+    FIELD(SLV_EVENT_CTRL, SLV_INTERRUPT,      0, 1)
+    FIELD(SLV_EVENT_CTRL, MASTER_INTERRUPT,   1, 1)
+    FIELD(SLV_EVENT_CTRL, HOT_JOIN_INTERRUPT, 3, 1)
+    FIELD(SLV_EVENT_CTRL, ACTIVITY_STATE,     4, 2)
+    FIELD(SLV_EVENT_CTRL, MRL_UPDATED,        6, 1)
+    FIELD(SLV_EVENT_CTRL, MWL_UPDATED,        7, 1)
 REG32(INTR_STATUS,                  0x3c)
+    FIELD(INTR_STATUS, TX_THLD,           0, 1)
+    FIELD(INTR_STATUS, RX_THLD,           1, 1)
+    FIELD(INTR_STATUS, IBI_THLD,          2, 1)
+    FIELD(INTR_STATUS, CMD_QUEUE_RDY,     3, 1)
+    FIELD(INTR_STATUS, RESP_RDY,          4, 1)
+    FIELD(INTR_STATUS, TRANSFER_ABORT,    5, 1)
+    FIELD(INTR_STATUS, CCC_UPDATED,       6, 1)
+    FIELD(INTR_STATUS, DYN_ADDR_ASSGN,    8, 1)
+    FIELD(INTR_STATUS, TRANSFER_ERR,      9, 1)
+    FIELD(INTR_STATUS, DEFSLV,            10, 1)
+    FIELD(INTR_STATUS, READ_REQ_RECV,     11, 1)
+    FIELD(INTR_STATUS, IBI_UPDATED,       12, 1)
+    FIELD(INTR_STATUS, BUSOWNER_UPDATED,  13, 1)
 REG32(INTR_STATUS_EN,               0x40)
+    FIELD(INTR_STATUS_EN, TX_THLD,          0, 1)
+    FIELD(INTR_STATUS_EN, RX_THLD,          1, 1)
+    FIELD(INTR_STATUS_EN, IBI_THLD,         2, 1)
+    FIELD(INTR_STATUS_EN, CMD_QUEUE_RDY,    3, 1)
+    FIELD(INTR_STATUS_EN, RESP_RDY,         4, 1)
+    FIELD(INTR_STATUS_EN, TRANSFER_ABORT,   5, 1)
+    FIELD(INTR_STATUS_EN, CCC_UPDATED,      6, 1)
+    FIELD(INTR_STATUS_EN, DYN_ADDR_ASSGN,   8, 1)
+    FIELD(INTR_STATUS_EN, TRANSFER_ERR,     9, 1)
+    FIELD(INTR_STATUS_EN, DEFSLV,           10, 1)
+    FIELD(INTR_STATUS_EN, READ_REQ_RECV,    11, 1)
+    FIELD(INTR_STATUS_EN, IBI_UPDATED,      12, 1)
+    FIELD(INTR_STATUS_EN, BUSOWNER_UPDATED, 13, 1)
 REG32(INTR_SIGNAL_EN,               0x44)
+    FIELD(INTR_SIGNAL_EN, TX_THLD,          0, 1)
+    FIELD(INTR_SIGNAL_EN, RX_THLD,          1, 1)
+    FIELD(INTR_SIGNAL_EN, IBI_THLD,         2, 1)
+    FIELD(INTR_SIGNAL_EN, CMD_QUEUE_RDY,    3, 1)
+    FIELD(INTR_SIGNAL_EN, RESP_RDY,         4, 1)
+    FIELD(INTR_SIGNAL_EN, TRANSFER_ABORT,   5, 1)
+    FIELD(INTR_SIGNAL_EN, CCC_UPDATED,      6, 1)
+    FIELD(INTR_SIGNAL_EN, DYN_ADDR_ASSGN,   8, 1)
+    FIELD(INTR_SIGNAL_EN, TRANSFER_ERR,     9, 1)
+    FIELD(INTR_SIGNAL_EN, DEFSLV,           10, 1)
+    FIELD(INTR_SIGNAL_EN, READ_REQ_RECV,    11, 1)
+    FIELD(INTR_SIGNAL_EN, IBI_UPDATED,      12, 1)
+    FIELD(INTR_SIGNAL_EN, BUSOWNER_UPDATED, 13, 1)
 REG32(INTR_FORCE,                   0x48)
+    FIELD(INTR_FORCE, TX_THLD,          0, 1)
+    FIELD(INTR_FORCE, RX_THLD,          1, 1)
+    FIELD(INTR_FORCE, IBI_THLD,         2, 1)
+    FIELD(INTR_FORCE, CMD_QUEUE_RDY,    3, 1)
+    FIELD(INTR_FORCE, RESP_RDY,         4, 1)
+    FIELD(INTR_FORCE, TRANSFER_ABORT,   5, 1)
+    FIELD(INTR_FORCE, CCC_UPDATED,      6, 1)
+    FIELD(INTR_FORCE, DYN_ADDR_ASSGN,   8, 1)
+    FIELD(INTR_FORCE, TRANSFER_ERR,     9, 1)
+    FIELD(INTR_FORCE, DEFSLV,           10, 1)
+    FIELD(INTR_FORCE, READ_REQ_RECV,    11, 1)
+    FIELD(INTR_FORCE, IBI_UPDATED,      12, 1)
+    FIELD(INTR_FORCE, BUSOWNER_UPDATED, 13, 1)
 REG32(QUEUE_STATUS_LEVEL,           0x4c)
+    FIELD(QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC,  0, 8)
+    FIELD(QUEUE_STATUS_LEVEL, RESP_BUF_BLR,         8, 8)
+    FIELD(QUEUE_STATUS_LEVEL, IBI_BUF_BLR,          16, 8)
+    FIELD(QUEUE_STATUS_LEVEL, IBI_STATUS_CNT,       24, 5)
 REG32(DATA_BUFFER_STATUS_LEVEL,     0x50)
+    FIELD(DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC, 0, 8)
+    FIELD(DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR,       16, 8)
 REG32(PRESENT_STATE,                0x54)
+    FIELD(PRESENT_STATE, SCL_LINE_SIGNAL_LEVEL, 0, 1)
+    FIELD(PRESENT_STATE, SDA_LINE_SIGNAL_LEVEL, 1, 1)
+    FIELD(PRESENT_STATE, CURRENT_MASTER,        2, 1)
+    FIELD(PRESENT_STATE, CM_TFR_STATUS,         8, 6)
+    FIELD(PRESENT_STATE, CM_TFR_ST_STATUS,      16, 6)
+    FIELD(PRESENT_STATE, CMD_TID,               24, 4)
 REG32(CCC_DEVICE_STATUS,            0x58)
+    FIELD(CCC_DEVICE_STATUS, PENDING_INTR,      0, 4)
+    FIELD(CCC_DEVICE_STATUS, PROTOCOL_ERR,      5, 1)
+    FIELD(CCC_DEVICE_STATUS, ACTIVITY_MODE,     6, 2)
+    FIELD(CCC_DEVICE_STATUS, UNDER_ERR,         8, 1)
+    FIELD(CCC_DEVICE_STATUS, SLV_BUSY,          9, 1)
+    FIELD(CCC_DEVICE_STATUS, OVERFLOW_ERR,      10, 1)
+    FIELD(CCC_DEVICE_STATUS, DATA_NOT_READY,    11, 1)
+    FIELD(CCC_DEVICE_STATUS, BUFFER_NOT_AVAIL,  12, 1)
 REG32(DEVICE_ADDR_TABLE_POINTER,    0x5c)
     FIELD(DEVICE_ADDR_TABLE_POINTER, DEPTH, 16, 16)
     FIELD(DEVICE_ADDR_TABLE_POINTER, ADDR,  0,  16)
 REG32(DEV_CHAR_TABLE_POINTER,       0x60)
+    FIELD(DEV_CHAR_TABLE_POINTER, P_DEV_CHAR_TABLE_START_ADDR,  0, 12)
+    FIELD(DEV_CHAR_TABLE_POINTER, DEV_CHAR_TABLE_DEPTH,         12, 7)
+    FIELD(DEV_CHAR_TABLE_POINTER, PRESENT_DEV_CHAR_TABLE_INDEX, 19, 3)
 REG32(VENDOR_SPECIFIC_REG_POINTER,  0x6c)
+    FIELD(VENDOR_SPECIFIC_REG_POINTER, P_VENDOR_REG_START_ADDR, 0, 16)
 REG32(SLV_MIPI_PID_VALUE,           0x70)
 REG32(SLV_PID_VALUE,                0x74)
+    FIELD(SLV_PID_VALUE, SLV_PID_DCR, 0, 12)
+    FIELD(SLV_PID_VALUE, SLV_INST_ID, 12, 4)
+    FIELD(SLV_PID_VALUE, SLV_PART_ID, 16, 16)
 REG32(SLV_CHAR_CTRL,                0x78)
+    FIELD(SLV_CHAR_CTRL, BCR,     0, 8)
+    FIELD(SLV_CHAR_CTRL, DCR,     8, 8)
+    FIELD(SLV_CHAR_CTRL, HDR_CAP, 16, 8)
 REG32(SLV_MAX_LEN,                  0x7c)
+    FIELD(SLV_MAX_LEN, MWL, 0, 16)
+    FIELD(SLV_MAX_LEN, MRL, 16, 16)
 REG32(MAX_READ_TURNAROUND,          0x80)
 REG32(MAX_DATA_SPEED,               0x84)
 REG32(SLV_DEBUG_STATUS,             0x88)
 REG32(SLV_INTR_REQ,                 0x8c)
+    FIELD(SLV_INTR_REQ, SIR,          0, 1)
+    FIELD(SLV_INTR_REQ, SIR_CTRL,     1, 2)
+    FIELD(SLV_INTR_REQ, MIR,          3, 1)
+    FIELD(SLV_INTR_REQ, TS,           4, 1)
+    FIELD(SLV_INTR_REQ, IBI_STS,      8, 2)
+REG32(SLV_TSX_SYMBL_TIMING,         0x90)
+    FIELD(SLV_TSX_SYMBL_TIMING, SLV_TSX_SYMBL_CNT, 0, 6)
 REG32(DEVICE_CTRL_EXTENDED,         0xb0)
+    FIELD(DEVICE_CTRL_EXTENDED, MODE, 0, 2)
+    FIELD(DEVICE_CTRL_EXTENDED, REQMST_ACK_CTRL, 3, 1)
 REG32(SCL_I3C_OD_TIMING,            0xb4)
+    FIELD(SCL_I3C_OD_TIMING, I3C_OD_LCNT, 0, 8)
+    FIELD(SCL_I3C_OD_TIMING, I3C_OD_HCNT, 16, 8)
 REG32(SCL_I3C_PP_TIMING,            0xb8)
+    FIELD(SCL_I3C_PP_TIMING, I3C_PP_LCNT, 0, 8)
+    FIELD(SCL_I3C_PP_TIMING, I3C_PP_HCNT, 16, 8)
 REG32(SCL_I2C_FM_TIMING,            0xbc)
 REG32(SCL_I2C_FMP_TIMING,           0xc0)
+    FIELD(SCL_I2C_FMP_TIMING, I2C_FMP_LCNT, 0, 16)
+    FIELD(SCL_I2C_FMP_TIMING, I2C_FMP_HCNT, 16, 8)
 REG32(SCL_EXT_LCNT_TIMING,          0xc8)
 REG32(SCL_EXT_TERMN_LCNT_TIMING,    0xcc)
 REG32(BUS_FREE_TIMING,              0xd4)
 REG32(BUS_IDLE_TIMING,              0xd8)
+    FIELD(BUS_IDLE_TIMING, BUS_IDLE_TIME, 0, 20)
 REG32(I3C_VER_ID,                   0xe0)
 REG32(I3C_VER_TYPE,                 0xe4)
 REG32(EXTENDED_CAPABILITY,          0xe8)
 REG32(SLAVE_CONFIG,                 0xec)
+/* Device characteristic table fields */
+REG32(DEVICE_CHARACTERISTIC_TABLE_LOC1, 0x200)
+REG32(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, 0x200)
+    FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, DYNAMIC_ADDR, 0, 8)
+    FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, DCR, 8, 8)
+    FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, BCR, 16, 8)
+    FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC_SECONDARY, STATIC_ADDR, 24, 8)
+REG32(DEVICE_CHARACTERISTIC_TABLE_LOC2, 0x204)
+    FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC2, MSB_PID, 0, 16)
+REG32(DEVICE_CHARACTERISTIC_TABLE_LOC3, 0x208)
+    FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC3, DCR, 0, 8)
+    FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC3, BCR, 8, 8)
+REG32(DEVICE_CHARACTERISTIC_TABLE_LOC4, 0x20c)
+    FIELD(DEVICE_CHARACTERISTIC_TABLE_LOC4, DEV_DYNAMIC_ADDR, 0, 8)
+/* Dev addr table fields */
+REG32(DEVICE_ADDR_TABLE_LOC1, 0x280)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, DEV_STATIC_ADDR, 0, 7)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, IBI_PEC_EN, 11, 1)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, IBI_WITH_DATA, 12, 1)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, SIR_REJECT, 13, 1)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, MR_REJECT, 14, 1)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, DEV_DYNAMIC_ADDR, 16, 8)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, IBI_ADDR_MASK, 24, 2)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, DEV_NACK_RETRY_CNT, 29, 2)
+    FIELD(DEVICE_ADDR_TABLE_LOC1, LEGACY_I2C_DEVICE, 31, 1)
 
 static const uint32_t dw_i3c_resets[DW_I3C_NR_REGS] = {
     [R_HW_CAPABILITY]               = 0x000e00bf,
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 06/22] hw/i3c/aspeed_i3c: Add more register fields
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (4 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 05/22] hw/i3c/dw-i3c: Add more register fields Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 07/22] hw/i3c/dw-i3c: Add more reset values Jamin Lin
                   ` (17 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture

Adds the rest of the Aspeed I3C controller register fields.

Signed-off-by: Joe Komlodi <komlodi@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>
---
 hw/i3c/aspeed_i3c.c | 54 +++++++++++++++++++++++++++++++++++----------
 1 file changed, 42 insertions(+), 12 deletions(-)

diff --git a/hw/i3c/aspeed_i3c.c b/hw/i3c/aspeed_i3c.c
index 647074d181..a215700288 100644
--- a/hw/i3c/aspeed_i3c.c
+++ b/hw/i3c/aspeed_i3c.c
@@ -21,28 +21,58 @@
 /* I3C Controller Registers */
 REG32(I3C1_REG0, 0x10)
 REG32(I3C1_REG1, 0x14)
-    FIELD(I3C1_REG1, I2C_MODE,  0,  1)
-    FIELD(I3C1_REG1, SA_EN,     15, 1)
+    FIELD(I3C1_REG1, I2C_MODE,      0,  1)
+    FIELD(I3C1_REG1, SLV_TEST_MODE, 1,  1)
+    FIELD(I3C1_REG1, ACT_MODE,      2,  2)
+    FIELD(I3C1_REG1, PENDING_INT,   4,  4)
+    FIELD(I3C1_REG1, SA,            8,  7)
+    FIELD(I3C1_REG1, SA_EN,         15, 1)
+    FIELD(I3C1_REG1, INST_ID,       16, 4)
 REG32(I3C2_REG0, 0x20)
 REG32(I3C2_REG1, 0x24)
-    FIELD(I3C2_REG1, I2C_MODE,  0,  1)
-    FIELD(I3C2_REG1, SA_EN,     15, 1)
+    FIELD(I3C2_REG1, I2C_MODE,      0,  1)
+    FIELD(I3C2_REG1, SLV_TEST_MODE, 1,  1)
+    FIELD(I3C2_REG1, ACT_MODE,      2,  2)
+    FIELD(I3C2_REG1, PENDING_INT,   4,  4)
+    FIELD(I3C2_REG1, SA,            8,  7)
+    FIELD(I3C2_REG1, SA_EN,         15, 1)
+    FIELD(I3C2_REG1, INST_ID,       16, 4)
 REG32(I3C3_REG0, 0x30)
 REG32(I3C3_REG1, 0x34)
-    FIELD(I3C3_REG1, I2C_MODE,  0,  1)
-    FIELD(I3C3_REG1, SA_EN,     15, 1)
+    FIELD(I3C3_REG1, I2C_MODE,      0,  1)
+    FIELD(I3C3_REG1, SLV_TEST_MODE, 1,  1)
+    FIELD(I3C3_REG1, ACT_MODE,      2,  2)
+    FIELD(I3C3_REG1, PENDING_INT,   4,  4)
+    FIELD(I3C3_REG1, SA,            8,  7)
+    FIELD(I3C3_REG1, SA_EN,         15, 1)
+    FIELD(I3C3_REG1, INST_ID,       16, 4)
 REG32(I3C4_REG0, 0x40)
 REG32(I3C4_REG1, 0x44)
-    FIELD(I3C4_REG1, I2C_MODE,  0,  1)
-    FIELD(I3C4_REG1, SA_EN,     15, 1)
+    FIELD(I3C4_REG1, I2C_MODE,      0,  1)
+    FIELD(I3C4_REG1, SLV_TEST_MODE, 1,  1)
+    FIELD(I3C4_REG1, ACT_MODE,      2,  2)
+    FIELD(I3C4_REG1, PENDING_INT,   4,  4)
+    FIELD(I3C4_REG1, SA,            8,  7)
+    FIELD(I3C4_REG1, SA_EN,         15, 1)
+    FIELD(I3C4_REG1, INST_ID,       16, 4)
 REG32(I3C5_REG0, 0x50)
 REG32(I3C5_REG1, 0x54)
-    FIELD(I3C5_REG1, I2C_MODE,  0,  1)
-    FIELD(I3C5_REG1, SA_EN,     15, 1)
+    FIELD(I3C5_REG1, I2C_MODE,      0,  1)
+    FIELD(I3C5_REG1, SLV_TEST_MODE, 1,  1)
+    FIELD(I3C5_REG1, ACT_MODE,      2,  2)
+    FIELD(I3C5_REG1, PENDING_INT,   4,  4)
+    FIELD(I3C5_REG1, SA,            8,  7)
+    FIELD(I3C5_REG1, SA_EN,         15, 1)
+    FIELD(I3C5_REG1, INST_ID,       16, 4)
 REG32(I3C6_REG0, 0x60)
 REG32(I3C6_REG1, 0x64)
-    FIELD(I3C6_REG1, I2C_MODE,  0,  1)
-    FIELD(I3C6_REG1, SA_EN,     15, 1)
+    FIELD(I3C6_REG1, I2C_MODE,      0,  1)
+    FIELD(I3C6_REG1, SLV_TEST_MODE, 1,  1)
+    FIELD(I3C6_REG1, ACT_MODE,      2,  2)
+    FIELD(I3C6_REG1, PENDING_INT,   4,  4)
+    FIELD(I3C6_REG1, SA,            8,  7)
+    FIELD(I3C6_REG1, SA_EN,         15, 1)
+    FIELD(I3C6_REG1, INST_ID,       16, 4)
 
 static uint64_t aspeed_i3c_read(void *opaque, hwaddr addr, unsigned int size)
 {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 07/22] hw/i3c/dw-i3c: Add more reset values
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (5 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 06/22] hw/i3c/aspeed_i3c: " Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 08/22] hw/i3c/aspeed_i3c: Add register RO field masks Jamin Lin
                   ` (16 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture

Adds reset values for the new registers added.

Signed-off-by: Joe Komlodi <komlodi@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>
---
 hw/i3c/dw-i3c.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 48dde008de..28da6016a1 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -268,14 +268,32 @@ REG32(DEVICE_ADDR_TABLE_LOC1, 0x280)
     FIELD(DEVICE_ADDR_TABLE_LOC1, LEGACY_I2C_DEVICE, 31, 1)
 
 static const uint32_t dw_i3c_resets[DW_I3C_NR_REGS] = {
-    [R_HW_CAPABILITY]               = 0x000e00bf,
+    /* Target mode is not supported, don't advertise it for now. */
+    [R_HW_CAPABILITY]               = 0x000e00b9,
     [R_QUEUE_THLD_CTRL]             = 0x01000101,
+    [R_DATA_BUFFER_THLD_CTRL]       = 0x01010100,
+    [R_SLV_EVENT_CTRL]              = 0x0000000b,
+    [R_QUEUE_STATUS_LEVEL]          = 0x00000002,
+    [R_DATA_BUFFER_STATUS_LEVEL]    = 0x00000010,
+    [R_PRESENT_STATE]               = 0x00000003,
     [R_I3C_VER_ID]                  = 0x3130302a,
     [R_I3C_VER_TYPE]                = 0x6c633033,
     [R_DEVICE_ADDR_TABLE_POINTER]   = 0x00080280,
     [R_DEV_CHAR_TABLE_POINTER]      = 0x00020200,
+    [R_SLV_CHAR_CTRL]               = 0x00010000,
     [A_VENDOR_SPECIFIC_REG_POINTER] = 0x000000b0,
     [R_SLV_MAX_LEN]                 = 0x00ff00ff,
+    [R_SLV_TSX_SYMBL_TIMING]        = 0x0000003f,
+    [R_SCL_I3C_OD_TIMING]           = 0x000a0010,
+    [R_SCL_I3C_PP_TIMING]           = 0x000a000a,
+    [R_SCL_I2C_FM_TIMING]           = 0x00100010,
+    [R_SCL_I2C_FMP_TIMING]          = 0x00100010,
+    [R_SCL_EXT_LCNT_TIMING]         = 0x20202020,
+    [R_SCL_EXT_TERMN_LCNT_TIMING]   = 0x00300000,
+    [R_BUS_FREE_TIMING]             = 0x00200020,
+    [R_BUS_IDLE_TIMING]             = 0x00000020,
+    [R_EXTENDED_CAPABILITY]         = 0x00000239,
+    [R_SLAVE_CONFIG]                = 0x00000023,
 };
 
 static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 08/22] hw/i3c/aspeed_i3c: Add register RO field masks
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (6 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 07/22] hw/i3c/dw-i3c: Add more reset values Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 09/22] hw/i3c/dw-i3c: " Jamin Lin
                   ` (15 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture

Adds read-only register masks for the Aspeed I3C controller registers.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/i3c/aspeed_i3c.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/hw/i3c/aspeed_i3c.c b/hw/i3c/aspeed_i3c.c
index a215700288..2700c5f4cd 100644
--- a/hw/i3c/aspeed_i3c.c
+++ b/hw/i3c/aspeed_i3c.c
@@ -74,6 +74,21 @@ REG32(I3C6_REG1, 0x64)
     FIELD(I3C6_REG1, SA_EN,         15, 1)
     FIELD(I3C6_REG1, INST_ID,       16, 4)
 
+static const uint32_t ast2600_i3c_controller_ro[ASPEED_I3C_NR_REGS] = {
+    [R_I3C1_REG0]                   = 0xcc000000,
+    [R_I3C1_REG1]                   = 0xfff00000,
+    [R_I3C2_REG0]                   = 0xcc000000,
+    [R_I3C2_REG1]                   = 0xfff00000,
+    [R_I3C3_REG0]                   = 0xcc000000,
+    [R_I3C3_REG1]                   = 0xfff00000,
+    [R_I3C4_REG0]                   = 0xcc000000,
+    [R_I3C4_REG1]                   = 0xfff00000,
+    [R_I3C5_REG0]                   = 0xcc000000,
+    [R_I3C5_REG1]                   = 0xfff00000,
+    [R_I3C6_REG0]                   = 0xcc000000,
+    [R_I3C6_REG1]                   = 0xfff00000,
+};
+
 static uint64_t aspeed_i3c_read(void *opaque, hwaddr addr, unsigned int size)
 {
     AspeedI3CState *s = ASPEED_I3C(opaque);
@@ -97,6 +112,7 @@ static void aspeed_i3c_write(void *opaque,
 
     addr >>= 2;
 
+    data &= ~ast2600_i3c_controller_ro[addr];
     /* I3C controller register */
     switch (addr) {
     case R_I3C1_REG1:
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 09/22] hw/i3c/dw-i3c: Add register RO field masks
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (7 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 08/22] hw/i3c/aspeed_i3c: Add register RO field masks Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 10/22] hw/i3c/dw-i3c: Treat more registers as read-as-zero Jamin Lin
                   ` (14 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture

Adds read-only register masks for the DwC I3C controller.

Signed-off-by: Joe Komlodi <komlodi@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>
---
 hw/i3c/dw-i3c.c | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 28da6016a1..87c8f63da9 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -296,6 +296,45 @@ static const uint32_t dw_i3c_resets[DW_I3C_NR_REGS] = {
     [R_SLAVE_CONFIG]                = 0x00000023,
 };
 
+static const uint32_t dw_i3c_ro[DW_I3C_NR_REGS] = {
+    [R_DEVICE_CTRL]                 = 0x04fffe00,
+    [R_DEVICE_ADDR]                 = 0x7f807f80,
+    [R_HW_CAPABILITY]               = 0xffffffff,
+    [R_IBI_QUEUE_STATUS]            = 0xffffffff,
+    [R_DATA_BUFFER_THLD_CTRL]       = 0xf8f8f8f8,
+    [R_IBI_QUEUE_CTRL]              = 0xfffffff0,
+    [R_RESET_CTRL]                  = 0xffffffc0,
+    [R_SLV_EVENT_CTRL]              = 0xffffff3f,
+    [R_INTR_STATUS]                 = 0xffff809f,
+    [R_INTR_STATUS_EN]              = 0xffff8080,
+    [R_INTR_SIGNAL_EN]              = 0xffff8080,
+    [R_INTR_FORCE]                  = 0xffff8000,
+    [R_QUEUE_STATUS_LEVEL]          = 0xffffffff,
+    [R_DATA_BUFFER_STATUS_LEVEL]    = 0xffffffff,
+    [R_PRESENT_STATE]               = 0xffffffff,
+    [R_CCC_DEVICE_STATUS]           = 0xffffffff,
+    [R_I3C_VER_ID]                  = 0xffffffff,
+    [R_I3C_VER_TYPE]                = 0xffffffff,
+    [R_DEVICE_ADDR_TABLE_POINTER]   = 0xffffffff,
+    [R_DEV_CHAR_TABLE_POINTER]      = 0xffcbffff,
+    [R_SLV_PID_VALUE]               = 0xffff0fff,
+    [R_SLV_CHAR_CTRL]               = 0xffffffff,
+    [A_VENDOR_SPECIFIC_REG_POINTER] = 0xffffffff,
+    [R_SLV_MAX_LEN]                 = 0xffffffff,
+    [R_MAX_READ_TURNAROUND]         = 0xffffffff,
+    [R_MAX_DATA_SPEED]              = 0xffffffff,
+    [R_SLV_INTR_REQ]                = 0xfffffff0,
+    [R_SLV_TSX_SYMBL_TIMING]        = 0xffffffc0,
+    [R_DEVICE_CTRL_EXTENDED]        = 0xfffffff8,
+    [R_SCL_I3C_OD_TIMING]           = 0xff00ff00,
+    [R_SCL_I3C_PP_TIMING]           = 0xff00ff00,
+    [R_SCL_I2C_FMP_TIMING]          = 0xff000000,
+    [R_SCL_EXT_TERMN_LCNT_TIMING]   = 0x0000fff0,
+    [R_BUS_IDLE_TIMING]             = 0xfff00000,
+    [R_EXTENDED_CAPABILITY]         = 0xffffffff,
+    [R_SLAVE_CONFIG]                = 0xffffffff,
+};
+
 static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
 {
     DWI3C *s = DW_I3C(opaque);
@@ -324,6 +363,7 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
 
     trace_dw_i3c_write(s->id, offset, value);
 
+    value &= ~dw_i3c_ro[addr];
     switch (addr) {
     case R_HW_CAPABILITY:
     case R_RESPONSE_QUEUE_PORT:
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 10/22] hw/i3c/dw-i3c: Treat more registers as read-as-zero
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (8 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 09/22] hw/i3c/dw-i3c: " Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 11/22] hw/i3c/dw-i3c: Use 32 bits on MMIO writes Jamin Lin
                   ` (13 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture

RESET_CTRL and INTR_FORCE are write-only.

Signed-off-by: Joe Komlodi <komlodi@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>
---
 hw/i3c/dw-i3c.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 87c8f63da9..eeaf1125e4 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -342,7 +342,10 @@ static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
     uint64_t value;
 
     switch (addr) {
+    /* RAZ */
     case R_COMMAND_QUEUE_PORT:
+    case R_RESET_CTRL:
+    case R_INTR_FORCE:
         value = 0;
         break;
     default:
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 11/22] hw/i3c/dw-i3c: Use 32 bits on MMIO writes
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (9 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 10/22] hw/i3c/dw-i3c: Treat more registers as read-as-zero Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 12/22] hw/i3c/dw-i3c: Add IRQ MMIO behavior Jamin Lin
                   ` (12 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture, Titus Rwantare

The registers are only 32 bits wide, so we should cast the 64-bit value
passed in to only be 32 bits wide.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Reviewed-by: Titus Rwantare <titusr@google.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/i3c/dw-i3c.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index eeaf1125e4..7ca99fb87e 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -363,10 +363,11 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
 {
     DWI3C *s = DW_I3C(opaque);
     uint32_t addr = offset >> 2;
+    uint32_t val32 = (uint32_t)value;
 
     trace_dw_i3c_write(s->id, offset, value);
 
-    value &= ~dw_i3c_ro[addr];
+    val32 &= ~dw_i3c_ro[addr];
     switch (addr) {
     case R_HW_CAPABILITY:
     case R_RESPONSE_QUEUE_PORT:
@@ -392,7 +393,7 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
     case R_RESET_CTRL:
         break;
     default:
-        s->regs[addr] = value;
+        s->regs[addr] = val32;
         break;
     }
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 12/22] hw/i3c/dw-i3c: Add IRQ MMIO behavior
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (10 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 11/22] hw/i3c/dw-i3c: Use 32 bits on MMIO writes Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX Jamin Lin
                   ` (11 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture, Hao Wu

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/i3c/dw-i3c.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 7ca99fb87e..0b99c7edfb 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -17,6 +17,7 @@
 #include "qapi/error.h"
 #include "migration/vmstate.h"
 #include "trace.h"
+#include "hw/core/irq.h"
 
 REG32(DEVICE_CTRL,                  0x00)
     FIELD(DEVICE_CTRL, I3C_BROADCAST_ADDR_INC,    0, 1)
@@ -335,6 +336,46 @@ static const uint32_t dw_i3c_ro[DW_I3C_NR_REGS] = {
     [R_SLAVE_CONFIG]                = 0xffffffff,
 };
 
+static void dw_i3c_update_irq(DWI3C *s)
+{
+    bool level = !!(s->regs[R_INTR_SIGNAL_EN] & s->regs[R_INTR_STATUS]);
+    qemu_set_irq(s->irq, level);
+}
+
+static uint32_t dw_i3c_intr_status_r(DWI3C *s)
+{
+    /* Only return the status whose corresponding EN bits are set. */
+    return s->regs[R_INTR_STATUS] & s->regs[R_INTR_STATUS_EN];
+}
+
+static void dw_i3c_intr_status_w(DWI3C *s, uint32_t val)
+{
+    /* INTR_STATUS[13:5] is w1c, other bits are RO. */
+    val &= 0x3fe0;
+    s->regs[R_INTR_STATUS] &= ~val;
+
+    dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_intr_status_en_w(DWI3C *s, uint32_t val)
+{
+    s->regs[R_INTR_STATUS_EN] = val;
+    dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_intr_signal_en_w(DWI3C *s, uint32_t val)
+{
+    s->regs[R_INTR_SIGNAL_EN] = val;
+    dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_intr_force_w(DWI3C *s, uint32_t val)
+{
+    /* INTR_FORCE is WO, just set the corresponding INTR_STATUS bits. */
+    s->regs[R_INTR_STATUS] = val;
+    dw_i3c_update_irq(s);
+}
+
 static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
 {
     DWI3C *s = DW_I3C(opaque);
@@ -348,6 +389,9 @@ static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
     case R_INTR_FORCE:
         value = 0;
         break;
+    case R_INTR_STATUS:
+        value = dw_i3c_intr_status_r(s);
+        break;
     default:
         value = s->regs[addr];
         break;
@@ -392,6 +436,18 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
         break;
     case R_RESET_CTRL:
         break;
+    case R_INTR_STATUS:
+        dw_i3c_intr_status_w(s, val32);
+        break;
+    case R_INTR_STATUS_EN:
+        dw_i3c_intr_status_en_w(s, val32);
+        break;
+    case R_INTR_SIGNAL_EN:
+        dw_i3c_intr_signal_en_w(s, val32);
+        break;
+    case R_INTR_FORCE:
+        dw_i3c_intr_force_w(s, val32);
+        break;
     default:
         s->regs[addr] = val32;
         break;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (11 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 12/22] hw/i3c/dw-i3c: Add IRQ MMIO behavior Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-27  9:56   ` Cédric Le Goater
  2026-02-25  2:12 ` [PATCH v7 14/22] hw/i3c/dw-i3c: Add IBI handling Jamin Lin
                   ` (10 subsequent siblings)
  23 siblings, 1 reply; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Stephen Longfield, Patrick Venture

This adds data and CCC transmission, reception, and the associated
queues required for data transmission and reception to happen.

The I3C controller transmits data by the user writing into a command
queue. When the queue has a command and an argument in it, the
controller starts executing the command.

The controller can execute 1 of 3 ways:
1. A larger data transfer that involves using the TX and RX queues. This
   is the most common way the controller does transactions.

2. A small data transfer that involves sending a couple bytes passed
   into the command queue argument.

3. An address assignment command. This is how the controller does
   ENTDAA. When ENTDAA succeeds in assigning an address to a target, it
   updates the controller's char table with the target's PID, BCR, and
   DCR.

The controller determines what addresses to send by looking at the index
in the device address table specified by the argument in the command
queue. ENTDAA also uses these addresses to assign to targets on the bus.

When the controller is done executing a command, it puts a response in
the response queue indicating how command execution went.

In order for the user to send and receive data to/from the controller,
the user reads/writes to a bidirectional TX/RX port.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Stephen Longfield <slongfield@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 include/hw/i3c/aspeed_i3c.h |   1 +
 include/hw/i3c/dw-i3c.h     | 143 +++++-
 hw/i3c/dw-i3c.c             | 882 +++++++++++++++++++++++++++++++++++-
 hw/i3c/trace-events         |  10 +
 4 files changed, 1030 insertions(+), 6 deletions(-)

diff --git a/include/hw/i3c/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
index ade5c42d39..b8827d31d7 100644
--- a/include/hw/i3c/aspeed_i3c.h
+++ b/include/hw/i3c/aspeed_i3c.h
@@ -2,6 +2,7 @@
  * ASPEED I3C Controller
  *
  * Copyright (C) 2021 ASPEED Technology Inc.
+ * Copyright (C) 2023 Google, LLC
  *
  * This code is licensed under the GPL version 2 or later.  See
  * the COPYING file in the top-level directory.
diff --git a/include/hw/i3c/dw-i3c.h b/include/hw/i3c/dw-i3c.h
index 7143e8ca7a..c50d67cc6b 100644
--- a/include/hw/i3c/dw-i3c.h
+++ b/include/hw/i3c/dw-i3c.h
@@ -10,20 +10,159 @@
 #ifndef DW_I3C_H
 #define DW_I3C_H
 
+#include "qemu/fifo32.h"
+#include "hw/i3c/i3c.h"
 #include "hw/core/sysbus.h"
 
 #define TYPE_DW_I3C "dw.i3c"
 OBJECT_DECLARE_SIMPLE_TYPE(DWI3C, DW_I3C)
 
-#define DW_I3C_NR_REGS (0x300 >> 2)
+/*
+ * Sufficiently large enough to handle configurations with large device address
+ * tables.
+ */
+#define DW_I3C_NR_REGS (0x1000 >> 2)
+
+/* From datasheet. */
+#define DW_I3C_CMD_ATTR_TRANSFER_CMD 0
+#define DW_I3C_CMD_ATTR_TRANSFER_ARG 1
+#define DW_I3C_CMD_ATTR_SHORT_DATA_ARG 2
+#define DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD 3
+
+/* Enum values from datasheet. */
+typedef enum DWI3CRespQueueErr {
+    DW_I3C_RESP_QUEUE_ERR_NONE = 0,
+    DW_I3C_RESP_QUEUE_ERR_CRC = 1,
+    DW_I3C_RESP_QUEUE_ERR_PARITY = 2,
+    DW_I3C_RESP_QUEUE_ERR_FRAME = 3,
+    DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK = 4,
+    DW_I3C_RESP_QUEUE_ERR_DAA_NACK = 5,
+    DW_I3C_RESP_QUEUE_ERR_OVERFLOW = 6,
+    DW_I3C_RESP_QUEUE_ERR_ABORTED = 8,
+    DW_I3C_RESP_QUEUE_ERR_I2C_NACK = 9,
+} DWI3CRespQueueErr;
+
+typedef enum DWI3CTransferState {
+    DW_I3C_TRANSFER_STATE_IDLE = 0x00,
+    DW_I3C_TRANSFER_STATE_START = 0x01,
+    DW_I3C_TRANSFER_STATE_RESTART = 0x02,
+    DW_I3C_TRANSFER_STATE_STOP = 0x03,
+    DW_I3C_TRANSFER_STATE_START_HOLD = 0x04,
+    DW_I3C_TRANSFER_STATE_BROADCAST_W = 0x05,
+    DW_I3C_TRANSFER_STATE_BROADCAST_R = 0x06,
+    DW_I3C_TRANSFER_STATE_DAA = 0x07,
+    DW_I3C_TRANSFER_STATE_DAA_GEN = 0x08,
+    DW_I3C_TRANSFER_STATE_CCC_BYTE = 0x0b,
+    DW_I3C_TRANSFER_STATE_HDR_CMD = 0x0c,
+    DW_I3C_TRANSFER_STATE_WRITE = 0x0d,
+    DW_I3C_TRANSFER_STATE_READ = 0x0e,
+    DW_I3C_TRANSFER_STATE_IBI_READ = 0x0f,
+    DW_I3C_TRANSFER_STATE_IBI_DIS = 0x10,
+    DW_I3C_TRANSFER_STATE_HDR_DDR_CRC = 0x11,
+    DW_I3C_TRANSFER_STATE_CLK_STRETCH = 0x12,
+    DW_I3C_TRANSFER_STATE_HALT = 0x13,
+} DWI3CTransferState;
+
+typedef enum DWI3CTransferStatus {
+    DW_I3C_TRANSFER_STATUS_IDLE = 0x00,
+    DW_I3C_TRANSFER_STATUS_BROACAST_CCC = 0x01,
+    DW_I3C_TRANSFER_STATUS_DIRECT_CCC_W = 0x02,
+    DW_I3C_TRANSFER_STATUS_DIRECT_CCC_R = 0x03,
+    DW_I3C_TRANSFER_STATUS_ENTDAA = 0x04,
+    DW_I3C_TRANSFER_STATUS_SETDASA = 0x05,
+    DW_I3C_TRANSFER_STATUS_I3C_SDR_W = 0x06,
+    DW_I3C_TRANSFER_STATUS_I3C_SDR_R = 0x07,
+    DW_I3C_TRANSFER_STATUS_I2C_SDR_W = 0x08,
+    DW_I3C_TRANSFER_STATUS_I2C_SDR_R = 0x09,
+    DW_I3C_TRANSFER_STATUS_HDR_TS_W = 0x0a,
+    DW_I3C_TRANSFER_STATUS_HDR_TS_R = 0x0b,
+    DW_I3C_TRANSFER_STATUS_HDR_DDR_W = 0x0c,
+    DW_I3C_TRANSFER_STATUS_HDR_DDR_R = 0x0d,
+    DW_I3C_TRANSFER_STATUS_IBI = 0x0e,
+    DW_I3C_TRANSFER_STATUS_HALT = 0x0f,
+} DWI3CTransferStatus;
+
+/*
+ * Transfer commands and arguments are 32-bit wide values that the user passes
+ * into the command queue. We interpret each 32-bit word based on the cmd_attr
+ * field.
+ */
+typedef struct DWI3CTransferCmd {
+    uint8_t cmd_attr:3;
+    uint8_t tid:4; /* Transaction ID */
+    uint16_t cmd:8;
+    uint8_t cp:1; /* Command present */
+    uint8_t dev_index:5;
+    uint8_t speed:3;
+    uint8_t resv0:1;
+    uint8_t dbp:1; /* Defining byte present */
+    uint8_t roc:1; /* Response on completion */
+    uint8_t sdap:1; /* Short data argument present */
+    uint8_t rnw:1; /* Read not write */
+    uint8_t resv1:1;
+    uint8_t toc:1; /* Termination (I3C STOP) on completion */
+    uint8_t pec:1; /* Parity error check enabled */
+} DWI3CTransferCmd;
+
+typedef struct DWI3CTransferArg {
+    uint8_t cmd_attr:3;
+    uint8_t resv:5;
+    uint8_t db; /* Defining byte */
+    uint16_t data_len;
+} DWI3CTransferArg;
+
+typedef struct DWI3CShortArg {
+    uint8_t cmd_attr:3;
+    uint8_t byte_strb:3;
+    uint8_t resv:2;
+    uint8_t byte0;
+    uint8_t byte1;
+    uint8_t byte2;
+} DWI3CShortArg;
+
+typedef struct DWI3CAddrAssignCmd {
+    uint8_t cmd_attr:3;
+    uint8_t tid:4; /* Transaction ID */
+    uint16_t cmd:8;
+    uint8_t resv0:1;
+    uint8_t dev_index:5;
+    uint16_t dev_count:5;
+    uint8_t roc:1; /* Response on completion */
+    uint8_t resv1:3;
+    uint8_t toc:1; /* Termination (I3C STOP) on completion */
+    uint8_t resv2:1;
+} DWI3CAddrAssignCmd;
+
+typedef union DWI3CCmdQueueData {
+    uint32_t word;
+    DWI3CTransferCmd transfer_cmd;
+    DWI3CTransferArg transfer_arg;
+    DWI3CShortArg short_arg;
+    DWI3CAddrAssignCmd addr_assign_cmd;
+} DWI3CCmdQueueData;
 
 struct DWI3C {
     SysBusDevice parent_obj;
 
     MemoryRegion mr;
     qemu_irq irq;
+    I3CBus *bus;
+
+    Fifo32 cmd_queue;
+    Fifo32 resp_queue;
+    Fifo32 tx_queue;
+    Fifo32 rx_queue;
 
-    uint8_t id;
+    struct {
+        uint8_t id;
+        uint8_t cmd_resp_queue_capacity_bytes;
+        uint16_t tx_rx_queue_capacity_bytes;
+        uint8_t num_addressable_devices;
+        uint16_t dev_addr_table_pointer;
+        uint16_t dev_addr_table_depth;
+        uint16_t dev_char_table_pointer;
+        uint16_t dev_char_table_depth;
+    } cfg;
     uint32_t regs[DW_I3C_NR_REGS];
 };
 
diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 0b99c7edfb..2453d74a8c 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -336,12 +336,179 @@ static const uint32_t dw_i3c_ro[DW_I3C_NR_REGS] = {
     [R_SLAVE_CONFIG]                = 0xffffffff,
 };
 
+static inline bool dw_i3c_has_hdr_ts(DWI3C *s)
+{
+    return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_TS);
+}
+
+static inline bool dw_i3c_has_hdr_ddr(DWI3C *s)
+{
+    return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_DDR);
+}
+
+static inline bool dw_i3c_can_transmit(DWI3C *s)
+{
+    /*
+     * We can only transmit if we're enabled and the resume bit is cleared.
+     * The resume bit is set on a transaction error, and software must clear it.
+     */
+    return ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_EN) &&
+           !ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_RESUME);
+}
+
+static inline uint8_t dw_i3c_fifo_threshold_from_reg(uint8_t regval)
+{
+    return regval = regval ? (2 << regval) : 1;
+}
+
 static void dw_i3c_update_irq(DWI3C *s)
 {
     bool level = !!(s->regs[R_INTR_SIGNAL_EN] & s->regs[R_INTR_STATUS]);
     qemu_set_irq(s->irq, level);
 }
 
+static void dw_i3c_end_transfer(DWI3C *s, bool is_i2c)
+{
+    if (is_i2c) {
+        legacy_i2c_end_transfer(s->bus);
+    } else {
+        i3c_end_transfer(s->bus);
+    }
+}
+
+static int dw_i3c_send_start(DWI3C *s, uint8_t addr, bool is_recv, bool is_i2c)
+{
+    int ret;
+
+    if (is_i2c) {
+        ret = legacy_i2c_start_transfer(s->bus, addr, is_recv);
+    } else {
+        ret = i3c_start_transfer(s->bus, addr, is_recv);
+    }
+    if (ret) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed on TX with addr 0x%.2x\n",
+                      path, addr);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                         DW_I3C_TRANSFER_STATE_HALT);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+                         DW_I3C_TRANSFER_STATUS_HALT);
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
+        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
+    }
+
+    return ret;
+}
+
+static int dw_i3c_send(DWI3C *s, const uint8_t *data, uint32_t num_to_send,
+                       uint32_t *num_sent, bool is_i2c)
+{
+    int ret;
+    uint32_t i;
+
+    *num_sent = 0;
+    if (is_i2c) {
+        /* Legacy I2C must be byte-by-byte. */
+        for (i = 0; i < num_to_send; i++) {
+            ret = legacy_i2c_send(s->bus, data[i]);
+            if (ret) {
+                break;
+            }
+            (*num_sent)++;
+        }
+    } else {
+        ret = i3c_send(s->bus, data, num_to_send, num_sent);
+    }
+    if (ret) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed sending byte 0x%.2x\n",
+                      path, data[*num_sent]);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                         DW_I3C_TRANSFER_STATE_HALT);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+                         DW_I3C_TRANSFER_STATUS_HALT);
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
+        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
+    }
+
+    trace_dw_i3c_send(s->cfg.id, *num_sent);
+
+    return ret;
+}
+
+static int dw_i3c_send_byte(DWI3C *s, uint8_t byte, bool is_i2c)
+{
+    /*
+     * Ignored, the caller will know if we sent 0 or 1 bytes depending on if
+     * we were ACKed/NACKed.
+     */
+    uint32_t num_sent;
+    return dw_i3c_send(s, &byte, 1, &num_sent, is_i2c);
+}
+
+static int dw_i3c_recv_data(DWI3C *s, bool is_i2c, uint8_t *data,
+                            uint16_t num_to_read, uint32_t *num_read)
+{
+    int ret;
+
+    if (is_i2c) {
+        for (uint16_t i = 0; i < num_to_read; i++) {
+            data[i] = legacy_i2c_recv(s->bus);
+        }
+        /* I2C devices can neither NACK a read, nor end transfers early. */
+        *num_read = num_to_read;
+        trace_dw_i3c_recv_data(s->cfg.id, *num_read);
+        return 0;
+    }
+    /* I3C devices can NACK if the controller sends an unsupported CCC. */
+    ret = i3c_recv(s->bus, data, num_to_read, num_read);
+    if (ret) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed receiving byte\n",
+                      object_get_canonical_path(OBJECT(s)));
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                         DW_I3C_TRANSFER_STATE_HALT);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+                         DW_I3C_TRANSFER_STATUS_HALT);
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
+        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
+    }
+
+    trace_dw_i3c_recv_data(s->cfg.id, *num_read);
+
+    return ret;
+}
+
+static inline bool dw_i3c_target_is_i2c(DWI3C *s, uint16_t offset)
+{
+    /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
+    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
+                                          ADDR) / sizeof(uint32_t)) + offset;
+    return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
+                   LEGACY_I2C_DEVICE);
+}
+
+static uint8_t dw_i3c_target_addr(DWI3C *s, uint16_t offset)
+{
+    if (offset > s->cfg.num_addressable_devices) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Device addr table offset %d out of "
+                      "bounds\n", path, offset);
+        /* If we're out of bounds, return an address of 0. */
+        return 0;
+    }
+
+    /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
+    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
+                                          ADDR) / sizeof(uint32_t)) + offset;
+    /* I2C devices use a static address. */
+    if (dw_i3c_target_is_i2c(s, offset)) {
+        return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
+                          DEV_STATIC_ADDR);
+    }
+    return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
+                      DEV_DYNAMIC_ADDR);
+}
+
 static uint32_t dw_i3c_intr_status_r(DWI3C *s)
 {
     /* Only return the status whose corresponding EN bits are set. */
@@ -376,6 +543,56 @@ static void dw_i3c_intr_force_w(DWI3C *s, uint32_t val)
     dw_i3c_update_irq(s);
 }
 
+static uint32_t dw_i3c_pop_rx(DWI3C *s)
+{
+    if (fifo32_is_empty(&s->rx_queue)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read RX FIFO when empty\n",
+                      path);
+        return 0;
+    }
+
+    uint32_t val = fifo32_pop(&s->rx_queue);
+    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR,
+                     fifo32_num_used(&s->rx_queue));
+
+    /* Threshold is 2^RX_BUF_THLD. */
+    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
+                                         RX_BUF_THLD);
+    threshold = dw_i3c_fifo_threshold_from_reg(threshold);
+    if (fifo32_num_used(&s->rx_queue) < threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 0);
+        dw_i3c_update_irq(s);
+    }
+
+    trace_dw_i3c_pop_rx(s->cfg.id, val);
+    return val;
+}
+
+static uint32_t dw_i3c_resp_queue_port_r(DWI3C *s)
+{
+    if (fifo32_is_empty(&s->resp_queue)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read response FIFO when "
+                      "empty\n", path);
+        return 0;
+    }
+
+    uint32_t val = fifo32_pop(&s->resp_queue);
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
+                     fifo32_num_used(&s->resp_queue));
+
+    /* Threshold is the register value + 1. */
+    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+                                         RESP_BUF_THLD) + 1;
+    if (fifo32_num_used(&s->resp_queue) < threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 0);
+        dw_i3c_update_irq(s);
+    }
+
+    return val;
+}
+
 static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
 {
     DWI3C *s = DW_I3C(opaque);
@@ -392,16 +609,635 @@ static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
     case R_INTR_STATUS:
         value = dw_i3c_intr_status_r(s);
         break;
+    case R_RX_TX_DATA_PORT:
+        value = dw_i3c_pop_rx(s);
+        break;
+    case R_RESPONSE_QUEUE_PORT:
+        value = dw_i3c_resp_queue_port_r(s);
+        break;
     default:
         value = s->regs[addr];
         break;
     }
 
-    trace_dw_i3c_read(s->id, offset, value);
+    trace_dw_i3c_read(s->cfg.id, offset, value);
 
     return value;
 }
 
+static void dw_i3c_resp_queue_push(DWI3C *s, uint8_t err, uint8_t tid,
+                                   uint8_t ccc_type, uint16_t data_len)
+{
+    uint32_t val = 0;
+    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, ERR_STATUS, err);
+    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, TID, tid);
+    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, CCCT, ccc_type);
+    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, DL, data_len);
+    if (!fifo32_is_full(&s->resp_queue)) {
+        trace_dw_i3c_resp_queue_push(s->cfg.id, val);
+        fifo32_push(&s->resp_queue, val);
+    }
+
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
+                     fifo32_num_used(&s->resp_queue));
+    /* Threshold is the register value + 1. */
+    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+                                         RESP_BUF_THLD) + 1;
+    if (fifo32_num_used(&s->resp_queue) >= threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 1);
+        dw_i3c_update_irq(s);
+    }
+}
+
+static void dw_i3c_push_tx(DWI3C *s, uint32_t val)
+{
+    if (fifo32_is_full(&s->tx_queue)) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to TX FIFO when "
+                      "full\n", object_get_canonical_path(OBJECT(s)));
+        return;
+    }
+
+    trace_dw_i3c_push_tx(s->cfg.id, val);
+    fifo32_push(&s->tx_queue, val);
+    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC,
+                     fifo32_num_free(&s->tx_queue));
+
+    /* Threshold is 2^TX_BUF_THLD. */
+    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
+                                               TX_BUF_THLD);
+    empty_threshold =
+        dw_i3c_fifo_threshold_from_reg(empty_threshold);
+    if (fifo32_num_free(&s->tx_queue) < empty_threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 0);
+        dw_i3c_update_irq(s);
+    }
+}
+
+static uint32_t dw_i3c_pop_tx(DWI3C *s)
+{
+    if (fifo32_is_empty(&s->tx_queue)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to pop from TX FIFO when "
+                      "empty\n", path);
+        return 0;
+    }
+
+    uint32_t val = fifo32_pop(&s->tx_queue);
+    trace_dw_i3c_pop_tx(s->cfg.id, val);
+    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC,
+                     fifo32_num_free(&s->tx_queue));
+
+    /* Threshold is 2^TX_BUF_THLD. */
+    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
+                                               TX_BUF_THLD);
+    empty_threshold =
+        dw_i3c_fifo_threshold_from_reg(empty_threshold);
+    if (fifo32_num_free(&s->tx_queue) >= empty_threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 1);
+        dw_i3c_update_irq(s);
+    }
+    return val;
+}
+
+static void dw_i3c_push_rx(DWI3C *s, uint32_t val)
+{
+    if (fifo32_is_full(&s->rx_queue)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to RX FIFO when "
+                      "full\n", path);
+        return;
+    }
+    trace_dw_i3c_push_rx(s->cfg.id, val);
+    fifo32_push(&s->rx_queue, val);
+
+    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR,
+                     fifo32_num_used(&s->rx_queue));
+    /* Threshold is 2^RX_BUF_THLD. */
+    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
+                                         RX_BUF_THLD);
+    threshold = dw_i3c_fifo_threshold_from_reg(threshold);
+    if (fifo32_num_used(&s->rx_queue) >= threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 1);
+        dw_i3c_update_irq(s);
+    }
+}
+
+static void dw_i3c_short_transfer(DWI3C *s, DWI3CTransferCmd cmd,
+                                  DWI3CShortArg arg)
+{
+    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
+    uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
+    bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index);
+    uint8_t data[4]; /* Max we can send on a short transfer is 4 bytes. */
+    uint8_t len = 0;
+    uint32_t bytes_sent; /* Ignored on short transfers. */
+
+    /* Can't do reads on a short transfer. */
+    if (cmd.rnw) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot do a read on a short "
+                      "transfer\n", path);
+        return;
+    }
+
+    if (dw_i3c_send_start(s, addr, /*is_recv=*/false, is_i2c)) {
+        err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+        goto transfer_done;
+    }
+
+    /* Are we sending a command? */
+    if (cmd.cp) {
+        data[len] = cmd.cmd;
+        len++;
+        /*
+         * byte0 is the defining byte for a command, and is only sent if a
+         * command is present and if the command has a defining byte present.
+         * (byte_strb & 0x01) is always treated as set by the controller, and is
+         * ignored.
+         */
+        if (cmd.dbp) {
+            data[len] += arg.byte0;
+            len++;
+        }
+    }
+
+    /* Send the bytes passed in the argument. */
+    if (arg.byte_strb & 0x02) {
+        data[len] = arg.byte1;
+        len++;
+    }
+    if (arg.byte_strb & 0x04) {
+        data[len] = arg.byte2;
+        len++;
+    }
+
+    if (dw_i3c_send(s, data, len, &bytes_sent, is_i2c)) {
+        err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+    } else {
+        /* Only go to an idle state on a successful transfer. */
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                         DW_I3C_TRANSFER_STATE_IDLE);
+    }
+
+transfer_done:
+    if (cmd.toc) {
+        dw_i3c_end_transfer(s, is_i2c);
+    }
+    if (cmd.roc) {
+        /*
+         * ccc_type is always 0 in controller mode, data_len is 0 in short
+         * transfers.
+         */
+        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
+                                          /*data_len=*/0);
+    }
+}
+
+/* Returns number of bytes transmitted. */
+static uint16_t dw_i3c_tx(DWI3C *s, uint16_t num, bool is_i2c)
+{
+    uint16_t bytes_sent = 0;
+    union {
+        uint8_t b[sizeof(uint32_t)];
+        uint32_t val;
+    } val32;
+
+    while (bytes_sent < num) {
+        val32.val = dw_i3c_pop_tx(s);
+        for (uint8_t i = 0; i < sizeof(val32.val); i++) {
+            if (dw_i3c_send_byte(s, val32.b[i], is_i2c)) {
+                return bytes_sent;
+            }
+            bytes_sent++;
+
+            /* We're not sending the full 32-bits, break early. */
+            if (bytes_sent >= num) {
+                break;
+            }
+        }
+    }
+
+    return bytes_sent;
+}
+
+/* Returns number of bytes received. */
+static uint16_t dw_i3c_rx(DWI3C *s, uint16_t num, bool is_i2c)
+{
+    /*
+     * Allocate a temporary buffer to read data from the target.
+     * Zero it and word-align it as well in case we're reading unaligned data.
+     */
+    g_autofree uint8_t *data = g_new0(uint8_t, num + (4 - (num & 0x03)));
+    uint32_t *data32 = (uint32_t *)data;
+    /*
+     * 32-bits since the I3C API wants a 32-bit number, even though the
+     * controller can only do 16-bit transfers.
+     */
+    uint32_t num_read = 0;
+
+    /* Can NACK if the target receives an unsupported CCC. */
+    if (dw_i3c_recv_data(s, is_i2c, data, num, &num_read)) {
+        return 0;
+    }
+
+    for (uint16_t i = 0; i < num_read / 4; i++) {
+        dw_i3c_push_rx(s, *data32);
+        data32++;
+    }
+    /*
+     * If we're pushing data that isn't 32-bit aligned, push what's left.
+     * It's software's responsibility to know what bits are valid in the partial
+     * data.
+     */
+    if (num_read & 0x03) {
+        dw_i3c_push_rx(s, *data32);
+    }
+
+    return num_read;
+}
+
+static int dw_i3c_transfer_ccc(DWI3C *s, DWI3CTransferCmd cmd,
+                               DWI3CTransferArg arg)
+{
+    /* CCC start is always a write. CCCs cannot be done on I2C devices. */
+    if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
+                                     /*is_i2c=*/false)) {
+        return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+    }
+    trace_dw_i3c_transfer_ccc(s->cfg.id, cmd.cmd);
+    if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) {
+        return DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+    }
+
+    /* On a direct CCC, we do a restart and then send the target's address. */
+    if (CCC_IS_DIRECT(cmd.cmd)) {
+        bool is_recv = cmd.rnw;
+        uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
+        if (dw_i3c_send_start(s, addr, is_recv, /*is_i2c=*/false)) {
+            return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+        }
+    }
+
+    return DW_I3C_RESP_QUEUE_ERR_NONE;
+}
+
+static void dw_i3c_transfer(DWI3C *s, DWI3CTransferCmd cmd,
+                            DWI3CTransferArg arg)
+{
+    bool is_recv = cmd.rnw;
+    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
+    uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
+    bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index);
+    uint16_t bytes_transferred = 0;
+
+    if (cmd.cp) {
+        /* We're sending a CCC. */
+        err = dw_i3c_transfer_ccc(s, cmd, arg);
+        if (err != DW_I3C_RESP_QUEUE_ERR_NONE) {
+            goto transfer_done;
+        }
+    } else {
+        if (ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_BROADCAST_ADDR_INC) &&
+            is_i2c == false) {
+            if (dw_i3c_send_start(s, I3C_BROADCAST,
+                                             /*is_recv=*/false, is_i2c)) {
+                err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+                goto transfer_done;
+            }
+        }
+        /* Otherwise we're doing a private transfer. */
+        if (dw_i3c_send_start(s, addr, is_recv, is_i2c)) {
+            err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+            goto transfer_done;
+        }
+    }
+
+    if (is_recv) {
+        bytes_transferred = dw_i3c_rx(s, arg.data_len, is_i2c);
+    } else {
+        bytes_transferred = dw_i3c_tx(s, arg.data_len, is_i2c);
+    }
+
+    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                     DW_I3C_TRANSFER_STATE_IDLE);
+
+transfer_done:
+    if (cmd.toc) {
+        dw_i3c_end_transfer(s, is_i2c);
+    }
+    if (cmd.roc) {
+        /*
+         * data_len is the number of bytes that still need to be TX'd, or the
+         * number of bytes RX'd.
+         */
+        uint16_t data_len = is_recv ? bytes_transferred : arg.data_len -
+                                                          bytes_transferred;
+        /* CCCT is always 0 in controller mode. */
+        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
+                                          data_len);
+    }
+
+    dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_transfer_cmd(DWI3C *s, DWI3CTransferCmd cmd,
+                                DWI3CCmdQueueData arg)
+{
+    uint8_t arg_attr = FIELD_EX32(arg.word, COMMAND_QUEUE_PORT, CMD_ATTR);
+
+    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CMD_TID, cmd.tid);
+
+    /* User is trying to do HDR transfers, see if we can do them. */
+    if (cmd.speed == 0x06 && !dw_i3c_has_hdr_ddr(s)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR DDR is not supported\n", path);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                         DW_I3C_TRANSFER_STATE_HALT);
+        return;
+    }
+    if (cmd.speed == 0x05 && !dw_i3c_has_hdr_ts(s)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR TS is not supported\n", path);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                         DW_I3C_TRANSFER_STATE_HALT);
+        return;
+    }
+
+    if (arg_attr == DW_I3C_CMD_ATTR_TRANSFER_ARG) {
+        dw_i3c_transfer(s, cmd, arg.transfer_arg);
+    } else if (arg_attr == DW_I3C_CMD_ATTR_SHORT_DATA_ARG) {
+        dw_i3c_short_transfer(s, cmd, arg.short_arg);
+    } else {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Unknown command queue cmd_attr 0x%x"
+                      "\n", path, arg_attr);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                         DW_I3C_TRANSFER_STATE_HALT);
+    }
+}
+
+static void dw_i3c_update_char_table(DWI3C *s, uint8_t offset, uint64_t pid,
+                                     uint8_t bcr, uint8_t dcr, uint8_t addr)
+{
+    if (offset > s->cfg.num_addressable_devices) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Device char table offset %d out of "
+                      "bounds\n", path, offset);
+        /* If we're out of bounds, do nothing. */
+        return;
+    }
+
+    /*
+     * Each device offset is 128 bits apart in the table, since each device gets
+     * 4 * 32-bits of entries in the table.
+     * / sizeof(uint32_t) because we're indexing into our 32-bit reg array.
+     */
+    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
+                                          P_DEV_CHAR_TABLE_START_ADDR) /
+                                          sizeof(uint32_t)) +
+                                          (offset * sizeof(uint32_t));
+    s->regs[dev_index] = pid & 0xffffffff;
+    pid >>= 32;
+    s->regs[dev_index + 1] = FIELD_DP32(s->regs[dev_index + 1],
+                                        DEVICE_CHARACTERISTIC_TABLE_LOC2,
+                                        MSB_PID, pid);
+    s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2],
+                                        DEVICE_CHARACTERISTIC_TABLE_LOC3, DCR,
+                                        dcr);
+    s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2],
+                                        DEVICE_CHARACTERISTIC_TABLE_LOC3, BCR,
+                                        bcr);
+    s->regs[dev_index + 3] = FIELD_DP32(s->regs[dev_index + 3],
+                                        DEVICE_CHARACTERISTIC_TABLE_LOC4,
+                                        DEV_DYNAMIC_ADDR, addr);
+
+    /* Increment PRESENT_DEV_CHAR_TABLE_INDEX. */
+    uint8_t idx = ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
+                     PRESENT_DEV_CHAR_TABLE_INDEX);
+    /* Increment and rollover. */
+    idx++;
+    if (idx >= ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
+                               DEV_CHAR_TABLE_DEPTH) / 4) {
+        idx = 0;
+    }
+    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
+                     PRESENT_DEV_CHAR_TABLE_INDEX, idx);
+}
+
+static void dw_i3c_addr_assign_cmd(DWI3C *s, DWI3CAddrAssignCmd cmd)
+{
+    uint8_t i = 0;
+    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
+
+    /* Tell everyone to ENTDAA. If these error, no one is on the bus. */
+    if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
+                                     /*is_i2c=*/false)) {
+        err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+        goto transfer_done;
+    }
+    if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) {
+        err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+        goto transfer_done;
+    }
+
+    /* Go through each device in the table and assign it an address. */
+    for (i = 0; i < cmd.dev_count; i++) {
+        uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index + i);
+        union {
+            uint64_t pid:48;
+            uint8_t bcr;
+            uint8_t dcr;
+            uint32_t w[2];
+            uint8_t b[8];
+        } target_info;
+
+        /* If this fails, there was no one left to ENTDAA. */
+        if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
+                                         /*is_i2c=*/false)) {
+            err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+            break;
+        }
+
+        /*
+         * In ENTDAA, we read 8 bytes from the target, which will be the
+         * target's PID, BCR, and DCR. After that, we send it the dynamic
+         * address.
+         * Don't bother checking the number of bytes received, it must send 8
+         * bytes during ENTDAA.
+         */
+        uint32_t num_read;
+        if (dw_i3c_recv_data(s, /*is_i2c=*/false, target_info.b,
+                                        I3C_ENTDAA_SIZE, &num_read)) {
+            g_autofree char *path = object_get_canonical_path(OBJECT(s));
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed ENTDAA CCC\n",
+                          path);
+            err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK;
+            goto transfer_done;
+        }
+        if (dw_i3c_send_byte(s, addr, /*is_i2c=*/false)) {
+            g_autofree char *path = object_get_canonical_path(OBJECT(s));
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed addr 0x%.2x "
+                          "during ENTDAA\n", path, addr);
+            err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK;
+            break;
+        }
+        dw_i3c_update_char_table(s, cmd.dev_index + i,
+                                            target_info.pid, target_info.bcr,
+                                            target_info.dcr, addr);
+
+        /* Push the PID, BCR, and DCR to the RX queue. */
+        dw_i3c_push_rx(s, target_info.w[0]);
+        dw_i3c_push_rx(s, target_info.w[1]);
+    }
+
+transfer_done:
+    /* Do we send a STOP? */
+    if (cmd.toc) {
+        dw_i3c_end_transfer(s, /*is_i2c=*/false);
+    }
+    /*
+     * For addr assign commands, the length field is the number of devices
+     * left to assign. CCCT is always 0 in controller mode.
+     */
+    if (cmd.roc) {
+        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
+                                         cmd.dev_count - i);
+    }
+}
+
+static uint32_t dw_i3c_cmd_queue_pop(DWI3C *s)
+{
+    if (fifo32_is_empty(&s->cmd_queue)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to dequeue command queue "
+                      "when it was empty\n", path);
+        return 0;
+    }
+    uint32_t val = fifo32_pop(&s->cmd_queue);
+
+    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+                                               CMD_BUF_EMPTY_THLD);
+    uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs,
+                                                   QUEUE_STATUS_LEVEL,
+                                                   CMD_QUEUE_EMPTY_LOC);
+    cmd_queue_empty_loc++;
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC,
+                     cmd_queue_empty_loc);
+    if (cmd_queue_empty_loc >= empty_threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 1);
+        dw_i3c_update_irq(s);
+    }
+
+    return val;
+}
+
+static void dw_i3c_cmd_queue_execute(DWI3C *s)
+{
+    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                     DW_I3C_TRANSFER_STATE_IDLE);
+    if (!dw_i3c_can_transmit(s)) {
+        return;
+    }
+
+    /*
+     * We only start executing when a command is passed into the FIFO.
+     * We expect there to be a multiple of 2 items in the queue. The first item
+     * should be an argument to a command, and the command should be the second
+     * item.
+     */
+    if (fifo32_num_used(&s->cmd_queue) & 1) {
+        return;
+    }
+
+    while (!fifo32_is_empty(&s->cmd_queue)) {
+        DWI3CCmdQueueData arg;
+        arg.word = dw_i3c_cmd_queue_pop(s);
+        DWI3CCmdQueueData cmd;
+        cmd.word = dw_i3c_cmd_queue_pop(s);
+        trace_dw_i3c_cmd_queue_execute(s->cfg.id, cmd.word, arg.word);
+
+        uint8_t cmd_attr = FIELD_EX32(cmd.word, COMMAND_QUEUE_PORT, CMD_ATTR);
+        switch (cmd_attr) {
+        case DW_I3C_CMD_ATTR_TRANSFER_CMD:
+            dw_i3c_transfer_cmd(s, cmd.transfer_cmd, arg);
+            break;
+        case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD:
+            /* Arg is discarded for addr assign commands. */
+            dw_i3c_addr_assign_cmd(s, cmd.addr_assign_cmd);
+            break;
+        case DW_I3C_CMD_ATTR_TRANSFER_ARG:
+        case DW_I3C_CMD_ATTR_SHORT_DATA_ARG:
+            {
+                g_autofree char *path = object_get_canonical_path(OBJECT(s));
+                qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received "
+                              "argument packet when it expected a command "
+                              "packet\n", path);
+            }
+            break;
+        default:
+            /*
+             * The caller's check before queueing an item should prevent this
+             * from happening.
+             */
+            g_assert_not_reached();
+            break;
+        }
+    }
+}
+
+static void dw_i3c_cmd_queue_push(DWI3C *s, uint32_t val)
+{
+    if (fifo32_is_full(&s->cmd_queue)) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received packet when "
+                      "already full\n", path);
+        return;
+    }
+    trace_dw_i3c_cmd_queue_push(s->cfg.id, val);
+    fifo32_push(&s->cmd_queue, val);
+
+    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+                                               CMD_BUF_EMPTY_THLD);
+    uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs,
+                                                   QUEUE_STATUS_LEVEL,
+                                                   CMD_QUEUE_EMPTY_LOC);
+    if (cmd_queue_empty_loc) {
+        cmd_queue_empty_loc--;
+        ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC,
+                         cmd_queue_empty_loc);
+    }
+    if (cmd_queue_empty_loc < empty_threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 0);
+        dw_i3c_update_irq(s);
+    }
+}
+
+static void dw_i3c_cmd_queue_port_w(DWI3C *s, uint32_t val)
+{
+    uint8_t cmd_attr = FIELD_EX32(val, COMMAND_QUEUE_PORT, CMD_ATTR);
+
+    switch (cmd_attr) {
+    /* If a command is received we can start executing it. */
+    case DW_I3C_CMD_ATTR_TRANSFER_CMD:
+    case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD:
+        dw_i3c_cmd_queue_push(s, val);
+        dw_i3c_cmd_queue_execute(s);
+        break;
+    /* If we get an argument just push it. */
+    case DW_I3C_CMD_ATTR_TRANSFER_ARG:
+    case DW_I3C_CMD_ATTR_SHORT_DATA_ARG:
+        dw_i3c_cmd_queue_push(s, val);
+        break;
+    default:
+        {
+            g_autofree char *path = object_get_canonical_path(OBJECT(s));
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received packet "
+                          "with unknown cmd attr 0x%x\n", path, cmd_attr);
+        }
+        break;
+    }
+}
+
 static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
                          unsigned size)
 {
@@ -409,7 +1245,7 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
     uint32_t addr = offset >> 2;
     uint32_t val32 = (uint32_t)value;
 
-    trace_dw_i3c_write(s->id, offset, value);
+    trace_dw_i3c_write(s->cfg.id, offset, value);
 
     val32 &= ~dw_i3c_ro[addr];
     switch (addr) {
@@ -433,6 +1269,10 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
                       __func__, offset, value);
         break;
     case R_RX_TX_DATA_PORT:
+        dw_i3c_push_tx(s, val32);
+        break;
+    case R_COMMAND_QUEUE_PORT:
+        dw_i3c_cmd_queue_port_w(s, val32);
         break;
     case R_RESET_CTRL:
         break;
@@ -475,22 +1315,56 @@ static void dw_i3c_reset_enter(Object *obj, ResetType type)
     DWI3C *s = DW_I3C(obj);
 
     memcpy(s->regs, dw_i3c_resets, sizeof(s->regs));
+    /*
+     * The user config for these may differ from our resets array, set them
+     * manually.
+     */
+    ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, ADDR,
+                     s->cfg.dev_addr_table_pointer);
+    ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, DEPTH,
+                     s->cfg.dev_addr_table_depth);
+    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
+                     P_DEV_CHAR_TABLE_START_ADDR,
+                     s->cfg.dev_char_table_pointer);
+    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, DEV_CHAR_TABLE_DEPTH,
+                     s->cfg.dev_char_table_depth);
 }
 
 static void dw_i3c_realize(DeviceState *dev, Error **errp)
 {
     DWI3C *s = DW_I3C(dev);
-    g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->id);
+    g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->cfg.id);
 
     sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
 
     memory_region_init_io(&s->mr, OBJECT(s), &dw_i3c_ops, s, name,
                           DW_I3C_NR_REGS << 2);
     sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr);
+
+    fifo32_create(&s->cmd_queue, s->cfg.cmd_resp_queue_capacity_bytes);
+    fifo32_create(&s->resp_queue, s->cfg.cmd_resp_queue_capacity_bytes);
+    fifo32_create(&s->tx_queue, s->cfg.tx_rx_queue_capacity_bytes);
+    fifo32_create(&s->rx_queue, s->cfg.tx_rx_queue_capacity_bytes);
+
+    s->bus = i3c_init_bus(DEVICE(s), name);
 }
 
 static const Property dw_i3c_properties[] = {
-    DEFINE_PROP_UINT8("device-id", DWI3C, id, 0),
+    DEFINE_PROP_UINT8("device-id", DWI3C, cfg.id, 0),
+    DEFINE_PROP_UINT8("command-response-queue-capacity-bytes", DWI3C,
+                      cfg.cmd_resp_queue_capacity_bytes, 0x10),
+    DEFINE_PROP_UINT16("tx-rx-queue-capacity-bytes", DWI3C,
+                      cfg.tx_rx_queue_capacity_bytes, 0x40),
+    DEFINE_PROP_UINT8("num-addressable-devices", DWI3C,
+                      cfg.num_addressable_devices, 8),
+    DEFINE_PROP_UINT16("dev-addr-table-pointer", DWI3C,
+                       cfg.dev_addr_table_pointer, 0x280),
+    DEFINE_PROP_UINT16("dev-addr-table-depth", DWI3C,
+                       cfg.dev_addr_table_depth, 0x08),
+    DEFINE_PROP_UINT16("dev-char-table-pointer", DWI3C,
+                       cfg.dev_char_table_pointer, 0x200),
+    DEFINE_PROP_UINT16("dev-char-table-depth", DWI3C,
+                       cfg.dev_char_table_depth, 0x20),
 };
 
 static void dw_i3c_class_init(ObjectClass *klass, const void *data)
diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
index 2d944387db..044ff06a01 100644
--- a/hw/i3c/trace-events
+++ b/hw/i3c/trace-events
@@ -7,6 +7,16 @@ aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64
 # dw-i3c,c
 dw_i3c_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
 dw_i3c_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
+dw_i3c_send(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] send %" PRId32 " bytes to bus"
+dw_i3c_recv_data(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] recv %" PRId32 " bytes from bus"
+dw_i3c_pop_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from RX FIFO"
+dw_i3c_resp_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to response queue"
+dw_i3c_push_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to TX FIFO"
+dw_i3c_pop_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from TX FIFO"
+dw_i3c_push_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to RX FIFO"
+dw_i3c_transfer_ccc(uint32_t deviceid, uint8_t ccc) "I3C Dev[%u] transfer CCC 0x%" PRIx8
+dw_i3c_cmd_queue_execute(uint32_t deviceid, uint32_t cmd, uint32_t arg) "I3C Dev[%u] execute command 0x%" PRIx32 " arg 0x%" PRIx32
+dw_i3c_cmd_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to cmd queue"
 
 # core.c
 i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " event 0x%" PRIx8
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 14/22] hw/i3c/dw-i3c: Add IBI handling
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (12 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 15/22] hw/i3c/dw-i3c: Add ctrl MMIO handling Jamin Lin
                   ` (9 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture, Stephen Longfield

Adds handling for different IBI events that the controller can receive.
This includes:
- Handling a hot-join from a target
- Handling a secondary controller on the bus requesting to be the
  primary bus controller
- Handling an interrupt request from a target.

When receiving an IBI, the controller sets an interrupt to notify
software about what happened.
When the IBI is finished being serviced, the controller pushes the
result of the IBI and any data received from the target into the IBI
queue.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Reviewed-by: Stephen Longfield <slongfield@google.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 include/hw/i3c/dw-i3c.h |  27 ++++
 hw/i3c/dw-i3c.c         | 317 ++++++++++++++++++++++++++++++++++++++++
 hw/i3c/trace-events     |   2 +
 3 files changed, 346 insertions(+)

diff --git a/include/hw/i3c/dw-i3c.h b/include/hw/i3c/dw-i3c.h
index c50d67cc6b..d26f60580f 100644
--- a/include/hw/i3c/dw-i3c.h
+++ b/include/hw/i3c/dw-i3c.h
@@ -141,6 +141,28 @@ typedef union DWI3CCmdQueueData {
     DWI3CAddrAssignCmd addr_assign_cmd;
 } DWI3CCmdQueueData;
 
+/*
+ * When we receive an IBI with data, we need to store it temporarily until
+ * the target is finished sending data. Then we can set the IBI queue status
+ * appropriately.
+ */
+typedef struct DWI3CIBIData {
+    /* Do we notify the user that an IBI was NACKed? */
+    bool notify_ibi_nack;
+    /* Intermediate storage of IBI_QUEUE_STATUS. */
+    uint32_t ibi_queue_status;
+    /* Temporary buffer to store IBI data from the target. */
+    Fifo8 ibi_intermediate_queue;
+    /* The address we should send a CCC_DISEC to. */
+    uint8_t disec_addr;
+    /* The byte we should send along with the CCC_DISEC. */
+    uint8_t disec_byte;
+    /* Should we send a direct DISEC CCC? (As opposed to global). */
+    bool send_direct_disec;
+    /* Was this IBI NACKed? */
+    bool ibi_nacked;
+} DWI3CIBIData;
+
 struct DWI3C {
     SysBusDevice parent_obj;
 
@@ -152,11 +174,16 @@ struct DWI3C {
     Fifo32 resp_queue;
     Fifo32 tx_queue;
     Fifo32 rx_queue;
+    Fifo32 ibi_queue;
+
+    /* Temporary storage for IBI data. */
+    DWI3CIBIData ibi_data;
 
     struct {
         uint8_t id;
         uint8_t cmd_resp_queue_capacity_bytes;
         uint16_t tx_rx_queue_capacity_bytes;
+        uint8_t ibi_queue_capacity_bytes;
         uint8_t num_addressable_devices;
         uint16_t dev_addr_table_pointer;
         uint16_t dev_addr_table_depth;
diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 2453d74a8c..832fcf326a 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -19,6 +19,14 @@
 #include "trace.h"
 #include "hw/core/irq.h"
 
+/*
+ * Disable event command values. sent along with a DISEC CCC to disable certain
+ * events on targets.
+ */
+#define DISEC_HJ 0x08
+#define DISEC_CR 0x02
+#define DISEC_INT 0x01
+
 REG32(DEVICE_CTRL,                  0x00)
     FIELD(DEVICE_CTRL, I3C_BROADCAST_ADDR_INC,    0, 1)
     FIELD(DEVICE_CTRL, I2C_SLAVE_PRESENT,         7, 1)
@@ -356,6 +364,23 @@ static inline bool dw_i3c_can_transmit(DWI3C *s)
            !ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_RESUME);
 }
 
+static inline uint8_t dw_i3c_ibi_slice_size(DWI3C *s)
+{
+    uint8_t ibi_slice_size = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+                                              IBI_DATA_THLD);
+    /* The minimum supported slice size is 4 bytes. */
+    if (ibi_slice_size == 0) {
+        ibi_slice_size = 1;
+    }
+    ibi_slice_size *= sizeof(uint32_t);
+    /* maximum supported size is 63 bytes. */
+    if (ibi_slice_size >= 64) {
+        ibi_slice_size = 63;
+    }
+
+    return ibi_slice_size;
+}
+
 static inline uint8_t dw_i3c_fifo_threshold_from_reg(uint8_t regval)
 {
     return regval = regval ? (2 << regval) : 1;
@@ -509,6 +534,266 @@ static uint8_t dw_i3c_target_addr(DWI3C *s, uint16_t offset)
                       DEV_DYNAMIC_ADDR);
 }
 
+static int dw_i3c_addr_table_index_from_addr(DWI3C *s, uint8_t addr)
+{
+    uint8_t table_size = ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
+                                          DEPTH);
+    for (uint8_t i = 0; i < table_size; i++) {
+        if (dw_i3c_target_addr(s, i) == addr) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+static void dw_i3c_send_disec(DWI3C *s)
+{
+    uint8_t ccc = I3C_CCC_DISEC;
+    if (s->ibi_data.send_direct_disec) {
+        ccc = I3C_CCCD_DISEC;
+    }
+
+    dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
+                                 /*is_i2c=*/false);
+    dw_i3c_send_byte(s, ccc, /*is_i2c=*/false);
+    if (s->ibi_data.send_direct_disec) {
+        dw_i3c_send_start(s, s->ibi_data.disec_addr,
+                                     /*is_recv=*/false, /*is_i2c=*/false);
+    }
+    dw_i3c_send_byte(s, s->ibi_data.disec_byte, /*is_i2c=*/false);
+}
+
+static int dw_i3c_handle_hj(DWI3C *s)
+{
+    if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_HOT_JOIN)) {
+        s->ibi_data.notify_ibi_nack = true;
+    }
+
+    bool nack_and_disable = ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL,
+                                             HOT_JOIN_ACK_NACK_CTRL);
+    if (nack_and_disable) {
+        s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
+                                                  IBI_QUEUE_STATUS,
+                                                  IBI_STATUS, 1);
+        s->ibi_data.ibi_nacked = true;
+        s->ibi_data.disec_byte = DISEC_HJ;
+        return -1;
+    }
+    return 0;
+}
+
+static int dw_i3c_handle_ctlr_req(DWI3C *s, uint8_t addr)
+{
+    if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_MASTER_REQ)) {
+        s->ibi_data.notify_ibi_nack = true;
+    }
+
+    int table_offset = dw_i3c_addr_table_index_from_addr(s, addr);
+    /* Doesn't exist in the table, NACK it, don't DISEC. */
+    if (table_offset < 0) {
+        return -1;
+    }
+
+    /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
+    table_offset += (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
+                                      ADDR) / sizeof(uint32_t));
+    if (FIELD_EX32(s->regs[table_offset], DEVICE_ADDR_TABLE_LOC1, MR_REJECT)) {
+        s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
+                                                  IBI_QUEUE_STATUS,
+                                                  IBI_STATUS, 1);
+        s->ibi_data.ibi_nacked = true;
+        s->ibi_data.disec_addr = addr;
+        /* Tell the requester to disable controller role requests. */
+        s->ibi_data.disec_byte = DISEC_CR;
+        s->ibi_data.send_direct_disec = true;
+        return -1;
+    }
+    return 0;
+}
+
+static int dw_i3c_handle_targ_irq(DWI3C *s, uint8_t addr)
+{
+    if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_SLAVE_IRQ)) {
+        s->ibi_data.notify_ibi_nack = true;
+    }
+
+    int table_offset = dw_i3c_addr_table_index_from_addr(s, addr);
+    /* Doesn't exist in the table, NACK it, don't DISEC. */
+    if (table_offset < 0) {
+        return -1;
+    }
+
+    /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
+    table_offset += (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
+                                      ADDR) / sizeof(uint32_t));
+    if (FIELD_EX32(s->regs[table_offset], DEVICE_ADDR_TABLE_LOC1, SIR_REJECT)) {
+        s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
+                                                  IBI_QUEUE_STATUS,
+                                                  IBI_STATUS, 1);
+        s->ibi_data.ibi_nacked = true;
+        s->ibi_data.disec_addr = addr;
+        /* Tell the requester to disable interrupts. */
+        s->ibi_data.disec_byte = DISEC_INT;
+        s->ibi_data.send_direct_disec = true;
+        return -1;
+    }
+    return 0;
+}
+
+static int dw_i3c_ibi_handle(I3CBus *bus, uint8_t addr, bool is_recv)
+{
+    DWI3C *s = DW_I3C(bus->parent_obj.parent);
+
+    trace_dw_i3c_ibi_handle(s->cfg.id, addr, is_recv);
+    s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
+                                              IBI_QUEUE_STATUS, IBI_ID,
+                                              (addr << 1) | is_recv);
+    /* Is this a hot join request? */
+    if (addr == I3C_HJ_ADDR) {
+        return dw_i3c_handle_hj(s);
+    }
+    /* Is secondary controller requesting access? */
+    if (!is_recv) {
+        return dw_i3c_handle_ctlr_req(s, addr);
+    }
+    /* Is this a target IRQ? */
+    if (is_recv) {
+        return dw_i3c_handle_targ_irq(s, addr);
+    }
+
+    /* At this point the IBI should have been ACKed or NACKed. */
+    g_assert_not_reached();
+    return -1;
+}
+
+static int dw_i3c_ibi_recv(I3CBus *bus, uint8_t data)
+{
+    DWI3C *s = DW_I3C(bus->parent_obj.parent);
+    if (fifo8_is_full(&s->ibi_data.ibi_intermediate_queue)) {
+        return -1;
+    }
+
+    fifo8_push(&s->ibi_data.ibi_intermediate_queue, data);
+    trace_dw_i3c_ibi_recv(s->cfg.id, data);
+    return 0;
+}
+
+static void dw_i3c_ibi_queue_push(DWI3C *s)
+{
+    /* Stored value is in 32-bit chunks, convert it to byte chunks. */
+    uint8_t ibi_slice_size = dw_i3c_ibi_slice_size(s);
+    uint8_t num_slices = (fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) /
+                         ibi_slice_size) +
+                         ((fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) %
+                         ibi_slice_size) ? 1 : 0);
+    uint8_t ibi_status_count = num_slices;
+    union {
+        uint8_t b[sizeof(uint32_t)];
+        uint32_t val32;
+    } ibi_data = {
+        .val32 = 0
+    };
+
+    /* The report was suppressed, do nothing. */
+    if (s->ibi_data.ibi_nacked && !s->ibi_data.notify_ibi_nack) {
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                         DW_I3C_TRANSFER_STATE_IDLE);
+        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+                         DW_I3C_TRANSFER_STATUS_IDLE);
+        return;
+    }
+
+    /* If we don't have any slices to push, just push the status. */
+    if (num_slices == 0) {
+        s->ibi_data.ibi_queue_status =
+             FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
+                        LAST_STATUS, 1);
+        fifo32_push(&s->ibi_queue, s->ibi_data.ibi_queue_status);
+        ibi_status_count = 1;
+    }
+
+    for (uint8_t i = 0; i < num_slices; i++) {
+        /* If this is the last slice, set LAST_STATUS. */
+        if (fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) <
+            ibi_slice_size) {
+            s->ibi_data.ibi_queue_status =
+                FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
+                           IBI_DATA_LEN,
+                           fifo8_num_used(&s->ibi_data.ibi_intermediate_queue));
+            s->ibi_data.ibi_queue_status =
+                FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
+                           LAST_STATUS, 1);
+        } else {
+            s->ibi_data.ibi_queue_status =
+                FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
+                           IBI_DATA_LEN, ibi_slice_size);
+        }
+
+        /* Push the IBI status header. */
+        fifo32_push(&s->ibi_queue, s->ibi_data.ibi_queue_status);
+        /* Move each IBI byte into a 32-bit word and push it into the queue. */
+        for (uint8_t j = 0; j < ibi_slice_size; ++j) {
+            if (fifo8_is_empty(&s->ibi_data.ibi_intermediate_queue)) {
+                break;
+            }
+
+            ibi_data.b[j & 3] = fifo8_pop(&s->ibi_data.ibi_intermediate_queue);
+            /* We have 32-bits, push it to the IBI FIFO. */
+            if ((j & 0x03) == 0x03) {
+                fifo32_push(&s->ibi_queue, ibi_data.val32);
+                ibi_data.val32 = 0;
+            }
+        }
+        /* If the data isn't 32-bit aligned, push the leftover bytes. */
+        if (ibi_slice_size & 0x03) {
+            fifo32_push(&s->ibi_queue, ibi_data.val32);
+        }
+
+        /* Clear out the data length for the next iteration. */
+        s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
+                                         IBI_QUEUE_STATUS, IBI_DATA_LEN, 0);
+    }
+
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR,
+                     fifo32_num_used(&s->ibi_queue));
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_STATUS_CNT,
+                     ibi_status_count);
+    /* Threshold is the register value + 1. */
+    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+                                         IBI_STATUS_THLD) + 1;
+    if (fifo32_num_used(&s->ibi_queue) >= threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 1);
+        dw_i3c_update_irq(s);
+    }
+
+    /* State update. */
+    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                     DW_I3C_TRANSFER_STATE_IDLE);
+    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+                     DW_I3C_TRANSFER_STATUS_IDLE);
+}
+
+static int dw_i3c_ibi_finish(I3CBus *bus)
+{
+    DWI3C *s = DW_I3C(bus->parent_obj.parent);
+    bool nack_and_disable_hj = ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL,
+                                                HOT_JOIN_ACK_NACK_CTRL);
+    if (nack_and_disable_hj || s->ibi_data.send_direct_disec) {
+        dw_i3c_send_disec(s);
+    }
+    dw_i3c_ibi_queue_push(s);
+
+    /* Clear out the intermediate values. */
+    s->ibi_data.ibi_queue_status = 0;
+    s->ibi_data.disec_addr = 0;
+    s->ibi_data.disec_byte = 0;
+    s->ibi_data.send_direct_disec = false;
+    s->ibi_data.notify_ibi_nack = false;
+    s->ibi_data.ibi_nacked = false;
+
+    return 0;
+}
+
 static uint32_t dw_i3c_intr_status_r(DWI3C *s)
 {
     /* Only return the status whose corresponding EN bits are set. */
@@ -569,6 +854,25 @@ static uint32_t dw_i3c_pop_rx(DWI3C *s)
     return val;
 }
 
+static uint32_t dw_i3c_ibi_queue_r(DWI3C *s)
+{
+    if (fifo32_is_empty(&s->ibi_queue)) {
+        return 0;
+    }
+
+    uint32_t val = fifo32_pop(&s->ibi_queue);
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR,
+                     fifo32_num_used(&s->ibi_queue));
+    /* Threshold is the register value + 1. */
+    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+                                         IBI_STATUS_THLD) + 1;
+    if (fifo32_num_used(&s->ibi_queue) < threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 0);
+        dw_i3c_update_irq(s);
+    }
+    return val;
+}
+
 static uint32_t dw_i3c_resp_queue_port_r(DWI3C *s)
 {
     if (fifo32_is_empty(&s->resp_queue)) {
@@ -606,6 +910,9 @@ static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
     case R_INTR_FORCE:
         value = 0;
         break;
+    case R_IBI_QUEUE_DATA:
+        value = dw_i3c_ibi_queue_r(s);
+        break;
     case R_INTR_STATUS:
         value = dw_i3c_intr_status_r(s);
         break;
@@ -1345,8 +1652,16 @@ static void dw_i3c_realize(DeviceState *dev, Error **errp)
     fifo32_create(&s->resp_queue, s->cfg.cmd_resp_queue_capacity_bytes);
     fifo32_create(&s->tx_queue, s->cfg.tx_rx_queue_capacity_bytes);
     fifo32_create(&s->rx_queue, s->cfg.tx_rx_queue_capacity_bytes);
+    fifo32_create(&s->ibi_queue, s->cfg.ibi_queue_capacity_bytes);
+    /* Arbitrarily large enough to not be an issue. */
+    fifo8_create(&s->ibi_data.ibi_intermediate_queue,
+                 s->cfg.ibi_queue_capacity_bytes * 8);
 
     s->bus = i3c_init_bus(DEVICE(s), name);
+    I3CBusClass *bc = I3C_BUS_GET_CLASS(s->bus);
+    bc->ibi_handle = dw_i3c_ibi_handle;
+    bc->ibi_recv = dw_i3c_ibi_recv;
+    bc->ibi_finish = dw_i3c_ibi_finish;
 }
 
 static const Property dw_i3c_properties[] = {
@@ -1355,6 +1670,8 @@ static const Property dw_i3c_properties[] = {
                       cfg.cmd_resp_queue_capacity_bytes, 0x10),
     DEFINE_PROP_UINT16("tx-rx-queue-capacity-bytes", DWI3C,
                       cfg.tx_rx_queue_capacity_bytes, 0x40),
+    DEFINE_PROP_UINT8("ibi-queue-capacity-bytes", DWI3C,
+                      cfg.ibi_queue_capacity_bytes, 0x10),
     DEFINE_PROP_UINT8("num-addressable-devices", DWI3C,
                       cfg.num_addressable_devices, 8),
     DEFINE_PROP_UINT16("dev-addr-table-pointer", DWI3C,
diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
index 044ff06a01..a262fcce39 100644
--- a/hw/i3c/trace-events
+++ b/hw/i3c/trace-events
@@ -9,6 +9,8 @@ dw_i3c_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read
 dw_i3c_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
 dw_i3c_send(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] send %" PRId32 " bytes to bus"
 dw_i3c_recv_data(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] recv %" PRId32 " bytes from bus"
+dw_i3c_ibi_recv(uint32_t deviceid, uint8_t ibi_byte) "I3C Dev[%u] recv IBI byte 0x%" PRIx8
+dw_i3c_ibi_handle(uint32_t deviceid, uint8_t addr, bool rnw) "I3C Dev[%u] handle IBI from address 0x%" PRIx8 " RnW=%d"
 dw_i3c_pop_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from RX FIFO"
 dw_i3c_resp_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to response queue"
 dw_i3c_push_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to TX FIFO"
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 15/22] hw/i3c/dw-i3c: Add ctrl MMIO handling
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (13 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 14/22] hw/i3c/dw-i3c: Add IBI handling Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 16/22] hw/i3c/dw-i3c: Add controller resets Jamin Lin
                   ` (8 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Titus Rwantare, Patrick Venture

Adds functionality to the CTRL register.

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>
---
 hw/i3c/dw-i3c.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 832fcf326a..b6ec0d9579 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -344,6 +344,8 @@ static const uint32_t dw_i3c_ro[DW_I3C_NR_REGS] = {
     [R_SLAVE_CONFIG]                = 0xffffffff,
 };
 
+static void dw_i3c_cmd_queue_execute(DWI3C *s);
+
 static inline bool dw_i3c_has_hdr_ts(DWI3C *s)
 {
     return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_TS);
@@ -503,6 +505,36 @@ static int dw_i3c_recv_data(DWI3C *s, bool is_i2c, uint8_t *data,
     return ret;
 }
 
+static void dw_i3c_ctrl_w(DWI3C *s, uint32_t val)
+{
+    /*
+     * If the user is setting I3C_RESUME, the controller was halted.
+     * Try and resume execution and leave the bit cleared.
+     */
+    if (FIELD_EX32(val, DEVICE_CTRL, I3C_RESUME)) {
+        dw_i3c_cmd_queue_execute(s);
+        val = FIELD_DP32(val, DEVICE_CTRL, I3C_RESUME, 0);
+    }
+    /*
+     * I3C_ABORT being set sends an I3C STOP. It's cleared when the STOP is
+     * sent.
+     */
+    if (FIELD_EX32(val, DEVICE_CTRL, I3C_ABORT)) {
+        dw_i3c_end_transfer(s, /*is_i2c=*/true);
+        dw_i3c_end_transfer(s, /*is_i2c=*/false);
+        val = FIELD_DP32(val, DEVICE_CTRL, I3C_ABORT, 0);
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ABORT, 1);
+        dw_i3c_update_irq(s);
+    }
+    /* Update present state. */
+    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+                     DW_I3C_TRANSFER_STATE_IDLE);
+    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+                     DW_I3C_TRANSFER_STATUS_IDLE);
+
+    s->regs[R_DEVICE_CTRL] = val;
+}
+
 static inline bool dw_i3c_target_is_i2c(DWI3C *s, uint16_t offset)
 {
     /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
@@ -1575,6 +1607,9 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
                       "] = 0x%08" PRIx64 "\n",
                       __func__, offset, value);
         break;
+    case R_DEVICE_CTRL:
+        dw_i3c_ctrl_w(s, val32);
+        break;
     case R_RX_TX_DATA_PORT:
         dw_i3c_push_tx(s, val32);
         break;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 16/22] hw/i3c/dw-i3c: Add controller resets
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (14 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 15/22] hw/i3c/dw-i3c: Add ctrl MMIO handling Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 17/22] hw/i3c/aspeed: Add I3C bus get function Jamin Lin
                   ` (7 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture, Stephen Longfield

Adds behavior to the device reset register.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Reviewed-by: Stephen Longfield <slongfield@google.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/i3c/dw-i3c.c     | 117 ++++++++++++++++++++++++++++++++++++++++++++
 hw/i3c/trace-events |   1 +
 2 files changed, 118 insertions(+)

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index b6ec0d9579..d742458129 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -860,6 +860,122 @@ static void dw_i3c_intr_force_w(DWI3C *s, uint32_t val)
     dw_i3c_update_irq(s);
 }
 
+static void dw_i3c_cmd_queue_reset(DWI3C *s)
+{
+    fifo32_reset(&s->cmd_queue);
+
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC,
+                     fifo32_num_free(&s->cmd_queue));
+    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+                                               CMD_BUF_EMPTY_THLD);
+    if (fifo32_num_free(&s->cmd_queue) >= empty_threshold) {
+        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 1);
+        dw_i3c_update_irq(s);
+    };
+}
+
+static void dw_i3c_resp_queue_reset(DWI3C *s)
+{
+    fifo32_reset(&s->resp_queue);
+
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
+                     fifo32_num_used(&s->resp_queue));
+    /*
+     * This interrupt will always be cleared because the threshold is a minimum
+     * of 1 and the queue size is 0.
+     */
+    ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 0);
+    dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_ibi_queue_reset(DWI3C *s)
+{
+    fifo32_reset(&s->ibi_queue);
+
+    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR,
+                     fifo32_num_used(&s->resp_queue));
+    /*
+     * This interrupt will always be cleared because the threshold is a minimum
+     * of 1 and the queue size is 0.
+     */
+    ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 0);
+    dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_tx_queue_reset(DWI3C *s)
+{
+    fifo32_reset(&s->tx_queue);
+
+    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC,
+                     fifo32_num_free(&s->tx_queue));
+    /* TX buf is empty, so this interrupt will always be set. */
+    ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 1);
+    dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_rx_queue_reset(DWI3C *s)
+{
+    fifo32_reset(&s->rx_queue);
+
+    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR,
+                     fifo32_num_used(&s->resp_queue));
+    /*
+     * This interrupt will always be cleared because the threshold is a minimum
+     * of 1 and the queue size is 0.
+     */
+    ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 0);
+    dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_reset(DeviceState *dev)
+{
+    DWI3C *s = DW_I3C(dev);
+    trace_dw_i3c_reset(s->cfg.id);
+
+    memcpy(s->regs, dw_i3c_resets, sizeof(s->regs));
+    /*
+     * The user config for these may differ from our resets array, set them
+     * manually.
+     */
+    ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, ADDR,
+                     s->cfg.dev_addr_table_pointer);
+    ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, DEPTH,
+                     s->cfg.dev_addr_table_depth);
+    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
+                     P_DEV_CHAR_TABLE_START_ADDR,
+                     s->cfg.dev_char_table_pointer);
+    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, DEV_CHAR_TABLE_DEPTH,
+                     s->cfg.dev_char_table_depth);
+
+    dw_i3c_cmd_queue_reset(s);
+    dw_i3c_resp_queue_reset(s);
+    dw_i3c_ibi_queue_reset(s);
+    dw_i3c_tx_queue_reset(s);
+    dw_i3c_rx_queue_reset(s);
+}
+
+static void dw_i3c_reset_ctrl_w(DWI3C *s, uint32_t val)
+{
+    if (FIELD_EX32(val, RESET_CTRL, CORE_RESET)) {
+        dw_i3c_reset(DEVICE(s));
+    }
+    if (FIELD_EX32(val, RESET_CTRL, CMD_QUEUE_RESET)) {
+        dw_i3c_cmd_queue_reset(s);
+    }
+    if (FIELD_EX32(val, RESET_CTRL, RESP_QUEUE_RESET)) {
+        dw_i3c_resp_queue_reset(s);
+    }
+    if (FIELD_EX32(val, RESET_CTRL, TX_BUF_RESET)) {
+        dw_i3c_tx_queue_reset(s);
+    }
+    if (FIELD_EX32(val, RESET_CTRL, RX_BUF_RESET)) {
+        dw_i3c_rx_queue_reset(s);
+    }
+    if (FIELD_EX32(val, RESET_CTRL, IBI_QUEUE_RESET)) {
+        dw_i3c_ibi_queue_reset(s);
+    }
+}
+
 static uint32_t dw_i3c_pop_rx(DWI3C *s)
 {
     if (fifo32_is_empty(&s->rx_queue)) {
@@ -1617,6 +1733,7 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
         dw_i3c_cmd_queue_port_w(s, val32);
         break;
     case R_RESET_CTRL:
+        dw_i3c_reset_ctrl_w(s, val32);
         break;
     case R_INTR_STATUS:
         dw_i3c_intr_status_w(s, val32);
diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
index a262fcce39..39f33d9a50 100644
--- a/hw/i3c/trace-events
+++ b/hw/i3c/trace-events
@@ -11,6 +11,7 @@ dw_i3c_send(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] send %" PRId32 "
 dw_i3c_recv_data(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] recv %" PRId32 " bytes from bus"
 dw_i3c_ibi_recv(uint32_t deviceid, uint8_t ibi_byte) "I3C Dev[%u] recv IBI byte 0x%" PRIx8
 dw_i3c_ibi_handle(uint32_t deviceid, uint8_t addr, bool rnw) "I3C Dev[%u] handle IBI from address 0x%" PRIx8 " RnW=%d"
+dw_i3c_reset(uint32_t deviceid) "I3C Dev[%u] reset"
 dw_i3c_pop_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from RX FIFO"
 dw_i3c_resp_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to response queue"
 dw_i3c_push_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to TX FIFO"
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 17/22] hw/i3c/aspeed: Add I3C bus get function
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (15 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 16/22] hw/i3c/dw-i3c: Add controller resets Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 18/22] hw/i3c: Add Mock target Jamin Lin
                   ` (6 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com

To retrieve the I3C bus object normally, the order is Aspeed I3C -> DW
I3C[n] -> bus object, so make a nice wrapper for people to use.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 include/hw/i3c/aspeed_i3c.h | 3 +++
 hw/i3c/aspeed_i3c.c         | 9 +++++++++
 2 files changed, 12 insertions(+)

diff --git a/include/hw/i3c/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
index b8827d31d7..42c9eedd85 100644
--- a/include/hw/i3c/aspeed_i3c.h
+++ b/include/hw/i3c/aspeed_i3c.h
@@ -31,4 +31,7 @@ struct AspeedI3CState {
     DWI3C devices[ASPEED_I3C_NR_DEVICES];
     uint8_t id;
 };
+
+I3CBus *aspeed_i3c_get_bus(AspeedI3CState *s, uint8_t bus_num);
+
 #endif /* ASPEED_I3C_H */
diff --git a/hw/i3c/aspeed_i3c.c b/hw/i3c/aspeed_i3c.c
index 2700c5f4cd..bac8c55bb0 100644
--- a/hw/i3c/aspeed_i3c.c
+++ b/hw/i3c/aspeed_i3c.c
@@ -153,6 +153,15 @@ static const MemoryRegionOps aspeed_i3c_ops = {
     }
 };
 
+I3CBus *aspeed_i3c_get_bus(AspeedI3CState *s, uint8_t bus_num)
+{
+    if (bus_num < ARRAY_SIZE(s->devices)) {
+        return s->devices[bus_num].bus;
+    }
+    /* Developer error, fail fast. */
+    g_assert_not_reached();
+}
+
 static void aspeed_i3c_reset(DeviceState *dev)
 {
     AspeedI3CState *s = ASPEED_I3C(dev);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (16 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 17/22] hw/i3c/aspeed: Add I3C bus get function Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-27  1:43   ` Jithu Joseph
                     ` (2 more replies)
  2026-02-25  2:12 ` [PATCH v7 19/22] hw/arm/aspeed: Build with I3C_DEVICES Jamin Lin
                   ` (5 subsequent siblings)
  23 siblings, 3 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Titus Rwantare, Patrick Venture

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


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 19/22] hw/arm/aspeed: Build with I3C_DEVICES
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (17 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 18/22] hw/i3c: Add Mock target Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 20/22] hw/i3c: Add hotplug support Jamin Lin
                   ` (4 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com

Allows us to attach the mock I3C target

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/arm/Kconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index d545ecd712..3396a32fd7 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -547,6 +547,7 @@ config ASPEED_SOC
     select I2C
     select I3C
     select DW_I3C
+    select I3C_DEVICES
     select DPS310
     select PCA9552
     select PCA9554
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 20/22] hw/i3c: Add hotplug support
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (18 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 19/22] hw/arm/aspeed: Build with I3C_DEVICES Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25  2:12 ` [PATCH v7 21/22] tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test Jamin Lin
                   ` (3 subsequent siblings)
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com, Patrick Venture

This adds support for hotplugging in I3C.
Conceptually this can be thought of as an I3C target being physically
socketed onto a board.
It is then the target's responsibility to go through the hot-join and
DAA process so it can participate on the bus.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/i3c/core.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/hw/i3c/core.c b/hw/i3c/core.c
index 22e6e226a7..8a37099ce3 100644
--- a/hw/i3c/core.c
+++ b/hw/i3c/core.c
@@ -11,6 +11,7 @@
 #include "qapi/error.h"
 #include "trace.h"
 #include "hw/i3c/i3c.h"
+#include "hw/core/hotplug.h"
 #include "hw/core/qdev-properties.h"
 
 /*
@@ -26,6 +27,17 @@ static const Property i3c_props[] = {
     DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0),
 };
 
+static void i3c_realize(BusState *bus, Error **errp)
+{
+    qbus_set_bus_hotplug_handler(bus);
+}
+
+static void i3c_class_init(ObjectClass *klass, const void *data)
+{
+    BusClass *k = BUS_CLASS(klass);
+    k->realize = i3c_realize;
+}
+
 I3CBus *i3c_init_bus(DeviceState *parent, const char *name)
 {
     return i3c_init_bus_type(TYPE_I3C_BUS, parent, name);
@@ -633,6 +645,11 @@ static const TypeInfo i3c_types[] = {
         .parent = TYPE_BUS,
         .instance_size = sizeof(I3CBus),
         .class_size = sizeof(I3CBusClass),
+        .class_init = i3c_class_init,
+        .interfaces = (InterfaceInfo[]) {
+            { TYPE_HOTPLUG_HANDLER },
+            { }
+        }
     },
     {
         .name = TYPE_I3C_TARGET,
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 21/22] tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (19 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 20/22] hw/i3c: Add hotplug support Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-25 10:47   ` Cédric Le Goater
  2026-02-25  2:12 ` [PATCH v7 22/22] MAINTAINERS: Add I3C maintainers and reviewer Jamin Lin
                   ` (2 subsequent siblings)
  23 siblings, 1 reply; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 tests/functional/arm/test_aspeed_ast2600_sdk.py | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk.py b/tests/functional/arm/test_aspeed_ast2600_sdk.py
index 971fa3390d..46b9f7058c 100755
--- a/tests/functional/arm/test_aspeed_ast2600_sdk.py
+++ b/tests/functional/arm/test_aspeed_ast2600_sdk.py
@@ -31,6 +31,15 @@ def do_ast2600_pcie_test(self):
             'ip addr show dev eth4',
             'inet 10.0.2.15/24')
 
+    def do_ast2600_i3c_test(self):
+        exec_command_and_wait_for_pattern(self,
+            'i3ctransfer -d /dev/bus/i3c/5-1234567890ab'
+            ' -w 0x12,0x34,0x56,0x78,0x90,0xab,0xcd,0xef',
+            'Success on message 0')
+        exec_command_and_wait_for_pattern(self,
+            'i3ctransfer -d /dev/bus/i3c/5-1234567890ab -r 8 | grep 0x | xargs',
+            '0x12 0x34 0x56 0x78 0x90 0xab 0xcd 0xef')
+
     def test_arm_ast2600_evb_sdk(self):
         self.set_machine('ast2600-evb')
         self.require_netdev('user')
@@ -43,6 +52,8 @@ def test_arm_ast2600_evb_sdk(self):
             'ds1338,bus=aspeed.i2c.bus.5,address=0x32')
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.0')
         self.vm.add_args('-netdev', 'user,id=net1')
+        self.vm.add_args('-device',
+            'mock-i3c-target,bus=dw.i3c.5,pid=0xab9078563412')
         self.do_test_arm_aspeed_sdk_start(
             self.scratch_file("ast2600-default", "image-bmc"))
 
@@ -69,6 +80,7 @@ def test_arm_ast2600_evb_sdk(self):
         exec_command_and_wait_for_pattern(self,
              '/sbin/hwclock -f /dev/rtc1', year)
         self.do_ast2600_pcie_test()
+        self.do_ast2600_i3c_test()
 
 
 if __name__ == '__main__':
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* [PATCH v7 22/22] MAINTAINERS: Add I3C maintainers and reviewer
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (20 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 21/22] tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test Jamin Lin
@ 2026-02-25  2:12 ` Jamin Lin
  2026-02-27  2:33 ` [PATCH v7 00/22] i3c: aspeed: Add I3C support Jithu Joseph
  2026-02-27  8:10 ` Cédric Le Goater
  23 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-02-25  2:12 UTC (permalink / raw)
  To: Paolo Bonzini, Peter Maydell, Cédric Le Goater, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Jamin Lin, Troy Lee, Kane Chen, nabihestefan@google.com,
	komlodi@google.com

Add a new I3C section to the MAINTAINERS file.

List Joe Komlodi, Cédric Le Goater and Jamin Lin
as maintainers, and Nabih Estefan as the reviewer,
covering the I3C core and related files under hw/i3c/
and include/hw/i3c/.

Signed-off-by: Nabih Estefan <nabihestefan@google.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 MAINTAINERS | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 233d2a5e71..3b90a5f735 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3933,6 +3933,19 @@ F: include/hw/i2c/smbus_master.h
 F: include/hw/i2c/smbus_slave.h
 F: include/hw/i2c/smbus_eeprom.h
 
+I3C
+M: Joe Komlodi <komlodi@google.com>
+M: Cédric Le Goater <clg@kaod.org>
+M: Jamin Lin <jamin_lin@aspeedtech.com>
+R: Nabih Estefan <nabihestefan@google.com>
+S: Maintained
+F: hw/i3c/*.c
+F: hw/i3c/.h
+F: hw/i3c/Kconfig
+F: hw/i3c/meson.build
+F: hw/i3c/trace-events
+F: include/hw/i3c/*.h
+
 PMBus
 M: Titus Rwantare <titusr@google.com>
 S: Maintained
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 21/22] tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test
  2026-02-25  2:12 ` [PATCH v7 21/22] tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test Jamin Lin
@ 2026-02-25 10:47   ` Cédric Le Goater
  0 siblings, 0 replies; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-25 10:47 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee, Troy Lee,
	Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com

On 2/25/26 03:12, Jamin Lin wrote:
> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> ---
>   tests/functional/arm/test_aspeed_ast2600_sdk.py | 12 ++++++++++++
>   1 file changed, 12 insertions(+)
> 
> diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk.py b/tests/functional/arm/test_aspeed_ast2600_sdk.py
> index 971fa3390d..46b9f7058c 100755
> --- a/tests/functional/arm/test_aspeed_ast2600_sdk.py
> +++ b/tests/functional/arm/test_aspeed_ast2600_sdk.py
> @@ -31,6 +31,15 @@ def do_ast2600_pcie_test(self):
>               'ip addr show dev eth4',
>               'inet 10.0.2.15/24')
>   
> +    def do_ast2600_i3c_test(self):
> +        exec_command_and_wait_for_pattern(self,
> +            'i3ctransfer -d /dev/bus/i3c/5-1234567890ab'
> +            ' -w 0x12,0x34,0x56,0x78,0x90,0xab,0xcd,0xef',
> +            'Success on message 0')
> +        exec_command_and_wait_for_pattern(self,
> +            'i3ctransfer -d /dev/bus/i3c/5-1234567890ab -r 8 | grep 0x | xargs',
> +            '0x12 0x34 0x56 0x78 0x90 0xab 0xcd 0xef')
> +
>       def test_arm_ast2600_evb_sdk(self):
>           self.set_machine('ast2600-evb')
>           self.require_netdev('user')
> @@ -43,6 +52,8 @@ def test_arm_ast2600_evb_sdk(self):
>               'ds1338,bus=aspeed.i2c.bus.5,address=0x32')
>           self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.0')
>           self.vm.add_args('-netdev', 'user,id=net1')
> +        self.vm.add_args('-device',
> +            'mock-i3c-target,bus=dw.i3c.5,pid=0xab9078563412')
>           self.do_test_arm_aspeed_sdk_start(
>               self.scratch_file("ast2600-default", "image-bmc"))
>   
> @@ -69,6 +80,7 @@ def test_arm_ast2600_evb_sdk(self):
>           exec_command_and_wait_for_pattern(self,
>                '/sbin/hwclock -f /dev/rtc1', year)
>           self.do_ast2600_pcie_test()
> +        self.do_ast2600_i3c_test()
>   
>   
>   if __name__ == '__main__':



Reviewed-by: Cédric Le Goater <clg@redhat.com>

Thanks,

C.




^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 02/22] hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj naming
  2026-02-25  2:12 ` [PATCH v7 02/22] hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj naming Jamin Lin
@ 2026-02-25 10:47   ` Cédric Le Goater
  0 siblings, 0 replies; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-25 10:47 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee, Troy Lee,
	Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com

On 2/25/26 03:12, Jamin Lin wrote:
> Following review feedback, update the Aspeed I3C device to use the
> DEFINE_TYPES() macro instead of an explicit type registration function.
> 
> DEFINE_TYPES() is the currently recommended approach in QEMU for
> registering multiple TypeInfo entries and avoids boilerplate
> type_init() code.
> 
> Additionally, rename embedded SysBusDevice fields from "parent" to
> "parent_obj" to comply with the QEMU coding style guidelines for QOM
> objects.
> 
> No functional change.
> 
> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> ---
>   include/hw/i3c/aspeed_i3c.h | 16 ++++++----------
>   hw/i3c/aspeed_i3c.c         | 35 +++++++++++++++--------------------
>   2 files changed, 21 insertions(+), 30 deletions(-)
> 
> diff --git a/include/hw/i3c/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
> index 7a984e1f01..bd0ffc84ea 100644
> --- a/include/hw/i3c/aspeed_i3c.h
> +++ b/include/hw/i3c/aspeed_i3c.h
> @@ -21,28 +21,24 @@ OBJECT_DECLARE_TYPE(AspeedI3CState, AspeedI3CClass, ASPEED_I3C)
>   #define ASPEED_I3C_NR_DEVICES 6
>   
>   OBJECT_DECLARE_SIMPLE_TYPE(AspeedI3CDevice, ASPEED_I3C_DEVICE)
> -typedef struct AspeedI3CDevice {
> -    /* <private> */
> -    SysBusDevice parent;
> +struct AspeedI3CDevice {
> +    SysBusDevice parent_obj;
>   
> -    /* <public> */
>       MemoryRegion mr;
>       qemu_irq irq;
>   
>       uint8_t id;
>       uint32_t regs[ASPEED_I3C_DEVICE_NR_REGS];
> -} AspeedI3CDevice;
> +};
>   
> -typedef struct AspeedI3CState {
> -    /* <private> */
> -    SysBusDevice parent;
> +struct AspeedI3CState {
> +    SysBusDevice parent_obj;
>   
> -    /* <public> */
>       MemoryRegion iomem;
>       MemoryRegion iomem_container;
>       qemu_irq irq;
>   
>       uint32_t regs[ASPEED_I3C_NR_REGS];
>       AspeedI3CDevice devices[ASPEED_I3C_NR_DEVICES];
> -} AspeedI3CState;
> +};
>   #endif /* ASPEED_I3C_H */
> diff --git a/hw/i3c/aspeed_i3c.c b/hw/i3c/aspeed_i3c.c
> index fff259ff66..e7cdfbfdbd 100644
> --- a/hw/i3c/aspeed_i3c.c
> +++ b/hw/i3c/aspeed_i3c.c
> @@ -337,13 +337,6 @@ static void aspeed_i3c_device_class_init(ObjectClass *klass, const void *data)
>       device_class_set_props(dc, aspeed_i3c_device_properties);
>   }
>   
> -static const TypeInfo aspeed_i3c_device_info = {
> -    .name = TYPE_ASPEED_I3C_DEVICE,
> -    .parent = TYPE_SYS_BUS_DEVICE,
> -    .instance_size = sizeof(AspeedI3CDevice),
> -    .class_init = aspeed_i3c_device_class_init,
> -};
> -
>   static const VMStateDescription vmstate_aspeed_i3c = {
>       .name = TYPE_ASPEED_I3C,
>       .version_id = 1,
> @@ -366,18 +359,20 @@ static void aspeed_i3c_class_init(ObjectClass *klass, const void *data)
>       dc->vmsd = &vmstate_aspeed_i3c;
>   }
>   
> -static const TypeInfo aspeed_i3c_info = {
> -    .name = TYPE_ASPEED_I3C,
> -    .parent = TYPE_SYS_BUS_DEVICE,
> -    .instance_init = aspeed_i3c_instance_init,
> -    .instance_size = sizeof(AspeedI3CState),
> -    .class_init = aspeed_i3c_class_init,
> +static const TypeInfo aspeed_i3c_types[] = {
> +    {
> +        .name = TYPE_ASPEED_I3C,
> +        .parent = TYPE_SYS_BUS_DEVICE,
> +        .instance_init = aspeed_i3c_instance_init,
> +        .instance_size = sizeof(AspeedI3CState),
> +        .class_init = aspeed_i3c_class_init,
> +    },
> +    {
> +        .name = TYPE_ASPEED_I3C_DEVICE,
> +        .parent = TYPE_SYS_BUS_DEVICE,
> +        .instance_size = sizeof(AspeedI3CDevice),
> +        .class_init = aspeed_i3c_device_class_init,
> +    },
>   };
>   
> -static void aspeed_i3c_register_types(void)
> -{
> -    type_register_static(&aspeed_i3c_device_info);
> -    type_register_static(&aspeed_i3c_info);
> -}
> -
> -type_init(aspeed_i3c_register_types);
> +DEFINE_TYPES(aspeed_i3c_types)

Reviewed-by: Cédric Le Goater <clg@redhat.com>

Thanks,

C.




^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-25  2:12 ` [PATCH v7 18/22] hw/i3c: Add Mock target Jamin Lin
@ 2026-02-27  1:43   ` Jithu Joseph
  2026-02-27  7:52     ` Cédric Le Goater
  2026-03-02  3:35     ` Jamin Lin
  2026-02-27  9:58   ` Cédric Le Goater
  2026-02-27 20:28   ` Jithu Joseph
  2 siblings, 2 replies; 47+ messages in thread
From: Jithu Joseph @ 2026-02-27  1:43 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Cédric Le Goater,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Titus Rwantare, Patrick Venture


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



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 03/22] hw/i3c: Add bus support
  2026-02-25  2:12 ` [PATCH v7 03/22] hw/i3c: Add bus support Jamin Lin
@ 2026-02-27  2:23   ` Jithu Joseph
  2026-02-27  7:51     ` Cédric Le Goater
  2026-03-02  3:36     ` Jamin Lin
  2026-02-27  9:47   ` Cédric Le Goater
  2026-02-27 20:27   ` Jithu Joseph
  2 siblings, 2 replies; 47+ messages in thread
From: Jithu Joseph @ 2026-02-27  2:23 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Cédric Le Goater,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Patrick Venture, Titus Rwantare, Jithu Joseph


Another minor comment which too can be addressed later

On 2/24/2026 6:12 PM, Jamin Lin wrote:
> Adds an I3C bus and a target class.
> The bus supports:
> - I3C data transmission and reception
> - CCCs (including ENTDAA)
> - IBIs
> - legacy I2C transactions
> 
> General usage of the bus is similar to I2C. Users are expected to
> initialize a bus via i3c_init_bus, and use the bus returned from the
> init function to do transactions on the bus.
> 
> In order to handle IBIs, the controller provides callbacks to handle
> receiving an IBI from a target, receiving (optional) additional IBI
> bytes from a target, and handling when a target is done with its IBI.
> 
> Similarly, target creation is done via i3c_target_create_simple and
> users use the provided I3CTarget to handle transactions.
> The target has functions provided that it can use to invoke an IBI and
> send additional bytes.
> 
> Along with the send, recv, and event callbacks that are expected of an
> I3C target, which are similar to I2C, there is a separate callback for
> CCC handling.
> This is to help encapsulate CCC handling and keep it separate from
> target-specific read/write functionality.
> 
> To avoid repition for required CCCs among I3C targets, there is some
> class-level CCC handling added. The CCC is then passed to the target in
> case it needs to handle it in some way.
> 
> Signed-off-by: Joe Komlodi <komlodi@google.com>
> Reviewed-by: Patrick Venture <venture@google.com>
> Reviewed-by: Titus Rwantare <titusr@google.com>
> Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> ---
>  include/hw/i3c/i3c.h | 277 ++++++++++++++++++
>  hw/i3c/core.c        | 647 +++++++++++++++++++++++++++++++++++++++++++
>  hw/i3c/meson.build   |   1 +
>  hw/i3c/trace-events  |  16 ++
>  4 files changed, 941 insertions(+)
>  create mode 100644 include/hw/i3c/i3c.h
>  create mode 100644 hw/i3c/core.c
> 

...

> +
> +int i3c_send_byte(I3CBus *bus, uint8_t data)
> +{
> +    /*
> +     * Ignored, the caller can determine how many were sent based on if this was
> +     * ACKed/NACKed.
> +     */
> +    uint32_t num_sent;

num_sent is uninitialized here. Even though i3c_send_byte ignores it after the call, it gets passed by pointer into i3c_send() where it is used in the trace_i3c_send() call.
If the send callback does not write *num_sent (as is the case with mock_i3c_target_tx, refer my prev comment for patch 18/22), the trace might show garbage. Initializing it to zero might reduce the confusion

> +    return i3c_send(bus, &data, 1, &num_sent);
> +}
> +
> +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
> +             uint32_t *num_sent)
> +{
> +    I3CTargetClass *tc;
> +    I3CTarget *t;
> +    I3CNode *node;
> +    int ret = 0;
> +
> +    /* If this message is a broadcast and no CCC has been found, grab it. */
> +    if (bus->broadcast && !bus->in_ccc) {
> +        bus->ccc = *data;
> +        bus->in_ccc = true;
> +        /*
> +         * We need to keep track if we're currently in ENTDAA.
> +         * On any other CCC, the CCC is over on a RESTART or STOP, but ENTDAA
> +         * is only over on a STOP.
> +         */
> +        if (bus->ccc == I3C_CCC_ENTDAA) {
> +            bus->in_entdaa = true;
> +        }
> +    }
> +
> +    QLIST_FOREACH(node, &bus->current_devs, next) {
> +        t = node->target;
> +        tc = I3C_TARGET_GET_CLASS(t);
> +        if (bus->in_ccc) {
> +            if (!tc->handle_ccc_write) {
> +                ret = -1;
> +                continue;
> +            }
> +            ret = i3c_target_handle_ccc_write(t, data, num_to_send, num_sent);
> +            /* Targets should only NACK on a direct CCC. */
> +            if (ret && !CCC_IS_DIRECT(bus->ccc)) {
> +                ret = 0;
> +            }
> +        } else {
> +            if (tc->send) {
> +                ret = ret || tc->send(t, data, num_to_send, num_sent);
> +            } else {
> +                ret = -1;
> +            }
> +        }
> +    }
> +
> +    trace_i3c_send(*num_sent, num_to_send, ret == 0);

This trace might print garbage if the target send callback doesnt set it 

> +
> +    return ret ? -1 : 0;
> +}
> +

Thanks
Jithu


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 00/22] i3c: aspeed: Add I3C support
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (21 preceding siblings ...)
  2026-02-25  2:12 ` [PATCH v7 22/22] MAINTAINERS: Add I3C maintainers and reviewer Jamin Lin
@ 2026-02-27  2:33 ` Jithu Joseph
  2026-02-27  7:53   ` Cédric Le Goater
  2026-02-27  8:10 ` Cédric Le Goater
  23 siblings, 1 reply; 47+ messages in thread
From: Jithu Joseph @ 2026-02-27  2:33 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Cédric Le Goater,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com

On 2/24/2026 6:11 PM, Jamin Lin wrote:

>   The initial patch series was based on work by Joe Komlodi <komlodi@google.com>.
> 
>   This series adds I3C bus support to QEMU and adds more functionality to the
>   Aspeed I3C controller.
> 

Thanks Jaimin for the series, I was able to test this series to a good extent over the last week

Tested read/write transfers from guest Linux using i3ctransfer against the mock-i3c-target  on an AST2600 based machine
Looks good to me, Feel free to add my tag for the whole series

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



Jithu



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 03/22] hw/i3c: Add bus support
  2026-02-27  2:23   ` Jithu Joseph
@ 2026-02-27  7:51     ` Cédric Le Goater
  2026-02-27 19:58       ` Jithu Joseph
  2026-03-02  3:36     ` Jamin Lin
  1 sibling, 1 reply; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-27  7:51 UTC (permalink / raw)
  To: Jithu Joseph, Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Patrick Venture, Titus Rwantare

Hello Jithu,

On 2/27/26 03:23, Jithu Joseph wrote:
> 
> Another minor comment which too can be addressed later
> 
> On 2/24/2026 6:12 PM, Jamin Lin wrote:
>> Adds an I3C bus and a target class.
>> The bus supports:
>> - I3C data transmission and reception
>> - CCCs (including ENTDAA)
>> - IBIs
>> - legacy I2C transactions
>>
>> General usage of the bus is similar to I2C. Users are expected to
>> initialize a bus via i3c_init_bus, and use the bus returned from the
>> init function to do transactions on the bus.
>>
>> In order to handle IBIs, the controller provides callbacks to handle
>> receiving an IBI from a target, receiving (optional) additional IBI
>> bytes from a target, and handling when a target is done with its IBI.
>>
>> Similarly, target creation is done via i3c_target_create_simple and
>> users use the provided I3CTarget to handle transactions.
>> The target has functions provided that it can use to invoke an IBI and
>> send additional bytes.
>>
>> Along with the send, recv, and event callbacks that are expected of an
>> I3C target, which are similar to I2C, there is a separate callback for
>> CCC handling.
>> This is to help encapsulate CCC handling and keep it separate from
>> target-specific read/write functionality.
>>
>> To avoid repition for required CCCs among I3C targets, there is some
>> class-level CCC handling added. The CCC is then passed to the target in
>> case it needs to handle it in some way.
>>
>> Signed-off-by: Joe Komlodi <komlodi@google.com>
>> Reviewed-by: Patrick Venture <venture@google.com>
>> Reviewed-by: Titus Rwantare <titusr@google.com>
>> Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
>> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
>> ---
>>   include/hw/i3c/i3c.h | 277 ++++++++++++++++++
>>   hw/i3c/core.c        | 647 +++++++++++++++++++++++++++++++++++++++++++
>>   hw/i3c/meson.build   |   1 +
>>   hw/i3c/trace-events  |  16 ++
>>   4 files changed, 941 insertions(+)
>>   create mode 100644 include/hw/i3c/i3c.h
>>   create mode 100644 hw/i3c/core.c
>>
> 
> ...
> 
>> +
>> +int i3c_send_byte(I3CBus *bus, uint8_t data)
>> +{
>> +    /*
>> +     * Ignored, the caller can determine how many were sent based on if this was
>> +     * ACKed/NACKed.
>> +     */
>> +    uint32_t num_sent;
> 
> num_sent is uninitialized here. Even though i3c_send_byte ignores it after the call, it gets passed by pointer into i3c_send() where it is used in the trace_i3c_send() call.
> If the send callback does not write *num_sent (as is the case with mock_i3c_target_tx, refer my prev comment for patch 18/22), the trace might show garbage. Initializing it to zero might reduce the confusion

Could please send a patch ? I will queue it after this series.

Also, since you have tested this series, would mind sending
a Tested-by tag ? possibly a Reviewed-by too.

Thanks

C.




> 
>> +    return i3c_send(bus, &data, 1, &num_sent);
>> +}
>> +
>> +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
>> +             uint32_t *num_sent)
>> +{
>> +    I3CTargetClass *tc;
>> +    I3CTarget *t;
>> +    I3CNode *node;
>> +    int ret = 0;
>> +
>> +    /* If this message is a broadcast and no CCC has been found, grab it. */
>> +    if (bus->broadcast && !bus->in_ccc) {
>> +        bus->ccc = *data;
>> +        bus->in_ccc = true;
>> +        /*
>> +         * We need to keep track if we're currently in ENTDAA.
>> +         * On any other CCC, the CCC is over on a RESTART or STOP, but ENTDAA
>> +         * is only over on a STOP.
>> +         */
>> +        if (bus->ccc == I3C_CCC_ENTDAA) {
>> +            bus->in_entdaa = true;
>> +        }
>> +    }
>> +
>> +    QLIST_FOREACH(node, &bus->current_devs, next) {
>> +        t = node->target;
>> +        tc = I3C_TARGET_GET_CLASS(t);
>> +        if (bus->in_ccc) {
>> +            if (!tc->handle_ccc_write) {
>> +                ret = -1;
>> +                continue;
>> +            }
>> +            ret = i3c_target_handle_ccc_write(t, data, num_to_send, num_sent);
>> +            /* Targets should only NACK on a direct CCC. */
>> +            if (ret && !CCC_IS_DIRECT(bus->ccc)) {
>> +                ret = 0;
>> +            }
>> +        } else {
>> +            if (tc->send) {
>> +                ret = ret || tc->send(t, data, num_to_send, num_sent);
>> +            } else {
>> +                ret = -1;
>> +            }
>> +        }
>> +    }
>> +
>> +    trace_i3c_send(*num_sent, num_to_send, ret == 0);
> 
> This trace might print garbage if the target send callback doesnt set it
> 
>> +
>> +    return ret ? -1 : 0;
>> +}
>> +
> 
> Thanks
> Jithu



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-27  1:43   ` Jithu Joseph
@ 2026-02-27  7:52     ` Cédric Le Goater
  2026-02-27 19:49       ` Jithu Joseph
  2026-03-02  3:35     ` Jamin Lin
  1 sibling, 1 reply; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-27  7:52 UTC (permalink / raw)
  To: Jithu Joseph, Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Titus Rwantare, Patrick Venture

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.



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 00/22] i3c: aspeed: Add I3C support
  2026-02-27  2:33 ` [PATCH v7 00/22] i3c: aspeed: Add I3C support Jithu Joseph
@ 2026-02-27  7:53   ` Cédric Le Goater
  2026-02-27 20:03     ` Jithu Joseph
  0 siblings, 1 reply; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-27  7:53 UTC (permalink / raw)
  To: Jithu Joseph, Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com

On 2/27/26 03:33, Jithu Joseph wrote:
> On 2/24/2026 6:11 PM, Jamin Lin wrote:
> 
>>    The initial patch series was based on work by Joe Komlodi <komlodi@google.com>.
>>
>>    This series adds I3C bus support to QEMU and adds more functionality to the
>>    Aspeed I3C controller.
>>
> 
> Thanks Jaimin for the series, I was able to test this series to a good extent over the last week
> 
> Tested read/write transfers from guest Linux using i3ctransfer against the mock-i3c-target  on an AST2600 based machine

Can you be more precise on the machine used ?

Thanks,

C.



> Looks good to me, Feel free to add my tag for the whole series
> 
> Tested-by: Jithu Joseph <jithu.joseph@oss.qualcomm.com>
> 
> 
> 
> Jithu
> 



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 00/22] i3c: aspeed: Add I3C support
  2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
                   ` (22 preceding siblings ...)
  2026-02-27  2:33 ` [PATCH v7 00/22] i3c: aspeed: Add I3C support Jithu Joseph
@ 2026-02-27  8:10 ` Cédric Le Goater
  23 siblings, 0 replies; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-27  8:10 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee, Troy Lee,
	Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com

On 2/25/26 03:11, Jamin Lin wrote:
> v1:
>    The initial patch series was based on work by Joe Komlodi <komlodi@google.com>.
> 
>    This series adds I3C bus support to QEMU and adds more functionality to the
>    Aspeed I3C controller.
> 
>    This implementation is a basic implementation that introduces IBIs
>    (including hot-join), CCCs, and SDR data transfer. As-is, it doesn't support
>    multi-controller buses or HDR transfers.
> 
>    First we add the I3C bus and controller model. With that added we extend
>    the functionality of the Aspeed I3C controller so it can do transfers
>    and handle IBIs.
> 
>    Next, we add a mock I3C target. It's intended to be a very simple target
>    just to verify that I3C is working on the guest. Internally, we've used it
>    on Linux to verify that i3C devices can be probed and can send/receive data
>    and IBIs.
>    This target is sort of like an EEPROM, and it can also send IBIs upon
>    reception of a user-defined magic number.
> 
>    Lastly we add  hotplugging support. The hotplugging doesn't do anything too
>    complicated, it just adds the device attempting to hotplug to the bus. It
>    is the device's responsibility to hot-join and go through the DAA process
>    to participate on the bus.
> 
> v2:
>    Jamin Lin <jamin_lin@aspeedtech.com> has taken ownership of the I3C patch
>    series for upstream submission.
> 
>    Changes in this version include:
> 
>    1. Added I3C functional tests.
>    2. Updated patch 4 to refine register field definitions.
>    3. Updated patch 7 to correct read-only register field masks.
> 
> v3:
>    1. Add Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
>    2. Fix a typo
>    3. Fix ASPEED mail server issue
> 
> v4:
>    1. Add Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> at the end of each patch.
> 
> v5:
>    1. Address review feedback by updating the I3C bus and device to use the
>     DEFINE_TYPES() macro instead of an explicit type registration function.
>    2. Rename variables to `parent_obj` for the parent object and `parent_class`
>     for the parent class to comply with QEMU QOM coding style guidelines.
> 
> v6:
>    1. Add I3C maintainers and reviewer
> 
> v7:
>    1.  Add I3C maintainers and reviewer
> 
> Jamin Lin (3):
>    hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj
>      naming
>    tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test
>    MAINTAINERS: Add I3C maintainers and reviewer
> 
> Joe Komlodi (19):
>    hw/misc/aspeed_i3c: Move to i3c directory
>    hw/i3c: Add bus support
>    hw/i3c: Split DesignWare I3C out of Aspeed I3C
>    hw/i3c/dw-i3c: Add more register fields
>    hw/i3c/aspeed_i3c: Add more register fields
>    hw/i3c/dw-i3c: Add more reset values
>    hw/i3c/aspeed_i3c: Add register RO field masks
>    hw/i3c/dw-i3c: Add register RO field masks
>    hw/i3c/dw-i3c: Treat more registers as read-as-zero
>    hw/i3c/dw-i3c: Use 32 bits on MMIO writes
>    hw/i3c/dw-i3c: Add IRQ MMIO behavior
>    hw/i3c/dw-i3c: Add data TX and RX
>    hw/i3c/dw-i3c: Add IBI handling
>    hw/i3c/dw-i3c: Add ctrl MMIO handling
>    hw/i3c/dw-i3c: Add controller resets
>    hw/i3c/aspeed: Add I3C bus get function
>    hw/i3c: Add Mock target
>    hw/arm/aspeed: Build with I3C_DEVICES
>    hw/i3c: Add hotplug support
> 
>   MAINTAINERS                                   |   13 +
>   meson.build                                   |    1 +
>   hw/i3c/trace.h                                |    2 +
>   include/hw/arm/aspeed_soc.h                   |    2 +-
>   include/hw/{misc => i3c}/aspeed_i3c.h         |   31 +-
>   include/hw/i3c/dw-i3c.h                       |  199 ++
>   include/hw/i3c/i3c.h                          |  277 +++
>   include/hw/i3c/mock-i3c-target.h              |   52 +
>   hw/i3c/aspeed_i3c.c                           |  258 +++
>   hw/i3c/core.c                                 |  664 ++++++
>   hw/i3c/dw-i3c.c                               | 1862 +++++++++++++++++
>   hw/i3c/mock-i3c-target.c                      |  298 +++
>   hw/misc/aspeed_i3c.c                          |  383 ----
>   hw/Kconfig                                    |    1 +
>   hw/arm/Kconfig                                |    3 +
>   hw/i3c/Kconfig                                |   15 +
>   hw/i3c/meson.build                            |    6 +
>   hw/i3c/trace-events                           |   48 +
>   hw/meson.build                                |    1 +
>   hw/misc/meson.build                           |    1 -
>   hw/misc/trace-events                          |    6 -
>   .../functional/arm/test_aspeed_ast2600_sdk.py |   12 +
>   22 files changed, 3723 insertions(+), 412 deletions(-)
>   create mode 100644 hw/i3c/trace.h
>   rename include/hw/{misc => i3c}/aspeed_i3c.h (53%)
>   create mode 100644 include/hw/i3c/dw-i3c.h
>   create mode 100644 include/hw/i3c/i3c.h
>   create mode 100644 include/hw/i3c/mock-i3c-target.h
>   create mode 100644 hw/i3c/aspeed_i3c.c
>   create mode 100644 hw/i3c/core.c
>   create mode 100644 hw/i3c/dw-i3c.c
>   create mode 100644 hw/i3c/mock-i3c-target.c
>   delete mode 100644 hw/misc/aspeed_i3c.c
>   create mode 100644 hw/i3c/Kconfig
>   create mode 100644 hw/i3c/meson.build
>   create mode 100644 hw/i3c/trace-events
> 


Applied to aspeed-next.

Thanks,

C.



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 03/22] hw/i3c: Add bus support
  2026-02-25  2:12 ` [PATCH v7 03/22] hw/i3c: Add bus support Jamin Lin
  2026-02-27  2:23   ` Jithu Joseph
@ 2026-02-27  9:47   ` Cédric Le Goater
  2026-03-02  1:31     ` Jamin Lin
  2026-02-27 20:27   ` Jithu Joseph
  2 siblings, 1 reply; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-27  9:47 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee, Troy Lee,
	Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Patrick Venture, Titus Rwantare

On 2/25/26 03:12, Jamin Lin wrote:
> Adds an I3C bus and a target class.
> The bus supports:
> - I3C data transmission and reception
> - CCCs (including ENTDAA)
> - IBIs
> - legacy I2C transactions
> 
> General usage of the bus is similar to I2C. Users are expected to
> initialize a bus via i3c_init_bus, and use the bus returned from the
> init function to do transactions on the bus.
> 
> In order to handle IBIs, the controller provides callbacks to handle
> receiving an IBI from a target, receiving (optional) additional IBI
> bytes from a target, and handling when a target is done with its IBI.
> 
> Similarly, target creation is done via i3c_target_create_simple and
> users use the provided I3CTarget to handle transactions.
> The target has functions provided that it can use to invoke an IBI and
> send additional bytes.
> 
> Along with the send, recv, and event callbacks that are expected of an
> I3C target, which are similar to I2C, there is a separate callback for
> CCC handling.
> This is to help encapsulate CCC handling and keep it separate from
> target-specific read/write functionality.
> 
> To avoid repition for required CCCs among I3C targets, there is some
> class-level CCC handling added. The CCC is then passed to the target in
> case it needs to handle it in some way.
> 
> Signed-off-by: Joe Komlodi <komlodi@google.com>
> Reviewed-by: Patrick Venture <venture@google.com>
> Reviewed-by: Titus Rwantare <titusr@google.com>
> Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> ---
>   include/hw/i3c/i3c.h | 277 ++++++++++++++++++
>   hw/i3c/core.c        | 647 +++++++++++++++++++++++++++++++++++++++++++
>   hw/i3c/meson.build   |   1 +
>   hw/i3c/trace-events  |  16 ++
>   4 files changed, 941 insertions(+)
>   create mode 100644 include/hw/i3c/i3c.h
>   create mode 100644 hw/i3c/core.c
> 
> diff --git a/include/hw/i3c/i3c.h b/include/hw/i3c/i3c.h
> new file mode 100644
> index 0000000000..6ba90793ad
> --- /dev/null
> +++ b/include/hw/i3c/i3c.h
> @@ -0,0 +1,277 @@
> +/*
> + * QEMU I3C bus interface.
> + *
> + * Copyright 2025 Google LLC
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef QEMU_INCLUDE_HW_I3C_I3C_H_
> +#define QEMU_INCLUDE_HW_I3C_I3C_H_
> +
> +#include "hw/core/qdev.h"
> +#include "qom/object.h"
> +#include "hw/i2c/i2c.h"
> +
> +#define TYPE_I3C_TARGET "i3c-target"
> +OBJECT_DECLARE_TYPE(I3CTarget, I3CTargetClass, I3C_TARGET)
> +
> +typedef enum I3CEvent {
> +    I3C_START_RECV,
> +    I3C_START_SEND,
> +    I3C_STOP,
> +    I3C_NACK,
> +} I3CEvent;
> +
> +typedef enum I3CCCC {
> +    /* Broadcast CCCs */
> +    I3C_CCC_ENEC      = 0x00,
> +    I3C_CCC_DISEC     = 0x01,
> +    I3C_CCC_ENTAS0    = 0x02,
> +    I3C_CCC_ENTAS1    = 0x03,
> +    I3C_CCC_ENTAS2    = 0x04,
> +    I3C_CCC_ENTAS3    = 0x05,
> +    I3C_CCC_RSTDAA    = 0x06,
> +    I3C_CCC_ENTDAA    = 0x07,
> +    I3C_CCC_DEFTGTS   = 0x08,
> +    I3C_CCC_SETMWL    = 0x09,
> +    I3C_CCC_SETMRL    = 0x0a,
> +    I3C_CCC_ENTTM     = 0x0b,
> +    I3C_CCC_SETBUSCON = 0x0c,
> +    I3C_CCC_ENDXFER   = 0x12,
> +    I3C_CCC_ENTHDR0   = 0x20,
> +    I3C_CCC_ENTHDR1   = 0x21,
> +    I3C_CCC_ENTHDR2   = 0x22,
> +    I3C_CCC_ENTHDR3   = 0x23,
> +    I3C_CCC_ENTHDR4   = 0x24,
> +    I3C_CCC_ENTHDR5   = 0x25,
> +    I3C_CCC_ENTHDR6   = 0x26,
> +    I3C_CCC_ENTHDR7   = 0x27,
> +    I3C_CCC_SETXTIME  = 0x28,
> +    I3C_CCC_SETAASA   = 0x29,
> +    I3C_CCC_RSTACT    = 0x2a,
> +    I3C_CCC_DEFGRPA   = 0x2b,
> +    I3C_CCC_RSTGRPA   = 0x2c,
> +    I3C_CCC_MLANE     = 0x2d,
> +    /* Direct CCCs */
> +    I3C_CCCD_ENEC       = 0x80,
> +    I3C_CCCD_DISEC      = 0x81,
> +    I3C_CCCD_ENTAS0     = 0x82,
> +    I3C_CCCD_ENTAS1     = 0x83,
> +    I3C_CCCD_ENTAS2     = 0x84,
> +    I3C_CCCD_ENTAS3     = 0x85,
> +    I3C_CCCD_SETDASA    = 0x87,
> +    I3C_CCCD_SETNEWDA   = 0x88,
> +    I3C_CCCD_SETMWL     = 0x89,
> +    I3C_CCCD_SETMRL     = 0x8a,
> +    I3C_CCCD_GETMWL     = 0x8b,
> +    I3C_CCCD_GETMRL     = 0x8c,
> +    I3C_CCCD_GETPID     = 0x8d,
> +    I3C_CCCD_GETBCR     = 0x8e,
> +    I3C_CCCD_GETDCR     = 0x8f,
> +    I3C_CCCD_GETSTATUS  = 0x90,
> +    I3C_CCCD_GETACCCR   = 0x91,
> +    I3C_CCCD_ENDXFER    = 0x92,
> +    I3C_CCCD_SETBRGTGT  = 0x93,
> +    I3C_CCCD_GETMXDS    = 0x94,
> +    I3C_CCCD_GETCAPS    = 0x95,
> +    I3C_CCCD_SETROUTE   = 0x96,
> +    I3C_CCCD_SETXTIME   = 0x98,
> +    I3C_CCCD_GETXTIME   = 0x99,
> +    I3C_CCCD_RSTACT     = 0x9a,
> +    I3C_CCCD_SETGRPA    = 0x9b,
> +    I3C_CCCD_RSTGRPA    = 0x9c,
> +    I3C_CCCD_MLANE      = 0x9d,
> +} I3CCCC;
> +
> +#define CCC_IS_DIRECT(_ccc) (_ccc & 0x80)
> +
> +#define I3C_BROADCAST 0x7e
> +#define I3C_HJ_ADDR 0x02
> +#define I3C_ENTDAA_SIZE 8
> +
> +struct I3CTargetClass {
> +    DeviceClass parent_class;
> +
> +    /*
> +     * Controller to target. Returns 0 for success, non-zero for NAK or other
> +     * error.
> +     */
> +    int (*send)(I3CTarget *s, const uint8_t *data, uint32_t num_to_send,
> +                uint32_t *num_sent);
> +    /*
> +     * Target to controller. I3C targets are able to terminate reads early, so
> +     * this returns the number of bytes read from the target.
> +     */
> +    uint32_t (*recv)(I3CTarget *s, uint8_t *data, uint32_t num_to_read);
> +    /* Notify the target of a bus state change. */
> +    int (*event)(I3CTarget *s, enum I3CEvent event);
> +    /*
> +     * Handle a read CCC transmitted from a controller.
> +     * CCCs are I3C commands that I3C targets support.
> +     * The target can NACK the CCC if it does not support it.
> +     */
> +    int (*handle_ccc_read)(I3CTarget *s, uint8_t *data, uint32_t num_to_read,
> +                           uint32_t *num_read);
> +    /*
> +     * Handle a write CCC transmitted from a controller.
> +     * CCCs are I3C commands that I3C targets support.
> +     * The target can NACK the CCC if it does not support it.
> +     */
> +    int (*handle_ccc_write)(I3CTarget *s, const uint8_t *data,
> +                            uint32_t num_to_send, uint32_t *num_sent);
> +
> +    /*
> +     * Matches and adds the candidate if the address matches the candidate's
> +     * address.
> +     * Returns true if the address matched, or if this was a broadcast, and
> +     * updates the device list. Otherwise returns false.
> +     */
> +    bool (*target_match)(I3CTarget *candidate, uint8_t address, bool is_read,
> +                         bool broadcast, bool in_entdaa);
> +};
> +
> +struct I3CTarget {
> +    DeviceState parent_obj;
> +
> +    uint8_t address;
> +    uint8_t static_address;
> +    uint8_t dcr;
> +    uint8_t bcr;
> +    uint64_t pid;
> +
> +    /* CCC State tracking. */
> +    I3CCCC curr_ccc;
> +    uint8_t ccc_byte_offset;
> +    bool in_ccc;
> +    bool in_test_mode;
> +};
> +
> +struct I3CNode {
> +    I3CTarget *target;
> +    QLIST_ENTRY(I3CNode) next;
> +};
> +
> +typedef struct I3CNode I3CNode;
> +
> +typedef QLIST_HEAD(I3CNodeList, I3CNode) I3CNodeList;
> +
> +#define TYPE_I3C_BUS "i3c-bus"
> +OBJECT_DECLARE_TYPE(I3CBus, I3CBusClass, I3C_BUS)
> +
> +struct I3CBus {
> +    BusState parent_obj;
> +
> +    /* Legacy I2C. */
> +    I2CBus *i2c_bus;
> +
> +    I3CNodeList current_devs;
> +    bool broadcast;
> +    uint8_t ccc;
> +    bool in_ccc;
> +    bool in_entdaa;
> +    uint8_t saved_address;
> +};
> +
> +struct I3CBusClass {
> +    BusClass parent_class;
> +
> +    /* Handle an incoming IBI request from a target */
> +    int (*ibi_handle) (I3CBus *bus, uint8_t addr, bool is_recv);
> +    /* Receive data from an IBI request */
> +    int (*ibi_recv) (I3CBus *bus, uint8_t data);
> +    /* Do anything that needs to be done, since the IBI is finished. */
> +    int (*ibi_finish) (I3CBus *bus);
> +};
> +
> +I3CBus *i3c_init_bus(DeviceState *parent, const char *name);
> +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent,
> +                          const char *name);
> +void i3c_set_target_address(I3CTarget *dev, uint8_t address);
> +bool i3c_bus_busy(I3CBus *bus);
> +
> +/*
> + * Start a transfer on an I3C bus.
> + * If is_recv is known at compile-time (i.e. a device will always be sending or
> + * will always be receiving at a certain point), prefer to use i3c_start_recv or
> + * i3c_start_send instead.
> + *
> + * Returns 0 on success, non-zero on an error.
> + */
> +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv);
> +
> +/*
> + * Start a receive transfer on an I3C bus.
> + *
> + * Returns 0 on success, non-zero on an error
> + */
> +int i3c_start_recv(I3CBus *bus, uint8_t address);
> +
> +/*
> + * Start a send transfer on an I3C bus.
> + *
> + * Returns 0 on success, non-zero on an error
> + */
> +int i3c_start_send(I3CBus *bus, uint8_t address);
> +
> +void i3c_end_transfer(I3CBus *bus);
> +void i3c_nack(I3CBus *bus);
> +int i3c_send_byte(I3CBus *bus, uint8_t data);
> +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
> +             uint32_t *num_sent);
> +/*
> + * I3C receives can only NACK on a CCC. The target should NACK a CCC it does not
> + * support.
> + */
> +int i3c_recv_byte(I3CBus *bus, uint8_t *data);
> +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read,
> +             uint32_t *num_read);
> +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event);
> +int i3c_do_entdaa(I3CBus *bus, uint8_t address, uint64_t *pid, uint8_t *bcr,
> +                  uint8_t *dcr);
> +int i3c_start_device_transfer(I3CTarget *dev, int send_length);
> +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address,
> +                             enum I3CEvent event);
> +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv);
> +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data);
> +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data);
> +
> +/*
> + * Legacy I2C functions.
> + *
> + * These are wrapper for I2C functions that take in an I3C bus instead of an I2C
> + * bus. Internally they use the I2C bus (and devices attached to it) that's a
> + * part of the I3C bus
> + */
> +void legacy_i2c_nack(I3CBus *bus);
> +uint8_t legacy_i2c_recv(I3CBus *bus);
> +int legacy_i2c_send(I3CBus *bus, uint8_t data);
> +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv);
> +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address);
> +int legacy_i2c_start_send(I3CBus *bus, uint8_t address);
> +void legacy_i2c_end_transfer(I3CBus *bus);
> +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name,
> +                                          uint8_t addr);
> +
> +/**
> + * Create an I3C Target.
> + *
> + * The target returned from this function still needs to be realized.
> + */
> +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
> +                          uint8_t bcr, uint64_t pid);
> +
> +/**
> + * Create and realize an I3C target.
> + *
> + * Create the target, initialize it, put it on the specified I3C bus, and
> + * realize it.
> + */
> +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name,
> +                                    uint8_t addr, uint8_t dcr, uint8_t bcr,
> +                                    uint64_t pid);
> +
> +/* Realize and drop the reference count on an I3C target. */
> +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp);
> +
> +#endif  /* QEMU_INCLUDE_HW_I3C_I3C_H_ */
> diff --git a/hw/i3c/core.c b/hw/i3c/core.c
> new file mode 100644
> index 0000000000..22e6e226a7
> --- /dev/null
> +++ b/hw/i3c/core.c
> @@ -0,0 +1,647 @@
> +/*
> + * QEMU I3C bus interface.
> + *
> + * Copyright 2025 Google LLC
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +#include "hw/i3c/i3c.h"
> +#include "hw/core/qdev-properties.h"
> +
> +/*
> + * In test mode (enabled by ENTTM CCC) we're supposed to send a random PID
> + * during ENTDAA, so we'll just send "QEMU".
> + */
> +#define TEST_MODE_PROVISIONED_ID 0x0000554d4551ULL
> +
> +static const Property i3c_props[] = {
> +    DEFINE_PROP_UINT8("static-address", struct I3CTarget, static_address, 0),
> +    DEFINE_PROP_UINT8("dcr", struct I3CTarget, dcr, 0),
> +    DEFINE_PROP_UINT8("bcr", struct I3CTarget, bcr, 0),
> +    DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0),
> +};
> +
> +I3CBus *i3c_init_bus(DeviceState *parent, const char *name)
> +{
> +    return i3c_init_bus_type(TYPE_I3C_BUS, parent, name);
> +}
> +
> +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent,
> +                          const char *name)
> +{
> +    I3CBus *bus;
> +
> +    bus = I3C_BUS(qbus_new(type, parent, name));
> +    QLIST_INIT(&bus->current_devs);
> +    bus->broadcast = false;
> +    bus->in_entdaa = false;
> +    bus->in_ccc = false;
> +
> +    /* I2C init. */
> +    g_autofree gchar *i2c_bus_name = g_strdup_printf("%s-legacy-i2c", name);
> +    bus->i2c_bus = i2c_init_bus(parent, i2c_bus_name);
> +
> +    return bus;
> +}
> +
> +bool i3c_bus_busy(I3CBus *bus)
> +{
> +    return !QLIST_EMPTY(&bus->current_devs);
> +}
> +
> +static bool i3c_target_match(I3CTarget *candidate, uint8_t address,
> +                             bool is_recv, bool broadcast, bool in_entdaa)
> +{
> +    /* Once a target has a dynamic address, it only responds to that. */
> +    uint8_t targ_addr = candidate->address ? candidate->address :
> +                                             candidate->static_address;
> +
> +    if (in_entdaa) {
> +        if (address != I3C_BROADCAST) {
> +            g_autofree char *path =
> +                object_get_canonical_path(OBJECT(candidate));
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address 0x%.2x sent during "
> +                          "ENTDAA instead of a broadcast address\n",
> +                          path, address);
> +            return false;
> +        }
> +
> +        /*
> +         * Targets should only ACK ENTDAA broadcasts if they have no dynamic
> +         * address.
> +         */
> +        return candidate->address == 0;
> +    }
> +
> +    /* Return if our addresses match, or if it's a broadcast. */
> +    return targ_addr == address || broadcast;
> +}
> +
> +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address,
> +                              enum I3CEvent event)
> +{
> +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(target);
> +    bool matched = tc->target_match(target, address, event == I3C_START_RECV,
> +                                    bus->broadcast, bus->in_entdaa);
> +
> +    if (matched) {
> +        I3CNode *node = g_new(struct I3CNode, 1);
> +        node->target = target;
> +        QLIST_INSERT_HEAD(&bus->current_devs, node, next);
> +    }
> +    return matched;
> +}
> +
> +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event)
> +{
> +    BusChild *child;
> +    I3CNode *node, *next;
> +
> +    /* Clear out any devices from a previous (re-)START. */
> +    QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
> +        QLIST_REMOVE(node, next);
> +        g_free(node);
> +    }
> +
> +    QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) {
> +        DeviceState *qdev = child->child;
> +        I3CTarget *target = I3C_TARGET(qdev);
> +
> +        if (i3c_target_match_and_add(bus, target, address, event)) {
> +            return true;
> +        }
> +    }
> +
> +    /* No one on the bus could respond. */
> +    return false;
> +}
> +
> +/* Class-level event handling, since we do some CCCs at the class level. */
> +static int i3c_target_event(I3CTarget *t, enum I3CEvent event)
> +{
> +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> +    trace_i3c_target_event(t->address, event);
> +
> +    if (event == I3C_STOP) {
> +        t->curr_ccc = 0;
> +        t->ccc_byte_offset = 0;
> +        t->in_ccc = false;
> +    }
> +    return tc->event(t, event);
> +}
> +
> +/*
> + * Sends a START or repeated START and the address for an I3C transaction.
> + *
> + * This function returns 0 if a device on the bus was able to respond to the
> + * address, and non-zero otherwise.
> + * A non-zero return represents a NACK.
> + */
> +static int i3c_do_start_transfer(I3CBus *bus, uint8_t address,
> +                                 enum I3CEvent event)
> +{
> +    I3CTargetClass *tc;
> +    I3CNode *node;
> +
> +    if (address == I3C_BROADCAST) {
> +        bus->broadcast = true;
> +        /* If we're not in ENTDAA, a broadcast is the start of a new CCC. */
> +        if (!bus->in_entdaa) {
> +            bus->in_ccc = false;
> +        }
> +    } else {
> +        bus->broadcast = false;
> +    }
> +
> +    /* No one responded to the address, NACK it. */
> +    if (!i3c_scan_bus(bus, address, event)) {
> +        return -1;
> +    }
> +
> +    QLIST_FOREACH(node, &bus->current_devs, next) {
> +        I3CTarget *t = node->target;
> +
> +        tc = I3C_TARGET_GET_CLASS(t);
> +        if (tc->event) {
> +            int rv = i3c_target_event(t, event);
> +            if (rv && !bus->broadcast) {
> +                return rv;
> +            }
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv)
> +{
> +    trace_i3c_start_transfer(address, is_recv);
> +    return i3c_do_start_transfer(bus, address, is_recv
> +                                               ? I3C_START_RECV
> +                                               : I3C_START_SEND);
> +}
> +
> +int i3c_start_recv(I3CBus *bus, uint8_t address)
> +{
> +    trace_i3c_start_transfer(address, true);
> +    return i3c_do_start_transfer(bus, address, I3C_START_RECV);
> +}
> +
> +int i3c_start_send(I3CBus *bus, uint8_t address)
> +{
> +    trace_i3c_start_transfer(address, false);
> +    return i3c_do_start_transfer(bus, address, I3C_START_SEND);
> +}
> +
> +void i3c_end_transfer(I3CBus *bus)
> +{
> +    I3CTargetClass *tc;
> +    I3CNode *node, *next;
> +
> +    trace_i3c_end_transfer();
> +
> +    /*
> +     * If we're in ENTDAA, we need to notify all devices when ENTDAA is done.
> +     * This is because everyone initially participates due to the broadcast,
> +     * but gradually drops out as they get assigned addresses.
> +     * Since the current_devs list only stores who's currently participating,
> +     * and not everyone who previously participated, we send the STOP to all
> +     * children.
> +     */
> +    if (bus->in_entdaa) {
> +        BusChild *child;
> +
> +        QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) {
> +            DeviceState *qdev = child->child;
> +            I3CTarget *t = I3C_TARGET(qdev);
> +            tc = I3C_TARGET_GET_CLASS(t);
> +            if (tc->event) {
> +                i3c_target_event(t, I3C_STOP);
> +            }
> +        }
> +    } else {
> +        QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
> +            I3CTarget *t = node->target;
> +            tc = I3C_TARGET_GET_CLASS(t);
> +            if (tc->event) {
> +                i3c_target_event(t, I3C_STOP);
> +            }
> +            QLIST_REMOVE(node, next);
> +            g_free(node);
> +        }
> +    }
> +    bus->broadcast = false;
> +    bus->in_entdaa = false;
> +    bus->in_ccc = false;
> +}
> +
> +/*
> + * Any CCCs that are universal across all I3C devices should be handled here.
> + * Once they're handled, we pass the CCC up to the I3C target to do anything
> + * else it may want with the bytes.
> + */
> +static int i3c_target_handle_ccc_write(I3CTarget *t, const uint8_t *data,
> +                                       uint32_t num_to_send, uint32_t *num_sent)
> +{
> +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> +    *num_sent = 0;
> +
> +    /* Is this the start of a new CCC? */
> +    if (!t->in_ccc) {
> +        t->curr_ccc = *data;
> +        t->in_ccc = true;
> +        *num_sent = 1;
> +        trace_i3c_target_handle_ccc(t->address, t->curr_ccc);
> +    }
> +
> +    switch (t->curr_ccc) {
> +    case I3C_CCC_ENTDAA:
> +        /*
> +         * This is the last byte of ENTDAA, the controller is assigning us an
> +         * address.
> +         */
> +        if (t->ccc_byte_offset == 8) {
> +            t->address = *data;
> +            t->in_ccc = false;
> +            t->curr_ccc = 0;
> +            t->ccc_byte_offset = 0;
> +            *num_sent = 1;
> +        }
> +        break;
> +    case I3C_CCCD_SETDASA:
> +        t->address = t->static_address;
> +        break;
> +    case I3C_CCC_SETAASA:
> +        t->address = t->static_address;
> +        break;
> +    case I3C_CCC_RSTDAA:
> +        t->address = 0;
> +        break;
> +    case I3C_CCCD_SETNEWDA:
> +        /* If this isn't the CCC byte, it's our new address. */
> +        if (*num_sent == 0) {
> +            t->address = *data;
> +            *num_sent = 1;
> +        }
> +        break;
> +    case I3C_CCC_ENTTM:
> +        /*
> +         * If there are still more to look at, the next byte is the test mode
> +         * byte.
> +         */
> +        if (*num_sent != num_to_send) {
> +            /* Enter test mode if the byte is non-zero. Otherwise exit. */
> +            t->in_test_mode = !!data[*num_sent];
> +            ++*num_sent;
> +        }
> +        break;
> +    /* Ignore other CCCs it's better to handle on a device-by-device basis. */
> +    default:
> +        break;
> +    }
> +    return tc->handle_ccc_write(t, data, num_to_send, num_sent);
> +}
> +
> +int i3c_send_byte(I3CBus *bus, uint8_t data)
> +{
> +    /*
> +     * Ignored, the caller can determine how many were sent based on if this was
> +     * ACKed/NACKed.
> +     */
> +    uint32_t num_sent;
> +    return i3c_send(bus, &data, 1, &num_sent);
> +}
> +
> +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
> +             uint32_t *num_sent)
> +{
> +    I3CTargetClass *tc;
> +    I3CTarget *t;
> +    I3CNode *node;
> +    int ret = 0;
> +
> +    /* If this message is a broadcast and no CCC has been found, grab it. */
> +    if (bus->broadcast && !bus->in_ccc) {
> +        bus->ccc = *data;
> +        bus->in_ccc = true;
> +        /*
> +         * We need to keep track if we're currently in ENTDAA.
> +         * On any other CCC, the CCC is over on a RESTART or STOP, but ENTDAA
> +         * is only over on a STOP.
> +         */
> +        if (bus->ccc == I3C_CCC_ENTDAA) {
> +            bus->in_entdaa = true;
> +        }
> +    }
> +
> +    QLIST_FOREACH(node, &bus->current_devs, next) {
> +        t = node->target;
> +        tc = I3C_TARGET_GET_CLASS(t);
> +        if (bus->in_ccc) {
> +            if (!tc->handle_ccc_write) {
> +                ret = -1;
> +                continue;
> +            }
> +            ret = i3c_target_handle_ccc_write(t, data, num_to_send, num_sent);
> +            /* Targets should only NACK on a direct CCC. */
> +            if (ret && !CCC_IS_DIRECT(bus->ccc)) {
> +                ret = 0;
> +            }
> +        } else {
> +            if (tc->send) {
> +                ret = ret || tc->send(t, data, num_to_send, num_sent);
> +            } else {
> +                ret = -1;
> +            }
> +        }
> +    }
> +
> +    trace_i3c_send(*num_sent, num_to_send, ret == 0);
> +
> +    return ret ? -1 : 0;
> +}
> +
> +static int i3c_target_handle_ccc_read(I3CTarget *t, uint8_t *data,
> +                                      uint32_t num_to_read, uint32_t *num_read)
> +{
> +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> +    uint8_t read_count = 0;
> +    uint64_t pid;
> +
> +    switch (t->curr_ccc) {
> +    case I3C_CCC_ENTDAA:
> +        if (t->in_test_mode) {
> +            pid = TEST_MODE_PROVISIONED_ID;
> +        } else {
> +            pid = t->pid;
> +        }
> +        /* Return the 6-byte PID, followed by BCR then DCR. */
> +        while (t->ccc_byte_offset < 6) {
> +            if (read_count >= num_to_read) {
> +                break;
> +            }
> +            data[read_count] = (pid >> (t->ccc_byte_offset * 8)) & 0xff;
> +            t->ccc_byte_offset++;
> +            read_count++;
> +        }
> +        if (read_count < num_to_read) {
> +            data[read_count] = t->bcr;
> +            t->ccc_byte_offset++;
> +            read_count++;
> +        }
> +        if (read_count < num_to_read) {
> +            data[read_count] = t->dcr;
> +            t->ccc_byte_offset++;
> +            read_count++;
> +        }
> +        *num_read = read_count;
> +        break;
> +    case I3C_CCCD_GETPID:
> +        while (t->ccc_byte_offset < 6) {
> +            if (read_count >= num_to_read) {
> +                break;
> +            }
> +            data[read_count] = (t->pid >> (t->ccc_byte_offset * 8)) & 0xff;
> +            t->ccc_byte_offset++;
> +            read_count++;
> +        }
> +        *num_read = read_count;
> +        break;
> +    case I3C_CCCD_GETBCR:
> +        *data = t->bcr;
> +        *num_read = 1;
> +        break;
> +    case I3C_CCCD_GETDCR:
> +        *data = t->dcr;
> +        *num_read = 1;
> +        break;
> +    default:
> +        /* Unhandled on the I3CTarget class level. */
> +        break;
> +    }
> +
> +    return tc->handle_ccc_read(t, data, num_to_read, num_read);
> +}
> +
> +int i3c_recv_byte(I3CBus *bus, uint8_t *data)
> +{
> +     /*
> +      * Ignored, the caller can determine how many bytes were read based on if
> +      * this is ACKed/NACKed.
> +      */
> +    uint32_t num_read;
> +    return i3c_recv(bus, data, 1, &num_read);
> +}
> +
> +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read,
> +             uint32_t *num_read)
> +{
> +    int ret = 0;
> +    I3CTargetClass *tc;
> +    I3CTarget *t;
> +
> +    *data = 0xff;
> +    if (!QLIST_EMPTY(&bus->current_devs)) {
> +        tc = I3C_TARGET_GET_CLASS(QLIST_FIRST(&bus->current_devs)->target);
> +        t = QLIST_FIRST(&bus->current_devs)->target;
> +        if (bus->in_ccc) {
> +            if (!tc->handle_ccc_read) {
> +                return -1;
> +            }
> +            ret = i3c_target_handle_ccc_read(t, data, num_to_read, num_read);
> +        } else {
> +            if (tc->recv) {
> +                /*
> +                 * Targets cannot NACK on a direct transfer, so the data
> +                 * is returned directly.
> +                 */
> +                *num_read = tc->recv(t, data, num_to_read);
> +            }
> +        }
> +    }
> +
> +    trace_i3c_recv(*num_read, num_to_read, ret == 0);
> +
> +    return ret;
> +}
> +
> +void i3c_nack(I3CBus *bus)
> +{
> +    I3CTargetClass *tc;
> +    I3CNode *node;
> +
> +    if (QLIST_EMPTY(&bus->current_devs)) {
> +        return;
> +    }
> +
> +    QLIST_FOREACH(node, &bus->current_devs, next) {
> +        tc = I3C_TARGET_GET_CLASS(node->target);
> +        if (tc->event) {
> +            i3c_target_event(node->target, I3C_NACK);
> +        }
> +    }
> +}
> +
> +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv)
> +{
> +    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
> +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> +    trace_i3c_target_send_ibi(addr, is_recv);
> +    return bc->ibi_handle(bus, addr, is_recv);
> +}
> +
> +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data)
> +{
> +    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
> +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> +    trace_i3c_target_send_ibi_bytes(data);
> +    return bc->ibi_recv(bus, data);
> +}
> +
> +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data)
> +{
> +    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
> +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> +    trace_i3c_target_ibi_finish();
> +    return bc->ibi_finish(bus);
> +}
> +
> +static bool i3c_addr_is_rsvd(uint8_t addr)
> +{
> +    const bool is_rsvd[255] = {

This array should be a static const and its size needs a fix (0x100)

Thanks,

C.


> +        [0x00] = true,
> +        [0x01] = true,
> +        [0x02] = true,
> +        [0x3e] = true,
> +        [0x5e] = true,
> +        [0x6e] = true,
> +        [0x76] = true,
> +        [0x7a] = true,
> +        [0x7c] = true,
> +        [0x7e] = true,
> +        [0x7f] = true,
> +    };
> +
> +    return is_rsvd[addr];
> +}
> +
> +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
> +                          uint8_t bcr, uint64_t pid)
> +{
> +    DeviceState *dev;
> +
> +    dev = qdev_new(name);
> +    qdev_prop_set_uint8(dev, "static-address", addr);
> +    qdev_prop_set_uint8(dev, "dcr", dcr);
> +    qdev_prop_set_uint8(dev, "bcr", bcr);
> +    qdev_prop_set_uint64(dev, "pid", pid);
> +
> +    if (i3c_addr_is_rsvd(addr)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(dev));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created with reserved "
> +                      "address 0x%.2x\n", path, addr);
> +    }
> +    return I3C_TARGET(dev);
> +}
> +
> +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp)
> +{
> +    return qdev_realize_and_unref(&dev->parent_obj, &bus->parent_obj, errp);
> +}
> +
> +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, uint8_t addr,
> +                                    uint8_t dcr, uint8_t bcr, uint64_t pid)
> +{
> +    I3CTarget *dev = i3c_target_new(name, addr, dcr, bcr, pid);
> +    dev->address = 0;
> +    i3c_target_realize_and_unref(dev, bus, &error_abort);
> +
> +    return dev;
> +}
> +
> +/* Legacy I2C functions. */
> +void legacy_i2c_nack(I3CBus *bus)
> +{
> +    trace_legacy_i2c_nack();
> +    i2c_nack(bus->i2c_bus);
> +}
> +
> +uint8_t legacy_i2c_recv(I3CBus *bus)
> +{
> +    uint8_t byte = i2c_recv(bus->i2c_bus);
> +    trace_legacy_i2c_recv(byte);
> +    return byte;
> +}
> +
> +int legacy_i2c_send(I3CBus *bus, uint8_t data)
> +{
> +    trace_legacy_i2c_send(data);
> +    return i2c_send(bus->i2c_bus, data);
> +}
> +
> +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv)
> +{
> +    trace_legacy_i2c_start_transfer(address, is_recv);
> +    return i2c_start_transfer(bus->i2c_bus, address, is_recv);
> +}
> +
> +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address)
> +{
> +    trace_legacy_i2c_start_transfer(address, true);
> +    return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/true);
> +}
> +
> +int legacy_i2c_start_send(I3CBus *bus, uint8_t address)
> +{
> +    trace_legacy_i2c_start_transfer(address, false);
> +    return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/false);
> +}
> +
> +void legacy_i2c_end_transfer(I3CBus *bus)
> +{
> +    trace_legacy_i2c_end_transfer();
> +    i2c_end_transfer(bus->i2c_bus);
> +}
> +
> +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name,
> +                                          uint8_t addr)
> +{
> +    I2CSlave *dev = i2c_slave_new(name, addr);
> +
> +    i2c_slave_realize_and_unref(dev, bus->i2c_bus, &error_abort);
> +    return dev;
> +}
> +
> +static void i3c_target_class_init(ObjectClass *klass, const void *data)
> +{
> +    DeviceClass *k = DEVICE_CLASS(klass);
> +    I3CTargetClass *sc = I3C_TARGET_CLASS(klass);
> +    set_bit(DEVICE_CATEGORY_MISC, k->categories);
> +    k->bus_type = TYPE_I3C_BUS;
> +    device_class_set_props(k, i3c_props);
> +    sc->target_match = i3c_target_match;
> +}
> +
> +static const TypeInfo i3c_types[] = {
> +    {
> +        .name = TYPE_I3C_BUS,
> +        .parent = TYPE_BUS,
> +        .instance_size = sizeof(I3CBus),
> +        .class_size = sizeof(I3CBusClass),
> +    },
> +    {
> +        .name = TYPE_I3C_TARGET,
> +        .parent = TYPE_DEVICE,
> +        .instance_size = sizeof(I3CTarget),
> +        .abstract = true,
> +        .class_size = sizeof(I3CTargetClass),
> +        .class_init = i3c_target_class_init,
> +    },
> +};
> +
> +DEFINE_TYPES(i3c_types)
> diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build
> index ebf20325cb..fb127613fe 100644
> --- a/hw/i3c/meson.build
> +++ b/hw/i3c/meson.build
> @@ -1,3 +1,4 @@
>   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'))
>   system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss)
> diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
> index 3ead84eb45..cdf7cb07f6 100644
> --- a/hw/i3c/trace-events
> +++ b/hw/i3c/trace-events
> @@ -5,3 +5,19 @@ aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read: offset 0x%" PRIx64 "
>   aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64 " data 0x%" PRIx64
>   aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
>   aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
> +
> +# core.c
> +i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " event 0x%" PRIx8
> +i3c_target_handle_ccc(uint8_t address, uint8_t ccc) "I3C target 0x%" PRIx8 " handling CCC 0x%" PRIx8
> +i3c_target_send_ibi(uint8_t address, bool is_recv) "I3C target IBI address 0x%" PRIx8 " RnW=%d"
> +i3c_target_send_ibi_bytes(uint8_t byte) "I3C target IBI byte 0x%" PRIx8
> +i3c_target_ibi_finish(void) "I3C target IBI finish"
> +i3c_start_transfer(uint8_t address, bool is_recv) "I3C START with address 0x%" PRIx8 " is_recv=%d"
> +i3c_end_transfer(void) "I3C transfer done"
> +i3c_send(uint32_t num_sent, uint32_t num_to_send, bool ack) "I3C send %" PRId32 "/%" PRId32 " bytes, ack=%d"
> +i3c_recv(uint32_t num_read, uint32_t num_to_read, bool ack) "I3C recv %" PRId32 "/%" PRId32 " bytes, ack=%d"
> +legacy_i2c_nack(void) "Legacy I2C NACK"
> +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"



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX
  2026-02-25  2:12 ` [PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX Jamin Lin
@ 2026-02-27  9:56   ` Cédric Le Goater
  2026-03-02  3:33     ` Jamin Lin
  0 siblings, 1 reply; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-27  9:56 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee, Troy Lee,
	Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Stephen Longfield, Patrick Venture

On 2/25/26 03:12, Jamin Lin wrote:
> This adds data and CCC transmission, reception, and the associated
> queues required for data transmission and reception to happen.
> 
> The I3C controller transmits data by the user writing into a command
> queue. When the queue has a command and an argument in it, the
> controller starts executing the command.
> 
> The controller can execute 1 of 3 ways:
> 1. A larger data transfer that involves using the TX and RX queues. This
>     is the most common way the controller does transactions.
> 
> 2. A small data transfer that involves sending a couple bytes passed
>     into the command queue argument.
> 
> 3. An address assignment command. This is how the controller does
>     ENTDAA. When ENTDAA succeeds in assigning an address to a target, it
>     updates the controller's char table with the target's PID, BCR, and
>     DCR.
> 
> The controller determines what addresses to send by looking at the index
> in the device address table specified by the argument in the command
> queue. ENTDAA also uses these addresses to assign to targets on the bus.
> 
> When the controller is done executing a command, it puts a response in
> the response queue indicating how command execution went.
> 
> In order for the user to send and receive data to/from the controller,
> the user reads/writes to a bidirectional TX/RX port.
> 
> Signed-off-by: Joe Komlodi <komlodi@google.com>
> Reviewed-by: Stephen Longfield <slongfield@google.com>
> Reviewed-by: Patrick Venture <venture@google.com>
> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> ---
>   include/hw/i3c/aspeed_i3c.h |   1 +
>   include/hw/i3c/dw-i3c.h     | 143 +++++-
>   hw/i3c/dw-i3c.c             | 882 +++++++++++++++++++++++++++++++++++-
>   hw/i3c/trace-events         |  10 +
>   4 files changed, 1030 insertions(+), 6 deletions(-)
> 
> diff --git a/include/hw/i3c/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
> index ade5c42d39..b8827d31d7 100644
> --- a/include/hw/i3c/aspeed_i3c.h
> +++ b/include/hw/i3c/aspeed_i3c.h
> @@ -2,6 +2,7 @@
>    * ASPEED I3C Controller
>    *
>    * Copyright (C) 2021 ASPEED Technology Inc.
> + * Copyright (C) 2023 Google, LLC
>    *
>    * This code is licensed under the GPL version 2 or later.  See
>    * the COPYING file in the top-level directory.
> diff --git a/include/hw/i3c/dw-i3c.h b/include/hw/i3c/dw-i3c.h
> index 7143e8ca7a..c50d67cc6b 100644
> --- a/include/hw/i3c/dw-i3c.h
> +++ b/include/hw/i3c/dw-i3c.h
> @@ -10,20 +10,159 @@
>   #ifndef DW_I3C_H
>   #define DW_I3C_H
>   
> +#include "qemu/fifo32.h"
> +#include "hw/i3c/i3c.h"
>   #include "hw/core/sysbus.h"
>   
>   #define TYPE_DW_I3C "dw.i3c"
>   OBJECT_DECLARE_SIMPLE_TYPE(DWI3C, DW_I3C)
>   
> -#define DW_I3C_NR_REGS (0x300 >> 2)
> +/*
> + * Sufficiently large enough to handle configurations with large device address
> + * tables.
> + */
> +#define DW_I3C_NR_REGS (0x1000 >> 2)
> +
> +/* From datasheet. */
> +#define DW_I3C_CMD_ATTR_TRANSFER_CMD 0
> +#define DW_I3C_CMD_ATTR_TRANSFER_ARG 1
> +#define DW_I3C_CMD_ATTR_SHORT_DATA_ARG 2
> +#define DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD 3
> +
> +/* Enum values from datasheet. */
> +typedef enum DWI3CRespQueueErr {
> +    DW_I3C_RESP_QUEUE_ERR_NONE = 0,
> +    DW_I3C_RESP_QUEUE_ERR_CRC = 1,
> +    DW_I3C_RESP_QUEUE_ERR_PARITY = 2,
> +    DW_I3C_RESP_QUEUE_ERR_FRAME = 3,
> +    DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK = 4,
> +    DW_I3C_RESP_QUEUE_ERR_DAA_NACK = 5,
> +    DW_I3C_RESP_QUEUE_ERR_OVERFLOW = 6,
> +    DW_I3C_RESP_QUEUE_ERR_ABORTED = 8,
> +    DW_I3C_RESP_QUEUE_ERR_I2C_NACK = 9,
> +} DWI3CRespQueueErr;
> +
> +typedef enum DWI3CTransferState {
> +    DW_I3C_TRANSFER_STATE_IDLE = 0x00,
> +    DW_I3C_TRANSFER_STATE_START = 0x01,
> +    DW_I3C_TRANSFER_STATE_RESTART = 0x02,
> +    DW_I3C_TRANSFER_STATE_STOP = 0x03,
> +    DW_I3C_TRANSFER_STATE_START_HOLD = 0x04,
> +    DW_I3C_TRANSFER_STATE_BROADCAST_W = 0x05,
> +    DW_I3C_TRANSFER_STATE_BROADCAST_R = 0x06,
> +    DW_I3C_TRANSFER_STATE_DAA = 0x07,
> +    DW_I3C_TRANSFER_STATE_DAA_GEN = 0x08,
> +    DW_I3C_TRANSFER_STATE_CCC_BYTE = 0x0b,
> +    DW_I3C_TRANSFER_STATE_HDR_CMD = 0x0c,
> +    DW_I3C_TRANSFER_STATE_WRITE = 0x0d,
> +    DW_I3C_TRANSFER_STATE_READ = 0x0e,
> +    DW_I3C_TRANSFER_STATE_IBI_READ = 0x0f,
> +    DW_I3C_TRANSFER_STATE_IBI_DIS = 0x10,
> +    DW_I3C_TRANSFER_STATE_HDR_DDR_CRC = 0x11,
> +    DW_I3C_TRANSFER_STATE_CLK_STRETCH = 0x12,
> +    DW_I3C_TRANSFER_STATE_HALT = 0x13,
> +} DWI3CTransferState;
> +
> +typedef enum DWI3CTransferStatus {
> +    DW_I3C_TRANSFER_STATUS_IDLE = 0x00,
> +    DW_I3C_TRANSFER_STATUS_BROACAST_CCC = 0x01,
> +    DW_I3C_TRANSFER_STATUS_DIRECT_CCC_W = 0x02,
> +    DW_I3C_TRANSFER_STATUS_DIRECT_CCC_R = 0x03,
> +    DW_I3C_TRANSFER_STATUS_ENTDAA = 0x04,
> +    DW_I3C_TRANSFER_STATUS_SETDASA = 0x05,
> +    DW_I3C_TRANSFER_STATUS_I3C_SDR_W = 0x06,
> +    DW_I3C_TRANSFER_STATUS_I3C_SDR_R = 0x07,
> +    DW_I3C_TRANSFER_STATUS_I2C_SDR_W = 0x08,
> +    DW_I3C_TRANSFER_STATUS_I2C_SDR_R = 0x09,
> +    DW_I3C_TRANSFER_STATUS_HDR_TS_W = 0x0a,
> +    DW_I3C_TRANSFER_STATUS_HDR_TS_R = 0x0b,
> +    DW_I3C_TRANSFER_STATUS_HDR_DDR_W = 0x0c,
> +    DW_I3C_TRANSFER_STATUS_HDR_DDR_R = 0x0d,
> +    DW_I3C_TRANSFER_STATUS_IBI = 0x0e,
> +    DW_I3C_TRANSFER_STATUS_HALT = 0x0f,
> +} DWI3CTransferStatus;
> +
> +/*
> + * Transfer commands and arguments are 32-bit wide values that the user passes
> + * into the command queue. We interpret each 32-bit word based on the cmd_attr
> + * field.
> + */
> +typedef struct DWI3CTransferCmd {
> +    uint8_t cmd_attr:3;
> +    uint8_t tid:4; /* Transaction ID */
> +    uint16_t cmd:8;
> +    uint8_t cp:1; /* Command present */
> +    uint8_t dev_index:5;
> +    uint8_t speed:3;
> +    uint8_t resv0:1;
> +    uint8_t dbp:1; /* Defining byte present */
> +    uint8_t roc:1; /* Response on completion */
> +    uint8_t sdap:1; /* Short data argument present */
> +    uint8_t rnw:1; /* Read not write */
> +    uint8_t resv1:1;
> +    uint8_t toc:1; /* Termination (I3C STOP) on completion */
> +    uint8_t pec:1; /* Parity error check enabled */
> +} DWI3CTransferCmd;
> +
> +typedef struct DWI3CTransferArg {
> +    uint8_t cmd_attr:3;
> +    uint8_t resv:5;
> +    uint8_t db; /* Defining byte */
> +    uint16_t data_len;
> +} DWI3CTransferArg;
> +
> +typedef struct DWI3CShortArg {
> +    uint8_t cmd_attr:3;
> +    uint8_t byte_strb:3;
> +    uint8_t resv:2;
> +    uint8_t byte0;
> +    uint8_t byte1;
> +    uint8_t byte2;
> +} DWI3CShortArg;
> +
> +typedef struct DWI3CAddrAssignCmd {
> +    uint8_t cmd_attr:3;
> +    uint8_t tid:4; /* Transaction ID */
> +    uint16_t cmd:8;
> +    uint8_t resv0:1;
> +    uint8_t dev_index:5;
> +    uint16_t dev_count:5;
> +    uint8_t roc:1; /* Response on completion */
> +    uint8_t resv1:3;
> +    uint8_t toc:1; /* Termination (I3C STOP) on completion */
> +    uint8_t resv2:1;
> +} DWI3CAddrAssignCmd;
> +
> +typedef union DWI3CCmdQueueData {
> +    uint32_t word;
> +    DWI3CTransferCmd transfer_cmd;
> +    DWI3CTransferArg transfer_arg;
> +    DWI3CShortArg short_arg;
> +    DWI3CAddrAssignCmd addr_assign_cmd;
> +} DWI3CCmdQueueData;
>   
>   struct DWI3C {
>       SysBusDevice parent_obj;
>   
>       MemoryRegion mr;
>       qemu_irq irq;
> +    I3CBus *bus;
> +
> +    Fifo32 cmd_queue;
> +    Fifo32 resp_queue;
> +    Fifo32 tx_queue;
> +    Fifo32 rx_queue;
>   
> -    uint8_t id;
> +    struct {
> +        uint8_t id;
> +        uint8_t cmd_resp_queue_capacity_bytes;
> +        uint16_t tx_rx_queue_capacity_bytes;
> +        uint8_t num_addressable_devices;
> +        uint16_t dev_addr_table_pointer;
> +        uint16_t dev_addr_table_depth;
> +        uint16_t dev_char_table_pointer;
> +        uint16_t dev_char_table_depth;
> +    } cfg;
>       uint32_t regs[DW_I3C_NR_REGS];
>   };
>   
> diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
> index 0b99c7edfb..2453d74a8c 100644
> --- a/hw/i3c/dw-i3c.c
> +++ b/hw/i3c/dw-i3c.c
> @@ -336,12 +336,179 @@ static const uint32_t dw_i3c_ro[DW_I3C_NR_REGS] = {
>       [R_SLAVE_CONFIG]                = 0xffffffff,
>   };
>   
> +static inline bool dw_i3c_has_hdr_ts(DWI3C *s)
> +{
> +    return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_TS);
> +}
> +
> +static inline bool dw_i3c_has_hdr_ddr(DWI3C *s)
> +{
> +    return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_DDR);
> +}
> +
> +static inline bool dw_i3c_can_transmit(DWI3C *s)
> +{
> +    /*
> +     * We can only transmit if we're enabled and the resume bit is cleared.
> +     * The resume bit is set on a transaction error, and software must clear it.
> +     */
> +    return ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_EN) &&
> +           !ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_RESUME);
> +}
> +
> +static inline uint8_t dw_i3c_fifo_threshold_from_reg(uint8_t regval)
> +{
> +    return regval = regval ? (2 << regval) : 1;
> +}
> +
>   static void dw_i3c_update_irq(DWI3C *s)
>   {
>       bool level = !!(s->regs[R_INTR_SIGNAL_EN] & s->regs[R_INTR_STATUS]);
>       qemu_set_irq(s->irq, level);
>   }
>   
> +static void dw_i3c_end_transfer(DWI3C *s, bool is_i2c)
> +{
> +    if (is_i2c) {
> +        legacy_i2c_end_transfer(s->bus);
> +    } else {
> +        i3c_end_transfer(s->bus);
> +    }
> +}
> +
> +static int dw_i3c_send_start(DWI3C *s, uint8_t addr, bool is_recv, bool is_i2c)
> +{
> +    int ret;
> +
> +    if (is_i2c) {
> +        ret = legacy_i2c_start_transfer(s->bus, addr, is_recv);
> +    } else {
> +        ret = i3c_start_transfer(s->bus, addr, is_recv);
> +    }
> +    if (ret) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed on TX with addr 0x%.2x\n",
> +                      path, addr);
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                         DW_I3C_TRANSFER_STATE_HALT);
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
> +                         DW_I3C_TRANSFER_STATUS_HALT);
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
> +        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
> +    }
> +
> +    return ret;
> +}
> +
> +static int dw_i3c_send(DWI3C *s, const uint8_t *data, uint32_t num_to_send,
> +                       uint32_t *num_sent, bool is_i2c)
> +{
> +    int ret;
> +    uint32_t i;
> +
> +    *num_sent = 0;
> +    if (is_i2c) {
> +        /* Legacy I2C must be byte-by-byte. */
> +        for (i = 0; i < num_to_send; i++) {
> +            ret = legacy_i2c_send(s->bus, data[i]);
> +            if (ret) {
> +                break;
> +            }
> +            (*num_sent)++;
> +        }
> +    } else {
> +        ret = i3c_send(s->bus, data, num_to_send, num_sent);
> +    }
> +    if (ret) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed sending byte 0x%.2x\n",
> +                      path, data[*num_sent]);
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                         DW_I3C_TRANSFER_STATE_HALT);
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
> +                         DW_I3C_TRANSFER_STATUS_HALT);
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
> +        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
> +    }
> +
> +    trace_dw_i3c_send(s->cfg.id, *num_sent);
> +
> +    return ret;
> +}
> +
> +static int dw_i3c_send_byte(DWI3C *s, uint8_t byte, bool is_i2c)
> +{
> +    /*
> +     * Ignored, the caller will know if we sent 0 or 1 bytes depending on if
> +     * we were ACKed/NACKed.
> +     */
> +    uint32_t num_sent;
> +    return dw_i3c_send(s, &byte, 1, &num_sent, is_i2c);
> +}
> +
> +static int dw_i3c_recv_data(DWI3C *s, bool is_i2c, uint8_t *data,
> +                            uint16_t num_to_read, uint32_t *num_read)
> +{
> +    int ret;
> +
> +    if (is_i2c) {
> +        for (uint16_t i = 0; i < num_to_read; i++) {
> +            data[i] = legacy_i2c_recv(s->bus);
> +        }
> +        /* I2C devices can neither NACK a read, nor end transfers early. */
> +        *num_read = num_to_read;
> +        trace_dw_i3c_recv_data(s->cfg.id, *num_read);
> +        return 0;
> +    }
> +    /* I3C devices can NACK if the controller sends an unsupported CCC. */
> +    ret = i3c_recv(s->bus, data, num_to_read, num_read);
> +    if (ret) {
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed receiving byte\n",
> +                      object_get_canonical_path(OBJECT(s)));
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                         DW_I3C_TRANSFER_STATE_HALT);
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
> +                         DW_I3C_TRANSFER_STATUS_HALT);
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
> +        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
> +    }
> +
> +    trace_dw_i3c_recv_data(s->cfg.id, *num_read);
> +
> +    return ret;
> +}
> +
> +static inline bool dw_i3c_target_is_i2c(DWI3C *s, uint16_t offset)
> +{
> +    /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
> +    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
> +                                          ADDR) / sizeof(uint32_t)) + offset;
> +    return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
> +                   LEGACY_I2C_DEVICE);
> +}
> +
> +static uint8_t dw_i3c_target_addr(DWI3C *s, uint16_t offset)
> +{
> +    if (offset > s->cfg.num_addressable_devices) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Device addr table offset %d out of "
> +                      "bounds\n", path, offset);
> +        /* If we're out of bounds, return an address of 0. */
> +        return 0;
> +    }
> +
> +    /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
> +    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
> +                                          ADDR) / sizeof(uint32_t)) + offset;
> +    /* I2C devices use a static address. */
> +    if (dw_i3c_target_is_i2c(s, offset)) {
> +        return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
> +                          DEV_STATIC_ADDR);
> +    }
> +    return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
> +                      DEV_DYNAMIC_ADDR);
> +}
> +
>   static uint32_t dw_i3c_intr_status_r(DWI3C *s)
>   {
>       /* Only return the status whose corresponding EN bits are set. */
> @@ -376,6 +543,56 @@ static void dw_i3c_intr_force_w(DWI3C *s, uint32_t val)
>       dw_i3c_update_irq(s);
>   }
>   
> +static uint32_t dw_i3c_pop_rx(DWI3C *s)
> +{
> +    if (fifo32_is_empty(&s->rx_queue)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read RX FIFO when empty\n",
> +                      path);
> +        return 0;
> +    }
> +
> +    uint32_t val = fifo32_pop(&s->rx_queue);
> +    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR,
> +                     fifo32_num_used(&s->rx_queue));
> +
> +    /* Threshold is 2^RX_BUF_THLD. */
> +    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
> +                                         RX_BUF_THLD);
> +    threshold = dw_i3c_fifo_threshold_from_reg(threshold);
> +    if (fifo32_num_used(&s->rx_queue) < threshold) {
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 0);
> +        dw_i3c_update_irq(s);
> +    }
> +
> +    trace_dw_i3c_pop_rx(s->cfg.id, val);
> +    return val;
> +}
> +
> +static uint32_t dw_i3c_resp_queue_port_r(DWI3C *s)
> +{
> +    if (fifo32_is_empty(&s->resp_queue)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read response FIFO when "
> +                      "empty\n", path);
> +        return 0;
> +    }
> +
> +    uint32_t val = fifo32_pop(&s->resp_queue);
> +    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
> +                     fifo32_num_used(&s->resp_queue));
> +
> +    /* Threshold is the register value + 1. */
> +    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
> +                                         RESP_BUF_THLD) + 1;
> +    if (fifo32_num_used(&s->resp_queue) < threshold) {
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 0);
> +        dw_i3c_update_irq(s);
> +    }
> +
> +    return val;
> +}
> +
>   static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
>   {
>       DWI3C *s = DW_I3C(opaque);
> @@ -392,16 +609,635 @@ static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
>       case R_INTR_STATUS:
>           value = dw_i3c_intr_status_r(s);
>           break;
> +    case R_RX_TX_DATA_PORT:
> +        value = dw_i3c_pop_rx(s);
> +        break;
> +    case R_RESPONSE_QUEUE_PORT:
> +        value = dw_i3c_resp_queue_port_r(s);
> +        break;
>       default:
>           value = s->regs[addr];
>           break;
>       }
>   
> -    trace_dw_i3c_read(s->id, offset, value);
> +    trace_dw_i3c_read(s->cfg.id, offset, value);
>   
>       return value;
>   }
>   
> +static void dw_i3c_resp_queue_push(DWI3C *s, uint8_t err, uint8_t tid,
> +                                   uint8_t ccc_type, uint16_t data_len)
> +{
> +    uint32_t val = 0;
> +    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, ERR_STATUS, err);
> +    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, TID, tid);
> +    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, CCCT, ccc_type);
> +    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, DL, data_len);
> +    if (!fifo32_is_full(&s->resp_queue)) {
> +        trace_dw_i3c_resp_queue_push(s->cfg.id, val);
> +        fifo32_push(&s->resp_queue, val);
> +    }
> +
> +    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
> +                     fifo32_num_used(&s->resp_queue));
> +    /* Threshold is the register value + 1. */
> +    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
> +                                         RESP_BUF_THLD) + 1;
> +    if (fifo32_num_used(&s->resp_queue) >= threshold) {
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 1);
> +        dw_i3c_update_irq(s);
> +    }
> +}
> +
> +static void dw_i3c_push_tx(DWI3C *s, uint32_t val)
> +{
> +    if (fifo32_is_full(&s->tx_queue)) {
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to TX FIFO when "
> +                      "full\n", object_get_canonical_path(OBJECT(s)));
> +        return;
> +    }
> +
> +    trace_dw_i3c_push_tx(s->cfg.id, val);
> +    fifo32_push(&s->tx_queue, val);
> +    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC,
> +                     fifo32_num_free(&s->tx_queue));
> +
> +    /* Threshold is 2^TX_BUF_THLD. */
> +    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
> +                                               TX_BUF_THLD);
> +    empty_threshold =
> +        dw_i3c_fifo_threshold_from_reg(empty_threshold);
> +    if (fifo32_num_free(&s->tx_queue) < empty_threshold) {
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 0);
> +        dw_i3c_update_irq(s);
> +    }
> +}
> +
> +static uint32_t dw_i3c_pop_tx(DWI3C *s)
> +{
> +    if (fifo32_is_empty(&s->tx_queue)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to pop from TX FIFO when "
> +                      "empty\n", path);
> +        return 0;
> +    }
> +
> +    uint32_t val = fifo32_pop(&s->tx_queue);
> +    trace_dw_i3c_pop_tx(s->cfg.id, val);
> +    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC,
> +                     fifo32_num_free(&s->tx_queue));
> +
> +    /* Threshold is 2^TX_BUF_THLD. */
> +    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
> +                                               TX_BUF_THLD);
> +    empty_threshold =
> +        dw_i3c_fifo_threshold_from_reg(empty_threshold);
> +    if (fifo32_num_free(&s->tx_queue) >= empty_threshold) {
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 1);
> +        dw_i3c_update_irq(s);
> +    }
> +    return val;
> +}
> +
> +static void dw_i3c_push_rx(DWI3C *s, uint32_t val)
> +{
> +    if (fifo32_is_full(&s->rx_queue)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to RX FIFO when "
> +                      "full\n", path);
> +        return;
> +    }
> +    trace_dw_i3c_push_rx(s->cfg.id, val);
> +    fifo32_push(&s->rx_queue, val);
> +
> +    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR,
> +                     fifo32_num_used(&s->rx_queue));
> +    /* Threshold is 2^RX_BUF_THLD. */
> +    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
> +                                         RX_BUF_THLD);
> +    threshold = dw_i3c_fifo_threshold_from_reg(threshold);
> +    if (fifo32_num_used(&s->rx_queue) >= threshold) {
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 1);
> +        dw_i3c_update_irq(s);
> +    }
> +}
> +
> +static void dw_i3c_short_transfer(DWI3C *s, DWI3CTransferCmd cmd,
> +                                  DWI3CShortArg arg)
> +{
> +    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
> +    uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
> +    bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index);
> +    uint8_t data[4]; /* Max we can send on a short transfer is 4 bytes. */
> +    uint8_t len = 0;
> +    uint32_t bytes_sent; /* Ignored on short transfers. */
> +
> +    /* Can't do reads on a short transfer. */
> +    if (cmd.rnw) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot do a read on a short "
> +                      "transfer\n", path);
> +        return;
> +    }
> +
> +    if (dw_i3c_send_start(s, addr, /*is_recv=*/false, is_i2c)) {
> +        err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> +        goto transfer_done;
> +    }
> +
> +    /* Are we sending a command? */
> +    if (cmd.cp) {
> +        data[len] = cmd.cmd;
> +        len++;
> +        /*
> +         * byte0 is the defining byte for a command, and is only sent if a
> +         * command is present and if the command has a defining byte present.
> +         * (byte_strb & 0x01) is always treated as set by the controller, and is
> +         * ignored.
> +         */
> +        if (cmd.dbp) {
> +            data[len] += arg.byte0;
> +            len++;
> +        }
> +    }
> +
> +    /* Send the bytes passed in the argument. */
> +    if (arg.byte_strb & 0x02) {
> +        data[len] = arg.byte1;
> +        len++;
> +    }
> +    if (arg.byte_strb & 0x04) {
> +        data[len] = arg.byte2;
> +        len++;
> +    }
> +
> +    if (dw_i3c_send(s, data, len, &bytes_sent, is_i2c)) {
> +        err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> +    } else {
> +        /* Only go to an idle state on a successful transfer. */
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                         DW_I3C_TRANSFER_STATE_IDLE);
> +    }
> +
> +transfer_done:
> +    if (cmd.toc) {
> +        dw_i3c_end_transfer(s, is_i2c);
> +    }
> +    if (cmd.roc) {
> +        /*
> +         * ccc_type is always 0 in controller mode, data_len is 0 in short
> +         * transfers.
> +         */
> +        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
> +                                          /*data_len=*/0);
> +    }
> +}
> +
> +/* Returns number of bytes transmitted. */
> +static uint16_t dw_i3c_tx(DWI3C *s, uint16_t num, bool is_i2c)
> +{
> +    uint16_t bytes_sent = 0;
> +    union {
> +        uint8_t b[sizeof(uint32_t)];
> +        uint32_t val;
> +    } val32;
> +
> +    while (bytes_sent < num) {
> +        val32.val = dw_i3c_pop_tx(s);
> +        for (uint8_t i = 0; i < sizeof(val32.val); i++) {
> +            if (dw_i3c_send_byte(s, val32.b[i], is_i2c)) {
> +                return bytes_sent;
> +            }
> +            bytes_sent++;
> +
> +            /* We're not sending the full 32-bits, break early. */
> +            if (bytes_sent >= num) {
> +                break;
> +            }
> +        }
> +    }
> +
> +    return bytes_sent;
> +}
> +
> +/* Returns number of bytes received. */
> +static uint16_t dw_i3c_rx(DWI3C *s, uint16_t num, bool is_i2c)
> +{
> +    /*
> +     * Allocate a temporary buffer to read data from the target.
> +     * Zero it and word-align it as well in case we're reading unaligned data.
> +     */
> +    g_autofree uint8_t *data = g_new0(uint8_t, num + (4 - (num & 0x03)));

Why not use ROUND_UP(num, 4) ?

Thanks,

C.


> +    uint32_t *data32 = (uint32_t *)data;
> +    /*
> +     * 32-bits since the I3C API wants a 32-bit number, even though the
> +     * controller can only do 16-bit transfers.
> +     */
> +    uint32_t num_read = 0;
> +
> +    /* Can NACK if the target receives an unsupported CCC. */
> +    if (dw_i3c_recv_data(s, is_i2c, data, num, &num_read)) {
> +        return 0;
> +    }
> +
> +    for (uint16_t i = 0; i < num_read / 4; i++) {
> +        dw_i3c_push_rx(s, *data32);
> +        data32++;
> +    }
> +    /*
> +     * If we're pushing data that isn't 32-bit aligned, push what's left.
> +     * It's software's responsibility to know what bits are valid in the partial
> +     * data.
> +     */
> +    if (num_read & 0x03) {
> +        dw_i3c_push_rx(s, *data32);
> +    }
> +
> +    return num_read;
> +}
> +
> +static int dw_i3c_transfer_ccc(DWI3C *s, DWI3CTransferCmd cmd,
> +                               DWI3CTransferArg arg)
> +{
> +    /* CCC start is always a write. CCCs cannot be done on I2C devices. */
> +    if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
> +                                     /*is_i2c=*/false)) {
> +        return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> +    }
> +    trace_dw_i3c_transfer_ccc(s->cfg.id, cmd.cmd);
> +    if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) {
> +        return DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> +    }
> +
> +    /* On a direct CCC, we do a restart and then send the target's address. */
> +    if (CCC_IS_DIRECT(cmd.cmd)) {
> +        bool is_recv = cmd.rnw;
> +        uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
> +        if (dw_i3c_send_start(s, addr, is_recv, /*is_i2c=*/false)) {
> +            return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> +        }
> +    }
> +
> +    return DW_I3C_RESP_QUEUE_ERR_NONE;
> +}
> +
> +static void dw_i3c_transfer(DWI3C *s, DWI3CTransferCmd cmd,
> +                            DWI3CTransferArg arg)
> +{
> +    bool is_recv = cmd.rnw;
> +    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
> +    uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
> +    bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index);
> +    uint16_t bytes_transferred = 0;
> +
> +    if (cmd.cp) {
> +        /* We're sending a CCC. */
> +        err = dw_i3c_transfer_ccc(s, cmd, arg);
> +        if (err != DW_I3C_RESP_QUEUE_ERR_NONE) {
> +            goto transfer_done;
> +        }
> +    } else {
> +        if (ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_BROADCAST_ADDR_INC) &&
> +            is_i2c == false) {
> +            if (dw_i3c_send_start(s, I3C_BROADCAST,
> +                                             /*is_recv=*/false, is_i2c)) {
> +                err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> +                goto transfer_done;
> +            }
> +        }
> +        /* Otherwise we're doing a private transfer. */
> +        if (dw_i3c_send_start(s, addr, is_recv, is_i2c)) {
> +            err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> +            goto transfer_done;
> +        }
> +    }
> +
> +    if (is_recv) {
> +        bytes_transferred = dw_i3c_rx(s, arg.data_len, is_i2c);
> +    } else {
> +        bytes_transferred = dw_i3c_tx(s, arg.data_len, is_i2c);
> +    }
> +
> +    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                     DW_I3C_TRANSFER_STATE_IDLE);
> +
> +transfer_done:
> +    if (cmd.toc) {
> +        dw_i3c_end_transfer(s, is_i2c);
> +    }
> +    if (cmd.roc) {
> +        /*
> +         * data_len is the number of bytes that still need to be TX'd, or the
> +         * number of bytes RX'd.
> +         */
> +        uint16_t data_len = is_recv ? bytes_transferred : arg.data_len -
> +                                                          bytes_transferred;
> +        /* CCCT is always 0 in controller mode. */
> +        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
> +                                          data_len);
> +    }
> +
> +    dw_i3c_update_irq(s);
> +}
> +
> +static void dw_i3c_transfer_cmd(DWI3C *s, DWI3CTransferCmd cmd,
> +                                DWI3CCmdQueueData arg)
> +{
> +    uint8_t arg_attr = FIELD_EX32(arg.word, COMMAND_QUEUE_PORT, CMD_ATTR);
> +
> +    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CMD_TID, cmd.tid);
> +
> +    /* User is trying to do HDR transfers, see if we can do them. */
> +    if (cmd.speed == 0x06 && !dw_i3c_has_hdr_ddr(s)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR DDR is not supported\n", path);
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                         DW_I3C_TRANSFER_STATE_HALT);
> +        return;
> +    }
> +    if (cmd.speed == 0x05 && !dw_i3c_has_hdr_ts(s)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR TS is not supported\n", path);
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                         DW_I3C_TRANSFER_STATE_HALT);
> +        return;
> +    }
> +
> +    if (arg_attr == DW_I3C_CMD_ATTR_TRANSFER_ARG) {
> +        dw_i3c_transfer(s, cmd, arg.transfer_arg);
> +    } else if (arg_attr == DW_I3C_CMD_ATTR_SHORT_DATA_ARG) {
> +        dw_i3c_short_transfer(s, cmd, arg.short_arg);
> +    } else {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Unknown command queue cmd_attr 0x%x"
> +                      "\n", path, arg_attr);
> +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                         DW_I3C_TRANSFER_STATE_HALT);
> +    }
> +}
> +
> +static void dw_i3c_update_char_table(DWI3C *s, uint8_t offset, uint64_t pid,
> +                                     uint8_t bcr, uint8_t dcr, uint8_t addr)
> +{
> +    if (offset > s->cfg.num_addressable_devices) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Device char table offset %d out of "
> +                      "bounds\n", path, offset);
> +        /* If we're out of bounds, do nothing. */
> +        return;
> +    }
> +
> +    /*
> +     * Each device offset is 128 bits apart in the table, since each device gets
> +     * 4 * 32-bits of entries in the table.
> +     * / sizeof(uint32_t) because we're indexing into our 32-bit reg array.
> +     */
> +    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
> +                                          P_DEV_CHAR_TABLE_START_ADDR) /
> +                                          sizeof(uint32_t)) +
> +                                          (offset * sizeof(uint32_t));
> +    s->regs[dev_index] = pid & 0xffffffff;
> +    pid >>= 32;
> +    s->regs[dev_index + 1] = FIELD_DP32(s->regs[dev_index + 1],
> +                                        DEVICE_CHARACTERISTIC_TABLE_LOC2,
> +                                        MSB_PID, pid);
> +    s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2],
> +                                        DEVICE_CHARACTERISTIC_TABLE_LOC3, DCR,
> +                                        dcr);
> +    s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2],
> +                                        DEVICE_CHARACTERISTIC_TABLE_LOC3, BCR,
> +                                        bcr);
> +    s->regs[dev_index + 3] = FIELD_DP32(s->regs[dev_index + 3],
> +                                        DEVICE_CHARACTERISTIC_TABLE_LOC4,
> +                                        DEV_DYNAMIC_ADDR, addr);
> +
> +    /* Increment PRESENT_DEV_CHAR_TABLE_INDEX. */
> +    uint8_t idx = ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
> +                     PRESENT_DEV_CHAR_TABLE_INDEX);
> +    /* Increment and rollover. */
> +    idx++;
> +    if (idx >= ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
> +                               DEV_CHAR_TABLE_DEPTH) / 4) {
> +        idx = 0;
> +    }
> +    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
> +                     PRESENT_DEV_CHAR_TABLE_INDEX, idx);
> +}
> +
> +static void dw_i3c_addr_assign_cmd(DWI3C *s, DWI3CAddrAssignCmd cmd)
> +{
> +    uint8_t i = 0;
> +    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
> +
> +    /* Tell everyone to ENTDAA. If these error, no one is on the bus. */
> +    if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
> +                                     /*is_i2c=*/false)) {
> +        err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> +        goto transfer_done;
> +    }
> +    if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) {
> +        err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> +        goto transfer_done;
> +    }
> +
> +    /* Go through each device in the table and assign it an address. */
> +    for (i = 0; i < cmd.dev_count; i++) {
> +        uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index + i);
> +        union {
> +            uint64_t pid:48;
> +            uint8_t bcr;
> +            uint8_t dcr;
> +            uint32_t w[2];
> +            uint8_t b[8];
> +        } target_info;
> +
> +        /* If this fails, there was no one left to ENTDAA. */
> +        if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
> +                                         /*is_i2c=*/false)) {
> +            err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> +            break;
> +        }
> +
> +        /*
> +         * In ENTDAA, we read 8 bytes from the target, which will be the
> +         * target's PID, BCR, and DCR. After that, we send it the dynamic
> +         * address.
> +         * Don't bother checking the number of bytes received, it must send 8
> +         * bytes during ENTDAA.
> +         */
> +        uint32_t num_read;
> +        if (dw_i3c_recv_data(s, /*is_i2c=*/false, target_info.b,
> +                                        I3C_ENTDAA_SIZE, &num_read)) {
> +            g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed ENTDAA CCC\n",
> +                          path);
> +            err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK;
> +            goto transfer_done;
> +        }
> +        if (dw_i3c_send_byte(s, addr, /*is_i2c=*/false)) {
> +            g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed addr 0x%.2x "
> +                          "during ENTDAA\n", path, addr);
> +            err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK;
> +            break;
> +        }
> +        dw_i3c_update_char_table(s, cmd.dev_index + i,
> +                                            target_info.pid, target_info.bcr,
> +                                            target_info.dcr, addr);
> +
> +        /* Push the PID, BCR, and DCR to the RX queue. */
> +        dw_i3c_push_rx(s, target_info.w[0]);
> +        dw_i3c_push_rx(s, target_info.w[1]);
> +    }
> +
> +transfer_done:
> +    /* Do we send a STOP? */
> +    if (cmd.toc) {
> +        dw_i3c_end_transfer(s, /*is_i2c=*/false);
> +    }
> +    /*
> +     * For addr assign commands, the length field is the number of devices
> +     * left to assign. CCCT is always 0 in controller mode.
> +     */
> +    if (cmd.roc) {
> +        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
> +                                         cmd.dev_count - i);
> +    }
> +}
> +
> +static uint32_t dw_i3c_cmd_queue_pop(DWI3C *s)
> +{
> +    if (fifo32_is_empty(&s->cmd_queue)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to dequeue command queue "
> +                      "when it was empty\n", path);
> +        return 0;
> +    }
> +    uint32_t val = fifo32_pop(&s->cmd_queue);
> +
> +    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
> +                                               CMD_BUF_EMPTY_THLD);
> +    uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs,
> +                                                   QUEUE_STATUS_LEVEL,
> +                                                   CMD_QUEUE_EMPTY_LOC);
> +    cmd_queue_empty_loc++;
> +    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC,
> +                     cmd_queue_empty_loc);
> +    if (cmd_queue_empty_loc >= empty_threshold) {
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 1);
> +        dw_i3c_update_irq(s);
> +    }
> +
> +    return val;
> +}
> +
> +static void dw_i3c_cmd_queue_execute(DWI3C *s)
> +{
> +    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> +                     DW_I3C_TRANSFER_STATE_IDLE);
> +    if (!dw_i3c_can_transmit(s)) {
> +        return;
> +    }
> +
> +    /*
> +     * We only start executing when a command is passed into the FIFO.
> +     * We expect there to be a multiple of 2 items in the queue. The first item
> +     * should be an argument to a command, and the command should be the second
> +     * item.
> +     */
> +    if (fifo32_num_used(&s->cmd_queue) & 1) {
> +        return;
> +    }
> +
> +    while (!fifo32_is_empty(&s->cmd_queue)) {
> +        DWI3CCmdQueueData arg;
> +        arg.word = dw_i3c_cmd_queue_pop(s);
> +        DWI3CCmdQueueData cmd;
> +        cmd.word = dw_i3c_cmd_queue_pop(s);
> +        trace_dw_i3c_cmd_queue_execute(s->cfg.id, cmd.word, arg.word);
> +
> +        uint8_t cmd_attr = FIELD_EX32(cmd.word, COMMAND_QUEUE_PORT, CMD_ATTR);
> +        switch (cmd_attr) {
> +        case DW_I3C_CMD_ATTR_TRANSFER_CMD:
> +            dw_i3c_transfer_cmd(s, cmd.transfer_cmd, arg);
> +            break;
> +        case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD:
> +            /* Arg is discarded for addr assign commands. */
> +            dw_i3c_addr_assign_cmd(s, cmd.addr_assign_cmd);
> +            break;
> +        case DW_I3C_CMD_ATTR_TRANSFER_ARG:
> +        case DW_I3C_CMD_ATTR_SHORT_DATA_ARG:
> +            {
> +                g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +                qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received "
> +                              "argument packet when it expected a command "
> +                              "packet\n", path);
> +            }
> +            break;
> +        default:
> +            /*
> +             * The caller's check before queueing an item should prevent this
> +             * from happening.
> +             */
> +            g_assert_not_reached();
> +            break;
> +        }
> +    }
> +}
> +
> +static void dw_i3c_cmd_queue_push(DWI3C *s, uint32_t val)
> +{
> +    if (fifo32_is_full(&s->cmd_queue)) {
> +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received packet when "
> +                      "already full\n", path);
> +        return;
> +    }
> +    trace_dw_i3c_cmd_queue_push(s->cfg.id, val);
> +    fifo32_push(&s->cmd_queue, val);
> +
> +    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
> +                                               CMD_BUF_EMPTY_THLD);
> +    uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs,
> +                                                   QUEUE_STATUS_LEVEL,
> +                                                   CMD_QUEUE_EMPTY_LOC);
> +    if (cmd_queue_empty_loc) {
> +        cmd_queue_empty_loc--;
> +        ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC,
> +                         cmd_queue_empty_loc);
> +    }
> +    if (cmd_queue_empty_loc < empty_threshold) {
> +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 0);
> +        dw_i3c_update_irq(s);
> +    }
> +}
> +
> +static void dw_i3c_cmd_queue_port_w(DWI3C *s, uint32_t val)
> +{
> +    uint8_t cmd_attr = FIELD_EX32(val, COMMAND_QUEUE_PORT, CMD_ATTR);
> +
> +    switch (cmd_attr) {
> +    /* If a command is received we can start executing it. */
> +    case DW_I3C_CMD_ATTR_TRANSFER_CMD:
> +    case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD:
> +        dw_i3c_cmd_queue_push(s, val);
> +        dw_i3c_cmd_queue_execute(s);
> +        break;
> +    /* If we get an argument just push it. */
> +    case DW_I3C_CMD_ATTR_TRANSFER_ARG:
> +    case DW_I3C_CMD_ATTR_SHORT_DATA_ARG:
> +        dw_i3c_cmd_queue_push(s, val);
> +        break;
> +    default:
> +        {
> +            g_autofree char *path = object_get_canonical_path(OBJECT(s));
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received packet "
> +                          "with unknown cmd attr 0x%x\n", path, cmd_attr);
> +        }
> +        break;
> +    }
> +}
> +
>   static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
>                            unsigned size)
>   {
> @@ -409,7 +1245,7 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
>       uint32_t addr = offset >> 2;
>       uint32_t val32 = (uint32_t)value;
>   
> -    trace_dw_i3c_write(s->id, offset, value);
> +    trace_dw_i3c_write(s->cfg.id, offset, value);
>   
>       val32 &= ~dw_i3c_ro[addr];
>       switch (addr) {
> @@ -433,6 +1269,10 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
>                         __func__, offset, value);
>           break;
>       case R_RX_TX_DATA_PORT:
> +        dw_i3c_push_tx(s, val32);
> +        break;
> +    case R_COMMAND_QUEUE_PORT:
> +        dw_i3c_cmd_queue_port_w(s, val32);
>           break;
>       case R_RESET_CTRL:
>           break;
> @@ -475,22 +1315,56 @@ static void dw_i3c_reset_enter(Object *obj, ResetType type)
>       DWI3C *s = DW_I3C(obj);
>   
>       memcpy(s->regs, dw_i3c_resets, sizeof(s->regs));
> +    /*
> +     * The user config for these may differ from our resets array, set them
> +     * manually.
> +     */
> +    ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, ADDR,
> +                     s->cfg.dev_addr_table_pointer);
> +    ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, DEPTH,
> +                     s->cfg.dev_addr_table_depth);
> +    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
> +                     P_DEV_CHAR_TABLE_START_ADDR,
> +                     s->cfg.dev_char_table_pointer);
> +    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, DEV_CHAR_TABLE_DEPTH,
> +                     s->cfg.dev_char_table_depth);
>   }
>   
>   static void dw_i3c_realize(DeviceState *dev, Error **errp)
>   {
>       DWI3C *s = DW_I3C(dev);
> -    g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->id);
> +    g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->cfg.id);
>   
>       sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
>   
>       memory_region_init_io(&s->mr, OBJECT(s), &dw_i3c_ops, s, name,
>                             DW_I3C_NR_REGS << 2);
>       sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr);
> +
> +    fifo32_create(&s->cmd_queue, s->cfg.cmd_resp_queue_capacity_bytes);
> +    fifo32_create(&s->resp_queue, s->cfg.cmd_resp_queue_capacity_bytes);
> +    fifo32_create(&s->tx_queue, s->cfg.tx_rx_queue_capacity_bytes);
> +    fifo32_create(&s->rx_queue, s->cfg.tx_rx_queue_capacity_bytes);
> +
> +    s->bus = i3c_init_bus(DEVICE(s), name);
>   }
>   
>   static const Property dw_i3c_properties[] = {
> -    DEFINE_PROP_UINT8("device-id", DWI3C, id, 0),
> +    DEFINE_PROP_UINT8("device-id", DWI3C, cfg.id, 0),
> +    DEFINE_PROP_UINT8("command-response-queue-capacity-bytes", DWI3C,
> +                      cfg.cmd_resp_queue_capacity_bytes, 0x10),
> +    DEFINE_PROP_UINT16("tx-rx-queue-capacity-bytes", DWI3C,
> +                      cfg.tx_rx_queue_capacity_bytes, 0x40),
> +    DEFINE_PROP_UINT8("num-addressable-devices", DWI3C,
> +                      cfg.num_addressable_devices, 8),
> +    DEFINE_PROP_UINT16("dev-addr-table-pointer", DWI3C,
> +                       cfg.dev_addr_table_pointer, 0x280),
> +    DEFINE_PROP_UINT16("dev-addr-table-depth", DWI3C,
> +                       cfg.dev_addr_table_depth, 0x08),
> +    DEFINE_PROP_UINT16("dev-char-table-pointer", DWI3C,
> +                       cfg.dev_char_table_pointer, 0x200),
> +    DEFINE_PROP_UINT16("dev-char-table-depth", DWI3C,
> +                       cfg.dev_char_table_depth, 0x20),
>   };
>   
>   static void dw_i3c_class_init(ObjectClass *klass, const void *data)
> diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
> index 2d944387db..044ff06a01 100644
> --- a/hw/i3c/trace-events
> +++ b/hw/i3c/trace-events
> @@ -7,6 +7,16 @@ aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64
>   # dw-i3c,c
>   dw_i3c_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
>   dw_i3c_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
> +dw_i3c_send(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] send %" PRId32 " bytes to bus"
> +dw_i3c_recv_data(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] recv %" PRId32 " bytes from bus"
> +dw_i3c_pop_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from RX FIFO"
> +dw_i3c_resp_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to response queue"
> +dw_i3c_push_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to TX FIFO"
> +dw_i3c_pop_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from TX FIFO"
> +dw_i3c_push_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to RX FIFO"
> +dw_i3c_transfer_ccc(uint32_t deviceid, uint8_t ccc) "I3C Dev[%u] transfer CCC 0x%" PRIx8
> +dw_i3c_cmd_queue_execute(uint32_t deviceid, uint32_t cmd, uint32_t arg) "I3C Dev[%u] execute command 0x%" PRIx32 " arg 0x%" PRIx32
> +dw_i3c_cmd_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to cmd queue"
>   
>   # core.c
>   i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " event 0x%" PRIx8



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-25  2:12 ` [PATCH v7 18/22] hw/i3c: Add Mock target Jamin Lin
  2026-02-27  1:43   ` Jithu Joseph
@ 2026-02-27  9:58   ` Cédric Le Goater
  2026-03-02  3:34     ` Jamin Lin
  2026-02-27 20:28   ` Jithu Joseph
  2 siblings, 1 reply; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-27  9:58 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee, Troy Lee,
	Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Titus Rwantare, Patrick Venture

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"



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-27  7:52     ` Cédric Le Goater
@ 2026-02-27 19:49       ` Jithu Joseph
  2026-02-28  6:30         ` Cédric Le Goater
  0 siblings, 1 reply; 47+ messages in thread
From: Jithu Joseph @ 2026-02-27 19:49 UTC (permalink / raw)
  To: Cédric Le Goater, Jamin Lin, Paolo Bonzini, Peter Maydell,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Titus Rwantare, Patrick Venture

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



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 03/22] hw/i3c: Add bus support
  2026-02-27  7:51     ` Cédric Le Goater
@ 2026-02-27 19:58       ` Jithu Joseph
  0 siblings, 0 replies; 47+ messages in thread
From: Jithu Joseph @ 2026-02-27 19:58 UTC (permalink / raw)
  To: Cédric Le Goater, Jamin Lin, Paolo Bonzini, Peter Maydell,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Patrick Venture, Titus Rwantare

On 2/26/2026 11:51 PM, Cédric Le Goater wrote:
> Hello Jithu,
> 
> On 2/27/26 03:23, Jithu Joseph wrote:
>>

...

>>
>>> +
>>> +int i3c_send_byte(I3CBus *bus, uint8_t data)
>>> +{
>>> +    /*
>>> +     * Ignored, the caller can determine how many were sent based on if this was
>>> +     * ACKed/NACKed.
>>> +     */
>>> +    uint32_t num_sent;
>>
>> num_sent is uninitialized here. Even though i3c_send_byte ignores it after the call, it gets passed by pointer into i3c_send() where it is used in the trace_i3c_send() call.
>> If the send callback does not write *num_sent (as is the case with mock_i3c_target_tx, refer my prev comment for patch 18/22), the trace might show garbage. Initializing it to zero might reduce the confusion
> 
> Could please send a patch ? I will queue it after this series.

Got it,  I will send 2 patches one to address this comment and another one to address my comment on 18/22, which will apply  on-top of this series

> 
> Also, since you have tested this series, would mind sending
> a Tested-by tag ? possibly a Reviewed-by too.

You can add my Tested-by for the whole series , I have replied to the cover-letter with the tag .

I will send in Reviewed-by for the couple of patches I sent review comments for


Jithu

 


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 00/22] i3c: aspeed: Add I3C support
  2026-02-27  7:53   ` Cédric Le Goater
@ 2026-02-27 20:03     ` Jithu Joseph
  2026-02-28  6:32       ` Cédric Le Goater
  0 siblings, 1 reply; 47+ messages in thread
From: Jithu Joseph @ 2026-02-27 20:03 UTC (permalink / raw)
  To: Cédric Le Goater, Jamin Lin, Paolo Bonzini, Peter Maydell,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com

On 2/26/2026 11:53 PM, Cédric Le Goater wrote:
> On 2/27/26 03:33, Jithu Joseph wrote:
>> On 2/24/2026 6:11 PM, Jamin Lin wrote:
>>
>>>    The initial patch series was based on work by Joe Komlodi <komlodi@google.com>.
>>>
>>>    This series adds I3C bus support to QEMU and adds more functionality to the
>>>    Aspeed I3C controller.
>>>
>>
>> Thanks Jaimin for the series, I was able to test this series to a good extent over the last week
>>
>> Tested read/write transfers from guest Linux using i3ctransfer against the mock-i3c-target  on an AST2600 based machine
> 
> Can you be more precise on the machine used ?
> 

This is an internal clone of ast2600-evb which is still a "Work in Progress" and not yet upstream.


Thanks
Jithu



^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 03/22] hw/i3c: Add bus support
  2026-02-25  2:12 ` [PATCH v7 03/22] hw/i3c: Add bus support Jamin Lin
  2026-02-27  2:23   ` Jithu Joseph
  2026-02-27  9:47   ` Cédric Le Goater
@ 2026-02-27 20:27   ` Jithu Joseph
  2 siblings, 0 replies; 47+ messages in thread
From: Jithu Joseph @ 2026-02-27 20:27 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Cédric Le Goater,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Patrick Venture, Titus Rwantare

On 2/24/2026 6:12 PM, Jamin Lin wrote:
> Adds an I3C bus and a target class.
> The bus supports:
> - I3C data transmission and reception
> - CCCs (including ENTDAA)
> - IBIs
> - legacy I2C transactions

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



Thanks
Jithu


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-25  2:12 ` [PATCH v7 18/22] hw/i3c: Add Mock target Jamin Lin
  2026-02-27  1:43   ` Jithu Joseph
  2026-02-27  9:58   ` Cédric Le Goater
@ 2026-02-27 20:28   ` Jithu Joseph
  2 siblings, 0 replies; 47+ messages in thread
From: Jithu Joseph @ 2026-02-27 20:28 UTC (permalink / raw)
  To: Jamin Lin, Paolo Bonzini, Peter Maydell, Cédric Le Goater,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Titus Rwantare, Patrick Venture

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


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-27 19:49       ` Jithu Joseph
@ 2026-02-28  6:30         ` Cédric Le Goater
  0 siblings, 0 replies; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-28  6:30 UTC (permalink / raw)
  To: Jithu Joseph, Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Titus Rwantare, Patrick Venture

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.


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH v7 00/22] i3c: aspeed: Add I3C support
  2026-02-27 20:03     ` Jithu Joseph
@ 2026-02-28  6:32       ` Cédric Le Goater
  0 siblings, 0 replies; 47+ messages in thread
From: Cédric Le Goater @ 2026-02-28  6:32 UTC (permalink / raw)
  To: Jithu Joseph, Jamin Lin, Paolo Bonzini, Peter Maydell, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com

On 2/27/26 21:03, Jithu Joseph wrote:
> On 2/26/2026 11:53 PM, Cédric Le Goater wrote:
>> On 2/27/26 03:33, Jithu Joseph wrote:
>>> On 2/24/2026 6:11 PM, Jamin Lin wrote:
>>>
>>>>     The initial patch series was based on work by Joe Komlodi <komlodi@google.com>.
>>>>
>>>>     This series adds I3C bus support to QEMU and adds more functionality to the
>>>>     Aspeed I3C controller.
>>>>
>>>
>>> Thanks Jaimin for the series, I was able to test this series to a good extent over the last week
>>>
>>> Tested read/write transfers from guest Linux using i3ctransfer against the mock-i3c-target  on an AST2600 based machine
>>
>> Can you be more precise on the machine used ?
>>
> 
> This is an internal clone of ast2600-evb which is still a "Work in Progress" and not yet upstream.


OK.

For your awareness, qcom-dc-scm-v1-bmc and qcom-firework-bmc machines
will be removed in the next cycle.

   https://www.qemu.org/docs/master/about/deprecated.html#arm-qcom-dc-scm-v1-bmc-and-qcom-firework-bmc-machine-since-10-2

Thanks,

C.


> 
> 
> Thanks
> Jithu
> 



^ permalink raw reply	[flat|nested] 47+ messages in thread

* RE: [PATCH v7 03/22] hw/i3c: Add bus support
  2026-02-27  9:47   ` Cédric Le Goater
@ 2026-03-02  1:31     ` Jamin Lin
  0 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-03-02  1:31 UTC (permalink / raw)
  To: Cédric Le Goater, Paolo Bonzini, Peter Maydell, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Patrick Venture, Titus Rwantare

Hi Cédric

> Subject: Re: [PATCH v7 03/22] hw/i3c: Add bus support
> 
> On 2/25/26 03:12, Jamin Lin wrote:
> > Adds an I3C bus and a target class.
> > The bus supports:
> > - I3C data transmission and reception
> > - CCCs (including ENTDAA)
> > - IBIs
> > - legacy I2C transactions
> >
> > General usage of the bus is similar to I2C. Users are expected to
> > initialize a bus via i3c_init_bus, and use the bus returned from the
> > init function to do transactions on the bus.
> >
> > In order to handle IBIs, the controller provides callbacks to handle
> > receiving an IBI from a target, receiving (optional) additional IBI
> > bytes from a target, and handling when a target is done with its IBI.
> >
> > Similarly, target creation is done via i3c_target_create_simple and
> > users use the provided I3CTarget to handle transactions.
> > The target has functions provided that it can use to invoke an IBI and
> > send additional bytes.
> >
> > Along with the send, recv, and event callbacks that are expected of an
> > I3C target, which are similar to I2C, there is a separate callback for
> > CCC handling.
> > This is to help encapsulate CCC handling and keep it separate from
> > target-specific read/write functionality.
> >
> > To avoid repition for required CCCs among I3C targets, there is some
> > class-level CCC handling added. The CCC is then passed to the target
> > in case it needs to handle it in some way.
> >
> > Signed-off-by: Joe Komlodi <komlodi@google.com>
> > Reviewed-by: Patrick Venture <venture@google.com>
> > Reviewed-by: Titus Rwantare <titusr@google.com>
> > Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > ---
> >   include/hw/i3c/i3c.h | 277 ++++++++++++++++++
> >   hw/i3c/core.c        | 647
> +++++++++++++++++++++++++++++++++++++++++++
> >   hw/i3c/meson.build   |   1 +
> >   hw/i3c/trace-events  |  16 ++
> >   4 files changed, 941 insertions(+)
> >   create mode 100644 include/hw/i3c/i3c.h
> >   create mode 100644 hw/i3c/core.c
> >
> > diff --git a/include/hw/i3c/i3c.h b/include/hw/i3c/i3c.h new file mode
> > 100644 index 0000000000..6ba90793ad
> > --- /dev/null
> > +++ b/include/hw/i3c/i3c.h
> > @@ -0,0 +1,277 @@
> > +/*
> > + * QEMU I3C bus interface.
> > + *
> > + * Copyright 2025 Google LLC
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later  */
> > +
> > +#ifndef QEMU_INCLUDE_HW_I3C_I3C_H_
> > +#define QEMU_INCLUDE_HW_I3C_I3C_H_
> > +
> > +#include "hw/core/qdev.h"
> > +#include "qom/object.h"
> > +#include "hw/i2c/i2c.h"
> > +
> > +#define TYPE_I3C_TARGET "i3c-target"
> > +OBJECT_DECLARE_TYPE(I3CTarget, I3CTargetClass, I3C_TARGET)
> > +
> > +typedef enum I3CEvent {
> > +    I3C_START_RECV,
> > +    I3C_START_SEND,
> > +    I3C_STOP,
> > +    I3C_NACK,
> > +} I3CEvent;
> > +
> > +typedef enum I3CCCC {
> > +    /* Broadcast CCCs */
> > +    I3C_CCC_ENEC      = 0x00,
> > +    I3C_CCC_DISEC     = 0x01,
> > +    I3C_CCC_ENTAS0    = 0x02,
> > +    I3C_CCC_ENTAS1    = 0x03,
> > +    I3C_CCC_ENTAS2    = 0x04,
> > +    I3C_CCC_ENTAS3    = 0x05,
> > +    I3C_CCC_RSTDAA    = 0x06,
> > +    I3C_CCC_ENTDAA    = 0x07,
> > +    I3C_CCC_DEFTGTS   = 0x08,
> > +    I3C_CCC_SETMWL    = 0x09,
> > +    I3C_CCC_SETMRL    = 0x0a,
> > +    I3C_CCC_ENTTM     = 0x0b,
> > +    I3C_CCC_SETBUSCON = 0x0c,
> > +    I3C_CCC_ENDXFER   = 0x12,
> > +    I3C_CCC_ENTHDR0   = 0x20,
> > +    I3C_CCC_ENTHDR1   = 0x21,
> > +    I3C_CCC_ENTHDR2   = 0x22,
> > +    I3C_CCC_ENTHDR3   = 0x23,
> > +    I3C_CCC_ENTHDR4   = 0x24,
> > +    I3C_CCC_ENTHDR5   = 0x25,
> > +    I3C_CCC_ENTHDR6   = 0x26,
> > +    I3C_CCC_ENTHDR7   = 0x27,
> > +    I3C_CCC_SETXTIME  = 0x28,
> > +    I3C_CCC_SETAASA   = 0x29,
> > +    I3C_CCC_RSTACT    = 0x2a,
> > +    I3C_CCC_DEFGRPA   = 0x2b,
> > +    I3C_CCC_RSTGRPA   = 0x2c,
> > +    I3C_CCC_MLANE     = 0x2d,
> > +    /* Direct CCCs */
> > +    I3C_CCCD_ENEC       = 0x80,
> > +    I3C_CCCD_DISEC      = 0x81,
> > +    I3C_CCCD_ENTAS0     = 0x82,
> > +    I3C_CCCD_ENTAS1     = 0x83,
> > +    I3C_CCCD_ENTAS2     = 0x84,
> > +    I3C_CCCD_ENTAS3     = 0x85,
> > +    I3C_CCCD_SETDASA    = 0x87,
> > +    I3C_CCCD_SETNEWDA   = 0x88,
> > +    I3C_CCCD_SETMWL     = 0x89,
> > +    I3C_CCCD_SETMRL     = 0x8a,
> > +    I3C_CCCD_GETMWL     = 0x8b,
> > +    I3C_CCCD_GETMRL     = 0x8c,
> > +    I3C_CCCD_GETPID     = 0x8d,
> > +    I3C_CCCD_GETBCR     = 0x8e,
> > +    I3C_CCCD_GETDCR     = 0x8f,
> > +    I3C_CCCD_GETSTATUS  = 0x90,
> > +    I3C_CCCD_GETACCCR   = 0x91,
> > +    I3C_CCCD_ENDXFER    = 0x92,
> > +    I3C_CCCD_SETBRGTGT  = 0x93,
> > +    I3C_CCCD_GETMXDS    = 0x94,
> > +    I3C_CCCD_GETCAPS    = 0x95,
> > +    I3C_CCCD_SETROUTE   = 0x96,
> > +    I3C_CCCD_SETXTIME   = 0x98,
> > +    I3C_CCCD_GETXTIME   = 0x99,
> > +    I3C_CCCD_RSTACT     = 0x9a,
> > +    I3C_CCCD_SETGRPA    = 0x9b,
> > +    I3C_CCCD_RSTGRPA    = 0x9c,
> > +    I3C_CCCD_MLANE      = 0x9d,
> > +} I3CCCC;
> > +
> > +#define CCC_IS_DIRECT(_ccc) (_ccc & 0x80)
> > +
> > +#define I3C_BROADCAST 0x7e
> > +#define I3C_HJ_ADDR 0x02
> > +#define I3C_ENTDAA_SIZE 8
> > +
> > +struct I3CTargetClass {
> > +    DeviceClass parent_class;
> > +
> > +    /*
> > +     * Controller to target. Returns 0 for success, non-zero for NAK or
> other
> > +     * error.
> > +     */
> > +    int (*send)(I3CTarget *s, const uint8_t *data, uint32_t num_to_send,
> > +                uint32_t *num_sent);
> > +    /*
> > +     * Target to controller. I3C targets are able to terminate reads early, so
> > +     * this returns the number of bytes read from the target.
> > +     */
> > +    uint32_t (*recv)(I3CTarget *s, uint8_t *data, uint32_t num_to_read);
> > +    /* Notify the target of a bus state change. */
> > +    int (*event)(I3CTarget *s, enum I3CEvent event);
> > +    /*
> > +     * Handle a read CCC transmitted from a controller.
> > +     * CCCs are I3C commands that I3C targets support.
> > +     * The target can NACK the CCC if it does not support it.
> > +     */
> > +    int (*handle_ccc_read)(I3CTarget *s, uint8_t *data, uint32_t
> num_to_read,
> > +                           uint32_t *num_read);
> > +    /*
> > +     * Handle a write CCC transmitted from a controller.
> > +     * CCCs are I3C commands that I3C targets support.
> > +     * The target can NACK the CCC if it does not support it.
> > +     */
> > +    int (*handle_ccc_write)(I3CTarget *s, const uint8_t *data,
> > +                            uint32_t num_to_send, uint32_t
> > + *num_sent);
> > +
> > +    /*
> > +     * Matches and adds the candidate if the address matches the
> candidate's
> > +     * address.
> > +     * Returns true if the address matched, or if this was a broadcast, and
> > +     * updates the device list. Otherwise returns false.
> > +     */
> > +    bool (*target_match)(I3CTarget *candidate, uint8_t address, bool
> is_read,
> > +                         bool broadcast, bool in_entdaa); };
> > +
> > +struct I3CTarget {
> > +    DeviceState parent_obj;
> > +
> > +    uint8_t address;
> > +    uint8_t static_address;
> > +    uint8_t dcr;
> > +    uint8_t bcr;
> > +    uint64_t pid;
> > +
> > +    /* CCC State tracking. */
> > +    I3CCCC curr_ccc;
> > +    uint8_t ccc_byte_offset;
> > +    bool in_ccc;
> > +    bool in_test_mode;
> > +};
> > +
> > +struct I3CNode {
> > +    I3CTarget *target;
> > +    QLIST_ENTRY(I3CNode) next;
> > +};
> > +
> > +typedef struct I3CNode I3CNode;
> > +
> > +typedef QLIST_HEAD(I3CNodeList, I3CNode) I3CNodeList;
> > +
> > +#define TYPE_I3C_BUS "i3c-bus"
> > +OBJECT_DECLARE_TYPE(I3CBus, I3CBusClass, I3C_BUS)
> > +
> > +struct I3CBus {
> > +    BusState parent_obj;
> > +
> > +    /* Legacy I2C. */
> > +    I2CBus *i2c_bus;
> > +
> > +    I3CNodeList current_devs;
> > +    bool broadcast;
> > +    uint8_t ccc;
> > +    bool in_ccc;
> > +    bool in_entdaa;
> > +    uint8_t saved_address;
> > +};
> > +
> > +struct I3CBusClass {
> > +    BusClass parent_class;
> > +
> > +    /* Handle an incoming IBI request from a target */
> > +    int (*ibi_handle) (I3CBus *bus, uint8_t addr, bool is_recv);
> > +    /* Receive data from an IBI request */
> > +    int (*ibi_recv) (I3CBus *bus, uint8_t data);
> > +    /* Do anything that needs to be done, since the IBI is finished. */
> > +    int (*ibi_finish) (I3CBus *bus);
> > +};
> > +
> > +I3CBus *i3c_init_bus(DeviceState *parent, const char *name); I3CBus
> > +*i3c_init_bus_type(const char *type, DeviceState *parent,
> > +                          const char *name); void
> > +i3c_set_target_address(I3CTarget *dev, uint8_t address); bool
> > +i3c_bus_busy(I3CBus *bus);
> > +
> > +/*
> > + * Start a transfer on an I3C bus.
> > + * If is_recv is known at compile-time (i.e. a device will always be
> > +sending or
> > + * will always be receiving at a certain point), prefer to use
> > +i3c_start_recv or
> > + * i3c_start_send instead.
> > + *
> > + * Returns 0 on success, non-zero on an error.
> > + */
> > +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv);
> > +
> > +/*
> > + * Start a receive transfer on an I3C bus.
> > + *
> > + * Returns 0 on success, non-zero on an error  */ int
> > +i3c_start_recv(I3CBus *bus, uint8_t address);
> > +
> > +/*
> > + * Start a send transfer on an I3C bus.
> > + *
> > + * Returns 0 on success, non-zero on an error  */ int
> > +i3c_start_send(I3CBus *bus, uint8_t address);
> > +
> > +void i3c_end_transfer(I3CBus *bus);
> > +void i3c_nack(I3CBus *bus);
> > +int i3c_send_byte(I3CBus *bus, uint8_t data); int i3c_send(I3CBus
> > +*bus, const uint8_t *data, uint32_t num_to_send,
> > +             uint32_t *num_sent);
> > +/*
> > + * I3C receives can only NACK on a CCC. The target should NACK a CCC
> > +it does not
> > + * support.
> > + */
> > +int i3c_recv_byte(I3CBus *bus, uint8_t *data); int i3c_recv(I3CBus
> > +*bus, uint8_t *data, uint32_t num_to_read,
> > +             uint32_t *num_read);
> > +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event);
> > +int i3c_do_entdaa(I3CBus *bus, uint8_t address, uint64_t *pid, uint8_t *bcr,
> > +                  uint8_t *dcr);
> > +int i3c_start_device_transfer(I3CTarget *dev, int send_length); bool
> > +i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address,
> > +                             enum I3CEvent event); int
> > +i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv); int
> > +i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data); int
> > +i3c_target_ibi_finish(I3CTarget *t, uint8_t data);
> > +
> > +/*
> > + * Legacy I2C functions.
> > + *
> > + * These are wrapper for I2C functions that take in an I3C bus
> > +instead of an I2C
> > + * bus. Internally they use the I2C bus (and devices attached to it)
> > +that's a
> > + * part of the I3C bus
> > + */
> > +void legacy_i2c_nack(I3CBus *bus);
> > +uint8_t legacy_i2c_recv(I3CBus *bus); int legacy_i2c_send(I3CBus
> > +*bus, uint8_t data); int legacy_i2c_start_transfer(I3CBus *bus,
> > +uint8_t address, bool is_recv); int legacy_i2c_start_recv(I3CBus
> > +*bus, uint8_t address); int legacy_i2c_start_send(I3CBus *bus,
> > +uint8_t address); void legacy_i2c_end_transfer(I3CBus *bus); I2CSlave
> > +*legacy_i2c_device_create_simple(I3CBus *bus, const char *name,
> > +                                          uint8_t addr);
> > +
> > +/**
> > + * Create an I3C Target.
> > + *
> > + * The target returned from this function still needs to be realized.
> > + */
> > +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
> > +                          uint8_t bcr, uint64_t pid);
> > +
> > +/**
> > + * Create and realize an I3C target.
> > + *
> > + * Create the target, initialize it, put it on the specified I3C bus,
> > +and
> > + * realize it.
> > + */
> > +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name,
> > +                                    uint8_t addr, uint8_t dcr,
> uint8_t bcr,
> > +                                    uint64_t pid);
> > +
> > +/* Realize and drop the reference count on an I3C target. */ bool
> > +i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error
> > +**errp);
> > +
> > +#endif  /* QEMU_INCLUDE_HW_I3C_I3C_H_ */
> > diff --git a/hw/i3c/core.c b/hw/i3c/core.c new file mode 100644 index
> > 0000000000..22e6e226a7
> > --- /dev/null
> > +++ b/hw/i3c/core.c
> > @@ -0,0 +1,647 @@
> > +/*
> > + * QEMU I3C bus interface.
> > + *
> > + * Copyright 2025 Google LLC
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later  */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qemu/log.h"
> > +#include "qapi/error.h"
> > +#include "trace.h"
> > +#include "hw/i3c/i3c.h"
> > +#include "hw/core/qdev-properties.h"
> > +
> > +/*
> > + * In test mode (enabled by ENTTM CCC) we're supposed to send a
> > +random PID
> > + * during ENTDAA, so we'll just send "QEMU".
> > + */
> > +#define TEST_MODE_PROVISIONED_ID 0x0000554d4551ULL
> > +
> > +static const Property i3c_props[] = {
> > +    DEFINE_PROP_UINT8("static-address", struct I3CTarget, static_address,
> 0),
> > +    DEFINE_PROP_UINT8("dcr", struct I3CTarget, dcr, 0),
> > +    DEFINE_PROP_UINT8("bcr", struct I3CTarget, bcr, 0),
> > +    DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0), };
> > +
> > +I3CBus *i3c_init_bus(DeviceState *parent, const char *name) {
> > +    return i3c_init_bus_type(TYPE_I3C_BUS, parent, name); }
> > +
> > +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent,
> > +                          const char *name) {
> > +    I3CBus *bus;
> > +
> > +    bus = I3C_BUS(qbus_new(type, parent, name));
> > +    QLIST_INIT(&bus->current_devs);
> > +    bus->broadcast = false;
> > +    bus->in_entdaa = false;
> > +    bus->in_ccc = false;
> > +
> > +    /* I2C init. */
> > +    g_autofree gchar *i2c_bus_name = g_strdup_printf("%s-legacy-i2c",
> name);
> > +    bus->i2c_bus = i2c_init_bus(parent, i2c_bus_name);
> > +
> > +    return bus;
> > +}
> > +
> > +bool i3c_bus_busy(I3CBus *bus)
> > +{
> > +    return !QLIST_EMPTY(&bus->current_devs); }
> > +
> > +static bool i3c_target_match(I3CTarget *candidate, uint8_t address,
> > +                             bool is_recv, bool broadcast, bool
> > +in_entdaa) {
> > +    /* Once a target has a dynamic address, it only responds to that. */
> > +    uint8_t targ_addr = candidate->address ? candidate->address :
> > +
> > +candidate->static_address;
> > +
> > +    if (in_entdaa) {
> > +        if (address != I3C_BROADCAST) {
> > +            g_autofree char *path =
> > +                object_get_canonical_path(OBJECT(candidate));
> > +            qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address
> 0x%.2x sent during "
> > +                          "ENTDAA instead of a broadcast address\n",
> > +                          path, address);
> > +            return false;
> > +        }
> > +
> > +        /*
> > +         * Targets should only ACK ENTDAA broadcasts if they have no
> dynamic
> > +         * address.
> > +         */
> > +        return candidate->address == 0;
> > +    }
> > +
> > +    /* Return if our addresses match, or if it's a broadcast. */
> > +    return targ_addr == address || broadcast; }
> > +
> > +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t
> address,
> > +                              enum I3CEvent event) {
> > +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(target);
> > +    bool matched = tc->target_match(target, address, event ==
> I3C_START_RECV,
> > +                                    bus->broadcast,
> bus->in_entdaa);
> > +
> > +    if (matched) {
> > +        I3CNode *node = g_new(struct I3CNode, 1);
> > +        node->target = target;
> > +        QLIST_INSERT_HEAD(&bus->current_devs, node, next);
> > +    }
> > +    return matched;
> > +}
> > +
> > +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event)
> > +{
> > +    BusChild *child;
> > +    I3CNode *node, *next;
> > +
> > +    /* Clear out any devices from a previous (re-)START. */
> > +    QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
> > +        QLIST_REMOVE(node, next);
> > +        g_free(node);
> > +    }
> > +
> > +    QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) {
> > +        DeviceState *qdev = child->child;
> > +        I3CTarget *target = I3C_TARGET(qdev);
> > +
> > +        if (i3c_target_match_and_add(bus, target, address, event)) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    /* No one on the bus could respond. */
> > +    return false;
> > +}
> > +
> > +/* Class-level event handling, since we do some CCCs at the class
> > +level. */ static int i3c_target_event(I3CTarget *t, enum I3CEvent
> > +event) {
> > +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> > +    trace_i3c_target_event(t->address, event);
> > +
> > +    if (event == I3C_STOP) {
> > +        t->curr_ccc = 0;
> > +        t->ccc_byte_offset = 0;
> > +        t->in_ccc = false;
> > +    }
> > +    return tc->event(t, event);
> > +}
> > +
> > +/*
> > + * Sends a START or repeated START and the address for an I3C transaction.
> > + *
> > + * This function returns 0 if a device on the bus was able to respond
> > +to the
> > + * address, and non-zero otherwise.
> > + * A non-zero return represents a NACK.
> > + */
> > +static int i3c_do_start_transfer(I3CBus *bus, uint8_t address,
> > +                                 enum I3CEvent event) {
> > +    I3CTargetClass *tc;
> > +    I3CNode *node;
> > +
> > +    if (address == I3C_BROADCAST) {
> > +        bus->broadcast = true;
> > +        /* If we're not in ENTDAA, a broadcast is the start of a new CCC.
> */
> > +        if (!bus->in_entdaa) {
> > +            bus->in_ccc = false;
> > +        }
> > +    } else {
> > +        bus->broadcast = false;
> > +    }
> > +
> > +    /* No one responded to the address, NACK it. */
> > +    if (!i3c_scan_bus(bus, address, event)) {
> > +        return -1;
> > +    }
> > +
> > +    QLIST_FOREACH(node, &bus->current_devs, next) {
> > +        I3CTarget *t = node->target;
> > +
> > +        tc = I3C_TARGET_GET_CLASS(t);
> > +        if (tc->event) {
> > +            int rv = i3c_target_event(t, event);
> > +            if (rv && !bus->broadcast) {
> > +                return rv;
> > +            }
> > +        }
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv) {
> > +    trace_i3c_start_transfer(address, is_recv);
> > +    return i3c_do_start_transfer(bus, address, is_recv
> > +                                               ?
> I3C_START_RECV
> > +                                               :
> I3C_START_SEND); }
> > +
> > +int i3c_start_recv(I3CBus *bus, uint8_t address) {
> > +    trace_i3c_start_transfer(address, true);
> > +    return i3c_do_start_transfer(bus, address, I3C_START_RECV); }
> > +
> > +int i3c_start_send(I3CBus *bus, uint8_t address) {
> > +    trace_i3c_start_transfer(address, false);
> > +    return i3c_do_start_transfer(bus, address, I3C_START_SEND); }
> > +
> > +void i3c_end_transfer(I3CBus *bus)
> > +{
> > +    I3CTargetClass *tc;
> > +    I3CNode *node, *next;
> > +
> > +    trace_i3c_end_transfer();
> > +
> > +    /*
> > +     * If we're in ENTDAA, we need to notify all devices when ENTDAA is
> done.
> > +     * This is because everyone initially participates due to the broadcast,
> > +     * but gradually drops out as they get assigned addresses.
> > +     * Since the current_devs list only stores who's currently participating,
> > +     * and not everyone who previously participated, we send the STOP to
> all
> > +     * children.
> > +     */
> > +    if (bus->in_entdaa) {
> > +        BusChild *child;
> > +
> > +        QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) {
> > +            DeviceState *qdev = child->child;
> > +            I3CTarget *t = I3C_TARGET(qdev);
> > +            tc = I3C_TARGET_GET_CLASS(t);
> > +            if (tc->event) {
> > +                i3c_target_event(t, I3C_STOP);
> > +            }
> > +        }
> > +    } else {
> > +        QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
> > +            I3CTarget *t = node->target;
> > +            tc = I3C_TARGET_GET_CLASS(t);
> > +            if (tc->event) {
> > +                i3c_target_event(t, I3C_STOP);
> > +            }
> > +            QLIST_REMOVE(node, next);
> > +            g_free(node);
> > +        }
> > +    }
> > +    bus->broadcast = false;
> > +    bus->in_entdaa = false;
> > +    bus->in_ccc = false;
> > +}
> > +
> > +/*
> > + * Any CCCs that are universal across all I3C devices should be handled
> here.
> > + * Once they're handled, we pass the CCC up to the I3C target to do
> > +anything
> > + * else it may want with the bytes.
> > + */
> > +static int i3c_target_handle_ccc_write(I3CTarget *t, const uint8_t *data,
> > +                                       uint32_t num_to_send,
> uint32_t
> > +*num_sent) {
> > +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> > +    *num_sent = 0;
> > +
> > +    /* Is this the start of a new CCC? */
> > +    if (!t->in_ccc) {
> > +        t->curr_ccc = *data;
> > +        t->in_ccc = true;
> > +        *num_sent = 1;
> > +        trace_i3c_target_handle_ccc(t->address, t->curr_ccc);
> > +    }
> > +
> > +    switch (t->curr_ccc) {
> > +    case I3C_CCC_ENTDAA:
> > +        /*
> > +         * This is the last byte of ENTDAA, the controller is assigning us
> an
> > +         * address.
> > +         */
> > +        if (t->ccc_byte_offset == 8) {
> > +            t->address = *data;
> > +            t->in_ccc = false;
> > +            t->curr_ccc = 0;
> > +            t->ccc_byte_offset = 0;
> > +            *num_sent = 1;
> > +        }
> > +        break;
> > +    case I3C_CCCD_SETDASA:
> > +        t->address = t->static_address;
> > +        break;
> > +    case I3C_CCC_SETAASA:
> > +        t->address = t->static_address;
> > +        break;
> > +    case I3C_CCC_RSTDAA:
> > +        t->address = 0;
> > +        break;
> > +    case I3C_CCCD_SETNEWDA:
> > +        /* If this isn't the CCC byte, it's our new address. */
> > +        if (*num_sent == 0) {
> > +            t->address = *data;
> > +            *num_sent = 1;
> > +        }
> > +        break;
> > +    case I3C_CCC_ENTTM:
> > +        /*
> > +         * If there are still more to look at, the next byte is the test mode
> > +         * byte.
> > +         */
> > +        if (*num_sent != num_to_send) {
> > +            /* Enter test mode if the byte is non-zero. Otherwise exit. */
> > +            t->in_test_mode = !!data[*num_sent];
> > +            ++*num_sent;
> > +        }
> > +        break;
> > +    /* Ignore other CCCs it's better to handle on a device-by-device basis.
> */
> > +    default:
> > +        break;
> > +    }
> > +    return tc->handle_ccc_write(t, data, num_to_send, num_sent); }
> > +
> > +int i3c_send_byte(I3CBus *bus, uint8_t data) {
> > +    /*
> > +     * Ignored, the caller can determine how many were sent based on if
> this was
> > +     * ACKed/NACKed.
> > +     */
> > +    uint32_t num_sent;
> > +    return i3c_send(bus, &data, 1, &num_sent); }
> > +
> > +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
> > +             uint32_t *num_sent)
> > +{
> > +    I3CTargetClass *tc;
> > +    I3CTarget *t;
> > +    I3CNode *node;
> > +    int ret = 0;
> > +
> > +    /* If this message is a broadcast and no CCC has been found, grab it. */
> > +    if (bus->broadcast && !bus->in_ccc) {
> > +        bus->ccc = *data;
> > +        bus->in_ccc = true;
> > +        /*
> > +         * We need to keep track if we're currently in ENTDAA.
> > +         * On any other CCC, the CCC is over on a RESTART or STOP, but
> ENTDAA
> > +         * is only over on a STOP.
> > +         */
> > +        if (bus->ccc == I3C_CCC_ENTDAA) {
> > +            bus->in_entdaa = true;
> > +        }
> > +    }
> > +
> > +    QLIST_FOREACH(node, &bus->current_devs, next) {
> > +        t = node->target;
> > +        tc = I3C_TARGET_GET_CLASS(t);
> > +        if (bus->in_ccc) {
> > +            if (!tc->handle_ccc_write) {
> > +                ret = -1;
> > +                continue;
> > +            }
> > +            ret = i3c_target_handle_ccc_write(t, data, num_to_send,
> num_sent);
> > +            /* Targets should only NACK on a direct CCC. */
> > +            if (ret && !CCC_IS_DIRECT(bus->ccc)) {
> > +                ret = 0;
> > +            }
> > +        } else {
> > +            if (tc->send) {
> > +                ret = ret || tc->send(t, data, num_to_send, num_sent);
> > +            } else {
> > +                ret = -1;
> > +            }
> > +        }
> > +    }
> > +
> > +    trace_i3c_send(*num_sent, num_to_send, ret == 0);
> > +
> > +    return ret ? -1 : 0;
> > +}
> > +
> > +static int i3c_target_handle_ccc_read(I3CTarget *t, uint8_t *data,
> > +                                      uint32_t num_to_read,
> uint32_t
> > +*num_read) {
> > +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> > +    uint8_t read_count = 0;
> > +    uint64_t pid;
> > +
> > +    switch (t->curr_ccc) {
> > +    case I3C_CCC_ENTDAA:
> > +        if (t->in_test_mode) {
> > +            pid = TEST_MODE_PROVISIONED_ID;
> > +        } else {
> > +            pid = t->pid;
> > +        }
> > +        /* Return the 6-byte PID, followed by BCR then DCR. */
> > +        while (t->ccc_byte_offset < 6) {
> > +            if (read_count >= num_to_read) {
> > +                break;
> > +            }
> > +            data[read_count] = (pid >> (t->ccc_byte_offset * 8)) & 0xff;
> > +            t->ccc_byte_offset++;
> > +            read_count++;
> > +        }
> > +        if (read_count < num_to_read) {
> > +            data[read_count] = t->bcr;
> > +            t->ccc_byte_offset++;
> > +            read_count++;
> > +        }
> > +        if (read_count < num_to_read) {
> > +            data[read_count] = t->dcr;
> > +            t->ccc_byte_offset++;
> > +            read_count++;
> > +        }
> > +        *num_read = read_count;
> > +        break;
> > +    case I3C_CCCD_GETPID:
> > +        while (t->ccc_byte_offset < 6) {
> > +            if (read_count >= num_to_read) {
> > +                break;
> > +            }
> > +            data[read_count] = (t->pid >> (t->ccc_byte_offset * 8)) &
> 0xff;
> > +            t->ccc_byte_offset++;
> > +            read_count++;
> > +        }
> > +        *num_read = read_count;
> > +        break;
> > +    case I3C_CCCD_GETBCR:
> > +        *data = t->bcr;
> > +        *num_read = 1;
> > +        break;
> > +    case I3C_CCCD_GETDCR:
> > +        *data = t->dcr;
> > +        *num_read = 1;
> > +        break;
> > +    default:
> > +        /* Unhandled on the I3CTarget class level. */
> > +        break;
> > +    }
> > +
> > +    return tc->handle_ccc_read(t, data, num_to_read, num_read); }
> > +
> > +int i3c_recv_byte(I3CBus *bus, uint8_t *data) {
> > +     /*
> > +      * Ignored, the caller can determine how many bytes were read
> based on if
> > +      * this is ACKed/NACKed.
> > +      */
> > +    uint32_t num_read;
> > +    return i3c_recv(bus, data, 1, &num_read); }
> > +
> > +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read,
> > +             uint32_t *num_read)
> > +{
> > +    int ret = 0;
> > +    I3CTargetClass *tc;
> > +    I3CTarget *t;
> > +
> > +    *data = 0xff;
> > +    if (!QLIST_EMPTY(&bus->current_devs)) {
> > +        tc =
> I3C_TARGET_GET_CLASS(QLIST_FIRST(&bus->current_devs)->target);
> > +        t = QLIST_FIRST(&bus->current_devs)->target;
> > +        if (bus->in_ccc) {
> > +            if (!tc->handle_ccc_read) {
> > +                return -1;
> > +            }
> > +            ret = i3c_target_handle_ccc_read(t, data, num_to_read,
> num_read);
> > +        } else {
> > +            if (tc->recv) {
> > +                /*
> > +                 * Targets cannot NACK on a direct transfer, so the data
> > +                 * is returned directly.
> > +                 */
> > +                *num_read = tc->recv(t, data, num_to_read);
> > +            }
> > +        }
> > +    }
> > +
> > +    trace_i3c_recv(*num_read, num_to_read, ret == 0);
> > +
> > +    return ret;
> > +}
> > +
> > +void i3c_nack(I3CBus *bus)
> > +{
> > +    I3CTargetClass *tc;
> > +    I3CNode *node;
> > +
> > +    if (QLIST_EMPTY(&bus->current_devs)) {
> > +        return;
> > +    }
> > +
> > +    QLIST_FOREACH(node, &bus->current_devs, next) {
> > +        tc = I3C_TARGET_GET_CLASS(node->target);
> > +        if (tc->event) {
> > +            i3c_target_event(node->target, I3C_NACK);
> > +        }
> > +    }
> > +}
> > +
> > +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv) {
> > +    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
> > +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> > +    trace_i3c_target_send_ibi(addr, is_recv);
> > +    return bc->ibi_handle(bus, addr, is_recv); }
> > +
> > +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data) {
> > +    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
> > +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> > +    trace_i3c_target_send_ibi_bytes(data);
> > +    return bc->ibi_recv(bus, data);
> > +}
> > +
> > +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data) {
> > +    I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus);
> > +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> > +    trace_i3c_target_ibi_finish();
> > +    return bc->ibi_finish(bus);
> > +}
> > +
> > +static bool i3c_addr_is_rsvd(uint8_t addr) {
> > +    const bool is_rsvd[255] = {
> 
> This array should be a static const and its size needs a fix (0x100)
> 
I saw that you created a new patch here:
https://github.com/legoater/qemu/commit/c4e07223550b4de486cb43ea176cfbe1be30a1ce 

Thank you for your support.

> Thanks,
> 
> C.
> 
> 
> > +        [0x00] = true,
> > +        [0x01] = true,
> > +        [0x02] = true,
> > +        [0x3e] = true,
> > +        [0x5e] = true,
> > +        [0x6e] = true,
> > +        [0x76] = true,
> > +        [0x7a] = true,
> > +        [0x7c] = true,
> > +        [0x7e] = true,
> > +        [0x7f] = true,
> > +    };
> > +
> > +    return is_rsvd[addr];
> > +}
> > +
> > +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
> > +                          uint8_t bcr, uint64_t pid) {
> > +    DeviceState *dev;
> > +
> > +    dev = qdev_new(name);
> > +    qdev_prop_set_uint8(dev, "static-address", addr);
> > +    qdev_prop_set_uint8(dev, "dcr", dcr);
> > +    qdev_prop_set_uint8(dev, "bcr", bcr);
> > +    qdev_prop_set_uint64(dev, "pid", pid);
> > +
> > +    if (i3c_addr_is_rsvd(addr)) {
> > +        g_autofree char *path =
> object_get_canonical_path(OBJECT(dev));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created
> with reserved "
> > +                      "address 0x%.2x\n", path, addr);
> > +    }
> > +    return I3C_TARGET(dev);
> > +}
> > +
> > +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error
> > +**errp) {
> > +    return qdev_realize_and_unref(&dev->parent_obj, &bus->parent_obj,
> > +errp); }
> > +
> > +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name,
> uint8_t addr,
> > +                                    uint8_t dcr, uint8_t bcr,
> > +uint64_t pid) {
> > +    I3CTarget *dev = i3c_target_new(name, addr, dcr, bcr, pid);
> > +    dev->address = 0;
> > +    i3c_target_realize_and_unref(dev, bus, &error_abort);
> > +
> > +    return dev;
> > +}
> > +
> > +/* Legacy I2C functions. */
> > +void legacy_i2c_nack(I3CBus *bus)
> > +{
> > +    trace_legacy_i2c_nack();
> > +    i2c_nack(bus->i2c_bus);
> > +}
> > +
> > +uint8_t legacy_i2c_recv(I3CBus *bus)
> > +{
> > +    uint8_t byte = i2c_recv(bus->i2c_bus);
> > +    trace_legacy_i2c_recv(byte);
> > +    return byte;
> > +}
> > +
> > +int legacy_i2c_send(I3CBus *bus, uint8_t data) {
> > +    trace_legacy_i2c_send(data);
> > +    return i2c_send(bus->i2c_bus, data); }
> > +
> > +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool
> > +is_recv) {
> > +    trace_legacy_i2c_start_transfer(address, is_recv);
> > +    return i2c_start_transfer(bus->i2c_bus, address, is_recv); }
> > +
> > +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address) {
> > +    trace_legacy_i2c_start_transfer(address, true);
> > +    return i2c_start_transfer(bus->i2c_bus, address,
> > +/*is_recv=*/true); }
> > +
> > +int legacy_i2c_start_send(I3CBus *bus, uint8_t address) {
> > +    trace_legacy_i2c_start_transfer(address, false);
> > +    return i2c_start_transfer(bus->i2c_bus, address,
> > +/*is_recv=*/false); }
> > +
> > +void legacy_i2c_end_transfer(I3CBus *bus) {
> > +    trace_legacy_i2c_end_transfer();
> > +    i2c_end_transfer(bus->i2c_bus);
> > +}
> > +
> > +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char
> *name,
> > +                                          uint8_t addr) {
> > +    I2CSlave *dev = i2c_slave_new(name, addr);
> > +
> > +    i2c_slave_realize_and_unref(dev, bus->i2c_bus, &error_abort);
> > +    return dev;
> > +}
> > +
> > +static void i3c_target_class_init(ObjectClass *klass, const void
> > +*data) {
> > +    DeviceClass *k = DEVICE_CLASS(klass);
> > +    I3CTargetClass *sc = I3C_TARGET_CLASS(klass);
> > +    set_bit(DEVICE_CATEGORY_MISC, k->categories);
> > +    k->bus_type = TYPE_I3C_BUS;
> > +    device_class_set_props(k, i3c_props);
> > +    sc->target_match = i3c_target_match; }
> > +
> > +static const TypeInfo i3c_types[] = {
> > +    {
> > +        .name = TYPE_I3C_BUS,
> > +        .parent = TYPE_BUS,
> > +        .instance_size = sizeof(I3CBus),
> > +        .class_size = sizeof(I3CBusClass),
> > +    },
> > +    {
> > +        .name = TYPE_I3C_TARGET,
> > +        .parent = TYPE_DEVICE,
> > +        .instance_size = sizeof(I3CTarget),
> > +        .abstract = true,
> > +        .class_size = sizeof(I3CTargetClass),
> > +        .class_init = i3c_target_class_init,
> > +    },
> > +};
> > +
> > +DEFINE_TYPES(i3c_types)
> > diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build index
> > ebf20325cb..fb127613fe 100644
> > --- a/hw/i3c/meson.build
> > +++ b/hw/i3c/meson.build
> > @@ -1,3 +1,4 @@
> >   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'))
> >   system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss) diff --git
> > a/hw/i3c/trace-events b/hw/i3c/trace-events index
> > 3ead84eb45..cdf7cb07f6 100644
> > --- a/hw/i3c/trace-events
> > +++ b/hw/i3c/trace-events
> > @@ -5,3 +5,19 @@ aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C
> read: offset 0x%" PRIx64 "
> >   aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%"
> PRIx64 " data 0x%" PRIx64
> >   aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data)
> "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
> >   aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t
> > data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
> > +
> > +# core.c
> > +i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%"
> > +PRIx8 " event 0x%" PRIx8 i3c_target_handle_ccc(uint8_t address,
> > +uint8_t ccc) "I3C target 0x%" PRIx8 " handling CCC 0x%" PRIx8
> i3c_target_send_ibi(uint8_t address, bool is_recv) "I3C target IBI address 0x%"
> PRIx8 " RnW=%d"
> > +i3c_target_send_ibi_bytes(uint8_t byte) "I3C target IBI byte 0x%"
> > +PRIx8
> > +i3c_target_ibi_finish(void) "I3C target IBI finish"
> > +i3c_start_transfer(uint8_t address, bool is_recv) "I3C START with address
> 0x%" PRIx8 " is_recv=%d"
> > +i3c_end_transfer(void) "I3C transfer done"
> > +i3c_send(uint32_t num_sent, uint32_t num_to_send, bool ack) "I3C send %"
> PRId32 "/%" PRId32 " bytes, ack=%d"
> > +i3c_recv(uint32_t num_read, uint32_t num_to_read, bool ack) "I3C recv %"
> PRId32 "/%" PRId32 " bytes, ack=%d"
> > +legacy_i2c_nack(void) "Legacy I2C NACK"
> > +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"


^ permalink raw reply	[flat|nested] 47+ messages in thread

* RE: [PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX
  2026-02-27  9:56   ` Cédric Le Goater
@ 2026-03-02  3:33     ` Jamin Lin
  0 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-03-02  3:33 UTC (permalink / raw)
  To: Cédric Le Goater, Paolo Bonzini, Peter Maydell, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Stephen Longfield, Patrick Venture

Hi Cédric,

> Subject: Re: [PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX
> 
> On 2/25/26 03:12, Jamin Lin wrote:
> > This adds data and CCC transmission, reception, and the associated
> > queues required for data transmission and reception to happen.
> >
> > The I3C controller transmits data by the user writing into a command
> > queue. When the queue has a command and an argument in it, the
> > controller starts executing the command.
> >
> > The controller can execute 1 of 3 ways:
> > 1. A larger data transfer that involves using the TX and RX queues. This
> >     is the most common way the controller does transactions.
> >
> > 2. A small data transfer that involves sending a couple bytes passed
> >     into the command queue argument.
> >
> > 3. An address assignment command. This is how the controller does
> >     ENTDAA. When ENTDAA succeeds in assigning an address to a target, it
> >     updates the controller's char table with the target's PID, BCR, and
> >     DCR.
> >
> > The controller determines what addresses to send by looking at the
> > index in the device address table specified by the argument in the
> > command queue. ENTDAA also uses these addresses to assign to targets on
> the bus.
> >
> > When the controller is done executing a command, it puts a response in
> > the response queue indicating how command execution went.
> >
> > In order for the user to send and receive data to/from the controller,
> > the user reads/writes to a bidirectional TX/RX port.
> >
> > Signed-off-by: Joe Komlodi <komlodi@google.com>
> > Reviewed-by: Stephen Longfield <slongfield@google.com>
> > Reviewed-by: Patrick Venture <venture@google.com>
> > Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > ---
> >   include/hw/i3c/aspeed_i3c.h |   1 +
> >   include/hw/i3c/dw-i3c.h     | 143 +++++-
> >   hw/i3c/dw-i3c.c             | 882
> +++++++++++++++++++++++++++++++++++-
> >   hw/i3c/trace-events         |  10 +
> >   4 files changed, 1030 insertions(+), 6 deletions(-)
> >
> > diff --git a/include/hw/i3c/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
> > index ade5c42d39..b8827d31d7 100644
> > --- a/include/hw/i3c/aspeed_i3c.h
> > +++ b/include/hw/i3c/aspeed_i3c.h
> > @@ -2,6 +2,7 @@
> >    * ASPEED I3C Controller
> >    *
> >    * Copyright (C) 2021 ASPEED Technology Inc.
> > + * Copyright (C) 2023 Google, LLC
> >    *
> >    * This code is licensed under the GPL version 2 or later.  See
> >    * the COPYING file in the top-level directory.
> > diff --git a/include/hw/i3c/dw-i3c.h b/include/hw/i3c/dw-i3c.h index
> > 7143e8ca7a..c50d67cc6b 100644
> > --- a/include/hw/i3c/dw-i3c.h
> > +++ b/include/hw/i3c/dw-i3c.h
> > @@ -10,20 +10,159 @@
> >   #ifndef DW_I3C_H
> >   #define DW_I3C_H
> >
> > +#include "qemu/fifo32.h"
> > +#include "hw/i3c/i3c.h"
> >   #include "hw/core/sysbus.h"
> >
> >   #define TYPE_DW_I3C "dw.i3c"
> >   OBJECT_DECLARE_SIMPLE_TYPE(DWI3C, DW_I3C)
> >
> > -#define DW_I3C_NR_REGS (0x300 >> 2)
> > +/*
> > + * Sufficiently large enough to handle configurations with large
> > +device address
> > + * tables.
> > + */
> > +#define DW_I3C_NR_REGS (0x1000 >> 2)
> > +
> > +/* From datasheet. */
> > +#define DW_I3C_CMD_ATTR_TRANSFER_CMD 0 #define
> > +DW_I3C_CMD_ATTR_TRANSFER_ARG 1 #define
> DW_I3C_CMD_ATTR_SHORT_DATA_ARG
> > +2 #define DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD 3
> > +
> > +/* Enum values from datasheet. */
> > +typedef enum DWI3CRespQueueErr {
> > +    DW_I3C_RESP_QUEUE_ERR_NONE = 0,
> > +    DW_I3C_RESP_QUEUE_ERR_CRC = 1,
> > +    DW_I3C_RESP_QUEUE_ERR_PARITY = 2,
> > +    DW_I3C_RESP_QUEUE_ERR_FRAME = 3,
> > +    DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK = 4,
> > +    DW_I3C_RESP_QUEUE_ERR_DAA_NACK = 5,
> > +    DW_I3C_RESP_QUEUE_ERR_OVERFLOW = 6,
> > +    DW_I3C_RESP_QUEUE_ERR_ABORTED = 8,
> > +    DW_I3C_RESP_QUEUE_ERR_I2C_NACK = 9, } DWI3CRespQueueErr;
> > +
> > +typedef enum DWI3CTransferState {
> > +    DW_I3C_TRANSFER_STATE_IDLE = 0x00,
> > +    DW_I3C_TRANSFER_STATE_START = 0x01,
> > +    DW_I3C_TRANSFER_STATE_RESTART = 0x02,
> > +    DW_I3C_TRANSFER_STATE_STOP = 0x03,
> > +    DW_I3C_TRANSFER_STATE_START_HOLD = 0x04,
> > +    DW_I3C_TRANSFER_STATE_BROADCAST_W = 0x05,
> > +    DW_I3C_TRANSFER_STATE_BROADCAST_R = 0x06,
> > +    DW_I3C_TRANSFER_STATE_DAA = 0x07,
> > +    DW_I3C_TRANSFER_STATE_DAA_GEN = 0x08,
> > +    DW_I3C_TRANSFER_STATE_CCC_BYTE = 0x0b,
> > +    DW_I3C_TRANSFER_STATE_HDR_CMD = 0x0c,
> > +    DW_I3C_TRANSFER_STATE_WRITE = 0x0d,
> > +    DW_I3C_TRANSFER_STATE_READ = 0x0e,
> > +    DW_I3C_TRANSFER_STATE_IBI_READ = 0x0f,
> > +    DW_I3C_TRANSFER_STATE_IBI_DIS = 0x10,
> > +    DW_I3C_TRANSFER_STATE_HDR_DDR_CRC = 0x11,
> > +    DW_I3C_TRANSFER_STATE_CLK_STRETCH = 0x12,
> > +    DW_I3C_TRANSFER_STATE_HALT = 0x13, } DWI3CTransferState;
> > +
> > +typedef enum DWI3CTransferStatus {
> > +    DW_I3C_TRANSFER_STATUS_IDLE = 0x00,
> > +    DW_I3C_TRANSFER_STATUS_BROACAST_CCC = 0x01,
> > +    DW_I3C_TRANSFER_STATUS_DIRECT_CCC_W = 0x02,
> > +    DW_I3C_TRANSFER_STATUS_DIRECT_CCC_R = 0x03,
> > +    DW_I3C_TRANSFER_STATUS_ENTDAA = 0x04,
> > +    DW_I3C_TRANSFER_STATUS_SETDASA = 0x05,
> > +    DW_I3C_TRANSFER_STATUS_I3C_SDR_W = 0x06,
> > +    DW_I3C_TRANSFER_STATUS_I3C_SDR_R = 0x07,
> > +    DW_I3C_TRANSFER_STATUS_I2C_SDR_W = 0x08,
> > +    DW_I3C_TRANSFER_STATUS_I2C_SDR_R = 0x09,
> > +    DW_I3C_TRANSFER_STATUS_HDR_TS_W = 0x0a,
> > +    DW_I3C_TRANSFER_STATUS_HDR_TS_R = 0x0b,
> > +    DW_I3C_TRANSFER_STATUS_HDR_DDR_W = 0x0c,
> > +    DW_I3C_TRANSFER_STATUS_HDR_DDR_R = 0x0d,
> > +    DW_I3C_TRANSFER_STATUS_IBI = 0x0e,
> > +    DW_I3C_TRANSFER_STATUS_HALT = 0x0f, } DWI3CTransferStatus;
> > +
> > +/*
> > + * Transfer commands and arguments are 32-bit wide values that the
> > +user passes
> > + * into the command queue. We interpret each 32-bit word based on the
> > +cmd_attr
> > + * field.
> > + */
> > +typedef struct DWI3CTransferCmd {
> > +    uint8_t cmd_attr:3;
> > +    uint8_t tid:4; /* Transaction ID */
> > +    uint16_t cmd:8;
> > +    uint8_t cp:1; /* Command present */
> > +    uint8_t dev_index:5;
> > +    uint8_t speed:3;
> > +    uint8_t resv0:1;
> > +    uint8_t dbp:1; /* Defining byte present */
> > +    uint8_t roc:1; /* Response on completion */
> > +    uint8_t sdap:1; /* Short data argument present */
> > +    uint8_t rnw:1; /* Read not write */
> > +    uint8_t resv1:1;
> > +    uint8_t toc:1; /* Termination (I3C STOP) on completion */
> > +    uint8_t pec:1; /* Parity error check enabled */ }
> > +DWI3CTransferCmd;
> > +
> > +typedef struct DWI3CTransferArg {
> > +    uint8_t cmd_attr:3;
> > +    uint8_t resv:5;
> > +    uint8_t db; /* Defining byte */
> > +    uint16_t data_len;
> > +} DWI3CTransferArg;
> > +
> > +typedef struct DWI3CShortArg {
> > +    uint8_t cmd_attr:3;
> > +    uint8_t byte_strb:3;
> > +    uint8_t resv:2;
> > +    uint8_t byte0;
> > +    uint8_t byte1;
> > +    uint8_t byte2;
> > +} DWI3CShortArg;
> > +
> > +typedef struct DWI3CAddrAssignCmd {
> > +    uint8_t cmd_attr:3;
> > +    uint8_t tid:4; /* Transaction ID */
> > +    uint16_t cmd:8;
> > +    uint8_t resv0:1;
> > +    uint8_t dev_index:5;
> > +    uint16_t dev_count:5;
> > +    uint8_t roc:1; /* Response on completion */
> > +    uint8_t resv1:3;
> > +    uint8_t toc:1; /* Termination (I3C STOP) on completion */
> > +    uint8_t resv2:1;
> > +} DWI3CAddrAssignCmd;
> > +
> > +typedef union DWI3CCmdQueueData {
> > +    uint32_t word;
> > +    DWI3CTransferCmd transfer_cmd;
> > +    DWI3CTransferArg transfer_arg;
> > +    DWI3CShortArg short_arg;
> > +    DWI3CAddrAssignCmd addr_assign_cmd; } DWI3CCmdQueueData;
> >
> >   struct DWI3C {
> >       SysBusDevice parent_obj;
> >
> >       MemoryRegion mr;
> >       qemu_irq irq;
> > +    I3CBus *bus;
> > +
> > +    Fifo32 cmd_queue;
> > +    Fifo32 resp_queue;
> > +    Fifo32 tx_queue;
> > +    Fifo32 rx_queue;
> >
> > -    uint8_t id;
> > +    struct {
> > +        uint8_t id;
> > +        uint8_t cmd_resp_queue_capacity_bytes;
> > +        uint16_t tx_rx_queue_capacity_bytes;
> > +        uint8_t num_addressable_devices;
> > +        uint16_t dev_addr_table_pointer;
> > +        uint16_t dev_addr_table_depth;
> > +        uint16_t dev_char_table_pointer;
> > +        uint16_t dev_char_table_depth;
> > +    } cfg;
> >       uint32_t regs[DW_I3C_NR_REGS];
> >   };
> >
> > diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c index
> > 0b99c7edfb..2453d74a8c 100644
> > --- a/hw/i3c/dw-i3c.c
> > +++ b/hw/i3c/dw-i3c.c
> > @@ -336,12 +336,179 @@ static const uint32_t
> dw_i3c_ro[DW_I3C_NR_REGS] = {
> >       [R_SLAVE_CONFIG]                = 0xffffffff,
> >   };
> >
> > +static inline bool dw_i3c_has_hdr_ts(DWI3C *s) {
> > +    return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_TS); }
> > +
> > +static inline bool dw_i3c_has_hdr_ddr(DWI3C *s) {
> > +    return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_DDR); }
> > +
> > +static inline bool dw_i3c_can_transmit(DWI3C *s) {
> > +    /*
> > +     * We can only transmit if we're enabled and the resume bit is
> cleared.
> > +     * The resume bit is set on a transaction error, and software must
> clear it.
> > +     */
> > +    return ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_EN) &&
> > +           !ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_RESUME); }
> > +
> > +static inline uint8_t dw_i3c_fifo_threshold_from_reg(uint8_t regval)
> > +{
> > +    return regval = regval ? (2 << regval) : 1; }
> > +
> >   static void dw_i3c_update_irq(DWI3C *s)
> >   {
> >       bool level = !!(s->regs[R_INTR_SIGNAL_EN] &
> s->regs[R_INTR_STATUS]);
> >       qemu_set_irq(s->irq, level);
> >   }
> >
> > +static void dw_i3c_end_transfer(DWI3C *s, bool is_i2c) {
> > +    if (is_i2c) {
> > +        legacy_i2c_end_transfer(s->bus);
> > +    } else {
> > +        i3c_end_transfer(s->bus);
> > +    }
> > +}
> > +
> > +static int dw_i3c_send_start(DWI3C *s, uint8_t addr, bool is_recv,
> > +bool is_i2c) {
> > +    int ret;
> > +
> > +    if (is_i2c) {
> > +        ret = legacy_i2c_start_transfer(s->bus, addr, is_recv);
> > +    } else {
> > +        ret = i3c_start_transfer(s->bus, addr, is_recv);
> > +    }
> > +    if (ret) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed on TX with
> addr 0x%.2x\n",
> > +                      path, addr);
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE,
> CM_TFR_ST_STATUS,
> > +                         DW_I3C_TRANSFER_STATE_HALT);
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
> > +                         DW_I3C_TRANSFER_STATUS_HALT);
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
> > +        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
> > +    }
> > +
> > +    return ret;
> > +}
> > +
> > +static int dw_i3c_send(DWI3C *s, const uint8_t *data, uint32_t
> num_to_send,
> > +                       uint32_t *num_sent, bool is_i2c) {
> > +    int ret;
> > +    uint32_t i;
> > +
> > +    *num_sent = 0;
> > +    if (is_i2c) {
> > +        /* Legacy I2C must be byte-by-byte. */
> > +        for (i = 0; i < num_to_send; i++) {
> > +            ret = legacy_i2c_send(s->bus, data[i]);
> > +            if (ret) {
> > +                break;
> > +            }
> > +            (*num_sent)++;
> > +        }
> > +    } else {
> > +        ret = i3c_send(s->bus, data, num_to_send, num_sent);
> > +    }
> > +    if (ret) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed sending byte
> 0x%.2x\n",
> > +                      path, data[*num_sent]);
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE,
> CM_TFR_ST_STATUS,
> > +                         DW_I3C_TRANSFER_STATE_HALT);
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
> > +                         DW_I3C_TRANSFER_STATUS_HALT);
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
> > +        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
> > +    }
> > +
> > +    trace_dw_i3c_send(s->cfg.id, *num_sent);
> > +
> > +    return ret;
> > +}
> > +
> > +static int dw_i3c_send_byte(DWI3C *s, uint8_t byte, bool is_i2c) {
> > +    /*
> > +     * Ignored, the caller will know if we sent 0 or 1 bytes depending on if
> > +     * we were ACKed/NACKed.
> > +     */
> > +    uint32_t num_sent;
> > +    return dw_i3c_send(s, &byte, 1, &num_sent, is_i2c); }
> > +
> > +static int dw_i3c_recv_data(DWI3C *s, bool is_i2c, uint8_t *data,
> > +                            uint16_t num_to_read, uint32_t
> *num_read)
> > +{
> > +    int ret;
> > +
> > +    if (is_i2c) {
> > +        for (uint16_t i = 0; i < num_to_read; i++) {
> > +            data[i] = legacy_i2c_recv(s->bus);
> > +        }
> > +        /* I2C devices can neither NACK a read, nor end transfers early. */
> > +        *num_read = num_to_read;
> > +        trace_dw_i3c_recv_data(s->cfg.id, *num_read);
> > +        return 0;
> > +    }
> > +    /* I3C devices can NACK if the controller sends an unsupported CCC. */
> > +    ret = i3c_recv(s->bus, data, num_to_read, num_read);
> > +    if (ret) {
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed receiving
> byte\n",
> > +                      object_get_canonical_path(OBJECT(s)));
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE,
> CM_TFR_ST_STATUS,
> > +                         DW_I3C_TRANSFER_STATE_HALT);
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
> > +                         DW_I3C_TRANSFER_STATUS_HALT);
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
> > +        ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
> > +    }
> > +
> > +    trace_dw_i3c_recv_data(s->cfg.id, *num_read);
> > +
> > +    return ret;
> > +}
> > +
> > +static inline bool dw_i3c_target_is_i2c(DWI3C *s, uint16_t offset) {
> > +    /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array.
> */
> > +    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs,
> DEVICE_ADDR_TABLE_POINTER,
> > +                                          ADDR) /
> sizeof(uint32_t)) + offset;
> > +    return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
> > +                   LEGACY_I2C_DEVICE); }
> > +
> > +static uint8_t dw_i3c_target_addr(DWI3C *s, uint16_t offset) {
> > +    if (offset > s->cfg.num_addressable_devices) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Device addr table
> offset %d out of "
> > +                      "bounds\n", path, offset);
> > +        /* If we're out of bounds, return an address of 0. */
> > +        return 0;
> > +    }
> > +
> > +    /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array.
> */
> > +    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs,
> DEVICE_ADDR_TABLE_POINTER,
> > +                                          ADDR) /
> sizeof(uint32_t)) + offset;
> > +    /* I2C devices use a static address. */
> > +    if (dw_i3c_target_is_i2c(s, offset)) {
> > +        return FIELD_EX32(s->regs[dev_index],
> DEVICE_ADDR_TABLE_LOC1,
> > +                          DEV_STATIC_ADDR);
> > +    }
> > +    return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
> > +                      DEV_DYNAMIC_ADDR); }
> > +
> >   static uint32_t dw_i3c_intr_status_r(DWI3C *s)
> >   {
> >       /* Only return the status whose corresponding EN bits are set.
> > */ @@ -376,6 +543,56 @@ static void dw_i3c_intr_force_w(DWI3C *s,
> uint32_t val)
> >       dw_i3c_update_irq(s);
> >   }
> >
> > +static uint32_t dw_i3c_pop_rx(DWI3C *s) {
> > +    if (fifo32_is_empty(&s->rx_queue)) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read RX FIFO
> when empty\n",
> > +                      path);
> > +        return 0;
> > +    }
> > +
> > +    uint32_t val = fifo32_pop(&s->rx_queue);
> > +    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL,
> RX_BUF_BLR,
> > +                     fifo32_num_used(&s->rx_queue));
> > +
> > +    /* Threshold is 2^RX_BUF_THLD. */
> > +    uint8_t threshold = ARRAY_FIELD_EX32(s->regs,
> DATA_BUFFER_THLD_CTRL,
> > +                                         RX_BUF_THLD);
> > +    threshold = dw_i3c_fifo_threshold_from_reg(threshold);
> > +    if (fifo32_num_used(&s->rx_queue) < threshold) {
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 0);
> > +        dw_i3c_update_irq(s);
> > +    }
> > +
> > +    trace_dw_i3c_pop_rx(s->cfg.id, val);
> > +    return val;
> > +}
> > +
> > +static uint32_t dw_i3c_resp_queue_port_r(DWI3C *s) {
> > +    if (fifo32_is_empty(&s->resp_queue)) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read response
> FIFO when "
> > +                      "empty\n", path);
> > +        return 0;
> > +    }
> > +
> > +    uint32_t val = fifo32_pop(&s->resp_queue);
> > +    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
> > +                     fifo32_num_used(&s->resp_queue));
> > +
> > +    /* Threshold is the register value + 1. */
> > +    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
> > +                                         RESP_BUF_THLD) + 1;
> > +    if (fifo32_num_used(&s->resp_queue) < threshold) {
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 0);
> > +        dw_i3c_update_irq(s);
> > +    }
> > +
> > +    return val;
> > +}
> > +
> >   static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
> >   {
> >       DWI3C *s = DW_I3C(opaque);
> > @@ -392,16 +609,635 @@ static uint64_t dw_i3c_read(void *opaque,
> hwaddr offset, unsigned size)
> >       case R_INTR_STATUS:
> >           value = dw_i3c_intr_status_r(s);
> >           break;
> > +    case R_RX_TX_DATA_PORT:
> > +        value = dw_i3c_pop_rx(s);
> > +        break;
> > +    case R_RESPONSE_QUEUE_PORT:
> > +        value = dw_i3c_resp_queue_port_r(s);
> > +        break;
> >       default:
> >           value = s->regs[addr];
> >           break;
> >       }
> >
> > -    trace_dw_i3c_read(s->id, offset, value);
> > +    trace_dw_i3c_read(s->cfg.id, offset, value);
> >
> >       return value;
> >   }
> >
> > +static void dw_i3c_resp_queue_push(DWI3C *s, uint8_t err, uint8_t tid,
> > +                                   uint8_t ccc_type, uint16_t
> > +data_len) {
> > +    uint32_t val = 0;
> > +    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, ERR_STATUS, err);
> > +    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, TID, tid);
> > +    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, CCCT, ccc_type);
> > +    val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, DL, data_len);
> > +    if (!fifo32_is_full(&s->resp_queue)) {
> > +        trace_dw_i3c_resp_queue_push(s->cfg.id, val);
> > +        fifo32_push(&s->resp_queue, val);
> > +    }
> > +
> > +    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
> > +                     fifo32_num_used(&s->resp_queue));
> > +    /* Threshold is the register value + 1. */
> > +    uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
> > +                                         RESP_BUF_THLD) + 1;
> > +    if (fifo32_num_used(&s->resp_queue) >= threshold) {
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 1);
> > +        dw_i3c_update_irq(s);
> > +    }
> > +}
> > +
> > +static void dw_i3c_push_tx(DWI3C *s, uint32_t val) {
> > +    if (fifo32_is_full(&s->tx_queue)) {
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to TX
> FIFO when "
> > +                      "full\n", object_get_canonical_path(OBJECT(s)));
> > +        return;
> > +    }
> > +
> > +    trace_dw_i3c_push_tx(s->cfg.id, val);
> > +    fifo32_push(&s->tx_queue, val);
> > +    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL,
> TX_BUF_EMPTY_LOC,
> > +                     fifo32_num_free(&s->tx_queue));
> > +
> > +    /* Threshold is 2^TX_BUF_THLD. */
> > +    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs,
> DATA_BUFFER_THLD_CTRL,
> > +                                               TX_BUF_THLD);
> > +    empty_threshold =
> > +        dw_i3c_fifo_threshold_from_reg(empty_threshold);
> > +    if (fifo32_num_free(&s->tx_queue) < empty_threshold) {
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 0);
> > +        dw_i3c_update_irq(s);
> > +    }
> > +}
> > +
> > +static uint32_t dw_i3c_pop_tx(DWI3C *s) {
> > +    if (fifo32_is_empty(&s->tx_queue)) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to pop from TX
> FIFO when "
> > +                      "empty\n", path);
> > +        return 0;
> > +    }
> > +
> > +    uint32_t val = fifo32_pop(&s->tx_queue);
> > +    trace_dw_i3c_pop_tx(s->cfg.id, val);
> > +    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL,
> TX_BUF_EMPTY_LOC,
> > +                     fifo32_num_free(&s->tx_queue));
> > +
> > +    /* Threshold is 2^TX_BUF_THLD. */
> > +    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs,
> DATA_BUFFER_THLD_CTRL,
> > +                                               TX_BUF_THLD);
> > +    empty_threshold =
> > +        dw_i3c_fifo_threshold_from_reg(empty_threshold);
> > +    if (fifo32_num_free(&s->tx_queue) >= empty_threshold) {
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 1);
> > +        dw_i3c_update_irq(s);
> > +    }
> > +    return val;
> > +}
> > +
> > +static void dw_i3c_push_rx(DWI3C *s, uint32_t val) {
> > +    if (fifo32_is_full(&s->rx_queue)) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to RX
> FIFO when "
> > +                      "full\n", path);
> > +        return;
> > +    }
> > +    trace_dw_i3c_push_rx(s->cfg.id, val);
> > +    fifo32_push(&s->rx_queue, val);
> > +
> > +    ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL,
> RX_BUF_BLR,
> > +                     fifo32_num_used(&s->rx_queue));
> > +    /* Threshold is 2^RX_BUF_THLD. */
> > +    uint8_t threshold = ARRAY_FIELD_EX32(s->regs,
> DATA_BUFFER_THLD_CTRL,
> > +                                         RX_BUF_THLD);
> > +    threshold = dw_i3c_fifo_threshold_from_reg(threshold);
> > +    if (fifo32_num_used(&s->rx_queue) >= threshold) {
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 1);
> > +        dw_i3c_update_irq(s);
> > +    }
> > +}
> > +
> > +static void dw_i3c_short_transfer(DWI3C *s, DWI3CTransferCmd cmd,
> > +                                  DWI3CShortArg arg) {
> > +    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
> > +    uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
> > +    bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index);
> > +    uint8_t data[4]; /* Max we can send on a short transfer is 4 bytes. */
> > +    uint8_t len = 0;
> > +    uint32_t bytes_sent; /* Ignored on short transfers. */
> > +
> > +    /* Can't do reads on a short transfer. */
> > +    if (cmd.rnw) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot do a read on a
> short "
> > +                      "transfer\n", path);
> > +        return;
> > +    }
> > +
> > +    if (dw_i3c_send_start(s, addr, /*is_recv=*/false, is_i2c)) {
> > +        err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> > +        goto transfer_done;
> > +    }
> > +
> > +    /* Are we sending a command? */
> > +    if (cmd.cp) {
> > +        data[len] = cmd.cmd;
> > +        len++;
> > +        /*
> > +         * byte0 is the defining byte for a command, and is only sent if a
> > +         * command is present and if the command has a defining byte
> present.
> > +         * (byte_strb & 0x01) is always treated as set by the controller,
> and is
> > +         * ignored.
> > +         */
> > +        if (cmd.dbp) {
> > +            data[len] += arg.byte0;
> > +            len++;
> > +        }
> > +    }
> > +
> > +    /* Send the bytes passed in the argument. */
> > +    if (arg.byte_strb & 0x02) {
> > +        data[len] = arg.byte1;
> > +        len++;
> > +    }
> > +    if (arg.byte_strb & 0x04) {
> > +        data[len] = arg.byte2;
> > +        len++;
> > +    }
> > +
> > +    if (dw_i3c_send(s, data, len, &bytes_sent, is_i2c)) {
> > +        err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> > +    } else {
> > +        /* Only go to an idle state on a successful transfer. */
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE,
> CM_TFR_ST_STATUS,
> > +                         DW_I3C_TRANSFER_STATE_IDLE);
> > +    }
> > +
> > +transfer_done:
> > +    if (cmd.toc) {
> > +        dw_i3c_end_transfer(s, is_i2c);
> > +    }
> > +    if (cmd.roc) {
> > +        /*
> > +         * ccc_type is always 0 in controller mode, data_len is 0 in short
> > +         * transfers.
> > +         */
> > +        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
> > +                                          /*data_len=*/0);
> > +    }
> > +}
> > +
> > +/* Returns number of bytes transmitted. */ static uint16_t
> > +dw_i3c_tx(DWI3C *s, uint16_t num, bool is_i2c) {
> > +    uint16_t bytes_sent = 0;
> > +    union {
> > +        uint8_t b[sizeof(uint32_t)];
> > +        uint32_t val;
> > +    } val32;
> > +
> > +    while (bytes_sent < num) {
> > +        val32.val = dw_i3c_pop_tx(s);
> > +        for (uint8_t i = 0; i < sizeof(val32.val); i++) {
> > +            if (dw_i3c_send_byte(s, val32.b[i], is_i2c)) {
> > +                return bytes_sent;
> > +            }
> > +            bytes_sent++;
> > +
> > +            /* We're not sending the full 32-bits, break early. */
> > +            if (bytes_sent >= num) {
> > +                break;
> > +            }
> > +        }
> > +    }
> > +
> > +    return bytes_sent;
> > +}
> > +
> > +/* Returns number of bytes received. */ static uint16_t
> > +dw_i3c_rx(DWI3C *s, uint16_t num, bool is_i2c) {
> > +    /*
> > +     * Allocate a temporary buffer to read data from the target.
> > +     * Zero it and word-align it as well in case we're reading unaligned
> data.
> > +     */
> > +    g_autofree uint8_t *data = g_new0(uint8_t, num + (4 - (num &
> > +0x03)));
> 
> Why not use ROUND_UP(num, 4) ?
> 

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,
> 
> C.
> 
> 
> > +    uint32_t *data32 = (uint32_t *)data;
> > +    /*
> > +     * 32-bits since the I3C API wants a 32-bit number, even though the
> > +     * controller can only do 16-bit transfers.
> > +     */
> > +    uint32_t num_read = 0;
> > +
> > +    /* Can NACK if the target receives an unsupported CCC. */
> > +    if (dw_i3c_recv_data(s, is_i2c, data, num, &num_read)) {
> > +        return 0;
> > +    }
> > +
> > +    for (uint16_t i = 0; i < num_read / 4; i++) {
> > +        dw_i3c_push_rx(s, *data32);
> > +        data32++;
> > +    }
> > +    /*
> > +     * If we're pushing data that isn't 32-bit aligned, push what's left.
> > +     * It's software's responsibility to know what bits are valid in the
> partial
> > +     * data.
> > +     */
> > +    if (num_read & 0x03) {
> > +        dw_i3c_push_rx(s, *data32);
> > +    }
> > +
> > +    return num_read;
> > +}
> > +
> > +static int dw_i3c_transfer_ccc(DWI3C *s, DWI3CTransferCmd cmd,
> > +                               DWI3CTransferArg arg) {
> > +    /* CCC start is always a write. CCCs cannot be done on I2C devices. */
> > +    if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
> > +                                     /*is_i2c=*/false)) {
> > +        return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> > +    }
> > +    trace_dw_i3c_transfer_ccc(s->cfg.id, cmd.cmd);
> > +    if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) {
> > +        return DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> > +    }
> > +
> > +    /* On a direct CCC, we do a restart and then send the target's address.
> */
> > +    if (CCC_IS_DIRECT(cmd.cmd)) {
> > +        bool is_recv = cmd.rnw;
> > +        uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
> > +        if (dw_i3c_send_start(s, addr, is_recv, /*is_i2c=*/false)) {
> > +            return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> > +        }
> > +    }
> > +
> > +    return DW_I3C_RESP_QUEUE_ERR_NONE; }
> > +
> > +static void dw_i3c_transfer(DWI3C *s, DWI3CTransferCmd cmd,
> > +                            DWI3CTransferArg arg) {
> > +    bool is_recv = cmd.rnw;
> > +    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
> > +    uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
> > +    bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index);
> > +    uint16_t bytes_transferred = 0;
> > +
> > +    if (cmd.cp) {
> > +        /* We're sending a CCC. */
> > +        err = dw_i3c_transfer_ccc(s, cmd, arg);
> > +        if (err != DW_I3C_RESP_QUEUE_ERR_NONE) {
> > +            goto transfer_done;
> > +        }
> > +    } else {
> > +        if (ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL,
> I3C_BROADCAST_ADDR_INC) &&
> > +            is_i2c == false) {
> > +            if (dw_i3c_send_start(s, I3C_BROADCAST,
> > +                                             /*is_recv=*/false,
> is_i2c)) {
> > +                err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> > +                goto transfer_done;
> > +            }
> > +        }
> > +        /* Otherwise we're doing a private transfer. */
> > +        if (dw_i3c_send_start(s, addr, is_recv, is_i2c)) {
> > +            err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
> > +            goto transfer_done;
> > +        }
> > +    }
> > +
> > +    if (is_recv) {
> > +        bytes_transferred = dw_i3c_rx(s, arg.data_len, is_i2c);
> > +    } else {
> > +        bytes_transferred = dw_i3c_tx(s, arg.data_len, is_i2c);
> > +    }
> > +
> > +    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> > +                     DW_I3C_TRANSFER_STATE_IDLE);
> > +
> > +transfer_done:
> > +    if (cmd.toc) {
> > +        dw_i3c_end_transfer(s, is_i2c);
> > +    }
> > +    if (cmd.roc) {
> > +        /*
> > +         * data_len is the number of bytes that still need to be TX'd, or
> the
> > +         * number of bytes RX'd.
> > +         */
> > +        uint16_t data_len = is_recv ? bytes_transferred : arg.data_len -
> > +
> bytes_transferred;
> > +        /* CCCT is always 0 in controller mode. */
> > +        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
> > +                                          data_len);
> > +    }
> > +
> > +    dw_i3c_update_irq(s);
> > +}
> > +
> > +static void dw_i3c_transfer_cmd(DWI3C *s, DWI3CTransferCmd cmd,
> > +                                DWI3CCmdQueueData arg) {
> > +    uint8_t arg_attr = FIELD_EX32(arg.word, COMMAND_QUEUE_PORT,
> > +CMD_ATTR);
> > +
> > +    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CMD_TID, cmd.tid);
> > +
> > +    /* User is trying to do HDR transfers, see if we can do them. */
> > +    if (cmd.speed == 0x06 && !dw_i3c_has_hdr_ddr(s)) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR DDR is not
> supported\n", path);
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE,
> CM_TFR_ST_STATUS,
> > +                         DW_I3C_TRANSFER_STATE_HALT);
> > +        return;
> > +    }
> > +    if (cmd.speed == 0x05 && !dw_i3c_has_hdr_ts(s)) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR TS is not
> supported\n", path);
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE,
> CM_TFR_ST_STATUS,
> > +                         DW_I3C_TRANSFER_STATE_HALT);
> > +        return;
> > +    }
> > +
> > +    if (arg_attr == DW_I3C_CMD_ATTR_TRANSFER_ARG) {
> > +        dw_i3c_transfer(s, cmd, arg.transfer_arg);
> > +    } else if (arg_attr == DW_I3C_CMD_ATTR_SHORT_DATA_ARG) {
> > +        dw_i3c_short_transfer(s, cmd, arg.short_arg);
> > +    } else {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Unknown command
> queue cmd_attr 0x%x"
> > +                      "\n", path, arg_attr);
> > +        ARRAY_FIELD_DP32(s->regs, PRESENT_STATE,
> CM_TFR_ST_STATUS,
> > +                         DW_I3C_TRANSFER_STATE_HALT);
> > +    }
> > +}
> > +
> > +static void dw_i3c_update_char_table(DWI3C *s, uint8_t offset, uint64_t
> pid,
> > +                                     uint8_t bcr, uint8_t dcr,
> > +uint8_t addr) {
> > +    if (offset > s->cfg.num_addressable_devices) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Device char table
> offset %d out of "
> > +                      "bounds\n", path, offset);
> > +        /* If we're out of bounds, do nothing. */
> > +        return;
> > +    }
> > +
> > +    /*
> > +     * Each device offset is 128 bits apart in the table, since each device
> gets
> > +     * 4 * 32-bits of entries in the table.
> > +     * / sizeof(uint32_t) because we're indexing into our 32-bit reg array.
> > +     */
> > +    uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs,
> DEV_CHAR_TABLE_POINTER,
> > +
> P_DEV_CHAR_TABLE_START_ADDR) /
> > +                                          sizeof(uint32_t)) +
> > +                                          (offset *
> sizeof(uint32_t));
> > +    s->regs[dev_index] = pid & 0xffffffff;
> > +    pid >>= 32;
> > +    s->regs[dev_index + 1] = FIELD_DP32(s->regs[dev_index + 1],
> > +
> DEVICE_CHARACTERISTIC_TABLE_LOC2,
> > +                                        MSB_PID, pid);
> > +    s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2],
> > +
> DEVICE_CHARACTERISTIC_TABLE_LOC3, DCR,
> > +                                        dcr);
> > +    s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2],
> > +
> DEVICE_CHARACTERISTIC_TABLE_LOC3, BCR,
> > +                                        bcr);
> > +    s->regs[dev_index + 3] = FIELD_DP32(s->regs[dev_index + 3],
> > +
> DEVICE_CHARACTERISTIC_TABLE_LOC4,
> > +                                        DEV_DYNAMIC_ADDR,
> addr);
> > +
> > +    /* Increment PRESENT_DEV_CHAR_TABLE_INDEX. */
> > +    uint8_t idx = ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
> > +                     PRESENT_DEV_CHAR_TABLE_INDEX);
> > +    /* Increment and rollover. */
> > +    idx++;
> > +    if (idx >= ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
> > +                               DEV_CHAR_TABLE_DEPTH) / 4) {
> > +        idx = 0;
> > +    }
> > +    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
> > +                     PRESENT_DEV_CHAR_TABLE_INDEX, idx); }
> > +
> > +static void dw_i3c_addr_assign_cmd(DWI3C *s, DWI3CAddrAssignCmd
> cmd)
> > +{
> > +    uint8_t i = 0;
> > +    uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
> > +
> > +    /* Tell everyone to ENTDAA. If these error, no one is on the bus. */
> > +    if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
> > +                                     /*is_i2c=*/false)) {
> > +        err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> > +        goto transfer_done;
> > +    }
> > +    if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) {
> > +        err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> > +        goto transfer_done;
> > +    }
> > +
> > +    /* Go through each device in the table and assign it an address. */
> > +    for (i = 0; i < cmd.dev_count; i++) {
> > +        uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index + i);
> > +        union {
> > +            uint64_t pid:48;
> > +            uint8_t bcr;
> > +            uint8_t dcr;
> > +            uint32_t w[2];
> > +            uint8_t b[8];
> > +        } target_info;
> > +
> > +        /* If this fails, there was no one left to ENTDAA. */
> > +        if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
> > +                                         /*is_i2c=*/false)) {
> > +            err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
> > +            break;
> > +        }
> > +
> > +        /*
> > +         * In ENTDAA, we read 8 bytes from the target, which will be the
> > +         * target's PID, BCR, and DCR. After that, we send it the dynamic
> > +         * address.
> > +         * Don't bother checking the number of bytes received, it must
> send 8
> > +         * bytes during ENTDAA.
> > +         */
> > +        uint32_t num_read;
> > +        if (dw_i3c_recv_data(s, /*is_i2c=*/false, target_info.b,
> > +                                        I3C_ENTDAA_SIZE,
> &num_read)) {
> > +            g_autofree char *path =
> object_get_canonical_path(OBJECT(s));
> > +            qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed
> ENTDAA CCC\n",
> > +                          path);
> > +            err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK;
> > +            goto transfer_done;
> > +        }
> > +        if (dw_i3c_send_byte(s, addr, /*is_i2c=*/false)) {
> > +            g_autofree char *path =
> object_get_canonical_path(OBJECT(s));
> > +            qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed
> addr 0x%.2x "
> > +                          "during ENTDAA\n", path, addr);
> > +            err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK;
> > +            break;
> > +        }
> > +        dw_i3c_update_char_table(s, cmd.dev_index + i,
> > +                                            target_info.pid,
> target_info.bcr,
> > +                                            target_info.dcr, addr);
> > +
> > +        /* Push the PID, BCR, and DCR to the RX queue. */
> > +        dw_i3c_push_rx(s, target_info.w[0]);
> > +        dw_i3c_push_rx(s, target_info.w[1]);
> > +    }
> > +
> > +transfer_done:
> > +    /* Do we send a STOP? */
> > +    if (cmd.toc) {
> > +        dw_i3c_end_transfer(s, /*is_i2c=*/false);
> > +    }
> > +    /*
> > +     * For addr assign commands, the length field is the number of
> devices
> > +     * left to assign. CCCT is always 0 in controller mode.
> > +     */
> > +    if (cmd.roc) {
> > +        dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
> > +                                         cmd.dev_count - i);
> > +    }
> > +}
> > +
> > +static uint32_t dw_i3c_cmd_queue_pop(DWI3C *s) {
> > +    if (fifo32_is_empty(&s->cmd_queue)) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to dequeue
> command queue "
> > +                      "when it was empty\n", path);
> > +        return 0;
> > +    }
> > +    uint32_t val = fifo32_pop(&s->cmd_queue);
> > +
> > +    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs,
> QUEUE_THLD_CTRL,
> > +
> CMD_BUF_EMPTY_THLD);
> > +    uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs,
> > +
> QUEUE_STATUS_LEVEL,
> > +
> CMD_QUEUE_EMPTY_LOC);
> > +    cmd_queue_empty_loc++;
> > +    ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL,
> CMD_QUEUE_EMPTY_LOC,
> > +                     cmd_queue_empty_loc);
> > +    if (cmd_queue_empty_loc >= empty_threshold) {
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 1);
> > +        dw_i3c_update_irq(s);
> > +    }
> > +
> > +    return val;
> > +}
> > +
> > +static void dw_i3c_cmd_queue_execute(DWI3C *s) {
> > +    ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
> > +                     DW_I3C_TRANSFER_STATE_IDLE);
> > +    if (!dw_i3c_can_transmit(s)) {
> > +        return;
> > +    }
> > +
> > +    /*
> > +     * We only start executing when a command is passed into the FIFO.
> > +     * We expect there to be a multiple of 2 items in the queue. The first
> item
> > +     * should be an argument to a command, and the command should be
> the second
> > +     * item.
> > +     */
> > +    if (fifo32_num_used(&s->cmd_queue) & 1) {
> > +        return;
> > +    }
> > +
> > +    while (!fifo32_is_empty(&s->cmd_queue)) {
> > +        DWI3CCmdQueueData arg;
> > +        arg.word = dw_i3c_cmd_queue_pop(s);
> > +        DWI3CCmdQueueData cmd;
> > +        cmd.word = dw_i3c_cmd_queue_pop(s);
> > +        trace_dw_i3c_cmd_queue_execute(s->cfg.id, cmd.word,
> > + arg.word);
> > +
> > +        uint8_t cmd_attr = FIELD_EX32(cmd.word,
> COMMAND_QUEUE_PORT, CMD_ATTR);
> > +        switch (cmd_attr) {
> > +        case DW_I3C_CMD_ATTR_TRANSFER_CMD:
> > +            dw_i3c_transfer_cmd(s, cmd.transfer_cmd, arg);
> > +            break;
> > +        case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD:
> > +            /* Arg is discarded for addr assign commands. */
> > +            dw_i3c_addr_assign_cmd(s, cmd.addr_assign_cmd);
> > +            break;
> > +        case DW_I3C_CMD_ATTR_TRANSFER_ARG:
> > +        case DW_I3C_CMD_ATTR_SHORT_DATA_ARG:
> > +            {
> > +                g_autofree char *path =
> object_get_canonical_path(OBJECT(s));
> > +                qemu_log_mask(LOG_GUEST_ERROR, "%s: Command
> queue received "
> > +                              "argument packet when it expected a
> command "
> > +                              "packet\n", path);
> > +            }
> > +            break;
> > +        default:
> > +            /*
> > +             * The caller's check before queueing an item should
> prevent this
> > +             * from happening.
> > +             */
> > +            g_assert_not_reached();
> > +            break;
> > +        }
> > +    }
> > +}
> > +
> > +static void dw_i3c_cmd_queue_push(DWI3C *s, uint32_t val) {
> > +    if (fifo32_is_full(&s->cmd_queue)) {
> > +        g_autofree char *path = object_get_canonical_path(OBJECT(s));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue
> received packet when "
> > +                      "already full\n", path);
> > +        return;
> > +    }
> > +    trace_dw_i3c_cmd_queue_push(s->cfg.id, val);
> > +    fifo32_push(&s->cmd_queue, val);
> > +
> > +    uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs,
> QUEUE_THLD_CTRL,
> > +
> CMD_BUF_EMPTY_THLD);
> > +    uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs,
> > +
> QUEUE_STATUS_LEVEL,
> > +
> CMD_QUEUE_EMPTY_LOC);
> > +    if (cmd_queue_empty_loc) {
> > +        cmd_queue_empty_loc--;
> > +        ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL,
> CMD_QUEUE_EMPTY_LOC,
> > +                         cmd_queue_empty_loc);
> > +    }
> > +    if (cmd_queue_empty_loc < empty_threshold) {
> > +        ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 0);
> > +        dw_i3c_update_irq(s);
> > +    }
> > +}
> > +
> > +static void dw_i3c_cmd_queue_port_w(DWI3C *s, uint32_t val) {
> > +    uint8_t cmd_attr = FIELD_EX32(val, COMMAND_QUEUE_PORT,
> CMD_ATTR);
> > +
> > +    switch (cmd_attr) {
> > +    /* If a command is received we can start executing it. */
> > +    case DW_I3C_CMD_ATTR_TRANSFER_CMD:
> > +    case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD:
> > +        dw_i3c_cmd_queue_push(s, val);
> > +        dw_i3c_cmd_queue_execute(s);
> > +        break;
> > +    /* If we get an argument just push it. */
> > +    case DW_I3C_CMD_ATTR_TRANSFER_ARG:
> > +    case DW_I3C_CMD_ATTR_SHORT_DATA_ARG:
> > +        dw_i3c_cmd_queue_push(s, val);
> > +        break;
> > +    default:
> > +        {
> > +            g_autofree char *path =
> object_get_canonical_path(OBJECT(s));
> > +            qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue
> received packet "
> > +                          "with unknown cmd attr 0x%x\n", path,
> cmd_attr);
> > +        }
> > +        break;
> > +    }
> > +}
> > +
> >   static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
> >                            unsigned size)
> >   {
> > @@ -409,7 +1245,7 @@ static void dw_i3c_write(void *opaque, hwaddr
> offset, uint64_t value,
> >       uint32_t addr = offset >> 2;
> >       uint32_t val32 = (uint32_t)value;
> >
> > -    trace_dw_i3c_write(s->id, offset, value);
> > +    trace_dw_i3c_write(s->cfg.id, offset, value);
> >
> >       val32 &= ~dw_i3c_ro[addr];
> >       switch (addr) {
> > @@ -433,6 +1269,10 @@ static void dw_i3c_write(void *opaque, hwaddr
> offset, uint64_t value,
> >                         __func__, offset, value);
> >           break;
> >       case R_RX_TX_DATA_PORT:
> > +        dw_i3c_push_tx(s, val32);
> > +        break;
> > +    case R_COMMAND_QUEUE_PORT:
> > +        dw_i3c_cmd_queue_port_w(s, val32);
> >           break;
> >       case R_RESET_CTRL:
> >           break;
> > @@ -475,22 +1315,56 @@ static void dw_i3c_reset_enter(Object *obj,
> ResetType type)
> >       DWI3C *s = DW_I3C(obj);
> >
> >       memcpy(s->regs, dw_i3c_resets, sizeof(s->regs));
> > +    /*
> > +     * The user config for these may differ from our resets array, set them
> > +     * manually.
> > +     */
> > +    ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, ADDR,
> > +                     s->cfg.dev_addr_table_pointer);
> > +    ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, DEPTH,
> > +                     s->cfg.dev_addr_table_depth);
> > +    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
> > +                     P_DEV_CHAR_TABLE_START_ADDR,
> > +                     s->cfg.dev_char_table_pointer);
> > +    ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
> DEV_CHAR_TABLE_DEPTH,
> > +                     s->cfg.dev_char_table_depth);
> >   }
> >
> >   static void dw_i3c_realize(DeviceState *dev, Error **errp)
> >   {
> >       DWI3C *s = DW_I3C(dev);
> > -    g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->id);
> > +    g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d",
> > + s->cfg.id);
> >
> >       sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
> >
> >       memory_region_init_io(&s->mr, OBJECT(s), &dw_i3c_ops, s, name,
> >                             DW_I3C_NR_REGS << 2);
> >       sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr);
> > +
> > +    fifo32_create(&s->cmd_queue,
> s->cfg.cmd_resp_queue_capacity_bytes);
> > +    fifo32_create(&s->resp_queue,
> s->cfg.cmd_resp_queue_capacity_bytes);
> > +    fifo32_create(&s->tx_queue, s->cfg.tx_rx_queue_capacity_bytes);
> > +    fifo32_create(&s->rx_queue, s->cfg.tx_rx_queue_capacity_bytes);
> > +
> > +    s->bus = i3c_init_bus(DEVICE(s), name);
> >   }
> >
> >   static const Property dw_i3c_properties[] = {
> > -    DEFINE_PROP_UINT8("device-id", DWI3C, id, 0),
> > +    DEFINE_PROP_UINT8("device-id", DWI3C, cfg.id, 0),
> > +    DEFINE_PROP_UINT8("command-response-queue-capacity-bytes",
> DWI3C,
> > +                      cfg.cmd_resp_queue_capacity_bytes, 0x10),
> > +    DEFINE_PROP_UINT16("tx-rx-queue-capacity-bytes", DWI3C,
> > +                      cfg.tx_rx_queue_capacity_bytes, 0x40),
> > +    DEFINE_PROP_UINT8("num-addressable-devices", DWI3C,
> > +                      cfg.num_addressable_devices, 8),
> > +    DEFINE_PROP_UINT16("dev-addr-table-pointer", DWI3C,
> > +                       cfg.dev_addr_table_pointer, 0x280),
> > +    DEFINE_PROP_UINT16("dev-addr-table-depth", DWI3C,
> > +                       cfg.dev_addr_table_depth, 0x08),
> > +    DEFINE_PROP_UINT16("dev-char-table-pointer", DWI3C,
> > +                       cfg.dev_char_table_pointer, 0x200),
> > +    DEFINE_PROP_UINT16("dev-char-table-depth", DWI3C,
> > +                       cfg.dev_char_table_depth, 0x20),
> >   };
> >
> >   static void dw_i3c_class_init(ObjectClass *klass, const void *data)
> > diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events index
> > 2d944387db..044ff06a01 100644
> > --- a/hw/i3c/trace-events
> > +++ b/hw/i3c/trace-events
> > @@ -7,6 +7,16 @@ aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C
> write: offset 0x%" PRIx64
> >   # dw-i3c,c
> >   dw_i3c_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C
> Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
> >   dw_i3c_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C
> > Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
> > +dw_i3c_send(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] send %"
> PRId32 " bytes to bus"
> > +dw_i3c_recv_data(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u]
> recv %" PRId32 " bytes from bus"
> > +dw_i3c_pop_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%"
> PRIx32 " from RX FIFO"
> > +dw_i3c_resp_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u]
> push 0x%" PRIx32 " to response queue"
> > +dw_i3c_push_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%"
> PRIx32 " to TX FIFO"
> > +dw_i3c_pop_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%"
> PRIx32 " from TX FIFO"
> > +dw_i3c_push_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%"
> PRIx32 " to RX FIFO"
> > +dw_i3c_transfer_ccc(uint32_t deviceid, uint8_t ccc) "I3C Dev[%u]
> > +transfer CCC 0x%" PRIx8 dw_i3c_cmd_queue_execute(uint32_t deviceid,
> > +uint32_t cmd, uint32_t arg) "I3C Dev[%u] execute command 0x%" PRIx32 "
> arg 0x%" PRIx32 dw_i3c_cmd_queue_push(uint32_t deviceid, uint32_t data)
> "I3C Dev[%u] push 0x%" PRIx32 " to cmd queue"
> >
> >   # core.c
> >   i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%"
> > PRIx8 " event 0x%" PRIx8


^ permalink raw reply	[flat|nested] 47+ messages in thread

* RE: [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-27  9:58   ` Cédric Le Goater
@ 2026-03-02  3:34     ` Jamin Lin
  0 siblings, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-03-02  3:34 UTC (permalink / raw)
  To: Cédric Le Goater, Paolo Bonzini, Peter Maydell, Steven Lee,
	Troy Lee, Andrew Jeffery, Joel Stanley, Marc-André Lureau,
	Daniel P. Berrangé, Philippe Mathieu-Daudé,
	open list:All patches CC here, open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Titus Rwantare, Patrick Venture

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"


^ permalink raw reply	[flat|nested] 47+ messages in thread

* RE: [PATCH v7 18/22] hw/i3c: Add Mock target
  2026-02-27  1:43   ` Jithu Joseph
  2026-02-27  7:52     ` Cédric Le Goater
@ 2026-03-02  3:35     ` Jamin Lin
  1 sibling, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-03-02  3:35 UTC (permalink / raw)
  To: Jithu Joseph, Paolo Bonzini, Peter Maydell, Cédric Le Goater,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Titus Rwantare, Patrick Venture

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


^ permalink raw reply	[flat|nested] 47+ messages in thread

* RE: [PATCH v7 03/22] hw/i3c: Add bus support
  2026-02-27  2:23   ` Jithu Joseph
  2026-02-27  7:51     ` Cédric Le Goater
@ 2026-03-02  3:36     ` Jamin Lin
  1 sibling, 0 replies; 47+ messages in thread
From: Jamin Lin @ 2026-03-02  3:36 UTC (permalink / raw)
  To: Jithu Joseph, Paolo Bonzini, Peter Maydell, Cédric Le Goater,
	Steven Lee, Troy Lee, Andrew Jeffery, Joel Stanley,
	Marc-André Lureau, Daniel P. Berrangé,
	Philippe Mathieu-Daudé, open list:All patches CC here,
	open list:ARM TCG CPUs
  Cc: Troy Lee, Kane Chen, nabihestefan@google.com, komlodi@google.com,
	Patrick Venture, Titus Rwantare

Hi Jithu Joseph

> Subject: Re: [PATCH v7 03/22] hw/i3c: Add bus support
> 
> 
> Another minor comment which too can be addressed later
> 
> On 2/24/2026 6:12 PM, Jamin Lin wrote:
> > Adds an I3C bus and a target class.
> > The bus supports:
> > - I3C data transmission and reception
> > - CCCs (including ENTDAA)
> > - IBIs
> > - legacy I2C transactions
> >
> > General usage of the bus is similar to I2C. Users are expected to
> > initialize a bus via i3c_init_bus, and use the bus returned from the
> > init function to do transactions on the bus.
> >
> > In order to handle IBIs, the controller provides callbacks to handle
> > receiving an IBI from a target, receiving (optional) additional IBI
> > bytes from a target, and handling when a target is done with its IBI.
> >
> > Similarly, target creation is done via i3c_target_create_simple and
> > users use the provided I3CTarget to handle transactions.
> > The target has functions provided that it can use to invoke an IBI and
> > send additional bytes.
> >
> > Along with the send, recv, and event callbacks that are expected of an
> > I3C target, which are similar to I2C, there is a separate callback for
> > CCC handling.
> > This is to help encapsulate CCC handling and keep it separate from
> > target-specific read/write functionality.
> >
> > To avoid repition for required CCCs among I3C targets, there is some
> > class-level CCC handling added. The CCC is then passed to the target
> > in case it needs to handle it in some way.
> >
> > Signed-off-by: Joe Komlodi <komlodi@google.com>
> > Reviewed-by: Patrick Venture <venture@google.com>
> > Reviewed-by: Titus Rwantare <titusr@google.com>
> > Reviewed-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> > ---
> >  include/hw/i3c/i3c.h | 277 ++++++++++++++++++
> >  hw/i3c/core.c        | 647
> +++++++++++++++++++++++++++++++++++++++++++
> >  hw/i3c/meson.build   |   1 +
> >  hw/i3c/trace-events  |  16 ++
> >  4 files changed, 941 insertions(+)
> >  create mode 100644 include/hw/i3c/i3c.h  create mode 100644
> > hw/i3c/core.c
> >
> 
> ...
> 
> > +
> > +int i3c_send_byte(I3CBus *bus, uint8_t data) {
> > +    /*
> > +     * Ignored, the caller can determine how many were sent based on if
> this was
> > +     * ACKed/NACKed.
> > +     */
> > +    uint32_t num_sent;
> 
> num_sent is uninitialized here. Even though i3c_send_byte ignores it after the
> call, it gets passed by pointer into i3c_send() where it is used in the
> trace_i3c_send() call.
> If the send callback does not write *num_sent (as is the case with
> mock_i3c_target_tx, refer my prev comment for patch 18/22), the trace might
> show garbage. Initializing it to zero might reduce the confusion
> 

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

> > +    return i3c_send(bus, &data, 1, &num_sent); }
> > +
> > +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
> > +             uint32_t *num_sent)
> > +{
> > +    I3CTargetClass *tc;
> > +    I3CTarget *t;
> > +    I3CNode *node;
> > +    int ret = 0;
> > +
> > +    /* If this message is a broadcast and no CCC has been found, grab it. */
> > +    if (bus->broadcast && !bus->in_ccc) {
> > +        bus->ccc = *data;
> > +        bus->in_ccc = true;
> > +        /*
> > +         * We need to keep track if we're currently in ENTDAA.
> > +         * On any other CCC, the CCC is over on a RESTART or STOP, but
> ENTDAA
> > +         * is only over on a STOP.
> > +         */
> > +        if (bus->ccc == I3C_CCC_ENTDAA) {
> > +            bus->in_entdaa = true;
> > +        }
> > +    }
> > +
> > +    QLIST_FOREACH(node, &bus->current_devs, next) {
> > +        t = node->target;
> > +        tc = I3C_TARGET_GET_CLASS(t);
> > +        if (bus->in_ccc) {
> > +            if (!tc->handle_ccc_write) {
> > +                ret = -1;
> > +                continue;
> > +            }
> > +            ret = i3c_target_handle_ccc_write(t, data, num_to_send,
> num_sent);
> > +            /* Targets should only NACK on a direct CCC. */
> > +            if (ret && !CCC_IS_DIRECT(bus->ccc)) {
> > +                ret = 0;
> > +            }
> > +        } else {
> > +            if (tc->send) {
> > +                ret = ret || tc->send(t, data, num_to_send, num_sent);
> > +            } else {
> > +                ret = -1;
> > +            }
> > +        }
> > +    }
> > +
> > +    trace_i3c_send(*num_sent, num_to_send, ret == 0);
> 
> This trace might print garbage if the target send callback doesnt set it
> 
> > +
> > +    return ret ? -1 : 0;
> > +}
> > +
> 
> Thanks
> Jithu

^ permalink raw reply	[flat|nested] 47+ messages in thread

end of thread, other threads:[~2026-03-02  3:36 UTC | newest]

Thread overview: 47+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-25  2:11 [PATCH v7 00/22] i3c: aspeed: Add I3C support Jamin Lin
2026-02-25  2:12 ` [PATCH v7 01/22] hw/misc/aspeed_i3c: Move to i3c directory Jamin Lin
2026-02-25  2:12 ` [PATCH v7 02/22] hw/i3c/aspeed_i3c: Switch to DEFINE_TYPES() and align parent_obj naming Jamin Lin
2026-02-25 10:47   ` Cédric Le Goater
2026-02-25  2:12 ` [PATCH v7 03/22] hw/i3c: Add bus support Jamin Lin
2026-02-27  2:23   ` Jithu Joseph
2026-02-27  7:51     ` Cédric Le Goater
2026-02-27 19:58       ` Jithu Joseph
2026-03-02  3:36     ` Jamin Lin
2026-02-27  9:47   ` Cédric Le Goater
2026-03-02  1:31     ` Jamin Lin
2026-02-27 20:27   ` Jithu Joseph
2026-02-25  2:12 ` [PATCH v7 04/22] hw/i3c: Split DesignWare I3C out of Aspeed I3C Jamin Lin
2026-02-25  2:12 ` [PATCH v7 05/22] hw/i3c/dw-i3c: Add more register fields Jamin Lin
2026-02-25  2:12 ` [PATCH v7 06/22] hw/i3c/aspeed_i3c: " Jamin Lin
2026-02-25  2:12 ` [PATCH v7 07/22] hw/i3c/dw-i3c: Add more reset values Jamin Lin
2026-02-25  2:12 ` [PATCH v7 08/22] hw/i3c/aspeed_i3c: Add register RO field masks Jamin Lin
2026-02-25  2:12 ` [PATCH v7 09/22] hw/i3c/dw-i3c: " Jamin Lin
2026-02-25  2:12 ` [PATCH v7 10/22] hw/i3c/dw-i3c: Treat more registers as read-as-zero Jamin Lin
2026-02-25  2:12 ` [PATCH v7 11/22] hw/i3c/dw-i3c: Use 32 bits on MMIO writes Jamin Lin
2026-02-25  2:12 ` [PATCH v7 12/22] hw/i3c/dw-i3c: Add IRQ MMIO behavior Jamin Lin
2026-02-25  2:12 ` [PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX Jamin Lin
2026-02-27  9:56   ` Cédric Le Goater
2026-03-02  3:33     ` Jamin Lin
2026-02-25  2:12 ` [PATCH v7 14/22] hw/i3c/dw-i3c: Add IBI handling Jamin Lin
2026-02-25  2:12 ` [PATCH v7 15/22] hw/i3c/dw-i3c: Add ctrl MMIO handling Jamin Lin
2026-02-25  2:12 ` [PATCH v7 16/22] hw/i3c/dw-i3c: Add controller resets Jamin Lin
2026-02-25  2:12 ` [PATCH v7 17/22] hw/i3c/aspeed: Add I3C bus get function Jamin Lin
2026-02-25  2:12 ` [PATCH v7 18/22] hw/i3c: Add Mock target Jamin Lin
2026-02-27  1:43   ` Jithu Joseph
2026-02-27  7:52     ` Cédric Le Goater
2026-02-27 19:49       ` Jithu Joseph
2026-02-28  6:30         ` Cédric Le Goater
2026-03-02  3:35     ` Jamin Lin
2026-02-27  9:58   ` Cédric Le Goater
2026-03-02  3:34     ` Jamin Lin
2026-02-27 20:28   ` Jithu Joseph
2026-02-25  2:12 ` [PATCH v7 19/22] hw/arm/aspeed: Build with I3C_DEVICES Jamin Lin
2026-02-25  2:12 ` [PATCH v7 20/22] hw/i3c: Add hotplug support Jamin Lin
2026-02-25  2:12 ` [PATCH v7 21/22] tests/functional/arm/test_aspeed_ast2600_sdk: Add i3c functional test Jamin Lin
2026-02-25 10:47   ` Cédric Le Goater
2026-02-25  2:12 ` [PATCH v7 22/22] MAINTAINERS: Add I3C maintainers and reviewer Jamin Lin
2026-02-27  2:33 ` [PATCH v7 00/22] i3c: aspeed: Add I3C support Jithu Joseph
2026-02-27  7:53   ` Cédric Le Goater
2026-02-27 20:03     ` Jithu Joseph
2026-02-28  6:32       ` Cédric Le Goater
2026-02-27  8:10 ` Cédric Le Goater

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox