diff options
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | main.c | 131 | ||||
| -rw-r--r-- | stream.c | 250 | ||||
| -rw-r--r-- | stream.h | 46 | ||||
| -rw-r--r-- | uvc-gadget.c | 707 | ||||
| -rw-r--r-- | uvc.c | 340 | ||||
| -rw-r--r-- | uvc.h | 50 | 
7 files changed, 820 insertions, 708 deletions
| @@ -10,7 +10,9 @@ LDFLAGS		:= -g  OBJS		:= \  			configfs.o \  			events.o \ -			uvc-gadget.o \ +			main.o \ +			stream.o \ +			uvc.o \  			v4l2.o  all: uvc-gadget @@ -0,0 +1,131 @@ +/* + * UVC gadget test application + * + * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + */ + +#include <signal.h> +#include <stdio.h> +#include <unistd.h> + +#include "configfs.h" +#include "events.h" +#include "stream.h" + +static void usage(const char *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, " -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. */ +static struct events *sigint_events; + +static void sigint_handler(int signal __attribute__((__unused__))) +{ +	/* Stop the main loop when the user presses CTRL-C */ +	events_stop(sigint_events); +} + +int main(int argc, char *argv[]) +{ +	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:h")) != -1) { +		switch (opt) { +		case 'c': +			cap_device = optarg; +			break; + +		case 'h': +			usage(argv[0]); +			return 0; + +		default: +			fprintf(stderr, "Invalid option '-%c'\n", opt); +			usage(argv[0]); +			return 1; +		} +	} + +	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 +	 * to be interrupted, and resources to be freed cleanly. +	 */ +	events_init(&events); + +	sigint_events = &events; +	signal(SIGINT, sigint_handler); + +	/* Create and initialise the stream. */ +	stream = uvc_stream_new(fc->video, cap_device); +	if (stream == NULL) { +		ret = 1; +		goto done; +	} + +	uvc_stream_init_uvc(stream, fc); +	uvc_stream_set_event_handler(stream, &events); + +	/* Main capture loop */ +	events_loop(&events); + +done: +	/* Cleanup */ +	uvc_stream_delete(stream); +	events_cleanup(&events); +	configfs_free_uvc_function(fc); + +	return ret; +} diff --git a/stream.c b/stream.c new file mode 100644 index 0000000..b6e0efe --- /dev/null +++ b/stream.c @@ -0,0 +1,250 @@ +/* + * UVC stream handling + * + * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "events.h" +#include "stream.h" +#include "uvc.h" +#include "v4l2.h" + +/* --------------------------------------------------------------------------- + * Video streaming + */ + +static void capture_video_process(void *d) +{ +	struct uvc_stream *stream = d; +	struct v4l2_video_buffer buf; +	int ret; + +	ret = v4l2_dequeue_buffer(stream->cap, &buf); +	if (ret < 0) +		return; + +	v4l2_queue_buffer(stream->uvc->vdev, &buf); +} + +static void uvc_video_process(void *d) +{ +	struct uvc_stream *stream = d; +	struct v4l2_video_buffer buf; +	int ret; + +	ret = v4l2_dequeue_buffer(stream->uvc->vdev, &buf); +	if (ret < 0) +		return; + +	v4l2_queue_buffer(stream->cap, &buf); +} + +static int uvc_video_enable(struct uvc_stream *stream, int enable) +{ +	struct v4l2_device *cap = stream->cap; +	struct uvc_device *dev = stream->uvc; +	int ret; + +	if (!enable) { +		printf("Stopping video stream.\n"); +		events_unwatch_fd(stream->events, dev->vdev->fd, EVENT_WRITE); +		v4l2_stream_off(dev->vdev); +		v4l2_free_buffers(dev->vdev); +		return 0; +	} + +	printf("Starting video stream.\n"); + +	ret = v4l2_alloc_buffers(dev->vdev, V4L2_MEMORY_DMABUF, 4); +	if (ret < 0) { +		printf("Failed to allocate buffers: %s (%d)\n", strerror(-ret), -ret); +		return ret; +	} + +	ret = v4l2_import_buffers(dev->vdev, dev->vdev->nbufs, cap->buffers); +	if (ret < 0) { +		printf("Failed to import buffers: %s (%d)\n", strerror(-ret), -ret); +		goto error; +	} + +	v4l2_stream_on(dev->vdev); +	events_watch_fd(stream->events, dev->vdev->fd, EVENT_WRITE, +			uvc_video_process, stream); + +	return 0; + +error: +	v4l2_free_buffers(dev->vdev); +	return ret; +} + +static int capture_video_stream(struct uvc_stream *stream, int enable) +{ +	struct v4l2_device *cap = stream->cap; +	unsigned int i; +	int ret; + +	if (!enable) { +		printf("Stopping video capture stream.\n"); +		events_unwatch_fd(stream->events, cap->fd, EVENT_READ); +		v4l2_stream_off(cap); +		v4l2_free_buffers(cap); +		return 0; +	} + +	printf("Starting video capture stream.\n"); + +	ret = v4l2_alloc_buffers(cap, V4L2_MEMORY_MMAP, 4); +	if (ret < 0) { +		printf("Failed to allocate capture buffers.\n"); +		return ret; +	} + +	ret = v4l2_export_buffers(cap); +	if (ret < 0) { +		printf("Failed to export buffers.\n"); +		goto error; +	} + +	for (i = 0; i < cap->nbufs; ++i) { +		struct v4l2_video_buffer *buf = &cap->buffers[i]; + +		ret = v4l2_queue_buffer(cap, buf); +		if (ret < 0) +			goto error; +	} + +	v4l2_stream_on(cap); +	events_watch_fd(stream->events, cap->fd, EVENT_READ, +			capture_video_process, stream); + +	return 0; + +error: +	v4l2_free_buffers(cap); +	return ret; +} + +void uvc_stream_enable(struct uvc_stream *stream, int enable) +{ +	if (enable) { +		capture_video_stream(stream, 1); +		uvc_video_enable(stream, 1); +	} else { +		uvc_video_enable(stream, 0); +		capture_video_stream(stream, 0); +	} +} + +int uvc_stream_set_format(struct uvc_stream *stream) +{ +	struct v4l2_device *cap = stream->cap; +	struct uvc_device *dev = stream->uvc; +	struct v4l2_pix_format fmt; +	int ret; + +	printf("Setting format to 0x%08x %ux%u\n", +		dev->fcc, dev->width, dev->height); + +	memset(&fmt, 0, sizeof fmt); +	fmt.width = dev->width; +	fmt.height = dev->height; +	fmt.pixelformat = dev->fcc; +	fmt.field = V4L2_FIELD_NONE; +	if (dev->fcc == V4L2_PIX_FMT_MJPEG) +		fmt.sizeimage = dev->maxsize * 1.5; + +	ret = v4l2_set_format(dev->vdev, &fmt); +	if (ret < 0) +		return ret; + +	return v4l2_set_format(cap, &fmt); +} + +static int uvc_video_init(struct uvc_device *dev __attribute__((__unused__))) +{ +	return 0; +} + +/* --------------------------------------------------------------------------- + * Stream handling + */ + +struct uvc_stream *uvc_stream_new(const char *uvc_device, +				  const char *cap_device) +{ +	struct uvc_stream *stream; + +	stream = malloc(sizeof(*stream)); +	if (stream == NULL) +		return NULL; + +	memset(stream, 0, sizeof(*stream)); + +	stream->cap = v4l2_open(cap_device); +	if (stream->cap == NULL) +		goto error; + +	stream->uvc = uvc_open(uvc_device); +	if (stream->uvc == NULL) +		goto error; + +	return stream; + +error: +	if (stream->cap) +		v4l2_close(stream->cap); + +	free(stream); +	return NULL; +} + +void uvc_stream_delete(struct uvc_stream *stream) +{ +	if (stream == NULL) +		return; + +	v4l2_close(stream->cap); +	uvc_close(stream->uvc); + +	free(stream); +} + +void uvc_stream_init_uvc(struct uvc_stream *stream, +			 struct uvc_function_config *fc) +{ +	/* +	 * FIXME: The maximum size should be specified per format and frame. +	 */ +	stream->uvc->maxsize = 0; +	stream->uvc->fc = fc; + +	uvc_events_init(stream->uvc); +	uvc_video_init(stream->uvc); +} + +void uvc_stream_set_event_handler(struct uvc_stream *stream, +				  struct events *events) +{ +	stream->events = events; + +	events_watch_fd(stream->events, stream->uvc->vdev->fd, EVENT_EXCEPTION, +			uvc_events_process, stream); +} diff --git a/stream.h b/stream.h new file mode 100644 index 0000000..f3662d8 --- /dev/null +++ b/stream.h @@ -0,0 +1,46 @@ +/* + * UVC gadget test application + * + * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + */ + +#ifndef __STREAM_H__ +#define __STREAM_H__ + +struct events; +struct uvc_device; +struct uvc_function_config; +struct v4l2_device; + +struct uvc_stream +{ +	struct v4l2_device *cap; +	struct uvc_device *uvc; + +	struct events *events; +}; + +struct uvc_stream *uvc_stream_new(const char *uvc_device, +				  const char *cap_device); +void uvc_stream_init_uvc(struct uvc_stream *stream, +			 struct uvc_function_config *fc); +void uvc_stream_set_event_handler(struct uvc_stream *stream, +				  struct events *events); +void uvc_stream_delete(struct uvc_stream *stream); +int uvc_stream_set_format(struct uvc_stream *stream); +void uvc_stream_enable(struct uvc_stream *stream, int enable); + +#endif /* __STREAM_H__ */ diff --git a/uvc-gadget.c b/uvc-gadget.c deleted file mode 100644 index 8ccfa07..0000000 --- a/uvc-gadget.c +++ /dev/null @@ -1,707 +0,0 @@ -/* - * UVC gadget test application - * - * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - */ - -#include <sys/ioctl.h> -#include <sys/mman.h> -#include <sys/select.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <sys/types.h> - -#include <errno.h> -#include <fcntl.h> -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <linux/usb/ch9.h> -#include <linux/usb/g_uvc.h> -#include <linux/usb/video.h> -#include <linux/videodev2.h> - -#include "configfs.h" -#include "events.h" -#include "tools.h" -#include "v4l2.h" - -struct uvc_device -{ -	struct v4l2_device *vdev; - -	struct uvc_function_config *fc; - -	struct uvc_streaming_control probe; -	struct uvc_streaming_control commit; - -	int control; - -	unsigned int fcc; -	unsigned int width; -	unsigned int height; -	unsigned int maxsize; -}; - -struct uvc_stream -{ -	struct v4l2_device *cap; -	struct uvc_device *uvc; - -	struct events *events; -}; - -static struct uvc_device * -uvc_open(const char *devname) -{ -	struct uvc_device *dev; - -	dev = malloc(sizeof *dev); -	if (dev == NULL) -		return NULL; - -	memset(dev, 0, sizeof *dev); - -	dev->vdev = v4l2_open(devname); -	if (dev->vdev == NULL) { -		free(dev); -		return NULL; -	} - -	return dev; -} - -static void -uvc_close(struct uvc_device *dev) -{ -	v4l2_close(dev->vdev); -	dev->vdev = NULL; - -	free(dev); -} - -/* --------------------------------------------------------------------------- - * Video streaming - */ - -static void capture_video_process(void *d) -{ -	struct uvc_stream *stream = d; -	struct v4l2_video_buffer buf; -	int ret; - -	ret = v4l2_dequeue_buffer(stream->cap, &buf); -	if (ret < 0) -		return; - -	v4l2_queue_buffer(stream->uvc->vdev, &buf); -} - -static void -uvc_video_process(void *d) -{ -	struct uvc_stream *stream = d; -	struct v4l2_video_buffer buf; -	int ret; - -	ret = v4l2_dequeue_buffer(stream->uvc->vdev, &buf); -	if (ret < 0) -		return; - -	v4l2_queue_buffer(stream->cap, &buf); -} - -static int -uvc_video_stream(struct uvc_stream *stream, int enable) -{ -	struct v4l2_device *cap = stream->cap; -	struct uvc_device *dev = stream->uvc; -	int ret; - -	if (!enable) { -		printf("Stopping video stream.\n"); -		events_unwatch_fd(stream->events, dev->vdev->fd, EVENT_WRITE); -		v4l2_stream_off(dev->vdev); -		v4l2_free_buffers(dev->vdev); -		return 0; -	} - -	printf("Starting video stream.\n"); - -	ret = v4l2_alloc_buffers(dev->vdev, V4L2_MEMORY_DMABUF, 4); -	if (ret < 0) { -		printf("Failed to allocate buffers: %s (%d)\n", strerror(-ret), -ret); -		return ret; -	} - -	ret = v4l2_import_buffers(dev->vdev, dev->vdev->nbufs, cap->buffers); -	if (ret < 0) { -		printf("Failed to import buffers: %s (%d)\n", strerror(-ret), -ret); -		goto error; -	} - -	v4l2_stream_on(dev->vdev); -	events_watch_fd(stream->events, dev->vdev->fd, EVENT_WRITE, -			uvc_video_process, stream); - -	return 0; - -error: -	v4l2_free_buffers(dev->vdev); -	return ret; -} - -static int capture_video_stream(struct uvc_stream *stream, int enable) -{ -	struct v4l2_device *cap = stream->cap; -	unsigned int i; -	int ret; - -	if (!enable) { -		printf("Stopping video capture stream.\n"); -		events_unwatch_fd(stream->events, cap->fd, EVENT_READ); -		v4l2_stream_off(cap); -		v4l2_free_buffers(cap); -		return 0; -	} - -	printf("Starting video capture stream.\n"); - -	ret = v4l2_alloc_buffers(cap, V4L2_MEMORY_MMAP, 4); -	if (ret < 0) { -		printf("Failed to allocate capture buffers.\n"); -		return ret; -	} - -	ret = v4l2_export_buffers(cap); -	if (ret < 0) { -		printf("Failed to export buffers.\n"); -		goto error; -	} - -	for (i = 0; i < cap->nbufs; ++i) { -		struct v4l2_video_buffer *buf = &cap->buffers[i]; - -		ret = v4l2_queue_buffer(cap, buf); -		if (ret < 0) -			goto error; -	} - -	v4l2_stream_on(cap); -	events_watch_fd(stream->events, cap->fd, EVENT_READ, -			capture_video_process, stream); - -	return 0; - -error: -	v4l2_free_buffers(cap); -	return ret; -} - -static int -uvc_video_set_format(struct uvc_stream *stream) -{ -	struct v4l2_device *cap = stream->cap; -	struct uvc_device *dev = stream->uvc; -	struct v4l2_pix_format fmt; -	int ret; - -	printf("Setting format to 0x%08x %ux%u\n", -		dev->fcc, dev->width, dev->height); - -	memset(&fmt, 0, sizeof fmt); -	fmt.width = dev->width; -	fmt.height = dev->height; -	fmt.pixelformat = dev->fcc; -	fmt.field = V4L2_FIELD_NONE; -	if (dev->fcc == V4L2_PIX_FMT_MJPEG) -		fmt.sizeimage = dev->maxsize * 1.5; - -	ret = v4l2_set_format(dev->vdev, &fmt); -	if (ret < 0) -		return ret; - -	return v4l2_set_format(cap, &fmt); -} - -static int -uvc_video_init(struct uvc_device *dev __attribute__((__unused__))) -{ -	return 0; -} - -/* --------------------------------------------------------------------------- - * Request processing - */ - -static void -uvc_fill_streaming_control(struct uvc_device *dev, -			   struct uvc_streaming_control *ctrl, -			   int iformat, int iframe, unsigned int ival) -{ -	const struct uvc_function_config_format *format; -	const struct uvc_function_config_frame *frame; -	unsigned int i; - -	/* -	 * Restrict the iformat, iframe and ival to valid values. Negative -	 * values for iformat or iframe will result in the maximum valid value -	 * being selected. -	 */ -        iformat = clamp((unsigned int)iformat, 1U, -                        dev->fc->streaming.num_formats); -	format = &dev->fc->streaming.formats[iformat-1]; - -	iframe = clamp((unsigned int)iframe, 1U, format->num_frames); -	frame = &format->frames[iframe-1]; - -	for (i = 0; i < frame->num_intervals; ++i) { -		if (ival <= frame->intervals[i]) { -			ival = frame->intervals[i]; -			break; -		} -	} - -	if (i == frame->num_intervals) -		ival = frame->intervals[frame->num_intervals-1]; - -	memset(ctrl, 0, sizeof *ctrl); - -	ctrl->bmHint = 1; -	ctrl->bFormatIndex = iformat; -	ctrl->bFrameIndex = iframe ; -	ctrl->dwFrameInterval = ival; - -	switch (format->fcc) { -	case V4L2_PIX_FMT_YUYV: -		ctrl->dwMaxVideoFrameSize = frame->width * frame->height * 2; -		break; -	case V4L2_PIX_FMT_MJPEG: -		ctrl->dwMaxVideoFrameSize = dev->maxsize; -		break; -	} - -	ctrl->dwMaxPayloadTransferSize = dev->fc->streaming.ep.wMaxPacketSize; -	ctrl->bmFramingInfo = 3; -	ctrl->bPreferedVersion = 1; -	ctrl->bMaxVersion = 1; -} - -static void -uvc_events_process_standard(struct uvc_device *dev, -			    const struct usb_ctrlrequest *ctrl, -			    struct uvc_request_data *resp) -{ -	printf("standard request\n"); -	(void)dev; -	(void)ctrl; -	(void)resp; -} - -static void -uvc_events_process_control(struct uvc_device *dev, uint8_t req, uint8_t cs, -			   struct uvc_request_data *resp) -{ -	printf("control request (req %02x cs %02x)\n", req, cs); -	(void)dev; -	(void)resp; -} - -static void -uvc_events_process_streaming(struct uvc_device *dev, uint8_t req, uint8_t cs, -			     struct uvc_request_data *resp) -{ -	struct uvc_streaming_control *ctrl; - -	printf("streaming request (req %02x cs %02x)\n", req, cs); - -	if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL) -		return; - -	ctrl = (struct uvc_streaming_control *)&resp->data; -	resp->length = sizeof *ctrl; - -	switch (req) { -	case UVC_SET_CUR: -		dev->control = cs; -		resp->length = 34; -		break; - -	case UVC_GET_CUR: -		if (cs == UVC_VS_PROBE_CONTROL) -			memcpy(ctrl, &dev->probe, sizeof *ctrl); -		else -			memcpy(ctrl, &dev->commit, sizeof *ctrl); -		break; - -	case UVC_GET_MIN: -	case UVC_GET_MAX: -	case UVC_GET_DEF: -		uvc_fill_streaming_control(dev, ctrl, req == UVC_GET_MAX ? -1 : 1, -					   req == UVC_GET_MAX ? -1 : 1, 0); -		break; - -	case UVC_GET_RES: -		memset(ctrl, 0, sizeof *ctrl); -		break; - -	case UVC_GET_LEN: -		resp->data[0] = 0x00; -		resp->data[1] = 0x22; -		resp->length = 2; -		break; - -	case UVC_GET_INFO: -		resp->data[0] = 0x03; -		resp->length = 1; -		break; -	} -} - -static void -uvc_events_process_class(struct uvc_device *dev, -			 const struct usb_ctrlrequest *ctrl, -			 struct uvc_request_data *resp) -{ -	unsigned int interface = ctrl->wIndex & 0xff; - -	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE) -		return; - -	if (interface == dev->fc->control.intf.bInterfaceNumber) -		uvc_events_process_control(dev, ctrl->bRequest, ctrl->wValue >> 8, resp); -	else if (interface == dev->fc->streaming.intf.bInterfaceNumber) -		uvc_events_process_streaming(dev, ctrl->bRequest, ctrl->wValue >> 8, resp); -} - -static void -uvc_events_process_setup(struct uvc_device *dev, -			 const struct usb_ctrlrequest *ctrl, -			 struct uvc_request_data *resp) -{ -	dev->control = 0; - -	printf("bRequestType %02x bRequest %02x wValue %04x wIndex %04x " -		"wLength %04x\n", ctrl->bRequestType, ctrl->bRequest, -		ctrl->wValue, ctrl->wIndex, ctrl->wLength); - -	switch (ctrl->bRequestType & USB_TYPE_MASK) { -	case USB_TYPE_STANDARD: -		uvc_events_process_standard(dev, ctrl, resp); -		break; - -	case USB_TYPE_CLASS: -		uvc_events_process_class(dev, ctrl, resp); -		break; - -	default: -		break; -	} -} - -static void -uvc_events_process_data(struct uvc_stream *stream, -			const struct uvc_request_data *data) -{ -	struct uvc_device *dev = stream->uvc; -	const struct uvc_streaming_control *ctrl = -		(const struct uvc_streaming_control *)&data->data; -	struct uvc_streaming_control *target; - -	switch (dev->control) { -	case UVC_VS_PROBE_CONTROL: -		printf("setting probe control, length = %d\n", data->length); -		target = &dev->probe; -		break; - -	case UVC_VS_COMMIT_CONTROL: -		printf("setting commit control, length = %d\n", data->length); -		target = &dev->commit; -		break; - -	default: -		printf("setting unknown control, length = %d\n", data->length); -		return; -	} - -	uvc_fill_streaming_control(dev, target, ctrl->bFormatIndex, -				   ctrl->bFrameIndex, ctrl->dwFrameInterval); - -	if (dev->control == UVC_VS_COMMIT_CONTROL) { -		const struct uvc_function_config_format *format; -		const struct uvc_function_config_frame *frame; - -		format = &dev->fc->streaming.formats[target->bFormatIndex-1]; -		frame = &format->frames[target->bFrameIndex-1]; - -		dev->fcc = format->fcc; -		dev->width = frame->width; -		dev->height = frame->height; - -		uvc_video_set_format(stream); -	} -} - -static void -uvc_events_process(void *d) -{ -	struct uvc_stream *stream = d; -	struct uvc_device *dev = stream->uvc; -	struct v4l2_event v4l2_event; -	const struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; -	struct uvc_request_data resp; -	int ret; - -	ret = ioctl(dev->vdev->fd, VIDIOC_DQEVENT, &v4l2_event); -	if (ret < 0) { -		printf("VIDIOC_DQEVENT failed: %s (%d)\n", strerror(errno), -			errno); -		return; -	} - -	memset(&resp, 0, sizeof resp); -	resp.length = -EL2HLT; - -	switch (v4l2_event.type) { -	case UVC_EVENT_CONNECT: -	case UVC_EVENT_DISCONNECT: -		return; - -	case UVC_EVENT_SETUP: -		uvc_events_process_setup(dev, &uvc_event->req, &resp); -		break; - -	case UVC_EVENT_DATA: -		uvc_events_process_data(stream, &uvc_event->data); -		return; - -	case UVC_EVENT_STREAMON: -		capture_video_stream(stream, 1); -		uvc_video_stream(stream, 1); -		return; - -	case UVC_EVENT_STREAMOFF: -		uvc_video_stream(stream, 0); -		capture_video_stream(stream, 0); -		return; -	} - -	ioctl(dev->vdev->fd, UVCIOC_SEND_RESPONSE, &resp); -	if (ret < 0) { -		printf("UVCIOC_S_EVENT failed: %s (%d)\n", strerror(errno), -			errno); -		return; -	} -} - -static void -uvc_events_init(struct uvc_device *dev) -{ -	struct v4l2_event_subscription sub; - -	/* Default to the minimum values. */ -	uvc_fill_streaming_control(dev, &dev->probe, 1, 1, 0); -	uvc_fill_streaming_control(dev, &dev->commit, 1, 1, 0); - -	memset(&sub, 0, sizeof sub); -	sub.type = UVC_EVENT_SETUP; -	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); -	sub.type = UVC_EVENT_DATA; -	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); -	sub.type = UVC_EVENT_STREAMON; -	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); -	sub.type = UVC_EVENT_STREAMOFF; -	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); -} - -/* --------------------------------------------------------------------------- - * Stream handling - */ - -static struct uvc_stream *uvc_stream_new(const char *uvc_device, -					 const char *cap_device) -{ -	struct uvc_stream *stream; - -	stream = malloc(sizeof(*stream)); -	if (stream == NULL) -		return NULL; - -	memset(stream, 0, sizeof(*stream)); - -	stream->cap = v4l2_open(cap_device); -	if (stream->cap == NULL) -		goto error; - -	stream->uvc = uvc_open(uvc_device); -	if (stream->uvc == NULL) -		goto error; - -	return stream; - -error: -	if (stream->cap) -		v4l2_close(stream->cap); - -	free(stream); -	return NULL; -} - -static void uvc_stream_delete(struct uvc_stream *stream) -{ -	if (stream == NULL) -		return; - -	v4l2_close(stream->cap); -	uvc_close(stream->uvc); - -	free(stream); -} - -static void uvc_stream_init_uvc(struct uvc_stream *stream, -				struct uvc_function_config *fc) -{ -	/* -	 * FIXME: The maximum size should be specified per format and frame. -	 */ -	stream->uvc->maxsize = 0; -	stream->uvc->fc = fc; - -	uvc_events_init(stream->uvc); -	uvc_video_init(stream->uvc); -} - -static void uvc_stream_set_event_handler(struct uvc_stream *stream, -					 struct events *events) -{ -	stream->events = events; - -	events_watch_fd(stream->events, stream->uvc->vdev->fd, EVENT_EXCEPTION, -			uvc_events_process, stream); -} - -/* --------------------------------------------------------------------------- - * main - */ - -static void usage(const char *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, " -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. */ -static struct events *sigint_events; - -static void sigint_handler(int signal __attribute__((__unused__))) -{ -	/* Stop the main loop when the user presses CTRL-C */ -	events_stop(sigint_events); -} - -int main(int argc, char *argv[]) -{ -	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:h")) != -1) { -		switch (opt) { -		case 'c': -			cap_device = optarg; -			break; - -		case 'h': -			usage(argv[0]); -			return 0; - -		default: -			fprintf(stderr, "Invalid option '-%c'\n", opt); -			usage(argv[0]); -			return 1; -		} -	} - -	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 -	 * to be interrupted, and resources to be freed cleanly. -	 */ -	events_init(&events); - -	sigint_events = &events; -	signal(SIGINT, sigint_handler); - -	/* Create and initialise the stream. */ -	stream = uvc_stream_new(fc->video, cap_device); -	if (stream == NULL) { -		ret = 1; -		goto done; -	} - -	uvc_stream_init_uvc(stream, fc); -	uvc_stream_set_event_handler(stream, &events); - -	/* Main capture loop */ -	events_loop(&events); - -done: -	/* Cleanup */ -	uvc_stream_delete(stream); -	events_cleanup(&events); -	configfs_free_uvc_function(fc); - -	return ret; -} @@ -0,0 +1,340 @@ +/* + * UVC protocol handling + * + * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + */ + +#include <errno.h> +#include <linux/usb/ch9.h> +#include <linux/usb/g_uvc.h> +#include <linux/usb/video.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "configfs.h" +#include "stream.h" +#include "tools.h" +#include "uvc.h" +#include "v4l2.h" + +struct uvc_device *uvc_open(const char *devname) +{ +	struct uvc_device *dev; + +	dev = malloc(sizeof *dev); +	if (dev == NULL) +		return NULL; + +	memset(dev, 0, sizeof *dev); + +	dev->vdev = v4l2_open(devname); +	if (dev->vdev == NULL) { +		free(dev); +		return NULL; +	} + +	return dev; +} + +void uvc_close(struct uvc_device *dev) +{ +	v4l2_close(dev->vdev); +	dev->vdev = NULL; + +	free(dev); +} + +/* --------------------------------------------------------------------------- + * Request processing + */ + +static void +uvc_fill_streaming_control(struct uvc_device *dev, +			   struct uvc_streaming_control *ctrl, +			   int iformat, int iframe, unsigned int ival) +{ +	const struct uvc_function_config_format *format; +	const struct uvc_function_config_frame *frame; +	unsigned int i; + +	/* +	 * Restrict the iformat, iframe and ival to valid values. Negative +	 * values for iformat or iframe will result in the maximum valid value +	 * being selected. +	 */ +        iformat = clamp((unsigned int)iformat, 1U, +                        dev->fc->streaming.num_formats); +	format = &dev->fc->streaming.formats[iformat-1]; + +	iframe = clamp((unsigned int)iframe, 1U, format->num_frames); +	frame = &format->frames[iframe-1]; + +	for (i = 0; i < frame->num_intervals; ++i) { +		if (ival <= frame->intervals[i]) { +			ival = frame->intervals[i]; +			break; +		} +	} + +	if (i == frame->num_intervals) +		ival = frame->intervals[frame->num_intervals-1]; + +	memset(ctrl, 0, sizeof *ctrl); + +	ctrl->bmHint = 1; +	ctrl->bFormatIndex = iformat; +	ctrl->bFrameIndex = iframe ; +	ctrl->dwFrameInterval = ival; + +	switch (format->fcc) { +	case V4L2_PIX_FMT_YUYV: +		ctrl->dwMaxVideoFrameSize = frame->width * frame->height * 2; +		break; +	case V4L2_PIX_FMT_MJPEG: +		ctrl->dwMaxVideoFrameSize = dev->maxsize; +		break; +	} + +	ctrl->dwMaxPayloadTransferSize = dev->fc->streaming.ep.wMaxPacketSize; +	ctrl->bmFramingInfo = 3; +	ctrl->bPreferedVersion = 1; +	ctrl->bMaxVersion = 1; +} + +static void +uvc_events_process_standard(struct uvc_device *dev, +			    const struct usb_ctrlrequest *ctrl, +			    struct uvc_request_data *resp) +{ +	printf("standard request\n"); +	(void)dev; +	(void)ctrl; +	(void)resp; +} + +static void +uvc_events_process_control(struct uvc_device *dev, uint8_t req, uint8_t cs, +			   struct uvc_request_data *resp) +{ +	printf("control request (req %02x cs %02x)\n", req, cs); +	(void)dev; +	(void)resp; +} + +static void +uvc_events_process_streaming(struct uvc_device *dev, uint8_t req, uint8_t cs, +			     struct uvc_request_data *resp) +{ +	struct uvc_streaming_control *ctrl; + +	printf("streaming request (req %02x cs %02x)\n", req, cs); + +	if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL) +		return; + +	ctrl = (struct uvc_streaming_control *)&resp->data; +	resp->length = sizeof *ctrl; + +	switch (req) { +	case UVC_SET_CUR: +		dev->control = cs; +		resp->length = 34; +		break; + +	case UVC_GET_CUR: +		if (cs == UVC_VS_PROBE_CONTROL) +			memcpy(ctrl, &dev->probe, sizeof *ctrl); +		else +			memcpy(ctrl, &dev->commit, sizeof *ctrl); +		break; + +	case UVC_GET_MIN: +	case UVC_GET_MAX: +	case UVC_GET_DEF: +		uvc_fill_streaming_control(dev, ctrl, req == UVC_GET_MAX ? -1 : 1, +					   req == UVC_GET_MAX ? -1 : 1, 0); +		break; + +	case UVC_GET_RES: +		memset(ctrl, 0, sizeof *ctrl); +		break; + +	case UVC_GET_LEN: +		resp->data[0] = 0x00; +		resp->data[1] = 0x22; +		resp->length = 2; +		break; + +	case UVC_GET_INFO: +		resp->data[0] = 0x03; +		resp->length = 1; +		break; +	} +} + +static void +uvc_events_process_class(struct uvc_device *dev, +			 const struct usb_ctrlrequest *ctrl, +			 struct uvc_request_data *resp) +{ +	unsigned int interface = ctrl->wIndex & 0xff; + +	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE) +		return; + +	if (interface == dev->fc->control.intf.bInterfaceNumber) +		uvc_events_process_control(dev, ctrl->bRequest, ctrl->wValue >> 8, resp); +	else if (interface == dev->fc->streaming.intf.bInterfaceNumber) +		uvc_events_process_streaming(dev, ctrl->bRequest, ctrl->wValue >> 8, resp); +} + +static void +uvc_events_process_setup(struct uvc_device *dev, +			 const struct usb_ctrlrequest *ctrl, +			 struct uvc_request_data *resp) +{ +	dev->control = 0; + +	printf("bRequestType %02x bRequest %02x wValue %04x wIndex %04x " +		"wLength %04x\n", ctrl->bRequestType, ctrl->bRequest, +		ctrl->wValue, ctrl->wIndex, ctrl->wLength); + +	switch (ctrl->bRequestType & USB_TYPE_MASK) { +	case USB_TYPE_STANDARD: +		uvc_events_process_standard(dev, ctrl, resp); +		break; + +	case USB_TYPE_CLASS: +		uvc_events_process_class(dev, ctrl, resp); +		break; + +	default: +		break; +	} +} + +static void +uvc_events_process_data(struct uvc_stream *stream, +			const struct uvc_request_data *data) +{ +	struct uvc_device *dev = stream->uvc; +	const struct uvc_streaming_control *ctrl = +		(const struct uvc_streaming_control *)&data->data; +	struct uvc_streaming_control *target; + +	switch (dev->control) { +	case UVC_VS_PROBE_CONTROL: +		printf("setting probe control, length = %d\n", data->length); +		target = &dev->probe; +		break; + +	case UVC_VS_COMMIT_CONTROL: +		printf("setting commit control, length = %d\n", data->length); +		target = &dev->commit; +		break; + +	default: +		printf("setting unknown control, length = %d\n", data->length); +		return; +	} + +	uvc_fill_streaming_control(dev, target, ctrl->bFormatIndex, +				   ctrl->bFrameIndex, ctrl->dwFrameInterval); + +	if (dev->control == UVC_VS_COMMIT_CONTROL) { +		const struct uvc_function_config_format *format; +		const struct uvc_function_config_frame *frame; + +		format = &dev->fc->streaming.formats[target->bFormatIndex-1]; +		frame = &format->frames[target->bFrameIndex-1]; + +		dev->fcc = format->fcc; +		dev->width = frame->width; +		dev->height = frame->height; + +		uvc_stream_set_format(stream); +	} +} + +void uvc_events_process(void *d) +{ +	struct uvc_stream *stream = d; +	struct uvc_device *dev = stream->uvc; +	struct v4l2_event v4l2_event; +	const struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; +	struct uvc_request_data resp; +	int ret; + +	ret = ioctl(dev->vdev->fd, VIDIOC_DQEVENT, &v4l2_event); +	if (ret < 0) { +		printf("VIDIOC_DQEVENT failed: %s (%d)\n", strerror(errno), +			errno); +		return; +	} + +	memset(&resp, 0, sizeof resp); +	resp.length = -EL2HLT; + +	switch (v4l2_event.type) { +	case UVC_EVENT_CONNECT: +	case UVC_EVENT_DISCONNECT: +		return; + +	case UVC_EVENT_SETUP: +		uvc_events_process_setup(dev, &uvc_event->req, &resp); +		break; + +	case UVC_EVENT_DATA: +		uvc_events_process_data(stream, &uvc_event->data); +		return; + +	case UVC_EVENT_STREAMON: +		uvc_stream_enable(stream, 1); +		return; + +	case UVC_EVENT_STREAMOFF: +		uvc_stream_enable(stream, 0); +		return; +	} + +	ioctl(dev->vdev->fd, UVCIOC_SEND_RESPONSE, &resp); +	if (ret < 0) { +		printf("UVCIOC_S_EVENT failed: %s (%d)\n", strerror(errno), +			errno); +		return; +	} +} + +void uvc_events_init(struct uvc_device *dev) +{ +	struct v4l2_event_subscription sub; + +	/* Default to the minimum values. */ +	uvc_fill_streaming_control(dev, &dev->probe, 1, 1, 0); +	uvc_fill_streaming_control(dev, &dev->commit, 1, 1, 0); + +	memset(&sub, 0, sizeof sub); +	sub.type = UVC_EVENT_SETUP; +	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); +	sub.type = UVC_EVENT_DATA; +	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); +	sub.type = UVC_EVENT_STREAMON; +	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); +	sub.type = UVC_EVENT_STREAMOFF; +	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); +} @@ -0,0 +1,50 @@ +/* + * UVC protocol handling + * + * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + */ + +#ifndef __UVC_H__ +#define __UVC_H__ + +#include <linux/usb/video.h> + +struct v4l2_device; +struct uvc_function_config; + +struct uvc_device +{ +	struct v4l2_device *vdev; + +	struct uvc_function_config *fc; + +	struct uvc_streaming_control probe; +	struct uvc_streaming_control commit; + +	int control; + +	unsigned int fcc; +	unsigned int width; +	unsigned int height; +	unsigned int maxsize; +}; + +void uvc_events_process(void *d); +struct uvc_device *uvc_open(const char *devname); +void uvc_close(struct uvc_device *dev); +void uvc_events_init(struct uvc_device *dev); + +#endif /* __UVC_H__ */ | 
