/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2024 Dell Inc. * * Alvin Chen * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * UVC spec:https://www.usb.org/sites/default/files/USB_Video_Class_1_5.zip */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR uvc_debug #include #include #include #include #include #include #include #include #include #include #include #include #include #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; /* * guvcview enables video stream twice on ThinOS, first time it calls * VIDIOC_REQBUFS to alloc buf for data stream, then disable video * but second time it does not call VIDIOC_REQBUFS to alloc mem which * calls crash not call, we do not figure out why yet, so do not let go * temply, we will figure out why it behaves this weird here */ if (vbuf->length == 0) { return 1; } if (vbuf->bytesused + len > vbuf->length) { return 1; } /* * -------------------2208 release---------------------------------- * So far we found that sometime driver will upload incompleted frame * when using YUYV format so we check whether the data fully occupy * buffer, but NV12 format's data does not fully occupy buffer * originally, and now we not fully know all kinds of formats' * speciality so we quit it now for 2208 relase. * -------------------------end-------------------------------------- * Logitech BRIO gives wrong dwMaxFrameSize in its NV12 format * fixed it at video probe, so uncomment "return" here */ 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; /* FALLTHROUGH */ 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); /* check mmap finish */ 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