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
+