1 /*        $NetBSD: ucycom.c,v 1.57 2024/03/17 20:10:52 jakllsch Exp $ */
2 
3 /*
4  * Copyright (c) 2005 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Nick Hudson
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 /*
32  * This code is based on the ucom driver.
33  */
34 
35 /*
36  * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to
37  * RS232 bridges.
38  */
39 
40 #include <sys/cdefs.h>
41 __KERNEL_RCSID(0, "$NetBSD: ucycom.c,v 1.57 2024/03/17 20:10:52 jakllsch Exp $");
42 
43 #ifdef _KERNEL_OPT
44 #include "opt_usb.h"
45 #endif
46 
47 #include <sys/param.h>
48 #include <sys/systm.h>
49 #include <sys/conf.h>
50 #include <sys/kernel.h>
51 #include <sys/kmem.h>
52 #include <sys/device.h>
53 #include <sys/sysctl.h>
54 #include <sys/tty.h>
55 #include <sys/file.h>
56 #include <sys/vnode.h>
57 #include <sys/kauth.h>
58 #include <sys/lwp.h>
59 
60 #include <dev/usb/usb.h>
61 #include <dev/usb/usbhid.h>
62 
63 #include <dev/usb/usbdi.h>
64 #include <dev/usb/usbdi_util.h>
65 #include <dev/usb/usbdevs.h>
66 #include <dev/usb/uhidev.h>
67 #include <dev/hid/hid.h>
68 
69 #include "ioconf.h"
70 
71 #ifdef UCYCOM_DEBUG
72 #define DPRINTF(x)  if (ucycomdebug) printf x
73 #define DPRINTFN(n, x)        if (ucycomdebug > (n)) printf x
74 int       ucycomdebug = 20;
75 #else
76 #define DPRINTF(x)
77 #define DPRINTFN(n,x)
78 #endif
79 
80 
81 #define   UCYCOMCALLUNIT_MASK TTCALLUNIT_MASK
82 #define   UCYCOMUNIT_MASK               TTUNIT_MASK
83 #define   UCYCOMDIALOUT_MASK  TTDIALOUT_MASK
84 
85 #define   UCYCOMCALLUNIT(x)   TTCALLUNIT(x)
86 #define   UCYCOMUNIT(x)                 TTUNIT(x)
87 #define   UCYCOMDIALOUT(x)    TTDIALOUT(x)
88 
89 /* Configuration Byte */
90 #define UCYCOM_RESET                    0x80
91 #define UCYCOM_PARITY_TYPE_MASK         0x20
92 #define  UCYCOM_PARITY_ODD     0x20
93 #define  UCYCOM_PARITY_EVEN    0x00
94 #define UCYCOM_PARITY_MASK    0x10
95 #define  UCYCOM_PARITY_ON      0x10
96 #define  UCYCOM_PARITY_OFF     0x00
97 #define UCYCOM_STOP_MASK      0x08
98 #define  UCYCOM_STOP_BITS_2    0x08
99 #define  UCYCOM_STOP_BITS_1    0x00
100 #define UCYCOM_DATA_MASK      0x03
101 #define  UCYCOM_DATA_BITS_8    0x03
102 #define  UCYCOM_DATA_BITS_7    0x02
103 #define  UCYCOM_DATA_BITS_6    0x01
104 #define  UCYCOM_DATA_BITS_5    0x00
105 
106 /* Modem (Input) status byte */
107 #define UCYCOM_RI   0x80
108 #define UCYCOM_DCD  0x40
109 #define UCYCOM_DSR  0x20
110 #define UCYCOM_CTS  0x10
111 #define UCYCOM_ERROR          0x08
112 #define UCYCOM_LMASK          0x07
113 
114 /* Modem (Output) control byte */
115 #define UCYCOM_DTR  0x20
116 #define UCYCOM_RTS  0x10
117 #define UCYCOM_ORESET         0x08
118 
119 struct ucycom_softc {
120           device_t            sc_dev;
121           struct uhidev                 *sc_hdev;
122           struct usbd_device  *sc_udev;
123 
124           struct usb_task               sc_task;
125           struct tty                    *sc_tty;
126 
127           enum {
128                     UCYCOM_INIT_NONE,
129                     UCYCOM_INIT_INITED
130           } sc_init_state;
131 
132           kmutex_t sc_lock;   /* protects refcnt, others */
133 
134           /* uhidev parameters */
135           size_t                        sc_flen; /* feature report length */
136           size_t                        sc_olen; /* output report length */
137 
138           uint8_t                       *sc_obuf;
139           int                           sc_wlen;
140 
141           /* settings */
142           uint32_t            sc_baud;
143           uint8_t                       sc_cfg;   /* Data format */
144           uint8_t                       sc_mcr;   /* Modem control */
145           uint8_t                       sc_msr;   /* Modem status */
146           int                           sc_swflags;
147 
148           /* flags */
149           char                          sc_dying;
150 };
151 
152 static dev_type_open(ucycomopen);
153 static dev_type_close(ucycomclose);
154 static dev_type_read(ucycomread);
155 static dev_type_write(ucycomwrite);
156 static dev_type_ioctl(ucycomioctl);
157 static dev_type_stop(ucycomstop);
158 static dev_type_tty(ucycomtty);
159 static dev_type_poll(ucycompoll);
160 
161 const struct cdevsw ucycom_cdevsw = {
162           .d_open = ucycomopen,
163           .d_close = ucycomclose,
164           .d_read = ucycomread,
165           .d_write = ucycomwrite,
166           .d_ioctl = ucycomioctl,
167           .d_stop = ucycomstop,
168           .d_tty = ucycomtty,
169           .d_poll = ucycompoll,
170           .d_mmap = nommap,
171           .d_kqfilter = ttykqfilter,
172           .d_discard = nodiscard,
173           .d_flag = D_TTY
174 };
175 
176 Static int ucycomparam(struct tty *, struct termios *);
177 Static void ucycomstart(struct tty *);
178 Static void ucycomstarttask(void *);
179 Static void ucycomwritecb(struct usbd_xfer *, void *, usbd_status);
180 Static void ucycom_intr(void *, void *, u_int);
181 Static int ucycom_configure(struct ucycom_softc *, uint32_t, uint8_t);
182 Static void tiocm_to_ucycom(struct ucycom_softc *, u_long, int);
183 Static int ucycom_to_tiocm(struct ucycom_softc *);
184 Static void ucycom_set_status(struct ucycom_softc *);
185 Static void ucycom_dtr(struct ucycom_softc *, int);
186 #if 0
187 Static void ucycom_rts(struct ucycom_softc *, int);
188 #endif
189 Static void ucycom_cleanup(struct ucycom_softc *);
190 
191 #ifdef UCYCOM_DEBUG
192 Static void ucycom_get_cfg(struct ucycom_softc *);
193 #endif
194 
195 Static const struct usb_devno ucycom_devs[] = {
196           { USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_USBRS232 },
197           { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE },
198 };
199 #define ucycom_lookup(v, p) usb_lookup(ucycom_devs, v, p)
200 
201 static int          ucycom_match(device_t, cfdata_t, void *);
202 static void         ucycom_attach(device_t, device_t, void *);
203 static int          ucycom_detach(device_t, int);
204 static int          ucycom_activate(device_t, enum devact);
205 
206 CFATTACH_DECL_NEW(ucycom, sizeof(struct ucycom_softc), ucycom_match,
207     ucycom_attach, ucycom_detach, ucycom_activate);
208 
209 static int
ucycom_match(device_t parent,cfdata_t match,void * aux)210 ucycom_match(device_t parent, cfdata_t match, void *aux)
211 {
212           struct uhidev_attach_arg *uha = aux;
213 
214           return ucycom_lookup(uha->uiaa->uiaa_vendor, uha->uiaa->uiaa_product)
215               != NULL ? UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
216 }
217 
218 static void
ucycom_attach(device_t parent,device_t self,void * aux)219 ucycom_attach(device_t parent, device_t self, void *aux)
220 {
221           struct ucycom_softc *sc = device_private(self);
222           struct uhidev_attach_arg *uha = aux;
223           int size, repid;
224           void *desc;
225 
226           sc->sc_dev = self;
227           sc->sc_hdev = uha->parent;
228           sc->sc_udev = uha->uiaa->uiaa_device;
229           sc->sc_init_state = UCYCOM_INIT_NONE;
230 
231           uhidev_get_report_desc(uha->parent, &desc, &size);
232           repid = uha->reportid;
233           sc->sc_olen = hid_report_size(desc, size, hid_output, repid);
234           sc->sc_flen = hid_report_size(desc, size, hid_feature, repid);
235 
236           if (sc->sc_olen != 8 && sc->sc_olen != 32)
237                     return;
238           if (sc->sc_flen != 5)
239                     return;
240 
241           sc->sc_msr = sc->sc_mcr = 0;
242 
243           /* not MP-safe */
244           usb_init_task(&sc->sc_task, ucycomstarttask, sc, 0);
245 
246           /* set up tty */
247           sc->sc_tty = tty_alloc();
248           sc->sc_tty->t_sc = sc;
249           sc->sc_tty->t_oproc = ucycomstart;
250           sc->sc_tty->t_param = ucycomparam;
251 
252           tty_attach(sc->sc_tty);
253 
254           /* Nothing interesting to report */
255           aprint_normal("\n");
256 
257           sc->sc_init_state = UCYCOM_INIT_INITED;
258 }
259 
260 
261 static int
ucycom_detach(device_t self,int flags)262 ucycom_detach(device_t self, int flags)
263 {
264           struct ucycom_softc *sc = device_private(self);
265           struct tty *tp = sc->sc_tty;
266           int maj, mn;
267           int s;
268 
269           DPRINTF(("ucycom_detach: sc=%p flags=%d tp=%p\n", sc, flags, tp));
270 
271           sc->sc_dying = 1;
272 
273           s = splusb();
274           if (tp != NULL) {
275                     ttylock(tp);
276                     CLR(tp->t_state, TS_CARR_ON);
277                     CLR(tp->t_cflag, CLOCAL | MDMBUF);
278                     ttyflush(tp, FREAD|FWRITE);
279                     ttyunlock(tp);
280           }
281           /* Wait for processes to go away. */
282           usb_detach_waitold(sc->sc_dev);
283           splx(s);
284 
285           /* locate the major number */
286           maj = cdevsw_lookup_major(&ucycom_cdevsw);
287 
288           /* Nuke the vnodes for any open instances. */
289           mn = device_unit(self);
290 
291           DPRINTFN(2, ("ucycom_detach: maj=%d mn=%d\n", maj, mn));
292           vdevgone(maj, mn, mn, VCHR);
293           vdevgone(maj, mn | UCYCOMDIALOUT_MASK, mn | UCYCOMDIALOUT_MASK, VCHR);
294           vdevgone(maj, mn | UCYCOMCALLUNIT_MASK, mn | UCYCOMCALLUNIT_MASK, VCHR);
295 
296           usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER, NULL);
297 
298           /* Detach and free the tty. */
299           if (tp != NULL) {
300                     DPRINTF(("ucycom_detach: tty_detach %p\n", tp));
301                     tty_detach(tp);
302                     tty_free(tp);
303                     sc->sc_tty = NULL;
304           }
305 
306           return 0;
307 }
308 
309 static int
ucycom_activate(device_t self,enum devact act)310 ucycom_activate(device_t self, enum devact act)
311 {
312           struct ucycom_softc *sc = device_private(self);
313 
314           DPRINTFN(5,("ucycom_activate: %d\n", act));
315 
316           switch (act) {
317           case DVACT_DEACTIVATE:
318                     sc->sc_dying = 1;
319                     return 0;
320           default:
321                     return EOPNOTSUPP;
322           }
323 }
324 
325 #if 0
326 void
327 ucycom_shutdown(struct ucycom_softc *sc)
328 {
329           struct tty *tp = sc->sc_tty;
330 
331           DPRINTF(("ucycom_shutdown\n"));
332           /*
333            * Hang up if necessary.  Wait a bit, so the other side has time to
334            * notice even if we immediately open the port again.
335            */
336           if (ISSET(tp->t_cflag, HUPCL)) {
337                     ucycom_dtr(sc, 0);
338                     (void)tsleep(sc, TTIPRI, ttclos, hz);
339           }
340 }
341 #endif
342 
343 static int
ucycomopen(dev_t dev,int flag,int mode,struct lwp * l)344 ucycomopen(dev_t dev, int flag, int mode, struct lwp *l)
345 {
346           struct ucycom_softc *sc =
347               device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev));
348           struct tty *tp;
349           int s, err;
350 
351           DPRINTF(("ucycomopen: unit=%d\n", UCYCOMUNIT(dev)));
352           DPRINTF(("ucycomopen: sc=%p\n", sc));
353 
354           if (sc == NULL)
355                     return ENXIO;
356           if (sc->sc_dying)
357                     return EIO;
358           if (sc->sc_init_state != UCYCOM_INIT_INITED)
359                     return ENXIO;
360           if (!device_is_active(sc->sc_dev))
361                     return ENXIO;
362 
363           tp = sc->sc_tty;
364 
365           DPRINTF(("ucycomopen: tp=%p\n", tp));
366 
367           if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp))
368                     return EBUSY;
369 
370           s = spltty();
371 
372           if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
373                     struct termios t;
374 
375                     tp->t_dev = dev;
376 
377                     err = uhidev_open(sc->sc_hdev, &ucycom_intr, sc);
378                     if (err) {
379                               /* Any cleanup? */
380                               splx(s);
381                               return err;
382                     }
383 
384                     /*
385                      * Initialize the termios status to the defaults.  Add in the
386                      * sticky bits from TIOCSFLAGS.
387                      */
388                     t.c_ispeed = 0;
389                     t.c_ospeed = TTYDEF_SPEED;
390                     t.c_cflag = TTYDEF_CFLAG;
391                     if (ISSET(sc->sc_swflags, TIOCFLAG_CLOCAL))
392                               SET(t.c_cflag, CLOCAL);
393                     if (ISSET(sc->sc_swflags, TIOCFLAG_CRTSCTS))
394                               SET(t.c_cflag, CRTSCTS);
395                     if (ISSET(sc->sc_swflags, TIOCFLAG_MDMBUF))
396                               SET(t.c_cflag, MDMBUF);
397 
398                     tp->t_ospeed = 0;
399                     (void) ucycomparam(tp, &t);
400                     tp->t_iflag = TTYDEF_IFLAG;
401                     tp->t_oflag = TTYDEF_OFLAG;
402                     tp->t_lflag = TTYDEF_LFLAG;
403                     ttychars(tp);
404                     ttsetwater(tp);
405 
406                     /* Allocate an output report buffer */
407                     sc->sc_obuf = kmem_alloc(sc->sc_olen, KM_SLEEP);
408 
409                     DPRINTF(("ucycomopen: sc->sc_obuf=%p\n", sc->sc_obuf));
410 
411 #if 0
412                     /* XXX Don't do this as for some reason trying to do an
413                      * XXX interrupt out transfer at this point means everything
414                      * XXX gets stuck!?!
415                      */
416                     /*
417                      * Turn on DTR.  We must always do this, even if carrier is not
418                      * present, because otherwise we'd have to use TIOCSDTR
419                      * immediately after setting CLOCAL, which applications do not
420                      * expect.  We always assert DTR while the device is open
421                      * unless explicitly requested to deassert it.
422                      */
423                     ucycom_dtr(sc, 1);
424 #endif
425 
426 #if 0
427                     /* XXX CLR(sc->sc_rx_flags, RX_ANY_BLOCK);*/
428                     ucycom_hwiflow(sc);
429 #endif
430 
431           }
432           splx(s);
433 
434           err = ttyopen(tp, UCYCOMDIALOUT(dev), ISSET(flag, O_NONBLOCK));
435           if (err)
436                     goto bad;
437 
438           err = (*tp->t_linesw->l_open)(dev, tp);
439           if (err)
440                     goto bad;
441 
442           return 0;
443 
444 bad:
445           if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
446                     /*
447                      * We failed to open the device, and nobody else had it opened.
448                      * Clean up the state as appropriate.
449                      */
450                     ucycom_cleanup(sc);
451           }
452 
453           return err;
454 
455 }
456 
457 
458 static int
ucycomclose(dev_t dev,int flag,int mode,struct lwp * l)459 ucycomclose(dev_t dev, int flag, int mode, struct lwp *l)
460 {
461           struct ucycom_softc *sc =
462               device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev));
463           struct tty *tp = sc->sc_tty;
464 
465           DPRINTF(("ucycomclose: unit=%d\n", UCYCOMUNIT(dev)));
466           if (!ISSET(tp->t_state, TS_ISOPEN))
467                     return 0;
468 
469           (*tp->t_linesw->l_close)(tp, flag);
470           ttyclose(tp);
471 
472           if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
473                     /*
474                      * Although we got a last close, the device may still be in
475                      * use; e.g. if this was the dialout node, and there are still
476                      * processes waiting for carrier on the non-dialout node.
477                      */
478                     ucycom_cleanup(sc);
479           }
480 
481           return 0;
482 }
483 
484 Static void
ucycomstart(struct tty * tp)485 ucycomstart(struct tty *tp)
486 {
487           struct ucycom_softc *sc =
488               device_lookup_private(&ucycom_cd, UCYCOMUNIT(tp->t_dev));
489           usbd_status err __unused;
490           u_char *data;
491           int cnt, len, s;
492 
493           KASSERT(ttylocked(tp));
494 
495           if (sc->sc_dying)
496                     return;
497 
498           s = spltty();
499           if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) {
500                     DPRINTFN(4,("ucycomstart: no go, state=%#x\n", tp->t_state));
501                     goto out;
502           }
503 
504 #if 0
505           /* HW FLOW CTL */
506           if (sc->sc_tx_stopped)
507                     goto out;
508 #endif
509 
510           if (ttypull(tp) == 0)
511                     goto out;
512 
513           /* Grab the first contiguous region of buffer space. */
514           data = tp->t_outq.c_cf;
515           cnt = ndqb(&tp->t_outq, 0);
516 
517           if (cnt == 0) {
518                     DPRINTF(("ucycomstart: cnt == 0\n"));
519                     goto out;
520           }
521 
522           SET(tp->t_state, TS_BUSY);
523 
524           /*
525            * The 8 byte output report uses byte 0 for control and byte
526            * count.
527            *
528            * The 32 byte output report uses byte 0 for control. Byte 1
529            * is used for byte count.
530            */
531           memset(sc->sc_obuf, 0, sc->sc_olen);
532           len = cnt;
533           switch (sc->sc_olen) {
534           case 8:
535                     if (cnt > sc->sc_olen - 1) {
536                               DPRINTF(("ucycomstart(8): big buffer %d chars\n", len));
537                               len = sc->sc_olen - 1;
538                     }
539 
540                     memcpy(sc->sc_obuf + 1, data, len);
541                     sc->sc_obuf[0] = len | sc->sc_mcr;
542 
543                     DPRINTF(("ucycomstart(8): sc->sc_obuf[0] = %d | %d = %d\n",
544                         len, sc->sc_mcr, sc->sc_obuf[0]));
545 #ifdef UCYCOM_DEBUG
546                     if (ucycomdebug > 10) {
547                               uint32_t i;
548                               uint8_t *d = data;
549 
550                               DPRINTF(("ucycomstart(8): data ="));
551                               for (i = 0; i < len; i++)
552                                         DPRINTF((" %02x", d[i]));
553                               DPRINTF(("\n"));
554                     }
555 #endif
556                     break;
557 
558           case 32:
559                     if (cnt > sc->sc_olen - 2) {
560                               DPRINTF(("ucycomstart(32): big buffer %d chars\n",
561                                   len));
562                               len = sc->sc_olen - 2;
563                     }
564 
565                     memcpy(sc->sc_obuf + 2, data, len);
566                     sc->sc_obuf[0] = sc->sc_mcr;
567                     sc->sc_obuf[1] = len;
568                     DPRINTF(("ucycomstart(32): sc->sc_obuf[0] = %d\n"
569                         "sc->sc_obuf[1] = %d\n", sc->sc_obuf[0], sc->sc_obuf[1]));
570 #ifdef UCYCOM_DEBUG
571                     if (ucycomdebug > 10) {
572                               uint32_t i;
573                               uint8_t *d = data;
574 
575                               DPRINTF(("ucycomstart(32): data ="));
576                               for (i = 0; i < len; i++)
577                                         DPRINTF((" %02x", d[i]));
578                               DPRINTF(("\n"));
579                     }
580 #endif
581                     break;
582 
583           default:
584                     DPRINTFN(2,("ucycomstart: unknown output report size (%zd)\n",
585                         sc->sc_olen));
586                     goto out;
587           }
588           splx(s);
589           sc->sc_wlen = len;
590 
591 #ifdef UCYCOM_DEBUG
592           if (ucycomdebug > 5) {
593                     int i;
594 
595                     if (len != 0) {
596                               DPRINTF(("ucycomstart: sc->sc_obuf[0..%zd) =",
597                                   sc->sc_olen));
598                               for (i = 0; i < sc->sc_olen; i++)
599                                         DPRINTF((" %02x", sc->sc_obuf[i]));
600                               DPRINTF(("\n"));
601                     }
602           }
603 #endif
604           DPRINTFN(4,("ucycomstart: %d chars\n", len));
605           usb_add_task(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER);
606           return;
607 
608 out:
609           splx(s);
610 }
611 
612 Static void
ucycomstarttask(void * cookie)613 ucycomstarttask(void *cookie)
614 {
615           struct ucycom_softc *sc = cookie;
616           usbd_status err;
617 
618           /* What can we do on error? */
619           err = uhidev_write_async(sc->sc_hdev, sc->sc_obuf, sc->sc_olen, 0,
620               USBD_NO_TIMEOUT, ucycomwritecb, sc);
621 
622 #ifdef UCYCOM_DEBUG
623           if (err != USBD_IN_PROGRESS)
624                     DPRINTF(("ucycomstart: err=%s\n", usbd_errstr(err)));
625 #else
626           __USE(err);
627 #endif
628 }
629 
630 Static void
ucycomwritecb(struct usbd_xfer * xfer,void * p,usbd_status status)631 ucycomwritecb(struct usbd_xfer *xfer, void *p, usbd_status status)
632 {
633           struct ucycom_softc *sc = (struct ucycom_softc *)p;
634           struct tty *tp = sc->sc_tty;
635           usbd_status stat;
636           int len, s;
637 
638           if (status == USBD_CANCELLED || sc->sc_dying)
639                     goto error;
640 
641           if (status) {
642                     DPRINTF(("ucycomwritecb: status=%d\n", status));
643                     /* XXX we should restart after some delay. */
644                     goto error;
645           }
646 
647           usbd_get_xfer_status(xfer, NULL, NULL, &len, &stat);
648 
649           if (status != USBD_NORMAL_COMPLETION) {
650                     DPRINTFN(4,("ucycomwritecb: status = %d\n", status));
651                     goto error;
652           }
653 
654           DPRINTFN(4,("ucycomwritecb: did %d/%d chars\n", sc->sc_wlen, len));
655 
656           s = spltty();
657           CLR(tp->t_state, TS_BUSY);
658           if (ISSET(tp->t_state, TS_FLUSH))
659                     CLR(tp->t_state, TS_FLUSH);
660           else
661                     ndflush(&tp->t_outq, sc->sc_wlen);
662           (*tp->t_linesw->l_start)(tp);
663           splx(s);
664           return;
665 
666 error:
667           s = spltty();
668           CLR(tp->t_state, TS_BUSY);
669           splx(s);
670 }
671 
672 Static int
ucycomparam(struct tty * tp,struct termios * t)673 ucycomparam(struct tty *tp, struct termios *t)
674 {
675           struct ucycom_softc *sc = tp->t_sc;
676           uint32_t baud;
677           uint8_t cfg;
678           int err;
679 
680           if (t->c_ospeed < 0) {
681                     DPRINTF(("ucycomparam: c_ospeed < 0\n"));
682                     return EINVAL;
683           }
684 
685           /* Check requested parameters. */
686           if (t->c_ispeed && t->c_ispeed != t->c_ospeed)
687                     return EINVAL;
688 
689           /*
690            * For the console, always force CLOCAL and !HUPCL, so that the port
691            * is always active.
692            */
693           if (ISSET(sc->sc_swflags, TIOCFLAG_SOFTCAR)) {
694                     SET(t->c_cflag, CLOCAL);
695                     CLR(t->c_cflag, HUPCL);
696           }
697 
698           /*
699            * If there were no changes, don't do anything.  This avoids dropping
700            * input and improves performance when all we did was frob things like
701            * VMIN and VTIME.
702            */
703           if (tp->t_ospeed == t->c_ospeed &&
704               tp->t_cflag == t->c_cflag)
705                     return 0;
706 
707           /* XXX lcr = ISSET(sc->sc_lcr, LCR_SBREAK) | cflag2lcr(t->c_cflag); */
708 
709           /* And copy to tty. */
710           tp->t_ispeed = 0;
711           tp->t_ospeed = t->c_ospeed;
712           tp->t_cflag = t->c_cflag;
713 
714           baud = t->c_ispeed;
715           DPRINTF(("ucycomparam: baud=%d\n", baud));
716 
717           if (t->c_cflag & CIGNORE) {
718                     cfg = sc->sc_cfg;
719           } else {
720                     cfg = 0;
721                     switch (t->c_cflag & CSIZE) {
722                     case CS8:
723                               cfg |= UCYCOM_DATA_BITS_8;
724                               break;
725                     case CS7:
726                               cfg |= UCYCOM_DATA_BITS_7;
727                               break;
728                     case CS6:
729                               cfg |= UCYCOM_DATA_BITS_6;
730                               break;
731                     case CS5:
732                               cfg |= UCYCOM_DATA_BITS_5;
733                               break;
734                     default:
735                               return EINVAL;
736                     }
737                     cfg |= ISSET(t->c_cflag, CSTOPB) ?
738                         UCYCOM_STOP_BITS_2 : UCYCOM_STOP_BITS_1;
739                     cfg |= ISSET(t->c_cflag, PARENB) ?
740                         UCYCOM_PARITY_ON : UCYCOM_PARITY_OFF;
741                     cfg |= ISSET(t->c_cflag, PARODD) ?
742                         UCYCOM_PARITY_ODD : UCYCOM_PARITY_EVEN;
743           }
744 
745           /*
746            * Update the tty layer's idea of the carrier bit, in case we changed
747            * CLOCAL or MDMBUF.  We don't hang up here; we only do that by
748            * explicit request.
749            */
750           DPRINTF(("ucycomparam: l_modem\n"));
751           (void) (*tp->t_linesw->l_modem)(tp, 1 /* XXX carrier */ );
752 
753           err = ucycom_configure(sc, baud, cfg);
754           return err;
755 }
756 
757 static void
ucycomstop(struct tty * tp,int flag)758 ucycomstop(struct tty *tp, int flag)
759 {
760           DPRINTF(("ucycomstop: flag=%d\n", flag));
761 }
762 
763 static int
ucycomread(dev_t dev,struct uio * uio,int flag)764 ucycomread(dev_t dev, struct uio *uio, int flag)
765 {
766           struct ucycom_softc *sc =
767               device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev));
768           struct tty *tp = sc->sc_tty;
769           int err;
770 
771           DPRINTF(("ucycomread: sc=%p, tp=%p, uio=%p, flag=%d\n", sc, tp, uio,
772               flag));
773           if (sc->sc_dying)
774                     return EIO;
775 
776           err = ((*tp->t_linesw->l_read)(tp, uio, flag));
777           return err;
778 }
779 
780 
781 static int
ucycomwrite(dev_t dev,struct uio * uio,int flag)782 ucycomwrite(dev_t dev, struct uio *uio, int flag)
783 {
784           struct ucycom_softc *sc =
785               device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev));
786           struct tty *tp = sc->sc_tty;
787           int err;
788 
789           DPRINTF(("ucycomwrite: sc=%p, tp=%p, uio=%p, flag=%d\n", sc, tp, uio,
790               flag));
791           if (sc->sc_dying)
792                     return EIO;
793 
794           err = ((*tp->t_linesw->l_write)(tp, uio, flag));
795           return err;
796 }
797 
798 static struct tty *
ucycomtty(dev_t dev)799 ucycomtty(dev_t dev)
800 {
801           struct ucycom_softc *sc =
802               device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev));
803           struct tty *tp = sc->sc_tty;
804 
805           DPRINTF(("ucycomtty: sc=%p, tp=%p\n", sc, tp));
806 
807           return tp;
808 }
809 
810 static int
ucycomioctl(dev_t dev,u_long cmd,void * data,int flag,struct lwp * l)811 ucycomioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
812 {
813           struct ucycom_softc *sc =
814               device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev));
815           struct tty *tp = sc->sc_tty;
816           int err;
817           int s;
818 
819           if (sc->sc_dying)
820                     return EIO;
821 
822           DPRINTF(("ucycomioctl: sc=%p, tp=%p, data=%p\n", sc, tp, data));
823 
824           err = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l);
825           if (err != EPASSTHROUGH)
826                     return err;
827 
828           err = ttioctl(tp, cmd, data, flag, l);
829           if (err != EPASSTHROUGH)
830                     return err;
831 
832           err = 0;
833 
834           DPRINTF(("ucycomioctl: our cmd=0x%08lx\n", cmd));
835           s = spltty();
836 
837           switch (cmd) {
838 /*        case TIOCSBRK:
839                     ucycom_break(sc, 1);
840                     break;
841 
842           case TIOCCBRK:
843                     ucycom_break(sc, 0);
844                     break;
845 */
846           case TIOCSDTR:
847                     ucycom_dtr(sc, 1);
848                     break;
849 
850           case TIOCCDTR:
851                     ucycom_dtr(sc, 0);
852                     break;
853 
854           case TIOCGFLAGS:
855                     *(int *)data = sc->sc_swflags;
856                     break;
857 
858           case TIOCSFLAGS:
859                     err = kauth_authorize_device_tty(l->l_cred,
860                         KAUTH_DEVICE_TTY_PRIVSET, tp);
861                     if (err)
862                               break;
863                     sc->sc_swflags = *(int *)data;
864                     break;
865 
866           case TIOCMSET:
867           case TIOCMBIS:
868           case TIOCMBIC:
869                     tiocm_to_ucycom(sc, cmd, *(int *)data);
870                     break;
871 
872           case TIOCMGET:
873                     *(int *)data = ucycom_to_tiocm(sc);
874                     break;
875 
876           default:
877                     err = EPASSTHROUGH;
878                     break;
879           }
880 
881           splx(s);
882 
883           return err;
884 }
885 
886 static int
ucycompoll(dev_t dev,int events,struct lwp * l)887 ucycompoll(dev_t dev, int events, struct lwp *l)
888 {
889           struct ucycom_softc *sc =
890               device_lookup_private(&ucycom_cd, UCYCOMUNIT(dev));
891           struct tty *tp = sc->sc_tty;
892           int err;
893 
894           DPRINTF(("ucycompoll: sc=%p, tp=%p, events=%d, lwp=%p\n", sc, tp,
895               events, l));
896 
897           if (sc->sc_dying)
898                     return EIO;
899 
900           err = ((*tp->t_linesw->l_poll)(tp, events, l));
901           return err;
902 }
903 
904 Static int
ucycom_configure(struct ucycom_softc * sc,uint32_t baud,uint8_t cfg)905 ucycom_configure(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg)
906 {
907           uint8_t report[5];
908           int err;
909 
910           switch (baud) {
911           case 600:
912           case 1200:
913           case 2400:
914           case 4800:
915           case 9600:
916           case 19200:
917           case 38400:
918           case 57600:
919 #if 0
920           /*
921            * Stock chips only support standard baud rates in the 600 - 57600
922            * range, but higher rates can be achieved using custom firmware.
923            */
924           case 115200:
925           case 153600:
926           case 192000:
927 #endif
928                     break;
929           default:
930                     return EINVAL;
931           }
932 
933           DPRINTF(("ucycom_configure: setting %d baud, %d-%c-%d (%d)\n", baud,
934               5 + (cfg & UCYCOM_DATA_MASK),
935               (cfg & UCYCOM_PARITY_MASK) ?
936                     ((cfg & UCYCOM_PARITY_TYPE_MASK) ? 'O' : 'E') : 'N',
937               (cfg & UCYCOM_STOP_MASK) ? 2 : 1, cfg));
938 
939           report[0] = baud & 0xff;
940           report[1] = (baud >> 8) & 0xff;
941           report[2] = (baud >> 16) & 0xff;
942           report[3] = (baud >> 24) & 0xff;
943           report[4] = cfg;
944           err = uhidev_set_report(sc->sc_hdev, UHID_FEATURE_REPORT,
945               report, sc->sc_flen);
946           if (err != 0) {
947                     DPRINTF(("%s\n", usbd_errstr(err)));
948                     return EIO;
949           }
950           sc->sc_baud = baud;
951           sc->sc_cfg = cfg;
952 
953 #ifdef UCYCOM_DEBUG
954           ucycom_get_cfg(sc);
955 #endif
956 
957           return 0;
958 }
959 
960 Static void
ucycom_intr(void * cookie,void * ibuf,u_int len)961 ucycom_intr(void *cookie, void *ibuf, u_int len)
962 {
963           struct ucycom_softc *sc = cookie;
964           struct tty *tp = sc->sc_tty;
965           int (*rint)(int , struct tty *) = tp->t_linesw->l_rint;
966           uint8_t *cp = ibuf;
967           int s, n, st, chg;
968 
969           /* We understand 8 byte and 32 byte input records */
970           switch (len) {
971           case 8:
972                     n = cp[0] & UCYCOM_LMASK;
973                     st = cp[0] & ~UCYCOM_LMASK;
974                     cp++;
975                     break;
976 
977           case 32:
978                     st = cp[0];
979                     n = cp[1];
980                     cp += 2;
981                     n = uimin(n, 30);
982                     break;
983 
984           default:
985                     DPRINTFN(3,("ucycom_intr: Unknown input report length\n"));
986                     return;
987           }
988 
989 #ifdef UCYCOM_DEBUG
990           if (ucycomdebug > 5) {
991                     uint32_t i;
992 
993                     if (n != 0) {
994                               DPRINTF(("ucycom_intr: ibuf[0..%d) =", n));
995                               for (i = 0; i < n; i++)
996                                         DPRINTF((" %02x", cp[i]));
997                               DPRINTF(("\n"));
998                     }
999           }
1000 #endif
1001 
1002           /* Give characters to tty layer. */
1003           s = spltty();
1004           while (n-- > 0) {
1005                     DPRINTFN(7,("ucycom_intr: char=0x%02x\n", *cp));
1006                     if ((*rint)(*cp++, tp) == -1) {
1007                               /* XXX what should we do? */
1008                               aprint_error_dev(sc->sc_dev, "lost a character\n");
1009                               break;
1010                     }
1011           }
1012           splx(s);
1013           chg = st ^ sc->sc_msr;
1014           sc->sc_msr = st;
1015           if (ISSET(chg, UCYCOM_DCD))
1016                     (*tp->t_linesw->l_modem)(tp,
1017                         ISSET(sc->sc_msr, UCYCOM_DCD));
1018 
1019 }
1020 
1021 Static void
tiocm_to_ucycom(struct ucycom_softc * sc,u_long how,int ttybits)1022 tiocm_to_ucycom(struct ucycom_softc *sc, u_long how, int ttybits)
1023 {
1024           u_char combits;
1025           u_char before = sc->sc_mcr;
1026 
1027           combits = 0;
1028           if (ISSET(ttybits, TIOCM_DTR))
1029                     SET(combits, UCYCOM_DTR);
1030           if (ISSET(ttybits, TIOCM_RTS))
1031                     SET(combits, UCYCOM_RTS);
1032 
1033           switch (how) {
1034           case TIOCMBIC:
1035                     CLR(sc->sc_mcr, combits);
1036                     break;
1037 
1038           case TIOCMBIS:
1039                     SET(sc->sc_mcr, combits);
1040                     break;
1041 
1042           case TIOCMSET:
1043                     CLR(sc->sc_mcr, UCYCOM_DTR | UCYCOM_RTS);
1044                     SET(sc->sc_mcr, combits);
1045                     break;
1046           }
1047           if (before ^ sc->sc_mcr) {
1048                     DPRINTF(("tiocm_to_ucycom: something has changed\n"));
1049                     ucycom_set_status(sc);
1050           }
1051 }
1052 
1053 Static int
ucycom_to_tiocm(struct ucycom_softc * sc)1054 ucycom_to_tiocm(struct ucycom_softc *sc)
1055 {
1056           u_char combits;
1057           int ttybits = 0;
1058 
1059           combits = sc->sc_mcr;
1060           if (ISSET(combits, UCYCOM_DTR))
1061                     SET(ttybits, TIOCM_DTR);
1062           if (ISSET(combits, UCYCOM_RTS))
1063                     SET(ttybits, TIOCM_RTS);
1064 
1065           combits = sc->sc_msr;
1066           if (ISSET(combits, UCYCOM_DCD))
1067                     SET(ttybits, TIOCM_CD);
1068           if (ISSET(combits, UCYCOM_CTS))
1069                     SET(ttybits, TIOCM_CTS);
1070           if (ISSET(combits, UCYCOM_DSR))
1071                     SET(ttybits, TIOCM_DSR);
1072           if (ISSET(combits, UCYCOM_RI))
1073                     SET(ttybits, TIOCM_RI);
1074 
1075           return ttybits;
1076 }
1077 
1078 Static void
ucycom_dtr(struct ucycom_softc * sc,int set)1079 ucycom_dtr(struct ucycom_softc *sc, int set)
1080 {
1081           uint8_t old;
1082 
1083           old = sc->sc_mcr;
1084           if (set)
1085                     SET(sc->sc_mcr, UCYCOM_DTR);
1086           else
1087                     CLR(sc->sc_mcr, UCYCOM_DTR);
1088 
1089           if (old ^ sc->sc_mcr)
1090                     ucycom_set_status(sc);
1091 }
1092 
1093 #if 0
1094 Static void
1095 ucycom_rts(struct ucycom_softc *sc, int set)
1096 {
1097           uint8_t old;
1098 
1099           old = sc->sc_msr;
1100           if (set)
1101                     SET(sc->sc_mcr, UCYCOM_RTS);
1102           else
1103                     CLR(sc->sc_mcr, UCYCOM_RTS);
1104 
1105           if (old ^ sc->sc_mcr)
1106                     ucycom_set_status(sc);
1107 }
1108 #endif
1109 
1110 Static void
ucycom_set_status(struct ucycom_softc * sc)1111 ucycom_set_status(struct ucycom_softc *sc)
1112 {
1113           int err;
1114 
1115           if (sc->sc_olen != 8 && sc->sc_olen != 32) {
1116                     DPRINTFN(2,("ucycom_set_status: unknown output report "
1117                         "size (%zd)\n", sc->sc_olen));
1118                     return;
1119           }
1120 
1121           DPRINTF(("ucycom_set_status: %d\n", sc->sc_mcr));
1122 
1123           memset(sc->sc_obuf, 0, sc->sc_olen);
1124           sc->sc_obuf[0] = sc->sc_mcr;
1125 
1126           err = uhidev_write(sc->sc_hdev, sc->sc_obuf, sc->sc_olen);
1127           if (err) {
1128                     DPRINTF(("ucycom_set_status: err=%d\n", err));
1129           }
1130 }
1131 
1132 #ifdef UCYCOM_DEBUG
1133 Static void
ucycom_get_cfg(struct ucycom_softc * sc)1134 ucycom_get_cfg(struct ucycom_softc *sc)
1135 {
1136           int err, cfg, baud;
1137           uint8_t report[5];
1138 
1139           err = uhidev_get_report(sc->sc_hdev, UHID_FEATURE_REPORT,
1140               report, sc->sc_flen);
1141           if (err) {
1142                     DPRINTF(("%s: failed\n", __func__));
1143                     return;
1144           }
1145           cfg = report[4];
1146           baud = (report[3] << 24) + (report[2] << 16) + (report[1] << 8) +
1147               report[0];
1148           DPRINTF(("ucycom_get_cfg: device reports %d baud, %d-%c-%d (%d)\n",
1149               baud, 5 + (cfg & UCYCOM_DATA_MASK),
1150               (cfg & UCYCOM_PARITY_MASK) ?
1151                     ((cfg & UCYCOM_PARITY_TYPE_MASK) ? 'O' : 'E') : 'N',
1152               (cfg & UCYCOM_STOP_MASK) ? 2 : 1, cfg));
1153 }
1154 #endif
1155 
1156 Static void
ucycom_cleanup(struct ucycom_softc * sc)1157 ucycom_cleanup(struct ucycom_softc *sc)
1158 {
1159           uint8_t   *obuf;
1160 
1161           DPRINTF(("ucycom_cleanup: closing uhidev\n"));
1162 
1163           obuf = sc->sc_obuf;
1164           sc->sc_obuf = NULL;
1165           uhidev_close(sc->sc_hdev);
1166 
1167           if (obuf != NULL)
1168                     kmem_free(obuf, sc->sc_olen);
1169 }
1170