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
39 changes: 39 additions & 0 deletions .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,24 @@
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-015",
"title": "PWM_IN dispatch applies Table 33 Row 2 evt[2:0] classification before touching the scripted measurement",
"text": "rcp::mock::Server::dispatch, for a request addressed to the PWM_IN endpoint while RcpConfigured, shall decode the request's evt[2:0] field via endpoint::evt_row2_kind_of and drive pwm::PwmInEndpoint::handle_request with it: a Plain (000b) request shall be answered as a ReadResponse carrying the 4-byte big-endian encoding (pwm::encode_pwm_payload) of the value set_pwm_in_response last scripted (or PwmErrc::no_signal / wire error code PwmInNoSignal if none has been scripted yet), a Reserved (001b-110b) evt shall be rejected with endpoint::EndpointErrc::reserved_evt_row2 (wire error code UNSUPPORTED_CMD) without touching the scripted measurement, and a ConfigWrite (111b) evt shall be rejected with pwm::PwmErrc::config_write_not_supported (wire error code UNSUPPORTED_CMD) rather than crashing or being treated as a plain read.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B",
"tc18": "§13.5 Table 33 (\"ADC, PWM_IN, I²C, LIN, CAN, UART, ISELED, MDIO: 000b to 110b reserved – request to be rejected with error code = UNSUPPORTED_CMD\" / \"111b: The byte_msg_payload is not presented to the interface but used to change the configuration of the endpoint (see 12.7.1)\"), TC18.txt L4085-4092; PWM_IN's own request-handling section explicitly defers to this same rule: §13.7.6.3 (\"The interpretation of the evt-bits is described in the section 'The usage of evt bits in requests'\"), TC18.txt L5168-5180",
"tc18_master_id": "TC18-13.5-001"
},
{
"id": "REQ-MOCK-016",
"title": "Operational PWM_IN requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C/ADC",
"text": "rcp::mock::Server::dispatch, for a request addressed to the PWM_IN endpoint while the server's lifecycle state is not RcpConfigured, shall return regmap::RegMapErrc::request_rejected and an ErrorResponse without touching the scripted measurement or otherwise touching the endpoint's live state, the same operational-request gating REQ-MOCK-006/REQ-MOCK-012/REQ-MOCK-014 already apply to GPIO/SPI/I2C/ADC.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-UDP-001",
"title": "Frame round-trips an NTSCF-framed ACF_ABB request",
Expand Down Expand Up @@ -2964,6 +2982,27 @@
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-PWM-008",
"title": "PwmInEndpoint::handle_request applies Table 33 Row 2 evt[2:0] classification before reading",
"text": "rcp::pwm::PwmInEndpoint::handle_request shall classify its evt_op argument via rcp::endpoint::evt_row2_kind_of (masking to the low 3 bits first) and, for EvtRow2Kind::Plain, shall delegate to handle_read() with out_value unchanged; for EvtRow2Kind::Reserved, shall return endpoint::EndpointErrc::reserved_evt_row2 without touching out_value or any measured state.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B",
"tc18": "§13.5 Table 33 (\"ADC, PWM_IN, I²C, LIN, CAN, UART, ISELED, MDIO: 000b to 110b reserved – request to be rejected with error code = UNSUPPORTED_CMD\"), TC18.txt L4085-4092; PWM_IN's own request-handling section explicitly defers to this same rule rather than redescribing it: §13.7.6.3 (\"The interpretation of the evt-bits is described in the section 'The usage of evt bits in requests'\"), TC18.txt L5168-5180; corroborating worked example resolving the evt=000b ambiguity in Table 33's own favor: §13.7.9.3 Figure 34 (\"RC Client sends a standard read request\", ADC endpoint, evt field all-zero), TC18.txt L5590-5607",
"tc18_master_id": "TC18-13.5-001",
"severity": "cybersecurity"
},
{
"id": "REQ-PWM-009",
"title": "PwmInEndpoint::handle_request reports config-write (evt[2:0]==111b) as not yet supported",
"text": "rcp::pwm::PwmInEndpoint::handle_request, for EvtRow2Kind::ConfigWrite (evt[2:0]==111b), shall return PwmErrc::config_write_not_supported without touching out_value or any measured state and without crashing, rather than silently accepting the request as an ordinary read or silently doing nothing; full §12.7.1 configuration-write handling is deliberately deferred to a follow-up milestone, the same scope decision I2C's I2cErrc::config_write_not_supported and ADC's AdcErrc::config_write_not_supported already made. PwmErrc::config_write_not_supported shall report a non-empty diagnostic message in its own error_category.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B",
"tc18": "§12.7.1 (\"This can be done by setting evt[2:0] = 111b in request as described above.\"; Figure 19 – \"Configuration request\"; \"The data of the byte_msg_payload from a write request is written into the section of the EP that is addressed by the byte_bus_id.\"), TC18.txt L2844-2865",
"tc18_master_id": "TC18-12.7.1-001"
},
{
"id": "REQ-SEQ-001",
"title": "message_timestamp-repurposing request_type decode/encode (mtv=0)",
Expand Down
114 changes: 90 additions & 24 deletions include/rcp/mock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// fusa:req REQ-MOCK-012
// fusa:req REQ-MOCK-013
// fusa:req REQ-MOCK-014
// fusa:req REQ-MOCK-015
// fusa:req REQ-MOCK-016

// In-process RC Server simulator — a small, representative OPEN Alliance
// TC18 Remote Control Protocol Specification v0.5.1_RC server built
Expand All @@ -37,11 +39,12 @@
// mock::Server holds a real rcp::lifecycle::ServerLifecycle (v2.1.0), a
// real rcp::regmap::RegisterMap plus rcp::regmap::Ep0 (v2.1.0, including
// EP0 whole-map-read and root-client write semantics), and one instance
// each of four fully-built endpoint types — rcp::gpio::GpioEndpoint and
// rcp::spi::SpiEndpoint (both v2.3.0), plus rcp::i2c::I2cEndpoint and
// rcp::adc::AdcEndpoint (v2.4.0, wired in by the Table 30/33 Row 2 evt[2:0]
// validation pilot and its ADC follow-up, in that order) — as its
// representative endpoint set. dispatch() below is the single
// each of five fully-built endpoint types — rcp::gpio::GpioEndpoint and
// rcp::spi::SpiEndpoint (both v2.3.0), plus rcp::i2c::I2cEndpoint,
// rcp::adc::AdcEndpoint, and rcp::pwm::PwmInEndpoint (v2.4.0, wired in by
// the Table 30/33 Row 2 evt[2:0] validation pilot and its ADC and PWM_IN
// follow-ups, in that order) — as its representative endpoint set.
// dispatch() below is the single
// request/response entry point a test drives, decoding the standard
// request kind's evt[2:0]/op fields (rcp/acf.hpp, v2.0.0) the same way a
// real request-dispatch loop would. Conditional request kinds (v2.5.0),
Expand All @@ -62,14 +65,14 @@
// in an internal structured extraction of the specification named above;
// no text from that document is reproduced here. The concrete endpoint
// numbering (GPIO at endpoint id / byte_bus_id 1, SPI at 2, I2C at 3, ADC at
// 4), access-policy choice for operational requests (gated on lifecycle
// state only, not per-endpoint ownership — see dispatch()'s own comment),
// and EP0 partial-read encoding chosen in this file are this
// 4, PWM_IN at 5), access-policy choice for operational requests (gated on
// lifecycle state only, not per-endpoint ownership — see dispatch()'s own
// comment), and EP0 partial-read encoding chosen in this file are this
// implementation's own, purely for the purposes of being a usable
// in-process simulator — full bit-for-bit conformance against other TC18
// implementations is not claimed, same as the equivalent disclaimers in
// rcp/regmap.hpp, rcp/lifecycle.hpp, rcp/gpio.hpp, rcp/spi.hpp, rcp/i2c.hpp,
// and rcp/adc.hpp.
// rcp/adc.hpp, and rcp/pwm.hpp.
#pragma once

#include <rcp/acf.hpp>
Expand All @@ -79,6 +82,7 @@
#include <rcp/gpio.hpp>
#include <rcp/i2c.hpp>
#include <rcp/lifecycle.hpp>
#include <rcp/pwm.hpp>
#include <rcp/regmap.hpp>
#include <rcp/spi.hpp>

Expand All @@ -103,14 +107,16 @@ namespace mock {

using regmap::EndpointId;

constexpr EndpointId kGpioEndpointId = 1;
constexpr EndpointId kSpiEndpointId = 2;
constexpr EndpointId kI2cEndpointId = 3;
constexpr EndpointId kAdcEndpointId = 4;
constexpr avtp::ByteBusId kGpioByteBusId = static_cast<avtp::ByteBusId>(kGpioEndpointId);
constexpr avtp::ByteBusId kSpiByteBusId = static_cast<avtp::ByteBusId>(kSpiEndpointId);
constexpr avtp::ByteBusId kI2cByteBusId = static_cast<avtp::ByteBusId>(kI2cEndpointId);
constexpr avtp::ByteBusId kAdcByteBusId = static_cast<avtp::ByteBusId>(kAdcEndpointId);
constexpr EndpointId kGpioEndpointId = 1;
constexpr EndpointId kSpiEndpointId = 2;
constexpr EndpointId kI2cEndpointId = 3;
constexpr EndpointId kAdcEndpointId = 4;
constexpr EndpointId kPwmInEndpointId = 5;
constexpr avtp::ByteBusId kGpioByteBusId = static_cast<avtp::ByteBusId>(kGpioEndpointId);
constexpr avtp::ByteBusId kSpiByteBusId = static_cast<avtp::ByteBusId>(kSpiEndpointId);
constexpr avtp::ByteBusId kI2cByteBusId = static_cast<avtp::ByteBusId>(kI2cEndpointId);
constexpr avtp::ByteBusId kAdcByteBusId = static_cast<avtp::ByteBusId>(kAdcEndpointId);
constexpr avtp::ByteBusId kPwmInByteBusId = static_cast<avtp::ByteBusId>(kPwmInEndpointId);

// A discovery-shaped EP0 read only ever answers with the register map's
// magic number below — see this header's own scope note above.
Expand Down Expand Up @@ -145,6 +151,10 @@ inline acf::WireErrorCode wire_error_code_for(const std::error_code& ec) noexcep
return acf::WireErrorCode::UnsupportedCmd; // evt[2:0]==111b config-write, not yet implemented by this mock
if (ec == make_error_code(adc::AdcErrc::no_signal))
return acf::WireErrorCode::EpError; // internal no-valid-sample condition, not a TC18-defined ADC error code (see adc.hpp's own comment) — mirrors i2c::I2cErrc::nack's mapping above
if (ec == make_error_code(pwm::PwmErrc::config_write_not_supported))
return acf::WireErrorCode::UnsupportedCmd; // evt[2:0]==111b config-write, not yet implemented by this mock
if (ec == make_error_code(pwm::PwmErrc::no_signal))
return acf::WireErrorCode::PwmInNoSignal; // Table 27's own dedicated PWM_IN_NO_SIGNAL(9) code — unlike adc::AdcErrc::no_signal above, TC18 defines a real numbered code for this condition, so this maps to it directly rather than falling back to EpError
return acf::WireErrorCode::UnsupportedCmd;
}

Expand Down Expand Up @@ -175,6 +185,7 @@ class Server final {
spi::SpiEndpoint& spi() noexcept { return spi_; }
i2c::I2cEndpoint& i2c() noexcept { return i2c_; }
adc::AdcEndpoint& adc() noexcept { return adc_; }
pwm::PwmInEndpoint& pwm_in() noexcept { return pwm_in_; }

// set_spi_poci scripts the bytes a subsequent dispatch()/transfer()
// call on `channel` reads back as POCI-in data. A real SPI peripheral's
Expand Down Expand Up @@ -212,6 +223,24 @@ class Server final {
adc_samples_.assign(samples.begin(), samples.end());
}

// set_pwm_in_response scripts the PwmValue (period, active_duration) a
// subsequent dispatch()-driven plain (evt[2:0]==000b) PWM_IN request
// answers with — the same "test scripts the sensor, this mock does not
// model actual hardware" pattern set_spi_poci/set_i2c_response/
// set_adc_response above already establish. Unlike set_adc_response's
// FIFO queue, this calls pwm::PwmInEndpoint::record_measurement
// directly: PWM_IN's own read model (rcp/pwm.hpp) reports the most
// recently recorded measurement persistently, not a one-shot consumed
// sample the way ADC's take_sample callback models, so there is
// nothing to queue — the scripted value stays in effect (and fires
// both Table 44 trigger signals, per record_measurement's own
// contract) until this is called again, or pwm_in().clear_signal() is
// used to model signal loss (a subsequent plain request then answers
// PwmErrc::no_signal / wire error code PWM_IN_NO_SIGNAL again).
void set_pwm_in_response(pwm::PwmValue value) {
pwm_in_.record_measurement(value);
}

// advance_to_rcp_configured is a convenience for tests/simulators that
// don't care about exercising ServerLifecycle's intermediate
// plausibility-check gating themselves and just want a fully live
Expand Down Expand Up @@ -249,6 +278,7 @@ class Server final {
if (req.byte_bus_id == kSpiByteBusId) return dispatch_spi(req, req_payload, out_resp, out_resp_payload);
if (req.byte_bus_id == kI2cByteBusId) return dispatch_i2c(req, req_payload, out_resp, out_resp_payload);
if (req.byte_bus_id == kAdcByteBusId) return dispatch_adc(req, req_payload, out_resp, out_resp_payload);
if (req.byte_bus_id == kPwmInByteBusId) return dispatch_pwm_in(req, req_payload, out_resp, out_resp_payload);

return set_error_response(req, make_error_code(regmap::RegMapErrc::invalid_parameter),
out_resp, out_resp_payload);
Expand All @@ -269,14 +299,15 @@ class Server final {

static regmap::RegisterMap make_initial_register_map() {
regmap::RegisterMap regs;
regs.endpoint_count = 4;
regs.generic_configs.resize(4);
regs.functional_configs.resize(4);
regs.endpoint_count = 5;
regs.generic_configs.resize(5);
regs.functional_configs.resize(5);
regs.ep_id_mapping = {
{kGpioEndpointId, kGpioByteBusId},
{kSpiEndpointId, kSpiByteBusId},
{kI2cEndpointId, kI2cByteBusId},
{kAdcEndpointId, kAdcByteBusId},
{kGpioEndpointId, kGpioByteBusId},
{kSpiEndpointId, kSpiByteBusId},
{kI2cEndpointId, kI2cByteBusId},
{kAdcEndpointId, kAdcByteBusId},
{kPwmInEndpointId, kPwmInByteBusId},
};
return regs;
}
Expand Down Expand Up @@ -412,13 +443,48 @@ class Server final {
return {};
}

// PWM_IN is a response-only read model (§13.7.6.3, rcp/pwm.hpp's own
// header comment) with no functional request payload at all — this
// dispatch path does not branch on req.op either, same rationale as
// dispatch_adc's own comment (no write semantics implemented for this
// endpoint type, not an inherently full-duplex protocol). Every
// request addressed here drives one PwmInEndpoint::handle_request,
// which checks Table 33 Row 2's 3-way Plain/Reserved/ConfigWrite
// classification (rcp::endpoint::evt_row2_kind_of) before ever
// touching the endpoint's last-measured state — so a Reserved or
// ConfigWrite evt can never read or disturb it. Unlike dispatch_adc,
// there is no scripted-sample queue to consume: PWM_IN's read model
// reports the most recently recorded measurement persistently (see
// set_pwm_in_response's own comment), so a Plain request simply
// answers whatever is currently recorded, or PwmErrc::no_signal
// (wire error code PwmInNoSignal) if nothing has been recorded yet or
// the signal was cleared. `payload` is unused: §13.7.6.3 states the
// PWM_IN request itself carries no functional byte_msg_payload.
std::error_code dispatch_pwm_in(const acf::AcfMessageInfo& req, const std::vector<uint8_t>& /*payload*/,
acf::AcfMessageInfo& out_resp,
std::vector<uint8_t>& out_resp_payload) noexcept {
if (!operational_requests_allowed()) {
return set_error_response(req, make_error_code(regmap::RegMapErrc::request_rejected),
out_resp, out_resp_payload);
}
pwm::PwmValue value{};
auto ec = pwm_in_.handle_request(req.evt_op, value);
if (ec) return set_error_response(req, ec, out_resp, out_resp_payload);
auto wire = pwm::encode_pwm_payload(value);
out_resp_payload.assign(wire.begin(), wire.end());
out_resp = acf::make_response(req, req.evt_ack ? acf::ResponseKind::Acknowledge
: acf::ResponseKind::ReadResponse);
return {};
}

lifecycle::ServerLifecycle lifecycle_;
regmap::RegisterMap regs_;
regmap::Ep0 ep0_;
gpio::GpioEndpoint gpio_;
spi::SpiEndpoint spi_;
i2c::I2cEndpoint i2c_;
adc::AdcEndpoint adc_;
pwm::PwmInEndpoint pwm_in_;
std::array<std::vector<uint8_t>, spi::kMaxChannels> spi_poci_{};
std::vector<uint8_t> i2c_response_{};
bool i2c_acked_ = true;
Expand Down
Loading
Loading