123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- /*
- * Virtio Block Device
- *
- * Copyright IBM, Corp. 2007
- *
- * Authors:
- * Anthony Liguori <aliguori@us.ibm.com>
- *
- * This work is licensed under the terms of the GNU GPL, version 2. See
- * the COPYING file in the top-level directory.
- *
- */
- #include <qemu-common.h>
- #include <sysemu.h>
- #include "virtio-blk.h"
- #include "block_int.h"
- typedef struct VirtIOBlock
- {
- VirtIODevice vdev;
- BlockDriverState *bs;
- VirtQueue *vq;
- void *rq;
- } VirtIOBlock;
- static VirtIOBlock *to_virtio_blk(VirtIODevice *vdev)
- {
- return (VirtIOBlock *)vdev;
- }
- typedef struct VirtIOBlockReq
- {
- VirtIOBlock *dev;
- VirtQueueElement elem;
- struct virtio_blk_inhdr *in;
- struct virtio_blk_outhdr *out;
- size_t size;
- uint8_t *buffer;
- struct VirtIOBlockReq *next;
- } VirtIOBlockReq;
- static void virtio_blk_req_complete(VirtIOBlockReq *req, int status)
- {
- VirtIOBlock *s = req->dev;
- req->in->status = status;
- virtqueue_push(s->vq, &req->elem, req->size + sizeof(*req->in));
- virtio_notify(&s->vdev, s->vq);
- qemu_free(req->buffer);
- qemu_free(req);
- }
- static int virtio_blk_handle_write_error(VirtIOBlockReq *req, int error)
- {
- BlockInterfaceErrorAction action = drive_get_onerror(req->dev->bs);
- VirtIOBlock *s = req->dev;
- if (action == BLOCK_ERR_IGNORE)
- return 0;
- if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC)
- || action == BLOCK_ERR_STOP_ANY) {
- req->next = s->rq;
- s->rq = req;
- vm_stop(0);
- } else {
- virtio_blk_req_complete(req, VIRTIO_BLK_S_IOERR);
- }
- return 1;
- }
- static void virtio_blk_rw_complete(void *opaque, int ret)
- {
- VirtIOBlockReq *req = opaque;
- /* Copy read data to the guest */
- if (!ret && !(req->out->type & VIRTIO_BLK_T_OUT)) {
- size_t offset = 0;
- int i;
- for (i = 0; i < req->elem.in_num - 1; i++) {
- size_t len;
- /* Be pretty defensive wrt malicious guests */
- len = MIN(req->elem.in_sg[i].iov_len,
- req->size - offset);
- memcpy(req->elem.in_sg[i].iov_base,
- req->buffer + offset,
- len);
- offset += len;
- }
- } else if (ret && (req->out->type & VIRTIO_BLK_T_OUT)) {
- if (virtio_blk_handle_write_error(req, -ret))
- return;
- }
- virtio_blk_req_complete(req, VIRTIO_BLK_S_OK);
- }
- static VirtIOBlockReq *virtio_blk_alloc_request(VirtIOBlock *s)
- {
- VirtIOBlockReq *req = qemu_mallocz(sizeof(*req));
- req->dev = s;
- return req;
- }
- static VirtIOBlockReq *virtio_blk_get_request(VirtIOBlock *s)
- {
- VirtIOBlockReq *req = virtio_blk_alloc_request(s);
- if (req != NULL) {
- if (!virtqueue_pop(s->vq, &req->elem)) {
- qemu_free(req);
- return NULL;
- }
- }
- return req;
- }
- static int virtio_blk_handle_write(VirtIOBlockReq *req)
- {
- if (!req->buffer) {
- size_t offset = 0;
- int i;
- for (i = 1; i < req->elem.out_num; i++)
- req->size += req->elem.out_sg[i].iov_len;
- req->buffer = qemu_memalign(512, req->size);
- if (req->buffer == NULL) {
- qemu_free(req);
- return -1;
- }
- /* We copy the data from the SG list to avoid splitting up the request.
- This helps performance a lot until we can pass full sg lists as AIO
- operations */
- for (i = 1; i < req->elem.out_num; i++) {
- size_t len;
- len = MIN(req->elem.out_sg[i].iov_len,
- req->size - offset);
- memcpy(req->buffer + offset,
- req->elem.out_sg[i].iov_base,
- len);
- offset += len;
- }
- }
- bdrv_aio_write(req->dev->bs, req->out->sector, req->buffer, req->size / 512,
- virtio_blk_rw_complete, req);
- return 0;
- }
- static void virtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq)
- {
- VirtIOBlock *s = to_virtio_blk(vdev);
- VirtIOBlockReq *req;
- while ((req = virtio_blk_get_request(s))) {
- int i;
- if (req->elem.out_num < 1 || req->elem.in_num < 1) {
- fprintf(stderr, "virtio-blk missing headers\n");
- exit(1);
- }
- if (req->elem.out_sg[0].iov_len < sizeof(*req->out) ||
- req->elem.in_sg[req->elem.in_num - 1].iov_len < sizeof(*req->in)) {
- fprintf(stderr, "virtio-blk header not in correct element\n");
- exit(1);
- }
- req->out = (void *)req->elem.out_sg[0].iov_base;
- req->in = (void *)req->elem.in_sg[req->elem.in_num - 1].iov_base;
- if (req->out->type & VIRTIO_BLK_T_SCSI_CMD) {
- unsigned int len = sizeof(*req->in);
- req->in->status = VIRTIO_BLK_S_UNSUPP;
- virtqueue_push(vq, &req->elem, len);
- virtio_notify(vdev, vq);
- qemu_free(req);
- } else if (req->out->type & VIRTIO_BLK_T_OUT) {
- if (virtio_blk_handle_write(req) < 0)
- break;
- } else {
- for (i = 0; i < req->elem.in_num - 1; i++)
- req->size += req->elem.in_sg[i].iov_len;
- req->buffer = qemu_memalign(512, req->size);
- if (req->buffer == NULL) {
- qemu_free(req);
- break;
- }
- bdrv_aio_read(s->bs, req->out->sector,
- req->buffer,
- req->size / 512,
- virtio_blk_rw_complete,
- req);
- }
- }
- /*
- * FIXME: Want to check for completions before returning to guest mode,
- * so cached reads and writes are reported as quickly as possible. But
- * that should be done in the generic block layer.
- */
- }
- static void virtio_blk_dma_restart_cb(void *opaque, int running, int reason)
- {
- VirtIOBlock *s = opaque;
- VirtIOBlockReq *req = s->rq;
- if (!running)
- return;
- s->rq = NULL;
- while (req) {
- virtio_blk_handle_write(req);
- req = req->next;
- }
- }
- static void virtio_blk_reset(VirtIODevice *vdev)
- {
- /*
- * This should cancel pending requests, but can't do nicely until there
- * are per-device request lists.
- */
- qemu_aio_flush();
- }
- static void virtio_blk_update_config(VirtIODevice *vdev, uint8_t *config)
- {
- VirtIOBlock *s = to_virtio_blk(vdev);
- struct virtio_blk_config blkcfg;
- uint64_t capacity;
- int cylinders, heads, secs;
- bdrv_get_geometry(s->bs, &capacity);
- bdrv_get_geometry_hint(s->bs, &cylinders, &heads, &secs);
- memset(&blkcfg, 0, sizeof(blkcfg));
- stq_raw(&blkcfg.capacity, capacity);
- stl_raw(&blkcfg.seg_max, 128 - 2);
- stw_raw(&blkcfg.cylinders, cylinders);
- blkcfg.heads = heads;
- blkcfg.sectors = secs;
- memcpy(config, &blkcfg, sizeof(blkcfg));
- }
- static uint32_t virtio_blk_get_features(VirtIODevice *vdev)
- {
- return (1 << VIRTIO_BLK_F_SEG_MAX | 1 << VIRTIO_BLK_F_GEOMETRY);
- }
- static void virtio_blk_save(QEMUFile *f, void *opaque)
- {
- VirtIOBlock *s = opaque;
- VirtIOBlockReq *req = s->rq;
- virtio_save(&s->vdev, f);
-
- while (req) {
- qemu_put_sbyte(f, 1);
- qemu_put_buffer(f, (unsigned char*)&req->elem, sizeof(req->elem));
- req = req->next;
- }
- qemu_put_sbyte(f, 0);
- }
- static int virtio_blk_load(QEMUFile *f, void *opaque, int version_id)
- {
- VirtIOBlock *s = opaque;
- if (version_id != 2)
- return -EINVAL;
- virtio_load(&s->vdev, f);
- while (qemu_get_sbyte(f)) {
- VirtIOBlockReq *req = virtio_blk_alloc_request(s);
- qemu_get_buffer(f, (unsigned char*)&req->elem, sizeof(req->elem));
- req->next = s->rq;
- s->rq = req->next;
- }
- return 0;
- }
- void *virtio_blk_init(PCIBus *bus, BlockDriverState *bs)
- {
- VirtIOBlock *s;
- int cylinders, heads, secs;
- static int virtio_blk_id;
- s = (VirtIOBlock *)virtio_init_pci(bus, "virtio-blk",
- PCI_VENDOR_ID_REDHAT_QUMRANET,
- PCI_DEVICE_ID_VIRTIO_BLOCK,
- PCI_VENDOR_ID_REDHAT_QUMRANET,
- VIRTIO_ID_BLOCK,
- PCI_CLASS_STORAGE_OTHER, 0x00,
- sizeof(struct virtio_blk_config), sizeof(VirtIOBlock));
- if (!s)
- return NULL;
- s->vdev.get_config = virtio_blk_update_config;
- s->vdev.get_features = virtio_blk_get_features;
- s->vdev.reset = virtio_blk_reset;
- s->bs = bs;
- s->rq = NULL;
- bs->private = &s->vdev.pci_dev;
- bdrv_guess_geometry(s->bs, &cylinders, &heads, &secs);
- bdrv_set_geometry_hint(s->bs, cylinders, heads, secs);
- s->vq = virtio_add_queue(&s->vdev, 128, virtio_blk_handle_output);
- qemu_add_vm_change_state_handler(virtio_blk_dma_restart_cb, s);
- register_savevm("virtio-blk", virtio_blk_id++, 2,
- virtio_blk_save, virtio_blk_load, s);
- return s;
- }
|