/* * Mock I3C Device * * Copyright (c) 2023 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. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "qemu/osdep.h" #include "qemu/log.h" #include "trace.h" #include "hw/i3c/i3c.h" #include "hw/i3c/mock-target.h" #include "hw/irq.h" #include "hw/qdev-properties.h" #include "qapi/error.h" #include "qemu/module.h" #ifndef MOCK_TARGET_DEBUG #define MOCK_TARGET_DEBUG 0 #endif #define DB_PRINTF(...) do { \ if (MOCK_TARGET_DEBUG) { \ qemu_log("%s: ", __func__); \ qemu_log(__VA_ARGS__); \ } \ } while (0) #define IBI_DELAY_NS (1 * 1000 * 1000) static uint32_t mock_target_rx(I3CTarget *i3c, uint8_t *data, uint32_t num_to_read) { MockTargetState *s = MOCK_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_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_target_ibi_timer_start(MockTargetState *s) { int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); timer_mod(&s->qtimer, now + IBI_DELAY_NS); } static int mock_target_tx(I3CTarget *i3c, const uint8_t *data, uint32_t num_to_send, uint32_t *num_sent) { MockTargetState *s = MOCK_TARGET(i3c); int ret; uint32_t to_write; if (s->cfg.ibi_magic && num_to_send == 1 && s->cfg.ibi_magic == *data) { mock_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_target_tx(data[i]); s->buf[s->p_buf] = data[i]; s->p_buf++; } return ret; } static int mock_target_event(I3CTarget *i3c, enum I3CEvent event) { MockTargetState *s = MOCK_TARGET(i3c); trace_mock_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_target_handle_ccc_read(I3CTarget *i3c, uint8_t *data, uint32_t num_to_read, uint32_t *num_read) { MockTargetState *s = MOCK_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_target_handle_ccc_read(*num_read, num_to_read); return 0; } static int mock_target_handle_ccc_write(I3CTarget *i3c, const uint8_t *data, uint32_t num_to_send, uint32_t *num_sent) { MockTargetState *s = MOCK_TARGET(i3c); if (!s->curr_ccc) { s->in_ccc = true; s->curr_ccc = *data; trace_mock_target_new_ccc(s->curr_ccc); } *num_sent = 1; switch (s->curr_ccc) { case I3C_CCC_ENEC: s->can_ibi = true; break; case I3C_CCC_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_target_handle_ccc_read(*num_sent, num_to_send); return 0; } static void mock_target_do_ibi(MockTargetState *s) { if (!s->can_ibi) { DB_PRINTF("IBIs disabled by controller"); return; } trace_mock_target_do_ibi(s->i3c.address, true); int nack = i3c_target_send_ibi(&s->i3c, s->i3c.address, /*is_recv=*/true); /* Getting NACKed isn't necessarily an error, just print it out. */ if (nack) { DB_PRINTF("NACKed from controller when sending target interrupt.\n"); } } static void mock_target_timer_elapsed(void *opaque) { MockTargetState *s = MOCK_TARGET(opaque); timer_del(&s->qtimer); mock_target_do_ibi(s); } static void mock_target_reset(I3CTarget *i3c) { MockTargetState *s = MOCK_TARGET(i3c); s->can_ibi = false; } static void mock_target_realize(DeviceState *dev, Error **errp) { MockTargetState *s = MOCK_TARGET(dev); s->buf = g_new0(uint8_t, s->cfg.buf_size); mock_target_reset(&s->i3c); } static void mock_target_init(Object *obj) { MockTargetState *s = MOCK_TARGET(obj); s->can_ibi = false; /* For IBIs. */ timer_init_ns(&s->qtimer, QEMU_CLOCK_VIRTUAL, mock_target_timer_elapsed, s); } static Property remote_i3c_props[] = { /* The size of the internal buffer. */ DEFINE_PROP_UINT32("buf-size", MockTargetState, 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", MockTargetState, cfg.ibi_magic, 0x00), DEFINE_PROP_END_OF_LIST(), }; static void mock_target_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); I3CTargetClass *k = I3C_TARGET_CLASS(klass); dc->realize = mock_target_realize; k->event = mock_target_event; k->recv = mock_target_rx; k->send = mock_target_tx; k->handle_ccc_read = mock_target_handle_ccc_read; k->handle_ccc_write = mock_target_handle_ccc_write; device_class_set_props(dc, remote_i3c_props); } static const TypeInfo mock_target_info = { .name = TYPE_MOCK_TARGET, .parent = TYPE_I3C_TARGET, .instance_size = sizeof(MockTargetState), .instance_init = mock_target_init, .class_init = mock_target_class_init, }; static void mock_target_register_types(void) { type_register_static(&mock_target_info); } type_init(mock_target_register_types)