/*
* 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.
*/
/*
* USBA: Solaris USB Architecture support
*
* functions that deal with allocation/free/data_xfers
* for the control/bulk/interrupt/isoch pipes:
* usb_alloc_ctrl_req()
* usb_free_ctrl_req()
* usb_pipe_ctrl_xfer()
* usb_pipe_sync_ctrl_xfer()
* usb_pipe_ctrl_xfer_wait()
*
* usb_alloc_bulk_req()
* usb_free_bulk_req()
* usb_pipe_bulk_xfer()
* usb_pipe_bulk_transfer_size()
*
* usb_alloc_intr_req()
* usb_free_intr_req()
* usb_pipe_intr_xfer()
* usb_pipe_stop_intr_polling()
*
* usb_alloc_isoc_req()
* usb_free_isoc_req()
* usb_get_current_frame_number()
* usb_get_max_isoc_pkts()
* usb_pipe_isoc_xfer()
* usb_pipe_stop_isoc_polling()
*
* XXX to do:
* update return values where needed
* keep track of requests not freed
*
*/
#define USBA_FRAMEWORK
#include <sys/usb/usba/usba_impl.h>
#include <sys/usb/usba/hcdi_impl.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
/* prototypes */
static int usba_flags_attr_check(usba_pipe_handle_data_t *,
usb_req_attrs_t attrs, usb_flags_t);
static int _usba_check_req(usba_pipe_handle_data_t *, usb_opaque_t,
usb_flags_t, uchar_t);
/*
* usba_check_req:
* check pipe, request structure for validity
*
* Arguments:
* ph - pipe handle pointer
* req - opaque request pointer
* flags - usb flags
*
* Returns:
* USB_SUCCESS - valid request
* USB_INVALID_REQUEST - request contains some invalid values
* USB_PIPE_ERROR - pipe is in error state
* USB_INVALID_CONTEXT - sleep in interrupt context
* USB_INVALID_PIPE - zero pipe or wrong pipe
*/
static int
usba_check_req(usba_pipe_handle_data_t *ph_data, usb_opaque_t req,
usb_flags_t flags, uchar_t pipe_type)
{
int rval = _usba_check_req(ph_data, req, flags, pipe_type);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_check_req: ph_data=0x%p req=0x%p flags=0%x rval=%d",
(void *)ph_data, (void *)req, flags, rval);
}
return (rval);
}
static int
_usba_check_req(usba_pipe_handle_data_t *ph_data, usb_opaque_t req,
usb_flags_t flags, uchar_t pipe_type)
{
usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)req;
usb_bulk_req_t *bulk_req = (usb_bulk_req_t *)req;
usb_intr_req_t *intr_req = (usb_intr_req_t *)req;
usb_isoc_req_t *isoc_req = (usb_isoc_req_t *)req;
usba_req_wrapper_t *wrp = USBA_REQ2WRP(req);
mblk_t *data;
usb_cr_t *cr;
usb_req_attrs_t attrs;
usb_opaque_t cb, exc_cb;
uint_t timeout = 0;
uchar_t direction = ph_data->p_ep.bEndpointAddress &
USB_EP_DIR_MASK;
uchar_t ep_attrs = ph_data->p_ep.bmAttributes &
USB_EP_ATTR_MASK;
int n;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_check_req: ph_data=0x%p req=0x%p flags=0x%x",
(void *)ph_data, (void *)req, flags);
if (req == NULL) {
return (USB_INVALID_ARGS);
}
/* set completion reason first so it specifies an error */
switch (ep_attrs) {
case USB_EP_ATTR_CONTROL:
cr = &ctrl_req->ctrl_completion_reason;
break;
case USB_EP_ATTR_BULK:
cr = &bulk_req->bulk_completion_reason;
break;
case USB_EP_ATTR_INTR:
cr = &intr_req->intr_completion_reason;
break;
case USB_EP_ATTR_ISOCH:
cr = &isoc_req->isoc_completion_reason;
break;
}
*cr = USB_CR_UNSPECIFIED_ERR;
if (servicing_interrupt() && (flags & USB_FLAGS_SLEEP)) {
return (USB_INVALID_CONTEXT);
}
if (pipe_type != ep_attrs) {
return (USB_INVALID_PIPE);
}
/* we must have usba_device and default ph to do autoclearing */
ASSERT(ph_data->p_usba_device);
if (ph_data->p_usba_device->usb_ph_list[0].usba_ph_data == NULL) {
return (USB_INVALID_PIPE);
}
/* check if this is a valid request packet, ie. not freed */
if (usba_check_in_list(&(ph_data->p_usba_device->usb_allocated),
&wrp->wr_allocated_list) != USB_SUCCESS) {
return (USB_INVALID_REQUEST);
}
/* copy over some members for easy checking later */
switch (ep_attrs) {
case USB_EP_ATTR_CONTROL:
ctrl_req->ctrl_cb_flags = USB_CB_NO_INFO;
data = ctrl_req->ctrl_data;
attrs = ctrl_req->ctrl_attributes;
timeout = ctrl_req->ctrl_timeout;
cb = (usb_opaque_t)ctrl_req->ctrl_cb;
exc_cb = (usb_opaque_t)ctrl_req->ctrl_exc_cb;
if (flags & USB_FLAGS_SLEEP) {
flags |= USBA_WRP_FLAGS_WAIT;
}
/* force auto clearing on the default pipe */
if (USBA_IS_DEFAULT_PIPE(ph_data)) {
attrs |= USB_ATTRS_AUTOCLEARING;
}
break;
case USB_EP_ATTR_BULK:
bulk_req->bulk_cb_flags = USB_CB_NO_INFO;
data = bulk_req->bulk_data;
attrs = bulk_req->bulk_attributes;
timeout = bulk_req->bulk_timeout;
cb = (usb_opaque_t)bulk_req->bulk_cb;
exc_cb = (usb_opaque_t)bulk_req->bulk_exc_cb;
if (flags & USB_FLAGS_SLEEP) {
flags |= USBA_WRP_FLAGS_WAIT;
}
break;
case USB_EP_ATTR_INTR:
intr_req->intr_cb_flags = USB_CB_NO_INFO;
data = intr_req->intr_data;
attrs = intr_req->intr_attributes;
timeout = intr_req->intr_timeout;
cb = (usb_opaque_t)intr_req->intr_cb;
exc_cb = (usb_opaque_t)intr_req->intr_exc_cb;
if ((flags & USB_FLAGS_SLEEP) &&
(attrs & USB_ATTRS_ONE_XFER)) {
flags |= USBA_WRP_FLAGS_WAIT;
}
break;
case USB_EP_ATTR_ISOCH:
isoc_req->isoc_cb_flags = USB_CB_NO_INFO;
data = isoc_req->isoc_data;
attrs = isoc_req->isoc_attributes;
cb = (usb_opaque_t)isoc_req->isoc_cb;
exc_cb = (usb_opaque_t)isoc_req->isoc_exc_cb;
break;
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_check_req: attrs = 0x%x flags=0x%x", attrs, flags);
/* check flags and attr combinations */
if (usba_flags_attr_check(ph_data, attrs, flags) !=
USB_SUCCESS) {
return (USB_INVALID_REQUEST);
}
/* if no sleep, there must be callback ptrs */
if ((flags & USB_FLAGS_SLEEP) == 0) {
if (cb == NULL || exc_cb == NULL) {
return (USB_INVALID_REQUEST);
}
}
switch (ep_attrs) {
case USB_EP_ATTR_CONTROL:
if (ctrl_req->ctrl_wLength && (data == NULL)) {
return (USB_INVALID_REQUEST);
}
break;
case USB_EP_ATTR_BULK:
if ((bulk_req->bulk_len) && (data == NULL)) {
return (USB_INVALID_REQUEST);
}
break;
case USB_EP_ATTR_INTR:
if (direction == USB_EP_DIR_OUT) {
if (intr_req->intr_len && data == NULL) {
return (USB_INVALID_REQUEST);
}
}
if (direction == USB_EP_DIR_IN) {
if (!(intr_req->intr_attributes & USB_ATTRS_ONE_XFER)) {
if (cb == NULL || exc_cb == NULL) {
return (USB_INVALID_REQUEST);
}
}
if (data != NULL) {
return (USB_INVALID_REQUEST);
}
if (!(intr_req->intr_attributes & USB_ATTRS_ONE_XFER) &&
(timeout > 0)) {
return (USB_INVALID_REQUEST);
}
}
break;
case USB_EP_ATTR_ISOCH:
if (direction == USB_EP_DIR_IN) {
if (cb == NULL || exc_cb == NULL) {
return (USB_INVALID_REQUEST);
}
}
if (data == NULL) {
return (USB_INVALID_REQUEST);
}
/*
* Since ehci/ohci/uhci use (data->b_wptr - data->b_rptr) as
* real isoc_pkts_length, it should be checked.
*/
if (direction == USB_EP_DIR_OUT) {
if (MBLKL(data) <= 0) {
return (USB_INVALID_REQUEST);
}
}
/* special isoc checks */
if ((isoc_req->isoc_pkts_count == 0) ||
(isoc_req->isoc_pkt_descr == NULL)) {
return (USB_INVALID_REQUEST);
}
/* check attributes for conflicts, one must be specified */
if (!((isoc_req->isoc_attributes &
USB_ATTRS_ISOC_START_FRAME) ||
(isoc_req->isoc_attributes & USB_ATTRS_ISOC_XFER_ASAP))) {
return (USB_NO_FRAME_NUMBER);
}
/* both may not be specified */
if ((isoc_req->isoc_attributes &
(USB_ATTRS_ISOC_START_FRAME | USB_ATTRS_ISOC_XFER_ASAP)) ==
(USB_ATTRS_ISOC_START_FRAME | USB_ATTRS_ISOC_XFER_ASAP)) {
return (USB_NO_FRAME_NUMBER);
}
/* no start frame may be specified for ASAP attribute */
if (((isoc_req->isoc_attributes & USB_ATTRS_ISOC_XFER_ASAP)) &&
isoc_req->isoc_frame_no) {
return (USB_INVALID_REQUEST);
}
/* start frame must be specified for START FRAME attribute */
if (((isoc_req->isoc_attributes &
USB_ATTRS_ISOC_START_FRAME)) &&
(isoc_req->isoc_frame_no == 0)) {
return (USB_NO_FRAME_NUMBER);
}
/* each packet must have initialized pkt length */
for (n = 0; n < isoc_req->isoc_pkts_count; n++) {
if (isoc_req->isoc_pkt_descr[n].isoc_pkt_length == 0) {
return (USB_INVALID_REQUEST);
}
}
break;
}
/* save pipe_handle/attrs/timeout/usb_flags */
wrp->wr_ph_data = ph_data;
wrp->wr_usb_flags = flags;
wrp->wr_attrs = attrs;
/* zero some fields in case the request is reused */
wrp->wr_done = B_FALSE;
wrp->wr_cr = USB_CR_OK;
/* this request looks good */
*cr = USB_CR_OK;
return (USB_SUCCESS);
}
/*
* Table of invalid flags and attributes values. See "usbai.h"
* for a complete table on valid usb_req_attrs_t
*/
#define X ((uint_t)(-1))
#define OUT USB_EP_DIR_OUT
#define IN USB_EP_DIR_IN
struct flags_attr {
uint_t ep_dir;
uint_t ep_attr;
uint_t usb_flags; /* usb_flags SLEEP or none */
uint_t attrs;
} usb_invalid_flags_attrs[] = {
{ OUT, USB_EP_ATTR_BULK, X, USB_ATTRS_SHORT_XFER_OK },
{ OUT, USB_EP_ATTR_INTR, X, USB_ATTRS_SHORT_XFER_OK },
{ OUT, USB_EP_ATTR_ISOCH, X, USB_ATTRS_SHORT_XFER_OK },
{ X, USB_EP_ATTR_CONTROL, X, USB_ATTRS_ISOC_START_FRAME },
{ X, USB_EP_ATTR_BULK, X, USB_ATTRS_ISOC_START_FRAME },
{ X, USB_EP_ATTR_INTR, X, USB_ATTRS_ISOC_START_FRAME },
{ X, USB_EP_ATTR_CONTROL, X, USB_ATTRS_ISOC_XFER_ASAP },
{ X, USB_EP_ATTR_INTR, X, USB_ATTRS_ISOC_XFER_ASAP },
{ OUT, USB_EP_ATTR_INTR, X, USB_ATTRS_ONE_XFER },
{ X, USB_EP_ATTR_BULK, X, USB_ATTRS_ISOC_XFER_ASAP },
{ X, USB_EP_ATTR_CONTROL, X, USB_ATTRS_ONE_XFER },
{ X, USB_EP_ATTR_BULK, X, USB_ATTRS_ONE_XFER },
{ X, USB_EP_ATTR_ISOCH, X, USB_ATTRS_ONE_XFER },
};
#define N_INVALID_FLAGS_ATTRS (sizeof (usb_invalid_flags_attrs))/ \
sizeof (struct flags_attr)
/*
* function to check flags and attribute combinations for a particular pipe
* Arguments:
* ph - pipe handle pointer
* attrs - attributes of the request
* flags - usb_flags
*/
static int
usba_flags_attr_check(usba_pipe_handle_data_t *ph_data,
usb_req_attrs_t attrs,
usb_flags_t flags)
{
uchar_t i;
uchar_t ep_dir = ph_data->p_ep.bEndpointAddress & USB_EP_DIR_MASK;
uchar_t ep_attr = ph_data->p_ep.bmAttributes & USB_EP_ATTR_MASK;
flags &= USB_FLAGS_SLEEP; /* ignore other flags */
/*
* Do some attributes validation checks here.
*/
for (i = 0; i < N_INVALID_FLAGS_ATTRS; i++) {
if (((ep_dir == usb_invalid_flags_attrs[i].ep_dir) ||
(usb_invalid_flags_attrs[i].ep_dir == X)) &&
((ep_attr == usb_invalid_flags_attrs[i].ep_attr) ||
(usb_invalid_flags_attrs[i].ep_attr == X)) &&
((flags & usb_invalid_flags_attrs[i].usb_flags) ||
(usb_invalid_flags_attrs[i].usb_flags == X)) &&
((attrs & usb_invalid_flags_attrs[i].attrs) ||
(usb_invalid_flags_attrs[i].attrs == X))) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"invalid (%d) : flags = 0x%x, attrs = 0x%x",
i, flags, attrs);
return (USB_INVALID_REQUEST);
}
}
return (USB_SUCCESS);
}
/*
* usba_rval2cr:
* convert rval to meaningful completion reason
* XXX extend completion reasons to get better mapping
*/
static struct {
int rval;
usb_cr_t cr;
} rval2cr[] = {
{USB_SUCCESS, USB_CR_OK},
{USB_FAILURE, USB_CR_UNSPECIFIED_ERR},
{USB_NO_RESOURCES, USB_CR_NO_RESOURCES},
{USB_NO_BANDWIDTH, USB_CR_NO_RESOURCES},
{USB_NOT_SUPPORTED, USB_CR_UNSPECIFIED_ERR},
{USB_PIPE_ERROR, USB_CR_UNSPECIFIED_ERR},
{USB_INVALID_PIPE, USB_CR_UNSPECIFIED_ERR},
{USB_NO_FRAME_NUMBER, USB_CR_UNSPECIFIED_ERR},
{USB_INVALID_START_FRAME, USB_CR_UNSPECIFIED_ERR},
{USB_HC_HARDWARE_ERROR, USB_CR_UNSPECIFIED_ERR},
{USB_INVALID_REQUEST, USB_CR_UNSPECIFIED_ERR},
{USB_INVALID_CONTEXT, USB_CR_UNSPECIFIED_ERR},
{USB_INVALID_VERSION, USB_CR_UNSPECIFIED_ERR},
{USB_INVALID_ARGS, USB_CR_UNSPECIFIED_ERR},
{USB_INVALID_PERM, USB_CR_UNSPECIFIED_ERR},
{USB_BUSY, USB_CR_UNSPECIFIED_ERR},
{0xffff, 0}
};
usb_cr_t
usba_rval2cr(int rval)
{
int i;
for (i = 0; rval2cr[i].rval != 0xffff; i++) {
if (rval2cr[i].rval == rval) {
return (rval2cr[i].cr);
}
}
return (USB_CR_UNSPECIFIED_ERR);
}
/*
* usba_start_next_req:
* Arguments:
* ph_data - pointer to pipe handle
*
* Currently, only ctrl/bulk requests can be queued
*/
void
usba_start_next_req(usba_pipe_handle_data_t *ph_data)
{
usb_ctrl_req_t *ctrl_req;
usb_bulk_req_t *bulk_req;
usba_req_wrapper_t *wrp;
uchar_t ep_attrs = ph_data->p_ep.bmAttributes &
USB_EP_ATTR_MASK;
int rval;
usb_pipe_state_t state;
mutex_enter(&ph_data->p_mutex);
switch (ep_attrs) {
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_BULK:
switch (usba_get_ph_state(ph_data)) {
case USB_PIPE_STATE_IDLE:
case USB_PIPE_STATE_CLOSING:
break;
default:
mutex_exit(&ph_data->p_mutex);
return;
}
break;
case USB_EP_ATTR_ISOCH:
case USB_EP_ATTR_INTR:
default:
mutex_exit(&ph_data->p_mutex);
return;
}
while ((wrp = (usba_req_wrapper_t *)
usba_rm_first_pvt_from_list(&ph_data->p_queue)) != NULL) {
/* only submit to HCD when idle/active */
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_start_next_req: ph_data=0x%p state=%d",
(void *)ph_data, usba_get_ph_state(ph_data));
if (ep_attrs == USB_EP_ATTR_CONTROL) {
ph_data->p_active_cntrl_req_wrp = (usb_opaque_t)wrp;
}
if ((state = usba_get_ph_state(ph_data)) ==
USB_PIPE_STATE_IDLE) {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_ACTIVE);
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"starting req = 0x%p",
(void *)USBA_WRP2CTRL_REQ(wrp));
switch (ep_attrs) {
case USB_EP_ATTR_CONTROL:
mutex_exit(&ph_data->p_mutex);
ctrl_req = USBA_WRP2CTRL_REQ(wrp);
/* submit to hcd */
rval = ph_data->p_usba_device->usb_hcdi_ops->
usba_hcdi_pipe_ctrl_xfer(ph_data,
ctrl_req, wrp->wr_usb_flags);
mutex_enter(&ph_data->p_mutex);
break;
case USB_EP_ATTR_BULK:
mutex_exit(&ph_data->p_mutex);
bulk_req = USBA_WRP2BULK_REQ(wrp);
/* submit to hcd */
rval = ph_data->p_usba_device->usb_hcdi_ops->
usba_hcdi_pipe_bulk_xfer(ph_data,
bulk_req, wrp->wr_usb_flags);
mutex_enter(&ph_data->p_mutex);
break;
default:
/* there shouldn't be any requests */
rval = USB_FAILURE;
break;
}
if (rval != USB_SUCCESS) {
mutex_exit(&ph_data->p_mutex);
usba_do_req_exc_cb(wrp,
usba_rval2cr(rval),
USB_CB_SUBMIT_FAILED);
mutex_enter(&ph_data->p_mutex);
}
/* we are done */
break;
} else {
mutex_exit(&ph_data->p_mutex);
switch (state) {
case USB_PIPE_STATE_CLOSING:
usba_do_req_exc_cb(wrp, USB_CR_PIPE_CLOSING, 0);
break;
case USB_PIPE_STATE_ERROR:
default:
usba_do_req_exc_cb(wrp, USB_CR_FLUSHED, 0);
break;
}
mutex_enter(&ph_data->p_mutex);
}
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_start_next_req done: ph_data=0x%p state=%d", (void *)ph_data,
usba_get_ph_state(ph_data));
mutex_exit(&ph_data->p_mutex);
}
/*
* usba_req_wrapper_alloc:
* Allocate + Initialize a usba_req_wrapper_t
*
* Arguments:
* dip - dev_info_t of the client driver
* req_len - sizeof request
* flags -
* USB_FLAGS_SLEEP - Sleep if resources are not available
* no USB_FLAGS_SLEEP - Don't Sleep if resources are not available
*
* Return Values:
* pointer to usba_req_wrapper_t on success; NULL on failure.
*
*/
static usba_req_wrapper_t *
usba_req_wrapper_alloc(dev_info_t *dip,
size_t req_len,
usb_flags_t flags)
{
int kmflag;
usba_device_t *usba_device = usba_get_usba_device(dip);
usba_req_wrapper_t *wrp;
size_t wr_length = sizeof (usba_req_wrapper_t) + req_len;
ddi_iblock_cookie_t iblock_cookie =
usba_hcdi_get_hcdi(usba_device->usb_root_hub_dip)->
hcdi_iblock_cookie;
if (servicing_interrupt() && (flags & USB_FLAGS_SLEEP)) {
return (NULL);
}
kmflag = (flags & USB_FLAGS_SLEEP) ? KM_SLEEP : KM_NOSLEEP;
/* Allocate the usb_{c/b/i/i}_req + usba_req_wrapper_t structure */
if ((wrp = kmem_zalloc(wr_length, kmflag)) != NULL) {
wrp->wr_length = wr_length;
wrp->wr_dip = dip;
wrp->wr_req = (usb_opaque_t)USBA_SETREQ_ADDR(wrp);
cv_init(&wrp->wr_cv, NULL, CV_DRIVER, NULL);
/* initialize mutex for the queue */
usba_init_list(&wrp->wr_queue, (usb_opaque_t)wrp,
iblock_cookie);
usba_init_list(&wrp->wr_allocated_list, (usb_opaque_t)wrp,
iblock_cookie);
usba_add_to_list(&usba_device->usb_allocated,
&wrp->wr_allocated_list);
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_req_wrapper_alloc: wrp = 0x%p", (void *)wrp);
}
return (wrp);
}
/*
* usba_req_wrapper_free:
* Frees a usba_req_wrapper_t. Get rid of lists if any.
*
* Arguments:
* wrp: request wrapper structure
*/
void
usba_req_wrapper_free(usba_req_wrapper_t *wrp)
{
usba_device_t *usba_device;
usba_pipe_handle_data_t *ph_data;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_req_wrapper_free: wrp=0x%p", (void *)wrp);
if (wrp) {
/* remove from queues */
ph_data = USBA_WRP2PH_DATA(wrp);
if (ph_data) {
(void) usba_rm_from_list(&ph_data->p_queue,
&wrp->wr_queue);
}
usba_device = usba_get_usba_device(wrp->wr_dip);
if (usba_rm_from_list(&usba_device->usb_allocated,
&wrp->wr_allocated_list) != USB_SUCCESS) {
cmn_err(CE_PANIC,
"usba_req_wrapper_free: data corruption");
}
usba_destroy_list(&wrp->wr_queue);
usba_destroy_list(&wrp->wr_allocated_list);
cv_destroy(&wrp->wr_cv);
kmem_free(wrp, wrp->wr_length);
}
}
/*
* usba_check_intr_context
* Set USB_CB_INTR_CONTEXT callback flag if executing in interrupt context
*/
usb_cb_flags_t
usba_check_intr_context(usb_cb_flags_t cb_flags)
{
if (servicing_interrupt() != 0) {
cb_flags |= USB_CB_INTR_CONTEXT;
}
return (cb_flags);
}
/*
* usba_req_normal_cb:
* perform normal callback depending on request type
*/
void
usba_req_normal_cb(usba_req_wrapper_t *req_wrp)
{
usba_pipe_handle_data_t *ph_data = req_wrp->wr_ph_data;
usb_pipe_handle_t pipe_handle;
uint_t direction = ph_data->p_ep.bEndpointAddress &
USB_EP_DIR_MASK;
usb_pipe_state_t pipe_state;
pipe_handle = usba_get_pipe_handle(ph_data);
mutex_enter(&ph_data->p_mutex);
ASSERT(ph_data->p_req_count >= 0);
pipe_state = usba_get_ph_state(ph_data);
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_req_normal_cb: "
"ph_data=0x%p state=%d wrp=0x%p ref=%d req=%d",
(void *)ph_data, pipe_state, (void *)req_wrp,
usba_get_ph_ref_count(ph_data), ph_data->p_req_count);
ASSERT((pipe_state == USB_PIPE_STATE_ACTIVE) ||
(pipe_state == USB_PIPE_STATE_CLOSING));
/* set done to indicate that we will do callback or cv_signal */
ASSERT(req_wrp->wr_done == B_FALSE);
req_wrp->wr_done = B_TRUE;
/* update the pipe state */
switch (req_wrp->wr_ph_data->p_ep.bmAttributes &
USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_BULK:
usba_pipe_new_state(ph_data, USB_PIPE_STATE_IDLE);
break;
case USB_EP_ATTR_INTR:
if ((direction == USB_EP_DIR_IN) &&
(USBA_WRP2INTR_REQ(req_wrp)->intr_attributes &
USB_ATTRS_ONE_XFER)) {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_IDLE);
} else if ((direction == USB_EP_DIR_OUT) &&
(ph_data->p_req_count == 0)) {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_IDLE);
}
break;
case USB_EP_ATTR_ISOCH:
if ((ph_data->p_req_count == 0) &&
(direction == USB_EP_DIR_OUT)) {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_IDLE);
}
break;
}
/* now complete the request */
if (req_wrp->wr_usb_flags & USBA_WRP_FLAGS_WAIT) {
ph_data->p_active_cntrl_req_wrp = NULL;
cv_signal(&req_wrp->wr_cv);
mutex_exit(&ph_data->p_mutex);
} else {
mutex_exit(&ph_data->p_mutex);
/* This sets USB_CB_INTR_CONTEXT as needed. */
usba_req_set_cb_flags(req_wrp, USB_CB_NO_INFO);
switch (req_wrp->wr_ph_data->p_ep.bmAttributes &
USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
USBA_WRP2CTRL_REQ(req_wrp)->ctrl_cb(pipe_handle,
USBA_WRP2CTRL_REQ(req_wrp));
mutex_enter(&ph_data->p_mutex);
ph_data->p_active_cntrl_req_wrp = NULL;
mutex_exit(&ph_data->p_mutex);
break;
case USB_EP_ATTR_INTR:
USBA_WRP2INTR_REQ(req_wrp)->intr_cb(pipe_handle,
USBA_WRP2INTR_REQ(req_wrp));
break;
case USB_EP_ATTR_BULK:
USBA_WRP2BULK_REQ(req_wrp)->bulk_cb(pipe_handle,
USBA_WRP2BULK_REQ(req_wrp));
break;
case USB_EP_ATTR_ISOCH:
USBA_WRP2ISOC_REQ(req_wrp)->isoc_cb(pipe_handle,
USBA_WRP2ISOC_REQ(req_wrp));
break;
}
}
/* we are done with this request */
mutex_enter(&ph_data->p_mutex);
ph_data->p_req_count--;
ASSERT(ph_data->p_req_count >= 0);
mutex_exit(&ph_data->p_mutex);
}
/*
* usba_req_exc_cb:
* perform exception cb depending on request type.
* ensure the completion reason is non zero
*/
void
usba_req_exc_cb(usba_req_wrapper_t *req_wrp, usb_cr_t cr,
usb_cb_flags_t cb_flags)
{
usba_pipe_handle_data_t *ph_data = req_wrp->wr_ph_data;
usb_pipe_handle_t pipe_handle = usba_get_pipe_handle(ph_data);
mutex_enter(&req_wrp->wr_ph_data->p_mutex);
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_req_exc_cb: %s%d: ph_data=0x%p (ep%x) state=%d wrp=0x%p "
"ref=%d reqcnt=%d cr=%d",
ddi_driver_name(req_wrp->wr_dip),
ddi_get_instance(req_wrp->wr_dip),
(void *)ph_data, ph_data->p_ep.bEndpointAddress,
usba_get_ph_state(ph_data), (void *)req_wrp,
usba_get_ph_ref_count(ph_data), ph_data->p_req_count,
req_wrp->wr_cr);
ASSERT(req_wrp->wr_ph_data->p_req_count >= 0);
usba_req_set_cb_flags(req_wrp, cb_flags);
/* if there was no CR set already, set it now */
if (req_wrp->wr_cr == USB_CR_OK) {
req_wrp->wr_cr = (cr != USB_CR_OK) ?
cr : USB_CR_UNSPECIFIED_ERR;
}
ASSERT(req_wrp->wr_done == B_FALSE);
req_wrp->wr_done = B_TRUE;
switch (req_wrp->wr_ph_data->p_ep.bmAttributes &
USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
if (USBA_WRP2CTRL_REQ(req_wrp)->
ctrl_completion_reason == USB_CR_OK) {
USBA_WRP2CTRL_REQ(req_wrp)->
ctrl_completion_reason = req_wrp->wr_cr;
}
break;
case USB_EP_ATTR_INTR:
if (USBA_WRP2INTR_REQ(req_wrp)->
intr_completion_reason == USB_CR_OK) {
USBA_WRP2INTR_REQ(req_wrp)->
intr_completion_reason = req_wrp->wr_cr;
}
break;
case USB_EP_ATTR_BULK:
if (USBA_WRP2BULK_REQ(req_wrp)->
bulk_completion_reason == USB_CR_OK) {
USBA_WRP2BULK_REQ(req_wrp)->
bulk_completion_reason = req_wrp->wr_cr;
}
break;
case USB_EP_ATTR_ISOCH:
if (USBA_WRP2ISOC_REQ(req_wrp)->
isoc_completion_reason == USB_CR_OK) {
USBA_WRP2ISOC_REQ(req_wrp)->
isoc_completion_reason = req_wrp->wr_cr;
}
break;
}
if (req_wrp->wr_usb_flags & USBA_WRP_FLAGS_WAIT) {
cv_signal(&req_wrp->wr_cv);
if (ph_data->p_active_cntrl_req_wrp == (usb_opaque_t)req_wrp) {
ph_data->p_active_cntrl_req_wrp = NULL;
}
mutex_exit(&ph_data->p_mutex);
} else {
mutex_exit(&ph_data->p_mutex);
switch (req_wrp->wr_ph_data->p_ep.bmAttributes &
USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
USBA_WRP2CTRL_REQ(req_wrp)->ctrl_exc_cb(pipe_handle,
USBA_WRP2CTRL_REQ(req_wrp));
mutex_enter(&ph_data->p_mutex);
if (ph_data->p_active_cntrl_req_wrp ==
(usb_opaque_t)req_wrp) {
ph_data->p_active_cntrl_req_wrp = NULL;
}
mutex_exit(&ph_data->p_mutex);
break;
case USB_EP_ATTR_INTR:
USBA_WRP2INTR_REQ(req_wrp)->intr_exc_cb(pipe_handle,
USBA_WRP2INTR_REQ(req_wrp));
break;
case USB_EP_ATTR_BULK:
USBA_WRP2BULK_REQ(req_wrp)->bulk_exc_cb(pipe_handle,
USBA_WRP2BULK_REQ(req_wrp));
break;
case USB_EP_ATTR_ISOCH:
USBA_WRP2ISOC_REQ(req_wrp)->isoc_exc_cb(pipe_handle,
USBA_WRP2ISOC_REQ(req_wrp));
break;
}
}
/* we are done with this request */
mutex_enter(&ph_data->p_mutex);
ph_data->p_req_count--;
ASSERT(ph_data->p_req_count >= 0);
mutex_exit(&ph_data->p_mutex);
}
/*
* usba_do_req_exc_cb:
* called when flushing requests. rather than calling usba_req_exc_cb()
* directly, this function uses usba_hcdi_cb() which ensures callback
* order is preserved
*/
void
usba_do_req_exc_cb(usba_req_wrapper_t *req_wrp, usb_cr_t cr,
usb_cb_flags_t cb_flags)
{
req_wrp->wr_cb_flags |= cb_flags;
usba_hcdi_cb(req_wrp->wr_ph_data, req_wrp->wr_req, cr);
}
/*
* usba_req_set_cb_flags:
* This function sets the request's callback flags to those stored in the
* request wrapper ORed with those received as an argument. Additionally
* USB_CB_INTR_CONTEXT is set if called from interrupt context.
*
* NOTE: The xfer may have succeeded, which client driver can determine
* by looking at usb_cr_t
*/
void
usba_req_set_cb_flags(usba_req_wrapper_t *req_wrp,
usb_cb_flags_t cb_flags)
{
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_req_set_cb_flags: wrp=0x%p cb-flags=0x%x",
(void *)req_wrp, cb_flags);
cb_flags |= req_wrp->wr_cb_flags;
cb_flags = usba_check_intr_context(cb_flags);
/* do the callback under taskq context */
switch (req_wrp->wr_ph_data->p_ep.bmAttributes &
USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
USBA_WRP2CTRL_REQ(req_wrp)->ctrl_cb_flags |= cb_flags;
break;
case USB_EP_ATTR_INTR:
USBA_WRP2INTR_REQ(req_wrp)->intr_cb_flags |= cb_flags;
break;
case USB_EP_ATTR_BULK:
USBA_WRP2BULK_REQ(req_wrp)->bulk_cb_flags |= cb_flags;
break;
case USB_EP_ATTR_ISOCH:
USBA_WRP2ISOC_REQ(req_wrp)->isoc_cb_flags |= cb_flags;
break;
}
}
/*
* usba_pipe_sync_wait:
* wait for the request to finish.
* usba_hcdi_cb() does a cv_signal thru a soft intr
*
* Arguments:
* ph_data - pointer to pipe handle data
* wrp - pointer to usba_req_wrapper_structure.
*
* Return Values:
* USB_SUCCESS - request successfully executed
* USB_FAILURE - request failed
*/
static int
usba_pipe_sync_wait(usba_pipe_handle_data_t *ph_data,
usba_req_wrapper_t *wrp)
{
ASSERT(wrp->wr_usb_flags & USB_FLAGS_SLEEP);
ASSERT(ph_data == wrp->wr_ph_data);
mutex_enter(&ph_data->p_mutex);
while (wrp->wr_done != B_TRUE) {
cv_wait(&wrp->wr_cv, &ph_data->p_mutex);
}
mutex_exit(&ph_data->p_mutex);
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_wait: ph_data=0x%p cr=0x%x", (void *)ph_data,
wrp->wr_cr);
/* XXX return something better than USB_FAILURE?? */
return (wrp->wr_cr == USB_CR_OK ? USB_SUCCESS : USB_FAILURE);
}
/*
* Allocate usb control request and a USB request wrapper
*
* Arguments:
* dip - dev_info_t of the client driver
* len - length of "data" for this control request
* flags:
* USB_FLAGS_SLEEP - Sleep if resources are not available
* no USB_FLAGS_SLEEP - Don't Sleep if resources are not available
*
* Return Values: usb_ctrl_req_t on success, NULL on failure
*/
usb_ctrl_req_t *
usb_alloc_ctrl_req(dev_info_t *dip,
size_t len,
usb_flags_t flags)
{
usb_ctrl_req_t *ctrl_req = NULL;
usba_req_wrapper_t *wrp;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_alloc_ctrl_req: dip=0x%p, wlen=0x%lx, flags=0x%x",
(void *)dip, len, flags);
/* Allocate + Initialize the usba_req_wrapper_t structure */
if (dip &&
((wrp = usba_req_wrapper_alloc(dip, sizeof (*ctrl_req), flags)) !=
NULL)) {
ctrl_req = USBA_WRP2CTRL_REQ(wrp);
/* Allocate the usb_ctrl_req data mblk */
if (len) {
if (flags & USB_FLAGS_SLEEP) {
ctrl_req->ctrl_data = allocb_wait(len, BPRI_LO,
STR_NOSIG, NULL);
} else if ((ctrl_req->ctrl_data =
allocb(len, BPRI_HI)) == NULL) {
usba_req_wrapper_free(wrp);
ctrl_req = NULL;
}
}
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_alloc_ctrl_req: ctrl_req = 0x%p", (void *)ctrl_req);
return (ctrl_req);
}
/*
* usb_free_ctrl_req:
* free USB control request + wrapper
*
* Arguments:
* req - pointer to usb_ctrl_req_t
*/
void
usb_free_ctrl_req(usb_ctrl_req_t *req)
{
if (req) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_free_ctrl_req: req = 0x%p", (void *)req);
if (req->ctrl_data) {
freemsg(req->ctrl_data);
}
usba_req_wrapper_free(USBA_REQ2WRP(req));
}
}
/*
* Client driver calls this function to issue the control
* request to the USBA
*
* Arguments:
* pipe_handle: control pipe pipehandle (obtained via usb_pipe_open()
* req: control request
* usb_flags:
* USB_FLAGS_SLEEP - wait for the request to complete
*
* Return Values:
* USB_SUCCESS - request successfully executed
* USB_FAILURE - request failed
*/
int
usb_pipe_ctrl_xfer(usb_pipe_handle_t pipe_handle,
usb_ctrl_req_t *req,
usb_flags_t usb_flags)
{
int rval;
usba_req_wrapper_t *wrp = USBA_REQ2WRP(req);
usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle);
usba_device_t *usba_device;
usb_flags_t wrp_usb_flags;
usb_pipe_state_t pipe_state;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_ctrl_xfer: req=0x%p, wrp=0x%p\n\t"
"setup = 0x%x 0x%x 0x%x 0x%x 0x%x uf=0x%x",
(void *)req, (void *)wrp, req->ctrl_bmRequestType,
req->ctrl_bRequest, req->ctrl_wValue, req->ctrl_wIndex,
req->ctrl_wLength, usb_flags);
if (ph_data == NULL) {
return (USB_INVALID_PIPE);
}
mutex_enter(&ph_data->p_mutex);
usba_device = ph_data->p_usba_device;
if ((rval = usba_check_req(ph_data, (usb_opaque_t)req, usb_flags,
USB_EP_ATTR_CONTROL)) != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"request rejected: rval=%d", rval);
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
return (rval);
}
ASSERT(ph_data == wrp->wr_ph_data);
/* we accepted the request, so increment the req count */
ph_data->p_req_count++;
wrp_usb_flags = wrp->wr_usb_flags;
/* Get the current bulk pipe state */
pipe_state = usba_get_ph_state(ph_data);
/*
* if this is for the default pipe, and the pipe is in error,
* just queue the request. autoclearing will start this request
*
* if there is already an active request in the queue
* then just add this request to the queue.
*/
switch (pipe_state) {
case USB_PIPE_STATE_IDLE:
if (ph_data->p_queue.next ||
ph_data->p_active_cntrl_req_wrp) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_ctrl_xfer: queue request 0x%p",
(void *)req);
usba_add_to_list(&ph_data->p_queue, &wrp->wr_queue);
rval = USB_SUCCESS;
mutex_exit(&ph_data->p_mutex);
} else {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_ACTIVE);
ph_data->p_active_cntrl_req_wrp = (usb_opaque_t)wrp;
mutex_exit(&ph_data->p_mutex);
/* issue the request to HCD */
rval = usba_device->usb_hcdi_ops->
usba_hcdi_pipe_ctrl_xfer(ph_data, req, usb_flags);
}
break;
case USB_PIPE_STATE_ACTIVE:
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_ctrl_xfer: queue request 0x%p", (void *)req);
usba_add_to_list(&ph_data->p_queue, &wrp->wr_queue);
rval = USB_SUCCESS;
mutex_exit(&ph_data->p_mutex);
break;
case USB_PIPE_STATE_ERROR:
if (USBA_IS_DEFAULT_PIPE(ph_data)) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_ctrl_xfer: queue request 0x%p on "
"pending def pipe error", (void *)req);
usba_add_to_list(&ph_data->p_queue, &wrp->wr_queue);
rval = USB_SUCCESS;
} else {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_ctrl_xfer: pipe is in error state ");
rval = USB_PIPE_ERROR;
}
mutex_exit(&ph_data->p_mutex);
break;
default:
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_ctrl_xfer: pipe state %d", pipe_state);
rval = USB_PIPE_ERROR;
mutex_exit(&ph_data->p_mutex);
break;
}
/* if there has been a failure, decrement req count */
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_ctrl_xfer: hcd failed req 0x%p", (void *)req);
if (req->ctrl_completion_reason == USB_CR_OK) {
req->ctrl_completion_reason = usba_rval2cr(rval);
}
mutex_enter(&ph_data->p_mutex);
ASSERT(wrp->wr_done == B_FALSE);
ph_data->p_req_count--;
ASSERT(ph_data->p_req_count >= 0);
ph_data->p_active_cntrl_req_wrp = NULL;
if ((ph_data->p_req_count == 0) &&
(usba_get_ph_state(ph_data) == USB_PIPE_STATE_ACTIVE)) {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_IDLE);
}
mutex_exit(&ph_data->p_mutex);
/* if success and sleep specified, wait for completion */
} else if (wrp_usb_flags & USBA_WRP_FLAGS_WAIT) {
rval = usba_pipe_sync_wait(ph_data, wrp);
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_ctrl_xfer: rval=0x%x", rval);
usba_release_ph_data(ph_data->p_ph_impl);
return (rval);
}
/*
* usb_pipe_sync_ctrl_xfer():
* for simple synchronous control transactions this wrapper function
* will perform the allocation, xfer, and deallocation
* USB_ATTRS_AUTOCLEARING will be enabled
*
* Arguments:
* dip - pointer to clients devinfo
* pipe_handle - control pipe pipehandle (obtained via usb_pipe_open()
* bmRequestType - characteristics of request
* bRequest - specific request
* wValue - varies according to request
* wIndex - index or offset
* wLength - number of bytes to xfer
* data - pointer to pointer to data and may be NULL if
* wLength is 0
* attrs - required request attributes
* completion_reason - completion status
* cb_flags - request completions flags
* flags - none
*
* Return Values:
* USB_SUCCESS - request successfully executed
* USB_* - request failed
*
* Notes:
* - in the case of failure, the client should check completion_reason and
* and cb_flags and determine further recovery action
* - the client should check data and if non-zero, free the data on
* completion
*/
int
usb_pipe_sync_ctrl_xfer(dev_info_t *dip,
usb_pipe_handle_t pipe_handle,
uchar_t bmRequestType,
uchar_t bRequest,
uint16_t wValue,
uint16_t wIndex,
uint16_t wLength,
mblk_t **data,
usb_req_attrs_t attributes,
usb_cr_t *completion_reason,
usb_cb_flags_t *cb_flags,
usb_flags_t flags)
{
usba_pipe_handle_data_t *ph_data;
int rval;
usb_ctrl_req_t *ctrl_req;
size_t length;
#ifdef DEBUG
#define BUFSIZE 256
char *buf = kmem_alloc(BUFSIZE, KM_SLEEP);
#endif
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_sync_ctrl_xfer: ph=0x%p\n\t"
"setup = 0x%x 0x%x 0x%x 0x%x 0x%x uf = 0x%x", (void *)pipe_handle,
bmRequestType, bRequest, wValue, wIndex, wLength, flags);
if ((ph_data = usba_hold_ph_data(pipe_handle)) == NULL) {
rval = USB_INVALID_PIPE;
goto done;
}
if (servicing_interrupt()) {
rval = USB_INVALID_CONTEXT;
goto done;
}
if (dip == NULL) {
rval = USB_INVALID_ARGS;
goto done;
}
length = ((data) && (*data)) ? 0: wLength;
ctrl_req = usb_alloc_ctrl_req(dip,
length, flags | USB_FLAGS_SLEEP);
/* Initialize the ctrl_req structure */
ctrl_req->ctrl_bmRequestType = bmRequestType;
ctrl_req->ctrl_bRequest = bRequest;
ctrl_req->ctrl_wValue = wValue;
ctrl_req->ctrl_wIndex = wIndex;
ctrl_req->ctrl_wLength = wLength;
ctrl_req->ctrl_data = ctrl_req->ctrl_data ?
ctrl_req->ctrl_data : ((data) ? *data : NULL);
ctrl_req->ctrl_timeout = USB_PIPE_TIMEOUT;
ctrl_req->ctrl_attributes = attributes | USB_ATTRS_AUTOCLEARING;
/* Issue control xfer to the HCD */
rval = usb_pipe_ctrl_xfer(pipe_handle, ctrl_req,
flags | USB_FLAGS_SLEEP);
#ifdef DEBUG
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"req=0x%p, cr=%s cb_flags=%s data=0x%p rval=%s",
(void *)ctrl_req, usb_str_cr(ctrl_req->ctrl_completion_reason),
usb_str_cb_flags(ctrl_req->ctrl_cb_flags, buf, BUFSIZE),
(void *)ctrl_req->ctrl_data, usb_str_rval(rval));
#endif
/* copy back ctrl_req values */
if (data) {
*data = ctrl_req->ctrl_data;
}
if (completion_reason) {
*completion_reason = ctrl_req->ctrl_completion_reason;
}
if (cb_flags) {
*cb_flags = ctrl_req->ctrl_cb_flags;
}
/* Free up the control request now */
ctrl_req->ctrl_data = NULL; /* leave to client to free */
usb_free_ctrl_req(ctrl_req);
done:
#ifdef DEBUG
kmem_free(buf, BUFSIZE);
#endif
if (ph_data) {
usba_release_ph_data(ph_data->p_ph_impl);
}
return (rval);
}
/*
* usb_pipe_ctrl_xfer_wait():
* Easy-to-use wrapper around usb_pipe_sync_ctrl_xfer.
*
* ARGUMENTS:
* pipe_handle - control pipe pipehandle (obtained via usb_pipe_open())
* setup - setup descriptor params, attributes
* data - pointer to pointer to data and may be NULL when
* wLength is 0
* completion_reason - completion status.
* cb_flags - request completions flags.
* flags - none.
*
* RETURN VALUES:
* USB_SUCCESS - request successfully executed.
* USB_* - failure
*/
int
usb_pipe_ctrl_xfer_wait(
usb_pipe_handle_t pipe_handle,
usb_ctrl_setup_t *setup,
mblk_t **data,
usb_cr_t *completion_reason,
usb_cb_flags_t *cb_flags,
usb_flags_t flags)
{
return (usb_pipe_sync_ctrl_xfer(
usba_get_dip(pipe_handle),
pipe_handle,
setup->bmRequestType,
setup->bRequest,
setup->wValue,
setup->wIndex,
setup->wLength,
data,
setup->attrs,
completion_reason,
cb_flags,
flags));
}
/*
* usb_alloc_bulk_req:
* Allocate a usb bulk request + usba_req_wrapper_t
*
* Arguments:
* dip - dev_info_t of the client driver
* len - length of "data" for this bulk request
* flags:
* USB_FLAGS_SLEEP - Sleep if resources are not available
*
* Return Values:
* usb_bulk_req_t on success, NULL on failure
*/
usb_bulk_req_t *
usb_alloc_bulk_req(dev_info_t *dip,
size_t len,
usb_flags_t flags)
{
usb_bulk_req_t *bulk_req = NULL;
usba_req_wrapper_t *wrp;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_alloc_bulk_req: dip=0x%p wlen=0x%lx flags=0x%x",
(void *)dip, len, flags);
/* Allocate + Initialize the usba_req_wrapper_t structure */
if (dip &&
((wrp = usba_req_wrapper_alloc(dip, sizeof (*bulk_req), flags)) !=
NULL)) {
bulk_req = USBA_WRP2BULK_REQ(wrp);
/* Allocate the usb_bulk_req data mblk */
if (len) {
if (flags & USB_FLAGS_SLEEP) {
bulk_req->bulk_data = allocb_wait(len,
BPRI_LO, STR_NOSIG, NULL);
} else if ((bulk_req->bulk_data =
allocb(len, BPRI_HI)) == NULL) {
usba_req_wrapper_free(wrp);
bulk_req = NULL;
}
}
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_alloc_bulk_req: bulk_req = 0x%p", (void *)bulk_req);
return (bulk_req);
}
/*
* usb_free_bulk_req:
* free USB bulk request + wrapper
*
* Arguments:
* req - pointer to usb_bulk_req_t
*/
void
usb_free_bulk_req(usb_bulk_req_t *req)
{
if (req) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_free_bulk_req: req=0x%p", (void *)req);
if (req->bulk_data) {
freemsg(req->bulk_data);
}
usba_req_wrapper_free(USBA_REQ2WRP(req));
}
}
/*
* Client driver calls this function to issue the bulk xfer to the USBA
*
* Arguments:-
* pipe_handle - bulk pipe handle (obtained via usb_pipe_open()
* req - bulk data xfer request (IN or OUT)
* usb_flags - USB_FLAGS_SLEEP - wait for the request to complete
*
* Return Values:
* USB_SUCCESS - success
* USB_FAILURE - unspecified failure
*/
int
usb_pipe_bulk_xfer(usb_pipe_handle_t pipe_handle,
usb_bulk_req_t *req,
usb_flags_t usb_flags)
{
int rval;
usba_req_wrapper_t *wrp = USBA_REQ2WRP(req);
usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle);
usba_device_t *usba_device;
usb_flags_t wrp_usb_flags;
usb_pipe_state_t pipe_state;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_bulk_xfer: req=0x%p uf=0x%x", (void *)req, usb_flags);
if (ph_data == NULL) {
return (USB_INVALID_PIPE);
}
mutex_enter(&ph_data->p_mutex);
usba_device = ph_data->p_usba_device;
if ((rval = usba_check_req(ph_data, (usb_opaque_t)req, usb_flags,
USB_EP_ATTR_BULK)) != USB_SUCCESS) {
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
return (rval);
}
/* we accepted the request */
ph_data->p_req_count++;
wrp_usb_flags = wrp->wr_usb_flags;
/* Get the current bulk pipe state */
pipe_state = usba_get_ph_state(ph_data);
/*
* if there is already an active request in the queue
* then just add this request to the queue.
*/
switch (pipe_state) {
case USB_PIPE_STATE_IDLE:
if (ph_data->p_queue.next) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_bulk_xfer: queue request 0x%p",
(void *)req);
usba_add_to_list(&ph_data->p_queue, &wrp->wr_queue);
rval = USB_SUCCESS;
mutex_exit(&ph_data->p_mutex);
} else {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_ACTIVE);
mutex_exit(&ph_data->p_mutex);
/* issue the request to HCD */
rval = usba_device->usb_hcdi_ops->
usba_hcdi_pipe_bulk_xfer(ph_data, req, usb_flags);
}
break;
case USB_PIPE_STATE_ACTIVE:
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_bulk_xfer: queue request 0x%p", (void *)req);
usba_add_to_list(&ph_data->p_queue, &wrp->wr_queue);
rval = USB_SUCCESS;
mutex_exit(&ph_data->p_mutex);
break;
default:
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_bulk_xfer: pipe state %d", pipe_state);
rval = USB_PIPE_ERROR;
mutex_exit(&ph_data->p_mutex);
break;
}
if (rval != USB_SUCCESS) {
if (req->bulk_completion_reason == USB_CR_OK) {
req->bulk_completion_reason = usba_rval2cr(rval);
}
mutex_enter(&ph_data->p_mutex);
ASSERT(wrp->wr_done == B_FALSE);
ph_data->p_req_count--;
ASSERT(ph_data->p_req_count >= 0);
if ((ph_data->p_req_count == 0) &&
(usba_get_ph_state(ph_data) == USB_PIPE_STATE_ACTIVE)) {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_IDLE);
}
mutex_exit(&ph_data->p_mutex);
} else if (wrp_usb_flags & USBA_WRP_FLAGS_WAIT) {
rval = usba_pipe_sync_wait(ph_data, wrp);
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_bulk_xfer: rval=%d", rval);
usba_release_ph_data(ph_data->p_ph_impl);
return (rval);
}
/*
* usb_pipe_bulk_transfer_size:
* - request HCD to return bulk max transfer data size
*
* Arguments:
* dip - pointer to dev_info_t
* size - pointer to bulk_transfer_size
*
* Return Values:
* USB_SUCCESS - request successfully executed
* USB_FAILURE - request failed
*/
int
usb_pipe_bulk_transfer_size(dev_info_t *dip,
size_t *size)
{
return (usb_pipe_get_max_bulk_transfer_size(dip, size));
}
int
usb_pipe_get_max_bulk_transfer_size(dev_info_t *dip,
size_t *size)
{
usba_device_t *usba_device;
if ((dip == NULL) || (size == NULL)) {
return (USB_INVALID_ARGS);
}
usba_device = usba_get_usba_device(dip);
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_bulk_transfer_size: usba_device=0x%p",
(void *)usba_device);
if ((usba_device) &&
(usba_device->usb_hcdi_ops->usba_hcdi_bulk_transfer_size)) {
return (usba_device->usb_hcdi_ops->
usba_hcdi_bulk_transfer_size(usba_device, size));
} else {
*size = 0;
return (USB_FAILURE);
}
}
/*
* usb_alloc_intr_req:
* Allocate usb interrupt request
*
* Arguments:
* dip - dev_info_t of the client driver
* len - length of "data" for this interrupt request
* flags -
* USB_FLAGS_SLEEP - Sleep if resources are not available
*
* Return Values:
* usb_intr_req_t on success, NULL on failure
*/
usb_intr_req_t *
usb_alloc_intr_req(dev_info_t *dip,
size_t len,
usb_flags_t flags)
{
usb_intr_req_t *intr_req = NULL;
usba_req_wrapper_t *wrp;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_alloc_intr_req: dip=0x%p, len=0x%lx, flags=0x%x",
(void *)dip, len, flags);
/* Allocate + Initialize the usba_req_wrapper_t structure */
if ((dip &&
(wrp = usba_req_wrapper_alloc(dip, sizeof (*intr_req), flags)) !=
NULL)) {
intr_req = (usb_intr_req_t *)USBA_WRP2INTR_REQ(wrp);
/* Allocate the usb_intr_req data mblk */
if (len) {
if (flags & USB_FLAGS_SLEEP) {
intr_req->intr_data = allocb_wait(len, BPRI_LO,
STR_NOSIG, NULL);
} else if ((intr_req->intr_data =
allocb(len, BPRI_HI)) == NULL) {
usba_req_wrapper_free(wrp);
intr_req = NULL;
}
}
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_alloc_intr_req: intr_req=0x%p", (void *)intr_req);
return (intr_req);
}
/*
* usba_hcdi_dup_intr_req:
* create duplicate of interrupt request
*
* Arguments:
* dip - devinfo pointer
* reqp - original requestp pointer
* len - length of "data" for this interrupt request
* flags -
* USB_FLAGS_SLEEP - Sleep if resources are not available
*
* Return Values:
* usb_intr_req_t on success, NULL on failure
*/
usb_intr_req_t *
usba_hcdi_dup_intr_req(
dev_info_t *dip,
usb_intr_req_t *reqp,
size_t len,
usb_flags_t flags)
{
usb_intr_req_t *intr_reqp = NULL;
usba_req_wrapper_t *intr_wrp, *req_wrp;
if (reqp == NULL) {
return (NULL);
}
req_wrp = USBA_REQ2WRP(reqp);
if (((intr_reqp = usb_alloc_intr_req(dip, len, flags)) != NULL)) {
intr_reqp->intr_client_private = reqp->intr_client_private;
intr_reqp->intr_timeout = reqp->intr_timeout;
intr_reqp->intr_attributes = reqp->intr_attributes;
intr_reqp->intr_len = reqp->intr_len;
intr_reqp->intr_cb = reqp->intr_cb;
intr_reqp->intr_exc_cb = reqp->intr_exc_cb;
intr_wrp = USBA_REQ2WRP(intr_reqp);
intr_wrp->wr_dip = req_wrp->wr_dip;
intr_wrp->wr_ph_data = req_wrp->wr_ph_data;
intr_wrp->wr_attrs = req_wrp->wr_attrs;
intr_wrp->wr_usb_flags = req_wrp->wr_usb_flags;
}
return (intr_reqp);
}
/*
* usb_free_intr_req:
* free USB intr request + wrapper
*
* Arguments:
* req - pointer to usb_intr_req_t
*/
void
usb_free_intr_req(usb_intr_req_t *req)
{
if (req) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_free_intr_req: req = 0x%p", (void *)req);
if (req->intr_data) {
freemsg(req->intr_data);
}
usba_req_wrapper_free(USBA_REQ2WRP(req));
}
}
/*
* Client driver calls this function to issue the intr xfer to the USBA
*
* Arguments:-
* pipe_handle - intr pipe handle (obtained via usb_pipe_open()
* req - intr data xfer request (IN or OUT)
* flags -
* USB_FLAGS_SLEEP - wait for the request to complete
* Return Values
* USB_SUCCESS - success
* USB_FAILURE - unspecified failure
*/
int
usb_pipe_intr_xfer(usb_pipe_handle_t pipe_handle,
usb_intr_req_t *req,
usb_flags_t usb_flags)
{
int rval;
usba_req_wrapper_t *wrp = USBA_REQ2WRP(req);
usba_ph_impl_t *ph_impl = (usba_ph_impl_t *)pipe_handle;
usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle);
usba_device_t *usba_device;
uchar_t direction;
usb_flags_t wrp_usb_flags;
usb_pipe_state_t pipe_state;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_intr_req: req=0x%p uf=0x%x",
(void *)req, usb_flags);
if (ph_data == NULL) {
return (USB_INVALID_PIPE);
}
usba_device = ph_data->p_usba_device;
direction = ph_data->p_ep.bEndpointAddress & USB_EP_DIR_MASK;
mutex_enter(&ph_data->p_mutex);
if ((rval = usba_check_req(ph_data, (usb_opaque_t)req, usb_flags,
USB_EP_ATTR_INTR)) != USB_SUCCESS) {
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
return (rval);
}
/* Get the current interrupt pipe state */
pipe_state = usba_get_ph_state(ph_data);
switch (pipe_state) {
case USB_PIPE_STATE_IDLE:
/*
* if the pipe state is in middle of transition,
* i.e. stop polling is in progress, fail any
* attempt to do a start polling
*/
mutex_enter(&ph_impl->usba_ph_mutex);
if (ph_impl->usba_ph_state_changing > 0) {
mutex_exit(&ph_impl->usba_ph_mutex);
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_intr_req: fail request - "
"stop polling in progress");
return (USB_FAILURE);
} else {
mutex_exit(&ph_impl->usba_ph_mutex);
usba_pipe_new_state(ph_data, USB_PIPE_STATE_ACTIVE);
}
break;
case USB_PIPE_STATE_ACTIVE:
/*
* If this is interrupt IN pipe and if we are
* already polling, return failure.
*/
if (direction == USB_EP_DIR_IN) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI,
usbai_log_handle,
"usb_pipe_intr_req: already polling");
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
return (USB_FAILURE);
}
break;
default:
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_intr_req: pipe state %d", pipe_state);
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
return (USB_PIPE_ERROR);
}
/* we accept the request */
wrp_usb_flags = wrp->wr_usb_flags;
ph_data->p_req_count++;
mutex_exit(&ph_data->p_mutex);
/* issue the request out */
if ((rval = usba_device->usb_hcdi_ops->usba_hcdi_pipe_intr_xfer(ph_data,
req, usb_flags)) != USB_SUCCESS) {
/* the request failed, decrement the ref_count */
if (req->intr_completion_reason == USB_CR_OK) {
req->intr_completion_reason = usba_rval2cr(rval);
}
mutex_enter(&ph_data->p_mutex);
ASSERT(wrp->wr_done == B_FALSE);
ph_data->p_req_count--;
ASSERT(ph_data->p_req_count >= 0);
if ((ph_data->p_req_count == 0) &&
(usba_get_ph_state(ph_data) == USB_PIPE_STATE_ACTIVE)) {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_IDLE);
}
mutex_exit(&ph_data->p_mutex);
/* if sleep specified, wait for completion */
} else if (wrp_usb_flags & USBA_WRP_FLAGS_WAIT) {
rval = usba_pipe_sync_wait(ph_data, wrp);
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_intr_req: rval=0x%x", rval);
usba_release_ph_data(ph_data->p_ph_impl);
return (rval);
}
/*
* usba_pipe_sync_stop_intr_polling:
* - set up for sync transport, if necessary
* - request HCD to stop polling
* - wait for draining of all callbacks
*/
/*ARGSUSED*/
static int
usba_pipe_sync_stop_intr_polling(dev_info_t *dip,
usba_ph_impl_t *ph_impl,
usba_pipe_async_req_t *request,
usb_flags_t flags)
{
int rval;
usba_pipe_handle_data_t *ph_data;
usba_device_t *usba_device;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_intr_polling: flags=0x%x", flags);
ph_data = usba_get_ph_data((usb_pipe_handle_t)ph_impl);
if (ph_data == NULL) {
usba_release_ph_data(ph_impl);
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_intr_polling: pipe closed");
return (USB_INVALID_PIPE);
}
usba_device = ph_data->p_usba_device;
mutex_enter(&ph_data->p_mutex);
if (usba_get_ph_state(ph_data) == USB_PIPE_STATE_ERROR) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_intr_polling: pipe error");
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_impl);
return (USB_PIPE_ERROR);
}
if (usba_get_ph_state(ph_data) == USB_PIPE_STATE_IDLE) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_intr_polling: already idle");
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_impl);
return (USB_SUCCESS);
}
mutex_exit(&ph_data->p_mutex);
mutex_enter(&ph_impl->usba_ph_mutex);
ph_impl->usba_ph_state_changing++;
mutex_exit(&ph_impl->usba_ph_mutex);
flags |= USB_FLAGS_SLEEP;
for (;;) {
rval = usba_device->usb_hcdi_ops->
usba_hcdi_pipe_stop_intr_polling(ph_data, flags);
/*
* The host controller has stopped polling of the endpoint.
* Now, drain the callbacks if there are any on the callback
* queue.
*/
if (rval == USB_SUCCESS) {
mutex_enter(&ph_data->p_mutex);
/*
* there is a tiny window that the client driver
* may still have restarted the polling and we
* have to let the stop polling win)
*/
rval = usba_drain_cbs(ph_data, 0,
USB_CR_STOPPED_POLLING);
mutex_exit(&ph_data->p_mutex);
if (rval != USB_SUCCESS) {
continue;
}
}
break;
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_intr_polling: rval=0x%x", rval);
mutex_enter(&ph_impl->usba_ph_mutex);
ph_impl->usba_ph_state_changing--;
mutex_exit(&ph_impl->usba_ph_mutex);
usba_release_ph_data(ph_impl);
return (rval);
}
/*
* dummy callback function for stop polling
*/
static void
usba_dummy_callback(
usb_pipe_handle_t ph,
usb_opaque_t arg,
int rval,
usb_cb_flags_t flags)
{
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_dummy_callback: "
"ph=0x%p rval=0x%x flags=0x%x cb_arg=0x%p",
(void *)ph, rval, flags, (void *)arg);
}
/*
* usb_pipe_stop_intr_polling:
* stop polling for interrupt pipe IN data
* The HCD doesn't do a usba_hcdi_cb().
* It just returns success/failure
* Arguments:
* pipe_handle - pipe handle
* flags -
* USB_FLAGS_SLEEP: wait for completion
*/
void
usb_pipe_stop_intr_polling(usb_pipe_handle_t pipe_handle,
usb_flags_t flags)
{
usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle);
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_intr_polling: flags=0x%x", flags);
if (ph_data == NULL) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_intr_polling: pipe closed");
return;
}
if ((ph_data->p_ep.bmAttributes &
USB_EP_ATTR_MASK) != USB_EP_ATTR_INTR) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_intr_polling: wrong pipe type");
usba_release_ph_data(ph_data->p_ph_impl);
return;
}
if ((ph_data->p_ep.bEndpointAddress & USB_EP_DIR_IN) == 0) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_intr_polling: wrong pipe direction");
usba_release_ph_data(ph_data->p_ph_impl);
return;
}
if (servicing_interrupt() && (flags & USB_FLAGS_SLEEP)) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_intr_polling: invalid context");
usba_release_ph_data(ph_data->p_ph_impl);
return;
}
(void) usba_pipe_setup_func_call(ph_data->p_dip,
usba_pipe_sync_stop_intr_polling,
(usba_ph_impl_t *)pipe_handle, (usb_opaque_t)flags,
flags, usba_dummy_callback, NULL);
}
/*
* usb_alloc_isoc_req:
* - Allocate usb isochronous resources that includes usb isochronous
* request and array of packet descriptor structures and wrapper.
*
* Arguments:
* dip - dev_info_t of the client driver
* isoc_pkts_count - number of isoc_pkt_descr_t's
* len - length of "data" for this isochronous request
* flags -
* USB_FLAGS_SLEEP - Sleep if resources are not available
* no USB_FLAGS_SLEEP - Don't Sleep if resources are not available
*
* Return Values:
* usb_isoc_req_t on success, NULL on failure
*/
/*ARGSUSED*/
usb_isoc_req_t *
usb_alloc_isoc_req(dev_info_t *dip,
uint_t isoc_pkts_count,
size_t len,
usb_flags_t flags)
{
usb_isoc_req_t *isoc_req = NULL;
usba_req_wrapper_t *wrp;
size_t length = sizeof (*isoc_req) +
(sizeof (usb_isoc_pkt_descr_t) * isoc_pkts_count);
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_alloc_isoc_req: dip=0x%p pkt_cnt=%d len=%lu flags=0x%x",
(void *)dip, isoc_pkts_count, len, flags);
/* client needs to set isoc_pks_count */
if (dip && isoc_pkts_count) {
/* Allocate + Initialize the usba_req_wrapper_t structure */
if ((wrp = usba_req_wrapper_alloc(dip, length, flags)) !=
NULL) {
isoc_req = (usb_isoc_req_t *)USBA_WRP2ISOC_REQ(wrp);
/* Allocate the usb_isoc_req data mblk */
if (len) {
if ((isoc_req->isoc_data =
allocb(len, BPRI_HI)) == NULL) {
usba_req_wrapper_free(wrp);
isoc_req = NULL;
}
}
}
}
if (isoc_req) {
isoc_req->isoc_pkt_descr = (usb_isoc_pkt_descr_t *)
(((intptr_t)isoc_req) + (sizeof (usb_isoc_req_t)));
/* Initialize all the fields of usb isochronous request */
isoc_req->isoc_pkts_count = (ushort_t)isoc_pkts_count;
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_alloc_isoc_req: isoc_req = 0x%p", (void *)isoc_req);
return (isoc_req);
}
/*
* usba_hcdi_dup_isoc_req:
* create duplicate of isoc request
*
* Arguments:
* dip - devinfo pointer
* reqp - original request pointer
* len - length of "data" for this isoc request
* flags -
* USB_FLAGS_SLEEP - Sleep if resources are not available
*
* Return Values:
* usb_isoc_req_t on success, NULL on failure
*/
usb_isoc_req_t *
usba_hcdi_dup_isoc_req(
dev_info_t *dip,
usb_isoc_req_t *reqp,
usb_flags_t flags)
{
usb_isoc_req_t *isoc_reqp = NULL;
usba_req_wrapper_t *isoc_wrp, *req_wrp;
ushort_t count;
ushort_t isoc_pkts_count;
size_t length;
if (reqp == NULL) {
return (isoc_reqp);
}
isoc_pkts_count = reqp->isoc_pkts_count;
/* calculate total data length required in original request */
for (count = length = 0; count < isoc_pkts_count; count++) {
length += reqp->isoc_pkt_descr[count].isoc_pkt_length;
}
req_wrp = USBA_REQ2WRP(reqp);
if (((isoc_reqp = usb_alloc_isoc_req(dip,
isoc_pkts_count, length, flags)) != NULL)) {
isoc_reqp->isoc_frame_no = reqp->isoc_frame_no;
isoc_reqp->isoc_pkts_count = reqp->isoc_pkts_count;
isoc_reqp->isoc_pkts_length = reqp->isoc_pkts_length;
isoc_reqp->isoc_attributes = reqp->isoc_attributes;
isoc_reqp->isoc_client_private = reqp->isoc_client_private;
isoc_reqp->isoc_cb = reqp->isoc_cb;
isoc_reqp->isoc_exc_cb = reqp->isoc_exc_cb;
isoc_wrp = USBA_REQ2WRP(isoc_reqp);
isoc_wrp->wr_dip = req_wrp->wr_dip;
isoc_wrp->wr_ph_data = req_wrp->wr_ph_data;
isoc_wrp->wr_attrs = req_wrp->wr_attrs;
isoc_wrp->wr_usb_flags = req_wrp->wr_usb_flags;
for (count = 0; count < isoc_pkts_count; count++) {
isoc_reqp->isoc_pkt_descr[count].isoc_pkt_length =
reqp->isoc_pkt_descr[count].isoc_pkt_length;
}
}
return (isoc_reqp);
}
/*
* usb_free_isoc_req:
* - Deallocate usb isochronous resources that includes usb isochronous
* request and array of packet descriptor strcutures.
*
* Arguments:
* req - pointer to usb_isoc_req_t
*/
void
usb_free_isoc_req(usb_isoc_req_t *req)
{
if (req) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_free_isoc_req: req=0x%p", (void *)req);
if (req->isoc_data) {
freemsg(req->isoc_data);
}
usba_req_wrapper_free(USBA_REQ2WRP(req));
}
}
/*
* usb_get_current_frame_number:
* - request HCD to return current usb frame number
*
* Arguments:
* dip - pointer to dev_info_t
*
* Return Values:
* current_frame_number - request successfully executed
* 0 - request failed
*/
usb_frame_number_t
usb_get_current_frame_number(dev_info_t *dip)
{
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_get_current_frame_number: dip=0x%p", (void *)dip);
if (dip) {
usba_device_t *usba_device = usba_get_usba_device(dip);
usb_frame_number_t frame_number;
if (usba_device->usb_hcdi_ops->
usba_hcdi_get_current_frame_number) {
if (usba_device->usb_hcdi_ops->
usba_hcdi_get_current_frame_number(usba_device,
&frame_number) == USB_SUCCESS) {
return (frame_number);
}
}
}
return (0);
}
/*
* usb_get_max_isoc_pkts:
* - request HCD to return maximum isochronous packets per request
*
* Arguments:
* dip - pointer to dev_info_t
*
* Return Values:
* isoc_pkt - request successfully executed
* 0 - request failed
*/
uint_t
usb_get_max_isoc_pkts(dev_info_t *dip)
{
return (usb_get_max_pkts_per_isoc_request(dip));
}
uint_t
usb_get_max_pkts_per_isoc_request(dev_info_t *dip)
{
if (dip) {
usba_device_t *usba_device = usba_get_usba_device(dip);
uint_t max_isoc_pkts_per_request;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_get_max_isoc_pkts: usba_device=0x%p",
(void *)usba_device);
if (usba_device->usb_hcdi_ops->usba_hcdi_get_max_isoc_pkts) {
if (usba_device->usb_hcdi_ops->
usba_hcdi_get_max_isoc_pkts(usba_device,
&max_isoc_pkts_per_request) == USB_SUCCESS) {
return (max_isoc_pkts_per_request);
}
}
}
return (0);
}
/*
* usb_pipe_isoc_xfer:
* - check for pipe stalled
* - request HCD to transport isoc data asynchronously
*
* Arguments:
* pipe_handle - isoc pipe pipehandle (obtained via usb_pipe_open())
* req - isochronous request
*
* Return Values:
* USB_SUCCESS - request successfully executed
* USB_FAILURE - request failed
*/
int
usb_pipe_isoc_xfer(usb_pipe_handle_t pipe_handle,
usb_isoc_req_t *req,
usb_flags_t flags)
{
int rval;
usba_req_wrapper_t *wrp = USBA_REQ2WRP(req);
usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle);
usba_device_t *usba_device;
uchar_t direction;
usb_pipe_state_t pipe_state;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_isoc_xfer: flags=0x%x", flags);
if (ph_data == NULL) {
return (USB_INVALID_PIPE);
}
usba_device = ph_data->p_usba_device;
direction = ph_data->p_ep.bEndpointAddress & USB_EP_DIR_MASK;
mutex_enter(&ph_data->p_mutex);
if ((rval = usba_check_req(ph_data, (usb_opaque_t)req, flags,
USB_EP_ATTR_ISOCH)) != USB_SUCCESS) {
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
return (rval);
}
req->isoc_error_count = 0;
/* Get the current isoch pipe state */
pipe_state = usba_get_ph_state(ph_data);
switch (pipe_state) {
case USB_PIPE_STATE_IDLE:
usba_pipe_new_state(ph_data, USB_PIPE_STATE_ACTIVE);
break;
case USB_PIPE_STATE_ACTIVE:
if (direction == USB_EP_DIR_IN) {
USB_DPRINTF_L4(DPRINT_MASK_USBAI,
usbai_log_handle,
"usb_pipe_isoc_req: already polling");
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
return (USB_FAILURE);
}
break;
default:
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_isoc_req: pipe state %d", pipe_state);
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_data->p_ph_impl);
return (USB_PIPE_ERROR);
}
/* we accept the request */
ph_data->p_req_count++;
mutex_exit(&ph_data->p_mutex);
if ((rval = usba_device->usb_hcdi_ops->usba_hcdi_pipe_isoc_xfer(
ph_data, req, flags)) != USB_SUCCESS) {
if (req->isoc_completion_reason == USB_CR_OK) {
req->isoc_completion_reason = usba_rval2cr(rval);
}
mutex_enter(&ph_data->p_mutex);
ASSERT(wrp->wr_done == B_FALSE);
ph_data->p_req_count--;
if ((ph_data->p_req_count == 0) &&
(usba_get_ph_state(ph_data) == USB_PIPE_STATE_ACTIVE)) {
usba_pipe_new_state(ph_data, USB_PIPE_STATE_IDLE);
}
mutex_exit(&ph_data->p_mutex);
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usb_pipe_isoc_req: rval=%x", rval);
usba_release_ph_data(ph_data->p_ph_impl);
return (rval);
}
/*
* usba_pipe_sync_stop_isoc_polling:
* - set up for sync transport, if necessary
* - request HCD to stop polling
* - wait for draining of all callbacks
*
* Arguments:
* dip - dev_info pointer
* pipe_handle - pointer to pipe handle
* flags - USB_FLAGS_SLEEP: wait for completion
*/
/*ARGSUSED*/
static int
usba_pipe_sync_stop_isoc_polling(dev_info_t *dip,
usba_ph_impl_t *ph_impl,
usba_pipe_async_req_t *request,
usb_flags_t flags)
{
int rval;
usba_pipe_handle_data_t *ph_data = usba_get_ph_data(
(usb_pipe_handle_t)ph_impl);
usba_device_t *usba_device;
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_isoc_polling: uf=0x%x", flags);
if (ph_data == NULL) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_isoc_polling: pipe closed");
return (USB_INVALID_PIPE);
}
usba_device = ph_data->p_usba_device;
mutex_enter(&ph_data->p_mutex);
if (usba_get_ph_state(ph_data) == USB_PIPE_STATE_ERROR) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_isoc_polling: pipe error");
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_impl);
return (USB_PIPE_ERROR);
}
if (usba_get_ph_state(ph_data) == USB_PIPE_STATE_IDLE) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_isoc_polling: already stopped");
mutex_exit(&ph_data->p_mutex);
usba_release_ph_data(ph_impl);
return (USB_SUCCESS);
}
mutex_exit(&ph_data->p_mutex);
flags |= USB_FLAGS_SLEEP;
for (;;) {
rval = usba_device->usb_hcdi_ops->
usba_hcdi_pipe_stop_isoc_polling(ph_data, flags);
/*
* The host controller has stopped polling of the endpoint.
* Now, drain the callbacks if there are any on the callback
* queue.
*/
if (rval == USB_SUCCESS) {
mutex_enter(&ph_data->p_mutex);
/*
* there is a tiny window that the client driver
* may still have restarted the polling and we
* let the stop polling win
*/
rval = usba_drain_cbs(ph_data, 0,
USB_CR_STOPPED_POLLING);
mutex_exit(&ph_data->p_mutex);
if (rval != USB_SUCCESS) {
continue;
}
}
break;
}
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_sync_stop_isoc_polling: rval=0x%x", rval);
usba_release_ph_data(ph_impl);
return (rval);
}
/*
* usb_pipe_stop_isoc_polling:
* stop polling for isoc IN data
*
* Arguments:
* pipe_handle - pipe handle
* flags -
* USB_FLAGS_SLEEP: wait for completion
*/
void
usb_pipe_stop_isoc_polling(usb_pipe_handle_t pipe_handle,
usb_flags_t flags)
{
usba_pipe_handle_data_t *ph_data = usba_hold_ph_data(pipe_handle);
USB_DPRINTF_L4(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_isoc_polling: uf=0x%x", flags);
if (ph_data == NULL) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_isoc_polling: pipe closed");
return;
}
if ((ph_data->p_ep.bmAttributes & USB_EP_ATTR_MASK) !=
USB_EP_ATTR_ISOCH) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_isoc_polling: wrong pipe type");
usba_release_ph_data(ph_data->p_ph_impl);
return;
}
if ((ph_data->p_ep.bEndpointAddress & USB_EP_DIR_IN) == 0) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_isoc_polling: wrong pipe direction");
usba_release_ph_data(ph_data->p_ph_impl);
return;
}
if (servicing_interrupt() && (flags & USB_FLAGS_SLEEP)) {
USB_DPRINTF_L2(DPRINT_MASK_USBAI, usbai_log_handle,
"usba_pipe_stop_intr_polling: invalid context");
usba_release_ph_data(ph_data->p_ph_impl);
return;
}
(void) usba_pipe_setup_func_call(ph_data->p_dip,
usba_pipe_sync_stop_isoc_polling,
(usba_ph_impl_t *)pipe_handle, (usb_opaque_t)flags,
flags, usba_dummy_callback, NULL);
}