HelenOS sources
This source file includes following definitions.
- virtio_blk_irq_handler
- virtio_blk_register_interrupt
- virtio_blk_bd_open
- virtio_blk_bd_close
- virtio_blk_rw_block
- virtio_blk_bd_rw_blocks
- virtio_blk_bd_read_blocks
- virtio_blk_bd_write_blocks
- virtio_blk_bd_get_block_size
- virtio_blk_bd_get_num_blocks
- virtio_blk_initialize
- virtio_blk_uninitialize
- virtio_blk_bd_connection
- virtio_blk_dev_add
- main
#include "virtio-blk.h"
#include <stdio.h>
#include <stdint.h>
#include <as.h>
#include <ddf/driver.h>
#include <ddf/interrupt.h>
#include <ddf/log.h>
#include <pci_dev_iface.h>
#include <fibril_synch.h>
#include <bd_srv.h>
#include <virtio-pci.h>
#define NAME "virtio-blk"
#define VIRTIO_BLK_NUM_QUEUES 1
#define RQ_QUEUE 0
#define REQ_HEADER_DESC(descno) (0 * RQ_BUFFERS + (descno))
#define REQ_BUFFER_DESC(descno) (1 * RQ_BUFFERS + (descno))
#define REQ_FOOTER_DESC(descno) (2 * RQ_BUFFERS + (descno))
static errno_t virtio_blk_dev_add(ddf_dev_t *dev);
static driver_ops_t virtio_blk_driver_ops = {
.dev_add = virtio_blk_dev_add
};
static driver_t virtio_blk_driver = {
.name = NAME,
.driver_ops = &virtio_blk_driver_ops
};
static void virtio_blk_irq_handler(ipc_call_t *icall, void *arg)
{
virtio_blk_t *virtio_blk = (virtio_blk_t *)arg;
virtio_dev_t *vdev = &virtio_blk->virtio_dev;
uint16_t descno;
uint32_t len;
while (virtio_virtq_consume_used(vdev, RQ_QUEUE, &descno, &len)) {
assert(descno < RQ_BUFFERS);
fibril_mutex_lock(&virtio_blk->completion_lock[descno]);
fibril_condvar_signal(&virtio_blk->completion_cv[descno]);
fibril_mutex_unlock(&virtio_blk->completion_lock[descno]);
}
}
static errno_t virtio_blk_register_interrupt(ddf_dev_t *dev)
{
virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
virtio_dev_t *vdev = &virtio_blk->virtio_dev;
async_sess_t *parent_sess = ddf_dev_parent_sess_get(dev);
if (parent_sess == NULL)
return ENOMEM;
hw_res_list_parsed_t res;
hw_res_list_parsed_init(&res);
hw_res_list_parsed_init(&res);
errno_t rc = hw_res_get_list_parsed(parent_sess, &res, 0);
if (rc != EOK)
return rc;
if (res.irqs.count < 1) {
hw_res_list_parsed_clean(&res);
return EINVAL;
}
virtio_blk->irq = res.irqs.irqs[0];
hw_res_list_parsed_clean(&res);
irq_pio_range_t pio_ranges[] = {
{
.base = vdev->isr_phys,
.size = sizeof(vdev->isr_phys),
}
};
irq_cmd_t irq_commands[] = {
{
.cmd = CMD_PIO_READ_8,
.addr = (void *) vdev->isr_phys,
.dstarg = 2
},
{
.cmd = CMD_PREDICATE,
.value = 1,
.srcarg = 2
},
{
.cmd = CMD_ACCEPT
}
};
irq_code_t irq_code = {
.rangecount = sizeof(pio_ranges) / sizeof(irq_pio_range_t),
.ranges = pio_ranges,
.cmdcount = sizeof(irq_commands) / sizeof(irq_cmd_t),
.cmds = irq_commands
};
return register_interrupt_handler(dev, virtio_blk->irq,
virtio_blk_irq_handler, (void *)virtio_blk, &irq_code,
&virtio_blk->irq_handle);
}
static errno_t virtio_blk_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
{
return EOK;
}
static errno_t virtio_blk_bd_close(bd_srv_t *bd)
{
return EOK;
}
static errno_t virtio_blk_rw_block(virtio_blk_t *virtio_blk, bool read,
aoff64_t ba, void *buf)
{
virtio_dev_t *vdev = &virtio_blk->virtio_dev;
fibril_mutex_lock(&virtio_blk->free_lock);
uint16_t descno = virtio_alloc_desc(vdev, RQ_QUEUE,
&virtio_blk->rq_free_head);
while (descno == (uint16_t) -1U) {
fibril_condvar_wait(&virtio_blk->free_cv,
&virtio_blk->free_lock);
descno = virtio_alloc_desc(vdev, RQ_QUEUE,
&virtio_blk->rq_free_head);
}
fibril_mutex_unlock(&virtio_blk->free_lock);
assert(descno < RQ_BUFFERS);
virtio_blk_req_header_t *req_header =
(virtio_blk_req_header_t *) virtio_blk->rq_header[descno];
memset(req_header, 0, sizeof(virtio_blk_req_header_t));
pio_write_le32(&req_header->type,
read ? VIRTIO_BLK_T_IN : VIRTIO_BLK_T_OUT);
pio_write_le64(&req_header->sector, ba);
if (!read)
memcpy(virtio_blk->rq_buf[descno], buf, VIRTIO_BLK_BLOCK_SIZE);
fibril_mutex_lock(&virtio_blk->completion_lock[descno]);
virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_HEADER_DESC(descno),
virtio_blk->rq_header_p[descno], sizeof(virtio_blk_req_header_t),
VIRTQ_DESC_F_NEXT, REQ_BUFFER_DESC(descno));
virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_BUFFER_DESC(descno),
virtio_blk->rq_buf_p[descno], VIRTIO_BLK_BLOCK_SIZE,
VIRTQ_DESC_F_NEXT | (read ? VIRTQ_DESC_F_WRITE : 0),
REQ_FOOTER_DESC(descno));
virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_FOOTER_DESC(descno),
virtio_blk->rq_footer_p[descno], sizeof(virtio_blk_req_footer_t),
VIRTQ_DESC_F_WRITE, 0);
virtio_virtq_produce_available(vdev, RQ_QUEUE, descno);
fibril_condvar_wait(&virtio_blk->completion_cv[descno],
&virtio_blk->completion_lock[descno]);
fibril_mutex_unlock(&virtio_blk->completion_lock[descno]);
errno_t rc;
virtio_blk_req_footer_t *footer =
(virtio_blk_req_footer_t *) virtio_blk->rq_footer[descno];
switch (footer->status) {
case VIRTIO_BLK_S_OK:
rc = EOK;
break;
case VIRTIO_BLK_S_IOERR:
rc = EIO;
break;
case VIRTIO_BLK_S_UNSUPP:
rc = ENOTSUP;
break;
default:
ddf_msg(LVL_DEBUG, "device returned unknown status=%d\n",
(int) footer->status);
rc = EIO;
break;
}
if (rc == EOK && read)
memcpy(buf, virtio_blk->rq_buf[descno], VIRTIO_BLK_BLOCK_SIZE);
fibril_mutex_lock(&virtio_blk->free_lock);
virtio_free_desc(vdev, RQ_QUEUE, &virtio_blk->rq_free_head, descno);
fibril_condvar_signal(&virtio_blk->free_cv);
fibril_mutex_unlock(&virtio_blk->free_lock);
return rc;
}
static errno_t virtio_blk_bd_rw_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
void *buf, size_t size, bool read)
{
virtio_blk_t *virtio_blk = (virtio_blk_t *) bd->srvs->sarg;
aoff64_t i;
errno_t rc;
if (size != cnt * VIRTIO_BLK_BLOCK_SIZE)
return EINVAL;
for (i = 0; i < cnt; i++) {
rc = virtio_blk_rw_block(virtio_blk, read, ba + i,
buf + i * VIRTIO_BLK_BLOCK_SIZE);
if (rc != EOK)
return rc;
}
return EOK;
}
static errno_t virtio_blk_bd_read_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
void *buf, size_t size)
{
return virtio_blk_bd_rw_blocks(bd, ba, cnt, buf, size, true);
}
static errno_t virtio_blk_bd_write_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
const void *buf, size_t size)
{
return virtio_blk_bd_rw_blocks(bd, ba, cnt, (void *) buf, size, false);
}
static errno_t virtio_blk_bd_get_block_size(bd_srv_t *bd, size_t *size)
{
*size = VIRTIO_BLK_BLOCK_SIZE;
return EOK;
}
static errno_t virtio_blk_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *nb)
{
virtio_blk_t *virtio_blk = (virtio_blk_t *) bd->srvs->sarg;
virtio_blk_cfg_t *blkcfg = virtio_blk->virtio_dev.device_cfg;
*nb = pio_read_le64(&blkcfg->capacity);
return EOK;
}
bd_ops_t virtio_blk_bd_ops = {
.open = virtio_blk_bd_open,
.close = virtio_blk_bd_close,
.read_blocks = virtio_blk_bd_read_blocks,
.write_blocks = virtio_blk_bd_write_blocks,
.get_block_size = virtio_blk_bd_get_block_size,
.get_num_blocks = virtio_blk_bd_get_num_blocks,
};
static errno_t virtio_blk_initialize(ddf_dev_t *dev)
{
virtio_blk_t *virtio_blk = ddf_dev_data_alloc(dev,
sizeof(virtio_blk_t));
if (!virtio_blk)
return ENOMEM;
fibril_mutex_initialize(&virtio_blk->free_lock);
fibril_condvar_initialize(&virtio_blk->free_cv);
for (unsigned i = 0; i < RQ_BUFFERS; i++) {
fibril_mutex_initialize(&virtio_blk->completion_lock[i]);
fibril_condvar_initialize(&virtio_blk->completion_cv[i]);
}
bd_srvs_init(&virtio_blk->bds);
virtio_blk->bds.ops = &virtio_blk_bd_ops;
virtio_blk->bds.sarg = virtio_blk;
errno_t rc = virtio_pci_dev_initialize(dev, &virtio_blk->virtio_dev);
if (rc != EOK)
return rc;
virtio_dev_t *vdev = &virtio_blk->virtio_dev;
virtio_pci_common_cfg_t *cfg = virtio_blk->virtio_dev.common_cfg;
rc = virtio_blk_register_interrupt(dev);
if (rc != EOK)
goto fail;
rc = virtio_device_setup_start(vdev, 0);
if (rc != EOK)
goto fail;
uint16_t num_queues = pio_read_le16(&cfg->num_queues);
if (num_queues != VIRTIO_BLK_NUM_QUEUES) {
ddf_msg(LVL_NOTE, "Unsupported number of virtqueues: %u",
num_queues);
rc = ELIMIT;
goto fail;
}
vdev->queues = calloc(sizeof(virtq_t), num_queues);
if (!vdev->queues) {
rc = ENOMEM;
goto fail;
}
rc = virtio_virtq_setup(vdev, RQ_QUEUE, 3 * RQ_BUFFERS);
if (rc != EOK)
goto fail;
rc = virtio_setup_dma_bufs(RQ_BUFFERS, sizeof(virtio_blk_req_header_t),
true, virtio_blk->rq_header, virtio_blk->rq_header_p);
if (rc != EOK)
goto fail;
rc = virtio_setup_dma_bufs(RQ_BUFFERS, VIRTIO_BLK_BLOCK_SIZE,
true, virtio_blk->rq_buf, virtio_blk->rq_buf_p);
if (rc != EOK)
goto fail;
rc = virtio_setup_dma_bufs(RQ_BUFFERS, sizeof(virtio_blk_req_footer_t),
false, virtio_blk->rq_footer, virtio_blk->rq_footer_p);
if (rc != EOK)
goto fail;
virtio_create_desc_free_list(vdev, RQ_QUEUE, RQ_BUFFERS,
&virtio_blk->rq_free_head);
rc = hw_res_enable_interrupt(ddf_dev_parent_sess_get(dev),
virtio_blk->irq);
if (rc != EOK) {
ddf_msg(LVL_NOTE, "Failed to enable interrupt");
goto fail;
}
ddf_msg(LVL_NOTE, "Registered IRQ %d", virtio_blk->irq);
virtio_device_setup_finalize(vdev);
return EOK;
fail:
virtio_teardown_dma_bufs(virtio_blk->rq_header);
virtio_teardown_dma_bufs(virtio_blk->rq_buf);
virtio_teardown_dma_bufs(virtio_blk->rq_footer);
virtio_device_setup_fail(vdev);
virtio_pci_dev_cleanup(vdev);
return rc;
}
static void virtio_blk_uninitialize(ddf_dev_t *dev)
{
virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
virtio_teardown_dma_bufs(virtio_blk->rq_header);
virtio_teardown_dma_bufs(virtio_blk->rq_buf);
virtio_teardown_dma_bufs(virtio_blk->rq_footer);
virtio_device_setup_fail(&virtio_blk->virtio_dev);
virtio_pci_dev_cleanup(&virtio_blk->virtio_dev);
}
static void virtio_blk_bd_connection(ipc_call_t *icall, void *arg)
{
virtio_blk_t *virtio_blk;
ddf_fun_t *fun = (ddf_fun_t *) arg;
virtio_blk = (virtio_blk_t *) ddf_dev_data_get(ddf_fun_get_dev(fun));
bd_conn(icall, &virtio_blk->bds);
}
static errno_t virtio_blk_dev_add(ddf_dev_t *dev)
{
ddf_msg(LVL_NOTE, "%s %s (handle = %zu)", __func__,
ddf_dev_get_name(dev), ddf_dev_get_handle(dev));
errno_t rc = virtio_blk_initialize(dev);
if (rc != EOK)
return rc;
ddf_fun_t *fun = ddf_fun_create(dev, fun_exposed, "port0");
if (fun == NULL) {
rc = ENOMEM;
goto uninitialize;
}
ddf_fun_set_conn_handler(fun, virtio_blk_bd_connection);
rc = ddf_fun_bind(fun);
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed binding device function");
goto destroy;
}
rc = ddf_fun_add_to_category(fun, "disk");
if (rc != EOK) {
ddf_msg(LVL_ERROR, "Failed adding function to category");
goto unbind;
}
ddf_msg(LVL_NOTE, "The %s device has been successfully initialized.",
ddf_dev_get_name(dev));
return EOK;
unbind:
ddf_fun_unbind(fun);
destroy:
ddf_fun_destroy(fun);
uninitialize:
virtio_blk_uninitialize(dev);
return rc;
}
int main(void)
{
printf("%s: HelenOS virtio-blk driver\n", NAME);
(void) ddf_log_init(NAME);
return ddf_driver_main(&virtio_blk_driver);
}
HelenOS homepage, sources at GitHub