From 7286384bcf982242fd439d27a071e8cba7b2cfd6 Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Fri, 18 Aug 2017 10:19:04 +0200 Subject: [PATCH 1/7] s390/qeth: split L2 xmit paths l2_hard_start_xmit() actually doesn't contain much shared code, and having device-specific paths makes isolated changes a lot easier. So split it into three routines for IQD, OSN and OSD/OSM/OSX. No functional change. Signed-off-by: Julian Wiedmann Signed-off-by: David S. Miller --- drivers/s390/net/qeth_l2_main.c | 225 +++++++++++++++++--------------- 1 file changed, 123 insertions(+), 102 deletions(-) diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index 438a7f29e99f..310bfa225e20 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -676,21 +676,119 @@ static void qeth_l2_set_rx_mode(struct net_device *dev) qeth_promisc_to_bridge(card); } +static int qeth_l2_xmit_iqd(struct qeth_card *card, struct sk_buff *skb, + struct qeth_qdio_out_q *queue, int cast_type) +{ + unsigned int data_offset = ETH_HLEN; + struct qeth_hdr *hdr; + int rc; + + hdr = kmem_cache_alloc(qeth_core_header_cache, GFP_ATOMIC); + if (!hdr) + return -ENOMEM; + qeth_l2_fill_header(card, hdr, skb, cast_type); + hdr->hdr.l2.pkt_length = skb->len; + skb_copy_from_linear_data(skb, ((char *)hdr) + sizeof(*hdr), + data_offset); + + if (!qeth_get_elements_no(card, skb, 1, data_offset)) { + rc = -E2BIG; + goto out; + } + rc = qeth_do_send_packet_fast(card, queue, skb, hdr, data_offset, + data_offset); +out: + if (rc) + kmem_cache_free(qeth_core_header_cache, hdr); + return rc; +} + +static int qeth_l2_xmit_osa(struct qeth_card *card, struct sk_buff *skb, + struct qeth_qdio_out_q *queue, int cast_type) +{ + unsigned int elements, nr_frags; + struct sk_buff *skb_copy; + struct qeth_hdr *hdr; + int rc; + + /* fix hardware limitation: as long as we do not have sbal + * chaining we can not send long frag lists + */ + if (!qeth_get_elements_no(card, skb, 0, 0)) { + rc = skb_linearize(skb); + + if (card->options.performance_stats) { + if (rc) + card->perf_stats.tx_linfail++; + else + card->perf_stats.tx_lin++; + } + if (rc) + return rc; + } + nr_frags = skb_shinfo(skb)->nr_frags; + + /* create a copy with writeable headroom */ + skb_copy = skb_realloc_headroom(skb, sizeof(struct qeth_hdr)); + if (!skb_copy) + return -ENOMEM; + hdr = skb_push(skb_copy, sizeof(struct qeth_hdr)); + qeth_l2_fill_header(card, hdr, skb_copy, cast_type); + if (skb_copy->ip_summed == CHECKSUM_PARTIAL) + qeth_l2_hdr_csum(card, hdr, skb_copy); + + elements = qeth_get_elements_no(card, skb_copy, 0, 0); + if (!elements) { + rc = -E2BIG; + goto out; + } + if (qeth_hdr_chk_and_bounce(skb_copy, &hdr, sizeof(*hdr))) { + rc = -EINVAL; + goto out; + } + rc = qeth_do_send_packet(card, queue, skb_copy, hdr, elements); +out: + if (!rc) { + /* tx success, free dangling original */ + dev_kfree_skb_any(skb); + if (card->options.performance_stats && nr_frags) { + card->perf_stats.sg_skbs_sent++; + /* nr_frags + skb->data */ + card->perf_stats.sg_frags_sent += nr_frags + 1; + } + } else { + /* tx fail, free copy */ + dev_kfree_skb_any(skb_copy); + } + return rc; +} + +static int qeth_l2_xmit_osn(struct qeth_card *card, struct sk_buff *skb, + struct qeth_qdio_out_q *queue) +{ + unsigned int elements; + struct qeth_hdr *hdr; + + if (skb->protocol == htons(ETH_P_IPV6)) + return -EPROTONOSUPPORT; + + hdr = (struct qeth_hdr *)skb->data; + elements = qeth_get_elements_no(card, skb, 0, 0); + if (!elements) + return -E2BIG; + if (qeth_hdr_chk_and_bounce(skb, &hdr, sizeof(*hdr))) + return -EINVAL; + return qeth_do_send_packet(card, queue, skb, hdr, elements); +} + static netdev_tx_t qeth_l2_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) { - int rc; - struct qeth_hdr *hdr = NULL; - int elements = 0; struct qeth_card *card = dev->ml_priv; - struct sk_buff *new_skb = skb; int cast_type = qeth_l2_get_cast_type(card, skb); struct qeth_qdio_out_q *queue; int tx_bytes = skb->len; - int data_offset = -1; - int elements_needed = 0; - int hd_len = 0; - unsigned int nr_frags; + int rc; if (card->qdio.do_prio_queueing || (cast_type && card->info.is_multicast_different)) @@ -704,115 +802,38 @@ static netdev_tx_t qeth_l2_hard_start_xmit(struct sk_buff *skb, goto tx_drop; } - if ((card->info.type == QETH_CARD_TYPE_OSN) && - (skb->protocol == htons(ETH_P_IPV6))) - goto tx_drop; - if (card->options.performance_stats) { card->perf_stats.outbound_cnt++; card->perf_stats.outbound_start_time = qeth_get_micros(); } netif_stop_queue(dev); - /* fix hardware limitation: as long as we do not have sbal - * chaining we can not send long frag lists - */ - if ((card->info.type != QETH_CARD_TYPE_IQD) && - !qeth_get_elements_no(card, new_skb, 0, 0)) { - int lin_rc = skb_linearize(new_skb); - - if (card->options.performance_stats) { - if (lin_rc) - card->perf_stats.tx_linfail++; - else - card->perf_stats.tx_lin++; - } - if (lin_rc) - goto tx_drop; - } - nr_frags = skb_shinfo(new_skb)->nr_frags; - - if (card->info.type == QETH_CARD_TYPE_OSN) - hdr = (struct qeth_hdr *)skb->data; - else { - if (card->info.type == QETH_CARD_TYPE_IQD) { - new_skb = skb; - data_offset = ETH_HLEN; - hd_len = ETH_HLEN; - hdr = kmem_cache_alloc(qeth_core_header_cache, - GFP_ATOMIC); - if (!hdr) - goto tx_drop; - elements_needed++; - qeth_l2_fill_header(card, hdr, new_skb, cast_type); - hdr->hdr.l2.pkt_length = new_skb->len; - skb_copy_from_linear_data(new_skb, - ((char *)hdr) + sizeof(*hdr), - ETH_HLEN); - } else { - /* create a clone with writeable headroom */ - new_skb = skb_realloc_headroom(skb, - sizeof(struct qeth_hdr)); - if (!new_skb) - goto tx_drop; - hdr = skb_push(new_skb, sizeof(struct qeth_hdr)); - qeth_l2_fill_header(card, hdr, new_skb, cast_type); - if (new_skb->ip_summed == CHECKSUM_PARTIAL) - qeth_l2_hdr_csum(card, hdr, new_skb); - } + switch (card->info.type) { + case QETH_CARD_TYPE_OSN: + rc = qeth_l2_xmit_osn(card, skb, queue); + break; + case QETH_CARD_TYPE_IQD: + rc = qeth_l2_xmit_iqd(card, skb, queue, cast_type); + break; + default: + rc = qeth_l2_xmit_osa(card, skb, queue, cast_type); } - elements = qeth_get_elements_no(card, new_skb, elements_needed, - (data_offset > 0) ? data_offset : 0); - if (!elements) { - if (data_offset >= 0) - kmem_cache_free(qeth_core_header_cache, hdr); - goto tx_drop; - } - - if (card->info.type != QETH_CARD_TYPE_IQD) { - if (qeth_hdr_chk_and_bounce(new_skb, &hdr, - sizeof(struct qeth_hdr_layer2))) - goto tx_drop; - rc = qeth_do_send_packet(card, queue, new_skb, hdr, - elements); - } else - rc = qeth_do_send_packet_fast(card, queue, new_skb, hdr, - data_offset, hd_len); if (!rc) { card->stats.tx_packets++; card->stats.tx_bytes += tx_bytes; - if (card->options.performance_stats && nr_frags) { - card->perf_stats.sg_skbs_sent++; - /* nr_frags + skb->data */ - card->perf_stats.sg_frags_sent += nr_frags + 1; - } - if (new_skb != skb) - dev_kfree_skb_any(skb); - rc = NETDEV_TX_OK; - } else { - if (data_offset >= 0) - kmem_cache_free(qeth_core_header_cache, hdr); - - if (rc == -EBUSY) { - if (new_skb != skb) - dev_kfree_skb_any(new_skb); - return NETDEV_TX_BUSY; - } else - goto tx_drop; - } - - netif_wake_queue(dev); - if (card->options.performance_stats) - card->perf_stats.outbound_time += qeth_get_micros() - - card->perf_stats.outbound_start_time; - return rc; + if (card->options.performance_stats) + card->perf_stats.outbound_time += qeth_get_micros() - + card->perf_stats.outbound_start_time; + netif_wake_queue(dev); + return NETDEV_TX_OK; + } else if (rc == -EBUSY) { + return NETDEV_TX_BUSY; + } /* else fall through */ tx_drop: card->stats.tx_dropped++; card->stats.tx_errors++; - if ((new_skb != skb) && new_skb) - dev_kfree_skb_any(new_skb); dev_kfree_skb_any(skb); netif_wake_queue(dev); return NETDEV_TX_OK; From ae79fe03aed71db0771bcb8397425b9de502cf6c Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Fri, 18 Aug 2017 10:19:05 +0200 Subject: [PATCH 2/7] s390/qeth: pass full data length to l2_fill_header() For IQD we already need to fix up the qeth_hdr's length field, and future changes will require more flexibility for OSA as well. The device-specific path knows best what header length it requires, so just pass it from there. While at it, remove the unused qeth_card parameter. No functional change. Signed-off-by: Julian Wiedmann Signed-off-by: David S. Miller --- drivers/s390/net/qeth_l2_main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index 310bfa225e20..3f5b852408d3 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -259,13 +259,14 @@ static void qeth_l2_hdr_csum(struct qeth_card *card, struct qeth_hdr *hdr, card->perf_stats.tx_csum++; } -static void qeth_l2_fill_header(struct qeth_card *card, struct qeth_hdr *hdr, - struct sk_buff *skb, int cast_type) +static void qeth_l2_fill_header(struct qeth_hdr *hdr, struct sk_buff *skb, + int cast_type, unsigned int data_len) { struct vlan_ethhdr *veth = (struct vlan_ethhdr *)skb_mac_header(skb); memset(hdr, 0, sizeof(struct qeth_hdr)); hdr->hdr.l2.id = QETH_HEADER_TYPE_LAYER2; + hdr->hdr.l2.pkt_length = data_len; /* set byte byte 3 to casting flags */ if (cast_type == RTN_MULTICAST) @@ -275,7 +276,6 @@ static void qeth_l2_fill_header(struct qeth_card *card, struct qeth_hdr *hdr, else hdr->hdr.l2.flags[2] |= QETH_LAYER2_FLAG_UNICAST; - hdr->hdr.l2.pkt_length = skb->len - sizeof(struct qeth_hdr); /* VSWITCH relies on the VLAN * information to be present in * the QDIO header */ @@ -686,8 +686,7 @@ static int qeth_l2_xmit_iqd(struct qeth_card *card, struct sk_buff *skb, hdr = kmem_cache_alloc(qeth_core_header_cache, GFP_ATOMIC); if (!hdr) return -ENOMEM; - qeth_l2_fill_header(card, hdr, skb, cast_type); - hdr->hdr.l2.pkt_length = skb->len; + qeth_l2_fill_header(hdr, skb, cast_type, skb->len); skb_copy_from_linear_data(skb, ((char *)hdr) + sizeof(*hdr), data_offset); @@ -733,7 +732,8 @@ static int qeth_l2_xmit_osa(struct qeth_card *card, struct sk_buff *skb, if (!skb_copy) return -ENOMEM; hdr = skb_push(skb_copy, sizeof(struct qeth_hdr)); - qeth_l2_fill_header(card, hdr, skb_copy, cast_type); + qeth_l2_fill_header(hdr, skb_copy, cast_type, + skb_copy->len - sizeof(*hdr)); if (skb_copy->ip_summed == CHECKSUM_PARTIAL) qeth_l2_hdr_csum(card, hdr, skb_copy); From 13ddacb526ff17f45b09d6e02d7270e6bb3acaf8 Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Fri, 18 Aug 2017 10:19:06 +0200 Subject: [PATCH 3/7] s390/qeth: pass TSO header length to fill_buffer() The TSO code already calculates the length of its header element, no need to duplicate this in the low-level code again. Use this opportunity to make hd_len unsigned, and for TSO match its calculation to what tso_fill_header() does. No functional change. Signed-off-by: Julian Wiedmann Signed-off-by: David S. Miller --- drivers/s390/net/qeth_core.h | 7 ++++--- drivers/s390/net/qeth_core_main.c | 18 ++++++++---------- drivers/s390/net/qeth_l2_main.c | 4 ++-- drivers/s390/net/qeth_l3_main.c | 15 +++++++++------ 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/drivers/s390/net/qeth_core.h b/drivers/s390/net/qeth_core.h index 4a4ca5cb37a0..2f5673812810 100644 --- a/drivers/s390/net/qeth_core.h +++ b/drivers/s390/net/qeth_core.h @@ -949,9 +949,10 @@ int qeth_get_elements_for_frags(struct sk_buff *); int qeth_do_send_packet_fast(struct qeth_card *card, struct qeth_qdio_out_q *queue, struct sk_buff *skb, struct qeth_hdr *hdr, unsigned int offset, - int hd_len); -int qeth_do_send_packet(struct qeth_card *, struct qeth_qdio_out_q *, - struct sk_buff *, struct qeth_hdr *, int); + unsigned int hd_len); +int qeth_do_send_packet(struct qeth_card *card, struct qeth_qdio_out_q *queue, + struct sk_buff *skb, struct qeth_hdr *hdr, + unsigned int hd_len, int elements); int qeth_do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); int qeth_core_get_sset_count(struct net_device *, int); void qeth_core_get_ethtool_stats(struct net_device *, diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index 415424e618ad..6cafeceea3ce 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -3956,11 +3956,11 @@ static void __qeth_fill_buffer(struct sk_buff *skb, static int qeth_fill_buffer(struct qeth_qdio_out_q *queue, struct qeth_qdio_out_buffer *buf, struct sk_buff *skb, struct qeth_hdr *hdr, - unsigned int offset, int hd_len) + unsigned int offset, unsigned int hd_len) { struct qdio_buffer *buffer; - int flush_cnt = 0, hdr_len; bool is_first_elem = true; + int flush_cnt = 0; buffer = buf->buffer; refcount_inc(&skb->users); @@ -3970,14 +3970,12 @@ static int qeth_fill_buffer(struct qeth_qdio_out_q *queue, int element = buf->next_element_to_fill; is_first_elem = false; - hdr_len = sizeof(struct qeth_hdr_tso) + - ((struct qeth_hdr_tso *)hdr)->ext.dg_hdr_len; /*fill first buffer entry only with header information */ buffer->element[element].addr = skb->data; - buffer->element[element].length = hdr_len; + buffer->element[element].length = hd_len; buffer->element[element].eflags = SBAL_EFLAGS_FIRST_FRAG; buf->next_element_to_fill++; - skb_pull(skb, hdr_len); + skb_pull(skb, hd_len); } /* IQD */ @@ -4020,7 +4018,7 @@ static int qeth_fill_buffer(struct qeth_qdio_out_q *queue, int qeth_do_send_packet_fast(struct qeth_card *card, struct qeth_qdio_out_q *queue, struct sk_buff *skb, struct qeth_hdr *hdr, unsigned int offset, - int hd_len) + unsigned int hd_len) { struct qeth_qdio_out_buffer *buffer; int index; @@ -4050,8 +4048,8 @@ int qeth_do_send_packet_fast(struct qeth_card *card, EXPORT_SYMBOL_GPL(qeth_do_send_packet_fast); int qeth_do_send_packet(struct qeth_card *card, struct qeth_qdio_out_q *queue, - struct sk_buff *skb, struct qeth_hdr *hdr, - int elements_needed) + struct sk_buff *skb, struct qeth_hdr *hdr, + unsigned int hd_len, int elements_needed) { struct qeth_qdio_out_buffer *buffer; int start_index; @@ -4100,7 +4098,7 @@ int qeth_do_send_packet(struct qeth_card *card, struct qeth_qdio_out_q *queue, } } } - tmp = qeth_fill_buffer(queue, buffer, skb, hdr, 0, 0); + tmp = qeth_fill_buffer(queue, buffer, skb, hdr, 0, hd_len); queue->next_buf_to_fill = (queue->next_buf_to_fill + tmp) % QDIO_MAX_BUFFERS_PER_Q; flush_count += tmp; diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index 3f5b852408d3..c78d9fadb9c8 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -746,7 +746,7 @@ static int qeth_l2_xmit_osa(struct qeth_card *card, struct sk_buff *skb, rc = -EINVAL; goto out; } - rc = qeth_do_send_packet(card, queue, skb_copy, hdr, elements); + rc = qeth_do_send_packet(card, queue, skb_copy, hdr, 0, elements); out: if (!rc) { /* tx success, free dangling original */ @@ -778,7 +778,7 @@ static int qeth_l2_xmit_osn(struct qeth_card *card, struct sk_buff *skb, return -E2BIG; if (qeth_hdr_chk_and_bounce(skb, &hdr, sizeof(*hdr))) return -EINVAL; - return qeth_do_send_packet(card, queue, skb, hdr, elements); + return qeth_do_send_packet(card, queue, skb, hdr, 0, elements); } static netdev_tx_t qeth_l2_hard_start_xmit(struct sk_buff *skb, diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c index 0a3dc14a1381..fa8b638e3842 100644 --- a/drivers/s390/net/qeth_l3_main.c +++ b/drivers/s390/net/qeth_l3_main.c @@ -2637,6 +2637,7 @@ static netdev_tx_t qeth_l3_hard_start_xmit(struct sk_buff *skb, qeth_get_priority_queue(card, skb, ipv, cast_type) : card->qdio.default_out_queue]; int tx_bytes = skb->len; + unsigned int hd_len = 0; bool use_tso; int data_offset = -1; unsigned int nr_frags; @@ -2756,16 +2757,18 @@ static netdev_tx_t qeth_l3_hard_start_xmit(struct sk_buff *skb, if (card->info.type != QETH_CARD_TYPE_IQD) { int len; - if (use_tso) - len = ((unsigned long)tcp_hdr(new_skb) + - tcp_hdrlen(new_skb)) - - (unsigned long)new_skb->data; - else + if (use_tso) { + hd_len = sizeof(struct qeth_hdr_tso) + + ip_hdrlen(new_skb) + tcp_hdrlen(new_skb); + len = hd_len; + } else { len = sizeof(struct qeth_hdr_layer3); + } if (qeth_hdr_chk_and_bounce(new_skb, &hdr, len)) goto tx_drop; - rc = qeth_do_send_packet(card, queue, new_skb, hdr, elements); + rc = qeth_do_send_packet(card, queue, new_skb, hdr, hd_len, + elements); } else rc = qeth_do_send_packet_fast(card, queue, new_skb, hdr, data_offset, 0); From 9c3bfda999fb0e4f284021134e5040bed5c82ac5 Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Fri, 18 Aug 2017 10:19:07 +0200 Subject: [PATCH 4/7] s390/qeth: pass TSO data offset to fill_buffer() For TSO we need to skip the skb's qeth/IP/TCP headers when mapping it into buffer elements. Instead of (mis)using skb_pull(), pass a corresponding offset to fill_buffer() like we already do for IQDs. No actual change in the resulting TSO buffers. Signed-off-by: Julian Wiedmann Signed-off-by: David S. Miller --- drivers/s390/net/qeth_core.h | 2 +- drivers/s390/net/qeth_core_main.c | 10 ++++------ drivers/s390/net/qeth_l2_main.c | 4 ++-- drivers/s390/net/qeth_l3_main.c | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/s390/net/qeth_core.h b/drivers/s390/net/qeth_core.h index 2f5673812810..5753fbc485d5 100644 --- a/drivers/s390/net/qeth_core.h +++ b/drivers/s390/net/qeth_core.h @@ -952,7 +952,7 @@ int qeth_do_send_packet_fast(struct qeth_card *card, unsigned int hd_len); int qeth_do_send_packet(struct qeth_card *card, struct qeth_qdio_out_q *queue, struct sk_buff *skb, struct qeth_hdr *hdr, - unsigned int hd_len, int elements); + unsigned int hd_len, unsigned int offset, int elements); int qeth_do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); int qeth_core_get_sset_count(struct net_device *, int); void qeth_core_get_ethtool_stats(struct net_device *, diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index 6cafeceea3ce..4a5c3028dfb6 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -3975,11 +3975,8 @@ static int qeth_fill_buffer(struct qeth_qdio_out_q *queue, buffer->element[element].length = hd_len; buffer->element[element].eflags = SBAL_EFLAGS_FIRST_FRAG; buf->next_element_to_fill++; - skb_pull(skb, hd_len); - } - /* IQD */ - if (offset > 0) { + } else if (offset) { int element = buf->next_element_to_fill; is_first_elem = false; @@ -4049,7 +4046,8 @@ EXPORT_SYMBOL_GPL(qeth_do_send_packet_fast); int qeth_do_send_packet(struct qeth_card *card, struct qeth_qdio_out_q *queue, struct sk_buff *skb, struct qeth_hdr *hdr, - unsigned int hd_len, int elements_needed) + unsigned int offset, unsigned int hd_len, + int elements_needed) { struct qeth_qdio_out_buffer *buffer; int start_index; @@ -4098,7 +4096,7 @@ int qeth_do_send_packet(struct qeth_card *card, struct qeth_qdio_out_q *queue, } } } - tmp = qeth_fill_buffer(queue, buffer, skb, hdr, 0, hd_len); + tmp = qeth_fill_buffer(queue, buffer, skb, hdr, offset, hd_len); queue->next_buf_to_fill = (queue->next_buf_to_fill + tmp) % QDIO_MAX_BUFFERS_PER_Q; flush_count += tmp; diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index c78d9fadb9c8..a6233ab562f0 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -746,7 +746,7 @@ static int qeth_l2_xmit_osa(struct qeth_card *card, struct sk_buff *skb, rc = -EINVAL; goto out; } - rc = qeth_do_send_packet(card, queue, skb_copy, hdr, 0, elements); + rc = qeth_do_send_packet(card, queue, skb_copy, hdr, 0, 0, elements); out: if (!rc) { /* tx success, free dangling original */ @@ -778,7 +778,7 @@ static int qeth_l2_xmit_osn(struct qeth_card *card, struct sk_buff *skb, return -E2BIG; if (qeth_hdr_chk_and_bounce(skb, &hdr, sizeof(*hdr))) return -EINVAL; - return qeth_do_send_packet(card, queue, skb, hdr, 0, elements); + return qeth_do_send_packet(card, queue, skb, hdr, 0, 0, elements); } static netdev_tx_t qeth_l2_hard_start_xmit(struct sk_buff *skb, diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c index fa8b638e3842..02400bbcb610 100644 --- a/drivers/s390/net/qeth_l3_main.c +++ b/drivers/s390/net/qeth_l3_main.c @@ -2768,7 +2768,7 @@ static netdev_tx_t qeth_l3_hard_start_xmit(struct sk_buff *skb, if (qeth_hdr_chk_and_bounce(new_skb, &hdr, len)) goto tx_drop; rc = qeth_do_send_packet(card, queue, new_skb, hdr, hd_len, - elements); + hd_len, elements); } else rc = qeth_do_send_packet_fast(card, queue, new_skb, hdr, data_offset, 0); From f1588177b259e75a9fabcf3d4d0be4d6b0981d24 Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Fri, 18 Aug 2017 10:19:08 +0200 Subject: [PATCH 5/7] s390/qeth: pass full IQD header length to fill_buffer() This is a prerequisite for unifying the code to build header elements. The TSO header has a different size, so we can no longer rely on implicitly adding the size of a normal qeth_hdr. No functional change. Signed-off-by: Julian Wiedmann Signed-off-by: David S. Miller --- drivers/s390/net/qeth_core_main.c | 3 +-- drivers/s390/net/qeth_l2_main.c | 2 +- drivers/s390/net/qeth_l3_main.c | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index 4a5c3028dfb6..cef9f54d0eb9 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -3981,8 +3981,7 @@ static int qeth_fill_buffer(struct qeth_qdio_out_q *queue, is_first_elem = false; buffer->element[element].addr = hdr; - buffer->element[element].length = sizeof(struct qeth_hdr) + - hd_len; + buffer->element[element].length = hd_len; buffer->element[element].eflags = SBAL_EFLAGS_FIRST_FRAG; buf->is_header[element] = 1; buf->next_element_to_fill++; diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index a6233ab562f0..c85fadf21b38 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -695,7 +695,7 @@ static int qeth_l2_xmit_iqd(struct qeth_card *card, struct sk_buff *skb, goto out; } rc = qeth_do_send_packet_fast(card, queue, skb, hdr, data_offset, - data_offset); + sizeof(*hdr) + data_offset); out: if (rc) kmem_cache_free(qeth_core_header_cache, hdr); diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c index 02400bbcb610..ab661a431f7c 100644 --- a/drivers/s390/net/qeth_l3_main.c +++ b/drivers/s390/net/qeth_l3_main.c @@ -2670,6 +2670,7 @@ static netdev_tx_t qeth_l3_hard_start_xmit(struct sk_buff *skb, if (card->info.type == QETH_CARD_TYPE_IQD) { new_skb = skb; data_offset = ETH_HLEN; + hd_len = sizeof(*hdr); hdr = kmem_cache_alloc(qeth_core_header_cache, GFP_ATOMIC); if (!hdr) goto tx_drop; @@ -2771,7 +2772,7 @@ static netdev_tx_t qeth_l3_hard_start_xmit(struct sk_buff *skb, hd_len, elements); } else rc = qeth_do_send_packet_fast(card, queue, new_skb, hdr, - data_offset, 0); + data_offset, hd_len); if (!rc) { card->stats.tx_packets++; From eaf3cc087f6e15073dd4b02b7ad2f1301702bd74 Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Fri, 18 Aug 2017 10:19:09 +0200 Subject: [PATCH 6/7] s390/qeth: unify code to build header elements After plenty of refactoring, use hd_len as single indication that the skb needs a dedicated header element. This preserves existing behaviour for TSO, as 'hdr' always points to skb->data. Signed-off-by: Julian Wiedmann Signed-off-by: David S. Miller --- drivers/s390/net/qeth_core_main.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index cef9f54d0eb9..ffefdd97abca 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -3953,37 +3953,38 @@ static void __qeth_fill_buffer(struct sk_buff *skb, buf->next_element_to_fill = element; } +/** + * qeth_fill_buffer() - map skb into an output buffer + * @queue: QDIO queue to submit the buffer on + * @buf: buffer to transport the skb + * @skb: skb to map into the buffer + * @hdr: qeth_hdr for this skb. Either at skb->data, or allocated + * from qeth_core_header_cache. + * @offset: when mapping the skb, start at skb->data + offset + * @hd_len: if > 0, build a dedicated header element of this size + */ static int qeth_fill_buffer(struct qeth_qdio_out_q *queue, struct qeth_qdio_out_buffer *buf, struct sk_buff *skb, struct qeth_hdr *hdr, unsigned int offset, unsigned int hd_len) { - struct qdio_buffer *buffer; + struct qdio_buffer *buffer = buf->buffer; bool is_first_elem = true; int flush_cnt = 0; - buffer = buf->buffer; refcount_inc(&skb->users); skb_queue_tail(&buf->skb_list, skb); - if (hdr->hdr.l3.id == QETH_HEADER_TYPE_TSO) { - int element = buf->next_element_to_fill; - is_first_elem = false; - - /*fill first buffer entry only with header information */ - buffer->element[element].addr = skb->data; - buffer->element[element].length = hd_len; - buffer->element[element].eflags = SBAL_EFLAGS_FIRST_FRAG; - buf->next_element_to_fill++; - /* IQD */ - } else if (offset) { + /* build dedicated header element */ + if (hd_len) { int element = buf->next_element_to_fill; is_first_elem = false; buffer->element[element].addr = hdr; buffer->element[element].length = hd_len; buffer->element[element].eflags = SBAL_EFLAGS_FIRST_FRAG; - buf->is_header[element] = 1; + /* remember to free cache-allocated qeth_hdr: */ + buf->is_header[element] = ((void *)hdr != skb->data); buf->next_element_to_fill++; } From 0d6f02d37531ff6fad15c211162f3974fadc9ede Mon Sep 17 00:00:00 2001 From: Julian Wiedmann Date: Fri, 18 Aug 2017 10:19:10 +0200 Subject: [PATCH 7/7] s390/qeth: use skb_cow_head() for L2 OSA xmit Taking a full copy via skb_realloc_headroom() on every xmit is overkill and wastes CPU time; all we actually need is to push on the qeth_hdr. So rework the L2 OSA TX path to avoid the copy. Minor complications arise because struct qeth_hdr must not cross a page boundary. So add a new helper qeth_push_hdr() that catches this, and falls back to the hdr cache that we already use for IQDs. This change uncovered that qeth's TX completion takes rather long. Now that we no longer free the original skb straight away and thus call skb->destructor later than before, throughput regresses significantly. For now, restore old behaviour by adding an explicit skb_orphan(), and a big TODO to improve the TX completion time. Tested-by: Nils Hoppmann Signed-off-by: Julian Wiedmann Signed-off-by: David S. Miller --- drivers/s390/net/qeth_core.h | 1 + drivers/s390/net/qeth_core_main.c | 28 ++++++++++++++++ drivers/s390/net/qeth_l2_main.c | 56 +++++++++++++++++++------------ 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/drivers/s390/net/qeth_core.h b/drivers/s390/net/qeth_core.h index 5753fbc485d5..59e09854c4f7 100644 --- a/drivers/s390/net/qeth_core.h +++ b/drivers/s390/net/qeth_core.h @@ -985,6 +985,7 @@ int qeth_set_features(struct net_device *, netdev_features_t); int qeth_recover_features(struct net_device *); netdev_features_t qeth_fix_features(struct net_device *, netdev_features_t); int qeth_vm_request_mac(struct qeth_card *card); +int qeth_push_hdr(struct sk_buff *skb, struct qeth_hdr **hdr, unsigned int len); /* exports for OSN */ int qeth_osn_assist(struct net_device *, void *, int); diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index ffefdd97abca..bae7440abc01 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -3890,6 +3890,34 @@ int qeth_hdr_chk_and_bounce(struct sk_buff *skb, struct qeth_hdr **hdr, int len) } EXPORT_SYMBOL_GPL(qeth_hdr_chk_and_bounce); +/** + * qeth_push_hdr() - push a qeth_hdr onto an skb. + * @skb: skb that the qeth_hdr should be pushed onto. + * @hdr: double pointer to a qeth_hdr. When returning with >= 0, + * it contains a valid pointer to a qeth_hdr. + * @len: length of the hdr that needs to be pushed on. + * + * Returns the pushed length. If the header can't be pushed on + * (eg. because it would cross a page boundary), it is allocated from + * the cache instead and 0 is returned. + * Error to create the hdr is indicated by returning with < 0. + */ +int qeth_push_hdr(struct sk_buff *skb, struct qeth_hdr **hdr, unsigned int len) +{ + if (skb_headroom(skb) >= len && + qeth_get_elements_for_range((addr_t)skb->data - len, + (addr_t)skb->data) == 1) { + *hdr = skb_push(skb, len); + return len; + } + /* fall back */ + *hdr = kmem_cache_alloc(qeth_core_header_cache, GFP_ATOMIC); + if (!*hdr) + return -ENOMEM; + return 0; +} +EXPORT_SYMBOL_GPL(qeth_push_hdr); + static void __qeth_fill_buffer(struct sk_buff *skb, struct qeth_qdio_out_buffer *buf, bool is_first_elem, unsigned int offset) diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index c85fadf21b38..760b023eae95 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -705,9 +705,11 @@ static int qeth_l2_xmit_iqd(struct qeth_card *card, struct sk_buff *skb, static int qeth_l2_xmit_osa(struct qeth_card *card, struct sk_buff *skb, struct qeth_qdio_out_q *queue, int cast_type) { + int push_len = sizeof(struct qeth_hdr); unsigned int elements, nr_frags; - struct sk_buff *skb_copy; - struct qeth_hdr *hdr; + unsigned int hdr_elements = 0; + struct qeth_hdr *hdr = NULL; + unsigned int hd_len = 0; int rc; /* fix hardware limitation: as long as we do not have sbal @@ -727,38 +729,44 @@ static int qeth_l2_xmit_osa(struct qeth_card *card, struct sk_buff *skb, } nr_frags = skb_shinfo(skb)->nr_frags; - /* create a copy with writeable headroom */ - skb_copy = skb_realloc_headroom(skb, sizeof(struct qeth_hdr)); - if (!skb_copy) - return -ENOMEM; - hdr = skb_push(skb_copy, sizeof(struct qeth_hdr)); - qeth_l2_fill_header(hdr, skb_copy, cast_type, - skb_copy->len - sizeof(*hdr)); - if (skb_copy->ip_summed == CHECKSUM_PARTIAL) - qeth_l2_hdr_csum(card, hdr, skb_copy); + rc = skb_cow_head(skb, push_len); + if (rc) + return rc; + push_len = qeth_push_hdr(skb, &hdr, push_len); + if (push_len < 0) + return push_len; + if (!push_len) { + /* hdr was allocated from cache */ + hd_len = sizeof(*hdr); + hdr_elements = 1; + } + qeth_l2_fill_header(hdr, skb, cast_type, skb->len - push_len); + if (skb->ip_summed == CHECKSUM_PARTIAL) + qeth_l2_hdr_csum(card, hdr, skb); - elements = qeth_get_elements_no(card, skb_copy, 0, 0); + elements = qeth_get_elements_no(card, skb, hdr_elements, 0); if (!elements) { rc = -E2BIG; goto out; } - if (qeth_hdr_chk_and_bounce(skb_copy, &hdr, sizeof(*hdr))) { - rc = -EINVAL; - goto out; - } - rc = qeth_do_send_packet(card, queue, skb_copy, hdr, 0, 0, elements); + elements += hdr_elements; + + /* TODO: remove the skb_orphan() once TX completion is fast enough */ + skb_orphan(skb); + rc = qeth_do_send_packet(card, queue, skb, hdr, 0, hd_len, elements); out: if (!rc) { - /* tx success, free dangling original */ - dev_kfree_skb_any(skb); if (card->options.performance_stats && nr_frags) { card->perf_stats.sg_skbs_sent++; /* nr_frags + skb->data */ card->perf_stats.sg_frags_sent += nr_frags + 1; } } else { - /* tx fail, free copy */ - dev_kfree_skb_any(skb_copy); + if (hd_len) + kmem_cache_free(qeth_core_header_cache, hdr); + if (rc == -EBUSY) + /* roll back to ETH header */ + skb_pull(skb, push_len); } return rc; } @@ -1011,6 +1019,12 @@ static int qeth_l2_setup_netdev(struct qeth_card *card) card->dev->vlan_features |= NETIF_F_RXCSUM; } } + if (card->info.type != QETH_CARD_TYPE_OSN && + card->info.type != QETH_CARD_TYPE_IQD) { + card->dev->priv_flags &= ~IFF_TX_SKB_SHARING; + card->dev->needed_headroom = sizeof(struct qeth_hdr); + } + card->info.broadcast_capable = 1; qeth_l2_request_initial_mac(card); card->dev->gso_max_size = (QETH_MAX_BUFFER_ELEMENTS(card) - 1) *