Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <mrkiko.rs@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Enrico Mioso and committed by
David S. Miller
4a0e3e98 5a0266af

+67 -10
+1 -1
drivers/net/usb/cdc_mbim.c
··· 158 158 if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) 159 159 goto err; 160 160 161 - ret = cdc_ncm_bind_common(dev, intf, data_altsetting); 161 + ret = cdc_ncm_bind_common(dev, intf, data_altsetting, 0); 162 162 if (ret) 163 163 goto err; 164 164
+55 -6
drivers/net/usb/cdc_ncm.c
··· 684 684 ctx->tx_curr_skb = NULL; 685 685 } 686 686 687 + kfree(ctx->delayed_ndp16); 688 + 687 689 kfree(ctx); 688 690 } 689 691 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) 691 693 { 692 694 const struct usb_cdc_union_desc *union_desc = NULL; 693 695 struct cdc_ncm_ctx *ctx; ··· 857 855 /* finish setting up the device specific data */ 858 856 cdc_ncm_setup(dev); 859 857 858 + /* Device-specific flags */ 859 + ctx->drvflags = drvflags; 860 + 861 + /* Allocate the delayed NDP if needed. */ 862 + if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END) { 863 + ctx->delayed_ndp16 = kzalloc(ctx->max_ndp_size, GFP_KERNEL); 864 + if (!ctx->delayed_ndp16) 865 + goto error2; 866 + dev_info(&intf->dev, "NDP will be placed at end of frame for this device."); 867 + } 868 + 860 869 /* override ethtool_ops */ 861 870 dev->net->ethtool_ops = &cdc_ncm_ethtool_ops; 862 871 ··· 967 954 if (cdc_ncm_select_altsetting(intf) != CDC_NCM_COMM_ALTSETTING_NCM) 968 955 return -ENODEV; 969 956 970 - /* The NCM data altsetting is fixed */ 971 - ret = cdc_ncm_bind_common(dev, intf, CDC_NCM_DATA_ALTSETTING_NCM); 957 + /* The NCM data altsetting is fixed, so we hard-coded it. 958 + * Additionally, generic NCM devices are assumed to accept arbitrarily 959 + * placed NDP. 960 + */ 961 + ret = cdc_ncm_bind_common(dev, intf, CDC_NCM_DATA_ALTSETTING_NCM, 0); 972 962 973 963 /* 974 964 * We should get an event when network connection is "connected" or ··· 1002 986 struct usb_cdc_ncm_nth16 *nth16 = (void *)skb->data; 1003 987 size_t ndpoffset = le16_to_cpu(nth16->wNdpIndex); 1004 988 989 + /* If NDP should be moved to the end of the NCM package, we can't follow the 990 + * NTH16 header as we would normally do. NDP isn't written to the SKB yet, and 991 + * the wNdpIndex field in the header is actually not consistent with reality. It will be later. 992 + */ 993 + if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END) 994 + if (ctx->delayed_ndp16->dwSignature == sign) 995 + return ctx->delayed_ndp16; 996 + 1005 997 /* follow the chain of NDPs, looking for a match */ 1006 998 while (ndpoffset) { 1007 999 ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb->data + ndpoffset); ··· 1019 995 } 1020 996 1021 997 /* align new NDP */ 1022 - cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max); 998 + if (!(ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END)) 999 + cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max); 1023 1000 1024 1001 /* verify that there is room for the NDP and the datagram (reserve) */ 1025 1002 if ((ctx->tx_max - skb->len - reserve) < ctx->max_ndp_size) ··· 1033 1008 nth16->wNdpIndex = cpu_to_le16(skb->len); 1034 1009 1035 1010 /* push a new empty NDP */ 1036 - ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, ctx->max_ndp_size), 0, ctx->max_ndp_size); 1011 + if (!(ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END)) 1012 + ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, ctx->max_ndp_size), 0, ctx->max_ndp_size); 1013 + else 1014 + ndp16 = ctx->delayed_ndp16; 1015 + 1037 1016 ndp16->dwSignature = sign; 1038 1017 ndp16->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) + sizeof(struct usb_cdc_ncm_dpe16)); 1039 1018 return ndp16; ··· 1052 1023 struct sk_buff *skb_out; 1053 1024 u16 n = 0, index, ndplen; 1054 1025 u8 ready2send = 0; 1026 + u32 delayed_ndp_size; 1027 + 1028 + /* When our NDP gets written in cdc_ncm_ndp(), then skb_out->len gets updated 1029 + * accordingly. Otherwise, we should check here. 1030 + */ 1031 + if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END) 1032 + delayed_ndp_size = ctx->max_ndp_size; 1033 + else 1034 + delayed_ndp_size = 0; 1055 1035 1056 1036 /* if there is a remaining skb, it gets priority */ 1057 1037 if (skb != NULL) { ··· 1115 1077 cdc_ncm_align_tail(skb_out, ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max); 1116 1078 1117 1079 /* check if we had enough room left for both NDP and frame */ 1118 - if (!ndp16 || skb_out->len + skb->len > ctx->tx_max) { 1080 + if (!ndp16 || skb_out->len + skb->len + delayed_ndp_size > ctx->tx_max) { 1119 1081 if (n == 0) { 1120 1082 /* won't fit, MTU problem? */ 1121 1083 dev_kfree_skb_any(skb); ··· 1186 1148 ctx->tx_reason_max_datagram++; /* count reason for transmitting */ 1187 1149 /* frame goes out */ 1188 1150 /* variables will be reset at next call */ 1151 + } 1152 + 1153 + /* If requested, put NDP at end of frame. */ 1154 + if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END) { 1155 + nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data; 1156 + cdc_ncm_align_tail(skb_out, ctx->tx_ndp_modulus, 0, ctx->tx_max); 1157 + nth16->wNdpIndex = cpu_to_le16(skb_out->len); 1158 + memcpy(skb_put(skb_out, ctx->max_ndp_size), ctx->delayed_ndp16, ctx->max_ndp_size); 1159 + 1160 + /* Zero out delayed NDP - signature checking will naturally fail. */ 1161 + ndp16 = memset(ctx->delayed_ndp16, 0, ctx->max_ndp_size); 1189 1162 } 1190 1163 1191 1164 /* If collected data size is less or equal ctx->min_tx_pkt
+5 -2
drivers/net/usb/huawei_cdc_ncm.c
··· 73 73 struct usb_driver *subdriver = ERR_PTR(-ENODEV); 74 74 int ret = -ENODEV; 75 75 struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 76 + int drvflags = 0; 76 77 77 78 /* 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. 79 81 */ 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); 81 84 if (ret) 82 85 goto err; 83 86
+6 -1
include/linux/usb/cdc_ncm.h
··· 80 80 #define CDC_NCM_TIMER_INTERVAL_MIN 5UL 81 81 #define CDC_NCM_TIMER_INTERVAL_MAX (U32_MAX / NSEC_PER_USEC) 82 82 83 + /* Driver flags */ 84 + #define CDC_NCM_FLAG_NDP_TO_END 0x02 /* NDP is placed at end of frame */ 85 + 83 86 #define cdc_ncm_comm_intf_is_mbim(x) ((x)->desc.bInterfaceSubClass == USB_CDC_SUBCLASS_MBIM && \ 84 87 (x)->desc.bInterfaceProtocol == USB_CDC_PROTO_NONE) 85 88 #define cdc_ncm_data_intf_is_mbim(x) ((x)->desc.bInterfaceProtocol == USB_CDC_MBIM_PROTO_NTB) ··· 106 103 107 104 spinlock_t mtx; 108 105 atomic_t stop; 106 + int drvflags; 109 107 110 108 u32 timer_interval; 111 109 u32 max_ndp_size; 110 + struct usb_cdc_ncm_ndp16 *delayed_ndp16; 112 111 113 112 u32 tx_timer_pending; 114 113 u32 tx_curr_frame_num; ··· 138 133 }; 139 134 140 135 u8 cdc_ncm_select_altsetting(struct usb_interface *intf); 141 - int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting); 136 + int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting, int drvflags); 142 137 void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf); 143 138 struct sk_buff *cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign); 144 139 int cdc_ncm_rx_verify_nth16(struct cdc_ncm_ctx *ctx, struct sk_buff *skb_in);