1184610Salfred/*	$NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $	*/
2184610Salfred
3184610Salfred/*-
4184610Salfred * Copyright (c) 2001-2003, 2005, 2008
5184610Salfred *	Shunsuke Akiyama <akiyama@jp.FreeBSD.org>.
6184610Salfred * All rights reserved.
7184610Salfred *
8184610Salfred * Redistribution and use in source and binary forms, with or without
9184610Salfred * modification, are permitted provided that the following conditions
10184610Salfred * are met:
11184610Salfred * 1. Redistributions of source code must retain the above copyright
12184610Salfred *    notice, this list of conditions and the following disclaimer.
13184610Salfred * 2. Redistributions in binary form must reproduce the above copyright
14184610Salfred *    notice, this list of conditions and the following disclaimer in the
15184610Salfred *    documentation and/or other materials provided with the distribution.
16184610Salfred *
17184610Salfred * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18184610Salfred * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19184610Salfred * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20184610Salfred * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21184610Salfred * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22184610Salfred * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23184610Salfred * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24184610Salfred * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25184610Salfred * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26184610Salfred * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27184610Salfred * SUCH DAMAGE.
28184610Salfred */
29184610Salfred
30184610Salfred#include <sys/cdefs.h>
31184610Salfred__FBSDID("$FreeBSD$");
32184610Salfred
33184610Salfred/*-
34184610Salfred * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc.
35184610Salfred * All rights reserved.
36184610Salfred *
37184610Salfred * This code is derived from software contributed to The NetBSD Foundation
38184610Salfred * by Lennart Augustsson (lennart@augustsson.net) at
39184610Salfred * Carlstedt Research & Technology.
40184610Salfred *
41184610Salfred * Redistribution and use in source and binary forms, with or without
42184610Salfred * modification, are permitted provided that the following conditions
43184610Salfred * are met:
44184610Salfred * 1. Redistributions of source code must retain the above copyright
45184610Salfred *    notice, this list of conditions and the following disclaimer.
46184610Salfred * 2. Redistributions in binary form must reproduce the above copyright
47184610Salfred *    notice, this list of conditions and the following disclaimer in the
48184610Salfred *    documentation and/or other materials provided with the distribution.
49184610Salfred *
50184610Salfred * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
51184610Salfred * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
52184610Salfred * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
53184610Salfred * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
54184610Salfred * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55184610Salfred * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56184610Salfred * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
57184610Salfred * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
58184610Salfred * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
59184610Salfred * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
60184610Salfred * POSSIBILITY OF SUCH DAMAGE.
61184610Salfred */
62184610Salfred
63194677Sthompsa#include <sys/stdint.h>
64194677Sthompsa#include <sys/stddef.h>
65194677Sthompsa#include <sys/param.h>
66194677Sthompsa#include <sys/queue.h>
67194677Sthompsa#include <sys/types.h>
68194677Sthompsa#include <sys/systm.h>
69194677Sthompsa#include <sys/kernel.h>
70194677Sthompsa#include <sys/bus.h>
71194677Sthompsa#include <sys/module.h>
72194677Sthompsa#include <sys/lock.h>
73194677Sthompsa#include <sys/mutex.h>
74194677Sthompsa#include <sys/condvar.h>
75194677Sthompsa#include <sys/sysctl.h>
76194677Sthompsa#include <sys/sx.h>
77194677Sthompsa#include <sys/unistd.h>
78194677Sthompsa#include <sys/callout.h>
79194677Sthompsa#include <sys/malloc.h>
80194677Sthompsa#include <sys/priv.h>
81197570Sthompsa#include <sys/cons.h>
82197570Sthompsa#include <sys/kdb.h>
83194677Sthompsa
84188942Sthompsa#include <dev/usb/usb.h>
85194677Sthompsa#include <dev/usb/usbdi.h>
86194677Sthompsa#include <dev/usb/usbdi_util.h>
87184610Salfred
88194228Sthompsa#define	USB_DEBUG_VAR ucom_debug
89188942Sthompsa#include <dev/usb/usb_debug.h>
90194677Sthompsa#include <dev/usb/usb_busdma.h>
91188942Sthompsa#include <dev/usb/usb_process.h>
92184610Salfred
93188942Sthompsa#include <dev/usb/serial/usb_serial.h>
94184610Salfred
95197570Sthompsa#include "opt_gdb.h"
96197570Sthompsa
97227309Sedstatic SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom");
98197570Sthompsa
99207077Sthompsa#ifdef USB_DEBUG
100194228Sthompsastatic int ucom_debug = 0;
101184610Salfred
102192502SthompsaSYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW,
103194228Sthompsa    &ucom_debug, 0, "ucom debug level");
104184610Salfred#endif
105184610Salfred
106197570Sthompsa#define	UCOM_CONS_BUFSIZE 1024
107197570Sthompsa
108197570Sthompsastatic uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE];
109197570Sthompsastatic uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE];
110197570Sthompsa
111197570Sthompsastatic unsigned int ucom_cons_rx_low = 0;
112197570Sthompsastatic unsigned int ucom_cons_rx_high = 0;
113197570Sthompsa
114197570Sthompsastatic unsigned int ucom_cons_tx_low = 0;
115197570Sthompsastatic unsigned int ucom_cons_tx_high = 0;
116197570Sthompsa
117197570Sthompsastatic int ucom_cons_unit = -1;
118214761Sn_hibmastatic int ucom_cons_subunit = 0;
119197570Sthompsastatic int ucom_cons_baud = 9600;
120197570Sthompsastatic struct ucom_softc *ucom_cons_softc = NULL;
121197570Sthompsa
122197570SthompsaTUNABLE_INT("hw.usb.ucom.cons_unit", &ucom_cons_unit);
123242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RW | CTLFLAG_TUN,
124197570Sthompsa    &ucom_cons_unit, 0, "console unit number");
125214761Sn_hibmaTUNABLE_INT("hw.usb.ucom.cons_subunit", &ucom_cons_subunit);
126242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RW | CTLFLAG_TUN,
127214761Sn_hibma    &ucom_cons_subunit, 0, "console subunit number");
128197570SthompsaTUNABLE_INT("hw.usb.ucom.cons_baud", &ucom_cons_baud);
129242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RW | CTLFLAG_TUN,
130197570Sthompsa    &ucom_cons_baud, 0, "console baud rate");
131197570Sthompsa
132194228Sthompsastatic usb_proc_callback_t ucom_cfg_start_transfers;
133194228Sthompsastatic usb_proc_callback_t ucom_cfg_open;
134194228Sthompsastatic usb_proc_callback_t ucom_cfg_close;
135194228Sthompsastatic usb_proc_callback_t ucom_cfg_line_state;
136194228Sthompsastatic usb_proc_callback_t ucom_cfg_status_change;
137194228Sthompsastatic usb_proc_callback_t ucom_cfg_param;
138184610Salfred
139214761Sn_hibmastatic int	ucom_unit_alloc(void);
140214761Sn_hibmastatic void	ucom_unit_free(int);
141214761Sn_hibmastatic int	ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *);
142239179Shselaskystatic void	ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *);
143194228Sthompsastatic void	ucom_queue_command(struct ucom_softc *,
144193045Sthompsa		    usb_proc_callback_t *, struct termios *pt,
145192984Sthompsa		    struct usb_proc_msg *t0, struct usb_proc_msg *t1);
146194228Sthompsastatic void	ucom_shutdown(struct ucom_softc *);
147197570Sthompsastatic void	ucom_ring(struct ucom_softc *, uint8_t);
148194228Sthompsastatic void	ucom_break(struct ucom_softc *, uint8_t);
149194228Sthompsastatic void	ucom_dtr(struct ucom_softc *, uint8_t);
150194228Sthompsastatic void	ucom_rts(struct ucom_softc *, uint8_t);
151184610Salfred
152194228Sthompsastatic tsw_open_t ucom_open;
153194228Sthompsastatic tsw_close_t ucom_close;
154194228Sthompsastatic tsw_ioctl_t ucom_ioctl;
155194228Sthompsastatic tsw_modem_t ucom_modem;
156194228Sthompsastatic tsw_param_t ucom_param;
157194228Sthompsastatic tsw_outwakeup_t ucom_outwakeup;
158242619Shselaskystatic tsw_inwakeup_t ucom_inwakeup;
159194228Sthompsastatic tsw_free_t ucom_free;
160184610Salfred
161194228Sthompsastatic struct ttydevsw ucom_class = {
162184610Salfred	.tsw_flags = TF_INITLOCK | TF_CALLOUT,
163194228Sthompsa	.tsw_open = ucom_open,
164194228Sthompsa	.tsw_close = ucom_close,
165194228Sthompsa	.tsw_outwakeup = ucom_outwakeup,
166242619Shselasky	.tsw_inwakeup = ucom_inwakeup,
167194228Sthompsa	.tsw_ioctl = ucom_ioctl,
168194228Sthompsa	.tsw_param = ucom_param,
169194228Sthompsa	.tsw_modem = ucom_modem,
170194228Sthompsa	.tsw_free = ucom_free,
171184610Salfred};
172184610Salfred
173188942SthompsaMODULE_DEPEND(ucom, usb, 1, 1, 1);
174188942SthompsaMODULE_VERSION(ucom, 1);
175184610Salfred
176239179Shselasky#define	UCOM_UNIT_MAX 		128	/* maximum number of units */
177239179Shselasky#define	UCOM_TTY_PREFIX		"U"
178184610Salfred
179239179Shselaskystatic struct unrhdr *ucom_unrhdr;
180239179Shselaskystatic struct mtx ucom_mtx;
181239179Shselaskystatic int ucom_close_refs;
182184610Salfred
183239179Shselaskystatic void
184239179Shselaskyucom_init(void *arg)
185239179Shselasky{
186239179Shselasky	DPRINTF("\n");
187239179Shselasky	ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL);
188239179Shselasky	mtx_init(&ucom_mtx, "UCOM MTX", NULL, MTX_DEF);
189239179Shselasky}
190239179ShselaskySYSINIT(ucom_init, SI_SUB_KLD - 1, SI_ORDER_ANY, ucom_init, NULL);
191214843Sn_hibma
192239179Shselaskystatic void
193239179Shselaskyucom_uninit(void *arg)
194239179Shselasky{
195239179Shselasky	struct unrhdr *hdr;
196239179Shselasky	hdr = ucom_unrhdr;
197239179Shselasky	ucom_unrhdr = NULL;
198239179Shselasky
199239179Shselasky	DPRINTF("\n");
200239179Shselasky
201239179Shselasky	if (hdr != NULL)
202239179Shselasky		delete_unrhdr(hdr);
203239179Shselasky
204239179Shselasky	mtx_destroy(&ucom_mtx);
205239179Shselasky}
206268206ShselaskySYSUNINIT(ucom_uninit, SI_SUB_KLD - 3, SI_ORDER_ANY, ucom_uninit, NULL);
207239179Shselasky
208214761Sn_hibma/*
209214761Sn_hibma * Mark a unit number (the X in cuaUX) as in use.
210214761Sn_hibma *
211214761Sn_hibma * Note that devices using a different naming scheme (see ucom_tty_name()
212214761Sn_hibma * callback) still use this unit allocation.
213214761Sn_hibma */
214214761Sn_hibmastatic int
215214761Sn_hibmaucom_unit_alloc(void)
216184610Salfred{
217214761Sn_hibma	int unit;
218184610Salfred
219239179Shselasky	/* sanity checks */
220239179Shselasky	if (ucom_unrhdr == NULL) {
221239179Shselasky		DPRINTF("ucom_unrhdr is NULL\n");
222239179Shselasky		return (-1);
223214919Sn_hibma	}
224239179Shselasky	unit = alloc_unr(ucom_unrhdr);
225239179Shselasky	DPRINTF("unit %d is allocated\n", unit);
226239179Shselasky	return (unit);
227184610Salfred}
228184610Salfred
229214761Sn_hibma/*
230214761Sn_hibma * Mark the unit number as not in use.
231214761Sn_hibma */
232184610Salfredstatic void
233214761Sn_hibmaucom_unit_free(int unit)
234184610Salfred{
235239179Shselasky	/* sanity checks */
236239179Shselasky	if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) {
237239179Shselasky		DPRINTF("cannot free unit number\n");
238239179Shselasky		return;
239239179Shselasky	}
240239179Shselasky	DPRINTF("unit %d is freed\n", unit);
241239179Shselasky	free_unr(ucom_unrhdr, unit);
242184610Salfred}
243184610Salfred
244184610Salfred/*
245214761Sn_hibma * Setup a group of one or more serial ports.
246184610Salfred *
247188413Sthompsa * The mutex pointed to by "mtx" is applied before all
248188413Sthompsa * callbacks are called back. Also "mtx" must be applied
249188413Sthompsa * before calling into the ucom-layer!
250184610Salfred */
251184610Salfredint
252194228Sthompsaucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc,
253233774Shselasky    int subunits, void *parent,
254192984Sthompsa    const struct ucom_callback *callback, struct mtx *mtx)
255184610Salfred{
256233774Shselasky	int subunit;
257184610Salfred	int error = 0;
258184610Salfred
259184610Salfred	if ((sc == NULL) ||
260233774Shselasky	    (subunits <= 0) ||
261239179Shselasky	    (callback == NULL) ||
262239179Shselasky	    (mtx == NULL)) {
263184610Salfred		return (EINVAL);
264184610Salfred	}
265188413Sthompsa
266230209Shselasky	/* allocate a uniq unit number */
267214761Sn_hibma	ssc->sc_unit = ucom_unit_alloc();
268214761Sn_hibma	if (ssc->sc_unit == -1)
269184610Salfred		return (ENOMEM);
270188413Sthompsa
271230209Shselasky	/* generate TTY name string */
272230209Shselasky	snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname),
273230209Shselasky	    UCOM_TTY_PREFIX "%d", ssc->sc_unit);
274230209Shselasky
275230209Shselasky	/* create USB request handling process */
276194228Sthompsa	error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED);
277188413Sthompsa	if (error) {
278214761Sn_hibma		ucom_unit_free(ssc->sc_unit);
279188413Sthompsa		return (error);
280184610Salfred	}
281214761Sn_hibma	ssc->sc_subunits = subunits;
282239299Shselasky	ssc->sc_flag = UCOM_FLAG_ATTACHED |
283239299Shselasky	    UCOM_FLAG_FREE_UNIT;
284188413Sthompsa
285239299Shselasky	if (callback->ucom_free == NULL)
286239299Shselasky		ssc->sc_flag |= UCOM_FLAG_WAIT_REFS;
287239179Shselasky
288239299Shselasky	/* increment reference count */
289239299Shselasky	ucom_ref(ssc);
290239299Shselasky
291214831Sn_hibma	for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
292214761Sn_hibma		sc[subunit].sc_subunit = subunit;
293214761Sn_hibma		sc[subunit].sc_super = ssc;
294214761Sn_hibma		sc[subunit].sc_mtx = mtx;
295214761Sn_hibma		sc[subunit].sc_parent = parent;
296214761Sn_hibma		sc[subunit].sc_callback = callback;
297184610Salfred
298214761Sn_hibma		error = ucom_attach_tty(ssc, &sc[subunit]);
299184610Salfred		if (error) {
300214761Sn_hibma			ucom_detach(ssc, &sc[0]);
301188413Sthompsa			return (error);
302184610Salfred		}
303239179Shselasky		/* increment reference count */
304239179Shselasky		ucom_ref(ssc);
305239179Shselasky
306239179Shselasky		/* set subunit attached */
307214761Sn_hibma		sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED;
308184610Salfred	}
309214761Sn_hibma
310214831Sn_hibma	DPRINTF("tp = %p, unit = %d, subunits = %d\n",
311214831Sn_hibma		sc->sc_tty, ssc->sc_unit, ssc->sc_subunits);
312214761Sn_hibma
313188413Sthompsa	return (0);
314184610Salfred}
315184610Salfred
316188413Sthompsa/*
317239299Shselasky * The following function will do nothing if the structure pointed to
318239299Shselasky * by "ssc" and "sc" is zero or has already been detached.
319184610Salfred */
320184610Salfredvoid
321214761Sn_hibmaucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc)
322184610Salfred{
323233774Shselasky	int subunit;
324184610Salfred
325239299Shselasky	if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED))
326226219Shselasky		return;		/* not initialized */
327226219Shselasky
328230209Shselasky	if (ssc->sc_sysctl_ttyname != NULL) {
329230209Shselasky		sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0);
330230209Shselasky		ssc->sc_sysctl_ttyname = NULL;
331230204Shselasky	}
332230204Shselasky
333230204Shselasky	if (ssc->sc_sysctl_ttyports != NULL) {
334230204Shselasky		sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0);
335230204Shselasky		ssc->sc_sysctl_ttyports = NULL;
336230204Shselasky	}
337230204Shselasky
338194228Sthompsa	usb_proc_drain(&ssc->sc_tq);
339184610Salfred
340214831Sn_hibma	for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
341214761Sn_hibma		if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) {
342184610Salfred
343239179Shselasky			ucom_detach_tty(ssc, &sc[subunit]);
344184610Salfred
345214761Sn_hibma			/* avoid duplicate detach */
346214761Sn_hibma			sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED;
347184610Salfred		}
348184610Salfred	}
349194228Sthompsa	usb_proc_free(&ssc->sc_tq);
350239179Shselasky
351239299Shselasky	ucom_unref(ssc);
352239299Shselasky
353239299Shselasky	if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS)
354239179Shselasky		ucom_drain(ssc);
355239299Shselasky
356239299Shselasky	/* make sure we don't detach twice */
357239299Shselasky	ssc->sc_flag &= ~UCOM_FLAG_ATTACHED;
358184610Salfred}
359184610Salfred
360239179Shselaskyvoid
361239179Shselaskyucom_drain(struct ucom_super_softc *ssc)
362239179Shselasky{
363239179Shselasky	mtx_lock(&ucom_mtx);
364239299Shselasky	while (ssc->sc_refs > 0) {
365239179Shselasky		printf("ucom: Waiting for a TTY device to close.\n");
366239179Shselasky		usb_pause_mtx(&ucom_mtx, hz);
367239179Shselasky	}
368239179Shselasky	mtx_unlock(&ucom_mtx);
369239179Shselasky}
370239179Shselasky
371239179Shselaskyvoid
372239179Shselaskyucom_drain_all(void *arg)
373239179Shselasky{
374239179Shselasky	mtx_lock(&ucom_mtx);
375239179Shselasky	while (ucom_close_refs > 0) {
376239179Shselasky		printf("ucom: Waiting for all detached TTY "
377239179Shselasky		    "devices to have open fds closed.\n");
378239179Shselasky		usb_pause_mtx(&ucom_mtx, hz);
379239179Shselasky	}
380239179Shselasky	mtx_unlock(&ucom_mtx);
381239179Shselasky}
382239179Shselasky
383184610Salfredstatic int
384214761Sn_hibmaucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
385184610Salfred{
386184610Salfred	struct tty *tp;
387214831Sn_hibma	char buf[32];			/* temporary TTY device name buffer */
388184610Salfred
389194228Sthompsa	tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx);
390214761Sn_hibma	if (tp == NULL)
391214761Sn_hibma		return (ENOMEM);
392184610Salfred
393184610Salfred	/* Check if the client has a custom TTY name */
394214761Sn_hibma	buf[0] = '\0';
395194228Sthompsa	if (sc->sc_callback->ucom_tty_name) {
396194228Sthompsa		sc->sc_callback->ucom_tty_name(sc, buf,
397214761Sn_hibma		    sizeof(buf), ssc->sc_unit, sc->sc_subunit);
398184610Salfred	}
399184610Salfred	if (buf[0] == 0) {
400184610Salfred		/* Use default TTY name */
401214761Sn_hibma		if (ssc->sc_subunits > 1) {
402188413Sthompsa			/* multiple modems in one */
403214843Sn_hibma			snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u",
404214761Sn_hibma			    ssc->sc_unit, sc->sc_subunit);
405188413Sthompsa		} else {
406188413Sthompsa			/* single modem */
407214843Sn_hibma			snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u",
408214843Sn_hibma			    ssc->sc_unit);
409184610Salfred		}
410184610Salfred	}
411184610Salfred	tty_makedev(tp, NULL, "%s", buf);
412184610Salfred
413184610Salfred	sc->sc_tty = tp;
414184610Salfred
415184610Salfred	DPRINTF("ttycreate: %s\n", buf);
416184610Salfred
417197570Sthompsa	/* Check if this device should be a console */
418197570Sthompsa	if ((ucom_cons_softc == NULL) &&
419214761Sn_hibma	    (ssc->sc_unit == ucom_cons_unit) &&
420214761Sn_hibma	    (sc->sc_subunit == ucom_cons_subunit)) {
421197570Sthompsa
422242695Shselasky		DPRINTF("unit %d subunit %d is console",
423242695Shselasky		    ssc->sc_unit, sc->sc_subunit);
424214761Sn_hibma
425197570Sthompsa		ucom_cons_softc = sc;
426197570Sthompsa
427242695Shselasky		tty_init_console(tp, ucom_cons_baud);
428197570Sthompsa
429239179Shselasky		UCOM_MTX_LOCK(ucom_cons_softc);
430197570Sthompsa		ucom_cons_rx_low = 0;
431197570Sthompsa		ucom_cons_rx_high = 0;
432197570Sthompsa		ucom_cons_tx_low = 0;
433197570Sthompsa		ucom_cons_tx_high = 0;
434197570Sthompsa		sc->sc_flag |= UCOM_FLAG_CONSOLE;
435197570Sthompsa		ucom_open(ucom_cons_softc->sc_tty);
436242695Shselasky		ucom_param(ucom_cons_softc->sc_tty, &tp->t_termios_init_in);
437239179Shselasky		UCOM_MTX_UNLOCK(ucom_cons_softc);
438197570Sthompsa	}
439214761Sn_hibma
440214761Sn_hibma	return (0);
441184610Salfred}
442184610Salfred
443184610Salfredstatic void
444239179Shselaskyucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
445184610Salfred{
446184610Salfred	struct tty *tp = sc->sc_tty;
447184610Salfred
448184610Salfred	DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty);
449184610Salfred
450197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
451239179Shselasky		UCOM_MTX_LOCK(ucom_cons_softc);
452197570Sthompsa		ucom_close(ucom_cons_softc->sc_tty);
453214761Sn_hibma		sc->sc_flag &= ~UCOM_FLAG_CONSOLE;
454239179Shselasky		UCOM_MTX_UNLOCK(ucom_cons_softc);
455197570Sthompsa		ucom_cons_softc = NULL;
456197570Sthompsa	}
457197570Sthompsa
458184610Salfred	/* the config thread has been stopped when we get here */
459184610Salfred
460239179Shselasky	UCOM_MTX_LOCK(sc);
461184610Salfred	sc->sc_flag |= UCOM_FLAG_GONE;
462197570Sthompsa	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY);
463239179Shselasky	UCOM_MTX_UNLOCK(sc);
464239179Shselasky
465184610Salfred	if (tp) {
466239179Shselasky		mtx_lock(&ucom_mtx);
467239179Shselasky		ucom_close_refs++;
468239179Shselasky		mtx_unlock(&ucom_mtx);
469239179Shselasky
470184610Salfred		tty_lock(tp);
471184610Salfred
472194228Sthompsa		ucom_close(tp);	/* close, if any */
473184610Salfred
474184610Salfred		tty_rel_gone(tp);
475184610Salfred
476239179Shselasky		UCOM_MTX_LOCK(sc);
477184610Salfred		/*
478184610Salfred		 * make sure that read and write transfers are stopped
479184610Salfred		 */
480239179Shselasky		if (sc->sc_callback->ucom_stop_read)
481194228Sthompsa			(sc->sc_callback->ucom_stop_read) (sc);
482239179Shselasky		if (sc->sc_callback->ucom_stop_write)
483194228Sthompsa			(sc->sc_callback->ucom_stop_write) (sc);
484239179Shselasky		UCOM_MTX_UNLOCK(sc);
485184610Salfred	}
486184610Salfred}
487184610Salfred
488214843Sn_hibmavoid
489214843Sn_hibmaucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev)
490214843Sn_hibma{
491230204Shselasky	char buf[64];
492230204Shselasky	uint8_t iface_index;
493230204Shselasky	struct usb_attach_arg *uaa;
494214843Sn_hibma
495230209Shselasky	snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX
496230209Shselasky	    "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits);
497214843Sn_hibma
498230204Shselasky	/* Store the PNP info in the first interface for the device */
499230204Shselasky	uaa = device_get_ivars(dev);
500230204Shselasky	iface_index = uaa->info.bIfaceIndex;
501214843Sn_hibma
502230204Shselasky	if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0)
503230204Shselasky		device_printf(dev, "Could not set PNP info\n");
504230204Shselasky
505230204Shselasky	/*
506230209Shselasky	 * The following information is also replicated in the PNP-info
507230204Shselasky	 * string which is registered above:
508230204Shselasky	 */
509230209Shselasky	if (ssc->sc_sysctl_ttyname == NULL) {
510230209Shselasky		ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL,
511230204Shselasky		    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
512230209Shselasky		    OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0,
513230209Shselasky		    "TTY device basename");
514230204Shselasky	}
515230204Shselasky	if (ssc->sc_sysctl_ttyports == NULL) {
516230204Shselasky		ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL,
517230204Shselasky		    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
518230204Shselasky		    OID_AUTO, "ttyports", CTLFLAG_RD,
519230204Shselasky		    NULL, ssc->sc_subunits, "Number of ports");
520230204Shselasky	}
521214843Sn_hibma}
522214843Sn_hibma
523184610Salfredstatic void
524194228Sthompsaucom_queue_command(struct ucom_softc *sc,
525193045Sthompsa    usb_proc_callback_t *fn, struct termios *pt,
526192984Sthompsa    struct usb_proc_msg *t0, struct usb_proc_msg *t1)
527184610Salfred{
528192984Sthompsa	struct ucom_super_softc *ssc = sc->sc_super;
529192984Sthompsa	struct ucom_param_task *task;
530187176Sthompsa
531239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
532188413Sthompsa
533194228Sthompsa	if (usb_proc_is_gone(&ssc->sc_tq)) {
534187176Sthompsa		DPRINTF("proc is gone\n");
535188413Sthompsa		return;         /* nothing to do */
536187176Sthompsa	}
537188413Sthompsa	/*
538188413Sthompsa	 * NOTE: The task cannot get executed before we drop the
539188413Sthompsa	 * "sc_mtx" mutex. It is safe to update fields in the message
540188413Sthompsa	 * structure after that the message got queued.
541188413Sthompsa	 */
542192984Sthompsa	task = (struct ucom_param_task *)
543194228Sthompsa	  usb_proc_msignal(&ssc->sc_tq, t0, t1);
544187176Sthompsa
545188413Sthompsa	/* Setup callback and softc pointers */
546188413Sthompsa	task->hdr.pm_callback = fn;
547188413Sthompsa	task->sc = sc;
548184610Salfred
549188413Sthompsa	/*
550188413Sthompsa	 * Make a copy of the termios. This field is only present if
551188413Sthompsa	 * the "pt" field is not NULL.
552188413Sthompsa	 */
553188413Sthompsa	if (pt != NULL)
554188413Sthompsa		task->termios_copy = *pt;
555184610Salfred
556188413Sthompsa	/*
557188413Sthompsa	 * Closing the device should be synchronous.
558188413Sthompsa	 */
559194228Sthompsa	if (fn == ucom_cfg_close)
560194228Sthompsa		usb_proc_mwait(&ssc->sc_tq, t0, t1);
561188413Sthompsa
562190742Sthompsa	/*
563190742Sthompsa	 * In case of multiple configure requests,
564190742Sthompsa	 * keep track of the last one!
565190742Sthompsa	 */
566194228Sthompsa	if (fn == ucom_cfg_start_transfers)
567190742Sthompsa		sc->sc_last_start_xfer = &task->hdr;
568184610Salfred}
569184610Salfred
570184610Salfredstatic void
571194228Sthompsaucom_shutdown(struct ucom_softc *sc)
572184610Salfred{
573184610Salfred	struct tty *tp = sc->sc_tty;
574184610Salfred
575239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
576184610Salfred
577184610Salfred	DPRINTF("\n");
578184610Salfred
579184610Salfred	/*
580184610Salfred	 * Hang up if necessary:
581184610Salfred	 */
582184610Salfred	if (tp->t_termios.c_cflag & HUPCL) {
583194228Sthompsa		ucom_modem(tp, 0, SER_DTR);
584184610Salfred	}
585184610Salfred}
586184610Salfred
587184610Salfred/*
588184610Salfred * Return values:
589184610Salfred *    0: normal
590188413Sthompsa * else: taskqueue is draining or gone
591184610Salfred */
592184610Salfreduint8_t
593194228Sthompsaucom_cfg_is_gone(struct ucom_softc *sc)
594184610Salfred{
595192984Sthompsa	struct ucom_super_softc *ssc = sc->sc_super;
596184610Salfred
597194228Sthompsa	return (usb_proc_is_gone(&ssc->sc_tq));
598184610Salfred}
599184610Salfred
600184610Salfredstatic void
601194228Sthompsaucom_cfg_start_transfers(struct usb_proc_msg *_task)
602184610Salfred{
603192984Sthompsa	struct ucom_cfg_task *task =
604192984Sthompsa	    (struct ucom_cfg_task *)_task;
605192984Sthompsa	struct ucom_softc *sc = task->sc;
606187176Sthompsa
607184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
608184610Salfred		return;
609184610Salfred	}
610184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
611184610Salfred		/* TTY device closed */
612184610Salfred		return;
613184610Salfred	}
614184610Salfred
615190742Sthompsa	if (_task == sc->sc_last_start_xfer)
616190742Sthompsa		sc->sc_flag |= UCOM_FLAG_GP_DATA;
617190742Sthompsa
618194228Sthompsa	if (sc->sc_callback->ucom_start_read) {
619194228Sthompsa		(sc->sc_callback->ucom_start_read) (sc);
620184610Salfred	}
621194228Sthompsa	if (sc->sc_callback->ucom_start_write) {
622194228Sthompsa		(sc->sc_callback->ucom_start_write) (sc);
623184610Salfred	}
624184610Salfred}
625184610Salfred
626184610Salfredstatic void
627194228Sthompsaucom_start_transfers(struct ucom_softc *sc)
628184610Salfred{
629184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
630184610Salfred		return;
631184610Salfred	}
632184610Salfred	/*
633188413Sthompsa	 * Make sure that data transfers are started in both
634188413Sthompsa	 * directions:
635184610Salfred	 */
636194228Sthompsa	if (sc->sc_callback->ucom_start_read) {
637194228Sthompsa		(sc->sc_callback->ucom_start_read) (sc);
638184610Salfred	}
639194228Sthompsa	if (sc->sc_callback->ucom_start_write) {
640194228Sthompsa		(sc->sc_callback->ucom_start_write) (sc);
641184610Salfred	}
642184610Salfred}
643184610Salfred
644184610Salfredstatic void
645194228Sthompsaucom_cfg_open(struct usb_proc_msg *_task)
646184610Salfred{
647192984Sthompsa	struct ucom_cfg_task *task =
648192984Sthompsa	    (struct ucom_cfg_task *)_task;
649192984Sthompsa	struct ucom_softc *sc = task->sc;
650187176Sthompsa
651184610Salfred	DPRINTF("\n");
652184610Salfred
653184610Salfred	if (sc->sc_flag & UCOM_FLAG_LL_READY) {
654184610Salfred
655184610Salfred		/* already opened */
656184610Salfred
657184610Salfred	} else {
658184610Salfred
659184610Salfred		sc->sc_flag |= UCOM_FLAG_LL_READY;
660184610Salfred
661194228Sthompsa		if (sc->sc_callback->ucom_cfg_open) {
662194228Sthompsa			(sc->sc_callback->ucom_cfg_open) (sc);
663184610Salfred
664184610Salfred			/* wait a little */
665194228Sthompsa			usb_pause_mtx(sc->sc_mtx, hz / 10);
666184610Salfred		}
667184610Salfred	}
668184610Salfred}
669184610Salfred
670184610Salfredstatic int
671194228Sthompsaucom_open(struct tty *tp)
672184610Salfred{
673192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
674184610Salfred	int error;
675184610Salfred
676239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
677184610Salfred
678184610Salfred	if (sc->sc_flag & UCOM_FLAG_GONE) {
679184610Salfred		return (ENXIO);
680184610Salfred	}
681184610Salfred	if (sc->sc_flag & UCOM_FLAG_HL_READY) {
682184610Salfred		/* already opened */
683184610Salfred		return (0);
684184610Salfred	}
685184610Salfred	DPRINTF("tp = %p\n", tp);
686184610Salfred
687194228Sthompsa	if (sc->sc_callback->ucom_pre_open) {
688184610Salfred		/*
689184610Salfred		 * give the lower layer a chance to disallow TTY open, for
690184610Salfred		 * example if the device is not present:
691184610Salfred		 */
692194228Sthompsa		error = (sc->sc_callback->ucom_pre_open) (sc);
693184610Salfred		if (error) {
694184610Salfred			return (error);
695184610Salfred		}
696184610Salfred	}
697184610Salfred	sc->sc_flag |= UCOM_FLAG_HL_READY;
698184610Salfred
699184610Salfred	/* Disable transfers */
700184610Salfred	sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
701184610Salfred
702184610Salfred	sc->sc_lsr = 0;
703184610Salfred	sc->sc_msr = 0;
704184610Salfred	sc->sc_mcr = 0;
705184610Salfred
706188413Sthompsa	/* reset programmed line state */
707188413Sthompsa	sc->sc_pls_curr = 0;
708188413Sthompsa	sc->sc_pls_set = 0;
709188413Sthompsa	sc->sc_pls_clr = 0;
710184610Salfred
711242619Shselasky	/* reset jitter buffer */
712242619Shselasky	sc->sc_jitterbuf_in = 0;
713242619Shselasky	sc->sc_jitterbuf_out = 0;
714242619Shselasky
715194228Sthompsa	ucom_queue_command(sc, ucom_cfg_open, NULL,
716188413Sthompsa	    &sc->sc_open_task[0].hdr,
717188413Sthompsa	    &sc->sc_open_task[1].hdr);
718184610Salfred
719188413Sthompsa	/* Queue transfer enable command last */
720194228Sthompsa	ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
721188413Sthompsa	    &sc->sc_start_task[0].hdr,
722188413Sthompsa	    &sc->sc_start_task[1].hdr);
723188413Sthompsa
724194228Sthompsa	ucom_modem(tp, SER_DTR | SER_RTS, 0);
725184610Salfred
726197570Sthompsa	ucom_ring(sc, 0);
727197570Sthompsa
728194228Sthompsa	ucom_break(sc, 0);
729184610Salfred
730194228Sthompsa	ucom_status_change(sc);
731184610Salfred
732184610Salfred	return (0);
733184610Salfred}
734184610Salfred
735184610Salfredstatic void
736194228Sthompsaucom_cfg_close(struct usb_proc_msg *_task)
737184610Salfred{
738192984Sthompsa	struct ucom_cfg_task *task =
739192984Sthompsa	    (struct ucom_cfg_task *)_task;
740192984Sthompsa	struct ucom_softc *sc = task->sc;
741187176Sthompsa
742184610Salfred	DPRINTF("\n");
743184610Salfred
744184610Salfred	if (sc->sc_flag & UCOM_FLAG_LL_READY) {
745192820Sthompsa		sc->sc_flag &= ~UCOM_FLAG_LL_READY;
746194228Sthompsa		if (sc->sc_callback->ucom_cfg_close)
747194228Sthompsa			(sc->sc_callback->ucom_cfg_close) (sc);
748184610Salfred	} else {
749184610Salfred		/* already closed */
750184610Salfred	}
751184610Salfred}
752184610Salfred
753184610Salfredstatic void
754194228Sthompsaucom_close(struct tty *tp)
755184610Salfred{
756192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
757184610Salfred
758239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
759188413Sthompsa
760184610Salfred	DPRINTF("tp=%p\n", tp);
761184610Salfred
762184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
763184610Salfred		DPRINTF("tp=%p already closed\n", tp);
764184610Salfred		return;
765184610Salfred	}
766194228Sthompsa	ucom_shutdown(sc);
767184610Salfred
768194228Sthompsa	ucom_queue_command(sc, ucom_cfg_close, NULL,
769188413Sthompsa	    &sc->sc_close_task[0].hdr,
770188413Sthompsa	    &sc->sc_close_task[1].hdr);
771184610Salfred
772192820Sthompsa	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW);
773184610Salfred
774194228Sthompsa	if (sc->sc_callback->ucom_stop_read) {
775194228Sthompsa		(sc->sc_callback->ucom_stop_read) (sc);
776184610Salfred	}
777184610Salfred}
778184610Salfred
779242619Shselaskystatic void
780242619Shselaskyucom_inwakeup(struct tty *tp)
781242619Shselasky{
782242619Shselasky	struct ucom_softc *sc = tty_softc(tp);
783242619Shselasky	uint16_t pos;
784242619Shselasky
785242619Shselasky	if (sc == NULL)
786242619Shselasky		return;
787242619Shselasky
788242703Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
789242619Shselasky
790242703Shselasky	DPRINTF("tp=%p\n", tp);
791242702Shselasky
792242619Shselasky	if (ttydisc_can_bypass(tp) != 0 ||
793244489Shselasky	    (sc->sc_flag & UCOM_FLAG_HL_READY) == 0 ||
794244489Shselasky	    (sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) {
795242619Shselasky		return;
796242619Shselasky	}
797242619Shselasky
798244489Shselasky	/* prevent recursion */
799244489Shselasky	sc->sc_flag |= UCOM_FLAG_INWAKEUP;
800244489Shselasky
801242619Shselasky	pos = sc->sc_jitterbuf_out;
802242619Shselasky
803242619Shselasky	while (sc->sc_jitterbuf_in != pos) {
804242619Shselasky		int c;
805242619Shselasky
806242619Shselasky		c = (char)sc->sc_jitterbuf[pos];
807242619Shselasky
808242619Shselasky		if (ttydisc_rint(tp, c, 0) == -1)
809242619Shselasky			break;
810242619Shselasky		pos++;
811242619Shselasky		if (pos >= UCOM_JITTERBUF_SIZE)
812242619Shselasky			pos -= UCOM_JITTERBUF_SIZE;
813242619Shselasky	}
814242619Shselasky
815242619Shselasky	sc->sc_jitterbuf_out = pos;
816242619Shselasky
817242619Shselasky	/* clear RTS in async fashion */
818242619Shselasky	if ((sc->sc_jitterbuf_in == pos) &&
819242619Shselasky	    (sc->sc_flag & UCOM_FLAG_RTS_IFLOW))
820242619Shselasky		ucom_rts(sc, 0);
821244489Shselasky
822244489Shselasky	sc->sc_flag &= ~UCOM_FLAG_INWAKEUP;
823242619Shselasky}
824242619Shselasky
825184610Salfredstatic int
826194228Sthompsaucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
827184610Salfred{
828192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
829184610Salfred	int error;
830184610Salfred
831239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
832184610Salfred
833184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
834184610Salfred		return (EIO);
835184610Salfred	}
836184610Salfred	DPRINTF("cmd = 0x%08lx\n", cmd);
837184610Salfred
838184610Salfred	switch (cmd) {
839197570Sthompsa#if 0
840197570Sthompsa	case TIOCSRING:
841197570Sthompsa		ucom_ring(sc, 1);
842197570Sthompsa		error = 0;
843197570Sthompsa		break;
844197570Sthompsa	case TIOCCRING:
845197570Sthompsa		ucom_ring(sc, 0);
846197570Sthompsa		error = 0;
847197570Sthompsa		break;
848197570Sthompsa#endif
849184610Salfred	case TIOCSBRK:
850194228Sthompsa		ucom_break(sc, 1);
851184610Salfred		error = 0;
852184610Salfred		break;
853184610Salfred	case TIOCCBRK:
854194228Sthompsa		ucom_break(sc, 0);
855184610Salfred		error = 0;
856184610Salfred		break;
857184610Salfred	default:
858194228Sthompsa		if (sc->sc_callback->ucom_ioctl) {
859194228Sthompsa			error = (sc->sc_callback->ucom_ioctl)
860184610Salfred			    (sc, cmd, data, 0, td);
861184610Salfred		} else {
862184610Salfred			error = ENOIOCTL;
863184610Salfred		}
864184610Salfred		break;
865184610Salfred	}
866184610Salfred	return (error);
867184610Salfred}
868184610Salfred
869184610Salfredstatic int
870194228Sthompsaucom_modem(struct tty *tp, int sigon, int sigoff)
871184610Salfred{
872192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
873184610Salfred	uint8_t onoff;
874184610Salfred
875239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
876184610Salfred
877184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
878184610Salfred		return (0);
879184610Salfred	}
880184610Salfred	if ((sigon == 0) && (sigoff == 0)) {
881184610Salfred
882184610Salfred		if (sc->sc_mcr & SER_DTR) {
883184610Salfred			sigon |= SER_DTR;
884184610Salfred		}
885184610Salfred		if (sc->sc_mcr & SER_RTS) {
886184610Salfred			sigon |= SER_RTS;
887184610Salfred		}
888184610Salfred		if (sc->sc_msr & SER_CTS) {
889184610Salfred			sigon |= SER_CTS;
890184610Salfred		}
891184610Salfred		if (sc->sc_msr & SER_DCD) {
892184610Salfred			sigon |= SER_DCD;
893184610Salfred		}
894184610Salfred		if (sc->sc_msr & SER_DSR) {
895184610Salfred			sigon |= SER_DSR;
896184610Salfred		}
897184610Salfred		if (sc->sc_msr & SER_RI) {
898184610Salfred			sigon |= SER_RI;
899184610Salfred		}
900184610Salfred		return (sigon);
901184610Salfred	}
902184610Salfred	if (sigon & SER_DTR) {
903184610Salfred		sc->sc_mcr |= SER_DTR;
904184610Salfred	}
905184610Salfred	if (sigoff & SER_DTR) {
906184610Salfred		sc->sc_mcr &= ~SER_DTR;
907184610Salfred	}
908184610Salfred	if (sigon & SER_RTS) {
909184610Salfred		sc->sc_mcr |= SER_RTS;
910184610Salfred	}
911184610Salfred	if (sigoff & SER_RTS) {
912184610Salfred		sc->sc_mcr &= ~SER_RTS;
913184610Salfred	}
914184610Salfred	onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0;
915194228Sthompsa	ucom_dtr(sc, onoff);
916184610Salfred
917184610Salfred	onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0;
918194228Sthompsa	ucom_rts(sc, onoff);
919184610Salfred
920184610Salfred	return (0);
921184610Salfred}
922184610Salfred
923184610Salfredstatic void
924194228Sthompsaucom_cfg_line_state(struct usb_proc_msg *_task)
925184610Salfred{
926192984Sthompsa	struct ucom_cfg_task *task =
927192984Sthompsa	    (struct ucom_cfg_task *)_task;
928192984Sthompsa	struct ucom_softc *sc = task->sc;
929188413Sthompsa	uint8_t notch_bits;
930188413Sthompsa	uint8_t any_bits;
931188413Sthompsa	uint8_t prev_value;
932188413Sthompsa	uint8_t last_value;
933188413Sthompsa	uint8_t mask;
934187176Sthompsa
935184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
936184610Salfred		return;
937184610Salfred	}
938184610Salfred
939188413Sthompsa	mask = 0;
940188413Sthompsa	/* compute callback mask */
941194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_dtr)
942188413Sthompsa		mask |= UCOM_LS_DTR;
943194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_rts)
944188413Sthompsa		mask |= UCOM_LS_RTS;
945194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_break)
946188413Sthompsa		mask |= UCOM_LS_BREAK;
947197570Sthompsa	if (sc->sc_callback->ucom_cfg_set_ring)
948197570Sthompsa		mask |= UCOM_LS_RING;
949184610Salfred
950188413Sthompsa	/* compute the bits we are to program */
951188413Sthompsa	notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask;
952188413Sthompsa	any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask;
953188413Sthompsa	prev_value = sc->sc_pls_curr ^ notch_bits;
954188413Sthompsa	last_value = sc->sc_pls_curr;
955187176Sthompsa
956188413Sthompsa	/* reset programmed line state */
957188413Sthompsa	sc->sc_pls_curr = 0;
958188413Sthompsa	sc->sc_pls_set = 0;
959188413Sthompsa	sc->sc_pls_clr = 0;
960188413Sthompsa
961250576Seadler	/* ensure that we don't lose any levels */
962188413Sthompsa	if (notch_bits & UCOM_LS_DTR)
963194228Sthompsa		sc->sc_callback->ucom_cfg_set_dtr(sc,
964188413Sthompsa		    (prev_value & UCOM_LS_DTR) ? 1 : 0);
965188413Sthompsa	if (notch_bits & UCOM_LS_RTS)
966194228Sthompsa		sc->sc_callback->ucom_cfg_set_rts(sc,
967188413Sthompsa		    (prev_value & UCOM_LS_RTS) ? 1 : 0);
968188413Sthompsa	if (notch_bits & UCOM_LS_BREAK)
969194228Sthompsa		sc->sc_callback->ucom_cfg_set_break(sc,
970188413Sthompsa		    (prev_value & UCOM_LS_BREAK) ? 1 : 0);
971197570Sthompsa	if (notch_bits & UCOM_LS_RING)
972197570Sthompsa		sc->sc_callback->ucom_cfg_set_ring(sc,
973197570Sthompsa		    (prev_value & UCOM_LS_RING) ? 1 : 0);
974188413Sthompsa
975188413Sthompsa	/* set last value */
976188413Sthompsa	if (any_bits & UCOM_LS_DTR)
977194228Sthompsa		sc->sc_callback->ucom_cfg_set_dtr(sc,
978188413Sthompsa		    (last_value & UCOM_LS_DTR) ? 1 : 0);
979188413Sthompsa	if (any_bits & UCOM_LS_RTS)
980194228Sthompsa		sc->sc_callback->ucom_cfg_set_rts(sc,
981188413Sthompsa		    (last_value & UCOM_LS_RTS) ? 1 : 0);
982188413Sthompsa	if (any_bits & UCOM_LS_BREAK)
983194228Sthompsa		sc->sc_callback->ucom_cfg_set_break(sc,
984188413Sthompsa		    (last_value & UCOM_LS_BREAK) ? 1 : 0);
985197570Sthompsa	if (any_bits & UCOM_LS_RING)
986197570Sthompsa		sc->sc_callback->ucom_cfg_set_ring(sc,
987197570Sthompsa		    (last_value & UCOM_LS_RING) ? 1 : 0);
988187176Sthompsa}
989187176Sthompsa
990187176Sthompsastatic void
991194228Sthompsaucom_line_state(struct ucom_softc *sc,
992188413Sthompsa    uint8_t set_bits, uint8_t clear_bits)
993184610Salfred{
994239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
995184610Salfred
996184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
997184610Salfred		return;
998184610Salfred	}
999184610Salfred
1000188413Sthompsa	DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits);
1001184610Salfred
1002188413Sthompsa	/* update current programmed line state */
1003188413Sthompsa	sc->sc_pls_curr |= set_bits;
1004188413Sthompsa	sc->sc_pls_curr &= ~clear_bits;
1005188413Sthompsa	sc->sc_pls_set |= set_bits;
1006188413Sthompsa	sc->sc_pls_clr |= clear_bits;
1007187176Sthompsa
1008188413Sthompsa	/* defer driver programming */
1009194228Sthompsa	ucom_queue_command(sc, ucom_cfg_line_state, NULL,
1010188413Sthompsa	    &sc->sc_line_state_task[0].hdr,
1011188413Sthompsa	    &sc->sc_line_state_task[1].hdr);
1012184610Salfred}
1013184610Salfred
1014184610Salfredstatic void
1015197570Sthompsaucom_ring(struct ucom_softc *sc, uint8_t onoff)
1016197570Sthompsa{
1017197570Sthompsa	DPRINTF("onoff = %d\n", onoff);
1018197570Sthompsa
1019197570Sthompsa	if (onoff)
1020197570Sthompsa		ucom_line_state(sc, UCOM_LS_RING, 0);
1021197570Sthompsa	else
1022197570Sthompsa		ucom_line_state(sc, 0, UCOM_LS_RING);
1023197570Sthompsa}
1024197570Sthompsa
1025197570Sthompsastatic void
1026194228Sthompsaucom_break(struct ucom_softc *sc, uint8_t onoff)
1027187176Sthompsa{
1028188413Sthompsa	DPRINTF("onoff = %d\n", onoff);
1029187176Sthompsa
1030188413Sthompsa	if (onoff)
1031194228Sthompsa		ucom_line_state(sc, UCOM_LS_BREAK, 0);
1032188413Sthompsa	else
1033194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_BREAK);
1034187176Sthompsa}
1035187176Sthompsa
1036187176Sthompsastatic void
1037194228Sthompsaucom_dtr(struct ucom_softc *sc, uint8_t onoff)
1038184610Salfred{
1039184610Salfred	DPRINTF("onoff = %d\n", onoff);
1040184610Salfred
1041188413Sthompsa	if (onoff)
1042194228Sthompsa		ucom_line_state(sc, UCOM_LS_DTR, 0);
1043188413Sthompsa	else
1044194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_DTR);
1045184610Salfred}
1046184610Salfred
1047184610Salfredstatic void
1048194228Sthompsaucom_rts(struct ucom_softc *sc, uint8_t onoff)
1049184610Salfred{
1050184610Salfred	DPRINTF("onoff = %d\n", onoff);
1051184610Salfred
1052188413Sthompsa	if (onoff)
1053194228Sthompsa		ucom_line_state(sc, UCOM_LS_RTS, 0);
1054188413Sthompsa	else
1055194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_RTS);
1056184610Salfred}
1057184610Salfred
1058184610Salfredstatic void
1059194228Sthompsaucom_cfg_status_change(struct usb_proc_msg *_task)
1060184610Salfred{
1061192984Sthompsa	struct ucom_cfg_task *task =
1062192984Sthompsa	    (struct ucom_cfg_task *)_task;
1063192984Sthompsa	struct ucom_softc *sc = task->sc;
1064184610Salfred	struct tty *tp;
1065184610Salfred	uint8_t new_msr;
1066184610Salfred	uint8_t new_lsr;
1067184610Salfred	uint8_t onoff;
1068213872Shselasky	uint8_t lsr_delta;
1069184610Salfred
1070184610Salfred	tp = sc->sc_tty;
1071184610Salfred
1072239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1073184610Salfred
1074184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
1075184610Salfred		return;
1076184610Salfred	}
1077194228Sthompsa	if (sc->sc_callback->ucom_cfg_get_status == NULL) {
1078184610Salfred		return;
1079184610Salfred	}
1080184610Salfred	/* get status */
1081184610Salfred
1082184610Salfred	new_msr = 0;
1083184610Salfred	new_lsr = 0;
1084184610Salfred
1085194228Sthompsa	(sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr);
1086184610Salfred
1087184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1088184610Salfred		/* TTY device closed */
1089184610Salfred		return;
1090184610Salfred	}
1091184610Salfred	onoff = ((sc->sc_msr ^ new_msr) & SER_DCD);
1092213872Shselasky	lsr_delta = (sc->sc_lsr ^ new_lsr);
1093184610Salfred
1094184610Salfred	sc->sc_msr = new_msr;
1095184610Salfred	sc->sc_lsr = new_lsr;
1096184610Salfred
1097184610Salfred	if (onoff) {
1098184610Salfred
1099184610Salfred		onoff = (sc->sc_msr & SER_DCD) ? 1 : 0;
1100184610Salfred
1101184610Salfred		DPRINTF("DCD changed to %d\n", onoff);
1102184610Salfred
1103184610Salfred		ttydisc_modem(tp, onoff);
1104184610Salfred	}
1105213872Shselasky
1106213872Shselasky	if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) {
1107213872Shselasky
1108213872Shselasky		DPRINTF("BREAK detected\n");
1109213872Shselasky
1110213872Shselasky		ttydisc_rint(tp, 0, TRE_BREAK);
1111213872Shselasky		ttydisc_rint_done(tp);
1112213872Shselasky	}
1113213872Shselasky
1114213872Shselasky	if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) {
1115213872Shselasky
1116213872Shselasky		DPRINTF("Frame error detected\n");
1117213872Shselasky
1118213872Shselasky		ttydisc_rint(tp, 0, TRE_FRAMING);
1119213872Shselasky		ttydisc_rint_done(tp);
1120213872Shselasky	}
1121213872Shselasky
1122213872Shselasky	if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) {
1123213872Shselasky
1124213872Shselasky		DPRINTF("Parity error detected\n");
1125213872Shselasky
1126213872Shselasky		ttydisc_rint(tp, 0, TRE_PARITY);
1127213872Shselasky		ttydisc_rint_done(tp);
1128213872Shselasky	}
1129184610Salfred}
1130184610Salfred
1131184610Salfredvoid
1132194228Sthompsaucom_status_change(struct ucom_softc *sc)
1133184610Salfred{
1134239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1135184610Salfred
1136197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE)
1137197570Sthompsa		return;		/* not supported */
1138197570Sthompsa
1139184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1140184610Salfred		return;
1141184610Salfred	}
1142184610Salfred	DPRINTF("\n");
1143184610Salfred
1144194228Sthompsa	ucom_queue_command(sc, ucom_cfg_status_change, NULL,
1145188413Sthompsa	    &sc->sc_status_task[0].hdr,
1146188413Sthompsa	    &sc->sc_status_task[1].hdr);
1147184610Salfred}
1148184610Salfred
1149184610Salfredstatic void
1150194228Sthompsaucom_cfg_param(struct usb_proc_msg *_task)
1151184610Salfred{
1152192984Sthompsa	struct ucom_param_task *task =
1153192984Sthompsa	    (struct ucom_param_task *)_task;
1154192984Sthompsa	struct ucom_softc *sc = task->sc;
1155184610Salfred
1156184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
1157184610Salfred		return;
1158184610Salfred	}
1159194228Sthompsa	if (sc->sc_callback->ucom_cfg_param == NULL) {
1160184610Salfred		return;
1161184610Salfred	}
1162184610Salfred
1163194228Sthompsa	(sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy);
1164184610Salfred
1165184610Salfred	/* wait a little */
1166194228Sthompsa	usb_pause_mtx(sc->sc_mtx, hz / 10);
1167184610Salfred}
1168184610Salfred
1169184610Salfredstatic int
1170194228Sthompsaucom_param(struct tty *tp, struct termios *t)
1171184610Salfred{
1172192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
1173184610Salfred	uint8_t opened;
1174184610Salfred	int error;
1175184610Salfred
1176239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1177184610Salfred
1178184610Salfred	opened = 0;
1179184610Salfred	error = 0;
1180184610Salfred
1181184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1182184610Salfred
1183184610Salfred		/* XXX the TTY layer should call "open()" first! */
1184242695Shselasky		/*
1185242695Shselasky		 * Not quite: Its ordering is partly backwards, but
1186242695Shselasky		 * some parameters must be set early in ttydev_open(),
1187242695Shselasky		 * possibly before calling ttydevsw_open().
1188242695Shselasky		 */
1189194228Sthompsa		error = ucom_open(tp);
1190242695Shselasky		if (error)
1191184610Salfred			goto done;
1192242695Shselasky
1193184610Salfred		opened = 1;
1194184610Salfred	}
1195184610Salfred	DPRINTF("sc = %p\n", sc);
1196184610Salfred
1197184610Salfred	/* Check requested parameters. */
1198184610Salfred	if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) {
1199242695Shselasky		/* XXX c_ospeed == 0 is perfectly valid. */
1200184610Salfred		DPRINTF("mismatch ispeed and ospeed\n");
1201184610Salfred		error = EINVAL;
1202184610Salfred		goto done;
1203184610Salfred	}
1204184610Salfred	t->c_ispeed = t->c_ospeed;
1205184610Salfred
1206194228Sthompsa	if (sc->sc_callback->ucom_pre_param) {
1207184610Salfred		/* Let the lower layer verify the parameters */
1208194228Sthompsa		error = (sc->sc_callback->ucom_pre_param) (sc, t);
1209184610Salfred		if (error) {
1210184610Salfred			DPRINTF("callback error = %d\n", error);
1211184610Salfred			goto done;
1212184610Salfred		}
1213184610Salfred	}
1214184610Salfred
1215184610Salfred	/* Disable transfers */
1216184610Salfred	sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
1217184610Salfred
1218184610Salfred	/* Queue baud rate programming command first */
1219194228Sthompsa	ucom_queue_command(sc, ucom_cfg_param, t,
1220188413Sthompsa	    &sc->sc_param_task[0].hdr,
1221188413Sthompsa	    &sc->sc_param_task[1].hdr);
1222184610Salfred
1223184610Salfred	/* Queue transfer enable command last */
1224194228Sthompsa	ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
1225188413Sthompsa	    &sc->sc_start_task[0].hdr,
1226188413Sthompsa	    &sc->sc_start_task[1].hdr);
1227184610Salfred
1228184610Salfred	if (t->c_cflag & CRTS_IFLOW) {
1229184610Salfred		sc->sc_flag |= UCOM_FLAG_RTS_IFLOW;
1230184610Salfred	} else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) {
1231184610Salfred		sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW;
1232194228Sthompsa		ucom_modem(tp, SER_RTS, 0);
1233184610Salfred	}
1234184610Salfreddone:
1235184610Salfred	if (error) {
1236184610Salfred		if (opened) {
1237194228Sthompsa			ucom_close(tp);
1238184610Salfred		}
1239184610Salfred	}
1240184610Salfred	return (error);
1241184610Salfred}
1242184610Salfred
1243184610Salfredstatic void
1244194228Sthompsaucom_outwakeup(struct tty *tp)
1245184610Salfred{
1246192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
1247184610Salfred
1248239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1249184610Salfred
1250184610Salfred	DPRINTF("sc = %p\n", sc);
1251184610Salfred
1252184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1253184610Salfred		/* The higher layer is not ready */
1254184610Salfred		return;
1255184610Salfred	}
1256194228Sthompsa	ucom_start_transfers(sc);
1257184610Salfred}
1258184610Salfred
1259184610Salfred/*------------------------------------------------------------------------*
1260194228Sthompsa *	ucom_get_data
1261184610Salfred *
1262184610Salfred * Return values:
1263184610Salfred * 0: No data is available.
1264184610Salfred * Else: Data is available.
1265184610Salfred *------------------------------------------------------------------------*/
1266184610Salfreduint8_t
1267194228Sthompsaucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc,
1268184610Salfred    uint32_t offset, uint32_t len, uint32_t *actlen)
1269184610Salfred{
1270192984Sthompsa	struct usb_page_search res;
1271184610Salfred	struct tty *tp = sc->sc_tty;
1272184610Salfred	uint32_t cnt;
1273184610Salfred	uint32_t offset_orig;
1274184610Salfred
1275239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1276184610Salfred
1277197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
1278197570Sthompsa		unsigned int temp;
1279197570Sthompsa
1280197570Sthompsa		/* get total TX length */
1281197570Sthompsa
1282197570Sthompsa		temp = ucom_cons_tx_high - ucom_cons_tx_low;
1283197570Sthompsa		temp %= UCOM_CONS_BUFSIZE;
1284197570Sthompsa
1285197570Sthompsa		/* limit TX length */
1286197570Sthompsa
1287197570Sthompsa		if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low))
1288197570Sthompsa			temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low);
1289197570Sthompsa
1290197570Sthompsa		if (temp > len)
1291197570Sthompsa			temp = len;
1292197570Sthompsa
1293197570Sthompsa		/* copy in data */
1294197570Sthompsa
1295197570Sthompsa		usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp);
1296197570Sthompsa
1297197570Sthompsa		/* update counters */
1298197570Sthompsa
1299197570Sthompsa		ucom_cons_tx_low += temp;
1300197570Sthompsa		ucom_cons_tx_low %= UCOM_CONS_BUFSIZE;
1301197570Sthompsa
1302197570Sthompsa		/* store actual length */
1303197570Sthompsa
1304197570Sthompsa		*actlen = temp;
1305197570Sthompsa
1306197570Sthompsa		return (temp ? 1 : 0);
1307197570Sthompsa	}
1308197570Sthompsa
1309192820Sthompsa	if (tty_gone(tp) ||
1310192820Sthompsa	    !(sc->sc_flag & UCOM_FLAG_GP_DATA)) {
1311184610Salfred		actlen[0] = 0;
1312184610Salfred		return (0);		/* multiport device polling */
1313184610Salfred	}
1314184610Salfred	offset_orig = offset;
1315184610Salfred
1316184610Salfred	while (len != 0) {
1317184610Salfred
1318194228Sthompsa		usbd_get_page(pc, offset, &res);
1319184610Salfred
1320184610Salfred		if (res.length > len) {
1321184610Salfred			res.length = len;
1322184610Salfred		}
1323184610Salfred		/* copy data directly into USB buffer */
1324184610Salfred		cnt = ttydisc_getc(tp, res.buffer, res.length);
1325184610Salfred
1326184610Salfred		offset += cnt;
1327184610Salfred		len -= cnt;
1328184610Salfred
1329184610Salfred		if (cnt < res.length) {
1330184610Salfred			/* end of buffer */
1331184610Salfred			break;
1332184610Salfred		}
1333184610Salfred	}
1334184610Salfred
1335184610Salfred	actlen[0] = offset - offset_orig;
1336184610Salfred
1337184610Salfred	DPRINTF("cnt=%d\n", actlen[0]);
1338184610Salfred
1339184610Salfred	if (actlen[0] == 0) {
1340184610Salfred		return (0);
1341184610Salfred	}
1342184610Salfred	return (1);
1343184610Salfred}
1344184610Salfred
1345184610Salfredvoid
1346194228Sthompsaucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc,
1347184610Salfred    uint32_t offset, uint32_t len)
1348184610Salfred{
1349192984Sthompsa	struct usb_page_search res;
1350184610Salfred	struct tty *tp = sc->sc_tty;
1351184610Salfred	char *buf;
1352184610Salfred	uint32_t cnt;
1353184610Salfred
1354239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1355184610Salfred
1356197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
1357197570Sthompsa		unsigned int temp;
1358197570Sthompsa
1359197570Sthompsa		/* get maximum RX length */
1360197570Sthompsa
1361197570Sthompsa		temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low;
1362197570Sthompsa		temp %= UCOM_CONS_BUFSIZE;
1363197570Sthompsa
1364197570Sthompsa		/* limit RX length */
1365197570Sthompsa
1366197570Sthompsa		if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high))
1367197570Sthompsa			temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high);
1368197570Sthompsa
1369197570Sthompsa		if (temp > len)
1370197570Sthompsa			temp = len;
1371197570Sthompsa
1372197570Sthompsa		/* copy out data */
1373197570Sthompsa
1374197570Sthompsa		usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp);
1375197570Sthompsa
1376197570Sthompsa		/* update counters */
1377197570Sthompsa
1378197570Sthompsa		ucom_cons_rx_high += temp;
1379197570Sthompsa		ucom_cons_rx_high %= UCOM_CONS_BUFSIZE;
1380197570Sthompsa
1381197570Sthompsa		return;
1382197570Sthompsa	}
1383197570Sthompsa
1384192820Sthompsa	if (tty_gone(tp))
1385184610Salfred		return;			/* multiport device polling */
1386192820Sthompsa
1387184610Salfred	if (len == 0)
1388184610Salfred		return;			/* no data */
1389184610Salfred
1390184610Salfred	/* set a flag to prevent recursation ? */
1391184610Salfred
1392184610Salfred	while (len > 0) {
1393184610Salfred
1394194228Sthompsa		usbd_get_page(pc, offset, &res);
1395184610Salfred
1396184610Salfred		if (res.length > len) {
1397184610Salfred			res.length = len;
1398184610Salfred		}
1399184610Salfred		len -= res.length;
1400184610Salfred		offset += res.length;
1401184610Salfred
1402184610Salfred		/* pass characters to tty layer */
1403184610Salfred
1404184610Salfred		buf = res.buffer;
1405184610Salfred		cnt = res.length;
1406184610Salfred
1407184610Salfred		/* first check if we can pass the buffer directly */
1408184610Salfred
1409184610Salfred		if (ttydisc_can_bypass(tp)) {
1410242619Shselasky
1411242619Shselasky			/* clear any jitter buffer */
1412242619Shselasky			sc->sc_jitterbuf_in = 0;
1413242619Shselasky			sc->sc_jitterbuf_out = 0;
1414242619Shselasky
1415184610Salfred			if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) {
1416184610Salfred				DPRINTF("tp=%p, data lost\n", tp);
1417184610Salfred			}
1418184610Salfred			continue;
1419184610Salfred		}
1420184610Salfred		/* need to loop */
1421184610Salfred
1422184610Salfred		for (cnt = 0; cnt != res.length; cnt++) {
1423242619Shselasky			if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out ||
1424242619Shselasky			    ttydisc_rint(tp, buf[cnt], 0) == -1) {
1425242619Shselasky				uint16_t end;
1426242619Shselasky				uint16_t pos;
1427184610Salfred
1428242619Shselasky				pos = sc->sc_jitterbuf_in;
1429242619Shselasky				end = sc->sc_jitterbuf_out +
1430242619Shselasky				    UCOM_JITTERBUF_SIZE - 1;
1431242619Shselasky				if (end >= UCOM_JITTERBUF_SIZE)
1432242619Shselasky					end -= UCOM_JITTERBUF_SIZE;
1433242619Shselasky
1434242619Shselasky				for (; cnt != res.length; cnt++) {
1435242619Shselasky					if (pos == end)
1436242619Shselasky						break;
1437242619Shselasky					sc->sc_jitterbuf[pos] = buf[cnt];
1438242619Shselasky					pos++;
1439242619Shselasky					if (pos >= UCOM_JITTERBUF_SIZE)
1440242619Shselasky						pos -= UCOM_JITTERBUF_SIZE;
1441242619Shselasky				}
1442242619Shselasky
1443242619Shselasky				sc->sc_jitterbuf_in = pos;
1444242619Shselasky
1445242619Shselasky				/* set RTS in async fashion */
1446242619Shselasky				if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)
1447242619Shselasky					ucom_rts(sc, 1);
1448242619Shselasky
1449184610Salfred				DPRINTF("tp=%p, lost %d "
1450184610Salfred				    "chars\n", tp, res.length - cnt);
1451184610Salfred				break;
1452184610Salfred			}
1453184610Salfred		}
1454184610Salfred	}
1455184610Salfred	ttydisc_rint_done(tp);
1456184610Salfred}
1457184610Salfred
1458184610Salfredstatic void
1459194228Sthompsaucom_free(void *xsc)
1460184610Salfred{
1461192984Sthompsa	struct ucom_softc *sc = xsc;
1462184610Salfred
1463239179Shselasky	if (sc->sc_callback->ucom_free != NULL)
1464239179Shselasky		sc->sc_callback->ucom_free(sc);
1465239179Shselasky	else
1466239179Shselasky		ucom_unref(sc->sc_super);
1467239179Shselasky
1468239179Shselasky	mtx_lock(&ucom_mtx);
1469239179Shselasky	ucom_close_refs--;
1470239179Shselasky	mtx_unlock(&ucom_mtx);
1471184610Salfred}
1472197570Sthompsa
1473197570Sthompsastatic cn_probe_t ucom_cnprobe;
1474197570Sthompsastatic cn_init_t ucom_cninit;
1475197570Sthompsastatic cn_term_t ucom_cnterm;
1476197570Sthompsastatic cn_getc_t ucom_cngetc;
1477197570Sthompsastatic cn_putc_t ucom_cnputc;
1478228631Savgstatic cn_grab_t ucom_cngrab;
1479228631Savgstatic cn_ungrab_t ucom_cnungrab;
1480197570Sthompsa
1481197570SthompsaCONSOLE_DRIVER(ucom);
1482197570Sthompsa
1483197570Sthompsastatic void
1484197570Sthompsaucom_cnprobe(struct consdev  *cp)
1485197570Sthompsa{
1486198774Sthompsa	if (ucom_cons_unit != -1)
1487198774Sthompsa		cp->cn_pri = CN_NORMAL;
1488198774Sthompsa	else
1489198774Sthompsa		cp->cn_pri = CN_DEAD;
1490198774Sthompsa
1491198774Sthompsa	strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name));
1492197570Sthompsa}
1493197570Sthompsa
1494197570Sthompsastatic void
1495197570Sthompsaucom_cninit(struct consdev  *cp)
1496197570Sthompsa{
1497197570Sthompsa}
1498197570Sthompsa
1499197570Sthompsastatic void
1500197570Sthompsaucom_cnterm(struct consdev  *cp)
1501197570Sthompsa{
1502197570Sthompsa}
1503197570Sthompsa
1504228631Savgstatic void
1505228631Savgucom_cngrab(struct consdev *cp)
1506228631Savg{
1507228631Savg}
1508228631Savg
1509228631Savgstatic void
1510228631Savgucom_cnungrab(struct consdev *cp)
1511228631Savg{
1512228631Savg}
1513228631Savg
1514197570Sthompsastatic int
1515197570Sthompsaucom_cngetc(struct consdev *cd)
1516197570Sthompsa{
1517197570Sthompsa	struct ucom_softc *sc = ucom_cons_softc;
1518197570Sthompsa	int c;
1519197570Sthompsa
1520197570Sthompsa	if (sc == NULL)
1521197570Sthompsa		return (-1);
1522197570Sthompsa
1523239179Shselasky	UCOM_MTX_LOCK(sc);
1524197570Sthompsa
1525197570Sthompsa	if (ucom_cons_rx_low != ucom_cons_rx_high) {
1526197570Sthompsa		c = ucom_cons_rx_buf[ucom_cons_rx_low];
1527197570Sthompsa		ucom_cons_rx_low ++;
1528197570Sthompsa		ucom_cons_rx_low %= UCOM_CONS_BUFSIZE;
1529197570Sthompsa	} else {
1530197570Sthompsa		c = -1;
1531197570Sthompsa	}
1532197570Sthompsa
1533197570Sthompsa	/* start USB transfers */
1534197570Sthompsa	ucom_outwakeup(sc->sc_tty);
1535197570Sthompsa
1536239179Shselasky	UCOM_MTX_UNLOCK(sc);
1537197570Sthompsa
1538197570Sthompsa	/* poll if necessary */
1539197570Sthompsa	if (kdb_active && sc->sc_callback->ucom_poll)
1540197570Sthompsa		(sc->sc_callback->ucom_poll) (sc);
1541197570Sthompsa
1542197570Sthompsa	return (c);
1543197570Sthompsa}
1544197570Sthompsa
1545197570Sthompsastatic void
1546197570Sthompsaucom_cnputc(struct consdev *cd, int c)
1547197570Sthompsa{
1548197570Sthompsa	struct ucom_softc *sc = ucom_cons_softc;
1549197570Sthompsa	unsigned int temp;
1550197570Sthompsa
1551197570Sthompsa	if (sc == NULL)
1552197570Sthompsa		return;
1553197570Sthompsa
1554197570Sthompsa repeat:
1555197570Sthompsa
1556239179Shselasky	UCOM_MTX_LOCK(sc);
1557197570Sthompsa
1558197570Sthompsa	/* compute maximum TX length */
1559197570Sthompsa
1560197570Sthompsa	temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low;
1561197570Sthompsa	temp %= UCOM_CONS_BUFSIZE;
1562197570Sthompsa
1563197570Sthompsa	if (temp) {
1564197570Sthompsa		ucom_cons_tx_buf[ucom_cons_tx_high] = c;
1565197570Sthompsa		ucom_cons_tx_high ++;
1566197570Sthompsa		ucom_cons_tx_high %= UCOM_CONS_BUFSIZE;
1567197570Sthompsa	}
1568197570Sthompsa
1569197570Sthompsa	/* start USB transfers */
1570197570Sthompsa	ucom_outwakeup(sc->sc_tty);
1571197570Sthompsa
1572239179Shselasky	UCOM_MTX_UNLOCK(sc);
1573197570Sthompsa
1574197570Sthompsa	/* poll if necessary */
1575197570Sthompsa	if (kdb_active && sc->sc_callback->ucom_poll) {
1576197570Sthompsa		(sc->sc_callback->ucom_poll) (sc);
1577197570Sthompsa		/* simple flow control */
1578197570Sthompsa		if (temp == 0)
1579197570Sthompsa			goto repeat;
1580197570Sthompsa	}
1581197570Sthompsa}
1582197570Sthompsa
1583239179Shselasky/*------------------------------------------------------------------------*
1584239179Shselasky *	ucom_ref
1585239179Shselasky *
1586239179Shselasky * This function will increment the super UCOM reference count.
1587239179Shselasky *------------------------------------------------------------------------*/
1588239179Shselaskyvoid
1589239179Shselaskyucom_ref(struct ucom_super_softc *ssc)
1590239179Shselasky{
1591239179Shselasky	mtx_lock(&ucom_mtx);
1592239179Shselasky	ssc->sc_refs++;
1593239179Shselasky	mtx_unlock(&ucom_mtx);
1594239179Shselasky}
1595239179Shselasky
1596239179Shselasky/*------------------------------------------------------------------------*
1597239299Shselasky *	ucom_free_unit
1598239299Shselasky *
1599239299Shselasky * This function will free the super UCOM's allocated unit
1600239299Shselasky * number. This function can be called on a zero-initialized
1601239299Shselasky * structure. This function can be called multiple times.
1602239299Shselasky *------------------------------------------------------------------------*/
1603239299Shselaskystatic void
1604239299Shselaskyucom_free_unit(struct ucom_super_softc *ssc)
1605239299Shselasky{
1606239299Shselasky	if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT))
1607239299Shselasky		return;
1608239299Shselasky
1609239299Shselasky	ucom_unit_free(ssc->sc_unit);
1610239299Shselasky
1611239299Shselasky	ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT;
1612239299Shselasky}
1613239299Shselasky
1614239299Shselasky/*------------------------------------------------------------------------*
1615239179Shselasky *	ucom_unref
1616239179Shselasky *
1617239179Shselasky * This function will decrement the super UCOM reference count.
1618239179Shselasky *
1619239179Shselasky * Return values:
1620239179Shselasky * 0: UCOM structures are still referenced.
1621239179Shselasky * Else: UCOM structures are no longer referenced.
1622239179Shselasky *------------------------------------------------------------------------*/
1623239179Shselaskyint
1624239179Shselaskyucom_unref(struct ucom_super_softc *ssc)
1625239179Shselasky{
1626239179Shselasky	int retval;
1627239179Shselasky
1628239179Shselasky	mtx_lock(&ucom_mtx);
1629239179Shselasky	retval = (ssc->sc_refs < 2);
1630239179Shselasky	ssc->sc_refs--;
1631239179Shselasky	mtx_unlock(&ucom_mtx);
1632239179Shselasky
1633239299Shselasky	if (retval)
1634239299Shselasky		ucom_free_unit(ssc);
1635239299Shselasky
1636239179Shselasky	return (retval);
1637239179Shselasky}
1638239179Shselasky
1639197570Sthompsa#if defined(GDB)
1640197570Sthompsa
1641197570Sthompsa#include <gdb/gdb.h>
1642197570Sthompsa
1643197570Sthompsastatic gdb_probe_f ucom_gdbprobe;
1644197570Sthompsastatic gdb_init_f ucom_gdbinit;
1645197570Sthompsastatic gdb_term_f ucom_gdbterm;
1646197570Sthompsastatic gdb_getc_f ucom_gdbgetc;
1647197570Sthompsastatic gdb_putc_f ucom_gdbputc;
1648197570Sthompsa
1649197570SthompsaGDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc);
1650197570Sthompsa
1651197570Sthompsastatic int
1652197570Sthompsaucom_gdbprobe(void)
1653197570Sthompsa{
1654197570Sthompsa	return ((ucom_cons_softc != NULL) ? 0 : -1);
1655197570Sthompsa}
1656197570Sthompsa
1657197570Sthompsastatic void
1658197570Sthompsaucom_gdbinit(void)
1659197570Sthompsa{
1660197570Sthompsa}
1661197570Sthompsa
1662197570Sthompsastatic void
1663197570Sthompsaucom_gdbterm(void)
1664197570Sthompsa{
1665197570Sthompsa}
1666197570Sthompsa
1667197570Sthompsastatic void
1668197570Sthompsaucom_gdbputc(int c)
1669197570Sthompsa{
1670197570Sthompsa        ucom_cnputc(NULL, c);
1671197570Sthompsa}
1672197570Sthompsa
1673197570Sthompsastatic int
1674197570Sthompsaucom_gdbgetc(void)
1675197570Sthompsa{
1676197570Sthompsa        return (ucom_cngetc(NULL));
1677197570Sthompsa}
1678197570Sthompsa
1679197570Sthompsa#endif
1680