Commit 1dc936aa authored by Fam Zheng's avatar Fam Zheng Committed by Kevin Wolf
Browse files

virtio-blk: Use blk_aio_ioctl



Use the asynchronous interface of ioctl. This will not make the VM
unresponsive if the ioctl takes a long time.

Signed-off-by: default avatarFam Zheng <famz@redhat.com>
Reviewed-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
Signed-off-by: default avatarKevin Wolf <kwolf@redhat.com>
parent 75344fa4
Loading
Loading
Loading
Loading
+79 −46
Original line number Diff line number Diff line
@@ -115,6 +115,56 @@ static void virtio_blk_flush_complete(void *opaque, int ret)
    virtio_blk_free_request(req);
}

#ifdef __linux__

typedef struct {
    VirtIOBlockReq *req;
    struct sg_io_hdr hdr;
} VirtIOBlockIoctlReq;

static void virtio_blk_ioctl_complete(void *opaque, int status)
{
    VirtIOBlockIoctlReq *ioctl_req = opaque;
    VirtIOBlockReq *req = ioctl_req->req;
    VirtIODevice *vdev = VIRTIO_DEVICE(req->dev);
    struct virtio_scsi_inhdr *scsi;
    struct sg_io_hdr *hdr;

    scsi = (void *)req->elem.in_sg[req->elem.in_num - 2].iov_base;

    if (status) {
        status = VIRTIO_BLK_S_UNSUPP;
        virtio_stl_p(vdev, &scsi->errors, 255);
        goto out;
    }

    hdr = &ioctl_req->hdr;
    /*
     * From SCSI-Generic-HOWTO: "Some lower level drivers (e.g. ide-scsi)
     * clear the masked_status field [hence status gets cleared too, see
     * block/scsi_ioctl.c] even when a CHECK_CONDITION or COMMAND_TERMINATED
     * status has occurred.  However they do set DRIVER_SENSE in driver_status
     * field. Also a (sb_len_wr > 0) indicates there is a sense buffer.
     */
    if (hdr->status == 0 && hdr->sb_len_wr > 0) {
        hdr->status = CHECK_CONDITION;
    }

    virtio_stl_p(vdev, &scsi->errors,
                 hdr->status | (hdr->msg_status << 8) |
                 (hdr->host_status << 16) | (hdr->driver_status << 24));
    virtio_stl_p(vdev, &scsi->residual, hdr->resid);
    virtio_stl_p(vdev, &scsi->sense_len, hdr->sb_len_wr);
    virtio_stl_p(vdev, &scsi->data_len, hdr->dxfer_len);

out:
    virtio_blk_req_complete(req, status);
    virtio_blk_free_request(req);
    g_free(ioctl_req);
}

#endif

static VirtIOBlockReq *virtio_blk_get_request(VirtIOBlock *s)
{
    VirtIOBlockReq *req = virtio_blk_alloc_request(s);
@@ -137,7 +187,7 @@ static int virtio_blk_handle_scsi_req(VirtIOBlockReq *req)

#ifdef __linux__
    int i;
    struct sg_io_hdr hdr;
    VirtIOBlockIoctlReq *ioctl_req;
#endif

    /*
@@ -172,71 +222,52 @@ static int virtio_blk_handle_scsi_req(VirtIOBlockReq *req)
    }

#ifdef __linux__
    memset(&hdr, 0, sizeof(struct sg_io_hdr));
    hdr.interface_id = 'S';
    hdr.cmd_len = elem->out_sg[1].iov_len;
    hdr.cmdp = elem->out_sg[1].iov_base;
    hdr.dxfer_len = 0;
    ioctl_req = g_new0(VirtIOBlockIoctlReq, 1);
    ioctl_req->req = req;
    ioctl_req->hdr.interface_id = 'S';
    ioctl_req->hdr.cmd_len = elem->out_sg[1].iov_len;
    ioctl_req->hdr.cmdp = elem->out_sg[1].iov_base;
    ioctl_req->hdr.dxfer_len = 0;

    if (elem->out_num > 2) {
        /*
         * If there are more than the minimally required 2 output segments
         * there is write payload starting from the third iovec.
         */
        hdr.dxfer_direction = SG_DXFER_TO_DEV;
        hdr.iovec_count = elem->out_num - 2;
        ioctl_req->hdr.dxfer_direction = SG_DXFER_TO_DEV;
        ioctl_req->hdr.iovec_count = elem->out_num - 2;

        for (i = 0; i < hdr.iovec_count; i++)
            hdr.dxfer_len += elem->out_sg[i + 2].iov_len;
        for (i = 0; i < ioctl_req->hdr.iovec_count; i++) {
            ioctl_req->hdr.dxfer_len += elem->out_sg[i + 2].iov_len;
        }

        hdr.dxferp = elem->out_sg + 2;
        ioctl_req->hdr.dxferp = elem->out_sg + 2;

    } else if (elem->in_num > 3) {
        /*
         * If we have more than 3 input segments the guest wants to actually
         * read data.
         */
        hdr.dxfer_direction = SG_DXFER_FROM_DEV;
        hdr.iovec_count = elem->in_num - 3;
        for (i = 0; i < hdr.iovec_count; i++)
            hdr.dxfer_len += elem->in_sg[i].iov_len;
        ioctl_req->hdr.dxfer_direction = SG_DXFER_FROM_DEV;
        ioctl_req->hdr.iovec_count = elem->in_num - 3;
        for (i = 0; i < ioctl_req->hdr.iovec_count; i++) {
            ioctl_req->hdr.dxfer_len += elem->in_sg[i].iov_len;
        }

        hdr.dxferp = elem->in_sg;
        ioctl_req->hdr.dxferp = elem->in_sg;
    } else {
        /*
         * Some SCSI commands don't actually transfer any data.
         */
        hdr.dxfer_direction = SG_DXFER_NONE;
    }

    hdr.sbp = elem->in_sg[elem->in_num - 3].iov_base;
    hdr.mx_sb_len = elem->in_sg[elem->in_num - 3].iov_len;

    status = blk_ioctl(blk->blk, SG_IO, &hdr);
    if (status) {
        status = VIRTIO_BLK_S_UNSUPP;
        goto fail;
    }

    /*
     * From SCSI-Generic-HOWTO: "Some lower level drivers (e.g. ide-scsi)
     * clear the masked_status field [hence status gets cleared too, see
     * block/scsi_ioctl.c] even when a CHECK_CONDITION or COMMAND_TERMINATED
     * status has occurred.  However they do set DRIVER_SENSE in driver_status
     * field. Also a (sb_len_wr > 0) indicates there is a sense buffer.
     */
    if (hdr.status == 0 && hdr.sb_len_wr > 0) {
        hdr.status = CHECK_CONDITION;
        ioctl_req->hdr.dxfer_direction = SG_DXFER_NONE;
    }

    virtio_stl_p(vdev, &scsi->errors,
                 hdr.status | (hdr.msg_status << 8) |
                 (hdr.host_status << 16) | (hdr.driver_status << 24));
    virtio_stl_p(vdev, &scsi->residual, hdr.resid);
    virtio_stl_p(vdev, &scsi->sense_len, hdr.sb_len_wr);
    virtio_stl_p(vdev, &scsi->data_len, hdr.dxfer_len);
    ioctl_req->hdr.sbp = elem->in_sg[elem->in_num - 3].iov_base;
    ioctl_req->hdr.mx_sb_len = elem->in_sg[elem->in_num - 3].iov_len;

    return status;
    blk_aio_ioctl(blk->blk, SG_IO, &ioctl_req->hdr,
                  virtio_blk_ioctl_complete, ioctl_req);
    return -EINPROGRESS;
#else
    abort();
#endif
@@ -254,9 +285,11 @@ static void virtio_blk_handle_scsi(VirtIOBlockReq *req)
    int status;

    status = virtio_blk_handle_scsi_req(req);
    if (status != -EINPROGRESS) {
        virtio_blk_req_complete(req, status);
        virtio_blk_free_request(req);
    }
}

void virtio_submit_multiwrite(BlockBackend *blk, MultiReqBuffer *mrb)
{