// SPDX-License-Identifier: GPL-2.0-or-later /* * Host Side support for RNDIS Networking Links * Copyright (C) 2005 by David Brownell
*/ #include <linux/module.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/ethtool.h> #include <linux/workqueue.h> #include <linux/slab.h> #include <linux/mii.h> #include <linux/usb.h> #include <linux/usb/cdc.h> #include <linux/usb/usbnet.h> #include <linux/usb/rndis_host.h>
/* * 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. * * 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. * * Moreover there's a no-open-specs variant of RNDIS called "ActiveSync". * * 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).
*/
/* * RNDIS notifications from device: command completion; "reverse" * keepalives; etc
*/ void rndis_status(struct usbnet *dev, struct urb *urb)
{
netdev_dbg(dev->net, "rndis status urb, len %d stat %d\n",
urb->actual_length, urb->status); // FIXME for keepalives, respond immediately (asynchronously) // if not an RNDIS status, do like cdc_status(dev,urb) does
}
EXPORT_SYMBOL_GPL(rndis_status);
if (dev->driver_info->indication) {
dev->driver_info->indication(dev, msg, buflen);
} else {
u32 status = le32_to_cpu(msg->status);
switch (status) { case RNDIS_STATUS_MEDIA_CONNECT:
dev_info(udev, "rndis media connect\n"); break; case RNDIS_STATUS_MEDIA_DISCONNECT:
dev_info(udev, "rndis media disconnect\n"); break; default:
dev_info(udev, "rndis indication: 0x%08x\n", status);
}
}
}
/* * 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.
*/ int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf, int buflen)
{ struct cdc_state *info = (void *) &dev->data; struct usb_cdc_notification notification; int master_ifnum; int retval; int partial; unsigned count;
u32 xid = 0, msg_len, request_id, msg_type, rsp,
status;
/* REVISIT when this gets called from contexts other than probe() or * disconnect(): either serialize, or dispatch responses on xid
*/
/* 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;
}
/* Poll the control channel; the request probably completed immediately */
rsp = le32_to_cpu(buf->msg_type) | RNDIS_MSG_COMPLETION; for (count = 0; count < 10; count++) {
memset(buf, 0, CONTROL_BUFFER_SIZE);
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,
0, master_ifnum,
buf, buflen,
RNDIS_CONTROL_TIMEOUT_MS); if (likely(retval >= 8)) {
msg_type = le32_to_cpu(buf->msg_type);
msg_len = le32_to_cpu(buf->msg_len);
status = le32_to_cpu(buf->status);
request_id = (__force u32) buf->request_id; if (likely(msg_type == rsp)) { if (likely(request_id == xid)) { if (unlikely(rsp == RNDIS_MSG_RESET_C)) return 0; if (likely(RNDIS_STATUS_SUCCESS ==
status)) return 0;
dev_dbg(&info->control->dev, "rndis reply status %08x\n",
status); return -EL3RST;
}
dev_dbg(&info->control->dev, "rndis reply id %d expected %d\n",
request_id, xid); /* then likely retry */
} elseswitch (msg_type) { case RNDIS_MSG_INDICATE: /* fault/event */
rndis_msg_indicate(dev, (void *)buf, buflen); break; case RNDIS_MSG_KEEPALIVE: { /* ping */ struct rndis_keepalive_c *msg = (void *)buf;
/* * 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.
*/ staticint rndis_query(struct usbnet *dev, struct usb_interface *intf, void *buf, u32 oid, u32 in_len, 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;
/* 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. * * NOTE: there still seems to be weirdness 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...
*/
net->hard_header_len += sizeof (struct rndis_data_hdr);
dev->hard_mtu = net->mtu + net->hard_header_len;
retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE); if (unlikely(retval < 0)) { /* it might not even be an RNDIS device!! */
dev_err(&intf->dev, "RNDIS init failed, %d\n", retval); goto fail_and_release;
}
tmp = le32_to_cpu(u.init_c->max_transfer_size); if (tmp < dev->hard_mtu) { 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; goto halt_fail_and_release;
}
dev_warn(&intf->dev, "dev can't take %u byte packets (max %u), " "adjusting MTU to %u\n",
dev->hard_mtu, tmp, tmp - net->hard_header_len);
dev->hard_mtu = tmp;
net->mtu = dev->hard_mtu - net->hard_header_len;
}
/* REVISIT: peripheral "alignment" request is ignored ... */
dev_dbg(&intf->dev, "hard mtu %u (%u from dev), rx buflen %zu, align %d\n",
dev->hard_mtu, tmp, dev->rx_urb_size,
1 << le32_to_cpu(u.init_c->packet_alignment));
/* 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;
/* Check physical medium */
phym = NULL;
reply_len = sizeof *phym;
retval = rndis_query(dev, intf, u.buf,
RNDIS_OID_GEN_PHYSICAL_MEDIUM,
reply_len, (void **)&phym, &reply_len); if (retval != 0 || !phym) { /* OID is optional so don't fail here. */
phym_unspec = cpu_to_le32(RNDIS_PHYSICAL_MEDIUM_UNSPECIFIED);
phym = &phym_unspec;
} if ((flags & FLAG_RNDIS_PHYM_WIRELESS) &&
le32_to_cpup(phym) != RNDIS_PHYSICAL_MEDIUM_WIRELESS_LAN) {
netif_dbg(dev, probe, dev->net, "driver requires wireless physical medium, but device is not\n");
retval = -ENODEV; goto halt_fail_and_release;
} if ((flags & FLAG_RNDIS_PHYM_NOT_WIRELESS) &&
le32_to_cpup(phym) == RNDIS_PHYSICAL_MEDIUM_WIRELESS_LAN) {
netif_dbg(dev, probe, dev->net, "driver requires non-wireless physical medium, but device is wireless.\n");
retval = -ENODEV; goto halt_fail_and_release;
}
/* don't choke if we see oob, per-packet data, etc */ if (unlikely(msg_type != RNDIS_MSG_PACKET || skb->len < msg_len
|| (data_offset + data_len + 8) > msg_len)) {
dev->net->stats.rx_frame_errors++;
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); 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);
if (unlikely(dst_mac_fixup))
usbnet_cdc_zte_rx_fixup(dev, skb2);
usbnet_skb_return(dev, skb2);
}
/* caller will usbnet_skb_return the remaining packet */ if (unlikely(dst_mac_fixup))
usbnet_cdc_zte_rx_fixup(dev, skb);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.