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