diff --git a/hw/i3c/core.c b/hw/i3c/core.c new file mode 100644 index 0000000000..22e6e226a7 --- /dev/null +++ b/hw/i3c/core.c @@ -0,0 +1,647 @@ +/* + * QEMU I3C bus interface. + * + * Copyright 2025 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "trace.h" +#include "hw/i3c/i3c.h" +#include "hw/core/qdev-properties.h" + +/* + * In test mode (enabled by ENTTM CCC) we're supposed to send a random PID + * during ENTDAA, so we'll just send "QEMU". + */ +#define TEST_MODE_PROVISIONED_ID 0x0000554d4551ULL + +static const Property i3c_props[] = { + DEFINE_PROP_UINT8("static-address", struct I3CTarget, static_address, 0), + DEFINE_PROP_UINT8("dcr", struct I3CTarget, dcr, 0), + DEFINE_PROP_UINT8("bcr", struct I3CTarget, bcr, 0), + DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0), +}; + +I3CBus *i3c_init_bus(DeviceState *parent, const char *name) +{ + return i3c_init_bus_type(TYPE_I3C_BUS, parent, name); +} + +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent, + const char *name) +{ + I3CBus *bus; + + bus = I3C_BUS(qbus_new(type, parent, name)); + QLIST_INIT(&bus->current_devs); + bus->broadcast = false; + bus->in_entdaa = false; + bus->in_ccc = false; + + /* I2C init. */ + g_autofree gchar *i2c_bus_name = g_strdup_printf("%s-legacy-i2c", name); + bus->i2c_bus = i2c_init_bus(parent, i2c_bus_name); + + return bus; +} + +bool i3c_bus_busy(I3CBus *bus) +{ + return !QLIST_EMPTY(&bus->current_devs); +} + +static bool i3c_target_match(I3CTarget *candidate, uint8_t address, + bool is_recv, bool broadcast, bool in_entdaa) +{ + /* Once a target has a dynamic address, it only responds to that. */ + uint8_t targ_addr = candidate->address ? candidate->address : + candidate->static_address; + + if (in_entdaa) { + if (address != I3C_BROADCAST) { + g_autofree char *path = + object_get_canonical_path(OBJECT(candidate)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address 0x%.2x sent during " + "ENTDAA instead of a broadcast address\n", + path, address); + return false; + } + + /* + * Targets should only ACK ENTDAA broadcasts if they have no dynamic + * address. + */ + return candidate->address == 0; + } + + /* Return if our addresses match, or if it's a broadcast. */ + return targ_addr == address || broadcast; +} + +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address, + enum I3CEvent event) +{ + I3CTargetClass *tc = I3C_TARGET_GET_CLASS(target); + bool matched = tc->target_match(target, address, event == I3C_START_RECV, + bus->broadcast, bus->in_entdaa); + + if (matched) { + I3CNode *node = g_new(struct I3CNode, 1); + node->target = target; + QLIST_INSERT_HEAD(&bus->current_devs, node, next); + } + return matched; +} + +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event) +{ + BusChild *child; + I3CNode *node, *next; + + /* Clear out any devices from a previous (re-)START. */ + QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) { + QLIST_REMOVE(node, next); + g_free(node); + } + + QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) { + DeviceState *qdev = child->child; + I3CTarget *target = I3C_TARGET(qdev); + + if (i3c_target_match_and_add(bus, target, address, event)) { + return true; + } + } + + /* No one on the bus could respond. */ + return false; +} + +/* Class-level event handling, since we do some CCCs at the class level. */ +static int i3c_target_event(I3CTarget *t, enum I3CEvent event) +{ + I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t); + trace_i3c_target_event(t->address, event); + + if (event == I3C_STOP) { + t->curr_ccc = 0; + t->ccc_byte_offset = 0; + t->in_ccc = false; + } + return tc->event(t, event); +} + +/* + * Sends a START or repeated START and the address for an I3C transaction. + * + * This function returns 0 if a device on the bus was able to respond to the + * address, and non-zero otherwise. + * A non-zero return represents a NACK. + */ +static int i3c_do_start_transfer(I3CBus *bus, uint8_t address, + enum I3CEvent event) +{ + I3CTargetClass *tc; + I3CNode *node; + + if (address == I3C_BROADCAST) { + bus->broadcast = true; + /* If we're not in ENTDAA, a broadcast is the start of a new CCC. */ + if (!bus->in_entdaa) { + bus->in_ccc = false; + } + } else { + bus->broadcast = false; + } + + /* No one responded to the address, NACK it. */ + if (!i3c_scan_bus(bus, address, event)) { + return -1; + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + I3CTarget *t = node->target; + + tc = I3C_TARGET_GET_CLASS(t); + if (tc->event) { + int rv = i3c_target_event(t, event); + if (rv && !bus->broadcast) { + return rv; + } + } + } + + return 0; +} + +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv) +{ + trace_i3c_start_transfer(address, is_recv); + return i3c_do_start_transfer(bus, address, is_recv + ? I3C_START_RECV + : I3C_START_SEND); +} + +int i3c_start_recv(I3CBus *bus, uint8_t address) +{ + trace_i3c_start_transfer(address, true); + return i3c_do_start_transfer(bus, address, I3C_START_RECV); +} + +int i3c_start_send(I3CBus *bus, uint8_t address) +{ + trace_i3c_start_transfer(address, false); + return i3c_do_start_transfer(bus, address, I3C_START_SEND); +} + +void i3c_end_transfer(I3CBus *bus) +{ + I3CTargetClass *tc; + I3CNode *node, *next; + + trace_i3c_end_transfer(); + + /* + * If we're in ENTDAA, we need to notify all devices when ENTDAA is done. + * This is because everyone initially participates due to the broadcast, + * but gradually drops out as they get assigned addresses. + * Since the current_devs list only stores who's currently participating, + * and not everyone who previously participated, we send the STOP to all + * children. + */ + if (bus->in_entdaa) { + BusChild *child; + + QTAILQ_FOREACH(child, &bus->parent_obj.children, sibling) { + DeviceState *qdev = child->child; + I3CTarget *t = I3C_TARGET(qdev); + tc = I3C_TARGET_GET_CLASS(t); + if (tc->event) { + i3c_target_event(t, I3C_STOP); + } + } + } else { + QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) { + I3CTarget *t = node->target; + tc = I3C_TARGET_GET_CLASS(t); + if (tc->event) { + i3c_target_event(t, I3C_STOP); + } + QLIST_REMOVE(node, next); + g_free(node); + } + } + bus->broadcast = false; + bus->in_entdaa = false; + bus->in_ccc = false; +} + +/* + * Any CCCs that are universal across all I3C devices should be handled here. + * Once they're handled, we pass the CCC up to the I3C target to do anything + * else it may want with the bytes. + */ +static int i3c_target_handle_ccc_write(I3CTarget *t, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent) +{ + I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t); + *num_sent = 0; + + /* Is this the start of a new CCC? */ + if (!t->in_ccc) { + t->curr_ccc = *data; + t->in_ccc = true; + *num_sent = 1; + trace_i3c_target_handle_ccc(t->address, t->curr_ccc); + } + + switch (t->curr_ccc) { + case I3C_CCC_ENTDAA: + /* + * This is the last byte of ENTDAA, the controller is assigning us an + * address. + */ + if (t->ccc_byte_offset == 8) { + t->address = *data; + t->in_ccc = false; + t->curr_ccc = 0; + t->ccc_byte_offset = 0; + *num_sent = 1; + } + break; + case I3C_CCCD_SETDASA: + t->address = t->static_address; + break; + case I3C_CCC_SETAASA: + t->address = t->static_address; + break; + case I3C_CCC_RSTDAA: + t->address = 0; + break; + case I3C_CCCD_SETNEWDA: + /* If this isn't the CCC byte, it's our new address. */ + if (*num_sent == 0) { + t->address = *data; + *num_sent = 1; + } + break; + case I3C_CCC_ENTTM: + /* + * If there are still more to look at, the next byte is the test mode + * byte. + */ + if (*num_sent != num_to_send) { + /* Enter test mode if the byte is non-zero. Otherwise exit. */ + t->in_test_mode = !!data[*num_sent]; + ++*num_sent; + } + break; + /* Ignore other CCCs it's better to handle on a device-by-device basis. */ + default: + break; + } + return tc->handle_ccc_write(t, data, num_to_send, num_sent); +} + +int i3c_send_byte(I3CBus *bus, uint8_t data) +{ + /* + * Ignored, the caller can determine how many were sent based on if this was + * ACKed/NACKed. + */ + uint32_t num_sent; + return i3c_send(bus, &data, 1, &num_sent); +} + +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent) +{ + I3CTargetClass *tc; + I3CTarget *t; + I3CNode *node; + int ret = 0; + + /* If this message is a broadcast and no CCC has been found, grab it. */ + if (bus->broadcast && !bus->in_ccc) { + bus->ccc = *data; + bus->in_ccc = true; + /* + * We need to keep track if we're currently in ENTDAA. + * On any other CCC, the CCC is over on a RESTART or STOP, but ENTDAA + * is only over on a STOP. + */ + if (bus->ccc == I3C_CCC_ENTDAA) { + bus->in_entdaa = true; + } + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + t = node->target; + tc = I3C_TARGET_GET_CLASS(t); + if (bus->in_ccc) { + if (!tc->handle_ccc_write) { + ret = -1; + continue; + } + ret = i3c_target_handle_ccc_write(t, data, num_to_send, num_sent); + /* Targets should only NACK on a direct CCC. */ + if (ret && !CCC_IS_DIRECT(bus->ccc)) { + ret = 0; + } + } else { + if (tc->send) { + ret = ret || tc->send(t, data, num_to_send, num_sent); + } else { + ret = -1; + } + } + } + + trace_i3c_send(*num_sent, num_to_send, ret == 0); + + return ret ? -1 : 0; +} + +static int i3c_target_handle_ccc_read(I3CTarget *t, uint8_t *data, + uint32_t num_to_read, uint32_t *num_read) +{ + I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t); + uint8_t read_count = 0; + uint64_t pid; + + switch (t->curr_ccc) { + case I3C_CCC_ENTDAA: + if (t->in_test_mode) { + pid = TEST_MODE_PROVISIONED_ID; + } else { + pid = t->pid; + } + /* Return the 6-byte PID, followed by BCR then DCR. */ + while (t->ccc_byte_offset < 6) { + if (read_count >= num_to_read) { + break; + } + data[read_count] = (pid >> (t->ccc_byte_offset * 8)) & 0xff; + t->ccc_byte_offset++; + read_count++; + } + if (read_count < num_to_read) { + data[read_count] = t->bcr; + t->ccc_byte_offset++; + read_count++; + } + if (read_count < num_to_read) { + data[read_count] = t->dcr; + t->ccc_byte_offset++; + read_count++; + } + *num_read = read_count; + break; + case I3C_CCCD_GETPID: + while (t->ccc_byte_offset < 6) { + if (read_count >= num_to_read) { + break; + } + data[read_count] = (t->pid >> (t->ccc_byte_offset * 8)) & 0xff; + t->ccc_byte_offset++; + read_count++; + } + *num_read = read_count; + break; + case I3C_CCCD_GETBCR: + *data = t->bcr; + *num_read = 1; + break; + case I3C_CCCD_GETDCR: + *data = t->dcr; + *num_read = 1; + break; + default: + /* Unhandled on the I3CTarget class level. */ + break; + } + + return tc->handle_ccc_read(t, data, num_to_read, num_read); +} + +int i3c_recv_byte(I3CBus *bus, uint8_t *data) +{ + /* + * Ignored, the caller can determine how many bytes were read based on if + * this is ACKed/NACKed. + */ + uint32_t num_read; + return i3c_recv(bus, data, 1, &num_read); +} + +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read, + uint32_t *num_read) +{ + int ret = 0; + I3CTargetClass *tc; + I3CTarget *t; + + *data = 0xff; + if (!QLIST_EMPTY(&bus->current_devs)) { + tc = I3C_TARGET_GET_CLASS(QLIST_FIRST(&bus->current_devs)->target); + t = QLIST_FIRST(&bus->current_devs)->target; + if (bus->in_ccc) { + if (!tc->handle_ccc_read) { + return -1; + } + ret = i3c_target_handle_ccc_read(t, data, num_to_read, num_read); + } else { + if (tc->recv) { + /* + * Targets cannot NACK on a direct transfer, so the data + * is returned directly. + */ + *num_read = tc->recv(t, data, num_to_read); + } + } + } + + trace_i3c_recv(*num_read, num_to_read, ret == 0); + + return ret; +} + +void i3c_nack(I3CBus *bus) +{ + I3CTargetClass *tc; + I3CNode *node; + + if (QLIST_EMPTY(&bus->current_devs)) { + return; + } + + QLIST_FOREACH(node, &bus->current_devs, next) { + tc = I3C_TARGET_GET_CLASS(node->target); + if (tc->event) { + i3c_target_event(node->target, I3C_NACK); + } + } +} + +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv) +{ + I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus); + I3CBusClass *bc = I3C_BUS_GET_CLASS(bus); + trace_i3c_target_send_ibi(addr, is_recv); + return bc->ibi_handle(bus, addr, is_recv); +} + +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data) +{ + I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus); + I3CBusClass *bc = I3C_BUS_GET_CLASS(bus); + trace_i3c_target_send_ibi_bytes(data); + return bc->ibi_recv(bus, data); +} + +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data) +{ + I3CBus *bus = I3C_BUS(t->parent_obj.parent_bus); + I3CBusClass *bc = I3C_BUS_GET_CLASS(bus); + trace_i3c_target_ibi_finish(); + return bc->ibi_finish(bus); +} + +static bool i3c_addr_is_rsvd(uint8_t addr) +{ + const bool is_rsvd[255] = { + [0x00] = true, + [0x01] = true, + [0x02] = true, + [0x3e] = true, + [0x5e] = true, + [0x6e] = true, + [0x76] = true, + [0x7a] = true, + [0x7c] = true, + [0x7e] = true, + [0x7f] = true, + }; + + return is_rsvd[addr]; +} + +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr, + uint8_t bcr, uint64_t pid) +{ + DeviceState *dev; + + dev = qdev_new(name); + qdev_prop_set_uint8(dev, "static-address", addr); + qdev_prop_set_uint8(dev, "dcr", dcr); + qdev_prop_set_uint8(dev, "bcr", bcr); + qdev_prop_set_uint64(dev, "pid", pid); + + if (i3c_addr_is_rsvd(addr)) { + g_autofree char *path = object_get_canonical_path(OBJECT(dev)); + qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created with reserved " + "address 0x%.2x\n", path, addr); + } + return I3C_TARGET(dev); +} + +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp) +{ + return qdev_realize_and_unref(&dev->parent_obj, &bus->parent_obj, errp); +} + +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, uint8_t addr, + uint8_t dcr, uint8_t bcr, uint64_t pid) +{ + I3CTarget *dev = i3c_target_new(name, addr, dcr, bcr, pid); + dev->address = 0; + i3c_target_realize_and_unref(dev, bus, &error_abort); + + return dev; +} + +/* Legacy I2C functions. */ +void legacy_i2c_nack(I3CBus *bus) +{ + trace_legacy_i2c_nack(); + i2c_nack(bus->i2c_bus); +} + +uint8_t legacy_i2c_recv(I3CBus *bus) +{ + uint8_t byte = i2c_recv(bus->i2c_bus); + trace_legacy_i2c_recv(byte); + return byte; +} + +int legacy_i2c_send(I3CBus *bus, uint8_t data) +{ + trace_legacy_i2c_send(data); + return i2c_send(bus->i2c_bus, data); +} + +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv) +{ + trace_legacy_i2c_start_transfer(address, is_recv); + return i2c_start_transfer(bus->i2c_bus, address, is_recv); +} + +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address) +{ + trace_legacy_i2c_start_transfer(address, true); + return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/true); +} + +int legacy_i2c_start_send(I3CBus *bus, uint8_t address) +{ + trace_legacy_i2c_start_transfer(address, false); + return i2c_start_transfer(bus->i2c_bus, address, /*is_recv=*/false); +} + +void legacy_i2c_end_transfer(I3CBus *bus) +{ + trace_legacy_i2c_end_transfer(); + i2c_end_transfer(bus->i2c_bus); +} + +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name, + uint8_t addr) +{ + I2CSlave *dev = i2c_slave_new(name, addr); + + i2c_slave_realize_and_unref(dev, bus->i2c_bus, &error_abort); + return dev; +} + +static void i3c_target_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + I3CTargetClass *sc = I3C_TARGET_CLASS(klass); + set_bit(DEVICE_CATEGORY_MISC, k->categories); + k->bus_type = TYPE_I3C_BUS; + device_class_set_props(k, i3c_props); + sc->target_match = i3c_target_match; +} + +static const TypeInfo i3c_types[] = { + { + .name = TYPE_I3C_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(I3CBus), + .class_size = sizeof(I3CBusClass), + }, + { + .name = TYPE_I3C_TARGET, + .parent = TYPE_DEVICE, + .instance_size = sizeof(I3CTarget), + .abstract = true, + .class_size = sizeof(I3CTargetClass), + .class_init = i3c_target_class_init, + }, +}; + +DEFINE_TYPES(i3c_types) diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build index ebf20325cb..fb127613fe 100644 --- a/hw/i3c/meson.build +++ b/hw/i3c/meson.build @@ -1,3 +1,4 @@ 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')) system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss) diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events index 3ead84eb45..cdf7cb07f6 100644 --- a/hw/i3c/trace-events +++ b/hw/i3c/trace-events @@ -5,3 +5,19 @@ aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C read: offset 0x%" PRIx64 " aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64 " data 0x%" PRIx64 aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64 aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64 + +# core.c +i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " event 0x%" PRIx8 +i3c_target_handle_ccc(uint8_t address, uint8_t ccc) "I3C target 0x%" PRIx8 " handling CCC 0x%" PRIx8 +i3c_target_send_ibi(uint8_t address, bool is_recv) "I3C target IBI address 0x%" PRIx8 " RnW=%d" +i3c_target_send_ibi_bytes(uint8_t byte) "I3C target IBI byte 0x%" PRIx8 +i3c_target_ibi_finish(void) "I3C target IBI finish" +i3c_start_transfer(uint8_t address, bool is_recv) "I3C START with address 0x%" PRIx8 " is_recv=%d" +i3c_end_transfer(void) "I3C transfer done" +i3c_send(uint32_t num_sent, uint32_t num_to_send, bool ack) "I3C send %" PRId32 "/%" PRId32 " bytes, ack=%d" +i3c_recv(uint32_t num_read, uint32_t num_to_read, bool ack) "I3C recv %" PRId32 "/%" PRId32 " bytes, ack=%d" +legacy_i2c_nack(void) "Legacy I2C NACK" +legacy_i2c_recv(uint8_t byte) "Legacy I2C recv 0x%" PRIx8 +legacy_i2c_send(uint8_t byte) "Legacy I2C send 0x%" PRIx8 +legacy_i2c_start_transfer(uint8_t address, bool is_recv) "Legacy I2C START with address 0x%" PRIx8 " is_recv=%d" +legacy_i2c_end_transfer(void) "Legacy I2C STOP" diff --git a/include/hw/i3c/i3c.h b/include/hw/i3c/i3c.h new file mode 100644 index 0000000000..6ba90793ad --- /dev/null +++ b/include/hw/i3c/i3c.h @@ -0,0 +1,277 @@ +/* + * QEMU I3C bus interface. + * + * Copyright 2025 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_INCLUDE_HW_I3C_I3C_H_ +#define QEMU_INCLUDE_HW_I3C_I3C_H_ + +#include "hw/core/qdev.h" +#include "qom/object.h" +#include "hw/i2c/i2c.h" + +#define TYPE_I3C_TARGET "i3c-target" +OBJECT_DECLARE_TYPE(I3CTarget, I3CTargetClass, I3C_TARGET) + +typedef enum I3CEvent { + I3C_START_RECV, + I3C_START_SEND, + I3C_STOP, + I3C_NACK, +} I3CEvent; + +typedef enum I3CCCC { + /* Broadcast CCCs */ + I3C_CCC_ENEC = 0x00, + I3C_CCC_DISEC = 0x01, + I3C_CCC_ENTAS0 = 0x02, + I3C_CCC_ENTAS1 = 0x03, + I3C_CCC_ENTAS2 = 0x04, + I3C_CCC_ENTAS3 = 0x05, + I3C_CCC_RSTDAA = 0x06, + I3C_CCC_ENTDAA = 0x07, + I3C_CCC_DEFTGTS = 0x08, + I3C_CCC_SETMWL = 0x09, + I3C_CCC_SETMRL = 0x0a, + I3C_CCC_ENTTM = 0x0b, + I3C_CCC_SETBUSCON = 0x0c, + I3C_CCC_ENDXFER = 0x12, + I3C_CCC_ENTHDR0 = 0x20, + I3C_CCC_ENTHDR1 = 0x21, + I3C_CCC_ENTHDR2 = 0x22, + I3C_CCC_ENTHDR3 = 0x23, + I3C_CCC_ENTHDR4 = 0x24, + I3C_CCC_ENTHDR5 = 0x25, + I3C_CCC_ENTHDR6 = 0x26, + I3C_CCC_ENTHDR7 = 0x27, + I3C_CCC_SETXTIME = 0x28, + I3C_CCC_SETAASA = 0x29, + I3C_CCC_RSTACT = 0x2a, + I3C_CCC_DEFGRPA = 0x2b, + I3C_CCC_RSTGRPA = 0x2c, + I3C_CCC_MLANE = 0x2d, + /* Direct CCCs */ + I3C_CCCD_ENEC = 0x80, + I3C_CCCD_DISEC = 0x81, + I3C_CCCD_ENTAS0 = 0x82, + I3C_CCCD_ENTAS1 = 0x83, + I3C_CCCD_ENTAS2 = 0x84, + I3C_CCCD_ENTAS3 = 0x85, + I3C_CCCD_SETDASA = 0x87, + I3C_CCCD_SETNEWDA = 0x88, + I3C_CCCD_SETMWL = 0x89, + I3C_CCCD_SETMRL = 0x8a, + I3C_CCCD_GETMWL = 0x8b, + I3C_CCCD_GETMRL = 0x8c, + I3C_CCCD_GETPID = 0x8d, + I3C_CCCD_GETBCR = 0x8e, + I3C_CCCD_GETDCR = 0x8f, + I3C_CCCD_GETSTATUS = 0x90, + I3C_CCCD_GETACCCR = 0x91, + I3C_CCCD_ENDXFER = 0x92, + I3C_CCCD_SETBRGTGT = 0x93, + I3C_CCCD_GETMXDS = 0x94, + I3C_CCCD_GETCAPS = 0x95, + I3C_CCCD_SETROUTE = 0x96, + I3C_CCCD_SETXTIME = 0x98, + I3C_CCCD_GETXTIME = 0x99, + I3C_CCCD_RSTACT = 0x9a, + I3C_CCCD_SETGRPA = 0x9b, + I3C_CCCD_RSTGRPA = 0x9c, + I3C_CCCD_MLANE = 0x9d, +} I3CCCC; + +#define CCC_IS_DIRECT(_ccc) (_ccc & 0x80) + +#define I3C_BROADCAST 0x7e +#define I3C_HJ_ADDR 0x02 +#define I3C_ENTDAA_SIZE 8 + +struct I3CTargetClass { + DeviceClass parent_class; + + /* + * Controller to target. Returns 0 for success, non-zero for NAK or other + * error. + */ + int (*send)(I3CTarget *s, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent); + /* + * Target to controller. I3C targets are able to terminate reads early, so + * this returns the number of bytes read from the target. + */ + uint32_t (*recv)(I3CTarget *s, uint8_t *data, uint32_t num_to_read); + /* Notify the target of a bus state change. */ + int (*event)(I3CTarget *s, enum I3CEvent event); + /* + * Handle a read CCC transmitted from a controller. + * CCCs are I3C commands that I3C targets support. + * The target can NACK the CCC if it does not support it. + */ + int (*handle_ccc_read)(I3CTarget *s, uint8_t *data, uint32_t num_to_read, + uint32_t *num_read); + /* + * Handle a write CCC transmitted from a controller. + * CCCs are I3C commands that I3C targets support. + * The target can NACK the CCC if it does not support it. + */ + int (*handle_ccc_write)(I3CTarget *s, const uint8_t *data, + uint32_t num_to_send, uint32_t *num_sent); + + /* + * Matches and adds the candidate if the address matches the candidate's + * address. + * Returns true if the address matched, or if this was a broadcast, and + * updates the device list. Otherwise returns false. + */ + bool (*target_match)(I3CTarget *candidate, uint8_t address, bool is_read, + bool broadcast, bool in_entdaa); +}; + +struct I3CTarget { + DeviceState parent_obj; + + uint8_t address; + uint8_t static_address; + uint8_t dcr; + uint8_t bcr; + uint64_t pid; + + /* CCC State tracking. */ + I3CCCC curr_ccc; + uint8_t ccc_byte_offset; + bool in_ccc; + bool in_test_mode; +}; + +struct I3CNode { + I3CTarget *target; + QLIST_ENTRY(I3CNode) next; +}; + +typedef struct I3CNode I3CNode; + +typedef QLIST_HEAD(I3CNodeList, I3CNode) I3CNodeList; + +#define TYPE_I3C_BUS "i3c-bus" +OBJECT_DECLARE_TYPE(I3CBus, I3CBusClass, I3C_BUS) + +struct I3CBus { + BusState parent_obj; + + /* Legacy I2C. */ + I2CBus *i2c_bus; + + I3CNodeList current_devs; + bool broadcast; + uint8_t ccc; + bool in_ccc; + bool in_entdaa; + uint8_t saved_address; +}; + +struct I3CBusClass { + BusClass parent_class; + + /* Handle an incoming IBI request from a target */ + int (*ibi_handle) (I3CBus *bus, uint8_t addr, bool is_recv); + /* Receive data from an IBI request */ + int (*ibi_recv) (I3CBus *bus, uint8_t data); + /* Do anything that needs to be done, since the IBI is finished. */ + int (*ibi_finish) (I3CBus *bus); +}; + +I3CBus *i3c_init_bus(DeviceState *parent, const char *name); +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent, + const char *name); +void i3c_set_target_address(I3CTarget *dev, uint8_t address); +bool i3c_bus_busy(I3CBus *bus); + +/* + * Start a transfer on an I3C bus. + * If is_recv is known at compile-time (i.e. a device will always be sending or + * will always be receiving at a certain point), prefer to use i3c_start_recv or + * i3c_start_send instead. + * + * Returns 0 on success, non-zero on an error. + */ +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv); + +/* + * Start a receive transfer on an I3C bus. + * + * Returns 0 on success, non-zero on an error + */ +int i3c_start_recv(I3CBus *bus, uint8_t address); + +/* + * Start a send transfer on an I3C bus. + * + * Returns 0 on success, non-zero on an error + */ +int i3c_start_send(I3CBus *bus, uint8_t address); + +void i3c_end_transfer(I3CBus *bus); +void i3c_nack(I3CBus *bus); +int i3c_send_byte(I3CBus *bus, uint8_t data); +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send, + uint32_t *num_sent); +/* + * I3C receives can only NACK on a CCC. The target should NACK a CCC it does not + * support. + */ +int i3c_recv_byte(I3CBus *bus, uint8_t *data); +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read, + uint32_t *num_read); +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event); +int i3c_do_entdaa(I3CBus *bus, uint8_t address, uint64_t *pid, uint8_t *bcr, + uint8_t *dcr); +int i3c_start_device_transfer(I3CTarget *dev, int send_length); +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address, + enum I3CEvent event); +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv); +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data); +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data); + +/* + * Legacy I2C functions. + * + * These are wrapper for I2C functions that take in an I3C bus instead of an I2C + * bus. Internally they use the I2C bus (and devices attached to it) that's a + * part of the I3C bus + */ +void legacy_i2c_nack(I3CBus *bus); +uint8_t legacy_i2c_recv(I3CBus *bus); +int legacy_i2c_send(I3CBus *bus, uint8_t data); +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv); +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address); +int legacy_i2c_start_send(I3CBus *bus, uint8_t address); +void legacy_i2c_end_transfer(I3CBus *bus); +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char *name, + uint8_t addr); + +/** + * Create an I3C Target. + * + * The target returned from this function still needs to be realized. + */ +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr, + uint8_t bcr, uint64_t pid); + +/** + * Create and realize an I3C target. + * + * Create the target, initialize it, put it on the specified I3C bus, and + * realize it. + */ +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name, + uint8_t addr, uint8_t dcr, uint8_t bcr, + uint64_t pid); + +/* Realize and drop the reference count on an I3C target. */ +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp); + +#endif /* QEMU_INCLUDE_HW_I3C_I3C_H_ */