| // SPDX-License-Identifier: LGPL-2.1-only |
| /* |
| * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. |
| */ |
| |
| #include <cstring> |
| #include <fstream> |
| #include <iostream> |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/sysmacros.h> |
| #include <unistd.h> |
| |
| #include <linux/media.h> |
| |
| #include <media-info.h> |
| #ifndef __linux__ |
| #include <linux/videodev2.h> |
| #include <fcntl.h> |
| #endif |
| |
| static std::string num2s(unsigned num, bool is_hex = true) |
| { |
| char buf[16]; |
| |
| if (is_hex) |
| sprintf(buf, "%08x", num); |
| else |
| sprintf(buf, "%u", num); |
| return buf; |
| } |
| |
| static constexpr struct { |
| const char *devname; |
| enum media_type type; |
| } media_types[] = { |
| { "video", MEDIA_TYPE_VIDEO }, |
| { "vbi", MEDIA_TYPE_VBI }, |
| { "radio", MEDIA_TYPE_RADIO }, |
| { "swradio", MEDIA_TYPE_SDR }, |
| { "v4l-subdev", MEDIA_TYPE_SUBDEV }, |
| { "v4l-touch", MEDIA_TYPE_TOUCH }, |
| { "media", MEDIA_TYPE_MEDIA }, |
| { "frontend", MEDIA_TYPE_DVB_FRONTEND }, |
| { "demux", MEDIA_TYPE_DVB_DEMUX }, |
| { "dvr", MEDIA_TYPE_DVB_DVR }, |
| { "net", MEDIA_TYPE_DVB_NET }, |
| { "ca", MEDIA_TYPE_DTV_CA }, |
| { nullptr, MEDIA_TYPE_UNKNOWN } |
| }; |
| |
| media_type mi_media_detect_type(const char *device) |
| { |
| struct stat sb; |
| |
| if (stat(device, &sb) == -1) |
| return MEDIA_TYPE_CANT_STAT; |
| #ifdef __linux__ |
| std::string uevent_path("/sys/dev/char/"); |
| |
| uevent_path += num2s(major(sb.st_rdev), false) + ":" + |
| num2s(minor(sb.st_rdev), false) + "/uevent"; |
| |
| std::ifstream uevent_file(uevent_path.c_str()); |
| if (uevent_file.fail()) |
| return MEDIA_TYPE_UNKNOWN; |
| |
| std::string line; |
| |
| while (std::getline(uevent_file, line)) { |
| if (line.compare(0, 8, "DEVNAME=")) |
| continue; |
| |
| line.erase(0, 8); |
| if (!line.compare(0, 11, "dvb/adapter")) { |
| line.erase(0, 11); |
| unsigned len = 0; |
| while (line[len] && line[len] != '/') |
| len++; |
| line.erase(0, len + 1); |
| } |
| for (size_t i = 0; media_types[i].devname; i++) { |
| const char *devname = media_types[i].devname; |
| size_t len = strlen(devname); |
| |
| if (!line.compare(0, len, devname) && isdigit(line[0+len])) { |
| uevent_file.close(); |
| return media_types[i].type; |
| } |
| } |
| } |
| |
| uevent_file.close(); |
| #else // Not Linux |
| int fd = open(device, O_RDONLY); |
| if (fd >= 0) { |
| struct v4l2_capability caps; |
| int error = ioctl(fd, VIDIOC_QUERYCAP, &caps); |
| close(fd); |
| if (error == 0) { |
| if (caps.device_caps & V4L2_CAP_VIDEO_CAPTURE) { |
| return MEDIA_TYPE_VIDEO; |
| } else if (caps.device_caps & V4L2_CAP_VBI_CAPTURE) { |
| return MEDIA_TYPE_VBI; |
| } else if (caps.device_caps & V4L2_CAP_RADIO) { |
| return MEDIA_TYPE_RADIO; |
| } |
| } |
| } |
| #endif |
| return MEDIA_TYPE_UNKNOWN; |
| } |
| |
| std::string mi_media_get_device(__u32 major, __u32 minor) |
| { |
| char fmt[32]; |
| std::string uevent_path("/sys/dev/char/"); |
| |
| sprintf(fmt, "%d:%d", major, minor); |
| uevent_path += std::string(fmt) + "/uevent"; |
| |
| std::ifstream uevent_file(uevent_path.c_str()); |
| if (uevent_file.fail()) |
| return ""; |
| |
| std::string line; |
| |
| while (std::getline(uevent_file, line)) { |
| if (line.compare(0, 8, "DEVNAME=")) |
| continue; |
| uevent_file.close(); |
| return std::string("/dev/") + &line[8]; |
| } |
| |
| uevent_file.close(); |
| return ""; |
| } |
| |
| struct flag_def { |
| unsigned flag; |
| const char *str; |
| }; |
| |
| static std::string flags2s(unsigned val, const flag_def *def) |
| { |
| std::string s; |
| |
| while (def->flag) { |
| if (val & def->flag) { |
| if (s.length()) s += ", "; |
| s += def->str; |
| val &= ~def->flag; |
| } |
| def++; |
| } |
| if (val) { |
| if (s.length()) s += ", "; |
| s += num2s(val); |
| } |
| return s; |
| } |
| |
| int mi_get_dev_t_from_fd(int fd, dev_t *dev) |
| { |
| struct stat sb; |
| |
| if (fstat(fd, &sb) == -1) { |
| fprintf(stderr, "failed to stat file\n"); |
| return -1; |
| } |
| *dev = sb.st_rdev; |
| return 0; |
| } |
| |
| std::string mi_get_devpath_from_dev_t(dev_t dev) |
| { |
| if (!dev) |
| return ""; |
| |
| std::string media_uevent_path("/sys/dev/char/"); |
| |
| media_uevent_path += num2s(major(dev), false) + ":" + |
| num2s(minor(dev), false) + "/uevent"; |
| |
| FILE *uevent_fd = fopen(media_uevent_path.c_str(), "r"); |
| |
| if (uevent_fd == nullptr) { |
| fprintf(stderr, "failed to open %s\n", media_uevent_path.c_str()); |
| return ""; |
| } |
| |
| char *line = nullptr; |
| size_t size = 0; |
| std::string devpath; |
| |
| for (;;) { |
| ssize_t bytes = getline(&line, &size, uevent_fd); |
| |
| if (bytes <= 0) |
| break; |
| line[bytes - 1] = 0; |
| if (memcmp(line, "DEVNAME=", 8) || !line[8]) |
| continue; |
| devpath = "/dev/"; |
| devpath += line + 8; |
| break; |
| } |
| free(line); |
| |
| if (devpath.empty()) |
| fprintf(stderr, "failed to find DEVNAME in %s\n", media_uevent_path.c_str()); |
| return devpath; |
| } |
| |
| int mi_get_media_fd(int fd, const char *bus_info) |
| { |
| int media_fd = -1; |
| dev_t dev; |
| |
| if (mi_get_dev_t_from_fd(fd, &dev) < 0) |
| return -1; |
| |
| std::string media_path("/sys/dev/char/"); |
| |
| media_path += num2s(major(dev), false) + ":" + |
| num2s(minor(dev), false) + "/device"; |
| |
| DIR *dp; |
| struct dirent *ep; |
| dp = opendir(media_path.c_str()); |
| if (dp == nullptr) |
| return -1; |
| media_path[0] = 0; |
| while ((ep = readdir(dp))) { |
| if (!memcmp(ep->d_name, "media", 5) && isdigit(ep->d_name[5])) { |
| struct media_device_info mdinfo; |
| std::string devname("/dev/"); |
| |
| devname += ep->d_name; |
| media_fd = open(devname.c_str(), O_RDWR); |
| |
| if (bus_info && |
| (ioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &mdinfo) || |
| strcmp(mdinfo.bus_info, bus_info))) { |
| close(media_fd); |
| continue; |
| } |
| break; |
| } |
| } |
| closedir(dp); |
| return media_fd; |
| } |
| |
| static constexpr flag_def entity_flags_def[] = { |
| { MEDIA_ENT_FL_DEFAULT, "default" }, |
| { MEDIA_ENT_FL_CONNECTOR, "connector" }, |
| { 0, nullptr } |
| }; |
| |
| std::string mi_entflags2s(__u32 flags) |
| { |
| return flags2s(flags, entity_flags_def); |
| } |
| |
| static constexpr flag_def interface_types_def[] = { |
| { MEDIA_INTF_T_DVB_FE, "DVB Front End" }, |
| { MEDIA_INTF_T_DVB_DEMUX, "DVB Demuxer" }, |
| { MEDIA_INTF_T_DVB_DVR, "DVB DVR" }, |
| { MEDIA_INTF_T_DVB_CA, "DVB Conditional Access" }, |
| { MEDIA_INTF_T_DVB_NET, "DVB Net" }, |
| |
| { MEDIA_INTF_T_V4L_VIDEO, "V4L Video" }, |
| { MEDIA_INTF_T_V4L_VBI, "V4L VBI" }, |
| { MEDIA_INTF_T_V4L_RADIO, "V4L Radio" }, |
| { MEDIA_INTF_T_V4L_SUBDEV, "V4L Sub-Device" }, |
| { MEDIA_INTF_T_V4L_SWRADIO, "V4L Software Defined Radio" }, |
| { MEDIA_INTF_T_V4L_TOUCH, "V4L Touch" }, |
| |
| { MEDIA_INTF_T_ALSA_PCM_CAPTURE, "ALSA PCM Capture" }, |
| { MEDIA_INTF_T_ALSA_PCM_PLAYBACK, "ALSA PCM Playback" }, |
| { MEDIA_INTF_T_ALSA_CONTROL, "ALSA Control" }, |
| { MEDIA_INTF_T_ALSA_COMPRESS, "ALSA Compress" }, |
| { MEDIA_INTF_T_ALSA_RAWMIDI, "ALSA Raw MIDI" }, |
| { MEDIA_INTF_T_ALSA_HWDEP, "ALSA HWDEP" }, |
| { MEDIA_INTF_T_ALSA_SEQUENCER, "ALSA Sequencer" }, |
| { MEDIA_INTF_T_ALSA_TIMER, "ALSA Timer" }, |
| { 0, nullptr } |
| }; |
| |
| std::string mi_ifacetype2s(__u32 type) |
| { |
| for (unsigned i = 0; interface_types_def[i].str; i++) |
| if (type == interface_types_def[i].flag) |
| return interface_types_def[i].str; |
| return "FAIL: Unknown (" + num2s(type) + ")"; |
| } |
| |
| static constexpr flag_def entity_functions_def[] = { |
| { MEDIA_ENT_F_UNKNOWN, "FAIL: Uninitialized Function" }, |
| { MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN, "FAIL: Unknown V4L2 Sub-Device" }, |
| { MEDIA_ENT_T_DEVNODE_UNKNOWN, "FAIL: Unknown Device Node" }, |
| |
| { MEDIA_ENT_F_DTV_DEMOD, "Digital TV Demodulator" }, |
| { MEDIA_ENT_F_TS_DEMUX, "Transport Stream Demuxer" }, |
| { MEDIA_ENT_F_DTV_CA, "Digital TV Conditional Access" }, |
| { MEDIA_ENT_F_DTV_NET_DECAP, "Digital TV Network ULE/MLE Desencapsulation" }, |
| { MEDIA_ENT_F_IO_DTV, "Digital TV I/O" }, |
| { MEDIA_ENT_F_IO_V4L, "V4L2 I/O" }, |
| { MEDIA_ENT_F_IO_VBI, "VBI I/O" }, |
| { MEDIA_ENT_F_IO_SWRADIO, "Software Radio I/O" }, |
| |
| { MEDIA_ENT_F_CAM_SENSOR, "Camera Sensor" }, |
| { MEDIA_ENT_F_FLASH, "Flash Controller" }, |
| { MEDIA_ENT_F_LENS, "Lens Controller" }, |
| { MEDIA_ENT_F_ATV_DECODER, "Analog Video Decoder" }, |
| { MEDIA_ENT_F_DV_DECODER, "Digital Video Decoder" }, |
| { MEDIA_ENT_F_DV_ENCODER, "Digital Video Encoder" }, |
| { MEDIA_ENT_F_TUNER, "Tuner" }, |
| { MEDIA_ENT_F_IF_VID_DECODER, "IF-PLL Video Decoder" }, |
| { MEDIA_ENT_F_IF_AUD_DECODER, "IF-PLL Audio Decoder" }, |
| { MEDIA_ENT_F_AUDIO_CAPTURE, "Audio Capture" }, |
| { MEDIA_ENT_F_AUDIO_PLAYBACK, "Audio Playback" }, |
| { MEDIA_ENT_F_AUDIO_MIXER, "Audio Mixer" }, |
| { MEDIA_ENT_F_PROC_VIDEO_COMPOSER, "Video Composer" }, |
| { MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER, "Video Pixel Formatter" }, |
| { MEDIA_ENT_F_PROC_VIDEO_PIXEL_ENC_CONV, "Video Pixel Encoding Converter" }, |
| { MEDIA_ENT_F_PROC_VIDEO_LUT, "Video Look-Up Table" }, |
| { MEDIA_ENT_F_PROC_VIDEO_SCALER, "Video Scaler" }, |
| { MEDIA_ENT_F_PROC_VIDEO_STATISTICS, "Video Statistics" }, |
| { MEDIA_ENT_F_PROC_VIDEO_DECODER, "Video Decoder" }, |
| { MEDIA_ENT_F_PROC_VIDEO_ENCODER, "Video Encoder" }, |
| { MEDIA_ENT_F_VID_MUX, "Video Muxer" }, |
| { MEDIA_ENT_F_VID_IF_BRIDGE, "Video Interface Bridge" }, |
| { 0, nullptr } |
| }; |
| |
| std::string mi_entfunction2s(__u32 function, bool *is_invalid) |
| { |
| std::string s; |
| |
| if (function != MEDIA_ENT_T_DEVNODE_UNKNOWN && |
| (function & MEDIA_ENT_TYPE_MASK) == MEDIA_ENT_F_OLD_BASE && |
| function > MEDIA_ENT_T_DEVNODE_DVB) { |
| s = "Unknown device node (" + num2s(function) + ")"; |
| if (!is_invalid) |
| return s; |
| *is_invalid = true; |
| return "FAIL: " + s; |
| } |
| if (function != MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN && |
| (function & MEDIA_ENT_TYPE_MASK) == MEDIA_ENT_F_OLD_SUBDEV_BASE && |
| function > MEDIA_ENT_F_TUNER) { |
| s = "Unknown sub-device (" + num2s(function) + ")"; |
| if (!is_invalid) |
| return s; |
| *is_invalid = true; |
| return "FAIL: " + s; |
| } |
| |
| for (unsigned i = 0; entity_functions_def[i].str; i++) { |
| if (function == entity_functions_def[i].flag) { |
| bool fail = !memcmp(entity_functions_def[i].str, "FAIL: ", 6); |
| |
| if (is_invalid) { |
| *is_invalid = fail; |
| return entity_functions_def[i].str; |
| } |
| return fail ? entity_functions_def[i].str + 6 : entity_functions_def[i].str; |
| } |
| } |
| if (is_invalid) |
| return "WARNING: Unknown Function (" + num2s(function) + "), is v4l2-compliance out-of-date?"; |
| return "Unknown Function (" + num2s(function) + ")"; |
| } |
| |
| bool mi_func_requires_intf(__u32 function) |
| { |
| switch (function) { |
| case MEDIA_ENT_F_DTV_DEMOD: |
| case MEDIA_ENT_F_TS_DEMUX: |
| case MEDIA_ENT_F_DTV_CA: |
| case MEDIA_ENT_F_IO_V4L: |
| case MEDIA_ENT_F_IO_VBI: |
| case MEDIA_ENT_F_IO_SWRADIO: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static constexpr flag_def pad_flags_def[] = { |
| { MEDIA_PAD_FL_SINK, "Sink" }, |
| { MEDIA_PAD_FL_SOURCE, "Source" }, |
| { MEDIA_PAD_FL_MUST_CONNECT, "Must Connect" }, |
| { 0, nullptr } |
| }; |
| |
| std::string mi_padflags2s(__u32 flags) |
| { |
| return flags2s(flags, pad_flags_def); |
| } |
| |
| static constexpr flag_def link_flags_def[] = { |
| { MEDIA_LNK_FL_ENABLED, "Enabled" }, |
| { MEDIA_LNK_FL_IMMUTABLE, "Immutable" }, |
| { MEDIA_LNK_FL_DYNAMIC, "Dynamic" }, |
| { 0, nullptr } |
| }; |
| |
| std::string mi_linkflags2s(__u32 flags) |
| { |
| std::string s = flags2s(flags & ~MEDIA_LNK_FL_LINK_TYPE, link_flags_def); |
| |
| if (!s.empty()) |
| s = ", " + s; |
| switch (flags & MEDIA_LNK_FL_LINK_TYPE) { |
| case MEDIA_LNK_FL_DATA_LINK: |
| return "Data" + s; |
| case MEDIA_LNK_FL_INTERFACE_LINK: |
| return "Interface" + s; |
| default: |
| return "Unknown (" + num2s(flags) + ")" + s; |
| } |
| } |
| |
| static __u32 read_topology(int media_fd, __u32 major, __u32 minor, |
| __u32 media_version, bool *is_invalid, |
| __u32 *function) |
| { |
| media_v2_topology topology; |
| unsigned i, j; |
| |
| memset(&topology, 0, sizeof(topology)); |
| if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology)) |
| return 0; |
| |
| media_v2_entity v2_ents[topology.num_entities]; |
| media_v2_interface v2_ifaces[topology.num_interfaces]; |
| media_v2_pad v2_pads[topology.num_pads]; |
| media_v2_link v2_links[topology.num_links]; |
| |
| topology.ptr_entities = (uintptr_t)v2_ents; |
| topology.ptr_interfaces = (uintptr_t)v2_ifaces; |
| topology.ptr_pads = (uintptr_t)v2_pads; |
| topology.ptr_links = (uintptr_t)v2_links; |
| if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology)) |
| return 0; |
| for (i = 0; i < topology.num_interfaces; i++) |
| if (v2_ifaces[i].devnode.major == major && |
| v2_ifaces[i].devnode.minor == minor) |
| break; |
| if (i == topology.num_interfaces) { |
| fprintf(stderr, "FAIL: could not find device %d:%d in topology\n", |
| major, minor); |
| if (is_invalid) |
| *is_invalid = true; |
| if (function) |
| *function = MEDIA_ENT_F_UNKNOWN; |
| return MEDIA_ENT_F_UNKNOWN; |
| } |
| const media_v2_interface &iface = v2_ifaces[i]; |
| for (i = 0; i < topology.num_links; i++) { |
| __u32 type = v2_links[i].flags & MEDIA_LNK_FL_LINK_TYPE; |
| |
| if (type != MEDIA_LNK_FL_INTERFACE_LINK) |
| continue; |
| if (v2_links[i].source_id == iface.id) |
| break; |
| } |
| bool is_radio = iface.intf_type == MEDIA_INTF_T_V4L_RADIO; |
| |
| if (is_radio && i < topology.num_links) { |
| if (is_invalid) |
| *is_invalid = true; |
| fprintf(stderr, "FAIL: unexpectedly found link for radio interface 0x%08x in topology\n", |
| iface.id); |
| return MEDIA_ENT_F_UNKNOWN; |
| } |
| if (!is_radio) { |
| if (i == topology.num_links) { |
| if (is_invalid) |
| *is_invalid = true; |
| fprintf(stderr, "FAIL: could not find link for interface 0x%08x in topology\n", |
| iface.id); |
| return MEDIA_ENT_F_UNKNOWN; |
| } |
| __u32 ent_id = v2_links[i].sink_id; |
| for (i = 0; i < topology.num_entities; i++) { |
| if (v2_ents[i].id == ent_id) |
| break; |
| } |
| if (i == topology.num_entities) { |
| if (is_invalid) |
| *is_invalid = true; |
| fprintf(stderr, "FAIL: could not find entity 0x%08x in topology\n", |
| ent_id); |
| return MEDIA_ENT_F_UNKNOWN; |
| } |
| } |
| |
| printf("Interface Info:\n"); |
| printf("\tID : 0x%08x\n", iface.id); |
| printf("\tType : %s\n", mi_ifacetype2s(iface.intf_type).c_str()); |
| |
| if (is_radio) |
| return MEDIA_ENT_F_UNKNOWN; |
| |
| const media_v2_entity &ent = v2_ents[i]; |
| printf("Entity Info:\n"); |
| printf("\tID : 0x%08x (%u)\n", ent.id, ent.id); |
| printf("\tName : %s\n", ent.name); |
| printf("\tFunction : %s\n", mi_entfunction2s(ent.function, is_invalid).c_str()); |
| if (MEDIA_V2_ENTITY_HAS_FLAGS(media_version) && ent.flags) |
| printf("\tFlags : %s\n", mi_entflags2s(ent.flags).c_str()); |
| |
| // Yes, I know, lots of nested for-loops. If we get really complex |
| // devices with such large topologies that this becomes too inefficient |
| // then this needs to be changed and we'd need some maps to quickly |
| // look up values. |
| for (i = 0; i < topology.num_pads; i++) { |
| const media_v2_pad &pad = v2_pads[i]; |
| |
| if (pad.entity_id != ent.id) |
| continue; |
| printf("\tPad 0x%08x : ", pad.id); |
| if (MEDIA_V2_PAD_HAS_INDEX(media_version)) |
| printf("%u: ", pad.index); |
| printf("%s\n", mi_padflags2s(pad.flags).c_str()); |
| for (j = 0; j < topology.num_links; j++) { |
| const media_v2_link &link = v2_links[j]; |
| __u32 type = link.flags & MEDIA_LNK_FL_LINK_TYPE; |
| __u32 remote_pad; |
| __u32 remote_ent_id = 0; |
| const media_v2_entity *remote_ent = nullptr; |
| |
| if (type != MEDIA_LNK_FL_DATA_LINK || |
| (link.source_id != pad.id && link.sink_id != pad.id)) |
| continue; |
| bool is_sink = link.sink_id == pad.id; |
| remote_pad = is_sink ? link.source_id : link.sink_id; |
| for (unsigned k = 0; k < topology.num_pads; k++) |
| if (v2_pads[k].id == remote_pad) { |
| remote_ent_id = v2_pads[k].entity_id; |
| break; |
| } |
| if (!remote_ent_id) { |
| if (is_invalid) |
| *is_invalid = true; |
| fprintf(stderr, "FAIL: could not find remote pad 0x%08x in topology\n", |
| remote_pad); |
| return ent.id; |
| } |
| for (unsigned k = 0; k < topology.num_entities; k++) |
| if (v2_ents[k].id == remote_ent_id) { |
| remote_ent = &v2_ents[k]; |
| break; |
| } |
| if (!remote_ent) { |
| if (is_invalid) |
| *is_invalid = true; |
| fprintf(stderr, "FAIL: could not find remote entity 0x%08x in topology\n", |
| remote_ent_id); |
| return ent.id; |
| } |
| printf("\t Link 0x%08x: %s remote pad 0x%x of entity '%s' (%s): %s\n", |
| link.id, is_sink ? "from" : "to", remote_pad, |
| remote_ent->name, mi_entfunction2s(remote_ent->function).c_str(), |
| mi_linkflags2s(link.flags).c_str()); |
| if (function && !*function) |
| *function = remote_ent->function; |
| } |
| } |
| return ent.id; |
| } |
| |
| __u32 mi_media_info_for_fd(int media_fd, int fd, bool *is_invalid, __u32 *function) |
| { |
| struct media_device_info mdinfo; |
| struct stat sb; |
| __u32 ent_id = 0; |
| |
| if (is_invalid) |
| *is_invalid = false; |
| |
| if (function) |
| *function = MEDIA_ENT_F_UNKNOWN; |
| |
| if (ioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &mdinfo)) |
| return 0; |
| |
| struct media_entity_desc ent; |
| bool found = false; |
| |
| printf("Media Driver Info:\n"); |
| printf("\tDriver name : %s\n", mdinfo.driver); |
| printf("\tModel : %s\n", mdinfo.model); |
| printf("\tSerial : %s\n", mdinfo.serial); |
| printf("\tBus info : %s\n", mdinfo.bus_info); |
| printf("\tMedia version : %d.%d.%d\n", |
| mdinfo.media_version >> 16, |
| (mdinfo.media_version >> 8) & 0xff, |
| mdinfo.media_version & 0xff); |
| printf("\tHardware revision: 0x%08x (%u)\n", |
| mdinfo.hw_revision, mdinfo.hw_revision); |
| printf("\tDriver version : %d.%d.%d\n", |
| mdinfo.driver_version >> 16, |
| (mdinfo.driver_version >> 8) & 0xff, |
| mdinfo.driver_version & 0xff); |
| |
| if (fd < 0) |
| return 0; |
| |
| if (fstat(fd, &sb) == -1) { |
| fprintf(stderr, "failed to stat file\n"); |
| std::exit(EXIT_FAILURE); |
| } |
| |
| ent_id = read_topology(media_fd, major(sb.st_rdev), minor(sb.st_rdev), |
| mdinfo.media_version, is_invalid, function); |
| if (ent_id) |
| return ent_id; |
| |
| memset(&ent, 0, sizeof(ent)); |
| ent.id = MEDIA_ENT_ID_FLAG_NEXT; |
| while (!ioctl(media_fd, MEDIA_IOC_ENUM_ENTITIES, &ent)) { |
| if (ent.dev.major == major(sb.st_rdev) && |
| ent.dev.minor == minor(sb.st_rdev)) { |
| found = true; |
| break; |
| } |
| ent.id |= MEDIA_ENT_ID_FLAG_NEXT; |
| } |
| if (!found) |
| return 0; |
| |
| printf("Entity Info:\n"); |
| printf("\tID : %u\n", ent.id); |
| printf("\tName : %s\n", ent.name); |
| printf("\tType : %s\n", mi_entfunction2s(ent.type).c_str()); |
| if (ent.flags) |
| printf("\tFlags : %s\n", mi_entflags2s(ent.flags).c_str()); |
| if (ent.flags & MEDIA_ENT_FL_DEFAULT) { |
| printf("\tMajor : %u\n", ent.dev.major); |
| printf("\tMinor : %u\n", ent.dev.minor); |
| } |
| |
| struct media_links_enum links_enum; |
| struct media_pad_desc pads[ent.pads]; |
| struct media_link_desc links[ent.links]; |
| |
| memset(&links_enum, 0, sizeof(links_enum)); |
| links_enum.entity = ent.id; |
| links_enum.pads = pads; |
| links_enum.links = links; |
| if (ioctl(media_fd, MEDIA_IOC_ENUM_LINKS, &links_enum)) |
| return ent.id; |
| |
| for (unsigned i = 0; i < ent.pads; i++) |
| printf("\tPad : %u: %s\n", pads[i].index, |
| mi_padflags2s(pads[i].flags).c_str()); |
| for (unsigned i = 0; i < ent.links; i++) |
| printf("\tLink : %u->%u: %s\n", |
| links[i].source.entity, |
| links[i].sink.entity, |
| mi_linkflags2s(links[i].flags).c_str()); |
| return ent.id; |
| } |