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 <[email protected]> > > Reviewed-by: Titus Rwantare <[email protected]> > > Reviewed-by: Patrick Venture <[email protected]> > > Reviewed-by: Jamin Lin <[email protected]> > > Signed-off-by: Jamin Lin <[email protected]> > > --- > > 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"
