fresnel-fourier iter7 Phase 7 fix-forward: data links connect pads not entities directly

Empirical Phase 7 verification revealed the algorithm bug: data links
in MEDIA_IOC_G_TOPOLOGY connect PAD IDs, not entity IDs directly.
My iter7 Phase 6 commit compared link source_id/sink_id against
the proc entity_id, never matched → io_entity_ids stayed empty →
interface lookup never fired → returns -1 → falls back to legacy
hardcoded path.

Topology dump on fresnel /dev/media0 (rkvdec) confirmed:
- Entity 3 (rkvdec-proc) has function=0x4008 (DECODER) ✓
- Data link src=16777218 sink=16777220 — these are PAD ids
  (0x01000002, 0x01000004), NOT entity 3.
- Interface link src=50331660 (interface) sink=1 (entity) — for
  interface links source/sink ARE entity IDs.

Fix: resolve pads → entities via the topo.pads[] array.
1. Collect pads belonging to proc entity (via pads[].entity_id).
2. For each data link touching those pads, the OTHER pad's
   entity_id is an IO neighbor.
3. Find interface link to those IO entities (unchanged from prev).

Also allocate topo.pads[] in the 2-call ioctl pattern.

Signed-off-by: claude-noether <claude-noether@reauktion.de>
This commit is contained in:
claude-noether
2026-05-13 11:00:20 +00:00
parent c106d95869
commit 6df2159dd3
2 changed files with 57 additions and 22 deletions
+44 -11
View File
@@ -140,6 +140,7 @@ static int find_decoder_video_node_via_topology(int media_fd,
struct media_v2_entity *entities = NULL; struct media_v2_entity *entities = NULL;
struct media_v2_interface *interfaces = NULL; struct media_v2_interface *interfaces = NULL;
struct media_v2_link *links = NULL; struct media_v2_link *links = NULL;
struct media_v2_pad *pads = NULL;
int ret = -1; int ret = -1;
unsigned int i, j; unsigned int i, j;
@@ -147,50 +148,81 @@ static int find_decoder_video_node_via_topology(int media_fd,
if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topo) < 0) if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topo) < 0)
return -1; return -1;
if (topo.num_entities == 0 || topo.num_interfaces == 0 || if (topo.num_entities == 0 || topo.num_interfaces == 0 ||
topo.num_links == 0) topo.num_links == 0 || topo.num_pads == 0)
return -1; return -1;
entities = calloc(topo.num_entities, sizeof *entities); entities = calloc(topo.num_entities, sizeof *entities);
interfaces = calloc(topo.num_interfaces, sizeof *interfaces); interfaces = calloc(topo.num_interfaces, sizeof *interfaces);
links = calloc(topo.num_links, sizeof *links); links = calloc(topo.num_links, sizeof *links);
if (!entities || !interfaces || !links) pads = calloc(topo.num_pads, sizeof *pads);
if (!entities || !interfaces || !links || !pads)
goto out; goto out;
topo.ptr_entities = (uintptr_t)entities; topo.ptr_entities = (uintptr_t)entities;
topo.ptr_interfaces = (uintptr_t)interfaces; topo.ptr_interfaces = (uintptr_t)interfaces;
topo.ptr_links = (uintptr_t)links; topo.ptr_links = (uintptr_t)links;
topo.ptr_pads = (uintptr_t)pads;
if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topo) < 0) if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topo) < 0)
goto out; goto out;
for (i = 0; i < topo.num_entities; i++) { for (i = 0; i < topo.num_entities; i++) {
uint32_t proc_id; uint32_t proc_id;
uint32_t proc_pad_ids[16];
uint32_t io_entity_ids[16]; uint32_t io_entity_ids[16];
unsigned int proc_pad_count = 0;
unsigned int io_count = 0; unsigned int io_count = 0;
if (entities[i].function != MEDIA_ENT_F_PROC_VIDEO_DECODER) if (entities[i].function != MEDIA_ENT_F_PROC_VIDEO_DECODER)
continue; continue;
proc_id = entities[i].id; proc_id = entities[i].id;
/* Step 2: collect data-link neighbors of the proc entity. */ /* Step 2a: collect pads belonging to the proc entity. Data
* links connect PADs, not entities directly. */
for (j = 0; j < topo.num_pads; j++) {
if (pads[j].entity_id != proc_id)
continue;
if (proc_pad_count < (sizeof proc_pad_ids /
sizeof proc_pad_ids[0]))
proc_pad_ids[proc_pad_count++] = pads[j].id;
}
/* Step 2b: walk data links. For each link with either endpoint
* in proc_pad_ids[], the other endpoint is a pad belonging to
* an IO neighbor. Resolve that pad's entity_id via pads[]. */
for (j = 0; j < topo.num_links; j++) { for (j = 0; j < topo.num_links; j++) {
uint32_t other; uint32_t other_pad = 0;
unsigned int k;
if (links[j].flags & MEDIA_LNK_FL_INTERFACE_LINK) if (links[j].flags & MEDIA_LNK_FL_INTERFACE_LINK)
continue; continue;
if (links[j].source_id == proc_id) for (k = 0; k < proc_pad_count; k++) {
other = links[j].sink_id; if (links[j].source_id == proc_pad_ids[k])
else if (links[j].sink_id == proc_id) other_pad = links[j].sink_id;
other = links[j].source_id; else if (links[j].sink_id == proc_pad_ids[k])
else other_pad = links[j].source_id;
if (other_pad != 0)
break;
}
if (other_pad == 0)
continue;
/* Resolve other_pad to its entity_id. */
for (k = 0; k < topo.num_pads; k++) {
if (pads[k].id != other_pad)
continue; continue;
if (io_count < (sizeof io_entity_ids / if (io_count < (sizeof io_entity_ids /
sizeof io_entity_ids[0])) sizeof io_entity_ids[0]))
io_entity_ids[io_count++] = other; io_entity_ids[io_count++] =
pads[k].entity_id;
break;
}
} }
/* Step 3-4: find an interface link from any IO entity neighbor; /* Step 3-4: find an interface link from any IO entity neighbor;
* resolve devnode for the linked V4L_VIDEO interface. */ * resolve devnode for the linked V4L_VIDEO interface.
* Interface links connect interfaces↔entities directly (not
* via pads), so source_id/sink_id is an entity ID on one side
* and an interface ID on the other. */
for (j = 0; j < topo.num_links; j++) { for (j = 0; j < topo.num_links; j++) {
uint32_t intf_id = 0; uint32_t intf_id = 0;
unsigned int k; unsigned int k;
@@ -230,6 +262,7 @@ out:
free(entities); free(entities);
free(interfaces); free(interfaces);
free(links); free(links);
free(pads);
return ret; return ret;
} }
+11 -9
View File
@@ -171,17 +171,19 @@ run_consumer() {
sleep 1 sleep 1
# Parse pidstat by header: locate the %CPU column index from the # Parse pidstat by header: locate the %CPU column index from the
# column-name row, then apply it to data rows. Robust across # column-name row (where any field equals "%CPU"), then apply it
# sysstat 12.x point releases (where column positions shift). # to data rows. Robust across sysstat 12.x point releases.
# pidstat default output has no '#' header marker — the header is
# the first row containing "%CPU" as a field.
awk ' awk '
# Header row: find which field is %CPU. # Header row: any line where some field equals "%CPU".
$1 == "#" { !col {
for (i = 1; i <= NF; i++) if ($i == "%CPU") col = i for (i = 1; i <= NF; i++) if ($i == "%CPU") { col = i; next }
next
} }
# Data row: skip the average summary at end + blank lines. # Data row: lines whose value at $col is numeric. Skip the
col && NF >= col && $1 ~ /^[0-9]/ { # trailing "Average" summary by requiring $col to parse cleanly.
if ($col ~ /^[0-9]+(\.[0-9]+)?$/) print $col col && NF >= col && $col ~ /^[0-9]+(\.[0-9]+)?$/ {
print $col
} }
' "$logdir/pidstat.log" >"$logdir/cpu_pct.log" || true ' "$logdir/pidstat.log" >"$logdir/cpu_pct.log" || true