11.1Sbrad/*	$NetBSD: umcpmio_transport.c,v 1.1 2025/11/29 18:39:14 brad Exp $	*/
21.1Sbrad
31.1Sbrad/*
41.1Sbrad * Copyright (c) 2024, 2025 Brad Spencer <brad@anduin.eldar.org>
51.1Sbrad *
61.1Sbrad * Permission to use, copy, modify, and distribute this software for any
71.1Sbrad * purpose with or without fee is hereby granted, provided that the above
81.1Sbrad * copyright notice and this permission notice appear in all copies.
91.1Sbrad *
101.1Sbrad * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
111.1Sbrad * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
121.1Sbrad * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
131.1Sbrad * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
141.1Sbrad * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
151.1Sbrad * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
161.1Sbrad * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
171.1Sbrad */
181.1Sbrad
191.1Sbrad#include <sys/cdefs.h>
201.1Sbrad__KERNEL_RCSID(0, "$NetBSD: umcpmio_transport.c,v 1.1 2025/11/29 18:39:14 brad Exp $");
211.1Sbrad
221.1Sbrad#ifdef _KERNEL_OPT
231.1Sbrad#include "opt_usb.h"
241.1Sbrad#endif
251.1Sbrad
261.1Sbrad#include <sys/param.h>
271.1Sbrad#include <sys/types.h>
281.1Sbrad
291.1Sbrad#include <dev/hid/hid.h>
301.1Sbrad
311.1Sbrad#include <dev/usb/uhidev.h>
321.1Sbrad#include <dev/usb/usb.h>
331.1Sbrad#include <dev/usb/usbdevs.h>
341.1Sbrad#include <dev/usb/usbdi.h>
351.1Sbrad#include <dev/usb/usbdi_util.h>
361.1Sbrad#include <dev/usb/usbhid.h>
371.1Sbrad
381.1Sbrad#include <dev/usb/umcpmio.h>
391.1Sbrad#include <dev/usb/umcpmio_hid_reports.h>
401.1Sbrad#include <dev/usb/umcpmio_transport.h>
411.1Sbrad#include <dev/usb/umcpmio_io.h>
421.1Sbrad#include <dev/usb/umcpmio_subr.h>
431.1Sbrad
441.1Sbrad#define UMCPMIO_DEBUG 1
451.1Sbrad#ifdef UMCPMIO_DEBUG
461.1Sbrad#define DPRINTF(x)	do { if (umcpmiodebug) printf x; } while (0)
471.1Sbrad#define DPRINTFN(n, x)	do { if (umcpmiodebug > (n)) printf x; } while (0)
481.1Sbradextern int umcpmiodebug;
491.1Sbrad#else
501.1Sbrad#define DPRINTF(x)	__nothing
511.1Sbrad#define DPRINTFN(n, x)	__nothing
521.1Sbrad#endif
531.1Sbrad
541.1Sbrad
551.1Sbrad/* Stuff to communicate with the MCP2210 and MCP2221 / MCP2221A */
561.1Sbrad
571.1Sbrad
581.1Sbrad/*
591.1Sbrad * Communication with the HID function requires sending a HID report
601.1Sbrad * request and then waiting for a response.
611.1Sbrad *
621.1Sbrad * The panic that occurs when trying to use the interrupt... i.e.
631.1Sbrad * attaching though this driver seems to be related to the fact that
641.1Sbrad * a spin lock is held and the USB stack wants to wait.
651.1Sbrad *
661.1Sbrad * The USB stack *IS* going to have to wait for the response from
671.1Sbrad * the device, somehow...
681.1Sbrad *
691.1Sbrad * It didn't seem possible to defer the uhidev_write to a thread.
701.1Sbrad * Attempts to yield() while spinning hard also did not work and
711.1Sbrad * not yield()ing didn't allow anything else to run.
721.1Sbrad *
731.1Sbrad */
741.1Sbrad
751.1Sbrad/*
761.1Sbrad * This is the panic you will get:
771.1Sbrad *
781.1Sbrad * panic: kernel diagnostic assertion "ci->ci_mtx_count == -1" failed: file "../../../../kern/kern_synch.c", line 762 mi_switch: cpu0: ci_mtx_count (-2) != -1 (block with spin-mutex held)
791.1Sbrad */
801.1Sbrad
811.1Sbradstatic void
821.1Sbradumcpmio_uhidev_intr(void *cookie, void *ibuf, u_int len)
831.1Sbrad{
841.1Sbrad	struct umcpmio_softc *sc = cookie;
851.1Sbrad
861.1Sbrad	if (sc->sc_dying)
871.1Sbrad		return;
881.1Sbrad
891.1Sbrad	DPRINTFN(30, ("umcpmio_uhidev_intr: len=%d\n", len));
901.1Sbrad
911.1Sbrad	mutex_enter(&sc->sc_res_mutex);
921.1Sbrad	switch (len) {
931.1Sbrad	case MCP2221_RES_BUFFER_SIZE:
941.1Sbrad		if (sc->sc_res_buffer != NULL) {
951.1Sbrad			memcpy(sc->sc_res_buffer, ibuf,
961.1Sbrad			    MCP2221_RES_BUFFER_SIZE);
971.1Sbrad			sc->sc_res_ready = true;
981.1Sbrad			cv_signal(&sc->sc_res_cv);
991.1Sbrad		} else {
1001.1Sbrad			int d = umcpmiodebug;
1011.1Sbrad			device_printf(sc->sc_dev,
1021.1Sbrad			    "umcpmio_uhidev_intr: NULL sc_res_buffer:"
1031.1Sbrad			    " len=%d\n",
1041.1Sbrad			    len);
1051.1Sbrad			umcpmiodebug = 20;
1061.1Sbrad			umcpmio_dump_buffer(true, (uint8_t *) ibuf, len,
1071.1Sbrad			    "umcpmio_uhidev_intr: ibuf");
1081.1Sbrad			umcpmiodebug = d;
1091.1Sbrad		}
1101.1Sbrad
1111.1Sbrad		break;
1121.1Sbrad	default:
1131.1Sbrad		device_printf(sc->sc_dev,
1141.1Sbrad		    "umcpmio_uhidev_intr: Unknown interrupt length: %d",
1151.1Sbrad		    len);
1161.1Sbrad		break;
1171.1Sbrad	}
1181.1Sbrad	mutex_exit(&sc->sc_res_mutex);
1191.1Sbrad}
1201.1Sbrad
1211.1Sbrad/* Send a HID report.  This needs to be called with the action mutex held */
1221.1Sbrad
1231.1Sbradint
1241.1Sbradumcpmio_send_report(struct umcpmio_softc *sc, uint8_t *sendbuf,
1251.1Sbrad    size_t sendlen, uint8_t *resbuf, int timeout)
1261.1Sbrad{
1271.1Sbrad	int err = 0;
1281.1Sbrad	int err_count = 0;
1291.1Sbrad
1301.1Sbrad	if (sc->sc_dying)
1311.1Sbrad		return EIO;
1321.1Sbrad
1331.1Sbrad	KASSERT(mutex_owned(&sc->sc_action_mutex));
1341.1Sbrad
1351.1Sbrad	/* If this happens, it may be because the device was pulled from the
1361.1Sbrad	 * USB slot, or sometimes when SIGINTR occurs.  Just consider this a
1371.1Sbrad	 * EIO. */
1381.1Sbrad	if (sc->sc_res_buffer != NULL) {
1391.1Sbrad		sc->sc_res_buffer = NULL;
1401.1Sbrad		sc->sc_res_ready = false;
1411.1Sbrad		err = EIO;
1421.1Sbrad		goto out;
1431.1Sbrad	}
1441.1Sbrad	sc->sc_res_buffer = resbuf;
1451.1Sbrad	sc->sc_res_ready = false;
1461.1Sbrad
1471.1Sbrad	err = uhidev_write(sc->sc_hdev, sendbuf, sendlen);
1481.1Sbrad
1491.1Sbrad	if (err) {
1501.1Sbrad		DPRINTF(("umcpmio_send_report: uhidev_write errored with:"
1511.1Sbrad		    " err=%d\n", err));
1521.1Sbrad		goto out;
1531.1Sbrad	}
1541.1Sbrad	DPRINTFN(30, ("umcpmio_send_report: about to wait on cv.  err=%d\n",
1551.1Sbrad	    err));
1561.1Sbrad
1571.1Sbrad	mutex_enter(&sc->sc_res_mutex);
1581.1Sbrad	while (!sc->sc_res_ready) {
1591.1Sbrad		DPRINTFN(20, ("umcpmio_send_report: LOOP for response."
1601.1Sbrad		    "  sc_res_ready=%d, err_count=%d, timeout=%d\n",
1611.1Sbrad		    sc->sc_res_ready, err_count, mstohz(timeout)));
1621.1Sbrad
1631.1Sbrad		err = cv_timedwait_sig(&sc->sc_res_cv, &sc->sc_res_mutex,
1641.1Sbrad		    mstohz(timeout));
1651.1Sbrad
1661.1Sbrad		/* We are only going to allow this to loop on an error, any
1671.1Sbrad		 * error at all, so many times. */
1681.1Sbrad		if (err) {
1691.1Sbrad			DPRINTF(("umcpmio_send_report:"
1701.1Sbrad			    " cv_timedwait_sig reported an error:"
1711.1Sbrad			    " err=%d, sc->sc_res_ready=%d\n",
1721.1Sbrad			    err, sc->sc_res_ready));
1731.1Sbrad			err_count++;
1741.1Sbrad		}
1751.1Sbrad
1761.1Sbrad		/* The CV was interrupted, but the buffer is ready so, clear
1771.1Sbrad		 * the error and break out. */
1781.1Sbrad		if ((err == ERESTART) && (sc->sc_res_ready)) {
1791.1Sbrad			DPRINTF(("umcpmio_send_report:"
1801.1Sbrad			    " ERESTART and buffer is ready\n"));
1811.1Sbrad			err = 0;
1821.1Sbrad			break;
1831.1Sbrad		}
1841.1Sbrad
1851.1Sbrad		/* Too many times though the loop, just break out.  Turn a
1861.1Sbrad		 * ERESTART (interruption) into a I/O error at this point. */
1871.1Sbrad		if (err_count > sc->sc_response_errcnt) {
1881.1Sbrad			DPRINTF(("umcpmio_send_report: err_count exceeded:"
1891.1Sbrad			    " err=%d\n", err));
1901.1Sbrad			if (err == ERESTART)
1911.1Sbrad				err = EIO;
1921.1Sbrad			break;
1931.1Sbrad		}
1941.1Sbrad
1951.1Sbrad		/* This is a normal timeout, without interruption, try again */
1961.1Sbrad		if (err == EWOULDBLOCK) {
1971.1Sbrad			DPRINTF(("umcpmio_send_report: EWOULDBLOCK:"
1981.1Sbrad			    " err_count=%d\n", err_count));
1991.1Sbrad			continue;
2001.1Sbrad		}
2011.1Sbrad
2021.1Sbrad		/* The CV was interrupted and the buffer wasn't filled in, so
2031.1Sbrad		 * try again */
2041.1Sbrad		if ((err == ERESTART) && (!sc->sc_res_ready)) {
2051.1Sbrad			DPRINTF(("umcpmio_send_report:"
2061.1Sbrad			    " ERESTART and buffer is NOT ready."
2071.1Sbrad			    "  err_count=%d\n", err_count));
2081.1Sbrad			continue;
2091.1Sbrad		}
2101.1Sbrad	}
2111.1Sbrad
2121.1Sbrad	sc->sc_res_buffer = NULL;
2131.1Sbrad	sc->sc_res_ready = false;
2141.1Sbrad	mutex_exit(&sc->sc_res_mutex);
2151.1Sbrad
2161.1Sbrad	/* Turn most errors into an I/O error */
2171.1Sbrad	if (err &&
2181.1Sbrad	    err != ERESTART)
2191.1Sbrad		err = EIO;
2201.1Sbrad
2211.1Sbradout:
2221.1Sbrad	return err;
2231.1Sbrad}
2241.1Sbrad
2251.1Sbradint
2261.1Sbradumcpmio_hid_open(struct umcpmio_softc *sc)
2271.1Sbrad{
2281.1Sbrad	int err;
2291.1Sbrad
2301.1Sbrad	err = uhidev_open(sc->sc_hdev, &umcpmio_uhidev_intr, sc);
2311.1Sbrad	if (err) {
2321.1Sbrad		aprint_error_dev(sc->sc_dev, "umcpmio_hid_open: "
2331.1Sbrad		    " uhidev_open: err=%d\n", err);
2341.1Sbrad	}
2351.1Sbrad
2361.1Sbrad	/* It is not clear that this should be needed, but it was noted that
2371.1Sbrad	 * the MCP2221 / MCP2221A would sometimes not be ready if this delay
2381.1Sbrad	 * was not present.  In fact, the attempts to set stuff a little
2391.1Sbrad	 * later would sometimes fail.
2401.1Sbrad	 */
2411.1Sbrad
2421.1Sbrad	if (!err)
2431.1Sbrad		delay(1000);
2441.1Sbrad
2451.1Sbrad	return err;
2461.1Sbrad}
247