mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-23 23:42:43 +00:00
327 lines
9.6 KiB
Diff
327 lines
9.6 KiB
Diff
|
From a2d274c62e44b1995c170595db3865c6fe701226 Mon Sep 17 00:00:00 2001
|
||
|
From: Foster Snowhill <forst@pen.gy>
|
||
|
Date: Wed, 7 Jun 2023 15:57:01 +0200
|
||
|
Subject: [PATCH 3/4] usbnet: ipheth: add CDC NCM support
|
||
|
|
||
|
Recent iOS releases support CDC NCM encapsulation on RX. This mode is
|
||
|
the default on macOS and Windows. In this mode, an iOS device may include
|
||
|
one or more Ethernet frames inside a single URB.
|
||
|
|
||
|
Freshly booted iOS devices start in legacy mode, but are put into
|
||
|
NCM mode by the official Apple driver. When reconnecting such a device
|
||
|
from a macOS/Windows machine to a Linux host, the device stays in
|
||
|
NCM mode, making it unusable with the legacy ipheth driver code.
|
||
|
|
||
|
To correctly support such a device, the driver has to either support
|
||
|
the NCM mode too, or put the device back into legacy mode.
|
||
|
|
||
|
To match the behaviour of the macOS/Windows driver, and since there
|
||
|
is no documented control command to revert to legacy mode, implement
|
||
|
NCM support. The device is attempted to be put into NCM mode by default,
|
||
|
and falls back to legacy mode if the attempt fails.
|
||
|
|
||
|
Signed-off-by: Foster Snowhill <forst@pen.gy>
|
||
|
Tested-by: Georgi Valkov <gvalkov@gmail.com>
|
||
|
Signed-off-by: David S. Miller <davem@davemloft.net>
|
||
|
---
|
||
|
drivers/net/usb/ipheth.c | 180 +++++++++++++++++++++++++++++++++------
|
||
|
1 file changed, 155 insertions(+), 25 deletions(-)
|
||
|
|
||
|
--- a/drivers/net/usb/ipheth.c
|
||
|
+++ b/drivers/net/usb/ipheth.c
|
||
|
@@ -52,6 +52,7 @@
|
||
|
#include <linux/ethtool.h>
|
||
|
#include <linux/usb.h>
|
||
|
#include <linux/workqueue.h>
|
||
|
+#include <linux/usb/cdc.h>
|
||
|
|
||
|
#define USB_VENDOR_APPLE 0x05ac
|
||
|
|
||
|
@@ -59,8 +60,12 @@
|
||
|
#define IPHETH_USBINTF_SUBCLASS 253
|
||
|
#define IPHETH_USBINTF_PROTO 1
|
||
|
|
||
|
-#define IPHETH_BUF_SIZE 1514
|
||
|
#define IPHETH_IP_ALIGN 2 /* padding at front of URB */
|
||
|
+#define IPHETH_NCM_HEADER_SIZE (12 + 96) /* NCMH + NCM0 */
|
||
|
+#define IPHETH_TX_BUF_SIZE ETH_FRAME_LEN
|
||
|
+#define IPHETH_RX_BUF_SIZE_LEGACY (IPHETH_IP_ALIGN + ETH_FRAME_LEN)
|
||
|
+#define IPHETH_RX_BUF_SIZE_NCM 65536
|
||
|
+
|
||
|
#define IPHETH_TX_TIMEOUT (5 * HZ)
|
||
|
|
||
|
#define IPHETH_INTFNUM 2
|
||
|
@@ -71,6 +76,7 @@
|
||
|
#define IPHETH_CTRL_TIMEOUT (5 * HZ)
|
||
|
|
||
|
#define IPHETH_CMD_GET_MACADDR 0x00
|
||
|
+#define IPHETH_CMD_ENABLE_NCM 0x04
|
||
|
#define IPHETH_CMD_CARRIER_CHECK 0x45
|
||
|
|
||
|
#define IPHETH_CARRIER_CHECK_TIMEOUT round_jiffies_relative(1 * HZ)
|
||
|
@@ -97,6 +103,8 @@ struct ipheth_device {
|
||
|
u8 bulk_out;
|
||
|
struct delayed_work carrier_work;
|
||
|
bool confirmed_pairing;
|
||
|
+ int (*rcvbulk_callback)(struct urb *urb);
|
||
|
+ size_t rx_buf_len;
|
||
|
};
|
||
|
|
||
|
static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags);
|
||
|
@@ -116,12 +124,12 @@ static int ipheth_alloc_urbs(struct iphe
|
||
|
if (rx_urb == NULL)
|
||
|
goto free_tx_urb;
|
||
|
|
||
|
- tx_buf = usb_alloc_coherent(iphone->udev, IPHETH_BUF_SIZE,
|
||
|
+ tx_buf = usb_alloc_coherent(iphone->udev, IPHETH_TX_BUF_SIZE,
|
||
|
GFP_KERNEL, &tx_urb->transfer_dma);
|
||
|
if (tx_buf == NULL)
|
||
|
goto free_rx_urb;
|
||
|
|
||
|
- rx_buf = usb_alloc_coherent(iphone->udev, IPHETH_BUF_SIZE + IPHETH_IP_ALIGN,
|
||
|
+ rx_buf = usb_alloc_coherent(iphone->udev, iphone->rx_buf_len,
|
||
|
GFP_KERNEL, &rx_urb->transfer_dma);
|
||
|
if (rx_buf == NULL)
|
||
|
goto free_tx_buf;
|
||
|
@@ -134,7 +142,7 @@ static int ipheth_alloc_urbs(struct iphe
|
||
|
return 0;
|
||
|
|
||
|
free_tx_buf:
|
||
|
- usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE, tx_buf,
|
||
|
+ usb_free_coherent(iphone->udev, IPHETH_TX_BUF_SIZE, tx_buf,
|
||
|
tx_urb->transfer_dma);
|
||
|
free_rx_urb:
|
||
|
usb_free_urb(rx_urb);
|
||
|
@@ -146,9 +154,9 @@ error_nomem:
|
||
|
|
||
|
static void ipheth_free_urbs(struct ipheth_device *iphone)
|
||
|
{
|
||
|
- usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE + IPHETH_IP_ALIGN, iphone->rx_buf,
|
||
|
+ usb_free_coherent(iphone->udev, iphone->rx_buf_len, iphone->rx_buf,
|
||
|
iphone->rx_urb->transfer_dma);
|
||
|
- usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE, iphone->tx_buf,
|
||
|
+ usb_free_coherent(iphone->udev, IPHETH_TX_BUF_SIZE, iphone->tx_buf,
|
||
|
iphone->tx_urb->transfer_dma);
|
||
|
usb_free_urb(iphone->rx_urb);
|
||
|
usb_free_urb(iphone->tx_urb);
|
||
|
@@ -160,15 +168,106 @@ static void ipheth_kill_urbs(struct iphe
|
||
|
usb_kill_urb(dev->rx_urb);
|
||
|
}
|
||
|
|
||
|
-static void ipheth_rcvbulk_callback(struct urb *urb)
|
||
|
+static int ipheth_consume_skb(char *buf, int len, struct ipheth_device *dev)
|
||
|
{
|
||
|
- struct ipheth_device *dev;
|
||
|
struct sk_buff *skb;
|
||
|
- int status;
|
||
|
+
|
||
|
+ skb = dev_alloc_skb(len);
|
||
|
+ if (!skb) {
|
||
|
+ dev->net->stats.rx_dropped++;
|
||
|
+ return -ENOMEM;
|
||
|
+ }
|
||
|
+
|
||
|
+ skb_put_data(skb, buf, len);
|
||
|
+ skb->dev = dev->net;
|
||
|
+ skb->protocol = eth_type_trans(skb, dev->net);
|
||
|
+
|
||
|
+ dev->net->stats.rx_packets++;
|
||
|
+ dev->net->stats.rx_bytes += len;
|
||
|
+ netif_rx(skb);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int ipheth_rcvbulk_callback_legacy(struct urb *urb)
|
||
|
+{
|
||
|
+ struct ipheth_device *dev;
|
||
|
+ char *buf;
|
||
|
+ int len;
|
||
|
+
|
||
|
+ dev = urb->context;
|
||
|
+
|
||
|
+ if (urb->actual_length <= IPHETH_IP_ALIGN) {
|
||
|
+ dev->net->stats.rx_length_errors++;
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+ len = urb->actual_length - IPHETH_IP_ALIGN;
|
||
|
+ buf = urb->transfer_buffer + IPHETH_IP_ALIGN;
|
||
|
+
|
||
|
+ return ipheth_consume_skb(buf, len, dev);
|
||
|
+}
|
||
|
+
|
||
|
+static int ipheth_rcvbulk_callback_ncm(struct urb *urb)
|
||
|
+{
|
||
|
+ struct usb_cdc_ncm_nth16 *ncmh;
|
||
|
+ struct usb_cdc_ncm_ndp16 *ncm0;
|
||
|
+ struct usb_cdc_ncm_dpe16 *dpe;
|
||
|
+ struct ipheth_device *dev;
|
||
|
+ int retval = -EINVAL;
|
||
|
char *buf;
|
||
|
int len;
|
||
|
|
||
|
dev = urb->context;
|
||
|
+
|
||
|
+ if (urb->actual_length < IPHETH_NCM_HEADER_SIZE) {
|
||
|
+ dev->net->stats.rx_length_errors++;
|
||
|
+ return retval;
|
||
|
+ }
|
||
|
+
|
||
|
+ ncmh = urb->transfer_buffer;
|
||
|
+ if (ncmh->dwSignature != cpu_to_le32(USB_CDC_NCM_NTH16_SIGN) ||
|
||
|
+ le16_to_cpu(ncmh->wNdpIndex) >= urb->actual_length) {
|
||
|
+ dev->net->stats.rx_errors++;
|
||
|
+ return retval;
|
||
|
+ }
|
||
|
+
|
||
|
+ ncm0 = urb->transfer_buffer + le16_to_cpu(ncmh->wNdpIndex);
|
||
|
+ if (ncm0->dwSignature != cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN) ||
|
||
|
+ le16_to_cpu(ncmh->wHeaderLength) + le16_to_cpu(ncm0->wLength) >=
|
||
|
+ urb->actual_length) {
|
||
|
+ dev->net->stats.rx_errors++;
|
||
|
+ return retval;
|
||
|
+ }
|
||
|
+
|
||
|
+ dpe = ncm0->dpe16;
|
||
|
+ while (le16_to_cpu(dpe->wDatagramIndex) != 0 &&
|
||
|
+ le16_to_cpu(dpe->wDatagramLength) != 0) {
|
||
|
+ if (le16_to_cpu(dpe->wDatagramIndex) >= urb->actual_length ||
|
||
|
+ le16_to_cpu(dpe->wDatagramIndex) +
|
||
|
+ le16_to_cpu(dpe->wDatagramLength) > urb->actual_length) {
|
||
|
+ dev->net->stats.rx_length_errors++;
|
||
|
+ return retval;
|
||
|
+ }
|
||
|
+
|
||
|
+ buf = urb->transfer_buffer + le16_to_cpu(dpe->wDatagramIndex);
|
||
|
+ len = le16_to_cpu(dpe->wDatagramLength);
|
||
|
+
|
||
|
+ retval = ipheth_consume_skb(buf, len, dev);
|
||
|
+ if (retval != 0)
|
||
|
+ return retval;
|
||
|
+
|
||
|
+ dpe++;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static void ipheth_rcvbulk_callback(struct urb *urb)
|
||
|
+{
|
||
|
+ struct ipheth_device *dev;
|
||
|
+ int retval, status;
|
||
|
+
|
||
|
+ dev = urb->context;
|
||
|
if (dev == NULL)
|
||
|
return;
|
||
|
|
||
|
@@ -191,25 +290,27 @@ static void ipheth_rcvbulk_callback(stru
|
||
|
dev->net->stats.rx_length_errors++;
|
||
|
return;
|
||
|
}
|
||
|
- len = urb->actual_length - IPHETH_IP_ALIGN;
|
||
|
- buf = urb->transfer_buffer + IPHETH_IP_ALIGN;
|
||
|
|
||
|
- skb = dev_alloc_skb(len);
|
||
|
- if (!skb) {
|
||
|
- dev_err(&dev->intf->dev, "%s: dev_alloc_skb: -ENOMEM\n",
|
||
|
- __func__);
|
||
|
- dev->net->stats.rx_dropped++;
|
||
|
+ /* RX URBs starting with 0x00 0x01 do not encapsulate Ethernet frames,
|
||
|
+ * but rather are control frames. Their purpose is not documented, and
|
||
|
+ * they don't affect driver functionality, okay to drop them.
|
||
|
+ * There is usually just one 4-byte control frame as the very first
|
||
|
+ * URB received from the bulk IN endpoint.
|
||
|
+ */
|
||
|
+ if (unlikely
|
||
|
+ (((char *)urb->transfer_buffer)[0] == 0 &&
|
||
|
+ ((char *)urb->transfer_buffer)[1] == 1))
|
||
|
+ goto rx_submit;
|
||
|
+
|
||
|
+ retval = dev->rcvbulk_callback(urb);
|
||
|
+ if (retval != 0) {
|
||
|
+ dev_err(&dev->intf->dev, "%s: callback retval: %d\n",
|
||
|
+ __func__, retval);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
- skb_put_data(skb, buf, len);
|
||
|
- skb->dev = dev->net;
|
||
|
- skb->protocol = eth_type_trans(skb, dev->net);
|
||
|
-
|
||
|
- dev->net->stats.rx_packets++;
|
||
|
- dev->net->stats.rx_bytes += len;
|
||
|
+rx_submit:
|
||
|
dev->confirmed_pairing = true;
|
||
|
- netif_rx(skb);
|
||
|
ipheth_rx_submit(dev, GFP_ATOMIC);
|
||
|
}
|
||
|
|
||
|
@@ -310,6 +411,27 @@ static int ipheth_get_macaddr(struct iph
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
+static int ipheth_enable_ncm(struct ipheth_device *dev)
|
||
|
+{
|
||
|
+ struct usb_device *udev = dev->udev;
|
||
|
+ int retval;
|
||
|
+
|
||
|
+ retval = usb_control_msg(udev,
|
||
|
+ usb_sndctrlpipe(udev, IPHETH_CTRL_ENDP),
|
||
|
+ IPHETH_CMD_ENABLE_NCM, /* request */
|
||
|
+ 0x41, /* request type */
|
||
|
+ 0x00, /* value */
|
||
|
+ 0x02, /* index */
|
||
|
+ NULL,
|
||
|
+ 0,
|
||
|
+ IPHETH_CTRL_TIMEOUT);
|
||
|
+
|
||
|
+ dev_info(&dev->intf->dev, "%s: usb_control_msg: %d\n",
|
||
|
+ __func__, retval);
|
||
|
+
|
||
|
+ return retval;
|
||
|
+}
|
||
|
+
|
||
|
static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags)
|
||
|
{
|
||
|
struct usb_device *udev = dev->udev;
|
||
|
@@ -317,7 +439,7 @@ static int ipheth_rx_submit(struct iphet
|
||
|
|
||
|
usb_fill_bulk_urb(dev->rx_urb, udev,
|
||
|
usb_rcvbulkpipe(udev, dev->bulk_in),
|
||
|
- dev->rx_buf, IPHETH_BUF_SIZE + IPHETH_IP_ALIGN,
|
||
|
+ dev->rx_buf, dev->rx_buf_len,
|
||
|
ipheth_rcvbulk_callback,
|
||
|
dev);
|
||
|
dev->rx_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||
|
@@ -365,7 +487,7 @@ static netdev_tx_t ipheth_tx(struct sk_b
|
||
|
int retval;
|
||
|
|
||
|
/* Paranoid */
|
||
|
- if (skb->len > IPHETH_BUF_SIZE) {
|
||
|
+ if (skb->len > IPHETH_TX_BUF_SIZE) {
|
||
|
WARN(1, "%s: skb too large: %d bytes\n", __func__, skb->len);
|
||
|
dev->net->stats.tx_dropped++;
|
||
|
dev_kfree_skb_any(skb);
|
||
|
@@ -448,6 +570,8 @@ static int ipheth_probe(struct usb_inter
|
||
|
dev->net = netdev;
|
||
|
dev->intf = intf;
|
||
|
dev->confirmed_pairing = false;
|
||
|
+ dev->rx_buf_len = IPHETH_RX_BUF_SIZE_LEGACY;
|
||
|
+ dev->rcvbulk_callback = ipheth_rcvbulk_callback_legacy;
|
||
|
/* Set up endpoints */
|
||
|
hintf = usb_altnum_to_altsetting(intf, IPHETH_ALT_INTFNUM);
|
||
|
if (hintf == NULL) {
|
||
|
@@ -479,6 +603,12 @@ static int ipheth_probe(struct usb_inter
|
||
|
if (retval)
|
||
|
goto err_get_macaddr;
|
||
|
|
||
|
+ retval = ipheth_enable_ncm(dev);
|
||
|
+ if (!retval) {
|
||
|
+ dev->rx_buf_len = IPHETH_RX_BUF_SIZE_NCM;
|
||
|
+ dev->rcvbulk_callback = ipheth_rcvbulk_callback_ncm;
|
||
|
+ }
|
||
|
+
|
||
|
INIT_DELAYED_WORK(&dev->carrier_work, ipheth_carrier_check_work);
|
||
|
|
||
|
retval = ipheth_alloc_urbs(dev);
|