diff --git a/src/request.c b/src/request.c index 94e948e..911f1cd 100644 --- a/src/request.c +++ b/src/request.c @@ -41,6 +41,7 @@ #include "v4l2.h" #include +#include #include #include @@ -51,8 +52,137 @@ #include +#include #include +/* + * fresnel-fourier iter4 Phase 6 commit Z: device-path auto-detect via media controller topology + * + * 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. + * + * 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): + * + * 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). + * + * 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. + * + * Escape hatch: LIBVA_V4L2_REQUEST_NO_AUTODETECT=1 reverts to legacy + * hardcoded /dev/video0 + /dev/media0 for callers that relied on it. + */ +static const char * const known_decoder_drivers[] = { + "rkvdec", + "hantro-vpu", + "cedrus", + "sun4i_csi", + NULL +}; + +static int resolve_dev_node(uint32_t major, uint32_t minor, char *out, size_t out_sz) +{ + char sysfs_path[64], target[256]; + ssize_t n; + const char *base; + + snprintf(sysfs_path, sizeof sysfs_path, "/sys/dev/char/%u:%u", major, minor); + n = readlink(sysfs_path, target, sizeof target - 1); + if (n < 0) + return -1; + target[n] = '\0'; + base = strrchr(target, '/'); + base = base ? base + 1 : target; + snprintf(out, out_sz, "/dev/%s", base); + return 0; +} + +static int find_video_node_via_topology(int media_fd, char *video_out, size_t video_out_sz) +{ + struct media_v2_topology topo; + struct media_v2_interface *interfaces = NULL; + int ret = -1; + unsigned int i; + + memset(&topo, 0, sizeof topo); + if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topo) < 0) + return -1; + if (topo.num_interfaces == 0) + return -1; + + 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) + goto out; + topo.ptr_interfaces = (uintptr_t)interfaces; + 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) + continue; + if (resolve_dev_node(interfaces[i].devnode.major, + interfaces[i].devnode.minor, + video_out, video_out_sz) == 0) { + ret = 0; + break; + } + } +out: + free(interfaces); + return ret; +} + +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; + + 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; + } + } + close(fd); + } + return -1; +} + /* Set default visibility for the init function only. */ VAStatus __attribute__((visibility("default"))) VA_DRIVER_INIT_FUNC(VADriverContextP context); @@ -146,9 +276,23 @@ VAStatus VA_DRIVER_INIT_FUNC(VADriverContextP context) object_heap_init(&driver_data->image_heap, sizeof(struct object_image), IMAGE_ID_OFFSET); + static char auto_video[32], auto_media[32]; + bool auto_media_set = false; + video_path = getenv("LIBVA_V4L2_REQUEST_VIDEO_PATH"); - if (video_path == NULL) - video_path = "/dev/video0"; + if (video_path == NULL) { + if (getenv("LIBVA_V4L2_REQUEST_NO_AUTODETECT")) { + video_path = "/dev/video0"; + } else if (find_codec_device(auto_video, sizeof auto_video, + auto_media, sizeof auto_media) == 0) { + video_path = auto_video; + auto_media_set = true; + request_log("auto-selected codec device: %s + %s\n", + auto_video, auto_media); + } else { + video_path = "/dev/video0"; + } + } video_fd = open(video_path, O_RDWR | O_NONBLOCK); if (video_fd < 0) @@ -169,8 +313,12 @@ VAStatus VA_DRIVER_INIT_FUNC(VADriverContextP context) } media_path = getenv("LIBVA_V4L2_REQUEST_MEDIA_PATH"); - if (media_path == NULL) - media_path = "/dev/media0"; + if (media_path == NULL) { + if (auto_media_set) + media_path = auto_media; + else + media_path = "/dev/media0"; + } media_fd = open(media_path, O_RDWR | O_NONBLOCK); if (media_fd < 0)