xref: /freebsd/sys/dev/usb/input/uhid_snes.c (revision 9768746b)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright 2013, Michael Terrell <vashisnotatree@gmail.com>
5  * Copyright 2018, Johannes Lundberg <johalun0@gmail.com>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $FreeBSD$
30  */
31 
32 #include <sys/param.h>
33 #include <sys/module.h>
34 #include <sys/kernel.h>
35 #include <sys/systm.h>
36 
37 #include <sys/conf.h>
38 #include <sys/bus.h>
39 #include <sys/lock.h>
40 #include <sys/mutex.h>
41 #include <sys/syslog.h>
42 #include <sys/fcntl.h>
43 
44 #include <dev/usb/usb.h>
45 #include <dev/usb/usbdi.h>
46 #include <dev/usb/usbdi_util.h>
47 
48 #include <dev/usb/usbhid.h>
49 #include <dev/usb/usb_ioctl.h>
50 
51 #include "usb_rdesc.h"
52 
53 #define	UHID_SNES_IFQ_MAX_LEN 8
54 
55 #define	UREQ_GET_PORT_STATUS 0x01
56 #define	UREQ_SOFT_RESET      0x02
57 
58 #define	UP      0x7f00
59 #define	DOWN    0x7fff
60 #define	LEFT    0x00ff
61 #define	RIGHT   0xff7f
62 #define	X       0x1f
63 #define	Y       0x8f
64 #define	A       0x2f
65 #define	B       0x4f
66 #define	SELECT  0x10
67 #define	START   0x20
68 #define	LEFT_T  0x01
69 #define	RIGHT_T 0x02
70 
71 static const uint8_t uhid_snes_report_descr[] = { UHID_SNES_REPORT_DESCR() };
72 #define	SNES_DEV(v,p,i) { USB_VPI(v,p,i) }
73 
74 static const STRUCT_USB_HOST_ID snes_devs[] = {
75 	SNES_DEV(0x0810, 0xe501, 0), /* GeeekPi K-0161 */
76 	SNES_DEV(0x0079, 0x0011, 0)  /* Dragonrise */
77 };
78 
79 enum {
80 	UHID_SNES_INTR_DT_RD,
81 	UHID_SNES_STATUS_DT_RD,
82 	UHID_SNES_N_TRANSFER
83 };
84 
85 struct uhid_snes_softc {
86 	device_t sc_dev;
87 	struct usb_device *sc_usb_device;
88 	struct mtx sc_mutex;
89 	struct usb_callout sc_watchdog;
90 	uint8_t sc_iface_num;
91 	struct usb_xfer *sc_transfer[UHID_SNES_N_TRANSFER];
92 	struct usb_fifo_sc sc_fifo;
93 	struct usb_fifo_sc sc_fifo_no_reset;
94 	int sc_fflags;
95 	struct usb_fifo *sc_fifo_open[2];
96 	uint8_t sc_zero_length_packets;
97 	uint8_t sc_iid;
98 	uint8_t sc_oid;
99 	uint8_t sc_fid;
100 	uint8_t sc_iface_index;
101 
102 	uint32_t sc_isize;
103 	uint32_t sc_osize;
104 	uint32_t sc_fsize;
105 
106 	void *sc_repdesc_ptr;
107 
108 	uint16_t sc_repdesc_size;
109 
110 	struct usb_device *sc_udev;
111 #define	UHID_FLAG_IMMED        0x01	/* set if read should be immediate */
112 
113 };
114 
115 static device_probe_t uhid_snes_probe;
116 static device_attach_t uhid_snes_attach;
117 static device_detach_t uhid_snes_detach;
118 
119 static usb_fifo_open_t uhid_snes_open;
120 static usb_fifo_close_t uhid_snes_close;
121 static usb_fifo_ioctl_t uhid_snes_ioctl;
122 static usb_fifo_cmd_t uhid_snes_start_read;
123 static usb_fifo_cmd_t uhid_snes_stop_read;
124 
125 static void uhid_snes_reset(struct uhid_snes_softc *);
126 static void uhid_snes_watchdog(void *);
127 
128 static usb_callback_t uhid_snes_read_callback;
129 static usb_callback_t uhid_snes_status_callback;
130 
131 static struct usb_fifo_methods uhid_snes_fifo_methods = {
132 	.f_open = &uhid_snes_open,
133 	.f_close = &uhid_snes_close,
134 	.f_ioctl = &uhid_snes_ioctl,
135 	.f_start_read = &uhid_snes_start_read,
136 	.f_stop_read = &uhid_snes_stop_read,
137 	.basename[0] = "uhid_snes"
138 };
139 
140 static const struct usb_config uhid_snes_config[UHID_SNES_N_TRANSFER] = {
141 	[UHID_SNES_INTR_DT_RD] = {
142 		.callback = &uhid_snes_read_callback,
143 		.bufsize = sizeof(struct usb_device_request) +1,
144 		.flags = {.short_xfer_ok = 1, .short_frames_ok = 1,
145 			  .pipe_bof =1, .proxy_buffer =1},
146 		.type = UE_INTERRUPT,
147 		.endpoint = 0x81,
148 		.direction = UE_DIR_IN
149 	},
150 	[UHID_SNES_STATUS_DT_RD] = {
151 		.callback = &uhid_snes_status_callback,
152 		.bufsize = sizeof(struct usb_device_request) + 1,
153 		.timeout = 1000,
154 		.type = UE_CONTROL,
155 		.endpoint = 0x00,
156 		.direction = UE_DIR_ANY
157 	}
158 };
159 
160 static int
161 uhid_get_report(struct uhid_snes_softc *sc, uint8_t type,
162     uint8_t id, void *kern_data, void *user_data, uint16_t len)
163 {
164 	int err;
165 	uint8_t free_data = 0;
166 
167 	if (kern_data == NULL) {
168 		kern_data = malloc(len, M_USBDEV, M_WAITOK);
169 		free_data = 1;
170 	}
171 	err = usbd_req_get_report(sc->sc_udev, NULL, kern_data,
172 	    len, sc->sc_iface_index, type, id);
173 	if (err) {
174 		err = ENXIO;
175 		goto done;
176 	}
177 	if (user_data) {
178 		/* dummy buffer */
179 		err = copyout(kern_data, user_data, len);
180 		if (err) {
181 			goto done;
182 		}
183 	}
184 done:
185 	if (free_data) {
186 		free(kern_data, M_USBDEV);
187 	}
188 	return (err);
189 }
190 
191 static int
192 uhid_set_report(struct uhid_snes_softc *sc, uint8_t type,
193     uint8_t id, void *kern_data, void *user_data, uint16_t len)
194 {
195 	int err;
196 	uint8_t free_data = 0;
197 
198 	if (kern_data == NULL) {
199 		kern_data = malloc(len, M_USBDEV, M_WAITOK);
200 		free_data = 1;
201 		err = copyin(user_data, kern_data, len);
202 		if (err) {
203 			goto done;
204 		}
205 	}
206 	err = usbd_req_set_report(sc->sc_udev, NULL, kern_data,
207 	    len, sc->sc_iface_index, type, id);
208 	if (err) {
209 		err = ENXIO;
210 		goto done;
211 	}
212 done:
213 	if (free_data) {
214 		free(kern_data, M_USBDEV);
215 	}
216 	return (err);
217 }
218 
219 static int
220 uhid_snes_open(struct usb_fifo *fifo, int fflags)
221 {
222 	struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
223 	int error;
224 
225 	if (sc->sc_fflags & fflags) {
226 		uhid_snes_reset(sc);
227 		return (EBUSY);
228 	}
229 
230 	mtx_lock(&sc->sc_mutex);
231 	usbd_xfer_set_stall(sc->sc_transfer[UHID_SNES_INTR_DT_RD]);
232 	mtx_unlock(&sc->sc_mutex);
233 
234 	error = usb_fifo_alloc_buffer(fifo,
235 	    usbd_xfer_max_len(sc->sc_transfer[UHID_SNES_INTR_DT_RD]),
236 	    UHID_SNES_IFQ_MAX_LEN);
237 	if (error)
238 		return (ENOMEM);
239 
240 	sc->sc_fifo_open[USB_FIFO_RX] = fifo;
241 
242 	return (0);
243 }
244 
245 static void
246 uhid_snes_reset(struct uhid_snes_softc *sc)
247 {
248 	struct usb_device_request req;
249 	int error;
250 
251 	req.bRequest = UREQ_SOFT_RESET;
252 	USETW(req.wValue, 0);
253 	USETW(req.wIndex, sc->sc_iface_num);
254 	USETW(req.wLength, 0);
255 
256 	mtx_lock(&sc->sc_mutex);
257 
258 	error = usbd_do_request_flags(sc->sc_usb_device, &sc->sc_mutex,
259 	    &req, NULL, 0, NULL, 2 * USB_MS_HZ);
260 
261 	if (error) {
262 		usbd_do_request_flags(sc->sc_usb_device, &sc->sc_mutex,
263 		    &req, NULL, 0, NULL, 2 * USB_MS_HZ);
264 	}
265 
266 	mtx_unlock(&sc->sc_mutex);
267 }
268 
269 static void
270 uhid_snes_close(struct usb_fifo *fifo, int fflags)
271 {
272 	struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
273 
274 	sc->sc_fflags &= ~(fflags & FREAD);
275 	usb_fifo_free_buffer(fifo);
276 }
277 
278 static int
279 uhid_snes_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, int fflags)
280 {
281 	struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
282 	struct usb_gen_descriptor *ugd;
283 #ifdef COMPAT_FREEBSD32
284 	struct usb_gen_descriptor local_ugd;
285 	struct usb_gen_descriptor32 *ugd32 = NULL;
286 #endif
287 	uint32_t size;
288 	int error = 0;
289 	uint8_t id;
290 
291 	ugd = data;
292 #ifdef COMPAT_FREEBSD32
293 	switch (cmd) {
294 	case USB_GET_REPORT_DESC32:
295 	case USB_GET_REPORT32:
296 	case USB_SET_REPORT32:
297 		ugd32 = data;
298 		ugd = &local_ugd;
299 		usb_gen_descriptor_from32(ugd, ugd32);
300 		cmd = _IOC_NEWTYPE(cmd, struct usb_gen_descriptor);
301 		break;
302 	}
303 #endif
304 
305 	switch (cmd) {
306 	case USB_GET_REPORT_DESC:
307 		if (sc->sc_repdesc_size > ugd->ugd_maxlen) {
308 			size = ugd->ugd_maxlen;
309 		} else {
310 			size = sc->sc_repdesc_size;
311 		}
312 
313 		ugd->ugd_actlen = size;
314 		if (ugd->ugd_data == NULL)
315 			break; /* descriptor length only*/
316 		error = copyout(sc->sc_repdesc_ptr, ugd->ugd_data, size);
317 		break;
318 
319 	case USB_SET_IMMED:
320 		if (!(fflags & FREAD)) {
321 			error = EPERM;
322 			break;
323 		}
324 
325 		if (*(int *)data) {
326 			/* do a test read */
327 			error = uhid_get_report(sc, UHID_INPUT_REPORT,
328 			    sc->sc_iid, NULL, NULL, sc->sc_isize);
329 			if (error) {
330 				break;
331 			}
332 			mtx_lock(&sc->sc_mutex);
333 			sc->sc_fflags |= UHID_FLAG_IMMED;
334 			mtx_unlock(&sc->sc_mutex);
335 		} else {
336 			mtx_lock(&sc->sc_mutex);
337 			sc->sc_fflags &= ~UHID_FLAG_IMMED;
338 			mtx_unlock(&sc->sc_mutex);
339 		}
340 		break;
341 
342 	case USB_GET_REPORT:
343 		if (!(fflags & FREAD)) {
344 			error = EPERM;
345 			break;
346 		}
347 		switch (ugd->ugd_report_type) {
348 		case UHID_INPUT_REPORT:
349 			size = sc->sc_isize;
350 			id = sc->sc_iid;
351 			break;
352 		case UHID_OUTPUT_REPORT:
353 			size = sc->sc_osize;
354 			id = sc->sc_oid;
355 			break;
356 		case UHID_FEATURE_REPORT:
357 			size = sc->sc_fsize;
358 			id = sc->sc_fid;
359 			break;
360 		default:
361 			return (EINVAL);
362 		}
363 		if (id != 0)
364 			copyin(ugd->ugd_data, &id, 1);
365 		error = uhid_get_report(sc, ugd->ugd_report_type, id,
366 		    NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size));
367 		break;
368 
369 	case USB_SET_REPORT:
370 		if (!(fflags & FWRITE)) {
371 			error = EPERM;
372 			break;
373 		}
374 		switch (ugd->ugd_report_type) {
375 		case UHID_INPUT_REPORT:
376 			size = sc->sc_isize;
377 			id = sc->sc_iid;
378 			break;
379 		case UHID_OUTPUT_REPORT:
380 			size = sc->sc_osize;
381 			id = sc->sc_oid;
382 			break;
383 		case UHID_FEATURE_REPORT:
384 			size = sc->sc_fsize;
385 			id = sc->sc_fid;
386 			break;
387 		default:
388 			return (EINVAL);
389 		}
390 		if (id != 0)
391 			copyin(ugd->ugd_data, &id, 1);
392 		error = uhid_set_report(sc, ugd->ugd_report_type, id,
393 		    NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size));
394 		break;
395 
396 	case USB_GET_REPORT_ID:
397 		/* XXX: we only support reportid 0? */
398 		*(int *)data = 0;
399 		break;
400 
401 	default:
402 		error = EINVAL;
403 		break;
404 	}
405 
406 #ifdef COMPAT_FREEBSD32
407 	if (ugd32 != NULL)
408 		update_usb_gen_descriptor32(ugd32, ugd);
409 #endif
410 	return (error);
411 }
412 
413 static void
414 uhid_snes_watchdog(void *arg)
415 {
416 	struct uhid_snes_softc *sc = arg;
417 
418 	mtx_assert(&sc->sc_mutex, MA_OWNED);
419 
420 	if (sc->sc_fflags == 0)
421 		usbd_transfer_start(sc->sc_transfer[UHID_SNES_STATUS_DT_RD]);
422 
423 	usb_callout_reset(&sc->sc_watchdog, hz, &uhid_snes_watchdog, sc);
424 }
425 
426 static void
427 uhid_snes_start_read(struct usb_fifo *fifo)
428 {
429 	struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
430 
431 	usbd_transfer_start(sc->sc_transfer[UHID_SNES_INTR_DT_RD]);
432 }
433 
434 static void
435 uhid_snes_stop_read(struct usb_fifo *fifo)
436 {
437 	struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
438 
439 	usbd_transfer_stop(sc->sc_transfer[UHID_SNES_INTR_DT_RD]);
440 }
441 
442 static void
443 uhid_snes_read_callback(struct usb_xfer *transfer, usb_error_t error)
444 {
445 	struct uhid_snes_softc *sc = usbd_xfer_softc(transfer);
446 	struct usb_fifo *fifo = sc->sc_fifo_open[USB_FIFO_RX];
447 	struct usb_page_cache *pc;
448 	int actual, max;
449 
450 	usbd_xfer_status(transfer, &actual, NULL, NULL, NULL);
451 	if (fifo == NULL)
452 		return;
453 
454 	switch (USB_GET_STATE(transfer)) {
455 	case USB_ST_TRANSFERRED:
456 		if (actual == 0) {
457 			if (sc->sc_zero_length_packets == 4)
458 				/* Throttle transfers. */
459 				usbd_xfer_set_interval(transfer, 500);
460 			else
461 				sc->sc_zero_length_packets++;
462 
463 		} else {
464 			/* disable throttling. */
465 			usbd_xfer_set_interval(transfer, 0);
466 			sc->sc_zero_length_packets = 0;
467 		}
468 		pc = usbd_xfer_get_frame(transfer, 0);
469 		usb_fifo_put_data(fifo, pc, 0, actual, 1);
470 		/* Fall through */
471 	setup:
472 	case USB_ST_SETUP:
473 		if (usb_fifo_put_bytes_max(fifo) != 0) {
474 			max = usbd_xfer_max_len(transfer);
475 			usbd_xfer_set_frame_len(transfer, 0, max);
476 			usbd_transfer_submit(transfer);
477 		}
478 		break;
479 
480 	default:
481 		/*disable throttling. */
482 		usbd_xfer_set_interval(transfer, 0);
483 		sc->sc_zero_length_packets = 0;
484 
485 		if (error != USB_ERR_CANCELLED) {
486 			/* Issue a clear-stall request. */
487 			usbd_xfer_set_stall(transfer);
488 			goto setup;
489 		}
490 		break;
491 	}
492 }
493 
494 static void
495 uhid_snes_status_callback(struct usb_xfer *transfer, usb_error_t error)
496 {
497 	struct uhid_snes_softc *sc = usbd_xfer_softc(transfer);
498 	struct usb_device_request req;
499 	struct usb_page_cache *pc;
500 
501 	switch (USB_GET_STATE(transfer)) {
502 	case USB_ST_SETUP:
503 		req.bmRequestType = UT_READ_CLASS_INTERFACE;
504 		req.bRequest = UREQ_GET_PORT_STATUS;
505 		USETW(req.wValue, 0);
506 		req.wIndex[0] = sc->sc_iface_num;
507 		req.wIndex[1] = 0;
508 		USETW(req.wLength, 1);
509 
510 		pc = usbd_xfer_get_frame(transfer, 0);
511 		usbd_copy_in(pc, 0, &req, sizeof(req));
512 		usbd_xfer_set_frame_len(transfer, 0, sizeof(req));
513 		usbd_xfer_set_frame_len(transfer, 1, 1);
514 		usbd_xfer_set_frames(transfer, 2);
515 		usbd_transfer_submit(transfer);
516 		break;
517 
518 	default:
519 		break;
520 	}
521 
522 }
523 
524 static int
525 uhid_snes_probe(device_t dev)
526 {
527 	struct usb_attach_arg *uaa = device_get_ivars(dev);
528 
529 	if (uaa->usb_mode != USB_MODE_HOST)
530 		return (ENXIO);
531 
532 	return (usbd_lookup_id_by_uaa(snes_devs, sizeof(snes_devs), uaa));
533 }
534 
535 static int
536 uhid_snes_attach(device_t dev)
537 {
538 	struct usb_attach_arg *uaa = device_get_ivars(dev);
539 	struct uhid_snes_softc *sc = device_get_softc(dev);
540 	struct usb_interface_descriptor *idesc;
541 	struct usb_config_descriptor *cdesc;
542 	uint8_t alt_index, iface_index = uaa->info.bIfaceIndex;
543 	int error,unit = device_get_unit(dev);
544 
545 	sc->sc_dev = dev;
546 	sc->sc_usb_device = uaa->device;
547 	device_set_usb_desc(dev);
548 	mtx_init(&sc->sc_mutex, "uhid_snes", NULL, MTX_DEF | MTX_RECURSE);
549 	usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mutex, 0);
550 
551 	idesc = usbd_get_interface_descriptor(uaa->iface);
552 	alt_index = -1;
553 	for(;;) {
554 		if (idesc == NULL)
555 			break;
556 
557 		if ((idesc->bDescriptorType == UDESC_INTERFACE) &&
558 		     (idesc->bLength >= sizeof(*idesc))) {
559 			if (idesc->bInterfaceNumber != uaa->info.bIfaceNum) {
560 				break;
561 			} else {
562 				alt_index++;
563 				if (idesc->bInterfaceClass == UICLASS_HID)
564 					goto found;
565 			}
566 		}
567 
568 		cdesc = usbd_get_config_descriptor(uaa->device);
569 		idesc = (void *)usb_desc_foreach(cdesc, (void *)idesc);
570 		goto found;
571 	}
572 	goto detach;
573 
574 found:
575 	if (alt_index) {
576 		error = usbd_set_alt_interface_index(uaa->device, iface_index, alt_index);
577 		if (error)
578 			goto detach;
579 	}
580 
581 	sc->sc_iface_num = idesc->bInterfaceNumber;
582 
583 	error = usbd_transfer_setup(uaa->device, &iface_index,
584 	    sc->sc_transfer, uhid_snes_config, UHID_SNES_N_TRANSFER, sc,
585 	    &sc->sc_mutex);
586 
587 	if (error)
588 		goto detach;
589 
590 	error = usb_fifo_attach(uaa->device, sc, &sc->sc_mutex,
591 	    &uhid_snes_fifo_methods, &sc->sc_fifo, unit, -1,
592 	    iface_index, UID_ROOT, GID_OPERATOR, 0644);
593 	sc->sc_repdesc_size = sizeof(uhid_snes_report_descr);
594 	sc->sc_repdesc_ptr = __DECONST(void*, &uhid_snes_report_descr);
595 
596 	if (error)
597 		goto detach;
598 
599 	mtx_lock(&sc->sc_mutex);
600 	uhid_snes_watchdog(sc);
601 	mtx_unlock(&sc->sc_mutex);
602 	return (0);
603 
604 detach:
605 	uhid_snes_detach(dev);
606 	return (ENOMEM);
607 }
608 
609 static int
610 uhid_snes_detach(device_t dev)
611 {
612 	struct uhid_snes_softc *sc = device_get_softc(dev);
613 
614 	usb_fifo_detach(&sc->sc_fifo);
615 	usb_fifo_detach(&sc->sc_fifo_no_reset);
616 
617 	mtx_lock(&sc->sc_mutex);
618 	usb_callout_stop(&sc->sc_watchdog);
619 	mtx_unlock(&sc->sc_mutex);
620 
621 	usbd_transfer_unsetup(sc->sc_transfer, UHID_SNES_N_TRANSFER);
622 	usb_callout_drain(&sc->sc_watchdog);
623 	mtx_destroy(&sc->sc_mutex);
624 
625 	return (0);
626 }
627 
628 static device_method_t uhid_snes_methods[] = {
629 	DEVMETHOD(device_probe, uhid_snes_probe),
630 	DEVMETHOD(device_attach, uhid_snes_attach),
631 	DEVMETHOD(device_detach, uhid_snes_detach),
632 	DEVMETHOD_END
633 };
634 
635 static driver_t uhid_snes_driver = {
636 	"uhid_snes",
637 	uhid_snes_methods,
638 	sizeof(struct uhid_snes_softc)
639 };
640 
641 DRIVER_MODULE(uhid_snes, uhub, uhid_snes_driver, NULL, NULL);
642 MODULE_DEPEND(uhid_snes, usb, 1, 1, 1);
643 USB_PNP_HOST_INFO(snes_devs);
644