GCC Code Coverage Report


Directory: lib/fprime-samd/fprime-samd/
File: Svc/Seq/Seq.cpp
Date: 2026-10-20 16:48:53
Exec Total Coverage
Lines: 144 149 96.6%
Functions: 15 16 93.8%
Branches: 95 143 66.4%

Line Branch Exec Source
1 // ======================================================================
2 // \title Seq.cpp
3 // \author tumbar
4 // \brief cpp file for Seq component implementation class
5 // ======================================================================
6
7 #include "fprime-samd/Svc/Seq/Seq.hpp"
8 #include "Fw/Cmd/CmdResponseEnumAc.hpp"
9 #include "Fw/Types/Assert.hpp"
10 #include "Fw/Types/Serializable.hpp"
11 #include "SeqConfig/SeqNamesEnumAc.hpp"
12
13 namespace Samd21 {
14
15 // ----------------------------------------------------------------------
16 // Component construction and destruction
17 // ----------------------------------------------------------------------
18
19 27 Seq::Seq(const char* const compName)
20 : SeqComponentBase(compName),
21 27 m_sequences(nullptr),
22 27 m_offset(),
23 27 m_tableIdx(),
24 27 m_state(),
25 27 m_pendingTableIdx(SeqNames::NUM_CONSTANTS),
26 27 m_runOnErrorTableIdx(SeqNames::NUM_CONSTANTS),
27 27 m_sleepKind(SleepKind::NONE),
28 27 m_waitTicks(0),
29
1/2
✓ Branch 5 taken 27 times.
✗ Branch 6 not taken.
27 m_waitTimeBase(),
30 27 m_waitSeconds(0),
31 54 m_waitUseconds(0) {}
32
33 54 Seq::~Seq() {}
34
35 27 void Seq::configure(const Sequence* sequences) {
36
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 FW_ASSERT(sequences != nullptr);
37
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 27 times.
27 FW_ASSERT(this->m_sequences == nullptr);
38
39 27 this->m_sequences = sequences;
40
1/2
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 this->m_tableIdx = Samd21::SeqNames();
41 27 this->m_offset = 0;
42 27 this->m_state = State::IDLE;
43 27 this->m_pendingTableIdx = SeqNames::NUM_CONSTANTS;
44 27 this->m_runOnErrorTableIdx = SeqNames::NUM_CONSTANTS;
45 27 this->m_sleepKind = SleepKind::NONE;
46 27 }
47
48 ✗ void Seq::run(const Samd21::SeqNames& name) {
49 ✗ this->m_pendingTableIdx = name;
50 ✗ }
51
52 // ----------------------------------------------------------------------
53 // Handler implementations for typed input ports
54 // ----------------------------------------------------------------------
55
56 50 bool Seq::activeIn_handler(FwIndexType portNum, U32 context) {
57
2/2
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 43 times.
50 if (this->m_state == State::PENDING_NEXT) {
58 7 this->next();
59
60 // Work was done, we may need to re-cycle
61 7 return true;
62
6/6
✓ Branch 4 taken 42 times.
✓ Branch 5 taken 1 times.
✓ Branch 10 taken 28 times.
✓ Branch 11 taken 14 times.
✓ Branch 12 taken 28 times.
✓ Branch 13 taken 15 times.
43 } else if (this->m_state == State::IDLE && this->m_pendingTableIdx != SeqNames::NUM_CONSTANTS) {
63 // There is a pending run and we are ready to dispatch it
64
2/4
✓ Branch 10 taken 28 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 28 times.
✗ Branch 15 not taken.
28 this->log_ACTIVITY_LO_StartingSequence(Samd21::SeqNames(this->m_pendingTableIdx));
65
66 // Set up the sequence
67
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 28 times.
28 FW_ASSERT(this->m_pendingTableIdx < SeqNames::NUM_CONSTANTS, this->m_pendingTableIdx);
68 28 this->m_tableIdx = this->m_pendingTableIdx;
69 28 this->m_pendingTableIdx = SeqNames::NUM_CONSTANTS;
70 28 this->m_offset = 0;
71 28 this->next();
72
73 // Work was done, we may need to re-cycle
74 28 return true;
75 } else {
76 15 return false;
77 }
78 }
79
80 27 void Seq::commandResponseIn_handler(FwIndexType portNum,
81 FwOpcodeType opCode,
82 U32 cmdSeq,
83 const Fw::CmdResponse& response) {
84
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 25 times.
27 if (this->m_state != State::AWAITING_RESPONSE) {
85 2 this->log_WARNING_HI_DroppingStrayResponse(opCode, cmdSeq, response);
86 2 return;
87 }
88
89
2/2
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 20 times.
25 if (response != Fw::CmdResponse::OK) {
90 // A command in the action failed: abort the whole action.
91 5 this->log_WARNING_LO_CommandFailed(opCode, cmdSeq, response);
92 5 this->m_state = State::IDLE;
93 5 this->m_sleepKind = SleepKind::NONE;
94
95 // If a recovery table was armed, pend it so the next idle activeIn tick
96 // picks it up. It is one-shot: clear it so a failing recovery sequence
97 // does not re-trigger itself forever.
98
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 3 times.
5 if (this->m_runOnErrorTableIdx != SeqNames::NUM_CONSTANTS) {
99 2 this->m_pendingTableIdx = this->m_runOnErrorTableIdx;
100 2 this->m_runOnErrorTableIdx = SeqNames::NUM_CONSTANTS;
101 }
102
2/2
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 12 times.
20 } else if (this->m_sleepKind != SleepKind::NONE) {
103 // The command that just completed was a WAIT_* directive dispatched back
104 // to this component. Enter the sleep it requested; schedIn services it.
105 8 this->m_state = State::SLEEPING;
106
2/2
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 8 times.
12 } else if (!this->hasMoreRecordsInternal()) {
107 // Last command completed successfully: the action is done.
108
2/4
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 4 times.
✗ Branch 15 not taken.
4 this->log_ACTIVITY_LO_FinishedSequence(this->m_tableIdx);
109 4 this->m_state = State::IDLE;
110 } else {
111 // Wait for activeIn to pick up the next command
112 8 this->m_state = State::PENDING_NEXT;
113 }
114 }
115
116 18 void Seq::schedIn_handler(FwIndexType portNum, U32 context) {
117
2/2
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 13 times.
18 if (this->m_state != State::SLEEPING) {
118 5 return;
119 }
120
121
2/3
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
13 switch (this->m_sleepKind) {
122 5 case SleepKind::TICKS:
123 // WAIT_TICKS(n) blocks for n ticks. n == 0 resumes on this, the next
124 // tick after the command; each remaining tick decrements the counter.
125
2/2
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 2 times.
5 if (this->m_waitTicks == 0) {
126 3 this->m_sleepKind = SleepKind::NONE;
127 3 this->next();
128 } else {
129 2 this->m_waitTicks--;
130 }
131 5 break;
132
133 8 case SleepKind::UNTIL: {
134 // WAIT_UNTIL wakes once the FSW clock reaches the requested base and
135 // time. A differing base never matches, so the sleep persists.
136
1/2
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
8 const Fw::Time now = this->getTime();
137
1/2
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
8 const bool baseMatches = now.getTimeBase() == this->m_waitTimeBase;
138 const bool timeReached =
139
3/4
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 8 taken 5 times.
✓ Branch 9 taken 3 times.
13 (now.getSeconds() > this->m_waitSeconds) ||
140
6/8
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 2 times.
✓ Branch 12 taken 3 times.
✗ Branch 13 not taken.
✓ Branch 18 taken 2 times.
✓ Branch 19 taken 1 times.
5 (now.getSeconds() == this->m_waitSeconds && now.getUSeconds() >= this->m_waitUseconds);
141
4/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
8 if (baseMatches && timeReached) {
142 3 this->m_sleepKind = SleepKind::NONE;
143
1/2
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 this->next();
144 }
145 8 break;
146 8 }
147
148 ✗ case SleepKind::NONE:
149 default:
150 // SLEEPING with no wait kind is unreachable: the state is only
151 // entered after a WAIT_* command sets the kind.
152 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(this->m_sleepKind));
153 break;
154 }
155 }
156
157 // ----------------------------------------------------------------------
158 // Handler implementations for commands
159 // ----------------------------------------------------------------------
160
161 28 void Seq::RUN_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, const Samd21::SeqNames& table) {
162
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 28 times.
28 FW_ASSERT(this->m_sequences != nullptr);
163
164 28 this->m_pendingTableIdx = table;
165
2/4
✓ Branch 6 taken 28 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 28 times.
✗ Branch 10 not taken.
28 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
166 28 }
167
168 5 void Seq::RUN_ON_ERROR_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, const Samd21::SeqNames& table) {
169
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
5 FW_ASSERT(this->m_sequences != nullptr);
170
171 // Arm the recovery table and clear any pending normal RUN.
172 5 this->m_runOnErrorTableIdx = table;
173 5 this->m_pendingTableIdx = SeqNames::NUM_CONSTANTS;
174
2/4
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 5 times.
✗ Branch 10 not taken.
5 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
175 5 }
176
177 6 void Seq::CANCEL_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
178 // Stop whatever is executing or pending and reset the program counter to the
179 // start of the sequence. Nothing is dispatched. If a command was already in
180 // flight (AWAITING_RESPONSE), its response will arrive after we return to
181 // IDLE and be dropped as a stray response.
182
183 // Note where we were before resetting so the event reflects the cancelled
184 // point in the sequence.
185 6 this->log_ACTIVITY_HI_Cancelled(this->m_tableIdx, this->m_offset);
186
187 6 this->m_state = State::IDLE;
188 6 this->m_offset = 0;
189 6 this->m_pendingTableIdx = SeqNames::NUM_CONSTANTS;
190 6 this->m_runOnErrorTableIdx = SeqNames::NUM_CONSTANTS;
191 6 this->m_sleepKind = SleepKind::NONE;
192
2/4
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
6 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
193 6 }
194
195 4 void Seq::WAIT_TICKS_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, U32 n) {
196 // Record the wait and acknowledge. This handler runs re-entrantly while the
197 // dispatched WAIT_TICKS command is AWAITING_RESPONSE; the OK response below
198 // drives commandResponseIn, which observes m_sleepKind and enters SLEEPING.
199 4 this->m_sleepKind = SleepKind::TICKS;
200 4 this->m_waitTicks = n;
201
2/4
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 4 times.
✗ Branch 10 not taken.
4 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
202 4 }
203
204 4 void Seq::WAIT_UNTIL_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, const TimeBase& timeBase, U32 seconds, U32 useconds) {
205 // Record the absolute wakeup and acknowledge. See WAIT_TICKS_cmdHandler for
206 // the re-entrant handoff to commandResponseIn / SLEEPING.
207 4 this->m_sleepKind = SleepKind::UNTIL;
208 4 this->m_waitTimeBase = timeBase;
209 4 this->m_waitSeconds = seconds;
210 4 this->m_waitUseconds = useconds;
211
2/4
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 4 times.
✗ Branch 10 not taken.
4 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
212 4 }
213
214 // ----------------------------------------------------------------------
215 // Helper functions
216 // ----------------------------------------------------------------------
217
218 53 bool Seq::hasMoreRecordsInternal() const {
219 53 return this->m_offset < this->m_sequences[this->m_tableIdx].m_len;
220 }
221
222 39 U16 Seq::deserializeRecord(Fw::ExternalSerializeBuffer& cmd) {
223 78 Fw::ExternalSerializeBuffer buffer(const_cast<U8*>(this->m_sequences[this->m_tableIdx].m_data),
224
1/2
✓ Branch 13 taken 39 times.
✗ Branch 14 not taken.
78 this->m_sequences[this->m_tableIdx].m_len);
225
226
1/2
✓ Branch 13 taken 39 times.
✗ Branch 14 not taken.
39 buffer.moveSerToOffset(this->m_sequences[this->m_tableIdx].m_len);
227
1/2
✓ Branch 6 taken 39 times.
✗ Branch 7 not taken.
39 buffer.moveDeserToOffset(this->m_offset);
228
229 // Command size: the number of bytes spanning the opcode plus its arguments.
230 39 U32 cmdSize = 0;
231
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 Fw::SerializeStatus status = buffer.deserializeTo(cmdSize);
232
9/10
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 37 times.
✓ Branch 3 taken 1 times.
✓ Branch 6 taken 37 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 36 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 36 times.
39 if (status != Fw::FW_SERIALIZE_OK || cmdSize < sizeof(FwOpcodeType) || cmdSize > buffer.getDeserializeSizeLeft()) {
233
1/2
✓ Branch 9 taken 3 times.
✗ Branch 10 not taken.
3 this->log_WARNING_HI_InvalidSequence(cmdSize, this->m_offset);
234 3 return 0;
235 }
236
237
2/4
✓ Branch 5 taken 36 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 36 times.
✗ Branch 9 not taken.
36 cmd.setExtBuffer(const_cast<U8*>(buffer.getBuffAddrLeft()), cmdSize);
238
1/2
✓ Branch 10 taken 36 times.
✗ Branch 11 not taken.
36 cmd.moveSerToOffset(cmdSize);
239
240 // Advance the parent buffer past the command payload so the consumed-byte
241 // count below spans the whole record (header + command), not just the header.
242
1/2
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 status = buffer.deserializeSkip(cmdSize);
243
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
244
245 // Bytes consumed by this record (delta from the record start offset).
246
1/2
✓ Branch 13 taken 36 times.
✗ Branch 14 not taken.
36 return static_cast<U16>((this->m_sequences[this->m_tableIdx].m_len - buffer.getDeserializeSizeLeft()) -
247 36 this->m_offset);
248 39 }
249
250 36 void Seq::dispatchRecord(const Fw::ExternalSerializeBuffer& cmdBuf, U32 cmdSeq) {
251 // Copy the args into a local "owned" spot
252
2/4
✓ Branch 12 taken 36 times.
✗ Branch 13 not taken.
✓ Branch 22 taken 36 times.
✗ Branch 23 not taken.
36 Fw::ComBuffer cmd(cmdBuf.getBuffAddr(), cmdBuf.getSize());
253
1/2
✓ Branch 5 taken 36 times.
✗ Branch 6 not taken.
36 this->commandOut_out(0, cmd, cmdSeq);
254 72 }
255
256 41 void Seq::next() {
257 // The program counter can already be at the end of the buffer when the last
258 // command in a sequence is a WAIT_* directive: the wait advanced the offset
259 // past the final command, and schedIn calls next() once the wait expires.
260 // Finish cleanly rather than deserializing a zero-length record off the end.
261
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 39 times.
41 if (!this->hasMoreRecordsInternal()) {
262
2/4
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 2 times.
✗ Branch 15 not taken.
2 this->log_ACTIVITY_LO_FinishedSequence(this->m_tableIdx);
263 2 this->m_state = State::IDLE;
264 2 return;
265 }
266
267
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 Fw::ExternalSerializeBuffer cmd;
268
1/2
✓ Branch 4 taken 39 times.
✗ Branch 5 not taken.
39 U16 bytesRead = this->deserializeRecord(cmd);
269
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 36 times.
39 if (bytesRead == 0) {
270 3 this->m_state = State::IDLE;
271 3 return;
272 }
273
274 // We are actually ready to dispatch the record, increment
275 // program counter and wait for reply
276 36 U32 cmdSeq = this->m_offset;
277 36 this->m_offset += bytesRead;
278 36 this->m_state = State::AWAITING_RESPONSE;
279
1/2
✓ Branch 4 taken 36 times.
✗ Branch 5 not taken.
36 this->dispatchRecord(cmd, cmdSeq);
280
2/2
✓ Branch 2 taken 36 times.
✓ Branch 3 taken 3 times.
39 }
281
282 } // namespace Samd21
283