1223864Shselasky/*-
2223864Shselasky * Copyright (c) 2011 Anybots Inc
3223864Shselasky * written by Akinori Furukoshi <moonlightakkiy@yahoo.ca>
4223864Shselasky *  - ucom part is based on u3g.c
5223864Shselasky *
6223864Shselasky * Redistribution and use in source and binary forms, with or without
7223864Shselasky * modification, are permitted provided that the following conditions
8223864Shselasky * are met:
9223864Shselasky * 1. Redistributions of source code must retain the above copyright
10223864Shselasky *    notice, this list of conditions and the following disclaimer.
11223864Shselasky * 2. Redistributions in binary form must reproduce the above copyright
12223864Shselasky *    notice, this list of conditions and the following disclaimer in the
13223864Shselasky *    documentation and/or other materials provided with the distribution.
14223864Shselasky *
15223864Shselasky * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16223864Shselasky * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17223864Shselasky * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18223864Shselasky * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19223864Shselasky * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20223864Shselasky * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21223864Shselasky * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22223864Shselasky * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23223864Shselasky * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24223864Shselasky * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25223864Shselasky * SUCH DAMAGE.
26223864Shselasky */
27223864Shselasky
28223864Shselasky#include <sys/cdefs.h>
29223864Shselasky__FBSDID("$FreeBSD$");
30223864Shselasky
31223864Shselasky#include <sys/param.h>
32223864Shselasky#include <sys/systm.h>
33223864Shselasky#include <sys/queue.h>
34223864Shselasky#include <sys/systm.h>
35223864Shselasky#include <sys/kernel.h>
36223864Shselasky#include <sys/bus.h>
37223864Shselasky#include <sys/module.h>
38223864Shselasky#include <sys/sockio.h>
39223864Shselasky#include <sys/socket.h>
40223864Shselasky#include <sys/lock.h>
41223864Shselasky#include <sys/mutex.h>
42223864Shselasky#include <sys/condvar.h>
43223864Shselasky#include <sys/sysctl.h>
44223864Shselasky#include <sys/malloc.h>
45223864Shselasky#include <sys/taskqueue.h>
46223864Shselasky
47223864Shselasky#include <machine/bus.h>
48223864Shselasky
49223864Shselasky#include <net/if.h>
50223864Shselasky#include <net/if_types.h>
51223864Shselasky#include <net/netisr.h>
52223864Shselasky#include <net/bpf.h>
53223864Shselasky#include <net/ethernet.h>
54223864Shselasky
55223864Shselasky#include <netinet/in.h>
56223864Shselasky#include <netinet/ip.h>
57223864Shselasky#include <netinet/ip6.h>
58223864Shselasky#include <netinet/udp.h>
59223864Shselasky
60223864Shselasky#include <net80211/ieee80211_ioctl.h>
61223864Shselasky
62223864Shselasky#include <dev/usb/usb.h>
63223864Shselasky#include <dev/usb/usbdi.h>
64223864Shselasky#include <dev/usb/usbdi_util.h>
65223864Shselasky#include <dev/usb/usb_cdc.h>
66223864Shselasky#include "usbdevs.h"
67223864Shselasky
68223864Shselasky#define	USB_DEBUG_VAR usie_debug
69223864Shselasky#include <dev/usb/usb_debug.h>
70223864Shselasky#include <dev/usb/usb_process.h>
71223864Shselasky#include <dev/usb/usb_msctest.h>
72223864Shselasky
73223864Shselasky#include <dev/usb/serial/usb_serial.h>
74223864Shselasky
75223864Shselasky#include <dev/usb/net/if_usievar.h>
76223864Shselasky
77223864Shselasky#ifdef	USB_DEBUG
78223864Shselaskystatic int usie_debug = 0;
79223864Shselasky
80227309Sedstatic SYSCTL_NODE(_hw_usb, OID_AUTO, usie, CTLFLAG_RW, 0, "sierra USB modem");
81223864ShselaskySYSCTL_INT(_hw_usb_usie, OID_AUTO, debug, CTLFLAG_RW, &usie_debug, 0,
82223864Shselasky    "usie debug level");
83223864Shselasky#endif
84223864Shselasky
85223864Shselasky/* Sierra Wireless Direct IP modems */
86223864Shselaskystatic const STRUCT_USB_HOST_ID usie_devs[] = {
87223864Shselasky#define	USIE_DEV(v, d) {				\
88223864Shselasky    USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##d) }
89223864Shselasky	USIE_DEV(SIERRA, MC8700),
90223864Shselasky	USIE_DEV(SIERRA, TRUINSTALL),
91223864Shselasky	USIE_DEV(AIRPRIME, USB308),
92223864Shselasky#undef	USIE_DEV
93223864Shselasky};
94223864Shselasky
95223864Shselaskystatic device_probe_t usie_probe;
96223864Shselaskystatic device_attach_t usie_attach;
97223864Shselaskystatic device_detach_t usie_detach;
98239299Shselaskystatic void usie_free_softc(struct usie_softc *);
99223864Shselasky
100239180Shselaskystatic void usie_free(struct ucom_softc *);
101223864Shselaskystatic void usie_uc_update_line_state(struct ucom_softc *, uint8_t);
102223864Shselaskystatic void usie_uc_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
103223864Shselaskystatic void usie_uc_cfg_set_dtr(struct ucom_softc *, uint8_t);
104223864Shselaskystatic void usie_uc_cfg_set_rts(struct ucom_softc *, uint8_t);
105223864Shselaskystatic void usie_uc_cfg_open(struct ucom_softc *);
106223864Shselaskystatic void usie_uc_cfg_close(struct ucom_softc *);
107223864Shselaskystatic void usie_uc_start_read(struct ucom_softc *);
108223864Shselaskystatic void usie_uc_stop_read(struct ucom_softc *);
109223864Shselaskystatic void usie_uc_start_write(struct ucom_softc *);
110223864Shselaskystatic void usie_uc_stop_write(struct ucom_softc *);
111223864Shselasky
112223864Shselaskystatic usb_callback_t usie_uc_tx_callback;
113223864Shselaskystatic usb_callback_t usie_uc_rx_callback;
114223864Shselaskystatic usb_callback_t usie_uc_status_callback;
115223864Shselaskystatic usb_callback_t usie_if_tx_callback;
116223864Shselaskystatic usb_callback_t usie_if_rx_callback;
117223864Shselaskystatic usb_callback_t usie_if_status_callback;
118223864Shselasky
119223864Shselaskystatic void usie_if_sync_to(void *);
120223864Shselaskystatic void usie_if_sync_cb(void *, int);
121223864Shselaskystatic void usie_if_status_cb(void *, int);
122223864Shselasky
123223864Shselaskystatic void usie_if_start(struct ifnet *);
124249925Sglebiusstatic int usie_if_output(struct ifnet *, struct mbuf *,
125249925Sglebius	const struct sockaddr *, struct route *);
126223864Shselaskystatic void usie_if_init(void *);
127223864Shselaskystatic void usie_if_stop(struct usie_softc *);
128223864Shselaskystatic int usie_if_ioctl(struct ifnet *, u_long, caddr_t);
129223864Shselasky
130223864Shselaskystatic int usie_do_request(struct usie_softc *, struct usb_device_request *, void *);
131223864Shselaskystatic int usie_if_cmd(struct usie_softc *, uint8_t);
132223864Shselaskystatic void usie_cns_req(struct usie_softc *, uint32_t, uint16_t);
133223864Shselaskystatic void usie_cns_rsp(struct usie_softc *, struct usie_cns *);
134223864Shselaskystatic void usie_hip_rsp(struct usie_softc *, uint8_t *, uint32_t);
135223864Shselaskystatic int usie_driver_loaded(struct module *, int, void *);
136223864Shselasky
137223864Shselaskystatic const struct usb_config usie_uc_config[USIE_UC_N_XFER] = {
138223864Shselasky	[USIE_UC_STATUS] = {
139223864Shselasky		.type = UE_INTERRUPT,
140223864Shselasky		.endpoint = UE_ADDR_ANY,
141223864Shselasky		.direction = UE_DIR_IN,
142223864Shselasky		.bufsize = 0,		/* use wMaxPacketSize */
143223864Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
144223864Shselasky		.callback = &usie_uc_status_callback,
145223864Shselasky	},
146223864Shselasky	[USIE_UC_RX] = {
147223864Shselasky		.type = UE_BULK,
148223864Shselasky		.endpoint = UE_ADDR_ANY,
149223864Shselasky		.direction = UE_DIR_IN,
150223864Shselasky		.bufsize = USIE_BUFSIZE,
151223864Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,},
152223864Shselasky		.callback = &usie_uc_rx_callback,
153223864Shselasky	},
154223864Shselasky	[USIE_UC_TX] = {
155223864Shselasky		.type = UE_BULK,
156223864Shselasky		.endpoint = UE_ADDR_ANY,
157223864Shselasky		.direction = UE_DIR_OUT,
158223864Shselasky		.bufsize = USIE_BUFSIZE,
159223864Shselasky		.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
160223864Shselasky		.callback = &usie_uc_tx_callback,
161223864Shselasky	}
162223864Shselasky};
163223864Shselasky
164223864Shselaskystatic const struct usb_config usie_if_config[USIE_IF_N_XFER] = {
165223864Shselasky	[USIE_IF_STATUS] = {
166223864Shselasky		.type = UE_INTERRUPT,
167223864Shselasky		.endpoint = UE_ADDR_ANY,
168223864Shselasky		.direction = UE_DIR_IN,
169223864Shselasky		.bufsize = 0,		/* use wMaxPacketSize */
170223864Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
171223864Shselasky		.callback = &usie_if_status_callback,
172223864Shselasky	},
173223864Shselasky	[USIE_IF_RX] = {
174223864Shselasky		.type = UE_BULK,
175223864Shselasky		.endpoint = UE_ADDR_ANY,
176223864Shselasky		.direction = UE_DIR_IN,
177223864Shselasky		.bufsize = USIE_BUFSIZE,
178223864Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
179223864Shselasky		.callback = &usie_if_rx_callback,
180223864Shselasky	},
181223864Shselasky	[USIE_IF_TX] = {
182223864Shselasky		.type = UE_BULK,
183223864Shselasky		.endpoint = UE_ADDR_ANY,
184223864Shselasky		.direction = UE_DIR_OUT,
185223864Shselasky		.bufsize = MAX(USIE_BUFSIZE, MCLBYTES),
186223864Shselasky		.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
187223864Shselasky		.callback = &usie_if_tx_callback,
188223864Shselasky	}
189223864Shselasky};
190223864Shselasky
191223864Shselaskystatic device_method_t usie_methods[] = {
192223864Shselasky	DEVMETHOD(device_probe, usie_probe),
193223864Shselasky	DEVMETHOD(device_attach, usie_attach),
194223864Shselasky	DEVMETHOD(device_detach, usie_detach),
195239180Shselasky	DEVMETHOD_END
196223864Shselasky};
197223864Shselasky
198223864Shselaskystatic driver_t usie_driver = {
199223864Shselasky	.name = "usie",
200223864Shselasky	.methods = usie_methods,
201223864Shselasky	.size = sizeof(struct usie_softc),
202223864Shselasky};
203223864Shselasky
204223864Shselaskystatic devclass_t usie_devclass;
205223864Shselaskystatic eventhandler_tag usie_etag;
206223864Shselasky
207223864ShselaskyDRIVER_MODULE(usie, uhub, usie_driver, usie_devclass, usie_driver_loaded, 0);
208223864ShselaskyMODULE_DEPEND(usie, ucom, 1, 1, 1);
209223864ShselaskyMODULE_DEPEND(usie, usb, 1, 1, 1);
210223864ShselaskyMODULE_VERSION(usie, 1);
211223864Shselasky
212223864Shselaskystatic const struct ucom_callback usie_uc_callback = {
213223864Shselasky	.ucom_cfg_get_status = &usie_uc_cfg_get_status,
214223864Shselasky	.ucom_cfg_set_dtr = &usie_uc_cfg_set_dtr,
215223864Shselasky	.ucom_cfg_set_rts = &usie_uc_cfg_set_rts,
216223864Shselasky	.ucom_cfg_open = &usie_uc_cfg_open,
217223864Shselasky	.ucom_cfg_close = &usie_uc_cfg_close,
218223864Shselasky	.ucom_start_read = &usie_uc_start_read,
219223864Shselasky	.ucom_stop_read = &usie_uc_stop_read,
220223864Shselasky	.ucom_start_write = &usie_uc_start_write,
221223864Shselasky	.ucom_stop_write = &usie_uc_stop_write,
222239180Shselasky	.ucom_free = &usie_free,
223223864Shselasky};
224223864Shselasky
225223864Shselaskystatic void
226223864Shselaskyusie_autoinst(void *arg, struct usb_device *udev,
227223864Shselasky    struct usb_attach_arg *uaa)
228223864Shselasky{
229223864Shselasky	struct usb_interface *iface;
230223864Shselasky	struct usb_interface_descriptor *id;
231223864Shselasky	struct usb_device_request req;
232223864Shselasky	int err;
233223864Shselasky
234223864Shselasky	if (uaa->dev_state != UAA_DEV_READY)
235223864Shselasky		return;
236223864Shselasky
237223864Shselasky	iface = usbd_get_iface(udev, 0);
238223864Shselasky	if (iface == NULL)
239223864Shselasky		return;
240223864Shselasky
241223864Shselasky	id = iface->idesc;
242223864Shselasky	if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
243223864Shselasky		return;
244223864Shselasky
245223864Shselasky	if (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa) != 0)
246223864Shselasky		return;			/* no device match */
247223864Shselasky
248223864Shselasky	if (bootverbose) {
249223864Shselasky		DPRINTF("Ejecting %s %s\n",
250223864Shselasky		    usb_get_manufacturer(udev),
251223864Shselasky		    usb_get_product(udev));
252223864Shselasky	}
253223864Shselasky	req.bmRequestType = UT_VENDOR;
254223864Shselasky	req.bRequest = UR_SET_INTERFACE;
255223864Shselasky	USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP);
256223864Shselasky	USETW(req.wIndex, UHF_PORT_CONNECTION);
257223864Shselasky	USETW(req.wLength, 0);
258223864Shselasky
259223864Shselasky	/* at this moment there is no mutex */
260223864Shselasky	err = usbd_do_request_flags(udev, NULL, &req,
261223864Shselasky	    NULL, 0, NULL, 250 /* ms */ );
262223864Shselasky
263223864Shselasky	/* success, mark the udev as disappearing */
264223864Shselasky	if (err == 0)
265223864Shselasky		uaa->dev_state = UAA_DEV_EJECTING;
266223864Shselasky}
267223864Shselasky
268223864Shselaskystatic int
269223864Shselaskyusie_probe(device_t self)
270223864Shselasky{
271223864Shselasky	struct usb_attach_arg *uaa = device_get_ivars(self);
272223864Shselasky
273223864Shselasky	if (uaa->usb_mode != USB_MODE_HOST)
274223864Shselasky		return (ENXIO);
275223864Shselasky	if (uaa->info.bConfigIndex != USIE_CNFG_INDEX)
276223864Shselasky		return (ENXIO);
277223864Shselasky	if (uaa->info.bIfaceIndex != USIE_IFACE_INDEX)
278223864Shselasky		return (ENXIO);
279223864Shselasky	if (uaa->info.bInterfaceClass != UICLASS_VENDOR)
280223864Shselasky		return (ENXIO);
281223864Shselasky
282223864Shselasky	return (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa));
283223864Shselasky}
284223864Shselasky
285223864Shselaskystatic int
286223864Shselaskyusie_attach(device_t self)
287223864Shselasky{
288223864Shselasky	struct usie_softc *sc = device_get_softc(self);
289223864Shselasky	struct usb_attach_arg *uaa = device_get_ivars(self);
290223864Shselasky	struct ifnet *ifp;
291223864Shselasky	struct usb_interface *iface;
292223864Shselasky	struct usb_interface_descriptor *id;
293223864Shselasky	struct usb_device_request req;
294223864Shselasky	int err;
295223864Shselasky	uint16_t fwattr;
296223864Shselasky	uint8_t iface_index;
297223864Shselasky	uint8_t ifidx;
298223864Shselasky	uint8_t start;
299223864Shselasky
300223864Shselasky	device_set_usb_desc(self);
301223864Shselasky	sc->sc_udev = uaa->device;
302223864Shselasky	sc->sc_dev = self;
303223864Shselasky
304223864Shselasky	mtx_init(&sc->sc_mtx, "usie", MTX_NETWORK_LOCK, MTX_DEF);
305239180Shselasky	ucom_ref(&sc->sc_super_ucom);
306223864Shselasky
307223864Shselasky	TASK_INIT(&sc->sc_if_status_task, 0, usie_if_status_cb, sc);
308223864Shselasky	TASK_INIT(&sc->sc_if_sync_task, 0, usie_if_sync_cb, sc);
309223864Shselasky
310223864Shselasky	usb_callout_init_mtx(&sc->sc_if_sync_ch, &sc->sc_mtx, 0);
311223864Shselasky
312223864Shselasky	mtx_lock(&sc->sc_mtx);
313223864Shselasky
314223864Shselasky	/* set power mode to D0 */
315223864Shselasky	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
316223864Shselasky	req.bRequest = USIE_POWER;
317223864Shselasky	USETW(req.wValue, 0);
318223864Shselasky	USETW(req.wIndex, 0);
319223864Shselasky	USETW(req.wLength, 0);
320223864Shselasky	if (usie_do_request(sc, &req, NULL)) {
321223864Shselasky		mtx_unlock(&sc->sc_mtx);
322223864Shselasky		goto detach;
323223864Shselasky	}
324223864Shselasky	/* read fw attr */
325223864Shselasky	fwattr = 0;
326223864Shselasky	req.bmRequestType = UT_READ_VENDOR_DEVICE;
327223864Shselasky	req.bRequest = USIE_FW_ATTR;
328223864Shselasky	USETW(req.wValue, 0);
329223864Shselasky	USETW(req.wIndex, 0);
330223864Shselasky	USETW(req.wLength, sizeof(fwattr));
331223864Shselasky	if (usie_do_request(sc, &req, &fwattr)) {
332223864Shselasky		mtx_unlock(&sc->sc_mtx);
333223864Shselasky		goto detach;
334223864Shselasky	}
335223864Shselasky	mtx_unlock(&sc->sc_mtx);
336223864Shselasky
337223864Shselasky	/* check DHCP supports */
338223864Shselasky	DPRINTF("fwattr=%x\n", fwattr);
339223864Shselasky	if (!(fwattr & USIE_FW_DHCP)) {
340223864Shselasky		device_printf(self, "DHCP is not supported. A firmware upgrade might be needed.\n");
341223864Shselasky	}
342223864Shselasky
343223864Shselasky	/* find available interfaces */
344223864Shselasky	sc->sc_nucom = 0;
345223864Shselasky	for (ifidx = 0; ifidx < USIE_IFACE_MAX; ifidx++) {
346223864Shselasky		iface = usbd_get_iface(uaa->device, ifidx);
347223864Shselasky		if (iface == NULL)
348223864Shselasky			break;
349223864Shselasky
350223864Shselasky		id = usbd_get_interface_descriptor(iface);
351223864Shselasky		if ((id == NULL) || (id->bInterfaceClass != UICLASS_VENDOR))
352223864Shselasky			continue;
353223864Shselasky
354223864Shselasky		/* setup Direct IP transfer */
355223864Shselasky		if (id->bInterfaceNumber >= 7 && id->bNumEndpoints == 3) {
356223864Shselasky			sc->sc_if_ifnum = id->bInterfaceNumber;
357223864Shselasky			iface_index = ifidx;
358223864Shselasky
359223864Shselasky			DPRINTF("ifnum=%d, ifidx=%d\n",
360223864Shselasky			    sc->sc_if_ifnum, ifidx);
361223864Shselasky
362223864Shselasky			err = usbd_transfer_setup(uaa->device,
363223864Shselasky			    &iface_index, sc->sc_if_xfer, usie_if_config,
364223864Shselasky			    USIE_IF_N_XFER, sc, &sc->sc_mtx);
365223864Shselasky
366223864Shselasky			if (err == 0)
367223864Shselasky				continue;
368223864Shselasky
369223864Shselasky			device_printf(self,
370223864Shselasky			    "could not allocate USB transfers on "
371223864Shselasky			    "iface_index=%d, err=%s\n",
372223864Shselasky			    iface_index, usbd_errstr(err));
373223864Shselasky			goto detach;
374223864Shselasky		}
375223864Shselasky
376223864Shselasky		/* setup ucom */
377223864Shselasky		if (sc->sc_nucom >= USIE_UCOM_MAX)
378223864Shselasky			continue;
379223864Shselasky
380223864Shselasky		usbd_set_parent_iface(uaa->device, ifidx,
381223864Shselasky		    uaa->info.bIfaceIndex);
382223864Shselasky
383223864Shselasky		DPRINTF("NumEndpoints=%d bInterfaceNumber=%d\n",
384223864Shselasky		    id->bNumEndpoints, id->bInterfaceNumber);
385223864Shselasky
386223864Shselasky		if (id->bNumEndpoints == 2) {
387223864Shselasky			sc->sc_uc_xfer[sc->sc_nucom][0] = NULL;
388223864Shselasky			start = 1;
389223864Shselasky		} else
390223864Shselasky			start = 0;
391223864Shselasky
392223864Shselasky		err = usbd_transfer_setup(uaa->device, &ifidx,
393223864Shselasky		    sc->sc_uc_xfer[sc->sc_nucom] + start,
394223864Shselasky		    usie_uc_config + start, USIE_UC_N_XFER - start,
395223864Shselasky		    &sc->sc_ucom[sc->sc_nucom], &sc->sc_mtx);
396223864Shselasky
397223864Shselasky		if (err != 0) {
398223864Shselasky			DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err));
399223864Shselasky			continue;
400223864Shselasky		}
401223864Shselasky
402223864Shselasky		mtx_lock(&sc->sc_mtx);
403223864Shselasky		for (; start < USIE_UC_N_XFER; start++)
404223864Shselasky			usbd_xfer_set_stall(sc->sc_uc_xfer[sc->sc_nucom][start]);
405223864Shselasky		mtx_unlock(&sc->sc_mtx);
406223864Shselasky
407223864Shselasky		sc->sc_uc_ifnum[sc->sc_nucom] = id->bInterfaceNumber;
408223864Shselasky
409223864Shselasky		sc->sc_nucom++;		/* found a port */
410223864Shselasky	}
411223864Shselasky
412223864Shselasky	if (sc->sc_nucom == 0) {
413223864Shselasky		device_printf(self, "no comports found\n");
414223864Shselasky		goto detach;
415223864Shselasky	}
416223864Shselasky
417223864Shselasky	err = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
418223864Shselasky	    sc->sc_nucom, sc, &usie_uc_callback, &sc->sc_mtx);
419223864Shselasky
420223864Shselasky	if (err != 0) {
421223864Shselasky		DPRINTF("ucom_attach failed\n");
422223864Shselasky		goto detach;
423223864Shselasky	}
424223864Shselasky	DPRINTF("Found %d interfaces.\n", sc->sc_nucom);
425223864Shselasky
426223864Shselasky	/* setup ifnet (Direct IP) */
427223864Shselasky	sc->sc_ifp = ifp = if_alloc(IFT_OTHER);
428223864Shselasky
429223864Shselasky	if (ifp == NULL) {
430223864Shselasky		device_printf(self, "Could not allocate a network interface\n");
431223864Shselasky		goto detach;
432223864Shselasky	}
433223864Shselasky	if_initname(ifp, "usie", device_get_unit(self));
434223864Shselasky
435223864Shselasky	ifp->if_softc = sc;
436223864Shselasky	ifp->if_mtu = USIE_MTU_MAX;
437223864Shselasky	ifp->if_flags |= IFF_NOARP;
438223864Shselasky	ifp->if_init = usie_if_init;
439223864Shselasky	ifp->if_ioctl = usie_if_ioctl;
440223864Shselasky	ifp->if_start = usie_if_start;
441223864Shselasky	ifp->if_output = usie_if_output;
442223864Shselasky	IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen);
443223864Shselasky	ifp->if_snd.ifq_drv_maxlen = ifqmaxlen;
444223864Shselasky	IFQ_SET_READY(&ifp->if_snd);
445223864Shselasky
446223864Shselasky	if_attach(ifp);
447223864Shselasky	bpfattach(ifp, DLT_RAW, 0);
448223864Shselasky
449223864Shselasky	if (fwattr & USIE_PM_AUTO) {
450223864Shselasky		usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE);
451223864Shselasky		DPRINTF("enabling automatic suspend and resume\n");
452223864Shselasky	} else {
453223864Shselasky		usbd_set_power_mode(uaa->device, USB_POWER_MODE_ON);
454223864Shselasky		DPRINTF("USB power is always ON\n");
455223864Shselasky	}
456223864Shselasky
457223864Shselasky	DPRINTF("device attached\n");
458223864Shselasky	return (0);
459223864Shselasky
460223864Shselaskydetach:
461223864Shselasky	usie_detach(self);
462223864Shselasky	return (ENOMEM);
463223864Shselasky}
464223864Shselasky
465223864Shselaskystatic int
466223864Shselaskyusie_detach(device_t self)
467223864Shselasky{
468223864Shselasky	struct usie_softc *sc = device_get_softc(self);
469223864Shselasky	uint8_t x;
470223864Shselasky
471223864Shselasky	/* detach ifnet */
472223864Shselasky	if (sc->sc_ifp != NULL) {
473223864Shselasky		usie_if_stop(sc);
474223864Shselasky		usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER);
475223864Shselasky		bpfdetach(sc->sc_ifp);
476223864Shselasky		if_detach(sc->sc_ifp);
477223864Shselasky		if_free(sc->sc_ifp);
478223864Shselasky		sc->sc_ifp = NULL;
479223864Shselasky	}
480223864Shselasky	/* detach ucom */
481223864Shselasky	if (sc->sc_nucom > 0)
482223864Shselasky		ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
483223864Shselasky
484223864Shselasky	/* stop all USB transfers */
485223864Shselasky	usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER);
486223864Shselasky
487223864Shselasky	for (x = 0; x != USIE_UCOM_MAX; x++)
488223864Shselasky		usbd_transfer_unsetup(sc->sc_uc_xfer[x], USIE_UC_N_XFER);
489223864Shselasky
490223864Shselasky
491239299Shselasky	device_claim_softc(self);
492239299Shselasky
493239299Shselasky	usie_free_softc(sc);
494239299Shselasky
495223864Shselasky	return (0);
496223864Shselasky}
497223864Shselasky
498239180ShselaskyUCOM_UNLOAD_DRAIN(usie);
499239180Shselasky
500223864Shselaskystatic void
501239299Shselaskyusie_free_softc(struct usie_softc *sc)
502239180Shselasky{
503239180Shselasky	if (ucom_unref(&sc->sc_super_ucom)) {
504239299Shselasky		mtx_destroy(&sc->sc_mtx);
505239299Shselasky		device_free_softc(sc);
506239180Shselasky	}
507239180Shselasky}
508239180Shselasky
509239180Shselaskystatic void
510239180Shselaskyusie_free(struct ucom_softc *ucom)
511239180Shselasky{
512239299Shselasky	usie_free_softc(ucom->sc_parent);
513239180Shselasky}
514239180Shselasky
515239180Shselaskystatic void
516223864Shselaskyusie_uc_update_line_state(struct ucom_softc *ucom, uint8_t ls)
517223864Shselasky{
518223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
519223864Shselasky	struct usb_device_request req;
520223864Shselasky
521223864Shselasky	if (sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS] == NULL)
522223864Shselasky		return;
523223864Shselasky
524223864Shselasky	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
525223864Shselasky	req.bRequest = USIE_LINK_STATE;
526223864Shselasky	USETW(req.wValue, ls);
527223864Shselasky	USETW(req.wIndex, sc->sc_uc_ifnum[ucom->sc_subunit]);
528223864Shselasky	USETW(req.wLength, 0);
529223864Shselasky
530223864Shselasky	DPRINTF("sc_uc_ifnum=%d\n", sc->sc_uc_ifnum[ucom->sc_subunit]);
531223864Shselasky
532223864Shselasky	usie_do_request(sc, &req, NULL);
533223864Shselasky}
534223864Shselasky
535223864Shselaskystatic void
536223864Shselaskyusie_uc_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
537223864Shselasky{
538223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
539223864Shselasky
540223864Shselasky	*msr = sc->sc_msr;
541223864Shselasky	*lsr = sc->sc_lsr;
542223864Shselasky}
543223864Shselasky
544223864Shselaskystatic void
545223864Shselaskyusie_uc_cfg_set_dtr(struct ucom_softc *ucom, uint8_t flag)
546223864Shselasky{
547223864Shselasky	uint8_t dtr;
548223864Shselasky
549223864Shselasky	dtr = flag ? USIE_LS_DTR : 0;
550223864Shselasky	usie_uc_update_line_state(ucom, dtr);
551223864Shselasky}
552223864Shselasky
553223864Shselaskystatic void
554223864Shselaskyusie_uc_cfg_set_rts(struct ucom_softc *ucom, uint8_t flag)
555223864Shselasky{
556223864Shselasky	uint8_t rts;
557223864Shselasky
558223864Shselasky	rts = flag ? USIE_LS_RTS : 0;
559223864Shselasky	usie_uc_update_line_state(ucom, rts);
560223864Shselasky}
561223864Shselasky
562223864Shselaskystatic void
563223864Shselaskyusie_uc_cfg_open(struct ucom_softc *ucom)
564223864Shselasky{
565223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
566223864Shselasky
567223864Shselasky	/* usbd_transfer_start() is NULL safe */
568223864Shselasky
569223864Shselasky	usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]);
570223864Shselasky}
571223864Shselasky
572223864Shselaskystatic void
573223864Shselaskyusie_uc_cfg_close(struct ucom_softc *ucom)
574223864Shselasky{
575223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
576223864Shselasky
577223864Shselasky	usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]);
578223864Shselasky}
579223864Shselasky
580223864Shselaskystatic void
581223864Shselaskyusie_uc_start_read(struct ucom_softc *ucom)
582223864Shselasky{
583223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
584223864Shselasky
585223864Shselasky	usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]);
586223864Shselasky}
587223864Shselasky
588223864Shselaskystatic void
589223864Shselaskyusie_uc_stop_read(struct ucom_softc *ucom)
590223864Shselasky{
591223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
592223864Shselasky
593223864Shselasky	usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]);
594223864Shselasky}
595223864Shselasky
596223864Shselaskystatic void
597223864Shselaskyusie_uc_start_write(struct ucom_softc *ucom)
598223864Shselasky{
599223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
600223864Shselasky
601223864Shselasky	usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]);
602223864Shselasky}
603223864Shselasky
604223864Shselaskystatic void
605223864Shselaskyusie_uc_stop_write(struct ucom_softc *ucom)
606223864Shselasky{
607223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
608223864Shselasky
609223864Shselasky	usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]);
610223864Shselasky}
611223864Shselasky
612223864Shselaskystatic void
613223864Shselaskyusie_uc_rx_callback(struct usb_xfer *xfer, usb_error_t error)
614223864Shselasky{
615223864Shselasky	struct ucom_softc *ucom = usbd_xfer_softc(xfer);
616223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
617223864Shselasky	struct usb_page_cache *pc;
618223864Shselasky	uint32_t actlen;
619223864Shselasky
620223864Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
621223864Shselasky
622223864Shselasky	switch (USB_GET_STATE(xfer)) {
623223864Shselasky	case USB_ST_TRANSFERRED:
624223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
625223864Shselasky
626223864Shselasky		/* handle CnS response */
627223864Shselasky		if (ucom == sc->sc_ucom && actlen >= USIE_HIPCNS_MIN) {
628223864Shselasky
629223864Shselasky			DPRINTF("transferred=%u\n", actlen);
630223864Shselasky
631223864Shselasky			/* check if it is really CnS reply */
632223864Shselasky			usbd_copy_out(pc, 0, sc->sc_resp_temp, 1);
633223864Shselasky
634223864Shselasky			if (sc->sc_resp_temp[0] == USIE_HIP_FRM_CHR) {
635223864Shselasky
636223864Shselasky				/* verify actlen */
637223864Shselasky				if (actlen > USIE_BUFSIZE)
638223864Shselasky					actlen = USIE_BUFSIZE;
639223864Shselasky
640223864Shselasky				/* get complete message */
641223864Shselasky				usbd_copy_out(pc, 0, sc->sc_resp_temp, actlen);
642223864Shselasky				usie_hip_rsp(sc, sc->sc_resp_temp, actlen);
643223864Shselasky
644223864Shselasky				/* need to fall though */
645223864Shselasky				goto tr_setup;
646223864Shselasky			}
647223864Shselasky			/* else call ucom_put_data() */
648223864Shselasky		}
649223864Shselasky		/* standard ucom transfer */
650223864Shselasky		ucom_put_data(ucom, pc, 0, actlen);
651223864Shselasky
652223864Shselasky		/* fall though */
653223864Shselasky	case USB_ST_SETUP:
654223864Shselaskytr_setup:
655223864Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
656223864Shselasky		usbd_transfer_submit(xfer);
657223864Shselasky		break;
658223864Shselasky
659223864Shselasky	default:			/* Error */
660223864Shselasky		if (error != USB_ERR_CANCELLED) {
661223864Shselasky			usbd_xfer_set_stall(xfer);
662223864Shselasky			goto tr_setup;
663223864Shselasky		}
664223864Shselasky		break;
665223864Shselasky	}
666223864Shselasky}
667223864Shselasky
668223864Shselaskystatic void
669223864Shselaskyusie_uc_tx_callback(struct usb_xfer *xfer, usb_error_t error)
670223864Shselasky{
671223864Shselasky	struct ucom_softc *ucom = usbd_xfer_softc(xfer);
672223864Shselasky	struct usb_page_cache *pc;
673223864Shselasky	uint32_t actlen;
674223864Shselasky
675223864Shselasky	switch (USB_GET_STATE(xfer)) {
676223864Shselasky	case USB_ST_TRANSFERRED:
677223864Shselasky	case USB_ST_SETUP:
678223864Shselaskytr_setup:
679223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
680223864Shselasky
681223864Shselasky		/* handle CnS request */
682223864Shselasky		struct mbuf *m = usbd_xfer_get_priv(xfer);
683223864Shselasky
684223864Shselasky		if (m != NULL) {
685223864Shselasky			usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
686223864Shselasky			usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len);
687223864Shselasky			usbd_xfer_set_priv(xfer, NULL);
688223864Shselasky			usbd_transfer_submit(xfer);
689223864Shselasky			m_freem(m);
690223864Shselasky			break;
691223864Shselasky		}
692223864Shselasky		/* standard ucom transfer */
693223864Shselasky		if (ucom_get_data(ucom, pc, 0, USIE_BUFSIZE, &actlen)) {
694223864Shselasky			usbd_xfer_set_frame_len(xfer, 0, actlen);
695223864Shselasky			usbd_transfer_submit(xfer);
696223864Shselasky		}
697223864Shselasky		break;
698223864Shselasky
699223864Shselasky	default:			/* Error */
700223864Shselasky		if (error != USB_ERR_CANCELLED) {
701223864Shselasky			usbd_xfer_set_stall(xfer);
702223864Shselasky			goto tr_setup;
703223864Shselasky		}
704223864Shselasky		break;
705223864Shselasky	}
706223864Shselasky}
707223864Shselasky
708223864Shselaskystatic void
709223864Shselaskyusie_uc_status_callback(struct usb_xfer *xfer, usb_error_t error)
710223864Shselasky{
711223864Shselasky	struct usb_page_cache *pc;
712223864Shselasky	struct {
713223864Shselasky		struct usb_device_request req;
714223864Shselasky		uint16_t param;
715223864Shselasky	}      st;
716223864Shselasky	uint32_t actlen;
717223864Shselasky	uint16_t param;
718223864Shselasky
719223864Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
720223864Shselasky
721223864Shselasky	switch (USB_GET_STATE(xfer)) {
722223864Shselasky	case USB_ST_TRANSFERRED:
723223864Shselasky		DPRINTFN(4, "info received, actlen=%u\n", actlen);
724223864Shselasky
725223864Shselasky		if (actlen < sizeof(st)) {
726223864Shselasky			DPRINTF("data too short actlen=%u\n", actlen);
727223864Shselasky			goto tr_setup;
728223864Shselasky		}
729223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
730223864Shselasky		usbd_copy_out(pc, 0, &st, sizeof(st));
731223864Shselasky
732223864Shselasky		if (st.req.bmRequestType == 0xa1 && st.req.bRequest == 0x20) {
733223864Shselasky			struct ucom_softc *ucom = usbd_xfer_softc(xfer);
734223864Shselasky			struct usie_softc *sc = ucom->sc_parent;
735223864Shselasky
736223864Shselasky			param = le16toh(st.param);
737223864Shselasky			DPRINTF("param=%x\n", param);
738223864Shselasky			sc->sc_msr = sc->sc_lsr = 0;
739223864Shselasky			sc->sc_msr |= (param & USIE_DCD) ? SER_DCD : 0;
740223864Shselasky			sc->sc_msr |= (param & USIE_DSR) ? SER_DSR : 0;
741223864Shselasky			sc->sc_msr |= (param & USIE_RI) ? SER_RI : 0;
742223864Shselasky			sc->sc_msr |= (param & USIE_CTS) ? 0 : SER_CTS;
743223864Shselasky			sc->sc_msr |= (param & USIE_RTS) ? SER_RTS : 0;
744223864Shselasky			sc->sc_msr |= (param & USIE_DTR) ? SER_DTR : 0;
745223864Shselasky		}
746223864Shselasky		/* fall though */
747223864Shselasky	case USB_ST_SETUP:
748223864Shselaskytr_setup:
749223864Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
750223864Shselasky		usbd_transfer_submit(xfer);
751223864Shselasky		break;
752223864Shselasky
753223864Shselasky	default:			/* Error */
754223864Shselasky		DPRINTF("USB transfer error, %s\n",
755223864Shselasky		    usbd_errstr(error));
756223864Shselasky
757223864Shselasky		if (error != USB_ERR_CANCELLED) {
758223864Shselasky			usbd_xfer_set_stall(xfer);
759223864Shselasky			goto tr_setup;
760223864Shselasky		}
761223864Shselasky		break;
762223864Shselasky	}
763223864Shselasky}
764223864Shselasky
765223864Shselaskystatic void
766223864Shselaskyusie_if_rx_callback(struct usb_xfer *xfer, usb_error_t error)
767223864Shselasky{
768223864Shselasky	struct usie_softc *sc = usbd_xfer_softc(xfer);
769223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
770223864Shselasky	struct mbuf *m0;
771223864Shselasky	struct mbuf *m = NULL;
772223864Shselasky	struct usie_desc *rxd;
773223864Shselasky	uint32_t actlen;
774223864Shselasky	uint16_t err;
775223864Shselasky	uint16_t pkt;
776223864Shselasky	uint16_t ipl;
777223864Shselasky	uint16_t len;
778223864Shselasky	uint16_t diff;
779223864Shselasky	uint8_t pad;
780223864Shselasky	uint8_t ipv;
781223864Shselasky
782223864Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
783223864Shselasky
784223864Shselasky	switch (USB_GET_STATE(xfer)) {
785223864Shselasky	case USB_ST_TRANSFERRED:
786223864Shselasky		DPRINTFN(15, "rx done, actlen=%u\n", actlen);
787223864Shselasky
788223864Shselasky		if (actlen < sizeof(struct usie_hip)) {
789223864Shselasky			DPRINTF("data too short %u\n", actlen);
790223864Shselasky			goto tr_setup;
791223864Shselasky		}
792223864Shselasky		m = sc->sc_rxm;
793223864Shselasky		sc->sc_rxm = NULL;
794223864Shselasky
795223864Shselasky		/* fall though */
796223864Shselasky	case USB_ST_SETUP:
797223864Shselaskytr_setup:
798223864Shselasky
799223864Shselasky		if (sc->sc_rxm == NULL) {
800243857Sglebius			sc->sc_rxm = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR,
801223864Shselasky			    MJUMPAGESIZE /* could be bigger than MCLBYTES */ );
802223864Shselasky		}
803223864Shselasky		if (sc->sc_rxm == NULL) {
804223864Shselasky			DPRINTF("could not allocate Rx mbuf\n");
805223864Shselasky			ifp->if_ierrors++;
806223864Shselasky			usbd_xfer_set_stall(xfer);
807223864Shselasky			usbd_xfer_set_frames(xfer, 0);
808223864Shselasky		} else {
809223864Shselasky			/*
810223864Shselasky			 * Directly loading a mbuf cluster into DMA to
811223864Shselasky			 * save some data copying. This works because
812223864Shselasky			 * there is only one cluster.
813223864Shselasky			 */
814223864Shselasky			usbd_xfer_set_frame_data(xfer, 0,
815223864Shselasky			    mtod(sc->sc_rxm, caddr_t), MIN(MJUMPAGESIZE, USIE_RXSZ_MAX));
816223864Shselasky			usbd_xfer_set_frames(xfer, 1);
817223864Shselasky		}
818223864Shselasky		usbd_transfer_submit(xfer);
819223864Shselasky		break;
820223864Shselasky
821223864Shselasky	default:			/* Error */
822223864Shselasky		DPRINTF("USB transfer error, %s\n", usbd_errstr(error));
823223864Shselasky
824223864Shselasky		if (error != USB_ERR_CANCELLED) {
825223864Shselasky			/* try to clear stall first */
826223864Shselasky			usbd_xfer_set_stall(xfer);
827223864Shselasky			ifp->if_ierrors++;
828223864Shselasky			goto tr_setup;
829223864Shselasky		}
830223864Shselasky		if (sc->sc_rxm != NULL) {
831223864Shselasky			m_freem(sc->sc_rxm);
832223864Shselasky			sc->sc_rxm = NULL;
833223864Shselasky		}
834223864Shselasky		break;
835223864Shselasky	}
836223864Shselasky
837223864Shselasky	if (m == NULL)
838223864Shselasky		return;
839223864Shselasky
840223864Shselasky	mtx_unlock(&sc->sc_mtx);
841223864Shselasky
842223864Shselasky	m->m_pkthdr.len = m->m_len = actlen;
843223864Shselasky
844223864Shselasky	err = pkt = 0;
845223864Shselasky
846223864Shselasky	/* HW can aggregate multiple frames in a single USB xfer */
847223864Shselasky	for (;;) {
848223864Shselasky		rxd = mtod(m, struct usie_desc *);
849223864Shselasky
850223864Shselasky		len = be16toh(rxd->hip.len) & USIE_HIP_IP_LEN_MASK;
851223864Shselasky		pad = (rxd->hip.id & USIE_HIP_PAD) ? 1 : 0;
852223864Shselasky		ipl = (len - pad - ETHER_HDR_LEN);
853223864Shselasky		if (ipl >= len) {
854223864Shselasky			DPRINTF("Corrupt frame\n");
855223864Shselasky			m_freem(m);
856223864Shselasky			break;
857223864Shselasky		}
858223864Shselasky		diff = sizeof(struct usie_desc) + ipl + pad;
859223864Shselasky
860223864Shselasky		if (((rxd->hip.id & USIE_HIP_MASK) != USIE_HIP_IP) ||
861223864Shselasky		    (be16toh(rxd->desc_type) & USIE_TYPE_MASK) != USIE_IP_RX) {
862223864Shselasky			DPRINTF("received wrong type of packet\n");
863223864Shselasky			m->m_data += diff;
864223864Shselasky			m->m_pkthdr.len = (m->m_len -= diff);
865223864Shselasky			err++;
866223864Shselasky			if (m->m_pkthdr.len > 0)
867223864Shselasky				continue;
868223864Shselasky			m_freem(m);
869223864Shselasky			break;
870223864Shselasky		}
871223864Shselasky		switch (be16toh(rxd->ethhdr.ether_type)) {
872223864Shselasky		case ETHERTYPE_IP:
873223864Shselasky			ipv = NETISR_IP;
874223864Shselasky			break;
875223864Shselasky#ifdef INET6
876223864Shselasky		case ETHERTYPE_IPV6:
877223864Shselasky			ipv = NETISR_IPV6;
878223864Shselasky			break;
879223864Shselasky#endif
880223864Shselasky		default:
881223864Shselasky			DPRINTF("unsupported ether type\n");
882223864Shselasky			err++;
883223864Shselasky			break;
884223864Shselasky		}
885223864Shselasky
886223864Shselasky		/* the last packet */
887223864Shselasky		if (m->m_pkthdr.len <= diff) {
888223864Shselasky			m->m_data += (sizeof(struct usie_desc) + pad);
889223864Shselasky			m->m_pkthdr.len = m->m_len = ipl;
890223864Shselasky			m->m_pkthdr.rcvif = ifp;
891223864Shselasky			BPF_MTAP(sc->sc_ifp, m);
892223864Shselasky			netisr_dispatch(ipv, m);
893223864Shselasky			break;
894223864Shselasky		}
895223864Shselasky		/* copy aggregated frames to another mbuf */
896243857Sglebius		m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
897223864Shselasky		if (__predict_false(m0 == NULL)) {
898223864Shselasky			DPRINTF("could not allocate mbuf\n");
899223864Shselasky			err++;
900223864Shselasky			m_freem(m);
901223864Shselasky			break;
902223864Shselasky		}
903223864Shselasky		m_copydata(m, sizeof(struct usie_desc) + pad, ipl, mtod(m0, caddr_t));
904223864Shselasky		m0->m_pkthdr.rcvif = ifp;
905223864Shselasky		m0->m_pkthdr.len = m0->m_len = ipl;
906223864Shselasky
907223864Shselasky		BPF_MTAP(sc->sc_ifp, m0);
908223864Shselasky		netisr_dispatch(ipv, m0);
909223864Shselasky
910223864Shselasky		m->m_data += diff;
911223864Shselasky		m->m_pkthdr.len = (m->m_len -= diff);
912223864Shselasky	}
913223864Shselasky
914223864Shselasky	mtx_lock(&sc->sc_mtx);
915223864Shselasky
916223864Shselasky	ifp->if_ierrors += err;
917223864Shselasky	ifp->if_ipackets += pkt;
918223864Shselasky}
919223864Shselasky
920223864Shselaskystatic void
921223864Shselaskyusie_if_tx_callback(struct usb_xfer *xfer, usb_error_t error)
922223864Shselasky{
923223864Shselasky	struct usie_softc *sc = usbd_xfer_softc(xfer);
924223864Shselasky	struct usb_page_cache *pc;
925223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
926223864Shselasky	struct mbuf *m;
927223864Shselasky	uint16_t size;
928223864Shselasky
929223864Shselasky	switch (USB_GET_STATE(xfer)) {
930223864Shselasky	case USB_ST_TRANSFERRED:
931223864Shselasky		DPRINTFN(11, "transfer complete\n");
932223864Shselasky		ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
933223864Shselasky		ifp->if_opackets++;
934223864Shselasky
935223864Shselasky		/* fall though */
936223864Shselasky	case USB_ST_SETUP:
937223864Shselaskytr_setup:
938223864Shselasky
939223864Shselasky		if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
940223864Shselasky			break;
941223864Shselasky
942223864Shselasky		IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
943223864Shselasky		if (m == NULL)
944223864Shselasky			break;
945223864Shselasky
946233774Shselasky		if (m->m_pkthdr.len > (int)(MCLBYTES - ETHER_HDR_LEN +
947223864Shselasky		    ETHER_CRC_LEN - sizeof(sc->sc_txd))) {
948223864Shselasky			DPRINTF("packet len is too big: %d\n",
949223864Shselasky			    m->m_pkthdr.len);
950223864Shselasky			break;
951223864Shselasky		}
952223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
953223864Shselasky
954223864Shselasky		sc->sc_txd.hip.len = htobe16(m->m_pkthdr.len +
955223864Shselasky		    ETHER_HDR_LEN + ETHER_CRC_LEN);
956223864Shselasky		size = sizeof(sc->sc_txd);
957223864Shselasky
958223864Shselasky		usbd_copy_in(pc, 0, &sc->sc_txd, size);
959223864Shselasky		usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len);
960223864Shselasky		usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len +
961223864Shselasky		    size + ETHER_CRC_LEN);
962223864Shselasky
963223864Shselasky		BPF_MTAP(ifp, m);
964223864Shselasky
965223864Shselasky		m_freem(m);
966223864Shselasky
967223864Shselasky		usbd_transfer_submit(xfer);
968223864Shselasky		break;
969223864Shselasky
970223864Shselasky	default:			/* Error */
971223864Shselasky		DPRINTF("USB transfer error, %s\n",
972223864Shselasky		    usbd_errstr(error));
973223864Shselasky		ifp->if_oerrors++;
974223864Shselasky
975223864Shselasky		if (error != USB_ERR_CANCELLED) {
976223864Shselasky			usbd_xfer_set_stall(xfer);
977223864Shselasky			ifp->if_ierrors++;
978223864Shselasky			goto tr_setup;
979223864Shselasky		}
980223864Shselasky		break;
981223864Shselasky	}
982223864Shselasky}
983223864Shselasky
984223864Shselaskystatic void
985223864Shselaskyusie_if_status_callback(struct usb_xfer *xfer, usb_error_t error)
986223864Shselasky{
987223864Shselasky	struct usie_softc *sc = usbd_xfer_softc(xfer);
988223864Shselasky	struct usb_page_cache *pc;
989223864Shselasky	struct usb_cdc_notification cdc;
990223864Shselasky	uint32_t actlen;
991223864Shselasky
992223864Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
993223864Shselasky
994223864Shselasky	switch (USB_GET_STATE(xfer)) {
995223864Shselasky	case USB_ST_TRANSFERRED:
996223864Shselasky		DPRINTFN(4, "info received, actlen=%d\n", actlen);
997223864Shselasky
998223864Shselasky		/* usb_cdc_notification - .data[16] */
999223864Shselasky		if (actlen < (sizeof(cdc) - 16)) {
1000223864Shselasky			DPRINTF("data too short %d\n", actlen);
1001223864Shselasky			goto tr_setup;
1002223864Shselasky		}
1003223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
1004223864Shselasky		usbd_copy_out(pc, 0, &cdc, (sizeof(cdc) - 16));
1005223864Shselasky
1006223864Shselasky		DPRINTFN(4, "bNotification=%x\n", cdc.bNotification);
1007223864Shselasky
1008223864Shselasky		if (cdc.bNotification & UCDC_N_RESPONSE_AVAILABLE) {
1009223864Shselasky			taskqueue_enqueue(taskqueue_thread,
1010223864Shselasky			    &sc->sc_if_status_task);
1011223864Shselasky		}
1012223864Shselasky		/* fall though */
1013223864Shselasky	case USB_ST_SETUP:
1014223864Shselaskytr_setup:
1015223864Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
1016223864Shselasky		usbd_transfer_submit(xfer);
1017223864Shselasky		break;
1018223864Shselasky
1019223864Shselasky	default:			/* Error */
1020223864Shselasky		DPRINTF("USB transfer error, %s\n",
1021223864Shselasky		    usbd_errstr(error));
1022223864Shselasky
1023223864Shselasky		if (error != USB_ERR_CANCELLED) {
1024223864Shselasky			usbd_xfer_set_stall(xfer);
1025223864Shselasky			goto tr_setup;
1026223864Shselasky		}
1027223864Shselasky		break;
1028223864Shselasky	}
1029223864Shselasky}
1030223864Shselasky
1031223864Shselaskystatic void
1032223864Shselaskyusie_if_sync_to(void *arg)
1033223864Shselasky{
1034223864Shselasky	struct usie_softc *sc = arg;
1035223864Shselasky
1036223864Shselasky	taskqueue_enqueue(taskqueue_thread, &sc->sc_if_sync_task);
1037223864Shselasky}
1038223864Shselasky
1039223864Shselaskystatic void
1040223864Shselaskyusie_if_sync_cb(void *arg, int pending)
1041223864Shselasky{
1042223864Shselasky	struct usie_softc *sc = arg;
1043223864Shselasky
1044223864Shselasky	mtx_lock(&sc->sc_mtx);
1045223864Shselasky
1046223864Shselasky	/* call twice */
1047223864Shselasky	usie_if_cmd(sc, USIE_HIP_SYNC2M);
1048223864Shselasky	usie_if_cmd(sc, USIE_HIP_SYNC2M);
1049223864Shselasky
1050223864Shselasky	usb_callout_reset(&sc->sc_if_sync_ch, 2 * hz, usie_if_sync_to, sc);
1051223864Shselasky
1052223864Shselasky	mtx_unlock(&sc->sc_mtx);
1053223864Shselasky}
1054223864Shselasky
1055223864Shselaskystatic void
1056223864Shselaskyusie_if_status_cb(void *arg, int pending)
1057223864Shselasky{
1058223864Shselasky	struct usie_softc *sc = arg;
1059223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
1060223864Shselasky	struct usb_device_request req;
1061223864Shselasky	struct usie_hip *hip;
1062223864Shselasky	struct usie_lsi *lsi;
1063223864Shselasky	uint16_t actlen;
1064223864Shselasky	uint8_t ntries;
1065223864Shselasky	uint8_t pad;
1066223864Shselasky
1067223864Shselasky	mtx_lock(&sc->sc_mtx);
1068223864Shselasky
1069223864Shselasky	req.bmRequestType = UT_READ_CLASS_INTERFACE;
1070223864Shselasky	req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
1071223864Shselasky	USETW(req.wValue, 0);
1072223864Shselasky	USETW(req.wIndex, sc->sc_if_ifnum);
1073223864Shselasky	USETW(req.wLength, sizeof(sc->sc_status_temp));
1074223864Shselasky
1075223864Shselasky	for (ntries = 0; ntries != 10; ntries++) {
1076223864Shselasky		int err;
1077223864Shselasky
1078223864Shselasky		err = usbd_do_request_flags(sc->sc_udev,
1079223864Shselasky		    &sc->sc_mtx, &req, sc->sc_status_temp, USB_SHORT_XFER_OK,
1080223864Shselasky		    &actlen, USB_DEFAULT_TIMEOUT);
1081223864Shselasky
1082223864Shselasky		if (err == 0)
1083223864Shselasky			break;
1084223864Shselasky
1085223864Shselasky		DPRINTF("Control request failed: %s %d/10\n",
1086223864Shselasky		    usbd_errstr(err), ntries);
1087223864Shselasky
1088223864Shselasky		usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10));
1089223864Shselasky	}
1090223864Shselasky
1091223864Shselasky	if (ntries == 10) {
1092223864Shselasky		mtx_unlock(&sc->sc_mtx);
1093223864Shselasky		DPRINTF("Timeout\n");
1094223864Shselasky		return;
1095223864Shselasky	}
1096223864Shselasky
1097223864Shselasky	hip = (struct usie_hip *)sc->sc_status_temp;
1098223864Shselasky
1099223864Shselasky	pad = (hip->id & USIE_HIP_PAD) ? 1 : 0;
1100223864Shselasky
1101223864Shselasky	DPRINTF("hip.id=%x hip.len=%d actlen=%u pad=%d\n",
1102223864Shselasky	    hip->id, be16toh(hip->len), actlen, pad);
1103223864Shselasky
1104223864Shselasky	switch (hip->id & USIE_HIP_MASK) {
1105223864Shselasky	case USIE_HIP_SYNC2H:
1106223864Shselasky		usie_if_cmd(sc, USIE_HIP_SYNC2M);
1107223864Shselasky		break;
1108223864Shselasky	case USIE_HIP_RESTR:
1109223864Shselasky		usb_callout_stop(&sc->sc_if_sync_ch);
1110223864Shselasky		break;
1111223864Shselasky	case USIE_HIP_UMTS:
1112223864Shselasky		lsi = (struct usie_lsi *)(
1113223864Shselasky		    sc->sc_status_temp + sizeof(struct usie_hip) + pad);
1114223864Shselasky
1115223864Shselasky		DPRINTF("lsi.proto=%x lsi.len=%d\n", lsi->proto,
1116223864Shselasky		    be16toh(lsi->len));
1117223864Shselasky
1118223864Shselasky		if (lsi->proto != USIE_LSI_UMTS)
1119223864Shselasky			break;
1120223864Shselasky
1121223864Shselasky		if (lsi->area == USIE_LSI_AREA_NO ||
1122223864Shselasky		    lsi->area == USIE_LSI_AREA_NODATA) {
1123223864Shselasky			device_printf(sc->sc_dev, "no service available\n");
1124223864Shselasky			break;
1125223864Shselasky		}
1126223864Shselasky		if (lsi->state == USIE_LSI_STATE_IDLE) {
1127223864Shselasky			DPRINTF("lsi.state=%x\n", lsi->state);
1128223864Shselasky			break;
1129223864Shselasky		}
1130223864Shselasky		DPRINTF("ctx=%x\n", hip->param);
1131223864Shselasky		sc->sc_txd.hip.param = hip->param;
1132223864Shselasky
1133223864Shselasky		sc->sc_net.addr_len = lsi->pdp_addr_len;
1134223864Shselasky		memcpy(&sc->sc_net.dns1_addr, &lsi->dns1_addr, 16);
1135223864Shselasky		memcpy(&sc->sc_net.dns2_addr, &lsi->dns2_addr, 16);
1136223864Shselasky		memcpy(sc->sc_net.pdp_addr, lsi->pdp_addr, 16);
1137223864Shselasky		memcpy(sc->sc_net.gw_addr, lsi->gw_addr, 16);
1138223864Shselasky		ifp->if_flags |= IFF_UP;
1139223864Shselasky		ifp->if_drv_flags |= IFF_DRV_RUNNING;
1140223864Shselasky
1141223864Shselasky		device_printf(sc->sc_dev, "IP Addr=%d.%d.%d.%d\n",
1142223864Shselasky		    *lsi->pdp_addr, *(lsi->pdp_addr + 1),
1143223864Shselasky		    *(lsi->pdp_addr + 2), *(lsi->pdp_addr + 3));
1144223864Shselasky		device_printf(sc->sc_dev, "Gateway Addr=%d.%d.%d.%d\n",
1145223864Shselasky		    *lsi->gw_addr, *(lsi->gw_addr + 1),
1146223864Shselasky		    *(lsi->gw_addr + 2), *(lsi->gw_addr + 3));
1147223864Shselasky		device_printf(sc->sc_dev, "Prim NS Addr=%d.%d.%d.%d\n",
1148223864Shselasky		    *lsi->dns1_addr, *(lsi->dns1_addr + 1),
1149223864Shselasky		    *(lsi->dns1_addr + 2), *(lsi->dns1_addr + 3));
1150223864Shselasky		device_printf(sc->sc_dev, "Scnd NS Addr=%d.%d.%d.%d\n",
1151223864Shselasky		    *lsi->dns2_addr, *(lsi->dns2_addr + 1),
1152223864Shselasky		    *(lsi->dns2_addr + 2), *(lsi->dns2_addr + 3));
1153223864Shselasky
1154223864Shselasky		usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI);
1155223864Shselasky		break;
1156223864Shselasky
1157223864Shselasky	case USIE_HIP_RCGI:
1158223864Shselasky		/* ignore, workaround for sloppy windows */
1159223864Shselasky		break;
1160223864Shselasky	default:
1161223864Shselasky		DPRINTF("undefined msgid: %x\n", hip->id);
1162223864Shselasky		break;
1163223864Shselasky	}
1164223864Shselasky
1165223864Shselasky	mtx_unlock(&sc->sc_mtx);
1166223864Shselasky}
1167223864Shselasky
1168223864Shselaskystatic void
1169223864Shselaskyusie_if_start(struct ifnet *ifp)
1170223864Shselasky{
1171223864Shselasky	struct usie_softc *sc = ifp->if_softc;
1172223864Shselasky
1173223864Shselasky	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
1174223864Shselasky		DPRINTF("Not running\n");
1175223864Shselasky		return;
1176223864Shselasky	}
1177223864Shselasky	mtx_lock(&sc->sc_mtx);
1178223864Shselasky	usbd_transfer_start(sc->sc_if_xfer[USIE_IF_TX]);
1179223864Shselasky	mtx_unlock(&sc->sc_mtx);
1180223864Shselasky
1181223864Shselasky	DPRINTFN(3, "interface started\n");
1182223864Shselasky}
1183223864Shselasky
1184223864Shselaskystatic int
1185249925Sglebiususie_if_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst,
1186223864Shselasky    struct route *ro)
1187223864Shselasky{
1188223864Shselasky	int err;
1189223864Shselasky
1190223864Shselasky	DPRINTF("proto=%x\n", dst->sa_family);
1191223864Shselasky
1192223864Shselasky	switch (dst->sa_family) {
1193223864Shselasky#ifdef INET6
1194223864Shselasky	case AF_INET6;
1195223864Shselasky	/* fall though */
1196223864Shselasky#endif
1197223864Shselasky	case AF_INET:
1198223864Shselasky		break;
1199223864Shselasky
1200223864Shselasky		/* silently drop dhclient packets */
1201223864Shselasky	case AF_UNSPEC:
1202223864Shselasky		m_freem(m);
1203223864Shselasky		return (0);
1204223864Shselasky
1205223864Shselasky		/* drop other packet types */
1206223864Shselasky	default:
1207223864Shselasky		m_freem(m);
1208223864Shselasky		return (EAFNOSUPPORT);
1209223864Shselasky	}
1210223864Shselasky
1211223864Shselasky	err = (ifp->if_transmit)(ifp, m);
1212223864Shselasky	if (err) {
1213223864Shselasky		ifp->if_oerrors++;
1214223864Shselasky		return (ENOBUFS);
1215223864Shselasky	}
1216223864Shselasky	ifp->if_opackets++;
1217223864Shselasky
1218223864Shselasky	return (0);
1219223864Shselasky}
1220223864Shselasky
1221223864Shselaskystatic void
1222223864Shselaskyusie_if_init(void *arg)
1223223864Shselasky{
1224223864Shselasky	struct usie_softc *sc = arg;
1225223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
1226223864Shselasky	uint8_t i;
1227223864Shselasky
1228223864Shselasky	mtx_lock(&sc->sc_mtx);
1229223864Shselasky
1230223864Shselasky	/* write tx descriptor */
1231223864Shselasky	sc->sc_txd.hip.id = USIE_HIP_CTX;
1232223864Shselasky	sc->sc_txd.hip.param = 0;	/* init value */
1233223864Shselasky	sc->sc_txd.desc_type = htobe16(USIE_IP_TX);
1234223864Shselasky
1235223864Shselasky	for (i = 0; i != USIE_IF_N_XFER; i++)
1236223864Shselasky		usbd_xfer_set_stall(sc->sc_if_xfer[i]);
1237223864Shselasky
1238223864Shselasky	usbd_transfer_start(sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_RX]);
1239223864Shselasky	usbd_transfer_start(sc->sc_if_xfer[USIE_IF_STATUS]);
1240223864Shselasky	usbd_transfer_start(sc->sc_if_xfer[USIE_IF_RX]);
1241223864Shselasky
1242223864Shselasky	/* if not running, initiate the modem */
1243223864Shselasky	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
1244223864Shselasky		usie_cns_req(sc, USIE_CNS_ID_INIT, USIE_CNS_OB_LINK_UPDATE);
1245223864Shselasky
1246223864Shselasky	mtx_unlock(&sc->sc_mtx);
1247223864Shselasky
1248223864Shselasky	DPRINTF("ifnet initialized\n");
1249223864Shselasky}
1250223864Shselasky
1251223864Shselaskystatic void
1252223864Shselaskyusie_if_stop(struct usie_softc *sc)
1253223864Shselasky{
1254223864Shselasky	usb_callout_drain(&sc->sc_if_sync_ch);
1255223864Shselasky
1256223864Shselasky	mtx_lock(&sc->sc_mtx);
1257223864Shselasky
1258223864Shselasky	/* usie_cns_req() clears IFF_* flags */
1259223864Shselasky	usie_cns_req(sc, USIE_CNS_ID_STOP, USIE_CNS_OB_LINK_UPDATE);
1260223864Shselasky
1261223864Shselasky	usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_TX]);
1262223864Shselasky	usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_RX]);
1263223864Shselasky	usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_STATUS]);
1264223864Shselasky
1265223864Shselasky	/* shutdown device */
1266223864Shselasky	usie_if_cmd(sc, USIE_HIP_DOWN);
1267223864Shselasky
1268223864Shselasky	mtx_unlock(&sc->sc_mtx);
1269223864Shselasky}
1270223864Shselasky
1271223864Shselaskystatic int
1272223864Shselaskyusie_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
1273223864Shselasky{
1274223864Shselasky	struct usie_softc *sc = ifp->if_softc;
1275223864Shselasky	struct ieee80211req *ireq;
1276223864Shselasky	struct ieee80211req_sta_info si;
1277223864Shselasky	struct ifmediareq *ifmr;
1278223864Shselasky
1279223864Shselasky	switch (cmd) {
1280223864Shselasky	case SIOCSIFFLAGS:
1281223864Shselasky		if (ifp->if_flags & IFF_UP) {
1282223864Shselasky			if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
1283223864Shselasky				usie_if_init(sc);
1284223864Shselasky		} else {
1285223864Shselasky			if (ifp->if_drv_flags & IFF_DRV_RUNNING)
1286223864Shselasky				usie_if_stop(sc);
1287223864Shselasky		}
1288223864Shselasky		break;
1289223864Shselasky
1290223864Shselasky	case SIOCSIFCAP:
1291223864Shselasky		if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
1292223864Shselasky			device_printf(sc->sc_dev,
1293223864Shselasky			    "Connect to the network first.\n");
1294223864Shselasky			break;
1295223864Shselasky		}
1296223864Shselasky		mtx_lock(&sc->sc_mtx);
1297223864Shselasky		usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI);
1298223864Shselasky		mtx_unlock(&sc->sc_mtx);
1299223864Shselasky		break;
1300223864Shselasky
1301223864Shselasky	case SIOCG80211:
1302223864Shselasky		ireq = (struct ieee80211req *)data;
1303223864Shselasky
1304223864Shselasky		if (ireq->i_type != IEEE80211_IOC_STA_INFO)
1305223864Shselasky			break;
1306223864Shselasky
1307223864Shselasky		memset(&si, 0, sizeof(si));
1308223864Shselasky		si.isi_len = sizeof(si);
1309223864Shselasky		/*
1310223864Shselasky		 * ifconfig expects RSSI in 0.5dBm units
1311223864Shselasky		 * relative to the noise floor.
1312223864Shselasky		 */
1313223864Shselasky		si.isi_rssi = 2 * sc->sc_rssi;
1314223864Shselasky		if (copyout(&si, (uint8_t *)ireq->i_data + 8,
1315223864Shselasky		    sizeof(struct ieee80211req_sta_info)))
1316223864Shselasky			DPRINTF("copyout failed\n");
1317223864Shselasky		DPRINTF("80211\n");
1318223864Shselasky		break;
1319223864Shselasky
1320223864Shselasky	case SIOCGIFMEDIA:		/* to fool ifconfig */
1321223864Shselasky		ifmr = (struct ifmediareq *)data;
1322223864Shselasky		ifmr->ifm_count = 1;
1323223864Shselasky		DPRINTF("media\n");
1324223864Shselasky		break;
1325223864Shselasky
1326223864Shselasky	case SIOCSIFADDR:
1327223864Shselasky		break;
1328223864Shselasky
1329223864Shselasky	default:
1330223864Shselasky		return (EINVAL);
1331223864Shselasky	}
1332223864Shselasky	return (0);
1333223864Shselasky}
1334223864Shselasky
1335223864Shselaskystatic int
1336223864Shselaskyusie_do_request(struct usie_softc *sc, struct usb_device_request *req,
1337223864Shselasky    void *data)
1338223864Shselasky{
1339223864Shselasky	int err = 0;
1340223864Shselasky	int ntries;
1341223864Shselasky
1342223864Shselasky	mtx_assert(&sc->sc_mtx, MA_OWNED);
1343223864Shselasky
1344223864Shselasky	for (ntries = 0; ntries != 10; ntries++) {
1345223864Shselasky		err = usbd_do_request(sc->sc_udev,
1346223864Shselasky		    &sc->sc_mtx, req, data);
1347223864Shselasky		if (err == 0)
1348223864Shselasky			break;
1349223864Shselasky
1350223864Shselasky		DPRINTF("Control request failed: %s %d/10\n",
1351223864Shselasky		    usbd_errstr(err), ntries);
1352223864Shselasky
1353223864Shselasky		usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10));
1354223864Shselasky	}
1355223864Shselasky	return (err);
1356223864Shselasky}
1357223864Shselasky
1358223864Shselaskystatic int
1359223864Shselaskyusie_if_cmd(struct usie_softc *sc, uint8_t cmd)
1360223864Shselasky{
1361223864Shselasky	struct usb_device_request req;
1362223864Shselasky	struct usie_hip msg;
1363223864Shselasky
1364223864Shselasky	msg.len = 0;
1365223864Shselasky	msg.id = cmd;
1366223864Shselasky	msg.param = 0;
1367223864Shselasky
1368223864Shselasky	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
1369223864Shselasky	req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND;
1370223864Shselasky	USETW(req.wValue, 0);
1371223864Shselasky	USETW(req.wIndex, sc->sc_if_ifnum);
1372223864Shselasky	USETW(req.wLength, sizeof(msg));
1373223864Shselasky
1374223864Shselasky	DPRINTF("cmd=%x\n", cmd);
1375223864Shselasky
1376223864Shselasky	return (usie_do_request(sc, &req, &msg));
1377223864Shselasky}
1378223864Shselasky
1379223864Shselaskystatic void
1380223864Shselaskyusie_cns_req(struct usie_softc *sc, uint32_t id, uint16_t obj)
1381223864Shselasky{
1382223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
1383223864Shselasky	struct mbuf *m;
1384223864Shselasky	struct usb_xfer *xfer;
1385223864Shselasky	struct usie_hip *hip;
1386223864Shselasky	struct usie_cns *cns;
1387223864Shselasky	uint8_t *param;
1388223864Shselasky	uint8_t *tmp;
1389223864Shselasky	uint8_t cns_len;
1390223864Shselasky
1391243857Sglebius	m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
1392223864Shselasky	if (__predict_false(m == NULL)) {
1393223864Shselasky		DPRINTF("could not allocate mbuf\n");
1394223864Shselasky		ifp->if_ierrors++;
1395223864Shselasky		return;
1396223864Shselasky	}
1397223864Shselasky	/* to align usie_hip{} on 32 bit */
1398223864Shselasky	m->m_data += 3;
1399223864Shselasky	param = mtod(m, uint8_t *);
1400223864Shselasky	*param++ = USIE_HIP_FRM_CHR;
1401223864Shselasky	hip = (struct usie_hip *)param;
1402223864Shselasky	cns = (struct usie_cns *)(hip + 1);
1403223864Shselasky
1404223864Shselasky	tmp = param + USIE_HIPCNS_MIN - 2;
1405223864Shselasky
1406223864Shselasky	switch (obj) {
1407223864Shselasky	case USIE_CNS_OB_LINK_UPDATE:
1408223864Shselasky		cns_len = 2;
1409223864Shselasky		cns->op = USIE_CNS_OP_SET;
1410223864Shselasky		*tmp++ = 1;		/* profile ID, always use 1 for now */
1411223864Shselasky		*tmp++ = id == USIE_CNS_ID_INIT ? 1 : 0;
1412223864Shselasky		break;
1413223864Shselasky
1414223864Shselasky	case USIE_CNS_OB_PROF_WRITE:
1415223864Shselasky		cns_len = 245;
1416223864Shselasky		cns->op = USIE_CNS_OP_SET;
1417223864Shselasky		*tmp++ = 1;		/* profile ID, always use 1 for now */
1418223864Shselasky		*tmp++ = 2;
1419223864Shselasky		memcpy(tmp, &sc->sc_net, 34);
1420223864Shselasky		memset(tmp + 35, 0, 245 - 36);
1421223864Shselasky		tmp += 243;
1422223864Shselasky		break;
1423223864Shselasky
1424223864Shselasky	case USIE_CNS_OB_RSSI:
1425223864Shselasky		cns_len = 0;
1426223864Shselasky		cns->op = USIE_CNS_OP_REQ;
1427223864Shselasky		break;
1428223864Shselasky
1429223864Shselasky	default:
1430223864Shselasky		DPRINTF("unsupported CnS object type\n");
1431223864Shselasky		return;
1432223864Shselasky	}
1433223864Shselasky	*tmp = USIE_HIP_FRM_CHR;
1434223864Shselasky
1435223864Shselasky	hip->len = htobe16(sizeof(struct usie_cns) + cns_len);
1436223864Shselasky	hip->id = USIE_HIP_CNS2M;
1437223864Shselasky	hip->param = 0;			/* none for CnS */
1438223864Shselasky
1439223864Shselasky	cns->obj = htobe16(obj);
1440223864Shselasky	cns->id = htobe32(id);
1441223864Shselasky	cns->len = cns_len;
1442223864Shselasky	cns->rsv0 = cns->rsv1 = 0;	/* always '0' */
1443223864Shselasky
1444223864Shselasky	param = (uint8_t *)(cns + 1);
1445223864Shselasky
1446223864Shselasky	DPRINTF("param: %16D\n", param, ":");
1447223864Shselasky
1448223864Shselasky	m->m_pkthdr.len = m->m_len = USIE_HIPCNS_MIN + cns_len + 2;
1449223864Shselasky
1450223864Shselasky	xfer = sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_TX];
1451223864Shselasky
1452223864Shselasky	if (usbd_xfer_get_priv(xfer) == NULL) {
1453223864Shselasky		usbd_xfer_set_priv(xfer, m);
1454223864Shselasky		usbd_transfer_start(xfer);
1455223864Shselasky	} else {
1456223864Shselasky		DPRINTF("Dropped CNS event\n");
1457223864Shselasky		m_freem(m);
1458223864Shselasky	}
1459223864Shselasky}
1460223864Shselasky
1461223864Shselaskystatic void
1462223864Shselaskyusie_cns_rsp(struct usie_softc *sc, struct usie_cns *cns)
1463223864Shselasky{
1464223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
1465223864Shselasky
1466223864Shselasky	DPRINTF("received CnS\n");
1467223864Shselasky
1468223864Shselasky	switch (be16toh(cns->obj)) {
1469223864Shselasky	case USIE_CNS_OB_LINK_UPDATE:
1470223864Shselasky		if (be32toh(cns->id) & USIE_CNS_ID_INIT)
1471223864Shselasky			usie_if_sync_to(sc);
1472223864Shselasky		else if (be32toh(cns->id) & USIE_CNS_ID_STOP) {
1473223864Shselasky			ifp->if_flags &= ~IFF_UP;
1474223864Shselasky			ifp->if_drv_flags &=
1475223864Shselasky			    ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
1476223864Shselasky		} else
1477223864Shselasky			DPRINTF("undefined link update\n");
1478223864Shselasky		break;
1479223864Shselasky
1480223864Shselasky	case USIE_CNS_OB_RSSI:
1481223864Shselasky		sc->sc_rssi = be16toh(*(int16_t *)(cns + 1));
1482223864Shselasky		if (sc->sc_rssi <= 0)
1483223864Shselasky			device_printf(sc->sc_dev, "No signal\n");
1484223864Shselasky		else {
1485223864Shselasky			device_printf(sc->sc_dev, "RSSI=%ddBm\n",
1486223864Shselasky			    sc->sc_rssi - 110);
1487223864Shselasky		}
1488223864Shselasky		break;
1489223864Shselasky
1490223864Shselasky	case USIE_CNS_OB_PROF_WRITE:
1491223864Shselasky		break;
1492223864Shselasky
1493223864Shselasky	case USIE_CNS_OB_PDP_READ:
1494223864Shselasky		break;
1495223864Shselasky
1496223864Shselasky	default:
1497223864Shselasky		DPRINTF("undefined CnS\n");
1498223864Shselasky		break;
1499223864Shselasky	}
1500223864Shselasky}
1501223864Shselasky
1502223864Shselaskystatic void
1503223864Shselaskyusie_hip_rsp(struct usie_softc *sc, uint8_t *rsp, uint32_t len)
1504223864Shselasky{
1505223864Shselasky	struct usie_hip *hip;
1506223864Shselasky	struct usie_cns *cns;
1507223864Shselasky	uint32_t i;
1508223864Shselasky	uint32_t j;
1509223864Shselasky	uint32_t off;
1510223864Shselasky	uint8_t tmp[USIE_HIPCNS_MAX] __aligned(4);
1511223864Shselasky
1512223864Shselasky	for (off = 0; (off + USIE_HIPCNS_MIN) <= len; off++) {
1513223864Shselasky
1514223864Shselasky		uint8_t pad;
1515223864Shselasky
1516223864Shselasky		while ((off < len) && (rsp[off] == USIE_HIP_FRM_CHR))
1517223864Shselasky			off++;
1518223864Shselasky
1519223864Shselasky		/* Unstuff the bytes */
1520223864Shselasky		for (i = j = 0; ((i + off) < len) &&
1521223864Shselasky		    (j < USIE_HIPCNS_MAX); i++) {
1522223864Shselasky
1523223864Shselasky			if (rsp[i + off] == USIE_HIP_FRM_CHR)
1524223864Shselasky				break;
1525223864Shselasky
1526223864Shselasky			if (rsp[i + off] == USIE_HIP_ESC_CHR) {
1527223864Shselasky				if ((i + off + 1) >= len)
1528223864Shselasky					break;
1529223864Shselasky				tmp[j++] = rsp[i++ + off + 1] ^ 0x20;
1530223864Shselasky			} else {
1531223864Shselasky				tmp[j++] = rsp[i + off];
1532223864Shselasky			}
1533223864Shselasky		}
1534223864Shselasky
1535223864Shselasky		off += i;
1536223864Shselasky
1537223864Shselasky		DPRINTF("frame len=%d\n", j);
1538223864Shselasky
1539223864Shselasky		if (j < sizeof(struct usie_hip)) {
1540223864Shselasky			DPRINTF("too little data\n");
1541223864Shselasky			break;
1542223864Shselasky		}
1543223864Shselasky		/*
1544223864Shselasky		 * Make sure we are not reading the stack if something
1545223864Shselasky		 * is wrong.
1546223864Shselasky		 */
1547223864Shselasky		memset(tmp + j, 0, sizeof(tmp) - j);
1548223864Shselasky
1549223864Shselasky		hip = (struct usie_hip *)tmp;
1550223864Shselasky
1551223864Shselasky		DPRINTF("hip: len=%d msgID=%02x, param=%02x\n",
1552223864Shselasky		    be16toh(hip->len), hip->id, hip->param);
1553223864Shselasky
1554223864Shselasky		pad = (hip->id & USIE_HIP_PAD) ? 1 : 0;
1555223864Shselasky
1556223864Shselasky		if ((hip->id & USIE_HIP_MASK) == USIE_HIP_CNS2H) {
1557223864Shselasky			cns = (struct usie_cns *)(((uint8_t *)(hip + 1)) + pad);
1558223864Shselasky
1559223864Shselasky			if (j < (sizeof(struct usie_cns) +
1560223864Shselasky			    sizeof(struct usie_hip) + pad)) {
1561223864Shselasky				DPRINTF("too little data\n");
1562223864Shselasky				break;
1563223864Shselasky			}
1564223864Shselasky			DPRINTF("cns: obj=%04x, op=%02x, rsv0=%02x, "
1565223864Shselasky			    "app=%08x, rsv1=%02x, len=%d\n",
1566223864Shselasky			    be16toh(cns->obj), cns->op, cns->rsv0,
1567223864Shselasky			    be32toh(cns->id), cns->rsv1, cns->len);
1568223864Shselasky
1569223864Shselasky			if (cns->op & USIE_CNS_OP_ERR)
1570223864Shselasky				DPRINTF("CnS error response\n");
1571223864Shselasky			else
1572223864Shselasky				usie_cns_rsp(sc, cns);
1573223864Shselasky
1574223864Shselasky			i = sizeof(struct usie_hip) + pad + sizeof(struct usie_cns);
1575223864Shselasky			j = cns->len;
1576223864Shselasky		} else {
1577223864Shselasky			i = sizeof(struct usie_hip) + pad;
1578223864Shselasky			j = be16toh(hip->len);
1579223864Shselasky		}
1580223864Shselasky#ifdef	USB_DEBUG
1581223864Shselasky		if (usie_debug == 0)
1582223864Shselasky			continue;
1583223864Shselasky
1584223864Shselasky		while (i < USIE_HIPCNS_MAX && j > 0) {
1585223864Shselasky			DPRINTF("param[0x%02x] = 0x%02x\n", i, tmp[i]);
1586223864Shselasky			i++;
1587223864Shselasky			j--;
1588223864Shselasky		}
1589223864Shselasky#endif
1590223864Shselasky	}
1591223864Shselasky}
1592223864Shselasky
1593223864Shselaskystatic int
1594223864Shselaskyusie_driver_loaded(struct module *mod, int what, void *arg)
1595223864Shselasky{
1596223864Shselasky	switch (what) {
1597223864Shselasky	case MOD_LOAD:
1598223864Shselasky		/* register autoinstall handler */
1599223864Shselasky		usie_etag = EVENTHANDLER_REGISTER(usb_dev_configured,
1600223864Shselasky		    usie_autoinst, NULL, EVENTHANDLER_PRI_ANY);
1601223864Shselasky		break;
1602223864Shselasky	case MOD_UNLOAD:
1603223864Shselasky		EVENTHANDLER_DEREGISTER(usb_dev_configured, usie_etag);
1604223864Shselasky		break;
1605223864Shselasky	default:
1606223864Shselasky		return (EOPNOTSUPP);
1607223864Shselasky	}
1608223864Shselasky	return (0);
1609223864Shselasky}
1610223864Shselasky
1611