jcs's openbsd hax
openbsd
at jcs 499 lines 12 kB view raw
1/* $OpenBSD: uslhcom.c,v 1.10 2024/05/23 03:21:09 jsg Exp $ */ 2 3/* 4 * Copyright (c) 2015 SASANO Takayoshi <uaa@openbsd.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/* 20 * Device driver for Silicon Labs CP2110 USB HID-UART bridge. 21 */ 22 23#include <sys/param.h> 24#include <sys/systm.h> 25#include <sys/malloc.h> 26#include <sys/tty.h> 27#include <sys/device.h> 28 29#include <dev/usb/usb.h> 30#include <dev/usb/usbdi.h> 31#include <dev/usb/usbdevs.h> 32 33#include <dev/usb/usbhid.h> 34#include <dev/usb/uhidev.h> 35 36#include <dev/usb/ucomvar.h> 37#include <dev/usb/uslhcomreg.h> 38 39#ifdef USLHCOM_DEBUG 40#define DPRINTF(x) if (uslhcomdebug) printf x 41#else 42#define DPRINTF(x) 43#endif 44 45struct uslhcom_softc { 46 struct uhidev sc_hdev; 47 struct usbd_device *sc_udev; 48 49 u_char *sc_ibuf; 50 u_int sc_icnt; 51 52 u_char sc_lsr; 53 u_char sc_msr; 54 55 struct device *sc_subdev; 56}; 57 58void uslhcom_get_status(void *, int, u_char *, u_char *); 59void uslhcom_set(void *, int, int, int); 60int uslhcom_param(void *, int, struct termios *); 61int uslhcom_open(void *, int); 62void uslhcom_close(void *, int); 63void uslhcom_write(void *, int, u_char *, u_char *, u_int32_t *); 64void uslhcom_read(void *, int, u_char **, u_int32_t *); 65void uslhcom_intr(struct uhidev *, void *, u_int); 66 67int uslhcom_match(struct device *, void *, void *); 68void uslhcom_attach(struct device *, struct device *, void *); 69int uslhcom_detach(struct device *, int); 70 71int uslhcom_uart_endis(struct uslhcom_softc *, int); 72int uslhcom_clear_fifo(struct uslhcom_softc *, int); 73int uslhcom_get_version(struct uslhcom_softc *, struct uslhcom_version_info *); 74int uslhcom_get_uart_status(struct uslhcom_softc *, struct uslhcom_uart_status *); 75int uslhcom_set_break(struct uslhcom_softc *, int); 76int uslhcom_set_config(struct uslhcom_softc *, struct uslhcom_uart_config *); 77void uslhcom_set_baud_rate(struct uslhcom_uart_config *, u_int32_t); 78int uslhcom_create_config(struct uslhcom_uart_config *, struct termios *); 79int uslhcom_setup(struct uslhcom_softc *, struct uslhcom_uart_config *); 80 81const struct ucom_methods uslhcom_methods = { 82 uslhcom_get_status, 83 uslhcom_set, 84 uslhcom_param, 85 NULL, 86 uslhcom_open, 87 uslhcom_close, 88 uslhcom_read, 89 uslhcom_write, 90}; 91 92static const struct usb_devno uslhcom_devs[] = { 93 { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2110 }, 94}; 95 96struct cfdriver uslhcom_cd = { 97 NULL, "uslhcom", DV_DULL 98}; 99 100const struct cfattach uslhcom_ca = { 101 sizeof(struct uslhcom_softc), 102 uslhcom_match, uslhcom_attach, uslhcom_detach 103}; 104 105/* ---------------------------------------------------------------------- 106 * driver entry points 107 */ 108 109int 110uslhcom_match(struct device *parent, void *match, void *aux) 111{ 112 struct uhidev_attach_arg *uha = aux; 113 114 /* use all report IDs */ 115 if (!UHIDEV_CLAIM_MULTIPLE_REPORTID(uha)) 116 return UMATCH_NONE; 117 118 return (usb_lookup(uslhcom_devs, 119 uha->uaa->vendor, uha->uaa->product) != NULL ? 120 UMATCH_VENDOR_PRODUCT : UMATCH_NONE); 121} 122 123void 124uslhcom_attach(struct device *parent, struct device *self, void *aux) 125{ 126 struct uslhcom_softc *sc = (struct uslhcom_softc *)self; 127 struct uhidev_attach_arg *uha = aux; 128 struct usbd_device *dev = uha->parent->sc_udev; 129 struct ucom_attach_args uca; 130 struct uslhcom_version_info version; 131 int err, repid, size, rsize; 132 void *desc; 133 134 sc->sc_hdev.sc_intr = uslhcom_intr; 135 sc->sc_hdev.sc_parent = uha->parent; 136 sc->sc_hdev.sc_report_id = uha->reportid; 137 138 uhidev_get_report_desc(uha->parent, &desc, &size); 139 for (repid = 0; repid < uha->parent->sc_nrepid; repid++) { 140 rsize = hid_report_size(desc, size, hid_input, repid); 141 if (sc->sc_hdev.sc_isize < rsize) sc->sc_hdev.sc_isize = rsize; 142 rsize = hid_report_size(desc, size, hid_output, repid); 143 if (sc->sc_hdev.sc_osize < rsize) sc->sc_hdev.sc_osize = rsize; 144 rsize = hid_report_size(desc, size, hid_feature, repid); 145 if (sc->sc_hdev.sc_fsize < rsize) sc->sc_hdev.sc_fsize = rsize; 146 } 147 148 printf("\n"); 149 150 sc->sc_udev = dev; 151 152 err = uhidev_open(&sc->sc_hdev); 153 if (err) { 154 DPRINTF(("uslhcom_attach: uhidev_open %d\n", err)); 155 return; 156 } 157 158 DPRINTF(("uslhcom_attach: sc %p opipe %p ipipe %p report_id %d\n", 159 sc, sc->sc_hdev.sc_parent->sc_opipe, 160 sc->sc_hdev.sc_parent->sc_ipipe, uha->reportid)); 161 DPRINTF(("uslhcom_attach: isize %d osize %d fsize %d\n", 162 sc->sc_hdev.sc_isize, sc->sc_hdev.sc_osize, 163 sc->sc_hdev.sc_fsize)); 164 165 uslhcom_uart_endis(sc, UART_DISABLE); 166 uslhcom_get_version(sc, &version); 167 printf("%s: pid %#x rev %#x\n", sc->sc_hdev.sc_dev.dv_xname, 168 version.product_id, version.product_revision); 169 170 /* setup ucom layer */ 171 bzero(&uca, sizeof uca); 172 uca.portno = UCOM_UNK_PORTNO; 173 uca.bulkin = uca.bulkout = -1; 174 uca.ibufsize = uca.ibufsizepad = 0; 175 uca.obufsize = sc->sc_hdev.sc_osize; 176 uca.opkthdrlen = USLHCOM_TX_HEADER_SIZE; 177 uca.uhidev = sc->sc_hdev.sc_parent; 178 uca.device = uha->uaa->device; 179 uca.iface = uha->uaa->iface; 180 uca.methods = &uslhcom_methods; 181 uca.arg = sc; 182 uca.info = NULL; 183 184 sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch); 185} 186 187int 188uslhcom_detach(struct device *self, int flags) 189{ 190 struct uslhcom_softc *sc = (struct uslhcom_softc *)self; 191 192 DPRINTF(("uslhcom_detach: sc=%p flags=%d\n", sc, flags)); 193 if (sc->sc_subdev != NULL) { 194 config_detach(sc->sc_subdev, flags); 195 sc->sc_subdev = NULL; 196 } 197 198 if (sc->sc_hdev.sc_state & UHIDEV_OPEN) 199 uhidev_close(&sc->sc_hdev); 200 201 return 0; 202} 203 204/* ---------------------------------------------------------------------- 205 * low level I/O 206 */ 207 208int 209uslhcom_uart_endis(struct uslhcom_softc *sc, int enable) 210{ 211 int len; 212 u_char val; 213 214 len = sizeof(val); 215 val = enable; 216 217 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 218 GET_SET_UART_ENABLE, &val, len) != len; 219} 220 221int 222uslhcom_clear_fifo(struct uslhcom_softc *sc, int fifo) 223{ 224 int len; 225 u_char val; 226 227 len = sizeof(val); 228 val = fifo; 229 230 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 231 SET_CLEAR_FIFOS, &val, len) != len; 232} 233 234int 235uslhcom_get_version(struct uslhcom_softc *sc, struct uslhcom_version_info *version) 236{ 237 int len; 238 239 len = sizeof(*version); 240 241 return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 242 GET_VERSION, version, len) < len; 243} 244 245int 246uslhcom_get_uart_status(struct uslhcom_softc *sc, struct uslhcom_uart_status *status) 247{ 248 int len; 249 250 len = sizeof(*status); 251 252 return uhidev_get_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 253 GET_UART_STATUS, status, len) < len; 254} 255 256int 257uslhcom_set_break(struct uslhcom_softc *sc, int onoff) 258{ 259 int len, reportid; 260 u_char val; 261 262 len = sizeof(val); 263 264 if (onoff) { 265 val = 0; /* send break until SET_STOP_LINE_BREAK */ 266 reportid = SET_TRANSMIT_LINE_BREAK; 267 } else { 268 val = 0; /* any value can be accepted */ 269 reportid = SET_STOP_LINE_BREAK; 270 } 271 272 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 273 reportid, &val, len) != len; 274} 275 276int 277uslhcom_set_config(struct uslhcom_softc *sc, struct uslhcom_uart_config *config) 278{ 279 int len; 280 281 len = sizeof(*config); 282 283 return uhidev_set_report(sc->sc_hdev.sc_parent, UHID_FEATURE_REPORT, 284 GET_SET_UART_CONFIG, config, len) != len; 285} 286 287void 288uslhcom_set_baud_rate(struct uslhcom_uart_config *config, u_int32_t baud_rate) 289{ 290 config->baud_rate[0] = baud_rate >> 24; 291 config->baud_rate[1] = baud_rate >> 16; 292 config->baud_rate[2] = baud_rate >> 8; 293 config->baud_rate[3] = baud_rate >> 0; 294} 295 296int 297uslhcom_create_config(struct uslhcom_uart_config *config, struct termios *t) 298{ 299 if (t->c_ospeed < UART_CONFIG_BAUD_RATE_MIN || 300 t->c_ospeed > UART_CONFIG_BAUD_RATE_MAX) 301 return EINVAL; 302 303 uslhcom_set_baud_rate(config, t->c_ospeed); 304 305 if (ISSET(t->c_cflag, PARENB)) { 306 if (ISSET(t->c_cflag, PARODD)) 307 config->parity = UART_CONFIG_PARITY_ODD; 308 else 309 config->parity = UART_CONFIG_PARITY_EVEN; 310 } else 311 config->parity = UART_CONFIG_PARITY_NONE; 312 313 if (ISSET(t->c_cflag, CRTSCTS)) 314 config->data_control = UART_CONFIG_DATA_CONTROL_HARD; 315 else 316 config->data_control = UART_CONFIG_DATA_CONTROL_NONE; 317 318 switch (ISSET(t->c_cflag, CSIZE)) { 319 case CS5: 320 config->data_bits = UART_CONFIG_DATA_BITS_5; 321 break; 322 case CS6: 323 config->data_bits = UART_CONFIG_DATA_BITS_6; 324 break; 325 case CS7: 326 config->data_bits = UART_CONFIG_DATA_BITS_7; 327 break; 328 case CS8: 329 config->data_bits = UART_CONFIG_DATA_BITS_8; 330 break; 331 default: 332 return EINVAL; 333 } 334 335 if (ISSET(t->c_cflag, CSTOPB)) 336 config->stop_bits = UART_CONFIG_STOP_BITS_2; 337 else 338 config->stop_bits = UART_CONFIG_STOP_BITS_1; 339 340 return 0; 341} 342 343int 344uslhcom_setup(struct uslhcom_softc *sc, struct uslhcom_uart_config *config) 345{ 346 struct uslhcom_uart_status status; 347 348 if (uslhcom_uart_endis(sc, UART_DISABLE)) 349 return EIO; 350 351 if (uslhcom_set_config(sc, config)) 352 return EIO; 353 354 if (uslhcom_clear_fifo(sc, CLEAR_TX_FIFO | CLEAR_RX_FIFO)) 355 return EIO; 356 357 if (uslhcom_get_uart_status(sc, &status)) 358 return EIO; 359 360 if (uslhcom_uart_endis(sc, UART_ENABLE)) 361 return EIO; 362 363 return 0; 364} 365 366/* ---------------------------------------------------------------------- 367 * methods for ucom 368 */ 369 370void 371uslhcom_get_status(void *arg, int portno, u_char *rlsr, u_char *rmsr) 372{ 373 struct uslhcom_softc *sc = arg; 374 375 if (usbd_is_dying(sc->sc_udev)) 376 return; 377 378 *rlsr = sc->sc_lsr; 379 *rmsr = sc->sc_msr; 380} 381 382void 383uslhcom_set(void *arg, int portno, int reg, int onoff) 384{ 385 struct uslhcom_softc *sc = arg; 386 387 if (usbd_is_dying(sc->sc_udev)) 388 return; 389 390 switch (reg) { 391 case UCOM_SET_DTR: 392 case UCOM_SET_RTS: 393 /* no support, do nothing */ 394 break; 395 case UCOM_SET_BREAK: 396 uslhcom_set_break(sc, onoff); 397 break; 398 } 399} 400 401int 402uslhcom_param(void *arg, int portno, struct termios *t) 403{ 404 struct uslhcom_softc *sc = arg; 405 struct uslhcom_uart_config config; 406 int ret; 407 408 if (usbd_is_dying(sc->sc_udev)) 409 return 0; 410 411 ret = uslhcom_create_config(&config, t); 412 if (ret) 413 return ret; 414 415 ret = uslhcom_setup(sc, &config); 416 if (ret) 417 return ret; 418 419 return 0; 420} 421 422int 423uslhcom_open(void *arg, int portno) 424{ 425 struct uslhcom_softc *sc = arg; 426 struct uslhcom_uart_config config; 427 int ret; 428 429 if (usbd_is_dying(sc->sc_udev)) 430 return EIO; 431 432 sc->sc_ibuf = malloc(sc->sc_hdev.sc_isize, M_USBDEV, M_WAITOK); 433 434 uslhcom_set_baud_rate(&config, 9600); 435 config.parity = UART_CONFIG_PARITY_NONE; 436 config.data_control = UART_CONFIG_DATA_CONTROL_NONE; 437 config.data_bits = UART_CONFIG_DATA_BITS_8; 438 config.stop_bits = UART_CONFIG_STOP_BITS_1; 439 440 ret = uslhcom_set_config(sc, &config); 441 if (ret) 442 return ret; 443 444 return 0; 445} 446 447void 448uslhcom_close(void *arg, int portno) 449{ 450 struct uslhcom_softc *sc = arg; 451 int s; 452 453 if (usbd_is_dying(sc->sc_udev)) 454 return; 455 456 uslhcom_uart_endis(sc, UART_DISABLE); 457 458 s = splusb(); 459 if (sc->sc_ibuf != NULL) { 460 free(sc->sc_ibuf, M_USBDEV, sc->sc_hdev.sc_isize); 461 sc->sc_ibuf = NULL; 462 } 463 splx(s); 464} 465 466void 467uslhcom_read(void *arg, int portno, u_char **ptr, u_int32_t *cnt) 468{ 469 struct uslhcom_softc *sc = arg; 470 471 *ptr = sc->sc_ibuf; 472 *cnt = sc->sc_icnt; 473} 474 475void 476uslhcom_write(void *arg, int portno, u_char *to, u_char *data, u_int32_t *cnt) 477{ 478 bcopy(data, &to[USLHCOM_TX_HEADER_SIZE], *cnt); 479 to[0] = *cnt; /* add Report ID (= transmit length) */ 480 *cnt += USLHCOM_TX_HEADER_SIZE; 481} 482 483void 484uslhcom_intr(struct uhidev *addr, void *ibuf, u_int len) 485{ 486 extern void ucomreadcb(struct usbd_xfer *, void *, usbd_status); 487 struct uslhcom_softc *sc = (struct uslhcom_softc *)addr; 488 int s; 489 490 if (sc->sc_ibuf == NULL) 491 return; 492 493 s = spltty(); 494 sc->sc_icnt = len; /* Report ID is already stripped */ 495 bcopy(ibuf, sc->sc_ibuf, len); 496 ucomreadcb(addr->sc_parent->sc_ixfer, sc->sc_subdev, 497 USBD_NORMAL_COMPLETION); 498 splx(s); 499}