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
80248085Smariusstatic 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;
98240659Shselaskystatic void usie_free_softc(struct usie_softc *);
99223864Shselasky
100240659Shselaskystatic 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 *);
124223864Shselaskystatic int usie_if_output(struct ifnet *, struct mbuf *, struct sockaddr *, struct route *);
125223864Shselaskystatic void usie_if_init(void *);
126223864Shselaskystatic void usie_if_stop(struct usie_softc *);
127223864Shselaskystatic int usie_if_ioctl(struct ifnet *, u_long, caddr_t);
128223864Shselasky
129223864Shselaskystatic int usie_do_request(struct usie_softc *, struct usb_device_request *, void *);
130223864Shselaskystatic int usie_if_cmd(struct usie_softc *, uint8_t);
131223864Shselaskystatic void usie_cns_req(struct usie_softc *, uint32_t, uint16_t);
132223864Shselaskystatic void usie_cns_rsp(struct usie_softc *, struct usie_cns *);
133223864Shselaskystatic void usie_hip_rsp(struct usie_softc *, uint8_t *, uint32_t);
134223864Shselaskystatic int usie_driver_loaded(struct module *, int, void *);
135223864Shselasky
136223864Shselaskystatic const struct usb_config usie_uc_config[USIE_UC_N_XFER] = {
137223864Shselasky	[USIE_UC_STATUS] = {
138223864Shselasky		.type = UE_INTERRUPT,
139223864Shselasky		.endpoint = UE_ADDR_ANY,
140223864Shselasky		.direction = UE_DIR_IN,
141223864Shselasky		.bufsize = 0,		/* use wMaxPacketSize */
142223864Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
143223864Shselasky		.callback = &usie_uc_status_callback,
144223864Shselasky	},
145223864Shselasky	[USIE_UC_RX] = {
146223864Shselasky		.type = UE_BULK,
147223864Shselasky		.endpoint = UE_ADDR_ANY,
148223864Shselasky		.direction = UE_DIR_IN,
149223864Shselasky		.bufsize = USIE_BUFSIZE,
150223864Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,},
151223864Shselasky		.callback = &usie_uc_rx_callback,
152223864Shselasky	},
153223864Shselasky	[USIE_UC_TX] = {
154223864Shselasky		.type = UE_BULK,
155223864Shselasky		.endpoint = UE_ADDR_ANY,
156223864Shselasky		.direction = UE_DIR_OUT,
157223864Shselasky		.bufsize = USIE_BUFSIZE,
158223864Shselasky		.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
159223864Shselasky		.callback = &usie_uc_tx_callback,
160223864Shselasky	}
161223864Shselasky};
162223864Shselasky
163223864Shselaskystatic const struct usb_config usie_if_config[USIE_IF_N_XFER] = {
164223864Shselasky	[USIE_IF_STATUS] = {
165223864Shselasky		.type = UE_INTERRUPT,
166223864Shselasky		.endpoint = UE_ADDR_ANY,
167223864Shselasky		.direction = UE_DIR_IN,
168223864Shselasky		.bufsize = 0,		/* use wMaxPacketSize */
169223864Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
170223864Shselasky		.callback = &usie_if_status_callback,
171223864Shselasky	},
172223864Shselasky	[USIE_IF_RX] = {
173223864Shselasky		.type = UE_BULK,
174223864Shselasky		.endpoint = UE_ADDR_ANY,
175223864Shselasky		.direction = UE_DIR_IN,
176223864Shselasky		.bufsize = USIE_BUFSIZE,
177223864Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
178223864Shselasky		.callback = &usie_if_rx_callback,
179223864Shselasky	},
180223864Shselasky	[USIE_IF_TX] = {
181223864Shselasky		.type = UE_BULK,
182223864Shselasky		.endpoint = UE_ADDR_ANY,
183223864Shselasky		.direction = UE_DIR_OUT,
184223864Shselasky		.bufsize = MAX(USIE_BUFSIZE, MCLBYTES),
185223864Shselasky		.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
186223864Shselasky		.callback = &usie_if_tx_callback,
187223864Shselasky	}
188223864Shselasky};
189223864Shselasky
190223864Shselaskystatic device_method_t usie_methods[] = {
191223864Shselasky	DEVMETHOD(device_probe, usie_probe),
192223864Shselasky	DEVMETHOD(device_attach, usie_attach),
193223864Shselasky	DEVMETHOD(device_detach, usie_detach),
194240659Shselasky	DEVMETHOD_END
195223864Shselasky};
196223864Shselasky
197223864Shselaskystatic driver_t usie_driver = {
198223864Shselasky	.name = "usie",
199223864Shselasky	.methods = usie_methods,
200223864Shselasky	.size = sizeof(struct usie_softc),
201223864Shselasky};
202223864Shselasky
203223864Shselaskystatic devclass_t usie_devclass;
204223864Shselaskystatic eventhandler_tag usie_etag;
205223864Shselasky
206223864ShselaskyDRIVER_MODULE(usie, uhub, usie_driver, usie_devclass, usie_driver_loaded, 0);
207223864ShselaskyMODULE_DEPEND(usie, ucom, 1, 1, 1);
208223864ShselaskyMODULE_DEPEND(usie, usb, 1, 1, 1);
209223864ShselaskyMODULE_VERSION(usie, 1);
210223864Shselasky
211223864Shselaskystatic const struct ucom_callback usie_uc_callback = {
212223864Shselasky	.ucom_cfg_get_status = &usie_uc_cfg_get_status,
213223864Shselasky	.ucom_cfg_set_dtr = &usie_uc_cfg_set_dtr,
214223864Shselasky	.ucom_cfg_set_rts = &usie_uc_cfg_set_rts,
215223864Shselasky	.ucom_cfg_open = &usie_uc_cfg_open,
216223864Shselasky	.ucom_cfg_close = &usie_uc_cfg_close,
217223864Shselasky	.ucom_start_read = &usie_uc_start_read,
218223864Shselasky	.ucom_stop_read = &usie_uc_stop_read,
219223864Shselasky	.ucom_start_write = &usie_uc_start_write,
220223864Shselasky	.ucom_stop_write = &usie_uc_stop_write,
221240659Shselasky	.ucom_free = &usie_free,
222223864Shselasky};
223223864Shselasky
224223864Shselaskystatic void
225223864Shselaskyusie_autoinst(void *arg, struct usb_device *udev,
226223864Shselasky    struct usb_attach_arg *uaa)
227223864Shselasky{
228223864Shselasky	struct usb_interface *iface;
229223864Shselasky	struct usb_interface_descriptor *id;
230223864Shselasky	struct usb_device_request req;
231223864Shselasky	int err;
232223864Shselasky
233223864Shselasky	if (uaa->dev_state != UAA_DEV_READY)
234223864Shselasky		return;
235223864Shselasky
236223864Shselasky	iface = usbd_get_iface(udev, 0);
237223864Shselasky	if (iface == NULL)
238223864Shselasky		return;
239223864Shselasky
240223864Shselasky	id = iface->idesc;
241223864Shselasky	if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
242223864Shselasky		return;
243223864Shselasky
244223864Shselasky	if (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa) != 0)
245223864Shselasky		return;			/* no device match */
246223864Shselasky
247223864Shselasky	if (bootverbose) {
248223864Shselasky		DPRINTF("Ejecting %s %s\n",
249223864Shselasky		    usb_get_manufacturer(udev),
250223864Shselasky		    usb_get_product(udev));
251223864Shselasky	}
252223864Shselasky	req.bmRequestType = UT_VENDOR;
253223864Shselasky	req.bRequest = UR_SET_INTERFACE;
254223864Shselasky	USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP);
255223864Shselasky	USETW(req.wIndex, UHF_PORT_CONNECTION);
256223864Shselasky	USETW(req.wLength, 0);
257223864Shselasky
258223864Shselasky	/* at this moment there is no mutex */
259223864Shselasky	err = usbd_do_request_flags(udev, NULL, &req,
260223864Shselasky	    NULL, 0, NULL, 250 /* ms */ );
261223864Shselasky
262223864Shselasky	/* success, mark the udev as disappearing */
263223864Shselasky	if (err == 0)
264223864Shselasky		uaa->dev_state = UAA_DEV_EJECTING;
265223864Shselasky}
266223864Shselasky
267223864Shselaskystatic int
268223864Shselaskyusie_probe(device_t self)
269223864Shselasky{
270223864Shselasky	struct usb_attach_arg *uaa = device_get_ivars(self);
271223864Shselasky
272223864Shselasky	if (uaa->usb_mode != USB_MODE_HOST)
273223864Shselasky		return (ENXIO);
274223864Shselasky	if (uaa->info.bConfigIndex != USIE_CNFG_INDEX)
275223864Shselasky		return (ENXIO);
276223864Shselasky	if (uaa->info.bIfaceIndex != USIE_IFACE_INDEX)
277223864Shselasky		return (ENXIO);
278223864Shselasky	if (uaa->info.bInterfaceClass != UICLASS_VENDOR)
279223864Shselasky		return (ENXIO);
280223864Shselasky
281223864Shselasky	return (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa));
282223864Shselasky}
283223864Shselasky
284223864Shselaskystatic int
285223864Shselaskyusie_attach(device_t self)
286223864Shselasky{
287223864Shselasky	struct usie_softc *sc = device_get_softc(self);
288223864Shselasky	struct usb_attach_arg *uaa = device_get_ivars(self);
289223864Shselasky	struct ifnet *ifp;
290223864Shselasky	struct usb_interface *iface;
291223864Shselasky	struct usb_interface_descriptor *id;
292223864Shselasky	struct usb_device_request req;
293223864Shselasky	int err;
294223864Shselasky	uint16_t fwattr;
295223864Shselasky	uint8_t iface_index;
296223864Shselasky	uint8_t ifidx;
297223864Shselasky	uint8_t start;
298223864Shselasky
299223864Shselasky	device_set_usb_desc(self);
300223864Shselasky	sc->sc_udev = uaa->device;
301223864Shselasky	sc->sc_dev = self;
302223864Shselasky
303223864Shselasky	mtx_init(&sc->sc_mtx, "usie", MTX_NETWORK_LOCK, MTX_DEF);
304240659Shselasky	ucom_ref(&sc->sc_super_ucom);
305223864Shselasky
306223864Shselasky	TASK_INIT(&sc->sc_if_status_task, 0, usie_if_status_cb, sc);
307223864Shselasky	TASK_INIT(&sc->sc_if_sync_task, 0, usie_if_sync_cb, sc);
308223864Shselasky
309223864Shselasky	usb_callout_init_mtx(&sc->sc_if_sync_ch, &sc->sc_mtx, 0);
310223864Shselasky
311223864Shselasky	mtx_lock(&sc->sc_mtx);
312223864Shselasky
313223864Shselasky	/* set power mode to D0 */
314223864Shselasky	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
315223864Shselasky	req.bRequest = USIE_POWER;
316223864Shselasky	USETW(req.wValue, 0);
317223864Shselasky	USETW(req.wIndex, 0);
318223864Shselasky	USETW(req.wLength, 0);
319223864Shselasky	if (usie_do_request(sc, &req, NULL)) {
320223864Shselasky		mtx_unlock(&sc->sc_mtx);
321223864Shselasky		goto detach;
322223864Shselasky	}
323223864Shselasky	/* read fw attr */
324223864Shselasky	fwattr = 0;
325223864Shselasky	req.bmRequestType = UT_READ_VENDOR_DEVICE;
326223864Shselasky	req.bRequest = USIE_FW_ATTR;
327223864Shselasky	USETW(req.wValue, 0);
328223864Shselasky	USETW(req.wIndex, 0);
329223864Shselasky	USETW(req.wLength, sizeof(fwattr));
330223864Shselasky	if (usie_do_request(sc, &req, &fwattr)) {
331223864Shselasky		mtx_unlock(&sc->sc_mtx);
332223864Shselasky		goto detach;
333223864Shselasky	}
334223864Shselasky	mtx_unlock(&sc->sc_mtx);
335223864Shselasky
336223864Shselasky	/* check DHCP supports */
337223864Shselasky	DPRINTF("fwattr=%x\n", fwattr);
338223864Shselasky	if (!(fwattr & USIE_FW_DHCP)) {
339223864Shselasky		device_printf(self, "DHCP is not supported. A firmware upgrade might be needed.\n");
340223864Shselasky	}
341223864Shselasky
342223864Shselasky	/* find available interfaces */
343223864Shselasky	sc->sc_nucom = 0;
344223864Shselasky	for (ifidx = 0; ifidx < USIE_IFACE_MAX; ifidx++) {
345223864Shselasky		iface = usbd_get_iface(uaa->device, ifidx);
346223864Shselasky		if (iface == NULL)
347223864Shselasky			break;
348223864Shselasky
349223864Shselasky		id = usbd_get_interface_descriptor(iface);
350223864Shselasky		if ((id == NULL) || (id->bInterfaceClass != UICLASS_VENDOR))
351223864Shselasky			continue;
352223864Shselasky
353223864Shselasky		/* setup Direct IP transfer */
354223864Shselasky		if (id->bInterfaceNumber >= 7 && id->bNumEndpoints == 3) {
355223864Shselasky			sc->sc_if_ifnum = id->bInterfaceNumber;
356223864Shselasky			iface_index = ifidx;
357223864Shselasky
358223864Shselasky			DPRINTF("ifnum=%d, ifidx=%d\n",
359223864Shselasky			    sc->sc_if_ifnum, ifidx);
360223864Shselasky
361223864Shselasky			err = usbd_transfer_setup(uaa->device,
362223864Shselasky			    &iface_index, sc->sc_if_xfer, usie_if_config,
363223864Shselasky			    USIE_IF_N_XFER, sc, &sc->sc_mtx);
364223864Shselasky
365223864Shselasky			if (err == 0)
366223864Shselasky				continue;
367223864Shselasky
368223864Shselasky			device_printf(self,
369223864Shselasky			    "could not allocate USB transfers on "
370223864Shselasky			    "iface_index=%d, err=%s\n",
371223864Shselasky			    iface_index, usbd_errstr(err));
372223864Shselasky			goto detach;
373223864Shselasky		}
374223864Shselasky
375223864Shselasky		/* setup ucom */
376223864Shselasky		if (sc->sc_nucom >= USIE_UCOM_MAX)
377223864Shselasky			continue;
378223864Shselasky
379223864Shselasky		usbd_set_parent_iface(uaa->device, ifidx,
380223864Shselasky		    uaa->info.bIfaceIndex);
381223864Shselasky
382223864Shselasky		DPRINTF("NumEndpoints=%d bInterfaceNumber=%d\n",
383223864Shselasky		    id->bNumEndpoints, id->bInterfaceNumber);
384223864Shselasky
385223864Shselasky		if (id->bNumEndpoints == 2) {
386223864Shselasky			sc->sc_uc_xfer[sc->sc_nucom][0] = NULL;
387223864Shselasky			start = 1;
388223864Shselasky		} else
389223864Shselasky			start = 0;
390223864Shselasky
391223864Shselasky		err = usbd_transfer_setup(uaa->device, &ifidx,
392223864Shselasky		    sc->sc_uc_xfer[sc->sc_nucom] + start,
393223864Shselasky		    usie_uc_config + start, USIE_UC_N_XFER - start,
394223864Shselasky		    &sc->sc_ucom[sc->sc_nucom], &sc->sc_mtx);
395223864Shselasky
396223864Shselasky		if (err != 0) {
397223864Shselasky			DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err));
398223864Shselasky			continue;
399223864Shselasky		}
400223864Shselasky
401223864Shselasky		mtx_lock(&sc->sc_mtx);
402223864Shselasky		for (; start < USIE_UC_N_XFER; start++)
403223864Shselasky			usbd_xfer_set_stall(sc->sc_uc_xfer[sc->sc_nucom][start]);
404223864Shselasky		mtx_unlock(&sc->sc_mtx);
405223864Shselasky
406223864Shselasky		sc->sc_uc_ifnum[sc->sc_nucom] = id->bInterfaceNumber;
407223864Shselasky
408223864Shselasky		sc->sc_nucom++;		/* found a port */
409223864Shselasky	}
410223864Shselasky
411223864Shselasky	if (sc->sc_nucom == 0) {
412223864Shselasky		device_printf(self, "no comports found\n");
413223864Shselasky		goto detach;
414223864Shselasky	}
415223864Shselasky
416223864Shselasky	err = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
417223864Shselasky	    sc->sc_nucom, sc, &usie_uc_callback, &sc->sc_mtx);
418223864Shselasky
419223864Shselasky	if (err != 0) {
420223864Shselasky		DPRINTF("ucom_attach failed\n");
421223864Shselasky		goto detach;
422223864Shselasky	}
423223864Shselasky	DPRINTF("Found %d interfaces.\n", sc->sc_nucom);
424223864Shselasky
425223864Shselasky	/* setup ifnet (Direct IP) */
426223864Shselasky	sc->sc_ifp = ifp = if_alloc(IFT_OTHER);
427223864Shselasky
428223864Shselasky	if (ifp == NULL) {
429223864Shselasky		device_printf(self, "Could not allocate a network interface\n");
430223864Shselasky		goto detach;
431223864Shselasky	}
432223864Shselasky	if_initname(ifp, "usie", device_get_unit(self));
433223864Shselasky
434223864Shselasky	ifp->if_softc = sc;
435223864Shselasky	ifp->if_mtu = USIE_MTU_MAX;
436223864Shselasky	ifp->if_flags |= IFF_NOARP;
437223864Shselasky	ifp->if_init = usie_if_init;
438223864Shselasky	ifp->if_ioctl = usie_if_ioctl;
439223864Shselasky	ifp->if_start = usie_if_start;
440223864Shselasky	ifp->if_output = usie_if_output;
441223864Shselasky	IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen);
442223864Shselasky	ifp->if_snd.ifq_drv_maxlen = ifqmaxlen;
443223864Shselasky	IFQ_SET_READY(&ifp->if_snd);
444223864Shselasky
445223864Shselasky	if_attach(ifp);
446223864Shselasky	bpfattach(ifp, DLT_RAW, 0);
447223864Shselasky
448223864Shselasky	if (fwattr & USIE_PM_AUTO) {
449223864Shselasky		usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE);
450223864Shselasky		DPRINTF("enabling automatic suspend and resume\n");
451223864Shselasky	} else {
452223864Shselasky		usbd_set_power_mode(uaa->device, USB_POWER_MODE_ON);
453223864Shselasky		DPRINTF("USB power is always ON\n");
454223864Shselasky	}
455223864Shselasky
456223864Shselasky	DPRINTF("device attached\n");
457223864Shselasky	return (0);
458223864Shselasky
459223864Shselaskydetach:
460223864Shselasky	usie_detach(self);
461223864Shselasky	return (ENOMEM);
462223864Shselasky}
463223864Shselasky
464223864Shselaskystatic int
465223864Shselaskyusie_detach(device_t self)
466223864Shselasky{
467223864Shselasky	struct usie_softc *sc = device_get_softc(self);
468223864Shselasky	uint8_t x;
469223864Shselasky
470223864Shselasky	/* detach ifnet */
471223864Shselasky	if (sc->sc_ifp != NULL) {
472223864Shselasky		usie_if_stop(sc);
473223864Shselasky		usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER);
474223864Shselasky		bpfdetach(sc->sc_ifp);
475223864Shselasky		if_detach(sc->sc_ifp);
476223864Shselasky		if_free(sc->sc_ifp);
477223864Shselasky		sc->sc_ifp = NULL;
478223864Shselasky	}
479223864Shselasky	/* detach ucom */
480223864Shselasky	if (sc->sc_nucom > 0)
481223864Shselasky		ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
482223864Shselasky
483223864Shselasky	/* stop all USB transfers */
484223864Shselasky	usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER);
485223864Shselasky
486223864Shselasky	for (x = 0; x != USIE_UCOM_MAX; x++)
487223864Shselasky		usbd_transfer_unsetup(sc->sc_uc_xfer[x], USIE_UC_N_XFER);
488223864Shselasky
489223864Shselasky
490240659Shselasky	device_claim_softc(self);
491240659Shselasky
492240659Shselasky	usie_free_softc(sc);
493240659Shselasky
494223864Shselasky	return (0);
495223864Shselasky}
496223864Shselasky
497240659ShselaskyUCOM_UNLOAD_DRAIN(usie);
498240659Shselasky
499223864Shselaskystatic void
500240659Shselaskyusie_free_softc(struct usie_softc *sc)
501240659Shselasky{
502240659Shselasky	if (ucom_unref(&sc->sc_super_ucom)) {
503240659Shselasky		mtx_destroy(&sc->sc_mtx);
504240659Shselasky		device_free_softc(sc);
505240659Shselasky	}
506240659Shselasky}
507240659Shselasky
508240659Shselaskystatic void
509240659Shselaskyusie_free(struct ucom_softc *ucom)
510240659Shselasky{
511240659Shselasky	usie_free_softc(ucom->sc_parent);
512240659Shselasky}
513240659Shselasky
514240659Shselaskystatic void
515223864Shselaskyusie_uc_update_line_state(struct ucom_softc *ucom, uint8_t ls)
516223864Shselasky{
517223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
518223864Shselasky	struct usb_device_request req;
519223864Shselasky
520223864Shselasky	if (sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS] == NULL)
521223864Shselasky		return;
522223864Shselasky
523223864Shselasky	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
524223864Shselasky	req.bRequest = USIE_LINK_STATE;
525223864Shselasky	USETW(req.wValue, ls);
526223864Shselasky	USETW(req.wIndex, sc->sc_uc_ifnum[ucom->sc_subunit]);
527223864Shselasky	USETW(req.wLength, 0);
528223864Shselasky
529223864Shselasky	DPRINTF("sc_uc_ifnum=%d\n", sc->sc_uc_ifnum[ucom->sc_subunit]);
530223864Shselasky
531223864Shselasky	usie_do_request(sc, &req, NULL);
532223864Shselasky}
533223864Shselasky
534223864Shselaskystatic void
535223864Shselaskyusie_uc_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
536223864Shselasky{
537223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
538223864Shselasky
539223864Shselasky	*msr = sc->sc_msr;
540223864Shselasky	*lsr = sc->sc_lsr;
541223864Shselasky}
542223864Shselasky
543223864Shselaskystatic void
544223864Shselaskyusie_uc_cfg_set_dtr(struct ucom_softc *ucom, uint8_t flag)
545223864Shselasky{
546223864Shselasky	uint8_t dtr;
547223864Shselasky
548223864Shselasky	dtr = flag ? USIE_LS_DTR : 0;
549223864Shselasky	usie_uc_update_line_state(ucom, dtr);
550223864Shselasky}
551223864Shselasky
552223864Shselaskystatic void
553223864Shselaskyusie_uc_cfg_set_rts(struct ucom_softc *ucom, uint8_t flag)
554223864Shselasky{
555223864Shselasky	uint8_t rts;
556223864Shselasky
557223864Shselasky	rts = flag ? USIE_LS_RTS : 0;
558223864Shselasky	usie_uc_update_line_state(ucom, rts);
559223864Shselasky}
560223864Shselasky
561223864Shselaskystatic void
562223864Shselaskyusie_uc_cfg_open(struct ucom_softc *ucom)
563223864Shselasky{
564223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
565223864Shselasky
566223864Shselasky	/* usbd_transfer_start() is NULL safe */
567223864Shselasky
568223864Shselasky	usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]);
569223864Shselasky}
570223864Shselasky
571223864Shselaskystatic void
572223864Shselaskyusie_uc_cfg_close(struct ucom_softc *ucom)
573223864Shselasky{
574223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
575223864Shselasky
576223864Shselasky	usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]);
577223864Shselasky}
578223864Shselasky
579223864Shselaskystatic void
580223864Shselaskyusie_uc_start_read(struct ucom_softc *ucom)
581223864Shselasky{
582223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
583223864Shselasky
584223864Shselasky	usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]);
585223864Shselasky}
586223864Shselasky
587223864Shselaskystatic void
588223864Shselaskyusie_uc_stop_read(struct ucom_softc *ucom)
589223864Shselasky{
590223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
591223864Shselasky
592223864Shselasky	usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]);
593223864Shselasky}
594223864Shselasky
595223864Shselaskystatic void
596223864Shselaskyusie_uc_start_write(struct ucom_softc *ucom)
597223864Shselasky{
598223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
599223864Shselasky
600223864Shselasky	usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]);
601223864Shselasky}
602223864Shselasky
603223864Shselaskystatic void
604223864Shselaskyusie_uc_stop_write(struct ucom_softc *ucom)
605223864Shselasky{
606223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
607223864Shselasky
608223864Shselasky	usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]);
609223864Shselasky}
610223864Shselasky
611223864Shselaskystatic void
612223864Shselaskyusie_uc_rx_callback(struct usb_xfer *xfer, usb_error_t error)
613223864Shselasky{
614223864Shselasky	struct ucom_softc *ucom = usbd_xfer_softc(xfer);
615223864Shselasky	struct usie_softc *sc = ucom->sc_parent;
616223864Shselasky	struct usb_page_cache *pc;
617223864Shselasky	uint32_t actlen;
618223864Shselasky
619223864Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
620223864Shselasky
621223864Shselasky	switch (USB_GET_STATE(xfer)) {
622223864Shselasky	case USB_ST_TRANSFERRED:
623223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
624223864Shselasky
625223864Shselasky		/* handle CnS response */
626223864Shselasky		if (ucom == sc->sc_ucom && actlen >= USIE_HIPCNS_MIN) {
627223864Shselasky
628223864Shselasky			DPRINTF("transferred=%u\n", actlen);
629223864Shselasky
630223864Shselasky			/* check if it is really CnS reply */
631223864Shselasky			usbd_copy_out(pc, 0, sc->sc_resp_temp, 1);
632223864Shselasky
633223864Shselasky			if (sc->sc_resp_temp[0] == USIE_HIP_FRM_CHR) {
634223864Shselasky
635223864Shselasky				/* verify actlen */
636223864Shselasky				if (actlen > USIE_BUFSIZE)
637223864Shselasky					actlen = USIE_BUFSIZE;
638223864Shselasky
639223864Shselasky				/* get complete message */
640223864Shselasky				usbd_copy_out(pc, 0, sc->sc_resp_temp, actlen);
641223864Shselasky				usie_hip_rsp(sc, sc->sc_resp_temp, actlen);
642223864Shselasky
643223864Shselasky				/* need to fall though */
644223864Shselasky				goto tr_setup;
645223864Shselasky			}
646223864Shselasky			/* else call ucom_put_data() */
647223864Shselasky		}
648223864Shselasky		/* standard ucom transfer */
649223864Shselasky		ucom_put_data(ucom, pc, 0, actlen);
650223864Shselasky
651223864Shselasky		/* fall though */
652223864Shselasky	case USB_ST_SETUP:
653223864Shselaskytr_setup:
654223864Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
655223864Shselasky		usbd_transfer_submit(xfer);
656223864Shselasky		break;
657223864Shselasky
658223864Shselasky	default:			/* Error */
659223864Shselasky		if (error != USB_ERR_CANCELLED) {
660223864Shselasky			usbd_xfer_set_stall(xfer);
661223864Shselasky			goto tr_setup;
662223864Shselasky		}
663223864Shselasky		break;
664223864Shselasky	}
665223864Shselasky}
666223864Shselasky
667223864Shselaskystatic void
668223864Shselaskyusie_uc_tx_callback(struct usb_xfer *xfer, usb_error_t error)
669223864Shselasky{
670223864Shselasky	struct ucom_softc *ucom = usbd_xfer_softc(xfer);
671223864Shselasky	struct usb_page_cache *pc;
672223864Shselasky	uint32_t actlen;
673223864Shselasky
674223864Shselasky	switch (USB_GET_STATE(xfer)) {
675223864Shselasky	case USB_ST_TRANSFERRED:
676223864Shselasky	case USB_ST_SETUP:
677223864Shselaskytr_setup:
678223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
679223864Shselasky
680223864Shselasky		/* handle CnS request */
681223864Shselasky		struct mbuf *m = usbd_xfer_get_priv(xfer);
682223864Shselasky
683223864Shselasky		if (m != NULL) {
684223864Shselasky			usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
685223864Shselasky			usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len);
686223864Shselasky			usbd_xfer_set_priv(xfer, NULL);
687223864Shselasky			usbd_transfer_submit(xfer);
688223864Shselasky			m_freem(m);
689223864Shselasky			break;
690223864Shselasky		}
691223864Shselasky		/* standard ucom transfer */
692223864Shselasky		if (ucom_get_data(ucom, pc, 0, USIE_BUFSIZE, &actlen)) {
693223864Shselasky			usbd_xfer_set_frame_len(xfer, 0, actlen);
694223864Shselasky			usbd_transfer_submit(xfer);
695223864Shselasky		}
696223864Shselasky		break;
697223864Shselasky
698223864Shselasky	default:			/* Error */
699223864Shselasky		if (error != USB_ERR_CANCELLED) {
700223864Shselasky			usbd_xfer_set_stall(xfer);
701223864Shselasky			goto tr_setup;
702223864Shselasky		}
703223864Shselasky		break;
704223864Shselasky	}
705223864Shselasky}
706223864Shselasky
707223864Shselaskystatic void
708223864Shselaskyusie_uc_status_callback(struct usb_xfer *xfer, usb_error_t error)
709223864Shselasky{
710223864Shselasky	struct usb_page_cache *pc;
711223864Shselasky	struct {
712223864Shselasky		struct usb_device_request req;
713223864Shselasky		uint16_t param;
714223864Shselasky	}      st;
715223864Shselasky	uint32_t actlen;
716223864Shselasky	uint16_t param;
717223864Shselasky
718223864Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
719223864Shselasky
720223864Shselasky	switch (USB_GET_STATE(xfer)) {
721223864Shselasky	case USB_ST_TRANSFERRED:
722223864Shselasky		DPRINTFN(4, "info received, actlen=%u\n", actlen);
723223864Shselasky
724223864Shselasky		if (actlen < sizeof(st)) {
725223864Shselasky			DPRINTF("data too short actlen=%u\n", actlen);
726223864Shselasky			goto tr_setup;
727223864Shselasky		}
728223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
729223864Shselasky		usbd_copy_out(pc, 0, &st, sizeof(st));
730223864Shselasky
731223864Shselasky		if (st.req.bmRequestType == 0xa1 && st.req.bRequest == 0x20) {
732223864Shselasky			struct ucom_softc *ucom = usbd_xfer_softc(xfer);
733223864Shselasky			struct usie_softc *sc = ucom->sc_parent;
734223864Shselasky
735223864Shselasky			param = le16toh(st.param);
736223864Shselasky			DPRINTF("param=%x\n", param);
737223864Shselasky			sc->sc_msr = sc->sc_lsr = 0;
738223864Shselasky			sc->sc_msr |= (param & USIE_DCD) ? SER_DCD : 0;
739223864Shselasky			sc->sc_msr |= (param & USIE_DSR) ? SER_DSR : 0;
740223864Shselasky			sc->sc_msr |= (param & USIE_RI) ? SER_RI : 0;
741223864Shselasky			sc->sc_msr |= (param & USIE_CTS) ? 0 : SER_CTS;
742223864Shselasky			sc->sc_msr |= (param & USIE_RTS) ? SER_RTS : 0;
743223864Shselasky			sc->sc_msr |= (param & USIE_DTR) ? SER_DTR : 0;
744223864Shselasky		}
745223864Shselasky		/* fall though */
746223864Shselasky	case USB_ST_SETUP:
747223864Shselaskytr_setup:
748223864Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
749223864Shselasky		usbd_transfer_submit(xfer);
750223864Shselasky		break;
751223864Shselasky
752223864Shselasky	default:			/* Error */
753223864Shselasky		DPRINTF("USB transfer error, %s\n",
754223864Shselasky		    usbd_errstr(error));
755223864Shselasky
756223864Shselasky		if (error != USB_ERR_CANCELLED) {
757223864Shselasky			usbd_xfer_set_stall(xfer);
758223864Shselasky			goto tr_setup;
759223864Shselasky		}
760223864Shselasky		break;
761223864Shselasky	}
762223864Shselasky}
763223864Shselasky
764223864Shselaskystatic void
765223864Shselaskyusie_if_rx_callback(struct usb_xfer *xfer, usb_error_t error)
766223864Shselasky{
767223864Shselasky	struct usie_softc *sc = usbd_xfer_softc(xfer);
768223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
769223864Shselasky	struct mbuf *m0;
770223864Shselasky	struct mbuf *m = NULL;
771223864Shselasky	struct usie_desc *rxd;
772223864Shselasky	uint32_t actlen;
773223864Shselasky	uint16_t err;
774223864Shselasky	uint16_t pkt;
775223864Shselasky	uint16_t ipl;
776223864Shselasky	uint16_t len;
777223864Shselasky	uint16_t diff;
778223864Shselasky	uint8_t pad;
779223864Shselasky	uint8_t ipv;
780223864Shselasky
781223864Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
782223864Shselasky
783223864Shselasky	switch (USB_GET_STATE(xfer)) {
784223864Shselasky	case USB_ST_TRANSFERRED:
785223864Shselasky		DPRINTFN(15, "rx done, actlen=%u\n", actlen);
786223864Shselasky
787223864Shselasky		if (actlen < sizeof(struct usie_hip)) {
788223864Shselasky			DPRINTF("data too short %u\n", actlen);
789223864Shselasky			goto tr_setup;
790223864Shselasky		}
791223864Shselasky		m = sc->sc_rxm;
792223864Shselasky		sc->sc_rxm = NULL;
793223864Shselasky
794223864Shselasky		/* fall though */
795223864Shselasky	case USB_ST_SETUP:
796223864Shselaskytr_setup:
797223864Shselasky
798223864Shselasky		if (sc->sc_rxm == NULL) {
799248078Smarius			sc->sc_rxm = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR,
800223864Shselasky			    MJUMPAGESIZE /* could be bigger than MCLBYTES */ );
801223864Shselasky		}
802223864Shselasky		if (sc->sc_rxm == NULL) {
803223864Shselasky			DPRINTF("could not allocate Rx mbuf\n");
804223864Shselasky			ifp->if_ierrors++;
805223864Shselasky			usbd_xfer_set_stall(xfer);
806223864Shselasky			usbd_xfer_set_frames(xfer, 0);
807223864Shselasky		} else {
808223864Shselasky			/*
809223864Shselasky			 * Directly loading a mbuf cluster into DMA to
810223864Shselasky			 * save some data copying. This works because
811223864Shselasky			 * there is only one cluster.
812223864Shselasky			 */
813223864Shselasky			usbd_xfer_set_frame_data(xfer, 0,
814223864Shselasky			    mtod(sc->sc_rxm, caddr_t), MIN(MJUMPAGESIZE, USIE_RXSZ_MAX));
815223864Shselasky			usbd_xfer_set_frames(xfer, 1);
816223864Shselasky		}
817223864Shselasky		usbd_transfer_submit(xfer);
818223864Shselasky		break;
819223864Shselasky
820223864Shselasky	default:			/* Error */
821223864Shselasky		DPRINTF("USB transfer error, %s\n", usbd_errstr(error));
822223864Shselasky
823223864Shselasky		if (error != USB_ERR_CANCELLED) {
824223864Shselasky			/* try to clear stall first */
825223864Shselasky			usbd_xfer_set_stall(xfer);
826223864Shselasky			ifp->if_ierrors++;
827223864Shselasky			goto tr_setup;
828223864Shselasky		}
829223864Shselasky		if (sc->sc_rxm != NULL) {
830223864Shselasky			m_freem(sc->sc_rxm);
831223864Shselasky			sc->sc_rxm = NULL;
832223864Shselasky		}
833223864Shselasky		break;
834223864Shselasky	}
835223864Shselasky
836223864Shselasky	if (m == NULL)
837223864Shselasky		return;
838223864Shselasky
839223864Shselasky	mtx_unlock(&sc->sc_mtx);
840223864Shselasky
841223864Shselasky	m->m_pkthdr.len = m->m_len = actlen;
842223864Shselasky
843223864Shselasky	err = pkt = 0;
844223864Shselasky
845223864Shselasky	/* HW can aggregate multiple frames in a single USB xfer */
846223864Shselasky	for (;;) {
847223864Shselasky		rxd = mtod(m, struct usie_desc *);
848223864Shselasky
849223864Shselasky		len = be16toh(rxd->hip.len) & USIE_HIP_IP_LEN_MASK;
850223864Shselasky		pad = (rxd->hip.id & USIE_HIP_PAD) ? 1 : 0;
851223864Shselasky		ipl = (len - pad - ETHER_HDR_LEN);
852223864Shselasky		if (ipl >= len) {
853223864Shselasky			DPRINTF("Corrupt frame\n");
854223864Shselasky			m_freem(m);
855223864Shselasky			break;
856223864Shselasky		}
857223864Shselasky		diff = sizeof(struct usie_desc) + ipl + pad;
858223864Shselasky
859223864Shselasky		if (((rxd->hip.id & USIE_HIP_MASK) != USIE_HIP_IP) ||
860223864Shselasky		    (be16toh(rxd->desc_type) & USIE_TYPE_MASK) != USIE_IP_RX) {
861223864Shselasky			DPRINTF("received wrong type of packet\n");
862223864Shselasky			m->m_data += diff;
863223864Shselasky			m->m_pkthdr.len = (m->m_len -= diff);
864223864Shselasky			err++;
865223864Shselasky			if (m->m_pkthdr.len > 0)
866223864Shselasky				continue;
867223864Shselasky			m_freem(m);
868223864Shselasky			break;
869223864Shselasky		}
870223864Shselasky		switch (be16toh(rxd->ethhdr.ether_type)) {
871223864Shselasky		case ETHERTYPE_IP:
872223864Shselasky			ipv = NETISR_IP;
873223864Shselasky			break;
874223864Shselasky#ifdef INET6
875223864Shselasky		case ETHERTYPE_IPV6:
876223864Shselasky			ipv = NETISR_IPV6;
877223864Shselasky			break;
878223864Shselasky#endif
879223864Shselasky		default:
880223864Shselasky			DPRINTF("unsupported ether type\n");
881223864Shselasky			err++;
882223864Shselasky			break;
883223864Shselasky		}
884223864Shselasky
885223864Shselasky		/* the last packet */
886223864Shselasky		if (m->m_pkthdr.len <= diff) {
887223864Shselasky			m->m_data += (sizeof(struct usie_desc) + pad);
888223864Shselasky			m->m_pkthdr.len = m->m_len = ipl;
889223864Shselasky			m->m_pkthdr.rcvif = ifp;
890223864Shselasky			BPF_MTAP(sc->sc_ifp, m);
891223864Shselasky			netisr_dispatch(ipv, m);
892223864Shselasky			break;
893223864Shselasky		}
894223864Shselasky		/* copy aggregated frames to another mbuf */
895248078Smarius		m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
896223864Shselasky		if (__predict_false(m0 == NULL)) {
897223864Shselasky			DPRINTF("could not allocate mbuf\n");
898223864Shselasky			err++;
899223864Shselasky			m_freem(m);
900223864Shselasky			break;
901223864Shselasky		}
902223864Shselasky		m_copydata(m, sizeof(struct usie_desc) + pad, ipl, mtod(m0, caddr_t));
903223864Shselasky		m0->m_pkthdr.rcvif = ifp;
904223864Shselasky		m0->m_pkthdr.len = m0->m_len = ipl;
905223864Shselasky
906223864Shselasky		BPF_MTAP(sc->sc_ifp, m0);
907223864Shselasky		netisr_dispatch(ipv, m0);
908223864Shselasky
909223864Shselasky		m->m_data += diff;
910223864Shselasky		m->m_pkthdr.len = (m->m_len -= diff);
911223864Shselasky	}
912223864Shselasky
913223864Shselasky	mtx_lock(&sc->sc_mtx);
914223864Shselasky
915223864Shselasky	ifp->if_ierrors += err;
916223864Shselasky	ifp->if_ipackets += pkt;
917223864Shselasky}
918223864Shselasky
919223864Shselaskystatic void
920223864Shselaskyusie_if_tx_callback(struct usb_xfer *xfer, usb_error_t error)
921223864Shselasky{
922223864Shselasky	struct usie_softc *sc = usbd_xfer_softc(xfer);
923223864Shselasky	struct usb_page_cache *pc;
924223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
925223864Shselasky	struct mbuf *m;
926223864Shselasky	uint16_t size;
927223864Shselasky
928223864Shselasky	switch (USB_GET_STATE(xfer)) {
929223864Shselasky	case USB_ST_TRANSFERRED:
930223864Shselasky		DPRINTFN(11, "transfer complete\n");
931223864Shselasky		ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
932223864Shselasky		ifp->if_opackets++;
933223864Shselasky
934223864Shselasky		/* fall though */
935223864Shselasky	case USB_ST_SETUP:
936223864Shselaskytr_setup:
937223864Shselasky
938223864Shselasky		if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
939223864Shselasky			break;
940223864Shselasky
941223864Shselasky		IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
942223864Shselasky		if (m == NULL)
943223864Shselasky			break;
944223864Shselasky
945235000Shselasky		if (m->m_pkthdr.len > (int)(MCLBYTES - ETHER_HDR_LEN +
946223864Shselasky		    ETHER_CRC_LEN - sizeof(sc->sc_txd))) {
947223864Shselasky			DPRINTF("packet len is too big: %d\n",
948223864Shselasky			    m->m_pkthdr.len);
949223864Shselasky			break;
950223864Shselasky		}
951223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
952223864Shselasky
953223864Shselasky		sc->sc_txd.hip.len = htobe16(m->m_pkthdr.len +
954223864Shselasky		    ETHER_HDR_LEN + ETHER_CRC_LEN);
955223864Shselasky		size = sizeof(sc->sc_txd);
956223864Shselasky
957223864Shselasky		usbd_copy_in(pc, 0, &sc->sc_txd, size);
958223864Shselasky		usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len);
959223864Shselasky		usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len +
960223864Shselasky		    size + ETHER_CRC_LEN);
961223864Shselasky
962223864Shselasky		BPF_MTAP(ifp, m);
963223864Shselasky
964223864Shselasky		m_freem(m);
965223864Shselasky
966223864Shselasky		usbd_transfer_submit(xfer);
967223864Shselasky		break;
968223864Shselasky
969223864Shselasky	default:			/* Error */
970223864Shselasky		DPRINTF("USB transfer error, %s\n",
971223864Shselasky		    usbd_errstr(error));
972223864Shselasky		ifp->if_oerrors++;
973223864Shselasky
974223864Shselasky		if (error != USB_ERR_CANCELLED) {
975223864Shselasky			usbd_xfer_set_stall(xfer);
976223864Shselasky			ifp->if_ierrors++;
977223864Shselasky			goto tr_setup;
978223864Shselasky		}
979223864Shselasky		break;
980223864Shselasky	}
981223864Shselasky}
982223864Shselasky
983223864Shselaskystatic void
984223864Shselaskyusie_if_status_callback(struct usb_xfer *xfer, usb_error_t error)
985223864Shselasky{
986223864Shselasky	struct usie_softc *sc = usbd_xfer_softc(xfer);
987223864Shselasky	struct usb_page_cache *pc;
988223864Shselasky	struct usb_cdc_notification cdc;
989223864Shselasky	uint32_t actlen;
990223864Shselasky
991223864Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
992223864Shselasky
993223864Shselasky	switch (USB_GET_STATE(xfer)) {
994223864Shselasky	case USB_ST_TRANSFERRED:
995223864Shselasky		DPRINTFN(4, "info received, actlen=%d\n", actlen);
996223864Shselasky
997223864Shselasky		/* usb_cdc_notification - .data[16] */
998223864Shselasky		if (actlen < (sizeof(cdc) - 16)) {
999223864Shselasky			DPRINTF("data too short %d\n", actlen);
1000223864Shselasky			goto tr_setup;
1001223864Shselasky		}
1002223864Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
1003223864Shselasky		usbd_copy_out(pc, 0, &cdc, (sizeof(cdc) - 16));
1004223864Shselasky
1005223864Shselasky		DPRINTFN(4, "bNotification=%x\n", cdc.bNotification);
1006223864Shselasky
1007223864Shselasky		if (cdc.bNotification & UCDC_N_RESPONSE_AVAILABLE) {
1008223864Shselasky			taskqueue_enqueue(taskqueue_thread,
1009223864Shselasky			    &sc->sc_if_status_task);
1010223864Shselasky		}
1011223864Shselasky		/* fall though */
1012223864Shselasky	case USB_ST_SETUP:
1013223864Shselaskytr_setup:
1014223864Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
1015223864Shselasky		usbd_transfer_submit(xfer);
1016223864Shselasky		break;
1017223864Shselasky
1018223864Shselasky	default:			/* Error */
1019223864Shselasky		DPRINTF("USB transfer error, %s\n",
1020223864Shselasky		    usbd_errstr(error));
1021223864Shselasky
1022223864Shselasky		if (error != USB_ERR_CANCELLED) {
1023223864Shselasky			usbd_xfer_set_stall(xfer);
1024223864Shselasky			goto tr_setup;
1025223864Shselasky		}
1026223864Shselasky		break;
1027223864Shselasky	}
1028223864Shselasky}
1029223864Shselasky
1030223864Shselaskystatic void
1031223864Shselaskyusie_if_sync_to(void *arg)
1032223864Shselasky{
1033223864Shselasky	struct usie_softc *sc = arg;
1034223864Shselasky
1035223864Shselasky	taskqueue_enqueue(taskqueue_thread, &sc->sc_if_sync_task);
1036223864Shselasky}
1037223864Shselasky
1038223864Shselaskystatic void
1039223864Shselaskyusie_if_sync_cb(void *arg, int pending)
1040223864Shselasky{
1041223864Shselasky	struct usie_softc *sc = arg;
1042223864Shselasky
1043223864Shselasky	mtx_lock(&sc->sc_mtx);
1044223864Shselasky
1045223864Shselasky	/* call twice */
1046223864Shselasky	usie_if_cmd(sc, USIE_HIP_SYNC2M);
1047223864Shselasky	usie_if_cmd(sc, USIE_HIP_SYNC2M);
1048223864Shselasky
1049223864Shselasky	usb_callout_reset(&sc->sc_if_sync_ch, 2 * hz, usie_if_sync_to, sc);
1050223864Shselasky
1051223864Shselasky	mtx_unlock(&sc->sc_mtx);
1052223864Shselasky}
1053223864Shselasky
1054223864Shselaskystatic void
1055223864Shselaskyusie_if_status_cb(void *arg, int pending)
1056223864Shselasky{
1057223864Shselasky	struct usie_softc *sc = arg;
1058223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
1059223864Shselasky	struct usb_device_request req;
1060223864Shselasky	struct usie_hip *hip;
1061223864Shselasky	struct usie_lsi *lsi;
1062223864Shselasky	uint16_t actlen;
1063223864Shselasky	uint8_t ntries;
1064223864Shselasky	uint8_t pad;
1065223864Shselasky
1066223864Shselasky	mtx_lock(&sc->sc_mtx);
1067223864Shselasky
1068223864Shselasky	req.bmRequestType = UT_READ_CLASS_INTERFACE;
1069223864Shselasky	req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
1070223864Shselasky	USETW(req.wValue, 0);
1071223864Shselasky	USETW(req.wIndex, sc->sc_if_ifnum);
1072223864Shselasky	USETW(req.wLength, sizeof(sc->sc_status_temp));
1073223864Shselasky
1074223864Shselasky	for (ntries = 0; ntries != 10; ntries++) {
1075223864Shselasky		int err;
1076223864Shselasky
1077223864Shselasky		err = usbd_do_request_flags(sc->sc_udev,
1078223864Shselasky		    &sc->sc_mtx, &req, sc->sc_status_temp, USB_SHORT_XFER_OK,
1079223864Shselasky		    &actlen, USB_DEFAULT_TIMEOUT);
1080223864Shselasky
1081223864Shselasky		if (err == 0)
1082223864Shselasky			break;
1083223864Shselasky
1084223864Shselasky		DPRINTF("Control request failed: %s %d/10\n",
1085223864Shselasky		    usbd_errstr(err), ntries);
1086223864Shselasky
1087223864Shselasky		usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10));
1088223864Shselasky	}
1089223864Shselasky
1090223864Shselasky	if (ntries == 10) {
1091223864Shselasky		mtx_unlock(&sc->sc_mtx);
1092223864Shselasky		DPRINTF("Timeout\n");
1093223864Shselasky		return;
1094223864Shselasky	}
1095223864Shselasky
1096223864Shselasky	hip = (struct usie_hip *)sc->sc_status_temp;
1097223864Shselasky
1098223864Shselasky	pad = (hip->id & USIE_HIP_PAD) ? 1 : 0;
1099223864Shselasky
1100223864Shselasky	DPRINTF("hip.id=%x hip.len=%d actlen=%u pad=%d\n",
1101223864Shselasky	    hip->id, be16toh(hip->len), actlen, pad);
1102223864Shselasky
1103223864Shselasky	switch (hip->id & USIE_HIP_MASK) {
1104223864Shselasky	case USIE_HIP_SYNC2H:
1105223864Shselasky		usie_if_cmd(sc, USIE_HIP_SYNC2M);
1106223864Shselasky		break;
1107223864Shselasky	case USIE_HIP_RESTR:
1108223864Shselasky		usb_callout_stop(&sc->sc_if_sync_ch);
1109223864Shselasky		break;
1110223864Shselasky	case USIE_HIP_UMTS:
1111223864Shselasky		lsi = (struct usie_lsi *)(
1112223864Shselasky		    sc->sc_status_temp + sizeof(struct usie_hip) + pad);
1113223864Shselasky
1114223864Shselasky		DPRINTF("lsi.proto=%x lsi.len=%d\n", lsi->proto,
1115223864Shselasky		    be16toh(lsi->len));
1116223864Shselasky
1117223864Shselasky		if (lsi->proto != USIE_LSI_UMTS)
1118223864Shselasky			break;
1119223864Shselasky
1120223864Shselasky		if (lsi->area == USIE_LSI_AREA_NO ||
1121223864Shselasky		    lsi->area == USIE_LSI_AREA_NODATA) {
1122223864Shselasky			device_printf(sc->sc_dev, "no service available\n");
1123223864Shselasky			break;
1124223864Shselasky		}
1125223864Shselasky		if (lsi->state == USIE_LSI_STATE_IDLE) {
1126223864Shselasky			DPRINTF("lsi.state=%x\n", lsi->state);
1127223864Shselasky			break;
1128223864Shselasky		}
1129223864Shselasky		DPRINTF("ctx=%x\n", hip->param);
1130223864Shselasky		sc->sc_txd.hip.param = hip->param;
1131223864Shselasky
1132223864Shselasky		sc->sc_net.addr_len = lsi->pdp_addr_len;
1133223864Shselasky		memcpy(&sc->sc_net.dns1_addr, &lsi->dns1_addr, 16);
1134223864Shselasky		memcpy(&sc->sc_net.dns2_addr, &lsi->dns2_addr, 16);
1135223864Shselasky		memcpy(sc->sc_net.pdp_addr, lsi->pdp_addr, 16);
1136223864Shselasky		memcpy(sc->sc_net.gw_addr, lsi->gw_addr, 16);
1137223864Shselasky		ifp->if_flags |= IFF_UP;
1138223864Shselasky		ifp->if_drv_flags |= IFF_DRV_RUNNING;
1139223864Shselasky
1140223864Shselasky		device_printf(sc->sc_dev, "IP Addr=%d.%d.%d.%d\n",
1141223864Shselasky		    *lsi->pdp_addr, *(lsi->pdp_addr + 1),
1142223864Shselasky		    *(lsi->pdp_addr + 2), *(lsi->pdp_addr + 3));
1143223864Shselasky		device_printf(sc->sc_dev, "Gateway Addr=%d.%d.%d.%d\n",
1144223864Shselasky		    *lsi->gw_addr, *(lsi->gw_addr + 1),
1145223864Shselasky		    *(lsi->gw_addr + 2), *(lsi->gw_addr + 3));
1146223864Shselasky		device_printf(sc->sc_dev, "Prim NS Addr=%d.%d.%d.%d\n",
1147223864Shselasky		    *lsi->dns1_addr, *(lsi->dns1_addr + 1),
1148223864Shselasky		    *(lsi->dns1_addr + 2), *(lsi->dns1_addr + 3));
1149223864Shselasky		device_printf(sc->sc_dev, "Scnd NS Addr=%d.%d.%d.%d\n",
1150223864Shselasky		    *lsi->dns2_addr, *(lsi->dns2_addr + 1),
1151223864Shselasky		    *(lsi->dns2_addr + 2), *(lsi->dns2_addr + 3));
1152223864Shselasky
1153223864Shselasky		usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI);
1154223864Shselasky		break;
1155223864Shselasky
1156223864Shselasky	case USIE_HIP_RCGI:
1157223864Shselasky		/* ignore, workaround for sloppy windows */
1158223864Shselasky		break;
1159223864Shselasky	default:
1160223864Shselasky		DPRINTF("undefined msgid: %x\n", hip->id);
1161223864Shselasky		break;
1162223864Shselasky	}
1163223864Shselasky
1164223864Shselasky	mtx_unlock(&sc->sc_mtx);
1165223864Shselasky}
1166223864Shselasky
1167223864Shselaskystatic void
1168223864Shselaskyusie_if_start(struct ifnet *ifp)
1169223864Shselasky{
1170223864Shselasky	struct usie_softc *sc = ifp->if_softc;
1171223864Shselasky
1172223864Shselasky	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
1173223864Shselasky		DPRINTF("Not running\n");
1174223864Shselasky		return;
1175223864Shselasky	}
1176223864Shselasky	mtx_lock(&sc->sc_mtx);
1177223864Shselasky	usbd_transfer_start(sc->sc_if_xfer[USIE_IF_TX]);
1178223864Shselasky	mtx_unlock(&sc->sc_mtx);
1179223864Shselasky
1180223864Shselasky	DPRINTFN(3, "interface started\n");
1181223864Shselasky}
1182223864Shselasky
1183223864Shselaskystatic int
1184223864Shselaskyusie_if_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
1185223864Shselasky    struct route *ro)
1186223864Shselasky{
1187223864Shselasky	int err;
1188223864Shselasky
1189223864Shselasky	DPRINTF("proto=%x\n", dst->sa_family);
1190223864Shselasky
1191223864Shselasky	switch (dst->sa_family) {
1192223864Shselasky#ifdef INET6
1193223864Shselasky	case AF_INET6;
1194223864Shselasky	/* fall though */
1195223864Shselasky#endif
1196223864Shselasky	case AF_INET:
1197223864Shselasky		break;
1198223864Shselasky
1199223864Shselasky		/* silently drop dhclient packets */
1200223864Shselasky	case AF_UNSPEC:
1201223864Shselasky		m_freem(m);
1202223864Shselasky		return (0);
1203223864Shselasky
1204223864Shselasky		/* drop other packet types */
1205223864Shselasky	default:
1206223864Shselasky		m_freem(m);
1207223864Shselasky		return (EAFNOSUPPORT);
1208223864Shselasky	}
1209223864Shselasky
1210223864Shselasky	err = (ifp->if_transmit)(ifp, m);
1211223864Shselasky	if (err) {
1212223864Shselasky		ifp->if_oerrors++;
1213223864Shselasky		return (ENOBUFS);
1214223864Shselasky	}
1215223864Shselasky	ifp->if_opackets++;
1216223864Shselasky
1217223864Shselasky	return (0);
1218223864Shselasky}
1219223864Shselasky
1220223864Shselaskystatic void
1221223864Shselaskyusie_if_init(void *arg)
1222223864Shselasky{
1223223864Shselasky	struct usie_softc *sc = arg;
1224223864Shselasky	struct ifnet *ifp = sc->sc_ifp;
1225223864Shselasky	uint8_t i;
1226223864Shselasky
1227223864Shselasky	mtx_lock(&sc->sc_mtx);
1228223864Shselasky
1229223864Shselasky	/* write tx descriptor */
1230223864Shselasky	sc->sc_txd.hip.id = USIE_HIP_CTX;
1231223864Shselasky	sc->sc_txd.hip.param = 0;	/* init value */
1232223864Shselasky	sc->sc_txd.desc_type = htobe16(USIE_IP_TX);
1233223864Shselasky
1234223864Shselasky	for (i = 0; i != USIE_IF_N_XFER; i++)
1235223864Shselasky		usbd_xfer_set_stall(sc->sc_if_xfer[i]);
1236223864Shselasky
1237223864Shselasky	usbd_transfer_start(sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_RX]);
1238223864Shselasky	usbd_transfer_start(sc->sc_if_xfer[USIE_IF_STATUS]);
1239223864Shselasky	usbd_transfer_start(sc->sc_if_xfer[USIE_IF_RX]);
1240223864Shselasky
1241223864Shselasky	/* if not running, initiate the modem */
1242223864Shselasky	if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
1243223864Shselasky		usie_cns_req(sc, USIE_CNS_ID_INIT, USIE_CNS_OB_LINK_UPDATE);
1244223864Shselasky
1245223864Shselasky	mtx_unlock(&sc->sc_mtx);
1246223864Shselasky
1247223864Shselasky	DPRINTF("ifnet initialized\n");
1248223864Shselasky}
1249223864Shselasky
1250223864Shselaskystatic void
1251223864Shselaskyusie_if_stop(struct usie_softc *sc)
1252223864Shselasky{
1253223864Shselasky	usb_callout_drain(&sc->sc_if_sync_ch);
1254223864Shselasky
1255223864Shselasky	mtx_lock(&sc->sc_mtx);
1256223864Shselasky
1257223864Shselasky	/* usie_cns_req() clears IFF_* flags */
1258223864Shselasky	usie_cns_req(sc, USIE_CNS_ID_STOP, USIE_CNS_OB_LINK_UPDATE);
1259223864Shselasky
1260223864Shselasky	usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_TX]);
1261223864Shselasky	usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_RX]);
1262223864Shselasky	usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_STATUS]);
1263223864Shselasky
1264223864Shselasky	/* shutdown device */
1265223864Shselasky	usie_if_cmd(sc, USIE_HIP_DOWN);
1266223864Shselasky
1267223864Shselasky	mtx_unlock(&sc->sc_mtx);
1268223864Shselasky}
1269223864Shselasky
1270223864Shselaskystatic int
1271223864Shselaskyusie_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
1272223864Shselasky{
1273223864Shselasky	struct usie_softc *sc = ifp->if_softc;
1274223864Shselasky	struct ieee80211req *ireq;
1275223864Shselasky	struct ieee80211req_sta_info si;
1276223864Shselasky	struct ifmediareq *ifmr;
1277223864Shselasky
1278223864Shselasky	switch (cmd) {
1279223864Shselasky	case SIOCSIFFLAGS:
1280223864Shselasky		if (ifp->if_flags & IFF_UP) {
1281223864Shselasky			if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
1282223864Shselasky				usie_if_init(sc);
1283223864Shselasky		} else {
1284223864Shselasky			if (ifp->if_drv_flags & IFF_DRV_RUNNING)
1285223864Shselasky				usie_if_stop(sc);
1286223864Shselasky		}
1287223864Shselasky		break;
1288223864Shselasky
1289223864Shselasky	case SIOCSIFCAP:
1290223864Shselasky		if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
1291223864Shselasky			device_printf(sc->sc_dev,
1292223864Shselasky			    "Connect to the network first.\n");
1293223864Shselasky			break;
1294223864Shselasky		}
1295223864Shselasky		mtx_lock(&sc->sc_mtx);
1296223864Shselasky		usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI);
1297223864Shselasky		mtx_unlock(&sc->sc_mtx);
1298223864Shselasky		break;
1299223864Shselasky
1300223864Shselasky	case SIOCG80211:
1301223864Shselasky		ireq = (struct ieee80211req *)data;
1302223864Shselasky
1303223864Shselasky		if (ireq->i_type != IEEE80211_IOC_STA_INFO)
1304223864Shselasky			break;
1305223864Shselasky
1306223864Shselasky		memset(&si, 0, sizeof(si));
1307223864Shselasky		si.isi_len = sizeof(si);
1308223864Shselasky		/*
1309223864Shselasky		 * ifconfig expects RSSI in 0.5dBm units
1310223864Shselasky		 * relative to the noise floor.
1311223864Shselasky		 */
1312223864Shselasky		si.isi_rssi = 2 * sc->sc_rssi;
1313223864Shselasky		if (copyout(&si, (uint8_t *)ireq->i_data + 8,
1314223864Shselasky		    sizeof(struct ieee80211req_sta_info)))
1315223864Shselasky			DPRINTF("copyout failed\n");
1316223864Shselasky		DPRINTF("80211\n");
1317223864Shselasky		break;
1318223864Shselasky
1319223864Shselasky	case SIOCGIFMEDIA:		/* to fool ifconfig */
1320223864Shselasky		ifmr = (struct ifmediareq *)data;
1321223864Shselasky		ifmr->ifm_count = 1;
1322223864Shselasky		DPRINTF("media\n");
1323223864Shselasky		break;
1324223864Shselasky
1325223864Shselasky	case SIOCSIFADDR:
1326223864Shselasky	case SIOCSIFDSTADDR:
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
1391248078Smarius	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