Commit 542b5415 authored by Dariusz Stojaczyk's avatar Dariusz Stojaczyk Committed by Ben Walker
Browse files

vhost_scsi: implemented abstract vhost event layer



Implemented abstract vhost event layer on top of virtio eventq.
This allows us to defer virtio-event-related actions to be called from
the vhost poller thread. This is required for hot-attach, for which
io_channels have to be created on the I/O thread.

This adds full support for hot-attaching and hot-detaching vhost scsi
devices. They can be now attached to a controller via RPC at any time.

Change-Id: Icf353bfcf69c83ef16b8fc771ea4c487002094f9
Signed-off-by: default avatarDariusz Stojaczyk <dariuszx.stojaczyk@intel.com>
Signed-off-by: default avatarPawel Kaminski <pawelx.kaminski@intel.com>
Reviewed-on: https://review.gerrithub.io/370016


Reviewed-by: default avatarPawel Wodkowski <pawelx.wodkowski@intel.com>
Tested-by: default avatarSPDK Automated Test System <sys_sgsw@intel.com>
Reviewed-by: default avatarDaniel Verkamp <daniel.verkamp@intel.com>
Reviewed-by: default avatarBen Walker <benjamin.walker@intel.com>
parent 40b74cd3
Loading
Loading
Loading
Loading
+205 −90
Original line number Diff line number Diff line
@@ -46,6 +46,8 @@
#include "spdk/vhost.h"
#include "vhost_internal.h"

#include "spdk_internal/assert.h"

/* Features supported by SPDK VHOST lib. */
#define SPDK_VHOST_SCSI_FEATURES	((1ULL << VIRTIO_F_VERSION_1) | \
					(1ULL << VHOST_F_LOG_ALL) | \
@@ -75,13 +77,13 @@
struct spdk_vhost_scsi_dev {
	struct spdk_vhost_dev vdev;
	struct spdk_scsi_dev *scsi_dev[SPDK_VHOST_SCSI_CTRLR_MAX_DEVS];
	bool removed_dev[SPDK_VHOST_SCSI_CTRLR_MAX_DEVS];
	bool detached_dev[SPDK_VHOST_SCSI_CTRLR_MAX_DEVS];

	struct spdk_ring *task_pool;
	struct spdk_poller *requestq_poller;
	struct spdk_poller *mgmt_poller;

	struct spdk_ring *eventq_ring;
	struct spdk_ring *vhost_events;
} __rte_cache_aligned;

struct spdk_vhost_scsi_task {
@@ -101,6 +103,18 @@ struct spdk_vhost_scsi_task {
	struct rte_vhost_vring *vq;
};

enum spdk_vhost_scsi_event_type {
	SPDK_VHOST_SCSI_EVENT_HOTATTACH,
	SPDK_VHOST_SCSI_EVENT_HOTDETACH,
};

struct spdk_vhost_scsi_event {
	enum spdk_vhost_scsi_event_type type;
	unsigned dev_index;
	struct spdk_scsi_dev *dev;
	struct spdk_scsi_lun *lun;
};

static int new_device(int vid);
static void destroy_device(int vid);

@@ -147,36 +161,102 @@ spdk_vhost_get_tasks(struct spdk_vhost_scsi_dev *svdev, struct spdk_vhost_scsi_t
}

static void
process_eventq(struct spdk_vhost_scsi_dev *svdev)
spdk_vhost_scsi_event_process(struct spdk_vhost_scsi_dev *svdev, struct spdk_vhost_scsi_event *ev,
			      struct virtio_scsi_event *desc_ev)
{
	int event_id, reason_id;
	int dev_id, lun_id;

	assert(ev->dev);
	assert(ev->dev_index < SPDK_VHOST_SCSI_CTRLR_MAX_DEVS);
	dev_id = ev->dev_index;

	switch (ev->type) {
	case SPDK_VHOST_SCSI_EVENT_HOTATTACH:
		event_id = VIRTIO_SCSI_T_TRANSPORT_RESET;
		reason_id = VIRTIO_SCSI_EVT_RESET_RESCAN;

		spdk_scsi_dev_allocate_io_channels(ev->dev);
		svdev->scsi_dev[dev_id] = ev->dev;
		svdev->detached_dev[dev_id] = false;
		SPDK_NOTICELOG("%s: hot-attached device %d\n", svdev->vdev.name, dev_id);
		break;
	case SPDK_VHOST_SCSI_EVENT_HOTDETACH:
		event_id = VIRTIO_SCSI_T_TRANSPORT_RESET;
		reason_id = VIRTIO_SCSI_EVT_RESET_REMOVED;

		if (ev->lun == NULL) {
			svdev->detached_dev[dev_id] = true;
			SPDK_NOTICELOG("%s: marked 'Dev %d' for hot-detach\n", svdev->vdev.name, dev_id);
		} else {
			SPDK_NOTICELOG("%s: hotremoved LUN '%s'\n", svdev->vdev.name, spdk_scsi_lun_get_name(ev->lun));
		}
		break;
	default:
		SPDK_UNREACHABLE();
	}

	/* some events may apply to the entire device via lun id set to 0 */
	lun_id = ev->lun == NULL ? 0 : spdk_scsi_lun_get_id(ev->lun);

	if (desc_ev) {
		desc_ev->event = event_id;
		desc_ev->lun[0] = 1;
		desc_ev->lun[1] = dev_id;
		desc_ev->lun[2] = lun_id >> 8; /* relies on linux kernel implementation */
		desc_ev->lun[3] = lun_id & 0xFF;
		memset(&desc_ev->lun[4], 0, 4);
		desc_ev->reason = reason_id;
	}
}

/**
 * Process vhost event, send virtio event
 */
static void
process_event(struct spdk_vhost_scsi_dev *svdev, struct spdk_vhost_scsi_event *ev)
{
	struct rte_vhost_vring *vq;
	struct vring_desc *desc;
	struct virtio_scsi_event *ev, *desc_ev;
	struct virtio_scsi_event *desc_ev;
	uint32_t req_size;
	uint16_t req;
	struct rte_vhost_vring *vq = &svdev->vdev.virtqueue[VIRTIO_SCSI_EVENTQ];

	vq = &svdev->vdev.virtqueue[VIRTIO_SCSI_EVENTQ];

	while (spdk_ring_dequeue(svdev->eventq_ring, (void **)&ev, 1) == 1) {
	if (spdk_vhost_vq_avail_ring_get(vq, &req, 1) != 1) {
			SPDK_ERRLOG("Controller %s: Failed to send virtio event (no avail ring entries?).\n",
		SPDK_ERRLOG("%s: no avail virtio eventq ring entries. virtio event won't be sent.\n",
			    svdev->vdev.name);
			spdk_dma_free(ev);
			break;
		}

		desc = NULL;
		desc_ev = NULL;
		req_size = 0;
		/* even though we can't send virtio event,
		 * the spdk vhost event should still be processed
		 */
	} else {
		desc =  spdk_vhost_vq_get_desc(vq, req);
		desc_ev = spdk_vhost_gpa_to_vva(&svdev->vdev, desc->addr);

		if (desc->len >= sizeof(*desc_ev) && desc_ev != NULL) {
		req_size = sizeof(*desc_ev);
			memcpy(desc_ev, ev, sizeof(*desc_ev));
		} else {
			SPDK_ERRLOG("Controller %s: Invalid eventq descriptor.\n", svdev->vdev.name);

		if (desc->len < sizeof(*desc_ev) || desc_ev == NULL) {
			SPDK_ERRLOG("%s: invalid eventq descriptor.\n", svdev->vdev.name);
			desc_ev = NULL;
			req_size = 0;
		}
	}

	spdk_vhost_scsi_event_process(svdev, ev, desc_ev);

	if (desc) {
		spdk_vhost_vq_used_ring_enqueue(&svdev->vdev, vq, req, req_size);
	}
}

static void
process_eventq(struct spdk_vhost_scsi_dev *svdev)
{
	struct spdk_vhost_scsi_event *ev;

	while (spdk_ring_dequeue(svdev->vhost_events, (void **)&ev, 1) == 1) {
		process_event(svdev, ev);
		spdk_dma_free(ev);
	}
}
@@ -190,55 +270,39 @@ process_removed_devs(struct spdk_vhost_scsi_dev *svdev)
	for (i = 0; i < SPDK_VHOST_SCSI_CTRLR_MAX_DEVS; ++i) {
		dev = svdev->scsi_dev[i];

		if (dev && svdev->removed_dev[i] && !spdk_scsi_dev_has_pending_tasks(dev)) {
		if (dev && svdev->detached_dev[i] && !spdk_scsi_dev_has_pending_tasks(dev)) {
			spdk_scsi_dev_free_io_channels(dev);
			spdk_scsi_dev_destruct(dev);
			svdev->scsi_dev[i] = NULL;
			SPDK_NOTICELOG("%s: hotremoved device 'Dev %u'.\n", svdev->vdev.name, i);
			SPDK_NOTICELOG("%s: hot-detached 'Dev %d'.\n", svdev->vdev.name, i);
		}
	}
}

static void
eventq_enqueue(struct spdk_vhost_scsi_dev *svdev, const struct spdk_scsi_dev *dev,
	       const struct spdk_scsi_lun *lun, uint32_t event, uint32_t reason)
enqueue_vhost_event(struct spdk_vhost_scsi_dev *svdev, enum spdk_vhost_scsi_event_type type,
		    int dev_index, struct spdk_scsi_dev *dev, struct spdk_scsi_lun *lun)
{
	struct virtio_scsi_event *ev;
	int dev_id, lun_id;
	struct spdk_vhost_scsi_event *ev;

	if (dev == NULL) {
		SPDK_ERRLOG("%s: eventq device cannot be NULL.\n", svdev->vdev.name);
		SPDK_ERRLOG("%s: vhost event device cannot be NULL.\n", svdev->vdev.name);
		return;
	}

	for (dev_id = 0; dev_id < SPDK_VHOST_SCSI_CTRLR_MAX_DEVS; dev_id++) {
		if (svdev->scsi_dev[dev_id] == dev) {
			break;
		}
	}

	if (dev_id == SPDK_VHOST_SCSI_CTRLR_MAX_DEVS) {
		SPDK_ERRLOG("Dev %s is not a part of vhost scsi controller '%s'.\n", spdk_scsi_dev_get_name(dev),
			    svdev->vdev.name);
	ev = spdk_dma_zmalloc(sizeof(*ev), SPDK_CACHE_LINE_SIZE, NULL);
	if (ev == NULL) {
		SPDK_ERRLOG("%s: failed to alloc vhost event.\n", svdev->vdev.name);
		return;
	}

	/* some events may apply to the entire device via lun id set to 0 */
	lun_id = lun == NULL ? 0 : spdk_scsi_lun_get_id(lun);
	ev->type = type;
	ev->dev_index = dev_index;
	ev->dev = dev;
	ev->lun = lun;

	ev = spdk_dma_zmalloc(sizeof(*ev), SPDK_CACHE_LINE_SIZE, NULL);
	assert(ev);

	ev->event = event;
	ev->lun[0] = 1;
	ev->lun[1] = dev_id;
	ev->lun[2] = lun_id >> 8; /* relies on linux kernel implementation */
	ev->lun[3] = lun_id & 0xFF;
	ev->reason = reason;

	if (spdk_ring_enqueue(svdev->eventq_ring, (void **)&ev, 1) != 1) {
		SPDK_ERRLOG("Controller %s: Failed to inform guest about LUN #%d removal (no room in ring?).\n",
			    svdev->vdev.name, lun_id);
	if (spdk_ring_enqueue(svdev->vhost_events, (void **)&ev, 1) != 1) {
		SPDK_ERRLOG("%s: failed to enqueue vhost event (no room in ring?).\n", svdev->vdev.name);
		spdk_dma_free(ev);
	}
}
@@ -329,10 +393,10 @@ spdk_vhost_scsi_task_init_target(struct spdk_vhost_scsi_task *task, const __u8 *
	dev = task->svdev->scsi_dev[lun[1]];
	task->scsi_dev = dev;
	if (dev == NULL) {
		/* If dev has been hotremoved, return 0 to allow sending additional
		 * hotremove event via sense codes.
		/* If dev has been hot-detached, return 0 to allow sending additional
		 * scsi hotremove event via sense codes.
		 */
		return task->svdev->removed_dev[lun[1]] ? 0 : -1;
		return task->svdev->detached_dev[lun[1]] ? 0 : -1;
	}

	task->scsi.target_port = spdk_scsi_dev_find_port_by_id(task->scsi_dev, 0);
@@ -658,7 +722,6 @@ static void
remove_vdev_cb(void *arg)
{
	struct spdk_vhost_scsi_dev *svdev = arg;
	void *ev;
	uint32_t i;

	for (i = 0; i < SPDK_VHOST_SCSI_CTRLR_MAX_DEVS; i++) {
@@ -670,11 +733,6 @@ remove_vdev_cb(void *arg)

	SPDK_NOTICELOG("Stopping poller for vhost controller %s\n", svdev->vdev.name);
	spdk_vhost_dev_mem_unregister(&svdev->vdev);

	/* Cleanup not sent events */
	while (spdk_ring_dequeue(svdev->eventq_ring, &ev, 1) == 1) {
		spdk_dma_free(ev);
	}
}

static struct spdk_vhost_scsi_dev *
@@ -704,17 +762,11 @@ spdk_vhost_scsi_dev_construct(const char *name, uint64_t cpumask)
		return -ENOMEM;
	}

	svdev->eventq_ring = spdk_ring_create(SPDK_RING_TYPE_MP_SC, 16, SOCKET_ID_ANY);
	if (svdev->eventq_ring == NULL) {
		spdk_dma_free(svdev);
		return -ENOMEM;
	}

	rc = spdk_vhost_dev_construct(&svdev->vdev, name, cpumask, SPDK_VHOST_DEV_T_SCSI,
				      &spdk_vhost_scsi_device_backend);

	if (rc) {
		spdk_ring_free(svdev->eventq_ring);
		spdk_ring_free(svdev->vhost_events);
		spdk_dma_free(svdev);
		return rc;
	}
@@ -743,7 +795,7 @@ spdk_vhost_scsi_dev_remove(struct spdk_vhost_dev *vdev)
		return -EIO;
	}

	spdk_ring_free(svdev->eventq_ring);
	spdk_ring_free(svdev->vhost_events);
	spdk_dma_free(svdev);
	return 0;
}
@@ -763,16 +815,35 @@ static void
spdk_vhost_scsi_lun_hotremove(const struct spdk_scsi_lun *lun, void *arg)
{
	struct spdk_vhost_scsi_dev *svdev = arg;
	const struct spdk_scsi_dev *scsi_dev;
	unsigned scsi_dev_num;

	assert(lun != NULL);
	assert(svdev != NULL);
	if ((svdev->vdev.negotiated_features & (1ULL << VIRTIO_SCSI_F_HOTPLUG)) == 0) {
		SPDK_WARNLOG("Controller %s: hotremove is not supported\n", svdev->vdev.name);
		SPDK_WARNLOG("%s: hotremove is not enabled for this controller.\n", svdev->vdev.name);
		return;
	}

	scsi_dev = spdk_scsi_lun_get_dev(lun);

	for (scsi_dev_num = 0; scsi_dev_num < SPDK_VHOST_SCSI_CTRLR_MAX_DEVS; ++scsi_dev_num) {
		if (svdev->scsi_dev[scsi_dev_num] == scsi_dev) {
			break;
		}
	}

	if (scsi_dev_num == SPDK_VHOST_SCSI_CTRLR_MAX_DEVS) {
		SPDK_ERRLOG("%s: '%s' is not a part of this controller.\n", svdev->vdev.name,
			    spdk_scsi_dev_get_name(scsi_dev));
		return;
	}

	eventq_enqueue(svdev, spdk_scsi_lun_get_dev(lun), lun, VIRTIO_SCSI_T_TRANSPORT_RESET,
		       VIRTIO_SCSI_EVT_RESET_REMOVED);
	enqueue_vhost_event(svdev, SPDK_VHOST_SCSI_EVENT_HOTDETACH, scsi_dev_num,
			    (struct spdk_scsi_dev *) scsi_dev, (struct spdk_scsi_lun *) lun);

	SPDK_NOTICELOG("%s: queued LUN '%s' for hotremove\n", svdev->vdev.name,
		       spdk_scsi_dev_get_name(scsi_dev));
}

int
@@ -780,6 +851,7 @@ spdk_vhost_scsi_dev_add_dev(const char *ctrlr_name, unsigned scsi_dev_num, const
{
	struct spdk_vhost_scsi_dev *svdev;
	struct spdk_vhost_dev *vdev;
	struct spdk_scsi_dev *scsi_dev;
	char dev_name[SPDK_SCSI_DEV_MAX_NAME];
	int lun_id_list[1];
	char *lun_names_list[1];
@@ -814,9 +886,10 @@ spdk_vhost_scsi_dev_add_dev(const char *ctrlr_name, unsigned scsi_dev_num, const
		return -EINVAL;
	}

	if (vdev->lcore != -1) {
		SPDK_ERRLOG("Controller %s is in use and hotplug is not supported\n", ctrlr_name);
		return -ENODEV;
	if (vdev->lcore != -1 && (svdev->vdev.negotiated_features & (1ULL << VIRTIO_SCSI_F_HOTPLUG)) == 0) {
		SPDK_ERRLOG("%s: 'Dev %u' is in use and hot-attach is not enabled for this controller\n",
			    ctrlr_name, scsi_dev_num);
		return -ENOTSUP;
	}

	if (svdev->scsi_dev[scsi_dev_num] != NULL) {
@@ -831,16 +904,23 @@ spdk_vhost_scsi_dev_add_dev(const char *ctrlr_name, unsigned scsi_dev_num, const
	lun_id_list[0] = 0;
	lun_names_list[0] = (char *)lun_name;

	svdev->removed_dev[scsi_dev_num] = false;
	svdev->scsi_dev[scsi_dev_num] = spdk_scsi_dev_construct(dev_name, lun_names_list, lun_id_list, 1,
	scsi_dev = spdk_scsi_dev_construct(dev_name, lun_names_list, lun_id_list, 1,
					   SPDK_SPC_PROTOCOL_IDENTIFIER_SAS, spdk_vhost_scsi_lun_hotremove, svdev);

	if (svdev->scsi_dev[scsi_dev_num] == NULL) {
	if (scsi_dev == NULL) {
		SPDK_ERRLOG("Couldn't create spdk SCSI device '%s' using lun device '%s' in controller: %s\n",
			    dev_name, lun_name, vdev->name);
		return -EINVAL;
	}
	spdk_scsi_dev_add_port(svdev->scsi_dev[scsi_dev_num], 0, "vhost");

	spdk_scsi_dev_add_port(scsi_dev, 0, "vhost");

	if (vdev->lcore == -1) {
		svdev->detached_dev[scsi_dev_num] = false;
		svdev->scsi_dev[scsi_dev_num] = scsi_dev;
	} else {
		enqueue_vhost_event(svdev, SPDK_VHOST_SCSI_EVENT_HOTATTACH, scsi_dev_num, scsi_dev, NULL);
	}

	SPDK_NOTICELOG("Controller %s: defined device '%s' using lun '%s'\n",
		       vdev->name, dev_name, lun_name);
	return 0;
@@ -877,14 +957,14 @@ spdk_vhost_scsi_dev_remove_dev(struct spdk_vhost_dev *vdev, unsigned scsi_dev_nu
	}

	if ((svdev->vdev.negotiated_features & (1ULL << VIRTIO_SCSI_F_HOTPLUG)) == 0) {
		SPDK_WARNLOG("Controller %s: hotremove is not supported\n", svdev->vdev.name);
		SPDK_WARNLOG("%s: 'Dev %u' is in use and hot-detach is not enabled for this controller.\n",
			     svdev->vdev.name, scsi_dev_num);
		return -ENOTSUP;
	}

	svdev->removed_dev[scsi_dev_num] = true;
	eventq_enqueue(svdev, scsi_dev, NULL, VIRTIO_SCSI_T_TRANSPORT_RESET, VIRTIO_SCSI_EVT_RESET_REMOVED);
	enqueue_vhost_event(svdev, SPDK_VHOST_SCSI_EVENT_HOTDETACH, scsi_dev_num, scsi_dev, NULL);

	SPDK_NOTICELOG("%s: 'Dev %u' marked for hotremove.\n", vdev->name, scsi_dev_num);
	SPDK_NOTICELOG("%s: queued 'Dev %u' for hot-detach.\n", vdev->name, scsi_dev_num);
	return 0;
}

@@ -1026,20 +1106,45 @@ alloc_task_pool(struct spdk_vhost_scsi_dev *svdev)
static int
new_device(int vid)
{
	struct spdk_vhost_dev *vdev;
	struct spdk_vhost_scsi_dev *svdev = NULL;
	int rc = -1;

	svdev = to_scsi_dev(spdk_vhost_dev_load(vid));
	if (svdev == NULL) {
	vdev = spdk_vhost_dev_load(vid);
	if (vdev == NULL) {
		SPDK_ERRLOG("Trying start a controller with unknown vid: %d.\n", vid);
		return -1;
	}

	if (alloc_task_pool(svdev)) {
	svdev = to_scsi_dev(vdev);
	if (svdev == NULL) {
		SPDK_ERRLOG("Trying to start non-scsi controller as scsi one.\n");
		goto out;
	}

	rc = alloc_task_pool(svdev);
	if (rc != 0) {
		SPDK_ERRLOG("%s: failed to alloc task pool.", vdev->name);
		goto out;
	}

	svdev->vhost_events = spdk_ring_create(SPDK_RING_TYPE_MP_SC, 16,
					       spdk_env_get_socket_id(vdev->lcore));
	if (svdev->vhost_events == NULL) {
		SPDK_ERRLOG("%s: failed to alloc event pool.", vdev->name);
		goto out;
	}

	spdk_vhost_timed_event_send(vdev->lcore, add_vdev_cb, svdev, 1, "add scsi vdev");

	rc = 0;

out:
	if (rc != 0) {
		spdk_vhost_dev_unload(&svdev->vdev);
		return -1;
	}

	spdk_vhost_timed_event_send(svdev->vdev.lcore, add_vdev_cb, svdev, 1, "add scsi vdev");
	return 0;
	return rc;
}

static void
@@ -1047,6 +1152,7 @@ destroy_device(int vid)
{
	struct spdk_vhost_scsi_dev *svdev;
	struct spdk_vhost_dev *vdev;
	void *ev;
	struct spdk_vhost_timed_event event = {0};
	uint32_t i;

@@ -1076,6 +1182,15 @@ destroy_device(int vid)

	spdk_vhost_timed_event_send(vdev->lcore, remove_vdev_cb, svdev, 1, "remove scsi vdev");

	/* Flush not sent events */
	while (spdk_ring_dequeue(svdev->vhost_events, &ev, 1) == 1) {
		/* process vhost event, but don't send virtio event */
		spdk_vhost_scsi_event_process(svdev, ev, NULL);
		spdk_dma_free(ev);
	}

	spdk_ring_free(svdev->vhost_events);

	free_task_pool(svdev);
	spdk_vhost_dev_unload(vdev);
}
+20 −0
Original line number Diff line number Diff line
@@ -208,6 +208,26 @@ done
$BASE_DIR/vm_run.sh $x --work-dir=$TEST_DIR $used_vms
vm_wait_for_boot 600 $used_vms

if [[ $test_type == "spdk_vhost_scsi" ]]; then
	for vm_conf in ${vms[@]}; do
		IFS=',' read -ra conf <<< "$vm_conf"
		while IFS=':' read -ra disks; do
			for disk in "${disks[@]}"; do
				echo "INFO: Hotdetach test. Trying to remove existing device from a controller naa.$disk.${conf[0]}"
				$rpc_py remove_vhost_scsi_dev naa.$disk.${conf[0]} 0

				sleep 0.1

				echo "INFO: Hotattach test. Re-adding device 0 to naa.$disk.${conf[0]}"
				$rpc_py add_vhost_scsi_lun naa.$disk.${conf[0]} 0 $disk
			done
		done <<< "${conf[2]}"
		unset IFS;
	done
fi

sleep 0.1

echo "==============="
echo ""
echo "INFO: Testing..."