xref: /freebsd/sys/dev/usb/usb_dev.c (revision 5c8c627bf9260f9d4175ea416f0d52f322cb2dd3)
102ac6454SAndrew Thompson /* $FreeBSD$ */
202ac6454SAndrew Thompson /*-
302ac6454SAndrew Thompson  * Copyright (c) 2006-2008 Hans Petter Selasky. All rights reserved.
402ac6454SAndrew Thompson  *
502ac6454SAndrew Thompson  * Redistribution and use in source and binary forms, with or without
602ac6454SAndrew Thompson  * modification, are permitted provided that the following conditions
702ac6454SAndrew Thompson  * are met:
802ac6454SAndrew Thompson  * 1. Redistributions of source code must retain the above copyright
902ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer.
1002ac6454SAndrew Thompson  * 2. Redistributions in binary form must reproduce the above copyright
1102ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer in the
1202ac6454SAndrew Thompson  *    documentation and/or other materials provided with the distribution.
1302ac6454SAndrew Thompson  *
1402ac6454SAndrew Thompson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1502ac6454SAndrew Thompson  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1602ac6454SAndrew Thompson  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1702ac6454SAndrew Thompson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1802ac6454SAndrew Thompson  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1902ac6454SAndrew Thompson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2002ac6454SAndrew Thompson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2102ac6454SAndrew Thompson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2202ac6454SAndrew Thompson  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2302ac6454SAndrew Thompson  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2402ac6454SAndrew Thompson  * SUCH DAMAGE.
2502ac6454SAndrew Thompson  *
2602ac6454SAndrew Thompson  *
27a593f6b8SAndrew Thompson  * usb_dev.c - An abstraction layer for creating devices under /dev/...
2802ac6454SAndrew Thompson  */
2902ac6454SAndrew Thompson 
30d2b99310SHans Petter Selasky #ifdef USB_GLOBAL_INCLUDE_FILE
31d2b99310SHans Petter Selasky #include USB_GLOBAL_INCLUDE_FILE
32d2b99310SHans Petter Selasky #else
33ed6d949aSAndrew Thompson #include <sys/stdint.h>
34ed6d949aSAndrew Thompson #include <sys/stddef.h>
35ed6d949aSAndrew Thompson #include <sys/param.h>
36ed6d949aSAndrew Thompson #include <sys/queue.h>
37ed6d949aSAndrew Thompson #include <sys/types.h>
38ed6d949aSAndrew Thompson #include <sys/systm.h>
39ed6d949aSAndrew Thompson #include <sys/kernel.h>
40ed6d949aSAndrew Thompson #include <sys/bus.h>
41ed6d949aSAndrew Thompson #include <sys/module.h>
42ed6d949aSAndrew Thompson #include <sys/lock.h>
43ed6d949aSAndrew Thompson #include <sys/mutex.h>
44ed6d949aSAndrew Thompson #include <sys/condvar.h>
45ed6d949aSAndrew Thompson #include <sys/sysctl.h>
46ed6d949aSAndrew Thompson #include <sys/sx.h>
47ed6d949aSAndrew Thompson #include <sys/unistd.h>
48ed6d949aSAndrew Thompson #include <sys/callout.h>
49ed6d949aSAndrew Thompson #include <sys/malloc.h>
50ed6d949aSAndrew Thompson #include <sys/priv.h>
51ed6d949aSAndrew Thompson #include <sys/vnode.h>
52ed6d949aSAndrew Thompson #include <sys/conf.h>
53ed6d949aSAndrew Thompson #include <sys/fcntl.h>
54ed6d949aSAndrew Thompson 
5502ac6454SAndrew Thompson #include <dev/usb/usb.h>
5602ac6454SAndrew Thompson #include <dev/usb/usb_ioctl.h>
57ed6d949aSAndrew Thompson #include <dev/usb/usbdi.h>
58ed6d949aSAndrew Thompson #include <dev/usb/usbdi_util.h>
5902ac6454SAndrew Thompson 
60a593f6b8SAndrew Thompson #define	USB_DEBUG_VAR usb_fifo_debug
6102ac6454SAndrew Thompson 
6202ac6454SAndrew Thompson #include <dev/usb/usb_core.h>
6302ac6454SAndrew Thompson #include <dev/usb/usb_dev.h>
64ed6d949aSAndrew Thompson #include <dev/usb/usb_mbuf.h>
6502ac6454SAndrew Thompson #include <dev/usb/usb_process.h>
6602ac6454SAndrew Thompson #include <dev/usb/usb_device.h>
6702ac6454SAndrew Thompson #include <dev/usb/usb_debug.h>
6802ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
6902ac6454SAndrew Thompson #include <dev/usb/usb_generic.h>
7002ac6454SAndrew Thompson #include <dev/usb/usb_dynamic.h>
7102ac6454SAndrew Thompson #include <dev/usb/usb_util.h>
7202ac6454SAndrew Thompson 
7302ac6454SAndrew Thompson #include <dev/usb/usb_controller.h>
7402ac6454SAndrew Thompson #include <dev/usb/usb_bus.h>
7502ac6454SAndrew Thompson 
7602ac6454SAndrew Thompson #include <sys/filio.h>
7702ac6454SAndrew Thompson #include <sys/ttycom.h>
7802ac6454SAndrew Thompson #include <sys/syscallsubr.h>
7902ac6454SAndrew Thompson 
8002ac6454SAndrew Thompson #include <machine/stdarg.h>
81d2b99310SHans Petter Selasky #endif			/* USB_GLOBAL_INCLUDE_FILE */
8202ac6454SAndrew Thompson 
838755859aSAndrew Thompson #if USB_HAVE_UGEN
848755859aSAndrew Thompson 
85ed6d949aSAndrew Thompson #ifdef USB_DEBUG
86a593f6b8SAndrew Thompson static int usb_fifo_debug = 0;
8702ac6454SAndrew Thompson 
886472ac3dSEd Schouten static SYSCTL_NODE(_hw_usb, OID_AUTO, dev, CTLFLAG_RW, 0, "USB device");
8983cadd7dSHans Petter Selasky SYSCTL_INT(_hw_usb_dev, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN,
90a593f6b8SAndrew Thompson     &usb_fifo_debug, 0, "Debug Level");
91c13fd8d4SAndrew Thompson TUNABLE_INT("hw.usb.dev.debug", &usb_fifo_debug);
9202ac6454SAndrew Thompson #endif
9302ac6454SAndrew Thompson 
9402ac6454SAndrew Thompson #if ((__FreeBSD_version >= 700001) || (__FreeBSD_version == 0) || \
9502ac6454SAndrew Thompson      ((__FreeBSD_version >= 600034) && (__FreeBSD_version < 700000)))
9602ac6454SAndrew Thompson #define	USB_UCRED struct ucred *ucred,
9702ac6454SAndrew Thompson #else
9802ac6454SAndrew Thompson #define	USB_UCRED
9902ac6454SAndrew Thompson #endif
10002ac6454SAndrew Thompson 
10102ac6454SAndrew Thompson /* prototypes */
10202ac6454SAndrew Thompson 
103a593f6b8SAndrew Thompson static int	usb_fifo_open(struct usb_cdev_privdata *,
104760bc48eSAndrew Thompson 		    struct usb_fifo *, int);
105a593f6b8SAndrew Thompson static void	usb_fifo_close(struct usb_fifo *, int);
106a593f6b8SAndrew Thompson static void	usb_dev_init(void *);
107a593f6b8SAndrew Thompson static void	usb_dev_init_post(void *);
108a593f6b8SAndrew Thompson static void	usb_dev_uninit(void *);
109a593f6b8SAndrew Thompson static int	usb_fifo_uiomove(struct usb_fifo *, void *, int,
11002ac6454SAndrew Thompson 		    struct uio *);
111a593f6b8SAndrew Thompson static void	usb_fifo_check_methods(struct usb_fifo_methods *);
112a593f6b8SAndrew Thompson static struct	usb_fifo *usb_fifo_alloc(void);
113a593f6b8SAndrew Thompson static struct	usb_endpoint *usb_dev_get_ep(struct usb_device *, uint8_t,
114f5f145baSAndrew Thompson 		    uint8_t);
115a593f6b8SAndrew Thompson static void	usb_loc_fill(struct usb_fs_privdata *,
116760bc48eSAndrew Thompson 		    struct usb_cdev_privdata *);
117a593f6b8SAndrew Thompson static void	usb_close(void *);
118a593f6b8SAndrew Thompson static usb_error_t usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *, int);
119a593f6b8SAndrew Thompson static usb_error_t usb_usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *);
120a593f6b8SAndrew Thompson static void	usb_unref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *);
12102ac6454SAndrew Thompson 
122a593f6b8SAndrew Thompson static d_open_t usb_open;
123a593f6b8SAndrew Thompson static d_ioctl_t usb_ioctl;
124a593f6b8SAndrew Thompson static d_read_t usb_read;
125a593f6b8SAndrew Thompson static d_write_t usb_write;
126a593f6b8SAndrew Thompson static d_poll_t usb_poll;
12702ac6454SAndrew Thompson 
128a593f6b8SAndrew Thompson static d_ioctl_t usb_static_ioctl;
12902ac6454SAndrew Thompson 
130a593f6b8SAndrew Thompson static usb_fifo_open_t usb_fifo_dummy_open;
131a593f6b8SAndrew Thompson static usb_fifo_close_t usb_fifo_dummy_close;
132a593f6b8SAndrew Thompson static usb_fifo_ioctl_t usb_fifo_dummy_ioctl;
133a593f6b8SAndrew Thompson static usb_fifo_cmd_t usb_fifo_dummy_cmd;
13402ac6454SAndrew Thompson 
135ee3e3ff5SAndrew Thompson /* character device structure used for devices (/dev/ugenX.Y and /dev/uXXX) */
136a593f6b8SAndrew Thompson struct cdevsw usb_devsw = {
13702ac6454SAndrew Thompson 	.d_version = D_VERSION,
138a593f6b8SAndrew Thompson 	.d_open = usb_open,
139a593f6b8SAndrew Thompson 	.d_ioctl = usb_ioctl,
140ee3e3ff5SAndrew Thompson 	.d_name = "usbdev",
14102ac6454SAndrew Thompson 	.d_flags = D_TRACKCLOSE,
142a593f6b8SAndrew Thompson 	.d_read = usb_read,
143a593f6b8SAndrew Thompson 	.d_write = usb_write,
144a593f6b8SAndrew Thompson 	.d_poll = usb_poll
14502ac6454SAndrew Thompson };
14602ac6454SAndrew Thompson 
147a593f6b8SAndrew Thompson static struct cdev* usb_dev = NULL;
148ee3e3ff5SAndrew Thompson 
149ee3e3ff5SAndrew Thompson /* character device structure used for /dev/usb */
150a593f6b8SAndrew Thompson static struct cdevsw usb_static_devsw = {
151ee3e3ff5SAndrew Thompson 	.d_version = D_VERSION,
152a593f6b8SAndrew Thompson 	.d_ioctl = usb_static_ioctl,
153ee3e3ff5SAndrew Thompson 	.d_name = "usb"
15402ac6454SAndrew Thompson };
15502ac6454SAndrew Thompson 
156a593f6b8SAndrew Thompson static TAILQ_HEAD(, usb_symlink) usb_sym_head;
157a593f6b8SAndrew Thompson static struct sx usb_sym_lock;
15802ac6454SAndrew Thompson 
159a593f6b8SAndrew Thompson struct mtx usb_ref_lock;
16002ac6454SAndrew Thompson 
16102ac6454SAndrew Thompson /*------------------------------------------------------------------------*
162a593f6b8SAndrew Thompson  *	usb_loc_fill
16302ac6454SAndrew Thompson  *
164760bc48eSAndrew Thompson  * This is used to fill out a usb_cdev_privdata structure based on the
165760bc48eSAndrew Thompson  * device's address as contained in usb_fs_privdata.
16602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
167ee3e3ff5SAndrew Thompson static void
168a593f6b8SAndrew Thompson usb_loc_fill(struct usb_fs_privdata* pd, struct usb_cdev_privdata *cpd)
16902ac6454SAndrew Thompson {
170ee3e3ff5SAndrew Thompson 	cpd->bus_index = pd->bus_index;
171ee3e3ff5SAndrew Thompson 	cpd->dev_index = pd->dev_index;
172ee3e3ff5SAndrew Thompson 	cpd->ep_addr = pd->ep_addr;
173ee3e3ff5SAndrew Thompson 	cpd->fifo_index = pd->fifo_index;
17402ac6454SAndrew Thompson }
17502ac6454SAndrew Thompson 
17602ac6454SAndrew Thompson /*------------------------------------------------------------------------*
177a593f6b8SAndrew Thompson  *	usb_ref_device
17802ac6454SAndrew Thompson  *
17902ac6454SAndrew Thompson  * This function is used to atomically refer an USB device by its
18002ac6454SAndrew Thompson  * device location. If this function returns success the USB device
18102ac6454SAndrew Thompson  * will not dissappear until the USB device is unreferenced.
18202ac6454SAndrew Thompson  *
18302ac6454SAndrew Thompson  * Return values:
18402ac6454SAndrew Thompson  *  0: Success, refcount incremented on the given USB device.
18502ac6454SAndrew Thompson  *  Else: Failure.
18602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
1878019e7e7SAndrew Thompson static usb_error_t
188a593f6b8SAndrew Thompson usb_ref_device(struct usb_cdev_privdata *cpd,
189e13e19faSAndrew Thompson     struct usb_cdev_refdata *crd, int need_uref)
19002ac6454SAndrew Thompson {
191760bc48eSAndrew Thompson 	struct usb_fifo **ppf;
192760bc48eSAndrew Thompson 	struct usb_fifo *f;
19302ac6454SAndrew Thompson 
194e13e19faSAndrew Thompson 	DPRINTFN(2, "cpd=%p need uref=%d\n", cpd, need_uref);
195e13e19faSAndrew Thompson 
196e13e19faSAndrew Thompson 	/* clear all refs */
197e13e19faSAndrew Thompson 	memset(crd, 0, sizeof(*crd));
19802ac6454SAndrew Thompson 
199a593f6b8SAndrew Thompson 	mtx_lock(&usb_ref_lock);
200a593f6b8SAndrew Thompson 	cpd->bus = devclass_get_softc(usb_devclass_ptr, cpd->bus_index);
201ee3e3ff5SAndrew Thompson 	if (cpd->bus == NULL) {
202ee3e3ff5SAndrew Thompson 		DPRINTFN(2, "no bus at %u\n", cpd->bus_index);
20302ac6454SAndrew Thompson 		goto error;
20402ac6454SAndrew Thompson 	}
205ee3e3ff5SAndrew Thompson 	cpd->udev = cpd->bus->devices[cpd->dev_index];
206ee3e3ff5SAndrew Thompson 	if (cpd->udev == NULL) {
207ee3e3ff5SAndrew Thompson 		DPRINTFN(2, "no device at %u\n", cpd->dev_index);
20802ac6454SAndrew Thompson 		goto error;
20902ac6454SAndrew Thompson 	}
210f97da128SHans Petter Selasky 	if (cpd->udev->state == USB_STATE_DETACHED &&
211f97da128SHans Petter Selasky 	    (need_uref != 2)) {
212f97da128SHans Petter Selasky 		DPRINTFN(2, "device is detached\n");
213f97da128SHans Petter Selasky 		goto error;
214f97da128SHans Petter Selasky 	}
215ee3e3ff5SAndrew Thompson 	if (cpd->udev->refcount == USB_DEV_REF_MAX) {
21602ac6454SAndrew Thompson 		DPRINTFN(2, "no dev ref\n");
21702ac6454SAndrew Thompson 		goto error;
21802ac6454SAndrew Thompson 	}
2190ed53d45SAndrew Thompson 	if (need_uref) {
2200ed53d45SAndrew Thompson 		DPRINTFN(2, "ref udev - needed\n");
2210ed53d45SAndrew Thompson 		cpd->udev->refcount++;
2220ed53d45SAndrew Thompson 
223a593f6b8SAndrew Thompson 		mtx_unlock(&usb_ref_lock);
2240ed53d45SAndrew Thompson 
2250ed53d45SAndrew Thompson 		/*
226a18a7a41SHans Petter Selasky 		 * We need to grab the enumeration SX-lock before
227a18a7a41SHans Petter Selasky 		 * grabbing the FIFO refs to avoid deadlock at detach!
2280ed53d45SAndrew Thompson 		 */
229a18a7a41SHans Petter Selasky 		crd->do_unlock = usbd_enum_lock(cpd->udev);
2300ed53d45SAndrew Thompson 
231a593f6b8SAndrew Thompson 		mtx_lock(&usb_ref_lock);
232a488edb5SAndrew Thompson 
233a488edb5SAndrew Thompson 		/*
234a488edb5SAndrew Thompson 		 * Set "is_uref" after grabbing the default SX lock
235a488edb5SAndrew Thompson 		 */
236e13e19faSAndrew Thompson 		crd->is_uref = 1;
2370ed53d45SAndrew Thompson 	}
2380ed53d45SAndrew Thompson 
23902ac6454SAndrew Thompson 	/* check if we are doing an open */
240ee3e3ff5SAndrew Thompson 	if (cpd->fflags == 0) {
241e13e19faSAndrew Thompson 		/* use zero defaults */
24202ac6454SAndrew Thompson 	} else {
24302ac6454SAndrew Thompson 		/* check for write */
244ee3e3ff5SAndrew Thompson 		if (cpd->fflags & FWRITE) {
245ee3e3ff5SAndrew Thompson 			ppf = cpd->udev->fifo;
246ee3e3ff5SAndrew Thompson 			f = ppf[cpd->fifo_index + USB_FIFO_TX];
247e13e19faSAndrew Thompson 			crd->txfifo = f;
248e13e19faSAndrew Thompson 			crd->is_write = 1;	/* ref */
249ee3e3ff5SAndrew Thompson 			if (f == NULL || f->refcount == USB_FIFO_REF_MAX)
25002ac6454SAndrew Thompson 				goto error;
2517214348fSAndrew Thompson 			if (f->curr_cpd != cpd)
2527214348fSAndrew Thompson 				goto error;
25302ac6454SAndrew Thompson 			/* check if USB-FS is active */
25402ac6454SAndrew Thompson 			if (f->fs_ep_max != 0) {
255e13e19faSAndrew Thompson 				crd->is_usbfs = 1;
25602ac6454SAndrew Thompson 			}
25702ac6454SAndrew Thompson 		}
25802ac6454SAndrew Thompson 
25902ac6454SAndrew Thompson 		/* check for read */
260ee3e3ff5SAndrew Thompson 		if (cpd->fflags & FREAD) {
261ee3e3ff5SAndrew Thompson 			ppf = cpd->udev->fifo;
262ee3e3ff5SAndrew Thompson 			f = ppf[cpd->fifo_index + USB_FIFO_RX];
263e13e19faSAndrew Thompson 			crd->rxfifo = f;
264e13e19faSAndrew Thompson 			crd->is_read = 1;	/* ref */
265ee3e3ff5SAndrew Thompson 			if (f == NULL || f->refcount == USB_FIFO_REF_MAX)
26602ac6454SAndrew Thompson 				goto error;
2677214348fSAndrew Thompson 			if (f->curr_cpd != cpd)
2687214348fSAndrew Thompson 				goto error;
26902ac6454SAndrew Thompson 			/* check if USB-FS is active */
27002ac6454SAndrew Thompson 			if (f->fs_ep_max != 0) {
271e13e19faSAndrew Thompson 				crd->is_usbfs = 1;
27202ac6454SAndrew Thompson 			}
27302ac6454SAndrew Thompson 		}
27402ac6454SAndrew Thompson 	}
27502ac6454SAndrew Thompson 
27602ac6454SAndrew Thompson 	/* when everything is OK we increment the refcounts */
277e13e19faSAndrew Thompson 	if (crd->is_write) {
27802ac6454SAndrew Thompson 		DPRINTFN(2, "ref write\n");
279e13e19faSAndrew Thompson 		crd->txfifo->refcount++;
28002ac6454SAndrew Thompson 	}
281e13e19faSAndrew Thompson 	if (crd->is_read) {
28202ac6454SAndrew Thompson 		DPRINTFN(2, "ref read\n");
283e13e19faSAndrew Thompson 		crd->rxfifo->refcount++;
28402ac6454SAndrew Thompson 	}
285a593f6b8SAndrew Thompson 	mtx_unlock(&usb_ref_lock);
28602ac6454SAndrew Thompson 
28702ac6454SAndrew Thompson 	return (0);
28802ac6454SAndrew Thompson 
28902ac6454SAndrew Thompson error:
290a18a7a41SHans Petter Selasky 	if (crd->do_unlock)
291cb18f7d1SAlfred Perlstein 		usbd_enum_unlock(cpd->udev);
292cb18f7d1SAlfred Perlstein 
293a18a7a41SHans Petter Selasky 	if (crd->is_uref) {
2940ed53d45SAndrew Thompson 		if (--(cpd->udev->refcount) == 0) {
29591cd9240SAndrew Thompson 			cv_signal(&cpd->udev->ref_cv);
2960ed53d45SAndrew Thompson 		}
2970ed53d45SAndrew Thompson 	}
298a593f6b8SAndrew Thompson 	mtx_unlock(&usb_ref_lock);
29902ac6454SAndrew Thompson 	DPRINTFN(2, "fail\n");
30002ac6454SAndrew Thompson 	return (USB_ERR_INVAL);
30102ac6454SAndrew Thompson }
30202ac6454SAndrew Thompson 
30302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
304a593f6b8SAndrew Thompson  *	usb_usb_ref_device
30502ac6454SAndrew Thompson  *
30602ac6454SAndrew Thompson  * This function is used to upgrade an USB reference to include the
30702ac6454SAndrew Thompson  * USB device reference on a USB location.
30802ac6454SAndrew Thompson  *
30902ac6454SAndrew Thompson  * Return values:
31002ac6454SAndrew Thompson  *  0: Success, refcount incremented on the given USB device.
31102ac6454SAndrew Thompson  *  Else: Failure.
31202ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
313e0a69b51SAndrew Thompson static usb_error_t
314a593f6b8SAndrew Thompson usb_usb_ref_device(struct usb_cdev_privdata *cpd,
315e13e19faSAndrew Thompson     struct usb_cdev_refdata *crd)
31602ac6454SAndrew Thompson {
31702ac6454SAndrew Thompson 	/*
31802ac6454SAndrew Thompson 	 * Check if we already got an USB reference on this location:
31902ac6454SAndrew Thompson 	 */
320e13e19faSAndrew Thompson 	if (crd->is_uref)
32102ac6454SAndrew Thompson 		return (0);		/* success */
32202ac6454SAndrew Thompson 
32302ac6454SAndrew Thompson 	/*
3240ed53d45SAndrew Thompson 	 * To avoid deadlock at detach we need to drop the FIFO ref
3250ed53d45SAndrew Thompson 	 * and re-acquire a new ref!
32602ac6454SAndrew Thompson 	 */
327a593f6b8SAndrew Thompson 	usb_unref_device(cpd, crd);
32802ac6454SAndrew Thompson 
329a593f6b8SAndrew Thompson 	return (usb_ref_device(cpd, crd, 1 /* need uref */));
33002ac6454SAndrew Thompson }
33102ac6454SAndrew Thompson 
33202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
333a593f6b8SAndrew Thompson  *	usb_unref_device
33402ac6454SAndrew Thompson  *
33502ac6454SAndrew Thompson  * This function will release the reference count by one unit for the
33602ac6454SAndrew Thompson  * given USB device.
33702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
3388019e7e7SAndrew Thompson static void
339a593f6b8SAndrew Thompson usb_unref_device(struct usb_cdev_privdata *cpd,
340e13e19faSAndrew Thompson     struct usb_cdev_refdata *crd)
34102ac6454SAndrew Thompson {
342a488edb5SAndrew Thompson 
343e13e19faSAndrew Thompson 	DPRINTFN(2, "cpd=%p is_uref=%d\n", cpd, crd->is_uref);
344a488edb5SAndrew Thompson 
345a18a7a41SHans Petter Selasky 	if (crd->do_unlock)
346cb18f7d1SAlfred Perlstein 		usbd_enum_unlock(cpd->udev);
347cb18f7d1SAlfred Perlstein 
348a593f6b8SAndrew Thompson 	mtx_lock(&usb_ref_lock);
349e13e19faSAndrew Thompson 	if (crd->is_read) {
350e13e19faSAndrew Thompson 		if (--(crd->rxfifo->refcount) == 0) {
3518437751dSAndrew Thompson 			cv_signal(&crd->rxfifo->cv_drain);
35202ac6454SAndrew Thompson 		}
353e13e19faSAndrew Thompson 		crd->is_read = 0;
35402ac6454SAndrew Thompson 	}
355e13e19faSAndrew Thompson 	if (crd->is_write) {
356e13e19faSAndrew Thompson 		if (--(crd->txfifo->refcount) == 0) {
3578437751dSAndrew Thompson 			cv_signal(&crd->txfifo->cv_drain);
35802ac6454SAndrew Thompson 		}
359e13e19faSAndrew Thompson 		crd->is_write = 0;
36002ac6454SAndrew Thompson 	}
361e13e19faSAndrew Thompson 	if (crd->is_uref) {
362ee3e3ff5SAndrew Thompson 		if (--(cpd->udev->refcount) == 0) {
36391cd9240SAndrew Thompson 			cv_signal(&cpd->udev->ref_cv);
36402ac6454SAndrew Thompson 		}
365e13e19faSAndrew Thompson 		crd->is_uref = 0;
36602ac6454SAndrew Thompson 	}
367a593f6b8SAndrew Thompson 	mtx_unlock(&usb_ref_lock);
36802ac6454SAndrew Thompson }
36902ac6454SAndrew Thompson 
370760bc48eSAndrew Thompson static struct usb_fifo *
371a593f6b8SAndrew Thompson usb_fifo_alloc(void)
37202ac6454SAndrew Thompson {
373760bc48eSAndrew Thompson 	struct usb_fifo *f;
37402ac6454SAndrew Thompson 
37502ac6454SAndrew Thompson 	f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO);
37602ac6454SAndrew Thompson 	if (f) {
3778437751dSAndrew Thompson 		cv_init(&f->cv_io, "FIFO-IO");
3788437751dSAndrew Thompson 		cv_init(&f->cv_drain, "FIFO-DRAIN");
37902ac6454SAndrew Thompson 		f->refcount = 1;
38002ac6454SAndrew Thompson 	}
38102ac6454SAndrew Thompson 	return (f);
38202ac6454SAndrew Thompson }
38302ac6454SAndrew Thompson 
38402ac6454SAndrew Thompson /*------------------------------------------------------------------------*
385a593f6b8SAndrew Thompson  *	usb_fifo_create
38602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
38702ac6454SAndrew Thompson static int
388a593f6b8SAndrew Thompson usb_fifo_create(struct usb_cdev_privdata *cpd,
389e13e19faSAndrew Thompson     struct usb_cdev_refdata *crd)
39002ac6454SAndrew Thompson {
391760bc48eSAndrew Thompson 	struct usb_device *udev = cpd->udev;
392760bc48eSAndrew Thompson 	struct usb_fifo *f;
393ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
39402ac6454SAndrew Thompson 	uint8_t n;
39502ac6454SAndrew Thompson 	uint8_t is_tx;
39602ac6454SAndrew Thompson 	uint8_t is_rx;
39702ac6454SAndrew Thompson 	uint8_t no_null;
39802ac6454SAndrew Thompson 	uint8_t is_busy;
399ae60fdfbSAndrew Thompson 	int e = cpd->ep_addr;
40002ac6454SAndrew Thompson 
401ee3e3ff5SAndrew Thompson 	is_tx = (cpd->fflags & FWRITE) ? 1 : 0;
402ee3e3ff5SAndrew Thompson 	is_rx = (cpd->fflags & FREAD) ? 1 : 0;
40302ac6454SAndrew Thompson 	no_null = 1;
40402ac6454SAndrew Thompson 	is_busy = 0;
40502ac6454SAndrew Thompson 
406ee3e3ff5SAndrew Thompson 	/* Preallocated FIFO */
407ae60fdfbSAndrew Thompson 	if (e < 0) {
408ee3e3ff5SAndrew Thompson 		DPRINTFN(5, "Preallocated FIFO\n");
409ee3e3ff5SAndrew Thompson 		if (is_tx) {
410ee3e3ff5SAndrew Thompson 			f = udev->fifo[cpd->fifo_index + USB_FIFO_TX];
411ee3e3ff5SAndrew Thompson 			if (f == NULL)
412ee3e3ff5SAndrew Thompson 				return (EINVAL);
413e13e19faSAndrew Thompson 			crd->txfifo = f;
414ee3e3ff5SAndrew Thompson 		}
415ee3e3ff5SAndrew Thompson 		if (is_rx) {
416ee3e3ff5SAndrew Thompson 			f = udev->fifo[cpd->fifo_index + USB_FIFO_RX];
417ee3e3ff5SAndrew Thompson 			if (f == NULL)
418ee3e3ff5SAndrew Thompson 				return (EINVAL);
419e13e19faSAndrew Thompson 			crd->rxfifo = f;
420ee3e3ff5SAndrew Thompson 		}
421ee3e3ff5SAndrew Thompson 		return (0);
422ee3e3ff5SAndrew Thompson 	}
42302ac6454SAndrew Thompson 
424ae60fdfbSAndrew Thompson 	KASSERT(e >= 0 && e <= 15, ("endpoint %d out of range", e));
425ee3e3ff5SAndrew Thompson 
426ee3e3ff5SAndrew Thompson 	/* search for a free FIFO slot */
427ae60fdfbSAndrew Thompson 	DPRINTFN(5, "Endpoint device, searching for 0x%02x\n", e);
42802ac6454SAndrew Thompson 	for (n = 0;; n += 2) {
42902ac6454SAndrew Thompson 
43002ac6454SAndrew Thompson 		if (n == USB_FIFO_MAX) {
43102ac6454SAndrew Thompson 			if (no_null) {
43202ac6454SAndrew Thompson 				no_null = 0;
43302ac6454SAndrew Thompson 				n = 0;
43402ac6454SAndrew Thompson 			} else {
43502ac6454SAndrew Thompson 				/* end of FIFOs reached */
436ee3e3ff5SAndrew Thompson 				DPRINTFN(5, "out of FIFOs\n");
43702ac6454SAndrew Thompson 				return (ENOMEM);
43802ac6454SAndrew Thompson 			}
43902ac6454SAndrew Thompson 		}
44002ac6454SAndrew Thompson 		/* Check for TX FIFO */
44102ac6454SAndrew Thompson 		if (is_tx) {
44202ac6454SAndrew Thompson 			f = udev->fifo[n + USB_FIFO_TX];
44302ac6454SAndrew Thompson 			if (f != NULL) {
444ae60fdfbSAndrew Thompson 				if (f->dev_ep_index != e) {
44502ac6454SAndrew Thompson 					/* wrong endpoint index */
44602ac6454SAndrew Thompson 					continue;
44702ac6454SAndrew Thompson 				}
4487214348fSAndrew Thompson 				if (f->curr_cpd != NULL) {
44902ac6454SAndrew Thompson 					/* FIFO is opened */
45002ac6454SAndrew Thompson 					is_busy = 1;
45102ac6454SAndrew Thompson 					continue;
45202ac6454SAndrew Thompson 				}
45302ac6454SAndrew Thompson 			} else if (no_null) {
45402ac6454SAndrew Thompson 				continue;
45502ac6454SAndrew Thompson 			}
45602ac6454SAndrew Thompson 		}
45702ac6454SAndrew Thompson 		/* Check for RX FIFO */
45802ac6454SAndrew Thompson 		if (is_rx) {
45902ac6454SAndrew Thompson 			f = udev->fifo[n + USB_FIFO_RX];
46002ac6454SAndrew Thompson 			if (f != NULL) {
461ae60fdfbSAndrew Thompson 				if (f->dev_ep_index != e) {
46202ac6454SAndrew Thompson 					/* wrong endpoint index */
46302ac6454SAndrew Thompson 					continue;
46402ac6454SAndrew Thompson 				}
4657214348fSAndrew Thompson 				if (f->curr_cpd != NULL) {
46602ac6454SAndrew Thompson 					/* FIFO is opened */
46702ac6454SAndrew Thompson 					is_busy = 1;
46802ac6454SAndrew Thompson 					continue;
46902ac6454SAndrew Thompson 				}
47002ac6454SAndrew Thompson 			} else if (no_null) {
47102ac6454SAndrew Thompson 				continue;
47202ac6454SAndrew Thompson 			}
47302ac6454SAndrew Thompson 		}
47402ac6454SAndrew Thompson 		break;
47502ac6454SAndrew Thompson 	}
47602ac6454SAndrew Thompson 
47702ac6454SAndrew Thompson 	if (no_null == 0) {
478ae60fdfbSAndrew Thompson 		if (e >= (USB_EP_MAX / 2)) {
47902ac6454SAndrew Thompson 			/* we don't create any endpoints in this range */
4807214348fSAndrew Thompson 			DPRINTFN(5, "ep out of range\n");
48102ac6454SAndrew Thompson 			return (is_busy ? EBUSY : EINVAL);
48202ac6454SAndrew Thompson 		}
48302ac6454SAndrew Thompson 	}
4847214348fSAndrew Thompson 
485ae60fdfbSAndrew Thompson 	if ((e != 0) && is_busy) {
4867214348fSAndrew Thompson 		/*
4877214348fSAndrew Thompson 		 * Only the default control endpoint is allowed to be
4887214348fSAndrew Thompson 		 * opened multiple times!
4897214348fSAndrew Thompson 		 */
4907214348fSAndrew Thompson 		DPRINTFN(5, "busy\n");
4917214348fSAndrew Thompson 		return (EBUSY);
4927214348fSAndrew Thompson 	}
4937214348fSAndrew Thompson 
49402ac6454SAndrew Thompson 	/* Check TX FIFO */
49502ac6454SAndrew Thompson 	if (is_tx &&
49602ac6454SAndrew Thompson 	    (udev->fifo[n + USB_FIFO_TX] == NULL)) {
497a593f6b8SAndrew Thompson 		ep = usb_dev_get_ep(udev, e, USB_FIFO_TX);
498ae60fdfbSAndrew Thompson 		DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_TX);
499ae60fdfbSAndrew Thompson 		if (ep == NULL) {
500ae60fdfbSAndrew Thompson 			DPRINTFN(5, "dev_get_endpoint returned NULL\n");
50102ac6454SAndrew Thompson 			return (EINVAL);
50202ac6454SAndrew Thompson 		}
503a593f6b8SAndrew Thompson 		f = usb_fifo_alloc();
50402ac6454SAndrew Thompson 		if (f == NULL) {
505ee3e3ff5SAndrew Thompson 			DPRINTFN(5, "could not alloc tx fifo\n");
50602ac6454SAndrew Thompson 			return (ENOMEM);
50702ac6454SAndrew Thompson 		}
50802ac6454SAndrew Thompson 		/* update some fields */
50902ac6454SAndrew Thompson 		f->fifo_index = n + USB_FIFO_TX;
510ae60fdfbSAndrew Thompson 		f->dev_ep_index = e;
51191cd9240SAndrew Thompson 		f->priv_mtx = &udev->device_mtx;
512ae60fdfbSAndrew Thompson 		f->priv_sc0 = ep;
513a593f6b8SAndrew Thompson 		f->methods = &usb_ugen_methods;
514ae60fdfbSAndrew Thompson 		f->iface_index = ep->iface_index;
51502ac6454SAndrew Thompson 		f->udev = udev;
516a593f6b8SAndrew Thompson 		mtx_lock(&usb_ref_lock);
51702ac6454SAndrew Thompson 		udev->fifo[n + USB_FIFO_TX] = f;
518a593f6b8SAndrew Thompson 		mtx_unlock(&usb_ref_lock);
51902ac6454SAndrew Thompson 	}
52002ac6454SAndrew Thompson 	/* Check RX FIFO */
52102ac6454SAndrew Thompson 	if (is_rx &&
52202ac6454SAndrew Thompson 	    (udev->fifo[n + USB_FIFO_RX] == NULL)) {
52302ac6454SAndrew Thompson 
524a593f6b8SAndrew Thompson 		ep = usb_dev_get_ep(udev, e, USB_FIFO_RX);
525ae60fdfbSAndrew Thompson 		DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_RX);
526ae60fdfbSAndrew Thompson 		if (ep == NULL) {
527ae60fdfbSAndrew Thompson 			DPRINTFN(5, "dev_get_endpoint returned NULL\n");
52802ac6454SAndrew Thompson 			return (EINVAL);
52902ac6454SAndrew Thompson 		}
530a593f6b8SAndrew Thompson 		f = usb_fifo_alloc();
53102ac6454SAndrew Thompson 		if (f == NULL) {
532ee3e3ff5SAndrew Thompson 			DPRINTFN(5, "could not alloc rx fifo\n");
53302ac6454SAndrew Thompson 			return (ENOMEM);
53402ac6454SAndrew Thompson 		}
53502ac6454SAndrew Thompson 		/* update some fields */
53602ac6454SAndrew Thompson 		f->fifo_index = n + USB_FIFO_RX;
537ae60fdfbSAndrew Thompson 		f->dev_ep_index = e;
53891cd9240SAndrew Thompson 		f->priv_mtx = &udev->device_mtx;
539ae60fdfbSAndrew Thompson 		f->priv_sc0 = ep;
540a593f6b8SAndrew Thompson 		f->methods = &usb_ugen_methods;
541ae60fdfbSAndrew Thompson 		f->iface_index = ep->iface_index;
54202ac6454SAndrew Thompson 		f->udev = udev;
543a593f6b8SAndrew Thompson 		mtx_lock(&usb_ref_lock);
54402ac6454SAndrew Thompson 		udev->fifo[n + USB_FIFO_RX] = f;
545a593f6b8SAndrew Thompson 		mtx_unlock(&usb_ref_lock);
54602ac6454SAndrew Thompson 	}
54702ac6454SAndrew Thompson 	if (is_tx) {
548e13e19faSAndrew Thompson 		crd->txfifo = udev->fifo[n + USB_FIFO_TX];
54902ac6454SAndrew Thompson 	}
55002ac6454SAndrew Thompson 	if (is_rx) {
551e13e19faSAndrew Thompson 		crd->rxfifo = udev->fifo[n + USB_FIFO_RX];
55202ac6454SAndrew Thompson 	}
553ee3e3ff5SAndrew Thompson 	/* fill out fifo index */
554ee3e3ff5SAndrew Thompson 	DPRINTFN(5, "fifo index = %d\n", n);
555ee3e3ff5SAndrew Thompson 	cpd->fifo_index = n;
55602ac6454SAndrew Thompson 
55702ac6454SAndrew Thompson 	/* complete */
55802ac6454SAndrew Thompson 
55902ac6454SAndrew Thompson 	return (0);
56002ac6454SAndrew Thompson }
56102ac6454SAndrew Thompson 
56202ac6454SAndrew Thompson void
563a593f6b8SAndrew Thompson usb_fifo_free(struct usb_fifo *f)
56402ac6454SAndrew Thompson {
56502ac6454SAndrew Thompson 	uint8_t n;
56602ac6454SAndrew Thompson 
56702ac6454SAndrew Thompson 	if (f == NULL) {
56802ac6454SAndrew Thompson 		/* be NULL safe */
56902ac6454SAndrew Thompson 		return;
57002ac6454SAndrew Thompson 	}
57102ac6454SAndrew Thompson 	/* destroy symlink devices, if any */
57202ac6454SAndrew Thompson 	for (n = 0; n != 2; n++) {
57302ac6454SAndrew Thompson 		if (f->symlink[n]) {
574a593f6b8SAndrew Thompson 			usb_free_symlink(f->symlink[n]);
57502ac6454SAndrew Thompson 			f->symlink[n] = NULL;
57602ac6454SAndrew Thompson 		}
57702ac6454SAndrew Thompson 	}
578a593f6b8SAndrew Thompson 	mtx_lock(&usb_ref_lock);
57902ac6454SAndrew Thompson 
58002ac6454SAndrew Thompson 	/* delink ourselves to stop calls from userland */
58102ac6454SAndrew Thompson 	if ((f->fifo_index < USB_FIFO_MAX) &&
58202ac6454SAndrew Thompson 	    (f->udev != NULL) &&
58302ac6454SAndrew Thompson 	    (f->udev->fifo[f->fifo_index] == f)) {
58402ac6454SAndrew Thompson 		f->udev->fifo[f->fifo_index] = NULL;
58502ac6454SAndrew Thompson 	} else {
586767cb2e2SAndrew Thompson 		DPRINTFN(0, "USB FIFO %p has not been linked\n", f);
58702ac6454SAndrew Thompson 	}
58802ac6454SAndrew Thompson 
58902ac6454SAndrew Thompson 	/* decrease refcount */
59002ac6454SAndrew Thompson 	f->refcount--;
59102ac6454SAndrew Thompson 	/* prevent any write flush */
59202ac6454SAndrew Thompson 	f->flag_iserror = 1;
59302ac6454SAndrew Thompson 	/* need to wait until all callers have exited */
59402ac6454SAndrew Thompson 	while (f->refcount != 0) {
595a593f6b8SAndrew Thompson 		mtx_unlock(&usb_ref_lock);	/* avoid LOR */
59602ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
59702ac6454SAndrew Thompson 		/* get I/O thread out of any sleep state */
59802ac6454SAndrew Thompson 		if (f->flag_sleeping) {
59902ac6454SAndrew Thompson 			f->flag_sleeping = 0;
6008437751dSAndrew Thompson 			cv_broadcast(&f->cv_io);
60102ac6454SAndrew Thompson 		}
60202ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
603a593f6b8SAndrew Thompson 		mtx_lock(&usb_ref_lock);
60402ac6454SAndrew Thompson 
605c7d8c1c6SHans Petter Selasky 		/*
606c7d8c1c6SHans Petter Selasky 		 * Check if the "f->refcount" variable reached zero
607c7d8c1c6SHans Petter Selasky 		 * during the unlocked time before entering wait:
608c7d8c1c6SHans Petter Selasky 		 */
609c7d8c1c6SHans Petter Selasky 		if (f->refcount == 0)
610c7d8c1c6SHans Petter Selasky 			break;
611c7d8c1c6SHans Petter Selasky 
61202ac6454SAndrew Thompson 		/* wait for sync */
613a593f6b8SAndrew Thompson 		cv_wait(&f->cv_drain, &usb_ref_lock);
61402ac6454SAndrew Thompson 	}
615a593f6b8SAndrew Thompson 	mtx_unlock(&usb_ref_lock);
61602ac6454SAndrew Thompson 
61702ac6454SAndrew Thompson 	/* take care of closing the device here, if any */
618a593f6b8SAndrew Thompson 	usb_fifo_close(f, 0);
61902ac6454SAndrew Thompson 
6208437751dSAndrew Thompson 	cv_destroy(&f->cv_io);
6218437751dSAndrew Thompson 	cv_destroy(&f->cv_drain);
62202ac6454SAndrew Thompson 
62302ac6454SAndrew Thompson 	free(f, M_USBDEV);
62402ac6454SAndrew Thompson }
62502ac6454SAndrew Thompson 
626ae60fdfbSAndrew Thompson static struct usb_endpoint *
627a593f6b8SAndrew Thompson usb_dev_get_ep(struct usb_device *udev, uint8_t ep_index, uint8_t dir)
62802ac6454SAndrew Thompson {
629ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
63002ac6454SAndrew Thompson 	uint8_t ep_dir;
63102ac6454SAndrew Thompson 
63202ac6454SAndrew Thompson 	if (ep_index == 0) {
6335b3bb704SAndrew Thompson 		ep = &udev->ctrl_ep;
63402ac6454SAndrew Thompson 	} else {
63502ac6454SAndrew Thompson 		if (dir == USB_FIFO_RX) {
636f29a0724SAndrew Thompson 			if (udev->flags.usb_mode == USB_MODE_HOST) {
63702ac6454SAndrew Thompson 				ep_dir = UE_DIR_IN;
63802ac6454SAndrew Thompson 			} else {
63902ac6454SAndrew Thompson 				ep_dir = UE_DIR_OUT;
64002ac6454SAndrew Thompson 			}
64102ac6454SAndrew Thompson 		} else {
642f29a0724SAndrew Thompson 			if (udev->flags.usb_mode == USB_MODE_HOST) {
64302ac6454SAndrew Thompson 				ep_dir = UE_DIR_OUT;
64402ac6454SAndrew Thompson 			} else {
64502ac6454SAndrew Thompson 				ep_dir = UE_DIR_IN;
64602ac6454SAndrew Thompson 			}
64702ac6454SAndrew Thompson 		}
648a593f6b8SAndrew Thompson 		ep = usbd_get_ep_by_addr(udev, ep_index | ep_dir);
64902ac6454SAndrew Thompson 	}
65002ac6454SAndrew Thompson 
651ae60fdfbSAndrew Thompson 	if (ep == NULL) {
652ae60fdfbSAndrew Thompson 		/* if the endpoint does not exist then return */
65302ac6454SAndrew Thompson 		return (NULL);
65402ac6454SAndrew Thompson 	}
655ae60fdfbSAndrew Thompson 	if (ep->edesc == NULL) {
656ae60fdfbSAndrew Thompson 		/* invalid endpoint */
65702ac6454SAndrew Thompson 		return (NULL);
65802ac6454SAndrew Thompson 	}
659ae60fdfbSAndrew Thompson 	return (ep);			/* success */
66002ac6454SAndrew Thompson }
66102ac6454SAndrew Thompson 
66202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
663a593f6b8SAndrew Thompson  *	usb_fifo_open
66402ac6454SAndrew Thompson  *
66502ac6454SAndrew Thompson  * Returns:
66602ac6454SAndrew Thompson  * 0: Success
66702ac6454SAndrew Thompson  * Else: Failure
66802ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
66902ac6454SAndrew Thompson static int
670a593f6b8SAndrew Thompson usb_fifo_open(struct usb_cdev_privdata *cpd,
671760bc48eSAndrew Thompson     struct usb_fifo *f, int fflags)
67202ac6454SAndrew Thompson {
67302ac6454SAndrew Thompson 	int err;
67402ac6454SAndrew Thompson 
67502ac6454SAndrew Thompson 	if (f == NULL) {
67602ac6454SAndrew Thompson 		/* no FIFO there */
67702ac6454SAndrew Thompson 		DPRINTFN(2, "no FIFO\n");
67802ac6454SAndrew Thompson 		return (ENXIO);
67902ac6454SAndrew Thompson 	}
68002ac6454SAndrew Thompson 	/* remove FWRITE and FREAD flags */
68102ac6454SAndrew Thompson 	fflags &= ~(FWRITE | FREAD);
68202ac6454SAndrew Thompson 
68302ac6454SAndrew Thompson 	/* set correct file flags */
68402ac6454SAndrew Thompson 	if ((f->fifo_index & 1) == USB_FIFO_TX) {
68502ac6454SAndrew Thompson 		fflags |= FWRITE;
68602ac6454SAndrew Thompson 	} else {
68702ac6454SAndrew Thompson 		fflags |= FREAD;
68802ac6454SAndrew Thompson 	}
68902ac6454SAndrew Thompson 
69002ac6454SAndrew Thompson 	/* check if we are already opened */
69102ac6454SAndrew Thompson 	/* we don't need any locks when checking this variable */
6927214348fSAndrew Thompson 	if (f->curr_cpd != NULL) {
69302ac6454SAndrew Thompson 		err = EBUSY;
69402ac6454SAndrew Thompson 		goto done;
69502ac6454SAndrew Thompson 	}
696ee3e3ff5SAndrew Thompson 
6977214348fSAndrew Thompson 	/* reset short flag before open */
6987214348fSAndrew Thompson 	f->flag_short = 0;
6997214348fSAndrew Thompson 
70002ac6454SAndrew Thompson 	/* call open method */
701ee3e3ff5SAndrew Thompson 	err = (f->methods->f_open) (f, fflags);
70202ac6454SAndrew Thompson 	if (err) {
70302ac6454SAndrew Thompson 		goto done;
70402ac6454SAndrew Thompson 	}
70502ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
70602ac6454SAndrew Thompson 
70702ac6454SAndrew Thompson 	/* reset sleep flag */
70802ac6454SAndrew Thompson 	f->flag_sleeping = 0;
70902ac6454SAndrew Thompson 
71002ac6454SAndrew Thompson 	/* reset error flag */
71102ac6454SAndrew Thompson 	f->flag_iserror = 0;
71202ac6454SAndrew Thompson 
71302ac6454SAndrew Thompson 	/* reset complete flag */
71402ac6454SAndrew Thompson 	f->flag_iscomplete = 0;
71502ac6454SAndrew Thompson 
71602ac6454SAndrew Thompson 	/* reset select flag */
71702ac6454SAndrew Thompson 	f->flag_isselect = 0;
71802ac6454SAndrew Thompson 
71902ac6454SAndrew Thompson 	/* reset flushing flag */
72002ac6454SAndrew Thompson 	f->flag_flushing = 0;
72102ac6454SAndrew Thompson 
72202ac6454SAndrew Thompson 	/* reset ASYNC proc flag */
72302ac6454SAndrew Thompson 	f->async_p = NULL;
72402ac6454SAndrew Thompson 
725a593f6b8SAndrew Thompson 	mtx_lock(&usb_ref_lock);
7267214348fSAndrew Thompson 	/* flag the fifo as opened to prevent others */
7277214348fSAndrew Thompson 	f->curr_cpd = cpd;
728a593f6b8SAndrew Thompson 	mtx_unlock(&usb_ref_lock);
72902ac6454SAndrew Thompson 
73002ac6454SAndrew Thompson 	/* reset queue */
731a593f6b8SAndrew Thompson 	usb_fifo_reset(f);
73202ac6454SAndrew Thompson 
73302ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
73402ac6454SAndrew Thompson done:
73502ac6454SAndrew Thompson 	return (err);
73602ac6454SAndrew Thompson }
73702ac6454SAndrew Thompson 
73802ac6454SAndrew Thompson /*------------------------------------------------------------------------*
739a593f6b8SAndrew Thompson  *	usb_fifo_reset
74002ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
74102ac6454SAndrew Thompson void
742a593f6b8SAndrew Thompson usb_fifo_reset(struct usb_fifo *f)
74302ac6454SAndrew Thompson {
744760bc48eSAndrew Thompson 	struct usb_mbuf *m;
74502ac6454SAndrew Thompson 
74602ac6454SAndrew Thompson 	if (f == NULL) {
74702ac6454SAndrew Thompson 		return;
74802ac6454SAndrew Thompson 	}
74902ac6454SAndrew Thompson 	while (1) {
75002ac6454SAndrew Thompson 		USB_IF_DEQUEUE(&f->used_q, m);
75102ac6454SAndrew Thompson 		if (m) {
75202ac6454SAndrew Thompson 			USB_IF_ENQUEUE(&f->free_q, m);
75302ac6454SAndrew Thompson 		} else {
75402ac6454SAndrew Thompson 			break;
75502ac6454SAndrew Thompson 		}
75602ac6454SAndrew Thompson 	}
757bd73b187SAlfred Perlstein 	/* reset have fragment flag */
758bd73b187SAlfred Perlstein 	f->flag_have_fragment = 0;
75902ac6454SAndrew Thompson }
76002ac6454SAndrew Thompson 
76102ac6454SAndrew Thompson /*------------------------------------------------------------------------*
762a593f6b8SAndrew Thompson  *	usb_fifo_close
76302ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
76402ac6454SAndrew Thompson static void
765a593f6b8SAndrew Thompson usb_fifo_close(struct usb_fifo *f, int fflags)
76602ac6454SAndrew Thompson {
76702ac6454SAndrew Thompson 	int err;
76802ac6454SAndrew Thompson 
76902ac6454SAndrew Thompson 	/* check if we are not opened */
7707214348fSAndrew Thompson 	if (f->curr_cpd == NULL) {
77102ac6454SAndrew Thompson 		/* nothing to do - already closed */
77202ac6454SAndrew Thompson 		return;
77302ac6454SAndrew Thompson 	}
77402ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
77502ac6454SAndrew Thompson 
7767214348fSAndrew Thompson 	/* clear current cdev private data pointer */
7777214348fSAndrew Thompson 	f->curr_cpd = NULL;
77802ac6454SAndrew Thompson 
77902ac6454SAndrew Thompson 	/* check if we are selected */
78002ac6454SAndrew Thompson 	if (f->flag_isselect) {
78102ac6454SAndrew Thompson 		selwakeup(&f->selinfo);
78202ac6454SAndrew Thompson 		f->flag_isselect = 0;
78302ac6454SAndrew Thompson 	}
78402ac6454SAndrew Thompson 	/* check if a thread wants SIGIO */
78502ac6454SAndrew Thompson 	if (f->async_p != NULL) {
78602ac6454SAndrew Thompson 		PROC_LOCK(f->async_p);
7878451d0ddSKip Macy 		kern_psignal(f->async_p, SIGIO);
78802ac6454SAndrew Thompson 		PROC_UNLOCK(f->async_p);
78902ac6454SAndrew Thompson 		f->async_p = NULL;
79002ac6454SAndrew Thompson 	}
79102ac6454SAndrew Thompson 	/* remove FWRITE and FREAD flags */
79202ac6454SAndrew Thompson 	fflags &= ~(FWRITE | FREAD);
79302ac6454SAndrew Thompson 
79402ac6454SAndrew Thompson 	/* flush written data, if any */
79502ac6454SAndrew Thompson 	if ((f->fifo_index & 1) == USB_FIFO_TX) {
79602ac6454SAndrew Thompson 
79702ac6454SAndrew Thompson 		if (!f->flag_iserror) {
79802ac6454SAndrew Thompson 
79902ac6454SAndrew Thompson 			/* set flushing flag */
80002ac6454SAndrew Thompson 			f->flag_flushing = 1;
80102ac6454SAndrew Thompson 
802bd73b187SAlfred Perlstein 			/* get the last packet in */
803bd73b187SAlfred Perlstein 			if (f->flag_have_fragment) {
804bd73b187SAlfred Perlstein 				struct usb_mbuf *m;
805bd73b187SAlfred Perlstein 				f->flag_have_fragment = 0;
806bd73b187SAlfred Perlstein 				USB_IF_DEQUEUE(&f->free_q, m);
807bd73b187SAlfred Perlstein 				if (m) {
808bd73b187SAlfred Perlstein 					USB_IF_ENQUEUE(&f->used_q, m);
809bd73b187SAlfred Perlstein 				}
810bd73b187SAlfred Perlstein 			}
811bd73b187SAlfred Perlstein 
81202ac6454SAndrew Thompson 			/* start write transfer, if not already started */
81302ac6454SAndrew Thompson 			(f->methods->f_start_write) (f);
81402ac6454SAndrew Thompson 
81502ac6454SAndrew Thompson 			/* check if flushed already */
81602ac6454SAndrew Thompson 			while (f->flag_flushing &&
81702ac6454SAndrew Thompson 			    (!f->flag_iserror)) {
81802ac6454SAndrew Thompson 				/* wait until all data has been written */
81902ac6454SAndrew Thompson 				f->flag_sleeping = 1;
8208437751dSAndrew Thompson 				err = cv_wait_sig(&f->cv_io, f->priv_mtx);
82102ac6454SAndrew Thompson 				if (err) {
82202ac6454SAndrew Thompson 					DPRINTF("signal received\n");
82302ac6454SAndrew Thompson 					break;
82402ac6454SAndrew Thompson 				}
82502ac6454SAndrew Thompson 			}
82602ac6454SAndrew Thompson 		}
82702ac6454SAndrew Thompson 		fflags |= FWRITE;
82802ac6454SAndrew Thompson 
82902ac6454SAndrew Thompson 		/* stop write transfer, if not already stopped */
83002ac6454SAndrew Thompson 		(f->methods->f_stop_write) (f);
83102ac6454SAndrew Thompson 	} else {
83202ac6454SAndrew Thompson 		fflags |= FREAD;
83302ac6454SAndrew Thompson 
83402ac6454SAndrew Thompson 		/* stop write transfer, if not already stopped */
83502ac6454SAndrew Thompson 		(f->methods->f_stop_read) (f);
83602ac6454SAndrew Thompson 	}
83702ac6454SAndrew Thompson 
83802ac6454SAndrew Thompson 	/* check if we are sleeping */
83902ac6454SAndrew Thompson 	if (f->flag_sleeping) {
84002ac6454SAndrew Thompson 		DPRINTFN(2, "Sleeping at close!\n");
84102ac6454SAndrew Thompson 	}
84202ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
84302ac6454SAndrew Thompson 
84402ac6454SAndrew Thompson 	/* call close method */
845ee3e3ff5SAndrew Thompson 	(f->methods->f_close) (f, fflags);
84602ac6454SAndrew Thompson 
84702ac6454SAndrew Thompson 	DPRINTF("closed\n");
84802ac6454SAndrew Thompson }
84902ac6454SAndrew Thompson 
85002ac6454SAndrew Thompson /*------------------------------------------------------------------------*
851a593f6b8SAndrew Thompson  *	usb_open - cdev callback
85202ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
85302ac6454SAndrew Thompson static int
854a593f6b8SAndrew Thompson usb_open(struct cdev *dev, int fflags, int devtype, struct thread *td)
85502ac6454SAndrew Thompson {
856760bc48eSAndrew Thompson 	struct usb_fs_privdata* pd = (struct usb_fs_privdata*)dev->si_drv1;
857e13e19faSAndrew Thompson 	struct usb_cdev_refdata refs;
858760bc48eSAndrew Thompson 	struct usb_cdev_privdata *cpd;
859ee3e3ff5SAndrew Thompson 	int err, ep;
86002ac6454SAndrew Thompson 
8617870adb6SEd Schouten 	DPRINTFN(2, "%s fflags=0x%08x\n", devtoname(dev), fflags);
86202ac6454SAndrew Thompson 
863ee3e3ff5SAndrew Thompson 	KASSERT(fflags & (FREAD|FWRITE), ("invalid open flags"));
864ee3e3ff5SAndrew Thompson 	if (((fflags & FREAD) && !(pd->mode & FREAD)) ||
865ee3e3ff5SAndrew Thompson 	    ((fflags & FWRITE) && !(pd->mode & FWRITE))) {
866ee3e3ff5SAndrew Thompson 		DPRINTFN(2, "access mode not supported\n");
86702ac6454SAndrew Thompson 		return (EPERM);
86802ac6454SAndrew Thompson 	}
869ee3e3ff5SAndrew Thompson 
870ee3e3ff5SAndrew Thompson 	cpd = malloc(sizeof(*cpd), M_USBDEV, M_WAITOK | M_ZERO);
871ee3e3ff5SAndrew Thompson 	ep = cpd->ep_addr = pd->ep_addr;
872ee3e3ff5SAndrew Thompson 
873a593f6b8SAndrew Thompson 	usb_loc_fill(pd, cpd);
874a593f6b8SAndrew Thompson 	err = usb_ref_device(cpd, &refs, 1);
87502ac6454SAndrew Thompson 	if (err) {
87602ac6454SAndrew Thompson 		DPRINTFN(2, "cannot ref device\n");
877ee3e3ff5SAndrew Thompson 		free(cpd, M_USBDEV);
87802ac6454SAndrew Thompson 		return (ENXIO);
87902ac6454SAndrew Thompson 	}
880ee3e3ff5SAndrew Thompson 	cpd->fflags = fflags;	/* access mode for open lifetime */
88102ac6454SAndrew Thompson 
88202ac6454SAndrew Thompson 	/* create FIFOs, if any */
883a593f6b8SAndrew Thompson 	err = usb_fifo_create(cpd, &refs);
88402ac6454SAndrew Thompson 	/* check for error */
88502ac6454SAndrew Thompson 	if (err) {
886ee3e3ff5SAndrew Thompson 		DPRINTFN(2, "cannot create fifo\n");
887a593f6b8SAndrew Thompson 		usb_unref_device(cpd, &refs);
888ee3e3ff5SAndrew Thompson 		free(cpd, M_USBDEV);
88902ac6454SAndrew Thompson 		return (err);
89002ac6454SAndrew Thompson 	}
89102ac6454SAndrew Thompson 	if (fflags & FREAD) {
892a593f6b8SAndrew Thompson 		err = usb_fifo_open(cpd, refs.rxfifo, fflags);
89302ac6454SAndrew Thompson 		if (err) {
89402ac6454SAndrew Thompson 			DPRINTFN(2, "read open failed\n");
895a593f6b8SAndrew Thompson 			usb_unref_device(cpd, &refs);
896ee3e3ff5SAndrew Thompson 			free(cpd, M_USBDEV);
89702ac6454SAndrew Thompson 			return (err);
89802ac6454SAndrew Thompson 		}
89902ac6454SAndrew Thompson 	}
90002ac6454SAndrew Thompson 	if (fflags & FWRITE) {
901a593f6b8SAndrew Thompson 		err = usb_fifo_open(cpd, refs.txfifo, fflags);
90202ac6454SAndrew Thompson 		if (err) {
90302ac6454SAndrew Thompson 			DPRINTFN(2, "write open failed\n");
90402ac6454SAndrew Thompson 			if (fflags & FREAD) {
905a593f6b8SAndrew Thompson 				usb_fifo_close(refs.rxfifo, fflags);
90602ac6454SAndrew Thompson 			}
907a593f6b8SAndrew Thompson 			usb_unref_device(cpd, &refs);
908ee3e3ff5SAndrew Thompson 			free(cpd, M_USBDEV);
90902ac6454SAndrew Thompson 			return (err);
91002ac6454SAndrew Thompson 		}
91102ac6454SAndrew Thompson 	}
912a593f6b8SAndrew Thompson 	usb_unref_device(cpd, &refs);
913a593f6b8SAndrew Thompson 	devfs_set_cdevpriv(cpd, usb_close);
91402ac6454SAndrew Thompson 
915ee3e3ff5SAndrew Thompson 	return (0);
91602ac6454SAndrew Thompson }
91702ac6454SAndrew Thompson 
91802ac6454SAndrew Thompson /*------------------------------------------------------------------------*
919a593f6b8SAndrew Thompson  *	usb_close - cdev callback
92002ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
92102ac6454SAndrew Thompson static void
922a593f6b8SAndrew Thompson usb_close(void *arg)
92302ac6454SAndrew Thompson {
924e13e19faSAndrew Thompson 	struct usb_cdev_refdata refs;
925760bc48eSAndrew Thompson 	struct usb_cdev_privdata *cpd = arg;
926ee3e3ff5SAndrew Thompson 	int err;
92702ac6454SAndrew Thompson 
9287214348fSAndrew Thompson 	DPRINTFN(2, "cpd=%p\n", cpd);
929ee3e3ff5SAndrew Thompson 
930f97da128SHans Petter Selasky 	err = usb_ref_device(cpd, &refs,
931f97da128SHans Petter Selasky 	    2 /* uref and allow detached state */);
932f97da128SHans Petter Selasky 	if (err) {
933*5c8c627bSHans Petter Selasky 		DPRINTFN(2, "Cannot grab USB reference when "
934f97da128SHans Petter Selasky 		    "closing USB file handle\n");
935fbaee0f1SHans Petter Selasky 		goto done;
93602ac6454SAndrew Thompson 	}
937ee3e3ff5SAndrew Thompson 	if (cpd->fflags & FREAD) {
938a593f6b8SAndrew Thompson 		usb_fifo_close(refs.rxfifo, cpd->fflags);
93902ac6454SAndrew Thompson 	}
940ee3e3ff5SAndrew Thompson 	if (cpd->fflags & FWRITE) {
941a593f6b8SAndrew Thompson 		usb_fifo_close(refs.txfifo, cpd->fflags);
94202ac6454SAndrew Thompson 	}
943a593f6b8SAndrew Thompson 	usb_unref_device(cpd, &refs);
944fbaee0f1SHans Petter Selasky done:
945ee3e3ff5SAndrew Thompson 	free(cpd, M_USBDEV);
94602ac6454SAndrew Thompson }
94702ac6454SAndrew Thompson 
94802ac6454SAndrew Thompson static void
949a593f6b8SAndrew Thompson usb_dev_init(void *arg)
95002ac6454SAndrew Thompson {
951a593f6b8SAndrew Thompson 	mtx_init(&usb_ref_lock, "USB ref mutex", NULL, MTX_DEF);
952a593f6b8SAndrew Thompson 	sx_init(&usb_sym_lock, "USB sym mutex");
953a593f6b8SAndrew Thompson 	TAILQ_INIT(&usb_sym_head);
95402ac6454SAndrew Thompson 
95502ac6454SAndrew Thompson 	/* check the UGEN methods */
956a593f6b8SAndrew Thompson 	usb_fifo_check_methods(&usb_ugen_methods);
95702ac6454SAndrew Thompson }
95802ac6454SAndrew Thompson 
959a593f6b8SAndrew Thompson SYSINIT(usb_dev_init, SI_SUB_KLD, SI_ORDER_FIRST, usb_dev_init, NULL);
96002ac6454SAndrew Thompson 
96102ac6454SAndrew Thompson static void
962a593f6b8SAndrew Thompson usb_dev_init_post(void *arg)
96302ac6454SAndrew Thompson {
96402ac6454SAndrew Thompson 	/*
965ee3e3ff5SAndrew Thompson 	 * Create /dev/usb - this is needed for usbconfig(8), which
966ee3e3ff5SAndrew Thompson 	 * needs a well-known device name to access.
96702ac6454SAndrew Thompson 	 */
968a593f6b8SAndrew Thompson 	usb_dev = make_dev(&usb_static_devsw, 0, UID_ROOT, GID_OPERATOR,
969ee3e3ff5SAndrew Thompson 	    0644, USB_DEVICE_NAME);
970a593f6b8SAndrew Thompson 	if (usb_dev == NULL) {
971767cb2e2SAndrew Thompson 		DPRINTFN(0, "Could not create usb bus device\n");
97202ac6454SAndrew Thompson 	}
97302ac6454SAndrew Thompson }
97402ac6454SAndrew Thompson 
975a593f6b8SAndrew Thompson SYSINIT(usb_dev_init_post, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, usb_dev_init_post, NULL);
97602ac6454SAndrew Thompson 
97702ac6454SAndrew Thompson static void
978a593f6b8SAndrew Thompson usb_dev_uninit(void *arg)
97902ac6454SAndrew Thompson {
980a593f6b8SAndrew Thompson 	if (usb_dev != NULL) {
981a593f6b8SAndrew Thompson 		destroy_dev(usb_dev);
982a593f6b8SAndrew Thompson 		usb_dev = NULL;
98302ac6454SAndrew Thompson 	}
984a593f6b8SAndrew Thompson 	mtx_destroy(&usb_ref_lock);
985a593f6b8SAndrew Thompson 	sx_destroy(&usb_sym_lock);
98602ac6454SAndrew Thompson }
98702ac6454SAndrew Thompson 
988a593f6b8SAndrew Thompson SYSUNINIT(usb_dev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb_dev_uninit, NULL);
98902ac6454SAndrew Thompson 
99002ac6454SAndrew Thompson static int
991a593f6b8SAndrew Thompson usb_ioctl_f_sub(struct usb_fifo *f, u_long cmd, void *addr,
99202ac6454SAndrew Thompson     struct thread *td)
99302ac6454SAndrew Thompson {
99402ac6454SAndrew Thompson 	int error = 0;
99502ac6454SAndrew Thompson 
99602ac6454SAndrew Thompson 	switch (cmd) {
99702ac6454SAndrew Thompson 	case FIODTYPE:
99802ac6454SAndrew Thompson 		*(int *)addr = 0;	/* character device */
99902ac6454SAndrew Thompson 		break;
100002ac6454SAndrew Thompson 
100102ac6454SAndrew Thompson 	case FIONBIO:
100202ac6454SAndrew Thompson 		/* handled by upper FS layer */
100302ac6454SAndrew Thompson 		break;
100402ac6454SAndrew Thompson 
100502ac6454SAndrew Thompson 	case FIOASYNC:
100602ac6454SAndrew Thompson 		if (*(int *)addr) {
100702ac6454SAndrew Thompson 			if (f->async_p != NULL) {
100802ac6454SAndrew Thompson 				error = EBUSY;
100902ac6454SAndrew Thompson 				break;
101002ac6454SAndrew Thompson 			}
101102ac6454SAndrew Thompson 			f->async_p = USB_TD_GET_PROC(td);
101202ac6454SAndrew Thompson 		} else {
101302ac6454SAndrew Thompson 			f->async_p = NULL;
101402ac6454SAndrew Thompson 		}
101502ac6454SAndrew Thompson 		break;
101602ac6454SAndrew Thompson 
101702ac6454SAndrew Thompson 		/* XXX this is not the most general solution */
101802ac6454SAndrew Thompson 	case TIOCSPGRP:
101902ac6454SAndrew Thompson 		if (f->async_p == NULL) {
102002ac6454SAndrew Thompson 			error = EINVAL;
102102ac6454SAndrew Thompson 			break;
102202ac6454SAndrew Thompson 		}
102302ac6454SAndrew Thompson 		if (*(int *)addr != USB_PROC_GET_GID(f->async_p)) {
102402ac6454SAndrew Thompson 			error = EPERM;
102502ac6454SAndrew Thompson 			break;
102602ac6454SAndrew Thompson 		}
102702ac6454SAndrew Thompson 		break;
102802ac6454SAndrew Thompson 	default:
102902ac6454SAndrew Thompson 		return (ENOIOCTL);
103002ac6454SAndrew Thompson 	}
1031cba30496SAndrew Thompson 	DPRINTFN(3, "cmd 0x%lx = %d\n", cmd, error);
103202ac6454SAndrew Thompson 	return (error);
103302ac6454SAndrew Thompson }
103402ac6454SAndrew Thompson 
1035ee3e3ff5SAndrew Thompson /*------------------------------------------------------------------------*
1036a593f6b8SAndrew Thompson  *	usb_ioctl - cdev callback
1037ee3e3ff5SAndrew Thompson  *------------------------------------------------------------------------*/
103802ac6454SAndrew Thompson static int
1039a593f6b8SAndrew Thompson usb_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int fflag, struct thread* td)
104002ac6454SAndrew Thompson {
1041e13e19faSAndrew Thompson 	struct usb_cdev_refdata refs;
1042760bc48eSAndrew Thompson 	struct usb_cdev_privdata* cpd;
1043760bc48eSAndrew Thompson 	struct usb_fifo *f;
104402ac6454SAndrew Thompson 	int fflags;
104502ac6454SAndrew Thompson 	int err;
104602ac6454SAndrew Thompson 
1047cba30496SAndrew Thompson 	DPRINTFN(2, "cmd=0x%lx\n", cmd);
1048cba30496SAndrew Thompson 
1049ee3e3ff5SAndrew Thompson 	err = devfs_get_cdevpriv((void **)&cpd);
1050ee3e3ff5SAndrew Thompson 	if (err != 0)
1051ee3e3ff5SAndrew Thompson 		return (err);
1052ee3e3ff5SAndrew Thompson 
1053f5f145baSAndrew Thompson 	/*
1054e13e19faSAndrew Thompson 	 * Performance optimisation: We try to check for IOCTL's that
1055f5f145baSAndrew Thompson 	 * don't need the USB reference first. Then we grab the USB
1056f5f145baSAndrew Thompson 	 * reference if we need it!
1057f5f145baSAndrew Thompson 	 */
1058a593f6b8SAndrew Thompson 	err = usb_ref_device(cpd, &refs, 0 /* no uref */ );
1059cb18f7d1SAlfred Perlstein 	if (err)
106002ac6454SAndrew Thompson 		return (ENXIO);
1061cb18f7d1SAlfred Perlstein 
1062ee3e3ff5SAndrew Thompson 	fflags = cpd->fflags;
106302ac6454SAndrew Thompson 
106402ac6454SAndrew Thompson 	f = NULL;			/* set default value */
106502ac6454SAndrew Thompson 	err = ENOIOCTL;			/* set default value */
106602ac6454SAndrew Thompson 
106702ac6454SAndrew Thompson 	if (fflags & FWRITE) {
1068e13e19faSAndrew Thompson 		f = refs.txfifo;
1069a593f6b8SAndrew Thompson 		err = usb_ioctl_f_sub(f, cmd, addr, td);
107002ac6454SAndrew Thompson 	}
107102ac6454SAndrew Thompson 	if (fflags & FREAD) {
1072e13e19faSAndrew Thompson 		f = refs.rxfifo;
1073a593f6b8SAndrew Thompson 		err = usb_ioctl_f_sub(f, cmd, addr, td);
107402ac6454SAndrew Thompson 	}
1075ee3e3ff5SAndrew Thompson 	KASSERT(f != NULL, ("fifo not found"));
10768f9750b7SHans Petter Selasky 	if (err != ENOIOCTL)
10778f9750b7SHans Petter Selasky 		goto done;
10788f9750b7SHans Petter Selasky 
1079ee3e3ff5SAndrew Thompson 	err = (f->methods->f_ioctl) (f, cmd, addr, fflags);
10808f9750b7SHans Petter Selasky 
1081cba30496SAndrew Thompson 	DPRINTFN(2, "f_ioctl cmd 0x%lx = %d\n", cmd, err);
10828f9750b7SHans Petter Selasky 
10838f9750b7SHans Petter Selasky 	if (err != ENOIOCTL)
10848f9750b7SHans Petter Selasky 		goto done;
10858f9750b7SHans Petter Selasky 
1086a593f6b8SAndrew Thompson 	if (usb_usb_ref_device(cpd, &refs)) {
108702ac6454SAndrew Thompson 		err = ENXIO;
108802ac6454SAndrew Thompson 		goto done;
108902ac6454SAndrew Thompson 	}
10908f9750b7SHans Petter Selasky 
1091ee3e3ff5SAndrew Thompson 	err = (f->methods->f_ioctl_post) (f, cmd, addr, fflags);
10928f9750b7SHans Petter Selasky 
1093cba30496SAndrew Thompson 	DPRINTFN(2, "f_ioctl_post cmd 0x%lx = %d\n", cmd, err);
10948f9750b7SHans Petter Selasky 
10958f9750b7SHans Petter Selasky 	if (err == ENOIOCTL)
109602ac6454SAndrew Thompson 		err = ENOTTY;
10978f9750b7SHans Petter Selasky 
10988f9750b7SHans Petter Selasky 	if (err)
10998f9750b7SHans Petter Selasky 		goto done;
11008f9750b7SHans Petter Selasky 
11018f9750b7SHans Petter Selasky 	/* Wait for re-enumeration, if any */
11028f9750b7SHans Petter Selasky 
1103d6f4a9f9SHans Petter Selasky 	while (f->udev->re_enumerate_wait != USB_RE_ENUM_DONE) {
11048f9750b7SHans Petter Selasky 
11058f9750b7SHans Petter Selasky 		usb_unref_device(cpd, &refs);
11068f9750b7SHans Petter Selasky 
11078f9750b7SHans Petter Selasky 		usb_pause_mtx(NULL, hz / 128);
11088f9750b7SHans Petter Selasky 
11098f9750b7SHans Petter Selasky 		if (usb_ref_device(cpd, &refs, 1 /* need uref */)) {
11108f9750b7SHans Petter Selasky 			err = ENXIO;
11118f9750b7SHans Petter Selasky 			goto done;
111202ac6454SAndrew Thompson 		}
11138f9750b7SHans Petter Selasky 	}
11148f9750b7SHans Petter Selasky 
111502ac6454SAndrew Thompson done:
1116a593f6b8SAndrew Thompson 	usb_unref_device(cpd, &refs);
111702ac6454SAndrew Thompson 	return (err);
111802ac6454SAndrew Thompson }
111902ac6454SAndrew Thompson 
112002ac6454SAndrew Thompson /* ARGSUSED */
112102ac6454SAndrew Thompson static int
1122a593f6b8SAndrew Thompson usb_poll(struct cdev* dev, int events, struct thread* td)
112302ac6454SAndrew Thompson {
1124e13e19faSAndrew Thompson 	struct usb_cdev_refdata refs;
1125760bc48eSAndrew Thompson 	struct usb_cdev_privdata* cpd;
1126760bc48eSAndrew Thompson 	struct usb_fifo *f;
1127760bc48eSAndrew Thompson 	struct usb_mbuf *m;
11288a93629eSAndrew Thompson 	int fflags, revents;
112902ac6454SAndrew Thompson 
11308a93629eSAndrew Thompson 	if (devfs_get_cdevpriv((void **)&cpd) != 0 ||
1131a593f6b8SAndrew Thompson 	    usb_ref_device(cpd, &refs, 0) != 0)
11328a93629eSAndrew Thompson 		return (events &
11338a93629eSAndrew Thompson 		    (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM));
1134ee3e3ff5SAndrew Thompson 
1135ee3e3ff5SAndrew Thompson 	fflags = cpd->fflags;
113602ac6454SAndrew Thompson 
113702ac6454SAndrew Thompson 	/* Figure out who needs service */
1138ee3e3ff5SAndrew Thompson 	revents = 0;
113902ac6454SAndrew Thompson 	if ((events & (POLLOUT | POLLWRNORM)) &&
114002ac6454SAndrew Thompson 	    (fflags & FWRITE)) {
114102ac6454SAndrew Thompson 
1142e13e19faSAndrew Thompson 		f = refs.txfifo;
114302ac6454SAndrew Thompson 
114402ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
114502ac6454SAndrew Thompson 
1146e13e19faSAndrew Thompson 		if (!refs.is_usbfs) {
114702ac6454SAndrew Thompson 			if (f->flag_iserror) {
114802ac6454SAndrew Thompson 				/* we got an error */
114902ac6454SAndrew Thompson 				m = (void *)1;
115002ac6454SAndrew Thompson 			} else {
115102ac6454SAndrew Thompson 				if (f->queue_data == NULL) {
115202ac6454SAndrew Thompson 					/*
115302ac6454SAndrew Thompson 					 * start write transfer, if not
115402ac6454SAndrew Thompson 					 * already started
115502ac6454SAndrew Thompson 					 */
115602ac6454SAndrew Thompson 					(f->methods->f_start_write) (f);
115702ac6454SAndrew Thompson 				}
115802ac6454SAndrew Thompson 				/* check if any packets are available */
115902ac6454SAndrew Thompson 				USB_IF_POLL(&f->free_q, m);
116002ac6454SAndrew Thompson 			}
116102ac6454SAndrew Thompson 		} else {
116202ac6454SAndrew Thompson 			if (f->flag_iscomplete) {
116302ac6454SAndrew Thompson 				m = (void *)1;
116402ac6454SAndrew Thompson 			} else {
116502ac6454SAndrew Thompson 				m = NULL;
116602ac6454SAndrew Thompson 			}
116702ac6454SAndrew Thompson 		}
116802ac6454SAndrew Thompson 
116902ac6454SAndrew Thompson 		if (m) {
117002ac6454SAndrew Thompson 			revents |= events & (POLLOUT | POLLWRNORM);
117102ac6454SAndrew Thompson 		} else {
117202ac6454SAndrew Thompson 			f->flag_isselect = 1;
117302ac6454SAndrew Thompson 			selrecord(td, &f->selinfo);
117402ac6454SAndrew Thompson 		}
117502ac6454SAndrew Thompson 
117602ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
117702ac6454SAndrew Thompson 	}
117802ac6454SAndrew Thompson 	if ((events & (POLLIN | POLLRDNORM)) &&
117902ac6454SAndrew Thompson 	    (fflags & FREAD)) {
118002ac6454SAndrew Thompson 
1181e13e19faSAndrew Thompson 		f = refs.rxfifo;
118202ac6454SAndrew Thompson 
118302ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
118402ac6454SAndrew Thompson 
1185e13e19faSAndrew Thompson 		if (!refs.is_usbfs) {
118602ac6454SAndrew Thompson 			if (f->flag_iserror) {
118702ac6454SAndrew Thompson 				/* we have and error */
118802ac6454SAndrew Thompson 				m = (void *)1;
118902ac6454SAndrew Thompson 			} else {
119002ac6454SAndrew Thompson 				if (f->queue_data == NULL) {
119102ac6454SAndrew Thompson 					/*
119202ac6454SAndrew Thompson 					 * start read transfer, if not
119302ac6454SAndrew Thompson 					 * already started
119402ac6454SAndrew Thompson 					 */
119502ac6454SAndrew Thompson 					(f->methods->f_start_read) (f);
119602ac6454SAndrew Thompson 				}
119702ac6454SAndrew Thompson 				/* check if any packets are available */
119802ac6454SAndrew Thompson 				USB_IF_POLL(&f->used_q, m);
119902ac6454SAndrew Thompson 			}
120002ac6454SAndrew Thompson 		} else {
120102ac6454SAndrew Thompson 			if (f->flag_iscomplete) {
120202ac6454SAndrew Thompson 				m = (void *)1;
120302ac6454SAndrew Thompson 			} else {
120402ac6454SAndrew Thompson 				m = NULL;
120502ac6454SAndrew Thompson 			}
120602ac6454SAndrew Thompson 		}
120702ac6454SAndrew Thompson 
120802ac6454SAndrew Thompson 		if (m) {
120902ac6454SAndrew Thompson 			revents |= events & (POLLIN | POLLRDNORM);
121002ac6454SAndrew Thompson 		} else {
121102ac6454SAndrew Thompson 			f->flag_isselect = 1;
121202ac6454SAndrew Thompson 			selrecord(td, &f->selinfo);
121302ac6454SAndrew Thompson 
1214e13e19faSAndrew Thompson 			if (!refs.is_usbfs) {
121502ac6454SAndrew Thompson 				/* start reading data */
121602ac6454SAndrew Thompson 				(f->methods->f_start_read) (f);
121702ac6454SAndrew Thompson 			}
121802ac6454SAndrew Thompson 		}
121902ac6454SAndrew Thompson 
122002ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
122102ac6454SAndrew Thompson 	}
1222a593f6b8SAndrew Thompson 	usb_unref_device(cpd, &refs);
122302ac6454SAndrew Thompson 	return (revents);
122402ac6454SAndrew Thompson }
122502ac6454SAndrew Thompson 
122602ac6454SAndrew Thompson static int
1227a593f6b8SAndrew Thompson usb_read(struct cdev *dev, struct uio *uio, int ioflag)
122802ac6454SAndrew Thompson {
1229e13e19faSAndrew Thompson 	struct usb_cdev_refdata refs;
1230760bc48eSAndrew Thompson 	struct usb_cdev_privdata* cpd;
1231760bc48eSAndrew Thompson 	struct usb_fifo *f;
1232760bc48eSAndrew Thompson 	struct usb_mbuf *m;
123302ac6454SAndrew Thompson 	int fflags;
123402ac6454SAndrew Thompson 	int resid;
123502ac6454SAndrew Thompson 	int io_len;
123602ac6454SAndrew Thompson 	int err;
123702ac6454SAndrew Thompson 	uint8_t tr_data = 0;
123802ac6454SAndrew Thompson 
1239ee3e3ff5SAndrew Thompson 	err = devfs_get_cdevpriv((void **)&cpd);
1240ee3e3ff5SAndrew Thompson 	if (err != 0)
1241ee3e3ff5SAndrew Thompson 		return (err);
124202ac6454SAndrew Thompson 
1243a593f6b8SAndrew Thompson 	err = usb_ref_device(cpd, &refs, 0 /* no uref */ );
124402ac6454SAndrew Thompson 	if (err) {
124502ac6454SAndrew Thompson 		return (ENXIO);
124602ac6454SAndrew Thompson 	}
1247ee3e3ff5SAndrew Thompson 	fflags = cpd->fflags;
1248ee3e3ff5SAndrew Thompson 
1249e13e19faSAndrew Thompson 	f = refs.rxfifo;
125002ac6454SAndrew Thompson 	if (f == NULL) {
125102ac6454SAndrew Thompson 		/* should not happen */
1252a593f6b8SAndrew Thompson 		usb_unref_device(cpd, &refs);
125302ac6454SAndrew Thompson 		return (EPERM);
125402ac6454SAndrew Thompson 	}
125502ac6454SAndrew Thompson 
1256ee3e3ff5SAndrew Thompson 	resid = uio->uio_resid;
125702ac6454SAndrew Thompson 
125802ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
125902ac6454SAndrew Thompson 
126002ac6454SAndrew Thompson 	/* check for permanent read error */
126102ac6454SAndrew Thompson 	if (f->flag_iserror) {
126202ac6454SAndrew Thompson 		err = EIO;
126302ac6454SAndrew Thompson 		goto done;
126402ac6454SAndrew Thompson 	}
126502ac6454SAndrew Thompson 	/* check if USB-FS interface is active */
1266e13e19faSAndrew Thompson 	if (refs.is_usbfs) {
126702ac6454SAndrew Thompson 		/*
126802ac6454SAndrew Thompson 		 * The queue is used for events that should be
126902ac6454SAndrew Thompson 		 * retrieved using the "USB_FS_COMPLETE" ioctl.
127002ac6454SAndrew Thompson 		 */
127102ac6454SAndrew Thompson 		err = EINVAL;
127202ac6454SAndrew Thompson 		goto done;
127302ac6454SAndrew Thompson 	}
127402ac6454SAndrew Thompson 	while (uio->uio_resid > 0) {
127502ac6454SAndrew Thompson 
127602ac6454SAndrew Thompson 		USB_IF_DEQUEUE(&f->used_q, m);
127702ac6454SAndrew Thompson 
127802ac6454SAndrew Thompson 		if (m == NULL) {
127902ac6454SAndrew Thompson 
128002ac6454SAndrew Thompson 			/* start read transfer, if not already started */
128102ac6454SAndrew Thompson 
128202ac6454SAndrew Thompson 			(f->methods->f_start_read) (f);
128302ac6454SAndrew Thompson 
128407532e49SAndrew Thompson 			if (ioflag & IO_NDELAY) {
128502ac6454SAndrew Thompson 				if (tr_data) {
128602ac6454SAndrew Thompson 					/* return length before error */
128702ac6454SAndrew Thompson 					break;
128802ac6454SAndrew Thompson 				}
128902ac6454SAndrew Thompson 				err = EWOULDBLOCK;
129002ac6454SAndrew Thompson 				break;
129102ac6454SAndrew Thompson 			}
129202ac6454SAndrew Thompson 			DPRINTF("sleeping\n");
129302ac6454SAndrew Thompson 
1294a593f6b8SAndrew Thompson 			err = usb_fifo_wait(f);
129502ac6454SAndrew Thompson 			if (err) {
129602ac6454SAndrew Thompson 				break;
129702ac6454SAndrew Thompson 			}
129802ac6454SAndrew Thompson 			continue;
129902ac6454SAndrew Thompson 		}
130002ac6454SAndrew Thompson 		if (f->methods->f_filter_read) {
130102ac6454SAndrew Thompson 			/*
130202ac6454SAndrew Thompson 			 * Sometimes it is convenient to process data at the
130302ac6454SAndrew Thompson 			 * expense of a userland process instead of a kernel
130402ac6454SAndrew Thompson 			 * process.
130502ac6454SAndrew Thompson 			 */
130602ac6454SAndrew Thompson 			(f->methods->f_filter_read) (f, m);
130702ac6454SAndrew Thompson 		}
130802ac6454SAndrew Thompson 		tr_data = 1;
130902ac6454SAndrew Thompson 
131002ac6454SAndrew Thompson 		io_len = MIN(m->cur_data_len, uio->uio_resid);
131102ac6454SAndrew Thompson 
131202ac6454SAndrew Thompson 		DPRINTFN(2, "transfer %d bytes from %p\n",
131302ac6454SAndrew Thompson 		    io_len, m->cur_data_ptr);
131402ac6454SAndrew Thompson 
1315a593f6b8SAndrew Thompson 		err = usb_fifo_uiomove(f,
131602ac6454SAndrew Thompson 		    m->cur_data_ptr, io_len, uio);
131702ac6454SAndrew Thompson 
131802ac6454SAndrew Thompson 		m->cur_data_len -= io_len;
131902ac6454SAndrew Thompson 		m->cur_data_ptr += io_len;
132002ac6454SAndrew Thompson 
132102ac6454SAndrew Thompson 		if (m->cur_data_len == 0) {
132202ac6454SAndrew Thompson 
132302ac6454SAndrew Thompson 			uint8_t last_packet;
132402ac6454SAndrew Thompson 
132502ac6454SAndrew Thompson 			last_packet = m->last_packet;
132602ac6454SAndrew Thompson 
132702ac6454SAndrew Thompson 			USB_IF_ENQUEUE(&f->free_q, m);
132802ac6454SAndrew Thompson 
132902ac6454SAndrew Thompson 			if (last_packet) {
133002ac6454SAndrew Thompson 				/* keep framing */
133102ac6454SAndrew Thompson 				break;
133202ac6454SAndrew Thompson 			}
133302ac6454SAndrew Thompson 		} else {
133402ac6454SAndrew Thompson 			USB_IF_PREPEND(&f->used_q, m);
133502ac6454SAndrew Thompson 		}
133602ac6454SAndrew Thompson 
133702ac6454SAndrew Thompson 		if (err) {
133802ac6454SAndrew Thompson 			break;
133902ac6454SAndrew Thompson 		}
134002ac6454SAndrew Thompson 	}
134102ac6454SAndrew Thompson done:
134202ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
134302ac6454SAndrew Thompson 
1344a593f6b8SAndrew Thompson 	usb_unref_device(cpd, &refs);
134502ac6454SAndrew Thompson 
134602ac6454SAndrew Thompson 	return (err);
134702ac6454SAndrew Thompson }
134802ac6454SAndrew Thompson 
134902ac6454SAndrew Thompson static int
1350a593f6b8SAndrew Thompson usb_write(struct cdev *dev, struct uio *uio, int ioflag)
135102ac6454SAndrew Thompson {
1352e13e19faSAndrew Thompson 	struct usb_cdev_refdata refs;
1353760bc48eSAndrew Thompson 	struct usb_cdev_privdata* cpd;
1354760bc48eSAndrew Thompson 	struct usb_fifo *f;
1355760bc48eSAndrew Thompson 	struct usb_mbuf *m;
1356bd73b187SAlfred Perlstein 	uint8_t *pdata;
135702ac6454SAndrew Thompson 	int fflags;
135802ac6454SAndrew Thompson 	int resid;
135902ac6454SAndrew Thompson 	int io_len;
136002ac6454SAndrew Thompson 	int err;
136102ac6454SAndrew Thompson 	uint8_t tr_data = 0;
136202ac6454SAndrew Thompson 
136302ac6454SAndrew Thompson 	DPRINTFN(2, "\n");
136402ac6454SAndrew Thompson 
1365ee3e3ff5SAndrew Thompson 	err = devfs_get_cdevpriv((void **)&cpd);
1366ee3e3ff5SAndrew Thompson 	if (err != 0)
1367ee3e3ff5SAndrew Thompson 		return (err);
136802ac6454SAndrew Thompson 
1369a593f6b8SAndrew Thompson 	err = usb_ref_device(cpd, &refs, 0 /* no uref */ );
137002ac6454SAndrew Thompson 	if (err) {
137102ac6454SAndrew Thompson 		return (ENXIO);
137202ac6454SAndrew Thompson 	}
1373ee3e3ff5SAndrew Thompson 	fflags = cpd->fflags;
1374ee3e3ff5SAndrew Thompson 
1375e13e19faSAndrew Thompson 	f = refs.txfifo;
137602ac6454SAndrew Thompson 	if (f == NULL) {
137702ac6454SAndrew Thompson 		/* should not happen */
1378a593f6b8SAndrew Thompson 		usb_unref_device(cpd, &refs);
137902ac6454SAndrew Thompson 		return (EPERM);
138002ac6454SAndrew Thompson 	}
138102ac6454SAndrew Thompson 	resid = uio->uio_resid;
138202ac6454SAndrew Thompson 
138302ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
138402ac6454SAndrew Thompson 
138502ac6454SAndrew Thompson 	/* check for permanent write error */
138602ac6454SAndrew Thompson 	if (f->flag_iserror) {
138702ac6454SAndrew Thompson 		err = EIO;
138802ac6454SAndrew Thompson 		goto done;
138902ac6454SAndrew Thompson 	}
139002ac6454SAndrew Thompson 	/* check if USB-FS interface is active */
1391e13e19faSAndrew Thompson 	if (refs.is_usbfs) {
139202ac6454SAndrew Thompson 		/*
139302ac6454SAndrew Thompson 		 * The queue is used for events that should be
139402ac6454SAndrew Thompson 		 * retrieved using the "USB_FS_COMPLETE" ioctl.
139502ac6454SAndrew Thompson 		 */
139602ac6454SAndrew Thompson 		err = EINVAL;
139702ac6454SAndrew Thompson 		goto done;
139802ac6454SAndrew Thompson 	}
139902ac6454SAndrew Thompson 	if (f->queue_data == NULL) {
140002ac6454SAndrew Thompson 		/* start write transfer, if not already started */
140102ac6454SAndrew Thompson 		(f->methods->f_start_write) (f);
140202ac6454SAndrew Thompson 	}
140302ac6454SAndrew Thompson 	/* we allow writing zero length data */
140402ac6454SAndrew Thompson 	do {
140502ac6454SAndrew Thompson 		USB_IF_DEQUEUE(&f->free_q, m);
140602ac6454SAndrew Thompson 
140702ac6454SAndrew Thompson 		if (m == NULL) {
140802ac6454SAndrew Thompson 
140907532e49SAndrew Thompson 			if (ioflag & IO_NDELAY) {
141002ac6454SAndrew Thompson 				if (tr_data) {
141102ac6454SAndrew Thompson 					/* return length before error */
141202ac6454SAndrew Thompson 					break;
141302ac6454SAndrew Thompson 				}
141402ac6454SAndrew Thompson 				err = EWOULDBLOCK;
141502ac6454SAndrew Thompson 				break;
141602ac6454SAndrew Thompson 			}
141702ac6454SAndrew Thompson 			DPRINTF("sleeping\n");
141802ac6454SAndrew Thompson 
1419a593f6b8SAndrew Thompson 			err = usb_fifo_wait(f);
142002ac6454SAndrew Thompson 			if (err) {
142102ac6454SAndrew Thompson 				break;
142202ac6454SAndrew Thompson 			}
142302ac6454SAndrew Thompson 			continue;
142402ac6454SAndrew Thompson 		}
142502ac6454SAndrew Thompson 		tr_data = 1;
142602ac6454SAndrew Thompson 
1427bd73b187SAlfred Perlstein 		if (f->flag_have_fragment == 0) {
142802ac6454SAndrew Thompson 			USB_MBUF_RESET(m);
1429bd73b187SAlfred Perlstein 			io_len = m->cur_data_len;
1430bd73b187SAlfred Perlstein 			pdata = m->cur_data_ptr;
1431bd73b187SAlfred Perlstein 			if (io_len > uio->uio_resid)
1432bd73b187SAlfred Perlstein 				io_len = uio->uio_resid;
143302ac6454SAndrew Thompson 			m->cur_data_len = io_len;
1434bd73b187SAlfred Perlstein 		} else {
1435bd73b187SAlfred Perlstein 			io_len = m->max_data_len - m->cur_data_len;
1436bd73b187SAlfred Perlstein 			pdata = m->cur_data_ptr + m->cur_data_len;
1437bd73b187SAlfred Perlstein 			if (io_len > uio->uio_resid)
1438bd73b187SAlfred Perlstein 				io_len = uio->uio_resid;
1439bd73b187SAlfred Perlstein 			m->cur_data_len += io_len;
1440bd73b187SAlfred Perlstein 		}
144102ac6454SAndrew Thompson 
144202ac6454SAndrew Thompson 		DPRINTFN(2, "transfer %d bytes to %p\n",
1443bd73b187SAlfred Perlstein 		    io_len, pdata);
144402ac6454SAndrew Thompson 
1445bd73b187SAlfred Perlstein 		err = usb_fifo_uiomove(f, pdata, io_len, uio);
144602ac6454SAndrew Thompson 
144702ac6454SAndrew Thompson 		if (err) {
1448bd73b187SAlfred Perlstein 			f->flag_have_fragment = 0;
144902ac6454SAndrew Thompson 			USB_IF_ENQUEUE(&f->free_q, m);
145002ac6454SAndrew Thompson 			break;
145102ac6454SAndrew Thompson 		}
1452bd73b187SAlfred Perlstein 
1453bd73b187SAlfred Perlstein 		/* check if the buffer is ready to be transmitted */
1454bd73b187SAlfred Perlstein 
1455bd73b187SAlfred Perlstein 		if ((f->flag_write_defrag == 0) ||
1456bd73b187SAlfred Perlstein 		    (m->cur_data_len == m->max_data_len)) {
1457bd73b187SAlfred Perlstein 			f->flag_have_fragment = 0;
1458bd73b187SAlfred Perlstein 
145902ac6454SAndrew Thompson 			/*
1460bd73b187SAlfred Perlstein 			 * Check for write filter:
1461bd73b187SAlfred Perlstein 			 *
1462bd73b187SAlfred Perlstein 			 * Sometimes it is convenient to process data
1463bd73b187SAlfred Perlstein 			 * at the expense of a userland process
1464bd73b187SAlfred Perlstein 			 * instead of a kernel process.
146502ac6454SAndrew Thompson 			 */
1466bd73b187SAlfred Perlstein 			if (f->methods->f_filter_write) {
146702ac6454SAndrew Thompson 				(f->methods->f_filter_write) (f, m);
146802ac6454SAndrew Thompson 			}
1469bd73b187SAlfred Perlstein 
1470bd73b187SAlfred Perlstein 			/* Put USB mbuf in the used queue */
147102ac6454SAndrew Thompson 			USB_IF_ENQUEUE(&f->used_q, m);
147202ac6454SAndrew Thompson 
1473bd73b187SAlfred Perlstein 			/* Start writing data, if not already started */
147402ac6454SAndrew Thompson 			(f->methods->f_start_write) (f);
1475bd73b187SAlfred Perlstein 		} else {
1476bd73b187SAlfred Perlstein 			/* Wait for more data or close */
1477bd73b187SAlfred Perlstein 			f->flag_have_fragment = 1;
1478bd73b187SAlfred Perlstein 			USB_IF_PREPEND(&f->free_q, m);
1479bd73b187SAlfred Perlstein 		}
148002ac6454SAndrew Thompson 
148102ac6454SAndrew Thompson 	} while (uio->uio_resid > 0);
148202ac6454SAndrew Thompson done:
148302ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
148402ac6454SAndrew Thompson 
1485a593f6b8SAndrew Thompson 	usb_unref_device(cpd, &refs);
148602ac6454SAndrew Thompson 
1487ee3e3ff5SAndrew Thompson 	return (err);
1488ee3e3ff5SAndrew Thompson }
148902ac6454SAndrew Thompson 
1490ee3e3ff5SAndrew Thompson int
1491a593f6b8SAndrew Thompson usb_static_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
1492ee3e3ff5SAndrew Thompson     struct thread *td)
1493ee3e3ff5SAndrew Thompson {
1494ee3e3ff5SAndrew Thompson 	union {
1495760bc48eSAndrew Thompson 		struct usb_read_dir *urd;
1496ee3e3ff5SAndrew Thompson 		void* data;
1497ee3e3ff5SAndrew Thompson 	} u;
1498ce4092bdSHans Petter Selasky 	int err;
1499ee3e3ff5SAndrew Thompson 
1500ee3e3ff5SAndrew Thompson 	u.data = data;
1501ee3e3ff5SAndrew Thompson 	switch (cmd) {
1502ee3e3ff5SAndrew Thompson 		case USB_READ_DIR:
1503a593f6b8SAndrew Thompson 			err = usb_read_symlink(u.urd->urd_data,
1504ee3e3ff5SAndrew Thompson 			    u.urd->urd_startentry, u.urd->urd_maxlen);
1505ee3e3ff5SAndrew Thompson 			break;
1506ee3e3ff5SAndrew Thompson 		case USB_DEV_QUIRK_GET:
1507ee3e3ff5SAndrew Thompson 		case USB_QUIRK_NAME_GET:
1508ee3e3ff5SAndrew Thompson 		case USB_DEV_QUIRK_ADD:
1509ee3e3ff5SAndrew Thompson 		case USB_DEV_QUIRK_REMOVE:
1510a593f6b8SAndrew Thompson 			err = usb_quirk_ioctl_p(cmd, data, fflag, td);
1511ee3e3ff5SAndrew Thompson 			break;
1512ee3e3ff5SAndrew Thompson 		case USB_GET_TEMPLATE:
1513a593f6b8SAndrew Thompson 			*(int *)data = usb_template;
1514ce4092bdSHans Petter Selasky 			err = 0;
1515ee3e3ff5SAndrew Thompson 			break;
1516ee3e3ff5SAndrew Thompson 		case USB_SET_TEMPLATE:
151750230f98SAndrew Thompson 			err = priv_check(curthread, PRIV_DRIVER);
1518ee3e3ff5SAndrew Thompson 			if (err)
1519ee3e3ff5SAndrew Thompson 				break;
1520a593f6b8SAndrew Thompson 			usb_template = *(int *)data;
1521ee3e3ff5SAndrew Thompson 			break;
1522ce4092bdSHans Petter Selasky 		default:
1523ce4092bdSHans Petter Selasky 			err = ENOTTY;
1524ce4092bdSHans Petter Selasky 			break;
1525ee3e3ff5SAndrew Thompson 	}
152602ac6454SAndrew Thompson 	return (err);
152702ac6454SAndrew Thompson }
152802ac6454SAndrew Thompson 
152902ac6454SAndrew Thompson static int
1530a593f6b8SAndrew Thompson usb_fifo_uiomove(struct usb_fifo *f, void *cp,
153102ac6454SAndrew Thompson     int n, struct uio *uio)
153202ac6454SAndrew Thompson {
153302ac6454SAndrew Thompson 	int error;
153402ac6454SAndrew Thompson 
153502ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
153602ac6454SAndrew Thompson 
153702ac6454SAndrew Thompson 	/*
153802ac6454SAndrew Thompson 	 * "uiomove()" can sleep so one needs to make a wrapper,
153902ac6454SAndrew Thompson 	 * exiting the mutex and checking things:
154002ac6454SAndrew Thompson 	 */
154102ac6454SAndrew Thompson 	error = uiomove(cp, n, uio);
154202ac6454SAndrew Thompson 
154302ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
154402ac6454SAndrew Thompson 
154502ac6454SAndrew Thompson 	return (error);
154602ac6454SAndrew Thompson }
154702ac6454SAndrew Thompson 
154802ac6454SAndrew Thompson int
1549a593f6b8SAndrew Thompson usb_fifo_wait(struct usb_fifo *f)
155002ac6454SAndrew Thompson {
155102ac6454SAndrew Thompson 	int err;
155202ac6454SAndrew Thompson 
155302ac6454SAndrew Thompson 	mtx_assert(f->priv_mtx, MA_OWNED);
155402ac6454SAndrew Thompson 
155502ac6454SAndrew Thompson 	if (f->flag_iserror) {
155602ac6454SAndrew Thompson 		/* we are gone */
155702ac6454SAndrew Thompson 		return (EIO);
155802ac6454SAndrew Thompson 	}
155902ac6454SAndrew Thompson 	f->flag_sleeping = 1;
156002ac6454SAndrew Thompson 
15618437751dSAndrew Thompson 	err = cv_wait_sig(&f->cv_io, f->priv_mtx);
156202ac6454SAndrew Thompson 
156302ac6454SAndrew Thompson 	if (f->flag_iserror) {
156402ac6454SAndrew Thompson 		/* we are gone */
156502ac6454SAndrew Thompson 		err = EIO;
156602ac6454SAndrew Thompson 	}
156702ac6454SAndrew Thompson 	return (err);
156802ac6454SAndrew Thompson }
156902ac6454SAndrew Thompson 
157002ac6454SAndrew Thompson void
1571a593f6b8SAndrew Thompson usb_fifo_signal(struct usb_fifo *f)
157202ac6454SAndrew Thompson {
157302ac6454SAndrew Thompson 	if (f->flag_sleeping) {
157402ac6454SAndrew Thompson 		f->flag_sleeping = 0;
15758437751dSAndrew Thompson 		cv_broadcast(&f->cv_io);
157602ac6454SAndrew Thompson 	}
157702ac6454SAndrew Thompson }
157802ac6454SAndrew Thompson 
157902ac6454SAndrew Thompson void
1580a593f6b8SAndrew Thompson usb_fifo_wakeup(struct usb_fifo *f)
158102ac6454SAndrew Thompson {
1582a593f6b8SAndrew Thompson 	usb_fifo_signal(f);
158302ac6454SAndrew Thompson 
158402ac6454SAndrew Thompson 	if (f->flag_isselect) {
158502ac6454SAndrew Thompson 		selwakeup(&f->selinfo);
158602ac6454SAndrew Thompson 		f->flag_isselect = 0;
158702ac6454SAndrew Thompson 	}
158802ac6454SAndrew Thompson 	if (f->async_p != NULL) {
158902ac6454SAndrew Thompson 		PROC_LOCK(f->async_p);
15908451d0ddSKip Macy 		kern_psignal(f->async_p, SIGIO);
159102ac6454SAndrew Thompson 		PROC_UNLOCK(f->async_p);
159202ac6454SAndrew Thompson 	}
159302ac6454SAndrew Thompson }
159402ac6454SAndrew Thompson 
159502ac6454SAndrew Thompson static int
1596a593f6b8SAndrew Thompson usb_fifo_dummy_open(struct usb_fifo *fifo, int fflags)
159702ac6454SAndrew Thompson {
159802ac6454SAndrew Thompson 	return (0);
159902ac6454SAndrew Thompson }
160002ac6454SAndrew Thompson 
160102ac6454SAndrew Thompson static void
1602a593f6b8SAndrew Thompson usb_fifo_dummy_close(struct usb_fifo *fifo, int fflags)
160302ac6454SAndrew Thompson {
160402ac6454SAndrew Thompson 	return;
160502ac6454SAndrew Thompson }
160602ac6454SAndrew Thompson 
160702ac6454SAndrew Thompson static int
1608a593f6b8SAndrew Thompson usb_fifo_dummy_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
160902ac6454SAndrew Thompson {
161002ac6454SAndrew Thompson 	return (ENOIOCTL);
161102ac6454SAndrew Thompson }
161202ac6454SAndrew Thompson 
161302ac6454SAndrew Thompson static void
1614a593f6b8SAndrew Thompson usb_fifo_dummy_cmd(struct usb_fifo *fifo)
161502ac6454SAndrew Thompson {
161602ac6454SAndrew Thompson 	fifo->flag_flushing = 0;	/* not flushing */
161702ac6454SAndrew Thompson }
161802ac6454SAndrew Thompson 
161902ac6454SAndrew Thompson static void
1620a593f6b8SAndrew Thompson usb_fifo_check_methods(struct usb_fifo_methods *pm)
162102ac6454SAndrew Thompson {
162202ac6454SAndrew Thompson 	/* check that all callback functions are OK */
162302ac6454SAndrew Thompson 
162402ac6454SAndrew Thompson 	if (pm->f_open == NULL)
1625a593f6b8SAndrew Thompson 		pm->f_open = &usb_fifo_dummy_open;
162602ac6454SAndrew Thompson 
162702ac6454SAndrew Thompson 	if (pm->f_close == NULL)
1628a593f6b8SAndrew Thompson 		pm->f_close = &usb_fifo_dummy_close;
162902ac6454SAndrew Thompson 
163002ac6454SAndrew Thompson 	if (pm->f_ioctl == NULL)
1631a593f6b8SAndrew Thompson 		pm->f_ioctl = &usb_fifo_dummy_ioctl;
163202ac6454SAndrew Thompson 
163302ac6454SAndrew Thompson 	if (pm->f_ioctl_post == NULL)
1634a593f6b8SAndrew Thompson 		pm->f_ioctl_post = &usb_fifo_dummy_ioctl;
163502ac6454SAndrew Thompson 
163602ac6454SAndrew Thompson 	if (pm->f_start_read == NULL)
1637a593f6b8SAndrew Thompson 		pm->f_start_read = &usb_fifo_dummy_cmd;
163802ac6454SAndrew Thompson 
163902ac6454SAndrew Thompson 	if (pm->f_stop_read == NULL)
1640a593f6b8SAndrew Thompson 		pm->f_stop_read = &usb_fifo_dummy_cmd;
164102ac6454SAndrew Thompson 
164202ac6454SAndrew Thompson 	if (pm->f_start_write == NULL)
1643a593f6b8SAndrew Thompson 		pm->f_start_write = &usb_fifo_dummy_cmd;
164402ac6454SAndrew Thompson 
164502ac6454SAndrew Thompson 	if (pm->f_stop_write == NULL)
1646a593f6b8SAndrew Thompson 		pm->f_stop_write = &usb_fifo_dummy_cmd;
164702ac6454SAndrew Thompson }
164802ac6454SAndrew Thompson 
164902ac6454SAndrew Thompson /*------------------------------------------------------------------------*
1650a593f6b8SAndrew Thompson  *	usb_fifo_attach
165102ac6454SAndrew Thompson  *
165202ac6454SAndrew Thompson  * The following function will create a duplex FIFO.
165302ac6454SAndrew Thompson  *
165402ac6454SAndrew Thompson  * Return values:
165502ac6454SAndrew Thompson  * 0: Success.
165602ac6454SAndrew Thompson  * Else: Failure.
165702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
165802ac6454SAndrew Thompson int
1659a593f6b8SAndrew Thompson usb_fifo_attach(struct usb_device *udev, void *priv_sc,
1660760bc48eSAndrew Thompson     struct mtx *priv_mtx, struct usb_fifo_methods *pm,
16616d917491SHans Petter Selasky     struct usb_fifo_sc *f_sc, uint16_t unit, int16_t subunit,
1662ee3e3ff5SAndrew Thompson     uint8_t iface_index, uid_t uid, gid_t gid, int mode)
166302ac6454SAndrew Thompson {
1664760bc48eSAndrew Thompson 	struct usb_fifo *f_tx;
1665760bc48eSAndrew Thompson 	struct usb_fifo *f_rx;
1666ee3e3ff5SAndrew Thompson 	char devname[32];
166702ac6454SAndrew Thompson 	uint8_t n;
166802ac6454SAndrew Thompson 
166902ac6454SAndrew Thompson 	f_sc->fp[USB_FIFO_TX] = NULL;
167002ac6454SAndrew Thompson 	f_sc->fp[USB_FIFO_RX] = NULL;
167102ac6454SAndrew Thompson 
167202ac6454SAndrew Thompson 	if (pm == NULL)
167302ac6454SAndrew Thompson 		return (EINVAL);
167402ac6454SAndrew Thompson 
167502ac6454SAndrew Thompson 	/* check the methods */
1676a593f6b8SAndrew Thompson 	usb_fifo_check_methods(pm);
167702ac6454SAndrew Thompson 
167802ac6454SAndrew Thompson 	if (priv_mtx == NULL)
167902ac6454SAndrew Thompson 		priv_mtx = &Giant;
168002ac6454SAndrew Thompson 
168102ac6454SAndrew Thompson 	/* search for a free FIFO slot */
168202ac6454SAndrew Thompson 	for (n = 0;; n += 2) {
168302ac6454SAndrew Thompson 
168402ac6454SAndrew Thompson 		if (n == USB_FIFO_MAX) {
168502ac6454SAndrew Thompson 			/* end of FIFOs reached */
168602ac6454SAndrew Thompson 			return (ENOMEM);
168702ac6454SAndrew Thompson 		}
168802ac6454SAndrew Thompson 		/* Check for TX FIFO */
168902ac6454SAndrew Thompson 		if (udev->fifo[n + USB_FIFO_TX] != NULL) {
169002ac6454SAndrew Thompson 			continue;
169102ac6454SAndrew Thompson 		}
169202ac6454SAndrew Thompson 		/* Check for RX FIFO */
169302ac6454SAndrew Thompson 		if (udev->fifo[n + USB_FIFO_RX] != NULL) {
169402ac6454SAndrew Thompson 			continue;
169502ac6454SAndrew Thompson 		}
169602ac6454SAndrew Thompson 		break;
169702ac6454SAndrew Thompson 	}
169802ac6454SAndrew Thompson 
1699a593f6b8SAndrew Thompson 	f_tx = usb_fifo_alloc();
1700a593f6b8SAndrew Thompson 	f_rx = usb_fifo_alloc();
170102ac6454SAndrew Thompson 
170202ac6454SAndrew Thompson 	if ((f_tx == NULL) || (f_rx == NULL)) {
1703a593f6b8SAndrew Thompson 		usb_fifo_free(f_tx);
1704a593f6b8SAndrew Thompson 		usb_fifo_free(f_rx);
170502ac6454SAndrew Thompson 		return (ENOMEM);
170602ac6454SAndrew Thompson 	}
170702ac6454SAndrew Thompson 	/* initialise FIFO structures */
170802ac6454SAndrew Thompson 
170902ac6454SAndrew Thompson 	f_tx->fifo_index = n + USB_FIFO_TX;
17102989a677SAndrew Thompson 	f_tx->dev_ep_index = -1;
171102ac6454SAndrew Thompson 	f_tx->priv_mtx = priv_mtx;
171202ac6454SAndrew Thompson 	f_tx->priv_sc0 = priv_sc;
171302ac6454SAndrew Thompson 	f_tx->methods = pm;
171402ac6454SAndrew Thompson 	f_tx->iface_index = iface_index;
171502ac6454SAndrew Thompson 	f_tx->udev = udev;
171602ac6454SAndrew Thompson 
171702ac6454SAndrew Thompson 	f_rx->fifo_index = n + USB_FIFO_RX;
17182989a677SAndrew Thompson 	f_rx->dev_ep_index = -1;
171902ac6454SAndrew Thompson 	f_rx->priv_mtx = priv_mtx;
172002ac6454SAndrew Thompson 	f_rx->priv_sc0 = priv_sc;
172102ac6454SAndrew Thompson 	f_rx->methods = pm;
172202ac6454SAndrew Thompson 	f_rx->iface_index = iface_index;
172302ac6454SAndrew Thompson 	f_rx->udev = udev;
172402ac6454SAndrew Thompson 
172502ac6454SAndrew Thompson 	f_sc->fp[USB_FIFO_TX] = f_tx;
172602ac6454SAndrew Thompson 	f_sc->fp[USB_FIFO_RX] = f_rx;
172702ac6454SAndrew Thompson 
1728a593f6b8SAndrew Thompson 	mtx_lock(&usb_ref_lock);
172902ac6454SAndrew Thompson 	udev->fifo[f_tx->fifo_index] = f_tx;
173002ac6454SAndrew Thompson 	udev->fifo[f_rx->fifo_index] = f_rx;
1731a593f6b8SAndrew Thompson 	mtx_unlock(&usb_ref_lock);
173202ac6454SAndrew Thompson 
173302ac6454SAndrew Thompson 	for (n = 0; n != 4; n++) {
173402ac6454SAndrew Thompson 
173502ac6454SAndrew Thompson 		if (pm->basename[n] == NULL) {
173602ac6454SAndrew Thompson 			continue;
173702ac6454SAndrew Thompson 		}
17386d917491SHans Petter Selasky 		if (subunit < 0) {
1739ee3e3ff5SAndrew Thompson 			if (snprintf(devname, sizeof(devname),
174002ac6454SAndrew Thompson 			    "%s%u%s", pm->basename[n],
174102ac6454SAndrew Thompson 			    unit, pm->postfix[n] ?
174202ac6454SAndrew Thompson 			    pm->postfix[n] : "")) {
174302ac6454SAndrew Thompson 				/* ignore */
174402ac6454SAndrew Thompson 			}
174502ac6454SAndrew Thompson 		} else {
1746ee3e3ff5SAndrew Thompson 			if (snprintf(devname, sizeof(devname),
17476d917491SHans Petter Selasky 			    "%s%u.%d%s", pm->basename[n],
174802ac6454SAndrew Thompson 			    unit, subunit, pm->postfix[n] ?
174902ac6454SAndrew Thompson 			    pm->postfix[n] : "")) {
175002ac6454SAndrew Thompson 				/* ignore */
175102ac6454SAndrew Thompson 			}
175202ac6454SAndrew Thompson 		}
175302ac6454SAndrew Thompson 
175402ac6454SAndrew Thompson 		/*
175502ac6454SAndrew Thompson 		 * Distribute the symbolic links into two FIFO structures:
175602ac6454SAndrew Thompson 		 */
175702ac6454SAndrew Thompson 		if (n & 1) {
175802ac6454SAndrew Thompson 			f_rx->symlink[n / 2] =
1759a593f6b8SAndrew Thompson 			    usb_alloc_symlink(devname);
176002ac6454SAndrew Thompson 		} else {
176102ac6454SAndrew Thompson 			f_tx->symlink[n / 2] =
1762a593f6b8SAndrew Thompson 			    usb_alloc_symlink(devname);
176302ac6454SAndrew Thompson 		}
1764ee3e3ff5SAndrew Thompson 
17652ffd5fdcSHans Petter Selasky 		/* Create the device */
17662ffd5fdcSHans Petter Selasky 		f_sc->dev = usb_make_dev(udev, devname, -1,
17672ffd5fdcSHans Petter Selasky 		    f_tx->fifo_index & f_rx->fifo_index,
17682ffd5fdcSHans Petter Selasky 		    FREAD|FWRITE, uid, gid, mode);
176902ac6454SAndrew Thompson 	}
177002ac6454SAndrew Thompson 
177102ac6454SAndrew Thompson 	DPRINTFN(2, "attached %p/%p\n", f_tx, f_rx);
177202ac6454SAndrew Thompson 	return (0);
177302ac6454SAndrew Thompson }
177402ac6454SAndrew Thompson 
177502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
1776a593f6b8SAndrew Thompson  *	usb_fifo_alloc_buffer
177702ac6454SAndrew Thompson  *
177802ac6454SAndrew Thompson  * Return values:
177902ac6454SAndrew Thompson  * 0: Success
178002ac6454SAndrew Thompson  * Else failure
178102ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
178202ac6454SAndrew Thompson int
1783a593f6b8SAndrew Thompson usb_fifo_alloc_buffer(struct usb_fifo *f, usb_size_t bufsize,
178402ac6454SAndrew Thompson     uint16_t nbuf)
178502ac6454SAndrew Thompson {
1786a593f6b8SAndrew Thompson 	usb_fifo_free_buffer(f);
178702ac6454SAndrew Thompson 
178802ac6454SAndrew Thompson 	/* allocate an endpoint */
178902ac6454SAndrew Thompson 	f->free_q.ifq_maxlen = nbuf;
179002ac6454SAndrew Thompson 	f->used_q.ifq_maxlen = nbuf;
179102ac6454SAndrew Thompson 
1792a593f6b8SAndrew Thompson 	f->queue_data = usb_alloc_mbufs(
179302ac6454SAndrew Thompson 	    M_USBDEV, &f->free_q, bufsize, nbuf);
179402ac6454SAndrew Thompson 
179502ac6454SAndrew Thompson 	if ((f->queue_data == NULL) && bufsize && nbuf) {
179602ac6454SAndrew Thompson 		return (ENOMEM);
179702ac6454SAndrew Thompson 	}
179802ac6454SAndrew Thompson 	return (0);			/* success */
179902ac6454SAndrew Thompson }
180002ac6454SAndrew Thompson 
180102ac6454SAndrew Thompson /*------------------------------------------------------------------------*
1802a593f6b8SAndrew Thompson  *	usb_fifo_free_buffer
180302ac6454SAndrew Thompson  *
180402ac6454SAndrew Thompson  * This function will free the buffers associated with a FIFO. This
180502ac6454SAndrew Thompson  * function can be called multiple times in a row.
180602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
180702ac6454SAndrew Thompson void
1808a593f6b8SAndrew Thompson usb_fifo_free_buffer(struct usb_fifo *f)
180902ac6454SAndrew Thompson {
181002ac6454SAndrew Thompson 	if (f->queue_data) {
181102ac6454SAndrew Thompson 		/* free old buffer */
181202ac6454SAndrew Thompson 		free(f->queue_data, M_USBDEV);
181302ac6454SAndrew Thompson 		f->queue_data = NULL;
181402ac6454SAndrew Thompson 	}
181502ac6454SAndrew Thompson 	/* reset queues */
181602ac6454SAndrew Thompson 
1817271ae033SHans Petter Selasky 	memset(&f->free_q, 0, sizeof(f->free_q));
1818271ae033SHans Petter Selasky 	memset(&f->used_q, 0, sizeof(f->used_q));
181902ac6454SAndrew Thompson }
182002ac6454SAndrew Thompson 
182102ac6454SAndrew Thompson void
1822a593f6b8SAndrew Thompson usb_fifo_detach(struct usb_fifo_sc *f_sc)
182302ac6454SAndrew Thompson {
182402ac6454SAndrew Thompson 	if (f_sc == NULL) {
182502ac6454SAndrew Thompson 		return;
182602ac6454SAndrew Thompson 	}
1827a593f6b8SAndrew Thompson 	usb_fifo_free(f_sc->fp[USB_FIFO_TX]);
1828a593f6b8SAndrew Thompson 	usb_fifo_free(f_sc->fp[USB_FIFO_RX]);
182902ac6454SAndrew Thompson 
183002ac6454SAndrew Thompson 	f_sc->fp[USB_FIFO_TX] = NULL;
183102ac6454SAndrew Thompson 	f_sc->fp[USB_FIFO_RX] = NULL;
183202ac6454SAndrew Thompson 
18332ffd5fdcSHans Petter Selasky 	usb_destroy_dev(f_sc->dev);
18342ffd5fdcSHans Petter Selasky 
18357214348fSAndrew Thompson 	f_sc->dev = NULL;
1836ee3e3ff5SAndrew Thompson 
183702ac6454SAndrew Thompson 	DPRINTFN(2, "detached %p\n", f_sc);
183802ac6454SAndrew Thompson }
183902ac6454SAndrew Thompson 
1840f9cb546cSAndrew Thompson usb_size_t
1841a593f6b8SAndrew Thompson usb_fifo_put_bytes_max(struct usb_fifo *f)
184202ac6454SAndrew Thompson {
1843760bc48eSAndrew Thompson 	struct usb_mbuf *m;
1844f9cb546cSAndrew Thompson 	usb_size_t len;
184502ac6454SAndrew Thompson 
184602ac6454SAndrew Thompson 	USB_IF_POLL(&f->free_q, m);
184702ac6454SAndrew Thompson 
184802ac6454SAndrew Thompson 	if (m) {
184902ac6454SAndrew Thompson 		len = m->max_data_len;
185002ac6454SAndrew Thompson 	} else {
185102ac6454SAndrew Thompson 		len = 0;
185202ac6454SAndrew Thompson 	}
185302ac6454SAndrew Thompson 	return (len);
185402ac6454SAndrew Thompson }
185502ac6454SAndrew Thompson 
185602ac6454SAndrew Thompson /*------------------------------------------------------------------------*
1857a593f6b8SAndrew Thompson  *	usb_fifo_put_data
185802ac6454SAndrew Thompson  *
185902ac6454SAndrew Thompson  * what:
186002ac6454SAndrew Thompson  *  0 - normal operation
186102ac6454SAndrew Thompson  *  1 - set last packet flag to enforce framing
186202ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
186302ac6454SAndrew Thompson void
1864a593f6b8SAndrew Thompson usb_fifo_put_data(struct usb_fifo *f, struct usb_page_cache *pc,
1865e0a69b51SAndrew Thompson     usb_frlength_t offset, usb_frlength_t len, uint8_t what)
186602ac6454SAndrew Thompson {
1867760bc48eSAndrew Thompson 	struct usb_mbuf *m;
1868e0a69b51SAndrew Thompson 	usb_frlength_t io_len;
186902ac6454SAndrew Thompson 
187002ac6454SAndrew Thompson 	while (len || (what == 1)) {
187102ac6454SAndrew Thompson 
187202ac6454SAndrew Thompson 		USB_IF_DEQUEUE(&f->free_q, m);
187302ac6454SAndrew Thompson 
187402ac6454SAndrew Thompson 		if (m) {
187502ac6454SAndrew Thompson 			USB_MBUF_RESET(m);
187602ac6454SAndrew Thompson 
187702ac6454SAndrew Thompson 			io_len = MIN(len, m->cur_data_len);
187802ac6454SAndrew Thompson 
1879a593f6b8SAndrew Thompson 			usbd_copy_out(pc, offset, m->cur_data_ptr, io_len);
188002ac6454SAndrew Thompson 
188102ac6454SAndrew Thompson 			m->cur_data_len = io_len;
188202ac6454SAndrew Thompson 			offset += io_len;
188302ac6454SAndrew Thompson 			len -= io_len;
188402ac6454SAndrew Thompson 
188502ac6454SAndrew Thompson 			if ((len == 0) && (what == 1)) {
188602ac6454SAndrew Thompson 				m->last_packet = 1;
188702ac6454SAndrew Thompson 			}
188802ac6454SAndrew Thompson 			USB_IF_ENQUEUE(&f->used_q, m);
188902ac6454SAndrew Thompson 
1890a593f6b8SAndrew Thompson 			usb_fifo_wakeup(f);
189102ac6454SAndrew Thompson 
189202ac6454SAndrew Thompson 			if ((len == 0) || (what == 1)) {
189302ac6454SAndrew Thompson 				break;
189402ac6454SAndrew Thompson 			}
189502ac6454SAndrew Thompson 		} else {
189602ac6454SAndrew Thompson 			break;
189702ac6454SAndrew Thompson 		}
189802ac6454SAndrew Thompson 	}
189902ac6454SAndrew Thompson }
190002ac6454SAndrew Thompson 
190102ac6454SAndrew Thompson void
1902a593f6b8SAndrew Thompson usb_fifo_put_data_linear(struct usb_fifo *f, void *ptr,
1903f9cb546cSAndrew Thompson     usb_size_t len, uint8_t what)
190402ac6454SAndrew Thompson {
1905760bc48eSAndrew Thompson 	struct usb_mbuf *m;
1906f9cb546cSAndrew Thompson 	usb_size_t io_len;
190702ac6454SAndrew Thompson 
190802ac6454SAndrew Thompson 	while (len || (what == 1)) {
190902ac6454SAndrew Thompson 
191002ac6454SAndrew Thompson 		USB_IF_DEQUEUE(&f->free_q, m);
191102ac6454SAndrew Thompson 
191202ac6454SAndrew Thompson 		if (m) {
191302ac6454SAndrew Thompson 			USB_MBUF_RESET(m);
191402ac6454SAndrew Thompson 
191502ac6454SAndrew Thompson 			io_len = MIN(len, m->cur_data_len);
191602ac6454SAndrew Thompson 
1917271ae033SHans Petter Selasky 			memcpy(m->cur_data_ptr, ptr, io_len);
191802ac6454SAndrew Thompson 
191902ac6454SAndrew Thompson 			m->cur_data_len = io_len;
192002ac6454SAndrew Thompson 			ptr = USB_ADD_BYTES(ptr, io_len);
192102ac6454SAndrew Thompson 			len -= io_len;
192202ac6454SAndrew Thompson 
192302ac6454SAndrew Thompson 			if ((len == 0) && (what == 1)) {
192402ac6454SAndrew Thompson 				m->last_packet = 1;
192502ac6454SAndrew Thompson 			}
192602ac6454SAndrew Thompson 			USB_IF_ENQUEUE(&f->used_q, m);
192702ac6454SAndrew Thompson 
1928a593f6b8SAndrew Thompson 			usb_fifo_wakeup(f);
192902ac6454SAndrew Thompson 
193002ac6454SAndrew Thompson 			if ((len == 0) || (what == 1)) {
193102ac6454SAndrew Thompson 				break;
193202ac6454SAndrew Thompson 			}
193302ac6454SAndrew Thompson 		} else {
193402ac6454SAndrew Thompson 			break;
193502ac6454SAndrew Thompson 		}
193602ac6454SAndrew Thompson 	}
193702ac6454SAndrew Thompson }
193802ac6454SAndrew Thompson 
193902ac6454SAndrew Thompson uint8_t
1940a593f6b8SAndrew Thompson usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len)
194102ac6454SAndrew Thompson {
1942760bc48eSAndrew Thompson 	struct usb_mbuf *m;
194302ac6454SAndrew Thompson 
194402ac6454SAndrew Thompson 	USB_IF_DEQUEUE(&f->free_q, m);
194502ac6454SAndrew Thompson 
194602ac6454SAndrew Thompson 	if (m) {
194702ac6454SAndrew Thompson 		m->cur_data_len = len;
194802ac6454SAndrew Thompson 		m->cur_data_ptr = ptr;
194902ac6454SAndrew Thompson 		USB_IF_ENQUEUE(&f->used_q, m);
1950a593f6b8SAndrew Thompson 		usb_fifo_wakeup(f);
195102ac6454SAndrew Thompson 		return (1);
195202ac6454SAndrew Thompson 	}
195302ac6454SAndrew Thompson 	return (0);
195402ac6454SAndrew Thompson }
195502ac6454SAndrew Thompson 
195602ac6454SAndrew Thompson void
1957a593f6b8SAndrew Thompson usb_fifo_put_data_error(struct usb_fifo *f)
195802ac6454SAndrew Thompson {
195902ac6454SAndrew Thompson 	f->flag_iserror = 1;
1960a593f6b8SAndrew Thompson 	usb_fifo_wakeup(f);
196102ac6454SAndrew Thompson }
196202ac6454SAndrew Thompson 
196302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
1964a593f6b8SAndrew Thompson  *	usb_fifo_get_data
196502ac6454SAndrew Thompson  *
196602ac6454SAndrew Thompson  * what:
196702ac6454SAndrew Thompson  *  0 - normal operation
1968760bc48eSAndrew Thompson  *  1 - only get one "usb_mbuf"
196902ac6454SAndrew Thompson  *
197002ac6454SAndrew Thompson  * returns:
197102ac6454SAndrew Thompson  *  0 - no more data
197202ac6454SAndrew Thompson  *  1 - data in buffer
197302ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
197402ac6454SAndrew Thompson uint8_t
1975a593f6b8SAndrew Thompson usb_fifo_get_data(struct usb_fifo *f, struct usb_page_cache *pc,
1976e0a69b51SAndrew Thompson     usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen,
197702ac6454SAndrew Thompson     uint8_t what)
197802ac6454SAndrew Thompson {
1979760bc48eSAndrew Thompson 	struct usb_mbuf *m;
1980e0a69b51SAndrew Thompson 	usb_frlength_t io_len;
198102ac6454SAndrew Thompson 	uint8_t tr_data = 0;
198202ac6454SAndrew Thompson 
198302ac6454SAndrew Thompson 	actlen[0] = 0;
198402ac6454SAndrew Thompson 
198502ac6454SAndrew Thompson 	while (1) {
198602ac6454SAndrew Thompson 
198702ac6454SAndrew Thompson 		USB_IF_DEQUEUE(&f->used_q, m);
198802ac6454SAndrew Thompson 
198902ac6454SAndrew Thompson 		if (m) {
199002ac6454SAndrew Thompson 
199102ac6454SAndrew Thompson 			tr_data = 1;
199202ac6454SAndrew Thompson 
199302ac6454SAndrew Thompson 			io_len = MIN(len, m->cur_data_len);
199402ac6454SAndrew Thompson 
1995a593f6b8SAndrew Thompson 			usbd_copy_in(pc, offset, m->cur_data_ptr, io_len);
199602ac6454SAndrew Thompson 
199702ac6454SAndrew Thompson 			len -= io_len;
199802ac6454SAndrew Thompson 			offset += io_len;
199902ac6454SAndrew Thompson 			actlen[0] += io_len;
200002ac6454SAndrew Thompson 			m->cur_data_ptr += io_len;
200102ac6454SAndrew Thompson 			m->cur_data_len -= io_len;
200202ac6454SAndrew Thompson 
200302ac6454SAndrew Thompson 			if ((m->cur_data_len == 0) || (what == 1)) {
200402ac6454SAndrew Thompson 				USB_IF_ENQUEUE(&f->free_q, m);
200502ac6454SAndrew Thompson 
2006a593f6b8SAndrew Thompson 				usb_fifo_wakeup(f);
200702ac6454SAndrew Thompson 
200802ac6454SAndrew Thompson 				if (what == 1) {
200902ac6454SAndrew Thompson 					break;
201002ac6454SAndrew Thompson 				}
201102ac6454SAndrew Thompson 			} else {
201202ac6454SAndrew Thompson 				USB_IF_PREPEND(&f->used_q, m);
201302ac6454SAndrew Thompson 			}
201402ac6454SAndrew Thompson 		} else {
201502ac6454SAndrew Thompson 
201602ac6454SAndrew Thompson 			if (tr_data) {
201702ac6454SAndrew Thompson 				/* wait for data to be written out */
201802ac6454SAndrew Thompson 				break;
201902ac6454SAndrew Thompson 			}
202002ac6454SAndrew Thompson 			if (f->flag_flushing) {
20217214348fSAndrew Thompson 				/* check if we should send a short packet */
20227214348fSAndrew Thompson 				if (f->flag_short != 0) {
20237214348fSAndrew Thompson 					f->flag_short = 0;
20247214348fSAndrew Thompson 					tr_data = 1;
20257214348fSAndrew Thompson 					break;
20267214348fSAndrew Thompson 				}
20277214348fSAndrew Thompson 				/* flushing complete */
202802ac6454SAndrew Thompson 				f->flag_flushing = 0;
2029a593f6b8SAndrew Thompson 				usb_fifo_wakeup(f);
203002ac6454SAndrew Thompson 			}
203102ac6454SAndrew Thompson 			break;
203202ac6454SAndrew Thompson 		}
203302ac6454SAndrew Thompson 		if (len == 0) {
203402ac6454SAndrew Thompson 			break;
203502ac6454SAndrew Thompson 		}
203602ac6454SAndrew Thompson 	}
203702ac6454SAndrew Thompson 	return (tr_data);
203802ac6454SAndrew Thompson }
203902ac6454SAndrew Thompson 
204002ac6454SAndrew Thompson uint8_t
2041a593f6b8SAndrew Thompson usb_fifo_get_data_linear(struct usb_fifo *f, void *ptr,
2042f9cb546cSAndrew Thompson     usb_size_t len, usb_size_t *actlen, uint8_t what)
204302ac6454SAndrew Thompson {
2044760bc48eSAndrew Thompson 	struct usb_mbuf *m;
2045f9cb546cSAndrew Thompson 	usb_size_t io_len;
204602ac6454SAndrew Thompson 	uint8_t tr_data = 0;
204702ac6454SAndrew Thompson 
204802ac6454SAndrew Thompson 	actlen[0] = 0;
204902ac6454SAndrew Thompson 
205002ac6454SAndrew Thompson 	while (1) {
205102ac6454SAndrew Thompson 
205202ac6454SAndrew Thompson 		USB_IF_DEQUEUE(&f->used_q, m);
205302ac6454SAndrew Thompson 
205402ac6454SAndrew Thompson 		if (m) {
205502ac6454SAndrew Thompson 
205602ac6454SAndrew Thompson 			tr_data = 1;
205702ac6454SAndrew Thompson 
205802ac6454SAndrew Thompson 			io_len = MIN(len, m->cur_data_len);
205902ac6454SAndrew Thompson 
2060271ae033SHans Petter Selasky 			memcpy(ptr, m->cur_data_ptr, io_len);
206102ac6454SAndrew Thompson 
206202ac6454SAndrew Thompson 			len -= io_len;
206302ac6454SAndrew Thompson 			ptr = USB_ADD_BYTES(ptr, io_len);
206402ac6454SAndrew Thompson 			actlen[0] += io_len;
206502ac6454SAndrew Thompson 			m->cur_data_ptr += io_len;
206602ac6454SAndrew Thompson 			m->cur_data_len -= io_len;
206702ac6454SAndrew Thompson 
206802ac6454SAndrew Thompson 			if ((m->cur_data_len == 0) || (what == 1)) {
206902ac6454SAndrew Thompson 				USB_IF_ENQUEUE(&f->free_q, m);
207002ac6454SAndrew Thompson 
2071a593f6b8SAndrew Thompson 				usb_fifo_wakeup(f);
207202ac6454SAndrew Thompson 
207302ac6454SAndrew Thompson 				if (what == 1) {
207402ac6454SAndrew Thompson 					break;
207502ac6454SAndrew Thompson 				}
207602ac6454SAndrew Thompson 			} else {
207702ac6454SAndrew Thompson 				USB_IF_PREPEND(&f->used_q, m);
207802ac6454SAndrew Thompson 			}
207902ac6454SAndrew Thompson 		} else {
208002ac6454SAndrew Thompson 
208102ac6454SAndrew Thompson 			if (tr_data) {
208202ac6454SAndrew Thompson 				/* wait for data to be written out */
208302ac6454SAndrew Thompson 				break;
208402ac6454SAndrew Thompson 			}
208502ac6454SAndrew Thompson 			if (f->flag_flushing) {
20867214348fSAndrew Thompson 				/* check if we should send a short packet */
20877214348fSAndrew Thompson 				if (f->flag_short != 0) {
20887214348fSAndrew Thompson 					f->flag_short = 0;
20897214348fSAndrew Thompson 					tr_data = 1;
20907214348fSAndrew Thompson 					break;
20917214348fSAndrew Thompson 				}
20927214348fSAndrew Thompson 				/* flushing complete */
209302ac6454SAndrew Thompson 				f->flag_flushing = 0;
2094a593f6b8SAndrew Thompson 				usb_fifo_wakeup(f);
209502ac6454SAndrew Thompson 			}
209602ac6454SAndrew Thompson 			break;
209702ac6454SAndrew Thompson 		}
209802ac6454SAndrew Thompson 		if (len == 0) {
209902ac6454SAndrew Thompson 			break;
210002ac6454SAndrew Thompson 		}
210102ac6454SAndrew Thompson 	}
210202ac6454SAndrew Thompson 	return (tr_data);
210302ac6454SAndrew Thompson }
210402ac6454SAndrew Thompson 
210502ac6454SAndrew Thompson uint8_t
2106a593f6b8SAndrew Thompson usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr, usb_size_t *plen)
210702ac6454SAndrew Thompson {
2108760bc48eSAndrew Thompson 	struct usb_mbuf *m;
210902ac6454SAndrew Thompson 
211002ac6454SAndrew Thompson 	USB_IF_POLL(&f->used_q, m);
211102ac6454SAndrew Thompson 
211202ac6454SAndrew Thompson 	if (m) {
211302ac6454SAndrew Thompson 		*plen = m->cur_data_len;
211402ac6454SAndrew Thompson 		*pptr = m->cur_data_ptr;
211502ac6454SAndrew Thompson 
211602ac6454SAndrew Thompson 		return (1);
211702ac6454SAndrew Thompson 	}
211802ac6454SAndrew Thompson 	return (0);
211902ac6454SAndrew Thompson }
212002ac6454SAndrew Thompson 
212102ac6454SAndrew Thompson void
2122a593f6b8SAndrew Thompson usb_fifo_get_data_error(struct usb_fifo *f)
212302ac6454SAndrew Thompson {
212402ac6454SAndrew Thompson 	f->flag_iserror = 1;
2125a593f6b8SAndrew Thompson 	usb_fifo_wakeup(f);
212602ac6454SAndrew Thompson }
212702ac6454SAndrew Thompson 
212802ac6454SAndrew Thompson /*------------------------------------------------------------------------*
2129a593f6b8SAndrew Thompson  *	usb_alloc_symlink
213002ac6454SAndrew Thompson  *
213102ac6454SAndrew Thompson  * Return values:
213202ac6454SAndrew Thompson  * NULL: Failure
213302ac6454SAndrew Thompson  * Else: Pointer to symlink entry
213402ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
2135760bc48eSAndrew Thompson struct usb_symlink *
2136a593f6b8SAndrew Thompson usb_alloc_symlink(const char *target)
213702ac6454SAndrew Thompson {
2138760bc48eSAndrew Thompson 	struct usb_symlink *ps;
213902ac6454SAndrew Thompson 
214002ac6454SAndrew Thompson 	ps = malloc(sizeof(*ps), M_USBDEV, M_WAITOK);
214102ac6454SAndrew Thompson 	if (ps == NULL) {
214202ac6454SAndrew Thompson 		return (ps);
214302ac6454SAndrew Thompson 	}
2144ee3e3ff5SAndrew Thompson 	/* XXX no longer needed */
2145ee3e3ff5SAndrew Thompson 	strlcpy(ps->src_path, target, sizeof(ps->src_path));
2146ee3e3ff5SAndrew Thompson 	ps->src_len = strlen(ps->src_path);
214702ac6454SAndrew Thompson 	strlcpy(ps->dst_path, target, sizeof(ps->dst_path));
214802ac6454SAndrew Thompson 	ps->dst_len = strlen(ps->dst_path);
214902ac6454SAndrew Thompson 
2150a593f6b8SAndrew Thompson 	sx_xlock(&usb_sym_lock);
2151a593f6b8SAndrew Thompson 	TAILQ_INSERT_TAIL(&usb_sym_head, ps, sym_entry);
2152a593f6b8SAndrew Thompson 	sx_unlock(&usb_sym_lock);
215302ac6454SAndrew Thompson 	return (ps);
215402ac6454SAndrew Thompson }
215502ac6454SAndrew Thompson 
215602ac6454SAndrew Thompson /*------------------------------------------------------------------------*
2157a593f6b8SAndrew Thompson  *	usb_free_symlink
215802ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
215902ac6454SAndrew Thompson void
2160a593f6b8SAndrew Thompson usb_free_symlink(struct usb_symlink *ps)
216102ac6454SAndrew Thompson {
216202ac6454SAndrew Thompson 	if (ps == NULL) {
216302ac6454SAndrew Thompson 		return;
216402ac6454SAndrew Thompson 	}
2165a593f6b8SAndrew Thompson 	sx_xlock(&usb_sym_lock);
2166a593f6b8SAndrew Thompson 	TAILQ_REMOVE(&usb_sym_head, ps, sym_entry);
2167a593f6b8SAndrew Thompson 	sx_unlock(&usb_sym_lock);
216802ac6454SAndrew Thompson 
216902ac6454SAndrew Thompson 	free(ps, M_USBDEV);
217002ac6454SAndrew Thompson }
217102ac6454SAndrew Thompson 
217202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
2173a593f6b8SAndrew Thompson  *	usb_read_symlink
217402ac6454SAndrew Thompson  *
217502ac6454SAndrew Thompson  * Return value:
217602ac6454SAndrew Thompson  * 0: Success
217702ac6454SAndrew Thompson  * Else: Failure
217802ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
217902ac6454SAndrew Thompson int
2180a593f6b8SAndrew Thompson usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len)
218102ac6454SAndrew Thompson {
2182760bc48eSAndrew Thompson 	struct usb_symlink *ps;
218302ac6454SAndrew Thompson 	uint32_t temp;
218402ac6454SAndrew Thompson 	uint32_t delta = 0;
218502ac6454SAndrew Thompson 	uint8_t len;
218602ac6454SAndrew Thompson 	int error = 0;
218702ac6454SAndrew Thompson 
2188a593f6b8SAndrew Thompson 	sx_xlock(&usb_sym_lock);
218902ac6454SAndrew Thompson 
2190a593f6b8SAndrew Thompson 	TAILQ_FOREACH(ps, &usb_sym_head, sym_entry) {
219102ac6454SAndrew Thompson 
219202ac6454SAndrew Thompson 		/*
219302ac6454SAndrew Thompson 		 * Compute total length of source and destination symlink
219402ac6454SAndrew Thompson 		 * strings pluss one length byte and two NUL bytes:
219502ac6454SAndrew Thompson 		 */
219602ac6454SAndrew Thompson 		temp = ps->src_len + ps->dst_len + 3;
219702ac6454SAndrew Thompson 
219802ac6454SAndrew Thompson 		if (temp > 255) {
219902ac6454SAndrew Thompson 			/*
220002ac6454SAndrew Thompson 			 * Skip entry because this length cannot fit
220102ac6454SAndrew Thompson 			 * into one byte:
220202ac6454SAndrew Thompson 			 */
220302ac6454SAndrew Thompson 			continue;
220402ac6454SAndrew Thompson 		}
220502ac6454SAndrew Thompson 		if (startentry != 0) {
220602ac6454SAndrew Thompson 			/* decrement read offset */
220702ac6454SAndrew Thompson 			startentry--;
220802ac6454SAndrew Thompson 			continue;
220902ac6454SAndrew Thompson 		}
221002ac6454SAndrew Thompson 		if (temp > user_len) {
221102ac6454SAndrew Thompson 			/* out of buffer space */
221202ac6454SAndrew Thompson 			break;
221302ac6454SAndrew Thompson 		}
221402ac6454SAndrew Thompson 		len = temp;
221502ac6454SAndrew Thompson 
221602ac6454SAndrew Thompson 		/* copy out total length */
221702ac6454SAndrew Thompson 
221802ac6454SAndrew Thompson 		error = copyout(&len,
221902ac6454SAndrew Thompson 		    USB_ADD_BYTES(user_ptr, delta), 1);
222002ac6454SAndrew Thompson 		if (error) {
222102ac6454SAndrew Thompson 			break;
222202ac6454SAndrew Thompson 		}
222302ac6454SAndrew Thompson 		delta += 1;
222402ac6454SAndrew Thompson 
222502ac6454SAndrew Thompson 		/* copy out source string */
222602ac6454SAndrew Thompson 
222702ac6454SAndrew Thompson 		error = copyout(ps->src_path,
222802ac6454SAndrew Thompson 		    USB_ADD_BYTES(user_ptr, delta), ps->src_len);
222902ac6454SAndrew Thompson 		if (error) {
223002ac6454SAndrew Thompson 			break;
223102ac6454SAndrew Thompson 		}
223202ac6454SAndrew Thompson 		len = 0;
223302ac6454SAndrew Thompson 		delta += ps->src_len;
223402ac6454SAndrew Thompson 		error = copyout(&len,
223502ac6454SAndrew Thompson 		    USB_ADD_BYTES(user_ptr, delta), 1);
223602ac6454SAndrew Thompson 		if (error) {
223702ac6454SAndrew Thompson 			break;
223802ac6454SAndrew Thompson 		}
223902ac6454SAndrew Thompson 		delta += 1;
224002ac6454SAndrew Thompson 
224102ac6454SAndrew Thompson 		/* copy out destination string */
224202ac6454SAndrew Thompson 
224302ac6454SAndrew Thompson 		error = copyout(ps->dst_path,
224402ac6454SAndrew Thompson 		    USB_ADD_BYTES(user_ptr, delta), ps->dst_len);
224502ac6454SAndrew Thompson 		if (error) {
224602ac6454SAndrew Thompson 			break;
224702ac6454SAndrew Thompson 		}
224802ac6454SAndrew Thompson 		len = 0;
224902ac6454SAndrew Thompson 		delta += ps->dst_len;
225002ac6454SAndrew Thompson 		error = copyout(&len,
225102ac6454SAndrew Thompson 		    USB_ADD_BYTES(user_ptr, delta), 1);
225202ac6454SAndrew Thompson 		if (error) {
225302ac6454SAndrew Thompson 			break;
225402ac6454SAndrew Thompson 		}
225502ac6454SAndrew Thompson 		delta += 1;
225602ac6454SAndrew Thompson 
225702ac6454SAndrew Thompson 		user_len -= temp;
225802ac6454SAndrew Thompson 	}
225902ac6454SAndrew Thompson 
226002ac6454SAndrew Thompson 	/* a zero length entry indicates the end */
226102ac6454SAndrew Thompson 
226202ac6454SAndrew Thompson 	if ((user_len != 0) && (error == 0)) {
226302ac6454SAndrew Thompson 
226402ac6454SAndrew Thompson 		len = 0;
226502ac6454SAndrew Thompson 
226602ac6454SAndrew Thompson 		error = copyout(&len,
226702ac6454SAndrew Thompson 		    USB_ADD_BYTES(user_ptr, delta), 1);
226802ac6454SAndrew Thompson 	}
2269a593f6b8SAndrew Thompson 	sx_unlock(&usb_sym_lock);
227002ac6454SAndrew Thompson 	return (error);
227102ac6454SAndrew Thompson }
22727214348fSAndrew Thompson 
22737214348fSAndrew Thompson void
2274a593f6b8SAndrew Thompson usb_fifo_set_close_zlp(struct usb_fifo *f, uint8_t onoff)
22757214348fSAndrew Thompson {
22767214348fSAndrew Thompson 	if (f == NULL)
22777214348fSAndrew Thompson 		return;
22787214348fSAndrew Thompson 
22797214348fSAndrew Thompson 	/* send a Zero Length Packet, ZLP, before close */
22807214348fSAndrew Thompson 	f->flag_short = onoff;
22817214348fSAndrew Thompson }
2282ed6d949aSAndrew Thompson 
2283bd73b187SAlfred Perlstein void
2284bd73b187SAlfred Perlstein usb_fifo_set_write_defrag(struct usb_fifo *f, uint8_t onoff)
2285bd73b187SAlfred Perlstein {
2286bd73b187SAlfred Perlstein 	if (f == NULL)
2287bd73b187SAlfred Perlstein 		return;
2288bd73b187SAlfred Perlstein 
2289bd73b187SAlfred Perlstein 	/* defrag written data */
2290bd73b187SAlfred Perlstein 	f->flag_write_defrag = onoff;
2291bd73b187SAlfred Perlstein 	/* reset defrag state */
2292bd73b187SAlfred Perlstein 	f->flag_have_fragment = 0;
2293bd73b187SAlfred Perlstein }
2294bd73b187SAlfred Perlstein 
2295ed6d949aSAndrew Thompson void *
2296ed6d949aSAndrew Thompson usb_fifo_softc(struct usb_fifo *f)
2297ed6d949aSAndrew Thompson {
2298ed6d949aSAndrew Thompson 	return (f->priv_sc0);
2299ed6d949aSAndrew Thompson }
23008755859aSAndrew Thompson #endif	/* USB_HAVE_UGEN */
2301