From 83fa2cb53742f43c3a254d0453bd96048ee29a82 Mon Sep 17 00:00:00 2001 From: Kieran Bingham Date: Fri, 25 May 2018 16:31:32 +0100 Subject: configfs: Provide configfs support 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 Signed-off-by: Laurent Pinchart --- Makefile | 8 +- configfs.c | 343 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ configfs.h | 25 +++++ uvc-gadget.c | 44 ++++++-- 4 files changed, 410 insertions(+), 10 deletions(-) create mode 100644 configfs.c create mode 100644 configfs.h diff --git a/Makefile b/Makefile index 5a5e8f5..4c5d498 100644 --- 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 index 0000000..8d36a29 --- /dev/null +++ b/configfs.c @@ -0,0 +1,343 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * ConfigFS Gadget device handling + * + * Copyright (C) 2018 Kieran Bingham + * + * Contact: Kieran Bingham + */ + +/* To provide basename and asprintf from the GNU library. */ + #define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..56056c1 --- /dev/null +++ b/configfs.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * ConfigFS Gadget device handling + * + * Copyright (C) 2018 Kieran Bingham + * + * Contact: Kieran Bingham + */ + +#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 diff --git a/uvc-gadget.c b/uvc-gadget.c index 62377d8..dbf1e10 100644 --- a/uvc-gadget.c +++ b/uvc-gadget.c @@ -38,6 +38,7 @@ #include #include +#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] \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 instance specifier\n"); + fprintf(stderr, "\n"); + + fprintf(stderr, " For ConfigFS devices the 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; } -- cgit v1.2.3