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 two-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 two endpoints: GPIO at endpoint id/byte_bus_id 1 and SPI at endpoint id/byte_bus_id 2.",
"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.",
"standard": "iso26262",
"level": "ASIL-B",
"asil": "ASIL-B"
Expand Down Expand Up @@ -160,6 +160,24 @@
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-MOCK-011",
"title": "I2C dispatch applies Table 33 Row 2 evt[2:0] classification before touching endpoint state",
"text": "rcp::mock::Server::dispatch, for a request addressed to the I2C endpoint while RcpConfigured, shall decode the request's evt[2:0] field via endpoint::evt_row2_kind_of and drive i2c::I2cEndpoint::handle_request with it: a Plain (000b) request shall be answered as a ReadResponse carrying whatever bytes set_i2c_response last scripted, a Reserved (001b-110b) evt shall be rejected with endpoint::EndpointErrc::reserved_evt_row2 (wire error code UNSUPPORTED_CMD) without recording anything as sent/received, and a ConfigWrite (111b) evt shall be rejected with i2c::I2cErrc::config_write_not_supported (wire error code UNSUPPORTED_CMD) rather than crashing or being treated as a plain transfer.",
"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-012",
"title": "Operational I2C requests are rejected before RCP_CONFIGURED, same gating as GPIO/SPI",
"text": "rcp::mock::Server::dispatch, for a request addressed to the I2C endpoint while the server's lifecycle state is not RcpConfigured, shall return regmap::RegMapErrc::request_rejected and an ErrorResponse without touching the endpoint's live state, the same operational-request gating REQ-MOCK-006 already applies to GPIO/SPI.",
"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 @@ -2500,6 +2518,17 @@
"tc18": "§11.2.2.5 (\"Each endpoint may generate one or more trigger signals.\"), TC18.txt L1689",
"tc18_master_id": "TC18-11.2.2.5-002"
},
{
"id": "REQ-ENDPOINT-007",
"title": "evt[2:0] Row 2 (ADC/PWM_IN/I2C/LIN/CAN/UART/ISELED/MDIO) classification",
"text": "rcp::endpoint::evt_row2_kind_of shall classify evt_op values, masked to the low 3 bits first: 000b as EvtRow2Kind::Plain, 111b as EvtRow2Kind::ConfigWrite, and every other value (001b-110b) as EvtRow2Kind::Reserved, per TC18 §13.5 Table 33's shared rule for the {ADC, PWM_IN, I2C, LIN, CAN, UART, ISELED, MDIO} endpoint-type row. EndpointErrc::reserved_evt_row2 shall report a non-empty diagnostic message in its own error_category. RESOLVED AMBIGUITY: Table 33's own literal text groups evt[2:0]=000b together with 001b-110b as a single \"reserved -- reject\" range for this row, which read in isolation would make evt=000b illegal too, leaving every endpoint type in this row permanently unusable for ordinary requests. That reading is contradicted by TC18's own §13.7.9.3 worked example for ADC (a member of this row) showing evt[2:0]=000b used in an ordinary standard read request that succeeds normally. This implementation follows the worked example (evt=000b is the ordinary, successful case), the same resolution c-RCP's rcp_acf_evt_row2_is_plain() already shipped for this identical ambiguity.",
"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: §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-GPIO-001",
"title": "GPIO payload is a 4-byte big-endian bitmask",
Expand Down Expand Up @@ -2678,6 +2707,26 @@
"level": "ASIL-B",
"asil": "ASIL-B"
},
{
"id": "REQ-I2C-006",
"title": "I2cEndpoint::handle_request applies Table 33 Row 2 evt[2:0] classification before transferring",
"text": "rcp::i2c::I2cEndpoint::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 transfer() with out_bytes/in_bytes/acked unchanged; for EvtRow2Kind::Reserved, shall return endpoint::EndpointErrc::reserved_evt_row2 without recording anything as sent or received.",
"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: §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-I2C-007",
"title": "I2cEndpoint::handle_request reports config-write (evt[2:0]==111b) as not yet supported",
"text": "rcp::i2c::I2cEndpoint::handle_request, for EvtRow2Kind::ConfigWrite (evt[2:0]==111b), shall return I2cErrc::config_write_not_supported without recording anything as sent or received and without crashing, rather than silently accepting the request as an ordinary transfer or silently doing nothing; full §12.7.1 configuration-write handling is deliberately deferred to a follow-up milestone. I2cErrc::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-UART-001",
"title": "UART TX queue is independent of the RX FIFO",
Expand Down
51 changes: 51 additions & 0 deletions include/rcp/endpoint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// fusa:req REQ-ENDPOINT-004
// fusa:req REQ-ENDPOINT-005
// fusa:req REQ-ENDPOINT-006
// fusa:req REQ-ENDPOINT-007

// Shared endpoint-registration and request-dispatch scaffolding — the pieces
// every concrete OPEN Alliance TC18 Remote Control Protocol Specification
Expand Down Expand Up @@ -114,6 +115,50 @@ constexpr WriteSemantics write_semantics_of(uint8_t evt_op) noexcept {
return static_cast<WriteSemantics>(evt_op & 0x07);
}

// ── evt[2:0] Row 2 classification (Table 33's ADC/PWM_IN/I2C/LIN/CAN/UART/ ──
// ISELED/MDIO row) ──────────────────────────────────────────────────────────
// The 3-way evt[2:0] classification shared by every endpoint type in Table
// 33's second row (extraction §13.5): I2C (this milestone, rcp/i2c.hpp) and,
// in later milestones, ADC, PWM_IN, LIN, CAN, UART, ISELED, and MDIO. Unlike
// WriteSemantics above (GPIO/PWM_OUT's own row) and spi::channel_of (SPI's
// own dedicated row), this row's evt[2:0] carries no combinable value or
// channel selector at all: 000b is the only value a plain (non-
// configuration) request in this row may carry, 001b-110b are reserved (a
// request carrying one must be rejected with error code UNSUPPORTED_CMD,
// never silently treated as a no-op or as if it were 000b), and 111b
// selects an entirely different, configuration-write-shaped request
// (§12.7.1) that a plain read/write decoder must never accept either.
//
// RESOLVED AMBIGUITY: Table 33's own literal text groups evt[2:0]=000b
// together with 001b-110b as a single "reserved — reject" range for this
// row, which read in isolation would make evt=000b illegal too, leaving
// every endpoint type in this row permanently unusable for ordinary
// requests. That reading is contradicted by TC18's own §13.7.9.3 worked
// example for ADC (a member of this row) showing evt[2:0]=000b used in an
// ordinary standard read request (Figure 34's own bit-level evt field is
// all zero) that succeeds normally, not rejected. This implementation
// follows the worked example — evt=000b is the ordinary, successful case —
// the same resolution c-RCP's rcp_acf_evt_row2_is_plain() already shipped
// for this identical ambiguity.
//
// This function is deliberately not scoped to I2C: every Row 2 endpoint
// type is expected to call it from its own request-decode entry point,
// same as write_semantics_of above is shared by GPIO and PWM_OUT.
enum class EvtRow2Kind : uint8_t {
Plain = 0, // evt[2:0] == 000b — the only valid plain-request value
ConfigWrite = 1, // evt[2:0] == 111b — §12.7.1 configuration-write shape
Reserved = 2, // every other evt[2:0] value (001b-110b) — reject as UNSUPPORTED_CMD
};

// evt_row2_kind_of masks its argument down to the low 3 bits before
// classifying, same discipline as write_semantics_of above.
constexpr EvtRow2Kind evt_row2_kind_of(uint8_t evt_op) noexcept {
const uint8_t masked = static_cast<uint8_t>(evt_op & 0x07);
if (masked == 0x00) return EvtRow2Kind::Plain;
if (masked == 0x07) return EvtRow2Kind::ConfigWrite;
return EvtRow2Kind::Reserved;
}

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

enum class EndpointErrc : int {
Expand All @@ -125,6 +170,10 @@ enum class EndpointErrc : int {
// endpoint-type-specific — see e.g. gpio::apply_gpio_write) was passed
// to it directly.
non_combinable_write_semantics = 2,
// evt_row2_kind_of classified a Table 33 Row 2 request's evt[2:0] as
// Reserved (001b-110b) — TC18 requires this be rejected with error code
// UNSUPPORTED_CMD, never silently accepted or treated as Plain.
reserved_evt_row2 = 3,
};

inline const std::error_category& endpoint_category() noexcept {
Expand All @@ -136,6 +185,8 @@ inline const std::error_category& endpoint_category() noexcept {
return "rcp/endpoint: evt[2:0]=4 (Reserved) is not a valid write semantics";
case EndpointErrc::non_combinable_write_semantics:
return "rcp/endpoint: write semantics does not combine via apply_bitmask_write";
case EndpointErrc::reserved_evt_row2:
return "rcp/endpoint: evt[2:0] is a reserved Table 33 Row 2 value (001b-110b)";
default:
return "rcp/endpoint: unknown error";
}
Expand Down
62 changes: 61 additions & 1 deletion include/rcp/i2c.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// fusa:req REQ-I2C-003
// fusa:req REQ-I2C-004
// fusa:req REQ-I2C-005
// fusa:req REQ-I2C-006
// fusa:req REQ-I2C-007

// I2C endpoint (ep_type 0x04) — controller-only raw byte-stream transfer
// (including address bytes, per the OPEN Alliance TC18 Remote Control
Expand All @@ -19,6 +21,15 @@
// the target device address travels inside the raw byte stream itself
// rather than as a separate selector field.
//
// Table 30/33 Row 2 evt[2:0] validation pilot (post-v2.4.0): I2cEndpoint's
// handle_request is this repo's first 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
// a concrete endpoint type's request decode. The other seven Row 2 endpoint
// types are expected to add their own handle_request-shaped entry point
// calling the same rcp::endpoint::evt_row2_kind_of in their own follow-up
// PRs, matching this shape.
//
// OPEN ITEM, called out explicitly per the roadmap rather than guessed at:
// the extraction leaves the mapping of I2C's `i2c_mode` field (device
// speed grade — Standard/Fast/Fast-mode-Plus/High-Speed) onto wire values
Expand Down Expand Up @@ -68,6 +79,13 @@ constexpr I2cMode i2c_mode_of(bool hs) noexcept {

enum class I2cErrc : int {
nack = 1, // the addressed device did not acknowledge (controller-only view)
// evt_row2_kind_of classified the request as ConfigWrite (evt[2:0] ==
// 111b, §12.7.1). This milestone's pilot deliberately does not
// implement the configuration-write shape (relative EP_functional-
// config start address + configuration data) — see handle_request's own
// comment. Reported explicitly rather than silently accepted as a plain
// transfer or silently ignored.
config_write_not_supported = 2,
};

inline const std::error_category& i2c_category() noexcept {
Expand All @@ -76,7 +94,9 @@ inline const std::error_category& i2c_category() noexcept {
std::string message(int ev) const override {
switch (static_cast<I2cErrc>(ev)) {
case I2cErrc::nack: return "rcp/i2c: addressed device did not acknowledge (NACK)";
default: return "rcp/i2c: unknown error";
case I2cErrc::config_write_not_supported:
return "rcp/i2c: evt[2:0]=111b configuration-write requests are not yet implemented";
default: return "rcp/i2c: unknown error";
}
}
};
Expand Down Expand Up @@ -153,6 +173,46 @@ class I2cEndpoint {
return {};
}

// handle_request is I2C's request-decode entry point — the piece this
// header previously had none of (issue: cpp-RCP had zero Table 33 Row 2
// evt[2:0] validation anywhere). It classifies the incoming request's
// evt[2:0] field via rcp::endpoint::evt_row2_kind_of (Table 33's shared
// ADC/PWM_IN/I2C/LIN/CAN/UART/ISELED/MDIO rule) before doing anything
// else, so a Reserved value can never reach transfer() and be misread
// as an ordinary transfer, and a ConfigWrite value can never be
// silently accepted or silently dropped:
// - Plain (evt[2:0] == 000b): delegates straight to transfer() with
// `out_bytes`/`in_bytes`/`acked` unchanged — I2C's existing
// controller-only raw byte-stream transfer model (extraction
// §13.7.7.3) already IS this row's correct "plain request"
// behavior; evt[2:0] carries no combinable value or channel
// selector for this row the way it does for GPIO/PWM_OUT or SPI.
// - Reserved (evt[2:0] in 001b-110b): returns
// endpoint::EndpointErrc::reserved_evt_row2 without touching any
// endpoint state or recording anything as sent/received — TC18
// requires this be rejected with error code UNSUPPORTED_CMD.
// - ConfigWrite (evt[2:0] == 111b): §12.7.1's configuration-write
// shape targets the I2C EP's own functional-config block (relative
// start address + configuration data), not a bus transfer at all.
// Full handling is deliberately out of scope for this milestone's
// pilot (nontrivial — it needs EP_functional-config wiring this
// header does not yet have); this returns
// I2cErrc::config_write_not_supported rather than crashing,
// silently accepting the request as a transfer, or silently doing
// nothing.
std::error_code handle_request(uint8_t evt_op, std::vector<uint8_t> out_bytes,
std::vector<uint8_t> in_bytes, bool acked = true) {
switch (endpoint::evt_row2_kind_of(evt_op)) {
case endpoint::EvtRow2Kind::Plain:
return transfer(std::move(out_bytes), std::move(in_bytes), acked);
case endpoint::EvtRow2Kind::Reserved:
return endpoint::make_error_code(endpoint::EndpointErrc::reserved_evt_row2);
case endpoint::EvtRow2Kind::ConfigWrite:
return make_error_code(I2cErrc::config_write_not_supported);
}
return endpoint::make_error_code(endpoint::EndpointErrc::reserved_evt_row2); // unreachable
}

const std::vector<uint8_t>& last_sent() const noexcept { return last_out_; }
const std::vector<uint8_t>& last_received() const noexcept { return last_in_; }
endpoint::TriggerRegistry& triggers() noexcept { return triggers_; }
Expand Down
Loading
Loading