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