fix(simulator): restore host serial port bridge in WASM simulator (#7284)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fc61006fe4
commit
a2aeab9234
@@ -68,11 +68,61 @@ static void host_simuLcdNotify(wasm_exec_env_t exec_env)
|
||||
iface->notifyLcdReady();
|
||||
}
|
||||
|
||||
// WAMR native callbacks: aux serial bridge (firmware -> host). These run on
|
||||
// the WAMR worker thread; the WasmSimulatorInterface methods they call emit
|
||||
// Qt signals, which Qt::AutoConnection automatically queues to the GUI
|
||||
// thread where HostSerialConnector lives.
|
||||
static void host_simuAuxSerialStart(wasm_exec_env_t exec_env, uint32_t port_nr,
|
||||
uint32_t baudrate, uint32_t encoding)
|
||||
{
|
||||
auto * inst = wasm_runtime_get_module_inst(exec_env);
|
||||
auto * iface = static_cast<WasmSimulatorInterface *>(
|
||||
wasm_runtime_get_custom_data(inst));
|
||||
if (iface)
|
||||
iface->onAuxSerialStart((uint8_t)port_nr, baudrate, (uint8_t)encoding);
|
||||
}
|
||||
|
||||
static void host_simuAuxSerialStop(wasm_exec_env_t exec_env, uint32_t port_nr)
|
||||
{
|
||||
auto * inst = wasm_runtime_get_module_inst(exec_env);
|
||||
auto * iface = static_cast<WasmSimulatorInterface *>(
|
||||
wasm_runtime_get_custom_data(inst));
|
||||
if (iface)
|
||||
iface->onAuxSerialStop((uint8_t)port_nr);
|
||||
}
|
||||
|
||||
static void host_simuAuxSerialSetBaudrate(wasm_exec_env_t exec_env,
|
||||
uint32_t port_nr, uint32_t baudrate)
|
||||
{
|
||||
auto * inst = wasm_runtime_get_module_inst(exec_env);
|
||||
auto * iface = static_cast<WasmSimulatorInterface *>(
|
||||
wasm_runtime_get_custom_data(inst));
|
||||
if (iface)
|
||||
iface->onAuxSerialSetBaudrate((uint8_t)port_nr, baudrate);
|
||||
}
|
||||
|
||||
static void host_simuAuxSerialSendBuffer(wasm_exec_env_t exec_env,
|
||||
uint32_t port_nr, uint8_t * data,
|
||||
uint32_t len)
|
||||
{
|
||||
auto * inst = wasm_runtime_get_module_inst(exec_env);
|
||||
auto * iface = static_cast<WasmSimulatorInterface *>(
|
||||
wasm_runtime_get_custom_data(inst));
|
||||
if (iface && data && len > 0)
|
||||
iface->onAuxSerialSendBuffer((uint8_t)port_nr, data, len);
|
||||
}
|
||||
|
||||
static NativeSymbol s_nativeSymbols[] = {
|
||||
{"simuGetAnalog", (void *)host_simuGetAnalog, "(i)i", nullptr},
|
||||
{"simuQueueAudio", (void *)host_simuQueueAudio, "(*~)", nullptr},
|
||||
{"simuTrace", (void *)host_simuTrace, "($)", nullptr},
|
||||
{"simuLcdNotify", (void *)host_simuLcdNotify, "()", nullptr},
|
||||
{"simuAuxSerialStart", (void *)host_simuAuxSerialStart, "(iii)", nullptr},
|
||||
{"simuAuxSerialStop", (void *)host_simuAuxSerialStop, "(i)", nullptr},
|
||||
{"simuAuxSerialSetBaudrate", (void *)host_simuAuxSerialSetBaudrate,
|
||||
"(ii)", nullptr},
|
||||
{"simuAuxSerialSendBuffer", (void *)host_simuAuxSerialSendBuffer,
|
||||
"(i*~)", nullptr},
|
||||
};
|
||||
|
||||
WasmSimulatorInterface::WasmSimulatorInterface(const QString & wasmPath,
|
||||
@@ -338,6 +388,10 @@ bool WasmSimulatorInterface::resolveExports()
|
||||
m_fnGetCustomSwitchIndex =
|
||||
wasm_runtime_lookup_function(m_moduleInst, "simuGetCustomSwitchIndex");
|
||||
|
||||
// Aux serial: host -> firmware data injection
|
||||
m_fnAuxSerialReceive =
|
||||
wasm_runtime_lookup_function(m_moduleInst, "simuAuxSerialReceive");
|
||||
|
||||
m_fnMalloc = wasm_runtime_lookup_function(m_moduleInst, "malloc");
|
||||
m_fnFree = wasm_runtime_lookup_function(m_moduleInst, "free");
|
||||
|
||||
@@ -857,8 +911,56 @@ void WasmSimulatorInterface::queueAudio(const uint8_t * buf, uint32_t len)
|
||||
void WasmSimulatorInterface::receiveAuxSerialData(const quint8 port_num,
|
||||
const QByteArray & data)
|
||||
{
|
||||
Q_UNUSED(port_num);
|
||||
Q_UNUSED(data);
|
||||
if (!m_fnAuxSerialReceive || !m_execEnv || !m_fnMalloc || !m_fnFree ||
|
||||
data.isEmpty())
|
||||
return;
|
||||
|
||||
QMutexLocker lckr(&m_mutex);
|
||||
uint32_t len = data.size();
|
||||
uint32_t allocArgv[1] = {len};
|
||||
if (!wasm_runtime_call_wasm(m_execEnv, m_fnMalloc, 1, allocArgv) ||
|
||||
!allocArgv[0])
|
||||
return;
|
||||
|
||||
uint32_t wasmBuf = allocArgv[0];
|
||||
void * nativePtr = wasm_runtime_addr_app_to_native(m_moduleInst, wasmBuf);
|
||||
if (nativePtr)
|
||||
memcpy(nativePtr, data.constData(), len);
|
||||
|
||||
uint32_t argv[3] = {(uint32_t)port_num, wasmBuf, len};
|
||||
wasm_runtime_call_wasm(m_execEnv, m_fnAuxSerialReceive, 3, argv);
|
||||
|
||||
uint32_t freeArgv[1] = {wasmBuf};
|
||||
wasm_runtime_call_wasm(m_execEnv, m_fnFree, 1, freeArgv);
|
||||
}
|
||||
|
||||
void WasmSimulatorInterface::onAuxSerialStart(uint8_t port_nr,
|
||||
uint32_t baudrate,
|
||||
uint8_t encoding)
|
||||
{
|
||||
emit auxSerialSetEncoding(port_nr, encoding);
|
||||
if (baudrate != 0)
|
||||
emit auxSerialSetBaudrate(port_nr, baudrate);
|
||||
emit auxSerialStart(port_nr);
|
||||
}
|
||||
|
||||
void WasmSimulatorInterface::onAuxSerialStop(uint8_t port_nr)
|
||||
{
|
||||
emit auxSerialStop(port_nr);
|
||||
}
|
||||
|
||||
void WasmSimulatorInterface::onAuxSerialSetBaudrate(uint8_t port_nr,
|
||||
uint32_t baudrate)
|
||||
{
|
||||
emit auxSerialSetBaudrate(port_nr, baudrate);
|
||||
}
|
||||
|
||||
void WasmSimulatorInterface::onAuxSerialSendBuffer(uint8_t port_nr,
|
||||
const uint8_t * data,
|
||||
uint32_t len)
|
||||
{
|
||||
emit auxSerialSendData(port_nr,
|
||||
QByteArray((const char *)data, (int)len));
|
||||
}
|
||||
|
||||
void WasmSimulatorInterface::run()
|
||||
|
||||
@@ -84,6 +84,15 @@ class WasmSimulatorInterface : public SimulatorInterface
|
||||
// Called by WASM import simuLcdNotify (from WAMR thread)
|
||||
void notifyLcdReady();
|
||||
|
||||
// Called by WASM aux serial imports (from WAMR thread). These re-emit
|
||||
// the matching SimulatorInterface signals so that HostSerialConnector
|
||||
// (wired up in SimulatorMainWindow) drives the real host serial port.
|
||||
void onAuxSerialStart(uint8_t port_nr, uint32_t baudrate, uint8_t encoding);
|
||||
void onAuxSerialStop(uint8_t port_nr);
|
||||
void onAuxSerialSetBaudrate(uint8_t port_nr, uint32_t baudrate);
|
||||
void onAuxSerialSendBuffer(uint8_t port_nr, const uint8_t * data,
|
||||
uint32_t len);
|
||||
|
||||
protected slots:
|
||||
void run();
|
||||
void onLcdNotify();
|
||||
@@ -196,6 +205,9 @@ class WasmSimulatorInterface : public SimulatorInterface
|
||||
static constexpr int MAX_FS_LEDS = 8;
|
||||
uint32_t m_lastFSLedColors[MAX_FS_LEDS] = {};
|
||||
|
||||
// Aux serial: host -> firmware data injection
|
||||
wasm_function_inst_t m_fnAuxSerialReceive = nullptr;
|
||||
|
||||
wasm_function_inst_t m_fnMalloc = nullptr;
|
||||
wasm_function_inst_t m_fnFree = nullptr;
|
||||
};
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <deque>
|
||||
|
||||
int g_snapshot_idx = 0;
|
||||
|
||||
@@ -68,6 +69,10 @@ extern const etx_hal_adc_driver_t simu_adc_driver;
|
||||
void lcdCopy(void * dest, void * src);
|
||||
void lcdFlushed();
|
||||
|
||||
#if defined(AUX_SERIAL) || defined(AUX2_SERIAL)
|
||||
static void hostSerialInit();
|
||||
#endif
|
||||
|
||||
void simuInit()
|
||||
{
|
||||
#if defined(ROTARY_ENCODER_NAVIGATION)
|
||||
@@ -81,6 +86,10 @@ void simuInit()
|
||||
adcInit(&simu_adc_driver);
|
||||
// Switches
|
||||
switchInit();
|
||||
|
||||
#if defined(AUX_SERIAL) || defined(AUX2_SERIAL)
|
||||
hostSerialInit();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool keysStates[MAX_KEYS] = { false };
|
||||
@@ -394,29 +403,111 @@ const etx_serial_port_t UsbSerialPort = { "USB-VCP", nullptr, nullptr };
|
||||
#endif
|
||||
|
||||
#if defined(AUX_SERIAL) || defined(AUX2_SERIAL)
|
||||
static void* null_drv_init(void* hw_def, const etx_serial_init* dev) { return nullptr; }
|
||||
static void null_drv_deinit(void* ctx) { }
|
||||
static void null_drv_send_byte(void* ctx, uint8_t b) { }
|
||||
static void null_drv_send_buffer(void* ctx, const uint8_t* b, uint32_t l) { }
|
||||
static bool null_drv_tx_completed(void* ctx) { return true; }
|
||||
static int null_drv_get_byte(void* ctx, uint8_t* b) { return 0; }
|
||||
static void null_drv_set_baudrate(void* ctx, uint32_t baudrate) { }
|
||||
#if !defined(__wasm__)
|
||||
// Native builds (unit-tests, SDL simulator) don't have a WASM host to
|
||||
// forward aux-serial traffic to: WASM_IMPORT is a no-op macro off-target,
|
||||
// so the declarations in simulib.h become plain externs with no
|
||||
// definitions. Provide no-op stubs so host_drv_* can link — the host
|
||||
// bridge is only meaningfully exercised in the WASM simulator, where
|
||||
// these symbols are resolved as WAMR imports by Companion.
|
||||
void simuAuxSerialStart(uint8_t, uint32_t, uint8_t) {}
|
||||
void simuAuxSerialStop(uint8_t) {}
|
||||
void simuAuxSerialSetBaudrate(uint8_t, uint32_t) {}
|
||||
void simuAuxSerialSendBuffer(uint8_t, const uint8_t*, uint32_t) {}
|
||||
#endif
|
||||
|
||||
const etx_serial_driver_t null_drv = {
|
||||
.init = null_drv_init,
|
||||
.deinit = null_drv_deinit,
|
||||
.sendByte = null_drv_send_byte,
|
||||
.sendBuffer = null_drv_send_buffer,
|
||||
.txCompleted = null_drv_tx_completed,
|
||||
// Per-port bridge state. TX is forwarded to the host via WASM imports;
|
||||
// RX bytes pushed in via simuAuxSerialReceive() are buffered in rxQueue
|
||||
// and consumed by the firmware through host_drv_get_byte().
|
||||
struct host_serial_port_t {
|
||||
uint8_t index;
|
||||
mutex_handle_t rxMutex;
|
||||
std::deque<uint8_t> rxQueue;
|
||||
};
|
||||
|
||||
static host_serial_port_t hostSerialPorts[MAX_AUX_SERIAL] = {
|
||||
{ SP_AUX1, {}, {} },
|
||||
{ SP_AUX2, {}, {} },
|
||||
};
|
||||
|
||||
static void hostSerialInit()
|
||||
{
|
||||
for (uint8_t i = 0; i < MAX_AUX_SERIAL; ++i)
|
||||
mutex_create(&hostSerialPorts[i].rxMutex);
|
||||
}
|
||||
|
||||
static void* host_drv_init(void* hw_def, const etx_serial_init* dev)
|
||||
{
|
||||
if (hw_def == nullptr || dev == nullptr) return nullptr;
|
||||
|
||||
auto* port = static_cast<host_serial_port_t*>(hw_def);
|
||||
mutex_lock(&port->rxMutex);
|
||||
port->rxQueue.clear();
|
||||
mutex_unlock(&port->rxMutex);
|
||||
simuAuxSerialStart(port->index, dev->baudrate, dev->encoding);
|
||||
return port;
|
||||
}
|
||||
|
||||
static void host_drv_deinit(void* ctx)
|
||||
{
|
||||
if (ctx == nullptr) return;
|
||||
auto* port = static_cast<host_serial_port_t*>(ctx);
|
||||
simuAuxSerialStop(port->index);
|
||||
}
|
||||
|
||||
static void host_drv_send_byte(void* ctx, uint8_t b)
|
||||
{
|
||||
if (ctx == nullptr) return;
|
||||
auto* port = static_cast<host_serial_port_t*>(ctx);
|
||||
simuAuxSerialSendBuffer(port->index, &b, 1);
|
||||
}
|
||||
|
||||
static void host_drv_send_buffer(void* ctx, const uint8_t* b, uint32_t l)
|
||||
{
|
||||
if (ctx == nullptr || b == nullptr || l == 0) return;
|
||||
auto* port = static_cast<host_serial_port_t*>(ctx);
|
||||
simuAuxSerialSendBuffer(port->index, b, l);
|
||||
}
|
||||
|
||||
static bool host_drv_tx_completed(void*) { return true; }
|
||||
|
||||
static int host_drv_get_byte(void* ctx, uint8_t* b)
|
||||
{
|
||||
if (ctx == nullptr || b == nullptr) return 0;
|
||||
auto* port = static_cast<host_serial_port_t*>(ctx);
|
||||
mutex_lock(&port->rxMutex);
|
||||
if (port->rxQueue.empty()) {
|
||||
mutex_unlock(&port->rxMutex);
|
||||
return 0;
|
||||
}
|
||||
*b = port->rxQueue.front();
|
||||
port->rxQueue.pop_front();
|
||||
mutex_unlock(&port->rxMutex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void host_drv_set_baudrate(void* ctx, uint32_t baudrate)
|
||||
{
|
||||
if (ctx == nullptr) return;
|
||||
auto* port = static_cast<host_serial_port_t*>(ctx);
|
||||
simuAuxSerialSetBaudrate(port->index, baudrate);
|
||||
}
|
||||
|
||||
const etx_serial_driver_t host_drv = {
|
||||
.init = host_drv_init,
|
||||
.deinit = host_drv_deinit,
|
||||
.sendByte = host_drv_send_byte,
|
||||
.sendBuffer = host_drv_send_buffer,
|
||||
.txCompleted = host_drv_tx_completed,
|
||||
.waitForTxCompleted = nullptr,
|
||||
.enableRx = nullptr,
|
||||
.getByte = null_drv_get_byte,
|
||||
.getByte = host_drv_get_byte,
|
||||
.getLastByte = nullptr,
|
||||
.getBufferedBytes = nullptr,
|
||||
.copyRxBuffer = nullptr,
|
||||
.clearRxBuffer = nullptr,
|
||||
.getBaudrate = nullptr,
|
||||
.setBaudrate = null_drv_set_baudrate,
|
||||
.setBaudrate = host_drv_set_baudrate,
|
||||
.setPolarity = nullptr,
|
||||
.setHWOption = nullptr,
|
||||
.setReceiveCb = nullptr,
|
||||
@@ -424,6 +515,16 @@ const etx_serial_driver_t null_drv = {
|
||||
.setBaudrateCb = nullptr,
|
||||
};
|
||||
|
||||
void simuAuxSerialReceive(uint8_t port_nr, const uint8_t* data, uint32_t len)
|
||||
{
|
||||
if (port_nr >= MAX_AUX_SERIAL || data == nullptr || len == 0) return;
|
||||
auto& port = hostSerialPorts[port_nr];
|
||||
mutex_lock(&port.rxMutex);
|
||||
for (uint32_t i = 0; i < len; ++i)
|
||||
port.rxQueue.push_back(data[i]);
|
||||
mutex_unlock(&port.rxMutex);
|
||||
}
|
||||
|
||||
#if defined(AUX_SERIAL_PWR_GPIO)
|
||||
static void null_pwr_aux(uint8_t) {}
|
||||
#endif
|
||||
@@ -437,8 +538,8 @@ static void null_pwr_aux(uint8_t) {}
|
||||
#endif
|
||||
static etx_serial_port_t auxSerialPort = {
|
||||
"AUX1",
|
||||
&null_drv,
|
||||
nullptr,
|
||||
&host_drv,
|
||||
&hostSerialPorts[SP_AUX1],
|
||||
AUX_SERIAL_PWR
|
||||
};
|
||||
#define AUX_SERIAL_PORT &auxSerialPort
|
||||
@@ -454,8 +555,8 @@ static etx_serial_port_t auxSerialPort = {
|
||||
#endif
|
||||
static etx_serial_port_t aux2SerialPort = {
|
||||
"AUX2",
|
||||
&null_drv,
|
||||
nullptr,
|
||||
&host_drv,
|
||||
&hostSerialPorts[SP_AUX2],
|
||||
AUX2_SERIAL_PWR
|
||||
};
|
||||
#define AUX2_SERIAL_PORT &aux2SerialPort
|
||||
|
||||
@@ -140,6 +140,11 @@ uint8_t WASM_EXPORT(simuGetNumGVars)();
|
||||
uint8_t WASM_EXPORT(simuGetNumFlightModes)();
|
||||
int32_t WASM_EXPORT(simuGetGVar)(uint8_t gv, uint8_t fm);
|
||||
|
||||
// Aux serial: push bytes received from a host serial port into the firmware's
|
||||
// rx queue for the matching aux port (port_nr is 0 for AUX1, 1 for AUX2).
|
||||
void WASM_EXPORT(simuAuxSerialReceive)(uint8_t port_nr, const uint8_t* data,
|
||||
uint32_t len);
|
||||
|
||||
// -- WASM imports (provided by host) --
|
||||
|
||||
// simuGetAnalog: return ADC-range value for input at index idx.
|
||||
@@ -157,6 +162,18 @@ void WASM_IMPORT(simuTrace)(const char* text);
|
||||
// listener so the frame can be rendered without polling.
|
||||
void WASM_IMPORT(simuLcdNotify)();
|
||||
|
||||
// Aux serial bridge (firmware -> host). port_nr is 0 for AUX1, 1 for AUX2.
|
||||
// encoding values match SimulatorSerialEncoding (0=8N1, 1=8E2, 2=PXX1_PWM)
|
||||
// and ETX_Encoding_* — they share the same numeric values. Called when the
|
||||
// firmware initialises an aux serial port (start), shuts it down (stop),
|
||||
// reconfigures the baudrate, or transmits data.
|
||||
void WASM_IMPORT(simuAuxSerialStart)(uint8_t port_nr, uint32_t baudrate,
|
||||
uint8_t encoding);
|
||||
void WASM_IMPORT(simuAuxSerialStop)(uint8_t port_nr);
|
||||
void WASM_IMPORT(simuAuxSerialSetBaudrate)(uint8_t port_nr, uint32_t baudrate);
|
||||
void WASM_IMPORT(simuAuxSerialSendBuffer)(uint8_t port_nr, const uint8_t* data,
|
||||
uint32_t len);
|
||||
|
||||
// -- Internal (not exported) --
|
||||
void simuMain();
|
||||
std::string simuFatfsGetCurrentPath();
|
||||
|
||||
Reference in New Issue
Block a user