1253544Shselasky/*-
2253544Shselasky * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
3253544Shselasky *
4253544Shselasky * Redistribution and use in source and binary forms, with or without
5253544Shselasky * modification, are permitted provided that the following conditions
6253544Shselasky * are met:
7253544Shselasky * 1. Redistributions of source code must retain the above copyright
8253544Shselasky *    notice, this list of conditions and the following disclaimer.
9253544Shselasky * 2. Redistributions in binary form must reproduce the above copyright
10253544Shselasky *    notice, this list of conditions and the following disclaimer in the
11253544Shselasky *    documentation and/or other materials provided with the distribution.
12253544Shselasky *
13253544Shselasky * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14253544Shselasky * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15253544Shselasky * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16253544Shselasky * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17253544Shselasky * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18253544Shselasky * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19253544Shselasky * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20253544Shselasky * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21253544Shselasky * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22253544Shselasky * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23253544Shselasky * SUCH DAMAGE.
24253544Shselasky */
25253544Shselasky
26253544Shselasky/*
27253544Shselasky * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
28253544Shselasky */
29253544Shselasky
30253618Sobrien#include <sys/param.h>
31253618Sobrien__FBSDID("$FreeBSD$");
32253618Sobrien
33253544Shselasky#include <sys/stdint.h>
34253544Shselasky#include <sys/stddef.h>
35253544Shselasky#include <sys/queue.h>
36253544Shselasky#include <sys/systm.h>
37253544Shselasky#include <sys/kernel.h>
38253544Shselasky#include <sys/bus.h>
39253544Shselasky#include <sys/linker_set.h>
40253544Shselasky#include <sys/module.h>
41253544Shselasky#include <sys/lock.h>
42253544Shselasky#include <sys/mutex.h>
43253544Shselasky#include <sys/condvar.h>
44253544Shselasky#include <sys/sysctl.h>
45253544Shselasky#include <sys/sx.h>
46253544Shselasky#include <sys/unistd.h>
47253544Shselasky#include <sys/callout.h>
48253544Shselasky#include <sys/malloc.h>
49253544Shselasky#include <sys/priv.h>
50253544Shselasky
51253544Shselasky#include <dev/usb/usb.h>
52253544Shselasky#include <dev/usb/usbdi.h>
53253544Shselasky#include <dev/usb/usbdi_util.h>
54253544Shselasky#include <dev/usb/usbhid.h>
55253544Shselasky#include "usb_if.h"
56253544Shselasky
57253544Shselasky#define	USB_DEBUG_VAR g_mouse_debug
58253544Shselasky#include <dev/usb/usb_debug.h>
59253544Shselasky
60253544Shselasky#include <dev/usb/gadget/g_mouse.h>
61253544Shselasky
62253544Shselaskystatic SYSCTL_NODE(_hw_usb, OID_AUTO, g_mouse, CTLFLAG_RW, 0, "USB mouse gadget");
63253544Shselasky
64253544Shselasky#ifdef USB_DEBUG
65253544Shselaskystatic int g_mouse_debug = 0;
66253544Shselasky
67276701ShselaskySYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, debug, CTLFLAG_RWTUN,
68253544Shselasky    &g_mouse_debug, 0, "Debug level");
69253544Shselasky#endif
70253544Shselasky
71253544Shselaskystatic int g_mouse_mode = 0;
72253544Shselasky
73276701ShselaskySYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, mode, CTLFLAG_RWTUN,
74253544Shselasky    &g_mouse_mode, 0, "Mode selection");
75253544Shselasky
76253544Shselaskystatic int g_mouse_button_press_interval = 0;
77253544Shselasky
78276701ShselaskySYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, button_press_interval, CTLFLAG_RWTUN,
79253544Shselasky    &g_mouse_button_press_interval, 0, "Mouse button update interval in milliseconds");
80253544Shselasky
81253544Shselaskystatic int g_mouse_cursor_update_interval = 1023;
82253544Shselasky
83276701ShselaskySYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, cursor_update_interval, CTLFLAG_RWTUN,
84253544Shselasky    &g_mouse_cursor_update_interval, 0, "Mouse cursor update interval in milliseconds");
85253544Shselasky
86253544Shselaskystatic int g_mouse_cursor_radius = 128;
87253544Shselasky
88276701ShselaskySYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, cursor_radius, CTLFLAG_RWTUN,
89253544Shselasky    &g_mouse_cursor_radius, 0, "Mouse cursor radius in pixels");
90253544Shselasky
91253544Shselaskystruct g_mouse_data {
92253544Shselasky	uint8_t buttons;
93253544Shselasky#define	BUT_0 0x01
94253544Shselasky#define	BUT_1 0x02
95253544Shselasky#define	BUT_2 0x04
96253544Shselasky	int8_t dx;
97253544Shselasky	int8_t dy;
98253544Shselasky	int8_t dz;
99253544Shselasky};
100253544Shselasky
101253544Shselaskyenum {
102253544Shselasky	G_MOUSE_INTR_DT,
103253544Shselasky	G_MOUSE_N_TRANSFER,
104253544Shselasky};
105253544Shselasky
106253544Shselaskystruct g_mouse_softc {
107253544Shselasky	struct mtx sc_mtx;
108253544Shselasky	struct usb_callout sc_button_press_callout;
109253544Shselasky	struct usb_callout sc_cursor_update_callout;
110253544Shselasky	struct g_mouse_data sc_data;
111253544Shselasky	struct usb_xfer *sc_xfer[G_MOUSE_N_TRANSFER];
112253544Shselasky
113253544Shselasky	int	sc_mode;
114253544Shselasky	int	sc_radius;
115253544Shselasky	int	sc_last_x_state;
116253544Shselasky	int	sc_last_y_state;
117253544Shselasky	int	sc_curr_x_state;
118253544Shselasky	int	sc_curr_y_state;
119253544Shselasky	int	sc_tick;
120253544Shselasky
121253544Shselasky	uint8_t sc_do_cursor_update;
122253544Shselasky	uint8_t sc_do_button_update;
123253544Shselasky};
124253544Shselasky
125253544Shselaskystatic device_probe_t g_mouse_probe;
126253544Shselaskystatic device_attach_t g_mouse_attach;
127253544Shselaskystatic device_detach_t g_mouse_detach;
128253544Shselaskystatic usb_handle_request_t g_mouse_handle_request;
129253544Shselaskystatic usb_callback_t g_mouse_intr_callback;
130253544Shselasky
131253544Shselaskystatic devclass_t g_mouse_devclass;
132253544Shselasky
133253544Shselaskystatic device_method_t g_mouse_methods[] = {
134253544Shselasky	/* USB interface */
135253544Shselasky	DEVMETHOD(usb_handle_request, g_mouse_handle_request),
136253544Shselasky
137253544Shselasky	/* Device interface */
138253544Shselasky	DEVMETHOD(device_probe, g_mouse_probe),
139253544Shselasky	DEVMETHOD(device_attach, g_mouse_attach),
140253544Shselasky	DEVMETHOD(device_detach, g_mouse_detach),
141253544Shselasky
142253544Shselasky	DEVMETHOD_END
143253544Shselasky};
144253544Shselasky
145253544Shselaskystatic driver_t g_mouse_driver = {
146253544Shselasky	.name = "g_mouse",
147253544Shselasky	.methods = g_mouse_methods,
148253544Shselasky	.size = sizeof(struct g_mouse_softc),
149253544Shselasky};
150253544Shselasky
151253544ShselaskyDRIVER_MODULE(g_mouse, uhub, g_mouse_driver, g_mouse_devclass, 0, 0);
152253544ShselaskyMODULE_DEPEND(g_mouse, usb, 1, 1, 1);
153253544Shselasky
154253544Shselaskystatic const struct usb_config g_mouse_config[G_MOUSE_N_TRANSFER] = {
155253544Shselasky
156253544Shselasky	[G_MOUSE_INTR_DT] = {
157253544Shselasky		.type = UE_INTERRUPT,
158253544Shselasky		.endpoint = UE_ADDR_ANY,
159253544Shselasky		.direction = UE_DIR_IN,
160253544Shselasky		.flags = {.ext_buffer = 1,.pipe_bof = 1,},
161253544Shselasky		.bufsize = sizeof(struct g_mouse_data),
162253544Shselasky		.callback = &g_mouse_intr_callback,
163253544Shselasky		.frames = 1,
164253544Shselasky		.usb_mode = USB_MODE_DEVICE,
165253544Shselasky	},
166253544Shselasky};
167253544Shselasky
168253544Shselaskystatic void g_mouse_button_press_timeout(void *arg);
169253544Shselaskystatic void g_mouse_cursor_update_timeout(void *arg);
170253544Shselasky
171253544Shselaskystatic void
172253544Shselaskyg_mouse_button_press_timeout_reset(struct g_mouse_softc *sc)
173253544Shselasky{
174253544Shselasky	int i = g_mouse_button_press_interval;
175253544Shselasky
176253544Shselasky	if (i <= 0) {
177253544Shselasky		sc->sc_data.buttons = 0;
178253544Shselasky		sc->sc_do_button_update = 0;
179253544Shselasky	} else {
180253544Shselasky		sc->sc_do_button_update = 1;
181253544Shselasky	}
182253544Shselasky
183253544Shselasky	if ((i <= 0) || (i > 1023))
184253544Shselasky		i = 1023;
185253544Shselasky
186253544Shselasky	i = USB_MS_TO_TICKS(i);
187253544Shselasky
188253544Shselasky	usb_callout_reset(&sc->sc_button_press_callout, i,
189253544Shselasky	    &g_mouse_button_press_timeout, sc);
190253544Shselasky}
191253544Shselasky
192253544Shselaskystatic void
193253544Shselaskyg_mouse_cursor_update_timeout_reset(struct g_mouse_softc *sc)
194253544Shselasky{
195253544Shselasky	int i = g_mouse_cursor_update_interval;
196253544Shselasky
197253544Shselasky	if (i <= 0) {
198253544Shselasky		sc->sc_data.dx = 0;
199253544Shselasky		sc->sc_data.dy = 0;
200253544Shselasky		sc->sc_do_cursor_update = 0;
201253544Shselasky		sc->sc_tick = 0;
202253544Shselasky	} else {
203253544Shselasky		sc->sc_do_cursor_update = 1;
204253544Shselasky	}
205253544Shselasky
206253544Shselasky	if ((i <= 0) || (i > 1023))
207253544Shselasky		i = 1023;
208253544Shselasky
209253544Shselasky	i = USB_MS_TO_TICKS(i);
210253544Shselasky
211253544Shselasky	usb_callout_reset(&sc->sc_cursor_update_callout, i,
212253544Shselasky	    &g_mouse_cursor_update_timeout, sc);
213253544Shselasky}
214253544Shselasky
215253544Shselaskystatic void
216253544Shselaskyg_mouse_update_mode_radius(struct g_mouse_softc *sc)
217253544Shselasky{
218253544Shselasky	sc->sc_mode = g_mouse_mode;
219253544Shselasky	sc->sc_radius = g_mouse_cursor_radius;
220253544Shselasky
221253544Shselasky	if (sc->sc_radius < 0)
222253544Shselasky		sc->sc_radius = 0;
223253544Shselasky	else if (sc->sc_radius > 1023)
224253544Shselasky		sc->sc_radius = 1023;
225253544Shselasky}
226253544Shselasky
227253544Shselaskystatic void
228253544Shselaskyg_mouse_button_press_timeout(void *arg)
229253544Shselasky{
230253544Shselasky	struct g_mouse_softc *sc = arg;
231253544Shselasky
232253544Shselasky	g_mouse_update_mode_radius(sc);
233253544Shselasky
234253544Shselasky	DPRINTFN(11, "Timeout %p (button press)\n", sc->sc_xfer[G_MOUSE_INTR_DT]);
235253544Shselasky
236253544Shselasky	g_mouse_button_press_timeout_reset(sc);
237253544Shselasky
238253544Shselasky	usbd_transfer_start(sc->sc_xfer[G_MOUSE_INTR_DT]);
239253544Shselasky}
240253544Shselasky
241253544Shselaskystatic void
242253544Shselaskyg_mouse_cursor_update_timeout(void *arg)
243253544Shselasky{
244253544Shselasky	struct g_mouse_softc *sc = arg;
245253544Shselasky
246253544Shselasky	g_mouse_update_mode_radius(sc);
247253544Shselasky
248253544Shselasky	DPRINTFN(11, "Timeout %p (cursor update)\n", sc->sc_xfer[G_MOUSE_INTR_DT]);
249253544Shselasky
250253544Shselasky	g_mouse_cursor_update_timeout_reset(sc);
251253544Shselasky
252253544Shselasky	usbd_transfer_start(sc->sc_xfer[G_MOUSE_INTR_DT]);
253253544Shselasky}
254253544Shselasky
255253544Shselaskystatic int
256253544Shselaskyg_mouse_probe(device_t dev)
257253544Shselasky{
258253544Shselasky	struct usb_attach_arg *uaa = device_get_ivars(dev);
259253544Shselasky
260253544Shselasky	DPRINTFN(11, "\n");
261253544Shselasky
262253544Shselasky	if (uaa->usb_mode != USB_MODE_DEVICE)
263253544Shselasky		return (ENXIO);
264253544Shselasky
265253544Shselasky	if ((uaa->info.bInterfaceClass == UICLASS_HID) &&
266253544Shselasky	    (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
267253544Shselasky	    (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE))
268253544Shselasky		return (0);
269253544Shselasky
270253544Shselasky	return (ENXIO);
271253544Shselasky}
272253544Shselasky
273253544Shselaskystatic int
274253544Shselaskyg_mouse_attach(device_t dev)
275253544Shselasky{
276253544Shselasky	struct g_mouse_softc *sc = device_get_softc(dev);
277253544Shselasky	struct usb_attach_arg *uaa = device_get_ivars(dev);
278253544Shselasky	int error;
279253544Shselasky
280253544Shselasky	DPRINTFN(11, "\n");
281253544Shselasky
282253544Shselasky	device_set_usb_desc(dev);
283253544Shselasky
284253544Shselasky	mtx_init(&sc->sc_mtx, "g_mouse", NULL, MTX_DEF);
285253544Shselasky
286253544Shselasky	usb_callout_init_mtx(&sc->sc_button_press_callout, &sc->sc_mtx, 0);
287253544Shselasky	usb_callout_init_mtx(&sc->sc_cursor_update_callout, &sc->sc_mtx, 0);
288253544Shselasky
289253544Shselasky	sc->sc_mode = G_MOUSE_MODE_SILENT;
290253544Shselasky
291253544Shselasky	error = usbd_transfer_setup(uaa->device,
292253544Shselasky	    &uaa->info.bIfaceIndex, sc->sc_xfer, g_mouse_config,
293253544Shselasky	    G_MOUSE_N_TRANSFER, sc, &sc->sc_mtx);
294253544Shselasky
295253544Shselasky	if (error) {
296253544Shselasky		DPRINTF("error=%s\n", usbd_errstr(error));
297253544Shselasky		goto detach;
298253544Shselasky	}
299253544Shselasky
300253544Shselasky	mtx_lock(&sc->sc_mtx);
301253544Shselasky	g_mouse_button_press_timeout_reset(sc);
302253544Shselasky	g_mouse_cursor_update_timeout_reset(sc);
303253544Shselasky	mtx_unlock(&sc->sc_mtx);
304253544Shselasky
305253544Shselasky	return (0);			/* success */
306253544Shselasky
307253544Shselaskydetach:
308253544Shselasky	g_mouse_detach(dev);
309253544Shselasky
310253544Shselasky	return (ENXIO);			/* error */
311253544Shselasky}
312253544Shselasky
313253544Shselaskystatic int
314253544Shselaskyg_mouse_detach(device_t dev)
315253544Shselasky{
316253544Shselasky	struct g_mouse_softc *sc = device_get_softc(dev);
317253544Shselasky
318253544Shselasky	DPRINTF("\n");
319253544Shselasky
320253544Shselasky	mtx_lock(&sc->sc_mtx);
321253544Shselasky	usb_callout_stop(&sc->sc_button_press_callout);
322253544Shselasky	usb_callout_stop(&sc->sc_cursor_update_callout);
323253544Shselasky	mtx_unlock(&sc->sc_mtx);
324253544Shselasky
325253544Shselasky	usbd_transfer_unsetup(sc->sc_xfer, G_MOUSE_N_TRANSFER);
326253544Shselasky
327253544Shselasky	usb_callout_drain(&sc->sc_button_press_callout);
328253544Shselasky	usb_callout_drain(&sc->sc_cursor_update_callout);
329253544Shselasky
330253544Shselasky	mtx_destroy(&sc->sc_mtx);
331253544Shselasky
332253544Shselasky	return (0);
333253544Shselasky}
334253544Shselasky
335253544Shselaskystatic void
336253544Shselaskyg_mouse_intr_callback(struct usb_xfer *xfer, usb_error_t error)
337253544Shselasky{
338253544Shselasky	struct g_mouse_softc *sc = usbd_xfer_softc(xfer);
339253544Shselasky	int actlen;
340253544Shselasky	int aframes;
341253544Shselasky	int dx;
342253544Shselasky	int dy;
343253544Shselasky	int radius;
344253544Shselasky
345253544Shselasky	usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
346253544Shselasky
347253544Shselasky	DPRINTF("st=%d aframes=%d actlen=%d bytes\n",
348253544Shselasky	    USB_GET_STATE(xfer), aframes, actlen);
349253544Shselasky
350253544Shselasky	switch (USB_GET_STATE(xfer)) {
351253544Shselasky	case USB_ST_TRANSFERRED:
352253544Shselasky		if (!(sc->sc_do_cursor_update || sc->sc_do_button_update))
353253544Shselasky			break;
354253544Shselasky
355253544Shselasky	case USB_ST_SETUP:
356253544Shselaskytr_setup:
357253544Shselasky
358253544Shselasky	  if (sc->sc_do_cursor_update) {
359253544Shselasky		sc->sc_do_cursor_update = 0;
360253544Shselasky		sc->sc_tick += 80;
361253544Shselasky		if ((sc->sc_tick < 0) || (sc->sc_tick > 7999))
362253544Shselasky			sc->sc_tick = 0;
363253544Shselasky	  }
364253544Shselasky
365253544Shselasky	  if (sc->sc_do_button_update) {
366253544Shselasky			sc->sc_do_button_update = 0;
367253544Shselasky			sc->sc_data.buttons ^= BUT_0;
368253544Shselasky	  }
369253544Shselasky
370253544Shselasky	  radius = sc->sc_radius;
371253544Shselasky
372253544Shselasky		switch (sc->sc_mode) {
373253544Shselasky		case G_MOUSE_MODE_SILENT:
374253544Shselasky			sc->sc_data.buttons = 0;
375253544Shselasky			break;
376253544Shselasky		case G_MOUSE_MODE_SPIRAL:
377253544Shselasky			radius = (radius * (8000-sc->sc_tick)) / 8000;
378253544Shselasky		case G_MOUSE_MODE_CIRCLE:
379253544Shselasky			/* TODO */
380253544Shselasky			sc->sc_curr_y_state = 0;
381253544Shselasky			sc->sc_curr_x_state = 0;
382253544Shselasky			break;
383253544Shselasky		case G_MOUSE_MODE_BOX:
384253544Shselasky			if (sc->sc_tick < 2000) {
385253544Shselasky				sc->sc_curr_x_state = (sc->sc_tick * radius) / 2000;
386253544Shselasky				sc->sc_curr_y_state = 0;
387253544Shselasky			} else if (sc->sc_tick < 4000) {
388253544Shselasky				sc->sc_curr_x_state = radius;
389253544Shselasky				sc->sc_curr_y_state = -(((sc->sc_tick - 2000) * radius) / 2000);
390253544Shselasky			} else if (sc->sc_tick < 6000) {
391253544Shselasky				sc->sc_curr_x_state = radius - (((sc->sc_tick - 4000) * radius) / 2000);
392253544Shselasky				sc->sc_curr_y_state = -radius;
393253544Shselasky			} else {
394253544Shselasky				sc->sc_curr_x_state = 0;
395253544Shselasky				sc->sc_curr_y_state = -radius + (((sc->sc_tick - 6000) * radius) / 2000);
396253544Shselasky			}
397253544Shselasky			break;
398253544Shselasky		default:
399253544Shselasky			break;
400253544Shselasky		}
401253544Shselasky
402253544Shselasky		dx = sc->sc_curr_x_state - sc->sc_last_x_state;
403253544Shselasky		dy = sc->sc_curr_y_state - sc->sc_last_y_state;
404253544Shselasky
405253544Shselasky		if (dx < -63)
406253544Shselasky		  dx = -63;
407253544Shselasky		else if (dx > 63)
408253544Shselasky		  dx = 63;
409253544Shselasky
410253544Shselasky		if (dy < -63)
411253544Shselasky		  dy = -63;
412253544Shselasky		else if (dy > 63)
413253544Shselasky		  dy = 63;
414253544Shselasky
415253544Shselasky		sc->sc_last_x_state += dx;
416253544Shselasky		sc->sc_last_y_state += dy;
417253544Shselasky
418253544Shselasky		sc->sc_data.dx = dx;
419253544Shselasky		sc->sc_data.dy = dy;
420253544Shselasky
421253544Shselasky		usbd_xfer_set_frame_data(xfer, 0, &sc->sc_data, sizeof(sc->sc_data));
422253544Shselasky		usbd_xfer_set_frames(xfer, 1);
423253544Shselasky		usbd_transfer_submit(xfer);
424253544Shselasky		break;
425253544Shselasky
426253544Shselasky	default:			/* Error */
427253544Shselasky		DPRINTF("error=%s\n", usbd_errstr(error));
428253544Shselasky
429253544Shselasky		if (error != USB_ERR_CANCELLED) {
430253544Shselasky			/* try to clear stall first */
431253544Shselasky			usbd_xfer_set_stall(xfer);
432253544Shselasky			goto tr_setup;
433253544Shselasky		}
434253544Shselasky		break;
435253544Shselasky	}
436253544Shselasky}
437253544Shselasky
438253544Shselaskystatic int
439253544Shselaskyg_mouse_handle_request(device_t dev,
440253544Shselasky    const void *preq, void **pptr, uint16_t *plen,
441253544Shselasky    uint16_t offset, uint8_t *pstate)
442253544Shselasky{
443253544Shselasky	const struct usb_device_request *req = preq;
444253544Shselasky	uint8_t is_complete = *pstate;
445253544Shselasky
446253544Shselasky	if (!is_complete) {
447253544Shselasky		if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
448253544Shselasky		    (req->bRequest == UR_SET_PROTOCOL) &&
449253544Shselasky		    (req->wValue[0] == 0x00) &&
450253544Shselasky		    (req->wValue[1] == 0x00)) {
451253544Shselasky			*plen = 0;
452253544Shselasky			return (0);
453253544Shselasky		}
454253544Shselasky	}
455253544Shselasky	return (ENXIO);			/* use builtin handler */
456253544Shselasky}
457