Skip to content

fix: v2.24.0 — PWM_OUT write semantics + GPIO input-pin masking (BREAKING) - #104

Merged
SoundMatt merged 1 commit into
mainfrom
fix/pwm-gpio-write-semantics
Aug 2, 2026
Merged

SoundMatt merged 1 commit into
mainfrom
fix/pwm-gpio-write-semantics

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

Summary

  • PWM_OUT (issue fix: v2.24.0 — PWM_OUT write semantics + GPIO input-pin masking (BREAKING) #104): a prior fix (issue [cpp-RCP-01] PWM_OUT payload fields are 32-bit instead of the required 16-bit (field order is correct) #70) had narrowed PwmOutEndpoint::handle_write to WriteSemantics::Replace only, reasoning that PWM_OUT's own request-handling section (§13.7.5.3) describes no write combination. That missed that §13.5 Table 30 — the table that governs evt[2:0] across every endpoint type — explicitly assigns PWM_OUT the same eight write semantics as GPIO, including Add/Subtract, whose own worked examples in that row name PWM_out's duty cycle directly ("this can be used to increase/decrease the duty cycle of PWM_out"). This was a real regression: rcp/endpoint.hpp's own saturating_add/saturating_subtract were built templated on the caller's unsigned width specifically so GPIO's 32-bit pin mask and PWM_OUT's narrower period/duration fields could share one implementation, per a surviving v2.4.0 roadmap comment issue [cpp-RCP-01] PWM_OUT payload fields are 32-bit instead of the required 16-bit (field order is correct) #70's fix then contradicted without updating. Restored Or/And/Xor/Add/Subtract via a dedicated 16-bit-width combinator — not GPIO's uint32_t apply_bitmask_write directly, which would saturate at the wrong width and silently wrap instead of clamping at 0xFFFF.
  • GPIO (issue docs: v2.25.0 — extract and formally reference every TC18 SHOULD/MAY clause #105): TC18 §13.7.4.3 states a write request to a pin currently configured as an input is ignored for that pin. apply_gpio_write never consulted GpioState::directions before this fix — every write semantics, including a bare Replace, overwrote every addressed bit regardless of configured direction, so a request could silently corrupt an input pin's externally-driven value. The combinator's result is now committed only for output-configured bits; input-configured bits are left unchanged.
  • CAN format (the third flagged item): investigated and found to have no live code path. rcp/can.hpp is validation-only (identifier range checks, frame-format payload ceilings, XL acceptance filtering) — there is no ACF-level wire encode/decode for CAN anywhere in this codebase, so there's no evt[2:0]-carries-format defect to fix. The existing .fusa-reqs.json entries accurately describe this validation-only scope. Left as a known, documented gap (a feature build-out, not a bug), not silently closed.
  • Also corrected two stale .fusa-reqs.json entries found along the way: REQ-PWM-002/003 described the (buggy) Replace-only behavior, and REQ-PWM-006 still described an invented "MidPulse" trigger signal superseded by a real Table-44 fix (RisingEdge/FallingEdge) whose code+tests were already correct.
  • BREAKING: both real fixes change behavior for any caller relying on the previous (incorrect) results.

Test plan

  • New/updated Catch2 tests for both fixes, including a saturation-width regression test (Add/Subtract must saturate at 0xFFFF for PWM_OUT's 16-bit fields, not 0xFFFFFFFF)
  • Mutation-tested both fixes: reverted each to its prior (buggy) behavior, confirmed the new tests fail with exactly the expected assertions, restored
  • Fixed 3 pre-existing tests whose fixtures implicitly relied on the old unmasked-write behavior (test_gpio.cpp x2, test_mock.cpp, test_capi.cpp) by explicitly configuring pin direction via Reconfigure before writing, rather than weakening the fix
  • Full clean build + ctest: 53/53 passed
  • ASan/UBSan build + ctest: 53/53 passed clean
  • cpfusa check (fresh v0.18.0 build matching CI's exact pin): 0 errors
  • cpfusa trace --req-coverage 100 --sec-tested 100: 376/376 requirements traced and tested

…KING)

PWM_OUT (issue #104): a prior fix (issue #70) had narrowed handle_write to
Replace only, reasoning that PWM_OUT's own request-handling section
describes no write combination. That missed that §13.5 Table 30 — the
table governing evt[2:0] across every endpoint type — explicitly assigns
PWM_OUT the same eight write semantics as GPIO, including Add/Subtract,
whose own worked examples in that row name PWM_out's duty cycle directly.
Restored Or/And/Xor/Add/Subtract via a dedicated 16-bit-width combinator
(not GPIO's uint32_t apply_bitmask_write directly, which would saturate
Add/Subtract at the wrong width and silently wrap instead of clamping).

GPIO (issue #105): TC18 §13.7.4.3 states a write to an input-configured
pin is ignored for that pin. apply_gpio_write never consulted
GpioState::directions — every write semantics, including a bare Replace,
overwrote every addressed bit regardless of direction, so a request could
silently corrupt an input pin's externally-driven value. Now masked: the
combinator's result is committed only for output-configured bits.

CAN format (flagged, investigated): no live code path found. rcp/can.hpp
is validation-only (identifier ranges, frame-format ceilings, XL
filtering) with no ACF wire codec anywhere in this codebase for CAN — the
existing requirements accurately describe that scope. Left as a known,
documented gap (a feature build-out, not a bug fix), not silently closed.

Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit 230adf0 into main Aug 2, 2026
22 checks passed
@SoundMatt
SoundMatt deleted the fix/pwm-gpio-write-semantics branch August 2, 2026 04:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant