GCC Code Coverage Report


Directory: ./
File: Svc/WasmSequencer/WasmSequencerInterpreter.cpp
Date: 2026-09-23 22:11:34
Exec Total Coverage
Lines: 7 428 1.6%
Functions: 2 44 4.5%
Branches: 1 238 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::ARGS:
217 ✗ this->dispatchArgs();
218 ✗ break;
219 ✗ case WasmSequencer_HostFunction::TIME:
220 ✗ this->dispatchTime();
221 ✗ break;
222 ✗ case WasmSequencer_HostFunction::SERIAL_OUT:
223 ✗ this->dispatchSerialOut();
224 ✗ break;
225 ✗ case WasmSequencer_HostFunction::SERIAL_RECV:
226 ✗ this->dispatchSerialRecv();
227 ✗ break;
228 }
229 ✗ }
230
231 ✗ Fw::Success WasmSequencer ::readGuestMemory(WasmSequencer_HostFunction::T kind, U32 addr, U8* dst, FwSizeType len) {
232 ✗ const spacewasm_status_t status = spacewasm_mem_read(this->m_pendingHostFunction.caller, addr, dst, len);
233 ✗ if (status != SPACEWASM_OK) {
234 ✗ this->log_WARNING_HI_HostFunctionInvalidPointer(kind, static_cast<WasmSequencer_Status::T>(status));
235 ✗ return Fw::Success::FAILURE;
236 } else {
237 ✗ return Fw::Success::SUCCESS;
238 }
239 }
240
241 ✗ Fw::Success WasmSequencer ::writeGuestMemory(WasmSequencer_HostFunction::T kind,
242 U32 addr,
243 const U8* src,
244 FwSizeType len) {
245 ✗ const spacewasm_status_t status = spacewasm_mem_write(this->m_pendingHostFunction.caller, addr, src, len);
246 ✗ if (status != SPACEWASM_OK) {
247 ✗ this->log_WARNING_HI_HostFunctionInvalidPointer(kind, static_cast<WasmSequencer_Status::T>(status));
248 ✗ return Fw::Success::FAILURE;
249 } else {
250 ✗ return Fw::Success::SUCCESS;
251 }
252 }
253
254 // ----------------------------------------------------------------------
255 // Per-host-function dispatch helpers (arms of dispatchPendingHostFunction)
256 // ----------------------------------------------------------------------
257
258 ✗ void WasmSequencer ::dispatchCommand() {
259 ✗ Fw::ComBuffer cmd;
260
261 // Write the CMD descriptor to the ComBuffer
262 ✗ auto serStatus = cmd.serializeFrom(static_cast<FwPacketDescriptorType>(Fw::ComPacketType::FW_PACKET_COMMAND));
263 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
264
265 // Copy the com buffer from the guest memory into our memory
266 ✗ if (this->readGuestMemory(Svc::WasmSequencer_HostFunction::COMMAND, this->m_pendingHostFunction.u.command.ptr,
267 ✗ cmd.getBuffAddr() + sizeof(FwPacketDescriptorType),
268 ✗ this->m_pendingHostFunction.u.command.len) != Fw::Success::SUCCESS) {
269 ✗ this->interpreter_sendSignal_hostResponseFailure();
270 ✗ return;
271 }
272
273 // Memory read succeeded, update the ComBuffer to hold the encoded command
274 ✗ serStatus = cmd.moveSerToOffset(this->m_pendingHostFunction.u.command.len + sizeof(FwPacketDescriptorType));
275 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
276
277 // Dispatch command to CmdDisp. The command context (cmdUid)
278 // encodes the current sequence + command instance so we can
279 // reject late/stale responses in cmdResponseIn_handler.
280 ✗ this->m_tlm.commandsDispatched++;
281
282 // Start the host-function timeout clock: we are about to block in
283 // AWAITING_RESPONSE until the command response comes back in.
284 ✗ this->m_hostFunctionStart = this->getTime();
285 ✗ this->m_hasHostFunctionStart = true;
286
287 ✗ this->cmdOut_out(0, cmd, this->makeCmdUid());
288 ✗ }
289
290 ✗ void WasmSequencer ::dispatchTelemetry() {
291 ✗ Fw::Time time;
292 ✗ Fw::TlmBuffer tlmBuffer;
293
294 ✗ auto valid = this->getTlmChan_out(0, this->m_pendingHostFunction.u.telemetry.chanId, time, tlmBuffer);
295
296 // Write the response into guest memory
297 ✗ FW_ASSERT(this->m_pendingHostFunction.u.telemetry.timeLen == Fw::Time::SERIALIZED_SIZE,
298 static_cast<FwAssertArgType>(this->m_pendingHostFunction.u.telemetry.timeLen), Fw::Time::SERIALIZED_SIZE);
299 ✗ Fw::LinearBufferTemplate<Fw::Time::SERIALIZED_SIZE> timeBuf;
300
301 ✗ auto serStatus = time.serializeTo(timeBuf);
302 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
303
304 // Write the time
305 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::TELEMETRY,
306 ✗ this->m_pendingHostFunction.u.telemetry.timePtr, timeBuf.getBuffAddr(),
307 ✗ this->m_pendingHostFunction.u.telemetry.timeLen) != Fw::Success::SUCCESS) {
308 ✗ this->interpreter_sendSignal_hostResponseFailure();
309 ✗ return;
310 }
311
312 ✗ if (tlmBuffer.getSize() > this->m_pendingHostFunction.u.telemetry.valueLen) {
313 ✗ this->log_WARNING_HI_BufferTooSmall(WasmSequencer_HostFunction::TELEMETRY,
314 this->m_pendingHostFunction.u.telemetry.valueLen,
315 ✗ static_cast<U32>(tlmBuffer.getSize()));
316 ✗ this->interpreter_sendSignal_hostResponseFailure();
317 ✗ return;
318 }
319
320 // Write the value
321 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::TELEMETRY,
322 ✗ this->m_pendingHostFunction.u.telemetry.valuePtr, tlmBuffer.getBuffAddr(),
323 ✗ tlmBuffer.getSize()) != Fw::Success::SUCCESS) {
324 ✗ this->interpreter_sendSignal_hostResponseFailure();
325 ✗ return;
326 }
327
328 ✗ this->interpreter_sendSignal_hostResumeI32(static_cast<I32>(valid.e));
329 ✗ }
330
331 ✗ void WasmSequencer ::dispatchParameter() {
332 ✗ Fw::ParamBuffer prmBuf;
333 ✗ auto prmStatus = this->getParam_out(0, this->m_pendingHostFunction.u.parameter.prmId, prmBuf);
334
335 ✗ if (prmBuf.getSize() > this->m_pendingHostFunction.u.parameter.len) {
336 ✗ this->log_WARNING_HI_BufferTooSmall(WasmSequencer_HostFunction::PARAMETER,
337 this->m_pendingHostFunction.u.parameter.len,
338 ✗ static_cast<U32>(prmBuf.getSize()));
339 ✗ this->interpreter_sendSignal_hostResponseFailure();
340 ✗ return;
341 }
342
343 // Write the parameter to linear memory
344 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::PARAMETER, this->m_pendingHostFunction.u.parameter.ptr,
345 ✗ prmBuf.getBuffAddr(), prmBuf.getSize()) != Fw::Success::SUCCESS) {
346 ✗ this->interpreter_sendSignal_hostResponseFailure();
347 ✗ return;
348 }
349
350 ✗ this->interpreter_sendSignal_hostResumeI32(static_cast<I32>(prmStatus.e));
351 ✗ }
352
353 ✗ void WasmSequencer ::dispatchEvent() {
354 U8 stringStorage[FW_LOG_STRING_MAX_SIZE + 1];
355 ✗ FW_ASSERT(this->m_pendingHostFunction.u.event.msgLen <= FW_LOG_STRING_MAX_SIZE,
356 static_cast<FwAssertArgType>(this->m_pendingHostFunction.u.event.msgLen), FW_LOG_STRING_MAX_SIZE);
357 ✗ const Fw::ExternalString msg(reinterpret_cast<char*>(stringStorage), FW_LOG_STRING_MAX_SIZE + 1);
358
359 ✗ if (this->readGuestMemory(Svc::WasmSequencer_HostFunction::EVENT, this->m_pendingHostFunction.u.event.msgPtr,
360 ✗ stringStorage, this->m_pendingHostFunction.u.event.msgLen) != Fw::Success::SUCCESS) {
361 ✗ this->interpreter_sendSignal_hostResponseFailure();
362 ✗ return;
363 }
364 ✗ stringStorage[this->m_pendingHostFunction.u.event.msgLen] = 0;
365
366 // Emit the event at the guest-requested severity. FATAL and
367 // COMMAND are forbidden for guest programs (FATAL would let
368 // untrusted code trigger the FatalHandler; COMMAND is reserved
369 // for the command dispatcher). A forbidden or out-of-range
370 // severity is reported via HostFunctionInvalidSeverity, carrying
371 // the raw id and the guest message, and the guest continues.
372 ✗ const I32 rawSeverity = static_cast<I32>(this->m_pendingHostFunction.u.event.rawSeverity);
373 ✗ switch (static_cast<Fw::LogSeverity::T>(rawSeverity)) {
374 ✗ case Fw::LogSeverity::WARNING_HI:
375 ✗ this->log_WARNING_HI_LogWarningHi(msg);
376 ✗ break;
377 ✗ case Fw::LogSeverity::WARNING_LO:
378 ✗ this->log_WARNING_LO_LogWarningLo(msg);
379 ✗ break;
380 ✗ case Fw::LogSeverity::ACTIVITY_HI:
381 ✗ this->log_ACTIVITY_HI_LogActivityHi(msg);
382 ✗ break;
383 ✗ case Fw::LogSeverity::ACTIVITY_LO:
384 ✗ this->log_ACTIVITY_LO_LogActivityLo(msg);
385 ✗ break;
386 ✗ case Fw::LogSeverity::DIAGNOSTIC:
387 ✗ this->log_DIAGNOSTIC_LogDiagnostic(msg);
388 ✗ break;
389 ✗ case Fw::LogSeverity::FATAL: // fallthrough to catch restricted severities
390 case Fw::LogSeverity::COMMAND:
391 default:
392 ✗ this->log_WARNING_HI_HostFunctionInvalidSeverity(rawSeverity, msg);
393 ✗ break;
394 }
395
396 ✗ this->interpreter_sendSignal_hostResume();
397 ✗ }
398
399 ✗ void WasmSequencer ::dispatchRelativeSleep() {
400 ✗ const U32 seconds = static_cast<U32>(this->m_pendingHostFunction.u.rsleep.us / 1000000);
401 ✗ const U32 useconds = static_cast<U32>(this->m_pendingHostFunction.u.rsleep.us % 1000000);
402
403 ✗ const Fw::Time now = this->getTime();
404 const U64 deadlineSeconds =
405 ✗ static_cast<U64>(now.getSeconds()) + seconds + (static_cast<U64>(now.getUSeconds()) + useconds) / 1000000u;
406
407 ✗ Fw::Time timer = now;
408 ✗ if (deadlineSeconds > static_cast<U64>(std::numeric_limits<U32>::max())) {
409 ✗ timer.set(std::numeric_limits<U32>::max(), 999999u);
410 } else {
411 ✗ timer.add(seconds, useconds);
412 }
413
414 ✗ this->m_pendingTimer = timer;
415 ✗ this->m_hasPendingTimer = true;
416 ✗ }
417
418 ✗ void WasmSequencer ::dispatchAbsoluteSleep() {
419 ✗ U32 seconds = static_cast<U32>(this->m_pendingHostFunction.u.asleep.us / 1000000);
420 ✗ U32 useconds = static_cast<U32>(this->m_pendingHostFunction.u.asleep.us % 1000000);
421
422 // Absolute is relative to epoch, we still need to get the time for base/context
423 ✗ Fw::Time timer = this->getTime();
424 ✗ timer.set(seconds, useconds);
425
426 ✗ this->m_pendingTimer = timer;
427 ✗ this->m_hasPendingTimer = true;
428 ✗ }
429
430 ✗ void WasmSequencer ::dispatchArgs() {
431 ✗ const FwSizeType argCapacity = static_cast<FwSizeType>(sizeof(this->m_args.get_buffer()));
432 ✗ if (this->m_args.get_size() > argCapacity) {
433 ✗ this->log_WARNING_HI_BufferTooLarge(WasmSequencer_HostFunction::ARGS, static_cast<U32>(this->m_args.get_size()),
434 static_cast<U32>(argCapacity));
435 ✗ this->interpreter_sendSignal_hostResponseFailure();
436 ✗ return;
437 }
438
439 ✗ if (this->m_args.get_size() > this->m_pendingHostFunction.u.args.len) {
440 // Too many param bytes and we are going to leak data into the guest memory
441 ✗ this->log_WARNING_HI_BufferTooSmall(WasmSequencer_HostFunction::ARGS, this->m_pendingHostFunction.u.args.len,
442 ✗ static_cast<U32>(this->m_args.get_size()));
443 ✗ this->interpreter_sendSignal_hostResponseFailure();
444 ✗ return;
445 }
446
447 // Write the arguments to linear memory
448 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::ARGS, this->m_pendingHostFunction.u.args.ptr,
449 ✗ this->m_args.get_buffer(), this->m_args.get_size()) != Fw::Success::SUCCESS) {
450 ✗ this->interpreter_sendSignal_hostResponseFailure();
451 ✗ return;
452 }
453
454 ✗ this->interpreter_sendSignal_hostResumeI32(static_cast<I32>(this->m_args.get_size()));
455 }
456
457 ✗ void WasmSequencer ::dispatchTime() {
458 ✗ auto time = this->getTime();
459
460 ✗ FW_ASSERT(this->m_pendingHostFunction.u.time.len == Fw::Time::SERIALIZED_SIZE,
461 static_cast<FwAssertArgType>(this->m_pendingHostFunction.u.time.len), Fw::Time::SERIALIZED_SIZE);
462 ✗ Fw::LinearBufferTemplate<Fw::Time::SERIALIZED_SIZE> timeBuf;
463
464 ✗ auto serStatus = time.serializeTo(timeBuf);
465 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
466
467 // Write the time
468 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::TIME, this->m_pendingHostFunction.u.time.ptr,
469 ✗ timeBuf.getBuffAddr(), this->m_pendingHostFunction.u.time.len) != Fw::Success::SUCCESS) {
470 ✗ this->interpreter_sendSignal_hostResponseFailure();
471 ✗ return;
472 }
473
474 ✗ this->interpreter_sendSignal_hostResume();
475 ✗ }
476
477 ✗ void WasmSequencer ::dispatchSerialOut() {
478 ✗ const FwIndexType portNum = static_cast<FwIndexType>(this->m_pendingHostFunction.u.serialOut.index);
479
480 // Copy the payload out of guest memory into our own buffer
481 ✗ if (this->readGuestMemory(Svc::WasmSequencer_HostFunction::SERIAL_OUT, this->m_pendingHostFunction.u.serialOut.ptr,
482 this->m_serialOutBuffer.getBuffAddr(),
483 ✗ this->m_pendingHostFunction.u.serialOut.len) != Fw::Success::SUCCESS) {
484 ✗ this->interpreter_sendSignal_hostResponseFailure();
485 ✗ return;
486 }
487
488 ✗ auto serStatus = this->m_serialOutBuffer.setBuffLen(this->m_pendingHostFunction.u.serialOut.len);
489 ✗ FW_ASSERT(serStatus == Fw::FW_SERIALIZE_OK, serStatus);
490
491 // Invoke the serial output port.
492 ✗ serStatus = this->serialOut_out(portNum, this->m_serialOutBuffer);
493 ✗ if (serStatus != Fw::FW_SERIALIZE_OK) {
494 ✗ this->log_WARNING_HI_SerialPortSendFailed(Svc::WasmSequencer_HostFunction::SERIAL_OUT,
495 static_cast<I32>(serStatus));
496 ✗ this->interpreter_sendSignal_hostResponseFailure();
497 ✗ return;
498 }
499
500 // Send worked, wake up the interpreter
501 ✗ this->interpreter_sendSignal_hostResume();
502 }
503
504 ✗ void WasmSequencer ::dispatchSerialRecv() {
505 ✗ const FwIndexType portNum = static_cast<FwIndexType>(this->m_pendingHostFunction.u.serialRecv.index);
506 ✗ FW_ASSERT(portNum < NUM_SERIALIN_INPUT_PORTS, portNum);
507 ✗ Os::ScopeLock scopeLock(this->m_serialInMutex);
508 ✗ auto& queue = this->m_serialInQueue[portNum];
509
510 // Check if there is an available message on the queue
511 ✗ if (queue.get_allocated_size() > 0) {
512 // There is data available signal to the state machine to pull this data and wake up without blocking
513 ✗ this->interpreter_sendSignal_serialInMessage(portNum);
514 } else {
515 // Check if we should block or not
516 ✗ switch (this->m_pendingHostFunction.u.serialRecv.blockingType) {
517 ✗ case Svc::BlockState::BLOCK:
518 // Do not immediately resume the interpreter, this will let the state machine asynchronously
519 // wait for a signal that we got a message (or timeout).
520 ✗ this->m_hostFunctionStart = this->getTime();
521 ✗ this->m_hasHostFunctionStart = true;
522 ✗ break;
523 ✗ case Svc::BlockState::NO_BLOCK:
524 // We are non-blocking and the queue is empty, report this back to the interpreter and wake back
525 // up
526 ✗ constexpr I32 FPRIME_SERIAL_RECV_QUEUE_STATUS_EMPTY = 1;
527 ✗ this->interpreter_sendSignal_hostResumeI32(FPRIME_SERIAL_RECV_QUEUE_STATUS_EMPTY);
528 ✗ break;
529 }
530 }
531 ✗ }
532
533 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_clearPendingHostFunction(
534 SmId smId,
535 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
536 ✗ this->m_pendingHostFunction.kind = WasmSequencer_HostFunction::NONE;
537 ✗ this->m_pendingHostFunction.caller = nullptr;
538 ✗ this->m_hasPendingTimer = false;
539 ✗ this->m_hasHostFunctionStart = false;
540 ✗ }
541
542 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_setContext(
543 SmId smId,
544 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
545 const Svc::WasmSequencer_RequestContext& value) {
546 ✗ FW_ASSERT(!this->m_hasExecutingContext);
547 ✗ this->m_hasExecutingContext = true;
548 ✗ this->m_executingContext = value;
549
550 // A fresh execution window is starting. Bump the sequence counter so any command
551 // dispatched from this program carries a distinct cmdUid
552 ✗ this->m_sequencesStarted++;
553 ✗ }
554
555 1 void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_clearContext(
556 SmId smId,
557 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
558 1 this->m_hasExecutingContext = false;
559 1 }
560
561 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_resume(
562 SmId smId,
563 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
564 ✗ FW_ASSERT(this->m_wasm != nullptr);
565 ✗ auto status = spacewasm_resume(this->m_wasm);
566 ✗ FW_ASSERT(status == SPACEWASM_OK, status);
567 ✗ }
568
569 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_resumeI32(
570 SmId smId,
571 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
572 I32 value) {
573 ✗ FW_ASSERT(this->m_wasm != nullptr);
574 spacewasm_value_t return_val;
575 ✗ return_val.tag = SPACEWASM_I32;
576 ✗ return_val.u.i32_ = value;
577
578 ✗ auto status = spacewasm_resume_value(this->m_wasm, return_val);
579 ✗ FW_ASSERT(status == SPACEWASM_OK, status);
580 ✗ }
581
582 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_checkSleepTimers(
583 SmId smId,
584 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
585 // Check if we have overrun the timer
586 ✗ FW_ASSERT(this->m_hasPendingTimer);
587
588 ✗ const Fw::Time now = this->getTime();
589 ✗ switch (Fw::Time::compare(now, this->m_pendingTimer)) {
590 ✗ case Fw::TimeComparison::LT:
591 // No timer overrun
592 ✗ break;
593 ✗ case Fw::TimeComparison::EQ:
594 case Fw::TimeComparison::GT: {
595 // Timeout!
596 ✗ this->interpreter_sendSignal_hostResume();
597 ✗ break;
598 }
599 ✗ case Fw::TimeComparison::INCOMPARABLE:
600 // Time base / context changed since we set the timer
601 ✗ this->interpreter_sendSignal_hostResponseTimeIncomparable();
602 ✗ break;
603 }
604 ✗ }
605
606 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_checkTimeout(
607 SmId smId,
608 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) {
609 // Only blocking host functions that await an external event are subject to the
610 // host-function timeout (COMMAND -> cmdResponseIn, blocking SERIAL_RECV -> serialIn).
611 // Sleeps have their own wake timer (checkSleepTimers), separate from this timeout.
612 ✗ if (!this->m_hasHostFunctionStart) {
613 ✗ return;
614 }
615
616 ✗ Fw::ParamValid prmValid;
617 ✗ const F32 timeoutSecs = this->paramGet_HOST_FUNCTION_TIMEOUT_SECS(prmValid);
618
619 // A non-positive or out-of-range timeout disables the check entirely.
620 ✗ if (timeoutSecs <= 0.0f || timeoutSecs >= static_cast<F32>(std::numeric_limits<U32>::max())) {
621 ✗ return;
622 }
623
624 // Deadline = function start + timeout. Round microseconds up so the timeout
625 // is never reported early.
626 ✗ U32 seconds = static_cast<U32>(timeoutSecs);
627 ✗ U32 useconds = static_cast<U32>((timeoutSecs - static_cast<F32>(seconds)) * 1000000.0f + 0.5f);
628
629 // Rounding up can push the microsecond field to 1000000; carry it into
630 // seconds so Fw::Time::add() always receives a normalized (< 1e6) value.
631 ✗ if (useconds >= 1000000u) {
632 ✗ seconds += useconds / 1000000u;
633 ✗ useconds %= 1000000u;
634 }
635
636 ✗ Fw::Time deadline = this->m_hostFunctionStart;
637 ✗ deadline.add(seconds, useconds);
638
639 ✗ const Fw::Time now = this->getTime();
640 ✗ switch (Fw::Time::compare(now, deadline)) {
641 ✗ case Fw::TimeComparison::LT:
642 // Deadline not yet reached.
643 ✗ break;
644 ✗ case Fw::TimeComparison::EQ:
645 case Fw::TimeComparison::GT:
646 // Host function timed out waiting for its reply.
647 ✗ this->interpreter_sendSignal_hostResponseTimeout();
648 ✗ break;
649 ✗ case Fw::TimeComparison::INCOMPARABLE:
650 // Time base / context changed since the host function started.
651 ✗ this->interpreter_sendSignal_hostResponseTimeIncomparable();
652 ✗ break;
653 }
654 ✗ }
655
656 ✗ void WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_action_dequeueSerialAndResume(
657 SmId smId,
658 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
659 const FwIndexType& value) {
660 ✗ const FwIndexType portNum = static_cast<FwIndexType>(this->m_pendingHostFunction.u.serialRecv.index);
661 ✗ FW_ASSERT(portNum < NUM_SERIALIN_INPUT_PORTS, portNum);
662 ✗ Os::ScopeLock scopeLock(this->m_serialInMutex);
663 ✗ auto& queue = this->m_serialInQueue[portNum];
664
665 ✗ this->m_dequeueSucceeded = false;
666
667 // Message is available, pull out the size
668 U32 msgSize;
669 ✗ auto status = queue.peek(msgSize);
670 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
671
672 ✗ if (msgSize > this->m_pendingHostFunction.u.serialRecv.dataSize) {
673 ✗ this->log_WARNING_HI_BufferTooSmall(Svc::WasmSequencer_HostFunction::SERIAL_RECV,
674 this->m_pendingHostFunction.u.serialRecv.dataSize, msgSize);
675 // The frame doesn't fit in the guest's buffer and never will; drop it from the queue
676 // so it doesn't poison every subsequent serial_recv call on this port.
677 ✗ status = queue.rotate(static_cast<U32>(sizeof(U32)) + msgSize);
678 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
679 ✗ return;
680 }
681
682 // The queue holds [U32 size][payload]; skip the size prefix when copying the payload.
683 ✗ const U32 queuePayloadStart = sizeof(U32);
684 ✗ const U32 dataSize = static_cast<U32>(sizeof(U32)) + msgSize;
685
686 // Write the message size in little endian to the guest memory
687 ✗ Fw::LinearBufferTemplate<sizeof(U32)> msgSizeSer;
688 ✗ status = msgSizeSer.serializeFrom(msgSize, Fw::Endianness::LITTLE);
689 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
690
691 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::SERIAL_RECV,
692 ✗ this->m_pendingHostFunction.u.serialRecv.actualSizePtr, msgSizeSer.getBuffAddr(),
693 ✗ sizeof(U32)) != Fw::Success::SUCCESS) {
694 ✗ return;
695 }
696
697 // Pull the message off the queue, we may need to do it in multiple chunks
698 ✗ constexpr U32 CHUNK_SIZE = 32;
699 U8 scratch[CHUNK_SIZE];
700
701 // Pull all the full chunks off the queue
702 ✗ U32 queueOffset = queuePayloadStart;
703 ✗ for (; (queueOffset + CHUNK_SIZE) <= dataSize; queueOffset += CHUNK_SIZE) {
704 ✗ status = queue.peek(scratch, CHUNK_SIZE, queueOffset);
705 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
706
707 // Copy the data into the guest memory
708 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::SERIAL_RECV,
709 ✗ this->m_pendingHostFunction.u.serialRecv.dataPtr + (queueOffset - queuePayloadStart),
710 ✗ scratch, CHUNK_SIZE) != Fw::Success::SUCCESS) {
711 ✗ return;
712 }
713 }
714
715 // Pull out the final partial chunk
716 ✗ if (queueOffset < dataSize) {
717 ✗ const U32 remaining = dataSize - queueOffset;
718 ✗ FW_ASSERT(remaining < CHUNK_SIZE, static_cast<FwAssertArgType>(dataSize),
719 static_cast<FwAssertArgType>(queueOffset), CHUNK_SIZE);
720
721 ✗ status = queue.peek(scratch, remaining, queueOffset);
722 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
723
724 // Copy the data into the guest memory
725 ✗ if (this->writeGuestMemory(Svc::WasmSequencer_HostFunction::SERIAL_RECV,
726 ✗ this->m_pendingHostFunction.u.serialRecv.dataPtr + (queueOffset - queuePayloadStart),
727 ✗ scratch, remaining) != Fw::Success::SUCCESS) {
728 ✗ return;
729 }
730 }
731
732 // Dequeue the message now that we fully copied it into guest memory
733 ✗ status = queue.rotate(dataSize);
734 ✗ FW_ASSERT(status == Fw::FW_SERIALIZE_OK);
735
736 ✗ this->m_dequeueSucceeded = true;
737
738 // Yay, we successfully dequeued a message from the queue, wake up the interpreter to tell it about our success
739 ✗ constexpr I32 FPRIME_SERIAL_RECV_QUEUE_STATUS_OK = 0;
740 spacewasm_value_t return_val;
741 ✗ return_val.tag = SPACEWASM_I32;
742 ✗ return_val.u.i32_ = FPRIME_SERIAL_RECV_QUEUE_STATUS_OK;
743
744 ✗ auto resumeStatus = spacewasm_resume_value(this->m_wasm, return_val);
745 ✗ FW_ASSERT(resumeStatus == SPACEWASM_OK, resumeStatus);
746 ✗ }
747
748 // ----------------------------------------------------------------------
749 // Implementations for internal state machine guards
750 // ----------------------------------------------------------------------
751
752 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_pendingPause(
753 SmId smId,
754 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) const {
755 ✗ return this->m_pendingPause;
756 }
757
758 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_pendingHostFunction(
759 SmId smId,
760 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) const {
761 ✗ return this->m_pendingHostFunction.isPending();
762 }
763
764 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_pendingHostFunctionIsSleep(
765 SmId smId,
766 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) const {
767 ✗ return this->m_pendingHostFunction.kind == WasmSequencer_HostFunction::ASLEEP ||
768 ✗ this->m_pendingHostFunction.kind == WasmSequencer_HostFunction::RSLEEP;
769 }
770
771 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_blockingSerialIn(
772 SmId smId,
773 Svc_WasmSequencer_InterpreterStateMachine::Signal signal,
774 const FwIndexType& value) const {
775 ✗ return (this->m_pendingHostFunction.kind == Svc::WasmSequencer_HostFunction::SERIAL_RECV &&
776 ✗ this->m_pendingHostFunction.u.serialRecv.index == static_cast<U32>(value));
777 }
778
779 ✗ bool WasmSequencer ::Svc_WasmSequencer_InterpreterStateMachine_guard_dequeueSucceeded(
780 SmId smId,
781 Svc_WasmSequencer_InterpreterStateMachine::Signal signal) const {
782 ✗ return this->m_dequeueSucceeded;
783 }
784
785 } // namespace Svc
786