GCC Code Coverage Report


Directory: ./
File: Svc/WasmSequencer/WasmSequencerInterpreter.cpp
Date: 2026-10-14 22:11:56
Exec Total Coverage
Lines: 7 409 1.7%
Functions: 2 43 4.7%
Branches: 1 226 0.4%

Line Branch Exec Source
1 // ======================================================================
2 // \title WasmSequencerInterpreter.cpp
3 // \author tumbar
4 // \brief cpp file for WasmSequencer engine state machine
5 // ======================================================================
6
7 #include "Fw/Com/ComPacket.hpp"
8 #include "Fw/Types/Assert.hpp"
9 #include "Fw/Types/LinearBufferTemplate.hpp"
10 #include "Fw/Types/Serializable.hpp"
11 #include "Fw/Types/SuccessEnumAc.hpp"
12 #include "Svc/Seq/BlockStateEnumAc.hpp"
13 #include "Svc/WasmSequencer/WasmSequencer.hpp"
14 #include "Svc/WasmSequencer/WasmSequencer_HostFunctionEnumAc.hpp"
15 #include "Svc/WasmSequencer/WasmSequencer_TrapReasonEnumAc.hpp"
16 #include "config/FwAssertArgTypeAliasAc.h"
17 #include "spacewasm.h"
18
19 namespace Svc {
20
21 // ----------------------------------------------------------------------
22 // Implementations for internal state machine actions
23 // ----------------------------------------------------------------------
24
25 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_cmdReplyOK(
26 SmId smId,
27 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
28 const Svc::WasmSequencer_CommandRequest& value) {
29 ✗ this->cmdResponse_out(value.get_opcode(), value.get_cmdSeq(), Fw::CmdResponse::OK);
30 ✗ }
31
32 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_signalEntered(
33 SmId smId,
34 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
35 ✗ this->interpreter_sendSignal_entered();
36 ✗ }
37
38 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_spin(
39 SmId smId,
40 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
41 ✗ FW_ASSERT(this->m_wasm);
42
43 ✗ Fw::ParamValid prmValid;
44 ✗ const auto fuelParam = this->paramGet_INSTRUCTION_FUEL(prmValid);
45 // Min of 1 so we don't spin the component queue forever
46 ✗ const auto fuel = (fuelParam == 0) ? 1 : fuelParam;
47
48 ✗ spacewasm_trap_t trap = SPACEWASM_TRAP_NONE;
49 ✗ const spacewasm_run_status_t runStatus = spacewasm_run(this->m_wasm, fuel, &trap);
50
51 ✗ switch (runStatus) {
52 ✗ case SPACEWASM_RUN_FINISHED: {
53 spacewasm_value_t result;
54 ✗ auto status = spacewasm_get_result(this->m_wasm, spacewasm_valtype_t::SPACEWASM_I32, &result);
55 ✗ if (status == SPACEWASM_ERR_NOT_FOUND) {
56 // Return status code was "void" (success)
57 ✗ this->interpreter_sendSignal_interpreterFinished(0);
58 } else {
59 ✗ FW_ASSERT(status == SPACEWASM_OK);
60 ✗ FW_ASSERT(result.tag == spacewasm_valtype_t::SPACEWASM_I32);
61 ✗ this->interpreter_sendSignal_interpreterFinished(result.u.i32_);
62 }
63
64 ✗ break;
65 }
66 ✗ case SPACEWASM_RUN_TRAP:
67 // Filter out HOST traps due to exit/panic. These host functions just use trap as a mechanism
68 // to kill the interpreter.
69 ✗ if (trap == SPACEWASM_TRAP_HOST && (this->m_exit.reason == WasmSequencer_ExitReason::HOST_EXIT ||
70 ✗ this->m_exit.reason == WasmSequencer_ExitReason::HOST_PANIC)) {
71 ✗ this->interpreter_sendSignal_interpreterTrap(WasmSequencer_TrapReason::NONE);
72 } else {
73 ✗ this->interpreter_sendSignal_interpreterTrap(WasmSequencer::mapTrapReason(trap));
74 }
75 ✗ break;
76 ✗ case SPACEWASM_RUN_PAUSE:
77 ✗ this->interpreter_sendSignal_interpreterHostFunctionNeedsPause();
78 ✗ break;
79 ✗ case SPACEWASM_RUN_OUT_OF_FUEL:
80 ✗ this->interpreter_sendSignal_interpreterOutOfFuel();
81 ✗ break;
82 ✗ default:
83 ✗ FW_ASSERT(false, runStatus);
84 ✗ break;
85 }
86 ✗ }
87
88 1 void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_reset(
89 SmId smId,
90 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
91
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (this->m_wasm != nullptr) {
92 ✗ auto status = spacewasm_reset(this->m_wasm);
93 ✗ FW_ASSERT(status == SPACEWASM_OK);
94 } else {
95 1 FW_ASSERT(signal == Svc_WasmSequencer_InterpreterStateMachine::Signal::__FPRIME_INITIAL_TRANSITION);
96 }
97 1 }
98
99 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_clearExitStatus(
100 SmId smId,
101 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
102 ✗ this->m_exit.reason = WasmSequencer_ExitReason::UNKNOWN;
103 ✗ this->m_exit.lastHostFunction = WasmSequencer_HostFunction::NONE;
104 ✗ this->m_exit.code = 0;
105 ✗ this->m_exit.lastTrapReason = WasmSequencer_TrapReason::NONE;
106 ✗ }
107
108 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setExitReason_INTERPRETER_FINISHED(
109 SmId smId,
110 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
111 ✗ this->m_exit.reason = WasmSequencer_ExitReason::INTERPRETER_FINISHED;
112 ✗ }
113
114 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setExitReason_INTERPRETER_TRAP(
115 SmId smId,
116 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
117 ✗ if (this->m_exit.reason == WasmSequencer_ExitReason::UNKNOWN) {
118 ✗ this->m_exit.reason = WasmSequencer_ExitReason::INTERPRETER_TRAP;
119 }
120 ✗ }
121
122 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setExitReason_REPLY_TIMEOUT(
123 SmId smId,
124 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
125 ✗ this->m_exit.reason = WasmSequencer_ExitReason::REPLY_TIMEOUT;
126 ✗ }
127
128 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setExitReason_HOST_FAILURE(
129 SmId smId,
130 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
131 ✗ this->m_exit.reason = WasmSequencer_ExitReason::HOST_FAILURE;
132 ✗ }
133
134 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setExitReason_UNEXPECTED_REPLY(
135 SmId smId,
136 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
137 ✗ this->m_exit.reason = WasmSequencer_ExitReason::UNEXPECTED_REPLY;
138 ✗ }
139
140 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setExitReason_TIMER_INCOMPARABLE(
141 SmId smId,
142 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
143 ✗ this->m_exit.reason = WasmSequencer_ExitReason::TIMER_INCOMPARABLE;
144 ✗ }
145
146 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setExitReason_CANCEL(
147 SmId smId,
148 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
149 ✗ this->m_exit.reason = WasmSequencer_ExitReason::CANCEL;
150 ✗ }
151
152 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setExitCode(
153 SmId smId,
154 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
155 I32 value) {
156 ✗ this->m_exit.code = value;
157 ✗ }
158
159 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setTrapReason(
160 SmId smId,
161 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
162 const Svc::WasmSequencer_TrapReason& value) {
163 ✗ this->m_exit.lastTrapReason = value;
164 ✗ }
165
166 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setLastHostFunction(
167 SmId smId,
168 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
169 ✗ this->m_exit.lastHostFunction = this->m_pendingHostFunction.kind;
170 ✗ }
171
172 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_finish(
173 SmId smId,
174 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
175 ✗ this->controller_sendSignal_engineFinished(this->m_executingContext);
176 ✗ }
177
178 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_reportPaused(
179 SmId smId,
180 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
181 ✗ this->log_ACTIVITY_HI_SequencePaused();
182 ✗ }
183
184 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_clearPause(
185 SmId smId,
186 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
187 ✗ this->m_pendingPause = false;
188 ✗ }
189
190 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_dispatchPendingHostFunction(
191 SmId smId,
192 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
193 ✗ switch (this->m_pendingHostFunction.kind) {
194 ✗ case WasmSequencer_HostFunction::NONE:
195 // Invalid host function
196 ✗ FW_ASSERT(false);
197 ✗ break;
198 ✗ case WasmSequencer_HostFunction::COMMAND:
199 ✗ this->dispatchCommand();
200 ✗ break;
201 ✗ case WasmSequencer_HostFunction::TELEMETRY:
202 ✗ this->dispatchTelemetry();
203 ✗ break;
204 ✗ case WasmSequencer_HostFunction::PARAMETER:
205 ✗ this->dispatchParameter();
206 ✗ break;
207 ✗ case WasmSequencer_HostFunction::EVENT:
208 ✗ this->dispatchEvent();
209 ✗ break;
210 ✗ case WasmSequencer_HostFunction::RSLEEP:
211 ✗ this->dispatchRelativeSleep();
212 ✗ break;
213 ✗ case WasmSequencer_HostFunction::ASLEEP:
214 ✗ this->dispatchAbsoluteSleep();
215 ✗ break;
216 ✗ case WasmSequencer_HostFunction::TIME:
217 ✗ this->dispatchTime();
218 ✗ break;
219 ✗ case WasmSequencer_HostFunction::SERIAL_OUT:
220 ✗ this->dispatchSerialOut();
221 ✗ break;
222 ✗ case WasmSequencer_HostFunction::SERIAL_RECV:
223 ✗ this->dispatchSerialRecv();
224 ✗ break;
225 }
226 ✗ }
227
228 ✗ Fw::Success WasmSequencer ::readGuestMemory(WasmSequencer_HostFunction::T kind, U32 addr, U8* dst, FwSizeType len) {
229 ✗ const spacewasm_status_t status = spacewasm_mem_read(this->m_pendingHostFunction.caller, addr, dst, len);
230 ✗ if (status != SPACEWASM_OK) {
231 ✗ this->log_WARNING_HI_HostFunctionInvalidPointer(kind, static_cast<WasmSequencer_Status::T>(status));
232 ✗ return Fw::Success::FAILURE;
233 } else {
234 ✗ return Fw::Success::SUCCESS;
235 }
236 }
237
238 ✗ Fw::Success WasmSequencer ::writeGuestMemory(WasmSequencer_HostFunction::T kind,
239 U32 addr,
240 const U8* src,
241 FwSizeType len) {
242 ✗ const spacewasm_status_t status = spacewasm_mem_write(this->m_pendingHostFunction.caller, addr, src, len);
243 ✗ if (status != SPACEWASM_OK) {
244 ✗ this->log_WARNING_HI_HostFunctionInvalidPointer(kind, static_cast<WasmSequencer_Status::T>(status));
245 ✗ return Fw::Success::FAILURE;
246 } else {
247 ✗ return Fw::Success::SUCCESS;
248 }
249 }
250
251 // ----------------------------------------------------------------------
252 // Per-host-function dispatch helpers (arms of dispatchPendingHostFunction)
253 // ----------------------------------------------------------------------
254
255 ✗ void WasmSequencer ::dispatchCommand() {
256 ✗ Fw::ComBuffer cmd;
257
258 // Write the CMD descriptor to the ComBuffer
259 ✗ auto serStatus = cmd.serializeFrom(static_cast<FwPacketDescriptorType>(Fw::ComPacketType::FW_PACKET_COMMAND));
260 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
261
262 // Copy the com buffer from the guest memory into our memory
263 ✗ if (this->readGuestMemory(Svc::WasmSequencer_HostFunction::COMMAND, this->m_pendingHostFunction.u.command.ptr,
264 ✗ cmd.getBuffAddr() + sizeof(FwPacketDescriptorType),
265 ✗ this->m_pendingHostFunction.u.command.len) != Fw::Success::SUCCESS) {
266 ✗ this->interpreter_sendSignal_hostResponseFailure();
267 ✗ return;
268 }
269
270 // Memory read succeeded, update the ComBuffer to hold the encoded command
271 ✗ serStatus = cmd.moveSerToOffset(this->m_pendingHostFunction.u.command.len + sizeof(FwPacketDescriptorType));
272 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
273
274 // Dispatch command to CmdDisp. The command context (cmdUid)
275 // encodes the current sequence + command instance so we can
276 // reject late/stale responses in cmdResponseIn_handler.
277 ✗ this->m_tlm.commandsDispatched++;
278
279 // Start the host-function timeout clock: we are about to block in
280 // AWAITING_RESPONSE until the command response comes back in.
281 ✗ this->m_hostFunctionStart = this->getTime();
282 ✗ this->m_hasHostFunctionStart = true;
283
284 ✗ this->cmdOut_out(0, cmd, this->makeCmdUid());
285 ✗ }
286
287 ✗ void WasmSequencer ::dispatchTelemetry() {
288 ✗ Fw::Time time;
289 ✗ Fw::TlmBuffer tlmBuffer;
290
291 ✗ auto valid = this->getTlmChan_out(0, this->m_pendingHostFunction.u.telemetry.chanId, time, tlmBuffer);
292
293 // Write the response into guest memory
294 ✗ FW_ASSERT(this->m_pendingHostFunction.u.telemetry.timeLen == Fw::Time::SERIALIZED_SIZE,
295 static_cast<FwAssertArgType>(this->m_pendingHostFunction.u.telemetry.timeLen), Fw::Time::SERIALIZED_SIZE);
296 ✗ Fw::LinearBufferTemplate<Fw::Time::SERIALIZED_SIZE> timeBuf;
297
298 ✗ auto serStatus = time.serializeTo(timeBuf);
299 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
300
301 // Write the time
302 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::TELEMETRY,
303 ✗ this->m_pendingHostFunction.u.telemetry.timePtr, timeBuf.getBuffAddr(),
304 ✗ this->m_pendingHostFunction.u.telemetry.timeLen) != Fw::Success::SUCCESS) {
305 ✗ this->interpreter_sendSignal_hostResponseFailure();
306 ✗ return;
307 }
308
309 ✗ if (tlmBuffer.getSize() > this->m_pendingHostFunction.u.telemetry.valueLen) {
310 ✗ this->log_WARNING_HI_BufferTooSmall(WasmSequencer_HostFunction::TELEMETRY,
311 this->m_pendingHostFunction.u.telemetry.valueLen,
312 ✗ static_cast<U32>(tlmBuffer.getSize()));
313 ✗ this->interpreter_sendSignal_hostResponseFailure();
314 ✗ return;
315 }
316
317 // Write the value
318 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::TELEMETRY,
319 ✗ this->m_pendingHostFunction.u.telemetry.valuePtr, tlmBuffer.getBuffAddr(),
320 ✗ tlmBuffer.getSize()) != Fw::Success::SUCCESS) {
321 ✗ this->interpreter_sendSignal_hostResponseFailure();
322 ✗ return;
323 }
324
325 ✗ this->interpreter_sendSignal_hostResumeI32(static_cast<I32>(valid.e));
326 ✗ }
327
328 ✗ void WasmSequencer ::dispatchParameter() {
329 ✗ Fw::ParamBuffer prmBuf;
330 ✗ auto prmStatus = this->getParam_out(0, this->m_pendingHostFunction.u.parameter.prmId, prmBuf);
331
332 ✗ if (prmBuf.getSize() > this->m_pendingHostFunction.u.parameter.len) {
333 ✗ this->log_WARNING_HI_BufferTooSmall(WasmSequencer_HostFunction::PARAMETER,
334 this->m_pendingHostFunction.u.parameter.len,
335 ✗ static_cast<U32>(prmBuf.getSize()));
336 ✗ this->interpreter_sendSignal_hostResponseFailure();
337 ✗ return;
338 }
339
340 // Write the parameter to linear memory
341 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::PARAMETER, this->m_pendingHostFunction.u.parameter.ptr,
342 ✗ prmBuf.getBuffAddr(), prmBuf.getSize()) != Fw::Success::SUCCESS) {
343 ✗ this->interpreter_sendSignal_hostResponseFailure();
344 ✗ return;
345 }
346
347 ✗ this->interpreter_sendSignal_hostResumeI32(static_cast<I32>(prmStatus.e));
348 ✗ }
349
350 ✗ void WasmSequencer ::dispatchEvent() {
351 U8 stringStorage[FW_LOG_STRING_MAX_SIZE + 1];
352 ✗ FW_ASSERT(this->m_pendingHostFunction.u.event.msgLen <= FW_LOG_STRING_MAX_SIZE,
353 static_cast<FwAssertArgType>(this->m_pendingHostFunction.u.event.msgLen), FW_LOG_STRING_MAX_SIZE);
354 ✗ const Fw::ExternalString msg(reinterpret_cast<char*>(stringStorage), FW_LOG_STRING_MAX_SIZE + 1);
355
356 ✗ if (this->readGuestMemory(Svc::WasmSequencer_HostFunction::EVENT, this->m_pendingHostFunction.u.event.msgPtr,
357 ✗ stringStorage, this->m_pendingHostFunction.u.event.msgLen) != Fw::Success::SUCCESS) {
358 ✗ this->interpreter_sendSignal_hostResponseFailure();
359 ✗ return;
360 }
361 ✗ stringStorage[this->m_pendingHostFunction.u.event.msgLen] = 0;
362
363 // Emit the event at the guest-requested severity. FATAL and
364 // COMMAND are forbidden for guest programs (FATAL would let
365 // untrusted code trigger the FatalHandler; COMMAND is reserved
366 // for the command dispatcher). A forbidden or out-of-range
367 // severity is reported via HostFunctionInvalidSeverity, carrying
368 // the raw id and the guest message, and the guest continues.
369 ✗ const I32 rawSeverity = static_cast<I32>(this->m_pendingHostFunction.u.event.rawSeverity);
370 ✗ switch (static_cast<Fw::LogSeverity::T>(rawSeverity)) {
371 ✗ case Fw::LogSeverity::WARNING_HI:
372 ✗ this->log_WARNING_HI_LogWarningHi(msg);
373 ✗ break;
374 ✗ case Fw::LogSeverity::WARNING_LO:
375 ✗ this->log_WARNING_LO_LogWarningLo(msg);
376 ✗ break;
377 ✗ case Fw::LogSeverity::ACTIVITY_HI:
378 ✗ this->log_ACTIVITY_HI_LogActivityHi(msg);
379 ✗ break;
380 ✗ case Fw::LogSeverity::ACTIVITY_LO:
381 ✗ this->log_ACTIVITY_LO_LogActivityLo(msg);
382 ✗ break;
383 ✗ case Fw::LogSeverity::DIAGNOSTIC:
384 ✗ this->log_DIAGNOSTIC_LogDiagnostic(msg);
385 ✗ break;
386 ✗ case Fw::LogSeverity::FATAL: // fallthrough to catch restricted severities
387 case Fw::LogSeverity::COMMAND:
388 default:
389 ✗ this->log_WARNING_HI_HostFunctionInvalidSeverity(rawSeverity, msg);
390 ✗ break;
391 }
392
393 ✗ this->interpreter_sendSignal_hostResume();
394 ✗ }
395
396 ✗ void WasmSequencer ::dispatchRelativeSleep() {
397 ✗ const U32 seconds = static_cast<U32>(this->m_pendingHostFunction.u.rsleep.us / 1000000);
398 ✗ const U32 useconds = static_cast<U32>(this->m_pendingHostFunction.u.rsleep.us % 1000000);
399
400 ✗ const Fw::Time now = this->getTime();
401 const U64 deadlineSeconds =
402 ✗ static_cast<U64>(now.getSeconds()) + seconds + (static_cast<U64>(now.getUSeconds()) + useconds) / 1000000u;
403
404 ✗ Fw::Time timer = now;
405 ✗ if (deadlineSeconds > static_cast<U64>(std::numeric_limits<U32>::max())) {
406 ✗ timer.set(std::numeric_limits<U32>::max(), 999999u);
407 } else {
408 ✗ timer.add(seconds, useconds);
409 }
410
411 ✗ this->m_pendingTimer = timer;
412 ✗ this->m_hasPendingTimer = true;
413 ✗ }
414
415 ✗ void WasmSequencer ::dispatchAbsoluteSleep() {
416 ✗ U32 seconds = static_cast<U32>(this->m_pendingHostFunction.u.asleep.us / 1000000);
417 ✗ U32 useconds = static_cast<U32>(this->m_pendingHostFunction.u.asleep.us % 1000000);
418
419 // Absolute is relative to epoch, we still need to get the time for base/context
420 ✗ Fw::Time timer = this->getTime();
421 ✗ timer.set(seconds, useconds);
422
423 ✗ this->m_pendingTimer = timer;
424 ✗ this->m_hasPendingTimer = true;
425 ✗ }
426
427 ✗ void WasmSequencer ::dispatchTime() {
428 ✗ auto time = this->getTime();
429
430 ✗ FW_ASSERT(this->m_pendingHostFunction.u.time.len == Fw::Time::SERIALIZED_SIZE,
431 static_cast<FwAssertArgType>(this->m_pendingHostFunction.u.time.len), Fw::Time::SERIALIZED_SIZE);
432 ✗ Fw::LinearBufferTemplate<Fw::Time::SERIALIZED_SIZE> timeBuf;
433
434 ✗ auto serStatus = time.serializeTo(timeBuf);
435 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
436
437 // Write the time
438 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::TIME, this->m_pendingHostFunction.u.time.ptr,
439 ✗ timeBuf.getBuffAddr(), this->m_pendingHostFunction.u.time.len) != Fw::Success::SUCCESS) {
440 ✗ this->interpreter_sendSignal_hostResponseFailure();
441 ✗ return;
442 }
443
444 ✗ this->interpreter_sendSignal_hostResume();
445 ✗ }
446
447 ✗ void WasmSequencer ::dispatchSerialOut() {
448 ✗ const FwIndexType portNum = static_cast<FwIndexType>(this->m_pendingHostFunction.u.serialOut.index);
449
450 // Copy the payload out of guest memory into our own buffer
451 ✗ if (this->readGuestMemory(Svc::WasmSequencer_HostFunction::SERIAL_OUT, this->m_pendingHostFunction.u.serialOut.ptr,
452 this->m_serialOutBuffer.getBuffAddr(),
453 ✗ this->m_pendingHostFunction.u.serialOut.len) != Fw::Success::SUCCESS) {
454 ✗ this->interpreter_sendSignal_hostResponseFailure();
455 ✗ return;
456 }
457
458 ✗ auto serStatus = this->m_serialOutBuffer.setBuffLen(this->m_pendingHostFunction.u.serialOut.len);
459 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
460
461 // Invoke the serial output port.
462 ✗ serStatus = this->serialOut_out(portNum, this->m_serialOutBuffer);
463 ✗ if (serStatus != Fw::FW_SERIALIZE_OK) {
464 ✗ this->log_WARNING_HI_SerialPortSendFailed(Svc::WasmSequencer_HostFunction::SERIAL_OUT,
465 static_cast<I32>(serStatus));
466 ✗ this->interpreter_sendSignal_hostResponseFailure();
467 ✗ return;
468 }
469
470 // Send worked, wake up the interpreter
471 ✗ this->interpreter_sendSignal_hostResume();
472 }
473
474 ✗ void WasmSequencer ::dispatchSerialRecv() {
475 ✗ const FwIndexType portNum = static_cast<FwIndexType>(this->m_pendingHostFunction.u.serialRecv.index);
476 ✗ FW_ASSERT(portNum < NUM_SERIALIN_INPUT_PORTS, portNum);
477 ✗ Os::ScopeLock scopeLock(this->m_serialInMutex);
478 ✗ auto& queue = this->m_serialInQueue[portNum];
479
480 // Check if there is an available message on the queue
481 ✗ if (queue.get_allocated_size() > 0) {
482 // There is data available signal to the state machine to pull this data and wake up without blocking
483 ✗ this->interpreter_sendSignal_serialInMessage(portNum);
484 } else {
485 // Check if we should block or not
486 ✗ switch (this->m_pendingHostFunction.u.serialRecv.blockingType) {
487 ✗ case Svc::BlockState::BLOCK:
488 // Do not immediately resume the interpreter, this will let the state machine asynchronously
489 // wait for a signal that we got a message (or timeout).
490 ✗ this->m_hostFunctionStart = this->getTime();
491 ✗ this->m_hasHostFunctionStart = true;
492 ✗ break;
493 ✗ case Svc::BlockState::NO_BLOCK:
494 // We are non-blocking and the queue is empty, report this back to the interpreter and wake back
495 // up
496 ✗ constexpr I32 FPRIME_SERIAL_RECV_QUEUE_STATUS_EMPTY = 1;
497 ✗ this->interpreter_sendSignal_hostResumeI32(FPRIME_SERIAL_RECV_QUEUE_STATUS_EMPTY);
498 ✗ break;
499 }
500 }
501 ✗ }
502
503 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_clearPendingHostFunction(
504 SmId smId,
505 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
506 ✗ this->m_pendingHostFunction.kind = WasmSequencer_HostFunction::NONE;
507 ✗ this->m_pendingHostFunction.caller = nullptr;
508 ✗ this->m_hasPendingTimer = false;
509 ✗ this->m_hasHostFunctionStart = false;
510 ✗ }
511
512 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setContext(
513 SmId smId,
514 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
515 const Svc::WasmSequencer_RequestContext& value) {
516 ✗ FW_ASSERT(!this->m_hasExecutingContext);
517 ✗ this->m_hasExecutingContext = true;
518 ✗ this->m_executingContext = value;
519
520 // A fresh execution window is starting. Bump the sequence counter so any command
521 // dispatched from this program carries a distinct cmdUid
522 ✗ this->m_sequencesStarted++;
523 ✗ }
524
525 1 void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_clearContext(
526 SmId smId,
527 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
528 1 this->m_hasExecutingContext = false;
529 1 }
530
531 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_resume(
532 SmId smId,
533 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
534 ✗ FW_ASSERT(this->m_wasm != nullptr);
535 ✗ auto status = spacewasm_resume(this->m_wasm);
536 ✗ FW_ASSERT(status == SPACEWASM_OK, status);
537 ✗ }
538
539 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_resumeI32(
540 SmId smId,
541 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
542 I32 value) {
543 ✗ FW_ASSERT(this->m_wasm != nullptr);
544 spacewasm_value_t return_val;
545 ✗ return_val.tag = SPACEWASM_I32;
546 ✗ return_val.u.i32_ = value;
547
548 ✗ auto status = spacewasm_resume_value(this->m_wasm, return_val);
549 ✗ FW_ASSERT(status == SPACEWASM_OK, status);
550 ✗ }
551
552 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_checkSleepTimers(
553 SmId smId,
554 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
555 // Check if we have overrun the timer
556 ✗ FW_ASSERT(this->m_hasPendingTimer);
557
558 ✗ const Fw::Time now = this->getTime();
559 ✗ switch (Fw::Time::compare(now, this->m_pendingTimer)) {
560 ✗ case Fw::TimeComparison::LT:
561 // No timer overrun
562 ✗ break;
563 ✗ case Fw::TimeComparison::EQ:
564 case Fw::TimeComparison::GT: {
565 // Timeout!
566 ✗ this->interpreter_sendSignal_hostResume();
567 ✗ break;
568 }
569 ✗ case Fw::TimeComparison::INCOMPARABLE:
570 // Time base / context changed since we set the timer
571 ✗ this->interpreter_sendSignal_hostResponseTimeIncomparable();
572 ✗ break;
573 }
574 ✗ }
575
576 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_checkTimeout(
577 SmId smId,
578 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
579 // Only blocking host functions that await an external event are subject to the
580 // host-function timeout (COMMAND -> cmdResponseIn, blocking SERIAL_RECV -> serialIn).
581 // Sleeps have their own wake timer (checkSleepTimers), separate from this timeout.
582 ✗ if (!this->m_hasHostFunctionStart) {
583 ✗ return;
584 }
585
586 ✗ Fw::ParamValid prmValid;
587 ✗ const F32 timeoutSecs = this->paramGet_HOST_FUNCTION_TIMEOUT_SECS(prmValid);
588
589 // A non-positive or out-of-range timeout disables the check entirely.
590 ✗ if (timeoutSecs <= 0.0f || timeoutSecs >= static_cast<F32>(std::numeric_limits<U32>::max())) {
591 ✗ return;
592 }
593
594 // Deadline = function start + timeout. Round microseconds up so the timeout
595 // is never reported early.
596 ✗ U32 seconds = static_cast<U32>(timeoutSecs);
597 ✗ U32 useconds = static_cast<U32>((timeoutSecs - static_cast<F32>(seconds)) * 1000000.0f + 0.5f);
598
599 // Rounding up can push the microsecond field to 1000000; carry it into
600 // seconds so Fw::Time::add() always receives a normalized (< 1e6) value.
601 ✗ if (useconds >= 1000000u) {
602 ✗ seconds += useconds / 1000000u;
603 ✗ useconds %= 1000000u;
604 }
605
606 ✗ Fw::Time deadline = this->m_hostFunctionStart;
607 ✗ deadline.add(seconds, useconds);
608
609 ✗ const Fw::Time now = this->getTime();
610 ✗ switch (Fw::Time::compare(now, deadline)) {
611 ✗ case Fw::TimeComparison::LT:
612 // Deadline not yet reached.
613 ✗ break;
614 ✗ case Fw::TimeComparison::EQ:
615 case Fw::TimeComparison::GT:
616 // Host function timed out waiting for its reply.
617 ✗ this->interpreter_sendSignal_hostResponseTimeout();
618 ✗ break;
619 ✗ case Fw::TimeComparison::INCOMPARABLE:
620 // Time base / context changed since the host function started.
621 ✗ this->interpreter_sendSignal_hostResponseTimeIncomparable();
622 ✗ break;
623 }
624 ✗ }
625
626 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_dequeueSerialAndResume(
627 SmId smId,
628 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
629 const FwIndexType& value) {
630 ✗ const FwIndexType portNum = static_cast<FwIndexType>(this->m_pendingHostFunction.u.serialRecv.index);
631 ✗ FW_ASSERT(portNum < NUM_SERIALIN_INPUT_PORTS, portNum);
632 ✗ Os::ScopeLock scopeLock(this->m_serialInMutex);
633 ✗ auto& queue = this->m_serialInQueue[portNum];
634
635 ✗ this->m_dequeueSucceeded = false;
636
637 // Message is available, pull out the size
638 U32 msgSize;
639 ✗ auto status = queue.peek(msgSize);
640 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
641
642 ✗ if (msgSize > this->m_pendingHostFunction.u.serialRecv.dataSize) {
643 ✗ this->log_WARNING_HI_BufferTooSmall(Svc::WasmSequencer_HostFunction::SERIAL_RECV,
644 this->m_pendingHostFunction.u.serialRecv.dataSize, msgSize);
645 // The frame doesn't fit in the guest's buffer and never will; drop it from the queue
646 // so it doesn't poison every subsequent serial_recv call on this port.
647 ✗ status = queue.rotate(static_cast<U32>(sizeof(U32)) + msgSize);
648 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
649 ✗ return;
650 }
651
652 // The queue holds [U32 size][payload]; skip the size prefix when copying the payload.
653 ✗ const U32 queuePayloadStart = sizeof(U32);
654 ✗ const U32 dataSize = static_cast<U32>(sizeof(U32)) + msgSize;
655
656 // Write the message size in little endian to the guest memory
657 ✗ Fw::LinearBufferTemplate<sizeof(U32)> msgSizeSer;
658 ✗ status = msgSizeSer.serializeFrom(msgSize, Fw::Endianness::LITTLE);
659 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
660
661 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::SERIAL_RECV,
662 ✗ this->m_pendingHostFunction.u.serialRecv.actualSizePtr, msgSizeSer.getBuffAddr(),
663 ✗ sizeof(U32)) != Fw::Success::SUCCESS) {
664 ✗ return;
665 }
666
667 // Pull the message off the queue, we may need to do it in multiple chunks
668 ✗ constexpr U32 CHUNK_SIZE = 32;
669 U8 scratch[CHUNK_SIZE];
670
671 // Pull all the full chunks off the queue
672 ✗ U32 queueOffset = queuePayloadStart;
673 ✗ for (; (queueOffset + CHUNK_SIZE) <= dataSize; queueOffset += CHUNK_SIZE) {
674 ✗ status = queue.peek(scratch, CHUNK_SIZE, queueOffset);
675 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
676
677 // Copy the data into the guest memory
678 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::SERIAL_RECV,
679 ✗ this->m_pendingHostFunction.u.serialRecv.dataPtr + (queueOffset - queuePayloadStart),
680 ✗ scratch, CHUNK_SIZE) != Fw::Success::SUCCESS) {
681 ✗ return;
682 }
683 }
684
685 // Pull out the final partial chunk
686 ✗ if (queueOffset < dataSize) {
687 ✗ const U32 remaining = dataSize - queueOffset;
688 ✗ FW_ASSERT(remaining < CHUNK_SIZE, static_cast<FwAssertArgType>(dataSize),
689 static_cast<FwAssertArgType>(queueOffset), CHUNK_SIZE);
690
691 ✗ status = queue.peek(scratch, remaining, queueOffset);
692 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
693
694 // Copy the data into the guest memory
695 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::SERIAL_RECV,
696 ✗ this->m_pendingHostFunction.u.serialRecv.dataPtr + (queueOffset - queuePayloadStart),
697 ✗ scratch, remaining) != Fw::Success::SUCCESS) {
698 ✗ return;
699 }
700 }
701
702 // Dequeue the message now that we fully copied it into guest memory
703 ✗ status = queue.rotate(dataSize);
704 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK);
705
706 ✗ this->m_dequeueSucceeded = true;
707
708 // Yay, we successfully dequeued a message from the queue, wake up the interpreter to tell it about our success
709 ✗ constexpr I32 FPRIME_SERIAL_RECV_QUEUE_STATUS_OK = 0;
710 spacewasm_value_t return_val;
711 ✗ return_val.tag = SPACEWASM_I32;
712 ✗ return_val.u.i32_ = FPRIME_SERIAL_RECV_QUEUE_STATUS_OK;
713
714 ✗ auto resumeStatus = spacewasm_resume_value(this->m_wasm, return_val);
715 ✗ FW_ASSERT(resumeStatus == SPACEWASM_OK, resumeStatus);
716 ✗ }
717
718 // ----------------------------------------------------------------------
719 // Implementations for internal state machine guards
720 // ----------------------------------------------------------------------
721
722 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_pendingPause(
723 SmId smId,
724 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) const {
725 ✗ return this->m_pendingPause;
726 }
727
728 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_pendingHostFunction(
729 SmId smId,
730 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) const {
731 ✗ return this->m_pendingHostFunction.isPending();
732 }
733
734 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_pendingHostFunctionIsSleep(
735 SmId smId,
736 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) const {
737 ✗ return this->m_pendingHostFunction.kind == WasmSequencer_HostFunction::ASLEEP ||
738 ✗ this->m_pendingHostFunction.kind == WasmSequencer_HostFunction::RSLEEP;
739 }
740
741 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_blockingSerialIn(
742 SmId smId,
743 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
744 const FwIndexType& value) const {
745 ✗ return (this->m_pendingHostFunction.kind == Svc::WasmSequencer_HostFunction::SERIAL_RECV &&
746 ✗ this->m_pendingHostFunction.u.serialRecv.index == static_cast<U32>(value));
747 }
748
749 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_dequeueSucceeded(
750 SmId smId,
751 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) const {
752 ✗ return this->m_dequeueSucceeded;
753 }
754
755 } // namespace Svc
756