xref: /freebsd/sys/dev/usb/usb_generic.c (revision e9cec016)
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 
1296472ac3dSEd Schouten static 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 	}
243271ae033SHans Petter Selasky 	memset(usb_config, 0, 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;
256a5cf1aaaSHans Petter Selasky 	usb_config[0].stream_id = 0;	/* XXX support more stream ID's */
257760bc48eSAndrew Thompson 	usb_config[0].direction = UE_DIR_TX;
258760bc48eSAndrew Thompson 	usb_config[0].interval = USB_DEFAULT_INTERVAL;
259760bc48eSAndrew Thompson 	usb_config[0].flags.proxy_buffer = 1;
260760bc48eSAndrew Thompson 	usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
26102ac6454SAndrew Thompson 
26202ac6454SAndrew Thompson 	switch (ed->bmAttributes & UE_XFERTYPE) {
26302ac6454SAndrew Thompson 	case UE_INTERRUPT:
26402ac6454SAndrew Thompson 	case UE_BULK:
26502ac6454SAndrew Thompson 		if (f->flag_short) {
266760bc48eSAndrew Thompson 			usb_config[0].flags.force_short_xfer = 1;
26702ac6454SAndrew Thompson 		}
2685b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_write_callback;
269760bc48eSAndrew Thompson 		usb_config[0].timeout = f->timeout;
270760bc48eSAndrew Thompson 		usb_config[0].frames = 1;
271760bc48eSAndrew Thompson 		usb_config[0].bufsize = f->bufsize;
272760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
27302ac6454SAndrew Thompson 			return (EIO);
27402ac6454SAndrew Thompson 		}
27502ac6454SAndrew Thompson 		/* first transfer does not clear stall */
27602ac6454SAndrew Thompson 		f->flag_stall = 0;
27702ac6454SAndrew Thompson 		break;
27802ac6454SAndrew Thompson 
27902ac6454SAndrew Thompson 	case UE_ISOCHRONOUS:
280760bc48eSAndrew Thompson 		usb_config[0].flags.short_xfer_ok = 1;
281760bc48eSAndrew Thompson 		usb_config[0].bufsize = 0;	/* use default */
282760bc48eSAndrew Thompson 		usb_config[0].frames = f->nframes;
283760bc48eSAndrew Thompson 		usb_config[0].callback = &ugen_isoc_write_callback;
284760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;
28502ac6454SAndrew Thompson 
28602ac6454SAndrew Thompson 		/* clone configuration */
287760bc48eSAndrew Thompson 		usb_config[1] = usb_config[0];
28802ac6454SAndrew Thompson 
289760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
29002ac6454SAndrew Thompson 			return (EIO);
29102ac6454SAndrew Thompson 		}
29202ac6454SAndrew Thompson 		break;
29302ac6454SAndrew Thompson 	default:
29402ac6454SAndrew Thompson 		return (EINVAL);
29502ac6454SAndrew Thompson 	}
29602ac6454SAndrew Thompson 	return (0);
29702ac6454SAndrew Thompson }
29802ac6454SAndrew Thompson 
29902ac6454SAndrew Thompson static int
300760bc48eSAndrew Thompson ugen_open_pipe_read(struct usb_fifo *f)
30102ac6454SAndrew Thompson {
302760bc48eSAndrew Thompson 	struct usb_config usb_config[2];
303ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
304ae60fdfbSAndrew Thompson 	struct usb_endpoint_descriptor *ed = ep->edesc;
30502ac6454SAndrew Thompson 
30602ac6454SAndrew Thompson 	mtx_assert(f->priv_mtx, MA_OWNED);
30702ac6454SAndrew Thompson 
30802ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
30902ac6454SAndrew Thompson 		/* transfers are already opened */
31002ac6454SAndrew Thompson 		return (0);
31102ac6454SAndrew Thompson 	}
312271ae033SHans Petter Selasky 	memset(usb_config, 0, sizeof(usb_config));
31302ac6454SAndrew Thompson 
314760bc48eSAndrew Thompson 	usb_config[1].type = UE_CONTROL;
315760bc48eSAndrew Thompson 	usb_config[1].endpoint = 0;
316760bc48eSAndrew Thompson 	usb_config[1].direction = UE_DIR_ANY;
317760bc48eSAndrew Thompson 	usb_config[1].timeout = 1000;	/* 1 second */
318760bc48eSAndrew Thompson 	usb_config[1].interval = 50;/* 50 milliseconds */
319760bc48eSAndrew Thompson 	usb_config[1].bufsize = sizeof(struct usb_device_request);
320760bc48eSAndrew Thompson 	usb_config[1].callback = &ugen_read_clear_stall_callback;
321760bc48eSAndrew Thompson 	usb_config[1].usb_mode = USB_MODE_HOST;
32202ac6454SAndrew Thompson 
323760bc48eSAndrew Thompson 	usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
324760bc48eSAndrew Thompson 	usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
325a5cf1aaaSHans Petter Selasky 	usb_config[0].stream_id = 0;	/* XXX support more stream ID's */
326760bc48eSAndrew Thompson 	usb_config[0].direction = UE_DIR_RX;
327760bc48eSAndrew Thompson 	usb_config[0].interval = USB_DEFAULT_INTERVAL;
328760bc48eSAndrew Thompson 	usb_config[0].flags.proxy_buffer = 1;
329760bc48eSAndrew Thompson 	usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
33002ac6454SAndrew Thompson 
33102ac6454SAndrew Thompson 	switch (ed->bmAttributes & UE_XFERTYPE) {
33202ac6454SAndrew Thompson 	case UE_INTERRUPT:
33302ac6454SAndrew Thompson 	case UE_BULK:
33402ac6454SAndrew Thompson 		if (f->flag_short) {
335760bc48eSAndrew Thompson 			usb_config[0].flags.short_xfer_ok = 1;
33602ac6454SAndrew Thompson 		}
337760bc48eSAndrew Thompson 		usb_config[0].timeout = f->timeout;
338760bc48eSAndrew Thompson 		usb_config[0].frames = 1;
3395b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_read_callback;
340760bc48eSAndrew Thompson 		usb_config[0].bufsize = f->bufsize;
34102ac6454SAndrew Thompson 
342760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
34302ac6454SAndrew Thompson 			return (EIO);
34402ac6454SAndrew Thompson 		}
34502ac6454SAndrew Thompson 		/* first transfer does not clear stall */
34602ac6454SAndrew Thompson 		f->flag_stall = 0;
34702ac6454SAndrew Thompson 		break;
34802ac6454SAndrew Thompson 
34902ac6454SAndrew Thompson 	case UE_ISOCHRONOUS:
350760bc48eSAndrew Thompson 		usb_config[0].flags.short_xfer_ok = 1;
351760bc48eSAndrew Thompson 		usb_config[0].bufsize = 0;	/* use default */
352760bc48eSAndrew Thompson 		usb_config[0].frames = f->nframes;
353760bc48eSAndrew Thompson 		usb_config[0].callback = &ugen_isoc_read_callback;
354760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;
35502ac6454SAndrew Thompson 
35602ac6454SAndrew Thompson 		/* clone configuration */
357760bc48eSAndrew Thompson 		usb_config[1] = usb_config[0];
35802ac6454SAndrew Thompson 
359760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
36002ac6454SAndrew Thompson 			return (EIO);
36102ac6454SAndrew Thompson 		}
36202ac6454SAndrew Thompson 		break;
36302ac6454SAndrew Thompson 
36402ac6454SAndrew Thompson 	default:
36502ac6454SAndrew Thompson 		return (EINVAL);
36602ac6454SAndrew Thompson 	}
36702ac6454SAndrew Thompson 	return (0);
36802ac6454SAndrew Thompson }
36902ac6454SAndrew Thompson 
37002ac6454SAndrew Thompson static void
371760bc48eSAndrew Thompson ugen_start_read(struct usb_fifo *f)
37202ac6454SAndrew Thompson {
37302ac6454SAndrew Thompson 	/* check that pipes are open */
37402ac6454SAndrew Thompson 	if (ugen_open_pipe_read(f)) {
37502ac6454SAndrew Thompson 		/* signal error */
376a593f6b8SAndrew Thompson 		usb_fifo_put_data_error(f);
37702ac6454SAndrew Thompson 	}
37802ac6454SAndrew Thompson 	/* start transfers */
379a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[0]);
380a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[1]);
38102ac6454SAndrew Thompson }
38202ac6454SAndrew Thompson 
38302ac6454SAndrew Thompson static void
384760bc48eSAndrew Thompson ugen_start_write(struct usb_fifo *f)
38502ac6454SAndrew Thompson {
38602ac6454SAndrew Thompson 	/* check that pipes are open */
38702ac6454SAndrew Thompson 	if (ugen_open_pipe_write(f)) {
38802ac6454SAndrew Thompson 		/* signal error */
389a593f6b8SAndrew Thompson 		usb_fifo_get_data_error(f);
39002ac6454SAndrew Thompson 	}
39102ac6454SAndrew Thompson 	/* start transfers */
392a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[0]);
393a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[1]);
39402ac6454SAndrew Thompson }
39502ac6454SAndrew Thompson 
39602ac6454SAndrew Thompson static void
397760bc48eSAndrew Thompson ugen_stop_io(struct usb_fifo *f)
39802ac6454SAndrew Thompson {
39902ac6454SAndrew Thompson 	/* stop transfers */
400a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[0]);
401a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[1]);
40202ac6454SAndrew Thompson }
40302ac6454SAndrew Thompson 
40402ac6454SAndrew Thompson static void
4055b3bb704SAndrew Thompson ugen_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error)
40602ac6454SAndrew Thompson {
407ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
408760bc48eSAndrew Thompson 	struct usb_mbuf *m;
40902ac6454SAndrew Thompson 
41002ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
41102ac6454SAndrew Thompson 
41202ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
41302ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
41402ac6454SAndrew Thompson 		if (xfer->actlen == 0) {
41502ac6454SAndrew Thompson 			if (f->fifo_zlp != 4) {
41602ac6454SAndrew Thompson 				f->fifo_zlp++;
41702ac6454SAndrew Thompson 			} else {
41802ac6454SAndrew Thompson 				/*
41902ac6454SAndrew Thompson 				 * Throttle a little bit we have multiple ZLPs
42002ac6454SAndrew Thompson 				 * in a row!
42102ac6454SAndrew Thompson 				 */
42202ac6454SAndrew Thompson 				xfer->interval = 64;	/* ms */
42302ac6454SAndrew Thompson 			}
42402ac6454SAndrew Thompson 		} else {
42502ac6454SAndrew Thompson 			/* clear throttle */
42602ac6454SAndrew Thompson 			xfer->interval = 0;
42702ac6454SAndrew Thompson 			f->fifo_zlp = 0;
42802ac6454SAndrew Thompson 		}
429a593f6b8SAndrew Thompson 		usb_fifo_put_data(f, xfer->frbuffers, 0,
43002ac6454SAndrew Thompson 		    xfer->actlen, 1);
43102ac6454SAndrew Thompson 
43202ac6454SAndrew Thompson 	case USB_ST_SETUP:
43302ac6454SAndrew Thompson 		if (f->flag_stall) {
434a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
43502ac6454SAndrew Thompson 			break;
43602ac6454SAndrew Thompson 		}
43702ac6454SAndrew Thompson 		USB_IF_POLL(&f->free_q, m);
43802ac6454SAndrew Thompson 		if (m) {
439ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
440a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
44102ac6454SAndrew Thompson 		}
44202ac6454SAndrew Thompson 		break;
44302ac6454SAndrew Thompson 
44402ac6454SAndrew Thompson 	default:			/* Error */
44502ac6454SAndrew Thompson 		if (xfer->error != USB_ERR_CANCELLED) {
44601cf7831SAndrew Thompson 			/* send a zero length packet to userland */
447a593f6b8SAndrew Thompson 			usb_fifo_put_data(f, xfer->frbuffers, 0, 0, 1);
44802ac6454SAndrew Thompson 			f->flag_stall = 1;
44902ac6454SAndrew Thompson 			f->fifo_zlp = 0;
450a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
45102ac6454SAndrew Thompson 		}
45202ac6454SAndrew Thompson 		break;
45302ac6454SAndrew Thompson 	}
45402ac6454SAndrew Thompson }
45502ac6454SAndrew Thompson 
45602ac6454SAndrew Thompson static void
4575b3bb704SAndrew Thompson ugen_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error)
45802ac6454SAndrew Thompson {
459ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
460e0a69b51SAndrew Thompson 	usb_frlength_t actlen;
46102ac6454SAndrew Thompson 
46202ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
46302ac6454SAndrew Thompson 
46402ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
46502ac6454SAndrew Thompson 	case USB_ST_SETUP:
46602ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
46702ac6454SAndrew Thompson 		/*
46802ac6454SAndrew Thompson 		 * If writing is in stall, just jump to clear stall
46902ac6454SAndrew Thompson 		 * callback and solve the situation.
47002ac6454SAndrew Thompson 		 */
47102ac6454SAndrew Thompson 		if (f->flag_stall) {
472a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
47302ac6454SAndrew Thompson 			break;
47402ac6454SAndrew Thompson 		}
47502ac6454SAndrew Thompson 		/*
47602ac6454SAndrew Thompson 		 * Write data, setup and perform hardware transfer.
47702ac6454SAndrew Thompson 		 */
478a593f6b8SAndrew Thompson 		if (usb_fifo_get_data(f, xfer->frbuffers, 0,
47902ac6454SAndrew Thompson 		    xfer->max_data_length, &actlen, 0)) {
480ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, 0, actlen);
481a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
48202ac6454SAndrew Thompson 		}
48302ac6454SAndrew Thompson 		break;
48402ac6454SAndrew Thompson 
48502ac6454SAndrew Thompson 	default:			/* Error */
48602ac6454SAndrew Thompson 		if (xfer->error != USB_ERR_CANCELLED) {
48702ac6454SAndrew Thompson 			f->flag_stall = 1;
488a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
48902ac6454SAndrew Thompson 		}
49002ac6454SAndrew Thompson 		break;
49102ac6454SAndrew Thompson 	}
49202ac6454SAndrew Thompson }
49302ac6454SAndrew Thompson 
49402ac6454SAndrew Thompson static void
495ed6d949aSAndrew Thompson ugen_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
49602ac6454SAndrew Thompson {
497ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
498760bc48eSAndrew Thompson 	struct usb_xfer *xfer_other = f->xfer[0];
49902ac6454SAndrew Thompson 
50002ac6454SAndrew Thompson 	if (f->flag_stall == 0) {
50102ac6454SAndrew Thompson 		/* nothing to do */
50202ac6454SAndrew Thompson 		return;
50302ac6454SAndrew Thompson 	}
504a593f6b8SAndrew Thompson 	if (usbd_clear_stall_callback(xfer, xfer_other)) {
50502ac6454SAndrew Thompson 		DPRINTFN(5, "f=%p: stall cleared\n", f);
50602ac6454SAndrew Thompson 		f->flag_stall = 0;
507a593f6b8SAndrew Thompson 		usbd_transfer_start(xfer_other);
50802ac6454SAndrew Thompson 	}
50902ac6454SAndrew Thompson }
51002ac6454SAndrew Thompson 
51102ac6454SAndrew Thompson static void
512ed6d949aSAndrew Thompson ugen_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
51302ac6454SAndrew Thompson {
514ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
515760bc48eSAndrew Thompson 	struct usb_xfer *xfer_other = f->xfer[0];
51602ac6454SAndrew Thompson 
51702ac6454SAndrew Thompson 	if (f->flag_stall == 0) {
51802ac6454SAndrew Thompson 		/* nothing to do */
51902ac6454SAndrew Thompson 		return;
52002ac6454SAndrew Thompson 	}
521a593f6b8SAndrew Thompson 	if (usbd_clear_stall_callback(xfer, xfer_other)) {
52202ac6454SAndrew Thompson 		DPRINTFN(5, "f=%p: stall cleared\n", f);
52302ac6454SAndrew Thompson 		f->flag_stall = 0;
524a593f6b8SAndrew Thompson 		usbd_transfer_start(xfer_other);
52502ac6454SAndrew Thompson 	}
52602ac6454SAndrew Thompson }
52702ac6454SAndrew Thompson 
52802ac6454SAndrew Thompson static void
529ed6d949aSAndrew Thompson ugen_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error)
53002ac6454SAndrew Thompson {
531ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
532e0a69b51SAndrew Thompson 	usb_frlength_t offset;
533e0a69b51SAndrew Thompson 	usb_frcount_t n;
53402ac6454SAndrew Thompson 
53502ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
53602ac6454SAndrew Thompson 
53702ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
53802ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
53902ac6454SAndrew Thompson 
54002ac6454SAndrew Thompson 		DPRINTFN(6, "actlen=%d\n", xfer->actlen);
54102ac6454SAndrew Thompson 
54202ac6454SAndrew Thompson 		offset = 0;
54302ac6454SAndrew Thompson 
54402ac6454SAndrew Thompson 		for (n = 0; n != xfer->aframes; n++) {
545a593f6b8SAndrew Thompson 			usb_fifo_put_data(f, xfer->frbuffers, offset,
54602ac6454SAndrew Thompson 			    xfer->frlengths[n], 1);
54702ac6454SAndrew Thompson 			offset += xfer->max_frame_size;
54802ac6454SAndrew Thompson 		}
54902ac6454SAndrew Thompson 
55002ac6454SAndrew Thompson 	case USB_ST_SETUP:
55102ac6454SAndrew Thompson tr_setup:
55202ac6454SAndrew Thompson 		for (n = 0; n != xfer->nframes; n++) {
55302ac6454SAndrew Thompson 			/* setup size for next transfer */
554ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, xfer->max_frame_size);
55502ac6454SAndrew Thompson 		}
556a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
55702ac6454SAndrew Thompson 		break;
55802ac6454SAndrew Thompson 
55902ac6454SAndrew Thompson 	default:			/* Error */
56002ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
56102ac6454SAndrew Thompson 			break;
56202ac6454SAndrew Thompson 		}
56302ac6454SAndrew Thompson 		goto tr_setup;
56402ac6454SAndrew Thompson 	}
56502ac6454SAndrew Thompson }
56602ac6454SAndrew Thompson 
56702ac6454SAndrew Thompson static void
568ed6d949aSAndrew Thompson ugen_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error)
56902ac6454SAndrew Thompson {
570ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
571e0a69b51SAndrew Thompson 	usb_frlength_t actlen;
572e0a69b51SAndrew Thompson 	usb_frlength_t offset;
573e0a69b51SAndrew Thompson 	usb_frcount_t n;
57402ac6454SAndrew Thompson 
57502ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
57602ac6454SAndrew Thompson 
57702ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
57802ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
57902ac6454SAndrew Thompson 	case USB_ST_SETUP:
58002ac6454SAndrew Thompson tr_setup:
58102ac6454SAndrew Thompson 		offset = 0;
58202ac6454SAndrew Thompson 		for (n = 0; n != xfer->nframes; n++) {
583a593f6b8SAndrew Thompson 			if (usb_fifo_get_data(f, xfer->frbuffers, offset,
58402ac6454SAndrew Thompson 			    xfer->max_frame_size, &actlen, 1)) {
585ed6d949aSAndrew Thompson 				usbd_xfer_set_frame_len(xfer, n, actlen);
58602ac6454SAndrew Thompson 				offset += actlen;
58702ac6454SAndrew Thompson 			} else {
58802ac6454SAndrew Thompson 				break;
58902ac6454SAndrew Thompson 			}
59002ac6454SAndrew Thompson 		}
59102ac6454SAndrew Thompson 
59202ac6454SAndrew Thompson 		for (; n != xfer->nframes; n++) {
59302ac6454SAndrew Thompson 			/* fill in zero frames */
594ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, 0);
59502ac6454SAndrew Thompson 		}
596a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
59702ac6454SAndrew Thompson 		break;
59802ac6454SAndrew Thompson 
59902ac6454SAndrew Thompson 	default:			/* Error */
60002ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
60102ac6454SAndrew Thompson 			break;
60202ac6454SAndrew Thompson 		}
60302ac6454SAndrew Thompson 		goto tr_setup;
60402ac6454SAndrew Thompson 	}
60502ac6454SAndrew Thompson }
60602ac6454SAndrew Thompson 
60702ac6454SAndrew Thompson static int
608760bc48eSAndrew Thompson ugen_set_config(struct usb_fifo *f, uint8_t index)
60902ac6454SAndrew Thompson {
61002ac6454SAndrew Thompson 	DPRINTFN(2, "index %u\n", index);
61102ac6454SAndrew Thompson 
612f29a0724SAndrew Thompson 	if (f->udev->flags.usb_mode != USB_MODE_HOST) {
61302ac6454SAndrew Thompson 		/* not possible in device side mode */
61402ac6454SAndrew Thompson 		return (ENOTTY);
61502ac6454SAndrew Thompson 	}
61602ac6454SAndrew Thompson 	if (f->udev->curr_config_index == index) {
61702ac6454SAndrew Thompson 		/* no change needed */
61802ac6454SAndrew Thompson 		return (0);
61902ac6454SAndrew Thompson 	}
62002ac6454SAndrew Thompson 	/* make sure all FIFO's are gone */
62102ac6454SAndrew Thompson 	/* else there can be a deadlock */
62202ac6454SAndrew Thompson 	if (ugen_fs_uninit(f)) {
62302ac6454SAndrew Thompson 		/* ignore any errors */
62402ac6454SAndrew Thompson 		DPRINTFN(6, "no FIFOs\n");
62502ac6454SAndrew Thompson 	}
62602ac6454SAndrew Thompson 	/* change setting - will free generic FIFOs, if any */
627a593f6b8SAndrew Thompson 	if (usbd_set_config_index(f->udev, index)) {
62802ac6454SAndrew Thompson 		return (EIO);
62902ac6454SAndrew Thompson 	}
63002ac6454SAndrew Thompson 	/* probe and attach */
631a593f6b8SAndrew Thompson 	if (usb_probe_and_attach(f->udev, USB_IFACE_INDEX_ANY)) {
63202ac6454SAndrew Thompson 		return (EIO);
63302ac6454SAndrew Thompson 	}
63402ac6454SAndrew Thompson 	return (0);
63502ac6454SAndrew Thompson }
63602ac6454SAndrew Thompson 
63702ac6454SAndrew Thompson static int
638760bc48eSAndrew Thompson ugen_set_interface(struct usb_fifo *f,
63902ac6454SAndrew Thompson     uint8_t iface_index, uint8_t alt_index)
64002ac6454SAndrew Thompson {
64102ac6454SAndrew Thompson 	DPRINTFN(2, "%u, %u\n", iface_index, alt_index);
64202ac6454SAndrew Thompson 
643f29a0724SAndrew Thompson 	if (f->udev->flags.usb_mode != USB_MODE_HOST) {
64402ac6454SAndrew Thompson 		/* not possible in device side mode */
64502ac6454SAndrew Thompson 		return (ENOTTY);
64602ac6454SAndrew Thompson 	}
64702ac6454SAndrew Thompson 	/* make sure all FIFO's are gone */
64802ac6454SAndrew Thompson 	/* else there can be a deadlock */
64902ac6454SAndrew Thompson 	if (ugen_fs_uninit(f)) {
65002ac6454SAndrew Thompson 		/* ignore any errors */
65102ac6454SAndrew Thompson 		DPRINTFN(6, "no FIFOs\n");
65202ac6454SAndrew Thompson 	}
65302ac6454SAndrew Thompson 	/* change setting - will free generic FIFOs, if any */
654a593f6b8SAndrew Thompson 	if (usbd_set_alt_interface_index(f->udev, iface_index, alt_index)) {
65502ac6454SAndrew Thompson 		return (EIO);
65602ac6454SAndrew Thompson 	}
65702ac6454SAndrew Thompson 	/* probe and attach */
658a593f6b8SAndrew Thompson 	if (usb_probe_and_attach(f->udev, iface_index)) {
65902ac6454SAndrew Thompson 		return (EIO);
66002ac6454SAndrew Thompson 	}
66102ac6454SAndrew Thompson 	return (0);
66202ac6454SAndrew Thompson }
66302ac6454SAndrew Thompson 
66402ac6454SAndrew Thompson /*------------------------------------------------------------------------*
66502ac6454SAndrew Thompson  *	ugen_get_cdesc
66602ac6454SAndrew Thompson  *
66702ac6454SAndrew Thompson  * This function will retrieve the complete configuration descriptor
66802ac6454SAndrew Thompson  * at the given index.
66902ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
67002ac6454SAndrew Thompson static int
671760bc48eSAndrew Thompson ugen_get_cdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
67202ac6454SAndrew Thompson {
673760bc48eSAndrew Thompson 	struct usb_config_descriptor *cdesc;
674760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
67502ac6454SAndrew Thompson 	int error;
67602ac6454SAndrew Thompson 	uint16_t len;
67702ac6454SAndrew Thompson 	uint8_t free_data;
67802ac6454SAndrew Thompson 
67902ac6454SAndrew Thompson 	DPRINTFN(6, "\n");
68002ac6454SAndrew Thompson 
68102ac6454SAndrew Thompson 	if (ugd->ugd_data == NULL) {
68202ac6454SAndrew Thompson 		/* userland pointer should not be zero */
68302ac6454SAndrew Thompson 		return (EINVAL);
68402ac6454SAndrew Thompson 	}
68502ac6454SAndrew Thompson 	if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) ||
68602ac6454SAndrew Thompson 	    (ugd->ugd_config_index == udev->curr_config_index)) {
687a593f6b8SAndrew Thompson 		cdesc = usbd_get_config_descriptor(udev);
68802ac6454SAndrew Thompson 		if (cdesc == NULL) {
68902ac6454SAndrew Thompson 			return (ENXIO);
69002ac6454SAndrew Thompson 		}
69102ac6454SAndrew Thompson 		free_data = 0;
69202ac6454SAndrew Thompson 
69302ac6454SAndrew Thompson 	} else {
694a593f6b8SAndrew Thompson 		if (usbd_req_get_config_desc_full(udev,
695e2805033SAndrew Thompson 		    NULL, &cdesc, M_USBDEV,
69602ac6454SAndrew Thompson 		    ugd->ugd_config_index)) {
69702ac6454SAndrew Thompson 			return (ENXIO);
69802ac6454SAndrew Thompson 		}
69902ac6454SAndrew Thompson 		free_data = 1;
70002ac6454SAndrew Thompson 	}
70102ac6454SAndrew Thompson 
70202ac6454SAndrew Thompson 	len = UGETW(cdesc->wTotalLength);
70302ac6454SAndrew Thompson 	if (len > ugd->ugd_maxlen) {
70402ac6454SAndrew Thompson 		len = ugd->ugd_maxlen;
70502ac6454SAndrew Thompson 	}
70602ac6454SAndrew Thompson 	DPRINTFN(6, "len=%u\n", len);
70702ac6454SAndrew Thompson 
70802ac6454SAndrew Thompson 	ugd->ugd_actlen = len;
70902ac6454SAndrew Thompson 	ugd->ugd_offset = 0;
71002ac6454SAndrew Thompson 
71102ac6454SAndrew Thompson 	error = copyout(cdesc, ugd->ugd_data, len);
71202ac6454SAndrew Thompson 
71302ac6454SAndrew Thompson 	if (free_data) {
71402ac6454SAndrew Thompson 		free(cdesc, M_USBDEV);
71502ac6454SAndrew Thompson 	}
71602ac6454SAndrew Thompson 	return (error);
71702ac6454SAndrew Thompson }
71802ac6454SAndrew Thompson 
71902ac6454SAndrew Thompson static int
720760bc48eSAndrew Thompson ugen_get_sdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
72102ac6454SAndrew Thompson {
72202ac6454SAndrew Thompson 	void *ptr = f->udev->bus->scratch[0].data;
72302ac6454SAndrew Thompson 	uint16_t size = sizeof(f->udev->bus->scratch[0].data);
72402ac6454SAndrew Thompson 	int error;
72502ac6454SAndrew Thompson 
726a593f6b8SAndrew Thompson 	if (usbd_req_get_string_desc(f->udev, NULL, ptr,
72702ac6454SAndrew Thompson 	    size, ugd->ugd_lang_id, ugd->ugd_string_index)) {
72802ac6454SAndrew Thompson 		error = EINVAL;
72902ac6454SAndrew Thompson 	} else {
73002ac6454SAndrew Thompson 
73102ac6454SAndrew Thompson 		if (size > ((uint8_t *)ptr)[0]) {
73202ac6454SAndrew Thompson 			size = ((uint8_t *)ptr)[0];
73302ac6454SAndrew Thompson 		}
73402ac6454SAndrew Thompson 		if (size > ugd->ugd_maxlen) {
73502ac6454SAndrew Thompson 			size = ugd->ugd_maxlen;
73602ac6454SAndrew Thompson 		}
73702ac6454SAndrew Thompson 		ugd->ugd_actlen = size;
73802ac6454SAndrew Thompson 		ugd->ugd_offset = 0;
73902ac6454SAndrew Thompson 
74002ac6454SAndrew Thompson 		error = copyout(ptr, ugd->ugd_data, size);
74102ac6454SAndrew Thompson 	}
74202ac6454SAndrew Thompson 	return (error);
74302ac6454SAndrew Thompson }
74402ac6454SAndrew Thompson 
74502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
74602ac6454SAndrew Thompson  *	ugen_get_iface_driver
74702ac6454SAndrew Thompson  *
74802ac6454SAndrew Thompson  * This function generates an USB interface description for userland.
74902ac6454SAndrew Thompson  *
75002ac6454SAndrew Thompson  * Returns:
75102ac6454SAndrew Thompson  *    0: Success
75202ac6454SAndrew Thompson  * Else: Failure
75302ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
75402ac6454SAndrew Thompson static int
755760bc48eSAndrew Thompson ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
75602ac6454SAndrew Thompson {
757760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
758760bc48eSAndrew Thompson 	struct usb_interface *iface;
75902ac6454SAndrew Thompson 	const char *ptr;
76002ac6454SAndrew Thompson 	const char *desc;
76102ac6454SAndrew Thompson 	unsigned int len;
76202ac6454SAndrew Thompson 	unsigned int maxlen;
76302ac6454SAndrew Thompson 	char buf[128];
76402ac6454SAndrew Thompson 	int error;
76502ac6454SAndrew Thompson 
76602ac6454SAndrew Thompson 	DPRINTFN(6, "\n");
76702ac6454SAndrew Thompson 
76802ac6454SAndrew Thompson 	if ((ugd->ugd_data == NULL) || (ugd->ugd_maxlen == 0)) {
76902ac6454SAndrew Thompson 		/* userland pointer should not be zero */
77002ac6454SAndrew Thompson 		return (EINVAL);
77102ac6454SAndrew Thompson 	}
77202ac6454SAndrew Thompson 
773a593f6b8SAndrew Thompson 	iface = usbd_get_iface(udev, ugd->ugd_iface_index);
77402ac6454SAndrew Thompson 	if ((iface == NULL) || (iface->idesc == NULL)) {
77502ac6454SAndrew Thompson 		/* invalid interface index */
77602ac6454SAndrew Thompson 		return (EINVAL);
77702ac6454SAndrew Thompson 	}
77802ac6454SAndrew Thompson 
77902ac6454SAndrew Thompson 	/* read out device nameunit string, if any */
78002ac6454SAndrew Thompson 	if ((iface->subdev != NULL) &&
78102ac6454SAndrew Thompson 	    device_is_attached(iface->subdev) &&
78202ac6454SAndrew Thompson 	    (ptr = device_get_nameunit(iface->subdev)) &&
78302ac6454SAndrew Thompson 	    (desc = device_get_desc(iface->subdev))) {
78402ac6454SAndrew Thompson 
78502ac6454SAndrew Thompson 		/* print description */
78602ac6454SAndrew Thompson 		snprintf(buf, sizeof(buf), "%s: <%s>", ptr, desc);
78702ac6454SAndrew Thompson 
78802ac6454SAndrew Thompson 		/* range checks */
78902ac6454SAndrew Thompson 		maxlen = ugd->ugd_maxlen - 1;
79002ac6454SAndrew Thompson 		len = strlen(buf);
79102ac6454SAndrew Thompson 		if (len > maxlen)
79202ac6454SAndrew Thompson 			len = maxlen;
79302ac6454SAndrew Thompson 
79402ac6454SAndrew Thompson 		/* update actual length, including terminating zero */
79502ac6454SAndrew Thompson 		ugd->ugd_actlen = len + 1;
79602ac6454SAndrew Thompson 
79702ac6454SAndrew Thompson 		/* copy out interface description */
79802ac6454SAndrew Thompson 		error = copyout(buf, ugd->ugd_data, ugd->ugd_actlen);
79902ac6454SAndrew Thompson 	} else {
80002ac6454SAndrew Thompson 		/* zero length string is default */
80102ac6454SAndrew Thompson 		error = copyout("", ugd->ugd_data, 1);
80202ac6454SAndrew Thompson 	}
80302ac6454SAndrew Thompson 	return (error);
80402ac6454SAndrew Thompson }
80502ac6454SAndrew Thompson 
80602ac6454SAndrew Thompson /*------------------------------------------------------------------------*
807a593f6b8SAndrew Thompson  *	usb_gen_fill_deviceinfo
80802ac6454SAndrew Thompson  *
80902ac6454SAndrew Thompson  * This function dumps information about an USB device to the
81002ac6454SAndrew Thompson  * structure pointed to by the "di" argument.
81102ac6454SAndrew Thompson  *
81202ac6454SAndrew Thompson  * Returns:
81302ac6454SAndrew Thompson  *    0: Success
81402ac6454SAndrew Thompson  * Else: Failure
81502ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
81602ac6454SAndrew Thompson static int
817a593f6b8SAndrew Thompson usb_gen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di)
81802ac6454SAndrew Thompson {
819760bc48eSAndrew Thompson 	struct usb_device *udev;
820760bc48eSAndrew Thompson 	struct usb_device *hub;
82102ac6454SAndrew Thompson 
82202ac6454SAndrew Thompson 	udev = f->udev;
82302ac6454SAndrew Thompson 
82402ac6454SAndrew Thompson 	bzero(di, sizeof(di[0]));
82502ac6454SAndrew Thompson 
82602ac6454SAndrew Thompson 	di->udi_bus = device_get_unit(udev->bus->bdev);
82702ac6454SAndrew Thompson 	di->udi_addr = udev->address;
82802ac6454SAndrew Thompson 	di->udi_index = udev->device_index;
829ae538d85SAndrew Thompson 	strlcpy(di->udi_serial, usb_get_serial(udev), sizeof(di->udi_serial));
830ae538d85SAndrew Thompson 	strlcpy(di->udi_vendor, usb_get_manufacturer(udev), sizeof(di->udi_vendor));
831ae538d85SAndrew Thompson 	strlcpy(di->udi_product, usb_get_product(udev), sizeof(di->udi_product));
832a593f6b8SAndrew Thompson 	usb_printbcd(di->udi_release, sizeof(di->udi_release),
83302ac6454SAndrew Thompson 	    UGETW(udev->ddesc.bcdDevice));
83402ac6454SAndrew Thompson 	di->udi_vendorNo = UGETW(udev->ddesc.idVendor);
83502ac6454SAndrew Thompson 	di->udi_productNo = UGETW(udev->ddesc.idProduct);
83602ac6454SAndrew Thompson 	di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice);
83702ac6454SAndrew Thompson 	di->udi_class = udev->ddesc.bDeviceClass;
83802ac6454SAndrew Thompson 	di->udi_subclass = udev->ddesc.bDeviceSubClass;
83902ac6454SAndrew Thompson 	di->udi_protocol = udev->ddesc.bDeviceProtocol;
84002ac6454SAndrew Thompson 	di->udi_config_no = udev->curr_config_no;
84102ac6454SAndrew Thompson 	di->udi_config_index = udev->curr_config_index;
84202ac6454SAndrew Thompson 	di->udi_power = udev->flags.self_powered ? 0 : udev->power;
84302ac6454SAndrew Thompson 	di->udi_speed = udev->speed;
844f29a0724SAndrew Thompson 	di->udi_mode = udev->flags.usb_mode;
84502ac6454SAndrew Thompson 	di->udi_power_mode = udev->power_mode;
846ec8f3127SAndrew Thompson 	di->udi_suspended = udev->flags.peer_suspended;
84702ac6454SAndrew Thompson 
84802ac6454SAndrew Thompson 	hub = udev->parent_hub;
84902ac6454SAndrew Thompson 	if (hub) {
85002ac6454SAndrew Thompson 		di->udi_hubaddr = hub->address;
85102ac6454SAndrew Thompson 		di->udi_hubindex = hub->device_index;
85202ac6454SAndrew Thompson 		di->udi_hubport = udev->port_no;
85302ac6454SAndrew Thompson 	}
85402ac6454SAndrew Thompson 	return (0);
85502ac6454SAndrew Thompson }
85602ac6454SAndrew Thompson 
85702ac6454SAndrew Thompson /*------------------------------------------------------------------------*
85802ac6454SAndrew Thompson  *	ugen_check_request
85902ac6454SAndrew Thompson  *
86002ac6454SAndrew Thompson  * Return values:
86102ac6454SAndrew Thompson  * 0: Access allowed
86202ac6454SAndrew Thompson  * Else: No access
86302ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
86402ac6454SAndrew Thompson static int
865760bc48eSAndrew Thompson ugen_check_request(struct usb_device *udev, struct usb_device_request *req)
86602ac6454SAndrew Thompson {
867ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
86802ac6454SAndrew Thompson 	int error;
86902ac6454SAndrew Thompson 
87002ac6454SAndrew Thompson 	/*
87102ac6454SAndrew Thompson 	 * Avoid requests that would damage the bus integrity:
87202ac6454SAndrew Thompson 	 */
87302ac6454SAndrew Thompson 	if (((req->bmRequestType == UT_WRITE_DEVICE) &&
87402ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_ADDRESS)) ||
87502ac6454SAndrew Thompson 	    ((req->bmRequestType == UT_WRITE_DEVICE) &&
87602ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_CONFIG)) ||
87702ac6454SAndrew Thompson 	    ((req->bmRequestType == UT_WRITE_INTERFACE) &&
87802ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_INTERFACE))) {
87902ac6454SAndrew Thompson 		/*
88002ac6454SAndrew Thompson 		 * These requests can be useful for testing USB drivers.
88102ac6454SAndrew Thompson 		 */
88202ac6454SAndrew Thompson 		error = priv_check(curthread, PRIV_DRIVER);
88302ac6454SAndrew Thompson 		if (error) {
88402ac6454SAndrew Thompson 			return (error);
88502ac6454SAndrew Thompson 		}
88602ac6454SAndrew Thompson 	}
88702ac6454SAndrew Thompson 	/*
88802ac6454SAndrew Thompson 	 * Special case - handle clearing of stall
88902ac6454SAndrew Thompson 	 */
89002ac6454SAndrew Thompson 	if (req->bmRequestType == UT_WRITE_ENDPOINT) {
89102ac6454SAndrew Thompson 
892a593f6b8SAndrew Thompson 		ep = usbd_get_ep_by_addr(udev, req->wIndex[0]);
893ae60fdfbSAndrew Thompson 		if (ep == NULL) {
89402ac6454SAndrew Thompson 			return (EINVAL);
89502ac6454SAndrew Thompson 		}
89602ac6454SAndrew Thompson 		if ((req->bRequest == UR_CLEAR_FEATURE) &&
89702ac6454SAndrew Thompson 		    (UGETW(req->wValue) == UF_ENDPOINT_HALT)) {
898a593f6b8SAndrew Thompson 			usbd_clear_data_toggle(udev, ep);
89902ac6454SAndrew Thompson 		}
90002ac6454SAndrew Thompson 	}
90102ac6454SAndrew Thompson 	/* TODO: add more checks to verify the interface index */
90202ac6454SAndrew Thompson 
90302ac6454SAndrew Thompson 	return (0);
90402ac6454SAndrew Thompson }
90502ac6454SAndrew Thompson 
90602ac6454SAndrew Thompson int
907760bc48eSAndrew Thompson ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur)
90802ac6454SAndrew Thompson {
90902ac6454SAndrew Thompson 	int error;
91002ac6454SAndrew Thompson 	uint16_t len;
91102ac6454SAndrew Thompson 	uint16_t actlen;
91202ac6454SAndrew Thompson 
91302ac6454SAndrew Thompson 	if (ugen_check_request(f->udev, &ur->ucr_request)) {
91402ac6454SAndrew Thompson 		return (EPERM);
91502ac6454SAndrew Thompson 	}
91602ac6454SAndrew Thompson 	len = UGETW(ur->ucr_request.wLength);
91702ac6454SAndrew Thompson 
91802ac6454SAndrew Thompson 	/* check if "ucr_data" is valid */
91902ac6454SAndrew Thompson 	if (len != 0) {
92002ac6454SAndrew Thompson 		if (ur->ucr_data == NULL) {
92102ac6454SAndrew Thompson 			return (EFAULT);
92202ac6454SAndrew Thompson 		}
92302ac6454SAndrew Thompson 	}
92402ac6454SAndrew Thompson 	/* do the USB request */
925a593f6b8SAndrew Thompson 	error = usbd_do_request_flags
92602ac6454SAndrew Thompson 	    (f->udev, NULL, &ur->ucr_request, ur->ucr_data,
92702ac6454SAndrew Thompson 	    (ur->ucr_flags & USB_SHORT_XFER_OK) |
92802ac6454SAndrew Thompson 	    USB_USER_DATA_PTR, &actlen,
92902ac6454SAndrew Thompson 	    USB_DEFAULT_TIMEOUT);
93002ac6454SAndrew Thompson 
93102ac6454SAndrew Thompson 	ur->ucr_actlen = actlen;
93202ac6454SAndrew Thompson 
93302ac6454SAndrew Thompson 	if (error) {
93402ac6454SAndrew Thompson 		error = EIO;
93502ac6454SAndrew Thompson 	}
93602ac6454SAndrew Thompson 	return (error);
93702ac6454SAndrew Thompson }
93802ac6454SAndrew Thompson 
93902ac6454SAndrew Thompson /*------------------------------------------------------------------------
94002ac6454SAndrew Thompson  *	ugen_re_enumerate
94102ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
94202ac6454SAndrew Thompson static int
943760bc48eSAndrew Thompson ugen_re_enumerate(struct usb_fifo *f)
94402ac6454SAndrew Thompson {
945760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
94602ac6454SAndrew Thompson 	int error;
94702ac6454SAndrew Thompson 
94802ac6454SAndrew Thompson 	/*
94902ac6454SAndrew Thompson 	 * This request can be useful for testing USB drivers:
95002ac6454SAndrew Thompson 	 */
95102ac6454SAndrew Thompson 	error = priv_check(curthread, PRIV_DRIVER);
95202ac6454SAndrew Thompson 	if (error) {
95302ac6454SAndrew Thompson 		return (error);
95402ac6454SAndrew Thompson 	}
9558f9750b7SHans Petter Selasky 	if (udev->flags.usb_mode != USB_MODE_HOST) {
9568f9750b7SHans Petter Selasky 		/* not possible in device side mode */
95756b57046SHans Petter Selasky 		DPRINTFN(6, "device mode\n");
9588f9750b7SHans Petter Selasky 		return (ENOTTY);
95902ac6454SAndrew Thompson 	}
96056b57046SHans Petter Selasky 	if (udev->parent_hub == NULL) {
96156b57046SHans Petter Selasky 		/* the root HUB cannot be re-enumerated */
96256b57046SHans Petter Selasky 		DPRINTFN(6, "cannot reset root HUB\n");
96356b57046SHans Petter Selasky 		return (EINVAL);
96456b57046SHans Petter Selasky 	}
9658f9750b7SHans Petter Selasky 	/* make sure all FIFO's are gone */
9668f9750b7SHans Petter Selasky 	/* else there can be a deadlock */
9678f9750b7SHans Petter Selasky 	if (ugen_fs_uninit(f)) {
9688f9750b7SHans Petter Selasky 		/* ignore any errors */
9698f9750b7SHans Petter Selasky 		DPRINTFN(6, "no FIFOs\n");
97002ac6454SAndrew Thompson 	}
9719eb0d702SHans Petter Selasky 	/* start re-enumeration of device */
9729eb0d702SHans Petter Selasky 	usbd_start_re_enumerate(udev);
97302ac6454SAndrew Thompson 	return (0);
97402ac6454SAndrew Thompson }
97502ac6454SAndrew Thompson 
97602ac6454SAndrew Thompson int
977760bc48eSAndrew Thompson ugen_fs_uninit(struct usb_fifo *f)
97802ac6454SAndrew Thompson {
97902ac6454SAndrew Thompson 	if (f->fs_xfer == NULL) {
98002ac6454SAndrew Thompson 		return (EINVAL);
98102ac6454SAndrew Thompson 	}
982a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(f->fs_xfer, f->fs_ep_max);
98302ac6454SAndrew Thompson 	free(f->fs_xfer, M_USB);
98402ac6454SAndrew Thompson 	f->fs_xfer = NULL;
98502ac6454SAndrew Thompson 	f->fs_ep_max = 0;
98602ac6454SAndrew Thompson 	f->fs_ep_ptr = NULL;
98702ac6454SAndrew Thompson 	f->flag_iscomplete = 0;
988a593f6b8SAndrew Thompson 	usb_fifo_free_buffer(f);
98902ac6454SAndrew Thompson 	return (0);
99002ac6454SAndrew Thompson }
99102ac6454SAndrew Thompson 
99202ac6454SAndrew Thompson static uint8_t
993760bc48eSAndrew Thompson ugen_fs_get_complete(struct usb_fifo *f, uint8_t *pindex)
99402ac6454SAndrew Thompson {
995760bc48eSAndrew Thompson 	struct usb_mbuf *m;
99602ac6454SAndrew Thompson 
99702ac6454SAndrew Thompson 	USB_IF_DEQUEUE(&f->used_q, m);
99802ac6454SAndrew Thompson 
99902ac6454SAndrew Thompson 	if (m) {
100002ac6454SAndrew Thompson 		*pindex = *((uint8_t *)(m->cur_data_ptr));
100102ac6454SAndrew Thompson 
100202ac6454SAndrew Thompson 		USB_IF_ENQUEUE(&f->free_q, m);
100302ac6454SAndrew Thompson 
100402ac6454SAndrew Thompson 		return (0);		/* success */
100502ac6454SAndrew Thompson 	} else {
100602ac6454SAndrew Thompson 
100702ac6454SAndrew Thompson 		*pindex = 0;		/* fix compiler warning */
100802ac6454SAndrew Thompson 
100902ac6454SAndrew Thompson 		f->flag_iscomplete = 0;
101002ac6454SAndrew Thompson 	}
101102ac6454SAndrew Thompson 	return (1);			/* failure */
101202ac6454SAndrew Thompson }
101302ac6454SAndrew Thompson 
101402ac6454SAndrew Thompson static void
1015760bc48eSAndrew Thompson ugen_fs_set_complete(struct usb_fifo *f, uint8_t index)
101602ac6454SAndrew Thompson {
1017760bc48eSAndrew Thompson 	struct usb_mbuf *m;
101802ac6454SAndrew Thompson 
101902ac6454SAndrew Thompson 	USB_IF_DEQUEUE(&f->free_q, m);
102002ac6454SAndrew Thompson 
102102ac6454SAndrew Thompson 	if (m == NULL) {
102202ac6454SAndrew Thompson 		/* can happen during close */
102302ac6454SAndrew Thompson 		DPRINTF("out of buffers\n");
102402ac6454SAndrew Thompson 		return;
102502ac6454SAndrew Thompson 	}
102602ac6454SAndrew Thompson 	USB_MBUF_RESET(m);
102702ac6454SAndrew Thompson 
102802ac6454SAndrew Thompson 	*((uint8_t *)(m->cur_data_ptr)) = index;
102902ac6454SAndrew Thompson 
103002ac6454SAndrew Thompson 	USB_IF_ENQUEUE(&f->used_q, m);
103102ac6454SAndrew Thompson 
103202ac6454SAndrew Thompson 	f->flag_iscomplete = 1;
103302ac6454SAndrew Thompson 
1034a593f6b8SAndrew Thompson 	usb_fifo_wakeup(f);
103502ac6454SAndrew Thompson }
103602ac6454SAndrew Thompson 
103702ac6454SAndrew Thompson static int
1038760bc48eSAndrew Thompson ugen_fs_copy_in(struct usb_fifo *f, uint8_t ep_index)
103902ac6454SAndrew Thompson {
1040760bc48eSAndrew Thompson 	struct usb_device_request *req;
1041760bc48eSAndrew Thompson 	struct usb_xfer *xfer;
1042760bc48eSAndrew Thompson 	struct usb_fs_endpoint fs_ep;
104302ac6454SAndrew Thompson 	void *uaddr;			/* userland pointer */
104402ac6454SAndrew Thompson 	void *kaddr;
1045e0a69b51SAndrew Thompson 	usb_frlength_t offset;
1046e0a69b51SAndrew Thompson 	usb_frlength_t rem;
1047e0a69b51SAndrew Thompson 	usb_frcount_t n;
104802ac6454SAndrew Thompson 	uint32_t length;
104902ac6454SAndrew Thompson 	int error;
105002ac6454SAndrew Thompson 	uint8_t isread;
105102ac6454SAndrew Thompson 
105202ac6454SAndrew Thompson 	if (ep_index >= f->fs_ep_max) {
105302ac6454SAndrew Thompson 		return (EINVAL);
105402ac6454SAndrew Thompson 	}
105502ac6454SAndrew Thompson 	xfer = f->fs_xfer[ep_index];
105602ac6454SAndrew Thompson 	if (xfer == NULL) {
105702ac6454SAndrew Thompson 		return (EINVAL);
105802ac6454SAndrew Thompson 	}
105902ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
1060a593f6b8SAndrew Thompson 	if (usbd_transfer_pending(xfer)) {
106102ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
106202ac6454SAndrew Thompson 		return (EBUSY);		/* should not happen */
106302ac6454SAndrew Thompson 	}
106402ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
106502ac6454SAndrew Thompson 
106602ac6454SAndrew Thompson 	error = copyin(f->fs_ep_ptr +
106702ac6454SAndrew Thompson 	    ep_index, &fs_ep, sizeof(fs_ep));
106802ac6454SAndrew Thompson 	if (error) {
106902ac6454SAndrew Thompson 		return (error);
107002ac6454SAndrew Thompson 	}
107102ac6454SAndrew Thompson 	/* security checks */
107202ac6454SAndrew Thompson 
107302ac6454SAndrew Thompson 	if (fs_ep.nFrames > xfer->max_frame_count) {
107402ac6454SAndrew Thompson 		xfer->error = USB_ERR_INVAL;
107502ac6454SAndrew Thompson 		goto complete;
107602ac6454SAndrew Thompson 	}
107702ac6454SAndrew Thompson 	if (fs_ep.nFrames == 0) {
107802ac6454SAndrew Thompson 		xfer->error = USB_ERR_INVAL;
107902ac6454SAndrew Thompson 		goto complete;
108002ac6454SAndrew Thompson 	}
108102ac6454SAndrew Thompson 	error = copyin(fs_ep.ppBuffer,
108202ac6454SAndrew Thompson 	    &uaddr, sizeof(uaddr));
108302ac6454SAndrew Thompson 	if (error) {
108402ac6454SAndrew Thompson 		return (error);
108502ac6454SAndrew Thompson 	}
108602ac6454SAndrew Thompson 	/* reset first frame */
1087ed6d949aSAndrew Thompson 	usbd_xfer_set_frame_offset(xfer, 0, 0);
108802ac6454SAndrew Thompson 
108902ac6454SAndrew Thompson 	if (xfer->flags_int.control_xfr) {
109002ac6454SAndrew Thompson 
109102ac6454SAndrew Thompson 		req = xfer->frbuffers[0].buffer;
109202ac6454SAndrew Thompson 
109302ac6454SAndrew Thompson 		error = copyin(fs_ep.pLength,
109402ac6454SAndrew Thompson 		    &length, sizeof(length));
109502ac6454SAndrew Thompson 		if (error) {
109602ac6454SAndrew Thompson 			return (error);
109702ac6454SAndrew Thompson 		}
1098599ce1c3SAndrew Thompson 		if (length != sizeof(*req)) {
109902ac6454SAndrew Thompson 			xfer->error = USB_ERR_INVAL;
110002ac6454SAndrew Thompson 			goto complete;
110102ac6454SAndrew Thompson 		}
110202ac6454SAndrew Thompson 		if (length != 0) {
110302ac6454SAndrew Thompson 			error = copyin(uaddr, req, length);
110402ac6454SAndrew Thompson 			if (error) {
110502ac6454SAndrew Thompson 				return (error);
110602ac6454SAndrew Thompson 			}
110702ac6454SAndrew Thompson 		}
110802ac6454SAndrew Thompson 		if (ugen_check_request(f->udev, req)) {
110902ac6454SAndrew Thompson 			xfer->error = USB_ERR_INVAL;
111002ac6454SAndrew Thompson 			goto complete;
111102ac6454SAndrew Thompson 		}
1112ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, 0, length);
111302ac6454SAndrew Thompson 
111402ac6454SAndrew Thompson 		/* Host mode only ! */
111502ac6454SAndrew Thompson 		if ((req->bmRequestType &
111602ac6454SAndrew Thompson 		    (UT_READ | UT_WRITE)) == UT_READ) {
111702ac6454SAndrew Thompson 			isread = 1;
111802ac6454SAndrew Thompson 		} else {
111902ac6454SAndrew Thompson 			isread = 0;
112002ac6454SAndrew Thompson 		}
112102ac6454SAndrew Thompson 		n = 1;
112202ac6454SAndrew Thompson 		offset = sizeof(*req);
112302ac6454SAndrew Thompson 
112402ac6454SAndrew Thompson 	} else {
112502ac6454SAndrew Thompson 		/* Device and Host mode */
112602ac6454SAndrew Thompson 		if (USB_GET_DATA_ISREAD(xfer)) {
112702ac6454SAndrew Thompson 			isread = 1;
112802ac6454SAndrew Thompson 		} else {
112902ac6454SAndrew Thompson 			isread = 0;
113002ac6454SAndrew Thompson 		}
113102ac6454SAndrew Thompson 		n = 0;
113202ac6454SAndrew Thompson 		offset = 0;
113302ac6454SAndrew Thompson 	}
113402ac6454SAndrew Thompson 
1135ed6d949aSAndrew Thompson 	rem = usbd_xfer_max_len(xfer);
113602ac6454SAndrew Thompson 	xfer->nframes = fs_ep.nFrames;
113702ac6454SAndrew Thompson 	xfer->timeout = fs_ep.timeout;
113802ac6454SAndrew Thompson 	if (xfer->timeout > 65535) {
113902ac6454SAndrew Thompson 		xfer->timeout = 65535;
114002ac6454SAndrew Thompson 	}
114102ac6454SAndrew Thompson 	if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK)
114202ac6454SAndrew Thompson 		xfer->flags.short_xfer_ok = 1;
114302ac6454SAndrew Thompson 	else
114402ac6454SAndrew Thompson 		xfer->flags.short_xfer_ok = 0;
114502ac6454SAndrew Thompson 
114602ac6454SAndrew Thompson 	if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK)
114702ac6454SAndrew Thompson 		xfer->flags.short_frames_ok = 1;
114802ac6454SAndrew Thompson 	else
114902ac6454SAndrew Thompson 		xfer->flags.short_frames_ok = 0;
115002ac6454SAndrew Thompson 
115102ac6454SAndrew Thompson 	if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT)
115202ac6454SAndrew Thompson 		xfer->flags.force_short_xfer = 1;
115302ac6454SAndrew Thompson 	else
115402ac6454SAndrew Thompson 		xfer->flags.force_short_xfer = 0;
115502ac6454SAndrew Thompson 
115602ac6454SAndrew Thompson 	if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL)
1157ed6d949aSAndrew Thompson 		usbd_xfer_set_stall(xfer);
115802ac6454SAndrew Thompson 	else
115902ac6454SAndrew Thompson 		xfer->flags.stall_pipe = 0;
116002ac6454SAndrew Thompson 
116102ac6454SAndrew Thompson 	for (; n != xfer->nframes; n++) {
116202ac6454SAndrew Thompson 
116302ac6454SAndrew Thompson 		error = copyin(fs_ep.pLength + n,
116402ac6454SAndrew Thompson 		    &length, sizeof(length));
116502ac6454SAndrew Thompson 		if (error) {
116602ac6454SAndrew Thompson 			break;
116702ac6454SAndrew Thompson 		}
1168ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, n, length);
116902ac6454SAndrew Thompson 
117002ac6454SAndrew Thompson 		if (length > rem) {
117102ac6454SAndrew Thompson 			xfer->error = USB_ERR_INVAL;
117202ac6454SAndrew Thompson 			goto complete;
117302ac6454SAndrew Thompson 		}
117402ac6454SAndrew Thompson 		rem -= length;
117502ac6454SAndrew Thompson 
117602ac6454SAndrew Thompson 		if (!isread) {
117702ac6454SAndrew Thompson 
117802ac6454SAndrew Thompson 			/* we need to know the source buffer */
117902ac6454SAndrew Thompson 			error = copyin(fs_ep.ppBuffer + n,
118002ac6454SAndrew Thompson 			    &uaddr, sizeof(uaddr));
118102ac6454SAndrew Thompson 			if (error) {
118202ac6454SAndrew Thompson 				break;
118302ac6454SAndrew Thompson 			}
118402ac6454SAndrew Thompson 			if (xfer->flags_int.isochronous_xfr) {
118502ac6454SAndrew Thompson 				/* get kernel buffer address */
118602ac6454SAndrew Thompson 				kaddr = xfer->frbuffers[0].buffer;
118702ac6454SAndrew Thompson 				kaddr = USB_ADD_BYTES(kaddr, offset);
118802ac6454SAndrew Thompson 			} else {
118902ac6454SAndrew Thompson 				/* set current frame offset */
1190ed6d949aSAndrew Thompson 				usbd_xfer_set_frame_offset(xfer, offset, n);
119102ac6454SAndrew Thompson 
119202ac6454SAndrew Thompson 				/* get kernel buffer address */
119302ac6454SAndrew Thompson 				kaddr = xfer->frbuffers[n].buffer;
119402ac6454SAndrew Thompson 			}
119502ac6454SAndrew Thompson 
119602ac6454SAndrew Thompson 			/* move data */
119702ac6454SAndrew Thompson 			error = copyin(uaddr, kaddr, length);
119802ac6454SAndrew Thompson 			if (error) {
119902ac6454SAndrew Thompson 				break;
120002ac6454SAndrew Thompson 			}
120102ac6454SAndrew Thompson 		}
120202ac6454SAndrew Thompson 		offset += length;
120302ac6454SAndrew Thompson 	}
120402ac6454SAndrew Thompson 	return (error);
120502ac6454SAndrew Thompson 
120602ac6454SAndrew Thompson complete:
120702ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
120802ac6454SAndrew Thompson 	ugen_fs_set_complete(f, ep_index);
120902ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
121002ac6454SAndrew Thompson 	return (0);
121102ac6454SAndrew Thompson }
121202ac6454SAndrew Thompson 
121302ac6454SAndrew Thompson static int
1214760bc48eSAndrew Thompson ugen_fs_copy_out(struct usb_fifo *f, uint8_t ep_index)
121502ac6454SAndrew Thompson {
1216760bc48eSAndrew Thompson 	struct usb_device_request *req;
1217760bc48eSAndrew Thompson 	struct usb_xfer *xfer;
1218760bc48eSAndrew Thompson 	struct usb_fs_endpoint fs_ep;
1219760bc48eSAndrew Thompson 	struct usb_fs_endpoint *fs_ep_uptr;	/* userland ptr */
122002ac6454SAndrew Thompson 	void *uaddr;			/* userland ptr */
122102ac6454SAndrew Thompson 	void *kaddr;
1222e0a69b51SAndrew Thompson 	usb_frlength_t offset;
1223e0a69b51SAndrew Thompson 	usb_frlength_t rem;
1224e0a69b51SAndrew Thompson 	usb_frcount_t n;
122502ac6454SAndrew Thompson 	uint32_t length;
122602ac6454SAndrew Thompson 	uint32_t temp;
122702ac6454SAndrew Thompson 	int error;
122802ac6454SAndrew Thompson 	uint8_t isread;
122902ac6454SAndrew Thompson 
1230578d0effSAndrew Thompson 	if (ep_index >= f->fs_ep_max)
123102ac6454SAndrew Thompson 		return (EINVAL);
1232578d0effSAndrew Thompson 
123302ac6454SAndrew Thompson 	xfer = f->fs_xfer[ep_index];
1234578d0effSAndrew Thompson 	if (xfer == NULL)
123502ac6454SAndrew Thompson 		return (EINVAL);
1236578d0effSAndrew Thompson 
123702ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
1238a593f6b8SAndrew Thompson 	if (usbd_transfer_pending(xfer)) {
123902ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
124002ac6454SAndrew Thompson 		return (EBUSY);		/* should not happen */
124102ac6454SAndrew Thompson 	}
124202ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
124302ac6454SAndrew Thompson 
124402ac6454SAndrew Thompson 	fs_ep_uptr = f->fs_ep_ptr + ep_index;
124502ac6454SAndrew Thompson 	error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep));
124602ac6454SAndrew Thompson 	if (error) {
124702ac6454SAndrew Thompson 		return (error);
124802ac6454SAndrew Thompson 	}
124902ac6454SAndrew Thompson 	fs_ep.status = xfer->error;
125002ac6454SAndrew Thompson 	fs_ep.aFrames = xfer->aframes;
125102ac6454SAndrew Thompson 	fs_ep.isoc_time_complete = xfer->isoc_time_complete;
125202ac6454SAndrew Thompson 	if (xfer->error) {
125302ac6454SAndrew Thompson 		goto complete;
125402ac6454SAndrew Thompson 	}
125502ac6454SAndrew Thompson 	if (xfer->flags_int.control_xfr) {
125602ac6454SAndrew Thompson 		req = xfer->frbuffers[0].buffer;
125702ac6454SAndrew Thompson 
125802ac6454SAndrew Thompson 		/* Host mode only ! */
125902ac6454SAndrew Thompson 		if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) {
126002ac6454SAndrew Thompson 			isread = 1;
126102ac6454SAndrew Thompson 		} else {
126202ac6454SAndrew Thompson 			isread = 0;
126302ac6454SAndrew Thompson 		}
126402ac6454SAndrew Thompson 		if (xfer->nframes == 0)
126502ac6454SAndrew Thompson 			n = 0;		/* should never happen */
126602ac6454SAndrew Thompson 		else
126702ac6454SAndrew Thompson 			n = 1;
126802ac6454SAndrew Thompson 	} else {
126902ac6454SAndrew Thompson 		/* Device and Host mode */
127002ac6454SAndrew Thompson 		if (USB_GET_DATA_ISREAD(xfer)) {
127102ac6454SAndrew Thompson 			isread = 1;
127202ac6454SAndrew Thompson 		} else {
127302ac6454SAndrew Thompson 			isread = 0;
127402ac6454SAndrew Thompson 		}
127502ac6454SAndrew Thompson 		n = 0;
127602ac6454SAndrew Thompson 	}
127702ac6454SAndrew Thompson 
127802ac6454SAndrew Thompson 	/* Update lengths and copy out data */
127902ac6454SAndrew Thompson 
1280ed6d949aSAndrew Thompson 	rem = usbd_xfer_max_len(xfer);
128102ac6454SAndrew Thompson 	offset = 0;
128202ac6454SAndrew Thompson 
128302ac6454SAndrew Thompson 	for (; n != xfer->nframes; n++) {
128402ac6454SAndrew Thompson 
128502ac6454SAndrew Thompson 		/* get initial length into "temp" */
128602ac6454SAndrew Thompson 		error = copyin(fs_ep.pLength + n,
128702ac6454SAndrew Thompson 		    &temp, sizeof(temp));
128802ac6454SAndrew Thompson 		if (error) {
128902ac6454SAndrew Thompson 			return (error);
129002ac6454SAndrew Thompson 		}
129102ac6454SAndrew Thompson 		if (temp > rem) {
129202ac6454SAndrew Thompson 			/* the userland length has been corrupted */
129302ac6454SAndrew Thompson 			DPRINTF("corrupt userland length "
129402ac6454SAndrew Thompson 			    "%u > %u\n", temp, rem);
129502ac6454SAndrew Thompson 			fs_ep.status = USB_ERR_INVAL;
129602ac6454SAndrew Thompson 			goto complete;
129702ac6454SAndrew Thompson 		}
129802ac6454SAndrew Thompson 		rem -= temp;
129902ac6454SAndrew Thompson 
130002ac6454SAndrew Thompson 		/* get actual transfer length */
130102ac6454SAndrew Thompson 		length = xfer->frlengths[n];
130202ac6454SAndrew Thompson 		if (length > temp) {
130302ac6454SAndrew Thompson 			/* data overflow */
130402ac6454SAndrew Thompson 			fs_ep.status = USB_ERR_INVAL;
130502ac6454SAndrew Thompson 			DPRINTF("data overflow %u > %u\n",
130602ac6454SAndrew Thompson 			    length, temp);
130702ac6454SAndrew Thompson 			goto complete;
130802ac6454SAndrew Thompson 		}
130902ac6454SAndrew Thompson 		if (isread) {
131002ac6454SAndrew Thompson 
131102ac6454SAndrew Thompson 			/* we need to know the destination buffer */
131202ac6454SAndrew Thompson 			error = copyin(fs_ep.ppBuffer + n,
131302ac6454SAndrew Thompson 			    &uaddr, sizeof(uaddr));
131402ac6454SAndrew Thompson 			if (error) {
131502ac6454SAndrew Thompson 				return (error);
131602ac6454SAndrew Thompson 			}
131702ac6454SAndrew Thompson 			if (xfer->flags_int.isochronous_xfr) {
131802ac6454SAndrew Thompson 				/* only one frame buffer */
131902ac6454SAndrew Thompson 				kaddr = USB_ADD_BYTES(
132002ac6454SAndrew Thompson 				    xfer->frbuffers[0].buffer, offset);
132102ac6454SAndrew Thompson 			} else {
132202ac6454SAndrew Thompson 				/* multiple frame buffers */
132302ac6454SAndrew Thompson 				kaddr = xfer->frbuffers[n].buffer;
132402ac6454SAndrew Thompson 			}
132502ac6454SAndrew Thompson 
132602ac6454SAndrew Thompson 			/* move data */
132702ac6454SAndrew Thompson 			error = copyout(kaddr, uaddr, length);
132802ac6454SAndrew Thompson 			if (error) {
132902ac6454SAndrew Thompson 				return (error);
133002ac6454SAndrew Thompson 			}
133102ac6454SAndrew Thompson 		}
133202ac6454SAndrew Thompson 		/*
133302ac6454SAndrew Thompson 		 * Update offset according to initial length, which is
133402ac6454SAndrew Thompson 		 * needed by isochronous transfers!
133502ac6454SAndrew Thompson 		 */
133602ac6454SAndrew Thompson 		offset += temp;
133702ac6454SAndrew Thompson 
133802ac6454SAndrew Thompson 		/* update length */
133902ac6454SAndrew Thompson 		error = copyout(&length,
134002ac6454SAndrew Thompson 		    fs_ep.pLength + n, sizeof(length));
134102ac6454SAndrew Thompson 		if (error) {
134202ac6454SAndrew Thompson 			return (error);
134302ac6454SAndrew Thompson 		}
134402ac6454SAndrew Thompson 	}
134502ac6454SAndrew Thompson 
134602ac6454SAndrew Thompson complete:
134702ac6454SAndrew Thompson 	/* update "aFrames" */
134802ac6454SAndrew Thompson 	error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames,
134902ac6454SAndrew Thompson 	    sizeof(fs_ep.aFrames));
135002ac6454SAndrew Thompson 	if (error)
135102ac6454SAndrew Thompson 		goto done;
135202ac6454SAndrew Thompson 
135302ac6454SAndrew Thompson 	/* update "isoc_time_complete" */
135402ac6454SAndrew Thompson 	error = copyout(&fs_ep.isoc_time_complete,
135502ac6454SAndrew Thompson 	    &fs_ep_uptr->isoc_time_complete,
135602ac6454SAndrew Thompson 	    sizeof(fs_ep.isoc_time_complete));
135702ac6454SAndrew Thompson 	if (error)
135802ac6454SAndrew Thompson 		goto done;
135902ac6454SAndrew Thompson 	/* update "status" */
136002ac6454SAndrew Thompson 	error = copyout(&fs_ep.status, &fs_ep_uptr->status,
136102ac6454SAndrew Thompson 	    sizeof(fs_ep.status));
136202ac6454SAndrew Thompson done:
136302ac6454SAndrew Thompson 	return (error);
136402ac6454SAndrew Thompson }
136502ac6454SAndrew Thompson 
136602ac6454SAndrew Thompson static uint8_t
1367760bc48eSAndrew Thompson ugen_fifo_in_use(struct usb_fifo *f, int fflags)
136802ac6454SAndrew Thompson {
1369760bc48eSAndrew Thompson 	struct usb_fifo *f_rx;
1370760bc48eSAndrew Thompson 	struct usb_fifo *f_tx;
137102ac6454SAndrew Thompson 
137202ac6454SAndrew Thompson 	f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX];
137302ac6454SAndrew Thompson 	f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX];
137402ac6454SAndrew Thompson 
137502ac6454SAndrew Thompson 	if ((fflags & FREAD) && f_rx &&
137602ac6454SAndrew Thompson 	    (f_rx->xfer[0] || f_rx->xfer[1])) {
137702ac6454SAndrew Thompson 		return (1);		/* RX FIFO in use */
137802ac6454SAndrew Thompson 	}
137902ac6454SAndrew Thompson 	if ((fflags & FWRITE) && f_tx &&
138002ac6454SAndrew Thompson 	    (f_tx->xfer[0] || f_tx->xfer[1])) {
138102ac6454SAndrew Thompson 		return (1);		/* TX FIFO in use */
138202ac6454SAndrew Thompson 	}
138302ac6454SAndrew Thompson 	return (0);			/* not in use */
138402ac6454SAndrew Thompson }
138502ac6454SAndrew Thompson 
138602ac6454SAndrew Thompson static int
1387760bc48eSAndrew Thompson ugen_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
138802ac6454SAndrew Thompson {
1389760bc48eSAndrew Thompson 	struct usb_config usb_config[1];
1390760bc48eSAndrew Thompson 	struct usb_device_request req;
139102ac6454SAndrew Thompson 	union {
1392760bc48eSAndrew Thompson 		struct usb_fs_complete *pcomp;
1393760bc48eSAndrew Thompson 		struct usb_fs_start *pstart;
1394760bc48eSAndrew Thompson 		struct usb_fs_stop *pstop;
1395760bc48eSAndrew Thompson 		struct usb_fs_open *popen;
1396e9cec016SHans Petter Selasky 		struct usb_fs_open_stream *popen_stream;
1397760bc48eSAndrew Thompson 		struct usb_fs_close *pclose;
1398760bc48eSAndrew Thompson 		struct usb_fs_clear_stall_sync *pstall;
139902ac6454SAndrew Thompson 		void   *addr;
140002ac6454SAndrew Thompson 	}     u;
1401ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
1402760bc48eSAndrew Thompson 	struct usb_endpoint_descriptor *ed;
14036c352e99SHans Petter Selasky 	struct usb_xfer *xfer;
140402ac6454SAndrew Thompson 	int error = 0;
140502ac6454SAndrew Thompson 	uint8_t iface_index;
140602ac6454SAndrew Thompson 	uint8_t isread;
140702ac6454SAndrew Thompson 	uint8_t ep_index;
14081c497368SHans Petter Selasky 	uint8_t pre_scale;
140902ac6454SAndrew Thompson 
141002ac6454SAndrew Thompson 	u.addr = addr;
141102ac6454SAndrew Thompson 
141202ac6454SAndrew Thompson 	DPRINTFN(6, "cmd=0x%08lx\n", cmd);
141302ac6454SAndrew Thompson 
141402ac6454SAndrew Thompson 	switch (cmd) {
141502ac6454SAndrew Thompson 	case USB_FS_COMPLETE:
141602ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
141702ac6454SAndrew Thompson 		error = ugen_fs_get_complete(f, &ep_index);
141802ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
141902ac6454SAndrew Thompson 
142002ac6454SAndrew Thompson 		if (error) {
142102ac6454SAndrew Thompson 			error = EBUSY;
142202ac6454SAndrew Thompson 			break;
142302ac6454SAndrew Thompson 		}
142402ac6454SAndrew Thompson 		u.pcomp->ep_index = ep_index;
142502ac6454SAndrew Thompson 		error = ugen_fs_copy_out(f, u.pcomp->ep_index);
142602ac6454SAndrew Thompson 		break;
142702ac6454SAndrew Thompson 
142802ac6454SAndrew Thompson 	case USB_FS_START:
142902ac6454SAndrew Thompson 		error = ugen_fs_copy_in(f, u.pstart->ep_index);
14306c352e99SHans Petter Selasky 		if (error)
143102ac6454SAndrew Thompson 			break;
143202ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
14336c352e99SHans Petter Selasky 		xfer = f->fs_xfer[u.pstart->ep_index];
14346c352e99SHans Petter Selasky 		usbd_transfer_start(xfer);
143502ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
143602ac6454SAndrew Thompson 		break;
143702ac6454SAndrew Thompson 
143802ac6454SAndrew Thompson 	case USB_FS_STOP:
143902ac6454SAndrew Thompson 		if (u.pstop->ep_index >= f->fs_ep_max) {
144002ac6454SAndrew Thompson 			error = EINVAL;
144102ac6454SAndrew Thompson 			break;
144202ac6454SAndrew Thompson 		}
144302ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
14446c352e99SHans Petter Selasky 		xfer = f->fs_xfer[u.pstart->ep_index];
14456c352e99SHans Petter Selasky 		if (usbd_transfer_pending(xfer)) {
14466c352e99SHans Petter Selasky 			usbd_transfer_stop(xfer);
14476c352e99SHans Petter Selasky 			/*
14486c352e99SHans Petter Selasky 			 * Check if the USB transfer was stopped
14496c352e99SHans Petter Selasky 			 * before it was even started. Else a cancel
14506c352e99SHans Petter Selasky 			 * callback will be pending.
14516c352e99SHans Petter Selasky 			 */
14526c352e99SHans Petter Selasky 			if (!xfer->flags_int.transferring) {
14536c352e99SHans Petter Selasky 				ugen_fs_set_complete(xfer->priv_sc,
14546c352e99SHans Petter Selasky 				    USB_P2U(xfer->priv_fifo));
14556c352e99SHans Petter Selasky 			}
14566c352e99SHans Petter Selasky 		}
145702ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
145802ac6454SAndrew Thompson 		break;
145902ac6454SAndrew Thompson 
146002ac6454SAndrew Thompson 	case USB_FS_OPEN:
1461e9cec016SHans Petter Selasky 	case USB_FS_OPEN_STREAM:
146202ac6454SAndrew Thompson 		if (u.popen->ep_index >= f->fs_ep_max) {
146302ac6454SAndrew Thompson 			error = EINVAL;
146402ac6454SAndrew Thompson 			break;
146502ac6454SAndrew Thompson 		}
146602ac6454SAndrew Thompson 		if (f->fs_xfer[u.popen->ep_index] != NULL) {
146702ac6454SAndrew Thompson 			error = EBUSY;
146802ac6454SAndrew Thompson 			break;
146902ac6454SAndrew Thompson 		}
147002ac6454SAndrew Thompson 		if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) {
147102ac6454SAndrew Thompson 			u.popen->max_bufsize = USB_FS_MAX_BUFSIZE;
147202ac6454SAndrew Thompson 		}
14731c497368SHans Petter Selasky 		if (u.popen->max_frames & USB_FS_MAX_FRAMES_PRE_SCALE) {
14741c497368SHans Petter Selasky 			pre_scale = 1;
14751c497368SHans Petter Selasky 			u.popen->max_frames &= ~USB_FS_MAX_FRAMES_PRE_SCALE;
14761c497368SHans Petter Selasky 		} else {
14771c497368SHans Petter Selasky 			pre_scale = 0;
14781c497368SHans Petter Selasky 		}
147902ac6454SAndrew Thompson 		if (u.popen->max_frames > USB_FS_MAX_FRAMES) {
148002ac6454SAndrew Thompson 			u.popen->max_frames = USB_FS_MAX_FRAMES;
148102ac6454SAndrew Thompson 			break;
148202ac6454SAndrew Thompson 		}
148302ac6454SAndrew Thompson 		if (u.popen->max_frames == 0) {
148402ac6454SAndrew Thompson 			error = EINVAL;
148502ac6454SAndrew Thompson 			break;
148602ac6454SAndrew Thompson 		}
1487a593f6b8SAndrew Thompson 		ep = usbd_get_ep_by_addr(f->udev, u.popen->ep_no);
1488ae60fdfbSAndrew Thompson 		if (ep == NULL) {
148902ac6454SAndrew Thompson 			error = EINVAL;
149002ac6454SAndrew Thompson 			break;
149102ac6454SAndrew Thompson 		}
1492ae60fdfbSAndrew Thompson 		ed = ep->edesc;
149302ac6454SAndrew Thompson 		if (ed == NULL) {
149402ac6454SAndrew Thompson 			error = ENXIO;
149502ac6454SAndrew Thompson 			break;
149602ac6454SAndrew Thompson 		}
1497ae60fdfbSAndrew Thompson 		iface_index = ep->iface_index;
149802ac6454SAndrew Thompson 
14991c497368SHans Petter Selasky 		memset(usb_config, 0, sizeof(usb_config));
150002ac6454SAndrew Thompson 
1501760bc48eSAndrew Thompson 		usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
1502760bc48eSAndrew Thompson 		usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
1503760bc48eSAndrew Thompson 		usb_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN);
1504760bc48eSAndrew Thompson 		usb_config[0].interval = USB_DEFAULT_INTERVAL;
1505760bc48eSAndrew Thompson 		usb_config[0].flags.proxy_buffer = 1;
15061c497368SHans Petter Selasky 		if (pre_scale != 0)
15071c497368SHans Petter Selasky 			usb_config[0].flags.pre_scale_frames = 1;
15085b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_fs_callback;
1509760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;	/* no timeout */
1510760bc48eSAndrew Thompson 		usb_config[0].frames = u.popen->max_frames;
1511760bc48eSAndrew Thompson 		usb_config[0].bufsize = u.popen->max_bufsize;
1512760bc48eSAndrew Thompson 		usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
1513e9cec016SHans Petter Selasky 		if (cmd == USB_FS_OPEN_STREAM)
1514e9cec016SHans Petter Selasky 			usb_config[0].stream_id = u.popen_stream->stream_id;
151502ac6454SAndrew Thompson 
1516760bc48eSAndrew Thompson 		if (usb_config[0].type == UE_CONTROL) {
1517f29a0724SAndrew Thompson 			if (f->udev->flags.usb_mode != USB_MODE_HOST) {
151802ac6454SAndrew Thompson 				error = EINVAL;
151902ac6454SAndrew Thompson 				break;
152002ac6454SAndrew Thompson 			}
152102ac6454SAndrew Thompson 		} else {
152202ac6454SAndrew Thompson 
1523760bc48eSAndrew Thompson 			isread = ((usb_config[0].endpoint &
152402ac6454SAndrew Thompson 			    (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN);
152502ac6454SAndrew Thompson 
1526f29a0724SAndrew Thompson 			if (f->udev->flags.usb_mode != USB_MODE_HOST) {
152702ac6454SAndrew Thompson 				isread = !isread;
152802ac6454SAndrew Thompson 			}
152902ac6454SAndrew Thompson 			/* check permissions */
153002ac6454SAndrew Thompson 			if (isread) {
153102ac6454SAndrew Thompson 				if (!(fflags & FREAD)) {
153202ac6454SAndrew Thompson 					error = EPERM;
153302ac6454SAndrew Thompson 					break;
153402ac6454SAndrew Thompson 				}
153502ac6454SAndrew Thompson 			} else {
153602ac6454SAndrew Thompson 				if (!(fflags & FWRITE)) {
153702ac6454SAndrew Thompson 					error = EPERM;
153802ac6454SAndrew Thompson 					break;
153902ac6454SAndrew Thompson 				}
154002ac6454SAndrew Thompson 			}
154102ac6454SAndrew Thompson 		}
1542a593f6b8SAndrew Thompson 		error = usbd_transfer_setup(f->udev, &iface_index,
1543760bc48eSAndrew Thompson 		    f->fs_xfer + u.popen->ep_index, usb_config, 1,
154402ac6454SAndrew Thompson 		    f, f->priv_mtx);
154502ac6454SAndrew Thompson 		if (error == 0) {
154602ac6454SAndrew Thompson 			/* update maximums */
154702ac6454SAndrew Thompson 			u.popen->max_packet_length =
154802ac6454SAndrew Thompson 			    f->fs_xfer[u.popen->ep_index]->max_frame_size;
154902ac6454SAndrew Thompson 			u.popen->max_bufsize =
155002ac6454SAndrew Thompson 			    f->fs_xfer[u.popen->ep_index]->max_data_length;
15511c497368SHans Petter Selasky 			/* update number of frames */
15521c497368SHans Petter Selasky 			u.popen->max_frames =
15531c497368SHans Petter Selasky 			    f->fs_xfer[u.popen->ep_index]->nframes;
15541c497368SHans Petter Selasky 			/* store index of endpoint */
155502ac6454SAndrew Thompson 			f->fs_xfer[u.popen->ep_index]->priv_fifo =
155602ac6454SAndrew Thompson 			    ((uint8_t *)0) + u.popen->ep_index;
155702ac6454SAndrew Thompson 		} else {
155802ac6454SAndrew Thompson 			error = ENOMEM;
155902ac6454SAndrew Thompson 		}
156002ac6454SAndrew Thompson 		break;
156102ac6454SAndrew Thompson 
156202ac6454SAndrew Thompson 	case USB_FS_CLOSE:
156302ac6454SAndrew Thompson 		if (u.pclose->ep_index >= f->fs_ep_max) {
156402ac6454SAndrew Thompson 			error = EINVAL;
156502ac6454SAndrew Thompson 			break;
156602ac6454SAndrew Thompson 		}
156702ac6454SAndrew Thompson 		if (f->fs_xfer[u.pclose->ep_index] == NULL) {
156802ac6454SAndrew Thompson 			error = EINVAL;
156902ac6454SAndrew Thompson 			break;
157002ac6454SAndrew Thompson 		}
1571a593f6b8SAndrew Thompson 		usbd_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1);
157202ac6454SAndrew Thompson 		break;
157302ac6454SAndrew Thompson 
157402ac6454SAndrew Thompson 	case USB_FS_CLEAR_STALL_SYNC:
157502ac6454SAndrew Thompson 		if (u.pstall->ep_index >= f->fs_ep_max) {
157602ac6454SAndrew Thompson 			error = EINVAL;
157702ac6454SAndrew Thompson 			break;
157802ac6454SAndrew Thompson 		}
157902ac6454SAndrew Thompson 		if (f->fs_xfer[u.pstall->ep_index] == NULL) {
158002ac6454SAndrew Thompson 			error = EINVAL;
158102ac6454SAndrew Thompson 			break;
158202ac6454SAndrew Thompson 		}
1583f29a0724SAndrew Thompson 		if (f->udev->flags.usb_mode != USB_MODE_HOST) {
158402ac6454SAndrew Thompson 			error = EINVAL;
158502ac6454SAndrew Thompson 			break;
158602ac6454SAndrew Thompson 		}
158702ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
1588a593f6b8SAndrew Thompson 		error = usbd_transfer_pending(f->fs_xfer[u.pstall->ep_index]);
158902ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
159002ac6454SAndrew Thompson 
159102ac6454SAndrew Thompson 		if (error) {
159202ac6454SAndrew Thompson 			return (EBUSY);
159302ac6454SAndrew Thompson 		}
1594ae60fdfbSAndrew Thompson 		ep = f->fs_xfer[u.pstall->ep_index]->endpoint;
159502ac6454SAndrew Thompson 
159602ac6454SAndrew Thompson 		/* setup a clear-stall packet */
159702ac6454SAndrew Thompson 		req.bmRequestType = UT_WRITE_ENDPOINT;
159802ac6454SAndrew Thompson 		req.bRequest = UR_CLEAR_FEATURE;
159902ac6454SAndrew Thompson 		USETW(req.wValue, UF_ENDPOINT_HALT);
1600ae60fdfbSAndrew Thompson 		req.wIndex[0] = ep->edesc->bEndpointAddress;
160102ac6454SAndrew Thompson 		req.wIndex[1] = 0;
160202ac6454SAndrew Thompson 		USETW(req.wLength, 0);
160302ac6454SAndrew Thompson 
1604a593f6b8SAndrew Thompson 		error = usbd_do_request(f->udev, NULL, &req, NULL);
160502ac6454SAndrew Thompson 		if (error == 0) {
1606a593f6b8SAndrew Thompson 			usbd_clear_data_toggle(f->udev, ep);
160702ac6454SAndrew Thompson 		} else {
160802ac6454SAndrew Thompson 			error = ENXIO;
160902ac6454SAndrew Thompson 		}
161002ac6454SAndrew Thompson 		break;
161102ac6454SAndrew Thompson 
161202ac6454SAndrew Thompson 	default:
161302ac6454SAndrew Thompson 		error = ENOIOCTL;
161402ac6454SAndrew Thompson 		break;
161502ac6454SAndrew Thompson 	}
161602ac6454SAndrew Thompson 
161702ac6454SAndrew Thompson 	DPRINTFN(6, "error=%d\n", error);
161802ac6454SAndrew Thompson 
161902ac6454SAndrew Thompson 	return (error);
162002ac6454SAndrew Thompson }
162102ac6454SAndrew Thompson 
162202ac6454SAndrew Thompson static int
1623760bc48eSAndrew Thompson ugen_set_short_xfer(struct usb_fifo *f, void *addr)
162402ac6454SAndrew Thompson {
162502ac6454SAndrew Thompson 	uint8_t t;
162602ac6454SAndrew Thompson 
162702ac6454SAndrew Thompson 	if (*(int *)addr)
162802ac6454SAndrew Thompson 		t = 1;
162902ac6454SAndrew Thompson 	else
163002ac6454SAndrew Thompson 		t = 0;
163102ac6454SAndrew Thompson 
163202ac6454SAndrew Thompson 	if (f->flag_short == t) {
163302ac6454SAndrew Thompson 		/* same value like before - accept */
163402ac6454SAndrew Thompson 		return (0);
163502ac6454SAndrew Thompson 	}
163602ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
163702ac6454SAndrew Thompson 		/* cannot change this during transfer */
163802ac6454SAndrew Thompson 		return (EBUSY);
163902ac6454SAndrew Thompson 	}
164002ac6454SAndrew Thompson 	f->flag_short = t;
164102ac6454SAndrew Thompson 	return (0);
164202ac6454SAndrew Thompson }
164302ac6454SAndrew Thompson 
164402ac6454SAndrew Thompson static int
1645760bc48eSAndrew Thompson ugen_set_timeout(struct usb_fifo *f, void *addr)
164602ac6454SAndrew Thompson {
164702ac6454SAndrew Thompson 	f->timeout = *(int *)addr;
164802ac6454SAndrew Thompson 	if (f->timeout > 65535) {
164902ac6454SAndrew Thompson 		/* limit user input */
165002ac6454SAndrew Thompson 		f->timeout = 65535;
165102ac6454SAndrew Thompson 	}
165202ac6454SAndrew Thompson 	return (0);
165302ac6454SAndrew Thompson }
165402ac6454SAndrew Thompson 
165502ac6454SAndrew Thompson static int
1656760bc48eSAndrew Thompson ugen_get_frame_size(struct usb_fifo *f, void *addr)
165702ac6454SAndrew Thompson {
165802ac6454SAndrew Thompson 	if (f->xfer[0]) {
165902ac6454SAndrew Thompson 		*(int *)addr = f->xfer[0]->max_frame_size;
166002ac6454SAndrew Thompson 	} else {
166102ac6454SAndrew Thompson 		return (EINVAL);
166202ac6454SAndrew Thompson 	}
166302ac6454SAndrew Thompson 	return (0);
166402ac6454SAndrew Thompson }
166502ac6454SAndrew Thompson 
166602ac6454SAndrew Thompson static int
1667760bc48eSAndrew Thompson ugen_set_buffer_size(struct usb_fifo *f, void *addr)
166802ac6454SAndrew Thompson {
1669e0a69b51SAndrew Thompson 	usb_frlength_t t;
167002ac6454SAndrew Thompson 
167101cf7831SAndrew Thompson 	if (*(int *)addr < 0)
167201cf7831SAndrew Thompson 		t = 0;		/* use "wMaxPacketSize" */
167302ac6454SAndrew Thompson 	else if (*(int *)addr < (256 * 1024))
167402ac6454SAndrew Thompson 		t = *(int *)addr;
167502ac6454SAndrew Thompson 	else
167602ac6454SAndrew Thompson 		t = 256 * 1024;
167702ac6454SAndrew Thompson 
167802ac6454SAndrew Thompson 	if (f->bufsize == t) {
167902ac6454SAndrew Thompson 		/* same value like before - accept */
168002ac6454SAndrew Thompson 		return (0);
168102ac6454SAndrew Thompson 	}
168202ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
168302ac6454SAndrew Thompson 		/* cannot change this during transfer */
168402ac6454SAndrew Thompson 		return (EBUSY);
168502ac6454SAndrew Thompson 	}
168602ac6454SAndrew Thompson 	f->bufsize = t;
168702ac6454SAndrew Thompson 	return (0);
168802ac6454SAndrew Thompson }
168902ac6454SAndrew Thompson 
169002ac6454SAndrew Thompson static int
1691760bc48eSAndrew Thompson ugen_get_buffer_size(struct usb_fifo *f, void *addr)
169202ac6454SAndrew Thompson {
169302ac6454SAndrew Thompson 	*(int *)addr = f->bufsize;
169402ac6454SAndrew Thompson 	return (0);
169502ac6454SAndrew Thompson }
169602ac6454SAndrew Thompson 
169702ac6454SAndrew Thompson static int
1698760bc48eSAndrew Thompson ugen_get_iface_desc(struct usb_fifo *f,
1699760bc48eSAndrew Thompson     struct usb_interface_descriptor *idesc)
170002ac6454SAndrew Thompson {
1701760bc48eSAndrew Thompson 	struct usb_interface *iface;
170202ac6454SAndrew Thompson 
1703a593f6b8SAndrew Thompson 	iface = usbd_get_iface(f->udev, f->iface_index);
170402ac6454SAndrew Thompson 	if (iface && iface->idesc) {
170502ac6454SAndrew Thompson 		*idesc = *(iface->idesc);
170602ac6454SAndrew Thompson 	} else {
170702ac6454SAndrew Thompson 		return (EIO);
170802ac6454SAndrew Thompson 	}
170902ac6454SAndrew Thompson 	return (0);
171002ac6454SAndrew Thompson }
171102ac6454SAndrew Thompson 
171202ac6454SAndrew Thompson static int
1713760bc48eSAndrew Thompson ugen_get_endpoint_desc(struct usb_fifo *f,
1714760bc48eSAndrew Thompson     struct usb_endpoint_descriptor *ed)
171502ac6454SAndrew Thompson {
1716ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
171702ac6454SAndrew Thompson 
1718ed6d949aSAndrew Thompson 	ep = usb_fifo_softc(f);
171902ac6454SAndrew Thompson 
1720ae60fdfbSAndrew Thompson 	if (ep && ep->edesc) {
1721ae60fdfbSAndrew Thompson 		*ed = *ep->edesc;
172202ac6454SAndrew Thompson 	} else {
172302ac6454SAndrew Thompson 		return (EINVAL);
172402ac6454SAndrew Thompson 	}
172502ac6454SAndrew Thompson 	return (0);
172602ac6454SAndrew Thompson }
172702ac6454SAndrew Thompson 
172802ac6454SAndrew Thompson static int
1729760bc48eSAndrew Thompson ugen_set_power_mode(struct usb_fifo *f, int mode)
173002ac6454SAndrew Thompson {
1731760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
173202ac6454SAndrew Thompson 	int err;
173302ac6454SAndrew Thompson 	uint8_t old_mode;
173402ac6454SAndrew Thompson 
173502ac6454SAndrew Thompson 	if ((udev == NULL) ||
173602ac6454SAndrew Thompson 	    (udev->parent_hub == NULL)) {
173702ac6454SAndrew Thompson 		return (EINVAL);
173802ac6454SAndrew Thompson 	}
173950230f98SAndrew Thompson 	err = priv_check(curthread, PRIV_DRIVER);
174002ac6454SAndrew Thompson 	if (err)
174102ac6454SAndrew Thompson 		return (err);
174202ac6454SAndrew Thompson 
174302ac6454SAndrew Thompson 	/* get old power mode */
174402ac6454SAndrew Thompson 	old_mode = udev->power_mode;
174502ac6454SAndrew Thompson 
174602ac6454SAndrew Thompson 	/* if no change, then just return */
174702ac6454SAndrew Thompson 	if (old_mode == mode)
174802ac6454SAndrew Thompson 		return (0);
174902ac6454SAndrew Thompson 
175002ac6454SAndrew Thompson 	switch (mode) {
175102ac6454SAndrew Thompson 	case USB_POWER_MODE_OFF:
175202ac6454SAndrew Thompson 		/* get the device unconfigured */
175302ac6454SAndrew Thompson 		err = ugen_set_config(f, USB_UNCONFIG_INDEX);
175402ac6454SAndrew Thompson 		if (err) {
175502ac6454SAndrew Thompson 			DPRINTFN(0, "Could not unconfigure "
175602ac6454SAndrew Thompson 			    "device (ignored)\n");
175702ac6454SAndrew Thompson 		}
175802ac6454SAndrew Thompson 
175902ac6454SAndrew Thompson 		/* clear port enable */
1760a593f6b8SAndrew Thompson 		err = usbd_req_clear_port_feature(udev->parent_hub,
176102ac6454SAndrew Thompson 		    NULL, udev->port_no, UHF_PORT_ENABLE);
176202ac6454SAndrew Thompson 		break;
176302ac6454SAndrew Thompson 
176402ac6454SAndrew Thompson 	case USB_POWER_MODE_ON:
176502ac6454SAndrew Thompson 	case USB_POWER_MODE_SAVE:
176602ac6454SAndrew Thompson 		break;
176702ac6454SAndrew Thompson 
176802ac6454SAndrew Thompson 	case USB_POWER_MODE_RESUME:
17692df1e9a6SAndrew Thompson #if USB_HAVE_POWERD
17702df1e9a6SAndrew Thompson 		/* let USB-powerd handle resume */
17712df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17722df1e9a6SAndrew Thompson 		udev->pwr_save.write_refs++;
17732df1e9a6SAndrew Thompson 		udev->pwr_save.last_xfer_time = ticks;
17742df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17752df1e9a6SAndrew Thompson 
17762df1e9a6SAndrew Thompson 		/* set new power mode */
17772df1e9a6SAndrew Thompson 		usbd_set_power_mode(udev, USB_POWER_MODE_SAVE);
17782df1e9a6SAndrew Thompson 
17792df1e9a6SAndrew Thompson 		/* wait for resume to complete */
17802df1e9a6SAndrew Thompson 		usb_pause_mtx(NULL, hz / 4);
17812df1e9a6SAndrew Thompson 
17822df1e9a6SAndrew Thompson 		/* clear write reference */
17832df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17842df1e9a6SAndrew Thompson 		udev->pwr_save.write_refs--;
17852df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17862df1e9a6SAndrew Thompson #endif
178702ac6454SAndrew Thompson 		mode = USB_POWER_MODE_SAVE;
178802ac6454SAndrew Thompson 		break;
178902ac6454SAndrew Thompson 
179002ac6454SAndrew Thompson 	case USB_POWER_MODE_SUSPEND:
17912df1e9a6SAndrew Thompson #if USB_HAVE_POWERD
17922df1e9a6SAndrew Thompson 		/* let USB-powerd handle suspend */
17932df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17942df1e9a6SAndrew Thompson 		udev->pwr_save.last_xfer_time = ticks - (256 * hz);
17952df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17962df1e9a6SAndrew Thompson #endif
179702ac6454SAndrew Thompson 		mode = USB_POWER_MODE_SAVE;
179802ac6454SAndrew Thompson 		break;
179902ac6454SAndrew Thompson 
180002ac6454SAndrew Thompson 	default:
180102ac6454SAndrew Thompson 		return (EINVAL);
180202ac6454SAndrew Thompson 	}
180302ac6454SAndrew Thompson 
180402ac6454SAndrew Thompson 	if (err)
180502ac6454SAndrew Thompson 		return (ENXIO);		/* I/O failure */
180602ac6454SAndrew Thompson 
180702ac6454SAndrew Thompson 	/* if we are powered off we need to re-enumerate first */
180802ac6454SAndrew Thompson 	if (old_mode == USB_POWER_MODE_OFF) {
18098f9750b7SHans Petter Selasky 		if (udev->flags.usb_mode == USB_MODE_HOST) {
18108f9750b7SHans Petter Selasky 			if (udev->re_enumerate_wait == 0)
18118f9750b7SHans Petter Selasky 				udev->re_enumerate_wait = 1;
18128f9750b7SHans Petter Selasky 		}
18138f9750b7SHans Petter Selasky 		/* set power mode will wake up the explore thread */
181402ac6454SAndrew Thompson 	}
181502ac6454SAndrew Thompson 
181602ac6454SAndrew Thompson 	/* set new power mode */
1817a593f6b8SAndrew Thompson 	usbd_set_power_mode(udev, mode);
181802ac6454SAndrew Thompson 
181902ac6454SAndrew Thompson 	return (0);			/* success */
182002ac6454SAndrew Thompson }
182102ac6454SAndrew Thompson 
182202ac6454SAndrew Thompson static int
1823760bc48eSAndrew Thompson ugen_get_power_mode(struct usb_fifo *f)
182402ac6454SAndrew Thompson {
1825760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
182602ac6454SAndrew Thompson 
1827d2d71ce7SAndrew Thompson 	if (udev == NULL)
182802ac6454SAndrew Thompson 		return (USB_POWER_MODE_ON);
1829d2d71ce7SAndrew Thompson 
183002ac6454SAndrew Thompson 	return (udev->power_mode);
183102ac6454SAndrew Thompson }
183202ac6454SAndrew Thompson 
183302ac6454SAndrew Thompson static int
1834760bc48eSAndrew Thompson ugen_do_port_feature(struct usb_fifo *f, uint8_t port_no,
183502ac6454SAndrew Thompson     uint8_t set, uint16_t feature)
183602ac6454SAndrew Thompson {
1837760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
1838760bc48eSAndrew Thompson 	struct usb_hub *hub;
183902ac6454SAndrew Thompson 	int err;
184002ac6454SAndrew Thompson 
184150230f98SAndrew Thompson 	err = priv_check(curthread, PRIV_DRIVER);
184202ac6454SAndrew Thompson 	if (err) {
184302ac6454SAndrew Thompson 		return (err);
184402ac6454SAndrew Thompson 	}
184502ac6454SAndrew Thompson 	if (port_no == 0) {
184602ac6454SAndrew Thompson 		return (EINVAL);
184702ac6454SAndrew Thompson 	}
184802ac6454SAndrew Thompson 	if ((udev == NULL) ||
184902ac6454SAndrew Thompson 	    (udev->hub == NULL)) {
185002ac6454SAndrew Thompson 		return (EINVAL);
185102ac6454SAndrew Thompson 	}
185202ac6454SAndrew Thompson 	hub = udev->hub;
185302ac6454SAndrew Thompson 
185402ac6454SAndrew Thompson 	if (port_no > hub->nports) {
185502ac6454SAndrew Thompson 		return (EINVAL);
185602ac6454SAndrew Thompson 	}
185702ac6454SAndrew Thompson 	if (set)
1858a593f6b8SAndrew Thompson 		err = usbd_req_set_port_feature(udev,
185902ac6454SAndrew Thompson 		    NULL, port_no, feature);
186002ac6454SAndrew Thompson 	else
1861a593f6b8SAndrew Thompson 		err = usbd_req_clear_port_feature(udev,
186202ac6454SAndrew Thompson 		    NULL, port_no, feature);
186302ac6454SAndrew Thompson 
186402ac6454SAndrew Thompson 	if (err)
186502ac6454SAndrew Thompson 		return (ENXIO);		/* failure */
186602ac6454SAndrew Thompson 
186702ac6454SAndrew Thompson 	return (0);			/* success */
186802ac6454SAndrew Thompson }
186902ac6454SAndrew Thompson 
187002ac6454SAndrew Thompson static int
1871760bc48eSAndrew Thompson ugen_iface_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
187202ac6454SAndrew Thompson {
1873760bc48eSAndrew Thompson 	struct usb_fifo *f_rx;
1874760bc48eSAndrew Thompson 	struct usb_fifo *f_tx;
187502ac6454SAndrew Thompson 	int error = 0;
187602ac6454SAndrew Thompson 
187702ac6454SAndrew Thompson 	f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX];
187802ac6454SAndrew Thompson 	f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX];
187902ac6454SAndrew Thompson 
188002ac6454SAndrew Thompson 	switch (cmd) {
188102ac6454SAndrew Thompson 	case USB_SET_RX_SHORT_XFER:
188202ac6454SAndrew Thompson 		if (fflags & FREAD) {
188302ac6454SAndrew Thompson 			error = ugen_set_short_xfer(f_rx, addr);
188402ac6454SAndrew Thompson 		} else {
188502ac6454SAndrew Thompson 			error = EINVAL;
188602ac6454SAndrew Thompson 		}
188702ac6454SAndrew Thompson 		break;
188802ac6454SAndrew Thompson 
188902ac6454SAndrew Thompson 	case USB_SET_TX_FORCE_SHORT:
189002ac6454SAndrew Thompson 		if (fflags & FWRITE) {
189102ac6454SAndrew Thompson 			error = ugen_set_short_xfer(f_tx, addr);
189202ac6454SAndrew Thompson 		} else {
189302ac6454SAndrew Thompson 			error = EINVAL;
189402ac6454SAndrew Thompson 		}
189502ac6454SAndrew Thompson 		break;
189602ac6454SAndrew Thompson 
189702ac6454SAndrew Thompson 	case USB_SET_RX_TIMEOUT:
189802ac6454SAndrew Thompson 		if (fflags & FREAD) {
189902ac6454SAndrew Thompson 			error = ugen_set_timeout(f_rx, addr);
190002ac6454SAndrew Thompson 		} else {
190102ac6454SAndrew Thompson 			error = EINVAL;
190202ac6454SAndrew Thompson 		}
190302ac6454SAndrew Thompson 		break;
190402ac6454SAndrew Thompson 
190502ac6454SAndrew Thompson 	case USB_SET_TX_TIMEOUT:
190602ac6454SAndrew Thompson 		if (fflags & FWRITE) {
190702ac6454SAndrew Thompson 			error = ugen_set_timeout(f_tx, addr);
190802ac6454SAndrew Thompson 		} else {
190902ac6454SAndrew Thompson 			error = EINVAL;
191002ac6454SAndrew Thompson 		}
191102ac6454SAndrew Thompson 		break;
191202ac6454SAndrew Thompson 
191302ac6454SAndrew Thompson 	case USB_GET_RX_FRAME_SIZE:
191402ac6454SAndrew Thompson 		if (fflags & FREAD) {
191502ac6454SAndrew Thompson 			error = ugen_get_frame_size(f_rx, addr);
191602ac6454SAndrew Thompson 		} else {
191702ac6454SAndrew Thompson 			error = EINVAL;
191802ac6454SAndrew Thompson 		}
191902ac6454SAndrew Thompson 		break;
192002ac6454SAndrew Thompson 
192102ac6454SAndrew Thompson 	case USB_GET_TX_FRAME_SIZE:
192202ac6454SAndrew Thompson 		if (fflags & FWRITE) {
192302ac6454SAndrew Thompson 			error = ugen_get_frame_size(f_tx, addr);
192402ac6454SAndrew Thompson 		} else {
192502ac6454SAndrew Thompson 			error = EINVAL;
192602ac6454SAndrew Thompson 		}
192702ac6454SAndrew Thompson 		break;
192802ac6454SAndrew Thompson 
192902ac6454SAndrew Thompson 	case USB_SET_RX_BUFFER_SIZE:
193002ac6454SAndrew Thompson 		if (fflags & FREAD) {
193102ac6454SAndrew Thompson 			error = ugen_set_buffer_size(f_rx, addr);
193202ac6454SAndrew Thompson 		} else {
193302ac6454SAndrew Thompson 			error = EINVAL;
193402ac6454SAndrew Thompson 		}
193502ac6454SAndrew Thompson 		break;
193602ac6454SAndrew Thompson 
193702ac6454SAndrew Thompson 	case USB_SET_TX_BUFFER_SIZE:
193802ac6454SAndrew Thompson 		if (fflags & FWRITE) {
193902ac6454SAndrew Thompson 			error = ugen_set_buffer_size(f_tx, addr);
194002ac6454SAndrew Thompson 		} else {
194102ac6454SAndrew Thompson 			error = EINVAL;
194202ac6454SAndrew Thompson 		}
194302ac6454SAndrew Thompson 		break;
194402ac6454SAndrew Thompson 
194502ac6454SAndrew Thompson 	case USB_GET_RX_BUFFER_SIZE:
194602ac6454SAndrew Thompson 		if (fflags & FREAD) {
194702ac6454SAndrew Thompson 			error = ugen_get_buffer_size(f_rx, addr);
194802ac6454SAndrew Thompson 		} else {
194902ac6454SAndrew Thompson 			error = EINVAL;
195002ac6454SAndrew Thompson 		}
195102ac6454SAndrew Thompson 		break;
195202ac6454SAndrew Thompson 
195302ac6454SAndrew Thompson 	case USB_GET_TX_BUFFER_SIZE:
195402ac6454SAndrew Thompson 		if (fflags & FWRITE) {
195502ac6454SAndrew Thompson 			error = ugen_get_buffer_size(f_tx, addr);
195602ac6454SAndrew Thompson 		} else {
195702ac6454SAndrew Thompson 			error = EINVAL;
195802ac6454SAndrew Thompson 		}
195902ac6454SAndrew Thompson 		break;
196002ac6454SAndrew Thompson 
196102ac6454SAndrew Thompson 	case USB_GET_RX_INTERFACE_DESC:
196202ac6454SAndrew Thompson 		if (fflags & FREAD) {
196302ac6454SAndrew Thompson 			error = ugen_get_iface_desc(f_rx, addr);
196402ac6454SAndrew Thompson 		} else {
196502ac6454SAndrew Thompson 			error = EINVAL;
196602ac6454SAndrew Thompson 		}
196702ac6454SAndrew Thompson 		break;
196802ac6454SAndrew Thompson 
196902ac6454SAndrew Thompson 	case USB_GET_TX_INTERFACE_DESC:
197002ac6454SAndrew Thompson 		if (fflags & FWRITE) {
197102ac6454SAndrew Thompson 			error = ugen_get_iface_desc(f_tx, addr);
197202ac6454SAndrew Thompson 		} else {
197302ac6454SAndrew Thompson 			error = EINVAL;
197402ac6454SAndrew Thompson 		}
197502ac6454SAndrew Thompson 		break;
197602ac6454SAndrew Thompson 
197702ac6454SAndrew Thompson 	case USB_GET_RX_ENDPOINT_DESC:
197802ac6454SAndrew Thompson 		if (fflags & FREAD) {
197902ac6454SAndrew Thompson 			error = ugen_get_endpoint_desc(f_rx, addr);
198002ac6454SAndrew Thompson 		} else {
198102ac6454SAndrew Thompson 			error = EINVAL;
198202ac6454SAndrew Thompson 		}
198302ac6454SAndrew Thompson 		break;
198402ac6454SAndrew Thompson 
198502ac6454SAndrew Thompson 	case USB_GET_TX_ENDPOINT_DESC:
198602ac6454SAndrew Thompson 		if (fflags & FWRITE) {
198702ac6454SAndrew Thompson 			error = ugen_get_endpoint_desc(f_tx, addr);
198802ac6454SAndrew Thompson 		} else {
198902ac6454SAndrew Thompson 			error = EINVAL;
199002ac6454SAndrew Thompson 		}
199102ac6454SAndrew Thompson 		break;
199202ac6454SAndrew Thompson 
199302ac6454SAndrew Thompson 	case USB_SET_RX_STALL_FLAG:
199402ac6454SAndrew Thompson 		if ((fflags & FREAD) && (*(int *)addr)) {
199502ac6454SAndrew Thompson 			f_rx->flag_stall = 1;
199602ac6454SAndrew Thompson 		}
199702ac6454SAndrew Thompson 		break;
199802ac6454SAndrew Thompson 
199902ac6454SAndrew Thompson 	case USB_SET_TX_STALL_FLAG:
200002ac6454SAndrew Thompson 		if ((fflags & FWRITE) && (*(int *)addr)) {
200102ac6454SAndrew Thompson 			f_tx->flag_stall = 1;
200202ac6454SAndrew Thompson 		}
200302ac6454SAndrew Thompson 		break;
200402ac6454SAndrew Thompson 
200502ac6454SAndrew Thompson 	default:
200602ac6454SAndrew Thompson 		error = ENOIOCTL;
200702ac6454SAndrew Thompson 		break;
200802ac6454SAndrew Thompson 	}
200902ac6454SAndrew Thompson 	return (error);
201002ac6454SAndrew Thompson }
201102ac6454SAndrew Thompson 
201202ac6454SAndrew Thompson static int
2013760bc48eSAndrew Thompson ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
201402ac6454SAndrew Thompson {
201502ac6454SAndrew Thompson 	union {
2016760bc48eSAndrew Thompson 		struct usb_interface_descriptor *idesc;
2017760bc48eSAndrew Thompson 		struct usb_alt_interface *ai;
2018760bc48eSAndrew Thompson 		struct usb_device_descriptor *ddesc;
2019760bc48eSAndrew Thompson 		struct usb_config_descriptor *cdesc;
2020760bc48eSAndrew Thompson 		struct usb_device_stats *stat;
2021760bc48eSAndrew Thompson 		struct usb_fs_init *pinit;
2022760bc48eSAndrew Thompson 		struct usb_fs_uninit *puninit;
202302ac6454SAndrew Thompson 		uint32_t *ptime;
202402ac6454SAndrew Thompson 		void   *addr;
202502ac6454SAndrew Thompson 		int    *pint;
202602ac6454SAndrew Thompson 	}     u;
2027760bc48eSAndrew Thompson 	struct usb_device_descriptor *dtemp;
2028760bc48eSAndrew Thompson 	struct usb_config_descriptor *ctemp;
2029760bc48eSAndrew Thompson 	struct usb_interface *iface;
203002ac6454SAndrew Thompson 	int error = 0;
203102ac6454SAndrew Thompson 	uint8_t n;
203202ac6454SAndrew Thompson 
203302ac6454SAndrew Thompson 	u.addr = addr;
203402ac6454SAndrew Thompson 
203502ac6454SAndrew Thompson 	DPRINTFN(6, "cmd=0x%08lx\n", cmd);
203602ac6454SAndrew Thompson 
203702ac6454SAndrew Thompson 	switch (cmd) {
203802ac6454SAndrew Thompson 	case USB_DISCOVER:
2039a593f6b8SAndrew Thompson 		usb_needs_explore_all();
204002ac6454SAndrew Thompson 		break;
204102ac6454SAndrew Thompson 
204202ac6454SAndrew Thompson 	case USB_SETDEBUG:
204302ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
204402ac6454SAndrew Thompson 			error = EPERM;
204502ac6454SAndrew Thompson 			break;
204602ac6454SAndrew Thompson 		}
2047a593f6b8SAndrew Thompson 		usb_debug = *(int *)addr;
204802ac6454SAndrew Thompson 		break;
204902ac6454SAndrew Thompson 
205002ac6454SAndrew Thompson 	case USB_GET_CONFIG:
205102ac6454SAndrew Thompson 		*(int *)addr = f->udev->curr_config_index;
205202ac6454SAndrew Thompson 		break;
205302ac6454SAndrew Thompson 
205402ac6454SAndrew Thompson 	case USB_SET_CONFIG:
205502ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
205602ac6454SAndrew Thompson 			error = EPERM;
205702ac6454SAndrew Thompson 			break;
205802ac6454SAndrew Thompson 		}
205902ac6454SAndrew Thompson 		error = ugen_set_config(f, *(int *)addr);
206002ac6454SAndrew Thompson 		break;
206102ac6454SAndrew Thompson 
206202ac6454SAndrew Thompson 	case USB_GET_ALTINTERFACE:
2063a593f6b8SAndrew Thompson 		iface = usbd_get_iface(f->udev,
206402ac6454SAndrew Thompson 		    u.ai->uai_interface_index);
206502ac6454SAndrew Thompson 		if (iface && iface->idesc) {
206602ac6454SAndrew Thompson 			u.ai->uai_alt_index = iface->alt_index;
206702ac6454SAndrew Thompson 		} else {
206802ac6454SAndrew Thompson 			error = EINVAL;
206902ac6454SAndrew Thompson 		}
207002ac6454SAndrew Thompson 		break;
207102ac6454SAndrew Thompson 
207202ac6454SAndrew Thompson 	case USB_SET_ALTINTERFACE:
207302ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
207402ac6454SAndrew Thompson 			error = EPERM;
207502ac6454SAndrew Thompson 			break;
207602ac6454SAndrew Thompson 		}
207702ac6454SAndrew Thompson 		error = ugen_set_interface(f,
207802ac6454SAndrew Thompson 		    u.ai->uai_interface_index, u.ai->uai_alt_index);
207902ac6454SAndrew Thompson 		break;
208002ac6454SAndrew Thompson 
208102ac6454SAndrew Thompson 	case USB_GET_DEVICE_DESC:
2082a593f6b8SAndrew Thompson 		dtemp = usbd_get_device_descriptor(f->udev);
208302ac6454SAndrew Thompson 		if (!dtemp) {
208402ac6454SAndrew Thompson 			error = EIO;
208502ac6454SAndrew Thompson 			break;
208602ac6454SAndrew Thompson 		}
208702ac6454SAndrew Thompson 		*u.ddesc = *dtemp;
208802ac6454SAndrew Thompson 		break;
208902ac6454SAndrew Thompson 
209002ac6454SAndrew Thompson 	case USB_GET_CONFIG_DESC:
2091a593f6b8SAndrew Thompson 		ctemp = usbd_get_config_descriptor(f->udev);
209202ac6454SAndrew Thompson 		if (!ctemp) {
209302ac6454SAndrew Thompson 			error = EIO;
209402ac6454SAndrew Thompson 			break;
209502ac6454SAndrew Thompson 		}
209602ac6454SAndrew Thompson 		*u.cdesc = *ctemp;
209702ac6454SAndrew Thompson 		break;
209802ac6454SAndrew Thompson 
209902ac6454SAndrew Thompson 	case USB_GET_FULL_DESC:
210002ac6454SAndrew Thompson 		error = ugen_get_cdesc(f, addr);
210102ac6454SAndrew Thompson 		break;
210202ac6454SAndrew Thompson 
210302ac6454SAndrew Thompson 	case USB_GET_STRING_DESC:
210402ac6454SAndrew Thompson 		error = ugen_get_sdesc(f, addr);
210502ac6454SAndrew Thompson 		break;
210602ac6454SAndrew Thompson 
210702ac6454SAndrew Thompson 	case USB_GET_IFACE_DRIVER:
210802ac6454SAndrew Thompson 		error = ugen_get_iface_driver(f, addr);
210902ac6454SAndrew Thompson 		break;
211002ac6454SAndrew Thompson 
211102ac6454SAndrew Thompson 	case USB_REQUEST:
211202ac6454SAndrew Thompson 	case USB_DO_REQUEST:
211302ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
211402ac6454SAndrew Thompson 			error = EPERM;
211502ac6454SAndrew Thompson 			break;
211602ac6454SAndrew Thompson 		}
211702ac6454SAndrew Thompson 		error = ugen_do_request(f, addr);
211802ac6454SAndrew Thompson 		break;
211902ac6454SAndrew Thompson 
212002ac6454SAndrew Thompson 	case USB_DEVICEINFO:
212102ac6454SAndrew Thompson 	case USB_GET_DEVICEINFO:
2122a593f6b8SAndrew Thompson 		error = usb_gen_fill_deviceinfo(f, addr);
212302ac6454SAndrew Thompson 		break;
212402ac6454SAndrew Thompson 
212502ac6454SAndrew Thompson 	case USB_DEVICESTATS:
212602ac6454SAndrew Thompson 		for (n = 0; n != 4; n++) {
212702ac6454SAndrew Thompson 
212802ac6454SAndrew Thompson 			u.stat->uds_requests_fail[n] =
212902ac6454SAndrew Thompson 			    f->udev->bus->stats_err.uds_requests[n];
213002ac6454SAndrew Thompson 
213102ac6454SAndrew Thompson 			u.stat->uds_requests_ok[n] =
213202ac6454SAndrew Thompson 			    f->udev->bus->stats_ok.uds_requests[n];
213302ac6454SAndrew Thompson 		}
213402ac6454SAndrew Thompson 		break;
213502ac6454SAndrew Thompson 
213602ac6454SAndrew Thompson 	case USB_DEVICEENUMERATE:
213702ac6454SAndrew Thompson 		error = ugen_re_enumerate(f);
213802ac6454SAndrew Thompson 		break;
213902ac6454SAndrew Thompson 
214002ac6454SAndrew Thompson 	case USB_GET_PLUGTIME:
214102ac6454SAndrew Thompson 		*u.ptime = f->udev->plugtime;
214202ac6454SAndrew Thompson 		break;
214302ac6454SAndrew Thompson 
214402ac6454SAndrew Thompson 	case USB_CLAIM_INTERFACE:
214502ac6454SAndrew Thompson 	case USB_RELEASE_INTERFACE:
214602ac6454SAndrew Thompson 		/* TODO */
214702ac6454SAndrew Thompson 		break;
214802ac6454SAndrew Thompson 
214902ac6454SAndrew Thompson 	case USB_IFACE_DRIVER_ACTIVE:
2150a7aca4cdSAndrew Thompson 
2151a7aca4cdSAndrew Thompson 		n = *u.pint & 0xFF;
2152a7aca4cdSAndrew Thompson 
2153a7aca4cdSAndrew Thompson 		iface = usbd_get_iface(f->udev, n);
2154a7aca4cdSAndrew Thompson 
2155a7aca4cdSAndrew Thompson 		if (iface && iface->subdev)
2156a7aca4cdSAndrew Thompson 			error = 0;
2157a7aca4cdSAndrew Thompson 		else
2158a7aca4cdSAndrew Thompson 			error = ENXIO;
215902ac6454SAndrew Thompson 		break;
216002ac6454SAndrew Thompson 
216102ac6454SAndrew Thompson 	case USB_IFACE_DRIVER_DETACH:
2162a7aca4cdSAndrew Thompson 
216302ac6454SAndrew Thompson 		error = priv_check(curthread, PRIV_DRIVER);
2164a7aca4cdSAndrew Thompson 
2165a7aca4cdSAndrew Thompson 		if (error)
2166a7aca4cdSAndrew Thompson 			break;
2167a7aca4cdSAndrew Thompson 
2168a7aca4cdSAndrew Thompson 		n = *u.pint & 0xFF;
2169a7aca4cdSAndrew Thompson 
2170a7aca4cdSAndrew Thompson 		if (n == USB_IFACE_INDEX_ANY) {
2171a7aca4cdSAndrew Thompson 			error = EINVAL;
217202ac6454SAndrew Thompson 			break;
217302ac6454SAndrew Thompson 		}
2174a7aca4cdSAndrew Thompson 
2175d9073c1eSHans Petter Selasky 		/*
2176d9073c1eSHans Petter Selasky 		 * Detach the currently attached driver.
2177d9073c1eSHans Petter Selasky 		 */
2178a7aca4cdSAndrew Thompson 		usb_detach_device(f->udev, n, 0);
2179d9073c1eSHans Petter Selasky 
2180d9073c1eSHans Petter Selasky 		/*
2181d9073c1eSHans Petter Selasky 		 * Set parent to self, this should keep attach away
2182d9073c1eSHans Petter Selasky 		 * until the next set configuration event.
2183d9073c1eSHans Petter Selasky 		 */
2184d9073c1eSHans Petter Selasky 		usbd_set_parent_iface(f->udev, n, n);
218502ac6454SAndrew Thompson 		break;
218602ac6454SAndrew Thompson 
218702ac6454SAndrew Thompson 	case USB_SET_POWER_MODE:
218802ac6454SAndrew Thompson 		error = ugen_set_power_mode(f, *u.pint);
218902ac6454SAndrew Thompson 		break;
219002ac6454SAndrew Thompson 
219102ac6454SAndrew Thompson 	case USB_GET_POWER_MODE:
219202ac6454SAndrew Thompson 		*u.pint = ugen_get_power_mode(f);
219302ac6454SAndrew Thompson 		break;
219402ac6454SAndrew Thompson 
219502ac6454SAndrew Thompson 	case USB_SET_PORT_ENABLE:
219602ac6454SAndrew Thompson 		error = ugen_do_port_feature(f,
219702ac6454SAndrew Thompson 		    *u.pint, 1, UHF_PORT_ENABLE);
219802ac6454SAndrew Thompson 		break;
219902ac6454SAndrew Thompson 
220002ac6454SAndrew Thompson 	case USB_SET_PORT_DISABLE:
220102ac6454SAndrew Thompson 		error = ugen_do_port_feature(f,
220202ac6454SAndrew Thompson 		    *u.pint, 0, UHF_PORT_ENABLE);
220302ac6454SAndrew Thompson 		break;
220402ac6454SAndrew Thompson 
220502ac6454SAndrew Thompson 	case USB_FS_INIT:
220602ac6454SAndrew Thompson 		/* verify input parameters */
220702ac6454SAndrew Thompson 		if (u.pinit->pEndpoints == NULL) {
220802ac6454SAndrew Thompson 			error = EINVAL;
220902ac6454SAndrew Thompson 			break;
221002ac6454SAndrew Thompson 		}
221102ac6454SAndrew Thompson 		if (u.pinit->ep_index_max > 127) {
221202ac6454SAndrew Thompson 			error = EINVAL;
221302ac6454SAndrew Thompson 			break;
221402ac6454SAndrew Thompson 		}
221502ac6454SAndrew Thompson 		if (u.pinit->ep_index_max == 0) {
221602ac6454SAndrew Thompson 			error = EINVAL;
221702ac6454SAndrew Thompson 			break;
221802ac6454SAndrew Thompson 		}
221902ac6454SAndrew Thompson 		if (f->fs_xfer != NULL) {
222002ac6454SAndrew Thompson 			error = EBUSY;
222102ac6454SAndrew Thompson 			break;
222202ac6454SAndrew Thompson 		}
222302ac6454SAndrew Thompson 		if (f->dev_ep_index != 0) {
222402ac6454SAndrew Thompson 			error = EINVAL;
222502ac6454SAndrew Thompson 			break;
222602ac6454SAndrew Thompson 		}
222702ac6454SAndrew Thompson 		if (ugen_fifo_in_use(f, fflags)) {
222802ac6454SAndrew Thompson 			error = EBUSY;
222902ac6454SAndrew Thompson 			break;
223002ac6454SAndrew Thompson 		}
2231a593f6b8SAndrew Thompson 		error = usb_fifo_alloc_buffer(f, 1, u.pinit->ep_index_max);
223202ac6454SAndrew Thompson 		if (error) {
223302ac6454SAndrew Thompson 			break;
223402ac6454SAndrew Thompson 		}
223502ac6454SAndrew Thompson 		f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) *
223602ac6454SAndrew Thompson 		    u.pinit->ep_index_max, M_USB, M_WAITOK | M_ZERO);
223702ac6454SAndrew Thompson 		if (f->fs_xfer == NULL) {
2238a593f6b8SAndrew Thompson 			usb_fifo_free_buffer(f);
223902ac6454SAndrew Thompson 			error = ENOMEM;
224002ac6454SAndrew Thompson 			break;
224102ac6454SAndrew Thompson 		}
224202ac6454SAndrew Thompson 		f->fs_ep_max = u.pinit->ep_index_max;
224302ac6454SAndrew Thompson 		f->fs_ep_ptr = u.pinit->pEndpoints;
224402ac6454SAndrew Thompson 		break;
224502ac6454SAndrew Thompson 
224602ac6454SAndrew Thompson 	case USB_FS_UNINIT:
224702ac6454SAndrew Thompson 		if (u.puninit->dummy != 0) {
224802ac6454SAndrew Thompson 			error = EINVAL;
224902ac6454SAndrew Thompson 			break;
225002ac6454SAndrew Thompson 		}
225102ac6454SAndrew Thompson 		error = ugen_fs_uninit(f);
225202ac6454SAndrew Thompson 		break;
225302ac6454SAndrew Thompson 
225402ac6454SAndrew Thompson 	default:
225502ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
225602ac6454SAndrew Thompson 		error = ugen_iface_ioctl(f, cmd, addr, fflags);
225702ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
225802ac6454SAndrew Thompson 		break;
225902ac6454SAndrew Thompson 	}
226002ac6454SAndrew Thompson 	DPRINTFN(6, "error=%d\n", error);
226102ac6454SAndrew Thompson 	return (error);
226202ac6454SAndrew Thompson }
226302ac6454SAndrew Thompson 
226402ac6454SAndrew Thompson static void
22655b3bb704SAndrew Thompson ugen_ctrl_fs_callback(struct usb_xfer *xfer, usb_error_t error)
226602ac6454SAndrew Thompson {
226702ac6454SAndrew Thompson 	;				/* workaround for a bug in "indent" */
226802ac6454SAndrew Thompson 
226902ac6454SAndrew Thompson 	DPRINTF("st=%u alen=%u aframes=%u\n",
227002ac6454SAndrew Thompson 	    USB_GET_STATE(xfer), xfer->actlen, xfer->aframes);
227102ac6454SAndrew Thompson 
227202ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
227302ac6454SAndrew Thompson 	case USB_ST_SETUP:
2274a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
227502ac6454SAndrew Thompson 		break;
227602ac6454SAndrew Thompson 	default:
227702ac6454SAndrew Thompson 		ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo));
227802ac6454SAndrew Thompson 		break;
227902ac6454SAndrew Thompson 	}
228002ac6454SAndrew Thompson }
22818755859aSAndrew Thompson #endif	/* USB_HAVE_UGEN */
2282