xref: /freebsd-src/sys/dev/usb/serial/umodem.c (revision 685dc743dc3b5645e34836464128e1c0558b404b)
102ac6454SAndrew Thompson /*	$NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $	*/
202ac6454SAndrew Thompson 
302ac6454SAndrew Thompson #include <sys/cdefs.h>
402ac6454SAndrew Thompson /*-
5*eebd9d53SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
6718cf2ccSPedro F. Giffuni  *
7f86e6000SWarner Losh  * Copyright (c) 2003 M. Warner Losh <imp@FreeBSD.org>
802ac6454SAndrew Thompson  *
902ac6454SAndrew Thompson  * Redistribution and use in source and binary forms, with or without
1002ac6454SAndrew Thompson  * modification, are permitted provided that the following conditions
1102ac6454SAndrew Thompson  * are met:
1202ac6454SAndrew Thompson  * 1. Redistributions of source code must retain the above copyright
1302ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer.
1402ac6454SAndrew Thompson  * 2. Redistributions in binary form must reproduce the above copyright
1502ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer in the
1602ac6454SAndrew Thompson  *    documentation and/or other materials provided with the distribution.
1702ac6454SAndrew Thompson  *
1802ac6454SAndrew Thompson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1902ac6454SAndrew Thompson  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2002ac6454SAndrew Thompson  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2102ac6454SAndrew Thompson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2202ac6454SAndrew Thompson  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2302ac6454SAndrew Thompson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2402ac6454SAndrew Thompson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2502ac6454SAndrew Thompson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2602ac6454SAndrew Thompson  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2702ac6454SAndrew Thompson  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2802ac6454SAndrew Thompson  * SUCH DAMAGE.
2902ac6454SAndrew Thompson  */
3002ac6454SAndrew Thompson 
3102ac6454SAndrew Thompson /*-
3202ac6454SAndrew Thompson  * Copyright (c) 1998 The NetBSD Foundation, Inc.
3302ac6454SAndrew Thompson  * All rights reserved.
3402ac6454SAndrew Thompson  *
3502ac6454SAndrew Thompson  * This code is derived from software contributed to The NetBSD Foundation
3602ac6454SAndrew Thompson  * by Lennart Augustsson (lennart@augustsson.net) at
3702ac6454SAndrew Thompson  * Carlstedt Research & Technology.
3802ac6454SAndrew Thompson  *
3902ac6454SAndrew Thompson  * Redistribution and use in source and binary forms, with or without
4002ac6454SAndrew Thompson  * modification, are permitted provided that the following conditions
4102ac6454SAndrew Thompson  * are met:
4202ac6454SAndrew Thompson  * 1. Redistributions of source code must retain the above copyright
4302ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer.
4402ac6454SAndrew Thompson  * 2. Redistributions in binary form must reproduce the above copyright
4502ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer in the
4602ac6454SAndrew Thompson  *    documentation and/or other materials provided with the distribution.
4702ac6454SAndrew Thompson  *
4802ac6454SAndrew Thompson  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
4902ac6454SAndrew Thompson  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
5002ac6454SAndrew Thompson  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
5102ac6454SAndrew Thompson  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
5202ac6454SAndrew Thompson  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
5302ac6454SAndrew Thompson  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
5402ac6454SAndrew Thompson  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
5502ac6454SAndrew Thompson  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
5602ac6454SAndrew Thompson  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
5702ac6454SAndrew Thompson  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
5802ac6454SAndrew Thompson  * POSSIBILITY OF SUCH DAMAGE.
5902ac6454SAndrew Thompson  */
6002ac6454SAndrew Thompson 
6102ac6454SAndrew Thompson /*
6202ac6454SAndrew Thompson  * Comm Class spec:  http://www.usb.org/developers/devclass_docs/usbccs10.pdf
6302ac6454SAndrew Thompson  *                   http://www.usb.org/developers/devclass_docs/usbcdc11.pdf
64c3beab6aSAndrew Thompson  *                   http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip
6502ac6454SAndrew Thompson  */
6602ac6454SAndrew Thompson 
6702ac6454SAndrew Thompson /*
6802ac6454SAndrew Thompson  * TODO:
6902ac6454SAndrew Thompson  * - Add error recovery in various places; the big problem is what
7002ac6454SAndrew Thompson  *   to do in a callback if there is an error.
7102ac6454SAndrew Thompson  * - Implement a Call Device for modems without multiplexed commands.
7202ac6454SAndrew Thompson  *
7302ac6454SAndrew Thompson  */
7402ac6454SAndrew Thompson 
75ed6d949aSAndrew Thompson #include <sys/stdint.h>
76ed6d949aSAndrew Thompson #include <sys/stddef.h>
77ed6d949aSAndrew Thompson #include <sys/param.h>
78ed6d949aSAndrew Thompson #include <sys/queue.h>
79ed6d949aSAndrew Thompson #include <sys/types.h>
80ed6d949aSAndrew Thompson #include <sys/systm.h>
81ed6d949aSAndrew Thompson #include <sys/kernel.h>
82ed6d949aSAndrew Thompson #include <sys/bus.h>
83ed6d949aSAndrew Thompson #include <sys/module.h>
84ed6d949aSAndrew Thompson #include <sys/lock.h>
85ed6d949aSAndrew Thompson #include <sys/mutex.h>
86ed6d949aSAndrew Thompson #include <sys/condvar.h>
87ed6d949aSAndrew Thompson #include <sys/sysctl.h>
88ed6d949aSAndrew Thompson #include <sys/sx.h>
89ed6d949aSAndrew Thompson #include <sys/unistd.h>
90ed6d949aSAndrew Thompson #include <sys/callout.h>
91ed6d949aSAndrew Thompson #include <sys/malloc.h>
92ed6d949aSAndrew Thompson #include <sys/priv.h>
93ed6d949aSAndrew Thompson 
9402ac6454SAndrew Thompson #include <dev/usb/usb.h>
95ed6d949aSAndrew Thompson #include <dev/usb/usbdi.h>
96ed6d949aSAndrew Thompson #include <dev/usb/usbdi_util.h>
97ed6d949aSAndrew Thompson #include <dev/usb/usbhid.h>
9802ac6454SAndrew Thompson #include <dev/usb/usb_cdc.h>
99ed6d949aSAndrew Thompson #include "usbdevs.h"
10027aae196SHans Petter Selasky #include "usb_if.h"
101ed6d949aSAndrew Thompson 
10202ac6454SAndrew Thompson #include <dev/usb/usb_ioctl.h>
10302ac6454SAndrew Thompson 
10402ac6454SAndrew Thompson #define	USB_DEBUG_VAR umodem_debug
10502ac6454SAndrew Thompson #include <dev/usb/usb_debug.h>
10602ac6454SAndrew Thompson #include <dev/usb/usb_process.h>
1074b5437a1SAndrew Thompson #include <dev/usb/quirk/usb_quirk.h>
10802ac6454SAndrew Thompson 
10902ac6454SAndrew Thompson #include <dev/usb/serial/usb_serial.h>
11002ac6454SAndrew Thompson 
111b850ecc1SAndrew Thompson #ifdef USB_DEBUG
11202ac6454SAndrew Thompson static int umodem_debug = 0;
11302ac6454SAndrew Thompson 
114f8d2b1f3SPawel Biernacki static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
115f8d2b1f3SPawel Biernacki     "USB umodem");
116ece4b0bdSHans Petter Selasky SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RWTUN,
11702ac6454SAndrew Thompson     &umodem_debug, 0, "Debug level");
11802ac6454SAndrew Thompson #endif
11902ac6454SAndrew Thompson 
12027aae196SHans Petter Selasky static const STRUCT_USB_DUAL_ID umodem_dual_devs[] = {
12102ac6454SAndrew Thompson 	/* Generic Modem class match */
12202ac6454SAndrew Thompson 	{USB_IFACE_CLASS(UICLASS_CDC),
12302ac6454SAndrew Thompson 		USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
12402ac6454SAndrew Thompson 		USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)},
125c48efaecSAdrian Chadd 	{USB_IFACE_CLASS(UICLASS_CDC),
126c48efaecSAdrian Chadd 		USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
127c48efaecSAdrian Chadd 		USB_IFACE_PROTOCOL(UIPROTO_CDC_NONE)},
12827aae196SHans Petter Selasky };
12927aae196SHans Petter Selasky 
13027aae196SHans Petter Selasky static const STRUCT_USB_HOST_ID umodem_host_devs[] = {
13113ceb654SHans Petter Selasky 	/* Huawei Modem class match */
132cdadbbb0SHans Petter Selasky 	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
133cdadbbb0SHans Petter Selasky 		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x01)},
134cdadbbb0SHans Petter Selasky 	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
135cdadbbb0SHans Petter Selasky 		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x02)},
136cdadbbb0SHans Petter Selasky 	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
137cdadbbb0SHans Petter Selasky 		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x10)},
138cdadbbb0SHans Petter Selasky 	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
139cdadbbb0SHans Petter Selasky 		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x12)},
140cdadbbb0SHans Petter Selasky 	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
141cdadbbb0SHans Petter Selasky 		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x61)},
142cdadbbb0SHans Petter Selasky 	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
143cdadbbb0SHans Petter Selasky 		USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x62)},
1443d9b56b0SHans Petter Selasky 	{USB_VENDOR(USB_VENDOR_HUAWEI),USB_IFACE_CLASS(UICLASS_CDC),
14513ceb654SHans Petter Selasky 		USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
14613ceb654SHans Petter Selasky 		USB_IFACE_PROTOCOL(0xFF)},
14728d54982SKornel Duleba 	{USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(0xFF),
14828d54982SKornel Duleba 		USB_IFACE_SUBCLASS(0xF), USB_IFACE_PROTOCOL(0xFF)},
14902ac6454SAndrew Thompson 	/* Kyocera AH-K3001V */
15002ac6454SAndrew Thompson 	{USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)},
15102ac6454SAndrew Thompson 	{USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)},
15202ac6454SAndrew Thompson 	{USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)},
1538463bd8aSHans Petter Selasky 	/* Winbond */
1548463bd8aSHans Petter Selasky 	{USB_VENDOR(USB_VENDOR_WINBOND), USB_PRODUCT(USB_PRODUCT_WINBOND_CDC)},
15502ac6454SAndrew Thompson };
15602ac6454SAndrew Thompson 
15702ac6454SAndrew Thompson /*
1584815449eSThomas Quinot  * As speeds for umodem devices increase, these numbers will need to
15902ac6454SAndrew Thompson  * be increased. They should be good for G3 speeds and below.
16002ac6454SAndrew Thompson  *
16102ac6454SAndrew Thompson  * TODO: The TTY buffers should be increased!
16202ac6454SAndrew Thompson  */
16302ac6454SAndrew Thompson #define	UMODEM_BUF_SIZE 1024
16402ac6454SAndrew Thompson 
16502ac6454SAndrew Thompson enum {
16602ac6454SAndrew Thompson 	UMODEM_BULK_WR,
16702ac6454SAndrew Thompson 	UMODEM_BULK_RD,
16827aae196SHans Petter Selasky 	UMODEM_INTR_WR,
16902ac6454SAndrew Thompson 	UMODEM_INTR_RD,
17002ac6454SAndrew Thompson 	UMODEM_N_TRANSFER,
17102ac6454SAndrew Thompson };
17202ac6454SAndrew Thompson 
17302ac6454SAndrew Thompson #define	UMODEM_MODVER			1	/* module version */
17402ac6454SAndrew Thompson 
17502ac6454SAndrew Thompson struct umodem_softc {
176760bc48eSAndrew Thompson 	struct ucom_super_softc sc_super_ucom;
177760bc48eSAndrew Thompson 	struct ucom_softc sc_ucom;
17802ac6454SAndrew Thompson 
179760bc48eSAndrew Thompson 	struct usb_xfer *sc_xfer[UMODEM_N_TRANSFER];
180760bc48eSAndrew Thompson 	struct usb_device *sc_udev;
181deefe583SAndrew Thompson 	struct mtx sc_mtx;
18202ac6454SAndrew Thompson 
18302ac6454SAndrew Thompson 	uint16_t sc_line;
18402ac6454SAndrew Thompson 
18502ac6454SAndrew Thompson 	uint8_t	sc_lsr;			/* local status register */
18602ac6454SAndrew Thompson 	uint8_t	sc_msr;			/* modem status register */
18702ac6454SAndrew Thompson 	uint8_t	sc_ctrl_iface_no;
18802ac6454SAndrew Thompson 	uint8_t	sc_data_iface_no;
18902ac6454SAndrew Thompson 	uint8_t sc_iface_index[2];
19002ac6454SAndrew Thompson 	uint8_t	sc_cm_over_data;
19102ac6454SAndrew Thompson 	uint8_t	sc_cm_cap;		/* CM capabilities */
19202ac6454SAndrew Thompson 	uint8_t	sc_acm_cap;		/* ACM capabilities */
19327aae196SHans Petter Selasky 	uint8_t	sc_line_coding[32];	/* used in USB device mode */
19427aae196SHans Petter Selasky 	uint8_t	sc_abstract_state[32];	/* used in USB device mode */
19502ac6454SAndrew Thompson };
19602ac6454SAndrew Thompson 
19702ac6454SAndrew Thompson static device_probe_t umodem_probe;
19802ac6454SAndrew Thompson static device_attach_t umodem_attach;
19902ac6454SAndrew Thompson static device_detach_t umodem_detach;
20027aae196SHans Petter Selasky static usb_handle_request_t umodem_handle_request;
20127aae196SHans Petter Selasky 
202c01fc06eSHans Petter Selasky static void umodem_free_softc(struct umodem_softc *);
20302ac6454SAndrew Thompson 
20427aae196SHans Petter Selasky static usb_callback_t umodem_intr_read_callback;
20527aae196SHans Petter Selasky static usb_callback_t umodem_intr_write_callback;
206e0a69b51SAndrew Thompson static usb_callback_t umodem_write_callback;
207e0a69b51SAndrew Thompson static usb_callback_t umodem_read_callback;
20802ac6454SAndrew Thompson 
2095805d178SHans Petter Selasky static void	umodem_free(struct ucom_softc *);
210760bc48eSAndrew Thompson static void	umodem_start_read(struct ucom_softc *);
211760bc48eSAndrew Thompson static void	umodem_stop_read(struct ucom_softc *);
212760bc48eSAndrew Thompson static void	umodem_start_write(struct ucom_softc *);
213760bc48eSAndrew Thompson static void	umodem_stop_write(struct ucom_softc *);
214760bc48eSAndrew Thompson static void	umodem_get_caps(struct usb_attach_arg *, uint8_t *, uint8_t *);
215760bc48eSAndrew Thompson static void	umodem_cfg_get_status(struct ucom_softc *, uint8_t *,
21602ac6454SAndrew Thompson 		    uint8_t *);
217760bc48eSAndrew Thompson static int	umodem_pre_param(struct ucom_softc *, struct termios *);
218760bc48eSAndrew Thompson static void	umodem_cfg_param(struct ucom_softc *, struct termios *);
21940e43b05SHans Petter Selasky static void	umodem_cfg_open(struct ucom_softc *);
220760bc48eSAndrew Thompson static int	umodem_ioctl(struct ucom_softc *, uint32_t, caddr_t, int,
22102ac6454SAndrew Thompson 		    struct thread *);
222760bc48eSAndrew Thompson static void	umodem_cfg_set_dtr(struct ucom_softc *, uint8_t);
223760bc48eSAndrew Thompson static void	umodem_cfg_set_rts(struct ucom_softc *, uint8_t);
224760bc48eSAndrew Thompson static void	umodem_cfg_set_break(struct ucom_softc *, uint8_t);
225760bc48eSAndrew Thompson static void	*umodem_get_desc(struct usb_attach_arg *, uint8_t, uint8_t);
226e0a69b51SAndrew Thompson static usb_error_t umodem_set_comm_feature(struct usb_device *, uint8_t,
22702ac6454SAndrew Thompson 		    uint16_t, uint16_t);
228655dc9d0SAndrew Thompson static void	umodem_poll(struct ucom_softc *ucom);
229a1538640SHans Petter Selasky static void	umodem_find_data_iface(struct usb_attach_arg *uaa,
230a1538640SHans Petter Selasky 		    uint8_t, uint8_t *, uint8_t *);
23102ac6454SAndrew Thompson 
232760bc48eSAndrew Thompson static const struct usb_config umodem_config[UMODEM_N_TRANSFER] = {
23302ac6454SAndrew Thompson 	[UMODEM_BULK_WR] = {
23402ac6454SAndrew Thompson 		.type = UE_BULK,
23502ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
23627aae196SHans Petter Selasky 		.direction = UE_DIR_TX,
23702ac6454SAndrew Thompson 		.if_index = 0,
2384eae601eSAndrew Thompson 		.bufsize = UMODEM_BUF_SIZE,
2394eae601eSAndrew Thompson 		.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
2404eae601eSAndrew Thompson 		.callback = &umodem_write_callback,
24127aae196SHans Petter Selasky 		.usb_mode = USB_MODE_DUAL,
24202ac6454SAndrew Thompson 	},
24302ac6454SAndrew Thompson 
24402ac6454SAndrew Thompson 	[UMODEM_BULK_RD] = {
24502ac6454SAndrew Thompson 		.type = UE_BULK,
24602ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
24727aae196SHans Petter Selasky 		.direction = UE_DIR_RX,
24802ac6454SAndrew Thompson 		.if_index = 0,
2494eae601eSAndrew Thompson 		.bufsize = UMODEM_BUF_SIZE,
2504eae601eSAndrew Thompson 		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
2514eae601eSAndrew Thompson 		.callback = &umodem_read_callback,
25227aae196SHans Petter Selasky 		.usb_mode = USB_MODE_DUAL,
25327aae196SHans Petter Selasky 	},
25427aae196SHans Petter Selasky 
25527aae196SHans Petter Selasky 	[UMODEM_INTR_WR] = {
25627aae196SHans Petter Selasky 		.type = UE_INTERRUPT,
25727aae196SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
25827aae196SHans Petter Selasky 		.direction = UE_DIR_TX,
25927aae196SHans Petter Selasky 		.if_index = 1,
26027aae196SHans Petter Selasky 		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
26127aae196SHans Petter Selasky 		.bufsize = 0,	/* use wMaxPacketSize */
26227aae196SHans Petter Selasky 		.callback = &umodem_intr_write_callback,
26327aae196SHans Petter Selasky 		.usb_mode = USB_MODE_DEVICE,
26402ac6454SAndrew Thompson 	},
26502ac6454SAndrew Thompson 
26602ac6454SAndrew Thompson 	[UMODEM_INTR_RD] = {
26702ac6454SAndrew Thompson 		.type = UE_INTERRUPT,
26802ac6454SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
26927aae196SHans Petter Selasky 		.direction = UE_DIR_RX,
27002ac6454SAndrew Thompson 		.if_index = 1,
2714eae601eSAndrew Thompson 		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
2724eae601eSAndrew Thompson 		.bufsize = 0,	/* use wMaxPacketSize */
27327aae196SHans Petter Selasky 		.callback = &umodem_intr_read_callback,
27427aae196SHans Petter Selasky 		.usb_mode = USB_MODE_HOST,
27502ac6454SAndrew Thompson 	},
27602ac6454SAndrew Thompson };
27702ac6454SAndrew Thompson 
278760bc48eSAndrew Thompson static const struct ucom_callback umodem_callback = {
279a593f6b8SAndrew Thompson 	.ucom_cfg_get_status = &umodem_cfg_get_status,
280a593f6b8SAndrew Thompson 	.ucom_cfg_set_dtr = &umodem_cfg_set_dtr,
281a593f6b8SAndrew Thompson 	.ucom_cfg_set_rts = &umodem_cfg_set_rts,
282a593f6b8SAndrew Thompson 	.ucom_cfg_set_break = &umodem_cfg_set_break,
283a593f6b8SAndrew Thompson 	.ucom_cfg_param = &umodem_cfg_param,
284a593f6b8SAndrew Thompson 	.ucom_pre_param = &umodem_pre_param,
28540e43b05SHans Petter Selasky 	.ucom_cfg_open = &umodem_cfg_open,
286a593f6b8SAndrew Thompson 	.ucom_ioctl = &umodem_ioctl,
287a593f6b8SAndrew Thompson 	.ucom_start_read = &umodem_start_read,
288a593f6b8SAndrew Thompson 	.ucom_stop_read = &umodem_stop_read,
289a593f6b8SAndrew Thompson 	.ucom_start_write = &umodem_start_write,
290a593f6b8SAndrew Thompson 	.ucom_stop_write = &umodem_stop_write,
291655dc9d0SAndrew Thompson 	.ucom_poll = &umodem_poll,
2925805d178SHans Petter Selasky 	.ucom_free = &umodem_free,
29302ac6454SAndrew Thompson };
29402ac6454SAndrew Thompson 
29502ac6454SAndrew Thompson static device_method_t umodem_methods[] = {
29627aae196SHans Petter Selasky 	/* USB interface */
29727aae196SHans Petter Selasky 	DEVMETHOD(usb_handle_request, umodem_handle_request),
29827aae196SHans Petter Selasky 
29927aae196SHans Petter Selasky 	/* Device interface */
30002ac6454SAndrew Thompson 	DEVMETHOD(device_probe, umodem_probe),
30102ac6454SAndrew Thompson 	DEVMETHOD(device_attach, umodem_attach),
30202ac6454SAndrew Thompson 	DEVMETHOD(device_detach, umodem_detach),
3035805d178SHans Petter Selasky 	DEVMETHOD_END
30402ac6454SAndrew Thompson };
30502ac6454SAndrew Thompson 
30602ac6454SAndrew Thompson static driver_t umodem_driver = {
30702ac6454SAndrew Thompson 	.name = "umodem",
30802ac6454SAndrew Thompson 	.methods = umodem_methods,
30902ac6454SAndrew Thompson 	.size = sizeof(struct umodem_softc),
31002ac6454SAndrew Thompson };
31102ac6454SAndrew Thompson 
312bc9372d7SJohn Baldwin DRIVER_MODULE(umodem, uhub, umodem_driver, NULL, NULL);
31302ac6454SAndrew Thompson MODULE_DEPEND(umodem, ucom, 1, 1, 1);
31402ac6454SAndrew Thompson MODULE_DEPEND(umodem, usb, 1, 1, 1);
31502ac6454SAndrew Thompson MODULE_VERSION(umodem, UMODEM_MODVER);
316f809f280SWarner Losh USB_PNP_DUAL_INFO(umodem_dual_devs);
317f809f280SWarner Losh USB_PNP_HOST_INFO(umodem_host_devs);
31802ac6454SAndrew Thompson 
31902ac6454SAndrew Thompson static int
umodem_probe(device_t dev)32002ac6454SAndrew Thompson umodem_probe(device_t dev)
32102ac6454SAndrew Thompson {
322760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
32302ac6454SAndrew Thompson 	int error;
32402ac6454SAndrew Thompson 
32502ac6454SAndrew Thompson 	DPRINTFN(11, "\n");
32602ac6454SAndrew Thompson 
32727aae196SHans Petter Selasky 	error = usbd_lookup_id_by_uaa(umodem_host_devs,
32827aae196SHans Petter Selasky 	    sizeof(umodem_host_devs), uaa);
32927aae196SHans Petter Selasky 	if (error) {
33027aae196SHans Petter Selasky 		error = usbd_lookup_id_by_uaa(umodem_dual_devs,
33127aae196SHans Petter Selasky 		    sizeof(umodem_dual_devs), uaa);
3329cfbe3e7SHans Petter Selasky 		if (error)
33302ac6454SAndrew Thompson 			return (error);
33427aae196SHans Petter Selasky 	}
3359cfbe3e7SHans Petter Selasky 	return (BUS_PROBE_GENERIC);
33602ac6454SAndrew Thompson }
33702ac6454SAndrew Thompson 
33802ac6454SAndrew Thompson static int
umodem_attach(device_t dev)33902ac6454SAndrew Thompson umodem_attach(device_t dev)
34002ac6454SAndrew Thompson {
341760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
34202ac6454SAndrew Thompson 	struct umodem_softc *sc = device_get_softc(dev);
343760bc48eSAndrew Thompson 	struct usb_cdc_cm_descriptor *cmd;
344760bc48eSAndrew Thompson 	struct usb_cdc_union_descriptor *cud;
34502ac6454SAndrew Thompson 	uint8_t i;
34602ac6454SAndrew Thompson 	int error;
34702ac6454SAndrew Thompson 
348a593f6b8SAndrew Thompson 	device_set_usb_desc(dev);
349deefe583SAndrew Thompson 	mtx_init(&sc->sc_mtx, "umodem", NULL, MTX_DEF);
3505805d178SHans Petter Selasky 	ucom_ref(&sc->sc_super_ucom);
35102ac6454SAndrew Thompson 
35202ac6454SAndrew Thompson 	sc->sc_ctrl_iface_no = uaa->info.bIfaceNum;
35302ac6454SAndrew Thompson 	sc->sc_iface_index[1] = uaa->info.bIfaceIndex;
35402ac6454SAndrew Thompson 	sc->sc_udev = uaa->device;
35502ac6454SAndrew Thompson 
35602ac6454SAndrew Thompson 	umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap);
35702ac6454SAndrew Thompson 
35802ac6454SAndrew Thompson 	/* get the data interface number */
35902ac6454SAndrew Thompson 
36073c3e8b1SSteffen Dirkwinkel 	cmd = NULL;
36173c3e8b1SSteffen Dirkwinkel 	if (!usb_test_quirk(uaa, UQ_IGNORE_CDC_CM))
36202ac6454SAndrew Thompson 		cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
36302ac6454SAndrew Thompson 
36402ac6454SAndrew Thompson 	if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) {
3653c8d24f4SAndrew Thompson 		cud = usbd_find_descriptor(uaa->device, NULL,
366c3beab6aSAndrew Thompson 		    uaa->info.bIfaceIndex, UDESC_CS_INTERFACE,
3676d917491SHans Petter Selasky 		    0xFF, UDESCSUB_CDC_UNION, 0xFF);
368c3beab6aSAndrew Thompson 
369c3beab6aSAndrew Thompson 		if ((cud == NULL) || (cud->bLength < sizeof(*cud))) {
370a1538640SHans Petter Selasky 			DPRINTF("Missing descriptor. "
3713e889e24SAndrew Thompson 			    "Assuming data interface is next.\n");
372a1538640SHans Petter Selasky 			if (sc->sc_ctrl_iface_no == 0xFF) {
37302ac6454SAndrew Thompson 				goto detach;
374a1538640SHans Petter Selasky 			} else {
375a1538640SHans Petter Selasky 				uint8_t class_match = 0;
376a1538640SHans Petter Selasky 
377a1538640SHans Petter Selasky 				/* set default interface number */
378a1538640SHans Petter Selasky 				sc->sc_data_iface_no = 0xFF;
379a1538640SHans Petter Selasky 
380a1538640SHans Petter Selasky 				/* try to find the data interface backwards */
381a1538640SHans Petter Selasky 				umodem_find_data_iface(uaa,
382a1538640SHans Petter Selasky 				    uaa->info.bIfaceIndex - 1,
383a1538640SHans Petter Selasky 				    &sc->sc_data_iface_no, &class_match);
384a1538640SHans Petter Selasky 
385a1538640SHans Petter Selasky 				/* try to find the data interface forwards */
386a1538640SHans Petter Selasky 				umodem_find_data_iface(uaa,
387a1538640SHans Petter Selasky 				    uaa->info.bIfaceIndex + 1,
388a1538640SHans Petter Selasky 				    &sc->sc_data_iface_no, &class_match);
389a1538640SHans Petter Selasky 
390a1538640SHans Petter Selasky 				/* check if nothing was found */
391a1538640SHans Petter Selasky 				if (sc->sc_data_iface_no == 0xFF)
392a1538640SHans Petter Selasky 					goto detach;
393a1538640SHans Petter Selasky 			}
3943e889e24SAndrew Thompson 		} else {
395c3beab6aSAndrew Thompson 			sc->sc_data_iface_no = cud->bSlaveInterface[0];
3963e889e24SAndrew Thompson 		}
397c3beab6aSAndrew Thompson 	} else {
39802ac6454SAndrew Thompson 		sc->sc_data_iface_no = cmd->bDataInterface;
399c3beab6aSAndrew Thompson 	}
40002ac6454SAndrew Thompson 
40102ac6454SAndrew Thompson 	device_printf(dev, "data interface %d, has %sCM over "
40202ac6454SAndrew Thompson 	    "data, has %sbreak\n",
40302ac6454SAndrew Thompson 	    sc->sc_data_iface_no,
40402ac6454SAndrew Thompson 	    sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ",
40502ac6454SAndrew Thompson 	    sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no ");
40602ac6454SAndrew Thompson 
40702ac6454SAndrew Thompson 	/* get the data interface too */
40802ac6454SAndrew Thompson 
40902ac6454SAndrew Thompson 	for (i = 0;; i++) {
410760bc48eSAndrew Thompson 		struct usb_interface *iface;
411760bc48eSAndrew Thompson 		struct usb_interface_descriptor *id;
41202ac6454SAndrew Thompson 
413a593f6b8SAndrew Thompson 		iface = usbd_get_iface(uaa->device, i);
41402ac6454SAndrew Thompson 
41502ac6454SAndrew Thompson 		if (iface) {
416a593f6b8SAndrew Thompson 			id = usbd_get_interface_descriptor(iface);
41702ac6454SAndrew Thompson 
41802ac6454SAndrew Thompson 			if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) {
41902ac6454SAndrew Thompson 				sc->sc_iface_index[0] = i;
420a593f6b8SAndrew Thompson 				usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
42102ac6454SAndrew Thompson 				break;
42202ac6454SAndrew Thompson 			}
42302ac6454SAndrew Thompson 		} else {
424767cb2e2SAndrew Thompson 			device_printf(dev, "no data interface\n");
42502ac6454SAndrew Thompson 			goto detach;
42602ac6454SAndrew Thompson 		}
42702ac6454SAndrew Thompson 	}
42802ac6454SAndrew Thompson 
4294b5437a1SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_ASSUME_CM_OVER_DATA)) {
4304b5437a1SAndrew Thompson 		sc->sc_cm_over_data = 1;
4314b5437a1SAndrew Thompson 	} else {
43202ac6454SAndrew Thompson 		if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) {
43302ac6454SAndrew Thompson 			if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) {
43402ac6454SAndrew Thompson 				error = umodem_set_comm_feature
43502ac6454SAndrew Thompson 				(uaa->device, sc->sc_ctrl_iface_no,
43602ac6454SAndrew Thompson 				 UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED);
43702ac6454SAndrew Thompson 
43802ac6454SAndrew Thompson 				/* ignore any errors */
43902ac6454SAndrew Thompson 			}
44002ac6454SAndrew Thompson 			sc->sc_cm_over_data = 1;
44102ac6454SAndrew Thompson 		}
4424b5437a1SAndrew Thompson 	}
443a593f6b8SAndrew Thompson 	error = usbd_transfer_setup(uaa->device,
44402ac6454SAndrew Thompson 	    sc->sc_iface_index, sc->sc_xfer,
44502ac6454SAndrew Thompson 	    umodem_config, UMODEM_N_TRANSFER,
446deefe583SAndrew Thompson 	    sc, &sc->sc_mtx);
44702ac6454SAndrew Thompson 	if (error) {
44827aae196SHans Petter Selasky 		device_printf(dev, "Can't setup transfer\n");
44902ac6454SAndrew Thompson 		goto detach;
45002ac6454SAndrew Thompson 	}
45102ac6454SAndrew Thompson 
4527762e8a1SEdward Tomasz Napierala 	ucom_set_usb_mode(&sc->sc_super_ucom, uaa->usb_mode);
4537762e8a1SEdward Tomasz Napierala 
454a593f6b8SAndrew Thompson 	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
455deefe583SAndrew Thompson 	    &umodem_callback, &sc->sc_mtx);
45602ac6454SAndrew Thompson 	if (error) {
45727aae196SHans Petter Selasky 		device_printf(dev, "Can't attach com\n");
45802ac6454SAndrew Thompson 		goto detach;
45902ac6454SAndrew Thompson 	}
4606416c259SNick Hibma 	ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
4616416c259SNick Hibma 
46202ac6454SAndrew Thompson 	return (0);
46302ac6454SAndrew Thompson 
46402ac6454SAndrew Thompson detach:
46502ac6454SAndrew Thompson 	umodem_detach(dev);
46602ac6454SAndrew Thompson 	return (ENXIO);
46702ac6454SAndrew Thompson }
46802ac6454SAndrew Thompson 
46902ac6454SAndrew Thompson static void
umodem_find_data_iface(struct usb_attach_arg * uaa,uint8_t iface_index,uint8_t * p_data_no,uint8_t * p_match_class)470a1538640SHans Petter Selasky umodem_find_data_iface(struct usb_attach_arg *uaa,
471a1538640SHans Petter Selasky     uint8_t iface_index, uint8_t *p_data_no, uint8_t *p_match_class)
472a1538640SHans Petter Selasky {
473a1538640SHans Petter Selasky 	struct usb_interface_descriptor *id;
474a1538640SHans Petter Selasky 	struct usb_interface *iface;
475a1538640SHans Petter Selasky 
476a1538640SHans Petter Selasky 	iface = usbd_get_iface(uaa->device, iface_index);
477a1538640SHans Petter Selasky 
478a1538640SHans Petter Selasky 	/* check for end of interfaces */
479a1538640SHans Petter Selasky 	if (iface == NULL)
480a1538640SHans Petter Selasky 		return;
481a1538640SHans Petter Selasky 
482a1538640SHans Petter Selasky 	id = usbd_get_interface_descriptor(iface);
483a1538640SHans Petter Selasky 
484a1538640SHans Petter Selasky 	/* check for non-matching interface class */
485a1538640SHans Petter Selasky 	if (id->bInterfaceClass != UICLASS_CDC_DATA ||
486a1538640SHans Petter Selasky 	    id->bInterfaceSubClass != UISUBCLASS_DATA) {
487a1538640SHans Petter Selasky 		/* if we got a class match then return */
488a1538640SHans Petter Selasky 		if (*p_match_class)
489a1538640SHans Petter Selasky 			return;
490a1538640SHans Petter Selasky 	} else {
491a1538640SHans Petter Selasky 		*p_match_class = 1;
492a1538640SHans Petter Selasky 	}
493a1538640SHans Petter Selasky 
494a1538640SHans Petter Selasky 	DPRINTFN(11, "Match at index %u\n", iface_index);
495a1538640SHans Petter Selasky 
496a1538640SHans Petter Selasky 	*p_data_no = id->bInterfaceNumber;
497a1538640SHans Petter Selasky }
498a1538640SHans Petter Selasky 
499a1538640SHans Petter Selasky static void
umodem_start_read(struct ucom_softc * ucom)500760bc48eSAndrew Thompson umodem_start_read(struct ucom_softc *ucom)
50102ac6454SAndrew Thompson {
50202ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
50302ac6454SAndrew Thompson 
50402ac6454SAndrew Thompson 	/* start interrupt endpoint, if any */
505a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_RD]);
50602ac6454SAndrew Thompson 
50702ac6454SAndrew Thompson 	/* start read endpoint */
508a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_RD]);
50902ac6454SAndrew Thompson }
51002ac6454SAndrew Thompson 
51102ac6454SAndrew Thompson static void
umodem_stop_read(struct ucom_softc * ucom)512760bc48eSAndrew Thompson umodem_stop_read(struct ucom_softc *ucom)
51302ac6454SAndrew Thompson {
51402ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
51502ac6454SAndrew Thompson 
51602ac6454SAndrew Thompson 	/* stop interrupt endpoint, if any */
517a593f6b8SAndrew Thompson 	usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_RD]);
51802ac6454SAndrew Thompson 
51902ac6454SAndrew Thompson 	/* stop read endpoint */
520a593f6b8SAndrew Thompson 	usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_RD]);
52102ac6454SAndrew Thompson }
52202ac6454SAndrew Thompson 
52302ac6454SAndrew Thompson static void
umodem_start_write(struct ucom_softc * ucom)524760bc48eSAndrew Thompson umodem_start_write(struct ucom_softc *ucom)
52502ac6454SAndrew Thompson {
52602ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
52702ac6454SAndrew Thompson 
52827aae196SHans Petter Selasky 	usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_WR]);
529a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_WR]);
53002ac6454SAndrew Thompson }
53102ac6454SAndrew Thompson 
53202ac6454SAndrew Thompson static void
umodem_stop_write(struct ucom_softc * ucom)533760bc48eSAndrew Thompson umodem_stop_write(struct ucom_softc *ucom)
53402ac6454SAndrew Thompson {
53502ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
53602ac6454SAndrew Thompson 
53727aae196SHans Petter Selasky 	usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_WR]);
538a593f6b8SAndrew Thompson 	usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_WR]);
53902ac6454SAndrew Thompson }
54002ac6454SAndrew Thompson 
54102ac6454SAndrew Thompson static void
umodem_get_caps(struct usb_attach_arg * uaa,uint8_t * cm,uint8_t * acm)542760bc48eSAndrew Thompson umodem_get_caps(struct usb_attach_arg *uaa, uint8_t *cm, uint8_t *acm)
54302ac6454SAndrew Thompson {
544760bc48eSAndrew Thompson 	struct usb_cdc_cm_descriptor *cmd;
545760bc48eSAndrew Thompson 	struct usb_cdc_acm_descriptor *cad;
54602ac6454SAndrew Thompson 
54702ac6454SAndrew Thompson 	cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
54802ac6454SAndrew Thompson 	if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) {
549c3beab6aSAndrew Thompson 		DPRINTF("no CM desc (faking one)\n");
550c3beab6aSAndrew Thompson 		*cm = USB_CDC_CM_DOES_CM | USB_CDC_CM_OVER_DATA;
551c3beab6aSAndrew Thompson 	} else
55202ac6454SAndrew Thompson 		*cm = cmd->bmCapabilities;
55302ac6454SAndrew Thompson 
55402ac6454SAndrew Thompson 	cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM);
55502ac6454SAndrew Thompson 	if ((cad == NULL) || (cad->bLength < sizeof(*cad))) {
55602ac6454SAndrew Thompson 		DPRINTF("no ACM desc\n");
557c3beab6aSAndrew Thompson 		*acm = 0;
558c3beab6aSAndrew Thompson 	} else
55902ac6454SAndrew Thompson 		*acm = cad->bmCapabilities;
56002ac6454SAndrew Thompson }
56102ac6454SAndrew Thompson 
56202ac6454SAndrew Thompson static void
umodem_cfg_get_status(struct ucom_softc * ucom,uint8_t * lsr,uint8_t * msr)563760bc48eSAndrew Thompson umodem_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
56402ac6454SAndrew Thompson {
56502ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
56602ac6454SAndrew Thompson 
56702ac6454SAndrew Thompson 	DPRINTF("\n");
56802ac6454SAndrew Thompson 
569fe67c846SIan Lepore 	/* XXX Note: sc_lsr is always zero */
57002ac6454SAndrew Thompson 	*lsr = sc->sc_lsr;
57102ac6454SAndrew Thompson 	*msr = sc->sc_msr;
57202ac6454SAndrew Thompson }
57302ac6454SAndrew Thompson 
57402ac6454SAndrew Thompson static int
umodem_pre_param(struct ucom_softc * ucom,struct termios * t)575760bc48eSAndrew Thompson umodem_pre_param(struct ucom_softc *ucom, struct termios *t)
57602ac6454SAndrew Thompson {
57702ac6454SAndrew Thompson 	return (0);			/* we accept anything */
57802ac6454SAndrew Thompson }
57902ac6454SAndrew Thompson 
58002ac6454SAndrew Thompson static void
umodem_cfg_param(struct ucom_softc * ucom,struct termios * t)581760bc48eSAndrew Thompson umodem_cfg_param(struct ucom_softc *ucom, struct termios *t)
58202ac6454SAndrew Thompson {
58302ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
584760bc48eSAndrew Thompson 	struct usb_cdc_line_state ls;
585760bc48eSAndrew Thompson 	struct usb_device_request req;
58602ac6454SAndrew Thompson 
58702ac6454SAndrew Thompson 	DPRINTF("sc=%p\n", sc);
58802ac6454SAndrew Thompson 
589271ae033SHans Petter Selasky 	memset(&ls, 0, sizeof(ls));
59002ac6454SAndrew Thompson 
59102ac6454SAndrew Thompson 	USETDW(ls.dwDTERate, t->c_ospeed);
59202ac6454SAndrew Thompson 
59302ac6454SAndrew Thompson 	ls.bCharFormat = (t->c_cflag & CSTOPB) ?
59402ac6454SAndrew Thompson 	    UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1;
59502ac6454SAndrew Thompson 
59602ac6454SAndrew Thompson 	ls.bParityType = (t->c_cflag & PARENB) ?
59702ac6454SAndrew Thompson 	    ((t->c_cflag & PARODD) ?
59802ac6454SAndrew Thompson 	    UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE;
59902ac6454SAndrew Thompson 
60002ac6454SAndrew Thompson 	switch (t->c_cflag & CSIZE) {
60102ac6454SAndrew Thompson 	case CS5:
60202ac6454SAndrew Thompson 		ls.bDataBits = 5;
60302ac6454SAndrew Thompson 		break;
60402ac6454SAndrew Thompson 	case CS6:
60502ac6454SAndrew Thompson 		ls.bDataBits = 6;
60602ac6454SAndrew Thompson 		break;
60702ac6454SAndrew Thompson 	case CS7:
60802ac6454SAndrew Thompson 		ls.bDataBits = 7;
60902ac6454SAndrew Thompson 		break;
61002ac6454SAndrew Thompson 	case CS8:
61102ac6454SAndrew Thompson 		ls.bDataBits = 8;
61202ac6454SAndrew Thompson 		break;
61302ac6454SAndrew Thompson 	}
61402ac6454SAndrew Thompson 
61502ac6454SAndrew Thompson 	DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n",
61602ac6454SAndrew Thompson 	    UGETDW(ls.dwDTERate), ls.bCharFormat,
61702ac6454SAndrew Thompson 	    ls.bParityType, ls.bDataBits);
61802ac6454SAndrew Thompson 
61902ac6454SAndrew Thompson 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
62002ac6454SAndrew Thompson 	req.bRequest = UCDC_SET_LINE_CODING;
62102ac6454SAndrew Thompson 	USETW(req.wValue, 0);
62202ac6454SAndrew Thompson 	req.wIndex[0] = sc->sc_ctrl_iface_no;
62302ac6454SAndrew Thompson 	req.wIndex[1] = 0;
62402ac6454SAndrew Thompson 	USETW(req.wLength, sizeof(ls));
62502ac6454SAndrew Thompson 
626a593f6b8SAndrew Thompson 	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
62702ac6454SAndrew Thompson 	    &req, &ls, 0, 1000);
62802ac6454SAndrew Thompson }
62902ac6454SAndrew Thompson 
63040e43b05SHans Petter Selasky static void
umodem_cfg_open(struct ucom_softc * ucom)63140e43b05SHans Petter Selasky umodem_cfg_open(struct ucom_softc *ucom)
63240e43b05SHans Petter Selasky {
63340e43b05SHans Petter Selasky 	struct umodem_softc *sc = ucom->sc_parent;
63440e43b05SHans Petter Selasky 
63540e43b05SHans Petter Selasky 	/* clear stall, if in USB host mode */
63640e43b05SHans Petter Selasky 	if ((sc->sc_super_ucom.sc_flag & UCOM_FLAG_DEVICE_MODE) == 0) {
63740e43b05SHans Petter Selasky 		usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]);
63840e43b05SHans Petter Selasky 		usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]);
63940e43b05SHans Petter Selasky 	}
64040e43b05SHans Petter Selasky }
64140e43b05SHans Petter Selasky 
64202ac6454SAndrew Thompson static int
umodem_ioctl(struct ucom_softc * ucom,uint32_t cmd,caddr_t data,int flag,struct thread * td)643760bc48eSAndrew Thompson umodem_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data,
64402ac6454SAndrew Thompson     int flag, struct thread *td)
64502ac6454SAndrew Thompson {
64602ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
64702ac6454SAndrew Thompson 	int error = 0;
64802ac6454SAndrew Thompson 
64902ac6454SAndrew Thompson 	DPRINTF("cmd=0x%08x\n", cmd);
65002ac6454SAndrew Thompson 
65102ac6454SAndrew Thompson 	switch (cmd) {
65202ac6454SAndrew Thompson 	case USB_GET_CM_OVER_DATA:
65302ac6454SAndrew Thompson 		*(int *)data = sc->sc_cm_over_data;
65402ac6454SAndrew Thompson 		break;
65502ac6454SAndrew Thompson 
65602ac6454SAndrew Thompson 	case USB_SET_CM_OVER_DATA:
65702ac6454SAndrew Thompson 		if (*(int *)data != sc->sc_cm_over_data) {
65802ac6454SAndrew Thompson 			/* XXX change it */
65902ac6454SAndrew Thompson 		}
66002ac6454SAndrew Thompson 		break;
66102ac6454SAndrew Thompson 
66202ac6454SAndrew Thompson 	default:
66302ac6454SAndrew Thompson 		DPRINTF("unknown\n");
66402ac6454SAndrew Thompson 		error = ENOIOCTL;
66502ac6454SAndrew Thompson 		break;
66602ac6454SAndrew Thompson 	}
66702ac6454SAndrew Thompson 
66802ac6454SAndrew Thompson 	return (error);
66902ac6454SAndrew Thompson }
67002ac6454SAndrew Thompson 
67102ac6454SAndrew Thompson static void
umodem_cfg_set_dtr(struct ucom_softc * ucom,uint8_t onoff)672760bc48eSAndrew Thompson umodem_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
67302ac6454SAndrew Thompson {
67402ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
675760bc48eSAndrew Thompson 	struct usb_device_request req;
67602ac6454SAndrew Thompson 
67702ac6454SAndrew Thompson 	DPRINTF("onoff=%d\n", onoff);
67802ac6454SAndrew Thompson 
67902ac6454SAndrew Thompson 	if (onoff)
68002ac6454SAndrew Thompson 		sc->sc_line |= UCDC_LINE_DTR;
68102ac6454SAndrew Thompson 	else
68202ac6454SAndrew Thompson 		sc->sc_line &= ~UCDC_LINE_DTR;
68302ac6454SAndrew Thompson 
68402ac6454SAndrew Thompson 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
68502ac6454SAndrew Thompson 	req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
68602ac6454SAndrew Thompson 	USETW(req.wValue, sc->sc_line);
68702ac6454SAndrew Thompson 	req.wIndex[0] = sc->sc_ctrl_iface_no;
68802ac6454SAndrew Thompson 	req.wIndex[1] = 0;
68902ac6454SAndrew Thompson 	USETW(req.wLength, 0);
69002ac6454SAndrew Thompson 
691a593f6b8SAndrew Thompson 	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
69202ac6454SAndrew Thompson 	    &req, NULL, 0, 1000);
69302ac6454SAndrew Thompson }
69402ac6454SAndrew Thompson 
69502ac6454SAndrew Thompson static void
umodem_cfg_set_rts(struct ucom_softc * ucom,uint8_t onoff)696760bc48eSAndrew Thompson umodem_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
69702ac6454SAndrew Thompson {
69802ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
699760bc48eSAndrew Thompson 	struct usb_device_request req;
70002ac6454SAndrew Thompson 
70102ac6454SAndrew Thompson 	DPRINTF("onoff=%d\n", onoff);
70202ac6454SAndrew Thompson 
70302ac6454SAndrew Thompson 	if (onoff)
70402ac6454SAndrew Thompson 		sc->sc_line |= UCDC_LINE_RTS;
70502ac6454SAndrew Thompson 	else
70602ac6454SAndrew Thompson 		sc->sc_line &= ~UCDC_LINE_RTS;
70702ac6454SAndrew Thompson 
70802ac6454SAndrew Thompson 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
70902ac6454SAndrew Thompson 	req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
71002ac6454SAndrew Thompson 	USETW(req.wValue, sc->sc_line);
71102ac6454SAndrew Thompson 	req.wIndex[0] = sc->sc_ctrl_iface_no;
71202ac6454SAndrew Thompson 	req.wIndex[1] = 0;
71302ac6454SAndrew Thompson 	USETW(req.wLength, 0);
71402ac6454SAndrew Thompson 
715a593f6b8SAndrew Thompson 	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
71602ac6454SAndrew Thompson 	    &req, NULL, 0, 1000);
71702ac6454SAndrew Thompson }
71802ac6454SAndrew Thompson 
71902ac6454SAndrew Thompson static void
umodem_cfg_set_break(struct ucom_softc * ucom,uint8_t onoff)720760bc48eSAndrew Thompson umodem_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
72102ac6454SAndrew Thompson {
72202ac6454SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
723760bc48eSAndrew Thompson 	struct usb_device_request req;
72402ac6454SAndrew Thompson 	uint16_t temp;
72502ac6454SAndrew Thompson 
72602ac6454SAndrew Thompson 	DPRINTF("onoff=%d\n", onoff);
72702ac6454SAndrew Thompson 
72802ac6454SAndrew Thompson 	if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) {
72902ac6454SAndrew Thompson 		temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF;
73002ac6454SAndrew Thompson 
73102ac6454SAndrew Thompson 		req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
73202ac6454SAndrew Thompson 		req.bRequest = UCDC_SEND_BREAK;
73302ac6454SAndrew Thompson 		USETW(req.wValue, temp);
73402ac6454SAndrew Thompson 		req.wIndex[0] = sc->sc_ctrl_iface_no;
73502ac6454SAndrew Thompson 		req.wIndex[1] = 0;
73602ac6454SAndrew Thompson 		USETW(req.wLength, 0);
73702ac6454SAndrew Thompson 
738a593f6b8SAndrew Thompson 		ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
73902ac6454SAndrew Thompson 		    &req, NULL, 0, 1000);
74002ac6454SAndrew Thompson 	}
74102ac6454SAndrew Thompson }
74202ac6454SAndrew Thompson 
74302ac6454SAndrew Thompson static void
umodem_intr_write_callback(struct usb_xfer * xfer,usb_error_t error)74427aae196SHans Petter Selasky umodem_intr_write_callback(struct usb_xfer *xfer, usb_error_t error)
74527aae196SHans Petter Selasky {
74627aae196SHans Petter Selasky 	int actlen;
74727aae196SHans Petter Selasky 
74827aae196SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
74927aae196SHans Petter Selasky 
75027aae196SHans Petter Selasky 	switch (USB_GET_STATE(xfer)) {
75127aae196SHans Petter Selasky 	case USB_ST_TRANSFERRED:
75227aae196SHans Petter Selasky 
75327aae196SHans Petter Selasky 		DPRINTF("Transferred %d bytes\n", actlen);
75427aae196SHans Petter Selasky 
75527aae196SHans Petter Selasky 		/* FALLTHROUGH */
75627aae196SHans Petter Selasky 	case USB_ST_SETUP:
75727aae196SHans Petter Selasky tr_setup:
75827aae196SHans Petter Selasky 		break;
75927aae196SHans Petter Selasky 
76027aae196SHans Petter Selasky 	default:			/* Error */
76127aae196SHans Petter Selasky 		if (error != USB_ERR_CANCELLED) {
76227aae196SHans Petter Selasky 			/* start clear stall */
76327aae196SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
76427aae196SHans Petter Selasky 			goto tr_setup;
76527aae196SHans Petter Selasky 		}
76627aae196SHans Petter Selasky 		break;
76727aae196SHans Petter Selasky 	}
76827aae196SHans Petter Selasky }
76927aae196SHans Petter Selasky 
77027aae196SHans Petter Selasky static void
umodem_intr_read_callback(struct usb_xfer * xfer,usb_error_t error)77127aae196SHans Petter Selasky umodem_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
77202ac6454SAndrew Thompson {
773760bc48eSAndrew Thompson 	struct usb_cdc_notification pkt;
774ed6d949aSAndrew Thompson 	struct umodem_softc *sc = usbd_xfer_softc(xfer);
775ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
77602ac6454SAndrew Thompson 	uint16_t wLen;
777ed6d949aSAndrew Thompson 	int actlen;
778ed6d949aSAndrew Thompson 
779ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
78002ac6454SAndrew Thompson 
78102ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
78202ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
78302ac6454SAndrew Thompson 
784ed6d949aSAndrew Thompson 		if (actlen < 8) {
78502ac6454SAndrew Thompson 			DPRINTF("received short packet, "
786ed6d949aSAndrew Thompson 			    "%d bytes\n", actlen);
78702ac6454SAndrew Thompson 			goto tr_setup;
78802ac6454SAndrew Thompson 		}
7896d917491SHans Petter Selasky 		if (actlen > (int)sizeof(pkt)) {
79002ac6454SAndrew Thompson 			DPRINTF("truncating message\n");
791ed6d949aSAndrew Thompson 			actlen = sizeof(pkt);
79202ac6454SAndrew Thompson 		}
793ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
794ed6d949aSAndrew Thompson 		usbd_copy_out(pc, 0, &pkt, actlen);
79502ac6454SAndrew Thompson 
796ed6d949aSAndrew Thompson 		actlen -= 8;
79702ac6454SAndrew Thompson 
79802ac6454SAndrew Thompson 		wLen = UGETW(pkt.wLength);
799ed6d949aSAndrew Thompson 		if (actlen > wLen) {
800ed6d949aSAndrew Thompson 			actlen = wLen;
80102ac6454SAndrew Thompson 		}
80202ac6454SAndrew Thompson 		if (pkt.bmRequestType != UCDC_NOTIFICATION) {
80302ac6454SAndrew Thompson 			DPRINTF("unknown message type, "
80402ac6454SAndrew Thompson 			    "0x%02x, on notify pipe!\n",
80502ac6454SAndrew Thompson 			    pkt.bmRequestType);
80602ac6454SAndrew Thompson 			goto tr_setup;
80702ac6454SAndrew Thompson 		}
80802ac6454SAndrew Thompson 		switch (pkt.bNotification) {
80902ac6454SAndrew Thompson 		case UCDC_N_SERIAL_STATE:
81002ac6454SAndrew Thompson 			/*
81102ac6454SAndrew Thompson 			 * Set the serial state in ucom driver based on
81202ac6454SAndrew Thompson 			 * the bits from the notify message
81302ac6454SAndrew Thompson 			 */
814ed6d949aSAndrew Thompson 			if (actlen < 2) {
81502ac6454SAndrew Thompson 				DPRINTF("invalid notification "
816ed6d949aSAndrew Thompson 				    "length, %d bytes!\n", actlen);
81702ac6454SAndrew Thompson 				break;
81802ac6454SAndrew Thompson 			}
81902ac6454SAndrew Thompson 			DPRINTF("notify bytes = %02x%02x\n",
82002ac6454SAndrew Thompson 			    pkt.data[0],
82102ac6454SAndrew Thompson 			    pkt.data[1]);
82202ac6454SAndrew Thompson 
82302ac6454SAndrew Thompson 			/* Currently, lsr is always zero. */
82402ac6454SAndrew Thompson 			sc->sc_lsr = 0;
82502ac6454SAndrew Thompson 			sc->sc_msr = 0;
82602ac6454SAndrew Thompson 
82702ac6454SAndrew Thompson 			if (pkt.data[0] & UCDC_N_SERIAL_RI) {
82802ac6454SAndrew Thompson 				sc->sc_msr |= SER_RI;
82902ac6454SAndrew Thompson 			}
83002ac6454SAndrew Thompson 			if (pkt.data[0] & UCDC_N_SERIAL_DSR) {
83102ac6454SAndrew Thompson 				sc->sc_msr |= SER_DSR;
83202ac6454SAndrew Thompson 			}
83302ac6454SAndrew Thompson 			if (pkt.data[0] & UCDC_N_SERIAL_DCD) {
83402ac6454SAndrew Thompson 				sc->sc_msr |= SER_DCD;
83502ac6454SAndrew Thompson 			}
836a593f6b8SAndrew Thompson 			ucom_status_change(&sc->sc_ucom);
83702ac6454SAndrew Thompson 			break;
83802ac6454SAndrew Thompson 
83902ac6454SAndrew Thompson 		default:
84002ac6454SAndrew Thompson 			DPRINTF("unknown notify message: 0x%02x\n",
84102ac6454SAndrew Thompson 			    pkt.bNotification);
84202ac6454SAndrew Thompson 			break;
84302ac6454SAndrew Thompson 		}
84402ac6454SAndrew Thompson 
84502ac6454SAndrew Thompson 	case USB_ST_SETUP:
84602ac6454SAndrew Thompson tr_setup:
847ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
848a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
84902ac6454SAndrew Thompson 		return;
85002ac6454SAndrew Thompson 
85102ac6454SAndrew Thompson 	default:			/* Error */
852ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
85302ac6454SAndrew Thompson 			/* try to clear stall first */
854ed6d949aSAndrew Thompson 			usbd_xfer_set_stall(xfer);
85502ac6454SAndrew Thompson 			goto tr_setup;
85602ac6454SAndrew Thompson 		}
85702ac6454SAndrew Thompson 		return;
85802ac6454SAndrew Thompson 	}
85902ac6454SAndrew Thompson }
86002ac6454SAndrew Thompson 
86102ac6454SAndrew Thompson static void
umodem_write_callback(struct usb_xfer * xfer,usb_error_t error)862ed6d949aSAndrew Thompson umodem_write_callback(struct usb_xfer *xfer, usb_error_t error)
86302ac6454SAndrew Thompson {
864ed6d949aSAndrew Thompson 	struct umodem_softc *sc = usbd_xfer_softc(xfer);
865ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
86602ac6454SAndrew Thompson 	uint32_t actlen;
86702ac6454SAndrew Thompson 
86802ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
86902ac6454SAndrew Thompson 	case USB_ST_SETUP:
87002ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
87102ac6454SAndrew Thompson tr_setup:
872ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
873ed6d949aSAndrew Thompson 		if (ucom_get_data(&sc->sc_ucom, pc, 0,
87402ac6454SAndrew Thompson 		    UMODEM_BUF_SIZE, &actlen)) {
875ed6d949aSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, 0, actlen);
876a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
87702ac6454SAndrew Thompson 		}
878ec97e9caSHans Petter Selasky 		return;
87902ac6454SAndrew Thompson 
88002ac6454SAndrew Thompson 	default:			/* Error */
881ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
88202ac6454SAndrew Thompson 			/* try to clear stall first */
883ed6d949aSAndrew Thompson 			usbd_xfer_set_stall(xfer);
88402ac6454SAndrew Thompson 			goto tr_setup;
88502ac6454SAndrew Thompson 		}
886ec97e9caSHans Petter Selasky 		return;
88702ac6454SAndrew Thompson 	}
88802ac6454SAndrew Thompson }
88902ac6454SAndrew Thompson 
89002ac6454SAndrew Thompson static void
umodem_read_callback(struct usb_xfer * xfer,usb_error_t error)891ed6d949aSAndrew Thompson umodem_read_callback(struct usb_xfer *xfer, usb_error_t error)
89202ac6454SAndrew Thompson {
893ed6d949aSAndrew Thompson 	struct umodem_softc *sc = usbd_xfer_softc(xfer);
894ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
895ed6d949aSAndrew Thompson 	int actlen;
896ed6d949aSAndrew Thompson 
897ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
89802ac6454SAndrew Thompson 
89902ac6454SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
90002ac6454SAndrew Thompson 	case USB_ST_TRANSFERRED:
90102ac6454SAndrew Thompson 
902ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d\n", actlen);
90302ac6454SAndrew Thompson 
904ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
905ed6d949aSAndrew Thompson 		ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
90602ac6454SAndrew Thompson 
90702ac6454SAndrew Thompson 	case USB_ST_SETUP:
90802ac6454SAndrew Thompson tr_setup:
909ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
910a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
91102ac6454SAndrew Thompson 		return;
91202ac6454SAndrew Thompson 
91302ac6454SAndrew Thompson 	default:			/* Error */
914ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
91502ac6454SAndrew Thompson 			/* try to clear stall first */
916ed6d949aSAndrew Thompson 			usbd_xfer_set_stall(xfer);
91702ac6454SAndrew Thompson 			goto tr_setup;
91802ac6454SAndrew Thompson 		}
91902ac6454SAndrew Thompson 		return;
92002ac6454SAndrew Thompson 	}
92102ac6454SAndrew Thompson }
92202ac6454SAndrew Thompson 
92302ac6454SAndrew Thompson static void *
umodem_get_desc(struct usb_attach_arg * uaa,uint8_t type,uint8_t subtype)924760bc48eSAndrew Thompson umodem_get_desc(struct usb_attach_arg *uaa, uint8_t type, uint8_t subtype)
92502ac6454SAndrew Thompson {
9263c8d24f4SAndrew Thompson 	return (usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex,
9276d917491SHans Petter Selasky 	    type, 0xFF, subtype, 0xFF));
92802ac6454SAndrew Thompson }
92902ac6454SAndrew Thompson 
930e0a69b51SAndrew Thompson static usb_error_t
umodem_set_comm_feature(struct usb_device * udev,uint8_t iface_no,uint16_t feature,uint16_t state)931760bc48eSAndrew Thompson umodem_set_comm_feature(struct usb_device *udev, uint8_t iface_no,
93202ac6454SAndrew Thompson     uint16_t feature, uint16_t state)
93302ac6454SAndrew Thompson {
934760bc48eSAndrew Thompson 	struct usb_device_request req;
935760bc48eSAndrew Thompson 	struct usb_cdc_abstract_state ast;
93602ac6454SAndrew Thompson 
93702ac6454SAndrew Thompson 	DPRINTF("feature=%d state=%d\n",
93802ac6454SAndrew Thompson 	    feature, state);
93902ac6454SAndrew Thompson 
94002ac6454SAndrew Thompson 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
94102ac6454SAndrew Thompson 	req.bRequest = UCDC_SET_COMM_FEATURE;
94202ac6454SAndrew Thompson 	USETW(req.wValue, feature);
94302ac6454SAndrew Thompson 	req.wIndex[0] = iface_no;
94402ac6454SAndrew Thompson 	req.wIndex[1] = 0;
94502ac6454SAndrew Thompson 	USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH);
94602ac6454SAndrew Thompson 	USETW(ast.wState, state);
94702ac6454SAndrew Thompson 
948a593f6b8SAndrew Thompson 	return (usbd_do_request(udev, NULL, &req, &ast));
94902ac6454SAndrew Thompson }
95002ac6454SAndrew Thompson 
95102ac6454SAndrew Thompson static int
umodem_detach(device_t dev)95202ac6454SAndrew Thompson umodem_detach(device_t dev)
95302ac6454SAndrew Thompson {
95402ac6454SAndrew Thompson 	struct umodem_softc *sc = device_get_softc(dev);
95502ac6454SAndrew Thompson 
95602ac6454SAndrew Thompson 	DPRINTF("sc=%p\n", sc);
95702ac6454SAndrew Thompson 
958015bb88fSNick Hibma 	ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
959a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(sc->sc_xfer, UMODEM_N_TRANSFER);
96002ac6454SAndrew Thompson 
961c01fc06eSHans Petter Selasky 	device_claim_softc(dev);
962c01fc06eSHans Petter Selasky 
963c01fc06eSHans Petter Selasky 	umodem_free_softc(sc);
964c01fc06eSHans Petter Selasky 
96502ac6454SAndrew Thompson 	return (0);
96602ac6454SAndrew Thompson }
967655dc9d0SAndrew Thompson 
9685805d178SHans Petter Selasky UCOM_UNLOAD_DRAIN(umodem);
9695805d178SHans Petter Selasky 
9705805d178SHans Petter Selasky static void
umodem_free_softc(struct umodem_softc * sc)971c01fc06eSHans Petter Selasky umodem_free_softc(struct umodem_softc *sc)
9725805d178SHans Petter Selasky {
9735805d178SHans Petter Selasky 	if (ucom_unref(&sc->sc_super_ucom)) {
9745805d178SHans Petter Selasky 		mtx_destroy(&sc->sc_mtx);
975c01fc06eSHans Petter Selasky 		device_free_softc(sc);
9765805d178SHans Petter Selasky 	}
9775805d178SHans Petter Selasky }
9785805d178SHans Petter Selasky 
9795805d178SHans Petter Selasky static void
umodem_free(struct ucom_softc * ucom)9805805d178SHans Petter Selasky umodem_free(struct ucom_softc *ucom)
9815805d178SHans Petter Selasky {
982c01fc06eSHans Petter Selasky 	umodem_free_softc(ucom->sc_parent);
9835805d178SHans Petter Selasky }
9845805d178SHans Petter Selasky 
985655dc9d0SAndrew Thompson static void
umodem_poll(struct ucom_softc * ucom)986655dc9d0SAndrew Thompson umodem_poll(struct ucom_softc *ucom)
987655dc9d0SAndrew Thompson {
988655dc9d0SAndrew Thompson 	struct umodem_softc *sc = ucom->sc_parent;
989655dc9d0SAndrew Thompson 	usbd_transfer_poll(sc->sc_xfer, UMODEM_N_TRANSFER);
990655dc9d0SAndrew Thompson }
99127aae196SHans Petter Selasky 
99227aae196SHans Petter Selasky static int
umodem_handle_request(device_t dev,const void * preq,void ** pptr,uint16_t * plen,uint16_t offset,uint8_t * pstate)99327aae196SHans Petter Selasky umodem_handle_request(device_t dev,
99427aae196SHans Petter Selasky     const void *preq, void **pptr, uint16_t *plen,
99527aae196SHans Petter Selasky     uint16_t offset, uint8_t *pstate)
99627aae196SHans Petter Selasky {
99727aae196SHans Petter Selasky 	struct umodem_softc *sc = device_get_softc(dev);
99827aae196SHans Petter Selasky 	const struct usb_device_request *req = preq;
99927aae196SHans Petter Selasky 	uint8_t is_complete = *pstate;
100027aae196SHans Petter Selasky 
100127aae196SHans Petter Selasky 	DPRINTF("sc=%p\n", sc);
100227aae196SHans Petter Selasky 
100327aae196SHans Petter Selasky 	if (!is_complete) {
100427aae196SHans Petter Selasky 		if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
100527aae196SHans Petter Selasky 		    (req->bRequest == UCDC_SET_LINE_CODING) &&
100627aae196SHans Petter Selasky 		    (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
100727aae196SHans Petter Selasky 		    (req->wIndex[1] == 0x00) &&
100827aae196SHans Petter Selasky 		    (req->wValue[0] == 0x00) &&
100927aae196SHans Petter Selasky 		    (req->wValue[1] == 0x00)) {
101027aae196SHans Petter Selasky 			if (offset == 0) {
101127aae196SHans Petter Selasky 				*plen = sizeof(sc->sc_line_coding);
101227aae196SHans Petter Selasky 				*pptr = &sc->sc_line_coding;
101327aae196SHans Petter Selasky 			} else {
101427aae196SHans Petter Selasky 				*plen = 0;
101527aae196SHans Petter Selasky 			}
101627aae196SHans Petter Selasky 			return (0);
101727aae196SHans Petter Selasky 		} else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
101827aae196SHans Petter Selasky 		    (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
101927aae196SHans Petter Selasky 		    (req->wIndex[1] == 0x00) &&
102027aae196SHans Petter Selasky 		    (req->bRequest == UCDC_SET_COMM_FEATURE)) {
102127aae196SHans Petter Selasky 			if (offset == 0) {
102227aae196SHans Petter Selasky 				*plen = sizeof(sc->sc_abstract_state);
102327aae196SHans Petter Selasky 				*pptr = &sc->sc_abstract_state;
102427aae196SHans Petter Selasky 			} else {
102527aae196SHans Petter Selasky 				*plen = 0;
102627aae196SHans Petter Selasky 			}
102727aae196SHans Petter Selasky 			return (0);
102827aae196SHans Petter Selasky 		} else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
102927aae196SHans Petter Selasky 		    (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
103027aae196SHans Petter Selasky 		    (req->wIndex[1] == 0x00) &&
103127aae196SHans Petter Selasky 		    (req->bRequest == UCDC_SET_CONTROL_LINE_STATE)) {
103227aae196SHans Petter Selasky 			*plen = 0;
103327aae196SHans Petter Selasky 			return (0);
103427aae196SHans Petter Selasky 		} else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
103527aae196SHans Petter Selasky 		    (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
103627aae196SHans Petter Selasky 		    (req->wIndex[1] == 0x00) &&
103727aae196SHans Petter Selasky 		    (req->bRequest == UCDC_SEND_BREAK)) {
103827aae196SHans Petter Selasky 			*plen = 0;
103927aae196SHans Petter Selasky 			return (0);
104027aae196SHans Petter Selasky 		}
104127aae196SHans Petter Selasky 	}
104227aae196SHans Petter Selasky 	return (ENXIO);			/* use builtin handler */
104327aae196SHans Petter Selasky }
1044