usb_handle_request.c revision 196498
1252190Srpaulo/* $FreeBSD: head/sys/dev/usb/usb_handle_request.c 196498 2009-08-24 05:05:38Z alfred $ */
2252190Srpaulo/*-
3252190Srpaulo * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
4252190Srpaulo *
5252190Srpaulo * Redistribution and use in source and binary forms, with or without
6252190Srpaulo * modification, are permitted provided that the following conditions
7252190Srpaulo * are met:
8252190Srpaulo * 1. Redistributions of source code must retain the above copyright
9252190Srpaulo *    notice, this list of conditions and the following disclaimer.
10252190Srpaulo * 2. Redistributions in binary form must reproduce the above copyright
11252190Srpaulo *    notice, this list of conditions and the following disclaimer in the
12252190Srpaulo *    documentation and/or other materials provided with the distribution.
13252190Srpaulo *
14252190Srpaulo * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15252190Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16252190Srpaulo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17252190Srpaulo * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18252190Srpaulo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19252190Srpaulo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20252190Srpaulo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21252190Srpaulo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22252190Srpaulo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23252190Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24252190Srpaulo * SUCH DAMAGE.
25252190Srpaulo */
26252190Srpaulo
27252190Srpaulo#include <sys/stdint.h>
28252190Srpaulo#include <sys/stddef.h>
29252190Srpaulo#include <sys/param.h>
30252190Srpaulo#include <sys/queue.h>
31252190Srpaulo#include <sys/types.h>
32252190Srpaulo#include <sys/systm.h>
33252190Srpaulo#include <sys/kernel.h>
34252190Srpaulo#include <sys/bus.h>
35252190Srpaulo#include <sys/linker_set.h>
36252190Srpaulo#include <sys/module.h>
37252190Srpaulo#include <sys/lock.h>
38252190Srpaulo#include <sys/mutex.h>
39252190Srpaulo#include <sys/condvar.h>
40252190Srpaulo#include <sys/sysctl.h>
41252190Srpaulo#include <sys/sx.h>
42252190Srpaulo#include <sys/unistd.h>
43252190Srpaulo#include <sys/callout.h>
44252190Srpaulo#include <sys/malloc.h>
45252190Srpaulo#include <sys/priv.h>
46252190Srpaulo
47252190Srpaulo#include <dev/usb/usb.h>
48252190Srpaulo#include <dev/usb/usbdi.h>
49252190Srpaulo#include <dev/usb/usbdi_util.h>
50252190Srpaulo#include "usb_if.h"
51252190Srpaulo
52252190Srpaulo#define	USB_DEBUG_VAR usb_debug
53252190Srpaulo
54252190Srpaulo#include <dev/usb/usb_core.h>
55252190Srpaulo#include <dev/usb/usb_process.h>
56252190Srpaulo#include <dev/usb/usb_busdma.h>
57252190Srpaulo#include <dev/usb/usb_transfer.h>
58252190Srpaulo#include <dev/usb/usb_device.h>
59252190Srpaulo#include <dev/usb/usb_debug.h>
60252190Srpaulo#include <dev/usb/usb_dynamic.h>
61252190Srpaulo#include <dev/usb/usb_hub.h>
62252190Srpaulo
63252190Srpaulo#include <dev/usb/usb_controller.h>
64252190Srpaulo#include <dev/usb/usb_bus.h>
65252190Srpaulo
66252190Srpaulo/* function prototypes */
67252190Srpaulo
68252190Srpaulostatic uint8_t usb_handle_get_stall(struct usb_device *, uint8_t);
69252190Srpaulostatic usb_error_t	 usb_handle_remote_wakeup(struct usb_xfer *, uint8_t);
70252190Srpaulostatic usb_error_t	 usb_handle_request(struct usb_xfer *);
71252190Srpaulostatic usb_error_t	 usb_handle_set_config(struct usb_xfer *, uint8_t);
72252190Srpaulostatic usb_error_t	 usb_handle_set_stall(struct usb_xfer *, uint8_t,
73252190Srpaulo			    uint8_t);
74252190Srpaulostatic usb_error_t	 usb_handle_iface_request(struct usb_xfer *, void **,
75252190Srpaulo			    uint16_t *, struct usb_device_request, uint16_t,
76252190Srpaulo			    uint8_t);
77252190Srpaulo
78252190Srpaulo/*------------------------------------------------------------------------*
79252190Srpaulo *	usb_handle_request_callback
80252190Srpaulo *
81252190Srpaulo * This function is the USB callback for generic USB Device control
82252190Srpaulo * transfers.
83252190Srpaulo *------------------------------------------------------------------------*/
84252190Srpaulovoid
85252190Srpaulousb_handle_request_callback(struct usb_xfer *xfer, usb_error_t error)
86252190Srpaulo{
87252190Srpaulo	usb_error_t err;
88252190Srpaulo
89252190Srpaulo	/* check the current transfer state */
90252190Srpaulo
91252190Srpaulo	switch (USB_GET_STATE(xfer)) {
92252190Srpaulo	case USB_ST_SETUP:
93252190Srpaulo	case USB_ST_TRANSFERRED:
94252190Srpaulo
95252190Srpaulo		/* handle the request */
96252190Srpaulo		err = usb_handle_request(xfer);
97252190Srpaulo
98252190Srpaulo		if (err) {
99252190Srpaulo
100252190Srpaulo			if (err == USB_ERR_BAD_CONTEXT) {
101252190Srpaulo				/* we need to re-setup the control transfer */
102252190Srpaulo				usb_needs_explore(xfer->xroot->bus, 0);
103252190Srpaulo				break;
104252190Srpaulo			}
105252190Srpaulo			goto tr_restart;
106252190Srpaulo		}
107252190Srpaulo		usbd_transfer_submit(xfer);
108252190Srpaulo		break;
109252190Srpaulo
110252190Srpaulo	default:
111252190Srpaulo		/* check if a control transfer is active */
112252190Srpaulo		if (xfer->flags_int.control_rem != 0xFFFF) {
113252190Srpaulo			/* handle the request */
114252190Srpaulo			err = usb_handle_request(xfer);
115252190Srpaulo		}
116252190Srpaulo		if (xfer->error != USB_ERR_CANCELLED) {
117252190Srpaulo			/* should not happen - try stalling */
118252190Srpaulo			goto tr_restart;
119252190Srpaulo		}
120252190Srpaulo		break;
121252190Srpaulo	}
122252190Srpaulo	return;
123252190Srpaulo
124252190Srpaulotr_restart:
125252190Srpaulo	/*
126252190Srpaulo	 * If a control transfer is active, stall it, and wait for the
127252190Srpaulo	 * next control transfer.
128252190Srpaulo	 */
129252190Srpaulo	usbd_xfer_set_frame_len(xfer, 0, sizeof(struct usb_device_request));
130252190Srpaulo	xfer->nframes = 1;
131252190Srpaulo	xfer->flags.manual_status = 1;
132252190Srpaulo	xfer->flags.force_short_xfer = 0;
133252190Srpaulo	usbd_xfer_set_stall(xfer);	/* cancel previous transfer, if any */
134252190Srpaulo	usbd_transfer_submit(xfer);
135252190Srpaulo}
136252190Srpaulo
137252190Srpaulo/*------------------------------------------------------------------------*
138252190Srpaulo *	usb_handle_set_config
139252190Srpaulo *
140252190Srpaulo * Returns:
141252190Srpaulo *    0: Success
142252190Srpaulo * Else: Failure
143252190Srpaulo *------------------------------------------------------------------------*/
144252190Srpaulostatic usb_error_t
145252190Srpaulousb_handle_set_config(struct usb_xfer *xfer, uint8_t conf_no)
146252190Srpaulo{
147252190Srpaulo	struct usb_device *udev = xfer->xroot->udev;
148252190Srpaulo	usb_error_t err = 0;
149252190Srpaulo
150252190Srpaulo	/*
151252190Srpaulo	 * We need to protect against other threads doing probe and
152252190Srpaulo	 * attach:
153252190Srpaulo	 */
154252190Srpaulo	USB_XFER_UNLOCK(xfer);
155252190Srpaulo
156252190Srpaulo	usbd_enum_lock(udev);
157252190Srpaulo
158252190Srpaulo	if (conf_no == USB_UNCONFIG_NO) {
159252190Srpaulo		conf_no = USB_UNCONFIG_INDEX;
160252190Srpaulo	} else {
161252190Srpaulo		/*
162252190Srpaulo		 * The relationship between config number and config index
163252190Srpaulo		 * is very simple in our case:
164252190Srpaulo		 */
165252190Srpaulo		conf_no--;
166252190Srpaulo	}
167252190Srpaulo
168252190Srpaulo	if (usbd_set_config_index(udev, conf_no)) {
169252190Srpaulo		DPRINTF("set config %d failed\n", conf_no);
170252190Srpaulo		err = USB_ERR_STALLED;
171252190Srpaulo		goto done;
172252190Srpaulo	}
173252190Srpaulo	if (usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY)) {
174252190Srpaulo		DPRINTF("probe and attach failed\n");
175252190Srpaulo		err = USB_ERR_STALLED;
176252190Srpaulo		goto done;
177252190Srpaulo	}
178252190Srpaulodone:
179252190Srpaulo	usbd_enum_unlock(udev);
180252190Srpaulo	USB_XFER_LOCK(xfer);
181252190Srpaulo	return (err);
182252190Srpaulo}
183252190Srpaulo
184252190Srpaulostatic usb_error_t
185252190Srpaulousb_check_alt_setting(struct usb_device *udev,
186252190Srpaulo     struct usb_interface *iface, uint8_t alt_index)
187252190Srpaulo{
188252190Srpaulo	uint8_t do_unlock;
189252190Srpaulo	usb_error_t err = 0;
190252190Srpaulo
191252190Srpaulo	/* automatic locking */
192252190Srpaulo	if (usbd_enum_is_locked(udev)) {
193252190Srpaulo		do_unlock = 0;
194252190Srpaulo	} else {
195252190Srpaulo		do_unlock = 1;
196252190Srpaulo		usbd_enum_lock(udev);
197252190Srpaulo	}
198252190Srpaulo
199252190Srpaulo	if (alt_index >= usbd_get_no_alts(udev->cdesc, iface->idesc))
200252190Srpaulo		err = USB_ERR_INVAL;
201252190Srpaulo
202252190Srpaulo	if (do_unlock)
203252190Srpaulo		usbd_enum_unlock(udev);
204252190Srpaulo
205252190Srpaulo	return (err);
206252190Srpaulo}
207252190Srpaulo
208252190Srpaulo/*------------------------------------------------------------------------*
209252190Srpaulo *	usb_handle_iface_request
210252190Srpaulo *
211252190Srpaulo * Returns:
212252190Srpaulo *    0: Success
213252190Srpaulo * Else: Failure
214252190Srpaulo *------------------------------------------------------------------------*/
215252190Srpaulostatic usb_error_t
216252190Srpaulousb_handle_iface_request(struct usb_xfer *xfer,
217252190Srpaulo    void **ppdata, uint16_t *plen,
218252190Srpaulo    struct usb_device_request req, uint16_t off, uint8_t state)
219252190Srpaulo{
220252190Srpaulo	struct usb_interface *iface;
221252190Srpaulo	struct usb_interface *iface_parent;	/* parent interface */
222252190Srpaulo	struct usb_device *udev = xfer->xroot->udev;
223252190Srpaulo	int error;
224252190Srpaulo	uint8_t iface_index;
225252190Srpaulo	uint8_t temp_state;
226252190Srpaulo
227252190Srpaulo	if ((req.bmRequestType & 0x1F) == UT_INTERFACE) {
228252190Srpaulo		iface_index = req.wIndex[0];	/* unicast */
229252190Srpaulo	} else {
230252190Srpaulo		iface_index = 0;	/* broadcast */
231252190Srpaulo	}
232252190Srpaulo
233252190Srpaulo	/*
234252190Srpaulo	 * We need to protect against other threads doing probe and
235252190Srpaulo	 * attach:
236252190Srpaulo	 */
237252190Srpaulo	USB_XFER_UNLOCK(xfer);
238252190Srpaulo
239252190Srpaulo	usbd_enum_lock(udev);
240252190Srpaulo
241252190Srpaulo	error = ENXIO;
242252190Srpaulo
243252190Srpaulotr_repeat:
244252190Srpaulo	iface = usbd_get_iface(udev, iface_index);
245252190Srpaulo	if ((iface == NULL) ||
246252190Srpaulo	    (iface->idesc == NULL)) {
247252190Srpaulo		/* end of interfaces non-existing interface */
248252190Srpaulo		goto tr_stalled;
249252190Srpaulo	}
250252190Srpaulo	/* set initial state */
251252190Srpaulo
252252190Srpaulo	temp_state = state;
253252190Srpaulo
254252190Srpaulo	/* forward request to interface, if any */
255252190Srpaulo
256252190Srpaulo	if ((error != 0) &&
257252190Srpaulo	    (error != ENOTTY) &&
258252190Srpaulo	    (iface->subdev != NULL) &&
259252190Srpaulo	    device_is_attached(iface->subdev)) {
260252190Srpaulo#if 0
261252190Srpaulo		DEVMETHOD(usb_handle_request, NULL);	/* dummy */
262252190Srpaulo#endif
263252190Srpaulo		error = USB_HANDLE_REQUEST(iface->subdev,
264252190Srpaulo		    &req, ppdata, plen,
265252190Srpaulo		    off, &temp_state);
266252190Srpaulo	}
267252190Srpaulo	iface_parent = usbd_get_iface(udev, iface->parent_iface_index);
268252190Srpaulo
269252190Srpaulo	if ((iface_parent == NULL) ||
270252190Srpaulo	    (iface_parent->idesc == NULL)) {
271252190Srpaulo		/* non-existing interface */
272252190Srpaulo		iface_parent = NULL;
273252190Srpaulo	}
274252190Srpaulo	/* forward request to parent interface, if any */
275252190Srpaulo
276252190Srpaulo	if ((error != 0) &&
277252190Srpaulo	    (error != ENOTTY) &&
278252190Srpaulo	    (iface_parent != NULL) &&
279252190Srpaulo	    (iface_parent->subdev != NULL) &&
280252190Srpaulo	    ((req.bmRequestType & 0x1F) == UT_INTERFACE) &&
281252190Srpaulo	    (iface_parent->subdev != iface->subdev) &&
282252190Srpaulo	    device_is_attached(iface_parent->subdev)) {
283252190Srpaulo		error = USB_HANDLE_REQUEST(iface_parent->subdev,
284252190Srpaulo		    &req, ppdata, plen, off, &temp_state);
285252190Srpaulo	}
286252190Srpaulo	if (error == 0) {
287252190Srpaulo		/* negativly adjust pointer and length */
288252190Srpaulo		*ppdata = ((uint8_t *)(*ppdata)) - off;
289252190Srpaulo		*plen += off;
290252190Srpaulo
291252190Srpaulo		if ((state == USB_HR_NOT_COMPLETE) &&
292252190Srpaulo		    (temp_state == USB_HR_COMPLETE_OK))
293252190Srpaulo			goto tr_short;
294252190Srpaulo		else
295252190Srpaulo			goto tr_valid;
296252190Srpaulo	} else if (error == ENOTTY) {
297252190Srpaulo		goto tr_stalled;
298252190Srpaulo	}
299252190Srpaulo	if ((req.bmRequestType & 0x1F) != UT_INTERFACE) {
300252190Srpaulo		iface_index++;		/* iterate */
301252190Srpaulo		goto tr_repeat;
302252190Srpaulo	}
303252190Srpaulo	if (state != USB_HR_NOT_COMPLETE) {
304252190Srpaulo		/* we are complete */
305252190Srpaulo		goto tr_valid;
306252190Srpaulo	}
307252190Srpaulo	switch (req.bmRequestType) {
308252190Srpaulo	case UT_WRITE_INTERFACE:
309252190Srpaulo		switch (req.bRequest) {
310252190Srpaulo		case UR_SET_INTERFACE:
311252190Srpaulo			/*
312252190Srpaulo			 * We assume that the endpoints are the same
313252190Srpaulo			 * accross the alternate settings.
314252190Srpaulo			 *
315252190Srpaulo			 * Reset the endpoints, because re-attaching
316252190Srpaulo			 * only a part of the device is not possible.
317252190Srpaulo			 */
318252190Srpaulo			error = usb_check_alt_setting(udev,
319252190Srpaulo			    iface, req.wValue[0]);
320252190Srpaulo			if (error) {
321252190Srpaulo				DPRINTF("alt setting does not exist %s\n",
322252190Srpaulo				    usbd_errstr(error));
323252190Srpaulo				goto tr_stalled;
324252190Srpaulo			}
325252190Srpaulo			error = usb_reset_iface_endpoints(udev, iface_index);
326252190Srpaulo			if (error) {
327252190Srpaulo				DPRINTF("alt setting failed %s\n",
328252190Srpaulo				    usbd_errstr(error));
329252190Srpaulo				goto tr_stalled;
330252190Srpaulo			}
331252190Srpaulo			/* update the current alternate setting */
332252190Srpaulo			iface->alt_index = req.wValue[0];
333252190Srpaulo			break;
334252190Srpaulo
335252190Srpaulo		default:
336252190Srpaulo			goto tr_stalled;
337252190Srpaulo		}
338252190Srpaulo		break;
339252190Srpaulo
340252190Srpaulo	case UT_READ_INTERFACE:
341252190Srpaulo		switch (req.bRequest) {
342252190Srpaulo		case UR_GET_INTERFACE:
343252190Srpaulo			*ppdata = &iface->alt_index;
344252190Srpaulo			*plen = 1;
345252190Srpaulo			break;
346252190Srpaulo
347252190Srpaulo		default:
348252190Srpaulo			goto tr_stalled;
349252190Srpaulo		}
350252190Srpaulo		break;
351252190Srpaulo	default:
352252190Srpaulo		goto tr_stalled;
353252190Srpaulo	}
354252190Srpaulotr_valid:
355252190Srpaulo	usbd_enum_unlock(udev);
356252190Srpaulo	USB_XFER_LOCK(xfer);
357252190Srpaulo	return (0);
358252190Srpaulo
359252190Srpaulotr_short:
360252190Srpaulo	usbd_enum_unlock(udev);
361252190Srpaulo	USB_XFER_LOCK(xfer);
362252190Srpaulo	return (USB_ERR_SHORT_XFER);
363252190Srpaulo
364252190Srpaulotr_stalled:
365252190Srpaulo	usbd_enum_unlock(udev);
366252190Srpaulo	USB_XFER_LOCK(xfer);
367252190Srpaulo	return (USB_ERR_STALLED);
368252190Srpaulo}
369252190Srpaulo
370252190Srpaulo/*------------------------------------------------------------------------*
371252190Srpaulo *	usb_handle_stall
372252190Srpaulo *
373252190Srpaulo * Returns:
374252190Srpaulo *    0: Success
375252190Srpaulo * Else: Failure
376252190Srpaulo *------------------------------------------------------------------------*/
377252190Srpaulostatic usb_error_t
378252190Srpaulousb_handle_set_stall(struct usb_xfer *xfer, uint8_t ep, uint8_t do_stall)
379252190Srpaulo{
380252190Srpaulo	struct usb_device *udev = xfer->xroot->udev;
381252190Srpaulo	usb_error_t err;
382252190Srpaulo
383252190Srpaulo	USB_XFER_UNLOCK(xfer);
384252190Srpaulo	err = usbd_set_endpoint_stall(udev,
385252190Srpaulo	    usbd_get_ep_by_addr(udev, ep), do_stall);
386252190Srpaulo	USB_XFER_LOCK(xfer);
387252190Srpaulo	return (err);
388252190Srpaulo}
389252190Srpaulo
390252190Srpaulo/*------------------------------------------------------------------------*
391252190Srpaulo *	usb_handle_get_stall
392252190Srpaulo *
393252190Srpaulo * Returns:
394252190Srpaulo *    0: Success
395252190Srpaulo * Else: Failure
396252190Srpaulo *------------------------------------------------------------------------*/
397252190Srpaulostatic uint8_t
398252190Srpaulousb_handle_get_stall(struct usb_device *udev, uint8_t ea_val)
399252190Srpaulo{
400252190Srpaulo	struct usb_endpoint *ep;
401252190Srpaulo	uint8_t halted;
402252190Srpaulo
403252190Srpaulo	ep = usbd_get_ep_by_addr(udev, ea_val);
404252190Srpaulo	if (ep == NULL) {
405252190Srpaulo		/* nothing to do */
406252190Srpaulo		return (0);
407252190Srpaulo	}
408252190Srpaulo	USB_BUS_LOCK(udev->bus);
409252190Srpaulo	halted = ep->is_stalled;
410252190Srpaulo	USB_BUS_UNLOCK(udev->bus);
411252190Srpaulo
412252190Srpaulo	return (halted);
413252190Srpaulo}
414252190Srpaulo
415252190Srpaulo/*------------------------------------------------------------------------*
416252190Srpaulo *	usb_handle_remote_wakeup
417252190Srpaulo *
418252190Srpaulo * Returns:
419252190Srpaulo *    0: Success
420252190Srpaulo * Else: Failure
421252190Srpaulo *------------------------------------------------------------------------*/
422252190Srpaulostatic usb_error_t
423252190Srpaulousb_handle_remote_wakeup(struct usb_xfer *xfer, uint8_t is_on)
424252190Srpaulo{
425252190Srpaulo	struct usb_device *udev;
426252190Srpaulo	struct usb_bus *bus;
427252190Srpaulo
428252190Srpaulo	udev = xfer->xroot->udev;
429252190Srpaulo	bus = udev->bus;
430252190Srpaulo
431252190Srpaulo	USB_BUS_LOCK(bus);
432252190Srpaulo
433252190Srpaulo	if (is_on) {
434252190Srpaulo		udev->flags.remote_wakeup = 1;
435252190Srpaulo	} else {
436252190Srpaulo		udev->flags.remote_wakeup = 0;
437252190Srpaulo	}
438252190Srpaulo
439252190Srpaulo	USB_BUS_UNLOCK(bus);
440252190Srpaulo
441252190Srpaulo	/* In case we are out of sync, update the power state. */
442252190Srpaulo	usb_bus_power_update(udev->bus);
443252190Srpaulo	return (0);			/* success */
444252190Srpaulo}
445252190Srpaulo
446252190Srpaulo/*------------------------------------------------------------------------*
447252190Srpaulo *	usb_handle_request
448252190Srpaulo *
449252190Srpaulo * Internal state sequence:
450252190Srpaulo *
451252190Srpaulo * USB_HR_NOT_COMPLETE -> USB_HR_COMPLETE_OK v USB_HR_COMPLETE_ERR
452252190Srpaulo *
453252190Srpaulo * Returns:
454252190Srpaulo * 0: Ready to start hardware
455252190Srpaulo * Else: Stall current transfer, if any
456252190Srpaulo *------------------------------------------------------------------------*/
457252190Srpaulostatic usb_error_t
458252190Srpaulousb_handle_request(struct usb_xfer *xfer)
459252190Srpaulo{
460252190Srpaulo	struct usb_device_request req;
461252190Srpaulo	struct usb_device *udev;
462252190Srpaulo	const void *src_zcopy;		/* zero-copy source pointer */
463252190Srpaulo	const void *src_mcopy;		/* non zero-copy source pointer */
464252190Srpaulo	uint16_t off;			/* data offset */
465252190Srpaulo	uint16_t rem;			/* data remainder */
466252190Srpaulo	uint16_t max_len;		/* max fragment length */
467252190Srpaulo	uint16_t wValue;
468252190Srpaulo	uint16_t wIndex;
469252190Srpaulo	uint8_t state;
470252190Srpaulo	uint8_t is_complete = 1;
471252190Srpaulo	usb_error_t err;
472252190Srpaulo	union {
473252190Srpaulo		uWord	wStatus;
474252190Srpaulo		uint8_t	buf[2];
475252190Srpaulo	}     temp;
476252190Srpaulo
477252190Srpaulo	/*
478252190Srpaulo	 * Filter the USB transfer state into
479252190Srpaulo	 * something which we understand:
480252190Srpaulo	 */
481252190Srpaulo
482252190Srpaulo	switch (USB_GET_STATE(xfer)) {
483252190Srpaulo	case USB_ST_SETUP:
484252190Srpaulo		state = USB_HR_NOT_COMPLETE;
485252190Srpaulo
486252190Srpaulo		if (!xfer->flags_int.control_act) {
487252190Srpaulo			/* nothing to do */
488252190Srpaulo			goto tr_stalled;
489252190Srpaulo		}
490252190Srpaulo		break;
491252190Srpaulo	case USB_ST_TRANSFERRED:
492252190Srpaulo		if (!xfer->flags_int.control_act) {
493252190Srpaulo			state = USB_HR_COMPLETE_OK;
494252190Srpaulo		} else {
495252190Srpaulo			state = USB_HR_NOT_COMPLETE;
496252190Srpaulo		}
497252190Srpaulo		break;
498252190Srpaulo	default:
499252190Srpaulo		state = USB_HR_COMPLETE_ERR;
500252190Srpaulo		break;
501252190Srpaulo	}
502252190Srpaulo
503252190Srpaulo	/* reset frame stuff */
504252190Srpaulo
505252190Srpaulo	usbd_xfer_set_frame_len(xfer, 0, 0);
506252190Srpaulo
507252190Srpaulo	usbd_xfer_set_frame_offset(xfer, 0, 0);
508252190Srpaulo	usbd_xfer_set_frame_offset(xfer, sizeof(req), 1);
509252190Srpaulo
510252190Srpaulo	/* get the current request, if any */
511252190Srpaulo
512252190Srpaulo	usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req));
513252190Srpaulo
514252190Srpaulo	if (xfer->flags_int.control_rem == 0xFFFF) {
515252190Srpaulo		/* first time - not initialised */
516252190Srpaulo		rem = UGETW(req.wLength);
517252190Srpaulo		off = 0;
518252190Srpaulo	} else {
519252190Srpaulo		/* not first time - initialised */
520252190Srpaulo		rem = xfer->flags_int.control_rem;
521252190Srpaulo		off = UGETW(req.wLength) - rem;
522252190Srpaulo	}
523252190Srpaulo
524252190Srpaulo	/* set some defaults */
525252190Srpaulo
526252190Srpaulo	max_len = 0;
527252190Srpaulo	src_zcopy = NULL;
528252190Srpaulo	src_mcopy = NULL;
529252190Srpaulo	udev = xfer->xroot->udev;
530252190Srpaulo
531252190Srpaulo	/* get some request fields decoded */
532252190Srpaulo
533252190Srpaulo	wValue = UGETW(req.wValue);
534252190Srpaulo	wIndex = UGETW(req.wIndex);
535252190Srpaulo
536252190Srpaulo	DPRINTF("req 0x%02x 0x%02x 0x%04x 0x%04x "
537252190Srpaulo	    "off=0x%x rem=0x%x, state=%d\n", req.bmRequestType,
538252190Srpaulo	    req.bRequest, wValue, wIndex, off, rem, state);
539252190Srpaulo
540252190Srpaulo	/* demultiplex the control request */
541252190Srpaulo
542252190Srpaulo	switch (req.bmRequestType) {
543252190Srpaulo	case UT_READ_DEVICE:
544252190Srpaulo		if (state != USB_HR_NOT_COMPLETE) {
545252190Srpaulo			break;
546252190Srpaulo		}
547252190Srpaulo		switch (req.bRequest) {
548252190Srpaulo		case UR_GET_DESCRIPTOR:
549252190Srpaulo			goto tr_handle_get_descriptor;
550252190Srpaulo		case UR_GET_CONFIG:
551252190Srpaulo			goto tr_handle_get_config;
552252190Srpaulo		case UR_GET_STATUS:
553252190Srpaulo			goto tr_handle_get_status;
554252190Srpaulo		default:
555252190Srpaulo			goto tr_stalled;
556252190Srpaulo		}
557252190Srpaulo		break;
558252190Srpaulo
559252190Srpaulo	case UT_WRITE_DEVICE:
560252190Srpaulo		switch (req.bRequest) {
561252190Srpaulo		case UR_SET_ADDRESS:
562252190Srpaulo			goto tr_handle_set_address;
563252190Srpaulo		case UR_SET_CONFIG:
564252190Srpaulo			goto tr_handle_set_config;
565252190Srpaulo		case UR_CLEAR_FEATURE:
566252190Srpaulo			switch (wValue) {
567252190Srpaulo			case UF_DEVICE_REMOTE_WAKEUP:
568252190Srpaulo				goto tr_handle_clear_wakeup;
569252190Srpaulo			default:
570252190Srpaulo				goto tr_stalled;
571252190Srpaulo			}
572252190Srpaulo			break;
573252190Srpaulo		case UR_SET_FEATURE:
574252190Srpaulo			switch (wValue) {
575252190Srpaulo			case UF_DEVICE_REMOTE_WAKEUP:
576252190Srpaulo				goto tr_handle_set_wakeup;
577252190Srpaulo			default:
578252190Srpaulo				goto tr_stalled;
579252190Srpaulo			}
580252190Srpaulo			break;
581252190Srpaulo		default:
582252190Srpaulo			goto tr_stalled;
583252190Srpaulo		}
584252190Srpaulo		break;
585252190Srpaulo
586252190Srpaulo	case UT_WRITE_ENDPOINT:
587252190Srpaulo		switch (req.bRequest) {
588252190Srpaulo		case UR_CLEAR_FEATURE:
589252190Srpaulo			switch (wValue) {
590252190Srpaulo			case UF_ENDPOINT_HALT:
591252190Srpaulo				goto tr_handle_clear_halt;
592252190Srpaulo			default:
593252190Srpaulo				goto tr_stalled;
594252190Srpaulo			}
595252190Srpaulo			break;
596252190Srpaulo		case UR_SET_FEATURE:
597252190Srpaulo			switch (wValue) {
598252190Srpaulo			case UF_ENDPOINT_HALT:
599252190Srpaulo				goto tr_handle_set_halt;
600252190Srpaulo			default:
601252190Srpaulo				goto tr_stalled;
602252190Srpaulo			}
603252190Srpaulo			break;
604252190Srpaulo		default:
605252190Srpaulo			goto tr_stalled;
606252190Srpaulo		}
607252190Srpaulo		break;
608252190Srpaulo
609252190Srpaulo	case UT_READ_ENDPOINT:
610252190Srpaulo		switch (req.bRequest) {
611252190Srpaulo		case UR_GET_STATUS:
612252190Srpaulo			goto tr_handle_get_ep_status;
613252190Srpaulo		default:
614252190Srpaulo			goto tr_stalled;
615252190Srpaulo		}
616252190Srpaulo		break;
617252190Srpaulo	default:
618252190Srpaulo		/* we use "USB_ADD_BYTES" to de-const the src_zcopy */
619252190Srpaulo		err = usb_handle_iface_request(xfer,
620252190Srpaulo		    USB_ADD_BYTES(&src_zcopy, 0),
621252190Srpaulo		    &max_len, req, off, state);
622252190Srpaulo		if (err == 0) {
623252190Srpaulo			is_complete = 0;
624252190Srpaulo			goto tr_valid;
625252190Srpaulo		} else if (err == USB_ERR_SHORT_XFER) {
626252190Srpaulo			goto tr_valid;
627252190Srpaulo		}
628252190Srpaulo		/*
629252190Srpaulo		 * Reset zero-copy pointer and max length
630252190Srpaulo		 * variable in case they were unintentionally
631252190Srpaulo		 * set:
632252190Srpaulo		 */
633252190Srpaulo		src_zcopy = NULL;
634252190Srpaulo		max_len = 0;
635252190Srpaulo
636252190Srpaulo		/*
637252190Srpaulo		 * Check if we have a vendor specific
638252190Srpaulo		 * descriptor:
639252190Srpaulo		 */
640252190Srpaulo		goto tr_handle_get_descriptor;
641252190Srpaulo	}
642252190Srpaulo	goto tr_valid;
643252190Srpaulo
644252190Srpaulotr_handle_get_descriptor:
645252190Srpaulo	err = (usb_temp_get_desc_p) (udev, &req, &src_zcopy, &max_len);
646252190Srpaulo	if (err)
647252190Srpaulo		goto tr_stalled;
648252190Srpaulo	if (src_zcopy == NULL)
649252190Srpaulo		goto tr_stalled;
650252190Srpaulo	goto tr_valid;
651252190Srpaulo
652252190Srpaulotr_handle_get_config:
653252190Srpaulo	temp.buf[0] = udev->curr_config_no;
654252190Srpaulo	src_mcopy = temp.buf;
655252190Srpaulo	max_len = 1;
656252190Srpaulo	goto tr_valid;
657252190Srpaulo
658252190Srpaulotr_handle_get_status:
659252190Srpaulo
660252190Srpaulo	wValue = 0;
661252190Srpaulo
662252190Srpaulo	USB_BUS_LOCK(udev->bus);
663252190Srpaulo	if (udev->flags.remote_wakeup) {
664252190Srpaulo		wValue |= UDS_REMOTE_WAKEUP;
665252190Srpaulo	}
666252190Srpaulo	if (udev->flags.self_powered) {
667252190Srpaulo		wValue |= UDS_SELF_POWERED;
668252190Srpaulo	}
669252190Srpaulo	USB_BUS_UNLOCK(udev->bus);
670252190Srpaulo
671252190Srpaulo	USETW(temp.wStatus, wValue);
672252190Srpaulo	src_mcopy = temp.wStatus;
673252190Srpaulo	max_len = sizeof(temp.wStatus);
674252190Srpaulo	goto tr_valid;
675252190Srpaulo
676252190Srpaulotr_handle_set_address:
677252190Srpaulo	if (state == USB_HR_NOT_COMPLETE) {
678252190Srpaulo		if (wValue >= 0x80) {
679252190Srpaulo			/* invalid value */
680252190Srpaulo			goto tr_stalled;
681252190Srpaulo		} else if (udev->curr_config_no != 0) {
682252190Srpaulo			/* we are configured ! */
683252190Srpaulo			goto tr_stalled;
684252190Srpaulo		}
685252190Srpaulo	} else if (state != USB_HR_NOT_COMPLETE) {
686252190Srpaulo		udev->address = (wValue & 0x7F);
687252190Srpaulo		goto tr_bad_context;
688252190Srpaulo	}
689252190Srpaulo	goto tr_valid;
690252190Srpaulo
691252190Srpaulotr_handle_set_config:
692252190Srpaulo	if (state == USB_HR_NOT_COMPLETE) {
693252190Srpaulo		if (usb_handle_set_config(xfer, req.wValue[0])) {
694252190Srpaulo			goto tr_stalled;
695252190Srpaulo		}
696252190Srpaulo	}
697252190Srpaulo	goto tr_valid;
698252190Srpaulo
699252190Srpaulotr_handle_clear_halt:
700252190Srpaulo	if (state == USB_HR_NOT_COMPLETE) {
701252190Srpaulo		if (usb_handle_set_stall(xfer, req.wIndex[0], 0)) {
702252190Srpaulo			goto tr_stalled;
703252190Srpaulo		}
704252190Srpaulo	}
705252190Srpaulo	goto tr_valid;
706252190Srpaulo
707252190Srpaulotr_handle_clear_wakeup:
708252190Srpaulo	if (state == USB_HR_NOT_COMPLETE) {
709252190Srpaulo		if (usb_handle_remote_wakeup(xfer, 0)) {
710252190Srpaulo			goto tr_stalled;
711252190Srpaulo		}
712252190Srpaulo	}
713252190Srpaulo	goto tr_valid;
714252190Srpaulo
715252190Srpaulotr_handle_set_halt:
716252190Srpaulo	if (state == USB_HR_NOT_COMPLETE) {
717252190Srpaulo		if (usb_handle_set_stall(xfer, req.wIndex[0], 1)) {
718252190Srpaulo			goto tr_stalled;
719252190Srpaulo		}
720252190Srpaulo	}
721252190Srpaulo	goto tr_valid;
722252190Srpaulo
723252190Srpaulotr_handle_set_wakeup:
724252190Srpaulo	if (state == USB_HR_NOT_COMPLETE) {
725252190Srpaulo		if (usb_handle_remote_wakeup(xfer, 1)) {
726252190Srpaulo			goto tr_stalled;
727252190Srpaulo		}
728252190Srpaulo	}
729252190Srpaulo	goto tr_valid;
730252190Srpaulo
731252190Srpaulotr_handle_get_ep_status:
732252190Srpaulo	if (state == USB_HR_NOT_COMPLETE) {
733252190Srpaulo		temp.wStatus[0] =
734252190Srpaulo		    usb_handle_get_stall(udev, req.wIndex[0]);
735252190Srpaulo		temp.wStatus[1] = 0;
736252190Srpaulo		src_mcopy = temp.wStatus;
737252190Srpaulo		max_len = sizeof(temp.wStatus);
738252190Srpaulo	}
739252190Srpaulo	goto tr_valid;
740252190Srpaulo
741252190Srpaulotr_valid:
742252190Srpaulo	if (state != USB_HR_NOT_COMPLETE) {
743252190Srpaulo		goto tr_stalled;
744252190Srpaulo	}
745252190Srpaulo	/* subtract offset from length */
746252190Srpaulo
747252190Srpaulo	max_len -= off;
748252190Srpaulo
749252190Srpaulo	/* Compute the real maximum data length */
750252190Srpaulo
751252190Srpaulo	if (max_len > xfer->max_data_length) {
752252190Srpaulo		max_len = usbd_xfer_max_len(xfer);
753252190Srpaulo	}
754252190Srpaulo	if (max_len > rem) {
755252190Srpaulo		max_len = rem;
756252190Srpaulo	}
757252190Srpaulo	/*
758252190Srpaulo	 * If the remainder is greater than the maximum data length,
759252190Srpaulo	 * we need to truncate the value for the sake of the
760252190Srpaulo	 * comparison below:
761252190Srpaulo	 */
762252190Srpaulo	if (rem > xfer->max_data_length) {
763252190Srpaulo		rem = usbd_xfer_max_len(xfer);
764252190Srpaulo	}
765252190Srpaulo	if ((rem != max_len) && (is_complete != 0)) {
766252190Srpaulo		/*
767252190Srpaulo	         * If we don't transfer the data we can transfer, then
768252190Srpaulo	         * the transfer is short !
769252190Srpaulo	         */
770252190Srpaulo		xfer->flags.force_short_xfer = 1;
771252190Srpaulo		xfer->nframes = 2;
772252190Srpaulo	} else {
773252190Srpaulo		/*
774252190Srpaulo		 * Default case
775252190Srpaulo		 */
776252190Srpaulo		xfer->flags.force_short_xfer = 0;
777252190Srpaulo		xfer->nframes = max_len ? 2 : 1;
778252190Srpaulo	}
779252190Srpaulo	if (max_len > 0) {
780252190Srpaulo		if (src_mcopy) {
781252190Srpaulo			src_mcopy = USB_ADD_BYTES(src_mcopy, off);
782252190Srpaulo			usbd_copy_in(xfer->frbuffers + 1, 0,
783252190Srpaulo			    src_mcopy, max_len);
784252190Srpaulo			usbd_xfer_set_frame_len(xfer, 1, max_len);
785252190Srpaulo		} else {
786252190Srpaulo			usbd_xfer_set_frame_data(xfer, 1,
787252190Srpaulo			    USB_ADD_BYTES(src_zcopy, off), max_len);
788252190Srpaulo		}
789252190Srpaulo	} else {
790252190Srpaulo		/* the end is reached, send status */
791252190Srpaulo		xfer->flags.manual_status = 0;
792252190Srpaulo		usbd_xfer_set_frame_len(xfer, 1, 0);
793252190Srpaulo	}
794252190Srpaulo	DPRINTF("success\n");
795252190Srpaulo	return (0);			/* success */
796252190Srpaulo
797252190Srpaulotr_stalled:
798252190Srpaulo	DPRINTF("%s\n", (state != USB_HR_NOT_COMPLETE) ?
799252190Srpaulo	    "complete" : "stalled");
800252190Srpaulo	return (USB_ERR_STALLED);
801252190Srpaulo
802252190Srpaulotr_bad_context:
803252190Srpaulo	DPRINTF("bad context\n");
804252190Srpaulo	return (USB_ERR_BAD_CONTEXT);
805252190Srpaulo}
806252190Srpaulo