diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index b5158e85aa7..64c31bfb445 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -38,5 +38,17 @@ config DRIVERS_VIRTIO_NET_QUEUE_LEN default 16 endif + +menuconfig DRIVERS_VIRTIO_BLK + bool "Virtio block support" + default n + depends on DRIVERS_VIRTIO_MMIO_NUM > 0 + endif +if DRIVERS_VIRTIO_BLK +config DRIVERS_VIRTIO_BLK_QUEUE_LEN + int "Queue length" + default 16 + +endif diff --git a/drivers/virtio/Make.defs b/drivers/virtio/Make.defs index 6f1b35ed835..889bdcd1385 100644 --- a/drivers/virtio/Make.defs +++ b/drivers/virtio/Make.defs @@ -28,6 +28,10 @@ ifeq ($(CONFIG_DRIVERS_VIRTIO_NET),y) CSRCS += virtio-mmio-net.c endif +ifeq ($(CONFIG_DRIVERS_VIRTIO_BLK),y) + CSRCS += virtio-mmio-blk.c +endif + # Include build support DEPPATH += --dep-path virtio diff --git a/drivers/virtio/virtio-mmio-blk.c b/drivers/virtio/virtio-mmio-blk.c new file mode 100644 index 00000000000..31e815c6b9e --- /dev/null +++ b/drivers/virtio/virtio-mmio-blk.c @@ -0,0 +1,524 @@ +/**************************************************************************** + * drivers/virtio/virtio-mmio-blk.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "virtio-mmio-blk.h" + +#ifdef CONFIG_DRIVERS_VIRTIO_BLK + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define VIRTIO_BLK_REQ_HEADER_SIZE 16 +#define VIRTIO_BLK_REQ_FOOTER_SIZE 1 + +#define VIRTIO_BLK_IN 0 /* read */ +#define VIRTIO_BLK_OUT 1 /* write */ + +#define VIRTIO_BLK_Q 0 + +#define VIRTIO_BLK_SECTOR_SIZE 512 + +/* VIRTIO_BLK_NINTERFACES determines the number of + * physical interfaces that will be supported. + */ + +#ifndef VIRTIO_BLK_NINTERFACES +# define VIRTIO_BLK_NINTERFACES 1 +#endif + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Block driver methods *****************************************************/ + +static int virtblk_open(FAR struct inode *inode); +static int virtblk_close(FAR struct inode *inode); +static ssize_t virtblk_read(FAR struct inode *inode, + FAR unsigned char *buffer, + blkcnt_t startsector, unsigned int nsectors); +static ssize_t virtblk_write(FAR struct inode *inode, + FAR const unsigned char *buffer, + blkcnt_t startsector, + unsigned int nsectors); +static int virtblk_geometry(FAR struct inode *inode, + FAR struct geometry *geometry); +static int virtblk_ioctl(FAR struct inode *inode, int cmd, + unsigned long arg); + +static int virtblk_interrupt(int irq, FAR void *context, FAR void *arg); + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct virtio_blk_req +{ + uint32_t type; + uint32_t reserved; + uint64_t sector; + uint8_t status; + uint8_t pad[3]; + uint32_t descriptor; +}; + +struct virtblk_driver_s +{ + int irq; + + FAR struct virtio_mmio_regs *regs; /* virtio_mmio registers */ + FAR struct virtqueue *txq; /* TX queue */ + + uint64_t nsectors; /* number of sectors on device */ + sem_t req_sem; /* semaphore for virtio request */ +}; + +struct virtio_blk_config +{ + uint64_t capacity; + uint32_t size_max; + uint32_t seg_max; + uint32_t chs; + uint32_t blk_size; + uint64_t topology; + uint8_t writeback; +}; + +/* Driver state structure */ + +static struct virtblk_driver_s g_virtblk[VIRTIO_BLK_NINTERFACES]; + +static uint32_t g_virtblk_ninterfaces = 0; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct block_operations g_virtblk_bops = +{ + virtblk_open, /* open */ + virtblk_close, /* close */ + virtblk_read, /* read */ + virtblk_write, /* write */ + virtblk_geometry, /* geometry */ + virtblk_ioctl /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: virtblk_rw_common + * + * Description: + * Common function for read and write + * + ****************************************************************************/ + +static ssize_t virtblk_rw_common(FAR struct virtblk_driver_s *priv, + FAR void *buffer, + blkcnt_t startsector, + unsigned int nsectors, + bool write) +{ + struct virtio_blk_req req[2]; + uint16_t idx; + uint32_t d1; + uint32_t d2; + uint32_t d3; + + memset(req, 0, sizeof(req)); + idx = priv->txq->avail->idx; + + if (write) + { + req[0].type = VIRTIO_BLK_OUT; + } + else + { + req[0].type = VIRTIO_BLK_IN; + } + + req[0].sector = startsector; + + /* Allocate descriptor for header */ + + d1 = virtq_alloc_desc(priv->txq, idx, + (FAR void *)&req[0]); + d2 = virtq_alloc_desc(priv->txq, idx + 1, + (FAR void *)buffer); + DEBUGASSERT(d1 != d2); + + req[0].descriptor = d1; + + d3 = virtq_alloc_desc(priv->txq, idx + 2, + (FAR void *)&req[1]); + DEBUGASSERT(d2 != d3); + + /* Set up the descriptor for d1 (header) */ + + priv->txq->desc[d1].len = VIRTIO_BLK_REQ_HEADER_SIZE; + priv->txq->desc[d1].flags = VIRTQ_DESC_F_NEXT; + priv->txq->desc[d1].next = d2; + + /* Set up the descriptor for d2 (sector buffer) */ + + priv->txq->desc[d2].len = VIRTIO_BLK_SECTOR_SIZE * nsectors; + priv->txq->desc[d2].flags = VIRTQ_DESC_F_NEXT; + + if (!write) + { + /* Make the sector buffer writable for read operation */ + + priv->txq->desc[d2].flags |= VIRTQ_DESC_F_WRITE; + } + + priv->txq->desc[d2].next = d3; + + /* Set up the descriptor for d3 (status) */ + + priv->txq->desc[d3].len = 1; + priv->txq->desc[d3].flags = VIRTQ_DESC_F_WRITE; + priv->txq->desc[d3].next = 0; + + /* Set the first descriptor to the avail->ring */ + + priv->txq->avail->ring[d1] = d1; + + /* Increment the avail->idx for each three descriptors */ + + virtio_mb(); + priv->txq->avail->idx += 1; + + /* Send request */ + + virtio_putreg32(VIRTIO_BLK_Q, &priv->regs->queue_notify); + + finfo("*** finish updating queue_notify\n"); + + /* Wait for the request completion */ + + nxsem_wait_uninterruptible(&priv->req_sem); + + /* On success, return the number of blocks read */ + + return nsectors; +} + +/**************************************************************************** + * Name: virtblk_open + * + * Description: Open the block device + * + ****************************************************************************/ + +static int virtblk_open(FAR struct inode *inode) +{ + finfo("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + + return OK; +} + +/**************************************************************************** + * Name: virtblk_close + * + * Description: close the block device + * + ****************************************************************************/ + +static int virtblk_close(FAR struct inode *inode) +{ + finfo("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + + return OK; +} + +/**************************************************************************** + * Name: virtblk_read + * + * Description: + * Read the specified number of sectors from the read-ahead buffer or from + * the physical device. + * + ****************************************************************************/ + +static ssize_t virtblk_read(FAR struct inode *inode, + FAR unsigned char *buffer, + blkcnt_t startsector, unsigned int nsectors) +{ + FAR struct virtblk_driver_s *priv; + + finfo("Entry: nsectors=%d \n", nsectors); + DEBUGASSERT(inode && inode->i_private); + + priv = (FAR struct virtblk_driver_s *)inode->i_private; + return virtblk_rw_common(priv, + buffer, + startsector, nsectors, false); +} + +/**************************************************************************** + * Name: virtblk_write + * + * Description: + * Write the specified number of sectors to the write buffer or to the + * physical device. + * + ****************************************************************************/ + +static ssize_t virtblk_write(FAR struct inode *inode, + FAR const unsigned char *buffer, + blkcnt_t startsector, unsigned int nsectors) +{ + FAR struct virtblk_driver_s *priv; + + finfo("Entry: nsectors=%d \n", nsectors); + DEBUGASSERT(inode && inode->i_private); + + priv = (FAR struct virtblk_driver_s *)inode->i_private; + + return virtblk_rw_common(priv, + (FAR void *)buffer, + startsector, nsectors, true); +} + +/**************************************************************************** + * Name: virtblk_geometry + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int virtblk_geometry(FAR struct inode *inode, + FAR struct geometry *geometry) +{ + FAR struct virtblk_driver_s *priv; + int ret = -EINVAL; + + finfo("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + + priv = (FAR struct virtblk_driver_s *)inode->i_private; + + if (geometry) + { + geometry->geo_available = true; + geometry->geo_mediachanged = false; + geometry->geo_writeenabled = true; + geometry->geo_nsectors = priv->nsectors; + geometry->geo_sectorsize = VIRTIO_BLK_SECTOR_SIZE; + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: virtblk_ioctl + * + * Description: Return device geometry + * + ****************************************************************************/ + +static int virtblk_ioctl(FAR struct inode *inode, + int cmd, unsigned long arg) +{ + int ret = -ENOTTY; + + finfo("Entry\n"); + DEBUGASSERT(inode && inode->i_private); + + return ret; +} + +/**************************************************************************** + * Name: virtblk_interrupt + * + * Description: + * Hardware interrupt handler + * + * Input Parameters: + * irq - Number of the IRQ that generated the interrupt + * context - Interrupt register state save info (architecture-specific) + * + * Returned Value: + * OK on success + * + * Assumptions: + * Runs in the context of a the Ethernet interrupt handler. Local + * interrupts are disabled by the interrupt logic. + * + ****************************************************************************/ + +static int virtblk_interrupt(int irq, FAR void *context, FAR void *arg) +{ + FAR struct virtblk_driver_s *priv = (FAR struct virtblk_driver_s *)arg; + uint32_t stat; + + DEBUGASSERT(priv != NULL); + + /* Get and clear interrupt status bits */ + + stat = virtio_getreg32(&priv->regs->interrupt_status); + virtio_putreg32(stat, &priv->regs->interrupt_ack); + finfo("+++ called (stat=0x%" PRIx32 ")\n", stat); + + nxsem_post(&priv->req_sem); + return OK; +} + +/**************************************************************************** + * Name: virtblk_initialize + * + * Description: + * Initialize the virt-blk driver + * + * Input Parameters: + * + * + * Returned Value: + * OK on success; Negated errno on failure. + * + * Assumptions: + * Called early in initialization before multi-tasking is initiated. + * + ****************************************************************************/ + +static int virtblk_initialize(FAR struct virtio_mmio_regs *regs, int irq) +{ + FAR struct virtio_blk_config *blk_config; + FAR struct virtblk_driver_s *priv; + char devname[16]; + int ret = -ENOMEM; + + priv = &g_virtblk[g_virtblk_ninterfaces]; + + /* Initialize the driver structure */ + + memset(priv, 0, sizeof(struct virtblk_driver_s)); + + /* Check if a Ethernet chip is recognized at its I/O base */ + + /* Attach the IRQ to the driver */ + + priv->irq = irq; + + if (irq_attach(priv->irq, virtblk_interrupt, priv)) + { + /* We could not attach the ISR to the interrupt */ + + return -EAGAIN; + } + + /* Setup virtio related */ + + priv->regs = regs; + priv->txq = virtq_create(CONFIG_DRIVERS_VIRTIO_BLK_QUEUE_LEN); + + virtq_add_to_mmio_device(regs, priv->txq, VIRTIO_BLK_Q); + + nxsem_init(&priv->req_sem, 0, 0); + + /* Create a ramdisk device name */ + + snprintf(devname, 16, "/dev/virtblk%" PRId32, g_virtblk_ninterfaces); + + blk_config = (struct virtio_blk_config *)regs->config; + + finfo("capacity=%" PRId64 " (sectors) \n", blk_config->capacity); + + /* Save the capacity to nsectors */ + + priv->nsectors = blk_config->capacity; + + /* Register the device with the OS */ + + ret = register_blockdriver(devname, &g_virtblk_bops, 0, priv); + up_enable_irq(priv->irq); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: virtio_mmio_blk_init + * + * Description: + * Called from virtio-mmio.c to initialize virtblk + * + ****************************************************************************/ + +int virtio_mmio_blk_init(FAR struct virtio_mmio_regs *regs, uint32_t irq) +{ + int ret = OK; + + /* TODO: feature negotiation */ + + /* Set STATUS_FEATURE_OK */ + + virtio_putreg32(virtio_getreg32(®s->status) | VIRTIO_STATUS_FEATURES_OK, + ®s->status); + virtio_mb(); + + ret = virtblk_initialize(regs, irq); + + if (OK != ret) + { + vrterr("error: virtblk_initialize() returned %d \n", ret); + return ret; + } + + /* Set STATUS_FRIVER_OK */ + + virtio_putreg32(virtio_getreg32(®s->status) | VIRTIO_STATUS_DRIVER_OK, + ®s->status); + virtio_mb(); + + g_virtblk_ninterfaces++; + + return ret; +} + +#endif /* CONFIG_DRIVERS_VIRTIO_BLK */ diff --git a/drivers/virtio/virtio-mmio-blk.h b/drivers/virtio/virtio-mmio-blk.h new file mode 100644 index 00000000000..7f4749f5ad7 --- /dev/null +++ b/drivers/virtio/virtio-mmio-blk.h @@ -0,0 +1,68 @@ +/**************************************************************************** + * drivers/virtio/virtio-mmio-blk.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __DRIVERS_VIRTIO_VIRTIO_MMIO_BLK_H +#define __DRIVERS_VIRTIO_VIRTIO_MMIO_BLK_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef CONFIG_DRIVERS_VIRTIO_BLK + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: virtio_mmio_blk_init + * + * Description: + * Called from virtio-mmio.c to initialize virtblk + * + ****************************************************************************/ + +int virtio_mmio_blk_init(FAR struct virtio_mmio_regs *regs, uint32_t intid); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_DRIVERS_VIRTIO_BLK */ +#endif /* __DRIVERS_VIRTIO_VIRTIO_MMIO_BLK_H */ diff --git a/drivers/virtio/virtio-mmio.c b/drivers/virtio/virtio-mmio.c index 073a27a5100..f5a380f6ae1 100644 --- a/drivers/virtio/virtio-mmio.c +++ b/drivers/virtio/virtio-mmio.c @@ -36,6 +36,10 @@ # include "virtio-mmio-net.h" #endif +#ifdef CONFIG_DRIVERS_VIRTIO_BLK +# include "virtio-mmio-blk.h" +#endif + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ @@ -145,6 +149,11 @@ static int virtio_dev_init(uintptr_t virt, uint32_t irq) case VIRTIO_DEV_NET: ret = virtio_mmio_net_init(regs, irq); break; +#endif +#ifdef CONFIG_DRIVERS_VIRTIO_BLK + case VIRTIO_DEV_BLK: + ret = virtio_mmio_blk_init(regs, irq); + break; #endif default: vrtwarn("unsupported device_id 0x%" PRIx32 "\n", val);