From a1c599f4b6a05aefb948aaacf9a0984f577252bb Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 26 Apr 2020 22:02:41 +0700 Subject: [PATCH 1/8] clean up log message --- src/device/usbd.c | 31 ++++++++++++++++++++++--------- src/device/usbd_control.c | 11 ++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/device/usbd.c b/src/device/usbd.c index f0b7fcefc..1f660f0f2 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -232,15 +232,15 @@ bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, #if CFG_TUSB_DEBUG >= 2 static char const* const _usbd_event_str[DCD_EVENT_COUNT] = { - "INVALID" , - "BUS_RESET" , - "UNPLUGGED" , + "Invalid" , + "Bus Reset" , + "Unplugged" , "SOF" , - "SUSPEND" , - "RESUME" , - "SETUP_RECEIVED" , - "XFER_COMPLETE" , - "FUNC_CALL" + "Suspend" , + "Resume" , + "Setup Received" , + "Xfer Complete" , + "Func Call" }; static char const* const _tusb_std_request_str[] = @@ -260,6 +260,19 @@ static char const* const _tusb_std_request_str[] = "Synch Frame" }; +// for usbd_control to print the name of control complete driver +void usbd_driver_print_control_complete_name(bool (*control_complete) (uint8_t, tusb_control_request_t const * )) +{ + for (uint8_t i = 0; i < USBD_CLASS_DRIVER_COUNT; i++) + { + if (_usbd_driver[i].control_complete == control_complete ) + { + TU_LOG2(" %s control complete\r\n", _usbd_driver[i].name); + return; + } + } +} + #endif //--------------------------------------------------------------------+ @@ -356,7 +369,7 @@ void tud_task (void) if ( !osal_queue_receive(_usbd_q, &event) ) return; - TU_LOG2("USBD: %s", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED"); + TU_LOG2("USBD %s", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED"); TU_LOG2("%s", (event.event_id != DCD_EVENT_XFER_COMPLETE && event.event_id != DCD_EVENT_SETUP_RECEIVED) ? "\r\n" : " "); switch ( event.event_id ) diff --git a/src/device/usbd_control.c b/src/device/usbd_control.c index b0ae85e3b..2778a0d77 100644 --- a/src/device/usbd_control.c +++ b/src/device/usbd_control.c @@ -58,6 +58,7 @@ static uint8_t _usbd_ctrl_buf[CFG_TUD_ENDPOINT0_SIZE]; // Application API //--------------------------------------------------------------------+ +// Queue ZLP status transaction static inline bool _status_stage_xact(uint8_t rhport, tusb_control_request_t const * request) { // Opposite to endpoint in Data Phase @@ -81,7 +82,7 @@ bool tud_control_status(uint8_t rhport, tusb_control_request_t const * request) return _status_stage_xact(rhport, request); } -// Transfer an transaction in Data Stage +// Queue an transaction in Data Stage // Each transaction has up to Endpoint0's max packet size. // This function can also transfer an zero-length packet static bool _data_stage_xact(uint8_t rhport) @@ -102,7 +103,6 @@ static bool _data_stage_xact(uint8_t rhport) } // Transmit data to/from the control endpoint. -// // If the request's wLength is zero, a status packet is sent instead. bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, void* buffer, uint16_t len) { @@ -182,7 +182,7 @@ bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result // Data Stage is complete when all request's length are transferred or // a short packet is sent including zero-length packet. - if ( (_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || xferred_bytes < CFG_TUD_ENDPOINT0_SIZE ) + if ( (_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || (xferred_bytes < CFG_TUD_ENDPOINT0_SIZE) ) { // DATA stage is complete bool is_ok = true; @@ -191,6 +191,11 @@ bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result // callback can still stall control in status phase e.g out data does not make sense if ( _ctrl_xfer.complete_cb ) { + #if CFG_TUSB_DEBUG >= 2 + extern void usbd_driver_print_control_complete_name(bool (*control_complete) (uint8_t, tusb_control_request_t const *)); + usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb); + #endif + is_ok = _ctrl_xfer.complete_cb(rhport, &_ctrl_xfer.request); } From 83353dd93f2c24b87b9d22e3543e91420d758513 Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 26 Apr 2020 22:03:05 +0700 Subject: [PATCH 2/8] add TODO for usbnet clean up --- src/class/net/net_device.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/class/net/net_device.c b/src/class/net/net_device.c index ad1d66fca..e3faa0187 100644 --- a/src/class/net/net_device.c +++ b/src/class/net/net_device.c @@ -89,7 +89,8 @@ static const struct ecm_notify_struct ecm_notify_csc = .uplink = 9728000, }; -CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static union +// TODO remove CFG_TUSB_MEM_SECTION, control internal buffer is already in this special section +CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) static union { uint8_t rndis_buf[120]; struct ecm_notify_struct ecm_buf; @@ -98,6 +99,7 @@ CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static union //--------------------------------------------------------------------+ // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ +// TODO remove CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_SECTION static netd_interface_t _netd_itf; static bool can_xmit; From 8d18d6077bb1100e0f5ac5b3a11b807751d93149 Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 26 Apr 2020 22:14:59 +0700 Subject: [PATCH 3/8] turn off TX FIFO Empty for EPIN if all bytes are written fix dcd synopsys issue with usbnet #289 --- src/portable/st/synopsys/dcd_synopsys.c | 60 +++++++++++++++---------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/portable/st/synopsys/dcd_synopsys.c b/src/portable/st/synopsys/dcd_synopsys.c index 9c1672220..a86d07578 100644 --- a/src/portable/st/synopsys/dcd_synopsys.c +++ b/src/portable/st/synopsys/dcd_synopsys.c @@ -291,9 +291,10 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) if(dir == TUSB_DIR_OUT) { - out_ep[epnum].DOEPCTL |= (1 << USB_OTG_DOEPCTL_USBAEP_Pos) | \ - desc_edpt->bmAttributes.xfer << USB_OTG_DOEPCTL_EPTYP_Pos | \ - desc_edpt->wMaxPacketSize.size << USB_OTG_DOEPCTL_MPSIZ_Pos; + out_ep[epnum].DOEPCTL |= (1 << USB_OTG_DOEPCTL_USBAEP_Pos) | + (desc_edpt->bmAttributes.xfer << USB_OTG_DOEPCTL_EPTYP_Pos) | + (desc_edpt->wMaxPacketSize.size << USB_OTG_DOEPCTL_MPSIZ_Pos); + dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_OEPM_Pos + epnum)); } else @@ -321,11 +322,12 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) // - Offset: GRXFSIZ + 16 + Size*(epnum-1) // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n". - in_ep[epnum].DIEPCTL |= (1 << USB_OTG_DIEPCTL_USBAEP_Pos) | \ - epnum << USB_OTG_DIEPCTL_TXFNUM_Pos | \ - desc_edpt->bmAttributes.xfer << USB_OTG_DIEPCTL_EPTYP_Pos | \ - (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? USB_OTG_DOEPCTL_SD0PID_SEVNFRM : 0) | \ - desc_edpt->wMaxPacketSize.size << USB_OTG_DIEPCTL_MPSIZ_Pos; + in_ep[epnum].DIEPCTL |= (1 << USB_OTG_DIEPCTL_USBAEP_Pos) | + (epnum << USB_OTG_DIEPCTL_TXFNUM_Pos) | + (desc_edpt->bmAttributes.xfer << USB_OTG_DIEPCTL_EPTYP_Pos) | + (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? USB_OTG_DOEPCTL_SD0PID_SEVNFRM : 0) | + (desc_edpt->wMaxPacketSize.size << USB_OTG_DIEPCTL_MPSIZ_Pos); + dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_IEPM_Pos + epnum)); // Both TXFD and TXSA are in unit of 32-bit words. @@ -352,9 +354,9 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t uint8_t const dir = tu_edpt_dir(ep_addr); xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->queued_len = 0; + xfer->buffer = buffer; + xfer->total_len = total_bytes; + xfer->queued_len = 0; xfer->short_packet = false; uint16_t num_packets = (total_bytes / xfer->max_size); @@ -365,21 +367,23 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t num_packets++; } - // IN and OUT endpoint xfers are interrupt-driven, we just schedule them - // here. + // IN and OUT endpoint xfers are interrupt-driven, we just schedule them here. if(dir == TUSB_DIR_IN) { // A full IN transfer (multiple packets, possibly) triggers XFRC. - in_ep[epnum].DIEPTSIZ = (num_packets << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | \ - ((total_bytes & USB_OTG_DIEPTSIZ_XFRSIZ_Msk) << USB_OTG_DIEPTSIZ_XFRSIZ_Pos); + in_ep[epnum].DIEPTSIZ = (num_packets << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | + ((total_bytes & USB_OTG_DIEPTSIZ_XFRSIZ_Msk) << USB_OTG_DIEPTSIZ_XFRSIZ_Pos); + in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; + // Enable fifo empty interrupt only if there are something to put in the fifo. if(total_bytes != 0) { dev->DIEPEMPMSK |= (1 << epnum); } } else { // Each complete packet for OUT xfers triggers XFRC. - out_ep[epnum].DOEPTSIZ |= (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos) | \ - ((xfer->max_size & USB_OTG_DOEPTSIZ_XFRSIZ_Msk) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos); + out_ep[epnum].DOEPTSIZ |= (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos) | + ((xfer->max_size & USB_OTG_DOEPTSIZ_XFRSIZ_Msk) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos); + out_ep[epnum].DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; } @@ -535,6 +539,7 @@ static void receive_packet(xfer_ctl_t * xfer, /* USB_OTG_OUTEndpointTypeDef * ou xfer->short_packet = (xfer_size < xfer->max_size); } +// Write a data packet to EPIN FIFO static void transmit_packet(xfer_ctl_t * xfer, USB_OTG_INEndpointTypeDef * in_ep, uint8_t fifo_num) { usb_fifo_t tx_fifo = FIFO_BASE(fifo_num); @@ -658,21 +663,30 @@ static void handle_epout_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_OUTEndpointTy static void handle_epin_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_INEndpointTypeDef * in_ep) { // DAINT for a given EP clears when DIEPINTx is cleared. // IEPINT will be cleared when DAINT's out bits are cleared. - for(uint8_t n = 0; n < EP_MAX; n++) { - xfer_ctl_t * xfer = XFER_CTL_BASE(n, TUSB_DIR_IN); + for ( uint8_t n = 0; n < EP_MAX; n++ ) + { + xfer_ctl_t *xfer = XFER_CTL_BASE(n, TUSB_DIR_IN); - if(dev->DAINT & (1 << (USB_OTG_DAINT_IEPINT_Pos + n))) { + if ( dev->DAINT & (1 << (USB_OTG_DAINT_IEPINT_Pos + n)) ) + { // IN XFER complete (entire xfer). - if(in_ep[n].DIEPINT & USB_OTG_DIEPINT_XFRC) { + if ( in_ep[n].DIEPINT & USB_OTG_DIEPINT_XFRC ) + { in_ep[n].DIEPINT = USB_OTG_DIEPINT_XFRC; - dev->DIEPEMPMSK &= ~(1 << n); // Turn off TXFE b/c xfer inactive. dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); } // XFER FIFO empty - if(in_ep[n].DIEPINT & USB_OTG_DIEPINT_TXFE) { + if ( in_ep[n].DIEPINT & USB_OTG_DIEPINT_TXFE ) + { in_ep[n].DIEPINT = USB_OTG_DIEPINT_TXFE; transmit_packet(xfer, &in_ep[n], n); + + // Turn off TXFE if all bytes are written. + if (xfer->queued_len == xfer->total_len) + { + dev->DIEPEMPMSK &= ~(1 << n); + } } } } From 00fcf829a12c2f075f696c78a64640b8b8e44b6b Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 26 Apr 2020 22:41:04 +0700 Subject: [PATCH 4/8] sync synopsis fix for esp32s2 --- src/portable/espressif/esp32s2/dcd_esp32s2.c | 36 +++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/portable/espressif/esp32s2/dcd_esp32s2.c b/src/portable/espressif/esp32s2/dcd_esp32s2.c index 10c789da3..d49b69a10 100644 --- a/src/portable/espressif/esp32s2/dcd_esp32s2.c +++ b/src/portable/espressif/esp32s2/dcd_esp32s2.c @@ -244,8 +244,8 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt) if (dir == TUSB_DIR_OUT) { out_ep[epnum].doepctl |= USB_USBACTEP0_M | - desc_edpt->bmAttributes.xfer << USB_EPTYPE0_S | - desc_edpt->wMaxPacketSize.size << USB_MPS0_S; + desc_edpt->bmAttributes.xfer << USB_EPTYPE0_S | + desc_edpt->wMaxPacketSize.size << USB_MPS0_S; USB0.daintmsk |= (1 << (16 + epnum)); } else { // "USB Data FIFOs" section in reference manual @@ -272,10 +272,11 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt) // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n". in_ep[epnum].diepctl |= USB_D_USBACTEP1_M | - epnum << USB_D_TXFNUM1_S | - desc_edpt->bmAttributes.xfer << USB_D_EPTYPE1_S | - (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? (1 << USB_DI_SETD0PID1_S) : 0) | - desc_edpt->wMaxPacketSize.size << 0; + epnum << USB_D_TXFNUM1_S | + desc_edpt->bmAttributes.xfer << USB_D_EPTYPE1_S | + (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? (1 << USB_DI_SETD0PID1_S) : 0) | + desc_edpt->wMaxPacketSize.size << 0; + USB0.daintmsk |= (1 << (0 + epnum)); // Both TXFD and TXSA are in unit of 32-bit words. @@ -295,12 +296,12 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to (void)rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); - xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir); - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->queued_len = 0; + xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); + xfer->buffer = buffer; + xfer->total_len = total_bytes; + xfer->queued_len = 0; xfer->short_packet = false; uint16_t num_packets = (total_bytes / xfer->max_size); @@ -321,7 +322,11 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to // A full IN transfer (multiple packets, possibly) triggers XFRC. USB0.in_ep_reg[epnum].dieptsiz = (num_packets << USB_D_PKTCNT0_S) | total_bytes; USB0.in_ep_reg[epnum].diepctl |= USB_D_EPENA1_M | USB_D_CNAK1_M; // Enable | CNAK - USB0.dtknqr4_fifoemptymsk |= (1 << epnum); + + // Enable fifo empty interrupt only if there are something to put in the fifo. + if(total_bytes != 0) { + USB0.dtknqr4_fifoemptymsk |= (1 << epnum); + } } else { // Each complete packet for OUT xfers triggers XFRC. USB0.out_ep_reg[epnum].doeptsiz |= USB_PKTCNT0_M | ((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S); @@ -617,7 +622,6 @@ static void handle_epin_ints(void) if (USB0.in_ep_reg[n].diepint & USB_D_XFERCOMPL0_M) { ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER complete!"); USB0.in_ep_reg[n].diepint = USB_D_XFERCOMPL0_M; - USB0.dtknqr4_fifoemptymsk &= ~(1 << n); // Turn off TXFE b/c xfer inactive. dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); } @@ -626,6 +630,12 @@ static void handle_epin_ints(void) ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER FIFO empty!"); USB0.in_ep_reg[n].diepint = USB_D_TXFEMP0_M; transmit_packet(xfer, &USB0.in_ep_reg[n], n); + + // Turn off TXFE if all bytes are written. + if (xfer->queued_len == xfer->total_len) + { + USB0.dtknqr4_fifoemptymsk &= ~(1 << n); + } } } } From d0487088ac094eaaff54c04241d8018ffc729b36 Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 26 Apr 2020 23:04:17 +0700 Subject: [PATCH 5/8] revert a change to net driver --- src/class/net/net_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/class/net/net_device.c b/src/class/net/net_device.c index e3faa0187..fedd4db99 100644 --- a/src/class/net/net_device.c +++ b/src/class/net/net_device.c @@ -90,7 +90,7 @@ static const struct ecm_notify_struct ecm_notify_csc = }; // TODO remove CFG_TUSB_MEM_SECTION, control internal buffer is already in this special section -CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) static union +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static union { uint8_t rndis_buf[120]; struct ecm_notify_struct ecm_buf; From e785b091184b5e741d9c2ade0b54bca651834bd1 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 27 Apr 2020 12:06:14 +0700 Subject: [PATCH 6/8] TXFE is read only bit --- src/portable/st/synopsys/dcd_synopsys.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/portable/st/synopsys/dcd_synopsys.c b/src/portable/st/synopsys/dcd_synopsys.c index a86d07578..b479930ed 100644 --- a/src/portable/st/synopsys/dcd_synopsys.c +++ b/src/portable/st/synopsys/dcd_synopsys.c @@ -679,7 +679,8 @@ static void handle_epin_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_INEndpointType // XFER FIFO empty if ( in_ep[n].DIEPINT & USB_OTG_DIEPINT_TXFE ) { - in_ep[n].DIEPINT = USB_OTG_DIEPINT_TXFE; + // DIEPINT's TXFE bit is read-only -> no need to clear + transmit_packet(xfer, &in_ep[n], n); // Turn off TXFE if all bytes are written. From 958b5510cb9ac913b30772ec248e0ffcd3827890 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 27 Apr 2020 13:17:47 +0700 Subject: [PATCH 7/8] added comment for hw clearing TXFE --- src/portable/st/synopsys/dcd_synopsys.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/portable/st/synopsys/dcd_synopsys.c b/src/portable/st/synopsys/dcd_synopsys.c index b479930ed..4e0d6f8c4 100644 --- a/src/portable/st/synopsys/dcd_synopsys.c +++ b/src/portable/st/synopsys/dcd_synopsys.c @@ -679,7 +679,10 @@ static void handle_epin_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_INEndpointType // XFER FIFO empty if ( in_ep[n].DIEPINT & USB_OTG_DIEPINT_TXFE ) { - // DIEPINT's TXFE bit is read-only -> no need to clear + // DIEPINT's TXFE bit is read-only, software cannot clear it. + // It will only be cleared by hardware when written bytes is more than + // - 64 bytes or + // - Half of TX FIFO size (configured by DIEPTXF) transmit_packet(xfer, &in_ep[n], n); From fcdb22b2f9db6f4e08ce05a1b2b7a1c9cc06a4ab Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 28 Apr 2020 10:53:43 +0700 Subject: [PATCH 8/8] fix typo --- src/device/usbd_control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device/usbd_control.c b/src/device/usbd_control.c index 2778a0d77..353a66ad1 100644 --- a/src/device/usbd_control.c +++ b/src/device/usbd_control.c @@ -82,7 +82,7 @@ bool tud_control_status(uint8_t rhport, tusb_control_request_t const * request) return _status_stage_xact(rhport, request); } -// Queue an transaction in Data Stage +// Queue a transaction in Data Stage // Each transaction has up to Endpoint0's max packet size. // This function can also transfer an zero-length packet static bool _data_stage_xact(uint8_t rhport)