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
53 changes: 51 additions & 2 deletions .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@
},
{
"id": "REQ-MOCK-001",
"title": "Server starts HW_UNCONFIGURED with a representative three-endpoint register map",
"text": "rcp::mock::Server shall construct with rcp::lifecycle::ServerLifecycle in state HwUnconfigured and an rcp::regmap::RegisterMap whose endpoint_count, generic_configs, functional_configs, and ep_id_mapping describe exactly three endpoints: GPIO at endpoint id/byte_bus_id 1, SPI at endpoint id/byte_bus_id 2, and I2C at endpoint id/byte_bus_id 3.",
"title": "Server starts HW_UNCONFIGURED with a representative four-endpoint register map",
"text": "rcp::mock::Server shall construct with rcp::lifecycle::ServerLifecycle in state HwUnconfigured and an rcp::regmap::RegisterMap whose endpoint_count, generic_configs, functional_configs, and ep_id_mapping describe exactly four endpoints: GPIO at endpoint id/byte_bus_id 1, SPI at endpoint id/byte_bus_id 2, I2C at endpoint id/byte_bus_id 3, and ADC at endpoint id/byte_bus_id 4.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
Expand Down Expand Up @@ -178,6 +178,24 @@
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-013",
"title": "ADC dispatch applies Table 33 Row 2 evt[2:0] classification before consuming a scripted sample",
"text": "rcp::mock::Server::dispatch, for a request addressed to the ADC endpoint while RcpConfigured, shall decode the request's evt[2:0] field via endpoint::evt_row2_kind_of and drive adc::AdcEndpoint::handle_request with it against a default-constructed adc::AdcAveragingConfig: a Plain (000b) request shall be answered as a ReadResponse carrying the 16-bit big-endian encoding (adc::encode_adc_value) of the next value set_adc_response last scripted, consumed in FIFO order without auto-refill (an exhausted scripted queue reports adc::AdcErrc::no_signal), a Reserved (001b-110b) evt shall be rejected with endpoint::EndpointErrc::reserved_evt_row2 (wire error code UNSUPPORTED_CMD) without consuming a scripted sample, and a ConfigWrite (111b) evt shall be rejected with adc::AdcErrc::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; 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"
},
{
"id": "REQ-MOCK-014",
"title": "Operational ADC requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI/I2C",
"text": "rcp::mock::Server::dispatch, for a request addressed to the ADC endpoint while the server's lifecycle state is not RcpConfigured, shall return regmap::RegMapErrc::request_rejected and an ErrorResponse without consuming a scripted sample or otherwise touching the endpoint's live state, the same operational-request gating REQ-MOCK-006/REQ-MOCK-012 already apply to GPIO/SPI/I2C.",
"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 @@ -2847,6 +2865,37 @@
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-ADC-007",
"title": "encode_adc_value encodes a single measurement as a 16-bit big-endian wire value",
"text": "rcp::adc::encode_adc_value shall encode its uint16_t argument as exactly rcp::adc::kAdcValueLen (2) bytes, big-endian, matching the \"16 bit ADC value\" field TC18's ADC response figures show.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B",
"tc18": "§13.7.9.3 (\"16 bit ADC value 0\"/\"16 bit ADC value 1\" fields, Figure 34/Figure 35 - ADC response with 8 measurements), TC18.txt L5590-5628",
"tc18_master_id": "TC18-13.7.9.3-006"
},
{
"id": "REQ-ADC-008",
"title": "AdcEndpoint::handle_request applies Table 33 Row 2 evt[2:0] classification before sampling",
"text": "rcp::adc::AdcEndpoint::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 request_reading() with cfg/take_sample/out_value unchanged; for EvtRow2Kind::Reserved, shall return endpoint::EndpointErrc::reserved_evt_row2 without invoking take_sample or otherwise touching out_value.",
"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; 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-ADC-009",
"title": "AdcEndpoint::handle_request reports config-write (evt[2:0]==111b) as not yet supported",
"text": "rcp::adc::AdcEndpoint::handle_request, for EvtRow2Kind::ConfigWrite (evt[2:0]==111b), shall return AdcErrc::config_write_not_supported without invoking take_sample or otherwise touching out_value 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 already made. AdcErrc::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-PWM-001",
"title": "PWM_OUT/PWM_IN shared period/active-duration payload shape",
Expand Down
109 changes: 105 additions & 4 deletions include/rcp/adc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
// fusa:req REQ-ADC-004
// fusa:req REQ-ADC-005
// fusa:req REQ-ADC-006
// fusa:req REQ-ADC-007
// fusa:req REQ-ADC-008
// fusa:req REQ-ADC-009

// ADC endpoint (ep_type 0x09) — the three-level averaging model
// (adc_sample_interval -> adc_avg_intervals_per_request ->
Expand Down Expand Up @@ -46,19 +49,46 @@
// cannot produce a valid sample) without pretending to be, or to map to,
// any numbered TC18 error code.
//
// Table 30/33 Row 2 evt[2:0] validation (post-v2.4.0, second endpoint type
// after I2C): AdcEndpoint::handle_request is this header's own wiring of
// rcp::endpoint::evt_row2_kind_of — the shared 3-way evt[2:0] classifier for
// Table 33's {ADC, PWM_IN, I2C, LIN, CAN, UART, ISELED, MDIO} row — into
// ADC's request decode, following the exact shape rcp/i2c.hpp's
// I2cEndpoint::handle_request established as the pilot. ADC is in fact the
// row's own worked example (§13.7.9.3 Figure 34, "RC Client sends a standard
// read request", evt field all-zero) that resolved the row's evt=000b
// textual ambiguity in the first place — see rcp/endpoint.hpp's own "RESOLVED
// AMBIGUITY" comment on evt_row2_kind_of, which cites this exact section.
// Plain (evt[2:0]==000b) delegates to request_reading — the SelfTimed
// cadence pattern below, ADC's on-demand "standard read request" behavior,
// matching Figure 34 — not to request_reading_from_trigger_queue, whose
// ExternalTrigger cadence remains a separate entry point this milestone does
// not fold into handle_request. Reserved (001b-110b) is rejected with
// endpoint::EndpointErrc::reserved_evt_row2. ConfigWrite (evt[2:0]==111b,
// §12.7.1) is reported as AdcErrc::config_write_not_supported rather than
// crashing or silently accepted as a plain read — ADC has no EP_functional-
// config wiring in this codebase yet (same gap I2C's own handle_request
// comment calls out for I2C), so full §12.7.1 handling is out of scope here
// too.
//
// Field names and behavior below implement TC18's *behavior* as described in
// an internal structured extraction of the specification named above; no
// text from that document is reproduced here. The concrete averaging
// combinator (arithmetic mean at both levels) and cadence-pattern API shape
// chosen in this file are this implementation's own, same as the equivalent
// disclaimers in rcp/avtp.hpp, rcp/regmap.hpp, rcp/endpoint.hpp,
// rcp/gpio.hpp, and rcp/spi.hpp.
// combinator (arithmetic mean at both levels), cadence-pattern API shape,
// and single-value wire codec (encode_adc_value) chosen in this file are
// this implementation's own, same as the equivalent disclaimers in
// rcp/avtp.hpp, rcp/regmap.hpp, rcp/endpoint.hpp, rcp/gpio.hpp, rcp/spi.hpp,
// and rcp/i2c.hpp.
#pragma once

#include <rcp/avtp.hpp>
#include <rcp/endpoint.hpp>

#include <cstdint>
#include <optional>
#include <string>
#include <system_error>
#include <utility>
#include <vector>

namespace rcp {
Expand All @@ -84,6 +114,13 @@ enum class AdcCadence : uint8_t {
enum class AdcErrc : int {
no_signal = 1, // internal condition: no valid sample before timeout/underrun — not a TC18 error code (see header comment; Table 27 has no ADC-specific no-signal entry)
invalid_averaging_config = 2, // adc_avg_intervals_per_request or adc_combine_avg_values is 0
// evt_row2_kind_of classified the request as ConfigWrite (evt[2:0] ==
// 111b, §12.7.1). This milestone deliberately does not implement the
// configuration-write shape (relative EP_functional-config start
// address + configuration data) — see AdcEndpoint::handle_request's own
// comment. Reported explicitly rather than silently accepted as a plain
// read or silently ignored, same as I2C's I2cErrc::config_write_not_supported.
config_write_not_supported = 3,
};

inline const std::error_category& adc_category() noexcept {
Expand All @@ -96,6 +133,8 @@ inline const std::error_category& adc_category() noexcept {
"TC18's Table 27 defines no ADC-specific no-signal error code)";
case AdcErrc::invalid_averaging_config:
return "rcp/adc: adc_avg_intervals_per_request/adc_combine_avg_values must be >= 1";
case AdcErrc::config_write_not_supported:
return "rcp/adc: evt[2:0]=111b configuration-write requests are not yet implemented";
default:
return "rcp/adc: unknown error";
}
Expand All @@ -109,6 +148,24 @@ inline std::error_code make_error_code(AdcErrc e) noexcept {
return {static_cast<int>(e), adc_category()};
}

// ── Wire codec ────────────────────────────────────────────────────────────────
// A single measurement value is a 16-bit big-endian "ADC value" field on the
// wire (§13.7.9.3's own field labels, Figure 34/Figure 35), reusing
// rcp/avtp.hpp's put_u16 rather than re-deriving byte order here, the same
// discipline rcp/pwm.hpp's encode_pwm_payload and rcp/gpio.hpp's
// encode_gpio_payload already follow. Only a single scripted-value encoding
// is provided — the multi-measurement (readsize > 2, Figure 35) response
// shape a real averaging/combine-count response can carry is out of scope
// for this milestone, same "data model, not full wire codec" scope split
// documented in rcp/regmap.hpp.
constexpr size_t kAdcValueLen = sizeof(uint16_t);

inline std::vector<uint8_t> encode_adc_value(uint16_t value) {
std::vector<uint8_t> buf(kAdcValueLen);
avtp::detail::put_u16(buf.data(), value);
return buf;
}

// ── Level 1 / level 2 combinators ─────────────────────────────────────────────
// Both levels of the averaging model use the same arithmetic-mean
// combinator in this implementation — the extraction does not mandate a
Expand Down Expand Up @@ -202,6 +259,50 @@ class AdcEndpoint {
triggered_samples.begin() + static_cast<long>(needed));
return compute_average(averaged_intervals, out_value);
}

// handle_request is ADC's request-decode entry point — the piece this
// header previously had none of, mirroring rcp::i2c::I2cEndpoint::
// handle_request's shape exactly (this repo's second Table 33 Row 2
// endpoint type after I2C). It classifies the incoming request's
// evt[2:0] field via rcp::endpoint::evt_row2_kind_of before doing
// anything else, so a Reserved value can never reach request_reading and
// be misread as an ordinary read, and a ConfigWrite value can never be
// silently accepted or silently dropped:
// - Plain (evt[2:0] == 000b): delegates to request_reading (the
// SelfTimed cadence pattern above) with `cfg`/`take_sample`/
// `out_value` unchanged — ADC's "standard read request" behavior,
// matching §13.7.9.3 Figure 34's own worked example (the same
// figure rcp/endpoint.hpp's evt_row2_kind_of cites to resolve Table
// 33's evt=000b ambiguity in the first place). This does not
// delegate to request_reading_from_trigger_queue — the
// ExternalTrigger cadence pattern remains its own separate entry
// point, not folded into handle_request by this milestone.
// - Reserved (evt[2:0] in 001b-110b): returns
// endpoint::EndpointErrc::reserved_evt_row2 without invoking
// `take_sample` or touching `out_value` — TC18 requires this be
// rejected with error code UNSUPPORTED_CMD.
// - ConfigWrite (evt[2:0] == 111b): §12.7.1's configuration-write
// shape targets the ADC EP's own functional-config block (relative
// start address + configuration data), not a sampling read at all.
// Full handling is deliberately out of scope for this milestone
// (nontrivial — it needs EP_functional-config wiring this header
// does not yet have, the same gap I2C's own handle_request defers
// for the identical reason); this returns
// AdcErrc::config_write_not_supported rather than crashing, silently
// accepting the request as a read, or silently doing nothing.
template <typename SampleFn>
std::error_code handle_request(uint8_t evt_op, const AdcAveragingConfig& cfg, SampleFn take_sample,
uint16_t& out_value) {
switch (endpoint::evt_row2_kind_of(evt_op)) {
case endpoint::EvtRow2Kind::Plain:
return request_reading(cfg, std::move(take_sample), out_value);
case endpoint::EvtRow2Kind::Reserved:
return endpoint::make_error_code(endpoint::EndpointErrc::reserved_evt_row2);
case endpoint::EvtRow2Kind::ConfigWrite:
return make_error_code(AdcErrc::config_write_not_supported);
}
return endpoint::make_error_code(endpoint::EndpointErrc::reserved_evt_row2); // unreachable
}
};

} // namespace adc
Expand Down
Loading
Loading