|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
| 2 | +/* |
| 3 | + * VDPA simulator for block device. |
| 4 | + * |
| 5 | + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. |
| 6 | + * |
| 7 | + */ |
| 8 | + |
| 9 | +#include <linux/init.h> |
| 10 | +#include <linux/module.h> |
| 11 | +#include <linux/device.h> |
| 12 | +#include <linux/kernel.h> |
| 13 | +#include <linux/sched.h> |
| 14 | +#include <linux/blkdev.h> |
| 15 | +#include <linux/vringh.h> |
| 16 | +#include <linux/vdpa.h> |
| 17 | +#include <uapi/linux/virtio_blk.h> |
| 18 | + |
| 19 | +#include "vdpa_sim.h" |
| 20 | + |
| 21 | +#define DRV_VERSION "0.1" |
| 22 | +#define DRV_AUTHOR "Max Gurtovoy <mgurtovoy@nvidia.com>" |
| 23 | +#define DRV_DESC "vDPA Device Simulator for block device" |
| 24 | +#define DRV_LICENSE "GPL v2" |
| 25 | + |
| 26 | +#define VDPASIM_BLK_FEATURES (VDPASIM_FEATURES | \ |
| 27 | + (1ULL << VIRTIO_BLK_F_SIZE_MAX) | \ |
| 28 | + (1ULL << VIRTIO_BLK_F_SEG_MAX) | \ |
| 29 | + (1ULL << VIRTIO_BLK_F_BLK_SIZE) | \ |
| 30 | + (1ULL << VIRTIO_BLK_F_TOPOLOGY) | \ |
| 31 | + (1ULL << VIRTIO_BLK_F_MQ)) |
| 32 | + |
| 33 | +#define VDPASIM_BLK_CAPACITY 0x40000 |
| 34 | +#define VDPASIM_BLK_SIZE_MAX 0x1000 |
| 35 | +#define VDPASIM_BLK_SEG_MAX 32 |
| 36 | +#define VDPASIM_BLK_VQ_NUM 1 |
| 37 | + |
| 38 | +static struct vdpasim *vdpasim_blk_dev; |
| 39 | + |
| 40 | +static void vdpasim_blk_work(struct work_struct *work) |
| 41 | +{ |
| 42 | + struct vdpasim *vdpasim = container_of(work, struct vdpasim, work); |
| 43 | + u8 status = VIRTIO_BLK_S_OK; |
| 44 | + int i; |
| 45 | + |
| 46 | + spin_lock(&vdpasim->lock); |
| 47 | + |
| 48 | + if (!(vdpasim->status & VIRTIO_CONFIG_S_DRIVER_OK)) |
| 49 | + goto out; |
| 50 | + |
| 51 | + for (i = 0; i < VDPASIM_BLK_VQ_NUM; i++) { |
| 52 | + struct vdpasim_virtqueue *vq = &vdpasim->vqs[i]; |
| 53 | + |
| 54 | + if (!vq->ready) |
| 55 | + continue; |
| 56 | + |
| 57 | + while (vringh_getdesc_iotlb(&vq->vring, &vq->out_iov, |
| 58 | + &vq->in_iov, &vq->head, |
| 59 | + GFP_ATOMIC) > 0) { |
| 60 | + int write; |
| 61 | + |
| 62 | + vq->in_iov.i = vq->in_iov.used - 1; |
| 63 | + write = vringh_iov_push_iotlb(&vq->vring, &vq->in_iov, |
| 64 | + &status, 1); |
| 65 | + if (write <= 0) |
| 66 | + break; |
| 67 | + |
| 68 | + /* Make sure data is wrote before advancing index */ |
| 69 | + smp_wmb(); |
| 70 | + |
| 71 | + vringh_complete_iotlb(&vq->vring, vq->head, write); |
| 72 | + |
| 73 | + /* Make sure used is visible before rasing the interrupt. */ |
| 74 | + smp_wmb(); |
| 75 | + |
| 76 | + local_bh_disable(); |
| 77 | + if (vringh_need_notify_iotlb(&vq->vring) > 0) |
| 78 | + vringh_notify(&vq->vring); |
| 79 | + local_bh_enable(); |
| 80 | + } |
| 81 | + } |
| 82 | +out: |
| 83 | + spin_unlock(&vdpasim->lock); |
| 84 | +} |
| 85 | + |
| 86 | +static void vdpasim_blk_get_config(struct vdpasim *vdpasim, void *config) |
| 87 | +{ |
| 88 | + struct virtio_blk_config *blk_config = config; |
| 89 | + |
| 90 | + memset(config, 0, sizeof(struct virtio_blk_config)); |
| 91 | + |
| 92 | + blk_config->capacity = cpu_to_vdpasim64(vdpasim, VDPASIM_BLK_CAPACITY); |
| 93 | + blk_config->size_max = cpu_to_vdpasim32(vdpasim, VDPASIM_BLK_SIZE_MAX); |
| 94 | + blk_config->seg_max = cpu_to_vdpasim32(vdpasim, VDPASIM_BLK_SEG_MAX); |
| 95 | + blk_config->num_queues = cpu_to_vdpasim16(vdpasim, VDPASIM_BLK_VQ_NUM); |
| 96 | + blk_config->min_io_size = cpu_to_vdpasim16(vdpasim, 1); |
| 97 | + blk_config->opt_io_size = cpu_to_vdpasim32(vdpasim, 1); |
| 98 | + blk_config->blk_size = cpu_to_vdpasim32(vdpasim, SECTOR_SIZE); |
| 99 | +} |
| 100 | + |
| 101 | +static int __init vdpasim_blk_init(void) |
| 102 | +{ |
| 103 | + struct vdpasim_dev_attr dev_attr = {}; |
| 104 | + int ret; |
| 105 | + |
| 106 | + dev_attr.id = VIRTIO_ID_BLOCK; |
| 107 | + dev_attr.supported_features = VDPASIM_BLK_FEATURES; |
| 108 | + dev_attr.nvqs = VDPASIM_BLK_VQ_NUM; |
| 109 | + dev_attr.config_size = sizeof(struct virtio_blk_config); |
| 110 | + dev_attr.get_config = vdpasim_blk_get_config; |
| 111 | + dev_attr.work_fn = vdpasim_blk_work; |
| 112 | + dev_attr.buffer_size = PAGE_SIZE; |
| 113 | + |
| 114 | + vdpasim_blk_dev = vdpasim_create(&dev_attr); |
| 115 | + if (IS_ERR(vdpasim_blk_dev)) { |
| 116 | + ret = PTR_ERR(vdpasim_blk_dev); |
| 117 | + goto out; |
| 118 | + } |
| 119 | + |
| 120 | + ret = vdpa_register_device(&vdpasim_blk_dev->vdpa, VDPASIM_BLK_VQ_NUM); |
| 121 | + if (ret) |
| 122 | + goto put_dev; |
| 123 | + |
| 124 | + return 0; |
| 125 | + |
| 126 | +put_dev: |
| 127 | + put_device(&vdpasim_blk_dev->vdpa.dev); |
| 128 | +out: |
| 129 | + return ret; |
| 130 | +} |
| 131 | + |
| 132 | +static void __exit vdpasim_blk_exit(void) |
| 133 | +{ |
| 134 | + struct vdpa_device *vdpa = &vdpasim_blk_dev->vdpa; |
| 135 | + |
| 136 | + vdpa_unregister_device(vdpa); |
| 137 | +} |
| 138 | + |
| 139 | +module_init(vdpasim_blk_init) |
| 140 | +module_exit(vdpasim_blk_exit) |
| 141 | + |
| 142 | +MODULE_VERSION(DRV_VERSION); |
| 143 | +MODULE_LICENSE(DRV_LICENSE); |
| 144 | +MODULE_AUTHOR(DRV_AUTHOR); |
| 145 | +MODULE_DESCRIPTION(DRV_DESC); |
0 commit comments