Skip to content

Commit 4a0e3e9

Browse files
mrkikodavem330
authored andcommitted
cdc_ncm: Add support for moving NDP to end of NCM frame
NCM specs are not actually mandating a specific position in the frame for the NDP (Network Datagram Pointer). However, some Huawei devices will ignore our aggregates if it is not placed after the datagrams it points to. Add support for doing just this, in a per-device configurable way. While at it, update NCM subdrivers, disabling this functionality in all of them, except in huawei_cdc_ncm where it is enabled instead. We aren't making any distinction between different Huawei NCM devices, based on what the vendor driver does. Standard NCM devices are left unaffected: if they are compliant, they should be always usable, still stay on the safe side. This change has been tested and working with a Huawei E3131 device (which works regardless of NDP position), a Huawei E3531 (also working both ways) and an E3372 (which mandates NDP to be after indexed datagrams). V1->V2: - corrected wrong NDP acronym definition - fixed possible NULL pointer dereference - patch cleanup V2->V3: - Properly account for the NDP size when writing new packets to SKB Signed-off-by: Enrico Mioso <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 5a0266a commit 4a0e3e9

File tree

4 files changed

+67
-10
lines changed

4 files changed

+67
-10
lines changed

drivers/net/usb/cdc_mbim.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf)
158158
if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))
159159
goto err;
160160

161-
ret = cdc_ncm_bind_common(dev, intf, data_altsetting);
161+
ret = cdc_ncm_bind_common(dev, intf, data_altsetting, 0);
162162
if (ret)
163163
goto err;
164164

drivers/net/usb/cdc_ncm.c

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -684,10 +684,12 @@ static void cdc_ncm_free(struct cdc_ncm_ctx *ctx)
684684
ctx->tx_curr_skb = NULL;
685685
}
686686

687+
kfree(ctx->delayed_ndp16);
688+
687689
kfree(ctx);
688690
}
689691

690-
int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting)
692+
int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting, int drvflags)
691693
{
692694
const struct usb_cdc_union_desc *union_desc = NULL;
693695
struct cdc_ncm_ctx *ctx;
@@ -855,6 +857,17 @@ int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_
855857
/* finish setting up the device specific data */
856858
cdc_ncm_setup(dev);
857859

860+
/* Device-specific flags */
861+
ctx->drvflags = drvflags;
862+
863+
/* Allocate the delayed NDP if needed. */
864+
if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END) {
865+
ctx->delayed_ndp16 = kzalloc(ctx->max_ndp_size, GFP_KERNEL);
866+
if (!ctx->delayed_ndp16)
867+
goto error2;
868+
dev_info(&intf->dev, "NDP will be placed at end of frame for this device.");
869+
}
870+
858871
/* override ethtool_ops */
859872
dev->net->ethtool_ops = &cdc_ncm_ethtool_ops;
860873

@@ -954,8 +967,11 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
954967
if (cdc_ncm_select_altsetting(intf) != CDC_NCM_COMM_ALTSETTING_NCM)
955968
return -ENODEV;
956969

957-
/* The NCM data altsetting is fixed */
958-
ret = cdc_ncm_bind_common(dev, intf, CDC_NCM_DATA_ALTSETTING_NCM);
970+
/* The NCM data altsetting is fixed, so we hard-coded it.
971+
* Additionally, generic NCM devices are assumed to accept arbitrarily
972+
* placed NDP.
973+
*/
974+
ret = cdc_ncm_bind_common(dev, intf, CDC_NCM_DATA_ALTSETTING_NCM, 0);
959975

960976
/*
961977
* We should get an event when network connection is "connected" or
@@ -986,6 +1002,14 @@ static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_
9861002
struct usb_cdc_ncm_nth16 *nth16 = (void *)skb->data;
9871003
size_t ndpoffset = le16_to_cpu(nth16->wNdpIndex);
9881004

1005+
/* If NDP should be moved to the end of the NCM package, we can't follow the
1006+
* NTH16 header as we would normally do. NDP isn't written to the SKB yet, and
1007+
* the wNdpIndex field in the header is actually not consistent with reality. It will be later.
1008+
*/
1009+
if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END)
1010+
if (ctx->delayed_ndp16->dwSignature == sign)
1011+
return ctx->delayed_ndp16;
1012+
9891013
/* follow the chain of NDPs, looking for a match */
9901014
while (ndpoffset) {
9911015
ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb->data + ndpoffset);
@@ -995,7 +1019,8 @@ static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_
9951019
}
9961020

9971021
/* align new NDP */
998-
cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max);
1022+
if (!(ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END))
1023+
cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max);
9991024

10001025
/* verify that there is room for the NDP and the datagram (reserve) */
10011026
if ((ctx->tx_max - skb->len - reserve) < ctx->max_ndp_size)
@@ -1008,7 +1033,11 @@ static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_
10081033
nth16->wNdpIndex = cpu_to_le16(skb->len);
10091034

10101035
/* push a new empty NDP */
1011-
ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, ctx->max_ndp_size), 0, ctx->max_ndp_size);
1036+
if (!(ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END))
1037+
ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, ctx->max_ndp_size), 0, ctx->max_ndp_size);
1038+
else
1039+
ndp16 = ctx->delayed_ndp16;
1040+
10121041
ndp16->dwSignature = sign;
10131042
ndp16->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) + sizeof(struct usb_cdc_ncm_dpe16));
10141043
return ndp16;
@@ -1023,6 +1052,15 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
10231052
struct sk_buff *skb_out;
10241053
u16 n = 0, index, ndplen;
10251054
u8 ready2send = 0;
1055+
u32 delayed_ndp_size;
1056+
1057+
/* When our NDP gets written in cdc_ncm_ndp(), then skb_out->len gets updated
1058+
* accordingly. Otherwise, we should check here.
1059+
*/
1060+
if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END)
1061+
delayed_ndp_size = ctx->max_ndp_size;
1062+
else
1063+
delayed_ndp_size = 0;
10261064

10271065
/* if there is a remaining skb, it gets priority */
10281066
if (skb != NULL) {
@@ -1077,7 +1115,7 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
10771115
cdc_ncm_align_tail(skb_out, ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max);
10781116

10791117
/* check if we had enough room left for both NDP and frame */
1080-
if (!ndp16 || skb_out->len + skb->len > ctx->tx_max) {
1118+
if (!ndp16 || skb_out->len + skb->len + delayed_ndp_size > ctx->tx_max) {
10811119
if (n == 0) {
10821120
/* won't fit, MTU problem? */
10831121
dev_kfree_skb_any(skb);
@@ -1150,6 +1188,17 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
11501188
/* variables will be reset at next call */
11511189
}
11521190

1191+
/* If requested, put NDP at end of frame. */
1192+
if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END) {
1193+
nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data;
1194+
cdc_ncm_align_tail(skb_out, ctx->tx_ndp_modulus, 0, ctx->tx_max);
1195+
nth16->wNdpIndex = cpu_to_le16(skb_out->len);
1196+
memcpy(skb_put(skb_out, ctx->max_ndp_size), ctx->delayed_ndp16, ctx->max_ndp_size);
1197+
1198+
/* Zero out delayed NDP - signature checking will naturally fail. */
1199+
ndp16 = memset(ctx->delayed_ndp16, 0, ctx->max_ndp_size);
1200+
}
1201+
11531202
/* If collected data size is less or equal ctx->min_tx_pkt
11541203
* bytes, we send buffers as it is. If we get more data, it
11551204
* would be more efficient for USB HS mobile device with DMA

drivers/net/usb/huawei_cdc_ncm.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,14 @@ static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev,
7373
struct usb_driver *subdriver = ERR_PTR(-ENODEV);
7474
int ret = -ENODEV;
7575
struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
76+
int drvflags = 0;
7677

7778
/* altsetting should always be 1 for NCM devices - so we hard-coded
78-
* it here
79+
* it here. Some huawei devices will need the NDP part of the NCM package to
80+
* be at the end of the frame.
7981
*/
80-
ret = cdc_ncm_bind_common(usbnet_dev, intf, 1);
82+
drvflags |= CDC_NCM_FLAG_NDP_TO_END;
83+
ret = cdc_ncm_bind_common(usbnet_dev, intf, 1, drvflags);
8184
if (ret)
8285
goto err;
8386

include/linux/usb/cdc_ncm.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@
8080
#define CDC_NCM_TIMER_INTERVAL_MIN 5UL
8181
#define CDC_NCM_TIMER_INTERVAL_MAX (U32_MAX / NSEC_PER_USEC)
8282

83+
/* Driver flags */
84+
#define CDC_NCM_FLAG_NDP_TO_END 0x02 /* NDP is placed at end of frame */
85+
8386
#define cdc_ncm_comm_intf_is_mbim(x) ((x)->desc.bInterfaceSubClass == USB_CDC_SUBCLASS_MBIM && \
8487
(x)->desc.bInterfaceProtocol == USB_CDC_PROTO_NONE)
8588
#define cdc_ncm_data_intf_is_mbim(x) ((x)->desc.bInterfaceProtocol == USB_CDC_MBIM_PROTO_NTB)
@@ -103,9 +106,11 @@ struct cdc_ncm_ctx {
103106

104107
spinlock_t mtx;
105108
atomic_t stop;
109+
int drvflags;
106110

107111
u32 timer_interval;
108112
u32 max_ndp_size;
113+
struct usb_cdc_ncm_ndp16 *delayed_ndp16;
109114

110115
u32 tx_timer_pending;
111116
u32 tx_curr_frame_num;
@@ -133,7 +138,7 @@ struct cdc_ncm_ctx {
133138
};
134139

135140
u8 cdc_ncm_select_altsetting(struct usb_interface *intf);
136-
int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting);
141+
int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting, int drvflags);
137142
void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf);
138143
struct sk_buff *cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign);
139144
int cdc_ncm_rx_verify_nth16(struct cdc_ncm_ctx *ctx, struct sk_buff *skb_in);

0 commit comments

Comments
 (0)