Skip to content

Commit ab03354

Browse files
fix(simulator): restore host serial port bridge in WASM simulator
PR #6435 ported the simulator plug-ins to WASM but did not bridge the aux serial ports across the WASM boundary, leaving AUX1/AUX2 backed by a no-op driver. Lua serialWrite() and any other code touching the aux serials was silently dropped, breaking host serial integration in Companion's simulator (#7283). Add a host bridge driver in simulib.cpp that forwards TX through new WASM imports (simuAuxSerialStart/Stop/SetBaudrate/SendBuffer) and serves RX from per-port mutex-protected queues, populated by a new simuAuxSerialReceive export. On the Companion side, register native callbacks that re-emit the existing SimulatorInterface aux serial signals so HostSerialConnector (already wired up in SimulatorMainWindow) drives the real host port, and implement WasmSimulatorInterface::receiveAuxSerialData() to push host bytes back into the firmware. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1f5dc81 commit ab03354

4 files changed

Lines changed: 241 additions & 22 deletions

File tree

companion/src/simulation/wasmsimulatorinterface.cpp

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,61 @@ static void host_simuLcdNotify(wasm_exec_env_t exec_env)
6868
iface->notifyLcdReady();
6969
}
7070

71+
// WAMR native callbacks: aux serial bridge (firmware -> host). These run on
72+
// the WAMR worker thread; the WasmSimulatorInterface methods they call emit
73+
// Qt signals, which Qt::AutoConnection automatically queues to the GUI
74+
// thread where HostSerialConnector lives.
75+
static void host_simuAuxSerialStart(wasm_exec_env_t exec_env, uint32_t port_nr,
76+
uint32_t baudrate, uint32_t encoding)
77+
{
78+
auto * inst = wasm_runtime_get_module_inst(exec_env);
79+
auto * iface = static_cast<WasmSimulatorInterface *>(
80+
wasm_runtime_get_custom_data(inst));
81+
if (iface)
82+
iface->onAuxSerialStart((uint8_t)port_nr, baudrate, (uint8_t)encoding);
83+
}
84+
85+
static void host_simuAuxSerialStop(wasm_exec_env_t exec_env, uint32_t port_nr)
86+
{
87+
auto * inst = wasm_runtime_get_module_inst(exec_env);
88+
auto * iface = static_cast<WasmSimulatorInterface *>(
89+
wasm_runtime_get_custom_data(inst));
90+
if (iface)
91+
iface->onAuxSerialStop((uint8_t)port_nr);
92+
}
93+
94+
static void host_simuAuxSerialSetBaudrate(wasm_exec_env_t exec_env,
95+
uint32_t port_nr, uint32_t baudrate)
96+
{
97+
auto * inst = wasm_runtime_get_module_inst(exec_env);
98+
auto * iface = static_cast<WasmSimulatorInterface *>(
99+
wasm_runtime_get_custom_data(inst));
100+
if (iface)
101+
iface->onAuxSerialSetBaudrate((uint8_t)port_nr, baudrate);
102+
}
103+
104+
static void host_simuAuxSerialSendBuffer(wasm_exec_env_t exec_env,
105+
uint32_t port_nr, uint8_t * data,
106+
uint32_t len)
107+
{
108+
auto * inst = wasm_runtime_get_module_inst(exec_env);
109+
auto * iface = static_cast<WasmSimulatorInterface *>(
110+
wasm_runtime_get_custom_data(inst));
111+
if (iface && data && len > 0)
112+
iface->onAuxSerialSendBuffer((uint8_t)port_nr, data, len);
113+
}
114+
71115
static NativeSymbol s_nativeSymbols[] = {
72116
{"simuGetAnalog", (void *)host_simuGetAnalog, "(i)i", nullptr},
73117
{"simuQueueAudio", (void *)host_simuQueueAudio, "(*~)", nullptr},
74118
{"simuTrace", (void *)host_simuTrace, "($)", nullptr},
75119
{"simuLcdNotify", (void *)host_simuLcdNotify, "()", nullptr},
120+
{"simuAuxSerialStart", (void *)host_simuAuxSerialStart, "(iii)", nullptr},
121+
{"simuAuxSerialStop", (void *)host_simuAuxSerialStop, "(i)", nullptr},
122+
{"simuAuxSerialSetBaudrate", (void *)host_simuAuxSerialSetBaudrate,
123+
"(ii)", nullptr},
124+
{"simuAuxSerialSendBuffer", (void *)host_simuAuxSerialSendBuffer,
125+
"(i*~)", nullptr},
76126
};
77127

78128
WasmSimulatorInterface::WasmSimulatorInterface(const QString & wasmPath,
@@ -338,6 +388,10 @@ bool WasmSimulatorInterface::resolveExports()
338388
m_fnGetCustomSwitchIndex =
339389
wasm_runtime_lookup_function(m_moduleInst, "simuGetCustomSwitchIndex");
340390

391+
// Aux serial: host -> firmware data injection
392+
m_fnAuxSerialReceive =
393+
wasm_runtime_lookup_function(m_moduleInst, "simuAuxSerialReceive");
394+
341395
m_fnMalloc = wasm_runtime_lookup_function(m_moduleInst, "malloc");
342396
m_fnFree = wasm_runtime_lookup_function(m_moduleInst, "free");
343397

@@ -844,8 +898,56 @@ void WasmSimulatorInterface::queueAudio(const uint8_t * buf, uint32_t len)
844898
void WasmSimulatorInterface::receiveAuxSerialData(const quint8 port_num,
845899
const QByteArray & data)
846900
{
847-
Q_UNUSED(port_num);
848-
Q_UNUSED(data);
901+
if (!m_fnAuxSerialReceive || !m_execEnv || !m_fnMalloc || !m_fnFree ||
902+
data.isEmpty())
903+
return;
904+
905+
QMutexLocker lckr(&m_mutex);
906+
uint32_t len = data.size();
907+
uint32_t allocArgv[1] = {len};
908+
if (!wasm_runtime_call_wasm(m_execEnv, m_fnMalloc, 1, allocArgv) ||
909+
!allocArgv[0])
910+
return;
911+
912+
uint32_t wasmBuf = allocArgv[0];
913+
void * nativePtr = wasm_runtime_addr_app_to_native(m_moduleInst, wasmBuf);
914+
if (nativePtr)
915+
memcpy(nativePtr, data.constData(), len);
916+
917+
uint32_t argv[3] = {(uint32_t)port_num, wasmBuf, len};
918+
wasm_runtime_call_wasm(m_execEnv, m_fnAuxSerialReceive, 3, argv);
919+
920+
uint32_t freeArgv[1] = {wasmBuf};
921+
wasm_runtime_call_wasm(m_execEnv, m_fnFree, 1, freeArgv);
922+
}
923+
924+
void WasmSimulatorInterface::onAuxSerialStart(uint8_t port_nr,
925+
uint32_t baudrate,
926+
uint8_t encoding)
927+
{
928+
emit auxSerialSetEncoding(port_nr, encoding);
929+
if (baudrate != 0)
930+
emit auxSerialSetBaudrate(port_nr, baudrate);
931+
emit auxSerialStart(port_nr);
932+
}
933+
934+
void WasmSimulatorInterface::onAuxSerialStop(uint8_t port_nr)
935+
{
936+
emit auxSerialStop(port_nr);
937+
}
938+
939+
void WasmSimulatorInterface::onAuxSerialSetBaudrate(uint8_t port_nr,
940+
uint32_t baudrate)
941+
{
942+
emit auxSerialSetBaudrate(port_nr, baudrate);
943+
}
944+
945+
void WasmSimulatorInterface::onAuxSerialSendBuffer(uint8_t port_nr,
946+
const uint8_t * data,
947+
uint32_t len)
948+
{
949+
emit auxSerialSendData(port_nr,
950+
QByteArray((const char *)data, (int)len));
849951
}
850952

851953
void WasmSimulatorInterface::run()

companion/src/simulation/wasmsimulatorinterface.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ class WasmSimulatorInterface : public SimulatorInterface
8484
// Called by WASM import simuLcdNotify (from WAMR thread)
8585
void notifyLcdReady();
8686

87+
// Called by WASM aux serial imports (from WAMR thread). These re-emit
88+
// the matching SimulatorInterface signals so that HostSerialConnector
89+
// (wired up in SimulatorMainWindow) drives the real host serial port.
90+
void onAuxSerialStart(uint8_t port_nr, uint32_t baudrate, uint8_t encoding);
91+
void onAuxSerialStop(uint8_t port_nr);
92+
void onAuxSerialSetBaudrate(uint8_t port_nr, uint32_t baudrate);
93+
void onAuxSerialSendBuffer(uint8_t port_nr, const uint8_t * data,
94+
uint32_t len);
95+
8796
protected slots:
8897
void run();
8998
void onLcdNotify();
@@ -196,6 +205,9 @@ class WasmSimulatorInterface : public SimulatorInterface
196205
static constexpr int MAX_FS_LEDS = 8;
197206
uint32_t m_lastFSLedColors[MAX_FS_LEDS] = {};
198207

208+
// Aux serial: host -> firmware data injection
209+
wasm_function_inst_t m_fnAuxSerialReceive = nullptr;
210+
199211
wasm_function_inst_t m_fnMalloc = nullptr;
200212
wasm_function_inst_t m_fnFree = nullptr;
201213
};

radio/src/targets/simu/simulib.cpp

Lines changed: 108 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#endif
4545

4646
#include <assert.h>
47+
#include <deque>
4748

4849
int g_snapshot_idx = 0;
4950

@@ -68,6 +69,10 @@ extern const etx_hal_adc_driver_t simu_adc_driver;
6869
void lcdCopy(void * dest, void * src);
6970
void lcdFlushed();
7071

72+
#if defined(AUX_SERIAL) || defined(AUX2_SERIAL)
73+
static void hostSerialInit();
74+
#endif
75+
7176
void simuInit()
7277
{
7378
#if defined(ROTARY_ENCODER_NAVIGATION)
@@ -81,6 +86,10 @@ void simuInit()
8186
adcInit(&simu_adc_driver);
8287
// Switches
8388
switchInit();
89+
90+
#if defined(AUX_SERIAL) || defined(AUX2_SERIAL)
91+
hostSerialInit();
92+
#endif
8493
}
8594

8695
bool keysStates[MAX_KEYS] = { false };
@@ -394,36 +403,115 @@ const etx_serial_port_t UsbSerialPort = { "USB-VCP", nullptr, nullptr };
394403
#endif
395404

396405
#if defined(AUX_SERIAL) || defined(AUX2_SERIAL)
397-
static void* null_drv_init(void* hw_def, const etx_serial_init* dev) { return nullptr; }
398-
static void null_drv_deinit(void* ctx) { }
399-
static void null_drv_send_byte(void* ctx, uint8_t b) { }
400-
static void null_drv_send_buffer(void* ctx, const uint8_t* b, uint32_t l) { }
401-
static bool null_drv_tx_completed(void* ctx) { return true; }
402-
static int null_drv_get_byte(void* ctx, uint8_t* b) { return 0; }
403-
static void null_drv_set_baudrate(void* ctx, uint32_t baudrate) { }
404-
405-
const etx_serial_driver_t null_drv = {
406-
.init = null_drv_init,
407-
.deinit = null_drv_deinit,
408-
.sendByte = null_drv_send_byte,
409-
.sendBuffer = null_drv_send_buffer,
410-
.txCompleted = null_drv_tx_completed,
406+
// Per-port bridge state. TX is forwarded to the host via WASM imports;
407+
// RX bytes pushed in via simuAuxSerialReceive() are buffered in rxQueue
408+
// and consumed by the firmware through host_drv_get_byte().
409+
struct host_serial_port_t {
410+
uint8_t index;
411+
mutex_handle_t rxMutex;
412+
std::deque<uint8_t> rxQueue;
413+
};
414+
415+
static host_serial_port_t hostSerialPorts[MAX_AUX_SERIAL] = {
416+
{ SP_AUX1, {}, {} },
417+
{ SP_AUX2, {}, {} },
418+
};
419+
420+
static void hostSerialInit()
421+
{
422+
for (uint8_t i = 0; i < MAX_AUX_SERIAL; ++i)
423+
mutex_create(&hostSerialPorts[i].rxMutex);
424+
}
425+
426+
static void* host_drv_init(void* hw_def, const etx_serial_init* dev)
427+
{
428+
if (hw_def == nullptr || dev == nullptr) return nullptr;
429+
430+
auto* port = static_cast<host_serial_port_t*>(hw_def);
431+
mutex_lock(&port->rxMutex);
432+
port->rxQueue.clear();
433+
mutex_unlock(&port->rxMutex);
434+
simuAuxSerialStart(port->index, dev->baudrate, dev->encoding);
435+
return port;
436+
}
437+
438+
static void host_drv_deinit(void* ctx)
439+
{
440+
if (ctx == nullptr) return;
441+
auto* port = static_cast<host_serial_port_t*>(ctx);
442+
simuAuxSerialStop(port->index);
443+
}
444+
445+
static void host_drv_send_byte(void* ctx, uint8_t b)
446+
{
447+
if (ctx == nullptr) return;
448+
auto* port = static_cast<host_serial_port_t*>(ctx);
449+
simuAuxSerialSendBuffer(port->index, &b, 1);
450+
}
451+
452+
static void host_drv_send_buffer(void* ctx, const uint8_t* b, uint32_t l)
453+
{
454+
if (ctx == nullptr || b == nullptr || l == 0) return;
455+
auto* port = static_cast<host_serial_port_t*>(ctx);
456+
simuAuxSerialSendBuffer(port->index, b, l);
457+
}
458+
459+
static bool host_drv_tx_completed(void*) { return true; }
460+
461+
static int host_drv_get_byte(void* ctx, uint8_t* b)
462+
{
463+
if (ctx == nullptr || b == nullptr) return 0;
464+
auto* port = static_cast<host_serial_port_t*>(ctx);
465+
mutex_lock(&port->rxMutex);
466+
if (port->rxQueue.empty()) {
467+
mutex_unlock(&port->rxMutex);
468+
return 0;
469+
}
470+
*b = port->rxQueue.front();
471+
port->rxQueue.pop_front();
472+
mutex_unlock(&port->rxMutex);
473+
return 1;
474+
}
475+
476+
static void host_drv_set_baudrate(void* ctx, uint32_t baudrate)
477+
{
478+
if (ctx == nullptr) return;
479+
auto* port = static_cast<host_serial_port_t*>(ctx);
480+
simuAuxSerialSetBaudrate(port->index, baudrate);
481+
}
482+
483+
const etx_serial_driver_t host_drv = {
484+
.init = host_drv_init,
485+
.deinit = host_drv_deinit,
486+
.sendByte = host_drv_send_byte,
487+
.sendBuffer = host_drv_send_buffer,
488+
.txCompleted = host_drv_tx_completed,
411489
.waitForTxCompleted = nullptr,
412490
.enableRx = nullptr,
413-
.getByte = null_drv_get_byte,
491+
.getByte = host_drv_get_byte,
414492
.getLastByte = nullptr,
415493
.getBufferedBytes = nullptr,
416494
.copyRxBuffer = nullptr,
417495
.clearRxBuffer = nullptr,
418496
.getBaudrate = nullptr,
419-
.setBaudrate = null_drv_set_baudrate,
497+
.setBaudrate = host_drv_set_baudrate,
420498
.setPolarity = nullptr,
421499
.setHWOption = nullptr,
422500
.setReceiveCb = nullptr,
423501
.setIdleCb = nullptr,
424502
.setBaudrateCb = nullptr,
425503
};
426504

505+
void simuAuxSerialReceive(uint8_t port_nr, const uint8_t* data, uint32_t len)
506+
{
507+
if (port_nr >= MAX_AUX_SERIAL || data == nullptr || len == 0) return;
508+
auto& port = hostSerialPorts[port_nr];
509+
mutex_lock(&port.rxMutex);
510+
for (uint32_t i = 0; i < len; ++i)
511+
port.rxQueue.push_back(data[i]);
512+
mutex_unlock(&port.rxMutex);
513+
}
514+
427515
#if defined(AUX_SERIAL_PWR_GPIO)
428516
static void null_pwr_aux(uint8_t) {}
429517
#endif
@@ -437,8 +525,8 @@ static void null_pwr_aux(uint8_t) {}
437525
#endif
438526
static etx_serial_port_t auxSerialPort = {
439527
"AUX1",
440-
&null_drv,
441-
nullptr,
528+
&host_drv,
529+
&hostSerialPorts[SP_AUX1],
442530
AUX_SERIAL_PWR
443531
};
444532
#define AUX_SERIAL_PORT &auxSerialPort
@@ -454,8 +542,8 @@ static etx_serial_port_t auxSerialPort = {
454542
#endif
455543
static etx_serial_port_t aux2SerialPort = {
456544
"AUX2",
457-
&null_drv,
458-
nullptr,
545+
&host_drv,
546+
&hostSerialPorts[SP_AUX2],
459547
AUX2_SERIAL_PWR
460548
};
461549
#define AUX2_SERIAL_PORT &aux2SerialPort

radio/src/targets/simu/simulib.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ uint8_t WASM_EXPORT(simuGetNumGVars)();
140140
uint8_t WASM_EXPORT(simuGetNumFlightModes)();
141141
int32_t WASM_EXPORT(simuGetGVar)(uint8_t gv, uint8_t fm);
142142

143+
// Aux serial: push bytes received from a host serial port into the firmware's
144+
// rx queue for the matching aux port (port_nr is 0 for AUX1, 1 for AUX2).
145+
void WASM_EXPORT(simuAuxSerialReceive)(uint8_t port_nr, const uint8_t* data,
146+
uint32_t len);
147+
143148
// -- WASM imports (provided by host) --
144149

145150
// simuGetAnalog: return ADC-range value for input at index idx.
@@ -157,6 +162,18 @@ void WASM_IMPORT(simuTrace)(const char* text);
157162
// listener so the frame can be rendered without polling.
158163
void WASM_IMPORT(simuLcdNotify)();
159164

165+
// Aux serial bridge (firmware -> host). port_nr is 0 for AUX1, 1 for AUX2.
166+
// encoding values match SimulatorSerialEncoding (0=8N1, 1=8E2, 2=PXX1_PWM)
167+
// and ETX_Encoding_* — they share the same numeric values. Called when the
168+
// firmware initialises an aux serial port (start), shuts it down (stop),
169+
// reconfigures the baudrate, or transmits data.
170+
void WASM_IMPORT(simuAuxSerialStart)(uint8_t port_nr, uint32_t baudrate,
171+
uint8_t encoding);
172+
void WASM_IMPORT(simuAuxSerialStop)(uint8_t port_nr);
173+
void WASM_IMPORT(simuAuxSerialSetBaudrate)(uint8_t port_nr, uint32_t baudrate);
174+
void WASM_IMPORT(simuAuxSerialSendBuffer)(uint8_t port_nr, const uint8_t* data,
175+
uint32_t len);
176+
160177
// -- Internal (not exported) --
161178
void simuMain();
162179
std::string simuFatfsGetCurrentPath();

0 commit comments

Comments
 (0)