From c106d9586996659ce24f60fb02f3feba7d21f24d Mon Sep 17 00:00:00 2001 From: claude-noether Date: Wed, 13 May 2026 09:38:54 +0000 Subject: [PATCH] fresnel-fourier iter7 Phase 6: auto-detect with decoder-entity discrimination (B1a) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor request.c::find_video_node_via_topology to find_decoder_video_node_via_topology — walks media-topology entities looking for MEDIA_ENT_F_PROC_VIDEO_DECODER function, then follows the kernel's link graph (data link from proc to IO entity, interface link from IO entity to V4L_VIDEO interface) to the correct /dev/videoN. Two-pass find_codec_device: pass 1 accepts only "rkvdec" (multi-codec decoder, 3 of 5 codecs); pass 2 accepts any known_decoder_drivers entry. Pre-iter7 the walk picked whichever media device matched the hantro-vpu driver name first — which on RK3399 could be the encoder half of the same media device, surfacing as an empty profile list. Phase 5 amendments incorporated: - CRIT-1: use MEDIA_LNK_FL_INTERFACE_LINK (1U<<28) to discriminate interface vs data links. - CRIT-2: check both source_id and sink_id of each link. - IMP-3: 2-call MEDIA_IOC_G_TOPOLOGY pattern (allocate all 3 arrays before second call); pre-iter7 had a spurious memset + third call. iter4-B1b (multi-decoder routing — open BOTH rkvdec AND hantro from one backend instance) still deferred. Post-iter7 MPEG-2/VP8 (hantro) still need LIBVA_V4L2_REQUEST_VIDEO_PATH override. Signed-off-by: claude-noether --- src/request.c | 222 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 165 insertions(+), 57 deletions(-) diff --git a/src/request.c b/src/request.c index 911f1cd..f7b5154 100644 --- a/src/request.c +++ b/src/request.c @@ -56,33 +56,32 @@ #include /* - * fresnel-fourier iter4 Phase 6 commit Z: device-path auto-detect via media controller topology + * fresnel-fourier iter4 Phase 6 commit Z + iter7 Phase 6 (B1a): device-path + * auto-detect via media controller topology with decoder-entity discrimination. * - * Pre-iter4 the backend hardcoded /dev/video0 + /dev/media0 as defaults - * when no env override was set. On Linux 7.0 the udev/probe order - * changed and rockchip-rga (an RGB color converter, no codec support) - * now claims /dev/video0 — the legacy default returns an empty profile - * list. + * Pre-iter4 the backend hardcoded /dev/video0 + /dev/media0. On Linux 7.0 the + * udev/probe order changed and rockchip-rga (an RGB color converter, no codec + * support) now claims /dev/video0 — the legacy default returns an empty + * profile list. iter4 commit Z replaced enumeration-order discovery with + * media-topology discovery. * - * Discovery is driven by the media controller graph (NOT by walking - * /dev/video* in enumeration order — that approach can mispair the - * video and media nodes when one driver registers multiple media - * devices, and depends on probe-order luck): + * iter7 (B1a): the iter4 walk treated the hantro-vpu driver name as a single + * unit, but hantro-vpu registers BOTH encoder and decoder entities under one + * /dev/mediaN on RK3399. iter4's "pick the first V4L_VIDEO interface" could + * land on the encoder. iter7 walks ENTITIES looking for + * MEDIA_ENT_F_PROC_VIDEO_DECODER, then follows the kernel's link graph + * (data link from proc to IO entity, interface link from IO entity to V4L + * interface) to the correct /dev/videoN. * - * 1. Walk /dev/media0..N. For each, MEDIA_IOC_DEVICE_INFO names the - * driver. Match against the known-decoder list. - * 2. MEDIA_IOC_G_TOPOLOGY returns the entity/interface graph. The - * MEDIA_INTF_T_V4L_VIDEO interface entries carry major:minor of - * the V4L2 video node owned by THIS media controller — guaranteed - * paired by the kernel. - * 3. Resolve major:minor to /dev/videoN via /sys/dev/char/: - * (the kernel's char-device sysfs symlink whose basename is the - * device node name). + * Two-pass to prefer rkvdec: pass 1 accepts only "rkvdec" (multi-codec + * decoder, 3 of 5 codecs); pass 2 accepts any known decoder driver. On + * RK3399 this makes auto-detect always pick rkvdec when available. * - * Phase 5 C4: walk picks rkvdec on RK3399 (rkvdec's media controller - * enumerates before hantro's). MPEG-2/VP8 (hantro) still need explicit - * LIBVA_V4L2_REQUEST_VIDEO_PATH override; full multi-decoder dispatch - * is iter4-B1 backlog. + * iter4-B1b (multi-decoder routing — open BOTH rkvdec AND hantro from one + * backend instance, dispatch per codec) is still deferred. Post-iter7 the + * backend opens one decoder per process; MPEG-2/VP8 (hantro) still need + * explicit LIBVA_V4L2_REQUEST_VIDEO_PATH override when iter7's pass-1 + * lands on rkvdec. * * Escape hatch: LIBVA_V4L2_REQUEST_NO_AUTODETECT=1 reverts to legacy * hardcoded /dev/video0 + /dev/media0 for callers that relied on it. @@ -112,73 +111,182 @@ static int resolve_dev_node(uint32_t major, uint32_t minor, char *out, size_t ou return 0; } -static int find_video_node_via_topology(int media_fd, char *video_out, size_t video_out_sz) +/* + * iter7 B1a: walk topology graph from decoder-proc entity to its V4L_VIDEO + * interface. Returns 0 + sets video_out on success, -1 if this media device + * has no decoder entity (e.g. encoder-only device). + * + * Algorithm (per Phase 5 review, empirically validated against + * boltzmann:~/src/linux-rockchip): + * 1. For each entity E with function == MEDIA_ENT_F_PROC_VIDEO_DECODER: + * 2. Find IO entity neighbors via DATA links (entity↔entity). + * 3. Find the V4L_VIDEO interface via INTERFACE links from those IO + * neighbors. + * 4. Resolve interface.devnode.major:minor to /dev/videoN. + * + * Two-call MEDIA_IOC_G_TOPOLOGY pattern (Phase 5 IMP-3): first call gets + * counts; second call fills the three arrays after we allocate them. + * + * Link discrimination via MEDIA_LNK_FL_INTERFACE_LINK (1U<<28): + * data links have flags & MEDIA_LNK_FL_INTERFACE_LINK == 0; interface + * links have it set. source_id/sink_id ordering is not guaranteed — + * check both endpoints. + */ +static int find_decoder_video_node_via_topology(int media_fd, + char *video_out, + size_t video_out_sz) { struct media_v2_topology topo; + struct media_v2_entity *entities = NULL; struct media_v2_interface *interfaces = NULL; + struct media_v2_link *links = NULL; int ret = -1; - unsigned int i; + unsigned int i, j; memset(&topo, 0, sizeof topo); if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topo) < 0) return -1; - if (topo.num_interfaces == 0) + if (topo.num_entities == 0 || topo.num_interfaces == 0 || + topo.num_links == 0) return -1; + entities = calloc(topo.num_entities, sizeof *entities); interfaces = calloc(topo.num_interfaces, sizeof *interfaces); - if (!interfaces) - return -1; - - memset(&topo, 0, sizeof topo); - if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topo) < 0) + links = calloc(topo.num_links, sizeof *links); + if (!entities || !interfaces || !links) goto out; + + topo.ptr_entities = (uintptr_t)entities; topo.ptr_interfaces = (uintptr_t)interfaces; + topo.ptr_links = (uintptr_t)links; + if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topo) < 0) goto out; - for (i = 0; i < topo.num_interfaces; i++) { - if (interfaces[i].intf_type != MEDIA_INTF_T_V4L_VIDEO) + for (i = 0; i < topo.num_entities; i++) { + uint32_t proc_id; + uint32_t io_entity_ids[16]; + unsigned int io_count = 0; + + if (entities[i].function != MEDIA_ENT_F_PROC_VIDEO_DECODER) continue; - if (resolve_dev_node(interfaces[i].devnode.major, - interfaces[i].devnode.minor, - video_out, video_out_sz) == 0) { - ret = 0; - break; + proc_id = entities[i].id; + + /* Step 2: collect data-link neighbors of the proc entity. */ + for (j = 0; j < topo.num_links; j++) { + uint32_t other; + + if (links[j].flags & MEDIA_LNK_FL_INTERFACE_LINK) + continue; + if (links[j].source_id == proc_id) + other = links[j].sink_id; + else if (links[j].sink_id == proc_id) + other = links[j].source_id; + else + continue; + if (io_count < (sizeof io_entity_ids / + sizeof io_entity_ids[0])) + io_entity_ids[io_count++] = other; + } + + /* Step 3-4: find an interface link from any IO entity neighbor; + * resolve devnode for the linked V4L_VIDEO interface. */ + for (j = 0; j < topo.num_links; j++) { + uint32_t intf_id = 0; + unsigned int k; + + if (!(links[j].flags & MEDIA_LNK_FL_INTERFACE_LINK)) + continue; + for (k = 0; k < io_count; k++) { + if (links[j].source_id == io_entity_ids[k]) + intf_id = links[j].sink_id; + else if (links[j].sink_id == io_entity_ids[k]) + intf_id = links[j].source_id; + if (intf_id != 0) + break; + } + if (intf_id == 0) + continue; + + for (k = 0; k < topo.num_interfaces; k++) { + if (interfaces[k].id != intf_id) + continue; + if (interfaces[k].intf_type != + MEDIA_INTF_T_V4L_VIDEO) + break; + if (resolve_dev_node( + interfaces[k].devnode.major, + interfaces[k].devnode.minor, + video_out, video_out_sz) == 0) + ret = 0; + break; + } + if (ret == 0) + goto out; } } + out: + free(entities); free(interfaces); + free(links); return ret; } +/* + * iter7 B1a: two-pass walk of /dev/media0..N. Pass 1 accepts only "rkvdec" + * (multi-codec decoder serving 3 of 5 codecs). Pass 2 accepts any + * known_decoder_drivers entry. Within each pass, the chosen media device + * must ALSO contain at least one MEDIA_ENT_F_PROC_VIDEO_DECODER entity — + * guards against encoder-only devices that happen to share the same driver + * name (e.g. hantro-vpu encoder vs decoder inside one /dev/mediaN). + */ static int find_codec_device(char *video_out, size_t video_out_sz, char *media_out, size_t media_out_sz) { struct media_device_info info; char path[32]; const char * const *kd; - int fd, i; + int fd, i, pass; - for (i = 0; i < 16; i++) { - snprintf(path, sizeof path, "/dev/media%d", i); - fd = open(path, O_RDWR | O_NONBLOCK); - if (fd < 0) - continue; - memset(&info, 0, sizeof info); - if (ioctl(fd, MEDIA_IOC_DEVICE_INFO, &info) == 0) { - for (kd = known_decoder_drivers; *kd; kd++) { - if (strcmp(info.driver, *kd) != 0) - continue; - if (find_video_node_via_topology(fd, video_out, - video_out_sz) == 0) { - snprintf(media_out, media_out_sz, "%s", path); - close(fd); - return 0; - } - break; + for (pass = 0; pass < 2; pass++) { + for (i = 0; i < 16; i++) { + bool match; + + snprintf(path, sizeof path, "/dev/media%d", i); + fd = open(path, O_RDWR | O_NONBLOCK); + if (fd < 0) + continue; + memset(&info, 0, sizeof info); + if (ioctl(fd, MEDIA_IOC_DEVICE_INFO, &info) != 0) { + close(fd); + continue; } + if (pass == 0) { + /* Pass 1: rkvdec only. */ + match = (strcmp(info.driver, "rkvdec") == 0); + } else { + /* Pass 2: any known decoder driver. */ + match = false; + for (kd = known_decoder_drivers; *kd; kd++) { + if (strcmp(info.driver, *kd) == 0) { + match = true; + break; + } + } + } + if (!match) { + close(fd); + continue; + } + if (find_decoder_video_node_via_topology( + fd, video_out, video_out_sz) == 0) { + snprintf(media_out, media_out_sz, "%s", path); + close(fd); + return 0; + } + close(fd); } - close(fd); } return -1; }