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