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

vdpa_sim_blk: implement ramdisk behaviour

The previous implementation wrote only the status of each request.
This patch implements a more accurate block device simulator,
providing a ramdisk-like behavior and adding input validation.

Acked-by: Jason Wang <jasowang@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
Link: https://lore.kernel.org/r/20210315163450.254396-13-sgarzare@redhat.com
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>

authored by

Stefano Garzarella and committed by
Michael S. Tsirkin
7d189f61 0c853c2c

+146 -18
+146 -18
drivers/vdpa/vdpa_sim/vdpa_sim_blk.c
··· 3 3 * VDPA simulator for block device. 4 4 * 5 5 * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. 6 + * Copyright (c) 2021, Red Hat Inc. All rights reserved. 6 7 * 7 8 */ 8 9 ··· 15 14 #include <linux/blkdev.h> 16 15 #include <linux/vringh.h> 17 16 #include <linux/vdpa.h> 17 + #include <linux/blkdev.h> 18 18 #include <uapi/linux/virtio_blk.h> 19 19 20 20 #include "vdpa_sim.h" ··· 39 37 40 38 static struct vdpasim *vdpasim_blk_dev; 41 39 40 + static bool vdpasim_blk_check_range(u64 start_sector, size_t range_size) 41 + { 42 + u64 range_sectors = range_size >> SECTOR_SHIFT; 43 + 44 + if (range_size > VDPASIM_BLK_SIZE_MAX * VDPASIM_BLK_SEG_MAX) 45 + return false; 46 + 47 + if (start_sector > VDPASIM_BLK_CAPACITY) 48 + return false; 49 + 50 + if (range_sectors > VDPASIM_BLK_CAPACITY - start_sector) 51 + return false; 52 + 53 + return true; 54 + } 55 + 56 + /* Returns 'true' if the request is handled (with or without an I/O error) 57 + * and the status is correctly written in the last byte of the 'in iov', 58 + * 'false' otherwise. 59 + */ 60 + static bool vdpasim_blk_handle_req(struct vdpasim *vdpasim, 61 + struct vdpasim_virtqueue *vq) 62 + { 63 + size_t pushed = 0, to_pull, to_push; 64 + struct virtio_blk_outhdr hdr; 65 + ssize_t bytes; 66 + loff_t offset; 67 + u64 sector; 68 + u8 status; 69 + u32 type; 70 + int ret; 71 + 72 + ret = vringh_getdesc_iotlb(&vq->vring, &vq->out_iov, &vq->in_iov, 73 + &vq->head, GFP_ATOMIC); 74 + if (ret != 1) 75 + return false; 76 + 77 + if (vq->out_iov.used < 1 || vq->in_iov.used < 1) { 78 + dev_err(&vdpasim->vdpa.dev, "missing headers - out_iov: %u in_iov %u\n", 79 + vq->out_iov.used, vq->in_iov.used); 80 + return false; 81 + } 82 + 83 + if (vq->in_iov.iov[vq->in_iov.used - 1].iov_len < 1) { 84 + dev_err(&vdpasim->vdpa.dev, "request in header too short\n"); 85 + return false; 86 + } 87 + 88 + /* The last byte is the status and we checked if the last iov has 89 + * enough room for it. 90 + */ 91 + to_push = vringh_kiov_length(&vq->in_iov) - 1; 92 + 93 + to_pull = vringh_kiov_length(&vq->out_iov); 94 + 95 + bytes = vringh_iov_pull_iotlb(&vq->vring, &vq->out_iov, &hdr, 96 + sizeof(hdr)); 97 + if (bytes != sizeof(hdr)) { 98 + dev_err(&vdpasim->vdpa.dev, "request out header too short\n"); 99 + return false; 100 + } 101 + 102 + to_pull -= bytes; 103 + 104 + type = vdpasim32_to_cpu(vdpasim, hdr.type); 105 + sector = vdpasim64_to_cpu(vdpasim, hdr.sector); 106 + offset = sector << SECTOR_SHIFT; 107 + status = VIRTIO_BLK_S_OK; 108 + 109 + switch (type) { 110 + case VIRTIO_BLK_T_IN: 111 + if (!vdpasim_blk_check_range(sector, to_push)) { 112 + dev_err(&vdpasim->vdpa.dev, 113 + "reading over the capacity - offset: 0x%llx len: 0x%zx\n", 114 + offset, to_push); 115 + status = VIRTIO_BLK_S_IOERR; 116 + break; 117 + } 118 + 119 + bytes = vringh_iov_push_iotlb(&vq->vring, &vq->in_iov, 120 + vdpasim->buffer + offset, 121 + to_push); 122 + if (bytes < 0) { 123 + dev_err(&vdpasim->vdpa.dev, 124 + "vringh_iov_push_iotlb() error: %zd offset: 0x%llx len: 0x%zx\n", 125 + bytes, offset, to_push); 126 + status = VIRTIO_BLK_S_IOERR; 127 + break; 128 + } 129 + 130 + pushed += bytes; 131 + break; 132 + 133 + case VIRTIO_BLK_T_OUT: 134 + if (!vdpasim_blk_check_range(sector, to_pull)) { 135 + dev_err(&vdpasim->vdpa.dev, 136 + "writing over the capacity - offset: 0x%llx len: 0x%zx\n", 137 + offset, to_pull); 138 + status = VIRTIO_BLK_S_IOERR; 139 + break; 140 + } 141 + 142 + bytes = vringh_iov_pull_iotlb(&vq->vring, &vq->out_iov, 143 + vdpasim->buffer + offset, 144 + to_pull); 145 + if (bytes < 0) { 146 + dev_err(&vdpasim->vdpa.dev, 147 + "vringh_iov_pull_iotlb() error: %zd offset: 0x%llx len: 0x%zx\n", 148 + bytes, offset, to_pull); 149 + status = VIRTIO_BLK_S_IOERR; 150 + break; 151 + } 152 + break; 153 + 154 + default: 155 + dev_warn(&vdpasim->vdpa.dev, 156 + "Unsupported request type %d\n", type); 157 + status = VIRTIO_BLK_S_IOERR; 158 + break; 159 + } 160 + 161 + /* If some operations fail, we need to skip the remaining bytes 162 + * to put the status in the last byte 163 + */ 164 + if (to_push - pushed > 0) 165 + vringh_kiov_advance(&vq->in_iov, to_push - pushed); 166 + 167 + /* Last byte is the status */ 168 + bytes = vringh_iov_push_iotlb(&vq->vring, &vq->in_iov, &status, 1); 169 + if (bytes != 1) 170 + return false; 171 + 172 + pushed += bytes; 173 + 174 + /* Make sure data is wrote before advancing index */ 175 + smp_wmb(); 176 + 177 + vringh_complete_iotlb(&vq->vring, vq->head, pushed); 178 + 179 + return true; 180 + } 181 + 42 182 static void vdpasim_blk_work(struct work_struct *work) 43 183 { 44 184 struct vdpasim *vdpasim = container_of(work, struct vdpasim, work); 45 - u8 status = VIRTIO_BLK_S_OK; 46 185 int i; 47 186 48 187 spin_lock(&vdpasim->lock); ··· 197 54 if (!vq->ready) 198 55 continue; 199 56 200 - while (vringh_getdesc_iotlb(&vq->vring, &vq->out_iov, 201 - &vq->in_iov, &vq->head, 202 - GFP_ATOMIC) > 0) { 203 - int write; 204 - 205 - vq->in_iov.i = vq->in_iov.used - 1; 206 - write = vringh_iov_push_iotlb(&vq->vring, &vq->in_iov, 207 - &status, 1); 208 - if (write <= 0) 209 - break; 210 - 211 - /* Make sure data is wrote before advancing index */ 212 - smp_wmb(); 213 - 214 - vringh_complete_iotlb(&vq->vring, vq->head, write); 215 - 57 + while (vdpasim_blk_handle_req(vdpasim, vq)) { 216 58 /* Make sure used is visible before rasing the interrupt. */ 217 59 smp_wmb(); 218 60 ··· 237 109 dev_attr.config_size = sizeof(struct virtio_blk_config); 238 110 dev_attr.get_config = vdpasim_blk_get_config; 239 111 dev_attr.work_fn = vdpasim_blk_work; 240 - dev_attr.buffer_size = PAGE_SIZE; 112 + dev_attr.buffer_size = VDPASIM_BLK_CAPACITY << SECTOR_SHIFT; 241 113 242 114 vdpasim_blk_dev = vdpasim_create(&dev_attr); 243 115 if (IS_ERR(vdpasim_blk_dev)) {