From b3ec4d492df50abe1d5bf1355da3e9d5f3b3919f Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 23 May 2016 00:33:58 +0300 Subject: Import media-enum as 3rd party code Signed-off-by: Laurent Pinchart --- 3rdparty/media-enum/.gitignore | 2 + 3rdparty/media-enum/Makefile | 22 + 3rdparty/media-enum/include/media-enumerate.h | 114 +++++ 3rdparty/media-enum/media-enumerate.c | 646 ++++++++++++++++++++++++++ 4 files changed, 784 insertions(+) create mode 100644 3rdparty/media-enum/.gitignore create mode 100644 3rdparty/media-enum/Makefile create mode 100644 3rdparty/media-enum/include/media-enumerate.h create mode 100644 3rdparty/media-enum/media-enumerate.c diff --git a/3rdparty/media-enum/.gitignore b/3rdparty/media-enum/.gitignore new file mode 100644 index 0000000..9eca6c8 --- /dev/null +++ b/3rdparty/media-enum/.gitignore @@ -0,0 +1,2 @@ +*.a +*.o diff --git a/3rdparty/media-enum/Makefile b/3rdparty/media-enum/Makefile new file mode 100644 index 0000000..a79744b --- /dev/null +++ b/3rdparty/media-enum/Makefile @@ -0,0 +1,22 @@ +AR := $(CROSS_COMPILE)ar +CC := $(CROSS_COMPILE)gcc +CFLAGS := -O2 -W -Wall -Wno-unused-parameter -I$(KDIR)/usr/include -I$(topdir)/3rdparty/media-ctl/include -Iinclude/ -I. -fPIC +LDFLAGS := + +OBJECTS := media-enumerate.o + +TARGET := libmediaenum.a + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) -r $@ $^ + +%.o : %.c + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + -$(RM) *.o + -$(RM) $(TARGET) + +install: diff --git a/3rdparty/media-enum/include/media-enumerate.h b/3rdparty/media-enum/include/media-enumerate.h new file mode 100644 index 0000000..ecd6b3d --- /dev/null +++ b/3rdparty/media-enum/include/media-enumerate.h @@ -0,0 +1,114 @@ +/* + * Media device discovery library + * + * Copyright (C) 2012 Ideas on board SPRL + * + * Contact: Laurent Pinchart + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#ifndef __MEDIA_ENUMERATE_H__ +#define __MEDIA_ENUMERATE_H__ + +struct media_enumerate; + +/** + * @brief Create a new media device enumeration context. + * + * Media device enumeration contexts are used to enumerate media devices. They + * store all context information required by enumeration and cache the + * enumeration results for later access. Enumeration itself is performed by the + * separate media_enumerate_scan() function. + * + * Enumeration contexts are reference-counted, see media_enumerate_ref() and + * media_enumerate_unref() for more information. + * + * @return A pointer to the new media device enumeration context or NULL if + * memory cannot be allocated. + */ +struct media_enumerate *media_enumerate_new(void); + +/** + * @brief Take a reference to the enumeration context. + * @param media_enum - enumeration context instance. + * + * Media device enumeration contexts are reference-counted. Taking a reference + * to an enumeration context prevents it and all the information it stores from + * being freed until all references are released. The reference count is + * initialized to 1 when the context is created. + * + * @return A pointer to @a media. + */ +struct media_enumerate *media_enumerate_ref(struct media_enumerate *media_enum); + +/** + * @brief Release a reference to the enumeration context. + * @param media_enum - enumeration context instance. + * + * Release a reference to the enumeration context. When the reference count + * reaches 0 this function frees the context, including all information it + * stores. + */ +void media_enumerate_unref(struct media_enumerate *media_enum); + +/** + * @brief Enumerate media devices in the system + * @param media_enum - enumeration context instance. + * + * Scan the system to locate all available media devices. This function must be + * called once and only once before accessing the list of enumerated devices. + * + * Devices are enumerated using libudev if available or through sysfs otherwise. + * sysfs-based enumeration requires device nodes to use the standard Linux + * device names and be numbered by the device sysfs device number. For instance + * the device node corresponding to /sys/class/video4linux/video0 must be named + * /dev/video0. libudev-based enumeration doesn't have this restriction. + * + * Media devices are enumerated by scanning media controller, V4L and ALSA + * kernel devices, in that order. Emulated media devices are created as needed + * for V4L devices that are not part of a media controller based device. ALSA + * devices without a corresponding media controller or V4L devices are skipped. + * + * @return Zero on success or a negative error code on failure. + */ +int media_enumerate_scan(struct media_enumerate *media_enum); + +/** + * @brief Get the number of media devices found during enumeration + * @param media_enum - enumeration context instance. + * + * This function returns the total number of media devices found in the system + * during enumeration. If media devices haven't been enumerated yet it will + * return 0. + * + * @return The number of media devices found in the system + */ +unsigned int media_enumerate_get_devices_count(struct media_enumerate *media_enum); + +/** + * @brief Get the media devices + * @param media_enum - enumeration context instance. + * + * This function returns a pointer to the array of enumerated media devices. If + * If media devices haven't been enumerated yet it will return NULL. + * + * The array of media devices is owned by the enumeration context and will be + * freed when the enumeration context is destroyed. + * + * @return A pointer to an array of media devices + */ +struct media_device **media_enumerate_get_devices(struct media_enumerate *media_enum); + +#endif /* __MEDIA_ENUMERATE_H__ */ diff --git a/3rdparty/media-enum/media-enumerate.c b/3rdparty/media-enum/media-enumerate.c new file mode 100644 index 0000000..0db4cdd --- /dev/null +++ b/3rdparty/media-enum/media-enumerate.c @@ -0,0 +1,646 @@ +/* + * Media device discovery library + * + * Copyright (C) 2012 Ideas on board SPRL + * + * Contact: Laurent Pinchart + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#define HAVE_LIBUDEV + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBUDEV +#include +#endif + +#include "mediactl.h" + +#include "media-enumerate.h" + +struct media_enumerate { + unsigned int refcount; + + char **syspaths; + struct media_device **devices; + unsigned int devices_count; + unsigned int devices_max; + +#ifdef HAVE_LIBUDEV + struct udev *udev; +#endif +}; + +static int media_enumerate_add_device(struct media_enumerate *media_enum, + const char *syspath, + struct media_device *media) +{ + if (media_enum->devices_count == media_enum->devices_max) { + struct media_device **devices; + char **syspaths; + unsigned int count; + + count = media_enum->devices_max * 2; + count = count < 16 ? 16 : count; + + devices = realloc(media_enum->devices, count * sizeof *devices); + if (devices == NULL) + return -ENOMEM; + media_enum->devices = devices; + + syspaths = realloc(media_enum->syspaths, count * sizeof *syspaths); + if (syspaths == NULL) + return -ENOMEM; + media_enum->syspaths = syspaths; + + media_enum->devices_max = count; + } + + media_enum->devices[media_enum->devices_count] = media; + media_enum->syspaths[media_enum->devices_count] = strdup(syspath); + media_enum->devices_count++; + + return 0; +} + +typedef int(*media_enumerate_callback)(struct media_enumerate *, const char *, + const char *, dev_t); + +enum media_enumerate_bus_type { + BUS_TYPE_UNKNOWN, + BUS_TYPE_PCI, + BUS_TYPE_USB, +}; + +static enum media_enumerate_bus_type +media_enumerate_get_physical_bus(const char *syspath) +{ + char *modalias; + char type[4]; + int ret; + int fd; + + modalias = malloc(strlen(syspath) + 10); + if (modalias == NULL) + return BUS_TYPE_UNKNOWN; + + sprintf(modalias, "%s/modalias", syspath); + fd = open(modalias, O_RDONLY); + free(modalias); + + if (fd == -1) + return BUS_TYPE_UNKNOWN; + + ret = read(fd, type, 3); + close(fd); + + if (ret != 3) + return BUS_TYPE_UNKNOWN; + + type[3] = '\0'; + + if (!strcmp(type, "pci")) + return BUS_TYPE_PCI; + else if (!strcmp(type, "usb")) + return BUS_TYPE_USB; + else + return BUS_TYPE_UNKNOWN; +} + +static char *media_enumerate_get_physical_parent(const char *syspath) +{ + enum media_enumerate_bus_type bus_type; + char *phys; + char *p; + + phys = realpath(syspath, NULL); + if (phys == NULL) + return NULL; + + p = strstr(phys, "media"); + if (p) + *(p - 1) = '\0'; + p = strstr(phys, "sound"); + if (p) + *(p - 1) = '\0'; + p = strstr(phys, "video4linux"); + if (p) + *(p - 1) = '\0'; + + bus_type = media_enumerate_get_physical_bus(phys); + + switch (bus_type) { + case BUS_TYPE_PCI: + /* Remove the PCI function from the path if present. */ + p = strrchr(phys, '.'); + if (p) + *p = '\0'; + break; + case BUS_TYPE_USB: + /* Remove the USB interface from the path if present. */ + p = strrchr(phys, '/'); + if (p && strchr(p, ':')) + *p = '\0'; + break; + default: + break; + } + + return phys; +} + +#ifdef HAVE_LIBUDEV + +/* ----------------------------------------------------------------------------- + * udev helpers + */ + +static int media_enumerate_udev_scan_one(struct media_enumerate *media_enum, + const char *syspath, + media_enumerate_callback callback) +{ + struct udev_device *dev; + const char *devnode; + char *devpath = NULL; + dev_t devnum; + int ret = -ENODEV; + + dev = udev_device_new_from_syspath(media_enum->udev, syspath); + if (dev == NULL) + return -ENODEV; + + devnode = udev_device_get_devnode(dev); + if (devnode == NULL) + goto done; + + devpath = media_enumerate_get_physical_parent(syspath); + if (devpath == NULL) + goto done; + + devnum = udev_device_get_devnum(dev); + ret = callback(media_enum, devpath, devnode, devnum); + +done: + udev_device_unref(dev); + free(devpath); + return ret; +} + +static int media_enumerate_udev_scan(struct media_enumerate *media_enum, + const char *subsystem, const char *type, + media_enumerate_callback callback) +{ + struct udev_enumerate *udev_enum = NULL; + struct udev_list_entry *devs; + struct udev_list_entry *dev; + unsigned int len; + int ret; + + udev_enum = udev_enumerate_new(media_enum->udev); + if (udev_enum == NULL) { + ret = -ENOMEM; + goto done; + } + + ret = udev_enumerate_add_match_subsystem(udev_enum, subsystem); + if (ret < 0) + goto done; + + ret = udev_enumerate_scan_devices(udev_enum); + if (ret < 0) + goto done; + + devs = udev_enumerate_get_list_entry(udev_enum); + if (devs == NULL) + goto done; + + len = strlen(type); + + udev_list_entry_foreach(dev, devs) { + const char *syspath; + const char *p; + + syspath = udev_list_entry_get_name(dev); + + p = strrchr(syspath, '/'); + if (p == NULL || strncmp(p + 1, type, len)) + continue; + + media_enumerate_udev_scan_one(media_enum, syspath, callback); + } + + ret = 0; + +done: + udev_enumerate_unref(udev_enum); + return ret; +} + +#else + +/* ----------------------------------------------------------------------------- + * sysfs helpers + */ + +static int +media_enumerate_sysfs_scan_one(struct media_enumerate *media_enum, + const char *sysbase, const char *prefix, int idx, + media_enumerate_callback callback) +{ + unsigned int major; + unsigned int minor; + struct stat devstat; + unsigned int len; + char *devname; + char *devpath; + char dev[20]; + char *end; + int ret; + int fd; + + /* Read the device major and minor from sysfs. */ + len = strlen(sysbase) + strlen(prefix) + 17; + devname = malloc(len); + if (devname == NULL) + return -ENOMEM; + + sprintf(devname, "%s/%s%u", sysbase, prefix, idx); + devpath = media_enumerate_get_physical_parent(devname); + if (devpath == NULL) + goto done; + + sprintf(devname, "%s/%s%u/dev", sysbase, prefix, idx); + fd = open(devname, O_RDONLY); + if (fd == -1) { + ret = -ENODEV; + goto done; + } + + ret = read(fd, dev, sizeof dev - 1); + close(fd); + if (ret < 0) { + ret = -ENODEV; + goto done; + } + + dev[ret] = '\0'; + major = strtoul(dev, &end, 10); + if (*end != ':') { + ret = -ENODEV; + goto done; + } + + minor = strtoul(end + 1, &end, 10); + if (!isspace(*end) && end != '\0') { + ret = -ENODEV; + goto done; + } + + /* Verify that the device node exists. udev might have reordered the + * device nodes, make sure the major/minor match as a sanity check. + */ + if (!strcmp(prefix, "controlC")) + sprintf(devname, "/dev/snd/%s%u", prefix, idx); + else + sprintf(devname, "/dev/%s%u", prefix, idx); + + ret = stat(devname, &devstat); + if (ret < 0) { + ret = -ENODEV; + goto done; + } + + if (major(devstat.st_rdev) != major || minor(devstat.st_rdev) != minor) { + ret = -ENODEV; + goto done; + } + + ret = callback(media_enum, devpath, devname, makedev(major, minor)); + +done: + free(devname); + free(devpath); + return ret; +} + +static int +media_enumerate_sysfs_scan(struct media_enumerate *media_enum, + const char *sysbase, const char *prefix, + media_enumerate_callback callback) +{ + struct dirent *ent; + unsigned int len; + char *end; + DIR *dir; + + len = strlen(prefix); + + dir = opendir(sysbase); + if (dir == NULL) + return -ENODEV; + + while ((ent = readdir(dir)) != NULL) { + unsigned int idx; + + if (strncmp(ent->d_name, prefix, len)) + continue; + + idx = strtoul(ent->d_name + len, &end, 10); + if (*end != '\0') + continue; + + media_enumerate_sysfs_scan_one(media_enum, sysbase, prefix, idx, + callback); + } + + closedir(dir); + + return 0; +} + +#endif + +/* ----------------------------------------------------------------------------- + * Media devices enumeration + */ + +static int media_enumerate_match_one_media(struct media_enumerate *media_enum, + const char *syspath, const char *devname, + dev_t devnum __attribute__((__unused__))) +{ + struct media_device *media; + int ret; + + media = media_device_new(devname); + if (media == NULL) + return -ENODEV; + + ret = media_device_enumerate(media); + if (ret < 0) { + media_device_unref(media); + return -ENODEV; + } + + return media_enumerate_add_device(media_enum, syspath, media); +} + +static int media_enumerate_scan_media(struct media_enumerate *media_enum) +{ +#ifdef HAVE_LIBUDEV + return media_enumerate_udev_scan(media_enum, "media", "media", + media_enumerate_match_one_media); +#else + return media_enumerate_sysfs_scan(media_enum, "/sys/bus/media/devices", + "media", media_enumerate_match_one_media); +#endif /* HAVE_LIBUDEV */ +} + +/* ----------------------------------------------------------------------------- + * Video devices enumeration + */ + +static int media_enumerate_match_one_video(struct media_enumerate *media_enum, + const char *syspath, + const char *devname, dev_t devnum) +{ + struct media_entity_desc entity; + struct media_device_info info; + struct v4l2_capability cap; + struct media_device *media; + unsigned int i, j; + int ret; + int fd; + + /* Verify whether the video device node is part of an already enumerated + * media device. + */ + for (i = 0; i < media_enum->devices_count; ++i) { + unsigned int nents; + + media = media_enum->devices[i]; + nents = media_get_entities_count(media); + + for (j = 0; j < nents; ++j) { + struct media_entity *entity = media_get_entity(media, j); + const struct media_entity_desc *info = media_entity_get_info(entity); + dev_t dev; + + if (info->type != MEDIA_ENT_T_DEVNODE_V4L) + continue; + + dev = makedev(info->v4l.major, info->v4l.minor); + if (dev == devnum) + return 0; + } + } + + fd = open(devname, O_RDWR); + if (fd < 0) + return -ENODEV; + + memset(&cap, 0, sizeof cap); + ret = ioctl(fd, VIDIOC_QUERYCAP, &cap); + close(fd); + + if (ret < 0) + return -ENODEV; + + memset(&info, 0, sizeof info); + memcpy(info.driver, cap.driver, sizeof info.driver); + memcpy(info.model, cap.card, sizeof info.model); + memcpy(info.bus_info, cap.bus_info, sizeof info.bus_info); + info.driver_version = cap.version; + + media = media_device_new_emulated(&info); + if (media == NULL) + return -ENOMEM; + + memset(&entity, 0, sizeof entity); + memcpy(entity.name, cap.card, sizeof entity.name); + entity.type = MEDIA_ENT_T_DEVNODE_V4L; + entity.flags = MEDIA_ENT_FL_DEFAULT; + entity.v4l.major = major(devnum); + entity.v4l.minor = minor(devnum); + + ret = media_device_add_entity(media, &entity, devname); + if (ret < 0) { + media_device_unref(media); + return ret; + } + + return media_enumerate_add_device(media_enum, syspath, media); +} + +static int media_enumerate_scan_video(struct media_enumerate *media_enum) +{ +#ifdef HAVE_LIBUDEV + return media_enumerate_udev_scan(media_enum, "video4linux", "video", + media_enumerate_match_one_video); +#else + return media_enumerate_sysfs_scan(media_enum, "/sys/class/video4linux", + "video", media_enumerate_match_one_video); +#endif /* HAVE_LIBUDEV */ +} + +/* ----------------------------------------------------------------------------- + * Audio devices enumeration + */ + +static int media_enumerate_match_one_audio(struct media_enumerate *media_enum, + const char *syspath, const char *devname, + dev_t devnum __attribute__((__unused__))) +{ + struct media_entity_desc entity; + struct media_device *parent = NULL; + struct media_device *media; + unsigned int card; + unsigned int i, j; + char *endp; + + /* Verify whether the sound card is part of an already enumerated media + * device. + */ + card = strtoul(strrchr(devname, '/') + strlen("/controlC"), &endp, 10); + if (*endp != '\0') + return -EINVAL; + + for (i = 0; i < media_enum->devices_count; ++i) { + unsigned int nents; + + media = media_enum->devices[i]; + nents = media_get_entities_count(media); + + if (!strcmp(media_enum->syspaths[i], syspath)) + parent = media; + + for (j = 0; j < nents; ++j) { + struct media_entity *entity = media_get_entity(media, j); + const struct media_entity_desc *info = media_entity_get_info(entity); + + if (info->type != MEDIA_ENT_T_DEVNODE_ALSA) + continue; + + if (info->alsa.card == card) + return 0; + } + } + + if (!parent) + return 0; + + memset(&entity, 0, sizeof entity); + entity.type = MEDIA_ENT_T_DEVNODE_ALSA; + entity.flags = MEDIA_ENT_FL_DEFAULT; + snprintf(entity.name, sizeof entity.name, "ALSA %u/%u/%u", card, 0, 0); + entity.alsa.card = card; + entity.alsa.device = 0; + entity.alsa.subdevice = 0; + + return media_device_add_entity(parent, &entity, devname); +} + +static int media_enumerate_scan_audio(struct media_enumerate *media_enum) +{ +#ifdef HAVE_LIBUDEV + return media_enumerate_udev_scan(media_enum, "sound", "control", + media_enumerate_match_one_audio); +#else + return media_enumerate_sysfs_scan(media_enum, "/sys/class/sound", + "controlC", media_enumerate_match_one_audio); +#endif /* HAVE_LIBUDEV */ +} + +/* -------------------------------------------------------------------------- */ + +struct media_enumerate *media_enumerate_new(void) +{ + struct media_enumerate *media_enum; + + media_enum = calloc(1, sizeof *media_enum); + if (media_enum == NULL) + return NULL; + + media_enum->refcount = 1; + +#ifdef HAVE_LIBUDEV + media_enum->udev = udev_new(); + if (media_enum->udev == NULL) { + printf("Unable to get udev context\n"); + media_enumerate_unref(media_enum); + return NULL; + } +#endif + + return media_enum; +} + +struct media_enumerate *media_enumerate_ref(struct media_enumerate *media_enum) +{ + media_enum->refcount++; + return media_enum; +} + +void media_enumerate_unref(struct media_enumerate *media_enum) +{ + unsigned int i; + + media_enum->refcount--; + if (media_enum->refcount > 0) + return; + + for (i = 0; i < media_enum->devices_count; ++i) { + media_device_unref(media_enum->devices[i]); + free(media_enum->syspaths[i]); + } + +#ifdef HAVE_LIBUDEV + udev_unref(media_enum->udev); +#endif + + free(media_enum->devices); + free(media_enum->syspaths); + free(media_enum); +} + +int media_enumerate_scan(struct media_enumerate *media_enum) +{ + media_enumerate_scan_media(media_enum); + media_enumerate_scan_video(media_enum); + media_enumerate_scan_audio(media_enum); + + return 0; +} + +struct media_device **media_enumerate_get_devices(struct media_enumerate *media_enum) +{ + return media_enum->devices; +} + +unsigned int media_enumerate_get_devices_count(struct media_enumerate *media_enum) +{ + return media_enum->devices_count; +} -- cgit v1.2.3