nuttx/drivers/virtio/virtio-blk.c
wangbowen6 9aa57b6c53 virtio: add virtio framework in NuttX
1. virtio devics/drivers match and probe/remote mechanism;
2. virtio mmio transport layer based on OpenAmp (Compatible with both
   virtio mmio version 1 and 2);
3. virtio-serial driver based on new virtio framework;
4. virtio-rng driver based on new virtio framework;
5. virtio-net driver based on new virtio framework
   (IOB Offload implementation);
6. virtio-blk driver based on new virtio framework;
7. Remove the old virtio mmio framework, the old framework only
   support mmio transport layer, and the new framwork support
   more transport layer and this commit has implemented all the
   old virtio drivers;
8. Refresh the the qemu-arm64 and qemu-riscv virtio related
   configs, and update its README.txt;

New virtio-net driver has better performance
Compared with previous virtio-mmio-net:
|                        | master/-c | master/-s | this/-c | this/-s |
| :--------------------: | :-------: | :-------: | :-----: | :-----: |
| qemu-armv8a:netnsh     |  539Mbps  |  524Mbps  | 906Mbps | 715Mbps |
| qemu-armv8a:netnsh_smp |  401Mbps  |  437Mbps  | 583Mbps | 505Mbps |
| rv-virt:netnsh         |  487Mbps  |  512Mbps  | 760Mbps | 634Mbps |
| rv-virt:netnsh_smp     |  387Mbps  |  455Mbps  | 447Mbps | 502Mbps |
| rv-virt:netnsh64       |  602Mbps  |  595Mbps  | 881Mbps | 769Mbps |
| rv-virt:netnsh64_smp   |  414Mbps  |  515Mbps  | 491Mbps | 525Mbps |
| rv-virt:knetnsh64      |  515Mbps  |  457Mbps  | 606Mbps | 540Mbps |
| rv-virt:knetnsh64_smp  |  308Mbps  |  389Mbps  | 415Mbps | 474Mbps |
Note: Both CONFIG_IOB_NBUFFERS=64, using iperf command, all in Mbits/sec
      Tested in QEMU 7.2.2

Signed-off-by: wangbowen6 <wangbowen6@xiaomi.com>
Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
2023-08-10 03:39:39 +08:00

587 lines
17 KiB
C

/****************************************************************************
* drivers/virtio/virtio-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 <stdio.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/virtio/virtio.h>
#include "virtio-blk.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define VIRTIO_BLK_REQ_HEADER_SIZE sizeof(struct virtio_blk_req_s)
#define VIRTIO_BLK_RESP_HEADER_SIZE sizeof(struct virtio_blk_resp_s)
/* Block request type */
#define VIRTIO_BLK_T_IN 0 /* READ */
#define VIRTIO_BLK_T_OUT 1 /* WRITE */
#define VIRTIO_BLK_T_FLUSH 4 /* FLUSH */
/* Block request return status */
#define VIRTIO_BLK_S_OK 0
#define VIRTIO_BLK_S_IOERR 1
#define VIRTIO_BLK_S_UNSUPP 2
/* Block device sector size */
#define VIRTIO_BLK_SECTOR_SIZE 512
/****************************************************************************
* Private Types
****************************************************************************/
/* Block request out header */
begin_packed_struct struct virtio_blk_req_s
{
uint32_t type;
uint32_t reserved;
uint64_t sector;
} end_packed_struct;
/* Block request in header */
begin_packed_struct struct virtio_blk_resp_s
{
uint8_t status;
} end_packed_struct;
begin_packed_struct struct virtio_blk_config_s
{
uint64_t capacity;
uint32_t size_max;
uint32_t seg_max;
uint16_t cylinders; /* block geometry */
uint8_t heads; /* block geometry */
uint8_t sectors; /* block geometry */
uint32_t blk_size;
uint8_t physical_block_exp;
uint8_t alignment_offset;
uint16_t min_io_size;
uint32_t opt_io_size;
uint8_t writeback;
uint8_t unused0;
uint16_t num_queues;
uint32_t max_discard_sectors;
uint32_t max_discard_seg;
uint32_t discard_sector_alignment;
uint32_t max_write_zeroes_sectors;
uint32_t max_write_zeroes_seg;
uint8_t write_zeroes_may_unmap;
uint8_t unused1[3];
uint32_t max_secure_erase_sectors;
uint32_t max_secure_erase_seg;
uint32_t secure_erase_sector_alignment;
} end_packed_struct;
struct virtio_blk_priv_s
{
FAR struct virtio_device *vdev; /* Virtio deivce */
FAR struct virtio_blk_req_s *req; /* Virtio block out header */
FAR struct virtio_blk_resp_s *resp; /* Virtio block in header */
mutex_t lock; /* Lock */
uint64_t nsectors; /* Sectore numbers */
char name[NAME_MAX]; /* Device name */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* BLK block_operations functions and they helper function */
static ssize_t virtio_blk_rdwr(FAR struct virtio_blk_priv_s *priv,
FAR void *buffer, blkcnt_t startsector,
unsigned int nsectors, bool write);
static int virtio_blk_open(FAR struct inode *inode);
static int virtio_blk_close(FAR struct inode *inode);
static ssize_t virtio_blk_read(FAR struct inode *inode,
FAR unsigned char *buffer,
blkcnt_t startsector, unsigned int nsectors);
static ssize_t virtio_blk_write(FAR struct inode *inode,
FAR const unsigned char *buffer,
blkcnt_t startsector, unsigned int nsectors);
static int virtio_blk_geometry(FAR struct inode *inode,
FAR struct geometry *geometry);
static int virtio_blk_ioctl(FAR struct inode *inode, int cmd,
unsigned long arg);
static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv);
/* Other functions */
static int virtio_blk_init(FAR struct virtio_blk_priv_s *priv,
FAR struct virtio_device *vdev);
static void virtio_blk_uninit(FAR struct virtio_blk_priv_s *priv);
static void virtio_blk_done(FAR struct virtqueue *vq);
static int virtio_blk_probe(FAR struct virtio_device *vdev);
static void virtio_blk_remove(FAR struct virtio_device *vdev);
/****************************************************************************
* Private Data
****************************************************************************/
static struct virtio_driver g_virtio_blk_driver =
{
LIST_INITIAL_VALUE(g_virtio_blk_driver.node), /* node */
VIRTIO_ID_BLOCK, /* device id */
virtio_blk_probe, /* probe */
virtio_blk_remove, /* remove */
};
static const struct block_operations g_virtio_blk_bops =
{
virtio_blk_open, /* open */
virtio_blk_close, /* close */
virtio_blk_read, /* read */
virtio_blk_write, /* write */
virtio_blk_geometry, /* geometry */
virtio_blk_ioctl /* ioctl */
};
static int g_virtio_blk_idx = 0;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: virtio_blk_rdwr
*
* Description:
* Common function for read and write
*
****************************************************************************/
static ssize_t virtio_blk_rdwr(FAR struct virtio_blk_priv_s *priv,
FAR void *buffer, blkcnt_t startsector,
unsigned int nsectors, bool write)
{
FAR struct virtio_device *vdev = priv->vdev;
FAR struct virtqueue *vq = vdev->vrings_info[0].vq;
FAR struct virtqueue_buf vb[3];
sem_t respsem;
ssize_t ret;
int readnum;
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
return ret;
}
nxsem_init(&respsem, 0, 0);
/* Build the block request */
priv->req->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
priv->req->reserved = 0;
priv->req->sector = startsector;
priv->resp->status = VIRTIO_BLK_S_IOERR;
/* Fill the virtqueue buffer:
* Buffer 0: the block out header;
* Buffer 1: the read/write buffer;
* Buffer 2: the block in header, return the status.
*/
vb[0].buf = priv->req;
vb[0].len = VIRTIO_BLK_REQ_HEADER_SIZE;
vb[1].buf = buffer;
vb[1].len = nsectors * VIRTIO_BLK_SECTOR_SIZE;
vb[2].buf = priv->resp;
vb[2].len = VIRTIO_BLK_RESP_HEADER_SIZE;
readnum = write ? 2 : 1;
ret = virtqueue_add_buffer(vq, vb, readnum, 3 - readnum, &respsem);
if (ret < 0)
{
vrterr("virtqueue_add_buffer failed, ret=%zd\n", ret);
goto err;
}
virtqueue_kick(vq);
/* Wait for the request completion */
nxsem_wait_uninterruptible(&respsem);
if (priv->resp->status != VIRTIO_BLK_S_OK)
{
vrterr("%s Error\n", write ? "Write" : "Read");
ret = -EIO;
}
err:
nxmutex_unlock(&priv->lock);
return ret >= 0 ? nsectors : ret;
}
/****************************************************************************
* Name: virtio_blk_open
*
* Description: Open the block device
*
****************************************************************************/
static int virtio_blk_open(FAR struct inode *inode)
{
DEBUGASSERT(inode && inode->i_private);
return OK;
}
/****************************************************************************
* Name: virtio_blk_close
*
* Description: close the block device
*
****************************************************************************/
static int virtio_blk_close(FAR struct inode *inode)
{
DEBUGASSERT(inode && inode->i_private);
return OK;
}
/****************************************************************************
* Name: virtio_blk_read
*
* Description:
* Read the specified number of sectors from the read-ahead buffer or from
* the physical device.
*
****************************************************************************/
static ssize_t virtio_blk_read(FAR struct inode *inode,
FAR unsigned char *buffer,
blkcnt_t startsector, unsigned int nsectors)
{
FAR struct virtio_blk_priv_s *priv;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct virtio_blk_priv_s *)inode->i_private;
return virtio_blk_rdwr(priv, buffer, startsector, nsectors, false);
}
/****************************************************************************
* Name: virtio_blk_write
*
* Description:
* Write the specified number of sectors to the write buffer or to the
* physical device.
*
****************************************************************************/
static ssize_t virtio_blk_write(FAR struct inode *inode,
FAR const unsigned char *buffer,
blkcnt_t startsector, unsigned int nsectors)
{
FAR struct virtio_blk_priv_s *priv;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct virtio_blk_priv_s *)inode->i_private;
return virtio_blk_rdwr(priv, (FAR void *)buffer, startsector, nsectors,
true);
}
/****************************************************************************
* Name: virtio_blk_geometry
*
* Description: Return device geometry
*
****************************************************************************/
static int virtio_blk_geometry(FAR struct inode *inode,
FAR struct geometry *geometry)
{
FAR struct virtio_blk_priv_s *priv;
int ret = -EINVAL;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct virtio_blk_priv_s *)inode->i_private;
if (geometry)
{
geometry->geo_available = true;
geometry->geo_mediachanged = false;
geometry->geo_writeenabled = true;
geometry->geo_nsectors = priv->nsectors;
geometry->geo_sectorsize = VIRTIO_BLK_SECTOR_SIZE;
ret = OK;
}
return ret;
}
/****************************************************************************
* Name: virtio_blk_ioctl
****************************************************************************/
static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv)
{
FAR struct virtio_device *vdev = priv->vdev;
FAR struct virtqueue *vq = vdev->vrings_info[0].vq;
FAR struct virtqueue_buf vb[2];
sem_t respsem;
int ret;
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
return ret;
}
nxsem_init(&respsem, 0, 0);
/* Build the block request */
priv->req->type = VIRTIO_BLK_T_FLUSH;
priv->req->reserved = 0;
priv->req->sector = 0;
priv->resp->status = VIRTIO_BLK_S_IOERR;
vb[0].buf = priv->req;
vb[0].len = VIRTIO_BLK_REQ_HEADER_SIZE;
vb[1].buf = priv->resp;
vb[1].len = VIRTIO_BLK_RESP_HEADER_SIZE;
ret = virtqueue_add_buffer(vq, vb, 1, 1, &respsem);
if (ret < 0)
{
goto err;
}
virtqueue_kick(vq);
/* Wait for the request completion */
nxsem_wait_uninterruptible(&respsem);
if (priv->resp->status != VIRTIO_BLK_S_OK)
{
vrterr("Flush Error\n");
ret = -EIO;
}
err:
nxmutex_unlock(&priv->lock);
return ret;
}
/****************************************************************************
* Name: virtio_blk_ioctl
****************************************************************************/
static int virtio_blk_ioctl(FAR struct inode *inode, int cmd,
unsigned long arg)
{
FAR struct virtio_blk_priv_s *priv;
int ret = -ENOTTY;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct virtio_blk_priv_s *)inode->i_private;
switch (cmd)
{
case BIOC_FLUSH:
ret = virtio_blk_flush(priv);
break;
}
return ret;
}
/****************************************************************************
* Name: virtio_blk_done
****************************************************************************/
static void virtio_blk_done(FAR struct virtqueue *vq)
{
FAR sem_t *respsem;
respsem = virtqueue_get_buffer(vq, NULL, NULL);
if (respsem != NULL)
{
nxsem_post(respsem);
}
}
/****************************************************************************
* Name: virtio_blk_init
****************************************************************************/
static int virtio_blk_init(FAR struct virtio_blk_priv_s *priv,
FAR struct virtio_device *vdev)
{
FAR const char *vqname[1];
vq_callback callback[1];
int ret;
priv->vdev = vdev;
vdev->priv = priv;
nxmutex_init(&priv->lock);
/* Alloc the request and in header from tansport layer */
priv->req = virtio_alloc_buf(vdev, sizeof(*priv->req), 16);
if (priv->req == NULL)
{
ret = -ENOMEM;
goto err_with_lock;
}
priv->resp = virtio_alloc_buf(vdev, sizeof(*priv->resp), 16);
if (priv->resp == NULL)
{
ret = -ENOMEM;
goto err_with_req;
}
/* Initialize the virtio device */
virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER);
virtio_set_features(vdev, 0);
virtio_set_status(vdev, VIRTIO_CONFIG_FEATURES_OK);
vqname[0] = "virtio_blk_vq";
callback[0] = virtio_blk_done;
ret = virtio_create_virtqueues(vdev, 0, 1, vqname, callback);
if (ret < 0)
{
vrterr("virtio_device_create_virtqueue failed, ret=%d\n", ret);
goto err_with_resp;
}
virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER_OK);
virtqueue_enable_cb(vdev->vrings_info[0].vq);
return OK;
err_with_resp:
virtio_free_buf(vdev, priv->resp);
err_with_req:
virtio_free_buf(vdev, priv->req);
err_with_lock:
nxmutex_destroy(&priv->lock);
return ret;
}
/****************************************************************************
* Name: virtio_blk_uninit
****************************************************************************/
static void virtio_blk_uninit(FAR struct virtio_blk_priv_s *priv)
{
FAR struct virtio_device *vdev = priv->vdev;
virtio_reset_device(vdev);
virtio_delete_virtqueues(vdev);
virtio_free_buf(vdev, priv->resp);
virtio_free_buf(vdev, priv->req);
nxmutex_destroy(&priv->lock);
}
/****************************************************************************
* Name: virtio_blk_probe
****************************************************************************/
static int virtio_blk_probe(FAR struct virtio_device *vdev)
{
FAR struct virtio_blk_priv_s *priv;
int ret;
/* Alloc the virtio block driver private data */
priv = kmm_zalloc(sizeof(*priv));
if (priv == NULL)
{
vrterr("No enough memory\n");
return -ENOMEM;
}
/* Init the virtio block driver */
ret = virtio_blk_init(priv, vdev);
if (ret < 0)
{
vrterr("virtio_blk_init failed, ret=%d\n", ret);
goto err_with_priv;
}
/* Read the block config and save the capacity to nsectors */
virtio_read_config_member(priv->vdev, struct virtio_blk_config_s, capacity,
&priv->nsectors);
vrtinfo("Virio blk capacity=%" PRIu64 " sectors\n", priv->nsectors);
/* Register block driver */
snprintf(priv->name, NAME_MAX, "/dev/virtblk%d", g_virtio_blk_idx);
ret = register_blockdriver(priv->name, &g_virtio_blk_bops, 0660, priv);
if (ret < 0)
{
vrterr("Register block driver failed, ret=%d\n", ret);
goto err_with_init;
}
g_virtio_blk_idx++;
return ret;
err_with_init:
virtio_blk_uninit(priv);
err_with_priv:
kmm_free(priv);
return ret;
}
/****************************************************************************
* Name: virtio_blk_remove
****************************************************************************/
static void virtio_blk_remove(FAR struct virtio_device *vdev)
{
FAR struct virtio_blk_priv_s *priv = vdev->priv;
unregister_driver(priv->name);
virtio_blk_uninit(priv);
kmm_free(priv);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: virtio_register_blk_driver
****************************************************************************/
int virtio_register_blk_driver(void)
{
return virtio_register_driver(&g_virtio_blk_driver);
}