summaryrefslogtreecommitdiffstats
path: root/drivers/block/virtio_blk.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/block/virtio_blk.c')
-rw-r--r--drivers/block/virtio_blk.c135
1 files changed, 135 insertions, 0 deletions
diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c
new file mode 100644
index 0000000000..cbef500d59
--- /dev/null
+++ b/drivers/block/virtio_blk.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
+ * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <block.h>
+#include <disks.h>
+#include <linux/virtio_types.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ring.h>
+#include <uapi/linux/virtio_blk.h>
+
+struct virtio_blk_priv {
+ struct virtqueue *vq;
+ struct virtio_device *vdev;
+ struct block_device blk;
+};
+
+static int virtio_blk_do_req(struct virtio_blk_priv *priv, void *buffer,
+ sector_t sector, blkcnt_t blkcnt, u32 type)
+{
+ unsigned int num_out = 0, num_in = 0;
+ struct virtio_sg *sgs[3];
+ u8 status;
+ int ret;
+
+ struct virtio_blk_outhdr out_hdr = {
+ .type = cpu_to_virtio32(priv->vdev, type),
+ .sector = cpu_to_virtio64(priv->vdev, sector),
+ };
+ struct virtio_sg hdr_sg = { &out_hdr, sizeof(out_hdr) };
+ struct virtio_sg data_sg = { buffer, blkcnt * 512 };
+ struct virtio_sg status_sg = { &status, sizeof(status) };
+
+ sgs[num_out++] = &hdr_sg;
+
+ switch(type) {
+ case VIRTIO_BLK_T_OUT:
+ sgs[num_out++] = &data_sg;
+ break;
+ case VIRTIO_BLK_T_IN:
+ sgs[num_out + num_in++] = &data_sg;
+ break;
+ }
+
+ sgs[num_out + num_in++] = &status_sg;
+
+ ret = virtqueue_add(priv->vq, sgs, num_out, num_in);
+ if (ret)
+ return ret;
+
+ virtqueue_kick(priv->vq);
+
+ while (!virtqueue_get_buf(priv->vq, NULL))
+ ;
+
+ return status == VIRTIO_BLK_S_OK ? 0 : -EIO;
+}
+
+static int virtio_blk_read(struct block_device *blk, void *buffer,
+ sector_t start, blkcnt_t blkcnt)
+{
+ struct virtio_blk_priv *priv = container_of(blk, struct virtio_blk_priv, blk);
+ return virtio_blk_do_req(priv, buffer, start, blkcnt,
+ VIRTIO_BLK_T_IN);
+}
+
+static int virtio_blk_write(struct block_device *blk, const void *buffer,
+ sector_t start, blkcnt_t blkcnt)
+{
+ struct virtio_blk_priv *priv = container_of(blk, struct virtio_blk_priv, blk);
+ return virtio_blk_do_req(priv, (void *)buffer, start, blkcnt,
+ VIRTIO_BLK_T_OUT);
+}
+
+static struct block_device_ops virtio_blk_ops = {
+ .read = virtio_blk_read,
+ .write = virtio_blk_write,
+};
+
+static int virtio_blk_probe(struct virtio_device *vdev)
+{
+ struct virtio_blk_priv *priv;
+ u64 cap;
+ int devnum;
+ int ret;
+
+ priv = xzalloc(sizeof(*priv));
+
+ ret = virtio_find_vqs(vdev, 1, &priv->vq);
+ if (ret)
+ return ret;
+
+ priv->vdev = vdev;
+ vdev->priv = priv;
+
+ devnum = cdev_find_free_index("virtioblk");
+ priv->blk.cdev.name = xasprintf("virtioblk%d", devnum);
+ cdev_set_of_node(&priv->blk.cdev, vdev->dev.device_node);
+ priv->blk.dev = &vdev->dev;
+ priv->blk.blockbits = SECTOR_SHIFT;
+ virtio_cread(vdev, struct virtio_blk_config, capacity, &cap);
+ priv->blk.num_blocks = cap;
+ priv->blk.ops = &virtio_blk_ops;
+ priv->blk.type = BLK_TYPE_VIRTUAL;
+
+ return blockdevice_register(&priv->blk);
+}
+
+static void virtio_blk_remove(struct virtio_device *vdev)
+{
+ struct virtio_blk_priv *priv = vdev->priv;
+
+ vdev->config->reset(vdev);
+ blockdevice_unregister(&priv->blk);
+ vdev->config->del_vqs(vdev);
+
+ free(priv);
+}
+
+static const struct virtio_device_id id_table[] = {
+ { VIRTIO_ID_BLOCK, VIRTIO_DEV_ANY_ID },
+ { 0 },
+};
+
+static struct virtio_driver virtio_blk = {
+ .driver.name = "virtio_blk",
+ .id_table = id_table,
+ .probe = virtio_blk_probe,
+ .remove = virtio_blk_remove,
+};
+device_virtio_driver(virtio_blk);