#include <sys/cdefs.h>
#include <sys/stdint.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/unistd.h>
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/sbuf.h>
#include <sys/filedesc.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usbdi_util.h>
#include <bus/u4b/usbhid.h>
#define USB_DEBUG_VAR uvc_debug
#include <bus/u4b/usb_debug.h>
#include <bus/u4b/usb_core.h>
#include <bus/u4b/usb_dev.h>
#include <bus/u4b/usb_mbuf.h>
#include <bus/u4b/usb_process.h>
#include <bus/u4b/usb_device.h>
#include <bus/u4b/usb_busdma.h>
#include <bus/u4b/usb_dynamic.h>
#include <bus/u4b/usb_util.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <contrib/v4l/videodev.h>
#include <contrib/v4l/videodev2.h>
#include "uvc_drv.h"
#include "uvc_buf.h"
#include "uvc_v4l2.h"
#define UVC_LOCK(lkp) lockmgr(lkp, LK_EXCLUSIVE)
#define UVC_UNLOCK(lkp) lockmgr(lkp, LK_RELEASE)
#define FRAME_DUMP 0
#if FRAME_DUMP
static void uvc_writefile(char *path, void *data, int len);
#endif
static void
uvc_buf_fill_v4l2(struct v4l2_buffer *buf, uint32_t index, uint32_t size,
uint32_t len)
{
buf->index = index;
buf->m.offset = index * size;
buf->length = len;
buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf->sequence = 0;
buf->field = V4L2_FIELD_NONE;
buf->memory = V4L2_MEMORY_MMAP;
buf->flags = 0;
}
static __inline int
uvc_buf_queue_mmap_locked(struct uvc_buf_queue *bq, vm_paddr_t *paddr, vm_offset_t offset)
{
uint64_t max_offset = (uint64_t)(bq->buf_size * bq->buf_count) - PAGE_SIZE;
if (bq->mem == NULL)
return EINVAL;
if (offset < 0)
return EINVAL;
if (max_offset < 0)
return EINVAL;
if (offset > max_offset) {
kprintf("WARNING: %s offset out of bound: %ld\n; max: %ld",
__func__,
offset,
max_offset);
return EINVAL;
}
*paddr = atop(vtophys((uint8_t *)bq->mem + offset));
return (0);
}
int
uvc_buf_queue_mmap(struct uvc_buf_queue *bq, vm_paddr_t *paddr, vm_offset_t offset)
{
int res;
UVC_LOCK(&bq->mtx);
res = uvc_buf_queue_mmap_locked(bq, paddr, offset);
UVC_UNLOCK(&bq->mtx);
return (res);
}
static void
uvc_buf_queue_show_qbuf(struct uvc_buf *buf)
{
DPRINTF("buf index:%lu status:%lu mem:%p offset:%lu\n",
buf->index, buf->status, buf->mem, buf->offset);
}
static void
uvc_buf_queue_show(struct uvc_buf_queue *bq)
{
int i;
DPRINTF("mem:%p status:%lx flags:%lx seq:%lu buf size:%lu count:%lu\n",
bq->mem, bq->status, bq->flags, bq->seq,
bq->buf_size, bq->buf_count);
for (i = 0; i < UVC_BUF_MAX_BUFFERS; i++)
uvc_buf_queue_show_qbuf(bq->buf + i);
}
int
uvc_buf_reset_buf(struct uvc_buf_queue *bq)
{
struct uvc_buf *buf;
UVC_LOCK(&bq->mtx);
buf = STAILQ_FIRST(&bq->product);
if (buf) {
buf->vbuf.bytesused = 0;
}
UVC_UNLOCK(&bq->mtx);
return 0;
}
static int
uvc_buf_check_length(struct uvc_drv_video *v, struct uvc_buf *buf,
uint32_t len, uint32_t finish)
{
struct v4l2_buffer *vbuf = &buf->vbuf;
if (vbuf->length == 0) {
return 1;
}
if (vbuf->bytesused + len > vbuf->length) {
return 1;
}
if (v->cur_fmt && (v->cur_fmt->fcc != V4L2_PIX_FMT_MJPEG &&
v->cur_fmt->fcc != V4L2_PIX_FMT_H264)) {
if (finish && (vbuf->bytesused + len < vbuf->length)) {
return 1;
}
}
return 0;
}
int
uvc_bulkbuf_sell_buf(struct uvc_buf_queue *bq,
struct usb_page_cache *pc, usb_frlength_t offset,
usb_frlength_t len,
usb_frlength_t actlen,
usb_frlength_t maxFramelen)
{
struct uvc_buf *buf;
struct uvc_drv_video *v;
unsigned char *ptr;
uint32_t maxlen, nbytes;
int ret = 0;
UVC_LOCK(&bq->mtx);
if (!UVC_BUF_QUEUE_IS_RUNNING(bq)) {
ret = EINVAL;
goto done;
}
v = bq->video;
buf = STAILQ_FIRST(&bq->product);
if (!buf) {
ret = EINVAL;
v->bulk.skip_payload = 1;
}
if (!v->bulk.skip_payload && buf) {
if (buf->status != UVC_BUF_STATE_ACTIVE) {
if (v->bulk.last_fid ==
(v->bulk.header[1] & UVC_PL_HEADER_BIT_FID)) {
v->bulk.skip_payload = 1;
buf->vbuf.bytesused = 0;
goto done;
}
buf->status = UVC_BUF_STATE_ACTIVE;
}
}
if (v->bulk.last_fid != (v->bulk.header[1] & UVC_PL_HEADER_BIT_FID))
v->bulk.last_fid = (v->bulk.header[1] & UVC_PL_HEADER_BIT_FID);
if (len != 0 && !v->bulk.skip_payload) {
maxlen = buf->vbuf.length - buf->vbuf.bytesused;
nbytes = min(maxlen, len);
ptr = (char *)(buf->mem) + buf->offset;
usbd_copy_out(pc, offset, ptr + buf->vbuf.bytesused,
nbytes);
buf->vbuf.bytesused += nbytes;
if (len > maxlen) {
len -= nbytes;
goto finished;
}
}
if (actlen < maxFramelen ||
v->bulk.payload_size >= v->bulk.max_payload_size) {
if (!v->bulk.skip_payload && buf)
if (uvc_drv_check_video_context(v, buf->mem,
buf->vbuf.bytesused)) {
buf->vbuf.bytesused = 0;
goto clean;
}
if (!v->bulk.skip_payload &&
((v->bulk.header[1] & UVC_PL_HEADER_BIT_EOF) ||
(bq->video && bq->video->sc &&
bq->video->sc->quirks & UVC_NO_EOF)))
finished:
if (buf->vbuf.bytesused > 0) {
STAILQ_REMOVE_HEAD(&bq->product, link);
buf->vbuf.sequence = bq->seq++;
microtime(&buf->vbuf.timestamp);
buf->status = UVC_BUF_STATE_DONE;
STAILQ_INSERT_TAIL(&bq->consumer, buf, link);
cv_broadcast(&bq->io_cv);
KNOTE(&bq->sel.ki_note, 0);
}
clean:
v->bulk.header_size = 0;
v->bulk.skip_payload = 0;
v->bulk.payload_size = 0;
}
done:
UVC_UNLOCK(&bq->mtx);
return ret;
}
int
uvc_buf_sell_buf(struct uvc_buf_queue *bq,
struct usb_page_cache *pc, usb_frlength_t offset,
usb_frlength_t len, uint32_t finish, uint8_t fid)
{
struct uvc_buf *buf;
unsigned char *ptr;
int ret = 0;
UVC_LOCK(&bq->mtx);
if (!UVC_BUF_QUEUE_IS_RUNNING(bq)) {
ret = EINVAL;
goto done;
}
buf = STAILQ_FIRST(&bq->product);
if (buf) {
if (buf->status != UVC_BUF_STATE_ACTIVE) {
if (bq->video->last_fid == fid) {
buf->vbuf.bytesused = 0;
goto done;
}
buf->status = UVC_BUF_STATE_ACTIVE;
}
if (uvc_buf_check_length(bq->video, buf, len, finish)) {
buf->vbuf.bytesused = 0;
goto done;
}
if (bq->video->last_fid != fid)
bq->video->last_fid = fid;
if (len != 0) {
ptr = (char *)(buf->mem) + buf->offset;
usbd_copy_out(pc, offset, ptr + buf->vbuf.bytesused,
len);
buf->vbuf.bytesused += len;
}
if (!finish)
goto done;
if (uvc_drv_check_video_context(bq->video, (char *)buf->mem + buf->offset,
buf->vbuf.bytesused)) {
buf->vbuf.bytesused = 0;
goto done;
}
if (buf->vbuf.bytesused > 0) {
DPRINTF("%s Got frame %ld %4lu\n", __func__, (long)bq->video, bq->seq);
#if FRAME_DUMP
char path[PATH_MAX];
ksprintf(path, "/tmp/%x_%4lu.data", (short)bq->video, bq->seq);
uvc_writefile(path,
(void *)((char *)buf->mem + buf->offset),
buf->vbuf.bytesused);
#endif
STAILQ_REMOVE_HEAD(&bq->product, link);
buf->vbuf.sequence = bq->seq++;
microtime(&buf->vbuf.timestamp);
buf->status = UVC_BUF_STATE_DONE;
STAILQ_INSERT_TAIL(&bq->consumer, buf, link);
cv_broadcast(&bq->io_cv);
KNOTE(&bq->sel.ki_note, 0);
}
}
done:
UVC_UNLOCK(&bq->mtx);
return ret;
}
static void
uvc_buf_queue_query_buf_locked(struct uvc_buf *buf, struct v4l2_buffer *vbuf)
{
memcpy(vbuf, &buf->vbuf, sizeof(*vbuf));
switch (buf->status) {
case UVC_BUF_STATE_QUEUED:
case UVC_BUF_STATE_ACTIVE:
vbuf->flags |= V4L2_BUF_FLAG_QUEUED;
break;
case UVC_BUF_STATE_DONE:
case UVC_BUF_STATE_ERROR:
vbuf->flags |= V4L2_BUF_FLAG_DONE;
break;
case UVC_BUF_STATE_IDLE:
default:
break;
}
}
int
uvc_buf_queue_dequeue_buf(struct uvc_buf_queue *bq,
struct v4l2_buffer *vbuf, int nonblock)
{
struct uvc_buf *buf;
int ret = 0;
UVC_LOCK(&bq->mtx);
do {
if (!UVC_BUF_QUEUE_IS_RUNNING(bq)) {
ret = EINVAL;
break;
}
buf = STAILQ_FIRST(&bq->consumer);
if (buf) {
switch (buf->status) {
case UVC_BUF_STATE_ERROR:
ret = EIO;
case UVC_BUF_STATE_DONE:
buf->status = UVC_BUF_STATE_IDLE;
break;
default:
DPRINTF("fatal %s\n", __func__);
break;
}
STAILQ_REMOVE_HEAD(&bq->consumer, link);
uvc_buf_queue_query_buf_locked(buf, vbuf);
break;
} else {
if (STAILQ_EMPTY(&bq->product)) {
DPRINTF("%s buf empty\n", __func__);
ret = EPIPE;
break;
}
if (!nonblock) {
ret = cv_wait_sig(&bq->io_cv, &bq->mtx);
if (ret) {
DPRINTF("cv wait sig:%d\n", ret);
break;
}
} else {
ret = EAGAIN;
}
}
} while (!nonblock);
UVC_UNLOCK(&bq->mtx);
return ret;
}
int
uvc_buf_queue_queue_buf(struct uvc_buf_queue *bq, struct v4l2_buffer *vbuf)
{
struct uvc_buf *buf;
int ret = 0;
UVC_LOCK(&bq->mtx);
if (vbuf->index >= bq->buf_count) {
ret = EINVAL;
goto done;
}
if (!UVC_BUF_QUEUE_IS_RUNNING(bq)) {
ret = EINVAL;
goto done;
}
buf = bq->buf + vbuf->index;
if (buf->status != UVC_BUF_STATE_IDLE) {
DPRINTF("%s wrong buf type %lu\n", __func__, buf->status);
ret = EINVAL;
goto done;
}
buf->status = UVC_BUF_STATE_QUEUED;
buf->vbuf.bytesused = 0;
STAILQ_INSERT_TAIL(&bq->product, buf, link);
done:
UVC_UNLOCK(&bq->mtx);
return ret;
}
int
uvc_buf_queue_query_buf(struct uvc_buf_queue *bq, struct v4l2_buffer *vbuf)
{
int ret = 0;
DPRINTF("%s\n", __func__);
UVC_LOCK(&bq->mtx);
if (vbuf->index >= bq->buf_count) {
ret = EINVAL;
goto done;
}
uvc_buf_queue_query_buf_locked(bq->buf + vbuf->index, vbuf);
done:
UVC_UNLOCK(&bq->mtx);
return ret;
}
void
uvc_buf_queue_set_drop_flag(struct uvc_buf_queue *bq)
{
bq->flags |= UVC_BUFFER_QUEUE_DROP_INCOMPLETE;
}
static void
uvc_buf_queue_free_bufs_locked(struct uvc_buf_queue *bq)
{
if (bq->mem) {
kfree(bq->mem, M_UVC);
bq->mem = NULL;
bq->buf_count = 0;
}
}
void
uvc_buf_queue_free_bufs(struct uvc_buf_queue *bq)
{
UVC_LOCK(&bq->mtx);
uvc_buf_queue_free_bufs_locked(bq);
UVC_UNLOCK(&bq->mtx);
}
int
uvc_buf_queue_req_bufs(struct uvc_buf_queue *bq, uint32_t *count, uint32_t len)
{
void *mem;
struct uvc_buf *buf;
int i, num, ret = 0;
unsigned long rl;
DPRINTF("%s\n", __func__);
rl = round_page(len);
DPRINTF("page align size:%lu-real size:%u\n", rl, len);
num = *count;
if (num > UVC_BUF_MAX_BUFFERS)
num = UVC_BUF_MAX_BUFFERS;
UVC_LOCK(&bq->mtx);
uvc_buf_queue_free_bufs_locked(bq);
if (!num)
goto done;
for (; num > 0; num--) {
mem = kmalloc(num * rl, M_UVC, M_WAITOK | M_ZERO);
if (mem)
break;
}
if (!mem) {
ret = ENOMEM;
goto done;
}
for (i = 0; i < num; i++) {
buf = bq->buf + i;
buf->index = i;
buf->mem = mem;
buf->offset = i * rl;
buf->status = UVC_BUF_STATE_IDLE;
uvc_buf_fill_v4l2(&buf->vbuf, i, rl, len);
}
bq->mem = mem;
bq->buf_count = num;
bq->buf_size = rl;
*count = num;
uvc_buf_queue_show(bq);
done:
UVC_UNLOCK(&bq->mtx);
DPRINTF("reqbuf nums:%d\n", *count);
return ret;
}
static void
uvc_buf_queue_init_qbuf(struct uvc_buf *buf, int ind)
{
memset(buf, 0x0, sizeof(*buf));
buf->index = ind;
buf->status = UVC_BUF_STATE_IDLE;
}
static void
uvc_buf_queue_filter_detach(struct knote *kn)
{
struct uvc_buf_queue *queue = (struct uvc_buf_queue *)kn->kn_hook;
struct klist *klist = &queue->sel.ki_note;
UVC_LOCK(&queue->mtx);
knote_remove(klist, kn);
UVC_UNLOCK(&queue->mtx);
}
static int
uvc_buf_queue_kqread(struct knote *kn, long hint __unused)
{
struct uvc_buf_queue *queue = (struct uvc_buf_queue *)kn->kn_hook;
int ready = 0;
UVC_LOCK(&queue->mtx);
if (STAILQ_FIRST(&queue->consumer))
ready = 1;
if ((queue->flags & UVC_BUFFER_QUEUE_WORKING) == 0) {
kn->kn_flags |= EV_ERROR;
ready = 1;
}
UVC_UNLOCK(&queue->mtx);
return ready;
}
static struct filterops uvc_buf_queue_filtops_read = {
.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
.f_attach = NULL,
.f_detach = uvc_buf_queue_filter_detach,
.f_event = uvc_buf_queue_kqread
};
int
uvc_buf_queue_kqfilter(struct uvc_buf_queue *queue, struct knote *kn)
{
struct klist *klist;
switch (kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &uvc_buf_queue_filtops_read;
kn->kn_hook = (caddr_t)queue;
klist = &queue->sel.ki_note;
UVC_LOCK(&queue->mtx);
knote_insert(klist, kn);
UVC_UNLOCK(&queue->mtx);
return 0;
default:
return EOPNOTSUPP;
}
}
int
uvc_buf_queue_disable(struct uvc_buf_queue *queue)
{
int i;
DPRINTF("%s\n", __func__);
UVC_LOCK(&queue->mtx);
if ((queue->flags & UVC_BUFFER_QUEUE_WORKING) == 0) {
UVC_UNLOCK(&queue->mtx);
return EINVAL;
}
STAILQ_INIT(&queue->consumer);
STAILQ_INIT(&queue->product);
for (i = 0; i < UVC_BUF_MAX_BUFFERS; i++)
uvc_buf_queue_init_qbuf(queue->buf + i, i);
cv_broadcast(&queue->io_cv);
KNOTE(&queue->sel.ki_note, 0);
queue->flags = 0;
UVC_UNLOCK(&queue->mtx);
return 0;
}
int
uvc_buf_queue_enable(struct uvc_buf_queue *queue)
{
DPRINTF("%s\n", __func__);
UVC_LOCK(&queue->mtx);
if (queue->flags & UVC_BUFFER_QUEUE_WORKING) {
UVC_UNLOCK(&queue->mtx);
return EBUSY;
}
queue->seq = 0;
queue->flags |= UVC_BUFFER_QUEUE_WORKING;
UVC_UNLOCK(&queue->mtx);
return 0;
}
void
uvc_buf_queue_init(struct uvc_drv_video *v, struct uvc_buf_queue *bq)
{
int i;
DPRINTF("init buf\n");
bq->video = v;
lockinit(&bq->mtx, "uvc buffer lock", 0, LK_CANRECURSE);
cv_init(&bq->io_cv, "uvcbufiocv");
STAILQ_INIT(&bq->consumer);
STAILQ_INIT(&bq->product);
bzero(&bq->sel, sizeof(bq->sel));
for (i = 0; i < UVC_BUF_MAX_BUFFERS; i++)
uvc_buf_queue_init_qbuf(bq->buf + i, i);
return;
}
#if FRAME_DUMP
static void
uvc_writefile(char *path, void *data, int len)
{
struct thread *td;
struct uio auio;
struct iovec aiov;
int error, fd = -1;
td = curthread;
pwd_ensure_dirs();
KLG("write: %s; %p; %d\n", path, data, len);
error = kern_openat(td, AT_FDCWD, path, UIO_SYSSPACE, O_CREAT | O_RDWR, 0666);
if (error) {
KLG("open %s error: %d\n", path, error);
goto out;
}
fd = td->td_retval[0];
aiov.iov_base = data;
aiov.iov_len = len;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_resid = len;
auio.uio_segflg = UIO_SYSSPACE;
error = kern_writev(td, fd, &auio);
if (error) {
KLG("write %s error: %d\n", path, error);
goto out;
}
out:
if (fd >= 0)
kern_close(td, fd);
}
#endif