Goto sanos source index
//
// virtioblk.c
//
// Block device driver for virtio
//
// Copyright (C) 2011 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <os/krnl.h>
//
// Feature bits
//
#define VIRTIO_BLK_F_BARRIER (1 << 0) // Does host support barriers?
#define VIRTIO_BLK_F_SIZE_MAX (1 << 1) // Indicates maximum segment size
#define VIRTIO_BLK_F_SEG_MAX (1 << 2) // Indicates maximum # of segments
#define VIRTIO_BLK_F_GEOMETRY (1 << 4) // Geometry available
#define VIRTIO_BLK_F_RO (1 << 5) // Disk is read-only
#define VIRTIO_BLK_F_BLK_SIZE (1 << 6) // Block size of disk is available
#define VIRTIO_BLK_F_SCSI (1 << 7) // Supports scsi command passthru
#define VIRTIO_BLK_F_FLUSH (1 << 9) // Cache flush command support
#define VIRTIO_BLK_F_TOPOLOGY (1 << 10) // Topology information is available
//
// Virtual disk configuration block
//
struct virtio_blk_config {
unsigned __int64 capacity; // The capacity (in 512-byte sectors)
unsigned long size_max; // The maximum segment size
unsigned long seg_max; // The maximum number of segments
struct virtio_blk_geometry { // Device geometry
unsigned short cylinders;
unsigned char heads;
unsigned char sectors;
} geometry;
unsigned long blk_size; // Block size of device
unsigned char physical_block_exp; // Exponent for physical block per logical block
unsigned char alignment_offset; // Alignment offset in logical blocks
unsigned short min_io_size; // Minimum I/O size without performance penalty in logical blocks
unsigned long opt_io_size; // Optimal sustained I/O size in logical blocks
};
//
// Virtual block device request header
//
#define VIRTIO_BLK_T_IN 0
#define VIRTIO_BLK_T_OUT 1
#define VIRTIO_BLK_T_SCSI_CMD 2
#define VIRTIO_BLK_T_FLUSH 4
#define VIRTIO_BLK_T_GET_ID 8
struct virtio_blk_outhdr {
unsigned long type;
unsigned long ioprio;
unsigned __int64 sector;
};
//
// Virtual block device satus codes
//
#define VIRTIO_BLK_S_OK 0
#define VIRTIO_BLK_S_IOERR 1
#define VIRTIO_BLK_S_UNSUPP 2
//
// Virtual disk device data
//
struct virtioblk {
struct virtio_device vd;
struct virtio_blk_config config;
struct virtio_queue vq;
int capacity;
dev_t devno;
};
//
// Virtual block device request
//
struct virtioblk_request {
struct virtio_blk_outhdr hdr;
unsigned char status;
struct thread *thread;
unsigned int size;
};
static void virtioblk_setup_request(struct virtioblk_request *req, struct scatterlist *sg, void *buffer, int size) {
req->status = 0;
req->thread = self();
sg[0].data = &req->hdr;
sg[0].size = sizeof(req->hdr);
sg[1].data = buffer;
sg[1].size = size;
sg[2].data = &req->status;
sg[2].size = sizeof(req->status);
}
static int virtioblk_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
struct virtioblk *vblk = (struct virtioblk *) dev->privdata;
struct geometry *geom;
switch (cmd) {
case IOCTL_GETDEVSIZE:
return vblk->capacity;
case IOCTL_GETBLKSIZE:
return SECTORSIZE;
case IOCTL_GETGEOMETRY:
if (!args || size != sizeof(struct geometry)) return -EINVAL;
if (!(vblk->vd.features & VIRTIO_BLK_F_GEOMETRY)) return -ENOSYS;
geom = (struct geometry *) args;
geom->cyls = vblk->config.geometry.cylinders;
geom->heads = vblk->config.geometry.heads;
geom->spt = vblk->config.geometry.sectors;
geom->sectorsize = SECTORSIZE;
geom->sectors = vblk->capacity;
return 0;
}
return -ENOSYS;
}
static int virtioblk_read(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
struct virtioblk *vblk = (struct virtioblk *) dev->privdata;
struct virtioblk_request req;
struct scatterlist sg[3];
int rc;
// Setup read request
virtioblk_setup_request(&req, sg, buffer, count);
req.hdr.type = VIRTIO_BLK_T_IN;
req.hdr.ioprio = 0;
req.hdr.sector = blkno;
// Issue request
rc = virtio_enqueue(&vblk->vq, sg, 1, 2, &req);
if (rc < 0) return rc;
virtio_kick(&vblk->vq);
// Wait for request to complete
enter_wait(THREAD_WAIT_DEVIO);
// Check status code
switch (req.status) {
case VIRTIO_BLK_S_OK: rc = req.size - 1; break;
case VIRTIO_BLK_S_UNSUPP: rc = -ENODEV; break;
case VIRTIO_BLK_S_IOERR: rc = -EIO; break;
default: rc = -EUNKNOWN; break;
}
return rc;
}
static int virtioblk_write(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
struct virtioblk *vblk = (struct virtioblk *) dev->privdata;
struct virtioblk_request req;
struct scatterlist sg[3];
int rc;
// Setup write request
virtioblk_setup_request(&req, sg, buffer, count);
req.hdr.type = VIRTIO_BLK_T_OUT;
req.hdr.ioprio = 0;
req.hdr.sector = blkno;
// Issue request
rc = virtio_enqueue(&vblk->vq, sg, 2, 1, &req);
if (rc < 0) return rc;
virtio_kick(&vblk->vq);
// Wait for request to complete
enter_wait(THREAD_WAIT_DEVIO);
// Check status code
switch (req.status) {
case VIRTIO_BLK_S_OK: rc = req.size - 1; break;
case VIRTIO_BLK_S_UNSUPP: rc = -ENODEV; break;
case VIRTIO_BLK_S_IOERR: rc = -EIO; break;
default: rc = -EUNKNOWN; break;
}
return rc;
}
static int virtioblk_callback(struct virtio_queue *vq) {
struct virtioblk_request *req;
unsigned int len;
while ((req = virtio_dequeue(vq, &len)) != NULL) {
req->size = len;
mark_thread_ready(req->thread, 1, 2);
}
return 0;
}
struct driver virtioblk_driver = {
"virtioblk",
DEV_TYPE_BLOCK,
virtioblk_ioctl,
virtioblk_read,
virtioblk_write
};
static int install_virtioblk(struct unit *unit) {
struct virtioblk *vblk;
int rc;
// Setup unit information
if (!unit) return -ENOSYS;
unit->vendorname = "VIRTIO";
unit->productname = "VIRTIO Virtual Block Device";
// Allocate memory for device
vblk = kmalloc(sizeof(struct virtioblk));
if (vblk == NULL) return -ENOMEM;
memset(vblk, 0, sizeof(struct virtioblk));
// Initialize virtual device
rc = virtio_device_init(&vblk->vd, unit, VIRTIO_BLK_F_SEG_MAX | VIRTIO_BLK_F_SIZE_MAX | VIRTIO_BLK_F_GEOMETRY | VIRTIO_BLK_F_RO | VIRTIO_BLK_F_BLK_SIZE | VIRTIO_BLK_F_FLUSH);
if (rc < 0) return rc;
// Get block device configuration
virtio_get_config(&vblk->vd, &vblk->config, sizeof(vblk->config));
if ((vblk->config.capacity & ~0x7FFFFFFF)) {
vblk->capacity = 0x7FFFFFFF;
} else {
vblk->capacity = (int) vblk->config.capacity;
}
// Initialize queue for disk requests
rc = virtio_queue_init(&vblk->vq, &vblk->vd, 0, virtioblk_callback);
if (rc < 0) return rc;
// Create device
vblk->devno = dev_make("vd#", &virtioblk_driver, unit, vblk);
virtio_setup_complete(&vblk->vd, 1);
kprintf(KERN_INFO "%s: virtio disk, %dMB\n", device(vblk->devno)->name, vblk->capacity / (1024 * 1024 / SECTORSIZE));
return 0;
}
int __declspec(dllexport) virtioblk(struct unit *unit, char *opts) {
return install_virtioblk(unit);
}
void init_vblk() {
// Try to find a virtio block device for booting
struct unit *unit = lookup_unit_by_subunit(NULL, 0x1AF40002, 0xFFFFFFFF);
if (unit) install_virtioblk(unit);
}