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