@samitouri / QOSamiQemu / commits / 05158fadb3

hw/i3c: Add Mock target

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> Reviewed-by: Jithu Joseph <jithu.joseph@oss.qualcomm.com> Tested-by: Jithu Joseph <jithu.joseph@oss.qualcomm.com> Link: https://lore.kernel.org/qemu-devel/20260225021158.1586584-19-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater <clg@redhat.com>

Jamin Lin committed Feb 25, 2026 at 02:12 UTC 05158fadb3dfc69b43b12f8a86e44cd6ecc7afa7
5 files changed +371
hw/i3c/Kconfig
+10
@@ -3,3 +3,13 @@ config I3C
3
4 config DW_I3C
5 bool
6 +
7 +config I3C_DEVICES
8 + # Device group for i3c devices which can reasonably be user-plugged to any
9 + # board's i3c bus.
10 + bool
11 +
12 +config MOCK_I3C_TARGET
13 + bool
14 + select I3C
15 + default y if I3C_DEVICES
hw/i3c/meson.build
+1
@@ -2,4 +2,5 @@ i3c_ss = ss.source_set()
2 i3c_ss.add(when: 'CONFIG_I3C', if_true: files('core.c'))
3 i3c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i3c.c'))
4 i3c_ss.add(when: 'CONFIG_DW_I3C', if_true: files('dw-i3c.c'))
5 +i3c_ss.add(when: 'CONFIG_MOCK_I3C_TARGET', if_true: files('mock-i3c-target.c'))
6 system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss)
hw/i3c/mock-i3c-target.c new
+298
@@ -0,0 +1,298 @@
1 +/*
2 + * Mock I3C Device
3 + *
4 + * Copyright (c) 2025 Google LLC
5 + *
6 + * The mock I3C device can be thought of as a simple EEPROM. It has a buffer,
7 + * and the pointer in the buffer is reset to 0 on an I3C STOP.
8 + * To write to the buffer, issue a private write and send data.
9 + * To read from the buffer, issue a private read.
10 + *
11 + * The mock target also supports sending target interrupt IBIs.
12 + * To issue an IBI, set the 'ibi-magic-num' property to a non-zero number, and
13 + * send that number in a private transaction. The mock target will issue an IBI
14 + * after 1 second.
15 + *
16 + * It also supports a handful of CCCs that are typically used when probing I3C
17 + * devices.
18 + *
19 + * SPDX-License-Identifier: GPL-2.0-or-later
20 + */
21 +
22 +#include "qemu/osdep.h"
23 +#include "qemu/log.h"
24 +#include "trace.h"
25 +#include "hw/i3c/i3c.h"
26 +#include "hw/i3c/mock-i3c-target.h"
27 +#include "hw/core/irq.h"
28 +#include "hw/core/qdev-properties.h"
29 +#include "qapi/error.h"
30 +#include "qemu/module.h"
31 +
32 +#define IBI_DELAY_NS (1 * 1000 * 1000)
33 +
34 +static uint32_t mock_i3c_target_rx(I3CTarget *i3c, uint8_t *data,
35 + uint32_t num_to_read)
36 +{
37 + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
38 + uint32_t i;
39 +
40 + /* Bounds check. */
41 + if (s->p_buf == s->cfg.buf_size) {
42 + return 0;
43 + }
44 +
45 + for (i = 0; i < num_to_read; i++) {
46 + data[i] = s->buf[s->p_buf];
47 + trace_mock_i3c_target_rx(data[i]);
48 + s->p_buf++;
49 + if (s->p_buf == s->cfg.buf_size) {
50 + break;
51 + }
52 + }
53 +
54 + /* Return the number of bytes we're sending to the controller. */
55 + return i;
56 +}
57 +
58 +static void mock_i3c_target_ibi_timer_start(MockI3cTargetState *s)
59 +{
60 + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
61 + timer_mod(&s->qtimer, now + IBI_DELAY_NS);
62 +}
63 +
64 +static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
65 + uint32_t num_to_send, uint32_t *num_sent)
66 +{
67 + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
68 + int ret;
69 + uint32_t to_write;
70 +
71 + if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) {
72 + mock_i3c_target_ibi_timer_start(s);
73 + return 0;
74 + }
75 +
76 + /* Bounds check. */
77 + if (num_to_send + s->p_buf > s->cfg.buf_size) {
78 + to_write = s->cfg.buf_size - s->p_buf;
79 + ret = -1;
80 + } else {
81 + to_write = num_to_send;
82 + ret = 0;
83 + }
84 + for (uint32_t i = 0; i < to_write; i++) {
85 + trace_mock_i3c_target_tx(data[i]);
86 + s->buf[s->p_buf] = data[i];
87 + s->p_buf++;
88 + }
89 + return ret;
90 +}
91 +
92 +static int mock_i3c_target_event(I3CTarget *i3c, enum I3CEvent event)
93 +{
94 + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
95 +
96 + trace_mock_i3c_target_event(event);
97 + if (event == I3C_STOP) {
98 + s->in_ccc = false;
99 + s->curr_ccc = 0;
100 + s->ccc_byte_offset = 0;
101 + s->p_buf = 0;
102 + }
103 +
104 + return 0;
105 +}
106 +
107 +static int mock_i3c_target_handle_ccc_read(I3CTarget *i3c, uint8_t *data,
108 + uint32_t num_to_read,
109 + uint32_t *num_read)
110 +{
111 + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
112 +
113 + switch (s->curr_ccc) {
114 + case I3C_CCCD_GETMXDS:
115 + /* Default data rate for I3C. */
116 + while (s->ccc_byte_offset < num_to_read) {
117 + if (s->ccc_byte_offset >= 2) {
118 + break;
119 + }
120 + data[s->ccc_byte_offset] = 0;
121 + *num_read = s->ccc_byte_offset;
122 + s->ccc_byte_offset++;
123 + }
124 + break;
125 + case I3C_CCCD_GETCAPS:
126 + /* Support I3C version 1.1.x, no other features. */
127 + while (s->ccc_byte_offset < num_to_read) {
128 + if (s->ccc_byte_offset >= 2) {
129 + break;
130 + }
131 + if (s->ccc_byte_offset == 0) {
132 + data[s->ccc_byte_offset] = 0;
133 + } else {
134 + data[s->ccc_byte_offset] = 0x01;
135 + }
136 + *num_read = s->ccc_byte_offset;
137 + s->ccc_byte_offset++;
138 + }
139 + break;
140 + case I3C_CCCD_GETMWL:
141 + case I3C_CCCD_GETMRL:
142 + /* MWL/MRL is MSB first. */
143 + while (s->ccc_byte_offset < num_to_read) {
144 + if (s->ccc_byte_offset >= 2) {
145 + break;
146 + }
147 + data[s->ccc_byte_offset] = (s->cfg.buf_size &
148 + (0xff00 >> (s->ccc_byte_offset * 8))) >>
149 + (8 - (s->ccc_byte_offset * 8));
150 + s->ccc_byte_offset++;
151 + *num_read = num_to_read;
152 + }
153 + break;
154 + case I3C_CCC_ENTDAA:
155 + case I3C_CCCD_GETPID:
156 + case I3C_CCCD_GETBCR:
157 + case I3C_CCCD_GETDCR:
158 + /* Nothing to do. */
159 + break;
160 + default:
161 + qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc);
162 + return -1;
163 + }
164 +
165 + trace_mock_i3c_target_handle_ccc_read(*num_read, num_to_read);
166 + return 0;
167 +}
168 +
169 +static int mock_i3c_target_handle_ccc_write(I3CTarget *i3c, const uint8_t *data,
170 + uint32_t num_to_send,
171 + uint32_t *num_sent)
172 +{
173 + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
174 +
175 + if (!s->curr_ccc) {
176 + s->in_ccc = true;
177 + s->curr_ccc = *data;
178 + trace_mock_i3c_target_new_ccc(s->curr_ccc);
179 + }
180 +
181 + *num_sent = 1;
182 + switch (s->curr_ccc) {
183 + case I3C_CCC_ENEC:
184 + case I3C_CCCD_ENEC:
185 + s->can_ibi = true;
186 + break;
187 + case I3C_CCC_DISEC:
188 + case I3C_CCCD_DISEC:
189 + s->can_ibi = false;
190 + break;
191 + case I3C_CCC_ENTDAA:
192 + case I3C_CCC_SETAASA:
193 + case I3C_CCC_RSTDAA:
194 + case I3C_CCCD_SETDASA:
195 + case I3C_CCCD_GETPID:
196 + case I3C_CCCD_GETBCR:
197 + case I3C_CCCD_GETDCR:
198 + case I3C_CCCD_GETMWL:
199 + case I3C_CCCD_GETMRL:
200 + case I3C_CCCD_GETMXDS:
201 + case I3C_CCCD_GETCAPS:
202 + /* Nothing to do. */
203 + break;
204 + default:
205 + qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc);
206 + return -1;
207 + }
208 +
209 + trace_mock_i3c_target_handle_ccc_write(*num_sent, num_to_send);
210 + return 0;
211 +}
212 +
213 +static void mock_i3c_target_do_ibi(MockI3cTargetState *s)
214 +{
215 + if (!s->can_ibi) {
216 + return;
217 + }
218 +
219 + trace_mock_i3c_target_do_ibi(s->parent_obj.address, true);
220 + int nack = i3c_target_send_ibi(&s->parent_obj, s->parent_obj.address,
221 + /*is_recv=*/true);
222 + /* Getting NACKed isn't necessarily an error, just print it out. */
223 + if (nack) {
224 + trace_mock_i3c_target_do_ibi_nack("sending");
225 + }
226 + nack = i3c_target_ibi_finish(&s->parent_obj, 0x00);
227 + if (nack) {
228 + trace_mock_i3c_target_do_ibi_nack("finishing");
229 + }
230 +}
231 +
232 +static void mock_i3c_target_timer_elapsed(void *opaque)
233 +{
234 + MockI3cTargetState *s = MOCK_I3C_TARGET(opaque);
235 + timer_del(&s->qtimer);
236 + mock_i3c_target_do_ibi(s);
237 +}
238 +
239 +static void mock_i3c_target_reset(I3CTarget *i3c)
240 +{
241 + MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
242 + s->can_ibi = false;
243 +}
244 +
245 +static void mock_i3c_target_realize(DeviceState *dev, Error **errp)
246 +{
247 + MockI3cTargetState *s = MOCK_I3C_TARGET(dev);
248 + s->buf = g_new0(uint8_t, s->cfg.buf_size);
249 + mock_i3c_target_reset(&s->parent_obj);
250 +}
251 +
252 +static void mock_i3c_target_init(Object *obj)
253 +{
254 + MockI3cTargetState *s = MOCK_I3C_TARGET(obj);
255 + s->can_ibi = false;
256 +
257 + /* For IBIs. */
258 + timer_init_ns(&s->qtimer, QEMU_CLOCK_VIRTUAL, mock_i3c_target_timer_elapsed,
259 + s);
260 +}
261 +
262 +static const Property remote_i3c_props[] = {
263 + /* The size of the internal buffer. */
264 + DEFINE_PROP_UINT32("buf-size", MockI3cTargetState, cfg.buf_size, 0x100),
265 + /*
266 + * If the mock target receives this number, it will issue an IBI after
267 + * 1 second. Disabled if the IBI magic number is 0.
268 + */
269 + DEFINE_PROP_UINT8("ibi-magic-num", MockI3cTargetState, cfg.ibi_magic, 0x00),
270 +};
271 +
272 +static void mock_i3c_target_class_init(ObjectClass *klass, const void *data)
273 +{
274 + DeviceClass *dc = DEVICE_CLASS(klass);
275 + I3CTargetClass *k = I3C_TARGET_CLASS(klass);
276 +
277 + dc->realize = mock_i3c_target_realize;
278 + k->event = mock_i3c_target_event;
279 + k->recv = mock_i3c_target_rx;
280 + k->send = mock_i3c_target_tx;
281 + k->handle_ccc_read = mock_i3c_target_handle_ccc_read;
282 + k->handle_ccc_write = mock_i3c_target_handle_ccc_write;
283 +
284 + device_class_set_props(dc, remote_i3c_props);
285 +}
286 +
287 +static const TypeInfo mock_i3c_target_types[] = {
288 + {
289 + .name = TYPE_MOCK_I3C_TARGET,
290 + .parent = TYPE_I3C_TARGET,
291 + .instance_size = sizeof(MockI3cTargetState),
292 + .instance_init = mock_i3c_target_init,
293 + .class_init = mock_i3c_target_class_init,
294 + },
295 +};
296 +
297 +DEFINE_TYPES(mock_i3c_target_types)
298 +
hw/i3c/trace-events
+10
@@ -36,3 +36,13 @@ legacy_i2c_recv(uint8_t byte) "Legacy I2C recv 0x%" PRIx8
36 legacy_i2c_send(uint8_t byte) "Legacy I2C send 0x%" PRIx8
37 legacy_i2c_start_transfer(uint8_t address, bool is_recv) "Legacy I2C START with address 0x%" PRIx8 " is_recv=%d"
38 legacy_i2c_end_transfer(void) "Legacy I2C STOP"
39 +
40 +# mock-target.c
41 +mock_i3c_target_rx(uint8_t byte) "I3C mock target read 0x%" PRIx8
42 +mock_i3c_target_tx(uint8_t byte) "I3C mock target write 0x%" PRIx8
43 +mock_i3c_target_event(uint8_t event) "I3C mock target event 0x%" PRIx8
44 +mock_i3c_target_handle_ccc_read(uint32_t num_read, uint32_t num_to_read) "I3C mock target read %" PRId32 "/%" PRId32 " bytes"
45 +mock_i3c_target_new_ccc(uint8_t ccc) "I3C mock target handle CCC 0x%" PRIx8
46 +mock_i3c_target_handle_ccc_write(uint32_t num_sent, uint32_t num_to_send) "I3C mock target send %" PRId32 "/%" PRId32 " bytes"
47 +mock_i3c_target_do_ibi(uint8_t address, bool is_recv) "I3C mock target IBI with address 0x%" PRIx8 " RnW=%d"
48 +mock_i3c_target_do_ibi_nack(const char *reason) "NACKed from controller when %s target interrupt"
include/hw/i3c/mock-i3c-target.h new
+52
@@ -0,0 +1,52 @@
1 +#ifndef MOCK_I3C_TARGET_H_
2 +#define MOCK_I3C_TARGET_H_
3 +
4 +/*
5 + * Mock I3C Device
6 + *
7 + * Copyright (c) 2025 Google LLC
8 + *
9 + * The mock I3C device can be thought of as a simple EEPROM. It has a buffer,
10 + * and the pointer in the buffer is reset to 0 on an I3C STOP.
11 + * To write to the buffer, issue a private write and send data.
12 + * To read from the buffer, issue a private read.
13 + *
14 + * The mock target also supports sending target interrupt IBIs.
15 + * To issue an IBI, set the 'ibi-magic-num' property to a non-zero number, and
16 + * send that number in a private transaction. The mock target will issue an IBI
17 + * after 1 second.
18 + *
19 + * It also supports a handful of CCCs that are typically used when probing I3C
20 + * devices.
21 + *
22 + * SPDX-License-Identifier: GPL-2.0-or-later
23 + */
24 +
25 +#include "qemu/osdep.h"
26 +#include "qemu/timer.h"
27 +#include "hw/i3c/i3c.h"
28 +
29 +#define TYPE_MOCK_I3C_TARGET "mock-i3c-target"
30 +OBJECT_DECLARE_SIMPLE_TYPE(MockI3cTargetState, MOCK_I3C_TARGET)
31 +
32 +struct MockI3cTargetState {
33 + I3CTarget parent_obj;
34 +
35 + /* General device state */
36 + bool can_ibi;
37 + QEMUTimer qtimer;
38 + size_t p_buf;
39 + uint8_t *buf;
40 +
41 + /* For Handing CCCs. */
42 + bool in_ccc;
43 + I3CCCC curr_ccc;
44 + uint8_t ccc_byte_offset;
45 +
46 + struct {
47 + uint32_t buf_size;
48 + uint8_t ibi_magic;
49 + } cfg;
50 +};
51 +
52 +#endif