usb: gadget: uvc: add scatter gather support
This patch adds support for scatter gather transfers. If the underlying gadgets sg_supported == true, then the videeobuf2-dma-sg is used and the encode routine maps all scatter entries to separate scatterlists for the usb gadget. When streaming 1080p with request size of 1024 times 3 bytes top shows a difference of about 6.4% CPU load applying this patch: PID USER PR NI VIRT RES %CPU %MEM TIME+ S COMMAND 64 root 0 -20 0.0m 0.0m 7.7 0.0 0:01.25 I [kworker/u5:0-uvcvideo] 83 root 0 -20 0.0m 0.0m 4.5 0.0 0:03.71 I [kworker/u5:3-uvcvideo] 307 root -51 0 0.0m 0.0m 3.8 0.0 0:01.05 S [irq/51-dwc3] vs. 64 root 0 -20 0.0m 0.0m 5.8 0.0 0:01.79 I [kworker/u5:0-uvcvideo] 306 root -51 0 0.0m 0.0m 3.2 0.0 0:01.97 S [irq/51-dwc3] 82 root 0 -20 0.0m 0.0m 0.6 0.0 0:01.86 I [kworker/u5:1-uvcvideo] Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de> Link: https://lore.kernel.org/r/20210628155311.16762-5-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Родитель
b9b82d3d0d
Коммит
e81e7f9a0e
|
@ -450,6 +450,7 @@ config USB_CONFIGFS_F_UVC
|
||||||
depends on USB_CONFIGFS
|
depends on USB_CONFIGFS
|
||||||
depends on VIDEO_V4L2
|
depends on VIDEO_V4L2
|
||||||
depends on VIDEO_DEV
|
depends on VIDEO_DEV
|
||||||
|
select VIDEOBUF2_DMA_SG
|
||||||
select VIDEOBUF2_VMALLOC
|
select VIDEOBUF2_VMALLOC
|
||||||
select USB_F_UVC
|
select USB_F_UVC
|
||||||
help
|
help
|
||||||
|
|
|
@ -75,6 +75,8 @@ struct uvc_request {
|
||||||
struct usb_request *req;
|
struct usb_request *req;
|
||||||
u8 *req_buffer;
|
u8 *req_buffer;
|
||||||
struct uvc_video *video;
|
struct uvc_video *video;
|
||||||
|
struct sg_table sgt;
|
||||||
|
u8 header[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct uvc_video {
|
struct uvc_video {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <linux/wait.h>
|
#include <linux/wait.h>
|
||||||
|
|
||||||
#include <media/v4l2-common.h>
|
#include <media/v4l2-common.h>
|
||||||
|
#include <media/videobuf2-dma-sg.h>
|
||||||
#include <media/videobuf2-vmalloc.h>
|
#include <media/videobuf2-vmalloc.h>
|
||||||
|
|
||||||
#include "uvc.h"
|
#include "uvc.h"
|
||||||
|
@ -76,7 +77,12 @@ static int uvc_buffer_prepare(struct vb2_buffer *vb)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
buf->state = UVC_BUF_STATE_QUEUED;
|
buf->state = UVC_BUF_STATE_QUEUED;
|
||||||
buf->mem = vb2_plane_vaddr(vb, 0);
|
if (queue->use_sg) {
|
||||||
|
buf->sgt = vb2_dma_sg_plane_desc(vb, 0);
|
||||||
|
buf->sg = buf->sgt->sgl;
|
||||||
|
} else {
|
||||||
|
buf->mem = vb2_plane_vaddr(vb, 0);
|
||||||
|
}
|
||||||
buf->length = vb2_plane_size(vb, 0);
|
buf->length = vb2_plane_size(vb, 0);
|
||||||
if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
||||||
buf->bytesused = 0;
|
buf->bytesused = 0;
|
||||||
|
@ -116,9 +122,11 @@ static const struct vb2_ops uvc_queue_qops = {
|
||||||
.wait_finish = vb2_ops_wait_finish,
|
.wait_finish = vb2_ops_wait_finish,
|
||||||
};
|
};
|
||||||
|
|
||||||
int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
|
int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type,
|
||||||
struct mutex *lock)
|
struct mutex *lock)
|
||||||
{
|
{
|
||||||
|
struct uvc_video *video = container_of(queue, struct uvc_video, queue);
|
||||||
|
struct usb_composite_dev *cdev = video->uvc->func.config->cdev;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
queue->queue.type = type;
|
queue->queue.type = type;
|
||||||
|
@ -127,9 +135,17 @@ int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
|
||||||
queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
|
queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
|
||||||
queue->queue.ops = &uvc_queue_qops;
|
queue->queue.ops = &uvc_queue_qops;
|
||||||
queue->queue.lock = lock;
|
queue->queue.lock = lock;
|
||||||
queue->queue.mem_ops = &vb2_vmalloc_memops;
|
if (cdev->gadget->sg_supported) {
|
||||||
|
queue->queue.mem_ops = &vb2_dma_sg_memops;
|
||||||
|
queue->use_sg = 1;
|
||||||
|
} else {
|
||||||
|
queue->queue.mem_ops = &vb2_vmalloc_memops;
|
||||||
|
}
|
||||||
|
|
||||||
queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
|
queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
|
||||||
| V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
|
| V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
|
||||||
|
queue->queue.dev = dev;
|
||||||
|
|
||||||
ret = vb2_queue_init(&queue->queue);
|
ret = vb2_queue_init(&queue->queue);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -34,6 +34,9 @@ struct uvc_buffer {
|
||||||
|
|
||||||
enum uvc_buffer_state state;
|
enum uvc_buffer_state state;
|
||||||
void *mem;
|
void *mem;
|
||||||
|
struct sg_table *sgt;
|
||||||
|
struct scatterlist *sg;
|
||||||
|
unsigned int offset;
|
||||||
unsigned int length;
|
unsigned int length;
|
||||||
unsigned int bytesused;
|
unsigned int bytesused;
|
||||||
};
|
};
|
||||||
|
@ -50,6 +53,8 @@ struct uvc_video_queue {
|
||||||
|
|
||||||
unsigned int buf_used;
|
unsigned int buf_used;
|
||||||
|
|
||||||
|
bool use_sg;
|
||||||
|
|
||||||
spinlock_t irqlock; /* Protects flags and irqqueue */
|
spinlock_t irqlock; /* Protects flags and irqqueue */
|
||||||
struct list_head irqqueue;
|
struct list_head irqqueue;
|
||||||
};
|
};
|
||||||
|
@ -59,7 +64,7 @@ static inline int uvc_queue_streaming(struct uvc_video_queue *queue)
|
||||||
return vb2_is_streaming(&queue->queue);
|
return vb2_is_streaming(&queue->queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
|
int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type,
|
||||||
struct mutex *lock);
|
struct mutex *lock);
|
||||||
|
|
||||||
void uvcg_free_buffers(struct uvc_video_queue *queue);
|
void uvcg_free_buffers(struct uvc_video_queue *queue);
|
||||||
|
|
|
@ -27,10 +27,10 @@ static int
|
||||||
uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
|
uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf,
|
||||||
u8 *data, int len)
|
u8 *data, int len)
|
||||||
{
|
{
|
||||||
data[0] = 2;
|
data[0] = UVCG_REQUEST_HEADER_LEN;
|
||||||
data[1] = UVC_STREAM_EOH | video->fid;
|
data[1] = UVC_STREAM_EOH | video->fid;
|
||||||
|
|
||||||
if (buf->bytesused - video->queue.buf_used <= len - 2)
|
if (buf->bytesused - video->queue.buf_used <= len - UVCG_REQUEST_HEADER_LEN)
|
||||||
data[1] |= UVC_STREAM_EOF;
|
data[1] |= UVC_STREAM_EOF;
|
||||||
|
|
||||||
return 2;
|
return 2;
|
||||||
|
@ -94,6 +94,71 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video,
|
||||||
video->payload_size = 0;
|
video->payload_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video,
|
||||||
|
struct uvc_buffer *buf)
|
||||||
|
{
|
||||||
|
unsigned int pending = buf->bytesused - video->queue.buf_used;
|
||||||
|
struct uvc_request *ureq = req->context;
|
||||||
|
struct scatterlist *sg, *iter;
|
||||||
|
unsigned int len = video->req_size;
|
||||||
|
unsigned int sg_left, part = 0;
|
||||||
|
unsigned int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
sg = ureq->sgt.sgl;
|
||||||
|
sg_init_table(sg, ureq->sgt.nents);
|
||||||
|
|
||||||
|
/* Init the header. */
|
||||||
|
ret = uvc_video_encode_header(video, buf, ureq->header,
|
||||||
|
video->req_size);
|
||||||
|
sg_set_buf(sg, ureq->header, UVCG_REQUEST_HEADER_LEN);
|
||||||
|
len -= ret;
|
||||||
|
|
||||||
|
if (pending <= len)
|
||||||
|
len = pending;
|
||||||
|
|
||||||
|
req->length = (len == pending) ?
|
||||||
|
len + UVCG_REQUEST_HEADER_LEN : video->req_size;
|
||||||
|
|
||||||
|
/* Init the pending sgs with payload */
|
||||||
|
sg = sg_next(sg);
|
||||||
|
|
||||||
|
for_each_sg(sg, iter, ureq->sgt.nents - 1, i) {
|
||||||
|
if (!len || !buf->sg)
|
||||||
|
break;
|
||||||
|
|
||||||
|
sg_left = sg_dma_len(buf->sg) - buf->offset;
|
||||||
|
part = min_t(unsigned int, len, sg_left);
|
||||||
|
|
||||||
|
sg_set_page(iter, sg_page(buf->sg), part, buf->offset);
|
||||||
|
|
||||||
|
if (part == sg_left) {
|
||||||
|
buf->offset = 0;
|
||||||
|
buf->sg = sg_next(buf->sg);
|
||||||
|
} else {
|
||||||
|
buf->offset += part;
|
||||||
|
}
|
||||||
|
len -= part;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assign the video data with header. */
|
||||||
|
req->buf = NULL;
|
||||||
|
req->sg = ureq->sgt.sgl;
|
||||||
|
req->num_sgs = i + 1;
|
||||||
|
|
||||||
|
req->length -= len;
|
||||||
|
video->queue.buf_used += req->length - UVCG_REQUEST_HEADER_LEN;
|
||||||
|
|
||||||
|
if (buf->bytesused == video->queue.buf_used || !buf->sg) {
|
||||||
|
video->queue.buf_used = 0;
|
||||||
|
buf->state = UVC_BUF_STATE_DONE;
|
||||||
|
buf->offset = 0;
|
||||||
|
uvcg_queue_next_buffer(&video->queue, buf);
|
||||||
|
video->fid ^= UVC_STREAM_FID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
|
uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
|
||||||
struct uvc_buffer *buf)
|
struct uvc_buffer *buf)
|
||||||
|
@ -180,6 +245,8 @@ uvc_video_free_requests(struct uvc_video *video)
|
||||||
|
|
||||||
if (video->ureq) {
|
if (video->ureq) {
|
||||||
for (i = 0; i < video->uvc_num_requests; ++i) {
|
for (i = 0; i < video->uvc_num_requests; ++i) {
|
||||||
|
sg_free_table(&video->ureq[i].sgt);
|
||||||
|
|
||||||
if (video->ureq[i].req) {
|
if (video->ureq[i].req) {
|
||||||
usb_ep_free_request(video->ep, video->ureq[i].req);
|
usb_ep_free_request(video->ep, video->ureq[i].req);
|
||||||
video->ureq[i].req = NULL;
|
video->ureq[i].req = NULL;
|
||||||
|
@ -233,6 +300,10 @@ uvc_video_alloc_requests(struct uvc_video *video)
|
||||||
video->ureq[i].video = video;
|
video->ureq[i].video = video;
|
||||||
|
|
||||||
list_add_tail(&video->ureq[i].req->list, &video->req_free);
|
list_add_tail(&video->ureq[i].req->list, &video->req_free);
|
||||||
|
/* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
|
||||||
|
sg_alloc_table(&video->ureq[i].sgt,
|
||||||
|
DIV_ROUND_UP(req_size - 2, PAGE_SIZE) + 2,
|
||||||
|
GFP_KERNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
video->req_size = req_size;
|
video->req_size = req_size;
|
||||||
|
@ -342,7 +413,8 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
|
||||||
video->encode = uvc_video_encode_bulk;
|
video->encode = uvc_video_encode_bulk;
|
||||||
video->payload_size = 0;
|
video->payload_size = 0;
|
||||||
} else
|
} else
|
||||||
video->encode = uvc_video_encode_isoc;
|
video->encode = video->queue.use_sg ?
|
||||||
|
uvc_video_encode_isoc_sg : uvc_video_encode_isoc;
|
||||||
|
|
||||||
schedule_work(&video->pump);
|
schedule_work(&video->pump);
|
||||||
|
|
||||||
|
@ -366,8 +438,8 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
|
||||||
video->imagesize = 320 * 240 * 2;
|
video->imagesize = 320 * 240 * 2;
|
||||||
|
|
||||||
/* Initialize the video buffers queue. */
|
/* Initialize the video buffers queue. */
|
||||||
uvcg_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT,
|
uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent,
|
||||||
&video->mutex);
|
V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#ifndef __UVC_VIDEO_H__
|
#ifndef __UVC_VIDEO_H__
|
||||||
#define __UVC_VIDEO_H__
|
#define __UVC_VIDEO_H__
|
||||||
|
|
||||||
|
#define UVCG_REQUEST_HEADER_LEN 2
|
||||||
|
|
||||||
struct uvc_video;
|
struct uvc_video;
|
||||||
|
|
||||||
int uvcg_video_enable(struct uvc_video *video, int enable);
|
int uvcg_video_enable(struct uvc_video *video, int enable);
|
||||||
|
|
|
@ -502,6 +502,7 @@ config USB_G_WEBCAM
|
||||||
tristate "USB Webcam Gadget"
|
tristate "USB Webcam Gadget"
|
||||||
depends on VIDEO_V4L2
|
depends on VIDEO_V4L2
|
||||||
select USB_LIBCOMPOSITE
|
select USB_LIBCOMPOSITE
|
||||||
|
select VIDEOBUF2_DMA_SG
|
||||||
select VIDEOBUF2_VMALLOC
|
select VIDEOBUF2_VMALLOC
|
||||||
select USB_F_UVC
|
select USB_F_UVC
|
||||||
help
|
help
|
||||||
|
|
Загрузка…
Ссылка в новой задаче