diff --git a/src/portable/foosn/fomu/dcd_fomu.c b/src/portable/foosn/fomu/dcd_fomu.c index d7f9d3d1..f49e9e50 100644 --- a/src/portable/foosn/fomu/dcd_fomu.c +++ b/src/portable/foosn/fomu/dcd_fomu.c @@ -41,105 +41,130 @@ void mputln(const char *str); // SIE Command //--------------------------------------------------------------------+ -static uint8_t volatile out_buffer_length[16]; -static uint8_t volatile * out_buffer[16]; -static uint8_t volatile out_buffer_max[16]; +#define EP_SIZE 64 + +static uint16_t volatile rx_buffer_length[16]; +static uint8_t volatile * rx_buffer[16]; +static uint16_t volatile rx_buffer_max[16]; static volatile bool tx_in_progress; static volatile uint8_t tx_ep; static volatile uint16_t tx_len; +static uint8_t volatile * tx_buffer; +static volatile uint16_t tx_offset; //--------------------------------------------------------------------+ // PIPE HELPER //--------------------------------------------------------------------+ static void finish_tx(void) { - // // Don't allow requeueing -- only queue more data if the system is idle. - // if (!(usb_in_status_read() & 2)) { - // return; - // } - - // Don't send empty data - if (!tx_in_progress) { - return; - } + // Don't send empty data + if (!tx_in_progress) { + return; + } + tx_offset += EP_SIZE; + if (tx_offset >= tx_len) { tx_in_progress = 0; + tx_buffer = NULL; dcd_event_xfer_complete(0, tx_ep, tx_len, XFER_RESULT_SUCCESS, true); return; + } + + // Send more data + uint8_t added_bytes; + for (added_bytes = 0; (added_bytes < EP_SIZE) && (added_bytes + tx_offset < tx_len); added_bytes++) { + usb_in_data_write(tx_buffer[added_bytes + tx_offset]); + } + + // Updating the epno queues the data + usb_in_ctrl_write(tu_edpt_number(tx_ep) & 0xf); + return; } static void process_rx(bool in_isr) { // If there isn't any data in the FIFO, don't do anything. - if (!(usb_out_status_read() & 1)) - return; + if (!(usb_out_status_read() & (1 << CSR_USB_OUT_STATUS_HAVE_OFFSET))) + return; + + uint8_t out_ep = (usb_out_status_read() >> CSR_USB_OUT_STATUS_EPNO_OFFSET) & 0xf; + + // If the destination buffer doesn't exist, don't drain the hardware + // fifo. Note that this can cause deadlocks if the host is waiting + // on some other endpoint's data! + if (rx_buffer[out_ep] == NULL) + return; - uint8_t out_ep = (usb_out_status_read() >> 2) & 0xf; uint32_t total_read = 0; - uint32_t current_offset = out_buffer_length[out_ep]; - while (usb_out_status_read() & 1) { + uint32_t current_offset = rx_buffer_length[out_ep]; + while (usb_out_status_read() & (1 << CSR_USB_OUT_STATUS_HAVE_OFFSET)) { uint8_t c = usb_out_data_read(); total_read++; - if (out_buffer_length[out_ep] < out_buffer_max[out_ep]) - out_buffer[out_ep][current_offset++] = c; + if (rx_buffer_length[out_ep] < rx_buffer_max[out_ep]) + rx_buffer[out_ep][current_offset++] = c; } // Strip off the CRC16 - total_read -= 2; - out_buffer_length[out_ep] += total_read; - if (out_buffer_length[out_ep] > out_buffer_max[out_ep]) - out_buffer_length[out_ep] = out_buffer_max[out_ep]; + rx_buffer_length[out_ep] += (total_read - 2); + if (rx_buffer_length[out_ep] > rx_buffer_max[out_ep]) + rx_buffer_length[out_ep] = rx_buffer_max[out_ep]; - if (out_buffer_max[out_ep] == out_buffer_length[out_ep]) { - out_buffer[out_ep] = NULL; - dcd_event_xfer_complete(0, tu_edpt_addr(out_ep, TUSB_DIR_OUT), out_buffer_length[out_ep], XFER_RESULT_SUCCESS, in_isr); + if (rx_buffer_max[out_ep] == rx_buffer_length[out_ep]) { + rx_buffer[out_ep] = NULL; + uint16_t len = rx_buffer_length[out_ep]; + dcd_event_xfer_complete(0, tu_edpt_addr(out_ep, TUSB_DIR_OUT), len, XFER_RESULT_SUCCESS, in_isr); } - // Acknowledge having received the data - usb_out_ctrl_write(2); + // Acknowledge having received the data, and re-enable data reception + usb_out_ctrl_write(1 << CSR_USB_OUT_CTRL_ENABLE_OFFSET); } //--------------------------------------------------------------------+ // CONTROLLER API //--------------------------------------------------------------------+ +static void dcd_reset(void) +{ + usb_address_write(0); + + // Reset all three FIFO handlers + usb_setup_ctrl_write(1 << CSR_USB_SETUP_CTRL_RESET_OFFSET); + usb_in_ctrl_write(1 << CSR_USB_IN_CTRL_RESET_OFFSET); + usb_out_ctrl_write(1 << CSR_USB_OUT_CTRL_RESET_OFFSET); + + // Accept incoming data by default. + usb_out_ctrl_write(CSR_USB_OUT_CTRL_ENABLE_OFFSET); + + memset(rx_buffer, 0, sizeof(rx_buffer)); + tx_in_progress = 0; + tx_len = 0; + tx_buffer = NULL; + tx_offset = 0; + + dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); +} + // Initializes the USB peripheral for device mode and enables it. void dcd_init(uint8_t rhport) { (void) rhport; usb_pullup_out_write(0); - usb_address_write(0); - usb_out_ctrl_write(0); usb_setup_ev_enable_write(0); usb_in_ev_enable_write(0); usb_out_ev_enable_write(0); - // Reset the IN handler - usb_in_ctrl_write(0x20); - - // Reset the SETUP handler - usb_setup_ctrl_write(0x04); - - // Reset the OUT handler - usb_out_ctrl_write(0x04); - // Enable all event handlers and clear their contents usb_setup_ev_pending_write(usb_setup_ev_pending_read()); usb_in_ev_pending_write(usb_in_ev_pending_read()); usb_out_ev_pending_write(usb_out_ev_pending_read()); - usb_setup_ev_enable_write(3); usb_in_ev_enable_write(1); usb_out_ev_enable_write(1); - - // Accept incoming data by default. - usb_out_ctrl_write(2); + usb_setup_ev_enable_write(3); // Turn on the external pullup usb_pullup_out_write(1); - - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, false); } // Enables or disables the USB device interrupt(s). May be used to @@ -222,36 +247,45 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t t { (void)rhport; + // These sorts of transfers are handled in hardware + if ((tu_edpt_number(ep_addr) == 0) && (total_bytes == 0) && (buffer == NULL)) { + dcd_event_xfer_complete(0, ep_addr, total_bytes, XFER_RESULT_SUCCESS, false); + + // An IN packet is sent to acknowledge an OUT token. Re-enable OUT after this. + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) + usb_out_ctrl_write(1 << CSR_USB_OUT_CTRL_ENABLE_OFFSET); + return true; + } + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { - // These sorts of transfers are handled in hardware - if ((tu_edpt_number(ep_addr) == 0) && (total_bytes == 0) && (buffer == 0)) { - dcd_event_xfer_complete(0, ep_addr, total_bytes, XFER_RESULT_SUCCESS, false); - return true; - } uint32_t offset; + // Wait for the tx pipe to free up while (tx_in_progress) ; tx_in_progress = 1; tx_ep = ep_addr; tx_len = total_bytes; + tx_buffer = buffer; + tx_offset = 0; - for (offset = 0; offset < total_bytes; offset++) { + for (offset = 0; (offset < EP_SIZE) && (offset < total_bytes); offset++) { usb_in_data_write(buffer[offset]); } // Updating the epno queues the data usb_in_ctrl_write(tu_edpt_number(ep_addr) & 0xf); - last_tx_buffer = buffer; - last_tx_bytes = total_bytes; } else if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { + TU_ASSERT(rx_buffer[tu_edpt_number(ep_addr)] == NULL); - // Wait for the rx pipe to free up - while (out_buffer[tu_edpt_number(ep_addr)]) - ; - out_buffer_max[tu_edpt_number(ep_addr)] = total_bytes; - out_buffer[tu_edpt_number(ep_addr)] = buffer; - out_buffer_length[tu_edpt_number(ep_addr)] = 0; + rx_buffer_length[tu_edpt_number(ep_addr)] = 0; + rx_buffer_max[tu_edpt_number(ep_addr)] = total_bytes; + rx_buffer[tu_edpt_number(ep_addr)] = buffer; + + // If there's data in the buffer already, we'll try draining it + // into the current fifo immediately. Note that since this + // bit is set, an interrupt won't fire again, so there is + // no need for a lock here. process_rx(false); } return true; @@ -273,11 +307,7 @@ void hal_dcd_isr(uint8_t rhport) // This event means a bus reset occurred. Reset everything, and // abandon any further processing. if (setup_pending & 2) { - usb_setup_ctrl_write(1 << CSR_USB_SETUP_CTRL_RESET_OFFSET); - usb_in_ctrl_write(1 << CSR_USB_IN_CTRL_RESET_OFFSET); - usb_out_ctrl_write(1 << CSR_USB_OUT_CTRL_RESET_OFFSET); - - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); + dcd_reset(); return; }