diff options
author | Michael Knudsen <mk@cvs.openbsd.org> | 2010-03-01 23:35:57 +0000 |
---|---|---|
committer | Michael Knudsen <mk@cvs.openbsd.org> | 2010-03-01 23:35:57 +0000 |
commit | 49e1733747bd6981f6a910e48bdb08dd22a203d9 (patch) | |
tree | 6ab2caf8b10f96601f5de29ac7793d1679f60700 /sys/dev | |
parent | f85ddbed645690840d94d0c6f85ff9f9db230311 (diff) |
Add urndis(4), a driver for RNDIS Ethernet over USB.
It provides an Ethernet transport typically over EDGE or 3G on
cellphones similar to what cdce(4) does on other phones. It is
likely to work with most of HTC's recent and coming Android based
phones but a bunch of other things (phones in particular) may use
it.
Started by me; brought to a working state by Jonathan Armani and
Fabien Romano over the past week, with some input and additions
from me.
Tested on i386 and macppc by me (HTC Hero), amd64 by Jonathan and
Fabien (HTC Hero), and by gilles@ (HTC Magic) on i386 or amd64.
It still has a few kinks to work out, but it works well enough that
I can commit this over my HTC Hero.
`commit it!' deraadt
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/usb/files.usb | 8 | ||||
-rw-r--r-- | sys/dev/usb/if_urndis.c | 1668 | ||||
-rw-r--r-- | sys/dev/usb/if_urndisreg.h | 334 | ||||
-rw-r--r-- | sys/dev/usb/usb.h | 3 |
4 files changed, 2010 insertions, 3 deletions
diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb index b30224b9eb3..ac4d5416ae7 100644 --- a/sys/dev/usb/files.usb +++ b/sys/dev/usb/files.usb @@ -1,4 +1,4 @@ -# $OpenBSD: files.usb,v 1.88 2010/03/01 07:56:47 yuo Exp $ +# $OpenBSD: files.usb,v 1.89 2010/03/01 23:35:56 mk Exp $ # $NetBSD: files.usb,v 1.16 2000/02/14 20:29:54 augustss Exp $ # # Config file and device description for machine-independent USB code. @@ -191,6 +191,11 @@ device cdce: ether, ifnet, ifmedia attach cdce at uhub file dev/usb/if_cdce.c cdce +# RNDIS +device urndis: ether, ifnet, ifmedia +attach urndis at uhub +file dev/usb/if_urndis.c urndis + # Moschip MCS7x30 Ethernet device mos: ether, ifnet, mii, ifmedia attach mos at uhub @@ -356,7 +361,6 @@ device cdcef {} attach cdcef at usbf file dev/usb/if_cdcef.c cdcef - # Atheros AR5005UG/AR5005UX device uath: ether, ifnet, ifmedia, wlan, firmload attach uath at uhub diff --git a/sys/dev/usb/if_urndis.c b/sys/dev/usb/if_urndis.c new file mode 100644 index 00000000000..bb086aee738 --- /dev/null +++ b/sys/dev/usb/if_urndis.c @@ -0,0 +1,1668 @@ +/* $OpenBSD: if_urndis.c,v 1.1 2010/03/01 23:35:56 mk Exp $ */ + +/* + * Copyright (c) 2010 Jonathan Armani <dbd@asystant.net> + * Copyright (c) 2010 Fabien Romano <fromano@asystant.net> + * Copyright (c) 2010 Michael Knudsen <mk@openbsd.org> + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/cdefs.h> + +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/rwlock.h> +#include <sys/mbuf.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/socket.h> + +#include <sys/device.h> + +#include <machine/bus.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> + +#if NBPFILTER > 0 +#include <net/bpf.h> +#endif + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/if_ether.h> +#endif + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usbdevs.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/if_urndisreg.h> + +#ifdef RNDIS_DEBUG +#define DPRINTF(x) do { printf x; } while (0) +#else +#define DPRINTF(x) +#endif + +#define DEVNAME(sc) ((sc)->sc_dev.dv_xname) + +int urndis_newbuf(struct urndis_softc *, struct urndis_chain *, struct mbuf *); + +void urndis_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +int urndis_ioctl(struct ifnet *, u_long, caddr_t); +void urndis_watchdog(struct ifnet *); + +void urndis_start(struct ifnet *); +void urndis_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +void urndis_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +int urndis_rx_list_init(struct urndis_softc *); +int urndis_tx_list_init(struct urndis_softc *); + +void urndis_init(struct urndis_softc *); +void urndis_stop(struct urndis_softc *); + +usbd_status urndis_ctrl_msg(struct urndis_softc *, uint8_t, uint8_t, + uint16_t, uint16_t, void *, size_t); +usbd_status urndis_ctrl_send(struct urndis_softc *, void *, size_t); +struct urndis_comp_hdr *urndis_ctrl_recv(struct urndis_softc *); + +urndis_status urndis_ctrl_handle(struct urndis_softc *, + struct urndis_comp_hdr *, void **, size_t *); +urndis_status urndis_ctrl_handle_init(struct urndis_softc *, + const struct urndis_comp_hdr *); +urndis_status urndis_ctrl_handle_query(struct urndis_softc *, + const struct urndis_comp_hdr *, void **, size_t *); +urndis_status urndis_ctrl_handle_reset(struct urndis_softc *, + const struct urndis_comp_hdr *); + +urndis_status urndis_ctrl_init(struct urndis_softc *); +urndis_status urndis_ctrl_halt(struct urndis_softc *); +urndis_status urndis_ctrl_query(struct urndis_softc *, urndis_oid, void *, size_t, + void **, size_t *); +urndis_status urndis_ctrl_set(struct urndis_softc *, urndis_oid, void *, size_t); +urndis_status urndis_ctrl_set_param(struct urndis_softc *, const char *, u_int32_t, + void *, size_t); +urndis_status urndis_ctrl_reset(struct urndis_softc *); +urndis_status urndis_ctrl_keepalive(struct urndis_softc *); + +int urndis_encap(struct urndis_softc *, struct mbuf *, int); +void urndis_decap(struct urndis_softc *, struct urndis_chain *, size_t); + +int urndis_match(struct device *, void *, void *); +void urndis_attach(struct device *, struct device *, void *); +int urndis_detach(struct device *, int); +int urndis_activate(struct device *, int); + +struct cfdriver urndis_cd = { + NULL, "urndis", DV_IFNET +}; + +struct cfattach urndis_ca = { + sizeof(struct urndis_softc), + urndis_match, + urndis_attach, + urndis_detach, + urndis_activate, +}; + +usbd_status +urndis_ctrl_msg(struct urndis_softc *sc, uint8_t rt, uint8_t r, + uint16_t index, uint16_t value, void *buf, size_t buflen) +{ + usb_device_request_t req; + + req.bmRequestType = rt; + req.bRequest = r; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, buflen); + + return usbd_do_request(sc->sc_udev, &req, buf); +} + +usbd_status +urndis_ctrl_send(struct urndis_softc *sc, void *buf, size_t len) +{ + usbd_status err; + + if (sc->sc_dying) + return(0); + + err = urndis_ctrl_msg(sc, UT_WRITE_CLASS_INTERFACE, UR_GET_STATUS, + sc->sc_ifaceno_ctl, 0, buf, len); + + if (err != USBD_NORMAL_COMPLETION) + printf("%s: %s\n", DEVNAME(sc), usbd_errstr(err)); + + return err; +} + +struct urndis_comp_hdr * +urndis_ctrl_recv(struct urndis_softc *sc) +{ +#define RNDIS_RESPONSE_LEN 0x400 /* XXX seriously? */ + struct urndis_comp_hdr *hdr, *pkt; + char *buf; + usbd_status err; + + buf = malloc(RNDIS_RESPONSE_LEN, M_TEMP, M_WAITOK | M_CANFAIL); + if (buf == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return NULL; + } + + err = urndis_ctrl_msg(sc, UT_READ_CLASS_INTERFACE, UR_CLEAR_FEATURE, + sc->sc_ifaceno_ctl, 0, buf, RNDIS_RESPONSE_LEN); + + if (err != USBD_NORMAL_COMPLETION && err != USBD_SHORT_XFER) { + printf("%s: %s\n", DEVNAME(sc), usbd_errstr(err)); + free(buf, M_TEMP); + return NULL; + } + + hdr = (struct urndis_comp_hdr *)buf; + DPRINTF(("%s: urndis_ctrl_recv: type 0x%x len %u\n", + DEVNAME(sc), + letoh32(hdr->rm_type), + letoh32(hdr->rm_len))); + + if (letoh32(hdr->rm_len) > RNDIS_RESPONSE_LEN) { + printf("%s: ctrl message error: wrong size %u > %u\n", + DEVNAME(sc), + letoh32(hdr->rm_len), + RNDIS_RESPONSE_LEN); + free(buf, M_TEMP); + return NULL; + } + + if (letoh32(hdr->rm_len) < 128) { + pkt = malloc(letoh32(hdr->rm_len), + M_TEMP, M_WAITOK | M_CANFAIL); + if (pkt == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + /* XXX just use buf? */ + free(buf, M_TEMP); + } else { + memcpy(pkt, hdr, letoh32(hdr->rm_len)); + free(buf, M_TEMP); + } + } else { + pkt = hdr; + } + + return pkt; +} + +urndis_status +urndis_ctrl_handle(struct urndis_softc *sc, struct urndis_comp_hdr *hdr, + void **buf, size_t *bufsz) +{ + urndis_status rval; + + DPRINTF(("%s: urndis_ctrl_handle\n", DEVNAME(sc))); + + if (buf && bufsz) { + *buf = NULL; + *bufsz = 0; + } + + switch (letoh32(hdr->rm_type)) { + case REMOTE_NDIS_INITIALIZE_CMPLT: + rval = urndis_ctrl_handle_init(sc, hdr); + break; + + case REMOTE_NDIS_QUERY_CMPLT: + rval = urndis_ctrl_handle_query(sc, hdr, buf, bufsz); + break; + + case REMOTE_NDIS_RESET_CMPLT: + rval = urndis_ctrl_handle_reset(sc, hdr); + break; + + case REMOTE_NDIS_KEEPALIVE_CMPLT: + case REMOTE_NDIS_SET_CMPLT: + rval = letoh32(hdr->rm_status); + break; + + default: + printf("%s: ctrl message error: unknown event 0x%x\n", + DEVNAME(sc), letoh32(hdr->rm_type)); + rval = RNDIS_STATUS_FAILURE; + } + + free(hdr, M_TEMP); + + return rval; +} + +urndis_status +urndis_ctrl_handle_init(struct urndis_softc *sc, const struct urndis_comp_hdr *hdr) +{ + const struct urndis_init_comp *msg; + + msg = (struct urndis_init_comp *) hdr; + + DPRINTF(("%s: urndis_ctrl_handle_init: len %u rid %u status 0x%x " + "ver_major %u ver_minor %u devflags 0x%x medium 0x%x pktmaxcnt %u " + "pktmaxsz %u align %u aflistoffset %u aflistsz %u\n", + DEVNAME(sc), + letoh32(msg->rm_len), + letoh32(msg->rm_rid), + letoh32(msg->rm_status), + letoh32(msg->rm_ver_major), + letoh32(msg->rm_ver_minor), + letoh32(msg->rm_devflags), + letoh32(msg->rm_medium), + letoh32(msg->rm_pktmaxcnt), + letoh32(msg->rm_pktmaxsz), + letoh32(msg->rm_align), + letoh32(msg->rm_aflistoffset), + letoh32(msg->rm_aflistsz))); + + if (letoh32(msg->rm_status) != RNDIS_STATUS_SUCCESS) { + printf("%s: init failed 0x%x\n", + DEVNAME(sc), + letoh32(msg->rm_status)); + + return letoh32(msg->rm_status); + } + + if (letoh32(msg->rm_devflags) != RNDIS_DF_CONNECTIONLESS) { + printf("%s: wrong device type (current type: 0x%x)\n", + DEVNAME(sc), + letoh32(msg->rm_devflags)); + + return RNDIS_STATUS_FAILURE; + } + + if (letoh32(msg->rm_medium) != RNDIS_MEDIUM_802_3) { + printf("%s: medium not 802.3 (current medium: 0x%x)\n", + DEVNAME(sc), letoh32(msg->rm_medium)); + + return RNDIS_STATUS_FAILURE; + } + + sc->sc_lim_pktcnt = letoh32(msg->rm_pktmaxcnt); + sc->sc_lim_pktsz = letoh32(msg->rm_pktmaxsz); + sc->sc_pktalign = letoh32(msg->rm_align); + + return letoh32(msg->rm_status); +} + +urndis_status +urndis_ctrl_handle_query(struct urndis_softc *sc, + const struct urndis_comp_hdr *hdr, void **buf, size_t *bufsz) +{ + const struct urndis_query_comp *msg; + + msg = (struct urndis_query_comp *) hdr; + + DPRINTF(("%s: urndis_ctrl_handle_query: len %u rid %u status 0x%x " + "buflen %u bufoff %u\n", + DEVNAME(sc), + letoh32(msg->rm_len), + letoh32(msg->rm_rid), + letoh32(msg->rm_status), + letoh32(msg->rm_infobuflen), + letoh32(msg->rm_infobufoffset))); + + if (buf && bufsz) { + *buf = NULL; + *bufsz = 0; + } + + if (letoh32(msg->rm_status) != RNDIS_STATUS_SUCCESS) { + printf("%s: query failed 0x%x\n", + DEVNAME(sc), + letoh32(msg->rm_status)); + + return letoh32(msg->rm_status); + } + /* XXX : 8 -> rid offset in struct */ + if (letoh32(msg->rm_infobuflen) + letoh32(msg->rm_infobufoffset) + + 8 > letoh32(msg->rm_len)) { + printf("%s: ctrl message error: invalid query info " + "len/offset/end_position(%d/%d/%d) -> " + "go out of buffer limit %d\n", + DEVNAME(sc), + letoh32(msg->rm_infobuflen), + letoh32(msg->rm_infobufoffset), + letoh32(msg->rm_infobuflen) + + letoh32(msg->rm_infobufoffset) + 8, + letoh32(msg->rm_len)); + return RNDIS_STATUS_FAILURE; + } + + if (buf && bufsz) { + *buf = malloc(letoh32(msg->rm_infobuflen), + M_TEMP, M_WAITOK | M_CANFAIL); + if (*buf == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } else { + char *p; + *bufsz = letoh32(msg->rm_infobuflen); + + p = (char *)&msg->rm_rid; + p += letoh32(msg->rm_infobufoffset); + memcpy(*buf, p, letoh32(msg->rm_infobuflen)); + } + } + + return letoh32(msg->rm_status); +} + +urndis_status +urndis_ctrl_handle_reset(struct urndis_softc *sc, + const struct urndis_comp_hdr *hdr) +{ + const struct urndis_reset_comp *msg; + urndis_status rval; + + msg = (struct urndis_reset_comp *) hdr; + + rval = letoh32(msg->rm_status); + + DPRINTF(("%s: urndis_ctrl_handle_reset: len %u status 0x%x " + "adrreset %u\n", + DEVNAME(sc), + letoh32(msg->rm_len), + rval, + letoh32(msg->rm_adrreset))); + + if (rval != RNDIS_STATUS_SUCCESS) { + printf("%s: reset failed 0x%x\n", DEVNAME(sc), rval); + return rval; + } + + if (letoh32(msg->rm_adrreset) != 0) { + u_int32_t filter; + + filter = htole32(sc->sc_filter); + rval = urndis_ctrl_set(sc, OID_GEN_CURRENT_PACKET_FILTER, + &filter, sizeof(filter)); + if (rval != RNDIS_STATUS_SUCCESS) { + printf("%s: unable to reset data filters\n", + DEVNAME(sc)); + return rval; + } + } + + return rval; +} + +urndis_status +urndis_ctrl_init(struct urndis_softc *sc) +{ + struct urndis_init_req *msg; + urndis_status rval; + struct urndis_comp_hdr *hdr; + + msg = malloc(sizeof(*msg), M_TEMP, M_WAITOK); + if (msg == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + + msg->rm_type = htole32(REMOTE_NDIS_INITIALIZE_MSG); + msg->rm_len = htole32(sizeof(*msg)); + msg->rm_rid = htole32(0); + msg->rm_ver_major = htole32(1); + msg->rm_ver_minor = htole32(1); + msg->rm_max_xfersz = htole32(RNDIS_BUFSZ); + + DPRINTF(("%s: urndis_ctrl_init send: type %u len %u rid %u ver_major %u " + "ver_minor %u max_xfersz %u\n", + DEVNAME(sc), + letoh32(msg->rm_type), + letoh32(msg->rm_len), + letoh32(msg->rm_rid), + letoh32(msg->rm_ver_major), + letoh32(msg->rm_ver_minor), + letoh32(msg->rm_max_xfersz))); + + rval = urndis_ctrl_send(sc, msg, sizeof(*msg)); + free(msg, M_TEMP); + + if (rval != RNDIS_STATUS_SUCCESS) { + printf("%s: init failed\n", DEVNAME(sc)); + return rval; + } + + if ((hdr = urndis_ctrl_recv(sc)) == NULL) { + printf("%s: unable to get init response\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + rval = urndis_ctrl_handle(sc, hdr, NULL, NULL); + + return rval; +} + +urndis_status +urndis_ctrl_halt(struct urndis_softc *sc) +{ + struct urndis_halt_req *msg; + urndis_status rval; + + msg = malloc(sizeof(*msg), M_TEMP, M_WAITOK); + if (msg == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + + msg->rm_type = htole32(REMOTE_NDIS_HALT_MSG); + msg->rm_len = htole32(sizeof(*msg)); + msg->rm_rid = 0; + + DPRINTF(("%s: urndis_ctrl_halt send: type %u len %u rid %u\n", + DEVNAME(sc), + letoh32(msg->rm_type), + letoh32(msg->rm_len), + letoh32(msg->rm_rid))); + + rval = urndis_ctrl_send(sc, msg, sizeof(*msg)); + free(msg, M_TEMP); + + if (rval != RNDIS_STATUS_SUCCESS) + printf("%s: halt failed\n", DEVNAME(sc)); + + return rval; +} + +urndis_status +urndis_ctrl_query(struct urndis_softc *sc, urndis_oid oid, + void *qbuf, size_t qlen, + void **rbuf, size_t *rbufsz) +{ + struct urndis_query_req *msg; + urndis_status rval; + struct urndis_comp_hdr *hdr; + + msg = malloc(sizeof(*msg) + qlen, M_TEMP, M_WAITOK); + if (msg == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + + msg->rm_type = htole32(REMOTE_NDIS_QUERY_MSG); + msg->rm_len = htole32(sizeof(*msg) + qlen); + msg->rm_rid = 0; /* XXX */ + msg->rm_oid = htole32(oid); + msg->rm_infobuflen = htole32(qlen); + if (qlen != 0) { + msg->rm_infobufoffset = htole32(20); + memcpy((char*)msg + 20, qbuf, qlen); + } else + msg->rm_infobufoffset = 0; + msg->rm_devicevchdl = 0; + + DPRINTF(("%s: urndis_ctrl_query send: type %u len %u rid %u oid 0x%x " + "infobuflen %u infobufoffset %u devicevchdl %u\n", + DEVNAME(sc), + letoh32(msg->rm_type), + letoh32(msg->rm_len), + letoh32(msg->rm_rid), + letoh32(msg->rm_oid), + letoh32(msg->rm_infobuflen), + letoh32(msg->rm_infobufoffset), + letoh32(msg->rm_devicevchdl))); + + rval = urndis_ctrl_send(sc, msg, sizeof(*msg)); + free(msg, M_TEMP); + + if (rval != RNDIS_STATUS_SUCCESS) { + printf("%s: query failed\n", DEVNAME(sc)); + return rval; + } + + if ((hdr = urndis_ctrl_recv(sc)) == NULL) { + printf("%s: unable to get query response\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + rval = urndis_ctrl_handle(sc, hdr, rbuf, rbufsz); + + return rval; +} + +urndis_status +urndis_ctrl_set(struct urndis_softc *sc, urndis_oid oid, void *buf, size_t len) +{ + struct urndis_set_req *msg; + urndis_status rval; + struct urndis_comp_hdr *hdr; + + msg = malloc(sizeof(*msg) + len, M_TEMP, M_WAITOK); + if (msg == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + + msg->rm_type = htole32(REMOTE_NDIS_SET_MSG); + msg->rm_len = htole32(sizeof(*msg) + len); + msg->rm_rid = 0; /* XXX */ + msg->rm_oid = htole32(oid); + msg->rm_infobuflen = htole32(len); + if (len != 0) { + msg->rm_infobufoffset = htole32(20); + memcpy((char*)msg + 20, buf, len); + } else + msg->rm_infobufoffset = 0; + msg->rm_devicevchdl = 0; + + DPRINTF(("%s: urndis_ctrl_set send: type %u len %u rid %u oid 0x%x " + "infobuflen %u infobufoffset %u devicevchdl %u\n", + DEVNAME(sc), + letoh32(msg->rm_type), + letoh32(msg->rm_len), + letoh32(msg->rm_rid), + letoh32(msg->rm_oid), + letoh32(msg->rm_infobuflen), + letoh32(msg->rm_infobufoffset), + letoh32(msg->rm_devicevchdl))); + + rval = urndis_ctrl_send(sc, msg, sizeof(*msg)); + free(msg, M_TEMP); + + if (rval != RNDIS_STATUS_SUCCESS) { + printf("%s: set failed\n", DEVNAME(sc)); + return rval; + } + + if ((hdr = urndis_ctrl_recv(sc)) == NULL) { + printf("%s: unable to get set response\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + rval = urndis_ctrl_handle(sc, hdr, NULL, NULL); + if (rval != RNDIS_STATUS_SUCCESS) + printf("%s: set failed 0x%x\n", DEVNAME(sc), rval); + + return rval; +} + +urndis_status +urndis_ctrl_set_param(struct urndis_softc *sc, const char *name, u_int32_t type, + void *buf, size_t len) +{ + struct urndis_set_parameter *param; + urndis_status rval; + size_t namelen, tlen; + + if (name) + namelen = strlen(name); + else + namelen = 0; + tlen = sizeof(*param) + len + namelen; + param = malloc(tlen, M_TEMP, M_WAITOK); + if (param == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + + param->rm_namelen = htole32(namelen); + param->rm_valuelen = htole32(len); + param->rm_type = htole32(type); + if (namelen != 0) { + param->rm_nameoffset = htole32(20); + memcpy(param + 20, name, namelen); + } else + param->rm_nameoffset = 0; + if (len != 0) { + param->rm_valueoffset = htole32(20 + namelen); + memcpy(param + 20 + namelen, buf, len); + } else + param->rm_valueoffset = 0; + + DPRINTF(("%s: urndis_ctrl_set_param send: nameoffset %u namelen %u " + "type 0x%x valueoffset %u valuelen %u\n", + DEVNAME(sc), + letoh32(param->rm_nameoffset), + letoh32(param->rm_namelen), + letoh32(param->rm_type), + letoh32(param->rm_valueoffset), + letoh32(param->rm_valuelen))); + + rval = urndis_ctrl_set(sc, OID_GEN_RNDIS_CONFIG_PARAMETER, param, tlen); + free(param, M_TEMP); + if (rval != RNDIS_STATUS_SUCCESS) + printf("%s: set param failed 0x%x\n", DEVNAME(sc), rval); + + return rval; +} + +/* XXX : adrreset, get it from response */ +urndis_status +urndis_ctrl_reset(struct urndis_softc *sc) +{ + struct urndis_reset_req *reset; + urndis_status rval; + struct urndis_comp_hdr *hdr; + + reset = malloc(sizeof(*reset), M_TEMP, M_WAITOK); + if (reset == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + + reset->rm_type = htole32(REMOTE_NDIS_RESET_MSG); + reset->rm_len = htole32(sizeof(*reset)); + reset->rm_rid = 0; /* XXX rm_rid == reserved ... remove ? */ + + DPRINTF(("%s: urndis_ctrl_reset send: type %u len %u rid %u\n", + DEVNAME(sc), + letoh32(reset->rm_type), + letoh32(reset->rm_len), + letoh32(reset->rm_rid))); + + rval = urndis_ctrl_send(sc, reset, sizeof(*reset)); + free(reset, M_TEMP); + + if (rval != RNDIS_STATUS_SUCCESS) { + printf("%s: reset failed\n", DEVNAME(sc)); + return rval; + } + + if ((hdr = urndis_ctrl_recv(sc)) == NULL) { + printf("%s: unable to get reset response\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + rval = urndis_ctrl_handle(sc, hdr, NULL, NULL); + + return rval; +} + +urndis_status +urndis_ctrl_keepalive(struct urndis_softc *sc) +{ + struct urndis_keepalive_req *keep; + urndis_status rval; + struct urndis_comp_hdr *hdr; + + keep = malloc(sizeof(*keep), M_TEMP, M_WAITOK); + if (keep == NULL) { + printf("%s: out of memory\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + + keep->rm_type = htole32(REMOTE_NDIS_KEEPALIVE_MSG); + keep->rm_len = htole32(sizeof(*keep)); + keep->rm_rid = 0; /* XXX rm_rid == reserved ... remove ? */ + + DPRINTF(("%s: urndis_ctrl_reset send: type %u len %u rid %u\n", + DEVNAME(sc), + letoh32(keep->rm_type), + letoh32(keep->rm_len), + letoh32(keep->rm_rid))); + + rval = urndis_ctrl_send(sc, keep, sizeof(*keep)); + free(keep, M_TEMP); + + if (rval != RNDIS_STATUS_SUCCESS) { + printf("%s: keepalive failed\n", DEVNAME(sc)); + return rval; + } + + if ((hdr = urndis_ctrl_recv(sc)) == NULL) { + printf("%s: unable to get keepalive response\n", DEVNAME(sc)); + return RNDIS_STATUS_FAILURE; + } + rval = urndis_ctrl_handle(sc, hdr, NULL, NULL); + if (rval != RNDIS_STATUS_SUCCESS) { + printf("%s: keepalive failed 0x%x\n", DEVNAME(sc), rval); + urndis_ctrl_reset(sc); + } + + return rval; +} + +int +urndis_encap(struct urndis_softc *sc, struct mbuf *m, int idx) +{ + struct urndis_chain *c; + usbd_status err; + struct urndis_packet_msg *msg; + + c = &sc->sc_data.sc_tx_chain[idx]; + + msg = (struct urndis_packet_msg *)c->sc_buf; + + memset(msg, 0, sizeof(*msg)); + msg->rm_type = htole32(REMOTE_NDIS_PACKET_MSG); + msg->rm_len = htole32(sizeof(*msg) + m->m_pkthdr.len); + + /* XXX : 36 -> dataoffset corresponding in this struct */ + msg->rm_dataoffset = htole32(36); + msg->rm_datalen = htole32(m->m_pkthdr.len); + + /* XXX : 8 -> dataoffset offset in struct */ + m_copydata(m, 0, m->m_pkthdr.len, + ((char*)msg + 36 + 8)); + + DPRINTF(("%s: urndis_encap type 0x%x len %u data(off %u len %u)\n", + DEVNAME(sc), + letoh32(msg->rm_type), + letoh32(msg->rm_len), + letoh32(msg->rm_dataoffset), + letoh32(msg->rm_datalen))); + + c->sc_mbuf = m; + + usbd_setup_xfer(c->sc_xfer, sc->sc_bulkout_pipe, c, c->sc_buf, + letoh32(msg->rm_len), USBD_FORCE_SHORT_XFER | USBD_NO_COPY, 10000, + urndis_txeof); + + /* Transmit */ + err = usbd_transfer(c->sc_xfer); + if (err != USBD_IN_PROGRESS) { + urndis_stop(sc); + return(EIO); + } + + sc->sc_data.sc_tx_cnt++; + + return(0); +} + +/* XXX draft, must implement Multi-Packets Transfer ... :( */ +void +urndis_decap(struct urndis_softc *sc, struct urndis_chain *c, size_t len) +{ + struct mbuf *m; + struct urndis_packet_msg *msg; + struct ifnet *ifp; + int s; + int offset; + + ifp = GET_IFP(sc); + offset = 0; + + while (len > 0) { + msg = (struct urndis_packet_msg *)((char*)c->sc_buf + offset); + m = c->sc_mbuf; + + DPRINTF(("%s: urndis_decap buffer size left %u\n", DEVNAME(sc), + (int)len)); + + DPRINTF(("%s: urndis_decap len %u data(off:%u len:%u) " + "oobdata(off:%u len:%u nb:%u) perpacket(off:%u len:%u)\n", + DEVNAME(sc), + letoh32(msg->rm_len), + letoh32(msg->rm_dataoffset), + letoh32(msg->rm_datalen), + letoh32(msg->rm_oobdataoffset), + letoh32(msg->rm_oobdatalen), + letoh32(msg->rm_oobdataelements), + letoh32(msg->rm_pktinfooffset), + letoh32(msg->rm_pktinfooffset))); + + if (len < sizeof(*msg)) { + printf("%s: urndis_decap invalid buffer len %u < " + "minimum header %u\n", + DEVNAME(sc), + letoh32(msg->rm_len), + sizeof(*msg)); + return; + } + if (letoh32(msg->rm_type) != REMOTE_NDIS_PACKET_MSG) { + printf("%s: urndis_decap invalid type 0x%x != 0x%x\n", + DEVNAME(sc), + letoh32(msg->rm_type), + REMOTE_NDIS_PACKET_MSG); + return; + } + if (letoh32(msg->rm_len) < sizeof(*msg)) { + printf("%s: urndis_decap invalid msg len %u < %u\n", + DEVNAME(sc), + letoh32(msg->rm_len), + sizeof(*msg)); + return; + } + if (letoh32(msg->rm_len) > len) { + printf("%s: urndis_decap invalid msg len %u > buffer " + "len %u\n", + DEVNAME(sc), + letoh32(msg->rm_len), + len); + return; + } + /* XXX : 8 -> dataoffset offset in struct */ + if (letoh32(msg->rm_dataoffset) + + letoh32(msg->rm_datalen) + 8 > letoh32(msg->rm_len)) { + printf("%s: urndis_decap invalid data " + "len/offset/end_position(%u/%u/%u) -> " + "go out of receive buffer limit %u\n", + DEVNAME(sc), + letoh32(msg->rm_datalen), + letoh32(msg->rm_dataoffset), + letoh32(msg->rm_dataoffset) + + letoh32(msg->rm_datalen) + 8, + letoh32(msg->rm_len)); + return; + } + + memcpy(mtod(m, char*), + ((char*)&msg->rm_dataoffset + letoh32(msg->rm_dataoffset)), + letoh32(msg->rm_datalen)); + m->m_pkthdr.len = m->m_len = letoh32(msg->rm_datalen); + + if (m->m_len < sizeof(struct ether_header)) { + ifp->if_ierrors++; + printf("%s: urndis_decap invalid ethernet size " + "%d < %d\n", + DEVNAME(sc), + m->m_len, + sizeof(struct ether_header)); + return; + } + ifp->if_ipackets++; + m->m_pkthdr.rcvif = ifp; + + s = splnet(); + + if (urndis_newbuf(sc, c, NULL) == ENOBUFS) { + ifp->if_ierrors++; + } else { + +#if NBPFILTER > 0 + if (ifp->if_bpf) + bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_IN); +#endif + + ether_input_mbuf(ifp, m); + + } + splx(s); + + offset += letoh32(msg->rm_len); + len -= letoh32(msg->rm_len); + } +} + +int +urndis_newbuf(struct urndis_softc *sc, struct urndis_chain *c, struct mbuf *m) +{ + struct mbuf *m_new = NULL; + + if (m == NULL) { + MGETHDR(m_new, M_DONTWAIT, MT_DATA); + if (m_new == NULL) { + printf("%s: no memory for rx list " + "-- packet dropped!\n", + DEVNAME(sc)); + return (ENOBUFS); + } + MCLGET(m_new, M_DONTWAIT); + if (!(m_new->m_flags & M_EXT)) { + printf("%s: no memory for rx list " + "-- packet dropped!\n", + DEVNAME(sc)); + m_freem(m_new); + return (ENOBUFS); + } + m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; + } else { + m_new = m; + m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; + m_new->m_data = m_new->m_ext.ext_buf; + } + + m_adj(m_new, ETHER_ALIGN); + c->sc_mbuf = m_new; + return (0); +} + +int +urndis_rx_list_init(struct urndis_softc *sc) +{ + struct urndis_cdata *cd; + struct urndis_chain *c; + int i; + + cd = &sc->sc_data; + for (i = 0; i < RNDIS_RX_LIST_CNT; i++) { + c = &cd->sc_rx_chain[i]; + c->sc_sc = sc; + c->sc_idx = i; + + if (urndis_newbuf(sc, c, NULL) == ENOBUFS) + return (ENOBUFS); + + if (c->sc_xfer == NULL) { + c->sc_xfer = usbd_alloc_xfer(sc->sc_udev); + if (c->sc_xfer == NULL) + return (ENOBUFS); + c->sc_buf = usbd_alloc_buffer(c->sc_xfer, + RNDIS_BUFSZ); + if (c->sc_buf == NULL) + return (ENOBUFS); + } + } + + return (0); +} + +int +urndis_tx_list_init(struct urndis_softc *sc) +{ + struct urndis_cdata *cd; + struct urndis_chain *c; + int i; + + cd = &sc->sc_data; + for (i = 0; i < RNDIS_TX_LIST_CNT; i++) { + c = &cd->sc_tx_chain[i]; + c->sc_sc = sc; + c->sc_idx = i; + c->sc_mbuf = NULL; + if (c->sc_xfer == NULL) { + c->sc_xfer = usbd_alloc_xfer(sc->sc_udev); + if (c->sc_xfer == NULL) + return (ENOBUFS); + c->sc_buf = usbd_alloc_buffer(c->sc_xfer, + RNDIS_BUFSZ); + if (c->sc_buf == NULL) + return (ENOBUFS); + } + } + return (0); +} + +void +urndis_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct urndis_softc *sc; + u_int32_t count; + + sc = (struct urndis_softc *)priv; + + if (status == USBD_CANCELLED || sc->sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(("urndis_intr: status=%d\n", status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); + return; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + + if (count != sizeof(sc->sc_intr_buf)) { + printf("%s: intr input failure: %u bytes\n", DEVNAME(sc), + count); + return; + } + + sc->sc_intr_buf.notification = UGETDW(&sc->sc_intr_buf.notification); + switch (sc->sc_intr_buf.notification) { + case 0x1: + DPRINTF(("%s: RESPONSE_AVAILABLE\n", DEVNAME(sc))); + break; + + default: + printf("%s: intr input failure: notification 0x%04x\n", + DEVNAME(sc), sc->sc_intr_buf.notification); + break; + } +} + +int +urndis_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct urndis_softc *sc; + struct ifaddr *ifa; + int s, error; + + sc = ifp->if_softc; + ifa = (struct ifaddr *)data; + error = 0; + + if (sc->sc_dying) + return (EIO); + + s = splnet(); + + switch(command) { + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + urndis_init(sc); + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + arp_ifinit(&sc->sc_arpcom, ifa); + break; + } + break; + + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_flags & IFF_RUNNING)) + urndis_init(sc); + } else { + if (ifp->if_flags & IFF_RUNNING) + urndis_stop(sc); + } + error = 0; + break; + + default: + error = ether_ioctl(ifp, &sc->sc_arpcom, command, data); + break; + } + + if (error == ENETRESET) + error = 0; + + splx(s); + return (error); +} + +void +urndis_watchdog(struct ifnet *ifp) +{ + struct urndis_softc *sc; + + sc = ifp->if_softc; + + if (sc->sc_dying) + return; + + ifp->if_oerrors++; + printf("%s: watchdog timeout\n", DEVNAME(sc)); + + urndis_ctrl_keepalive(sc); +} + +void +urndis_init(struct urndis_softc *sc) +{ + struct ifnet *ifp; + int i, s; + usbd_status err; + + + ifp = GET_IFP(sc); + if (ifp->if_flags & IFF_RUNNING) + return; + + if (urndis_ctrl_init(sc) != RNDIS_STATUS_SUCCESS) + return; + + s = splnet(); + + if (sc->sc_intr_no != -1 && sc->sc_intr_pipe == NULL) { + usbd_status err; + + DPRINTF(("urndis_init: establish interrupt pipe\n")); + err = usbd_open_pipe_intr(sc->sc_iface_ctl, + sc->sc_intr_no, + USBD_SHORT_XFER_OK, &sc->sc_intr_pipe, sc, + &sc->sc_intr_buf, sizeof(sc->sc_intr_buf), + urndis_intr, USBD_DEFAULT_INTERVAL); + if (err) { + printf("%s: open interrupt pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + splx(s); + return; + } + } + + if (urndis_tx_list_init(sc) == ENOBUFS) { + printf("%s: tx list init failed\n", + DEVNAME(sc)); + splx(s); + return; + } + + if (urndis_rx_list_init(sc) == ENOBUFS) { + printf("%s: rx list init failed\n", + DEVNAME(sc)); + splx(s); + return; + } + + err = usbd_open_pipe(sc->sc_iface_data, sc->sc_bulkin_no, + USBD_EXCLUSIVE_USE, &sc->sc_bulkin_pipe); + if (err) { + printf("%s: open rx pipe failed: %s\n", DEVNAME(sc), + usbd_errstr(err)); + splx(s); + return; + } + + err = usbd_open_pipe(sc->sc_iface_data, sc->sc_bulkout_no, + USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe); + if (err) { + printf("%s: open tx pipe failed: %s\n", DEVNAME(sc), + usbd_errstr(err)); + splx(s); + return; + } + + for (i = 0; i < RNDIS_RX_LIST_CNT; i++) { + struct urndis_chain *c; + + c = &sc->sc_data.sc_rx_chain[i]; + usbd_setup_xfer(c->sc_xfer, sc->sc_bulkin_pipe, c, + c->sc_buf, RNDIS_BUFSZ, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, urndis_rxeof); + usbd_transfer(c->sc_xfer); + } + + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + + splx(s); +} + +void +urndis_stop(struct urndis_softc *sc) +{ + usbd_status err; + struct ifnet *ifp; + int i; + + ifp = GET_IFP(sc); + ifp->if_timer = 0; + ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); + + if (sc->sc_bulkin_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_bulkin_pipe); + if (err) + printf("%s: abort rx pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_bulkin_pipe); + if (err) + printf("%s: close rx pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + sc->sc_bulkin_pipe = NULL; + } + + if (sc->sc_bulkout_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_bulkout_pipe); + if (err) + printf("%s: abort tx pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_bulkout_pipe); + if (err) + printf("%s: close tx pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + sc->sc_bulkout_pipe = NULL; + } + + if (sc->sc_intr_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_intr_pipe); + if (err) + printf("%s: abort interrupt pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_intr_pipe); + if (err) + printf("%s: close interrupt pipe failed: %s\n", + DEVNAME(sc), usbd_errstr(err)); + sc->sc_intr_pipe = NULL; + } + + for (i = 0; i < RNDIS_RX_LIST_CNT; i++) { + if (sc->sc_data.sc_rx_chain[i].sc_mbuf != NULL) { + m_freem(sc->sc_data.sc_rx_chain[i].sc_mbuf); + sc->sc_data.sc_rx_chain[i].sc_mbuf = NULL; + } + if (sc->sc_data.sc_rx_chain[i].sc_xfer != NULL) { + usbd_free_xfer(sc->sc_data.sc_rx_chain[i].sc_xfer); + sc->sc_data.sc_rx_chain[i].sc_xfer = NULL; + } + } + + for (i = 0; i < RNDIS_TX_LIST_CNT; i++) { + if (sc->sc_data.sc_tx_chain[i].sc_mbuf != NULL) { + m_freem(sc->sc_data.sc_tx_chain[i].sc_mbuf); + sc->sc_data.sc_tx_chain[i].sc_mbuf = NULL; + } + if (sc->sc_data.sc_tx_chain[i].sc_xfer != NULL) { + usbd_free_xfer(sc->sc_data.sc_tx_chain[i].sc_xfer); + sc->sc_data.sc_tx_chain[i].sc_xfer = NULL; + } + } +} + +void +urndis_start(struct ifnet *ifp) +{ + struct urndis_softc *sc; + struct mbuf *m_head = NULL; + + sc = ifp->if_softc; + + if (sc->sc_dying || (ifp->if_flags & IFF_OACTIVE)) + return; + + IFQ_POLL(&ifp->if_snd, m_head); + if (m_head == NULL) + return; + + if (urndis_encap(sc, m_head, 0)) { + ifp->if_flags |= IFF_OACTIVE; + return; + } + IFQ_DEQUEUE(&ifp->if_snd, m_head); + + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ +#if NBPFILTER > 0 + if (ifp->if_bpf) + bpf_mtap(ifp->if_bpf, m_head, BPF_DIRECTION_OUT); +#endif + + ifp->if_flags |= IFF_OACTIVE; + + /* + * Set a timeout in case the chip goes out to lunch. + */ + ifp->if_timer = 5; + + return; +} + +void +urndis_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct urndis_chain *c; + struct urndis_softc *sc; + struct ifnet *ifp; + size_t total_len; + + c = priv; + sc = c->sc_sc; + ifp = GET_IFP(sc); + total_len = 0; + + if (sc->sc_dying || !(ifp->if_flags & IFF_RUNNING)) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; +#if 0 + if (sc->sc_rxeof_errors == 0) + printf("%s: usb error on rx: %s\n", DEVNAME(sc), + usbd_errstr(status)); +#endif + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe); + +#if 0 + DELAY(sc->sc_rxeof_errors * 10000); + if (sc->sc_rxeof_errors++ > 10) { + printf("%s: too many errors, disabling\n", + DEVNAME(sc)); + sc->sc_dying = 1; + return; + } +#endif + goto done; + } + +#if 0 + sc->sc_rxeof_errors = 0; +#endif + + usbd_get_xfer_status(xfer, NULL, NULL, (u_int32_t*)&total_len, NULL); + urndis_decap(sc, c, total_len); + +done: + /* Setup new transfer. */ + usbd_setup_xfer(c->sc_xfer, sc->sc_bulkin_pipe, c, c->sc_buf, + RNDIS_BUFSZ, USBD_SHORT_XFER_OK | USBD_NO_COPY, USBD_NO_TIMEOUT, + urndis_rxeof); + usbd_transfer(c->sc_xfer); +} + +void +urndis_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct urndis_chain *c; + struct urndis_softc *sc; + struct ifnet *ifp; + usbd_status err; + int s; + + c = priv; + sc = c->sc_sc; + ifp = GET_IFP(sc); + + DPRINTF(("%s: urndis_txeof\n", DEVNAME(sc))); + + if (sc->sc_dying) + return; + + s = splnet(); + + ifp->if_timer = 0; + ifp->if_flags &= ~IFF_OACTIVE; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + splx(s); + return; + } + ifp->if_oerrors++; + printf("%s: usb error on tx: %s\n", DEVNAME(sc), + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulkout_pipe); + splx(s); + return; + } + + usbd_get_xfer_status(c->sc_xfer, NULL, NULL, NULL, &err); + + if (c->sc_mbuf != NULL) { + m_freem(c->sc_mbuf); + c->sc_mbuf = NULL; + } + + if (err) + ifp->if_oerrors++; + else + ifp->if_opackets++; + + if (IFQ_IS_EMPTY(&ifp->if_snd) == 0) + urndis_start(ifp); + + splx(s); +} + +int +urndis_match(struct device *parent, void *match, void *aux) +{ + struct usb_attach_arg *uaa; + usb_interface_descriptor_t *id; + + uaa = aux; + + if (!uaa->iface) + return (UMATCH_NONE); + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) + return (UMATCH_NONE); + + if (id->bInterfaceClass == UICLASS_WIRELESS && + id->bInterfaceSubClass == UISUBCLASS_RF && + id->bInterfaceProtocol == UIPROTO_RNDIS) + return (UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO); + + /* XXX Hack for HTC Hero for now */ + if (id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == UISUBCLASS_ABSTRACT_CONTROL_MODEL) + return (UMATCH_IFACECLASS_GENERIC); + + + return (UMATCH_NONE); +} + +void +urndis_attach(struct device *parent, struct device *self, void *aux) +{ + struct urndis_softc *sc; + struct usb_attach_arg *uaa; + struct ifnet *ifp; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usb_config_descriptor_t *cd; + usb_cdc_union_descriptor_t *ud; + const usb_descriptor_t *desc; + usbd_desc_iter_t iter; + int if_ctl, if_data; + int i, j, altcnt; + int s; + u_char eaddr[ETHER_ADDR_LEN]; + void *buf; + size_t bufsz; + u_int32_t filter; + + sc = (void *)self; + uaa = aux; + + sc->sc_udev = uaa->device; + sc->sc_iface_ctl = uaa->iface; + id = usbd_get_interface_descriptor(sc->sc_iface_ctl); + if_ctl = id->bInterfaceNumber; + sc->sc_ifaceno_ctl = if_ctl; + if_data = -1; + + usb_desc_iter_init(sc->sc_udev, &iter); + while ((desc = usb_desc_iter_next(&iter)) != NULL) { + + if (desc->bDescriptorType != UDESC_CS_INTERFACE) { + continue; + } + switch (desc->bDescriptorSubtype) { + case UDESCSUB_CDC_UNION: + ud = (usb_cdc_union_descriptor_t *)desc; + /* XXX bail out when found first? */ + if (if_data == -1) + if_data = ud->bSlaveInterface[0]; + break; + } + } + + if (if_data == -1) { + DPRINTF(("urndis_attach: no union interface\n")); + sc->sc_iface_data = sc->sc_iface_ctl; + } else { + DPRINTF(("urndis_attach: union interface: ctl %u, data %u\n", + if_ctl, if_data)); + for (i = 0; i < uaa->nifaces; i++) { + if (uaa->ifaces[i] == NULL) + continue; + id = usbd_get_interface_descriptor(uaa->ifaces[i]); + if (id && id->bInterfaceNumber == if_data) { + sc->sc_iface_data = uaa->ifaces[i]; + uaa->ifaces[i] = NULL; + } + } + } + + if (sc->sc_iface_data == NULL) { + printf("%s: no data interface\n", DEVNAME(sc)); + return; + } + + id = usbd_get_interface_descriptor(sc->sc_iface_ctl); + sc->sc_intr_no = -1; + for (i = 0; i < id->bNumEndpoints && sc->sc_intr_no == -1; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_iface_ctl, i); + if (!ed) { + printf("%s: no descriptor for interrupt endpoint %u\n", + DEVNAME(sc)); + return; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->sc_intr_no = ed->bEndpointAddress; + break; + } + } + DPRINTF(("%s: found intr endpoint %u\n", DEVNAME(sc), + sc->sc_intr_no)); + + id = usbd_get_interface_descriptor(sc->sc_iface_data); + cd = usbd_get_config_descriptor(sc->sc_udev); + altcnt = usbd_get_no_alts(cd, id->bInterfaceNumber); + + for (j = 0; j < altcnt; j++) { + if (usbd_set_interface(sc->sc_iface_data, j)) { + printf("%s: interface alternate setting %u failed\n", + DEVNAME(sc), j); + return; + } + /* Find endpoints. */ + id = usbd_get_interface_descriptor(sc->sc_iface_data); + sc->sc_bulkin_no = sc->sc_bulkout_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor( + sc->sc_iface_data, i); + if (!ed) { + printf("%s: no descriptor for bulk endpoint " + "%u\n", DEVNAME(sc), i); + return; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_bulkin_no = ed->bEndpointAddress; + } + else if ( + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_bulkout_no = ed->bEndpointAddress; + } + } + + if (sc->sc_bulkin_no != -1 && sc->sc_bulkout_no != -1) { + DPRINTF(("%s: intr=0x%x, in=0x%x, out=0x%x\n", + DEVNAME(sc), + sc->sc_intr_no, sc->sc_bulkin_no, + sc->sc_bulkout_no)); + goto found; + } + } + + if (sc->sc_bulkin_no == -1) + printf("%s: could not find data bulk in\n", DEVNAME(sc)); + if (sc->sc_bulkout_no == -1 ) + printf("%s: could not find data bulk out\n", DEVNAME(sc)); + return; + + found: + + urndis_init(sc); + + s = splnet(); + + if (urndis_ctrl_query(sc, OID_802_3_PERMANENT_ADDRESS, NULL, 0, + &buf, &bufsz) != RNDIS_STATUS_SUCCESS) { + printf("%s: unable to get hardware address\n", DEVNAME(sc)); + return; + } + + if (bufsz == ETHER_ADDR_LEN) { + memcpy(eaddr, buf, ETHER_ADDR_LEN); + printf("%s: address %s\n", DEVNAME(sc), ether_sprintf(eaddr)); + free(buf, M_TEMP); + } else { + printf("%s: invalid address\n", DEVNAME(sc)); + free(buf, M_TEMP); + return; + } + + /* Initialize packet filter */ + sc->sc_filter = RNDIS_PACKET_TYPE_BROADCAST; + sc->sc_filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; + filter = htole32(sc->sc_filter); + if (urndis_ctrl_set(sc, OID_GEN_CURRENT_PACKET_FILTER, &filter, + sizeof(filter)) != RNDIS_STATUS_SUCCESS) { + printf("%s: unable to set data filters\n", DEVNAME(sc)); + return; + } + + bcopy(eaddr, (char *)&sc->sc_arpcom.ac_enaddr, ETHER_ADDR_LEN); + + + ifp = GET_IFP(sc); + ifp->if_softc = sc; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + ifp->if_start = urndis_start; + ifp->if_ioctl = urndis_ioctl; +#if 0 + ifp->if_watchdog = urndis_watchdog; +#endif + + strlcpy(ifp->if_xname, DEVNAME(sc), IFNAMSIZ); + + IFQ_SET_READY(&ifp->if_snd); + + if_attach(ifp); + ether_ifattach(ifp); + + splx(s); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, &sc->sc_dev); +} + +int +urndis_detach(struct device *self, int flags) +{ + struct urndis_softc *sc; + struct ifnet *ifp; + int s; + + sc = (void*)self; + s = splusb(); + + DPRINTF(("urndis_detach: %s flags %u\n", DEVNAME(sc), + flags)); + + sc->sc_dying = 1; + + ifp = GET_IFP(sc); + + ether_ifdetach(ifp); + if_detach(ifp); + + urndis_stop(sc); + + splx(s); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, + &sc->sc_dev); + + return 0; +} + +int +urndis_activate(struct device *self, int devact) +{ + struct urndis_softc *sc; + + sc = (struct urndis_softc *)self; + + switch (devact) { + case DVACT_ACTIVATE: + break; + + case DVACT_DEACTIVATE: + sc->sc_dying = 1; + break; + } + + return 0; +} diff --git a/sys/dev/usb/if_urndisreg.h b/sys/dev/usb/if_urndisreg.h new file mode 100644 index 00000000000..9e02d0a3706 --- /dev/null +++ b/sys/dev/usb/if_urndisreg.h @@ -0,0 +1,334 @@ +/* $OpenBSD: if_urndisreg.h,v 1.1 2010/03/01 23:35:56 mk Exp $ */ + +/* + * Copyright (c) 2010 Jonathan Armani <dbd@asystant.net> + * Copyright (c) 2010 Fabien Romano <fromano@asystant.net> + * Copyright (c) 2010 Michael Knudsen <mk@openbsd.org> + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RNDIS_RX_LIST_CNT 1 +#define RNDIS_TX_LIST_CNT 1 +#define RNDIS_BUFSZ 1542 + +struct urndis_notification { + u_int32_t notification; + u_int32_t reserved; +}; + +struct urndis_chain { + struct urndis_softc *sc_sc; + usbd_xfer_handle sc_xfer; + char *sc_buf; + struct mbuf *sc_mbuf; + int sc_accum; + int sc_idx; +}; + +struct urndis_cdata { + struct urndis_chain sc_rx_chain[RNDIS_RX_LIST_CNT]; + struct urndis_chain sc_tx_chain[RNDIS_TX_LIST_CNT]; + int sc_tx_prod; + int sc_tx_cons; + int sc_tx_cnt; + int sc_rx_prod; +}; + +#define GET_IFP(sc) (&(sc)->sc_arpcom.ac_if) +struct urndis_softc { + struct device sc_dev; + + int sc_dying; + struct arpcom sc_arpcom; + + /* RNDIS device info */ + u_int32_t sc_lim_pktcnt; + u_int32_t sc_lim_pktsz; + u_int32_t sc_pktalign; + u_int32_t sc_filter; + + /* USB goo */ + usbd_device_handle sc_udev; + int sc_ifaceno_ctl; + usbd_interface_handle sc_iface_ctl; + usbd_interface_handle sc_iface_data; + + int sc_intr_no; + usbd_pipe_handle sc_intr_pipe; + int sc_intr_size; + struct urndis_notification sc_intr_buf; + + int sc_bulkin_no; + usbd_pipe_handle sc_bulkin_pipe; + int sc_bulkout_no; + usbd_pipe_handle sc_bulkout_pipe; + + struct urndis_cdata sc_data; +}; + +typedef u_int32_t urndis_status; + +#define RNDIS_STATUS_BUFFER_OVERFLOW 0x80000005L +#define RNDIS_STATUS_FAILURE 0xC0000001L +#define RNDIS_STATUS_INVALID_DATA 0xC0010015L +#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000BL +#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000CL +#define RNDIS_STATUS_NOT_SUPPORTED 0xC00000BBL +#define RNDIS_STATUS_PENDING STATUS_PENDING /* XXX */ +#define RNDIS_STATUS_RESOURCES 0xC000009AL +#define RNDIS_STATUS_SUCCESS 0x00000000L + +typedef u_int32_t urndis_req_id; /* seq nr. */ + +typedef u_int32_t urndis_oid; + +#define OID_GEN_SUPPORTED_LIST 0x00010101 +#define OID_GEN_HARDWARE_STATUS 0x00010102 +#define OID_GEN_MEDIA_SUPPORTED 0x00010103 +#define OID_GEN_MEDIA_IN_USE 0x00010104 +#define OID_GEN_MAXIMUM_LOOKAHEAD 0x00010105 +#define OID_GEN_MAXIMUM_FRAME_SIZE 0x00010106 +#define OID_GEN_LINK_SPEED 0x00010107 +#define OID_GEN_TRANSMIT_BUFFER_SPACE 0x00010108 +#define OID_GEN_RECEIVE_BUFFER_SPACE 0x00010109 +#define OID_GEN_TRANSMIT_BLOCK_SIZE 0x0001010A +#define OID_GEN_RECEIVE_BLOCK_SIZE 0x0001010B +#define OID_GEN_VENDOR_ID 0x0001010C +#define OID_GEN_VENDOR_DESCRIPTION 0x0001010D +#define OID_GEN_CURRENT_PACKET_FILTER 0x0001010E +#define OID_GEN_CURRENT_LOOKAHEAD 0x0001010F +#define OID_GEN_DRIVER_VERSION 0x00010110 +#define OID_GEN_MAXIMUM_TOTAL_SIZE 0x00010111 +#define OID_GEN_PROTOCOL_OPTIONS 0x00010112 +#define OID_GEN_MAC_OPTIONS 0x00010113 +#define OID_GEN_MEDIA_CONNECT_STATUS 0x00010114 +#define OID_GEN_MAXIMUM_SEND_PACKETS 0x00010115 +#define OID_GEN_VENDOR_DRIVER_VERSION 0x00010116 +#define OID_GEN_SUPPORTED_GUIDS 0x00010117 +#define OID_GEN_NETWORK_LAYER_ADDRESSES 0x00010118 +#define OID_GEN_TRANSPORT_HEADER_OFFSET 0x00010119 +#define OID_GEN_MACHINE_NAME 0x0001021A +#define OID_GEN_RNDIS_CONFIG_PARAMETER 0x0001021B +#define OID_GEN_VLAN_ID 0x0001021C + + +#define OID_802_3_PERMANENT_ADDRESS 0x01010101 +#define OID_802_3_CURRENT_ADDRESS 0x01010102 +#define OID_802_3_MULTICAST_LIST 0x01010103 +#define OID_802_3_MAXIMUM_LIST_SIZE 0x01010104 +#define OID_802_3_MAC_OPTIONS 0x01010105 +#define OID_802_3_RCV_ERROR_ALIGNMENT 0x01020101 +#define OID_802_3_XMIT_ONE_COLLISION 0x01020102 +#define OID_802_3_XMIT_MORE_COLLISIONS 0x01020103 +#define OID_802_3_XMIT_DEFERRED 0x01020201 +#define OID_802_3_XMIT_MAX_COLLISIONS 0x01020202 +#define OID_802_3_RCV_OVERRUN 0x01020203 +#define OID_802_3_XMIT_UNDERRUN 0x01020204 +#define OID_802_3_XMIT_HEARTBEAT_FAILURE 0x01020205 +#define OID_802_3_XMIT_TIMES_CRS_LOST 0x01020206 +#define OID_802_3_XMIT_LATE_COLLISIONS 0x01020207 + +typedef u_int32_t urndis_medium; +#define RNDIS_MEDIUM_802_3 0x00000000 + +/* Device flags */ +#define RNDIS_DF_CONNECTIONLESS 0x00000001 +#define RNDIS_DF_CONNECTION_ORIENTED 0x00000002 + +/* + * RNDIS data message + */ +#define REMOTE_NDIS_PACKET_MSG 0x00000001 + +struct urndis_packet_msg { + u_int32_t rm_type; + u_int32_t rm_len; + u_int32_t rm_dataoffset; + u_int32_t rm_datalen; + u_int32_t rm_oobdataoffset; + u_int32_t rm_oobdatalen; + u_int32_t rm_oobdataelements; + u_int32_t rm_pktinfooffset; + u_int32_t rm_pktinfolen; + u_int32_t rm_vchandle; /* XXX zero */ + u_int32_t rm_reserved; +}; + +/* + * RNDIS control messages + */ +struct urndis_comp_hdr { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; + urndis_status rm_status; +}; + +/* Initialize the device. */ +#define REMOTE_NDIS_INITIALIZE_MSG 0x00000002 +#define REMOTE_NDIS_INITIALIZE_CMPLT 0x80000002 + +struct urndis_init_req { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; + u_int32_t rm_ver_major; + u_int32_t rm_ver_minor; + u_int32_t rm_max_xfersz; +}; + +struct urndis_init_comp { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; + urndis_status rm_status; + u_int32_t rm_ver_major; + u_int32_t rm_ver_minor; + u_int32_t rm_devflags; + urndis_medium rm_medium; + u_int32_t rm_pktmaxcnt; + u_int32_t rm_pktmaxsz; + u_int32_t rm_align; + u_int32_t rm_aflistoffset; + u_int32_t rm_aflistsz; +}; + +/* Halt the device. No response sent. */ +#define REMOTE_NDIS_HALT_MSG 0x00000003 + +struct urndis_halt_req { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; +}; + +/* Send a query object. */ +#define REMOTE_NDIS_QUERY_MSG 0x00000004 +#define REMOTE_NDIS_QUERY_CMPLT 0x80000004 + +struct urndis_query_req { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; + urndis_oid rm_oid; + u_int32_t rm_infobuflen; + u_int32_t rm_infobufoffset; + u_int32_t rm_devicevchdl; /* XXX DeviceVcHandle */ +}; + +struct urndis_query_comp { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; + urndis_status rm_status; + u_int32_t rm_infobuflen; + u_int32_t rm_infobufoffset; +}; + +/* Send a set object request. */ +#define REMOTE_NDIS_SET_MSG 0x00000005 +#define REMOTE_NDIS_SET_CMPLT 0x80000005 + +struct urndis_set_req { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; + urndis_oid rm_oid; + u_int32_t rm_infobuflen; + u_int32_t rm_infobufoffset; + u_int32_t rm_devicevchdl; /* XXX DeviceVcHandle */ +}; + +struct urndis_set_comp { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; + urndis_status rm_status; +}; + +#define REMOTE_NDIS_SET_PARAM_NUMERIC 0x00000000 +#define REMOTE_NDIS_SET_PARAM_STRING 0x00000002 + +struct urndis_set_parameter { + u_int32_t rm_nameoffset; + u_int32_t rm_namelen; + u_int32_t rm_type; + u_int32_t rm_valueoffset; + u_int32_t rm_valuelen; +}; + +/* Perform a soft reset on the device. */ +#define REMOTE_NDIS_RESET_MSG 0x00000006 +#define REMOTE_NDIS_RESET_CMPLT 0x80000006 + +struct urndis_reset_req { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; +}; + +struct urndis_reset_comp { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_status rm_status; + u_int32_t rm_adrreset; +}; + +/* 802.3 link-state or undefined message error. */ +#define REMOTE_NDIS_INDICATE_STATUS_MSG 0x00000007 + +struct urndis_status_msg { /* XXX unhappy about the name */ + u_int32_t rm_type; + u_int32_t rm_len; + urndis_status rm_status; + u_int32_t rm_statusbuflen; + u_int32_t rm_statusbufoffset; + urndis_status rm_diagstatus; + u_int32_t rm_erroroffset; +#if 0 + (RNDIS_MESSAGE) Message; /* XXX */ +#endif +}; + +/* Keepalive messsage. May be sent by device. */ +#define REMOTE_NDIS_KEEPALIVE_MSG 0x00000008 +#define REMOTE_NDIS_KEEPALIVE_CMPLT 0x80000008 + +struct urndis_keepalive_req { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; +}; + +struct urndis_keepalive_comp { + u_int32_t rm_type; + u_int32_t rm_len; + urndis_req_id rm_rid; + urndis_status rm_status; +}; + +/* packet filter bits used by OID_GEN_CURRENT_PACKET_FILTER */ +#define RNDIS_PACKET_TYPE_DIRECTED 0x00000001 +#define RNDIS_PACKET_TYPE_MULTICAST 0x00000002 +#define RNDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004 +#define RNDIS_PACKET_TYPE_BROADCAST 0x00000008 +#define RNDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010 +#define RNDIS_PACKET_TYPE_PROMISCUOUS 0x00000020 +#define RNDIS_PACKET_TYPE_SMT 0x00000040 +#define RNDIS_PACKET_TYPE_ALL_LOCAL 0x00000080 +#define RNDIS_PACKET_TYPE_GROUP 0x00001000 +#define RNDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00002000 +#define RNDIS_PACKET_TYPE_FUNCTIONAL 0x00004000 +#define RNDIS_PACKET_TYPE_MAC_FRAME 0x00008000 diff --git a/sys/dev/usb/usb.h b/sys/dev/usb/usb.h index d6207c678ec..c2131b20ca3 100644 --- a/sys/dev/usb/usb.h +++ b/sys/dev/usb/usb.h @@ -1,4 +1,4 @@ -/* $OpenBSD: usb.h,v 1.34 2008/12/09 03:08:07 yuo Exp $ */ +/* $OpenBSD: usb.h,v 1.35 2010/03/01 23:35:56 mk Exp $ */ /* $NetBSD: usb.h,v 1.69 2002/09/22 23:20:50 augustss Exp $ */ /* $FreeBSD: src/sys/dev/usb/usb.h,v 1.14 1999/11/17 22:33:46 n_hibma Exp $ */ @@ -478,6 +478,7 @@ typedef struct { #define UICLASS_WIRELESS 0xe0 #define UISUBCLASS_RF 0x01 #define UIPROTO_BLUETOOTH 0x01 +#define UIPROTO_RNDIS 0x03 #define UICLASS_APPL_SPEC 0xfe #define UISUBCLASS_FIRMWARE_DOWNLOAD 1 |