xref: /openbsd-src/sys/dev/usb/ujoy.c (revision b9ae17a00bed12afbf856d60e03f648694a9de20)
1*b9ae17a0Sguenther /*	$OpenBSD: ujoy.c,v 1.6 2024/12/30 02:46:00 guenther Exp $ */
21d44892eSthfr 
31d44892eSthfr /*
41d44892eSthfr  * Copyright (c) 2021 Thomas Frohwein	<thfr@openbsd.org>
51d44892eSthfr  * Copyright (c) 2021 Bryan Steele	<brynet@openbsd.org>
61d44892eSthfr  *
71d44892eSthfr  * Permission to use, copy, modify, and distribute this software for any
81d44892eSthfr  * purpose with or without fee is hereby granted, provided that the above
91d44892eSthfr  * copyright notice and this permission notice appear in all copies.
101d44892eSthfr  *
111d44892eSthfr  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
121d44892eSthfr  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
131d44892eSthfr  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
141d44892eSthfr  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
151d44892eSthfr  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
161d44892eSthfr  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
171d44892eSthfr  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
181d44892eSthfr  */
191d44892eSthfr 
201d44892eSthfr #include <sys/param.h>
211d44892eSthfr #include <sys/systm.h>
221d44892eSthfr #include <sys/device.h>
231d44892eSthfr #include <sys/ioctl.h>
241d44892eSthfr #include <sys/conf.h>
251d44892eSthfr #include <sys/tty.h>
261d44892eSthfr #include <sys/fcntl.h>
271d44892eSthfr 
281d44892eSthfr #include <dev/usb/usb.h>
291d44892eSthfr #include <dev/usb/usbhid.h>
301d44892eSthfr 
311d44892eSthfr #include <dev/usb/usbdi.h>
321d44892eSthfr 
331d44892eSthfr #include <dev/usb/uhidev.h>
341d44892eSthfr #include <dev/usb/uhid.h>
351d44892eSthfr 
361d44892eSthfr int ujoy_match(struct device *, void *, void *);
371d44892eSthfr 
381d44892eSthfr struct cfdriver ujoy_cd = {
391d44892eSthfr 	NULL, "ujoy", DV_DULL
401d44892eSthfr };
411d44892eSthfr 
421d44892eSthfr const struct cfattach ujoy_ca = {
431d44892eSthfr 	sizeof(struct uhid_softc),
441d44892eSthfr 	ujoy_match,
451d44892eSthfr 	uhid_attach,
461d44892eSthfr 	uhid_detach,
471d44892eSthfr };
481d44892eSthfr 
491d44892eSthfr /*
501d44892eSthfr  * XXX workaround:
511d44892eSthfr  *
521d44892eSthfr  * This is a copy of sys/dev/hid/hid.c:hid_is_collection(), synced up to the
531d44892eSthfr  * NetBSD version.  Our current hid_is_collection() is not playing nice with
541d44892eSthfr  * all HID devices like the PS4 controller.  But applying this version
551d44892eSthfr  * globally breaks other HID devices like ims(4) and imt(4).  Until our global
561d44892eSthfr  * hid_is_collection() can't be fixed to play nice with all HID devices, we
571d44892eSthfr  * go for this dedicated ujoy(4) version.
581d44892eSthfr  */
591d44892eSthfr int
601d44892eSthfr ujoy_hid_is_collection(const void *desc, int size, uint8_t id, int32_t usage)
611d44892eSthfr {
621d44892eSthfr 	struct hid_data *hd;
631d44892eSthfr 	struct hid_item hi;
641d44892eSthfr 	uint32_t coll_usage = ~0;
651d44892eSthfr 
661d44892eSthfr 	hd = hid_start_parse(desc, size, hid_input);
671d44892eSthfr 	if (hd == NULL)
681d44892eSthfr 		return (0);
691d44892eSthfr 
701d44892eSthfr 	while (hid_get_item(hd, &hi)) {
711d44892eSthfr 		if (hi.kind == hid_collection &&
721d44892eSthfr 		    hi.collection == HCOLL_APPLICATION)
731d44892eSthfr 			coll_usage = hi.usage;
741d44892eSthfr 
751d44892eSthfr 		if (hi.kind == hid_endcollection)
761d44892eSthfr 			coll_usage = ~0;
771d44892eSthfr 
781d44892eSthfr 		if (hi.kind == hid_input &&
791d44892eSthfr 		    coll_usage == usage &&
801d44892eSthfr 		    hi.report_ID == id) {
811d44892eSthfr 			hid_end_parse(hd);
821d44892eSthfr 			return (1);
831d44892eSthfr 		}
841d44892eSthfr 	}
851d44892eSthfr 	hid_end_parse(hd);
861d44892eSthfr 
871d44892eSthfr 	return (0);
881d44892eSthfr }
891d44892eSthfr 
901d44892eSthfr int
911d44892eSthfr ujoy_match(struct device *parent, void *match, void *aux)
921d44892eSthfr {
931d44892eSthfr 	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
941d44892eSthfr 	int			  size;
951d44892eSthfr 	void			 *desc;
961d44892eSthfr 	int			  ret = UMATCH_NONE;
971d44892eSthfr 
98faac88c0Santon 	if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
991d44892eSthfr 		return (ret);
1001d44892eSthfr 
1011d44892eSthfr 	/* Find the general usage page and gamecontroller collections */
1021d44892eSthfr 	uhidev_get_report_desc(uha->parent, &desc, &size);
1031d44892eSthfr 
1041d44892eSthfr 	if (ujoy_hid_is_collection(desc, size, uha->reportid,
1051d44892eSthfr 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_JOYSTICK)))
1061d44892eSthfr     		ret = UMATCH_IFACECLASS;
1071d44892eSthfr 
1081d44892eSthfr 	if (ujoy_hid_is_collection(desc, size, uha->reportid,
1091d44892eSthfr 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_GAME_PAD)))
1101d44892eSthfr     		ret = UMATCH_IFACECLASS;
1111d44892eSthfr 
1121d44892eSthfr 	return (ret);
1131d44892eSthfr }
1141d44892eSthfr 
1151d44892eSthfr int
1161d44892eSthfr ujoyopen(dev_t dev, int flag, int mode, struct proc *p)
1171d44892eSthfr {
1181d44892eSthfr 	/* Restrict ujoy devices to read operations */
1191d44892eSthfr 	if ((flag & FWRITE))
1201d44892eSthfr     		return (EPERM);
1211d44892eSthfr 	return (uhid_do_open(dev, flag, mode, p));
1221d44892eSthfr }
1231d44892eSthfr 
1241d44892eSthfr int
1251d44892eSthfr ujoyioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
1261d44892eSthfr {
1271d44892eSthfr 	switch (cmd) {
1281d44892eSthfr 	case FIOASYNC:
1291d44892eSthfr 	case USB_GET_DEVICEINFO:
1301d44892eSthfr     	case USB_GET_REPORT:
1311d44892eSthfr     	case USB_GET_REPORT_DESC:
1321d44892eSthfr     	case USB_GET_REPORT_ID:
1331d44892eSthfr 		break;
1341d44892eSthfr 	default:
1351d44892eSthfr 		return (EPERM);
1361d44892eSthfr 	}
1371d44892eSthfr 
1381d44892eSthfr 	return (uhidioctl(dev, cmd, addr, flag, p));
1391d44892eSthfr }
140