/* * 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. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * USB video class driver (usbvc(7D)) * * 1. Overview * ------------ * * This driver supports USB video class devices that used to capture video, * e.g., some webcams. It is developed according to "USB Device Class * Definition for Video Devices" spec. This spec defines detail info needed by * designing a USB video device. It is available at: * http://www.usb.org/developers/devclass_docs * * This driver implements: * * - V4L2 interfaces for applications to communicate with video devices. * V4L2 is an API that is widely used by video applications, like Ekiga, * luvcview, etc. The API spec is at: * http://www.thedirks.org/v4l2/ * This driver is according to V4L2 spec version 0.20 * * - Video capture function. (Video output is not supported by now.) * * - Isochronous transfer for video data. (Bulk transfer is not supported.) * * - read & mmap I/O methods for userland video applications to get video * data. Userland video applications can use read() system call directly, * it is the simplest way but not the most efficient way. Applications can * also use mmap() system call to map several bufs (they are linked as a * buf list), and then use some specific ioctls to start/stop isoc polling, * to queue/dequeue bufs. * * 2. Source and header files * --------------------------- * * There are two source files and three header files for this driver: * * - usbvc.c Main source file, implements usb video class spec. * * - usbvc_v4l2.c V4L2 interface specific code. * * - usbvc_var.h Main header file, includes soft state structure. * * - usbvc.h The descriptors in usb video class spec. * * - videodev2.h This header file is included in V4L2 spec. It defines * ioctls and data structures that used as an interface between video * applications and video drivers. This is the only header file that * usbvc driver should export to userland application. * * 3. USB video class devices overview * ----------------------------------- * According to UVC spec, there must be one control interface in a UVC device. * Control interface is used to receive control commands from user, all the * commands are sent through default ctrl pipe. usbvc driver implements V4L2 * API, so ioctls are implemented to relay user commands to UVC device. * * There can be no or multiple stream interfaces in a UVC device. Stream * interfaces are used to do video data I/O. In practice, if no stream * interface, the video device can do nothing since it has no data I/O. * * usbvc driver parses descriptors of control interface and stream interfaces. * The descriptors tell the function layout and the capability of the device. * During attach, usbvc driver set up some key data structures according to * the descriptors. * * 4. I/O methods * --------------- * * Userland applications use ioctls to set/get video formats of the device, * and control brightness, contrast, image size, etc. * * Besides implementing standard read I/O method to get video data from * the device, usbvc driver also implements some specific ioctls to implement * mmap I/O method. * * A view from userland application: ioctl and mmap flow chart: * * REQBUFS -> QUERYBUF -> mmap() -> * * -> QBUF -> STREAMON -> DQBUF -> process image -> QBUF * ^ | * | | * | v * |---<-------------------- * * The above queue and dequeue buf operations can be stopped by issuing a * STREAMOFF ioctl. * * 5. Device states * ---------------- * * The device has four states (refer to usbai.h): * * - USB_DEV_ONLINE: In action or ready for action. * * - USB_DEV_DISCONNECTED: Hotplug removed, or device not present/correct * on resume (CPR). * * - USB_DEV_SUSPENDED: Device has been suspended along with the system. * * - USB_DEV_PWRED_DOWN: Device has been powered down. (Note that this * driver supports only two power states, powered down and * full power.) * * 6. Serialize * ------------- * In order to avoid race conditions between driver entry points, access to * the device is serialized. All the ioctls, and read, open/close are * serialized. The functions usbvc_serialize/release_access are implemented * for this purpose. * * 7. PM & CPR * ------------ * PM & CPR are supported. pm_busy_component and pm_idle_component mark * the device as busy or idle to the system. */ #if defined(lint) && !defined(DEBUG) #define DEBUG #endif #define USBDRV_MAJOR_VER 2 #define USBDRV_MINOR_VER 0 #include #include #include #include #include /* V4L2 API header file */ /* Descriptors according to USB video class spec */ #include static uint_t usbvc_errmask = (uint_t)PRINT_MASK_ALL; static uint_t usbvc_errlevel = 4; static uint_t usbvc_instance_debug = (uint_t)-1; static char *name = "usbvc"; /* Driver name, used all over. */ /* * Function Prototypes */ /* Entries */ static int usbvc_info(dev_info_t *, ddi_info_cmd_t, void *, void **); static int usbvc_attach(dev_info_t *, ddi_attach_cmd_t); static int usbvc_detach(dev_info_t *, ddi_detach_cmd_t); static void usbvc_cleanup(dev_info_t *, usbvc_state_t *); static int usbvc_open(dev_t *, int, int, cred_t *); static int usbvc_close(dev_t, int, int, cred_t *); static int usbvc_read(dev_t, struct uio *uip_p, cred_t *); static int usbvc_strategy(struct buf *); static void usbvc_minphys(struct buf *); static int usbvc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); static int usbvc_devmap(dev_t, devmap_cookie_t, offset_t, size_t, size_t *, uint_t); /* pm and cpr */ static int usbvc_power(dev_info_t *, int, int); static void usbvc_init_power_mgmt(usbvc_state_t *); static void usbvc_destroy_power_mgmt(usbvc_state_t *); static void usbvc_pm_busy_component(usbvc_state_t *); static void usbvc_pm_idle_component(usbvc_state_t *); static int usbvc_pwrlvl0(usbvc_state_t *); static int usbvc_pwrlvl1(usbvc_state_t *); static int usbvc_pwrlvl2(usbvc_state_t *); static int usbvc_pwrlvl3(usbvc_state_t *); static void usbvc_cpr_suspend(dev_info_t *); static void usbvc_cpr_resume(dev_info_t *); static void usbvc_restore_device_state(dev_info_t *, usbvc_state_t *); /* Events */ static int usbvc_disconnect_event_cb(dev_info_t *); static int usbvc_reconnect_event_cb(dev_info_t *); /* Sync objs and lists */ static void usbvc_init_sync_objs(usbvc_state_t *); static void usbvc_fini_sync_objs(usbvc_state_t *); static void usbvc_init_lists(usbvc_state_t *); static void usbvc_fini_lists(usbvc_state_t *); static void usbvc_free_ctrl_descr(usbvc_state_t *); static void usbvc_free_stream_descr(usbvc_state_t *); /* Parse descriptors */ static int usbvc_chk_descr_len(uint8_t, uint8_t, uint8_t, usb_cvs_data_t *); static usbvc_stream_if_t *usbvc_parse_stream_if(usbvc_state_t *, int); static int usbvc_parse_ctrl_if(usbvc_state_t *); static int usbvc_parse_stream_ifs(usbvc_state_t *); static void usbvc_parse_color_still(usbvc_state_t *, usbvc_format_group_t *, usb_cvs_data_t *, uint_t, uint_t); static void usbvc_parse_frames(usbvc_state_t *, usbvc_format_group_t *, usb_cvs_data_t *, uint_t, uint_t); static int usbvc_parse_format_group(usbvc_state_t *, usbvc_format_group_t *, usb_cvs_data_t *, uint_t, uint_t); static int usbvc_parse_format_groups(usbvc_state_t *, usbvc_stream_if_t *); static int usbvc_parse_stream_header(usbvc_state_t *, usbvc_stream_if_t *); /* read I/O functions */ static int usbvc_alloc_read_bufs(usbvc_state_t *, usbvc_stream_if_t *); static int usbvc_read_buf(usbvc_state_t *, struct buf *); static void usbvc_free_read_buf(usbvc_buf_t *); static void usbvc_free_read_bufs(usbvc_state_t *, usbvc_stream_if_t *); static void usbvc_close_isoc_pipe(usbvc_state_t *, usbvc_stream_if_t *); /* callbacks */ static void usbvc_isoc_cb(usb_pipe_handle_t, usb_isoc_req_t *); static void usbvc_isoc_exc_cb(usb_pipe_handle_t, usb_isoc_req_t *); /* Others */ static int usbvc_set_alt(usbvc_state_t *, usbvc_stream_if_t *); static int usbvc_decode_stream_header(usbvc_state_t *, usbvc_buf_grp_t *, mblk_t *, int); static int usbvc_serialize_access(usbvc_state_t *, boolean_t); static void usbvc_release_access(usbvc_state_t *); static int usbvc_set_default_stream_fmt(usbvc_state_t *); static usb_event_t usbvc_events = { usbvc_disconnect_event_cb, usbvc_reconnect_event_cb, NULL, NULL }; /* module loading stuff */ struct cb_ops usbvc_cb_ops = { usbvc_open, /* open */ usbvc_close, /* close */ usbvc_strategy, /* strategy */ nulldev, /* print */ nulldev, /* dump */ usbvc_read, /* read */ nodev, /* write */ usbvc_ioctl, /* ioctl */ usbvc_devmap, /* devmap */ nodev, /* mmap */ ddi_devmap_segmap, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* cb_prop_op */ NULL, /* streamtab */ D_MP | D_DEVMAP }; static struct dev_ops usbvc_ops = { DEVO_REV, /* devo_rev, */ 0, /* refcnt */ usbvc_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ usbvc_attach, /* attach */ usbvc_detach, /* detach */ nodev, /* reset */ &usbvc_cb_ops, /* driver operations */ NULL, /* bus operations */ usbvc_power /* power */ }; static struct modldrv usbvc_modldrv = { &mod_driverops, "USB video class driver", &usbvc_ops }; static struct modlinkage modlinkage = { MODREV_1, &usbvc_modldrv, NULL }; /* Soft state structures */ #define USBVC_INITIAL_SOFT_SPACE 1 static void *usbvc_statep; /* * Module-wide initialization routine. */ int _init(void) { int rval; if ((rval = ddi_soft_state_init(&usbvc_statep, sizeof (usbvc_state_t), USBVC_INITIAL_SOFT_SPACE)) != 0) { return (rval); } if ((rval = mod_install(&modlinkage)) != 0) { ddi_soft_state_fini(&usbvc_statep); } return (rval); } /* * Module-wide tear-down routine. */ int _fini(void) { int rval; if ((rval = mod_remove(&modlinkage)) != 0) { return (rval); } ddi_soft_state_fini(&usbvc_statep); return (rval); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * usbvc_info: * Get minor number, soft state structure, etc. */ /*ARGSUSED*/ static int usbvc_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { usbvc_state_t *usbvcp; int error = DDI_FAILURE; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if ((usbvcp = ddi_get_soft_state(usbvc_statep, getminor((dev_t)arg))) != NULL) { *result = usbvcp->usbvc_dip; if (*result != NULL) { error = DDI_SUCCESS; } } else { *result = NULL; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)(uintptr_t)getminor((dev_t)arg); error = DDI_SUCCESS; break; default: break; } return (error); } /* * Entry functions. */ /* * usbvc_attach: * Attach or resume. * * For attach, initialize state and device, including: * state variables, locks, device node * device registration with system * power management, hotplugging * For resume, restore device and state */ static int usbvc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance = ddi_get_instance(dip); usbvc_state_t *usbvcp = NULL; int status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: usbvc_cpr_resume(dip); return (DDI_SUCCESS); default: return (DDI_FAILURE); } if (ddi_soft_state_zalloc(usbvc_statep, instance) == DDI_SUCCESS) { usbvcp = ddi_get_soft_state(usbvc_statep, instance); } if (usbvcp == NULL) { return (DDI_FAILURE); } usbvcp->usbvc_dip = dip; usbvcp->usbvc_log_handle = usb_alloc_log_hdl(dip, "usbvc", &usbvc_errlevel, &usbvc_errmask, &usbvc_instance_debug, 0); USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_attach: enter"); if ((status = usb_client_attach(dip, USBDRV_VERSION, 0)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_attach: usb_client_attach failed, error code:%d", status); goto fail; } if ((status = usb_get_dev_data(dip, &usbvcp->usbvc_reg, USB_PARSE_LVL_ALL, 0)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_attach: usb_get_dev_data failed, error code:%d", status); goto fail; } usbvc_init_sync_objs(usbvcp); /* create minor node */ if ((status = ddi_create_minor_node(dip, name, S_IFCHR, instance, "usb_video", 0)) != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_attach: Error creating minor node, error code:%d", status); goto fail; } mutex_enter(&usbvcp->usbvc_mutex); usbvc_init_lists(usbvcp); usbvcp->usbvc_default_ph = usbvcp->usbvc_reg->dev_default_ph; /* Put online before PM init as can get power managed afterward. */ usbvcp->usbvc_dev_state = USB_DEV_ONLINE; mutex_exit(&usbvcp->usbvc_mutex); /* initialize power management */ usbvc_init_power_mgmt(usbvcp); if ((status = usbvc_parse_ctrl_if(usbvcp)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_attach: parse ctrl interface fail, error code:%d", status); goto fail; } if ((status = usbvc_parse_stream_ifs(usbvcp)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_attach: parse stream interfaces fail, error code:%d", status); goto fail; } (void) usbvc_set_default_stream_fmt(usbvcp); /* Register for events */ if ((status = usb_register_event_cbs(dip, &usbvc_events, 0)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_attach: register_event_cbs failed, error code:%d", status); goto fail; } /* Report device */ ddi_report_dev(dip); return (DDI_SUCCESS); fail: if (usbvcp) { usbvc_cleanup(dip, usbvcp); } return (DDI_FAILURE); } /* * usbvc_detach: * detach or suspend driver instance * * Note: in detach, only contention threads is from pm and disconnnect. */ static int usbvc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance(dip); usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, instance); int rval = USB_FAILURE; switch (cmd) { case DDI_DETACH: mutex_enter(&usbvcp->usbvc_mutex); ASSERT((usbvcp->usbvc_drv_state & USBVC_OPEN) == 0); mutex_exit(&usbvcp->usbvc_mutex); USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_detach: enter for detach"); usbvc_cleanup(dip, usbvcp); rval = USB_SUCCESS; break; case DDI_SUSPEND: USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_detach: enter for suspend"); usbvc_cpr_suspend(dip); rval = USB_SUCCESS; break; default: break; } return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE); } /* * usbvc_cleanup: * clean up the driver state for detach */ static void usbvc_cleanup(dev_info_t *dip, usbvc_state_t *usbvcp) { USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "Cleanup: enter"); if (usbvcp->usbvc_locks_initialized) { /* This must be done 1st to prevent more events from coming. */ usb_unregister_event_cbs(dip, &usbvc_events); /* * At this point, no new activity can be initiated. The driver * has disabled hotplug callbacks. The Solaris framework has * disabled new opens on a device being detached, and does not * allow detaching an open device. * * The following ensures that all driver activity has drained. */ mutex_enter(&usbvcp->usbvc_mutex); (void) usbvc_serialize_access(usbvcp, USBVC_SER_NOSIG); usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); /* All device activity has died down. */ usbvc_destroy_power_mgmt(usbvcp); mutex_enter(&usbvcp->usbvc_mutex); usbvc_fini_lists(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); ddi_remove_minor_node(dip, NULL); usbvc_fini_sync_objs(usbvcp); } usb_client_detach(dip, usbvcp->usbvc_reg); usb_free_log_hdl(usbvcp->usbvc_log_handle); ddi_soft_state_free(usbvc_statep, ddi_get_instance(dip)); ddi_prop_remove_all(dip); } /*ARGSUSED*/ static int usbvc_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) { usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, getminor(*devp)); if (usbvcp == NULL) { return (ENXIO); } /* * Keep it simple: one client at a time. * Exclusive open only */ mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_open: enter, dev_stat=%d", usbvcp->usbvc_dev_state); if (usbvcp->usbvc_dev_state == USB_DEV_DISCONNECTED) { mutex_exit(&usbvcp->usbvc_mutex); return (ENODEV); } if (usbvcp->usbvc_dev_state == USB_DEV_SUSPENDED) { mutex_exit(&usbvcp->usbvc_mutex); return (EIO); } if ((usbvcp->usbvc_drv_state & USBVC_OPEN) != 0) { mutex_exit(&usbvcp->usbvc_mutex); return (EBUSY); } usbvcp->usbvc_drv_state |= USBVC_OPEN; if (usbvc_serialize_access(usbvcp, USBVC_SER_SIG) == 0) { usbvcp->usbvc_drv_state &= ~USBVC_OPEN; usbvcp->usbvc_serial_inuse = B_FALSE; mutex_exit(&usbvcp->usbvc_mutex); return (EINTR); } /* raise power */ usbvc_pm_busy_component(usbvcp); if (usbvcp->usbvc_pm->usbvc_current_power != USB_DEV_OS_FULL_PWR) { usbvcp->usbvc_pm->usbvc_raise_power = B_TRUE; mutex_exit(&usbvcp->usbvc_mutex); (void) pm_raise_power(usbvcp->usbvc_dip, 0, USB_DEV_OS_FULL_PWR); mutex_enter(&usbvcp->usbvc_mutex); usbvcp->usbvc_pm->usbvc_raise_power = B_FALSE; } /* Device is idle until it is used. */ usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_open: end."); return (0); } /*ARGSUSED*/ static int usbvc_close(dev_t dev, int flag, int otyp, cred_t *cred_p) { usbvc_stream_if_t *strm_if; int if_num; usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, getminor(dev)); USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "close: enter"); mutex_enter(&usbvcp->usbvc_mutex); (void) usbvc_serialize_access(usbvcp, USBVC_SER_NOSIG); mutex_exit(&usbvcp->usbvc_mutex); /* Perform device session cleanup here. */ USB_DPRINTF_L3(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "close: cleaning up..."); /* * USBA automatically flushes/resets active non-default pipes * when they are closed. We can't reset default pipe, but we * can wait for all requests on it from this dip to drain. */ (void) usb_pipe_drain_reqs(usbvcp->usbvc_dip, usbvcp->usbvc_reg->dev_default_ph, 0, USB_FLAGS_SLEEP, NULL, 0); mutex_enter(&usbvcp->usbvc_mutex); 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; } usbvc_close_isoc_pipe(usbvcp, strm_if); if_num = strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber; mutex_exit(&usbvcp->usbvc_mutex); /* reset alternate to the default one. */ (void) usb_set_alt_if(usbvcp->usbvc_dip, if_num, 0, USB_FLAGS_SLEEP, NULL, NULL); mutex_enter(&usbvcp->usbvc_mutex); usbvc_free_read_bufs(usbvcp, strm_if); /* reset the desired read buf number to the default value on close */ strm_if->buf_read_num = USBVC_DEFAULT_READ_BUF_NUM; usbvc_free_map_bufs(usbvcp, strm_if); usbvcp->usbvc_drv_state &= ~USBVC_OPEN; usbvc_release_access(usbvcp); usbvc_pm_idle_component(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); return (0); } /*ARGSUSED*/ /* Read isoc data from usb video devices */ static int usbvc_read(dev_t dev, struct uio *uio_p, cred_t *cred_p) { int rval; usbvc_stream_if_t *strm_if; usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, getminor(dev)); USB_DPRINTF_L4(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: enter"); mutex_enter(&usbvcp->usbvc_mutex); if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: Device is not available," " dev_stat=%d", usbvcp->usbvc_dev_state); mutex_exit(&usbvcp->usbvc_mutex); return (EFAULT); } if ((uio_p->uio_fmode & (FNDELAY|FNONBLOCK)) && (usbvcp->usbvc_serial_inuse != B_FALSE)) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: non-blocking read, return fail."); mutex_exit(&usbvcp->usbvc_mutex); return (EAGAIN); } if (usbvc_serialize_access(usbvcp, USBVC_SER_SIG) <= 0) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: serialize_access failed."); rval = EFAULT; goto fail; } /* Get the first stream interface */ strm_if = usbvcp->usbvc_curr_strm; if (!strm_if) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: no stream interfaces"); rval = EFAULT; goto fail; } /* * If it is the first read, open isoc pipe and allocate bufs for * read I/O method. */ if (strm_if->datain_ph == NULL) { if (usbvc_open_isoc_pipe(usbvcp, strm_if) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: first read, open pipe fail"); rval = EFAULT; goto fail; } if (usbvc_alloc_read_bufs(usbvcp, strm_if) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: allocate rw bufs fail"); rval = EFAULT; goto fail; } } /* start polling if it is not started yet */ if (strm_if->start_polling != 1) { if (usbvc_start_isoc_polling(usbvcp, strm_if, NULL) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: usbvc_start_isoc_polling fail"); rval = EFAULT; goto fail; } strm_if->start_polling = 1; } if (list_is_empty(&strm_if->buf_read.uv_buf_done)) { USB_DPRINTF_L3(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: full buf list is empty."); if (uio_p->uio_fmode & (FNDELAY | FNONBLOCK)) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: fail, " "non-blocking read, done buf is empty."); rval = EAGAIN; goto fail; } /* no available buffers, block here */ while (list_is_empty(&strm_if->buf_read.uv_buf_done)) { USB_DPRINTF_L3(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_read: wait for done buf"); if (cv_wait_sig(&usbvcp->usbvc_read_cv, &usbvcp->usbvc_mutex) <= 0) { /* no done buf and cv is signaled */ rval = EINTR; goto fail; } if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) { /* Device is disconnected. */ rval = EINTR; goto fail; } } } mutex_exit(&usbvcp->usbvc_mutex); rval = physio(usbvc_strategy, NULL, dev, B_READ, usbvc_minphys, uio_p); mutex_enter(&usbvcp->usbvc_mutex); usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); return (rval); fail: usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); return (rval); } /* * strategy: * Called through physio to setup and start the transfer. */ static int usbvc_strategy(struct buf *bp) { usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, getminor(bp->b_edev)); USB_DPRINTF_L4(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_strategy: enter"); /* * Initialize residual count here in case transfer doesn't even get * started. */ bp->b_resid = bp->b_bcount; /* Needed as this is a character driver. */ if (bp->b_flags & (B_PHYS | B_PAGEIO)) { bp_mapin(bp); } mutex_enter(&usbvcp->usbvc_mutex); /* Make sure device has not been disconnected. */ if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_strategy: device can't be accessed"); mutex_exit(&usbvcp->usbvc_mutex); goto fail; } /* read data from uv_buf_done list */ if (usbvc_read_buf(usbvcp, bp) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_strategy: read full buf list fail"); mutex_exit(&usbvcp->usbvc_mutex); goto fail; } mutex_exit(&usbvcp->usbvc_mutex); biodone(bp); return (0); fail: USB_DPRINTF_L2(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_strategy: strategy fail"); bp->b_private = NULL; bioerror(bp, EIO); biodone(bp); return (0); } static void usbvc_minphys(struct buf *bp) { dev_t dev = bp->b_edev; usbvc_stream_if_t *strm_if; uint32_t maxsize; usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, getminor(dev)); mutex_enter(&usbvcp->usbvc_mutex); strm_if = usbvcp->usbvc_curr_strm; LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, maxsize); USB_DPRINTF_L3(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "usbvc_minphys: max read size=%d", maxsize); if (bp->b_bcount > maxsize) { bp->b_bcount = maxsize; } mutex_exit(&usbvcp->usbvc_mutex); } /* * ioctl entry. */ /*ARGSUSED*/ static int usbvc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred_p, int *rval_p) { int rv = 0; usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, getminor(dev)); if (usbvcp == NULL) { return (ENXIO); } USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle, "ioctl enter, cmd=%x", cmd); mutex_enter(&usbvcp->usbvc_mutex); if (usbvcp->usbvc_dev_state != USB_DEV_ONLINE) { USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle, "ioctl: Device is not online," " dev_stat=%d", usbvcp->usbvc_dev_state); mutex_exit(&usbvcp->usbvc_mutex); return (EFAULT); } if (usbvc_serialize_access(usbvcp, USBVC_SER_SIG) <= 0) { usbvcp->usbvc_serial_inuse = B_FALSE; mutex_exit(&usbvcp->usbvc_mutex); USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle, "serialize_access failed."); return (EFAULT); } mutex_exit(&usbvcp->usbvc_mutex); rv = usbvc_v4l2_ioctl(usbvcp, cmd, arg, mode); mutex_enter(&usbvcp->usbvc_mutex); usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle, "usbvc_ioctl exit"); return (rv); } /* Entry for mmap system call */ static int usbvc_devmap(dev_t dev, devmap_cookie_t handle, offset_t off, size_t len, size_t *maplen, uint_t model) { usbvc_state_t *usbvcp; int error, i; usbvc_buf_t *buf = NULL; usbvc_stream_if_t *strm_if; usbvc_buf_grp_t *bufgrp; usbvcp = ddi_get_soft_state(usbvc_statep, getminor(dev)); if (usbvcp == NULL) { USB_DPRINTF_L2(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle, "usbvc_devmap: usbvcp == NULL"); return (ENXIO); } USB_DPRINTF_L3(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle, "devmap: memory map for instance(%d), off=%llx," "len=%ld, maplen=%ld, model=%d", getminor(dev), off, len, *maplen, model); mutex_enter(&usbvcp->usbvc_mutex); (void) usbvc_serialize_access(usbvcp, USBVC_SER_NOSIG); strm_if = usbvcp->usbvc_curr_strm; if (!strm_if) { USB_DPRINTF_L2(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle, "usbvc_devmap: No current strm if"); mutex_exit(&usbvcp->usbvc_mutex); return (ENXIO); } bufgrp = &strm_if->buf_map; for (i = 0; i < bufgrp->buf_cnt; i++) { if (bufgrp->buf_head[i].v4l2_buf.m.offset == off) { buf = &bufgrp->buf_head[i]; break; } } USB_DPRINTF_L3(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle, "usbvc_devmap: idx=%d", i); if (buf == NULL) { mutex_exit(&usbvcp->usbvc_mutex); return (ENXIO); } /* * round up len to a multiple of a page size, according to chapter * 10 of "writing device drivers" */ len = ptob(btopr(len)); if (len > ptob(btopr(buf->len))) { USB_DPRINTF_L2(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle, "usbvc_devmap: len=0x%lx", len); mutex_exit(&usbvcp->usbvc_mutex); return (ENXIO); } mutex_exit(&usbvcp->usbvc_mutex); error = devmap_umem_setup(handle, usbvcp->usbvc_dip, NULL, buf->umem_cookie, off, len, PROT_ALL, DEVMAP_DEFAULTS, NULL); mutex_enter(&usbvcp->usbvc_mutex); *maplen = len; if (error == 0 && buf->status == USBVC_BUF_INIT) { buf->status = USBVC_BUF_MAPPED; } else { USB_DPRINTF_L3(PRINT_MASK_DEVMAP, usbvcp->usbvc_log_handle, "usbvc_devmap: devmap_umem_setup, err=%d", error); } (void) usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); return (error); } /* * pm and cpr */ /* * usbvc_power : * Power entry point, the workhorse behind pm_raise_power, pm_lower_power, * usb_req_raise_power and usb_req_lower_power. */ /* ARGSUSED */ static int usbvc_power(dev_info_t *dip, int comp, int level) { usbvc_state_t *usbvcp; usbvc_power_t *pm; int rval = USB_FAILURE; usbvcp = ddi_get_soft_state(usbvc_statep, ddi_get_instance(dip)); mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_power: enter: level = %d, dev_state: %x", level, usbvcp->usbvc_dev_state); if (usbvcp->usbvc_pm == NULL) { goto done; } pm = usbvcp->usbvc_pm; /* Check if we are transitioning to a legal power level */ if (USB_DEV_PWRSTATE_OK(pm->usbvc_pwr_states, level)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_power: illegal power level = %d " "pwr_states: %x", level, pm->usbvc_pwr_states); goto done; } /* * if we are about to raise power and asked to lower power, fail */ if (pm->usbvc_raise_power && (level < (int)pm->usbvc_current_power)) { goto done; } switch (level) { case USB_DEV_OS_PWR_OFF : rval = usbvc_pwrlvl0(usbvcp); break; case USB_DEV_OS_PWR_1 : rval = usbvc_pwrlvl1(usbvcp); break; case USB_DEV_OS_PWR_2 : rval = usbvc_pwrlvl2(usbvcp); break; case USB_DEV_OS_FULL_PWR : rval = usbvc_pwrlvl3(usbvcp); break; } done: mutex_exit(&usbvcp->usbvc_mutex); return ((rval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE); } /* * usbvc_init_power_mgmt: * Initialize power management and remote wakeup functionality. * No mutex is necessary in this function as it's called only by attach. */ static void usbvc_init_power_mgmt(usbvc_state_t *usbvcp) { usbvc_power_t *usbvcpm; uint_t pwr_states; USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "init_power_mgmt enter"); /* Allocate the state structure */ usbvcpm = kmem_zalloc(sizeof (usbvc_power_t), KM_SLEEP); mutex_enter(&usbvcp->usbvc_mutex); usbvcp->usbvc_pm = usbvcpm; usbvcpm->usbvc_state = usbvcp; usbvcpm->usbvc_pm_capabilities = 0; usbvcpm->usbvc_current_power = USB_DEV_OS_FULL_PWR; mutex_exit(&usbvcp->usbvc_mutex); if (usb_create_pm_components(usbvcp->usbvc_dip, &pwr_states) == USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_init_power_mgmt: created PM components"); if (usb_handle_remote_wakeup(usbvcp->usbvc_dip, USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS) { usbvcpm->usbvc_wakeup_enabled = 1; } else { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_init_power_mgmt:" " remote wakeup not supported"); } mutex_enter(&usbvcp->usbvc_mutex); usbvcpm->usbvc_pwr_states = (uint8_t)pwr_states; usbvc_pm_busy_component(usbvcp); usbvcpm->usbvc_raise_power = B_TRUE; mutex_exit(&usbvcp->usbvc_mutex); (void) pm_raise_power( usbvcp->usbvc_dip, 0, USB_DEV_OS_FULL_PWR); mutex_enter(&usbvcp->usbvc_mutex); usbvcpm->usbvc_raise_power = B_FALSE; usbvc_pm_idle_component(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); } USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_init_power_mgmt: end"); } /* * usbvc_destroy_power_mgmt: * Shut down and destroy power management and remote wakeup functionality. */ static void usbvc_destroy_power_mgmt(usbvc_state_t *usbvcp) { usbvc_power_t *pm; int rval; USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "destroy_power_mgmt enter"); mutex_enter(&usbvcp->usbvc_mutex); pm = usbvcp->usbvc_pm; if (pm && (usbvcp->usbvc_dev_state != USB_DEV_DISCONNECTED)) { usbvc_pm_busy_component(usbvcp); if (pm->usbvc_wakeup_enabled) { pm->usbvc_raise_power = B_TRUE; mutex_exit(&usbvcp->usbvc_mutex); /* First bring the device to full power */ (void) pm_raise_power(usbvcp->usbvc_dip, 0, USB_DEV_OS_FULL_PWR); if ((rval = usb_handle_remote_wakeup( usbvcp->usbvc_dip, USB_REMOTE_WAKEUP_DISABLE)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_destroy_power_mgmt: " "Error disabling rmt wakeup: rval = %d", rval); } mutex_enter(&usbvcp->usbvc_mutex); pm->usbvc_raise_power = B_FALSE; } mutex_exit(&usbvcp->usbvc_mutex); /* * Since remote wakeup is disabled now, * no one can raise power * and get to device once power is lowered here. */ (void) pm_lower_power(usbvcp->usbvc_dip, 0, USB_DEV_OS_PWR_OFF); mutex_enter(&usbvcp->usbvc_mutex); usbvc_pm_idle_component(usbvcp); } if (pm) { kmem_free(pm, sizeof (usbvc_power_t)); usbvcp->usbvc_pm = NULL; } mutex_exit(&usbvcp->usbvc_mutex); } static void usbvc_pm_busy_component(usbvc_state_t *usbvcp) { ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pm_busy_component: enter"); usbvcp->usbvc_pm->usbvc_pm_busy++; mutex_exit(&usbvcp->usbvc_mutex); if (pm_busy_component(usbvcp->usbvc_dip, 0) != DDI_SUCCESS) { mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pm_busy_component: pm busy fail, usbvc_pm_busy=%d", usbvcp->usbvc_pm->usbvc_pm_busy); usbvcp->usbvc_pm->usbvc_pm_busy--; mutex_exit(&usbvcp->usbvc_mutex); } mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pm_busy_component: exit"); } static void usbvc_pm_idle_component(usbvc_state_t *usbvcp) { ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pm_idle_component: enter"); if (usbvcp->usbvc_pm != NULL) { mutex_exit(&usbvcp->usbvc_mutex); if (pm_idle_component(usbvcp->usbvc_dip, 0) == DDI_SUCCESS) { mutex_enter(&usbvcp->usbvc_mutex); ASSERT(usbvcp->usbvc_pm->usbvc_pm_busy > 0); usbvcp->usbvc_pm->usbvc_pm_busy--; mutex_exit(&usbvcp->usbvc_mutex); } mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pm_idle_component: %d", usbvcp->usbvc_pm->usbvc_pm_busy); } } /* * usbvc_pwrlvl0: * Functions to handle power transition for OS levels 0 -> 3 */ static int usbvc_pwrlvl0(usbvc_state_t *usbvcp) { int rval; USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pwrlvl0, dev_state: %x", usbvcp->usbvc_dev_state); switch (usbvcp->usbvc_dev_state) { case USB_DEV_ONLINE: /* Deny the powerdown request if the device is busy */ if (usbvcp->usbvc_pm->usbvc_pm_busy != 0) { USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pwrlvl0: usbvc_pm_busy"); return (USB_FAILURE); } /* Issue USB D3 command to the device here */ rval = usb_set_device_pwrlvl3(usbvcp->usbvc_dip); ASSERT(rval == USB_SUCCESS); usbvcp->usbvc_dev_state = USB_DEV_PWRED_DOWN; usbvcp->usbvc_pm->usbvc_current_power = USB_DEV_OS_PWR_OFF; /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: /* allow a disconnect/cpr'ed device to go to lower power */ return (USB_SUCCESS); case USB_DEV_PWRED_DOWN: default: USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pwrlvl0: illegal dev state"); return (USB_FAILURE); } } /* * usbvc_pwrlvl1: * Functions to handle power transition to OS levels -> 2 */ static int usbvc_pwrlvl1(usbvc_state_t *usbvcp) { int rval; USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pwrlvl1"); /* Issue USB D2 command to the device here */ rval = usb_set_device_pwrlvl2(usbvcp->usbvc_dip); ASSERT(rval == USB_SUCCESS); return (USB_FAILURE); } /* * usbvc_pwrlvl2: * Functions to handle power transition to OS levels -> 1 */ static int usbvc_pwrlvl2(usbvc_state_t *usbvcp) { int rval; USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pwrlvl2"); /* Issue USB D1 command to the device here */ rval = usb_set_device_pwrlvl1(usbvcp->usbvc_dip); ASSERT(rval == USB_SUCCESS); return (USB_FAILURE); } /* * usbvc_pwrlvl3: * Functions to handle power transition to OS level -> 0 */ static int usbvc_pwrlvl3(usbvc_state_t *usbvcp) { USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pwrlvl3, dev_stat=%d", usbvcp->usbvc_dev_state); switch (usbvcp->usbvc_dev_state) { case USB_DEV_PWRED_DOWN: /* Issue USB D0 command to the device here */ (void) usb_set_device_pwrlvl0(usbvcp->usbvc_dip); usbvcp->usbvc_dev_state = USB_DEV_ONLINE; usbvcp->usbvc_pm->usbvc_current_power = USB_DEV_OS_FULL_PWR; /* FALLTHRU */ case USB_DEV_ONLINE: /* we are already in full power */ /* FALLTHRU */ case USB_DEV_DISCONNECTED: case USB_DEV_SUSPENDED: /* * PM framework tries to put us in full power * during system shutdown. If we are disconnected/cpr'ed * return success anyways */ return (USB_SUCCESS); default: USB_DPRINTF_L2(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_pwrlvl3: illegal dev state"); return (USB_FAILURE); } } /* * usbvc_cpr_suspend: * Clean up device. * Wait for any IO to finish, then close pipes. * Quiesce device. */ static void usbvc_cpr_suspend(dev_info_t *dip) { int instance = ddi_get_instance(dip); usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, instance); USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_cpr_suspend enter"); mutex_enter(&usbvcp->usbvc_mutex); /* * Set dev_state to suspended so other driver threads don't start any * new I/O. */ usbvcp->usbvc_dev_state = USB_DEV_SUSPENDED; /* * Wake up the read threads in case there are any threads are blocking. * After being waked up, those threads will quit immediately since the * dev_state is not ONLINE */ if (usbvcp->usbvc_io_type == V4L2_MEMORY_MMAP) { cv_broadcast(&usbvcp->usbvc_mapio_cv); } else { cv_broadcast(&usbvcp->usbvc_read_cv); } /* Wait for the other threads to quit */ (void) usbvc_serialize_access(usbvcp, USBVC_SER_NOSIG); usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_cpr_suspend: return"); } /* * usbvc_cpr_resume: * * usbvc_restore_device_state marks success by putting device back online */ static void usbvc_cpr_resume(dev_info_t *dip) { int instance = ddi_get_instance(dip); usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, instance); USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "resume: enter"); /* * NOTE: A pm_raise_power in usbvc_restore_device_state will bring * the power-up state of device into synch with the system. */ mutex_enter(&usbvcp->usbvc_mutex); usbvc_restore_device_state(dip, usbvcp); mutex_exit(&usbvcp->usbvc_mutex); } /* * usbvc_restore_device_state: * Called during hotplug-reconnect and resume. * reenable power management * Verify the device is the same as before the disconnect/suspend. * Restore device state * Thaw any IO which was frozen. * Quiesce device. (Other routines will activate if thawed IO.) * Set device online. * Leave device disconnected if there are problems. */ static void usbvc_restore_device_state(dev_info_t *dip, usbvc_state_t *usbvcp) { USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_restore_device_state: enter"); ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); ASSERT((usbvcp->usbvc_dev_state == USB_DEV_DISCONNECTED) || (usbvcp->usbvc_dev_state == USB_DEV_SUSPENDED)); usbvc_pm_busy_component(usbvcp); usbvcp->usbvc_pm->usbvc_raise_power = B_TRUE; mutex_exit(&usbvcp->usbvc_mutex); (void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR); /* Check if we are talking to the same device */ if (usb_check_same_device(dip, usbvcp->usbvc_log_handle, USB_LOG_L0, PRINT_MASK_ALL, USB_CHK_BASIC|USB_CHK_CFG, NULL) != USB_SUCCESS) { goto fail; } mutex_enter(&usbvcp->usbvc_mutex); usbvcp->usbvc_pm->usbvc_raise_power = B_FALSE; usbvcp->usbvc_dev_state = USB_DEV_ONLINE; mutex_exit(&usbvcp->usbvc_mutex); if (usbvcp->usbvc_pm->usbvc_wakeup_enabled) { /* Failure here means device disappeared again. */ if (usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_ENABLE) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "device may or may not be accessible. " "Please verify reconnection"); } } mutex_enter(&usbvcp->usbvc_mutex); usbvc_pm_idle_component(usbvcp); USB_DPRINTF_L4(PRINT_MASK_PM, usbvcp->usbvc_log_handle, "usbvc_restore_device_state: end"); return; fail: /* change the device state from suspended to disconnected */ mutex_enter(&usbvcp->usbvc_mutex); usbvcp->usbvc_dev_state = USB_DEV_DISCONNECTED; usbvc_pm_idle_component(usbvcp); } /* Events */ /* * usbvc_disconnect_event_cb: * Called when device hotplug-removed. * Close pipes. (This does not attempt to contact device.) * Set state to DISCONNECTED */ static int usbvc_disconnect_event_cb(dev_info_t *dip) { int instance = ddi_get_instance(dip); usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, instance); USB_DPRINTF_L4(PRINT_MASK_HOTPLUG, usbvcp->usbvc_log_handle, "disconnect: enter"); mutex_enter(&usbvcp->usbvc_mutex); /* * Save any state of device or IO in progress required by * usbvc_restore_device_state for proper device "thawing" later. */ usbvcp->usbvc_dev_state = USB_DEV_DISCONNECTED; /* * wake up the read threads in case there are any threads are blocking, * after being waked up, those threads will quit fail immediately since * we have changed the dev_stat. */ if (usbvcp->usbvc_io_type == V4L2_MEMORY_MMAP) { cv_broadcast(&usbvcp->usbvc_mapio_cv); } else { cv_broadcast(&usbvcp->usbvc_read_cv); } /* Wait for the other threads to quit */ (void) usbvc_serialize_access(usbvcp, USBVC_SER_SIG); usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); return (USB_SUCCESS); } /* * usbvc_reconnect_event_cb: * Called with device hotplug-inserted * Restore state */ static int usbvc_reconnect_event_cb(dev_info_t *dip) { int instance = ddi_get_instance(dip); usbvc_state_t *usbvcp = ddi_get_soft_state(usbvc_statep, instance); USB_DPRINTF_L4(PRINT_MASK_HOTPLUG, usbvcp->usbvc_log_handle, "reconnect: enter"); mutex_enter(&usbvcp->usbvc_mutex); (void) usbvc_serialize_access(usbvcp, USBVC_SER_SIG); usbvc_restore_device_state(dip, usbvcp); usbvc_release_access(usbvcp); mutex_exit(&usbvcp->usbvc_mutex); return (USB_SUCCESS); } /* Sync objs and lists */ /* * init/fini sync objects during attach */ static void usbvc_init_sync_objs(usbvc_state_t *usbvcp) { mutex_init(&usbvcp->usbvc_mutex, NULL, MUTEX_DRIVER, usbvcp->usbvc_reg->dev_iblock_cookie); cv_init(&usbvcp->usbvc_serial_cv, NULL, CV_DRIVER, NULL); cv_init(&usbvcp->usbvc_read_cv, NULL, CV_DRIVER, NULL); cv_init(&usbvcp->usbvc_mapio_cv, NULL, CV_DRIVER, NULL); usbvcp->usbvc_serial_inuse = B_FALSE; usbvcp->usbvc_locks_initialized = B_TRUE; } static void usbvc_fini_sync_objs(usbvc_state_t *usbvcp) { cv_destroy(&usbvcp->usbvc_serial_cv); cv_destroy(&usbvcp->usbvc_read_cv); cv_destroy(&usbvcp->usbvc_mapio_cv); mutex_destroy(&usbvcp->usbvc_mutex); } static void usbvc_init_lists(usbvc_state_t *usbvcp) { /* video terminals */ list_create(&(usbvcp->usbvc_term_list), sizeof (usbvc_terms_t), offsetof(usbvc_terms_t, term_node)); /* video units */ list_create(&(usbvcp->usbvc_unit_list), sizeof (usbvc_units_t), offsetof(usbvc_units_t, unit_node)); /* stream interfaces */ list_create(&(usbvcp->usbvc_stream_list), sizeof (usbvc_stream_if_t), offsetof(usbvc_stream_if_t, stream_if_node)); } /* * Free all the data structures allocated when parsing descriptors of ctrl * and stream interfaces. It is safe to call this function because it always * checks the pointer before free mem. */ static void usbvc_fini_lists(usbvc_state_t *usbvcp) { USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_fini_lists: enter"); usbvc_free_ctrl_descr(usbvcp); /* Free all video stream structure and the sub-structures */ usbvc_free_stream_descr(usbvcp); USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_fini_lists: end"); } /* * Free all the data structures allocated when parsing descriptors of ctrl * interface. */ static void usbvc_free_ctrl_descr(usbvc_state_t *usbvcp) { usbvc_terms_t *term; usbvc_units_t *unit; USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_free_ctrl_descr: enter"); if (usbvcp->usbvc_vc_header) { kmem_free(usbvcp->usbvc_vc_header, sizeof (usbvc_vc_header_t)); } /* Free all video terminal structure */ while (!list_is_empty(&usbvcp->usbvc_term_list)) { term = list_head(&usbvcp->usbvc_term_list); if (term != NULL) { list_remove(&(usbvcp->usbvc_term_list), term); kmem_free(term, sizeof (usbvc_terms_t)); } } /* Free all video unit structure */ while (!list_is_empty(&usbvcp->usbvc_unit_list)) { unit = list_head(&usbvcp->usbvc_unit_list); if (unit != NULL) { list_remove(&(usbvcp->usbvc_unit_list), unit); kmem_free(unit, sizeof (usbvc_units_t)); } } } /* * Free all the data structures allocated when parsing descriptors of stream * interfaces. */ static void usbvc_free_stream_descr(usbvc_state_t *usbvcp) { usbvc_stream_if_t *strm; usbvc_input_header_t *in_hdr; usbvc_output_header_t *out_hdr; uint8_t fmt_cnt, frm_cnt; while (!list_is_empty(&usbvcp->usbvc_stream_list)) { USB_DPRINTF_L3(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_fini_lists: stream list not empty."); strm = list_head(&usbvcp->usbvc_stream_list); if (strm != NULL) { /* unlink this stream's data structure from the list */ list_remove(&(usbvcp->usbvc_stream_list), strm); } else { /* No real stream data structure in the list */ return; } in_hdr = strm->input_header; out_hdr = strm->output_header; if (in_hdr) { fmt_cnt = in_hdr->descr->bNumFormats; } else if (out_hdr) { fmt_cnt = out_hdr->descr->bNumFormats; } USB_DPRINTF_L3(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_fini_lists:" " fmtgrp cnt=%d", fmt_cnt); /* Free headers */ if (in_hdr) { kmem_free(in_hdr, sizeof (usbvc_input_header_t)); } if (out_hdr) { kmem_free(out_hdr, sizeof (usbvc_output_header_t)); } /* Free format descriptors */ if (strm->format_group) { int i; usbvc_format_group_t *fmtgrp; for (i = 0; i < fmt_cnt; i++) { fmtgrp = &strm->format_group[i]; if (fmtgrp->format == NULL) { break; } if (fmtgrp->still) { kmem_free(fmtgrp->still, sizeof (usbvc_still_image_frame_t)); } frm_cnt = fmtgrp->format->bNumFrameDescriptors; USB_DPRINTF_L3(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_fini_lists:" " frame cnt=%d", frm_cnt); if (fmtgrp->frames) { kmem_free(fmtgrp->frames, sizeof (usbvc_frames_t) * frm_cnt); } } kmem_free(strm->format_group, sizeof (usbvc_format_group_t) * fmt_cnt); } USB_DPRINTF_L3(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_fini_lists:" " free stream_if_t"); kmem_free(strm, sizeof (usbvc_stream_if_t)); } } /* * Parse class specific descriptors of the video device */ /* * Check the length of a class specific descriptor. Make sure cvs_buf_len is * not less than the length expected according to uvc spec. * * Args: * - off_num: the cvs_buf offset of the descriptor element that * indicates the number of variable descriptor elements; * - size: the size of each variable descriptor element, if zero, then the * size value is offered by off_size; * - off_size: the cvs_buf offset of the descriptor element that indicates * the size of each variable descriptor element; */ static int usbvc_chk_descr_len(uint8_t off_num, uint8_t size, uint8_t off_size, usb_cvs_data_t *cvs_data) { uchar_t *cvs_buf; uint_t cvs_buf_len; cvs_buf = cvs_data->cvs_buf; cvs_buf_len = cvs_data->cvs_buf_len; if (size == 0) { if (cvs_buf_len > off_size) { size = cvs_buf[off_size]; } else { return (USB_FAILURE); } } if (cvs_buf_len < (off_num + 1)) { return (USB_FAILURE); } if (cvs_buf_len < (cvs_buf[off_num] * size + off_num +1)) { return (USB_FAILURE); } return (USB_SUCCESS); } /* Parse the descriptors of control interface */ static int usbvc_parse_ctrl_if(usbvc_state_t *usbvcp) { int if_num; int cvs_num; usb_alt_if_data_t *if_alt_data; usb_cvs_data_t *cvs_data; uchar_t *cvs_buf; uint_t cvs_buf_len; uint16_t version; if_num = usbvcp->usbvc_reg->dev_curr_if; if_alt_data = usbvcp->usbvc_reg->dev_curr_cfg->cfg_if[if_num].if_alt; cvs_data = if_alt_data->altif_cvs; for (cvs_num = 0; cvs_num < if_alt_data->altif_n_cvs; cvs_num++) { cvs_buf = cvs_data[cvs_num].cvs_buf; cvs_buf_len = cvs_data[cvs_num].cvs_buf_len; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: cvs_num= %d, cvs_buf_len=%d", cvs_num, cvs_buf_len); /* * parse interface cvs descriptors here; by checking * bDescriptorType (cvs_buf[1]) */ if (cvs_buf[1] != CS_INTERFACE) { continue; } /* * Different descriptors in VC interface; according to * bDescriptorSubType (cvs_buf[2]) */ switch (cvs_buf[2]) { case VC_HEADER: /* * According to uvc spec, there must be one and only * be one header. If more than one, return failure. */ if (usbvcp->usbvc_vc_header) { return (USB_FAILURE); } /* * Check if it is a valid HEADER descriptor in case of * a device not compliant to uvc spec. This descriptor * is critical, return failure if not a valid one. */ if (usbvc_chk_descr_len(11, 1, 0, cvs_data) != USB_SUCCESS) { return (USB_FAILURE); } usbvcp->usbvc_vc_header = (usbvc_vc_header_t *)kmem_zalloc( sizeof (usbvc_vc_header_t), KM_SLEEP); usbvcp->usbvc_vc_header->descr = (usbvc_vc_header_descr_t *)&cvs_buf[0]; LE_TO_UINT16(usbvcp->usbvc_vc_header->descr->bcdUVC, 0, version); USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if:" " VC header, bcdUVC=%x", version); if (usbvcp->usbvc_vc_header->descr->bInCollection == 0) { USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: no strm interfaces"); break; } /* stream interface numbers */ usbvcp->usbvc_vc_header->baInterfaceNr = &cvs_buf[12]; break; case VC_INPUT_TERMINAL: { usbvc_terms_t *term; /* * Check if it is a valid descriptor in case of a * device not compliant to uvc spec */ if (cvs_buf_len < USBVC_I_TERM_LEN_MIN) { break; } term = (usbvc_terms_t *) kmem_zalloc(sizeof (usbvc_terms_t), KM_SLEEP); term->descr = (usbvc_term_descr_t *)cvs_buf; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: " "input term type=%x", term->descr->wTerminalType); if (term->descr->wTerminalType == ITT_CAMERA) { if (usbvc_chk_descr_len(14, 1, 0, cvs_data) != USB_SUCCESS) { kmem_free(term, sizeof (usbvc_terms_t)); break; } term->bmControls = &cvs_buf[15]; } else if (cvs_buf_len > 8) { /* other input terms */ term->bSpecific = &cvs_buf[8]; } list_insert_tail(&(usbvcp->usbvc_term_list), term); break; } case VC_OUTPUT_TERMINAL: { usbvc_terms_t *term; if (cvs_buf_len < USBVC_O_TERM_LEN_MIN) { break; } term = (usbvc_terms_t *) kmem_zalloc(sizeof (usbvc_terms_t), KM_SLEEP); term->descr = (usbvc_term_descr_t *)cvs_buf; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if:" " output term id= %x", term->descr->bTerminalID); if (cvs_buf_len > 9) { term->bSpecific = &cvs_buf[9]; } list_insert_tail(&(usbvcp->usbvc_term_list), term); break; } case VC_PROCESSING_UNIT: { uint8_t sz; usbvc_units_t *unit; if (usbvc_chk_descr_len(7, 1, 0, cvs_data) != USB_SUCCESS) { break; } /* bControlSize */ sz = cvs_buf[7]; if ((sz + 8) >= cvs_buf_len) { break; } unit = (usbvc_units_t *) kmem_zalloc(sizeof (usbvc_units_t), KM_SLEEP); unit->descr = (usbvc_unit_descr_t *)cvs_buf; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: " "unit type=%x", unit->descr->bDescriptorSubType); if (sz != 0) { unit->bmControls = &cvs_buf[8]; } unit->iProcessing = cvs_buf[8 + sz]; /* * video class 1.1 version add one element * (bmVideoStandards) to processing unit descriptor */ if (cvs_buf_len > (9 + sz)) { unit->bmVideoStandards = cvs_buf[9 + sz]; } list_insert_tail(&(usbvcp->usbvc_unit_list), unit); break; } case VC_SELECTOR_UNIT: { uint8_t pins; usbvc_units_t *unit; if (usbvc_chk_descr_len(4, 1, 0, cvs_data) != USB_SUCCESS) { break; } pins = cvs_buf[4]; if ((pins + 5) >= cvs_buf_len) { break; } unit = (usbvc_units_t *) kmem_zalloc(sizeof (usbvc_units_t), KM_SLEEP); unit->descr = (usbvc_unit_descr_t *)cvs_buf; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: " "unit type=%x", unit->descr->bDescriptorSubType); if (pins > 0) { unit->baSourceID = &cvs_buf[5]; } unit->iSelector = cvs_buf[5 + pins]; list_insert_tail(&(usbvcp->usbvc_unit_list), unit); break; } case VC_EXTENSION_UNIT: { uint8_t pins, n; usbvc_units_t *unit; if (usbvc_chk_descr_len(21, 1, 0, cvs_data) != USB_SUCCESS) { break; } pins = cvs_buf[21]; if ((pins + 22) >= cvs_buf_len) { break; } /* Size of bmControls */ n = cvs_buf[pins + 22]; if (usbvc_chk_descr_len(pins + 22, 1, 0, cvs_data) != USB_SUCCESS) { break; } if ((23 + pins + n) >= cvs_buf_len) { break; } unit = (usbvc_units_t *) kmem_zalloc(sizeof (usbvc_units_t), KM_SLEEP); unit->descr = (usbvc_unit_descr_t *)cvs_buf; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: " "unit type=%x", unit->descr->bDescriptorSubType); if (pins != 0) { unit->baSourceID = &cvs_buf[22]; } unit->bControlSize = cvs_buf[22 + pins]; if (unit->bControlSize != 0) { unit->bmControls = &cvs_buf[23 + pins]; } unit->iExtension = cvs_buf[23 + pins + n]; list_insert_tail(&(usbvcp->usbvc_unit_list), unit); break; } default: break; } } /* * For webcam which is not compliant to video class specification * and no header descriptor in VC interface, return USB_FAILURE. */ if (!usbvcp->usbvc_vc_header) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_ctrl_if: no header descriptor"); return (USB_FAILURE); } return (USB_SUCCESS); } /* Parse all the cvs descriptors in one stream interface. */ usbvc_stream_if_t * usbvc_parse_stream_if(usbvc_state_t *usbvcp, int if_num) { usb_alt_if_data_t *if_alt_data; uint_t i, j; usbvc_stream_if_t *strm_if; uint16_t pktsize; uint8_t ep_adr; strm_if = (usbvc_stream_if_t *)kmem_zalloc(sizeof (usbvc_stream_if_t), KM_SLEEP); strm_if->if_descr = &usbvcp->usbvc_reg->dev_curr_cfg->cfg_if[if_num]; if_alt_data = strm_if->if_descr->if_alt; if (usbvc_parse_stream_header(usbvcp, strm_if) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_stream_if: parse header fail"); kmem_free(strm_if, sizeof (usbvc_stream_if_t)); return (NULL); } if (usbvc_parse_format_groups(usbvcp, strm_if) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_stream_if: parse groups fail"); kmem_free(strm_if, sizeof (usbvc_stream_if_t)); return (NULL); } /* Parse the alternate settings to find the maximum bandwidth. */ for (i = 0; i < strm_if->if_descr->if_n_alt; i++) { if_alt_data = &strm_if->if_descr->if_alt[i]; for (j = 0; j < if_alt_data->altif_n_ep; j++) { ep_adr = if_alt_data->altif_ep[j].ep_descr.bEndpointAddress; if (strm_if->input_header != NULL && ep_adr != strm_if->input_header->descr->bEndpointAddress) { continue; } if (strm_if->output_header != NULL && ep_adr != strm_if->output_header->descr->bEndpointAddress) { continue; } pktsize = if_alt_data->altif_ep[j].ep_descr.wMaxPacketSize; pktsize = HS_PKT_SIZE(pktsize); if (pktsize > strm_if->max_isoc_payload) { strm_if->max_isoc_payload = pktsize; } } } /* initialize MJPEC FID toggle */ strm_if->fid = 0xff; /* * initialize desired number of buffers used internally in read() mode */ strm_if->buf_read_num = USBVC_DEFAULT_READ_BUF_NUM; USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_stream_if: return. max_isoc_payload=%x", strm_if->max_isoc_payload); return (strm_if); } /* * Parse all the stream interfaces asociated with the video control interface. * This driver will attach to a video control interface on the device, * there might be multiple video stream interfaces associated with one video * control interface. */ static int usbvc_parse_stream_ifs(usbvc_state_t *usbvcp) { int i, if_cnt, if_num; usbvc_stream_if_t *strm_if; if_cnt = usbvcp->usbvc_vc_header->descr->bInCollection; if (if_cnt == 0) { ASSERT(list_is_empty(&usbvcp->usbvc_stream_list)); USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_stream_ifs: no stream interfaces"); return (USB_SUCCESS); } for (i = 0; i < if_cnt; i++) { if_num = usbvcp->usbvc_vc_header->baInterfaceNr[i]; strm_if = usbvc_parse_stream_if(usbvcp, if_num); if (strm_if == NULL) { USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_stream_ifs:" " parse stream interface %d failed.", if_num); return (USB_FAILURE); } /* video data buffers */ list_create(&(strm_if->buf_map.uv_buf_free), sizeof (usbvc_buf_t), offsetof(usbvc_buf_t, buf_node)); list_create(&(strm_if->buf_map.uv_buf_done), sizeof (usbvc_buf_t), offsetof(usbvc_buf_t, buf_node)); list_create(&(strm_if->buf_read.uv_buf_free), sizeof (usbvc_buf_t), offsetof(usbvc_buf_t, buf_node)); list_create(&(strm_if->buf_read.uv_buf_done), sizeof (usbvc_buf_t), offsetof(usbvc_buf_t, buf_node)); list_insert_tail(&(usbvcp->usbvc_stream_list), strm_if); } /* Make the first stream interface as the default one. */ usbvcp->usbvc_curr_strm = (usbvc_stream_if_t *)list_head(&usbvcp->usbvc_stream_list); return (USB_SUCCESS); } /* * Parse colorspace descriptor and still image descriptor of a format group. * There is only one colorspace or still image descriptor in one format group. */ static void usbvc_parse_color_still(usbvc_state_t *usbvcp, usbvc_format_group_t *fmtgrp, usb_cvs_data_t *cvs_data, uint_t cvs_num, uint_t altif_n_cvs) { uint8_t frame_cnt; uint_t last_frame, i; uchar_t *cvs_buf; uint_t cvs_buf_len; frame_cnt = fmtgrp->format->bNumFrameDescriptors; last_frame = frame_cnt + cvs_num; /* * Find the still image descr and color format descr if there are any. * UVC Spec: only one still image and one color descr is allowed in * one format group. */ for (i = 1; i <= 2; i++) { if ((last_frame + i) >= altif_n_cvs) { break; } cvs_buf = cvs_data[last_frame + i].cvs_buf; cvs_buf_len = cvs_data[last_frame + i].cvs_buf_len; if (cvs_buf[2] == VS_STILL_IMAGE_FRAME) { uint8_t m, n, off; usbvc_still_image_frame_t *st; if (usbvc_chk_descr_len(4, 4, 0, cvs_data) != USB_SUCCESS) { continue; } /* Number of Image Size patterns of this format */ n = cvs_buf[4]; /* offset of bNumCompressionPattern */ off = 9 + 4 * n -4; if (off >= cvs_buf_len) { continue; } /* Number of compression pattern of this format */ m = cvs_buf[off]; if (usbvc_chk_descr_len(m, 1, 0, cvs_data) != USB_SUCCESS) { continue; } fmtgrp->still = (usbvc_still_image_frame_t *) kmem_zalloc(sizeof (usbvc_still_image_frame_t), KM_SLEEP); st = fmtgrp->still; st->descr = (usbvc_still_image_frame_descr_t *)cvs_buf; n = st->descr->bNumImageSizePatterns; if (n > 0) { st->width_height = (width_height_t *)&cvs_buf[5]; } st->bNumCompressionPattern = cvs_buf[off]; if (cvs_buf[off] > 0) { st->bCompression = &cvs_buf[off + 1]; } } if (cvs_buf[2] == VS_COLORFORMAT) { fmtgrp->color = (usbvc_color_matching_descr_t *)cvs_buf; fmtgrp->v4l2_color = usbvc_v4l2_colorspace( fmtgrp->color->bColorPrimaries); } } USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_color_still: still=%p, color=%p", (void *)fmtgrp->still, (void *)fmtgrp->color); } /* * Parse frame descriptors of a format group. There might be multi frame * descriptors in one format group. */ static void usbvc_parse_frames(usbvc_state_t *usbvcp, usbvc_format_group_t *fmtgrp, usb_cvs_data_t *cvs_data, uint_t cvs_num, uint_t altif_n_cvs) { uint_t last_frame; usbvc_frames_t *frm; usb_cvs_data_t *cvs; uchar_t *cvs_buf; uint_t cvs_buf_len; uint8_t i; uint8_t frame_cnt = fmtgrp->format->bNumFrameDescriptors; USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_format_group: frame_cnt=%d", frame_cnt); if (frame_cnt == 0) { fmtgrp->frames = NULL; return; } /* All these mem allocated will be freed in cleanup() */ fmtgrp->frames = (usbvc_frames_t *) kmem_zalloc(sizeof (usbvc_frames_t) * frame_cnt, KM_SLEEP); last_frame = frame_cnt + cvs_num; cvs_num++; i = 0; /* * Traverse from the format decr's first frame decr to the the last * frame descr. */ for (; cvs_num <= last_frame; cvs_num++) { USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_frames: cvs_num=%d, i=%d", cvs_num, i); if (cvs_num >= altif_n_cvs) { USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_frames: less frames than " "expected, cvs_num=%d, i=%d", cvs_num, i); break; } cvs = &cvs_data[cvs_num]; cvs_buf = cvs->cvs_buf; cvs_buf_len = cvs->cvs_buf_len; if (cvs_buf_len < USBVC_FRAME_LEN_MIN) { i++; continue; } frm = &fmtgrp->frames[i]; frm->descr = (usbvc_frame_descr_t *)cvs->cvs_buf; /* Descriptor for discrete frame interval */ if (frm->descr->bFrameIntervalType > 0) { if (usbvc_chk_descr_len(25, 4, 0, cvs) != USB_SUCCESS) { frm->descr = NULL; i++; continue; } frm->dwFrameInterval = (uint8_t *)&cvs_buf[26]; } else { /* Continuous interval */ if (cvs_buf_len < USBVC_FRAME_LEN_CON) { frm->descr = NULL; i++; continue; } /* Continuous frame intervals */ LE_TO_UINT32(cvs_buf, 26, frm->dwMinFrameInterval); LE_TO_UINT32(cvs_buf, 30, frm->dwMaxFrameInterval); LE_TO_UINT32(cvs_buf, 34, frm->dwFrameIntervalStep); } i++; } fmtgrp->frame_cnt = i; USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_frames: %d frames are actually parsed", fmtgrp->frame_cnt); } /* Parse one of the format groups in a stream interface */ static int usbvc_parse_format_group(usbvc_state_t *usbvcp, usbvc_format_group_t *fmtgrp, usb_cvs_data_t *cvs_data, uint_t cvs_num, uint_t altif_n_cvs) { usbvc_format_descr_t *fmt; fmt = fmtgrp->format; USB_DPRINTF_L4(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_format_group: frame_cnt=%d, cvs_num=%d", fmt->bNumFrameDescriptors, cvs_num); switch (fmt->bDescriptorSubType) { case VS_FORMAT_UNCOMPRESSED: usbvc_parse_color_still(usbvcp, fmtgrp, cvs_data, cvs_num, altif_n_cvs); usbvc_parse_frames(usbvcp, fmtgrp, cvs_data, cvs_num, altif_n_cvs); fmtgrp->v4l2_bpp = fmt->fmt.uncompressed.bBitsPerPixel / 8; fmtgrp->v4l2_pixelformat = usbvc_v4l2_guid2fcc( (uint8_t *)&fmt->fmt.uncompressed.guidFormat); break; case VS_FORMAT_MJPEG: usbvc_parse_color_still(usbvcp, fmtgrp, cvs_data, cvs_num, altif_n_cvs); usbvc_parse_frames(usbvcp, fmtgrp, cvs_data, cvs_num, altif_n_cvs); fmtgrp->v4l2_bpp = 0; fmtgrp->v4l2_pixelformat = V4L2_PIX_FMT_MJPEG; break; case VS_FORMAT_MPEG2TS: case VS_FORMAT_DV: case VS_FORMAT_FRAME_BASED: case VS_FORMAT_STREAM_BASED: USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_format_group: format not supported yet."); return (USB_FAILURE); default: USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_format_group: unknown format."); return (USB_FAILURE); } return (USB_SUCCESS); } /* Parse the descriptors belong to one format */ static int usbvc_parse_format_groups(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if) { usb_alt_if_data_t *if_alt_data; usb_cvs_data_t *cvs_data; uint8_t fmtgrp_num, fmtgrp_cnt; uchar_t *cvs_buf; uint_t cvs_num = 0; usbvc_format_group_t *fmtgrp; fmtgrp_cnt = 0; /* * bNumFormats indicates the number of formats in this stream * interface. On some devices, we see this number is larger than * the truth. */ if (strm_if->input_header) { fmtgrp_cnt = strm_if->input_header->descr->bNumFormats; } else if (strm_if->output_header) { fmtgrp_cnt = strm_if->output_header->descr->bNumFormats; } if (!fmtgrp_cnt) { return (USB_FAILURE); } USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_format_groups: fmtgrp_cnt=%d", fmtgrp_cnt); fmtgrp = (usbvc_format_group_t *) kmem_zalloc(sizeof (usbvc_format_group_t) * fmtgrp_cnt, KM_SLEEP); if_alt_data = strm_if->if_descr->if_alt; cvs_data = if_alt_data->altif_cvs; for (fmtgrp_num = 0; fmtgrp_num < fmtgrp_cnt && cvs_num < if_alt_data->altif_n_cvs; cvs_num++) { cvs_buf = cvs_data[cvs_num].cvs_buf; switch (cvs_buf[2]) { case VS_FORMAT_UNCOMPRESSED: case VS_FORMAT_MJPEG: case VS_FORMAT_MPEG2TS: case VS_FORMAT_DV: case VS_FORMAT_FRAME_BASED: case VS_FORMAT_STREAM_BASED: fmtgrp[fmtgrp_num].format = (usbvc_format_descr_t *)cvs_buf; /* * Now cvs_data[cvs_num].cvs_buf is format descriptor, * usbvc_parse_format_group will then parse the frame * descriptors following this format descriptor. */ (void) usbvc_parse_format_group(usbvcp, &fmtgrp[fmtgrp_num], cvs_data, cvs_num, if_alt_data->altif_n_cvs); fmtgrp_num++; break; default: break; } } /* Save the number of parsed format groups. */ strm_if->fmtgrp_cnt = fmtgrp_num; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_format_groups: acctually %d formats parsed", fmtgrp_num); /* * If can't find any formats, then free all allocated * usbvc_format_group_t, return failure. */ if (!(fmtgrp[0].format)) { kmem_free(fmtgrp, sizeof (usbvc_format_group_t) * fmtgrp_cnt); strm_if->format_group = NULL; USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_format_groups: can't find any formats"); return (USB_FAILURE); } strm_if->format_group = fmtgrp; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_format_groups: %d format groups parsed", fmtgrp_num); return (USB_SUCCESS); } /* * Parse the input/output header in one stream interface. * UVC Spec: there must be one and only one header in one stream interface. */ int usbvc_parse_stream_header(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if) { usb_alt_if_data_t *if_alt_data; usb_cvs_data_t *cvs_data; int cvs_num; uchar_t *cvs_buf; usbvc_input_header_t *in_hdr; usbvc_output_header_t *out_hdr; if_alt_data = strm_if->if_descr->if_alt; cvs_data = if_alt_data->altif_cvs; for (cvs_num = 0; cvs_num < if_alt_data->altif_n_cvs; cvs_num++) { cvs_buf = cvs_data[cvs_num].cvs_buf; USB_DPRINTF_L3(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_stream_header: cvs_num= %d", cvs_num); /* * parse interface cvs descriptors here; by checking * bDescriptorType (cvs_buf[1]) */ if (cvs_buf[1] != CS_INTERFACE) { continue; } if (cvs_buf[2] == VS_INPUT_HEADER) { if (usbvc_chk_descr_len(3, 0, 12, cvs_data) != USB_SUCCESS) { continue; } strm_if->input_header = (usbvc_input_header_t *) kmem_zalloc(sizeof (usbvc_input_header_t), KM_SLEEP); in_hdr = strm_if->input_header; in_hdr->descr = (usbvc_input_header_descr_t *)cvs_buf; if (in_hdr->descr->bNumFormats > 0) { in_hdr->bmaControls = &cvs_buf[13]; } return (USB_SUCCESS); } else if (cvs_buf[2] == VS_OUTPUT_HEADER) { if (usbvc_chk_descr_len(3, 0, 8, cvs_data) != USB_SUCCESS) { continue; } strm_if->output_header = (usbvc_output_header_t *) kmem_zalloc(sizeof (usbvc_output_header_t), KM_SLEEP); out_hdr = strm_if->output_header; out_hdr->descr = (usbvc_output_header_descr_t *)cvs_buf; if (out_hdr->descr->bNumFormats > 0) { out_hdr->bmaControls = &cvs_buf[13]; } return (USB_SUCCESS); } else { continue; } } /* Didn't find one header descriptor. */ USB_DPRINTF_L2(PRINT_MASK_ATTA, usbvcp->usbvc_log_handle, "usbvc_parse_stream_header: FAIL"); return (USB_FAILURE); } /* read I/O functions */ /* Allocate bufs for read I/O method */ static int usbvc_alloc_read_bufs(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if) { usbvc_buf_t *buf; uchar_t *data; int i; uint32_t len; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, len); if (!len) { return (USB_FAILURE); } for (i = 0; i < strm_if->buf_read_num; i++) { mutex_exit(&usbvcp->usbvc_mutex); buf = (usbvc_buf_t *)kmem_zalloc(sizeof (usbvc_buf_t), KM_SLEEP); data = (uchar_t *)kmem_zalloc(len, KM_SLEEP); mutex_enter(&usbvcp->usbvc_mutex); buf->data = data; buf->len = len; list_insert_tail(&(strm_if->buf_read.uv_buf_free), buf); } strm_if->buf_read.buf_cnt = strm_if->buf_read_num; USB_DPRINTF_L4(PRINT_MASK_READ, usbvcp->usbvc_log_handle, "read_bufs: %d bufs allocated", strm_if->buf_read.buf_cnt); return (USB_SUCCESS); } /* Read a done buf, copy data to bp. This function is for read I/O method */ static int usbvc_read_buf(usbvc_state_t *usbvcp, struct buf *bp) { usbvc_buf_t *buf; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); /* read a buf from full list and then put it to free list */ buf = list_head(&usbvcp->usbvc_curr_strm->buf_read.uv_buf_done); USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_read_buf: buf=%p, buf->filled=%d, bfu->len=%d," " bp->b_bcount=%ld, bp->b_resid=%lu", (void *)buf, buf->filled, buf->len, bp->b_bcount, bp->b_resid); list_remove(&usbvcp->usbvc_curr_strm->buf_read.uv_buf_done, buf); bcopy(buf->data, bp->b_un.b_addr, buf->filled); bp->b_private = NULL; bp->b_resid = bp->b_bcount - buf->filled; list_insert_tail(&usbvcp->usbvc_curr_strm->buf_read.uv_buf_free, buf); return (USB_SUCCESS); } /* Free one buf which is for read/write IO style */ static void usbvc_free_read_buf(usbvc_buf_t *buf) { if (buf != NULL) { if (buf->data) { kmem_free(buf->data, buf->len); } kmem_free(buf, sizeof (usbvc_buf_t)); } } /* Free all bufs which are for read/write IO style */ static void usbvc_free_read_bufs(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if) { usbvc_buf_t *buf; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); if (!strm_if) { return; } buf = strm_if->buf_read.buf_filling; usbvc_free_read_buf(buf); strm_if->buf_read.buf_filling = NULL; while (!list_is_empty(&strm_if->buf_read.uv_buf_free)) { buf = list_head(&strm_if->buf_read.uv_buf_free); if (buf != NULL) { list_remove(&(strm_if->buf_read.uv_buf_free), buf); usbvc_free_read_buf(buf); } } while (!list_is_empty(&strm_if->buf_read.uv_buf_done)) { buf = list_head(&strm_if->buf_read.uv_buf_done); if (buf != NULL) { list_remove(&(strm_if->buf_read.uv_buf_done), buf); usbvc_free_read_buf(buf); } } strm_if->buf_read.buf_cnt = 0; USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_free_read_bufs: return"); } /* * Allocate bufs for mapped I/O , return the number of allocated bufs * if success, return 0 if fail. */ int usbvc_alloc_map_bufs(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if, int buf_cnt, int buf_len) { int i = 0; usbvc_buf_t *bufs; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_alloc_map_bufs: bufcnt=%d, buflen=%d", buf_cnt, buf_len); if (buf_len <= 0 || buf_cnt <= 0) { USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_alloc_map_bufs: len<=0, cnt<=0"); return (0); } mutex_exit(&usbvcp->usbvc_mutex); bufs = (usbvc_buf_t *) kmem_zalloc(sizeof (usbvc_buf_t) * buf_cnt, KM_SLEEP); mutex_enter(&usbvcp->usbvc_mutex); strm_if->buf_map.buf_head = bufs; buf_len = ptob(btopr(buf_len)); mutex_exit(&usbvcp->usbvc_mutex); bufs[0].data = ddi_umem_alloc(buf_len * buf_cnt, DDI_UMEM_SLEEP, &bufs[0].umem_cookie); mutex_enter(&usbvcp->usbvc_mutex); for (i = 0; i < buf_cnt; i++) { bufs[i].len = buf_len; bufs[i].data = bufs[0].data + (buf_len * i); bufs[i].umem_cookie = bufs[0].umem_cookie; bufs[i].status = USBVC_BUF_INIT; bufs[i].v4l2_buf.index = i; bufs[i].v4l2_buf.m.offset = i * bufs[i].len; bufs[i].v4l2_buf.length = bufs[i].len; bufs[i].v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; bufs[i].v4l2_buf.sequence = 0; bufs[i].v4l2_buf.field = V4L2_FIELD_NONE; bufs[i].v4l2_buf.memory = V4L2_MEMORY_MMAP; bufs[i].v4l2_buf.flags = V4L2_MEMORY_MMAP; list_insert_tail(&strm_if->buf_map.uv_buf_free, &bufs[i]); USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_alloc_map_bufs: prepare %d buffers of %d bytes", buf_cnt, bufs[i].len); } strm_if->buf_map.buf_cnt = buf_cnt; strm_if->buf_map.buf_filling = NULL; return (buf_cnt); } /* Free all bufs which are for memory map IO style */ void usbvc_free_map_bufs(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if) { usbvc_buf_t *buf; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); if (!strm_if) { return; } strm_if->buf_map.buf_filling = NULL; while (!list_is_empty(&strm_if->buf_map.uv_buf_free)) { buf = (usbvc_buf_t *)list_head(&strm_if->buf_map.uv_buf_free); list_remove(&(strm_if->buf_map.uv_buf_free), buf); } while (!list_is_empty(&strm_if->buf_map.uv_buf_done)) { buf = (usbvc_buf_t *)list_head(&strm_if->buf_map.uv_buf_done); list_remove(&(strm_if->buf_map.uv_buf_done), buf); } buf = strm_if->buf_map.buf_head; if (!buf) { USB_DPRINTF_L2(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_free_map_bufs: no data buf need be freed, return"); return; } if (buf->umem_cookie) { ddi_umem_free(buf->umem_cookie); } kmem_free(buf, sizeof (usbvc_buf_t) * strm_if->buf_map.buf_cnt); strm_if->buf_map.buf_cnt = 0; strm_if->buf_map.buf_head = NULL; USB_DPRINTF_L4(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_free_map_bufs: return"); } /* * Open the isoc pipe, this pipe is for video data transfer */ int usbvc_open_isoc_pipe(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if) { usb_pipe_policy_t policy; int rval = USB_SUCCESS; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); if ((rval = usbvc_set_alt(usbvcp, strm_if)) != USB_SUCCESS) { return (rval); } bzero(&policy, sizeof (usb_pipe_policy_t)); policy.pp_max_async_reqs = 2; mutex_exit(&usbvcp->usbvc_mutex); if ((rval = usb_pipe_open(usbvcp->usbvc_dip, strm_if->curr_ep, &policy, USB_FLAGS_SLEEP, &strm_if->datain_ph)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_open_isoc_pipe: open pipe fail"); mutex_enter(&usbvcp->usbvc_mutex); return (rval); } mutex_enter(&usbvcp->usbvc_mutex); strm_if->start_polling = 0; USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_open_isoc_pipe: success, datain_ph=%p", (void *)strm_if->datain_ph); return (rval); } /* * Open the isoc pipe */ static void usbvc_close_isoc_pipe(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if) { ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); if (!strm_if) { USB_DPRINTF_L2(PRINT_MASK_CLOSE, usbvcp->usbvc_log_handle, "usbvc_close_isoc_pipe: stream interface is NULL"); return; } if (strm_if->datain_ph) { mutex_exit(&usbvcp->usbvc_mutex); usb_pipe_close(usbvcp->usbvc_dip, strm_if->datain_ph, USB_FLAGS_SLEEP, NULL, NULL); mutex_enter(&usbvcp->usbvc_mutex); } strm_if->datain_ph = NULL; } /* * Start to get video data from isoc pipe in the stream interface, * issue isoc req. */ int usbvc_start_isoc_polling(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if, uchar_t io_type) { int rval = USB_SUCCESS; uint_t if_num; usb_isoc_req_t *req; ushort_t pkt_size; ushort_t n_pkt, pkt; uint32_t frame_size; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); pkt_size = HS_PKT_SIZE(strm_if->curr_ep->wMaxPacketSize); if_num = strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber; LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, frame_size); n_pkt = (frame_size + (pkt_size) - 1) / (pkt_size); USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle, "usbvc_start_isoc_polling: if_num=%d, alt=%d, n_pkt=%d," " pkt_size=0x%x, MaxPacketSize=0x%x(Tsac#=%d), frame_size=0x%x", if_num, strm_if->curr_alt, n_pkt, pkt_size, strm_if->curr_ep->wMaxPacketSize, (1 + ((strm_if->curr_ep->wMaxPacketSize>> 11) & 3)), frame_size); if (n_pkt > USBVC_MAX_PKTS) { n_pkt = USBVC_MAX_PKTS; } USB_DPRINTF_L3(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle, "usbvc_start_isoc_polling: n_pkt=%d", n_pkt); mutex_exit(&usbvcp->usbvc_mutex); if ((req = usb_alloc_isoc_req(usbvcp->usbvc_dip, n_pkt, n_pkt * pkt_size, USB_FLAGS_SLEEP)) != NULL) { mutex_enter(&usbvcp->usbvc_mutex); /* Initialize the packet descriptor */ for (pkt = 0; pkt < n_pkt; pkt++) { req->isoc_pkt_descr[pkt].isoc_pkt_length = pkt_size; } req->isoc_pkts_count = n_pkt; /* * zero here indicates that HCDs will use * isoc_pkt_descr->isoc_pkt_length to calculate * isoc_pkts_length. */ req->isoc_pkts_length = 0; req->isoc_attributes = USB_ATTRS_ISOC_XFER_ASAP | USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING; req->isoc_cb = usbvc_isoc_cb; req->isoc_exc_cb = usbvc_isoc_exc_cb; usbvcp->usbvc_io_type = io_type; req->isoc_client_private = (usb_opaque_t)usbvcp; mutex_exit(&usbvcp->usbvc_mutex); rval = usb_pipe_isoc_xfer(strm_if->datain_ph, req, 0); mutex_enter(&usbvcp->usbvc_mutex); } else { mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L2(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle, "usbvc_start_isoc_polling: alloc_isoc_req fail"); return (USB_FAILURE); } if (rval != USB_SUCCESS) { if (req) { usb_free_isoc_req(req); req = NULL; } } USB_DPRINTF_L4(PRINT_MASK_IOCTL, usbvcp->usbvc_log_handle, "usbvc_start_isoc_polling: return, rval=%d", rval); return (rval); } /* callbacks for receiving video data (isco in transfer) */ /*ARGSUSED*/ /* Isoc transfer callback, get video data */ static void usbvc_isoc_cb(usb_pipe_handle_t ph, usb_isoc_req_t *isoc_req) { usbvc_state_t *usbvcp = (usbvc_state_t *)isoc_req->isoc_client_private; int i; mblk_t *data = isoc_req->isoc_data; usbvc_buf_grp_t *bufgrp; mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_isoc_cb: rq=0x%p, fno=%" PRId64 ", n_pkts=%u, flag=0x%x," " data=0x%p, cnt=%d", (void *)isoc_req, isoc_req->isoc_frame_no, isoc_req->isoc_pkts_count, isoc_req->isoc_attributes, (void *)isoc_req->isoc_data, isoc_req->isoc_error_count); ASSERT((isoc_req->isoc_cb_flags & USB_CB_INTR_CONTEXT) != 0); for (i = 0; i < isoc_req->isoc_pkts_count; i++) { USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "\tpkt%d: " "pktsize=%d status=%d resid=%d", i, isoc_req->isoc_pkt_descr[i].isoc_pkt_length, isoc_req->isoc_pkt_descr[i].isoc_pkt_status, isoc_req->isoc_pkt_descr[i].isoc_pkt_actual_length); if (isoc_req->isoc_pkt_descr[i].isoc_pkt_status != USB_CR_OK) { USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "record: pkt=%d status=%s", i, usb_str_cr( isoc_req->isoc_pkt_descr[i].isoc_pkt_status)); } if (usbvcp->usbvc_io_type == V4L2_MEMORY_MMAP) { bufgrp = &usbvcp->usbvc_curr_strm->buf_map; } else { bufgrp = &usbvcp->usbvc_curr_strm->buf_read; } if (isoc_req->isoc_pkt_descr[i].isoc_pkt_actual_length) { if (usbvc_decode_stream_header(usbvcp, bufgrp, data, isoc_req->isoc_pkt_descr[i].isoc_pkt_actual_length) != USB_SUCCESS) { USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "decode error"); } if (bufgrp->buf_filling && (bufgrp->buf_filling->status == USBVC_BUF_ERR || bufgrp->buf_filling->status == USBVC_BUF_DONE)) { /* Move the buf to the full list */ list_insert_tail(&bufgrp->uv_buf_done, bufgrp->buf_filling); bufgrp->buf_filling = NULL; if (usbvcp->usbvc_io_type == V4L2_MEMORY_MMAP) { cv_broadcast(&usbvcp->usbvc_mapio_cv); } else { cv_broadcast(&usbvcp->usbvc_read_cv); } } } data->b_rptr += isoc_req->isoc_pkt_descr[i].isoc_pkt_length; } mutex_exit(&usbvcp->usbvc_mutex); usb_free_isoc_req(isoc_req); } /*ARGSUSED*/ static void usbvc_isoc_exc_cb(usb_pipe_handle_t ph, usb_isoc_req_t *isoc_req) { usbvc_state_t *usbvcp = (usbvc_state_t *)isoc_req->isoc_client_private; usb_cr_t completion_reason; int rval; usbvc_stream_if_t *strm_if; ASSERT(!list_is_empty(&usbvcp->usbvc_stream_list)); mutex_enter(&usbvcp->usbvc_mutex); /* get the first stream interface */ strm_if = usbvcp->usbvc_curr_strm; completion_reason = isoc_req->isoc_completion_reason; USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_isoc_exc_cb: ph=0x%p, isoc_req=0x%p, cr=%d", (void *)ph, (void *)isoc_req, completion_reason); ASSERT((isoc_req->isoc_cb_flags & USB_CB_INTR_CONTEXT) == 0); switch (completion_reason) { case USB_CR_STOPPED_POLLING: case USB_CR_PIPE_CLOSING: case USB_CR_PIPE_RESET: break; case USB_CR_NO_RESOURCES: /* * keep the show going: Since we have the original * request, we just resubmit it */ rval = usb_pipe_isoc_xfer(strm_if->datain_ph, isoc_req, USB_FLAGS_NOSLEEP); USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_isoc_exc_cb: restart capture rval=%d", rval); mutex_exit(&usbvcp->usbvc_mutex); return; default: mutex_exit(&usbvcp->usbvc_mutex); usb_pipe_stop_isoc_polling(ph, USB_FLAGS_NOSLEEP); USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_isoc_exc_cb: stop polling"); mutex_enter(&usbvcp->usbvc_mutex); } usb_free_isoc_req(isoc_req); strm_if->start_polling = 0; USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_isoc_exc_cb: start_polling=%d cr=%d", strm_if->start_polling, completion_reason); mutex_exit(&usbvcp->usbvc_mutex); } /* * Other utility functions */ /* * Find a proper alternate according to the bandwidth that the current video * format need; * Set alternate by calling usb_set_alt_if; * Called before open pipes in stream interface. */ static int usbvc_set_alt(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if) { usb_alt_if_data_t *alt; uint_t i, j, if_num; uint16_t pktsize, curr_pktsize; uint32_t bandwidth; int rval = USB_SUCCESS; usbvc_input_header_t *ihd; usbvc_output_header_t *ohd; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); LE_TO_UINT32(strm_if->ctrl_pc.dwMaxPayloadTransferSize, 0, bandwidth); if (!bandwidth) { USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_set_alt: bandwidth is not set yet"); return (USB_FAILURE); } USB_DPRINTF_L3(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_set_alt: bandwidth=%x", bandwidth); strm_if->curr_ep = NULL; curr_pktsize = 0xffff; ohd = strm_if->output_header; ihd = strm_if->input_header; /* * Find one alternate setting whose isoc ep's max pktsize is just * enough for the bandwidth. */ for (i = 0; i < strm_if->if_descr->if_n_alt; i++) { alt = &strm_if->if_descr->if_alt[i]; for (j = 0; j < alt->altif_n_ep; j++) { /* if this stream interface is for input */ if (ihd != NULL && alt->altif_ep[j].ep_descr.bEndpointAddress != ihd->descr->bEndpointAddress) { continue; } /* if this stream interface is for output */ if (ohd != NULL && alt->altif_ep[j].ep_descr.bEndpointAddress != ohd->descr->bEndpointAddress) { continue; } pktsize = alt->altif_ep[j].ep_descr.wMaxPacketSize; pktsize = HS_PKT_SIZE(pktsize); if (pktsize >= bandwidth && pktsize < curr_pktsize) { curr_pktsize = pktsize; strm_if->curr_alt = i; strm_if->curr_ep = &alt->altif_ep[j].ep_descr; } } } if (!strm_if->curr_ep) { USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_set_alt: can't find a proper ep to satisfy" " the given bandwidth"); return (USB_FAILURE); } USB_DPRINTF_L3(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_set_alt: strm_if->curr_alt=%d", strm_if->curr_alt); if_num = strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber; mutex_exit(&usbvcp->usbvc_mutex); if ((rval = usb_set_alt_if(usbvcp->usbvc_dip, if_num, strm_if->curr_alt, USB_FLAGS_SLEEP, NULL, NULL)) != USB_SUCCESS) { mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L2(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_set_alt: usb_set_alt_if fail, if.alt=%d.%d, rval=%d", if_num, strm_if->curr_alt, rval); return (rval); } mutex_enter(&usbvcp->usbvc_mutex); USB_DPRINTF_L4(PRINT_MASK_OPEN, usbvcp->usbvc_log_handle, "usbvc_set_alt: return, if_num=%d, alt=%d", if_num, strm_if->curr_alt); return (rval); } /* * Decode stream header for mjpeg and uncompressed format video data. * mjpeg and uncompressed format have the same stream header. See their * payload spec, 2.2 and 2.4 */ static int usbvc_decode_stream_header(usbvc_state_t *usbvcp, usbvc_buf_grp_t *bufgrp, mblk_t *data, int actual_len) { uint32_t len, buf_left, data_len; usbvc_stream_if_t *strm_if; uchar_t head_flag, head_len; usbvc_buf_t *buf_filling; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); USB_DPRINTF_L4(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: enter. actual_len=%x", actual_len); /* header length check. */ if (actual_len < 2) { USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: header is not completed"); return (USB_FAILURE); } head_len = data->b_rptr[0]; head_flag = data->b_rptr[1]; USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: headlen=%x", head_len); /* header length check. */ if (actual_len < head_len) { USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: actual_len < head_len"); return (USB_FAILURE); } /* * If there is no stream data in this packet and this packet is not * used to indicate the end of a frame, then just skip it. */ if ((actual_len == head_len) && !(head_flag & USBVC_STREAM_EOF)) { USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: only header, no data"); return (USB_FAILURE); } /* Get the first stream interface */ strm_if = usbvcp->usbvc_curr_strm; LE_TO_UINT32(strm_if->ctrl_pc.dwMaxVideoFrameSize, 0, len); USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: dwMaxVideoFrameSize=%x, head_flag=%x", len, head_flag); /* * if no buf is filling, pick one buf from free list and alloc data * mem for the buf. */ if (!bufgrp->buf_filling) { if (list_is_empty(&bufgrp->uv_buf_free)) { strm_if->fid = head_flag & USBVC_STREAM_FID; USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: free list are empty"); return (USB_FAILURE); } else { bufgrp->buf_filling = (usbvc_buf_t *)list_head(&bufgrp->uv_buf_free); /* unlink from buf free list */ list_remove(&bufgrp->uv_buf_free, bufgrp->buf_filling); } bufgrp->buf_filling->filled = 0; USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: status=%d", bufgrp->buf_filling->status); bufgrp->buf_filling->status = USBVC_BUF_EMPTY; } buf_filling = bufgrp->buf_filling; ASSERT(buf_filling->len >= buf_filling->filled); buf_left = buf_filling->len - buf_filling->filled; /* if no buf room left, then return with a err status */ if (buf_left == 0) { buf_filling->status = USBVC_BUF_ERR; return (USB_FAILURE); } /* get this sample's data length except header */ data_len = actual_len - head_len; USB_DPRINTF_L3(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: fid=%x, len=%x, filled=%x", strm_if->fid, buf_filling->len, buf_filling->filled); /* if the first sample for a frame */ if (buf_filling->filled == 0) { /* * Only if it is the frist packet of a frame, * we will begin filling a frame. */ if (strm_if->fid != 0xff && strm_if->fid == (head_flag & USBVC_STREAM_FID)) { USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: 1st sample of a frame," " fid is incorrect."); return (USB_FAILURE); } strm_if->fid = head_flag & USBVC_STREAM_FID; /* If in the middle of a frame, fid should be consistent. */ } else if (strm_if->fid != (head_flag & USBVC_STREAM_FID)) { USB_DPRINTF_L2(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: fid is incorrect."); strm_if->fid = head_flag & USBVC_STREAM_FID; buf_filling->status = USBVC_BUF_ERR; return (USB_FAILURE); } if (data_len) { bcopy((void *)(data->b_rptr + head_len), (void *)(buf_filling->data + buf_filling->filled), min(data_len, buf_left)); buf_filling->filled += min(data_len, buf_left); } /* If the last packet for this frame */ if (head_flag & USBVC_STREAM_EOF) { buf_filling->status = USBVC_BUF_DONE; } if (data_len > buf_left) { buf_filling->status = USBVC_BUF_ERR; } USB_DPRINTF_L4(PRINT_MASK_CB, usbvcp->usbvc_log_handle, "usbvc_decode_stream_header: buf_status=%d", buf_filling->status); return (USB_SUCCESS); } /* * usbvc_serialize_access: * Get the serial synchronization object before returning. * * Arguments: * usbvcp - Pointer to usbvc state structure * waitsig - Set to: * USBVC_SER_SIG - to wait such that a signal can interrupt * USBVC_SER_NOSIG - to wait such that a signal cannot interrupt */ static int usbvc_serialize_access(usbvc_state_t *usbvcp, boolean_t waitsig) { int rval = 1; ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); while (usbvcp->usbvc_serial_inuse) { if (waitsig == USBVC_SER_SIG) { rval = cv_wait_sig(&usbvcp->usbvc_serial_cv, &usbvcp->usbvc_mutex); } else { cv_wait(&usbvcp->usbvc_serial_cv, &usbvcp->usbvc_mutex); } } usbvcp->usbvc_serial_inuse = B_TRUE; return (rval); } /* * usbvc_release_access: * Release the serial synchronization object. */ static void usbvc_release_access(usbvc_state_t *usbvcp) { ASSERT(mutex_owned(&usbvcp->usbvc_mutex)); usbvcp->usbvc_serial_inuse = B_FALSE; cv_broadcast(&usbvcp->usbvc_serial_cv); } /* Send req to video control interface to get ctrl */ int usbvc_vc_get_ctrl(usbvc_state_t *usbvcp, uint8_t req_code, uint8_t entity_id, uint16_t cs, uint16_t wlength, mblk_t *data) { usb_cb_flags_t cb_flags; usb_cr_t cr; usb_ctrl_setup_t setup; setup.bmRequestType = USBVC_GET_IF; /* bmRequestType */ setup.bRequest = req_code; /* bRequest */ setup.wValue = cs<<8; setup.wIndex = entity_id<<8; setup.wLength = wlength; setup.attrs = 0; if (usb_pipe_ctrl_xfer_wait(usbvcp->usbvc_default_ph, &setup, &data, &cr, &cb_flags, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_vc_get_ctrl: cmd failed, cr=%d, cb_flags=%x", cr, cb_flags); return (USB_FAILURE); } return (USB_SUCCESS); } /* Send req to video control interface to get ctrl */ int usbvc_vc_set_ctrl(usbvc_state_t *usbvcp, uint8_t req_code, uint8_t entity_id, uint16_t cs, uint16_t wlength, mblk_t *data) { usb_cb_flags_t cb_flags; usb_cr_t cr; usb_ctrl_setup_t setup; setup.bmRequestType = USBVC_SET_IF; /* bmRequestType */ setup.bRequest = req_code; /* bRequest */ setup.wValue = cs<<8; setup.wIndex = entity_id<<8; setup.wLength = wlength; setup.attrs = 0; if (usb_pipe_ctrl_xfer_wait(usbvcp->usbvc_default_ph, &setup, &data, &cr, &cb_flags, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_vc_set_ctrl: cmd failed, cr=%d, cb_flags=%x", cr, cb_flags); return (USB_FAILURE); } return (USB_SUCCESS); } /* Set probe or commit ctrl for video stream interface */ int usbvc_vs_set_probe_commit(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if, usbvc_vs_probe_commit_t *ctrl_pc, uchar_t cs) { mblk_t *data; usb_cb_flags_t cb_flags; usb_cr_t cr; usb_ctrl_setup_t setup; int rval; setup.bmRequestType = USBVC_SET_IF; /* bmRequestType */ setup.bRequest = SET_CUR; /* bRequest */ /* wValue, VS_PROBE_CONTROL or VS_COMMIT_CONTROL */ setup.wValue = cs; /* UVC Spec: this value must be put to the high byte */ setup.wValue = setup.wValue << 8; setup.wIndex = strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber; setup.wLength = usbvcp->usbvc_vc_header->descr->bcdUVC[0] ? 34 : 26; setup.attrs = 0; USB_DPRINTF_L3(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_vs_set_probe_commit: wLength=%d", setup.wLength); /* Data block */ if ((data = allocb(setup.wLength, BPRI_HI)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_vs_set_probe_commit: allocb failed"); return (USB_FAILURE); } bcopy(ctrl_pc, data->b_rptr, setup.wLength); data->b_wptr += setup.wLength; if ((rval = usb_pipe_ctrl_xfer_wait(usbvcp->usbvc_default_ph, &setup, &data, &cr, &cb_flags, 0)) != USB_SUCCESS) { if (data) { freemsg(data); } USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_vs_set_probe_commit: fail, rval=%d, cr=%d, " "cb_flags=%x", rval, cr, cb_flags); return (rval); } if (data) { freemsg(data); } return (USB_SUCCESS); } /* Get probe ctrl for vodeo stream interface */ int usbvc_vs_get_probe(usbvc_state_t *usbvcp, usbvc_stream_if_t *strm_if, usbvc_vs_probe_commit_t *ctrl_pc, uchar_t bRequest) { mblk_t *data = NULL; usb_cb_flags_t cb_flags; usb_cr_t cr; usb_ctrl_setup_t setup; setup.bmRequestType = USBVC_GET_IF; /* bmRequestType */ setup.bRequest = bRequest; /* bRequest */ setup.wValue = VS_PROBE_CONTROL; /* wValue, PROBE or COMMIT */ setup.wValue = setup.wValue << 8; setup.wIndex = (uint16_t)strm_if->if_descr->if_alt->altif_descr.bInterfaceNumber; setup.wLength = usbvcp->usbvc_vc_header->descr->bcdUVC[0] ? 34 : 26; setup.attrs = 0; if (usb_pipe_ctrl_xfer_wait(usbvcp->usbvc_default_ph, &setup, &data, &cr, &cb_flags, 0) != USB_SUCCESS) { if (data) { freemsg(data); } USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_vs_get_probe: cmd failed, cr=%d, cb_flags=%x", cr, cb_flags); return (USB_FAILURE); } bcopy(data->b_rptr, ctrl_pc, setup.wLength); if (data) { freemsg(data); } return (USB_SUCCESS); } /* Set a default format when open the device */ static int usbvc_set_default_stream_fmt(usbvc_state_t *usbvcp) { usbvc_vs_probe_commit_t ctrl, ctrl_get; usbvc_stream_if_t *strm_if; usbvc_format_group_t *curr_fmtgrp; uint32_t bandwidth; uint8_t index, i; USB_DPRINTF_L4(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_set_default_stream_fmt: enter"); mutex_enter(&usbvcp->usbvc_mutex); if (list_is_empty(&usbvcp->usbvc_stream_list)) { USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_set_default_stream_fmt: no stream interface, fail"); mutex_exit(&usbvcp->usbvc_mutex); return (USB_FAILURE); } bzero((void *)&ctrl, sizeof (usbvc_vs_probe_commit_t)); /* Get the current stream interface */ strm_if = usbvcp->usbvc_curr_strm; /* Fill the probe commit req data */ ctrl.bmHint[0] = 0; for (i = 0; i < strm_if->fmtgrp_cnt; i++) { curr_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 (!curr_fmtgrp || !curr_fmtgrp->v4l2_pixelformat || curr_fmtgrp->frame_cnt == 0) { USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_set_default_stream_fmt: no frame, fail"); continue; } else { break; } } if (!curr_fmtgrp || curr_fmtgrp->frame_cnt == 0) { USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_set_default_stream_fmt: can't find a fmtgrp" "which has a frame, fail"); mutex_exit(&usbvcp->usbvc_mutex); return (USB_FAILURE); } ctrl.bFormatIndex = curr_fmtgrp->format->bFormatIndex; /* use the first frame descr as default */ ctrl.bFrameIndex = curr_fmtgrp->frames[0].descr->bFrameIndex; /* use bcopy to keep the byte sequence as 32 bit little endian */ bcopy(&(curr_fmtgrp->frames[0].descr->dwDefaultFrameInterval[0]), &(ctrl.dwFrameInterval[0]), 4); mutex_exit(&usbvcp->usbvc_mutex); if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl, VS_PROBE_CONTROL) != USB_SUCCESS) { return (USB_FAILURE); } if (usbvc_vs_get_probe(usbvcp, strm_if, &ctrl_get, GET_CUR) != USB_SUCCESS) { return (USB_FAILURE); } mutex_enter(&usbvcp->usbvc_mutex); LE_TO_UINT32(strm_if->ctrl_pc.dwMaxPayloadTransferSize, 0, bandwidth); USB_DPRINTF_L3(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_set_default_stream_fmt: get bandwidth=%x", bandwidth); mutex_exit(&usbvcp->usbvc_mutex); if (usbvc_vs_set_probe_commit(usbvcp, strm_if, &ctrl_get, VS_COMMIT_CONTROL) != USB_SUCCESS) { return (USB_FAILURE); } mutex_enter(&usbvcp->usbvc_mutex); /* it's good to check index here before use it */ index = ctrl_get.bFormatIndex - curr_fmtgrp->format->bFormatIndex; if (index < strm_if->fmtgrp_cnt) { strm_if->cur_format_group = &strm_if->format_group[index]; } else { USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_set_default_stream_fmt: format index out of range"); mutex_exit(&usbvcp->usbvc_mutex); return (USB_FAILURE); } index = ctrl_get.bFrameIndex - strm_if->cur_format_group->frames[0].descr->bFrameIndex; if (index < strm_if->cur_format_group->frame_cnt) { strm_if->cur_format_group->cur_frame = &strm_if->cur_format_group->frames[index]; } else { USB_DPRINTF_L2(PRINT_MASK_DEVCTRL, usbvcp->usbvc_log_handle, "usbvc_set_default_stream: frame index out of range"); mutex_exit(&usbvcp->usbvc_mutex); return (USB_FAILURE); } /* * by now, the video format is set successfully. record the current * setting to strm_if->ctrl_pc */ bcopy(&ctrl_get, &strm_if->ctrl_pc, sizeof (usbvc_vs_probe_commit_t)); mutex_exit(&usbvcp->usbvc_mutex); return (USB_SUCCESS); }