Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7c4949b
Refactor: Standardize pacing structures to long double precision
Sakoram Jun 29, 2026
7059fbc
Fix: Hardon async t3 delay req timestamp acquisition and PTP tests
Sakoram Jun 29, 2026
a24e9b5
Feat: Add E830 TX time launch support for TSN pacing
Sakoram Jun 29, 2026
05ae052
Fix: Prevent stuck epoch recovery on ST20/ST40 TX pacing
Sakoram Jul 7, 2026
b51f5ae
Test: Add unit coverage for ST20 TX epoch/pacing math
Sakoram Jul 7, 2026
22717f8
Test: Add NoCtx regression test for PTP epoch recovery
Sakoram Jul 7, 2026
0396893
Test: Support 2-port/PF runs for NoCtx tests
Sakoram Jul 7, 2026
63a73ea
Fix: Require ptp sync with phc for TSN launch-time pacing
Sakoram Jul 7, 2026
a06dd51
Docs: Note --ptp limitation on E810/E830 secondary ports
Sakoram Jul 7, 2026
3888ef8
Test: Add st20p pacing_way coverage across VF and PF interfaces
Sakoram Jul 7, 2026
7a483bb
Refactor: Move Intel NIC device ID constants to const.py
Sakoram Jul 13, 2026
f7e9442
Test: Parametrize st20p pacing_way test over application
Sakoram Jul 13, 2026
d375421
Fix: Honor application TX pacing timestamps
Sakoram Jul 13, 2026
1777dcc
Fix: Reject media clock timestamps for ST40 user pacing
Sakoram Jul 14, 2026
66d8ae7
Docs: State TAI-only pacing on USER_PACING flags
Sakoram Jul 14, 2026
5d7694d
Fix: Reject media clock timestamps for ST30 user pacing
Sakoram Jul 14, 2026
e6c8693
Fix: Resolve SEGV in St20/St30 PipelineRxTest transport stubs
Sakoram Jul 14, 2026
280d4fa
Fix: Enable ptp service for TSN pacing NoCtx epoch test
Sakoram Jul 14, 2026
73c6049
Test: Add wire-level TSN pacing spread smoke test
Sakoram Jul 15, 2026
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
1 change: 1 addition & 0 deletions .github/instructions/mtl-system-setup.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ decision logic and domain knowledge that tool descriptions alone don't convey.
- **ldconfig**: Always run after installing DPDK or MTL — libraries won't be found otherwise.
- **MtlManager**: Must be running for lcore allocation. Start with `manager_start`.
- **E810 vs E830**: Both supported, same VF workflow. E830 device ID `12d2`, E810 device ID `1592`.
- **NoCtx `_pf_` tests (TSN/launch-time-pacing)**: only validated on E830 (`12d2`). Not gated by device ID in the test code — check the port's device ID yourself (e.g. `cat /sys/bus/pci/devices/<bdf>/device`) before running `run_pf.sh` / `run_noctx_pf_tests`; expect unrelated failures on E810 or other NICs.
- **NUMA**: Allocate from the same NUMA node as the NIC for best performance. VFs inherit PF's NUMA node.
- **VF BDF patterns**: PF `.0` creates VFs at `:01.0-5`, PF `.1` at `:11.0-5`.
- **versions.env**: Defines `DPDK_VER`, `ICE_VER`, `ICE_DMID`. Patches live in `patches/dpdk/<ver>/`.
218 changes: 193 additions & 25 deletions .github/mcp/mtl_mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1541,44 +1541,48 @@ def run_noctx_tests(
invokes one process per test, sleeping `cooldown_seconds` between them
(mirrors tests/integration_tests/noctx/run.sh).

All 4 ports are required — this is the full-suite run and a handful of
tests exercise redundant (4-port) sessions; requiring fewer here would let
those tests fail ambiguously depending on how many ports happen to be
supplied. Tests named with a `_pf_` infix (e.g. TSN/launch-time-pacing)
require a PF, which this VF-oriented run can never satisfy, so they are
excluded automatically — use `run_noctx_pf_tests` for those instead.

Args:
port_1..port_4: VF BDFs. Auto-discovered (vfio-pci) if any are empty.
port_1, port_2, port_3, port_4: BDFs for the 4 ports. Auto-discovered
(any vfio-pci-bound ports, PF or VF) if left empty.
gtest_filter: Gtest filter scoped to NoCtxTest (e.g. '*nonsplit*' or
'NoCtxTest.st40i_*'). All NoCtxTest cases if empty.
timeout_seconds: Max seconds per individual test process (default 600).
cooldown_seconds: Sleep between tests (default 10).
"""

# Auto-discover
if not all([port_1, port_2, port_3, port_4]):
vfs = (
# Auto-discover only if the caller supplied nothing; otherwise respect
# exactly what was passed.
if not any([port_1, port_2, port_3, port_4]):
discovered = (
_run_output(
"dpdk-devbind.py -s 2>/dev/null | awk '/drv=vfio-pci/{print $1}' | head -4"
)
.strip()
.splitlines()
)
if len(vfs) < 4:
return (
"Error: noctx tests require 4 VF ports bound to vfio-pci. "
f"Found {len(vfs)}. Run `setup_after_reboot_auto` first."
)
ports = [v.strip() for v in vfs[:4]]
port_1 = port_1 or ports[0]
port_2 = port_2 or ports[1]
port_3 = port_3 or ports[2]
port_4 = port_4 or ports[3]

# Validate all BDFs
for label, bdf in [
("port_1", port_1),
("port_2", port_2),
("port_3", port_3),
("port_4", port_4),
]:
ports = [v.strip() for v in discovered[:4]]
else:
ports = [p for p in (port_1, port_2, port_3, port_4) if p]

if len(ports) < 4:
return (
"Error: noctx full-suite run requires 4 ports bound to vfio-pci "
f"(PF or VF). Found {len(ports)}. Run `setup_after_reboot_auto` first, "
"or supply port_1..port_4 explicitly."
)

# Validate the BDFs we ended up with
for idx, bdf in enumerate(ports, start=1):
err = _validate_bdf(bdf)
if err:
return f"Error: {label} — {err}"
return f"Error: port_{idx} — {err}"

# Sanitize gtest_filter
if gtest_filter and not re.match(r"^[a-zA-Z0-9_.*:/-]+$", gtest_filter):
Expand All @@ -1590,7 +1594,7 @@ def run_noctx_tests(

sim_hint = "" if _build_has_simulate_drops() else _SIM_DROPS_HINT

port_list = f"{port_1},{port_2},{port_3},{port_4}"
port_list = ",".join(ports)

# Default to all NoCtxTest cases; scope user filter to NoCtxTest.*
if not gtest_filter:
Expand All @@ -1600,6 +1604,12 @@ def run_noctx_tests(
else:
list_filter = f"NoCtxTest.*{gtest_filter}*"

# PF-only tests (name contains "_pf_") can never pass on these VF-capable
# ports; exclude them unless the caller already wrote their own negative
# pattern (gtest filter syntax only supports one "-" exclusion group).
if "-" not in list_filter:
list_filter = f"{list_filter}-NoCtxTest.*_pf_*"

# Enumerate matching test names (one process per test below)
listing = _run_output(
f"{binary} --gtest_list_tests --no_ctx"
Expand Down Expand Up @@ -1673,7 +1683,165 @@ def run_noctx_tests(
return (
f"## Noctx Test Results\n"
f"- Status: {status}\n"
f"- Ports: {port_1}, {port_2}, {port_3}, {port_4}\n"
f"- Ports: {port_list}\n"
f"- Tests run: {len(results)} (passed {passed}, failed {failed})\n"
f"- Full log: `{log_path}`\n"
f"\n### Per-test results\n{per_test}\n"
f"{last_failure}"
f"{sim_hint}"
)


@mcp.tool()
def run_noctx_pf_tests(
port_1: str = "",
port_2: str = "",
gtest_filter: str = "",
timeout_seconds: int = 600,
cooldown_seconds: int = 10,
) -> str:
"""
Run the NoCtxTest cases that require PF ports (mirrors
tests/integration_tests/noctx/run_pf.sh).

Tests named with a `_pf_` infix (e.g. TSN/launch-time-pacing) require a PF
port — that offload is not advertised by VF drivers — so `run_noctx_tests`
excludes them. This tool runs just those, against 2 PF-bound ports (all
current PF-only tests use a single TX/RX pair).

Args:
port_1, port_2: BDFs for a PF TX/RX pair. Auto-discovered (vfio-pci
ports without a `physfn` sysfs link, i.e. real PFs,
not VFs) if left empty.
gtest_filter: Substring/glob to match against the test name (e.g.
'st20p_tx_epoch_onward_recovers_after_ptp_step' or
'st20p_*'), matched anywhere in the name — no need to
include "_pf_" yourself, it's required separately.
All `_pf_` cases if empty.
timeout_seconds: Max seconds per individual test process (default 600).
cooldown_seconds: Sleep between tests (default 10).
"""

if not any([port_1, port_2]):
discovered = (
_run_output(
"for d in $(dpdk-devbind.py -s 2>/dev/null | awk '/drv=vfio-pci/{print $1}'); do "
'[ ! -e "/sys/bus/pci/devices/$d/physfn" ] && echo "$d"; done | head -2'
)
.strip()
.splitlines()
)
ports = [v.strip() for v in discovered[:2]]
else:
ports = [p for p in (port_1, port_2) if p]

if len(ports) < 2:
return (
"Error: PF noctx tests require 2 ports bound to vfio-pci as PFs "
f"(not VFs). Found {len(ports)}. Run `nic_bind_pmd` on a PF, or "
"supply port_1/port_2 explicitly."
)

for idx, bdf in enumerate(ports, start=1):
err = _validate_bdf(bdf)
if err:
return f"Error: port_{idx} — {err}"

if gtest_filter and not re.match(r"^[a-zA-Z0-9_.*:/-]+$", gtest_filter):
return "Error: invalid gtest_filter characters"

binary = REPO_ROOT / "build/tests/KahawaiTest"
if not binary.is_file():
return "Error: KahawaiTest not built. Run `build_mtl` first."

sim_hint = "" if _build_has_simulate_drops() else _SIM_DROPS_HINT

port_list = ",".join(ports)

# Scope to NoCtxTest, same convention as run_noctx_tests: don't assume
# where "_pf_" falls relative to the caller's filter text (the naming
# convention puts the descriptive prefix *before* "_pf_", e.g.
# "st20p_..._pf_tsn_pacing", so a filter matching that prefix would never
# match a glob that requires "_pf_" to come first). Enumerate with just
# the caller's filter, then require "_pf_" as a separate post-filter.
if not gtest_filter:
list_filter = "NoCtxTest.*"
elif gtest_filter.startswith("NoCtxTest."):
list_filter = gtest_filter
else:
list_filter = f"NoCtxTest.*{gtest_filter}*"

listing = _run_output(
f"{binary} --gtest_list_tests --no_ctx"
f" --port_list={port_list}"
f" --gtest_filter={list_filter}",
timeout=60,
)
test_names: list[str] = []
current_suite = ""
for raw in listing.splitlines():
if not raw or raw.startswith(("DISABLED", "Note:")):
continue
if not raw.startswith(" "):
current_suite = raw.strip().rstrip(".")
else:
name = raw.strip()
if name and current_suite == "NoCtxTest" and "_pf_" in name:
test_names.append(name)

if not test_names:
return (
f"No PF-only (name contains '_pf_') NoCtxTest cases matched filter "
f"'{list_filter}'.\nListing output:\n{listing}"
)

sections: list[str] = []
results: list[tuple[str, str]] = [] # (name, PASS|FAIL|TIMEOUT)
for idx, name in enumerate(test_names):
full = f"NoCtxTest.{name}"
out = _run_output(
f"{binary} --auto_start_stop"
f" --port_list={port_list}"
f" --gtest_filter={full}"
f" --no_ctx_tests",
timeout=timeout_seconds,
)
if "*** TIMEOUT" in out:
status = "TIMEOUT"
elif re.search(r"\[\s+PASSED\s+\]\s+1\s+test", out):
status = "PASS"
else:
status = "FAIL"
results.append((name, status))
sections.append(f"===== {full}: {status} =====\n{out}\n")
if idx < len(test_names) - 1 and cooldown_seconds > 0:
time.sleep(cooldown_seconds)

combined = "\n".join(sections)
log_path = _save_test_log("noctx_pf", combined)

passed = sum(1 for _, s in results if s == "PASS")
failed = sum(1 for _, s in results if s != "PASS")
status = "PASSED" if failed == 0 else "FAILED"

per_test = "\n".join(f"- {s}: NoCtxTest.{n}" for n, s in results)
last_failure = ""
for name, st in results:
if st != "PASS":
for sec in sections:
if sec.startswith(f"===== NoCtxTest.{name}:"):
tail = "\n".join(sec.splitlines()[-30:])
last_failure = (
f"\n### First failure: NoCtxTest.{name} (last 30 lines)\n"
f"```\n{tail}\n```"
)
break
break

return (
f"## Noctx PF Test Results\n"
f"- Status: {status}\n"
f"- Ports: {port_list}\n"
f"- Tests run: {len(results)} (passed {passed}, failed {failed})\n"
f"- Full log: `{log_path}`\n"
f"\n### Per-test results\n{per_test}\n"
Expand Down
2 changes: 2 additions & 0 deletions doc/chunks/_ptp_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ This project includes built-in support for the Precision Time Protocol (PTP) pro
To enable this feature in the RxTxApp sample application, use the `--ptp` argument. The control for the built-in PTP feature is the `MTL_FLAG_PTP_ENABLE` flag in the `mtl_init_params` structure.

Note: Currently, the VF (Virtual Function) does not support the hardware timesync feature. Therefore, for VF deployment, the timestamp of the transmitted (TX) and received (RX) packets is read from the CPU TSC (TimeStamp Counter) instead. In this case, it is not possible to obtain a stable delta in the PTP adjustment, and the maximum accuracy achieved will be up to 1us.

Note: For Intel® E810 and E830 Series Ethernet Adapters, the built-in PTP relies on the hardware timesync feature of the primary port (port 0) of the NIC. If the primary port is not opened/used by MTL (for example, only a secondary port is passed to `mtl_init`), `--ptp` will not work on the secondary port.
Comment thread
DawidWesierski4 marked this conversation as resolved.
8 changes: 8 additions & 0 deletions doc/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ In the case that the rate-limiting feature is unavailable, TSC (Timestamp Counte

![TX Pacing](png/tx_pacing.png)

#### 4.3.3. TSN launch-time pacing

TSN launch-time pacing (`--pacing_way tsn` / `ST21_TX_PACING_WAY_TSN`) offloads packet pacing to the NIC: MTL stamps each packet with a launch time and the NIC releases it once its internal clock (the PHC) reaches that timestamp. This has three requirements, all enforced at session init:

- The NIC must be an Intel® E830 Series Ethernet Adapter. E810 lacks the TxPP launch-time hardware engine, so it never advertises the `RTE_ETH_TX_OFFLOAD_SEND_ON_TIMESTAMP` capability.
- The port must be a PF (Physical Function). VFs do not support the hardware timesync feature, so there is no HW PHC to compare the launch time against.
- The built-in PTP service must be enabled (`--ptp` / `MTL_FLAG_PTP_ENABLE`), and not forced to the TSC source (`MTL_FLAG_PTP_SOURCE_TSC`). The packet launch time is only meaningful if it is expressed in the same, PHC-synced clock domain the NIC compares it against. See [PTP Setup](run.md#71-ptp-setup) for how to enable it.

### 4.4. ST2110 RX

The RX (Receive) packet classification in MTL includes two types: Flow Director and RSS (Receive Side Scaling). Flow Director is preferred if the NIC is capable, as it can directly feed the desired packet into the RX session packet handling function.
Expand Down
10 changes: 9 additions & 1 deletion include/st20_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ extern "C" {
* User control the frame transmission time by passing a timestamp in
* st20_tx_frame_meta.timestamp, lib will wait until timestamp is reached for each frame.
* The time of sending is aligned with virtual receiver read schedule.
* Only ST10_TIMESTAMP_FMT_TAI is honored; ST10_TIMESTAMP_FMT_MEDIA_CLK is not
* supported for pacing and falls back to the default epoch-based pacing.
*/
#define ST20_TX_FLAG_USER_PACING (MTL_BIT32(3))
/**
Expand Down Expand Up @@ -118,6 +120,8 @@ extern "C" {
* Flag bit in flags of struct st22_tx_ops.
* User control the frame pacing by pass a timestamp in st22_tx_frame_meta,
* lib will wait until timestamp is reached for each frame.
* Only ST10_TIMESTAMP_FMT_TAI is honored; ST10_TIMESTAMP_FMT_MEDIA_CLK is not
* supported for pacing and falls back to the default epoch-based pacing.
*/
#define ST22_TX_FLAG_USER_PACING (MTL_BIT32(3))
/**
Expand Down Expand Up @@ -407,7 +411,11 @@ struct st20_tx_frame_meta {
enum st20_fmt fmt;
/** Second field type indicate for interlaced mode, set by user */
bool second_field;
/** Timestamp format */
/**
* Timestamp format. When ST20_TX_FLAG_USER_PACING is set, only
* ST10_TIMESTAMP_FMT_TAI is honored for pacing; ST10_TIMESTAMP_FMT_MEDIA_CLK
* falls back to the default epoch-based pacing.
*/
enum st10_timestamp_fmt tfmt;
/** Timestamp value */
uint64_t timestamp;
Expand Down
8 changes: 7 additions & 1 deletion include/st30_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ typedef struct st_rx_audio_session_handle_impl* st30_rx_handle;
* Flag bit in flags of struct st30_tx_ops.
* User control the frame pacing by pass a timestamp in st30_tx_frame_meta,
* lib will wait until timestamp is reached for each frame.
* Only ST10_TIMESTAMP_FMT_TAI is honored; ST10_TIMESTAMP_FMT_MEDIA_CLK is not
* supported for pacing and falls back to the default epoch-based pacing.
*/
#define ST30_TX_FLAG_USER_PACING (MTL_BIT32(3))
/**
Expand Down Expand Up @@ -259,7 +261,11 @@ struct st30_tx_frame_meta {
enum st30_sampling sampling;
/** Session packet time */
enum st30_ptime ptime;
/** Frame timestamp format */
/**
* Frame timestamp format. When ST30_TX_FLAG_USER_PACING is set, only
* ST10_TIMESTAMP_FMT_TAI is honored for pacing; ST10_TIMESTAMP_FMT_MEDIA_CLK
* falls back to the default epoch-based pacing.
*/
enum st10_timestamp_fmt tfmt;
/** Frame timestamp value */
uint64_t timestamp;
Expand Down
8 changes: 7 additions & 1 deletion include/st40_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ typedef struct st_rx_ancillary_session_handle_impl* st40_rx_handle;
* Flag bit in flags of struct st40_tx_ops.
* User control the frame pacing by pass a timestamp in st40_tx_frame_meta,
* lib will wait until timestamp is reached for each frame.
* Only ST10_TIMESTAMP_FMT_TAI is honored; ST10_TIMESTAMP_FMT_MEDIA_CLK is not
* supported for pacing and falls back to the default epoch-based pacing.
*/
#define ST40_TX_FLAG_USER_PACING (MTL_BIT32(3))
/**
Expand Down Expand Up @@ -316,7 +318,11 @@ struct st40_frame {
struct st40_tx_frame_meta {
/** Frame fps */
enum st_fps fps;
/** Frame timestamp format */
/**
* Frame timestamp format. When ST40_TX_FLAG_USER_PACING is set, only
* ST10_TIMESTAMP_FMT_TAI is honored for pacing; ST10_TIMESTAMP_FMT_MEDIA_CLK
* falls back to the default epoch-based pacing.
*/
enum st10_timestamp_fmt tfmt;
/** Frame timestamp value */
uint64_t timestamp;
Expand Down
2 changes: 2 additions & 0 deletions include/st40_pipeline_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ enum st40p_tx_flag {
* Flag bit in flags of struct st40_tx_ops.
* User control the frame pacing by pass a timestamp in st40_tx_frame_meta,
* lib will wait until timestamp is reached for each frame.
* Only ST10_TIMESTAMP_FMT_TAI is honored; ST10_TIMESTAMP_FMT_MEDIA_CLK is not
* supported for pacing and falls back to the default epoch-based pacing.
*/
ST40P_TX_FLAG_USER_PACING = (MTL_BIT32(3)),
/**
Expand Down
4 changes: 4 additions & 0 deletions include/st_pipeline_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,8 @@ enum st22p_tx_flag {
/**
* User control the frame pacing by pass a timestamp in st_frame,
* lib will wait until timestamp is reached for each frame.
* Only ST10_TIMESTAMP_FMT_TAI is honored; ST10_TIMESTAMP_FMT_MEDIA_CLK is not
* supported for pacing and falls back to the default epoch-based pacing.
*/
ST22P_TX_FLAG_USER_PACING = (MTL_BIT32(3)),
/**
Expand Down Expand Up @@ -443,6 +445,8 @@ enum st20p_tx_flag {
* User control frame transmission time by pass a timestamp in st_frame.timestamp,
* lib will wait until timestamp is reached for each frame. The time of sending is
* aligned with virtual receiver read schedule.
* Only ST10_TIMESTAMP_FMT_TAI is honored; ST10_TIMESTAMP_FMT_MEDIA_CLK is not
* supported for pacing and falls back to the default epoch-based pacing.
*/
ST20P_TX_FLAG_USER_PACING = (MTL_BIT32(3)),
/**
Expand Down
Loading
Loading