From 1b9fa481214f62e860d5b5e28353a04d7f6e13c9 Mon Sep 17 00:00:00 2001 From: Konstantin Sharlaimov Date: Sun, 17 May 2026 09:08:39 +0200 Subject: [PATCH 1/3] refactor(usb): dynamic endpoint descriptor generation Replace static endpoint variables with EndpointIN and EndpointOUT functions. Allows flexible endpoint remapping across USB configs. Map CDC, HID, MIDI and MSC USB devices to bidirectional endpoints consistently across different configurations. --- builder/sizes_test.go | 6 +- src/machine/usb/descriptor/cdc.go | 9 +- src/machine/usb/descriptor/endpoint.go | 128 +++++++------------------ src/machine/usb/descriptor/hid.go | 15 ++- src/machine/usb/descriptor/joystick.go | 15 ++- src/machine/usb/descriptor/midi.go | 31 +++--- src/machine/usb/descriptor/msc.go | 16 +++- src/machine/usb/usb.go | 14 +-- 8 files changed, 101 insertions(+), 133 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index a51a371d81..e96577d9b5 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 3817, 299, 0, 2252}, - {"microbit", "examples/serial", 2820, 356, 8, 2248}, - {"wioterminal", "examples/pininterrupt", 8036, 1652, 132, 7480}, + {"hifive1b", "examples/echo", 3680, 280, 0, 2252}, + {"microbit", "examples/serial", 2694, 342, 8, 2248}, + {"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/machine/usb/descriptor/cdc.go b/src/machine/usb/descriptor/cdc.go index ec72186e3a..d32611ca7b 100644 --- a/src/machine/usb/descriptor/cdc.go +++ b/src/machine/usb/descriptor/cdc.go @@ -150,6 +150,9 @@ var InterfaceCDCData = InterfaceType{ data: interfaceCDCData[:], } +// EP1 IN : CDC Call Management +// EP2 OUT: CDC OUT +// EP2 IN : CDC IN var CDC = Descriptor{ Device: DeviceCDC.Bytes(), Configuration: Append([][]byte{ @@ -160,9 +163,9 @@ var CDC = Descriptor{ ClassSpecificCDCCallManagement.Bytes(), ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), - EndpointEP1IN.Bytes(), + EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(), InterfaceCDCData.Bytes(), - EndpointEP2OUT.Bytes(), - EndpointEP3IN.Bytes(), + EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), + EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), }), } diff --git a/src/machine/usb/descriptor/endpoint.go b/src/machine/usb/descriptor/endpoint.go index 57a17060c4..c918caed1e 100644 --- a/src/machine/usb/descriptor/endpoint.go +++ b/src/machine/usb/descriptor/endpoint.go @@ -15,104 +15,44 @@ const ( TransferTypeInterrupt ) -var endpointEP1IN = [endpointTypeLen]byte{ - endpointTypeLen, - TypeEndpoint, - 0x81, // EndpointAddress - 0x03, // Attributes - 0x10, // MaxPacketSizeL - 0x00, // MaxPacketSizeH - 0x10, // Interval -} - -var EndpointEP1IN = EndpointType{ - data: endpointEP1IN[:], -} - -var endpointEP2OUT = [endpointTypeLen]byte{ - endpointTypeLen, - TypeEndpoint, - 0x02, // EndpointAddress - 0x02, // Attributes - 0x40, // MaxPacketSizeL - 0x00, // MaxPacketSizeH - 0x00, // Interval -} - -var EndpointEP2OUT = EndpointType{ - data: endpointEP2OUT[:], -} +type EndpointNumber uint8 -var endpointEP3IN = [endpointTypeLen]byte{ - endpointTypeLen, - TypeEndpoint, - 0x83, // EndpointAddress - 0x02, // Attributes - 0x40, // MaxPacketSizeL - 0x00, // MaxPacketSizeH - 0x00, // Interval -} - -var EndpointEP3IN = EndpointType{ - data: endpointEP3IN[:], -} - -var endpointEP4IN = [endpointTypeLen]byte{ - endpointTypeLen, - TypeEndpoint, - 0x84, // EndpointAddress - 0x03, // Attributes - 0x40, // MaxPacketSizeL - 0x00, // MaxPacketSizeH - 0x01, // Interval -} - -var EndpointEP4IN = EndpointType{ - data: endpointEP4IN[:], -} - -var endpointEP5OUT = [endpointTypeLen]byte{ - endpointTypeLen, - TypeEndpoint, - 0x05, // EndpointAddress - 0x03, // Attributes - 0x40, // MaxPacketSizeL - 0x00, // MaxPacketSizeH - 0x01, // Interval -} - -var EndpointEP5OUT = EndpointType{ - data: endpointEP5OUT[:], -} - -// Mass Storage Class bulk in endpoint -var endpointMSCIN = [endpointTypeLen]byte{ - endpointTypeLen, - TypeEndpoint, - 0x86, // EndpointAddress - TransferTypeBulk, // Attributes - 0x40, // MaxPacketSizeL (64 bytes) - 0x00, // MaxPacketSizeH - 0x00, // Interval -} +const ( + EndpointEP1 EndpointNumber = iota + EndpointEP2 + EndpointEP3 + EndpointEP4 +) -var EndpointMSCIN = EndpointType{ - data: endpointMSCIN[:], -} +const ( + maxEndpoints = 4 +) -// Mass Storage Class bulk out endpoint -var endpointMSCOUT = [endpointTypeLen]byte{ - endpointTypeLen, - TypeEndpoint, - 0x07, // EndpointAddress - TransferTypeBulk, // Attributes - 0x40, // MaxPacketSizeL (64 bytes) - 0x00, // MaxPacketSizeH - 0x00, // Interval -} +var ( + endpointEPIn = [maxEndpoints][endpointTypeLen]byte{} + endpointEPOut = [maxEndpoints][endpointTypeLen]byte{} +) -var EndpointMSCOUT = EndpointType{ - data: endpointMSCOUT[:], +func EndpointIN(ep EndpointNumber, transferType uint8, maxPacketSize uint16, interval uint8) EndpointType { + e := EndpointType{data: endpointEPIn[ep][:]} + e.Length(endpointTypeLen) + e.Type(TypeEndpoint) + e.EndpointAddress(uint8(ep+1) | 0x80) // EndpointNumber is 0-based, addresses are 1-based + e.Attributes(transferType) + e.MaxPacketSize(maxPacketSize) + e.Interval(interval) + return e +} + +func EndpointOUT(ep EndpointNumber, transferType uint8, maxPacketSize uint16, interval uint8) EndpointType { + e := EndpointType{data: endpointEPOut[ep][:]} + e.Length(endpointTypeLen) + e.Type(TypeEndpoint) + e.EndpointAddress(uint8(ep + 1)) // EndpointNumber is 0-based, addresses are 1-based + e.Attributes(transferType) + e.MaxPacketSize(maxPacketSize) + e.Interval(interval) + return e } const ( diff --git a/src/machine/usb/descriptor/hid.go b/src/machine/usb/descriptor/hid.go index 06b9801530..6ee9683c0c 100644 --- a/src/machine/usb/descriptor/hid.go +++ b/src/machine/usb/descriptor/hid.go @@ -111,6 +111,11 @@ var ClassHID = ClassHIDType{ data: classHID[:], } +// EP1 IN : CDC Call Management +// EP2 OUT: CDC OUT +// EP2 IN : CDC IN +// EP3 OUT: HID OUT +// EP3 IN : HID IN var CDCHID = Descriptor{ Device: DeviceCDC.Bytes(), Configuration: Append([][]byte{ @@ -121,14 +126,14 @@ var CDCHID = Descriptor{ ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), ClassSpecificCDCCallManagement.Bytes(), - EndpointEP1IN.Bytes(), + EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(), InterfaceCDCData.Bytes(), - EndpointEP2OUT.Bytes(), - EndpointEP3IN.Bytes(), + EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), + EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), InterfaceHID.Bytes(), ClassHID.Bytes(), - EndpointEP4IN.Bytes(), - EndpointEP5OUT.Bytes(), + EndpointIN(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(), + EndpointOUT(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(), }), HID: map[uint16][]byte{ 2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified! diff --git a/src/machine/usb/descriptor/joystick.go b/src/machine/usb/descriptor/joystick.go index 65756e0d63..725c3efa64 100644 --- a/src/machine/usb/descriptor/joystick.go +++ b/src/machine/usb/descriptor/joystick.go @@ -121,6 +121,11 @@ var JoystickDefaultHIDReport = Append([][]byte{ // CDCJoystick requires that you append the JoystickDescriptor // to the Configuration before using. This is in order to support // custom configurations. +// EP1 IN : CDC Call Management +// EP2 OUT: CDC OUT +// EP2 IN : CDC IN +// EP3 OUT: HID OUT +// EP3 IN : HID IN var CDCJoystick = Descriptor{ Device: DeviceJoystick.Bytes(), Configuration: Append([][]byte{ @@ -131,14 +136,14 @@ var CDCJoystick = Descriptor{ ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), ClassSpecificCDCCallManagement.Bytes(), - EndpointEP1IN.Bytes(), + EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(), InterfaceCDCData.Bytes(), - EndpointEP2OUT.Bytes(), - EndpointEP3IN.Bytes(), + EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), + EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), InterfaceHIDJoystick.Bytes(), ClassHIDJoystick.Bytes(), - EndpointEP4IN.Bytes(), - EndpointEP5OUT.Bytes(), + EndpointIN(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(), + EndpointOUT(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(), }), HID: map[uint16][]byte{}, } diff --git a/src/machine/usb/descriptor/midi.go b/src/machine/usb/descriptor/midi.go index fad81f31d5..6524e05787 100644 --- a/src/machine/usb/descriptor/midi.go +++ b/src/machine/usb/descriptor/midi.go @@ -171,10 +171,10 @@ var ClassSpecificMIDIInEndpoint = ClassSpecificType{ const endpointMIDITypeLen = 9 -var endpointEP6IN = [endpointMIDITypeLen]byte{ +var endpointMIDIIN = [endpointMIDITypeLen]byte{ endpointMIDITypeLen, TypeEndpoint, - 0x86, // EndpointAddress + 0x83, // EndpointAddress 0x02, // Attributes 0x40, // MaxPacketSizeL 0x00, // MaxPacketSizeH @@ -183,14 +183,14 @@ var endpointEP6IN = [endpointMIDITypeLen]byte{ 0x00, // sync address } -var EndpointEP6IN = EndpointType{ - data: endpointEP6IN[:], +var EndpointMIDIIN = EndpointType{ + data: endpointMIDIIN[:], } -var endpointEP7OUT = [endpointMIDITypeLen]byte{ +var endpointMIDIOUT = [endpointMIDITypeLen]byte{ endpointMIDITypeLen, TypeEndpoint, - 0x07, // EndpointAddress + 0x03, // EndpointAddress 0x02, // Attributes 0x40, // MaxPacketSizeL 0x00, // MaxPacketSizeH @@ -199,8 +199,8 @@ var endpointEP7OUT = [endpointMIDITypeLen]byte{ 0x00, // sync address } -var EndpointEP7OUT = EndpointType{ - data: endpointEP7OUT[:], +var EndpointMIDIOUT = EndpointType{ + data: endpointMIDIOUT[:], } var configurationCDCMIDI = [configurationTypeLen]byte{ @@ -218,6 +218,11 @@ var ConfigurationCDCMIDI = ConfigurationType{ data: configurationCDCMIDI[:], } +// EP1 IN : CDC Call Management +// EP2 OUT: CDC OUT +// EP2 IN : CDC IN +// EP3 OUT: MIDI OUT (custom endpoint descriptor) +// EP3 IN : MIDI IN (custom endpoint descriptor) var CDCMIDI = Descriptor{ Device: DeviceCDC.Bytes(), Configuration: Append([][]byte{ @@ -228,10 +233,10 @@ var CDCMIDI = Descriptor{ ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), ClassSpecificCDCCallManagement.Bytes(), - EndpointEP1IN.Bytes(), + EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(), InterfaceCDCData.Bytes(), - EndpointEP2OUT.Bytes(), - EndpointEP3IN.Bytes(), + EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), + EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), InterfaceAssociationMIDI.Bytes(), InterfaceAudio.Bytes(), ClassSpecificAudioInterface.Bytes(), @@ -241,9 +246,9 @@ var CDCMIDI = Descriptor{ ClassSpecificMIDIInJack2.Bytes(), ClassSpecificMIDIOutJack1.Bytes(), ClassSpecificMIDIOutJack2.Bytes(), - EndpointEP7OUT.Bytes(), + EndpointMIDIOUT.Bytes(), ClassSpecificMIDIOutEndpoint.Bytes(), - EndpointEP6IN.Bytes(), + EndpointMIDIIN.Bytes(), ClassSpecificMIDIInEndpoint.Bytes(), }), } diff --git a/src/machine/usb/descriptor/msc.go b/src/machine/usb/descriptor/msc.go index 55c6ddd857..42e0088b93 100644 --- a/src/machine/usb/descriptor/msc.go +++ b/src/machine/usb/descriptor/msc.go @@ -52,7 +52,17 @@ var ConfigurationMSC = ConfigurationType{ data: configurationMSC[:], } +var ( + EndpointMSCIN = EndpointIN(EndpointEP3, TransferTypeBulk, 0x40, 0x00) + EndpointMSCOUT = EndpointOUT(EndpointEP3, TransferTypeBulk, 0x40, 0x00) +) + // Mass Storage Class +// EP1 IN : CDC Call Management +// EP2 OUT: CDC OUT +// EP2 IN : CDC IN +// EP3 OUT: MSC OUT +// EP3 IN : MSC IN var MSC = Descriptor{ Device: DeviceCDC.Bytes(), Configuration: Append([][]byte{ @@ -63,10 +73,10 @@ var MSC = Descriptor{ ClassSpecificCDCACM.Bytes(), ClassSpecificCDCUnion.Bytes(), ClassSpecificCDCCallManagement.Bytes(), - EndpointEP1IN.Bytes(), + EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(), InterfaceCDCData.Bytes(), - EndpointEP2OUT.Bytes(), - EndpointEP3IN.Bytes(), + EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), + EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(), InterfaceAssociationMSC.Bytes(), InterfaceMSC.Bytes(), EndpointMSCIN.Bytes(), diff --git a/src/machine/usb/usb.go b/src/machine/usb/usb.go index 40983a9a3c..1d2dad4352 100644 --- a/src/machine/usb/usb.go +++ b/src/machine/usb/usb.go @@ -74,13 +74,13 @@ const ( CONTROL_ENDPOINT = 0 CDC_ENDPOINT_ACM = 1 CDC_ENDPOINT_OUT = 2 - CDC_ENDPOINT_IN = 3 - HID_ENDPOINT_IN = 4 // for Interrupt In - HID_ENDPOINT_OUT = 5 // for Interrupt Out - MIDI_ENDPOINT_IN = 6 // for Bulk In - MIDI_ENDPOINT_OUT = 7 // for Bulk Out - MSC_ENDPOINT_IN = 6 // for Bulk In - MSC_ENDPOINT_OUT = 7 // for Bulk Out + CDC_ENDPOINT_IN = 2 + HID_ENDPOINT_IN = 3 // for Interrupt In + HID_ENDPOINT_OUT = 3 // for Interrupt Out + MIDI_ENDPOINT_IN = 3 // for Bulk In + MIDI_ENDPOINT_OUT = 3 // for Bulk Out + MSC_ENDPOINT_IN = 3 // for Bulk In + MSC_ENDPOINT_OUT = 3 // for Bulk Out // bmRequestType REQUEST_HOSTTODEVICE = 0x00 From e7f4a694106c035a8623d4d0d7d2b1ac7eb55fb0 Mon Sep 17 00:00:00 2001 From: Konstantin Sharlaimov Date: Mon, 8 Jun 2026 20:37:33 +0200 Subject: [PATCH 2/3] 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 28e8c6d223..45ba18d454 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 4cec23e2f2..562a718eaf 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 00507aac69..fb4aaf2c2a 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) { From 71e0018b7319f61f086a2d8f9ca94abf7143c989 Mon Sep 17 00:00:00 2001 From: Konstantin Sharlaimov Date: Mon, 8 Jun 2026 20:46:47 +0200 Subject: [PATCH 3/3] fix(usb): implement endpoint stall for nRF52840. Implement SetStallEPIn, SetStallEPOut, ClearStallEPIn, and ClearStallEPOut methods on the nRF52840 USBDevice struct using the hardware EPSTALL register to support MSC driver requirements. Also, correct the handleUSBIRQ loops to iterate over physical endpoint numbers rather than dynamic configuration entries to prevent missed or incorrectly routed endpoint interrupts. --- builder/sizes_test.go | 6 +++--- src/machine/machine_nrf52840_usb.go | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index e96577d9b5..f7ee7e1b27 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 3680, 280, 0, 2252}, - {"microbit", "examples/serial", 2694, 342, 8, 2248}, - {"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248}, + {"hifive1b", "examples/echo", 3817, 299, 0, 2252}, + {"microbit", "examples/serial", 2820, 356, 8, 2248}, + {"wioterminal", "examples/pininterrupt", 8020, 1652, 132, 7480}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/machine/machine_nrf52840_usb.go b/src/machine/machine_nrf52840_usb.go index 4a2c5d5a7c..0dc222b372 100644 --- a/src/machine/machine_nrf52840_usb.go +++ b/src/machine/machine_nrf52840_usb.go @@ -172,7 +172,7 @@ func handleUSBIRQ(interrupt.Interrupt) { epDataStatus := nrf.USBD.EPDATASTATUS.Get() nrf.USBD.EPDATASTATUS.Set(epDataStatus) var i uint32 - for i = 1; i < uint32(len(endPoints)); i++ { + for i = 1; i < NumberOfUSBEndpoints; i++ { // Check if endpoint has a pending interrupt inDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPIN1<<(i-1)) > 0 outDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPOUT1<<(i-1)) > 0 @@ -191,7 +191,7 @@ func handleUSBIRQ(interrupt.Interrupt) { } // ENDEPOUT[n] events - for i := 0; i < len(endPoints); i++ { + for i := 0; i < NumberOfUSBEndpoints; i++ { if nrf.USBD.EVENTS_ENDEPOUT[i].Get() > 0 { nrf.USBD.EVENTS_ENDEPOUT[i].Set(0) buf := handleEndpointRx(uint32(i)) @@ -367,3 +367,19 @@ func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { return b, nil } + +func (dev *USBDevice) SetStallEPIn(ep uint32) { + nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_IO | nrf.USBD_EPSTALL_STALL) +} + +func (dev *USBDevice) SetStallEPOut(ep uint32) { + nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_STALL) +} + +func (dev *USBDevice) ClearStallEPIn(ep uint32) { + nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_IO) +} + +func (dev *USBDevice) ClearStallEPOut(ep uint32) { + nrf.USBD.EPSTALL.Set(ep) +}