Home | History | Annotate | Line # | Download | only in usb
      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 (at) 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)
     48 extern 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 
     81 static void
     82 umcpmio_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 
    123 int
    124 umcpmio_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 
    221 out:
    222 	return err;
    223 }
    224 
    225 int
    226 umcpmio_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