configfs: Provide configfs support
authorKieran Bingham <kieran.bingham@ideasonboard.com>
Fri, 25 May 2018 15:31:32 +0000 (16:31 +0100)
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Sat, 26 May 2018 15:27:17 +0000 (18:27 +0300)
Parse a configuration name from the commandline, and utilise it to
identify the configfs configuration path.

Only the short-name (i.e. "uvc.1") is necessary to provide if there is
no ambiguity regarding the gadget, otherwise the gadget path should be
included ("g1/functions/uvc.1"). If the parameter is not provided then
the first function is utilised.

Legacy g_webcam is still supported, and the parameter will define the
UDC to match against if provided.

Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Makefile
configfs.c [new file with mode: 0644]
configfs.h [new file with mode: 0644]
uvc-gadget.c

index 5a5e8f5..4c5d498 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,9 +7,15 @@ KERNEL_INCLUDE := -I$(KERNEL_DIR)/include -I$(KERNEL_DIR)/arch/$(ARCH)/include
 CFLAGS         := -W -Wall -g $(KERNEL_INCLUDE)
 LDFLAGS                := -g
 
+OBJS           := \
+                       configfs.o \
+                       events.o \
+                       uvc-gadget.o \
+                       v4l2.o
+
 all: uvc-gadget
 
-uvc-gadget: events.o uvc-gadget.o v4l2.o
+uvc-gadget: $(OBJS)
        $(CC) $(LDFLAGS) -o $@ $^
 
 clean:
diff --git a/configfs.c b/configfs.c
new file mode 100644 (file)
index 0000000..8d36a29
--- /dev/null
@@ -0,0 +1,343 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * ConfigFS Gadget device handling
+ *
+ * Copyright (C) 2018 Kieran Bingham
+ *
+ * Contact: Kieran Bingham <kieran.bingham@ideasonboard.com>
+ */
+
+/* To provide basename and asprintf from the GNU library. */
+ #define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "configfs.h"
+
+/* -----------------------------------------------------------------------------
+ * Path handling and support
+ */
+
+static char *path_join(const char *dirname, const char *name)
+{
+       char *path;
+
+       asprintf(&path, "%s/%s", dirname, name);
+
+       return path;
+}
+
+static char *path_glob_first_match(const char *g)
+{
+       glob_t globbuf;
+       char *match = NULL;
+
+       glob(g, 0, NULL, &globbuf);
+
+       if (globbuf.gl_pathc)
+               match = strdup(globbuf.gl_pathv[0]);
+
+       globfree(&globbuf);
+
+       return match;
+}
+
+/* -----------------------------------------------------------------------------
+ * Attribute handling
+ */
+
+static int attribute_read(const char *path, const char *file, char *buf,
+                         unsigned int len)
+{
+       char *f;
+       int ret;
+       int fd;
+
+       f = path_join(path, file);
+       if (!f)
+               return -ENOMEM;
+
+       fd = open(f, O_RDONLY);
+       free(f);
+       if (fd == -1) {
+               printf("Failed to open attribute %s: %s\n", file,
+                      strerror(errno));
+               return -ENOENT;
+       }
+
+       ret = read(fd, buf, len - 1);
+       close(fd);
+
+       if (ret < 0) {
+               printf("Failed to read attribute %s: %s\n", file,
+                      strerror(errno));
+               return -ENODATA;
+       }
+
+       buf[ret] = '\0';
+
+       return 0;
+}
+
+static int attribute_read_uint(const char *path, const char *file,
+                              unsigned int *val)
+{
+       /* 4,294,967,295 */
+       char buf[11];
+       char *endptr;
+       int ret;
+
+       ret = attribute_read(path, file, buf, sizeof(buf));
+       if (ret)
+               return ret;
+
+       errno = 0;
+
+       /* base 0: Autodetect hex, octal, decimal. */
+       *val = strtoul(buf, &endptr, 0);
+       if (errno)
+               return -errno;
+
+       if (endptr == buf)
+               return -ENODATA;
+
+       return 0;
+}
+
+static char *attribute_read_str(const char *path, const char *file)
+{
+       char buf[1024];
+       char *p;
+       int ret;
+
+       ret = attribute_read(path, file, buf, sizeof(buf));
+       if (ret)
+               return NULL;
+
+       p = strrchr(buf, '\n');
+       if (p != buf)
+               *p = '\0';
+
+       return strdup(buf);
+}
+
+/* -----------------------------------------------------------------------------
+ * UDC parsing
+ */
+
+/*
+ * udc_find_video_device - Find the video device node for a UVC function
+ * @udc: The UDC name
+ * @function: The UVC function name
+ *
+ * This function finds the video device node corresponding to a UVC function as
+ * specified by a @function name and @udc name.
+ *
+ * The @function parameter specifies the name of the USB function, usually in
+ * the form "uvc.%u". If NULL the first function found will be used.
+ *
+ * The @udc parameter specifies the name of the UDC. If NULL any UDC that
+ * contains a function matching the @function name will be used.
+ *
+ * Return a pointer to a newly allocated string containing the video device node
+ * full path if the function is found. Otherwise return NULL. The returned
+ * pointer must be freed by the caller with a call to free().
+ */
+static char *udc_find_video_device(const char *udc, const char *function)
+{
+       char *vpath;
+       char *video = NULL;
+       glob_t globbuf;
+       unsigned int i;
+
+       asprintf(&vpath, "/sys/class/udc/%s/device/gadget/video4linux/video*",
+                udc ? udc : "*");
+       if (!vpath)
+               return NULL;
+
+       glob(vpath, 0, NULL, &globbuf);
+       free(vpath);
+
+       for (i = 0; i < globbuf.gl_pathc; ++i) {
+               char *config;
+               bool match;
+
+               /* Match on the first if no search string. */
+               if (!function)
+                       break;
+
+               config = attribute_read_str(globbuf.gl_pathv[i],
+                                           "function_name");
+               match = strcmp(function, config) == 0;
+
+               free(config);
+
+               if (match)
+                       break;
+       }
+
+       if (i < globbuf.gl_pathc) {
+               const char *v = basename(globbuf.gl_pathv[i]);
+
+               video = path_join("/dev", v);
+       }
+
+       globfree(&globbuf);
+
+       return video;
+}
+
+/* -----------------------------------------------------------------------------
+ * Legacy g_webcam support
+ */
+
+static const struct uvc_function_config g_webcam_config = {
+       .streaming_interval = 1,
+       .streaming_maxburst = 0,
+       .streaming_maxpacket = 1024,
+};
+
+static int parse_legacy_g_webcam(const char *udc,
+                                struct uvc_function_config *fc)
+{
+       *fc = g_webcam_config;
+
+       fc->video = udc_find_video_device(udc, NULL);
+
+       return fc->video ? 0 : -ENODEV;
+}
+
+/* -----------------------------------------------------------------------------
+ * ConfigFS support
+ */
+
+/*
+ * configfs_find_uvc_function - Find the ConfigFS full path for a UVC function
+ * @function: The UVC function name
+ *
+ * Return a pointer to a newly allocated string containing the full ConfigFS
+ * path to the function if the function is found. Otherwise return NULL. The
+ * returned pointer must be freed by the caller with a call to free().
+ */
+static char *configfs_find_uvc_function(const char *function)
+{
+       const char *target = function ? function : "*";
+       const char *root;
+       char *func_path;
+       char *path;
+
+       /*
+        * The function description can be provided as a path from the
+        * usb_gadget root "g1/functions/uvc.0", or if there is no ambiguity
+        * over the gadget name, a shortcut "uvc.0" can be provided.
+        */
+       if (!strchr(target, '/'))
+               root = "/sys/kernel/config/usb_gadget/*/functions";
+       else
+               root = "/sys/kernel/config/usb_gadget";
+
+       path = path_join(root, target);
+
+       func_path = path_glob_first_match(path);
+       free(path);
+
+       return func_path;
+}
+
+/*
+ * configfs_free_uvc_function - Free a uvc_function_config object
+ * @fc: The uvc_function_config to be freed
+ *
+ * Free the given @fc function previously allocated by a call to
+ * configfs_parse_uvc_function().
+ */
+void configfs_free_uvc_function(struct uvc_function_config *fc)
+{
+       free(fc->udc);
+       free(fc->video);
+
+       free(fc);
+}
+
+/*
+ * configfs_parse_uvc_function - Parse a UVC function configuration in ConfigFS
+ * @function: The function name
+ *
+ * This function locates and parse the configuration of a UVC function in
+ * ConfigFS as specified by the @function name argument. The function name can
+ * be fully qualified with a gadget name (e.g. "g%u/functions/uvc.%u"), or as a
+ * shortcut can be an unqualified function name (e.g. "uvc.%u"). When the
+ * function name is unqualified, the first function matching the name in any
+ * UDC will be returned.
+ *
+ * Return a pointer to a newly allocated UVC function configuration structure
+ * that contains configuration parameters for the function, if the function is
+ * found. Otherwise return NULL. The returned pointer must be freed by the
+ * caller with a call to free().
+ */
+struct uvc_function_config *configfs_parse_uvc_function(const char *function)
+{
+       struct uvc_function_config *fc;
+       char *fpath;
+       int ret = 0;
+
+       fc = malloc(sizeof *fc);
+       if (fc == NULL)
+               return NULL;
+
+       memset(fc, 0, sizeof *fc);
+
+       /* Find the function in ConfigFS. */
+       fpath = configfs_find_uvc_function(function);
+       if (!fpath) {
+               /*
+                * If the function can't be found attempt legacy parsing to
+                * support the g_webcam gadget. The function parameter contains
+                * a UDC name in that case.
+                */
+               ret = parse_legacy_g_webcam(function, fc);
+               if (ret) {
+                       configfs_free_uvc_function(fc);
+                       fc = NULL;
+               }
+
+               return fc;
+       }
+
+       /*
+        * Parse the function configuration. Remove the gadget name qualifier
+        * from the function name, if any.
+        */
+       if (function)
+               function = basename(function);
+
+       fc->udc = attribute_read_str(fpath, "../../UDC");
+       fc->video = udc_find_video_device(fc->udc, function);
+       if (!fc->video)
+               ret = -ENODEV;
+
+       ret = ret ? : attribute_read_uint(fpath, "streaming_interval",
+                                         &fc->streaming_interval);
+
+       ret = ret ? : attribute_read_uint(fpath, "streaming_maxburst",
+                                         &fc->streaming_maxburst);
+
+       ret = ret ? : attribute_read_uint(fpath, "streaming_maxpacket",
+                                         &fc->streaming_maxpacket);
+
+       if (ret) {
+               configfs_free_uvc_function(fc);
+               fc = NULL;
+       }
+
+       free(fpath);
+
+       return fc;
+}
diff --git a/configfs.h b/configfs.h
new file mode 100644 (file)
index 0000000..56056c1
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * ConfigFS Gadget device handling
+ *
+ * Copyright (C) 2018 Kieran Bingham
+ *
+ * Contact: Kieran Bingham <kieran.bingham@ideasonboard.com>
+ */
+
+#ifndef __CONFIGFS_H__
+#define __CONFIGFS_H__
+
+struct uvc_function_config {
+       char *video;
+       char *udc;
+
+       unsigned int streaming_interval;
+       unsigned int streaming_maxburst;
+       unsigned int streaming_maxpacket;
+};
+
+struct uvc_function_config *configfs_parse_uvc_function(const char *function);
+void configfs_free_uvc_function(struct uvc_function_config *fc);
+
+#endif
index 62377d8..dbf1e10 100644 (file)
@@ -38,6 +38,7 @@
 #include <linux/usb/video.h>
 #include <linux/videodev2.h>
 
+#include "configfs.h"
 #include "events.h"
 #include "tools.h"
 #include "v4l2.h"
@@ -636,12 +637,30 @@ static void uvc_stream_set_event_handler(struct uvc_stream *stream,
 
 static void usage(const char *argv0)
 {
-       fprintf(stderr, "Usage: %s [options]\n", argv0);
+       fprintf(stderr, "Usage: %s [options] <uvc device>\n", argv0);
        fprintf(stderr, "Available options are\n");
        fprintf(stderr, " -c device     V4L2 source device\n");
-       fprintf(stderr, " -d device     Video device\n");
        fprintf(stderr, " -h            Print this help screen and exit\n");
        fprintf(stderr, " -i image      MJPEG image\n");
+       fprintf(stderr, "\n");
+       fprintf(stderr, " <uvc device>  UVC device instance specifier\n");
+       fprintf(stderr, "\n");
+
+       fprintf(stderr, "  For ConfigFS devices the <uvc device> parameter can take the form of a shortened\n");
+       fprintf(stderr, "  function specifier such as: 'uvc.0', or if multiple gadgets are configured, the\n");
+       fprintf(stderr, "  gadget name should be included to prevent ambiguity: 'g1/functions/uvc.0'.\n");
+       fprintf(stderr, "\n");
+       fprintf(stderr, "  For legacy g_webcam UVC instances, this parameter will identify the UDC that the\n");
+       fprintf(stderr, "  UVC function is bound to.\n");
+       fprintf(stderr, "\n");
+       fprintf(stderr, "  The parameter is optional, and if not provided the first UVC function on the first\n");
+       fprintf(stderr, "  gadget identified will be used.\n");
+       fprintf(stderr, "\n");
+       fprintf(stderr, "Example usage:\n");
+       fprintf(stderr, "    uvc-gadget uvc.1\n");
+       fprintf(stderr, "    uvc-gadget g1/functions/uvc.1\n");
+       fprintf(stderr, "\n");
+       fprintf(stderr, "    uvc-gadget musb-hdrc.0.auto\n");
 }
 
 /* Necessary for and only used by signal handler. */
@@ -655,23 +674,20 @@ static void sigint_handler(int signal __attribute__((__unused__)))
 
 int main(int argc, char *argv[])
 {
-       char *uvc_device = "/dev/video0";
+       char *function = NULL;
        char *cap_device = "/dev/video1";
+       struct uvc_function_config *fc;
        struct uvc_stream *stream;
        struct events events;
        int ret = 0;
        int opt;
 
-       while ((opt = getopt(argc, argv, "c:d:h")) != -1) {
+       while ((opt = getopt(argc, argv, "c:h")) != -1) {
                switch (opt) {
                case 'c':
                        cap_device = optarg;
                        break;
 
-               case 'd':
-                       uvc_device = optarg;
-                       break;
-
                case 'h':
                        usage(argv[0]);
                        return 0;
@@ -683,6 +699,15 @@ int main(int argc, char *argv[])
                }
        }
 
+       if (argv[optind] != NULL)
+               function = argv[optind];
+
+       fc = configfs_parse_uvc_function(function);
+       if (!fc) {
+               printf("Failed to identify function configuration\n");
+               return 1;
+       }
+
        /*
         * Create the events handler. Register a signal handler for SIGINT,
         * received when the user presses CTRL-C. This will allow the main loop
@@ -694,7 +719,7 @@ int main(int argc, char *argv[])
        signal(SIGINT, sigint_handler);
 
        /* Create and initialise the stream. */
-       stream = uvc_stream_new(uvc_device, cap_device);
+       stream = uvc_stream_new(fc->video, cap_device);
        if (stream == NULL) {
                ret = 1;
                goto done;
@@ -710,6 +735,7 @@ done:
        /* Cleanup */
        uvc_stream_delete(stream);
        events_cleanup(&events);
+       configfs_free_uvc_function(fc);
 
        return ret;
 }