Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
510 changes: 459 additions & 51 deletions .fusa-reqs-pending.json

Large diffs are not rendered by default.

291 changes: 290 additions & 1 deletion .fusa-reqs.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions include/rcp/mock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
// fusa:req REQ-MOCK-024
// fusa:req REQ-MOCK-025
// fusa:req REQ-MOCK-026
// fusa:req REQ-WDG-010
// fusa:req REQ-E2E-033
// fusa:req REQ-E2E-039
// fusa:req REQ-E2E-041
// fusa:req REQ-E2E-047
// fusa:req REQ-MOCK-027
// fusa:req REQ-MOCK-028
// fusa:req REQ-MOCK-029

// In-process RC Server simulator — a small, representative OPEN Alliance
// TC18 Remote Control Protocol Specification v0.5.1_RC server built
Expand Down
73 changes: 72 additions & 1 deletion include/rcp/powerstate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@
// fusa:req REQ-PWR-012
// fusa:req REQ-PWR-013
// fusa:req REQ-PWR-014
// fusa:req REQ-PWRMODE-001
// fusa:req REQ-PWRMODE-002
// fusa:req REQ-PWRMODE-003
// fusa:req REQ-PWRMODE-005
// fusa:req REQ-PWRMODE-006
// fusa:req REQ-PWRMODE-007
// fusa:req REQ-PWRMODE-008
// fusa:req REQ-PWRMODE-009
// fusa:req REQ-PWRMODE-010
// fusa:req REQ-PWRMODE-011
// fusa:req REQ-PWRMODE-013
// fusa:req REQ-PWRMODE-014
// fusa:req REQ-PWRMODE-015
// fusa:req REQ-PWRMODE-016
// fusa:req REQ-PWRMODE-018
// fusa:req REQ-PWRMODE-020
// fusa:req REQ-PWRMODE-024
// fusa:req REQ-PWRMODE-025

// Power management — the OPEN Alliance TC18 Remote Control Protocol
// Specification v0.5.1_RC's actual power-mode model (`Normal`/`StandBy`/
Expand Down Expand Up @@ -58,6 +76,7 @@
// disclaimer as every other endpoint/lifecycle header in this codebase.
#pragma once

#include <rcp/lifecycle.hpp>
#include <rcp/wakeup.hpp>

#include <cstdint>
Expand Down Expand Up @@ -119,6 +138,36 @@ constexpr StartKind start_kind_on_exit(PowerMode from) noexcept {
return from == PowerMode::StandBy ? StartKind::Hot : StartKind::Cold;
}

// cold_start_lifecycle_target reports the lifecycle::ServerState a cold
// start's own re-init sequence should target, given the caller's own
// already-recovered fact of what state was persisted (e.g. read back from
// NVM, or device defaults absent NVM) — REQ-PWRMODE-003/014 (TC18 §12.3,
// §12.4.1): "After a cold start the RC Server will be in its configured
// lifecycle state," recovered from NVM where present rather than always
// reset to HwUnconfigured. This header owns no NVM access and no
// default-configuration table of its own (the same "caller supplies
// already-classified inputs" convention PowerManager::Hooks establishes)
// -- recovered_state is the caller's own already-recovered fact, returned
// unchanged when it is one of lifecycle::ServerState's three valid values.
// Any other value (unrecognized or corrupt) is treated as "nothing
// recovered," never as an unvalidated advanced state, and maps to
// lifecycle::ServerState::HwUnconfigured, this function's own fail-safe
// default. This header never itself calls
// lifecycle::ServerLifecycle::advance() -- it only names the target state
// for a caller's own re-init sequence to drive toward, mirroring
// PowerManager's own "primitives, not a scheduler" scoping (see this
// file's header comment).
constexpr lifecycle::ServerState cold_start_lifecycle_target(lifecycle::ServerState recovered_state) noexcept {
switch (recovered_state) {
case lifecycle::ServerState::HwUnconfigured:
case lifecycle::ServerState::HwConfigured:
case lifecycle::ServerState::RcpConfigured:
return recovered_state;
default:
return lifecycle::ServerState::HwUnconfigured;
}
}

// ── Errors ────────────────────────────────────────────────────────────────────

enum class PowerErrc : int {
Expand All @@ -128,6 +177,9 @@ enum class PowerErrc : int {
response_ack_queue_not_empty = 4, // entry refusal: caller's response_ack_queues_empty hook reported false
not_asleep = 5, // wake-from-sleep handshake step requested while mode() != Sleep
handshake_repeat_limit_exceeded = 6, // WakeUp message repeated cfg.wakeup_repeat_limit times with no echo
network_not_available = 7, // REQ-PWRMODE-016: begin_wake_from_sleep() called with
// network_available=false -- a free, uncounted retry;
// wake_stage() is left at Idle, not HandshakeActive.
};

inline const std::error_category& power_category() noexcept {
Expand All @@ -147,6 +199,8 @@ inline const std::error_category& power_category() noexcept {
return "rcp/powerstate: wake-from-sleep handshake requested while not in Sleep";
case PowerErrc::handshake_repeat_limit_exceeded:
return "rcp/powerstate: WakeUp message repeat limit exceeded without an echo";
case PowerErrc::network_not_available:
return "rcp/powerstate: network interface not yet available -- a free, uncounted retry";
default:
return "rcp/powerstate: unknown error";
}
Expand Down Expand Up @@ -258,9 +312,26 @@ class PowerManager {
// (extraction §3.3): step 1, network-interface re-enablement, runs
// synchronously here via Hooks::reenable_network_interface. Requires
// mode() == Sleep.
std::error_code begin_wake_from_sleep() noexcept {
//
// REQ-PWRMODE-016 (TC18 §12.4.1): network availability is checked
// BEFORE any WakeUp message is sent — network_available is the
// caller's own already-classified fact (e.g. "BEACONs detected by the
// PHY"; this header reads no hardware itself, matching every other
// Hooks-driven check in this class). Defaults to true so every
// pre-existing caller (and every REQ-PWR-*-tagged test) that never
// passed this argument keeps its exact prior behavior. When false,
// this is a cheap, retriable "not yet": wake_stage() is left at Idle
// (not HandshakeActive), reenable_network_interface() is NOT called,
// and wake_attempts_ is untouched — these retries are NOT counted
// against cfg_.wakeup_repeat_limit, which governs only the
// WakeUp-message repetition inside note_wakeup_attempt_sent() once
// this step has actually advanced. A caller polls this again once the
// network comes up.
std::error_code begin_wake_from_sleep(bool network_available = true) noexcept {
if (mode_ != PowerMode::Sleep)
return make_error_code(PowerErrc::not_asleep);
if (!network_available)
return make_error_code(PowerErrc::network_not_available);
if (hooks_.reenable_network_interface) hooks_.reenable_network_interface();
wake_attempts_ = 0;
wake_stage_ = WakeStage::HandshakeActive;
Expand Down
1 change: 1 addition & 0 deletions include/rcp/wakeup.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
// fusa:req REQ-WAKEUP-034
// fusa:req REQ-WAKEUP-035
// fusa:req REQ-WAKEUP-036
// fusa:req REQ-PWRMODE-023

// WakeUp endpoint (ep_type 0x01) — TC18's dedicated power-management
// endpoint: the fixed-opcode SleepCMD request/response, the fixed-opcode
Expand Down
8 changes: 8 additions & 0 deletions tests/test_mock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
// fusa:test REQ-MOCK-024
// fusa:test REQ-MOCK-025
// fusa:test REQ-MOCK-026
// fusa:test REQ-WDG-010
// fusa:test REQ-E2E-033
// fusa:test REQ-E2E-039
// fusa:test REQ-E2E-041
// fusa:test REQ-E2E-047
// fusa:test REQ-MOCK-027
// fusa:test REQ-MOCK-028
// fusa:test REQ-MOCK-029

// Tests for rcp/mock.hpp — the in-process RC Server simulator (ROADMAP.md
// milestone 56, "Test & Simulation Harness Rebuild", v2.12.0). See
Expand Down
76 changes: 76 additions & 0 deletions tests/test_powerstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@
// fusa:test REQ-PWR-012
// fusa:test REQ-PWR-013
// fusa:test REQ-PWR-014
// fusa:test REQ-PWRMODE-001
// fusa:test REQ-PWRMODE-002
// fusa:test REQ-PWRMODE-003
// fusa:test REQ-PWRMODE-005
// fusa:test REQ-PWRMODE-006
// fusa:test REQ-PWRMODE-007
// fusa:test REQ-PWRMODE-008
// fusa:test REQ-PWRMODE-009
// fusa:test REQ-PWRMODE-010
// fusa:test REQ-PWRMODE-011
// fusa:test REQ-PWRMODE-013
// fusa:test REQ-PWRMODE-014
// fusa:test REQ-PWRMODE-015
// fusa:test REQ-PWRMODE-016
// fusa:test REQ-PWRMODE-018
// fusa:test REQ-PWRMODE-020
// fusa:test REQ-PWRMODE-024
// fusa:test REQ-PWRMODE-025

// Tests for rcp/powerstate.hpp — the TC18 power-mode model, entry-refusal
// conditions, and hot-start-from-Sleep handshake (ROADMAP.md milestone 53,
Expand Down Expand Up @@ -280,3 +298,61 @@ TEST_CASE("PowerErrc reports a non-empty message in its own category", "[powerst
REQUIRE(ec.category() == power_category());
REQUIRE_FALSE(ec.message().empty());
}

// ── cold_start_lifecycle_target (REQ-PWRMODE-003/014) ───────────────────────

TEST_CASE("cold_start_lifecycle_target returns a valid recovered_state unchanged",
"[powerstate][REQ-PWRMODE-003][REQ-PWRMODE-014]") {
REQUIRE(cold_start_lifecycle_target(rcp::lifecycle::ServerState::HwUnconfigured) ==
rcp::lifecycle::ServerState::HwUnconfigured);
REQUIRE(cold_start_lifecycle_target(rcp::lifecycle::ServerState::HwConfigured) ==
rcp::lifecycle::ServerState::HwConfigured);
REQUIRE(cold_start_lifecycle_target(rcp::lifecycle::ServerState::RcpConfigured) ==
rcp::lifecycle::ServerState::RcpConfigured);
}

TEST_CASE("cold_start_lifecycle_target falls back to HwUnconfigured for an unrecognized value",
"[powerstate][REQ-PWRMODE-003][REQ-PWRMODE-014]") {
auto bogus = static_cast<rcp::lifecycle::ServerState>(0xFF);
REQUIRE(cold_start_lifecycle_target(bogus) == rcp::lifecycle::ServerState::HwUnconfigured);
}

// ── begin_wake_from_sleep's network_available gate (REQ-PWRMODE-016) ────────

TEST_CASE("begin_wake_from_sleep defaults to network_available=true, preserving every "
"pre-existing caller's exact behavior",
"[powerstate][REQ-PWRMODE-016]") {
rcp::wakeup::WakeupEndpoint wep;
int reenable_calls = 0;
PowerManager::Hooks hooks;
hooks.reenable_network_interface = [&] { ++reenable_calls; };
PowerManager mgr(wep, hooks);

REQUIRE_FALSE(mgr.enter_sleep());
REQUIRE_FALSE(mgr.begin_wake_from_sleep()); // no explicit argument — defaults to true
REQUIRE(reenable_calls == 1);
REQUIRE(mgr.wake_stage() == WakeStage::HandshakeActive);
}

TEST_CASE("begin_wake_from_sleep(false) is a free, uncounted retry that leaves wake_stage() at "
"Idle and never touches the network-reenable hook",
"[powerstate][REQ-PWRMODE-016]") {
rcp::wakeup::WakeupEndpoint wep;
int reenable_calls = 0;
PowerManager::Hooks hooks;
hooks.reenable_network_interface = [&] { ++reenable_calls; };
PowerManager mgr(wep, hooks);

REQUIRE_FALSE(mgr.enter_sleep());

auto ec = mgr.begin_wake_from_sleep(/*network_available=*/false);
REQUIRE(ec == make_error_code(PowerErrc::network_not_available));
REQUIRE(mgr.wake_stage() == WakeStage::Idle); // still Idle, not HandshakeActive
REQUIRE(reenable_calls == 0); // network hook never fired
REQUIRE(mgr.wake_attempts() == 0); // not counted against the repeat limit

// A later retry once the network comes up succeeds normally.
REQUIRE_FALSE(mgr.begin_wake_from_sleep(/*network_available=*/true));
REQUIRE(reenable_calls == 1);
REQUIRE(mgr.wake_stage() == WakeStage::HandshakeActive);
}
1 change: 1 addition & 0 deletions tests/test_wakeup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
// fusa:test REQ-WAKEUP-034
// fusa:test REQ-WAKEUP-035
// fusa:test REQ-WAKEUP-036
// fusa:test REQ-PWRMODE-023

// Tests for rcp/wakeup.hpp — the WakeUp endpoint type, re-derived from
// c-RCP's tests/test_ep_wakeup.c (ROADMAP.md Phase 17, cpp-RCP issue #129,
Expand Down
Loading