umodem.c revision 276243
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/10/sys/dev/usb/serial/umodem.c 276243 2014-12-26 11:32:34Z hselasky $");
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
102#include <dev/usb/usb_ioctl.h>
103
104#define	USB_DEBUG_VAR umodem_debug
105#include <dev/usb/usb_debug.h>
106#include <dev/usb/usb_process.h>
107#include <dev/usb/quirk/usb_quirk.h>
108
109#include <dev/usb/serial/usb_serial.h>
110
111#ifdef USB_DEBUG
112static int umodem_debug = 0;
113
114static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW, 0, "USB umodem");
115SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RW,
116    &umodem_debug, 0, "Debug level");
117#endif
118
119static const STRUCT_USB_HOST_ID umodem_devs[] = {
120	/* Generic Modem class match */
121	{USB_IFACE_CLASS(UICLASS_CDC),
122		USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
123		USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)},
124	/* Huawei Modem class match */
125	{USB_VENDOR(USB_VENDOR_HUAWEI),USB_IFACE_CLASS(UICLASS_CDC),
126		USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
127		USB_IFACE_PROTOCOL(0xFF)},
128	/* Kyocera AH-K3001V */
129	{USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)},
130	{USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)},
131	{USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)},
132};
133
134/*
135 * As speeds for umodem devices increase, these numbers will need to
136 * be increased. They should be good for G3 speeds and below.
137 *
138 * TODO: The TTY buffers should be increased!
139 */
140#define	UMODEM_BUF_SIZE 1024
141
142enum {
143	UMODEM_BULK_WR,
144	UMODEM_BULK_RD,
145	UMODEM_INTR_RD,
146	UMODEM_N_TRANSFER,
147};
148
149#define	UMODEM_MODVER			1	/* module version */
150
151struct umodem_softc {
152	struct ucom_super_softc sc_super_ucom;
153	struct ucom_softc sc_ucom;
154
155	struct usb_xfer *sc_xfer[UMODEM_N_TRANSFER];
156	struct usb_device *sc_udev;
157	struct mtx sc_mtx;
158
159	uint16_t sc_line;
160
161	uint8_t	sc_lsr;			/* local status register */
162	uint8_t	sc_msr;			/* modem status register */
163	uint8_t	sc_ctrl_iface_no;
164	uint8_t	sc_data_iface_no;
165	uint8_t sc_iface_index[2];
166	uint8_t	sc_cm_over_data;
167	uint8_t	sc_cm_cap;		/* CM capabilities */
168	uint8_t	sc_acm_cap;		/* ACM capabilities */
169};
170
171static device_probe_t umodem_probe;
172static device_attach_t umodem_attach;
173static device_detach_t umodem_detach;
174static void umodem_free_softc(struct umodem_softc *);
175
176static usb_callback_t umodem_intr_callback;
177static usb_callback_t umodem_write_callback;
178static usb_callback_t umodem_read_callback;
179
180static void	umodem_free(struct ucom_softc *);
181static void	umodem_start_read(struct ucom_softc *);
182static void	umodem_stop_read(struct ucom_softc *);
183static void	umodem_start_write(struct ucom_softc *);
184static void	umodem_stop_write(struct ucom_softc *);
185static void	umodem_get_caps(struct usb_attach_arg *, uint8_t *, uint8_t *);
186static void	umodem_cfg_get_status(struct ucom_softc *, uint8_t *,
187		    uint8_t *);
188static int	umodem_pre_param(struct ucom_softc *, struct termios *);
189static void	umodem_cfg_param(struct ucom_softc *, struct termios *);
190static int	umodem_ioctl(struct ucom_softc *, uint32_t, caddr_t, int,
191		    struct thread *);
192static void	umodem_cfg_set_dtr(struct ucom_softc *, uint8_t);
193static void	umodem_cfg_set_rts(struct ucom_softc *, uint8_t);
194static void	umodem_cfg_set_break(struct ucom_softc *, uint8_t);
195static void	*umodem_get_desc(struct usb_attach_arg *, uint8_t, uint8_t);
196static usb_error_t umodem_set_comm_feature(struct usb_device *, uint8_t,
197		    uint16_t, uint16_t);
198static void	umodem_poll(struct ucom_softc *ucom);
199static void	umodem_find_data_iface(struct usb_attach_arg *uaa,
200		    uint8_t, uint8_t *, uint8_t *);
201
202static const struct usb_config umodem_config[UMODEM_N_TRANSFER] = {
203
204	[UMODEM_BULK_WR] = {
205		.type = UE_BULK,
206		.endpoint = UE_ADDR_ANY,
207		.direction = UE_DIR_OUT,
208		.if_index = 0,
209		.bufsize = UMODEM_BUF_SIZE,
210		.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
211		.callback = &umodem_write_callback,
212	},
213
214	[UMODEM_BULK_RD] = {
215		.type = UE_BULK,
216		.endpoint = UE_ADDR_ANY,
217		.direction = UE_DIR_IN,
218		.if_index = 0,
219		.bufsize = UMODEM_BUF_SIZE,
220		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
221		.callback = &umodem_read_callback,
222	},
223
224	[UMODEM_INTR_RD] = {
225		.type = UE_INTERRUPT,
226		.endpoint = UE_ADDR_ANY,
227		.direction = UE_DIR_IN,
228		.if_index = 1,
229		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
230		.bufsize = 0,	/* use wMaxPacketSize */
231		.callback = &umodem_intr_callback,
232	},
233};
234
235static const struct ucom_callback umodem_callback = {
236	.ucom_cfg_get_status = &umodem_cfg_get_status,
237	.ucom_cfg_set_dtr = &umodem_cfg_set_dtr,
238	.ucom_cfg_set_rts = &umodem_cfg_set_rts,
239	.ucom_cfg_set_break = &umodem_cfg_set_break,
240	.ucom_cfg_param = &umodem_cfg_param,
241	.ucom_pre_param = &umodem_pre_param,
242	.ucom_ioctl = &umodem_ioctl,
243	.ucom_start_read = &umodem_start_read,
244	.ucom_stop_read = &umodem_stop_read,
245	.ucom_start_write = &umodem_start_write,
246	.ucom_stop_write = &umodem_stop_write,
247	.ucom_poll = &umodem_poll,
248	.ucom_free = &umodem_free,
249};
250
251static device_method_t umodem_methods[] = {
252	DEVMETHOD(device_probe, umodem_probe),
253	DEVMETHOD(device_attach, umodem_attach),
254	DEVMETHOD(device_detach, umodem_detach),
255	DEVMETHOD_END
256};
257
258static devclass_t umodem_devclass;
259
260static driver_t umodem_driver = {
261	.name = "umodem",
262	.methods = umodem_methods,
263	.size = sizeof(struct umodem_softc),
264};
265
266DRIVER_MODULE(umodem, uhub, umodem_driver, umodem_devclass, NULL, 0);
267MODULE_DEPEND(umodem, ucom, 1, 1, 1);
268MODULE_DEPEND(umodem, usb, 1, 1, 1);
269MODULE_VERSION(umodem, UMODEM_MODVER);
270
271static int
272umodem_probe(device_t dev)
273{
274	struct usb_attach_arg *uaa = device_get_ivars(dev);
275	int error;
276
277	DPRINTFN(11, "\n");
278
279	if (uaa->usb_mode != USB_MODE_HOST)
280		return (ENXIO);
281
282	error = usbd_lookup_id_by_uaa(umodem_devs, sizeof(umodem_devs), uaa);
283	if (error)
284		return (error);
285
286	return (BUS_PROBE_GENERIC);
287}
288
289static int
290umodem_attach(device_t dev)
291{
292	struct usb_attach_arg *uaa = device_get_ivars(dev);
293	struct umodem_softc *sc = device_get_softc(dev);
294	struct usb_cdc_cm_descriptor *cmd;
295	struct usb_cdc_union_descriptor *cud;
296	uint8_t i;
297	int error;
298
299	device_set_usb_desc(dev);
300	mtx_init(&sc->sc_mtx, "umodem", NULL, MTX_DEF);
301	ucom_ref(&sc->sc_super_ucom);
302
303	sc->sc_ctrl_iface_no = uaa->info.bIfaceNum;
304	sc->sc_iface_index[1] = uaa->info.bIfaceIndex;
305	sc->sc_udev = uaa->device;
306
307	umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap);
308
309	/* get the data interface number */
310
311	cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
312
313	if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) {
314
315		cud = usbd_find_descriptor(uaa->device, NULL,
316		    uaa->info.bIfaceIndex, UDESC_CS_INTERFACE,
317		    0xFF, UDESCSUB_CDC_UNION, 0xFF);
318
319		if ((cud == NULL) || (cud->bLength < sizeof(*cud))) {
320			DPRINTF("Missing descriptor. "
321			    "Assuming data interface is next.\n");
322			if (sc->sc_ctrl_iface_no == 0xFF) {
323				goto detach;
324			} else {
325				uint8_t class_match = 0;
326
327				/* set default interface number */
328				sc->sc_data_iface_no = 0xFF;
329
330				/* try to find the data interface backwards */
331				umodem_find_data_iface(uaa,
332				    uaa->info.bIfaceIndex - 1,
333				    &sc->sc_data_iface_no, &class_match);
334
335				/* try to find the data interface forwards */
336				umodem_find_data_iface(uaa,
337				    uaa->info.bIfaceIndex + 1,
338				    &sc->sc_data_iface_no, &class_match);
339
340				/* check if nothing was found */
341				if (sc->sc_data_iface_no == 0xFF)
342					goto detach;
343			}
344		} else {
345			sc->sc_data_iface_no = cud->bSlaveInterface[0];
346		}
347	} else {
348		sc->sc_data_iface_no = cmd->bDataInterface;
349	}
350
351	device_printf(dev, "data interface %d, has %sCM over "
352	    "data, has %sbreak\n",
353	    sc->sc_data_iface_no,
354	    sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ",
355	    sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no ");
356
357	/* get the data interface too */
358
359	for (i = 0;; i++) {
360		struct usb_interface *iface;
361		struct usb_interface_descriptor *id;
362
363		iface = usbd_get_iface(uaa->device, i);
364
365		if (iface) {
366
367			id = usbd_get_interface_descriptor(iface);
368
369			if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) {
370				sc->sc_iface_index[0] = i;
371				usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
372				break;
373			}
374		} else {
375			device_printf(dev, "no data interface\n");
376			goto detach;
377		}
378	}
379
380	if (usb_test_quirk(uaa, UQ_ASSUME_CM_OVER_DATA)) {
381		sc->sc_cm_over_data = 1;
382	} else {
383		if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) {
384			if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) {
385
386				error = umodem_set_comm_feature
387				(uaa->device, sc->sc_ctrl_iface_no,
388				 UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED);
389
390				/* ignore any errors */
391			}
392			sc->sc_cm_over_data = 1;
393		}
394	}
395	error = usbd_transfer_setup(uaa->device,
396	    sc->sc_iface_index, sc->sc_xfer,
397	    umodem_config, UMODEM_N_TRANSFER,
398	    sc, &sc->sc_mtx);
399	if (error) {
400		goto detach;
401	}
402
403	/* clear stall at first run */
404	mtx_lock(&sc->sc_mtx);
405	usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]);
406	usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]);
407	mtx_unlock(&sc->sc_mtx);
408
409	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
410	    &umodem_callback, &sc->sc_mtx);
411	if (error) {
412		goto detach;
413	}
414	ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
415
416	return (0);
417
418detach:
419	umodem_detach(dev);
420	return (ENXIO);
421}
422
423static void
424umodem_find_data_iface(struct usb_attach_arg *uaa,
425    uint8_t iface_index, uint8_t *p_data_no, uint8_t *p_match_class)
426{
427	struct usb_interface_descriptor *id;
428	struct usb_interface *iface;
429
430	iface = usbd_get_iface(uaa->device, iface_index);
431
432	/* check for end of interfaces */
433	if (iface == NULL)
434		return;
435
436	id = usbd_get_interface_descriptor(iface);
437
438	/* check for non-matching interface class */
439	if (id->bInterfaceClass != UICLASS_CDC_DATA ||
440	    id->bInterfaceSubClass != UISUBCLASS_DATA) {
441		/* if we got a class match then return */
442		if (*p_match_class)
443			return;
444	} else {
445		*p_match_class = 1;
446	}
447
448	DPRINTFN(11, "Match at index %u\n", iface_index);
449
450	*p_data_no = id->bInterfaceNumber;
451}
452
453static void
454umodem_start_read(struct ucom_softc *ucom)
455{
456	struct umodem_softc *sc = ucom->sc_parent;
457
458	/* start interrupt endpoint, if any */
459	usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_RD]);
460
461	/* start read endpoint */
462	usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_RD]);
463}
464
465static void
466umodem_stop_read(struct ucom_softc *ucom)
467{
468	struct umodem_softc *sc = ucom->sc_parent;
469
470	/* stop interrupt endpoint, if any */
471	usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_RD]);
472
473	/* stop read endpoint */
474	usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_RD]);
475}
476
477static void
478umodem_start_write(struct ucom_softc *ucom)
479{
480	struct umodem_softc *sc = ucom->sc_parent;
481
482	usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_WR]);
483}
484
485static void
486umodem_stop_write(struct ucom_softc *ucom)
487{
488	struct umodem_softc *sc = ucom->sc_parent;
489
490	usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_WR]);
491}
492
493static void
494umodem_get_caps(struct usb_attach_arg *uaa, uint8_t *cm, uint8_t *acm)
495{
496	struct usb_cdc_cm_descriptor *cmd;
497	struct usb_cdc_acm_descriptor *cad;
498
499	cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
500	if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) {
501		DPRINTF("no CM desc (faking one)\n");
502		*cm = USB_CDC_CM_DOES_CM | USB_CDC_CM_OVER_DATA;
503	} else
504		*cm = cmd->bmCapabilities;
505
506	cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM);
507	if ((cad == NULL) || (cad->bLength < sizeof(*cad))) {
508		DPRINTF("no ACM desc\n");
509		*acm = 0;
510	} else
511		*acm = cad->bmCapabilities;
512}
513
514static void
515umodem_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
516{
517	struct umodem_softc *sc = ucom->sc_parent;
518
519	DPRINTF("\n");
520
521	*lsr = sc->sc_lsr;
522	*msr = sc->sc_msr;
523}
524
525static int
526umodem_pre_param(struct ucom_softc *ucom, struct termios *t)
527{
528	return (0);			/* we accept anything */
529}
530
531static void
532umodem_cfg_param(struct ucom_softc *ucom, struct termios *t)
533{
534	struct umodem_softc *sc = ucom->sc_parent;
535	struct usb_cdc_line_state ls;
536	struct usb_device_request req;
537
538	DPRINTF("sc=%p\n", sc);
539
540	memset(&ls, 0, sizeof(ls));
541
542	USETDW(ls.dwDTERate, t->c_ospeed);
543
544	ls.bCharFormat = (t->c_cflag & CSTOPB) ?
545	    UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1;
546
547	ls.bParityType = (t->c_cflag & PARENB) ?
548	    ((t->c_cflag & PARODD) ?
549	    UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE;
550
551	switch (t->c_cflag & CSIZE) {
552	case CS5:
553		ls.bDataBits = 5;
554		break;
555	case CS6:
556		ls.bDataBits = 6;
557		break;
558	case CS7:
559		ls.bDataBits = 7;
560		break;
561	case CS8:
562		ls.bDataBits = 8;
563		break;
564	}
565
566	DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n",
567	    UGETDW(ls.dwDTERate), ls.bCharFormat,
568	    ls.bParityType, ls.bDataBits);
569
570	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
571	req.bRequest = UCDC_SET_LINE_CODING;
572	USETW(req.wValue, 0);
573	req.wIndex[0] = sc->sc_ctrl_iface_no;
574	req.wIndex[1] = 0;
575	USETW(req.wLength, sizeof(ls));
576
577	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
578	    &req, &ls, 0, 1000);
579}
580
581static int
582umodem_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data,
583    int flag, struct thread *td)
584{
585	struct umodem_softc *sc = ucom->sc_parent;
586	int error = 0;
587
588	DPRINTF("cmd=0x%08x\n", cmd);
589
590	switch (cmd) {
591	case USB_GET_CM_OVER_DATA:
592		*(int *)data = sc->sc_cm_over_data;
593		break;
594
595	case USB_SET_CM_OVER_DATA:
596		if (*(int *)data != sc->sc_cm_over_data) {
597			/* XXX change it */
598		}
599		break;
600
601	default:
602		DPRINTF("unknown\n");
603		error = ENOIOCTL;
604		break;
605	}
606
607	return (error);
608}
609
610static void
611umodem_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
612{
613	struct umodem_softc *sc = ucom->sc_parent;
614	struct usb_device_request req;
615
616	DPRINTF("onoff=%d\n", onoff);
617
618	if (onoff)
619		sc->sc_line |= UCDC_LINE_DTR;
620	else
621		sc->sc_line &= ~UCDC_LINE_DTR;
622
623	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
624	req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
625	USETW(req.wValue, sc->sc_line);
626	req.wIndex[0] = sc->sc_ctrl_iface_no;
627	req.wIndex[1] = 0;
628	USETW(req.wLength, 0);
629
630	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
631	    &req, NULL, 0, 1000);
632}
633
634static void
635umodem_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
636{
637	struct umodem_softc *sc = ucom->sc_parent;
638	struct usb_device_request req;
639
640	DPRINTF("onoff=%d\n", onoff);
641
642	if (onoff)
643		sc->sc_line |= UCDC_LINE_RTS;
644	else
645		sc->sc_line &= ~UCDC_LINE_RTS;
646
647	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
648	req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
649	USETW(req.wValue, sc->sc_line);
650	req.wIndex[0] = sc->sc_ctrl_iface_no;
651	req.wIndex[1] = 0;
652	USETW(req.wLength, 0);
653
654	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
655	    &req, NULL, 0, 1000);
656}
657
658static void
659umodem_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
660{
661	struct umodem_softc *sc = ucom->sc_parent;
662	struct usb_device_request req;
663	uint16_t temp;
664
665	DPRINTF("onoff=%d\n", onoff);
666
667	if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) {
668
669		temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF;
670
671		req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
672		req.bRequest = UCDC_SEND_BREAK;
673		USETW(req.wValue, temp);
674		req.wIndex[0] = sc->sc_ctrl_iface_no;
675		req.wIndex[1] = 0;
676		USETW(req.wLength, 0);
677
678		ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
679		    &req, NULL, 0, 1000);
680	}
681}
682
683static void
684umodem_intr_callback(struct usb_xfer *xfer, usb_error_t error)
685{
686	struct usb_cdc_notification pkt;
687	struct umodem_softc *sc = usbd_xfer_softc(xfer);
688	struct usb_page_cache *pc;
689	uint16_t wLen;
690	int actlen;
691
692	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
693
694	switch (USB_GET_STATE(xfer)) {
695	case USB_ST_TRANSFERRED:
696
697		if (actlen < 8) {
698			DPRINTF("received short packet, "
699			    "%d bytes\n", actlen);
700			goto tr_setup;
701		}
702		if (actlen > (int)sizeof(pkt)) {
703			DPRINTF("truncating message\n");
704			actlen = sizeof(pkt);
705		}
706		pc = usbd_xfer_get_frame(xfer, 0);
707		usbd_copy_out(pc, 0, &pkt, actlen);
708
709		actlen -= 8;
710
711		wLen = UGETW(pkt.wLength);
712		if (actlen > wLen) {
713			actlen = wLen;
714		}
715		if (pkt.bmRequestType != UCDC_NOTIFICATION) {
716			DPRINTF("unknown message type, "
717			    "0x%02x, on notify pipe!\n",
718			    pkt.bmRequestType);
719			goto tr_setup;
720		}
721		switch (pkt.bNotification) {
722		case UCDC_N_SERIAL_STATE:
723			/*
724			 * Set the serial state in ucom driver based on
725			 * the bits from the notify message
726			 */
727			if (actlen < 2) {
728				DPRINTF("invalid notification "
729				    "length, %d bytes!\n", actlen);
730				break;
731			}
732			DPRINTF("notify bytes = %02x%02x\n",
733			    pkt.data[0],
734			    pkt.data[1]);
735
736			/* Currently, lsr is always zero. */
737			sc->sc_lsr = 0;
738			sc->sc_msr = 0;
739
740			if (pkt.data[0] & UCDC_N_SERIAL_RI) {
741				sc->sc_msr |= SER_RI;
742			}
743			if (pkt.data[0] & UCDC_N_SERIAL_DSR) {
744				sc->sc_msr |= SER_DSR;
745			}
746			if (pkt.data[0] & UCDC_N_SERIAL_DCD) {
747				sc->sc_msr |= SER_DCD;
748			}
749			ucom_status_change(&sc->sc_ucom);
750			break;
751
752		default:
753			DPRINTF("unknown notify message: 0x%02x\n",
754			    pkt.bNotification);
755			break;
756		}
757
758	case USB_ST_SETUP:
759tr_setup:
760		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
761		usbd_transfer_submit(xfer);
762		return;
763
764	default:			/* Error */
765		if (error != USB_ERR_CANCELLED) {
766			/* try to clear stall first */
767			usbd_xfer_set_stall(xfer);
768			goto tr_setup;
769		}
770		return;
771
772	}
773}
774
775static void
776umodem_write_callback(struct usb_xfer *xfer, usb_error_t error)
777{
778	struct umodem_softc *sc = usbd_xfer_softc(xfer);
779	struct usb_page_cache *pc;
780	uint32_t actlen;
781
782	switch (USB_GET_STATE(xfer)) {
783	case USB_ST_SETUP:
784	case USB_ST_TRANSFERRED:
785tr_setup:
786		pc = usbd_xfer_get_frame(xfer, 0);
787		if (ucom_get_data(&sc->sc_ucom, pc, 0,
788		    UMODEM_BUF_SIZE, &actlen)) {
789
790			usbd_xfer_set_frame_len(xfer, 0, actlen);
791			usbd_transfer_submit(xfer);
792		}
793		return;
794
795	default:			/* Error */
796		if (error != USB_ERR_CANCELLED) {
797			/* try to clear stall first */
798			usbd_xfer_set_stall(xfer);
799			goto tr_setup;
800		}
801		return;
802	}
803}
804
805static void
806umodem_read_callback(struct usb_xfer *xfer, usb_error_t error)
807{
808	struct umodem_softc *sc = usbd_xfer_softc(xfer);
809	struct usb_page_cache *pc;
810	int actlen;
811
812	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
813
814	switch (USB_GET_STATE(xfer)) {
815	case USB_ST_TRANSFERRED:
816
817		DPRINTF("actlen=%d\n", actlen);
818
819		pc = usbd_xfer_get_frame(xfer, 0);
820		ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
821
822	case USB_ST_SETUP:
823tr_setup:
824		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
825		usbd_transfer_submit(xfer);
826		return;
827
828	default:			/* Error */
829		if (error != USB_ERR_CANCELLED) {
830			/* try to clear stall first */
831			usbd_xfer_set_stall(xfer);
832			goto tr_setup;
833		}
834		return;
835	}
836}
837
838static void *
839umodem_get_desc(struct usb_attach_arg *uaa, uint8_t type, uint8_t subtype)
840{
841	return (usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex,
842	    type, 0xFF, subtype, 0xFF));
843}
844
845static usb_error_t
846umodem_set_comm_feature(struct usb_device *udev, uint8_t iface_no,
847    uint16_t feature, uint16_t state)
848{
849	struct usb_device_request req;
850	struct usb_cdc_abstract_state ast;
851
852	DPRINTF("feature=%d state=%d\n",
853	    feature, state);
854
855	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
856	req.bRequest = UCDC_SET_COMM_FEATURE;
857	USETW(req.wValue, feature);
858	req.wIndex[0] = iface_no;
859	req.wIndex[1] = 0;
860	USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH);
861	USETW(ast.wState, state);
862
863	return (usbd_do_request(udev, NULL, &req, &ast));
864}
865
866static int
867umodem_detach(device_t dev)
868{
869	struct umodem_softc *sc = device_get_softc(dev);
870
871	DPRINTF("sc=%p\n", sc);
872
873	ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
874	usbd_transfer_unsetup(sc->sc_xfer, UMODEM_N_TRANSFER);
875
876	device_claim_softc(dev);
877
878	umodem_free_softc(sc);
879
880	return (0);
881}
882
883UCOM_UNLOAD_DRAIN(umodem);
884
885static void
886umodem_free_softc(struct umodem_softc *sc)
887{
888	if (ucom_unref(&sc->sc_super_ucom)) {
889		mtx_destroy(&sc->sc_mtx);
890		device_free_softc(sc);
891	}
892}
893
894static void
895umodem_free(struct ucom_softc *ucom)
896{
897	umodem_free_softc(ucom->sc_parent);
898}
899
900static void
901umodem_poll(struct ucom_softc *ucom)
902{
903	struct umodem_softc *sc = ucom->sc_parent;
904	usbd_transfer_poll(sc->sc_xfer, UMODEM_N_TRANSFER);
905}
906