[dpdk-dev] [PATCH v3] examples/vhost: introduce a new vhost-user-scsi sample application

Wodkowski, PawelX pawelx.wodkowski at intel.com
Wed Jul 12 16:49:38 CEST 2017


Hi, sorry for late review but just spotted this patch.
Please see my comments

> diff --git a/examples/vhost_scsi/scsi.c b/examples/vhost_scsi/scsi.c
> new file mode 100644
> index 0000000..08dfe61
> --- /dev/null
> +++ b/examples/vhost_scsi/scsi.c
> @@ -0,0 +1,507 @@

> +static int
> +vhost_hex2bin(char ch)
> +{
> +	if ((ch >= '0') && (ch <= '9'))
> +		return ch - '0';
> +	ch = tolower(ch);
> +	if ((ch >= 'a') && (ch <= 'f'))
> +		return ch - 'a' + 10;
> +	return (int)ch;
> +}

consider using strtol()

> +
> +int
> +vhost_bdev_process_scsi_commands(struct vhost_block_dev *bdev,
> +				 struct vhost_scsi_task *task)
> +{
> +	int len;
> +	uint8_t *data;
> +	uint64_t fmt_lun = 0;
> +	const uint8_t *lun;
> +	uint8_t *cdb = (uint8_t *)task->req->cdb;
> +
> +	lun = (const uint8_t *)task->req->lun;
> +	/* only 1 LUN supported */
> +	if (lun[0] != 1 || lun[1] >= 1)
> +		return -1;
> +
> +	switch (cdb[0]) {
> +	case SPC_INQUIRY:
> +		len = vhost_bdev_scsi_inquiry_command(bdev, task);
> +		task->data_len = len;
> +		break;
> +	case SPC_REPORT_LUNS:
> +		data = (uint8_t *)task->iovs[0].iov_base;
> +		fmt_lun |= (0x0ULL & 0x00ffULL) << 48;

Please provide a description for this magical formula for non-SCSI proficient community.

> +		to_be64((void *)&data[8], fmt_lun);
> +		to_be32((void *)data, 8);
> +		task->data_len = 16;
> +		scsi_task_set_status(task, SCSI_STATUS_GOOD, 0, 0, 0);
> +		break;
> +	case SPC_MODE_SELECT_6:
> +	case SPC_MODE_SELECT_10:
> +		/* TODO: Add SCSI Commands support */
> +		scsi_task_set_status(task, SCSI_STATUS_GOOD, 0, 0, 0);
> +		break;
> +	case SPC_MODE_SENSE_6:
> +	case SPC_MODE_SENSE_10:
> +		/* TODO: Add SCSI Commands support */
> +		scsi_task_set_status(task, SCSI_STATUS_GOOD, 0, 0, 0);
> +		break;

If those cases are mandatory they should be implemented if not removing TODO would be better than informing that
there is something to do.

> +	case SPC_TEST_UNIT_READY:
> +		scsi_task_set_status(task, SCSI_STATUS_GOOD, 0, 0, 0);
> +		break;
> +	default:
> +		len = vhost_bdev_scsi_process_block(bdev, task);
> +		task->data_len = len;
> +	}
> +
> +	return 0;
> +}
> diff --git a/examples/vhost_scsi/scsi_spec.h b/examples/vhost_scsi/scsi_spec.h
> new file mode 100644
> index 0000000..0474d92
> --- /dev/null
> +++ b/examples/vhost_scsi/scsi_spec.h
> @@ -0,0 +1,488 @@

Most of the scsi_spec.h file is unused, please remove unnecessary parts. 

> diff --git a/examples/vhost_scsi/vhost_scsi.c
> b/examples/vhost_scsi/vhost_scsi.c
> new file mode 100644
> index 0000000..160c2e0
> --- /dev/null
> +++ b/examples/vhost_scsi/vhost_scsi.c
> @@ -0,0 +1,472 @@

> +
> +#define VIRTIO_SCSI_FEATURES ((1 << VIRTIO_F_NOTIFY_ON_EMPTY) |\
> +			      (1 << VIRTIO_RING_F_EVENT_IDX) |\

Am I missing something or this example app doesn't support EVENT_IDX?

> +			      (1 << VIRTIO_SCSI_F_INOUT) |\
> +			      (1 << VIRTIO_SCSI_F_CHANGE))
> +
> +/* Path to folder where character device will be created. Can be set by user.
> */
> +static char dev_pathname[PATH_MAX] = "";
> +
> +static struct vhost_scsi_ctrlr *g_vhost_ctrlr;
> +static int g_should_stop;
> +static sem_t exit_sem;
> +
> +#define NUM_OF_SCSI_QUEUES 3
> +
> +static struct vhost_scsi_ctrlr *
> +vhost_scsi_ctrlr_find(__rte_unused const char *ctrlr_name)
> +{
> +	/* currently we only support 1 socket file fd */

So remove this function.

> +	return g_vhost_ctrlr;
> +}
> +
> +static uint64_t gpa_to_vva(int vid, uint64_t gpa)
> +{
> +	char path[PATH_MAX];
> +	struct vhost_scsi_ctrlr *ctrlr;
> +	int ret = 0;
> +
> +	ret = rte_vhost_get_ifname(vid, path, PATH_MAX);
> +	if (ret) {
> +		fprintf(stderr, "Cannot get socket name\n");
> +		assert(ret != 0);
> +	}
> +
> +	ctrlr = vhost_scsi_ctrlr_find(path);
> +	if (!ctrlr) {
> +		fprintf(stderr, "Controller is not ready\n");
> +		assert(ctrlr != NULL);
> +	}
> +
> +	assert(ctrlr->mem != NULL);
> +
> +	return rte_vhost_gpa_to_vva(ctrlr->mem, gpa);
> +}
> +

Replace vid in  vhost_block_dev by pointer to struct vhost_scsi_ctrlr
This way you will not need this whole function at all.

> +static struct vring_desc *
> +descriptor_get_next(struct vring_desc *vq_desc, struct vring_desc
> *cur_desc)
> +{
> +	return &vq_desc[cur_desc->next];
> +}
> +
> +static bool
> +descriptor_has_next(struct vring_desc *cur_desc)
> +{
> +	return !!(cur_desc->flags & VRING_DESC_F_NEXT);
> +}
> +
> +static bool
> +descriptor_is_wr(struct vring_desc *cur_desc)
> +{
> +	return !!(cur_desc->flags & VRING_DESC_F_WRITE);
> +}

I have an impression that all those functions should go to library instead of example application.

> +
> +static struct vhost_block_dev *
> +vhost_scsi_bdev_construct(const char *bdev_name, const char
> *bdev_serial,
> +			  uint32_t blk_size, uint64_t blk_cnt,
> +			  bool wce_enable)
> +{
> +	struct vhost_block_dev *bdev;
> +
> +	bdev = rte_zmalloc(NULL, sizeof(*bdev), RTE_CACHE_LINE_SIZE);
> +	if (!bdev)
> +		return NULL;
> +
> +	strncpy(bdev->name, bdev_name, sizeof(bdev->name));
> +	strncpy(bdev->product_name, bdev_serial, sizeof(bdev-
> >product_name));
> +	bdev->blocklen = blk_size;
> +	bdev->blockcnt = blk_cnt;
> +	bdev->write_cache = wce_enable;

write_cache is not used and should be removed.

> +
> +	/* use memory as disk storage space */
> +	bdev->data = rte_zmalloc(NULL, blk_cnt * blk_size, 0);
> +	if (!bdev->data) {
> +		fprintf(stderr, "no enough reseverd huge memory for
> disk\n");
> +		return NULL;
> +	}
> +
> +	return bdev;
> +}
> +
> +static void
> +process_requestq(struct vhost_scsi_ctrlr *ctrlr, uint32_t q_idx)
> +{
> +	int ret;
> +	struct vhost_scsi_queue *scsi_vq;
> +	struct rte_vhost_vring *vq;
> +
> +	scsi_vq = &ctrlr->bdev->queues[q_idx];
> +	vq = &scsi_vq->vq;
> +	ret = rte_vhost_get_vhost_vring(ctrlr->bdev->vid, q_idx, vq);
> +	assert(ret == 0);
> +
> +	while (vq->avail->idx != scsi_vq->last_used_idx) {
> +		int req_idx;
> +		uint16_t last_idx;
> +		struct vhost_scsi_task *task;
> +
> +		last_idx = scsi_vq->last_used_idx & (vq->size - 1);
> +		req_idx = vq->avail->ring[last_idx];
> +
> +		task = rte_zmalloc(NULL, sizeof(*task), 0);
> +		assert(task != NULL);
> +
> +		task->ctrlr = ctrlr;
> +		task->bdev = ctrlr->bdev;
> +		task->vq = vq;
> +		task->req_idx = req_idx;
> +		task->desc = &task->vq->desc[task->req_idx];
> +
> +		/* does not support indirect descriptors */
> +		assert((task->desc->flags & VRING_DESC_F_INDIRECT) == 0);
> +		scsi_vq->last_used_idx++;
> +
> +		task->req = (void *)gpa_to_vva(task->bdev->vid,
> +					       task->desc->addr);
> +
> +		task->desc = descriptor_get_next(task->vq->desc, task-
> >desc);
> +		if (!descriptor_has_next(task->desc)) {
> +			task->dxfer_dir = SCSI_DIR_NONE;
> +			task->resp = (void *)gpa_to_vva(task->bdev->vid,
> +							task->desc->addr);
> +
> +		} else if (!descriptor_is_wr(task->desc)) {
> +			task->dxfer_dir = SCSI_DIR_TO_DEV;
> +			vhost_process_write_payload_chain(task);
> +		} else {
> +			task->dxfer_dir = SCSI_DIR_FROM_DEV;
> +			vhost_process_read_payload_chain(task);
> +		}
> +
> +		ret = vhost_bdev_process_scsi_commands(ctrlr->bdev,
> task);
> +		if (ret) {
> +			/* invalid response */
> +			task->resp->response =
> VIRTIO_SCSI_S_BAD_TARGET;
> +		} else {
> +			/* successfully */
> +			task->resp->response = VIRTIO_SCSI_S_OK;
> +			task->resp->status = 0;

You need to fill resp->resid field here.

> +		}
> +		submit_completion(task);
> +		rte_free(task);
> +	}
> +}
> +
> +/* Main framework for processing IOs */
> +static void *
> +ctrlr_worker(void *arg)
> +{
> +	uint32_t idx, num;
> +	struct vhost_scsi_ctrlr *ctrlr = (struct vhost_scsi_ctrlr *)arg;
> +	cpu_set_t cpuset;
> +	pthread_t thread;
> +
> +	thread = pthread_self();
> +	CPU_ZERO(&cpuset);
> +	CPU_SET(0, &cpuset);
> +	pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
> +
> +	num =  rte_vhost_get_vring_num(ctrlr->bdev->vid);
> +	fprintf(stdout, "Ctrlr Worker Thread Started with %u Vring\n", num);
> +
> +	if (num != NUM_OF_SCSI_QUEUES) {

Again, how many queues doeas this app support 8 or NUM_OF_SCSI_QUEUES?

> +		fprintf(stderr, "Only 1 IO queue are supported\n");
> +		exit(0);
> +	}
> +
> +	while (!g_should_stop && ctrlr->bdev != NULL) {
> +		/* At least 3 vrings, currently only can support 1 IO queue
> +		 * Queue 2 for IO queue, does not support TMF and hotplug
> +		 * for the example application now
> +		 */
> +		for (idx = 2; idx < num; idx++)
> +			process_requestq(ctrlr, idx);
> +	}
> +
> +	fprintf(stdout, "Ctrlr Worker Thread Exiting\n");
> +	sem_post(&exit_sem);
> +	return NULL;
> +}
> +

> +
> +static struct vhost_scsi_ctrlr *
> +vhost_scsi_ctrlr_construct(const char *ctrlr_name)
> +{
> +	int ret;
> +	struct vhost_scsi_ctrlr *ctrlr;
> +	char *path;
> +	char cwd[PATH_MAX];
> +
> +	/* always use current directory */
> +	path = getcwd(cwd, PATH_MAX);
> +	if (!path) {
> +		fprintf(stderr, "Cannot get current working directory\n");
> +		return NULL;
> +	}
> +	snprintf(dev_pathname, sizeof(dev_pathname), "%s/%s", path,
> ctrlr_name);
> +
> +	if (access(dev_pathname, F_OK) != -1) {
> +		if (unlink(dev_pathname) != 0)
> +			rte_exit(EXIT_FAILURE, "Cannot remove %s.\n",
> +				 dev_pathname);
> +	}
> +
> +	fprintf(stdout, "socket file: %s\n", dev_pathname);
> +
> +	if (rte_vhost_driver_register(dev_pathname, 0) != 0) {
> +		fprintf(stderr, "socket %s already exists\n", dev_pathname);
> +		return NULL;
> +	}
> +
> +	ret = rte_vhost_driver_set_features(dev_pathname,
> VIRTIO_SCSI_FEATURES);
> +	if (ret) {
> +		fprintf(stderr, "Set vhost driver features failed\n");
> +		return NULL;
> +	}
> +
> +	ctrlr = rte_zmalloc(NULL, sizeof(*ctrlr), RTE_CACHE_LINE_SIZE);
> +	if (!ctrlr)
> +		return NULL;
> +
> +	ctrlr->name = strdup(ctrlr_name);

name is never used  and also never free.

> +
> +	rte_vhost_driver_callback_register(dev_pathname,
> +					   &vhost_scsi_device_ops);
> +
> +	return ctrlr;
> +}
> +
> +static void
> +signal_handler(__rte_unused int signum)
> +{

Some message would be good to inform about successful exit.

> +	if (access(dev_pathname, F_OK) == 0)
> +		unlink(dev_pathname);
> +	exit(0);
> +}
> +

> diff --git a/examples/vhost_scsi/vhost_scsi.h b/examples/vhost_scsi/vhost_scsi.h
> new file mode 100644
> index 0000000..b5340cc
> --- /dev/null
> +++ b/examples/vhost_scsi/vhost_scsi.h
> @@ -0,0 +1,250 @@

Do we need this file at all? I think it can got to .c file

> +
> +#include <rte_vhost.h>
> +
> +static inline uint16_t
> +from_be16(const void *ptr)
> +{
> +	const uint8_t *tmp = (const uint8_t *)ptr;
> +
> +	return (((uint16_t)tmp[0] << 8) | tmp[1]);
> +}
> +

Why implementing this instead of using existing macros from rte_byteorder.h?

> +
> +struct vaddr_region {
> +	void *vaddr;
> +	uint64_t len;
> +};

I don't see any usage of this struct.

> +
> +struct vhost_scsi_queue {
> +	struct rte_vhost_vring vq;
> +	uint16_t last_avail_idx;
> +	uint16_t last_used_idx;
> +};
> +
> +struct vhost_block_dev {
> +	/** ID for vhost library. */
> +	int vid;

Don't think this is right place for vid. As mentioned, pointer to struct vhost_scsi_ctrlr would be better here.

> +	/** Queues for the block device */
> +	struct vhost_scsi_queue queues[8];

You define 8 queues here but in vhost_scsi.c NUM_OF_SCSI_QUEUES is 3.
Maybe move definition of NUM_OF_SCSI_QUEUES to .h file and use it here instead of '8'

> +	/** Unique name for this block device. */
> +	char name[64];
> +
> +	/** Unique product name for this kind of block device. */
> +	char product_name[256];
> +
> +	/** Size in bytes of a logical block for the backend */
> +	uint32_t blocklen;
> +
> +	/** Number of blocks */
> +	uint64_t blockcnt;
> +
> +	/** write cache enabled, not used at the moment */
> +	int write_cache;

So can be removed.

> +
> +	/** use memory as disk storage space */
> +	uint8_t *data;
> +};
> +

Thanks
Pawel


More information about the dev mailing list