summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorweongyo <weongyo@FreeBSD.org>2009-03-18 02:38:35 +0000
committerweongyo <weongyo@FreeBSD.org>2009-03-18 02:38:35 +0000
commit7fabe111cbd4c1aa825b40730fb4647c65b44c75 (patch)
treef5626d73555fba83b35b098e9ba3484894783293
parentbcc40d445d09395afa286b5446648255d2cb5968 (diff)
downloadFreeBSD-src-7fabe111cbd4c1aa825b40730fb4647c65b44c75.zip
FreeBSD-src-7fabe111cbd4c1aa825b40730fb4647c65b44c75.tar.gz
Some NDIS USB drivers try to call URB funcs like URB_FUNCTION_VENDOR_xxx
or URB_FUNCTION_CLASS_xxx with HAL preemption lock that means it's non-sleepable during USB requests though usb2_do_request() requires a sleep so it needs to send queries to the default pipe without those interfaces to avoid sleep.
-rw-r--r--sys/compat/ndis/subr_usbd.c330
-rw-r--r--sys/dev/if_ndis/if_ndis_usb.c4
-rw-r--r--sys/dev/if_ndis/if_ndisvar.h4
3 files changed, 286 insertions, 52 deletions
diff --git a/sys/compat/ndis/subr_usbd.c b/sys/compat/ndis/subr_usbd.c
index dde87a4..926c4a1 100644
--- a/sys/compat/ndis/subr_usbd.c
+++ b/sys/compat/ndis/subr_usbd.c
@@ -77,6 +77,38 @@ __FBSDID("$FreeBSD$");
static driver_object usbd_driver;
static usb2_callback_t usbd_non_isoc_callback;
+static usb2_callback_t usbd_ctrl_callback;
+
+#define USBD_CTRL_READ_PIPE 0
+#define USBD_CTRL_WRITE_PIPE 1
+#define USBD_CTRL_MAX_PIPE 2
+#define USBD_CTRL_READ_BUFFER_SP 256
+#define USBD_CTRL_READ_BUFFER_SIZE \
+ (sizeof(struct usb2_device_request) + USBD_CTRL_READ_BUFFER_SP)
+#define USBD_CTRL_WRITE_BUFFER_SIZE \
+ (sizeof(struct usb2_device_request))
+static struct usb2_config usbd_default_epconfig[USBD_CTRL_MAX_PIPE] = {
+ [USBD_CTRL_READ_PIPE] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* control pipe */
+ .direction = UE_DIR_ANY,
+ .if_index = 0,
+ .mh.bufsize = USBD_CTRL_READ_BUFFER_SIZE,
+ .mh.flags = { .short_xfer_ok = 1, },
+ .mh.callback = &usbd_ctrl_callback,
+ .mh.timeout = 5000, /* 5 seconds */
+ },
+ [USBD_CTRL_WRITE_PIPE] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* control pipe */
+ .direction = UE_DIR_ANY,
+ .if_index = 0,
+ .mh.bufsize = USBD_CTRL_WRITE_BUFFER_SIZE,
+ .mh.flags = { .proxy_buffer = 1, },
+ .mh.callback = &usbd_ctrl_callback,
+ .mh.timeout = 5000, /* 5 seconds */
+ }
+};
static int32_t usbd_func_bulkintr(irp *);
static int32_t usbd_func_vendorclass(irp *);
@@ -84,6 +116,9 @@ static int32_t usbd_func_selconf(irp *);
static int32_t usbd_func_abort_pipe(irp *);
static usb2_error_t usbd_setup_endpoint(irp *, uint8_t,
struct usb2_endpoint_descriptor *);
+static usb2_error_t usbd_setup_endpoint_default(irp *, uint8_t);
+static usb2_error_t usbd_setup_endpoint_one(irp *, uint8_t,
+ struct ndisusb_ep *, struct usb2_config *);
static int32_t usbd_func_getdesc(irp *);
static union usbd_urb *usbd_geturb(irp *);
static struct ndisusb_ep*usbd_get_ndisep(irp *, usb_endpoint_descriptor_t *);
@@ -558,6 +593,57 @@ usbd_func_selconf(ip)
}
static usb2_error_t
+usbd_setup_endpoint_one(ip, ifidx, ne, epconf)
+ irp *ip;
+ uint8_t ifidx;
+ struct ndisusb_ep *ne;
+ struct usb2_config *epconf;
+{
+ device_t dev = IRP_NDIS_DEV(ip);
+ struct ndis_softc *sc = device_get_softc(dev);
+ struct usb2_xfer *xfer;
+ usb2_error_t status;
+
+ InitializeListHead(&ne->ne_active);
+ InitializeListHead(&ne->ne_pending);
+ KeInitializeSpinLock(&ne->ne_lock);
+
+ status = usb2_transfer_setup(sc->ndisusb_dev, &ifidx, ne->ne_xfer,
+ epconf, 1, sc, &sc->ndisusb_mtx);
+ if (status != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(dev, "couldn't setup xfer: %s\n",
+ usb2_errstr(status));
+ return (status);
+ }
+ xfer = ne->ne_xfer[0];
+ xfer->priv_fifo = ne;
+
+ return (status);
+}
+
+static usb2_error_t
+usbd_setup_endpoint_default(ip, ifidx)
+ irp *ip;
+ uint8_t ifidx;
+{
+ device_t dev = IRP_NDIS_DEV(ip);
+ struct ndis_softc *sc = device_get_softc(dev);
+ usb2_error_t status;
+
+ if (ifidx > 0)
+ device_printf(dev, "warning: ifidx > 0 isn't supported.\n");
+
+ status = usbd_setup_endpoint_one(ip, ifidx, &sc->ndisusb_dread_ep,
+ &usbd_default_epconfig[USBD_CTRL_READ_PIPE]);
+ if (status != USB_ERR_NORMAL_COMPLETION)
+ return (status);
+
+ status = usbd_setup_endpoint_one(ip, ifidx, &sc->ndisusb_dwrite_ep,
+ &usbd_default_epconfig[USBD_CTRL_WRITE_PIPE]);
+ return (status);
+}
+
+static usb2_error_t
usbd_setup_endpoint(ip, ifidx, ep)
irp *ip;
uint8_t ifidx;
@@ -644,62 +730,54 @@ usbd_func_vendorclass(ip)
irp *ip;
{
device_t dev = IRP_NDIS_DEV(ip);
+ int32_t error;
struct ndis_softc *sc = device_get_softc(dev);
+ struct ndisusb_ep *ne;
+ struct ndisusb_xfer *nx;
struct usbd_urb_vendor_or_class_request *vcreq;
- uint8_t type = 0;
union usbd_urb *urb;
- struct usb2_device_request req;
- usb2_error_t status;
+
+ if (!(sc->ndisusb_status & NDISUSB_STATUS_SETUP_EP)) {
+ /*
+ * XXX In some cases the interface number isn't 0. However
+ * some driver (eg. RTL8187L NDIS driver) calls this function
+ * before calling URB_FUNCTION_SELECT_CONFIGURATION.
+ */
+ error = usbd_setup_endpoint_default(ip, 0);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return usbd_usb2urb(error);
+ sc->ndisusb_status |= NDISUSB_STATUS_SETUP_EP;
+ }
urb = usbd_geturb(ip);
vcreq = &urb->uu_vcreq;
+ ne = (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) ?
+ &sc->ndisusb_dread_ep : &sc->ndisusb_dwrite_ep;
+ IRP_NDISUSB_EP(ip) = ne;
+ ip->irp_cancelfunc = (cancel_func)usbd_irpcancel_wrap;
- switch (urb->uu_hdr.uuh_func) {
- case URB_FUNCTION_CLASS_DEVICE:
- type = UT_CLASS | UT_DEVICE;
- break;
- case URB_FUNCTION_CLASS_INTERFACE:
- type = UT_CLASS | UT_INTERFACE;
- break;
- case URB_FUNCTION_CLASS_OTHER:
- type = UT_CLASS | UT_OTHER;
- break;
- case URB_FUNCTION_CLASS_ENDPOINT:
- type = UT_CLASS | UT_ENDPOINT;
- break;
- case URB_FUNCTION_VENDOR_DEVICE:
- type = UT_VENDOR | UT_DEVICE;
- break;
- case URB_FUNCTION_VENDOR_INTERFACE:
- type = UT_VENDOR | UT_INTERFACE;
- break;
- case URB_FUNCTION_VENDOR_OTHER:
- type = UT_VENDOR | UT_OTHER;
- break;
- case URB_FUNCTION_VENDOR_ENDPOINT:
- type = UT_VENDOR | UT_ENDPOINT;
- break;
- default:
- /* never reached. */
- break;
+ nx = malloc(sizeof(struct ndisusb_xfer), M_USBDEV, M_NOWAIT | M_ZERO);
+ if (nx == NULL) {
+ device_printf(IRP_NDIS_DEV(ip), "out of memory\n");
+ return (USBD_STATUS_NO_MEMORY);
}
+ nx->nx_ep = ne;
+ nx->nx_priv = ip;
+ KeAcquireSpinLockAtDpcLevel(&ne->ne_lock);
+ InsertTailList((&ne->ne_pending), (&nx->nx_next));
+ KeReleaseSpinLockFromDpcLevel(&ne->ne_lock);
- type |= (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) ?
- UT_READ : UT_WRITE;
- type |= vcreq->uvc_reserved1;
-
- req.bmRequestType = type;
- req.bRequest = vcreq->uvc_req;
- USETW(req.wIndex, vcreq->uvc_idx);
- USETW(req.wValue, vcreq->uvc_value);
- USETW(req.wLength, vcreq->uvc_trans_buflen);
+ /* we've done to setup xfer. Let's transfer it. */
+ ip->irp_iostat.isb_status = STATUS_PENDING;
+ ip->irp_iostat.isb_info = 0;
+ USBD_URB_STATUS(urb) = USBD_STATUS_PENDING;
+ IoMarkIrpPending(ip);
- NDISUSB_LOCK(sc);
- status = usb2_do_request(sc->ndisusb_dev, &sc->ndisusb_mtx, &req,
- vcreq->uvc_trans_buf);
- NDISUSB_UNLOCK(sc);
+ error = usbd_taskadd(ip, NDISUSB_TASK_VENDOR);
+ if (error != USBD_STATUS_SUCCESS)
+ return (error);
- return usbd_usb2urb(status);
+ return (USBD_STATUS_PENDING);
}
static void
@@ -872,6 +950,146 @@ extra:
}
}
+static void
+usbd_ctrl_callback(struct usb2_xfer *xfer)
+{
+ irp *ip;
+ struct ndis_softc *sc = xfer->priv_sc;
+ struct ndisusb_ep *ne = xfer->priv_fifo;
+ struct ndisusb_xfer *nx;
+ uint8_t irql;
+ union usbd_urb *urb;
+ struct usbd_urb_vendor_or_class_request *vcreq;
+ uint8_t type = 0;
+ struct usb2_device_request req;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ nx = usbd_aq_getfirst(sc, ne);
+ if (nx == NULL)
+ return;
+
+ ip = nx->nx_priv;
+ urb = usbd_geturb(ip);
+ vcreq = &urb->uu_vcreq;
+
+ if (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) {
+ usb2_copy_out(xfer->frbuffers + 1, 0,
+ vcreq->uvc_trans_buf, xfer->frlengths[1]);
+ nx->nx_urbactlen += xfer->frlengths[1];
+ }
+
+ usbd_xfer_complete(sc, ne, nx, USB_ERR_NORMAL_COMPLETION);
+ /* fall through */
+ case USB_ST_SETUP:
+next:
+ /* get next transfer */
+ KeAcquireSpinLock(&ne->ne_lock, &irql);
+ if (IsListEmpty(&ne->ne_pending)) {
+ KeReleaseSpinLock(&ne->ne_lock, irql);
+ return;
+ }
+ nx = CONTAINING_RECORD(ne->ne_pending.nle_flink,
+ struct ndisusb_xfer, nx_next);
+ RemoveEntryList(&nx->nx_next);
+ /* add a entry to the active queue's tail. */
+ InsertTailList((&ne->ne_active), (&nx->nx_next));
+ KeReleaseSpinLock(&ne->ne_lock, irql);
+
+ ip = nx->nx_priv;
+ urb = usbd_geturb(ip);
+ vcreq = &urb->uu_vcreq;
+
+ switch (urb->uu_hdr.uuh_func) {
+ case URB_FUNCTION_CLASS_DEVICE:
+ type = UT_CLASS | UT_DEVICE;
+ break;
+ case URB_FUNCTION_CLASS_INTERFACE:
+ type = UT_CLASS | UT_INTERFACE;
+ break;
+ case URB_FUNCTION_CLASS_OTHER:
+ type = UT_CLASS | UT_OTHER;
+ break;
+ case URB_FUNCTION_CLASS_ENDPOINT:
+ type = UT_CLASS | UT_ENDPOINT;
+ break;
+ case URB_FUNCTION_VENDOR_DEVICE:
+ type = UT_VENDOR | UT_DEVICE;
+ break;
+ case URB_FUNCTION_VENDOR_INTERFACE:
+ type = UT_VENDOR | UT_INTERFACE;
+ break;
+ case URB_FUNCTION_VENDOR_OTHER:
+ type = UT_VENDOR | UT_OTHER;
+ break;
+ case URB_FUNCTION_VENDOR_ENDPOINT:
+ type = UT_VENDOR | UT_ENDPOINT;
+ break;
+ default:
+ /* never reached. */
+ break;
+ }
+
+ type |= (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) ?
+ UT_READ : UT_WRITE;
+ type |= vcreq->uvc_reserved1;
+
+ req.bmRequestType = type;
+ req.bRequest = vcreq->uvc_req;
+ USETW(req.wIndex, vcreq->uvc_idx);
+ USETW(req.wValue, vcreq->uvc_value);
+ USETW(req.wLength, vcreq->uvc_trans_buflen);
+
+ nx->nx_urbbuf = vcreq->uvc_trans_buf;
+ nx->nx_urblen = vcreq->uvc_trans_buflen;
+ nx->nx_urbactlen = 0;
+
+ usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
+ xfer->frlengths[0] = sizeof(req);
+ xfer->nframes = 1;
+ if (vcreq->uvc_trans_flags & USBD_TRANSFER_DIRECTION_IN) {
+ if (vcreq->uvc_trans_buflen >= USBD_CTRL_READ_BUFFER_SP)
+ device_printf(sc->ndis_dev,
+ "warning: not enough buffer space (%d).\n",
+ vcreq->uvc_trans_buflen);
+ xfer->frlengths[1] = MIN(xfer->max_data_length,
+ vcreq->uvc_trans_buflen);
+ xfer->nframes = 2;
+ } else {
+ if (nx->nx_urblen > 0)
+ device_printf(sc->ndis_dev,
+ "warning: not enough write buffer space"
+ " (%d).\n", nx->nx_urblen);
+ /*
+ * XXX with my local tests there was no cases to require
+ * a extra buffer until now but it'd need to update in
+ * the future if it needs to be.
+ */
+ if (nx->nx_urblen > 0) {
+ usb2_copy_in(xfer->frbuffers + 1 , 0,
+ nx->nx_urbbuf, nx->nx_urblen);
+ xfer->frlengths[1] = nx->nx_urblen;
+ xfer->nframes = 2;
+ }
+ }
+ usb2_start_hardware(xfer);
+ break;
+ default:
+ nx = usbd_aq_getfirst(sc, ne);
+ if (nx == NULL)
+ return;
+ if (xfer->error != USB_ERR_CANCELLED) {
+ xfer->flags.stall_pipe = 1;
+ device_printf(sc->ndis_dev, "usb xfer warning (%s)\n",
+ usb2_errstr(xfer->error));
+ }
+ usbd_xfer_complete(sc, ne, nx, xfer->error);
+ if (xfer->error != USB_ERR_CANCELLED)
+ goto next;
+ break;
+ }
+}
+
static struct ndisusb_ep *
usbd_get_ndisep(ip, ep)
irp *ip;
@@ -902,6 +1120,7 @@ usbd_xfertask(dobj, arg)
struct ndisusb_xferdone *nd;
struct ndisusb_xfer *nq;
struct usbd_urb_bulk_or_intr_transfer *ubi;
+ struct usbd_urb_vendor_or_class_request *vcreq;
union usbd_urb *urb;
usb2_error_t status;
void *priv;
@@ -922,18 +1141,19 @@ usbd_xfertask(dobj, arg)
ip = priv;
urb = usbd_geturb(ip);
- KASSERT(urb->uu_hdr.uuh_func ==
- URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER,
- ("function(%d) isn't for bulk or interrupt",
- urb->uu_hdr.uuh_func));
-
ip->irp_cancelfunc = NULL;
IRP_NDISUSB_EP(ip) = NULL;
switch (status) {
case USB_ERR_NORMAL_COMPLETION:
- ubi = &urb->uu_bulkintr;
- ubi->ubi_trans_buflen = nq->nx_urbactlen;
+ if (urb->uu_hdr.uuh_func ==
+ URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER) {
+ ubi = &urb->uu_bulkintr;
+ ubi->ubi_trans_buflen = nq->nx_urbactlen;
+ } else {
+ vcreq = &urb->uu_vcreq;
+ vcreq->uvc_trans_buflen = nq->nx_urbactlen;
+ }
ip->irp_iostat.isb_info = nq->nx_urbactlen;
ip->irp_iostat.isb_status = STATUS_SUCCESS;
USBD_URB_STATUS(urb) = USBD_STATUS_SUCCESS;
@@ -1037,6 +1257,12 @@ usbd_task(dobj, arg)
usb2_transfer_stop(ne->ne_xfer[0]);
usb2_transfer_start(ne->ne_xfer[0]);
break;
+ case NDISUSB_TASK_VENDOR:
+ ne = (urb->uu_vcreq.uvc_trans_flags &
+ USBD_TRANSFER_DIRECTION_IN) ?
+ &sc->ndisusb_dread_ep : &sc->ndisusb_dwrite_ep;
+ usb2_transfer_start(ne->ne_xfer[0]);
+ break;
default:
break;
}
diff --git a/sys/dev/if_ndis/if_ndis_usb.c b/sys/dev/if_ndis/if_ndis_usb.c
index 7aeed9a..ec9741b 100644
--- a/sys/dev/if_ndis/if_ndis_usb.c
+++ b/sys/dev/if_ndis/if_ndis_usb.c
@@ -210,6 +210,10 @@ ndisusb_detach(device_t self)
ndis_pnpevent_nic(self, NDIS_PNP_EVENT_SURPRISE_REMOVED);
+ if (sc->ndisusb_status & NDISUSB_STATUS_SETUP_EP) {
+ usb2_transfer_unsetup(sc->ndisusb_dread_ep.ne_xfer, 1);
+ usb2_transfer_unsetup(sc->ndisusb_dwrite_ep.ne_xfer, 1);
+ }
for (i = 0; i < NDISUSB_ENDPT_MAX; i++) {
ne = &sc->ndisusb_ep[i];
usb2_transfer_unsetup(ne->ne_xfer, 1);
diff --git a/sys/dev/if_ndis/if_ndisvar.h b/sys/dev/if_ndis/if_ndisvar.h
index c86b267..6db48a6 100644
--- a/sys/dev/if_ndis/if_ndisvar.h
+++ b/sys/dev/if_ndis/if_ndisvar.h
@@ -146,6 +146,7 @@ struct ndisusb_task {
unsigned nt_type;
#define NDISUSB_TASK_TSTART 0
#define NDISUSB_TASK_IRPCANCEL 1
+#define NDISUSB_TASK_VENDOR 2
void *nt_ctx;
list_entry nt_tasklist;
};
@@ -229,6 +230,8 @@ struct ndis_softc {
struct usb2_device *ndisusb_dev;
struct mtx ndisusb_mtx;
+ struct ndisusb_ep ndisusb_dread_ep;
+ struct ndisusb_ep ndisusb_dwrite_ep;
#define NDISUSB_GET_ENDPT(addr) \
((UE_GET_DIR(addr) >> 7) | (UE_GET_ADDR(addr) << 1))
#define NDISUSB_ENDPT_MAX ((UE_ADDR + 1) * 2)
@@ -241,6 +244,7 @@ struct ndis_softc {
kspin_lock ndisusb_tasklock;
int ndisusb_status;
#define NDISUSB_STATUS_DETACH 0x1
+#define NDISUSB_STATUS_SETUP_EP 0x2
};
#define NDIS_LOCK(_sc) mtx_lock(&(_sc)->ndis_mtx)
OpenPOWER on IntegriCloud