Il 20/06/2013 04:40, Rusty Russell ha scritto:
> Paolo Bonzini <pbonz...@redhat.com> writes:
>> Il 19/06/2013 10:24, Michael S. Tsirkin ha scritto:
>>>>> 2) We introduce VIRTIO_NET_F_ANY_LAYOUT and VIRTIO_BLK_F_ANY_LAYOUT
>>>>>    specifically for net and block (note the new names).
>>
>> So why not a transport feature?  Is it just because the SCSI commands
>> for virtio-blk also require a config space field?  Sorry if I missed
>> this upthread.
> 
> Mainly because I'm not sure that *all* devices are now safe.  Are they?

virtio-scsi's implementation in QEMU is not safe (been delaying that for
too long, sorry), but the spec is safe.

Paolo

> I had a stress test half-written for this, pasted below.
> 
> Otherwise I'd be happy to do both: use feature 25 for
> VIRTIO_F_ANY_LAYOUT and another feature bit for the virtio-blk layout
> change.
> 
> Cheers,
> Rusty.
> 
> 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 <ru...@rustcorp.com.au>
> 
> diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
> index 8d5bddb..99c0187 100644
> --- a/drivers/virtio/Kconfig
> +++ b/drivers/virtio/Kconfig
> @@ -5,6 +5,22 @@ config VIRTIO
>         bus, such as CONFIG_VIRTIO_PCI, CONFIG_VIRTIO_MMIO, CONFIG_LGUEST,
>         CONFIG_RPMSG or CONFIG_S390_GUEST.
>  
> +config VIRTIO_TORTURE
> +     bool "Virtio torture debugging"
> +     depends on VIRTIO && DEBUG_KERNEL
> +     help
> +
> +       This makes the virtio_ring implementation stress-test
> +       devices.  In particularly, creatively change the format of
> +       requests to make sure that devices are properly implemented,
> +       as well as implement various checks to ensure drivers are
> +       correct.  This will make your virtual machine slow *and*
> +       unreliable!  Say N.
> +
> +       Put virtio_ring.device_torture to your boot commandline to
> +       torture devices (otherwise only simply sanity checks are
> +       done).
> +
>  menu "Virtio drivers"
>  
>  config VIRTIO_PCI
> diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c
> index e82821a..6e5271c 100644
> --- a/drivers/virtio/virtio_ring.c
> +++ b/drivers/virtio/virtio_ring.c
> @@ -45,35 +45,6 @@
>  #define virtio_wmb(vq) wmb()
>  #endif
>  
> -#ifdef DEBUG
> -/* For development, we want to crash whenever the ring is screwed. */
> -#define BAD_RING(_vq, fmt, args...)                          \
> -     do {                                                    \
> -             dev_err(&(_vq)->vq.vdev->dev,                   \
> -                     "%s:"fmt, (_vq)->vq.name, ##args);      \
> -             BUG();                                          \
> -     } while (0)
> -/* Caller is supposed to guarantee no reentry. */
> -#define START_USE(_vq)                                               \
> -     do {                                                    \
> -             if ((_vq)->in_use)                              \
> -                     panic("%s:in_use = %i\n",               \
> -                           (_vq)->vq.name, (_vq)->in_use);   \
> -             (_vq)->in_use = __LINE__;                       \
> -     } while (0)
> -#define END_USE(_vq) \
> -     do { BUG_ON(!(_vq)->in_use); (_vq)->in_use = 0; } while(0)
> -#else
> -#define BAD_RING(_vq, fmt, args...)                          \
> -     do {                                                    \
> -             dev_err(&_vq->vq.vdev->dev,                     \
> -                     "%s:"fmt, (_vq)->vq.name, ##args);      \
> -             (_vq)->broken = true;                           \
> -     } while (0)
> -#define START_USE(vq)
> -#define END_USE(vq)
> -#endif
> -
>  struct indirect_cache {
>       unsigned int max;
>       struct vring_desc *cache;
> @@ -109,7 +80,7 @@ struct vring_virtqueue
>       /* How to notify other side. FIXME: commonalize hcalls! */
>       void (*notify)(struct virtqueue *vq);
>  
> -#ifdef DEBUG
> +#ifdef CONFIG_VIRTIO_TORTURE
>       /* They're supposed to lock for us. */
>       unsigned int in_use;
>  
> @@ -134,6 +105,200 @@ static inline struct indirect_cache 
> *indirect_cache(struct vring_virtqueue *vq)
>       return (struct indirect_cache *)&vq->data[vq->vring.num];
>  }
>  
> +#ifdef CONFIG_VIRTIO_TORTURE
> +/* For development, we want to crash whenever the ring is screwed. */
> +#define BAD_RING(_vq, fmt, args...)                          \
> +     do {                                                    \
> +             dev_err(&(_vq)->vq.vdev->dev,                   \
> +                     "%s:"fmt, (_vq)->vq.name, ##args);      \
> +             BUG();                                          \
> +     } while (0)
> +/* Caller is supposed to guarantee no reentry. */
> +#define START_USE(_vq)                                               \
> +     do {                                                    \
> +             if ((_vq)->in_use)                              \
> +                     panic("%s:in_use = %i\n",               \
> +                           (_vq)->vq.name, (_vq)->in_use);   \
> +             (_vq)->in_use = __LINE__;                       \
> +     } while (0)
> +#define END_USE(_vq) \
> +     do { BUG_ON(!(_vq)->in_use); (_vq)->in_use = 0; } while(0)
> +
> +static bool device_torture;
> +module_param(device_torture, bool, 0644);
> +
> +struct torture {
> +     unsigned int orig_out, orig_in;
> +     void *orig_data;
> +     struct scatterlist sg[4];
> +     struct scatterlist orig_sg[];
> +};
> +
> +static unsigned tot_len(struct scatterlist sg[], unsigned num)
> +{
> +     unsigned 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;
> +     unsigned long outlen, inlen, ourseed, len1;
> +     void *buf;
> +
> +     if (!device_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 (!device_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;
> +}
> +
> +static unsigned long tot_inlen(struct virtqueue *vq, unsigned int i)
> +{
> +     struct vring_desc *desc;
> +     unsigned long len = 0;
> +
> +     if (vq->vring.desc[i].flags & VRING_DESC_F_INDIRECT) {
> +             unsigned int num = vq->vring.desc[i].len / sizeof(*desc);
> +             desc = phys_to_virt(vq->vring.desc[i].addr);
> +
> +             for (i = 0; i < num; i++) {
> +                     if (desc[i].flags & VRING_DESC_F_WRITE)
> +                             len += desc[i].flags.len;
> +             }
> +     } else {
> +             desc = vq->vring.desc;
> +             while (desc[i].flags & VRING_DESC_F_NEXT) {
> +                     if (desc[i].flags & VRING_DESC_F_WRITE)
> +                             len += desc[i].flags.len;
> +                     i = desc[i].next;
> +             }
> +     }
> +
> +     return len;
> +}
> +#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;
> +}
> +
> +#define BAD_RING(_vq, fmt, args...)                          \
> +     do {                                                    \
> +             dev_err(&_vq->vq.vdev->dev,                     \
> +                     "%s:"fmt, (_vq)->vq.name, ##args);      \
> +             (_vq)->broken = true;                           \
> +     } while (0)
> +#define START_USE(vq)
> +#define END_USE(vq)
> +#endif /* CONFIG_VIRTIO_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[],
> @@ -228,7 +393,10 @@ int virtqueue_add_buf(struct virtqueue *_vq,
>  
>       BUG_ON(data == NULL);
>  
> -#ifdef DEBUG
> +     if (!torture_replace(&sg, &out, &in, &data, gfp))
> +             return -ENOMEM;
> +
> +#ifdef CONFIG_VIRTIO_TORTURE
>       {
>               ktime_t now = ktime_get();
>  
> @@ -261,6 +429,7 @@ int virtqueue_add_buf(struct virtqueue *_vq,
>               if (out)
>                       vq->notify(&vq->vq);
>               END_USE(vq);
> +             torture_done(data);
>               return -ENOSPC;
>       }
>  
> @@ -341,7 +510,7 @@ bool virtqueue_kick_prepare(struct virtqueue *_vq)
>       new = vq->vring.avail->idx;
>       vq->num_added = 0;
>  
> -#ifdef DEBUG
> +#ifdef CONFIG_VIRTIO_TORTURE
>       if (vq->last_add_time_valid) {
>               WARN_ON(ktime_to_ms(ktime_sub(ktime_get(),
>                                             vq->last_add_time)) > 100);
> @@ -474,6 +643,14 @@ void *virtqueue_get_buf(struct virtqueue *_vq, unsigned 
> int *len)
>               return NULL;
>       }
>  
> +#ifdef CONFIG_VIRTIO_TORTURE
> +     if (unlikely(tot_inlen(vq, i) < *len)) {
> +             BAD_RING(vq, "id %u: %u > %u used!\n",
> +                      i, *len, tot_inlen(vq, i));
> +             return NULL;
> +     }
> +#endif
> +
>       /* detach_buf clears data, so grab it now. */
>       ret = vq->data[i];
>       detach_buf(vq, i);
> @@ -486,12 +663,14 @@ void *virtqueue_get_buf(struct virtqueue *_vq, unsigned 
> int *len)
>               virtio_mb(vq);
>       }
>  
> -#ifdef DEBUG
> +#ifdef CONFIG_VIRTIO_TORTURE
>       vq->last_add_time_valid = false;
> +     BUG_ON(*len > 
> +
>  #endif
>  
>       END_USE(vq);
> -     return ret;
> +     return torture_done(ret);
>  }
>  EXPORT_SYMBOL_GPL(virtqueue_get_buf);
>  
> @@ -683,7 +862,7 @@ struct virtqueue *vring_new_virtqueue(unsigned int index,
>       vq->last_used_idx = 0;
>       vq->num_added = 0;
>       list_add_tail(&vq->vq.list, &vdev->vqs);
> -#ifdef DEBUG
> +#ifdef CONFIG_VIRTIO_TORTURE
>       vq->in_use = false;
>       vq->last_add_time_valid = false;
>  #endif
> 

_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization

Reply via email to