usb: gadget: uvc: fix sg handling in error case

If there is a transmission error the buffer will be returned too early,
causing a memory fault as subsequent requests for that buffer are still
queued up to be sent. Refactor the error handling to wait for the final
request to come in before reporting back the buffer to userspace for all
transfer types (bulk/isoc/isoc_sg). This ensures userspace knows if the
frame was successfully sent.

Fixes: e81e7f9a0e ("usb: gadget: uvc: add scatter gather support")
Cc: <stable@vger.kernel.org>
Signed-off-by: Dan Vacura <w36195@motorola.com>
Link: https://lore.kernel.org/r/20221018215044.765044-4-w36195@motorola.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Dan Vacura 2022-10-18 16:50:39 -05:00 committed by Greg Kroah-Hartman
parent 8e8e923a49
commit 0a0a2760b0
2 changed files with 19 additions and 7 deletions

View File

@ -304,6 +304,7 @@ int uvcg_queue_enable(struct uvc_video_queue *queue, int enable)
queue->sequence = 0; queue->sequence = 0;
queue->buf_used = 0; queue->buf_used = 0;
queue->flags &= ~UVC_QUEUE_DROP_INCOMPLETE;
} else { } else {
ret = vb2_streamoff(&queue->queue, queue->queue.type); ret = vb2_streamoff(&queue->queue, queue->queue.type);
if (ret < 0) if (ret < 0)
@ -329,10 +330,11 @@ int uvcg_queue_enable(struct uvc_video_queue *queue, int enable)
void uvcg_complete_buffer(struct uvc_video_queue *queue, void uvcg_complete_buffer(struct uvc_video_queue *queue,
struct uvc_buffer *buf) struct uvc_buffer *buf)
{ {
if ((queue->flags & UVC_QUEUE_DROP_INCOMPLETE) && if (queue->flags & UVC_QUEUE_DROP_INCOMPLETE) {
buf->length != buf->bytesused) { queue->flags &= ~UVC_QUEUE_DROP_INCOMPLETE;
buf->state = UVC_BUF_STATE_QUEUED; buf->state = UVC_BUF_STATE_ERROR;
vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0); vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0);
vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_ERROR);
return; return;
} }

View File

@ -88,6 +88,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
struct uvc_buffer *buf) struct uvc_buffer *buf)
{ {
void *mem = req->buf; void *mem = req->buf;
struct uvc_request *ureq = req->context;
int len = video->req_size; int len = video->req_size;
int ret; int ret;
@ -113,13 +114,14 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
video->queue.buf_used = 0; video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_DONE;
list_del(&buf->queue); list_del(&buf->queue);
uvcg_complete_buffer(&video->queue, buf);
video->fid ^= UVC_STREAM_FID; video->fid ^= UVC_STREAM_FID;
ureq->last_buf = buf;
video->payload_size = 0; video->payload_size = 0;
} }
if (video->payload_size == video->max_payload_size || if (video->payload_size == video->max_payload_size ||
video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE ||
buf->bytesused == video->queue.buf_used) buf->bytesused == video->queue.buf_used)
video->payload_size = 0; video->payload_size = 0;
} }
@ -180,7 +182,8 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
req->length -= len; req->length -= len;
video->queue.buf_used += req->length - header_len; video->queue.buf_used += req->length - header_len;
if (buf->bytesused == video->queue.buf_used || !buf->sg) { if (buf->bytesused == video->queue.buf_used || !buf->sg ||
video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) {
video->queue.buf_used = 0; video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_DONE;
buf->offset = 0; buf->offset = 0;
@ -195,6 +198,7 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
struct uvc_buffer *buf) struct uvc_buffer *buf)
{ {
void *mem = req->buf; void *mem = req->buf;
struct uvc_request *ureq = req->context;
int len = video->req_size; int len = video->req_size;
int ret; int ret;
@ -209,12 +213,13 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
req->length = video->req_size - len; req->length = video->req_size - len;
if (buf->bytesused == video->queue.buf_used) { if (buf->bytesused == video->queue.buf_used ||
video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) {
video->queue.buf_used = 0; video->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_DONE;
list_del(&buf->queue); list_del(&buf->queue);
uvcg_complete_buffer(&video->queue, buf);
video->fid ^= UVC_STREAM_FID; video->fid ^= UVC_STREAM_FID;
ureq->last_buf = buf;
} }
} }
@ -255,6 +260,11 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
case 0: case 0:
break; break;
case -EXDEV:
uvcg_dbg(&video->uvc->func, "VS request missed xfer.\n");
queue->flags |= UVC_QUEUE_DROP_INCOMPLETE;
break;
case -ESHUTDOWN: /* disconnect from host. */ case -ESHUTDOWN: /* disconnect from host. */
uvcg_dbg(&video->uvc->func, "VS request cancelled.\n"); uvcg_dbg(&video->uvc->func, "VS request cancelled.\n");
uvcg_queue_cancel(queue, 1); uvcg_queue_cancel(queue, 1);