mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 14:32:22 +00:00
Protect ESP32 HWCDC console sessions
This commit is contained in:
1 parent
36435b450f
commit
b724d19d6f
18 files changed
+196
-104
No files matched your search
@@ -1978,37 +1978,8 @@ void halt() {
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
#if defined(ESP32) && defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE == 1 \
|
||||
&& defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT \
|
||||
&& defined(ENABLE_USB_INTERFACE)
|
||||
// Configure HWCDC before begin() allocates its mutex and enables the USB ISR.
|
||||
// The bundled setTxBufferSize() deletes/recreates its ring without taking the
|
||||
// TX mutex or masking that ISR, so even an otherwise-quiet post-begin resize
|
||||
// can race the interrupt handler.
|
||||
// A failed resize deletes HWCDC's existing ring. Try progressively smaller
|
||||
// queues while setup still has no producers, and tell reset cleanup the exact
|
||||
// capacity it must observe before reopening a host session.
|
||||
static const size_t usb_tx_sizes[] = {
|
||||
MESH_ESP32_USB_TX_BUFFER_SIZE, 2048, 1024, 512, 256};
|
||||
size_t usb_tx_capacity = 0;
|
||||
for (size_t candidate : usb_tx_sizes) {
|
||||
usb_tx_capacity = Serial.setTxBufferSize(candidate);
|
||||
if (usb_tx_capacity == candidate) break;
|
||||
}
|
||||
Serial.setTxTimeoutMs(5);
|
||||
#endif
|
||||
mesh::prepareUsbLoggingPort();
|
||||
Serial.begin(115200);
|
||||
#if defined(ESP32) && defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE == 1 \
|
||||
&& defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT \
|
||||
&& defined(ENABLE_USB_INTERFACE)
|
||||
// If every pre-begin allocation failed, begin() may have recovered by
|
||||
// creating its built-in 256-byte ring. Discover that ring without replacing
|
||||
// it after the ISR is live; a zero result leaves reset cleanup quarantined.
|
||||
if (usb_tx_capacity == 0) {
|
||||
usb_tx_capacity = Serial.availableForWrite();
|
||||
}
|
||||
mesh::setUsbCompanionTxBufferCapacity(usb_tx_capacity);
|
||||
#endif
|
||||
#if MESH_PACKET_LOGGING
|
||||
mesh::serialLogBegin();
|
||||
#endif
|
||||
|
||||
@@ -2402,7 +2402,7 @@ void MyMesh::printRecentRepeatersSerial() {
|
||||
return;
|
||||
}
|
||||
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
if (hasPendingSerialOutput()) {
|
||||
mesh::usbConsolePort().printf("Err - USB output busy\r\n");
|
||||
return;
|
||||
@@ -4845,7 +4845,7 @@ void MyMesh::updateFloodAdvertTimer() {
|
||||
}
|
||||
|
||||
void MyMesh::dumpLogFile() {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
if (hasPendingSerialOutput()) {
|
||||
mesh::usbConsolePort().printf("Err - USB output busy\r\n");
|
||||
return;
|
||||
@@ -4869,7 +4869,7 @@ void MyMesh::dumpLogFile() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
bool MyMesh::hasPendingSerialOutput() const {
|
||||
return serial_log_active || serial_log_eof_pending || serial_recent_next >= 0;
|
||||
}
|
||||
@@ -11536,7 +11536,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char *
|
||||
// Compatibility path for manually defined legacy portable builds. Current
|
||||
// release builds use FULL and do not enter this branch.
|
||||
_cli.handleCommand(sender_timestamp, command, reply);
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
if (sender_timestamp == 0 && serial_log_eof_pending
|
||||
&& strcmp(reply, " EOF") == 0) reply[0] = 0;
|
||||
#endif
|
||||
@@ -11942,7 +11942,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char *
|
||||
#endif
|
||||
} else{
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
if (sender_timestamp == 0 && serial_log_eof_pending
|
||||
&& strcmp(reply, " EOF") == 0) reply[0] = 0;
|
||||
#endif
|
||||
@@ -11956,7 +11956,7 @@ void MyMesh::loop() {
|
||||
// Check radio FIRST to ensure we don't miss incoming packets
|
||||
// MQTT processing runs in a separate FreeRTOS task on Core 0, so we don't call bridge.loop() here
|
||||
mesh::Mesh::loop();
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
servicePendingSerialOutput();
|
||||
#endif
|
||||
_cli.loop();
|
||||
|
||||
@@ -288,7 +288,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
};
|
||||
|
||||
FILESYSTEM* _fs;
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
File serial_log_dump;
|
||||
size_t serial_log_remaining = 0;
|
||||
size_t serial_log_pending_size = 0;
|
||||
@@ -982,7 +982,7 @@ public:
|
||||
}
|
||||
|
||||
void dumpLogFile() override;
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
// Large local-only replies advance between radio service passes.
|
||||
bool hasPendingSerialOutput() const;
|
||||
void servicePendingSerialOutput();
|
||||
|
||||
@@ -71,8 +71,9 @@ static unsigned long userBtnDownAt = 0;
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
mesh::prepareUsbLoggingPort();
|
||||
Serial.begin(115200);
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::beginUsbLoggingPort();
|
||||
#endif
|
||||
#if MESH_PACKET_LOGGING
|
||||
@@ -116,7 +117,7 @@ void setup() {
|
||||
radioinit_attempts = 0;
|
||||
const uint32_t retry_started = millis();
|
||||
while (millis() - retry_started < 60000UL) {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::serviceUsbLoggingPort();
|
||||
mesh::serviceUsbTerminalPort();
|
||||
#endif
|
||||
@@ -224,7 +225,7 @@ void setup() {
|
||||
|
||||
static void __attribute__((noinline)) serviceCommandInterfaces() {
|
||||
bool usb_ready = true;
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::serviceUsbLoggingPort();
|
||||
mesh::serviceUsbTerminalPort();
|
||||
if (mesh::takeUsbTerminalSessionReset()) {
|
||||
@@ -351,7 +352,7 @@ void loop() {
|
||||
if (display_ready) ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::serviceUsbTerminalPort();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1577,7 +1577,7 @@ void MyMesh::updateFloodAdvertTimer() {
|
||||
}
|
||||
|
||||
void MyMesh::dumpLogFile() {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
if (hasPendingSerialOutput()) {
|
||||
mesh::usbConsolePort().printf("Err - USB output busy\r\n");
|
||||
return;
|
||||
@@ -1601,7 +1601,7 @@ void MyMesh::dumpLogFile() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
bool MyMesh::hasPendingSerialOutput() const {
|
||||
return serial_log_active || serial_log_eof_pending;
|
||||
}
|
||||
@@ -2415,7 +2415,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
// handled by the role-independent mesh clock synchronizer
|
||||
} else {
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
if (sender_timestamp == 0 && serial_log_eof_pending
|
||||
&& strcmp(reply, " EOF") == 0) reply[0] = 0;
|
||||
#endif
|
||||
@@ -2433,7 +2433,7 @@ void MyMesh::loop() {
|
||||
// Check radio FIRST to ensure we don't miss incoming packets
|
||||
// MQTT processing can take time, so we prioritize radio reception
|
||||
mesh::Mesh::loop();
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
servicePendingSerialOutput();
|
||||
#endif
|
||||
_cli.loop();
|
||||
|
||||
@@ -158,7 +158,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks,
|
||||
#endif
|
||||
{
|
||||
FILESYSTEM* _fs;
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
File serial_log_dump;
|
||||
size_t serial_log_remaining = 0;
|
||||
size_t serial_log_pending_size = 0;
|
||||
@@ -463,7 +463,7 @@ public:
|
||||
}
|
||||
|
||||
void dumpLogFile() override;
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
// Large local-only replies advance between radio service passes.
|
||||
bool hasPendingSerialOutput() const;
|
||||
void servicePendingSerialOutput();
|
||||
|
||||
@@ -43,8 +43,9 @@ static char ethernet_command[MAX_POST_TEXT_LEN+1];
|
||||
unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
|
||||
|
||||
void setup() {
|
||||
mesh::prepareUsbLoggingPort();
|
||||
Serial.begin(115200);
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::beginUsbLoggingPort();
|
||||
#endif
|
||||
#if MESH_PACKET_LOGGING
|
||||
@@ -157,7 +158,7 @@ void loop() {
|
||||
board.feedWatchdog(the_mesh.getNodePrefs()->system_watchdog_enabled != 0);
|
||||
#endif
|
||||
bool usb_ready = true;
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::serviceUsbLoggingPort();
|
||||
mesh::serviceUsbTerminalPort();
|
||||
if (mesh::takeUsbTerminalSessionReset()) {
|
||||
@@ -219,7 +220,7 @@ void loop() {
|
||||
if (display_ready) ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::serviceUsbTerminalPort();
|
||||
#endif
|
||||
#ifdef TBEAM_1W
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <helpers/ClientPathPersistence.h>
|
||||
#include <helpers/LazyPersistence.h>
|
||||
#include <helpers/radiolib/RXPowerSaving.h>
|
||||
#include <helpers/UsbLogging.h>
|
||||
|
||||
static uint32_t nextRadioApplyRetryDelay(uint8_t& failure_count) {
|
||||
uint8_t shift = failure_count < 5 ? failure_count : 5;
|
||||
@@ -623,14 +624,15 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
|
||||
}
|
||||
}
|
||||
} else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) {
|
||||
Serial.println("ACL:");
|
||||
Stream& console = mesh::usbConsolePort();
|
||||
console.println("ACL:");
|
||||
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||
auto c = acl.getClientByIdx(i);
|
||||
if (c->permissions == 0) continue; // skip deleted entries
|
||||
|
||||
Serial.printf("%02X ", c->permissions);
|
||||
mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE);
|
||||
Serial.printf("\n");
|
||||
console.printf("%02X ", c->permissions);
|
||||
mesh::Utils::printHex(console, c->id.pub_key, PUB_KEY_SIZE);
|
||||
console.print('\n');
|
||||
}
|
||||
reply[0] = 0;
|
||||
} else if (memcmp(command, "io ", 2) == 0) { // io {value}: write, io: read
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "SensorMesh.h"
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
#include <helpers/UsbLogging.h>
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
@@ -59,7 +60,11 @@ static char command[160];
|
||||
static const unsigned long POWERSAVING_FIRST_SLEEP_SECS = 120;
|
||||
|
||||
void setup() {
|
||||
mesh::prepareUsbLoggingPort();
|
||||
Serial.begin(115200);
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::beginUsbLoggingPort();
|
||||
#endif
|
||||
delay(1000);
|
||||
|
||||
board.begin();
|
||||
@@ -124,8 +129,10 @@ void setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("Sensor ID: ");
|
||||
mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println();
|
||||
Stream& console = mesh::usbConsolePort();
|
||||
console.print("Sensor ID: ");
|
||||
mesh::Utils::printHex(console, the_mesh.self_id.pub_key, PUB_KEY_SIZE);
|
||||
console.println();
|
||||
|
||||
command[0] = 0;
|
||||
|
||||
@@ -149,14 +156,23 @@ void loop() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.feedWatchdog(the_mesh.getNodePrefs()->system_watchdog_enabled != 0);
|
||||
#endif
|
||||
bool usb_ready = true;
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::serviceUsbLoggingPort();
|
||||
mesh::serviceUsbTerminalPort();
|
||||
if (mesh::takeUsbTerminalSessionReset()) command[0] = 0;
|
||||
usb_ready = mesh::tryCompleteUsbTerminalSessionReset()
|
||||
&& mesh::canAcceptUsbConsoleCommand();
|
||||
#endif
|
||||
Stream& console = mesh::usbConsolePort();
|
||||
int len = strlen(command);
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
char c = Serial.read();
|
||||
while (usb_ready && console.available() && len < sizeof(command)-1) {
|
||||
char c = console.read();
|
||||
if (c != '\n') {
|
||||
command[len++] = c;
|
||||
command[len] = 0;
|
||||
}
|
||||
Serial.print(c);
|
||||
console.print(c);
|
||||
}
|
||||
if (len == sizeof(command)-1) { // command buffer full
|
||||
command[sizeof(command)-1] = '\r';
|
||||
@@ -168,7 +184,8 @@ void loop() {
|
||||
reply[0] = 0;
|
||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
if (reply[0]) {
|
||||
Serial.print(" -> "); Serial.println(reply);
|
||||
console.print(" -> ");
|
||||
console.println(reply);
|
||||
}
|
||||
|
||||
command[0] = 0; // reset command buffer
|
||||
@@ -180,6 +197,9 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::serviceUsbTerminalPort();
|
||||
#endif
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
|
||||
+4
-4
@@ -24,7 +24,7 @@
|
||||
#define MAX_TRANS_UNIT 255
|
||||
|
||||
#if defined(ARDUINO) && \
|
||||
(defined(NRF52_PLATFORM) || MESH_ESP32_TINYUSB_NONBLOCKING) && \
|
||||
(defined(NRF52_PLATFORM) || MESH_ESP32_USB_CONSOLE_COOPERATIVE) && \
|
||||
((defined(MESH_DEBUG) && MESH_DEBUG) || \
|
||||
(defined(BRIDGE_DEBUG) && BRIDGE_DEBUG) || \
|
||||
(defined(POWERSAVING_DEBUG) && POWERSAVING_DEBUG))
|
||||
@@ -86,7 +86,7 @@ inline size_t nrf52DebugPrintf(const char* format, ...) {
|
||||
|
||||
#if MESH_DEBUG && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#if defined(NRF52_PLATFORM) || MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if defined(NRF52_PLATFORM) || MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
#define MESH_DEBUG_PRINT(F, ...) do { mesh::nrf52DebugPrintf("DEBUG: " F, ##__VA_ARGS__); } while(0)
|
||||
#define MESH_DEBUG_PRINTLN(F, ...) do { mesh::nrf52DebugPrintf("DEBUG: " F "\n", ##__VA_ARGS__); } while(0)
|
||||
#else
|
||||
@@ -99,7 +99,7 @@ inline size_t nrf52DebugPrintf(const char* format, ...) {
|
||||
#endif
|
||||
|
||||
#if BRIDGE_DEBUG && ARDUINO
|
||||
#if defined(NRF52_PLATFORM) || MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if defined(NRF52_PLATFORM) || MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
#define BRIDGE_DEBUG_PRINTLN(F, ...) do { mesh::nrf52DebugPrintf("%s BRIDGE: " F, getLogDateTime(), ##__VA_ARGS__); } while(0)
|
||||
#else
|
||||
#define BRIDGE_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled() && mesh::usbLoggingPort().availableForWrite() > 0) { mesh::usbLoggingPort().printf("%s BRIDGE: " F, getLogDateTime(), ##__VA_ARGS__); } } while(0)
|
||||
@@ -110,7 +110,7 @@ inline size_t nrf52DebugPrintf(const char* format, ...) {
|
||||
|
||||
#if POWERSAVING_DEBUG && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#if defined(NRF52_PLATFORM) || MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
#if defined(NRF52_PLATFORM) || MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
#define POWERSAVING_DEBUG_PRINT(F, ...) do { mesh::nrf52DebugPrintf("POWERSAVING: " F, ##__VA_ARGS__); } while(0)
|
||||
#define POWERSAVING_DEBUG_PRINTLN(F, ...) do { mesh::nrf52DebugPrintf("POWERSAVING: " F "\n", ##__VA_ARGS__); } while(0)
|
||||
#else
|
||||
|
||||
@@ -1124,9 +1124,9 @@ void ESP32Board::enterDeepSleep(uint32_t secs) {
|
||||
sensors.getLocationProvider()->stop();
|
||||
}
|
||||
|
||||
// Native TinyUSB must not wait for a host which has stopped reading.
|
||||
// Keep the original flush behavior for UART and USB-Serial-JTAG targets.
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
// Native USB must not wait for a host which has stopped reading.
|
||||
// Keep the original flush behavior for UART targets.
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::serviceUsbTerminalPort();
|
||||
#else
|
||||
Serial.flush();
|
||||
|
||||
+42
-10
@@ -5,14 +5,9 @@
|
||||
#include <atomic>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(ESP32) && defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE == 1 \
|
||||
&& defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT \
|
||||
&& defined(ENABLE_USB_INTERFACE)
|
||||
#define MESH_ESP32_HWCDC_SESSION_GUARD 1
|
||||
#if MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
#include "esp_idf_version.h"
|
||||
#include "hal/usb_serial_jtag_ll.h"
|
||||
#else
|
||||
#define MESH_ESP32_HWCDC_SESSION_GUARD 0
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || MESH_ESP32_HWCDC_SESSION_GUARD \
|
||||
@@ -296,8 +291,7 @@ static std::atomic<bool> esp32_hwcdc_tx_kick_pending{false};
|
||||
static portMUX_TYPE esp32_hwcdc_session_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
static uint32_t esp32_hwcdc_taken_bus_reset_generation = 0;
|
||||
static bool esp32_hwcdc_event_handler_registered = false;
|
||||
static std::atomic<size_t> esp32_hwcdc_tx_buffer_capacity{
|
||||
MESH_ESP32_USB_TX_BUFFER_SIZE};
|
||||
static std::atomic<size_t> esp32_hwcdc_tx_buffer_capacity{0};
|
||||
// A busy writer or temporary allocation failure may require more than one main
|
||||
// loop to purge. Keep the original detach state/generation across retries so a
|
||||
// failed attempt never reattaches stale bytes or turns into a reboot loop.
|
||||
@@ -530,7 +524,8 @@ static void setPlatformDebugOutputEnabled(bool enabled) {
|
||||
// bytes from racing a binary mOTA/Companion record's capacity preflight.
|
||||
(void)enabled;
|
||||
Serial.setDebugOutput(false);
|
||||
#elif defined(ESP32_PLATFORM) && defined(ENABLE_USB_INTERFACE)
|
||||
#elif defined(ESP32_PLATFORM) \
|
||||
&& (defined(ENABLE_USB_INTERFACE) || MESH_ESP32_HWCDC_SESSION_GUARD)
|
||||
// Arduino-ESP32 log_e()/ESP-IDF diagnostics otherwise write straight to
|
||||
// the same UART/CDC stream used by Binary Companion.
|
||||
#if MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
@@ -789,6 +784,23 @@ void setUsbCompanionTxBufferCapacity(size_t capacity) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void prepareUsbLoggingPort() {
|
||||
#if MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
// HWCDC::setTxBufferSize() deletes/recreates its ring without taking the TX
|
||||
// mutex or masking the USB ISR. Resize only during early setup, before
|
||||
// Serial.begin() creates that mutex and enables the interrupt handler.
|
||||
static const size_t usb_tx_sizes[] = {
|
||||
MESH_ESP32_USB_TX_BUFFER_SIZE, 2048, 1024, 512, 256};
|
||||
size_t capacity = 0;
|
||||
for (size_t candidate : usb_tx_sizes) {
|
||||
capacity = Serial.setTxBufferSize(candidate);
|
||||
if (capacity == candidate) break;
|
||||
}
|
||||
Serial.setTxTimeoutMs(5);
|
||||
setUsbCompanionTxBufferCapacity(capacity);
|
||||
#endif
|
||||
}
|
||||
|
||||
void beginUsbLoggingPort() {
|
||||
// setup() calls this once before role preferences are loaded and again
|
||||
// afterwards. The first call silences framework diagnostics on a protected
|
||||
@@ -802,6 +814,11 @@ void beginUsbLoggingPort() {
|
||||
}
|
||||
#endif
|
||||
#if MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
// If every pre-begin resize failed, begin() may have recovered by allocating
|
||||
// HWCDC's built-in ring. Discover it without replacing a live ISR-owned ring.
|
||||
if (esp32_hwcdc_tx_buffer_capacity.load(std::memory_order_acquire) == 0) {
|
||||
setUsbCompanionTxBufferCapacity(Serial.availableForWrite());
|
||||
}
|
||||
if (!esp32_hwcdc_event_handler_registered) {
|
||||
Serial.onEvent(ARDUINO_HW_CDC_ANY_EVENT, handleEsp32HwcdcEvent);
|
||||
esp32_hwcdc_event_handler_registered = true;
|
||||
@@ -833,6 +850,8 @@ void beginUsbLoggingPort() {
|
||||
void serviceUsbLoggingPort() {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
serviceEsp32TinyUsbPorts();
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
serviceEsp32HwcdcTxKick();
|
||||
#endif
|
||||
#if defined(MESH_DUAL_CDC_LOGGING)
|
||||
const bool connected = dedicated_usb_logging_port_started
|
||||
@@ -1061,6 +1080,8 @@ Stream& usbTerminalPort() {
|
||||
Stream& usbConsolePort() {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
return buffered_esp32_tinyusb_terminal_port;
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
return guarded_esp32_hwcdc_port;
|
||||
#else
|
||||
return Serial;
|
||||
#endif
|
||||
@@ -1070,6 +1091,10 @@ bool canAcceptUsbConsoleCommand() {
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING
|
||||
return buffered_esp32_tinyusb_terminal_port.availableForWrite()
|
||||
>= static_cast<int>(esp32_tinyusb_functional_reserve);
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
// Leave enough room for a complete ordinary CLI response. Larger listings
|
||||
// already retain and retry a short write from their cooperative pump.
|
||||
return guarded_esp32_hwcdc_port.availableForWrite() >= 256;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
@@ -1141,7 +1166,7 @@ bool takeUsbTerminalSessionReset() {
|
||||
}
|
||||
primary_usb_terminal_taken_reset_generation = reset_generation;
|
||||
return true;
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD && defined(ENABLE_USB_INTERFACE)
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
const uint32_t reset_generation =
|
||||
esp32_hwcdc_bus_reset_generation.load(std::memory_order_acquire);
|
||||
if (reset_generation == esp32_hwcdc_taken_bus_reset_generation) {
|
||||
@@ -1181,6 +1206,13 @@ bool tryCompleteUsbTerminalSessionReset() {
|
||||
if ((int32_t)(millis() - settle_until) < 0) return false;
|
||||
return nonblocking_primary_usb_companion_port.tryRunExclusive(
|
||||
completePrimaryUsbSessionReset);
|
||||
#elif MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
if (!esp32_hwcdc_cleanup_pending
|
||||
&& esp32_hwcdc_allowed_generation.load(std::memory_order_acquire)
|
||||
== esp32_hwcdc_access_generation.load(std::memory_order_acquire)) {
|
||||
return true;
|
||||
}
|
||||
return resetUsbCompanionTransport();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
|
||||
@@ -24,6 +24,23 @@
|
||||
#define MESH_ESP32_TINYUSB_NONBLOCKING 0
|
||||
#endif
|
||||
|
||||
// ESP32-S3 USB-Serial/JTAG (HWCDC) has a software TX ring which can retain a
|
||||
// previous host's bytes across a USB bus reset. This capability is independent
|
||||
// of the Companion role: room, repeater, and sensor consoles need the same
|
||||
// session gate and bounded writes.
|
||||
#if defined(ESP32) && defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE == 1 \
|
||||
&& defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
|
||||
#define MESH_ESP32_HWCDC_SESSION_GUARD 1
|
||||
#else
|
||||
#define MESH_ESP32_HWCDC_SESSION_GUARD 0
|
||||
#endif
|
||||
|
||||
#if MESH_ESP32_TINYUSB_NONBLOCKING || MESH_ESP32_HWCDC_SESSION_GUARD
|
||||
#define MESH_ESP32_USB_CONSOLE_COOPERATIVE 1
|
||||
#else
|
||||
#define MESH_ESP32_USB_CONSOLE_COOPERATIVE 0
|
||||
#endif
|
||||
|
||||
namespace mesh {
|
||||
|
||||
#ifndef MESH_ESP32_USB_TX_BUFFER_SIZE
|
||||
@@ -59,8 +76,13 @@ bool saveUsbLoggingBootPreference(bool enabled);
|
||||
// instead of mistaking an allocation failure for a permanently non-empty ring.
|
||||
void setUsbCompanionTxBufferCapacity(size_t capacity);
|
||||
|
||||
// Configure ESP32 HWCDC's bounded write timeout and TX ring before Serial.begin
|
||||
// creates its mutex and enables the USB ISR. Safe as a no-op on other ports.
|
||||
void prepareUsbLoggingPort();
|
||||
|
||||
// Start the optional dedicated USB logging interface. Ordinary and single-TTY
|
||||
// builds use Serial; nRF52 Full Companion uses a second CDC ACM port.
|
||||
// builds use Serial; nRF52 Full Companion uses a second CDC ACM port. Call this
|
||||
// after Serial.begin() so native USB roles also register session events.
|
||||
void beginUsbLoggingPort();
|
||||
// Enable owner-task service when a host opens the dedicated logging endpoint.
|
||||
// That task emits the identity marker from the actual TinyUSB write result and
|
||||
@@ -83,8 +105,8 @@ Stream& usbMotaPort();
|
||||
// it from the application loop; discard it before changing the CDC protocol or
|
||||
// after a host disconnect so stale text cannot prefix a later Binary session.
|
||||
Stream& usbTerminalPort();
|
||||
// Repeater/room-server console: protect ESP32 TinyUSB while preserving the
|
||||
// historical raw Serial behavior of other platforms and roles.
|
||||
// Repeater/room-server/sensor console: protect both ESP32 native USB transports
|
||||
// while preserving the historical raw Serial behavior of other platforms.
|
||||
Stream& usbConsolePort();
|
||||
// Diagnostic backlog alone must not starve console input. Native ESP32 CDC
|
||||
// admits another command when its reserved functional capacity is available;
|
||||
|
||||
@@ -26,12 +26,7 @@
|
||||
#include "../UsbLogging.h"
|
||||
#ifndef OTA_FOLDER_SERIAL_STREAM
|
||||
#if defined(NRF52_PLATFORM) \
|
||||
|| MESH_ESP32_TINYUSB_NONBLOCKING \
|
||||
|| (defined(ESP32) && defined(ARDUINO_USB_MODE) \
|
||||
&& ARDUINO_USB_MODE == 1 \
|
||||
&& defined(ARDUINO_USB_CDC_ON_BOOT) \
|
||||
&& ARDUINO_USB_CDC_ON_BOOT \
|
||||
&& defined(ENABLE_USB_INTERFACE))
|
||||
|| MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
// Native USB serial-folder requests share the primary session facade.
|
||||
// It prevents a host reset from racing a cached mOTA Stream reference;
|
||||
// dedicated-UART overrides retain their normal stream.
|
||||
|
||||
@@ -24,6 +24,7 @@ HARNESS = r'''
|
||||
#include <cstdint>
|
||||
#include <helpers/FileRead.h>
|
||||
#define MESH_ESP32_TINYUSB_NONBLOCKING 1
|
||||
#define MESH_ESP32_USB_CONSOLE_COOPERATIVE 1
|
||||
#define MAX_ROUTE_HASH_BYTES 3
|
||||
#define PACKET_LOG_FILE "/packet_log"
|
||||
static void require(bool ok, const char* what) {
|
||||
@@ -248,7 +249,7 @@ class CooperativeOutputTest(unittest.TestCase):
|
||||
with self.subTest(role=role), tempfile.TemporaryDirectory() as temporary:
|
||||
text = (ROOT / f"examples/{role}/MyMesh.cpp").read_text()
|
||||
dump_start = text.index("void MyMesh::dumpLogFile()")
|
||||
dump_end = text.index("\n#if MESH_ESP32_TINYUSB_NONBLOCKING\nbool MyMesh::hasPendingSerialOutput", dump_start)
|
||||
dump_end = text.index("\n#if MESH_ESP32_USB_CONSOLE_COOPERATIVE\nbool MyMesh::hasPendingSerialOutput", dump_start)
|
||||
pump_start = text.index("bool MyMesh::hasPendingSerialOutput()", dump_end)
|
||||
pump_end = text.index("\n#endif", pump_start)
|
||||
methods = text[dump_start:dump_end] + "\n" + text[pump_start:pump_end]
|
||||
|
||||
@@ -18,13 +18,17 @@ class Esp32TinyUsbNonblockingTest(unittest.TestCase):
|
||||
if compiler is None:
|
||||
self.skipTest("a host C++17 compiler is required")
|
||||
with tempfile.TemporaryDirectory(prefix=".tmp-usb-mode0-", dir=ROOT) as temporary:
|
||||
for mode, companion in ((0, False), (0, True), (1, False)):
|
||||
with self.subTest(usb_mode=mode, companion=companion):
|
||||
for mode, companion, cdc_on_boot in (
|
||||
(0, False, 1), (0, True, 1), (1, False, 0)
|
||||
):
|
||||
with self.subTest(usb_mode=mode, companion=companion,
|
||||
cdc_on_boot=cdc_on_boot):
|
||||
binary = Path(temporary) / f"usb-mode-{mode}-{companion}.exe"
|
||||
command = [
|
||||
compiler, "-std=c++17", "-DARDUINO=1", "-DESP32=1",
|
||||
"-DESP32_PLATFORM=1", "-DMESH_DEBUG=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1", f"-DARDUINO_USB_MODE={mode}",
|
||||
f"-DARDUINO_USB_CDC_ON_BOOT={cdc_on_boot}",
|
||||
f"-DARDUINO_USB_MODE={mode}",
|
||||
f"-I{FIXTURE / 'mocks'}", f"-I{ROOT / 'src'}",
|
||||
str(FIXTURE / "test_esp32_tinyusb_nonblocking.cpp"),
|
||||
str(ROOT / "src/helpers/UsbLogging.cpp"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep native-TinyUSB repeater/room output away from blocking Arduino writes."""
|
||||
"""Keep ESP32 native-USB role output away from blocking Arduino writes."""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
@@ -22,6 +22,8 @@ class Esp32TinyUsbRoleHygieneTest(unittest.TestCase):
|
||||
r"Stream& usbConsolePort\(\)\s*\{\s*"
|
||||
r"#if MESH_ESP32_TINYUSB_NONBLOCKING\s+"
|
||||
r"return buffered_esp32_tinyusb_terminal_port;\s+"
|
||||
r"#elif MESH_ESP32_HWCDC_SESSION_GUARD\s+"
|
||||
r"return guarded_esp32_hwcdc_port;\s+"
|
||||
r"#else\s+return Serial;\s+#endif",
|
||||
)
|
||||
|
||||
@@ -98,14 +100,49 @@ class Esp32TinyUsbRoleHygieneTest(unittest.TestCase):
|
||||
text = source(f"examples/{role}/main.cpp")
|
||||
setup = text[text.index("void setup()") : text.index("void loop()")]
|
||||
loop = text[text.index("void loop()") :]
|
||||
self.assertLess(
|
||||
setup.index("mesh::prepareUsbLoggingPort();"),
|
||||
setup.index("Serial.begin(115200);"),
|
||||
)
|
||||
self.assertIn("mesh::beginUsbLoggingPort();", setup)
|
||||
self.assertIn("mesh::serviceUsbTerminalPort();", loop)
|
||||
|
||||
def test_sensor_uses_the_same_guarded_console_lifecycle(self):
|
||||
main = source("examples/simple_sensor/main.cpp")
|
||||
sensor = source("examples/simple_sensor/SensorMesh.cpp")
|
||||
setup = main[main.index("void setup()") : main.index("void loop()")]
|
||||
loop = main[main.index("void loop()") :]
|
||||
self.assertLess(
|
||||
setup.index("mesh::prepareUsbLoggingPort();"),
|
||||
setup.index("Serial.begin(115200);"),
|
||||
)
|
||||
self.assertIn("mesh::beginUsbLoggingPort();", setup)
|
||||
self.assertIn("mesh::takeUsbTerminalSessionReset()", loop)
|
||||
self.assertIn("mesh::tryCompleteUsbTerminalSessionReset()", loop)
|
||||
self.assertIn("mesh::canAcceptUsbConsoleCommand()", loop)
|
||||
for text in (main, sensor):
|
||||
self.assertNotRegex(
|
||||
text,
|
||||
r"\bSerial\s*\.\s*(?:print|println|printf|write|flush|available|read|peek)\s*\(",
|
||||
)
|
||||
self.assertNotRegex(text, r"\bprintHex\s*\(\s*Serial\b")
|
||||
self.assertIn("mesh::usbConsolePort()", main)
|
||||
self.assertIn("mesh::usbConsolePort()", sensor)
|
||||
|
||||
def test_hwcdc_guard_is_not_limited_to_companion_builds(self):
|
||||
header = source("src/helpers/UsbLogging.h")
|
||||
start = header.index("#if defined(ESP32) && defined(ARDUINO_USB_MODE)",
|
||||
header.index("MESH_ESP32_HWCDC_SESSION_GUARD") - 300)
|
||||
end = header.index("#if MESH_ESP32_TINYUSB_NONBLOCKING", start)
|
||||
guard = header[start:end]
|
||||
self.assertIn("ARDUINO_USB_MODE == 1", guard)
|
||||
self.assertNotIn("ENABLE_USB_INTERFACE", guard)
|
||||
|
||||
def test_sleep_keeps_raw_flush_only_for_other_esp32_transports(self):
|
||||
text = source("src/helpers/ESP32Board.cpp")
|
||||
sleep = text[text.index("void ESP32Board::enterDeepSleep(") :]
|
||||
guard = re.search(
|
||||
r"#if MESH_ESP32_TINYUSB_NONBLOCKING\s+"
|
||||
r"#if MESH_ESP32_USB_CONSOLE_COOPERATIVE\s+"
|
||||
r"mesh::serviceUsbTerminalPort\(\);\s+"
|
||||
r"#else\s+Serial\.flush\(\);\s+#endif",
|
||||
sleep,
|
||||
|
||||
@@ -53,9 +53,9 @@ class Esp32UsbSerialHygieneTest(unittest.TestCase):
|
||||
hwcdc = main[start : main.index("#elif", start)]
|
||||
|
||||
serial_begin = main.index("Serial.begin(115200);")
|
||||
tx_buffer = main.index("Serial.setTxBufferSize(candidate);")
|
||||
prepare = main.index("mesh::prepareUsbLoggingPort();")
|
||||
logging_begin = main.index("mesh::beginUsbLoggingPort();")
|
||||
self.assertLess(tx_buffer, serial_begin)
|
||||
self.assertLess(prepare, serial_begin)
|
||||
self.assertLess(serial_begin, logging_begin)
|
||||
|
||||
self.assertIn("board.isUsbHostConnected()", hwcdc)
|
||||
@@ -151,12 +151,20 @@ class Esp32UsbSerialHygieneTest(unittest.TestCase):
|
||||
self.assertNotIn("Serial.begin(", purge)
|
||||
|
||||
setup = main[main.index("void setup()") : main.index("board.begin();")]
|
||||
self.assertIn("static const size_t usb_tx_sizes[]", setup)
|
||||
self.assertIn("Serial.setTxBufferSize(candidate)", setup)
|
||||
self.assertIn("usb_tx_capacity = Serial.availableForWrite();", setup)
|
||||
self.assertIn(
|
||||
"mesh::setUsbCompanionTxBufferCapacity(usb_tx_capacity)", setup
|
||||
)
|
||||
self.assertIn("mesh::prepareUsbLoggingPort();", setup)
|
||||
prepare_start = logging.index("void prepareUsbLoggingPort()")
|
||||
prepare = logging[prepare_start : logging.index(
|
||||
"void beginUsbLoggingPort()", prepare_start
|
||||
)]
|
||||
self.assertIn("static const size_t usb_tx_sizes[]", prepare)
|
||||
self.assertIn("Serial.setTxBufferSize(candidate)", prepare)
|
||||
self.assertIn("Serial.setTxTimeoutMs(5);", prepare)
|
||||
begin_start = logging.index("void beginUsbLoggingPort()")
|
||||
begin = logging[begin_start : logging.index(
|
||||
"void serviceUsbLoggingPort()", begin_start
|
||||
)]
|
||||
self.assertIn("Serial.availableForWrite()", begin)
|
||||
self.assertIn("setUsbCompanionTxBufferCapacity(", begin)
|
||||
|
||||
debug_start = logging.index(
|
||||
"static void setPlatformDebugOutputEnabled(bool enabled)"
|
||||
@@ -189,9 +197,7 @@ class Esp32UsbSerialHygieneTest(unittest.TestCase):
|
||||
ota_context.index("#ifndef OTA_FOLDER_SERIAL_STREAM") :
|
||||
ota_context.index("#ifndef OTA_FOLDER_SERIAL_BAUD")
|
||||
]
|
||||
self.assertIn("defined(ESP32)", ota_stream)
|
||||
self.assertIn("ARDUINO_USB_MODE == 1", ota_stream)
|
||||
self.assertIn("defined(ENABLE_USB_INTERFACE)", ota_stream)
|
||||
self.assertIn("MESH_ESP32_USB_CONSOLE_COOPERATIVE", ota_stream)
|
||||
self.assertIn("::mesh::usbMotaPort()", ota_stream)
|
||||
|
||||
self.assertIn(
|
||||
|
||||
Reference in new issue
Block a user