mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 19:41:59 +00:00
441 lines
21 KiB
Python
441 lines
21 KiB
Python
"""Build-local owner-task hooks for Arduino-ESP32 native CDC session cleanup.
|
|
|
|
Do not patch the shared SDK. Fail closed when the native-CDC implementation
|
|
changes: the endpoint query must match the descriptor actually compiled.
|
|
"""
|
|
from pathlib import Path
|
|
|
|
|
|
GUARD = "#if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE\n"
|
|
CDC_INCLUDE = '#include "esp32-hal-tinyusb.h"\n'
|
|
CDC_HOOKS = GUARD + '''#include "device/usbd_pvt.h"
|
|
extern "C" void meshEsp32TinyUsbCdcLineState(bool dtr) __attribute__((weak));
|
|
extern "C" bool meshEsp32TinyUsbAcceptRx() __attribute__((weak));
|
|
extern "C" void meshEsp32TinyUsbTxComplete() __attribute__((weak));
|
|
extern "C" bool meshEsp32TinyUsbTxPending() {
|
|
return usbd_edpt_busy(0, 0x84);
|
|
}
|
|
#endif
|
|
'''
|
|
LINE_STATE = "void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)\n{\n"
|
|
LINE_HOOK = GUARD + ''' // Capture the close synchronously, before another host can enqueue RX.
|
|
if (itf == 0 && meshEsp32TinyUsbCdcLineState) {
|
|
meshEsp32TinyUsbCdcLineState(dtr);
|
|
}
|
|
#endif
|
|
'''
|
|
RX = "void USBCDC::_onRX(){\n"
|
|
RX_HOOK = GUARD + ''' if (itf == 0 && meshEsp32TinyUsbAcceptRx && !meshEsp32TinyUsbAcceptRx()) {
|
|
tud_cdc_n_read_flush(itf);
|
|
return;
|
|
}
|
|
#endif
|
|
'''
|
|
TX_COMPLETE = "void tud_cdc_tx_complete_cb(uint8_t itf){\n"
|
|
TX_HOOK = GUARD + ''' if (itf == 0 && meshEsp32TinyUsbTxComplete) {
|
|
meshEsp32TinyUsbTxComplete();
|
|
}
|
|
#endif
|
|
'''
|
|
USB_INCLUDE = '#include "esp32-hal-tinyusb.h"\n'
|
|
USB_HOOKS = GUARD + '''extern "C" void meshEsp32TinyUsbDeviceSessionBoundary(bool mounted) __attribute__((weak));
|
|
#endif
|
|
'''
|
|
MOUNT = "void tud_mount_cb(void){\n"
|
|
UNMOUNT = "void tud_umount_cb(void){\n"
|
|
|
|
|
|
def patched_cdc_source(source):
|
|
source = source.replace("\r\n", "\n")
|
|
descriptor = "TUD_CDC_DESCRIPTOR(*itf, str_index, 0x85, 64, 0x03, 0x84, 64)"
|
|
if (source.count(CDC_HOOKS) == 1 and source.count(LINE_HOOK) == 1
|
|
and source.count(RX_HOOK) == 1 and source.count(TX_HOOK) == 1
|
|
and source.count(descriptor) == 1):
|
|
return source
|
|
if (source.count(CDC_INCLUDE) != 1 or source.count(LINE_STATE) != 1
|
|
or source.count(RX) != 1 or source.count(TX_COMPLETE) != 1
|
|
or source.count(descriptor) != 1
|
|
or "meshEsp32TinyUsb" in source):
|
|
raise RuntimeError("ESP32 USB session fix: changed native CDC/endpoint layout; review SDK update")
|
|
return (source.replace(CDC_INCLUDE, CDC_INCLUDE + CDC_HOOKS)
|
|
.replace(LINE_STATE, LINE_STATE + LINE_HOOK)
|
|
.replace(RX, RX + RX_HOOK)
|
|
.replace(TX_COMPLETE, TX_COMPLETE + TX_HOOK))
|
|
|
|
|
|
def patched_usb_source(source):
|
|
source = source.replace("\r\n", "\n")
|
|
mount_hook = GUARD + " if (meshEsp32TinyUsbDeviceSessionBoundary) meshEsp32TinyUsbDeviceSessionBoundary(true);\n#endif\n"
|
|
unmount_hook = GUARD + " if (meshEsp32TinyUsbDeviceSessionBoundary) meshEsp32TinyUsbDeviceSessionBoundary(false);\n#endif\n"
|
|
if (source.count(USB_HOOKS) == 1 and source.count(mount_hook) == 1
|
|
and source.count(unmount_hook) == 1):
|
|
return source
|
|
if (source.count(USB_INCLUDE) != 1 or source.count(MOUNT) != 1
|
|
or source.count(UNMOUNT) != 1 or "meshEsp32TinyUsb" in source):
|
|
raise RuntimeError("ESP32 USB session fix: changed USB lifecycle callbacks; review SDK update")
|
|
return (source.replace(USB_INCLUDE, USB_INCLUDE + USB_HOOKS)
|
|
.replace(MOUNT, MOUNT + mount_hook)
|
|
.replace(UNMOUNT, UNMOUNT + unmount_hook))
|
|
|
|
|
|
def native_primary_enabled(build_env):
|
|
defines = {}
|
|
for definition in build_env.get("CPPDEFINES", []):
|
|
if isinstance(definition, (tuple, list)):
|
|
defines[definition[0]] = str(definition[1])
|
|
else:
|
|
defines[str(definition)] = "1"
|
|
return (defines.get("ARDUINO_USB_CDC_ON_BOOT") == "1"
|
|
and defines.get("ARDUINO_USB_MODE") == "0")
|
|
|
|
|
|
def replace_source(build_env, node):
|
|
if not native_primary_enabled(build_env):
|
|
return node
|
|
source = Path(node.srcnode().get_abspath())
|
|
patcher = patched_cdc_source if source.name == "USBCDC.cpp" else patched_usb_source
|
|
patched = patcher(source.read_text(encoding="utf-8"))
|
|
destination = Path(build_env.subst("$BUILD_DIR")) / "patched-esp32-usb" / source.name
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
if not destination.exists() or destination.read_text(encoding="utf-8") != patched:
|
|
destination.write_text(patched, encoding="utf-8")
|
|
return build_env.File(str(destination))
|
|
|
|
|
|
|
|
# HWCDC TX/PHY fixes are limited to the installed Arduino2.0.17 C3/S3 driver.
|
|
# The separately pinned 3.x transforms below only classify startup resets.
|
|
# Other framework versions retain their own driver unchanged.
|
|
PINNED_HWCDC_SHA256 = "d0a8ca606c2729c8522a041113285dbf27033c22a5a6af8649a7305ffe84c449"
|
|
PATCHED_HWCDC_SHA256 = "0969a94ae32edcfeeedb3daab19e5697e6c7647dc31578cdb0c3a0cb29e7c6d6"
|
|
HWCDC_S3_PHY_GUARD = "#if CONFIG_IDF_TARGET_ESP32S3 && ARDUINO_USB_MODE && ARDUINO_USB_CDC_ON_BOOT\n"
|
|
HWCDC_S3_PHY_HELPERS = HWCDC_S3_PHY_GUARD + r'''#include "soc/usb_wrap_struct.h"
|
|
#include "soc/rtc_cntl_struct.h"
|
|
|
|
// Exact ESP-IDF 4.4.7 usb_phy_ll.h bodies, renamed locally. The complete HAL
|
|
// header has unrelated volatile struct copies that cannot compile as C++.
|
|
static inline void mesh_hwcdc_int_jtag_enable(usb_serial_jtag_dev_t *hw)
|
|
{
|
|
// USB_Serial_JTAG use internal PHY
|
|
hw->conf0.phy_sel = 0;
|
|
// Disable software control USB D+ D- pullup pulldown (Device FS: dp_pullup = 1)
|
|
hw->conf0.pad_pull_override = 0;
|
|
// Enable USB D+ pullup
|
|
hw->conf0.dp_pullup = 1;
|
|
// Enable USB pad function
|
|
hw->conf0.usb_pad_enable = 1;
|
|
// phy_sel is controlled by the following register value
|
|
RTCCNTL.usb_conf.sw_hw_usb_phy_sel = 1;
|
|
// phy_sel=sw_usb_phy_sel=0, USB_Serial_JTAG is connected with internal PHY
|
|
RTCCNTL.usb_conf.sw_usb_phy_sel = 0;
|
|
}
|
|
|
|
static inline void mesh_hwcdc_usb_wrap_pad_enable(usb_wrap_dev_t *hw, bool pad_en)
|
|
{
|
|
hw->otg_conf.pad_enable = pad_en;
|
|
}
|
|
#endif
|
|
'''
|
|
HWCDC_S3_PHY_CONFIG = r""" // TinyUSB's RTC PHY selection survives a software restart (IDF #9826).
|
|
// An OTA upgrade must release its old pullup before claiming the shared
|
|
// PHY, so the host discards the previous USB device's descriptors.
|
|
if (RTCCNTL.usb_conf.sw_hw_usb_phy_sel && RTCCNTL.usb_conf.sw_usb_phy_sel) {
|
|
mesh_hwcdc_usb_wrap_pad_enable(&USB_WRAP, false);
|
|
delay(20);
|
|
}
|
|
mesh_hwcdc_int_jtag_enable(&USB_SERIAL_JTAG);
|
|
"""
|
|
HWCDC_TX_SUPPORT = r"""
|
|
// MeshCore pinned HWCDC TX suffix/interrupt backport (upstream #12606).
|
|
extern "C" bool meshEsp32HwcdcShouldReportBusReset() __attribute__((weak));
|
|
static uint8_t mesh_hwcdc_tx_stash[64] = {0};
|
|
static size_t mesh_hwcdc_tx_stash_len = 0;
|
|
static bool mesh_hwcdc_tx_allowed = true;
|
|
static bool mesh_hwcdc_fifo_pending = false;
|
|
static uint32_t mesh_hwcdc_active_writers = 0;
|
|
static portMUX_TYPE mesh_hwcdc_tx_mux = portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
static inline void mesh_hwcdc_enable_tx_intr() {
|
|
portENTER_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
if (mesh_hwcdc_tx_allowed) {
|
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
}
|
|
|
|
extern "C" void meshEsp32HwcdcSetTxAllowed(bool allowed) {
|
|
portENTER_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
mesh_hwcdc_tx_allowed = allowed;
|
|
if (!allowed) usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
portEXIT_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
}
|
|
|
|
extern "C" void meshEsp32HwcdcKickTx() {
|
|
portENTER_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
if (mesh_hwcdc_tx_allowed) {
|
|
usb_serial_jtag_ll_txfifo_flush();
|
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
}
|
|
|
|
extern "C" bool meshEsp32HwcdcTxPending() {
|
|
portENTER_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
UBaseType_t waiting = 0;
|
|
if (tx_ring_buf) vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &waiting);
|
|
const bool pending = mesh_hwcdc_tx_stash_len != 0 || waiting != 0
|
|
|| mesh_hwcdc_fifo_pending || mesh_hwcdc_active_writers != 0;
|
|
portEXIT_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
return pending;
|
|
}
|
|
|
|
static inline void mesh_hwcdc_clear_tx_stash() {
|
|
portENTER_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
mesh_hwcdc_tx_stash_len = 0;
|
|
portEXIT_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
}
|
|
|
|
extern "C" bool meshEsp32HwcdcDiscardTxStash() {
|
|
// Caller has detached pads and stopped producers. Do not mistake ordinary
|
|
// flush/ring-empty for completion of a previously staged FIFO payload.
|
|
portENTER_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
const bool permitted = !mesh_hwcdc_tx_allowed && mesh_hwcdc_active_writers == 0;
|
|
if (permitted) {
|
|
mesh_hwcdc_tx_stash_len = 0;
|
|
mesh_hwcdc_fifo_pending = false;
|
|
}
|
|
portEXIT_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
return permitted;
|
|
}
|
|
|
|
class mesh_hwcdc_writer_scope {
|
|
bool _entered;
|
|
public:
|
|
mesh_hwcdc_writer_scope() {
|
|
portENTER_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
_entered = mesh_hwcdc_tx_allowed;
|
|
if (_entered) ++mesh_hwcdc_active_writers;
|
|
portEXIT_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
}
|
|
~mesh_hwcdc_writer_scope() {
|
|
if (!_entered) return;
|
|
portENTER_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
--mesh_hwcdc_active_writers;
|
|
portEXIT_CRITICAL_SAFE(&mesh_hwcdc_tx_mux);
|
|
}
|
|
operator bool() const { return _entered; }
|
|
mesh_hwcdc_writer_scope(const mesh_hwcdc_writer_scope&) = delete;
|
|
mesh_hwcdc_writer_scope& operator=(const mesh_hwcdc_writer_scope&) = delete;
|
|
};
|
|
"""
|
|
HWCDC_IN_EMPTY = r""" if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {
|
|
// A real host pickup is stronger proof than the SOF tick heuristic.
|
|
connected = true;
|
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
size_t sent_size = 0;
|
|
bool staged = false;
|
|
portENTER_CRITICAL_ISR(&mesh_hwcdc_tx_mux);
|
|
mesh_hwcdc_fifo_pending = false; // this IN_EMPTY acknowledges the previous payload
|
|
if (!mesh_hwcdc_tx_allowed) {
|
|
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
} else if (tx_ring_buf != NULL && usb_serial_jtag_ll_txfifo_writable() == 1) {
|
|
size_t queued_size = mesh_hwcdc_tx_stash_len;
|
|
bool from_stash = queued_size != 0;
|
|
uint8_t* queued_buff = from_stash ? mesh_hwcdc_tx_stash
|
|
: (uint8_t*)xRingbufferReceiveUpToFromISR(tx_ring_buf, &queued_size, 64);
|
|
if (queued_buff != NULL && queued_size != 0) {
|
|
sent_size = usb_serial_jtag_ll_write_txfifo(queued_buff, queued_size);
|
|
usb_serial_jtag_ll_txfifo_flush();
|
|
staged = true;
|
|
mesh_hwcdc_fifo_pending = sent_size != 0;
|
|
mesh_hwcdc_tx_stash_len = queued_size - sent_size;
|
|
if (mesh_hwcdc_tx_stash_len) {
|
|
memmove(mesh_hwcdc_tx_stash, queued_buff + sent_size, mesh_hwcdc_tx_stash_len);
|
|
}
|
|
if (!from_stash) vRingbufferReturnItemFromISR(tx_ring_buf, queued_buff, &xTaskWoken);
|
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
} else {
|
|
// Preserve the full-packet terminating ZLP. Disable and inspect
|
|
// again under the producer/ISR lock so a wakeup cannot be lost.
|
|
usb_serial_jtag_ll_txfifo_flush();
|
|
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
UBaseType_t waiting = 0;
|
|
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &waiting);
|
|
if (waiting || mesh_hwcdc_tx_stash_len) {
|
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
}
|
|
}
|
|
}
|
|
portEXIT_CRITICAL_ISR(&mesh_hwcdc_tx_mux);
|
|
if (staged) {
|
|
event.tx.len = sent_size;
|
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_TX_EVENT,
|
|
&event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
|
}
|
|
}
|
|
|
|
"""
|
|
HWCDC_CONNECTED = r"""bool HWCDC::isCDC_Connected()
|
|
{
|
|
if (!isPlugged()) {
|
|
connected = false;
|
|
return false;
|
|
}
|
|
if (connected) return true;
|
|
// Re-arm on every attempt. Keep a suffix across transient SOF false.
|
|
meshEsp32HwcdcKickTx();
|
|
return false;
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
def patched_hwcdc_source(source):
|
|
import hashlib
|
|
source = source.replace("\r\n", "\n")
|
|
digest = hashlib.sha256(source.encode()).hexdigest()
|
|
if digest == PATCHED_HWCDC_SHA256:
|
|
return source
|
|
if digest != PINNED_HWCDC_SHA256:
|
|
raise RuntimeError("ESP32 HWCDC TX fix: changed pinned2.0.17 source; review SDK update")
|
|
# Perform exact-shape transforms only after the complete input hash passes.
|
|
source = source.replace('#include "esp_freertos_hooks.h"\n',
|
|
'#include "esp_freertos_hooks.h"\n#include <string.h>\n', 1)
|
|
source = source.replace('#include "hal/usb_serial_jtag_ll.h"\n',
|
|
'#include "hal/usb_serial_jtag_ll.h"\n' + HWCDC_S3_PHY_HELPERS, 1)
|
|
begin = source.index('void HWCDC::begin(unsigned long baud)')
|
|
phy_begin = source.index(' // Configure PHY\n', begin)
|
|
phy_end = source.index(' usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);', phy_begin)
|
|
source = (source[:phy_begin] + HWCDC_S3_PHY_GUARD + HWCDC_S3_PHY_CONFIG
|
|
+ '#else\n' + source[phy_begin:phy_end] + '#endif\n' + source[phy_end:])
|
|
source = source.replace('usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);',
|
|
'mesh_hwcdc_enable_tx_intr();')
|
|
begin = source.index(' if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {')
|
|
end = source.index(' if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) {', begin)
|
|
source = source[:begin] + HWCDC_IN_EMPTY + source[end:]
|
|
begin = source.index('bool HWCDC::isCDC_Connected()')
|
|
end = source.index('static void flushTXBuffer(', begin)
|
|
source = source[:begin] + HWCDC_CONNECTED + source[end:]
|
|
source = source.replace('static xSemaphoreHandle tx_lock = NULL;\n',
|
|
'static xSemaphoreHandle tx_lock = NULL;\n' + HWCDC_TX_SUPPORT, 1)
|
|
source = source.replace(' connected = false;\n }\n\n// if (usbjtag_intr_status',
|
|
' portENTER_CRITICAL_ISR(&mesh_hwcdc_tx_mux);\n'
|
|
' mesh_hwcdc_tx_stash_len = 0;\n mesh_hwcdc_fifo_pending = false;\n'
|
|
' portEXIT_CRITICAL_ISR(&mesh_hwcdc_tx_mux);\n connected = false;\n }\n\n// if (usbjtag_intr_status', 1)
|
|
source = source.replace(
|
|
' arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_BUS_RESET_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);',
|
|
' // Classify at capture: setup-time enumeration must not become an\n'
|
|
' // active-session reset if the framework event task runs later.\n'
|
|
' if (!meshEsp32HwcdcShouldReportBusReset || meshEsp32HwcdcShouldReportBusReset()) {\n'
|
|
' arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_BUS_RESET_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);\n'
|
|
' }', 1)
|
|
source = source.replace(' if(buffer == NULL) {\n',
|
|
' if(buffer == NULL) {\n mesh_hwcdc_clear_tx_stash();\n', 1)
|
|
source = source.replace('size_t HWCDC::setTxBufferSize(size_t tx_queue_len){\n',
|
|
'size_t HWCDC::setTxBufferSize(size_t tx_queue_len){\n mesh_hwcdc_clear_tx_stash();\n', 1)
|
|
source = source.replace(' if(tx_ring_buf == NULL) {\n return;\n }\n if(!HWCDC::isConnected())',
|
|
' if(tx_ring_buf == NULL) {\n return;\n }\n'
|
|
' mesh_hwcdc_writer_scope writer;\n if (!writer) return;\n if(!HWCDC::isConnected())', 1)
|
|
source = source.replace('size_t HWCDC::write(const uint8_t *buffer, size_t size)\n{\n',
|
|
'size_t HWCDC::write(const uint8_t *buffer, size_t size)\n{\n'
|
|
' mesh_hwcdc_writer_scope writer;\n if (!writer) return 0;\n', 1)
|
|
source = source.replace(' flushTXBuffer((const uint8_t*)&c, 1);\n return;\n',
|
|
' flushTXBuffer((const uint8_t*)&c, 1);\n meshEsp32HwcdcKickTx();\n return;\n', 1)
|
|
# The disconnected write path previously left new bytes parked without
|
|
# an interrupt. Kick after its existing FIFO-policy admission.
|
|
source = source.replace(' flushTXBuffer(buffer, size);\n } else {\n',
|
|
' flushTXBuffer(buffer, size);\n meshEsp32HwcdcKickTx();\n } else {\n', 1)
|
|
return source
|
|
|
|
|
|
def hwcdc_primary_enabled(build_env):
|
|
defines = {}
|
|
for definition in build_env.get("CPPDEFINES", []):
|
|
if isinstance(definition, (tuple, list)):
|
|
defines[definition[0]] = str(definition[1])
|
|
else:
|
|
defines[str(definition)] = "1"
|
|
return (defines.get("ARDUINO_USB_CDC_ON_BOOT") == "1"
|
|
and defines.get("ARDUINO_USB_MODE") == "1")
|
|
|
|
|
|
# Keep each reviewed 3.x driver's own TX/FIFO/PHY implementation intact. Only
|
|
# classify the startup BUS_RESET event at capture, under an exact source pin.
|
|
PINNED_HWCDC_313_SHA256 = "a09e99f1a37931269625019f51f1541a1185cce83f686d7aca4204ecdfea91dd"
|
|
PATCHED_HWCDC_313_SHA256 = "a2efe89e363908baf955e60751985edf1411fa930f2971e2d98b69fda3a1d19c"
|
|
PINNED_HWCDC_3311_SHA256 = "c5ed5fdd05aa0df9b74d390812643599223f96b459256718d0ad328aeaba6a8a"
|
|
PATCHED_HWCDC_3311_SHA256 = "7b693bf5d72df97336360969ff429c67f3783d472123aeb79b3aabc8154b51ba"
|
|
HWCDC_STARTUP_PINS = {
|
|
(3, 1, 3): (PINNED_HWCDC_313_SHA256, PATCHED_HWCDC_313_SHA256),
|
|
(3, 3, 11): (PINNED_HWCDC_3311_SHA256, PATCHED_HWCDC_3311_SHA256),
|
|
}
|
|
HWCDC_STARTUP_INCLUDE = '#include "hal/usb_serial_jtag_ll.h"\n'
|
|
HWCDC_STARTUP_HOOK = 'extern "C" bool meshEsp32HwcdcShouldReportBusReset() __attribute__((weak));\n'
|
|
HWCDC_RESET_POST = (
|
|
' arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_BUS_RESET_EVENT, '
|
|
'&event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);')
|
|
HWCDC_RESET_CAPTURE = (
|
|
' // Snapshot startup before the framework event task can delay delivery.\n'
|
|
' if (!meshEsp32HwcdcShouldReportBusReset || meshEsp32HwcdcShouldReportBusReset()) {\n'
|
|
+ ' ' + HWCDC_RESET_POST + '\n'
|
|
' }')
|
|
|
|
|
|
def patched_hwcdc_startup_source(source, *, version=(3, 3, 11)):
|
|
import hashlib
|
|
original_digest, patched_digest = HWCDC_STARTUP_PINS[version]
|
|
source = source.replace("\r\n", "\n")
|
|
digest = hashlib.sha256(source.encode()).hexdigest()
|
|
if digest == patched_digest:
|
|
return source
|
|
if digest != original_digest:
|
|
label = ".".join(str(part) for part in version)
|
|
raise RuntimeError("ESP32 HWCDC startup fix: changed pinned" + label + " source; review SDK update")
|
|
return source.replace(HWCDC_STARTUP_INCLUDE,
|
|
HWCDC_STARTUP_INCLUDE + HWCDC_STARTUP_HOOK, 1).replace(
|
|
HWCDC_RESET_POST, HWCDC_RESET_CAPTURE, 1)
|
|
|
|
|
|
def hwcdc_framework_version(source):
|
|
import re
|
|
version = source.parent / "esp_arduino_version.h"
|
|
values = dict(re.findall(r"^#define ESP_ARDUINO_VERSION_(MAJOR|MINOR|PATCH)\s+(\d+)\s*$",
|
|
version.read_text(encoding="utf-8"), re.MULTILINE))
|
|
if set(values) != {"MAJOR", "MINOR", "PATCH"}:
|
|
raise RuntimeError("ESP32 HWCDC TX fix: malformed framework version header")
|
|
return tuple(int(values[key]) for key in ("MAJOR", "MINOR", "PATCH"))
|
|
|
|
|
|
def pinned_hwcdc_framework(source):
|
|
return hwcdc_framework_version(source) == (2, 0, 17)
|
|
|
|
|
|
def replace_hwcdc_source(build_env, node):
|
|
if not hwcdc_primary_enabled(build_env):
|
|
return node
|
|
source = Path(node.srcnode().get_abspath())
|
|
version = hwcdc_framework_version(source)
|
|
if version == (2, 0, 17):
|
|
patched = patched_hwcdc_source(source.read_text(encoding="utf-8"))
|
|
elif version in HWCDC_STARTUP_PINS:
|
|
patched = patched_hwcdc_startup_source(source.read_text(encoding="utf-8"), version=version)
|
|
else:
|
|
return node
|
|
destination = Path(build_env.subst("$BUILD_DIR")) / "patched-esp32-usb" / source.name
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
if not destination.exists() or destination.read_text(encoding="utf-8") != patched:
|
|
destination.write_text(patched, encoding="utf-8")
|
|
build_env.AppendUnique(CPPPATH=[str(source.parent)])
|
|
return build_env.File(str(destination))
|
|
|
|
|
|
def install(build_env):
|
|
build_env.AddBuildMiddleware(replace_source, "*cores*esp32*USBCDC.cpp")
|
|
build_env.AddBuildMiddleware(replace_source, "*cores*esp32*USB.cpp")
|
|
build_env.AddBuildMiddleware(replace_hwcdc_source, "*cores*esp32*HWCDC.cpp")
|
|
|
|
|
|
if "Import" in globals():
|
|
Import("env")
|
|
install(env)
|