diff options
author | Julian Wiedmann | 2018-07-19 12:43:56 +0200 |
---|---|---|
committer | David S. Miller | 2018-07-21 19:12:30 +0200 |
commit | ba86ceee9d1b5aa71fe3db75b2ec5452c9a48307 (patch) | |
tree | d4af4b1cab727029b75e594f83c5cf99ecefed75 /drivers/s390/net/qeth_core_main.c | |
parent | s390/qeth: add statistics for consumed buffer elements (diff) | |
download | kernel-qcow2-linux-ba86ceee9d1b5aa71fe3db75b2ec5452c9a48307.tar.gz kernel-qcow2-linux-ba86ceee9d1b5aa71fe3db75b2ec5452c9a48307.tar.xz kernel-qcow2-linux-ba86ceee9d1b5aa71fe3db75b2ec5452c9a48307.zip |
s390/qeth: merge linearize-check into HW header construction
When checking whether an skb needs to be linearized to fit into an IO
buffer, it's desirable to consider the skb's final size and layout
(ie. after the HW header was added). But a subsequent linearization can
then cause the re-positioned HW header to violate its alignment
restrictions.
Dealing with this situation in two different code paths is quite tricky.
This patch integrates a) linearize-check and b) HW header construction
into one 3 step-sequence:
1. evaluate how the HW header needs to be added (to identify if it takes
up an additional buffer element), then
2. check if the required buffer elements exceed the device's limit.
Linearize when necessary and re-evaluate the HW header placement.
3. Add the HW header in the best-possible way:
a) push, without taking up an additional buffer element
b) push, but consume another buffer element
c) allocate a header object from the cache.
Signed-off-by: Julian Wiedmann <jwi@linux.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/s390/net/qeth_core_main.c')
-rw-r--r-- | drivers/s390/net/qeth_core_main.c | 86 |
1 files changed, 71 insertions, 15 deletions
diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index 84f1e1e33f3f..e7b34624df1e 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -3831,6 +3831,17 @@ int qeth_get_elements_for_frags(struct sk_buff *skb) } EXPORT_SYMBOL_GPL(qeth_get_elements_for_frags); +static unsigned int qeth_count_elements(struct sk_buff *skb, int data_offset) +{ + unsigned int elements = qeth_get_elements_for_frags(skb); + addr_t end = (addr_t)skb->data + skb_headlen(skb); + addr_t start = (addr_t)skb->data + data_offset; + + if (start != end) + elements += qeth_get_elements_for_range(start, end); + return elements; +} + /** * qeth_get_elements_no() - find number of SBALEs for skb data, inc. frags. * @card: qeth card structure, to check max. elems. @@ -3846,12 +3857,7 @@ EXPORT_SYMBOL_GPL(qeth_get_elements_for_frags); int qeth_get_elements_no(struct qeth_card *card, struct sk_buff *skb, int extra_elems, int data_offset) { - addr_t end = (addr_t)skb->data + skb_headlen(skb); - int elements = qeth_get_elements_for_frags(skb); - addr_t start = (addr_t)skb->data + data_offset; - - if (start != end) - elements += qeth_get_elements_for_range(start, end); + int elements = qeth_count_elements(skb, data_offset); if ((elements + extra_elems) > QETH_MAX_BUFFER_ELEMENTS(card)) { QETH_DBF_MESSAGE(2, "Invalid size of IP packet " @@ -3885,22 +3891,72 @@ 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. + * qeth_add_hw_header() - add a HW header to an skb. + * @skb: skb that the HW header should be added to. * @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. + * @len: length of the HW header. * * 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. + * The number of needed buffer elements is returned in @elements. * 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) { +int qeth_add_hw_header(struct qeth_card *card, struct sk_buff *skb, + struct qeth_hdr **hdr, unsigned int len, + unsigned int *elements) +{ + const unsigned int max_elements = QETH_MAX_BUFFER_ELEMENTS(card); + unsigned int __elements; + addr_t start, end; + bool push_ok; + int rc; + +check_layout: + start = (addr_t)skb->data - len; + end = (addr_t)skb->data; + + if (qeth_get_elements_for_range(start, end + 1) == 1) { + /* Push HW header into same page as first protocol header. */ + push_ok = true; + __elements = qeth_count_elements(skb, 0); + } else { + __elements = 1 + qeth_count_elements(skb, 0); + if (qeth_get_elements_for_range(start, end) == 1) + /* Push HW header into a new page. */ + push_ok = true; + else + /* Use header cache. */ + push_ok = false; + } + + /* Compress skb to fit into one IO buffer: */ + if (__elements > max_elements) { + if (!skb_is_nonlinear(skb)) { + /* Drop it, no easy way of shrinking it further. */ + QETH_DBF_MESSAGE(2, "Dropped an oversized skb (Max Elements=%u / Actual=%u / Length=%u).\n", + max_elements, __elements, skb->len); + return -E2BIG; + } + + 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; + + /* Linearization changed the layout, re-evaluate: */ + goto check_layout; + } + + *elements = __elements; + /* Add the header: */ + if (push_ok) { *hdr = skb_push(skb, len); return len; } @@ -3910,7 +3966,7 @@ int qeth_push_hdr(struct sk_buff *skb, struct qeth_hdr **hdr, unsigned int len) return -ENOMEM; return 0; } -EXPORT_SYMBOL_GPL(qeth_push_hdr); +EXPORT_SYMBOL_GPL(qeth_add_hw_header); static void __qeth_fill_buffer(struct sk_buff *skb, struct qeth_qdio_out_buffer *buf, |