Skip to content

Commit e1069bb

Browse files
Jim Baxterdavem330
authored andcommitted
net: cdc_ncm: Reduce memory use when kernel memory low
The CDC-NCM driver can require large amounts of memory to create skb's and this can be a problem when the memory becomes fragmented. This especially affects embedded systems that have constrained resources but wish to maximise the throughput of CDC-NCM with 16KiB NTB's. The issue is after running for a while the kernel memory can become fragmented and it needs compacting. If the NTB allocation is needed before the memory has been compacted the atomic allocation can fail which can cause increased latency, large re-transmissions or disconnections depending upon the data being transmitted at the time. This situation occurs for less than a second until the kernel has compacted the memory but the failed devices can take a lot longer to recover from the failed TX packets. To ease this temporary situation I modified the CDC-NCM TX path to temporarily switch into a reduced memory mode which allocates an NTB that will fit into a USB_CDC_NCM_NTB_MIN_OUT_SIZE (default 2048 Bytes) sized memory block and only transmit NTB's with a single network frame until the memory situation is resolved. Each time this issue occurs we wait for an increasing number of reduced size allocations before requesting a full size one to not put additional pressure on a low memory system. Once the memory is compacted the CDC-NCM data can resume transmitting at the normal tx_max rate once again. Signed-off-by: Jim Baxter <[email protected]> Reviewed-by: Bjørn Mork <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 2da95be commit e1069bb

File tree

2 files changed

+45
-12
lines changed

2 files changed

+45
-12
lines changed

drivers/net/usb/cdc_ncm.c

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ static const struct cdc_ncm_stats cdc_ncm_gstrings_stats[] = {
8989
CDC_NCM_SIMPLE_STAT(rx_ntbs),
9090
};
9191

92+
#define CDC_NCM_LOW_MEM_MAX_CNT 10
93+
9294
static int cdc_ncm_get_sset_count(struct net_device __always_unused *netdev, int sset)
9395
{
9496
switch (sset) {
@@ -1055,10 +1057,10 @@ static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_
10551057

10561058
/* align new NDP */
10571059
if (!(ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END))
1058-
cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max);
1060+
cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_curr_size);
10591061

10601062
/* verify that there is room for the NDP and the datagram (reserve) */
1061-
if ((ctx->tx_max - skb->len - reserve) < ctx->max_ndp_size)
1063+
if ((ctx->tx_curr_size - skb->len - reserve) < ctx->max_ndp_size)
10621064
return NULL;
10631065

10641066
/* link to it */
@@ -1111,13 +1113,41 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
11111113

11121114
/* allocate a new OUT skb */
11131115
if (!skb_out) {
1114-
skb_out = alloc_skb(ctx->tx_max, GFP_ATOMIC);
1116+
if (ctx->tx_low_mem_val == 0) {
1117+
ctx->tx_curr_size = ctx->tx_max;
1118+
skb_out = alloc_skb(ctx->tx_curr_size, GFP_ATOMIC);
1119+
/* If the memory allocation fails we will wait longer
1120+
* each time before attempting another full size
1121+
* allocation again to not overload the system
1122+
* further.
1123+
*/
1124+
if (skb_out == NULL) {
1125+
ctx->tx_low_mem_max_cnt = min(ctx->tx_low_mem_max_cnt + 1,
1126+
(unsigned)CDC_NCM_LOW_MEM_MAX_CNT);
1127+
ctx->tx_low_mem_val = ctx->tx_low_mem_max_cnt;
1128+
}
1129+
}
11151130
if (skb_out == NULL) {
1116-
if (skb != NULL) {
1117-
dev_kfree_skb_any(skb);
1118-
dev->net->stats.tx_dropped++;
1131+
/* See if a very small allocation is possible.
1132+
* We will send this packet immediately and hope
1133+
* that there is more memory available later.
1134+
*/
1135+
if (skb)
1136+
ctx->tx_curr_size = max(skb->len,
1137+
(u32)USB_CDC_NCM_NTB_MIN_OUT_SIZE);
1138+
else
1139+
ctx->tx_curr_size = USB_CDC_NCM_NTB_MIN_OUT_SIZE;
1140+
skb_out = alloc_skb(ctx->tx_curr_size, GFP_ATOMIC);
1141+
1142+
/* No allocation possible so we will abort */
1143+
if (skb_out == NULL) {
1144+
if (skb != NULL) {
1145+
dev_kfree_skb_any(skb);
1146+
dev->net->stats.tx_dropped++;
1147+
}
1148+
goto exit_no_skb;
11191149
}
1120-
goto exit_no_skb;
1150+
ctx->tx_low_mem_val--;
11211151
}
11221152
/* fill out the initial 16-bit NTB header */
11231153
nth16 = skb_put_zero(skb_out, sizeof(struct usb_cdc_ncm_nth16));
@@ -1148,10 +1178,10 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
11481178
ndp16 = cdc_ncm_ndp(ctx, skb_out, sign, skb->len + ctx->tx_modulus + ctx->tx_remainder);
11491179

11501180
/* align beginning of next frame */
1151-
cdc_ncm_align_tail(skb_out, ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max);
1181+
cdc_ncm_align_tail(skb_out, ctx->tx_modulus, ctx->tx_remainder, ctx->tx_curr_size);
11521182

11531183
/* check if we had enough room left for both NDP and frame */
1154-
if (!ndp16 || skb_out->len + skb->len + delayed_ndp_size > ctx->tx_max) {
1184+
if (!ndp16 || skb_out->len + skb->len + delayed_ndp_size > ctx->tx_curr_size) {
11551185
if (n == 0) {
11561186
/* won't fit, MTU problem? */
11571187
dev_kfree_skb_any(skb);
@@ -1227,7 +1257,7 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
12271257
/* If requested, put NDP at end of frame. */
12281258
if (ctx->drvflags & CDC_NCM_FLAG_NDP_TO_END) {
12291259
nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data;
1230-
cdc_ncm_align_tail(skb_out, ctx->tx_ndp_modulus, 0, ctx->tx_max);
1260+
cdc_ncm_align_tail(skb_out, ctx->tx_ndp_modulus, 0, ctx->tx_curr_size);
12311261
nth16->wNdpIndex = cpu_to_le16(skb_out->len);
12321262
skb_put_data(skb_out, ctx->delayed_ndp16, ctx->max_ndp_size);
12331263

@@ -1246,9 +1276,9 @@ cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign)
12461276
*/
12471277
if (!(dev->driver_info->flags & FLAG_SEND_ZLP) &&
12481278
skb_out->len > ctx->min_tx_pkt) {
1249-
padding_count = ctx->tx_max - skb_out->len;
1279+
padding_count = ctx->tx_curr_size - skb_out->len;
12501280
skb_put_zero(skb_out, padding_count);
1251-
} else if (skb_out->len < ctx->tx_max &&
1281+
} else if (skb_out->len < ctx->tx_curr_size &&
12521282
(skb_out->len % dev->maxpacket) == 0) {
12531283
skb_put_u8(skb_out, 0); /* force short packet */
12541284
}

include/linux/usb/cdc_ncm.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ struct cdc_ncm_ctx {
117117
u32 tx_curr_frame_num;
118118
u32 rx_max;
119119
u32 tx_max;
120+
u32 tx_curr_size;
121+
u32 tx_low_mem_max_cnt;
122+
u32 tx_low_mem_val;
120123
u32 max_datagram_size;
121124
u16 tx_max_datagrams;
122125
u16 tx_remainder;

0 commit comments

Comments
 (0)