/* * QEMU I3C bus interface. * * Copyright 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/log.h" #include "qapi/error.h" #include "trace.h" #include "hw/i3c/i3c.h" #include "hw/hotplug.h" #include "hw/qdev-properties.h" static 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), DEFINE_PROP_END_OF_LIST(), }; static void i3c_realize(BusState *bus, Error **errp) { qbus_set_bus_hotplug_handler(bus); } static void i3c_class_init(ObjectClass *klass, void *data) { BusClass *k = BUS_CLASS(klass); k->realize = i3c_realize; } static const TypeInfo i3c_bus_info = { .name = TYPE_I3C_BUS, .parent = TYPE_BUS, .instance_size = sizeof(I3CBus), .class_size = sizeof(I3CBusClass), .class_init = i3c_class_init, .interfaces = (InterfaceInfo[]) { { TYPE_HOTPLUG_HANDLER }, { } } }; 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); } bool i3c_target_match(I3CBus *bus, I3CTarget *target, uint8_t address) { /* Once a target has a dynamic address, it only responds to that. */ uint8_t targ_addr = target->address ? target->address : target->static_address; if (bus->in_entdaa) { if (address != I3C_BROADCAST) { qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address 0x%.2x sent during " "ENTDAA instead of a broadcast address\n", object_get_canonical_path(OBJECT(bus)), address); return false; } /* * Targets should only ACK ENTDAA broadcasts if they have no dynamic * address. */ if (target->address == 0) { I3CNode *node = g_new(struct I3CNode, 1); node->target = target; QLIST_INSERT_HEAD(&bus->current_devs, node, next); } return target->address == 0; } if ((targ_addr == address) || bus->broadcast) { I3CNode *node = g_new(struct I3CNode, 1); node->target = target; QLIST_INSERT_HEAD(&bus->current_devs, node, next); return true; } return false; } bool i3c_scan_bus(I3CBus *bus, uint8_t address) { 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->qbus.children, sibling) { DeviceState *qdev = child->child; I3CTarget *target = I3C_TARGET(qdev); if (i3c_target_match(bus, target, address)) { 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)) { 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->qbus.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; /* 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; switch (t->curr_ccc) { case I3C_CCC_ENTDAA: /* 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] = (t->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->qdev.parent_bus); I3CBusClass *bc = I3C_BUS_GET_CLASS(bus); trace_i3c_target_send_ibi(addr, is_recv); return bc->ibi_handle(bus, t, addr, is_recv); } int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data) { I3CBus *bus = I3C_BUS(t->qdev.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->qdev.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)) { qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created with reserved " "address 0x%.2x\n", object_get_canonical_path(OBJECT(dev)), addr); } return I3C_TARGET(dev); } bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error **errp) { return qdev_realize_and_unref(&dev->qdev, &bus->qbus, 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, void *data) { DeviceClass *k = DEVICE_CLASS(klass); set_bit(DEVICE_CATEGORY_MISC, k->categories); k->bus_type = TYPE_I3C_BUS; device_class_set_props(k, i3c_props); } static const TypeInfo i3c_target_type_info = { .name = TYPE_I3C_TARGET, .parent = TYPE_DEVICE, .instance_size = sizeof(I3CTarget), .abstract = true, .class_size = sizeof(I3CTargetClass), .class_init = i3c_target_class_init, }; static void i3c_register_types(void) { type_register_static(&i3c_bus_info); type_register_static(&i3c_target_type_info); } type_init(i3c_register_types)