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
84294637Sian#include <dev/uart/uart_ppstypes.h>
85294637Sian
86188942Sthompsa#include <dev/usb/usb.h>
87194677Sthompsa#include <dev/usb/usbdi.h>
88194677Sthompsa#include <dev/usb/usbdi_util.h>
89184610Salfred
90194228Sthompsa#define	USB_DEBUG_VAR ucom_debug
91188942Sthompsa#include <dev/usb/usb_debug.h>
92194677Sthompsa#include <dev/usb/usb_busdma.h>
93188942Sthompsa#include <dev/usb/usb_process.h>
94184610Salfred
95188942Sthompsa#include <dev/usb/serial/usb_serial.h>
96184610Salfred
97197570Sthompsa#include "opt_gdb.h"
98197570Sthompsa
99227309Sedstatic SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom");
100197570Sthompsa
101283341Sianstatic int ucom_pps_mode;
102283341Sian
103283341SianSYSCTL_INT(_hw_usb_ucom, OID_AUTO, pps_mode, CTLFLAG_RWTUN,
104294637Sian    &ucom_pps_mode, 0,
105294637Sian    "pulse capture mode: 0/1/2=disabled/CTS/DCD; add 0x10 to invert");
106283341Sian
107207077Sthompsa#ifdef USB_DEBUG
108194228Sthompsastatic int ucom_debug = 0;
109184610Salfred
110192502SthompsaSYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW,
111194228Sthompsa    &ucom_debug, 0, "ucom debug level");
112184610Salfred#endif
113184610Salfred
114197570Sthompsa#define	UCOM_CONS_BUFSIZE 1024
115197570Sthompsa
116197570Sthompsastatic uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE];
117197570Sthompsastatic uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE];
118197570Sthompsa
119197570Sthompsastatic unsigned int ucom_cons_rx_low = 0;
120197570Sthompsastatic unsigned int ucom_cons_rx_high = 0;
121197570Sthompsa
122197570Sthompsastatic unsigned int ucom_cons_tx_low = 0;
123197570Sthompsastatic unsigned int ucom_cons_tx_high = 0;
124197570Sthompsa
125197570Sthompsastatic int ucom_cons_unit = -1;
126214761Sn_hibmastatic int ucom_cons_subunit = 0;
127197570Sthompsastatic int ucom_cons_baud = 9600;
128197570Sthompsastatic struct ucom_softc *ucom_cons_softc = NULL;
129197570Sthompsa
130197570SthompsaTUNABLE_INT("hw.usb.ucom.cons_unit", &ucom_cons_unit);
131242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RW | CTLFLAG_TUN,
132197570Sthompsa    &ucom_cons_unit, 0, "console unit number");
133214761Sn_hibmaTUNABLE_INT("hw.usb.ucom.cons_subunit", &ucom_cons_subunit);
134242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RW | CTLFLAG_TUN,
135214761Sn_hibma    &ucom_cons_subunit, 0, "console subunit number");
136197570SthompsaTUNABLE_INT("hw.usb.ucom.cons_baud", &ucom_cons_baud);
137242126ShselaskySYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RW | CTLFLAG_TUN,
138197570Sthompsa    &ucom_cons_baud, 0, "console baud rate");
139197570Sthompsa
140194228Sthompsastatic usb_proc_callback_t ucom_cfg_start_transfers;
141194228Sthompsastatic usb_proc_callback_t ucom_cfg_open;
142194228Sthompsastatic usb_proc_callback_t ucom_cfg_close;
143194228Sthompsastatic usb_proc_callback_t ucom_cfg_line_state;
144194228Sthompsastatic usb_proc_callback_t ucom_cfg_status_change;
145194228Sthompsastatic usb_proc_callback_t ucom_cfg_param;
146184610Salfred
147214761Sn_hibmastatic int	ucom_unit_alloc(void);
148214761Sn_hibmastatic void	ucom_unit_free(int);
149214761Sn_hibmastatic int	ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *);
150239179Shselaskystatic void	ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *);
151194228Sthompsastatic void	ucom_queue_command(struct ucom_softc *,
152193045Sthompsa		    usb_proc_callback_t *, struct termios *pt,
153192984Sthompsa		    struct usb_proc_msg *t0, struct usb_proc_msg *t1);
154194228Sthompsastatic void	ucom_shutdown(struct ucom_softc *);
155197570Sthompsastatic void	ucom_ring(struct ucom_softc *, uint8_t);
156194228Sthompsastatic void	ucom_break(struct ucom_softc *, uint8_t);
157194228Sthompsastatic void	ucom_dtr(struct ucom_softc *, uint8_t);
158194228Sthompsastatic void	ucom_rts(struct ucom_softc *, uint8_t);
159184610Salfred
160194228Sthompsastatic tsw_open_t ucom_open;
161194228Sthompsastatic tsw_close_t ucom_close;
162194228Sthompsastatic tsw_ioctl_t ucom_ioctl;
163194228Sthompsastatic tsw_modem_t ucom_modem;
164194228Sthompsastatic tsw_param_t ucom_param;
165194228Sthompsastatic tsw_outwakeup_t ucom_outwakeup;
166242619Shselaskystatic tsw_inwakeup_t ucom_inwakeup;
167194228Sthompsastatic tsw_free_t ucom_free;
168184610Salfred
169194228Sthompsastatic struct ttydevsw ucom_class = {
170184610Salfred	.tsw_flags = TF_INITLOCK | TF_CALLOUT,
171194228Sthompsa	.tsw_open = ucom_open,
172194228Sthompsa	.tsw_close = ucom_close,
173194228Sthompsa	.tsw_outwakeup = ucom_outwakeup,
174242619Shselasky	.tsw_inwakeup = ucom_inwakeup,
175194228Sthompsa	.tsw_ioctl = ucom_ioctl,
176194228Sthompsa	.tsw_param = ucom_param,
177194228Sthompsa	.tsw_modem = ucom_modem,
178194228Sthompsa	.tsw_free = ucom_free,
179184610Salfred};
180184610Salfred
181188942SthompsaMODULE_DEPEND(ucom, usb, 1, 1, 1);
182188942SthompsaMODULE_VERSION(ucom, 1);
183184610Salfred
184239179Shselasky#define	UCOM_UNIT_MAX 		128	/* maximum number of units */
185239179Shselasky#define	UCOM_TTY_PREFIX		"U"
186184610Salfred
187239179Shselaskystatic struct unrhdr *ucom_unrhdr;
188239179Shselaskystatic struct mtx ucom_mtx;
189239179Shselaskystatic int ucom_close_refs;
190184610Salfred
191239179Shselaskystatic void
192239179Shselaskyucom_init(void *arg)
193239179Shselasky{
194239179Shselasky	DPRINTF("\n");
195239179Shselasky	ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL);
196239179Shselasky	mtx_init(&ucom_mtx, "UCOM MTX", NULL, MTX_DEF);
197239179Shselasky}
198239179ShselaskySYSINIT(ucom_init, SI_SUB_KLD - 1, SI_ORDER_ANY, ucom_init, NULL);
199214843Sn_hibma
200239179Shselaskystatic void
201239179Shselaskyucom_uninit(void *arg)
202239179Shselasky{
203239179Shselasky	struct unrhdr *hdr;
204239179Shselasky	hdr = ucom_unrhdr;
205239179Shselasky	ucom_unrhdr = NULL;
206239179Shselasky
207239179Shselasky	DPRINTF("\n");
208239179Shselasky
209239179Shselasky	if (hdr != NULL)
210239179Shselasky		delete_unrhdr(hdr);
211239179Shselasky
212239179Shselasky	mtx_destroy(&ucom_mtx);
213239179Shselasky}
214268206ShselaskySYSUNINIT(ucom_uninit, SI_SUB_KLD - 3, SI_ORDER_ANY, ucom_uninit, NULL);
215239179Shselasky
216214761Sn_hibma/*
217214761Sn_hibma * Mark a unit number (the X in cuaUX) as in use.
218214761Sn_hibma *
219214761Sn_hibma * Note that devices using a different naming scheme (see ucom_tty_name()
220214761Sn_hibma * callback) still use this unit allocation.
221214761Sn_hibma */
222214761Sn_hibmastatic int
223214761Sn_hibmaucom_unit_alloc(void)
224184610Salfred{
225214761Sn_hibma	int unit;
226184610Salfred
227239179Shselasky	/* sanity checks */
228239179Shselasky	if (ucom_unrhdr == NULL) {
229239179Shselasky		DPRINTF("ucom_unrhdr is NULL\n");
230239179Shselasky		return (-1);
231214919Sn_hibma	}
232239179Shselasky	unit = alloc_unr(ucom_unrhdr);
233239179Shselasky	DPRINTF("unit %d is allocated\n", unit);
234239179Shselasky	return (unit);
235184610Salfred}
236184610Salfred
237214761Sn_hibma/*
238214761Sn_hibma * Mark the unit number as not in use.
239214761Sn_hibma */
240184610Salfredstatic void
241214761Sn_hibmaucom_unit_free(int unit)
242184610Salfred{
243239179Shselasky	/* sanity checks */
244239179Shselasky	if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) {
245239179Shselasky		DPRINTF("cannot free unit number\n");
246239179Shselasky		return;
247239179Shselasky	}
248239179Shselasky	DPRINTF("unit %d is freed\n", unit);
249239179Shselasky	free_unr(ucom_unrhdr, unit);
250184610Salfred}
251184610Salfred
252184610Salfred/*
253214761Sn_hibma * Setup a group of one or more serial ports.
254184610Salfred *
255188413Sthompsa * The mutex pointed to by "mtx" is applied before all
256188413Sthompsa * callbacks are called back. Also "mtx" must be applied
257188413Sthompsa * before calling into the ucom-layer!
258184610Salfred */
259184610Salfredint
260194228Sthompsaucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc,
261233774Shselasky    int subunits, void *parent,
262192984Sthompsa    const struct ucom_callback *callback, struct mtx *mtx)
263184610Salfred{
264233774Shselasky	int subunit;
265184610Salfred	int error = 0;
266184610Salfred
267184610Salfred	if ((sc == NULL) ||
268233774Shselasky	    (subunits <= 0) ||
269239179Shselasky	    (callback == NULL) ||
270239179Shselasky	    (mtx == NULL)) {
271184610Salfred		return (EINVAL);
272184610Salfred	}
273188413Sthompsa
274230209Shselasky	/* allocate a uniq unit number */
275214761Sn_hibma	ssc->sc_unit = ucom_unit_alloc();
276214761Sn_hibma	if (ssc->sc_unit == -1)
277184610Salfred		return (ENOMEM);
278188413Sthompsa
279230209Shselasky	/* generate TTY name string */
280230209Shselasky	snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname),
281230209Shselasky	    UCOM_TTY_PREFIX "%d", ssc->sc_unit);
282230209Shselasky
283230209Shselasky	/* create USB request handling process */
284194228Sthompsa	error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED);
285188413Sthompsa	if (error) {
286214761Sn_hibma		ucom_unit_free(ssc->sc_unit);
287188413Sthompsa		return (error);
288184610Salfred	}
289214761Sn_hibma	ssc->sc_subunits = subunits;
290239299Shselasky	ssc->sc_flag = UCOM_FLAG_ATTACHED |
291239299Shselasky	    UCOM_FLAG_FREE_UNIT;
292188413Sthompsa
293239299Shselasky	if (callback->ucom_free == NULL)
294239299Shselasky		ssc->sc_flag |= UCOM_FLAG_WAIT_REFS;
295239179Shselasky
296239299Shselasky	/* increment reference count */
297239299Shselasky	ucom_ref(ssc);
298239299Shselasky
299214831Sn_hibma	for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
300214761Sn_hibma		sc[subunit].sc_subunit = subunit;
301214761Sn_hibma		sc[subunit].sc_super = ssc;
302214761Sn_hibma		sc[subunit].sc_mtx = mtx;
303214761Sn_hibma		sc[subunit].sc_parent = parent;
304214761Sn_hibma		sc[subunit].sc_callback = callback;
305184610Salfred
306214761Sn_hibma		error = ucom_attach_tty(ssc, &sc[subunit]);
307184610Salfred		if (error) {
308214761Sn_hibma			ucom_detach(ssc, &sc[0]);
309188413Sthompsa			return (error);
310184610Salfred		}
311239179Shselasky		/* increment reference count */
312239179Shselasky		ucom_ref(ssc);
313239179Shselasky
314239179Shselasky		/* set subunit attached */
315214761Sn_hibma		sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED;
316184610Salfred	}
317214761Sn_hibma
318214831Sn_hibma	DPRINTF("tp = %p, unit = %d, subunits = %d\n",
319214831Sn_hibma		sc->sc_tty, ssc->sc_unit, ssc->sc_subunits);
320214761Sn_hibma
321188413Sthompsa	return (0);
322184610Salfred}
323184610Salfred
324188413Sthompsa/*
325239299Shselasky * The following function will do nothing if the structure pointed to
326239299Shselasky * by "ssc" and "sc" is zero or has already been detached.
327184610Salfred */
328184610Salfredvoid
329214761Sn_hibmaucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc)
330184610Salfred{
331233774Shselasky	int subunit;
332184610Salfred
333239299Shselasky	if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED))
334226219Shselasky		return;		/* not initialized */
335226219Shselasky
336230209Shselasky	if (ssc->sc_sysctl_ttyname != NULL) {
337230209Shselasky		sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0);
338230209Shselasky		ssc->sc_sysctl_ttyname = NULL;
339230204Shselasky	}
340230204Shselasky
341230204Shselasky	if (ssc->sc_sysctl_ttyports != NULL) {
342230204Shselasky		sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0);
343230204Shselasky		ssc->sc_sysctl_ttyports = NULL;
344230204Shselasky	}
345230204Shselasky
346194228Sthompsa	usb_proc_drain(&ssc->sc_tq);
347184610Salfred
348214831Sn_hibma	for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
349214761Sn_hibma		if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) {
350184610Salfred
351239179Shselasky			ucom_detach_tty(ssc, &sc[subunit]);
352184610Salfred
353214761Sn_hibma			/* avoid duplicate detach */
354214761Sn_hibma			sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED;
355184610Salfred		}
356184610Salfred	}
357194228Sthompsa	usb_proc_free(&ssc->sc_tq);
358239179Shselasky
359239299Shselasky	ucom_unref(ssc);
360239299Shselasky
361239299Shselasky	if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS)
362239179Shselasky		ucom_drain(ssc);
363239299Shselasky
364239299Shselasky	/* make sure we don't detach twice */
365239299Shselasky	ssc->sc_flag &= ~UCOM_FLAG_ATTACHED;
366184610Salfred}
367184610Salfred
368239179Shselaskyvoid
369239179Shselaskyucom_drain(struct ucom_super_softc *ssc)
370239179Shselasky{
371239179Shselasky	mtx_lock(&ucom_mtx);
372239299Shselasky	while (ssc->sc_refs > 0) {
373239179Shselasky		printf("ucom: Waiting for a TTY device to close.\n");
374239179Shselasky		usb_pause_mtx(&ucom_mtx, hz);
375239179Shselasky	}
376239179Shselasky	mtx_unlock(&ucom_mtx);
377239179Shselasky}
378239179Shselasky
379239179Shselaskyvoid
380239179Shselaskyucom_drain_all(void *arg)
381239179Shselasky{
382239179Shselasky	mtx_lock(&ucom_mtx);
383239179Shselasky	while (ucom_close_refs > 0) {
384239179Shselasky		printf("ucom: Waiting for all detached TTY "
385239179Shselasky		    "devices to have open fds closed.\n");
386239179Shselasky		usb_pause_mtx(&ucom_mtx, hz);
387239179Shselasky	}
388239179Shselasky	mtx_unlock(&ucom_mtx);
389239179Shselasky}
390239179Shselasky
391184610Salfredstatic int
392214761Sn_hibmaucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
393184610Salfred{
394184610Salfred	struct tty *tp;
395214831Sn_hibma	char buf[32];			/* temporary TTY device name buffer */
396184610Salfred
397194228Sthompsa	tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx);
398214761Sn_hibma	if (tp == NULL)
399214761Sn_hibma		return (ENOMEM);
400184610Salfred
401184610Salfred	/* Check if the client has a custom TTY name */
402214761Sn_hibma	buf[0] = '\0';
403194228Sthompsa	if (sc->sc_callback->ucom_tty_name) {
404194228Sthompsa		sc->sc_callback->ucom_tty_name(sc, buf,
405214761Sn_hibma		    sizeof(buf), ssc->sc_unit, sc->sc_subunit);
406184610Salfred	}
407184610Salfred	if (buf[0] == 0) {
408184610Salfred		/* Use default TTY name */
409214761Sn_hibma		if (ssc->sc_subunits > 1) {
410188413Sthompsa			/* multiple modems in one */
411214843Sn_hibma			snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u",
412214761Sn_hibma			    ssc->sc_unit, sc->sc_subunit);
413188413Sthompsa		} else {
414188413Sthompsa			/* single modem */
415214843Sn_hibma			snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u",
416214843Sn_hibma			    ssc->sc_unit);
417184610Salfred		}
418184610Salfred	}
419184610Salfred	tty_makedev(tp, NULL, "%s", buf);
420184610Salfred
421184610Salfred	sc->sc_tty = tp;
422184610Salfred
423283341Sian	sc->sc_pps.ppscap = PPS_CAPTUREBOTH;
424283341Sian	sc->sc_pps.driver_abi = PPS_ABI_VERSION;
425283341Sian	sc->sc_pps.driver_mtx = sc->sc_mtx;
426283341Sian	pps_init_abi(&sc->sc_pps);
427283341Sian
428184610Salfred	DPRINTF("ttycreate: %s\n", buf);
429184610Salfred
430197570Sthompsa	/* Check if this device should be a console */
431197570Sthompsa	if ((ucom_cons_softc == NULL) &&
432214761Sn_hibma	    (ssc->sc_unit == ucom_cons_unit) &&
433214761Sn_hibma	    (sc->sc_subunit == ucom_cons_subunit)) {
434197570Sthompsa
435242695Shselasky		DPRINTF("unit %d subunit %d is console",
436242695Shselasky		    ssc->sc_unit, sc->sc_subunit);
437214761Sn_hibma
438197570Sthompsa		ucom_cons_softc = sc;
439197570Sthompsa
440242695Shselasky		tty_init_console(tp, ucom_cons_baud);
441197570Sthompsa
442239179Shselasky		UCOM_MTX_LOCK(ucom_cons_softc);
443197570Sthompsa		ucom_cons_rx_low = 0;
444197570Sthompsa		ucom_cons_rx_high = 0;
445197570Sthompsa		ucom_cons_tx_low = 0;
446197570Sthompsa		ucom_cons_tx_high = 0;
447197570Sthompsa		sc->sc_flag |= UCOM_FLAG_CONSOLE;
448197570Sthompsa		ucom_open(ucom_cons_softc->sc_tty);
449242695Shselasky		ucom_param(ucom_cons_softc->sc_tty, &tp->t_termios_init_in);
450239179Shselasky		UCOM_MTX_UNLOCK(ucom_cons_softc);
451197570Sthompsa	}
452214761Sn_hibma
453214761Sn_hibma	return (0);
454184610Salfred}
455184610Salfred
456184610Salfredstatic void
457239179Shselaskyucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
458184610Salfred{
459184610Salfred	struct tty *tp = sc->sc_tty;
460184610Salfred
461184610Salfred	DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty);
462184610Salfred
463197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
464239179Shselasky		UCOM_MTX_LOCK(ucom_cons_softc);
465197570Sthompsa		ucom_close(ucom_cons_softc->sc_tty);
466214761Sn_hibma		sc->sc_flag &= ~UCOM_FLAG_CONSOLE;
467239179Shselasky		UCOM_MTX_UNLOCK(ucom_cons_softc);
468197570Sthompsa		ucom_cons_softc = NULL;
469197570Sthompsa	}
470197570Sthompsa
471184610Salfred	/* the config thread has been stopped when we get here */
472184610Salfred
473239179Shselasky	UCOM_MTX_LOCK(sc);
474184610Salfred	sc->sc_flag |= UCOM_FLAG_GONE;
475197570Sthompsa	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY);
476239179Shselasky	UCOM_MTX_UNLOCK(sc);
477239179Shselasky
478184610Salfred	if (tp) {
479239179Shselasky		mtx_lock(&ucom_mtx);
480239179Shselasky		ucom_close_refs++;
481239179Shselasky		mtx_unlock(&ucom_mtx);
482239179Shselasky
483184610Salfred		tty_lock(tp);
484184610Salfred
485194228Sthompsa		ucom_close(tp);	/* close, if any */
486184610Salfred
487184610Salfred		tty_rel_gone(tp);
488184610Salfred
489239179Shselasky		UCOM_MTX_LOCK(sc);
490184610Salfred		/*
491184610Salfred		 * make sure that read and write transfers are stopped
492184610Salfred		 */
493239179Shselasky		if (sc->sc_callback->ucom_stop_read)
494194228Sthompsa			(sc->sc_callback->ucom_stop_read) (sc);
495239179Shselasky		if (sc->sc_callback->ucom_stop_write)
496194228Sthompsa			(sc->sc_callback->ucom_stop_write) (sc);
497239179Shselasky		UCOM_MTX_UNLOCK(sc);
498184610Salfred	}
499184610Salfred}
500184610Salfred
501214843Sn_hibmavoid
502214843Sn_hibmaucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev)
503214843Sn_hibma{
504230204Shselasky	char buf[64];
505230204Shselasky	uint8_t iface_index;
506230204Shselasky	struct usb_attach_arg *uaa;
507214843Sn_hibma
508230209Shselasky	snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX
509230209Shselasky	    "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits);
510214843Sn_hibma
511230204Shselasky	/* Store the PNP info in the first interface for the device */
512230204Shselasky	uaa = device_get_ivars(dev);
513230204Shselasky	iface_index = uaa->info.bIfaceIndex;
514214843Sn_hibma
515230204Shselasky	if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0)
516230204Shselasky		device_printf(dev, "Could not set PNP info\n");
517230204Shselasky
518230204Shselasky	/*
519230209Shselasky	 * The following information is also replicated in the PNP-info
520230204Shselasky	 * string which is registered above:
521230204Shselasky	 */
522230209Shselasky	if (ssc->sc_sysctl_ttyname == NULL) {
523230209Shselasky		ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL,
524230204Shselasky		    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
525230209Shselasky		    OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0,
526230209Shselasky		    "TTY device basename");
527230204Shselasky	}
528230204Shselasky	if (ssc->sc_sysctl_ttyports == NULL) {
529230204Shselasky		ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL,
530230204Shselasky		    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
531230204Shselasky		    OID_AUTO, "ttyports", CTLFLAG_RD,
532230204Shselasky		    NULL, ssc->sc_subunits, "Number of ports");
533230204Shselasky	}
534214843Sn_hibma}
535214843Sn_hibma
536184610Salfredstatic void
537194228Sthompsaucom_queue_command(struct ucom_softc *sc,
538193045Sthompsa    usb_proc_callback_t *fn, struct termios *pt,
539192984Sthompsa    struct usb_proc_msg *t0, struct usb_proc_msg *t1)
540184610Salfred{
541192984Sthompsa	struct ucom_super_softc *ssc = sc->sc_super;
542192984Sthompsa	struct ucom_param_task *task;
543187176Sthompsa
544239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
545188413Sthompsa
546194228Sthompsa	if (usb_proc_is_gone(&ssc->sc_tq)) {
547187176Sthompsa		DPRINTF("proc is gone\n");
548188413Sthompsa		return;         /* nothing to do */
549187176Sthompsa	}
550188413Sthompsa	/*
551188413Sthompsa	 * NOTE: The task cannot get executed before we drop the
552188413Sthompsa	 * "sc_mtx" mutex. It is safe to update fields in the message
553188413Sthompsa	 * structure after that the message got queued.
554188413Sthompsa	 */
555192984Sthompsa	task = (struct ucom_param_task *)
556194228Sthompsa	  usb_proc_msignal(&ssc->sc_tq, t0, t1);
557187176Sthompsa
558188413Sthompsa	/* Setup callback and softc pointers */
559188413Sthompsa	task->hdr.pm_callback = fn;
560188413Sthompsa	task->sc = sc;
561184610Salfred
562188413Sthompsa	/*
563188413Sthompsa	 * Make a copy of the termios. This field is only present if
564188413Sthompsa	 * the "pt" field is not NULL.
565188413Sthompsa	 */
566188413Sthompsa	if (pt != NULL)
567188413Sthompsa		task->termios_copy = *pt;
568184610Salfred
569188413Sthompsa	/*
570188413Sthompsa	 * Closing the device should be synchronous.
571188413Sthompsa	 */
572194228Sthompsa	if (fn == ucom_cfg_close)
573194228Sthompsa		usb_proc_mwait(&ssc->sc_tq, t0, t1);
574188413Sthompsa
575190742Sthompsa	/*
576190742Sthompsa	 * In case of multiple configure requests,
577190742Sthompsa	 * keep track of the last one!
578190742Sthompsa	 */
579194228Sthompsa	if (fn == ucom_cfg_start_transfers)
580190742Sthompsa		sc->sc_last_start_xfer = &task->hdr;
581184610Salfred}
582184610Salfred
583184610Salfredstatic void
584194228Sthompsaucom_shutdown(struct ucom_softc *sc)
585184610Salfred{
586184610Salfred	struct tty *tp = sc->sc_tty;
587184610Salfred
588239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
589184610Salfred
590184610Salfred	DPRINTF("\n");
591184610Salfred
592184610Salfred	/*
593184610Salfred	 * Hang up if necessary:
594184610Salfred	 */
595184610Salfred	if (tp->t_termios.c_cflag & HUPCL) {
596194228Sthompsa		ucom_modem(tp, 0, SER_DTR);
597184610Salfred	}
598184610Salfred}
599184610Salfred
600184610Salfred/*
601184610Salfred * Return values:
602184610Salfred *    0: normal
603188413Sthompsa * else: taskqueue is draining or gone
604184610Salfred */
605184610Salfreduint8_t
606194228Sthompsaucom_cfg_is_gone(struct ucom_softc *sc)
607184610Salfred{
608192984Sthompsa	struct ucom_super_softc *ssc = sc->sc_super;
609184610Salfred
610194228Sthompsa	return (usb_proc_is_gone(&ssc->sc_tq));
611184610Salfred}
612184610Salfred
613184610Salfredstatic void
614194228Sthompsaucom_cfg_start_transfers(struct usb_proc_msg *_task)
615184610Salfred{
616192984Sthompsa	struct ucom_cfg_task *task =
617192984Sthompsa	    (struct ucom_cfg_task *)_task;
618192984Sthompsa	struct ucom_softc *sc = task->sc;
619187176Sthompsa
620184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
621184610Salfred		return;
622184610Salfred	}
623184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
624184610Salfred		/* TTY device closed */
625184610Salfred		return;
626184610Salfred	}
627184610Salfred
628190742Sthompsa	if (_task == sc->sc_last_start_xfer)
629190742Sthompsa		sc->sc_flag |= UCOM_FLAG_GP_DATA;
630190742Sthompsa
631194228Sthompsa	if (sc->sc_callback->ucom_start_read) {
632194228Sthompsa		(sc->sc_callback->ucom_start_read) (sc);
633184610Salfred	}
634194228Sthompsa	if (sc->sc_callback->ucom_start_write) {
635194228Sthompsa		(sc->sc_callback->ucom_start_write) (sc);
636184610Salfred	}
637184610Salfred}
638184610Salfred
639184610Salfredstatic void
640194228Sthompsaucom_start_transfers(struct ucom_softc *sc)
641184610Salfred{
642184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
643184610Salfred		return;
644184610Salfred	}
645184610Salfred	/*
646188413Sthompsa	 * Make sure that data transfers are started in both
647188413Sthompsa	 * directions:
648184610Salfred	 */
649194228Sthompsa	if (sc->sc_callback->ucom_start_read) {
650194228Sthompsa		(sc->sc_callback->ucom_start_read) (sc);
651184610Salfred	}
652194228Sthompsa	if (sc->sc_callback->ucom_start_write) {
653194228Sthompsa		(sc->sc_callback->ucom_start_write) (sc);
654184610Salfred	}
655184610Salfred}
656184610Salfred
657184610Salfredstatic void
658194228Sthompsaucom_cfg_open(struct usb_proc_msg *_task)
659184610Salfred{
660192984Sthompsa	struct ucom_cfg_task *task =
661192984Sthompsa	    (struct ucom_cfg_task *)_task;
662192984Sthompsa	struct ucom_softc *sc = task->sc;
663187176Sthompsa
664184610Salfred	DPRINTF("\n");
665184610Salfred
666184610Salfred	if (sc->sc_flag & UCOM_FLAG_LL_READY) {
667184610Salfred
668184610Salfred		/* already opened */
669184610Salfred
670184610Salfred	} else {
671184610Salfred
672184610Salfred		sc->sc_flag |= UCOM_FLAG_LL_READY;
673184610Salfred
674194228Sthompsa		if (sc->sc_callback->ucom_cfg_open) {
675194228Sthompsa			(sc->sc_callback->ucom_cfg_open) (sc);
676184610Salfred
677184610Salfred			/* wait a little */
678194228Sthompsa			usb_pause_mtx(sc->sc_mtx, hz / 10);
679184610Salfred		}
680184610Salfred	}
681184610Salfred}
682184610Salfred
683184610Salfredstatic int
684194228Sthompsaucom_open(struct tty *tp)
685184610Salfred{
686192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
687184610Salfred	int error;
688184610Salfred
689239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
690184610Salfred
691184610Salfred	if (sc->sc_flag & UCOM_FLAG_GONE) {
692184610Salfred		return (ENXIO);
693184610Salfred	}
694184610Salfred	if (sc->sc_flag & UCOM_FLAG_HL_READY) {
695184610Salfred		/* already opened */
696184610Salfred		return (0);
697184610Salfred	}
698184610Salfred	DPRINTF("tp = %p\n", tp);
699184610Salfred
700194228Sthompsa	if (sc->sc_callback->ucom_pre_open) {
701184610Salfred		/*
702184610Salfred		 * give the lower layer a chance to disallow TTY open, for
703184610Salfred		 * example if the device is not present:
704184610Salfred		 */
705194228Sthompsa		error = (sc->sc_callback->ucom_pre_open) (sc);
706184610Salfred		if (error) {
707184610Salfred			return (error);
708184610Salfred		}
709184610Salfred	}
710184610Salfred	sc->sc_flag |= UCOM_FLAG_HL_READY;
711184610Salfred
712184610Salfred	/* Disable transfers */
713184610Salfred	sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
714184610Salfred
715184610Salfred	sc->sc_lsr = 0;
716184610Salfred	sc->sc_msr = 0;
717184610Salfred	sc->sc_mcr = 0;
718184610Salfred
719188413Sthompsa	/* reset programmed line state */
720188413Sthompsa	sc->sc_pls_curr = 0;
721188413Sthompsa	sc->sc_pls_set = 0;
722188413Sthompsa	sc->sc_pls_clr = 0;
723184610Salfred
724242619Shselasky	/* reset jitter buffer */
725242619Shselasky	sc->sc_jitterbuf_in = 0;
726242619Shselasky	sc->sc_jitterbuf_out = 0;
727242619Shselasky
728194228Sthompsa	ucom_queue_command(sc, ucom_cfg_open, NULL,
729188413Sthompsa	    &sc->sc_open_task[0].hdr,
730188413Sthompsa	    &sc->sc_open_task[1].hdr);
731184610Salfred
732188413Sthompsa	/* Queue transfer enable command last */
733194228Sthompsa	ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
734188413Sthompsa	    &sc->sc_start_task[0].hdr,
735188413Sthompsa	    &sc->sc_start_task[1].hdr);
736188413Sthompsa
737194228Sthompsa	ucom_modem(tp, SER_DTR | SER_RTS, 0);
738184610Salfred
739197570Sthompsa	ucom_ring(sc, 0);
740197570Sthompsa
741194228Sthompsa	ucom_break(sc, 0);
742184610Salfred
743194228Sthompsa	ucom_status_change(sc);
744184610Salfred
745184610Salfred	return (0);
746184610Salfred}
747184610Salfred
748184610Salfredstatic void
749194228Sthompsaucom_cfg_close(struct usb_proc_msg *_task)
750184610Salfred{
751192984Sthompsa	struct ucom_cfg_task *task =
752192984Sthompsa	    (struct ucom_cfg_task *)_task;
753192984Sthompsa	struct ucom_softc *sc = task->sc;
754187176Sthompsa
755184610Salfred	DPRINTF("\n");
756184610Salfred
757184610Salfred	if (sc->sc_flag & UCOM_FLAG_LL_READY) {
758192820Sthompsa		sc->sc_flag &= ~UCOM_FLAG_LL_READY;
759194228Sthompsa		if (sc->sc_callback->ucom_cfg_close)
760194228Sthompsa			(sc->sc_callback->ucom_cfg_close) (sc);
761184610Salfred	} else {
762184610Salfred		/* already closed */
763184610Salfred	}
764184610Salfred}
765184610Salfred
766184610Salfredstatic void
767194228Sthompsaucom_close(struct tty *tp)
768184610Salfred{
769192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
770184610Salfred
771239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
772188413Sthompsa
773184610Salfred	DPRINTF("tp=%p\n", tp);
774184610Salfred
775184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
776184610Salfred		DPRINTF("tp=%p already closed\n", tp);
777184610Salfred		return;
778184610Salfred	}
779194228Sthompsa	ucom_shutdown(sc);
780184610Salfred
781194228Sthompsa	ucom_queue_command(sc, ucom_cfg_close, NULL,
782188413Sthompsa	    &sc->sc_close_task[0].hdr,
783188413Sthompsa	    &sc->sc_close_task[1].hdr);
784184610Salfred
785192820Sthompsa	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW);
786184610Salfred
787194228Sthompsa	if (sc->sc_callback->ucom_stop_read) {
788194228Sthompsa		(sc->sc_callback->ucom_stop_read) (sc);
789184610Salfred	}
790184610Salfred}
791184610Salfred
792242619Shselaskystatic void
793242619Shselaskyucom_inwakeup(struct tty *tp)
794242619Shselasky{
795242619Shselasky	struct ucom_softc *sc = tty_softc(tp);
796242619Shselasky	uint16_t pos;
797242619Shselasky
798242619Shselasky	if (sc == NULL)
799242619Shselasky		return;
800242619Shselasky
801242703Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
802242619Shselasky
803242703Shselasky	DPRINTF("tp=%p\n", tp);
804242702Shselasky
805242619Shselasky	if (ttydisc_can_bypass(tp) != 0 ||
806244489Shselasky	    (sc->sc_flag & UCOM_FLAG_HL_READY) == 0 ||
807244489Shselasky	    (sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) {
808242619Shselasky		return;
809242619Shselasky	}
810242619Shselasky
811244489Shselasky	/* prevent recursion */
812244489Shselasky	sc->sc_flag |= UCOM_FLAG_INWAKEUP;
813244489Shselasky
814242619Shselasky	pos = sc->sc_jitterbuf_out;
815242619Shselasky
816242619Shselasky	while (sc->sc_jitterbuf_in != pos) {
817242619Shselasky		int c;
818242619Shselasky
819242619Shselasky		c = (char)sc->sc_jitterbuf[pos];
820242619Shselasky
821242619Shselasky		if (ttydisc_rint(tp, c, 0) == -1)
822242619Shselasky			break;
823242619Shselasky		pos++;
824242619Shselasky		if (pos >= UCOM_JITTERBUF_SIZE)
825242619Shselasky			pos -= UCOM_JITTERBUF_SIZE;
826242619Shselasky	}
827242619Shselasky
828242619Shselasky	sc->sc_jitterbuf_out = pos;
829242619Shselasky
830242619Shselasky	/* clear RTS in async fashion */
831242619Shselasky	if ((sc->sc_jitterbuf_in == pos) &&
832242619Shselasky	    (sc->sc_flag & UCOM_FLAG_RTS_IFLOW))
833242619Shselasky		ucom_rts(sc, 0);
834244489Shselasky
835244489Shselasky	sc->sc_flag &= ~UCOM_FLAG_INWAKEUP;
836242619Shselasky}
837242619Shselasky
838184610Salfredstatic int
839194228Sthompsaucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
840184610Salfred{
841192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
842184610Salfred	int error;
843184610Salfred
844239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
845184610Salfred
846184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
847184610Salfred		return (EIO);
848184610Salfred	}
849184610Salfred	DPRINTF("cmd = 0x%08lx\n", cmd);
850184610Salfred
851184610Salfred	switch (cmd) {
852197570Sthompsa#if 0
853197570Sthompsa	case TIOCSRING:
854197570Sthompsa		ucom_ring(sc, 1);
855197570Sthompsa		error = 0;
856197570Sthompsa		break;
857197570Sthompsa	case TIOCCRING:
858197570Sthompsa		ucom_ring(sc, 0);
859197570Sthompsa		error = 0;
860197570Sthompsa		break;
861197570Sthompsa#endif
862184610Salfred	case TIOCSBRK:
863194228Sthompsa		ucom_break(sc, 1);
864184610Salfred		error = 0;
865184610Salfred		break;
866184610Salfred	case TIOCCBRK:
867194228Sthompsa		ucom_break(sc, 0);
868184610Salfred		error = 0;
869184610Salfred		break;
870184610Salfred	default:
871194228Sthompsa		if (sc->sc_callback->ucom_ioctl) {
872194228Sthompsa			error = (sc->sc_callback->ucom_ioctl)
873184610Salfred			    (sc, cmd, data, 0, td);
874184610Salfred		} else {
875184610Salfred			error = ENOIOCTL;
876184610Salfred		}
877283341Sian		if (error == ENOIOCTL)
878283341Sian			error = pps_ioctl(cmd, data, &sc->sc_pps);
879184610Salfred		break;
880184610Salfred	}
881184610Salfred	return (error);
882184610Salfred}
883184610Salfred
884184610Salfredstatic int
885194228Sthompsaucom_modem(struct tty *tp, int sigon, int sigoff)
886184610Salfred{
887192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
888184610Salfred	uint8_t onoff;
889184610Salfred
890239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
891184610Salfred
892184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
893184610Salfred		return (0);
894184610Salfred	}
895184610Salfred	if ((sigon == 0) && (sigoff == 0)) {
896184610Salfred
897184610Salfred		if (sc->sc_mcr & SER_DTR) {
898184610Salfred			sigon |= SER_DTR;
899184610Salfred		}
900184610Salfred		if (sc->sc_mcr & SER_RTS) {
901184610Salfred			sigon |= SER_RTS;
902184610Salfred		}
903184610Salfred		if (sc->sc_msr & SER_CTS) {
904184610Salfred			sigon |= SER_CTS;
905184610Salfred		}
906184610Salfred		if (sc->sc_msr & SER_DCD) {
907184610Salfred			sigon |= SER_DCD;
908184610Salfred		}
909184610Salfred		if (sc->sc_msr & SER_DSR) {
910184610Salfred			sigon |= SER_DSR;
911184610Salfred		}
912184610Salfred		if (sc->sc_msr & SER_RI) {
913184610Salfred			sigon |= SER_RI;
914184610Salfred		}
915184610Salfred		return (sigon);
916184610Salfred	}
917184610Salfred	if (sigon & SER_DTR) {
918184610Salfred		sc->sc_mcr |= SER_DTR;
919184610Salfred	}
920184610Salfred	if (sigoff & SER_DTR) {
921184610Salfred		sc->sc_mcr &= ~SER_DTR;
922184610Salfred	}
923184610Salfred	if (sigon & SER_RTS) {
924184610Salfred		sc->sc_mcr |= SER_RTS;
925184610Salfred	}
926184610Salfred	if (sigoff & SER_RTS) {
927184610Salfred		sc->sc_mcr &= ~SER_RTS;
928184610Salfred	}
929184610Salfred	onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0;
930194228Sthompsa	ucom_dtr(sc, onoff);
931184610Salfred
932184610Salfred	onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0;
933194228Sthompsa	ucom_rts(sc, onoff);
934184610Salfred
935184610Salfred	return (0);
936184610Salfred}
937184610Salfred
938184610Salfredstatic void
939194228Sthompsaucom_cfg_line_state(struct usb_proc_msg *_task)
940184610Salfred{
941192984Sthompsa	struct ucom_cfg_task *task =
942192984Sthompsa	    (struct ucom_cfg_task *)_task;
943192984Sthompsa	struct ucom_softc *sc = task->sc;
944188413Sthompsa	uint8_t notch_bits;
945188413Sthompsa	uint8_t any_bits;
946188413Sthompsa	uint8_t prev_value;
947188413Sthompsa	uint8_t last_value;
948188413Sthompsa	uint8_t mask;
949187176Sthompsa
950184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
951184610Salfred		return;
952184610Salfred	}
953184610Salfred
954188413Sthompsa	mask = 0;
955188413Sthompsa	/* compute callback mask */
956194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_dtr)
957188413Sthompsa		mask |= UCOM_LS_DTR;
958194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_rts)
959188413Sthompsa		mask |= UCOM_LS_RTS;
960194228Sthompsa	if (sc->sc_callback->ucom_cfg_set_break)
961188413Sthompsa		mask |= UCOM_LS_BREAK;
962197570Sthompsa	if (sc->sc_callback->ucom_cfg_set_ring)
963197570Sthompsa		mask |= UCOM_LS_RING;
964184610Salfred
965188413Sthompsa	/* compute the bits we are to program */
966188413Sthompsa	notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask;
967188413Sthompsa	any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask;
968188413Sthompsa	prev_value = sc->sc_pls_curr ^ notch_bits;
969188413Sthompsa	last_value = sc->sc_pls_curr;
970187176Sthompsa
971188413Sthompsa	/* reset programmed line state */
972188413Sthompsa	sc->sc_pls_curr = 0;
973188413Sthompsa	sc->sc_pls_set = 0;
974188413Sthompsa	sc->sc_pls_clr = 0;
975188413Sthompsa
976250576Seadler	/* ensure that we don't lose any levels */
977188413Sthompsa	if (notch_bits & UCOM_LS_DTR)
978194228Sthompsa		sc->sc_callback->ucom_cfg_set_dtr(sc,
979188413Sthompsa		    (prev_value & UCOM_LS_DTR) ? 1 : 0);
980188413Sthompsa	if (notch_bits & UCOM_LS_RTS)
981194228Sthompsa		sc->sc_callback->ucom_cfg_set_rts(sc,
982188413Sthompsa		    (prev_value & UCOM_LS_RTS) ? 1 : 0);
983188413Sthompsa	if (notch_bits & UCOM_LS_BREAK)
984194228Sthompsa		sc->sc_callback->ucom_cfg_set_break(sc,
985188413Sthompsa		    (prev_value & UCOM_LS_BREAK) ? 1 : 0);
986197570Sthompsa	if (notch_bits & UCOM_LS_RING)
987197570Sthompsa		sc->sc_callback->ucom_cfg_set_ring(sc,
988197570Sthompsa		    (prev_value & UCOM_LS_RING) ? 1 : 0);
989188413Sthompsa
990188413Sthompsa	/* set last value */
991188413Sthompsa	if (any_bits & UCOM_LS_DTR)
992194228Sthompsa		sc->sc_callback->ucom_cfg_set_dtr(sc,
993188413Sthompsa		    (last_value & UCOM_LS_DTR) ? 1 : 0);
994188413Sthompsa	if (any_bits & UCOM_LS_RTS)
995194228Sthompsa		sc->sc_callback->ucom_cfg_set_rts(sc,
996188413Sthompsa		    (last_value & UCOM_LS_RTS) ? 1 : 0);
997188413Sthompsa	if (any_bits & UCOM_LS_BREAK)
998194228Sthompsa		sc->sc_callback->ucom_cfg_set_break(sc,
999188413Sthompsa		    (last_value & UCOM_LS_BREAK) ? 1 : 0);
1000197570Sthompsa	if (any_bits & UCOM_LS_RING)
1001197570Sthompsa		sc->sc_callback->ucom_cfg_set_ring(sc,
1002197570Sthompsa		    (last_value & UCOM_LS_RING) ? 1 : 0);
1003187176Sthompsa}
1004187176Sthompsa
1005187176Sthompsastatic void
1006194228Sthompsaucom_line_state(struct ucom_softc *sc,
1007188413Sthompsa    uint8_t set_bits, uint8_t clear_bits)
1008184610Salfred{
1009239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1010184610Salfred
1011184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1012184610Salfred		return;
1013184610Salfred	}
1014184610Salfred
1015188413Sthompsa	DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits);
1016184610Salfred
1017188413Sthompsa	/* update current programmed line state */
1018188413Sthompsa	sc->sc_pls_curr |= set_bits;
1019188413Sthompsa	sc->sc_pls_curr &= ~clear_bits;
1020188413Sthompsa	sc->sc_pls_set |= set_bits;
1021188413Sthompsa	sc->sc_pls_clr |= clear_bits;
1022187176Sthompsa
1023188413Sthompsa	/* defer driver programming */
1024194228Sthompsa	ucom_queue_command(sc, ucom_cfg_line_state, NULL,
1025188413Sthompsa	    &sc->sc_line_state_task[0].hdr,
1026188413Sthompsa	    &sc->sc_line_state_task[1].hdr);
1027184610Salfred}
1028184610Salfred
1029184610Salfredstatic void
1030197570Sthompsaucom_ring(struct ucom_softc *sc, uint8_t onoff)
1031197570Sthompsa{
1032197570Sthompsa	DPRINTF("onoff = %d\n", onoff);
1033197570Sthompsa
1034197570Sthompsa	if (onoff)
1035197570Sthompsa		ucom_line_state(sc, UCOM_LS_RING, 0);
1036197570Sthompsa	else
1037197570Sthompsa		ucom_line_state(sc, 0, UCOM_LS_RING);
1038197570Sthompsa}
1039197570Sthompsa
1040197570Sthompsastatic void
1041194228Sthompsaucom_break(struct ucom_softc *sc, uint8_t onoff)
1042187176Sthompsa{
1043188413Sthompsa	DPRINTF("onoff = %d\n", onoff);
1044187176Sthompsa
1045188413Sthompsa	if (onoff)
1046194228Sthompsa		ucom_line_state(sc, UCOM_LS_BREAK, 0);
1047188413Sthompsa	else
1048194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_BREAK);
1049187176Sthompsa}
1050187176Sthompsa
1051187176Sthompsastatic void
1052194228Sthompsaucom_dtr(struct ucom_softc *sc, uint8_t onoff)
1053184610Salfred{
1054184610Salfred	DPRINTF("onoff = %d\n", onoff);
1055184610Salfred
1056188413Sthompsa	if (onoff)
1057194228Sthompsa		ucom_line_state(sc, UCOM_LS_DTR, 0);
1058188413Sthompsa	else
1059194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_DTR);
1060184610Salfred}
1061184610Salfred
1062184610Salfredstatic void
1063194228Sthompsaucom_rts(struct ucom_softc *sc, uint8_t onoff)
1064184610Salfred{
1065184610Salfred	DPRINTF("onoff = %d\n", onoff);
1066184610Salfred
1067188413Sthompsa	if (onoff)
1068194228Sthompsa		ucom_line_state(sc, UCOM_LS_RTS, 0);
1069188413Sthompsa	else
1070194228Sthompsa		ucom_line_state(sc, 0, UCOM_LS_RTS);
1071184610Salfred}
1072184610Salfred
1073184610Salfredstatic void
1074194228Sthompsaucom_cfg_status_change(struct usb_proc_msg *_task)
1075184610Salfred{
1076192984Sthompsa	struct ucom_cfg_task *task =
1077192984Sthompsa	    (struct ucom_cfg_task *)_task;
1078192984Sthompsa	struct ucom_softc *sc = task->sc;
1079184610Salfred	struct tty *tp;
1080294637Sian	int onoff;
1081184610Salfred	uint8_t new_msr;
1082184610Salfred	uint8_t new_lsr;
1083283341Sian	uint8_t msr_delta;
1084213872Shselasky	uint8_t lsr_delta;
1085294637Sian	uint8_t pps_signal;
1086184610Salfred
1087184610Salfred	tp = sc->sc_tty;
1088184610Salfred
1089239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1090184610Salfred
1091184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
1092184610Salfred		return;
1093184610Salfred	}
1094194228Sthompsa	if (sc->sc_callback->ucom_cfg_get_status == NULL) {
1095184610Salfred		return;
1096184610Salfred	}
1097184610Salfred	/* get status */
1098184610Salfred
1099184610Salfred	new_msr = 0;
1100184610Salfred	new_lsr = 0;
1101184610Salfred
1102194228Sthompsa	(sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr);
1103184610Salfred
1104184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1105184610Salfred		/* TTY device closed */
1106184610Salfred		return;
1107184610Salfred	}
1108283341Sian	msr_delta = (sc->sc_msr ^ new_msr);
1109213872Shselasky	lsr_delta = (sc->sc_lsr ^ new_lsr);
1110184610Salfred
1111184610Salfred	sc->sc_msr = new_msr;
1112184610Salfred	sc->sc_lsr = new_lsr;
1113184610Salfred
1114283341Sian	/*
1115294637Sian	 * Time pulse counting support.
1116283341Sian	 */
1117294637Sian	switch(ucom_pps_mode & UART_PPS_SIGNAL_MASK) {
1118294637Sian	case UART_PPS_CTS:
1119294637Sian		pps_signal = SER_CTS;
1120283341Sian		break;
1121294637Sian	case UART_PPS_DCD:
1122294637Sian		pps_signal = SER_DCD;
1123283341Sian		break;
1124283341Sian	default:
1125294637Sian		pps_signal = 0;
1126283341Sian		break;
1127283341Sian	}
1128184610Salfred
1129294637Sian	if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) &&
1130294637Sian	    (msr_delta & pps_signal)) {
1131294637Sian		pps_capture(&sc->sc_pps);
1132294637Sian		onoff = (sc->sc_msr & pps_signal) ? 1 : 0;
1133294637Sian		if (ucom_pps_mode & UART_PPS_INVERT_PULSE)
1134294637Sian			onoff = !onoff;
1135294637Sian		pps_event(&sc->sc_pps, onoff ? PPS_CAPTUREASSERT :
1136294637Sian		    PPS_CAPTURECLEAR);
1137294637Sian	}
1138294637Sian
1139283341Sian	if (msr_delta & SER_DCD) {
1140184610Salfred
1141294637Sian		onoff = (sc->sc_msr & SER_DCD) ? 1 : 0;
1142283341Sian
1143184610Salfred		DPRINTF("DCD changed to %d\n", onoff);
1144184610Salfred
1145184610Salfred		ttydisc_modem(tp, onoff);
1146184610Salfred	}
1147213872Shselasky
1148213872Shselasky	if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) {
1149213872Shselasky
1150213872Shselasky		DPRINTF("BREAK detected\n");
1151213872Shselasky
1152213872Shselasky		ttydisc_rint(tp, 0, TRE_BREAK);
1153213872Shselasky		ttydisc_rint_done(tp);
1154213872Shselasky	}
1155213872Shselasky
1156213872Shselasky	if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) {
1157213872Shselasky
1158213872Shselasky		DPRINTF("Frame error detected\n");
1159213872Shselasky
1160213872Shselasky		ttydisc_rint(tp, 0, TRE_FRAMING);
1161213872Shselasky		ttydisc_rint_done(tp);
1162213872Shselasky	}
1163213872Shselasky
1164213872Shselasky	if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) {
1165213872Shselasky
1166213872Shselasky		DPRINTF("Parity error detected\n");
1167213872Shselasky
1168213872Shselasky		ttydisc_rint(tp, 0, TRE_PARITY);
1169213872Shselasky		ttydisc_rint_done(tp);
1170213872Shselasky	}
1171184610Salfred}
1172184610Salfred
1173184610Salfredvoid
1174194228Sthompsaucom_status_change(struct ucom_softc *sc)
1175184610Salfred{
1176239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1177184610Salfred
1178197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE)
1179197570Sthompsa		return;		/* not supported */
1180197570Sthompsa
1181184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1182184610Salfred		return;
1183184610Salfred	}
1184184610Salfred	DPRINTF("\n");
1185184610Salfred
1186194228Sthompsa	ucom_queue_command(sc, ucom_cfg_status_change, NULL,
1187188413Sthompsa	    &sc->sc_status_task[0].hdr,
1188188413Sthompsa	    &sc->sc_status_task[1].hdr);
1189184610Salfred}
1190184610Salfred
1191184610Salfredstatic void
1192194228Sthompsaucom_cfg_param(struct usb_proc_msg *_task)
1193184610Salfred{
1194192984Sthompsa	struct ucom_param_task *task =
1195192984Sthompsa	    (struct ucom_param_task *)_task;
1196192984Sthompsa	struct ucom_softc *sc = task->sc;
1197184610Salfred
1198184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
1199184610Salfred		return;
1200184610Salfred	}
1201194228Sthompsa	if (sc->sc_callback->ucom_cfg_param == NULL) {
1202184610Salfred		return;
1203184610Salfred	}
1204184610Salfred
1205194228Sthompsa	(sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy);
1206184610Salfred
1207184610Salfred	/* wait a little */
1208194228Sthompsa	usb_pause_mtx(sc->sc_mtx, hz / 10);
1209184610Salfred}
1210184610Salfred
1211184610Salfredstatic int
1212194228Sthompsaucom_param(struct tty *tp, struct termios *t)
1213184610Salfred{
1214192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
1215184610Salfred	uint8_t opened;
1216184610Salfred	int error;
1217184610Salfred
1218239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1219184610Salfred
1220184610Salfred	opened = 0;
1221184610Salfred	error = 0;
1222184610Salfred
1223184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1224184610Salfred
1225184610Salfred		/* XXX the TTY layer should call "open()" first! */
1226242695Shselasky		/*
1227242695Shselasky		 * Not quite: Its ordering is partly backwards, but
1228242695Shselasky		 * some parameters must be set early in ttydev_open(),
1229242695Shselasky		 * possibly before calling ttydevsw_open().
1230242695Shselasky		 */
1231194228Sthompsa		error = ucom_open(tp);
1232242695Shselasky		if (error)
1233184610Salfred			goto done;
1234242695Shselasky
1235184610Salfred		opened = 1;
1236184610Salfred	}
1237184610Salfred	DPRINTF("sc = %p\n", sc);
1238184610Salfred
1239184610Salfred	/* Check requested parameters. */
1240184610Salfred	if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) {
1241242695Shselasky		/* XXX c_ospeed == 0 is perfectly valid. */
1242184610Salfred		DPRINTF("mismatch ispeed and ospeed\n");
1243184610Salfred		error = EINVAL;
1244184610Salfred		goto done;
1245184610Salfred	}
1246184610Salfred	t->c_ispeed = t->c_ospeed;
1247184610Salfred
1248194228Sthompsa	if (sc->sc_callback->ucom_pre_param) {
1249184610Salfred		/* Let the lower layer verify the parameters */
1250194228Sthompsa		error = (sc->sc_callback->ucom_pre_param) (sc, t);
1251184610Salfred		if (error) {
1252184610Salfred			DPRINTF("callback error = %d\n", error);
1253184610Salfred			goto done;
1254184610Salfred		}
1255184610Salfred	}
1256184610Salfred
1257184610Salfred	/* Disable transfers */
1258184610Salfred	sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
1259184610Salfred
1260184610Salfred	/* Queue baud rate programming command first */
1261194228Sthompsa	ucom_queue_command(sc, ucom_cfg_param, t,
1262188413Sthompsa	    &sc->sc_param_task[0].hdr,
1263188413Sthompsa	    &sc->sc_param_task[1].hdr);
1264184610Salfred
1265184610Salfred	/* Queue transfer enable command last */
1266194228Sthompsa	ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
1267188413Sthompsa	    &sc->sc_start_task[0].hdr,
1268188413Sthompsa	    &sc->sc_start_task[1].hdr);
1269184610Salfred
1270184610Salfred	if (t->c_cflag & CRTS_IFLOW) {
1271184610Salfred		sc->sc_flag |= UCOM_FLAG_RTS_IFLOW;
1272184610Salfred	} else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) {
1273184610Salfred		sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW;
1274194228Sthompsa		ucom_modem(tp, SER_RTS, 0);
1275184610Salfred	}
1276184610Salfreddone:
1277184610Salfred	if (error) {
1278184610Salfred		if (opened) {
1279194228Sthompsa			ucom_close(tp);
1280184610Salfred		}
1281184610Salfred	}
1282184610Salfred	return (error);
1283184610Salfred}
1284184610Salfred
1285184610Salfredstatic void
1286194228Sthompsaucom_outwakeup(struct tty *tp)
1287184610Salfred{
1288192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
1289184610Salfred
1290239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1291184610Salfred
1292184610Salfred	DPRINTF("sc = %p\n", sc);
1293184610Salfred
1294184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
1295184610Salfred		/* The higher layer is not ready */
1296184610Salfred		return;
1297184610Salfred	}
1298194228Sthompsa	ucom_start_transfers(sc);
1299184610Salfred}
1300184610Salfred
1301184610Salfred/*------------------------------------------------------------------------*
1302194228Sthompsa *	ucom_get_data
1303184610Salfred *
1304184610Salfred * Return values:
1305184610Salfred * 0: No data is available.
1306184610Salfred * Else: Data is available.
1307184610Salfred *------------------------------------------------------------------------*/
1308184610Salfreduint8_t
1309194228Sthompsaucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc,
1310184610Salfred    uint32_t offset, uint32_t len, uint32_t *actlen)
1311184610Salfred{
1312192984Sthompsa	struct usb_page_search res;
1313184610Salfred	struct tty *tp = sc->sc_tty;
1314184610Salfred	uint32_t cnt;
1315184610Salfred	uint32_t offset_orig;
1316184610Salfred
1317239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1318184610Salfred
1319197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
1320197570Sthompsa		unsigned int temp;
1321197570Sthompsa
1322197570Sthompsa		/* get total TX length */
1323197570Sthompsa
1324197570Sthompsa		temp = ucom_cons_tx_high - ucom_cons_tx_low;
1325197570Sthompsa		temp %= UCOM_CONS_BUFSIZE;
1326197570Sthompsa
1327197570Sthompsa		/* limit TX length */
1328197570Sthompsa
1329197570Sthompsa		if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low))
1330197570Sthompsa			temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low);
1331197570Sthompsa
1332197570Sthompsa		if (temp > len)
1333197570Sthompsa			temp = len;
1334197570Sthompsa
1335197570Sthompsa		/* copy in data */
1336197570Sthompsa
1337197570Sthompsa		usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp);
1338197570Sthompsa
1339197570Sthompsa		/* update counters */
1340197570Sthompsa
1341197570Sthompsa		ucom_cons_tx_low += temp;
1342197570Sthompsa		ucom_cons_tx_low %= UCOM_CONS_BUFSIZE;
1343197570Sthompsa
1344197570Sthompsa		/* store actual length */
1345197570Sthompsa
1346197570Sthompsa		*actlen = temp;
1347197570Sthompsa
1348197570Sthompsa		return (temp ? 1 : 0);
1349197570Sthompsa	}
1350197570Sthompsa
1351192820Sthompsa	if (tty_gone(tp) ||
1352192820Sthompsa	    !(sc->sc_flag & UCOM_FLAG_GP_DATA)) {
1353184610Salfred		actlen[0] = 0;
1354184610Salfred		return (0);		/* multiport device polling */
1355184610Salfred	}
1356184610Salfred	offset_orig = offset;
1357184610Salfred
1358184610Salfred	while (len != 0) {
1359184610Salfred
1360194228Sthompsa		usbd_get_page(pc, offset, &res);
1361184610Salfred
1362184610Salfred		if (res.length > len) {
1363184610Salfred			res.length = len;
1364184610Salfred		}
1365184610Salfred		/* copy data directly into USB buffer */
1366184610Salfred		cnt = ttydisc_getc(tp, res.buffer, res.length);
1367184610Salfred
1368184610Salfred		offset += cnt;
1369184610Salfred		len -= cnt;
1370184610Salfred
1371184610Salfred		if (cnt < res.length) {
1372184610Salfred			/* end of buffer */
1373184610Salfred			break;
1374184610Salfred		}
1375184610Salfred	}
1376184610Salfred
1377184610Salfred	actlen[0] = offset - offset_orig;
1378184610Salfred
1379184610Salfred	DPRINTF("cnt=%d\n", actlen[0]);
1380184610Salfred
1381184610Salfred	if (actlen[0] == 0) {
1382184610Salfred		return (0);
1383184610Salfred	}
1384184610Salfred	return (1);
1385184610Salfred}
1386184610Salfred
1387184610Salfredvoid
1388194228Sthompsaucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc,
1389184610Salfred    uint32_t offset, uint32_t len)
1390184610Salfred{
1391192984Sthompsa	struct usb_page_search res;
1392184610Salfred	struct tty *tp = sc->sc_tty;
1393184610Salfred	char *buf;
1394184610Salfred	uint32_t cnt;
1395184610Salfred
1396239179Shselasky	UCOM_MTX_ASSERT(sc, MA_OWNED);
1397184610Salfred
1398197570Sthompsa	if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
1399197570Sthompsa		unsigned int temp;
1400197570Sthompsa
1401197570Sthompsa		/* get maximum RX length */
1402197570Sthompsa
1403197570Sthompsa		temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low;
1404197570Sthompsa		temp %= UCOM_CONS_BUFSIZE;
1405197570Sthompsa
1406197570Sthompsa		/* limit RX length */
1407197570Sthompsa
1408197570Sthompsa		if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high))
1409197570Sthompsa			temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high);
1410197570Sthompsa
1411197570Sthompsa		if (temp > len)
1412197570Sthompsa			temp = len;
1413197570Sthompsa
1414197570Sthompsa		/* copy out data */
1415197570Sthompsa
1416197570Sthompsa		usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp);
1417197570Sthompsa
1418197570Sthompsa		/* update counters */
1419197570Sthompsa
1420197570Sthompsa		ucom_cons_rx_high += temp;
1421197570Sthompsa		ucom_cons_rx_high %= UCOM_CONS_BUFSIZE;
1422197570Sthompsa
1423197570Sthompsa		return;
1424197570Sthompsa	}
1425197570Sthompsa
1426192820Sthompsa	if (tty_gone(tp))
1427184610Salfred		return;			/* multiport device polling */
1428192820Sthompsa
1429184610Salfred	if (len == 0)
1430184610Salfred		return;			/* no data */
1431184610Salfred
1432184610Salfred	/* set a flag to prevent recursation ? */
1433184610Salfred
1434184610Salfred	while (len > 0) {
1435184610Salfred
1436194228Sthompsa		usbd_get_page(pc, offset, &res);
1437184610Salfred
1438184610Salfred		if (res.length > len) {
1439184610Salfred			res.length = len;
1440184610Salfred		}
1441184610Salfred		len -= res.length;
1442184610Salfred		offset += res.length;
1443184610Salfred
1444184610Salfred		/* pass characters to tty layer */
1445184610Salfred
1446184610Salfred		buf = res.buffer;
1447184610Salfred		cnt = res.length;
1448184610Salfred
1449184610Salfred		/* first check if we can pass the buffer directly */
1450184610Salfred
1451184610Salfred		if (ttydisc_can_bypass(tp)) {
1452242619Shselasky
1453242619Shselasky			/* clear any jitter buffer */
1454242619Shselasky			sc->sc_jitterbuf_in = 0;
1455242619Shselasky			sc->sc_jitterbuf_out = 0;
1456242619Shselasky
1457184610Salfred			if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) {
1458184610Salfred				DPRINTF("tp=%p, data lost\n", tp);
1459184610Salfred			}
1460184610Salfred			continue;
1461184610Salfred		}
1462184610Salfred		/* need to loop */
1463184610Salfred
1464184610Salfred		for (cnt = 0; cnt != res.length; cnt++) {
1465242619Shselasky			if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out ||
1466242619Shselasky			    ttydisc_rint(tp, buf[cnt], 0) == -1) {
1467242619Shselasky				uint16_t end;
1468242619Shselasky				uint16_t pos;
1469184610Salfred
1470242619Shselasky				pos = sc->sc_jitterbuf_in;
1471242619Shselasky				end = sc->sc_jitterbuf_out +
1472242619Shselasky				    UCOM_JITTERBUF_SIZE - 1;
1473242619Shselasky				if (end >= UCOM_JITTERBUF_SIZE)
1474242619Shselasky					end -= UCOM_JITTERBUF_SIZE;
1475242619Shselasky
1476242619Shselasky				for (; cnt != res.length; cnt++) {
1477242619Shselasky					if (pos == end)
1478242619Shselasky						break;
1479242619Shselasky					sc->sc_jitterbuf[pos] = buf[cnt];
1480242619Shselasky					pos++;
1481242619Shselasky					if (pos >= UCOM_JITTERBUF_SIZE)
1482242619Shselasky						pos -= UCOM_JITTERBUF_SIZE;
1483242619Shselasky				}
1484242619Shselasky
1485242619Shselasky				sc->sc_jitterbuf_in = pos;
1486242619Shselasky
1487242619Shselasky				/* set RTS in async fashion */
1488242619Shselasky				if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)
1489242619Shselasky					ucom_rts(sc, 1);
1490242619Shselasky
1491184610Salfred				DPRINTF("tp=%p, lost %d "
1492184610Salfred				    "chars\n", tp, res.length - cnt);
1493184610Salfred				break;
1494184610Salfred			}
1495184610Salfred		}
1496184610Salfred	}
1497184610Salfred	ttydisc_rint_done(tp);
1498184610Salfred}
1499184610Salfred
1500184610Salfredstatic void
1501194228Sthompsaucom_free(void *xsc)
1502184610Salfred{
1503192984Sthompsa	struct ucom_softc *sc = xsc;
1504184610Salfred
1505239179Shselasky	if (sc->sc_callback->ucom_free != NULL)
1506239179Shselasky		sc->sc_callback->ucom_free(sc);
1507239179Shselasky	else
1508239179Shselasky		ucom_unref(sc->sc_super);
1509239179Shselasky
1510239179Shselasky	mtx_lock(&ucom_mtx);
1511239179Shselasky	ucom_close_refs--;
1512239179Shselasky	mtx_unlock(&ucom_mtx);
1513184610Salfred}
1514197570Sthompsa
1515197570Sthompsastatic cn_probe_t ucom_cnprobe;
1516197570Sthompsastatic cn_init_t ucom_cninit;
1517197570Sthompsastatic cn_term_t ucom_cnterm;
1518197570Sthompsastatic cn_getc_t ucom_cngetc;
1519197570Sthompsastatic cn_putc_t ucom_cnputc;
1520228631Savgstatic cn_grab_t ucom_cngrab;
1521228631Savgstatic cn_ungrab_t ucom_cnungrab;
1522197570Sthompsa
1523197570SthompsaCONSOLE_DRIVER(ucom);
1524197570Sthompsa
1525197570Sthompsastatic void
1526197570Sthompsaucom_cnprobe(struct consdev  *cp)
1527197570Sthompsa{
1528198774Sthompsa	if (ucom_cons_unit != -1)
1529198774Sthompsa		cp->cn_pri = CN_NORMAL;
1530198774Sthompsa	else
1531198774Sthompsa		cp->cn_pri = CN_DEAD;
1532198774Sthompsa
1533198774Sthompsa	strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name));
1534197570Sthompsa}
1535197570Sthompsa
1536197570Sthompsastatic void
1537197570Sthompsaucom_cninit(struct consdev  *cp)
1538197570Sthompsa{
1539197570Sthompsa}
1540197570Sthompsa
1541197570Sthompsastatic void
1542197570Sthompsaucom_cnterm(struct consdev  *cp)
1543197570Sthompsa{
1544197570Sthompsa}
1545197570Sthompsa
1546228631Savgstatic void
1547228631Savgucom_cngrab(struct consdev *cp)
1548228631Savg{
1549228631Savg}
1550228631Savg
1551228631Savgstatic void
1552228631Savgucom_cnungrab(struct consdev *cp)
1553228631Savg{
1554228631Savg}
1555228631Savg
1556197570Sthompsastatic int
1557197570Sthompsaucom_cngetc(struct consdev *cd)
1558197570Sthompsa{
1559197570Sthompsa	struct ucom_softc *sc = ucom_cons_softc;
1560197570Sthompsa	int c;
1561197570Sthompsa
1562197570Sthompsa	if (sc == NULL)
1563197570Sthompsa		return (-1);
1564197570Sthompsa
1565239179Shselasky	UCOM_MTX_LOCK(sc);
1566197570Sthompsa
1567197570Sthompsa	if (ucom_cons_rx_low != ucom_cons_rx_high) {
1568197570Sthompsa		c = ucom_cons_rx_buf[ucom_cons_rx_low];
1569197570Sthompsa		ucom_cons_rx_low ++;
1570197570Sthompsa		ucom_cons_rx_low %= UCOM_CONS_BUFSIZE;
1571197570Sthompsa	} else {
1572197570Sthompsa		c = -1;
1573197570Sthompsa	}
1574197570Sthompsa
1575197570Sthompsa	/* start USB transfers */
1576197570Sthompsa	ucom_outwakeup(sc->sc_tty);
1577197570Sthompsa
1578239179Shselasky	UCOM_MTX_UNLOCK(sc);
1579197570Sthompsa
1580197570Sthompsa	/* poll if necessary */
1581197570Sthompsa	if (kdb_active && sc->sc_callback->ucom_poll)
1582197570Sthompsa		(sc->sc_callback->ucom_poll) (sc);
1583197570Sthompsa
1584197570Sthompsa	return (c);
1585197570Sthompsa}
1586197570Sthompsa
1587197570Sthompsastatic void
1588197570Sthompsaucom_cnputc(struct consdev *cd, int c)
1589197570Sthompsa{
1590197570Sthompsa	struct ucom_softc *sc = ucom_cons_softc;
1591197570Sthompsa	unsigned int temp;
1592197570Sthompsa
1593197570Sthompsa	if (sc == NULL)
1594197570Sthompsa		return;
1595197570Sthompsa
1596197570Sthompsa repeat:
1597197570Sthompsa
1598239179Shselasky	UCOM_MTX_LOCK(sc);
1599197570Sthompsa
1600197570Sthompsa	/* compute maximum TX length */
1601197570Sthompsa
1602197570Sthompsa	temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low;
1603197570Sthompsa	temp %= UCOM_CONS_BUFSIZE;
1604197570Sthompsa
1605197570Sthompsa	if (temp) {
1606197570Sthompsa		ucom_cons_tx_buf[ucom_cons_tx_high] = c;
1607197570Sthompsa		ucom_cons_tx_high ++;
1608197570Sthompsa		ucom_cons_tx_high %= UCOM_CONS_BUFSIZE;
1609197570Sthompsa	}
1610197570Sthompsa
1611197570Sthompsa	/* start USB transfers */
1612197570Sthompsa	ucom_outwakeup(sc->sc_tty);
1613197570Sthompsa
1614239179Shselasky	UCOM_MTX_UNLOCK(sc);
1615197570Sthompsa
1616197570Sthompsa	/* poll if necessary */
1617197570Sthompsa	if (kdb_active && sc->sc_callback->ucom_poll) {
1618197570Sthompsa		(sc->sc_callback->ucom_poll) (sc);
1619197570Sthompsa		/* simple flow control */
1620197570Sthompsa		if (temp == 0)
1621197570Sthompsa			goto repeat;
1622197570Sthompsa	}
1623197570Sthompsa}
1624197570Sthompsa
1625239179Shselasky/*------------------------------------------------------------------------*
1626239179Shselasky *	ucom_ref
1627239179Shselasky *
1628239179Shselasky * This function will increment the super UCOM reference count.
1629239179Shselasky *------------------------------------------------------------------------*/
1630239179Shselaskyvoid
1631239179Shselaskyucom_ref(struct ucom_super_softc *ssc)
1632239179Shselasky{
1633239179Shselasky	mtx_lock(&ucom_mtx);
1634239179Shselasky	ssc->sc_refs++;
1635239179Shselasky	mtx_unlock(&ucom_mtx);
1636239179Shselasky}
1637239179Shselasky
1638239179Shselasky/*------------------------------------------------------------------------*
1639239299Shselasky *	ucom_free_unit
1640239299Shselasky *
1641239299Shselasky * This function will free the super UCOM's allocated unit
1642239299Shselasky * number. This function can be called on a zero-initialized
1643239299Shselasky * structure. This function can be called multiple times.
1644239299Shselasky *------------------------------------------------------------------------*/
1645239299Shselaskystatic void
1646239299Shselaskyucom_free_unit(struct ucom_super_softc *ssc)
1647239299Shselasky{
1648239299Shselasky	if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT))
1649239299Shselasky		return;
1650239299Shselasky
1651239299Shselasky	ucom_unit_free(ssc->sc_unit);
1652239299Shselasky
1653239299Shselasky	ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT;
1654239299Shselasky}
1655239299Shselasky
1656239299Shselasky/*------------------------------------------------------------------------*
1657239179Shselasky *	ucom_unref
1658239179Shselasky *
1659239179Shselasky * This function will decrement the super UCOM reference count.
1660239179Shselasky *
1661239179Shselasky * Return values:
1662239179Shselasky * 0: UCOM structures are still referenced.
1663239179Shselasky * Else: UCOM structures are no longer referenced.
1664239179Shselasky *------------------------------------------------------------------------*/
1665239179Shselaskyint
1666239179Shselaskyucom_unref(struct ucom_super_softc *ssc)
1667239179Shselasky{
1668239179Shselasky	int retval;
1669239179Shselasky
1670239179Shselasky	mtx_lock(&ucom_mtx);
1671239179Shselasky	retval = (ssc->sc_refs < 2);
1672239179Shselasky	ssc->sc_refs--;
1673239179Shselasky	mtx_unlock(&ucom_mtx);
1674239179Shselasky
1675239299Shselasky	if (retval)
1676239299Shselasky		ucom_free_unit(ssc);
1677239299Shselasky
1678239179Shselasky	return (retval);
1679239179Shselasky}
1680239179Shselasky
1681197570Sthompsa#if defined(GDB)
1682197570Sthompsa
1683197570Sthompsa#include <gdb/gdb.h>
1684197570Sthompsa
1685197570Sthompsastatic gdb_probe_f ucom_gdbprobe;
1686197570Sthompsastatic gdb_init_f ucom_gdbinit;
1687197570Sthompsastatic gdb_term_f ucom_gdbterm;
1688197570Sthompsastatic gdb_getc_f ucom_gdbgetc;
1689197570Sthompsastatic gdb_putc_f ucom_gdbputc;
1690197570Sthompsa
1691197570SthompsaGDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc);
1692197570Sthompsa
1693197570Sthompsastatic int
1694197570Sthompsaucom_gdbprobe(void)
1695197570Sthompsa{
1696197570Sthompsa	return ((ucom_cons_softc != NULL) ? 0 : -1);
1697197570Sthompsa}
1698197570Sthompsa
1699197570Sthompsastatic void
1700197570Sthompsaucom_gdbinit(void)
1701197570Sthompsa{
1702197570Sthompsa}
1703197570Sthompsa
1704197570Sthompsastatic void
1705197570Sthompsaucom_gdbterm(void)
1706197570Sthompsa{
1707197570Sthompsa}
1708197570Sthompsa
1709197570Sthompsastatic void
1710197570Sthompsaucom_gdbputc(int c)
1711197570Sthompsa{
1712197570Sthompsa        ucom_cnputc(NULL, c);
1713197570Sthompsa}
1714197570Sthompsa
1715197570Sthompsastatic int
1716197570Sthompsaucom_gdbgetc(void)
1717197570Sthompsa{
1718197570Sthompsa        return (ucom_cngetc(NULL));
1719197570Sthompsa}
1720197570Sthompsa
1721197570Sthompsa#endif
1722