Przeglądaj źródła

refactor working

Jari 3 miesięcy temu
rodzic
commit
5271a29dfb

+ 1 - 0
OpenPuck/OpenPuck.ino

@@ -217,6 +217,7 @@ void setup()
 	// Route all device->host HID sends through the usbd task (SOF drain) so loop() never calls tud_* directly
 	// -> the cross-task blocking-defer that deadlocked the loop under comms load can no longer happen.
 	usbTxBegin();
+	webusbInit(); // also drain the WebUSB status blob from the usbd task (its flush() can block loop() too)
 	hapticInit();
 	static const char *MODE_NAME[] = {
 		"STEAM(puck)",	       "XBOX(xinput+mouse)",

+ 7 - 4
OpenPuck/mode_xinput.cpp

@@ -256,14 +256,15 @@ static void xinputSend(uint8_t slot, uint16_t buttons, uint8_t lt, uint8_t rt,
 	r[12] = ry & 0xFF;
 	r[13] = ry >> 8;
 	memset(r + 14, 0, 6);
-	// Hand off to the usbd task: the xfer is issued from usbTxDrainHook() (SOF), not here, so loop() never
+	// Hand off to the usbd task: the xfer is issued from xiSofDrain() (SOF), not here, so loop() never
 	// calls into the dcd transfer path (which can block on the device event queue under load).
 	S.txPending = true;
 }
 
-// usbd-task drain (called from tud_sof_cb via usbTxDrainHook). Issues the deferred XInput IN transfers. Runs at
-// higher priority than loop(), so the claim->xfer is atomic w.r.t. xinputSend() refilling inBuf -- no torn DMA.
-extern "C" void usbTxDrainHook(void)
+// usbd-task drain (registered with usbTxRegisterDrain, called from tud_sof_cb). Issues the deferred XInput IN
+// transfers. Runs at higher priority than loop(), so the claim->xfer is atomic w.r.t. xinputSend() refilling
+// inBuf -- no torn DMA.
+static void xiSofDrain(void)
 {
 	if (!tud_mounted())
 		return;
@@ -489,6 +490,8 @@ void XboxController::beginPool()
 	g_mouse.begin();
 	for (int s = 0; s < NSLOT; s++)
 		g_xinput[s].setStringDescriptor("Controller");
+	// Drain the deferred XInput IN transfers from the usbd task every SOF (only registered in this mode).
+	usbTxRegisterDrain(xiSofDrain);
 }
 void XboxController::mountSlots(uint8_t k)
 {

+ 28 - 11
OpenPuck/usb_tx.cpp

@@ -95,22 +95,39 @@ void usbTxDrain(void)
 	}
 }
 
-// Weak no-op default; mode_xinput overrides it to flush its raw custom-class IN endpoints from the usbd task.
-extern "C" __attribute__((weak)) void usbTxDrainHook(void)
+// Per-frame drain callbacks for non-HID senders (XInput endpoint, WebUSB blob). Registered at setup(),
+// invoked from tud_sof_cb on the usbd task. Fixed, tiny capacity; registration is setup-only (single-threaded)
+// so no locking is needed and the SOF reader sees a stable list.
+#define TX_DRAIN_MAX 4
+static usbTxDrainFn g_drainFns[TX_DRAIN_MAX];
+static uint8_t g_nDrain;
+
+void usbTxRegisterDrain(usbTxDrainFn fn)
 {
+	if (fn && g_nDrain < TX_DRAIN_MAX)
+		g_drainFns[g_nDrain++] = fn;
 }
 
-// TinyUSB dispatches tud_sof_cb from tud_task() on the usbd task once per USB (micro)frame. Overriding the weak
-// default routes that into our drain. Keep it minimal -- it runs every ~1 ms.
-extern "C" void tud_sof_cb(uint32_t frame_count)
+// Drain trigger: a DEDICATED FreeRTOS task -- NOT tud_sof_cb. SOF proved unusable here: TinyUSB gates the SOF
+// callback on `sof_consumer`, which configuration_reset() wipes on every bus reset (tu_varclr(&_usbd_dev)), so
+// after the host's first reset tud_sof_cb silently stops firing and NO device->host data flows (0x45 input,
+// the 0x79 connect report, streamed reports, the WebUSB blob -- all dead, while RF keeps working). A plain task
+// is ungated and always runs. Crucially it is a SEPARATE task from loop(): if a sendReport ever blocks on a
+// full usbd event queue, this task just blocks/yields -- loop() keeps running and feeding the ~8 s watchdog,
+// which is the whole reason we moved sends off loop() in the first place.
+static void usbTxTask(void *arg)
 {
-	(void)frame_count;
-	usbTxDrain();
-	usbTxDrainHook();
+	(void)arg;
+	for (;;) {
+		usbTxDrain();
+		for (uint8_t i = 0; i < g_nDrain; i++)
+			g_drainFns[i]();
+		vTaskDelay(1); // ~1 ms, matching the USB frame cadence
+	}
 }
-
 void usbTxBegin(void)
 {
-	// SOF callbacks are off by default (they are high frequency); turn them on so usbTxDrain() runs.
-	tud_sof_cb_enable(true);
+	// 512 words (2 KB): covers the deepest drain path (webusbSendBlob's 115 B frame + the tud_* call chain).
+	// TASK_PRIO_LOW = same as loop(): cooperative, won't preempt the RF poll timing; vTaskDelay yields each ms.
+	xTaskCreate(usbTxTask, "usbtx", 512, NULL, TASK_PRIO_LOW, NULL);
 }

+ 7 - 5
OpenPuck/usb_tx.h

@@ -31,8 +31,10 @@ void usbTxBegin(void);
 // Drain pending reports (one ready report per destination per call). usbd-task only -- invoked by tud_sof_cb.
 void usbTxDrain(void);
 
-// usbd-task drain hook for senders that don't go through Adafruit_USBD_HID (the XInput custom-class endpoint).
-// tud_sof_cb calls this right after usbTxDrain(), on the usbd task. A weak no-op default lives in usb_tx.cpp;
-// mode_xinput overrides it to flush its raw IN endpoints, so its gamepad report is also issued off the loop
-// task. extern "C" so the override links regardless of translation unit.
-extern "C" void usbTxDrainHook(void);
+// Register a callback to run every USB frame ON THE usbd TASK (from tud_sof_cb, right after usbTxDrain()).
+// This is how senders that DON'T go through Adafruit_USBD_HID get their transmits off the loop task too:
+//   - mode_xinput flushes its raw custom-class IN endpoints;
+//   - webusb_config sends the status blob (its usb_web.flush() can otherwise block the loop on a full queue).
+// Call once at setup() (before usbTxBegin()). Safe no-op if usb isn't up yet. Capacity is small + fixed.
+typedef void (*usbTxDrainFn)(void);
+void usbTxRegisterDrain(usbTxDrainFn fn);

+ 30 - 5
OpenPuck/webusb_config.cpp

@@ -7,11 +7,18 @@
 #include "triton.h" // g_in raw IMU (diagnostic readout)
 #include "build_info.h"
 #include "fault_diag.h"
+#include "usb_tx.h"
 #include <Arduino.h>
 #include <string.h>
 
 Adafruit_USBD_WebUSB usb_web;
 
+// Panel-blob send is deferred to the usbd task: webusbPoll() (loop) just sets this, and webusbSofDrain()
+// (registered with usb_tx, runs from tud_sof_cb on the usbd task) does the actual write+flush. usb_web.flush()
+// goes through the same blocking osd_queue_send path as HID sends, so doing it from loop() could stall the
+// loop and trip the watchdog under load -- with the panel open during heavy use that was a live risk.
+static volatile bool g_blobRequest = false;
+
 // blob payload = [ver=10][mode][mDiv][mFric][qamMap(active)][abSwap(active)][back0..3(active)][connSlot(0xFF=none)][linkUp]
 //                [f1ps_lo][f1ps_hi][pollU100][newps_lo][newps_hi][e7b][relayOp][relaySub][fwdNewOnly]
 //                [qos][persistMode][chordBtn B][chordBtn X][chordBtn Y][pollsps_lo][pollsps_hi]
@@ -145,6 +152,22 @@ static void webusbSendBlob()
 	usb_web.write(p, sizeof p);
 	usb_web.flush();
 }
+
+// Runs on the usbd task (registered via usbTxRegisterDrain -> tud_sof_cb). Sends the blob if loop() asked for
+// one. Keeps every usb_web write/flush off the loop task so it can't block on the device event queue.
+static void webusbSofDrain(void)
+{
+	if (g_blobRequest) {
+		g_blobRequest = false;
+		webusbSendBlob();
+	}
+}
+// Register the SOF drain. Call once from setup() (harmless even if WebUSB isn't enumerated -- webusbSendBlob
+// no-ops while disconnected, and g_blobRequest is only ever set by panel traffic, which needs a connection).
+void webusbInit(void)
+{
+	usbTxRegisterDrain(webusbSofDrain);
+}
 #if OPK_LOG
 // Stream the capture ring (haptics / relayed host commands) to the panel as 0xA6 frames. Frame formats:
 //   entry: [0xA6][L][T=1][age u32 LE][slot][rid][n][bytes n]   (L = 8 + n)
@@ -219,7 +242,8 @@ void webusbPoll()
 			if (n < need)
 				break; // wait for more bytes
 			if (op == 0x01) {
-				webusbSendBlob();
+				g_blobRequest =
+					true; // sent from the usbd task (webusbSofDrain)
 			}
 #if OPK_LOG
 
@@ -297,7 +321,7 @@ void webusbPoll()
 							applyActiveType();
 					}
 					saveCfg();
-					webusbSendBlob();
+					g_blobRequest = true;
 					memmove(buf, buf + need, n - need);
 					n -= need;
 					continue;
@@ -430,12 +454,13 @@ void webusbPoll()
 				}
 				if (persist)
 					saveCfg();
-				webusbSendBlob();
+				g_blobRequest = true;
 			} else if (op == 0x03) {
 				uint8_t m = buf[1];
 				if (modeValid(m) && !USBDevice.suspended()) {
-					webusbSendBlob();
-					usb_web.flush();
+					// best-effort status to the panel before the reboot; the SOF drain
+					// sends it during the delay(40) below (no blocking flush on loop()).
+					g_blobRequest = true;
 					saveMode(m);
 					delay(40);
 					faultDiagArmIntentionalReset();

+ 1 - 0
OpenPuck/webusb_config.h

@@ -12,4 +12,5 @@
 
 extern Adafruit_USBD_WebUSB usb_web;
 
+void webusbInit(); // register the SOF-driven blob sender; call once from setup() after usbTxBegin()
 void webusbPoll(); // drain host commands + reply; call every loop()