1/*	$NetBSD: umcpmio_transport.c,v 1.1 2025/11/29 18:39:14 brad Exp $	*/
2
3/*
4 * Copyright (c) 2024, 2025 Brad Spencer <brad@anduin.eldar.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/cdefs.h>
20__KERNEL_RCSID(0, "$NetBSD: umcpmio_transport.c,v 1.1 2025/11/29 18:39:14 brad Exp $");
21
22#ifdef _KERNEL_OPT
23#include "opt_usb.h"
24#endif
25
26#include <sys/param.h>
27#include <sys/types.h>
28
29#include <dev/hid/hid.h>
30
31#include <dev/usb/uhidev.h>
32#include <dev/usb/usb.h>
33#include <dev/usb/usbdevs.h>
34#include <dev/usb/usbdi.h>
35#include <dev/usb/usbdi_util.h>
36#include <dev/usb/usbhid.h>
37
38#include <dev/usb/umcpmio.h>
39#include <dev/usb/umcpmio_hid_reports.h>
40#include <dev/usb/umcpmio_transport.h>
41#include <dev/usb/umcpmio_io.h>
42#include <dev/usb/umcpmio_subr.h>
43
44#define UMCPMIO_DEBUG 1
45#ifdef UMCPMIO_DEBUG
46#define DPRINTF(x)	do { if (umcpmiodebug) printf x; } while (0)
47#define DPRINTFN(n, x)	do { if (umcpmiodebug > (n)) printf x; } while (0)
48extern int umcpmiodebug;
49#else
50#define DPRINTF(x)	__nothing
51#define DPRINTFN(n, x)	__nothing
52#endif
53
54
55/* Stuff to communicate with the MCP2210 and MCP2221 / MCP2221A */
56
57
58/*
59 * Communication with the HID function requires sending a HID report
60 * request and then waiting for a response.
61 *
62 * The panic that occurs when trying to use the interrupt... i.e.
63 * attaching though this driver seems to be related to the fact that
64 * a spin lock is held and the USB stack wants to wait.
65 *
66 * The USB stack *IS* going to have to wait for the response from
67 * the device, somehow...
68 *
69 * It didn't seem possible to defer the uhidev_write to a thread.
70 * Attempts to yield() while spinning hard also did not work and
71 * not yield()ing didn't allow anything else to run.
72 *
73 */
74
75/*
76 * This is the panic you will get:
77 *
78 * 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)
79 */
80
81static void
82umcpmio_uhidev_intr(void *cookie, void *ibuf, u_int len)
83{
84	struct umcpmio_softc *sc = cookie;
85
86	if (sc->sc_dying)
87		return;
88
89	DPRINTFN(30, ("umcpmio_uhidev_intr: len=%d\n", len));
90
91	mutex_enter(&sc->sc_res_mutex);
92	switch (len) {
93	case MCP2221_RES_BUFFER_SIZE:
94		if (sc->sc_res_buffer != NULL) {
95			memcpy(sc->sc_res_buffer, ibuf,
96			    MCP2221_RES_BUFFER_SIZE);
97			sc->sc_res_ready = true;
98			cv_signal(&sc->sc_res_cv);
99		} else {
100			int d = umcpmiodebug;
101			device_printf(sc->sc_dev,
102			    "umcpmio_uhidev_intr: NULL sc_res_buffer:"
103			    " len=%d\n",
104			    len);
105			umcpmiodebug = 20;
106			umcpmio_dump_buffer(true, (uint8_t *) ibuf, len,
107			    "umcpmio_uhidev_intr: ibuf");
108			umcpmiodebug = d;
109		}
110
111		break;
112	default:
113		device_printf(sc->sc_dev,
114		    "umcpmio_uhidev_intr: Unknown interrupt length: %d",
115		    len);
116		break;
117	}
118	mutex_exit(&sc->sc_res_mutex);
119}
120
121/* Send a HID report.  This needs to be called with the action mutex held */
122
123int
124umcpmio_send_report(struct umcpmio_softc *sc, uint8_t *sendbuf,
125    size_t sendlen, uint8_t *resbuf, int timeout)
126{
127	int err = 0;
128	int err_count = 0;
129
130	if (sc->sc_dying)
131		return EIO;
132
133	KASSERT(mutex_owned(&sc->sc_action_mutex));
134
135	/* If this happens, it may be because the device was pulled from the
136	 * USB slot, or sometimes when SIGINTR occurs.  Just consider this a
137	 * EIO. */
138	if (sc->sc_res_buffer != NULL) {
139		sc->sc_res_buffer = NULL;
140		sc->sc_res_ready = false;
141		err = EIO;
142		goto out;
143	}
144	sc->sc_res_buffer = resbuf;
145	sc->sc_res_ready = false;
146
147	err = uhidev_write(sc->sc_hdev, sendbuf, sendlen);
148
149	if (err) {
150		DPRINTF(("umcpmio_send_report: uhidev_write errored with:"
151		    " err=%d\n", err));
152		goto out;
153	}
154	DPRINTFN(30, ("umcpmio_send_report: about to wait on cv.  err=%d\n",
155	    err));
156
157	mutex_enter(&sc->sc_res_mutex);
158	while (!sc->sc_res_ready) {
159		DPRINTFN(20, ("umcpmio_send_report: LOOP for response."
160		    "  sc_res_ready=%d, err_count=%d, timeout=%d\n",
161		    sc->sc_res_ready, err_count, mstohz(timeout)));
162
163		err = cv_timedwait_sig(&sc->sc_res_cv, &sc->sc_res_mutex,
164		    mstohz(timeout));
165
166		/* We are only going to allow this to loop on an error, any
167		 * error at all, so many times. */
168		if (err) {
169			DPRINTF(("umcpmio_send_report:"
170			    " cv_timedwait_sig reported an error:"
171			    " err=%d, sc->sc_res_ready=%d\n",
172			    err, sc->sc_res_ready));
173			err_count++;
174		}
175
176		/* The CV was interrupted, but the buffer is ready so, clear
177		 * the error and break out. */
178		if ((err == ERESTART) && (sc->sc_res_ready)) {
179			DPRINTF(("umcpmio_send_report:"
180			    " ERESTART and buffer is ready\n"));
181			err = 0;
182			break;
183		}
184
185		/* Too many times though the loop, just break out.  Turn a
186		 * ERESTART (interruption) into a I/O error at this point. */
187		if (err_count > sc->sc_response_errcnt) {
188			DPRINTF(("umcpmio_send_report: err_count exceeded:"
189			    " err=%d\n", err));
190			if (err == ERESTART)
191				err = EIO;
192			break;
193		}
194
195		/* This is a normal timeout, without interruption, try again */
196		if (err == EWOULDBLOCK) {
197			DPRINTF(("umcpmio_send_report: EWOULDBLOCK:"
198			    " err_count=%d\n", err_count));
199			continue;
200		}
201
202		/* The CV was interrupted and the buffer wasn't filled in, so
203		 * try again */
204		if ((err == ERESTART) && (!sc->sc_res_ready)) {
205			DPRINTF(("umcpmio_send_report:"
206			    " ERESTART and buffer is NOT ready."
207			    "  err_count=%d\n", err_count));
208			continue;
209		}
210	}
211
212	sc->sc_res_buffer = NULL;
213	sc->sc_res_ready = false;
214	mutex_exit(&sc->sc_res_mutex);
215
216	/* Turn most errors into an I/O error */
217	if (err &&
218	    err != ERESTART)
219		err = EIO;
220
221out:
222	return err;
223}
224
225int
226umcpmio_hid_open(struct umcpmio_softc *sc)
227{
228	int err;
229
230	err = uhidev_open(sc->sc_hdev, &umcpmio_uhidev_intr, sc);
231	if (err) {
232		aprint_error_dev(sc->sc_dev, "umcpmio_hid_open: "
233		    " uhidev_open: err=%d\n", err);
234	}
235
236	/* It is not clear that this should be needed, but it was noted that
237	 * the MCP2221 / MCP2221A would sometimes not be ready if this delay
238	 * was not present.  In fact, the attempts to set stuff a little
239	 * later would sometimes fail.
240	 */
241
242	if (!err)
243		delay(1000);
244
245	return err;
246}
247