usb_serial.c revision 250576
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: head/sys/dev/usb/serial/usb_serial.c 250576 2013-05-12 16:43:26Z eadler $");
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 * 3. All advertising materials mentioning features or use of this software
50184610Salfred *    must display the following acknowledgement:
51184610Salfred *        This product includes software developed by the NetBSD
52184610Salfred *        Foundation, Inc. and its contributors.
53184610Salfred * 4. Neither the name of The NetBSD Foundation nor the names of its
54184610Salfred *    contributors may be used to endorse or promote products derived
55184610Salfred *    from this software without specific prior written permission.
56184610Salfred *
57184610Salfred * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
58184610Salfred * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
59184610Salfred * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
60184610Salfred * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
61184610Salfred * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
62184610Salfred * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
63184610Salfred * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
64184610Salfred * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
65184610Salfred * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
66184610Salfred * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
67184610Salfred * POSSIBILITY OF SUCH DAMAGE.
68184610Salfred */
69184610Salfred
70194677Sthompsa#include <sys/stdint.h>
71194677Sthompsa#include <sys/stddef.h>
72194677Sthompsa#include <sys/param.h>
73194677Sthompsa#include <sys/queue.h>
74194677Sthompsa#include <sys/types.h>
75194677Sthompsa#include <sys/systm.h>
76194677Sthompsa#include <sys/kernel.h>
77194677Sthompsa#include <sys/bus.h>
78194677Sthompsa#include <sys/module.h>
79194677Sthompsa#include <sys/lock.h>
80194677Sthompsa#include <sys/mutex.h>
81194677Sthompsa#include <sys/condvar.h>
82194677Sthompsa#include <sys/sysctl.h>
83194677Sthompsa#include <sys/sx.h>
84194677Sthompsa#include <sys/unistd.h>
85194677Sthompsa#include <sys/callout.h>
86194677Sthompsa#include <sys/malloc.h>
87194677Sthompsa#include <sys/priv.h>
88197570Sthompsa#include <sys/cons.h>
89197570Sthompsa#include <sys/kdb.h>
90194677Sthompsa
91188942Sthompsa#include <dev/usb/usb.h>
92194677Sthompsa#include <dev/usb/usbdi.h>
93194677Sthompsa#include <dev/usb/usbdi_util.h>
94184610Salfred
95194228Sthompsa#define	USB_DEBUG_VAR ucom_debug
96188942Sthompsa#include <dev/usb/usb_debug.h>
97194677Sthompsa#include <dev/usb/usb_busdma.h>
98188942Sthompsa#include <dev/usb/usb_process.h>
99184610Salfred
100188942Sthompsa#include <dev/usb/serial/usb_serial.h>
101184610Salfred
102197570Sthompsa#include "opt_gdb.h"
103197570Sthompsa
104227309Sedstatic SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom");
105197570Sthompsa
106207077Sthompsa#ifdef USB_DEBUG
107194228Sthompsastatic int ucom_debug = 0;
108184610Salfred
109192502SthompsaSYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW,
110194228Sthompsa    &ucom_debug, 0, "ucom debug level");
111184610Salfred#endif
112184610Salfred
113197570Sthompsa#define	UCOM_CONS_BUFSIZE 1024
114197570Sthompsa
115197570Sthompsastatic uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE];
116197570Sthompsastatic uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE];
117197570Sthompsa
118197570Sthompsastatic unsigned int ucom_cons_rx_low = 0;
119197570Sthompsastatic unsigned int ucom_cons_rx_high = 0;
120197570Sthompsa
121197570Sthompsastatic unsigned int ucom_cons_tx_low = 0;
122197570Sthompsastatic unsigned int ucom_cons_tx_high = 0;
123197570Sthompsa
124197570Sthompsastatic int ucom_cons_unit = -1;
125214761Sn_hibmastatic int ucom_cons_subunit = 0;
126197570Sthompsastatic int ucom_cons_baud = 9600;
127197570Sthompsastatic struct ucom_softc *ucom_cons_softc = NULL;
128197570Sthompsa
129197570SthompsaTUNABLE_INT("hw.usb.ucom.cons_unit", &ucom_cons_unit);
130242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RW | CTLFLAG_TUN,
131197570Sthompsa    &ucom_cons_unit, 0, "console unit number");
132214761Sn_hibmaTUNABLE_INT("hw.usb.ucom.cons_subunit", &ucom_cons_subunit);
133242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RW | CTLFLAG_TUN,
134214761Sn_hibma    &ucom_cons_subunit, 0, "console subunit number");
135197570SthompsaTUNABLE_INT("hw.usb.ucom.cons_baud", &ucom_cons_baud);
136242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RW | CTLFLAG_TUN,
137197570Sthompsa    &ucom_cons_baud, 0, "console baud rate");
138197570Sthompsa
139194228Sthompsastatic usb_proc_callback_t ucom_cfg_start_transfers;
140194228Sthompsastatic usb_proc_callback_t ucom_cfg_open;
141194228Sthompsastatic usb_proc_callback_t ucom_cfg_close;
142194228Sthompsastatic usb_proc_callback_t ucom_cfg_line_state;
143194228Sthompsastatic usb_proc_callback_t ucom_cfg_status_change;
144194228Sthompsastatic usb_proc_callback_t ucom_cfg_param;
145184610Salfred
146214761Sn_hibmastatic int	ucom_unit_alloc(void);
147214761Sn_hibmastatic void	ucom_unit_free(int);
148214761Sn_hibmastatic int	ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *);
149239179Shselaskystatic void	ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *);
150194228Sthompsastatic void	ucom_queue_command(struct ucom_softc *,
151193045Sthompsa		    usb_proc_callback_t *, struct termios *pt,
152192984Sthompsa		    struct usb_proc_msg *t0, struct usb_proc_msg *t1);
153194228Sthompsastatic void	ucom_shutdown(struct ucom_softc *);
154197570Sthompsastatic void	ucom_ring(struct ucom_softc *, uint8_t);
155194228Sthompsastatic void	ucom_break(struct ucom_softc *, uint8_t);
156194228Sthompsastatic void	ucom_dtr(struct ucom_softc *, uint8_t);
157194228Sthompsastatic void	ucom_rts(struct ucom_softc *, uint8_t);
158184610Salfred
159194228Sthompsastatic tsw_open_t ucom_open;
160194228Sthompsastatic tsw_close_t ucom_close;
161194228Sthompsastatic tsw_ioctl_t ucom_ioctl;
162194228Sthompsastatic tsw_modem_t ucom_modem;
163194228Sthompsastatic tsw_param_t ucom_param;
164194228Sthompsastatic tsw_outwakeup_t ucom_outwakeup;
165242619Shselaskystatic tsw_inwakeup_t ucom_inwakeup;
166194228Sthompsastatic tsw_free_t ucom_free;
167184610Salfred
168194228Sthompsastatic struct ttydevsw ucom_class = {
169184610Salfred	.tsw_flags = TF_INITLOCK | TF_CALLOUT,
170194228Sthompsa	.tsw_open = ucom_open,
171194228Sthompsa	.tsw_close = ucom_close,
172194228Sthompsa	.tsw_outwakeup = ucom_outwakeup,
173242619Shselasky	.tsw_inwakeup = ucom_inwakeup,
174194228Sthompsa	.tsw_ioctl = ucom_ioctl,
175194228Sthompsa	.tsw_param = ucom_param,
176194228Sthompsa	.tsw_modem = ucom_modem,
177194228Sthompsa	.tsw_free = ucom_free,
178184610Salfred};
179184610Salfred
180188942SthompsaMODULE_DEPEND(ucom, usb, 1, 1, 1);
181188942SthompsaMODULE_VERSION(ucom, 1);
182184610Salfred
183239179Shselasky#define	UCOM_UNIT_MAX 		128	/* maximum number of units */
184239179Shselasky#define	UCOM_TTY_PREFIX		"U"
185184610Salfred
186239179Shselaskystatic struct unrhdr *ucom_unrhdr;
187239179Shselaskystatic struct mtx ucom_mtx;
188239179Shselaskystatic int ucom_close_refs;
189184610Salfred
190239179Shselaskystatic void
191239179Shselaskyucom_init(void *arg)
192239179Shselasky{
193239179Shselasky	DPRINTF("\n");
194239179Shselasky	ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL);
195239179Shselasky	mtx_init(&ucom_mtx, "UCOM MTX", NULL, MTX_DEF);
196239179Shselasky}
197239179ShselaskySYSINIT(ucom_init, SI_SUB_KLD - 1, SI_ORDER_ANY, ucom_init, NULL);
198214843Sn_hibma
199239179Shselaskystatic void
200239179Shselaskyucom_uninit(void *arg)
201239179Shselasky{
202239179Shselasky	struct unrhdr *hdr;
203239179Shselasky	hdr = ucom_unrhdr;
204239179Shselasky	ucom_unrhdr = NULL;
205239179Shselasky
206239179Shselasky	DPRINTF("\n");
207239179Shselasky
208239179Shselasky	if (hdr != NULL)
209239179Shselasky		delete_unrhdr(hdr);
210239179Shselasky
211239179Shselasky	mtx_destroy(&ucom_mtx);
212239179Shselasky}
213239179ShselaskySYSUNINIT(ucom_uninit, SI_SUB_KLD - 2, SI_ORDER_ANY, ucom_uninit, NULL);
214239179Shselasky
215214761Sn_hibma/*
216214761Sn_hibma * Mark a unit number (the X in cuaUX) as in use.
217214761Sn_hibma *
218214761Sn_hibma * Note that devices using a different naming scheme (see ucom_tty_name()
219214761Sn_hibma * callback) still use this unit allocation.
220214761Sn_hibma */
221214761Sn_hibmastatic int
222214761Sn_hibmaucom_unit_alloc(void)
223184610Salfred{
224214761Sn_hibma	int unit;
225184610Salfred
226239179Shselasky	/* sanity checks */
227239179Shselasky	if (ucom_unrhdr == NULL) {
228239179Shselasky		DPRINTF("ucom_unrhdr is NULL\n");
229239179Shselasky		return (-1);
230214919Sn_hibma	}
231239179Shselasky	unit = alloc_unr(ucom_unrhdr);
232239179Shselasky	DPRINTF("unit %d is allocated\n", unit);
233239179Shselasky	return (unit);
234184610Salfred}
235184610Salfred
236214761Sn_hibma/*
237214761Sn_hibma * Mark the unit number as not in use.
238214761Sn_hibma */
239184610Salfredstatic void
240214761Sn_hibmaucom_unit_free(int unit)
241184610Salfred{
242239179Shselasky	/* sanity checks */
243239179Shselasky	if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) {
244239179Shselasky		DPRINTF("cannot free unit number\n");
245239179Shselasky		return;
246239179Shselasky	}
247239179Shselasky	DPRINTF("unit %d is freed\n", unit);
248239179Shselasky	free_unr(ucom_unrhdr, unit);
249184610Salfred}
250184610Salfred
251184610Salfred/*
252214761Sn_hibma * Setup a group of one or more serial ports.
253184610Salfred *
254188413Sthompsa * The mutex pointed to by "mtx" is applied before all
255188413Sthompsa * callbacks are called back. Also "mtx" must be applied
256188413Sthompsa * before calling into the ucom-layer!
257184610Salfred */
258184610Salfredint
259194228Sthompsaucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc,
260233774Shselasky    int subunits, void *parent,
261192984Sthompsa    const struct ucom_callback *callback, struct mtx *mtx)
262184610Salfred{
263233774Shselasky	int subunit;
264184610Salfred	int error = 0;
265184610Salfred
266184610Salfred	if ((sc == NULL) ||
267233774Shselasky	    (subunits <= 0) ||
268239179Shselasky	    (callback == NULL) ||
269239179Shselasky	    (mtx == NULL)) {
270184610Salfred		return (EINVAL);
271184610Salfred	}
272188413Sthompsa
273230209Shselasky	/* allocate a uniq unit number */
274214761Sn_hibma	ssc->sc_unit = ucom_unit_alloc();
275214761Sn_hibma	if (ssc->sc_unit == -1)
276184610Salfred		return (ENOMEM);
277188413Sthompsa
278230209Shselasky	/* generate TTY name string */
279230209Shselasky	snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname),
280230209Shselasky	    UCOM_TTY_PREFIX "%d", ssc->sc_unit);
281230209Shselasky
282230209Shselasky	/* create USB request handling process */
283194228Sthompsa	error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED);
284188413Sthompsa	if (error) {
285214761Sn_hibma		ucom_unit_free(ssc->sc_unit);
286188413Sthompsa		return (error);
287184610Salfred	}
288214761Sn_hibma	ssc->sc_subunits = subunits;
289239299Shselasky	ssc->sc_flag = UCOM_FLAG_ATTACHED |
290239299Shselasky	    UCOM_FLAG_FREE_UNIT;
291188413Sthompsa
292239299Shselasky	if (callback->ucom_free == NULL)
293239299Shselasky		ssc->sc_flag |= UCOM_FLAG_WAIT_REFS;
294239179Shselasky
295239299Shselasky	/* increment reference count */
296239299Shselasky	ucom_ref(ssc);
297239299Shselasky
298214831Sn_hibma	for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
299214761Sn_hibma		sc[subunit].sc_subunit = subunit;
300214761Sn_hibma		sc[subunit].sc_super = ssc;
301214761Sn_hibma		sc[subunit].sc_mtx = mtx;
302214761Sn_hibma		sc[subunit].sc_parent = parent;
303214761Sn_hibma		sc[subunit].sc_callback = callback;
304184610Salfred
305214761Sn_hibma		error = ucom_attach_tty(ssc, &sc[subunit]);
306184610Salfred		if (error) {
307214761Sn_hibma			ucom_detach(ssc, &sc[0]);
308188413Sthompsa			return (error);
309184610Salfred		}
310239179Shselasky		/* increment reference count */
311239179Shselasky		ucom_ref(ssc);
312239179Shselasky
313239179Shselasky		/* set subunit attached */
314214761Sn_hibma		sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED;
315184610Salfred	}
316214761Sn_hibma
317214831Sn_hibma	DPRINTF("tp = %p, unit = %d, subunits = %d\n",
318214831Sn_hibma		sc->sc_tty, ssc->sc_unit, ssc->sc_subunits);
319214761Sn_hibma
320188413Sthompsa	return (0);
321184610Salfred}
322184610Salfred
323188413Sthompsa/*
324239299Shselasky * The following function will do nothing if the structure pointed to
325239299Shselasky * by "ssc" and "sc" is zero or has already been detached.
326184610Salfred */
327184610Salfredvoid
328214761Sn_hibmaucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc)
329184610Salfred{
330233774Shselasky	int subunit;
331184610Salfred
332239299Shselasky	if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED))
333226219Shselasky		return;		/* not initialized */
334226219Shselasky
335230209Shselasky	if (ssc->sc_sysctl_ttyname != NULL) {
336230209Shselasky		sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0);
337230209Shselasky		ssc->sc_sysctl_ttyname = NULL;
338230204Shselasky	}
339230204Shselasky
340230204Shselasky	if (ssc->sc_sysctl_ttyports != NULL) {
341230204Shselasky		sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0);
342230204Shselasky		ssc->sc_sysctl_ttyports = NULL;
343230204Shselasky	}
344230204Shselasky
345194228Sthompsa	usb_proc_drain(&ssc->sc_tq);
346184610Salfred
347214831Sn_hibma	for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
348214761Sn_hibma		if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) {
349184610Salfred
350239179Shselasky			ucom_detach_tty(ssc, &sc[subunit]);
351184610Salfred
352214761Sn_hibma			/* avoid duplicate detach */
353214761Sn_hibma			sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED;
354184610Salfred		}
355184610Salfred	}
356194228Sthompsa	usb_proc_free(&ssc->sc_tq);
357239179Shselasky
358239299Shselasky	ucom_unref(ssc);
359239299Shselasky
360239299Shselasky	if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS)
361239179Shselasky		ucom_drain(ssc);
362239299Shselasky
363239299Shselasky	/* make sure we don't detach twice */
364239299Shselasky	ssc->sc_flag &= ~UCOM_FLAG_ATTACHED;
365184610Salfred}
366184610Salfred
367239179Shselaskyvoid
368239179Shselaskyucom_drain(struct ucom_super_softc *ssc)
369239179Shselasky{
370239179Shselasky	mtx_lock(&ucom_mtx);
371239299Shselasky	while (ssc->sc_refs > 0) {
372239179Shselasky		printf("ucom: Waiting for a TTY device to close.\n");
373239179Shselasky		usb_pause_mtx(&ucom_mtx, hz);
374239179Shselasky	}
375239179Shselasky	mtx_unlock(&ucom_mtx);
376239179Shselasky}
377239179Shselasky
378239179Shselaskyvoid
379239179Shselaskyucom_drain_all(void *arg)
380239179Shselasky{
381239179Shselasky	mtx_lock(&ucom_mtx);
382239179Shselasky	while (ucom_close_refs > 0) {
383239179Shselasky		printf("ucom: Waiting for all detached TTY "
384239179Shselasky		    "devices to have open fds closed.\n");
385239179Shselasky		usb_pause_mtx(&ucom_mtx, hz);
386239179Shselasky	}
387239179Shselasky	mtx_unlock(&ucom_mtx);
388239179Shselasky}
389239179Shselasky
390184610Salfredstatic int
391214761Sn_hibmaucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
392184610Salfred{
393184610Salfred	struct tty *tp;
394214831Sn_hibma	char buf[32];			/* temporary TTY device name buffer */
395184610Salfred
396194228Sthompsa	tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx);
397214761Sn_hibma	if (tp == NULL)
398214761Sn_hibma		return (ENOMEM);
399184610Salfred
400184610Salfred	/* Check if the client has a custom TTY name */
401214761Sn_hibma	buf[0] = '\0';
402194228Sthompsa	if (sc->sc_callback->ucom_tty_name) {
403194228Sthompsa		sc->sc_callback->ucom_tty_name(sc, buf,
404214761Sn_hibma		    sizeof(buf), ssc->sc_unit, sc->sc_subunit);
405184610Salfred	}
406184610Salfred	if (buf[0] == 0) {
407184610Salfred		/* Use default TTY name */
408214761Sn_hibma		if (ssc->sc_subunits > 1) {
409188413Sthompsa			/* multiple modems in one */
410214843Sn_hibma			snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u",
411214761Sn_hibma			    ssc->sc_unit, sc->sc_subunit);
412188413Sthompsa		} else {
413188413Sthompsa			/* single modem */
414214843Sn_hibma			snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u",
415214843Sn_hibma			    ssc->sc_unit);
416184610Salfred		}
417184610Salfred	}
418184610Salfred	tty_makedev(tp, NULL, "%s", buf);
419184610Salfred
420184610Salfred	sc->sc_tty = tp;
421184610Salfred
422184610Salfred	DPRINTF("ttycreate: %s\n", buf);
423184610Salfred
424197570Sthompsa	/* Check if this device should be a console */
425197570Sthompsa	if ((ucom_cons_softc == NULL) &&
426214761Sn_hibma	    (ssc->sc_unit == ucom_cons_unit) &&
427214761Sn_hibma	    (sc->sc_subunit == ucom_cons_subunit)) {
428197570Sthompsa
429242695Shselasky		DPRINTF("unit %d subunit %d is console",
430242695Shselasky		    ssc->sc_unit, sc->sc_subunit);
431214761Sn_hibma
432197570Sthompsa		ucom_cons_softc = sc;
433197570Sthompsa
434242695Shselasky		tty_init_console(tp, ucom_cons_baud);
435197570Sthompsa
436239179Shselasky		UCOM_MTX_LOCK(ucom_cons_softc);
437197570Sthompsa		ucom_cons_rx_low = 0;
438197570Sthompsa		ucom_cons_rx_high = 0;
439197570Sthompsa		ucom_cons_tx_low = 0;
440197570Sthompsa		ucom_cons_tx_high = 0;
441197570Sthompsa		sc->sc_flag |= UCOM_FLAG_CONSOLE;
442197570Sthompsa		ucom_open(ucom_cons_softc->sc_tty);
443242695Shselasky		ucom_param(ucom_cons_softc->sc_tty, &tp->t_termios_init_in);
444239179Shselasky		UCOM_MTX_UNLOCK(ucom_cons_softc);
445197570Sthompsa	}
446214761Sn_hibma
447214761Sn_hibma	return (0);
448184610Salfred}
449184610Salfred
450184610Salfredstatic void
451239179Shselaskyucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
452184610Salfred{
453184610Salfred	struct tty *tp = sc->sc_tty;
454184610Salfred
455184610Salfred	DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty);
456184610Salfred
457197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
458239179Shselasky		UCOM_MTX_LOCK(ucom_cons_softc);
459197570Sthompsa		ucom_close(ucom_cons_softc->sc_tty);
460214761Sn_hibma		sc->sc_flag &= ~UCOM_FLAG_CONSOLE;
461239179Shselasky		UCOM_MTX_UNLOCK(ucom_cons_softc);
462197570Sthompsa		ucom_cons_softc = NULL;
463197570Sthompsa	}
464197570Sthompsa
465184610Salfred	/* the config thread has been stopped when we get here */
466184610Salfred
467239179Shselasky	UCOM_MTX_LOCK(sc);
468184610Salfred	sc->sc_flag |= UCOM_FLAG_GONE;
469197570Sthompsa	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY);
470239179Shselasky	UCOM_MTX_UNLOCK(sc);
471239179Shselasky
472184610Salfred	if (tp) {
473239179Shselasky		mtx_lock(&ucom_mtx);
474239179Shselasky		ucom_close_refs++;
475239179Shselasky		mtx_unlock(&ucom_mtx);
476239179Shselasky
477184610Salfred		tty_lock(tp);
478184610Salfred
479194228Sthompsa		ucom_close(tp);	/* close, if any */
480184610Salfred
481184610Salfred		tty_rel_gone(tp);
482184610Salfred
483239179Shselasky		UCOM_MTX_LOCK(sc);
484184610Salfred		/*
485184610Salfred		 * make sure that read and write transfers are stopped
486184610Salfred		 */
487239179Shselasky		if (sc->sc_callback->ucom_stop_read)
488194228Sthompsa			(sc->sc_callback->ucom_stop_read) (sc);
489239179Shselasky		if (sc->sc_callback->ucom_stop_write)
490194228Sthompsa			(sc->sc_callback->ucom_stop_write) (sc);
491239179Shselasky		UCOM_MTX_UNLOCK(sc);
492184610Salfred	}
493184610Salfred}
494184610Salfred
495214843Sn_hibmavoid
496214843Sn_hibmaucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev)
497214843Sn_hibma{
498230204Shselasky	char buf[64];
499230204Shselasky	uint8_t iface_index;
500230204Shselasky	struct usb_attach_arg *uaa;
501214843Sn_hibma
502230209Shselasky	snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX
503230209Shselasky	    "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits);
504214843Sn_hibma
505230204Shselasky	/* Store the PNP info in the first interface for the device */
506230204Shselasky	uaa = device_get_ivars(dev);
507230204Shselasky	iface_index = uaa->info.bIfaceIndex;
508214843Sn_hibma
509230204Shselasky	if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0)
510230204Shselasky		device_printf(dev, "Could not set PNP info\n");
511230204Shselasky
512230204Shselasky	/*
513230209Shselasky	 * The following information is also replicated in the PNP-info
514230204Shselasky	 * string which is registered above:
515230204Shselasky	 */
516230209Shselasky	if (ssc->sc_sysctl_ttyname == NULL) {
517230209Shselasky		ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL,
518230204Shselasky		    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
519230209Shselasky		    OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0,
520230209Shselasky		    "TTY device basename");
521230204Shselasky	}
522230204Shselasky	if (ssc->sc_sysctl_ttyports == NULL) {
523230204Shselasky		ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL,
524230204Shselasky		    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
525230204Shselasky		    OID_AUTO, "ttyports", CTLFLAG_RD,
526230204Shselasky		    NULL, ssc->sc_subunits, "Number of ports");
527230204Shselasky	}
528214843Sn_hibma}
529214843Sn_hibma
530184610Salfredstatic void
531194228Sthompsaucom_queue_command(struct ucom_softc *sc,
532193045Sthompsa    usb_proc_callback_t *fn, struct termios *pt,
533192984Sthompsa    struct usb_proc_msg *t0, struct usb_proc_msg *t1)
534184610Salfred{
535192984Sthompsa	struct ucom_super_softc *ssc = sc->sc_super;
536192984Sthompsa	struct ucom_param_task *task;
537187176Sthompsa
538239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
539188413Sthompsa
540194228Sthompsa	if (usb_proc_is_gone(&ssc->sc_tq)) {
541187176Sthompsa		DPRINTF("proc is gone\n");
542188413Sthompsa		return;         /* nothing to do */
543187176Sthompsa	}
544188413Sthompsa	/*
545188413Sthompsa	 * NOTE: The task cannot get executed before we drop the
546188413Sthompsa	 * "sc_mtx" mutex. It is safe to update fields in the message
547188413Sthompsa	 * structure after that the message got queued.
548188413Sthompsa	 */
549192984Sthompsa	task = (struct ucom_param_task *)
550194228Sthompsa	  usb_proc_msignal(&ssc->sc_tq, t0, t1);
551187176Sthompsa
552188413Sthompsa	/* Setup callback and softc pointers */
553188413Sthompsa	task->hdr.pm_callback = fn;
554188413Sthompsa	task->sc = sc;
555184610Salfred
556188413Sthompsa	/*
557188413Sthompsa	 * Make a copy of the termios. This field is only present if
558188413Sthompsa	 * the "pt" field is not NULL.
559188413Sthompsa	 */
560188413Sthompsa	if (pt != NULL)
561188413Sthompsa		task->termios_copy = *pt;
562184610Salfred
563188413Sthompsa	/*
564188413Sthompsa	 * Closing the device should be synchronous.
565188413Sthompsa	 */
566194228Sthompsa	if (fn == ucom_cfg_close)
567194228Sthompsa		usb_proc_mwait(&ssc->sc_tq, t0, t1);
568188413Sthompsa
569190742Sthompsa	/*
570190742Sthompsa	 * In case of multiple configure requests,
571190742Sthompsa	 * keep track of the last one!
572190742Sthompsa	 */
573194228Sthompsa	if (fn == ucom_cfg_start_transfers)
574190742Sthompsa		sc->sc_last_start_xfer = &task->hdr;
575184610Salfred}
576184610Salfred
577184610Salfredstatic void
578194228Sthompsaucom_shutdown(struct ucom_softc *sc)
579184610Salfred{
580184610Salfred	struct tty *tp = sc->sc_tty;
581184610Salfred
582239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
583184610Salfred
584184610Salfred	DPRINTF("\n");
585184610Salfred
586184610Salfred	/*
587184610Salfred	 * Hang up if necessary:
588184610Salfred	 */
589184610Salfred	if (tp->t_termios.c_cflag & HUPCL) {
590194228Sthompsa		ucom_modem(tp, 0, SER_DTR);
591184610Salfred	}
592184610Salfred}
593184610Salfred
594184610Salfred/*
595184610Salfred * Return values:
596184610Salfred *    0: normal
597188413Sthompsa * else: taskqueue is draining or gone
598184610Salfred */
599184610Salfreduint8_t
600194228Sthompsaucom_cfg_is_gone(struct ucom_softc *sc)
601184610Salfred{
602192984Sthompsa	struct ucom_super_softc *ssc = sc->sc_super;
603184610Salfred
604194228Sthompsa	return (usb_proc_is_gone(&ssc->sc_tq));
605184610Salfred}
606184610Salfred
607184610Salfredstatic void
608194228Sthompsaucom_cfg_start_transfers(struct usb_proc_msg *_task)
609184610Salfred{
610192984Sthompsa	struct ucom_cfg_task *task =
611192984Sthompsa	    (struct ucom_cfg_task *)_task;
612192984Sthompsa	struct ucom_softc *sc = task->sc;
613187176Sthompsa
614184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
615184610Salfred		return;
616184610Salfred	}
617184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
618184610Salfred		/* TTY device closed */
619184610Salfred		return;
620184610Salfred	}
621184610Salfred
622190742Sthompsa	if (_task == sc->sc_last_start_xfer)
623190742Sthompsa		sc->sc_flag |= UCOM_FLAG_GP_DATA;
624190742Sthompsa
625194228Sthompsa	if (sc->sc_callback->ucom_start_read) {
626194228Sthompsa		(sc->sc_callback->ucom_start_read) (sc);
627184610Salfred	}
628194228Sthompsa	if (sc->sc_callback->ucom_start_write) {
629194228Sthompsa		(sc->sc_callback->ucom_start_write) (sc);
630184610Salfred	}
631184610Salfred}
632184610Salfred
633184610Salfredstatic void
634194228Sthompsaucom_start_transfers(struct ucom_softc *sc)
635184610Salfred{
636184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
637184610Salfred		return;
638184610Salfred	}
639184610Salfred	/*
640188413Sthompsa	 * Make sure that data transfers are started in both
641188413Sthompsa	 * directions:
642184610Salfred	 */
643194228Sthompsa	if (sc->sc_callback->ucom_start_read) {
644194228Sthompsa		(sc->sc_callback->ucom_start_read) (sc);
645184610Salfred	}
646194228Sthompsa	if (sc->sc_callback->ucom_start_write) {
647194228Sthompsa		(sc->sc_callback->ucom_start_write) (sc);
648184610Salfred	}
649184610Salfred}
650184610Salfred
651184610Salfredstatic void
652194228Sthompsaucom_cfg_open(struct usb_proc_msg *_task)
653184610Salfred{
654192984Sthompsa	struct ucom_cfg_task *task =
655192984Sthompsa	    (struct ucom_cfg_task *)_task;
656192984Sthompsa	struct ucom_softc *sc = task->sc;
657187176Sthompsa
658184610Salfred	DPRINTF("\n");
659184610Salfred
660184610Salfred	if (sc->sc_flag & UCOM_FLAG_LL_READY) {
661184610Salfred
662184610Salfred		/* already opened */
663184610Salfred
664184610Salfred	} else {
665184610Salfred
666184610Salfred		sc->sc_flag |= UCOM_FLAG_LL_READY;
667184610Salfred
668194228Sthompsa		if (sc->sc_callback->ucom_cfg_open) {
669194228Sthompsa			(sc->sc_callback->ucom_cfg_open) (sc);
670184610Salfred
671184610Salfred			/* wait a little */
672194228Sthompsa			usb_pause_mtx(sc->sc_mtx, hz / 10);
673184610Salfred		}
674184610Salfred	}
675184610Salfred}
676184610Salfred
677184610Salfredstatic int
678194228Sthompsaucom_open(struct tty *tp)
679184610Salfred{
680192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
681184610Salfred	int error;
682184610Salfred
683239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
684184610Salfred
685184610Salfred	if (sc->sc_flag & UCOM_FLAG_GONE) {
686184610Salfred		return (ENXIO);
687184610Salfred	}
688184610Salfred	if (sc->sc_flag & UCOM_FLAG_HL_READY) {
689184610Salfred		/* already opened */
690184610Salfred		return (0);
691184610Salfred	}
692184610Salfred	DPRINTF("tp = %p\n", tp);
693184610Salfred
694194228Sthompsa	if (sc->sc_callback->ucom_pre_open) {
695184610Salfred		/*
696184610Salfred		 * give the lower layer a chance to disallow TTY open, for
697184610Salfred		 * example if the device is not present:
698184610Salfred		 */
699194228Sthompsa		error = (sc->sc_callback->ucom_pre_open) (sc);
700184610Salfred		if (error) {
701184610Salfred			return (error);
702184610Salfred		}
703184610Salfred	}
704184610Salfred	sc->sc_flag |= UCOM_FLAG_HL_READY;
705184610Salfred
706184610Salfred	/* Disable transfers */
707184610Salfred	sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
708184610Salfred
709184610Salfred	sc->sc_lsr = 0;
710184610Salfred	sc->sc_msr = 0;
711184610Salfred	sc->sc_mcr = 0;
712184610Salfred
713188413Sthompsa	/* reset programmed line state */
714188413Sthompsa	sc->sc_pls_curr = 0;
715188413Sthompsa	sc->sc_pls_set = 0;
716188413Sthompsa	sc->sc_pls_clr = 0;
717184610Salfred
718242619Shselasky	/* reset jitter buffer */
719242619Shselasky	sc->sc_jitterbuf_in = 0;
720242619Shselasky	sc->sc_jitterbuf_out = 0;
721242619Shselasky
722194228Sthompsa	ucom_queue_command(sc, ucom_cfg_open, NULL,
723188413Sthompsa	    &sc->sc_open_task[0].hdr,
724188413Sthompsa	    &sc->sc_open_task[1].hdr);
725184610Salfred
726188413Sthompsa	/* Queue transfer enable command last */
727194228Sthompsa	ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
728188413Sthompsa	    &sc->sc_start_task[0].hdr,
729188413Sthompsa	    &sc->sc_start_task[1].hdr);
730188413Sthompsa
731194228Sthompsa	ucom_modem(tp, SER_DTR | SER_RTS, 0);
732184610Salfred
733197570Sthompsa	ucom_ring(sc, 0);
734197570Sthompsa
735194228Sthompsa	ucom_break(sc, 0);
736184610Salfred
737194228Sthompsa	ucom_status_change(sc);
738184610Salfred
739184610Salfred	return (0);
740184610Salfred}
741184610Salfred
742184610Salfredstatic void
743194228Sthompsaucom_cfg_close(struct usb_proc_msg *_task)
744184610Salfred{
745192984Sthompsa	struct ucom_cfg_task *task =
746192984Sthompsa	    (struct ucom_cfg_task *)_task;
747192984Sthompsa	struct ucom_softc *sc = task->sc;
748187176Sthompsa
749184610Salfred	DPRINTF("\n");
750184610Salfred
751184610Salfred	if (sc->sc_flag & UCOM_FLAG_LL_READY) {
752192820Sthompsa		sc->sc_flag &= ~UCOM_FLAG_LL_READY;
753194228Sthompsa		if (sc->sc_callback->ucom_cfg_close)
754194228Sthompsa			(sc->sc_callback->ucom_cfg_close) (sc);
755184610Salfred	} else {
756184610Salfred		/* already closed */
757184610Salfred	}
758184610Salfred}
759184610Salfred
760184610Salfredstatic void
761194228Sthompsaucom_close(struct tty *tp)
762184610Salfred{
763192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
764184610Salfred
765239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
766188413Sthompsa
767184610Salfred	DPRINTF("tp=%p\n", tp);
768184610Salfred
769184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
770184610Salfred		DPRINTF("tp=%p already closed\n", tp);
771184610Salfred		return;
772184610Salfred	}
773194228Sthompsa	ucom_shutdown(sc);
774184610Salfred
775194228Sthompsa	ucom_queue_command(sc, ucom_cfg_close, NULL,
776188413Sthompsa	    &sc->sc_close_task[0].hdr,
777188413Sthompsa	    &sc->sc_close_task[1].hdr);
778184610Salfred
779192820Sthompsa	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW);
780184610Salfred
781194228Sthompsa	if (sc->sc_callback->ucom_stop_read) {
782194228Sthompsa		(sc->sc_callback->ucom_stop_read) (sc);
783184610Salfred	}
784184610Salfred}
785184610Salfred
786242619Shselaskystatic void
787242619Shselaskyucom_inwakeup(struct tty *tp)
788242619Shselasky{
789242619Shselasky	struct ucom_softc *sc = tty_softc(tp);
790242619Shselasky	uint16_t pos;
791242619Shselasky
792242619Shselasky	if (sc == NULL)
793242619Shselasky		return;
794242619Shselasky
795242703Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
796242619Shselasky
797242703Shselasky	DPRINTF("tp=%p\n", tp);
798242702Shselasky
799242619Shselasky	if (ttydisc_can_bypass(tp) != 0 ||
800244489Shselasky	    (sc->sc_flag & UCOM_FLAG_HL_READY) == 0 ||
801244489Shselasky	    (sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) {
802242619Shselasky		return;
803242619Shselasky	}
804242619Shselasky
805244489Shselasky	/* prevent recursion */
806244489Shselasky	sc->sc_flag |= UCOM_FLAG_INWAKEUP;
807244489Shselasky
808242619Shselasky	pos = sc->sc_jitterbuf_out;
809242619Shselasky
810242619Shselasky	while (sc->sc_jitterbuf_in != pos) {
811242619Shselasky		int c;
812242619Shselasky
813242619Shselasky		c = (char)sc->sc_jitterbuf[pos];
814242619Shselasky
815242619Shselasky		if (ttydisc_rint(tp, c, 0) == -1)
816242619Shselasky			break;
817242619Shselasky		pos++;
818242619Shselasky		if (pos >= UCOM_JITTERBUF_SIZE)
819242619Shselasky			pos -= UCOM_JITTERBUF_SIZE;
820242619Shselasky	}
821242619Shselasky
822242619Shselasky	sc->sc_jitterbuf_out = pos;
823242619Shselasky
824242619Shselasky	/* clear RTS in async fashion */
825242619Shselasky	if ((sc->sc_jitterbuf_in == pos) &&
826242619Shselasky	    (sc->sc_flag & UCOM_FLAG_RTS_IFLOW))
827242619Shselasky		ucom_rts(sc, 0);
828244489Shselasky
829244489Shselasky	sc->sc_flag &= ~UCOM_FLAG_INWAKEUP;
830242619Shselasky}
831242619Shselasky
832184610Salfredstatic int
833194228Sthompsaucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
834184610Salfred{
835192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
836184610Salfred	int error;
837184610Salfred
838239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
839184610Salfred
840184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
841184610Salfred		return (EIO);
842184610Salfred	}
843184610Salfred	DPRINTF("cmd = 0x%08lx\n", cmd);
844184610Salfred
845184610Salfred	switch (cmd) {
846197570Sthompsa#if 0
847197570Sthompsa	case TIOCSRING:
848197570Sthompsa		ucom_ring(sc, 1);
849197570Sthompsa		error = 0;
850197570Sthompsa		break;
851197570Sthompsa	case TIOCCRING:
852197570Sthompsa		ucom_ring(sc, 0);
853197570Sthompsa		error = 0;
854197570Sthompsa		break;
855197570Sthompsa#endif
856184610Salfred	case TIOCSBRK:
857194228Sthompsa		ucom_break(sc, 1);
858184610Salfred		error = 0;
859184610Salfred		break;
860184610Salfred	case TIOCCBRK:
861194228Sthompsa		ucom_break(sc, 0);
862184610Salfred		error = 0;
863184610Salfred		break;
864184610Salfred	default:
865194228Sthompsa		if (sc->sc_callback->ucom_ioctl) {
866194228Sthompsa			error = (sc->sc_callback->ucom_ioctl)
867184610Salfred			    (sc, cmd, data, 0, td);
868184610Salfred		} else {
869184610Salfred			error = ENOIOCTL;
870184610Salfred		}
871184610Salfred		break;
872184610Salfred	}
873184610Salfred	return (error);
874184610Salfred}
875184610Salfred
876184610Salfredstatic int
877194228Sthompsaucom_modem(struct tty *tp, int sigon, int sigoff)
878184610Salfred{
879192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
880184610Salfred	uint8_t onoff;
881184610Salfred
882239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
883184610Salfred
884184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
885184610Salfred		return (0);
886184610Salfred	}
887184610Salfred	if ((sigon == 0) && (sigoff == 0)) {
888184610Salfred
889184610Salfred		if (sc->sc_mcr & SER_DTR) {
890184610Salfred			sigon |= SER_DTR;
891184610Salfred		}
892184610Salfred		if (sc->sc_mcr & SER_RTS) {
893184610Salfred			sigon |= SER_RTS;
894184610Salfred		}
895184610Salfred		if (sc->sc_msr & SER_CTS) {
896184610Salfred			sigon |= SER_CTS;
897184610Salfred		}
898184610Salfred		if (sc->sc_msr & SER_DCD) {
899184610Salfred			sigon |= SER_DCD;
900184610Salfred		}
901184610Salfred		if (sc->sc_msr & SER_DSR) {
902184610Salfred			sigon |= SER_DSR;
903184610Salfred		}
904184610Salfred		if (sc->sc_msr & SER_RI) {
905184610Salfred			sigon |= SER_RI;
906184610Salfred		}
907184610Salfred		return (sigon);
908184610Salfred	}
909184610Salfred	if (sigon & SER_DTR) {
910184610Salfred		sc->sc_mcr |= SER_DTR;
911184610Salfred	}
912184610Salfred	if (sigoff & SER_DTR) {
913184610Salfred		sc->sc_mcr &= ~SER_DTR;
914184610Salfred	}
915184610Salfred	if (sigon & SER_RTS) {
916184610Salfred		sc->sc_mcr |= SER_RTS;
917184610Salfred	}
918184610Salfred	if (sigoff & SER_RTS) {
919184610Salfred		sc->sc_mcr &= ~SER_RTS;
920184610Salfred	}
921184610Salfred	onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0;
922194228Sthompsa	ucom_dtr(sc, onoff);
923184610Salfred
924184610Salfred	onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0;
925194228Sthompsa	ucom_rts(sc, onoff);
926184610Salfred
927184610Salfred	return (0);
928184610Salfred}
929184610Salfred
930184610Salfredstatic void
931194228Sthompsaucom_cfg_line_state(struct usb_proc_msg *_task)
932184610Salfred{
933192984Sthompsa	struct ucom_cfg_task *task =
934192984Sthompsa	    (struct ucom_cfg_task *)_task;
935192984Sthompsa	struct ucom_softc *sc = task->sc;
936188413Sthompsa	uint8_t notch_bits;
937188413Sthompsa	uint8_t any_bits;
938188413Sthompsa	uint8_t prev_value;
939188413Sthompsa	uint8_t last_value;
940188413Sthompsa	uint8_t mask;
941187176Sthompsa
942184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
943184610Salfred		return;
944184610Salfred	}
945184610Salfred
946188413Sthompsa	mask = 0;
947188413Sthompsa	/* compute callback mask */
948194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_dtr)
949188413Sthompsa		mask |= UCOM_LS_DTR;
950194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_rts)
951188413Sthompsa		mask |= UCOM_LS_RTS;
952194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_break)
953188413Sthompsa		mask |= UCOM_LS_BREAK;
954197570Sthompsa	if (sc->sc_callback->ucom_cfg_set_ring)
955197570Sthompsa		mask |= UCOM_LS_RING;
956184610Salfred
957188413Sthompsa	/* compute the bits we are to program */
958188413Sthompsa	notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask;
959188413Sthompsa	any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask;
960188413Sthompsa	prev_value = sc->sc_pls_curr ^ notch_bits;
961188413Sthompsa	last_value = sc->sc_pls_curr;
962187176Sthompsa
963188413Sthompsa	/* reset programmed line state */
964188413Sthompsa	sc->sc_pls_curr = 0;
965188413Sthompsa	sc->sc_pls_set = 0;
966188413Sthompsa	sc->sc_pls_clr = 0;
967188413Sthompsa
968250576Seadler	/* ensure that we don't lose any levels */
969188413Sthompsa	if (notch_bits & UCOM_LS_DTR)
970194228Sthompsa		sc->sc_callback->ucom_cfg_set_dtr(sc,
971188413Sthompsa		    (prev_value & UCOM_LS_DTR) ? 1 : 0);
972188413Sthompsa	if (notch_bits & UCOM_LS_RTS)
973194228Sthompsa		sc->sc_callback->ucom_cfg_set_rts(sc,
974188413Sthompsa		    (prev_value & UCOM_LS_RTS) ? 1 : 0);
975188413Sthompsa	if (notch_bits & UCOM_LS_BREAK)
976194228Sthompsa		sc->sc_callback->ucom_cfg_set_break(sc,
977188413Sthompsa		    (prev_value & UCOM_LS_BREAK) ? 1 : 0);
978197570Sthompsa	if (notch_bits & UCOM_LS_RING)
979197570Sthompsa		sc->sc_callback->ucom_cfg_set_ring(sc,
980197570Sthompsa		    (prev_value & UCOM_LS_RING) ? 1 : 0);
981188413Sthompsa
982188413Sthompsa	/* set last value */
983188413Sthompsa	if (any_bits & UCOM_LS_DTR)
984194228Sthompsa		sc->sc_callback->ucom_cfg_set_dtr(sc,
985188413Sthompsa		    (last_value & UCOM_LS_DTR) ? 1 : 0);
986188413Sthompsa	if (any_bits & UCOM_LS_RTS)
987194228Sthompsa		sc->sc_callback->ucom_cfg_set_rts(sc,
988188413Sthompsa		    (last_value & UCOM_LS_RTS) ? 1 : 0);
989188413Sthompsa	if (any_bits & UCOM_LS_BREAK)
990194228Sthompsa		sc->sc_callback->ucom_cfg_set_break(sc,
991188413Sthompsa		    (last_value & UCOM_LS_BREAK) ? 1 : 0);
992197570Sthompsa	if (any_bits & UCOM_LS_RING)
993197570Sthompsa		sc->sc_callback->ucom_cfg_set_ring(sc,
994197570Sthompsa		    (last_value & UCOM_LS_RING) ? 1 : 0);
995187176Sthompsa}
996187176Sthompsa
997187176Sthompsastatic void
998194228Sthompsaucom_line_state(struct ucom_softc *sc,
999188413Sthompsa    uint8_t set_bits, uint8_t clear_bits)
1000184610Salfred{
1001239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1002184610Salfred
1003184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1004184610Salfred		return;
1005184610Salfred	}
1006184610Salfred
1007188413Sthompsa	DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits);
1008184610Salfred
1009188413Sthompsa	/* update current programmed line state */
1010188413Sthompsa	sc->sc_pls_curr |= set_bits;
1011188413Sthompsa	sc->sc_pls_curr &= ~clear_bits;
1012188413Sthompsa	sc->sc_pls_set |= set_bits;
1013188413Sthompsa	sc->sc_pls_clr |= clear_bits;
1014187176Sthompsa
1015188413Sthompsa	/* defer driver programming */
1016194228Sthompsa	ucom_queue_command(sc, ucom_cfg_line_state, NULL,
1017188413Sthompsa	    &sc->sc_line_state_task[0].hdr,
1018188413Sthompsa	    &sc->sc_line_state_task[1].hdr);
1019184610Salfred}
1020184610Salfred
1021184610Salfredstatic void
1022197570Sthompsaucom_ring(struct ucom_softc *sc, uint8_t onoff)
1023197570Sthompsa{
1024197570Sthompsa	DPRINTF("onoff = %d\n", onoff);
1025197570Sthompsa
1026197570Sthompsa	if (onoff)
1027197570Sthompsa		ucom_line_state(sc, UCOM_LS_RING, 0);
1028197570Sthompsa	else
1029197570Sthompsa		ucom_line_state(sc, 0, UCOM_LS_RING);
1030197570Sthompsa}
1031197570Sthompsa
1032197570Sthompsastatic void
1033194228Sthompsaucom_break(struct ucom_softc *sc, uint8_t onoff)
1034187176Sthompsa{
1035188413Sthompsa	DPRINTF("onoff = %d\n", onoff);
1036187176Sthompsa
1037188413Sthompsa	if (onoff)
1038194228Sthompsa		ucom_line_state(sc, UCOM_LS_BREAK, 0);
1039188413Sthompsa	else
1040194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_BREAK);
1041187176Sthompsa}
1042187176Sthompsa
1043187176Sthompsastatic void
1044194228Sthompsaucom_dtr(struct ucom_softc *sc, uint8_t onoff)
1045184610Salfred{
1046184610Salfred	DPRINTF("onoff = %d\n", onoff);
1047184610Salfred
1048188413Sthompsa	if (onoff)
1049194228Sthompsa		ucom_line_state(sc, UCOM_LS_DTR, 0);
1050188413Sthompsa	else
1051194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_DTR);
1052184610Salfred}
1053184610Salfred
1054184610Salfredstatic void
1055194228Sthompsaucom_rts(struct ucom_softc *sc, uint8_t onoff)
1056184610Salfred{
1057184610Salfred	DPRINTF("onoff = %d\n", onoff);
1058184610Salfred
1059188413Sthompsa	if (onoff)
1060194228Sthompsa		ucom_line_state(sc, UCOM_LS_RTS, 0);
1061188413Sthompsa	else
1062194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_RTS);
1063184610Salfred}
1064184610Salfred
1065184610Salfredstatic void
1066194228Sthompsaucom_cfg_status_change(struct usb_proc_msg *_task)
1067184610Salfred{
1068192984Sthompsa	struct ucom_cfg_task *task =
1069192984Sthompsa	    (struct ucom_cfg_task *)_task;
1070192984Sthompsa	struct ucom_softc *sc = task->sc;
1071184610Salfred	struct tty *tp;
1072184610Salfred	uint8_t new_msr;
1073184610Salfred	uint8_t new_lsr;
1074184610Salfred	uint8_t onoff;
1075213872Shselasky	uint8_t lsr_delta;
1076184610Salfred
1077184610Salfred	tp = sc->sc_tty;
1078184610Salfred
1079239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1080184610Salfred
1081184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
1082184610Salfred		return;
1083184610Salfred	}
1084194228Sthompsa	if (sc->sc_callback->ucom_cfg_get_status == NULL) {
1085184610Salfred		return;
1086184610Salfred	}
1087184610Salfred	/* get status */
1088184610Salfred
1089184610Salfred	new_msr = 0;
1090184610Salfred	new_lsr = 0;
1091184610Salfred
1092194228Sthompsa	(sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr);
1093184610Salfred
1094184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1095184610Salfred		/* TTY device closed */
1096184610Salfred		return;
1097184610Salfred	}
1098184610Salfred	onoff = ((sc->sc_msr ^ new_msr) & SER_DCD);
1099213872Shselasky	lsr_delta = (sc->sc_lsr ^ new_lsr);
1100184610Salfred
1101184610Salfred	sc->sc_msr = new_msr;
1102184610Salfred	sc->sc_lsr = new_lsr;
1103184610Salfred
1104184610Salfred	if (onoff) {
1105184610Salfred
1106184610Salfred		onoff = (sc->sc_msr & SER_DCD) ? 1 : 0;
1107184610Salfred
1108184610Salfred		DPRINTF("DCD changed to %d\n", onoff);
1109184610Salfred
1110184610Salfred		ttydisc_modem(tp, onoff);
1111184610Salfred	}
1112213872Shselasky
1113213872Shselasky	if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) {
1114213872Shselasky
1115213872Shselasky		DPRINTF("BREAK detected\n");
1116213872Shselasky
1117213872Shselasky		ttydisc_rint(tp, 0, TRE_BREAK);
1118213872Shselasky		ttydisc_rint_done(tp);
1119213872Shselasky	}
1120213872Shselasky
1121213872Shselasky	if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) {
1122213872Shselasky
1123213872Shselasky		DPRINTF("Frame error detected\n");
1124213872Shselasky
1125213872Shselasky		ttydisc_rint(tp, 0, TRE_FRAMING);
1126213872Shselasky		ttydisc_rint_done(tp);
1127213872Shselasky	}
1128213872Shselasky
1129213872Shselasky	if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) {
1130213872Shselasky
1131213872Shselasky		DPRINTF("Parity error detected\n");
1132213872Shselasky
1133213872Shselasky		ttydisc_rint(tp, 0, TRE_PARITY);
1134213872Shselasky		ttydisc_rint_done(tp);
1135213872Shselasky	}
1136184610Salfred}
1137184610Salfred
1138184610Salfredvoid
1139194228Sthompsaucom_status_change(struct ucom_softc *sc)
1140184610Salfred{
1141239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1142184610Salfred
1143197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE)
1144197570Sthompsa		return;		/* not supported */
1145197570Sthompsa
1146184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1147184610Salfred		return;
1148184610Salfred	}
1149184610Salfred	DPRINTF("\n");
1150184610Salfred
1151194228Sthompsa	ucom_queue_command(sc, ucom_cfg_status_change, NULL,
1152188413Sthompsa	    &sc->sc_status_task[0].hdr,
1153188413Sthompsa	    &sc->sc_status_task[1].hdr);
1154184610Salfred}
1155184610Salfred
1156184610Salfredstatic void
1157194228Sthompsaucom_cfg_param(struct usb_proc_msg *_task)
1158184610Salfred{
1159192984Sthompsa	struct ucom_param_task *task =
1160192984Sthompsa	    (struct ucom_param_task *)_task;
1161192984Sthompsa	struct ucom_softc *sc = task->sc;
1162184610Salfred
1163184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
1164184610Salfred		return;
1165184610Salfred	}
1166194228Sthompsa	if (sc->sc_callback->ucom_cfg_param == NULL) {
1167184610Salfred		return;
1168184610Salfred	}
1169184610Salfred
1170194228Sthompsa	(sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy);
1171184610Salfred
1172184610Salfred	/* wait a little */
1173194228Sthompsa	usb_pause_mtx(sc->sc_mtx, hz / 10);
1174184610Salfred}
1175184610Salfred
1176184610Salfredstatic int
1177194228Sthompsaucom_param(struct tty *tp, struct termios *t)
1178184610Salfred{
1179192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
1180184610Salfred	uint8_t opened;
1181184610Salfred	int error;
1182184610Salfred
1183239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1184184610Salfred
1185184610Salfred	opened = 0;
1186184610Salfred	error = 0;
1187184610Salfred
1188184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1189184610Salfred
1190184610Salfred		/* XXX the TTY layer should call "open()" first! */
1191242695Shselasky		/*
1192242695Shselasky		 * Not quite: Its ordering is partly backwards, but
1193242695Shselasky		 * some parameters must be set early in ttydev_open(),
1194242695Shselasky		 * possibly before calling ttydevsw_open().
1195242695Shselasky		 */
1196194228Sthompsa		error = ucom_open(tp);
1197242695Shselasky		if (error)
1198184610Salfred			goto done;
1199242695Shselasky
1200184610Salfred		opened = 1;
1201184610Salfred	}
1202184610Salfred	DPRINTF("sc = %p\n", sc);
1203184610Salfred
1204184610Salfred	/* Check requested parameters. */
1205184610Salfred	if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) {
1206242695Shselasky		/* XXX c_ospeed == 0 is perfectly valid. */
1207184610Salfred		DPRINTF("mismatch ispeed and ospeed\n");
1208184610Salfred		error = EINVAL;
1209184610Salfred		goto done;
1210184610Salfred	}
1211184610Salfred	t->c_ispeed = t->c_ospeed;
1212184610Salfred
1213194228Sthompsa	if (sc->sc_callback->ucom_pre_param) {
1214184610Salfred		/* Let the lower layer verify the parameters */
1215194228Sthompsa		error = (sc->sc_callback->ucom_pre_param) (sc, t);
1216184610Salfred		if (error) {
1217184610Salfred			DPRINTF("callback error = %d\n", error);
1218184610Salfred			goto done;
1219184610Salfred		}
1220184610Salfred	}
1221184610Salfred
1222184610Salfred	/* Disable transfers */
1223184610Salfred	sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
1224184610Salfred
1225184610Salfred	/* Queue baud rate programming command first */
1226194228Sthompsa	ucom_queue_command(sc, ucom_cfg_param, t,
1227188413Sthompsa	    &sc->sc_param_task[0].hdr,
1228188413Sthompsa	    &sc->sc_param_task[1].hdr);
1229184610Salfred
1230184610Salfred	/* Queue transfer enable command last */
1231194228Sthompsa	ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
1232188413Sthompsa	    &sc->sc_start_task[0].hdr,
1233188413Sthompsa	    &sc->sc_start_task[1].hdr);
1234184610Salfred
1235184610Salfred	if (t->c_cflag & CRTS_IFLOW) {
1236184610Salfred		sc->sc_flag |= UCOM_FLAG_RTS_IFLOW;
1237184610Salfred	} else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) {
1238184610Salfred		sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW;
1239194228Sthompsa		ucom_modem(tp, SER_RTS, 0);
1240184610Salfred	}
1241184610Salfreddone:
1242184610Salfred	if (error) {
1243184610Salfred		if (opened) {
1244194228Sthompsa			ucom_close(tp);
1245184610Salfred		}
1246184610Salfred	}
1247184610Salfred	return (error);
1248184610Salfred}
1249184610Salfred
1250184610Salfredstatic void
1251194228Sthompsaucom_outwakeup(struct tty *tp)
1252184610Salfred{
1253192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
1254184610Salfred
1255239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1256184610Salfred
1257184610Salfred	DPRINTF("sc = %p\n", sc);
1258184610Salfred
1259184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1260184610Salfred		/* The higher layer is not ready */
1261184610Salfred		return;
1262184610Salfred	}
1263194228Sthompsa	ucom_start_transfers(sc);
1264184610Salfred}
1265184610Salfred
1266184610Salfred/*------------------------------------------------------------------------*
1267194228Sthompsa *	ucom_get_data
1268184610Salfred *
1269184610Salfred * Return values:
1270184610Salfred * 0: No data is available.
1271184610Salfred * Else: Data is available.
1272184610Salfred *------------------------------------------------------------------------*/
1273184610Salfreduint8_t
1274194228Sthompsaucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc,
1275184610Salfred    uint32_t offset, uint32_t len, uint32_t *actlen)
1276184610Salfred{
1277192984Sthompsa	struct usb_page_search res;
1278184610Salfred	struct tty *tp = sc->sc_tty;
1279184610Salfred	uint32_t cnt;
1280184610Salfred	uint32_t offset_orig;
1281184610Salfred
1282239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1283184610Salfred
1284197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
1285197570Sthompsa		unsigned int temp;
1286197570Sthompsa
1287197570Sthompsa		/* get total TX length */
1288197570Sthompsa
1289197570Sthompsa		temp = ucom_cons_tx_high - ucom_cons_tx_low;
1290197570Sthompsa		temp %= UCOM_CONS_BUFSIZE;
1291197570Sthompsa
1292197570Sthompsa		/* limit TX length */
1293197570Sthompsa
1294197570Sthompsa		if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low))
1295197570Sthompsa			temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low);
1296197570Sthompsa
1297197570Sthompsa		if (temp > len)
1298197570Sthompsa			temp = len;
1299197570Sthompsa
1300197570Sthompsa		/* copy in data */
1301197570Sthompsa
1302197570Sthompsa		usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp);
1303197570Sthompsa
1304197570Sthompsa		/* update counters */
1305197570Sthompsa
1306197570Sthompsa		ucom_cons_tx_low += temp;
1307197570Sthompsa		ucom_cons_tx_low %= UCOM_CONS_BUFSIZE;
1308197570Sthompsa
1309197570Sthompsa		/* store actual length */
1310197570Sthompsa
1311197570Sthompsa		*actlen = temp;
1312197570Sthompsa
1313197570Sthompsa		return (temp ? 1 : 0);
1314197570Sthompsa	}
1315197570Sthompsa
1316192820Sthompsa	if (tty_gone(tp) ||
1317192820Sthompsa	    !(sc->sc_flag & UCOM_FLAG_GP_DATA)) {
1318184610Salfred		actlen[0] = 0;
1319184610Salfred		return (0);		/* multiport device polling */
1320184610Salfred	}
1321184610Salfred	offset_orig = offset;
1322184610Salfred
1323184610Salfred	while (len != 0) {
1324184610Salfred
1325194228Sthompsa		usbd_get_page(pc, offset, &res);
1326184610Salfred
1327184610Salfred		if (res.length > len) {
1328184610Salfred			res.length = len;
1329184610Salfred		}
1330184610Salfred		/* copy data directly into USB buffer */
1331184610Salfred		cnt = ttydisc_getc(tp, res.buffer, res.length);
1332184610Salfred
1333184610Salfred		offset += cnt;
1334184610Salfred		len -= cnt;
1335184610Salfred
1336184610Salfred		if (cnt < res.length) {
1337184610Salfred			/* end of buffer */
1338184610Salfred			break;
1339184610Salfred		}
1340184610Salfred	}
1341184610Salfred
1342184610Salfred	actlen[0] = offset - offset_orig;
1343184610Salfred
1344184610Salfred	DPRINTF("cnt=%d\n", actlen[0]);
1345184610Salfred
1346184610Salfred	if (actlen[0] == 0) {
1347184610Salfred		return (0);
1348184610Salfred	}
1349184610Salfred	return (1);
1350184610Salfred}
1351184610Salfred
1352184610Salfredvoid
1353194228Sthompsaucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc,
1354184610Salfred    uint32_t offset, uint32_t len)
1355184610Salfred{
1356192984Sthompsa	struct usb_page_search res;
1357184610Salfred	struct tty *tp = sc->sc_tty;
1358184610Salfred	char *buf;
1359184610Salfred	uint32_t cnt;
1360184610Salfred
1361239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1362184610Salfred
1363197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
1364197570Sthompsa		unsigned int temp;
1365197570Sthompsa
1366197570Sthompsa		/* get maximum RX length */
1367197570Sthompsa
1368197570Sthompsa		temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low;
1369197570Sthompsa		temp %= UCOM_CONS_BUFSIZE;
1370197570Sthompsa
1371197570Sthompsa		/* limit RX length */
1372197570Sthompsa
1373197570Sthompsa		if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high))
1374197570Sthompsa			temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high);
1375197570Sthompsa
1376197570Sthompsa		if (temp > len)
1377197570Sthompsa			temp = len;
1378197570Sthompsa
1379197570Sthompsa		/* copy out data */
1380197570Sthompsa
1381197570Sthompsa		usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp);
1382197570Sthompsa
1383197570Sthompsa		/* update counters */
1384197570Sthompsa
1385197570Sthompsa		ucom_cons_rx_high += temp;
1386197570Sthompsa		ucom_cons_rx_high %= UCOM_CONS_BUFSIZE;
1387197570Sthompsa
1388197570Sthompsa		return;
1389197570Sthompsa	}
1390197570Sthompsa
1391192820Sthompsa	if (tty_gone(tp))
1392184610Salfred		return;			/* multiport device polling */
1393192820Sthompsa
1394184610Salfred	if (len == 0)
1395184610Salfred		return;			/* no data */
1396184610Salfred
1397184610Salfred	/* set a flag to prevent recursation ? */
1398184610Salfred
1399184610Salfred	while (len > 0) {
1400184610Salfred
1401194228Sthompsa		usbd_get_page(pc, offset, &res);
1402184610Salfred
1403184610Salfred		if (res.length > len) {
1404184610Salfred			res.length = len;
1405184610Salfred		}
1406184610Salfred		len -= res.length;
1407184610Salfred		offset += res.length;
1408184610Salfred
1409184610Salfred		/* pass characters to tty layer */
1410184610Salfred
1411184610Salfred		buf = res.buffer;
1412184610Salfred		cnt = res.length;
1413184610Salfred
1414184610Salfred		/* first check if we can pass the buffer directly */
1415184610Salfred
1416184610Salfred		if (ttydisc_can_bypass(tp)) {
1417242619Shselasky
1418242619Shselasky			/* clear any jitter buffer */
1419242619Shselasky			sc->sc_jitterbuf_in = 0;
1420242619Shselasky			sc->sc_jitterbuf_out = 0;
1421242619Shselasky
1422184610Salfred			if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) {
1423184610Salfred				DPRINTF("tp=%p, data lost\n", tp);
1424184610Salfred			}
1425184610Salfred			continue;
1426184610Salfred		}
1427184610Salfred		/* need to loop */
1428184610Salfred
1429184610Salfred		for (cnt = 0; cnt != res.length; cnt++) {
1430242619Shselasky			if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out ||
1431242619Shselasky			    ttydisc_rint(tp, buf[cnt], 0) == -1) {
1432242619Shselasky				uint16_t end;
1433242619Shselasky				uint16_t pos;
1434184610Salfred
1435242619Shselasky				pos = sc->sc_jitterbuf_in;
1436242619Shselasky				end = sc->sc_jitterbuf_out +
1437242619Shselasky				    UCOM_JITTERBUF_SIZE - 1;
1438242619Shselasky				if (end >= UCOM_JITTERBUF_SIZE)
1439242619Shselasky					end -= UCOM_JITTERBUF_SIZE;
1440242619Shselasky
1441242619Shselasky				for (; cnt != res.length; cnt++) {
1442242619Shselasky					if (pos == end)
1443242619Shselasky						break;
1444242619Shselasky					sc->sc_jitterbuf[pos] = buf[cnt];
1445242619Shselasky					pos++;
1446242619Shselasky					if (pos >= UCOM_JITTERBUF_SIZE)
1447242619Shselasky						pos -= UCOM_JITTERBUF_SIZE;
1448242619Shselasky				}
1449242619Shselasky
1450242619Shselasky				sc->sc_jitterbuf_in = pos;
1451242619Shselasky
1452242619Shselasky				/* set RTS in async fashion */
1453242619Shselasky				if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)
1454242619Shselasky					ucom_rts(sc, 1);
1455242619Shselasky
1456184610Salfred				DPRINTF("tp=%p, lost %d "
1457184610Salfred				    "chars\n", tp, res.length - cnt);
1458184610Salfred				break;
1459184610Salfred			}
1460184610Salfred		}
1461184610Salfred	}
1462184610Salfred	ttydisc_rint_done(tp);
1463184610Salfred}
1464184610Salfred
1465184610Salfredstatic void
1466194228Sthompsaucom_free(void *xsc)
1467184610Salfred{
1468192984Sthompsa	struct ucom_softc *sc = xsc;
1469184610Salfred
1470239179Shselasky	if (sc->sc_callback->ucom_free != NULL)
1471239179Shselasky		sc->sc_callback->ucom_free(sc);
1472239179Shselasky	else
1473239179Shselasky		ucom_unref(sc->sc_super);
1474239179Shselasky
1475239179Shselasky	mtx_lock(&ucom_mtx);
1476239179Shselasky	ucom_close_refs--;
1477239179Shselasky	mtx_unlock(&ucom_mtx);
1478184610Salfred}
1479197570Sthompsa
1480197570Sthompsastatic cn_probe_t ucom_cnprobe;
1481197570Sthompsastatic cn_init_t ucom_cninit;
1482197570Sthompsastatic cn_term_t ucom_cnterm;
1483197570Sthompsastatic cn_getc_t ucom_cngetc;
1484197570Sthompsastatic cn_putc_t ucom_cnputc;
1485228631Savgstatic cn_grab_t ucom_cngrab;
1486228631Savgstatic cn_ungrab_t ucom_cnungrab;
1487197570Sthompsa
1488197570SthompsaCONSOLE_DRIVER(ucom);
1489197570Sthompsa
1490197570Sthompsastatic void
1491197570Sthompsaucom_cnprobe(struct consdev  *cp)
1492197570Sthompsa{
1493198774Sthompsa	if (ucom_cons_unit != -1)
1494198774Sthompsa		cp->cn_pri = CN_NORMAL;
1495198774Sthompsa	else
1496198774Sthompsa		cp->cn_pri = CN_DEAD;
1497198774Sthompsa
1498198774Sthompsa	strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name));
1499197570Sthompsa}
1500197570Sthompsa
1501197570Sthompsastatic void
1502197570Sthompsaucom_cninit(struct consdev  *cp)
1503197570Sthompsa{
1504197570Sthompsa}
1505197570Sthompsa
1506197570Sthompsastatic void
1507197570Sthompsaucom_cnterm(struct consdev  *cp)
1508197570Sthompsa{
1509197570Sthompsa}
1510197570Sthompsa
1511228631Savgstatic void
1512228631Savgucom_cngrab(struct consdev *cp)
1513228631Savg{
1514228631Savg}
1515228631Savg
1516228631Savgstatic void
1517228631Savgucom_cnungrab(struct consdev *cp)
1518228631Savg{
1519228631Savg}
1520228631Savg
1521197570Sthompsastatic int
1522197570Sthompsaucom_cngetc(struct consdev *cd)
1523197570Sthompsa{
1524197570Sthompsa	struct ucom_softc *sc = ucom_cons_softc;
1525197570Sthompsa	int c;
1526197570Sthompsa
1527197570Sthompsa	if (sc == NULL)
1528197570Sthompsa		return (-1);
1529197570Sthompsa
1530239179Shselasky	UCOM_MTX_LOCK(sc);
1531197570Sthompsa
1532197570Sthompsa	if (ucom_cons_rx_low != ucom_cons_rx_high) {
1533197570Sthompsa		c = ucom_cons_rx_buf[ucom_cons_rx_low];
1534197570Sthompsa		ucom_cons_rx_low ++;
1535197570Sthompsa		ucom_cons_rx_low %= UCOM_CONS_BUFSIZE;
1536197570Sthompsa	} else {
1537197570Sthompsa		c = -1;
1538197570Sthompsa	}
1539197570Sthompsa
1540197570Sthompsa	/* start USB transfers */
1541197570Sthompsa	ucom_outwakeup(sc->sc_tty);
1542197570Sthompsa
1543239179Shselasky	UCOM_MTX_UNLOCK(sc);
1544197570Sthompsa
1545197570Sthompsa	/* poll if necessary */
1546197570Sthompsa	if (kdb_active && sc->sc_callback->ucom_poll)
1547197570Sthompsa		(sc->sc_callback->ucom_poll) (sc);
1548197570Sthompsa
1549197570Sthompsa	return (c);
1550197570Sthompsa}
1551197570Sthompsa
1552197570Sthompsastatic void
1553197570Sthompsaucom_cnputc(struct consdev *cd, int c)
1554197570Sthompsa{
1555197570Sthompsa	struct ucom_softc *sc = ucom_cons_softc;
1556197570Sthompsa	unsigned int temp;
1557197570Sthompsa
1558197570Sthompsa	if (sc == NULL)
1559197570Sthompsa		return;
1560197570Sthompsa
1561197570Sthompsa repeat:
1562197570Sthompsa
1563239179Shselasky	UCOM_MTX_LOCK(sc);
1564197570Sthompsa
1565197570Sthompsa	/* compute maximum TX length */
1566197570Sthompsa
1567197570Sthompsa	temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low;
1568197570Sthompsa	temp %= UCOM_CONS_BUFSIZE;
1569197570Sthompsa
1570197570Sthompsa	if (temp) {
1571197570Sthompsa		ucom_cons_tx_buf[ucom_cons_tx_high] = c;
1572197570Sthompsa		ucom_cons_tx_high ++;
1573197570Sthompsa		ucom_cons_tx_high %= UCOM_CONS_BUFSIZE;
1574197570Sthompsa	}
1575197570Sthompsa
1576197570Sthompsa	/* start USB transfers */
1577197570Sthompsa	ucom_outwakeup(sc->sc_tty);
1578197570Sthompsa
1579239179Shselasky	UCOM_MTX_UNLOCK(sc);
1580197570Sthompsa
1581197570Sthompsa	/* poll if necessary */
1582197570Sthompsa	if (kdb_active && sc->sc_callback->ucom_poll) {
1583197570Sthompsa		(sc->sc_callback->ucom_poll) (sc);
1584197570Sthompsa		/* simple flow control */
1585197570Sthompsa		if (temp == 0)
1586197570Sthompsa			goto repeat;
1587197570Sthompsa	}
1588197570Sthompsa}
1589197570Sthompsa
1590239179Shselasky/*------------------------------------------------------------------------*
1591239179Shselasky *	ucom_ref
1592239179Shselasky *
1593239179Shselasky * This function will increment the super UCOM reference count.
1594239179Shselasky *------------------------------------------------------------------------*/
1595239179Shselaskyvoid
1596239179Shselaskyucom_ref(struct ucom_super_softc *ssc)
1597239179Shselasky{
1598239179Shselasky	mtx_lock(&ucom_mtx);
1599239179Shselasky	ssc->sc_refs++;
1600239179Shselasky	mtx_unlock(&ucom_mtx);
1601239179Shselasky}
1602239179Shselasky
1603239179Shselasky/*------------------------------------------------------------------------*
1604239299Shselasky *	ucom_free_unit
1605239299Shselasky *
1606239299Shselasky * This function will free the super UCOM's allocated unit
1607239299Shselasky * number. This function can be called on a zero-initialized
1608239299Shselasky * structure. This function can be called multiple times.
1609239299Shselasky *------------------------------------------------------------------------*/
1610239299Shselaskystatic void
1611239299Shselaskyucom_free_unit(struct ucom_super_softc *ssc)
1612239299Shselasky{
1613239299Shselasky	if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT))
1614239299Shselasky		return;
1615239299Shselasky
1616239299Shselasky	ucom_unit_free(ssc->sc_unit);
1617239299Shselasky
1618239299Shselasky	ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT;
1619239299Shselasky}
1620239299Shselasky
1621239299Shselasky/*------------------------------------------------------------------------*
1622239179Shselasky *	ucom_unref
1623239179Shselasky *
1624239179Shselasky * This function will decrement the super UCOM reference count.
1625239179Shselasky *
1626239179Shselasky * Return values:
1627239179Shselasky * 0: UCOM structures are still referenced.
1628239179Shselasky * Else: UCOM structures are no longer referenced.
1629239179Shselasky *------------------------------------------------------------------------*/
1630239179Shselaskyint
1631239179Shselaskyucom_unref(struct ucom_super_softc *ssc)
1632239179Shselasky{
1633239179Shselasky	int retval;
1634239179Shselasky
1635239179Shselasky	mtx_lock(&ucom_mtx);
1636239179Shselasky	retval = (ssc->sc_refs < 2);
1637239179Shselasky	ssc->sc_refs--;
1638239179Shselasky	mtx_unlock(&ucom_mtx);
1639239179Shselasky
1640239299Shselasky	if (retval)
1641239299Shselasky		ucom_free_unit(ssc);
1642239299Shselasky
1643239179Shselasky	return (retval);
1644239179Shselasky}
1645239179Shselasky
1646197570Sthompsa#if defined(GDB)
1647197570Sthompsa
1648197570Sthompsa#include <gdb/gdb.h>
1649197570Sthompsa
1650197570Sthompsastatic gdb_probe_f ucom_gdbprobe;
1651197570Sthompsastatic gdb_init_f ucom_gdbinit;
1652197570Sthompsastatic gdb_term_f ucom_gdbterm;
1653197570Sthompsastatic gdb_getc_f ucom_gdbgetc;
1654197570Sthompsastatic gdb_putc_f ucom_gdbputc;
1655197570Sthompsa
1656197570SthompsaGDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc);
1657197570Sthompsa
1658197570Sthompsastatic int
1659197570Sthompsaucom_gdbprobe(void)
1660197570Sthompsa{
1661197570Sthompsa	return ((ucom_cons_softc != NULL) ? 0 : -1);
1662197570Sthompsa}
1663197570Sthompsa
1664197570Sthompsastatic void
1665197570Sthompsaucom_gdbinit(void)
1666197570Sthompsa{
1667197570Sthompsa}
1668197570Sthompsa
1669197570Sthompsastatic void
1670197570Sthompsaucom_gdbterm(void)
1671197570Sthompsa{
1672197570Sthompsa}
1673197570Sthompsa
1674197570Sthompsastatic void
1675197570Sthompsaucom_gdbputc(int c)
1676197570Sthompsa{
1677197570Sthompsa        ucom_cnputc(NULL, c);
1678197570Sthompsa}
1679197570Sthompsa
1680197570Sthompsastatic int
1681197570Sthompsaucom_gdbgetc(void)
1682197570Sthompsa{
1683197570Sthompsa        return (ucom_cngetc(NULL));
1684197570Sthompsa}
1685197570Sthompsa
1686197570Sthompsa#endif
1687