Преглед на файлове

trying another way for wake

Jariullah Safi преди 4 месеца
родител
ревизия
df0dad2d20
променени са 10 файла, в които са добавени 80 реда и са изтрити 13 реда
  1. 23 6
      ARCHITECTURE.md
  2. 5 0
      OpenPuck/controllers.h
  3. 1 1
      OpenPuck/mode_hidgyro.cpp
  4. 1 1
      OpenPuck/mode_ps5.cpp
  5. 1 1
      OpenPuck/mode_switch_hori.cpp
  6. 1 1
      OpenPuck/mode_switch_pro.cpp
  7. 4 1
      OpenPuck/mode_xinput.cpp
  8. 35 0
      OpenPuck/puck_hid.cpp
  9. 1 0
      OpenPuck/puck_hid.h
  10. 8 2
      OpenPuck/rf_link.cpp

+ 23 - 6
ARCHITECTURE.md

@@ -206,12 +206,23 @@ Because the discovery address is shared, two things prevent one puck from distur
 
 ## Wake from sleep
 
-The puck can wake a sleeping host via `USBDevice.remoteWakeup()` — driven from `rf_link.cpp` on a Steam-button
-short-press or a fresh RF reconnect (guarded by `USBDevice.suspended()`). Every mode advertises the
-remote-wakeup capability in its config descriptor, so the device is always *armed*. The catch is that a host
-only *honors* a resume signal from an allow-listed input device class (HID keyboard/mouse) — 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.
+Waking the host is an **explicit gesture, never a side effect of input**. While the bus is suspended, the puck
+personality sends *nothing* (no `0x45` forwards, no lizard mouse/keyboard, no periodic `0x79`/`0x7B` status) —
+a suspended-bus `sendReport` can itself translate into a host wake, which made the PC wake on any trackpad
+graze and nearly impossible to keep asleep. The only wake triggers are a **Steam-button short press** or a
+**controller connect**, detected in `rf_link.cpp` (guarded by `USBDevice.suspended()`). Each fires two things:
+
+1. `USBDevice.remoteWakeup()` — the device-level USB resume signal; and
+2. `g_active->wakeEvent()` — the controller queues a **wake nudge**: once the bus has resumed, the puck
+   personality sends a deliberate space-bar press+release (keyboard report `0x41`) and a mouse click
+   press+release (report `0x40`) on its own interface (`wakeNudgeTask` in `puck_hid.cpp`). The nudge exists
+   because some hosts ignore a bare resume signal unless real keyboard/mouse input follows it; it can't be
+   sent *during* suspend (reports can't cross a suspended bus), so it's delivered immediately after resume.
+
+Every mode advertises the remote-wakeup capability in its config descriptor, so the device is always *armed*.
+The catch is that a host only *honors* a resume from an allow-listed input device class (HID keyboard/mouse) —
+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.
 
 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
@@ -224,6 +235,12 @@ Note hosts differ in what they honor: Windows keys on the input device class (th
 Deck gates wake per-device in `/sys/.../power/wakeup` regardless of class, so a mode that still won't wake the
 Deck may just need that sysfs flag enabled host-side.
 
+**`bcdDevice` rule:** Windows caches a device's configuration descriptor keyed by `VID:PID:bcdDevice`. If you
+change a mode's interfaces (as adding the wake mouse did) without bumping that mode's `setDeviceVersion(...)`,
+Windows keeps serving the stale cached descriptor — the change is invisible and wake silently breaks, with no
+fix short of the user manually uninstalling the device. So: **whenever you change a mode's USB descriptor, bump
+its `bcdDevice`.** The clean modes were bumped for the wake interface; each `begin()` documents its value.
+
 ## RF reverse-engineering tooling
 
 `rf_diag.cpp` holds everything used to *discover* the protocol — promiscuous capture, config sweeps, bit-exact

+ 5 - 0
OpenPuck/controllers.h

@@ -10,6 +10,10 @@
 //                    modes (Xbox, puck/lizard) build + send their host report here. STREAM-style modes ignore
 //                    it and instead emit at a fixed cadence from task(); they read the decoded g_in directly.
 //   task()        -- called every loop(): streaming emit, handshake/subcommand draining, mode-specific upkeep.
+//   wakeEvent()   -- called from rf_link.cpp when a deliberate wake gesture fires while the host is suspended
+//                    (Steam-button short press, or controller connect). The device-level resume signal
+//                    (USBDevice.remoteWakeup) has already been sent; a controller can additionally queue a
+//                    host-visible input nudge to deliver once the bus resumes (see puck_hid.cpp).
 //   isPuck()      -- true for Steam/Lizard (keep the boot CDC composite; different USB lifecycle in setup()).
 //
 // To add a new controller: implement IController in a new mode_*.cpp, give it a singleton, and wire it into
@@ -23,6 +27,7 @@ public:
   virtual void begin() = 0;
   virtual void onReport45(const uint8_t* rep, bool fresh, uint8_t bodyTlen) { (void)rep; (void)fresh; (void)bodyTlen; }
   virtual void task() {}
+  virtual void wakeEvent() {}
   virtual bool isPuck() const { return false; }
 };
 

+ 1 - 1
OpenPuck/mode_hidgyro.cpp

@@ -55,7 +55,7 @@ static void hidGyroBuild(uint8_t out[63]){
 
 void HidGyroController::begin(){
   USBDevice.setID(0x054C, 0x05C4);
-  USBDevice.setDeviceVersion(0x0100);
+  USBDevice.setDeviceVersion(0x0101);   // bumped from 0x0100 for the added wake-mouse interface (Windows caches config by VID:PID:bcdDevice)
   USBDevice.setManufacturerDescriptor("Sony Computer Entertainment");
   USBDevice.setProductDescriptor("Wireless Controller");
   g_hidGyro.enableOutEndpoint(true);

+ 1 - 1
OpenPuck/mode_ps5.cpp

@@ -57,7 +57,7 @@ static void ps5Build(uint8_t out[63]){
 
 void Ps5Controller::begin(){
   USBDevice.setID(0x054C, 0x0CE6);
-  USBDevice.setDeviceVersion(0x0100);
+  USBDevice.setDeviceVersion(0x0101);   // bumped from 0x0100 for the added wake-mouse interface (Windows caches config by VID:PID:bcdDevice)
   USBDevice.setManufacturerDescriptor("Sony Interactive Entertainment");
   USBDevice.setProductDescriptor("DualSense Wireless Controller");
   g_ps5.enableOutEndpoint(true);

+ 1 - 1
OpenPuck/mode_switch_hori.cpp

@@ -57,7 +57,7 @@ static void switchBuildHoripad(uint8_t out[8]){
 
 void SwitchHoriController::begin(){
   USBDevice.setID(0x0F0D, 0x0092);
-  USBDevice.setDeviceVersion(0x0200);
+  USBDevice.setDeviceVersion(0x0201);   // bumped from 0x0200 for the added wake-mouse interface (Windows caches config by VID:PID:bcdDevice)
   USBDevice.setManufacturerDescriptor("HORI CO.,LTD.");
   USBDevice.setProductDescriptor("POKKEN CONTROLLER");
   g_switch.enableOutEndpoint(true);

+ 1 - 1
OpenPuck/mode_switch_pro.cpp

@@ -203,7 +203,7 @@ static void jcSet(uint8_t rid, hid_report_type_t type, uint8_t const* b, uint16_
 
 void SwitchProController::begin(){
   USBDevice.setID(0x057E, 0x2009);
-  USBDevice.setDeviceVersion(0x0210);
+  USBDevice.setDeviceVersion(0x0211);   // bumped from 0x0210 for the added wake-mouse interface (Windows caches config by VID:PID:bcdDevice)
   USBDevice.setManufacturerDescriptor("Nintendo Co., Ltd.");
   USBDevice.setProductDescriptor("Pro Controller");
   jcBuildStickCal();

+ 4 - 1
OpenPuck/mode_xinput.cpp

@@ -142,7 +142,10 @@ static void rfXboxMouse(const uint8_t* r){
 void XboxController::begin(){
   g_rumble=0;
   USBDevice.setID(0x045E, 0x028E);   // device-level 045E:028E match -> Windows xusb / SDL / Linux xpad all bind it
-  USBDevice.setDeviceVersion(0x0114);
+  // bcdDevice 0x0115 (was 0x0114): Windows caches the config descriptor by VID:PID:bcdDevice, so any change to
+  // this mode's interfaces (here: the wake-mouse interface) MUST bump bcdDevice or Windows serves a stale
+  // descriptor and the change is invisible (no manual Device-Manager cache-clear should ever be needed).
+  USBDevice.setDeviceVersion(0x0115);
   USBDevice.setManufacturerDescriptor("Microsoft");
   USBDevice.setProductDescriptor("Controller");
   g_xinput.setStringDescriptor("Controller"); g_xinput.begin();   // XInput vendor interface (FF/5D/01) -> MI_00

+ 35 - 0
OpenPuck/puck_hid.cpp

@@ -172,6 +172,12 @@ void SteamPuckController::begin(){
 // Forward the controller's report 0x45 to Steam, or drive lizard kb/mouse when Steam is closed. This is a
 // PURELY USB-SIDE decision -- it changes nothing about the RF poll or the host->controller relay.
 void SteamPuckController::onReport45(const uint8_t* rep, bool fresh, uint8_t bodyTlen){
+  // Host asleep -> forward NOTHING automatically. While suspended, every sendReport attempt (lizard mouse from
+  // a trackpad graze, 0x45 forwards) can translate into a host wake -- which made the PC wake on any controller
+  // movement and nearly impossible to keep asleep. Waking is an explicit gesture only: rf_link fires
+  // wakeEvent() (Steam-button short press / controller connect), and task() delivers a deliberate space+click
+  // nudge after the bus resumes.
+  if (USBDevice.suspended()) return;
   if (g_connSlot < 0 || g_connSlot >= NSLOT) return;
   if (lizardActive()){
     rfLizard(rep, &hid[g_connSlot], &hid[g_connSlot], 0x40, 0x41);
@@ -185,10 +191,39 @@ void SteamPuckController::onReport45(const uint8_t* rep, bool fresh, uint8_t bod
   }
 }
 
+// ---- wake nudge: a deliberate space-bar press + mouse click on the puck's OWN kb/mouse reports (0x41/0x40),
+// delivered right after the bus resumes. The resume signal alone is ignored by some hosts unless real input
+// from a keyboard/mouse-class device follows; this provides it. Queued by wakeEvent() (called from rf_link on
+// a Steam-button short press or a controller connect while suspended); stepped here at ~15ms per phase.
+static uint8_t       g_nudgeStep = 0;      // 0=idle, 1=space down, 2=space up, 3=click down, 4=click up
+static unsigned long g_nudgeMs = 0;
+void SteamPuckController::wakeEvent(){
+  g_nudgeStep = 1; g_nudgeMs = millis();
+}
+static void wakeNudgeTask(){
+  if(!g_nudgeStep) return;
+  if(millis()-g_nudgeMs > 5000){ g_nudgeStep=0; return; }            // bus never resumed -> drop the nudge
+  if(USBDevice.suspended()) return;                                   // wait for resume; reports can't cross a suspended bus
+  if(g_connSlot<0 || g_connSlot>=NSLOT || !hid[g_connSlot].ready()) return;
+  static unsigned long stepMs=0;
+  if(millis()-stepMs < 15) return;                                    // pace the press/release edges
+  stepMs=millis();
+  switch(g_nudgeStep){
+    case 1: { uint8_t k[8]={0,0,HID_KEY_SPACE,0,0,0,0,0}; hid[g_connSlot].sendReport(0x41,k,8); break; }
+    case 2: { uint8_t k[8]={0,0,0,0,0,0,0,0};             hid[g_connSlot].sendReport(0x41,k,8); break; }
+    case 3: case 4: {
+      hid_mouse_report_t m; m.buttons=(g_nudgeStep==3)?1:0; m.x=0; m.y=0; m.wheel=0; m.pan=0;
+      hid[g_connSlot].sendReport(0x40,&m,sizeof m); break; }
+  }
+  g_nudgeStep = (g_nudgeStep>=4) ? 0 : (uint8_t)(g_nudgeStep+1);
+}
+
 // USB connection presentation (like the real dongle): report 0x79 = connection state (01=disc, 02=conn),
 // edge-triggered, + periodic 0x7B status. Live-captured: this is what Steam reads to mark the controller
 // connected. Without it Steam shows disconnected even though 0x45 input is streaming.
 void SteamPuckController::task(){
+  wakeNudgeTask();
+  if (USBDevice.suspended()) return;   // no periodic 0x79/0x7B while the host sleeps -- those sends can wake it too
   static bool usbConn=false; static unsigned long last79=0, last7B=0;
   bool conn = (g_connSlot>=0 && millis()-g_connReplyMs < 300);
   if (g_connSlot>=0 && g_connSlot<NSLOT && hid[g_connSlot].ready()){

+ 1 - 0
OpenPuck/puck_hid.h

@@ -22,6 +22,7 @@ public:
   void begin() override;
   void onReport45(const uint8_t* rep, bool fresh, uint8_t bodyTlen) override;
   void task() override;
+  void wakeEvent() override;   // queue the space+click wake nudge (delivered by task() once the bus resumes)
   bool isPuck() const override { return true; }
 };
 extern SteamPuckController g_steamPuck;

+ 8 - 2
OpenPuck/rf_link.cpp

@@ -153,7 +153,10 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t* payload, uint8_t plen){
               if(fresh){
                 bool steamNow=(bb & TB_STEAM)!=0;
                 if(steamNow && !steamWasDown) steamDownMs=millis();                                                            // rising edge: record press time
-                if(!steamNow && steamWasDown && millis()-steamDownMs<1000u && USBDevice.suspended()) USBDevice.remoteWakeup(); // falling edge within 1 s -> short press -> wake
+                if(!steamNow && steamWasDown && millis()-steamDownMs<1000u && USBDevice.suspended()){                          // falling edge within 1 s -> short press -> wake
+                  USBDevice.remoteWakeup();
+                  if(g_active) g_active->wakeEvent();   // queue the post-resume input nudge (space+click on puck modes)
+                }
                 steamWasDown=steamNow;
               }
             }
@@ -249,7 +252,10 @@ void rfLinkTask(){
   if (g_connOn && millis()-g_connCooldown > 2500) { rfConnStep(); }            // connected-mode: poll controller, read input
   { static bool wasRfConn=false;                                               // remote wakeup on new RF controller connection
     bool nowRfConn=(g_connSlot>=0 && millis()-g_connReplyMs<300);
-    if(nowRfConn && !wasRfConn && USBDevice.suspended()) USBDevice.remoteWakeup();
+    if(nowRfConn && !wasRfConn && USBDevice.suspended()){
+      USBDevice.remoteWakeup();
+      if(g_active) g_active->wakeEvent();   // queue the post-resume input nudge (space+click on puck modes)
+    }
     wasRfConn=nowRfConn;
   }
   // QoS: if the current channel is degrading (crcfail+noRx), hop to the next clean candidate (conservative).