umodem.c revision 331722
1/*	$NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $	*/
2
3#include <sys/cdefs.h>
4__FBSDID("$FreeBSD: stable/11/sys/dev/usb/serial/umodem.c 331722 2018-03-29 02:50:57Z eadler $");
5
6/*-
7 * Copyright (c) 2003, M. Warner Losh <imp@FreeBSD.org>.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32/*-
33 * Copyright (c) 1998 The NetBSD Foundation, Inc.
34 * All rights reserved.
35 *
36 * This code is derived from software contributed to The NetBSD Foundation
37 * by Lennart Augustsson (lennart@augustsson.net) at
38 * Carlstedt Research & Technology.
39 *
40 * Redistribution and use in source and binary forms, with or without
41 * modification, are permitted provided that the following conditions
42 * are met:
43 * 1. Redistributions of source code must retain the above copyright
44 *    notice, this list of conditions and the following disclaimer.
45 * 2. Redistributions in binary form must reproduce the above copyright
46 *    notice, this list of conditions and the following disclaimer in the
47 *    documentation and/or other materials provided with the distribution.
48 *
49 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
50 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
51 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
52 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
53 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
54 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
55 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
56 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
57 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
58 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
59 * POSSIBILITY OF SUCH DAMAGE.
60 */
61
62/*
63 * Comm Class spec:  http://www.usb.org/developers/devclass_docs/usbccs10.pdf
64 *                   http://www.usb.org/developers/devclass_docs/usbcdc11.pdf
65 *                   http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip
66 */
67
68/*
69 * TODO:
70 * - Add error recovery in various places; the big problem is what
71 *   to do in a callback if there is an error.
72 * - Implement a Call Device for modems without multiplexed commands.
73 *
74 */
75
76#include <sys/stdint.h>
77#include <sys/stddef.h>
78#include <sys/param.h>
79#include <sys/queue.h>
80#include <sys/types.h>
81#include <sys/systm.h>
82#include <sys/kernel.h>
83#include <sys/bus.h>
84#include <sys/module.h>
85#include <sys/lock.h>
86#include <sys/mutex.h>
87#include <sys/condvar.h>
88#include <sys/sysctl.h>
89#include <sys/sx.h>
90#include <sys/unistd.h>
91#include <sys/callout.h>
92#include <sys/malloc.h>
93#include <sys/priv.h>
94
95#include <dev/usb/usb.h>
96#include <dev/usb/usbdi.h>
97#include <dev/usb/usbdi_util.h>
98#include <dev/usb/usbhid.h>
99#include <dev/usb/usb_cdc.h>
100#include "usbdevs.h"
101#include "usb_if.h"
102
103#include <dev/usb/usb_ioctl.h>
104
105#define	USB_DEBUG_VAR umodem_debug
106#include <dev/usb/usb_debug.h>
107#include <dev/usb/usb_process.h>
108#include <dev/usb/quirk/usb_quirk.h>
109
110#include <dev/usb/serial/usb_serial.h>
111
112#ifdef USB_DEBUG
113static int umodem_debug = 0;
114
115static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW, 0, "USB umodem");
116SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RWTUN,
117    &umodem_debug, 0, "Debug level");
118#endif
119
120static const STRUCT_USB_DUAL_ID umodem_dual_devs[] = {
121	/* Generic Modem class match */
122	{USB_IFACE_CLASS(UICLASS_CDC),
123		USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
124		USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)},
125	{USB_IFACE_CLASS(UICLASS_CDC),
126		USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
127		USB_IFACE_PROTOCOL(UIPROTO_CDC_NONE)},
128};
129
130static const STRUCT_USB_HOST_ID umodem_host_devs[] = {
131	/* Huawei Modem class match */
132	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
133		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x01)},
134	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
135		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x02)},
136	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
137		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x10)},
138	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
139		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x12)},
140	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
141		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x61)},
142	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
143		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x62)},
144	{USB_VENDOR(USB_VENDOR_HUAWEI),USB_IFACE_CLASS(UICLASS_CDC),
145		USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
146		USB_IFACE_PROTOCOL(0xFF)},
147	/* Kyocera AH-K3001V */
148	{USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)},
149	{USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)},
150	{USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)},
151};
152
153/*
154 * As speeds for umodem devices increase, these numbers will need to
155 * be increased. They should be good for G3 speeds and below.
156 *
157 * TODO: The TTY buffers should be increased!
158 */
159#define	UMODEM_BUF_SIZE 1024
160
161enum {
162	UMODEM_BULK_WR,
163	UMODEM_BULK_RD,
164	UMODEM_INTR_WR,
165	UMODEM_INTR_RD,
166	UMODEM_N_TRANSFER,
167};
168
169#define	UMODEM_MODVER			1	/* module version */
170
171struct umodem_softc {
172	struct ucom_super_softc sc_super_ucom;
173	struct ucom_softc sc_ucom;
174
175	struct usb_xfer *sc_xfer[UMODEM_N_TRANSFER];
176	struct usb_device *sc_udev;
177	struct mtx sc_mtx;
178
179	uint16_t sc_line;
180
181	uint8_t	sc_lsr;			/* local status register */
182	uint8_t	sc_msr;			/* modem status register */
183	uint8_t	sc_ctrl_iface_no;
184	uint8_t	sc_data_iface_no;
185	uint8_t sc_iface_index[2];
186	uint8_t	sc_cm_over_data;
187	uint8_t	sc_cm_cap;		/* CM capabilities */
188	uint8_t	sc_acm_cap;		/* ACM capabilities */
189	uint8_t	sc_line_coding[32];	/* used in USB device mode */
190	uint8_t	sc_abstract_state[32];	/* used in USB device mode */
191};
192
193static device_probe_t umodem_probe;
194static device_attach_t umodem_attach;
195static device_detach_t umodem_detach;
196static usb_handle_request_t umodem_handle_request;
197
198static void umodem_free_softc(struct umodem_softc *);
199
200static usb_callback_t umodem_intr_read_callback;
201static usb_callback_t umodem_intr_write_callback;
202static usb_callback_t umodem_write_callback;
203static usb_callback_t umodem_read_callback;
204
205static void	umodem_free(struct ucom_softc *);
206static void	umodem_start_read(struct ucom_softc *);
207static void	umodem_stop_read(struct ucom_softc *);
208static void	umodem_start_write(struct ucom_softc *);
209static void	umodem_stop_write(struct ucom_softc *);
210static void	umodem_get_caps(struct usb_attach_arg *, uint8_t *, uint8_t *);
211static void	umodem_cfg_get_status(struct ucom_softc *, uint8_t *,
212		    uint8_t *);
213static int	umodem_pre_param(struct ucom_softc *, struct termios *);
214static void	umodem_cfg_param(struct ucom_softc *, struct termios *);
215static int	umodem_ioctl(struct ucom_softc *, uint32_t, caddr_t, int,
216		    struct thread *);
217static void	umodem_cfg_set_dtr(struct ucom_softc *, uint8_t);
218static void	umodem_cfg_set_rts(struct ucom_softc *, uint8_t);
219static void	umodem_cfg_set_break(struct ucom_softc *, uint8_t);
220static void	*umodem_get_desc(struct usb_attach_arg *, uint8_t, uint8_t);
221static usb_error_t umodem_set_comm_feature(struct usb_device *, uint8_t,
222		    uint16_t, uint16_t);
223static void	umodem_poll(struct ucom_softc *ucom);
224static void	umodem_find_data_iface(struct usb_attach_arg *uaa,
225		    uint8_t, uint8_t *, uint8_t *);
226
227static const struct usb_config umodem_config[UMODEM_N_TRANSFER] = {
228
229	[UMODEM_BULK_WR] = {
230		.type = UE_BULK,
231		.endpoint = UE_ADDR_ANY,
232		.direction = UE_DIR_TX,
233		.if_index = 0,
234		.bufsize = UMODEM_BUF_SIZE,
235		.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
236		.callback = &umodem_write_callback,
237		.usb_mode = USB_MODE_DUAL,
238	},
239
240	[UMODEM_BULK_RD] = {
241		.type = UE_BULK,
242		.endpoint = UE_ADDR_ANY,
243		.direction = UE_DIR_RX,
244		.if_index = 0,
245		.bufsize = UMODEM_BUF_SIZE,
246		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
247		.callback = &umodem_read_callback,
248		.usb_mode = USB_MODE_DUAL,
249	},
250
251	[UMODEM_INTR_WR] = {
252		.type = UE_INTERRUPT,
253		.endpoint = UE_ADDR_ANY,
254		.direction = UE_DIR_TX,
255		.if_index = 1,
256		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
257		.bufsize = 0,	/* use wMaxPacketSize */
258		.callback = &umodem_intr_write_callback,
259		.usb_mode = USB_MODE_DEVICE,
260	},
261
262	[UMODEM_INTR_RD] = {
263		.type = UE_INTERRUPT,
264		.endpoint = UE_ADDR_ANY,
265		.direction = UE_DIR_RX,
266		.if_index = 1,
267		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
268		.bufsize = 0,	/* use wMaxPacketSize */
269		.callback = &umodem_intr_read_callback,
270		.usb_mode = USB_MODE_HOST,
271	},
272};
273
274static const struct ucom_callback umodem_callback = {
275	.ucom_cfg_get_status = &umodem_cfg_get_status,
276	.ucom_cfg_set_dtr = &umodem_cfg_set_dtr,
277	.ucom_cfg_set_rts = &umodem_cfg_set_rts,
278	.ucom_cfg_set_break = &umodem_cfg_set_break,
279	.ucom_cfg_param = &umodem_cfg_param,
280	.ucom_pre_param = &umodem_pre_param,
281	.ucom_ioctl = &umodem_ioctl,
282	.ucom_start_read = &umodem_start_read,
283	.ucom_stop_read = &umodem_stop_read,
284	.ucom_start_write = &umodem_start_write,
285	.ucom_stop_write = &umodem_stop_write,
286	.ucom_poll = &umodem_poll,
287	.ucom_free = &umodem_free,
288};
289
290static device_method_t umodem_methods[] = {
291	/* USB interface */
292	DEVMETHOD(usb_handle_request, umodem_handle_request),
293
294	/* Device interface */
295	DEVMETHOD(device_probe, umodem_probe),
296	DEVMETHOD(device_attach, umodem_attach),
297	DEVMETHOD(device_detach, umodem_detach),
298	DEVMETHOD_END
299};
300
301static devclass_t umodem_devclass;
302
303static driver_t umodem_driver = {
304	.name = "umodem",
305	.methods = umodem_methods,
306	.size = sizeof(struct umodem_softc),
307};
308
309DRIVER_MODULE(umodem, uhub, umodem_driver, umodem_devclass, NULL, 0);
310MODULE_DEPEND(umodem, ucom, 1, 1, 1);
311MODULE_DEPEND(umodem, usb, 1, 1, 1);
312MODULE_VERSION(umodem, UMODEM_MODVER);
313USB_PNP_DUAL_INFO(umodem_dual_devs);
314USB_PNP_HOST_INFO(umodem_host_devs);
315
316static int
317umodem_probe(device_t dev)
318{
319	struct usb_attach_arg *uaa = device_get_ivars(dev);
320	int error;
321
322	DPRINTFN(11, "\n");
323
324	error = usbd_lookup_id_by_uaa(umodem_host_devs,
325	    sizeof(umodem_host_devs), uaa);
326	if (error) {
327		error = usbd_lookup_id_by_uaa(umodem_dual_devs,
328		    sizeof(umodem_dual_devs), uaa);
329		if (error)
330			return (error);
331	}
332	return (BUS_PROBE_GENERIC);
333}
334
335static int
336umodem_attach(device_t dev)
337{
338	struct usb_attach_arg *uaa = device_get_ivars(dev);
339	struct umodem_softc *sc = device_get_softc(dev);
340	struct usb_cdc_cm_descriptor *cmd;
341	struct usb_cdc_union_descriptor *cud;
342	uint8_t i;
343	int error;
344
345	device_set_usb_desc(dev);
346	mtx_init(&sc->sc_mtx, "umodem", NULL, MTX_DEF);
347	ucom_ref(&sc->sc_super_ucom);
348
349	sc->sc_ctrl_iface_no = uaa->info.bIfaceNum;
350	sc->sc_iface_index[1] = uaa->info.bIfaceIndex;
351	sc->sc_udev = uaa->device;
352
353	umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap);
354
355	/* get the data interface number */
356
357	cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
358
359	if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) {
360
361		cud = usbd_find_descriptor(uaa->device, NULL,
362		    uaa->info.bIfaceIndex, UDESC_CS_INTERFACE,
363		    0xFF, UDESCSUB_CDC_UNION, 0xFF);
364
365		if ((cud == NULL) || (cud->bLength < sizeof(*cud))) {
366			DPRINTF("Missing descriptor. "
367			    "Assuming data interface is next.\n");
368			if (sc->sc_ctrl_iface_no == 0xFF) {
369				goto detach;
370			} else {
371				uint8_t class_match = 0;
372
373				/* set default interface number */
374				sc->sc_data_iface_no = 0xFF;
375
376				/* try to find the data interface backwards */
377				umodem_find_data_iface(uaa,
378				    uaa->info.bIfaceIndex - 1,
379				    &sc->sc_data_iface_no, &class_match);
380
381				/* try to find the data interface forwards */
382				umodem_find_data_iface(uaa,
383				    uaa->info.bIfaceIndex + 1,
384				    &sc->sc_data_iface_no, &class_match);
385
386				/* check if nothing was found */
387				if (sc->sc_data_iface_no == 0xFF)
388					goto detach;
389			}
390		} else {
391			sc->sc_data_iface_no = cud->bSlaveInterface[0];
392		}
393	} else {
394		sc->sc_data_iface_no = cmd->bDataInterface;
395	}
396
397	device_printf(dev, "data interface %d, has %sCM over "
398	    "data, has %sbreak\n",
399	    sc->sc_data_iface_no,
400	    sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ",
401	    sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no ");
402
403	/* get the data interface too */
404
405	for (i = 0;; i++) {
406		struct usb_interface *iface;
407		struct usb_interface_descriptor *id;
408
409		iface = usbd_get_iface(uaa->device, i);
410
411		if (iface) {
412
413			id = usbd_get_interface_descriptor(iface);
414
415			if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) {
416				sc->sc_iface_index[0] = i;
417				usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
418				break;
419			}
420		} else {
421			device_printf(dev, "no data interface\n");
422			goto detach;
423		}
424	}
425
426	if (usb_test_quirk(uaa, UQ_ASSUME_CM_OVER_DATA)) {
427		sc->sc_cm_over_data = 1;
428	} else {
429		if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) {
430			if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) {
431
432				error = umodem_set_comm_feature
433				(uaa->device, sc->sc_ctrl_iface_no,
434				 UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED);
435
436				/* ignore any errors */
437			}
438			sc->sc_cm_over_data = 1;
439		}
440	}
441	error = usbd_transfer_setup(uaa->device,
442	    sc->sc_iface_index, sc->sc_xfer,
443	    umodem_config, UMODEM_N_TRANSFER,
444	    sc, &sc->sc_mtx);
445	if (error) {
446		device_printf(dev, "Can't setup transfer\n");
447		goto detach;
448	}
449
450	/* clear stall at first run, if USB host mode */
451	if (uaa->usb_mode == USB_MODE_HOST) {
452		mtx_lock(&sc->sc_mtx);
453		usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]);
454		usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]);
455		mtx_unlock(&sc->sc_mtx);
456	}
457
458	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
459	    &umodem_callback, &sc->sc_mtx);
460	if (error) {
461		device_printf(dev, "Can't attach com\n");
462		goto detach;
463	}
464	ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
465
466	return (0);
467
468detach:
469	umodem_detach(dev);
470	return (ENXIO);
471}
472
473static void
474umodem_find_data_iface(struct usb_attach_arg *uaa,
475    uint8_t iface_index, uint8_t *p_data_no, uint8_t *p_match_class)
476{
477	struct usb_interface_descriptor *id;
478	struct usb_interface *iface;
479
480	iface = usbd_get_iface(uaa->device, iface_index);
481
482	/* check for end of interfaces */
483	if (iface == NULL)
484		return;
485
486	id = usbd_get_interface_descriptor(iface);
487
488	/* check for non-matching interface class */
489	if (id->bInterfaceClass != UICLASS_CDC_DATA ||
490	    id->bInterfaceSubClass != UISUBCLASS_DATA) {
491		/* if we got a class match then return */
492		if (*p_match_class)
493			return;
494	} else {
495		*p_match_class = 1;
496	}
497
498	DPRINTFN(11, "Match at index %u\n", iface_index);
499
500	*p_data_no = id->bInterfaceNumber;
501}
502
503static void
504umodem_start_read(struct ucom_softc *ucom)
505{
506	struct umodem_softc *sc = ucom->sc_parent;
507
508	/* start interrupt endpoint, if any */
509	usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_RD]);
510
511	/* start read endpoint */
512	usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_RD]);
513}
514
515static void
516umodem_stop_read(struct ucom_softc *ucom)
517{
518	struct umodem_softc *sc = ucom->sc_parent;
519
520	/* stop interrupt endpoint, if any */
521	usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_RD]);
522
523	/* stop read endpoint */
524	usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_RD]);
525}
526
527static void
528umodem_start_write(struct ucom_softc *ucom)
529{
530	struct umodem_softc *sc = ucom->sc_parent;
531
532	usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_WR]);
533	usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_WR]);
534}
535
536static void
537umodem_stop_write(struct ucom_softc *ucom)
538{
539	struct umodem_softc *sc = ucom->sc_parent;
540
541	usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_WR]);
542	usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_WR]);
543}
544
545static void
546umodem_get_caps(struct usb_attach_arg *uaa, uint8_t *cm, uint8_t *acm)
547{
548	struct usb_cdc_cm_descriptor *cmd;
549	struct usb_cdc_acm_descriptor *cad;
550
551	cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
552	if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) {
553		DPRINTF("no CM desc (faking one)\n");
554		*cm = USB_CDC_CM_DOES_CM | USB_CDC_CM_OVER_DATA;
555	} else
556		*cm = cmd->bmCapabilities;
557
558	cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM);
559	if ((cad == NULL) || (cad->bLength < sizeof(*cad))) {
560		DPRINTF("no ACM desc\n");
561		*acm = 0;
562	} else
563		*acm = cad->bmCapabilities;
564}
565
566static void
567umodem_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
568{
569	struct umodem_softc *sc = ucom->sc_parent;
570
571	DPRINTF("\n");
572
573	/* XXX Note: sc_lsr is always zero */
574	*lsr = sc->sc_lsr;
575	*msr = sc->sc_msr;
576}
577
578static int
579umodem_pre_param(struct ucom_softc *ucom, struct termios *t)
580{
581	return (0);			/* we accept anything */
582}
583
584static void
585umodem_cfg_param(struct ucom_softc *ucom, struct termios *t)
586{
587	struct umodem_softc *sc = ucom->sc_parent;
588	struct usb_cdc_line_state ls;
589	struct usb_device_request req;
590
591	DPRINTF("sc=%p\n", sc);
592
593	memset(&ls, 0, sizeof(ls));
594
595	USETDW(ls.dwDTERate, t->c_ospeed);
596
597	ls.bCharFormat = (t->c_cflag & CSTOPB) ?
598	    UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1;
599
600	ls.bParityType = (t->c_cflag & PARENB) ?
601	    ((t->c_cflag & PARODD) ?
602	    UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE;
603
604	switch (t->c_cflag & CSIZE) {
605	case CS5:
606		ls.bDataBits = 5;
607		break;
608	case CS6:
609		ls.bDataBits = 6;
610		break;
611	case CS7:
612		ls.bDataBits = 7;
613		break;
614	case CS8:
615		ls.bDataBits = 8;
616		break;
617	}
618
619	DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n",
620	    UGETDW(ls.dwDTERate), ls.bCharFormat,
621	    ls.bParityType, ls.bDataBits);
622
623	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
624	req.bRequest = UCDC_SET_LINE_CODING;
625	USETW(req.wValue, 0);
626	req.wIndex[0] = sc->sc_ctrl_iface_no;
627	req.wIndex[1] = 0;
628	USETW(req.wLength, sizeof(ls));
629
630	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
631	    &req, &ls, 0, 1000);
632}
633
634static int
635umodem_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data,
636    int flag, struct thread *td)
637{
638	struct umodem_softc *sc = ucom->sc_parent;
639	int error = 0;
640
641	DPRINTF("cmd=0x%08x\n", cmd);
642
643	switch (cmd) {
644	case USB_GET_CM_OVER_DATA:
645		*(int *)data = sc->sc_cm_over_data;
646		break;
647
648	case USB_SET_CM_OVER_DATA:
649		if (*(int *)data != sc->sc_cm_over_data) {
650			/* XXX change it */
651		}
652		break;
653
654	default:
655		DPRINTF("unknown\n");
656		error = ENOIOCTL;
657		break;
658	}
659
660	return (error);
661}
662
663static void
664umodem_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
665{
666	struct umodem_softc *sc = ucom->sc_parent;
667	struct usb_device_request req;
668
669	DPRINTF("onoff=%d\n", onoff);
670
671	if (onoff)
672		sc->sc_line |= UCDC_LINE_DTR;
673	else
674		sc->sc_line &= ~UCDC_LINE_DTR;
675
676	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
677	req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
678	USETW(req.wValue, sc->sc_line);
679	req.wIndex[0] = sc->sc_ctrl_iface_no;
680	req.wIndex[1] = 0;
681	USETW(req.wLength, 0);
682
683	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
684	    &req, NULL, 0, 1000);
685}
686
687static void
688umodem_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
689{
690	struct umodem_softc *sc = ucom->sc_parent;
691	struct usb_device_request req;
692
693	DPRINTF("onoff=%d\n", onoff);
694
695	if (onoff)
696		sc->sc_line |= UCDC_LINE_RTS;
697	else
698		sc->sc_line &= ~UCDC_LINE_RTS;
699
700	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
701	req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
702	USETW(req.wValue, sc->sc_line);
703	req.wIndex[0] = sc->sc_ctrl_iface_no;
704	req.wIndex[1] = 0;
705	USETW(req.wLength, 0);
706
707	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
708	    &req, NULL, 0, 1000);
709}
710
711static void
712umodem_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
713{
714	struct umodem_softc *sc = ucom->sc_parent;
715	struct usb_device_request req;
716	uint16_t temp;
717
718	DPRINTF("onoff=%d\n", onoff);
719
720	if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) {
721
722		temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF;
723
724		req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
725		req.bRequest = UCDC_SEND_BREAK;
726		USETW(req.wValue, temp);
727		req.wIndex[0] = sc->sc_ctrl_iface_no;
728		req.wIndex[1] = 0;
729		USETW(req.wLength, 0);
730
731		ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
732		    &req, NULL, 0, 1000);
733	}
734}
735
736static void
737umodem_intr_write_callback(struct usb_xfer *xfer, usb_error_t error)
738{
739	int actlen;
740
741	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
742
743	switch (USB_GET_STATE(xfer)) {
744	case USB_ST_TRANSFERRED:
745
746		DPRINTF("Transferred %d bytes\n", actlen);
747
748		/* FALLTHROUGH */
749	case USB_ST_SETUP:
750tr_setup:
751		break;
752
753	default:			/* Error */
754		if (error != USB_ERR_CANCELLED) {
755			/* start clear stall */
756			usbd_xfer_set_stall(xfer);
757			goto tr_setup;
758		}
759		break;
760	}
761}
762
763static void
764umodem_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
765{
766	struct usb_cdc_notification pkt;
767	struct umodem_softc *sc = usbd_xfer_softc(xfer);
768	struct usb_page_cache *pc;
769	uint16_t wLen;
770	int actlen;
771
772	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
773
774	switch (USB_GET_STATE(xfer)) {
775	case USB_ST_TRANSFERRED:
776
777		if (actlen < 8) {
778			DPRINTF("received short packet, "
779			    "%d bytes\n", actlen);
780			goto tr_setup;
781		}
782		if (actlen > (int)sizeof(pkt)) {
783			DPRINTF("truncating message\n");
784			actlen = sizeof(pkt);
785		}
786		pc = usbd_xfer_get_frame(xfer, 0);
787		usbd_copy_out(pc, 0, &pkt, actlen);
788
789		actlen -= 8;
790
791		wLen = UGETW(pkt.wLength);
792		if (actlen > wLen) {
793			actlen = wLen;
794		}
795		if (pkt.bmRequestType != UCDC_NOTIFICATION) {
796			DPRINTF("unknown message type, "
797			    "0x%02x, on notify pipe!\n",
798			    pkt.bmRequestType);
799			goto tr_setup;
800		}
801		switch (pkt.bNotification) {
802		case UCDC_N_SERIAL_STATE:
803			/*
804			 * Set the serial state in ucom driver based on
805			 * the bits from the notify message
806			 */
807			if (actlen < 2) {
808				DPRINTF("invalid notification "
809				    "length, %d bytes!\n", actlen);
810				break;
811			}
812			DPRINTF("notify bytes = %02x%02x\n",
813			    pkt.data[0],
814			    pkt.data[1]);
815
816			/* Currently, lsr is always zero. */
817			sc->sc_lsr = 0;
818			sc->sc_msr = 0;
819
820			if (pkt.data[0] & UCDC_N_SERIAL_RI) {
821				sc->sc_msr |= SER_RI;
822			}
823			if (pkt.data[0] & UCDC_N_SERIAL_DSR) {
824				sc->sc_msr |= SER_DSR;
825			}
826			if (pkt.data[0] & UCDC_N_SERIAL_DCD) {
827				sc->sc_msr |= SER_DCD;
828			}
829			ucom_status_change(&sc->sc_ucom);
830			break;
831
832		default:
833			DPRINTF("unknown notify message: 0x%02x\n",
834			    pkt.bNotification);
835			break;
836		}
837
838	case USB_ST_SETUP:
839tr_setup:
840		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
841		usbd_transfer_submit(xfer);
842		return;
843
844	default:			/* Error */
845		if (error != USB_ERR_CANCELLED) {
846			/* try to clear stall first */
847			usbd_xfer_set_stall(xfer);
848			goto tr_setup;
849		}
850		return;
851
852	}
853}
854
855static void
856umodem_write_callback(struct usb_xfer *xfer, usb_error_t error)
857{
858	struct umodem_softc *sc = usbd_xfer_softc(xfer);
859	struct usb_page_cache *pc;
860	uint32_t actlen;
861
862	switch (USB_GET_STATE(xfer)) {
863	case USB_ST_SETUP:
864	case USB_ST_TRANSFERRED:
865tr_setup:
866		pc = usbd_xfer_get_frame(xfer, 0);
867		if (ucom_get_data(&sc->sc_ucom, pc, 0,
868		    UMODEM_BUF_SIZE, &actlen)) {
869
870			usbd_xfer_set_frame_len(xfer, 0, actlen);
871			usbd_transfer_submit(xfer);
872		}
873		return;
874
875	default:			/* Error */
876		if (error != USB_ERR_CANCELLED) {
877			/* try to clear stall first */
878			usbd_xfer_set_stall(xfer);
879			goto tr_setup;
880		}
881		return;
882	}
883}
884
885static void
886umodem_read_callback(struct usb_xfer *xfer, usb_error_t error)
887{
888	struct umodem_softc *sc = usbd_xfer_softc(xfer);
889	struct usb_page_cache *pc;
890	int actlen;
891
892	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
893
894	switch (USB_GET_STATE(xfer)) {
895	case USB_ST_TRANSFERRED:
896
897		DPRINTF("actlen=%d\n", actlen);
898
899		pc = usbd_xfer_get_frame(xfer, 0);
900		ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
901
902	case USB_ST_SETUP:
903tr_setup:
904		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
905		usbd_transfer_submit(xfer);
906		return;
907
908	default:			/* Error */
909		if (error != USB_ERR_CANCELLED) {
910			/* try to clear stall first */
911			usbd_xfer_set_stall(xfer);
912			goto tr_setup;
913		}
914		return;
915	}
916}
917
918static void *
919umodem_get_desc(struct usb_attach_arg *uaa, uint8_t type, uint8_t subtype)
920{
921	return (usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex,
922	    type, 0xFF, subtype, 0xFF));
923}
924
925static usb_error_t
926umodem_set_comm_feature(struct usb_device *udev, uint8_t iface_no,
927    uint16_t feature, uint16_t state)
928{
929	struct usb_device_request req;
930	struct usb_cdc_abstract_state ast;
931
932	DPRINTF("feature=%d state=%d\n",
933	    feature, state);
934
935	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
936	req.bRequest = UCDC_SET_COMM_FEATURE;
937	USETW(req.wValue, feature);
938	req.wIndex[0] = iface_no;
939	req.wIndex[1] = 0;
940	USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH);
941	USETW(ast.wState, state);
942
943	return (usbd_do_request(udev, NULL, &req, &ast));
944}
945
946static int
947umodem_detach(device_t dev)
948{
949	struct umodem_softc *sc = device_get_softc(dev);
950
951	DPRINTF("sc=%p\n", sc);
952
953	ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
954	usbd_transfer_unsetup(sc->sc_xfer, UMODEM_N_TRANSFER);
955
956	device_claim_softc(dev);
957
958	umodem_free_softc(sc);
959
960	return (0);
961}
962
963UCOM_UNLOAD_DRAIN(umodem);
964
965static void
966umodem_free_softc(struct umodem_softc *sc)
967{
968	if (ucom_unref(&sc->sc_super_ucom)) {
969		mtx_destroy(&sc->sc_mtx);
970		device_free_softc(sc);
971	}
972}
973
974static void
975umodem_free(struct ucom_softc *ucom)
976{
977	umodem_free_softc(ucom->sc_parent);
978}
979
980static void
981umodem_poll(struct ucom_softc *ucom)
982{
983	struct umodem_softc *sc = ucom->sc_parent;
984	usbd_transfer_poll(sc->sc_xfer, UMODEM_N_TRANSFER);
985}
986
987static int
988umodem_handle_request(device_t dev,
989    const void *preq, void **pptr, uint16_t *plen,
990    uint16_t offset, uint8_t *pstate)
991{
992	struct umodem_softc *sc = device_get_softc(dev);
993	const struct usb_device_request *req = preq;
994	uint8_t is_complete = *pstate;
995
996	DPRINTF("sc=%p\n", sc);
997
998	if (!is_complete) {
999		if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
1000		    (req->bRequest == UCDC_SET_LINE_CODING) &&
1001		    (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
1002		    (req->wIndex[1] == 0x00) &&
1003		    (req->wValue[0] == 0x00) &&
1004		    (req->wValue[1] == 0x00)) {
1005			if (offset == 0) {
1006				*plen = sizeof(sc->sc_line_coding);
1007				*pptr = &sc->sc_line_coding;
1008			} else {
1009				*plen = 0;
1010			}
1011			return (0);
1012		} else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
1013		    (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
1014		    (req->wIndex[1] == 0x00) &&
1015		    (req->bRequest == UCDC_SET_COMM_FEATURE)) {
1016			if (offset == 0) {
1017				*plen = sizeof(sc->sc_abstract_state);
1018				*pptr = &sc->sc_abstract_state;
1019			} else {
1020				*plen = 0;
1021			}
1022			return (0);
1023		} else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
1024		    (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
1025		    (req->wIndex[1] == 0x00) &&
1026		    (req->bRequest == UCDC_SET_CONTROL_LINE_STATE)) {
1027			*plen = 0;
1028			return (0);
1029		} else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
1030		    (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
1031		    (req->wIndex[1] == 0x00) &&
1032		    (req->bRequest == UCDC_SEND_BREAK)) {
1033			*plen = 0;
1034			return (0);
1035		}
1036	}
1037	return (ENXIO);			/* use builtin handler */
1038}
1039