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