/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* USB video class driver: V4L2 interface implementation.
*/
#include <sys/usb/usba.h>
#include <sys/fcntl.h>
#include <sys/cmn_err.h>
#include <sys/usb/clients/video/usbvc/usbvc_var.h>
#include <sys/usb/clients/video/usbvc/usbvc.h>
#include <sys/videodev2.h>
static int usbvc_v4l2_set_format(usbvc_state_t *, struct v4l2_format *);
static int usbvc_v4l2_get_format(usbvc_state_t *, struct v4l2_format *);
static void usbvc_v4l2_query_buf(usbvc_state_t *, usbvc_buf_t *,
struct v4l2_buffer *);
static int usbvc_v4l2_enqueue_buf(usbvc_state_t *, usbvc_buf_t *,
struct v4l2_buffer *);
static int usbvc_v4l2_dequeue_buffer(usbvc_state_t *,
struct v4l2_buffer *, int);
static int usbvc_v4l2_query_ctrl(usbvc_state_t *, struct v4l2_queryctrl *);
static int usbvc_v4l2_get_ctrl(usbvc_state_t *, struct v4l2_control *);
static int usbvc_v4l2_set_ctrl(usbvc_state_t *, struct v4l2_control *);
static int usbvc_v4l2_set_parm(usbvc_state_t *, struct v4l2_streamparm *);
static int usbvc_v4l2_get_parm(usbvc_state_t *, struct v4l2_streamparm *);
/* Video controls that supported by usbvc driver */
static usbvc_v4l2_ctrl_map_t usbvc_v4l2_ctrls[] = {
{
"Brightness",
PU_BRIGHTNESS_CONTROL,
2,
0,
V4L2_CTRL_TYPE_INTEGER
},
{
"Contrast",
PU_CONTRAST_CONTROL,
2,
1,
V4L2_CTRL_TYPE_INTEGER
},
{
"Saturation",
PU_SATURATION_CONTROL,
2,
3,
V4L2_CTRL_TYPE_INTEGER
},
{
"Hue",
PU_HUE_CONTROL,
2,
2,
V4L2_CTRL_TYPE_INTEGER
},
{
"Gamma",
PU_GAMMA_CONTROL,
2,
5,
V4L2_CTRL_TYPE_INTEGER
}
};
/*
* V4L2 colorspaces.
*/
static const uint8_t color_primaries[] = {
0,
V4L2_COLORSPACE_SRGB,
V4L2_COLORSPACE_470_SYSTEM_M,
V4L2_COLORSPACE_470_SYSTEM_BG,
V4L2_COLORSPACE_SMPTE170M,
V4L2_COLORSPACE_SMPTE240M,
};
/* V4L2 ioctls */
int
usbvc_v4l2_ioctl(usbvc_state_t *usbvcp, int cmd, intptr_t arg, int mode)
{
int rv = 0;
switch (cmd) {
case VIDIOC_QUERYCAP: /* Query capabilities */
{
struct v4l2_capability caps;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_QUERYCAP");
bzero(&caps, sizeof (caps));
(void) strncpy((char *)&caps.driver, "usbvc",
sizeof (caps.driver));
if (usbvcp->usbvc_reg->dev_product) {
(void) strncpy((char *)&caps.card,
usbvcp->usbvc_reg->dev_product, sizeof (caps.card));
} else {
(void) strncpy((char *)&caps.card, "Generic USB video"
"class device", sizeof (caps.card));
}
(void) strncpy((char *)&caps.bus_info, "usb",
sizeof (caps.bus_info));
caps.version = 1;
caps.capabilities = V4L2_CAP_VIDEO_CAPTURE
| V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
USBVC_COPYOUT(caps);
break;
}
case VIDIOC_ENUM_FMT:
{
struct v4l2_fmtdesc fmtdesc;
usbvc_format_group_t *fmtgrp;
usbvc_stream_if_t *strm_if;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_ENUM_FMT");
USBVC_COPYIN(fmtdesc);
mutex_enter(&usbvcp->usbvc_mutex);
strm_if = usbvcp->usbvc_curr_strm;
if (fmtdesc.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
fmtdesc.index >= strm_if->fmtgrp_cnt) {
rv = EINVAL;
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
fmtgrp = &strm_if->format_group[fmtdesc.index];
fmtdesc.pixelformat = fmtgrp->v4l2_pixelformat;
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_ENUM_FMT, idx=%d, grpcnt=%d",
fmtdesc.index, strm_if->fmtgrp_cnt);
switch (fmtgrp->format->bDescriptorSubType) {
case VS_FORMAT_MJPEG:
fmtdesc.flags = V4L2_FMT_FLAG_COMPRESSED;
(void) strncpy(fmtdesc.description, "MJPEG",
sizeof (fmtdesc.description));
break;
case VS_FORMAT_UNCOMPRESSED:
fmtdesc.flags = 0;
if (fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV) {
(void) strncpy(fmtdesc.description, "YUYV",
sizeof (fmtdesc.description));
} else if (fmtdesc.pixelformat == V4L2_PIX_FMT_NV12) {
(void) strncpy(fmtdesc.description, "NV12",
sizeof (fmtdesc.description));
} else {
(void) strncpy(fmtdesc.description,
"Unknown format",
sizeof (fmtdesc.description));
}
break;
default:
fmtdesc.flags = 0;
(void) strncpy(fmtdesc.description, "Unknown format",
sizeof (fmtdesc.description));
}
mutex_exit(&usbvcp->usbvc_mutex);
USBVC_COPYOUT(fmtdesc);
break;
}
case VIDIOC_S_FMT:
{
struct v4l2_format fmt;
usbvc_stream_if_t *strm_if;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_S_FMT");
mutex_enter(&usbvcp->usbvc_mutex);
strm_if = usbvcp->usbvc_curr_strm;
/* If data I/O is in progress */
if (strm_if->start_polling == 1) {
rv = EBUSY;
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
mutex_exit(&usbvcp->usbvc_mutex);
USBVC_COPYIN(fmt);
if (usbvc_v4l2_set_format(usbvcp, &fmt) != USB_SUCCESS) {
rv = EFAULT;
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle,
"V4L2 ioctl VIDIOC_S_FMT fail");
}
USBVC_COPYOUT(fmt);
break;
}
case VIDIOC_G_FMT:
{
struct v4l2_format fmt;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_G_FMT");
USBVC_COPYIN(fmt);
if ((rv = usbvc_v4l2_get_format(usbvcp, &fmt)) != 0) {
break;
}
USBVC_COPYOUT(fmt);
break;
}
case VIDIOC_REQBUFS: /* for memory mapping IO method */
{
struct v4l2_requestbuffers reqbuf;
uint_t bufsize;
usbvc_stream_if_t *strm_if;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_REQBUFS");
USBVC_COPYIN(reqbuf);
if (reqbuf.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
reqbuf.memory != V4L2_MEMORY_MMAP) {
rv = EINVAL;
break;
}
mutex_enter(&usbvcp->usbvc_mutex);
strm_if = usbvcp->usbvc_curr_strm;
if (!strm_if) {
mutex_exit(&usbvcp->usbvc_mutex);
rv = EINVAL;
break;
}
if (reqbuf.count > USBVC_MAX_MAP_BUF_NUM) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle,
"V4L2 ioctl: req too many buffers, fail");
rv = EINVAL;
break;
}
/* If some bufs were already allocated */
if (strm_if->buf_map.buf_cnt) {
/*
* According to v4l2 spec, application can change the
* buffer number and also free all buffers if set
* count to 0
*/
if (reqbuf.count == 0) {
if (strm_if->start_polling == 1) {
mutex_exit(&usbvcp->usbvc_mutex);
usb_pipe_stop_isoc_polling(
strm_if->datain_ph,
USB_FLAGS_SLEEP);
mutex_enter(&usbvcp->usbvc_mutex);
strm_if->start_polling = 0;
}
usbvc_free_map_bufs(usbvcp, strm_if);
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
if (reqbuf.count == strm_if->buf_map.buf_cnt) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle,
"v4l2 ioctls: req the same buffers"
" as we already have, just return success");
break;
} else {
/*
* req different number of bufs, according to
* v4l2 spec, this is not allowed when there
* are some bufs still mapped.
*/
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle,
"v4l2 ioctls: req different number bufs"
"than the exist ones, fail");
rv = EINVAL;
break;
}
}
if (reqbuf.count == 0) {
mutex_exit(&usbvcp->usbvc_mutex);
rv = EINVAL;
break;
}
LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, bufsize);
if ((reqbuf.count =
(uint32_t)usbvc_alloc_map_bufs(usbvcp, strm_if,
reqbuf.count, bufsize)) == 0) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_REQBUFS: alloc fail");
rv = EINVAL;
break;
}
mutex_exit(&usbvcp->usbvc_mutex);
/*
* return buf number that acctually allocated to application
*/
USBVC_COPYOUT(reqbuf);
break;
}
case VIDIOC_QUERYBUF: /* for memory mapping IO method */
{
struct v4l2_buffer buf;
usbvc_buf_grp_t *usbvc_bufg;
USBVC_COPYIN(buf);
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_QUERYBUF: idx=%d", buf.index);
mutex_enter(&usbvcp->usbvc_mutex);
usbvc_bufg = &usbvcp->usbvc_curr_strm->buf_map;
if ((buf.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) ||
(buf.index >= usbvc_bufg->buf_cnt)) {
mutex_exit(&usbvcp->usbvc_mutex);
rv = EINVAL;
break;
}
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_QUERYBUF: len=%d",
usbvc_bufg->buf_head[buf.index].v4l2_buf.length);
usbvc_v4l2_query_buf(usbvcp, &usbvc_bufg->buf_head[buf.index],
&buf);
mutex_exit(&usbvcp->usbvc_mutex);
USBVC_COPYOUT(buf);
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_QUERYBUF,(index=%d)len=%d",
buf.index, buf.length);
break;
}
case VIDIOC_QBUF:
{
struct v4l2_buffer buf;
usbvc_buf_grp_t *usbvc_bufg;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_QBUF");
USBVC_COPYIN(buf);
mutex_enter(&usbvcp->usbvc_mutex);
usbvc_bufg = &usbvcp->usbvc_curr_strm->buf_map;
if ((buf.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) ||
(buf.index >= usbvc_bufg->buf_cnt) ||
(buf.memory != V4L2_MEMORY_MMAP)) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle, "V4L2 ioctl: "
"VIDIOC_QBUF error:index=%d,type=%d,memory=%d",
buf.index, buf.type, buf.memory);
rv = EINVAL;
break;
}
rv = usbvc_v4l2_enqueue_buf(usbvcp,
&usbvc_bufg->buf_head[buf.index], &buf);
if (rv < 0) {
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
mutex_exit(&usbvcp->usbvc_mutex);
USBVC_COPYOUT(buf);
break;
}
case VIDIOC_DQBUF:
{
struct v4l2_buffer buf;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_DQBUF");
USBVC_COPYIN(buf);
mutex_enter(&usbvcp->usbvc_mutex);
if ((rv = usbvc_v4l2_dequeue_buffer(usbvcp, &buf, mode)) != 0) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle, "V4L2 ioctl: "
"VIDIOC_DQBUF: fail, rv=%d", rv);
break;
}
mutex_exit(&usbvcp->usbvc_mutex);
USBVC_COPYOUT(buf);
break;
}
case VIDIOC_STREAMON:
{
int type; /* v4l2_buf_type */
usbvc_stream_if_t *strm_if;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_STREAMON");
USBVC_COPYIN(type);
mutex_enter(&usbvcp->usbvc_mutex);
strm_if = usbvcp->usbvc_curr_strm;
if (!strm_if) {
mutex_exit(&usbvcp->usbvc_mutex);
rv = EINVAL;
break;
}
if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle, "V4L2 ioctl: "
"VIDIOC_STREAMON: fail. Only capture type is"
" supported by now.");
rv = EINVAL;
break;
}
/* if the first read, open isoc pipe */
if (!strm_if->datain_ph) {
if (usbvc_open_isoc_pipe(usbvcp, strm_if) !=
USB_SUCCESS) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle, "V4L2 ioctl:"
" first read, open pipe fail");
rv = EINVAL;
break;
}
}
/* If it is already started */
if (strm_if->start_polling == 1) {
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
/* At present, VIDIOC_STREAMON supports mmap io only. */
if (usbvc_start_isoc_polling(usbvcp, strm_if,
V4L2_MEMORY_MMAP) != USB_SUCCESS) {
rv = EFAULT;
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
strm_if->start_polling = 1;
strm_if->stream_on = 1; /* the only place to set this value */
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
case VIDIOC_STREAMOFF:
{
int type; /* v4l2_buf_type */
usbvc_stream_if_t *strm_if;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_STREAMOFF");
USBVC_COPYIN(type);
mutex_enter(&usbvcp->usbvc_mutex);
strm_if = usbvcp->usbvc_curr_strm;
if (!strm_if) {
mutex_exit(&usbvcp->usbvc_mutex);
rv = EINVAL;
break;
}
if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle, "V4L2 ioctl: "
"VIDIOC_STREAMON: fail. Only capture type is "
"supported by now.");
rv = EINVAL;
break;
}
/* Need close the isoc data pipe if any reads are performed. */
strm_if = usbvcp->usbvc_curr_strm;
if (strm_if->start_polling == 1) {
mutex_exit(&usbvcp->usbvc_mutex);
usb_pipe_stop_isoc_polling(strm_if->datain_ph,
USB_FLAGS_SLEEP);
mutex_enter(&usbvcp->usbvc_mutex);
strm_if->start_polling = 0;
}
strm_if->stream_on = 0;
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
case VIDIOC_ENUMINPUT:
{
struct v4l2_input input;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: ENUMINPUT");
USBVC_COPYIN(input);
if (input.index != 0) { /* Support only one INPUT now */
rv = EINVAL;
break;
}
(void) strncpy((char *)input.name, "Camera Terminal",
sizeof (input.name));
input.type = V4L2_INPUT_TYPE_CAMERA;
USBVC_COPYOUT(input);
break;
}
case VIDIOC_G_INPUT:
{
int input_idx = 0; /* Support only one input now */
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: G_INPUT");
USBVC_COPYOUT(input_idx);
break;
}
case VIDIOC_S_INPUT:
{
int input_idx;
USBVC_COPYIN(input_idx);
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: S_INPUT");
if (input_idx != 0) { /* Support only one input now */
rv = EINVAL;
}
break;
}
/* Query the device that what kinds of video ctrls are supported */
case VIDIOC_QUERYCTRL:
{
struct v4l2_queryctrl queryctrl;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: QUERYCTRL");
USBVC_COPYIN(queryctrl);
if (usbvc_v4l2_query_ctrl(usbvcp, &queryctrl) != USB_SUCCESS) {
rv = EINVAL;
break;
}
USBVC_COPYOUT(queryctrl);
break;
}
case VIDIOC_G_CTRL:
{
struct v4l2_control ctrl;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: G_CTRL");
USBVC_COPYIN(ctrl);
if (usbvc_v4l2_get_ctrl(usbvcp, &ctrl) != USB_SUCCESS) {
rv = EINVAL;
break;
}
USBVC_COPYOUT(ctrl);
break;
}
case VIDIOC_S_CTRL:
{
struct v4l2_control ctrl;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: S_CTRL");
USBVC_COPYIN(ctrl);
if (usbvc_v4l2_set_ctrl(usbvcp, &ctrl) != USB_SUCCESS) {
rv = EINVAL;
break;
}
USBVC_COPYOUT(ctrl);
break;
}
case VIDIOC_S_PARM:
{
struct v4l2_streamparm parm;
usbvc_stream_if_t *strm_if;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_S_PARM");
mutex_enter(&usbvcp->usbvc_mutex);
strm_if = usbvcp->usbvc_curr_strm;
/* If data I/O is in progress */
if (strm_if->start_polling == 1) {
rv = EBUSY;
mutex_exit(&usbvcp->usbvc_mutex);
break;
}
mutex_exit(&usbvcp->usbvc_mutex);
USBVC_COPYIN(parm);
/* Support capture only, so far. */
if (parm.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
rv = EINVAL;
break;
}
if (usbvc_v4l2_set_parm(usbvcp, &parm) != USB_SUCCESS) {
rv = EINVAL;
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle,
"V4L2 ioctl VIDIOC_S_PARM fail");
}
USBVC_COPYOUT(parm);
break;
}
case VIDIOC_G_PARM:
{
struct v4l2_streamparm parm;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"V4L2 ioctl: VIDIOC_G_PARM");
USBVC_COPYIN(parm);
/* Support capture only, so far. */
if (parm.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
rv = EINVAL;
break;
}
if ((rv = usbvc_v4l2_get_parm(usbvcp, &parm)) != USB_SUCCESS) {
break;
}
USBVC_COPYOUT(parm);
break;
}
/* These ioctls are for analog video standards. */
case VIDIOC_G_STD:
case VIDIOC_S_STD:
case VIDIOC_ENUMSTD:
case VIDIOC_QUERYSTD:
rv = EINVAL;
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_ioctl: not a supported cmd, cmd=%x", cmd);
break;
default:
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_ioctl: not a valid cmd value, cmd=%x", cmd);
rv = ENOTTY;
}
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_ioctl: exit, rv=%d", rv);
return (rv);
}
/*
* Convert GUID in uncompressed format descriptor to the pixelformat element
* in struct v4l2_pix_format
*/
uint32_t
usbvc_v4l2_guid2fcc(uint8_t *guid)
{
uint32_t ret;
uint8_t y[16] = USBVC_FORMAT_GUID_YUY2;
uint8_t n[16] = USBVC_FORMAT_GUID_NV12;
if (!memcmp((void *)guid, (void *) &y[0], 16)) {
ret = V4L2_PIX_FMT_YUYV;
return (ret);
}
if (!memcmp((void *)guid, (void *) &n, 16)) {
ret = V4L2_PIX_FMT_NV12;
return (ret);
}
return (0);
}
/*
* Find a frame which has the closest image size as the input args
* (width, height)
*/
static usbvc_frames_t *
usbvc_match_image_size(uint32_t width, uint32_t height,
usbvc_format_group_t *fmtgrp)
{
uint32_t w, h, diff, sz, i;
usbvc_frames_t *frame = NULL;
usbvc_frame_descr_t *descr;
diff = 0xffffffff;
for (i = 0; i < fmtgrp->frame_cnt; i++) {
descr = fmtgrp->frames[i].descr;
if (descr == NULL) {
continue;
}
LE_TO_UINT16(descr->wWidth, 0, w);
LE_TO_UINT16(descr->wHeight, 0, h);
sz = min(w, width) * min(h, height);
sz = (w * h + width * height - sz * 2);
if (sz < diff) {
frame = &fmtgrp->frames[i];
diff = sz;
}
if (diff == 0) {
return (frame);
}
}
return (frame);
}
/* Implement ioctl VIDIOC_S_FMT, set a video format */
static int
usbvc_v4l2_set_format(usbvc_state_t *usbvcp, struct v4l2_format *format)
{
usbvc_vs_probe_commit_t ctrl, ctrl_max, ctrl_min, ctrl_curr;
usbvc_stream_if_t *strm_if;
usbvc_format_group_t *fmtgrp;
usbvc_frames_t *frame;
uint32_t w, h, interval, bandwidth;
uint8_t type, i;
mutex_enter(&usbvcp->usbvc_mutex);
/*
* Get the first stream interface. Todo: deal with multi stream
* interfaces.
*/
strm_if = usbvcp->usbvc_curr_strm;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: strm_if->fmtgrp_cnt=%d",
strm_if->fmtgrp_cnt);
/* Find the proper format group according to compress type and guid */
for (i = 0; i < strm_if->fmtgrp_cnt; i++) {
fmtgrp = &strm_if->format_group[i];
/*
* If v4l2_pixelformat is NULL, then that means there is not
* a parsed format in format_group[i].
*/
if (!fmtgrp->v4l2_pixelformat || fmtgrp->frame_cnt == 0) {
USB_DPRINTF_L3(PRINT_MASK_DEVCTRL,
usbvcp->usbvc_log_handle,
"usbvc_set_default_stream_fmt: no frame, fail");
continue;
}
type = fmtgrp->format->bDescriptorSubType;
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: type =%x, i =%d", type, i);
if ((type == VS_FORMAT_MJPEG) ||
(type == VS_FORMAT_UNCOMPRESSED)) {
if (format->fmt.pix.pixelformat ==
fmtgrp->v4l2_pixelformat) {
break;
}
}
}
if (i >= strm_if->fmtgrp_cnt) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: can't find a proper format, "
"pixelformat=%x", format->fmt.pix.pixelformat);
return (USB_FAILURE);
}
fmtgrp = &strm_if->format_group[i];
frame = usbvc_match_image_size(format->fmt.pix.width,
format->fmt.pix.height, fmtgrp);
if (frame == NULL) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: can't find a proper frame, rw=%d, "
"rh=%d", format->fmt.pix.width, format->fmt.pix.height);
return (USB_FAILURE);
}
/* frame interval */
LE_TO_UINT32(frame->descr->dwDefaultFrameInterval, 0, interval);
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: Default Frame Interval=%x", interval);
/*
* Begin negotiate formats.
*/
bzero((void *)&ctrl, sizeof (usbvc_vs_probe_commit_t));
/* dwFrameInterval is fixed */
ctrl.bmHint[0] = 1;
ctrl.bFormatIndex = fmtgrp->format->bFormatIndex;
ctrl.bFrameIndex = frame->descr->bFrameIndex;
UINT32_TO_LE(interval, 0, ctrl.dwFrameInterval);
mutex_exit(&usbvcp->usbvc_mutex);
/* Probe, just a test before the real try */
if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl, VS_PROBE_CONTROL)
!= USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: set probe failed");
return (USB_FAILURE);
}
/* Get max values */
if (usbvc_vs_get_probe(usbvcp, strm_if, &ctrl_max, GET_MAX) !=
USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: get probe MAX failed");
return (USB_FAILURE);
}
/* Use the best quality first */
bcopy(&ctrl_max.wCompQuality, &ctrl.wCompQuality, 2);
/*
* By now, we've get some parametres of ctrl req, next try to set ctrl.
*/
for (i = 0; i < 2; i++) {
/* Probe */
if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl,
VS_PROBE_CONTROL) != USB_SUCCESS) {
return (USB_FAILURE);
}
/* Get current value after probe */
if (usbvc_vs_get_probe(usbvcp, strm_if, &ctrl_curr, GET_CUR)
!= USB_SUCCESS) {
return (USB_FAILURE);
}
LE_TO_UINT32(ctrl_curr.dwMaxPayloadTransferSize, 0, bandwidth);
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: bandwidth=%x", bandwidth);
/*
* If the bandwidth does not exceed the max value of all the
* alternatives in this interface, we done.
*/
if (bandwidth <= strm_if->max_isoc_payload) {
break;
}
if (i >= 1) {
return (USB_FAILURE);
}
/* Get minimum values since the bandwidth is not enough */
if (usbvc_vs_get_probe(usbvcp, strm_if, &ctrl_min, GET_MIN) !=
USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: get probe MIN failed");
return (USB_FAILURE);
}
/* To keep simple, just use some minimum values to try again */
bcopy(&ctrl_min.wKeyFrameRate, &ctrl_curr.wKeyFrameRate, 2);
bcopy(&ctrl_min.wPFrameRate, &ctrl_curr.wPFrameRate, 2);
bcopy(&ctrl_min.wCompWindowSize, &ctrl_curr.wCompWindowSize, 2);
bcopy(&ctrl_max.wCompQuality, &ctrl_curr.wCompQuality, 2);
bcopy(&ctrl_curr, &ctrl,
sizeof (usbvc_vs_probe_commit_t));
}
bcopy(&ctrl_curr, &ctrl, sizeof (usbvc_vs_probe_commit_t));
/* commit the values we negotiated above */
if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl,
VS_COMMIT_CONTROL) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: set probe failed, i=%d", i);
return (USB_FAILURE);
}
mutex_enter(&usbvcp->usbvc_mutex);
/*
* It's good to check index here before use it. bFormatIndex is based
* on 1, and format_group[i] is based on 0, so minus 1
*/
i = ctrl.bFormatIndex - 1;
if (i < strm_if->fmtgrp_cnt) {
strm_if->cur_format_group = &strm_if->format_group[i];
} else {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: format index out of range");
mutex_exit(&usbvcp->usbvc_mutex);
return (USB_FAILURE);
}
/* bFrameIndex is based on 1, and frames[i] is based on 0, so minus 1 */
i = ctrl.bFrameIndex -1;
if (i < strm_if->cur_format_group->frame_cnt) {
strm_if->cur_format_group->cur_frame =
&strm_if->cur_format_group->frames[i];
} else {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: frame index out of range");
return (USB_FAILURE);
}
/*
* by now, the video format is set successfully. record the current
* setting to strm_if->ctrl_pc
*/
bcopy(&ctrl_curr, &strm_if->ctrl_pc, sizeof (usbvc_vs_probe_commit_t));
format->fmt.pix.colorspace = fmtgrp->v4l2_color;
format->fmt.pix.field = V4L2_FIELD_NONE;
format->fmt.pix.priv = 0;
LE_TO_UINT16(frame->descr->wWidth, 0, w);
LE_TO_UINT16(frame->descr->wHeight, 0, h);
format->fmt.pix.width = w;
format->fmt.pix.height = h;
format->fmt.pix.bytesperline = fmtgrp->v4l2_bpp * w;
LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0,
format->fmt.pix.sizeimage);
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_format: dwMaxVideoFrameSize=%x, w=%x, h=%x",
format->fmt.pix.sizeimage, w, h);
return (USB_SUCCESS);
}
/* Implement ioctl VIDIOC_G_FMT, get the current video format */
static int
usbvc_v4l2_get_format(usbvc_state_t *usbvcp, struct v4l2_format *format)
{
usbvc_stream_if_t *strm_if;
usbvc_format_group_t *fmtgrp;
uint16_t w, h;
if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
return (EINVAL);
}
mutex_enter(&usbvcp->usbvc_mutex);
/* get the current interface. */
strm_if = usbvcp->usbvc_curr_strm;
fmtgrp = strm_if->cur_format_group;
if (!fmtgrp || !fmtgrp->cur_frame) {
mutex_exit(&usbvcp->usbvc_mutex);
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_get_format: fail, no current format or frame,"
"fmtgrp=%p", (void *)fmtgrp);
return (EINVAL);
}
format->fmt.pix.colorspace = fmtgrp->v4l2_color;
format->fmt.pix.priv = 0;
format->fmt.pix.pixelformat = fmtgrp->v4l2_pixelformat;
LE_TO_UINT16(fmtgrp->cur_frame->descr->wWidth, 0, w);
LE_TO_UINT16(fmtgrp->cur_frame->descr->wHeight, 0, h);
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"v4l2 ioctl get format ");
format->fmt.pix.width = w;
format->fmt.pix.height = h;
format->fmt.pix.field = V4L2_FIELD_NONE;
format->fmt.pix.bytesperline = fmtgrp->v4l2_bpp * w;
LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0,
format->fmt.pix.sizeimage);
mutex_exit(&usbvcp->usbvc_mutex);
return (0);
}
/*
* Convert color space descriptor's bColorPrimaries to the colorspace element
* in struct v4l2_pix_format
*/
uint8_t
usbvc_v4l2_colorspace(uint8_t color_prim)
{
if (color_prim < NELEM(color_primaries)) {
return (color_primaries[color_prim]);
}
return (0);
}
/* Implement ioctl VIDIOC_QUERYBUF, get the buf status */
static void
usbvc_v4l2_query_buf(usbvc_state_t *usbvcp, usbvc_buf_t *usbvc_buf,
struct v4l2_buffer *v4l2_buf)
{
ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
bcopy(&(usbvc_buf->v4l2_buf), v4l2_buf, sizeof (struct v4l2_buffer));
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_query_buf: uv_buf_len=%d, len=%d",
usbvc_buf->v4l2_buf.length, v4l2_buf->length);
if (usbvc_buf->status >= USBVC_BUF_MAPPED) {
v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;
}
switch (usbvc_buf->status) {
case USBVC_BUF_DONE:
case USBVC_BUF_ERR:
v4l2_buf->flags |= V4L2_BUF_FLAG_DONE;
break;
case USBVC_BUF_EMPTY:
v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
break;
case USBVC_BUF_INIT:
default:
break;
}
}
/* Implement ioctl VIDIOC_QBUF, queue a empty buf to the free list */
static int
usbvc_v4l2_enqueue_buf(usbvc_state_t *usbvcp, usbvc_buf_t *usbvc_buf,
struct v4l2_buffer *buf)
{
usbvc_buf_t *donebuf;
boolean_t queued = B_FALSE;
usbvc_buf_grp_t *bufgrp;
ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
bufgrp = &usbvcp->usbvc_curr_strm->buf_map;
if (usbvc_buf == bufgrp->buf_filling) {
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"enqueue_buffer(%d) , want to queue buf_filling, "
"just return success", buf->index);
return (0);
}
if (!list_is_empty(&bufgrp->uv_buf_done)) {
donebuf = (usbvc_buf_t *)list_head(&bufgrp->uv_buf_done);
while (donebuf) {
if (donebuf == &(bufgrp->buf_head[buf->index])) {
queued = B_TRUE;
break;
}
donebuf = (usbvc_buf_t *)list_next(&bufgrp->uv_buf_done,
donebuf);
}
}
if (queued) {
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"enqueue_buffer(%d), still in done list, don't insert to"
" free list", buf->index);
return (0);
}
if (usbvc_buf->status == USBVC_BUF_EMPTY) {
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"enqueue buffer(%d), already queued.", buf->index);
return (0);
}
if (usbvc_buf->status < USBVC_BUF_MAPPED) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"enqueue buffer(%d), state error, not mapped.", buf->index);
return (EINVAL);
}
/*
* The buf is put to the buf free list when allocated, so, if the buf
* is the first time to enqueue, just change the state to empty is
* enough.
*/
if (usbvc_buf->status == USBVC_BUF_MAPPED) {
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"queue_buffer(%d), 1st time queue this buf", buf->index);
usbvc_buf->status = USBVC_BUF_EMPTY;
} else {
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"enqueue_buffer(%d) , USBVC_BUF_EMPTY", buf->index);
usbvc_buf->status = USBVC_BUF_EMPTY;
usbvc_buf->v4l2_buf.bytesused = 0;
list_insert_tail(&bufgrp->uv_buf_free, usbvc_buf);
}
buf->flags &= ~V4L2_BUF_FLAG_DONE;
buf->flags |= V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED;
return (0);
}
/* Implement ioctl VIDIOC_DQBUF, pick a buf from done list */
static int
usbvc_v4l2_dequeue_buffer(usbvc_state_t *usbvcp, struct v4l2_buffer *buf,
int mode)
{
usbvc_buf_t *buf_done;
ASSERT(mutex_owned(&usbvcp->usbvc_mutex));
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_dequeue_buffer: idx=%x", buf->index);
/* v4l2 spec: app just set type and memory field */
if ((buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) ||
(buf->memory != V4L2_MEMORY_MMAP)) {
return (EINVAL);
}
if ((mode & (O_NDELAY|O_NONBLOCK)) &&
(list_is_empty(&usbvcp->usbvc_curr_strm->buf_map.uv_buf_done))) {
/* non-blocking */
return (EAGAIN);
}
/* no available buffers, block here */
while (list_is_empty(&usbvcp->usbvc_curr_strm->buf_map.uv_buf_done)) {
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_dequeue_buffer: wait for done buf");
if (cv_wait_sig(&usbvcp->usbvc_mapio_cv, &usbvcp->usbvc_mutex)
<= 0) {
/* no done buf and is signaled */
return (EINTR);
}
if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) {
/* Device is disconnected. */
return (EINTR);
}
}
buf_done = list_head(&usbvcp->usbvc_curr_strm->buf_map.uv_buf_done);
list_remove(&usbvcp->usbvc_curr_strm->buf_map.uv_buf_done, buf_done);
/*
* just copy the v4l2_buf structure because app need only the index
* value to locate the mapped memory
*/
bcopy(&buf_done->v4l2_buf, buf, sizeof (struct v4l2_buffer));
buf->flags |= V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_MAPPED;
buf->bytesused = buf_done->filled;
USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_dequeue_buffer: bytesused=%d, idx=%x, status=%d",
buf->bytesused, buf->index, buf_done->status);
return (0);
}
/*
* Check if a ctrl_id is supported by the device, if yes, find the
* corresponding processing unit and fill usbvc_v4l2_ctrl_t
*/
static int
usbvc_v4l2_match_ctrl(usbvc_state_t *usbvcp, usbvc_v4l2_ctrl_t *ctrl,
uint32_t ctrl_id)
{
uint8_t idx;
usbvc_units_t *unit;
uchar_t bit;
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_match_ctrl: ctrl_id=%x", ctrl_id);
if (ctrl_id >= V4L2_CID_PRIVATE_BASE) {
return (USB_FAILURE);
}
if (ctrl_id < V4L2_CID_BASE) {
return (USB_FAILURE);
}
/* get the idx of ctrl array usbvc_v4l2_ctrl */
idx = ctrl_id - V4L2_CID_BASE;
if (ctrl_id == V4L2_CID_GAMMA) {
/* The 4th one is for Gamma ctrl */
bit = usbvc_v4l2_ctrls[4].bit;
} else if ((ctrl_id >= V4L2_CID_BRIGHTNESS) &&
(ctrl_id <= V4L2_CID_HUE)) {
/* The idxth one is for this ctrl */
bit = usbvc_v4l2_ctrls[idx].bit;
} else {
return (USB_FAILURE);
}
unit = (usbvc_units_t *)list_head(&usbvcp->usbvc_unit_list);
/*
* Check if there is a processing unit supportting this ctrl.
* Todo: check if the ctrl and the unit is really for the right
* stream interface in case of multi stream interfaces.
*/
while (unit != NULL) {
if (unit->descr->bDescriptorSubType == VC_PROCESSING_UNIT) {
if (bit >=
(unit->descr->unit.processing.bControlSize * 8)) {
/*
* If this unit's bmControls size is smaller
* than bit, then next
*/
unit = (usbvc_units_t *)
list_next(&usbvcp->usbvc_unit_list, unit);
continue;
} else {
/*
* The first two bytes of bmControls are
* for ctrls
*/
if ((bit < 8) &&
unit->bmControls[0] & (0x1 << bit)) {
break;
}
if ((bit >= 8 && bit < 16) &&
unit->bmControls[1] & (0x1 << bit)) {
break;
}
}
}
unit = (usbvc_units_t *)list_next(&usbvcp->usbvc_unit_list,
unit);
}
if (unit == NULL) {
return (USB_FAILURE);
}
ctrl->entity_id = unit->descr->bUnitID;
if (ctrl_id == V4L2_CID_GAMMA) {
ctrl->ctrl_map = &usbvc_v4l2_ctrls[4];
} else {
ctrl->ctrl_map = &usbvc_v4l2_ctrls[idx];
}
return (USB_SUCCESS);
}
/*
* Implement ioctl VIDIOC_QUERYCTRL, query the ctrl types that the device
* supports
*/
static int
usbvc_v4l2_query_ctrl(usbvc_state_t *usbvcp, struct v4l2_queryctrl *queryctrl)
{
usbvc_v4l2_ctrl_t ctrl;
mblk_t *data;
char req[16];
if (usbvc_v4l2_match_ctrl(usbvcp, &ctrl, queryctrl->id) !=
USB_SUCCESS) {
return (USB_FAILURE);
}
if ((data = allocb(ctrl.ctrl_map->len, BPRI_LO)) == NULL) {
return (USB_FAILURE);
}
if (usbvc_vc_get_ctrl(usbvcp, GET_MIN, ctrl.entity_id,
ctrl.ctrl_map->selector, ctrl.ctrl_map->len, data) !=
USB_SUCCESS) {
(void) strncpy(&req[0], "GET_MIN", sizeof (req));
goto fail;
}
LE_TO_UINT16(data->b_rptr, 0, queryctrl->minimum);
if (usbvc_vc_get_ctrl(usbvcp, GET_MAX, ctrl.entity_id,
ctrl.ctrl_map->selector, ctrl.ctrl_map->len, data) != USB_SUCCESS) {
(void) strncpy(&req[0], "GET_MAX", sizeof (req));
goto fail;
}
LE_TO_UINT16(data->b_rptr, 0, queryctrl->maximum);
if (usbvc_vc_get_ctrl(usbvcp, GET_RES, ctrl.entity_id,
ctrl.ctrl_map->selector, ctrl.ctrl_map->len, data) != USB_SUCCESS) {
(void) strncpy(&req[0], "GET_RES", sizeof (req));
goto fail;
}
LE_TO_UINT16(data->b_rptr, 0, queryctrl->step);
if (usbvc_vc_get_ctrl(usbvcp, GET_DEF, ctrl.entity_id,
ctrl.ctrl_map->selector, ctrl.ctrl_map->len, data) != USB_SUCCESS) {
(void) strncpy(&req[0], "GET_DEF", sizeof (req));
goto fail;
}
LE_TO_UINT16(data->b_rptr, 0, queryctrl->default_value);
(void) strncpy(queryctrl->name, ctrl.ctrl_map->name,
sizeof (queryctrl->name));
queryctrl->type = ctrl.ctrl_map->type;
queryctrl->flags = 0;
if (data) {
freemsg(data);
}
return (USB_SUCCESS);
fail:
if (data) {
freemsg(data);
}
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_query_ctrl: fail when %s", req);
return (USB_FAILURE);
}
/* Implement ioctl VIDIOC_G_CTRL, get current ctrl */
static int
usbvc_v4l2_get_ctrl(usbvc_state_t *usbvcp, struct v4l2_control *v4l2_ctrl)
{
usbvc_v4l2_ctrl_t ctrl;
mblk_t *data;
if (usbvc_v4l2_match_ctrl(usbvcp, &ctrl, v4l2_ctrl->id) !=
USB_SUCCESS) {
return (USB_FAILURE);
}
if ((data = allocb(ctrl.ctrl_map->len, BPRI_LO)) == NULL) {
return (USB_FAILURE);
}
if (usbvc_vc_get_ctrl(usbvcp, GET_CUR, ctrl.entity_id,
ctrl.ctrl_map->selector, ctrl.ctrl_map->len, data) != USB_SUCCESS) {
if (data) {
freemsg(data);
}
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_get_ctrl: fail");
return (USB_FAILURE);
}
LE_TO_UINT16(data->b_rptr, 0, v4l2_ctrl->value);
if (data) {
freemsg(data);
}
return (USB_SUCCESS);
}
/* Implement ioctl VIDIOC_S_CTRL */
static int
usbvc_v4l2_set_ctrl(usbvc_state_t *usbvcp, struct v4l2_control *v4l2_ctrl)
{
usbvc_v4l2_ctrl_t ctrl;
mblk_t *data;
if (usbvc_v4l2_match_ctrl(usbvcp, &ctrl, v4l2_ctrl->id) !=
USB_SUCCESS) {
return (USB_FAILURE);
}
if ((data = allocb(ctrl.ctrl_map->len, BPRI_LO)) == NULL) {
return (USB_FAILURE);
}
UINT16_TO_LE(v4l2_ctrl->value, 0, data->b_wptr);
data->b_wptr += 2;
if (usbvc_vc_set_ctrl(usbvcp, SET_CUR, ctrl.entity_id,
ctrl.ctrl_map->selector, ctrl.ctrl_map->len, data) !=
USB_SUCCESS) {
if (data) {
freemsg(data);
}
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_ctrl: fail");
return (USB_FAILURE);
}
if (data) {
freemsg(data);
}
return (USB_SUCCESS);
}
/* For the given interval, find the closest frame interval to it. */
static uint32_t
usbvc_find_interval(usbvc_frames_t *frame, uint32_t interval)
{
uint32_t step, i, closest, index, approx1, approx2;
/*
* for continuous case, there is a min and a max, and also a step
* value. The available intervals are those between min and max
* values.
*/
if (!frame->descr->bFrameIntervalType) {
step = frame->dwFrameIntervalStep;
if (step == 0) {
/* a malfunction device */
return (0);
} else if (interval <= frame->dwMinFrameInterval) {
/* return the most possible interval we can handle */
return (frame->dwMinFrameInterval);
} else if (interval >= frame->dwMaxFrameInterval) {
/* return the most possible interval we can handle */
return (frame->dwMaxFrameInterval);
}
approx1 = (interval / step) * step;
approx2 = approx1 + step;
closest = ((interval - approx1) < (approx2 - interval)) ?
approx1 : approx2;
return (closest);
}
/*
* for discrete case, search all the available intervals, find the
* closest one.
*/
closest = 0;
approx2 = (uint32_t)-1;
for (index = 0; index < frame->descr->bFrameIntervalType; index++) {
LE_TO_UINT32(frame->dwFrameInterval, index * 4, i);
approx1 = (i > interval) ? (i - interval) : (interval - i);
if (approx1 == 0) {
/* find the matched one, return it immediately */
return (i);
}
if (approx1 < approx2) {
approx2 = approx1;
closest = i;
}
}
return (closest);
}
/* Implement ioctl VIDIOC_S_PARM. Support capture only, so far. */
static int
usbvc_v4l2_set_parm(usbvc_state_t *usbvcp, struct v4l2_streamparm *parm)
{
usbvc_stream_if_t *strm_if;
usbvc_format_group_t *cur_fmt;
usbvc_frames_t *cur_frame;
uint32_t n, d, c, i;
usbvc_vs_probe_commit_t ctrl;
mutex_enter(&usbvcp->usbvc_mutex);
strm_if = usbvcp->usbvc_curr_strm;
if (!strm_if->cur_format_group ||
!strm_if->cur_format_group->cur_frame) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: current format or"
" frame is not set. cur_fmt=%p",
(void *)strm_if->cur_format_group);
mutex_exit(&usbvcp->usbvc_mutex);
return (USB_FAILURE);
}
cur_fmt = strm_if->cur_format_group;
cur_frame = cur_fmt->cur_frame;
mutex_exit(&usbvcp->usbvc_mutex);
if (parm->parm.capture.readbuffers > USBVC_MAX_READ_BUF_NUM) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: ask too many read buffers,"
" readbuffers=%d",
parm->parm.capture.readbuffers);
return (USB_FAILURE);
}
n = parm->parm.capture.timeperframe.numerator;
d = parm->parm.capture.timeperframe.denominator;
/* check the values passed in, in case of zero devide */
if (d == 0) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: invalid denominator=%d", d);
return (USB_FAILURE);
}
/*
* UVC frame intervals are in 100ns units, need convert from
* 1s unit to 100ns unit
*/
c = USBVC_FRAME_INTERVAL_DENOMINATOR;
/* check the values passed in, in case of overflow */
if (n / d >= ((uint32_t)-1) / c) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: overflow, numerator=%d,"
" denominator=%d", n, d);
return (USB_FAILURE);
}
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: numerator=%d, denominator=%d", n, d);
/* compute the interval in 100ns unit */
if (n <= ((uint32_t)-1) / c) {
i = (n * c) / d;
} else {
do {
n >>= 1;
d >>= 1;
/* decrease both n and d, in case overflow */
} while (n && d && n > ((uint32_t)-1) / c);
if (!d) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL,
usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: can't compute interval,"
" denominator=%d", d);
return (USB_FAILURE);
}
i = (n * c) / d;
}
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: want interval=%d, n=%d, d=%d, c=%d",
i, n, d, c);
/*
* Begin negotiate frame intervals.
*/
bcopy(&strm_if->ctrl_pc, &ctrl, sizeof (usbvc_vs_probe_commit_t));
i = usbvc_find_interval(cur_frame, i);
if (i == 0) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: can not find an proper interval."
" i=%d, n=%d, d=%d", i, n, d);
return (USB_FAILURE);
}
USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: get interval=%d", i);
UINT32_TO_LE(i, 0, ctrl.dwFrameInterval);
/* Probe, just a test before the real try */
if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl, VS_PROBE_CONTROL)
!= USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: set probe failed");
return (USB_FAILURE);
}
/* Commit the frame interval. */
if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl, VS_COMMIT_CONTROL)
!= USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle,
"usbvc_v4l2_set_parm: set commit failed");
return (USB_FAILURE);
}
bcopy(&ctrl, &strm_if->ctrl_pc, sizeof (usbvc_vs_probe_commit_t));
LE_TO_UINT32(ctrl.dwFrameInterval, 0, i);
parm->parm.capture.timeperframe.numerator = i;
parm->parm.capture.timeperframe.denominator = c;
mutex_enter(&usbvcp->usbvc_mutex);
/*
* According to ioctl VIDIOC_S_PARM, zero value of readbuffers will not
* be set. And the current value is expected to return to application.
*/
if (parm->parm.capture.readbuffers != 0) {
strm_if->buf_read_num = parm->parm.capture.readbuffers;
} else {
parm->parm.capture.readbuffers = strm_if->buf_read_num;
}
mutex_exit(&usbvcp->usbvc_mutex);
return (USB_SUCCESS);
}
/* Implement ioctl VIDIOC_G_PARM. */
static int
usbvc_v4l2_get_parm(usbvc_state_t *usbvcp, struct v4l2_streamparm *parm)
{
usbvc_stream_if_t *strm_if;
uint32_t n, d;
bzero(parm, sizeof (*parm));
mutex_enter(&usbvcp->usbvc_mutex);
strm_if = usbvcp->usbvc_curr_strm;
/* return the actual number of buffers allocated for read() I/O */
parm->parm.capture.readbuffers = strm_if->buf_read.buf_cnt;
/* in 100ns units */
LE_TO_UINT32(strm_if->ctrl_pc.dwFrameInterval, 0, n);
mutex_exit(&usbvcp->usbvc_mutex);
/*
* According to UVC payload specs, the dwFrameInterval in frame
* descriptors is in 100ns unit.
*/
d = USBVC_FRAME_INTERVAL_DENOMINATOR;
parm->parm.capture.timeperframe.numerator = n;
parm->parm.capture.timeperframe.denominator = d;
/* Support capture only, so far. */
parm->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
parm->parm.capture.capturemode = 0; /* no high quality imaging mode */
parm->parm.capture.extendedmode = 0; /* no driver specific parameters */
/* Always success for current support of this command */
return (USB_SUCCESS);
}