diff --git a/lib/usbd/backend/dwc_otg_private.h b/lib/usbd/backend/dwc_otg_private.h index e1a1f247..9424901f 100644 --- a/lib/usbd/backend/dwc_otg_private.h +++ b/lib/usbd/backend/dwc_otg_private.h @@ -83,11 +83,19 @@ struct dwc_otg_private_data { /* FIXME: used for all endpoint setup_data. */ struct usb_setup_data setup_data; + + /* Premature terminated OUT URBs are probably have followed by + * COMP_OUT cause IRQ RXC - that should be ommited, + * else next URB will terminate by this loosed RXC */ + uint32_t ep_prematured; }; #define USBD_DEVICE_EXTRA \ struct dwc_otg_private_data private_data; + + +#include "../usbd_private.h" void dwc_otg_init(usbd_device *dev); void dwc_otg_set_address(usbd_device *dev, uint8_t addr); @@ -97,6 +105,8 @@ void dwc_otg_ep_prepare(usbd_device *dev, uint8_t addr, usbd_ep_type type, uint16_t max_size, uint16_t internval, usbd_ep_flags flags); void dwc_otg_ep_prepare_end(usbd_device *dev); +void dwc_otg_stop_ep(usbd_device *dev, uint8_t ep_num); +bool dwc_otg_is_active_ep(usbd_device *dev, uint8_t ep_addr); void dwc_otg_set_ep_dtog(usbd_device *dev, uint8_t addr, bool dtog); bool dwc_otg_get_ep_dtog(usbd_device *dev, uint8_t addr); void dwc_otg_set_ep_stall(usbd_device *dev, uint8_t addr, bool stall); @@ -112,6 +122,23 @@ typedef struct usbd_urb usbd_urb; void dwc_otg_urb_submit(usbd_device *dev, usbd_urb *urb); void dwc_otg_urb_cancel(usbd_device *dev, usbd_urb *urb); +static inline +bool is_ep_prematured(usbd_device *dev, uint8_t ep_addr) +{ + return (dev->private_data.ep_prematured & ep_free_mask(ep_addr)); +} + +static inline +void mark_ep_as_prematured(usbd_device *dev, uint8_t ep_addr, bool yes) +{ + uint32_t mask = ep_free_mask(ep_addr); + if (yes) { + dev->private_data.ep_prematured |= mask; + } else { + dev->private_data.ep_prematured &= ~mask; + } +} + END_DECLS #endif diff --git a/lib/usbd/backend/usbd_dwc_otg.c b/lib/usbd/backend/usbd_dwc_otg.c index ebb42f89..89a74308 100644 --- a/lib/usbd/backend/usbd_dwc_otg.c +++ b/lib/usbd/backend/usbd_dwc_otg.c @@ -117,6 +117,34 @@ static void flush_fifo(usbd_device *dev) (DWC_OTG_GRSTCTL_RXFFLSH | DWC_OTG_GRSTCTL_TXFFLSH)); } +void fifo_flush_ep(usbd_device *dev, uint8_t ep_addr){ + + uint8_t num = ENDPOINT_NUMBER(ep_addr); + + if (IS_IN_ENDPOINT(ep_addr)) { + REBASE(DWC_OTG_DIEPxCTL, num) |= DWC_OTG_DIEPCTL_SNAK; + /* Wait for AHB idle. */ + while (!(REBASE(DWC_OTG_GRSTCTL) & DWC_OTG_GRSTCTL_AHBIDL)); + + /* Flush RX, TX FIFO */ + REBASE(DWC_OTG_GRSTCTL) = DWC_OTG_GRSTCTL_TXFFLSH | DWC_OTG_GRSTCTL_TXFNUM(num); + + const uint32_t XFLUSH = DWC_OTG_GRSTCTL_TXFFLSH; + while ( (REBASE(DWC_OTG_GRSTCTL) & XFLUSH) != 0 ); + } + else { + REBASE(DWC_OTG_DOEPxCTL, num) |= DWC_OTG_DOEPCTL_SNAK; + /* Wait for AHB idle. */ + while (!(REBASE(DWC_OTG_GRSTCTL) & DWC_OTG_GRSTCTL_AHBIDL)); + + /* Flush RX, TX FIFO */ + REBASE(DWC_OTG_GRSTCTL) = DWC_OTG_GRSTCTL_RXFFLSH; + + const uint32_t XFLUSH = DWC_OTG_GRSTCTL_RXFFLSH; + while ( (REBASE(DWC_OTG_GRSTCTL) & XFLUSH) != 0 ); + } +} + void dwc_otg_init(usbd_device *dev) { /* Wait for AHB idle. */ @@ -173,11 +201,17 @@ static void disable_all_non_ep0(usbd_device *dev) REBASE(DWC_OTG_DOEPxINT, i) = 0xFFFF; REBASE(DWC_OTG_DOEPxTSIZ, i) = 0; - REBASE(DWC_OTG_DOEPxCTL, i) = DWC_OTG_DOEPCTL_SNAK; + if ((REBASE(DWC_OTG_DOEPxCTL, i) & DWC_OTG_DOEPCTL_EPENA) == 0) + REBASE(DWC_OTG_DOEPxCTL, i) = DWC_OTG_DOEPCTL_SNAK; + else + REBASE(DWC_OTG_DOEPxCTL, i) = DWC_OTG_DOEPCTL_SNAK | DWC_OTG_DOEPCTL_EPDIS; REBASE(DWC_OTG_DIEPxINT, i) = 0xFFFF; REBASE(DWC_OTG_DIEPxTSIZ, i) = 0; - REBASE(DWC_OTG_DIEPxCTL, i) = DWC_OTG_DIEPCTL_SNAK; + if ((REBASE(DWC_OTG_DIEPxCTL, i) & DWC_OTG_DIEPCTL_EPENA) == 0) + REBASE(DWC_OTG_DIEPxCTL, i) = DWC_OTG_DIEPCTL_SNAK; + else + REBASE(DWC_OTG_DIEPxCTL, i) = DWC_OTG_DIEPCTL_SNAK | DWC_OTG_DOEPCTL_EPDIS; } REBASE(DWC_OTG_DAINTMSK) &= ~(DWC_OTG_DAINTMSK_OEPM(0) | DWC_OTG_DAINTMSK_IEPM(0)); @@ -207,6 +241,7 @@ void dwc_otg_ep_prepare_start(usbd_device *dev) dev->private_data.fifo_rx_usage_overall = 16; /* by EP0 and constant */ dev->private_data.fifo_rx_usage_packet = fifo_word + 1; /* EP0 */ + dev->private_data.ep_prematured = 0; disable_all_non_ep0(dev); @@ -596,6 +631,12 @@ void dwc_otg_urb_cancel(usbd_device *dev, usbd_urb *urb) { (void) dev; (void) urb; + uint8_t ep_addr = urb->transfer.ep_addr; //ENDPOINT_NUMBER + if (dwc_otg_is_active_ep(dev, ep_addr)){ + USBD_LOGF_LN(USB_VIO, "usbd:stop ep%"PRIx8"\n", ep_addr); + dwc_otg_stop_ep(dev, ep_addr); + fifo_flush_ep(dev, ep_addr); + } } /** @@ -662,6 +703,45 @@ static inline void fifo_read_and_throw(usbd_device *dev, unsigned bytes) } } +bool dwc_otg_is_active_ep(usbd_device *dev, uint8_t ep_addr){ + uint8_t ep_num = ENDPOINT_NUMBER(ep_addr); + if (IS_IN_ENDPOINT(ep_addr)) { + if ( DWC_OTG_DIEPTSIZ_XFRSIZ_GET( REBASE(DWC_OTG_DIEPxTSIZ, ep_num) ) != 0 ) + return true; + if ((REBASE(DWC_OTG_DIEPxCTL, ep_num) & DWC_OTG_DIEPCTL_NAKSTS) != 0) + return false; + //* looks that DIEPxTSIZ==0 not relyable, it still can be some data hunged in txfifo + //* waiting for transmition + //if ( DWC_OTG_DIEPTSIZ_XFRSIZ_GET( REBASE(DWC_OTG_DIEPxTSIZ, ep_num) ) == 0 ) + // return false; + } + else{ + if ((REBASE(DWC_OTG_DOEPxCTL, ep_num) & DWC_OTG_DOEPCTL_NAKSTS) != 0) + return false; + if (DWC_OTG_DOEPTSIZ_XFRSIZ_GET(REBASE(DWC_OTG_DOEPxTSIZ, ep_num)) == 0) + return false; + } + return true; +} + +void dwc_otg_stop_ep(usbd_device *dev, uint8_t ep_addr){ + uint8_t ep_num = ENDPOINT_NUMBER(ep_addr); + if (IS_IN_ENDPOINT(ep_addr)) { + if ((REBASE(DWC_OTG_DIEPxCTL, ep_num) & DWC_OTG_DIEPCTL_EPENA) == 0) + REBASE(DWC_OTG_DIEPxCTL, ep_num) |= DWC_OTG_DIEPCTL_SNAK; + else + REBASE(DWC_OTG_DIEPxCTL, ep_num) |= DWC_OTG_DIEPCTL_SNAK | DWC_OTG_DOEPCTL_EPDIS; + REBASE(DWC_OTG_DIEPxINT, ep_num) = 0xFFFF; + REBASE(DWC_OTG_DAINTMSK) &= ~DWC_OTG_DAINTMSK_IEPM(ep_num); + } else { + if ((REBASE(DWC_OTG_DOEPxCTL, ep_num) & DWC_OTG_DOEPCTL_EPENA) == 0) + REBASE(DWC_OTG_DOEPxCTL, ep_num) |= DWC_OTG_DOEPCTL_SNAK; + else + REBASE(DWC_OTG_DOEPxCTL, ep_num) |= DWC_OTG_DOEPCTL_SNAK | DWC_OTG_DOEPCTL_EPDIS; + REBASE(DWC_OTG_DOEPxINT, ep_num) = 0xFFFF ^ DWC_OTG_DOEPINT_STUP; + REBASE(DWC_OTG_DAINTMSK) &= ~DWC_OTG_DAINTMSK_OEPM(ep_num); + } +} /** * Perform a premature URB complete * This is useful when some kind of unexpected things happen in between @@ -673,17 +753,10 @@ static void premature_urb_complete(usbd_device *dev, usbd_urb *urb, usbd_transfer_status status) { usbd_transfer *transfer = &urb->transfer; - uint8_t ep_num = ENDPOINT_NUMBER(transfer->ep_addr); + uint8_t ep_addr = transfer->ep_addr; - if (IS_IN_ENDPOINT(transfer->ep_addr)) { - REBASE(DWC_OTG_DIEPxCTL, ep_num) |= DWC_OTG_DIEPCTL_SNAK; - REBASE(DWC_OTG_DIEPxINT, ep_num) = 0xFFFF; - REBASE(DWC_OTG_DAINTMSK) &= ~DWC_OTG_DAINTMSK_IEPM(ep_num); - } else { - REBASE(DWC_OTG_DOEPxCTL, ep_num) |= DWC_OTG_DOEPCTL_SNAK; - REBASE(DWC_OTG_DOEPxINT, ep_num) = 0xFFFF ^ DWC_OTG_DOEPINT_STUP; - REBASE(DWC_OTG_DAINTMSK) &= ~DWC_OTG_DAINTMSK_OEPM(ep_num); - } + dwc_otg_stop_ep(dev, ep_addr); + mark_ep_as_prematured(dev, ep_addr, true); usbd_urb_complete(dev, urb, status); } @@ -919,6 +992,15 @@ static void process_out_endpoint_interrupt(usbd_device *dev, uint8_t ep_num) LOGF_LN("Transfer Complete: endpoint 0x%"PRIx8, ep_addr); usbd_urb *urb = usbd_find_active_urb(dev, ep_addr); + if (is_ep_prematured(dev, ep_addr)) { + if (urb != NULL){ + //* ommit this RXC and restart current URB + REBASE(DWC_OTG_DOEPxCTL, ep_num) |= DWC_OTG_DOEP0CTL_EPENA + | DWC_OTG_DOEP0CTL_CNAK; + } + mark_ep_as_prematured(dev, ep_addr, false); + } + else if (!ep_num && urb != NULL && dev->private_data.ep0tsiz_pktcnt) { /* We are still expecting data! */ diff --git a/lib/usbd/class/usbd_msc.c b/lib/usbd/class/usbd_msc.c index 5b5d7d53..fae6200c 100644 --- a/lib/usbd/class/usbd_msc.c +++ b/lib/usbd/class/usbd_msc.c @@ -664,15 +664,15 @@ static void buf_send_to_host_callback(usbd_device *dev, usbd_urb_id urb_id) { (void) urb_id; + usbd_msc *ms = transfer->user_data; + struct usb_msc_trans *trans = &ms->trans; if (status != USBD_SUCCESS) { - try_resubmit(dev, transfer, status); + trans->csw.bCSWStatus = USB_MSC_CSW_STATUS_FAILED; + csw_send_to_host(ms, trans); return; } - usbd_msc *ms = transfer->user_data; - struct usb_msc_trans *trans = &ms->trans; - trans->byte_count += transfer->transferred; if (trans->byte_count < trans->bytes_to_send) { @@ -804,6 +804,23 @@ bool usbd_msc_setup_ep0(usbd_msc *ms, return true; }} } + switch (setup_data->bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (setup_data->wValue == USB_FEAT_ENDPOINT_HALT) { + uint8_t ep_addr = setup_data->wIndex; + /* according to HAL: + * epIN now should terminate all URB and report CSW with fail + * epOUT here can reset ep and continue receive current URB + */ + //usbd_transfer_cancel_ep(dev, ep_addr); + usbd_urb* urb = usbd_find_active_urb(dev, ep_addr); + if (urb == 0) + return false; + usbd_transfer_cancel(dev, urb->id); + return true; + } + break; + } return false; } diff --git a/lib/usbd/usbd_transfer.c b/lib/usbd/usbd_transfer.c index fccc0d22..4f76e26f 100644 --- a/lib/usbd/usbd_transfer.c +++ b/lib/usbd/usbd_transfer.c @@ -530,6 +530,9 @@ static void detach_from_active(usbd_device *dev, usbd_urb *item) void usbd_urb_complete(usbd_device *dev, usbd_urb *urb, usbd_transfer_status status) { + //* this allows to ommit urb_cancel on ep in detaching + mark_ep_as_free(dev, urb->transfer.ep_addr, true); + detach_from_active(dev, urb); urb_callback(dev, urb, status); unused_push(dev, urb);