| From foo@baz Tue Jan 26 21:35:03 PST 2016 |
| From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn@mork.no> |
| Date: Wed, 23 Dec 2015 13:42:43 +0100 |
| Subject: net: cdc_ncm: avoid changing RX/TX buffers on MTU changes |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn@mork.no> |
| |
| [ Upstream commit 1dfddff5fcd869fcab0c52fafae099dfa435a935 ] |
| |
| 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> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/net/usb/cdc_mbim.c | 2 +- |
| drivers/net/usb/cdc_ncm.c | 31 +++++++++++++++++++++++++++++++ |
| include/linux/usb/cdc_ncm.h | 1 + |
| 3 files changed, 33 insertions(+), 1 deletion(-) |
| |
| --- a/drivers/net/usb/cdc_mbim.c |
| +++ b/drivers/net/usb/cdc_mbim.c |
| @@ -100,7 +100,7 @@ static const struct net_device_ops cdc_m |
| .ndo_stop = usbnet_stop, |
| .ndo_start_xmit = usbnet_start_xmit, |
| .ndo_tx_timeout = usbnet_tx_timeout, |
| - .ndo_change_mtu = usbnet_change_mtu, |
| + .ndo_change_mtu = cdc_ncm_change_mtu, |
| .ndo_set_mac_address = eth_mac_addr, |
| .ndo_validate_addr = eth_validate_addr, |
| .ndo_vlan_rx_add_vid = cdc_mbim_rx_add_vid, |
| --- a/drivers/net/usb/cdc_ncm.c |
| +++ b/drivers/net/usb/cdc_ncm.c |
| @@ -41,6 +41,7 @@ |
| #include <linux/module.h> |
| #include <linux/netdevice.h> |
| #include <linux/ctype.h> |
| +#include <linux/etherdevice.h> |
| #include <linux/ethtool.h> |
| #include <linux/workqueue.h> |
| #include <linux/mii.h> |
| @@ -689,6 +690,33 @@ static void cdc_ncm_free(struct cdc_ncm_ |
| kfree(ctx); |
| } |
| |
| +/* we need to override the usbnet change_mtu ndo for two reasons: |
| + * - respect the negotiated maximum datagram size |
| + * - avoid unwanted changes to rx and tx buffers |
| + */ |
| +int cdc_ncm_change_mtu(struct net_device *net, int new_mtu) |
| +{ |
| + struct usbnet *dev = netdev_priv(net); |
| + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; |
| + int maxmtu = ctx->max_datagram_size - cdc_ncm_eth_hlen(dev); |
| + |
| + if (new_mtu <= 0 || new_mtu > maxmtu) |
| + return -EINVAL; |
| + net->mtu = new_mtu; |
| + return 0; |
| +} |
| +EXPORT_SYMBOL_GPL(cdc_ncm_change_mtu); |
| + |
| +static const struct net_device_ops cdc_ncm_netdev_ops = { |
| + .ndo_open = usbnet_open, |
| + .ndo_stop = usbnet_stop, |
| + .ndo_start_xmit = usbnet_start_xmit, |
| + .ndo_tx_timeout = usbnet_tx_timeout, |
| + .ndo_change_mtu = cdc_ncm_change_mtu, |
| + .ndo_set_mac_address = eth_mac_addr, |
| + .ndo_validate_addr = eth_validate_addr, |
| +}; |
| + |
| int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting, int drvflags) |
| { |
| const struct usb_cdc_union_desc *union_desc = NULL; |
| @@ -874,6 +902,9 @@ advance: |
| /* add our sysfs attrs */ |
| dev->net->sysfs_groups[0] = &cdc_ncm_sysfs_attr_group; |
| |
| + /* must handle MTU changes */ |
| + dev->net->netdev_ops = &cdc_ncm_netdev_ops; |
| + |
| return 0; |
| |
| error2: |
| --- a/include/linux/usb/cdc_ncm.h |
| +++ b/include/linux/usb/cdc_ncm.h |
| @@ -138,6 +138,7 @@ struct cdc_ncm_ctx { |
| }; |
| |
| u8 cdc_ncm_select_altsetting(struct usb_interface *intf); |
| +int cdc_ncm_change_mtu(struct net_device *net, int new_mtu); |
| int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting, int drvflags); |
| void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf); |
| struct sk_buff *cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign); |