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

usbnet: Fix tx_bytes statistic running backward in cdc_ncm

cdc_ncm disagrees with usbnet about how much framing overhead should
be counted in the tx_bytes statistics, and tries 'fix' this by
decrementing tx_bytes on the transmit path. But statistics must never
be decremented except due to roll-over; this will thoroughly confuse
user-space. Also, tx_bytes is only incremented by usbnet in the
completion path.

Fix this by requiring drivers that set FLAG_MULTI_FRAME to set a
tx_bytes delta along with the tx_packets count.

Fixes: beeecd42c3b4 ("net: cdc_ncm/cdc_mbim: adding NCM protocol statistics")
Signed-off-by: Ben Hutchings <ben.hutchings@codethink.co.uk>
Signed-off-by: Bjørn Mork <bjorn@mork.no>

authored by

Ben Hutchings and committed by
David S. Miller
7a1e890e 1e9e39f4

+22 -11
+1 -1
drivers/net/usb/asix_common.c
··· 189 189 skb_put(skb, sizeof(padbytes)); 190 190 } 191 191 192 - usbnet_set_skb_tx_stats(skb, 1); 192 + usbnet_set_skb_tx_stats(skb, 1, 0); 193 193 return skb; 194 194 } 195 195
+3 -4
drivers/net/usb/cdc_ncm.c
··· 1177 1177 ctx->tx_overhead += skb_out->len - ctx->tx_curr_frame_payload; 1178 1178 ctx->tx_ntbs++; 1179 1179 1180 - /* usbnet has already counted all the framing overhead. 1180 + /* usbnet will count all the framing overhead by default. 1181 1181 * Adjust the stats so that the tx_bytes counter show real 1182 1182 * payload data instead. 1183 1183 */ 1184 - dev->net->stats.tx_bytes -= skb_out->len - ctx->tx_curr_frame_payload; 1185 - 1186 - usbnet_set_skb_tx_stats(skb_out, n); 1184 + usbnet_set_skb_tx_stats(skb_out, n, 1185 + ctx->tx_curr_frame_payload - skb_out->len); 1187 1186 1188 1187 return skb_out; 1189 1188
+1 -1
drivers/net/usb/sr9800.c
··· 144 144 skb_put(skb, sizeof(padbytes)); 145 145 } 146 146 147 - usbnet_set_skb_tx_stats(skb, 1); 147 + usbnet_set_skb_tx_stats(skb, 1, 0); 148 148 return skb; 149 149 } 150 150
+13 -3
drivers/net/usb/usbnet.c
··· 1346 1346 } else 1347 1347 urb->transfer_flags |= URB_ZERO_PACKET; 1348 1348 } 1349 - entry->length = urb->transfer_buffer_length = length; 1350 - if (!(info->flags & FLAG_MULTI_PACKET)) 1351 - usbnet_set_skb_tx_stats(skb, 1); 1349 + urb->transfer_buffer_length = length; 1350 + 1351 + if (info->flags & FLAG_MULTI_PACKET) { 1352 + /* Driver has set number of packets and a length delta. 1353 + * Calculate the complete length and ensure that it's 1354 + * positive. 1355 + */ 1356 + entry->length += length; 1357 + if (WARN_ON_ONCE(entry->length <= 0)) 1358 + entry->length = length; 1359 + } else { 1360 + usbnet_set_skb_tx_stats(skb, 1, length); 1361 + } 1352 1362 1353 1363 spin_lock_irqsave(&dev->txq.lock, flags); 1354 1364 retval = usb_autopm_get_interface_async(dev->intf);
+4 -2
include/linux/usb/usbnet.h
··· 227 227 struct urb *urb; 228 228 struct usbnet *dev; 229 229 enum skb_state state; 230 - size_t length; 230 + long length; 231 231 unsigned long packets; 232 232 }; 233 233 ··· 235 235 * tx_fixup method before returning an skb. 236 236 */ 237 237 static inline void 238 - usbnet_set_skb_tx_stats(struct sk_buff *skb, unsigned long packets) 238 + usbnet_set_skb_tx_stats(struct sk_buff *skb, 239 + unsigned long packets, long bytes_delta) 239 240 { 240 241 struct skb_data *entry = (struct skb_data *) skb->cb; 241 242 242 243 entry->packets = packets; 244 + entry->length = bytes_delta; 243 245 } 244 246 245 247 extern int usbnet_open(struct net_device *net);