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: Dariusz Stojaczyk <dariuszx.stojaczyk@intel.com>
Signed-off-by: Pawel Kaminski <pawelx.kaminski@intel.com>
Reviewed-on: https://review.gerrithub.io/370016
Reviewed-by: Pawel Wodkowski <pawelx.wodkowski@intel.com>
Tested-by: SPDK Automated Test System <sys_sgsw@intel.com>
Reviewed-by: Daniel Verkamp <daniel.verkamp@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
This commit is contained in:
Dariusz Stojaczyk 2017-07-05 12:13:22 +02:00 committed by Ben Walker
parent 40b74cd343
commit 542b541588
2 changed files with 226 additions and 91 deletions

View File

@ -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);
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 = spdk_dma_zmalloc(sizeof(*ev), SPDK_CACHE_LINE_SIZE, NULL);
assert(ev);
if (ev == NULL) {
SPDK_ERRLOG("%s: failed to alloc vhost event.\n", svdev->vdev.name);
return;
}
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;
ev->type = type;
ev->dev_index = dev_index;
ev->dev = dev;
ev->lun = lun;
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;
}
eventq_enqueue(svdev, spdk_scsi_lun_get_dev(lun), lun, VIRTIO_SCSI_T_TRANSPORT_RESET,
VIRTIO_SCSI_EVT_RESET_REMOVED);
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;
}
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));
vdev = spdk_vhost_dev_load(vid);
if (vdev == NULL) {
SPDK_ERRLOG("Trying start a controller with unknown vid: %d.\n", vid);
return -1;
}
svdev = to_scsi_dev(vdev);
if (svdev == NULL) {
return -1;
SPDK_ERRLOG("Trying to start non-scsi controller as scsi one.\n");
goto out;
}
if (alloc_task_pool(svdev)) {
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);
}

View File

@ -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..."