machine: add USBDevice.Attach and USBDevice.Detach (#5563)

* machine: add USBDevice.Attach and USBDevice.Detach

The USB device is attached to the bus automatically during startup,
before user code has a chance to finish its USB configuration (device
identifiers, extra HID interfaces, ...). Composite devices such as
keyboards may therefore be enumerated by the host with an incomplete
configuration.

Attach and Detach expose the soft-connect control (DP pull-up) so that
an application or library can detach in an init function, complete its
configuration, and attach again to let the host enumerate the finished
device. They can also be used to force re-enumeration without
replugging the cable.

Implemented for atsamd21, atsamd51, nrf52840, rp2040 and rp2350.

* machine: make USB Detach sticky on nrf52840

The USB IRQ handler re-enables the DP pull-up on every power-ready
event, silently undoing an earlier Detach. Guard the pull-up write with
a detached flag so the device stays off the bus until Attach is called.

* machine: add USB Attach and Detach to stm32 and esp32 targets

- stm32f4, stm32f7, stm32h7: implement Attach and Detach using the
  DCTL soft-disconnect bit that Configure already toggles.
- esp32c3, esp32c6, esp32s3: add no-op stubs to keep user code
  portable; the fixed-function USB Serial/JTAG controller has no
  software-controlled soft-connect.

* machine: use Attach and Detach in USB Configure

Replace the direct soft-connect register writes in Configure with the
equivalent Attach and Detach calls on atsamd21, atsamd51, rp2040,
rp2350, stm32f4, stm32f7 and stm32h7.
This commit is contained in:
sago35
2026-08-21 00:17:33 +09:00
committed by GitHub
parent 1958d6ff9d
commit 93bc65479f
10 changed files with 134 additions and 7 deletions
+15 -1
View File
@@ -51,7 +51,7 @@ func (dev *USBDevice) Configure(config UARTConfig) {
sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos)
// attach
sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH)
dev.Attach()
// enable interrupt for end of reset
sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST)
@@ -68,6 +68,20 @@ func (dev *USBDevice) Configure(config UARTConfig) {
dev.initcomplete = true
}
// Attach connects the device to the USB bus, allowing the host to detect and
// enumerate it. It can be used together with Detach to delay enumeration
// until the USB configuration (device identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH)
}
// Detach disconnects the device from the USB bus. To the host this appears
// as if the device was unplugged. A subsequent Attach makes the host
// enumerate the device again.
func (dev *USBDevice) Detach() {
sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_DETACH)
}
func handlePadCalibration() {
// Load Pad Calibration data from non-volatile memory
// This requires registers that are not included in the SVD file.
+15 -1
View File
@@ -51,7 +51,7 @@ func (dev *USBDevice) Configure(config UARTConfig) {
sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos)
// attach
sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH)
dev.Attach()
// enable interrupt for end of reset
sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST)
@@ -71,6 +71,20 @@ func (dev *USBDevice) Configure(config UARTConfig) {
dev.initcomplete = true
}
// Attach connects the device to the USB bus, allowing the host to detect and
// enumerate it. It can be used together with Detach to delay enumeration
// until the USB configuration (device identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH)
}
// Detach disconnects the device from the USB bus. To the host this appears
// as if the device was unplugged. A subsequent Attach makes the host
// enumerate the device again.
func (dev *USBDevice) Detach() {
sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_DETACH)
}
func handlePadCalibration() {
// Load Pad Calibration data from non-volatile memory
// This requires registers that are not included in the SVD file.
+5
View File
@@ -67,6 +67,11 @@ func (dev *USBDevice) SetStallEPOut(ep uint32) {}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
// Attach and Detach are no-ops: the USB Serial/JTAG controller has no
// software-controlled soft-connect, it is always attached to the bus.
func (dev *USBDevice) Attach() {}
func (dev *USBDevice) Detach() {}
// initUSB is intentionally empty — the interp phase evaluates init()
// functions at compile time and cannot access hardware registers.
// Actual hardware setup is deferred to the first Configure() call.
+5
View File
@@ -66,6 +66,11 @@ func (dev *USBDevice) SetStallEPOut(ep uint32) {}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
// Attach and Detach are no-ops: the USB Serial/JTAG controller has no
// software-controlled soft-connect, it is always attached to the bus.
func (dev *USBDevice) Attach() {}
func (dev *USBDevice) Detach() {}
// initUSB is intentionally empty — the interp phase evaluates init()
// functions at compile time and cannot access hardware registers.
// Actual hardware setup is deferred to the first Configure() call.
+5
View File
@@ -67,6 +67,11 @@ func (dev *USBDevice) SetStallEPOut(ep uint32) {}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
// Attach and Detach are no-ops: the USB Serial/JTAG controller has no
// software-controlled soft-connect, it is always attached to the bus.
func (dev *USBDevice) Attach() {}
func (dev *USBDevice) Detach() {}
// initUSB is intentionally empty — the interp phase evaluates init()
// functions at compile time and cannot access hardware registers.
// Actual hardware setup is deferred to the first Configure() call.
+25 -1
View File
@@ -22,6 +22,11 @@ var (
epinen uint32
epouten uint32
easyDMABusy volatile.Register8
// usbDetached keeps the device detached from the bus after Detach: the
// USB IRQ handler re-enables the DP pull-up on every power-ready event,
// which would otherwise silently undo a Detach.
usbDetached bool
)
// enterCriticalSection is used to protect access to easyDMA - only one thing
@@ -89,6 +94,23 @@ func (dev *USBDevice) Configure(config UARTConfig) {
dev.initcomplete = true
}
// Attach connects the device to the USB bus by enabling the DP pull-up,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
usbDetached = false
nrf.USBD.USBPULLUP.Set(1)
}
// Detach disconnects the device from the USB bus by disabling the DP pull-up.
// To the host this appears as if the device was unplugged. A subsequent
// Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
usbDetached = true
nrf.USBD.USBPULLUP.Set(0)
}
func handleUSBIRQ(interrupt.Interrupt) {
if nrf.USBD.EVENTS_SOF.Get() == 1 {
nrf.USBD.EVENTS_SOF.Set(0)
@@ -103,7 +125,9 @@ func handleUSBIRQ(interrupt.Interrupt) {
// Configure control endpoint
initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL)
nrf.USBD.USBPULLUP.Set(1)
if !usbDetached {
nrf.USBD.USBPULLUP.Set(1)
}
usbConfiguration = 0
}
+15
View File
@@ -43,9 +43,24 @@ func (dev *USBDevice) Configure(config UARTConfig) {
rp.USBCTRL_REGS_INTE_SETUP_REQ)
// Present full speed device by enabling pull up on DP
dev.Attach()
}
// Attach connects the device to the USB bus by enabling the DP pull-up,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
rp.USBCTRL_REGS.SIE_CTRL.SetBits(rp.USBCTRL_REGS_SIE_CTRL_PULLUP_EN)
}
// Detach disconnects the device from the USB bus by disabling the DP pull-up.
// To the host this appears as if the device was unplugged. A subsequent
// Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
rp.USBCTRL_REGS.SIE_CTRL.ClearBits(rp.USBCTRL_REGS_SIE_CTRL_PULLUP_EN)
}
func handleUSBIRQ(intr interrupt.Interrupt) {
status := rp.USBCTRL_REGS.INTS.Get()
+16 -1
View File
@@ -43,12 +43,27 @@ func (dev *USBDevice) Configure(config UARTConfig) {
rp.USB_INTE_SETUP_REQ)
// Present full speed device by enabling pull up on DP
rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN)
dev.Attach()
// 12.7.2 Disable phy isolation
rp.USB.SetMAIN_CTRL_PHY_ISO(0x0)
}
// Attach connects the device to the USB bus by enabling the DP pull-up,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN)
}
// Detach disconnects the device from the USB bus by disabling the DP pull-up.
// To the host this appears as if the device was unplugged. A subsequent
// Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
rp.USB.SIE_CTRL.ClearBits(rp.USB_SIE_CTRL_PULLUP_EN)
}
func handleUSBIRQ(intr interrupt.Interrupt) {
status := rp.USB.INTS.Get()
+17 -2
View File
@@ -254,7 +254,7 @@ func (dev *USBDevice) Configure(config UARTConfig) {
otgPower.PCGCCTL.Set(0)
// Soft-disconnect now (after CSRST reset DCTL to its default connected state).
otgDevice.DCTL.SetBits(dctlSDIS)
dev.Detach()
// ---- 7. Configure data FIFOs --------------------------------------------
@@ -311,11 +311,26 @@ func (dev *USBDevice) Configure(config UARTConfig) {
// ---- 12. Connect to host (clear soft-disconnect) -----------------------
otgDevice.DCTL.ClearBits(dctlSDIS)
dev.Attach()
dev.initcomplete = true
}
// Attach connects the device to the USB bus by releasing soft disconnect,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
otgDevice.DCTL.ClearBits(dctlSDIS)
}
// Detach disconnects the device from the USB bus by asserting soft
// disconnect. To the host this appears as if the device was unplugged. A
// subsequent Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
otgDevice.DCTL.SetBits(dctlSDIS)
}
// handleUSBIRQ is the OTG FS interrupt handler, dispatching on GINTSTS bits.
func handleUSBIRQ(intr interrupt.Interrupt) {
status := stm32.OTG_FS_GLOBAL.GINTSTS.Get() &
+16 -1
View File
@@ -206,7 +206,7 @@ func (dev *USBDevice) Configure(config UARTConfig) {
// Stay soft-disconnected until configuration is complete; CSRST left
// DCTL at its default "connected" state.
usbOTG.DCTL.SetBits(DCTL_SDIS)
dev.Detach()
// 5. Force device mode now that the core is out of reset. The mode
// change takes effect only after up to 25 ms (RM0433); poll GINTSTS.CMOD
@@ -265,9 +265,24 @@ func (dev *USBDevice) Configure(config UARTConfig) {
dev.initcomplete = true
// Release soft-disconnect: pulls D+ high, making device visible to host.
dev.Attach()
}
// Attach connects the device to the USB bus by releasing soft disconnect,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
usbOTG.DCTL.ClearBits(DCTL_SDIS)
}
// Detach disconnects the device from the USB bus by asserting soft
// disconnect. To the host this appears as if the device was unplugged. A
// subsequent Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
usbOTG.DCTL.SetBits(DCTL_SDIS)
}
func initEndpoint(ep, config uint32) {
if ep == 0 {
// Control endpoint