GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)

This commit is contained in:
lai
2026-09-06 03:52:57 +08:00
commit b1928b41c0
21813 changed files with 4413081 additions and 0 deletions
@@ -0,0 +1,62 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
# Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
#
# VirtIO is a virtualization standard for network and disk device drivers
# where just the guest's device driver "knows" it is running in a virtual
# environment, and cooperates with the hypervisor. This enables guests to
# get high performance network and disk operations, and gives most of the
# performance benefits of paravirtualization. In the U-Boot case, the guest
# is U-Boot itself, while the virtual environment are normally QEMU targets
# like ARM, RISC-V and x86.
#
# See http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.pdf for
# the VirtIO specification v1.0.
menu "VirtIO Drivers"
config VIRTIO
bool
help
This option is selected by any driver which implements the virtio
transport, such as CONFIG_VIRTIO_MMIO or CONFIG_VIRTIO_PCI.
config VIRTIO_MMIO
bool "Platform bus driver for memory mapped virtio devices"
select VIRTIO
help
This driver provides support for memory mapped virtio
platform device driver.
config VIRTIO_PCI
bool "PCI driver for virtio devices"
depends on DM_PCI
select VIRTIO
help
This driver provides support for virtio based paravirtual device
drivers over PCI.
config VIRTIO_SANDBOX
bool "Sandbox driver for virtio devices"
depends on SANDBOX
select VIRTIO
help
This driver provides support for Sandbox implementation of virtio
transport driver which is used for testing purpose only.
config VIRTIO_NET
bool "virtio net driver"
depends on VIRTIO
help
This is the virtual net driver for virtio. It can be used with
QEMU based targets.
config VIRTIO_BLK
bool "virtio block driver"
depends on VIRTIO
help
This is the virtual block driver for virtio. It can be used with
QEMU based targets.
endmenu
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
# Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
obj-y += virtio-uclass.o virtio_ring.o
obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o
obj-$(CONFIG_VIRTIO_PCI) += virtio_pci_legacy.o virtio_pci_modern.o
obj-$(CONFIG_VIRTIO_SANDBOX) += virtio_sandbox.o
obj-$(CONFIG_VIRTIO_NET) += virtio_net.o
obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
@@ -0,0 +1,369 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* VirtIO is a virtualization standard for network and disk device drivers
* where just the guest's device driver "knows" it is running in a virtual
* environment, and cooperates with the hypervisor. This enables guests to
* get high performance network and disk operations, and gives most of the
* performance benefits of paravirtualization. In the U-Boot case, the guest
* is U-Boot itself, while the virtual environment are normally QEMU targets
* like ARM, RISC-V and x86.
*
* See http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.pdf for
* the VirtIO specification v1.0.
*/
#include <common.h>
#include <dm.h>
#include <virtio_types.h>
#include <virtio.h>
#include <dm/lists.h>
static const char *const virtio_drv_name[VIRTIO_ID_MAX_NUM] = {
[VIRTIO_ID_NET] = VIRTIO_NET_DRV_NAME,
[VIRTIO_ID_BLOCK] = VIRTIO_BLK_DRV_NAME,
};
int virtio_get_config(struct udevice *vdev, unsigned int offset,
void *buf, unsigned int len)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->get_config(vdev->parent, offset, buf, len);
}
int virtio_set_config(struct udevice *vdev, unsigned int offset,
void *buf, unsigned int len)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->set_config(vdev->parent, offset, buf, len);
}
int virtio_generation(struct udevice *vdev, u32 *counter)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
if (!ops->generation)
return -ENOSYS;
return ops->generation(vdev->parent, counter);
}
int virtio_get_status(struct udevice *vdev, u8 *status)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->get_status(vdev->parent, status);
}
int virtio_set_status(struct udevice *vdev, u8 status)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->set_status(vdev->parent, status);
}
int virtio_reset(struct udevice *vdev)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->reset(vdev->parent);
}
int virtio_get_features(struct udevice *vdev, u64 *features)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->get_features(vdev->parent, features);
}
int virtio_set_features(struct udevice *vdev)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->set_features(vdev->parent);
}
int virtio_find_vqs(struct udevice *vdev, unsigned int nvqs,
struct virtqueue *vqs[])
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->find_vqs(vdev->parent, nvqs, vqs);
}
int virtio_del_vqs(struct udevice *vdev)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->del_vqs(vdev->parent);
}
int virtio_notify(struct udevice *vdev, struct virtqueue *vq)
{
struct dm_virtio_ops *ops;
ops = virtio_get_ops(vdev->parent);
return ops->notify(vdev->parent, vq);
}
void virtio_add_status(struct udevice *vdev, u8 status)
{
u8 old;
if (!virtio_get_status(vdev, &old))
virtio_set_status(vdev, old | status);
}
int virtio_finalize_features(struct udevice *vdev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(vdev->parent);
u8 status;
int ret;
ret = virtio_set_features(vdev);
if (ret)
return ret;
if (uc_priv->legacy)
return 0;
virtio_add_status(vdev, VIRTIO_CONFIG_S_FEATURES_OK);
ret = virtio_get_status(vdev, &status);
if (ret)
return ret;
if (!(status & VIRTIO_CONFIG_S_FEATURES_OK)) {
debug("(%s): device refuses features %x\n", vdev->name, status);
return -ENODEV;
}
return 0;
}
void virtio_driver_features_init(struct virtio_dev_priv *priv,
const u32 *feature,
u32 feature_size,
const u32 *feature_legacy,
u32 feature_legacy_size)
{
priv->feature_table = feature;
priv->feature_table_size = feature_size;
priv->feature_table_legacy = feature_legacy;
priv->feature_table_size_legacy = feature_legacy_size;
}
int virtio_init(void)
{
struct udevice *bus;
int ret;
/* Enumerate all known virtio devices */
ret = uclass_first_device(UCLASS_VIRTIO, &bus);
if (ret)
return ret;
while (bus) {
ret = uclass_next_device(&bus);
if (ret)
break;
}
return ret;
}
static int virtio_uclass_pre_probe(struct udevice *udev)
{
struct dm_virtio_ops *ops;
ops = (struct dm_virtio_ops *)(udev->driver->ops);
/*
* Check virtio transport driver ops here so that we don't need
* check these ops each time when the virtio_xxx APIs are called.
*
* Only generation op is optional. All other ops are must-have.
*/
if (!ops->get_config || !ops->set_config ||
!ops->get_status || !ops->set_status ||
!ops->get_features || !ops->set_features ||
!ops->find_vqs || !ops->del_vqs ||
!ops->reset || !ops->notify)
return -ENOENT;
return 0;
}
static int virtio_uclass_post_probe(struct udevice *udev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
char dev_name[30], *str;
struct udevice *vdev;
int ret;
if (uc_priv->device > VIRTIO_ID_MAX_NUM) {
debug("(%s): virtio device ID %d exceeds maximum num\n",
udev->name, uc_priv->device);
return 0;
}
if (!virtio_drv_name[uc_priv->device]) {
debug("(%s): underlying virtio device driver unavailable\n",
udev->name);
return 0;
}
snprintf(dev_name, sizeof(dev_name), "%s#%d",
virtio_drv_name[uc_priv->device], udev->seq);
str = strdup(dev_name);
if (!str)
return -ENOMEM;
ret = device_bind_driver(udev, virtio_drv_name[uc_priv->device],
str, &vdev);
if (ret == -ENOENT) {
debug("(%s): no driver configured\n", udev->name);
return 0;
}
if (ret) {
free(str);
return ret;
}
device_set_name_alloced(vdev);
INIT_LIST_HEAD(&uc_priv->vqs);
return 0;
}
static int virtio_uclass_child_post_bind(struct udevice *vdev)
{
/* Acknowledge that we've seen the device */
virtio_add_status(vdev, VIRTIO_CONFIG_S_ACKNOWLEDGE);
return 0;
}
static int virtio_uclass_child_pre_probe(struct udevice *vdev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(vdev->parent);
u64 device_features;
u64 driver_features;
u64 driver_features_legacy;
int i;
int ret;
/*
* Save the real virtio device (eg: virtio-net, virtio-blk) to
* the transport (parent) device's uclass priv for future use.
*/
uc_priv->vdev = vdev;
/*
* We always start by resetting the device, in case a previous driver
* messed it up. This also tests that code path a little.
*/
ret = virtio_reset(vdev);
if (ret)
goto err;
/* We have a driver! */
virtio_add_status(vdev, VIRTIO_CONFIG_S_DRIVER);
/* Figure out what features the device supports */
virtio_get_features(vdev, &device_features);
debug("(%s) plain device features supported %016llx\n",
vdev->name, device_features);
if (!(device_features & (1ULL << VIRTIO_F_VERSION_1)))
uc_priv->legacy = true;
/* Figure out what features the driver supports */
driver_features = 0;
for (i = 0; i < uc_priv->feature_table_size; i++) {
unsigned int f = uc_priv->feature_table[i];
WARN_ON(f >= 64);
driver_features |= (1ULL << f);
}
/* Some drivers have a separate feature table for virtio v1.0 */
if (uc_priv->feature_table_legacy) {
driver_features_legacy = 0;
for (i = 0; i < uc_priv->feature_table_size_legacy; i++) {
unsigned int f = uc_priv->feature_table_legacy[i];
WARN_ON(f >= 64);
driver_features_legacy |= (1ULL << f);
}
} else {
driver_features_legacy = driver_features;
}
if (uc_priv->legacy) {
debug("(%s): legacy virtio device\n", vdev->name);
uc_priv->features = driver_features_legacy & device_features;
} else {
debug("(%s): v1.0 complaint virtio device\n", vdev->name);
uc_priv->features = driver_features & device_features;
}
/* Transport features always preserved to pass to finalize_features */
for (i = VIRTIO_TRANSPORT_F_START; i < VIRTIO_TRANSPORT_F_END; i++)
if ((device_features & (1ULL << i)) &&
(i == VIRTIO_F_VERSION_1))
__virtio_set_bit(vdev->parent, i);
debug("(%s) final negotiated features supported %016llx\n",
vdev->name, uc_priv->features);
ret = virtio_finalize_features(vdev);
if (ret)
goto err;
return 0;
err:
virtio_add_status(vdev, VIRTIO_CONFIG_S_FAILED);
return ret;
}
static int virtio_uclass_child_post_probe(struct udevice *vdev)
{
/* Indicates that the driver is set up and ready to drive the device */
virtio_add_status(vdev, VIRTIO_CONFIG_S_DRIVER_OK);
return 0;
}
UCLASS_DRIVER(virtio) = {
.name = "virtio",
.id = UCLASS_VIRTIO,
.flags = DM_UC_FLAG_SEQ_ALIAS,
.pre_probe = virtio_uclass_pre_probe,
.post_probe = virtio_uclass_post_probe,
.child_post_bind = virtio_uclass_child_post_bind,
.child_pre_probe = virtio_uclass_child_pre_probe,
.child_post_probe = virtio_uclass_child_post_probe,
.per_device_auto_alloc_size = sizeof(struct virtio_dev_priv),
};
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*/
#include <common.h>
#include <blk.h>
#include <dm.h>
#include <virtio_types.h>
#include <virtio.h>
#include <virtio_ring.h>
#include "virtio_blk.h"
struct virtio_blk_priv {
struct virtqueue *vq;
};
static ulong virtio_blk_do_req(struct udevice *dev, u64 sector,
lbaint_t blkcnt, void *buffer, u32 type)
{
struct virtio_blk_priv *priv = dev_get_priv(dev);
unsigned int num_out = 0, num_in = 0;
struct virtio_sg *sgs[3];
u8 status;
int ret;
struct virtio_blk_outhdr out_hdr = {
.type = cpu_to_virtio32(dev, type),
.sector = cpu_to_virtio64(dev, sector),
};
struct virtio_sg hdr_sg = { &out_hdr, sizeof(out_hdr) };
struct virtio_sg data_sg = { buffer, blkcnt * 512 };
struct virtio_sg status_sg = { &status, sizeof(status) };
sgs[num_out++] = &hdr_sg;
if (type & VIRTIO_BLK_T_OUT)
sgs[num_out++] = &data_sg;
else
sgs[num_out + num_in++] = &data_sg;
sgs[num_out + num_in++] = &status_sg;
ret = virtqueue_add(priv->vq, sgs, num_out, num_in);
if (ret)
return ret;
virtqueue_kick(priv->vq);
while (!virtqueue_get_buf(priv->vq, NULL))
;
return status == VIRTIO_BLK_S_OK ? blkcnt : -EIO;
}
static ulong virtio_blk_read(struct udevice *dev, lbaint_t start,
lbaint_t blkcnt, void *buffer)
{
return virtio_blk_do_req(dev, start, blkcnt, buffer,
VIRTIO_BLK_T_IN);
}
static ulong virtio_blk_write(struct udevice *dev, lbaint_t start,
lbaint_t blkcnt, const void *buffer)
{
return virtio_blk_do_req(dev, start, blkcnt, (void *)buffer,
VIRTIO_BLK_T_OUT);
}
static int virtio_blk_bind(struct udevice *dev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
struct blk_desc *desc = dev_get_uclass_platdata(dev);
int devnum;
desc->if_type = IF_TYPE_VIRTIO;
/*
* Initialize the devnum to -ENODEV. This is to make sure that
* blk_next_free_devnum() works as expected, since the default
* value 0 is a valid devnum.
*/
desc->devnum = -ENODEV;
devnum = blk_next_free_devnum(IF_TYPE_VIRTIO);
if (devnum < 0)
return devnum;
desc->devnum = devnum;
desc->part_type = PART_TYPE_UNKNOWN;
/*
* virtio mmio transport supplies string identification for us,
* while pci trnasport uses a 2-byte subvendor value.
*/
if (uc_priv->vendor >> 16)
sprintf(desc->vendor, "%s", (char *)&uc_priv->vendor);
else
sprintf(desc->vendor, "%04x", uc_priv->vendor);
desc->bdev = dev;
/* Indicate what driver features we support */
virtio_driver_features_init(uc_priv, NULL, 0, NULL, 0);
return 0;
}
static int virtio_blk_probe(struct udevice *dev)
{
struct virtio_blk_priv *priv = dev_get_priv(dev);
struct blk_desc *desc = dev_get_uclass_platdata(dev);
u64 cap;
int ret;
ret = virtio_find_vqs(dev, 1, &priv->vq);
if (ret)
return ret;
desc->blksz = 512;
virtio_cread(dev, struct virtio_blk_config, capacity, &cap);
desc->lba = cap;
return 0;
}
static const struct blk_ops virtio_blk_ops = {
.read = virtio_blk_read,
.write = virtio_blk_write,
};
U_BOOT_DRIVER(virtio_blk) = {
.name = VIRTIO_BLK_DRV_NAME,
.id = UCLASS_BLK,
.ops = &virtio_blk_ops,
.bind = virtio_blk_bind,
.probe = virtio_blk_probe,
.remove = virtio_reset,
.priv_auto_alloc_size = sizeof(struct virtio_blk_priv),
.flags = DM_FLAG_ACTIVE_DMA,
};
@@ -0,0 +1,129 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* From Linux kernel include/uapi/linux/virtio_blk.h
*/
#ifndef _LINUX_VIRTIO_BLK_H
#define _LINUX_VIRTIO_BLK_H
/* Feature bits */
#define VIRTIO_BLK_F_SIZE_MAX 1 /* Indicates maximum segment size */
#define VIRTIO_BLK_F_SEG_MAX 2 /* Indicates maximum # of segments */
#define VIRTIO_BLK_F_GEOMETRY 4 /* Legacy geometry available */
#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */
#define VIRTIO_BLK_F_BLK_SIZE 6 /* Block size of disk is available */
#define VIRTIO_BLK_F_TOPOLOGY 10 /* Topology information is available */
#define VIRTIO_BLK_F_MQ 12 /* Support more than one vq */
/* Legacy feature bits */
#ifndef VIRTIO_BLK_NO_LEGACY
#define VIRTIO_BLK_F_BARRIER 0 /* Does host support barriers? */
#define VIRTIO_BLK_F_SCSI 7 /* Supports scsi command passthru */
#define VIRTIO_BLK_F_FLUSH 9 /* Flush command supported */
#define VIRTIO_BLK_F_CONFIG_WCE 11 /* Writeback mode available in config */
#ifndef __KERNEL__
/* Old (deprecated) name for VIRTIO_BLK_F_FLUSH */
#define VIRTIO_BLK_F_WCE VIRTIO_BLK_F_FLUSH
#endif
#endif /* !VIRTIO_BLK_NO_LEGACY */
#define VIRTIO_BLK_ID_BYTES 20 /* ID string length */
struct __packed virtio_blk_config {
/* The capacity (in 512-byte sectors) */
__u64 capacity;
/* The maximum segment size (if VIRTIO_BLK_F_SIZE_MAX) */
__u32 size_max;
/* The maximum number of segments (if VIRTIO_BLK_F_SEG_MAX) */
__u32 seg_max;
/* geometry of the device (if VIRTIO_BLK_F_GEOMETRY) */
struct virtio_blk_geometry {
__u16 cylinders;
__u8 heads;
__u8 sectors;
} geometry;
/* block size of device (if VIRTIO_BLK_F_BLK_SIZE) */
__u32 blk_size;
/* the next 4 entries are guarded by VIRTIO_BLK_F_TOPOLOGY */
/* exponent for physical block per logical block */
__u8 physical_block_exp;
/* alignment offset in logical blocks */
__u8 alignment_offset;
/* minimum I/O size without performance penalty in logical blocks */
__u16 min_io_size;
/* optimal sustained I/O size in logical blocks */
__u32 opt_io_size;
/* writeback mode (if VIRTIO_BLK_F_CONFIG_WCE) */
__u8 wce;
__u8 unused;
/* number of vqs, only available when VIRTIO_BLK_F_MQ is set */
__u16 num_queues;
};
/*
* Command types
*
* Usage is a bit tricky as some bits are used as flags and some are not.
*
* Rules:
* VIRTIO_BLK_T_OUT may be combined with VIRTIO_BLK_T_SCSI_CMD or
* VIRTIO_BLK_T_BARRIER. VIRTIO_BLK_T_FLUSH is a command of its own
* and may not be combined with any of the other flags.
*/
/* These two define direction */
#define VIRTIO_BLK_T_IN 0
#define VIRTIO_BLK_T_OUT 1
#ifndef VIRTIO_BLK_NO_LEGACY
/* This bit says it's a scsi command, not an actual read or write */
#define VIRTIO_BLK_T_SCSI_CMD 2
#endif /* VIRTIO_BLK_NO_LEGACY */
/* Cache flush command */
#define VIRTIO_BLK_T_FLUSH 4
/* Get device ID command */
#define VIRTIO_BLK_T_GET_ID 8
#ifndef VIRTIO_BLK_NO_LEGACY
/* Barrier before this op */
#define VIRTIO_BLK_T_BARRIER 0x80000000
#endif /* !VIRTIO_BLK_NO_LEGACY */
/*
* This comes first in the read scatter-gather list.
* For legacy virtio, if VIRTIO_F_ANY_LAYOUT is not negotiated,
* this is the first element of the read scatter-gather list.
*/
struct virtio_blk_outhdr {
/* VIRTIO_BLK_T* */
__virtio32 type;
/* io priority */
__virtio32 ioprio;
/* Sector (ie. 512 byte offset) */
__virtio64 sector;
};
#ifndef VIRTIO_BLK_NO_LEGACY
struct virtio_scsi_inhdr {
__virtio32 errors;
__virtio32 data_len;
__virtio32 sense_len;
__virtio32 residual;
};
#endif /* !VIRTIO_BLK_NO_LEGACY */
/* And this is the final byte of the write scatter-gather list */
#define VIRTIO_BLK_S_OK 0
#define VIRTIO_BLK_S_IOERR 1
#define VIRTIO_BLK_S_UNSUPP 2
#endif /* _LINUX_VIRTIO_BLK_H */
@@ -0,0 +1,413 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* VirtIO memory-maped I/O transport driver
* Ported from Linux drivers/virtio/virtio_mmio.c
*/
#include <common.h>
#include <dm.h>
#include <virtio_types.h>
#include <virtio.h>
#include <virtio_ring.h>
#include <linux/compat.h>
#include <linux/io.h>
#include "virtio_mmio.h"
static int virtio_mmio_get_config(struct udevice *udev, unsigned int offset,
void *buf, unsigned int len)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
void __iomem *base = priv->base + VIRTIO_MMIO_CONFIG;
u8 b;
__le16 w;
__le32 l;
if (priv->version == 1) {
u8 *ptr = buf;
int i;
for (i = 0; i < len; i++)
ptr[i] = readb(base + offset + i);
return 0;
}
switch (len) {
case 1:
b = readb(base + offset);
memcpy(buf, &b, sizeof(b));
break;
case 2:
w = cpu_to_le16(readw(base + offset));
memcpy(buf, &w, sizeof(w));
break;
case 4:
l = cpu_to_le32(readl(base + offset));
memcpy(buf, &l, sizeof(l));
break;
case 8:
l = cpu_to_le32(readl(base + offset));
memcpy(buf, &l, sizeof(l));
l = cpu_to_le32(readl(base + offset + sizeof(l)));
memcpy(buf + sizeof(l), &l, sizeof(l));
break;
default:
WARN_ON(true);
}
return 0;
}
static int virtio_mmio_set_config(struct udevice *udev, unsigned int offset,
const void *buf, unsigned int len)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
void __iomem *base = priv->base + VIRTIO_MMIO_CONFIG;
u8 b;
__le16 w;
__le32 l;
if (priv->version == 1) {
const u8 *ptr = buf;
int i;
for (i = 0; i < len; i++)
writeb(ptr[i], base + offset + i);
return 0;
}
switch (len) {
case 1:
memcpy(&b, buf, sizeof(b));
writeb(b, base + offset);
break;
case 2:
memcpy(&w, buf, sizeof(w));
writew(le16_to_cpu(w), base + offset);
break;
case 4:
memcpy(&l, buf, sizeof(l));
writel(le32_to_cpu(l), base + offset);
break;
case 8:
memcpy(&l, buf, sizeof(l));
writel(le32_to_cpu(l), base + offset);
memcpy(&l, buf + sizeof(l), sizeof(l));
writel(le32_to_cpu(l), base + offset + sizeof(l));
break;
default:
WARN_ON(true);
}
return 0;
}
static int virtio_mmio_generation(struct udevice *udev, u32 *counter)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
if (priv->version == 1)
*counter = 0;
else
*counter = readl(priv->base + VIRTIO_MMIO_CONFIG_GENERATION);
return 0;
}
static int virtio_mmio_get_status(struct udevice *udev, u8 *status)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
*status = readl(priv->base + VIRTIO_MMIO_STATUS) & 0xff;
return 0;
}
static int virtio_mmio_set_status(struct udevice *udev, u8 status)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
/* We should never be setting status to 0 */
WARN_ON(status == 0);
writel(status, priv->base + VIRTIO_MMIO_STATUS);
return 0;
}
static int virtio_mmio_reset(struct udevice *udev)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
/* 0 status means a reset */
writel(0, priv->base + VIRTIO_MMIO_STATUS);
return 0;
}
static int virtio_mmio_get_features(struct udevice *udev, u64 *features)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
writel(1, priv->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
*features = readl(priv->base + VIRTIO_MMIO_DEVICE_FEATURES);
*features <<= 32;
writel(0, priv->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
*features |= readl(priv->base + VIRTIO_MMIO_DEVICE_FEATURES);
return 0;
}
static int virtio_mmio_set_features(struct udevice *udev)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
/* Make sure there is are no mixed devices */
if (priv->version == 2 && uc_priv->legacy) {
debug("New virtio-mmio devices (version 2) must provide VIRTIO_F_VERSION_1 feature!\n");
return -EINVAL;
}
writel(1, priv->base + VIRTIO_MMIO_DRIVER_FEATURES_SEL);
writel((u32)(uc_priv->features >> 32),
priv->base + VIRTIO_MMIO_DRIVER_FEATURES);
writel(0, priv->base + VIRTIO_MMIO_DRIVER_FEATURES_SEL);
writel((u32)uc_priv->features,
priv->base + VIRTIO_MMIO_DRIVER_FEATURES);
return 0;
}
static struct virtqueue *virtio_mmio_setup_vq(struct udevice *udev,
unsigned int index)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
struct virtqueue *vq;
unsigned int num;
int err;
/* Select the queue we're interested in */
writel(index, priv->base + VIRTIO_MMIO_QUEUE_SEL);
/* Queue shouldn't already be set up */
if (readl(priv->base + (priv->version == 1 ?
VIRTIO_MMIO_QUEUE_PFN : VIRTIO_MMIO_QUEUE_READY))) {
err = -ENOENT;
goto error_available;
}
num = readl(priv->base + VIRTIO_MMIO_QUEUE_NUM_MAX);
if (num == 0) {
err = -ENOENT;
goto error_new_virtqueue;
}
/* Create the vring */
vq = vring_create_virtqueue(index, num, VIRTIO_MMIO_VRING_ALIGN, udev);
if (!vq) {
err = -ENOMEM;
goto error_new_virtqueue;
}
/* Activate the queue */
writel(virtqueue_get_vring_size(vq),
priv->base + VIRTIO_MMIO_QUEUE_NUM);
if (priv->version == 1) {
u64 q_pfn = virtqueue_get_desc_addr(vq) >> PAGE_SHIFT;
/*
* 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 (q_pfn >> 32) {
debug("platform bug: legacy virtio-mmio must not be used with RAM above 0x%llxGB\n",
0x1ULL << (32 + PAGE_SHIFT - 30));
err = -E2BIG;
goto error_bad_pfn;
}
writel(PAGE_SIZE, priv->base + VIRTIO_MMIO_QUEUE_ALIGN);
writel(q_pfn, priv->base + VIRTIO_MMIO_QUEUE_PFN);
} else {
u64 addr;
addr = virtqueue_get_desc_addr(vq);
writel((u32)addr, priv->base + VIRTIO_MMIO_QUEUE_DESC_LOW);
writel((u32)(addr >> 32),
priv->base + VIRTIO_MMIO_QUEUE_DESC_HIGH);
addr = virtqueue_get_avail_addr(vq);
writel((u32)addr, priv->base + VIRTIO_MMIO_QUEUE_AVAIL_LOW);
writel((u32)(addr >> 32),
priv->base + VIRTIO_MMIO_QUEUE_AVAIL_HIGH);
addr = virtqueue_get_used_addr(vq);
writel((u32)addr, priv->base + VIRTIO_MMIO_QUEUE_USED_LOW);
writel((u32)(addr >> 32),
priv->base + VIRTIO_MMIO_QUEUE_USED_HIGH);
writel(1, priv->base + VIRTIO_MMIO_QUEUE_READY);
}
return vq;
error_bad_pfn:
vring_del_virtqueue(vq);
error_new_virtqueue:
if (priv->version == 1) {
writel(0, priv->base + VIRTIO_MMIO_QUEUE_PFN);
} else {
writel(0, priv->base + VIRTIO_MMIO_QUEUE_READY);
WARN_ON(readl(priv->base + VIRTIO_MMIO_QUEUE_READY));
}
error_available:
return ERR_PTR(err);
}
static void virtio_mmio_del_vq(struct virtqueue *vq)
{
struct virtio_mmio_priv *priv = dev_get_priv(vq->vdev);
unsigned int index = vq->index;
/* Select and deactivate the queue */
writel(index, priv->base + VIRTIO_MMIO_QUEUE_SEL);
if (priv->version == 1) {
writel(0, priv->base + VIRTIO_MMIO_QUEUE_PFN);
} else {
writel(0, priv->base + VIRTIO_MMIO_QUEUE_READY);
WARN_ON(readl(priv->base + VIRTIO_MMIO_QUEUE_READY));
}
vring_del_virtqueue(vq);
}
static int virtio_mmio_del_vqs(struct udevice *udev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
struct virtqueue *vq, *n;
list_for_each_entry_safe(vq, n, &uc_priv->vqs, list)
virtio_mmio_del_vq(vq);
return 0;
}
static int virtio_mmio_find_vqs(struct udevice *udev, unsigned int nvqs,
struct virtqueue *vqs[])
{
int i;
for (i = 0; i < nvqs; ++i) {
vqs[i] = virtio_mmio_setup_vq(udev, i);
if (IS_ERR(vqs[i])) {
virtio_mmio_del_vqs(udev);
return PTR_ERR(vqs[i]);
}
}
return 0;
}
static int virtio_mmio_notify(struct udevice *udev, struct virtqueue *vq)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
/*
* We write the queue's selector into the notification register
* to signal the other end
*/
writel(vq->index, priv->base + VIRTIO_MMIO_QUEUE_NOTIFY);
return 0;
}
static int virtio_mmio_ofdata_to_platdata(struct udevice *udev)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
priv->base = (void __iomem *)(ulong)dev_read_addr(udev);
if (priv->base == (void __iomem *)FDT_ADDR_T_NONE)
return -EINVAL;
return 0;
}
static int virtio_mmio_probe(struct udevice *udev)
{
struct virtio_mmio_priv *priv = dev_get_priv(udev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
u32 magic;
/* Check magic value */
magic = readl(priv->base + VIRTIO_MMIO_MAGIC_VALUE);
if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) {
debug("(%s): wrong magic value 0x%08x!\n", udev->name, magic);
return 0;
}
/* Check device version */
priv->version = readl(priv->base + VIRTIO_MMIO_VERSION);
if (priv->version < 1 || priv->version > 2) {
debug("(%s): version %d not supported!\n",
udev->name, priv->version);
return 0;
}
/* Check devicd ID */
uc_priv->device = readl(priv->base + VIRTIO_MMIO_DEVICE_ID);
if (uc_priv->device == 0) {
/*
* virtio-mmio device with an ID 0 is a (dummy) placeholder
* with no function. End probing now with no error reported.
*/
return 0;
}
uc_priv->vendor = readl(priv->base + VIRTIO_MMIO_VENDOR_ID);
if (priv->version == 1)
writel(PAGE_SIZE, priv->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
debug("(%s): device (%d) vendor (%08x) version (%d)\n", udev->name,
uc_priv->device, uc_priv->vendor, priv->version);
return 0;
}
static const struct dm_virtio_ops virtio_mmio_ops = {
.get_config = virtio_mmio_get_config,
.set_config = virtio_mmio_set_config,
.generation = virtio_mmio_generation,
.get_status = virtio_mmio_get_status,
.set_status = virtio_mmio_set_status,
.reset = virtio_mmio_reset,
.get_features = virtio_mmio_get_features,
.set_features = virtio_mmio_set_features,
.find_vqs = virtio_mmio_find_vqs,
.del_vqs = virtio_mmio_del_vqs,
.notify = virtio_mmio_notify,
};
static const struct udevice_id virtio_mmio_ids[] = {
{ .compatible = "virtio,mmio" },
{ }
};
U_BOOT_DRIVER(virtio_mmio) = {
.name = "virtio-mmio",
.id = UCLASS_VIRTIO,
.of_match = virtio_mmio_ids,
.ops = &virtio_mmio_ops,
.probe = virtio_mmio_probe,
.ofdata_to_platdata = virtio_mmio_ofdata_to_platdata,
.priv_auto_alloc_size = sizeof(struct virtio_mmio_priv),
};
@@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* From Linux kernel include/uapi/linux/virtio_mmio.h
*/
#ifndef _LINUX_VIRTIO_MMIO_H
#define _LINUX_VIRTIO_MMIO_H
/* Control registers */
/* Magic value ("virt" string) - Read Only */
#define VIRTIO_MMIO_MAGIC_VALUE 0x000
/* 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
#ifndef VIRTIO_MMIO_NO_LEGACY /* LEGACY DEVICES ONLY! */
/* Guest's memory page size in bytes - Write Only */
#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028
#endif
/* 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
#ifndef VIRTIO_MMIO_NO_LEGACY /* LEGACY DEVICES ONLY! */
/* Used Ring alignment for the currently selected queue - Write Only */
#define VIRTIO_MMIO_QUEUE_ALIGN 0x03c
/* Guest's PFN for the currently selected queue - Read Write */
#define VIRTIO_MMIO_QUEUE_PFN 0x040
#endif
/* 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
/* 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
/* 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
/* Interrupt flags (re: interrupt status & acknowledge registers) */
#define VIRTIO_MMIO_INT_VRING BIT(0)
#define VIRTIO_MMIO_INT_CONFIG BIT(1)
/*
* The alignment to use between consumer and producer parts of vring.
* Currently hardcoded to the page size.
*/
#define PAGE_SHIFT 12
#define VIRTIO_MMIO_VRING_ALIGN PAGE_SIZE
/**
* virtio mmio transport driver private data
*
* @base: mmio transport device register base
* @version: mmio transport device version
*/
struct virtio_mmio_priv {
void __iomem *base;
u32 version;
};
#endif /* _LINUX_VIRTIO_MMIO_H */
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*/
#include <common.h>
#include <dm.h>
#include <net.h>
#include <virtio_types.h>
#include <virtio.h>
#include <virtio_ring.h>
#include "virtio_net.h"
/* Amount of buffers to keep in the RX virtqueue */
#define VIRTIO_NET_NUM_RX_BUFS 32
/*
* This value comes from the VirtIO spec: 1500 for maximum packet size,
* 14 for the Ethernet header, 12 for virtio_net_hdr. In total 1526 bytes.
*/
#define VIRTIO_NET_RX_BUF_SIZE 1526
struct virtio_net_priv {
union {
struct virtqueue *vqs[2];
struct {
struct virtqueue *rx_vq;
struct virtqueue *tx_vq;
};
};
char rx_buff[VIRTIO_NET_NUM_RX_BUFS][VIRTIO_NET_RX_BUF_SIZE];
bool rx_running;
int net_hdr_len;
};
/*
* For simplicity, the driver only negotiates the VIRTIO_NET_F_MAC feature.
* For the VIRTIO_NET_F_STATUS feature, we don't negotiate it, hence per spec
* we should assume the link is always active.
*/
static const u32 feature[] = {
VIRTIO_NET_F_MAC
};
static const u32 feature_legacy[] = {
VIRTIO_NET_F_MAC
};
static int virtio_net_start(struct udevice *dev)
{
struct virtio_net_priv *priv = dev_get_priv(dev);
struct virtio_sg sg;
struct virtio_sg *sgs[] = { &sg };
int i;
if (!priv->rx_running) {
/* receive buffer length is always 1526 */
sg.length = VIRTIO_NET_RX_BUF_SIZE;
/* setup the receive buffer address */
for (i = 0; i < VIRTIO_NET_NUM_RX_BUFS; i++) {
sg.addr = priv->rx_buff[i];
virtqueue_add(priv->rx_vq, sgs, 0, 1);
}
virtqueue_kick(priv->rx_vq);
/* setup the receive queue only once */
priv->rx_running = true;
}
return 0;
}
static int virtio_net_send(struct udevice *dev, void *packet, int length)
{
struct virtio_net_priv *priv = dev_get_priv(dev);
struct virtio_net_hdr hdr;
struct virtio_net_hdr_v1 hdr_v1;
struct virtio_sg hdr_sg;
struct virtio_sg data_sg = { packet, length };
struct virtio_sg *sgs[] = { &hdr_sg, &data_sg };
int ret;
if (priv->net_hdr_len == sizeof(struct virtio_net_hdr))
hdr_sg.addr = &hdr;
else
hdr_sg.addr = &hdr_v1;
hdr_sg.length = priv->net_hdr_len;
memset(hdr_sg.addr, 0, priv->net_hdr_len);
ret = virtqueue_add(priv->tx_vq, sgs, 2, 0);
if (ret)
return ret;
virtqueue_kick(priv->tx_vq);
while (1) {
if (virtqueue_get_buf(priv->tx_vq, NULL))
break;
}
return 0;
}
static int virtio_net_recv(struct udevice *dev, int flags, uchar **packetp)
{
struct virtio_net_priv *priv = dev_get_priv(dev);
unsigned int len;
void *buf;
buf = virtqueue_get_buf(priv->rx_vq, &len);
if (!buf)
return -EAGAIN;
*packetp = buf + priv->net_hdr_len;
return len - priv->net_hdr_len;
}
static int virtio_net_free_pkt(struct udevice *dev, uchar *packet, int length)
{
struct virtio_net_priv *priv = dev_get_priv(dev);
void *buf = packet - priv->net_hdr_len;
struct virtio_sg sg = { buf, VIRTIO_NET_RX_BUF_SIZE };
struct virtio_sg *sgs[] = { &sg };
/* Put the buffer back to the rx ring */
virtqueue_add(priv->rx_vq, sgs, 0, 1);
return 0;
}
static void virtio_net_stop(struct udevice *dev)
{
/*
* There is no way to stop the queue from running, unless we issue
* a reset to the virtio device, and re-do the queue initialization
* from the beginning.
*/
}
static int virtio_net_write_hwaddr(struct udevice *dev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
struct eth_pdata *pdata = dev_get_platdata(dev);
int i;
/*
* v1.0 compliant device's MAC address is set through control channel,
* which we don't support for now.
*/
if (!uc_priv->legacy)
return -ENOSYS;
for (i = 0; i < sizeof(pdata->enetaddr); i++) {
virtio_cwrite8(dev,
offsetof(struct virtio_net_config, mac) + i,
pdata->enetaddr[i]);
}
return 0;
}
static int virtio_net_read_rom_hwaddr(struct udevice *dev)
{
struct eth_pdata *pdata = dev_get_platdata(dev);
if (!pdata)
return -ENOSYS;
if (virtio_has_feature(dev, VIRTIO_NET_F_MAC)) {
virtio_cread_bytes(dev,
offsetof(struct virtio_net_config, mac),
pdata->enetaddr, sizeof(pdata->enetaddr));
}
return 0;
}
static int virtio_net_bind(struct udevice *dev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
/* Indicate what driver features we support */
virtio_driver_features_init(uc_priv, feature, ARRAY_SIZE(feature),
feature_legacy, ARRAY_SIZE(feature_legacy));
return 0;
}
static int virtio_net_probe(struct udevice *dev)
{
struct virtio_net_priv *priv = dev_get_priv(dev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
int ret;
ret = virtio_find_vqs(dev, 2, priv->vqs);
if (ret < 0)
return ret;
/*
* For v1.0 compliant device, it always assumes the member
* 'num_buffers' exists in the struct virtio_net_hdr while
* the legacy driver only presented 'num_buffers' when
* VIRTIO_NET_F_MRG_RXBUF was negotiated. Without that feature
* the structure was 2 bytes shorter.
*/
if (uc_priv->legacy)
priv->net_hdr_len = sizeof(struct virtio_net_hdr);
else
priv->net_hdr_len = sizeof(struct virtio_net_hdr_v1);
return 0;
}
static const struct eth_ops virtio_net_ops = {
.start = virtio_net_start,
.send = virtio_net_send,
.recv = virtio_net_recv,
.free_pkt = virtio_net_free_pkt,
.stop = virtio_net_stop,
.write_hwaddr = virtio_net_write_hwaddr,
.read_rom_hwaddr = virtio_net_read_rom_hwaddr,
};
U_BOOT_DRIVER(virtio_net) = {
.name = VIRTIO_NET_DRV_NAME,
.id = UCLASS_ETH,
.bind = virtio_net_bind,
.probe = virtio_net_probe,
.remove = virtio_reset,
.ops = &virtio_net_ops,
.priv_auto_alloc_size = sizeof(struct virtio_net_priv),
.platdata_auto_alloc_size = sizeof(struct eth_pdata),
.flags = DM_FLAG_ACTIVE_DMA,
};
@@ -0,0 +1,268 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* From Linux kernel include/uapi/linux/virtio_net.h
*/
#ifndef _LINUX_VIRTIO_NET_H
#define _LINUX_VIRTIO_NET_H
/* TODO: needs to be removed! */
#define ETH_ALEN 6
/* The feature bitmap for virtio net */
/* Host handles pkts w/ partial csum */
#define VIRTIO_NET_F_CSUM 0
/* Guest handles pkts w/ partial csum */
#define VIRTIO_NET_F_GUEST_CSUM 1
/* Dynamic offload configuration */
#define VIRTIO_NET_F_CTRL_GUEST_OFFLOADS 2
/* Initial MTU advice */
#define VIRTIO_NET_F_MTU 3
/* Host has given MAC address */
#define VIRTIO_NET_F_MAC 5
/* Guest can handle TSOv4 in */
#define VIRTIO_NET_F_GUEST_TSO4 7
/* Guest can handle TSOv6 in */
#define VIRTIO_NET_F_GUEST_TSO6 8
/* Guest can handle TSO[6] w/ ECN in */
#define VIRTIO_NET_F_GUEST_ECN 9
/* Guest can handle UFO in */
#define VIRTIO_NET_F_GUEST_UFO 10
/* Host can handle TSOv4 in */
#define VIRTIO_NET_F_HOST_TSO4 11
/* Host can handle TSOv6 in */
#define VIRTIO_NET_F_HOST_TSO6 12
/* Host can handle TSO[6] w/ ECN in */
#define VIRTIO_NET_F_HOST_ECN 13
/* Host can handle UFO in */
#define VIRTIO_NET_F_HOST_UFO 14
/* Host can merge receive buffers */
#define VIRTIO_NET_F_MRG_RXBUF 15
/* virtio_net_config.status available */
#define VIRTIO_NET_F_STATUS 16
/* Control channel available */
#define VIRTIO_NET_F_CTRL_VQ 17
/* Control channel RX mode support */
#define VIRTIO_NET_F_CTRL_RX 18
/* Control channel VLAN filtering */
#define VIRTIO_NET_F_CTRL_VLAN 19
/* Extra RX mode control support */
#define VIRTIO_NET_F_CTRL_RX_EXTRA 20
/* Guest can announce device on the network */
#define VIRTIO_NET_F_GUEST_ANNOUNCE 21
/* Device supports receive flow steering */
#define VIRTIO_NET_F_MQ 22
/* Set MAC address */
#define VIRTIO_NET_F_CTRL_MAC_ADDR 23
/* Device set linkspeed and duplex */
#define VIRTIO_NET_F_SPEED_DUPLEX 63
#ifndef VIRTIO_NET_NO_LEGACY
/* Host handles pkts w/ any GSO type */
#define VIRTIO_NET_F_GSO 6
#endif /* VIRTIO_NET_NO_LEGACY */
#define VIRTIO_NET_S_LINK_UP 1 /* Link is up */
#define VIRTIO_NET_S_ANNOUNCE 2 /* Announcement is needed */
struct __packed virtio_net_config {
/* The config defining mac address (if VIRTIO_NET_F_MAC) */
__u8 mac[ETH_ALEN];
/* See VIRTIO_NET_F_STATUS and VIRTIO_NET_S_* above */
__u16 status;
/*
* Maximum number of each of transmit and receive queues;
* see VIRTIO_NET_F_MQ and VIRTIO_NET_CTRL_MQ.
* Legal values are between 1 and 0x8000
*/
__u16 max_virtqueue_pairs;
/* Default maximum transmit unit advice */
__u16 mtu;
/*
* speed, in units of 1Mb. All values 0 to INT_MAX are legal.
* Any other value stands for unknown.
*/
__u32 speed;
/*
* 0x00 - half duplex
* 0x01 - full duplex
* Any other value stands for unknown.
*/
__u8 duplex;
};
/*
* This header comes first in the scatter-gather list. If you don't
* specify GSO or CSUM features, you can simply ignore the header.
*
* This is bitwise-equivalent to the legacy struct virtio_net_hdr_mrg_rxbuf,
* only flattened.
*/
struct virtio_net_hdr_v1 {
#define VIRTIO_NET_HDR_F_NEEDS_CSUM 0x01 /* Use csum_start, csum_offset */
#define VIRTIO_NET_HDR_F_DATA_VALID 0x02 /* Csum is valid */
__u8 flags;
#define VIRTIO_NET_HDR_GSO_NONE 0x00 /* Not a GSO frame */
#define VIRTIO_NET_HDR_GSO_TCPV4 0x01 /* GSO frame, IPv4 TCP (TSO) */
#define VIRTIO_NET_HDR_GSO_UDP 0x03 /* GSO frame, IPv4 UDP (UFO) */
#define VIRTIO_NET_HDR_GSO_TCPV6 0x04 /* GSO frame, IPv6 TCP */
#define VIRTIO_NET_HDR_GSO_ECN 0x80 /* TCP has ECN set */
__u8 gso_type;
__virtio16 hdr_len; /* Ethernet + IP + tcp/udp hdrs */
__virtio16 gso_size; /* Bytes to append to hdr_len per frame */
__virtio16 csum_start; /* Position to start checksumming from */
__virtio16 csum_offset; /* Offset after that to place checksum */
__virtio16 num_buffers; /* Number of merged rx buffers */
};
#ifndef VIRTIO_NET_NO_LEGACY
/*
* This header comes first in the scatter-gather list.
*
* For legacy virtio, if VIRTIO_F_ANY_LAYOUT is not negotiated, it must
* be the first element of the scatter-gather list. If you don't
* specify GSO or CSUM features, you can simply ignore the header.
*/
struct virtio_net_hdr {
/* See VIRTIO_NET_HDR_F_* */
__u8 flags;
/* See VIRTIO_NET_HDR_GSO_* */
__u8 gso_type;
__virtio16 hdr_len; /* Ethernet + IP + tcp/udp hdrs */
__virtio16 gso_size; /* Bytes to append to hdr_len per frame */
__virtio16 csum_start; /* Position to start checksumming from */
__virtio16 csum_offset; /* Offset after that to place checksum */
};
/*
* This is the version of the header to use when the MRG_RXBUF
* feature has been negotiated.
*/
struct virtio_net_hdr_mrg_rxbuf {
struct virtio_net_hdr hdr;
__virtio16 num_buffers; /* Number of merged rx buffers */
};
#endif /* ...VIRTIO_NET_NO_LEGACY */
/*
* Control virtqueue data structures
*
* The control virtqueue expects a header in the first sg entry
* and an ack/status response in the last entry. Data for the
* command goes in between.
*/
struct __packed virtio_net_ctrl_hdr {
__u8 class;
__u8 cmd;
};
typedef __u8 virtio_net_ctrl_ack;
#define VIRTIO_NET_OK 0
#define VIRTIO_NET_ERR 1
/*
* Control the RX mode, ie. promisucous, allmulti, etc...
*
* All commands require an "out" sg entry containing a 1 byte state value,
* zero = disable, non-zero = enable.
*
* Commands 0 and 1 are supported with the VIRTIO_NET_F_CTRL_RX feature.
* Commands 2-5 are added with VIRTIO_NET_F_CTRL_RX_EXTRA.
*/
#define VIRTIO_NET_CTRL_RX 0
#define VIRTIO_NET_CTRL_RX_PROMISC 0
#define VIRTIO_NET_CTRL_RX_ALLMULTI 1
#define VIRTIO_NET_CTRL_RX_ALLUNI 2
#define VIRTIO_NET_CTRL_RX_NOMULTI 3
#define VIRTIO_NET_CTRL_RX_NOUNI 4
#define VIRTIO_NET_CTRL_RX_NOBCAST 5
/*
* Control the MAC
*
* The MAC filter table is managed by the hypervisor, the guest should assume
* the size is infinite. Filtering should be considered non-perfect, ie. based
* on hypervisor resources, the guest may received packets from sources not
* specified in the filter list.
*
* In addition to the class/cmd header, the TABLE_SET command requires two
* out scatterlists. Each contains a 4 byte count of entries followed by a
* concatenated byte stream of the ETH_ALEN MAC addresses. The first sg list
* contains unicast addresses, the second is for multicast. This functionality
* is present if the VIRTIO_NET_F_CTRL_RX feature is available.
*
* The ADDR_SET command requests one out scatterlist, it contains a 6 bytes MAC
* address. This functionality is present if the VIRTIO_NET_F_CTRL_MAC_ADDR
* feature is available.
*/
struct __packed virtio_net_ctrl_mac {
__virtio32 entries;
__u8 macs[][ETH_ALEN];
};
#define VIRTIO_NET_CTRL_MAC 1
#define VIRTIO_NET_CTRL_MAC_TABLE_SET 0
#define VIRTIO_NET_CTRL_MAC_ADDR_SET 1
/*
* Control VLAN filtering
*
* The VLAN filter table is controlled via a simple ADD/DEL interface. VLAN IDs
* not added may be filterd by the hypervisor. Del is the opposite of add. Both
* commands expect an out entry containing a 2 byte VLAN ID. VLAN filterting is
* available with the VIRTIO_NET_F_CTRL_VLAN feature bit.
*/
#define VIRTIO_NET_CTRL_VLAN 2
#define VIRTIO_NET_CTRL_VLAN_ADD 0
#define VIRTIO_NET_CTRL_VLAN_DEL 1
/*
* Control link announce acknowledgment
*
* The command VIRTIO_NET_CTRL_ANNOUNCE_ACK is used to indicate that driver has
* recevied the notification; device would clear the VIRTIO_NET_S_ANNOUNCE bit
* in the status field after it receives this command.
*/
#define VIRTIO_NET_CTRL_ANNOUNCE 3
#define VIRTIO_NET_CTRL_ANNOUNCE_ACK 0
/*
* Control receive flow steering
*
* The command VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET enables receive flow steering,
* specifying the number of the transmit and receive queues that will be used.
* After the command is consumed and acked by the device, the device will not
* steer new packets on receive virtqueues other than specified nor read from
* transmit virtqueues other than specified. Accordingly, driver should not
* transmit new packets on virtqueues other than specified.
*/
struct virtio_net_ctrl_mq {
__virtio16 virtqueue_pairs;
};
#define VIRTIO_NET_CTRL_MQ 4
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 0
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN 1
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX 0x8000
/*
* Control network offloads
*
* Reconfigures the network offloads that guest can handle.
*
* Available with the VIRTIO_NET_F_CTRL_GUEST_OFFLOADS feature bit.
*
* Command data format matches the feature bit mask exactly.
*
* See VIRTIO_NET_F_GUEST_* for the list of offloads
* that can be enabled/disabled.
*/
#define VIRTIO_NET_CTRL_GUEST_OFFLOADS 5
#define VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET 0
#endif /* _LINUX_VIRTIO_NET_H */
@@ -0,0 +1,173 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/*
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* From Linux kernel include/uapi/linux/virtio_pci.h
*/
#ifndef _LINUX_VIRTIO_PCI_H
#define _LINUX_VIRTIO_PCI_H
#ifndef VIRTIO_PCI_NO_LEGACY
/* A 32-bit r/o bitmask of the features supported by the host */
#define VIRTIO_PCI_HOST_FEATURES 0
/* A 32-bit r/w bitmask of features activated by the guest */
#define VIRTIO_PCI_GUEST_FEATURES 4
/* A 32-bit r/w PFN for the currently selected queue */
#define VIRTIO_PCI_QUEUE_PFN 8
/* A 16-bit r/o queue size for the currently selected queue */
#define VIRTIO_PCI_QUEUE_NUM 12
/* A 16-bit r/w queue selector */
#define VIRTIO_PCI_QUEUE_SEL 14
/* A 16-bit r/w queue notifier */
#define VIRTIO_PCI_QUEUE_NOTIFY 16
/* An 8-bit device status register */
#define VIRTIO_PCI_STATUS 18
/*
* An 8-bit r/o interrupt status register. Reading the value will return the
* current contents of the ISR and will also clear it. This is effectively
* a read-and-acknowledge.
*/
#define VIRTIO_PCI_ISR 19
/* MSI-X registers: only enabled if MSI-X is enabled */
/* A 16-bit vector for configuration changes */
#define VIRTIO_MSI_CONFIG_VECTOR 20
/* A 16-bit vector for selected queue notifications */
#define VIRTIO_MSI_QUEUE_VECTOR 22
/*
* The remaining space is defined by each driver as the per-driver
* configuration space
*/
#define VIRTIO_PCI_CONFIG_OFF(msix) ((msix) ? 24 : 20)
/* Virtio ABI version, this must match exactly */
#define VIRTIO_PCI_ABI_VERSION 0
/*
* How many bits to shift physical queue address written to QUEUE_PFN.
* 12 is historical, and due to x86 page size.
*/
#define VIRTIO_PCI_QUEUE_ADDR_SHIFT 12
/*
* The alignment to use between consumer and producer parts of vring.
* x86 pagesize again.
*/
#define VIRTIO_PCI_VRING_ALIGN 4096
#endif /* VIRTIO_PCI_NO_LEGACY */
/* The bit of the ISR which indicates a device configuration change */
#define VIRTIO_PCI_ISR_CONFIG 0x2
/* Vector value used to disable MSI for queue */
#define VIRTIO_MSI_NO_VECTOR 0xffff
#ifndef VIRTIO_PCI_NO_MODERN
/* IDs for different capabilities. Must all exist. */
/* Common configuration */
#define VIRTIO_PCI_CAP_COMMON_CFG 1
/* Notifications */
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
/* ISR access */
#define VIRTIO_PCI_CAP_ISR_CFG 3
/* Device specific configuration */
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
/* PCI configuration access */
#define VIRTIO_PCI_CAP_PCI_CFG 5
/* This is the PCI capability header: */
struct virtio_pci_cap {
__u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */
__u8 cap_next; /* Generic PCI field: next ptr */
__u8 cap_len; /* Generic PCI field: capability length */
__u8 cfg_type; /* Identifies the structure */
__u8 bar; /* Where to find it */
__u8 padding[3]; /* Pad to full dword */
__le32 offset; /* Offset within bar */
__le32 length; /* Length of the structure, in bytes */
};
struct virtio_pci_notify_cap {
struct virtio_pci_cap cap;
__le32 notify_off_multiplier; /* Multiplier for queue_notify_off */
};
/* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
struct virtio_pci_common_cfg {
/* About the whole device */
__le32 device_feature_select; /* read-write */
__le32 device_feature; /* read-only */
__le32 guest_feature_select; /* read-write */
__le32 guest_feature; /* read-write */
__le16 msix_config; /* read-write */
__le16 num_queues; /* read-only */
__u8 device_status; /* read-write */
__u8 config_generation; /* read-only */
/* About a specific virtqueue */
__le16 queue_select; /* read-write */
__le16 queue_size; /* read-write, power of 2 */
__le16 queue_msix_vector; /* read-write */
__le16 queue_enable; /* read-write */
__le16 queue_notify_off; /* read-only */
__le32 queue_desc_lo; /* read-write */
__le32 queue_desc_hi; /* read-write */
__le32 queue_avail_lo; /* read-write */
__le32 queue_avail_hi; /* read-write */
__le32 queue_used_lo; /* read-write */
__le32 queue_used_hi; /* read-write */
};
/* Fields in VIRTIO_PCI_CAP_PCI_CFG: */
struct virtio_pci_cfg_cap {
struct virtio_pci_cap cap;
__u8 pci_cfg_data[4]; /* Data for BAR access */
};
/* Macro versions of offsets for the Old Timers! */
#define VIRTIO_PCI_CAP_VNDR 0
#define VIRTIO_PCI_CAP_NEXT 1
#define VIRTIO_PCI_CAP_LEN 2
#define VIRTIO_PCI_CAP_CFG_TYPE 3
#define VIRTIO_PCI_CAP_BAR 4
#define VIRTIO_PCI_CAP_OFFSET 8
#define VIRTIO_PCI_CAP_LENGTH 12
#define VIRTIO_PCI_NOTIFY_CAP_MULT 16
#define VIRTIO_PCI_COMMON_DFSELECT 0
#define VIRTIO_PCI_COMMON_DF 4
#define VIRTIO_PCI_COMMON_GFSELECT 8
#define VIRTIO_PCI_COMMON_GF 12
#define VIRTIO_PCI_COMMON_MSIX 16
#define VIRTIO_PCI_COMMON_NUMQ 18
#define VIRTIO_PCI_COMMON_STATUS 20
#define VIRTIO_PCI_COMMON_CFGGENERATION 21
#define VIRTIO_PCI_COMMON_Q_SELECT 22
#define VIRTIO_PCI_COMMON_Q_SIZE 24
#define VIRTIO_PCI_COMMON_Q_MSIX 26
#define VIRTIO_PCI_COMMON_Q_ENABLE 28
#define VIRTIO_PCI_COMMON_Q_NOFF 30
#define VIRTIO_PCI_COMMON_Q_DESCLO 32
#define VIRTIO_PCI_COMMON_Q_DESCHI 36
#define VIRTIO_PCI_COMMON_Q_AVAILLO 40
#define VIRTIO_PCI_COMMON_Q_AVAILHI 44
#define VIRTIO_PCI_COMMON_Q_USEDLO 48
#define VIRTIO_PCI_COMMON_Q_USEDHI 52
#endif /* VIRTIO_PCI_NO_MODERN */
#endif /* _LINUX_VIRTIO_PCI_H */
@@ -0,0 +1,421 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* VirtIO PCI bus transport driver
* Ported from Linux drivers/virtio/virtio_pci*.c
*/
#include <common.h>
#include <dm.h>
#include <virtio_types.h>
#include <virtio.h>
#include <virtio_ring.h>
#include <dm/device.h>
#include <linux/compat.h>
#include <linux/io.h>
#include "virtio_pci.h"
#define VIRTIO_PCI_DRV_NAME "virtio-pci.l"
/* PCI device ID in the range 0x1000 to 0x103f */
#define VIRTIO_PCI_VENDOR_ID 0x1af4
#define VIRTIO_PCI_DEVICE_ID00 0x1000
#define VIRTIO_PCI_DEVICE_ID01 0x1001
#define VIRTIO_PCI_DEVICE_ID02 0x1002
#define VIRTIO_PCI_DEVICE_ID03 0x1003
#define VIRTIO_PCI_DEVICE_ID04 0x1004
#define VIRTIO_PCI_DEVICE_ID05 0x1005
#define VIRTIO_PCI_DEVICE_ID06 0x1006
#define VIRTIO_PCI_DEVICE_ID07 0x1007
#define VIRTIO_PCI_DEVICE_ID08 0x1008
#define VIRTIO_PCI_DEVICE_ID09 0x1009
#define VIRTIO_PCI_DEVICE_ID0A 0x100a
#define VIRTIO_PCI_DEVICE_ID0B 0x100b
#define VIRTIO_PCI_DEVICE_ID0C 0x100c
#define VIRTIO_PCI_DEVICE_ID0D 0x100d
#define VIRTIO_PCI_DEVICE_ID0E 0x100e
#define VIRTIO_PCI_DEVICE_ID0F 0x100f
#define VIRTIO_PCI_DEVICE_ID10 0x1010
#define VIRTIO_PCI_DEVICE_ID11 0x1011
#define VIRTIO_PCI_DEVICE_ID12 0x1012
#define VIRTIO_PCI_DEVICE_ID13 0x1013
#define VIRTIO_PCI_DEVICE_ID14 0x1014
#define VIRTIO_PCI_DEVICE_ID15 0x1015
#define VIRTIO_PCI_DEVICE_ID16 0x1016
#define VIRTIO_PCI_DEVICE_ID17 0x1017
#define VIRTIO_PCI_DEVICE_ID18 0x1018
#define VIRTIO_PCI_DEVICE_ID19 0x1019
#define VIRTIO_PCI_DEVICE_ID1A 0x101a
#define VIRTIO_PCI_DEVICE_ID1B 0x101b
#define VIRTIO_PCI_DEVICE_ID1C 0x101c
#define VIRTIO_PCI_DEVICE_ID1D 0x101d
#define VIRTIO_PCI_DEVICE_ID1E 0x101e
#define VIRTIO_PCI_DEVICE_ID1F 0x101f
#define VIRTIO_PCI_DEVICE_ID20 0x1020
#define VIRTIO_PCI_DEVICE_ID21 0x1021
#define VIRTIO_PCI_DEVICE_ID22 0x1022
#define VIRTIO_PCI_DEVICE_ID23 0x1023
#define VIRTIO_PCI_DEVICE_ID24 0x1024
#define VIRTIO_PCI_DEVICE_ID25 0x1025
#define VIRTIO_PCI_DEVICE_ID26 0x1026
#define VIRTIO_PCI_DEVICE_ID27 0x1027
#define VIRTIO_PCI_DEVICE_ID28 0x1028
#define VIRTIO_PCI_DEVICE_ID29 0x1029
#define VIRTIO_PCI_DEVICE_ID2A 0x102a
#define VIRTIO_PCI_DEVICE_ID2B 0x102b
#define VIRTIO_PCI_DEVICE_ID2C 0x102c
#define VIRTIO_PCI_DEVICE_ID2D 0x102d
#define VIRTIO_PCI_DEVICE_ID2E 0x102e
#define VIRTIO_PCI_DEVICE_ID2F 0x102f
#define VIRTIO_PCI_DEVICE_ID30 0x1030
#define VIRTIO_PCI_DEVICE_ID31 0x1031
#define VIRTIO_PCI_DEVICE_ID32 0x1032
#define VIRTIO_PCI_DEVICE_ID33 0x1033
#define VIRTIO_PCI_DEVICE_ID34 0x1034
#define VIRTIO_PCI_DEVICE_ID35 0x1035
#define VIRTIO_PCI_DEVICE_ID36 0x1036
#define VIRTIO_PCI_DEVICE_ID37 0x1037
#define VIRTIO_PCI_DEVICE_ID38 0x1038
#define VIRTIO_PCI_DEVICE_ID39 0x1039
#define VIRTIO_PCI_DEVICE_ID3A 0x103a
#define VIRTIO_PCI_DEVICE_ID3B 0x103b
#define VIRTIO_PCI_DEVICE_ID3C 0x103c
#define VIRTIO_PCI_DEVICE_ID3D 0x103d
#define VIRTIO_PCI_DEVICE_ID3E 0x103e
#define VIRTIO_PCI_DEVICE_ID3F 0x103f
/**
* virtio pci transport driver private data
*
* @ioaddr: pci transport device register base
* @version: pci transport device version
*/
struct virtio_pci_priv {
void __iomem *ioaddr;
};
static int virtio_pci_get_config(struct udevice *udev, unsigned int offset,
void *buf, unsigned int len)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
void __iomem *ioaddr = priv->ioaddr + VIRTIO_PCI_CONFIG_OFF(false);
u8 *ptr = buf;
int i;
for (i = 0; i < len; i++)
ptr[i] = ioread8(ioaddr + i);
return 0;
}
static int virtio_pci_set_config(struct udevice *udev, unsigned int offset,
const void *buf, unsigned int len)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
void __iomem *ioaddr = priv->ioaddr + VIRTIO_PCI_CONFIG_OFF(false);
const u8 *ptr = buf;
int i;
for (i = 0; i < len; i++)
iowrite8(ptr[i], ioaddr + i);
return 0;
}
static int virtio_pci_get_status(struct udevice *udev, u8 *status)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
*status = ioread8(priv->ioaddr + VIRTIO_PCI_STATUS);
return 0;
}
static int virtio_pci_set_status(struct udevice *udev, u8 status)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
/* We should never be setting status to 0 */
WARN_ON(status == 0);
iowrite8(status, priv->ioaddr + VIRTIO_PCI_STATUS);
return 0;
}
static int virtio_pci_reset(struct udevice *udev)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
/* 0 status means a reset */
iowrite8(0, priv->ioaddr + VIRTIO_PCI_STATUS);
/*
* Flush out the status write, and flush in device writes,
* including MSI-X interrupts, if any.
*/
ioread8(priv->ioaddr + VIRTIO_PCI_STATUS);
return 0;
}
static int virtio_pci_get_features(struct udevice *udev, u64 *features)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
/*
* When someone needs more than 32 feature bits, we'll need to
* steal a bit to indicate that the rest are somewhere else.
*/
*features = ioread32(priv->ioaddr + VIRTIO_PCI_HOST_FEATURES);
return 0;
}
static int virtio_pci_set_features(struct udevice *udev)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
/* Make sure we don't have any features > 32 bits! */
WARN_ON((u32)uc_priv->features != uc_priv->features);
/* We only support 32 feature bits */
iowrite32(uc_priv->features, priv->ioaddr + VIRTIO_PCI_GUEST_FEATURES);
return 0;
}
static struct virtqueue *virtio_pci_setup_vq(struct udevice *udev,
unsigned int index)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
struct virtqueue *vq;
unsigned int num;
int err;
/* Select the queue we're interested in */
iowrite16(index, priv->ioaddr + VIRTIO_PCI_QUEUE_SEL);
/* Check if queue is either not available or already active */
num = ioread16(priv->ioaddr + VIRTIO_PCI_QUEUE_NUM);
if (!num || ioread32(priv->ioaddr + VIRTIO_PCI_QUEUE_PFN)) {
err = -ENOENT;
goto error_available;
}
/* Create the vring */
vq = vring_create_virtqueue(index, num, VIRTIO_PCI_VRING_ALIGN, udev);
if (!vq) {
err = -ENOMEM;
goto error_available;
}
/* Activate the queue */
iowrite32(virtqueue_get_desc_addr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT,
priv->ioaddr + VIRTIO_PCI_QUEUE_PFN);
return vq;
error_available:
return ERR_PTR(err);
}
static void virtio_pci_del_vq(struct virtqueue *vq)
{
struct virtio_pci_priv *priv = dev_get_priv(vq->vdev);
unsigned int index = vq->index;
iowrite16(index, priv->ioaddr + VIRTIO_PCI_QUEUE_SEL);
/* Select and deactivate the queue */
iowrite32(0, priv->ioaddr + VIRTIO_PCI_QUEUE_PFN);
vring_del_virtqueue(vq);
}
static int virtio_pci_del_vqs(struct udevice *udev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
struct virtqueue *vq, *n;
list_for_each_entry_safe(vq, n, &uc_priv->vqs, list)
virtio_pci_del_vq(vq);
return 0;
}
static int virtio_pci_find_vqs(struct udevice *udev, unsigned int nvqs,
struct virtqueue *vqs[])
{
int i;
for (i = 0; i < nvqs; ++i) {
vqs[i] = virtio_pci_setup_vq(udev, i);
if (IS_ERR(vqs[i])) {
virtio_pci_del_vqs(udev);
return PTR_ERR(vqs[i]);
}
}
return 0;
}
static int virtio_pci_notify(struct udevice *udev, struct virtqueue *vq)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
/*
* We write the queue's selector into the notification register
* to signal the other end
*/
iowrite16(vq->index, priv->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY);
return 0;
}
static int virtio_pci_bind(struct udevice *udev)
{
static unsigned int num_devs;
char name[20];
/* Create a unique device name for PCI type devices */
sprintf(name, "%s#%u", VIRTIO_PCI_DRV_NAME, num_devs++);
device_set_name(udev, name);
return 0;
}
static int virtio_pci_probe(struct udevice *udev)
{
struct pci_child_platdata *pplat = dev_get_parent_platdata(udev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
struct virtio_pci_priv *priv = dev_get_priv(udev);
u16 subvendor, subdevice;
u8 revision;
/* We only own devices >= 0x1000 and <= 0x103f: leave the rest. */
if (pplat->device < 0x1000 || pplat->device > 0x103f)
return -ENODEV;
/* Transitional devices must have a PCI revision ID of 0 */
dm_pci_read_config8(udev, PCI_REVISION_ID, &revision);
if (revision != VIRTIO_PCI_ABI_VERSION) {
printf("(%s): virtio_pci expected ABI version %d, got %d\n",
udev->name, VIRTIO_PCI_ABI_VERSION, revision);
return -ENODEV;
}
/*
* Transitional devices must have the PCI subsystem device ID matching
* the virtio device ID
*/
dm_pci_read_config16(udev, PCI_SUBSYSTEM_ID, &subdevice);
dm_pci_read_config16(udev, PCI_SUBSYSTEM_VENDOR_ID, &subvendor);
uc_priv->device = subdevice;
uc_priv->vendor = subvendor;
priv->ioaddr = dm_pci_map_bar(udev, PCI_BASE_ADDRESS_0, PCI_REGION_IO);
if (!priv->ioaddr)
return -ENXIO;
debug("(%s): virtio legacy device reg base %04lx\n",
udev->name, (ulong)priv->ioaddr);
debug("(%s): device (%d) vendor (%08x) version (%d)\n", udev->name,
uc_priv->device, uc_priv->vendor, revision);
return 0;
}
static const struct dm_virtio_ops virtio_pci_ops = {
.get_config = virtio_pci_get_config,
.set_config = virtio_pci_set_config,
.get_status = virtio_pci_get_status,
.set_status = virtio_pci_set_status,
.reset = virtio_pci_reset,
.get_features = virtio_pci_get_features,
.set_features = virtio_pci_set_features,
.find_vqs = virtio_pci_find_vqs,
.del_vqs = virtio_pci_del_vqs,
.notify = virtio_pci_notify,
};
U_BOOT_DRIVER(virtio_pci_legacy) = {
.name = VIRTIO_PCI_DRV_NAME,
.id = UCLASS_VIRTIO,
.ops = &virtio_pci_ops,
.bind = virtio_pci_bind,
.probe = virtio_pci_probe,
.priv_auto_alloc_size = sizeof(struct virtio_pci_priv),
};
static struct pci_device_id virtio_pci_supported[] = {
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID00) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID01) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID02) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID03) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID04) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID05) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID06) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID07) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID08) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID09) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0A) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0B) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0C) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0D) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0E) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0F) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID10) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID11) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID12) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID13) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID14) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID15) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID16) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID17) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID18) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID19) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1A) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1B) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1C) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1D) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1E) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1F) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID20) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID21) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID22) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID23) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID24) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID25) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID26) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID27) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID28) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID29) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2A) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2B) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2C) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2D) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2E) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2F) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID30) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID31) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID32) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID33) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID34) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID35) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID36) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID37) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID38) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID39) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3A) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3B) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3C) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3D) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3E) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3F) },
{},
};
U_BOOT_PCI_DEVICE(virtio_pci_legacy, virtio_pci_supported);
@@ -0,0 +1,609 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* VirtIO PCI bus transport driver
* Ported from Linux drivers/virtio/virtio_pci*.c
*/
#include <common.h>
#include <dm.h>
#include <virtio_types.h>
#include <virtio.h>
#include <virtio_ring.h>
#include <dm/device.h>
#include <linux/compat.h>
#include <linux/io.h>
#include "virtio_pci.h"
#define VIRTIO_PCI_DRV_NAME "virtio-pci.m"
/* PCI device ID in the range 0x1040 to 0x107f */
#define VIRTIO_PCI_VENDOR_ID 0x1af4
#define VIRTIO_PCI_DEVICE_ID00 0x1040
#define VIRTIO_PCI_DEVICE_ID01 0x1041
#define VIRTIO_PCI_DEVICE_ID02 0x1042
#define VIRTIO_PCI_DEVICE_ID03 0x1043
#define VIRTIO_PCI_DEVICE_ID04 0x1044
#define VIRTIO_PCI_DEVICE_ID05 0x1045
#define VIRTIO_PCI_DEVICE_ID06 0x1046
#define VIRTIO_PCI_DEVICE_ID07 0x1047
#define VIRTIO_PCI_DEVICE_ID08 0x1048
#define VIRTIO_PCI_DEVICE_ID09 0x1049
#define VIRTIO_PCI_DEVICE_ID0A 0x104a
#define VIRTIO_PCI_DEVICE_ID0B 0x104b
#define VIRTIO_PCI_DEVICE_ID0C 0x104c
#define VIRTIO_PCI_DEVICE_ID0D 0x104d
#define VIRTIO_PCI_DEVICE_ID0E 0x104e
#define VIRTIO_PCI_DEVICE_ID0F 0x104f
#define VIRTIO_PCI_DEVICE_ID10 0x1050
#define VIRTIO_PCI_DEVICE_ID11 0x1051
#define VIRTIO_PCI_DEVICE_ID12 0x1052
#define VIRTIO_PCI_DEVICE_ID13 0x1053
#define VIRTIO_PCI_DEVICE_ID14 0x1054
#define VIRTIO_PCI_DEVICE_ID15 0x1055
#define VIRTIO_PCI_DEVICE_ID16 0x1056
#define VIRTIO_PCI_DEVICE_ID17 0x1057
#define VIRTIO_PCI_DEVICE_ID18 0x1058
#define VIRTIO_PCI_DEVICE_ID19 0x1059
#define VIRTIO_PCI_DEVICE_ID1A 0x105a
#define VIRTIO_PCI_DEVICE_ID1B 0x105b
#define VIRTIO_PCI_DEVICE_ID1C 0x105c
#define VIRTIO_PCI_DEVICE_ID1D 0x105d
#define VIRTIO_PCI_DEVICE_ID1E 0x105e
#define VIRTIO_PCI_DEVICE_ID1F 0x105f
#define VIRTIO_PCI_DEVICE_ID20 0x1060
#define VIRTIO_PCI_DEVICE_ID21 0x1061
#define VIRTIO_PCI_DEVICE_ID22 0x1062
#define VIRTIO_PCI_DEVICE_ID23 0x1063
#define VIRTIO_PCI_DEVICE_ID24 0x1064
#define VIRTIO_PCI_DEVICE_ID25 0x1065
#define VIRTIO_PCI_DEVICE_ID26 0x1066
#define VIRTIO_PCI_DEVICE_ID27 0x1067
#define VIRTIO_PCI_DEVICE_ID28 0x1068
#define VIRTIO_PCI_DEVICE_ID29 0x1069
#define VIRTIO_PCI_DEVICE_ID2A 0x106a
#define VIRTIO_PCI_DEVICE_ID2B 0x106b
#define VIRTIO_PCI_DEVICE_ID2C 0x106c
#define VIRTIO_PCI_DEVICE_ID2D 0x106d
#define VIRTIO_PCI_DEVICE_ID2E 0x106e
#define VIRTIO_PCI_DEVICE_ID2F 0x106f
#define VIRTIO_PCI_DEVICE_ID30 0x1070
#define VIRTIO_PCI_DEVICE_ID31 0x1071
#define VIRTIO_PCI_DEVICE_ID32 0x1072
#define VIRTIO_PCI_DEVICE_ID33 0x1073
#define VIRTIO_PCI_DEVICE_ID34 0x1074
#define VIRTIO_PCI_DEVICE_ID35 0x1075
#define VIRTIO_PCI_DEVICE_ID36 0x1076
#define VIRTIO_PCI_DEVICE_ID37 0x1077
#define VIRTIO_PCI_DEVICE_ID38 0x1078
#define VIRTIO_PCI_DEVICE_ID39 0x1079
#define VIRTIO_PCI_DEVICE_ID3A 0x107a
#define VIRTIO_PCI_DEVICE_ID3B 0x107b
#define VIRTIO_PCI_DEVICE_ID3C 0x107c
#define VIRTIO_PCI_DEVICE_ID3D 0x107d
#define VIRTIO_PCI_DEVICE_ID3E 0x107e
#define VIRTIO_PCI_DEVICE_ID3F 0x107f
/**
* virtio pci transport driver private data
*
* @common: pci transport device common register block base
* @notify_base: pci transport device notify register block base
* @device: pci transport device device-specific register block base
* @device_len: pci transport device device-specific register block length
* @notify_offset_multiplier: multiply queue_notify_off by this value
*/
struct virtio_pci_priv {
struct virtio_pci_common_cfg __iomem *common;
void __iomem *notify_base;
void __iomem *device;
u32 device_len;
u32 notify_offset_multiplier;
};
static int virtio_pci_get_config(struct udevice *udev, unsigned int offset,
void *buf, unsigned int len)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
u8 b;
__le16 w;
__le32 l;
WARN_ON(offset + len > priv->device_len);
switch (len) {
case 1:
b = ioread8(priv->device + offset);
memcpy(buf, &b, sizeof(b));
break;
case 2:
w = cpu_to_le16(ioread16(priv->device + offset));
memcpy(buf, &w, sizeof(w));
break;
case 4:
l = cpu_to_le32(ioread32(priv->device + offset));
memcpy(buf, &l, sizeof(l));
break;
case 8:
l = cpu_to_le32(ioread32(priv->device + offset));
memcpy(buf, &l, sizeof(l));
l = cpu_to_le32(ioread32(priv->device + offset + sizeof(l)));
memcpy(buf + sizeof(l), &l, sizeof(l));
break;
default:
WARN_ON(true);
}
return 0;
}
static int virtio_pci_set_config(struct udevice *udev, unsigned int offset,
const void *buf, unsigned int len)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
u8 b;
__le16 w;
__le32 l;
WARN_ON(offset + len > priv->device_len);
switch (len) {
case 1:
memcpy(&b, buf, sizeof(b));
iowrite8(b, priv->device + offset);
break;
case 2:
memcpy(&w, buf, sizeof(w));
iowrite16(le16_to_cpu(w), priv->device + offset);
break;
case 4:
memcpy(&l, buf, sizeof(l));
iowrite32(le32_to_cpu(l), priv->device + offset);
break;
case 8:
memcpy(&l, buf, sizeof(l));
iowrite32(le32_to_cpu(l), priv->device + offset);
memcpy(&l, buf + sizeof(l), sizeof(l));
iowrite32(le32_to_cpu(l), priv->device + offset + sizeof(l));
break;
default:
WARN_ON(true);
}
return 0;
}
static int virtio_pci_generation(struct udevice *udev, u32 *counter)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
*counter = ioread8(&priv->common->config_generation);
return 0;
}
static int virtio_pci_get_status(struct udevice *udev, u8 *status)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
*status = ioread8(&priv->common->device_status);
return 0;
}
static int virtio_pci_set_status(struct udevice *udev, u8 status)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
/* We should never be setting status to 0 */
WARN_ON(status == 0);
iowrite8(status, &priv->common->device_status);
return 0;
}
static int virtio_pci_reset(struct udevice *udev)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
/* 0 status means a reset */
iowrite8(0, &priv->common->device_status);
/*
* After writing 0 to device_status, the driver MUST wait for a read
* of device_status to return 0 before reinitializing the device.
* This will flush out the status write, and flush in device writes,
* including MSI-X interrupts, if any.
*/
while (ioread8(&priv->common->device_status))
udelay(1000);
return 0;
}
static int virtio_pci_get_features(struct udevice *udev, u64 *features)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
iowrite32(0, &priv->common->device_feature_select);
*features = ioread32(&priv->common->device_feature);
iowrite32(1, &priv->common->device_feature_select);
*features |= ((u64)ioread32(&priv->common->device_feature) << 32);
return 0;
}
static int virtio_pci_set_features(struct udevice *udev)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
if (!__virtio_test_bit(udev, VIRTIO_F_VERSION_1)) {
debug("virtio: device uses modern interface but does not have VIRTIO_F_VERSION_1\n");
return -EINVAL;
}
iowrite32(0, &priv->common->guest_feature_select);
iowrite32((u32)uc_priv->features, &priv->common->guest_feature);
iowrite32(1, &priv->common->guest_feature_select);
iowrite32(uc_priv->features >> 32, &priv->common->guest_feature);
return 0;
}
static struct virtqueue *virtio_pci_setup_vq(struct udevice *udev,
unsigned int index)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
struct virtio_pci_common_cfg __iomem *cfg = priv->common;
struct virtqueue *vq;
u16 num;
u64 addr;
int err;
if (index >= ioread16(&cfg->num_queues))
return ERR_PTR(-ENOENT);
/* Select the queue we're interested in */
iowrite16(index, &cfg->queue_select);
/* Check if queue is either not available or already active */
num = ioread16(&cfg->queue_size);
if (!num || ioread16(&cfg->queue_enable))
return ERR_PTR(-ENOENT);
if (num & (num - 1)) {
printf("(%s): bad queue size %u", udev->name, num);
return ERR_PTR(-EINVAL);
}
/* Create the vring */
vq = vring_create_virtqueue(index, num, VIRTIO_PCI_VRING_ALIGN, udev);
if (!vq) {
err = -ENOMEM;
goto error_available;
}
/* Activate the queue */
iowrite16(virtqueue_get_vring_size(vq), &cfg->queue_size);
addr = virtqueue_get_desc_addr(vq);
iowrite32((u32)addr, &cfg->queue_desc_lo);
iowrite32(addr >> 32, &cfg->queue_desc_hi);
addr = virtqueue_get_avail_addr(vq);
iowrite32((u32)addr, &cfg->queue_avail_lo);
iowrite32(addr >> 32, &cfg->queue_avail_hi);
addr = virtqueue_get_used_addr(vq);
iowrite32((u32)addr, &cfg->queue_used_lo);
iowrite32(addr >> 32, &cfg->queue_used_hi);
iowrite16(1, &cfg->queue_enable);
return vq;
error_available:
return ERR_PTR(err);
}
static void virtio_pci_del_vq(struct virtqueue *vq)
{
struct virtio_pci_priv *priv = dev_get_priv(vq->vdev);
unsigned int index = vq->index;
iowrite16(index, &priv->common->queue_select);
/* Select and deactivate the queue */
iowrite16(0, &priv->common->queue_enable);
vring_del_virtqueue(vq);
}
static int virtio_pci_del_vqs(struct udevice *udev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
struct virtqueue *vq, *n;
list_for_each_entry_safe(vq, n, &uc_priv->vqs, list)
virtio_pci_del_vq(vq);
return 0;
}
static int virtio_pci_find_vqs(struct udevice *udev, unsigned int nvqs,
struct virtqueue *vqs[])
{
int i;
for (i = 0; i < nvqs; ++i) {
vqs[i] = virtio_pci_setup_vq(udev, i);
if (IS_ERR(vqs[i])) {
virtio_pci_del_vqs(udev);
return PTR_ERR(vqs[i]);
}
}
return 0;
}
static int virtio_pci_notify(struct udevice *udev, struct virtqueue *vq)
{
struct virtio_pci_priv *priv = dev_get_priv(udev);
u16 off;
/* Select the queue we're interested in */
iowrite16(vq->index, &priv->common->queue_select);
/* get offset of notification word for this vq */
off = ioread16(&priv->common->queue_notify_off);
/*
* We write the queue's selector into the notification register
* to signal the other end
*/
iowrite16(vq->index,
priv->notify_base + off * priv->notify_offset_multiplier);
return 0;
}
/**
* virtio_pci_find_capability - walk capabilities to find device info
*
* @udev: the transport device
* @cfg_type: the VIRTIO_PCI_CAP_* value we seek
*
* @return offset of the configuration structure
*/
static int virtio_pci_find_capability(struct udevice *udev, u8 cfg_type)
{
int pos;
int offset;
u8 type, bar;
for (pos = dm_pci_find_capability(udev, PCI_CAP_ID_VNDR);
pos > 0;
pos = dm_pci_find_next_capability(udev, pos, PCI_CAP_ID_VNDR)) {
offset = pos + offsetof(struct virtio_pci_cap, cfg_type);
dm_pci_read_config8(udev, offset, &type);
offset = pos + offsetof(struct virtio_pci_cap, bar);
dm_pci_read_config8(udev, offset, &bar);
/* Ignore structures with reserved BAR values */
if (bar > 0x5)
continue;
if (type == cfg_type)
return pos;
}
return 0;
}
/**
* virtio_pci_map_capability - map base address of the capability
*
* @udev: the transport device
* @off: offset of the configuration structure
*
* @return base address of the capability
*/
static void __iomem *virtio_pci_map_capability(struct udevice *udev, int off)
{
u8 bar;
u32 offset;
ulong base;
void __iomem *p;
if (!off)
return NULL;
offset = off + offsetof(struct virtio_pci_cap, bar);
dm_pci_read_config8(udev, offset, &bar);
offset = off + offsetof(struct virtio_pci_cap, offset);
dm_pci_read_config32(udev, offset, &offset);
/*
* TODO: adding 64-bit BAR support
*
* Per spec, the BAR is permitted to be either 32-bit or 64-bit.
* For simplicity, only read the BAR address as 32-bit.
*/
base = dm_pci_read_bar32(udev, bar);
p = (void __iomem *)base + offset;
return p;
}
static int virtio_pci_bind(struct udevice *udev)
{
static int num_devs;
char name[20];
/* Create a unique device name */
sprintf(name, "%s#%u", VIRTIO_PCI_DRV_NAME, num_devs++);
device_set_name(udev, name);
return 0;
}
static int virtio_pci_probe(struct udevice *udev)
{
struct pci_child_platdata *pplat = dev_get_parent_platdata(udev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
struct virtio_pci_priv *priv = dev_get_priv(udev);
u16 subvendor;
u8 revision;
int common, notify, device;
int offset;
/* We only own devices >= 0x1040 and <= 0x107f: leave the rest. */
if (pplat->device < 0x1040 || pplat->device > 0x107f)
return -ENODEV;
/* Transitional devices must not have a PCI revision ID of 0 */
dm_pci_read_config8(udev, PCI_REVISION_ID, &revision);
/* Modern devices: simply use PCI device id, but start from 0x1040. */
uc_priv->device = pplat->device - 0x1040;
dm_pci_read_config16(udev, PCI_SUBSYSTEM_VENDOR_ID, &subvendor);
uc_priv->vendor = subvendor;
/* Check for a common config: if not, use legacy mode (bar 0) */
common = virtio_pci_find_capability(udev, VIRTIO_PCI_CAP_COMMON_CFG);
if (!common) {
printf("(%s): leaving for legacy driver\n", udev->name);
return -ENODEV;
}
/* If common is there, notify should be too */
notify = virtio_pci_find_capability(udev, VIRTIO_PCI_CAP_NOTIFY_CFG);
if (!notify) {
printf("(%s): missing capabilities %i/%i\n", udev->name,
common, notify);
return -EINVAL;
}
/*
* Device capability is only mandatory for devices that have
* device-specific configuration.
*/
device = virtio_pci_find_capability(udev, VIRTIO_PCI_CAP_DEVICE_CFG);
if (device) {
offset = notify + offsetof(struct virtio_pci_cap, length);
dm_pci_read_config32(udev, offset, &priv->device_len);
}
/* Map configuration structures */
priv->common = virtio_pci_map_capability(udev, common);
priv->notify_base = virtio_pci_map_capability(udev, notify);
priv->device = virtio_pci_map_capability(udev, device);
debug("(%p): common @ %p, notify base @ %p, device @ %p\n",
udev, priv->common, priv->notify_base, priv->device);
/* Read notify_off_multiplier from config space */
offset = notify + offsetof(struct virtio_pci_notify_cap,
notify_off_multiplier);
dm_pci_read_config32(udev, offset, &priv->notify_offset_multiplier);
debug("(%s): device (%d) vendor (%08x) version (%d)\n", udev->name,
uc_priv->device, uc_priv->vendor, revision);
return 0;
}
static const struct dm_virtio_ops virtio_pci_ops = {
.get_config = virtio_pci_get_config,
.set_config = virtio_pci_set_config,
.generation = virtio_pci_generation,
.get_status = virtio_pci_get_status,
.set_status = virtio_pci_set_status,
.reset = virtio_pci_reset,
.get_features = virtio_pci_get_features,
.set_features = virtio_pci_set_features,
.find_vqs = virtio_pci_find_vqs,
.del_vqs = virtio_pci_del_vqs,
.notify = virtio_pci_notify,
};
U_BOOT_DRIVER(virtio_pci_modern) = {
.name = VIRTIO_PCI_DRV_NAME,
.id = UCLASS_VIRTIO,
.ops = &virtio_pci_ops,
.bind = virtio_pci_bind,
.probe = virtio_pci_probe,
.priv_auto_alloc_size = sizeof(struct virtio_pci_priv),
};
static struct pci_device_id virtio_pci_supported[] = {
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID00) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID01) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID02) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID03) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID04) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID05) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID06) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID07) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID08) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID09) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0A) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0B) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0C) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0D) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0E) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID0F) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID10) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID11) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID12) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID13) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID14) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID15) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID16) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID17) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID18) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID19) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1A) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1B) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1C) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1D) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1E) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID1F) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID20) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID21) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID22) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID23) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID24) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID25) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID26) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID27) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID28) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID29) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2A) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2B) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2C) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2D) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2E) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID2F) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID30) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID31) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID32) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID33) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID34) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID35) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID36) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID37) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID38) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID39) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3A) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3B) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3C) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3D) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3E) },
{ PCI_DEVICE(VIRTIO_PCI_VENDOR_ID, VIRTIO_PCI_DEVICE_ID3F) },
{},
};
U_BOOT_PCI_DEVICE(virtio_pci_modern, virtio_pci_supported);
@@ -0,0 +1,358 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* virtio ring implementation
*/
#include <common.h>
#include <dm.h>
#include <malloc.h>
#include <virtio_types.h>
#include <virtio.h>
#include <virtio_ring.h>
int virtqueue_add(struct virtqueue *vq, struct virtio_sg *sgs[],
unsigned int out_sgs, unsigned int in_sgs)
{
struct vring_desc *desc;
unsigned int total_sg = out_sgs + in_sgs;
unsigned int i, n, avail, descs_used, uninitialized_var(prev);
int head;
WARN_ON(total_sg == 0);
head = vq->free_head;
desc = vq->vring.desc;
i = head;
descs_used = total_sg;
if (vq->num_free < descs_used) {
debug("Can't add buf len %i - avail = %i\n",
descs_used, vq->num_free);
/*
* FIXME: for historical reasons, we force a notify here if
* there are outgoing parts to the buffer. Presumably the
* host should service the ring ASAP.
*/
if (out_sgs)
virtio_notify(vq->vdev, vq);
return -ENOSPC;
}
for (n = 0; n < out_sgs; n++) {
struct virtio_sg *sg = sgs[n];
desc[i].flags = cpu_to_virtio16(vq->vdev, VRING_DESC_F_NEXT);
desc[i].addr = cpu_to_virtio64(vq->vdev, (u64)(size_t)sg->addr);
desc[i].len = cpu_to_virtio32(vq->vdev, sg->length);
prev = i;
i = virtio16_to_cpu(vq->vdev, desc[i].next);
}
for (; n < (out_sgs + in_sgs); n++) {
struct virtio_sg *sg = sgs[n];
desc[i].flags = cpu_to_virtio16(vq->vdev, VRING_DESC_F_NEXT |
VRING_DESC_F_WRITE);
desc[i].addr = cpu_to_virtio64(vq->vdev,
(u64)(uintptr_t)sg->addr);
desc[i].len = cpu_to_virtio32(vq->vdev, sg->length);
prev = i;
i = virtio16_to_cpu(vq->vdev, desc[i].next);
}
/* Last one doesn't continue */
desc[prev].flags &= cpu_to_virtio16(vq->vdev, ~VRING_DESC_F_NEXT);
/* We're using some buffers from the free list. */
vq->num_free -= descs_used;
/* Update free pointer */
vq->free_head = i;
/*
* Put entry in available array (but don't update avail->idx
* until they do sync).
*/
avail = vq->avail_idx_shadow & (vq->vring.num - 1);
vq->vring.avail->ring[avail] = cpu_to_virtio16(vq->vdev, head);
/*
* Descriptors and available array need to be set before we expose the
* new available array entries.
*/
virtio_wmb();
vq->avail_idx_shadow++;
vq->vring.avail->idx = cpu_to_virtio16(vq->vdev, vq->avail_idx_shadow);
vq->num_added++;
/*
* This is very unlikely, but theoretically possible.
* Kick just in case.
*/
if (unlikely(vq->num_added == (1 << 16) - 1))
virtqueue_kick(vq);
return 0;
}
static bool virtqueue_kick_prepare(struct virtqueue *vq)
{
u16 new, old;
bool needs_kick;
/*
* We need to expose available array entries before checking
* avail event.
*/
virtio_mb();
old = vq->avail_idx_shadow - vq->num_added;
new = vq->avail_idx_shadow;
vq->num_added = 0;
if (vq->event) {
needs_kick = vring_need_event(virtio16_to_cpu(vq->vdev,
vring_avail_event(&vq->vring)), new, old);
} else {
needs_kick = !(vq->vring.used->flags & cpu_to_virtio16(vq->vdev,
VRING_USED_F_NO_NOTIFY));
}
return needs_kick;
}
void virtqueue_kick(struct virtqueue *vq)
{
if (virtqueue_kick_prepare(vq))
virtio_notify(vq->vdev, vq);
}
static void detach_buf(struct virtqueue *vq, unsigned int head)
{
unsigned int i;
__virtio16 nextflag = cpu_to_virtio16(vq->vdev, VRING_DESC_F_NEXT);
/* Put back on free list: unmap first-level descriptors and find end */
i = head;
while (vq->vring.desc[i].flags & nextflag) {
i = virtio16_to_cpu(vq->vdev, vq->vring.desc[i].next);
vq->num_free++;
}
vq->vring.desc[i].next = cpu_to_virtio16(vq->vdev, vq->free_head);
vq->free_head = head;
/* Plus final descriptor */
vq->num_free++;
}
static inline bool more_used(const struct virtqueue *vq)
{
return vq->last_used_idx != virtio16_to_cpu(vq->vdev,
vq->vring.used->idx);
}
void *virtqueue_get_buf(struct virtqueue *vq, unsigned int *len)
{
unsigned int i;
u16 last_used;
if (!more_used(vq)) {
debug("(%s.%d): No more buffers in queue\n",
vq->vdev->name, vq->index);
return NULL;
}
/* Only get used array entries after they have been exposed by host */
virtio_rmb();
last_used = (vq->last_used_idx & (vq->vring.num - 1));
i = virtio32_to_cpu(vq->vdev, vq->vring.used->ring[last_used].id);
if (len) {
*len = virtio32_to_cpu(vq->vdev,
vq->vring.used->ring[last_used].len);
debug("(%s.%d): last used idx %u with len %u\n",
vq->vdev->name, vq->index, i, *len);
}
if (unlikely(i >= vq->vring.num)) {
printf("(%s.%d): id %u out of range\n",
vq->vdev->name, vq->index, i);
return NULL;
}
detach_buf(vq, i);
vq->last_used_idx++;
/*
* If we expect an interrupt for the next entry, tell host
* by writing event index and flush out the write before
* the read in the next get_buf call.
*/
if (!(vq->avail_flags_shadow & VRING_AVAIL_F_NO_INTERRUPT))
virtio_store_mb(&vring_used_event(&vq->vring),
cpu_to_virtio16(vq->vdev, vq->last_used_idx));
return (void *)(uintptr_t)virtio64_to_cpu(vq->vdev,
vq->vring.desc[i].addr);
}
static struct virtqueue *__vring_new_virtqueue(unsigned int index,
struct vring vring,
struct udevice *udev)
{
unsigned int i;
struct virtqueue *vq;
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
struct udevice *vdev = uc_priv->vdev;
vq = malloc(sizeof(*vq));
if (!vq)
return NULL;
vq->vdev = vdev;
vq->index = index;
vq->num_free = vring.num;
vq->vring = vring;
vq->last_used_idx = 0;
vq->avail_flags_shadow = 0;
vq->avail_idx_shadow = 0;
vq->num_added = 0;
list_add_tail(&vq->list, &uc_priv->vqs);
vq->event = virtio_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX);
/* Tell other side not to bother us */
vq->avail_flags_shadow |= VRING_AVAIL_F_NO_INTERRUPT;
if (!vq->event)
vq->vring.avail->flags = cpu_to_virtio16(vdev,
vq->avail_flags_shadow);
/* Put everything in free lists */
vq->free_head = 0;
for (i = 0; i < vring.num - 1; i++)
vq->vring.desc[i].next = cpu_to_virtio16(vdev, i + 1);
return vq;
}
struct virtqueue *vring_create_virtqueue(unsigned int index, unsigned int num,
unsigned int vring_align,
struct udevice *udev)
{
struct virtqueue *vq;
void *queue = NULL;
struct vring vring;
/* We assume num is a power of 2 */
if (num & (num - 1)) {
printf("Bad virtqueue length %u\n", num);
return NULL;
}
/* TODO: allocate each queue chunk individually */
for (; num && vring_size(num, vring_align) > PAGE_SIZE; num /= 2) {
queue = memalign(PAGE_SIZE, vring_size(num, vring_align));
if (queue)
break;
}
if (!num)
return NULL;
if (!queue) {
/* Try to get a single page. You are my only hope! */
queue = memalign(PAGE_SIZE, vring_size(num, vring_align));
}
if (!queue)
return NULL;
memset(queue, 0, vring_size(num, vring_align));
vring_init(&vring, num, queue, vring_align);
vq = __vring_new_virtqueue(index, vring, udev);
if (!vq) {
free(queue);
return NULL;
}
debug("(%s): created vring @ %p for vq @ %p with num %u\n", udev->name,
queue, vq, num);
return vq;
}
void vring_del_virtqueue(struct virtqueue *vq)
{
free(vq->vring.desc);
list_del(&vq->list);
free(vq);
}
unsigned int virtqueue_get_vring_size(struct virtqueue *vq)
{
return vq->vring.num;
}
ulong virtqueue_get_desc_addr(struct virtqueue *vq)
{
return (ulong)vq->vring.desc;
}
ulong virtqueue_get_avail_addr(struct virtqueue *vq)
{
return (ulong)vq->vring.desc +
((char *)vq->vring.avail - (char *)vq->vring.desc);
}
ulong virtqueue_get_used_addr(struct virtqueue *vq)
{
return (ulong)vq->vring.desc +
((char *)vq->vring.used - (char *)vq->vring.desc);
}
bool virtqueue_poll(struct virtqueue *vq, u16 last_used_idx)
{
virtio_mb();
return last_used_idx != virtio16_to_cpu(vq->vdev, vq->vring.used->idx);
}
void virtqueue_dump(struct virtqueue *vq)
{
unsigned int i;
printf("virtqueue %p for dev %s:\n", vq, vq->vdev->name);
printf("\tindex %u, phys addr %p num %u\n",
vq->index, vq->vring.desc, vq->vring.num);
printf("\tfree_head %u, num_added %u, num_free %u\n",
vq->free_head, vq->num_added, vq->num_free);
printf("\tlast_used_idx %u, avail_flags_shadow %u, avail_idx_shadow %u\n",
vq->last_used_idx, vq->avail_flags_shadow, vq->avail_idx_shadow);
printf("Descriptor dump:\n");
for (i = 0; i < vq->vring.num; i++) {
printf("\tdesc[%u] = { 0x%llx, len %u, flags %u, next %u }\n",
i, vq->vring.desc[i].addr, vq->vring.desc[i].len,
vq->vring.desc[i].flags, vq->vring.desc[i].next);
}
printf("Avail ring dump:\n");
printf("\tflags %u, idx %u\n",
vq->vring.avail->flags, vq->vring.avail->idx);
for (i = 0; i < vq->vring.num; i++) {
printf("\tavail[%u] = %u\n",
i, vq->vring.avail->ring[i]);
}
printf("Used ring dump:\n");
printf("\tflags %u, idx %u\n",
vq->vring.used->flags, vq->vring.used->idx);
for (i = 0; i < vq->vring.num; i++) {
printf("\tused[%u] = { %u, %u }\n", i,
vq->vring.used->ring[i].id, vq->vring.used->ring[i].len);
}
}
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*
* VirtIO Sandbox transport driver, for testing purpose only
*/
#include <common.h>
#include <dm.h>
#include <virtio_types.h>
#include <virtio.h>
#include <virtio_ring.h>
#include <linux/compat.h>
#include <linux/io.h>
struct virtio_sandbox_priv {
u8 id;
u8 status;
u64 device_features;
u64 driver_features;
ulong queue_desc;
ulong queue_available;
ulong queue_used;
};
static int virtio_sandbox_get_config(struct udevice *udev, unsigned int offset,
void *buf, unsigned int len)
{
return 0;
}
static int virtio_sandbox_set_config(struct udevice *udev, unsigned int offset,
const void *buf, unsigned int len)
{
return 0;
}
static int virtio_sandbox_get_status(struct udevice *udev, u8 *status)
{
struct virtio_sandbox_priv *priv = dev_get_priv(udev);
*status = priv->status;
return 0;
}
static int virtio_sandbox_set_status(struct udevice *udev, u8 status)
{
struct virtio_sandbox_priv *priv = dev_get_priv(udev);
/* We should never be setting status to 0 */
WARN_ON(status == 0);
priv->status = status;
return 0;
}
static int virtio_sandbox_reset(struct udevice *udev)
{
struct virtio_sandbox_priv *priv = dev_get_priv(udev);
/* 0 status means a reset */
priv->status = 0;
return 0;
}
static int virtio_sandbox_get_features(struct udevice *udev, u64 *features)
{
struct virtio_sandbox_priv *priv = dev_get_priv(udev);
*features = priv->device_features;
return 0;
}
static int virtio_sandbox_set_features(struct udevice *udev)
{
struct virtio_sandbox_priv *priv = dev_get_priv(udev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
priv->driver_features = uc_priv->features;
return 0;
}
static struct virtqueue *virtio_sandbox_setup_vq(struct udevice *udev,
unsigned int index)
{
struct virtio_sandbox_priv *priv = dev_get_priv(udev);
struct virtqueue *vq;
ulong addr;
int err;
/* Create the vring */
vq = vring_create_virtqueue(index, 4, 4096, udev);
if (!vq) {
err = -ENOMEM;
goto error_new_virtqueue;
}
addr = virtqueue_get_desc_addr(vq);
priv->queue_desc = addr;
addr = virtqueue_get_avail_addr(vq);
priv->queue_available = addr;
addr = virtqueue_get_used_addr(vq);
priv->queue_used = addr;
return vq;
error_new_virtqueue:
return ERR_PTR(err);
}
static void virtio_sandbox_del_vq(struct virtqueue *vq)
{
vring_del_virtqueue(vq);
}
static int virtio_sandbox_del_vqs(struct udevice *udev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
struct virtqueue *vq, *n;
list_for_each_entry_safe(vq, n, &uc_priv->vqs, list)
virtio_sandbox_del_vq(vq);
return 0;
}
static int virtio_sandbox_find_vqs(struct udevice *udev, unsigned int nvqs,
struct virtqueue *vqs[])
{
int i;
for (i = 0; i < nvqs; ++i) {
vqs[i] = virtio_sandbox_setup_vq(udev, i);
if (IS_ERR(vqs[i])) {
virtio_sandbox_del_vqs(udev);
return PTR_ERR(vqs[i]);
}
}
return 0;
}
static int virtio_sandbox_notify(struct udevice *udev, struct virtqueue *vq)
{
return 0;
}
static int virtio_sandbox_probe(struct udevice *udev)
{
struct virtio_sandbox_priv *priv = dev_get_priv(udev);
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
/* fake some information for testing */
priv->device_features = VIRTIO_F_VERSION_1;
uc_priv->device = VIRTIO_ID_BLOCK;
uc_priv->vendor = ('u' << 24) | ('b' << 16) | ('o' << 8) | 't';
return 0;
}
/* check virtio device driver's remove routine was called to reset the device */
static int virtio_sandbox_child_post_remove(struct udevice *vdev)
{
u8 status;
virtio_get_status(vdev, &status);
if (status)
panic("virtio device was not reset\n");
return 0;
}
static const struct dm_virtio_ops virtio_sandbox1_ops = {
.get_config = virtio_sandbox_get_config,
.set_config = virtio_sandbox_set_config,
.get_status = virtio_sandbox_get_status,
.set_status = virtio_sandbox_set_status,
.reset = virtio_sandbox_reset,
.get_features = virtio_sandbox_get_features,
.set_features = virtio_sandbox_set_features,
.find_vqs = virtio_sandbox_find_vqs,
.del_vqs = virtio_sandbox_del_vqs,
.notify = virtio_sandbox_notify,
};
static const struct udevice_id virtio_sandbox1_ids[] = {
{ .compatible = "sandbox,virtio1" },
{ }
};
U_BOOT_DRIVER(virtio_sandbox1) = {
.name = "virtio-sandbox1",
.id = UCLASS_VIRTIO,
.of_match = virtio_sandbox1_ids,
.ops = &virtio_sandbox1_ops,
.probe = virtio_sandbox_probe,
.child_post_remove = virtio_sandbox_child_post_remove,
.priv_auto_alloc_size = sizeof(struct virtio_sandbox_priv),
};
/* this one without notify op */
static const struct dm_virtio_ops virtio_sandbox2_ops = {
.get_config = virtio_sandbox_get_config,
.set_config = virtio_sandbox_set_config,
.get_status = virtio_sandbox_get_status,
.set_status = virtio_sandbox_set_status,
.reset = virtio_sandbox_reset,
.get_features = virtio_sandbox_get_features,
.set_features = virtio_sandbox_set_features,
.find_vqs = virtio_sandbox_find_vqs,
.del_vqs = virtio_sandbox_del_vqs,
};
static const struct udevice_id virtio_sandbox2_ids[] = {
{ .compatible = "sandbox,virtio2" },
{ }
};
U_BOOT_DRIVER(virtio_sandbox2) = {
.name = "virtio-sandbox2",
.id = UCLASS_VIRTIO,
.of_match = virtio_sandbox2_ids,
.ops = &virtio_sandbox2_ops,
.probe = virtio_sandbox_probe,
.priv_auto_alloc_size = sizeof(struct virtio_sandbox_priv),
};