xref: /freebsd/sys/dev/usb/usb_generic.c (revision 9eb0d702)
102ac6454SAndrew Thompson /* $FreeBSD$ */
202ac6454SAndrew Thompson /*-
302ac6454SAndrew Thompson  * Copyright (c) 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 
27ed6d949aSAndrew Thompson #include <sys/stdint.h>
28ed6d949aSAndrew Thompson #include <sys/stddef.h>
29ed6d949aSAndrew Thompson #include <sys/param.h>
30ed6d949aSAndrew Thompson #include <sys/queue.h>
31ed6d949aSAndrew Thompson #include <sys/types.h>
32ed6d949aSAndrew Thompson #include <sys/systm.h>
33ed6d949aSAndrew Thompson #include <sys/kernel.h>
34ed6d949aSAndrew Thompson #include <sys/bus.h>
35ed6d949aSAndrew Thompson #include <sys/module.h>
36ed6d949aSAndrew Thompson #include <sys/lock.h>
37ed6d949aSAndrew Thompson #include <sys/mutex.h>
38ed6d949aSAndrew Thompson #include <sys/condvar.h>
39ed6d949aSAndrew Thompson #include <sys/sysctl.h>
40ed6d949aSAndrew Thompson #include <sys/sx.h>
41ed6d949aSAndrew Thompson #include <sys/unistd.h>
42ed6d949aSAndrew Thompson #include <sys/callout.h>
43ed6d949aSAndrew Thompson #include <sys/malloc.h>
44ed6d949aSAndrew Thompson #include <sys/priv.h>
45ed6d949aSAndrew Thompson #include <sys/conf.h>
46ed6d949aSAndrew Thompson #include <sys/fcntl.h>
47ed6d949aSAndrew Thompson 
4802ac6454SAndrew Thompson #include <dev/usb/usb.h>
4902ac6454SAndrew Thompson #include <dev/usb/usb_ioctl.h>
50ed6d949aSAndrew Thompson #include <dev/usb/usbdi.h>
51ed6d949aSAndrew Thompson #include <dev/usb/usbdi_util.h>
5202ac6454SAndrew Thompson 
5302ac6454SAndrew Thompson #define	USB_DEBUG_VAR ugen_debug
5402ac6454SAndrew Thompson 
5502ac6454SAndrew Thompson #include <dev/usb/usb_core.h>
5602ac6454SAndrew Thompson #include <dev/usb/usb_dev.h>
57ed6d949aSAndrew Thompson #include <dev/usb/usb_mbuf.h>
5802ac6454SAndrew Thompson #include <dev/usb/usb_process.h>
5902ac6454SAndrew Thompson #include <dev/usb/usb_device.h>
6002ac6454SAndrew Thompson #include <dev/usb/usb_debug.h>
6102ac6454SAndrew Thompson #include <dev/usb/usb_request.h>
6202ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
6302ac6454SAndrew Thompson #include <dev/usb/usb_util.h>
6402ac6454SAndrew Thompson #include <dev/usb/usb_hub.h>
6502ac6454SAndrew Thompson #include <dev/usb/usb_generic.h>
6602ac6454SAndrew Thompson #include <dev/usb/usb_transfer.h>
6702ac6454SAndrew Thompson 
6802ac6454SAndrew Thompson #include <dev/usb/usb_controller.h>
6902ac6454SAndrew Thompson #include <dev/usb/usb_bus.h>
7002ac6454SAndrew Thompson 
718755859aSAndrew Thompson #if USB_HAVE_UGEN
728755859aSAndrew Thompson 
7302ac6454SAndrew Thompson /* defines */
7402ac6454SAndrew Thompson 
7502ac6454SAndrew Thompson #define	UGEN_BULK_FS_BUFFER_SIZE	(64*32)	/* bytes */
7602ac6454SAndrew Thompson #define	UGEN_BULK_HS_BUFFER_SIZE	(1024*32)	/* bytes */
7702ac6454SAndrew Thompson #define	UGEN_HW_FRAMES	50		/* number of milliseconds per transfer */
7802ac6454SAndrew Thompson 
7902ac6454SAndrew Thompson /* function prototypes */
8002ac6454SAndrew Thompson 
81e0a69b51SAndrew Thompson static usb_callback_t ugen_read_clear_stall_callback;
82e0a69b51SAndrew Thompson static usb_callback_t ugen_write_clear_stall_callback;
835b3bb704SAndrew Thompson static usb_callback_t ugen_ctrl_read_callback;
845b3bb704SAndrew Thompson static usb_callback_t ugen_ctrl_write_callback;
85e0a69b51SAndrew Thompson static usb_callback_t ugen_isoc_read_callback;
86e0a69b51SAndrew Thompson static usb_callback_t ugen_isoc_write_callback;
875b3bb704SAndrew Thompson static usb_callback_t ugen_ctrl_fs_callback;
8802ac6454SAndrew Thompson 
89e0a69b51SAndrew Thompson static usb_fifo_open_t ugen_open;
90e0a69b51SAndrew Thompson static usb_fifo_close_t ugen_close;
91e0a69b51SAndrew Thompson static usb_fifo_ioctl_t ugen_ioctl;
92e0a69b51SAndrew Thompson static usb_fifo_ioctl_t ugen_ioctl_post;
93e0a69b51SAndrew Thompson static usb_fifo_cmd_t ugen_start_read;
94e0a69b51SAndrew Thompson static usb_fifo_cmd_t ugen_start_write;
95e0a69b51SAndrew Thompson static usb_fifo_cmd_t ugen_stop_io;
9602ac6454SAndrew Thompson 
97760bc48eSAndrew Thompson static int	ugen_transfer_setup(struct usb_fifo *,
98760bc48eSAndrew Thompson 		     const struct usb_config *, uint8_t);
99760bc48eSAndrew Thompson static int	ugen_open_pipe_write(struct usb_fifo *);
100760bc48eSAndrew Thompson static int	ugen_open_pipe_read(struct usb_fifo *);
101760bc48eSAndrew Thompson static int	ugen_set_config(struct usb_fifo *, uint8_t);
102760bc48eSAndrew Thompson static int	ugen_set_interface(struct usb_fifo *, uint8_t, uint8_t);
103760bc48eSAndrew Thompson static int	ugen_get_cdesc(struct usb_fifo *, struct usb_gen_descriptor *);
104760bc48eSAndrew Thompson static int	ugen_get_sdesc(struct usb_fifo *, struct usb_gen_descriptor *);
105760bc48eSAndrew Thompson static int	ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd);
106a593f6b8SAndrew Thompson static int	usb_gen_fill_deviceinfo(struct usb_fifo *,
107760bc48eSAndrew Thompson 		    struct usb_device_info *);
108760bc48eSAndrew Thompson static int	ugen_re_enumerate(struct usb_fifo *);
109760bc48eSAndrew Thompson static int	ugen_iface_ioctl(struct usb_fifo *, u_long, void *, int);
110760bc48eSAndrew Thompson static uint8_t	ugen_fs_get_complete(struct usb_fifo *, uint8_t *);
111760bc48eSAndrew Thompson static int	ugen_fs_uninit(struct usb_fifo *f);
11202ac6454SAndrew Thompson 
11302ac6454SAndrew Thompson /* structures */
11402ac6454SAndrew Thompson 
115a593f6b8SAndrew Thompson struct usb_fifo_methods usb_ugen_methods = {
11602ac6454SAndrew Thompson 	.f_open = &ugen_open,
11702ac6454SAndrew Thompson 	.f_close = &ugen_close,
11802ac6454SAndrew Thompson 	.f_ioctl = &ugen_ioctl,
11902ac6454SAndrew Thompson 	.f_ioctl_post = &ugen_ioctl_post,
12002ac6454SAndrew Thompson 	.f_start_read = &ugen_start_read,
12102ac6454SAndrew Thompson 	.f_stop_read = &ugen_stop_io,
12202ac6454SAndrew Thompson 	.f_start_write = &ugen_start_write,
12302ac6454SAndrew Thompson 	.f_stop_write = &ugen_stop_io,
12402ac6454SAndrew Thompson };
12502ac6454SAndrew Thompson 
126ed6d949aSAndrew Thompson #ifdef USB_DEBUG
12702ac6454SAndrew Thompson static int ugen_debug = 0;
12802ac6454SAndrew Thompson 
1299360ae40SAndrew Thompson SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW, 0, "USB generic");
1309360ae40SAndrew Thompson SYSCTL_INT(_hw_usb_ugen, OID_AUTO, debug, CTLFLAG_RW, &ugen_debug,
13102ac6454SAndrew Thompson     0, "Debug level");
132c13fd8d4SAndrew Thompson 
133c13fd8d4SAndrew Thompson TUNABLE_INT("hw.usb.ugen.debug", &ugen_debug);
13402ac6454SAndrew Thompson #endif
13502ac6454SAndrew Thompson 
13602ac6454SAndrew Thompson 
13702ac6454SAndrew Thompson /* prototypes */
13802ac6454SAndrew Thompson 
13902ac6454SAndrew Thompson static int
140760bc48eSAndrew Thompson ugen_transfer_setup(struct usb_fifo *f,
141760bc48eSAndrew Thompson     const struct usb_config *setup, uint8_t n_setup)
14202ac6454SAndrew Thompson {
143ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
144760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
145ae60fdfbSAndrew Thompson 	uint8_t iface_index = ep->iface_index;
14602ac6454SAndrew Thompson 	int error;
14702ac6454SAndrew Thompson 
14802ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
14902ac6454SAndrew Thompson 
15002ac6454SAndrew Thompson 	/*
151a593f6b8SAndrew Thompson 	 * "usbd_transfer_setup()" can sleep so one needs to make a wrapper,
15202ac6454SAndrew Thompson 	 * exiting the mutex and checking things
15302ac6454SAndrew Thompson 	 */
154a593f6b8SAndrew Thompson 	error = usbd_transfer_setup(udev, &iface_index, f->xfer,
15502ac6454SAndrew Thompson 	    setup, n_setup, f, f->priv_mtx);
15602ac6454SAndrew Thompson 	if (error == 0) {
15702ac6454SAndrew Thompson 
15802ac6454SAndrew Thompson 		if (f->xfer[0]->nframes == 1) {
159a593f6b8SAndrew Thompson 			error = usb_fifo_alloc_buffer(f,
16002ac6454SAndrew Thompson 			    f->xfer[0]->max_data_length, 2);
16102ac6454SAndrew Thompson 		} else {
162a593f6b8SAndrew Thompson 			error = usb_fifo_alloc_buffer(f,
16302ac6454SAndrew Thompson 			    f->xfer[0]->max_frame_size,
16402ac6454SAndrew Thompson 			    2 * f->xfer[0]->nframes);
16502ac6454SAndrew Thompson 		}
16602ac6454SAndrew Thompson 		if (error) {
167a593f6b8SAndrew Thompson 			usbd_transfer_unsetup(f->xfer, n_setup);
16802ac6454SAndrew Thompson 		}
16902ac6454SAndrew Thompson 	}
17002ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
17102ac6454SAndrew Thompson 
17202ac6454SAndrew Thompson 	return (error);
17302ac6454SAndrew Thompson }
17402ac6454SAndrew Thompson 
17502ac6454SAndrew Thompson static int
176760bc48eSAndrew Thompson ugen_open(struct usb_fifo *f, int fflags)
17702ac6454SAndrew Thompson {
178ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
179ae60fdfbSAndrew Thompson 	struct usb_endpoint_descriptor *ed = ep->edesc;
18002ac6454SAndrew Thompson 	uint8_t type;
18102ac6454SAndrew Thompson 
18202ac6454SAndrew Thompson 	DPRINTFN(6, "flag=0x%x\n", fflags);
18302ac6454SAndrew Thompson 
18402ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
185a593f6b8SAndrew Thompson 	switch (usbd_get_speed(f->udev)) {
18602ac6454SAndrew Thompson 	case USB_SPEED_LOW:
18702ac6454SAndrew Thompson 	case USB_SPEED_FULL:
18802ac6454SAndrew Thompson 		f->nframes = UGEN_HW_FRAMES;
18902ac6454SAndrew Thompson 		f->bufsize = UGEN_BULK_FS_BUFFER_SIZE;
19002ac6454SAndrew Thompson 		break;
19102ac6454SAndrew Thompson 	default:
19202ac6454SAndrew Thompson 		f->nframes = UGEN_HW_FRAMES * 8;
19302ac6454SAndrew Thompson 		f->bufsize = UGEN_BULK_HS_BUFFER_SIZE;
19402ac6454SAndrew Thompson 		break;
19502ac6454SAndrew Thompson 	}
19602ac6454SAndrew Thompson 
19702ac6454SAndrew Thompson 	type = ed->bmAttributes & UE_XFERTYPE;
19802ac6454SAndrew Thompson 	if (type == UE_INTERRUPT) {
19902ac6454SAndrew Thompson 		f->bufsize = 0;		/* use "wMaxPacketSize" */
20002ac6454SAndrew Thompson 	}
20102ac6454SAndrew Thompson 	f->timeout = USB_NO_TIMEOUT;
20202ac6454SAndrew Thompson 	f->flag_short = 0;
20302ac6454SAndrew Thompson 	f->fifo_zlp = 0;
20402ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
20502ac6454SAndrew Thompson 
20602ac6454SAndrew Thompson 	return (0);
20702ac6454SAndrew Thompson }
20802ac6454SAndrew Thompson 
20902ac6454SAndrew Thompson static void
210760bc48eSAndrew Thompson ugen_close(struct usb_fifo *f, int fflags)
21102ac6454SAndrew Thompson {
21202ac6454SAndrew Thompson 	DPRINTFN(6, "flag=0x%x\n", fflags);
21302ac6454SAndrew Thompson 
21402ac6454SAndrew Thompson 	/* cleanup */
21502ac6454SAndrew Thompson 
21602ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
217a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[0]);
218a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[1]);
21902ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
22002ac6454SAndrew Thompson 
221a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(f->xfer, 2);
222a593f6b8SAndrew Thompson 	usb_fifo_free_buffer(f);
22302ac6454SAndrew Thompson 
22402ac6454SAndrew Thompson 	if (ugen_fs_uninit(f)) {
22502ac6454SAndrew Thompson 		/* ignore any errors - we are closing */
22602ac6454SAndrew Thompson 		DPRINTFN(6, "no FIFOs\n");
22702ac6454SAndrew Thompson 	}
22802ac6454SAndrew Thompson }
22902ac6454SAndrew Thompson 
23002ac6454SAndrew Thompson static int
231760bc48eSAndrew Thompson ugen_open_pipe_write(struct usb_fifo *f)
23202ac6454SAndrew Thompson {
233760bc48eSAndrew Thompson 	struct usb_config usb_config[2];
234ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
235ae60fdfbSAndrew Thompson 	struct usb_endpoint_descriptor *ed = ep->edesc;
23602ac6454SAndrew Thompson 
23702ac6454SAndrew Thompson 	mtx_assert(f->priv_mtx, MA_OWNED);
23802ac6454SAndrew Thompson 
23902ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
24002ac6454SAndrew Thompson 		/* transfers are already opened */
24102ac6454SAndrew Thompson 		return (0);
24202ac6454SAndrew Thompson 	}
243760bc48eSAndrew Thompson 	bzero(usb_config, sizeof(usb_config));
24402ac6454SAndrew Thompson 
245760bc48eSAndrew Thompson 	usb_config[1].type = UE_CONTROL;
246760bc48eSAndrew Thompson 	usb_config[1].endpoint = 0;
247760bc48eSAndrew Thompson 	usb_config[1].direction = UE_DIR_ANY;
248760bc48eSAndrew Thompson 	usb_config[1].timeout = 1000;	/* 1 second */
249760bc48eSAndrew Thompson 	usb_config[1].interval = 50;/* 50 milliseconds */
250760bc48eSAndrew Thompson 	usb_config[1].bufsize = sizeof(struct usb_device_request);
251760bc48eSAndrew Thompson 	usb_config[1].callback = &ugen_write_clear_stall_callback;
252760bc48eSAndrew Thompson 	usb_config[1].usb_mode = USB_MODE_HOST;
25302ac6454SAndrew Thompson 
254760bc48eSAndrew Thompson 	usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
255760bc48eSAndrew Thompson 	usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
256760bc48eSAndrew Thompson 	usb_config[0].direction = UE_DIR_TX;
257760bc48eSAndrew Thompson 	usb_config[0].interval = USB_DEFAULT_INTERVAL;
258760bc48eSAndrew Thompson 	usb_config[0].flags.proxy_buffer = 1;
259760bc48eSAndrew Thompson 	usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
26002ac6454SAndrew Thompson 
26102ac6454SAndrew Thompson 	switch (ed->bmAttributes & UE_XFERTYPE) {
26202ac6454SAndrew Thompson 	case UE_INTERRUPT:
26302ac6454SAndrew Thompson 	case UE_BULK:
26402ac6454SAndrew Thompson 		if (f->flag_short) {
265760bc48eSAndrew Thompson 			usb_config[0].flags.force_short_xfer = 1;
26602ac6454SAndrew Thompson 		}
2675b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_write_callback;
268760bc48eSAndrew Thompson 		usb_config[0].timeout = f->timeout;
269760bc48eSAndrew Thompson 		usb_config[0].frames = 1;
270760bc48eSAndrew Thompson 		usb_config[0].bufsize = f->bufsize;
271760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
27202ac6454SAndrew Thompson 			return (EIO);
27302ac6454SAndrew Thompson 		}
27402ac6454SAndrew Thompson 		/* first transfer does not clear stall */
27502ac6454SAndrew Thompson 		f->flag_stall = 0;
27602ac6454SAndrew Thompson 		break;
27702ac6454SAndrew Thompson 
27802ac6454SAndrew Thompson 	case UE_ISOCHRONOUS:
279760bc48eSAndrew Thompson 		usb_config[0].flags.short_xfer_ok = 1;
280760bc48eSAndrew Thompson 		usb_config[0].bufsize = 0;	/* use default */
281760bc48eSAndrew Thompson 		usb_config[0].frames = f->nframes;
282760bc48eSAndrew Thompson 		usb_config[0].callback = &ugen_isoc_write_callback;
283760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;
28402ac6454SAndrew Thompson 
28502ac6454SAndrew Thompson 		/* clone configuration */
286760bc48eSAndrew Thompson 		usb_config[1] = usb_config[0];
28702ac6454SAndrew Thompson 
288760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
28902ac6454SAndrew Thompson 			return (EIO);
29002ac6454SAndrew Thompson 		}
29102ac6454SAndrew Thompson 		break;
29202ac6454SAndrew Thompson 	default:
29302ac6454SAndrew Thompson 		return (EINVAL);
29402ac6454SAndrew Thompson 	}
29502ac6454SAndrew Thompson 	return (0);
29602ac6454SAndrew Thompson }
29702ac6454SAndrew Thompson 
29802ac6454SAndrew Thompson static int
299760bc48eSAndrew Thompson ugen_open_pipe_read(struct usb_fifo *f)
30002ac6454SAndrew Thompson {
301760bc48eSAndrew Thompson 	struct usb_config usb_config[2];
302ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
303ae60fdfbSAndrew Thompson 	struct usb_endpoint_descriptor *ed = ep->edesc;
30402ac6454SAndrew Thompson 
30502ac6454SAndrew Thompson 	mtx_assert(f->priv_mtx, MA_OWNED);
30602ac6454SAndrew Thompson 
30702ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
30802ac6454SAndrew Thompson 		/* transfers are already opened */
30902ac6454SAndrew Thompson 		return (0);
31002ac6454SAndrew Thompson 	}
311760bc48eSAndrew Thompson 	bzero(usb_config, sizeof(usb_config));
31202ac6454SAndrew Thompson 
313760bc48eSAndrew Thompson 	usb_config[1].type = UE_CONTROL;
314760bc48eSAndrew Thompson 	usb_config[1].endpoint = 0;
315760bc48eSAndrew Thompson 	usb_config[1].direction = UE_DIR_ANY;
316760bc48eSAndrew Thompson 	usb_config[1].timeout = 1000;	/* 1 second */
317760bc48eSAndrew Thompson 	usb_config[1].interval = 50;/* 50 milliseconds */
318760bc48eSAndrew Thompson 	usb_config[1].bufsize = sizeof(struct usb_device_request);
319760bc48eSAndrew Thompson 	usb_config[1].callback = &ugen_read_clear_stall_callback;
320760bc48eSAndrew Thompson 	usb_config[1].usb_mode = USB_MODE_HOST;
32102ac6454SAndrew Thompson 
322760bc48eSAndrew Thompson 	usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
323760bc48eSAndrew Thompson 	usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
324760bc48eSAndrew Thompson 	usb_config[0].direction = UE_DIR_RX;
325760bc48eSAndrew Thompson 	usb_config[0].interval = USB_DEFAULT_INTERVAL;
326760bc48eSAndrew Thompson 	usb_config[0].flags.proxy_buffer = 1;
327760bc48eSAndrew Thompson 	usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
32802ac6454SAndrew Thompson 
32902ac6454SAndrew Thompson 	switch (ed->bmAttributes & UE_XFERTYPE) {
33002ac6454SAndrew Thompson 	case UE_INTERRUPT:
33102ac6454SAndrew Thompson 	case UE_BULK:
33202ac6454SAndrew Thompson 		if (f->flag_short) {
333760bc48eSAndrew Thompson 			usb_config[0].flags.short_xfer_ok = 1;
33402ac6454SAndrew Thompson 		}
335760bc48eSAndrew Thompson 		usb_config[0].timeout = f->timeout;
336760bc48eSAndrew Thompson 		usb_config[0].frames = 1;
3375b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_read_callback;
338760bc48eSAndrew Thompson 		usb_config[0].bufsize = f->bufsize;
33902ac6454SAndrew Thompson 
340760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
34102ac6454SAndrew Thompson 			return (EIO);
34202ac6454SAndrew Thompson 		}
34302ac6454SAndrew Thompson 		/* first transfer does not clear stall */
34402ac6454SAndrew Thompson 		f->flag_stall = 0;
34502ac6454SAndrew Thompson 		break;
34602ac6454SAndrew Thompson 
34702ac6454SAndrew Thompson 	case UE_ISOCHRONOUS:
348760bc48eSAndrew Thompson 		usb_config[0].flags.short_xfer_ok = 1;
349760bc48eSAndrew Thompson 		usb_config[0].bufsize = 0;	/* use default */
350760bc48eSAndrew Thompson 		usb_config[0].frames = f->nframes;
351760bc48eSAndrew Thompson 		usb_config[0].callback = &ugen_isoc_read_callback;
352760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;
35302ac6454SAndrew Thompson 
35402ac6454SAndrew Thompson 		/* clone configuration */
355760bc48eSAndrew Thompson 		usb_config[1] = usb_config[0];
35602ac6454SAndrew Thompson 
357760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
35802ac6454SAndrew Thompson 			return (EIO);
35902ac6454SAndrew Thompson 		}
36002ac6454SAndrew Thompson 		break;
36102ac6454SAndrew Thompson 
36202ac6454SAndrew Thompson 	default:
36302ac6454SAndrew Thompson 		return (EINVAL);
36402ac6454SAndrew Thompson 	}
36502ac6454SAndrew Thompson 	return (0);
36602ac6454SAndrew Thompson }
36702ac6454SAndrew Thompson 
36802ac6454SAndrew Thompson static void
369760bc48eSAndrew Thompson ugen_start_read(struct usb_fifo *f)
37002ac6454SAndrew Thompson {
37102ac6454SAndrew Thompson 	/* check that pipes are open */
37202ac6454SAndrew Thompson 	if (ugen_open_pipe_read(f)) {
37302ac6454SAndrew Thompson 		/* signal error */
374a593f6b8SAndrew Thompson 		usb_fifo_put_data_error(f);
37502ac6454SAndrew Thompson 	}
37602ac6454SAndrew Thompson 	/* start transfers */
377a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[0]);
378a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[1]);
37902ac6454SAndrew Thompson }
38002ac6454SAndrew Thompson 
38102ac6454SAndrew Thompson static void
382760bc48eSAndrew Thompson ugen_start_write(struct usb_fifo *f)
38302ac6454SAndrew Thompson {
38402ac6454SAndrew Thompson 	/* check that pipes are open */
38502ac6454SAndrew Thompson 	if (ugen_open_pipe_write(f)) {
38602ac6454SAndrew Thompson 		/* signal error */
387a593f6b8SAndrew Thompson 		usb_fifo_get_data_error(f);
38802ac6454SAndrew Thompson 	}
38902ac6454SAndrew Thompson 	/* start transfers */
390a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[0]);
391a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[1]);
39202ac6454SAndrew Thompson }
39302ac6454SAndrew Thompson 
39402ac6454SAndrew Thompson static void
395760bc48eSAndrew Thompson ugen_stop_io(struct usb_fifo *f)
39602ac6454SAndrew Thompson {
39702ac6454SAndrew Thompson 	/* stop transfers */
398a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[0]);
399a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[1]);
40002ac6454SAndrew Thompson }
40102ac6454SAndrew Thompson 
40202ac6454SAndrew Thompson static void
4035b3bb704SAndrew Thompson ugen_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error)
40402ac6454SAndrew Thompson {
405ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
406760bc48eSAndrew Thompson 	struct usb_mbuf *m;
40702ac6454SAndrew Thompson 
40802ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
40902ac6454SAndrew Thompson 
41002ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
41102ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
41202ac6454SAndrew Thompson 		if (xfer->actlen == 0) {
41302ac6454SAndrew Thompson 			if (f->fifo_zlp != 4) {
41402ac6454SAndrew Thompson 				f->fifo_zlp++;
41502ac6454SAndrew Thompson 			} else {
41602ac6454SAndrew Thompson 				/*
41702ac6454SAndrew Thompson 				 * Throttle a little bit we have multiple ZLPs
41802ac6454SAndrew Thompson 				 * in a row!
41902ac6454SAndrew Thompson 				 */
42002ac6454SAndrew Thompson 				xfer->interval = 64;	/* ms */
42102ac6454SAndrew Thompson 			}
42202ac6454SAndrew Thompson 		} else {
42302ac6454SAndrew Thompson 			/* clear throttle */
42402ac6454SAndrew Thompson 			xfer->interval = 0;
42502ac6454SAndrew Thompson 			f->fifo_zlp = 0;
42602ac6454SAndrew Thompson 		}
427a593f6b8SAndrew Thompson 		usb_fifo_put_data(f, xfer->frbuffers, 0,
42802ac6454SAndrew Thompson 		    xfer->actlen, 1);
42902ac6454SAndrew Thompson 
43002ac6454SAndrew Thompson 	case USB_ST_SETUP:
43102ac6454SAndrew Thompson 		if (f->flag_stall) {
432a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
43302ac6454SAndrew Thompson 			break;
43402ac6454SAndrew Thompson 		}
43502ac6454SAndrew Thompson 		USB_IF_POLL(&f->free_q, m);
43602ac6454SAndrew Thompson 		if (m) {
437ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
438a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
43902ac6454SAndrew Thompson 		}
44002ac6454SAndrew Thompson 		break;
44102ac6454SAndrew Thompson 
44202ac6454SAndrew Thompson 	default:			/* Error */
44302ac6454SAndrew Thompson 		if (xfer->error != USB_ERR_CANCELLED) {
44401cf7831SAndrew Thompson 			/* send a zero length packet to userland */
445a593f6b8SAndrew Thompson 			usb_fifo_put_data(f, xfer->frbuffers, 0, 0, 1);
44602ac6454SAndrew Thompson 			f->flag_stall = 1;
44702ac6454SAndrew Thompson 			f->fifo_zlp = 0;
448a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
44902ac6454SAndrew Thompson 		}
45002ac6454SAndrew Thompson 		break;
45102ac6454SAndrew Thompson 	}
45202ac6454SAndrew Thompson }
45302ac6454SAndrew Thompson 
45402ac6454SAndrew Thompson static void
4555b3bb704SAndrew Thompson ugen_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error)
45602ac6454SAndrew Thompson {
457ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
458e0a69b51SAndrew Thompson 	usb_frlength_t actlen;
45902ac6454SAndrew Thompson 
46002ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
46102ac6454SAndrew Thompson 
46202ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
46302ac6454SAndrew Thompson 	case USB_ST_SETUP:
46402ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
46502ac6454SAndrew Thompson 		/*
46602ac6454SAndrew Thompson 		 * If writing is in stall, just jump to clear stall
46702ac6454SAndrew Thompson 		 * callback and solve the situation.
46802ac6454SAndrew Thompson 		 */
46902ac6454SAndrew Thompson 		if (f->flag_stall) {
470a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
47102ac6454SAndrew Thompson 			break;
47202ac6454SAndrew Thompson 		}
47302ac6454SAndrew Thompson 		/*
47402ac6454SAndrew Thompson 		 * Write data, setup and perform hardware transfer.
47502ac6454SAndrew Thompson 		 */
476a593f6b8SAndrew Thompson 		if (usb_fifo_get_data(f, xfer->frbuffers, 0,
47702ac6454SAndrew Thompson 		    xfer->max_data_length, &actlen, 0)) {
478ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, 0, actlen);
479a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
48002ac6454SAndrew Thompson 		}
48102ac6454SAndrew Thompson 		break;
48202ac6454SAndrew Thompson 
48302ac6454SAndrew Thompson 	default:			/* Error */
48402ac6454SAndrew Thompson 		if (xfer->error != USB_ERR_CANCELLED) {
48502ac6454SAndrew Thompson 			f->flag_stall = 1;
486a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
48702ac6454SAndrew Thompson 		}
48802ac6454SAndrew Thompson 		break;
48902ac6454SAndrew Thompson 	}
49002ac6454SAndrew Thompson }
49102ac6454SAndrew Thompson 
49202ac6454SAndrew Thompson static void
493ed6d949aSAndrew Thompson ugen_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
49402ac6454SAndrew Thompson {
495ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
496760bc48eSAndrew Thompson 	struct usb_xfer *xfer_other = f->xfer[0];
49702ac6454SAndrew Thompson 
49802ac6454SAndrew Thompson 	if (f->flag_stall == 0) {
49902ac6454SAndrew Thompson 		/* nothing to do */
50002ac6454SAndrew Thompson 		return;
50102ac6454SAndrew Thompson 	}
502a593f6b8SAndrew Thompson 	if (usbd_clear_stall_callback(xfer, xfer_other)) {
50302ac6454SAndrew Thompson 		DPRINTFN(5, "f=%p: stall cleared\n", f);
50402ac6454SAndrew Thompson 		f->flag_stall = 0;
505a593f6b8SAndrew Thompson 		usbd_transfer_start(xfer_other);
50602ac6454SAndrew Thompson 	}
50702ac6454SAndrew Thompson }
50802ac6454SAndrew Thompson 
50902ac6454SAndrew Thompson static void
510ed6d949aSAndrew Thompson ugen_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
51102ac6454SAndrew Thompson {
512ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
513760bc48eSAndrew Thompson 	struct usb_xfer *xfer_other = f->xfer[0];
51402ac6454SAndrew Thompson 
51502ac6454SAndrew Thompson 	if (f->flag_stall == 0) {
51602ac6454SAndrew Thompson 		/* nothing to do */
51702ac6454SAndrew Thompson 		return;
51802ac6454SAndrew Thompson 	}
519a593f6b8SAndrew Thompson 	if (usbd_clear_stall_callback(xfer, xfer_other)) {
52002ac6454SAndrew Thompson 		DPRINTFN(5, "f=%p: stall cleared\n", f);
52102ac6454SAndrew Thompson 		f->flag_stall = 0;
522a593f6b8SAndrew Thompson 		usbd_transfer_start(xfer_other);
52302ac6454SAndrew Thompson 	}
52402ac6454SAndrew Thompson }
52502ac6454SAndrew Thompson 
52602ac6454SAndrew Thompson static void
527ed6d949aSAndrew Thompson ugen_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error)
52802ac6454SAndrew Thompson {
529ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
530e0a69b51SAndrew Thompson 	usb_frlength_t offset;
531e0a69b51SAndrew Thompson 	usb_frcount_t n;
53202ac6454SAndrew Thompson 
53302ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
53402ac6454SAndrew Thompson 
53502ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
53602ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
53702ac6454SAndrew Thompson 
53802ac6454SAndrew Thompson 		DPRINTFN(6, "actlen=%d\n", xfer->actlen);
53902ac6454SAndrew Thompson 
54002ac6454SAndrew Thompson 		offset = 0;
54102ac6454SAndrew Thompson 
54202ac6454SAndrew Thompson 		for (n = 0; n != xfer->aframes; n++) {
543a593f6b8SAndrew Thompson 			usb_fifo_put_data(f, xfer->frbuffers, offset,
54402ac6454SAndrew Thompson 			    xfer->frlengths[n], 1);
54502ac6454SAndrew Thompson 			offset += xfer->max_frame_size;
54602ac6454SAndrew Thompson 		}
54702ac6454SAndrew Thompson 
54802ac6454SAndrew Thompson 	case USB_ST_SETUP:
54902ac6454SAndrew Thompson tr_setup:
55002ac6454SAndrew Thompson 		for (n = 0; n != xfer->nframes; n++) {
55102ac6454SAndrew Thompson 			/* setup size for next transfer */
552ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, xfer->max_frame_size);
55302ac6454SAndrew Thompson 		}
554a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
55502ac6454SAndrew Thompson 		break;
55602ac6454SAndrew Thompson 
55702ac6454SAndrew Thompson 	default:			/* Error */
55802ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
55902ac6454SAndrew Thompson 			break;
56002ac6454SAndrew Thompson 		}
56102ac6454SAndrew Thompson 		goto tr_setup;
56202ac6454SAndrew Thompson 	}
56302ac6454SAndrew Thompson }
56402ac6454SAndrew Thompson 
56502ac6454SAndrew Thompson static void
566ed6d949aSAndrew Thompson ugen_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error)
56702ac6454SAndrew Thompson {
568ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
569e0a69b51SAndrew Thompson 	usb_frlength_t actlen;
570e0a69b51SAndrew Thompson 	usb_frlength_t offset;
571e0a69b51SAndrew Thompson 	usb_frcount_t n;
57202ac6454SAndrew Thompson 
57302ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
57402ac6454SAndrew Thompson 
57502ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
57602ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
57702ac6454SAndrew Thompson 	case USB_ST_SETUP:
57802ac6454SAndrew Thompson tr_setup:
57902ac6454SAndrew Thompson 		offset = 0;
58002ac6454SAndrew Thompson 		for (n = 0; n != xfer->nframes; n++) {
581a593f6b8SAndrew Thompson 			if (usb_fifo_get_data(f, xfer->frbuffers, offset,
58202ac6454SAndrew Thompson 			    xfer->max_frame_size, &actlen, 1)) {
583ed6d949aSAndrew Thompson 				usbd_xfer_set_frame_len(xfer, n, actlen);
58402ac6454SAndrew Thompson 				offset += actlen;
58502ac6454SAndrew Thompson 			} else {
58602ac6454SAndrew Thompson 				break;
58702ac6454SAndrew Thompson 			}
58802ac6454SAndrew Thompson 		}
58902ac6454SAndrew Thompson 
59002ac6454SAndrew Thompson 		for (; n != xfer->nframes; n++) {
59102ac6454SAndrew Thompson 			/* fill in zero frames */
592ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, 0);
59302ac6454SAndrew Thompson 		}
594a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
59502ac6454SAndrew Thompson 		break;
59602ac6454SAndrew Thompson 
59702ac6454SAndrew Thompson 	default:			/* Error */
59802ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
59902ac6454SAndrew Thompson 			break;
60002ac6454SAndrew Thompson 		}
60102ac6454SAndrew Thompson 		goto tr_setup;
60202ac6454SAndrew Thompson 	}
60302ac6454SAndrew Thompson }
60402ac6454SAndrew Thompson 
60502ac6454SAndrew Thompson static int
606760bc48eSAndrew Thompson ugen_set_config(struct usb_fifo *f, uint8_t index)
60702ac6454SAndrew Thompson {
60802ac6454SAndrew Thompson 	DPRINTFN(2, "index %u\n", index);
60902ac6454SAndrew Thompson 
610f29a0724SAndrew Thompson 	if (f->udev->flags.usb_mode != USB_MODE_HOST) {
61102ac6454SAndrew Thompson 		/* not possible in device side mode */
61202ac6454SAndrew Thompson 		return (ENOTTY);
61302ac6454SAndrew Thompson 	}
61402ac6454SAndrew Thompson 	if (f->udev->curr_config_index == index) {
61502ac6454SAndrew Thompson 		/* no change needed */
61602ac6454SAndrew Thompson 		return (0);
61702ac6454SAndrew Thompson 	}
61802ac6454SAndrew Thompson 	/* make sure all FIFO's are gone */
61902ac6454SAndrew Thompson 	/* else there can be a deadlock */
62002ac6454SAndrew Thompson 	if (ugen_fs_uninit(f)) {
62102ac6454SAndrew Thompson 		/* ignore any errors */
62202ac6454SAndrew Thompson 		DPRINTFN(6, "no FIFOs\n");
62302ac6454SAndrew Thompson 	}
62402ac6454SAndrew Thompson 	/* change setting - will free generic FIFOs, if any */
625a593f6b8SAndrew Thompson 	if (usbd_set_config_index(f->udev, index)) {
62602ac6454SAndrew Thompson 		return (EIO);
62702ac6454SAndrew Thompson 	}
62802ac6454SAndrew Thompson 	/* probe and attach */
629a593f6b8SAndrew Thompson 	if (usb_probe_and_attach(f->udev, USB_IFACE_INDEX_ANY)) {
63002ac6454SAndrew Thompson 		return (EIO);
63102ac6454SAndrew Thompson 	}
63202ac6454SAndrew Thompson 	return (0);
63302ac6454SAndrew Thompson }
63402ac6454SAndrew Thompson 
63502ac6454SAndrew Thompson static int
636760bc48eSAndrew Thompson ugen_set_interface(struct usb_fifo *f,
63702ac6454SAndrew Thompson     uint8_t iface_index, uint8_t alt_index)
63802ac6454SAndrew Thompson {
63902ac6454SAndrew Thompson 	DPRINTFN(2, "%u, %u\n", iface_index, alt_index);
64002ac6454SAndrew Thompson 
641f29a0724SAndrew Thompson 	if (f->udev->flags.usb_mode != USB_MODE_HOST) {
64202ac6454SAndrew Thompson 		/* not possible in device side mode */
64302ac6454SAndrew Thompson 		return (ENOTTY);
64402ac6454SAndrew Thompson 	}
64502ac6454SAndrew Thompson 	/* make sure all FIFO's are gone */
64602ac6454SAndrew Thompson 	/* else there can be a deadlock */
64702ac6454SAndrew Thompson 	if (ugen_fs_uninit(f)) {
64802ac6454SAndrew Thompson 		/* ignore any errors */
64902ac6454SAndrew Thompson 		DPRINTFN(6, "no FIFOs\n");
65002ac6454SAndrew Thompson 	}
65102ac6454SAndrew Thompson 	/* change setting - will free generic FIFOs, if any */
652a593f6b8SAndrew Thompson 	if (usbd_set_alt_interface_index(f->udev, iface_index, alt_index)) {
65302ac6454SAndrew Thompson 		return (EIO);
65402ac6454SAndrew Thompson 	}
65502ac6454SAndrew Thompson 	/* probe and attach */
656a593f6b8SAndrew Thompson 	if (usb_probe_and_attach(f->udev, iface_index)) {
65702ac6454SAndrew Thompson 		return (EIO);
65802ac6454SAndrew Thompson 	}
65902ac6454SAndrew Thompson 	return (0);
66002ac6454SAndrew Thompson }
66102ac6454SAndrew Thompson 
66202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
66302ac6454SAndrew Thompson  *	ugen_get_cdesc
66402ac6454SAndrew Thompson  *
66502ac6454SAndrew Thompson  * This function will retrieve the complete configuration descriptor
66602ac6454SAndrew Thompson  * at the given index.
66702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
66802ac6454SAndrew Thompson static int
669760bc48eSAndrew Thompson ugen_get_cdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
67002ac6454SAndrew Thompson {
671760bc48eSAndrew Thompson 	struct usb_config_descriptor *cdesc;
672760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
67302ac6454SAndrew Thompson 	int error;
67402ac6454SAndrew Thompson 	uint16_t len;
67502ac6454SAndrew Thompson 	uint8_t free_data;
67602ac6454SAndrew Thompson 
67702ac6454SAndrew Thompson 	DPRINTFN(6, "\n");
67802ac6454SAndrew Thompson 
67902ac6454SAndrew Thompson 	if (ugd->ugd_data == NULL) {
68002ac6454SAndrew Thompson 		/* userland pointer should not be zero */
68102ac6454SAndrew Thompson 		return (EINVAL);
68202ac6454SAndrew Thompson 	}
68302ac6454SAndrew Thompson 	if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) ||
68402ac6454SAndrew Thompson 	    (ugd->ugd_config_index == udev->curr_config_index)) {
685a593f6b8SAndrew Thompson 		cdesc = usbd_get_config_descriptor(udev);
68602ac6454SAndrew Thompson 		if (cdesc == NULL) {
68702ac6454SAndrew Thompson 			return (ENXIO);
68802ac6454SAndrew Thompson 		}
68902ac6454SAndrew Thompson 		free_data = 0;
69002ac6454SAndrew Thompson 
69102ac6454SAndrew Thompson 	} else {
692a593f6b8SAndrew Thompson 		if (usbd_req_get_config_desc_full(udev,
693e2805033SAndrew Thompson 		    NULL, &cdesc, M_USBDEV,
69402ac6454SAndrew Thompson 		    ugd->ugd_config_index)) {
69502ac6454SAndrew Thompson 			return (ENXIO);
69602ac6454SAndrew Thompson 		}
69702ac6454SAndrew Thompson 		free_data = 1;
69802ac6454SAndrew Thompson 	}
69902ac6454SAndrew Thompson 
70002ac6454SAndrew Thompson 	len = UGETW(cdesc->wTotalLength);
70102ac6454SAndrew Thompson 	if (len > ugd->ugd_maxlen) {
70202ac6454SAndrew Thompson 		len = ugd->ugd_maxlen;
70302ac6454SAndrew Thompson 	}
70402ac6454SAndrew Thompson 	DPRINTFN(6, "len=%u\n", len);
70502ac6454SAndrew Thompson 
70602ac6454SAndrew Thompson 	ugd->ugd_actlen = len;
70702ac6454SAndrew Thompson 	ugd->ugd_offset = 0;
70802ac6454SAndrew Thompson 
70902ac6454SAndrew Thompson 	error = copyout(cdesc, ugd->ugd_data, len);
71002ac6454SAndrew Thompson 
71102ac6454SAndrew Thompson 	if (free_data) {
71202ac6454SAndrew Thompson 		free(cdesc, M_USBDEV);
71302ac6454SAndrew Thompson 	}
71402ac6454SAndrew Thompson 	return (error);
71502ac6454SAndrew Thompson }
71602ac6454SAndrew Thompson 
71702ac6454SAndrew Thompson static int
718760bc48eSAndrew Thompson ugen_get_sdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
71902ac6454SAndrew Thompson {
72002ac6454SAndrew Thompson 	void *ptr = f->udev->bus->scratch[0].data;
72102ac6454SAndrew Thompson 	uint16_t size = sizeof(f->udev->bus->scratch[0].data);
72202ac6454SAndrew Thompson 	int error;
72302ac6454SAndrew Thompson 
724a593f6b8SAndrew Thompson 	if (usbd_req_get_string_desc(f->udev, NULL, ptr,
72502ac6454SAndrew Thompson 	    size, ugd->ugd_lang_id, ugd->ugd_string_index)) {
72602ac6454SAndrew Thompson 		error = EINVAL;
72702ac6454SAndrew Thompson 	} else {
72802ac6454SAndrew Thompson 
72902ac6454SAndrew Thompson 		if (size > ((uint8_t *)ptr)[0]) {
73002ac6454SAndrew Thompson 			size = ((uint8_t *)ptr)[0];
73102ac6454SAndrew Thompson 		}
73202ac6454SAndrew Thompson 		if (size > ugd->ugd_maxlen) {
73302ac6454SAndrew Thompson 			size = ugd->ugd_maxlen;
73402ac6454SAndrew Thompson 		}
73502ac6454SAndrew Thompson 		ugd->ugd_actlen = size;
73602ac6454SAndrew Thompson 		ugd->ugd_offset = 0;
73702ac6454SAndrew Thompson 
73802ac6454SAndrew Thompson 		error = copyout(ptr, ugd->ugd_data, size);
73902ac6454SAndrew Thompson 	}
74002ac6454SAndrew Thompson 	return (error);
74102ac6454SAndrew Thompson }
74202ac6454SAndrew Thompson 
74302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
74402ac6454SAndrew Thompson  *	ugen_get_iface_driver
74502ac6454SAndrew Thompson  *
74602ac6454SAndrew Thompson  * This function generates an USB interface description for userland.
74702ac6454SAndrew Thompson  *
74802ac6454SAndrew Thompson  * Returns:
74902ac6454SAndrew Thompson  *    0: Success
75002ac6454SAndrew Thompson  * Else: Failure
75102ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
75202ac6454SAndrew Thompson static int
753760bc48eSAndrew Thompson ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
75402ac6454SAndrew Thompson {
755760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
756760bc48eSAndrew Thompson 	struct usb_interface *iface;
75702ac6454SAndrew Thompson 	const char *ptr;
75802ac6454SAndrew Thompson 	const char *desc;
75902ac6454SAndrew Thompson 	unsigned int len;
76002ac6454SAndrew Thompson 	unsigned int maxlen;
76102ac6454SAndrew Thompson 	char buf[128];
76202ac6454SAndrew Thompson 	int error;
76302ac6454SAndrew Thompson 
76402ac6454SAndrew Thompson 	DPRINTFN(6, "\n");
76502ac6454SAndrew Thompson 
76602ac6454SAndrew Thompson 	if ((ugd->ugd_data == NULL) || (ugd->ugd_maxlen == 0)) {
76702ac6454SAndrew Thompson 		/* userland pointer should not be zero */
76802ac6454SAndrew Thompson 		return (EINVAL);
76902ac6454SAndrew Thompson 	}
77002ac6454SAndrew Thompson 
771a593f6b8SAndrew Thompson 	iface = usbd_get_iface(udev, ugd->ugd_iface_index);
77202ac6454SAndrew Thompson 	if ((iface == NULL) || (iface->idesc == NULL)) {
77302ac6454SAndrew Thompson 		/* invalid interface index */
77402ac6454SAndrew Thompson 		return (EINVAL);
77502ac6454SAndrew Thompson 	}
77602ac6454SAndrew Thompson 
77702ac6454SAndrew Thompson 	/* read out device nameunit string, if any */
77802ac6454SAndrew Thompson 	if ((iface->subdev != NULL) &&
77902ac6454SAndrew Thompson 	    device_is_attached(iface->subdev) &&
78002ac6454SAndrew Thompson 	    (ptr = device_get_nameunit(iface->subdev)) &&
78102ac6454SAndrew Thompson 	    (desc = device_get_desc(iface->subdev))) {
78202ac6454SAndrew Thompson 
78302ac6454SAndrew Thompson 		/* print description */
78402ac6454SAndrew Thompson 		snprintf(buf, sizeof(buf), "%s: <%s>", ptr, desc);
78502ac6454SAndrew Thompson 
78602ac6454SAndrew Thompson 		/* range checks */
78702ac6454SAndrew Thompson 		maxlen = ugd->ugd_maxlen - 1;
78802ac6454SAndrew Thompson 		len = strlen(buf);
78902ac6454SAndrew Thompson 		if (len > maxlen)
79002ac6454SAndrew Thompson 			len = maxlen;
79102ac6454SAndrew Thompson 
79202ac6454SAndrew Thompson 		/* update actual length, including terminating zero */
79302ac6454SAndrew Thompson 		ugd->ugd_actlen = len + 1;
79402ac6454SAndrew Thompson 
79502ac6454SAndrew Thompson 		/* copy out interface description */
79602ac6454SAndrew Thompson 		error = copyout(buf, ugd->ugd_data, ugd->ugd_actlen);
79702ac6454SAndrew Thompson 	} else {
79802ac6454SAndrew Thompson 		/* zero length string is default */
79902ac6454SAndrew Thompson 		error = copyout("", ugd->ugd_data, 1);
80002ac6454SAndrew Thompson 	}
80102ac6454SAndrew Thompson 	return (error);
80202ac6454SAndrew Thompson }
80302ac6454SAndrew Thompson 
80402ac6454SAndrew Thompson /*------------------------------------------------------------------------*
805a593f6b8SAndrew Thompson  *	usb_gen_fill_deviceinfo
80602ac6454SAndrew Thompson  *
80702ac6454SAndrew Thompson  * This function dumps information about an USB device to the
80802ac6454SAndrew Thompson  * structure pointed to by the "di" argument.
80902ac6454SAndrew Thompson  *
81002ac6454SAndrew Thompson  * Returns:
81102ac6454SAndrew Thompson  *    0: Success
81202ac6454SAndrew Thompson  * Else: Failure
81302ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
81402ac6454SAndrew Thompson static int
815a593f6b8SAndrew Thompson usb_gen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di)
81602ac6454SAndrew Thompson {
817760bc48eSAndrew Thompson 	struct usb_device *udev;
818760bc48eSAndrew Thompson 	struct usb_device *hub;
81902ac6454SAndrew Thompson 
82002ac6454SAndrew Thompson 	udev = f->udev;
82102ac6454SAndrew Thompson 
82202ac6454SAndrew Thompson 	bzero(di, sizeof(di[0]));
82302ac6454SAndrew Thompson 
82402ac6454SAndrew Thompson 	di->udi_bus = device_get_unit(udev->bus->bdev);
82502ac6454SAndrew Thompson 	di->udi_addr = udev->address;
82602ac6454SAndrew Thompson 	di->udi_index = udev->device_index;
827ae538d85SAndrew Thompson 	strlcpy(di->udi_serial, usb_get_serial(udev), sizeof(di->udi_serial));
828ae538d85SAndrew Thompson 	strlcpy(di->udi_vendor, usb_get_manufacturer(udev), sizeof(di->udi_vendor));
829ae538d85SAndrew Thompson 	strlcpy(di->udi_product, usb_get_product(udev), sizeof(di->udi_product));
830a593f6b8SAndrew Thompson 	usb_printbcd(di->udi_release, sizeof(di->udi_release),
83102ac6454SAndrew Thompson 	    UGETW(udev->ddesc.bcdDevice));
83202ac6454SAndrew Thompson 	di->udi_vendorNo = UGETW(udev->ddesc.idVendor);
83302ac6454SAndrew Thompson 	di->udi_productNo = UGETW(udev->ddesc.idProduct);
83402ac6454SAndrew Thompson 	di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice);
83502ac6454SAndrew Thompson 	di->udi_class = udev->ddesc.bDeviceClass;
83602ac6454SAndrew Thompson 	di->udi_subclass = udev->ddesc.bDeviceSubClass;
83702ac6454SAndrew Thompson 	di->udi_protocol = udev->ddesc.bDeviceProtocol;
83802ac6454SAndrew Thompson 	di->udi_config_no = udev->curr_config_no;
83902ac6454SAndrew Thompson 	di->udi_config_index = udev->curr_config_index;
84002ac6454SAndrew Thompson 	di->udi_power = udev->flags.self_powered ? 0 : udev->power;
84102ac6454SAndrew Thompson 	di->udi_speed = udev->speed;
842f29a0724SAndrew Thompson 	di->udi_mode = udev->flags.usb_mode;
84302ac6454SAndrew Thompson 	di->udi_power_mode = udev->power_mode;
844ec8f3127SAndrew Thompson 	di->udi_suspended = udev->flags.peer_suspended;
84502ac6454SAndrew Thompson 
84602ac6454SAndrew Thompson 	hub = udev->parent_hub;
84702ac6454SAndrew Thompson 	if (hub) {
84802ac6454SAndrew Thompson 		di->udi_hubaddr = hub->address;
84902ac6454SAndrew Thompson 		di->udi_hubindex = hub->device_index;
85002ac6454SAndrew Thompson 		di->udi_hubport = udev->port_no;
85102ac6454SAndrew Thompson 	}
85202ac6454SAndrew Thompson 	return (0);
85302ac6454SAndrew Thompson }
85402ac6454SAndrew Thompson 
85502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
85602ac6454SAndrew Thompson  *	ugen_check_request
85702ac6454SAndrew Thompson  *
85802ac6454SAndrew Thompson  * Return values:
85902ac6454SAndrew Thompson  * 0: Access allowed
86002ac6454SAndrew Thompson  * Else: No access
86102ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
86202ac6454SAndrew Thompson static int
863760bc48eSAndrew Thompson ugen_check_request(struct usb_device *udev, struct usb_device_request *req)
86402ac6454SAndrew Thompson {
865ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
86602ac6454SAndrew Thompson 	int error;
86702ac6454SAndrew Thompson 
86802ac6454SAndrew Thompson 	/*
86902ac6454SAndrew Thompson 	 * Avoid requests that would damage the bus integrity:
87002ac6454SAndrew Thompson 	 */
87102ac6454SAndrew Thompson 	if (((req->bmRequestType == UT_WRITE_DEVICE) &&
87202ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_ADDRESS)) ||
87302ac6454SAndrew Thompson 	    ((req->bmRequestType == UT_WRITE_DEVICE) &&
87402ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_CONFIG)) ||
87502ac6454SAndrew Thompson 	    ((req->bmRequestType == UT_WRITE_INTERFACE) &&
87602ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_INTERFACE))) {
87702ac6454SAndrew Thompson 		/*
87802ac6454SAndrew Thompson 		 * These requests can be useful for testing USB drivers.
87902ac6454SAndrew Thompson 		 */
88002ac6454SAndrew Thompson 		error = priv_check(curthread, PRIV_DRIVER);
88102ac6454SAndrew Thompson 		if (error) {
88202ac6454SAndrew Thompson 			return (error);
88302ac6454SAndrew Thompson 		}
88402ac6454SAndrew Thompson 	}
88502ac6454SAndrew Thompson 	/*
88602ac6454SAndrew Thompson 	 * Special case - handle clearing of stall
88702ac6454SAndrew Thompson 	 */
88802ac6454SAndrew Thompson 	if (req->bmRequestType == UT_WRITE_ENDPOINT) {
88902ac6454SAndrew Thompson 
890a593f6b8SAndrew Thompson 		ep = usbd_get_ep_by_addr(udev, req->wIndex[0]);
891ae60fdfbSAndrew Thompson 		if (ep == NULL) {
89202ac6454SAndrew Thompson 			return (EINVAL);
89302ac6454SAndrew Thompson 		}
89402ac6454SAndrew Thompson 		if ((req->bRequest == UR_CLEAR_FEATURE) &&
89502ac6454SAndrew Thompson 		    (UGETW(req->wValue) == UF_ENDPOINT_HALT)) {
896a593f6b8SAndrew Thompson 			usbd_clear_data_toggle(udev, ep);
89702ac6454SAndrew Thompson 		}
89802ac6454SAndrew Thompson 	}
89902ac6454SAndrew Thompson 	/* TODO: add more checks to verify the interface index */
90002ac6454SAndrew Thompson 
90102ac6454SAndrew Thompson 	return (0);
90202ac6454SAndrew Thompson }
90302ac6454SAndrew Thompson 
90402ac6454SAndrew Thompson int
905760bc48eSAndrew Thompson ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur)
90602ac6454SAndrew Thompson {
90702ac6454SAndrew Thompson 	int error;
90802ac6454SAndrew Thompson 	uint16_t len;
90902ac6454SAndrew Thompson 	uint16_t actlen;
91002ac6454SAndrew Thompson 
91102ac6454SAndrew Thompson 	if (ugen_check_request(f->udev, &ur->ucr_request)) {
91202ac6454SAndrew Thompson 		return (EPERM);
91302ac6454SAndrew Thompson 	}
91402ac6454SAndrew Thompson 	len = UGETW(ur->ucr_request.wLength);
91502ac6454SAndrew Thompson 
91602ac6454SAndrew Thompson 	/* check if "ucr_data" is valid */
91702ac6454SAndrew Thompson 	if (len != 0) {
91802ac6454SAndrew Thompson 		if (ur->ucr_data == NULL) {
91902ac6454SAndrew Thompson 			return (EFAULT);
92002ac6454SAndrew Thompson 		}
92102ac6454SAndrew Thompson 	}
92202ac6454SAndrew Thompson 	/* do the USB request */
923a593f6b8SAndrew Thompson 	error = usbd_do_request_flags
92402ac6454SAndrew Thompson 	    (f->udev, NULL, &ur->ucr_request, ur->ucr_data,
92502ac6454SAndrew Thompson 	    (ur->ucr_flags & USB_SHORT_XFER_OK) |
92602ac6454SAndrew Thompson 	    USB_USER_DATA_PTR, &actlen,
92702ac6454SAndrew Thompson 	    USB_DEFAULT_TIMEOUT);
92802ac6454SAndrew Thompson 
92902ac6454SAndrew Thompson 	ur->ucr_actlen = actlen;
93002ac6454SAndrew Thompson 
93102ac6454SAndrew Thompson 	if (error) {
93202ac6454SAndrew Thompson 		error = EIO;
93302ac6454SAndrew Thompson 	}
93402ac6454SAndrew Thompson 	return (error);
93502ac6454SAndrew Thompson }
93602ac6454SAndrew Thompson 
93702ac6454SAndrew Thompson /*------------------------------------------------------------------------
93802ac6454SAndrew Thompson  *	ugen_re_enumerate
93902ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
94002ac6454SAndrew Thompson static int
941760bc48eSAndrew Thompson ugen_re_enumerate(struct usb_fifo *f)
94202ac6454SAndrew Thompson {
943760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
94402ac6454SAndrew Thompson 	int error;
94502ac6454SAndrew Thompson 
94602ac6454SAndrew Thompson 	/*
94702ac6454SAndrew Thompson 	 * This request can be useful for testing USB drivers:
94802ac6454SAndrew Thompson 	 */
94902ac6454SAndrew Thompson 	error = priv_check(curthread, PRIV_DRIVER);
95002ac6454SAndrew Thompson 	if (error) {
95102ac6454SAndrew Thompson 		return (error);
95202ac6454SAndrew Thompson 	}
9538f9750b7SHans Petter Selasky 	if (udev->flags.usb_mode != USB_MODE_HOST) {
9548f9750b7SHans Petter Selasky 		/* not possible in device side mode */
95556b57046SHans Petter Selasky 		DPRINTFN(6, "device mode\n");
9568f9750b7SHans Petter Selasky 		return (ENOTTY);
95702ac6454SAndrew Thompson 	}
95856b57046SHans Petter Selasky 	if (udev->parent_hub == NULL) {
95956b57046SHans Petter Selasky 		/* the root HUB cannot be re-enumerated */
96056b57046SHans Petter Selasky 		DPRINTFN(6, "cannot reset root HUB\n");
96156b57046SHans Petter Selasky 		return (EINVAL);
96256b57046SHans Petter Selasky 	}
9638f9750b7SHans Petter Selasky 	/* make sure all FIFO's are gone */
9648f9750b7SHans Petter Selasky 	/* else there can be a deadlock */
9658f9750b7SHans Petter Selasky 	if (ugen_fs_uninit(f)) {
9668f9750b7SHans Petter Selasky 		/* ignore any errors */
9678f9750b7SHans Petter Selasky 		DPRINTFN(6, "no FIFOs\n");
96802ac6454SAndrew Thompson 	}
9699eb0d702SHans Petter Selasky 	/* start re-enumeration of device */
9709eb0d702SHans Petter Selasky 	usbd_start_re_enumerate(udev);
97102ac6454SAndrew Thompson 	return (0);
97202ac6454SAndrew Thompson }
97302ac6454SAndrew Thompson 
97402ac6454SAndrew Thompson int
975760bc48eSAndrew Thompson ugen_fs_uninit(struct usb_fifo *f)
97602ac6454SAndrew Thompson {
97702ac6454SAndrew Thompson 	if (f->fs_xfer == NULL) {
97802ac6454SAndrew Thompson 		return (EINVAL);
97902ac6454SAndrew Thompson 	}
980a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(f->fs_xfer, f->fs_ep_max);
98102ac6454SAndrew Thompson 	free(f->fs_xfer, M_USB);
98202ac6454SAndrew Thompson 	f->fs_xfer = NULL;
98302ac6454SAndrew Thompson 	f->fs_ep_max = 0;
98402ac6454SAndrew Thompson 	f->fs_ep_ptr = NULL;
98502ac6454SAndrew Thompson 	f->flag_iscomplete = 0;
986a593f6b8SAndrew Thompson 	usb_fifo_free_buffer(f);
98702ac6454SAndrew Thompson 	return (0);
98802ac6454SAndrew Thompson }
98902ac6454SAndrew Thompson 
99002ac6454SAndrew Thompson static uint8_t
991760bc48eSAndrew Thompson ugen_fs_get_complete(struct usb_fifo *f, uint8_t *pindex)
99202ac6454SAndrew Thompson {
993760bc48eSAndrew Thompson 	struct usb_mbuf *m;
99402ac6454SAndrew Thompson 
99502ac6454SAndrew Thompson 	USB_IF_DEQUEUE(&f->used_q, m);
99602ac6454SAndrew Thompson 
99702ac6454SAndrew Thompson 	if (m) {
99802ac6454SAndrew Thompson 		*pindex = *((uint8_t *)(m->cur_data_ptr));
99902ac6454SAndrew Thompson 
100002ac6454SAndrew Thompson 		USB_IF_ENQUEUE(&f->free_q, m);
100102ac6454SAndrew Thompson 
100202ac6454SAndrew Thompson 		return (0);		/* success */
100302ac6454SAndrew Thompson 	} else {
100402ac6454SAndrew Thompson 
100502ac6454SAndrew Thompson 		*pindex = 0;		/* fix compiler warning */
100602ac6454SAndrew Thompson 
100702ac6454SAndrew Thompson 		f->flag_iscomplete = 0;
100802ac6454SAndrew Thompson 	}
100902ac6454SAndrew Thompson 	return (1);			/* failure */
101002ac6454SAndrew Thompson }
101102ac6454SAndrew Thompson 
101202ac6454SAndrew Thompson static void
1013760bc48eSAndrew Thompson ugen_fs_set_complete(struct usb_fifo *f, uint8_t index)
101402ac6454SAndrew Thompson {
1015760bc48eSAndrew Thompson 	struct usb_mbuf *m;
101602ac6454SAndrew Thompson 
101702ac6454SAndrew Thompson 	USB_IF_DEQUEUE(&f->free_q, m);
101802ac6454SAndrew Thompson 
101902ac6454SAndrew Thompson 	if (m == NULL) {
102002ac6454SAndrew Thompson 		/* can happen during close */
102102ac6454SAndrew Thompson 		DPRINTF("out of buffers\n");
102202ac6454SAndrew Thompson 		return;
102302ac6454SAndrew Thompson 	}
102402ac6454SAndrew Thompson 	USB_MBUF_RESET(m);
102502ac6454SAndrew Thompson 
102602ac6454SAndrew Thompson 	*((uint8_t *)(m->cur_data_ptr)) = index;
102702ac6454SAndrew Thompson 
102802ac6454SAndrew Thompson 	USB_IF_ENQUEUE(&f->used_q, m);
102902ac6454SAndrew Thompson 
103002ac6454SAndrew Thompson 	f->flag_iscomplete = 1;
103102ac6454SAndrew Thompson 
1032a593f6b8SAndrew Thompson 	usb_fifo_wakeup(f);
103302ac6454SAndrew Thompson }
103402ac6454SAndrew Thompson 
103502ac6454SAndrew Thompson static int
1036760bc48eSAndrew Thompson ugen_fs_copy_in(struct usb_fifo *f, uint8_t ep_index)
103702ac6454SAndrew Thompson {
1038760bc48eSAndrew Thompson 	struct usb_device_request *req;
1039760bc48eSAndrew Thompson 	struct usb_xfer *xfer;
1040760bc48eSAndrew Thompson 	struct usb_fs_endpoint fs_ep;
104102ac6454SAndrew Thompson 	void *uaddr;			/* userland pointer */
104202ac6454SAndrew Thompson 	void *kaddr;
1043e0a69b51SAndrew Thompson 	usb_frlength_t offset;
1044e0a69b51SAndrew Thompson 	usb_frlength_t rem;
1045e0a69b51SAndrew Thompson 	usb_frcount_t n;
104602ac6454SAndrew Thompson 	uint32_t length;
104702ac6454SAndrew Thompson 	int error;
104802ac6454SAndrew Thompson 	uint8_t isread;
104902ac6454SAndrew Thompson 
105002ac6454SAndrew Thompson 	if (ep_index >= f->fs_ep_max) {
105102ac6454SAndrew Thompson 		return (EINVAL);
105202ac6454SAndrew Thompson 	}
105302ac6454SAndrew Thompson 	xfer = f->fs_xfer[ep_index];
105402ac6454SAndrew Thompson 	if (xfer == NULL) {
105502ac6454SAndrew Thompson 		return (EINVAL);
105602ac6454SAndrew Thompson 	}
105702ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
1058a593f6b8SAndrew Thompson 	if (usbd_transfer_pending(xfer)) {
105902ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
106002ac6454SAndrew Thompson 		return (EBUSY);		/* should not happen */
106102ac6454SAndrew Thompson 	}
106202ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
106302ac6454SAndrew Thompson 
106402ac6454SAndrew Thompson 	error = copyin(f->fs_ep_ptr +
106502ac6454SAndrew Thompson 	    ep_index, &fs_ep, sizeof(fs_ep));
106602ac6454SAndrew Thompson 	if (error) {
106702ac6454SAndrew Thompson 		return (error);
106802ac6454SAndrew Thompson 	}
106902ac6454SAndrew Thompson 	/* security checks */
107002ac6454SAndrew Thompson 
107102ac6454SAndrew Thompson 	if (fs_ep.nFrames > xfer->max_frame_count) {
107202ac6454SAndrew Thompson 		xfer->error = USB_ERR_INVAL;
107302ac6454SAndrew Thompson 		goto complete;
107402ac6454SAndrew Thompson 	}
107502ac6454SAndrew Thompson 	if (fs_ep.nFrames == 0) {
107602ac6454SAndrew Thompson 		xfer->error = USB_ERR_INVAL;
107702ac6454SAndrew Thompson 		goto complete;
107802ac6454SAndrew Thompson 	}
107902ac6454SAndrew Thompson 	error = copyin(fs_ep.ppBuffer,
108002ac6454SAndrew Thompson 	    &uaddr, sizeof(uaddr));
108102ac6454SAndrew Thompson 	if (error) {
108202ac6454SAndrew Thompson 		return (error);
108302ac6454SAndrew Thompson 	}
108402ac6454SAndrew Thompson 	/* reset first frame */
1085ed6d949aSAndrew Thompson 	usbd_xfer_set_frame_offset(xfer, 0, 0);
108602ac6454SAndrew Thompson 
108702ac6454SAndrew Thompson 	if (xfer->flags_int.control_xfr) {
108802ac6454SAndrew Thompson 
108902ac6454SAndrew Thompson 		req = xfer->frbuffers[0].buffer;
109002ac6454SAndrew Thompson 
109102ac6454SAndrew Thompson 		error = copyin(fs_ep.pLength,
109202ac6454SAndrew Thompson 		    &length, sizeof(length));
109302ac6454SAndrew Thompson 		if (error) {
109402ac6454SAndrew Thompson 			return (error);
109502ac6454SAndrew Thompson 		}
1096599ce1c3SAndrew Thompson 		if (length != sizeof(*req)) {
109702ac6454SAndrew Thompson 			xfer->error = USB_ERR_INVAL;
109802ac6454SAndrew Thompson 			goto complete;
109902ac6454SAndrew Thompson 		}
110002ac6454SAndrew Thompson 		if (length != 0) {
110102ac6454SAndrew Thompson 			error = copyin(uaddr, req, length);
110202ac6454SAndrew Thompson 			if (error) {
110302ac6454SAndrew Thompson 				return (error);
110402ac6454SAndrew Thompson 			}
110502ac6454SAndrew Thompson 		}
110602ac6454SAndrew Thompson 		if (ugen_check_request(f->udev, req)) {
110702ac6454SAndrew Thompson 			xfer->error = USB_ERR_INVAL;
110802ac6454SAndrew Thompson 			goto complete;
110902ac6454SAndrew Thompson 		}
1110ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, 0, length);
111102ac6454SAndrew Thompson 
111202ac6454SAndrew Thompson 		/* Host mode only ! */
111302ac6454SAndrew Thompson 		if ((req->bmRequestType &
111402ac6454SAndrew Thompson 		    (UT_READ | UT_WRITE)) == UT_READ) {
111502ac6454SAndrew Thompson 			isread = 1;
111602ac6454SAndrew Thompson 		} else {
111702ac6454SAndrew Thompson 			isread = 0;
111802ac6454SAndrew Thompson 		}
111902ac6454SAndrew Thompson 		n = 1;
112002ac6454SAndrew Thompson 		offset = sizeof(*req);
112102ac6454SAndrew Thompson 
112202ac6454SAndrew Thompson 	} else {
112302ac6454SAndrew Thompson 		/* Device and Host mode */
112402ac6454SAndrew Thompson 		if (USB_GET_DATA_ISREAD(xfer)) {
112502ac6454SAndrew Thompson 			isread = 1;
112602ac6454SAndrew Thompson 		} else {
112702ac6454SAndrew Thompson 			isread = 0;
112802ac6454SAndrew Thompson 		}
112902ac6454SAndrew Thompson 		n = 0;
113002ac6454SAndrew Thompson 		offset = 0;
113102ac6454SAndrew Thompson 	}
113202ac6454SAndrew Thompson 
1133ed6d949aSAndrew Thompson 	rem = usbd_xfer_max_len(xfer);
113402ac6454SAndrew Thompson 	xfer->nframes = fs_ep.nFrames;
113502ac6454SAndrew Thompson 	xfer->timeout = fs_ep.timeout;
113602ac6454SAndrew Thompson 	if (xfer->timeout > 65535) {
113702ac6454SAndrew Thompson 		xfer->timeout = 65535;
113802ac6454SAndrew Thompson 	}
113902ac6454SAndrew Thompson 	if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK)
114002ac6454SAndrew Thompson 		xfer->flags.short_xfer_ok = 1;
114102ac6454SAndrew Thompson 	else
114202ac6454SAndrew Thompson 		xfer->flags.short_xfer_ok = 0;
114302ac6454SAndrew Thompson 
114402ac6454SAndrew Thompson 	if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK)
114502ac6454SAndrew Thompson 		xfer->flags.short_frames_ok = 1;
114602ac6454SAndrew Thompson 	else
114702ac6454SAndrew Thompson 		xfer->flags.short_frames_ok = 0;
114802ac6454SAndrew Thompson 
114902ac6454SAndrew Thompson 	if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT)
115002ac6454SAndrew Thompson 		xfer->flags.force_short_xfer = 1;
115102ac6454SAndrew Thompson 	else
115202ac6454SAndrew Thompson 		xfer->flags.force_short_xfer = 0;
115302ac6454SAndrew Thompson 
115402ac6454SAndrew Thompson 	if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL)
1155ed6d949aSAndrew Thompson 		usbd_xfer_set_stall(xfer);
115602ac6454SAndrew Thompson 	else
115702ac6454SAndrew Thompson 		xfer->flags.stall_pipe = 0;
115802ac6454SAndrew Thompson 
115902ac6454SAndrew Thompson 	for (; n != xfer->nframes; n++) {
116002ac6454SAndrew Thompson 
116102ac6454SAndrew Thompson 		error = copyin(fs_ep.pLength + n,
116202ac6454SAndrew Thompson 		    &length, sizeof(length));
116302ac6454SAndrew Thompson 		if (error) {
116402ac6454SAndrew Thompson 			break;
116502ac6454SAndrew Thompson 		}
1166ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, n, length);
116702ac6454SAndrew Thompson 
116802ac6454SAndrew Thompson 		if (length > rem) {
116902ac6454SAndrew Thompson 			xfer->error = USB_ERR_INVAL;
117002ac6454SAndrew Thompson 			goto complete;
117102ac6454SAndrew Thompson 		}
117202ac6454SAndrew Thompson 		rem -= length;
117302ac6454SAndrew Thompson 
117402ac6454SAndrew Thompson 		if (!isread) {
117502ac6454SAndrew Thompson 
117602ac6454SAndrew Thompson 			/* we need to know the source buffer */
117702ac6454SAndrew Thompson 			error = copyin(fs_ep.ppBuffer + n,
117802ac6454SAndrew Thompson 			    &uaddr, sizeof(uaddr));
117902ac6454SAndrew Thompson 			if (error) {
118002ac6454SAndrew Thompson 				break;
118102ac6454SAndrew Thompson 			}
118202ac6454SAndrew Thompson 			if (xfer->flags_int.isochronous_xfr) {
118302ac6454SAndrew Thompson 				/* get kernel buffer address */
118402ac6454SAndrew Thompson 				kaddr = xfer->frbuffers[0].buffer;
118502ac6454SAndrew Thompson 				kaddr = USB_ADD_BYTES(kaddr, offset);
118602ac6454SAndrew Thompson 			} else {
118702ac6454SAndrew Thompson 				/* set current frame offset */
1188ed6d949aSAndrew Thompson 				usbd_xfer_set_frame_offset(xfer, offset, n);
118902ac6454SAndrew Thompson 
119002ac6454SAndrew Thompson 				/* get kernel buffer address */
119102ac6454SAndrew Thompson 				kaddr = xfer->frbuffers[n].buffer;
119202ac6454SAndrew Thompson 			}
119302ac6454SAndrew Thompson 
119402ac6454SAndrew Thompson 			/* move data */
119502ac6454SAndrew Thompson 			error = copyin(uaddr, kaddr, length);
119602ac6454SAndrew Thompson 			if (error) {
119702ac6454SAndrew Thompson 				break;
119802ac6454SAndrew Thompson 			}
119902ac6454SAndrew Thompson 		}
120002ac6454SAndrew Thompson 		offset += length;
120102ac6454SAndrew Thompson 	}
120202ac6454SAndrew Thompson 	return (error);
120302ac6454SAndrew Thompson 
120402ac6454SAndrew Thompson complete:
120502ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
120602ac6454SAndrew Thompson 	ugen_fs_set_complete(f, ep_index);
120702ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
120802ac6454SAndrew Thompson 	return (0);
120902ac6454SAndrew Thompson }
121002ac6454SAndrew Thompson 
121102ac6454SAndrew Thompson static int
1212760bc48eSAndrew Thompson ugen_fs_copy_out(struct usb_fifo *f, uint8_t ep_index)
121302ac6454SAndrew Thompson {
1214760bc48eSAndrew Thompson 	struct usb_device_request *req;
1215760bc48eSAndrew Thompson 	struct usb_xfer *xfer;
1216760bc48eSAndrew Thompson 	struct usb_fs_endpoint fs_ep;
1217760bc48eSAndrew Thompson 	struct usb_fs_endpoint *fs_ep_uptr;	/* userland ptr */
121802ac6454SAndrew Thompson 	void *uaddr;			/* userland ptr */
121902ac6454SAndrew Thompson 	void *kaddr;
1220e0a69b51SAndrew Thompson 	usb_frlength_t offset;
1221e0a69b51SAndrew Thompson 	usb_frlength_t rem;
1222e0a69b51SAndrew Thompson 	usb_frcount_t n;
122302ac6454SAndrew Thompson 	uint32_t length;
122402ac6454SAndrew Thompson 	uint32_t temp;
122502ac6454SAndrew Thompson 	int error;
122602ac6454SAndrew Thompson 	uint8_t isread;
122702ac6454SAndrew Thompson 
1228578d0effSAndrew Thompson 	if (ep_index >= f->fs_ep_max)
122902ac6454SAndrew Thompson 		return (EINVAL);
1230578d0effSAndrew Thompson 
123102ac6454SAndrew Thompson 	xfer = f->fs_xfer[ep_index];
1232578d0effSAndrew Thompson 	if (xfer == NULL)
123302ac6454SAndrew Thompson 		return (EINVAL);
1234578d0effSAndrew Thompson 
123502ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
1236a593f6b8SAndrew Thompson 	if (usbd_transfer_pending(xfer)) {
123702ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
123802ac6454SAndrew Thompson 		return (EBUSY);		/* should not happen */
123902ac6454SAndrew Thompson 	}
124002ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
124102ac6454SAndrew Thompson 
124202ac6454SAndrew Thompson 	fs_ep_uptr = f->fs_ep_ptr + ep_index;
124302ac6454SAndrew Thompson 	error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep));
124402ac6454SAndrew Thompson 	if (error) {
124502ac6454SAndrew Thompson 		return (error);
124602ac6454SAndrew Thompson 	}
124702ac6454SAndrew Thompson 	fs_ep.status = xfer->error;
124802ac6454SAndrew Thompson 	fs_ep.aFrames = xfer->aframes;
124902ac6454SAndrew Thompson 	fs_ep.isoc_time_complete = xfer->isoc_time_complete;
125002ac6454SAndrew Thompson 	if (xfer->error) {
125102ac6454SAndrew Thompson 		goto complete;
125202ac6454SAndrew Thompson 	}
125302ac6454SAndrew Thompson 	if (xfer->flags_int.control_xfr) {
125402ac6454SAndrew Thompson 		req = xfer->frbuffers[0].buffer;
125502ac6454SAndrew Thompson 
125602ac6454SAndrew Thompson 		/* Host mode only ! */
125702ac6454SAndrew Thompson 		if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) {
125802ac6454SAndrew Thompson 			isread = 1;
125902ac6454SAndrew Thompson 		} else {
126002ac6454SAndrew Thompson 			isread = 0;
126102ac6454SAndrew Thompson 		}
126202ac6454SAndrew Thompson 		if (xfer->nframes == 0)
126302ac6454SAndrew Thompson 			n = 0;		/* should never happen */
126402ac6454SAndrew Thompson 		else
126502ac6454SAndrew Thompson 			n = 1;
126602ac6454SAndrew Thompson 	} else {
126702ac6454SAndrew Thompson 		/* Device and Host mode */
126802ac6454SAndrew Thompson 		if (USB_GET_DATA_ISREAD(xfer)) {
126902ac6454SAndrew Thompson 			isread = 1;
127002ac6454SAndrew Thompson 		} else {
127102ac6454SAndrew Thompson 			isread = 0;
127202ac6454SAndrew Thompson 		}
127302ac6454SAndrew Thompson 		n = 0;
127402ac6454SAndrew Thompson 	}
127502ac6454SAndrew Thompson 
127602ac6454SAndrew Thompson 	/* Update lengths and copy out data */
127702ac6454SAndrew Thompson 
1278ed6d949aSAndrew Thompson 	rem = usbd_xfer_max_len(xfer);
127902ac6454SAndrew Thompson 	offset = 0;
128002ac6454SAndrew Thompson 
128102ac6454SAndrew Thompson 	for (; n != xfer->nframes; n++) {
128202ac6454SAndrew Thompson 
128302ac6454SAndrew Thompson 		/* get initial length into "temp" */
128402ac6454SAndrew Thompson 		error = copyin(fs_ep.pLength + n,
128502ac6454SAndrew Thompson 		    &temp, sizeof(temp));
128602ac6454SAndrew Thompson 		if (error) {
128702ac6454SAndrew Thompson 			return (error);
128802ac6454SAndrew Thompson 		}
128902ac6454SAndrew Thompson 		if (temp > rem) {
129002ac6454SAndrew Thompson 			/* the userland length has been corrupted */
129102ac6454SAndrew Thompson 			DPRINTF("corrupt userland length "
129202ac6454SAndrew Thompson 			    "%u > %u\n", temp, rem);
129302ac6454SAndrew Thompson 			fs_ep.status = USB_ERR_INVAL;
129402ac6454SAndrew Thompson 			goto complete;
129502ac6454SAndrew Thompson 		}
129602ac6454SAndrew Thompson 		rem -= temp;
129702ac6454SAndrew Thompson 
129802ac6454SAndrew Thompson 		/* get actual transfer length */
129902ac6454SAndrew Thompson 		length = xfer->frlengths[n];
130002ac6454SAndrew Thompson 		if (length > temp) {
130102ac6454SAndrew Thompson 			/* data overflow */
130202ac6454SAndrew Thompson 			fs_ep.status = USB_ERR_INVAL;
130302ac6454SAndrew Thompson 			DPRINTF("data overflow %u > %u\n",
130402ac6454SAndrew Thompson 			    length, temp);
130502ac6454SAndrew Thompson 			goto complete;
130602ac6454SAndrew Thompson 		}
130702ac6454SAndrew Thompson 		if (isread) {
130802ac6454SAndrew Thompson 
130902ac6454SAndrew Thompson 			/* we need to know the destination buffer */
131002ac6454SAndrew Thompson 			error = copyin(fs_ep.ppBuffer + n,
131102ac6454SAndrew Thompson 			    &uaddr, sizeof(uaddr));
131202ac6454SAndrew Thompson 			if (error) {
131302ac6454SAndrew Thompson 				return (error);
131402ac6454SAndrew Thompson 			}
131502ac6454SAndrew Thompson 			if (xfer->flags_int.isochronous_xfr) {
131602ac6454SAndrew Thompson 				/* only one frame buffer */
131702ac6454SAndrew Thompson 				kaddr = USB_ADD_BYTES(
131802ac6454SAndrew Thompson 				    xfer->frbuffers[0].buffer, offset);
131902ac6454SAndrew Thompson 			} else {
132002ac6454SAndrew Thompson 				/* multiple frame buffers */
132102ac6454SAndrew Thompson 				kaddr = xfer->frbuffers[n].buffer;
132202ac6454SAndrew Thompson 			}
132302ac6454SAndrew Thompson 
132402ac6454SAndrew Thompson 			/* move data */
132502ac6454SAndrew Thompson 			error = copyout(kaddr, uaddr, length);
132602ac6454SAndrew Thompson 			if (error) {
132702ac6454SAndrew Thompson 				return (error);
132802ac6454SAndrew Thompson 			}
132902ac6454SAndrew Thompson 		}
133002ac6454SAndrew Thompson 		/*
133102ac6454SAndrew Thompson 		 * Update offset according to initial length, which is
133202ac6454SAndrew Thompson 		 * needed by isochronous transfers!
133302ac6454SAndrew Thompson 		 */
133402ac6454SAndrew Thompson 		offset += temp;
133502ac6454SAndrew Thompson 
133602ac6454SAndrew Thompson 		/* update length */
133702ac6454SAndrew Thompson 		error = copyout(&length,
133802ac6454SAndrew Thompson 		    fs_ep.pLength + n, sizeof(length));
133902ac6454SAndrew Thompson 		if (error) {
134002ac6454SAndrew Thompson 			return (error);
134102ac6454SAndrew Thompson 		}
134202ac6454SAndrew Thompson 	}
134302ac6454SAndrew Thompson 
134402ac6454SAndrew Thompson complete:
134502ac6454SAndrew Thompson 	/* update "aFrames" */
134602ac6454SAndrew Thompson 	error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames,
134702ac6454SAndrew Thompson 	    sizeof(fs_ep.aFrames));
134802ac6454SAndrew Thompson 	if (error)
134902ac6454SAndrew Thompson 		goto done;
135002ac6454SAndrew Thompson 
135102ac6454SAndrew Thompson 	/* update "isoc_time_complete" */
135202ac6454SAndrew Thompson 	error = copyout(&fs_ep.isoc_time_complete,
135302ac6454SAndrew Thompson 	    &fs_ep_uptr->isoc_time_complete,
135402ac6454SAndrew Thompson 	    sizeof(fs_ep.isoc_time_complete));
135502ac6454SAndrew Thompson 	if (error)
135602ac6454SAndrew Thompson 		goto done;
135702ac6454SAndrew Thompson 	/* update "status" */
135802ac6454SAndrew Thompson 	error = copyout(&fs_ep.status, &fs_ep_uptr->status,
135902ac6454SAndrew Thompson 	    sizeof(fs_ep.status));
136002ac6454SAndrew Thompson done:
136102ac6454SAndrew Thompson 	return (error);
136202ac6454SAndrew Thompson }
136302ac6454SAndrew Thompson 
136402ac6454SAndrew Thompson static uint8_t
1365760bc48eSAndrew Thompson ugen_fifo_in_use(struct usb_fifo *f, int fflags)
136602ac6454SAndrew Thompson {
1367760bc48eSAndrew Thompson 	struct usb_fifo *f_rx;
1368760bc48eSAndrew Thompson 	struct usb_fifo *f_tx;
136902ac6454SAndrew Thompson 
137002ac6454SAndrew Thompson 	f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX];
137102ac6454SAndrew Thompson 	f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX];
137202ac6454SAndrew Thompson 
137302ac6454SAndrew Thompson 	if ((fflags & FREAD) && f_rx &&
137402ac6454SAndrew Thompson 	    (f_rx->xfer[0] || f_rx->xfer[1])) {
137502ac6454SAndrew Thompson 		return (1);		/* RX FIFO in use */
137602ac6454SAndrew Thompson 	}
137702ac6454SAndrew Thompson 	if ((fflags & FWRITE) && f_tx &&
137802ac6454SAndrew Thompson 	    (f_tx->xfer[0] || f_tx->xfer[1])) {
137902ac6454SAndrew Thompson 		return (1);		/* TX FIFO in use */
138002ac6454SAndrew Thompson 	}
138102ac6454SAndrew Thompson 	return (0);			/* not in use */
138202ac6454SAndrew Thompson }
138302ac6454SAndrew Thompson 
138402ac6454SAndrew Thompson static int
1385760bc48eSAndrew Thompson ugen_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
138602ac6454SAndrew Thompson {
1387760bc48eSAndrew Thompson 	struct usb_config usb_config[1];
1388760bc48eSAndrew Thompson 	struct usb_device_request req;
138902ac6454SAndrew Thompson 	union {
1390760bc48eSAndrew Thompson 		struct usb_fs_complete *pcomp;
1391760bc48eSAndrew Thompson 		struct usb_fs_start *pstart;
1392760bc48eSAndrew Thompson 		struct usb_fs_stop *pstop;
1393760bc48eSAndrew Thompson 		struct usb_fs_open *popen;
1394760bc48eSAndrew Thompson 		struct usb_fs_close *pclose;
1395760bc48eSAndrew Thompson 		struct usb_fs_clear_stall_sync *pstall;
139602ac6454SAndrew Thompson 		void   *addr;
139702ac6454SAndrew Thompson 	}     u;
1398ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
1399760bc48eSAndrew Thompson 	struct usb_endpoint_descriptor *ed;
14006c352e99SHans Petter Selasky 	struct usb_xfer *xfer;
140102ac6454SAndrew Thompson 	int error = 0;
140202ac6454SAndrew Thompson 	uint8_t iface_index;
140302ac6454SAndrew Thompson 	uint8_t isread;
140402ac6454SAndrew Thompson 	uint8_t ep_index;
14051c497368SHans Petter Selasky 	uint8_t pre_scale;
140602ac6454SAndrew Thompson 
140702ac6454SAndrew Thompson 	u.addr = addr;
140802ac6454SAndrew Thompson 
140902ac6454SAndrew Thompson 	DPRINTFN(6, "cmd=0x%08lx\n", cmd);
141002ac6454SAndrew Thompson 
141102ac6454SAndrew Thompson 	switch (cmd) {
141202ac6454SAndrew Thompson 	case USB_FS_COMPLETE:
141302ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
141402ac6454SAndrew Thompson 		error = ugen_fs_get_complete(f, &ep_index);
141502ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
141602ac6454SAndrew Thompson 
141702ac6454SAndrew Thompson 		if (error) {
141802ac6454SAndrew Thompson 			error = EBUSY;
141902ac6454SAndrew Thompson 			break;
142002ac6454SAndrew Thompson 		}
142102ac6454SAndrew Thompson 		u.pcomp->ep_index = ep_index;
142202ac6454SAndrew Thompson 		error = ugen_fs_copy_out(f, u.pcomp->ep_index);
142302ac6454SAndrew Thompson 		break;
142402ac6454SAndrew Thompson 
142502ac6454SAndrew Thompson 	case USB_FS_START:
142602ac6454SAndrew Thompson 		error = ugen_fs_copy_in(f, u.pstart->ep_index);
14276c352e99SHans Petter Selasky 		if (error)
142802ac6454SAndrew Thompson 			break;
142902ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
14306c352e99SHans Petter Selasky 		xfer = f->fs_xfer[u.pstart->ep_index];
14316c352e99SHans Petter Selasky 		usbd_transfer_start(xfer);
143202ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
143302ac6454SAndrew Thompson 		break;
143402ac6454SAndrew Thompson 
143502ac6454SAndrew Thompson 	case USB_FS_STOP:
143602ac6454SAndrew Thompson 		if (u.pstop->ep_index >= f->fs_ep_max) {
143702ac6454SAndrew Thompson 			error = EINVAL;
143802ac6454SAndrew Thompson 			break;
143902ac6454SAndrew Thompson 		}
144002ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
14416c352e99SHans Petter Selasky 		xfer = f->fs_xfer[u.pstart->ep_index];
14426c352e99SHans Petter Selasky 		if (usbd_transfer_pending(xfer)) {
14436c352e99SHans Petter Selasky 			usbd_transfer_stop(xfer);
14446c352e99SHans Petter Selasky 			/*
14456c352e99SHans Petter Selasky 			 * Check if the USB transfer was stopped
14466c352e99SHans Petter Selasky 			 * before it was even started. Else a cancel
14476c352e99SHans Petter Selasky 			 * callback will be pending.
14486c352e99SHans Petter Selasky 			 */
14496c352e99SHans Petter Selasky 			if (!xfer->flags_int.transferring) {
14506c352e99SHans Petter Selasky 				ugen_fs_set_complete(xfer->priv_sc,
14516c352e99SHans Petter Selasky 				    USB_P2U(xfer->priv_fifo));
14526c352e99SHans Petter Selasky 			}
14536c352e99SHans Petter Selasky 		}
145402ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
145502ac6454SAndrew Thompson 		break;
145602ac6454SAndrew Thompson 
145702ac6454SAndrew Thompson 	case USB_FS_OPEN:
145802ac6454SAndrew Thompson 		if (u.popen->ep_index >= f->fs_ep_max) {
145902ac6454SAndrew Thompson 			error = EINVAL;
146002ac6454SAndrew Thompson 			break;
146102ac6454SAndrew Thompson 		}
146202ac6454SAndrew Thompson 		if (f->fs_xfer[u.popen->ep_index] != NULL) {
146302ac6454SAndrew Thompson 			error = EBUSY;
146402ac6454SAndrew Thompson 			break;
146502ac6454SAndrew Thompson 		}
146602ac6454SAndrew Thompson 		if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) {
146702ac6454SAndrew Thompson 			u.popen->max_bufsize = USB_FS_MAX_BUFSIZE;
146802ac6454SAndrew Thompson 		}
14691c497368SHans Petter Selasky 		if (u.popen->max_frames & USB_FS_MAX_FRAMES_PRE_SCALE) {
14701c497368SHans Petter Selasky 			pre_scale = 1;
14711c497368SHans Petter Selasky 			u.popen->max_frames &= ~USB_FS_MAX_FRAMES_PRE_SCALE;
14721c497368SHans Petter Selasky 		} else {
14731c497368SHans Petter Selasky 			pre_scale = 0;
14741c497368SHans Petter Selasky 		}
147502ac6454SAndrew Thompson 		if (u.popen->max_frames > USB_FS_MAX_FRAMES) {
147602ac6454SAndrew Thompson 			u.popen->max_frames = USB_FS_MAX_FRAMES;
147702ac6454SAndrew Thompson 			break;
147802ac6454SAndrew Thompson 		}
147902ac6454SAndrew Thompson 		if (u.popen->max_frames == 0) {
148002ac6454SAndrew Thompson 			error = EINVAL;
148102ac6454SAndrew Thompson 			break;
148202ac6454SAndrew Thompson 		}
1483a593f6b8SAndrew Thompson 		ep = usbd_get_ep_by_addr(f->udev, u.popen->ep_no);
1484ae60fdfbSAndrew Thompson 		if (ep == NULL) {
148502ac6454SAndrew Thompson 			error = EINVAL;
148602ac6454SAndrew Thompson 			break;
148702ac6454SAndrew Thompson 		}
1488ae60fdfbSAndrew Thompson 		ed = ep->edesc;
148902ac6454SAndrew Thompson 		if (ed == NULL) {
149002ac6454SAndrew Thompson 			error = ENXIO;
149102ac6454SAndrew Thompson 			break;
149202ac6454SAndrew Thompson 		}
1493ae60fdfbSAndrew Thompson 		iface_index = ep->iface_index;
149402ac6454SAndrew Thompson 
14951c497368SHans Petter Selasky 		memset(usb_config, 0, sizeof(usb_config));
149602ac6454SAndrew Thompson 
1497760bc48eSAndrew Thompson 		usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
1498760bc48eSAndrew Thompson 		usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
1499760bc48eSAndrew Thompson 		usb_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN);
1500760bc48eSAndrew Thompson 		usb_config[0].interval = USB_DEFAULT_INTERVAL;
1501760bc48eSAndrew Thompson 		usb_config[0].flags.proxy_buffer = 1;
15021c497368SHans Petter Selasky 		if (pre_scale != 0)
15031c497368SHans Petter Selasky 			usb_config[0].flags.pre_scale_frames = 1;
15045b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_fs_callback;
1505760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;	/* no timeout */
1506760bc48eSAndrew Thompson 		usb_config[0].frames = u.popen->max_frames;
1507760bc48eSAndrew Thompson 		usb_config[0].bufsize = u.popen->max_bufsize;
1508760bc48eSAndrew Thompson 		usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
150902ac6454SAndrew Thompson 
1510760bc48eSAndrew Thompson 		if (usb_config[0].type == UE_CONTROL) {
1511f29a0724SAndrew Thompson 			if (f->udev->flags.usb_mode != USB_MODE_HOST) {
151202ac6454SAndrew Thompson 				error = EINVAL;
151302ac6454SAndrew Thompson 				break;
151402ac6454SAndrew Thompson 			}
151502ac6454SAndrew Thompson 		} else {
151602ac6454SAndrew Thompson 
1517760bc48eSAndrew Thompson 			isread = ((usb_config[0].endpoint &
151802ac6454SAndrew Thompson 			    (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN);
151902ac6454SAndrew Thompson 
1520f29a0724SAndrew Thompson 			if (f->udev->flags.usb_mode != USB_MODE_HOST) {
152102ac6454SAndrew Thompson 				isread = !isread;
152202ac6454SAndrew Thompson 			}
152302ac6454SAndrew Thompson 			/* check permissions */
152402ac6454SAndrew Thompson 			if (isread) {
152502ac6454SAndrew Thompson 				if (!(fflags & FREAD)) {
152602ac6454SAndrew Thompson 					error = EPERM;
152702ac6454SAndrew Thompson 					break;
152802ac6454SAndrew Thompson 				}
152902ac6454SAndrew Thompson 			} else {
153002ac6454SAndrew Thompson 				if (!(fflags & FWRITE)) {
153102ac6454SAndrew Thompson 					error = EPERM;
153202ac6454SAndrew Thompson 					break;
153302ac6454SAndrew Thompson 				}
153402ac6454SAndrew Thompson 			}
153502ac6454SAndrew Thompson 		}
1536a593f6b8SAndrew Thompson 		error = usbd_transfer_setup(f->udev, &iface_index,
1537760bc48eSAndrew Thompson 		    f->fs_xfer + u.popen->ep_index, usb_config, 1,
153802ac6454SAndrew Thompson 		    f, f->priv_mtx);
153902ac6454SAndrew Thompson 		if (error == 0) {
154002ac6454SAndrew Thompson 			/* update maximums */
154102ac6454SAndrew Thompson 			u.popen->max_packet_length =
154202ac6454SAndrew Thompson 			    f->fs_xfer[u.popen->ep_index]->max_frame_size;
154302ac6454SAndrew Thompson 			u.popen->max_bufsize =
154402ac6454SAndrew Thompson 			    f->fs_xfer[u.popen->ep_index]->max_data_length;
15451c497368SHans Petter Selasky 			/* update number of frames */
15461c497368SHans Petter Selasky 			u.popen->max_frames =
15471c497368SHans Petter Selasky 			    f->fs_xfer[u.popen->ep_index]->nframes;
15481c497368SHans Petter Selasky 			/* store index of endpoint */
154902ac6454SAndrew Thompson 			f->fs_xfer[u.popen->ep_index]->priv_fifo =
155002ac6454SAndrew Thompson 			    ((uint8_t *)0) + u.popen->ep_index;
155102ac6454SAndrew Thompson 		} else {
155202ac6454SAndrew Thompson 			error = ENOMEM;
155302ac6454SAndrew Thompson 		}
155402ac6454SAndrew Thompson 		break;
155502ac6454SAndrew Thompson 
155602ac6454SAndrew Thompson 	case USB_FS_CLOSE:
155702ac6454SAndrew Thompson 		if (u.pclose->ep_index >= f->fs_ep_max) {
155802ac6454SAndrew Thompson 			error = EINVAL;
155902ac6454SAndrew Thompson 			break;
156002ac6454SAndrew Thompson 		}
156102ac6454SAndrew Thompson 		if (f->fs_xfer[u.pclose->ep_index] == NULL) {
156202ac6454SAndrew Thompson 			error = EINVAL;
156302ac6454SAndrew Thompson 			break;
156402ac6454SAndrew Thompson 		}
1565a593f6b8SAndrew Thompson 		usbd_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1);
156602ac6454SAndrew Thompson 		break;
156702ac6454SAndrew Thompson 
156802ac6454SAndrew Thompson 	case USB_FS_CLEAR_STALL_SYNC:
156902ac6454SAndrew Thompson 		if (u.pstall->ep_index >= f->fs_ep_max) {
157002ac6454SAndrew Thompson 			error = EINVAL;
157102ac6454SAndrew Thompson 			break;
157202ac6454SAndrew Thompson 		}
157302ac6454SAndrew Thompson 		if (f->fs_xfer[u.pstall->ep_index] == NULL) {
157402ac6454SAndrew Thompson 			error = EINVAL;
157502ac6454SAndrew Thompson 			break;
157602ac6454SAndrew Thompson 		}
1577f29a0724SAndrew Thompson 		if (f->udev->flags.usb_mode != USB_MODE_HOST) {
157802ac6454SAndrew Thompson 			error = EINVAL;
157902ac6454SAndrew Thompson 			break;
158002ac6454SAndrew Thompson 		}
158102ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
1582a593f6b8SAndrew Thompson 		error = usbd_transfer_pending(f->fs_xfer[u.pstall->ep_index]);
158302ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
158402ac6454SAndrew Thompson 
158502ac6454SAndrew Thompson 		if (error) {
158602ac6454SAndrew Thompson 			return (EBUSY);
158702ac6454SAndrew Thompson 		}
1588ae60fdfbSAndrew Thompson 		ep = f->fs_xfer[u.pstall->ep_index]->endpoint;
158902ac6454SAndrew Thompson 
159002ac6454SAndrew Thompson 		/* setup a clear-stall packet */
159102ac6454SAndrew Thompson 		req.bmRequestType = UT_WRITE_ENDPOINT;
159202ac6454SAndrew Thompson 		req.bRequest = UR_CLEAR_FEATURE;
159302ac6454SAndrew Thompson 		USETW(req.wValue, UF_ENDPOINT_HALT);
1594ae60fdfbSAndrew Thompson 		req.wIndex[0] = ep->edesc->bEndpointAddress;
159502ac6454SAndrew Thompson 		req.wIndex[1] = 0;
159602ac6454SAndrew Thompson 		USETW(req.wLength, 0);
159702ac6454SAndrew Thompson 
1598a593f6b8SAndrew Thompson 		error = usbd_do_request(f->udev, NULL, &req, NULL);
159902ac6454SAndrew Thompson 		if (error == 0) {
1600a593f6b8SAndrew Thompson 			usbd_clear_data_toggle(f->udev, ep);
160102ac6454SAndrew Thompson 		} else {
160202ac6454SAndrew Thompson 			error = ENXIO;
160302ac6454SAndrew Thompson 		}
160402ac6454SAndrew Thompson 		break;
160502ac6454SAndrew Thompson 
160602ac6454SAndrew Thompson 	default:
160702ac6454SAndrew Thompson 		error = ENOIOCTL;
160802ac6454SAndrew Thompson 		break;
160902ac6454SAndrew Thompson 	}
161002ac6454SAndrew Thompson 
161102ac6454SAndrew Thompson 	DPRINTFN(6, "error=%d\n", error);
161202ac6454SAndrew Thompson 
161302ac6454SAndrew Thompson 	return (error);
161402ac6454SAndrew Thompson }
161502ac6454SAndrew Thompson 
161602ac6454SAndrew Thompson static int
1617760bc48eSAndrew Thompson ugen_set_short_xfer(struct usb_fifo *f, void *addr)
161802ac6454SAndrew Thompson {
161902ac6454SAndrew Thompson 	uint8_t t;
162002ac6454SAndrew Thompson 
162102ac6454SAndrew Thompson 	if (*(int *)addr)
162202ac6454SAndrew Thompson 		t = 1;
162302ac6454SAndrew Thompson 	else
162402ac6454SAndrew Thompson 		t = 0;
162502ac6454SAndrew Thompson 
162602ac6454SAndrew Thompson 	if (f->flag_short == t) {
162702ac6454SAndrew Thompson 		/* same value like before - accept */
162802ac6454SAndrew Thompson 		return (0);
162902ac6454SAndrew Thompson 	}
163002ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
163102ac6454SAndrew Thompson 		/* cannot change this during transfer */
163202ac6454SAndrew Thompson 		return (EBUSY);
163302ac6454SAndrew Thompson 	}
163402ac6454SAndrew Thompson 	f->flag_short = t;
163502ac6454SAndrew Thompson 	return (0);
163602ac6454SAndrew Thompson }
163702ac6454SAndrew Thompson 
163802ac6454SAndrew Thompson static int
1639760bc48eSAndrew Thompson ugen_set_timeout(struct usb_fifo *f, void *addr)
164002ac6454SAndrew Thompson {
164102ac6454SAndrew Thompson 	f->timeout = *(int *)addr;
164202ac6454SAndrew Thompson 	if (f->timeout > 65535) {
164302ac6454SAndrew Thompson 		/* limit user input */
164402ac6454SAndrew Thompson 		f->timeout = 65535;
164502ac6454SAndrew Thompson 	}
164602ac6454SAndrew Thompson 	return (0);
164702ac6454SAndrew Thompson }
164802ac6454SAndrew Thompson 
164902ac6454SAndrew Thompson static int
1650760bc48eSAndrew Thompson ugen_get_frame_size(struct usb_fifo *f, void *addr)
165102ac6454SAndrew Thompson {
165202ac6454SAndrew Thompson 	if (f->xfer[0]) {
165302ac6454SAndrew Thompson 		*(int *)addr = f->xfer[0]->max_frame_size;
165402ac6454SAndrew Thompson 	} else {
165502ac6454SAndrew Thompson 		return (EINVAL);
165602ac6454SAndrew Thompson 	}
165702ac6454SAndrew Thompson 	return (0);
165802ac6454SAndrew Thompson }
165902ac6454SAndrew Thompson 
166002ac6454SAndrew Thompson static int
1661760bc48eSAndrew Thompson ugen_set_buffer_size(struct usb_fifo *f, void *addr)
166202ac6454SAndrew Thompson {
1663e0a69b51SAndrew Thompson 	usb_frlength_t t;
166402ac6454SAndrew Thompson 
166501cf7831SAndrew Thompson 	if (*(int *)addr < 0)
166601cf7831SAndrew Thompson 		t = 0;		/* use "wMaxPacketSize" */
166702ac6454SAndrew Thompson 	else if (*(int *)addr < (256 * 1024))
166802ac6454SAndrew Thompson 		t = *(int *)addr;
166902ac6454SAndrew Thompson 	else
167002ac6454SAndrew Thompson 		t = 256 * 1024;
167102ac6454SAndrew Thompson 
167202ac6454SAndrew Thompson 	if (f->bufsize == t) {
167302ac6454SAndrew Thompson 		/* same value like before - accept */
167402ac6454SAndrew Thompson 		return (0);
167502ac6454SAndrew Thompson 	}
167602ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
167702ac6454SAndrew Thompson 		/* cannot change this during transfer */
167802ac6454SAndrew Thompson 		return (EBUSY);
167902ac6454SAndrew Thompson 	}
168002ac6454SAndrew Thompson 	f->bufsize = t;
168102ac6454SAndrew Thompson 	return (0);
168202ac6454SAndrew Thompson }
168302ac6454SAndrew Thompson 
168402ac6454SAndrew Thompson static int
1685760bc48eSAndrew Thompson ugen_get_buffer_size(struct usb_fifo *f, void *addr)
168602ac6454SAndrew Thompson {
168702ac6454SAndrew Thompson 	*(int *)addr = f->bufsize;
168802ac6454SAndrew Thompson 	return (0);
168902ac6454SAndrew Thompson }
169002ac6454SAndrew Thompson 
169102ac6454SAndrew Thompson static int
1692760bc48eSAndrew Thompson ugen_get_iface_desc(struct usb_fifo *f,
1693760bc48eSAndrew Thompson     struct usb_interface_descriptor *idesc)
169402ac6454SAndrew Thompson {
1695760bc48eSAndrew Thompson 	struct usb_interface *iface;
169602ac6454SAndrew Thompson 
1697a593f6b8SAndrew Thompson 	iface = usbd_get_iface(f->udev, f->iface_index);
169802ac6454SAndrew Thompson 	if (iface && iface->idesc) {
169902ac6454SAndrew Thompson 		*idesc = *(iface->idesc);
170002ac6454SAndrew Thompson 	} else {
170102ac6454SAndrew Thompson 		return (EIO);
170202ac6454SAndrew Thompson 	}
170302ac6454SAndrew Thompson 	return (0);
170402ac6454SAndrew Thompson }
170502ac6454SAndrew Thompson 
170602ac6454SAndrew Thompson static int
1707760bc48eSAndrew Thompson ugen_get_endpoint_desc(struct usb_fifo *f,
1708760bc48eSAndrew Thompson     struct usb_endpoint_descriptor *ed)
170902ac6454SAndrew Thompson {
1710ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
171102ac6454SAndrew Thompson 
1712ed6d949aSAndrew Thompson 	ep = usb_fifo_softc(f);
171302ac6454SAndrew Thompson 
1714ae60fdfbSAndrew Thompson 	if (ep && ep->edesc) {
1715ae60fdfbSAndrew Thompson 		*ed = *ep->edesc;
171602ac6454SAndrew Thompson 	} else {
171702ac6454SAndrew Thompson 		return (EINVAL);
171802ac6454SAndrew Thompson 	}
171902ac6454SAndrew Thompson 	return (0);
172002ac6454SAndrew Thompson }
172102ac6454SAndrew Thompson 
172202ac6454SAndrew Thompson static int
1723760bc48eSAndrew Thompson ugen_set_power_mode(struct usb_fifo *f, int mode)
172402ac6454SAndrew Thompson {
1725760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
172602ac6454SAndrew Thompson 	int err;
172702ac6454SAndrew Thompson 	uint8_t old_mode;
172802ac6454SAndrew Thompson 
172902ac6454SAndrew Thompson 	if ((udev == NULL) ||
173002ac6454SAndrew Thompson 	    (udev->parent_hub == NULL)) {
173102ac6454SAndrew Thompson 		return (EINVAL);
173202ac6454SAndrew Thompson 	}
173350230f98SAndrew Thompson 	err = priv_check(curthread, PRIV_DRIVER);
173402ac6454SAndrew Thompson 	if (err)
173502ac6454SAndrew Thompson 		return (err);
173602ac6454SAndrew Thompson 
173702ac6454SAndrew Thompson 	/* get old power mode */
173802ac6454SAndrew Thompson 	old_mode = udev->power_mode;
173902ac6454SAndrew Thompson 
174002ac6454SAndrew Thompson 	/* if no change, then just return */
174102ac6454SAndrew Thompson 	if (old_mode == mode)
174202ac6454SAndrew Thompson 		return (0);
174302ac6454SAndrew Thompson 
174402ac6454SAndrew Thompson 	switch (mode) {
174502ac6454SAndrew Thompson 	case USB_POWER_MODE_OFF:
174602ac6454SAndrew Thompson 		/* get the device unconfigured */
174702ac6454SAndrew Thompson 		err = ugen_set_config(f, USB_UNCONFIG_INDEX);
174802ac6454SAndrew Thompson 		if (err) {
174902ac6454SAndrew Thompson 			DPRINTFN(0, "Could not unconfigure "
175002ac6454SAndrew Thompson 			    "device (ignored)\n");
175102ac6454SAndrew Thompson 		}
175202ac6454SAndrew Thompson 
175302ac6454SAndrew Thompson 		/* clear port enable */
1754a593f6b8SAndrew Thompson 		err = usbd_req_clear_port_feature(udev->parent_hub,
175502ac6454SAndrew Thompson 		    NULL, udev->port_no, UHF_PORT_ENABLE);
175602ac6454SAndrew Thompson 		break;
175702ac6454SAndrew Thompson 
175802ac6454SAndrew Thompson 	case USB_POWER_MODE_ON:
175902ac6454SAndrew Thompson 	case USB_POWER_MODE_SAVE:
176002ac6454SAndrew Thompson 		break;
176102ac6454SAndrew Thompson 
176202ac6454SAndrew Thompson 	case USB_POWER_MODE_RESUME:
17632df1e9a6SAndrew Thompson #if USB_HAVE_POWERD
17642df1e9a6SAndrew Thompson 		/* let USB-powerd handle resume */
17652df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17662df1e9a6SAndrew Thompson 		udev->pwr_save.write_refs++;
17672df1e9a6SAndrew Thompson 		udev->pwr_save.last_xfer_time = ticks;
17682df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17692df1e9a6SAndrew Thompson 
17702df1e9a6SAndrew Thompson 		/* set new power mode */
17712df1e9a6SAndrew Thompson 		usbd_set_power_mode(udev, USB_POWER_MODE_SAVE);
17722df1e9a6SAndrew Thompson 
17732df1e9a6SAndrew Thompson 		/* wait for resume to complete */
17742df1e9a6SAndrew Thompson 		usb_pause_mtx(NULL, hz / 4);
17752df1e9a6SAndrew Thompson 
17762df1e9a6SAndrew Thompson 		/* clear write reference */
17772df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17782df1e9a6SAndrew Thompson 		udev->pwr_save.write_refs--;
17792df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17802df1e9a6SAndrew Thompson #endif
178102ac6454SAndrew Thompson 		mode = USB_POWER_MODE_SAVE;
178202ac6454SAndrew Thompson 		break;
178302ac6454SAndrew Thompson 
178402ac6454SAndrew Thompson 	case USB_POWER_MODE_SUSPEND:
17852df1e9a6SAndrew Thompson #if USB_HAVE_POWERD
17862df1e9a6SAndrew Thompson 		/* let USB-powerd handle suspend */
17872df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17882df1e9a6SAndrew Thompson 		udev->pwr_save.last_xfer_time = ticks - (256 * hz);
17892df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17902df1e9a6SAndrew Thompson #endif
179102ac6454SAndrew Thompson 		mode = USB_POWER_MODE_SAVE;
179202ac6454SAndrew Thompson 		break;
179302ac6454SAndrew Thompson 
179402ac6454SAndrew Thompson 	default:
179502ac6454SAndrew Thompson 		return (EINVAL);
179602ac6454SAndrew Thompson 	}
179702ac6454SAndrew Thompson 
179802ac6454SAndrew Thompson 	if (err)
179902ac6454SAndrew Thompson 		return (ENXIO);		/* I/O failure */
180002ac6454SAndrew Thompson 
180102ac6454SAndrew Thompson 	/* if we are powered off we need to re-enumerate first */
180202ac6454SAndrew Thompson 	if (old_mode == USB_POWER_MODE_OFF) {
18038f9750b7SHans Petter Selasky 		if (udev->flags.usb_mode == USB_MODE_HOST) {
18048f9750b7SHans Petter Selasky 			if (udev->re_enumerate_wait == 0)
18058f9750b7SHans Petter Selasky 				udev->re_enumerate_wait = 1;
18068f9750b7SHans Petter Selasky 		}
18078f9750b7SHans Petter Selasky 		/* set power mode will wake up the explore thread */
180802ac6454SAndrew Thompson 	}
180902ac6454SAndrew Thompson 
181002ac6454SAndrew Thompson 	/* set new power mode */
1811a593f6b8SAndrew Thompson 	usbd_set_power_mode(udev, mode);
181202ac6454SAndrew Thompson 
181302ac6454SAndrew Thompson 	return (0);			/* success */
181402ac6454SAndrew Thompson }
181502ac6454SAndrew Thompson 
181602ac6454SAndrew Thompson static int
1817760bc48eSAndrew Thompson ugen_get_power_mode(struct usb_fifo *f)
181802ac6454SAndrew Thompson {
1819760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
182002ac6454SAndrew Thompson 
1821d2d71ce7SAndrew Thompson 	if (udev == NULL)
182202ac6454SAndrew Thompson 		return (USB_POWER_MODE_ON);
1823d2d71ce7SAndrew Thompson 
182402ac6454SAndrew Thompson 	return (udev->power_mode);
182502ac6454SAndrew Thompson }
182602ac6454SAndrew Thompson 
182702ac6454SAndrew Thompson static int
1828760bc48eSAndrew Thompson ugen_do_port_feature(struct usb_fifo *f, uint8_t port_no,
182902ac6454SAndrew Thompson     uint8_t set, uint16_t feature)
183002ac6454SAndrew Thompson {
1831760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
1832760bc48eSAndrew Thompson 	struct usb_hub *hub;
183302ac6454SAndrew Thompson 	int err;
183402ac6454SAndrew Thompson 
183550230f98SAndrew Thompson 	err = priv_check(curthread, PRIV_DRIVER);
183602ac6454SAndrew Thompson 	if (err) {
183702ac6454SAndrew Thompson 		return (err);
183802ac6454SAndrew Thompson 	}
183902ac6454SAndrew Thompson 	if (port_no == 0) {
184002ac6454SAndrew Thompson 		return (EINVAL);
184102ac6454SAndrew Thompson 	}
184202ac6454SAndrew Thompson 	if ((udev == NULL) ||
184302ac6454SAndrew Thompson 	    (udev->hub == NULL)) {
184402ac6454SAndrew Thompson 		return (EINVAL);
184502ac6454SAndrew Thompson 	}
184602ac6454SAndrew Thompson 	hub = udev->hub;
184702ac6454SAndrew Thompson 
184802ac6454SAndrew Thompson 	if (port_no > hub->nports) {
184902ac6454SAndrew Thompson 		return (EINVAL);
185002ac6454SAndrew Thompson 	}
185102ac6454SAndrew Thompson 	if (set)
1852a593f6b8SAndrew Thompson 		err = usbd_req_set_port_feature(udev,
185302ac6454SAndrew Thompson 		    NULL, port_no, feature);
185402ac6454SAndrew Thompson 	else
1855a593f6b8SAndrew Thompson 		err = usbd_req_clear_port_feature(udev,
185602ac6454SAndrew Thompson 		    NULL, port_no, feature);
185702ac6454SAndrew Thompson 
185802ac6454SAndrew Thompson 	if (err)
185902ac6454SAndrew Thompson 		return (ENXIO);		/* failure */
186002ac6454SAndrew Thompson 
186102ac6454SAndrew Thompson 	return (0);			/* success */
186202ac6454SAndrew Thompson }
186302ac6454SAndrew Thompson 
186402ac6454SAndrew Thompson static int
1865760bc48eSAndrew Thompson ugen_iface_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
186602ac6454SAndrew Thompson {
1867760bc48eSAndrew Thompson 	struct usb_fifo *f_rx;
1868760bc48eSAndrew Thompson 	struct usb_fifo *f_tx;
186902ac6454SAndrew Thompson 	int error = 0;
187002ac6454SAndrew Thompson 
187102ac6454SAndrew Thompson 	f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX];
187202ac6454SAndrew Thompson 	f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX];
187302ac6454SAndrew Thompson 
187402ac6454SAndrew Thompson 	switch (cmd) {
187502ac6454SAndrew Thompson 	case USB_SET_RX_SHORT_XFER:
187602ac6454SAndrew Thompson 		if (fflags & FREAD) {
187702ac6454SAndrew Thompson 			error = ugen_set_short_xfer(f_rx, addr);
187802ac6454SAndrew Thompson 		} else {
187902ac6454SAndrew Thompson 			error = EINVAL;
188002ac6454SAndrew Thompson 		}
188102ac6454SAndrew Thompson 		break;
188202ac6454SAndrew Thompson 
188302ac6454SAndrew Thompson 	case USB_SET_TX_FORCE_SHORT:
188402ac6454SAndrew Thompson 		if (fflags & FWRITE) {
188502ac6454SAndrew Thompson 			error = ugen_set_short_xfer(f_tx, addr);
188602ac6454SAndrew Thompson 		} else {
188702ac6454SAndrew Thompson 			error = EINVAL;
188802ac6454SAndrew Thompson 		}
188902ac6454SAndrew Thompson 		break;
189002ac6454SAndrew Thompson 
189102ac6454SAndrew Thompson 	case USB_SET_RX_TIMEOUT:
189202ac6454SAndrew Thompson 		if (fflags & FREAD) {
189302ac6454SAndrew Thompson 			error = ugen_set_timeout(f_rx, addr);
189402ac6454SAndrew Thompson 		} else {
189502ac6454SAndrew Thompson 			error = EINVAL;
189602ac6454SAndrew Thompson 		}
189702ac6454SAndrew Thompson 		break;
189802ac6454SAndrew Thompson 
189902ac6454SAndrew Thompson 	case USB_SET_TX_TIMEOUT:
190002ac6454SAndrew Thompson 		if (fflags & FWRITE) {
190102ac6454SAndrew Thompson 			error = ugen_set_timeout(f_tx, addr);
190202ac6454SAndrew Thompson 		} else {
190302ac6454SAndrew Thompson 			error = EINVAL;
190402ac6454SAndrew Thompson 		}
190502ac6454SAndrew Thompson 		break;
190602ac6454SAndrew Thompson 
190702ac6454SAndrew Thompson 	case USB_GET_RX_FRAME_SIZE:
190802ac6454SAndrew Thompson 		if (fflags & FREAD) {
190902ac6454SAndrew Thompson 			error = ugen_get_frame_size(f_rx, addr);
191002ac6454SAndrew Thompson 		} else {
191102ac6454SAndrew Thompson 			error = EINVAL;
191202ac6454SAndrew Thompson 		}
191302ac6454SAndrew Thompson 		break;
191402ac6454SAndrew Thompson 
191502ac6454SAndrew Thompson 	case USB_GET_TX_FRAME_SIZE:
191602ac6454SAndrew Thompson 		if (fflags & FWRITE) {
191702ac6454SAndrew Thompson 			error = ugen_get_frame_size(f_tx, addr);
191802ac6454SAndrew Thompson 		} else {
191902ac6454SAndrew Thompson 			error = EINVAL;
192002ac6454SAndrew Thompson 		}
192102ac6454SAndrew Thompson 		break;
192202ac6454SAndrew Thompson 
192302ac6454SAndrew Thompson 	case USB_SET_RX_BUFFER_SIZE:
192402ac6454SAndrew Thompson 		if (fflags & FREAD) {
192502ac6454SAndrew Thompson 			error = ugen_set_buffer_size(f_rx, addr);
192602ac6454SAndrew Thompson 		} else {
192702ac6454SAndrew Thompson 			error = EINVAL;
192802ac6454SAndrew Thompson 		}
192902ac6454SAndrew Thompson 		break;
193002ac6454SAndrew Thompson 
193102ac6454SAndrew Thompson 	case USB_SET_TX_BUFFER_SIZE:
193202ac6454SAndrew Thompson 		if (fflags & FWRITE) {
193302ac6454SAndrew Thompson 			error = ugen_set_buffer_size(f_tx, addr);
193402ac6454SAndrew Thompson 		} else {
193502ac6454SAndrew Thompson 			error = EINVAL;
193602ac6454SAndrew Thompson 		}
193702ac6454SAndrew Thompson 		break;
193802ac6454SAndrew Thompson 
193902ac6454SAndrew Thompson 	case USB_GET_RX_BUFFER_SIZE:
194002ac6454SAndrew Thompson 		if (fflags & FREAD) {
194102ac6454SAndrew Thompson 			error = ugen_get_buffer_size(f_rx, addr);
194202ac6454SAndrew Thompson 		} else {
194302ac6454SAndrew Thompson 			error = EINVAL;
194402ac6454SAndrew Thompson 		}
194502ac6454SAndrew Thompson 		break;
194602ac6454SAndrew Thompson 
194702ac6454SAndrew Thompson 	case USB_GET_TX_BUFFER_SIZE:
194802ac6454SAndrew Thompson 		if (fflags & FWRITE) {
194902ac6454SAndrew Thompson 			error = ugen_get_buffer_size(f_tx, addr);
195002ac6454SAndrew Thompson 		} else {
195102ac6454SAndrew Thompson 			error = EINVAL;
195202ac6454SAndrew Thompson 		}
195302ac6454SAndrew Thompson 		break;
195402ac6454SAndrew Thompson 
195502ac6454SAndrew Thompson 	case USB_GET_RX_INTERFACE_DESC:
195602ac6454SAndrew Thompson 		if (fflags & FREAD) {
195702ac6454SAndrew Thompson 			error = ugen_get_iface_desc(f_rx, addr);
195802ac6454SAndrew Thompson 		} else {
195902ac6454SAndrew Thompson 			error = EINVAL;
196002ac6454SAndrew Thompson 		}
196102ac6454SAndrew Thompson 		break;
196202ac6454SAndrew Thompson 
196302ac6454SAndrew Thompson 	case USB_GET_TX_INTERFACE_DESC:
196402ac6454SAndrew Thompson 		if (fflags & FWRITE) {
196502ac6454SAndrew Thompson 			error = ugen_get_iface_desc(f_tx, addr);
196602ac6454SAndrew Thompson 		} else {
196702ac6454SAndrew Thompson 			error = EINVAL;
196802ac6454SAndrew Thompson 		}
196902ac6454SAndrew Thompson 		break;
197002ac6454SAndrew Thompson 
197102ac6454SAndrew Thompson 	case USB_GET_RX_ENDPOINT_DESC:
197202ac6454SAndrew Thompson 		if (fflags & FREAD) {
197302ac6454SAndrew Thompson 			error = ugen_get_endpoint_desc(f_rx, addr);
197402ac6454SAndrew Thompson 		} else {
197502ac6454SAndrew Thompson 			error = EINVAL;
197602ac6454SAndrew Thompson 		}
197702ac6454SAndrew Thompson 		break;
197802ac6454SAndrew Thompson 
197902ac6454SAndrew Thompson 	case USB_GET_TX_ENDPOINT_DESC:
198002ac6454SAndrew Thompson 		if (fflags & FWRITE) {
198102ac6454SAndrew Thompson 			error = ugen_get_endpoint_desc(f_tx, addr);
198202ac6454SAndrew Thompson 		} else {
198302ac6454SAndrew Thompson 			error = EINVAL;
198402ac6454SAndrew Thompson 		}
198502ac6454SAndrew Thompson 		break;
198602ac6454SAndrew Thompson 
198702ac6454SAndrew Thompson 	case USB_SET_RX_STALL_FLAG:
198802ac6454SAndrew Thompson 		if ((fflags & FREAD) && (*(int *)addr)) {
198902ac6454SAndrew Thompson 			f_rx->flag_stall = 1;
199002ac6454SAndrew Thompson 		}
199102ac6454SAndrew Thompson 		break;
199202ac6454SAndrew Thompson 
199302ac6454SAndrew Thompson 	case USB_SET_TX_STALL_FLAG:
199402ac6454SAndrew Thompson 		if ((fflags & FWRITE) && (*(int *)addr)) {
199502ac6454SAndrew Thompson 			f_tx->flag_stall = 1;
199602ac6454SAndrew Thompson 		}
199702ac6454SAndrew Thompson 		break;
199802ac6454SAndrew Thompson 
199902ac6454SAndrew Thompson 	default:
200002ac6454SAndrew Thompson 		error = ENOIOCTL;
200102ac6454SAndrew Thompson 		break;
200202ac6454SAndrew Thompson 	}
200302ac6454SAndrew Thompson 	return (error);
200402ac6454SAndrew Thompson }
200502ac6454SAndrew Thompson 
200602ac6454SAndrew Thompson static int
2007760bc48eSAndrew Thompson ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
200802ac6454SAndrew Thompson {
200902ac6454SAndrew Thompson 	union {
2010760bc48eSAndrew Thompson 		struct usb_interface_descriptor *idesc;
2011760bc48eSAndrew Thompson 		struct usb_alt_interface *ai;
2012760bc48eSAndrew Thompson 		struct usb_device_descriptor *ddesc;
2013760bc48eSAndrew Thompson 		struct usb_config_descriptor *cdesc;
2014760bc48eSAndrew Thompson 		struct usb_device_stats *stat;
2015760bc48eSAndrew Thompson 		struct usb_fs_init *pinit;
2016760bc48eSAndrew Thompson 		struct usb_fs_uninit *puninit;
201702ac6454SAndrew Thompson 		uint32_t *ptime;
201802ac6454SAndrew Thompson 		void   *addr;
201902ac6454SAndrew Thompson 		int    *pint;
202002ac6454SAndrew Thompson 	}     u;
2021760bc48eSAndrew Thompson 	struct usb_device_descriptor *dtemp;
2022760bc48eSAndrew Thompson 	struct usb_config_descriptor *ctemp;
2023760bc48eSAndrew Thompson 	struct usb_interface *iface;
202402ac6454SAndrew Thompson 	int error = 0;
202502ac6454SAndrew Thompson 	uint8_t n;
202602ac6454SAndrew Thompson 
202702ac6454SAndrew Thompson 	u.addr = addr;
202802ac6454SAndrew Thompson 
202902ac6454SAndrew Thompson 	DPRINTFN(6, "cmd=0x%08lx\n", cmd);
203002ac6454SAndrew Thompson 
203102ac6454SAndrew Thompson 	switch (cmd) {
203202ac6454SAndrew Thompson 	case USB_DISCOVER:
2033a593f6b8SAndrew Thompson 		usb_needs_explore_all();
203402ac6454SAndrew Thompson 		break;
203502ac6454SAndrew Thompson 
203602ac6454SAndrew Thompson 	case USB_SETDEBUG:
203702ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
203802ac6454SAndrew Thompson 			error = EPERM;
203902ac6454SAndrew Thompson 			break;
204002ac6454SAndrew Thompson 		}
2041a593f6b8SAndrew Thompson 		usb_debug = *(int *)addr;
204202ac6454SAndrew Thompson 		break;
204302ac6454SAndrew Thompson 
204402ac6454SAndrew Thompson 	case USB_GET_CONFIG:
204502ac6454SAndrew Thompson 		*(int *)addr = f->udev->curr_config_index;
204602ac6454SAndrew Thompson 		break;
204702ac6454SAndrew Thompson 
204802ac6454SAndrew Thompson 	case USB_SET_CONFIG:
204902ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
205002ac6454SAndrew Thompson 			error = EPERM;
205102ac6454SAndrew Thompson 			break;
205202ac6454SAndrew Thompson 		}
205302ac6454SAndrew Thompson 		error = ugen_set_config(f, *(int *)addr);
205402ac6454SAndrew Thompson 		break;
205502ac6454SAndrew Thompson 
205602ac6454SAndrew Thompson 	case USB_GET_ALTINTERFACE:
2057a593f6b8SAndrew Thompson 		iface = usbd_get_iface(f->udev,
205802ac6454SAndrew Thompson 		    u.ai->uai_interface_index);
205902ac6454SAndrew Thompson 		if (iface && iface->idesc) {
206002ac6454SAndrew Thompson 			u.ai->uai_alt_index = iface->alt_index;
206102ac6454SAndrew Thompson 		} else {
206202ac6454SAndrew Thompson 			error = EINVAL;
206302ac6454SAndrew Thompson 		}
206402ac6454SAndrew Thompson 		break;
206502ac6454SAndrew Thompson 
206602ac6454SAndrew Thompson 	case USB_SET_ALTINTERFACE:
206702ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
206802ac6454SAndrew Thompson 			error = EPERM;
206902ac6454SAndrew Thompson 			break;
207002ac6454SAndrew Thompson 		}
207102ac6454SAndrew Thompson 		error = ugen_set_interface(f,
207202ac6454SAndrew Thompson 		    u.ai->uai_interface_index, u.ai->uai_alt_index);
207302ac6454SAndrew Thompson 		break;
207402ac6454SAndrew Thompson 
207502ac6454SAndrew Thompson 	case USB_GET_DEVICE_DESC:
2076a593f6b8SAndrew Thompson 		dtemp = usbd_get_device_descriptor(f->udev);
207702ac6454SAndrew Thompson 		if (!dtemp) {
207802ac6454SAndrew Thompson 			error = EIO;
207902ac6454SAndrew Thompson 			break;
208002ac6454SAndrew Thompson 		}
208102ac6454SAndrew Thompson 		*u.ddesc = *dtemp;
208202ac6454SAndrew Thompson 		break;
208302ac6454SAndrew Thompson 
208402ac6454SAndrew Thompson 	case USB_GET_CONFIG_DESC:
2085a593f6b8SAndrew Thompson 		ctemp = usbd_get_config_descriptor(f->udev);
208602ac6454SAndrew Thompson 		if (!ctemp) {
208702ac6454SAndrew Thompson 			error = EIO;
208802ac6454SAndrew Thompson 			break;
208902ac6454SAndrew Thompson 		}
209002ac6454SAndrew Thompson 		*u.cdesc = *ctemp;
209102ac6454SAndrew Thompson 		break;
209202ac6454SAndrew Thompson 
209302ac6454SAndrew Thompson 	case USB_GET_FULL_DESC:
209402ac6454SAndrew Thompson 		error = ugen_get_cdesc(f, addr);
209502ac6454SAndrew Thompson 		break;
209602ac6454SAndrew Thompson 
209702ac6454SAndrew Thompson 	case USB_GET_STRING_DESC:
209802ac6454SAndrew Thompson 		error = ugen_get_sdesc(f, addr);
209902ac6454SAndrew Thompson 		break;
210002ac6454SAndrew Thompson 
210102ac6454SAndrew Thompson 	case USB_GET_IFACE_DRIVER:
210202ac6454SAndrew Thompson 		error = ugen_get_iface_driver(f, addr);
210302ac6454SAndrew Thompson 		break;
210402ac6454SAndrew Thompson 
210502ac6454SAndrew Thompson 	case USB_REQUEST:
210602ac6454SAndrew Thompson 	case USB_DO_REQUEST:
210702ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
210802ac6454SAndrew Thompson 			error = EPERM;
210902ac6454SAndrew Thompson 			break;
211002ac6454SAndrew Thompson 		}
211102ac6454SAndrew Thompson 		error = ugen_do_request(f, addr);
211202ac6454SAndrew Thompson 		break;
211302ac6454SAndrew Thompson 
211402ac6454SAndrew Thompson 	case USB_DEVICEINFO:
211502ac6454SAndrew Thompson 	case USB_GET_DEVICEINFO:
2116a593f6b8SAndrew Thompson 		error = usb_gen_fill_deviceinfo(f, addr);
211702ac6454SAndrew Thompson 		break;
211802ac6454SAndrew Thompson 
211902ac6454SAndrew Thompson 	case USB_DEVICESTATS:
212002ac6454SAndrew Thompson 		for (n = 0; n != 4; n++) {
212102ac6454SAndrew Thompson 
212202ac6454SAndrew Thompson 			u.stat->uds_requests_fail[n] =
212302ac6454SAndrew Thompson 			    f->udev->bus->stats_err.uds_requests[n];
212402ac6454SAndrew Thompson 
212502ac6454SAndrew Thompson 			u.stat->uds_requests_ok[n] =
212602ac6454SAndrew Thompson 			    f->udev->bus->stats_ok.uds_requests[n];
212702ac6454SAndrew Thompson 		}
212802ac6454SAndrew Thompson 		break;
212902ac6454SAndrew Thompson 
213002ac6454SAndrew Thompson 	case USB_DEVICEENUMERATE:
213102ac6454SAndrew Thompson 		error = ugen_re_enumerate(f);
213202ac6454SAndrew Thompson 		break;
213302ac6454SAndrew Thompson 
213402ac6454SAndrew Thompson 	case USB_GET_PLUGTIME:
213502ac6454SAndrew Thompson 		*u.ptime = f->udev->plugtime;
213602ac6454SAndrew Thompson 		break;
213702ac6454SAndrew Thompson 
213802ac6454SAndrew Thompson 	case USB_CLAIM_INTERFACE:
213902ac6454SAndrew Thompson 	case USB_RELEASE_INTERFACE:
214002ac6454SAndrew Thompson 		/* TODO */
214102ac6454SAndrew Thompson 		break;
214202ac6454SAndrew Thompson 
214302ac6454SAndrew Thompson 	case USB_IFACE_DRIVER_ACTIVE:
2144a7aca4cdSAndrew Thompson 
2145a7aca4cdSAndrew Thompson 		n = *u.pint & 0xFF;
2146a7aca4cdSAndrew Thompson 
2147a7aca4cdSAndrew Thompson 		iface = usbd_get_iface(f->udev, n);
2148a7aca4cdSAndrew Thompson 
2149a7aca4cdSAndrew Thompson 		if (iface && iface->subdev)
2150a7aca4cdSAndrew Thompson 			error = 0;
2151a7aca4cdSAndrew Thompson 		else
2152a7aca4cdSAndrew Thompson 			error = ENXIO;
215302ac6454SAndrew Thompson 		break;
215402ac6454SAndrew Thompson 
215502ac6454SAndrew Thompson 	case USB_IFACE_DRIVER_DETACH:
2156a7aca4cdSAndrew Thompson 
215702ac6454SAndrew Thompson 		error = priv_check(curthread, PRIV_DRIVER);
2158a7aca4cdSAndrew Thompson 
2159a7aca4cdSAndrew Thompson 		if (error)
2160a7aca4cdSAndrew Thompson 			break;
2161a7aca4cdSAndrew Thompson 
2162a7aca4cdSAndrew Thompson 		n = *u.pint & 0xFF;
2163a7aca4cdSAndrew Thompson 
2164a7aca4cdSAndrew Thompson 		if (n == USB_IFACE_INDEX_ANY) {
2165a7aca4cdSAndrew Thompson 			error = EINVAL;
216602ac6454SAndrew Thompson 			break;
216702ac6454SAndrew Thompson 		}
2168a7aca4cdSAndrew Thompson 
2169a7aca4cdSAndrew Thompson 		usb_detach_device(f->udev, n, 0);
217002ac6454SAndrew Thompson 		break;
217102ac6454SAndrew Thompson 
217202ac6454SAndrew Thompson 	case USB_SET_POWER_MODE:
217302ac6454SAndrew Thompson 		error = ugen_set_power_mode(f, *u.pint);
217402ac6454SAndrew Thompson 		break;
217502ac6454SAndrew Thompson 
217602ac6454SAndrew Thompson 	case USB_GET_POWER_MODE:
217702ac6454SAndrew Thompson 		*u.pint = ugen_get_power_mode(f);
217802ac6454SAndrew Thompson 		break;
217902ac6454SAndrew Thompson 
218002ac6454SAndrew Thompson 	case USB_SET_PORT_ENABLE:
218102ac6454SAndrew Thompson 		error = ugen_do_port_feature(f,
218202ac6454SAndrew Thompson 		    *u.pint, 1, UHF_PORT_ENABLE);
218302ac6454SAndrew Thompson 		break;
218402ac6454SAndrew Thompson 
218502ac6454SAndrew Thompson 	case USB_SET_PORT_DISABLE:
218602ac6454SAndrew Thompson 		error = ugen_do_port_feature(f,
218702ac6454SAndrew Thompson 		    *u.pint, 0, UHF_PORT_ENABLE);
218802ac6454SAndrew Thompson 		break;
218902ac6454SAndrew Thompson 
219002ac6454SAndrew Thompson 	case USB_FS_INIT:
219102ac6454SAndrew Thompson 		/* verify input parameters */
219202ac6454SAndrew Thompson 		if (u.pinit->pEndpoints == NULL) {
219302ac6454SAndrew Thompson 			error = EINVAL;
219402ac6454SAndrew Thompson 			break;
219502ac6454SAndrew Thompson 		}
219602ac6454SAndrew Thompson 		if (u.pinit->ep_index_max > 127) {
219702ac6454SAndrew Thompson 			error = EINVAL;
219802ac6454SAndrew Thompson 			break;
219902ac6454SAndrew Thompson 		}
220002ac6454SAndrew Thompson 		if (u.pinit->ep_index_max == 0) {
220102ac6454SAndrew Thompson 			error = EINVAL;
220202ac6454SAndrew Thompson 			break;
220302ac6454SAndrew Thompson 		}
220402ac6454SAndrew Thompson 		if (f->fs_xfer != NULL) {
220502ac6454SAndrew Thompson 			error = EBUSY;
220602ac6454SAndrew Thompson 			break;
220702ac6454SAndrew Thompson 		}
220802ac6454SAndrew Thompson 		if (f->dev_ep_index != 0) {
220902ac6454SAndrew Thompson 			error = EINVAL;
221002ac6454SAndrew Thompson 			break;
221102ac6454SAndrew Thompson 		}
221202ac6454SAndrew Thompson 		if (ugen_fifo_in_use(f, fflags)) {
221302ac6454SAndrew Thompson 			error = EBUSY;
221402ac6454SAndrew Thompson 			break;
221502ac6454SAndrew Thompson 		}
2216a593f6b8SAndrew Thompson 		error = usb_fifo_alloc_buffer(f, 1, u.pinit->ep_index_max);
221702ac6454SAndrew Thompson 		if (error) {
221802ac6454SAndrew Thompson 			break;
221902ac6454SAndrew Thompson 		}
222002ac6454SAndrew Thompson 		f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) *
222102ac6454SAndrew Thompson 		    u.pinit->ep_index_max, M_USB, M_WAITOK | M_ZERO);
222202ac6454SAndrew Thompson 		if (f->fs_xfer == NULL) {
2223a593f6b8SAndrew Thompson 			usb_fifo_free_buffer(f);
222402ac6454SAndrew Thompson 			error = ENOMEM;
222502ac6454SAndrew Thompson 			break;
222602ac6454SAndrew Thompson 		}
222702ac6454SAndrew Thompson 		f->fs_ep_max = u.pinit->ep_index_max;
222802ac6454SAndrew Thompson 		f->fs_ep_ptr = u.pinit->pEndpoints;
222902ac6454SAndrew Thompson 		break;
223002ac6454SAndrew Thompson 
223102ac6454SAndrew Thompson 	case USB_FS_UNINIT:
223202ac6454SAndrew Thompson 		if (u.puninit->dummy != 0) {
223302ac6454SAndrew Thompson 			error = EINVAL;
223402ac6454SAndrew Thompson 			break;
223502ac6454SAndrew Thompson 		}
223602ac6454SAndrew Thompson 		error = ugen_fs_uninit(f);
223702ac6454SAndrew Thompson 		break;
223802ac6454SAndrew Thompson 
223902ac6454SAndrew Thompson 	default:
224002ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
224102ac6454SAndrew Thompson 		error = ugen_iface_ioctl(f, cmd, addr, fflags);
224202ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
224302ac6454SAndrew Thompson 		break;
224402ac6454SAndrew Thompson 	}
224502ac6454SAndrew Thompson 	DPRINTFN(6, "error=%d\n", error);
224602ac6454SAndrew Thompson 	return (error);
224702ac6454SAndrew Thompson }
224802ac6454SAndrew Thompson 
224902ac6454SAndrew Thompson static void
22505b3bb704SAndrew Thompson ugen_ctrl_fs_callback(struct usb_xfer *xfer, usb_error_t error)
225102ac6454SAndrew Thompson {
225202ac6454SAndrew Thompson 	;				/* workaround for a bug in "indent" */
225302ac6454SAndrew Thompson 
225402ac6454SAndrew Thompson 	DPRINTF("st=%u alen=%u aframes=%u\n",
225502ac6454SAndrew Thompson 	    USB_GET_STATE(xfer), xfer->actlen, xfer->aframes);
225602ac6454SAndrew Thompson 
225702ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
225802ac6454SAndrew Thompson 	case USB_ST_SETUP:
2259a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
226002ac6454SAndrew Thompson 		break;
226102ac6454SAndrew Thompson 	default:
226202ac6454SAndrew Thompson 		ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo));
226302ac6454SAndrew Thompson 		break;
226402ac6454SAndrew Thompson 	}
226502ac6454SAndrew Thompson }
22668755859aSAndrew Thompson #endif	/* USB_HAVE_UGEN */
2267