| 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/1✓ Branch 5 taken 27 times.
|
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 | 27 | FW_ASSERT(sequences != nullptr); | |
| 37 | 27 | FW_ASSERT(this->m_sequences == nullptr); | |
| 38 | |||
| 39 | 27 | this->m_sequences = sequences; | |
| 40 |
1/1✓ Branch 2 taken 27 times.
|
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/2✓ Branch 10 taken 28 times.
✓ Branch 14 taken 28 times.
|
28 | this->log_ACTIVITY_LO_StartingSequence(Samd21::SeqNames(this->m_pendingTableIdx)); |
| 65 | |||
| 66 | // Set up the sequence | ||
| 67 | 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/2✓ Branch 10 taken 4 times.
✓ Branch 14 taken 4 times.
|
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/1✓ Branch 3 taken 8 times.
|
8 | const Fw::Time now = this->getTime(); |
| 137 |
1/1✓ Branch 8 taken 8 times.
|
8 | const bool baseMatches = now.getTimeBase() == this->m_waitTimeBase; |
| 138 | const bool timeReached = | ||
| 139 |
3/3✓ Branch 2 taken 8 times.
✓ Branch 8 taken 5 times.
✓ Branch 9 taken 3 times.
|
13 | (now.getSeconds() > this->m_waitSeconds) || |
| 140 |
6/6✓ Branch 2 taken 5 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 2 times.
✓ Branch 12 taken 3 times.
✓ 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/1✓ Branch 4 taken 3 times.
|
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 | 28 | FW_ASSERT(this->m_sequences != nullptr); | |
| 163 | |||
| 164 | 28 | this->m_pendingTableIdx = table; | |
| 165 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 9 taken 28 times.
|
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 | 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/2✓ Branch 6 taken 5 times.
✓ Branch 9 taken 5 times.
|
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/2✓ Branch 6 taken 6 times.
✓ Branch 9 taken 6 times.
|
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/2✓ Branch 6 taken 4 times.
✓ Branch 9 taken 4 times.
|
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/2✓ Branch 6 taken 4 times.
✓ Branch 9 taken 4 times.
|
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/1✓ Branch 13 taken 39 times.
|
78 | this->m_sequences[this->m_tableIdx].m_len); |
| 225 | |||
| 226 |
1/1✓ Branch 13 taken 39 times.
|
39 | buffer.moveSerToOffset(this->m_sequences[this->m_tableIdx].m_len); |
| 227 |
1/1✓ Branch 6 taken 39 times.
|
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/1✓ Branch 2 taken 39 times.
|
39 | Fw::SerializeStatus status = buffer.deserializeTo(cmdSize); |
| 232 |
9/9✓ 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 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/1✓ Branch 9 taken 3 times.
|
3 | this->log_WARNING_HI_InvalidSequence(cmdSize, this->m_offset); |
| 234 | 3 | return 0; | |
| 235 | } | ||
| 236 | |||
| 237 |
2/2✓ Branch 5 taken 36 times.
✓ Branch 8 taken 36 times.
|
36 | cmd.setExtBuffer(const_cast<U8*>(buffer.getBuffAddrLeft()), cmdSize); |
| 238 |
1/1✓ Branch 10 taken 36 times.
|
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/1✓ Branch 2 taken 36 times.
|
36 | status = buffer.deserializeSkip(cmdSize); |
| 243 | 36 | FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); | |
| 244 | |||
| 245 | // Bytes consumed by this record (delta from the record start offset). | ||
| 246 |
1/1✓ Branch 13 taken 36 times.
|
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/2✓ Branch 12 taken 36 times.
✓ Branch 22 taken 36 times.
|
36 | Fw::ComBuffer cmd(cmdBuf.getBuffAddr(), cmdBuf.getSize()); |
| 253 |
1/1✓ Branch 5 taken 36 times.
|
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/2✓ Branch 10 taken 2 times.
✓ Branch 14 taken 2 times.
|
2 | this->log_ACTIVITY_LO_FinishedSequence(this->m_tableIdx); |
| 263 | 2 | this->m_state = State::IDLE; | |
| 264 | 2 | return; | |
| 265 | } | ||
| 266 | |||
| 267 |
1/1✓ Branch 2 taken 39 times.
|
39 | Fw::ExternalSerializeBuffer cmd; |
| 268 |
1/1✓ Branch 4 taken 39 times.
|
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/1✓ Branch 4 taken 36 times.
|
36 | this->dispatchRecord(cmd, cmdSeq); |
| 280 | 39 | } | |
| 281 | |||
| 282 | } // namespace Samd21 | ||
| 283 |