* drivers/vhost/vhost-blk.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/kthread.h>
#include <nuttx/percpu.h>
#include <nuttx/semaphore.h>
#include <nuttx/vhost/vhost.h>
#include <nuttx/virtio/virtio-blk.h>
#include <nuttx/virtio/virtio-config.h>
#include "vhost-blk.h"
* Pre-processor Definitions
****************************************************************************/
* Private Types
****************************************************************************/
struct vhost_blk_priv_s
{
FAR struct vhost_device *hdev;
sem_t semrx;
pid_t pid;
struct file bdev;
};
* Private Function Prototypes
****************************************************************************/
static ssize_t vhost_blk_rdwr_handler(FAR struct vhost_blk_priv_s *priv,
FAR struct virtio_blk_req_s *req,
uint16_t idx, FAR uint16_t *next_idx,
bool write);
static int vhost_blk_probe(FAR struct vhost_device *vdev);
static void vhost_blk_remove(FAR struct vhost_device *vdev);
* Private Data
****************************************************************************/
static DEFINE_PER_CPU_BMP(struct vhost_driver, g_vhost_blk_driver) =
{
VIRTIO_ID_BLOCK,
vhost_blk_probe,
vhost_blk_remove,
};
#define g_vhost_blk_driver this_cpu_var_bmp(g_vhost_blk_driver)
* Private Functions
****************************************************************************/
* Name: vhost_blk_read_handler
****************************************************************************/
static ssize_t vhost_blk_rdwr_handler(FAR struct vhost_blk_priv_s *priv,
FAR struct virtio_blk_req_s *req,
uint16_t idx, FAR uint16_t *next_idx,
bool write)
{
FAR struct virtqueue *vq = priv->hdev->vrings_info[0].vq;
struct virtqueue_buf vb;
ssize_t ret = -EIO;
uint32_t len;
vb.buf = vhost_get_next_buffer(vq, idx, next_idx, &len);
if (vb.buf == NULL)
{
return -ENOBUFS;
}
if (write)
{
ret = file_write(&priv->bdev, vb.buf, len);
}
else
{
ret = file_read(&priv->bdev, vb.buf, len);
}
if (ret < 0)
{
vhosterr("%s error ret=%zd\n", write ? "Write" : "Read", ret);
}
vb.len = ret;
return ret;
}
* Name: vhost_blk_handler
****************************************************************************/
static int vhost_blk_handler(FAR struct vhost_blk_priv_s *priv,
FAR struct virtqueue *vq,
FAR void *buf, uint16_t idx)
{
FAR struct virtio_blk_req_s *req = (FAR struct virtio_blk_req_s *)buf;
FAR struct virtio_blk_resp_s *resp;
int ret = -EINVAL;
switch (req->type)
{
case VIRTIO_BLK_T_IN:
ret = vhost_blk_rdwr_handler(priv, req, idx, &idx, false);
break;
case VIRTIO_BLK_T_OUT:
ret = vhost_blk_rdwr_handler(priv, req, idx, &idx, true);
break;
case VIRTIO_BLK_T_FLUSH:
if (vhost_has_feature(priv->hdev, VIRTIO_BLK_F_FLUSH))
{
ret = file_ioctl(&priv->bdev, BIOC_FLUSH, 0);
}
break;
}
resp = vhost_get_next_buffer(vq, idx, NULL, NULL);
if (resp == NULL)
{
return -ENOBUFS;
}
resp->status = ret < 0 ? VIRTIO_BLK_S_IOERR : VIRTIO_BLK_S_OK;
return ret;
}
* Name: vhost_blk_work
****************************************************************************/
static void vhost_blk_work(FAR struct vhost_blk_priv_s *priv)
{
FAR struct virtqueue *vq;
FAR void *buf;
uint32_t len;
uint16_t idx;
int ret;
vq = priv->hdev->vrings_info[0].vq;
for (; ; )
{
buf = virtqueue_get_available_buffer(vq, &idx, &len);
if (buf == NULL)
{
break;
}
ret = vhost_blk_handler(priv, vq, buf, idx);
if (ret < 0)
{
vhosterr("handler failed, ret=%d\n", ret);
}
virtqueue_add_consumed_buffer(vq, idx, len);
}
virtqueue_kick(vq);
}
* Name: vhost_blk_thread
****************************************************************************/
static int vhost_blk_thread(int argc, FAR char *argv[])
{
FAR struct vhost_blk_priv_s *priv =
(FAR struct vhost_blk_priv_s *)((uintptr_t)strtoul(argv[1], NULL, 16));
while (1)
{
nxsem_wait_uninterruptible(&priv->semrx);
vhost_blk_work(priv);
}
return 0;
}
* Name: vhost_blk_callback
****************************************************************************/
static void vhost_blk_callback(FAR struct virtqueue *vq)
{
FAR struct vhost_blk_priv_s *priv = vq->vq_dev->priv;
int semcount;
nxsem_get_value(&priv->semrx, &semcount);
while (semcount++ < 1)
{
nxsem_post(&priv->semrx);
}
}
* Name: vhost_blk_probe
****************************************************************************/
static int vhost_blk_probe(FAR struct vhost_device *hdev)
{
FAR struct vhost_blk_priv_s *priv;
FAR const char *vqname[1];
vq_callback callback[1];
FAR char *argv[2];
char path[PATH_MAX];
char arg0[32];
int ret;
priv = kmm_zalloc(sizeof(*priv));
if (priv == NULL)
{
vhosterr("No enough memory\n");
return -ENOMEM;
}
priv->hdev = hdev;
hdev->priv = priv;
nxsem_init(&priv->semrx, 0, 0);
vhost_read_config_member(priv->hdev, struct vhost_blk_config_s,
path, &path);
ret = file_open(&priv->bdev, path, O_RDOK | O_WROK | O_CLOEXEC);
if (ret < 0)
{
vhosterr("ERROR: Failed to open block driver\n");
goto err_init;
}
vqname[0] = "virtio_blk";
callback[0] = vhost_blk_callback;
ret = vhost_create_virtqueues(hdev, 0, 1, vqname, callback);
if (ret < 0)
{
vhosterr("vhost_device_create_virtqueue failed, ret=%d\n", ret);
goto err_vq;
}
snprintf(arg0, sizeof(arg0), "%p", priv);
argv[0] = arg0;
argv[1] = NULL;
priv->pid = kthread_create("vhost-blk",
CONFIG_DRIVERS_VHOST_BLK_PRIORITY,
CONFIG_DRIVERS_VHOST_BLK_STACKSIZE,
vhost_blk_thread, argv);
if (priv->pid < 0)
{
vhosterr("vhost blk thread create failed %d\n", priv->pid);
ret = priv->pid;
goto err_thread;
}
virtqueue_enable_cb(hdev->vrings_info[0].vq);
return ret;
err_thread:
vhost_delete_virtqueues(hdev);
err_vq:
file_close(&priv->bdev);
nxsem_destroy(&priv->semrx);
err_init:
kmm_free(priv);
return ret;
}
* Name: vhost_blk_remove
****************************************************************************/
static void vhost_blk_remove(FAR struct vhost_device *hdev)
{
FAR struct vhost_blk_priv_s *priv = hdev->priv;
virtqueue_disable_cb(hdev->vrings_info[0].vq);
kthread_delete(priv->pid);
vhost_delete_virtqueues(hdev);
file_close(&priv->bdev);
nxsem_destroy(&priv->semrx);
kmm_free(priv);
}
* Public Functions
****************************************************************************/
* Name: vhost_register_blk_driver
****************************************************************************/
int vhost_register_blk_driver(void)
{
return vhost_register_driver(&g_vhost_blk_driver);
}