diff options
Diffstat (limited to 'vhost_user_blk.c')
-rw-r--r-- | vhost_user_blk.c | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/vhost_user_blk.c b/vhost_user_blk.c new file mode 100644 index 0000000..eebd599 --- /dev/null +++ b/vhost_user_blk.c @@ -0,0 +1,514 @@ +/* + * Based on vhost-user-blk.c of QEMU project + * + * Copyright(C) 2017 Intel Corporation. + * + * Authors: + * Changpeng Liu <changpeng.liu@intel.com> + * + * Largely based on the "vhost-user-scsi.c" and "vhost-scsi.c" implemented by: + * Felipe Franciosi <felipe@nutanix.com> + * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> + * Nicholas Bellinger <nab@risingtidesystems.com> + * + * Copyright (c) 2022 Virtual Open Systems SAS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdbool.h> +#include <sys/param.h> + +/* Project header files */ +#include "vhost_user_blk.h" + +#ifdef DEBUG +#define DBG(...) printf("vhost-user-blk: " __VA_ARGS__) +#else +#define DBG(...) +#endif /* DEBUG */ + + +#define REALIZE_CONNECTION_RETRIES 3 + +static int vhost_user_blk_start(VirtIODevice *vdev) +{ + VHostUserBlk *s = vdev->vhublk; + VirtioBus *k = vdev->vbus; + int i, ret; + + DBG("vhost_user_blk_start\n"); + + if (!k->set_guest_notifiers) { + DBG("binding does not support guest notifiers\n"); + return -1; + } + + ret = vhost_dev_enable_notifiers(s->vhost_dev, vdev); + if (ret < 0) { + DBG("Error enabling host notifiers\n"); + return ret; + } + + ret = k->set_guest_notifiers(k->vdev, s->vhost_dev->nvqs, true); + if (ret < 0) { + DBG("Error enabling host notifier\n"); + return ret; + } + + s->vhost_dev->acked_features = vdev->guest_features; + + /* FIXME: We might do not need that */ + ret = vhost_dev_prepare_inflight(s->vhost_dev, vdev); + if (ret < 0) { + DBG("Error setting inflight format\n"); + return ret; + } + + if (!s->inflight->addr) { + ret = vhost_dev_get_inflight(s->vhost_dev, s->queue_size, s->inflight); + if (ret < 0) { + DBG("Error getting inflight\n"); + return ret; + } + } + + ret = vhost_dev_set_inflight(s->vhost_dev, s->inflight); + if (ret < 0) { + DBG("Error setting inflight\n"); + return ret; + } + + DBG("After vhost_dev_set_inflight\n"); + + ret = vhost_dev_start(s->vhost_dev, vdev); + if (ret < 0) { + DBG("Error starting vhost\n"); + return ret; + } + s->started_vu = true; + + DBG("vhost_virtqueue_mask\n"); + /* + * guest_notifier_mask/pending not used yet, so just unmask + * everything here. virtio-pci will do the right thing by + * enabling/disabling irqfd. + */ + for (i = 0; i < s->vhost_dev->nvqs; i++) { + vhost_virtqueue_mask(s->vhost_dev, vdev, i, false); + } + + DBG("vhost_user_blk_start return successfully: %d\n", ret); + return ret; + +} + +static void vhost_user_blk_stop(VirtIODevice *vdev) +{ + DBG("Not yet implemented\n"); +} + +static int vhost_user_blk_handle_config_change(struct vhost_dev *dev) +{ + int ret; + struct virtio_blk_config blkcfg; + VHostUserBlk *s = dev->vdev->vhublk; + + DBG("vhost_user_blk_handle_config_change(...)\n"); + + ret = vhost_dev_get_config(dev, (uint8_t *)&blkcfg, + sizeof(struct virtio_blk_config)); + if (ret < 0) { + DBG("vhost_dev_get_config\n"); + return ret; + } + + /* valid for resize only */ + if (blkcfg.capacity != s->blkcfg.capacity) { + DBG("blkcfg.capacity != s->blkcfg.capacity\n"); + s->blkcfg.capacity = blkcfg.capacity; + memcpy(dev->vdev->config, &s->blkcfg, sizeof(struct virtio_blk_config)); + DBG("To virtio_notify_config\n"); + virtio_notify_config(dev->vdev); + } + + return 0; +} + + +const VhostDevConfigOps blk_ops = { + .vhost_dev_config_notifier = vhost_user_blk_handle_config_change, +}; + + +static uint64_t vhost_user_blk_get_features(VirtIODevice *vdev, + uint64_t features) +{ + VHostUserBlk *s = vdev->vhublk; + + DBG("vhost_user_blk_get_features()\n"); + + /* Turn on pre-defined features */ + virtio_add_feature(&features, VIRTIO_BLK_F_SEG_MAX); + virtio_add_feature(&features, VIRTIO_BLK_F_GEOMETRY); + virtio_add_feature(&features, VIRTIO_BLK_F_TOPOLOGY); + virtio_add_feature(&features, VIRTIO_BLK_F_FLUSH); + virtio_add_feature(&features, VIRTIO_BLK_F_DISCARD); + virtio_add_feature(&features, VIRTIO_BLK_F_WRITE_ZEROES); + /* + * TODO: Delete if not needed + * virtio_add_feature(&features, VIRTIO_BLK_F_BLK_SIZE); + */ + + /* + * The next line makes the blk read only + * + * virtio_add_feature(&features, VIRTIO_BLK_F_RO); + * + */ + + if (s->num_queues > 1) { + virtio_add_feature(&features, VIRTIO_BLK_F_MQ); + } + + return features; +} + +static int vhost_user_blk_connect(VirtIODevice *vdev) +{ + VHostUserBlk *s = vdev->vhublk; + int ret = 0; + + DBG("vhost_user_blk_connect(...)\n"); + + if (s->connected) { + DBG("s->connected\n"); + return 0; + } + s->connected = true; + s->vhost_dev->num_queues = s->num_queues; + s->vhost_dev->nvqs = s->num_queues; + s->vhost_dev->vqs = s->vhost_vqs; + s->vhost_dev->vq_index = 0; + s->vhost_dev->backend_features = 0; + + vhost_dev_set_config_notifier(s->vhost_dev, &blk_ops); + + vhost_dev_init(s->vhost_dev); + + /* Pass the new obtained features */ + global_vdev->host_features = global_vdev->vhublk->vhost_dev->features; + + /* Disable VIRTIO_RING_F_INDIRECT_DESC, to be supported in future release */ + global_vdev->host_features &= ~(1ULL << VIRTIO_RING_F_INDIRECT_DESC); + + DBG("After init global_vdev->host_features: 0x%lx\n", + global_vdev->host_features); + + /* Restore vhost state */ + if (virtio_device_started(vdev, vdev->status)) { + ret = vhost_user_blk_start(vdev); + if (ret < 0) { + DBG("vhost_user_blk_start failed\n"); + return ret; + } + } + + DBG("vhost_user_blk_connect return successfully!\n"); + + return 0; +} + +static void vhost_user_blk_disconnect(VirtIODevice *dev) +{ + DBG("vhost_user_blk_disconnect not yet implemented\n"); +} + +static void vhost_user_blk_chr_closed_bh(void *opaque) +{ + DBG("vhost_user_blk_chr_closed_bh not yet implemented\n"); +} + +static void vhost_user_blk_event(void *opaque) +{ + DBG("vhost_user_blk_event not yet implemented"); +} + +static int vhost_user_blk_realize_connect(VHostUserBlk *s) +{ + int ret; + + DBG("vhost_user_blk_realize_connect(...)\n"); + s->connected = false; + + DBG("s->vdev: 0x%lx\n", (uint64_t)s->parent); + DBG("global_vdev: 0x%lx\n", (uint64_t)global_vdev); + ret = vhost_user_blk_connect(s->parent); + if (ret < 0) { + DBG("vhost_user_blk_connect failed\n"); + return ret; + } + DBG("s->connected: %d\n", s->connected); + + ret = vhost_dev_get_config(s->vhost_dev, (uint8_t *)&s->blkcfg, + sizeof(struct virtio_blk_config)); + if (ret < 0) { + DBG("vhost_dev_get_config failed\n"); + return ret; + } + + return 0; +} + + +static void vhost_user_blk_device_unrealize(VirtIODevice *vdev) +{ + DBG("vhost_user_blk_device_unrealize not yet implemented\n"); +} + +static void vhost_user_blk_reset(VirtIODevice *vdev) +{ + DBG("vhost_user_blk_reset not yet implemented\n"); +} + +static void vhost_user_blk_set_config(VirtIODevice *vdev, + const uint8_t *config); + + +static void vhost_user_blk_update_config(VirtIODevice *vdev, uint8_t *config) +{ + VHostUserBlk *s = vdev->vhublk; + + DBG("vhost_user_blk_update_config(...)\n"); + + /* Our num_queues overrides the device backend */ + memcpy(&s->blkcfg.num_queues, &s->num_queues, sizeof(uint64_t)); + + memcpy(config, &s->blkcfg, sizeof(struct virtio_blk_config)); +} + +static void vhost_user_blk_set_config(VirtIODevice *vdev, const uint8_t *config) +{ + VHostUserBlk *s = vdev->vhublk; + struct virtio_blk_config *blkcfg = (struct virtio_blk_config *)config; + int ret; + + DBG("vhost_user_blk_set_config(...)\n"); + + + /* + * TODO: Disabled for the current release + * if (blkcfg->wce == s->blkcfg.wce) { + * DBG("blkcfg->wce == s->blkcfg.wce\n"); + * return; + * } + */ + + ret = vhost_dev_set_config(s->vhost_dev, &blkcfg->wce, + offsetof(struct virtio_blk_config, wce), + sizeof(blkcfg->wce), + VHOST_SET_CONFIG_TYPE_MASTER); + if (ret) { + DBG("set device config space failed\n"); + return; + } + + s->blkcfg.wce = blkcfg->wce; +} + + +static void vhost_user_blk_set_status(VirtIODevice *vdev, uint8_t status) +{ + VHostUserBlk *s = vdev->vhublk; + /* Just for testing: bool should_start = true; */ + bool should_start = virtio_device_started(vdev, status); + int ret; + + DBG("vhost_user_blk_set_status (...)\n"); + + /* TODO: Remove if not needed */ + if (!s->connected) { + DBG("Not connected!\n"); + return; + } + + DBG("should_start == %d\n", should_start); + if (s->vhost_dev->started == should_start) { + DBG("s->dev->started == should_start\n"); + return; + } + + if (should_start) { + ret = vhost_user_blk_start(vdev); + if (ret < 0) { + DBG("vhost_user_blk_start returned error\n"); + } + } else { + DBG("Call vhost_user_blk_stop (not yet in place)\n"); + /* TODO: vhost_user_blk_stop(vdev); */ + } + + DBG("vhost_user_blk_set_status return successfully\n"); +} + + +static void virtio_dev_class_init(VirtIODevice *vdev) +{ + DBG("virtio_dev_class_init\n"); + + vdev->vdev_class = (VirtioDeviceClass *)malloc(sizeof(VirtioDeviceClass)); + vdev->vdev_class->parent = vdev; + vdev->vdev_class->realize = vhost_user_blk_realize; + vdev->vdev_class->unrealize = vhost_user_blk_device_unrealize; + vdev->vdev_class->get_config = vhost_user_blk_update_config; + vdev->vdev_class->set_config = vhost_user_blk_set_config; + vdev->vdev_class->get_features = vhost_user_blk_get_features; + vdev->vdev_class->set_status = vhost_user_blk_set_status; + vdev->vdev_class->reset = vhost_user_blk_reset; +} + + +void vhost_user_blk_init(VirtIODevice *vdev) +{ + + DBG("vhost_user_blk_init\n"); + + VHostUserBlk *vhublk = (VHostUserBlk *)malloc(sizeof(VHostUserBlk)); + vdev->vhublk = vhublk; + vhublk->parent = vdev; + vhublk->virtqs = vdev->vqs; + vhublk->vhost_dev = dev; + + virtio_dev_class_init(vdev); + virtio_loopback_bus_init(vdev->vbus); +} + + +static void vhost_user_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq) +{ + /* + * Not normally called; it's the daemon that handles the queue; + * however virtio's cleanup path can call this. + */ + DBG("vhost_user_blk_handle_output not yet implemented\n"); +} + + +void print_config(uint8_t *config) +{ + struct virtio_blk_config *config_strct = (struct virtio_blk_config *)config; + + DBG("uint64_t capacity: %llu\n", config_strct->capacity); + DBG("uint32_t size_max: %u\n", config_strct->size_max); + DBG("uint32_t seg_max: %u\n", config_strct->seg_max); + + DBG("virtio_blk_geometry:\n"); + DBG(" uint16_t cylinders: %u\n", + config_strct->geometry.cylinders); + DBG(" uint8_t heads: %u\n", + config_strct->geometry.heads); + DBG(" uint8_t sectors: %u\n", + config_strct->geometry.sectors); + + DBG("uint32_t blk_size: %u\n", config_strct->blk_size); + DBG("uint8_t physical_block_exp: %u\n", + config_strct->physical_block_exp); + DBG("uint8_t alignment_offset: %u\n", + config_strct->alignment_offset); + DBG("uint16_t min_io_size: %u\n", config_strct->min_io_size); + DBG("uint32_t opt_io_size: %u\n", config_strct->opt_io_size); + DBG("uint8_t wce: %u\n", config_strct->wce); + DBG("uint8_t unused: %u\n", config_strct->unused); + DBG("uint16_t num_queues: %u\n", config_strct->num_queues); + DBG("uint32_t max_discard_sectors: %u\n", + config_strct->max_discard_sectors); + DBG("uint32_t max_discard_seg: %u\n", config_strct->max_discard_seg); + DBG("uint32_t discard_sector_alignment: %u\n", + config_strct->discard_sector_alignment); + DBG("uint32_t max_write_zeroes_sectors: %u\n", + config_strct->max_write_zeroes_sectors); + DBG("uint32_t max_write_zeroes_seg: %u\n", + config_strct->max_write_zeroes_seg); + DBG("uint8_t write_zeroes_may_unmap: %u\n", + config_strct->write_zeroes_may_unmap); + DBG("uint8_t unused1[3]: %u\n", config_strct->unused1[0]); + DBG("uint8_t unused1[3]: %u\n", config_strct->unused1[1]); + DBG("uint8_t unused1[3]: %u\n", config_strct->unused1[2]); +} + +void vhost_user_blk_realize(void) +{ + int retries; + int i, ret; + + DBG("vhost_user_blk_realize\n"); + + /* This needs to be added */ + proxy = (VirtIOMMIOProxy *)malloc(sizeof(VirtIOMMIOProxy)); + *proxy = (VirtIOMMIOProxy) { + .legacy = 1, + }; + + /* VIRTIO_ID_BLOCK is 2, check virtio_ids.h in linux */ + virtio_dev_init(global_vdev, "virtio-blk", 2, + sizeof(struct virtio_blk_config)); + + vhost_user_blk_init(global_vdev); + + /* FIXME: We temporarily hardcoded the vrtqueues number */ + global_vdev->vhublk->num_queues = 1; + + /* FIXME: We temporarily hardcoded the vrtqueues size */ + global_vdev->vhublk->queue_size = 128; + + /* NOTE: global_vdev->vqs == vhublk->virtqs */ + global_vdev->vqs = (VirtQueue **)malloc(sizeof(VirtQueue *) + * global_vdev->vhublk->num_queues); + for (i = 0; i < global_vdev->vhublk->num_queues; i++) { + global_vdev->vqs[i] = virtio_add_queue(global_vdev, + global_vdev->vhublk->queue_size, + vhost_user_blk_handle_output); + } + + global_vdev->vhublk->inflight = (struct vhost_inflight *)malloc( + sizeof(struct vhost_inflight)); + global_vdev->vhublk->vhost_vqs = (struct vhost_virtqueue *)malloc( + sizeof(struct vhost_virtqueue) * + global_vdev->vhublk->num_queues); + + retries = REALIZE_CONNECTION_RETRIES; + + do { + ret = vhost_user_blk_realize_connect(global_vdev->vhublk); + } while (ret < 0 && retries--); + + if (ret < 0) { + DBG("vhost_user_blk_realize_connect: -EPROTO\n"); + } + + DBG("final global_vdev->host_features: 0x%lx\n", + global_vdev->host_features); + + print_config((uint8_t *)(&global_vdev->vhublk->blkcfg)); + + return; + +} + |