871 lines
27 KiB
C
871 lines
27 KiB
C
/****************************************************************************
|
|
* drivers/virtio/virtio-mmio.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 <nuttx/config.h>
|
|
|
|
#include <debug.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <sys/param.h>
|
|
|
|
#include <nuttx/virtio/virtio.h>
|
|
#include <nuttx/virtio/virtio-mmio.h>
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define VIRITO_PAGE_SHIFT 12
|
|
#define VIRTIO_PAGE_SIZE (1 << VIRITO_PAGE_SHIFT)
|
|
#define VIRTIO_VRING_ALIGN VIRTIO_PAGE_SIZE
|
|
|
|
#define VIRTIO_MMIO_VERSION_1 1
|
|
|
|
/* Control registers */
|
|
|
|
/* Magic value ("virt" string) - Read Only */
|
|
|
|
#define VIRTIO_MMIO_MAGIC_VALUE 0x000
|
|
#define VIRTIO_MMIO_MAGIC_VALUE_STRING ('v' | ('i' << 8) | ('r' << 16) | ('t' << 24))
|
|
|
|
/* Virtio device version - Read Only */
|
|
|
|
#define VIRTIO_MMIO_VERSION 0x004
|
|
|
|
/* Virtio device ID - Read Only */
|
|
|
|
#define VIRTIO_MMIO_DEVICE_ID 0x008
|
|
|
|
/* Virtio vendor ID - Read Only */
|
|
|
|
#define VIRTIO_MMIO_VENDOR_ID 0x00c
|
|
|
|
/* Bitmask of the features supported by the device (host)
|
|
* (32 bits per set) - Read Only
|
|
*/
|
|
|
|
#define VIRTIO_MMIO_DEVICE_FEATURES 0x010
|
|
|
|
/* Device (host) features set selector - Write Only */
|
|
|
|
#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014
|
|
|
|
/* Bitmask of features activated by the driver (guest)
|
|
* (32 bits per set) - Write Only
|
|
*/
|
|
|
|
#define VIRTIO_MMIO_DRIVER_FEATURES 0x020
|
|
|
|
/* Activated features set selector - Write Only */
|
|
|
|
#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024
|
|
|
|
/* [VERSION 1 REGISTER] Guest page size */
|
|
|
|
#define VIRTIO_MMIO_PAGE_SIZE 0X028
|
|
|
|
/* Queue selector - Write Only */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_SEL 0x030
|
|
|
|
/* Maximum size of the currently selected queue - Read Only */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034
|
|
|
|
/* Queue size for the currently selected queue - Write Only */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_NUM 0x038
|
|
|
|
/* [VERSION 1 REGISTER] Used Ring alignment in the virtual queue */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_ALIGN 0x03c
|
|
|
|
/* [VERSION 1 REGISTER] Guest physical page number of the virtual queue
|
|
* Writing to this register notifies the device about location
|
|
*/
|
|
|
|
#define VIRTIO_MMIO_QUEUE_PFN 0x040
|
|
|
|
/* Ready bit for the currently selected queue - Read Write */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_READY 0x044
|
|
|
|
/* Queue notifier - Write Only */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050
|
|
|
|
/* Interrupt status - Read Only */
|
|
|
|
#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060
|
|
|
|
/* Interrupt acknowledge - Write Only */
|
|
|
|
#define VIRTIO_MMIO_INTERRUPT_ACK 0x064
|
|
#define VIRTIO_MMIO_INTERRUPT_VRING (1 << 0)
|
|
#define VIRTIO_MMIO_INTERRUPT_CONFIG (1 << 1)
|
|
|
|
/* Device status register - Read Write */
|
|
|
|
#define VIRTIO_MMIO_STATUS 0x070
|
|
|
|
/* Selected queue's Descriptor Table address, 64 bits in two halves */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080
|
|
#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084
|
|
|
|
/* Selected queue's Available Ring address, 64 bits in two halves */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_AVAIL_LOW 0x090
|
|
#define VIRTIO_MMIO_QUEUE_AVAIL_HIGH 0x094
|
|
|
|
/* Selected queue's Used Ring address, 64 bits in two halves */
|
|
|
|
#define VIRTIO_MMIO_QUEUE_USED_LOW 0x0a0
|
|
#define VIRTIO_MMIO_QUEUE_USED_HIGH 0x0a4
|
|
|
|
/* Shared memory region id */
|
|
|
|
#define VIRTIO_MMIO_SHM_SEL 0x0ac
|
|
|
|
/* Shared memory region length, 64 bits in two halves */
|
|
|
|
#define VIRTIO_MMIO_SHM_LEN_LOW 0x0b0
|
|
#define VIRTIO_MMIO_SHM_LEN_HIGH 0x0b4
|
|
|
|
/* Shared memory region base address, 64 bits in two halves */
|
|
|
|
#define VIRTIO_MMIO_SHM_BASE_LOW 0x0b8
|
|
#define VIRTIO_MMIO_SHM_BASE_HIGH 0x0bc
|
|
|
|
/* Configuration atomicity value */
|
|
|
|
#define VIRTIO_MMIO_CONFIG_GENERATION 0x0fc
|
|
|
|
/* The config space is defined by each driver as
|
|
* the per-driver configuration space - Read Write
|
|
*/
|
|
|
|
#define VIRTIO_MMIO_CONFIG 0x100
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct virtio_mmio_device_s
|
|
{
|
|
struct virtio_device vdev; /* Virtio deivce */
|
|
struct metal_io_region shm_io; /* Share memory io region, virtqueue
|
|
* use this io.
|
|
*/
|
|
struct metal_io_region cfg_io; /* Config memory io region, used to
|
|
* read/write mmio register
|
|
*/
|
|
metal_phys_addr_t shm_phy; /* Share memory physical address */
|
|
metal_phys_addr_t cfg_phy; /* Config memory physical address */
|
|
int irq; /* The mmio interrupt number */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Helper functions */
|
|
|
|
static uint32_t virtio_mmio_get_queue_len(FAR struct metal_io_region *io,
|
|
int idx);
|
|
static int virtio_mmio_config_virtqueue(FAR struct metal_io_region *io,
|
|
FAR struct virtqueue *vq);
|
|
static int virtio_mmio_init_device(FAR struct virtio_mmio_device_s *vmdev,
|
|
FAR void *regs, int irq);
|
|
|
|
/* Virtio mmio dispatch functions */
|
|
|
|
static int
|
|
virtio_mmio_create_virtqueue(FAR struct virtio_mmio_device_s *vmdev,
|
|
unsigned int i, FAR const char *name,
|
|
vq_callback callback);
|
|
static int virtio_mmio_create_virtqueues(FAR struct virtio_device *vdev,
|
|
unsigned int flags,
|
|
unsigned int nvqs,
|
|
FAR const char *names[],
|
|
vq_callback callbacks[]);
|
|
static void virtio_mmio_delete_virtqueues(FAR struct virtio_device *vdev);
|
|
static void virtio_mmio_set_status(FAR struct virtio_device *vdev,
|
|
uint8_t status);
|
|
static uint8_t virtio_mmio_get_status(FAR struct virtio_device *vdev);
|
|
static void virtio_mmio_write_config(FAR struct virtio_device *vdev,
|
|
uint32_t offset, void *dst,
|
|
int length);
|
|
static void virtio_mmio_read_config(FAR struct virtio_device *vdev,
|
|
uint32_t offset, FAR void *dst,
|
|
int length);
|
|
static uint32_t virtio_mmio_get_features(FAR struct virtio_device *vdev);
|
|
static void virtio_mmio_set_features(FAR struct virtio_device *vdev,
|
|
uint32_t features);
|
|
static uint32_t virtio_mmio_negotiate_features(struct virtio_device *vdev,
|
|
uint32_t features);
|
|
static void virtio_mmio_reset_device(FAR struct virtio_device *vdev);
|
|
static void virtio_mmio_notify(FAR struct virtqueue *vq);
|
|
|
|
/* Interrupt */
|
|
|
|
static int virtio_mmio_interrupt(int irq, FAR void *context, FAR void *arg);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct virtio_dispatch g_virtio_mmio_dispatch =
|
|
{
|
|
virtio_mmio_create_virtqueues, /* create_virtqueues */
|
|
virtio_mmio_delete_virtqueues, /* delete_virtqueues */
|
|
virtio_mmio_get_status, /* get_status */
|
|
virtio_mmio_set_status, /* set_status */
|
|
virtio_mmio_get_features, /* get_features */
|
|
virtio_mmio_set_features, /* set_features */
|
|
virtio_mmio_negotiate_features, /* negotiate_features */
|
|
virtio_mmio_read_config, /* read_config */
|
|
virtio_mmio_write_config, /* write_config */
|
|
virtio_mmio_reset_device, /* reset_device */
|
|
virtio_mmio_notify, /* notify */
|
|
NULL, /* notify_wait */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_get_queue_len
|
|
****************************************************************************/
|
|
|
|
static uint32_t virtio_mmio_get_queue_len(FAR struct metal_io_region *io,
|
|
int idx)
|
|
{
|
|
uint32_t len;
|
|
|
|
/* Select the queue we're interested in */
|
|
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_SEL, idx);
|
|
len = metal_io_read32(io, VIRTIO_MMIO_QUEUE_NUM_MAX);
|
|
|
|
if (CONFIG_DRIVERS_VIRTIO_MMIO_QUEUE_LEN != 0)
|
|
{
|
|
len = MIN(len, CONFIG_DRIVERS_VIRTIO_MMIO_QUEUE_LEN);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_config_virtqueue
|
|
****************************************************************************/
|
|
|
|
static int virtio_mmio_config_virtqueue(FAR struct metal_io_region *io,
|
|
FAR struct virtqueue *vq)
|
|
{
|
|
uint32_t version = vq->vq_dev->id.version;
|
|
uint64_t addr;
|
|
|
|
/* Select the queue we're interested in */
|
|
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_SEL, vq->vq_queue_index);
|
|
|
|
/* Queue shouldn't already be set up. */
|
|
|
|
if (metal_io_read32(io, version == VIRTIO_MMIO_VERSION_1 ?
|
|
VIRTIO_MMIO_QUEUE_PFN : VIRTIO_MMIO_QUEUE_READY))
|
|
{
|
|
vrterr("Virtio queue not ready\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Activate the queue */
|
|
|
|
if (version == VIRTIO_MMIO_VERSION_1)
|
|
{
|
|
uint64_t pfn = (uintptr_t)vq->vq_ring.desc >> VIRITO_PAGE_SHIFT;
|
|
|
|
vrtinfo("Legacy, desc=%p, pfn=0x%" PRIx64 ", align=%d\n",
|
|
vq->vq_ring.desc, pfn, VIRTIO_PAGE_SIZE);
|
|
|
|
/* virtio-mmio v1 uses a 32bit QUEUE PFN. If we have something
|
|
* that doesn't fit in 32bit, fail the setup rather than
|
|
* pretending to be successful.
|
|
*/
|
|
|
|
if (pfn >> 32)
|
|
{
|
|
vrterr("Legacy virtio-mmio used RAM shoud not above 0x%llxGB\n",
|
|
0x1ull << (2 + VIRITO_PAGE_SHIFT));
|
|
}
|
|
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_NUM, vq->vq_nentries);
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_ALIGN, VIRTIO_PAGE_SIZE);
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_PFN, pfn);
|
|
}
|
|
else
|
|
{
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_NUM, vq->vq_nentries);
|
|
|
|
addr = (uint64_t)(uintptr_t)vq->vq_ring.desc;
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_DESC_LOW, addr);
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_DESC_HIGH, addr >> 32);
|
|
|
|
addr = (uint64_t)(uintptr_t)vq->vq_ring.avail;
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_AVAIL_LOW, addr);
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, addr >> 32);
|
|
|
|
addr = (uint64_t)(uintptr_t)vq->vq_ring.used;
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_USED_LOW, addr);
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_USED_HIGH, addr >> 32);
|
|
|
|
metal_io_write32(io, VIRTIO_MMIO_QUEUE_READY, 1);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_create_virtqueue
|
|
****************************************************************************/
|
|
|
|
static int
|
|
virtio_mmio_create_virtqueue(FAR struct virtio_mmio_device_s *vmdev,
|
|
unsigned int i, FAR const char *name,
|
|
vq_callback callback)
|
|
{
|
|
FAR struct virtio_device *vdev = &vmdev->vdev;
|
|
FAR struct virtio_vring_info *vrinfo;
|
|
FAR struct vring_alloc_info *vralloc;
|
|
FAR struct virtqueue *vq;
|
|
int vringsize;
|
|
int ret;
|
|
|
|
/* Alloc virtqueue and init the vring info and vring alloc info */
|
|
|
|
vrinfo = &vdev->vrings_info[i];
|
|
vralloc = &vrinfo->info;
|
|
vralloc->num_descs = virtio_mmio_get_queue_len(&vmdev->cfg_io, i);
|
|
vq = virtqueue_allocate(vralloc->num_descs);
|
|
if (vq == NULL)
|
|
{
|
|
vrterr("virtqueue_allocate failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Init the vring info and vring alloc info */
|
|
|
|
vrinfo->vq = vq;
|
|
vrinfo->io = &vmdev->shm_io;
|
|
vrinfo->notifyid = i;
|
|
vralloc->align = VIRTIO_VRING_ALIGN;
|
|
vringsize = vring_size(vralloc->num_descs, VIRTIO_VRING_ALIGN);
|
|
vralloc->vaddr = virtio_zalloc_buf(vdev, vringsize, VIRTIO_VRING_ALIGN);
|
|
if (vralloc->vaddr == NULL)
|
|
{
|
|
vrterr("vring alloc failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize the virtio queue */
|
|
|
|
ret = virtqueue_create(vdev, i, name, vralloc, callback,
|
|
vdev->func->notify, vq);
|
|
if (ret < 0)
|
|
{
|
|
vrterr("virtqueue create error, ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
virtqueue_set_shmem_io(vq, &vmdev->shm_io);
|
|
|
|
/* Set the mmio virtqueue register */
|
|
|
|
ret = virtio_mmio_config_virtqueue(&vmdev->cfg_io, vq);
|
|
if (ret < 0)
|
|
{
|
|
vrterr("virtio_mmio_config_virtqueue failed, ret=%d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_create_virtqueues
|
|
****************************************************************************/
|
|
|
|
static int virtio_mmio_create_virtqueues(FAR struct virtio_device *vdev,
|
|
unsigned int flags,
|
|
unsigned int nvqs,
|
|
FAR const char *names[],
|
|
vq_callback callbacks[])
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vdev;
|
|
unsigned int i;
|
|
int ret = OK;
|
|
|
|
/* Alloc vring info */
|
|
|
|
vdev->vrings_num = nvqs;
|
|
vdev->vrings_info = kmm_zalloc(sizeof(struct virtio_vring_info) * nvqs);
|
|
if (vdev->vrings_info == NULL)
|
|
{
|
|
vrterr("alloc vrings info failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Alloc and init the virtqueue */
|
|
|
|
for (i = 0; i < nvqs; i++)
|
|
{
|
|
ret = virtio_mmio_create_virtqueue(vmdev, i, names[i], callbacks[i]);
|
|
if (ret < 0)
|
|
{
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
/* Finally, enable the interrupt */
|
|
|
|
up_enable_irq(vmdev->irq);
|
|
return ret;
|
|
|
|
err:
|
|
virtio_mmio_delete_virtqueues(vdev);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_delete_virtqueues
|
|
****************************************************************************/
|
|
|
|
static void virtio_mmio_delete_virtqueues(FAR struct virtio_device *vdev)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vdev;
|
|
FAR struct virtio_vring_info *vrinfo;
|
|
unsigned int i;
|
|
|
|
/* Disable interrupt first */
|
|
|
|
up_disable_irq(vmdev->irq);
|
|
|
|
/* Free the memory */
|
|
|
|
if (vdev->vrings_info != NULL)
|
|
{
|
|
for (i = 0; i < vdev->vrings_num; i++)
|
|
{
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_QUEUE_SEL, i);
|
|
if (vdev->id.version == VIRTIO_MMIO_VERSION_1)
|
|
{
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_QUEUE_PFN, 0);
|
|
}
|
|
else
|
|
{
|
|
/* Virtio 1.2: To stop using the queue the driver MUST write
|
|
* zero (0x0) to this QueueReady and MUST read the value back
|
|
* to ensure synchronization.
|
|
*/
|
|
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_QUEUE_READY, 0);
|
|
if (metal_io_read32(&vmdev->cfg_io, VIRTIO_MMIO_QUEUE_READY))
|
|
{
|
|
vrtwarn("queue ready set zero failed\n");
|
|
}
|
|
}
|
|
|
|
/* Free the vring buffer and virtqueue */
|
|
|
|
vrinfo = &vdev->vrings_info[i];
|
|
if (vrinfo->info.vaddr != NULL)
|
|
{
|
|
virtio_free_buf(vdev, vrinfo->info.vaddr);
|
|
}
|
|
|
|
if (vrinfo->vq != NULL)
|
|
{
|
|
virtqueue_free(vrinfo->vq);
|
|
}
|
|
}
|
|
|
|
kmm_free(vdev->vrings_info);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_set_status
|
|
****************************************************************************/
|
|
|
|
static void virtio_mmio_set_status(FAR struct virtio_device *vdev,
|
|
uint8_t status)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vdev;
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_STATUS, status);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_get_status
|
|
****************************************************************************/
|
|
|
|
static uint8_t virtio_mmio_get_status(FAR struct virtio_device *vdev)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vdev;
|
|
return metal_io_read32(&vmdev->cfg_io, VIRTIO_MMIO_STATUS);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_write_config
|
|
****************************************************************************/
|
|
|
|
static void virtio_mmio_write_config(FAR struct virtio_device *vdev,
|
|
uint32_t offset, void *src, int length)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vdev;
|
|
uint32_t write_offset = VIRTIO_MMIO_CONFIG + offset;
|
|
uint32_t u32data;
|
|
uint16_t u16data;
|
|
uint8_t u8data;
|
|
|
|
if (vdev->id.version == VIRTIO_MMIO_VERSION_1)
|
|
{
|
|
goto byte_write;
|
|
}
|
|
|
|
switch (length)
|
|
{
|
|
case 1:
|
|
memcpy(&u8data, src, sizeof(u8data));
|
|
metal_io_write8(&vmdev->cfg_io, write_offset, u8data);
|
|
break;
|
|
case 2:
|
|
memcpy(&u16data, src, sizeof(u16data));
|
|
metal_io_write16(&vmdev->cfg_io, write_offset, u16data);
|
|
break;
|
|
case 4:
|
|
memcpy(&u32data, src, sizeof(u32data));
|
|
metal_io_write32(&vmdev->cfg_io, write_offset, u32data);
|
|
break;
|
|
case 8:
|
|
memcpy(&u32data, src, sizeof(u32data));
|
|
metal_io_write32(&vmdev->cfg_io, write_offset, u32data);
|
|
memcpy(&u32data, src + sizeof(u32data), sizeof(u32data));
|
|
metal_io_write32(&vmdev->cfg_io, write_offset + sizeof(u32data),
|
|
u32data);
|
|
break;
|
|
default:
|
|
byte_write:
|
|
{
|
|
FAR char *s = src;
|
|
int i;
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
metal_io_write8(&vmdev->cfg_io, write_offset + i, s[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_read_config
|
|
****************************************************************************/
|
|
|
|
static void virtio_mmio_read_config(FAR struct virtio_device *vdev,
|
|
uint32_t offset, FAR void *dst,
|
|
int length)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vdev;
|
|
uint32_t read_offset = VIRTIO_MMIO_CONFIG + offset;
|
|
uint32_t u32data;
|
|
uint16_t u16data;
|
|
uint8_t u8data;
|
|
|
|
if (vdev->id.version == VIRTIO_MMIO_VERSION_1)
|
|
{
|
|
goto byte_read;
|
|
}
|
|
|
|
switch (length)
|
|
{
|
|
case 1:
|
|
u8data = metal_io_read8(&vmdev->cfg_io, read_offset);
|
|
memcpy(dst, &u8data, sizeof(u8data));
|
|
break;
|
|
case 2:
|
|
u16data = metal_io_read16(&vmdev->cfg_io, read_offset);
|
|
memcpy(dst, &u16data, sizeof(u16data));
|
|
break;
|
|
case 4:
|
|
u32data = metal_io_read32(&vmdev->cfg_io, read_offset);
|
|
memcpy(dst, &u32data, sizeof(u32data));
|
|
break;
|
|
case 8:
|
|
u32data = metal_io_read32(&vmdev->cfg_io, read_offset);
|
|
memcpy(dst, &u32data, sizeof(u32data));
|
|
u32data = metal_io_read32(&vmdev->cfg_io,
|
|
read_offset + sizeof(u32data));
|
|
memcpy(dst + sizeof(u32data), &u32data, sizeof(u32data));
|
|
break;
|
|
default:
|
|
byte_read:
|
|
{
|
|
FAR char *d = dst;
|
|
int i;
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
d[i] = metal_io_read8(&vmdev->cfg_io, read_offset + i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_get_features
|
|
****************************************************************************/
|
|
|
|
static uint32_t virtio_mmio_get_features(FAR struct virtio_device *vdev)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vdev;
|
|
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 0);
|
|
return metal_io_read32(&vmdev->cfg_io, VIRTIO_MMIO_DEVICE_FEATURES);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_set_features
|
|
****************************************************************************/
|
|
|
|
static void virtio_mmio_set_features(FAR struct virtio_device *vdev,
|
|
uint32_t features)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vdev;
|
|
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 0);
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_DRIVER_FEATURES, features);
|
|
vdev->features = features;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_negotiate_features
|
|
****************************************************************************/
|
|
|
|
static uint32_t virtio_mmio_negotiate_features(struct virtio_device *vdev,
|
|
uint32_t features)
|
|
{
|
|
features = features & virtio_mmio_get_features(vdev);
|
|
virtio_mmio_set_features(vdev, features);
|
|
return features;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_reset_device
|
|
****************************************************************************/
|
|
|
|
static void virtio_mmio_reset_device(FAR struct virtio_device *vdev)
|
|
{
|
|
virtio_mmio_set_status(vdev, VIRTIO_CONFIG_STATUS_RESET);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_notify
|
|
****************************************************************************/
|
|
|
|
static void virtio_mmio_notify(FAR struct virtqueue *vq)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev =
|
|
(FAR struct virtio_mmio_device_s *)vq->vq_dev;
|
|
|
|
/* VIRTIO_F_NOTIFICATION_DATA is not supported for now */
|
|
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_QUEUE_NOTIFY,
|
|
vq->vq_queue_index);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_interrupt
|
|
****************************************************************************/
|
|
|
|
static int virtio_mmio_interrupt(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev = arg;
|
|
FAR struct virtio_vring_info *vrings_info = vmdev->vdev.vrings_info;
|
|
FAR struct virtqueue *vq;
|
|
unsigned int i;
|
|
uint32_t isr;
|
|
|
|
isr = metal_io_read32(&vmdev->cfg_io, VIRTIO_MMIO_INTERRUPT_STATUS);
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_INTERRUPT_ACK, isr);
|
|
if (isr & VIRTIO_MMIO_INTERRUPT_VRING)
|
|
{
|
|
for (i = 0; i < vmdev->vdev.vrings_num; i++)
|
|
{
|
|
vq = vrings_info[i].vq;
|
|
if (vq->vq_used_cons_idx != vq->vq_ring.used->idx &&
|
|
vq->callback != NULL)
|
|
{
|
|
vq->callback(vq);
|
|
}
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_mmio_init_device
|
|
****************************************************************************/
|
|
|
|
static int virtio_mmio_init_device(FAR struct virtio_mmio_device_s *vmdev,
|
|
FAR void *regs, int irq)
|
|
{
|
|
FAR struct virtio_device *vdev = &vmdev->vdev;
|
|
uint32_t magic;
|
|
|
|
/* Save the irq */
|
|
|
|
vmdev->irq = irq;
|
|
|
|
/* Share memory io is used for the virtio buffer operations
|
|
* Config memory is used for the mmio register operations
|
|
*/
|
|
|
|
vmdev->shm_phy = (metal_phys_addr_t)0;
|
|
vmdev->cfg_phy = (metal_phys_addr_t)regs;
|
|
metal_io_init(&vmdev->shm_io, NULL, &vmdev->shm_phy,
|
|
SIZE_MAX, UINT_MAX, 0, metal_io_get_ops());
|
|
metal_io_init(&vmdev->cfg_io, regs, &vmdev->cfg_phy,
|
|
SIZE_MAX, UINT_MAX, 0, metal_io_get_ops());
|
|
|
|
/* Init the virtio device */
|
|
|
|
vdev->role = VIRTIO_DEV_DRIVER;
|
|
vdev->func = &g_virtio_mmio_dispatch;
|
|
|
|
magic = metal_io_read32(&vmdev->cfg_io, VIRTIO_MMIO_MAGIC_VALUE);
|
|
if (magic != VIRTIO_MMIO_MAGIC_VALUE_STRING)
|
|
{
|
|
vrterr("Bad magic value %" PRIu32 "\n", magic);
|
|
return -EINVAL;
|
|
}
|
|
|
|
vdev->id.version = metal_io_read32(&vmdev->cfg_io, VIRTIO_MMIO_VERSION);
|
|
vdev->id.device = metal_io_read32(&vmdev->cfg_io, VIRTIO_MMIO_DEVICE_ID);
|
|
if (vdev->id.device == 0)
|
|
{
|
|
vrtinfo("Device Id 0\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
vdev->id.vendor = metal_io_read32(&vmdev->cfg_io, VIRTIO_MMIO_VENDOR_ID);
|
|
|
|
/* Legacy mmio version, set the page size */
|
|
|
|
if (vdev->id.version == VIRTIO_MMIO_VERSION_1)
|
|
{
|
|
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_PAGE_SIZE,
|
|
VIRTIO_PAGE_SIZE);
|
|
}
|
|
|
|
vrtinfo("VIRTIO version: %"PRIu32" device: %"PRIu32" vendor: %"PRIx32"\n",
|
|
vdev->id.version, vdev->id.device, vdev->id.vendor);
|
|
|
|
/* Reset the virtio device and set ACK */
|
|
|
|
virtio_mmio_set_status(vdev, VIRTIO_CONFIG_STATUS_RESET);
|
|
virtio_mmio_set_status(vdev, VIRTIO_CONFIG_STATUS_ACK);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: virtio_register_mmio_device
|
|
*
|
|
* Description:
|
|
* Register virtio mmio device to the virtio bus
|
|
*
|
|
****************************************************************************/
|
|
|
|
int virtio_register_mmio_device(FAR void *regs, int irq)
|
|
{
|
|
FAR struct virtio_mmio_device_s *vmdev;
|
|
int ret;
|
|
|
|
DEBUGASSERT(regs != NULL);
|
|
|
|
vmdev = kmm_zalloc(sizeof(*vmdev));
|
|
if (vmdev == NULL)
|
|
{
|
|
vrterr("No enough memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = virtio_mmio_init_device(vmdev, regs, irq);
|
|
if (ret == -ENODEV)
|
|
{
|
|
vrtinfo("No virtio mmio device in regs=%p\n", regs);
|
|
goto err;
|
|
}
|
|
else if (ret < 0)
|
|
{
|
|
vrterr("virtio_mmio_device_init failed, ret=%d\n", ret);
|
|
goto err;
|
|
}
|
|
|
|
/* Attach the intterupt before register the device driver */
|
|
|
|
ret = irq_attach(irq, virtio_mmio_interrupt, vmdev);
|
|
if (ret < 0)
|
|
{
|
|
vrterr("irq_attach failed, ret=%d\n", ret);
|
|
goto err;
|
|
}
|
|
|
|
/* Register the virtio device */
|
|
|
|
ret = virtio_register_device(&vmdev->vdev);
|
|
if (ret < 0)
|
|
{
|
|
vrterr("virt_device_register failed, ret=%d\n", ret);
|
|
irq_detach(irq);
|
|
goto err;
|
|
}
|
|
|
|
return ret;
|
|
|
|
err:
|
|
kmm_free(vmdev);
|
|
return ret;
|
|
}
|