xref: /freebsd/sys/dev/usb/usb_generic.c (revision 56b57046)
102ac6454SAndrew Thompson /* $FreeBSD$ */
202ac6454SAndrew Thompson /*-
302ac6454SAndrew Thompson  * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
402ac6454SAndrew Thompson  *
502ac6454SAndrew Thompson  * Redistribution and use in source and binary forms, with or without
602ac6454SAndrew Thompson  * modification, are permitted provided that the following conditions
702ac6454SAndrew Thompson  * are met:
802ac6454SAndrew Thompson  * 1. Redistributions of source code must retain the above copyright
902ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer.
1002ac6454SAndrew Thompson  * 2. Redistributions in binary form must reproduce the above copyright
1102ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer in the
1202ac6454SAndrew Thompson  *    documentation and/or other materials provided with the distribution.
1302ac6454SAndrew Thompson  *
1402ac6454SAndrew Thompson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1502ac6454SAndrew Thompson  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1602ac6454SAndrew Thompson  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1702ac6454SAndrew Thompson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1802ac6454SAndrew Thompson  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1902ac6454SAndrew Thompson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2002ac6454SAndrew Thompson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2102ac6454SAndrew Thompson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2202ac6454SAndrew Thompson  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2302ac6454SAndrew Thompson  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2402ac6454SAndrew Thompson  * SUCH DAMAGE.
2502ac6454SAndrew Thompson  */
2602ac6454SAndrew Thompson 
27ed6d949aSAndrew Thompson #include <sys/stdint.h>
28ed6d949aSAndrew Thompson #include <sys/stddef.h>
29ed6d949aSAndrew Thompson #include <sys/param.h>
30ed6d949aSAndrew Thompson #include <sys/queue.h>
31ed6d949aSAndrew Thompson #include <sys/types.h>
32ed6d949aSAndrew Thompson #include <sys/systm.h>
33ed6d949aSAndrew Thompson #include <sys/kernel.h>
34ed6d949aSAndrew Thompson #include <sys/bus.h>
35ed6d949aSAndrew Thompson #include <sys/module.h>
36ed6d949aSAndrew Thompson #include <sys/lock.h>
37ed6d949aSAndrew Thompson #include <sys/mutex.h>
38ed6d949aSAndrew Thompson #include <sys/condvar.h>
39ed6d949aSAndrew Thompson #include <sys/sysctl.h>
40ed6d949aSAndrew Thompson #include <sys/sx.h>
41ed6d949aSAndrew Thompson #include <sys/unistd.h>
42ed6d949aSAndrew Thompson #include <sys/callout.h>
43ed6d949aSAndrew Thompson #include <sys/malloc.h>
44ed6d949aSAndrew Thompson #include <sys/priv.h>
45ed6d949aSAndrew Thompson #include <sys/conf.h>
46ed6d949aSAndrew Thompson #include <sys/fcntl.h>
47ed6d949aSAndrew Thompson 
4802ac6454SAndrew Thompson #include <dev/usb/usb.h>
4902ac6454SAndrew Thompson #include <dev/usb/usb_ioctl.h>
50ed6d949aSAndrew Thompson #include <dev/usb/usbdi.h>
51ed6d949aSAndrew Thompson #include <dev/usb/usbdi_util.h>
5202ac6454SAndrew Thompson 
5302ac6454SAndrew Thompson #define	USB_DEBUG_VAR ugen_debug
5402ac6454SAndrew Thompson 
5502ac6454SAndrew Thompson #include <dev/usb/usb_core.h>
5602ac6454SAndrew Thompson #include <dev/usb/usb_dev.h>
57ed6d949aSAndrew Thompson #include <dev/usb/usb_mbuf.h>
5802ac6454SAndrew Thompson #include <dev/usb/usb_process.h>
5902ac6454SAndrew Thompson #include <dev/usb/usb_device.h>
6002ac6454SAndrew Thompson #include <dev/usb/usb_debug.h>
6102ac6454SAndrew Thompson #include <dev/usb/usb_request.h>
6202ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
6302ac6454SAndrew Thompson #include <dev/usb/usb_util.h>
6402ac6454SAndrew Thompson #include <dev/usb/usb_hub.h>
6502ac6454SAndrew Thompson #include <dev/usb/usb_generic.h>
6602ac6454SAndrew Thompson #include <dev/usb/usb_transfer.h>
6702ac6454SAndrew Thompson 
6802ac6454SAndrew Thompson #include <dev/usb/usb_controller.h>
6902ac6454SAndrew Thompson #include <dev/usb/usb_bus.h>
7002ac6454SAndrew Thompson 
718755859aSAndrew Thompson #if USB_HAVE_UGEN
728755859aSAndrew Thompson 
7302ac6454SAndrew Thompson /* defines */
7402ac6454SAndrew Thompson 
7502ac6454SAndrew Thompson #define	UGEN_BULK_FS_BUFFER_SIZE	(64*32)	/* bytes */
7602ac6454SAndrew Thompson #define	UGEN_BULK_HS_BUFFER_SIZE	(1024*32)	/* bytes */
7702ac6454SAndrew Thompson #define	UGEN_HW_FRAMES	50		/* number of milliseconds per transfer */
7802ac6454SAndrew Thompson 
7902ac6454SAndrew Thompson /* function prototypes */
8002ac6454SAndrew Thompson 
81e0a69b51SAndrew Thompson static usb_callback_t ugen_read_clear_stall_callback;
82e0a69b51SAndrew Thompson static usb_callback_t ugen_write_clear_stall_callback;
835b3bb704SAndrew Thompson static usb_callback_t ugen_ctrl_read_callback;
845b3bb704SAndrew Thompson static usb_callback_t ugen_ctrl_write_callback;
85e0a69b51SAndrew Thompson static usb_callback_t ugen_isoc_read_callback;
86e0a69b51SAndrew Thompson static usb_callback_t ugen_isoc_write_callback;
875b3bb704SAndrew Thompson static usb_callback_t ugen_ctrl_fs_callback;
8802ac6454SAndrew Thompson 
89e0a69b51SAndrew Thompson static usb_fifo_open_t ugen_open;
90e0a69b51SAndrew Thompson static usb_fifo_close_t ugen_close;
91e0a69b51SAndrew Thompson static usb_fifo_ioctl_t ugen_ioctl;
92e0a69b51SAndrew Thompson static usb_fifo_ioctl_t ugen_ioctl_post;
93e0a69b51SAndrew Thompson static usb_fifo_cmd_t ugen_start_read;
94e0a69b51SAndrew Thompson static usb_fifo_cmd_t ugen_start_write;
95e0a69b51SAndrew Thompson static usb_fifo_cmd_t ugen_stop_io;
9602ac6454SAndrew Thompson 
97760bc48eSAndrew Thompson static int	ugen_transfer_setup(struct usb_fifo *,
98760bc48eSAndrew Thompson 		     const struct usb_config *, uint8_t);
99760bc48eSAndrew Thompson static int	ugen_open_pipe_write(struct usb_fifo *);
100760bc48eSAndrew Thompson static int	ugen_open_pipe_read(struct usb_fifo *);
101760bc48eSAndrew Thompson static int	ugen_set_config(struct usb_fifo *, uint8_t);
102760bc48eSAndrew Thompson static int	ugen_set_interface(struct usb_fifo *, uint8_t, uint8_t);
103760bc48eSAndrew Thompson static int	ugen_get_cdesc(struct usb_fifo *, struct usb_gen_descriptor *);
104760bc48eSAndrew Thompson static int	ugen_get_sdesc(struct usb_fifo *, struct usb_gen_descriptor *);
105760bc48eSAndrew Thompson static int	ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd);
106a593f6b8SAndrew Thompson static int	usb_gen_fill_deviceinfo(struct usb_fifo *,
107760bc48eSAndrew Thompson 		    struct usb_device_info *);
108760bc48eSAndrew Thompson static int	ugen_re_enumerate(struct usb_fifo *);
109760bc48eSAndrew Thompson static int	ugen_iface_ioctl(struct usb_fifo *, u_long, void *, int);
110760bc48eSAndrew Thompson static uint8_t	ugen_fs_get_complete(struct usb_fifo *, uint8_t *);
111760bc48eSAndrew Thompson static int	ugen_fs_uninit(struct usb_fifo *f);
11202ac6454SAndrew Thompson 
11302ac6454SAndrew Thompson /* structures */
11402ac6454SAndrew Thompson 
115a593f6b8SAndrew Thompson struct usb_fifo_methods usb_ugen_methods = {
11602ac6454SAndrew Thompson 	.f_open = &ugen_open,
11702ac6454SAndrew Thompson 	.f_close = &ugen_close,
11802ac6454SAndrew Thompson 	.f_ioctl = &ugen_ioctl,
11902ac6454SAndrew Thompson 	.f_ioctl_post = &ugen_ioctl_post,
12002ac6454SAndrew Thompson 	.f_start_read = &ugen_start_read,
12102ac6454SAndrew Thompson 	.f_stop_read = &ugen_stop_io,
12202ac6454SAndrew Thompson 	.f_start_write = &ugen_start_write,
12302ac6454SAndrew Thompson 	.f_stop_write = &ugen_stop_io,
12402ac6454SAndrew Thompson };
12502ac6454SAndrew Thompson 
126ed6d949aSAndrew Thompson #ifdef USB_DEBUG
12702ac6454SAndrew Thompson static int ugen_debug = 0;
12802ac6454SAndrew Thompson 
1299360ae40SAndrew Thompson SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW, 0, "USB generic");
1309360ae40SAndrew Thompson SYSCTL_INT(_hw_usb_ugen, OID_AUTO, debug, CTLFLAG_RW, &ugen_debug,
13102ac6454SAndrew Thompson     0, "Debug level");
132c13fd8d4SAndrew Thompson 
133c13fd8d4SAndrew Thompson TUNABLE_INT("hw.usb.ugen.debug", &ugen_debug);
13402ac6454SAndrew Thompson #endif
13502ac6454SAndrew Thompson 
13602ac6454SAndrew Thompson 
13702ac6454SAndrew Thompson /* prototypes */
13802ac6454SAndrew Thompson 
13902ac6454SAndrew Thompson static int
140760bc48eSAndrew Thompson ugen_transfer_setup(struct usb_fifo *f,
141760bc48eSAndrew Thompson     const struct usb_config *setup, uint8_t n_setup)
14202ac6454SAndrew Thompson {
143ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
144760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
145ae60fdfbSAndrew Thompson 	uint8_t iface_index = ep->iface_index;
14602ac6454SAndrew Thompson 	int error;
14702ac6454SAndrew Thompson 
14802ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
14902ac6454SAndrew Thompson 
15002ac6454SAndrew Thompson 	/*
151a593f6b8SAndrew Thompson 	 * "usbd_transfer_setup()" can sleep so one needs to make a wrapper,
15202ac6454SAndrew Thompson 	 * exiting the mutex and checking things
15302ac6454SAndrew Thompson 	 */
154a593f6b8SAndrew Thompson 	error = usbd_transfer_setup(udev, &iface_index, f->xfer,
15502ac6454SAndrew Thompson 	    setup, n_setup, f, f->priv_mtx);
15602ac6454SAndrew Thompson 	if (error == 0) {
15702ac6454SAndrew Thompson 
15802ac6454SAndrew Thompson 		if (f->xfer[0]->nframes == 1) {
159a593f6b8SAndrew Thompson 			error = usb_fifo_alloc_buffer(f,
16002ac6454SAndrew Thompson 			    f->xfer[0]->max_data_length, 2);
16102ac6454SAndrew Thompson 		} else {
162a593f6b8SAndrew Thompson 			error = usb_fifo_alloc_buffer(f,
16302ac6454SAndrew Thompson 			    f->xfer[0]->max_frame_size,
16402ac6454SAndrew Thompson 			    2 * f->xfer[0]->nframes);
16502ac6454SAndrew Thompson 		}
16602ac6454SAndrew Thompson 		if (error) {
167a593f6b8SAndrew Thompson 			usbd_transfer_unsetup(f->xfer, n_setup);
16802ac6454SAndrew Thompson 		}
16902ac6454SAndrew Thompson 	}
17002ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
17102ac6454SAndrew Thompson 
17202ac6454SAndrew Thompson 	return (error);
17302ac6454SAndrew Thompson }
17402ac6454SAndrew Thompson 
17502ac6454SAndrew Thompson static int
176760bc48eSAndrew Thompson ugen_open(struct usb_fifo *f, int fflags)
17702ac6454SAndrew Thompson {
178ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
179ae60fdfbSAndrew Thompson 	struct usb_endpoint_descriptor *ed = ep->edesc;
18002ac6454SAndrew Thompson 	uint8_t type;
18102ac6454SAndrew Thompson 
18202ac6454SAndrew Thompson 	DPRINTFN(6, "flag=0x%x\n", fflags);
18302ac6454SAndrew Thompson 
18402ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
185a593f6b8SAndrew Thompson 	switch (usbd_get_speed(f->udev)) {
18602ac6454SAndrew Thompson 	case USB_SPEED_LOW:
18702ac6454SAndrew Thompson 	case USB_SPEED_FULL:
18802ac6454SAndrew Thompson 		f->nframes = UGEN_HW_FRAMES;
18902ac6454SAndrew Thompson 		f->bufsize = UGEN_BULK_FS_BUFFER_SIZE;
19002ac6454SAndrew Thompson 		break;
19102ac6454SAndrew Thompson 	default:
19202ac6454SAndrew Thompson 		f->nframes = UGEN_HW_FRAMES * 8;
19302ac6454SAndrew Thompson 		f->bufsize = UGEN_BULK_HS_BUFFER_SIZE;
19402ac6454SAndrew Thompson 		break;
19502ac6454SAndrew Thompson 	}
19602ac6454SAndrew Thompson 
19702ac6454SAndrew Thompson 	type = ed->bmAttributes & UE_XFERTYPE;
19802ac6454SAndrew Thompson 	if (type == UE_INTERRUPT) {
19902ac6454SAndrew Thompson 		f->bufsize = 0;		/* use "wMaxPacketSize" */
20002ac6454SAndrew Thompson 	}
20102ac6454SAndrew Thompson 	f->timeout = USB_NO_TIMEOUT;
20202ac6454SAndrew Thompson 	f->flag_short = 0;
20302ac6454SAndrew Thompson 	f->fifo_zlp = 0;
20402ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
20502ac6454SAndrew Thompson 
20602ac6454SAndrew Thompson 	return (0);
20702ac6454SAndrew Thompson }
20802ac6454SAndrew Thompson 
20902ac6454SAndrew Thompson static void
210760bc48eSAndrew Thompson ugen_close(struct usb_fifo *f, int fflags)
21102ac6454SAndrew Thompson {
21202ac6454SAndrew Thompson 	DPRINTFN(6, "flag=0x%x\n", fflags);
21302ac6454SAndrew Thompson 
21402ac6454SAndrew Thompson 	/* cleanup */
21502ac6454SAndrew Thompson 
21602ac6454SAndrew Thompson 	mtx_lock(f->priv_mtx);
217a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[0]);
218a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[1]);
21902ac6454SAndrew Thompson 	mtx_unlock(f->priv_mtx);
22002ac6454SAndrew Thompson 
221a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(f->xfer, 2);
222a593f6b8SAndrew Thompson 	usb_fifo_free_buffer(f);
22302ac6454SAndrew Thompson 
22402ac6454SAndrew Thompson 	if (ugen_fs_uninit(f)) {
22502ac6454SAndrew Thompson 		/* ignore any errors - we are closing */
22602ac6454SAndrew Thompson 		DPRINTFN(6, "no FIFOs\n");
22702ac6454SAndrew Thompson 	}
22802ac6454SAndrew Thompson }
22902ac6454SAndrew Thompson 
23002ac6454SAndrew Thompson static int
231760bc48eSAndrew Thompson ugen_open_pipe_write(struct usb_fifo *f)
23202ac6454SAndrew Thompson {
233760bc48eSAndrew Thompson 	struct usb_config usb_config[2];
234ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
235ae60fdfbSAndrew Thompson 	struct usb_endpoint_descriptor *ed = ep->edesc;
23602ac6454SAndrew Thompson 
23702ac6454SAndrew Thompson 	mtx_assert(f->priv_mtx, MA_OWNED);
23802ac6454SAndrew Thompson 
23902ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
24002ac6454SAndrew Thompson 		/* transfers are already opened */
24102ac6454SAndrew Thompson 		return (0);
24202ac6454SAndrew Thompson 	}
243760bc48eSAndrew Thompson 	bzero(usb_config, sizeof(usb_config));
24402ac6454SAndrew Thompson 
245760bc48eSAndrew Thompson 	usb_config[1].type = UE_CONTROL;
246760bc48eSAndrew Thompson 	usb_config[1].endpoint = 0;
247760bc48eSAndrew Thompson 	usb_config[1].direction = UE_DIR_ANY;
248760bc48eSAndrew Thompson 	usb_config[1].timeout = 1000;	/* 1 second */
249760bc48eSAndrew Thompson 	usb_config[1].interval = 50;/* 50 milliseconds */
250760bc48eSAndrew Thompson 	usb_config[1].bufsize = sizeof(struct usb_device_request);
251760bc48eSAndrew Thompson 	usb_config[1].callback = &ugen_write_clear_stall_callback;
252760bc48eSAndrew Thompson 	usb_config[1].usb_mode = USB_MODE_HOST;
25302ac6454SAndrew Thompson 
254760bc48eSAndrew Thompson 	usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
255760bc48eSAndrew Thompson 	usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
256760bc48eSAndrew Thompson 	usb_config[0].direction = UE_DIR_TX;
257760bc48eSAndrew Thompson 	usb_config[0].interval = USB_DEFAULT_INTERVAL;
258760bc48eSAndrew Thompson 	usb_config[0].flags.proxy_buffer = 1;
259760bc48eSAndrew Thompson 	usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
26002ac6454SAndrew Thompson 
26102ac6454SAndrew Thompson 	switch (ed->bmAttributes & UE_XFERTYPE) {
26202ac6454SAndrew Thompson 	case UE_INTERRUPT:
26302ac6454SAndrew Thompson 	case UE_BULK:
26402ac6454SAndrew Thompson 		if (f->flag_short) {
265760bc48eSAndrew Thompson 			usb_config[0].flags.force_short_xfer = 1;
26602ac6454SAndrew Thompson 		}
2675b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_write_callback;
268760bc48eSAndrew Thompson 		usb_config[0].timeout = f->timeout;
269760bc48eSAndrew Thompson 		usb_config[0].frames = 1;
270760bc48eSAndrew Thompson 		usb_config[0].bufsize = f->bufsize;
271760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
27202ac6454SAndrew Thompson 			return (EIO);
27302ac6454SAndrew Thompson 		}
27402ac6454SAndrew Thompson 		/* first transfer does not clear stall */
27502ac6454SAndrew Thompson 		f->flag_stall = 0;
27602ac6454SAndrew Thompson 		break;
27702ac6454SAndrew Thompson 
27802ac6454SAndrew Thompson 	case UE_ISOCHRONOUS:
279760bc48eSAndrew Thompson 		usb_config[0].flags.short_xfer_ok = 1;
280760bc48eSAndrew Thompson 		usb_config[0].bufsize = 0;	/* use default */
281760bc48eSAndrew Thompson 		usb_config[0].frames = f->nframes;
282760bc48eSAndrew Thompson 		usb_config[0].callback = &ugen_isoc_write_callback;
283760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;
28402ac6454SAndrew Thompson 
28502ac6454SAndrew Thompson 		/* clone configuration */
286760bc48eSAndrew Thompson 		usb_config[1] = usb_config[0];
28702ac6454SAndrew Thompson 
288760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
28902ac6454SAndrew Thompson 			return (EIO);
29002ac6454SAndrew Thompson 		}
29102ac6454SAndrew Thompson 		break;
29202ac6454SAndrew Thompson 	default:
29302ac6454SAndrew Thompson 		return (EINVAL);
29402ac6454SAndrew Thompson 	}
29502ac6454SAndrew Thompson 	return (0);
29602ac6454SAndrew Thompson }
29702ac6454SAndrew Thompson 
29802ac6454SAndrew Thompson static int
299760bc48eSAndrew Thompson ugen_open_pipe_read(struct usb_fifo *f)
30002ac6454SAndrew Thompson {
301760bc48eSAndrew Thompson 	struct usb_config usb_config[2];
302ed6d949aSAndrew Thompson 	struct usb_endpoint *ep = usb_fifo_softc(f);
303ae60fdfbSAndrew Thompson 	struct usb_endpoint_descriptor *ed = ep->edesc;
30402ac6454SAndrew Thompson 
30502ac6454SAndrew Thompson 	mtx_assert(f->priv_mtx, MA_OWNED);
30602ac6454SAndrew Thompson 
30702ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
30802ac6454SAndrew Thompson 		/* transfers are already opened */
30902ac6454SAndrew Thompson 		return (0);
31002ac6454SAndrew Thompson 	}
311760bc48eSAndrew Thompson 	bzero(usb_config, sizeof(usb_config));
31202ac6454SAndrew Thompson 
313760bc48eSAndrew Thompson 	usb_config[1].type = UE_CONTROL;
314760bc48eSAndrew Thompson 	usb_config[1].endpoint = 0;
315760bc48eSAndrew Thompson 	usb_config[1].direction = UE_DIR_ANY;
316760bc48eSAndrew Thompson 	usb_config[1].timeout = 1000;	/* 1 second */
317760bc48eSAndrew Thompson 	usb_config[1].interval = 50;/* 50 milliseconds */
318760bc48eSAndrew Thompson 	usb_config[1].bufsize = sizeof(struct usb_device_request);
319760bc48eSAndrew Thompson 	usb_config[1].callback = &ugen_read_clear_stall_callback;
320760bc48eSAndrew Thompson 	usb_config[1].usb_mode = USB_MODE_HOST;
32102ac6454SAndrew Thompson 
322760bc48eSAndrew Thompson 	usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
323760bc48eSAndrew Thompson 	usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
324760bc48eSAndrew Thompson 	usb_config[0].direction = UE_DIR_RX;
325760bc48eSAndrew Thompson 	usb_config[0].interval = USB_DEFAULT_INTERVAL;
326760bc48eSAndrew Thompson 	usb_config[0].flags.proxy_buffer = 1;
327760bc48eSAndrew Thompson 	usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
32802ac6454SAndrew Thompson 
32902ac6454SAndrew Thompson 	switch (ed->bmAttributes & UE_XFERTYPE) {
33002ac6454SAndrew Thompson 	case UE_INTERRUPT:
33102ac6454SAndrew Thompson 	case UE_BULK:
33202ac6454SAndrew Thompson 		if (f->flag_short) {
333760bc48eSAndrew Thompson 			usb_config[0].flags.short_xfer_ok = 1;
33402ac6454SAndrew Thompson 		}
335760bc48eSAndrew Thompson 		usb_config[0].timeout = f->timeout;
336760bc48eSAndrew Thompson 		usb_config[0].frames = 1;
3375b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_read_callback;
338760bc48eSAndrew Thompson 		usb_config[0].bufsize = f->bufsize;
33902ac6454SAndrew Thompson 
340760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
34102ac6454SAndrew Thompson 			return (EIO);
34202ac6454SAndrew Thompson 		}
34302ac6454SAndrew Thompson 		/* first transfer does not clear stall */
34402ac6454SAndrew Thompson 		f->flag_stall = 0;
34502ac6454SAndrew Thompson 		break;
34602ac6454SAndrew Thompson 
34702ac6454SAndrew Thompson 	case UE_ISOCHRONOUS:
348760bc48eSAndrew Thompson 		usb_config[0].flags.short_xfer_ok = 1;
349760bc48eSAndrew Thompson 		usb_config[0].bufsize = 0;	/* use default */
350760bc48eSAndrew Thompson 		usb_config[0].frames = f->nframes;
351760bc48eSAndrew Thompson 		usb_config[0].callback = &ugen_isoc_read_callback;
352760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;
35302ac6454SAndrew Thompson 
35402ac6454SAndrew Thompson 		/* clone configuration */
355760bc48eSAndrew Thompson 		usb_config[1] = usb_config[0];
35602ac6454SAndrew Thompson 
357760bc48eSAndrew Thompson 		if (ugen_transfer_setup(f, usb_config, 2)) {
35802ac6454SAndrew Thompson 			return (EIO);
35902ac6454SAndrew Thompson 		}
36002ac6454SAndrew Thompson 		break;
36102ac6454SAndrew Thompson 
36202ac6454SAndrew Thompson 	default:
36302ac6454SAndrew Thompson 		return (EINVAL);
36402ac6454SAndrew Thompson 	}
36502ac6454SAndrew Thompson 	return (0);
36602ac6454SAndrew Thompson }
36702ac6454SAndrew Thompson 
36802ac6454SAndrew Thompson static void
369760bc48eSAndrew Thompson ugen_start_read(struct usb_fifo *f)
37002ac6454SAndrew Thompson {
37102ac6454SAndrew Thompson 	/* check that pipes are open */
37202ac6454SAndrew Thompson 	if (ugen_open_pipe_read(f)) {
37302ac6454SAndrew Thompson 		/* signal error */
374a593f6b8SAndrew Thompson 		usb_fifo_put_data_error(f);
37502ac6454SAndrew Thompson 	}
37602ac6454SAndrew Thompson 	/* start transfers */
377a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[0]);
378a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[1]);
37902ac6454SAndrew Thompson }
38002ac6454SAndrew Thompson 
38102ac6454SAndrew Thompson static void
382760bc48eSAndrew Thompson ugen_start_write(struct usb_fifo *f)
38302ac6454SAndrew Thompson {
38402ac6454SAndrew Thompson 	/* check that pipes are open */
38502ac6454SAndrew Thompson 	if (ugen_open_pipe_write(f)) {
38602ac6454SAndrew Thompson 		/* signal error */
387a593f6b8SAndrew Thompson 		usb_fifo_get_data_error(f);
38802ac6454SAndrew Thompson 	}
38902ac6454SAndrew Thompson 	/* start transfers */
390a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[0]);
391a593f6b8SAndrew Thompson 	usbd_transfer_start(f->xfer[1]);
39202ac6454SAndrew Thompson }
39302ac6454SAndrew Thompson 
39402ac6454SAndrew Thompson static void
395760bc48eSAndrew Thompson ugen_stop_io(struct usb_fifo *f)
39602ac6454SAndrew Thompson {
39702ac6454SAndrew Thompson 	/* stop transfers */
398a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[0]);
399a593f6b8SAndrew Thompson 	usbd_transfer_stop(f->xfer[1]);
40002ac6454SAndrew Thompson }
40102ac6454SAndrew Thompson 
40202ac6454SAndrew Thompson static void
4035b3bb704SAndrew Thompson ugen_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error)
40402ac6454SAndrew Thompson {
405ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
406760bc48eSAndrew Thompson 	struct usb_mbuf *m;
40702ac6454SAndrew Thompson 
40802ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
40902ac6454SAndrew Thompson 
41002ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
41102ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
41202ac6454SAndrew Thompson 		if (xfer->actlen == 0) {
41302ac6454SAndrew Thompson 			if (f->fifo_zlp != 4) {
41402ac6454SAndrew Thompson 				f->fifo_zlp++;
41502ac6454SAndrew Thompson 			} else {
41602ac6454SAndrew Thompson 				/*
41702ac6454SAndrew Thompson 				 * Throttle a little bit we have multiple ZLPs
41802ac6454SAndrew Thompson 				 * in a row!
41902ac6454SAndrew Thompson 				 */
42002ac6454SAndrew Thompson 				xfer->interval = 64;	/* ms */
42102ac6454SAndrew Thompson 			}
42202ac6454SAndrew Thompson 		} else {
42302ac6454SAndrew Thompson 			/* clear throttle */
42402ac6454SAndrew Thompson 			xfer->interval = 0;
42502ac6454SAndrew Thompson 			f->fifo_zlp = 0;
42602ac6454SAndrew Thompson 		}
427a593f6b8SAndrew Thompson 		usb_fifo_put_data(f, xfer->frbuffers, 0,
42802ac6454SAndrew Thompson 		    xfer->actlen, 1);
42902ac6454SAndrew Thompson 
43002ac6454SAndrew Thompson 	case USB_ST_SETUP:
43102ac6454SAndrew Thompson 		if (f->flag_stall) {
432a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
43302ac6454SAndrew Thompson 			break;
43402ac6454SAndrew Thompson 		}
43502ac6454SAndrew Thompson 		USB_IF_POLL(&f->free_q, m);
43602ac6454SAndrew Thompson 		if (m) {
437ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
438a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
43902ac6454SAndrew Thompson 		}
44002ac6454SAndrew Thompson 		break;
44102ac6454SAndrew Thompson 
44202ac6454SAndrew Thompson 	default:			/* Error */
44302ac6454SAndrew Thompson 		if (xfer->error != USB_ERR_CANCELLED) {
44401cf7831SAndrew Thompson 			/* send a zero length packet to userland */
445a593f6b8SAndrew Thompson 			usb_fifo_put_data(f, xfer->frbuffers, 0, 0, 1);
44602ac6454SAndrew Thompson 			f->flag_stall = 1;
44702ac6454SAndrew Thompson 			f->fifo_zlp = 0;
448a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
44902ac6454SAndrew Thompson 		}
45002ac6454SAndrew Thompson 		break;
45102ac6454SAndrew Thompson 	}
45202ac6454SAndrew Thompson }
45302ac6454SAndrew Thompson 
45402ac6454SAndrew Thompson static void
4555b3bb704SAndrew Thompson ugen_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error)
45602ac6454SAndrew Thompson {
457ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
458e0a69b51SAndrew Thompson 	usb_frlength_t actlen;
45902ac6454SAndrew Thompson 
46002ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
46102ac6454SAndrew Thompson 
46202ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
46302ac6454SAndrew Thompson 	case USB_ST_SETUP:
46402ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
46502ac6454SAndrew Thompson 		/*
46602ac6454SAndrew Thompson 		 * If writing is in stall, just jump to clear stall
46702ac6454SAndrew Thompson 		 * callback and solve the situation.
46802ac6454SAndrew Thompson 		 */
46902ac6454SAndrew Thompson 		if (f->flag_stall) {
470a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
47102ac6454SAndrew Thompson 			break;
47202ac6454SAndrew Thompson 		}
47302ac6454SAndrew Thompson 		/*
47402ac6454SAndrew Thompson 		 * Write data, setup and perform hardware transfer.
47502ac6454SAndrew Thompson 		 */
476a593f6b8SAndrew Thompson 		if (usb_fifo_get_data(f, xfer->frbuffers, 0,
47702ac6454SAndrew Thompson 		    xfer->max_data_length, &actlen, 0)) {
478ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, 0, actlen);
479a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
48002ac6454SAndrew Thompson 		}
48102ac6454SAndrew Thompson 		break;
48202ac6454SAndrew Thompson 
48302ac6454SAndrew Thompson 	default:			/* Error */
48402ac6454SAndrew Thompson 		if (xfer->error != USB_ERR_CANCELLED) {
48502ac6454SAndrew Thompson 			f->flag_stall = 1;
486a593f6b8SAndrew Thompson 			usbd_transfer_start(f->xfer[1]);
48702ac6454SAndrew Thompson 		}
48802ac6454SAndrew Thompson 		break;
48902ac6454SAndrew Thompson 	}
49002ac6454SAndrew Thompson }
49102ac6454SAndrew Thompson 
49202ac6454SAndrew Thompson static void
493ed6d949aSAndrew Thompson ugen_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
49402ac6454SAndrew Thompson {
495ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
496760bc48eSAndrew Thompson 	struct usb_xfer *xfer_other = f->xfer[0];
49702ac6454SAndrew Thompson 
49802ac6454SAndrew Thompson 	if (f->flag_stall == 0) {
49902ac6454SAndrew Thompson 		/* nothing to do */
50002ac6454SAndrew Thompson 		return;
50102ac6454SAndrew Thompson 	}
502a593f6b8SAndrew Thompson 	if (usbd_clear_stall_callback(xfer, xfer_other)) {
50302ac6454SAndrew Thompson 		DPRINTFN(5, "f=%p: stall cleared\n", f);
50402ac6454SAndrew Thompson 		f->flag_stall = 0;
505a593f6b8SAndrew Thompson 		usbd_transfer_start(xfer_other);
50602ac6454SAndrew Thompson 	}
50702ac6454SAndrew Thompson }
50802ac6454SAndrew Thompson 
50902ac6454SAndrew Thompson static void
510ed6d949aSAndrew Thompson ugen_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
51102ac6454SAndrew Thompson {
512ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
513760bc48eSAndrew Thompson 	struct usb_xfer *xfer_other = f->xfer[0];
51402ac6454SAndrew Thompson 
51502ac6454SAndrew Thompson 	if (f->flag_stall == 0) {
51602ac6454SAndrew Thompson 		/* nothing to do */
51702ac6454SAndrew Thompson 		return;
51802ac6454SAndrew Thompson 	}
519a593f6b8SAndrew Thompson 	if (usbd_clear_stall_callback(xfer, xfer_other)) {
52002ac6454SAndrew Thompson 		DPRINTFN(5, "f=%p: stall cleared\n", f);
52102ac6454SAndrew Thompson 		f->flag_stall = 0;
522a593f6b8SAndrew Thompson 		usbd_transfer_start(xfer_other);
52302ac6454SAndrew Thompson 	}
52402ac6454SAndrew Thompson }
52502ac6454SAndrew Thompson 
52602ac6454SAndrew Thompson static void
527ed6d949aSAndrew Thompson ugen_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error)
52802ac6454SAndrew Thompson {
529ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
530e0a69b51SAndrew Thompson 	usb_frlength_t offset;
531e0a69b51SAndrew Thompson 	usb_frcount_t n;
53202ac6454SAndrew Thompson 
53302ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
53402ac6454SAndrew Thompson 
53502ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
53602ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
53702ac6454SAndrew Thompson 
53802ac6454SAndrew Thompson 		DPRINTFN(6, "actlen=%d\n", xfer->actlen);
53902ac6454SAndrew Thompson 
54002ac6454SAndrew Thompson 		offset = 0;
54102ac6454SAndrew Thompson 
54202ac6454SAndrew Thompson 		for (n = 0; n != xfer->aframes; n++) {
543a593f6b8SAndrew Thompson 			usb_fifo_put_data(f, xfer->frbuffers, offset,
54402ac6454SAndrew Thompson 			    xfer->frlengths[n], 1);
54502ac6454SAndrew Thompson 			offset += xfer->max_frame_size;
54602ac6454SAndrew Thompson 		}
54702ac6454SAndrew Thompson 
54802ac6454SAndrew Thompson 	case USB_ST_SETUP:
54902ac6454SAndrew Thompson tr_setup:
55002ac6454SAndrew Thompson 		for (n = 0; n != xfer->nframes; n++) {
55102ac6454SAndrew Thompson 			/* setup size for next transfer */
552ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, xfer->max_frame_size);
55302ac6454SAndrew Thompson 		}
554a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
55502ac6454SAndrew Thompson 		break;
55602ac6454SAndrew Thompson 
55702ac6454SAndrew Thompson 	default:			/* Error */
55802ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
55902ac6454SAndrew Thompson 			break;
56002ac6454SAndrew Thompson 		}
56102ac6454SAndrew Thompson 		goto tr_setup;
56202ac6454SAndrew Thompson 	}
56302ac6454SAndrew Thompson }
56402ac6454SAndrew Thompson 
56502ac6454SAndrew Thompson static void
566ed6d949aSAndrew Thompson ugen_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error)
56702ac6454SAndrew Thompson {
568ed6d949aSAndrew Thompson 	struct usb_fifo *f = usbd_xfer_softc(xfer);
569e0a69b51SAndrew Thompson 	usb_frlength_t actlen;
570e0a69b51SAndrew Thompson 	usb_frlength_t offset;
571e0a69b51SAndrew Thompson 	usb_frcount_t n;
57202ac6454SAndrew Thompson 
57302ac6454SAndrew Thompson 	DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
57402ac6454SAndrew Thompson 
57502ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
57602ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
57702ac6454SAndrew Thompson 	case USB_ST_SETUP:
57802ac6454SAndrew Thompson tr_setup:
57902ac6454SAndrew Thompson 		offset = 0;
58002ac6454SAndrew Thompson 		for (n = 0; n != xfer->nframes; n++) {
581a593f6b8SAndrew Thompson 			if (usb_fifo_get_data(f, xfer->frbuffers, offset,
58202ac6454SAndrew Thompson 			    xfer->max_frame_size, &actlen, 1)) {
583ed6d949aSAndrew Thompson 				usbd_xfer_set_frame_len(xfer, n, actlen);
58402ac6454SAndrew Thompson 				offset += actlen;
58502ac6454SAndrew Thompson 			} else {
58602ac6454SAndrew Thompson 				break;
58702ac6454SAndrew Thompson 			}
58802ac6454SAndrew Thompson 		}
58902ac6454SAndrew Thompson 
59002ac6454SAndrew Thompson 		for (; n != xfer->nframes; n++) {
59102ac6454SAndrew Thompson 			/* fill in zero frames */
592ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, 0);
59302ac6454SAndrew Thompson 		}
594a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
59502ac6454SAndrew Thompson 		break;
59602ac6454SAndrew Thompson 
59702ac6454SAndrew Thompson 	default:			/* Error */
59802ac6454SAndrew Thompson 		if (xfer->error == USB_ERR_CANCELLED) {
59902ac6454SAndrew Thompson 			break;
60002ac6454SAndrew Thompson 		}
60102ac6454SAndrew Thompson 		goto tr_setup;
60202ac6454SAndrew Thompson 	}
60302ac6454SAndrew Thompson }
60402ac6454SAndrew Thompson 
60502ac6454SAndrew Thompson static int
606760bc48eSAndrew Thompson ugen_set_config(struct usb_fifo *f, uint8_t index)
60702ac6454SAndrew Thompson {
60802ac6454SAndrew Thompson 	DPRINTFN(2, "index %u\n", index);
60902ac6454SAndrew Thompson 
610f29a0724SAndrew Thompson 	if (f->udev->flags.usb_mode != USB_MODE_HOST) {
61102ac6454SAndrew Thompson 		/* not possible in device side mode */
61202ac6454SAndrew Thompson 		return (ENOTTY);
61302ac6454SAndrew Thompson 	}
61402ac6454SAndrew Thompson 	if (f->udev->curr_config_index == index) {
61502ac6454SAndrew Thompson 		/* no change needed */
61602ac6454SAndrew Thompson 		return (0);
61702ac6454SAndrew Thompson 	}
61802ac6454SAndrew Thompson 	/* make sure all FIFO's are gone */
61902ac6454SAndrew Thompson 	/* else there can be a deadlock */
62002ac6454SAndrew Thompson 	if (ugen_fs_uninit(f)) {
62102ac6454SAndrew Thompson 		/* ignore any errors */
62202ac6454SAndrew Thompson 		DPRINTFN(6, "no FIFOs\n");
62302ac6454SAndrew Thompson 	}
62402ac6454SAndrew Thompson 	/* change setting - will free generic FIFOs, if any */
625a593f6b8SAndrew Thompson 	if (usbd_set_config_index(f->udev, index)) {
62602ac6454SAndrew Thompson 		return (EIO);
62702ac6454SAndrew Thompson 	}
62802ac6454SAndrew Thompson 	/* probe and attach */
629a593f6b8SAndrew Thompson 	if (usb_probe_and_attach(f->udev, USB_IFACE_INDEX_ANY)) {
63002ac6454SAndrew Thompson 		return (EIO);
63102ac6454SAndrew Thompson 	}
63202ac6454SAndrew Thompson 	return (0);
63302ac6454SAndrew Thompson }
63402ac6454SAndrew Thompson 
63502ac6454SAndrew Thompson static int
636760bc48eSAndrew Thompson ugen_set_interface(struct usb_fifo *f,
63702ac6454SAndrew Thompson     uint8_t iface_index, uint8_t alt_index)
63802ac6454SAndrew Thompson {
63902ac6454SAndrew Thompson 	DPRINTFN(2, "%u, %u\n", iface_index, alt_index);
64002ac6454SAndrew Thompson 
641f29a0724SAndrew Thompson 	if (f->udev->flags.usb_mode != USB_MODE_HOST) {
64202ac6454SAndrew Thompson 		/* not possible in device side mode */
64302ac6454SAndrew Thompson 		return (ENOTTY);
64402ac6454SAndrew Thompson 	}
64502ac6454SAndrew Thompson 	/* make sure all FIFO's are gone */
64602ac6454SAndrew Thompson 	/* else there can be a deadlock */
64702ac6454SAndrew Thompson 	if (ugen_fs_uninit(f)) {
64802ac6454SAndrew Thompson 		/* ignore any errors */
64902ac6454SAndrew Thompson 		DPRINTFN(6, "no FIFOs\n");
65002ac6454SAndrew Thompson 	}
65102ac6454SAndrew Thompson 	/* change setting - will free generic FIFOs, if any */
652a593f6b8SAndrew Thompson 	if (usbd_set_alt_interface_index(f->udev, iface_index, alt_index)) {
65302ac6454SAndrew Thompson 		return (EIO);
65402ac6454SAndrew Thompson 	}
65502ac6454SAndrew Thompson 	/* probe and attach */
656a593f6b8SAndrew Thompson 	if (usb_probe_and_attach(f->udev, iface_index)) {
65702ac6454SAndrew Thompson 		return (EIO);
65802ac6454SAndrew Thompson 	}
65902ac6454SAndrew Thompson 	return (0);
66002ac6454SAndrew Thompson }
66102ac6454SAndrew Thompson 
66202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
66302ac6454SAndrew Thompson  *	ugen_get_cdesc
66402ac6454SAndrew Thompson  *
66502ac6454SAndrew Thompson  * This function will retrieve the complete configuration descriptor
66602ac6454SAndrew Thompson  * at the given index.
66702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
66802ac6454SAndrew Thompson static int
669760bc48eSAndrew Thompson ugen_get_cdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
67002ac6454SAndrew Thompson {
671760bc48eSAndrew Thompson 	struct usb_config_descriptor *cdesc;
672760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
67302ac6454SAndrew Thompson 	int error;
67402ac6454SAndrew Thompson 	uint16_t len;
67502ac6454SAndrew Thompson 	uint8_t free_data;
67602ac6454SAndrew Thompson 
67702ac6454SAndrew Thompson 	DPRINTFN(6, "\n");
67802ac6454SAndrew Thompson 
67902ac6454SAndrew Thompson 	if (ugd->ugd_data == NULL) {
68002ac6454SAndrew Thompson 		/* userland pointer should not be zero */
68102ac6454SAndrew Thompson 		return (EINVAL);
68202ac6454SAndrew Thompson 	}
68302ac6454SAndrew Thompson 	if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) ||
68402ac6454SAndrew Thompson 	    (ugd->ugd_config_index == udev->curr_config_index)) {
685a593f6b8SAndrew Thompson 		cdesc = usbd_get_config_descriptor(udev);
68602ac6454SAndrew Thompson 		if (cdesc == NULL) {
68702ac6454SAndrew Thompson 			return (ENXIO);
68802ac6454SAndrew Thompson 		}
68902ac6454SAndrew Thompson 		free_data = 0;
69002ac6454SAndrew Thompson 
69102ac6454SAndrew Thompson 	} else {
692a593f6b8SAndrew Thompson 		if (usbd_req_get_config_desc_full(udev,
693e2805033SAndrew Thompson 		    NULL, &cdesc, M_USBDEV,
69402ac6454SAndrew Thompson 		    ugd->ugd_config_index)) {
69502ac6454SAndrew Thompson 			return (ENXIO);
69602ac6454SAndrew Thompson 		}
69702ac6454SAndrew Thompson 		free_data = 1;
69802ac6454SAndrew Thompson 	}
69902ac6454SAndrew Thompson 
70002ac6454SAndrew Thompson 	len = UGETW(cdesc->wTotalLength);
70102ac6454SAndrew Thompson 	if (len > ugd->ugd_maxlen) {
70202ac6454SAndrew Thompson 		len = ugd->ugd_maxlen;
70302ac6454SAndrew Thompson 	}
70402ac6454SAndrew Thompson 	DPRINTFN(6, "len=%u\n", len);
70502ac6454SAndrew Thompson 
70602ac6454SAndrew Thompson 	ugd->ugd_actlen = len;
70702ac6454SAndrew Thompson 	ugd->ugd_offset = 0;
70802ac6454SAndrew Thompson 
70902ac6454SAndrew Thompson 	error = copyout(cdesc, ugd->ugd_data, len);
71002ac6454SAndrew Thompson 
71102ac6454SAndrew Thompson 	if (free_data) {
71202ac6454SAndrew Thompson 		free(cdesc, M_USBDEV);
71302ac6454SAndrew Thompson 	}
71402ac6454SAndrew Thompson 	return (error);
71502ac6454SAndrew Thompson }
71602ac6454SAndrew Thompson 
71702ac6454SAndrew Thompson static int
718760bc48eSAndrew Thompson ugen_get_sdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
71902ac6454SAndrew Thompson {
72002ac6454SAndrew Thompson 	void *ptr = f->udev->bus->scratch[0].data;
72102ac6454SAndrew Thompson 	uint16_t size = sizeof(f->udev->bus->scratch[0].data);
72202ac6454SAndrew Thompson 	int error;
72302ac6454SAndrew Thompson 
724a593f6b8SAndrew Thompson 	if (usbd_req_get_string_desc(f->udev, NULL, ptr,
72502ac6454SAndrew Thompson 	    size, ugd->ugd_lang_id, ugd->ugd_string_index)) {
72602ac6454SAndrew Thompson 		error = EINVAL;
72702ac6454SAndrew Thompson 	} else {
72802ac6454SAndrew Thompson 
72902ac6454SAndrew Thompson 		if (size > ((uint8_t *)ptr)[0]) {
73002ac6454SAndrew Thompson 			size = ((uint8_t *)ptr)[0];
73102ac6454SAndrew Thompson 		}
73202ac6454SAndrew Thompson 		if (size > ugd->ugd_maxlen) {
73302ac6454SAndrew Thompson 			size = ugd->ugd_maxlen;
73402ac6454SAndrew Thompson 		}
73502ac6454SAndrew Thompson 		ugd->ugd_actlen = size;
73602ac6454SAndrew Thompson 		ugd->ugd_offset = 0;
73702ac6454SAndrew Thompson 
73802ac6454SAndrew Thompson 		error = copyout(ptr, ugd->ugd_data, size);
73902ac6454SAndrew Thompson 	}
74002ac6454SAndrew Thompson 	return (error);
74102ac6454SAndrew Thompson }
74202ac6454SAndrew Thompson 
74302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
74402ac6454SAndrew Thompson  *	ugen_get_iface_driver
74502ac6454SAndrew Thompson  *
74602ac6454SAndrew Thompson  * This function generates an USB interface description for userland.
74702ac6454SAndrew Thompson  *
74802ac6454SAndrew Thompson  * Returns:
74902ac6454SAndrew Thompson  *    0: Success
75002ac6454SAndrew Thompson  * Else: Failure
75102ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
75202ac6454SAndrew Thompson static int
753760bc48eSAndrew Thompson ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
75402ac6454SAndrew Thompson {
755760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
756760bc48eSAndrew Thompson 	struct usb_interface *iface;
75702ac6454SAndrew Thompson 	const char *ptr;
75802ac6454SAndrew Thompson 	const char *desc;
75902ac6454SAndrew Thompson 	unsigned int len;
76002ac6454SAndrew Thompson 	unsigned int maxlen;
76102ac6454SAndrew Thompson 	char buf[128];
76202ac6454SAndrew Thompson 	int error;
76302ac6454SAndrew Thompson 
76402ac6454SAndrew Thompson 	DPRINTFN(6, "\n");
76502ac6454SAndrew Thompson 
76602ac6454SAndrew Thompson 	if ((ugd->ugd_data == NULL) || (ugd->ugd_maxlen == 0)) {
76702ac6454SAndrew Thompson 		/* userland pointer should not be zero */
76802ac6454SAndrew Thompson 		return (EINVAL);
76902ac6454SAndrew Thompson 	}
77002ac6454SAndrew Thompson 
771a593f6b8SAndrew Thompson 	iface = usbd_get_iface(udev, ugd->ugd_iface_index);
77202ac6454SAndrew Thompson 	if ((iface == NULL) || (iface->idesc == NULL)) {
77302ac6454SAndrew Thompson 		/* invalid interface index */
77402ac6454SAndrew Thompson 		return (EINVAL);
77502ac6454SAndrew Thompson 	}
77602ac6454SAndrew Thompson 
77702ac6454SAndrew Thompson 	/* read out device nameunit string, if any */
77802ac6454SAndrew Thompson 	if ((iface->subdev != NULL) &&
77902ac6454SAndrew Thompson 	    device_is_attached(iface->subdev) &&
78002ac6454SAndrew Thompson 	    (ptr = device_get_nameunit(iface->subdev)) &&
78102ac6454SAndrew Thompson 	    (desc = device_get_desc(iface->subdev))) {
78202ac6454SAndrew Thompson 
78302ac6454SAndrew Thompson 		/* print description */
78402ac6454SAndrew Thompson 		snprintf(buf, sizeof(buf), "%s: <%s>", ptr, desc);
78502ac6454SAndrew Thompson 
78602ac6454SAndrew Thompson 		/* range checks */
78702ac6454SAndrew Thompson 		maxlen = ugd->ugd_maxlen - 1;
78802ac6454SAndrew Thompson 		len = strlen(buf);
78902ac6454SAndrew Thompson 		if (len > maxlen)
79002ac6454SAndrew Thompson 			len = maxlen;
79102ac6454SAndrew Thompson 
79202ac6454SAndrew Thompson 		/* update actual length, including terminating zero */
79302ac6454SAndrew Thompson 		ugd->ugd_actlen = len + 1;
79402ac6454SAndrew Thompson 
79502ac6454SAndrew Thompson 		/* copy out interface description */
79602ac6454SAndrew Thompson 		error = copyout(buf, ugd->ugd_data, ugd->ugd_actlen);
79702ac6454SAndrew Thompson 	} else {
79802ac6454SAndrew Thompson 		/* zero length string is default */
79902ac6454SAndrew Thompson 		error = copyout("", ugd->ugd_data, 1);
80002ac6454SAndrew Thompson 	}
80102ac6454SAndrew Thompson 	return (error);
80202ac6454SAndrew Thompson }
80302ac6454SAndrew Thompson 
80402ac6454SAndrew Thompson /*------------------------------------------------------------------------*
805a593f6b8SAndrew Thompson  *	usb_gen_fill_deviceinfo
80602ac6454SAndrew Thompson  *
80702ac6454SAndrew Thompson  * This function dumps information about an USB device to the
80802ac6454SAndrew Thompson  * structure pointed to by the "di" argument.
80902ac6454SAndrew Thompson  *
81002ac6454SAndrew Thompson  * Returns:
81102ac6454SAndrew Thompson  *    0: Success
81202ac6454SAndrew Thompson  * Else: Failure
81302ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
81402ac6454SAndrew Thompson static int
815a593f6b8SAndrew Thompson usb_gen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di)
81602ac6454SAndrew Thompson {
817760bc48eSAndrew Thompson 	struct usb_device *udev;
818760bc48eSAndrew Thompson 	struct usb_device *hub;
81902ac6454SAndrew Thompson 
82002ac6454SAndrew Thompson 	udev = f->udev;
82102ac6454SAndrew Thompson 
82202ac6454SAndrew Thompson 	bzero(di, sizeof(di[0]));
82302ac6454SAndrew Thompson 
82402ac6454SAndrew Thompson 	di->udi_bus = device_get_unit(udev->bus->bdev);
82502ac6454SAndrew Thompson 	di->udi_addr = udev->address;
82602ac6454SAndrew Thompson 	di->udi_index = udev->device_index;
827ae538d85SAndrew Thompson 	strlcpy(di->udi_serial, usb_get_serial(udev), sizeof(di->udi_serial));
828ae538d85SAndrew Thompson 	strlcpy(di->udi_vendor, usb_get_manufacturer(udev), sizeof(di->udi_vendor));
829ae538d85SAndrew Thompson 	strlcpy(di->udi_product, usb_get_product(udev), sizeof(di->udi_product));
830a593f6b8SAndrew Thompson 	usb_printbcd(di->udi_release, sizeof(di->udi_release),
83102ac6454SAndrew Thompson 	    UGETW(udev->ddesc.bcdDevice));
83202ac6454SAndrew Thompson 	di->udi_vendorNo = UGETW(udev->ddesc.idVendor);
83302ac6454SAndrew Thompson 	di->udi_productNo = UGETW(udev->ddesc.idProduct);
83402ac6454SAndrew Thompson 	di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice);
83502ac6454SAndrew Thompson 	di->udi_class = udev->ddesc.bDeviceClass;
83602ac6454SAndrew Thompson 	di->udi_subclass = udev->ddesc.bDeviceSubClass;
83702ac6454SAndrew Thompson 	di->udi_protocol = udev->ddesc.bDeviceProtocol;
83802ac6454SAndrew Thompson 	di->udi_config_no = udev->curr_config_no;
83902ac6454SAndrew Thompson 	di->udi_config_index = udev->curr_config_index;
84002ac6454SAndrew Thompson 	di->udi_power = udev->flags.self_powered ? 0 : udev->power;
84102ac6454SAndrew Thompson 	di->udi_speed = udev->speed;
842f29a0724SAndrew Thompson 	di->udi_mode = udev->flags.usb_mode;
84302ac6454SAndrew Thompson 	di->udi_power_mode = udev->power_mode;
844ec8f3127SAndrew Thompson 	di->udi_suspended = udev->flags.peer_suspended;
84502ac6454SAndrew Thompson 
84602ac6454SAndrew Thompson 	hub = udev->parent_hub;
84702ac6454SAndrew Thompson 	if (hub) {
84802ac6454SAndrew Thompson 		di->udi_hubaddr = hub->address;
84902ac6454SAndrew Thompson 		di->udi_hubindex = hub->device_index;
85002ac6454SAndrew Thompson 		di->udi_hubport = udev->port_no;
85102ac6454SAndrew Thompson 	}
85202ac6454SAndrew Thompson 	return (0);
85302ac6454SAndrew Thompson }
85402ac6454SAndrew Thompson 
85502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
85602ac6454SAndrew Thompson  *	ugen_check_request
85702ac6454SAndrew Thompson  *
85802ac6454SAndrew Thompson  * Return values:
85902ac6454SAndrew Thompson  * 0: Access allowed
86002ac6454SAndrew Thompson  * Else: No access
86102ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
86202ac6454SAndrew Thompson static int
863760bc48eSAndrew Thompson ugen_check_request(struct usb_device *udev, struct usb_device_request *req)
86402ac6454SAndrew Thompson {
865ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
86602ac6454SAndrew Thompson 	int error;
86702ac6454SAndrew Thompson 
86802ac6454SAndrew Thompson 	/*
86902ac6454SAndrew Thompson 	 * Avoid requests that would damage the bus integrity:
87002ac6454SAndrew Thompson 	 */
87102ac6454SAndrew Thompson 	if (((req->bmRequestType == UT_WRITE_DEVICE) &&
87202ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_ADDRESS)) ||
87302ac6454SAndrew Thompson 	    ((req->bmRequestType == UT_WRITE_DEVICE) &&
87402ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_CONFIG)) ||
87502ac6454SAndrew Thompson 	    ((req->bmRequestType == UT_WRITE_INTERFACE) &&
87602ac6454SAndrew Thompson 	    (req->bRequest == UR_SET_INTERFACE))) {
87702ac6454SAndrew Thompson 		/*
87802ac6454SAndrew Thompson 		 * These requests can be useful for testing USB drivers.
87902ac6454SAndrew Thompson 		 */
88002ac6454SAndrew Thompson 		error = priv_check(curthread, PRIV_DRIVER);
88102ac6454SAndrew Thompson 		if (error) {
88202ac6454SAndrew Thompson 			return (error);
88302ac6454SAndrew Thompson 		}
88402ac6454SAndrew Thompson 	}
88502ac6454SAndrew Thompson 	/*
88602ac6454SAndrew Thompson 	 * Special case - handle clearing of stall
88702ac6454SAndrew Thompson 	 */
88802ac6454SAndrew Thompson 	if (req->bmRequestType == UT_WRITE_ENDPOINT) {
88902ac6454SAndrew Thompson 
890a593f6b8SAndrew Thompson 		ep = usbd_get_ep_by_addr(udev, req->wIndex[0]);
891ae60fdfbSAndrew Thompson 		if (ep == NULL) {
89202ac6454SAndrew Thompson 			return (EINVAL);
89302ac6454SAndrew Thompson 		}
89402ac6454SAndrew Thompson 		if ((req->bRequest == UR_CLEAR_FEATURE) &&
89502ac6454SAndrew Thompson 		    (UGETW(req->wValue) == UF_ENDPOINT_HALT)) {
896a593f6b8SAndrew Thompson 			usbd_clear_data_toggle(udev, ep);
89702ac6454SAndrew Thompson 		}
89802ac6454SAndrew Thompson 	}
89902ac6454SAndrew Thompson 	/* TODO: add more checks to verify the interface index */
90002ac6454SAndrew Thompson 
90102ac6454SAndrew Thompson 	return (0);
90202ac6454SAndrew Thompson }
90302ac6454SAndrew Thompson 
90402ac6454SAndrew Thompson int
905760bc48eSAndrew Thompson ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur)
90602ac6454SAndrew Thompson {
90702ac6454SAndrew Thompson 	int error;
90802ac6454SAndrew Thompson 	uint16_t len;
90902ac6454SAndrew Thompson 	uint16_t actlen;
91002ac6454SAndrew Thompson 
91102ac6454SAndrew Thompson 	if (ugen_check_request(f->udev, &ur->ucr_request)) {
91202ac6454SAndrew Thompson 		return (EPERM);
91302ac6454SAndrew Thompson 	}
91402ac6454SAndrew Thompson 	len = UGETW(ur->ucr_request.wLength);
91502ac6454SAndrew Thompson 
91602ac6454SAndrew Thompson 	/* check if "ucr_data" is valid */
91702ac6454SAndrew Thompson 	if (len != 0) {
91802ac6454SAndrew Thompson 		if (ur->ucr_data == NULL) {
91902ac6454SAndrew Thompson 			return (EFAULT);
92002ac6454SAndrew Thompson 		}
92102ac6454SAndrew Thompson 	}
92202ac6454SAndrew Thompson 	/* do the USB request */
923a593f6b8SAndrew Thompson 	error = usbd_do_request_flags
92402ac6454SAndrew Thompson 	    (f->udev, NULL, &ur->ucr_request, ur->ucr_data,
92502ac6454SAndrew Thompson 	    (ur->ucr_flags & USB_SHORT_XFER_OK) |
92602ac6454SAndrew Thompson 	    USB_USER_DATA_PTR, &actlen,
92702ac6454SAndrew Thompson 	    USB_DEFAULT_TIMEOUT);
92802ac6454SAndrew Thompson 
92902ac6454SAndrew Thompson 	ur->ucr_actlen = actlen;
93002ac6454SAndrew Thompson 
93102ac6454SAndrew Thompson 	if (error) {
93202ac6454SAndrew Thompson 		error = EIO;
93302ac6454SAndrew Thompson 	}
93402ac6454SAndrew Thompson 	return (error);
93502ac6454SAndrew Thompson }
93602ac6454SAndrew Thompson 
93702ac6454SAndrew Thompson /*------------------------------------------------------------------------
93802ac6454SAndrew Thompson  *	ugen_re_enumerate
93902ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
94002ac6454SAndrew Thompson static int
941760bc48eSAndrew Thompson ugen_re_enumerate(struct usb_fifo *f)
94202ac6454SAndrew Thompson {
943760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
94402ac6454SAndrew Thompson 	int error;
94502ac6454SAndrew Thompson 
94602ac6454SAndrew Thompson 	/*
94702ac6454SAndrew Thompson 	 * This request can be useful for testing USB drivers:
94802ac6454SAndrew Thompson 	 */
94902ac6454SAndrew Thompson 	error = priv_check(curthread, PRIV_DRIVER);
95002ac6454SAndrew Thompson 	if (error) {
95102ac6454SAndrew Thompson 		return (error);
95202ac6454SAndrew Thompson 	}
9538f9750b7SHans Petter Selasky 	if (udev->flags.usb_mode != USB_MODE_HOST) {
9548f9750b7SHans Petter Selasky 		/* not possible in device side mode */
95556b57046SHans Petter Selasky 		DPRINTFN(6, "device mode\n");
9568f9750b7SHans Petter Selasky 		return (ENOTTY);
95702ac6454SAndrew Thompson 	}
95856b57046SHans Petter Selasky 	if (udev->parent_hub == NULL) {
95956b57046SHans Petter Selasky 		/* the root HUB cannot be re-enumerated */
96056b57046SHans Petter Selasky 		DPRINTFN(6, "cannot reset root HUB\n");
96156b57046SHans Petter Selasky 		return (EINVAL);
96256b57046SHans Petter Selasky 	}
9638f9750b7SHans Petter Selasky 	/* make sure all FIFO's are gone */
9648f9750b7SHans Petter Selasky 	/* else there can be a deadlock */
9658f9750b7SHans Petter Selasky 	if (ugen_fs_uninit(f)) {
9668f9750b7SHans Petter Selasky 		/* ignore any errors */
9678f9750b7SHans Petter Selasky 		DPRINTFN(6, "no FIFOs\n");
96802ac6454SAndrew Thompson 	}
9698f9750b7SHans Petter Selasky 	if (udev->re_enumerate_wait == 0) {
9708f9750b7SHans Petter Selasky 		udev->re_enumerate_wait = 1;
9718f9750b7SHans Petter Selasky 		usb_needs_explore(udev->bus, 0);
97202ac6454SAndrew Thompson 	}
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;
1396760bc48eSAndrew Thompson 		struct usb_fs_close *pclose;
1397760bc48eSAndrew Thompson 		struct usb_fs_clear_stall_sync *pstall;
139802ac6454SAndrew Thompson 		void   *addr;
139902ac6454SAndrew Thompson 	}     u;
1400ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
1401760bc48eSAndrew Thompson 	struct usb_endpoint_descriptor *ed;
140202ac6454SAndrew Thompson 	int error = 0;
140302ac6454SAndrew Thompson 	uint8_t iface_index;
140402ac6454SAndrew Thompson 	uint8_t isread;
140502ac6454SAndrew Thompson 	uint8_t ep_index;
14061c497368SHans Petter Selasky 	uint8_t pre_scale;
140702ac6454SAndrew Thompson 
140802ac6454SAndrew Thompson 	u.addr = addr;
140902ac6454SAndrew Thompson 
141002ac6454SAndrew Thompson 	DPRINTFN(6, "cmd=0x%08lx\n", cmd);
141102ac6454SAndrew Thompson 
141202ac6454SAndrew Thompson 	switch (cmd) {
141302ac6454SAndrew Thompson 	case USB_FS_COMPLETE:
141402ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
141502ac6454SAndrew Thompson 		error = ugen_fs_get_complete(f, &ep_index);
141602ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
141702ac6454SAndrew Thompson 
141802ac6454SAndrew Thompson 		if (error) {
141902ac6454SAndrew Thompson 			error = EBUSY;
142002ac6454SAndrew Thompson 			break;
142102ac6454SAndrew Thompson 		}
142202ac6454SAndrew Thompson 		u.pcomp->ep_index = ep_index;
142302ac6454SAndrew Thompson 		error = ugen_fs_copy_out(f, u.pcomp->ep_index);
142402ac6454SAndrew Thompson 		break;
142502ac6454SAndrew Thompson 
142602ac6454SAndrew Thompson 	case USB_FS_START:
142702ac6454SAndrew Thompson 		error = ugen_fs_copy_in(f, u.pstart->ep_index);
142802ac6454SAndrew Thompson 		if (error) {
142902ac6454SAndrew Thompson 			break;
143002ac6454SAndrew Thompson 		}
143102ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
1432a593f6b8SAndrew Thompson 		usbd_transfer_start(f->fs_xfer[u.pstart->ep_index]);
143302ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
143402ac6454SAndrew Thompson 		break;
143502ac6454SAndrew Thompson 
143602ac6454SAndrew Thompson 	case USB_FS_STOP:
143702ac6454SAndrew Thompson 		if (u.pstop->ep_index >= f->fs_ep_max) {
143802ac6454SAndrew Thompson 			error = EINVAL;
143902ac6454SAndrew Thompson 			break;
144002ac6454SAndrew Thompson 		}
144102ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
1442a593f6b8SAndrew Thompson 		usbd_transfer_stop(f->fs_xfer[u.pstop->ep_index]);
144302ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
144402ac6454SAndrew Thompson 		break;
144502ac6454SAndrew Thompson 
144602ac6454SAndrew Thompson 	case USB_FS_OPEN:
144702ac6454SAndrew Thompson 		if (u.popen->ep_index >= f->fs_ep_max) {
144802ac6454SAndrew Thompson 			error = EINVAL;
144902ac6454SAndrew Thompson 			break;
145002ac6454SAndrew Thompson 		}
145102ac6454SAndrew Thompson 		if (f->fs_xfer[u.popen->ep_index] != NULL) {
145202ac6454SAndrew Thompson 			error = EBUSY;
145302ac6454SAndrew Thompson 			break;
145402ac6454SAndrew Thompson 		}
145502ac6454SAndrew Thompson 		if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) {
145602ac6454SAndrew Thompson 			u.popen->max_bufsize = USB_FS_MAX_BUFSIZE;
145702ac6454SAndrew Thompson 		}
14581c497368SHans Petter Selasky 		if (u.popen->max_frames & USB_FS_MAX_FRAMES_PRE_SCALE) {
14591c497368SHans Petter Selasky 			pre_scale = 1;
14601c497368SHans Petter Selasky 			u.popen->max_frames &= ~USB_FS_MAX_FRAMES_PRE_SCALE;
14611c497368SHans Petter Selasky 		} else {
14621c497368SHans Petter Selasky 			pre_scale = 0;
14631c497368SHans Petter Selasky 		}
146402ac6454SAndrew Thompson 		if (u.popen->max_frames > USB_FS_MAX_FRAMES) {
146502ac6454SAndrew Thompson 			u.popen->max_frames = USB_FS_MAX_FRAMES;
146602ac6454SAndrew Thompson 			break;
146702ac6454SAndrew Thompson 		}
146802ac6454SAndrew Thompson 		if (u.popen->max_frames == 0) {
146902ac6454SAndrew Thompson 			error = EINVAL;
147002ac6454SAndrew Thompson 			break;
147102ac6454SAndrew Thompson 		}
1472a593f6b8SAndrew Thompson 		ep = usbd_get_ep_by_addr(f->udev, u.popen->ep_no);
1473ae60fdfbSAndrew Thompson 		if (ep == NULL) {
147402ac6454SAndrew Thompson 			error = EINVAL;
147502ac6454SAndrew Thompson 			break;
147602ac6454SAndrew Thompson 		}
1477ae60fdfbSAndrew Thompson 		ed = ep->edesc;
147802ac6454SAndrew Thompson 		if (ed == NULL) {
147902ac6454SAndrew Thompson 			error = ENXIO;
148002ac6454SAndrew Thompson 			break;
148102ac6454SAndrew Thompson 		}
1482ae60fdfbSAndrew Thompson 		iface_index = ep->iface_index;
148302ac6454SAndrew Thompson 
14841c497368SHans Petter Selasky 		memset(usb_config, 0, sizeof(usb_config));
148502ac6454SAndrew Thompson 
1486760bc48eSAndrew Thompson 		usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
1487760bc48eSAndrew Thompson 		usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
1488760bc48eSAndrew Thompson 		usb_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN);
1489760bc48eSAndrew Thompson 		usb_config[0].interval = USB_DEFAULT_INTERVAL;
1490760bc48eSAndrew Thompson 		usb_config[0].flags.proxy_buffer = 1;
14911c497368SHans Petter Selasky 		if (pre_scale != 0)
14921c497368SHans Petter Selasky 			usb_config[0].flags.pre_scale_frames = 1;
14935b3bb704SAndrew Thompson 		usb_config[0].callback = &ugen_ctrl_fs_callback;
1494760bc48eSAndrew Thompson 		usb_config[0].timeout = 0;	/* no timeout */
1495760bc48eSAndrew Thompson 		usb_config[0].frames = u.popen->max_frames;
1496760bc48eSAndrew Thompson 		usb_config[0].bufsize = u.popen->max_bufsize;
1497760bc48eSAndrew Thompson 		usb_config[0].usb_mode = USB_MODE_DUAL;	/* both modes */
149802ac6454SAndrew Thompson 
1499760bc48eSAndrew Thompson 		if (usb_config[0].type == UE_CONTROL) {
1500f29a0724SAndrew Thompson 			if (f->udev->flags.usb_mode != USB_MODE_HOST) {
150102ac6454SAndrew Thompson 				error = EINVAL;
150202ac6454SAndrew Thompson 				break;
150302ac6454SAndrew Thompson 			}
150402ac6454SAndrew Thompson 		} else {
150502ac6454SAndrew Thompson 
1506760bc48eSAndrew Thompson 			isread = ((usb_config[0].endpoint &
150702ac6454SAndrew Thompson 			    (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN);
150802ac6454SAndrew Thompson 
1509f29a0724SAndrew Thompson 			if (f->udev->flags.usb_mode != USB_MODE_HOST) {
151002ac6454SAndrew Thompson 				isread = !isread;
151102ac6454SAndrew Thompson 			}
151202ac6454SAndrew Thompson 			/* check permissions */
151302ac6454SAndrew Thompson 			if (isread) {
151402ac6454SAndrew Thompson 				if (!(fflags & FREAD)) {
151502ac6454SAndrew Thompson 					error = EPERM;
151602ac6454SAndrew Thompson 					break;
151702ac6454SAndrew Thompson 				}
151802ac6454SAndrew Thompson 			} else {
151902ac6454SAndrew Thompson 				if (!(fflags & FWRITE)) {
152002ac6454SAndrew Thompson 					error = EPERM;
152102ac6454SAndrew Thompson 					break;
152202ac6454SAndrew Thompson 				}
152302ac6454SAndrew Thompson 			}
152402ac6454SAndrew Thompson 		}
1525a593f6b8SAndrew Thompson 		error = usbd_transfer_setup(f->udev, &iface_index,
1526760bc48eSAndrew Thompson 		    f->fs_xfer + u.popen->ep_index, usb_config, 1,
152702ac6454SAndrew Thompson 		    f, f->priv_mtx);
152802ac6454SAndrew Thompson 		if (error == 0) {
152902ac6454SAndrew Thompson 			/* update maximums */
153002ac6454SAndrew Thompson 			u.popen->max_packet_length =
153102ac6454SAndrew Thompson 			    f->fs_xfer[u.popen->ep_index]->max_frame_size;
153202ac6454SAndrew Thompson 			u.popen->max_bufsize =
153302ac6454SAndrew Thompson 			    f->fs_xfer[u.popen->ep_index]->max_data_length;
15341c497368SHans Petter Selasky 			/* update number of frames */
15351c497368SHans Petter Selasky 			u.popen->max_frames =
15361c497368SHans Petter Selasky 			    f->fs_xfer[u.popen->ep_index]->nframes;
15371c497368SHans Petter Selasky 			/* store index of endpoint */
153802ac6454SAndrew Thompson 			f->fs_xfer[u.popen->ep_index]->priv_fifo =
153902ac6454SAndrew Thompson 			    ((uint8_t *)0) + u.popen->ep_index;
154002ac6454SAndrew Thompson 		} else {
154102ac6454SAndrew Thompson 			error = ENOMEM;
154202ac6454SAndrew Thompson 		}
154302ac6454SAndrew Thompson 		break;
154402ac6454SAndrew Thompson 
154502ac6454SAndrew Thompson 	case USB_FS_CLOSE:
154602ac6454SAndrew Thompson 		if (u.pclose->ep_index >= f->fs_ep_max) {
154702ac6454SAndrew Thompson 			error = EINVAL;
154802ac6454SAndrew Thompson 			break;
154902ac6454SAndrew Thompson 		}
155002ac6454SAndrew Thompson 		if (f->fs_xfer[u.pclose->ep_index] == NULL) {
155102ac6454SAndrew Thompson 			error = EINVAL;
155202ac6454SAndrew Thompson 			break;
155302ac6454SAndrew Thompson 		}
1554a593f6b8SAndrew Thompson 		usbd_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1);
155502ac6454SAndrew Thompson 		break;
155602ac6454SAndrew Thompson 
155702ac6454SAndrew Thompson 	case USB_FS_CLEAR_STALL_SYNC:
155802ac6454SAndrew Thompson 		if (u.pstall->ep_index >= f->fs_ep_max) {
155902ac6454SAndrew Thompson 			error = EINVAL;
156002ac6454SAndrew Thompson 			break;
156102ac6454SAndrew Thompson 		}
156202ac6454SAndrew Thompson 		if (f->fs_xfer[u.pstall->ep_index] == NULL) {
156302ac6454SAndrew Thompson 			error = EINVAL;
156402ac6454SAndrew Thompson 			break;
156502ac6454SAndrew Thompson 		}
1566f29a0724SAndrew Thompson 		if (f->udev->flags.usb_mode != USB_MODE_HOST) {
156702ac6454SAndrew Thompson 			error = EINVAL;
156802ac6454SAndrew Thompson 			break;
156902ac6454SAndrew Thompson 		}
157002ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
1571a593f6b8SAndrew Thompson 		error = usbd_transfer_pending(f->fs_xfer[u.pstall->ep_index]);
157202ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
157302ac6454SAndrew Thompson 
157402ac6454SAndrew Thompson 		if (error) {
157502ac6454SAndrew Thompson 			return (EBUSY);
157602ac6454SAndrew Thompson 		}
1577ae60fdfbSAndrew Thompson 		ep = f->fs_xfer[u.pstall->ep_index]->endpoint;
157802ac6454SAndrew Thompson 
157902ac6454SAndrew Thompson 		/* setup a clear-stall packet */
158002ac6454SAndrew Thompson 		req.bmRequestType = UT_WRITE_ENDPOINT;
158102ac6454SAndrew Thompson 		req.bRequest = UR_CLEAR_FEATURE;
158202ac6454SAndrew Thompson 		USETW(req.wValue, UF_ENDPOINT_HALT);
1583ae60fdfbSAndrew Thompson 		req.wIndex[0] = ep->edesc->bEndpointAddress;
158402ac6454SAndrew Thompson 		req.wIndex[1] = 0;
158502ac6454SAndrew Thompson 		USETW(req.wLength, 0);
158602ac6454SAndrew Thompson 
1587a593f6b8SAndrew Thompson 		error = usbd_do_request(f->udev, NULL, &req, NULL);
158802ac6454SAndrew Thompson 		if (error == 0) {
1589a593f6b8SAndrew Thompson 			usbd_clear_data_toggle(f->udev, ep);
159002ac6454SAndrew Thompson 		} else {
159102ac6454SAndrew Thompson 			error = ENXIO;
159202ac6454SAndrew Thompson 		}
159302ac6454SAndrew Thompson 		break;
159402ac6454SAndrew Thompson 
159502ac6454SAndrew Thompson 	default:
159602ac6454SAndrew Thompson 		error = ENOIOCTL;
159702ac6454SAndrew Thompson 		break;
159802ac6454SAndrew Thompson 	}
159902ac6454SAndrew Thompson 
160002ac6454SAndrew Thompson 	DPRINTFN(6, "error=%d\n", error);
160102ac6454SAndrew Thompson 
160202ac6454SAndrew Thompson 	return (error);
160302ac6454SAndrew Thompson }
160402ac6454SAndrew Thompson 
160502ac6454SAndrew Thompson static int
1606760bc48eSAndrew Thompson ugen_set_short_xfer(struct usb_fifo *f, void *addr)
160702ac6454SAndrew Thompson {
160802ac6454SAndrew Thompson 	uint8_t t;
160902ac6454SAndrew Thompson 
161002ac6454SAndrew Thompson 	if (*(int *)addr)
161102ac6454SAndrew Thompson 		t = 1;
161202ac6454SAndrew Thompson 	else
161302ac6454SAndrew Thompson 		t = 0;
161402ac6454SAndrew Thompson 
161502ac6454SAndrew Thompson 	if (f->flag_short == t) {
161602ac6454SAndrew Thompson 		/* same value like before - accept */
161702ac6454SAndrew Thompson 		return (0);
161802ac6454SAndrew Thompson 	}
161902ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
162002ac6454SAndrew Thompson 		/* cannot change this during transfer */
162102ac6454SAndrew Thompson 		return (EBUSY);
162202ac6454SAndrew Thompson 	}
162302ac6454SAndrew Thompson 	f->flag_short = t;
162402ac6454SAndrew Thompson 	return (0);
162502ac6454SAndrew Thompson }
162602ac6454SAndrew Thompson 
162702ac6454SAndrew Thompson static int
1628760bc48eSAndrew Thompson ugen_set_timeout(struct usb_fifo *f, void *addr)
162902ac6454SAndrew Thompson {
163002ac6454SAndrew Thompson 	f->timeout = *(int *)addr;
163102ac6454SAndrew Thompson 	if (f->timeout > 65535) {
163202ac6454SAndrew Thompson 		/* limit user input */
163302ac6454SAndrew Thompson 		f->timeout = 65535;
163402ac6454SAndrew Thompson 	}
163502ac6454SAndrew Thompson 	return (0);
163602ac6454SAndrew Thompson }
163702ac6454SAndrew Thompson 
163802ac6454SAndrew Thompson static int
1639760bc48eSAndrew Thompson ugen_get_frame_size(struct usb_fifo *f, void *addr)
164002ac6454SAndrew Thompson {
164102ac6454SAndrew Thompson 	if (f->xfer[0]) {
164202ac6454SAndrew Thompson 		*(int *)addr = f->xfer[0]->max_frame_size;
164302ac6454SAndrew Thompson 	} else {
164402ac6454SAndrew Thompson 		return (EINVAL);
164502ac6454SAndrew Thompson 	}
164602ac6454SAndrew Thompson 	return (0);
164702ac6454SAndrew Thompson }
164802ac6454SAndrew Thompson 
164902ac6454SAndrew Thompson static int
1650760bc48eSAndrew Thompson ugen_set_buffer_size(struct usb_fifo *f, void *addr)
165102ac6454SAndrew Thompson {
1652e0a69b51SAndrew Thompson 	usb_frlength_t t;
165302ac6454SAndrew Thompson 
165401cf7831SAndrew Thompson 	if (*(int *)addr < 0)
165501cf7831SAndrew Thompson 		t = 0;		/* use "wMaxPacketSize" */
165602ac6454SAndrew Thompson 	else if (*(int *)addr < (256 * 1024))
165702ac6454SAndrew Thompson 		t = *(int *)addr;
165802ac6454SAndrew Thompson 	else
165902ac6454SAndrew Thompson 		t = 256 * 1024;
166002ac6454SAndrew Thompson 
166102ac6454SAndrew Thompson 	if (f->bufsize == t) {
166202ac6454SAndrew Thompson 		/* same value like before - accept */
166302ac6454SAndrew Thompson 		return (0);
166402ac6454SAndrew Thompson 	}
166502ac6454SAndrew Thompson 	if (f->xfer[0] || f->xfer[1]) {
166602ac6454SAndrew Thompson 		/* cannot change this during transfer */
166702ac6454SAndrew Thompson 		return (EBUSY);
166802ac6454SAndrew Thompson 	}
166902ac6454SAndrew Thompson 	f->bufsize = t;
167002ac6454SAndrew Thompson 	return (0);
167102ac6454SAndrew Thompson }
167202ac6454SAndrew Thompson 
167302ac6454SAndrew Thompson static int
1674760bc48eSAndrew Thompson ugen_get_buffer_size(struct usb_fifo *f, void *addr)
167502ac6454SAndrew Thompson {
167602ac6454SAndrew Thompson 	*(int *)addr = f->bufsize;
167702ac6454SAndrew Thompson 	return (0);
167802ac6454SAndrew Thompson }
167902ac6454SAndrew Thompson 
168002ac6454SAndrew Thompson static int
1681760bc48eSAndrew Thompson ugen_get_iface_desc(struct usb_fifo *f,
1682760bc48eSAndrew Thompson     struct usb_interface_descriptor *idesc)
168302ac6454SAndrew Thompson {
1684760bc48eSAndrew Thompson 	struct usb_interface *iface;
168502ac6454SAndrew Thompson 
1686a593f6b8SAndrew Thompson 	iface = usbd_get_iface(f->udev, f->iface_index);
168702ac6454SAndrew Thompson 	if (iface && iface->idesc) {
168802ac6454SAndrew Thompson 		*idesc = *(iface->idesc);
168902ac6454SAndrew Thompson 	} else {
169002ac6454SAndrew Thompson 		return (EIO);
169102ac6454SAndrew Thompson 	}
169202ac6454SAndrew Thompson 	return (0);
169302ac6454SAndrew Thompson }
169402ac6454SAndrew Thompson 
169502ac6454SAndrew Thompson static int
1696760bc48eSAndrew Thompson ugen_get_endpoint_desc(struct usb_fifo *f,
1697760bc48eSAndrew Thompson     struct usb_endpoint_descriptor *ed)
169802ac6454SAndrew Thompson {
1699ae60fdfbSAndrew Thompson 	struct usb_endpoint *ep;
170002ac6454SAndrew Thompson 
1701ed6d949aSAndrew Thompson 	ep = usb_fifo_softc(f);
170202ac6454SAndrew Thompson 
1703ae60fdfbSAndrew Thompson 	if (ep && ep->edesc) {
1704ae60fdfbSAndrew Thompson 		*ed = *ep->edesc;
170502ac6454SAndrew Thompson 	} else {
170602ac6454SAndrew Thompson 		return (EINVAL);
170702ac6454SAndrew Thompson 	}
170802ac6454SAndrew Thompson 	return (0);
170902ac6454SAndrew Thompson }
171002ac6454SAndrew Thompson 
171102ac6454SAndrew Thompson static int
1712760bc48eSAndrew Thompson ugen_set_power_mode(struct usb_fifo *f, int mode)
171302ac6454SAndrew Thompson {
1714760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
171502ac6454SAndrew Thompson 	int err;
171602ac6454SAndrew Thompson 	uint8_t old_mode;
171702ac6454SAndrew Thompson 
171802ac6454SAndrew Thompson 	if ((udev == NULL) ||
171902ac6454SAndrew Thompson 	    (udev->parent_hub == NULL)) {
172002ac6454SAndrew Thompson 		return (EINVAL);
172102ac6454SAndrew Thompson 	}
172250230f98SAndrew Thompson 	err = priv_check(curthread, PRIV_DRIVER);
172302ac6454SAndrew Thompson 	if (err)
172402ac6454SAndrew Thompson 		return (err);
172502ac6454SAndrew Thompson 
172602ac6454SAndrew Thompson 	/* get old power mode */
172702ac6454SAndrew Thompson 	old_mode = udev->power_mode;
172802ac6454SAndrew Thompson 
172902ac6454SAndrew Thompson 	/* if no change, then just return */
173002ac6454SAndrew Thompson 	if (old_mode == mode)
173102ac6454SAndrew Thompson 		return (0);
173202ac6454SAndrew Thompson 
173302ac6454SAndrew Thompson 	switch (mode) {
173402ac6454SAndrew Thompson 	case USB_POWER_MODE_OFF:
173502ac6454SAndrew Thompson 		/* get the device unconfigured */
173602ac6454SAndrew Thompson 		err = ugen_set_config(f, USB_UNCONFIG_INDEX);
173702ac6454SAndrew Thompson 		if (err) {
173802ac6454SAndrew Thompson 			DPRINTFN(0, "Could not unconfigure "
173902ac6454SAndrew Thompson 			    "device (ignored)\n");
174002ac6454SAndrew Thompson 		}
174102ac6454SAndrew Thompson 
174202ac6454SAndrew Thompson 		/* clear port enable */
1743a593f6b8SAndrew Thompson 		err = usbd_req_clear_port_feature(udev->parent_hub,
174402ac6454SAndrew Thompson 		    NULL, udev->port_no, UHF_PORT_ENABLE);
174502ac6454SAndrew Thompson 		break;
174602ac6454SAndrew Thompson 
174702ac6454SAndrew Thompson 	case USB_POWER_MODE_ON:
174802ac6454SAndrew Thompson 	case USB_POWER_MODE_SAVE:
174902ac6454SAndrew Thompson 		break;
175002ac6454SAndrew Thompson 
175102ac6454SAndrew Thompson 	case USB_POWER_MODE_RESUME:
17522df1e9a6SAndrew Thompson #if USB_HAVE_POWERD
17532df1e9a6SAndrew Thompson 		/* let USB-powerd handle resume */
17542df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17552df1e9a6SAndrew Thompson 		udev->pwr_save.write_refs++;
17562df1e9a6SAndrew Thompson 		udev->pwr_save.last_xfer_time = ticks;
17572df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17582df1e9a6SAndrew Thompson 
17592df1e9a6SAndrew Thompson 		/* set new power mode */
17602df1e9a6SAndrew Thompson 		usbd_set_power_mode(udev, USB_POWER_MODE_SAVE);
17612df1e9a6SAndrew Thompson 
17622df1e9a6SAndrew Thompson 		/* wait for resume to complete */
17632df1e9a6SAndrew Thompson 		usb_pause_mtx(NULL, hz / 4);
17642df1e9a6SAndrew Thompson 
17652df1e9a6SAndrew Thompson 		/* clear write reference */
17662df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17672df1e9a6SAndrew Thompson 		udev->pwr_save.write_refs--;
17682df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17692df1e9a6SAndrew Thompson #endif
177002ac6454SAndrew Thompson 		mode = USB_POWER_MODE_SAVE;
177102ac6454SAndrew Thompson 		break;
177202ac6454SAndrew Thompson 
177302ac6454SAndrew Thompson 	case USB_POWER_MODE_SUSPEND:
17742df1e9a6SAndrew Thompson #if USB_HAVE_POWERD
17752df1e9a6SAndrew Thompson 		/* let USB-powerd handle suspend */
17762df1e9a6SAndrew Thompson 		USB_BUS_LOCK(udev->bus);
17772df1e9a6SAndrew Thompson 		udev->pwr_save.last_xfer_time = ticks - (256 * hz);
17782df1e9a6SAndrew Thompson 		USB_BUS_UNLOCK(udev->bus);
17792df1e9a6SAndrew Thompson #endif
178002ac6454SAndrew Thompson 		mode = USB_POWER_MODE_SAVE;
178102ac6454SAndrew Thompson 		break;
178202ac6454SAndrew Thompson 
178302ac6454SAndrew Thompson 	default:
178402ac6454SAndrew Thompson 		return (EINVAL);
178502ac6454SAndrew Thompson 	}
178602ac6454SAndrew Thompson 
178702ac6454SAndrew Thompson 	if (err)
178802ac6454SAndrew Thompson 		return (ENXIO);		/* I/O failure */
178902ac6454SAndrew Thompson 
179002ac6454SAndrew Thompson 	/* if we are powered off we need to re-enumerate first */
179102ac6454SAndrew Thompson 	if (old_mode == USB_POWER_MODE_OFF) {
17928f9750b7SHans Petter Selasky 		if (udev->flags.usb_mode == USB_MODE_HOST) {
17938f9750b7SHans Petter Selasky 			if (udev->re_enumerate_wait == 0)
17948f9750b7SHans Petter Selasky 				udev->re_enumerate_wait = 1;
17958f9750b7SHans Petter Selasky 		}
17968f9750b7SHans Petter Selasky 		/* set power mode will wake up the explore thread */
179702ac6454SAndrew Thompson 	}
179802ac6454SAndrew Thompson 
179902ac6454SAndrew Thompson 	/* set new power mode */
1800a593f6b8SAndrew Thompson 	usbd_set_power_mode(udev, mode);
180102ac6454SAndrew Thompson 
180202ac6454SAndrew Thompson 	return (0);			/* success */
180302ac6454SAndrew Thompson }
180402ac6454SAndrew Thompson 
180502ac6454SAndrew Thompson static int
1806760bc48eSAndrew Thompson ugen_get_power_mode(struct usb_fifo *f)
180702ac6454SAndrew Thompson {
1808760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
180902ac6454SAndrew Thompson 
1810d2d71ce7SAndrew Thompson 	if (udev == NULL)
181102ac6454SAndrew Thompson 		return (USB_POWER_MODE_ON);
1812d2d71ce7SAndrew Thompson 
181302ac6454SAndrew Thompson 	return (udev->power_mode);
181402ac6454SAndrew Thompson }
181502ac6454SAndrew Thompson 
181602ac6454SAndrew Thompson static int
1817760bc48eSAndrew Thompson ugen_do_port_feature(struct usb_fifo *f, uint8_t port_no,
181802ac6454SAndrew Thompson     uint8_t set, uint16_t feature)
181902ac6454SAndrew Thompson {
1820760bc48eSAndrew Thompson 	struct usb_device *udev = f->udev;
1821760bc48eSAndrew Thompson 	struct usb_hub *hub;
182202ac6454SAndrew Thompson 	int err;
182302ac6454SAndrew Thompson 
182450230f98SAndrew Thompson 	err = priv_check(curthread, PRIV_DRIVER);
182502ac6454SAndrew Thompson 	if (err) {
182602ac6454SAndrew Thompson 		return (err);
182702ac6454SAndrew Thompson 	}
182802ac6454SAndrew Thompson 	if (port_no == 0) {
182902ac6454SAndrew Thompson 		return (EINVAL);
183002ac6454SAndrew Thompson 	}
183102ac6454SAndrew Thompson 	if ((udev == NULL) ||
183202ac6454SAndrew Thompson 	    (udev->hub == NULL)) {
183302ac6454SAndrew Thompson 		return (EINVAL);
183402ac6454SAndrew Thompson 	}
183502ac6454SAndrew Thompson 	hub = udev->hub;
183602ac6454SAndrew Thompson 
183702ac6454SAndrew Thompson 	if (port_no > hub->nports) {
183802ac6454SAndrew Thompson 		return (EINVAL);
183902ac6454SAndrew Thompson 	}
184002ac6454SAndrew Thompson 	if (set)
1841a593f6b8SAndrew Thompson 		err = usbd_req_set_port_feature(udev,
184202ac6454SAndrew Thompson 		    NULL, port_no, feature);
184302ac6454SAndrew Thompson 	else
1844a593f6b8SAndrew Thompson 		err = usbd_req_clear_port_feature(udev,
184502ac6454SAndrew Thompson 		    NULL, port_no, feature);
184602ac6454SAndrew Thompson 
184702ac6454SAndrew Thompson 	if (err)
184802ac6454SAndrew Thompson 		return (ENXIO);		/* failure */
184902ac6454SAndrew Thompson 
185002ac6454SAndrew Thompson 	return (0);			/* success */
185102ac6454SAndrew Thompson }
185202ac6454SAndrew Thompson 
185302ac6454SAndrew Thompson static int
1854760bc48eSAndrew Thompson ugen_iface_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
185502ac6454SAndrew Thompson {
1856760bc48eSAndrew Thompson 	struct usb_fifo *f_rx;
1857760bc48eSAndrew Thompson 	struct usb_fifo *f_tx;
185802ac6454SAndrew Thompson 	int error = 0;
185902ac6454SAndrew Thompson 
186002ac6454SAndrew Thompson 	f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX];
186102ac6454SAndrew Thompson 	f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX];
186202ac6454SAndrew Thompson 
186302ac6454SAndrew Thompson 	switch (cmd) {
186402ac6454SAndrew Thompson 	case USB_SET_RX_SHORT_XFER:
186502ac6454SAndrew Thompson 		if (fflags & FREAD) {
186602ac6454SAndrew Thompson 			error = ugen_set_short_xfer(f_rx, addr);
186702ac6454SAndrew Thompson 		} else {
186802ac6454SAndrew Thompson 			error = EINVAL;
186902ac6454SAndrew Thompson 		}
187002ac6454SAndrew Thompson 		break;
187102ac6454SAndrew Thompson 
187202ac6454SAndrew Thompson 	case USB_SET_TX_FORCE_SHORT:
187302ac6454SAndrew Thompson 		if (fflags & FWRITE) {
187402ac6454SAndrew Thompson 			error = ugen_set_short_xfer(f_tx, addr);
187502ac6454SAndrew Thompson 		} else {
187602ac6454SAndrew Thompson 			error = EINVAL;
187702ac6454SAndrew Thompson 		}
187802ac6454SAndrew Thompson 		break;
187902ac6454SAndrew Thompson 
188002ac6454SAndrew Thompson 	case USB_SET_RX_TIMEOUT:
188102ac6454SAndrew Thompson 		if (fflags & FREAD) {
188202ac6454SAndrew Thompson 			error = ugen_set_timeout(f_rx, addr);
188302ac6454SAndrew Thompson 		} else {
188402ac6454SAndrew Thompson 			error = EINVAL;
188502ac6454SAndrew Thompson 		}
188602ac6454SAndrew Thompson 		break;
188702ac6454SAndrew Thompson 
188802ac6454SAndrew Thompson 	case USB_SET_TX_TIMEOUT:
188902ac6454SAndrew Thompson 		if (fflags & FWRITE) {
189002ac6454SAndrew Thompson 			error = ugen_set_timeout(f_tx, addr);
189102ac6454SAndrew Thompson 		} else {
189202ac6454SAndrew Thompson 			error = EINVAL;
189302ac6454SAndrew Thompson 		}
189402ac6454SAndrew Thompson 		break;
189502ac6454SAndrew Thompson 
189602ac6454SAndrew Thompson 	case USB_GET_RX_FRAME_SIZE:
189702ac6454SAndrew Thompson 		if (fflags & FREAD) {
189802ac6454SAndrew Thompson 			error = ugen_get_frame_size(f_rx, addr);
189902ac6454SAndrew Thompson 		} else {
190002ac6454SAndrew Thompson 			error = EINVAL;
190102ac6454SAndrew Thompson 		}
190202ac6454SAndrew Thompson 		break;
190302ac6454SAndrew Thompson 
190402ac6454SAndrew Thompson 	case USB_GET_TX_FRAME_SIZE:
190502ac6454SAndrew Thompson 		if (fflags & FWRITE) {
190602ac6454SAndrew Thompson 			error = ugen_get_frame_size(f_tx, addr);
190702ac6454SAndrew Thompson 		} else {
190802ac6454SAndrew Thompson 			error = EINVAL;
190902ac6454SAndrew Thompson 		}
191002ac6454SAndrew Thompson 		break;
191102ac6454SAndrew Thompson 
191202ac6454SAndrew Thompson 	case USB_SET_RX_BUFFER_SIZE:
191302ac6454SAndrew Thompson 		if (fflags & FREAD) {
191402ac6454SAndrew Thompson 			error = ugen_set_buffer_size(f_rx, addr);
191502ac6454SAndrew Thompson 		} else {
191602ac6454SAndrew Thompson 			error = EINVAL;
191702ac6454SAndrew Thompson 		}
191802ac6454SAndrew Thompson 		break;
191902ac6454SAndrew Thompson 
192002ac6454SAndrew Thompson 	case USB_SET_TX_BUFFER_SIZE:
192102ac6454SAndrew Thompson 		if (fflags & FWRITE) {
192202ac6454SAndrew Thompson 			error = ugen_set_buffer_size(f_tx, addr);
192302ac6454SAndrew Thompson 		} else {
192402ac6454SAndrew Thompson 			error = EINVAL;
192502ac6454SAndrew Thompson 		}
192602ac6454SAndrew Thompson 		break;
192702ac6454SAndrew Thompson 
192802ac6454SAndrew Thompson 	case USB_GET_RX_BUFFER_SIZE:
192902ac6454SAndrew Thompson 		if (fflags & FREAD) {
193002ac6454SAndrew Thompson 			error = ugen_get_buffer_size(f_rx, addr);
193102ac6454SAndrew Thompson 		} else {
193202ac6454SAndrew Thompson 			error = EINVAL;
193302ac6454SAndrew Thompson 		}
193402ac6454SAndrew Thompson 		break;
193502ac6454SAndrew Thompson 
193602ac6454SAndrew Thompson 	case USB_GET_TX_BUFFER_SIZE:
193702ac6454SAndrew Thompson 		if (fflags & FWRITE) {
193802ac6454SAndrew Thompson 			error = ugen_get_buffer_size(f_tx, addr);
193902ac6454SAndrew Thompson 		} else {
194002ac6454SAndrew Thompson 			error = EINVAL;
194102ac6454SAndrew Thompson 		}
194202ac6454SAndrew Thompson 		break;
194302ac6454SAndrew Thompson 
194402ac6454SAndrew Thompson 	case USB_GET_RX_INTERFACE_DESC:
194502ac6454SAndrew Thompson 		if (fflags & FREAD) {
194602ac6454SAndrew Thompson 			error = ugen_get_iface_desc(f_rx, addr);
194702ac6454SAndrew Thompson 		} else {
194802ac6454SAndrew Thompson 			error = EINVAL;
194902ac6454SAndrew Thompson 		}
195002ac6454SAndrew Thompson 		break;
195102ac6454SAndrew Thompson 
195202ac6454SAndrew Thompson 	case USB_GET_TX_INTERFACE_DESC:
195302ac6454SAndrew Thompson 		if (fflags & FWRITE) {
195402ac6454SAndrew Thompson 			error = ugen_get_iface_desc(f_tx, addr);
195502ac6454SAndrew Thompson 		} else {
195602ac6454SAndrew Thompson 			error = EINVAL;
195702ac6454SAndrew Thompson 		}
195802ac6454SAndrew Thompson 		break;
195902ac6454SAndrew Thompson 
196002ac6454SAndrew Thompson 	case USB_GET_RX_ENDPOINT_DESC:
196102ac6454SAndrew Thompson 		if (fflags & FREAD) {
196202ac6454SAndrew Thompson 			error = ugen_get_endpoint_desc(f_rx, addr);
196302ac6454SAndrew Thompson 		} else {
196402ac6454SAndrew Thompson 			error = EINVAL;
196502ac6454SAndrew Thompson 		}
196602ac6454SAndrew Thompson 		break;
196702ac6454SAndrew Thompson 
196802ac6454SAndrew Thompson 	case USB_GET_TX_ENDPOINT_DESC:
196902ac6454SAndrew Thompson 		if (fflags & FWRITE) {
197002ac6454SAndrew Thompson 			error = ugen_get_endpoint_desc(f_tx, addr);
197102ac6454SAndrew Thompson 		} else {
197202ac6454SAndrew Thompson 			error = EINVAL;
197302ac6454SAndrew Thompson 		}
197402ac6454SAndrew Thompson 		break;
197502ac6454SAndrew Thompson 
197602ac6454SAndrew Thompson 	case USB_SET_RX_STALL_FLAG:
197702ac6454SAndrew Thompson 		if ((fflags & FREAD) && (*(int *)addr)) {
197802ac6454SAndrew Thompson 			f_rx->flag_stall = 1;
197902ac6454SAndrew Thompson 		}
198002ac6454SAndrew Thompson 		break;
198102ac6454SAndrew Thompson 
198202ac6454SAndrew Thompson 	case USB_SET_TX_STALL_FLAG:
198302ac6454SAndrew Thompson 		if ((fflags & FWRITE) && (*(int *)addr)) {
198402ac6454SAndrew Thompson 			f_tx->flag_stall = 1;
198502ac6454SAndrew Thompson 		}
198602ac6454SAndrew Thompson 		break;
198702ac6454SAndrew Thompson 
198802ac6454SAndrew Thompson 	default:
198902ac6454SAndrew Thompson 		error = ENOIOCTL;
199002ac6454SAndrew Thompson 		break;
199102ac6454SAndrew Thompson 	}
199202ac6454SAndrew Thompson 	return (error);
199302ac6454SAndrew Thompson }
199402ac6454SAndrew Thompson 
199502ac6454SAndrew Thompson static int
1996760bc48eSAndrew Thompson ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
199702ac6454SAndrew Thompson {
199802ac6454SAndrew Thompson 	union {
1999760bc48eSAndrew Thompson 		struct usb_interface_descriptor *idesc;
2000760bc48eSAndrew Thompson 		struct usb_alt_interface *ai;
2001760bc48eSAndrew Thompson 		struct usb_device_descriptor *ddesc;
2002760bc48eSAndrew Thompson 		struct usb_config_descriptor *cdesc;
2003760bc48eSAndrew Thompson 		struct usb_device_stats *stat;
2004760bc48eSAndrew Thompson 		struct usb_fs_init *pinit;
2005760bc48eSAndrew Thompson 		struct usb_fs_uninit *puninit;
200602ac6454SAndrew Thompson 		uint32_t *ptime;
200702ac6454SAndrew Thompson 		void   *addr;
200802ac6454SAndrew Thompson 		int    *pint;
200902ac6454SAndrew Thompson 	}     u;
2010760bc48eSAndrew Thompson 	struct usb_device_descriptor *dtemp;
2011760bc48eSAndrew Thompson 	struct usb_config_descriptor *ctemp;
2012760bc48eSAndrew Thompson 	struct usb_interface *iface;
201302ac6454SAndrew Thompson 	int error = 0;
201402ac6454SAndrew Thompson 	uint8_t n;
201502ac6454SAndrew Thompson 
201602ac6454SAndrew Thompson 	u.addr = addr;
201702ac6454SAndrew Thompson 
201802ac6454SAndrew Thompson 	DPRINTFN(6, "cmd=0x%08lx\n", cmd);
201902ac6454SAndrew Thompson 
202002ac6454SAndrew Thompson 	switch (cmd) {
202102ac6454SAndrew Thompson 	case USB_DISCOVER:
2022a593f6b8SAndrew Thompson 		usb_needs_explore_all();
202302ac6454SAndrew Thompson 		break;
202402ac6454SAndrew Thompson 
202502ac6454SAndrew Thompson 	case USB_SETDEBUG:
202602ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
202702ac6454SAndrew Thompson 			error = EPERM;
202802ac6454SAndrew Thompson 			break;
202902ac6454SAndrew Thompson 		}
2030a593f6b8SAndrew Thompson 		usb_debug = *(int *)addr;
203102ac6454SAndrew Thompson 		break;
203202ac6454SAndrew Thompson 
203302ac6454SAndrew Thompson 	case USB_GET_CONFIG:
203402ac6454SAndrew Thompson 		*(int *)addr = f->udev->curr_config_index;
203502ac6454SAndrew Thompson 		break;
203602ac6454SAndrew Thompson 
203702ac6454SAndrew Thompson 	case USB_SET_CONFIG:
203802ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
203902ac6454SAndrew Thompson 			error = EPERM;
204002ac6454SAndrew Thompson 			break;
204102ac6454SAndrew Thompson 		}
204202ac6454SAndrew Thompson 		error = ugen_set_config(f, *(int *)addr);
204302ac6454SAndrew Thompson 		break;
204402ac6454SAndrew Thompson 
204502ac6454SAndrew Thompson 	case USB_GET_ALTINTERFACE:
2046a593f6b8SAndrew Thompson 		iface = usbd_get_iface(f->udev,
204702ac6454SAndrew Thompson 		    u.ai->uai_interface_index);
204802ac6454SAndrew Thompson 		if (iface && iface->idesc) {
204902ac6454SAndrew Thompson 			u.ai->uai_alt_index = iface->alt_index;
205002ac6454SAndrew Thompson 		} else {
205102ac6454SAndrew Thompson 			error = EINVAL;
205202ac6454SAndrew Thompson 		}
205302ac6454SAndrew Thompson 		break;
205402ac6454SAndrew Thompson 
205502ac6454SAndrew Thompson 	case USB_SET_ALTINTERFACE:
205602ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
205702ac6454SAndrew Thompson 			error = EPERM;
205802ac6454SAndrew Thompson 			break;
205902ac6454SAndrew Thompson 		}
206002ac6454SAndrew Thompson 		error = ugen_set_interface(f,
206102ac6454SAndrew Thompson 		    u.ai->uai_interface_index, u.ai->uai_alt_index);
206202ac6454SAndrew Thompson 		break;
206302ac6454SAndrew Thompson 
206402ac6454SAndrew Thompson 	case USB_GET_DEVICE_DESC:
2065a593f6b8SAndrew Thompson 		dtemp = usbd_get_device_descriptor(f->udev);
206602ac6454SAndrew Thompson 		if (!dtemp) {
206702ac6454SAndrew Thompson 			error = EIO;
206802ac6454SAndrew Thompson 			break;
206902ac6454SAndrew Thompson 		}
207002ac6454SAndrew Thompson 		*u.ddesc = *dtemp;
207102ac6454SAndrew Thompson 		break;
207202ac6454SAndrew Thompson 
207302ac6454SAndrew Thompson 	case USB_GET_CONFIG_DESC:
2074a593f6b8SAndrew Thompson 		ctemp = usbd_get_config_descriptor(f->udev);
207502ac6454SAndrew Thompson 		if (!ctemp) {
207602ac6454SAndrew Thompson 			error = EIO;
207702ac6454SAndrew Thompson 			break;
207802ac6454SAndrew Thompson 		}
207902ac6454SAndrew Thompson 		*u.cdesc = *ctemp;
208002ac6454SAndrew Thompson 		break;
208102ac6454SAndrew Thompson 
208202ac6454SAndrew Thompson 	case USB_GET_FULL_DESC:
208302ac6454SAndrew Thompson 		error = ugen_get_cdesc(f, addr);
208402ac6454SAndrew Thompson 		break;
208502ac6454SAndrew Thompson 
208602ac6454SAndrew Thompson 	case USB_GET_STRING_DESC:
208702ac6454SAndrew Thompson 		error = ugen_get_sdesc(f, addr);
208802ac6454SAndrew Thompson 		break;
208902ac6454SAndrew Thompson 
209002ac6454SAndrew Thompson 	case USB_GET_IFACE_DRIVER:
209102ac6454SAndrew Thompson 		error = ugen_get_iface_driver(f, addr);
209202ac6454SAndrew Thompson 		break;
209302ac6454SAndrew Thompson 
209402ac6454SAndrew Thompson 	case USB_REQUEST:
209502ac6454SAndrew Thompson 	case USB_DO_REQUEST:
209602ac6454SAndrew Thompson 		if (!(fflags & FWRITE)) {
209702ac6454SAndrew Thompson 			error = EPERM;
209802ac6454SAndrew Thompson 			break;
209902ac6454SAndrew Thompson 		}
210002ac6454SAndrew Thompson 		error = ugen_do_request(f, addr);
210102ac6454SAndrew Thompson 		break;
210202ac6454SAndrew Thompson 
210302ac6454SAndrew Thompson 	case USB_DEVICEINFO:
210402ac6454SAndrew Thompson 	case USB_GET_DEVICEINFO:
2105a593f6b8SAndrew Thompson 		error = usb_gen_fill_deviceinfo(f, addr);
210602ac6454SAndrew Thompson 		break;
210702ac6454SAndrew Thompson 
210802ac6454SAndrew Thompson 	case USB_DEVICESTATS:
210902ac6454SAndrew Thompson 		for (n = 0; n != 4; n++) {
211002ac6454SAndrew Thompson 
211102ac6454SAndrew Thompson 			u.stat->uds_requests_fail[n] =
211202ac6454SAndrew Thompson 			    f->udev->bus->stats_err.uds_requests[n];
211302ac6454SAndrew Thompson 
211402ac6454SAndrew Thompson 			u.stat->uds_requests_ok[n] =
211502ac6454SAndrew Thompson 			    f->udev->bus->stats_ok.uds_requests[n];
211602ac6454SAndrew Thompson 		}
211702ac6454SAndrew Thompson 		break;
211802ac6454SAndrew Thompson 
211902ac6454SAndrew Thompson 	case USB_DEVICEENUMERATE:
212002ac6454SAndrew Thompson 		error = ugen_re_enumerate(f);
212102ac6454SAndrew Thompson 		break;
212202ac6454SAndrew Thompson 
212302ac6454SAndrew Thompson 	case USB_GET_PLUGTIME:
212402ac6454SAndrew Thompson 		*u.ptime = f->udev->plugtime;
212502ac6454SAndrew Thompson 		break;
212602ac6454SAndrew Thompson 
212702ac6454SAndrew Thompson 	case USB_CLAIM_INTERFACE:
212802ac6454SAndrew Thompson 	case USB_RELEASE_INTERFACE:
212902ac6454SAndrew Thompson 		/* TODO */
213002ac6454SAndrew Thompson 		break;
213102ac6454SAndrew Thompson 
213202ac6454SAndrew Thompson 	case USB_IFACE_DRIVER_ACTIVE:
2133a7aca4cdSAndrew Thompson 
2134a7aca4cdSAndrew Thompson 		n = *u.pint & 0xFF;
2135a7aca4cdSAndrew Thompson 
2136a7aca4cdSAndrew Thompson 		iface = usbd_get_iface(f->udev, n);
2137a7aca4cdSAndrew Thompson 
2138a7aca4cdSAndrew Thompson 		if (iface && iface->subdev)
2139a7aca4cdSAndrew Thompson 			error = 0;
2140a7aca4cdSAndrew Thompson 		else
2141a7aca4cdSAndrew Thompson 			error = ENXIO;
214202ac6454SAndrew Thompson 		break;
214302ac6454SAndrew Thompson 
214402ac6454SAndrew Thompson 	case USB_IFACE_DRIVER_DETACH:
2145a7aca4cdSAndrew Thompson 
214602ac6454SAndrew Thompson 		error = priv_check(curthread, PRIV_DRIVER);
2147a7aca4cdSAndrew Thompson 
2148a7aca4cdSAndrew Thompson 		if (error)
2149a7aca4cdSAndrew Thompson 			break;
2150a7aca4cdSAndrew Thompson 
2151a7aca4cdSAndrew Thompson 		n = *u.pint & 0xFF;
2152a7aca4cdSAndrew Thompson 
2153a7aca4cdSAndrew Thompson 		if (n == USB_IFACE_INDEX_ANY) {
2154a7aca4cdSAndrew Thompson 			error = EINVAL;
215502ac6454SAndrew Thompson 			break;
215602ac6454SAndrew Thompson 		}
2157a7aca4cdSAndrew Thompson 
2158a7aca4cdSAndrew Thompson 		usb_detach_device(f->udev, n, 0);
215902ac6454SAndrew Thompson 		break;
216002ac6454SAndrew Thompson 
216102ac6454SAndrew Thompson 	case USB_SET_POWER_MODE:
216202ac6454SAndrew Thompson 		error = ugen_set_power_mode(f, *u.pint);
216302ac6454SAndrew Thompson 		break;
216402ac6454SAndrew Thompson 
216502ac6454SAndrew Thompson 	case USB_GET_POWER_MODE:
216602ac6454SAndrew Thompson 		*u.pint = ugen_get_power_mode(f);
216702ac6454SAndrew Thompson 		break;
216802ac6454SAndrew Thompson 
216902ac6454SAndrew Thompson 	case USB_SET_PORT_ENABLE:
217002ac6454SAndrew Thompson 		error = ugen_do_port_feature(f,
217102ac6454SAndrew Thompson 		    *u.pint, 1, UHF_PORT_ENABLE);
217202ac6454SAndrew Thompson 		break;
217302ac6454SAndrew Thompson 
217402ac6454SAndrew Thompson 	case USB_SET_PORT_DISABLE:
217502ac6454SAndrew Thompson 		error = ugen_do_port_feature(f,
217602ac6454SAndrew Thompson 		    *u.pint, 0, UHF_PORT_ENABLE);
217702ac6454SAndrew Thompson 		break;
217802ac6454SAndrew Thompson 
217902ac6454SAndrew Thompson 	case USB_FS_INIT:
218002ac6454SAndrew Thompson 		/* verify input parameters */
218102ac6454SAndrew Thompson 		if (u.pinit->pEndpoints == NULL) {
218202ac6454SAndrew Thompson 			error = EINVAL;
218302ac6454SAndrew Thompson 			break;
218402ac6454SAndrew Thompson 		}
218502ac6454SAndrew Thompson 		if (u.pinit->ep_index_max > 127) {
218602ac6454SAndrew Thompson 			error = EINVAL;
218702ac6454SAndrew Thompson 			break;
218802ac6454SAndrew Thompson 		}
218902ac6454SAndrew Thompson 		if (u.pinit->ep_index_max == 0) {
219002ac6454SAndrew Thompson 			error = EINVAL;
219102ac6454SAndrew Thompson 			break;
219202ac6454SAndrew Thompson 		}
219302ac6454SAndrew Thompson 		if (f->fs_xfer != NULL) {
219402ac6454SAndrew Thompson 			error = EBUSY;
219502ac6454SAndrew Thompson 			break;
219602ac6454SAndrew Thompson 		}
219702ac6454SAndrew Thompson 		if (f->dev_ep_index != 0) {
219802ac6454SAndrew Thompson 			error = EINVAL;
219902ac6454SAndrew Thompson 			break;
220002ac6454SAndrew Thompson 		}
220102ac6454SAndrew Thompson 		if (ugen_fifo_in_use(f, fflags)) {
220202ac6454SAndrew Thompson 			error = EBUSY;
220302ac6454SAndrew Thompson 			break;
220402ac6454SAndrew Thompson 		}
2205a593f6b8SAndrew Thompson 		error = usb_fifo_alloc_buffer(f, 1, u.pinit->ep_index_max);
220602ac6454SAndrew Thompson 		if (error) {
220702ac6454SAndrew Thompson 			break;
220802ac6454SAndrew Thompson 		}
220902ac6454SAndrew Thompson 		f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) *
221002ac6454SAndrew Thompson 		    u.pinit->ep_index_max, M_USB, M_WAITOK | M_ZERO);
221102ac6454SAndrew Thompson 		if (f->fs_xfer == NULL) {
2212a593f6b8SAndrew Thompson 			usb_fifo_free_buffer(f);
221302ac6454SAndrew Thompson 			error = ENOMEM;
221402ac6454SAndrew Thompson 			break;
221502ac6454SAndrew Thompson 		}
221602ac6454SAndrew Thompson 		f->fs_ep_max = u.pinit->ep_index_max;
221702ac6454SAndrew Thompson 		f->fs_ep_ptr = u.pinit->pEndpoints;
221802ac6454SAndrew Thompson 		break;
221902ac6454SAndrew Thompson 
222002ac6454SAndrew Thompson 	case USB_FS_UNINIT:
222102ac6454SAndrew Thompson 		if (u.puninit->dummy != 0) {
222202ac6454SAndrew Thompson 			error = EINVAL;
222302ac6454SAndrew Thompson 			break;
222402ac6454SAndrew Thompson 		}
222502ac6454SAndrew Thompson 		error = ugen_fs_uninit(f);
222602ac6454SAndrew Thompson 		break;
222702ac6454SAndrew Thompson 
222802ac6454SAndrew Thompson 	default:
222902ac6454SAndrew Thompson 		mtx_lock(f->priv_mtx);
223002ac6454SAndrew Thompson 		error = ugen_iface_ioctl(f, cmd, addr, fflags);
223102ac6454SAndrew Thompson 		mtx_unlock(f->priv_mtx);
223202ac6454SAndrew Thompson 		break;
223302ac6454SAndrew Thompson 	}
223402ac6454SAndrew Thompson 	DPRINTFN(6, "error=%d\n", error);
223502ac6454SAndrew Thompson 	return (error);
223602ac6454SAndrew Thompson }
223702ac6454SAndrew Thompson 
223802ac6454SAndrew Thompson static void
22395b3bb704SAndrew Thompson ugen_ctrl_fs_callback(struct usb_xfer *xfer, usb_error_t error)
224002ac6454SAndrew Thompson {
224102ac6454SAndrew Thompson 	;				/* workaround for a bug in "indent" */
224202ac6454SAndrew Thompson 
224302ac6454SAndrew Thompson 	DPRINTF("st=%u alen=%u aframes=%u\n",
224402ac6454SAndrew Thompson 	    USB_GET_STATE(xfer), xfer->actlen, xfer->aframes);
224502ac6454SAndrew Thompson 
224602ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
224702ac6454SAndrew Thompson 	case USB_ST_SETUP:
2248a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
224902ac6454SAndrew Thompson 		break;
225002ac6454SAndrew Thompson 	default:
225102ac6454SAndrew Thompson 		ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo));
225202ac6454SAndrew Thompson 		break;
225302ac6454SAndrew Thompson 	}
225402ac6454SAndrew Thompson }
22558755859aSAndrew Thompson #endif	/* USB_HAVE_UGEN */
2256