nuttx/drivers/virtio/virtio-mmio.c
Bowen Wang af39ee49d8 virtio-mmio: use nuttx implemented io operation instead libmetal's
Openamp/libmetal's metal io operation used the atomic operation, but
in QEMU 8.1.2, the risc-v IO region is not allow to use atomic instruction
in SMP case.

Signed-off-by: Bowen Wang <wangbowen6@xiaomi.com>
2023-11-17 09:42:52 +01:00

876 lines
28 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 || length > 8)
{
FAR char *s = src;
int i;
for (i = 0; i < length; i++)
{
metal_io_write8(&vmdev->cfg_io, write_offset + i, s[i]);
}
return;
}
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:
DEBUGASSERT(0);
}
}
/****************************************************************************
* 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 || length > 8)
{
FAR char *d = dst;
int i;
for (i = 0; i < length; i++)
{
d[i] = metal_io_read8(&vmdev->cfg_io, read_offset + i);
}
return;
}
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:
DEBUGASSERT(0);
}
}
/****************************************************************************
* 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);
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);
}
}
}
metal_io_write32(&vmdev->cfg_io, VIRTIO_MMIO_INTERRUPT_ACK, isr);
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)
{
vrterr("Device Id 0\n");
return -EINVAL;
}
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)
{
struct metal_init_params params = METAL_INIT_DEFAULTS;
FAR struct virtio_mmio_device_s *vmdev;
static bool onceinit;
int ret;
DEBUGASSERT(regs != NULL);
if (onceinit == false)
{
onceinit = true;
ret = metal_init(&params);
if (ret < 0)
{
return ret;
}
}
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 < 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;
}