diff --git a/hw/opentitan/ot_usbdev.c b/hw/opentitan/ot_usbdev.c index 4230547db07a..b23ecc475ea0 100644 --- a/hw/opentitan/ot_usbdev.c +++ b/hw/opentitan/ot_usbdev.c @@ -39,6 +39,7 @@ #include "qemu/log.h" #include "chardev/char-fe.h" #include "hw/opentitan/ot_alert.h" +#include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_fifo32.h" #include "hw/opentitan/ot_usbdev.h" #include "hw/qdev-properties-system.h" @@ -63,6 +64,9 @@ #define OT_USBDEV_MAX_PACKET_SIZE 64u #define OT_USBDEV_BUFFER_COUNT 32u +/* Time in milliseconds for a bus reset to complete. */ +#define USBDEV_BUS_RESET_TIME_MS 10 + /* Standard constants */ #define OT_USBDEV_SETUP_PACKET_SIZE 8u @@ -527,6 +531,11 @@ struct OtUsbdevState { CharBackend usb_chr; OtUsbdevServer usb_server; + /* Timer to handle bus reset. */ + QEMUTimer bus_reset_timer; + /* A bus reset is in progress */ + bool bus_reset_in_progress; + char *ot_id; /* Link to the clkmgr. */ DeviceState *clock_src; @@ -606,6 +615,8 @@ static void ot_usbdev_complete_transfer_out(OtUsbdevState *s, uint8_t epnum, OtUsbdevServerTransferStatus status, const char *msg); static bool ot_usbdev_is_enabled(const OtUsbdevState *s); +static void +ot_usbdev_cancel_all_transfers(OtUsbdevState *s, const char *reason); /* * Update the INTR_STATE register for status interrupts. @@ -630,6 +641,10 @@ static void ot_usbdev_update_status_irqs(OtUsbdevState *s) set_mask |= USBDEV_INTR_PKT_RECEIVED_MASK; } } + /* + * @todo The BFM says that 'Disconnected' is held at 1 during IP + * reset, need to investigate this detail. + */ s->regs[R_USBDEV_INTR_STATE] |= set_mask; } @@ -990,6 +1005,8 @@ static void ot_usbdev_update_vbus(OtUsbdevState *s) s->regs[R_USBDEV_INTR_STATE] |= USBDEV_INTR_DISCONNECTED_MASK; s->regs[R_USBCTRL] = FIELD_DP32(s->regs[R_USBCTRL], USBCTRL, DEVICE_ADDRESS, 0u); + + ot_usbdev_cancel_all_transfers(s, "VBUS was disconnected"); } } @@ -1030,6 +1047,22 @@ static void ot_usbdev_retire_in_packets(OtUsbdevState *s, uint8_t ep) s->regs[R_CONFIGIN_0 + ep] = configin; } +/* + * Callback to notify bus reset completion. The timer is started + * in ot_usbdev_simulate_link_reset. + */ +static void ot_usbdev_link_reset_complete(void *opaque) +{ + OtUsbdevState *s = opaque; + s->bus_reset_in_progress = false; + + trace_ot_usbdev_link_reset(s->ot_id, true); + + if (ot_usbdev_has_vbus(s) && ot_usbdev_is_enabled(s)) { + ot_usbdev_set_raw_link_state(s, OT_USBDEV_LINK_STATE_ACTIVE_NOSOF); + } +} + /* * Simulate a link reset. * @@ -1039,8 +1072,9 @@ static void ot_usbdev_retire_in_packets(OtUsbdevState *s, uint8_t ep) static void ot_usbdev_simulate_link_reset(OtUsbdevState *s) { g_assert(!resettable_is_in_reset(OBJECT(s))); + g_assert(!s->bus_reset_in_progress); - trace_ot_usbdev_link_reset(s->ot_id); + trace_ot_usbdev_link_reset(s->ot_id, false); /* We cannot simulate a reset if the device is disconnected! */ if (ot_usbdev_get_link_state(s) == OT_USBDEV_LINK_STATE_DISCONNECTED) { @@ -1053,28 +1087,22 @@ static void ot_usbdev_simulate_link_reset(OtUsbdevState *s) s->regs[R_USBCTRL] = FIELD_DP32(s->regs[R_USBCTRL], USBCTRL, DEVICE_ADDRESS, 0u); s->regs[R_USBDEV_INTR_STATE] |= USBDEV_INTR_LINK_RESET_MASK; + ot_usbdev_update_irqs(s); for (unsigned ep = 0; ep < USBDEV_PARAM_N_ENDPOINTS; ep++) { /* Cancel any pending IN packets */ ot_usbdev_retire_in_packets(s, ep); - /* Cancel all pending transfers on the server */ - ot_usbdev_complete_transfer_in(s, ep, OT_USBDEV_SERVER_STATUS_CANCELLED, - "cancelled by link reset"); - ot_usbdev_complete_transfer_out(s, ep, - OT_USBDEV_SERVER_STATUS_CANCELLED, - "cancelled by link reset"); } + ot_usbdev_cancel_all_transfers(s, "cancelled by link reset"); - if (ot_usbdev_has_vbus(s) && ot_usbdev_is_enabled(s)) { - /* - * @todo BFM has some extra state processing but it seems incorrect - * @todo If we start tracking frames, this should transition the link - * state to ACTIVE_NOSOF and then simulate a transition to ACTIVE on - * the next SOF. - */ - ot_usbdev_set_raw_link_state(s, OT_USBDEV_LINK_STATE_ACTIVE); - } - ot_usbdev_update_irqs(s); + /* + * On real hardware, reset signalling typically takes several milliseconds + * so we kick a timer to trigger the link state change after the end of + * signalling. + */ + s->bus_reset_in_progress = true; + int64_t now = qemu_clock_get_ms(OT_VIRTUAL_CLOCK); + timer_mod(&s->bus_reset_timer, now + USBDEV_BUS_RESET_TIME_MS); } /* @@ -1229,6 +1257,21 @@ void ot_usbdev_complete_transfer_out(OtUsbdevState *s, uint8_t epnum, memset(xfer, 0, sizeof(OtUsbdevServerEpOutXfer)); } +/* + * Cancel all pending transfers + */ +void ot_usbdev_cancel_all_transfers(OtUsbdevState *s, const char *reason) +{ + for (unsigned ep = 0; ep < USBDEV_PARAM_N_ENDPOINTS; ep++) { + /* Cancel all pending transfers on the server */ + ot_usbdev_complete_transfer_in(s, ep, OT_USBDEV_SERVER_STATUS_CANCELLED, + reason); + ot_usbdev_complete_transfer_out(s, ep, + OT_USBDEV_SERVER_STATUS_CANCELLED, + reason); + } +} + /* * Try to make progress with an IN transfer on this endpoint. * @@ -2337,6 +2380,14 @@ static int ot_usbdev_chr_usb_can_receive(void *opaque) { OtUsbdevState *s = opaque; + /* + * If bus reset signalling is in progress, delay + * reception until it is done. + */ + if (s->bus_reset_in_progress) { + return 0; + } + /* Return remaining size in the buffer. */ return (int)s->usb_server.recv_rem; } @@ -2446,24 +2497,6 @@ static void ot_usbdev_chr_cmd_receive(void *opaque, const uint8_t *buf, /* * QEMU Initialization */ - -static const MemoryRegionOps ot_usbdev_ops = { - .read = &ot_usbdev_read, - .write = &ot_usbdev_write, - .endianness = DEVICE_NATIVE_ENDIAN, - .impl.min_access_size = sizeof(uint32_t), - .impl.max_access_size = sizeof(uint32_t), -}; - -static const MemoryRegionOps ot_usbdev_buffer_ops = { - .read = &ot_usbdev_buffer_read, - .write = &ot_usbdev_buffer_write, - .endianness = DEVICE_NATIVE_ENDIAN, - /* @todo The RTL probably supports sub-word reads, implement this */ - .impl.min_access_size = sizeof(uint32_t), - .impl.max_access_size = sizeof(uint32_t), -}; - static Property ot_usbdev_properties[] = { DEFINE_PROP_STRING("ot_id", OtUsbdevState, ot_id), DEFINE_PROP_STRING("clock-name", OtUsbdevState, usbclk_name), @@ -2485,30 +2518,22 @@ static Property ot_usbdev_properties[] = { DEFINE_PROP_END_OF_LIST(), }; -static void ot_usbdev_realize(DeviceState *dev, Error **errp) -{ - OtUsbdevState *s = OT_USBDEV(dev); - (void)errp; - - /* @todo: clarify if we need to track events/backend changes. */ - qemu_chr_fe_set_handlers(&s->cmd_chr, &ot_usbdev_chr_cmd_can_receive, - &ot_usbdev_chr_cmd_receive, NULL, NULL, s, NULL, - true); - - qemu_chr_fe_set_handlers(&s->usb_chr, &ot_usbdev_chr_usb_can_receive, - &ot_usbdev_chr_usb_receive, - &ot_usbdev_chr_usb_event_handler, NULL, s, NULL, - true); - - g_assert(s->ot_id); - g_assert(s->usbclk_name); - g_assert(s->aonclk_name); +static const MemoryRegionOps ot_usbdev_ops = { + .read = &ot_usbdev_read, + .write = &ot_usbdev_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = sizeof(uint32_t), + .impl.max_access_size = sizeof(uint32_t), +}; - /* If not in VBUS override mode, the VBUS gate starts on by default. */ - if (!s->vbus_override) { - s->vbus_gate = true; - } -} +static const MemoryRegionOps ot_usbdev_buffer_ops = { + .read = &ot_usbdev_buffer_read, + .write = &ot_usbdev_buffer_write, + .endianness = DEVICE_NATIVE_ENDIAN, + /* @todo The RTL probably supports sub-word reads, implement this */ + .impl.min_access_size = sizeof(uint32_t), + .impl.max_access_size = sizeof(uint32_t), +}; /* * QEMU reset handling @@ -2525,7 +2550,17 @@ static void ot_usbdev_reset_enter(Object *obj, ResetType type) c->parent_phases.enter(obj, type); } - /* @todo cancel everything here. */ + ot_usbdev_cancel_all_transfers(s, "device going into reset"); + + /* + * If there is a bus reset in progress, we cancel it. + * The rationale is that on a real bus, the device + * reset would cause the block to disable the pullup + * so the host would notice the disconnection and stop + * reset signalling. + */ + timer_del(&s->bus_reset_timer); + s->bus_reset_in_progress = false; /* See BFM (dut_reset) */ memset(s->regs, 0u, sizeof(s->regs)); @@ -2534,10 +2569,7 @@ static void ot_usbdev_reset_enter(Object *obj, ResetType type) fifo8_reset(&s->av_setup_fifo); fifo8_reset(&s->av_out_fifo); - /* - * @todo The BFM says that 'Disconnected' is held at 1 during IP - * reset, need to investigate this detail. - */ + ot_usbdev_update_status_irqs(s); ot_usbdev_update_irqs(s); } @@ -2556,6 +2588,31 @@ static void ot_usbdev_reset_exit(Object *obj, ResetType type) ot_usbdev_update_fifos_status(s); } +static void ot_usbdev_realize(DeviceState *dev, Error **errp) +{ + OtUsbdevState *s = OT_USBDEV(dev); + (void)errp; + + /* @todo: clarify if we need to track events/backend changes. */ + qemu_chr_fe_set_handlers(&s->cmd_chr, &ot_usbdev_chr_cmd_can_receive, + &ot_usbdev_chr_cmd_receive, NULL, NULL, s, NULL, + true); + + qemu_chr_fe_set_handlers(&s->usb_chr, &ot_usbdev_chr_usb_can_receive, + &ot_usbdev_chr_usb_receive, + &ot_usbdev_chr_usb_event_handler, NULL, s, NULL, + true); + + g_assert(s->ot_id); + g_assert(s->usbclk_name); + g_assert(s->aonclk_name); + + /* If not in VBUS override mode, the VBUS gate starts on by default. */ + if (!s->vbus_override) { + s->vbus_gate = true; + } +} + static void ot_usbdev_init(Object *obj) { OtUsbdevState *s = OT_USBDEV(obj); @@ -2580,6 +2637,9 @@ static void ot_usbdev_init(Object *obj) ot_fifo32_create(&s->rx_fifo, OT_USBDEV_RX_FIFO_DEPTH); fifo8_create(&s->av_out_fifo, OT_USBDEV_AV_OUT_FIFO_DEPTH); fifo8_create(&s->av_setup_fifo, OT_USBDEV_AV_SETUP_FIFO_DEPTH); + + timer_init_ms(&s->bus_reset_timer, OT_VIRTUAL_CLOCK, + &ot_usbdev_link_reset_complete, s); } static void ot_usbdev_class_init(ObjectClass *klass, void *data) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 1425460dd3f2..3ed8b02e2d04 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -653,7 +653,7 @@ ot_usbdev_io_buffer_write(const char *id, uint32_t addr, uint32_t val, uint32_t ot_usbdev_io_read_out(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_usbdev_io_write(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%x, pc=0x%x" ot_usbdev_irqs(const char *id, uint32_t active, uint32_t mask, uint32_t eff) "%s: act:0x%08x msk:0x%08x eff:0x%08x" -ot_usbdev_link_reset(const char *id) "%s" +ot_usbdev_link_reset(const char *id, bool completed) "%s: completed=%u" ot_usbdev_packet_received(const char *id, uint8_t ep, uint8_t bufid, uint32_t size) "%s: ep=%u, buf_id=%u, size=%u" ot_usbdev_packet_sent(const char *id, uint8_t ep, uint8_t bufid, uint32_t size) "%s: ep=%u, buf_id=%u, size=%u" ot_usbdev_link_state_changed(const char *id, const char *state) "%s: link_state=%s"