jcs's openbsd hax
openbsd
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}