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

net: cdc_ncm: avoid changing RX/TX buffers on MTU changes

NCM buffer sizes are negotiated with the device independently of
the network device MTU. The RX buffers are allocated by the
usbnet framework based on the rx_urb_size value set by cdc_ncm. A
single RX buffer can hold a number of MTU sized packets.

The default usbnet change_mtu ndo only modifies rx_urb_size if it
is equal to hard_mtu. And the cdc_ncm driver will set rx_urb_size
and hard_mtu independently of each other, based on dwNtbInMaxSize
and dwNtbOutMaxSize respectively. It was therefore assumed that
usbnet_change_mtu() would never touch rx_urb_size. This failed to
consider the case where dwNtbInMaxSize and dwNtbOutMaxSize happens
to be equal.

Fix by implementing an NCM specific change_mtu ndo, modifying the
netdev MTU without touching the buffer size settings.

Signed-off-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Bjørn Mork and committed by
David S. Miller
1dfddff5 184fc8b5

+33 -1
+1 -1
drivers/net/usb/cdc_mbim.c
··· 100 100 .ndo_stop = usbnet_stop, 101 101 .ndo_start_xmit = usbnet_start_xmit, 102 102 .ndo_tx_timeout = usbnet_tx_timeout, 103 - .ndo_change_mtu = usbnet_change_mtu, 103 + .ndo_change_mtu = cdc_ncm_change_mtu, 104 104 .ndo_set_mac_address = eth_mac_addr, 105 105 .ndo_validate_addr = eth_validate_addr, 106 106 .ndo_vlan_rx_add_vid = cdc_mbim_rx_add_vid,
+31
drivers/net/usb/cdc_ncm.c
··· 41 41 #include <linux/module.h> 42 42 #include <linux/netdevice.h> 43 43 #include <linux/ctype.h> 44 + #include <linux/etherdevice.h> 44 45 #include <linux/ethtool.h> 45 46 #include <linux/workqueue.h> 46 47 #include <linux/mii.h> ··· 690 689 kfree(ctx); 691 690 } 692 691 692 + /* we need to override the usbnet change_mtu ndo for two reasons: 693 + * - respect the negotiated maximum datagram size 694 + * - avoid unwanted changes to rx and tx buffers 695 + */ 696 + int cdc_ncm_change_mtu(struct net_device *net, int new_mtu) 697 + { 698 + struct usbnet *dev = netdev_priv(net); 699 + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; 700 + int maxmtu = ctx->max_datagram_size - cdc_ncm_eth_hlen(dev); 701 + 702 + if (new_mtu <= 0 || new_mtu > maxmtu) 703 + return -EINVAL; 704 + net->mtu = new_mtu; 705 + return 0; 706 + } 707 + EXPORT_SYMBOL_GPL(cdc_ncm_change_mtu); 708 + 709 + static const struct net_device_ops cdc_ncm_netdev_ops = { 710 + .ndo_open = usbnet_open, 711 + .ndo_stop = usbnet_stop, 712 + .ndo_start_xmit = usbnet_start_xmit, 713 + .ndo_tx_timeout = usbnet_tx_timeout, 714 + .ndo_change_mtu = cdc_ncm_change_mtu, 715 + .ndo_set_mac_address = eth_mac_addr, 716 + .ndo_validate_addr = eth_validate_addr, 717 + }; 718 + 693 719 int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting, int drvflags) 694 720 { 695 721 struct cdc_ncm_ctx *ctx; ··· 850 822 851 823 /* add our sysfs attrs */ 852 824 dev->net->sysfs_groups[0] = &cdc_ncm_sysfs_attr_group; 825 + 826 + /* must handle MTU changes */ 827 + dev->net->netdev_ops = &cdc_ncm_netdev_ops; 853 828 854 829 return 0; 855 830
+1
include/linux/usb/cdc_ncm.h
··· 138 138 }; 139 139 140 140 u8 cdc_ncm_select_altsetting(struct usb_interface *intf); 141 + int cdc_ncm_change_mtu(struct net_device *net, int new_mtu); 141 142 int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting, int drvflags); 142 143 void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf); 143 144 struct sk_buff *cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign);