mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-03 18:47:47 +00:00
ca584de84a
* machine/usb: support bidirectional endpoints by dynamic registration
407 lines
9.6 KiB
Go
407 lines
9.6 KiB
Go
//go:build sam || nrf52840 || rp2040 || rp2350
|
|
|
|
package machine
|
|
|
|
import (
|
|
"machine/usb"
|
|
"machine/usb/descriptor"
|
|
|
|
"errors"
|
|
)
|
|
|
|
type USBDevice struct {
|
|
initcomplete bool
|
|
InitEndpointComplete bool
|
|
}
|
|
|
|
type usbEndpointEntry struct {
|
|
Endpoint uint32
|
|
Config uint32
|
|
}
|
|
|
|
var (
|
|
USBDev = &USBDevice{}
|
|
USBCDC Serialer
|
|
|
|
endPoints = []usbEndpointEntry{
|
|
{
|
|
Endpoint: usb.CONTROL_ENDPOINT,
|
|
Config: usb.ENDPOINT_TYPE_CONTROL,
|
|
},
|
|
}
|
|
)
|
|
|
|
func initUSB() {
|
|
enableUSBCDC()
|
|
USBDev.Configure(UARTConfig{})
|
|
}
|
|
|
|
// Using go:linkname here because there's a circular dependency between the
|
|
// machine package and the machine/usb/cdc package.
|
|
//
|
|
//go:linkname enableUSBCDC machine/usb/cdc.EnableUSBCDC
|
|
func enableUSBCDC()
|
|
|
|
type Serialer interface {
|
|
WriteByte(c byte) error
|
|
Write(data []byte) (n int, err error)
|
|
Configure(config UARTConfig) error
|
|
Buffered() int
|
|
ReadByte() (byte, error)
|
|
DTR() bool
|
|
RTS() bool
|
|
}
|
|
|
|
var usbDescriptor descriptor.Descriptor
|
|
|
|
func usbVendorID() uint16 {
|
|
if usb.VendorID != 0 {
|
|
return usb.VendorID
|
|
}
|
|
|
|
return usb_VID
|
|
}
|
|
|
|
func usbProductID() uint16 {
|
|
if usb.ProductID != 0 {
|
|
return usb.ProductID
|
|
}
|
|
|
|
return usb_PID
|
|
}
|
|
|
|
func usbManufacturer() string {
|
|
if usb.Manufacturer != "" {
|
|
return usb.Manufacturer
|
|
}
|
|
|
|
return usb_STRING_MANUFACTURER
|
|
}
|
|
|
|
func usbProduct() string {
|
|
if usb.Product != "" {
|
|
return usb.Product
|
|
}
|
|
|
|
return usb_STRING_PRODUCT
|
|
}
|
|
|
|
func usbSerial() string {
|
|
if usb.Serial != "" {
|
|
return usb.Serial
|
|
}
|
|
return ""
|
|
}
|
|
|
|
const cdcLineInfoSize = 7
|
|
|
|
var (
|
|
ErrUSBReadTimeout = errors.New("USB read timeout")
|
|
ErrUSBBytesRead = errors.New("USB invalid number of bytes read")
|
|
ErrUSBBytesWritten = errors.New("USB invalid number of bytes written")
|
|
)
|
|
|
|
var (
|
|
usbEndpointDescriptors [NumberOfUSBEndpoints]descriptor.Device
|
|
|
|
isEndpointHalt = false
|
|
isRemoteWakeUpEnabled = false
|
|
|
|
usbConfiguration uint8
|
|
usbSetInterface uint8
|
|
)
|
|
|
|
//go:align 4
|
|
var udd_ep_control_cache_buffer [256]uint8
|
|
|
|
//go:align 4
|
|
var udd_ep_in_cache_buffer [NumberOfUSBEndpoints][64]uint8
|
|
|
|
//go:align 4
|
|
var udd_ep_out_cache_buffer [NumberOfUSBEndpoints][64]uint8
|
|
|
|
// usb_trans_buffer max size is 255 since that is max size
|
|
// for a descriptor (bLength is 1 byte), and the biggest use
|
|
// for this buffer is to transmit string descriptors. If
|
|
// this buffer is used for new purposes in future the length
|
|
// must be revisited.
|
|
var usb_trans_buffer [255]uint8
|
|
|
|
var (
|
|
usbTxHandler [NumberOfUSBEndpoints]func()
|
|
usbRxHandler [NumberOfUSBEndpoints]func([]byte) bool
|
|
usbSetupHandler [usb.NumberOfInterfaces]func(usb.Setup) bool
|
|
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:
|
|
sendDescriptorData(usbDescriptor.Configuration, setup.WLength)
|
|
return
|
|
|
|
case descriptor.TypeDevice:
|
|
usbDescriptor.Configure(usbVendorID(), usbProductID())
|
|
sendDescriptorData(usbDescriptor.Device, setup.WLength)
|
|
return
|
|
|
|
case descriptor.TypeString:
|
|
switch setup.WValueL {
|
|
case 0:
|
|
sendDescriptorData(usbLangInfo[:], setup.WLength)
|
|
|
|
case usb.IPRODUCT:
|
|
sendDescriptorString(usbProduct(), setup.WLength)
|
|
|
|
case usb.IMANUFACTURER:
|
|
sendDescriptorString(usbManufacturer(), setup.WLength)
|
|
|
|
case usb.ISERIAL:
|
|
serial := usbSerial()
|
|
if len(serial) == 0 {
|
|
SendZlp()
|
|
} else {
|
|
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 {
|
|
sendDescriptorData(h, setup.WLength)
|
|
return
|
|
}
|
|
case descriptor.TypeDeviceQualifier:
|
|
// skip
|
|
default:
|
|
}
|
|
|
|
// do not know how to handle this message, so return zero
|
|
SendZlp()
|
|
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))
|
|
// Write the header.
|
|
buf := usb_trans_buffer[:min(2*len(data)+2, maxEncBytes)]
|
|
hdr, body := buf[:2], buf[2:]
|
|
|
|
// hdr[0] (bLength) should convey the "original total string length" before being limited by the host's maxLen.
|
|
hdr[0] = byte(2*len(data) + 2)
|
|
hdr[1] = descriptor.TypeString
|
|
|
|
// Convert the string to UTF16.
|
|
// NOTE: Using range here would cause the length to disagree when multibyte codepoints are present.
|
|
limit := min(len(data), len(body)/2)
|
|
for i := 0; i < limit; 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:
|
|
usb_trans_buffer[0] = 0
|
|
usb_trans_buffer[1] = 0
|
|
|
|
if setup.BmRequestType != 0 { // endpoint
|
|
if isEndpointHalt {
|
|
usb_trans_buffer[0] = 1
|
|
}
|
|
}
|
|
|
|
sendDescriptorData(usb_trans_buffer[:2], setup.WLength)
|
|
return true
|
|
|
|
case usb.CLEAR_FEATURE:
|
|
if setup.WValueL == 1 { // DEVICEREMOTEWAKEUP
|
|
isRemoteWakeUpEnabled = false
|
|
} else if setup.WValueL == 0 { // ENDPOINTHALT
|
|
if idx := setup.WIndex & 0x7F; idx < NumberOfUSBEndpoints && usbStallHandler[idx] != nil {
|
|
// Host has requested to clear an endpoint stall. If the request is addressed to
|
|
// an endpoint with a configured StallHandler, forward the message on.
|
|
// The 0x7F mask is used to clear the direction bit from the endpoint number
|
|
return usbStallHandler[idx](setup)
|
|
}
|
|
isEndpointHalt = false
|
|
}
|
|
SendZlp()
|
|
return true
|
|
|
|
case usb.SET_FEATURE:
|
|
if setup.WValueL == 1 { // DEVICEREMOTEWAKEUP
|
|
isRemoteWakeUpEnabled = true
|
|
} else if setup.WValueL == 0 { // ENDPOINTHALT
|
|
if idx := setup.WIndex & 0x7F; idx < NumberOfUSBEndpoints && usbStallHandler[idx] != nil {
|
|
// Host has requested to stall an endpoint. If the request is addressed to
|
|
// an endpoint with a configured StallHandler, forward the message on.
|
|
// The 0x7F mask is used to clear the direction bit from the endpoint number
|
|
return usbStallHandler[idx](setup)
|
|
}
|
|
isEndpointHalt = true
|
|
}
|
|
SendZlp()
|
|
return true
|
|
|
|
case usb.SET_ADDRESS:
|
|
return handleUSBSetAddress(setup)
|
|
|
|
case usb.GET_DESCRIPTOR:
|
|
sendDescriptor(setup)
|
|
return true
|
|
|
|
case usb.SET_DESCRIPTOR:
|
|
return false
|
|
|
|
case usb.GET_CONFIGURATION:
|
|
usb_trans_buffer[0] = usbConfiguration
|
|
sendDescriptorData(usb_trans_buffer[:1], setup.WLength)
|
|
return true
|
|
|
|
case usb.SET_CONFIGURATION:
|
|
if setup.BmRequestType&usb.REQUEST_RECIPIENT == usb.REQUEST_DEVICE {
|
|
for _, entry := range endPoints {
|
|
if entry.Endpoint == usb.CONTROL_ENDPOINT {
|
|
continue
|
|
}
|
|
initEndpoint(uint32(entry.Endpoint), entry.Config)
|
|
}
|
|
|
|
usbConfiguration = setup.WValueL
|
|
USBDev.InitEndpointComplete = true
|
|
|
|
SendZlp()
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
case usb.GET_INTERFACE:
|
|
usb_trans_buffer[0] = usbSetInterface
|
|
sendDescriptorData(usb_trans_buffer[:1], setup.WLength)
|
|
return true
|
|
|
|
case usb.SET_INTERFACE:
|
|
usbSetInterface = setup.WValueL
|
|
|
|
SendZlp()
|
|
return true
|
|
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
// Initialization of endpoints is required even for non-CDC
|
|
ConfigureUSBEndpoint(usbDescriptor,
|
|
[]usb.EndpointConfig{
|
|
{
|
|
Index: usb.CDC_ENDPOINT_ACM,
|
|
IsIn: true,
|
|
Type: usb.ENDPOINT_TYPE_INTERRUPT,
|
|
},
|
|
{
|
|
Index: usb.CDC_ENDPOINT_OUT,
|
|
IsIn: false,
|
|
Type: usb.ENDPOINT_TYPE_BULK,
|
|
RxHandler: rxHandler,
|
|
},
|
|
{
|
|
Index: usb.CDC_ENDPOINT_IN,
|
|
IsIn: true,
|
|
Type: usb.ENDPOINT_TYPE_BULK,
|
|
TxHandler: txHandler,
|
|
},
|
|
},
|
|
[]usb.SetupConfig{
|
|
{
|
|
Index: usb.CDC_ACM_INTERFACE,
|
|
Handler: setupHandler,
|
|
},
|
|
})
|
|
}
|
|
|
|
func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) {
|
|
usbDescriptor = desc
|
|
|
|
for _, ep := range epSettings {
|
|
if ep.IsIn {
|
|
endPoints = append(endPoints, usbEndpointEntry{
|
|
Endpoint: uint32(ep.Index),
|
|
Config: uint32(ep.Type | usb.EndpointIn),
|
|
})
|
|
if ep.TxHandler != nil {
|
|
usbTxHandler[ep.Index] = ep.TxHandler
|
|
}
|
|
} else {
|
|
endPoints = append(endPoints, usbEndpointEntry{
|
|
Endpoint: uint32(ep.Index),
|
|
Config: uint32(ep.Type | usb.EndpointOut),
|
|
})
|
|
if ep.RxHandler != nil {
|
|
usbRxHandler[ep.Index] = func(b []byte) bool {
|
|
ep.RxHandler(b)
|
|
return true
|
|
}
|
|
} else if ep.DelayRxHandler != nil {
|
|
usbRxHandler[ep.Index] = ep.DelayRxHandler
|
|
}
|
|
}
|
|
if ep.StallHandler != nil {
|
|
usbStallHandler[ep.Index] = ep.StallHandler
|
|
}
|
|
}
|
|
|
|
for _, s := range setup {
|
|
usbSetupHandler[s.Index] = s.Handler
|
|
}
|
|
}
|