| /* |
| Copyright © 2011 by Mauro Carvalho Chehab |
| |
| The get_media_devices is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| The libv4l2util Library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with the libv4l2util Library; if not, write to the Free |
| Software Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA |
| 02110-1335 USA. |
| */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/sysmacros.h> |
| #include <sys/stat.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <dirent.h> |
| #include <limits.h> |
| #include "get_media_devices.h" |
| |
| #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) |
| |
| /** |
| * struct media_device_entry - Describes one device entry got via sysfs |
| * |
| * @device: sysfs name for a device. |
| * PCI devices are like: pci0000:00/0000:00:1b.0 |
| * USB devices are like: pci0000:00/0000:00:1d.7/usb1/1-8 |
| * @node: Device node, in sysfs or alsa hw identifier |
| * @device_type: Type of the device (V4L_*, DVB_*, SND_*) |
| */ |
| struct media_device_entry { |
| char *device; |
| char *node; |
| enum device_type type; |
| enum bus_type bus; |
| unsigned major, minor; /* Device major/minor */ |
| }; |
| |
| /** |
| * struct media_devices - Describes all devices found |
| * |
| * @device: sysfs name for a device. |
| * PCI devices are like: pci0000:00/0000:00:1b.0 |
| * USB devices are like: pci0000:00/0000:00:1d.7/usb1/1-8 |
| * @node: Device node, in sysfs or alsa hw identifier |
| * @device_type: Type of the device (V4L_*, DVB_*, SND_*) |
| */ |
| struct media_devices { |
| struct media_device_entry *md_entry; |
| unsigned int md_size; |
| }; |
| |
| typedef int (*fill_data_t)(struct media_device_entry *md); |
| |
| #define DEVICE_STR "devices" |
| |
| static void get_uevent_info(struct media_device_entry *md_ptr, char *dname) |
| { |
| FILE *fd; |
| char file[PATH_MAX + 8], *name, *p; |
| char s[1024]; |
| |
| snprintf(file, sizeof(file), "%s/%s/uevent", dname, md_ptr->node); |
| fd = fopen(file, "r"); |
| if (!fd) |
| return; |
| while (fgets(s, sizeof(s), fd)) { |
| p = strtok(s, "="); |
| if (!p) |
| continue; |
| name = p; |
| p = strtok(NULL, "\n"); |
| if (!p) |
| continue; |
| if (!strcmp(name, "MAJOR")) |
| md_ptr->major = atol(p); |
| else if (!strcmp(name, "MINOR")) |
| md_ptr->minor = atol(p); |
| } |
| |
| fclose(fd); |
| } |
| |
| static enum bus_type get_bus(char *device) |
| { |
| char file[PATH_MAX + 9]; |
| char s[1024]; |
| FILE *f; |
| |
| if (!strcmp(device, "/sys/devices/virtual")) |
| return MEDIA_BUS_VIRTUAL; |
| |
| snprintf(file, sizeof(file), "%s/modalias", device); |
| f = fopen(file, "r"); |
| if (!f) |
| return MEDIA_BUS_UNKNOWN; |
| if (!fgets(s, sizeof(s), f)) |
| return MEDIA_BUS_UNKNOWN; |
| fclose(f); |
| |
| if (!strncmp(s, "pci", 3)) |
| return MEDIA_BUS_PCI; |
| if (!strncmp(s, "usb", 3)) |
| return MEDIA_BUS_USB; |
| |
| return MEDIA_BUS_UNKNOWN; |
| } |
| |
| static int get_class(char *class, |
| struct media_device_entry **md, |
| unsigned int *md_size, |
| fill_data_t fill) |
| { |
| DIR *dir; |
| struct dirent *entry; |
| char dname[PATH_MAX]; |
| char fname[PATH_MAX + sizeof(entry->d_name)]; |
| char link[PATH_MAX]; |
| char virt_dev[60]; |
| int err = -2; |
| struct media_device_entry *md_ptr = NULL; |
| char *p, *device; |
| enum bus_type bus; |
| static int virtual = 0; |
| |
| snprintf(dname, PATH_MAX, "/sys/class/%s", class); |
| dir = opendir(dname); |
| if (!dir) { |
| return 0; |
| } |
| for (entry = readdir(dir); entry; entry = readdir(dir)) { |
| /* Skip . and .. */ |
| if (entry->d_name[0] == '.') |
| continue; |
| /* Canonicalize the device name */ |
| snprintf(fname, sizeof(fname), "%s/%s", dname, entry->d_name); |
| if (realpath(fname, link)) { |
| device = link; |
| |
| /* Remove the subsystem/class_name from the string */ |
| p = strstr(device, class); |
| if (!p) |
| continue; |
| *(p - 1) = '\0'; |
| |
| bus = get_bus(device); |
| |
| /* remove the /sys/devices/ from the name */ |
| device += 13; |
| |
| switch (bus) { |
| case MEDIA_BUS_PCI: |
| /* Remove the device function nr */ |
| p = strrchr(device, '.'); |
| if (!p) |
| continue; |
| *p = '\0'; |
| break; |
| case MEDIA_BUS_USB: |
| /* Remove USB interface from the path */ |
| p = strrchr(device, '/'); |
| if (!p) |
| continue; |
| /* In case we have a device where the driver |
| attaches directly to the usb device rather |
| then to an interface */ |
| if (!strchr(p, ':')) |
| break; |
| *p = '\0'; |
| break; |
| case MEDIA_BUS_VIRTUAL: |
| /* Don't group virtual devices */ |
| sprintf(virt_dev, "virtual%d", virtual++); |
| device = virt_dev; |
| break; |
| case MEDIA_BUS_UNKNOWN: |
| break; |
| } |
| |
| /* Add one more element to the devices struct */ |
| *md = realloc(*md, (*md_size + 1) * sizeof(*md_ptr)); |
| if (!*md) |
| goto error; |
| md_ptr = (*md) + *md_size; |
| (*md_size)++; |
| |
| /* Cleans previous data and fills it with device/node */ |
| memset(md_ptr, 0, sizeof(*md_ptr)); |
| md_ptr->type = UNKNOWN; |
| md_ptr->device = strdup(device); |
| md_ptr->node = strdup(entry->d_name); |
| |
| /* Retrieve major and minor information */ |
| get_uevent_info(md_ptr, dname); |
| |
| /* Used to identify the type of node */ |
| fill(md_ptr); |
| } |
| } |
| err = 0; |
| error: |
| closedir(dir); |
| return err; |
| } |
| |
| static int add_v4l_class(struct media_device_entry *md) |
| { |
| if (strstr(md->node, "video")) |
| md->type = MEDIA_V4L_VIDEO; |
| else if (strstr(md->node, "vbi")) |
| md->type = MEDIA_V4L_VBI; |
| else if (strstr(md->node, "swradio")) |
| md->type = MEDIA_V4L_SWRADIO; |
| else if (strstr(md->node, "radio")) |
| md->type = MEDIA_V4L_RADIO; |
| else if (strstr(md->node, "v4l-touch")) |
| md->type = MEDIA_V4L_TOUCH; |
| else if (strstr(md->node, "v4l-subdev")) |
| md->type = MEDIA_V4L_SUBDEV; |
| |
| return 0; |
| } |
| |
| static int add_snd_class(struct media_device_entry *md) |
| { |
| unsigned c = 65535, d = 65535; |
| char node[64]; |
| |
| if (strstr(md->node, "timer")) { |
| md->type = MEDIA_SND_TIMER; |
| return 0; |
| } |
| if (strstr(md->node, "seq")) { |
| md->type = MEDIA_SND_SEQ; |
| return 0; |
| } if (strstr(md->node, "card")) { |
| sscanf(md->node, "card%u", &c); |
| md->type = MEDIA_SND_CARD; |
| } else if (strstr(md->node, "hw")) { |
| sscanf(md->node, "hwC%uD%u", &c, &d); |
| md->type = MEDIA_SND_HW; |
| } else if (strstr(md->node, "control")) { |
| sscanf(md->node, "controlC%u", &c); |
| md->type = MEDIA_SND_CONTROL; |
| } else if (strstr(md->node, "pcm")) { |
| sscanf(md->node, "pcmC%uD%u", &c, &d); |
| if (md->node[strlen(md->node) - 1] == 'p') |
| md->type = MEDIA_SND_OUT; |
| else if (md->node[strlen(md->node) - 1] == 'c') |
| md->type = MEDIA_SND_CAP; |
| } |
| |
| if (c == 65535) |
| return 0; |
| |
| /* Reformat device to be useful for alsa userspace library */ |
| if (d == 65535) |
| snprintf(node, sizeof(node), "hw:%u", c); |
| else |
| snprintf(node, sizeof(node), "hw:%u,%u", c, d); |
| |
| free(md->node); |
| md->node = strdup(node); |
| |
| return 0; |
| } |
| |
| static int add_dvb_class(struct media_device_entry *md) |
| { |
| if (strstr(md->node, "video")) |
| md->type = MEDIA_DVB_VIDEO; |
| if (strstr(md->node, "audio")) |
| md->type = MEDIA_DVB_AUDIO; |
| if (strstr(md->node, "sec")) |
| md->type = MEDIA_DVB_SEC; |
| if (strstr(md->node, "frontend")) |
| md->type = MEDIA_DVB_FRONTEND; |
| else if (strstr(md->node, "demux")) |
| md->type = MEDIA_DVB_DEMUX; |
| else if (strstr(md->node, "dvr")) |
| md->type = MEDIA_DVB_DVR; |
| else if (strstr(md->node, "net")) |
| md->type = MEDIA_DVB_NET; |
| else if (strstr(md->node, "ca")) |
| md->type = MEDIA_DVB_CA; |
| else if (strstr(md->node, "osd")) |
| md->type = MEDIA_DVB_OSD; |
| |
| return 0; |
| } |
| |
| static int sort_media_device_entry(const void *a, const void *b) |
| { |
| const struct media_device_entry *md_a = a; |
| const struct media_device_entry *md_b = b; |
| int cmp; |
| |
| cmp = strcmp(md_a->device, md_b->device); |
| if (cmp) |
| return cmp; |
| cmp = (int)md_a->type - (int)md_b->type; |
| if (cmp) |
| return cmp; |
| |
| return strcmp(md_a->node, md_b->node); |
| } |
| |
| |
| /* Public functions */ |
| |
| void free_media_devices(void *opaque) |
| { |
| struct media_devices *md = opaque; |
| struct media_device_entry *md_ptr = md->md_entry; |
| int i; |
| for (i = 0; i < md->md_size; i++) { |
| free(md_ptr->node); |
| free(md_ptr->device); |
| md_ptr++; |
| } |
| free(md->md_entry); |
| free(md); |
| } |
| |
| void *discover_media_devices(void) |
| { |
| struct media_devices *md = NULL; |
| struct media_device_entry *md_entry = NULL; |
| |
| md = calloc(1, sizeof(*md)); |
| if (!md) |
| return NULL; |
| |
| md->md_size = 0; |
| if (get_class("video4linux", &md_entry, &md->md_size, add_v4l_class)) |
| goto error; |
| if (get_class("sound", &md_entry, &md->md_size, add_snd_class)) |
| goto error; |
| if (get_class("dvb", &md_entry, &md->md_size, add_dvb_class)) |
| goto error; |
| |
| /* There's no media device */ |
| if (!md_entry) |
| goto error; |
| |
| qsort(md_entry, md->md_size, sizeof(*md_entry), sort_media_device_entry); |
| |
| md->md_entry = md_entry; |
| |
| return md; |
| |
| error: |
| free_media_devices(md); |
| return NULL; |
| } |
| |
| const char *media_device_type(enum device_type type) |
| { |
| switch(type) { |
| /* V4L nodes */ |
| case MEDIA_V4L_VIDEO: |
| return "video"; |
| case MEDIA_V4L_VBI: |
| return "vbi"; |
| case MEDIA_V4L_RADIO: |
| return "radio"; |
| case MEDIA_V4L_SWRADIO: |
| return "swradio"; |
| case MEDIA_V4L_TOUCH: |
| return "v4l-touch"; |
| case MEDIA_V4L_SUBDEV: |
| return "v4l subdevice"; |
| |
| /* DVB nodes */ |
| case MEDIA_DVB_VIDEO: |
| return "dvb video"; |
| case MEDIA_DVB_AUDIO: |
| return "dvb audio"; |
| case MEDIA_DVB_SEC: |
| return "dvb sec"; |
| case MEDIA_DVB_FRONTEND: |
| return "dvb frontend"; |
| case MEDIA_DVB_DEMUX: |
| return "dvb demux"; |
| case MEDIA_DVB_DVR: |
| return "dvb dvr"; |
| case MEDIA_DVB_NET: |
| return "dvb net"; |
| case MEDIA_DVB_CA: |
| return "dvb conditional access"; |
| case MEDIA_DVB_OSD: |
| return "dvb OSD"; |
| |
| /* Alsa nodes */ |
| case MEDIA_SND_CARD: |
| return "sound card"; |
| case MEDIA_SND_CAP: |
| return "pcm capture"; |
| case MEDIA_SND_OUT: |
| return "pcm output"; |
| case MEDIA_SND_CONTROL: |
| return "mixer"; |
| case MEDIA_SND_HW: |
| return "sound hardware"; |
| case MEDIA_SND_TIMER: |
| return "sound timer"; |
| case MEDIA_SND_SEQ: |
| return "sound sequencer"; |
| |
| default: |
| return "unknown"; |
| }; |
| } |
| |
| void display_media_devices(void *opaque) |
| { |
| struct media_devices *md = opaque; |
| struct media_device_entry *md_ptr = md->md_entry; |
| int i; |
| char *prev = ""; |
| |
| for (i = 0; i < md->md_size; i++) { |
| if (strcmp(prev, md_ptr->device)) { |
| printf("\nDevice %s:\n\t", md_ptr->device); |
| prev = md_ptr->device; |
| } |
| printf("%s(%s, dev %i:%i) ", md_ptr->node, |
| media_device_type(md_ptr->type), |
| md_ptr->major, md_ptr->minor); |
| md_ptr++; |
| } |
| printf("\n"); |
| } |
| |
| const char *get_associated_device(void *opaque, |
| const char *last_seek, |
| const enum device_type desired_type, |
| const char *seek_device, |
| const enum device_type seek_type) |
| { |
| struct media_devices *md = opaque; |
| struct media_device_entry *md_ptr = md->md_entry; |
| int i, found = 0; |
| char *prev, *p; |
| |
| if (seek_type != NONE && seek_device[0]) { |
| /* Get just the device name */ |
| p = strrchr(seek_device, '/'); |
| if (p) |
| seek_device = p + 1; |
| |
| /* Step 1: Find the seek node */ |
| for (i = 0; i < md->md_size; i++, md_ptr++) { |
| if (last_seek && md_ptr->type == seek_type && |
| !strcmp(md_ptr->node, last_seek)) { |
| found = 1; |
| continue; |
| } |
| if (last_seek && !found) |
| continue; |
| if (md_ptr->type == seek_type && |
| !strcmp(seek_device, md_ptr->node)) |
| break; |
| } |
| if (i == md->md_size) |
| return NULL; |
| i++; |
| prev = md_ptr->device; |
| md_ptr++; |
| /* Step 2: find the associated node */ |
| for (; i < md->md_size && !strcmp(prev, md_ptr->device); i++, md_ptr++) { |
| if (last_seek && md_ptr->type == seek_type && |
| !strcmp(md_ptr->node, last_seek)) { |
| found = 1; |
| continue; |
| } |
| if (last_seek && !found) |
| continue; |
| if (md_ptr->type == desired_type) |
| return md_ptr->node; |
| } |
| } else { |
| for (i = 0; i < md->md_size; i++, md_ptr++) { |
| if (last_seek && !strcmp(md_ptr->node, last_seek)) { |
| found = 1; |
| continue; |
| } |
| if (last_seek && !found) |
| continue; |
| if (md_ptr->type == desired_type) |
| return md_ptr->node; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| const char *fget_associated_device(void *opaque, |
| const char *last_seek, |
| const enum device_type desired_type, |
| const int fd_seek_device, |
| const enum device_type seek_type) |
| { |
| struct media_devices *md = opaque; |
| struct media_device_entry *md_ptr = md->md_entry; |
| struct stat f_status; |
| unsigned int dev_major, dev_minor; |
| int i, found = 0; |
| char *prev; |
| |
| if (fstat(fd_seek_device, &f_status)) { |
| perror("Can't get file status"); |
| return NULL; |
| } |
| if (!S_ISCHR(f_status.st_mode)) { |
| fprintf(stderr, "File descriptor is not a char device\n"); |
| return NULL; |
| } |
| dev_major = major(f_status.st_rdev); |
| dev_minor = minor(f_status.st_rdev); |
| |
| /* Step 1: Find the seek node */ |
| for (i = 0; i < md->md_size; i++, md_ptr++) { |
| if (last_seek && md_ptr->type == seek_type |
| && md_ptr->major == dev_major |
| && md_ptr->minor == dev_minor) { |
| found = 1; |
| continue; |
| } |
| if (last_seek && !found) |
| continue; |
| if (md_ptr->type == seek_type |
| && md_ptr->major == dev_major |
| && md_ptr->minor == dev_minor) |
| break; |
| } |
| if (i == md->md_size) |
| return NULL; |
| i++; |
| prev = md_ptr->device; |
| md_ptr++; |
| /* Step 2: find the associated node */ |
| for (; i < md->md_size && !strcmp(prev, md_ptr->device); i++, md_ptr++) { |
| if (last_seek && md_ptr->type == seek_type |
| && md_ptr->major == dev_major |
| && md_ptr->minor == dev_minor) { |
| found = 1; |
| continue; |
| } |
| if (last_seek && !found) |
| continue; |
| if (md_ptr->type == desired_type) |
| return md_ptr->node; |
| } |
| return NULL; |
| } |
| |
| const char *get_not_associated_device(void *opaque, |
| const char *last_seek, |
| const enum device_type desired_type, |
| const enum device_type not_desired_type) |
| { |
| struct media_devices *md = opaque; |
| struct media_device_entry *md_ptr = md->md_entry; |
| int i, skip = 0, found = 0; |
| char *prev = "", *result = NULL; |
| |
| /* Step 1: Find a device without seek_type node */ |
| for (i = 0; i < md->md_size; i++, md_ptr++) { |
| if (last_seek && !strcmp(md_ptr->node, last_seek)) { |
| found = 1; |
| continue; |
| } |
| if (last_seek && !found) |
| continue; |
| if (strcmp(prev, md_ptr->device)) { |
| if (!skip && result) |
| break; |
| prev = md_ptr->device; |
| skip = 0; |
| result = NULL; |
| } |
| if (md_ptr->type == not_desired_type) |
| skip = 1; |
| else if (!skip && !result && md_ptr->type == desired_type) |
| result = md_ptr->node; |
| } |
| if (skip) |
| result = NULL; |
| |
| return result; |
| } |