From eab43851ac25d2fe748814d9c96749b58b45ec98 Mon Sep 17 00:00:00 2001 From: Konstantin Sharlaimov Date: Mon, 8 Jun 2026 20:37:33 +0200 Subject: [PATCH] fix(usb): support bidirectional endpoints on RP2 and SAMD21/51. Remap CDC and MSC endpoints to share physical endpoint numbers. This change separates IN/OUT directional states for RP2 endpoints to prevent cross-talk, ensures SAMD21 and SAMD51 endpoint configuration merging does not overwrite bidirectionally shared registers, implements missing SAMD endpoint stall and clear methods, and corrects SAMD interrupt loop bounds to iterate over physical endpoint numbers rather than dynamic configuration entries. --- src/machine/machine_atsamd21_usb.go | 28 ++++++++-- src/machine/machine_atsamd51_usb.go | 28 ++++++++-- src/machine/machine_rp2_usb.go | 80 ++++++++++++++++++++--------- src/machine/usb/cdc/cdc.go | 10 ++-- src/machine/usb/msc/msc.go | 9 ++-- src/machine/usb/msc/scsi.go | 4 +- src/machine/usb/msc/setup.go | 70 ++++++++++++------------- 7 files changed, 152 insertions(+), 77 deletions(-) diff --git a/src/machine/machine_atsamd21_usb.go b/src/machine/machine_atsamd21_usb.go index 28e8c6d22..45ba18d45 100644 --- a/src/machine/machine_atsamd21_usb.go +++ b/src/machine/machine_atsamd21_usb.go @@ -175,7 +175,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) { // Now the actual transfer handlers, ignore endpoint number 0 (setup) var i uint32 - for i = 1; i < uint32(len(endPoints)); i++ { + for i = 1; i < NumberOfUSBEndpoints; i++ { // Check if endpoint has a pending interrupt epFlags := getEPINTFLAG(i) setEPINTFLAG(i, epFlags) @@ -193,6 +193,8 @@ func handleUSBIRQ(intr interrupt.Interrupt) { } func initEndpoint(ep, config uint32) { + // Note: Both IN (Bank 1) and OUT (Bank 0) configurations share the same EPCFG register. + // We must use getEPCFG(ep) | ... to avoid clearing/disabling the opposite direction. switch config { case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: // set packet size @@ -202,7 +204,7 @@ func initEndpoint(ep, config uint32) { usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) // set endpoint type - setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos)) + setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)< m.sentBytes && m.cbw.isIn() { // 6.7.2 The Thirteen Cases - Case 5 (Hi > Di): STALL before status - m.stallEndpoint(usb.MSC_ENDPOINT_IN) + m.stallEndpointIn(usb.MSC_ENDPOINT_IN) } else if m.sendZLP { // Send a zero-length packet to force the end of the transfer before we send a CSW m.queuedBytes = 0 diff --git a/src/machine/usb/msc/scsi.go b/src/machine/usb/msc/scsi.go index 4cec23e2f..562a718ea 100644 --- a/src/machine/usb/msc/scsi.go +++ b/src/machine/usb/msc/scsi.go @@ -298,9 +298,9 @@ func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCo if expected > 0 && residue > 0 { if m.cbw.isIn() { - m.stallEndpoint(usb.MSC_ENDPOINT_IN) + m.stallEndpointIn(usb.MSC_ENDPOINT_IN) } else { - m.stallEndpoint(usb.MSC_ENDPOINT_OUT) + m.stallEndpointOut(usb.MSC_ENDPOINT_OUT) } } } diff --git a/src/machine/usb/msc/setup.go b/src/machine/usb/msc/setup.go index 00507aac6..fb4aaf2c2 100644 --- a/src/machine/usb/msc/setup.go +++ b/src/machine/usb/msc/setup.go @@ -52,32 +52,30 @@ func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool { // (c) a Clear Feature HALT to the Bulk-Out endpoint (clear stall OUT) // https://usb.org/sites/default/files/usbmassbulk_10.pdf if m.state == mscStateNeedReset { - wIndex := setup.WIndex & 0x7F // Clear the direction bit from the endpoint address for comparison + wIndex := uint8(setup.WIndex & 0x7F) if wIndex == usb.MSC_ENDPOINT_IN { - m.stallEndpoint(usb.MSC_ENDPOINT_IN) - } else if wIndex == usb.MSC_ENDPOINT_OUT { - m.stallEndpoint(usb.MSC_ENDPOINT_OUT) + if (setup.WIndex & 0x80) != 0 { + m.stallEndpointIn(wIndex) + } else { + m.stallEndpointOut(wIndex) + } } machine.SendZlp() return true } - // Clear the direction bit from the endpoint address for comparison - wIndex := setup.WIndex & 0x7F - - // Clear the IN/OUT stalls if addressed to the endpoint + wIndex := uint8(setup.WIndex & 0x7F) if wIndex == usb.MSC_ENDPOINT_IN { - m.clearStallEndpoint(usb.MSC_ENDPOINT_IN) - ok = true - } - if wIndex == usb.MSC_ENDPOINT_OUT { - m.clearStallEndpoint(usb.MSC_ENDPOINT_OUT) - ok = true - } - // Send a CSW if needed to resume after the IN endpoint stall is cleared - if m.state == mscStateStatus && wIndex == usb.MSC_ENDPOINT_IN { - m.sendCSW(m.respStatus) - ok = true + if (setup.WIndex & 0x80) != 0 { + m.clearStallEndpointIn(wIndex) + ok = true + if m.state == mscStateStatus { + m.sendCSW(m.respStatus) + } + } else { + m.clearStallEndpointOut(wIndex) + ok = true + } } if ok { @@ -120,26 +118,28 @@ func (m *msc) handleReset(setup usb.Setup, wValue uint16) bool { return true } -func (m *msc) stallEndpoint(ep uint8) { - if ep == usb.MSC_ENDPOINT_IN { - m.txStalled = true - machine.USBDev.SetStallEPIn(usb.MSC_ENDPOINT_IN) - } else if ep == usb.MSC_ENDPOINT_OUT { - m.rxStalled = true - machine.USBDev.SetStallEPOut(usb.MSC_ENDPOINT_OUT) - } else if ep == usb.CONTROL_ENDPOINT { +func (m *msc) stallEndpointIn(ep uint8) { + if ep == usb.CONTROL_ENDPOINT { machine.USBDev.SetStallEPIn(usb.CONTROL_ENDPOINT) + return } + m.txStalled = true + machine.USBDev.SetStallEPIn(uint32(ep)) } -func (m *msc) clearStallEndpoint(ep uint8) { - if ep == usb.MSC_ENDPOINT_IN { - machine.USBDev.ClearStallEPIn(usb.MSC_ENDPOINT_IN) - m.txStalled = false - } else if ep == usb.MSC_ENDPOINT_OUT { - machine.USBDev.ClearStallEPOut(usb.MSC_ENDPOINT_OUT) - m.rxStalled = false - } +func (m *msc) stallEndpointOut(ep uint8) { + m.rxStalled = true + machine.USBDev.SetStallEPOut(uint32(ep)) +} + +func (m *msc) clearStallEndpointIn(ep uint8) { + machine.USBDev.ClearStallEPIn(uint32(ep)) + m.txStalled = false +} + +func (m *msc) clearStallEndpointOut(ep uint8) { + machine.USBDev.ClearStallEPOut(uint32(ep)) + m.rxStalled = false } func (m *msc) setStringField(field []byte, value string) {