2005-09-01 00:54:36 +08:00
|
|
|
/*
|
|
|
|
* Host Side support for RNDIS Networking Links
|
|
|
|
* Copyright (C) 2005 by David Brownell
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include <linux/ethtool.h>
|
|
|
|
#include <linux/workqueue.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2005-09-01 00:54:36 +08:00
|
|
|
#include <linux/mii.h>
|
|
|
|
#include <linux/usb.h>
|
2006-06-14 00:57:47 +08:00
|
|
|
#include <linux/usb/cdc.h>
|
2008-01-26 06:51:45 +08:00
|
|
|
#include <linux/usb/usbnet.h>
|
|
|
|
#include <linux/usb/rndis_host.h>
|
2005-09-01 00:54:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* RNDIS is NDIS remoted over USB. It's a MSFT variant of CDC ACM ... of
|
|
|
|
* course ACM was intended for modems, not Ethernet links! USB's standard
|
|
|
|
* for Ethernet links is "CDC Ethernet", which is significantly simpler.
|
2006-04-03 02:19:08 +08:00
|
|
|
*
|
|
|
|
* NOTE that Microsoft's "RNDIS 1.0" specification is incomplete. Issues
|
|
|
|
* include:
|
|
|
|
* - Power management in particular relies on information that's scattered
|
|
|
|
* through other documentation, and which is incomplete or incorrect even
|
|
|
|
* there.
|
|
|
|
* - There are various undocumented protocol requirements, such as the
|
|
|
|
* need to send unused garbage in control-OUT messages.
|
|
|
|
* - In some cases, MS-Windows will emit undocumented requests; this
|
|
|
|
* matters more to peripheral implementations than host ones.
|
|
|
|
*
|
2006-12-15 08:01:28 +08:00
|
|
|
* Moreover there's a no-open-specs variant of RNDIS called "ActiveSync".
|
|
|
|
*
|
2006-04-03 02:19:08 +08:00
|
|
|
* For these reasons and others, ** USE OF RNDIS IS STRONGLY DISCOURAGED ** in
|
|
|
|
* favor of such non-proprietary alternatives as CDC Ethernet or the newer (and
|
|
|
|
* currently rare) "Ethernet Emulation Model" (EEM).
|
2005-09-01 00:54:36 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* RNDIS notifications from device: command completion; "reverse"
|
|
|
|
* keepalives; etc
|
|
|
|
*/
|
2008-01-26 06:51:17 +08:00
|
|
|
void rndis_status(struct usbnet *dev, struct urb *urb)
|
2005-09-01 00:54:36 +08:00
|
|
|
{
|
2010-02-17 18:30:23 +08:00
|
|
|
netdev_dbg(dev->net, "rndis status urb, len %d stat %d\n",
|
|
|
|
urb->actual_length, urb->status);
|
2005-09-01 00:54:36 +08:00
|
|
|
// FIXME for keepalives, respond immediately (asynchronously)
|
|
|
|
// if not an RNDIS status, do like cdc_status(dev,urb) does
|
|
|
|
}
|
2008-01-26 06:51:17 +08:00
|
|
|
EXPORT_SYMBOL_GPL(rndis_status);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
2009-07-31 00:41:52 +08:00
|
|
|
/*
|
|
|
|
* RNDIS indicate messages.
|
|
|
|
*/
|
|
|
|
static void rndis_msg_indicate(struct usbnet *dev, struct rndis_indicate *msg,
|
|
|
|
int buflen)
|
|
|
|
{
|
|
|
|
struct cdc_state *info = (void *)&dev->data;
|
|
|
|
struct device *udev = &info->control->dev;
|
|
|
|
|
|
|
|
if (dev->driver_info->indication) {
|
|
|
|
dev->driver_info->indication(dev, msg, buflen);
|
|
|
|
} else {
|
|
|
|
switch (msg->status) {
|
2012-05-12 06:15:39 +08:00
|
|
|
case cpu_to_le32(RNDIS_STATUS_MEDIA_CONNECT):
|
2009-07-31 00:41:52 +08:00
|
|
|
dev_info(udev, "rndis media connect\n");
|
|
|
|
break;
|
2012-05-12 06:15:39 +08:00
|
|
|
case cpu_to_le32(RNDIS_STATUS_MEDIA_DISCONNECT):
|
2009-07-31 00:41:52 +08:00
|
|
|
dev_info(udev, "rndis media disconnect\n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_info(udev, "rndis indication: 0x%08x\n",
|
|
|
|
le32_to_cpu(msg->status));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-09-01 00:54:36 +08:00
|
|
|
/*
|
|
|
|
* RPC done RNDIS-style. Caller guarantees:
|
|
|
|
* - message is properly byteswapped
|
|
|
|
* - there's no other request pending
|
|
|
|
* - buf can hold up to 1KB response (required by RNDIS spec)
|
|
|
|
* On return, the first few entries are already byteswapped.
|
|
|
|
*
|
|
|
|
* Call context is likely probe(), before interface name is known,
|
|
|
|
* which is why we won't try to use it in the diagnostics.
|
|
|
|
*/
|
2008-06-18 20:40:12 +08:00
|
|
|
int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf, int buflen)
|
2005-09-01 00:54:36 +08:00
|
|
|
{
|
|
|
|
struct cdc_state *info = (void *) &dev->data;
|
2011-04-14 05:48:55 +08:00
|
|
|
struct usb_cdc_notification notification;
|
2006-12-15 08:01:28 +08:00
|
|
|
int master_ifnum;
|
2005-09-01 00:54:36 +08:00
|
|
|
int retval;
|
2011-04-14 05:48:55 +08:00
|
|
|
int partial;
|
2005-09-01 00:54:36 +08:00
|
|
|
unsigned count;
|
|
|
|
__le32 rsp;
|
|
|
|
u32 xid = 0, msg_len, request_id;
|
|
|
|
|
|
|
|
/* REVISIT when this gets called from contexts other than probe() or
|
|
|
|
* disconnect(): either serialize, or dispatch responses on xid
|
|
|
|
*/
|
|
|
|
|
2006-12-15 08:01:28 +08:00
|
|
|
/* Issue the request; xid is unique, don't bother byteswapping it */
|
2012-05-12 06:15:39 +08:00
|
|
|
if (likely(buf->msg_type != cpu_to_le32(RNDIS_MSG_HALT) &&
|
|
|
|
buf->msg_type != cpu_to_le32(RNDIS_MSG_RESET))) {
|
2005-09-01 00:54:36 +08:00
|
|
|
xid = dev->xid++;
|
|
|
|
if (!xid)
|
|
|
|
xid = dev->xid++;
|
|
|
|
buf->request_id = (__force __le32) xid;
|
|
|
|
}
|
2006-12-15 08:01:28 +08:00
|
|
|
master_ifnum = info->control->cur_altsetting->desc.bInterfaceNumber;
|
2005-09-01 00:54:36 +08:00
|
|
|
retval = usb_control_msg(dev->udev,
|
|
|
|
usb_sndctrlpipe(dev->udev, 0),
|
|
|
|
USB_CDC_SEND_ENCAPSULATED_COMMAND,
|
|
|
|
USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
2006-12-15 08:01:28 +08:00
|
|
|
0, master_ifnum,
|
2005-09-01 00:54:36 +08:00
|
|
|
buf, le32_to_cpu(buf->msg_len),
|
|
|
|
RNDIS_CONTROL_TIMEOUT_MS);
|
|
|
|
if (unlikely(retval < 0 || xid == 0))
|
|
|
|
return retval;
|
|
|
|
|
2011-04-14 05:48:55 +08:00
|
|
|
/* Some devices don't respond on the control channel until
|
|
|
|
* polled on the status channel, so do that first. */
|
|
|
|
if (dev->driver_info->data & RNDIS_DRIVER_DATA_POLL_STATUS) {
|
|
|
|
retval = usb_interrupt_msg(
|
|
|
|
dev->udev,
|
|
|
|
usb_rcvintpipe(dev->udev,
|
|
|
|
dev->status->desc.bEndpointAddress),
|
|
|
|
¬ification, sizeof(notification), &partial,
|
|
|
|
RNDIS_CONTROL_TIMEOUT_MS);
|
|
|
|
if (unlikely(retval < 0))
|
|
|
|
return retval;
|
|
|
|
}
|
2005-09-01 00:54:36 +08:00
|
|
|
|
2011-04-14 05:48:55 +08:00
|
|
|
/* Poll the control channel; the request probably completed immediately */
|
2012-05-12 06:15:39 +08:00
|
|
|
rsp = buf->msg_type | cpu_to_le32(RNDIS_MSG_COMPLETION);
|
2005-09-01 00:54:36 +08:00
|
|
|
for (count = 0; count < 10; count++) {
|
2006-12-15 08:01:28 +08:00
|
|
|
memset(buf, 0, CONTROL_BUFFER_SIZE);
|
2005-09-01 00:54:36 +08:00
|
|
|
retval = usb_control_msg(dev->udev,
|
|
|
|
usb_rcvctrlpipe(dev->udev, 0),
|
|
|
|
USB_CDC_GET_ENCAPSULATED_RESPONSE,
|
|
|
|
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
2006-12-15 08:01:28 +08:00
|
|
|
0, master_ifnum,
|
2008-06-18 20:40:12 +08:00
|
|
|
buf, buflen,
|
2005-09-01 00:54:36 +08:00
|
|
|
RNDIS_CONTROL_TIMEOUT_MS);
|
|
|
|
if (likely(retval >= 8)) {
|
|
|
|
msg_len = le32_to_cpu(buf->msg_len);
|
|
|
|
request_id = (__force u32) buf->request_id;
|
|
|
|
if (likely(buf->msg_type == rsp)) {
|
|
|
|
if (likely(request_id == xid)) {
|
2012-05-12 06:15:39 +08:00
|
|
|
if (unlikely(rsp ==
|
|
|
|
cpu_to_le32(RNDIS_MSG_RESET_C)))
|
2005-09-01 00:54:36 +08:00
|
|
|
return 0;
|
2012-05-12 06:15:39 +08:00
|
|
|
if (likely(cpu_to_le32(RNDIS_STATUS_SUCCESS)
|
2005-09-01 00:54:36 +08:00
|
|
|
== buf->status))
|
|
|
|
return 0;
|
|
|
|
dev_dbg(&info->control->dev,
|
|
|
|
"rndis reply status %08x\n",
|
|
|
|
le32_to_cpu(buf->status));
|
|
|
|
return -EL3RST;
|
|
|
|
}
|
|
|
|
dev_dbg(&info->control->dev,
|
|
|
|
"rndis reply id %d expected %d\n",
|
|
|
|
request_id, xid);
|
|
|
|
/* then likely retry */
|
|
|
|
} else switch (buf->msg_type) {
|
2012-05-12 06:15:39 +08:00
|
|
|
case cpu_to_le32(RNDIS_MSG_INDICATE): /* fault/event */
|
2009-07-31 00:41:52 +08:00
|
|
|
rndis_msg_indicate(dev, (void *)buf, buflen);
|
2005-09-01 00:54:36 +08:00
|
|
|
break;
|
2012-05-12 06:15:39 +08:00
|
|
|
case cpu_to_le32(RNDIS_MSG_KEEPALIVE): { /* ping */
|
2005-09-01 00:54:36 +08:00
|
|
|
struct rndis_keepalive_c *msg = (void *)buf;
|
|
|
|
|
2012-05-12 06:15:39 +08:00
|
|
|
msg->msg_type = cpu_to_le32(RNDIS_MSG_KEEPALIVE_C);
|
2009-02-15 14:56:56 +08:00
|
|
|
msg->msg_len = cpu_to_le32(sizeof *msg);
|
2012-05-12 06:15:39 +08:00
|
|
|
msg->status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
|
2005-09-01 00:54:36 +08:00
|
|
|
retval = usb_control_msg(dev->udev,
|
|
|
|
usb_sndctrlpipe(dev->udev, 0),
|
|
|
|
USB_CDC_SEND_ENCAPSULATED_COMMAND,
|
|
|
|
USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
2006-12-15 08:01:28 +08:00
|
|
|
0, master_ifnum,
|
2005-09-01 00:54:36 +08:00
|
|
|
msg, sizeof *msg,
|
|
|
|
RNDIS_CONTROL_TIMEOUT_MS);
|
|
|
|
if (unlikely(retval < 0))
|
|
|
|
dev_dbg(&info->control->dev,
|
|
|
|
"rndis keepalive err %d\n",
|
|
|
|
retval);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_dbg(&info->control->dev,
|
|
|
|
"unexpected rndis msg %08x len %d\n",
|
|
|
|
le32_to_cpu(buf->msg_type), msg_len);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* device probably issued a protocol stall; ignore */
|
|
|
|
dev_dbg(&info->control->dev,
|
|
|
|
"rndis response error, code %d\n", retval);
|
|
|
|
}
|
2008-05-15 07:20:16 +08:00
|
|
|
msleep(20);
|
2005-09-01 00:54:36 +08:00
|
|
|
}
|
|
|
|
dev_dbg(&info->control->dev, "rndis response timeout\n");
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
}
|
2008-01-26 06:51:17 +08:00
|
|
|
EXPORT_SYMBOL_GPL(rndis_command);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
2007-04-18 08:53:20 +08:00
|
|
|
/*
|
|
|
|
* rndis_query:
|
|
|
|
*
|
|
|
|
* Performs a query for @oid along with 0 or more bytes of payload as
|
|
|
|
* specified by @in_len. If @reply_len is not set to -1 then the reply
|
|
|
|
* length is checked against this value, resulting in an error if it
|
|
|
|
* doesn't match.
|
|
|
|
*
|
|
|
|
* NOTE: Adding a payload exactly or greater than the size of the expected
|
|
|
|
* response payload is an evident requirement MSFT added for ActiveSync.
|
|
|
|
*
|
|
|
|
* The only exception is for OIDs that return a variably sized response,
|
|
|
|
* in which case no payload should be added. This undocumented (and
|
|
|
|
* nonsensical!) issue was found by sniffing protocol requests from the
|
|
|
|
* ActiveSync 4.1 Windows driver.
|
|
|
|
*/
|
|
|
|
static int rndis_query(struct usbnet *dev, struct usb_interface *intf,
|
2008-03-17 06:21:54 +08:00
|
|
|
void *buf, __le32 oid, u32 in_len,
|
2007-04-18 08:53:20 +08:00
|
|
|
void **reply, int *reply_len)
|
|
|
|
{
|
|
|
|
int retval;
|
|
|
|
union {
|
|
|
|
void *buf;
|
|
|
|
struct rndis_msg_hdr *header;
|
|
|
|
struct rndis_query *get;
|
|
|
|
struct rndis_query_c *get_c;
|
|
|
|
} u;
|
|
|
|
u32 off, len;
|
|
|
|
|
|
|
|
u.buf = buf;
|
|
|
|
|
|
|
|
memset(u.get, 0, sizeof *u.get + in_len);
|
2012-05-12 06:15:39 +08:00
|
|
|
u.get->msg_type = cpu_to_le32(RNDIS_MSG_QUERY);
|
2007-04-18 08:53:20 +08:00
|
|
|
u.get->msg_len = cpu_to_le32(sizeof *u.get + in_len);
|
|
|
|
u.get->oid = oid;
|
|
|
|
u.get->len = cpu_to_le32(in_len);
|
2009-02-15 14:56:56 +08:00
|
|
|
u.get->offset = cpu_to_le32(20);
|
2007-04-18 08:53:20 +08:00
|
|
|
|
2008-06-18 20:40:12 +08:00
|
|
|
retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE);
|
2007-04-18 08:53:20 +08:00
|
|
|
if (unlikely(retval < 0)) {
|
|
|
|
dev_err(&intf->dev, "RNDIS_MSG_QUERY(0x%08x) failed, %d\n",
|
|
|
|
oid, retval);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
off = le32_to_cpu(u.get_c->offset);
|
|
|
|
len = le32_to_cpu(u.get_c->len);
|
|
|
|
if (unlikely((8 + off + len) > CONTROL_BUFFER_SIZE))
|
|
|
|
goto response_error;
|
|
|
|
|
|
|
|
if (*reply_len != -1 && len != *reply_len)
|
|
|
|
goto response_error;
|
|
|
|
|
|
|
|
*reply = (unsigned char *) &u.get_c->request_id + off;
|
|
|
|
*reply_len = len;
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
response_error:
|
|
|
|
dev_err(&intf->dev, "RNDIS_MSG_QUERY(0x%08x) "
|
|
|
|
"invalid response - off %d len %d\n",
|
|
|
|
oid, off, len);
|
|
|
|
return -EDOM;
|
|
|
|
}
|
|
|
|
|
2009-03-21 03:36:00 +08:00
|
|
|
/* same as usbnet_netdev_ops but MTU change not allowed */
|
|
|
|
static const struct net_device_ops rndis_netdev_ops = {
|
|
|
|
.ndo_open = usbnet_open,
|
|
|
|
.ndo_stop = usbnet_stop,
|
|
|
|
.ndo_start_xmit = usbnet_start_xmit,
|
|
|
|
.ndo_tx_timeout = usbnet_tx_timeout,
|
|
|
|
.ndo_set_mac_address = eth_mac_addr,
|
|
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
|
|
};
|
|
|
|
|
2008-01-28 05:34:33 +08:00
|
|
|
int
|
|
|
|
generic_rndis_bind(struct usbnet *dev, struct usb_interface *intf, int flags)
|
2005-09-01 00:54:36 +08:00
|
|
|
{
|
|
|
|
int retval;
|
|
|
|
struct net_device *net = dev->net;
|
2007-01-16 18:03:01 +08:00
|
|
|
struct cdc_state *info = (void *) &dev->data;
|
2005-09-01 00:54:36 +08:00
|
|
|
union {
|
|
|
|
void *buf;
|
|
|
|
struct rndis_msg_hdr *header;
|
|
|
|
struct rndis_init *init;
|
|
|
|
struct rndis_init_c *init_c;
|
|
|
|
struct rndis_query *get;
|
|
|
|
struct rndis_query_c *get_c;
|
|
|
|
struct rndis_set *set;
|
|
|
|
struct rndis_set_c *set_c;
|
2008-01-26 06:50:55 +08:00
|
|
|
struct rndis_halt *halt;
|
2005-09-01 00:54:36 +08:00
|
|
|
} u;
|
2008-05-21 08:34:30 +08:00
|
|
|
u32 tmp;
|
|
|
|
__le32 phym_unspec, *phym;
|
2007-04-18 08:53:20 +08:00
|
|
|
int reply_len;
|
|
|
|
unsigned char *bp;
|
2005-09-01 00:54:36 +08:00
|
|
|
|
|
|
|
/* we can't rely on i/o from stack working, or stack allocation */
|
2006-12-15 08:01:28 +08:00
|
|
|
u.buf = kmalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL);
|
2005-09-01 00:54:36 +08:00
|
|
|
if (!u.buf)
|
|
|
|
return -ENOMEM;
|
|
|
|
retval = usbnet_generic_cdc_bind(dev, intf);
|
|
|
|
if (retval < 0)
|
2007-01-16 18:03:01 +08:00
|
|
|
goto fail;
|
2005-09-01 00:54:36 +08:00
|
|
|
|
2012-05-12 06:15:39 +08:00
|
|
|
u.init->msg_type = cpu_to_le32(RNDIS_MSG_INIT);
|
2009-02-15 14:56:56 +08:00
|
|
|
u.init->msg_len = cpu_to_le32(sizeof *u.init);
|
|
|
|
u.init->major_version = cpu_to_le32(1);
|
|
|
|
u.init->minor_version = cpu_to_le32(0);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
2006-12-15 08:01:28 +08:00
|
|
|
/* max transfer (in spec) is 0x4000 at full speed, but for
|
|
|
|
* TX we'll stick to one Ethernet packet plus RNDIS framing.
|
|
|
|
* For RX we handle drivers that zero-pad to end-of-packet.
|
|
|
|
* Don't let userspace change these settings.
|
2007-04-18 08:53:20 +08:00
|
|
|
*
|
|
|
|
* NOTE: there still seems to be wierdness here, as if we need
|
|
|
|
* to do some more things to make sure WinCE targets accept this.
|
|
|
|
* They default to jumbograms of 8KB or 16KB, which is absurd
|
|
|
|
* for such low data rates and which is also more than Linux
|
|
|
|
* can usually expect to allocate for SKB data...
|
2006-12-15 08:01:28 +08:00
|
|
|
*/
|
|
|
|
net->hard_header_len += sizeof (struct rndis_data_hdr);
|
|
|
|
dev->hard_mtu = net->mtu + net->hard_header_len;
|
|
|
|
|
2008-03-14 06:56:36 +08:00
|
|
|
dev->maxpacket = usb_maxpacket(dev->udev, dev->out, 1);
|
|
|
|
if (dev->maxpacket == 0) {
|
2010-02-17 18:30:24 +08:00
|
|
|
netif_dbg(dev, probe, dev->net,
|
|
|
|
"dev->maxpacket can't be 0\n");
|
2008-03-14 06:56:36 +08:00
|
|
|
retval = -EINVAL;
|
|
|
|
goto fail_and_release;
|
|
|
|
}
|
|
|
|
|
2006-12-15 08:01:28 +08:00
|
|
|
dev->rx_urb_size = dev->hard_mtu + (dev->maxpacket + 1);
|
|
|
|
dev->rx_urb_size &= ~(dev->maxpacket - 1);
|
|
|
|
u.init->max_transfer_size = cpu_to_le32(dev->rx_urb_size);
|
|
|
|
|
2009-03-21 03:36:00 +08:00
|
|
|
net->netdev_ops = &rndis_netdev_ops;
|
|
|
|
|
2008-06-18 20:40:12 +08:00
|
|
|
retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE);
|
2005-09-01 00:54:36 +08:00
|
|
|
if (unlikely(retval < 0)) {
|
|
|
|
/* it might not even be an RNDIS device!! */
|
|
|
|
dev_err(&intf->dev, "RNDIS init failed, %d\n", retval);
|
2007-04-18 08:53:20 +08:00
|
|
|
goto fail_and_release;
|
2006-12-15 08:01:28 +08:00
|
|
|
}
|
|
|
|
tmp = le32_to_cpu(u.init_c->max_transfer_size);
|
|
|
|
if (tmp < dev->hard_mtu) {
|
2007-10-24 06:47:19 +08:00
|
|
|
if (tmp <= net->hard_header_len) {
|
|
|
|
dev_err(&intf->dev,
|
|
|
|
"dev can't take %u byte packets (max %u)\n",
|
|
|
|
dev->hard_mtu, tmp);
|
|
|
|
retval = -EINVAL;
|
2008-01-26 06:50:55 +08:00
|
|
|
goto halt_fail_and_release;
|
2007-10-24 06:47:19 +08:00
|
|
|
}
|
|
|
|
dev_warn(&intf->dev,
|
|
|
|
"dev can't take %u byte packets (max %u), "
|
|
|
|
"adjusting MTU to %u\n",
|
2009-10-15 07:27:41 +08:00
|
|
|
dev->hard_mtu, tmp, tmp - net->hard_header_len);
|
|
|
|
dev->hard_mtu = tmp;
|
|
|
|
net->mtu = dev->hard_mtu - net->hard_header_len;
|
2005-09-01 00:54:36 +08:00
|
|
|
}
|
2006-12-15 08:01:28 +08:00
|
|
|
|
2005-09-01 00:54:36 +08:00
|
|
|
/* REVISIT: peripheral "alignment" request is ignored ... */
|
2006-12-15 08:01:28 +08:00
|
|
|
dev_dbg(&intf->dev,
|
|
|
|
"hard mtu %u (%u from dev), rx buflen %Zu, align %d\n",
|
|
|
|
dev->hard_mtu, tmp, dev->rx_urb_size,
|
2005-09-01 00:54:36 +08:00
|
|
|
1 << le32_to_cpu(u.init_c->packet_alignment));
|
|
|
|
|
2008-01-26 06:51:28 +08:00
|
|
|
/* module has some device initialization code needs to be done right
|
|
|
|
* after RNDIS_INIT */
|
|
|
|
if (dev->driver_info->early_init &&
|
|
|
|
dev->driver_info->early_init(dev) != 0)
|
|
|
|
goto halt_fail_and_release;
|
|
|
|
|
2008-01-28 05:34:33 +08:00
|
|
|
/* Check physical medium */
|
2008-03-23 18:45:44 +08:00
|
|
|
phym = NULL;
|
2008-01-28 05:34:33 +08:00
|
|
|
reply_len = sizeof *phym;
|
2012-05-12 06:15:39 +08:00
|
|
|
retval = rndis_query(dev, intf, u.buf,
|
|
|
|
cpu_to_le32(OID_GEN_PHYSICAL_MEDIUM),
|
2008-01-28 05:34:33 +08:00
|
|
|
0, (void **) &phym, &reply_len);
|
2008-03-23 18:45:44 +08:00
|
|
|
if (retval != 0 || !phym) {
|
2008-01-28 05:34:33 +08:00
|
|
|
/* OID is optional so don't fail here. */
|
2012-05-12 06:15:39 +08:00
|
|
|
phym_unspec = cpu_to_le32(RNDIS_PHYSICAL_MEDIUM_UNSPECIFIED);
|
2008-03-23 18:45:44 +08:00
|
|
|
phym = &phym_unspec;
|
|
|
|
}
|
2008-01-28 05:34:33 +08:00
|
|
|
if ((flags & FLAG_RNDIS_PHYM_WIRELESS) &&
|
2012-05-12 06:15:39 +08:00
|
|
|
*phym != cpu_to_le32(RNDIS_PHYSICAL_MEDIUM_WIRELESS_LAN)) {
|
2010-02-17 18:30:24 +08:00
|
|
|
netif_dbg(dev, probe, dev->net,
|
|
|
|
"driver requires wireless physical medium, but device is not\n");
|
2008-01-28 05:34:33 +08:00
|
|
|
retval = -ENODEV;
|
|
|
|
goto halt_fail_and_release;
|
|
|
|
}
|
|
|
|
if ((flags & FLAG_RNDIS_PHYM_NOT_WIRELESS) &&
|
2012-05-12 06:15:39 +08:00
|
|
|
*phym == cpu_to_le32(RNDIS_PHYSICAL_MEDIUM_WIRELESS_LAN)) {
|
2010-02-17 18:30:24 +08:00
|
|
|
netif_dbg(dev, probe, dev->net,
|
|
|
|
"driver requires non-wireless physical medium, but device is wireless.\n");
|
2008-01-28 05:34:33 +08:00
|
|
|
retval = -ENODEV;
|
|
|
|
goto halt_fail_and_release;
|
|
|
|
}
|
|
|
|
|
2007-04-18 08:53:20 +08:00
|
|
|
/* Get designated host ethernet address */
|
|
|
|
reply_len = ETH_ALEN;
|
2012-05-12 06:15:39 +08:00
|
|
|
retval = rndis_query(dev, intf, u.buf,
|
|
|
|
cpu_to_le32(OID_802_3_PERMANENT_ADDRESS),
|
2007-04-18 08:53:20 +08:00
|
|
|
48, (void **) &bp, &reply_len);
|
|
|
|
if (unlikely(retval< 0)) {
|
2005-09-01 00:54:36 +08:00
|
|
|
dev_err(&intf->dev, "rndis get ethaddr, %d\n", retval);
|
2008-01-26 06:50:55 +08:00
|
|
|
goto halt_fail_and_release;
|
2005-09-01 00:54:36 +08:00
|
|
|
}
|
2007-04-18 08:53:20 +08:00
|
|
|
memcpy(net->dev_addr, bp, ETH_ALEN);
|
2009-10-06 19:05:42 +08:00
|
|
|
memcpy(net->perm_addr, bp, ETH_ALEN);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
|
|
|
/* set a nonzero filter to enable data transfers */
|
|
|
|
memset(u.set, 0, sizeof *u.set);
|
2012-05-12 06:15:39 +08:00
|
|
|
u.set->msg_type = cpu_to_le32(RNDIS_MSG_SET);
|
2009-02-15 14:56:56 +08:00
|
|
|
u.set->msg_len = cpu_to_le32(4 + sizeof *u.set);
|
2012-05-12 06:15:39 +08:00
|
|
|
u.set->oid = cpu_to_le32(OID_GEN_CURRENT_PACKET_FILTER);
|
2009-02-15 14:56:56 +08:00
|
|
|
u.set->len = cpu_to_le32(4);
|
|
|
|
u.set->offset = cpu_to_le32((sizeof *u.set) - 8);
|
2012-05-12 06:15:39 +08:00
|
|
|
*(__le32 *)(u.buf + sizeof *u.set) = cpu_to_le32(RNDIS_DEFAULT_FILTER);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
2008-06-18 20:40:12 +08:00
|
|
|
retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE);
|
2005-09-01 00:54:36 +08:00
|
|
|
if (unlikely(retval < 0)) {
|
|
|
|
dev_err(&intf->dev, "rndis set packet filter, %d\n", retval);
|
2008-01-26 06:50:55 +08:00
|
|
|
goto halt_fail_and_release;
|
2005-09-01 00:54:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
retval = 0;
|
2007-01-16 18:03:01 +08:00
|
|
|
|
|
|
|
kfree(u.buf);
|
|
|
|
return retval;
|
|
|
|
|
2008-01-26 06:50:55 +08:00
|
|
|
halt_fail_and_release:
|
|
|
|
memset(u.halt, 0, sizeof *u.halt);
|
2012-05-12 06:15:39 +08:00
|
|
|
u.halt->msg_type = cpu_to_le32(RNDIS_MSG_HALT);
|
2009-02-15 14:56:56 +08:00
|
|
|
u.halt->msg_len = cpu_to_le32(sizeof *u.halt);
|
2008-06-18 20:40:12 +08:00
|
|
|
(void) rndis_command(dev, (void *)u.halt, CONTROL_BUFFER_SIZE);
|
2007-01-16 18:03:01 +08:00
|
|
|
fail_and_release:
|
|
|
|
usb_set_intfdata(info->data, NULL);
|
|
|
|
usb_driver_release_interface(driver_of(intf), info->data);
|
2007-04-18 08:53:20 +08:00
|
|
|
info->data = NULL;
|
2007-01-16 18:03:01 +08:00
|
|
|
fail:
|
2005-09-01 00:54:36 +08:00
|
|
|
kfree(u.buf);
|
|
|
|
return retval;
|
|
|
|
}
|
2008-01-26 06:51:17 +08:00
|
|
|
EXPORT_SYMBOL_GPL(generic_rndis_bind);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
2008-01-28 05:34:33 +08:00
|
|
|
static int rndis_bind(struct usbnet *dev, struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
return generic_rndis_bind(dev, intf, FLAG_RNDIS_PHYM_NOT_WIRELESS);
|
|
|
|
}
|
|
|
|
|
2008-01-26 06:51:17 +08:00
|
|
|
void rndis_unbind(struct usbnet *dev, struct usb_interface *intf)
|
2005-09-01 00:54:36 +08:00
|
|
|
{
|
|
|
|
struct rndis_halt *halt;
|
|
|
|
|
|
|
|
/* try to clear any rndis state/activity (no i/o from stack!) */
|
2008-01-26 06:50:49 +08:00
|
|
|
halt = kzalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL);
|
2005-09-01 00:54:36 +08:00
|
|
|
if (halt) {
|
2012-05-12 06:15:39 +08:00
|
|
|
halt->msg_type = cpu_to_le32(RNDIS_MSG_HALT);
|
2009-02-15 14:56:56 +08:00
|
|
|
halt->msg_len = cpu_to_le32(sizeof *halt);
|
2008-06-18 20:40:12 +08:00
|
|
|
(void) rndis_command(dev, (void *)halt, CONTROL_BUFFER_SIZE);
|
2005-09-01 00:54:36 +08:00
|
|
|
kfree(halt);
|
|
|
|
}
|
|
|
|
|
2008-01-26 06:50:38 +08:00
|
|
|
usbnet_cdc_unbind(dev, intf);
|
2005-09-01 00:54:36 +08:00
|
|
|
}
|
2008-01-26 06:51:17 +08:00
|
|
|
EXPORT_SYMBOL_GPL(rndis_unbind);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* DATA -- host must not write zlps
|
|
|
|
*/
|
2008-01-26 06:51:17 +08:00
|
|
|
int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
|
2005-09-01 00:54:36 +08:00
|
|
|
{
|
|
|
|
/* peripheral may have batched packets to us... */
|
|
|
|
while (likely(skb->len)) {
|
|
|
|
struct rndis_data_hdr *hdr = (void *)skb->data;
|
|
|
|
struct sk_buff *skb2;
|
|
|
|
u32 msg_len, data_offset, data_len;
|
|
|
|
|
|
|
|
msg_len = le32_to_cpu(hdr->msg_len);
|
|
|
|
data_offset = le32_to_cpu(hdr->data_offset);
|
|
|
|
data_len = le32_to_cpu(hdr->data_len);
|
|
|
|
|
|
|
|
/* don't choke if we see oob, per-packet data, etc */
|
2012-05-12 06:15:39 +08:00
|
|
|
if (unlikely(hdr->msg_type != cpu_to_le32(RNDIS_MSG_PACKET) ||
|
2009-12-03 15:58:21 +08:00
|
|
|
skb->len < msg_len ||
|
|
|
|
(data_offset + data_len + 8) > msg_len)) {
|
2009-06-30 00:52:26 +08:00
|
|
|
dev->net->stats.rx_frame_errors++;
|
2010-02-17 18:30:23 +08:00
|
|
|
netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n",
|
|
|
|
le32_to_cpu(hdr->msg_type),
|
|
|
|
msg_len, data_offset, data_len, skb->len);
|
2005-09-01 00:54:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
skb_pull(skb, 8 + data_offset);
|
|
|
|
|
|
|
|
/* at most one packet left? */
|
|
|
|
if (likely((data_len - skb->len) <= sizeof *hdr)) {
|
|
|
|
skb_trim(skb, data_len);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* try to return all the packets in the batch */
|
|
|
|
skb2 = skb_clone(skb, GFP_ATOMIC);
|
|
|
|
if (unlikely(!skb2))
|
|
|
|
break;
|
|
|
|
skb_pull(skb, msg_len - sizeof *hdr);
|
|
|
|
skb_trim(skb2, data_len);
|
|
|
|
usbnet_skb_return(dev, skb2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* caller will usbnet_skb_return the remaining packet */
|
|
|
|
return 1;
|
|
|
|
}
|
2008-01-26 06:51:17 +08:00
|
|
|
EXPORT_SYMBOL_GPL(rndis_rx_fixup);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
2008-01-26 06:51:17 +08:00
|
|
|
struct sk_buff *
|
2005-10-21 15:21:58 +08:00
|
|
|
rndis_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
|
2005-09-01 00:54:36 +08:00
|
|
|
{
|
|
|
|
struct rndis_data_hdr *hdr;
|
|
|
|
struct sk_buff *skb2;
|
|
|
|
unsigned len = skb->len;
|
|
|
|
|
|
|
|
if (likely(!skb_cloned(skb))) {
|
|
|
|
int room = skb_headroom(skb);
|
|
|
|
|
|
|
|
/* enough head room as-is? */
|
|
|
|
if (unlikely((sizeof *hdr) <= room))
|
|
|
|
goto fill;
|
|
|
|
|
|
|
|
/* enough room, but needs to be readjusted? */
|
|
|
|
room += skb_tailroom(skb);
|
|
|
|
if (likely((sizeof *hdr) <= room)) {
|
|
|
|
skb->data = memmove(skb->head + sizeof *hdr,
|
|
|
|
skb->data, len);
|
2007-04-20 11:29:13 +08:00
|
|
|
skb_set_tail_pointer(skb, len);
|
2005-09-01 00:54:36 +08:00
|
|
|
goto fill;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create a new skb, with the correct size (and tailpad) */
|
|
|
|
skb2 = skb_copy_expand(skb, sizeof *hdr, 1, flags);
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
if (unlikely(!skb2))
|
|
|
|
return skb2;
|
|
|
|
skb = skb2;
|
|
|
|
|
|
|
|
/* fill out the RNDIS header. we won't bother trying to batch
|
|
|
|
* packets; Linux minimizes wasted bandwidth through tx queues.
|
|
|
|
*/
|
|
|
|
fill:
|
|
|
|
hdr = (void *) __skb_push(skb, sizeof *hdr);
|
|
|
|
memset(hdr, 0, sizeof *hdr);
|
2012-05-12 06:15:39 +08:00
|
|
|
hdr->msg_type = cpu_to_le32(RNDIS_MSG_PACKET);
|
2005-09-01 00:54:36 +08:00
|
|
|
hdr->msg_len = cpu_to_le32(skb->len);
|
2009-02-15 14:56:56 +08:00
|
|
|
hdr->data_offset = cpu_to_le32(sizeof(*hdr) - 8);
|
2005-09-01 00:54:36 +08:00
|
|
|
hdr->data_len = cpu_to_le32(len);
|
|
|
|
|
|
|
|
/* FIXME make the last packet always be short ... */
|
|
|
|
return skb;
|
|
|
|
}
|
2008-01-26 06:51:17 +08:00
|
|
|
EXPORT_SYMBOL_GPL(rndis_tx_fixup);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
static const struct driver_info rndis_info = {
|
|
|
|
.description = "RNDIS device",
|
usbnet: use eth%d name for known ethernet devices
The documentation for the USB ethernet devices suggests that
only some devices are supposed to use usb0 as the network interface
name instead of eth0. The logic used there, and documented in
Kconfig for CDC is that eth0 will be used when the mac address
is a globally assigned one, but usb0 is used for the locally
managed range that is typically used on point-to-point links.
Unfortunately, this has caused a lot of pain on the smsc95xx
device that is used on the popular pandaboard without an
EEPROM to store the MAC address, which causes the driver to
call random_ether_address().
Obviously, there should be a proper MAC addressed assigned to
the device, and discussions are ongoing about how to solve
this, but this patch at least makes sure that the default
interface naming gets a little saner and matches what the
user can expect based on the documentation, including for
new devices.
The approach taken here is to flag whether a device might be a
point-to-point link with the new FLAG_POINTTOPOINT setting in
the usbnet driver_info. A driver can set both FLAG_POINTTOPOINT
and FLAG_ETHER if it is not sure (e.g. cdc_ether), or just one
of the two. The usbnet framework only looks at the MAC address
for device naming if both flags are set, otherwise it trusts the
flag.
Signed-off-by: Arnd Bergmann <arnd.bergmann@linaro.org>
Tested-by: Andy Green <andy.green@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-04-02 11:12:02 +08:00
|
|
|
.flags = FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT,
|
2008-01-28 05:34:33 +08:00
|
|
|
.bind = rndis_bind,
|
2005-09-01 00:54:36 +08:00
|
|
|
.unbind = rndis_unbind,
|
|
|
|
.status = rndis_status,
|
|
|
|
.rx_fixup = rndis_rx_fixup,
|
|
|
|
.tx_fixup = rndis_tx_fixup,
|
|
|
|
};
|
|
|
|
|
2011-04-14 05:48:55 +08:00
|
|
|
static const struct driver_info rndis_poll_status_info = {
|
|
|
|
.description = "RNDIS device (poll status before control)",
|
2011-04-15 14:23:45 +08:00
|
|
|
.flags = FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT,
|
2011-04-14 05:48:55 +08:00
|
|
|
.data = RNDIS_DRIVER_DATA_POLL_STATUS,
|
|
|
|
.bind = rndis_bind,
|
|
|
|
.unbind = rndis_unbind,
|
|
|
|
.status = rndis_status,
|
|
|
|
.rx_fixup = rndis_rx_fixup,
|
|
|
|
.tx_fixup = rndis_tx_fixup,
|
|
|
|
};
|
|
|
|
|
2005-09-01 00:54:36 +08:00
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static const struct usb_device_id products [] = {
|
|
|
|
{
|
2011-04-14 05:48:55 +08:00
|
|
|
/* 2Wire HomePortal 1000SW */
|
|
|
|
USB_DEVICE_AND_INTERFACE_INFO(0x1630, 0x0042,
|
|
|
|
USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
|
|
|
|
.driver_info = (unsigned long) &rndis_poll_status_info,
|
|
|
|
}, {
|
2005-09-01 00:54:36 +08:00
|
|
|
/* RNDIS is MSFT's un-official variant of CDC ACM */
|
|
|
|
USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
|
|
|
|
.driver_info = (unsigned long) &rndis_info,
|
2006-12-15 08:01:28 +08:00
|
|
|
}, {
|
|
|
|
/* "ActiveSync" is an undocumented variant of RNDIS, used in WM5 */
|
|
|
|
USB_INTERFACE_INFO(USB_CLASS_MISC, 1, 1),
|
2011-04-14 05:48:55 +08:00
|
|
|
.driver_info = (unsigned long) &rndis_poll_status_info,
|
2008-07-23 04:55:58 +08:00
|
|
|
}, {
|
|
|
|
/* RNDIS for tethering */
|
|
|
|
USB_INTERFACE_INFO(USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
|
|
|
|
.driver_info = (unsigned long) &rndis_info,
|
2005-09-01 00:54:36 +08:00
|
|
|
},
|
|
|
|
{ }, // END
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(usb, products);
|
|
|
|
|
|
|
|
static struct usb_driver rndis_driver = {
|
|
|
|
.name = "rndis_host",
|
|
|
|
.id_table = products,
|
|
|
|
.probe = usbnet_probe,
|
|
|
|
.disconnect = usbnet_disconnect,
|
|
|
|
.suspend = usbnet_suspend,
|
|
|
|
.resume = usbnet_resume,
|
|
|
|
};
|
|
|
|
|
2011-11-19 01:44:20 +08:00
|
|
|
module_usb_driver(rndis_driver);
|
2005-09-01 00:54:36 +08:00
|
|
|
|
|
|
|
MODULE_AUTHOR("David Brownell");
|
|
|
|
MODULE_DESCRIPTION("USB Host side RNDIS driver");
|
|
|
|
MODULE_LICENSE("GPL");
|