diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 45c1ca74e..7779d4adc 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 3668, 280, 0, 2244}, {"microbit", "examples/serial", 2694, 342, 8, 2248}, - {"wioterminal", "examples/pininterrupt", 7187, 1489, 116, 6888}, + {"wioterminal", "examples/pininterrupt", 6833, 1491, 120, 6888}, // 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_atsamd21_usb.go b/src/machine/machine_atsamd21_usb.go index 7b9d2e14f..e7faec8ec 100644 --- a/src/machine/machine_atsamd21_usb.go +++ b/src/machine/machine_atsamd21_usb.go @@ -337,7 +337,7 @@ func handleUSBSetAddress(setup usb.Setup) bool { // SendUSBInPacket sends a packet for USB (interrupt in / bulk in). func SendUSBInPacket(ep uint32, data []byte) bool { - sendUSBPacket(ep, data, 0) + sendUSBPacket(ep, data) // clear transfer complete flag setEPINTFLAG(ep, sam.USB_DEVICE_EPINTFLAG_TRCPT1) @@ -351,27 +351,28 @@ func SendUSBInPacket(ep uint32, data []byte) bool { // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // //go:noinline -func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { - l := uint16(len(data)) - if 0 < maxsize && maxsize < l { - l = maxsize +func sendUSBPacket(ep uint32, data []byte) { + // Select the corresponding buffer. + buffer := udd_ep_control_cache_buffer[:] + if ep != 0 { + buffer = udd_ep_in_cache_buffer[ep][:] } - // Set endpoint address for sending data - if ep == 0 { - copy(udd_ep_control_cache_buffer[:], data[:l]) - usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_control_cache_buffer)))) - } else { - copy(udd_ep_in_cache_buffer[ep][:], data[:l]) - usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) - } + // Copy the packet to the buffer. + copy(buffer[:len(data)], data) + + // Select the corresponding endpoint descriptor. + endpoint := &usbEndpointDescriptors[ep].DeviceDescBank[1] + + // Set the endpoint address. + endpoint.ADDR.Set(uint32(uintptr(unsafe.Pointer(unsafe.SliceData(buffer))))) // clear multi-packet size which is total bytes already sent - usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) + endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) // set byte count, which is total number of bytes to be sent - usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) - usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits((uint32(l) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + endpoint.PCKSIZE.SetBits((uint32(len(data)) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) } func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { diff --git a/src/machine/machine_atsamd51_usb.go b/src/machine/machine_atsamd51_usb.go index a95089f75..49b44739a 100644 --- a/src/machine/machine_atsamd51_usb.go +++ b/src/machine/machine_atsamd51_usb.go @@ -340,7 +340,7 @@ func handleUSBSetAddress(setup usb.Setup) bool { // SendUSBInPacket sends a packet for USB (interrupt in / bulk in). func SendUSBInPacket(ep uint32, data []byte) bool { - sendUSBPacket(ep, data, 0) + sendUSBPacket(ep, data) // clear transfer complete flag setEPINTFLAG(ep, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT1) @@ -354,27 +354,28 @@ func SendUSBInPacket(ep uint32, data []byte) bool { // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // //go:noinline -func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { - l := uint16(len(data)) - if 0 < maxsize && maxsize < l { - l = maxsize +func sendUSBPacket(ep uint32, data []byte) { + // Select the corresponding buffer. + buffer := udd_ep_control_cache_buffer[:] + if ep != 0 { + buffer = udd_ep_in_cache_buffer[ep][:] } - // Set endpoint address for sending data - if ep == 0 { - copy(udd_ep_control_cache_buffer[:], data[:l]) - usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_control_cache_buffer)))) - } else { - copy(udd_ep_in_cache_buffer[ep][:], data[:l]) - usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) - } + // Copy the packet to the buffer. + copy(buffer[:len(data)], data) + + // Select the corresponding endpoint descriptor. + endpoint := &usbEndpointDescriptors[ep].DeviceDescBank[1] + + // Set the endpoint address. + endpoint.ADDR.Set(uint32(uintptr(unsafe.Pointer(unsafe.SliceData(buffer))))) // clear multi-packet size which is total bytes already sent - usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) + endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) // set byte count, which is total number of bytes to be sent - usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) - usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits((uint32(l) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + endpoint.PCKSIZE.SetBits((uint32(len(data)) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) } func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { diff --git a/src/machine/machine_nrf52840_usb.go b/src/machine/machine_nrf52840_usb.go index 1fa46945f..cacafd47b 100644 --- a/src/machine/machine_nrf52840_usb.go +++ b/src/machine/machine_nrf52840_usb.go @@ -256,7 +256,7 @@ func initEndpoint(ep, config uint32) { // SendUSBInPacket sends a packet for USBHID (interrupt in / bulk in). func SendUSBInPacket(ep uint32, data []byte) bool { - sendUSBPacket(ep, data, 0) + sendUSBPacket(ep, data) // clear transfer complete flag nrf.USBD.INTENCLR.Set(nrf.USBD_INTENCLR_ENDEPOUT0 << 4) @@ -267,33 +267,32 @@ func SendUSBInPacket(ep uint32, data []byte) bool { // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // //go:noinline -func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { +func sendUSBPacket(ep uint32, data []byte) { + // Select the corresponding buffer. count := len(data) - if 0 < int(maxsize) && int(maxsize) < count { - count = int(maxsize) - } - + var buffer []byte if ep == 0 { - copy(udd_ep_control_cache_buffer[:], data[:count]) + buffer = udd_ep_control_cache_buffer[:] if count > usb.EndpointPacketSize { + // The packet must be sent in chunks. sendOnEP0DATADONE.offset = usb.EndpointPacketSize - sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset] + sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[usb.EndpointPacketSize] sendOnEP0DATADONE.count = count - usb.EndpointPacketSize count = usb.EndpointPacketSize } - sendViaEPIn( - ep, - &udd_ep_control_cache_buffer[0], - count, - ) } else { - copy(udd_ep_in_cache_buffer[ep][:], data[:count]) - sendViaEPIn( - ep, - &udd_ep_in_cache_buffer[ep][0], - count, - ) + buffer = udd_ep_in_cache_buffer[ep][:] } + + // Copy the packet to the buffer. + copy(buffer[:len(data)], data) + + // Send the first chunk of the packet. + sendViaEPIn( + ep, + &buffer[0], + count, + ) } func handleEndpointRx(ep uint32) []byte { diff --git a/src/machine/machine_rp2040_usb.go b/src/machine/machine_rp2040_usb.go index 087e3bf03..7f8210199 100644 --- a/src/machine/machine_rp2040_usb.go +++ b/src/machine/machine_rp2040_usb.go @@ -128,7 +128,7 @@ func handleUSBSetAddress(setup usb.Setup) bool { const ackTimeout = 570 rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) - sendUSBPacket(0, []byte{}, 0) + sendUSBPacket(0, []byte{}) // Wait for transfer to complete with a timeout. t := timer.timeElapsed() diff --git a/src/machine/machine_rp2350_usb.go b/src/machine/machine_rp2350_usb.go index ca09262ff..c98327bf1 100644 --- a/src/machine/machine_rp2350_usb.go +++ b/src/machine/machine_rp2350_usb.go @@ -131,7 +131,7 @@ func handleUSBSetAddress(setup usb.Setup) bool { const ackTimeout = 570 rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC) - sendUSBPacket(0, []byte{}, 0) + sendUSBPacket(0, []byte{}) // Wait for transfer to complete with a timeout. t := timer.timeElapsed() diff --git a/src/machine/machine_rp2_usb.go b/src/machine/machine_rp2_usb.go index 297cc9d9c..aa46aa858 100644 --- a/src/machine/machine_rp2_usb.go +++ b/src/machine/machine_rp2_usb.go @@ -72,19 +72,15 @@ func initEndpoint(ep, config uint32) { // SendUSBInPacket sends a packet for USB (interrupt in / bulk in). func SendUSBInPacket(ep uint32, data []byte) bool { - sendUSBPacket(ep, data, 0) + sendUSBPacket(ep, data) return true } // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // //go:noinline -func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { +func sendUSBPacket(ep uint32, data []byte) { count := len(data) - if 0 < int(maxsize) && int(maxsize) < count { - count = int(maxsize) - } - if ep == 0 { if count > usb.EndpointPacketSize { count = usb.EndpointPacketSize @@ -145,7 +141,7 @@ func setEPDataPID(ep uint32, dataOne bool) { } func SendZlp() { - sendUSBPacket(0, []byte{}, 0) + sendUSBPacket(0, []byte{}) } func sendViaEPIn(ep uint32, data []byte, count int) { diff --git a/src/machine/usb.go b/src/machine/usb.go index 968252103..3833c7813 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -81,21 +81,6 @@ func usbSerial() string { return "" } -// strToUTF16LEDescriptor converts a utf8 string into a string descriptor -// note: the following code only converts ascii characters to UTF16LE. In order -// to do a "proper" conversion, we would need to pull in the 'unicode/utf16' -// package, which at the time this was written added 512 bytes to the compiled -// binary. -func strToUTF16LEDescriptor(in string, out []byte) { - out[0] = byte(len(out)) - out[1] = descriptor.TypeString - for i, rune := range in { - out[(i<<1)+2] = byte(rune) - out[(i<<1)+3] = 0 - } - return -} - const cdcLineInfoSize = 7 var ( @@ -137,51 +122,53 @@ var ( usbStallHandler [NumberOfUSBEndpoints]func(usb.Setup) bool ) +var usbLangInfo = [4]byte{ + // length = 4 bytes + 0x04, + // descriptor type = string + 0x03, + // language codes + // 0x0409 = English (United States) + 0x09, 0x04, +} + // sendDescriptor creates and sends the various USB descriptor types that // can be requested by the host. func sendDescriptor(setup usb.Setup) { switch setup.WValueH { case descriptor.TypeConfiguration: - sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength) + sendDescriptorData(usbDescriptor.Configuration, setup.WLength) return + case descriptor.TypeDevice: usbDescriptor.Configure(usbVendorID(), usbProductID()) - sendUSBPacket(0, usbDescriptor.Device, setup.WLength) + sendDescriptorData(usbDescriptor.Device, setup.WLength) return case descriptor.TypeString: switch setup.WValueL { case 0: - usb_trans_buffer[0] = 0x04 - usb_trans_buffer[1] = 0x03 - usb_trans_buffer[2] = 0x09 - usb_trans_buffer[3] = 0x04 - sendUSBPacket(0, usb_trans_buffer[:4], setup.WLength) + sendDescriptorData(usbLangInfo[:], setup.WLength) case usb.IPRODUCT: - b := usb_trans_buffer[:(len(usbProduct())<<1)+2] - strToUTF16LEDescriptor(usbProduct(), b) - sendUSBPacket(0, b, setup.WLength) + sendDescriptorString(usbProduct(), setup.WLength) case usb.IMANUFACTURER: - b := usb_trans_buffer[:(len(usbManufacturer())<<1)+2] - strToUTF16LEDescriptor(usbManufacturer(), b) - sendUSBPacket(0, b, setup.WLength) + sendDescriptorString(usbManufacturer(), setup.WLength) case usb.ISERIAL: - sz := len(usbSerial()) - if sz == 0 { + serial := usbSerial() + if len(serial) == 0 { SendZlp() } else { - b := usb_trans_buffer[:(sz<<1)+2] - strToUTF16LEDescriptor(usbSerial(), b) - sendUSBPacket(0, b, setup.WLength) + sendDescriptorString(serial, setup.WLength) } } + // TODO: why do we do this when WValueL is unknown? return case descriptor.TypeHIDReport: if h, ok := usbDescriptor.HID[setup.WIndex]; ok { - sendUSBPacket(0, h, setup.WLength) + sendDescriptorData(h, setup.WLength) return } case descriptor.TypeDeviceQualifier: @@ -194,6 +181,39 @@ func sendDescriptor(setup usb.Setup) { return } +// sendDescriptorString sends a string descriptor, truncating it to fit maxLen or the buffer size. +// note: the following code only converts ascii characters to UTF16LE. In order +// to do a "proper" conversion, we would need to pull in the 'unicode/utf16' +// package, which at the time this was written added 512 bytes to the compiled +// binary. +// TODO: old comment, re-evaluate +func sendDescriptorString(data string, maxLen uint16) { + if maxLen < 2 { + // Something has gone horribly wrong. + SendZlp() + return + } + + // Clamp the length. + maxEncBytes := min(len(usb_trans_buffer), len(udd_ep_control_cache_buffer), int(maxLen)) + data = data[:min(len(data), (maxEncBytes-2)/2)] + + // Write the header. + buf := usb_trans_buffer[:2*len(data)+2] + hdr, body := buf[:2], buf[2:] + hdr[0] = byte(len(buf)) + hdr[1] = descriptor.TypeString + + // Convert the string to UTF16. + // NOTE: Using range here would cause the length to disagree when multibyte codepoints are present. + for i := 0; i < len(data); i++ { + body[2*i] = byte(data[i]) + body[2*i+1] = 0 + } + + sendUSBPacket(0, buf) +} + func handleStandardSetup(setup usb.Setup) bool { switch setup.BRequest { case usb.GET_STATUS: @@ -206,7 +226,7 @@ func handleStandardSetup(setup usb.Setup) bool { } } - sendUSBPacket(0, usb_trans_buffer[:2], setup.WLength) + sendDescriptorData(usb_trans_buffer[:2], setup.WLength) return true case usb.CLEAR_FEATURE: @@ -251,7 +271,7 @@ func handleStandardSetup(setup usb.Setup) bool { case usb.GET_CONFIGURATION: usb_trans_buffer[0] = usbConfiguration - sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength) + sendDescriptorData(usb_trans_buffer[:1], setup.WLength) return true case usb.SET_CONFIGURATION: @@ -271,7 +291,7 @@ func handleStandardSetup(setup usb.Setup) bool { case usb.GET_INTERFACE: usb_trans_buffer[0] = usbSetInterface - sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength) + sendDescriptorData(usb_trans_buffer[:1], setup.WLength) return true case usb.SET_INTERFACE: @@ -285,6 +305,21 @@ func handleStandardSetup(setup usb.Setup) bool { } } +// sendDescriptorData sends a descriptor, truncating it to fit maxLen or the buffer size. +func sendDescriptorData(data []byte, maxLen uint16) { + data = lenToCap(data) + data = data[:min(len(data), len(udd_ep_control_cache_buffer), int(maxLen))] + sendUSBPacket(0, data) +} + +// Set the cap of the slice to the length. +// This is safe, but cannot be proven by the compiler. +// +//go:nobounds +func lenToCap(b []byte) []byte { + return b[:len(b):len(b)] +} + func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { if len(usbDescriptor.Device) == 0 { usbDescriptor = descriptor.CDC