// RAM-only native-SIE address-retargeting experiment. These are vendor test // devices, not controllers. No usbd/tud global-device state is linked here. #include "usb_probe.h" #include "router.h" #include #include #include #include "device/dcd.h" #include "hardware/clocks.h" #include "hardware/structs/usb.h" #include "hardware/sync.h" #include "hardware/uart.h" #include "hardware/watchdog.h" #include "pico/stdlib.h" #define RHPORT 0u #define EP0_OUT 0x00u #define EP0_IN 0x80u #define HUB_EP 0x81u #define EP0_SIZE 64u #define EVENT_CAPACITY 32u #define PORT_COUNT 2u #define PORT_CONNECTION 0x0001u #define PORT_ENABLE 0x0002u #define PORT_SUSPEND 0x0004u #define PORT_RESET 0x0010u #define PORT_POWER 0x0100u #define C_CONNECTION 0x0001u #define C_ENABLE 0x0002u #define C_SUSPEND 0x0004u #define C_RESET 0x0010u enum { FEATURE_PORT_ENABLE = 1, FEATURE_PORT_SUSPEND = 2, FEATURE_PORT_RESET = 4, FEATURE_PORT_POWER = 8, FEATURE_C_CONNECTION = 16, FEATURE_C_ENABLE = 17, FEATURE_C_SUSPEND = 18, FEATURE_C_OVERCURRENT = 19, FEATURE_C_RESET = 20, }; typedef enum { CTRL_IDLE, CTRL_DATA_IN, CTRL_STATUS_IN, CTRL_STATUS_OUT, CTRL_STALLED, } control_stage; typedef enum { ACTION_NONE, ACTION_ADDRESS, ACTION_CONFIGURATION, ACTION_INTERFACE, ACTION_HALT, ACTION_CLEAR_HALT, ACTION_PORT_SET, ACTION_PORT_CLEAR, ACTION_KEEPALIVE, ACTION_ARM, ACTION_REBOOT, } control_action; typedef struct { uint16_t status; uint16_t change; uint32_t reset_deadline; uint32_t resume_deadline; bool resetting; bool resuming; } hub_port; typedef struct { dcd_event_t event; uint32_t generation; uint32_t endpoint_epoch; uint8_t setup_slot; } queued_event; typedef struct { tusb_control_request_t request; uint32_t generation; uint16_t length; uint16_t sent; uint16_t packet_length; uint8_t owner; control_stage stage; control_action action; bool need_zlp; } control_transfer; static uint8_t addresses[PROBE_ROUTER_SLOTS]; static uint8_t configurations[PROBE_ROUTER_SLOTS]; static uint8_t default_slot; static bool routing_enabled; static hub_port ports[PORT_COUNT]; static control_transfer control; static uint8_t control_data[128] TU_ATTR_ALIGNED(4); // Even a malformed nonempty status OUT cannot make the DCD copy into NULL. static uint8_t control_out[EP0_SIZE] TU_ATTR_ALIGNED(4); static uint32_t setup_count[PROBE_ROUTER_SLOTS]; static uint32_t bad_setup_owner; static uint32_t correlated_setups; static uint32_t system_clock_hz; static bool interrupt_open; static bool interrupt_pending; static bool interrupt_halted; static uint8_t interrupt_bitmap; static uint32_t endpoint_epoch; static bool reboot_pending; static bool failed; // Only the DCD IRQ produces; only Core 0's task consumes. All task-side DCD // operations run with USB IRQ disabled. The IRQ never rearms a transfer: this // SDK resets its transfer state *after* invoking dcd_event_handler(). static queued_event events[EVENT_CAPACITY]; static volatile uint32_t event_head; static volatile uint32_t event_tail; static volatile uint32_t event_generation; static volatile bool event_overflow; static uint32_t observed_setup_sequence; static const uint8_t hub_configuration[] = { 9, 2, 25, 0, 1, 1, 0, 0x80, 50, 9, 4, 0, 0, 1, 9, 0, 0, 0, 7, 5, HUB_EP, 3, 1, 0, 12, }; static const uint8_t child_configuration[] = { 9, 2, 18, 0, 1, 1, 0, 0x80, 0, 9, 4, 0, 0, 0, 0xff, 0, 0, 0, }; static const uint8_t hub_descriptor[] = { // Individual logical port power, no overcurrent sensing, 10ms power-good. // Both embedded vendor children are non-removable; USB 1.1 full-speed hub. 9, 0x29, PORT_COUNT, 0x11, 0, 5, 100, 0x06, 0xff, }; static const tusb_desc_endpoint_t hub_endpoint = { .bLength = 7, .bDescriptorType = TUSB_DESC_ENDPOINT, .bEndpointAddress = HUB_EP, .bmAttributes = { .xfer = TUSB_XFER_INTERRUPT }, .wMaxPacketSize = 1, .bInterval = 12, }; static void put16(uint8_t* out, uint16_t value) { out[0] = (uint8_t)value; out[1] = (uint8_t)(value >> 8); } static void put32(uint8_t* out, uint32_t value) { put16(out, (uint16_t)value); put16(out + 2, (uint16_t)(value >> 16)); } static void publish_addresses(void) { probe_router_publish(addresses, default_slot); } static void stall_control(void) { control.stage = CTRL_STALLED; control.action = ACTION_NONE; dcd_edpt_stall(RHPORT, EP0_OUT); dcd_edpt_stall(RHPORT, EP0_IN); } static bool queue_control(uint8_t endpoint, uint8_t* data, uint16_t length) { if (dcd_edpt_xfer(RHPORT, endpoint, data, length)) return true; stall_control(); return false; } static void status_in(control_action action) { control.action = action; control.stage = CTRL_STATUS_IN; queue_control(EP0_IN, control_out, 0); } static void next_control_packet(void) { uint16_t remaining = (uint16_t)(control.length - control.sent); control.packet_length = remaining > EP0_SIZE ? EP0_SIZE : remaining; if (remaining == 0) control.need_zlp = false; control.stage = CTRL_DATA_IN; queue_control(EP0_IN, control_data + control.sent, control.packet_length); } static void reply_data(uint16_t length) { control.length = length < control.request.wLength ? length : control.request.wLength; control.sent = 0; control.need_zlp = length < control.request.wLength && (length % EP0_SIZE) == 0; if (control.request.wLength == 0) { control.stage = CTRL_STATUS_OUT; queue_control(EP0_OUT, control_out, 0); } else { next_control_packet(); } } static void reply_copy(const uint8_t* data, uint16_t length) { memcpy(control_data, data, length); reply_data(length); } static void reply_word(uint16_t value, uint16_t length) { put16(control_data, value); reply_data(length); } static uint8_t changed_ports(void) { uint8_t bitmap = 0; for (unsigned i = 0; i < PORT_COUNT; ++i) { if (ports[i].change) bitmap |= (uint8_t)(1u << (i + 1)); } return bitmap; } static void arm_interrupt(void) { if (!interrupt_open || interrupt_pending || interrupt_halted) return; interrupt_bitmap = changed_ports(); if (!interrupt_bitmap) return; // NAK until a hub/port change exists. interrupt_pending = dcd_edpt_xfer(RHPORT, HUB_EP, &interrupt_bitmap, 1); if (!interrupt_pending) failed = true; } static void close_interrupt(void) { ++endpoint_epoch; dcd_edpt_close_all(RHPORT); interrupt_open = false; interrupt_pending = false; interrupt_halted = false; } static void open_interrupt(void) { close_interrupt(); interrupt_open = dcd_edpt_open(RHPORT, &hub_endpoint); if (!interrupt_open) failed = true; } static void forget_child(unsigned port) { uint8_t slot = (uint8_t)(port + 1); addresses[slot] = PROBE_ROUTER_UNASSIGNED; configurations[slot] = 0; if (default_slot == slot) default_slot = PROBE_ROUTER_UNASSIGNED; } static void reset_bus_state(void) { probe_router_enable(false); routing_enabled = false; correlated_setups = 0; addresses[0] = 0; addresses[1] = PROBE_ROUTER_UNASSIGNED; addresses[2] = PROBE_ROUTER_UNASSIGNED; default_slot = 0; memset(configurations, 0, sizeof(configurations)); memset(ports, 0, sizeof(ports)); memset(&control, 0, sizeof(control)); interrupt_open = false; interrupt_pending = false; interrupt_halted = false; ++endpoint_epoch; publish_addresses(); usb_hw->dev_addr_ctrl = 0; } static void fill_stats(void) { probe_router_stats router; probe_router_snapshot(&router); const uint32_t words[32] = { 0x42554850u, 3u, system_clock_hz, routing_enabled, addresses[0], addresses[1], addresses[2], default_slot, setup_count[0], setup_count[1], setup_count[2], bad_setup_owner, router.ready, router.sops, router.sync_ok, router.valid_tokens, router.valid_setups, router.crc_errors, router.late_samples, router.retargets, router.address_hits[0], router.address_hits[1], router.address_hits[2], router.cycles_per_bit, router.last_raw[0], router.last_raw[1], router.last_raw[2], router.last_raw_count, router.last_raw_eop, router.last_raw_late, usb_hw->phy_direct, correlated_setups, }; for (unsigned i = 0; i < 32; ++i) put32(control_data + 4 * i, words[i]); } static bool get_descriptor(void) { const tusb_control_request_t* request = &control.request; uint8_t type = (uint8_t)(request->wValue >> 8); uint8_t index = (uint8_t)request->wValue; if (type == TUSB_DESC_DEVICE && index == 0 && request->wIndex == 0) { uint8_t descriptor[] = { 18, 1, 0x10, 0x01, 0, 0, 0, EP0_SIZE, 0x09, 0x12, 0, 0, 0x00, 0x01, 1, 2, 3, 1, }; descriptor[4] = control.owner == 0 ? 9 : 0; descriptor[10] = (uint8_t)(control.owner + 1); reply_copy(descriptor, sizeof(descriptor)); return true; } if (type == TUSB_DESC_CONFIGURATION && index == 0 && request->wIndex == 0) { if (control.owner == 0) reply_copy(hub_configuration, sizeof(hub_configuration)); else reply_copy(child_configuration, sizeof(child_configuration)); return true; } if (type != TUSB_DESC_STRING) return false; if (index == 0 && request->wIndex == 0) { static const uint8_t languages[] = {4, 3, 0x09, 0x04}; reply_copy(languages, sizeof(languages)); return true; } if (request->wIndex != 0x0409) return false; const char* text; if (index == 1) text = "Native USB capability probe"; else if (index == 2) { static const char* const products[] = { "RP2350 native hub probe", "RP2350 vendor probe child 1", "RP2350 vendor probe child 2", }; text = products[control.owner]; } else if (index == 3) { static const char* const serials[] = {"PHUB-ROOT", "PHUB-CHILD1", "PHUB-CHILD2"}; text = serials[control.owner]; } else return false; uint16_t length = (uint16_t)strlen(text); control_data[0] = (uint8_t)(2 + 2 * length); control_data[1] = TUSB_DESC_STRING; for (uint16_t i = 0; i < length; ++i) put16(control_data + 2 + 2 * i, (uint8_t)text[i]); reply_data((uint16_t)(2 + 2 * length)); return true; } static bool endpoint_exists(uint16_t index) { return index == EP0_OUT || index == EP0_IN || (index == HUB_EP && control.owner == 0 && configurations[0] == 1); } static bool standard_request(void) { const tusb_control_request_t* request = &control.request; uint8_t slot = control.owner; switch (request->bRequest) { case TUSB_REQ_GET_DESCRIPTOR: return request->bmRequestType == 0x80 && get_descriptor(); case TUSB_REQ_SET_ADDRESS: if (request->bmRequestType != 0 || request->wValue > 127 || request->wIndex || request->wLength || configurations[slot]) return false; if (request->wValue == 0 && default_slot != PROBE_ROUTER_UNASSIGNED && default_slot != slot) return false; for (unsigned i = 0; i < PROBE_ROUTER_SLOTS; ++i) { if (i != slot && addresses[i] == request->wValue) return false; } status_in(ACTION_ADDRESS); return true; case TUSB_REQ_GET_CONFIGURATION: if (request->bmRequestType != 0x80 || request->wValue || request->wIndex || request->wLength != 1) return false; reply_word(configurations[slot], 1); return true; case TUSB_REQ_SET_CONFIGURATION: if (request->bmRequestType != 0 || request->wValue > 1 || request->wIndex || request->wLength || addresses[slot] == 0 || addresses[slot] == PROBE_ROUTER_UNASSIGNED) return false; status_in(ACTION_CONFIGURATION); return true; case TUSB_REQ_GET_STATUS: if (request->wValue || request->wLength != 2) return false; if (request->bmRequestType == 0x80 && request->wIndex == 0) { reply_word(0, 2); // Bus powered; no remote wakeup capability. return true; } if (request->bmRequestType == 0x81 && request->wIndex == 0 && configurations[slot]) { reply_word(0, 2); return true; } if (request->bmRequestType == 0x82 && endpoint_exists(request->wIndex)) { reply_word(request->wIndex == HUB_EP && interrupt_halted ? 1 : 0, 2); return true; } return false; case TUSB_REQ_CLEAR_FEATURE: case TUSB_REQ_SET_FEATURE: if (request->bmRequestType != 0x02 || request->wValue != 0 || request->wIndex != HUB_EP || request->wLength || slot != 0 || !configurations[0]) return false; status_in(request->bRequest == TUSB_REQ_SET_FEATURE ? ACTION_HALT : ACTION_CLEAR_HALT); return true; case TUSB_REQ_GET_INTERFACE: if (request->bmRequestType != 0x81 || request->wValue || request->wIndex || request->wLength != 1 || !configurations[slot]) return false; reply_word(0, 1); return true; case TUSB_REQ_SET_INTERFACE: if (request->bmRequestType != 0x01 || request->wValue || request->wIndex || request->wLength || !configurations[slot]) return false; status_in(ACTION_INTERFACE); return true; default: return false; } } static bool hub_request(void) { const tusb_control_request_t* request = &control.request; if (control.owner != 0) return false; if (request->bmRequestType == 0xa0 && request->bRequest == TUSB_REQ_GET_DESCRIPTOR && request->wValue == 0x2900 && request->wIndex == 0) { reply_copy(hub_descriptor, sizeof(hub_descriptor)); return true; } if (!configurations[0]) return false; if (request->bmRequestType == 0xa0 && request->bRequest == TUSB_REQ_GET_STATUS && request->wValue == 0 && request->wIndex == 0 && request->wLength == 4) { put32(control_data, 0); // No local-power loss or overcurrent changes. reply_data(4); return true; } if (request->bmRequestType == 0x20 && request->bRequest == TUSB_REQ_CLEAR_FEATURE && request->wValue <= 1 && request->wIndex == 0 && request->wLength == 0) { status_in(ACTION_NONE); // Both supported hub change flags are already clear. return true; } if (request->wIndex < 1 || request->wIndex > PORT_COUNT) return false; hub_port* port = &ports[request->wIndex - 1]; if (request->bmRequestType == 0xa3 && request->bRequest == TUSB_REQ_GET_STATUS && request->wValue == 0 && request->wLength == 4) { put16(control_data, port->status); put16(control_data + 2, port->change); reply_data(4); return true; } if (request->bmRequestType != 0x23 || request->wLength) return false; if (request->bRequest == TUSB_REQ_SET_FEATURE) { switch (request->wValue) { case FEATURE_PORT_POWER: break; case FEATURE_PORT_RESET: if (!routing_enabled || (port->status & (PORT_CONNECTION | PORT_POWER)) != (PORT_CONNECTION | PORT_POWER)) return false; // The one physical SIE cannot own two simultaneous default addresses. if (default_slot != PROBE_ROUTER_UNASSIGNED && default_slot != request->wIndex) return false; break; case FEATURE_PORT_SUSPEND: if ((port->status & (PORT_CONNECTION | PORT_ENABLE | PORT_POWER | PORT_RESET)) != (PORT_CONNECTION | PORT_ENABLE | PORT_POWER)) return false; break; default: return false; } status_in(ACTION_PORT_SET); return true; } if (request->bRequest == TUSB_REQ_CLEAR_FEATURE) { switch (request->wValue) { case FEATURE_PORT_POWER: case FEATURE_PORT_ENABLE: case FEATURE_PORT_SUSPEND: case FEATURE_C_CONNECTION: case FEATURE_C_ENABLE: case FEATURE_C_SUSPEND: case FEATURE_C_OVERCURRENT: case FEATURE_C_RESET: status_in(ACTION_PORT_CLEAR); return true; default: return false; } } return false; } static bool vendor_request(void) { const tusb_control_request_t* request = &control.request; if (request->wIndex) return false; if (request->bmRequestType == 0xc0 && request->bRequest == 0x5a && request->wValue == 0 && request->wLength == 128) { fill_stats(); control.action = ACTION_KEEPALIVE; reply_data(128); return true; } if (request->bmRequestType != 0x40 || request->wLength) return false; if (request->bRequest == 0x5b && request->wValue == 1 && control.owner == 0) { probe_router_stats router; probe_router_snapshot(&router); if (!router.ready || correlated_setups < 20) return false; status_in(ACTION_ARM); return true; } if (request->bRequest == 0x5c && request->wValue == 0) { status_in(ACTION_REBOOT); return true; } if (request->bRequest == 0x5d && !routing_enabled && probe_router_set_phase(request->wValue)) { status_in(ACTION_NONE); return true; } return false; } static void handle_setup(const queued_event* queued) { // A newer SETUP has already aborted this one's hardware transfer. if (queued->generation != event_generation) return; memset(&control, 0, sizeof(control)); control.request = queued->event.setup_received; control.generation = queued->generation; control.owner = routing_enabled ? queued->setup_slot : 0; if (routing_enabled && (control.owner >= PROBE_ROUTER_SLOTS || (addresses[control.owner] == PROBE_ROUTER_UNASSIGNED && default_slot != control.owner))) { ++bad_setup_owner; stall_control(); return; } ++setup_count[control.owner]; uint8_t type = control.request.bmRequestType & 0x60; bool supported = type == 0 ? standard_request() : type == 0x20 ? hub_request() : type == 0x40 ? vendor_request() : false; if (!supported) stall_control(); } static void apply_port_feature(bool set) { unsigned index = control.request.wIndex - 1; hub_port* port = &ports[index]; uint16_t feature = control.request.wValue; uint32_t now = time_us_32(); if (set) { if (feature == FEATURE_PORT_POWER) { port->status |= PORT_POWER; if (routing_enabled && !(port->status & PORT_CONNECTION)) { port->status |= PORT_CONNECTION; port->change |= C_CONNECTION; } } else if (feature == FEATURE_PORT_RESET) { forget_child(index); port->status = (uint16_t)((port->status | PORT_RESET) & ~(PORT_ENABLE | PORT_SUSPEND)); port->resetting = true; port->resuming = false; port->reset_deadline = now + 10000u; publish_addresses(); } else if (feature == FEATURE_PORT_SUSPEND) { port->status |= PORT_SUSPEND; port->resuming = false; } return; } if (feature >= FEATURE_C_CONNECTION && feature <= FEATURE_C_RESET) { port->change &= (uint16_t)~(1u << (feature - FEATURE_C_CONNECTION)); } else if (feature == FEATURE_PORT_ENABLE) { port->status &= (uint16_t)~(PORT_ENABLE | PORT_SUSPEND | PORT_RESET); port->resetting = false; port->resuming = false; forget_child(index); publish_addresses(); } else if (feature == FEATURE_PORT_POWER) { if (port->status & PORT_CONNECTION) port->change |= C_CONNECTION; port->status = 0; port->resetting = false; port->resuming = false; forget_child(index); publish_addresses(); } else if (feature == FEATURE_PORT_SUSPEND && (port->status & PORT_SUSPEND)) { port->resuming = true; port->resume_deadline = now + 20000u; } } static void complete_control(void) { uint8_t owner = control.owner; control_action action = control.action; control.stage = CTRL_IDLE; control.action = ACTION_NONE; switch (action) { case ACTION_ADDRESS: addresses[owner] = (uint8_t)control.request.wValue; if (addresses[owner] == 0) default_slot = owner; else if (default_slot == owner) default_slot = PROBE_ROUTER_UNASSIGNED; publish_addresses(); // Once routing is active, C1 is the sole address-register writer. // It selects the logical address from each token, after this ACK. // This prevents a C0 SET_ADDRESS completion changing the register // between another token's acceptance and its SETUP interrupt. if (!routing_enabled) dcd_edpt0_status_complete(RHPORT, &control.request); break; case ACTION_CONFIGURATION: configurations[owner] = (uint8_t)control.request.wValue; if (owner == 0) { if (configurations[0]) open_interrupt(); else { close_interrupt(); probe_router_enable(false); routing_enabled = false; memset(ports, 0, sizeof(ports)); forget_child(0); forget_child(1); publish_addresses(); usb_hw->dev_addr_ctrl = addresses[0]; } } break; case ACTION_INTERFACE: if (owner == 0) open_interrupt(); break; case ACTION_HALT: ++endpoint_epoch; interrupt_halted = true; interrupt_pending = false; dcd_edpt_stall(RHPORT, HUB_EP); break; case ACTION_CLEAR_HALT: ++endpoint_epoch; interrupt_pending = false; interrupt_halted = false; // Reopening also cancels a previously queued interrupt safely and // resets DATA0; no child has a noncontrol endpoint to disturb. open_interrupt(); break; case ACTION_PORT_SET: apply_port_feature(true); break; case ACTION_PORT_CLEAR: apply_port_feature(false); break; case ACTION_KEEPALIVE: watchdog_update(); break; case ACTION_ARM: if (!routing_enabled) { routing_enabled = true; publish_addresses(); probe_router_enable(true); for (unsigned i = 0; i < PORT_COUNT; ++i) { ports[i].status |= PORT_CONNECTION; ports[i].change |= C_CONNECTION; } } break; case ACTION_REBOOT: reboot_pending = true; break; case ACTION_NONE: break; } } static void handle_transfer(const queued_event* queued) { const dcd_event_t* event = &queued->event; uint8_t endpoint = event->xfer_complete.ep_addr; if (endpoint == HUB_EP) { if (queued->endpoint_epoch != endpoint_epoch) return; interrupt_pending = false; if (event->xfer_complete.result != XFER_RESULT_SUCCESS || event->xfer_complete.len != 1) failed = true; return; } if ((endpoint != EP0_IN && endpoint != EP0_OUT) || queued->generation != control.generation || control.stage == CTRL_IDLE || control.stage == CTRL_STALLED) return; if (event->xfer_complete.result != XFER_RESULT_SUCCESS) { stall_control(); return; } if ((control.stage == CTRL_STATUS_IN && endpoint == EP0_IN) || (control.stage == CTRL_STATUS_OUT && endpoint == EP0_OUT)) { if (event->xfer_complete.len == 0) complete_control(); else stall_control(); return; } if (queued->generation != event_generation) return; if (control.stage != CTRL_DATA_IN || endpoint != EP0_IN || event->xfer_complete.len != control.packet_length) { stall_control(); return; } control.sent = (uint16_t)(control.sent + control.packet_length); if (control.sent < control.length || control.need_zlp) next_control_packet(); else { control.stage = CTRL_STATUS_OUT; queue_control(EP0_OUT, control_out, 0); } } void dcd_event_handler(dcd_event_t const* event, bool in_isr) { (void)in_isr; if (event->rhport != RHPORT) return; if (event->event_id != DCD_EVENT_SETUP_RECEIVED && event->event_id != DCD_EVENT_XFER_COMPLETE && event->event_id != DCD_EVENT_BUS_RESET && event->event_id != DCD_EVENT_UNPLUGGED) return; if (event->event_id == DCD_EVENT_SETUP_RECEIVED || event->event_id == DCD_EVENT_BUS_RESET || event->event_id == DCD_EVENT_UNPLUGGED) ++event_generation; uint32_t head = event_head; uint32_t next = (head + 1u) % EVENT_CAPACITY; if (next == event_tail) { event_overflow = true; return; } queued_event* queued = &events[head]; queued->event = *event; queued->generation = event_generation; queued->endpoint_epoch = endpoint_epoch; queued->setup_slot = PROBE_ROUTER_UNASSIGNED; if (event->event_id == DCD_EVENT_SETUP_RECEIVED) { // C1 does not change the address while SETUP_REC is pending; C0 does // not write it in routed mode. This is the hardware-accepted address, // not a fallback inferred from whichever header we last sampled. const uint8_t hw_address = usb_hw->dev_addr_ctrl & 0x7fu; if (hw_address == 0) { queued->setup_slot = default_slot; } else { for (uint8_t slot = 0; slot < PROBE_ROUTER_SLOTS; ++slot) { if (addresses[slot] == hw_address) { queued->setup_slot = slot; break; } } } uint32_t sequence; const uint8_t candidate = probe_router_setup_slot(&sequence); if (candidate < PROBE_ROUTER_SLOTS && candidate == queued->setup_slot && sequence != observed_setup_sequence) ++correlated_setups; observed_setup_sequence = sequence; } __dmb(); event_head = next; } static void port_task(uint32_t now) { for (unsigned i = 0; i < PORT_COUNT; ++i) { hub_port* port = &ports[i]; if (port->resetting && (int32_t)(now - port->reset_deadline) >= 0) { port->resetting = false; port->status &= (uint16_t)~PORT_RESET; if (default_slot != PROBE_ROUTER_UNASSIGNED && default_slot != i + 1) { // Concurrent default-address resets cannot be represented honestly. failed = true; return; } port->status |= PORT_ENABLE; port->change |= C_RESET; addresses[i + 1] = 0; default_slot = (uint8_t)(i + 1); publish_addresses(); } if (port->resuming && (int32_t)(now - port->resume_deadline) >= 0) { port->resuming = false; port->status &= (uint16_t)~PORT_SUSPEND; port->change |= C_SUSPEND; } } } static void diagnostic_task(uint32_t now) { static uint32_t last_report; static char line[384]; static uint16_t length; static uint16_t sent; if ((uint32_t)(now - last_report) >= 1000000u && sent == length) { last_report = now; probe_router_stats router; probe_router_snapshot(&router); int count = snprintf(line, sizeof(line), "[PHUB] route=%u addr=%u,%u,%u default=%u setup=%" PRIu32 ",%" PRIu32 ",%" PRIu32 " bad=%" PRIu32 " ready=%" PRIu32 " sop=%" PRIu32 " sync=%" PRIu32 " token=%" PRIu32 " crc=%" PRIu32 " late=%" PRIu32 " retarget=%" PRIu32 " hits=%" PRIu32 ",%" PRIu32 ",%" PRIu32 " overflow=%u failed=%u" " raw=%08" PRIx32 "/%08" PRIx32 " n=%" PRIu32 " eop=%" PRIu32 "\r\n", routing_enabled, addresses[0], addresses[1], addresses[2], default_slot, setup_count[0], setup_count[1], setup_count[2], bad_setup_owner, router.ready, router.sops, router.sync_ok, router.valid_tokens, router.crc_errors, router.late_samples, router.retargets, router.address_hits[0], router.address_hits[1], router.address_hits[2], event_overflow, failed, router.last_raw[0], router.last_raw[1], router.last_raw_count, router.last_raw_eop); length = count < 0 ? 0 : (uint16_t)((unsigned)count < sizeof(line) ? (unsigned)count : sizeof(line) - 1); sent = 0; } // No blocking stdio writes: fill only available UART FIFO positions. USB // event service continues while the 115200-baud diagnostic line drains. for (unsigned budget = 0; sent < length && budget < 32 && uart_is_writable(uart_default); ++budget) uart_get_hw(uart_default)->dr = (uint8_t)line[sent++]; } void probe_hub_init(void) { system_clock_hz = clock_get_hz(clk_sys); reset_bus_state(); // Enabling, bus resets, ordinary enumeration, and UART never feed this. watchdog_enable(8000, false); const tusb_rhport_init_t init = { .role = TUSB_ROLE_DEVICE, .speed = TUSB_SPEED_FULL }; if (!dcd_init(RHPORT, &init)) failed = true; dcd_int_enable(RHPORT); } void probe_hub_task(void) { if (!failed) { for (unsigned count = 0; count < EVENT_CAPACITY; ++count) { dcd_int_disable(RHPORT); if (event_overflow) failed = true; if (failed || event_tail == event_head) { dcd_int_enable(RHPORT); break; } __dmb(); queued_event queued = events[event_tail]; event_tail = (event_tail + 1u) % EVENT_CAPACITY; switch (queued.event.event_id) { case DCD_EVENT_BUS_RESET: case DCD_EVENT_UNPLUGGED: reset_bus_state(); break; case DCD_EVENT_SETUP_RECEIVED: handle_setup(&queued); break; case DCD_EVENT_XFER_COMPLETE: handle_transfer(&queued); break; default: break; } dcd_int_enable(RHPORT); if (failed || reboot_pending) break; } } uint32_t now = time_us_32(); dcd_int_disable(RHPORT); if (!failed && !reboot_pending) { port_task(now); if (!failed) arm_interrupt(); } if (failed) { probe_router_enable(false); routing_enabled = false; dcd_disconnect(RHPORT); } dcd_int_enable(RHPORT); if (reboot_pending) { // This is reached only after the REBOOT request's status IN was ACKed. watchdog_reboot(0, 0, 10); reboot_pending = false; failed = true; } diagnostic_task(now); }