Distinguish poll-again from stop-polling in the initiator contract - #15
Distinguish poll-again from stop-polling in the initiator contract#15chrysh wants to merge 5 commits into
Conversation
…ator The polling caller could not implement a correct loop: a request in flight with T2 not yet elapsed surfaced as FdInitiatorModeError, the same error as calling in a non-initiator state, so waiting, done and misuse were indistinguishable. caliptra-mcu-sw grew ResponderAction and a cancellation flag for the responder side of the same problem; this puts the outcome in the return type instead. generate_initiator_request now returns InitiatorAction: - Request(n): transmit msg_buf[..n] (includes the MCTP type byte) - Waiting: request in flight or operation still running, poll again - Complete: the FD left initiator mode, stop polling The waiting case inside fd_progress becomes Ok(0) instead of an error, which also makes the T1 check reachable while waiting - before this, a silent UA could only be timed out on the T2 resend path. Errors are now real faults only, plus T1Timeout from the cancel path. Signed-off-by: Christina Quast <christina.quast@9elements.com>
The message-type byte is stamped before it is known whether a request is due, so Waiting still mutates the buffer and a too-short buffer errors on every poll. A caller reusing one TX/RX buffer and treating Waiting as buffer-untouched would read stale bytes as a frame. Assisted-by: Claude:claude-fable-5 Signed-off-by: Christina Quast <christina.quast@9elements.com>
|
I can't find usage of |
|
Referencing your question about the return of Complete, perhaps not. We may want the UA to do a GetStatus and retrieve a GetStatusReasonCode to examine the Timeout. |
Complete reads as success, but the variant also covers the T1-cancel path where the update failed. Idle states the FD fact and stays truthful in both cases; the reason stays on the GetStatus path. Signed-off-by: Christina Quast <christina.quast@9elements.com>
After a failed transfer/verify/apply the FD sets FdReqState::Failed and waits for the UA's CancelUpdate. The T1 cancel check only matched FdReqState::Sent, so a UA that went silent at that point left the caller polling Waiting forever. Extend the check to Failed, and skip the verify/apply pre-check T1 refresh in Failed for the same reason. The T1 baseline in Failed is the failure response itself: handle_response refreshes T1 before dispatching, so the UA gets a full T1 to issue the cancel. Signed-off-by: Christina Quast <christina.quast@9elements.com>
I would need to dig into PLDM in detail, but The PR never touches FdReqState::Failed directly — but the PR does change its observable behavior, indirectly. That's the elaboration he's asking for. The chain:
We agreed with the AI on this (see latest commit) The T1 cancel check in fd_progress now matches Sent | Failed instead of only Sent, and the verify/apply pre-check T1 refresh skips Failed (otherwise it would reset the clock every poll and the timeout could never elapse in Verify/Apply). |
Renamed to idle
Is that not done yet? |
The UA, not OpenProt, will do a GetStatus Request. The FD, OpenProt, will give the response which includes GetStatusReasonCode. GetStatusResponse is implemented in OpenProt FD. |
Thanks for the clarification. Can you explain in more detail what change you suggested with your original comment? |
Could a user of run_terminus, like the Orchestrator, see an unexpected Idle and report that so that a UA can do a GetStatus to see why? |
The T1 cancel is a protocol outcome, not a fault: the update is over and the FD is idle again, but it must keep serving responder commands so the UA can GetStatus the reason. Returning it as Err pushed callers the wrong way - ? on the polling loop would exit the loop and take the responder path down with it, leaving that GetStatus unanswered. fd_progress now returns FdProgress (Request/Waiting/Cancelled) instead of a bare length with the cancel smuggled through MsgHandlerError, and generate_initiator_request maps that to a new InitiatorAction::Cancelled. Errors are faults only; T1Timeout leaves MsgHandlerError. Signed-off-by: Christina Quast <christina.quast@9elements.com>
@CourtneyDrant A bit AI-ish, but hopefully helpful: Yes — and chasing this exposed a bug in the proposal, fixed in 7a339fd. The T1 cancel surfaced as With that, Idle can't happen unannounced: the FD only reaches it through a UA command (the UA knows why) or through T1, which announces itself as Phase 1 of let action = self
.cmd_interface
.generate_initiator_request(&mut fw_buf)
.map_err(PldmServiceError::MsgHandler)?;
let initiator_active = !matches!(action, InitiatorAction::Idle);
match action {
InitiatorAction::Request(pldm_len) => {
// unchanged: send via requester_transport, feed the response
// back through process_initiator_response
}
// Nothing due; fall through to the responder poll.
InitiatorAction::Waiting => {}
// T1 fired, update cancelled, FD is Idle again. Keep serving so
// the UA can GetStatus the reason. The platform already saw the
// cancel synchronously via FdOps::cancel_update_component.
InitiatorAction::Cancelled => {}
// Not in initiator mode; the responder poll below blocks with
// the caller's idle timeout.
InitiatorAction::Idle => {}
}Two things fall out:
If the Orchestrator wants more than the @CourtneyDrant If you think #14 and/or #15 are useful, can you merge them? If not, also feel free to close them! |
We shouldn't get rid of "should_start_initiator_mode. We are only in initiator mode in three states, Download, Verify, and Apply. |
Concrete proposal for the poll-contract discussion from the #12 review — code to argue about instead of an issue thread. Stacked on #14 (retarget/rebase as things merge); only the last commit is new here.
The problem: the polling caller can't implement a correct loop. A request in flight with T2 not yet elapsed — the most common polling state — surfaces as
Err(FdInitiatorModeError), the same error as calling in a non-initiator state. Waiting, done, and misuse are indistinguishable. caliptra-mcu-sw grewResponderAction::Continue/Completeplus a cancellation flag for the responder side of the same problem; their T1 path sets neither, so outcomes keep leaking into side-channels.The proposal: put the outcome in the return type.
generate_initiator_requestreturnsInitiatorAction:Request(n)— transmitmsg_buf[..n](1 MCTP type byte + encoded PLDM request)Waiting— request in flight or operation still running; poll againCancelled— the UA stayed silent for T1 and the update was cancelled (Modified generate_initiator_request #12); reported once, next poll returnsIdle. Report it and keep serving responder commands so the UA can still GetStatus the reason.Idle— the FD left initiator mode; stop polling. Carries no reason: GetStatusReasonCode is the channel for "why"Erris reserved for real faults (codec, transport, FdOps). The T1 cancel started out asErr(T1Timeout)here, but that pushed callers the wrong way —?in a polling loop would exit the loop and take the responder path down with it, leaving the UA's follow-up GetStatus unanswered — so it moved into the action type (review discussion below).Side effect worth its own look: turning the waiting case into a non-error inside
fd_progressmakes the T1 check reachable while waiting — previously the error short-circuited past it, so a silent UA could only be timed out on the T2 resend path. There's a test pinning that (test_fd_progress_t1_fires_while_waiting).Resolved during review:
FdReqState::Failednow arms T1 rather than mapping toWaitingforever: a silent UA that owes a CancelUpdate after a failed transfer/verify/apply gets timed out like any other silence.Completewas renamed toIdle— it also covers "update was cancelled and FD is idle again", so "complete" overpromised.Relates to #13.