summaryrefslogtreecommitdiff
path: root/media-enumerate.c
diff options
context:
space:
mode:
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>2012-07-30 16:40:42 +0200
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>2013-10-23 14:25:31 +0200
commit67cc5f8c2cdc361204cb5d71fc4a48c344fe95f1 (patch)
tree768e1b619569636e5985da9385cbee8cf6d7c15c /media-enumerate.c
Initial version of the libmediadev library
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Diffstat (limited to 'media-enumerate.c')
-rw-r--r--media-enumerate.c649
1 files changed, 649 insertions, 0 deletions
diff --git a/media-enumerate.c b/media-enumerate.c
new file mode 100644
index 0000000..1c3ab38
--- /dev/null
+++ b/media-enumerate.c
@@ -0,0 +1,649 @@
+/*
+ * Media device discovery library
+ *
+ * Copyright (C) 2012 Ideas on board SPRL
+ *
+ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#define HAVE_LIBUDEV
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef HAVE_LIBUDEV
+#include <libudev.h>
+#endif
+
+#include <mediactl/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) {
+ struct media_entity *entities;
+ unsigned int nents;
+
+ media = media_enum->devices[i];
+ entities = media_get_entities(media);
+ nents = media_get_entities_count(media);
+
+ for (j = 0; j < nents; ++j) {
+ struct media_entity *entity = &entities[j];
+ dev_t dev;
+
+ if (entity->info.type != MEDIA_ENT_T_DEVNODE_V4L)
+ continue;
+
+ dev = makedev(entity->info.v4l.major,
+ entity->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) {
+ struct media_entity *entities;
+ unsigned int nents;
+
+ media = media_enum->devices[i];
+ entities = media_get_entities(media);
+ 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 = &entities[j];
+
+ if (entity->info.type != MEDIA_ENT_T_DEVNODE_ALSA)
+ continue;
+
+ if (entity->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;
+}