xref: /openbmc/qemu/hw/i3c/mock-i3c-target.c (revision 1be2934580e149a42e40ebf909d6ba75272df5c2)
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/irq.h"
28 #include "hw/qdev-properties.h"
29 #include "qapi/error.h"
30 #include "qemu/module.h"
31 
32 #ifndef MOCK_I3C_TARGET_DEBUG
33 #define MOCK_I3C_TARGET_DEBUG 0
34 #endif
35 
36 #define DB_PRINTF(...) do { \
37         if (MOCK_I3C_TARGET_DEBUG) { \
38             qemu_log("%s: ", __func__); \
39             qemu_log(__VA_ARGS__); \
40         } \
41     } while (0)
42 
43 #define IBI_DELAY_NS (1 * 1000 * 1000)
44 
mock_i3c_target_rx(I3CTarget * i3c,uint8_t * data,uint32_t num_to_read)45 static uint32_t mock_i3c_target_rx(I3CTarget *i3c, uint8_t *data,
46                                    uint32_t num_to_read)
47 {
48     MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
49     uint32_t i;
50 
51     /* Bounds check. */
52     if (s->p_buf == s->cfg.buf_size) {
53         return 0;
54     }
55 
56     for (i = 0; i < num_to_read; i++) {
57         data[i] = s->buf[s->p_buf];
58         trace_mock_i3c_target_rx(data[i]);
59         s->p_buf++;
60         if (s->p_buf == s->cfg.buf_size) {
61             break;
62         }
63     }
64 
65     /* Return the number of bytes we're sending to the controller. */
66     return i;
67 }
68 
mock_i3c_target_ibi_timer_start(MockI3cTargetState * s)69 static void mock_i3c_target_ibi_timer_start(MockI3cTargetState *s)
70 {
71     int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
72     timer_mod(&s->qtimer, now + IBI_DELAY_NS);
73 }
74 
mock_i3c_target_tx(I3CTarget * i3c,const uint8_t * data,uint32_t num_to_send,uint32_t * num_sent)75 static int mock_i3c_target_tx(I3CTarget *i3c, const uint8_t *data,
76                               uint32_t num_to_send, uint32_t *num_sent)
77 {
78     MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
79     int ret;
80     uint32_t to_write;
81 
82     if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) {
83         mock_i3c_target_ibi_timer_start(s);
84         return 0;
85     }
86 
87     /* Bounds check. */
88     if (num_to_send + s->p_buf > s->cfg.buf_size) {
89         to_write = s->cfg.buf_size - s->p_buf;
90         ret = -1;
91     } else {
92         to_write = num_to_send;
93         ret = 0;
94     }
95     for (uint32_t i = 0; i < to_write; i++) {
96         trace_mock_i3c_target_tx(data[i]);
97         s->buf[s->p_buf] = data[i];
98         s->p_buf++;
99     }
100     return ret;
101 }
102 
mock_i3c_target_event(I3CTarget * i3c,enum I3CEvent event)103 static int mock_i3c_target_event(I3CTarget *i3c, enum I3CEvent event)
104 {
105     MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
106 
107     trace_mock_i3c_target_event(event);
108     if (event == I3C_STOP) {
109         s->in_ccc = false;
110         s->curr_ccc = 0;
111         s->ccc_byte_offset = 0;
112         s->p_buf = 0;
113     }
114 
115     return 0;
116 }
117 
mock_i3c_target_handle_ccc_read(I3CTarget * i3c,uint8_t * data,uint32_t num_to_read,uint32_t * num_read)118 static int mock_i3c_target_handle_ccc_read(I3CTarget *i3c, uint8_t *data,
119                                            uint32_t num_to_read,
120                                            uint32_t *num_read)
121 {
122     MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
123 
124     switch (s->curr_ccc) {
125     case I3C_CCCD_GETMXDS:
126         /* Default data rate for I3C. */
127         while (s->ccc_byte_offset < num_to_read) {
128             if (s->ccc_byte_offset >= 2) {
129                 break;
130             }
131             data[s->ccc_byte_offset] = 0;
132             *num_read = s->ccc_byte_offset;
133             s->ccc_byte_offset++;
134         }
135         break;
136     case I3C_CCCD_GETCAPS:
137         /* Support I3C version 1.1.x, no other features. */
138         while (s->ccc_byte_offset < num_to_read) {
139             if (s->ccc_byte_offset >= 2) {
140                 break;
141             }
142             if (s->ccc_byte_offset == 0) {
143                 data[s->ccc_byte_offset] = 0;
144             } else {
145                 data[s->ccc_byte_offset] = 0x01;
146             }
147             *num_read = s->ccc_byte_offset;
148             s->ccc_byte_offset++;
149         }
150         break;
151     case I3C_CCCD_GETMWL:
152     case I3C_CCCD_GETMRL:
153         /* MWL/MRL is MSB first. */
154         while (s->ccc_byte_offset < num_to_read) {
155             if (s->ccc_byte_offset >= 2) {
156                 break;
157             }
158             data[s->ccc_byte_offset] = (s->cfg.buf_size &
159                                         (0xff00 >> (s->ccc_byte_offset * 8))) >>
160                                         (8 - (s->ccc_byte_offset * 8));
161             s->ccc_byte_offset++;
162             *num_read = num_to_read;
163         }
164         break;
165     case I3C_CCC_ENTDAA:
166     case I3C_CCCD_GETPID:
167     case I3C_CCCD_GETBCR:
168     case I3C_CCCD_GETDCR:
169         /* Nothing to do. */
170         break;
171     default:
172         qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc);
173         return -1;
174     }
175 
176     trace_mock_i3c_target_handle_ccc_read(*num_read, num_to_read);
177     return 0;
178 }
179 
mock_i3c_target_handle_ccc_write(I3CTarget * i3c,const uint8_t * data,uint32_t num_to_send,uint32_t * num_sent)180 static int mock_i3c_target_handle_ccc_write(I3CTarget *i3c, const uint8_t *data,
181                                             uint32_t num_to_send,
182                                             uint32_t *num_sent)
183 {
184     MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
185 
186     if (!s->curr_ccc) {
187         s->in_ccc = true;
188         s->curr_ccc = *data;
189         trace_mock_i3c_target_new_ccc(s->curr_ccc);
190     }
191 
192     *num_sent = 1;
193     switch (s->curr_ccc) {
194     case I3C_CCC_ENEC:
195     case I3C_CCCD_ENEC:
196         s->can_ibi = true;
197         break;
198     case I3C_CCC_DISEC:
199     case I3C_CCCD_DISEC:
200         s->can_ibi = false;
201         break;
202     case I3C_CCC_ENTDAA:
203     case I3C_CCC_SETAASA:
204     case I3C_CCC_RSTDAA:
205     case I3C_CCCD_SETDASA:
206     case I3C_CCCD_GETPID:
207     case I3C_CCCD_GETBCR:
208     case I3C_CCCD_GETDCR:
209     case I3C_CCCD_GETMWL:
210     case I3C_CCCD_GETMRL:
211     case I3C_CCCD_GETMXDS:
212     case I3C_CCCD_GETCAPS:
213         /* Nothing to do. */
214         break;
215     default:
216         qemu_log_mask(LOG_GUEST_ERROR, "Unhandled CCC 0x%.2x\n", s->curr_ccc);
217         return -1;
218     }
219 
220     trace_mock_i3c_target_handle_ccc_write(*num_sent, num_to_send);
221     return 0;
222 }
223 
mock_i3c_target_do_ibi(MockI3cTargetState * s)224 static void mock_i3c_target_do_ibi(MockI3cTargetState *s)
225 {
226     if (!s->can_ibi) {
227         DB_PRINTF("IBIs disabled by controller");
228         return;
229     }
230 
231     trace_mock_i3c_target_do_ibi(s->i3c.address, true);
232     int nack = i3c_target_send_ibi(&s->i3c, s->i3c.address, /*is_recv=*/true);
233     /* Getting NACKed isn't necessarily an error, just print it out. */
234     if (nack) {
235         DB_PRINTF("NACKed from controller when sending target interrupt.\n");
236     }
237     nack = i3c_target_ibi_finish(&s->i3c, 0x00);
238     if (nack) {
239         DB_PRINTF("NACKed from controller when finishing target interrupt.\n");
240     }
241 }
242 
mock_i3c_target_timer_elapsed(void * opaque)243 static void mock_i3c_target_timer_elapsed(void *opaque)
244 {
245     MockI3cTargetState *s = MOCK_I3C_TARGET(opaque);
246     timer_del(&s->qtimer);
247     mock_i3c_target_do_ibi(s);
248 }
249 
mock_i3c_target_reset(I3CTarget * i3c)250 static void mock_i3c_target_reset(I3CTarget *i3c)
251 {
252     MockI3cTargetState *s = MOCK_I3C_TARGET(i3c);
253     s->can_ibi = false;
254 }
255 
mock_i3c_target_realize(DeviceState * dev,Error ** errp)256 static void mock_i3c_target_realize(DeviceState *dev, Error **errp)
257 {
258     MockI3cTargetState *s = MOCK_I3C_TARGET(dev);
259     s->buf = g_new0(uint8_t, s->cfg.buf_size);
260     mock_i3c_target_reset(&s->i3c);
261 }
262 
mock_i3c_target_init(Object * obj)263 static void mock_i3c_target_init(Object *obj)
264 {
265     MockI3cTargetState *s = MOCK_I3C_TARGET(obj);
266     s->can_ibi = false;
267 
268     /* For IBIs. */
269     timer_init_ns(&s->qtimer, QEMU_CLOCK_VIRTUAL, mock_i3c_target_timer_elapsed,
270                   s);
271 }
272 
273 static const Property remote_i3c_props[] = {
274     /* The size of the internal buffer. */
275     DEFINE_PROP_UINT32("buf-size", MockI3cTargetState, cfg.buf_size, 0x100),
276     /*
277      * If the mock target receives this number, it will issue an IBI after
278      * 1 second. Disabled if the IBI magic number is 0.
279      */
280     DEFINE_PROP_UINT8("ibi-magic-num", MockI3cTargetState, cfg.ibi_magic, 0x00),
281 };
282 
mock_i3c_target_class_init(ObjectClass * klass,const void * data)283 static void mock_i3c_target_class_init(ObjectClass *klass, const void *data)
284 {
285     DeviceClass *dc = DEVICE_CLASS(klass);
286     I3CTargetClass *k = I3C_TARGET_CLASS(klass);
287 
288     dc->realize = mock_i3c_target_realize;
289     k->event = mock_i3c_target_event;
290     k->recv = mock_i3c_target_rx;
291     k->send = mock_i3c_target_tx;
292     k->handle_ccc_read = mock_i3c_target_handle_ccc_read;
293     k->handle_ccc_write = mock_i3c_target_handle_ccc_write;
294 
295     device_class_set_props(dc, remote_i3c_props);
296 }
297 
298 static const TypeInfo mock_i3c_target_info = {
299     .name          = TYPE_MOCK_I3C_TARGET,
300     .parent        = TYPE_I3C_TARGET,
301     .instance_size = sizeof(MockI3cTargetState),
302     .instance_init = mock_i3c_target_init,
303     .class_init    = mock_i3c_target_class_init,
304 };
305 
mock_i3c_target_register_types(void)306 static void mock_i3c_target_register_types(void)
307 {
308     type_register_static(&mock_i3c_target_info);
309 }
310 
311 type_init(mock_i3c_target_register_types)
312