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

cfg80211: reuse existing page fragments in A-MSDU rx

This massively reduces data copying and thus improves rx performance

Signed-off-by: Felix Fietkau <nbd@openwrt.org>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

authored by

Felix Fietkau and committed by
Johannes Berg
2b67f944 2bf0ccc7

+77 -5
+77 -5
net/wireless/util.c
··· 644 644 } 645 645 EXPORT_SYMBOL(ieee80211_data_from_8023); 646 646 647 + static void 648 + __frame_add_frag(struct sk_buff *skb, struct page *page, 649 + void *ptr, int len, int size) 650 + { 651 + struct skb_shared_info *sh = skb_shinfo(skb); 652 + int page_offset; 653 + 654 + atomic_inc(&page->_count); 655 + page_offset = ptr - page_address(page); 656 + skb_add_rx_frag(skb, sh->nr_frags, page, page_offset, len, size); 657 + } 658 + 659 + static void 660 + __ieee80211_amsdu_copy_frag(struct sk_buff *skb, struct sk_buff *frame, 661 + int offset, int len) 662 + { 663 + struct skb_shared_info *sh = skb_shinfo(skb); 664 + const skb_frag_t *frag = &sh->frags[-1]; 665 + struct page *frag_page; 666 + void *frag_ptr; 667 + int frag_len, frag_size; 668 + int head_size = skb->len - skb->data_len; 669 + int cur_len; 670 + 671 + frag_page = virt_to_head_page(skb->head); 672 + frag_ptr = skb->data; 673 + frag_size = head_size; 674 + 675 + while (offset >= frag_size) { 676 + offset -= frag_size; 677 + frag++; 678 + frag_page = skb_frag_page(frag); 679 + frag_ptr = skb_frag_address(frag); 680 + frag_size = skb_frag_size(frag); 681 + } 682 + 683 + frag_ptr += offset; 684 + frag_len = frag_size - offset; 685 + 686 + cur_len = min(len, frag_len); 687 + 688 + __frame_add_frag(frame, frag_page, frag_ptr, cur_len, frag_size); 689 + len -= cur_len; 690 + 691 + while (len > 0) { 692 + frag++; 693 + frag_len = skb_frag_size(frag); 694 + cur_len = min(len, frag_len); 695 + __frame_add_frag(frame, skb_frag_page(frag), 696 + skb_frag_address(frag), cur_len, frag_len); 697 + len -= cur_len; 698 + } 699 + } 700 + 647 701 static struct sk_buff * 648 702 __ieee80211_amsdu_copy(struct sk_buff *skb, unsigned int hlen, 649 - int offset, int len) 703 + int offset, int len, bool reuse_frag) 650 704 { 651 705 struct sk_buff *frame; 706 + int cur_len = len; 652 707 653 708 if (skb->len - offset < len) 654 709 return NULL; 655 710 656 711 /* 712 + * When reusing framents, copy some data to the head to simplify 713 + * ethernet header handling and speed up protocol header processing 714 + * in the stack later. 715 + */ 716 + if (reuse_frag) 717 + cur_len = min_t(int, len, 32); 718 + 719 + /* 657 720 * Allocate and reserve two bytes more for payload 658 721 * alignment since sizeof(struct ethhdr) is 14. 659 722 */ 660 - frame = dev_alloc_skb(hlen + sizeof(struct ethhdr) + 2 + len); 723 + frame = dev_alloc_skb(hlen + sizeof(struct ethhdr) + 2 + cur_len); 661 724 662 725 skb_reserve(frame, hlen + sizeof(struct ethhdr) + 2); 663 - skb_copy_bits(skb, offset, skb_put(frame, len), len); 726 + skb_copy_bits(skb, offset, skb_put(frame, cur_len), cur_len); 727 + 728 + len -= cur_len; 729 + if (!len) 730 + return frame; 731 + 732 + offset += cur_len; 733 + __ieee80211_amsdu_copy_frag(skb, frame, offset, len); 664 734 665 735 return frame; 666 736 } ··· 746 676 u8 *payload; 747 677 int offset = 0, remaining, err; 748 678 struct ethhdr eth; 679 + bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb); 749 680 bool reuse_skb = false; 750 681 bool last = false; 751 682 ··· 774 703 offset += sizeof(struct ethhdr); 775 704 /* reuse skb for the last subframe */ 776 705 last = remaining <= subframe_len + padding; 777 - if (!skb_is_nonlinear(skb) && last) { 706 + if (!skb_is_nonlinear(skb) && !reuse_frag && last) { 778 707 skb_pull(skb, offset); 779 708 frame = skb; 780 709 reuse_skb = true; 781 710 } else { 782 - frame = __ieee80211_amsdu_copy(skb, hlen, offset, len); 711 + frame = __ieee80211_amsdu_copy(skb, hlen, offset, len, 712 + reuse_frag); 783 713 if (!frame) 784 714 goto purge; 785 715