Skip to content
Open
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
11 changes: 9 additions & 2 deletions .github/instructions/mtl-system-setup.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,24 @@ decision logic and domain knowledge that tool descriptions alone don't convey.
4. If MTL not built or rebuild needed → `build_mtl` or `mtl_clean_rebuild`

### "Run integration tests"
1. Verify prerequisites: DPDK installed, VFs bound, MtlManager running
1. Verify prerequisites **before** the first `run_gtest` call: `dpdk_devbind_status` (ports bound as VFs, not bare PFs — see below), MtlManager running (`manager_start` if not).
2. `run_gtest` with appropriate `gtest_filter` (e.g. `St20p*`, `St30*`)
3. If tests segfault → see "Test crashed" below
4. Report pass/fail counts
4. If RX session creation fails with multicast-join/IGMP errors → see "Multicast join / IGMP failure" below
5. Report pass/fail counts

### "Test crashed / segfault"
1. **SEGFAULT in `iavf_tm_node_add`** → `ice_driver_status` — almost always stock ICE
2. **SEGFAULT elsewhere** → `dmesg_tail` for kernel errors
3. `dpdk_status` → version mismatch?
4. `mtl_clean_rebuild` → stale build?

### "Multicast join / IGMP failure on RX session creation"
1. `dpdk_devbind_status` first — check if the port is a bare PF bound to `vfio-pci` with 0 VFs. E810/E830 must always run through VFs (see `nic_bind_pmd`'s own tool description: prefer `nic_create_vf` for E800 series); a bare PF-to-vfio-pci binding causes exactly this symptom.
2. Fix: `nic_bind_kernel` on the PF, then `nic_create_vf` (use `trusted=true` if the test needs multicast/promiscuous features).
3. Re-run `run_gtest` with the newly created VF BDFs as `p_port`/`r_port`.
4. This is an environment issue, not a code regression — confirm by reproducing on one unrelated, already-passing suite (e.g. `St30_rx.create_free_single`) only if you need extra certainty; don't use it as the primary diagnostic path, step 1 above is sufficient and faster.

### "Build failed"
1. **Permission errors / CMakeCache.txt** → `mtl_clean_rebuild`
2. **librte_*.so not found** → `sudo ldconfig` (or `dpdk_build` if DPDK not installed)
Expand Down
45 changes: 31 additions & 14 deletions .github/mcp/mtl_mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ def _save_test_log(name: str, content: str) -> Path:
return log_path


def _summarize_output(name: str, out: str, tail_lines: int = 40) -> str:
"""Save large command output to a log file, returning a short summary.

Full build/install output can be thousands of lines — dumping it into a
tool result burns context for little benefit. This saves it to disk and
returns the log path, total line count (like `wc -l`), and just the
tail, so the caller can grep/read the file directly if more is needed.
"""
log_path = _save_test_log(name, out)
total_lines = len(out.splitlines())
tail = "\n".join(out.splitlines()[-tail_lines:])
return (
f"- Full log: `{log_path}` ({total_lines} lines)\n"
f"### Output (last {tail_lines} lines)\n```\n{tail}\n```"
)


def _load_versions() -> dict[str, str]:
"""Parse versions.env into a dict."""
result: dict[str, str] = {}
Expand Down Expand Up @@ -788,7 +805,8 @@ def ice_driver_rebuild() -> str:
)

return (
f"## ICE Driver Build\n{out}\n\n## Reload\n{reload_out}\n\n"
f"## ICE Driver Build\n{_summarize_output('ice_driver_rebuild', out)}\n\n"
f"## Reload\n{reload_out}\n\n"
"## ⚠ Important: VFs Destroyed\n"
"The ICE driver reload destroyed all existing VFs.\n"
"You MUST re-create VFs before running any tests:\n"
Expand Down Expand Up @@ -846,7 +864,10 @@ def dpdk_build() -> str:
ver_after = _run_output(
"pkg-config --modversion libdpdk 2>/dev/null || echo 'not installed'"
)
return f"## DPDK Build\n{out}\n\n## Result\nInstalled version: {ver_after}"
return (
f"## DPDK Build\n{_summarize_output('dpdk_build', out)}\n\n"
f"## Result\nInstalled version: {ver_after}"
)


# ===================================================================
Expand Down Expand Up @@ -899,7 +920,7 @@ def build_mtl(mode: str = "debugonly") -> str:
libmtl = _run_output("ldconfig -p 2>/dev/null | grep libmtl || echo 'not found'")

return (
f"## MTL Build ({mode})\n{out}\n\n"
f"## MTL Build ({mode})\n{_summarize_output('build_mtl', out)}\n\n"
f"## Artifacts\n- RxTxApp: {rxtxapp}\n- MtlManager: {manager}\n- libmtl: {libmtl}"
)

Expand Down Expand Up @@ -1248,7 +1269,7 @@ def build_ffmpeg_plugin() -> str:
"bash .github/scripts/setup_environment.sh",
timeout=600,
)
return f"## FFmpeg Plugin Build\n{out}"
return f"## FFmpeg Plugin Build\n{_summarize_output('ffmpeg_plugin_build', out)}"


@mcp.tool()
Expand All @@ -1265,7 +1286,9 @@ def build_gstreamer_plugin() -> str:
"bash .github/scripts/setup_environment.sh",
timeout=600,
)
return f"## GStreamer Plugin Build\n{out}"
return (
f"## GStreamer Plugin Build\n{_summarize_output('gstreamer_plugin_build', out)}"
)


# ===================================================================
Expand All @@ -1291,7 +1314,7 @@ def install_dependencies() -> str:
"bash .github/scripts/setup_environment.sh",
timeout=300,
)
return f"## Install Dependencies\n{out}"
return f"## Install Dependencies\n{_summarize_output('install_dependencies', out)}"


# ===================================================================
Expand All @@ -1316,7 +1339,7 @@ def build_ebpf_xdp() -> str:
"bash .github/scripts/setup_environment.sh",
timeout=300,
)
return f"## eBPF/XDP Build\n{out}"
return f"## eBPF/XDP Build\n{_summarize_output('ebpf_xdp_build', out)}"


# ===================================================================
Expand Down Expand Up @@ -1495,14 +1518,8 @@ def run_gtest(
if failures:
summary += "\n### Failed Tests\n" + "\n".join(f"- {f}" for f in failures)

# Save full log
log_path = _save_test_log("gtest", out)
summary += f"\n- Full log: `{log_path}`"
summary += sim_hint

# Include last 40 lines for context
tail = "\n".join(out.splitlines()[-40:])
return f"{summary}\n\n### Output (last 40 lines)\n```\n{tail}\n```"
return f"{summary}\n\n{_summarize_output('gtest', out)}"


@mcp.tool()
Expand Down
13 changes: 12 additions & 1 deletion doc/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,18 @@ st40p_rx_put_frame
```

- **TX pipeline (`st40p_tx_*`)** wraps the lower-level `st40_tx_*` APIs while honoring redundancy, user timestamps, and user-managed frame buffers (for example when `ST40P_TX_FLAG_EXT_FRAME` is enabled). The helper also exposes the maximum user data word (UDW) size via `st40p_tx_max_udw_buff_size()` so that producers can pack metadata deterministically.
- **RX pipeline (`st40p_rx_*`)** assembles ancillary packets into user buffers and can optionally dump metadata/UDW buffers directly to files for debugging through `st40p_rx_set_dump`. Each RX callback receives both the metadata blocks and the captured payload, mirroring the TX layout to simplify round-trip validation.
- **RX pipeline (`st40p_rx_*`)** is a thin shim over the **FRAME_LEVEL transport**
(`ST40_TYPE_FRAME_LEVEL` on `st40_rx_ops`). The transport owns the frame-buffer pool,
parses RFC 8331 packets, and assembles full frames using a 64-bit per-frame
**session-merged sequence bitmap** that natively absorbs cross-port redundancy.
Once a frame is complete the transport invokes
`notify_frame_ready(priv, addr, struct st40_rx_frame_meta*)`; the pipeline records
the buffer pointer plus all stats (timestamps, per-port and session-level
`seq_lost`/`seq_discont`, `pkts_total`, `pkts_recv[]`, `status`) into its slot —
zero-copy. The app returns ownership through `st40p_rx_put_frame`, which calls
`st40_rx_put_framebuff(transport, addr)` under the hood. Apps that still want raw
RTP can fall back to `ST40_TYPE_RTP_LEVEL` and pull mbufs via
`st40_rx_get_mbuf`/`st40_rx_put_mbuf`.

Split-mode ancillary (packet-split) is supported via `tx_split_anc_by_pkt` on TX and
`rx_rtp_ring_size` plus `frame_info_path` on RX. Enable `tx_split_anc_by_pkt=true`
Expand Down
12 changes: 12 additions & 0 deletions doc/stats_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,18 @@ ST40 anc) a fully-lost frame is absorbed — the delivered frame count drops
rather than emitting an extra incomplete frame — so under heavy contiguous
loss `stat_frames_corrupted` is a lower bound on frame-integrity impact.

> **ST40p RX seq stats are bitmap-derived (transport-side).** The
> `seq_lost` / `seq_discont` fields on `st40_frame_info` (and the underlying
> `st40_rx_frame_meta`) are computed in the RX session from a per-frame
> 64-bit **session-merged** sequence bitmap. Each accepted packet (after
> the per-port redundancy filter) flips its bit; at marker time
> `seq_lost = (max_offset + 1) - popcount(bitmap)`. Packets that arrive on
> the redundant port and fill a gap left by the primary port flip the same
> bit, so cross-port redundancy is reflected directly in the count — there
> is no false positive `seq_discont` from cross-port reorder. The
> `port_seq_lost[]` / `port_seq_discont[]` arrays still report the per-port
> intra-frame view for observability.

**TX (any type).** `stat_epoch_drop` = app handed frame too late, slots were
skipped (counter advanced by the number of skipped slots).
`stat_epoch_onward` = system clock ran past `max_onward_epochs` ahead of the
Expand Down
13 changes: 3 additions & 10 deletions ecosystem/gstreamer_plugin/gst_mtl_st40_rx.c
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,8 @@ static void* gst_mtl_st40_rx_get_mbuf_with_timeout(Gst_Mtl_St40_Rx* src,

static struct st40_rfc8331_payload_hdr* gst_mtl_st40_rx_shift_payload_hdr(
struct st40_rfc8331_payload_hdr* payload_hdr, int udw_size) {
gint package_size;
gint payload_len;

package_size = ((WORD_10_BIT_ALIGN + udw_size) * USER_DATA_WORD_BIT_SIZE) / BYTE_SIZE;
payload_len = sizeof(struct st40_rfc8331_payload_hdr) -
(package_size % WORD_10_BIT_ALIGN) + package_size;

return (struct st40_rfc8331_payload_hdr*)((uint8_t*)payload_hdr + payload_len);
return (struct st40_rfc8331_payload_hdr*)((uint8_t*)payload_hdr +
st40_rfc8331_payload_bytes(udw_size));
}

static GstFlowReturn gst_mtl_st40_rx_check_parity(
Expand Down Expand Up @@ -446,8 +440,7 @@ static GstFlowReturn gst_mtl_st40_rx_fill_buffer(Gst_Mtl_St40_Rx* src, GstBuffer

payload_hdr = (struct st40_rfc8331_payload_hdr*)(&hdr[1]);
for (int i = 0; i < anc_count; i++) {
payload_hdr->swapped_first_hdr_chunk = ntohl(payload_hdr->swapped_first_hdr_chunk);
payload_hdr->swapped_second_hdr_chunk = ntohl(payload_hdr->swapped_second_hdr_chunk);
st40_rfc8331_payload_hdr_bswap(payload_hdr);
if (gst_mtl_st40_rx_check_parity(payload_hdr) != GST_FLOW_OK) {
for (int j = 0; j < i; j++) free(anc_data[j]);
return GST_FLOW_ERROR;
Expand Down
4 changes: 0 additions & 4 deletions ecosystem/gstreamer_plugin/gst_mtl_st40_rx.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@

#define IS_POWER_OF_2(x) (((x) & ((x)-1)) == 0)
#define ST40_RFC8331_PAYLOAD_MAX_ANCILLARY_COUNT 20
#define USER_DATA_WORD_BIT_SIZE 10
#define BYTE_SIZE 8
#define OFFSET_BIT 32
#define WORD_10_BIT_ALIGN ((OFFSET_BIT / USER_DATA_WORD_BIT_SIZE) + 1)

/**
* ST40_SIZE_OF_PAYLOAD_METADATA:
Expand Down
6 changes: 2 additions & 4 deletions ecosystem/gstreamer_plugin/gst_mtl_st40p_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -651,10 +651,8 @@ static GstFlowReturn gst_mtl_st40p_tx_parse_8331_anc_words(
payload_cursor = (uint8_t*)map_info.data + (buffer_size - bytes_left_to_process);

rfc8331_meta.headers[i] = (struct st40_rfc8331_payload_hdr*)payload_cursor;
payload_header.swapped_first_hdr_chunk =
ntohl(rfc8331_meta.headers[i]->swapped_first_hdr_chunk);
payload_header.swapped_second_hdr_chunk =
ntohl(rfc8331_meta.headers[i]->swapped_second_hdr_chunk);
payload_header = *rfc8331_meta.headers[i];
st40_rfc8331_payload_hdr_bswap(&payload_header);

payload_cursor = (uint8_t*)&rfc8331_meta.headers[i]->swapped_second_hdr_chunk;
/*
Expand Down
Loading
Loading