Browse Source

drop debug for wake

Jariullah Safi 4 months ago
parent
commit
d9bf678d15

+ 19 - 5
ARCHITECTURE.md

@@ -232,12 +232,26 @@ The catch is that a host only *honors* a resume from an allow-listed input devic
 a bare gamepad/vendor/composite presentation gets armed but ignored (notably Windows under Modern Standby).
 That's why Xbox mode (which exposes a boot mouse) woke Windows while the gamepad/puck modes didn't.
 
+Why a function (not just the device) must be wake-capable: device-level remote-wakeup is necessary but not
+sufficient on Windows — a *function driver* must hold a pending wait-wake request (`IRP_MN_WAIT_WAKE`) for the
+system to actually wake, and only the mouse/keyboard class drivers (`mouhid`/`kbdhid`) arm one by default
+(that's the "Allow this device to wake the computer" checkbox). The puck's generic-HID / CDC / WebUSB functions
+don't, so its armed device-level wake was getting ignored. A boot-mouse function fixes that.
+
 The fix: `wake_hid.cpp` registers a do-nothing **boot mouse** interface (the same shape Xbox mode already uses
-to wake Windows) so each mode is classified as a wake-capable input device. It's added in `setup()` for every
-**clean** mode. It is **not** added in puck mode,
-which is already at the nRF52840's 7-data-IN-endpoint limit (CDC + 4 puck HID + WebUSB) — adding it there would
-require freeing an endpoint (dropping CDC or WebUSB, or a bond slot). Puck mode already wakes on Linux/Steam
-Deck; making it wake Windows is the one case this doesn't cover without that tradeoff.
+to wake Windows) so each mode exposes a `mouhid`-bound, wait-wake-armed function. It's added in `setup()` for
+every **clean** mode and for **puck mode on a normal boot**.
+
+**Puck mode and the endpoint budget.** The nRF52840 has only 7 data IN endpoints. The puck composite used all
+7 (CDC = 2, 4 puck HID, WebUSB = 1), leaving no room for a wake interface. So puck mode now **drops the CDC
+serial console by default** (6 IN: 4 HID + WebUSB + wake mouse) — gaining Windows wake at the cost of the
+serial debug console. When you genuinely need the console, arm the **one-shot debug CDC** (WebUSB panel field
+20, or the CDC `D` command): the *next* boot keeps CDC and skips the wake mouse, and the boot after that reverts
+automatically (`g_debugCdcThisBoot`, stored in the reused `Cfg.rsvd0` byte, consumed in `loadCfg` exactly like
+`bootMode`). The two variants use different `bcdDevice` (0x0211 normal / 0x0212 debug) so Windows re-reads when
+you switch. (Trade-off to verify on hardware: the code historically warned that clearing the puck's CDC
+composite hurt simultaneous Steam + Chrome-WebUSB on Windows; the `bcdDevice` bump + MS OS 2.0 descriptors
+should keep WebUSB binding, but confirm Steam pairing + the browser panel still work.)
 
 Note hosts differ in what they honor: Windows keys on the input device class (this fix targets it); Linux/Steam
 Deck gates wake per-device in `/sys/.../power/wakeup` regardless of class, so a mode that still won't wake the

+ 12 - 8
OpenPuck/OpenPuck.ino

@@ -47,16 +47,18 @@ void setup() {
   g_active = controllerFor(g_usbMode);
 
   // ---- USB descriptor rebuild ----
-  // Puck (Steam/Lizard): KEEP the boot CDC composite (do NOT clearConfiguration) and append 4 HID + WebUSB --
-  // this is the layout that worked with Steam running + Chrome WebUSB at the same time. Clearing puck to "save
-  // space" broke that coexistence. Clean controller modes still clear the CDC composite and rebuild bare.
+  // Puck mode NORMALLY drops the CDC serial console (clearConfiguration) to free a USB endpoint for the
+  // wake-mouse interface that lets it wake a sleeping Windows host. The one-shot debug arm (g_debugCdcThisBoot,
+  // set via the WebUSB panel / CDC 'D', auto-reverting after one boot) instead KEEPS the boot CDC composite
+  // for that boot and skips the wake mouse (no endpoint room for both). Clean modes always rebuild bare.
   const bool puckMode = g_active->isPuck();
+  const bool keepCdc  = puckMode && g_debugCdcThisBoot;
   USBDevice.detach(); delay(30);
-  if (puckMode) {
-    USBDevice.setConfigurationBuffer(g_usbCfgDesc, sizeof g_usbCfgDesc);   // headroom over the default 256 B cap
+  if (keepCdc) {
+    USBDevice.setConfigurationBuffer(g_usbCfgDesc, sizeof g_usbCfgDesc);   // keep boot CDC composite (debug boot)
   } else {
     USBDevice.clearConfiguration();
-    USBDevice.setConfigurationBuffer(g_usbCfgDesc, sizeof g_usbCfgDesc);
+    USBDevice.setConfigurationBuffer(g_usbCfgDesc, sizeof g_usbCfgDesc);   // headroom over the default 256 B cap
   }
 
   // Distinct USB serial PER MODE (must be set AFTER clearConfiguration, which nulls it). Hosts cache USB
@@ -69,8 +71,9 @@ void setup() {
   g_active->begin();   // register this mode's USB interface(s) + set VID/PID/strings
 
   // Boot-mouse wake interface so the host honors USBDevice.remoteWakeup() in this mode (see wake_hid.h).
-  // Skipped in puck mode: its CDC + 4 HID + WebUSB composite already uses all 7 data IN endpoints.
-  if (!puckMode) wakeHidBegin();
+  // Added for every clean mode, and for puck mode on a normal (CDC-dropped) boot. Skipped only on the one-shot
+  // debug boot, where CDC takes the endpoint instead.
+  if (!keepCdc) wakeHidBegin();
 
   // WebUSB config panel -- every mode. Puck: historical CDC+HID+vendor stack (Steam + Chrome can share it).
   // Clean modes: bare gamepad + WebUSB (begin() sets bcdUSB 0x0210 + BOS).
@@ -85,6 +88,7 @@ void setup() {
   hapticInit();   // clear relay/active flags + arm the reconnect block & initial stop burst
   static const char* MODE_NAME[]={"STEAM(puck)","XBOX(xinput+mouse)","SWITCH(horipad)","LIZARD(puck kb/mouse)","SWITCH(pro+gyro)","PS5(dualsense)","HIDGYRO(ds4+motion)"};
   Serial.printf("# copycat up: unit=%s board=%s, mode=%s\n", g_unit, g_board, MODE_NAME[g_usbMode<=MODE_MAX?g_usbMode:0]);
+  if (puckMode) Serial.printf("# puck USB: %s\n", keepCdc ? "DEBUG boot (CDC console on, wake mouse off; reverts next boot)" : "normal (CDC off, wake mouse on)");
   Serial.printf("# session addr %02X%02X%02X%02X/%02X ch%u (discovery on ibex/ch2)\n",
                 g_sessBase[0],g_sessBase[1],g_sessBase[2],g_sessBase[3],g_sessPrefix,g_sessCh);
   // Hardware watchdog: if loop() ever stops feeding it (a wedged radio busy-wait, a HardFault spin, a blocked

+ 10 - 2
OpenPuck/config.cpp

@@ -9,6 +9,9 @@ uint8_t g_chordBtn[3] = { MODE_LIZARD, MODE_XBOX, MODE_SW_HORI };   // back4+B/X
 bool    g_persistMode = false;
 uint8_t g_bootMode = 0xFF;
 
+bool          g_debugCdcThisBoot = false;
+static uint8_t g_debugCdc = 0;   // persisted one-shot arm, stored in Cfg.rsvd0 (1 = keep CDC for the next boot)
+
 int     g_mDiv = 64, g_mFric = 94;
 uint8_t g_abSwap = 0;
 uint8_t g_back[4] = {5,6,7,8};   // L4->LB R4->RB L5->L3 R5->R3 (see codeToXB)
@@ -19,10 +22,10 @@ const uint32_t g_pollUs = POLL_US_DEFAULT;
 
 #define CFG_FILE "/cfg.bin"
 #define CFG_MAGIC 0xC7   // bumped (chordBtn[3]): old cfg ignored -> clean defaults on first boot
-struct Cfg { uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100, persistMode, bootMode, chordBtn[3]; };  // rsvd0 = ex-padSmooth (kept for layout)
+struct Cfg { uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100, persistMode, bootMode, chordBtn[3]; };  // rsvd0 = ex-padSmooth, now the one-shot debug-CDC arm
 
 void saveCfg(){
-  Cfg c={CFG_MAGIC,g_usbMode,(uint8_t)g_mDiv,(uint8_t)g_mFric,0,g_abSwap,
+  Cfg c={CFG_MAGIC,g_usbMode,(uint8_t)g_mDiv,(uint8_t)g_mFric,g_debugCdc,g_abSwap,
          {g_back[0],g_back[1],g_back[2],g_back[3]},(uint8_t)(g_pollUs/100),(uint8_t)(g_persistMode?1:0),g_bootMode,
          {g_chordBtn[0],g_chordBtn[1],g_chordBtn[2]}};
   InternalFS.remove(CFG_FILE); File f(InternalFS);
@@ -36,6 +39,9 @@ void loadCfg(){
       g_mDiv=c.mDiv?c.mDiv:64; g_mFric=c.mFric;
       g_abSwap=c.abSwap; for(int i=0;i<4;i++) g_back[i]=c.back[i];
       g_persistMode = c.persistMode?true:false;
+      // one-shot debug-CDC (Cfg.rsvd0): honor it for THIS boot, then consume so the next boot reverts to normal.
+      g_debugCdcThisBoot = c.rsvd0 ? true : false;
+      if(c.rsvd0){ g_debugCdc = 0; consume = true; }
       // poll rate is fixed (g_pollUs const = POLL_US_DEFAULT). A stale rate from an older build is never
       // applied; rewrite cfg so the persisted byte also matches the new default.
       if(c.pollU100 != (uint8_t)(POLL_US_DEFAULT/100)) consume=true;
@@ -56,3 +62,5 @@ void saveMode(uint8_t m){
   else             { g_bootMode=m; }
   saveCfg();
 }
+
+void armDebugCdcNextBoot(){ g_debugCdc = 1; saveCfg(); }   // next boot keeps CDC; loadCfg() consumes it after

+ 7 - 0
OpenPuck/config.h

@@ -32,6 +32,13 @@ extern uint8_t g_chordBtn[3];   // back4+B/X/Y -> these modes (A always STEAM)
 extern bool    g_persistMode;   // false (default) = always boot Steam; true = boot into last mode
 extern uint8_t g_bootMode;      // one-shot: boot into this mode once then clear (!persistMode + explicit switch)
 
+// One-shot debug CDC. Puck mode normally DROPS the CDC serial console to free the USB endpoint its wake-mouse
+// interface needs (so puck can wake a sleeping Windows host). Arming this keeps CDC for the NEXT boot only --
+// dropping the wake mouse that boot -- so someone can attach the serial debugger; the boot after reverts to
+// normal automatically. Mirrors the g_bootMode one-shot. Armed from the WebUSB panel or CDC 'D' command.
+extern bool    g_debugCdcThisBoot;   // decision for THIS boot: true => keep CDC, skip the wake interface
+void armDebugCdcNextBoot();          // persist the one-shot (caller reboots)
+
 // persisted, runtime-tunable config:
 extern int     g_mDiv, g_mFric; // xbox/lizard mouse sensitivity divisor / friction%
 extern uint8_t g_abSwap;        // 1 = swap A/B and X/Y (Nintendo face-button layout)

+ 4 - 3
OpenPuck/puck_hid.cpp

@@ -156,9 +156,10 @@ void SteamPuckController::begin(){
   USBDevice.setID(0x28DE, 0x1304);
   // Distinct bcdDevice so Windows keys a FRESH usbflags entry (cache is VID:PID:bcdDevice) and actually runs
   // MS OS 2.0 / WinUSB binding for the WebUSB vendor interface -- instead of reusing a stale "no WinUSB" entry
-  // tied to the real Steam Controller (28DE:1304), which has no WebUSB interface. Without this, the device
-  // enumerates and Steam binds it fine, but Chrome can't open WebUSB on Windows.
-  USBDevice.setDeviceVersion(0x0210);
+  // tied to the real Steam Controller (28DE:1304), which has no WebUSB interface. The normal (wake-mouse) and
+  // one-shot debug (CDC) boots present DIFFERENT interface sets, so they use DIFFERENT bcdDevice values --
+  // otherwise Windows would serve one's cached descriptor for the other when you reboot between them.
+  USBDevice.setDeviceVersion(g_debugCdcThisBoot ? 0x0212 : 0x0211);
   USBDevice.setManufacturerDescriptor("Valve Software");
   USBDevice.setProductDescriptor("Steam Controller Puck");
   for (int i = 0; i < NSLOT; i++) {

+ 1 - 0
OpenPuck/serial_console.cpp

@@ -75,6 +75,7 @@ void serialConsolePoll(){
       else if (line[0]=='A'){ g_balen=strtoul(line+1,0,16); g_pcnf1=0; Serial.printf("# balen=%u\n",g_balen); }
       else if (line[0]=='a'){ uint32_t v=strtoul(line+1,0,16); g_rfBase[0]=v>>24;g_rfBase[1]=v>>16;g_rfBase[2]=v>>8;g_rfBase[3]=v; Serial.printf("# base=%08lX\n",(unsigned long)v); if(g_rfListen) rfListenStart(); }
       else if (line[0]=='b'){ for(int i=0;i<NSLOT;i++) if(g_slot[i].used){Serial.printf("slot%d ",i); for(int j=0;j<24;j++)Serial.printf("%02X",g_slot[i].rec[j]); Serial.println();} }
+      else if (line[0]=='D'){ armDebugCdcNextBoot(); Serial.println("# debug-CDC armed: reset to KEEP the serial console one more boot (auto-reverts after; the wake mouse is dropped that boot)"); }
       else if (line[0]=='X'){ g_rfCap=!g_rfCap; g_rfHost=g_rfAuto=g_rfBeacon=g_rfListen=g_rfRaw=g_rfSweep=g_rfRespond=false; g_rfCh=2; g_capV=0; if(g_rfCap) rfCapStart(g_rfCh); Serial.printf("# CAPTURE(listen) %s ch%u on \"ibex\", cycling 1M/2M x BALEN3/4 (use cN to set ch)\n", g_rfCap?"ON":"off",g_rfCh); }
       else if (line[0]=='S'){ g_rfAuto=!g_rfAuto; g_rfHost=g_rfAuto; g_rfBeacon=g_rfListen=g_rfRaw=g_rfSweep=false; g_rfCh=2; g_cfgIdx=0; if(g_rfAuto) applyCfg(0); Serial.printf("# AUTOSWEEP %s\n", g_rfAuto?"ON":"off"); }
       li=0;

+ 4 - 3
OpenPuck/wake_hid.h

@@ -10,9 +10,10 @@
 // USBDevice.remoteWakeup() resume signal driven from rf_link.cpp; this interface only changes how the host
 // classifies us. (A boot keyboard was tried and was worse -- it didn't enumerate on Windows.)
 //
-// NOT used in puck (Steam) mode: that composite (CDC + 4 puck HID + WebUSB) already consumes all 7 of the
-// nRF52840's data IN endpoints, leaving no room for another interface. (Puck mode already wakes on Linux/Deck;
-// giving it a wake interface on Windows would require freeing an endpoint -- see ARCHITECTURE.md.)
+// Added for every clean mode AND for puck mode on a normal boot: puck mode drops its CDC serial console by
+// default (freeing the endpoint this interface needs) so it too can wake Windows. The exception is the one-shot
+// debug boot, where CDC is kept and this interface is skipped (no endpoint room for both) -- see config.h
+// (g_debugCdcThisBoot) and ARCHITECTURE.md.
 #pragma once
 
 void wakeHidBegin();   // register the boot-keyboard wake interface (call from setup() for clean modes)

+ 1 - 0
OpenPuck/webusb_config.cpp

@@ -63,6 +63,7 @@ void webusbPoll(){
           case 15: g_qos = v?1:0; g_hopIdx=0; g_qosBad=0; g_qosCheckMs=millis(); break;  // QoS adaptive channel hopping
           case 16: g_persistMode = v?true:false; break;   // persist last mode across reboots (else always boot Steam)
           case 17: case 18: case 19: if(modeValid(v)) g_chordBtn[f-17]=v; break;   // back4+B/X/Y mode assignments
+          case 20: armDebugCdcNextBoot(); usb_web.flush(); delay(40); NVIC_SystemReset(); break;  // reboot once WITH the CDC serial console (puck mode), then auto-revert
         }
         if(persist) saveCfg();
         webusbSendBlob();