/* * Copyright (c) 2019 Jakub Jermar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - 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. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "virtio-blk.h" #include #include #include #include #include #include #include #include #include #include #define NAME "virtio-blk" #define VIRTIO_BLK_NUM_QUEUES 1 #define RQ_QUEUE 0 /* * VIRTIO_BLK requests need at least two descriptors so that device-read-only * buffers are separated from device-writable buffers. For convenience, we * always use three descriptors for the request header, buffer and footer. * We therefore organize the virtque so that first RQ_BUFFERS descriptors are * used for request headers, the following RQ_BUFFERS descriptors are used * for in/out buffers and the last RQ_BUFFERS descriptors are used for request * footers. */ #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, 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; 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, &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; /* * Allocate a descriptor. * * The allocated descno will determine the header descriptor * (REQ_HEADER_DESC), the buffer descriptor (REQ_BUFFER_DESC) and the * footer (REQ_FOOTER_DESC) descriptor. */ 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); /* Setup the request header */ 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); /* Copy write data to the request. */ if (!read) memcpy(virtio_blk->rq_buf[descno], buf, VIRTIO_BLK_BLOCK_SIZE); fibril_mutex_lock(&virtio_blk->completion_lock[descno]); /* * Set the descriptors, chain them in the virtqueue and notify the * device. */ 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); /* * Wait for the completion of the request. */ 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; } /* Copy read data from the request */ if (rc == EOK && read) memcpy(buf, virtio_blk->rq_buf[descno], VIRTIO_BLK_BLOCK_SIZE); /* Free the descriptor and buffer */ 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; /* * Register IRQ */ rc = virtio_blk_register_interrupt(dev); if (rc != EOK) goto fail; /* Reset the device and negotiate the feature bits */ rc = virtio_device_setup_start(vdev, 0); if (rc != EOK) goto fail; /* Perform device-specific setup */ /* * Discover and configure the virtqueue */ 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; } /* For each in/out request we need 3 descriptors */ rc = virtio_virtq_setup(vdev, RQ_QUEUE, 3 * RQ_BUFFERS); if (rc != EOK) goto fail; /* * Setup DMA buffers */ 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; /* * Put all request descriptors on a free list. Because of the * correspondence between the request, buffer and footer descriptors, * we only need to manage allocations for one set: the request header * descriptors. */ virtio_create_desc_free_list(vdev, RQ_QUEUE, RQ_BUFFERS, &virtio_blk->rq_free_head); /* * Enable IRQ */ 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); /* Go live */ 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); }