Thinking about Sasha's patches, we can reduce ring usage for virtio net small packets dramatically if we put virtio net header inline with the data. This can be done for free in case guest net stack allocated extra head room for the packet, and I don't see why would this have any downsides. Even though with my recent patches qemu no longer requires header to be the first s/g element, we need a new feature bit to detect this. A trivial qemu patch will be sent separately. We could get rid of an extra s/g for big packets too, but since in practice everyone enables mergeable buffers, I don't see much of a point. Rusty, if you decide to pick this up I'll send a (rather trivial) spec patch shortly afterwards, but holidays are beginning here. Considering how simple the guest patch is, I hope it can make it in 3.7? Also note that patch 1 and 2 are IMO a good idea without patch 3. If you decide to defer patch 3 pls consider 1/2 separately. Before: [root at virtlab203 qemu]# ssh robin ./netperf/bin/netperf -t TCP_RR -H 11.0.0.4 TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 11.0.0.4 (11.0.0.4) port 0 AF_INET : demo Local /Remote Socket Size Request Resp. Elapsed Trans. Send Recv Size Size Time Rate bytes Bytes bytes bytes secs. per sec 16384 87380 1 1 10.00 2992.88 16384 87380 After: [root at virtlab203 qemu]# ssh robin ./netperf/bin/netperf -t TCP_RR -H 11.0.0.4 TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 11.0.0.4 (11.0.0.4) port 0 AF_INET : demo Local /Remote Socket Size Request Resp. Elapsed Trans. Send Recv Size Size Time Rate bytes Bytes bytes bytes secs. per sec 16384 87380 1 1 10.00 3195.57 16384 87380 Michael S. Tsirkin (3): virtio: add API to query ring capacity virtio-net: correct capacity math on ring full virtio-net: put virtio net header inline with data drivers/net/virtio_net.c | 57 +++++++++++++++++++++++++++++++------------- drivers/virtio/virtio_ring.c | 19 +++++++++++++++ include/linux/virtio.h | 2 ++ include/linux/virtio_net.h | 5 +++- 4 files changed, 66 insertions(+), 17 deletions(-) -- MST
Michael S. Tsirkin
2012-Sep-28 09:26 UTC
[PATCH 1/3] virtio: add API to query ring capacity
It's sometimes necessary to query ring capacity after dequeueing a buffer. Add an API for this. Signed-off-by: Michael S. Tsirkin <mst at redhat.com> --- drivers/virtio/virtio_ring.c | 19 +++++++++++++++++++ include/linux/virtio.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index 5aa43c3..ee3d80b 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -715,4 +715,23 @@ unsigned int virtqueue_get_vring_size(struct virtqueue *_vq) } EXPORT_SYMBOL_GPL(virtqueue_get_vring_size); +/** + * virtqueue_get_capacity - query available ring capacity + * @vq: the struct virtqueue we're talking about. + * + * Caller must ensure we don't call this with other virtqueue operations + * at the same time (except where noted), otherwise result is unreliable. + * + * Returns remaining capacity of queue. + * Note that it only really makes sense to treat all + * return values as "available": indirect buffers mean that + * we can put an entire sg[] array inside a single queue entry. + */ +unsigned int virtqueue_get_capacity(struct virtqueue *_vq) +{ + struct vring_virtqueue *vq = to_vvq(_vq); + return vq->num_free; +} +EXPORT_SYMBOL_GPL(virtqueue_get_capacity); + MODULE_LICENSE("GPL"); diff --git a/include/linux/virtio.h b/include/linux/virtio.h index a1ba8bb..fab61e8 100644 --- a/include/linux/virtio.h +++ b/include/linux/virtio.h @@ -50,6 +50,8 @@ void *virtqueue_detach_unused_buf(struct virtqueue *vq); unsigned int virtqueue_get_vring_size(struct virtqueue *vq); +unsigned int virtqueue_get_capacity(struct virtqueue *vq); + /** * virtio_device - representation of a device using virtio * @index: unique position on the virtio bus -- MST
Michael S. Tsirkin
2012-Sep-28 09:26 UTC
[PATCH 2/3] virtio-net: correct capacity math on ring full
Capacity math on ring full is wrong: we are looking at num_sg but that might be optimistic because of indirect buffer use. The implementation also penalizes fast path with extra memory accesses for the benefit of ring full condition handling which is slow path. It's easy to query ring capacity so let's do just that. This change also makes it easier to move vnet header for tx around as follow-up patch does. Signed-off-by: Michael S. Tsirkin <mst at redhat.com> --- drivers/net/virtio_net.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 83d2b0c..316f1be 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -95,7 +95,6 @@ struct skb_vnet_hdr { struct virtio_net_hdr hdr; struct virtio_net_hdr_mrg_rxbuf mhdr; }; - unsigned int num_sg; }; struct padded_vnet_hdr { @@ -557,10 +556,10 @@ again: return received; } -static unsigned int free_old_xmit_skbs(struct virtnet_info *vi) +static void free_old_xmit_skbs(struct virtnet_info *vi) { struct sk_buff *skb; - unsigned int len, tot_sgs = 0; + unsigned int len; struct virtnet_stats *stats = this_cpu_ptr(vi->stats); while ((skb = virtqueue_get_buf(vi->svq, &len)) != NULL) { @@ -571,16 +570,15 @@ static unsigned int free_old_xmit_skbs(struct virtnet_info *vi) stats->tx_packets++; u64_stats_update_end(&stats->tx_syncp); - tot_sgs += skb_vnet_hdr(skb)->num_sg; dev_kfree_skb_any(skb); } - return tot_sgs; } static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb) { struct skb_vnet_hdr *hdr = skb_vnet_hdr(skb); const unsigned char *dest = ((struct ethhdr *)skb->data)->h_dest; + unsigned num_sg; pr_debug("%s: xmit %p %pM\n", vi->dev->name, skb, dest); @@ -619,8 +617,8 @@ static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb) else sg_set_buf(vi->tx_sg, &hdr->hdr, sizeof hdr->hdr); - hdr->num_sg = skb_to_sgvec(skb, vi->tx_sg + 1, 0, skb->len) + 1; - return virtqueue_add_buf(vi->svq, vi->tx_sg, hdr->num_sg, + num_sg = skb_to_sgvec(skb, vi->tx_sg + 1, 0, skb->len) + 1; + return virtqueue_add_buf(vi->svq, vi->tx_sg, num_sg, 0, skb, GFP_ATOMIC); } @@ -664,7 +662,8 @@ static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) netif_stop_queue(dev); if (unlikely(!virtqueue_enable_cb_delayed(vi->svq))) { /* More just got used, free them then recheck. */ - capacity += free_old_xmit_skbs(vi); + free_old_xmit_skbs(vi); + capacity = virtqueue_get_capacity(vi->svq); if (capacity >= 2+MAX_SKB_FRAGS) { netif_start_queue(dev); virtqueue_disable_cb(vi->svq); -- MST
Michael S. Tsirkin
2012-Sep-28 09:26 UTC
[PATCH 3/3] virtio-net: put virtio net header inline with data
For small packets we can simplify xmit processing by linearizing buffers with the header: most packets seem to have enough head room we can use for this purpose. Since existing hypervisors require that header is the first s/g element, we need a feature bit for this. Signed-off-by: Michael S. Tsirkin <mst at redhat.com> --- drivers/net/virtio_net.c | 44 +++++++++++++++++++++++++++++++++++--------- include/linux/virtio_net.h | 5 ++++- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 316f1be..6e6e53e 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -67,6 +67,9 @@ struct virtnet_info { /* Host will merge rx buffers for big packets (shake it! shake it!) */ bool mergeable_rx_bufs; + /* Host can handle any s/g split between our header and packet data */ + bool any_header_sg; + /* enable config space updates */ bool config_enable; @@ -576,11 +579,28 @@ static void free_old_xmit_skbs(struct virtnet_info *vi) static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb) { - struct skb_vnet_hdr *hdr = skb_vnet_hdr(skb); + struct skb_vnet_hdr *hdr; const unsigned char *dest = ((struct ethhdr *)skb->data)->h_dest; unsigned num_sg; + unsigned hdr_len; + bool can_push; + pr_debug("%s: xmit %p %pM\n", vi->dev->name, skb, dest); + if (vi->mergeable_rx_bufs) + hdr_len = sizeof hdr->mhdr; + else + hdr_len = sizeof hdr->hdr; + + can_push = vi->any_header_sg && + !((unsigned long)skb->data & (__alignof__(*hdr) - 1)) && + !skb_header_cloned(skb) && skb_headroom(skb) >= hdr_len; + /* Even if we can, don't push here yet as this would skew + * csum_start offset below. */ + if (can_push) + hdr = (struct skb_vnet_hdr *)(skb->data - hdr_len); + else + hdr = skb_vnet_hdr(skb); if (skb->ip_summed == CHECKSUM_PARTIAL) { hdr->hdr.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; @@ -609,15 +629,18 @@ static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb) hdr->hdr.gso_size = hdr->hdr.hdr_len = 0; } - hdr->mhdr.num_buffers = 0; - - /* Encode metadata header at front. */ if (vi->mergeable_rx_bufs) - sg_set_buf(vi->tx_sg, &hdr->mhdr, sizeof hdr->mhdr); - else - sg_set_buf(vi->tx_sg, &hdr->hdr, sizeof hdr->hdr); + hdr->mhdr.num_buffers = 0; - num_sg = skb_to_sgvec(skb, vi->tx_sg + 1, 0, skb->len) + 1; + if (can_push) { + __skb_push(skb, hdr_len); + num_sg = skb_to_sgvec(skb, vi->tx_sg, 0, skb->len); + /* Pull header back to avoid skew in tx bytes calculations. */ + __skb_pull(skb, hdr_len); + } else { + sg_set_buf(vi->tx_sg, hdr, hdr_len); + num_sg = skb_to_sgvec(skb, vi->tx_sg + 1, 0, skb->len) + 1; + } return virtqueue_add_buf(vi->svq, vi->tx_sg, num_sg, 0, skb, GFP_ATOMIC); } @@ -1128,6 +1151,9 @@ static int virtnet_probe(struct virtio_device *vdev) if (virtio_has_feature(vdev, VIRTIO_NET_F_MRG_RXBUF)) vi->mergeable_rx_bufs = true; + if (virtio_has_feature(vdev, VIRTIO_NET_F_ANY_HEADER_SG)) + vi->any_header_sg = true; + err = init_vqs(vi); if (err) goto free_stats; @@ -1286,7 +1312,7 @@ static unsigned int features[] = { VIRTIO_NET_F_GUEST_ECN, VIRTIO_NET_F_GUEST_UFO, VIRTIO_NET_F_MRG_RXBUF, VIRTIO_NET_F_STATUS, VIRTIO_NET_F_CTRL_VQ, VIRTIO_NET_F_CTRL_RX, VIRTIO_NET_F_CTRL_VLAN, - VIRTIO_NET_F_GUEST_ANNOUNCE, + VIRTIO_NET_F_GUEST_ANNOUNCE, VIRTIO_NET_F_ANY_HEADER_SG }; static struct virtio_driver virtio_net_driver = { diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index 2470f54..16a577b 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -51,6 +51,7 @@ #define VIRTIO_NET_F_CTRL_RX_EXTRA 20 /* Extra RX mode control support */ #define VIRTIO_NET_F_GUEST_ANNOUNCE 21 /* Guest can announce device on the * network */ +#define VIRTIO_NET_F_ANY_HEADER_SG 22 /* Host can handle any header s/g */ #define VIRTIO_NET_S_LINK_UP 1 /* Link is up */ #define VIRTIO_NET_S_ANNOUNCE 2 /* Announcement is needed */ @@ -62,7 +63,9 @@ struct virtio_net_config { __u16 status; } __attribute__((packed)); -/* This is the first element of the scatter-gather list. If you don't +/* This header comes first in the scatter-gather list. + * If VIRTIO_NET_F_ANY_HEADER_SG is not negotiated, it must + * be the first element of the scatter-gather list. If you don't * specify GSO or CSUM features, you can simply ignore the header. */ struct virtio_net_hdr { #define VIRTIO_NET_HDR_F_NEEDS_CSUM 1 // Use csum_start, csum_offset -- MST
"Michael S. Tsirkin" <mst at redhat.com> writes:> Thinking about Sasha's patches, we can reduce ring usage > for virtio net small packets dramatically if we put > virtio net header inline with the data. > This can be done for free in case guest net stack allocated > extra head room for the packet, and I don't see > why would this have any downsides.I've been wanting to do this for the longest time... but...> Even though with my recent patches qemu > no longer requires header to be the first s/g element, > we need a new feature bit to detect this. > A trivial qemu patch will be sent separately.There's a reason I haven't done this. I really, really dislike "my implemention isn't broken" feature bits. We could have an infinite number of them, for each bug in each device. So my plan was to tie this assumption to the new PCI layout. And have a stress-testing patch like the one below in the kernel (see my virtio-wip branch for stuff like this). Turn it on at boot with "virtio_ring.torture" on the kernel commandline. BTW, I've fixed lguest, but my kvm here (Ubuntu precise, kvm-qemu 1.0) is too old. Building the latest git now... Cheers, Rusty. Subject: virtio: CONFIG_VIRTIO_DEVICE_TORTURE Virtio devices are not supposed to depend on the framing of the scatter-gather lists, but various implementations did. Safeguard this in future by adding an option to deliberately create perverse descriptors. Signed-off-by: Rusty Russell <rusty at rustcorp.com.au> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index 8d5bddb..930a4ea 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -5,6 +5,15 @@ config VIRTIO bus, such as CONFIG_VIRTIO_PCI, CONFIG_VIRTIO_MMIO, CONFIG_LGUEST, CONFIG_RPMSG or CONFIG_S390_GUEST. +config VIRTIO_DEVICE_TORTURE + bool "Virtio device torture tests" + depends on VIRTIO && DEBUG_KERNEL + help + This makes the virtio_ring implementation creatively change + the format of requests to make sure that devices are + properly implemented. This will make your virtual machine + slow *and* unreliable! Say N. + menu "Virtio drivers" config VIRTIO_PCI diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index e639584..8893753 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -124,6 +124,149 @@ struct vring_virtqueue #define to_vvq(_vq) container_of(_vq, struct vring_virtqueue, vq) +#ifdef CONFIG_VIRTIO_DEVICE_TORTURE +static bool torture; +module_param(torture, bool, 0644); + +struct torture { + unsigned int orig_out, orig_in; + void *orig_data; + struct scatterlist sg[4]; + struct scatterlist orig_sg[]; +}; + +static size_t tot_len(struct scatterlist sg[], unsigned num) +{ + size_t len, i; + + for (len = 0, i = 0; i < num; i++) + len += sg[i].length; + + return len; +} + +static void copy_sg_data(const struct scatterlist *dst, unsigned dnum, + const struct scatterlist *src, unsigned snum) +{ + unsigned len; + struct scatterlist s, d; + + s = *src; + d = *dst; + + while (snum && dnum) { + len = min(s.length, d.length); + memcpy(sg_virt(&d), sg_virt(&s), len); + d.offset += len; + d.length -= len; + s.offset += len; + s.length -= len; + if (!s.length) { + BUG_ON(snum == 0); + src++; + snum--; + s = *src; + } + if (!d.length) { + BUG_ON(dnum == 0); + dst++; + dnum--; + d = *dst; + } + } +} + +static bool torture_replace(struct scatterlist **sg, + unsigned int *out, + unsigned int *in, + void **data, + gfp_t gfp) +{ + static size_t seed; + struct torture *t; + size_t outlen, inlen, ourseed, len1; + void *buf; + + if (!torture) + return true; + + outlen = tot_len(*sg, *out); + inlen = tot_len(*sg + *out, *in); + + /* This will break horribly on large block requests. */ + t = kmalloc(sizeof(*t) + (*out + *in) * sizeof(t->orig_sg[1]) + + outlen + 1 + inlen + 1, gfp); + if (!t) + return false; + + sg_init_table(t->sg, 4); + buf = &t->orig_sg[*out + *in]; + + memcpy(t->orig_sg, *sg, sizeof(**sg) * (*out + *in)); + t->orig_out = *out; + t->orig_in = *in; + t->orig_data = *data; + *data = t; + + ourseed = ACCESS_ONCE(seed); + seed++; + + *sg = t->sg; + if (outlen) { + /* Split outbuf into two parts, one byte apart. */ + *out = 2; + len1 = ourseed % (outlen + 1); + sg_set_buf(&t->sg[0], buf, len1); + buf += len1 + 1; + sg_set_buf(&t->sg[1], buf, outlen - len1); + buf += outlen - len1; + copy_sg_data(t->sg, *out, t->orig_sg, t->orig_out); + } + + if (inlen) { + /* Split inbuf into two parts, one byte apart. */ + *in = 2; + len1 = ourseed % (inlen + 1); + sg_set_buf(&t->sg[*out], buf, len1); + buf += len1 + 1; + sg_set_buf(&t->sg[*out + 1], buf, inlen - len1); + buf += inlen - len1; + } + return true; +} + +static void *torture_done(struct torture *t) +{ + void *data; + + if (!torture) + return t; + + if (t->orig_in) + copy_sg_data(t->orig_sg + t->orig_out, t->orig_in, + t->sg + (t->orig_out ? 2 : 0), 2); + + data = t->orig_data; + kfree(t); + return data; +} + +#else +static bool torture_replace(struct scatterlist **sg, + unsigned int *out, + unsigned int *in, + void **data, + gfp_t gfp) +{ + return true; +} + +static void *torture_done(void *data) +{ + return data; +} +#endif /* CONFIG_VIRTIO_DEVICE_TORTURE */ + /* Set up an indirect table of descriptors and add it to the queue. */ static int vring_add_indirect(struct vring_virtqueue *vq, struct scatterlist sg[], @@ -213,6 +356,9 @@ int virtqueue_add_buf(struct virtqueue *_vq, BUG_ON(data == NULL); + if (!torture_replace(&sg, &out, &in, &data, gfp)) + return -ENOMEM; + #ifdef DEBUG { ktime_t now = ktime_get(); @@ -246,6 +392,7 @@ int virtqueue_add_buf(struct virtqueue *_vq, if (out) vq->notify(&vq->vq); END_USE(vq); + torture_done(data); return -ENOSPC; } @@ -476,7 +623,7 @@ void *virtqueue_get_buf(struct virtqueue *_vq, unsigned int *len) #endif END_USE(vq); - return ret; + return torture_done(ret); } EXPORT_SYMBOL_GPL(virtqueue_get_buf);
Il 03/10/2012 08:44, Rusty Russell ha scritto:> There's a reason I haven't done this. I really, really dislike "my > implemention isn't broken" feature bits. We could have an infinite > number of them, for each bug in each device.However, this bug affects (almost) all implementations and (almost) all devices. It even makes sense to reserve a transport feature bit for it instead of a device feature bit. Paolo
Il 05/10/2012 07:43, Rusty Russell ha scritto:>> > struct virtio_scsi_req_cmd { >> > // Read-only >> > u8 lun[8]; >> > u64 id; >> > u8 task_attr; >> > u8 prio; >> > u8 crn; >> > char cdb[cdb_size]; >> > char dataout[]; >> > // Write-only part >> > u32 sense_len; >> > u32 residual; >> > u16 status_qualifier; >> > u8 status; >> > u8 response; >> > u8 sense[sense_size]; >> > char datain[]; >> > }; >> > >> > where cdb_size and sense_size come from configuration space. The device >> > right now expects everything before dataout/datain to be in a single >> > descriptor, but that's in no way part of the spec. Am I missing >> > something egregious? > Since you wrote it, I hope not :)Yeah, I guess the confusion came from cdb_size and sense_size being in configuration space.> That's good. But virtio_blk's scsi command is insoluble AFAICT. As I > said to Anthony, the best rules are "always" and "never", so I'd really > rather not have to grandfather that in.It is, but we can add a rule that if the (transport) flag VIRTIO_RING_F_ANY_HEADER_SG is set, the cdb field is always 32 bytes in virtio-blk. Paolo
Reasonably Related Threads
- [PATCH 0/3] virtio-net: inline header support
- [PATCH] virtio-spec: add field for scsi command size
- [PATCH] virtio-spec: add field for scsi command size
- [PATCH] virtio-spec: add field for scsi command size
- [PATCH] virtio-spec: add field for scsi command size