Commit 1063b8b15fb49fcf88ffa282b19aaaf7ca9c678c
Committed by
Anthony Liguori
1 parent
451c4abd
virtio-blk: add SGI_IO passthru support
[had the qemu list address wrong the first time, reply to this message, not the previous if you were on Cc] Add support for SG_IO passthru (packet commands) to the virtio-blk backend. Conceptually based on an older patch from Hannes Reinecke but largely rewritten to match the code structure and layering in virtio-blk. Note that currently we issue the hose SG_IO synchronously. We could easily switch to async I/O, but that would required either bloating the VirtIOBlockReq by the size of struct sg_io_hdr or an additional memory allocation for each SG_IO request. Signed-off-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Showing
2 changed files
with
128 additions
and
7 deletions
hw/virtio-blk.c
| ... | ... | @@ -15,6 +15,9 @@ |
| 15 | 15 | #include <sysemu.h> |
| 16 | 16 | #include "virtio-blk.h" |
| 17 | 17 | #include "block_int.h" |
| 18 | +#ifdef __linux__ | |
| 19 | +# include <scsi/sg.h> | |
| 20 | +#endif | |
| 18 | 21 | |
| 19 | 22 | typedef struct VirtIOBlock |
| 20 | 23 | { |
| ... | ... | @@ -35,6 +38,7 @@ typedef struct VirtIOBlockReq |
| 35 | 38 | VirtQueueElement elem; |
| 36 | 39 | struct virtio_blk_inhdr *in; |
| 37 | 40 | struct virtio_blk_outhdr *out; |
| 41 | + struct virtio_scsi_inhdr *scsi; | |
| 38 | 42 | QEMUIOVector qiov; |
| 39 | 43 | struct VirtIOBlockReq *next; |
| 40 | 44 | } VirtIOBlockReq; |
| ... | ... | @@ -103,6 +107,108 @@ static VirtIOBlockReq *virtio_blk_get_request(VirtIOBlock *s) |
| 103 | 107 | return req; |
| 104 | 108 | } |
| 105 | 109 | |
| 110 | +#ifdef __linux__ | |
| 111 | +static void virtio_blk_handle_scsi(VirtIOBlockReq *req) | |
| 112 | +{ | |
| 113 | + struct sg_io_hdr hdr; | |
| 114 | + int ret, size = 0; | |
| 115 | + int status; | |
| 116 | + int i; | |
| 117 | + | |
| 118 | + /* | |
| 119 | + * We require at least one output segment each for the virtio_blk_outhdr | |
| 120 | + * and the SCSI command block. | |
| 121 | + * | |
| 122 | + * We also at least require the virtio_blk_inhdr, the virtio_scsi_inhdr | |
| 123 | + * and the sense buffer pointer in the input segments. | |
| 124 | + */ | |
| 125 | + if (req->elem.out_num < 2 || req->elem.in_num < 3) { | |
| 126 | + virtio_blk_req_complete(req, VIRTIO_BLK_S_IOERR); | |
| 127 | + return; | |
| 128 | + } | |
| 129 | + | |
| 130 | + /* | |
| 131 | + * No support for bidirection commands yet. | |
| 132 | + */ | |
| 133 | + if (req->elem.out_num > 2 && req->elem.in_num > 3) { | |
| 134 | + virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); | |
| 135 | + return; | |
| 136 | + } | |
| 137 | + | |
| 138 | + /* | |
| 139 | + * The scsi inhdr is placed in the second-to-last input segment, just | |
| 140 | + * before the regular inhdr. | |
| 141 | + */ | |
| 142 | + req->scsi = (void *)req->elem.in_sg[req->elem.in_num - 2].iov_base; | |
| 143 | + size = sizeof(*req->in) + sizeof(*req->scsi); | |
| 144 | + | |
| 145 | + memset(&hdr, 0, sizeof(struct sg_io_hdr)); | |
| 146 | + hdr.interface_id = 'S'; | |
| 147 | + hdr.cmd_len = req->elem.out_sg[1].iov_len; | |
| 148 | + hdr.cmdp = req->elem.out_sg[1].iov_base; | |
| 149 | + hdr.dxfer_len = 0; | |
| 150 | + | |
| 151 | + if (req->elem.out_num > 2) { | |
| 152 | + /* | |
| 153 | + * If there are more than the minimally required 2 output segments | |
| 154 | + * there is write payload starting from the third iovec. | |
| 155 | + */ | |
| 156 | + hdr.dxfer_direction = SG_DXFER_TO_DEV; | |
| 157 | + hdr.iovec_count = req->elem.out_num - 2; | |
| 158 | + | |
| 159 | + for (i = 0; i < hdr.iovec_count; i++) | |
| 160 | + hdr.dxfer_len += req->elem.out_sg[i + 2].iov_len; | |
| 161 | + | |
| 162 | + hdr.dxferp = req->elem.out_sg + 2; | |
| 163 | + | |
| 164 | + } else if (req->elem.in_num > 3) { | |
| 165 | + /* | |
| 166 | + * If we have more than 3 input segments the guest wants to actually | |
| 167 | + * read data. | |
| 168 | + */ | |
| 169 | + hdr.dxfer_direction = SG_DXFER_FROM_DEV; | |
| 170 | + hdr.iovec_count = req->elem.in_num - 3; | |
| 171 | + for (i = 0; i < hdr.iovec_count; i++) | |
| 172 | + hdr.dxfer_len += req->elem.in_sg[i].iov_len; | |
| 173 | + | |
| 174 | + hdr.dxferp = req->elem.in_sg; | |
| 175 | + size += hdr.dxfer_len; | |
| 176 | + } else { | |
| 177 | + /* | |
| 178 | + * Some SCSI commands don't actually transfer any data. | |
| 179 | + */ | |
| 180 | + hdr.dxfer_direction = SG_DXFER_NONE; | |
| 181 | + } | |
| 182 | + | |
| 183 | + hdr.sbp = req->elem.in_sg[req->elem.in_num - 3].iov_base; | |
| 184 | + hdr.mx_sb_len = req->elem.in_sg[req->elem.in_num - 3].iov_len; | |
| 185 | + size += hdr.mx_sb_len; | |
| 186 | + | |
| 187 | + ret = bdrv_ioctl(req->dev->bs, SG_IO, &hdr); | |
| 188 | + if (ret) { | |
| 189 | + status = VIRTIO_BLK_S_UNSUPP; | |
| 190 | + hdr.status = ret; | |
| 191 | + hdr.resid = hdr.dxfer_len; | |
| 192 | + } else if (hdr.status) { | |
| 193 | + status = VIRTIO_BLK_S_IOERR; | |
| 194 | + } else { | |
| 195 | + status = VIRTIO_BLK_S_OK; | |
| 196 | + } | |
| 197 | + | |
| 198 | + req->scsi->errors = hdr.status; | |
| 199 | + req->scsi->residual = hdr.resid; | |
| 200 | + req->scsi->sense_len = hdr.sb_len_wr; | |
| 201 | + req->scsi->data_len = hdr.dxfer_len; | |
| 202 | + | |
| 203 | + virtio_blk_req_complete(req, status); | |
| 204 | +} | |
| 205 | +#else | |
| 206 | +static void virtio_blk_handle_scsi(VirtIOBlockReq *req) | |
| 207 | +{ | |
| 208 | + virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); | |
| 209 | +} | |
| 210 | +#endif /* __linux__ */ | |
| 211 | + | |
| 106 | 212 | static void virtio_blk_handle_write(VirtIOBlockReq *req) |
| 107 | 213 | { |
| 108 | 214 | bdrv_aio_writev(req->dev->bs, req->out->sector, &req->qiov, |
| ... | ... | @@ -136,12 +242,7 @@ static void virtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq) |
| 136 | 242 | req->in = (void *)req->elem.in_sg[req->elem.in_num - 1].iov_base; |
| 137 | 243 | |
| 138 | 244 | if (req->out->type & VIRTIO_BLK_T_SCSI_CMD) { |
| 139 | - unsigned int len = sizeof(*req->in); | |
| 140 | - | |
| 141 | - req->in->status = VIRTIO_BLK_S_UNSUPP; | |
| 142 | - virtqueue_push(vq, &req->elem, len); | |
| 143 | - virtio_notify(vdev, vq); | |
| 144 | - qemu_free(req); | |
| 245 | + virtio_blk_handle_scsi(req); | |
| 145 | 246 | } else if (req->out->type & VIRTIO_BLK_T_OUT) { |
| 146 | 247 | qemu_iovec_init_external(&req->qiov, &req->elem.out_sg[1], |
| 147 | 248 | req->elem.out_num - 1); |
| ... | ... | @@ -203,7 +304,15 @@ static void virtio_blk_update_config(VirtIODevice *vdev, uint8_t *config) |
| 203 | 304 | |
| 204 | 305 | static uint32_t virtio_blk_get_features(VirtIODevice *vdev) |
| 205 | 306 | { |
| 206 | - return (1 << VIRTIO_BLK_F_SEG_MAX | 1 << VIRTIO_BLK_F_GEOMETRY); | |
| 307 | + uint32_t features = 0; | |
| 308 | + | |
| 309 | + features |= (1 << VIRTIO_BLK_F_SEG_MAX); | |
| 310 | + features |= (1 << VIRTIO_BLK_F_GEOMETRY); | |
| 311 | +#ifdef __linux__ | |
| 312 | + features |= (1 << VIRTIO_BLK_F_SCSI); | |
| 313 | +#endif | |
| 314 | + | |
| 315 | + return features; | |
| 207 | 316 | } |
| 208 | 317 | |
| 209 | 318 | static void virtio_blk_save(QEMUFile *f, void *opaque) | ... | ... |
hw/virtio-blk.h
| ... | ... | @@ -28,6 +28,9 @@ |
| 28 | 28 | #define VIRTIO_BLK_F_SIZE_MAX 1 /* Indicates maximum segment size */ |
| 29 | 29 | #define VIRTIO_BLK_F_SEG_MAX 2 /* Indicates maximum # of segments */ |
| 30 | 30 | #define VIRTIO_BLK_F_GEOMETRY 4 /* Indicates support of legacy geometry */ |
| 31 | +#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */ | |
| 32 | +#define VIRTIO_BLK_F_BLK_SIZE 6 /* Block size of disk is available*/ | |
| 33 | +#define VIRTIO_BLK_F_SCSI 7 /* Supports scsi command passthru */ | |
| 31 | 34 | |
| 32 | 35 | struct virtio_blk_config |
| 33 | 36 | { |
| ... | ... | @@ -70,6 +73,15 @@ struct virtio_blk_inhdr |
| 70 | 73 | unsigned char status; |
| 71 | 74 | }; |
| 72 | 75 | |
| 76 | +/* SCSI pass-through header */ | |
| 77 | +struct virtio_scsi_inhdr | |
| 78 | +{ | |
| 79 | + uint32_t errors; | |
| 80 | + uint32_t data_len; | |
| 81 | + uint32_t sense_len; | |
| 82 | + uint32_t residual; | |
| 83 | +}; | |
| 84 | + | |
| 73 | 85 | void *virtio_blk_init(PCIBus *bus, BlockDriverState *bs); |
| 74 | 86 | |
| 75 | 87 | #endif | ... | ... |