usb_serial.c revision 193045
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 193045 2009-05-29 18:46:57Z thompsa $");
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
70188942Sthompsa#include <dev/usb/usb.h>
71188942Sthompsa#include <dev/usb/usb_mfunc.h>
72188942Sthompsa#include <dev/usb/usb_error.h>
73188942Sthompsa#include <dev/usb/usb_cdc.h>
74188942Sthompsa#include <dev/usb/usb_ioctl.h>
75184610Salfred
76184610Salfred#define	USB_DEBUG_VAR usb2_com_debug
77184610Salfred
78188942Sthompsa#include <dev/usb/usb_core.h>
79188942Sthompsa#include <dev/usb/usb_debug.h>
80188942Sthompsa#include <dev/usb/usb_process.h>
81188942Sthompsa#include <dev/usb/usb_request.h>
82188942Sthompsa#include <dev/usb/usb_busdma.h>
83188942Sthompsa#include <dev/usb/usb_util.h>
84184610Salfred
85188942Sthompsa#include <dev/usb/serial/usb_serial.h>
86184610Salfred
87184610Salfred#if USB_DEBUG
88184610Salfredstatic int usb2_com_debug = 0;
89184610Salfred
90192502SthompsaSYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom");
91192502SthompsaSYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW,
92184610Salfred    &usb2_com_debug, 0, "ucom debug level");
93184610Salfred#endif
94184610Salfred
95193045Sthompsastatic usb_proc_callback_t usb2_com_cfg_start_transfers;
96193045Sthompsastatic usb_proc_callback_t usb2_com_cfg_open;
97193045Sthompsastatic usb_proc_callback_t usb2_com_cfg_close;
98193045Sthompsastatic usb_proc_callback_t usb2_com_cfg_line_state;
99193045Sthompsastatic usb_proc_callback_t usb2_com_cfg_status_change;
100193045Sthompsastatic usb_proc_callback_t usb2_com_cfg_param;
101184610Salfred
102185948Sthompsastatic uint8_t	usb2_com_units_alloc(uint32_t, uint32_t *);
103185948Sthompsastatic void	usb2_com_units_free(uint32_t, uint32_t);
104192984Sthompsastatic int	usb2_com_attach_tty(struct ucom_softc *, uint32_t);
105192984Sthompsastatic void	usb2_com_detach_tty(struct ucom_softc *);
106192984Sthompsastatic void	usb2_com_queue_command(struct ucom_softc *,
107193045Sthompsa		    usb_proc_callback_t *, struct termios *pt,
108192984Sthompsa		    struct usb_proc_msg *t0, struct usb_proc_msg *t1);
109192984Sthompsastatic void	usb2_com_shutdown(struct ucom_softc *);
110192984Sthompsastatic void	usb2_com_break(struct ucom_softc *, uint8_t);
111192984Sthompsastatic void	usb2_com_dtr(struct ucom_softc *, uint8_t);
112192984Sthompsastatic void	usb2_com_rts(struct ucom_softc *, uint8_t);
113184610Salfred
114184610Salfredstatic tsw_open_t usb2_com_open;
115184610Salfredstatic tsw_close_t usb2_com_close;
116184610Salfredstatic tsw_ioctl_t usb2_com_ioctl;
117184610Salfredstatic tsw_modem_t usb2_com_modem;
118184610Salfredstatic tsw_param_t usb2_com_param;
119188413Sthompsastatic tsw_outwakeup_t usb2_com_outwakeup;
120184610Salfredstatic tsw_free_t usb2_com_free;
121184610Salfred
122184610Salfredstatic struct ttydevsw usb2_com_class = {
123184610Salfred	.tsw_flags = TF_INITLOCK | TF_CALLOUT,
124184610Salfred	.tsw_open = usb2_com_open,
125184610Salfred	.tsw_close = usb2_com_close,
126188413Sthompsa	.tsw_outwakeup = usb2_com_outwakeup,
127184610Salfred	.tsw_ioctl = usb2_com_ioctl,
128184610Salfred	.tsw_param = usb2_com_param,
129184610Salfred	.tsw_modem = usb2_com_modem,
130184610Salfred	.tsw_free = usb2_com_free,
131184610Salfred};
132184610Salfred
133188942SthompsaMODULE_DEPEND(ucom, usb, 1, 1, 1);
134188942SthompsaMODULE_VERSION(ucom, 1);
135184610Salfred
136184610Salfred#define	UCOM_UNIT_MAX 0x1000		/* exclusive */
137184610Salfred#define	UCOM_SUB_UNIT_MAX 0x100		/* exclusive */
138184610Salfred
139184610Salfredstatic uint8_t usb2_com_bitmap[(UCOM_UNIT_MAX + 7) / 8];
140184610Salfred
141184610Salfredstatic uint8_t
142184610Salfredusb2_com_units_alloc(uint32_t sub_units, uint32_t *p_root_unit)
143184610Salfred{
144184610Salfred	uint32_t n;
145184610Salfred	uint32_t o;
146184610Salfred	uint32_t x;
147184610Salfred	uint32_t max = UCOM_UNIT_MAX - (UCOM_UNIT_MAX % sub_units);
148184610Salfred	uint8_t error = 1;
149184610Salfred
150184610Salfred	mtx_lock(&Giant);
151184610Salfred
152184610Salfred	for (n = 0; n < max; n += sub_units) {
153184610Salfred
154184610Salfred		/* check for free consecutive bits */
155184610Salfred
156184610Salfred		for (o = 0; o < sub_units; o++) {
157184610Salfred
158184610Salfred			x = n + o;
159184610Salfred
160184610Salfred			if (usb2_com_bitmap[x / 8] & (1 << (x % 8))) {
161184610Salfred				goto skip;
162184610Salfred			}
163184610Salfred		}
164184610Salfred
165184610Salfred		/* allocate */
166184610Salfred
167184610Salfred		for (o = 0; o < sub_units; o++) {
168184610Salfred
169184610Salfred			x = n + o;
170184610Salfred
171184610Salfred			usb2_com_bitmap[x / 8] |= (1 << (x % 8));
172184610Salfred		}
173184610Salfred
174184610Salfred		error = 0;
175184610Salfred
176184610Salfred		break;
177184610Salfred
178184610Salfredskip:		;
179184610Salfred	}
180184610Salfred
181184610Salfred	mtx_unlock(&Giant);
182184610Salfred
183184610Salfred	/*
184184610Salfred	 * Always set the variable pointed to by "p_root_unit" so that
185184610Salfred	 * the compiler does not think that it is used uninitialised:
186184610Salfred	 */
187184610Salfred	*p_root_unit = n;
188184610Salfred
189184610Salfred	return (error);
190184610Salfred}
191184610Salfred
192184610Salfredstatic void
193184610Salfredusb2_com_units_free(uint32_t root_unit, uint32_t sub_units)
194184610Salfred{
195184610Salfred	uint32_t x;
196184610Salfred
197184610Salfred	mtx_lock(&Giant);
198184610Salfred
199184610Salfred	while (sub_units--) {
200184610Salfred		x = root_unit + sub_units;
201184610Salfred		usb2_com_bitmap[x / 8] &= ~(1 << (x % 8));
202184610Salfred	}
203184610Salfred
204184610Salfred	mtx_unlock(&Giant);
205184610Salfred}
206184610Salfred
207184610Salfred/*
208184610Salfred * "N" sub_units are setup at a time. All sub-units will
209184610Salfred * be given sequential unit numbers. The number of
210184610Salfred * sub-units can be used to differentiate among
211184610Salfred * different types of devices.
212184610Salfred *
213188413Sthompsa * The mutex pointed to by "mtx" is applied before all
214188413Sthompsa * callbacks are called back. Also "mtx" must be applied
215188413Sthompsa * before calling into the ucom-layer!
216184610Salfred */
217184610Salfredint
218192984Sthompsausb2_com_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc,
219184610Salfred    uint32_t sub_units, void *parent,
220192984Sthompsa    const struct ucom_callback *callback, struct mtx *mtx)
221184610Salfred{
222184610Salfred	uint32_t n;
223184610Salfred	uint32_t root_unit;
224184610Salfred	int error = 0;
225184610Salfred
226184610Salfred	if ((sc == NULL) ||
227184610Salfred	    (sub_units == 0) ||
228184610Salfred	    (sub_units > UCOM_SUB_UNIT_MAX) ||
229184610Salfred	    (callback == NULL)) {
230184610Salfred		return (EINVAL);
231184610Salfred	}
232188413Sthompsa
233188413Sthompsa	/* XXX unit management does not really belong here */
234184610Salfred	if (usb2_com_units_alloc(sub_units, &root_unit)) {
235184610Salfred		return (ENOMEM);
236184610Salfred	}
237188413Sthompsa
238188413Sthompsa	error = usb2_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED);
239188413Sthompsa	if (error) {
240184610Salfred		usb2_com_units_free(root_unit, sub_units);
241188413Sthompsa		return (error);
242184610Salfred	}
243188413Sthompsa
244188413Sthompsa	for (n = 0; n != sub_units; n++, sc++) {
245184610Salfred		sc->sc_unit = root_unit + n;
246184610Salfred		sc->sc_local_unit = n;
247184610Salfred		sc->sc_super = ssc;
248188413Sthompsa		sc->sc_mtx = mtx;
249184610Salfred		sc->sc_parent = parent;
250184610Salfred		sc->sc_callback = callback;
251184610Salfred
252188413Sthompsa		error = usb2_com_attach_tty(sc, sub_units);
253184610Salfred		if (error) {
254184610Salfred			usb2_com_detach(ssc, sc - n, n);
255184610Salfred			usb2_com_units_free(root_unit + n, sub_units - n);
256188413Sthompsa			return (error);
257184610Salfred		}
258184610Salfred		sc->sc_flag |= UCOM_FLAG_ATTACHED;
259184610Salfred	}
260188413Sthompsa	return (0);
261184610Salfred}
262184610Salfred
263188413Sthompsa/*
264188413Sthompsa * NOTE: the following function will do nothing if
265184610Salfred * the structure pointed to by "ssc" and "sc" is zero.
266184610Salfred */
267184610Salfredvoid
268192984Sthompsausb2_com_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc,
269184610Salfred    uint32_t sub_units)
270184610Salfred{
271184610Salfred	uint32_t n;
272184610Salfred
273188413Sthompsa	usb2_proc_drain(&ssc->sc_tq);
274184610Salfred
275188413Sthompsa	for (n = 0; n != sub_units; n++, sc++) {
276184610Salfred		if (sc->sc_flag & UCOM_FLAG_ATTACHED) {
277184610Salfred
278188413Sthompsa			usb2_com_detach_tty(sc);
279184610Salfred
280184610Salfred			usb2_com_units_free(sc->sc_unit, 1);
281184610Salfred
282184610Salfred			/* avoid duplicate detach: */
283184610Salfred			sc->sc_flag &= ~UCOM_FLAG_ATTACHED;
284184610Salfred		}
285184610Salfred	}
286188413Sthompsa	usb2_proc_free(&ssc->sc_tq);
287184610Salfred}
288184610Salfred
289184610Salfredstatic int
290192984Sthompsausb2_com_attach_tty(struct ucom_softc *sc, uint32_t sub_units)
291184610Salfred{
292184610Salfred	struct tty *tp;
293184610Salfred	int error = 0;
294184610Salfred	char buf[32];			/* temporary TTY device name buffer */
295184610Salfred
296193018Sed	tp = tty_alloc_mutex(&usb2_com_class, sc, sc->sc_mtx);
297184610Salfred	if (tp == NULL) {
298184610Salfred		error = ENOMEM;
299184610Salfred		goto done;
300184610Salfred	}
301184610Salfred	DPRINTF("tp = %p, unit = %d\n", tp, sc->sc_unit);
302184610Salfred
303184610Salfred	buf[0] = 0;			/* set some default value */
304184610Salfred
305184610Salfred	/* Check if the client has a custom TTY name */
306184610Salfred	if (sc->sc_callback->usb2_com_tty_name) {
307184610Salfred		sc->sc_callback->usb2_com_tty_name(sc, buf,
308184610Salfred		    sizeof(buf), sc->sc_local_unit);
309184610Salfred	}
310184610Salfred	if (buf[0] == 0) {
311184610Salfred		/* Use default TTY name */
312188413Sthompsa		if (sub_units > 1) {
313188413Sthompsa			/* multiple modems in one */
314188413Sthompsa			if (snprintf(buf, sizeof(buf), "U%u.%u",
315188413Sthompsa			    sc->sc_unit - sc->sc_local_unit,
316188413Sthompsa			    sc->sc_local_unit)) {
317188413Sthompsa				/* ignore */
318188413Sthompsa			}
319188413Sthompsa		} else {
320188413Sthompsa			/* single modem */
321188413Sthompsa			if (snprintf(buf, sizeof(buf), "U%u", sc->sc_unit)) {
322188413Sthompsa				/* ignore */
323188413Sthompsa			}
324184610Salfred		}
325184610Salfred	}
326184610Salfred	tty_makedev(tp, NULL, "%s", buf);
327184610Salfred
328184610Salfred	sc->sc_tty = tp;
329184610Salfred
330184610Salfred	DPRINTF("ttycreate: %s\n", buf);
331184610Salfred	usb2_cv_init(&sc->sc_cv, "usb2_com");
332184610Salfred
333184610Salfreddone:
334184610Salfred	return (error);
335184610Salfred}
336184610Salfred
337184610Salfredstatic void
338192984Sthompsausb2_com_detach_tty(struct ucom_softc *sc)
339184610Salfred{
340184610Salfred	struct tty *tp = sc->sc_tty;
341184610Salfred
342184610Salfred	DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty);
343184610Salfred
344184610Salfred	/* the config thread has been stopped when we get here */
345184610Salfred
346188413Sthompsa	mtx_lock(sc->sc_mtx);
347184610Salfred	sc->sc_flag |= UCOM_FLAG_GONE;
348184610Salfred	sc->sc_flag &= ~(UCOM_FLAG_HL_READY |
349184610Salfred	    UCOM_FLAG_LL_READY);
350188413Sthompsa	mtx_unlock(sc->sc_mtx);
351184610Salfred	if (tp) {
352184610Salfred		tty_lock(tp);
353184610Salfred
354184610Salfred		usb2_com_close(tp);	/* close, if any */
355184610Salfred
356184610Salfred		tty_rel_gone(tp);
357184610Salfred
358188413Sthompsa		mtx_lock(sc->sc_mtx);
359184610Salfred		/* Wait for the callback after the TTY is torn down */
360184610Salfred		while (sc->sc_ttyfreed == 0)
361188413Sthompsa			usb2_cv_wait(&sc->sc_cv, sc->sc_mtx);
362184610Salfred		/*
363184610Salfred		 * make sure that read and write transfers are stopped
364184610Salfred		 */
365184610Salfred		if (sc->sc_callback->usb2_com_stop_read) {
366184610Salfred			(sc->sc_callback->usb2_com_stop_read) (sc);
367184610Salfred		}
368184610Salfred		if (sc->sc_callback->usb2_com_stop_write) {
369184610Salfred			(sc->sc_callback->usb2_com_stop_write) (sc);
370184610Salfred		}
371188413Sthompsa		mtx_unlock(sc->sc_mtx);
372184610Salfred	}
373184610Salfred	usb2_cv_destroy(&sc->sc_cv);
374184610Salfred}
375184610Salfred
376184610Salfredstatic void
377192984Sthompsausb2_com_queue_command(struct ucom_softc *sc,
378193045Sthompsa    usb_proc_callback_t *fn, struct termios *pt,
379192984Sthompsa    struct usb_proc_msg *t0, struct usb_proc_msg *t1)
380184610Salfred{
381192984Sthompsa	struct ucom_super_softc *ssc = sc->sc_super;
382192984Sthompsa	struct ucom_param_task *task;
383187176Sthompsa
384188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
385188413Sthompsa
386188413Sthompsa	if (usb2_proc_is_gone(&ssc->sc_tq)) {
387187176Sthompsa		DPRINTF("proc is gone\n");
388188413Sthompsa		return;         /* nothing to do */
389187176Sthompsa	}
390188413Sthompsa	/*
391188413Sthompsa	 * NOTE: The task cannot get executed before we drop the
392188413Sthompsa	 * "sc_mtx" mutex. It is safe to update fields in the message
393188413Sthompsa	 * structure after that the message got queued.
394188413Sthompsa	 */
395192984Sthompsa	task = (struct ucom_param_task *)
396188413Sthompsa	  usb2_proc_msignal(&ssc->sc_tq, t0, t1);
397187176Sthompsa
398188413Sthompsa	/* Setup callback and softc pointers */
399188413Sthompsa	task->hdr.pm_callback = fn;
400188413Sthompsa	task->sc = sc;
401184610Salfred
402188413Sthompsa	/*
403188413Sthompsa	 * Make a copy of the termios. This field is only present if
404188413Sthompsa	 * the "pt" field is not NULL.
405188413Sthompsa	 */
406188413Sthompsa	if (pt != NULL)
407188413Sthompsa		task->termios_copy = *pt;
408184610Salfred
409188413Sthompsa	/*
410188413Sthompsa	 * Closing the device should be synchronous.
411188413Sthompsa	 */
412188413Sthompsa	if (fn == usb2_com_cfg_close)
413188413Sthompsa		usb2_proc_mwait(&ssc->sc_tq, t0, t1);
414188413Sthompsa
415190742Sthompsa	/*
416190742Sthompsa	 * In case of multiple configure requests,
417190742Sthompsa	 * keep track of the last one!
418190742Sthompsa	 */
419190742Sthompsa	if (fn == usb2_com_cfg_start_transfers)
420190742Sthompsa		sc->sc_last_start_xfer = &task->hdr;
421184610Salfred}
422184610Salfred
423184610Salfredstatic void
424192984Sthompsausb2_com_shutdown(struct ucom_softc *sc)
425184610Salfred{
426184610Salfred	struct tty *tp = sc->sc_tty;
427184610Salfred
428188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
429184610Salfred
430184610Salfred	DPRINTF("\n");
431184610Salfred
432184610Salfred	/*
433184610Salfred	 * Hang up if necessary:
434184610Salfred	 */
435184610Salfred	if (tp->t_termios.c_cflag & HUPCL) {
436184610Salfred		usb2_com_modem(tp, 0, SER_DTR);
437184610Salfred	}
438184610Salfred}
439184610Salfred
440184610Salfred/*
441184610Salfred * Return values:
442184610Salfred *    0: normal
443188413Sthompsa * else: taskqueue is draining or gone
444184610Salfred */
445184610Salfreduint8_t
446192984Sthompsausb2_com_cfg_is_gone(struct ucom_softc *sc)
447184610Salfred{
448192984Sthompsa	struct ucom_super_softc *ssc = sc->sc_super;
449184610Salfred
450188413Sthompsa	return (usb2_proc_is_gone(&ssc->sc_tq));
451184610Salfred}
452184610Salfred
453184610Salfredstatic void
454192984Sthompsausb2_com_cfg_start_transfers(struct usb_proc_msg *_task)
455184610Salfred{
456192984Sthompsa	struct ucom_cfg_task *task =
457192984Sthompsa	    (struct ucom_cfg_task *)_task;
458192984Sthompsa	struct ucom_softc *sc = task->sc;
459187176Sthompsa
460184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
461184610Salfred		return;
462184610Salfred	}
463184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
464184610Salfred		/* TTY device closed */
465184610Salfred		return;
466184610Salfred	}
467184610Salfred
468190742Sthompsa	if (_task == sc->sc_last_start_xfer)
469190742Sthompsa		sc->sc_flag |= UCOM_FLAG_GP_DATA;
470190742Sthompsa
471184610Salfred	if (sc->sc_callback->usb2_com_start_read) {
472184610Salfred		(sc->sc_callback->usb2_com_start_read) (sc);
473184610Salfred	}
474184610Salfred	if (sc->sc_callback->usb2_com_start_write) {
475184610Salfred		(sc->sc_callback->usb2_com_start_write) (sc);
476184610Salfred	}
477184610Salfred}
478184610Salfred
479184610Salfredstatic void
480192984Sthompsausb2_com_start_transfers(struct ucom_softc *sc)
481184610Salfred{
482184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
483184610Salfred		return;
484184610Salfred	}
485184610Salfred	/*
486188413Sthompsa	 * Make sure that data transfers are started in both
487188413Sthompsa	 * directions:
488184610Salfred	 */
489184610Salfred	if (sc->sc_callback->usb2_com_start_read) {
490184610Salfred		(sc->sc_callback->usb2_com_start_read) (sc);
491184610Salfred	}
492184610Salfred	if (sc->sc_callback->usb2_com_start_write) {
493184610Salfred		(sc->sc_callback->usb2_com_start_write) (sc);
494184610Salfred	}
495184610Salfred}
496184610Salfred
497184610Salfredstatic void
498192984Sthompsausb2_com_cfg_open(struct usb_proc_msg *_task)
499184610Salfred{
500192984Sthompsa	struct ucom_cfg_task *task =
501192984Sthompsa	    (struct ucom_cfg_task *)_task;
502192984Sthompsa	struct ucom_softc *sc = task->sc;
503187176Sthompsa
504184610Salfred	DPRINTF("\n");
505184610Salfred
506184610Salfred	if (sc->sc_flag & UCOM_FLAG_LL_READY) {
507184610Salfred
508184610Salfred		/* already opened */
509184610Salfred
510184610Salfred	} else {
511184610Salfred
512184610Salfred		sc->sc_flag |= UCOM_FLAG_LL_READY;
513184610Salfred
514184610Salfred		if (sc->sc_callback->usb2_com_cfg_open) {
515184610Salfred			(sc->sc_callback->usb2_com_cfg_open) (sc);
516184610Salfred
517184610Salfred			/* wait a little */
518188413Sthompsa			usb2_pause_mtx(sc->sc_mtx, hz / 10);
519184610Salfred		}
520184610Salfred	}
521184610Salfred}
522184610Salfred
523184610Salfredstatic int
524184610Salfredusb2_com_open(struct tty *tp)
525184610Salfred{
526192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
527184610Salfred	int error;
528184610Salfred
529188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
530184610Salfred
531184610Salfred	if (sc->sc_flag & UCOM_FLAG_GONE) {
532184610Salfred		return (ENXIO);
533184610Salfred	}
534184610Salfred	if (sc->sc_flag & UCOM_FLAG_HL_READY) {
535184610Salfred		/* already opened */
536184610Salfred		return (0);
537184610Salfred	}
538184610Salfred	DPRINTF("tp = %p\n", tp);
539184610Salfred
540184610Salfred	if (sc->sc_callback->usb2_com_pre_open) {
541184610Salfred		/*
542184610Salfred		 * give the lower layer a chance to disallow TTY open, for
543184610Salfred		 * example if the device is not present:
544184610Salfred		 */
545184610Salfred		error = (sc->sc_callback->usb2_com_pre_open) (sc);
546184610Salfred		if (error) {
547184610Salfred			return (error);
548184610Salfred		}
549184610Salfred	}
550184610Salfred	sc->sc_flag |= UCOM_FLAG_HL_READY;
551184610Salfred
552184610Salfred	/* Disable transfers */
553184610Salfred	sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
554184610Salfred
555184610Salfred	sc->sc_lsr = 0;
556184610Salfred	sc->sc_msr = 0;
557184610Salfred	sc->sc_mcr = 0;
558184610Salfred
559188413Sthompsa	/* reset programmed line state */
560188413Sthompsa	sc->sc_pls_curr = 0;
561188413Sthompsa	sc->sc_pls_set = 0;
562188413Sthompsa	sc->sc_pls_clr = 0;
563184610Salfred
564188413Sthompsa	usb2_com_queue_command(sc, usb2_com_cfg_open, NULL,
565188413Sthompsa	    &sc->sc_open_task[0].hdr,
566188413Sthompsa	    &sc->sc_open_task[1].hdr);
567184610Salfred
568188413Sthompsa	/* Queue transfer enable command last */
569188413Sthompsa	usb2_com_queue_command(sc, usb2_com_cfg_start_transfers, NULL,
570188413Sthompsa	    &sc->sc_start_task[0].hdr,
571188413Sthompsa	    &sc->sc_start_task[1].hdr);
572188413Sthompsa
573184610Salfred	usb2_com_modem(tp, SER_DTR | SER_RTS, 0);
574184610Salfred
575184610Salfred	usb2_com_break(sc, 0);
576184610Salfred
577184610Salfred	usb2_com_status_change(sc);
578184610Salfred
579184610Salfred	return (0);
580184610Salfred}
581184610Salfred
582184610Salfredstatic void
583192984Sthompsausb2_com_cfg_close(struct usb_proc_msg *_task)
584184610Salfred{
585192984Sthompsa	struct ucom_cfg_task *task =
586192984Sthompsa	    (struct ucom_cfg_task *)_task;
587192984Sthompsa	struct ucom_softc *sc = task->sc;
588187176Sthompsa
589184610Salfred	DPRINTF("\n");
590184610Salfred
591184610Salfred	if (sc->sc_flag & UCOM_FLAG_LL_READY) {
592192820Sthompsa		sc->sc_flag &= ~UCOM_FLAG_LL_READY;
593192820Sthompsa		if (sc->sc_callback->usb2_com_cfg_close)
594184610Salfred			(sc->sc_callback->usb2_com_cfg_close) (sc);
595184610Salfred	} else {
596184610Salfred		/* already closed */
597184610Salfred	}
598184610Salfred}
599184610Salfred
600184610Salfredstatic void
601184610Salfredusb2_com_close(struct tty *tp)
602184610Salfred{
603192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
604184610Salfred
605188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
606188413Sthompsa
607184610Salfred	DPRINTF("tp=%p\n", tp);
608184610Salfred
609184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
610184610Salfred		DPRINTF("tp=%p already closed\n", tp);
611184610Salfred		return;
612184610Salfred	}
613184610Salfred	usb2_com_shutdown(sc);
614184610Salfred
615188413Sthompsa	usb2_com_queue_command(sc, usb2_com_cfg_close, NULL,
616188413Sthompsa	    &sc->sc_close_task[0].hdr,
617188413Sthompsa	    &sc->sc_close_task[1].hdr);
618184610Salfred
619192820Sthompsa	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW);
620184610Salfred
621184610Salfred	if (sc->sc_callback->usb2_com_stop_read) {
622184610Salfred		(sc->sc_callback->usb2_com_stop_read) (sc);
623184610Salfred	}
624184610Salfred}
625184610Salfred
626184610Salfredstatic int
627184610Salfredusb2_com_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
628184610Salfred{
629192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
630184610Salfred	int error;
631184610Salfred
632188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
633184610Salfred
634184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
635184610Salfred		return (EIO);
636184610Salfred	}
637184610Salfred	DPRINTF("cmd = 0x%08lx\n", cmd);
638184610Salfred
639184610Salfred	switch (cmd) {
640184610Salfred	case TIOCSBRK:
641184610Salfred		usb2_com_break(sc, 1);
642184610Salfred		error = 0;
643184610Salfred		break;
644184610Salfred	case TIOCCBRK:
645184610Salfred		usb2_com_break(sc, 0);
646184610Salfred		error = 0;
647184610Salfred		break;
648184610Salfred	default:
649184610Salfred		if (sc->sc_callback->usb2_com_ioctl) {
650184610Salfred			error = (sc->sc_callback->usb2_com_ioctl)
651184610Salfred			    (sc, cmd, data, 0, td);
652184610Salfred		} else {
653184610Salfred			error = ENOIOCTL;
654184610Salfred		}
655184610Salfred		break;
656184610Salfred	}
657184610Salfred	return (error);
658184610Salfred}
659184610Salfred
660184610Salfredstatic int
661184610Salfredusb2_com_modem(struct tty *tp, int sigon, int sigoff)
662184610Salfred{
663192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
664184610Salfred	uint8_t onoff;
665184610Salfred
666188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
667184610Salfred
668184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
669184610Salfred		return (0);
670184610Salfred	}
671184610Salfred	if ((sigon == 0) && (sigoff == 0)) {
672184610Salfred
673184610Salfred		if (sc->sc_mcr & SER_DTR) {
674184610Salfred			sigon |= SER_DTR;
675184610Salfred		}
676184610Salfred		if (sc->sc_mcr & SER_RTS) {
677184610Salfred			sigon |= SER_RTS;
678184610Salfred		}
679184610Salfred		if (sc->sc_msr & SER_CTS) {
680184610Salfred			sigon |= SER_CTS;
681184610Salfred		}
682184610Salfred		if (sc->sc_msr & SER_DCD) {
683184610Salfred			sigon |= SER_DCD;
684184610Salfred		}
685184610Salfred		if (sc->sc_msr & SER_DSR) {
686184610Salfred			sigon |= SER_DSR;
687184610Salfred		}
688184610Salfred		if (sc->sc_msr & SER_RI) {
689184610Salfred			sigon |= SER_RI;
690184610Salfred		}
691184610Salfred		return (sigon);
692184610Salfred	}
693184610Salfred	if (sigon & SER_DTR) {
694184610Salfred		sc->sc_mcr |= SER_DTR;
695184610Salfred	}
696184610Salfred	if (sigoff & SER_DTR) {
697184610Salfred		sc->sc_mcr &= ~SER_DTR;
698184610Salfred	}
699184610Salfred	if (sigon & SER_RTS) {
700184610Salfred		sc->sc_mcr |= SER_RTS;
701184610Salfred	}
702184610Salfred	if (sigoff & SER_RTS) {
703184610Salfred		sc->sc_mcr &= ~SER_RTS;
704184610Salfred	}
705184610Salfred	onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0;
706184610Salfred	usb2_com_dtr(sc, onoff);
707184610Salfred
708184610Salfred	onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0;
709184610Salfred	usb2_com_rts(sc, onoff);
710184610Salfred
711184610Salfred	return (0);
712184610Salfred}
713184610Salfred
714184610Salfredstatic void
715192984Sthompsausb2_com_cfg_line_state(struct usb_proc_msg *_task)
716184610Salfred{
717192984Sthompsa	struct ucom_cfg_task *task =
718192984Sthompsa	    (struct ucom_cfg_task *)_task;
719192984Sthompsa	struct ucom_softc *sc = task->sc;
720188413Sthompsa	uint8_t notch_bits;
721188413Sthompsa	uint8_t any_bits;
722188413Sthompsa	uint8_t prev_value;
723188413Sthompsa	uint8_t last_value;
724188413Sthompsa	uint8_t mask;
725187176Sthompsa
726184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
727184610Salfred		return;
728184610Salfred	}
729184610Salfred
730188413Sthompsa	mask = 0;
731188413Sthompsa	/* compute callback mask */
732188413Sthompsa	if (sc->sc_callback->usb2_com_cfg_set_dtr)
733188413Sthompsa		mask |= UCOM_LS_DTR;
734188413Sthompsa	if (sc->sc_callback->usb2_com_cfg_set_rts)
735188413Sthompsa		mask |= UCOM_LS_RTS;
736188413Sthompsa	if (sc->sc_callback->usb2_com_cfg_set_break)
737188413Sthompsa		mask |= UCOM_LS_BREAK;
738184610Salfred
739188413Sthompsa	/* compute the bits we are to program */
740188413Sthompsa	notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask;
741188413Sthompsa	any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask;
742188413Sthompsa	prev_value = sc->sc_pls_curr ^ notch_bits;
743188413Sthompsa	last_value = sc->sc_pls_curr;
744187176Sthompsa
745188413Sthompsa	/* reset programmed line state */
746188413Sthompsa	sc->sc_pls_curr = 0;
747188413Sthompsa	sc->sc_pls_set = 0;
748188413Sthompsa	sc->sc_pls_clr = 0;
749188413Sthompsa
750188413Sthompsa	/* ensure that we don't loose any levels */
751188413Sthompsa	if (notch_bits & UCOM_LS_DTR)
752188413Sthompsa		sc->sc_callback->usb2_com_cfg_set_dtr(sc,
753188413Sthompsa		    (prev_value & UCOM_LS_DTR) ? 1 : 0);
754188413Sthompsa	if (notch_bits & UCOM_LS_RTS)
755188413Sthompsa		sc->sc_callback->usb2_com_cfg_set_rts(sc,
756188413Sthompsa		    (prev_value & UCOM_LS_RTS) ? 1 : 0);
757188413Sthompsa	if (notch_bits & UCOM_LS_BREAK)
758188413Sthompsa		sc->sc_callback->usb2_com_cfg_set_break(sc,
759188413Sthompsa		    (prev_value & UCOM_LS_BREAK) ? 1 : 0);
760188413Sthompsa
761188413Sthompsa	/* set last value */
762188413Sthompsa	if (any_bits & UCOM_LS_DTR)
763188413Sthompsa		sc->sc_callback->usb2_com_cfg_set_dtr(sc,
764188413Sthompsa		    (last_value & UCOM_LS_DTR) ? 1 : 0);
765188413Sthompsa	if (any_bits & UCOM_LS_RTS)
766188413Sthompsa		sc->sc_callback->usb2_com_cfg_set_rts(sc,
767188413Sthompsa		    (last_value & UCOM_LS_RTS) ? 1 : 0);
768188413Sthompsa	if (any_bits & UCOM_LS_BREAK)
769188413Sthompsa		sc->sc_callback->usb2_com_cfg_set_break(sc,
770188413Sthompsa		    (last_value & UCOM_LS_BREAK) ? 1 : 0);
771187176Sthompsa}
772187176Sthompsa
773187176Sthompsastatic void
774192984Sthompsausb2_com_line_state(struct ucom_softc *sc,
775188413Sthompsa    uint8_t set_bits, uint8_t clear_bits)
776184610Salfred{
777188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
778184610Salfred
779184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
780184610Salfred		return;
781184610Salfred	}
782184610Salfred
783188413Sthompsa	DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits);
784184610Salfred
785188413Sthompsa	/* update current programmed line state */
786188413Sthompsa	sc->sc_pls_curr |= set_bits;
787188413Sthompsa	sc->sc_pls_curr &= ~clear_bits;
788188413Sthompsa	sc->sc_pls_set |= set_bits;
789188413Sthompsa	sc->sc_pls_clr |= clear_bits;
790187176Sthompsa
791188413Sthompsa	/* defer driver programming */
792188413Sthompsa	usb2_com_queue_command(sc, usb2_com_cfg_line_state, NULL,
793188413Sthompsa	    &sc->sc_line_state_task[0].hdr,
794188413Sthompsa	    &sc->sc_line_state_task[1].hdr);
795184610Salfred}
796184610Salfred
797184610Salfredstatic void
798192984Sthompsausb2_com_break(struct ucom_softc *sc, uint8_t onoff)
799187176Sthompsa{
800188413Sthompsa	DPRINTF("onoff = %d\n", onoff);
801187176Sthompsa
802188413Sthompsa	if (onoff)
803188413Sthompsa		usb2_com_line_state(sc, UCOM_LS_BREAK, 0);
804188413Sthompsa	else
805188413Sthompsa		usb2_com_line_state(sc, 0, UCOM_LS_BREAK);
806187176Sthompsa}
807187176Sthompsa
808187176Sthompsastatic void
809192984Sthompsausb2_com_dtr(struct ucom_softc *sc, uint8_t onoff)
810184610Salfred{
811184610Salfred	DPRINTF("onoff = %d\n", onoff);
812184610Salfred
813188413Sthompsa	if (onoff)
814188413Sthompsa		usb2_com_line_state(sc, UCOM_LS_DTR, 0);
815188413Sthompsa	else
816188413Sthompsa		usb2_com_line_state(sc, 0, UCOM_LS_DTR);
817184610Salfred}
818184610Salfred
819184610Salfredstatic void
820192984Sthompsausb2_com_rts(struct ucom_softc *sc, uint8_t onoff)
821184610Salfred{
822184610Salfred	DPRINTF("onoff = %d\n", onoff);
823184610Salfred
824188413Sthompsa	if (onoff)
825188413Sthompsa		usb2_com_line_state(sc, UCOM_LS_RTS, 0);
826188413Sthompsa	else
827188413Sthompsa		usb2_com_line_state(sc, 0, UCOM_LS_RTS);
828184610Salfred}
829184610Salfred
830184610Salfredstatic void
831192984Sthompsausb2_com_cfg_status_change(struct usb_proc_msg *_task)
832184610Salfred{
833192984Sthompsa	struct ucom_cfg_task *task =
834192984Sthompsa	    (struct ucom_cfg_task *)_task;
835192984Sthompsa	struct ucom_softc *sc = task->sc;
836184610Salfred	struct tty *tp;
837184610Salfred	uint8_t new_msr;
838184610Salfred	uint8_t new_lsr;
839184610Salfred	uint8_t onoff;
840184610Salfred
841184610Salfred	tp = sc->sc_tty;
842184610Salfred
843188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
844184610Salfred
845184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
846184610Salfred		return;
847184610Salfred	}
848184610Salfred	if (sc->sc_callback->usb2_com_cfg_get_status == NULL) {
849184610Salfred		return;
850184610Salfred	}
851184610Salfred	/* get status */
852184610Salfred
853184610Salfred	new_msr = 0;
854184610Salfred	new_lsr = 0;
855184610Salfred
856184610Salfred	(sc->sc_callback->usb2_com_cfg_get_status) (sc, &new_lsr, &new_msr);
857184610Salfred
858184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
859184610Salfred		/* TTY device closed */
860184610Salfred		return;
861184610Salfred	}
862184610Salfred	onoff = ((sc->sc_msr ^ new_msr) & SER_DCD);
863184610Salfred
864184610Salfred	sc->sc_msr = new_msr;
865184610Salfred	sc->sc_lsr = new_lsr;
866184610Salfred
867184610Salfred	if (onoff) {
868184610Salfred
869184610Salfred		onoff = (sc->sc_msr & SER_DCD) ? 1 : 0;
870184610Salfred
871184610Salfred		DPRINTF("DCD changed to %d\n", onoff);
872184610Salfred
873184610Salfred		ttydisc_modem(tp, onoff);
874184610Salfred	}
875184610Salfred}
876184610Salfred
877184610Salfredvoid
878192984Sthompsausb2_com_status_change(struct ucom_softc *sc)
879184610Salfred{
880188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
881184610Salfred
882184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
883184610Salfred		return;
884184610Salfred	}
885184610Salfred	DPRINTF("\n");
886184610Salfred
887188413Sthompsa	usb2_com_queue_command(sc, usb2_com_cfg_status_change, NULL,
888188413Sthompsa	    &sc->sc_status_task[0].hdr,
889188413Sthompsa	    &sc->sc_status_task[1].hdr);
890184610Salfred}
891184610Salfred
892184610Salfredstatic void
893192984Sthompsausb2_com_cfg_param(struct usb_proc_msg *_task)
894184610Salfred{
895192984Sthompsa	struct ucom_param_task *task =
896192984Sthompsa	    (struct ucom_param_task *)_task;
897192984Sthompsa	struct ucom_softc *sc = task->sc;
898184610Salfred
899184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
900184610Salfred		return;
901184610Salfred	}
902184610Salfred	if (sc->sc_callback->usb2_com_cfg_param == NULL) {
903184610Salfred		return;
904184610Salfred	}
905184610Salfred
906188413Sthompsa	(sc->sc_callback->usb2_com_cfg_param) (sc, &task->termios_copy);
907184610Salfred
908184610Salfred	/* wait a little */
909188413Sthompsa	usb2_pause_mtx(sc->sc_mtx, hz / 10);
910184610Salfred}
911184610Salfred
912184610Salfredstatic int
913184610Salfredusb2_com_param(struct tty *tp, struct termios *t)
914184610Salfred{
915192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
916184610Salfred	uint8_t opened;
917184610Salfred	int error;
918184610Salfred
919188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
920184610Salfred
921184610Salfred	opened = 0;
922184610Salfred	error = 0;
923184610Salfred
924184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
925184610Salfred
926184610Salfred		/* XXX the TTY layer should call "open()" first! */
927184610Salfred
928184610Salfred		error = usb2_com_open(tp);
929184610Salfred		if (error) {
930184610Salfred			goto done;
931184610Salfred		}
932184610Salfred		opened = 1;
933184610Salfred	}
934184610Salfred	DPRINTF("sc = %p\n", sc);
935184610Salfred
936184610Salfred	/* Check requested parameters. */
937184610Salfred	if (t->c_ospeed < 0) {
938184610Salfred		DPRINTF("negative ospeed\n");
939184610Salfred		error = EINVAL;
940184610Salfred		goto done;
941184610Salfred	}
942184610Salfred	if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) {
943184610Salfred		DPRINTF("mismatch ispeed and ospeed\n");
944184610Salfred		error = EINVAL;
945184610Salfred		goto done;
946184610Salfred	}
947184610Salfred	t->c_ispeed = t->c_ospeed;
948184610Salfred
949184610Salfred	if (sc->sc_callback->usb2_com_pre_param) {
950184610Salfred		/* Let the lower layer verify the parameters */
951184610Salfred		error = (sc->sc_callback->usb2_com_pre_param) (sc, t);
952184610Salfred		if (error) {
953184610Salfred			DPRINTF("callback error = %d\n", error);
954184610Salfred			goto done;
955184610Salfred		}
956184610Salfred	}
957184610Salfred
958184610Salfred	/* Disable transfers */
959184610Salfred	sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
960184610Salfred
961184610Salfred	/* Queue baud rate programming command first */
962188413Sthompsa	usb2_com_queue_command(sc, usb2_com_cfg_param, t,
963188413Sthompsa	    &sc->sc_param_task[0].hdr,
964188413Sthompsa	    &sc->sc_param_task[1].hdr);
965184610Salfred
966184610Salfred	/* Queue transfer enable command last */
967188413Sthompsa	usb2_com_queue_command(sc, usb2_com_cfg_start_transfers, NULL,
968188413Sthompsa	    &sc->sc_start_task[0].hdr,
969188413Sthompsa	    &sc->sc_start_task[1].hdr);
970184610Salfred
971184610Salfred	if (t->c_cflag & CRTS_IFLOW) {
972184610Salfred		sc->sc_flag |= UCOM_FLAG_RTS_IFLOW;
973184610Salfred	} else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) {
974184610Salfred		sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW;
975184610Salfred		usb2_com_modem(tp, SER_RTS, 0);
976184610Salfred	}
977184610Salfreddone:
978184610Salfred	if (error) {
979184610Salfred		if (opened) {
980184610Salfred			usb2_com_close(tp);
981184610Salfred		}
982184610Salfred	}
983184610Salfred	return (error);
984184610Salfred}
985184610Salfred
986184610Salfredstatic void
987188413Sthompsausb2_com_outwakeup(struct tty *tp)
988184610Salfred{
989192984Sthompsa	struct ucom_softc *sc = tty_softc(tp);
990184610Salfred
991188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
992184610Salfred
993184610Salfred	DPRINTF("sc = %p\n", sc);
994184610Salfred
995184610Salfred	if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
996184610Salfred		/* The higher layer is not ready */
997184610Salfred		return;
998184610Salfred	}
999184610Salfred	usb2_com_start_transfers(sc);
1000184610Salfred}
1001184610Salfred
1002184610Salfred/*------------------------------------------------------------------------*
1003184610Salfred *	usb2_com_get_data
1004184610Salfred *
1005184610Salfred * Return values:
1006184610Salfred * 0: No data is available.
1007184610Salfred * Else: Data is available.
1008184610Salfred *------------------------------------------------------------------------*/
1009184610Salfreduint8_t
1010192984Sthompsausb2_com_get_data(struct ucom_softc *sc, struct usb_page_cache *pc,
1011184610Salfred    uint32_t offset, uint32_t len, uint32_t *actlen)
1012184610Salfred{
1013192984Sthompsa	struct usb_page_search res;
1014184610Salfred	struct tty *tp = sc->sc_tty;
1015184610Salfred	uint32_t cnt;
1016184610Salfred	uint32_t offset_orig;
1017184610Salfred
1018188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
1019184610Salfred
1020192820Sthompsa	if (tty_gone(tp) ||
1021192820Sthompsa	    !(sc->sc_flag & UCOM_FLAG_GP_DATA)) {
1022184610Salfred		actlen[0] = 0;
1023184610Salfred		return (0);		/* multiport device polling */
1024184610Salfred	}
1025184610Salfred	offset_orig = offset;
1026184610Salfred
1027184610Salfred	while (len != 0) {
1028184610Salfred
1029184610Salfred		usb2_get_page(pc, offset, &res);
1030184610Salfred
1031184610Salfred		if (res.length > len) {
1032184610Salfred			res.length = len;
1033184610Salfred		}
1034184610Salfred		/* copy data directly into USB buffer */
1035184610Salfred		cnt = ttydisc_getc(tp, res.buffer, res.length);
1036184610Salfred
1037184610Salfred		offset += cnt;
1038184610Salfred		len -= cnt;
1039184610Salfred
1040184610Salfred		if (cnt < res.length) {
1041184610Salfred			/* end of buffer */
1042184610Salfred			break;
1043184610Salfred		}
1044184610Salfred	}
1045184610Salfred
1046184610Salfred	actlen[0] = offset - offset_orig;
1047184610Salfred
1048184610Salfred	DPRINTF("cnt=%d\n", actlen[0]);
1049184610Salfred
1050184610Salfred	if (actlen[0] == 0) {
1051184610Salfred		return (0);
1052184610Salfred	}
1053184610Salfred	return (1);
1054184610Salfred}
1055184610Salfred
1056184610Salfredvoid
1057192984Sthompsausb2_com_put_data(struct ucom_softc *sc, struct usb_page_cache *pc,
1058184610Salfred    uint32_t offset, uint32_t len)
1059184610Salfred{
1060192984Sthompsa	struct usb_page_search res;
1061184610Salfred	struct tty *tp = sc->sc_tty;
1062184610Salfred	char *buf;
1063184610Salfred	uint32_t cnt;
1064184610Salfred
1065188413Sthompsa	mtx_assert(sc->sc_mtx, MA_OWNED);
1066184610Salfred
1067192820Sthompsa	if (tty_gone(tp))
1068184610Salfred		return;			/* multiport device polling */
1069192820Sthompsa
1070184610Salfred	if (len == 0)
1071184610Salfred		return;			/* no data */
1072184610Salfred
1073184610Salfred	/* set a flag to prevent recursation ? */
1074184610Salfred
1075184610Salfred	while (len > 0) {
1076184610Salfred
1077184610Salfred		usb2_get_page(pc, offset, &res);
1078184610Salfred
1079184610Salfred		if (res.length > len) {
1080184610Salfred			res.length = len;
1081184610Salfred		}
1082184610Salfred		len -= res.length;
1083184610Salfred		offset += res.length;
1084184610Salfred
1085184610Salfred		/* pass characters to tty layer */
1086184610Salfred
1087184610Salfred		buf = res.buffer;
1088184610Salfred		cnt = res.length;
1089184610Salfred
1090184610Salfred		/* first check if we can pass the buffer directly */
1091184610Salfred
1092184610Salfred		if (ttydisc_can_bypass(tp)) {
1093184610Salfred			if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) {
1094184610Salfred				DPRINTF("tp=%p, data lost\n", tp);
1095184610Salfred			}
1096184610Salfred			continue;
1097184610Salfred		}
1098184610Salfred		/* need to loop */
1099184610Salfred
1100184610Salfred		for (cnt = 0; cnt != res.length; cnt++) {
1101184610Salfred			if (ttydisc_rint(tp, buf[cnt], 0) == -1) {
1102184610Salfred				/* XXX what should we do? */
1103184610Salfred
1104184610Salfred				DPRINTF("tp=%p, lost %d "
1105184610Salfred				    "chars\n", tp, res.length - cnt);
1106184610Salfred				break;
1107184610Salfred			}
1108184610Salfred		}
1109184610Salfred	}
1110184610Salfred	ttydisc_rint_done(tp);
1111184610Salfred}
1112184610Salfred
1113184610Salfredstatic void
1114184610Salfredusb2_com_free(void *xsc)
1115184610Salfred{
1116192984Sthompsa	struct ucom_softc *sc = xsc;
1117184610Salfred
1118188413Sthompsa	mtx_lock(sc->sc_mtx);
1119184610Salfred	sc->sc_ttyfreed = 1;
1120184610Salfred	usb2_cv_signal(&sc->sc_cv);
1121188413Sthompsa	mtx_unlock(sc->sc_mtx);
1122184610Salfred}
1123