Re: [PATCH 3/3] usb: gadget: uvc: add format/frame handling code

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Michael,

Thank you for the patch.

On Mon, May 31, 2021 at 12:22:39AM +0200, Michael Grzeschik wrote:
> The hostside format selection is currently only done in userspace, as
> the events for SET_CUR and GET_CUR is allways moved to the application

s/is allways/are always/

> layer. Since the v4l2 devices parses the configfs data, the format

s/devices/device/

> negotiation can be done in the kernel. This patch adds the functions to
> set the curent configuration and still forwards all unknown events to

s/curent/current/

Reading just from this sentence, it sounds like only unknown events are
forwarded to userspace, and all known events are not. I see from the
code that this is indeed the case.

s/and still fowards/while continuing to forward/

> the userspace level.

Ah, I see a lot of this is lifted from our uvc-gadget :)

Which is fine. It's nice to see this processing moved to the kernel,
especially since configfs already has the necessary information.

> 
> Signed-off-by: Michael Grzeschik <m.grzeschik@xxxxxxxxxxxxxx>
> ---
>  drivers/usb/gadget/function/f_uvc.c     | 270 ++++++++++++++++++++++--
>  drivers/usb/gadget/function/uvc.h       |  14 ++
>  drivers/usb/gadget/function/uvc_v4l2.c  |  16 +-
>  drivers/usb/gadget/function/uvc_video.c |  15 +-
>  4 files changed, 297 insertions(+), 18 deletions(-)
> 
> diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
> index 7945ea93a775a..cd40a063751ae 100644
> --- a/drivers/usb/gadget/function/f_uvc.c
> +++ b/drivers/usb/gadget/function/f_uvc.c
> @@ -201,21 +201,253 @@ static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
>   * Control requests
>   */
>  
> +void uvc_fill_streaming_control(struct uvc_device *uvc,
> +			   struct uvc_streaming_control *ctrl,
> +			   int iframe, int iformat, unsigned int ival)

Indentation.

> +{
> +	struct uvcg_format *ufmt;
> +	struct uvcg_frame *ufrm;
> +	unsigned int i;
> +
> +	/* Restrict the iformat, iframe and ival to valid values. Negative
> +	 * values for ifrmat and iframe will result in the maximum valid value
> +	 * being selected
> +	 */
> +	iformat = clamp((unsigned int)iformat, 1U, (unsigned int)uvc->nformats);
> +	ufmt = uvc->fmt[iformat-1];
> +
> +	iframe = clamp((unsigned int)iframe, 1U, (unsigned int)uvc->nframes);
> +	ufrm = find_frame_by_index(uvc, ufmt, iframe);
> +	if (!ufrm)
> +		return;
> +
> +	for (i = 0; i < ufrm->frame.b_frame_interval_type; i++) {
> +		if (ival <= ufrm->dw_frame_interval[i]) {
> +			ival = ufrm->dw_frame_interval[i];
> +			break;
> +		}
> +	}
> +
> +	if (i == ufrm->frame.b_frame_interval_type)
> +		ival = ufrm->dw_frame_interval[ufrm->frame.b_frame_interval_type-1];
> +
> +	memset(ctrl, 0, sizeof(*ctrl));
> +
> +	ctrl->bmHint = 1;
> +	ctrl->bFormatIndex = iformat;
> +	ctrl->bFrameIndex = iframe;
> +	ctrl->dwFrameInterval = ival;
> +	ctrl->dwMaxVideoFrameSize = ufrm->frame.dw_max_video_frame_buffer_size;
> +
> +	switch (uvc->func.config->cdev->gadget->speed) {
> +	case USB_SPEED_SUPER:
> +		ctrl->dwMaxPayloadTransferSize = uvc_ss_streaming_ep.wMaxPacketSize;
> +		break;
> +	case USB_SPEED_HIGH:
> +		ctrl->dwMaxPayloadTransferSize = uvc_hs_streaming_ep.wMaxPacketSize;
> +		break;
> +	case USB_SPEED_FULL:
> +	default:
> +		ctrl->dwMaxPayloadTransferSize = uvc_fs_streaming_ep.wMaxPacketSize;
> +		break;
> +	}
> +	ctrl->bmFramingInfo = 3;
> +	ctrl->bPreferedVersion = 1;
> +	ctrl->bMaxVersion = 1;
> +}
> +
> +static int uvc_events_process_data(struct uvc_device *uvc, struct usb_request *req)
> +{
> +	struct uvc_video *video = &uvc->video;
> +	struct uvc_streaming_control *target;
> +	struct uvc_streaming_control *ctrl;
> +	struct uvcg_frame *ufrm;
> +	struct uvcg_format *ufmt;
> +
> +	switch (video->control) {
> +	case UVC_VS_PROBE_CONTROL:
> +		pr_debug("setting probe control, length = %d\n", req->actual);
> +		target = &video->probe;
> +		break;
> +
> +	case UVC_VS_COMMIT_CONTROL:
> +		pr_debug("setting commit control, length = %d\n", req->actual);
> +		target = &video->commit;
> +		break;
> +
> +	default:
> +		pr_debug("setting unknown control, length = %d\n", req->actual);

Should this be error instead of debug?

> +		return -EOPNOTSUPP;
> +	}
> +
> +	ctrl = (struct uvc_streaming_control *)req->buf;
> +
> +	if (video->control == UVC_VS_PROBE_CONTROL)
> +		uvc_fill_streaming_control(uvc, target, ctrl->bFormatIndex,
> +					   ctrl->bFrameIndex, ctrl->dwFrameInterval);
> +
> +	ufmt = uvc->fmt[target->bFormatIndex-1];
> +	if (!ufmt)
> +		return -EINVAL;
> +
> +	ufrm = find_frame_by_index(uvc, ufmt, ctrl->bFrameIndex);
> +	if (!ufrm)
> +		return -EINVAL;
> +
> +	if (video->control == UVC_VS_COMMIT_CONTROL) {
> +		spin_lock(&video->frame_lock);
> +
> +		uvc_fill_streaming_control(uvc, target, ctrl->bFormatIndex,
> +				   ctrl->bFrameIndex, ctrl->dwFrameInterval);
> +
> +		video->cur_frame = ufrm;
> +		video->cur_format = ufmt;
> +
> +		spin_unlock(&video->frame_lock);
> +	}
> +
> +	return 0;
> +}
> +
> +static void
> +uvc_events_process_streaming(struct uvc_device *uvc, uint8_t req, uint8_t cs,
> +			     struct uvc_request_data *resp)
> +{
> +	struct uvc_streaming_control *ctrl;
> +
> +	pr_debug("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:
> +		uvc->video.control = cs;
> +		resp->length = 34;
> +		break;
> +
> +	case UVC_GET_CUR:
> +		if (cs == UVC_VS_PROBE_CONTROL)
> +			memcpy(ctrl, &uvc->video.probe, sizeof(*ctrl));
> +		else
> +			memcpy(ctrl, &uvc->video.commit, sizeof(*ctrl));
> +		break;
> +
> +	case UVC_GET_MIN:
> +	case UVC_GET_MAX:
> +	case UVC_GET_DEF:
> +		if (req == UVC_GET_MAX)
> +			uvc_fill_streaming_control(uvc, ctrl, -1, -1, UINT_MAX);
> +		else
> +			uvc_fill_streaming_control(uvc, ctrl, 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 int
> +uvc_events_process_class(struct uvc_device *uvc, const struct usb_ctrlrequest *ctrl,
> +			 struct uvc_request_data *resp)
> +{
> +	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
> +		return -EINVAL;
> +
> +	switch (ctrl->wIndex & 0xff) {
> +	case UVC_INTF_VIDEO_CONTROL:
> +		return -EOPNOTSUPP;
> +
> +	case UVC_INTF_VIDEO_STREAMING:
> +		uvc_events_process_streaming(uvc, ctrl->bRequest, ctrl->wValue >> 8, resp);
> +		break;
> +
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +uvc_events_process_setup(struct uvc_device *uvc, const struct usb_ctrlrequest *ctrl,
> +			 struct uvc_request_data *resp)
> +{
> +	uvc->video.control = 0;
> +
> +	pr_debug("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:
> +		return -EOPNOTSUPP;
> +
> +	case USB_TYPE_CLASS:
> +		return uvc_events_process_class(uvc, ctrl, resp);
> +
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +/* --------------------------------------------------------------------------
> + * Requests handling
> + */
> +
> +static
> +int uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
> +{
> +	struct usb_composite_dev *cdev = uvc->func.config->cdev;
> +	struct usb_request *req = uvc->control_req;
> +
> +	if (data->length < 0)
> +		return usb_ep_set_halt(cdev->gadget->ep0);
> +
> +	req->length = min_t(unsigned int, uvc->event_length, data->length);
> +	req->zero = data->length < uvc->event_length;
> +
> +	memcpy(req->buf, data->data, req->length);
> +
> +	return usb_ep_queue(cdev->gadget->ep0, req, GFP_KERNEL);
> +}

An exact copy of this function already exists in uvc_v4l2.c. Over there
it's used to handle UVCIOC_SEND_RESPONSE, but the content and purpose of
this and the other copy are the same, so I think they can be unified.

> +
>  static void
>  uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
>  {
>  	struct uvc_device *uvc = req->context;
> -	struct v4l2_event v4l2_event;
> -	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
> +	int ret;
>  
>  	if (uvc->event_setup_out) {
>  		uvc->event_setup_out = 0;
> +		ret = uvc_events_process_data(uvc, req);

If !ret, don't you still need to uvc_send_response()? That was the
previous behavior, just that userspace would receive the v4l2 event, and
then respond with UVCIOC_SEND_RESPONSE.

> +		if (ret == -EOPNOTSUPP) {
> +			struct v4l2_event v4l2_event;
> +			struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
>  
> -		memset(&v4l2_event, 0, sizeof(v4l2_event));
> -		v4l2_event.type = UVC_EVENT_DATA;
> -		uvc_event->data.length = req->actual;
> -		memcpy(&uvc_event->data.data, req->buf, req->actual);
> -		v4l2_event_queue(&uvc->vdev, &v4l2_event);
> +			memset(&v4l2_event, 0, sizeof(v4l2_event));
> +			v4l2_event.type = UVC_EVENT_DATA;
> +			uvc_event->data.length = req->actual;
> +			memcpy(&uvc_event->data.data, req->buf, req->actual);
> +			v4l2_event_queue(&uvc->vdev, &v4l2_event);
> +		}
>  	}
>  }
>  
> @@ -223,8 +455,8 @@ static int
>  uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
>  {
>  	struct uvc_device *uvc = to_uvc(f);
> -	struct v4l2_event v4l2_event;
> -	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
> +	struct uvc_request_data resp;
> +	int ret = 0;
>  
>  	if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) {
>  		uvcg_info(f, "invalid request type\n");
> @@ -241,12 +473,22 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
>  	uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN);
>  	uvc->event_length = le16_to_cpu(ctrl->wLength);
>  
> -	memset(&v4l2_event, 0, sizeof(v4l2_event));
> -	v4l2_event.type = UVC_EVENT_SETUP;
> -	memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
> -	v4l2_event_queue(&uvc->vdev, &v4l2_event);
> +	ret = uvc_events_process_setup(uvc, ctrl, &resp);
> +	if (ret == -EOPNOTSUPP) {
> +		struct v4l2_event v4l2_event;
> +		struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
>  
> -	return 0;
> +		memset(&v4l2_event, 0, sizeof(v4l2_event));
> +		v4l2_event.type = UVC_EVENT_SETUP;
> +		memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
> +		v4l2_event_queue(&uvc->vdev, &v4l2_event);
> +
> +		return 0;
> +	} else if (ret) {
> +		return ret;
> +	}
> +
> +	return uvc_send_response(uvc, &resp);
>  }
>  
>  void uvc_function_setup_continue(struct uvc_device *uvc)
> diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
> index 62d7420a25666..d06b5282b4058 100644
> --- a/drivers/usb/gadget/function/uvc.h
> +++ b/drivers/usb/gadget/function/uvc.h
> @@ -13,6 +13,7 @@
>  #include <linux/mutex.h>
>  #include <linux/spinlock.h>
>  #include <linux/usb/composite.h>
> +#include <linux/usb/video.h>
>  #include <linux/videodev2.h>
>  
>  #include <media/v4l2-device.h>
> @@ -85,6 +86,12 @@ struct uvc_video {
>  	struct uvcg_frame *cur_frame;
>  
>  	struct mutex mutex;	/* protects frame parameters */
> +	spinlock_t frame_lock;
> +
> +	struct uvc_streaming_control probe;
> +	struct uvc_streaming_control commit;
> +
> +	int control;
>  
>  	/* Requests */
>  	unsigned int req_size;
> @@ -170,4 +177,11 @@ extern void uvc_function_connect(struct uvc_device *uvc);
>  extern void uvc_function_disconnect(struct uvc_device *uvc);
>  
>  extern int uvc_frame_default(struct uvcg_format *ufmt);
> +extern struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc,
> +					      struct uvcg_format *ufmt,
> +					      int index);
> +extern void uvc_fill_streaming_control(struct uvc_device *uvc,
> +			   struct uvc_streaming_control *ctrl,
> +			   int iframe, int iformat, unsigned int ival);

Indentation.

> +
>  #endif /* _UVC_GADGET_H_ */
> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
> index 83830b8864a6e..a89d3d661d99f 100644
> --- a/drivers/usb/gadget/function/uvc_v4l2.c
> +++ b/drivers/usb/gadget/function/uvc_v4l2.c
> @@ -205,7 +205,8 @@ uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt)
>  }
>  
>  static int _uvc_v4l2_try_fmt(struct uvc_video *video,
> -	struct v4l2_format *fmt, struct uvcg_format **uvc_format, struct uvcg_frame **uvc_frame)
> +	struct v4l2_format *fmt, struct uvc_streaming_control *probe,
> +	struct uvcg_format **uvc_format, struct uvcg_frame **uvc_frame)
>  {
>  	struct uvc_device *uvc = video->uvc;
>  	struct uvcg_format *ufmt;
> @@ -253,6 +254,9 @@ static int _uvc_v4l2_try_fmt(struct uvc_video *video,
>  		*uvc_format = ufmt;
>  	if (uvc_frame != NULL)
>  		*uvc_frame = ufrm;
> +	if (probe)
> +		uvc_fill_streaming_control(uvc, probe,
> +				i + 1, ufrm->frame.b_frame_index, 0);
>  
>  	return 0;
>  }
> @@ -264,7 +268,7 @@ uvc_v4l2_try_fmt(struct file *file, void *fh, struct v4l2_format *fmt)
>  	struct uvc_device *uvc = video_get_drvdata(vdev);
>  	struct uvc_video *video = &uvc->video;
>  
> -	return _uvc_v4l2_try_fmt(video, fmt, NULL, NULL);
> +	return _uvc_v4l2_try_fmt(video, fmt, NULL, NULL, NULL);
>  }
>  
>  static int
> @@ -273,17 +277,23 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
>  	struct video_device *vdev = video_devdata(file);
>  	struct uvc_device *uvc = video_get_drvdata(vdev);
>  	struct uvc_video *video = &uvc->video;
> +	struct uvc_streaming_control probe;
>  	struct uvcg_format *ufmt;
>  	struct uvcg_frame *ufrm;
>  	int ret;
>  
> -	ret = _uvc_v4l2_try_fmt(video, fmt, &ufmt, &ufrm);
> +	ret = _uvc_v4l2_try_fmt(video, fmt, &probe, &ufmt, &ufrm);
>  	if (ret)
>  		return ret;
>  
> +	spin_lock(&video->frame_lock);
> +
> +	video->commit = probe;
>  	video->cur_format = ufmt;
>  	video->cur_frame = ufrm;
>  
> +	spin_unlock(&video->frame_lock);
> +
>  	return ret;
>  }
>  
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index b14780bddd838..8f549fe608362 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -346,13 +346,26 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
>   */
>  int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
>  {
> +	int framedef;
> +
>  	INIT_LIST_HEAD(&video->req_free);
>  	spin_lock_init(&video->req_lock);
> +	spin_lock_init(&video->frame_lock);
> +
> +	/* Allocate a stream specific work queue for asynchronous tasks. */
> +	video->async_wq = alloc_workqueue("uvcvideo", WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 0);

Where does async_wq come from?


Thanks,

Paul

> +	if (!video->async_wq)
> +		return -EINVAL;
> +
>  	INIT_WORK(&video->pump, uvcg_video_pump);
>  
>  	video->uvc = uvc;
>  	video->def_format = video->cur_format = uvc->fmt[0];
> -	video->cur_frame = uvc->frm[uvc_frame_default(video->def_format) - 1];
> +	framedef = uvc_frame_default(video->def_format);
> +	video->cur_frame = uvc->frm[framedef - 1];
> +
> +	uvc_fill_streaming_control(uvc, &video->probe, 1, framedef, 0);
> +	uvc_fill_streaming_control(uvc, &video->commit, 1, framedef, 0);
>  
>  	/* Initialize the video buffers queue. */
>  	uvcg_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT,
> -- 
> 2.29.2
> 



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux