mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	virtio-net: build xdp_buff with multi buffers
Support xdp for multi buffer packets in mergeable mode. Putting the first buffer as the linear part for xdp_buff, and the rest of the buffers as non-linear fragments to struct skb_shared_info in the tailroom belonging to xdp_buff. Let 'truesize' return to its literal meaning, that is, when xdp is set, it includes the length of headroom and tailroom. Signed-off-by: Heng Qi <hengqi@linux.alibaba.com> Reviewed-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com> Acked-by: Jason Wang <jasowang@redhat.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									50bd14bc98
								
							
						
					
					
						commit
						ef75cb51f1
					
				
					 1 changed files with 100 additions and 8 deletions
				
			
		| 
						 | 
					@ -938,6 +938,91 @@ static struct sk_buff *receive_big(struct net_device *dev,
 | 
				
			||||||
	return NULL;
 | 
						return NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* TODO: build xdp in big mode */
 | 
				
			||||||
 | 
					static int virtnet_build_xdp_buff_mrg(struct net_device *dev,
 | 
				
			||||||
 | 
									      struct virtnet_info *vi,
 | 
				
			||||||
 | 
									      struct receive_queue *rq,
 | 
				
			||||||
 | 
									      struct xdp_buff *xdp,
 | 
				
			||||||
 | 
									      void *buf,
 | 
				
			||||||
 | 
									      unsigned int len,
 | 
				
			||||||
 | 
									      unsigned int frame_sz,
 | 
				
			||||||
 | 
									      u16 *num_buf,
 | 
				
			||||||
 | 
									      unsigned int *xdp_frags_truesize,
 | 
				
			||||||
 | 
									      struct virtnet_rq_stats *stats)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct virtio_net_hdr_mrg_rxbuf *hdr = buf;
 | 
				
			||||||
 | 
						unsigned int headroom, tailroom, room;
 | 
				
			||||||
 | 
						unsigned int truesize, cur_frag_size;
 | 
				
			||||||
 | 
						struct skb_shared_info *shinfo;
 | 
				
			||||||
 | 
						unsigned int xdp_frags_truesz = 0;
 | 
				
			||||||
 | 
						struct page *page;
 | 
				
			||||||
 | 
						skb_frag_t *frag;
 | 
				
			||||||
 | 
						int offset;
 | 
				
			||||||
 | 
						void *ctx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						xdp_init_buff(xdp, frame_sz, &rq->xdp_rxq);
 | 
				
			||||||
 | 
						xdp_prepare_buff(xdp, buf - VIRTIO_XDP_HEADROOM,
 | 
				
			||||||
 | 
								 VIRTIO_XDP_HEADROOM + vi->hdr_len, len - vi->hdr_len, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (*num_buf > 1) {
 | 
				
			||||||
 | 
							/* If we want to build multi-buffer xdp, we need
 | 
				
			||||||
 | 
							 * to specify that the flags of xdp_buff have the
 | 
				
			||||||
 | 
							 * XDP_FLAGS_HAS_FRAG bit.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							if (!xdp_buff_has_frags(xdp))
 | 
				
			||||||
 | 
								xdp_buff_set_frags_flag(xdp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							shinfo = xdp_get_shared_info_from_buff(xdp);
 | 
				
			||||||
 | 
							shinfo->nr_frags = 0;
 | 
				
			||||||
 | 
							shinfo->xdp_frags_size = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ((*num_buf - 1) > MAX_SKB_FRAGS)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while ((--*num_buf) >= 1) {
 | 
				
			||||||
 | 
							buf = virtqueue_get_buf_ctx(rq->vq, &len, &ctx);
 | 
				
			||||||
 | 
							if (unlikely(!buf)) {
 | 
				
			||||||
 | 
								pr_debug("%s: rx error: %d buffers out of %d missing\n",
 | 
				
			||||||
 | 
									 dev->name, *num_buf,
 | 
				
			||||||
 | 
									 virtio16_to_cpu(vi->vdev, hdr->num_buffers));
 | 
				
			||||||
 | 
								dev->stats.rx_length_errors++;
 | 
				
			||||||
 | 
								return -EINVAL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							stats->bytes += len;
 | 
				
			||||||
 | 
							page = virt_to_head_page(buf);
 | 
				
			||||||
 | 
							offset = buf - page_address(page);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							truesize = mergeable_ctx_to_truesize(ctx);
 | 
				
			||||||
 | 
							headroom = mergeable_ctx_to_headroom(ctx);
 | 
				
			||||||
 | 
							tailroom = headroom ? sizeof(struct skb_shared_info) : 0;
 | 
				
			||||||
 | 
							room = SKB_DATA_ALIGN(headroom + tailroom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							cur_frag_size = truesize;
 | 
				
			||||||
 | 
							xdp_frags_truesz += cur_frag_size;
 | 
				
			||||||
 | 
							if (unlikely(len > truesize - room || cur_frag_size > PAGE_SIZE)) {
 | 
				
			||||||
 | 
								put_page(page);
 | 
				
			||||||
 | 
								pr_debug("%s: rx error: len %u exceeds truesize %lu\n",
 | 
				
			||||||
 | 
									 dev->name, len, (unsigned long)(truesize - room));
 | 
				
			||||||
 | 
								dev->stats.rx_length_errors++;
 | 
				
			||||||
 | 
								return -EINVAL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							frag = &shinfo->frags[shinfo->nr_frags++];
 | 
				
			||||||
 | 
							__skb_frag_set_page(frag, page);
 | 
				
			||||||
 | 
							skb_frag_off_set(frag, offset);
 | 
				
			||||||
 | 
							skb_frag_size_set(frag, len);
 | 
				
			||||||
 | 
							if (page_is_pfmemalloc(page))
 | 
				
			||||||
 | 
								xdp_buff_set_frag_pfmemalloc(xdp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							shinfo->xdp_frags_size += len;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						*xdp_frags_truesize = xdp_frags_truesz;
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct sk_buff *receive_mergeable(struct net_device *dev,
 | 
					static struct sk_buff *receive_mergeable(struct net_device *dev,
 | 
				
			||||||
					 struct virtnet_info *vi,
 | 
										 struct virtnet_info *vi,
 | 
				
			||||||
					 struct receive_queue *rq,
 | 
										 struct receive_queue *rq,
 | 
				
			||||||
| 
						 | 
					@ -956,15 +1041,17 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
 | 
				
			||||||
	unsigned int truesize = mergeable_ctx_to_truesize(ctx);
 | 
						unsigned int truesize = mergeable_ctx_to_truesize(ctx);
 | 
				
			||||||
	unsigned int headroom = mergeable_ctx_to_headroom(ctx);
 | 
						unsigned int headroom = mergeable_ctx_to_headroom(ctx);
 | 
				
			||||||
	unsigned int metasize = 0;
 | 
						unsigned int metasize = 0;
 | 
				
			||||||
 | 
						unsigned int tailroom = headroom ? sizeof(struct skb_shared_info) : 0;
 | 
				
			||||||
 | 
						unsigned int room = SKB_DATA_ALIGN(headroom + tailroom);
 | 
				
			||||||
	unsigned int frame_sz;
 | 
						unsigned int frame_sz;
 | 
				
			||||||
	int err;
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	head_skb = NULL;
 | 
						head_skb = NULL;
 | 
				
			||||||
	stats->bytes += len - vi->hdr_len;
 | 
						stats->bytes += len - vi->hdr_len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (unlikely(len > truesize)) {
 | 
						if (unlikely(len > truesize - room)) {
 | 
				
			||||||
		pr_debug("%s: rx error: len %u exceeds truesize %lu\n",
 | 
							pr_debug("%s: rx error: len %u exceeds truesize %lu\n",
 | 
				
			||||||
			 dev->name, len, (unsigned long)ctx);
 | 
								 dev->name, len, (unsigned long)(truesize - room));
 | 
				
			||||||
		dev->stats.rx_length_errors++;
 | 
							dev->stats.rx_length_errors++;
 | 
				
			||||||
		goto err_skb;
 | 
							goto err_skb;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -990,10 +1077,12 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
 | 
				
			||||||
		if (unlikely(hdr->hdr.gso_type))
 | 
							if (unlikely(hdr->hdr.gso_type))
 | 
				
			||||||
			goto err_xdp;
 | 
								goto err_xdp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* Buffers with headroom use PAGE_SIZE as alloc size,
 | 
							/* Now XDP core assumes frag size is PAGE_SIZE, but buffers
 | 
				
			||||||
		 * see add_recvbuf_mergeable() + get_mergeable_buf_len()
 | 
							 * with headroom may add hole in truesize, which
 | 
				
			||||||
 | 
							 * make their length exceed PAGE_SIZE. So we disabled the
 | 
				
			||||||
 | 
							 * hole mechanism for xdp. See add_recvbuf_mergeable().
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
		frame_sz = headroom ? PAGE_SIZE : truesize;
 | 
							frame_sz = truesize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* This happens when rx buffer size is underestimated
 | 
							/* This happens when rx buffer size is underestimated
 | 
				
			||||||
		 * or headroom is not enough because of the buffer
 | 
							 * or headroom is not enough because of the buffer
 | 
				
			||||||
| 
						 | 
					@ -1146,9 +1235,12 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
 | 
				
			||||||
		page = virt_to_head_page(buf);
 | 
							page = virt_to_head_page(buf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		truesize = mergeable_ctx_to_truesize(ctx);
 | 
							truesize = mergeable_ctx_to_truesize(ctx);
 | 
				
			||||||
		if (unlikely(len > truesize)) {
 | 
							headroom = mergeable_ctx_to_headroom(ctx);
 | 
				
			||||||
 | 
							tailroom = headroom ? sizeof(struct skb_shared_info) : 0;
 | 
				
			||||||
 | 
							room = SKB_DATA_ALIGN(headroom + tailroom);
 | 
				
			||||||
 | 
							if (unlikely(len > truesize - room)) {
 | 
				
			||||||
			pr_debug("%s: rx error: len %u exceeds truesize %lu\n",
 | 
								pr_debug("%s: rx error: len %u exceeds truesize %lu\n",
 | 
				
			||||||
				 dev->name, len, (unsigned long)ctx);
 | 
									 dev->name, len, (unsigned long)(truesize - room));
 | 
				
			||||||
			dev->stats.rx_length_errors++;
 | 
								dev->stats.rx_length_errors++;
 | 
				
			||||||
			goto err_skb;
 | 
								goto err_skb;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -1435,7 +1527,7 @@ static int add_recvbuf_mergeable(struct virtnet_info *vi,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sg_init_one(rq->sg, buf, len);
 | 
						sg_init_one(rq->sg, buf, len);
 | 
				
			||||||
	ctx = mergeable_len_to_ctx(len, headroom);
 | 
						ctx = mergeable_len_to_ctx(len + room, headroom);
 | 
				
			||||||
	err = virtqueue_add_inbuf_ctx(rq->vq, rq->sg, 1, buf, ctx, gfp);
 | 
						err = virtqueue_add_inbuf_ctx(rq->vq, rq->sg, 1, buf, ctx, gfp);
 | 
				
			||||||
	if (err < 0)
 | 
						if (err < 0)
 | 
				
			||||||
		put_page(virt_to_head_page(buf));
 | 
							put_page(virt_to_head_page(buf));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue