Adds a model to communicate to remote I3C devices over chardev. This allows QEMU to communicate to I3C targets that exist outside of QEMU.
The remote I3C protocol is as follows: On an I3C private and CCC TX (controller -> target) - 1-byte opcode - 4-byte number of bytes in the packet as a LE uint32 - n-byte payload On an I3C private and CCC RX (target -> controller) Controller to target: - 1-byte opcode - 4-byte number of bytes to read as a LE uint32 Remote target response: - 4-byte number of bytes in the packet as a LE uint32 - n-byte payload IBI (target -> controller, initiated by target) - 1-byte opcode - 1-byte IBI address - 1-byte RnW boolean - 4-byte length of IBI payload from target as a LE uint32 (can be 0) - n-byte IBI payload Signed-off-by: Joe Komlodi <koml...@google.com> Reviewed-by: Patrick Venture <vent...@google.com> Reviewed-by: Stephen Longfield <slongfi...@google.com> --- hw/i3c/Kconfig | 5 + hw/i3c/meson.build | 1 + hw/i3c/remote-i3c.c | 469 ++++++++++++++++++++++++++++++++++++ hw/i3c/trace-events | 7 + include/hw/i3c/remote-i3c.h | 72 ++++++ 5 files changed, 554 insertions(+) create mode 100644 hw/i3c/remote-i3c.c create mode 100644 include/hw/i3c/remote-i3c.h diff --git a/hw/i3c/Kconfig b/hw/i3c/Kconfig index f97f4066a9..09b83578f3 100644 --- a/hw/i3c/Kconfig +++ b/hw/i3c/Kconfig @@ -10,3 +10,8 @@ config MOCK_TARGET bool select I3C default y if I3C_DEVICES + +config REMOTE_I3C + bool + select I3C + default y if I3C_DEVICES diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build index 0f6268063e..5bc900de5b 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_MOCK_TARGET', if_true: files('mock-target.c')) +i3c_ss.add(when: 'CONFIG_REMOTE_I3C', if_true: files('remote-i3c.c')) softmmu_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss) diff --git a/hw/i3c/remote-i3c.c b/hw/i3c/remote-i3c.c new file mode 100644 index 0000000000..20a45e58d9 --- /dev/null +++ b/hw/i3c/remote-i3c.c @@ -0,0 +1,469 @@ +/* + * Remote I3C Device + * + * Copyright (c) 2023 Google LLC + * + * 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/bswap.h" +#include "qemu/log.h" +#include "qemu/fifo8.h" +#include "chardev/char-fe.h" +#include "trace.h" +#include "hw/i3c/i3c.h" +#include "hw/i3c/remote-i3c.h" +#include "hw/qdev-properties-system.h" + +typedef enum { + IBI_RX_STATE_DONE = 0, + IBI_RX_STATE_READ_ADDR = 1, + IBI_RX_STATE_READ_RNW = 2, + IBI_RX_STATE_READ_SIZE = 3, + IBI_RX_STATE_READ_DATA = 4, +} IBIRXState; + +typedef struct { + uint8_t addr; + bool is_recv; + uint32_t num_bytes; + uint8_t *data; +} IBIData; + +typedef struct { + I3CTarget parent_obj; + CharBackend chr; + /* For ease of debugging. */ + + struct { + char *name; + uint32_t buf_size; + } cfg; + + /* Intermediate buffer to store IBI data received over socket. */ + IBIData ibi_data; + Fifo8 tx_fifo; + Fifo8 rx_fifo; + uint8_t current_cmd; + IBIRXState ibi_rx_state; + /* + * To keep track of where we are in reading in data that's longer than + * 1-byte. + */ + uint32_t ibi_bytes_rxed; +} RemoteI3C; + +static uint32_t remote_i3c_recv(I3CTarget *t, uint8_t *data, + uint32_t num_to_read) +{ + RemoteI3C *i3c = REMOTE_I3C(t); + uint8_t type = REMOTE_I3C_RECV; + uint32_t num_read; + + qemu_chr_fe_write_all(&i3c->chr, &type, 1); + uint32_t num_to_read_le = cpu_to_le32(num_to_read); + qemu_chr_fe_write_all(&i3c->chr, (uint8_t *)&num_to_read_le, + sizeof(num_to_read_le)); + /* + * The response will first contain the size of the packet, as a LE uint32. + */ + qemu_chr_fe_read_all(&i3c->chr, (uint8_t *)&num_read, sizeof(num_read)); + + num_read = le32_to_cpu(num_read); + qemu_chr_fe_read_all(&i3c->chr, data, num_read); + trace_remote_i3c_recv(i3c->cfg.name, num_read, num_to_read); + return num_read; +} + +static inline bool remote_i3c_tx_in_progress(RemoteI3C *i3c) +{ + return !fifo8_is_empty(&i3c->tx_fifo); +} + +static int remote_i3c_chr_send_bytes(RemoteI3C *i3c) +{ + uint32_t i; + uint32_t num_bytes = fifo8_num_used(&i3c->tx_fifo); + g_autofree uint8_t *buf = g_new0(uint8_t, num_bytes); + + qemu_chr_fe_write_all(&i3c->chr, &i3c->current_cmd, + sizeof(i3c->current_cmd)); + + /* The FIFO data is stored in a ring buffer, move it into a linear one. */ + for (i = 0; i < num_bytes; i++) { + buf[i] = fifo8_pop(&i3c->tx_fifo); + } + + uint32_t num_bytes_le = cpu_to_le32(num_bytes); + qemu_chr_fe_write_all(&i3c->chr, (uint8_t *)&num_bytes_le, 4); + qemu_chr_fe_write_all(&i3c->chr, buf, num_bytes); + trace_remote_i3c_send(i3c->cfg.name, num_bytes, i3c->current_cmd == + REMOTE_I3C_HANDLE_CCC_WRITE); + + return 0; +} + +static bool remote_i3c_tx_fifo_push(RemoteI3C *i3c, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent) +{ + uint32_t num_to_push = num_to_send; + bool ack = true; + + /* + * For performance reasons, we buffer data being sent from the controller to + * us. + * If this FIFO has data in it, we transmit it when we receive an I3C + * STOP or START. + */ + if (fifo8_num_free(&i3c->tx_fifo) < num_to_send) { + qemu_log_mask(LOG_GUEST_ERROR, "%s-%s: TX FIFO buffer full.\n", + object_get_canonical_path(OBJECT(i3c)), i3c->cfg.name); + num_to_push = fifo8_num_free(&i3c->tx_fifo); + ack = false; + } + + *num_sent = num_to_push; + for (uint32_t i = 0; i < num_to_push; i++) { + fifo8_push(&i3c->tx_fifo, data[i]); + } + + return ack; +} + +static int remote_i3c_send(I3CTarget *t, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent) +{ + RemoteI3C *i3c = REMOTE_I3C(t); + i3c->current_cmd = REMOTE_I3C_SEND; + if (!remote_i3c_tx_fifo_push(i3c, data, num_to_send, num_sent)) { + return -1; + } + + return 0; +} + +static int remote_i3c_handle_ccc_read(I3CTarget *t, uint8_t *data, + uint32_t num_to_read, uint32_t *num_read) +{ + RemoteI3C *i3c = REMOTE_I3C(t); + uint8_t type = REMOTE_I3C_HANDLE_CCC_READ; + + qemu_chr_fe_write_all(&i3c->chr, &type, 1); + /* + * The response will first contain the size of the packet, as a LE uint32. + */ + qemu_chr_fe_read_all(&i3c->chr, (uint8_t *)num_read, 4); + *num_read = le32_to_cpu(*num_read); + qemu_chr_fe_read_all(&i3c->chr, data, *num_read); + trace_remote_i3c_ccc_read(i3c->cfg.name, *num_read, num_to_read); + + return 0; +} + +static int remote_i3c_handle_ccc_write(I3CTarget *t, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent) +{ + RemoteI3C *i3c = REMOTE_I3C(t); + i3c->current_cmd = REMOTE_I3C_HANDLE_CCC_WRITE; + if (!remote_i3c_tx_fifo_push(i3c, data, num_to_send, num_sent)) { + return -1; + } + + return 0; +} + +static int remote_i3c_event(I3CTarget *t, enum I3CEvent event) +{ + RemoteI3C *i3c = REMOTE_I3C(t); + uint8_t type; + trace_remote_i3c_event(i3c->cfg.name, event); + switch (event) { + case I3C_START_RECV: + type = REMOTE_I3C_START_RECV; + break; + case I3C_START_SEND: + type = REMOTE_I3C_START_SEND; + break; + case I3C_STOP: + type = REMOTE_I3C_STOP; + break; + case I3C_NACK: + type = REMOTE_I3C_NACK; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s-%s: Unknown I3C event %d\n", + object_get_canonical_path(OBJECT(i3c)), i3c->cfg.name, + event); + return -1; + } + + /* + * If we have a transfer buffered, send it out before we tell the remote + * target about the next event. + */ + if (remote_i3c_tx_in_progress(i3c)) { + remote_i3c_chr_send_bytes(i3c); + } + + qemu_chr_fe_write_all(&i3c->chr, &type, 1); + return 0; +} + +static void remote_i3c_chr_event(void *opaque, QEMUChrEvent evt) +{ + switch (evt) { + case CHR_EVENT_OPENED: + case CHR_EVENT_CLOSED: + case CHR_EVENT_BREAK: + case CHR_EVENT_MUX_IN: + case CHR_EVENT_MUX_OUT: + /* + * Ignore events. + * Our behavior stays the same regardless of what happens. + */ + break; + default: + g_assert_not_reached(); + } +} + +static void remote_i3c_rx_ibi(RemoteI3C *i3c, const uint8_t *buf, int size) +{ + uint32_t p_buf = 0; + while (p_buf < size) { + switch (i3c->ibi_rx_state) { + /* This is the start of a new IBI request. */ + case IBI_RX_STATE_DONE: + i3c->ibi_rx_state = IBI_RX_STATE_READ_ADDR; + p_buf++; + break; + case IBI_RX_STATE_READ_ADDR: + i3c->ibi_data.addr = buf[p_buf]; + p_buf++; + i3c->ibi_rx_state = IBI_RX_STATE_READ_RNW; + break; + case IBI_RX_STATE_READ_RNW: + i3c->ibi_data.is_recv = buf[p_buf]; + p_buf++; + i3c->ibi_rx_state = IBI_RX_STATE_READ_SIZE; + break; + case IBI_RX_STATE_READ_SIZE: + i3c->ibi_data.num_bytes |= ((uint32_t)buf[p_buf] << + (8 * i3c->ibi_bytes_rxed)); + i3c->ibi_bytes_rxed++; + p_buf++; + /* + * If we're done reading the num_bytes portion, move on to reading + * data. + */ + if (i3c->ibi_bytes_rxed == sizeof(i3c->ibi_data.num_bytes)) { + i3c->ibi_data.num_bytes = le32_to_cpu(i3c->ibi_data.num_bytes); + i3c->ibi_bytes_rxed = 0; + i3c->ibi_rx_state = IBI_RX_STATE_READ_DATA; + /* If there's no data to read, we're done. */ + if (i3c->ibi_data.num_bytes == 0) { + /* + * Sanity check to see if the remote target is doing + * something wonky. This would only happen if it sends + * another IBI before the first one has been ACKed/NACKed + * by the controller. + * We'll try to recover by just exiting early and discarding + * the leftover bytes. + */ + if (p_buf < size) { + qemu_log_mask(LOG_GUEST_ERROR, "%s-%s: Remote target " + "sent trailing bytes at the end of the " + "IBI request.", + object_get_canonical_path(OBJECT(i3c)), + i3c->cfg.name); + return; + } + i3c->ibi_rx_state = IBI_RX_STATE_DONE; + } else { + /* + * We have IBI bytes to read, allocate memory for it. + * Freed when we're done sending the IBI to the controller. + */ + i3c->ibi_data.data = g_new0(uint8_t, + i3c->ibi_data.num_bytes); + } + } + break; + case IBI_RX_STATE_READ_DATA: + i3c->ibi_data.data[i3c->ibi_bytes_rxed] = buf[p_buf]; + i3c->ibi_bytes_rxed++; + p_buf++; + if (i3c->ibi_bytes_rxed == i3c->ibi_data.num_bytes) { + /* + * Sanity check to see if the remote target is doing something + * wonky. This would only happen if it sends another IBI before + * the first one has been ACKed/NACKed by the controller. + * We'll try to recover by just exiting early and discarding the + * leftover bytes. + */ + if (p_buf < size) { + qemu_log_mask(LOG_GUEST_ERROR, "%s-%s: Remote target " + "sent trailing bytes at the end of the " + "IBI request.", + object_get_canonical_path(OBJECT(i3c)), i3c->cfg.name); + return; + } + i3c->ibi_rx_state = IBI_RX_STATE_DONE; + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s-%s: Remote target IBI state " + "machine reached unknown state 0x%x\n", + object_get_canonical_path(OBJECT(i3c)), i3c->cfg.name, + i3c->ibi_rx_state); + g_assert_not_reached(); + } + } +} + +static void remote_i3c_ibi_rx_state_reset(RemoteI3C *i3c) +{ + if (i3c->ibi_data.num_bytes) { + free(i3c->ibi_data.data); + } + i3c->ibi_data.addr = 0; + i3c->ibi_data.is_recv = 0; + i3c->ibi_data.num_bytes = 0; + i3c->ibi_bytes_rxed = 0; + i3c->ibi_rx_state = IBI_RX_STATE_DONE; +} + +static void remote_i3c_do_ibi(RemoteI3C *i3c) +{ + uint32_t i; + uint8_t resp = REMOTE_I3C_IBI_ACK; + + trace_remote_i3c_do_ibi(i3c->cfg.name, i3c->ibi_data.addr, + i3c->ibi_data.is_recv); + if (i3c_target_send_ibi(&i3c->parent_obj, i3c->ibi_data.addr, + i3c->ibi_data.is_recv)) { + resp = REMOTE_I3C_IBI_NACK; + } else { + for (i = 0; i < i3c->ibi_data.num_bytes; i++) { + if (i3c_target_send_ibi_bytes(&i3c->parent_obj, + i3c->ibi_data.data[i])) { + resp = REMOTE_I3C_IBI_DATA_NACK; + break; + } + } + } + + if (i3c_target_ibi_finish(&i3c->parent_obj, 0x00)) { + resp = REMOTE_I3C_IBI_NACK; + } + qemu_chr_fe_write_all(&i3c->chr, &resp, sizeof(resp)); + remote_i3c_ibi_rx_state_reset(i3c); +} + +static int remote_i3c_chr_can_receive(void *opaque) +{ + return 1; +} + +static void remote_i3c_chr_receive(void *opaque, const uint8_t *buf, int size) +{ + RemoteI3C *i3c = REMOTE_I3C(opaque); + + /* + * The only things we expect to receive unprompted are: + * - An ACK of a previous transfer + * - A NACK of a previous transfer + * - An IBI requested by the remote target. + * - Bytes for an IBI request. + */ + /* If we're in the middle of handling an IBI request, parse it here. */ + if (i3c->ibi_rx_state != IBI_RX_STATE_DONE) { + remote_i3c_rx_ibi(i3c, buf, size); + /* If we finished reading the IBI, do it. */ + if (i3c->ibi_rx_state == IBI_RX_STATE_DONE) { + remote_i3c_do_ibi(i3c); + } + return; + } + + switch (buf[0]) { + case REMOTE_I3C_RX_ACK: + break; + case REMOTE_I3C_RX_NACK: + qemu_log_mask(LOG_GUEST_ERROR, "%s-%s: Received NACK from remote " + "target\n", object_get_canonical_path(OBJECT(i3c)), + i3c->cfg.name); + break; + case REMOTE_I3C_IBI: + remote_i3c_rx_ibi(i3c, buf, size); + /* If we finished reading the IBI, do it. */ + if (i3c->ibi_rx_state == IBI_RX_STATE_DONE) { + remote_i3c_do_ibi(i3c); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s-%s: Unknown response 0x%x\n", + object_get_canonical_path(OBJECT(i3c)), i3c->cfg.name, + buf[0]); + break; + } +} + +static void remote_i3c_realize(DeviceState *dev, Error **errp) +{ + RemoteI3C *i3c = REMOTE_I3C(dev); + + fifo8_create(&i3c->tx_fifo, i3c->cfg.buf_size); + fifo8_create(&i3c->rx_fifo, i3c->cfg.buf_size); + i3c->ibi_data.data = g_new0(uint8_t, i3c->cfg.buf_size); + remote_i3c_ibi_rx_state_reset(i3c); + + qemu_chr_fe_set_handlers(&i3c->chr, remote_i3c_chr_can_receive, + remote_i3c_chr_receive, remote_i3c_chr_event, + NULL, OBJECT(i3c), NULL, true); +} + +static Property remote_i3c_props[] = { + DEFINE_PROP_CHR("chardev", RemoteI3C, chr), + DEFINE_PROP_UINT32("buf-size", RemoteI3C, cfg.buf_size, 0x10000), + DEFINE_PROP_STRING("device-name", RemoteI3C, cfg.name), + DEFINE_PROP_END_OF_LIST(), +}; + +static void remote_i3c_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I3CTargetClass *k = I3C_TARGET_CLASS(klass); + + k->recv = &remote_i3c_recv; + k->send = &remote_i3c_send; + k->event = &remote_i3c_event; + k->handle_ccc_read = &remote_i3c_handle_ccc_read; + k->handle_ccc_write = &remote_i3c_handle_ccc_write; + device_class_set_props(dc, remote_i3c_props); + dc->realize = remote_i3c_realize; +} + +static const TypeInfo remote_i3c_type = { + .name = TYPE_REMOTE_I3C, + .parent = TYPE_I3C_TARGET, + .instance_size = sizeof(RemoteI3C), + .class_size = sizeof(I3CTargetClass), + .class_init = remote_i3c_class_init, +}; + +static void remote_i3c_register(void) +{ + type_register_static(&remote_i3c_type); +} + +type_init(remote_i3c_register) diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events index 74c5a5dd6e..ff5efac784 100644 --- a/hw/i3c/trace-events +++ b/hw/i3c/trace-events @@ -43,3 +43,10 @@ mock_target_handle_ccc_read(uint32_t num_read, uint32_t num_to_read) "I3C mock t mock_target_new_ccc(uint8_t ccc) "I3C mock target handle CCC 0x%" PRIx8 mock_target_handle_ccc_write(uint32_t num_sent, uint32_t num_to_send) "I3C mock target send %" PRId32 "/%" PRId32 " bytes" mock_target_do_ibi(uint8_t address, bool is_recv) "I3C mock target IBI with address 0x%" PRIx8 " RnW=%d" + +# remote-i3c.c +remote_i3c_recv(const char *name, uint32_t num_read, uint32_t num_to_read) "Remote I3C '%s' read %" PRId32 "/%" PRId32 " bytes" +remote_i3c_send(const char *name, uint32_t num_sent, bool is_ccc) "Remote I3C '%s' send %" PRId32 " bytes, is_ccc=%d" +remote_i3c_ccc_read(const char *name, uint32_t num_read, uint32_t num_to_read) "Remote I3C '%s' CCC read %" PRId32 "/%" PRId32 " bytes" +remote_i3c_event(const char *name, uint8_t event) "Remote I3C '%s' event 0x%" PRIx8 +remote_i3c_do_ibi(const char *name, uint8_t addr, bool is_recv) "Remote I3C '%s' IBI with addr 0x%" PRIx8 " RnW=%d" diff --git a/include/hw/i3c/remote-i3c.h b/include/hw/i3c/remote-i3c.h new file mode 100644 index 0000000000..822b358098 --- /dev/null +++ b/include/hw/i3c/remote-i3c.h @@ -0,0 +1,72 @@ +/* + * Remote I3C Device + * + * Copyright (c) 2023 Google LLC + * + * 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. + */ + +/* + * The remote I3C protocol is as follows: + * On an I3C private and CCC TX (controller -> target) + * - 1-byte opcode + * - 4-byte number of bytes in the packet as a LE uint32 + * - n-byte payload + * + * On an I3C private and CCC RX (target -> controller) + * Controller to target: + * - 1-byte opcode + * - 4-byte number of bytes to read as a LE uint32 + * Remote target response: + * - 4-byte number of bytes in the packet as a LE uint32 + * - n-byte payload + * + * IBI (target -> controller, initiated by target) + * - 1-byte opcode + * - 1-byte IBI address + * - 1-byte RnW boolean + * - 4-byte length of IBI payload from target as a LE uint32 (can be 0) + * - n-byte IBI payload + */ + +#ifndef REMOTE_I3C_H_ +#define REMOTE_I3C_H_ + +#define TYPE_REMOTE_I3C "remote-i3c" +#define REMOTE_I3C(obj) OBJECT_CHECK(RemoteI3C, (obj), TYPE_REMOTE_I3C) + +/* 1-byte IBI addr, 1-byte is recv, 4-byte data len. */ +#define REMOTE_I3C_IBI_HDR_LEN 6 + +/* Stored in a uint8_t */ +typedef enum { + /* Sent from us to remote target. */ + REMOTE_I3C_START_RECV = 1, + REMOTE_I3C_START_SEND = 2, + REMOTE_I3C_STOP = 3, + REMOTE_I3C_NACK = 4, + REMOTE_I3C_RECV = 5, + REMOTE_I3C_SEND = 6, + REMOTE_I3C_HANDLE_CCC_WRITE = 7, + REMOTE_I3C_HANDLE_CCC_READ = 8, + REMOTE_I3C_IBI = 9, + /* Sent from remote target to us. */ + REMOTE_I3C_IBI_ACK = 0xc0, + REMOTE_I3C_IBI_NACK = 0xc1, + REMOTE_I3C_IBI_DATA_NACK = 0xc2, +} RemoteI3CCommand; + +typedef enum { + REMOTE_I3C_RX_ACK = 0, + REMOTE_I3C_RX_NACK = 1, +} RemoteI3CRXEvent; + +#endif -- 2.40.0.348.gf938b09366-goog