Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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: 50 additions & 3 deletions .github/workflows/esp32p4-ptp-lab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ jobs:
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft && contains(github.event.pull_request.body, '[run-phase-guard]') }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
# github.sha on pull_request denotes an auto-generated merge commit.
# Firmware build ID and provenance must use the actual PR HEAD.
ARSTACK_SOURCE_COMMIT: ${{ github.event.pull_request.head.sha }}
container:
image: espressif/idf:v5.5.4
steps:
Expand All @@ -43,13 +47,14 @@ jobs:
run: |
source /opt/esp/idf/export.sh
cmake -S tests/ptp_profile -B build-phase-guard -DCMAKE_BUILD_TYPE=Release
cmake --build build-phase-guard --target sv_ptp_phase_authority_tests sv_tx_egress_evidence_tests ptp_power_profile_tests ptp_runtime_tests --parallel 2
ctest --test-dir build-phase-guard --output-on-failure -R 'sv_ptp_phase_authority_tests|sv_tx_egress_evidence_tests|ptp_power_profile_tests|ptp_runtime_tests'
cmake --build build-phase-guard --target ptp_control_cancellation_tests ptp_emac_role_ownership_tests ptp_canonical_authority_tests ptp_receiver_clock_authority_tests sv_ptp_clock_lease_tests sv_ptp_phase_authority_tests sv_tx_egress_evidence_tests ptp_power_profile_tests ptp_runtime_tests --parallel 2
ctest --test-dir build-phase-guard --output-on-failure -R 'ptp_control_cancellation_tests|ptp_emac_role_ownership_tests|ptp_canonical_authority_tests|ptp_receiver_clock_authority_tests|sv_ptp_clock_lease_tests|sv_ptp_phase_authority_tests|sv_tx_egress_evidence_tests|ptp_power_profile_tests|ptp_runtime_tests'
- name: Compile guarded ESP32-P4 SOURCE firmware
shell: bash
working-directory: embedded/esp32p4_smv_injector
run: |
source /opt/esp/idf/export.sh
export ARSTACK_FIRMWARE_BUILD_ID="${ARSTACK_SOURCE_COMMIT:0:16}"
idf.py set-target esp32p4
python3 - <<'PY'
from pathlib import Path
Expand All @@ -73,6 +78,46 @@ jobs:
grep -qx '# CONFIG_AR_PTP_ALLOW_NONSTANDARD_LAB_CADENCE is not set' sdkconfig
idf.py build

- name: Stage guarded SOURCE bench artifact
shell: bash
working-directory: embedded/esp32p4_smv_injector
run: |
python3 tools/stage_ptp_experimental.py source "${GITHUB_WORKSPACE}/ptp-checkpoint"

- name: Compile guarded ESP32-P4 RECEIVER firmware
shell: bash
working-directory: embedded/esp32p4_smv_injector
run: |
source /opt/esp/idf/export.sh
export ARSTACK_FIRMWARE_BUILD_ID="${ARSTACK_SOURCE_COMMIT:0:16}"
python3 - <<'PY'
from pathlib import Path
p = Path("sdkconfig")
lines = [line for line in p.read_text(encoding="utf-8").splitlines()
if not line.startswith("CONFIG_AR_PTP_OPERATING_ROLE=")]
p.write_text("\n".join(lines) + "\nCONFIG_AR_PTP_OPERATING_ROLE=1\n",
encoding="utf-8")
PY
idf.py reconfigure
grep -qx 'CONFIG_AR_PTP_OPERATING_ROLE=1' sdkconfig
grep -qx 'CONFIG_AR_SMV_PTP_PHASE_SERVO_EXPERIMENTAL=y' sdkconfig
idf.py build

- name: Stage guarded RECEIVER bench artifact
shell: bash
working-directory: embedded/esp32p4_smv_injector
run: |
python3 tools/stage_ptp_experimental.py receiver "${GITHUB_WORKSPACE}/ptp-checkpoint"

- name: Upload M3D opt-in bench firmware (not a release)
uses: actions/upload-artifact@v4
with:
name: esp32p4-m3d-ptp-bench-${{ github.event.pull_request.head.sha }}
path: ptp-checkpoint/
if-no-files-found: error
retention-days: 7
compression-level: 6

build-esp32p4-ptp:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
strategy:
Expand Down Expand Up @@ -137,7 +182,9 @@ jobs:
'g_announce_sent.load',
'g_sync_sent.load',
'g_follow_up_sent.load',
'return g_ptp_ready.load',
'[[nodiscard]] bool source_has_fresh_evidence() noexcept',
'PtpSourceEvidenceFreshness::fresh(',
'return source_has_fresh_evidence();',
]
combined = eth + '\n' + ptp
missing = [token for token in required if token not in combined]
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/esp32p4-smv-injector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ jobs:
start_begin = source.index('if (take_start_request())')
start_end = source.index('if (!live_tx_running())', start_begin)
start_block = source[start_begin:start_end]
clock_start = start_block.rindex(
'sample_clock = start_sample_clock(task_handle, active_profile.publisher_rate_hz);'
)
# Match the call site, not an obsolete fixed argument list. The
# SOURCE/RECEIVER experimental arm passes ETH/PTP epoch metadata.
# Never relax the no-console-after-arming invariant.
clock_start = start_block.rindex('sample_clock = start_sample_clock(')
assert start_block.rindex('ESP_LOGI') < clock_start
assert 'ESP_LOG' not in start_block[clock_start:]
assert 'static_assert(std::atomic<std::uint32_t>::is_always_lock_free);' in source
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/ptp-core-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ jobs:
Build-Target 'smp_synch_policy_tests'
Build-Target 'smp_synch_wire_offset_tests'
Build-Target 'ptp_discipline_tests'
# Keep explicit MSVC targets in sync with the canonical CTest
# registrations; CTest otherwise fails with missing M3 binaries.
Build-Target 'ptp_control_cancellation_tests'
Build-Target 'ptp_emac_role_ownership_tests'
Build-Target 'ptp_canonical_authority_tests'
Build-Target 'ptp_receiver_clock_authority_tests'
Build-Target 'sv_ptp_clock_lease_tests'
Build-Target 'sv_ptp_epoch_tests'
Build-Target 'sv_ptp_phase_authority_tests'
Build-Target 'sv_tx_egress_evidence_tests'
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/ptp-m3e-bench-preflight.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: ESP32-P4 PTP M3E Bench Preflight

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- 'scripts/ptp_m3e_bench_gate.py'
- 'tests/test_ptp_m3e_bench_gate.py'
- 'docs/PTP_M3E_BENCH_EVIDENCE_GATE.md'
- '.github/workflows/ptp-m3e-bench-preflight.yml'
push:
branches: [main]
paths:
- 'scripts/ptp_m3e_bench_gate.py'
- 'tests/test_ptp_m3e_bench_gate.py'
- 'docs/PTP_M3E_BENCH_EVIDENCE_GATE.md'
- '.github/workflows/ptp-m3e-bench-preflight.yml'

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
offline-bench-preflight:
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: actions/checkout@v4
- name: Run M3E positive / negative artifact, serial and wire evidence tests
run: python3 -m unittest discover -s tests -p 'test_ptp_m3e_bench_gate.py' -v
32 changes: 32 additions & 0 deletions docs/PTP_CANONICAL_RUNTIME_AUTHORITY_M3A.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# M3A.1 — Canonical PTP Clock Authority & Receiver START/STOP Fencing

This milestone composes one role-tagged timing-authority snapshot from the
existing SOURCE readiness/fault generation and the M3B.1 RECEIVER worker-owned
lease. This is an **EMAC PTP clock** lease, **not** PPS-to-`smpCnt=0` phase
binding or evidence of precise SV egress. `sv_phase_qualified` remains false.

The public `ar_ptp_lab_get_timing_authority()` returns a compact SOURCE,
TIME_RECEIVER or MONITOR snapshot: role, generation, usable, selected GM
identity/port and independently qualified global traceability. One private
facade status read is the only input; it is the same path as GUI/serial status,
but without verbose per-observation logging.

Safety:
- SOURCE generation is double-checked around live status retrieval. A
stop/restart or missing/changed source generation revokes readiness.
- RECEIVER/MONITOR worker busy (pending/start/cleanup) is distinct from
runtime *ready*. A pending cleanup blocks a new SOURCE START/configure.
- A receiver STOP request revokes exposed clock usability immediately; the
worker finalizes hardware rollback later. Snapshot cannot resurrect a
stale LOCKED bit while STOP/cleanup is pending.
- RECEIVER clock readiness requires genuine selected GM, measured LOCKED,
nonzero generation and worker READY, not a status field alone.
- SOURCE lab time is not declared globally traceable; MONITOR is read-only.
- No heap, locks or ETH IOCTL are added in the high-frequency SMV ISR.
Control-plane facade and 5 ms receiver worker remain distinct.

Acceptance: strict portable `ptp_canonical_authority_tests` covers all role
claims and invalid/stale evidence. Guarded ESP32-P4 firmware compilation
builds the facade plus receiver worker, preserving standard runtime defaults.
Physical board, GPS/PTP lock, MAC timestamp egress, RECEIVER phase discipline
and global smpSynch promotion are not yet accepted.
25 changes: 25 additions & 0 deletions docs/PTP_EMAC_ROLE_OWNERSHIP_M3D1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# M3D.1 — Cross-role EMAC ownership gate

Follow-up to M3C.2. The SOURCE status `ar_ptp_source_is_running()`
indicates verified fresh outbound Announce/Sync evidence, **not** that the
underlying SOURCE worker owns the EMAC PTP timestamp hardware. A SOURCE task
can be actively starting, holding over or stopping with `is_running=false`.

Use the SOURCE worker's atomic `g_ptp_started` as an explicit lifetime claim
via `ar_ptp_source_runtime_active()`, matching the RECEIVER's
`ptp_receiver_runtime_active()`. The facade now rejects all cross-role
START and config changes while either worker is active, including a SOURCE
worker without valid fresh packet evidence, or while RECEIVER cleanup is
pending. The same role-ownership predicate is used for configuration,
startup and status routing. A separate nonblocking atomic START/config
transaction guard prevents overlapping control callers from both passing
the idle check before a worker has claimed the EMAC.

This closes false 'source idle' observations without changing PTP freshness,
clock authority, SMV phase/encoder, release images or PTP protocol timing.
The measured `ar_ptp_lab_is_running()` API retains its READY semantics;
the newly exported SOURCE active query is explicitly **not** a clock lease.

Portable regression enumerates all ownership combinations. Both guarded
firmware role builds are the integration gate. Physical ESP32-P4, external
GM, PTP lock and relay/PPS egress acceptance are still outstanding.
Loading
Loading