Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig VDPA
tristate "vDPA drivers"
depends on NET
help
Enable this module to support vDPA device that uses a
datapath which complies with virtio specifications with
vendor specific control path.
if VDPA
config VDPA_SIM
tristate "vDPA device simulator core"
depends on RUNTIME_TESTING_MENU
select VHOST_RING
select IOMMU_IOVA
help
Enable this module to support vDPA device simulators. These devices
are used for testing, prototyping and development of vDPA.
config VDPA_SIM_NET
tristate "vDPA simulator for networking device"
depends on VDPA_SIM
select GENERIC_NET_UTILS
help
vDPA networking device simulator which loops TX traffic back to RX.
config VDPA_SIM_BLOCK
tristate "vDPA simulator for block device"
depends on VDPA_SIM
help
vDPA block device simulator which terminates IO request in a
memory buffer.
config VDPA_USER
tristate "VDUSE (vDPA Device in Userspace) support"
depends on EVENTFD && MMU
select VHOST_IOTLB
select IOMMU_IOVA
help
With VDUSE it is possible to emulate a vDPA Device
in a userspace program.
config IFCVF
tristate "Intel IFC VF vDPA driver"
depends on PCI_MSI
help
This kernel module can drive Intel IFC VF NIC to offload
virtio dataplane traffic to hardware.
To compile this driver as a module, choose M here: the module will
be called ifcvf.
config MLX5_VDPA
bool
select VHOST_IOTLB
help
Support library for Mellanox VDPA drivers. Provides code that is
common for all types of VDPA drivers. The following drivers are planned:
net, block.
config MLX5_VDPA_NET
tristate "vDPA driver for ConnectX devices"
select MLX5_VDPA
select VHOST_RING
depends on MLX5_CORE
help
VDPA network driver for ConnectX6 and newer. Provides offloading
of virtio net datapath such that descriptors put on the ring will
be executed by the hardware. It also supports a variety of stateless
offloads depending on the actual device used and firmware version.
config MLX5_VDPA_STEERING_DEBUG
bool "expose steering counters on debugfs"
select MLX5_VDPA
help
Expose RX steering counters in debugfs to aid in debugging. For each VLAN
or non VLAN interface, two hardware counters are added to the RX flow
table: one for unicast and one for multicast.
The counters counts the number of packets and bytes and exposes them in
debugfs. Once can read the counters using, e.g.:
cat /sys/kernel/debug/mlx5/mlx5_core.sf.1/vdpa-0/rx/untagged/ucast/packets
cat /sys/kernel/debug/mlx5/mlx5_core.sf.1/vdpa-0/rx/untagged/mcast/bytes
config VP_VDPA
tristate "Virtio PCI bridge vDPA driver"
select VIRTIO_PCI_LIB
depends on PCI_MSI
help
This kernel module bridges virtio PCI device to vDPA bus.
config ALIBABA_ENI_VDPA
tristate "vDPA driver for Alibaba ENI"
select VIRTIO_PCI_LIB_LEGACY
depends on PCI_MSI && X86
help
VDPA driver for Alibaba ENI (Elastic Network Interface) which is built upon
virtio 0.9.5 specification.
config SNET_VDPA
tristate "SolidRun's vDPA driver for SolidNET"
depends on PCI_MSI && PCI_IOV && (HWMON || HWMON=n)
# This driver MAY create a HWMON device.
# Depending on (HWMON || HWMON=n) ensures that:
# If HWMON=n the driver can be compiled either as a module or built-in.
# If HWMON=y the driver can be compiled either as a module or built-in.
# If HWMON=m the driver is forced to be compiled as a module.
# By doing so, IS_ENABLED can be used instead of IS_REACHABLE
help
vDPA driver for SolidNET DPU.
With this driver, the VirtIO dataplane can be
offloaded to a SolidNET DPU.
This driver includes a HW monitor device that
reads health values from the DPU.
config PDS_VDPA
tristate "vDPA driver for AMD/Pensando DSC devices"
select VIRTIO_PCI_LIB
depends on PCI_MSI
depends on PDS_CORE
help
vDPA network driver for AMD/Pensando's PDS Core devices.
With this driver, the VirtIO dataplane can be
offloaded to an AMD/Pensando DSC device.
config OCTEONEP_VDPA
tristate "vDPA driver for Octeon DPU devices"
depends on m
depends on PCI_MSI
help
This is a vDPA driver designed for Marvell's Octeon DPU devices.
This driver enables the offloading of the VirtIO dataplane to an
Octeon DPU device.
Please note that this driver must be built as a module and it
cannot be loaded until the Octeon emulation software is running.
endif # VDPA
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VDPA) += vdpa.o
obj-$(CONFIG_VDPA_SIM) += vdpa_sim/
obj-$(CONFIG_VDPA_USER) += vdpa_user/
obj-$(CONFIG_IFCVF) += ifcvf/
obj-$(CONFIG_MLX5_VDPA) += mlx5/
obj-$(CONFIG_VP_VDPA) += virtio_pci/
obj-$(CONFIG_ALIBABA_ENI_VDPA) += alibaba/
obj-$(CONFIG_SNET_VDPA) += solidrun/
obj-$(CONFIG_PDS_VDPA) += pds/
obj-$(CONFIG_OCTEONEP_VDPA) += octeon_ep/
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ALIBABA_ENI_VDPA) += eni_vdpa.o
+560
View File
@@ -0,0 +1,560 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vDPA bridge driver for Alibaba ENI(Elastic Network Interface)
*
* Copyright (c) 2021, Alibaba Inc. All rights reserved.
* Author: Wu Zongyong <wuzongyong@linux.alibaba.com>
*
*/
#include "linux/bits.h"
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/vdpa.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ring.h>
#include <linux/virtio_pci.h>
#include <linux/virtio_pci_legacy.h>
#include <uapi/linux/virtio_net.h>
#define ENI_MSIX_NAME_SIZE 256
#define ENI_ERR(pdev, fmt, ...) \
dev_err(&pdev->dev, "%s"fmt, "eni_vdpa: ", ##__VA_ARGS__)
#define ENI_DBG(pdev, fmt, ...) \
dev_dbg(&pdev->dev, "%s"fmt, "eni_vdpa: ", ##__VA_ARGS__)
#define ENI_INFO(pdev, fmt, ...) \
dev_info(&pdev->dev, "%s"fmt, "eni_vdpa: ", ##__VA_ARGS__)
struct eni_vring {
void __iomem *notify;
char msix_name[ENI_MSIX_NAME_SIZE];
struct vdpa_callback cb;
int irq;
};
struct eni_vdpa {
struct vdpa_device vdpa;
struct virtio_pci_legacy_device ldev;
struct eni_vring *vring;
struct vdpa_callback config_cb;
char msix_name[ENI_MSIX_NAME_SIZE];
int config_irq;
int queues;
int vectors;
};
static struct eni_vdpa *vdpa_to_eni(struct vdpa_device *vdpa)
{
return container_of(vdpa, struct eni_vdpa, vdpa);
}
static struct virtio_pci_legacy_device *vdpa_to_ldev(struct vdpa_device *vdpa)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
return &eni_vdpa->ldev;
}
static u64 eni_vdpa_get_device_features(struct vdpa_device *vdpa)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
u64 features = vp_legacy_get_features(ldev);
features |= BIT_ULL(VIRTIO_F_ACCESS_PLATFORM);
features |= BIT_ULL(VIRTIO_F_ORDER_PLATFORM);
return features;
}
static int eni_vdpa_set_driver_features(struct vdpa_device *vdpa, u64 features)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
if (!(features & BIT_ULL(VIRTIO_NET_F_MRG_RXBUF)) && features) {
ENI_ERR(ldev->pci_dev,
"VIRTIO_NET_F_MRG_RXBUF is not negotiated\n");
return -EINVAL;
}
vp_legacy_set_features(ldev, (u32)features);
return 0;
}
static u64 eni_vdpa_get_driver_features(struct vdpa_device *vdpa)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
return vp_legacy_get_driver_features(ldev);
}
static u8 eni_vdpa_get_status(struct vdpa_device *vdpa)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
return vp_legacy_get_status(ldev);
}
static int eni_vdpa_get_vq_irq(struct vdpa_device *vdpa, u16 idx)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
int irq = eni_vdpa->vring[idx].irq;
if (irq == VIRTIO_MSI_NO_VECTOR)
return -EINVAL;
return irq;
}
static void eni_vdpa_free_irq(struct eni_vdpa *eni_vdpa)
{
struct virtio_pci_legacy_device *ldev = &eni_vdpa->ldev;
struct pci_dev *pdev = ldev->pci_dev;
int i;
for (i = 0; i < eni_vdpa->queues; i++) {
if (eni_vdpa->vring[i].irq != VIRTIO_MSI_NO_VECTOR) {
vp_legacy_queue_vector(ldev, i, VIRTIO_MSI_NO_VECTOR);
devm_free_irq(&pdev->dev, eni_vdpa->vring[i].irq,
&eni_vdpa->vring[i]);
eni_vdpa->vring[i].irq = VIRTIO_MSI_NO_VECTOR;
}
}
if (eni_vdpa->config_irq != VIRTIO_MSI_NO_VECTOR) {
vp_legacy_config_vector(ldev, VIRTIO_MSI_NO_VECTOR);
devm_free_irq(&pdev->dev, eni_vdpa->config_irq, eni_vdpa);
eni_vdpa->config_irq = VIRTIO_MSI_NO_VECTOR;
}
if (eni_vdpa->vectors) {
pci_free_irq_vectors(pdev);
eni_vdpa->vectors = 0;
}
}
static irqreturn_t eni_vdpa_vq_handler(int irq, void *arg)
{
struct eni_vring *vring = arg;
if (vring->cb.callback)
return vring->cb.callback(vring->cb.private);
return IRQ_HANDLED;
}
static irqreturn_t eni_vdpa_config_handler(int irq, void *arg)
{
struct eni_vdpa *eni_vdpa = arg;
if (eni_vdpa->config_cb.callback)
return eni_vdpa->config_cb.callback(eni_vdpa->config_cb.private);
return IRQ_HANDLED;
}
static int eni_vdpa_request_irq(struct eni_vdpa *eni_vdpa)
{
struct virtio_pci_legacy_device *ldev = &eni_vdpa->ldev;
struct pci_dev *pdev = ldev->pci_dev;
int i, ret, irq;
int queues = eni_vdpa->queues;
int vectors = queues + 1;
ret = pci_alloc_irq_vectors(pdev, vectors, vectors, PCI_IRQ_MSIX);
if (ret != vectors) {
ENI_ERR(pdev,
"failed to allocate irq vectors want %d but %d\n",
vectors, ret);
return ret;
}
eni_vdpa->vectors = vectors;
for (i = 0; i < queues; i++) {
snprintf(eni_vdpa->vring[i].msix_name, ENI_MSIX_NAME_SIZE,
"eni-vdpa[%s]-%d\n", pci_name(pdev), i);
irq = pci_irq_vector(pdev, i);
ret = devm_request_irq(&pdev->dev, irq,
eni_vdpa_vq_handler,
0, eni_vdpa->vring[i].msix_name,
&eni_vdpa->vring[i]);
if (ret) {
ENI_ERR(pdev, "failed to request irq for vq %d\n", i);
goto err;
}
vp_legacy_queue_vector(ldev, i, i);
eni_vdpa->vring[i].irq = irq;
}
snprintf(eni_vdpa->msix_name, ENI_MSIX_NAME_SIZE, "eni-vdpa[%s]-config\n",
pci_name(pdev));
irq = pci_irq_vector(pdev, queues);
ret = devm_request_irq(&pdev->dev, irq, eni_vdpa_config_handler, 0,
eni_vdpa->msix_name, eni_vdpa);
if (ret) {
ENI_ERR(pdev, "failed to request irq for config vq %d\n", i);
goto err;
}
vp_legacy_config_vector(ldev, queues);
eni_vdpa->config_irq = irq;
return 0;
err:
eni_vdpa_free_irq(eni_vdpa);
return ret;
}
static void eni_vdpa_set_status(struct vdpa_device *vdpa, u8 status)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
struct virtio_pci_legacy_device *ldev = &eni_vdpa->ldev;
u8 s = eni_vdpa_get_status(vdpa);
if (status & VIRTIO_CONFIG_S_DRIVER_OK &&
!(s & VIRTIO_CONFIG_S_DRIVER_OK)) {
eni_vdpa_request_irq(eni_vdpa);
}
vp_legacy_set_status(ldev, status);
if (!(status & VIRTIO_CONFIG_S_DRIVER_OK) &&
(s & VIRTIO_CONFIG_S_DRIVER_OK))
eni_vdpa_free_irq(eni_vdpa);
}
static int eni_vdpa_reset(struct vdpa_device *vdpa)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
struct virtio_pci_legacy_device *ldev = &eni_vdpa->ldev;
u8 s = eni_vdpa_get_status(vdpa);
vp_legacy_set_status(ldev, 0);
if (s & VIRTIO_CONFIG_S_DRIVER_OK)
eni_vdpa_free_irq(eni_vdpa);
return 0;
}
static u16 eni_vdpa_get_vq_num_max(struct vdpa_device *vdpa)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
return vp_legacy_get_queue_size(ldev, 0);
}
static u16 eni_vdpa_get_vq_num_min(struct vdpa_device *vdpa)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
return vp_legacy_get_queue_size(ldev, 0);
}
static u16 eni_vdpa_get_vq_size(struct vdpa_device *vdpa, u16 qid)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
return vp_legacy_get_queue_size(ldev, qid);
}
static int eni_vdpa_get_vq_state(struct vdpa_device *vdpa, u16 qid,
struct vdpa_vq_state *state)
{
return -EOPNOTSUPP;
}
static int eni_vdpa_set_vq_state(struct vdpa_device *vdpa, u16 qid,
const struct vdpa_vq_state *state)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
const struct vdpa_vq_state_split *split = &state->split;
/* ENI is build upon virtio-pci specfication which not support
* to set state of virtqueue. But if the state is equal to the
* device initial state by chance, we can let it go.
*/
if (!vp_legacy_get_queue_enable(ldev, qid)
&& split->avail_index == 0)
return 0;
return -EOPNOTSUPP;
}
static void eni_vdpa_set_vq_cb(struct vdpa_device *vdpa, u16 qid,
struct vdpa_callback *cb)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
eni_vdpa->vring[qid].cb = *cb;
}
static void eni_vdpa_set_vq_ready(struct vdpa_device *vdpa, u16 qid,
bool ready)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
/* ENI is a legacy virtio-pci device. This is not supported
* by specification. But we can disable virtqueue by setting
* address to 0.
*/
if (!ready)
vp_legacy_set_queue_address(ldev, qid, 0);
}
static bool eni_vdpa_get_vq_ready(struct vdpa_device *vdpa, u16 qid)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
return vp_legacy_get_queue_enable(ldev, qid);
}
static void eni_vdpa_set_vq_num(struct vdpa_device *vdpa, u16 qid,
u32 num)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
struct pci_dev *pdev = ldev->pci_dev;
u16 n = vp_legacy_get_queue_size(ldev, qid);
/* ENI is a legacy virtio-pci device which not allow to change
* virtqueue size. Just report a error if someone tries to
* change it.
*/
if (num != n)
ENI_ERR(pdev,
"not support to set vq %u fixed num %u to %u\n",
qid, n, num);
}
static int eni_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 qid,
u64 desc_area, u64 driver_area,
u64 device_area)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
u32 pfn = desc_area >> VIRTIO_PCI_QUEUE_ADDR_SHIFT;
vp_legacy_set_queue_address(ldev, qid, pfn);
return 0;
}
static void eni_vdpa_kick_vq(struct vdpa_device *vdpa, u16 qid)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
iowrite16(qid, eni_vdpa->vring[qid].notify);
}
static u32 eni_vdpa_get_device_id(struct vdpa_device *vdpa)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
return ldev->id.device;
}
static u32 eni_vdpa_get_vendor_id(struct vdpa_device *vdpa)
{
struct virtio_pci_legacy_device *ldev = vdpa_to_ldev(vdpa);
return ldev->id.vendor;
}
static u32 eni_vdpa_get_vq_align(struct vdpa_device *vdpa)
{
return VIRTIO_PCI_VRING_ALIGN;
}
static size_t eni_vdpa_get_config_size(struct vdpa_device *vdpa)
{
return sizeof(struct virtio_net_config);
}
static void eni_vdpa_get_config(struct vdpa_device *vdpa,
unsigned int offset,
void *buf, unsigned int len)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
struct virtio_pci_legacy_device *ldev = &eni_vdpa->ldev;
void __iomem *ioaddr = ldev->ioaddr +
VIRTIO_PCI_CONFIG_OFF(eni_vdpa->vectors) +
offset;
u8 *p = buf;
int i;
for (i = 0; i < len; i++)
*p++ = ioread8(ioaddr + i);
}
static void eni_vdpa_set_config(struct vdpa_device *vdpa,
unsigned int offset, const void *buf,
unsigned int len)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
struct virtio_pci_legacy_device *ldev = &eni_vdpa->ldev;
void __iomem *ioaddr = ldev->ioaddr +
VIRTIO_PCI_CONFIG_OFF(eni_vdpa->vectors) +
offset;
const u8 *p = buf;
int i;
for (i = 0; i < len; i++)
iowrite8(*p++, ioaddr + i);
}
static void eni_vdpa_set_config_cb(struct vdpa_device *vdpa,
struct vdpa_callback *cb)
{
struct eni_vdpa *eni_vdpa = vdpa_to_eni(vdpa);
eni_vdpa->config_cb = *cb;
}
static const struct vdpa_config_ops eni_vdpa_ops = {
.get_device_features = eni_vdpa_get_device_features,
.set_driver_features = eni_vdpa_set_driver_features,
.get_driver_features = eni_vdpa_get_driver_features,
.get_status = eni_vdpa_get_status,
.set_status = eni_vdpa_set_status,
.reset = eni_vdpa_reset,
.get_vq_num_max = eni_vdpa_get_vq_num_max,
.get_vq_num_min = eni_vdpa_get_vq_num_min,
.get_vq_size = eni_vdpa_get_vq_size,
.get_vq_state = eni_vdpa_get_vq_state,
.set_vq_state = eni_vdpa_set_vq_state,
.set_vq_cb = eni_vdpa_set_vq_cb,
.set_vq_ready = eni_vdpa_set_vq_ready,
.get_vq_ready = eni_vdpa_get_vq_ready,
.set_vq_num = eni_vdpa_set_vq_num,
.set_vq_address = eni_vdpa_set_vq_address,
.kick_vq = eni_vdpa_kick_vq,
.get_device_id = eni_vdpa_get_device_id,
.get_vendor_id = eni_vdpa_get_vendor_id,
.get_vq_align = eni_vdpa_get_vq_align,
.get_config_size = eni_vdpa_get_config_size,
.get_config = eni_vdpa_get_config,
.set_config = eni_vdpa_set_config,
.set_config_cb = eni_vdpa_set_config_cb,
.get_vq_irq = eni_vdpa_get_vq_irq,
};
static u16 eni_vdpa_get_num_queues(struct eni_vdpa *eni_vdpa)
{
struct virtio_pci_legacy_device *ldev = &eni_vdpa->ldev;
u32 features = vp_legacy_get_features(ldev);
u16 num = 2;
if (features & BIT_ULL(VIRTIO_NET_F_MQ)) {
__virtio16 max_virtqueue_pairs;
eni_vdpa_get_config(&eni_vdpa->vdpa,
offsetof(struct virtio_net_config, max_virtqueue_pairs),
&max_virtqueue_pairs,
sizeof(max_virtqueue_pairs));
num = 2 * __virtio16_to_cpu(virtio_legacy_is_little_endian(),
max_virtqueue_pairs);
}
if (features & BIT_ULL(VIRTIO_NET_F_CTRL_VQ))
num += 1;
return num;
}
static int eni_vdpa_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct device *dev = &pdev->dev;
struct eni_vdpa *eni_vdpa;
struct virtio_pci_legacy_device *ldev;
int ret, i;
ret = pcim_enable_device(pdev);
if (ret)
return ret;
eni_vdpa = vdpa_alloc_device(struct eni_vdpa, vdpa,
dev, &eni_vdpa_ops, NULL,
1, 1, NULL, false);
if (IS_ERR(eni_vdpa)) {
ENI_ERR(pdev, "failed to allocate vDPA structure\n");
return PTR_ERR(eni_vdpa);
}
ldev = &eni_vdpa->ldev;
ldev->pci_dev = pdev;
ret = vp_legacy_probe(ldev);
if (ret) {
ENI_ERR(pdev, "failed to probe legacy PCI device\n");
goto err;
}
pci_set_master(pdev);
pci_set_drvdata(pdev, eni_vdpa);
eni_vdpa->vdpa.vmap.dma_dev = &pdev->dev;
eni_vdpa->queues = eni_vdpa_get_num_queues(eni_vdpa);
eni_vdpa->vring = devm_kcalloc(&pdev->dev, eni_vdpa->queues,
sizeof(*eni_vdpa->vring),
GFP_KERNEL);
if (!eni_vdpa->vring) {
ret = -ENOMEM;
ENI_ERR(pdev, "failed to allocate virtqueues\n");
goto err_remove_vp_legacy;
}
for (i = 0; i < eni_vdpa->queues; i++) {
eni_vdpa->vring[i].irq = VIRTIO_MSI_NO_VECTOR;
eni_vdpa->vring[i].notify = ldev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY;
}
eni_vdpa->config_irq = VIRTIO_MSI_NO_VECTOR;
ret = vdpa_register_device(&eni_vdpa->vdpa, eni_vdpa->queues);
if (ret) {
ENI_ERR(pdev, "failed to register to vdpa bus\n");
goto err_remove_vp_legacy;
}
return 0;
err_remove_vp_legacy:
vp_legacy_remove(&eni_vdpa->ldev);
err:
put_device(&eni_vdpa->vdpa.dev);
return ret;
}
static void eni_vdpa_remove(struct pci_dev *pdev)
{
struct eni_vdpa *eni_vdpa = pci_get_drvdata(pdev);
vdpa_unregister_device(&eni_vdpa->vdpa);
vp_legacy_remove(&eni_vdpa->ldev);
}
static struct pci_device_id eni_pci_ids[] = {
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_REDHAT_QUMRANET,
VIRTIO_TRANS_ID_NET,
PCI_SUBVENDOR_ID_REDHAT_QUMRANET,
VIRTIO_ID_NET) },
{ 0 },
};
static struct pci_driver eni_vdpa_driver = {
.name = "alibaba-eni-vdpa",
.id_table = eni_pci_ids,
.probe = eni_vdpa_probe,
.remove = eni_vdpa_remove,
};
module_pci_driver(eni_vdpa_driver);
MODULE_AUTHOR("Wu Zongyong <wuzongyong@linux.alibaba.com>");
MODULE_DESCRIPTION("Alibaba ENI vDPA driver");
MODULE_LICENSE("GPL v2");
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_IFCVF) += ifcvf.o
ifcvf-$(CONFIG_IFCVF) += ifcvf_main.o ifcvf_base.o
+431
View File
@@ -0,0 +1,431 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel IFC VF NIC driver for virtio dataplane offloading
*
* Copyright (C) 2020 Intel Corporation.
*
* Author: Zhu Lingshan <lingshan.zhu@intel.com>
*
*/
#include "ifcvf_base.h"
u16 ifcvf_set_vq_vector(struct ifcvf_hw *hw, u16 qid, int vector)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
vp_iowrite16(qid, &cfg->queue_select);
vp_iowrite16(vector, &cfg->queue_msix_vector);
return vp_ioread16(&cfg->queue_msix_vector);
}
u16 ifcvf_set_config_vector(struct ifcvf_hw *hw, int vector)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
vp_iowrite16(vector, &cfg->msix_config);
return vp_ioread16(&cfg->msix_config);
}
static void __iomem *get_cap_addr(struct ifcvf_hw *hw,
struct virtio_pci_cap *cap)
{
u32 length, offset;
u8 bar;
length = le32_to_cpu(cap->length);
offset = le32_to_cpu(cap->offset);
bar = cap->bar;
if (bar >= IFCVF_PCI_MAX_RESOURCE) {
IFCVF_DBG(hw->pdev,
"Invalid bar number %u to get capabilities\n", bar);
return NULL;
}
if (offset + length > pci_resource_len(hw->pdev, bar)) {
IFCVF_DBG(hw->pdev,
"offset(%u) + len(%u) overflows bar%u's capability\n",
offset, length, bar);
return NULL;
}
return hw->base[bar] + offset;
}
static int ifcvf_read_config_range(struct pci_dev *dev,
uint32_t *val, int size, int where)
{
int ret, i;
for (i = 0; i < size; i += 4) {
ret = pci_read_config_dword(dev, where + i, val + i / 4);
if (ret < 0)
return ret;
}
return 0;
}
u16 ifcvf_get_vq_size(struct ifcvf_hw *hw, u16 qid)
{
u16 queue_size;
if (qid >= hw->nr_vring)
return 0;
vp_iowrite16(qid, &hw->common_cfg->queue_select);
queue_size = vp_ioread16(&hw->common_cfg->queue_size);
return queue_size;
}
u16 ifcvf_get_max_vq_size(struct ifcvf_hw *hw)
{
u16 queue_size, max_size, qid;
max_size = ifcvf_get_vq_size(hw, 0);
for (qid = 1; qid < hw->nr_vring; qid++) {
queue_size = ifcvf_get_vq_size(hw, qid);
/* 0 means the queue is unavailable */
if (!queue_size)
continue;
max_size = max(queue_size, max_size);
}
return max_size;
}
int ifcvf_init_hw(struct ifcvf_hw *hw, struct pci_dev *pdev)
{
struct virtio_pci_cap cap;
u16 notify_off;
int ret;
u8 pos;
u32 i;
ret = pci_read_config_byte(pdev, PCI_CAPABILITY_LIST, &pos);
if (ret) {
IFCVF_ERR(pdev, "Failed to read PCI capability list\n");
return -EIO;
}
hw->pdev = pdev;
while (pos) {
ret = ifcvf_read_config_range(pdev, (u32 *)&cap,
sizeof(cap), pos);
if (ret < 0) {
IFCVF_ERR(pdev,
"Failed to get PCI capability at %x\n", pos);
break;
}
if (cap.cap_vndr != PCI_CAP_ID_VNDR)
goto next;
switch (cap.cfg_type) {
case VIRTIO_PCI_CAP_COMMON_CFG:
hw->common_cfg = get_cap_addr(hw, &cap);
IFCVF_DBG(pdev, "hw->common_cfg = %p\n",
hw->common_cfg);
break;
case VIRTIO_PCI_CAP_NOTIFY_CFG:
pci_read_config_dword(pdev, pos + sizeof(cap),
&hw->notify_off_multiplier);
hw->notify_bar = cap.bar;
hw->notify_base = get_cap_addr(hw, &cap);
hw->notify_base_pa = pci_resource_start(pdev, cap.bar) +
le32_to_cpu(cap.offset);
IFCVF_DBG(pdev, "hw->notify_base = %p\n",
hw->notify_base);
break;
case VIRTIO_PCI_CAP_ISR_CFG:
hw->isr = get_cap_addr(hw, &cap);
IFCVF_DBG(pdev, "hw->isr = %p\n", hw->isr);
break;
case VIRTIO_PCI_CAP_DEVICE_CFG:
hw->dev_cfg = get_cap_addr(hw, &cap);
hw->cap_dev_config_size = le32_to_cpu(cap.length);
IFCVF_DBG(pdev, "hw->dev_cfg = %p\n", hw->dev_cfg);
break;
}
next:
pos = cap.cap_next;
}
if (hw->common_cfg == NULL || hw->notify_base == NULL ||
hw->isr == NULL || hw->dev_cfg == NULL) {
IFCVF_ERR(pdev, "Incomplete PCI capabilities\n");
return -EIO;
}
hw->nr_vring = vp_ioread16(&hw->common_cfg->num_queues);
hw->vring = kzalloc(sizeof(struct vring_info) * hw->nr_vring, GFP_KERNEL);
if (!hw->vring)
return -ENOMEM;
for (i = 0; i < hw->nr_vring; i++) {
vp_iowrite16(i, &hw->common_cfg->queue_select);
notify_off = vp_ioread16(&hw->common_cfg->queue_notify_off);
hw->vring[i].notify_addr = hw->notify_base +
notify_off * hw->notify_off_multiplier;
hw->vring[i].notify_pa = hw->notify_base_pa +
notify_off * hw->notify_off_multiplier;
hw->vring[i].irq = -EINVAL;
}
hw->lm_cfg = hw->base[IFCVF_LM_BAR];
IFCVF_DBG(pdev,
"PCI capability mapping: common cfg: %p, notify base: %p\n, isr cfg: %p, device cfg: %p, multiplier: %u\n",
hw->common_cfg, hw->notify_base, hw->isr,
hw->dev_cfg, hw->notify_off_multiplier);
hw->vqs_reused_irq = -EINVAL;
hw->config_irq = -EINVAL;
return 0;
}
u8 ifcvf_get_status(struct ifcvf_hw *hw)
{
return vp_ioread8(&hw->common_cfg->device_status);
}
void ifcvf_set_status(struct ifcvf_hw *hw, u8 status)
{
vp_iowrite8(status, &hw->common_cfg->device_status);
}
void ifcvf_reset(struct ifcvf_hw *hw)
{
ifcvf_set_status(hw, 0);
while (ifcvf_get_status(hw))
msleep(1);
}
u64 ifcvf_get_hw_features(struct ifcvf_hw *hw)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
u32 features_lo, features_hi;
u64 features;
vp_iowrite32(0, &cfg->device_feature_select);
features_lo = vp_ioread32(&cfg->device_feature);
vp_iowrite32(1, &cfg->device_feature_select);
features_hi = vp_ioread32(&cfg->device_feature);
features = ((u64)features_hi << 32) | features_lo;
return features;
}
/* return provisioned vDPA dev features */
u64 ifcvf_get_dev_features(struct ifcvf_hw *hw)
{
return hw->dev_features;
}
u64 ifcvf_get_driver_features(struct ifcvf_hw *hw)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
u32 features_lo, features_hi;
u64 features;
vp_iowrite32(0, &cfg->device_feature_select);
features_lo = vp_ioread32(&cfg->guest_feature);
vp_iowrite32(1, &cfg->device_feature_select);
features_hi = vp_ioread32(&cfg->guest_feature);
features = ((u64)features_hi << 32) | features_lo;
return features;
}
int ifcvf_verify_min_features(struct ifcvf_hw *hw, u64 features)
{
if (!(features & BIT_ULL(VIRTIO_F_ACCESS_PLATFORM)) && features) {
IFCVF_ERR(hw->pdev, "VIRTIO_F_ACCESS_PLATFORM is not negotiated\n");
return -EINVAL;
}
return 0;
}
u32 ifcvf_get_config_size(struct ifcvf_hw *hw)
{
u32 net_config_size = sizeof(struct virtio_net_config);
u32 blk_config_size = sizeof(struct virtio_blk_config);
u32 cap_size = hw->cap_dev_config_size;
u32 config_size;
/* If the onboard device config space size is greater than
* the size of struct virtio_net/blk_config, only the spec
* implementing contents size is returned, this is very
* unlikely, defensive programming.
*/
switch (hw->dev_type) {
case VIRTIO_ID_NET:
config_size = min(cap_size, net_config_size);
break;
case VIRTIO_ID_BLOCK:
config_size = min(cap_size, blk_config_size);
break;
default:
config_size = 0;
IFCVF_ERR(hw->pdev, "VIRTIO ID %u not supported\n", hw->dev_type);
}
return config_size;
}
void ifcvf_read_dev_config(struct ifcvf_hw *hw, u64 offset,
void *dst, int length)
{
u8 old_gen, new_gen, *p;
int i;
WARN_ON(offset + length > hw->config_size);
do {
old_gen = vp_ioread8(&hw->common_cfg->config_generation);
p = dst;
for (i = 0; i < length; i++)
*p++ = vp_ioread8(hw->dev_cfg + offset + i);
new_gen = vp_ioread8(&hw->common_cfg->config_generation);
} while (old_gen != new_gen);
}
void ifcvf_write_dev_config(struct ifcvf_hw *hw, u64 offset,
const void *src, int length)
{
const u8 *p;
int i;
p = src;
WARN_ON(offset + length > hw->config_size);
for (i = 0; i < length; i++)
vp_iowrite8(*p++, hw->dev_cfg + offset + i);
}
void ifcvf_set_driver_features(struct ifcvf_hw *hw, u64 features)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
vp_iowrite32(0, &cfg->guest_feature_select);
vp_iowrite32((u32)features, &cfg->guest_feature);
vp_iowrite32(1, &cfg->guest_feature_select);
vp_iowrite32(features >> 32, &cfg->guest_feature);
}
u16 ifcvf_get_vq_state(struct ifcvf_hw *hw, u16 qid)
{
struct ifcvf_lm_cfg __iomem *lm_cfg = hw->lm_cfg;
u16 last_avail_idx;
last_avail_idx = vp_ioread16(&lm_cfg->vq_state_region + qid * 2);
return last_avail_idx;
}
int ifcvf_set_vq_state(struct ifcvf_hw *hw, u16 qid, u16 num)
{
struct ifcvf_lm_cfg __iomem *lm_cfg = hw->lm_cfg;
vp_iowrite16(num, &lm_cfg->vq_state_region + qid * 2);
return 0;
}
void ifcvf_set_vq_num(struct ifcvf_hw *hw, u16 qid, u32 num)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
vp_iowrite16(qid, &cfg->queue_select);
vp_iowrite16(num, &cfg->queue_size);
}
int ifcvf_set_vq_address(struct ifcvf_hw *hw, u16 qid, u64 desc_area,
u64 driver_area, u64 device_area)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
vp_iowrite16(qid, &cfg->queue_select);
vp_iowrite64_twopart(desc_area, &cfg->queue_desc_lo,
&cfg->queue_desc_hi);
vp_iowrite64_twopart(driver_area, &cfg->queue_avail_lo,
&cfg->queue_avail_hi);
vp_iowrite64_twopart(device_area, &cfg->queue_used_lo,
&cfg->queue_used_hi);
return 0;
}
bool ifcvf_get_vq_ready(struct ifcvf_hw *hw, u16 qid)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
u16 queue_enable;
vp_iowrite16(qid, &cfg->queue_select);
queue_enable = vp_ioread16(&cfg->queue_enable);
return (bool)queue_enable;
}
void ifcvf_set_vq_ready(struct ifcvf_hw *hw, u16 qid, bool ready)
{
struct virtio_pci_common_cfg __iomem *cfg = hw->common_cfg;
vp_iowrite16(qid, &cfg->queue_select);
vp_iowrite16(ready, &cfg->queue_enable);
}
static void ifcvf_reset_vring(struct ifcvf_hw *hw)
{
u16 qid;
for (qid = 0; qid < hw->nr_vring; qid++) {
hw->vring[qid].cb.callback = NULL;
hw->vring[qid].cb.private = NULL;
ifcvf_set_vq_vector(hw, qid, VIRTIO_MSI_NO_VECTOR);
}
}
static void ifcvf_reset_config_handler(struct ifcvf_hw *hw)
{
hw->config_cb.callback = NULL;
hw->config_cb.private = NULL;
ifcvf_set_config_vector(hw, VIRTIO_MSI_NO_VECTOR);
}
static void ifcvf_synchronize_irq(struct ifcvf_hw *hw)
{
u32 nvectors = hw->num_msix_vectors;
struct pci_dev *pdev = hw->pdev;
int i, irq;
for (i = 0; i < nvectors; i++) {
irq = pci_irq_vector(pdev, i);
if (irq >= 0)
synchronize_irq(irq);
}
}
void ifcvf_stop(struct ifcvf_hw *hw)
{
ifcvf_synchronize_irq(hw);
ifcvf_reset_vring(hw);
ifcvf_reset_config_handler(hw);
}
void ifcvf_notify_queue(struct ifcvf_hw *hw, u16 qid)
{
vp_iowrite16(qid, hw->vring[qid].notify_addr);
}
+133
View File
@@ -0,0 +1,133 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Intel IFC VF NIC driver for virtio dataplane offloading
*
* Copyright (C) 2020 Intel Corporation.
*
* Author: Zhu Lingshan <lingshan.zhu@intel.com>
*
*/
#ifndef _IFCVF_H_
#define _IFCVF_H_
#include <linux/pci.h>
#include <linux/pci_regs.h>
#include <linux/vdpa.h>
#include <linux/virtio_pci_modern.h>
#include <uapi/linux/virtio_net.h>
#include <uapi/linux/virtio_blk.h>
#include <uapi/linux/virtio_config.h>
#include <uapi/linux/virtio_pci.h>
#include <uapi/linux/vdpa.h>
#define N3000_DEVICE_ID 0x1041
#define N3000_SUBSYS_DEVICE_ID 0x001A
#define IFCVF_QUEUE_ALIGNMENT PAGE_SIZE
#define IFCVF_PCI_MAX_RESOURCE 6
#define IFCVF_LM_BAR 4
#define IFCVF_MIN_VQ_SIZE 64
#define IFCVF_ERR(pdev, fmt, ...) dev_err(&pdev->dev, fmt, ##__VA_ARGS__)
#define IFCVF_DBG(pdev, fmt, ...) dev_dbg(&pdev->dev, fmt, ##__VA_ARGS__)
#define IFCVF_INFO(pdev, fmt, ...) dev_info(&pdev->dev, fmt, ##__VA_ARGS__)
/* all vqs and config interrupt has its own vector */
#define MSIX_VECTOR_PER_VQ_AND_CONFIG 1
/* all vqs share a vector, and config interrupt has a separate vector */
#define MSIX_VECTOR_SHARED_VQ_AND_CONFIG 2
/* all vqs and config interrupt share a vector */
#define MSIX_VECTOR_DEV_SHARED 3
struct vring_info {
u16 last_avail_idx;
void __iomem *notify_addr;
phys_addr_t notify_pa;
u32 irq;
struct vdpa_callback cb;
char msix_name[256];
};
struct ifcvf_lm_cfg {
__le64 control;
__le64 status;
__le64 lm_mem_log_start_addr;
__le64 lm_mem_log_end_addr;
__le16 vq_state_region;
};
struct ifcvf_hw {
u8 __iomem *isr;
/* Live migration */
struct ifcvf_lm_cfg __iomem *lm_cfg;
/* Notification bar number */
u8 notify_bar;
u8 msix_vector_status;
/* virtio-net or virtio-blk device config size */
u32 config_size;
/* Notificaiton bar address */
void __iomem *notify_base;
phys_addr_t notify_base_pa;
u32 notify_off_multiplier;
u32 dev_type;
u64 hw_features;
/* provisioned device features */
u64 dev_features;
struct virtio_pci_common_cfg __iomem *common_cfg;
void __iomem *dev_cfg;
struct vring_info *vring;
void __iomem * const *base;
char config_msix_name[256];
struct vdpa_callback config_cb;
int config_irq;
int vqs_reused_irq;
u16 nr_vring;
/* VIRTIO_PCI_CAP_DEVICE_CFG size */
u32 num_msix_vectors;
u32 cap_dev_config_size;
struct pci_dev *pdev;
};
struct ifcvf_adapter {
struct vdpa_device vdpa;
struct pci_dev *pdev;
struct ifcvf_hw *vf;
};
struct ifcvf_vdpa_mgmt_dev {
struct vdpa_mgmt_dev mdev;
struct ifcvf_hw vf;
struct ifcvf_adapter *adapter;
struct pci_dev *pdev;
};
int ifcvf_init_hw(struct ifcvf_hw *hw, struct pci_dev *dev);
void ifcvf_stop(struct ifcvf_hw *hw);
void ifcvf_notify_queue(struct ifcvf_hw *hw, u16 qid);
void ifcvf_read_dev_config(struct ifcvf_hw *hw, u64 offset,
void *dst, int length);
void ifcvf_write_dev_config(struct ifcvf_hw *hw, u64 offset,
const void *src, int length);
u8 ifcvf_get_status(struct ifcvf_hw *hw);
void ifcvf_set_status(struct ifcvf_hw *hw, u8 status);
void ifcvf_reset(struct ifcvf_hw *hw);
u64 ifcvf_get_dev_features(struct ifcvf_hw *hw);
u64 ifcvf_get_hw_features(struct ifcvf_hw *hw);
int ifcvf_verify_min_features(struct ifcvf_hw *hw, u64 features);
u16 ifcvf_get_vq_state(struct ifcvf_hw *hw, u16 qid);
int ifcvf_set_vq_state(struct ifcvf_hw *hw, u16 qid, u16 num);
u32 ifcvf_get_config_size(struct ifcvf_hw *hw);
u16 ifcvf_set_vq_vector(struct ifcvf_hw *hw, u16 qid, int vector);
u16 ifcvf_set_config_vector(struct ifcvf_hw *hw, int vector);
void ifcvf_set_vq_num(struct ifcvf_hw *hw, u16 qid, u32 num);
int ifcvf_set_vq_address(struct ifcvf_hw *hw, u16 qid, u64 desc_area,
u64 driver_area, u64 device_area);
bool ifcvf_get_vq_ready(struct ifcvf_hw *hw, u16 qid);
void ifcvf_set_vq_ready(struct ifcvf_hw *hw, u16 qid, bool ready);
void ifcvf_set_driver_features(struct ifcvf_hw *hw, u64 features);
u64 ifcvf_get_driver_features(struct ifcvf_hw *hw);
u16 ifcvf_get_max_vq_size(struct ifcvf_hw *hw);
u16 ifcvf_get_vq_size(struct ifcvf_hw *hw, u16 qid);
#endif /* _IFCVF_H_ */
+906
View File
@@ -0,0 +1,906 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel IFC VF NIC driver for virtio dataplane offloading
*
* Copyright (C) 2020 Intel Corporation.
*
* Author: Zhu Lingshan <lingshan.zhu@intel.com>
*
*/
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/sysfs.h>
#include "ifcvf_base.h"
#define DRIVER_AUTHOR "Intel Corporation"
#define IFCVF_DRIVER_NAME "ifcvf"
static irqreturn_t ifcvf_config_changed(int irq, void *arg)
{
struct ifcvf_hw *vf = arg;
if (vf->config_cb.callback)
return vf->config_cb.callback(vf->config_cb.private);
return IRQ_HANDLED;
}
static irqreturn_t ifcvf_vq_intr_handler(int irq, void *arg)
{
struct vring_info *vring = arg;
if (vring->cb.callback)
return vring->cb.callback(vring->cb.private);
return IRQ_HANDLED;
}
static irqreturn_t ifcvf_vqs_reused_intr_handler(int irq, void *arg)
{
struct ifcvf_hw *vf = arg;
struct vring_info *vring;
int i;
for (i = 0; i < vf->nr_vring; i++) {
vring = &vf->vring[i];
if (vring->cb.callback)
vring->cb.callback(vring->cb.private);
}
return IRQ_HANDLED;
}
static irqreturn_t ifcvf_dev_intr_handler(int irq, void *arg)
{
struct ifcvf_hw *vf = arg;
u8 isr;
isr = vp_ioread8(vf->isr);
if (isr & VIRTIO_PCI_ISR_CONFIG)
ifcvf_config_changed(irq, arg);
return ifcvf_vqs_reused_intr_handler(irq, arg);
}
static void ifcvf_free_irq_vectors(void *data)
{
pci_free_irq_vectors(data);
}
static void ifcvf_free_per_vq_irq(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
int i;
for (i = 0; i < vf->nr_vring; i++) {
if (vf->vring[i].irq != -EINVAL) {
devm_free_irq(&pdev->dev, vf->vring[i].irq, &vf->vring[i]);
vf->vring[i].irq = -EINVAL;
}
}
}
static void ifcvf_free_vqs_reused_irq(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
if (vf->vqs_reused_irq != -EINVAL) {
devm_free_irq(&pdev->dev, vf->vqs_reused_irq, vf);
vf->vqs_reused_irq = -EINVAL;
}
}
static void ifcvf_free_vq_irq(struct ifcvf_hw *vf)
{
if (vf->msix_vector_status == MSIX_VECTOR_PER_VQ_AND_CONFIG)
ifcvf_free_per_vq_irq(vf);
else
ifcvf_free_vqs_reused_irq(vf);
}
static void ifcvf_free_config_irq(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
if (vf->config_irq == -EINVAL)
return;
/* If the irq is shared by all vqs and the config interrupt,
* it is already freed in ifcvf_free_vq_irq, so here only
* need to free config irq when msix_vector_status != MSIX_VECTOR_DEV_SHARED
*/
if (vf->msix_vector_status != MSIX_VECTOR_DEV_SHARED) {
devm_free_irq(&pdev->dev, vf->config_irq, vf);
vf->config_irq = -EINVAL;
}
}
static void ifcvf_free_irq(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
ifcvf_free_vq_irq(vf);
ifcvf_free_config_irq(vf);
ifcvf_free_irq_vectors(pdev);
vf->num_msix_vectors = 0;
}
/* ifcvf MSIX vectors allocator, this helper tries to allocate
* vectors for all virtqueues and the config interrupt.
* It returns the number of allocated vectors, negative
* return value when fails.
*/
static int ifcvf_alloc_vectors(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
int max_intr, ret;
/* all queues and config interrupt */
max_intr = vf->nr_vring + 1;
ret = pci_alloc_irq_vectors(pdev, 1, max_intr, PCI_IRQ_MSIX | PCI_IRQ_AFFINITY);
if (ret < 0) {
IFCVF_ERR(pdev, "Failed to alloc IRQ vectors\n");
return ret;
}
if (ret < max_intr)
IFCVF_INFO(pdev,
"Requested %u vectors, however only %u allocated, lower performance\n",
max_intr, ret);
return ret;
}
static int ifcvf_request_per_vq_irq(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
int i, vector, ret, irq;
vf->vqs_reused_irq = -EINVAL;
for (i = 0; i < vf->nr_vring; i++) {
snprintf(vf->vring[i].msix_name, 256, "ifcvf[%s]-%d\n", pci_name(pdev), i);
vector = i;
irq = pci_irq_vector(pdev, vector);
ret = devm_request_irq(&pdev->dev, irq,
ifcvf_vq_intr_handler, 0,
vf->vring[i].msix_name,
&vf->vring[i]);
if (ret) {
IFCVF_ERR(pdev, "Failed to request irq for vq %d\n", i);
goto err;
}
vf->vring[i].irq = irq;
ret = ifcvf_set_vq_vector(vf, i, vector);
if (ret == VIRTIO_MSI_NO_VECTOR) {
IFCVF_ERR(pdev, "No msix vector for vq %u\n", i);
goto err;
}
}
return 0;
err:
ifcvf_free_irq(vf);
return -EFAULT;
}
static int ifcvf_request_vqs_reused_irq(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
int i, vector, ret, irq;
vector = 0;
snprintf(vf->vring[0].msix_name, 256, "ifcvf[%s]-vqs-reused-irq\n", pci_name(pdev));
irq = pci_irq_vector(pdev, vector);
ret = devm_request_irq(&pdev->dev, irq,
ifcvf_vqs_reused_intr_handler, 0,
vf->vring[0].msix_name, vf);
if (ret) {
IFCVF_ERR(pdev, "Failed to request reused irq for the device\n");
goto err;
}
vf->vqs_reused_irq = irq;
for (i = 0; i < vf->nr_vring; i++) {
vf->vring[i].irq = -EINVAL;
ret = ifcvf_set_vq_vector(vf, i, vector);
if (ret == VIRTIO_MSI_NO_VECTOR) {
IFCVF_ERR(pdev, "No msix vector for vq %u\n", i);
goto err;
}
}
return 0;
err:
ifcvf_free_irq(vf);
return -EFAULT;
}
static int ifcvf_request_dev_irq(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
int i, vector, ret, irq;
vector = 0;
snprintf(vf->vring[0].msix_name, 256, "ifcvf[%s]-dev-irq\n", pci_name(pdev));
irq = pci_irq_vector(pdev, vector);
ret = devm_request_irq(&pdev->dev, irq,
ifcvf_dev_intr_handler, 0,
vf->vring[0].msix_name, vf);
if (ret) {
IFCVF_ERR(pdev, "Failed to request irq for the device\n");
goto err;
}
vf->vqs_reused_irq = irq;
for (i = 0; i < vf->nr_vring; i++) {
vf->vring[i].irq = -EINVAL;
ret = ifcvf_set_vq_vector(vf, i, vector);
if (ret == VIRTIO_MSI_NO_VECTOR) {
IFCVF_ERR(pdev, "No msix vector for vq %u\n", i);
goto err;
}
}
vf->config_irq = irq;
ret = ifcvf_set_config_vector(vf, vector);
if (ret == VIRTIO_MSI_NO_VECTOR) {
IFCVF_ERR(pdev, "No msix vector for device config\n");
goto err;
}
return 0;
err:
ifcvf_free_irq(vf);
return -EFAULT;
}
static int ifcvf_request_vq_irq(struct ifcvf_hw *vf)
{
int ret;
if (vf->msix_vector_status == MSIX_VECTOR_PER_VQ_AND_CONFIG)
ret = ifcvf_request_per_vq_irq(vf);
else
ret = ifcvf_request_vqs_reused_irq(vf);
return ret;
}
static int ifcvf_request_config_irq(struct ifcvf_hw *vf)
{
struct pci_dev *pdev = vf->pdev;
int config_vector, ret;
if (vf->msix_vector_status == MSIX_VECTOR_PER_VQ_AND_CONFIG)
config_vector = vf->nr_vring;
else if (vf->msix_vector_status == MSIX_VECTOR_SHARED_VQ_AND_CONFIG)
/* vector 0 for vqs and 1 for config interrupt */
config_vector = 1;
else if (vf->msix_vector_status == MSIX_VECTOR_DEV_SHARED)
/* re-use the vqs vector */
return 0;
else
return -EINVAL;
snprintf(vf->config_msix_name, 256, "ifcvf[%s]-config\n",
pci_name(pdev));
vf->config_irq = pci_irq_vector(pdev, config_vector);
ret = devm_request_irq(&pdev->dev, vf->config_irq,
ifcvf_config_changed, 0,
vf->config_msix_name, vf);
if (ret) {
IFCVF_ERR(pdev, "Failed to request config irq\n");
goto err;
}
ret = ifcvf_set_config_vector(vf, config_vector);
if (ret == VIRTIO_MSI_NO_VECTOR) {
IFCVF_ERR(pdev, "No msix vector for device config\n");
goto err;
}
return 0;
err:
ifcvf_free_irq(vf);
return -EFAULT;
}
static int ifcvf_request_irq(struct ifcvf_hw *vf)
{
int nvectors, ret, max_intr;
nvectors = ifcvf_alloc_vectors(vf);
if (nvectors <= 0)
return -EFAULT;
vf->msix_vector_status = MSIX_VECTOR_PER_VQ_AND_CONFIG;
max_intr = vf->nr_vring + 1;
if (nvectors < max_intr)
vf->msix_vector_status = MSIX_VECTOR_SHARED_VQ_AND_CONFIG;
if (nvectors == 1) {
vf->msix_vector_status = MSIX_VECTOR_DEV_SHARED;
ret = ifcvf_request_dev_irq(vf);
return ret;
}
ret = ifcvf_request_vq_irq(vf);
if (ret)
return ret;
ret = ifcvf_request_config_irq(vf);
if (ret)
return ret;
vf->num_msix_vectors = nvectors;
return 0;
}
static struct ifcvf_adapter *vdpa_to_adapter(struct vdpa_device *vdpa_dev)
{
return container_of(vdpa_dev, struct ifcvf_adapter, vdpa);
}
static struct ifcvf_hw *vdpa_to_vf(struct vdpa_device *vdpa_dev)
{
struct ifcvf_adapter *adapter = vdpa_to_adapter(vdpa_dev);
return adapter->vf;
}
static u64 ifcvf_vdpa_get_device_features(struct vdpa_device *vdpa_dev)
{
struct ifcvf_adapter *adapter = vdpa_to_adapter(vdpa_dev);
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
struct pci_dev *pdev = adapter->pdev;
u32 type = vf->dev_type;
u64 features;
if (type == VIRTIO_ID_NET || type == VIRTIO_ID_BLOCK)
features = ifcvf_get_dev_features(vf);
else {
features = 0;
IFCVF_ERR(pdev, "VIRTIO ID %u not supported\n", vf->dev_type);
}
return features;
}
static int ifcvf_vdpa_set_driver_features(struct vdpa_device *vdpa_dev, u64 features)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
int ret;
ret = ifcvf_verify_min_features(vf, features);
if (ret)
return ret;
ifcvf_set_driver_features(vf, features);
return 0;
}
static u64 ifcvf_vdpa_get_driver_features(struct vdpa_device *vdpa_dev)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
u64 features;
features = ifcvf_get_driver_features(vf);
return features;
}
static u8 ifcvf_vdpa_get_status(struct vdpa_device *vdpa_dev)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return ifcvf_get_status(vf);
}
static void ifcvf_vdpa_set_status(struct vdpa_device *vdpa_dev, u8 status)
{
struct ifcvf_hw *vf;
u8 status_old;
int ret;
vf = vdpa_to_vf(vdpa_dev);
status_old = ifcvf_get_status(vf);
if (status_old == status)
return;
if ((status & VIRTIO_CONFIG_S_DRIVER_OK) &&
!(status_old & VIRTIO_CONFIG_S_DRIVER_OK)) {
ret = ifcvf_request_irq(vf);
if (ret) {
IFCVF_ERR(vf->pdev, "failed to request irq with error %d\n", ret);
return;
}
}
ifcvf_set_status(vf, status);
}
static int ifcvf_vdpa_reset(struct vdpa_device *vdpa_dev)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
u8 status = ifcvf_get_status(vf);
ifcvf_stop(vf);
if (status & VIRTIO_CONFIG_S_DRIVER_OK)
ifcvf_free_irq(vf);
ifcvf_reset(vf);
return 0;
}
static u16 ifcvf_vdpa_get_vq_num_max(struct vdpa_device *vdpa_dev)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return ifcvf_get_max_vq_size(vf);
}
static u16 ifcvf_vdpa_get_vq_num_min(struct vdpa_device *vdpa_dev)
{
return IFCVF_MIN_VQ_SIZE;
}
static int ifcvf_vdpa_get_vq_state(struct vdpa_device *vdpa_dev, u16 qid,
struct vdpa_vq_state *state)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
state->split.avail_index = ifcvf_get_vq_state(vf, qid);
return 0;
}
static int ifcvf_vdpa_set_vq_state(struct vdpa_device *vdpa_dev, u16 qid,
const struct vdpa_vq_state *state)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return ifcvf_set_vq_state(vf, qid, state->split.avail_index);
}
static void ifcvf_vdpa_set_vq_cb(struct vdpa_device *vdpa_dev, u16 qid,
struct vdpa_callback *cb)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
vf->vring[qid].cb = *cb;
}
static void ifcvf_vdpa_set_vq_ready(struct vdpa_device *vdpa_dev,
u16 qid, bool ready)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
ifcvf_set_vq_ready(vf, qid, ready);
}
static bool ifcvf_vdpa_get_vq_ready(struct vdpa_device *vdpa_dev, u16 qid)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return ifcvf_get_vq_ready(vf, qid);
}
static void ifcvf_vdpa_set_vq_num(struct vdpa_device *vdpa_dev, u16 qid,
u32 num)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
ifcvf_set_vq_num(vf, qid, num);
}
static int ifcvf_vdpa_set_vq_address(struct vdpa_device *vdpa_dev, u16 qid,
u64 desc_area, u64 driver_area,
u64 device_area)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return ifcvf_set_vq_address(vf, qid, desc_area, driver_area, device_area);
}
static void ifcvf_vdpa_kick_vq(struct vdpa_device *vdpa_dev, u16 qid)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
ifcvf_notify_queue(vf, qid);
}
static u32 ifcvf_vdpa_get_generation(struct vdpa_device *vdpa_dev)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return vp_ioread8(&vf->common_cfg->config_generation);
}
static u32 ifcvf_vdpa_get_device_id(struct vdpa_device *vdpa_dev)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return vf->dev_type;
}
static u32 ifcvf_vdpa_get_vendor_id(struct vdpa_device *vdpa_dev)
{
struct ifcvf_adapter *adapter = vdpa_to_adapter(vdpa_dev);
struct pci_dev *pdev = adapter->pdev;
return pdev->subsystem_vendor;
}
static u32 ifcvf_vdpa_get_vq_align(struct vdpa_device *vdpa_dev)
{
return IFCVF_QUEUE_ALIGNMENT;
}
static size_t ifcvf_vdpa_get_config_size(struct vdpa_device *vdpa_dev)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return vf->config_size;
}
static u32 ifcvf_vdpa_get_vq_group(struct vdpa_device *vdpa, u16 idx)
{
return 0;
}
static void ifcvf_vdpa_get_config(struct vdpa_device *vdpa_dev,
unsigned int offset,
void *buf, unsigned int len)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
ifcvf_read_dev_config(vf, offset, buf, len);
}
static void ifcvf_vdpa_set_config(struct vdpa_device *vdpa_dev,
unsigned int offset, const void *buf,
unsigned int len)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
ifcvf_write_dev_config(vf, offset, buf, len);
}
static void ifcvf_vdpa_set_config_cb(struct vdpa_device *vdpa_dev,
struct vdpa_callback *cb)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
vf->config_cb.callback = cb->callback;
vf->config_cb.private = cb->private;
}
static int ifcvf_vdpa_get_vq_irq(struct vdpa_device *vdpa_dev,
u16 qid)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
if (vf->vqs_reused_irq < 0)
return vf->vring[qid].irq;
else
return -EINVAL;
}
static u16 ifcvf_vdpa_get_vq_size(struct vdpa_device *vdpa_dev,
u16 qid)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
return ifcvf_get_vq_size(vf, qid);
}
static struct vdpa_notification_area ifcvf_get_vq_notification(struct vdpa_device *vdpa_dev,
u16 idx)
{
struct ifcvf_hw *vf = vdpa_to_vf(vdpa_dev);
struct vdpa_notification_area area;
area.addr = vf->vring[idx].notify_pa;
if (!vf->notify_off_multiplier)
area.size = PAGE_SIZE;
else
area.size = vf->notify_off_multiplier;
return area;
}
/*
* IFCVF currently doesn't have on-chip IOMMU, so not
* implemented set_map()/dma_map()/dma_unmap()
*/
static const struct vdpa_config_ops ifc_vdpa_ops = {
.get_device_features = ifcvf_vdpa_get_device_features,
.set_driver_features = ifcvf_vdpa_set_driver_features,
.get_driver_features = ifcvf_vdpa_get_driver_features,
.get_status = ifcvf_vdpa_get_status,
.set_status = ifcvf_vdpa_set_status,
.reset = ifcvf_vdpa_reset,
.get_vq_num_max = ifcvf_vdpa_get_vq_num_max,
.get_vq_num_min = ifcvf_vdpa_get_vq_num_min,
.get_vq_state = ifcvf_vdpa_get_vq_state,
.set_vq_state = ifcvf_vdpa_set_vq_state,
.set_vq_cb = ifcvf_vdpa_set_vq_cb,
.set_vq_ready = ifcvf_vdpa_set_vq_ready,
.get_vq_ready = ifcvf_vdpa_get_vq_ready,
.set_vq_num = ifcvf_vdpa_set_vq_num,
.set_vq_address = ifcvf_vdpa_set_vq_address,
.get_vq_irq = ifcvf_vdpa_get_vq_irq,
.get_vq_size = ifcvf_vdpa_get_vq_size,
.kick_vq = ifcvf_vdpa_kick_vq,
.get_generation = ifcvf_vdpa_get_generation,
.get_device_id = ifcvf_vdpa_get_device_id,
.get_vendor_id = ifcvf_vdpa_get_vendor_id,
.get_vq_align = ifcvf_vdpa_get_vq_align,
.get_vq_group = ifcvf_vdpa_get_vq_group,
.get_config_size = ifcvf_vdpa_get_config_size,
.get_config = ifcvf_vdpa_get_config,
.set_config = ifcvf_vdpa_set_config,
.set_config_cb = ifcvf_vdpa_set_config_cb,
.get_vq_notification = ifcvf_get_vq_notification,
};
static struct virtio_device_id id_table_net[] = {
{VIRTIO_ID_NET, VIRTIO_DEV_ANY_ID},
{0},
};
static struct virtio_device_id id_table_blk[] = {
{VIRTIO_ID_BLOCK, VIRTIO_DEV_ANY_ID},
{0},
};
static u32 get_dev_type(struct pci_dev *pdev)
{
u32 dev_type;
/* This drirver drives both modern virtio devices and transitional
* devices in modern mode.
* vDPA requires feature bit VIRTIO_F_ACCESS_PLATFORM,
* so legacy devices and transitional devices in legacy
* mode will not work for vDPA, this driver will not
* drive devices with legacy interface.
*/
if (pdev->device < 0x1040)
dev_type = pdev->subsystem_device;
else
dev_type = pdev->device - 0x1040;
return dev_type;
}
static int ifcvf_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name,
const struct vdpa_dev_set_config *config)
{
struct ifcvf_vdpa_mgmt_dev *ifcvf_mgmt_dev;
struct ifcvf_adapter *adapter;
struct vdpa_device *vdpa_dev;
struct pci_dev *pdev;
struct ifcvf_hw *vf;
u64 device_features;
int ret;
ifcvf_mgmt_dev = container_of(mdev, struct ifcvf_vdpa_mgmt_dev, mdev);
vf = &ifcvf_mgmt_dev->vf;
pdev = vf->pdev;
adapter = vdpa_alloc_device(struct ifcvf_adapter, vdpa,
&pdev->dev, &ifc_vdpa_ops,
NULL, 1, 1, NULL, false);
if (IS_ERR(adapter)) {
IFCVF_ERR(pdev, "Failed to allocate vDPA structure");
return PTR_ERR(adapter);
}
ifcvf_mgmt_dev->adapter = adapter;
adapter->pdev = pdev;
adapter->vdpa.vmap.dma_dev = &pdev->dev;
adapter->vdpa.mdev = mdev;
adapter->vf = vf;
vdpa_dev = &adapter->vdpa;
device_features = vf->hw_features;
if (config->mask & BIT_ULL(VDPA_ATTR_DEV_FEATURES)) {
if (config->device_features & ~device_features) {
IFCVF_ERR(pdev, "The provisioned features 0x%llx are not supported by this device with features 0x%llx\n",
config->device_features, device_features);
return -EINVAL;
}
device_features &= config->device_features;
}
vf->dev_features = device_features;
if (name)
ret = dev_set_name(&vdpa_dev->dev, "%s", name);
else
ret = dev_set_name(&vdpa_dev->dev, "vdpa%u", vdpa_dev->index);
if (ret) {
IFCVF_ERR(pdev, "Failed to set device name");
goto err;
}
ret = _vdpa_register_device(&adapter->vdpa, vf->nr_vring);
if (ret) {
IFCVF_ERR(pdev, "Failed to register to vDPA bus");
goto err;
}
return 0;
err:
put_device(&adapter->vdpa.dev);
return ret;
}
static void ifcvf_vdpa_dev_del(struct vdpa_mgmt_dev *mdev, struct vdpa_device *dev)
{
struct ifcvf_vdpa_mgmt_dev *ifcvf_mgmt_dev;
ifcvf_mgmt_dev = container_of(mdev, struct ifcvf_vdpa_mgmt_dev, mdev);
_vdpa_unregister_device(dev);
ifcvf_mgmt_dev->adapter = NULL;
}
static const struct vdpa_mgmtdev_ops ifcvf_vdpa_mgmt_dev_ops = {
.dev_add = ifcvf_vdpa_dev_add,
.dev_del = ifcvf_vdpa_dev_del
};
static int ifcvf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct ifcvf_vdpa_mgmt_dev *ifcvf_mgmt_dev;
struct device *dev = &pdev->dev;
struct ifcvf_hw *vf;
u32 dev_type;
int ret, i;
ret = pcim_enable_device(pdev);
if (ret) {
IFCVF_ERR(pdev, "Failed to enable device\n");
return ret;
}
ret = pcim_iomap_regions(pdev, BIT(0) | BIT(2) | BIT(4),
IFCVF_DRIVER_NAME);
if (ret) {
IFCVF_ERR(pdev, "Failed to request MMIO region\n");
return ret;
}
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret) {
IFCVF_ERR(pdev, "No usable DMA configuration\n");
return ret;
}
ret = devm_add_action_or_reset(dev, ifcvf_free_irq_vectors, pdev);
if (ret) {
IFCVF_ERR(pdev,
"Failed for adding devres for freeing irq vectors\n");
return ret;
}
pci_set_master(pdev);
ifcvf_mgmt_dev = kzalloc_obj(struct ifcvf_vdpa_mgmt_dev);
if (!ifcvf_mgmt_dev) {
IFCVF_ERR(pdev, "Failed to alloc memory for the vDPA management device\n");
return -ENOMEM;
}
vf = &ifcvf_mgmt_dev->vf;
vf->dev_type = get_dev_type(pdev);
vf->base = pcim_iomap_table(pdev);
vf->pdev = pdev;
ret = ifcvf_init_hw(vf, pdev);
if (ret) {
IFCVF_ERR(pdev, "Failed to init IFCVF hw\n");
goto err;
}
for (i = 0; i < vf->nr_vring; i++)
vf->vring[i].irq = -EINVAL;
vf->hw_features = ifcvf_get_hw_features(vf);
vf->config_size = ifcvf_get_config_size(vf);
dev_type = get_dev_type(pdev);
switch (dev_type) {
case VIRTIO_ID_NET:
ifcvf_mgmt_dev->mdev.id_table = id_table_net;
break;
case VIRTIO_ID_BLOCK:
ifcvf_mgmt_dev->mdev.id_table = id_table_blk;
break;
default:
IFCVF_ERR(pdev, "VIRTIO ID %u not supported\n", dev_type);
ret = -EOPNOTSUPP;
goto err;
}
ifcvf_mgmt_dev->mdev.ops = &ifcvf_vdpa_mgmt_dev_ops;
ifcvf_mgmt_dev->mdev.device = dev;
ifcvf_mgmt_dev->mdev.max_supported_vqs = vf->nr_vring;
ifcvf_mgmt_dev->mdev.supported_features = vf->hw_features;
ifcvf_mgmt_dev->mdev.config_attr_mask = (1 << VDPA_ATTR_DEV_FEATURES);
ret = vdpa_mgmtdev_register(&ifcvf_mgmt_dev->mdev);
if (ret) {
IFCVF_ERR(pdev,
"Failed to initialize the management interfaces\n");
goto err;
}
pci_set_drvdata(pdev, ifcvf_mgmt_dev);
return 0;
err:
kfree(ifcvf_mgmt_dev->vf.vring);
kfree(ifcvf_mgmt_dev);
return ret;
}
static void ifcvf_remove(struct pci_dev *pdev)
{
struct ifcvf_vdpa_mgmt_dev *ifcvf_mgmt_dev;
ifcvf_mgmt_dev = pci_get_drvdata(pdev);
vdpa_mgmtdev_unregister(&ifcvf_mgmt_dev->mdev);
kfree(ifcvf_mgmt_dev->vf.vring);
kfree(ifcvf_mgmt_dev);
}
static struct pci_device_id ifcvf_pci_ids[] = {
/* N3000 network device */
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_REDHAT_QUMRANET,
N3000_DEVICE_ID,
PCI_VENDOR_ID_INTEL,
N3000_SUBSYS_DEVICE_ID) },
/* C5000X-PL network device
* F2000X-PL network device
*/
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_REDHAT_QUMRANET,
VIRTIO_TRANS_ID_NET,
PCI_VENDOR_ID_INTEL,
VIRTIO_ID_NET) },
/* C5000X-PL block device */
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_REDHAT_QUMRANET,
VIRTIO_TRANS_ID_BLOCK,
PCI_VENDOR_ID_INTEL,
VIRTIO_ID_BLOCK) },
{ 0 },
};
MODULE_DEVICE_TABLE(pci, ifcvf_pci_ids);
static struct pci_driver ifcvf_driver = {
.name = IFCVF_DRIVER_NAME,
.id_table = ifcvf_pci_ids,
.probe = ifcvf_probe,
.remove = ifcvf_remove,
};
module_pci_driver(ifcvf_driver);
MODULE_DESCRIPTION("Intel IFC VF NIC driver for virtio dataplane offloading");
MODULE_LICENSE("GPL v2");
+4
View File
@@ -0,0 +1,4 @@
subdir-ccflags-y += -I$(srctree)/drivers/vdpa/mlx5/core
obj-$(CONFIG_MLX5_VDPA_NET) += mlx5_vdpa.o
mlx5_vdpa-$(CONFIG_MLX5_VDPA_NET) += net/mlx5_vnet.o core/resources.o core/mr.o net/debug.o
@@ -0,0 +1,189 @@
/* SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB */
/* Copyright (c) 2020 Mellanox Technologies Ltd. */
#ifndef __MLX5_VDPA_H__
#define __MLX5_VDPA_H__
#include <linux/etherdevice.h>
#include <linux/vringh.h>
#include <linux/vdpa.h>
#include <linux/mlx5/driver.h>
#define MLX5V_ETH_HARD_MTU (ETH_HLEN + VLAN_HLEN + ETH_FCS_LEN)
extern int mlx5_vdpa_max_iotlb_entries;
struct mlx5_vdpa_direct_mr {
u64 start;
u64 end;
u32 perm;
u32 mr;
struct sg_table sg_head;
int log_size;
int nsg;
int nent;
struct list_head list;
u64 offset;
};
struct mlx5_vdpa_mr {
u32 mkey;
/* list of direct MRs descendants of this indirect mr */
struct list_head head;
unsigned long num_directs;
unsigned long num_klms;
struct vhost_iotlb *iotlb;
bool user_mr;
refcount_t refcount;
struct list_head mr_list;
};
struct mlx5_vdpa_resources {
u32 pdn;
struct mlx5_uars_page *uar;
void __iomem *kick_addr;
u64 phys_kick_addr;
u16 uid;
u32 null_mkey;
bool valid;
};
struct mlx5_control_vq {
struct vhost_iotlb *iotlb;
/* spinlock to synchronize iommu table */
spinlock_t iommu_lock;
struct vringh vring;
bool ready;
u64 desc_addr;
u64 device_addr;
u64 driver_addr;
struct vdpa_callback event_cb;
struct vringh_kiov riov;
struct vringh_kiov wiov;
unsigned short head;
unsigned int received_desc;
unsigned int completed_desc;
};
struct mlx5_vdpa_wq_ent {
struct work_struct work;
struct mlx5_vdpa_dev *mvdev;
};
enum {
MLX5_VDPA_DATAVQ_GROUP,
MLX5_VDPA_CVQ_GROUP,
MLX5_VDPA_DATAVQ_DESC_GROUP,
MLX5_VDPA_NUMVQ_GROUPS
};
enum {
MLX5_VDPA_NUM_AS = 2
};
struct mlx5_vdpa_mr_resources {
struct mlx5_vdpa_mr *mr[MLX5_VDPA_NUM_AS];
unsigned int group2asid[MLX5_VDPA_NUMVQ_GROUPS];
/* Pre-deletion mr list */
struct list_head mr_list_head;
/* Deferred mr list */
struct list_head mr_gc_list_head;
struct workqueue_struct *wq_gc;
struct delayed_work gc_dwork_ent;
struct mutex lock;
atomic_t shutdown;
};
struct mlx5_vdpa_dev {
struct vdpa_device vdev;
struct mlx5_core_dev *mdev;
struct mlx5_vdpa_resources res;
struct mlx5_vdpa_mr_resources mres;
u64 mlx_features;
u64 actual_features;
u8 status;
u32 max_vqs;
u16 max_idx;
u32 generation;
struct mlx5_control_vq cvq;
struct workqueue_struct *wq;
bool suspended;
struct mlx5_async_ctx async_ctx;
};
struct mlx5_vdpa_async_cmd {
int err;
struct mlx5_async_work cb_work;
struct completion cmd_done;
void *in;
size_t inlen;
void *out;
size_t outlen;
};
int mlx5_vdpa_create_tis(struct mlx5_vdpa_dev *mvdev, void *in, u32 *tisn);
void mlx5_vdpa_destroy_tis(struct mlx5_vdpa_dev *mvdev, u32 tisn);
int mlx5_vdpa_create_rqt(struct mlx5_vdpa_dev *mvdev, void *in, int inlen, u32 *rqtn);
int mlx5_vdpa_modify_rqt(struct mlx5_vdpa_dev *mvdev, void *in, int inlen, u32 rqtn);
void mlx5_vdpa_destroy_rqt(struct mlx5_vdpa_dev *mvdev, u32 rqtn);
int mlx5_vdpa_create_tir(struct mlx5_vdpa_dev *mvdev, void *in, u32 *tirn);
void mlx5_vdpa_destroy_tir(struct mlx5_vdpa_dev *mvdev, u32 tirn);
int mlx5_vdpa_alloc_transport_domain(struct mlx5_vdpa_dev *mvdev, u32 *tdn);
void mlx5_vdpa_dealloc_transport_domain(struct mlx5_vdpa_dev *mvdev, u32 tdn);
int mlx5_vdpa_alloc_resources(struct mlx5_vdpa_dev *mvdev);
void mlx5_vdpa_free_resources(struct mlx5_vdpa_dev *mvdev);
int mlx5_vdpa_create_mkey(struct mlx5_vdpa_dev *mvdev, u32 *mkey, u32 *in,
int inlen);
int mlx5_vdpa_destroy_mkey(struct mlx5_vdpa_dev *mvdev, u32 mkey);
struct mlx5_vdpa_mr *mlx5_vdpa_create_mr(struct mlx5_vdpa_dev *mvdev,
struct vhost_iotlb *iotlb);
int mlx5_vdpa_init_mr_resources(struct mlx5_vdpa_dev *mvdev);
void mlx5_vdpa_destroy_mr_resources(struct mlx5_vdpa_dev *mvdev);
void mlx5_vdpa_clean_mrs(struct mlx5_vdpa_dev *mvdev);
void mlx5_vdpa_get_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr);
void mlx5_vdpa_put_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr);
void mlx5_vdpa_update_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr,
unsigned int asid);
int mlx5_vdpa_update_cvq_iotlb(struct mlx5_vdpa_dev *mvdev,
struct vhost_iotlb *iotlb,
unsigned int asid);
int mlx5_vdpa_create_dma_mr(struct mlx5_vdpa_dev *mvdev);
int mlx5_vdpa_reset_mr(struct mlx5_vdpa_dev *mvdev, unsigned int asid);
int mlx5_vdpa_exec_async_cmds(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_async_cmd *cmds,
int num_cmds);
#define mlx5_vdpa_err(__dev, format, ...) \
dev_err((__dev)->mdev->device, "%s:%d:(pid %d) error: " format, __func__, __LINE__, \
current->pid, ##__VA_ARGS__)
#define mlx5_vdpa_warn(__dev, format, ...) \
dev_warn((__dev)->mdev->device, "%s:%d:(pid %d) warning: " format, __func__, __LINE__, \
current->pid, ##__VA_ARGS__)
#define mlx5_vdpa_info(__dev, format, ...) \
dev_info((__dev)->mdev->device, "%s:%d:(pid %d): " format, __func__, __LINE__, \
current->pid, ##__VA_ARGS__)
#define mlx5_vdpa_dbg(__dev, format, ...) \
dev_debug((__dev)->mdev->device, "%s:%d:(pid %d): " format, __func__, __LINE__, \
current->pid, ##__VA_ARGS__)
#endif /* __MLX5_VDPA_H__ */
+923
View File
@@ -0,0 +1,923 @@
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2020 Mellanox Technologies Ltd. */
#include <linux/vhost_types.h>
#include <linux/vdpa.h>
#include <linux/gcd.h>
#include <linux/string.h>
#include <linux/mlx5/qp.h>
#include "mlx5_vdpa.h"
/* DIV_ROUND_UP where the divider is a power of 2 give by its log base 2 value */
#define MLX5_DIV_ROUND_UP_POW2(_n, _s) \
({ \
u64 __s = _s; \
u64 _res; \
_res = (((_n) + (1 << (__s)) - 1) >> (__s)); \
_res; \
})
static int get_octo_len(u64 len, int page_shift)
{
u64 page_size = 1ULL << page_shift;
int npages;
npages = ALIGN(len, page_size) >> page_shift;
return (npages + 1) / 2;
}
static void mlx5_set_access_mode(void *mkc, int mode)
{
MLX5_SET(mkc, mkc, access_mode_1_0, mode & 0x3);
MLX5_SET(mkc, mkc, access_mode_4_2, mode >> 2);
}
static void populate_mtts(struct mlx5_vdpa_direct_mr *mr, __be64 *mtt)
{
struct scatterlist *sg;
int nsg = mr->nsg;
u64 dma_addr;
u64 dma_len;
int j = 0;
int i;
for_each_sg(mr->sg_head.sgl, sg, mr->nent, i) {
for (dma_addr = sg_dma_address(sg), dma_len = sg_dma_len(sg);
nsg && dma_len;
nsg--, dma_addr += BIT(mr->log_size), dma_len -= BIT(mr->log_size))
mtt[j++] = cpu_to_be64(dma_addr);
}
}
struct mlx5_create_mkey_mem {
u8 out[MLX5_ST_SZ_BYTES(create_mkey_out)];
u8 in[MLX5_ST_SZ_BYTES(create_mkey_in)];
__be64 mtt[];
};
struct mlx5_destroy_mkey_mem {
u8 out[MLX5_ST_SZ_BYTES(destroy_mkey_out)];
u8 in[MLX5_ST_SZ_BYTES(destroy_mkey_in)];
};
static void fill_create_direct_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_direct_mr *mr,
struct mlx5_create_mkey_mem *mem)
{
void *in = &mem->in;
void *mkc;
MLX5_SET(create_mkey_in, in, uid, mvdev->res.uid);
mkc = MLX5_ADDR_OF(create_mkey_in, in, memory_key_mkey_entry);
MLX5_SET(mkc, mkc, lw, !!(mr->perm & VHOST_MAP_WO));
MLX5_SET(mkc, mkc, lr, !!(mr->perm & VHOST_MAP_RO));
mlx5_set_access_mode(mkc, MLX5_MKC_ACCESS_MODE_MTT);
MLX5_SET(mkc, mkc, qpn, 0xffffff);
MLX5_SET(mkc, mkc, pd, mvdev->res.pdn);
MLX5_SET64(mkc, mkc, start_addr, mr->offset);
MLX5_SET64(mkc, mkc, len, mr->end - mr->start);
MLX5_SET(mkc, mkc, log_page_size, mr->log_size);
MLX5_SET(mkc, mkc, translations_octword_size,
get_octo_len(mr->end - mr->start, mr->log_size));
MLX5_SET(create_mkey_in, in, translations_octword_actual_size,
get_octo_len(mr->end - mr->start, mr->log_size));
populate_mtts(mr, MLX5_ADDR_OF(create_mkey_in, in, klm_pas_mtt));
MLX5_SET(create_mkey_in, in, opcode, MLX5_CMD_OP_CREATE_MKEY);
MLX5_SET(create_mkey_in, in, uid, mvdev->res.uid);
}
static void create_direct_mr_end(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_direct_mr *mr,
struct mlx5_create_mkey_mem *mem)
{
u32 mkey_index = MLX5_GET(create_mkey_out, mem->out, mkey_index);
mr->mr = mlx5_idx_to_mkey(mkey_index);
}
static void fill_destroy_direct_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_direct_mr *mr,
struct mlx5_destroy_mkey_mem *mem)
{
void *in = &mem->in;
MLX5_SET(destroy_mkey_in, in, uid, mvdev->res.uid);
MLX5_SET(destroy_mkey_in, in, opcode, MLX5_CMD_OP_DESTROY_MKEY);
MLX5_SET(destroy_mkey_in, in, mkey_index, mlx5_mkey_to_idx(mr->mr));
}
static void destroy_direct_mr(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_direct_mr *mr)
{
if (!mr->mr)
return;
mlx5_vdpa_destroy_mkey(mvdev, mr->mr);
}
static u64 map_start(struct vhost_iotlb_map *map, struct mlx5_vdpa_direct_mr *mr)
{
return max_t(u64, map->start, mr->start);
}
static u64 map_end(struct vhost_iotlb_map *map, struct mlx5_vdpa_direct_mr *mr)
{
return min_t(u64, map->last + 1, mr->end);
}
static u64 maplen(struct vhost_iotlb_map *map, struct mlx5_vdpa_direct_mr *mr)
{
return map_end(map, mr) - map_start(map, mr);
}
#define MLX5_VDPA_INVALID_START_ADDR ((u64)-1)
#define MLX5_VDPA_INVALID_LEN ((u64)-1)
static u64 indir_start_addr(struct mlx5_vdpa_mr *mkey)
{
struct mlx5_vdpa_direct_mr *s;
s = list_first_entry_or_null(&mkey->head, struct mlx5_vdpa_direct_mr, list);
if (!s)
return MLX5_VDPA_INVALID_START_ADDR;
return s->start;
}
static u64 indir_len(struct mlx5_vdpa_mr *mkey)
{
struct mlx5_vdpa_direct_mr *s;
struct mlx5_vdpa_direct_mr *e;
s = list_first_entry_or_null(&mkey->head, struct mlx5_vdpa_direct_mr, list);
if (!s)
return MLX5_VDPA_INVALID_LEN;
e = list_last_entry(&mkey->head, struct mlx5_vdpa_direct_mr, list);
return e->end - s->start;
}
#define LOG_MAX_KLM_SIZE 30
#define MAX_KLM_SIZE BIT(LOG_MAX_KLM_SIZE)
static u32 klm_bcount(u64 size)
{
return (u32)size;
}
static void fill_indir(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mkey, void *in)
{
struct mlx5_vdpa_direct_mr *dmr;
struct mlx5_klm *klmarr;
struct mlx5_klm *klm;
bool first = true;
u64 preve;
int i;
klmarr = MLX5_ADDR_OF(create_mkey_in, in, klm_pas_mtt);
i = 0;
list_for_each_entry(dmr, &mkey->head, list) {
again:
klm = &klmarr[i++];
if (first) {
preve = dmr->start;
first = false;
}
if (preve == dmr->start) {
klm->key = cpu_to_be32(dmr->mr);
klm->bcount = cpu_to_be32(klm_bcount(dmr->end - dmr->start));
preve = dmr->end;
} else {
u64 bcount = min_t(u64, dmr->start - preve, MAX_KLM_SIZE);
klm->key = cpu_to_be32(mvdev->res.null_mkey);
klm->bcount = cpu_to_be32(klm_bcount(bcount));
preve += bcount;
goto again;
}
}
}
static int klm_byte_size(int nklms)
{
return 16 * ALIGN(nklms, 4);
}
#define MLX5_VDPA_MTT_ALIGN 16
static int create_direct_keys(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mr)
{
struct mlx5_vdpa_async_cmd *cmds;
struct mlx5_vdpa_direct_mr *dmr;
int err = 0;
int i = 0;
cmds = kvzalloc_objs(*cmds, mr->num_directs);
if (!cmds)
return -ENOMEM;
list_for_each_entry(dmr, &mr->head, list) {
struct mlx5_create_mkey_mem *cmd_mem;
int mttcount;
mttcount = ALIGN(dmr->nsg, MLX5_VDPA_MTT_ALIGN / sizeof(cmd_mem->mtt[0]));
cmd_mem = kvzalloc_flex(*cmd_mem, mtt, mttcount);
if (!cmd_mem) {
err = -ENOMEM;
goto done;
}
cmds[i].out = cmd_mem->out;
cmds[i].outlen = sizeof(cmd_mem->out);
cmds[i].in = cmd_mem->in;
cmds[i].inlen = struct_size(cmd_mem, mtt, mttcount) -
offsetof(struct mlx5_create_mkey_mem, in);
fill_create_direct_mr(mvdev, dmr, cmd_mem);
i++;
}
err = mlx5_vdpa_exec_async_cmds(mvdev, cmds, mr->num_directs);
if (err) {
mlx5_vdpa_err(mvdev, "error issuing MTT mkey creation for direct mrs: %d\n", err);
goto done;
}
i = 0;
list_for_each_entry(dmr, &mr->head, list) {
struct mlx5_vdpa_async_cmd *cmd = &cmds[i++];
struct mlx5_create_mkey_mem *cmd_mem;
cmd_mem = container_of(cmd->out, struct mlx5_create_mkey_mem, out);
if (!cmd->err) {
create_direct_mr_end(mvdev, dmr, cmd_mem);
} else {
err = err ? err : cmd->err;
mlx5_vdpa_err(mvdev, "error creating MTT mkey [0x%llx, 0x%llx]: %d\n",
dmr->start, dmr->end, cmd->err);
}
}
done:
for (i = i-1; i >= 0; i--) {
struct mlx5_create_mkey_mem *cmd_mem;
cmd_mem = container_of(cmds[i].out, struct mlx5_create_mkey_mem, out);
kvfree(cmd_mem);
}
kvfree(cmds);
return err;
}
DEFINE_FREE(free_cmds, struct mlx5_vdpa_async_cmd *, kvfree(_T))
DEFINE_FREE(free_cmd_mem, struct mlx5_destroy_mkey_mem *, kvfree(_T))
static int destroy_direct_keys(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mr)
{
struct mlx5_destroy_mkey_mem *cmd_mem __free(free_cmd_mem) = NULL;
struct mlx5_vdpa_async_cmd *cmds __free(free_cmds) = NULL;
struct mlx5_vdpa_direct_mr *dmr;
int err = 0;
int i = 0;
cmds = kvzalloc_objs(*cmds, mr->num_directs);
cmd_mem = kvzalloc_objs(*cmd_mem, mr->num_directs);
if (!cmds || !cmd_mem)
return -ENOMEM;
list_for_each_entry(dmr, &mr->head, list) {
cmds[i].out = cmd_mem[i].out;
cmds[i].outlen = sizeof(cmd_mem[i].out);
cmds[i].in = cmd_mem[i].in;
cmds[i].inlen = sizeof(cmd_mem[i].in);
fill_destroy_direct_mr(mvdev, dmr, &cmd_mem[i]);
i++;
}
err = mlx5_vdpa_exec_async_cmds(mvdev, cmds, mr->num_directs);
if (err) {
mlx5_vdpa_err(mvdev, "error issuing MTT mkey deletion for direct mrs: %d\n", err);
return err;
}
i = 0;
list_for_each_entry(dmr, &mr->head, list) {
struct mlx5_vdpa_async_cmd *cmd = &cmds[i++];
dmr->mr = 0;
if (cmd->err) {
err = err ? err : cmd->err;
mlx5_vdpa_err(mvdev, "error deleting MTT mkey [0x%llx, 0x%llx]: %d\n",
dmr->start, dmr->end, cmd->err);
}
}
return err;
}
static int create_indirect_key(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mr)
{
int inlen;
void *mkc;
void *in;
int err;
u64 start;
u64 len;
start = indir_start_addr(mr);
len = indir_len(mr);
if (start == MLX5_VDPA_INVALID_START_ADDR || len == MLX5_VDPA_INVALID_LEN)
return -EINVAL;
inlen = MLX5_ST_SZ_BYTES(create_mkey_in) + klm_byte_size(mr->num_klms);
in = kzalloc(inlen, GFP_KERNEL);
if (!in)
return -ENOMEM;
MLX5_SET(create_mkey_in, in, uid, mvdev->res.uid);
mkc = MLX5_ADDR_OF(create_mkey_in, in, memory_key_mkey_entry);
MLX5_SET(mkc, mkc, lw, 1);
MLX5_SET(mkc, mkc, lr, 1);
mlx5_set_access_mode(mkc, MLX5_MKC_ACCESS_MODE_KLMS);
MLX5_SET(mkc, mkc, qpn, 0xffffff);
MLX5_SET(mkc, mkc, pd, mvdev->res.pdn);
MLX5_SET64(mkc, mkc, start_addr, start);
MLX5_SET64(mkc, mkc, len, len);
MLX5_SET(mkc, mkc, translations_octword_size, klm_byte_size(mr->num_klms) / 16);
MLX5_SET(create_mkey_in, in, translations_octword_actual_size, mr->num_klms);
fill_indir(mvdev, mr, in);
err = mlx5_vdpa_create_mkey(mvdev, &mr->mkey, in, inlen);
kfree(in);
return err;
}
static void destroy_indirect_key(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mkey)
{
mlx5_vdpa_destroy_mkey(mvdev, mkey->mkey);
}
static int map_direct_mr(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_direct_mr *mr,
struct vhost_iotlb *iotlb)
{
struct vhost_iotlb_map *map;
unsigned long lgcd = 0;
int log_entity_size;
unsigned long size;
int err;
struct page *pg;
unsigned int nsg;
int sglen;
u64 pa, offset;
u64 paend;
struct scatterlist *sg;
struct device *dma = mvdev->vdev.vmap.dma_dev;
for (map = vhost_iotlb_itree_first(iotlb, mr->start, mr->end - 1);
map; map = vhost_iotlb_itree_next(map, mr->start, mr->end - 1)) {
size = maplen(map, mr);
lgcd = gcd(lgcd, size);
}
log_entity_size = ilog2(lgcd);
sglen = 1 << log_entity_size;
nsg = MLX5_DIV_ROUND_UP_POW2(mr->end - mr->start, log_entity_size);
err = sg_alloc_table(&mr->sg_head, nsg, GFP_KERNEL);
if (err)
return err;
sg = mr->sg_head.sgl;
for (map = vhost_iotlb_itree_first(iotlb, mr->start, mr->end - 1);
map; map = vhost_iotlb_itree_next(map, mr->start, mr->end - 1)) {
offset = mr->start > map->start ? mr->start - map->start : 0;
pa = map->addr + offset;
paend = map->addr + offset + maplen(map, mr);
for (; pa < paend; pa += sglen) {
pg = pfn_to_page(__phys_to_pfn(pa));
if (!sg) {
mlx5_vdpa_warn(mvdev, "sg null. start 0x%llx, end 0x%llx\n",
map->start, map->last + 1);
err = -ENOMEM;
goto err_map;
}
sg_set_page(sg, pg, sglen, 0);
sg = sg_next(sg);
if (!sg)
goto done;
}
}
done:
mr->log_size = log_entity_size;
mr->nsg = nsg;
mr->nent = dma_map_sg_attrs(dma, mr->sg_head.sgl, mr->nsg, DMA_BIDIRECTIONAL, 0);
if (!mr->nent) {
err = -ENOMEM;
goto err_map;
}
return 0;
err_map:
sg_free_table(&mr->sg_head);
return err;
}
static void unmap_direct_mr(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_direct_mr *mr)
{
struct device *dma = mvdev->vdev.vmap.dma_dev;
destroy_direct_mr(mvdev, mr);
dma_unmap_sg_attrs(dma, mr->sg_head.sgl, mr->nsg, DMA_BIDIRECTIONAL, 0);
sg_free_table(&mr->sg_head);
}
static int add_direct_chain(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr,
u64 start,
u64 size,
u8 perm,
struct vhost_iotlb *iotlb)
{
struct mlx5_vdpa_direct_mr *dmr;
struct mlx5_vdpa_direct_mr *n;
LIST_HEAD(tmp);
u64 st;
u64 sz;
int err;
st = start;
while (size) {
sz = (u32)min_t(u64, MAX_KLM_SIZE, size);
dmr = kzalloc_obj(*dmr);
if (!dmr) {
err = -ENOMEM;
goto err_alloc;
}
dmr->start = st;
dmr->end = st + sz;
dmr->perm = perm;
err = map_direct_mr(mvdev, dmr, iotlb);
if (err) {
kfree(dmr);
goto err_alloc;
}
list_add_tail(&dmr->list, &tmp);
size -= sz;
mr->num_directs++;
mr->num_klms++;
st += sz;
}
list_splice_tail(&tmp, &mr->head);
return 0;
err_alloc:
list_for_each_entry_safe(dmr, n, &mr->head, list) {
list_del_init(&dmr->list);
unmap_direct_mr(mvdev, dmr);
kfree(dmr);
}
return err;
}
/* The iotlb pointer contains a list of maps. Go over the maps, possibly
* merging mergeable maps, and create direct memory keys that provide the
* device access to memory. The direct mkeys are then referred to by the
* indirect memory key that provides access to the enitre address space given
* by iotlb.
*/
static int create_user_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr,
struct vhost_iotlb *iotlb)
{
struct mlx5_vdpa_direct_mr *dmr;
struct mlx5_vdpa_direct_mr *n;
struct vhost_iotlb_map *map;
u32 pperm = U16_MAX;
u64 last = U64_MAX;
u64 ps = U64_MAX;
u64 pe = U64_MAX;
u64 start = 0;
int err = 0;
int nnuls;
INIT_LIST_HEAD(&mr->head);
for (map = vhost_iotlb_itree_first(iotlb, start, last); map;
map = vhost_iotlb_itree_next(map, start, last)) {
start = map->start;
if (pe == map->start && pperm == map->perm) {
pe = map->last + 1;
} else {
if (ps != U64_MAX) {
if (pe < map->start) {
/* We have a hole in the map. Check how
* many null keys are required to fill it.
*/
nnuls = MLX5_DIV_ROUND_UP_POW2(map->start - pe,
LOG_MAX_KLM_SIZE);
mr->num_klms += nnuls;
}
err = add_direct_chain(mvdev, mr, ps, pe - ps, pperm, iotlb);
if (err)
goto err_chain;
}
ps = map->start;
pe = map->last + 1;
pperm = map->perm;
}
}
err = add_direct_chain(mvdev, mr, ps, pe - ps, pperm, iotlb);
if (err)
goto err_chain;
err = create_direct_keys(mvdev, mr);
if (err)
goto err_chain;
/* Create the memory key that defines the guests's address space. This
* memory key refers to the direct keys that contain the MTT
* translations
*/
err = create_indirect_key(mvdev, mr);
if (err)
goto err_chain;
mr->user_mr = true;
return 0;
err_chain:
list_for_each_entry_safe_reverse(dmr, n, &mr->head, list) {
list_del_init(&dmr->list);
unmap_direct_mr(mvdev, dmr);
kfree(dmr);
}
return err;
}
static int create_dma_mr(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mr)
{
int inlen = MLX5_ST_SZ_BYTES(create_mkey_in);
void *mkc;
u32 *in;
int err;
in = kzalloc(inlen, GFP_KERNEL);
if (!in)
return -ENOMEM;
mkc = MLX5_ADDR_OF(create_mkey_in, in, memory_key_mkey_entry);
MLX5_SET(mkc, mkc, access_mode_1_0, MLX5_MKC_ACCESS_MODE_PA);
MLX5_SET(mkc, mkc, length64, 1);
MLX5_SET(mkc, mkc, lw, 1);
MLX5_SET(mkc, mkc, lr, 1);
MLX5_SET(mkc, mkc, pd, mvdev->res.pdn);
MLX5_SET(mkc, mkc, qpn, 0xffffff);
err = mlx5_vdpa_create_mkey(mvdev, &mr->mkey, in, inlen);
if (!err)
mr->user_mr = false;
kfree(in);
return err;
}
static void destroy_dma_mr(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mr)
{
mlx5_vdpa_destroy_mkey(mvdev, mr->mkey);
}
static int dup_iotlb(struct vhost_iotlb *dst, struct vhost_iotlb *src)
{
struct vhost_iotlb_map *map;
u64 start = 0, last = ULLONG_MAX;
int err;
if (dst == src)
return -EINVAL;
if (!src) {
err = vhost_iotlb_add_range(dst, start, last, start, VHOST_ACCESS_RW);
return err;
}
for (map = vhost_iotlb_itree_first(src, start, last); map;
map = vhost_iotlb_itree_next(map, start, last)) {
err = vhost_iotlb_add_range(dst, map->start, map->last,
map->addr, map->perm);
if (err)
return err;
}
return 0;
}
static void prune_iotlb(struct vhost_iotlb *iotlb)
{
vhost_iotlb_del_range(iotlb, 0, ULLONG_MAX);
}
static void destroy_user_mr(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mr)
{
struct mlx5_vdpa_direct_mr *dmr;
struct mlx5_vdpa_direct_mr *n;
destroy_indirect_key(mvdev, mr);
destroy_direct_keys(mvdev, mr);
list_for_each_entry_safe_reverse(dmr, n, &mr->head, list) {
list_del_init(&dmr->list);
unmap_direct_mr(mvdev, dmr);
kfree(dmr);
}
}
static void _mlx5_vdpa_destroy_mr(struct mlx5_vdpa_dev *mvdev, struct mlx5_vdpa_mr *mr)
{
if (WARN_ON(!mr))
return;
if (mr->user_mr)
destroy_user_mr(mvdev, mr);
else
destroy_dma_mr(mvdev, mr);
vhost_iotlb_free(mr->iotlb);
list_del(&mr->mr_list);
kfree(mr);
}
/* There can be multiple .set_map() operations in quick succession.
* This large delay is a simple way to prevent the MR cleanup from blocking
* .set_map() MR creation in this scenario.
*/
#define MLX5_VDPA_MR_GC_TRIGGER_MS 2000
static void mlx5_vdpa_mr_gc_handler(struct work_struct *work)
{
struct mlx5_vdpa_mr_resources *mres;
struct mlx5_vdpa_mr *mr, *tmp;
struct mlx5_vdpa_dev *mvdev;
mres = container_of(work, struct mlx5_vdpa_mr_resources, gc_dwork_ent.work);
if (atomic_read(&mres->shutdown)) {
mutex_lock(&mres->lock);
} else if (!mutex_trylock(&mres->lock)) {
queue_delayed_work(mres->wq_gc, &mres->gc_dwork_ent,
msecs_to_jiffies(MLX5_VDPA_MR_GC_TRIGGER_MS));
return;
}
mvdev = container_of(mres, struct mlx5_vdpa_dev, mres);
list_for_each_entry_safe(mr, tmp, &mres->mr_gc_list_head, mr_list) {
_mlx5_vdpa_destroy_mr(mvdev, mr);
}
mutex_unlock(&mres->lock);
}
static void _mlx5_vdpa_put_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr)
{
struct mlx5_vdpa_mr_resources *mres = &mvdev->mres;
if (!mr)
return;
if (refcount_dec_and_test(&mr->refcount)) {
list_move_tail(&mr->mr_list, &mres->mr_gc_list_head);
queue_delayed_work(mres->wq_gc, &mres->gc_dwork_ent,
msecs_to_jiffies(MLX5_VDPA_MR_GC_TRIGGER_MS));
}
}
void mlx5_vdpa_put_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr)
{
mutex_lock(&mvdev->mres.lock);
_mlx5_vdpa_put_mr(mvdev, mr);
mutex_unlock(&mvdev->mres.lock);
}
static void _mlx5_vdpa_get_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr)
{
if (!mr)
return;
refcount_inc(&mr->refcount);
}
void mlx5_vdpa_get_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr)
{
mutex_lock(&mvdev->mres.lock);
_mlx5_vdpa_get_mr(mvdev, mr);
mutex_unlock(&mvdev->mres.lock);
}
void mlx5_vdpa_update_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *new_mr,
unsigned int asid)
{
struct mlx5_vdpa_mr *old_mr = mvdev->mres.mr[asid];
mutex_lock(&mvdev->mres.lock);
_mlx5_vdpa_put_mr(mvdev, old_mr);
mvdev->mres.mr[asid] = new_mr;
mutex_unlock(&mvdev->mres.lock);
}
static void mlx5_vdpa_show_mr_leaks(struct mlx5_vdpa_dev *mvdev)
{
struct mlx5_vdpa_mr *mr;
mutex_lock(&mvdev->mres.lock);
list_for_each_entry(mr, &mvdev->mres.mr_list_head, mr_list) {
mlx5_vdpa_warn(mvdev, "mkey still alive after resource delete: "
"mr: %p, mkey: 0x%x, refcount: %u\n",
mr, mr->mkey, refcount_read(&mr->refcount));
}
mutex_unlock(&mvdev->mres.lock);
}
void mlx5_vdpa_clean_mrs(struct mlx5_vdpa_dev *mvdev)
{
if (!mvdev->res.valid)
return;
for (int i = 0; i < MLX5_VDPA_NUM_AS; i++)
mlx5_vdpa_update_mr(mvdev, NULL, i);
prune_iotlb(mvdev->cvq.iotlb);
mlx5_vdpa_show_mr_leaks(mvdev);
}
static int _mlx5_vdpa_create_mr(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_mr *mr,
struct vhost_iotlb *iotlb)
{
int err;
if (mlx5_vdpa_max_iotlb_entries < 2)
return -EINVAL;
if (iotlb)
err = create_user_mr(mvdev, mr, iotlb);
else
err = create_dma_mr(mvdev, mr);
if (err)
return err;
mr->iotlb = vhost_iotlb_alloc(mlx5_vdpa_max_iotlb_entries, 0);
if (!mr->iotlb) {
err = -ENOMEM;
goto err_mr;
}
err = dup_iotlb(mr->iotlb, iotlb);
if (err)
goto err_iotlb;
list_add_tail(&mr->mr_list, &mvdev->mres.mr_list_head);
return 0;
err_iotlb:
vhost_iotlb_free(mr->iotlb);
err_mr:
if (iotlb)
destroy_user_mr(mvdev, mr);
else
destroy_dma_mr(mvdev, mr);
return err;
}
struct mlx5_vdpa_mr *mlx5_vdpa_create_mr(struct mlx5_vdpa_dev *mvdev,
struct vhost_iotlb *iotlb)
{
struct mlx5_vdpa_mr *mr;
int err;
mr = kzalloc_obj(*mr);
if (!mr)
return ERR_PTR(-ENOMEM);
mutex_lock(&mvdev->mres.lock);
err = _mlx5_vdpa_create_mr(mvdev, mr, iotlb);
mutex_unlock(&mvdev->mres.lock);
if (err)
goto out_err;
refcount_set(&mr->refcount, 1);
return mr;
out_err:
kfree(mr);
return ERR_PTR(err);
}
int mlx5_vdpa_update_cvq_iotlb(struct mlx5_vdpa_dev *mvdev,
struct vhost_iotlb *iotlb,
unsigned int asid)
{
int err;
if (mvdev->mres.group2asid[MLX5_VDPA_CVQ_GROUP] != asid)
return 0;
spin_lock(&mvdev->cvq.iommu_lock);
prune_iotlb(mvdev->cvq.iotlb);
err = dup_iotlb(mvdev->cvq.iotlb, iotlb);
spin_unlock(&mvdev->cvq.iommu_lock);
return err;
}
int mlx5_vdpa_create_dma_mr(struct mlx5_vdpa_dev *mvdev)
{
struct mlx5_vdpa_mr *mr;
mr = mlx5_vdpa_create_mr(mvdev, NULL);
if (IS_ERR(mr))
return PTR_ERR(mr);
mlx5_vdpa_update_mr(mvdev, mr, 0);
return mlx5_vdpa_update_cvq_iotlb(mvdev, NULL, 0);
}
int mlx5_vdpa_reset_mr(struct mlx5_vdpa_dev *mvdev, unsigned int asid)
{
if (asid >= MLX5_VDPA_NUM_AS)
return -EINVAL;
mlx5_vdpa_update_mr(mvdev, NULL, asid);
if (asid == 0 && MLX5_CAP_GEN(mvdev->mdev, umem_uid_0)) {
if (mlx5_vdpa_create_dma_mr(mvdev))
mlx5_vdpa_warn(mvdev, "create DMA MR failed\n");
} else {
mlx5_vdpa_update_cvq_iotlb(mvdev, NULL, asid);
}
return 0;
}
int mlx5_vdpa_init_mr_resources(struct mlx5_vdpa_dev *mvdev)
{
struct mlx5_vdpa_mr_resources *mres = &mvdev->mres;
mres->wq_gc = create_singlethread_workqueue("mlx5_vdpa_mr_gc");
if (!mres->wq_gc)
return -ENOMEM;
INIT_DELAYED_WORK(&mres->gc_dwork_ent, mlx5_vdpa_mr_gc_handler);
mutex_init(&mres->lock);
INIT_LIST_HEAD(&mres->mr_list_head);
INIT_LIST_HEAD(&mres->mr_gc_list_head);
return 0;
}
void mlx5_vdpa_destroy_mr_resources(struct mlx5_vdpa_dev *mvdev)
{
struct mlx5_vdpa_mr_resources *mres = &mvdev->mres;
if (!mres->wq_gc)
return;
atomic_set(&mres->shutdown, 1);
flush_delayed_work(&mres->gc_dwork_ent);
destroy_workqueue(mres->wq_gc);
mres->wq_gc = NULL;
mutex_destroy(&mres->lock);
}
@@ -0,0 +1,402 @@
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2020 Mellanox Technologies Ltd. */
#include <linux/iova.h>
#include <linux/mlx5/driver.h>
#include <linux/moduleparam.h>
#include "mlx5_vdpa.h"
int mlx5_vdpa_max_iotlb_entries = 2048;
module_param_named(max_iotlb_entries, mlx5_vdpa_max_iotlb_entries, int, 0444);
MODULE_PARM_DESC(max_iotlb_entries,
"Maximum number of iotlb entries. (default: 2048)");
static int alloc_pd(struct mlx5_vdpa_dev *dev, u32 *pdn, u16 uid)
{
struct mlx5_core_dev *mdev = dev->mdev;
u32 out[MLX5_ST_SZ_DW(alloc_pd_out)] = {};
u32 in[MLX5_ST_SZ_DW(alloc_pd_in)] = {};
int err;
MLX5_SET(alloc_pd_in, in, opcode, MLX5_CMD_OP_ALLOC_PD);
MLX5_SET(alloc_pd_in, in, uid, uid);
err = mlx5_cmd_exec_inout(mdev, alloc_pd, in, out);
if (!err)
*pdn = MLX5_GET(alloc_pd_out, out, pd);
return err;
}
static int dealloc_pd(struct mlx5_vdpa_dev *dev, u32 pdn, u16 uid)
{
u32 in[MLX5_ST_SZ_DW(dealloc_pd_in)] = {};
struct mlx5_core_dev *mdev = dev->mdev;
MLX5_SET(dealloc_pd_in, in, opcode, MLX5_CMD_OP_DEALLOC_PD);
MLX5_SET(dealloc_pd_in, in, pd, pdn);
MLX5_SET(dealloc_pd_in, in, uid, uid);
return mlx5_cmd_exec_in(mdev, dealloc_pd, in);
}
static int get_null_mkey(struct mlx5_vdpa_dev *dev, u32 *null_mkey)
{
u32 out[MLX5_ST_SZ_DW(query_special_contexts_out)] = {};
u32 in[MLX5_ST_SZ_DW(query_special_contexts_in)] = {};
struct mlx5_core_dev *mdev = dev->mdev;
int err;
MLX5_SET(query_special_contexts_in, in, opcode, MLX5_CMD_OP_QUERY_SPECIAL_CONTEXTS);
err = mlx5_cmd_exec_inout(mdev, query_special_contexts, in, out);
if (!err)
*null_mkey = MLX5_GET(query_special_contexts_out, out, null_mkey);
return err;
}
static int create_uctx(struct mlx5_vdpa_dev *mvdev, u16 *uid)
{
u32 out[MLX5_ST_SZ_DW(create_uctx_out)] = {};
int inlen;
void *in;
int err;
if (MLX5_CAP_GEN(mvdev->mdev, umem_uid_0))
return 0;
/* 0 means not supported */
if (!MLX5_CAP_GEN(mvdev->mdev, log_max_uctx))
return -EOPNOTSUPP;
inlen = MLX5_ST_SZ_BYTES(create_uctx_in);
in = kzalloc(inlen, GFP_KERNEL);
if (!in)
return -ENOMEM;
MLX5_SET(create_uctx_in, in, opcode, MLX5_CMD_OP_CREATE_UCTX);
MLX5_SET(create_uctx_in, in, uctx.cap, MLX5_UCTX_CAP_RAW_TX);
err = mlx5_cmd_exec(mvdev->mdev, in, inlen, out, sizeof(out));
kfree(in);
if (!err)
*uid = MLX5_GET(create_uctx_out, out, uid);
return err;
}
static void destroy_uctx(struct mlx5_vdpa_dev *mvdev, u32 uid)
{
u32 out[MLX5_ST_SZ_DW(destroy_uctx_out)] = {};
u32 in[MLX5_ST_SZ_DW(destroy_uctx_in)] = {};
if (!uid)
return;
MLX5_SET(destroy_uctx_in, in, opcode, MLX5_CMD_OP_DESTROY_UCTX);
MLX5_SET(destroy_uctx_in, in, uid, uid);
mlx5_cmd_exec(mvdev->mdev, in, sizeof(in), out, sizeof(out));
}
int mlx5_vdpa_create_tis(struct mlx5_vdpa_dev *mvdev, void *in, u32 *tisn)
{
u32 out[MLX5_ST_SZ_DW(create_tis_out)] = {};
int err;
MLX5_SET(create_tis_in, in, opcode, MLX5_CMD_OP_CREATE_TIS);
MLX5_SET(create_tis_in, in, uid, mvdev->res.uid);
err = mlx5_cmd_exec_inout(mvdev->mdev, create_tis, in, out);
if (!err)
*tisn = MLX5_GET(create_tis_out, out, tisn);
return err;
}
void mlx5_vdpa_destroy_tis(struct mlx5_vdpa_dev *mvdev, u32 tisn)
{
u32 in[MLX5_ST_SZ_DW(destroy_tis_in)] = {};
MLX5_SET(destroy_tis_in, in, opcode, MLX5_CMD_OP_DESTROY_TIS);
MLX5_SET(destroy_tis_in, in, uid, mvdev->res.uid);
MLX5_SET(destroy_tis_in, in, tisn, tisn);
mlx5_cmd_exec_in(mvdev->mdev, destroy_tis, in);
}
int mlx5_vdpa_create_rqt(struct mlx5_vdpa_dev *mvdev, void *in, int inlen, u32 *rqtn)
{
u32 out[MLX5_ST_SZ_DW(create_rqt_out)] = {};
int err;
MLX5_SET(create_rqt_in, in, opcode, MLX5_CMD_OP_CREATE_RQT);
err = mlx5_cmd_exec(mvdev->mdev, in, inlen, out, sizeof(out));
if (!err)
*rqtn = MLX5_GET(create_rqt_out, out, rqtn);
return err;
}
int mlx5_vdpa_modify_rqt(struct mlx5_vdpa_dev *mvdev, void *in, int inlen, u32 rqtn)
{
u32 out[MLX5_ST_SZ_DW(create_rqt_out)] = {};
MLX5_SET(modify_rqt_in, in, uid, mvdev->res.uid);
MLX5_SET(modify_rqt_in, in, rqtn, rqtn);
MLX5_SET(modify_rqt_in, in, opcode, MLX5_CMD_OP_MODIFY_RQT);
return mlx5_cmd_exec(mvdev->mdev, in, inlen, out, sizeof(out));
}
void mlx5_vdpa_destroy_rqt(struct mlx5_vdpa_dev *mvdev, u32 rqtn)
{
u32 in[MLX5_ST_SZ_DW(destroy_rqt_in)] = {};
MLX5_SET(destroy_rqt_in, in, opcode, MLX5_CMD_OP_DESTROY_RQT);
MLX5_SET(destroy_rqt_in, in, uid, mvdev->res.uid);
MLX5_SET(destroy_rqt_in, in, rqtn, rqtn);
mlx5_cmd_exec_in(mvdev->mdev, destroy_rqt, in);
}
int mlx5_vdpa_create_tir(struct mlx5_vdpa_dev *mvdev, void *in, u32 *tirn)
{
u32 out[MLX5_ST_SZ_DW(create_tir_out)] = {};
int err;
MLX5_SET(create_tir_in, in, opcode, MLX5_CMD_OP_CREATE_TIR);
err = mlx5_cmd_exec_inout(mvdev->mdev, create_tir, in, out);
if (!err)
*tirn = MLX5_GET(create_tir_out, out, tirn);
return err;
}
void mlx5_vdpa_destroy_tir(struct mlx5_vdpa_dev *mvdev, u32 tirn)
{
u32 in[MLX5_ST_SZ_DW(destroy_tir_in)] = {};
MLX5_SET(destroy_tir_in, in, opcode, MLX5_CMD_OP_DESTROY_TIR);
MLX5_SET(destroy_tir_in, in, uid, mvdev->res.uid);
MLX5_SET(destroy_tir_in, in, tirn, tirn);
mlx5_cmd_exec_in(mvdev->mdev, destroy_tir, in);
}
int mlx5_vdpa_alloc_transport_domain(struct mlx5_vdpa_dev *mvdev, u32 *tdn)
{
u32 out[MLX5_ST_SZ_DW(alloc_transport_domain_out)] = {};
u32 in[MLX5_ST_SZ_DW(alloc_transport_domain_in)] = {};
int err;
MLX5_SET(alloc_transport_domain_in, in, opcode, MLX5_CMD_OP_ALLOC_TRANSPORT_DOMAIN);
MLX5_SET(alloc_transport_domain_in, in, uid, mvdev->res.uid);
err = mlx5_cmd_exec_inout(mvdev->mdev, alloc_transport_domain, in, out);
if (!err)
*tdn = MLX5_GET(alloc_transport_domain_out, out, transport_domain);
return err;
}
void mlx5_vdpa_dealloc_transport_domain(struct mlx5_vdpa_dev *mvdev, u32 tdn)
{
u32 in[MLX5_ST_SZ_DW(dealloc_transport_domain_in)] = {};
MLX5_SET(dealloc_transport_domain_in, in, opcode, MLX5_CMD_OP_DEALLOC_TRANSPORT_DOMAIN);
MLX5_SET(dealloc_transport_domain_in, in, uid, mvdev->res.uid);
MLX5_SET(dealloc_transport_domain_in, in, transport_domain, tdn);
mlx5_cmd_exec_in(mvdev->mdev, dealloc_transport_domain, in);
}
int mlx5_vdpa_create_mkey(struct mlx5_vdpa_dev *mvdev, u32 *mkey, u32 *in,
int inlen)
{
u32 lout[MLX5_ST_SZ_DW(create_mkey_out)] = {};
u32 mkey_index;
int err;
MLX5_SET(create_mkey_in, in, opcode, MLX5_CMD_OP_CREATE_MKEY);
MLX5_SET(create_mkey_in, in, uid, mvdev->res.uid);
err = mlx5_cmd_exec(mvdev->mdev, in, inlen, lout, sizeof(lout));
if (err)
return err;
mkey_index = MLX5_GET(create_mkey_out, lout, mkey_index);
*mkey = mlx5_idx_to_mkey(mkey_index);
return 0;
}
int mlx5_vdpa_destroy_mkey(struct mlx5_vdpa_dev *mvdev, u32 mkey)
{
u32 in[MLX5_ST_SZ_DW(destroy_mkey_in)] = {};
MLX5_SET(destroy_mkey_in, in, uid, mvdev->res.uid);
MLX5_SET(destroy_mkey_in, in, opcode, MLX5_CMD_OP_DESTROY_MKEY);
MLX5_SET(destroy_mkey_in, in, mkey_index, mlx5_mkey_to_idx(mkey));
return mlx5_cmd_exec_in(mvdev->mdev, destroy_mkey, in);
}
static int init_ctrl_vq(struct mlx5_vdpa_dev *mvdev)
{
if (mlx5_vdpa_max_iotlb_entries < 2)
return -EINVAL;
mvdev->cvq.iotlb = vhost_iotlb_alloc(mlx5_vdpa_max_iotlb_entries, 0);
if (!mvdev->cvq.iotlb)
return -ENOMEM;
spin_lock_init(&mvdev->cvq.iommu_lock);
vringh_set_iotlb(&mvdev->cvq.vring, mvdev->cvq.iotlb, &mvdev->cvq.iommu_lock);
return 0;
}
static void cleanup_ctrl_vq(struct mlx5_vdpa_dev *mvdev)
{
vhost_iotlb_free(mvdev->cvq.iotlb);
}
int mlx5_vdpa_alloc_resources(struct mlx5_vdpa_dev *mvdev)
{
u64 offset = MLX5_CAP64_DEV_VDPA_EMULATION(mvdev->mdev, doorbell_bar_offset);
struct mlx5_vdpa_resources *res = &mvdev->res;
struct mlx5_core_dev *mdev = mvdev->mdev;
u64 kick_addr;
int err;
if (res->valid) {
mlx5_vdpa_warn(mvdev, "resources already allocated\n");
return -EINVAL;
}
res->uar = mlx5_get_uars_page(mdev);
if (IS_ERR(res->uar)) {
err = PTR_ERR(res->uar);
goto err_uars;
}
err = create_uctx(mvdev, &res->uid);
if (err)
goto err_uctx;
err = alloc_pd(mvdev, &res->pdn, res->uid);
if (err)
goto err_pd;
err = get_null_mkey(mvdev, &res->null_mkey);
if (err)
goto err_key;
kick_addr = mdev->bar_addr + offset;
res->phys_kick_addr = kick_addr;
res->kick_addr = ioremap(kick_addr, PAGE_SIZE);
if (!res->kick_addr) {
err = -ENOMEM;
goto err_key;
}
err = init_ctrl_vq(mvdev);
if (err)
goto err_ctrl;
res->valid = true;
return 0;
err_ctrl:
iounmap(res->kick_addr);
err_key:
dealloc_pd(mvdev, res->pdn, res->uid);
err_pd:
destroy_uctx(mvdev, res->uid);
err_uctx:
mlx5_put_uars_page(mdev, res->uar);
err_uars:
return err;
}
void mlx5_vdpa_free_resources(struct mlx5_vdpa_dev *mvdev)
{
struct mlx5_vdpa_resources *res = &mvdev->res;
if (!res->valid)
return;
cleanup_ctrl_vq(mvdev);
iounmap(res->kick_addr);
res->kick_addr = NULL;
dealloc_pd(mvdev, res->pdn, res->uid);
destroy_uctx(mvdev, res->uid);
mlx5_put_uars_page(mvdev->mdev, res->uar);
res->valid = false;
}
static void virtqueue_cmd_callback(int status, struct mlx5_async_work *context)
{
struct mlx5_vdpa_async_cmd *cmd =
container_of(context, struct mlx5_vdpa_async_cmd, cb_work);
cmd->err = mlx5_cmd_check(context->ctx->dev, status, cmd->in, cmd->out);
complete(&cmd->cmd_done);
}
static int issue_async_cmd(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_async_cmd *cmds,
int issued,
int *completed)
{
struct mlx5_vdpa_async_cmd *cmd = &cmds[issued];
int err;
retry:
err = mlx5_cmd_exec_cb(&mvdev->async_ctx,
cmd->in, cmd->inlen,
cmd->out, cmd->outlen,
virtqueue_cmd_callback,
&cmd->cb_work);
if (err == -EBUSY) {
if (*completed < issued) {
/* Throttled by own commands: wait for oldest completion. */
wait_for_completion(&cmds[*completed].cmd_done);
(*completed)++;
goto retry;
} else {
/* Throttled by external commands: switch to sync api. */
err = mlx5_cmd_exec(mvdev->mdev,
cmd->in, cmd->inlen,
cmd->out, cmd->outlen);
if (!err)
(*completed)++;
}
}
return err;
}
int mlx5_vdpa_exec_async_cmds(struct mlx5_vdpa_dev *mvdev,
struct mlx5_vdpa_async_cmd *cmds,
int num_cmds)
{
int completed = 0;
int issued = 0;
int err = 0;
for (int i = 0; i < num_cmds; i++)
init_completion(&cmds[i].cmd_done);
while (issued < num_cmds) {
err = issue_async_cmd(mvdev, cmds, issued, &completed);
if (err) {
mlx5_vdpa_err(mvdev, "error issuing command %d of %d: %d\n",
issued, num_cmds, err);
break;
}
issued++;
}
while (completed < issued)
wait_for_completion(&cmds[completed++].cmd_done);
return err;
}
+153
View File
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
#include <linux/debugfs.h>
#include <linux/mlx5/fs.h>
#include "mlx5_vnet.h"
static int tirn_show(struct seq_file *file, void *priv)
{
struct mlx5_vdpa_net *ndev = file->private;
seq_printf(file, "0x%x\n", ndev->res.tirn);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(tirn);
void mlx5_vdpa_remove_tirn(struct mlx5_vdpa_net *ndev)
{
if (ndev->debugfs)
debugfs_remove(ndev->res.tirn_dent);
}
void mlx5_vdpa_add_tirn(struct mlx5_vdpa_net *ndev)
{
ndev->res.tirn_dent = debugfs_create_file("tirn", 0444, ndev->rx_dent,
ndev, &tirn_fops);
}
static int rx_flow_table_show(struct seq_file *file, void *priv)
{
struct mlx5_vdpa_net *ndev = file->private;
seq_printf(file, "0x%x\n", mlx5_flow_table_id(ndev->rxft));
return 0;
}
DEFINE_SHOW_ATTRIBUTE(rx_flow_table);
void mlx5_vdpa_remove_rx_flow_table(struct mlx5_vdpa_net *ndev)
{
if (ndev->debugfs)
debugfs_remove(ndev->rx_table_dent);
}
void mlx5_vdpa_add_rx_flow_table(struct mlx5_vdpa_net *ndev)
{
ndev->rx_table_dent = debugfs_create_file("table_id", 0444, ndev->rx_dent,
ndev, &rx_flow_table_fops);
}
#if defined(CONFIG_MLX5_VDPA_STEERING_DEBUG)
static int packets_show(struct seq_file *file, void *priv)
{
struct mlx5_vdpa_counter *counter = file->private;
u64 packets;
u64 bytes;
int err;
err = mlx5_fc_query(counter->mdev, counter->counter, &packets, &bytes);
if (err)
return err;
seq_printf(file, "0x%llx\n", packets);
return 0;
}
static int bytes_show(struct seq_file *file, void *priv)
{
struct mlx5_vdpa_counter *counter = file->private;
u64 packets;
u64 bytes;
int err;
err = mlx5_fc_query(counter->mdev, counter->counter, &packets, &bytes);
if (err)
return err;
seq_printf(file, "0x%llx\n", bytes);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(packets);
DEFINE_SHOW_ATTRIBUTE(bytes);
static void add_counter_node(struct mlx5_vdpa_counter *counter,
struct dentry *parent)
{
debugfs_create_file("packets", 0444, parent, counter,
&packets_fops);
debugfs_create_file("bytes", 0444, parent, counter,
&bytes_fops);
}
void mlx5_vdpa_add_rx_counters(struct mlx5_vdpa_net *ndev,
struct macvlan_node *node)
{
static const char *ut = "untagged";
char vidstr[9];
u16 vid;
node->ucast_counter.mdev = ndev->mvdev.mdev;
node->mcast_counter.mdev = ndev->mvdev.mdev;
if (node->tagged) {
vid = key2vid(node->macvlan);
snprintf(vidstr, sizeof(vidstr), "0x%x", vid);
} else {
strcpy(vidstr, ut);
}
node->dent = debugfs_create_dir(vidstr, ndev->rx_dent);
if (IS_ERR(node->dent)) {
node->dent = NULL;
return;
}
node->ucast_counter.dent = debugfs_create_dir("ucast", node->dent);
if (IS_ERR(node->ucast_counter.dent))
return;
add_counter_node(&node->ucast_counter, node->ucast_counter.dent);
node->mcast_counter.dent = debugfs_create_dir("mcast", node->dent);
if (IS_ERR(node->mcast_counter.dent))
return;
add_counter_node(&node->mcast_counter, node->mcast_counter.dent);
}
void mlx5_vdpa_remove_rx_counters(struct mlx5_vdpa_net *ndev,
struct macvlan_node *node)
{
if (node->dent && ndev->debugfs)
debugfs_remove_recursive(node->dent);
}
#endif
void mlx5_vdpa_add_debugfs(struct mlx5_vdpa_net *ndev)
{
struct mlx5_core_dev *mdev;
mdev = ndev->mvdev.mdev;
ndev->debugfs = debugfs_create_dir(dev_name(&ndev->mvdev.vdev.dev),
mlx5_debugfs_get_dev_root(mdev));
if (!IS_ERR(ndev->debugfs))
ndev->rx_dent = debugfs_create_dir("rx", ndev->debugfs);
}
void mlx5_vdpa_remove_debugfs(struct mlx5_vdpa_net *ndev)
{
debugfs_remove_recursive(ndev->debugfs);
ndev->debugfs = NULL;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB */
/* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
#ifndef __MLX5_VNET_H__
#define __MLX5_VNET_H__
#include "mlx5_vdpa.h"
#define to_mlx5_vdpa_ndev(__mvdev) \
container_of(__mvdev, struct mlx5_vdpa_net, mvdev)
#define to_mvdev(__vdev) container_of((__vdev), struct mlx5_vdpa_dev, vdev)
struct mlx5_vdpa_net_resources {
u32 tisn;
u32 tdn;
u32 tirn;
u32 rqtn;
bool valid;
struct dentry *tirn_dent;
};
#define MLX5V_MACVLAN_SIZE 256
static inline u16 key2vid(u64 key)
{
return (u16)(key >> 48) & 0xfff;
}
#define MLX5_VDPA_IRQ_NAME_LEN 32
struct mlx5_vdpa_irq_pool_entry {
struct msi_map map;
bool used;
char name[MLX5_VDPA_IRQ_NAME_LEN];
void *dev_id;
};
struct mlx5_vdpa_irq_pool {
int num_ent;
struct mlx5_vdpa_irq_pool_entry *entries;
};
struct mlx5_vdpa_net {
struct mlx5_vdpa_dev mvdev;
struct mlx5_vdpa_net_resources res;
struct virtio_net_config config;
struct mlx5_vdpa_virtqueue *vqs;
struct vdpa_callback *event_cbs;
/* Serialize vq resources creation and destruction. This is required
* since memory map might change and we need to destroy and create
* resources while driver in operational.
*/
struct rw_semaphore reslock;
struct mlx5_flow_table *rxft;
struct dentry *rx_dent;
struct dentry *rx_table_dent;
bool setup;
bool needs_teardown;
u32 cur_num_vqs;
u32 rqt_size;
bool nb_registered;
struct notifier_block nb;
struct vdpa_callback config_cb;
struct mlx5_vdpa_wq_ent cvq_ent;
struct hlist_head macvlan_hash[MLX5V_MACVLAN_SIZE];
struct mlx5_vdpa_irq_pool irqp;
struct dentry *debugfs;
u32 umem_1_buffer_param_a;
u32 umem_1_buffer_param_b;
u32 umem_2_buffer_param_a;
u32 umem_2_buffer_param_b;
u32 umem_3_buffer_param_a;
u32 umem_3_buffer_param_b;
};
struct mlx5_vdpa_counter {
struct mlx5_fc *counter;
struct dentry *dent;
struct mlx5_core_dev *mdev;
};
struct macvlan_node {
struct hlist_node hlist;
struct mlx5_flow_handle *ucast_rule;
struct mlx5_flow_handle *mcast_rule;
u64 macvlan;
struct mlx5_vdpa_net *ndev;
bool tagged;
#if defined(CONFIG_MLX5_VDPA_STEERING_DEBUG)
struct dentry *dent;
struct mlx5_vdpa_counter ucast_counter;
struct mlx5_vdpa_counter mcast_counter;
#endif
};
void mlx5_vdpa_add_debugfs(struct mlx5_vdpa_net *ndev);
void mlx5_vdpa_remove_debugfs(struct mlx5_vdpa_net *ndev);
void mlx5_vdpa_add_rx_flow_table(struct mlx5_vdpa_net *ndev);
void mlx5_vdpa_remove_rx_flow_table(struct mlx5_vdpa_net *ndev);
void mlx5_vdpa_add_tirn(struct mlx5_vdpa_net *ndev);
void mlx5_vdpa_remove_tirn(struct mlx5_vdpa_net *ndev);
#if defined(CONFIG_MLX5_VDPA_STEERING_DEBUG)
void mlx5_vdpa_add_rx_counters(struct mlx5_vdpa_net *ndev,
struct macvlan_node *node);
void mlx5_vdpa_remove_rx_counters(struct mlx5_vdpa_net *ndev,
struct macvlan_node *node);
#else
static inline void mlx5_vdpa_add_rx_counters(struct mlx5_vdpa_net *ndev,
struct macvlan_node *node) {}
static inline void mlx5_vdpa_remove_rx_counters(struct mlx5_vdpa_net *ndev,
struct macvlan_node *node) {}
#endif
#endif /* __MLX5_VNET_H__ */
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_OCTEONEP_VDPA) += octep_vdpa.o
octep_vdpa-$(CONFIG_OCTEONEP_VDPA) += octep_vdpa_main.o
octep_vdpa-$(CONFIG_OCTEONEP_VDPA) += octep_vdpa_hw.o
@@ -0,0 +1,134 @@
/* SPDX-License-Identifier: GPL-2.0-only
* Copyright (C) 2024 Marvell.
*/
#ifndef __OCTEP_VDPA_H__
#define __OCTEP_VDPA_H__
#include <linux/pci.h>
#include <linux/pci_regs.h>
#include <linux/vdpa.h>
#include <linux/virtio_pci_modern.h>
#include <uapi/linux/virtio_crypto.h>
#include <uapi/linux/virtio_net.h>
#include <uapi/linux/virtio_blk.h>
#include <uapi/linux/virtio_config.h>
#include <uapi/linux/virtio_pci.h>
#include <uapi/linux/vdpa.h>
#define OCTEP_VDPA_DEVID_CN106K_PF 0xb900
#define OCTEP_VDPA_DEVID_CN106K_VF 0xb903
#define OCTEP_VDPA_DEVID_CN105K_PF 0xba00
#define OCTEP_VDPA_DEVID_CN105K_VF 0xba03
#define OCTEP_VDPA_DEVID_CN103K_PF 0xbd00
#define OCTEP_VDPA_DEVID_CN103K_VF 0xbd03
#define OCTEP_HW_MBOX_BAR 0
#define OCTEP_HW_CAPS_BAR 4
#define OCTEP_DEV_READY_SIGNATURE 0xBABABABA
#define OCTEP_EPF_RINFO(x) (0x000209f0 | ((x) << 25))
#define OCTEP_VF_MBOX_DATA(x) (0x00010210 | ((x) << 17))
#define OCTEP_PF_MBOX_DATA(x) (0x00022000 | ((x) << 4))
#define OCTEP_VF_EVENT_STATE(x) (0x00010030 | ((x) << 17))
#define OCTEP_VF_EVENT_REG(x) (0x00010060 | ((x) << 17))
#define OCTEP_VF_IN_CTRL(x) (0x00010000 | ((x) << 17))
#define OCTEP_VF_IN_CTRL_RPVF(val) (FIELD_GET(GENMASK_ULL(51, 48), val))
#define OCTEP_FW_READY_SIGNATURE0 0xFEEDFEED
#define OCTEP_FW_READY_SIGNATURE1 0x3355ffaa
#define OCTEP_MAX_CB_INTR 8
enum octep_vdpa_dev_status {
OCTEP_VDPA_DEV_STATUS_INVALID,
OCTEP_VDPA_DEV_STATUS_ALLOC,
OCTEP_VDPA_DEV_STATUS_WAIT_FOR_BAR_INIT,
OCTEP_VDPA_DEV_STATUS_INIT,
OCTEP_VDPA_DEV_STATUS_READY,
OCTEP_VDPA_DEV_STATUS_ADDED,
OCTEP_VDPA_DEV_STATUS_REMOVED,
OCTEP_VDPA_DEV_STATUS_UNINIT
};
enum octep_vdpa_dev_event_state {
OCTEP_VDPA_DEV_NO_EVENT,
OCTEP_VDPA_DEV_NEW_EVENT,
OCTEP_VDPA_DEV_EVENT_ACTIVE,
OCTEP_VDPA_DEV_EVENT_DONE,
};
enum octep_vdpa_dev_event {
OCTEP_VDPA_DEV_EVENT_NONE,
OCTEP_VDPA_DEV_EVENT_ACK,
OCTEP_VDPA_DEV_EVENT_NACK,
OCTEP_VDPA_DEV_ADD_EVENT,
OCTEP_VDPA_DEV_DEL_EVENT,
};
struct octep_vring_info {
struct vdpa_callback cb;
void __iomem *notify_addr;
void __iomem *cb_notify_addr;
phys_addr_t notify_pa;
};
enum octep_pci_vndr_cfg_type {
OCTEP_PCI_VNDR_CFG_TYPE_VIRTIO_ID,
OCTEP_PCI_VNDR_CFG_TYPE_MAX,
};
struct octep_pci_vndr_data {
struct virtio_pci_vndr_data hdr;
u8 id;
u8 bar;
union {
u64 data;
struct {
u32 offset;
u32 length;
};
};
};
struct octep_hw {
struct pci_dev *pdev;
u8 __iomem *base[PCI_STD_NUM_BARS];
struct virtio_pci_common_cfg __iomem *common_cfg;
u8 __iomem *dev_cfg;
u8 __iomem *isr;
void __iomem *notify_base;
phys_addr_t notify_base_pa;
u32 notify_off_multiplier;
u8 notify_bar;
struct octep_vring_info *vqs;
struct vdpa_callback config_cb;
u64 features;
u16 nr_vring;
u32 config_size;
int requested_irqs;
int nb_irqs;
int *irqs;
u8 dev_id;
};
u8 octep_hw_get_status(struct octep_hw *oct_hw);
void octep_hw_set_status(struct octep_hw *dev, uint8_t status);
void octep_hw_reset(struct octep_hw *oct_hw);
void octep_write_queue_select(struct octep_hw *oct_hw, u16 queue_id);
void octep_notify_queue(struct octep_hw *oct_hw, u16 qid);
void octep_read_dev_config(struct octep_hw *oct_hw, u64 offset, void *dst, int length);
int octep_set_vq_address(struct octep_hw *oct_hw, u16 qid, u64 desc_area, u64 driver_area,
u64 device_area);
void octep_set_vq_num(struct octep_hw *oct_hw, u16 qid, u32 num);
void octep_set_vq_ready(struct octep_hw *oct_hw, u16 qid, bool ready);
bool octep_get_vq_ready(struct octep_hw *oct_hw, u16 qid);
int octep_set_vq_state(struct octep_hw *oct_hw, u16 qid, const struct vdpa_vq_state *state);
int octep_get_vq_state(struct octep_hw *oct_hw, u16 qid, struct vdpa_vq_state *state);
u16 octep_get_vq_size(struct octep_hw *oct_hw);
int octep_hw_caps_read(struct octep_hw *oct_hw, struct pci_dev *pdev);
u64 octep_hw_get_dev_features(struct octep_hw *oct_hw);
void octep_hw_set_drv_features(struct octep_hw *oct_hw, u64 features);
u64 octep_hw_get_drv_features(struct octep_hw *oct_hw);
int octep_verify_features(u64 features);
#endif /* __OCTEP_VDPA_H__ */
@@ -0,0 +1,549 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2024 Marvell. */
#include <linux/iopoll.h>
#include <linux/build_bug.h>
#include "octep_vdpa.h"
enum octep_mbox_ids {
OCTEP_MBOX_MSG_SET_VQ_STATE = 1,
OCTEP_MBOX_MSG_GET_VQ_STATE,
};
#define OCTEP_HW_TIMEOUT 10000000
#define MBOX_OFFSET 64
#define MBOX_RSP_MASK 0x00000001
#define MBOX_RC_MASK 0x0000FFFE
#define MBOX_RSP_TO_ERR(val) (-(((val) & MBOX_RC_MASK) >> 2))
#define MBOX_AVAIL(val) (((val) & MBOX_RSP_MASK))
#define MBOX_RSP(val) ((val) & (MBOX_RC_MASK | MBOX_RSP_MASK))
#define DEV_RST_ACK_BIT 7
#define FEATURE_SEL_ACK_BIT 15
#define QUEUE_SEL_ACK_BIT 15
struct octep_mbox_hdr {
u8 ver;
u8 rsvd1;
u16 id;
u16 rsvd2;
#define MBOX_REQ_SIG (0xdead)
#define MBOX_RSP_SIG (0xbeef)
u16 sig;
};
struct octep_mbox_sts {
u16 rsp:1;
u16 rc:15;
u16 rsvd;
};
struct octep_mbox {
struct octep_mbox_hdr hdr;
struct octep_mbox_sts sts;
u64 rsvd;
u32 data[];
};
static inline struct octep_mbox __iomem *octep_get_mbox(struct octep_hw *oct_hw)
{
return (struct octep_mbox __iomem *)(oct_hw->dev_cfg + MBOX_OFFSET);
}
static inline int octep_wait_for_mbox_avail(struct octep_mbox __iomem *mbox)
{
u32 val;
return readx_poll_timeout(ioread32, &mbox->sts, val, MBOX_AVAIL(val), 10,
OCTEP_HW_TIMEOUT);
}
static inline int octep_wait_for_mbox_rsp(struct octep_mbox __iomem *mbox)
{
u32 val;
return readx_poll_timeout(ioread32, &mbox->sts, val, MBOX_RSP(val), 10,
OCTEP_HW_TIMEOUT);
}
static inline void octep_write_hdr(struct octep_mbox __iomem *mbox, u16 id, u16 sig)
{
iowrite16(id, &mbox->hdr.id);
iowrite16(sig, &mbox->hdr.sig);
}
static inline u32 octep_read_sig(struct octep_mbox __iomem *mbox)
{
return ioread16(&mbox->hdr.sig);
}
static inline void octep_write_sts(struct octep_mbox __iomem *mbox, u32 sts)
{
iowrite32(sts, &mbox->sts);
}
static inline u32 octep_read_sts(struct octep_mbox __iomem *mbox)
{
return ioread32(&mbox->sts);
}
static inline u32 octep_read32_word(struct octep_mbox __iomem *mbox, u16 word_idx)
{
return ioread32(&mbox->data[word_idx]);
}
static inline void octep_write32_word(struct octep_mbox __iomem *mbox, u16 word_idx, u32 word)
{
return iowrite32(word, &mbox->data[word_idx]);
}
static int octep_process_mbox(struct octep_hw *oct_hw, u16 id, u16 qid, void *buffer,
u32 buf_size, bool write)
{
struct octep_mbox __iomem *mbox = octep_get_mbox(oct_hw);
struct pci_dev *pdev = oct_hw->pdev;
u32 *p = (u32 *)buffer;
u16 data_wds;
int ret, i;
u32 val;
if (!IS_ALIGNED(buf_size, 4))
return -EINVAL;
/* Make sure mbox space is available */
ret = octep_wait_for_mbox_avail(mbox);
if (ret) {
dev_warn(&pdev->dev, "Timeout waiting for previous mbox data to be consumed\n");
return ret;
}
data_wds = buf_size / 4;
if (write) {
for (i = 1; i <= data_wds; i++) {
octep_write32_word(mbox, i, *p);
p++;
}
}
octep_write32_word(mbox, 0, (u32)qid);
octep_write_sts(mbox, 0);
octep_write_hdr(mbox, id, MBOX_REQ_SIG);
ret = octep_wait_for_mbox_rsp(mbox);
if (ret) {
dev_warn(&pdev->dev, "Timeout waiting for mbox : %d response\n", id);
return ret;
}
val = octep_read_sig(mbox);
if ((val & 0xFFFF) != MBOX_RSP_SIG) {
dev_warn(&pdev->dev, "Invalid Signature from mbox : %d response\n", id);
return -EINVAL;
}
val = octep_read_sts(mbox);
if (val & MBOX_RC_MASK) {
ret = MBOX_RSP_TO_ERR(val);
dev_warn(&pdev->dev, "Error while processing mbox : %d, err %d\n", id, ret);
return ret;
}
if (!write)
for (i = 1; i <= data_wds; i++)
*p++ = octep_read32_word(mbox, i);
return 0;
}
static void octep_mbox_init(struct octep_mbox __iomem *mbox)
{
iowrite32(1, &mbox->sts);
}
int octep_verify_features(u64 features)
{
/* Minimum features to expect */
if (!(features & BIT_ULL(VIRTIO_F_VERSION_1)))
return -EOPNOTSUPP;
if (!(features & BIT_ULL(VIRTIO_F_NOTIFICATION_DATA)))
return -EOPNOTSUPP;
if (!(features & BIT_ULL(VIRTIO_F_RING_PACKED)))
return -EOPNOTSUPP;
return 0;
}
u8 octep_hw_get_status(struct octep_hw *oct_hw)
{
return ioread8(&oct_hw->common_cfg->device_status);
}
void octep_hw_set_status(struct octep_hw *oct_hw, u8 status)
{
iowrite8(status, &oct_hw->common_cfg->device_status);
}
void octep_hw_reset(struct octep_hw *oct_hw)
{
u8 val;
octep_hw_set_status(oct_hw, 0 | BIT(DEV_RST_ACK_BIT));
if (readx_poll_timeout(ioread8, &oct_hw->common_cfg->device_status, val, !val, 10,
OCTEP_HW_TIMEOUT)) {
dev_warn(&oct_hw->pdev->dev, "Octeon device reset timeout\n");
return;
}
}
static int feature_sel_write_with_timeout(struct octep_hw *oct_hw, u32 select, void __iomem *addr)
{
u32 val;
iowrite32(select | BIT(FEATURE_SEL_ACK_BIT), addr);
if (readx_poll_timeout(ioread32, addr, val, val == select, 10, OCTEP_HW_TIMEOUT)) {
dev_warn(&oct_hw->pdev->dev, "Feature select%d write timeout\n", select);
return -1;
}
return 0;
}
u64 octep_hw_get_dev_features(struct octep_hw *oct_hw)
{
u32 features_lo, features_hi;
if (feature_sel_write_with_timeout(oct_hw, 0, &oct_hw->common_cfg->device_feature_select))
return 0;
features_lo = ioread32(&oct_hw->common_cfg->device_feature);
if (feature_sel_write_with_timeout(oct_hw, 1, &oct_hw->common_cfg->device_feature_select))
return 0;
features_hi = ioread32(&oct_hw->common_cfg->device_feature);
return ((u64)features_hi << 32) | features_lo;
}
u64 octep_hw_get_drv_features(struct octep_hw *oct_hw)
{
u32 features_lo, features_hi;
if (feature_sel_write_with_timeout(oct_hw, 0, &oct_hw->common_cfg->guest_feature_select))
return 0;
features_lo = ioread32(&oct_hw->common_cfg->guest_feature);
if (feature_sel_write_with_timeout(oct_hw, 1, &oct_hw->common_cfg->guest_feature_select))
return 0;
features_hi = ioread32(&oct_hw->common_cfg->guest_feature);
return ((u64)features_hi << 32) | features_lo;
}
void octep_hw_set_drv_features(struct octep_hw *oct_hw, u64 features)
{
if (feature_sel_write_with_timeout(oct_hw, 0, &oct_hw->common_cfg->guest_feature_select))
return;
iowrite32(features & (BIT_ULL(32) - 1), &oct_hw->common_cfg->guest_feature);
if (feature_sel_write_with_timeout(oct_hw, 1, &oct_hw->common_cfg->guest_feature_select))
return;
iowrite32(features >> 32, &oct_hw->common_cfg->guest_feature);
}
void octep_write_queue_select(struct octep_hw *oct_hw, u16 queue_id)
{
u16 val;
iowrite16(queue_id | BIT(QUEUE_SEL_ACK_BIT), &oct_hw->common_cfg->queue_select);
if (readx_poll_timeout(ioread16, &oct_hw->common_cfg->queue_select, val, val == queue_id,
10, OCTEP_HW_TIMEOUT)) {
dev_warn(&oct_hw->pdev->dev, "Queue select write timeout\n");
return;
}
}
void octep_notify_queue(struct octep_hw *oct_hw, u16 qid)
{
iowrite16(qid, oct_hw->vqs[qid].notify_addr);
}
void octep_read_dev_config(struct octep_hw *oct_hw, u64 offset, void *dst, int length)
{
u8 old_gen, new_gen, *p;
int i;
if (WARN_ON(offset + length > oct_hw->config_size))
return;
do {
old_gen = ioread8(&oct_hw->common_cfg->config_generation);
p = dst;
for (i = 0; i < length; i++)
*p++ = ioread8(oct_hw->dev_cfg + offset + i);
new_gen = ioread8(&oct_hw->common_cfg->config_generation);
} while (old_gen != new_gen);
}
int octep_set_vq_address(struct octep_hw *oct_hw, u16 qid, u64 desc_area, u64 driver_area,
u64 device_area)
{
struct virtio_pci_common_cfg __iomem *cfg = oct_hw->common_cfg;
octep_write_queue_select(oct_hw, qid);
vp_iowrite64_twopart(desc_area, &cfg->queue_desc_lo,
&cfg->queue_desc_hi);
vp_iowrite64_twopart(driver_area, &cfg->queue_avail_lo,
&cfg->queue_avail_hi);
vp_iowrite64_twopart(device_area, &cfg->queue_used_lo,
&cfg->queue_used_hi);
return 0;
}
int octep_get_vq_state(struct octep_hw *oct_hw, u16 qid, struct vdpa_vq_state *state)
{
return octep_process_mbox(oct_hw, OCTEP_MBOX_MSG_GET_VQ_STATE, qid, state,
sizeof(*state), 0);
}
int octep_set_vq_state(struct octep_hw *oct_hw, u16 qid, const struct vdpa_vq_state *state)
{
struct vdpa_vq_state q_state;
memcpy(&q_state, state, sizeof(struct vdpa_vq_state));
return octep_process_mbox(oct_hw, OCTEP_MBOX_MSG_SET_VQ_STATE, qid, &q_state,
sizeof(*state), 1);
}
void octep_set_vq_num(struct octep_hw *oct_hw, u16 qid, u32 num)
{
struct virtio_pci_common_cfg __iomem *cfg = oct_hw->common_cfg;
octep_write_queue_select(oct_hw, qid);
iowrite16(num, &cfg->queue_size);
}
void octep_set_vq_ready(struct octep_hw *oct_hw, u16 qid, bool ready)
{
struct virtio_pci_common_cfg __iomem *cfg = oct_hw->common_cfg;
octep_write_queue_select(oct_hw, qid);
iowrite16(ready, &cfg->queue_enable);
}
bool octep_get_vq_ready(struct octep_hw *oct_hw, u16 qid)
{
struct virtio_pci_common_cfg __iomem *cfg = oct_hw->common_cfg;
octep_write_queue_select(oct_hw, qid);
return ioread16(&cfg->queue_enable);
}
u16 octep_get_vq_size(struct octep_hw *oct_hw)
{
octep_write_queue_select(oct_hw, 0);
return ioread16(&oct_hw->common_cfg->queue_size);
}
static u32 octep_get_config_size(struct octep_hw *oct_hw)
{
switch (oct_hw->dev_id) {
case VIRTIO_ID_NET:
return sizeof(struct virtio_net_config);
case VIRTIO_ID_CRYPTO:
return sizeof(struct virtio_crypto_config);
default:
return 0;
}
}
static void __iomem *octep_get_cap_addr(struct octep_hw *oct_hw, struct virtio_pci_cap *cap)
{
struct device *dev = &oct_hw->pdev->dev;
u32 length = le32_to_cpu(cap->length);
u32 offset = le32_to_cpu(cap->offset);
u8 bar = cap->bar;
u32 len;
if (bar != OCTEP_HW_CAPS_BAR) {
dev_err(dev, "Invalid bar: %u\n", bar);
return NULL;
}
if (offset + length < offset) {
dev_err(dev, "offset(%u) + length(%u) overflows\n",
offset, length);
return NULL;
}
len = pci_resource_len(oct_hw->pdev, bar);
if (offset + length > len) {
dev_err(dev, "invalid cap: overflows bar space: %u > %u\n",
offset + length, len);
return NULL;
}
return oct_hw->base[bar] + offset;
}
/* In Octeon DPU device, the virtio config space is completely
* emulated by the device's firmware. So, the standard pci config
* read apis can't be used for reading the virtio capability.
*/
static void octep_pci_caps_read(struct octep_hw *oct_hw, void *buf, size_t len, off_t offset)
{
u8 __iomem *bar = oct_hw->base[OCTEP_HW_CAPS_BAR];
u8 *p = buf;
size_t i;
for (i = 0; i < len; i++)
*p++ = ioread8(bar + offset + i);
}
static int octep_pci_signature_verify(struct octep_hw *oct_hw)
{
u32 signature[2];
octep_pci_caps_read(oct_hw, &signature, sizeof(signature), 0);
if (signature[0] != OCTEP_FW_READY_SIGNATURE0)
return -1;
if (signature[1] != OCTEP_FW_READY_SIGNATURE1)
return -1;
return 0;
}
static void octep_vndr_data_process(struct octep_hw *oct_hw,
struct octep_pci_vndr_data *vndr_data)
{
BUILD_BUG_ON(sizeof(struct octep_pci_vndr_data) % 4 != 0);
switch (vndr_data->id) {
case OCTEP_PCI_VNDR_CFG_TYPE_VIRTIO_ID:
oct_hw->dev_id = (u8)vndr_data->data;
break;
default:
dev_err(&oct_hw->pdev->dev, "Invalid vendor data id %u\n",
vndr_data->id);
break;
}
}
int octep_hw_caps_read(struct octep_hw *oct_hw, struct pci_dev *pdev)
{
struct octep_pci_vndr_data vndr_data;
struct octep_mbox __iomem *mbox;
struct device *dev = &pdev->dev;
struct virtio_pci_cap cap;
u16 notify_off;
int i, ret;
u8 pos;
oct_hw->pdev = pdev;
ret = octep_pci_signature_verify(oct_hw);
if (ret) {
dev_err(dev, "Octeon Virtio FW is not initialized\n");
return -EIO;
}
octep_pci_caps_read(oct_hw, &pos, 1, PCI_CAPABILITY_LIST);
while (pos) {
octep_pci_caps_read(oct_hw, &cap, 2, pos);
if (cap.cap_vndr != PCI_CAP_ID_VNDR) {
dev_err(dev, "Found invalid capability vndr id: %d\n", cap.cap_vndr);
break;
}
octep_pci_caps_read(oct_hw, &cap, sizeof(cap), pos);
dev_info(dev, "[%2x] cfg type: %u, bar: %u, offset: %04x, len: %u\n",
pos, cap.cfg_type, cap.bar, cap.offset, cap.length);
switch (cap.cfg_type) {
case VIRTIO_PCI_CAP_COMMON_CFG:
oct_hw->common_cfg = octep_get_cap_addr(oct_hw, &cap);
break;
case VIRTIO_PCI_CAP_NOTIFY_CFG:
octep_pci_caps_read(oct_hw, &oct_hw->notify_off_multiplier,
4, pos + sizeof(cap));
oct_hw->notify_base = octep_get_cap_addr(oct_hw, &cap);
oct_hw->notify_bar = cap.bar;
oct_hw->notify_base_pa = pci_resource_start(pdev, cap.bar) +
le32_to_cpu(cap.offset);
break;
case VIRTIO_PCI_CAP_DEVICE_CFG:
oct_hw->dev_cfg = octep_get_cap_addr(oct_hw, &cap);
break;
case VIRTIO_PCI_CAP_ISR_CFG:
oct_hw->isr = octep_get_cap_addr(oct_hw, &cap);
break;
case VIRTIO_PCI_CAP_VENDOR_CFG:
octep_pci_caps_read(oct_hw, &vndr_data, sizeof(vndr_data), pos);
if (vndr_data.hdr.vendor_id != PCI_VENDOR_ID_CAVIUM) {
dev_err(dev, "Invalid vendor data\n");
return -EINVAL;
}
octep_vndr_data_process(oct_hw, &vndr_data);
break;
}
pos = cap.cap_next;
}
if (!oct_hw->common_cfg || !oct_hw->notify_base ||
!oct_hw->dev_cfg || !oct_hw->isr) {
dev_err(dev, "Incomplete PCI capabilities");
return -EIO;
}
dev_info(dev, "common cfg mapped at: %p\n", oct_hw->common_cfg);
dev_info(dev, "device cfg mapped at: %p\n", oct_hw->dev_cfg);
dev_info(dev, "isr cfg mapped at: %p\n", oct_hw->isr);
dev_info(dev, "notify base: %p, notify off multiplier: %u\n",
oct_hw->notify_base, oct_hw->notify_off_multiplier);
oct_hw->config_size = octep_get_config_size(oct_hw);
oct_hw->features = octep_hw_get_dev_features(oct_hw);
ret = octep_verify_features(oct_hw->features);
if (ret) {
dev_err(&pdev->dev, "Couldn't read features from the device FW\n");
return ret;
}
oct_hw->nr_vring = vp_ioread16(&oct_hw->common_cfg->num_queues);
oct_hw->vqs = devm_kcalloc(&pdev->dev, oct_hw->nr_vring, sizeof(*oct_hw->vqs), GFP_KERNEL);
if (!oct_hw->vqs)
return -ENOMEM;
dev_info(&pdev->dev, "Device features : %llx\n", oct_hw->features);
dev_info(&pdev->dev, "Maximum queues : %u\n", oct_hw->nr_vring);
for (i = 0; i < oct_hw->nr_vring; i++) {
octep_write_queue_select(oct_hw, i);
notify_off = vp_ioread16(&oct_hw->common_cfg->queue_notify_off);
oct_hw->vqs[i].notify_addr = oct_hw->notify_base +
notify_off * oct_hw->notify_off_multiplier;
oct_hw->vqs[i].cb_notify_addr = (u32 __iomem *)oct_hw->vqs[i].notify_addr + 1;
oct_hw->vqs[i].notify_pa = oct_hw->notify_base_pa +
notify_off * oct_hw->notify_off_multiplier;
}
mbox = octep_get_mbox(oct_hw);
octep_mbox_init(mbox);
dev_info(dev, "mbox mapped at: %p\n", mbox);
return 0;
}
@@ -0,0 +1,995 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2024 Marvell. */
#include <linux/bitfield.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/module.h>
#include <linux/iommu.h>
#include "octep_vdpa.h"
#define OCTEP_VDPA_DRIVER_NAME "octep_vdpa"
#define OCTEP_VDPA_NAME_BUFSIZE 16
struct octep_pf {
u8 __iomem *base[PCI_STD_NUM_BARS];
struct pci_dev *pdev;
struct resource res;
u64 vf_base;
int enabled_vfs;
u32 vf_stride;
u16 vf_devid;
};
struct octep_vdpa_event_wk {
struct work_struct work;
void *ctxptr;
};
struct octep_vdpa {
struct vdpa_device vdpa;
struct octep_hw *oct_hw;
struct pci_dev *pdev;
};
struct octep_vdpa_mgmt_dev {
struct vdpa_mgmt_dev mdev;
struct octep_hw oct_hw;
struct pci_dev *pdev;
/* Work entry to handle device setup */
struct work_struct setup_task;
/* Device status */
atomic_t status;
struct octep_vdpa *oct_vdpa;
struct octep_vdpa_event_wk event_wk;
};
static struct octep_hw *vdpa_to_octep_hw(struct vdpa_device *vdpa_dev)
{
struct octep_vdpa *oct_vdpa;
oct_vdpa = container_of(vdpa_dev, struct octep_vdpa, vdpa);
return oct_vdpa->oct_hw;
}
static inline void octep_vdpa_dev_event_schedule(struct octep_hw *oct_hw)
{
u8 __iomem *addr = oct_hw->base[OCTEP_HW_MBOX_BAR];
struct octep_vdpa_mgmt_dev *mgmt_dev;
mgmt_dev = container_of(oct_hw, struct octep_vdpa_mgmt_dev, oct_hw);
writeb(OCTEP_VDPA_DEV_EVENT_ACTIVE, addr + OCTEP_VF_EVENT_STATE(0));
schedule_work(&mgmt_dev->event_wk.work);
}
static irqreturn_t octep_vdpa_dev_event_handler(int irq, void *data)
{
struct octep_hw *oct_hw = data;
if (readb(oct_hw->base[OCTEP_HW_MBOX_BAR] + OCTEP_VF_EVENT_STATE(0)) ==
OCTEP_VDPA_DEV_NEW_EVENT)
octep_vdpa_dev_event_schedule(oct_hw);
return IRQ_HANDLED;
}
static irqreturn_t octep_vdpa_intr_handler(int irq, void *data)
{
struct octep_hw *oct_hw = data;
int i, start_ring_idx = -1;
/* Each device has multiple interrupts (nb_irqs) shared among rings
* (nr_vring). Device interrupts are mapped to the rings in a
* round-robin fashion.
*
* For example, if nb_irqs = 8 and nr_vring = 64:
* 0 -> 0, 8, 16, 24, 32, 40, 48, 56;
* 1 -> 1, 9, 17, 25, 33, 41, 49, 57;
* ...
* 7 -> 7, 15, 23, 31, 39, 47, 55, 63;
*/
for (i = 0; i < oct_hw->nb_irqs; i++) {
if (oct_hw->irqs[i] == irq) {
start_ring_idx = i;
break;
}
}
if (start_ring_idx == -1)
return IRQ_NONE;
for (i = start_ring_idx; i < oct_hw->nr_vring; i += oct_hw->nb_irqs) {
if (ioread8(oct_hw->vqs[i].cb_notify_addr)) {
/* Acknowledge the per ring notification to the device */
iowrite8(0, oct_hw->vqs[i].cb_notify_addr);
if (likely(oct_hw->vqs[i].cb.callback))
oct_hw->vqs[i].cb.callback(oct_hw->vqs[i].cb.private);
break;
}
}
/* Check for config interrupt. Config uses the first interrupt */
if (unlikely(irq == oct_hw->irqs[0])) {
if (ioread8(oct_hw->isr)) {
iowrite8(0, oct_hw->isr);
if (oct_hw->config_cb.callback)
oct_hw->config_cb.callback(oct_hw->config_cb.private);
}
octep_vdpa_dev_event_handler(irq, data);
}
return IRQ_HANDLED;
}
static void octep_free_irqs(struct octep_hw *oct_hw)
{
struct pci_dev *pdev = oct_hw->pdev;
int irq;
if (!oct_hw->irqs)
return;
for (irq = 0; irq < oct_hw->nb_irqs; irq++) {
if (!oct_hw->irqs[irq])
break;
devm_free_irq(&pdev->dev, oct_hw->irqs[irq], oct_hw);
}
pci_free_irq_vectors(pdev);
devm_kfree(&pdev->dev, oct_hw->irqs);
oct_hw->irqs = NULL;
oct_hw->requested_irqs = 0;
}
static int octep_request_irqs(struct octep_hw *oct_hw, irqreturn_t (*irq_handler)(int, void *),
int nb_irqs)
{
struct pci_dev *pdev = oct_hw->pdev;
int ret, irq, idx;
if ((oct_hw->requested_irqs != nb_irqs) || (nb_irqs == 1))
octep_free_irqs(oct_hw);
else
return 0;
oct_hw->irqs = devm_kcalloc(&pdev->dev, nb_irqs, sizeof(int), GFP_KERNEL);
if (!oct_hw->irqs)
return -ENOMEM;
ret = pci_alloc_irq_vectors(pdev, 1, nb_irqs, PCI_IRQ_MSIX);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to alloc msix vector");
return ret;
}
for (idx = 0; idx < nb_irqs; idx++) {
irq = pci_irq_vector(pdev, idx);
ret = devm_request_irq(&pdev->dev, irq, irq_handler, 0, dev_name(&pdev->dev),
oct_hw);
if (ret) {
dev_err(&pdev->dev, "Failed to register interrupt handler\n");
goto free_irqs;
}
oct_hw->irqs[idx] = irq;
}
oct_hw->requested_irqs = nb_irqs;
return 0;
free_irqs:
octep_free_irqs(oct_hw);
return ret;
}
static u64 octep_vdpa_get_device_features(struct vdpa_device *vdpa_dev)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return oct_hw->features;
}
static int octep_vdpa_set_driver_features(struct vdpa_device *vdpa_dev, u64 features)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
int ret;
pr_debug("Driver Features: %llx\n", features);
ret = octep_verify_features(features);
if (ret) {
dev_warn(&oct_hw->pdev->dev,
"Must negotiate minimum features 0x%llx for this device",
BIT_ULL(VIRTIO_F_VERSION_1) | BIT_ULL(VIRTIO_F_NOTIFICATION_DATA) |
BIT_ULL(VIRTIO_F_RING_PACKED));
return ret;
}
octep_hw_set_drv_features(oct_hw, features);
return 0;
}
static u64 octep_vdpa_get_driver_features(struct vdpa_device *vdpa_dev)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return octep_hw_get_drv_features(oct_hw);
}
static u8 octep_vdpa_get_status(struct vdpa_device *vdpa_dev)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return octep_hw_get_status(oct_hw);
}
static void octep_vdpa_set_status(struct vdpa_device *vdpa_dev, u8 status)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
u8 status_old;
status_old = octep_hw_get_status(oct_hw);
if (status_old == status)
return;
if ((status & VIRTIO_CONFIG_S_DRIVER_OK) &&
!(status_old & VIRTIO_CONFIG_S_DRIVER_OK)) {
if (octep_request_irqs(oct_hw, octep_vdpa_intr_handler, oct_hw->nb_irqs))
status = status_old | VIRTIO_CONFIG_S_FAILED;
}
octep_hw_set_status(oct_hw, status);
}
static int octep_vdpa_reset(struct vdpa_device *vdpa_dev)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
u8 status = octep_hw_get_status(oct_hw);
u16 qid;
if (status == 0)
return 0;
for (qid = 0; qid < oct_hw->nr_vring; qid++) {
oct_hw->vqs[qid].cb.callback = NULL;
oct_hw->vqs[qid].cb.private = NULL;
oct_hw->config_cb.callback = NULL;
oct_hw->config_cb.private = NULL;
}
octep_hw_reset(oct_hw);
if (status & VIRTIO_CONFIG_S_DRIVER_OK) {
octep_free_irqs(oct_hw);
octep_request_irqs(oct_hw, octep_vdpa_dev_event_handler, 1);
}
return 0;
}
static u16 octep_vdpa_get_vq_num_max(struct vdpa_device *vdpa_dev)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return octep_get_vq_size(oct_hw);
}
static int octep_vdpa_get_vq_state(struct vdpa_device *vdpa_dev, u16 qid,
struct vdpa_vq_state *state)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return octep_get_vq_state(oct_hw, qid, state);
}
static int octep_vdpa_set_vq_state(struct vdpa_device *vdpa_dev, u16 qid,
const struct vdpa_vq_state *state)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return octep_set_vq_state(oct_hw, qid, state);
}
static void octep_vdpa_set_vq_cb(struct vdpa_device *vdpa_dev, u16 qid, struct vdpa_callback *cb)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
oct_hw->vqs[qid].cb = *cb;
}
static void octep_vdpa_set_vq_ready(struct vdpa_device *vdpa_dev, u16 qid, bool ready)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
octep_set_vq_ready(oct_hw, qid, ready);
}
static bool octep_vdpa_get_vq_ready(struct vdpa_device *vdpa_dev, u16 qid)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return octep_get_vq_ready(oct_hw, qid);
}
static void octep_vdpa_set_vq_num(struct vdpa_device *vdpa_dev, u16 qid, u32 num)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
octep_set_vq_num(oct_hw, qid, num);
}
static int octep_vdpa_set_vq_address(struct vdpa_device *vdpa_dev, u16 qid, u64 desc_area,
u64 driver_area, u64 device_area)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
pr_debug("qid[%d]: desc_area: %llx\n", qid, desc_area);
pr_debug("qid[%d]: driver_area: %llx\n", qid, driver_area);
pr_debug("qid[%d]: device_area: %llx\n\n", qid, device_area);
return octep_set_vq_address(oct_hw, qid, desc_area, driver_area, device_area);
}
static void octep_vdpa_kick_vq(struct vdpa_device *vdpa_dev, u16 qid)
{
/* Not supported */
}
static void octep_vdpa_kick_vq_with_data(struct vdpa_device *vdpa_dev, u32 data)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
u16 idx = data & 0xFFFF;
vp_iowrite32(data, oct_hw->vqs[idx].notify_addr);
}
static u32 octep_vdpa_get_generation(struct vdpa_device *vdpa_dev)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return vp_ioread8(&oct_hw->common_cfg->config_generation);
}
static u32 octep_vdpa_get_device_id(struct vdpa_device *vdpa_dev)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return oct_hw->dev_id;
}
static u32 octep_vdpa_get_vendor_id(struct vdpa_device *vdpa_dev)
{
return PCI_VENDOR_ID_CAVIUM;
}
static u32 octep_vdpa_get_vq_align(struct vdpa_device *vdpa_dev)
{
return PAGE_SIZE;
}
static size_t octep_vdpa_get_config_size(struct vdpa_device *vdpa_dev)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
return oct_hw->config_size;
}
static void octep_vdpa_get_config(struct vdpa_device *vdpa_dev, unsigned int offset, void *buf,
unsigned int len)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
octep_read_dev_config(oct_hw, offset, buf, len);
}
static void octep_vdpa_set_config(struct vdpa_device *vdpa_dev, unsigned int offset,
const void *buf, unsigned int len)
{
/* Not supported */
}
static void octep_vdpa_set_config_cb(struct vdpa_device *vdpa_dev, struct vdpa_callback *cb)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
oct_hw->config_cb.callback = cb->callback;
oct_hw->config_cb.private = cb->private;
}
static struct vdpa_notification_area octep_get_vq_notification(struct vdpa_device *vdpa_dev,
u16 idx)
{
struct octep_hw *oct_hw = vdpa_to_octep_hw(vdpa_dev);
struct vdpa_notification_area area;
area.addr = oct_hw->vqs[idx].notify_pa;
area.size = PAGE_SIZE;
return area;
}
static struct vdpa_config_ops octep_vdpa_ops = {
.get_device_features = octep_vdpa_get_device_features,
.set_driver_features = octep_vdpa_set_driver_features,
.get_driver_features = octep_vdpa_get_driver_features,
.get_status = octep_vdpa_get_status,
.set_status = octep_vdpa_set_status,
.reset = octep_vdpa_reset,
.get_vq_num_max = octep_vdpa_get_vq_num_max,
.get_vq_state = octep_vdpa_get_vq_state,
.set_vq_state = octep_vdpa_set_vq_state,
.set_vq_cb = octep_vdpa_set_vq_cb,
.set_vq_ready = octep_vdpa_set_vq_ready,
.get_vq_ready = octep_vdpa_get_vq_ready,
.set_vq_num = octep_vdpa_set_vq_num,
.set_vq_address = octep_vdpa_set_vq_address,
.get_vq_irq = NULL,
.kick_vq = octep_vdpa_kick_vq,
.kick_vq_with_data = octep_vdpa_kick_vq_with_data,
.get_generation = octep_vdpa_get_generation,
.get_device_id = octep_vdpa_get_device_id,
.get_vendor_id = octep_vdpa_get_vendor_id,
.get_vq_align = octep_vdpa_get_vq_align,
.get_config_size = octep_vdpa_get_config_size,
.get_config = octep_vdpa_get_config,
.set_config = octep_vdpa_set_config,
.set_config_cb = octep_vdpa_set_config_cb,
.get_vq_notification = octep_get_vq_notification,
};
static int octep_iomap_region(struct pci_dev *pdev, u8 __iomem **tbl, u8 bar)
{
int ret;
ret = pci_request_region(pdev, bar, OCTEP_VDPA_DRIVER_NAME);
if (ret) {
dev_err(&pdev->dev, "Failed to request BAR:%u region\n", bar);
return ret;
}
tbl[bar] = pci_iomap(pdev, bar, pci_resource_len(pdev, bar));
if (!tbl[bar]) {
dev_err(&pdev->dev, "Failed to iomap BAR:%u\n", bar);
pci_release_region(pdev, bar);
ret = -ENOMEM;
}
return ret;
}
static void octep_iounmap_region(struct pci_dev *pdev, u8 __iomem **tbl, u8 bar)
{
pci_iounmap(pdev, tbl[bar]);
pci_release_region(pdev, bar);
}
static void octep_vdpa_pf_bar_shrink(struct octep_pf *octpf)
{
struct pci_dev *pf_dev = octpf->pdev;
struct resource *res = pf_dev->resource + PCI_STD_RESOURCES + 4;
struct pci_bus_region bus_region;
octpf->res.start = res->start;
octpf->res.end = res->end;
octpf->vf_base = res->start;
bus_region.start = res->start;
bus_region.end = res->start - 1;
pcibios_bus_to_resource(pf_dev->bus, res, &bus_region);
}
static void octep_vdpa_pf_bar_expand(struct octep_pf *octpf)
{
struct pci_dev *pf_dev = octpf->pdev;
struct resource *res = pf_dev->resource + PCI_STD_RESOURCES + 4;
struct pci_bus_region bus_region;
bus_region.start = octpf->res.start;
bus_region.end = octpf->res.end;
pcibios_bus_to_resource(pf_dev->bus, res, &bus_region);
}
static void octep_vdpa_remove_pf(struct pci_dev *pdev)
{
struct octep_pf *octpf = pci_get_drvdata(pdev);
pci_disable_sriov(pdev);
if (octpf->base[OCTEP_HW_CAPS_BAR])
octep_iounmap_region(pdev, octpf->base, OCTEP_HW_CAPS_BAR);
if (octpf->base[OCTEP_HW_MBOX_BAR])
octep_iounmap_region(pdev, octpf->base, OCTEP_HW_MBOX_BAR);
octep_vdpa_pf_bar_expand(octpf);
/* The pf version does not use managed PCI. */
pci_disable_device(pdev);
}
static void octep_vdpa_vf_bar_shrink(struct pci_dev *pdev)
{
struct resource *vf_res = pdev->resource + PCI_STD_RESOURCES + 4;
memset(vf_res, 0, sizeof(*vf_res));
}
static void octep_vdpa_remove_vf(struct pci_dev *pdev)
{
struct octep_vdpa_mgmt_dev *mgmt_dev = pci_get_drvdata(pdev);
struct octep_hw *oct_hw;
int status;
oct_hw = &mgmt_dev->oct_hw;
status = atomic_read(&mgmt_dev->status);
atomic_set(&mgmt_dev->status, OCTEP_VDPA_DEV_STATUS_UNINIT);
cancel_work_sync(&mgmt_dev->setup_task);
if ((status == OCTEP_VDPA_DEV_STATUS_READY) || (status == OCTEP_VDPA_DEV_STATUS_ADDED) ||
(status == OCTEP_VDPA_DEV_STATUS_REMOVED))
vdpa_mgmtdev_unregister(&mgmt_dev->mdev);
if (oct_hw->base[OCTEP_HW_CAPS_BAR])
octep_iounmap_region(pdev, oct_hw->base, OCTEP_HW_CAPS_BAR);
if (oct_hw->base[OCTEP_HW_MBOX_BAR])
octep_iounmap_region(pdev, oct_hw->base, OCTEP_HW_MBOX_BAR);
octep_vdpa_vf_bar_shrink(pdev);
octep_free_irqs(oct_hw);
}
static void octep_vdpa_remove(struct pci_dev *pdev)
{
if (pdev->is_virtfn)
octep_vdpa_remove_vf(pdev);
else
octep_vdpa_remove_pf(pdev);
}
static int octep_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name,
const struct vdpa_dev_set_config *config)
{
struct octep_vdpa_mgmt_dev *mgmt_dev = container_of(mdev, struct octep_vdpa_mgmt_dev, mdev);
struct octep_hw *oct_hw = &mgmt_dev->oct_hw;
struct pci_dev *pdev = oct_hw->pdev;
struct vdpa_device *vdpa_dev;
struct octep_vdpa *oct_vdpa;
u64 device_features;
int ret;
oct_vdpa = vdpa_alloc_device(struct octep_vdpa, vdpa, &pdev->dev, &octep_vdpa_ops,
NULL, 1, 1, NULL, false);
if (IS_ERR(oct_vdpa)) {
dev_err(&pdev->dev, "Failed to allocate vDPA structure for octep vdpa device");
return PTR_ERR(oct_vdpa);
}
oct_vdpa->pdev = pdev;
oct_vdpa->vdpa.vmap.dma_dev = &pdev->dev;
oct_vdpa->vdpa.mdev = mdev;
oct_vdpa->oct_hw = oct_hw;
vdpa_dev = &oct_vdpa->vdpa;
mgmt_dev->oct_vdpa = oct_vdpa;
device_features = oct_hw->features;
if (config->mask & BIT_ULL(VDPA_ATTR_DEV_FEATURES)) {
if (config->device_features & ~device_features) {
dev_err(&pdev->dev, "The provisioned features 0x%llx are not supported by this device with features 0x%llx\n",
config->device_features, device_features);
ret = -EINVAL;
goto vdpa_dev_put;
}
device_features &= config->device_features;
}
oct_hw->features = device_features;
dev_info(&pdev->dev, "Vdpa management device features : %llx\n", device_features);
ret = octep_verify_features(device_features);
if (ret) {
dev_warn(mdev->device,
"Must provision minimum features 0x%llx for this device",
BIT_ULL(VIRTIO_F_VERSION_1) | BIT_ULL(VIRTIO_F_ACCESS_PLATFORM) |
BIT_ULL(VIRTIO_F_NOTIFICATION_DATA) | BIT_ULL(VIRTIO_F_RING_PACKED));
goto vdpa_dev_put;
}
if (name)
ret = dev_set_name(&vdpa_dev->dev, "%s", name);
else
ret = dev_set_name(&vdpa_dev->dev, "vdpa%u", vdpa_dev->index);
ret = _vdpa_register_device(&oct_vdpa->vdpa, oct_hw->nr_vring);
if (ret) {
dev_err(&pdev->dev, "Failed to register to vDPA bus");
goto vdpa_dev_put;
}
atomic_set(&mgmt_dev->status, OCTEP_VDPA_DEV_STATUS_ADDED);
return 0;
vdpa_dev_put:
put_device(&oct_vdpa->vdpa.dev);
return ret;
}
static void octep_vdpa_dev_del(struct vdpa_mgmt_dev *mdev, struct vdpa_device *vdpa_dev)
{
struct octep_vdpa_mgmt_dev *mgmt_dev = container_of(mdev, struct octep_vdpa_mgmt_dev, mdev);
_vdpa_unregister_device(vdpa_dev);
atomic_set(&mgmt_dev->status, OCTEP_VDPA_DEV_STATUS_REMOVED);
}
static const struct vdpa_mgmtdev_ops octep_vdpa_mgmt_dev_ops = {
.dev_add = octep_vdpa_dev_add,
.dev_del = octep_vdpa_dev_del
};
static bool get_device_ready_status(u8 __iomem *addr)
{
u32 signature = readl(addr + OCTEP_VF_MBOX_DATA(0));
if (signature == OCTEP_DEV_READY_SIGNATURE) {
writel(0, addr + OCTEP_VF_MBOX_DATA(0));
return true;
}
return false;
}
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_NET, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static void octep_event_work(struct work_struct *work)
{
struct octep_vdpa_event_wk *wk = container_of(work, struct octep_vdpa_event_wk, work);
struct octep_vdpa_mgmt_dev *mgmt_dev = (struct octep_vdpa_mgmt_dev *)wk->ctxptr;
u8 __iomem *addr = mgmt_dev->oct_hw.base[OCTEP_HW_MBOX_BAR];
u8 event = readb(addr + OCTEP_VF_EVENT_REG(0));
struct vdpa_dev_set_config config = {0};
char name[OCTEP_VDPA_NAME_BUFSIZE];
int ret = 0;
switch (event) {
case OCTEP_VDPA_DEV_ADD_EVENT:
if (atomic_read(&mgmt_dev->status) != OCTEP_VDPA_DEV_STATUS_ADDED) {
snprintf(name, sizeof(name), "%s-%x", "vdpa", mgmt_dev->pdev->devfn);
ret = octep_vdpa_dev_add(&mgmt_dev->mdev, name, &config);
}
break;
case OCTEP_VDPA_DEV_DEL_EVENT:
if (atomic_read(&mgmt_dev->status) == OCTEP_VDPA_DEV_STATUS_ADDED)
octep_vdpa_dev_del(&mgmt_dev->mdev, &mgmt_dev->oct_vdpa->vdpa);
break;
default:
break;
}
event = ret ? OCTEP_VDPA_DEV_EVENT_NACK : OCTEP_VDPA_DEV_EVENT_ACK;
writeb(event, addr + OCTEP_VF_EVENT_REG(0));
writeb(OCTEP_VDPA_DEV_EVENT_DONE, addr + OCTEP_VF_EVENT_STATE(0));
}
static void octep_vdpa_setup_task(struct work_struct *work)
{
struct octep_vdpa_mgmt_dev *mgmt_dev = container_of(work, struct octep_vdpa_mgmt_dev,
setup_task);
struct pci_dev *pdev = mgmt_dev->pdev;
struct device *dev = &pdev->dev;
struct octep_hw *oct_hw;
unsigned long timeout;
u64 val;
int ret;
oct_hw = &mgmt_dev->oct_hw;
atomic_set(&mgmt_dev->status, OCTEP_VDPA_DEV_STATUS_WAIT_FOR_BAR_INIT);
/* Wait for a maximum of 5 sec */
timeout = jiffies + msecs_to_jiffies(5000);
while (!time_after(jiffies, timeout)) {
if (get_device_ready_status(oct_hw->base[OCTEP_HW_MBOX_BAR])) {
atomic_set(&mgmt_dev->status, OCTEP_VDPA_DEV_STATUS_INIT);
break;
}
if (atomic_read(&mgmt_dev->status) >= OCTEP_VDPA_DEV_STATUS_READY) {
dev_info(dev, "Stopping vDPA setup task.\n");
return;
}
usleep_range(1000, 1500);
}
if (atomic_read(&mgmt_dev->status) != OCTEP_VDPA_DEV_STATUS_INIT) {
dev_err(dev, "BAR initialization is timed out\n");
return;
}
ret = octep_iomap_region(pdev, oct_hw->base, OCTEP_HW_CAPS_BAR);
if (ret)
return;
val = readq(oct_hw->base[OCTEP_HW_MBOX_BAR] + OCTEP_VF_IN_CTRL(0));
oct_hw->nb_irqs = OCTEP_VF_IN_CTRL_RPVF(val);
if (!oct_hw->nb_irqs || oct_hw->nb_irqs > OCTEP_MAX_CB_INTR) {
dev_err(dev, "Invalid number of interrupts %d\n", oct_hw->nb_irqs);
goto unmap_region;
}
ret = octep_hw_caps_read(oct_hw, pdev);
if (ret < 0)
goto unmap_region;
mgmt_dev->mdev.ops = &octep_vdpa_mgmt_dev_ops;
mgmt_dev->mdev.id_table = id_table;
mgmt_dev->mdev.max_supported_vqs = oct_hw->nr_vring;
mgmt_dev->mdev.supported_features = oct_hw->features;
mgmt_dev->mdev.config_attr_mask = (1 << VDPA_ATTR_DEV_FEATURES);
mgmt_dev->mdev.device = dev;
ret = vdpa_mgmtdev_register(&mgmt_dev->mdev);
if (ret) {
dev_err(dev, "Failed to register vdpa management interface\n");
goto unmap_region;
}
atomic_set(&mgmt_dev->status, OCTEP_VDPA_DEV_STATUS_READY);
INIT_WORK(&mgmt_dev->event_wk.work, octep_event_work);
mgmt_dev->event_wk.ctxptr = mgmt_dev;
octep_request_irqs(&mgmt_dev->oct_hw, octep_vdpa_dev_event_handler, 1);
return;
unmap_region:
octep_iounmap_region(pdev, oct_hw->base, OCTEP_HW_CAPS_BAR);
oct_hw->base[OCTEP_HW_CAPS_BAR] = NULL;
}
static int octep_vdpa_probe_vf(struct pci_dev *pdev)
{
struct octep_vdpa_mgmt_dev *mgmt_dev;
struct device *dev = &pdev->dev;
int ret;
ret = pcim_enable_device(pdev);
if (ret) {
dev_err(dev, "Failed to enable device\n");
return ret;
}
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret) {
dev_err(dev, "No usable DMA configuration\n");
return ret;
}
pci_set_master(pdev);
mgmt_dev = devm_kzalloc(dev, sizeof(struct octep_vdpa_mgmt_dev), GFP_KERNEL);
if (!mgmt_dev)
return -ENOMEM;
ret = octep_iomap_region(pdev, mgmt_dev->oct_hw.base, OCTEP_HW_MBOX_BAR);
if (ret)
return ret;
mgmt_dev->pdev = pdev;
pci_set_drvdata(pdev, mgmt_dev);
atomic_set(&mgmt_dev->status, OCTEP_VDPA_DEV_STATUS_ALLOC);
INIT_WORK(&mgmt_dev->setup_task, octep_vdpa_setup_task);
schedule_work(&mgmt_dev->setup_task);
dev_info(&pdev->dev, "octep vdpa mgmt device setup task is queued\n");
return 0;
}
static void octep_vdpa_assign_barspace(struct pci_dev *vf_dev, struct pci_dev *pf_dev, u8 idx)
{
struct resource *vf_res = vf_dev->resource + PCI_STD_RESOURCES + 4;
struct resource *pf_res = pf_dev->resource + PCI_STD_RESOURCES + 4;
struct octep_pf *pf = pci_get_drvdata(pf_dev);
struct pci_bus_region bus_region;
vf_res->name = pci_name(vf_dev);
vf_res->flags = pf_res->flags;
vf_res->parent = (pf_dev->resource + PCI_STD_RESOURCES)->parent;
bus_region.start = pf->vf_base + idx * pf->vf_stride;
bus_region.end = bus_region.start + pf->vf_stride - 1;
pcibios_bus_to_resource(vf_dev->bus, vf_res, &bus_region);
}
static int octep_sriov_enable(struct pci_dev *pdev, int num_vfs)
{
struct octep_pf *pf = pci_get_drvdata(pdev);
u8 __iomem *addr = pf->base[OCTEP_HW_MBOX_BAR];
struct pci_dev *vf_pdev = NULL;
bool done = false;
int index = 0;
int ret, i;
u8 rpvf;
u64 val;
ret = pci_enable_sriov(pdev, num_vfs);
if (ret)
return ret;
pf->enabled_vfs = num_vfs;
while ((vf_pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM, PCI_ANY_ID, vf_pdev))) {
if (vf_pdev->device != pf->vf_devid)
continue;
octep_vdpa_assign_barspace(vf_pdev, pdev, index);
if (++index == num_vfs) {
done = true;
pci_dev_put(vf_pdev);
break;
}
}
val = readq(addr + OCTEP_EPF_RINFO(0));
rpvf = FIELD_GET(GENMASK_ULL(35, 32), val);
if (done) {
for (i = 0; i < pf->enabled_vfs; i++)
writel(OCTEP_DEV_READY_SIGNATURE, addr + OCTEP_PF_MBOX_DATA(i * rpvf));
}
return num_vfs;
}
static int octep_sriov_disable(struct pci_dev *pdev)
{
struct octep_pf *pf = pci_get_drvdata(pdev);
if (!pci_num_vf(pdev))
return 0;
pci_disable_sriov(pdev);
pf->enabled_vfs = 0;
return 0;
}
static int octep_vdpa_sriov_configure(struct pci_dev *pdev, int num_vfs)
{
if (num_vfs > 0)
return octep_sriov_enable(pdev, num_vfs);
else
return octep_sriov_disable(pdev);
}
static u16 octep_get_vf_devid(struct pci_dev *pdev)
{
u16 did;
switch (pdev->device) {
case OCTEP_VDPA_DEVID_CN106K_PF:
did = OCTEP_VDPA_DEVID_CN106K_VF;
break;
case OCTEP_VDPA_DEVID_CN105K_PF:
did = OCTEP_VDPA_DEVID_CN105K_VF;
break;
case OCTEP_VDPA_DEVID_CN103K_PF:
did = OCTEP_VDPA_DEVID_CN103K_VF;
break;
default:
did = 0xFFFF;
break;
}
return did;
}
static int octep_vdpa_pf_setup(struct octep_pf *octpf)
{
u8 __iomem *addr = octpf->base[OCTEP_HW_MBOX_BAR];
struct pci_dev *pdev = octpf->pdev;
int totalvfs;
size_t len;
u64 val;
totalvfs = pci_sriov_get_totalvfs(pdev);
if (unlikely(!totalvfs)) {
dev_info(&pdev->dev, "Total VFs are %d in PF sriov configuration\n", totalvfs);
return 0;
}
addr = octpf->base[OCTEP_HW_MBOX_BAR];
val = readq(addr + OCTEP_EPF_RINFO(0));
if (val == 0) {
dev_err(&pdev->dev, "Invalid device configuration\n");
return -EINVAL;
}
len = pci_resource_len(pdev, OCTEP_HW_CAPS_BAR);
octpf->vf_stride = len / totalvfs;
octpf->vf_devid = octep_get_vf_devid(pdev);
octep_vdpa_pf_bar_shrink(octpf);
return 0;
}
static int octep_vdpa_probe_pf(struct pci_dev *pdev)
{
struct device *dev = &pdev->dev;
struct octep_pf *octpf;
int ret;
ret = pci_enable_device(pdev);
if (ret) {
dev_err(dev, "Failed to enable device\n");
return ret;
}
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret) {
dev_err(dev, "No usable DMA configuration\n");
goto disable_pci;
}
octpf = devm_kzalloc(dev, sizeof(*octpf), GFP_KERNEL);
if (!octpf) {
ret = -ENOMEM;
goto disable_pci;
}
ret = octep_iomap_region(pdev, octpf->base, OCTEP_HW_MBOX_BAR);
if (ret)
goto disable_pci;
pci_set_master(pdev);
pci_set_drvdata(pdev, octpf);
octpf->pdev = pdev;
ret = octep_vdpa_pf_setup(octpf);
if (ret)
goto unmap_region;
return 0;
unmap_region:
octep_iounmap_region(pdev, octpf->base, OCTEP_HW_MBOX_BAR);
disable_pci:
pci_disable_device(pdev);
return ret;
}
static int octep_vdpa_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
if (pdev->is_virtfn)
return octep_vdpa_probe_vf(pdev);
else
return octep_vdpa_probe_pf(pdev);
}
static struct pci_device_id octep_pci_vdpa_map[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, OCTEP_VDPA_DEVID_CN106K_PF) },
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, OCTEP_VDPA_DEVID_CN106K_VF) },
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, OCTEP_VDPA_DEVID_CN105K_PF) },
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, OCTEP_VDPA_DEVID_CN105K_VF) },
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, OCTEP_VDPA_DEVID_CN103K_PF) },
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, OCTEP_VDPA_DEVID_CN103K_VF) },
{ 0 },
};
static struct pci_driver octep_pci_vdpa = {
.name = OCTEP_VDPA_DRIVER_NAME,
.id_table = octep_pci_vdpa_map,
.probe = octep_vdpa_probe,
.remove = octep_vdpa_remove,
.sriov_configure = octep_vdpa_sriov_configure
};
module_pci_driver(octep_pci_vdpa);
MODULE_AUTHOR("Marvell");
MODULE_DESCRIPTION("Marvell Octeon PCIe endpoint vDPA driver");
MODULE_LICENSE("GPL");
+9
View File
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
# Copyright(c) 2023 Advanced Micro Devices, Inc
obj-$(CONFIG_PDS_VDPA) := pds_vdpa.o
pds_vdpa-y := aux_drv.o \
cmds.o \
debugfs.o \
vdpa_dev.o
+140
View File
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/auxiliary_bus.h>
#include <linux/pci.h>
#include <linux/vdpa.h>
#include <linux/virtio_pci_modern.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_core_if.h>
#include <linux/pds/pds_adminq.h>
#include <linux/pds/pds_auxbus.h>
#include "aux_drv.h"
#include "debugfs.h"
#include "vdpa_dev.h"
static const struct auxiliary_device_id pds_vdpa_id_table[] = {
{ .name = PDS_VDPA_DEV_NAME, },
{},
};
static int pds_vdpa_device_id_check(struct pci_dev *pdev)
{
if (pdev->device != PCI_DEVICE_ID_PENSANDO_VDPA_VF ||
pdev->vendor != PCI_VENDOR_ID_PENSANDO)
return -ENODEV;
return PCI_DEVICE_ID_PENSANDO_VDPA_VF;
}
static int pds_vdpa_probe(struct auxiliary_device *aux_dev,
const struct auxiliary_device_id *id)
{
struct pds_auxiliary_dev *padev =
container_of(aux_dev, struct pds_auxiliary_dev, aux_dev);
struct device *dev = &aux_dev->dev;
struct pds_vdpa_aux *vdpa_aux;
int err;
vdpa_aux = kzalloc_obj(*vdpa_aux);
if (!vdpa_aux)
return -ENOMEM;
vdpa_aux->padev = padev;
vdpa_aux->vf_id = pci_iov_vf_id(padev->vf_pdev);
auxiliary_set_drvdata(aux_dev, vdpa_aux);
/* Get device ident info and set up the vdpa_mgmt_dev */
err = pds_vdpa_get_mgmt_info(vdpa_aux);
if (err)
goto err_free_mem;
/* Find the virtio configuration */
vdpa_aux->vd_mdev.pci_dev = padev->vf_pdev;
vdpa_aux->vd_mdev.device_id_check = pds_vdpa_device_id_check;
vdpa_aux->vd_mdev.dma_mask = DMA_BIT_MASK(PDS_CORE_ADDR_LEN);
err = vp_modern_probe(&vdpa_aux->vd_mdev);
if (err) {
dev_err(dev, "Unable to probe for virtio configuration: %pe\n",
ERR_PTR(err));
goto err_free_mgmt_info;
}
/* Let vdpa know that we can provide devices */
err = vdpa_mgmtdev_register(&vdpa_aux->vdpa_mdev);
if (err) {
dev_err(dev, "%s: Failed to initialize vdpa_mgmt interface: %pe\n",
__func__, ERR_PTR(err));
goto err_free_virtio;
}
pds_vdpa_debugfs_add_pcidev(vdpa_aux);
pds_vdpa_debugfs_add_ident(vdpa_aux);
return 0;
err_free_virtio:
vp_modern_remove(&vdpa_aux->vd_mdev);
err_free_mgmt_info:
pci_free_irq_vectors(padev->vf_pdev);
err_free_mem:
kfree(vdpa_aux);
auxiliary_set_drvdata(aux_dev, NULL);
return err;
}
static void pds_vdpa_remove(struct auxiliary_device *aux_dev)
{
struct pds_vdpa_aux *vdpa_aux = auxiliary_get_drvdata(aux_dev);
struct device *dev = &aux_dev->dev;
vdpa_mgmtdev_unregister(&vdpa_aux->vdpa_mdev);
pds_vdpa_release_irqs(vdpa_aux->pdsv);
vp_modern_remove(&vdpa_aux->vd_mdev);
pds_vdpa_debugfs_del_vdpadev(vdpa_aux);
kfree(vdpa_aux);
auxiliary_set_drvdata(aux_dev, NULL);
dev_info(dev, "Removed\n");
}
static struct auxiliary_driver pds_vdpa_driver = {
.name = PDS_DEV_TYPE_VDPA_STR,
.probe = pds_vdpa_probe,
.remove = pds_vdpa_remove,
.id_table = pds_vdpa_id_table,
};
static void __exit pds_vdpa_cleanup(void)
{
auxiliary_driver_unregister(&pds_vdpa_driver);
pds_vdpa_debugfs_destroy();
}
module_exit(pds_vdpa_cleanup);
static int __init pds_vdpa_init(void)
{
int err;
pds_vdpa_debugfs_create();
err = auxiliary_driver_register(&pds_vdpa_driver);
if (err) {
pr_err("%s: aux driver register failed: %pe\n",
PDS_VDPA_DRV_NAME, ERR_PTR(err));
pds_vdpa_debugfs_destroy();
}
return err;
}
module_init(pds_vdpa_init);
MODULE_DESCRIPTION(PDS_VDPA_DRV_DESCRIPTION);
MODULE_AUTHOR("Advanced Micro Devices, Inc");
MODULE_LICENSE("GPL");
+26
View File
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#ifndef _AUX_DRV_H_
#define _AUX_DRV_H_
#include <linux/virtio_pci_modern.h>
#define PDS_VDPA_DRV_DESCRIPTION "AMD/Pensando vDPA VF Device Driver"
#define PDS_VDPA_DRV_NAME KBUILD_MODNAME
struct pds_vdpa_aux {
struct pds_auxiliary_dev *padev;
struct vdpa_mgmt_dev vdpa_mdev;
struct pds_vdpa_device *pdsv;
struct pds_vdpa_ident ident;
int vf_id;
struct dentry *dentry;
struct virtio_pci_modern_device vd_mdev;
int nintrs;
};
#endif /* _AUX_DRV_H_ */
+185
View File
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/vdpa.h>
#include <linux/virtio_pci_modern.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_core_if.h>
#include <linux/pds/pds_adminq.h>
#include <linux/pds/pds_auxbus.h>
#include "vdpa_dev.h"
#include "aux_drv.h"
#include "cmds.h"
int pds_vdpa_init_hw(struct pds_vdpa_device *pdsv)
{
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
union pds_core_adminq_cmd cmd = {
.vdpa_init.opcode = PDS_VDPA_CMD_INIT,
.vdpa_init.vdpa_index = pdsv->vdpa_index,
.vdpa_init.vf_id = cpu_to_le16(pdsv->vdpa_aux->vf_id),
};
union pds_core_adminq_comp comp = {};
int err;
/* Initialize the vdpa/virtio device */
err = pds_client_adminq_cmd(padev, &cmd, sizeof(cmd.vdpa_init),
&comp, 0);
if (err)
dev_dbg(dev, "Failed to init hw, status %d: %pe\n",
comp.status, ERR_PTR(err));
return err;
}
int pds_vdpa_cmd_reset(struct pds_vdpa_device *pdsv)
{
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
union pds_core_adminq_cmd cmd = {
.vdpa.opcode = PDS_VDPA_CMD_RESET,
.vdpa.vdpa_index = pdsv->vdpa_index,
.vdpa.vf_id = cpu_to_le16(pdsv->vdpa_aux->vf_id),
};
union pds_core_adminq_comp comp = {};
int err;
err = pds_client_adminq_cmd(padev, &cmd, sizeof(cmd.vdpa), &comp, 0);
if (err)
dev_dbg(dev, "Failed to reset hw, status %d: %pe\n",
comp.status, ERR_PTR(err));
return err;
}
int pds_vdpa_cmd_set_status(struct pds_vdpa_device *pdsv, u8 status)
{
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
union pds_core_adminq_cmd cmd = {
.vdpa_status.opcode = PDS_VDPA_CMD_STATUS_UPDATE,
.vdpa_status.vdpa_index = pdsv->vdpa_index,
.vdpa_status.vf_id = cpu_to_le16(pdsv->vdpa_aux->vf_id),
.vdpa_status.status = status,
};
union pds_core_adminq_comp comp = {};
int err;
err = pds_client_adminq_cmd(padev, &cmd, sizeof(cmd.vdpa_status), &comp, 0);
if (err)
dev_dbg(dev, "Failed to set status to %#x, error status %d: %pe\n",
status, comp.status, ERR_PTR(err));
return err;
}
int pds_vdpa_cmd_set_mac(struct pds_vdpa_device *pdsv, u8 *mac)
{
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
union pds_core_adminq_cmd cmd = {
.vdpa_setattr.opcode = PDS_VDPA_CMD_SET_ATTR,
.vdpa_setattr.vdpa_index = pdsv->vdpa_index,
.vdpa_setattr.vf_id = cpu_to_le16(pdsv->vdpa_aux->vf_id),
.vdpa_setattr.attr = PDS_VDPA_ATTR_MAC,
};
union pds_core_adminq_comp comp = {};
int err;
ether_addr_copy(cmd.vdpa_setattr.mac, mac);
err = pds_client_adminq_cmd(padev, &cmd, sizeof(cmd.vdpa_setattr),
&comp, 0);
if (err)
dev_dbg(dev, "Failed to set mac address %pM, status %d: %pe\n",
mac, comp.status, ERR_PTR(err));
return err;
}
int pds_vdpa_cmd_set_max_vq_pairs(struct pds_vdpa_device *pdsv, u16 max_vqp)
{
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
union pds_core_adminq_cmd cmd = {
.vdpa_setattr.opcode = PDS_VDPA_CMD_SET_ATTR,
.vdpa_setattr.vdpa_index = pdsv->vdpa_index,
.vdpa_setattr.vf_id = cpu_to_le16(pdsv->vdpa_aux->vf_id),
.vdpa_setattr.attr = PDS_VDPA_ATTR_MAX_VQ_PAIRS,
.vdpa_setattr.max_vq_pairs = cpu_to_le16(max_vqp),
};
union pds_core_adminq_comp comp = {};
int err;
err = pds_client_adminq_cmd(padev, &cmd, sizeof(cmd.vdpa_setattr),
&comp, 0);
if (err)
dev_dbg(dev, "Failed to set max vq pairs %u, status %d: %pe\n",
max_vqp, comp.status, ERR_PTR(err));
return err;
}
int pds_vdpa_cmd_init_vq(struct pds_vdpa_device *pdsv, u16 qid, u16 invert_idx,
struct pds_vdpa_vq_info *vq_info)
{
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
union pds_core_adminq_cmd cmd = {
.vdpa_vq_init.opcode = PDS_VDPA_CMD_VQ_INIT,
.vdpa_vq_init.vdpa_index = pdsv->vdpa_index,
.vdpa_vq_init.vf_id = cpu_to_le16(pdsv->vdpa_aux->vf_id),
.vdpa_vq_init.qid = cpu_to_le16(qid),
.vdpa_vq_init.len = cpu_to_le16(ilog2(vq_info->q_len)),
.vdpa_vq_init.desc_addr = cpu_to_le64(vq_info->desc_addr),
.vdpa_vq_init.avail_addr = cpu_to_le64(vq_info->avail_addr),
.vdpa_vq_init.used_addr = cpu_to_le64(vq_info->used_addr),
.vdpa_vq_init.intr_index = cpu_to_le16(qid),
.vdpa_vq_init.avail_index = cpu_to_le16(vq_info->avail_idx ^ invert_idx),
.vdpa_vq_init.used_index = cpu_to_le16(vq_info->used_idx ^ invert_idx),
};
union pds_core_adminq_comp comp = {};
int err;
dev_dbg(dev, "%s: qid %d len %d desc_addr %#llx avail_addr %#llx used_addr %#llx\n",
__func__, qid, ilog2(vq_info->q_len),
vq_info->desc_addr, vq_info->avail_addr, vq_info->used_addr);
err = pds_client_adminq_cmd(padev, &cmd, sizeof(cmd.vdpa_vq_init),
&comp, 0);
if (err)
dev_dbg(dev, "Failed to init vq %d, status %d: %pe\n",
qid, comp.status, ERR_PTR(err));
return err;
}
int pds_vdpa_cmd_reset_vq(struct pds_vdpa_device *pdsv, u16 qid, u16 invert_idx,
struct pds_vdpa_vq_info *vq_info)
{
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
union pds_core_adminq_cmd cmd = {
.vdpa_vq_reset.opcode = PDS_VDPA_CMD_VQ_RESET,
.vdpa_vq_reset.vdpa_index = pdsv->vdpa_index,
.vdpa_vq_reset.vf_id = cpu_to_le16(pdsv->vdpa_aux->vf_id),
.vdpa_vq_reset.qid = cpu_to_le16(qid),
};
union pds_core_adminq_comp comp = {};
int err;
err = pds_client_adminq_cmd(padev, &cmd, sizeof(cmd.vdpa_vq_reset),
&comp, 0);
if (err) {
dev_dbg(dev, "Failed to reset vq %d, status %d: %pe\n",
qid, comp.status, ERR_PTR(err));
return err;
}
vq_info->avail_idx = le16_to_cpu(comp.vdpa_vq_reset.avail_index) ^ invert_idx;
vq_info->used_idx = le16_to_cpu(comp.vdpa_vq_reset.used_index) ^ invert_idx;
return 0;
}
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#ifndef _VDPA_CMDS_H_
#define _VDPA_CMDS_H_
int pds_vdpa_init_hw(struct pds_vdpa_device *pdsv);
int pds_vdpa_cmd_reset(struct pds_vdpa_device *pdsv);
int pds_vdpa_cmd_set_status(struct pds_vdpa_device *pdsv, u8 status);
int pds_vdpa_cmd_set_mac(struct pds_vdpa_device *pdsv, u8 *mac);
int pds_vdpa_cmd_set_max_vq_pairs(struct pds_vdpa_device *pdsv, u16 max_vqp);
int pds_vdpa_cmd_init_vq(struct pds_vdpa_device *pdsv, u16 qid, u16 invert_idx,
struct pds_vdpa_vq_info *vq_info);
int pds_vdpa_cmd_reset_vq(struct pds_vdpa_device *pdsv, u16 qid, u16 invert_idx,
struct pds_vdpa_vq_info *vq_info);
#endif /* _VDPA_CMDS_H_ */
+286
View File
@@ -0,0 +1,286 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/pci.h>
#include <linux/vdpa.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_core_if.h>
#include <linux/pds/pds_adminq.h>
#include <linux/pds/pds_auxbus.h>
#include "aux_drv.h"
#include "vdpa_dev.h"
#include "debugfs.h"
static struct dentry *dbfs_dir;
void pds_vdpa_debugfs_create(void)
{
dbfs_dir = debugfs_create_dir(PDS_VDPA_DRV_NAME, NULL);
}
void pds_vdpa_debugfs_destroy(void)
{
debugfs_remove_recursive(dbfs_dir);
dbfs_dir = NULL;
}
#define PRINT_SBIT_NAME(__seq, __f, __name) \
do { \
if ((__f) & (__name)) \
seq_printf(__seq, " %s", &#__name[16]); \
} while (0)
static void print_status_bits(struct seq_file *seq, u8 status)
{
seq_puts(seq, "status:");
PRINT_SBIT_NAME(seq, status, VIRTIO_CONFIG_S_ACKNOWLEDGE);
PRINT_SBIT_NAME(seq, status, VIRTIO_CONFIG_S_DRIVER);
PRINT_SBIT_NAME(seq, status, VIRTIO_CONFIG_S_DRIVER_OK);
PRINT_SBIT_NAME(seq, status, VIRTIO_CONFIG_S_FEATURES_OK);
PRINT_SBIT_NAME(seq, status, VIRTIO_CONFIG_S_NEEDS_RESET);
PRINT_SBIT_NAME(seq, status, VIRTIO_CONFIG_S_FAILED);
seq_puts(seq, "\n");
}
static void print_feature_bits_all(struct seq_file *seq, u64 features)
{
int i;
seq_puts(seq, "features:");
for (i = 0; i < (sizeof(u64) * 8); i++) {
u64 mask = BIT_ULL(i);
switch (features & mask) {
case BIT_ULL(VIRTIO_NET_F_CSUM):
seq_puts(seq, " VIRTIO_NET_F_CSUM");
break;
case BIT_ULL(VIRTIO_NET_F_GUEST_CSUM):
seq_puts(seq, " VIRTIO_NET_F_GUEST_CSUM");
break;
case BIT_ULL(VIRTIO_NET_F_CTRL_GUEST_OFFLOADS):
seq_puts(seq, " VIRTIO_NET_F_CTRL_GUEST_OFFLOADS");
break;
case BIT_ULL(VIRTIO_NET_F_MTU):
seq_puts(seq, " VIRTIO_NET_F_MTU");
break;
case BIT_ULL(VIRTIO_NET_F_MAC):
seq_puts(seq, " VIRTIO_NET_F_MAC");
break;
case BIT_ULL(VIRTIO_NET_F_GUEST_TSO4):
seq_puts(seq, " VIRTIO_NET_F_GUEST_TSO4");
break;
case BIT_ULL(VIRTIO_NET_F_GUEST_TSO6):
seq_puts(seq, " VIRTIO_NET_F_GUEST_TSO6");
break;
case BIT_ULL(VIRTIO_NET_F_GUEST_ECN):
seq_puts(seq, " VIRTIO_NET_F_GUEST_ECN");
break;
case BIT_ULL(VIRTIO_NET_F_GUEST_UFO):
seq_puts(seq, " VIRTIO_NET_F_GUEST_UFO");
break;
case BIT_ULL(VIRTIO_NET_F_HOST_TSO4):
seq_puts(seq, " VIRTIO_NET_F_HOST_TSO4");
break;
case BIT_ULL(VIRTIO_NET_F_HOST_TSO6):
seq_puts(seq, " VIRTIO_NET_F_HOST_TSO6");
break;
case BIT_ULL(VIRTIO_NET_F_HOST_ECN):
seq_puts(seq, " VIRTIO_NET_F_HOST_ECN");
break;
case BIT_ULL(VIRTIO_NET_F_HOST_UFO):
seq_puts(seq, " VIRTIO_NET_F_HOST_UFO");
break;
case BIT_ULL(VIRTIO_NET_F_MRG_RXBUF):
seq_puts(seq, " VIRTIO_NET_F_MRG_RXBUF");
break;
case BIT_ULL(VIRTIO_NET_F_STATUS):
seq_puts(seq, " VIRTIO_NET_F_STATUS");
break;
case BIT_ULL(VIRTIO_NET_F_CTRL_VQ):
seq_puts(seq, " VIRTIO_NET_F_CTRL_VQ");
break;
case BIT_ULL(VIRTIO_NET_F_CTRL_RX):
seq_puts(seq, " VIRTIO_NET_F_CTRL_RX");
break;
case BIT_ULL(VIRTIO_NET_F_CTRL_VLAN):
seq_puts(seq, " VIRTIO_NET_F_CTRL_VLAN");
break;
case BIT_ULL(VIRTIO_NET_F_CTRL_RX_EXTRA):
seq_puts(seq, " VIRTIO_NET_F_CTRL_RX_EXTRA");
break;
case BIT_ULL(VIRTIO_NET_F_GUEST_ANNOUNCE):
seq_puts(seq, " VIRTIO_NET_F_GUEST_ANNOUNCE");
break;
case BIT_ULL(VIRTIO_NET_F_MQ):
seq_puts(seq, " VIRTIO_NET_F_MQ");
break;
case BIT_ULL(VIRTIO_NET_F_CTRL_MAC_ADDR):
seq_puts(seq, " VIRTIO_NET_F_CTRL_MAC_ADDR");
break;
case BIT_ULL(VIRTIO_NET_F_HASH_REPORT):
seq_puts(seq, " VIRTIO_NET_F_HASH_REPORT");
break;
case BIT_ULL(VIRTIO_NET_F_RSS):
seq_puts(seq, " VIRTIO_NET_F_RSS");
break;
case BIT_ULL(VIRTIO_NET_F_RSC_EXT):
seq_puts(seq, " VIRTIO_NET_F_RSC_EXT");
break;
case BIT_ULL(VIRTIO_NET_F_STANDBY):
seq_puts(seq, " VIRTIO_NET_F_STANDBY");
break;
case BIT_ULL(VIRTIO_NET_F_SPEED_DUPLEX):
seq_puts(seq, " VIRTIO_NET_F_SPEED_DUPLEX");
break;
case BIT_ULL(VIRTIO_F_NOTIFY_ON_EMPTY):
seq_puts(seq, " VIRTIO_F_NOTIFY_ON_EMPTY");
break;
case BIT_ULL(VIRTIO_F_ANY_LAYOUT):
seq_puts(seq, " VIRTIO_F_ANY_LAYOUT");
break;
case BIT_ULL(VIRTIO_F_VERSION_1):
seq_puts(seq, " VIRTIO_F_VERSION_1");
break;
case BIT_ULL(VIRTIO_F_ACCESS_PLATFORM):
seq_puts(seq, " VIRTIO_F_ACCESS_PLATFORM");
break;
case BIT_ULL(VIRTIO_F_RING_PACKED):
seq_puts(seq, " VIRTIO_F_RING_PACKED");
break;
case BIT_ULL(VIRTIO_F_ORDER_PLATFORM):
seq_puts(seq, " VIRTIO_F_ORDER_PLATFORM");
break;
case BIT_ULL(VIRTIO_F_SR_IOV):
seq_puts(seq, " VIRTIO_F_SR_IOV");
break;
case 0:
break;
default:
seq_printf(seq, " bit_%d", i);
break;
}
}
seq_puts(seq, "\n");
}
void pds_vdpa_debugfs_add_pcidev(struct pds_vdpa_aux *vdpa_aux)
{
vdpa_aux->dentry = debugfs_create_dir(pci_name(vdpa_aux->padev->vf_pdev), dbfs_dir);
}
static int identity_show(struct seq_file *seq, void *v)
{
struct pds_vdpa_aux *vdpa_aux = seq->private;
struct vdpa_mgmt_dev *mgmt;
u64 hw_features;
seq_printf(seq, "aux_dev: %s\n",
dev_name(&vdpa_aux->padev->aux_dev.dev));
mgmt = &vdpa_aux->vdpa_mdev;
seq_printf(seq, "max_vqs: %d\n", mgmt->max_supported_vqs);
seq_printf(seq, "config_attr_mask: %#llx\n", mgmt->config_attr_mask);
hw_features = le64_to_cpu(vdpa_aux->ident.hw_features);
seq_printf(seq, "hw_features: %#llx\n", hw_features);
print_feature_bits_all(seq, hw_features);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(identity);
void pds_vdpa_debugfs_add_ident(struct pds_vdpa_aux *vdpa_aux)
{
debugfs_create_file("identity", 0400, vdpa_aux->dentry,
vdpa_aux, &identity_fops);
}
static int config_show(struct seq_file *seq, void *v)
{
struct pds_vdpa_device *pdsv = seq->private;
struct virtio_net_config vc;
u8 status;
memcpy_fromio(&vc, pdsv->vdpa_aux->vd_mdev.device,
sizeof(struct virtio_net_config));
seq_printf(seq, "mac: %pM\n", vc.mac);
seq_printf(seq, "max_virtqueue_pairs: %d\n",
__virtio16_to_cpu(true, vc.max_virtqueue_pairs));
seq_printf(seq, "mtu: %d\n", __virtio16_to_cpu(true, vc.mtu));
seq_printf(seq, "speed: %d\n", le32_to_cpu(vc.speed));
seq_printf(seq, "duplex: %d\n", vc.duplex);
seq_printf(seq, "rss_max_key_size: %d\n", vc.rss_max_key_size);
seq_printf(seq, "rss_max_indirection_table_length: %d\n",
le16_to_cpu(vc.rss_max_indirection_table_length));
seq_printf(seq, "supported_hash_types: %#x\n",
le32_to_cpu(vc.supported_hash_types));
seq_printf(seq, "vn_status: %#x\n",
__virtio16_to_cpu(true, vc.status));
status = vp_modern_get_status(&pdsv->vdpa_aux->vd_mdev);
seq_printf(seq, "dev_status: %#x\n", status);
print_status_bits(seq, status);
seq_printf(seq, "negotiated_features: %#llx\n", pdsv->negotiated_features);
print_feature_bits_all(seq, pdsv->negotiated_features);
seq_printf(seq, "vdpa_index: %d\n", pdsv->vdpa_index);
seq_printf(seq, "num_vqs: %d\n", pdsv->num_vqs);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(config);
static int vq_show(struct seq_file *seq, void *v)
{
struct pds_vdpa_vq_info *vq = seq->private;
seq_printf(seq, "ready: %d\n", vq->ready);
seq_printf(seq, "desc_addr: %#llx\n", vq->desc_addr);
seq_printf(seq, "avail_addr: %#llx\n", vq->avail_addr);
seq_printf(seq, "used_addr: %#llx\n", vq->used_addr);
seq_printf(seq, "q_len: %d\n", vq->q_len);
seq_printf(seq, "qid: %d\n", vq->qid);
seq_printf(seq, "doorbell: %#llx\n", vq->doorbell);
seq_printf(seq, "avail_idx: %d\n", vq->avail_idx);
seq_printf(seq, "used_idx: %d\n", vq->used_idx);
seq_printf(seq, "irq: %d\n", vq->irq);
seq_printf(seq, "irq-name: %s\n", vq->irq_name);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(vq);
void pds_vdpa_debugfs_add_vdpadev(struct pds_vdpa_aux *vdpa_aux)
{
int i;
debugfs_create_file("config", 0400, vdpa_aux->dentry, vdpa_aux->pdsv, &config_fops);
for (i = 0; i < vdpa_aux->pdsv->num_vqs; i++) {
char name[16];
snprintf(name, sizeof(name), "vq%02d", i);
debugfs_create_file(name, 0400, vdpa_aux->dentry,
&vdpa_aux->pdsv->vqs[i], &vq_fops);
}
}
void pds_vdpa_debugfs_del_vdpadev(struct pds_vdpa_aux *vdpa_aux)
{
debugfs_remove_recursive(vdpa_aux->dentry);
vdpa_aux->dentry = NULL;
}
void pds_vdpa_debugfs_reset_vdpadev(struct pds_vdpa_aux *vdpa_aux)
{
/* we don't keep track of the entries, so remove it all
* then rebuild the basics
*/
pds_vdpa_debugfs_del_vdpadev(vdpa_aux);
pds_vdpa_debugfs_add_pcidev(vdpa_aux);
pds_vdpa_debugfs_add_ident(vdpa_aux);
}
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#ifndef _PDS_VDPA_DEBUGFS_H_
#define _PDS_VDPA_DEBUGFS_H_
#include <linux/debugfs.h>
void pds_vdpa_debugfs_create(void);
void pds_vdpa_debugfs_destroy(void);
void pds_vdpa_debugfs_add_pcidev(struct pds_vdpa_aux *vdpa_aux);
void pds_vdpa_debugfs_add_ident(struct pds_vdpa_aux *vdpa_aux);
void pds_vdpa_debugfs_add_vdpadev(struct pds_vdpa_aux *vdpa_aux);
void pds_vdpa_debugfs_del_vdpadev(struct pds_vdpa_aux *vdpa_aux);
void pds_vdpa_debugfs_reset_vdpadev(struct pds_vdpa_aux *vdpa_aux);
#endif /* _PDS_VDPA_DEBUGFS_H_ */
+859
View File
@@ -0,0 +1,859 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#include <linux/pci.h>
#include <linux/vdpa.h>
#include <uapi/linux/vdpa.h>
#include <linux/virtio_pci_modern.h>
#include <linux/pds/pds_common.h>
#include <linux/pds/pds_core_if.h>
#include <linux/pds/pds_adminq.h>
#include <linux/pds/pds_auxbus.h>
#include "vdpa_dev.h"
#include "aux_drv.h"
#include "cmds.h"
#include "debugfs.h"
static u64 pds_vdpa_get_driver_features(struct vdpa_device *vdpa_dev);
static struct pds_vdpa_device *vdpa_to_pdsv(struct vdpa_device *vdpa_dev)
{
return container_of(vdpa_dev, struct pds_vdpa_device, vdpa_dev);
}
static int pds_vdpa_notify_handler(struct notifier_block *nb,
unsigned long ecode,
void *data)
{
struct pds_vdpa_device *pdsv = container_of(nb, struct pds_vdpa_device, nb);
struct device *dev = &pdsv->vdpa_aux->padev->aux_dev.dev;
dev_dbg(dev, "%s: event code %lu\n", __func__, ecode);
if (ecode == PDS_EVENT_RESET || ecode == PDS_EVENT_LINK_CHANGE) {
if (pdsv->config_cb.callback)
pdsv->config_cb.callback(pdsv->config_cb.private);
}
return 0;
}
static int pds_vdpa_register_event_handler(struct pds_vdpa_device *pdsv)
{
struct device *dev = &pdsv->vdpa_aux->padev->aux_dev.dev;
struct notifier_block *nb = &pdsv->nb;
int err;
if (!nb->notifier_call) {
nb->notifier_call = pds_vdpa_notify_handler;
err = pdsc_register_notify(nb);
if (err) {
nb->notifier_call = NULL;
dev_err(dev, "failed to register pds event handler: %pe\n",
ERR_PTR(err));
return -EINVAL;
}
dev_dbg(dev, "pds event handler registered\n");
}
return 0;
}
static void pds_vdpa_unregister_event_handler(struct pds_vdpa_device *pdsv)
{
if (pdsv->nb.notifier_call) {
pdsc_unregister_notify(&pdsv->nb);
pdsv->nb.notifier_call = NULL;
}
}
static int pds_vdpa_set_vq_address(struct vdpa_device *vdpa_dev, u16 qid,
u64 desc_addr, u64 driver_addr, u64 device_addr)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
pdsv->vqs[qid].desc_addr = desc_addr;
pdsv->vqs[qid].avail_addr = driver_addr;
pdsv->vqs[qid].used_addr = device_addr;
return 0;
}
static void pds_vdpa_set_vq_num(struct vdpa_device *vdpa_dev, u16 qid, u32 num)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
pdsv->vqs[qid].q_len = num;
}
static void pds_vdpa_kick_vq(struct vdpa_device *vdpa_dev, u16 qid)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
iowrite16(qid, pdsv->vqs[qid].notify);
}
static void pds_vdpa_set_vq_cb(struct vdpa_device *vdpa_dev, u16 qid,
struct vdpa_callback *cb)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
pdsv->vqs[qid].event_cb = *cb;
}
static irqreturn_t pds_vdpa_isr(int irq, void *data)
{
struct pds_vdpa_vq_info *vq;
vq = data;
if (vq->event_cb.callback)
vq->event_cb.callback(vq->event_cb.private);
return IRQ_HANDLED;
}
static void pds_vdpa_release_irq(struct pds_vdpa_device *pdsv, int qid)
{
if (pdsv->vqs[qid].irq == VIRTIO_MSI_NO_VECTOR)
return;
free_irq(pdsv->vqs[qid].irq, &pdsv->vqs[qid]);
pdsv->vqs[qid].irq = VIRTIO_MSI_NO_VECTOR;
}
static void pds_vdpa_set_vq_ready(struct vdpa_device *vdpa_dev, u16 qid, bool ready)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
struct device *dev = &pdsv->vdpa_dev.dev;
u64 driver_features;
u16 invert_idx = 0;
int err;
dev_dbg(dev, "%s: qid %d ready %d => %d\n",
__func__, qid, pdsv->vqs[qid].ready, ready);
if (ready == pdsv->vqs[qid].ready)
return;
driver_features = pds_vdpa_get_driver_features(vdpa_dev);
if (driver_features & BIT_ULL(VIRTIO_F_RING_PACKED))
invert_idx = PDS_VDPA_PACKED_INVERT_IDX;
if (ready) {
/* Pass vq setup info to DSC using adminq to gather up and
* send all info at once so FW can do its full set up in
* one easy operation
*/
err = pds_vdpa_cmd_init_vq(pdsv, qid, invert_idx, &pdsv->vqs[qid]);
if (err) {
dev_err(dev, "Failed to init vq %d: %pe\n",
qid, ERR_PTR(err));
ready = false;
}
} else {
err = pds_vdpa_cmd_reset_vq(pdsv, qid, invert_idx, &pdsv->vqs[qid]);
if (err)
dev_err(dev, "%s: reset_vq failed qid %d: %pe\n",
__func__, qid, ERR_PTR(err));
}
pdsv->vqs[qid].ready = ready;
}
static bool pds_vdpa_get_vq_ready(struct vdpa_device *vdpa_dev, u16 qid)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
return pdsv->vqs[qid].ready;
}
static int pds_vdpa_set_vq_state(struct vdpa_device *vdpa_dev, u16 qid,
const struct vdpa_vq_state *state)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
u64 driver_features;
u16 avail;
u16 used;
if (pdsv->vqs[qid].ready) {
dev_err(dev, "Setting device position is denied while vq is enabled\n");
return -EINVAL;
}
driver_features = pds_vdpa_get_driver_features(vdpa_dev);
if (driver_features & BIT_ULL(VIRTIO_F_RING_PACKED)) {
avail = state->packed.last_avail_idx |
(state->packed.last_avail_counter << 15);
used = state->packed.last_used_idx |
(state->packed.last_used_counter << 15);
/* The avail and used index are stored with the packed wrap
* counter bit inverted. This way, in case set_vq_state is
* not called, the initial value can be set to zero prior to
* feature negotiation, and it is good for both packed and
* split vq.
*/
avail ^= PDS_VDPA_PACKED_INVERT_IDX;
used ^= PDS_VDPA_PACKED_INVERT_IDX;
} else {
avail = state->split.avail_index;
/* state->split does not provide a used_index:
* the vq will be set to "empty" here, and the vq will read
* the current used index the next time the vq is kicked.
*/
used = avail;
}
if (used != avail) {
dev_dbg(dev, "Setting used equal to avail, for interoperability\n");
used = avail;
}
pdsv->vqs[qid].avail_idx = avail;
pdsv->vqs[qid].used_idx = used;
return 0;
}
static int pds_vdpa_get_vq_state(struct vdpa_device *vdpa_dev, u16 qid,
struct vdpa_vq_state *state)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
struct pds_auxiliary_dev *padev = pdsv->vdpa_aux->padev;
struct device *dev = &padev->aux_dev.dev;
u64 driver_features;
u16 avail;
u16 used;
if (pdsv->vqs[qid].ready) {
dev_err(dev, "Getting device position is denied while vq is enabled\n");
return -EINVAL;
}
avail = pdsv->vqs[qid].avail_idx;
used = pdsv->vqs[qid].used_idx;
driver_features = pds_vdpa_get_driver_features(vdpa_dev);
if (driver_features & BIT_ULL(VIRTIO_F_RING_PACKED)) {
avail ^= PDS_VDPA_PACKED_INVERT_IDX;
used ^= PDS_VDPA_PACKED_INVERT_IDX;
state->packed.last_avail_idx = avail & 0x7fff;
state->packed.last_avail_counter = avail >> 15;
state->packed.last_used_idx = used & 0x7fff;
state->packed.last_used_counter = used >> 15;
} else {
state->split.avail_index = avail;
/* state->split does not provide a used_index. */
}
return 0;
}
static struct vdpa_notification_area
pds_vdpa_get_vq_notification(struct vdpa_device *vdpa_dev, u16 qid)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
struct virtio_pci_modern_device *vd_mdev;
struct vdpa_notification_area area;
area.addr = pdsv->vqs[qid].notify_pa;
vd_mdev = &pdsv->vdpa_aux->vd_mdev;
if (!vd_mdev->notify_offset_multiplier)
area.size = PDS_PAGE_SIZE;
else
area.size = vd_mdev->notify_offset_multiplier;
return area;
}
static int pds_vdpa_get_vq_irq(struct vdpa_device *vdpa_dev, u16 qid)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
return pdsv->vqs[qid].irq;
}
static u32 pds_vdpa_get_vq_align(struct vdpa_device *vdpa_dev)
{
return PDS_PAGE_SIZE;
}
static u32 pds_vdpa_get_vq_group(struct vdpa_device *vdpa_dev, u16 idx)
{
return 0;
}
static u64 pds_vdpa_get_device_features(struct vdpa_device *vdpa_dev)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
return pdsv->supported_features;
}
static int pds_vdpa_set_driver_features(struct vdpa_device *vdpa_dev, u64 features)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
struct device *dev = &pdsv->vdpa_dev.dev;
u64 driver_features;
u64 nego_features;
u64 hw_features;
u64 missing;
if (!(features & BIT_ULL(VIRTIO_F_ACCESS_PLATFORM)) && features) {
dev_err(dev, "VIRTIO_F_ACCESS_PLATFORM is not negotiated\n");
return -EOPNOTSUPP;
}
/* Check for valid feature bits */
nego_features = features & pdsv->supported_features;
missing = features & ~nego_features;
if (missing) {
dev_err(dev, "Can't support all requested features in %#llx, missing %#llx features\n",
features, missing);
return -EOPNOTSUPP;
}
driver_features = pds_vdpa_get_driver_features(vdpa_dev);
pdsv->negotiated_features = nego_features;
dev_dbg(dev, "%s: %#llx => %#llx\n",
__func__, driver_features, nego_features);
/* if we're faking the F_MAC, strip it before writing to device */
hw_features = le64_to_cpu(pdsv->vdpa_aux->ident.hw_features);
if (!(hw_features & BIT_ULL(VIRTIO_NET_F_MAC)))
nego_features &= ~BIT_ULL(VIRTIO_NET_F_MAC);
if (driver_features == nego_features)
return 0;
vp_modern_set_features(&pdsv->vdpa_aux->vd_mdev, nego_features);
return 0;
}
static u64 pds_vdpa_get_driver_features(struct vdpa_device *vdpa_dev)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
return pdsv->negotiated_features;
}
static void pds_vdpa_set_config_cb(struct vdpa_device *vdpa_dev,
struct vdpa_callback *cb)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
pdsv->config_cb.callback = cb->callback;
pdsv->config_cb.private = cb->private;
}
static u16 pds_vdpa_get_vq_num_max(struct vdpa_device *vdpa_dev)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
/* qemu has assert() that vq_num_max <= VIRTQUEUE_MAX_SIZE (1024) */
return min_t(u16, 1024, BIT(le16_to_cpu(pdsv->vdpa_aux->ident.max_qlen)));
}
static u32 pds_vdpa_get_device_id(struct vdpa_device *vdpa_dev)
{
return VIRTIO_ID_NET;
}
static u32 pds_vdpa_get_vendor_id(struct vdpa_device *vdpa_dev)
{
return PCI_VENDOR_ID_PENSANDO;
}
static u8 pds_vdpa_get_status(struct vdpa_device *vdpa_dev)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
return vp_modern_get_status(&pdsv->vdpa_aux->vd_mdev);
}
static int pds_vdpa_request_irqs(struct pds_vdpa_device *pdsv)
{
struct pci_dev *pdev = pdsv->vdpa_aux->padev->vf_pdev;
struct pds_vdpa_aux *vdpa_aux = pdsv->vdpa_aux;
struct device *dev = &pdsv->vdpa_dev.dev;
int max_vq, nintrs, qid, err;
max_vq = vdpa_aux->vdpa_mdev.max_supported_vqs;
nintrs = pci_alloc_irq_vectors(pdev, max_vq, max_vq, PCI_IRQ_MSIX);
if (nintrs < 0) {
dev_err(dev, "Couldn't get %d msix vectors: %pe\n",
max_vq, ERR_PTR(nintrs));
return nintrs;
}
for (qid = 0; qid < pdsv->num_vqs; ++qid) {
int irq = pci_irq_vector(pdev, qid);
snprintf(pdsv->vqs[qid].irq_name, sizeof(pdsv->vqs[qid].irq_name),
"vdpa-%s-%d", dev_name(dev), qid);
err = request_irq(irq, pds_vdpa_isr, 0,
pdsv->vqs[qid].irq_name,
&pdsv->vqs[qid]);
if (err) {
dev_err(dev, "%s: no irq for qid %d: %pe\n",
__func__, qid, ERR_PTR(err));
goto err_release;
}
pdsv->vqs[qid].irq = irq;
}
vdpa_aux->nintrs = nintrs;
return 0;
err_release:
while (qid--)
pds_vdpa_release_irq(pdsv, qid);
pci_free_irq_vectors(pdev);
vdpa_aux->nintrs = 0;
return err;
}
void pds_vdpa_release_irqs(struct pds_vdpa_device *pdsv)
{
struct pds_vdpa_aux *vdpa_aux;
struct pci_dev *pdev;
int qid;
if (!pdsv)
return;
pdev = pdsv->vdpa_aux->padev->vf_pdev;
vdpa_aux = pdsv->vdpa_aux;
if (!vdpa_aux->nintrs)
return;
for (qid = 0; qid < pdsv->num_vqs; qid++)
pds_vdpa_release_irq(pdsv, qid);
pci_free_irq_vectors(pdev);
vdpa_aux->nintrs = 0;
}
static void pds_vdpa_set_status(struct vdpa_device *vdpa_dev, u8 status)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
struct device *dev = &pdsv->vdpa_dev.dev;
u8 old_status;
int i;
old_status = pds_vdpa_get_status(vdpa_dev);
dev_dbg(dev, "%s: old %#x new %#x\n", __func__, old_status, status);
if (status & ~old_status & VIRTIO_CONFIG_S_DRIVER_OK) {
if (pds_vdpa_request_irqs(pdsv))
status = old_status | VIRTIO_CONFIG_S_FAILED;
}
pds_vdpa_cmd_set_status(pdsv, status);
if (status == 0) {
struct vdpa_callback null_cb = { };
pds_vdpa_set_config_cb(vdpa_dev, &null_cb);
pds_vdpa_cmd_reset(pdsv);
for (i = 0; i < pdsv->num_vqs; i++) {
pdsv->vqs[i].avail_idx = 0;
pdsv->vqs[i].used_idx = 0;
}
pds_vdpa_cmd_set_mac(pdsv, pdsv->mac);
}
if (status & ~old_status & VIRTIO_CONFIG_S_FEATURES_OK) {
for (i = 0; i < pdsv->num_vqs; i++) {
pdsv->vqs[i].notify =
vp_modern_map_vq_notify(&pdsv->vdpa_aux->vd_mdev,
i, &pdsv->vqs[i].notify_pa);
}
}
if (old_status & ~status & VIRTIO_CONFIG_S_DRIVER_OK)
pds_vdpa_release_irqs(pdsv);
}
static void pds_vdpa_init_vqs_entry(struct pds_vdpa_device *pdsv, int qid,
void __iomem *notify)
{
memset(&pdsv->vqs[qid], 0, sizeof(pdsv->vqs[0]));
pdsv->vqs[qid].qid = qid;
pdsv->vqs[qid].pdsv = pdsv;
pdsv->vqs[qid].ready = false;
pdsv->vqs[qid].irq = VIRTIO_MSI_NO_VECTOR;
pdsv->vqs[qid].notify = notify;
}
static int pds_vdpa_reset(struct vdpa_device *vdpa_dev)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
struct device *dev;
int err = 0;
u8 status;
int i;
dev = &pdsv->vdpa_aux->padev->aux_dev.dev;
status = pds_vdpa_get_status(vdpa_dev);
if (status == 0)
return 0;
if (status & VIRTIO_CONFIG_S_DRIVER_OK) {
/* Reset the vqs */
for (i = 0; i < pdsv->num_vqs && !err; i++) {
err = pds_vdpa_cmd_reset_vq(pdsv, i, 0, &pdsv->vqs[i]);
if (err)
dev_err(dev, "%s: reset_vq failed qid %d: %pe\n",
__func__, i, ERR_PTR(err));
}
}
pds_vdpa_set_status(vdpa_dev, 0);
if (status & VIRTIO_CONFIG_S_DRIVER_OK) {
/* Reset the vq info */
for (i = 0; i < pdsv->num_vqs && !err; i++)
pds_vdpa_init_vqs_entry(pdsv, i, pdsv->vqs[i].notify);
}
return 0;
}
static size_t pds_vdpa_get_config_size(struct vdpa_device *vdpa_dev)
{
return sizeof(struct virtio_net_config);
}
static void pds_vdpa_get_config(struct vdpa_device *vdpa_dev,
unsigned int offset,
void *buf, unsigned int len)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
void __iomem *device;
if (offset + len > sizeof(struct virtio_net_config)) {
WARN(true, "%s: bad read, offset %d len %d\n", __func__, offset, len);
return;
}
device = pdsv->vdpa_aux->vd_mdev.device;
memcpy_fromio(buf, device + offset, len);
}
static void pds_vdpa_set_config(struct vdpa_device *vdpa_dev,
unsigned int offset, const void *buf,
unsigned int len)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
void __iomem *device;
if (offset + len > sizeof(struct virtio_net_config)) {
WARN(true, "%s: bad read, offset %d len %d\n", __func__, offset, len);
return;
}
device = pdsv->vdpa_aux->vd_mdev.device;
memcpy_toio(device + offset, buf, len);
}
static const struct vdpa_config_ops pds_vdpa_ops = {
.set_vq_address = pds_vdpa_set_vq_address,
.set_vq_num = pds_vdpa_set_vq_num,
.kick_vq = pds_vdpa_kick_vq,
.set_vq_cb = pds_vdpa_set_vq_cb,
.set_vq_ready = pds_vdpa_set_vq_ready,
.get_vq_ready = pds_vdpa_get_vq_ready,
.set_vq_state = pds_vdpa_set_vq_state,
.get_vq_state = pds_vdpa_get_vq_state,
.get_vq_notification = pds_vdpa_get_vq_notification,
.get_vq_irq = pds_vdpa_get_vq_irq,
.get_vq_align = pds_vdpa_get_vq_align,
.get_vq_group = pds_vdpa_get_vq_group,
.get_device_features = pds_vdpa_get_device_features,
.set_driver_features = pds_vdpa_set_driver_features,
.get_driver_features = pds_vdpa_get_driver_features,
.set_config_cb = pds_vdpa_set_config_cb,
.get_vq_num_max = pds_vdpa_get_vq_num_max,
.get_device_id = pds_vdpa_get_device_id,
.get_vendor_id = pds_vdpa_get_vendor_id,
.get_status = pds_vdpa_get_status,
.set_status = pds_vdpa_set_status,
.reset = pds_vdpa_reset,
.get_config_size = pds_vdpa_get_config_size,
.get_config = pds_vdpa_get_config,
.set_config = pds_vdpa_set_config,
};
static struct virtio_device_id pds_vdpa_id_table[] = {
{VIRTIO_ID_NET, VIRTIO_DEV_ANY_ID},
{0},
};
static int pds_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name,
const struct vdpa_dev_set_config *add_config)
{
struct pds_vdpa_aux *vdpa_aux;
struct pds_vdpa_device *pdsv;
struct vdpa_mgmt_dev *mgmt;
u16 fw_max_vqs, vq_pairs;
struct device *dma_dev;
struct pci_dev *pdev;
struct device *dev;
u8 status;
int err;
int i;
vdpa_aux = container_of(mdev, struct pds_vdpa_aux, vdpa_mdev);
dev = &vdpa_aux->padev->aux_dev.dev;
mgmt = &vdpa_aux->vdpa_mdev;
if (vdpa_aux->pdsv) {
dev_warn(dev, "Multiple vDPA devices on a VF is not supported.\n");
return -EOPNOTSUPP;
}
pdsv = vdpa_alloc_device(struct pds_vdpa_device, vdpa_dev,
dev, &pds_vdpa_ops, NULL,
1, 1, name, false);
if (IS_ERR(pdsv)) {
dev_err(dev, "Failed to allocate vDPA structure: %pe\n", pdsv);
return PTR_ERR(pdsv);
}
vdpa_aux->pdsv = pdsv;
pdsv->vdpa_aux = vdpa_aux;
pdev = vdpa_aux->padev->vf_pdev;
dma_dev = &pdev->dev;
pdsv->vdpa_dev.vmap.dma_dev = dma_dev;
status = pds_vdpa_get_status(&pdsv->vdpa_dev);
if (status == 0xff) {
dev_err(dev, "Broken PCI - status %#x\n", status);
err = -ENXIO;
goto err_unmap;
}
pdsv->supported_features = mgmt->supported_features;
if (add_config->mask & BIT_ULL(VDPA_ATTR_DEV_FEATURES)) {
u64 unsupp_features =
add_config->device_features & ~pdsv->supported_features;
if (unsupp_features) {
dev_err(dev, "Unsupported features: %#llx\n", unsupp_features);
err = -EOPNOTSUPP;
goto err_unmap;
}
pdsv->supported_features = add_config->device_features;
}
err = pds_vdpa_cmd_reset(pdsv);
if (err) {
dev_err(dev, "Failed to reset hw: %pe\n", ERR_PTR(err));
goto err_unmap;
}
err = pds_vdpa_init_hw(pdsv);
if (err) {
dev_err(dev, "Failed to init hw: %pe\n", ERR_PTR(err));
goto err_unmap;
}
fw_max_vqs = le16_to_cpu(pdsv->vdpa_aux->ident.max_vqs);
vq_pairs = fw_max_vqs / 2;
/* Make sure we have the queues being requested */
if (add_config->mask & (1 << VDPA_ATTR_DEV_NET_CFG_MAX_VQP))
vq_pairs = add_config->net.max_vq_pairs;
pdsv->num_vqs = 2 * vq_pairs;
if (pdsv->supported_features & BIT_ULL(VIRTIO_NET_F_CTRL_VQ))
pdsv->num_vqs++;
if (pdsv->num_vqs > fw_max_vqs) {
dev_err(dev, "%s: queue count requested %u greater than max %u\n",
__func__, pdsv->num_vqs, fw_max_vqs);
err = -ENOSPC;
goto err_unmap;
}
if (pdsv->num_vqs != fw_max_vqs) {
err = pds_vdpa_cmd_set_max_vq_pairs(pdsv, vq_pairs);
if (err) {
dev_err(dev, "Failed to set max_vq_pairs: %pe\n",
ERR_PTR(err));
goto err_unmap;
}
}
/* Set a mac, either from the user config if provided
* or use the device's mac if not 00:..:00
* or set a random mac
*/
if (add_config->mask & BIT_ULL(VDPA_ATTR_DEV_NET_CFG_MACADDR)) {
ether_addr_copy(pdsv->mac, add_config->net.mac);
} else {
struct virtio_net_config __iomem *vc;
vc = pdsv->vdpa_aux->vd_mdev.device;
memcpy_fromio(pdsv->mac, vc->mac, sizeof(pdsv->mac));
if (is_zero_ether_addr(pdsv->mac) &&
(pdsv->supported_features & BIT_ULL(VIRTIO_NET_F_MAC))) {
eth_random_addr(pdsv->mac);
dev_info(dev, "setting random mac %pM\n", pdsv->mac);
}
}
pds_vdpa_cmd_set_mac(pdsv, pdsv->mac);
for (i = 0; i < pdsv->num_vqs; i++) {
void __iomem *notify;
notify = vp_modern_map_vq_notify(&pdsv->vdpa_aux->vd_mdev,
i, &pdsv->vqs[i].notify_pa);
pds_vdpa_init_vqs_entry(pdsv, i, notify);
}
pdsv->vdpa_dev.mdev = &vdpa_aux->vdpa_mdev;
err = pds_vdpa_register_event_handler(pdsv);
if (err) {
dev_err(dev, "Failed to register for PDS events: %pe\n", ERR_PTR(err));
goto err_unmap;
}
/* We use the _vdpa_register_device() call rather than the
* vdpa_register_device() to avoid a deadlock because our
* dev_add() is called with the vdpa_dev_lock already set
* by vdpa_nl_cmd_dev_add_set_doit()
*/
err = _vdpa_register_device(&pdsv->vdpa_dev, pdsv->num_vqs);
if (err) {
dev_err(dev, "Failed to register to vDPA bus: %pe\n", ERR_PTR(err));
goto err_unevent;
}
pds_vdpa_debugfs_add_vdpadev(vdpa_aux);
return 0;
err_unevent:
pds_vdpa_unregister_event_handler(pdsv);
err_unmap:
put_device(&pdsv->vdpa_dev.dev);
vdpa_aux->pdsv = NULL;
return err;
}
static void pds_vdpa_dev_del(struct vdpa_mgmt_dev *mdev,
struct vdpa_device *vdpa_dev)
{
struct pds_vdpa_device *pdsv = vdpa_to_pdsv(vdpa_dev);
struct pds_vdpa_aux *vdpa_aux;
pds_vdpa_unregister_event_handler(pdsv);
vdpa_aux = container_of(mdev, struct pds_vdpa_aux, vdpa_mdev);
_vdpa_unregister_device(vdpa_dev);
pds_vdpa_cmd_reset(vdpa_aux->pdsv);
pds_vdpa_debugfs_reset_vdpadev(vdpa_aux);
vdpa_aux->pdsv = NULL;
dev_info(&vdpa_aux->padev->aux_dev.dev, "Removed vdpa device\n");
}
static const struct vdpa_mgmtdev_ops pds_vdpa_mgmt_dev_ops = {
.dev_add = pds_vdpa_dev_add,
.dev_del = pds_vdpa_dev_del
};
int pds_vdpa_get_mgmt_info(struct pds_vdpa_aux *vdpa_aux)
{
union pds_core_adminq_cmd cmd = {
.vdpa_ident.opcode = PDS_VDPA_CMD_IDENT,
.vdpa_ident.vf_id = cpu_to_le16(vdpa_aux->vf_id),
};
union pds_core_adminq_comp comp = {};
struct vdpa_mgmt_dev *mgmt;
struct pci_dev *pf_pdev;
struct device *pf_dev;
struct pci_dev *pdev;
dma_addr_t ident_pa;
struct device *dev;
u16 dev_intrs;
u16 max_vqs;
int err;
dev = &vdpa_aux->padev->aux_dev.dev;
pdev = vdpa_aux->padev->vf_pdev;
mgmt = &vdpa_aux->vdpa_mdev;
/* Get resource info through the PF's adminq. It is a block of info,
* so we need to map some memory for PF to make available to the
* firmware for writing the data.
*/
pf_pdev = pci_physfn(vdpa_aux->padev->vf_pdev);
pf_dev = &pf_pdev->dev;
ident_pa = dma_map_single(pf_dev, &vdpa_aux->ident,
sizeof(vdpa_aux->ident), DMA_FROM_DEVICE);
if (dma_mapping_error(pf_dev, ident_pa)) {
dev_err(dev, "Failed to map ident space\n");
return -ENOMEM;
}
cmd.vdpa_ident.ident_pa = cpu_to_le64(ident_pa);
cmd.vdpa_ident.len = cpu_to_le32(sizeof(vdpa_aux->ident));
err = pds_client_adminq_cmd(vdpa_aux->padev, &cmd,
sizeof(cmd.vdpa_ident), &comp, 0);
dma_unmap_single(pf_dev, ident_pa,
sizeof(vdpa_aux->ident), DMA_FROM_DEVICE);
if (err) {
dev_err(dev, "Failed to ident hw, status %d: %pe\n",
comp.status, ERR_PTR(err));
return err;
}
max_vqs = le16_to_cpu(vdpa_aux->ident.max_vqs);
dev_intrs = pci_msix_vec_count(pdev);
dev_dbg(dev, "ident.max_vqs %d dev_intrs %d\n", max_vqs, dev_intrs);
max_vqs = min_t(u16, dev_intrs, max_vqs);
mgmt->max_supported_vqs = min_t(u16, PDS_VDPA_MAX_QUEUES, max_vqs);
vdpa_aux->nintrs = 0;
mgmt->ops = &pds_vdpa_mgmt_dev_ops;
mgmt->id_table = pds_vdpa_id_table;
mgmt->device = dev;
mgmt->supported_features = le64_to_cpu(vdpa_aux->ident.hw_features);
/* advertise F_MAC even if the device doesn't */
mgmt->supported_features |= BIT_ULL(VIRTIO_NET_F_MAC);
mgmt->config_attr_mask = BIT_ULL(VDPA_ATTR_DEV_NET_CFG_MACADDR);
mgmt->config_attr_mask |= BIT_ULL(VDPA_ATTR_DEV_NET_CFG_MAX_VQP);
mgmt->config_attr_mask |= BIT_ULL(VDPA_ATTR_DEV_FEATURES);
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2023 Advanced Micro Devices, Inc */
#ifndef _VDPA_DEV_H_
#define _VDPA_DEV_H_
#include <linux/pci.h>
#include <linux/vdpa.h>
struct pds_vdpa_vq_info {
bool ready;
u64 desc_addr;
u64 avail_addr;
u64 used_addr;
u32 q_len;
u16 qid;
int irq;
char irq_name[32];
void __iomem *notify;
dma_addr_t notify_pa;
u64 doorbell;
u16 avail_idx;
u16 used_idx;
struct vdpa_callback event_cb;
struct pds_vdpa_device *pdsv;
};
#define PDS_VDPA_MAX_QUEUES 65
#define PDS_VDPA_MAX_QLEN 32768
struct pds_vdpa_device {
struct vdpa_device vdpa_dev;
struct pds_vdpa_aux *vdpa_aux;
struct pds_vdpa_vq_info vqs[PDS_VDPA_MAX_QUEUES];
u64 supported_features; /* supported device features */
u64 negotiated_features; /* negotiated features */
u8 vdpa_index; /* rsvd for future subdevice use */
u8 num_vqs; /* num vqs in use */
u8 mac[ETH_ALEN]; /* mac selected when the device was added */
struct vdpa_callback config_cb;
struct notifier_block nb;
};
#define PDS_VDPA_PACKED_INVERT_IDX 0x8000
void pds_vdpa_release_irqs(struct pds_vdpa_device *pdsv);
int pds_vdpa_get_mgmt_info(struct pds_vdpa_aux *vdpa_aux);
#endif /* _VDPA_DEV_H_ */
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SNET_VDPA) += snet_vdpa.o
snet_vdpa-$(CONFIG_SNET_VDPA) += snet_main.o
snet_vdpa-$(CONFIG_SNET_VDPA) += snet_ctrl.o
ifdef CONFIG_HWMON
snet_vdpa-$(CONFIG_SNET_VDPA) += snet_hwmon.o
endif
@@ -0,0 +1,336 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SolidRun DPU driver for control plane
*
* Copyright (C) 2022-2023 SolidRun
*
* Author: Alvaro Karsz <alvaro.karsz@solid-run.com>
*
*/
#include <linux/iopoll.h>
#include "snet_vdpa.h"
enum snet_ctrl_opcodes {
SNET_CTRL_OP_DESTROY = 1,
SNET_CTRL_OP_READ_VQ_STATE,
SNET_CTRL_OP_SUSPEND,
SNET_CTRL_OP_RESUME,
};
#define SNET_CTRL_TIMEOUT 2000000
#define SNET_CTRL_DATA_SIZE_MASK 0x0000FFFF
#define SNET_CTRL_IN_PROCESS_MASK 0x00010000
#define SNET_CTRL_CHUNK_RDY_MASK 0x00020000
#define SNET_CTRL_ERROR_MASK 0x0FFC0000
#define SNET_VAL_TO_ERR(val) (-(((val) & SNET_CTRL_ERROR_MASK) >> 18))
#define SNET_EMPTY_CTRL(val) (((val) & SNET_CTRL_ERROR_MASK) || \
!((val) & SNET_CTRL_IN_PROCESS_MASK))
#define SNET_DATA_READY(val) ((val) & (SNET_CTRL_ERROR_MASK | SNET_CTRL_CHUNK_RDY_MASK))
/* Control register used to read data from the DPU */
struct snet_ctrl_reg_ctrl {
/* Chunk size in 4B words */
u16 data_size;
/* We are in the middle of a command */
u16 in_process:1;
/* A data chunk is ready and can be consumed */
u16 chunk_ready:1;
/* Error code */
u16 error:10;
/* Saved for future usage */
u16 rsvd:4;
};
/* Opcode register */
struct snet_ctrl_reg_op {
u16 opcode;
/* Only if VQ index is relevant for the command */
u16 vq_idx;
};
struct snet_ctrl_regs {
struct snet_ctrl_reg_op op;
struct snet_ctrl_reg_ctrl ctrl;
u32 rsvd;
u32 data[];
};
static struct snet_ctrl_regs __iomem *snet_get_ctrl(struct snet *snet)
{
return snet->bar + snet->psnet->cfg.ctrl_off;
}
static int snet_wait_for_empty_ctrl(struct snet_ctrl_regs __iomem *regs)
{
u32 val;
return readx_poll_timeout(ioread32, &regs->ctrl, val, SNET_EMPTY_CTRL(val), 10,
SNET_CTRL_TIMEOUT);
}
static int snet_wait_for_empty_op(struct snet_ctrl_regs __iomem *regs)
{
u32 val;
return readx_poll_timeout(ioread32, &regs->op, val, !val, 10, SNET_CTRL_TIMEOUT);
}
static int snet_wait_for_data(struct snet_ctrl_regs __iomem *regs)
{
u32 val;
return readx_poll_timeout(ioread32, &regs->ctrl, val, SNET_DATA_READY(val), 10,
SNET_CTRL_TIMEOUT);
}
static u32 snet_read32_word(struct snet_ctrl_regs __iomem *ctrl_regs, u16 word_idx)
{
return ioread32(&ctrl_regs->data[word_idx]);
}
static u32 snet_read_ctrl(struct snet_ctrl_regs __iomem *ctrl_regs)
{
return ioread32(&ctrl_regs->ctrl);
}
static void snet_write_ctrl(struct snet_ctrl_regs __iomem *ctrl_regs, u32 val)
{
iowrite32(val, &ctrl_regs->ctrl);
}
static void snet_write_op(struct snet_ctrl_regs __iomem *ctrl_regs, u32 val)
{
iowrite32(val, &ctrl_regs->op);
}
static int snet_wait_for_dpu_completion(struct snet_ctrl_regs __iomem *ctrl_regs)
{
/* Wait until the DPU finishes completely.
* It will clear the opcode register.
*/
return snet_wait_for_empty_op(ctrl_regs);
}
/* Reading ctrl from the DPU:
* buf_size must be 4B aligned
*
* Steps:
*
* (1) Verify that the DPU is not in the middle of another operation by
* reading the in_process and error bits in the control register.
* (2) Write the request opcode and the VQ idx in the opcode register
* and write the buffer size in the control register.
* (3) Start readind chunks of data, chunk_ready bit indicates that a
* data chunk is available, we signal that we read the data by clearing the bit.
* (4) Detect that the transfer is completed when the in_process bit
* in the control register is cleared or when the an error appears.
*/
static int snet_ctrl_read_from_dpu(struct snet *snet, u16 opcode, u16 vq_idx, void *buffer,
u32 buf_size)
{
struct pci_dev *pdev = snet->pdev;
struct snet_ctrl_regs __iomem *regs = snet_get_ctrl(snet);
u32 *bfr_ptr = (u32 *)buffer;
u32 val;
u16 buf_words;
int ret;
u16 words, i, tot_words = 0;
/* Supported for config 2+ */
if (!SNET_CFG_VER(snet, 2))
return -EOPNOTSUPP;
if (!IS_ALIGNED(buf_size, 4))
return -EINVAL;
mutex_lock(&snet->ctrl_lock);
buf_words = buf_size / 4;
/* Make sure control register is empty */
ret = snet_wait_for_empty_ctrl(regs);
if (ret) {
SNET_WARN(pdev, "Timeout waiting for previous control data to be consumed\n");
goto exit;
}
/* We need to write the buffer size in the control register, and the opcode + vq index in
* the opcode register.
* We use a spinlock to serialize the writes.
*/
spin_lock(&snet->ctrl_spinlock);
snet_write_ctrl(regs, buf_words);
snet_write_op(regs, opcode | (vq_idx << 16));
spin_unlock(&snet->ctrl_spinlock);
while (buf_words != tot_words) {
ret = snet_wait_for_data(regs);
if (ret) {
SNET_WARN(pdev, "Timeout waiting for control data\n");
goto exit;
}
val = snet_read_ctrl(regs);
/* Error? */
if (val & SNET_CTRL_ERROR_MASK) {
ret = SNET_VAL_TO_ERR(val);
SNET_WARN(pdev, "Error while reading control data from DPU, err %d\n", ret);
goto exit;
}
words = min_t(u16, val & SNET_CTRL_DATA_SIZE_MASK, buf_words - tot_words);
for (i = 0; i < words; i++) {
*bfr_ptr = snet_read32_word(regs, i);
bfr_ptr++;
}
tot_words += words;
/* Is the job completed? */
if (!(val & SNET_CTRL_IN_PROCESS_MASK))
break;
/* Clear the chunk ready bit and continue */
val &= ~SNET_CTRL_CHUNK_RDY_MASK;
snet_write_ctrl(regs, val);
}
ret = snet_wait_for_dpu_completion(regs);
if (ret)
SNET_WARN(pdev, "Timeout waiting for the DPU to complete a control command\n");
exit:
mutex_unlock(&snet->ctrl_lock);
return ret;
}
/* Send a control message to the DPU using the old mechanism
* used with config version 1.
*/
static int snet_send_ctrl_msg_old(struct snet *snet, u32 opcode)
{
struct pci_dev *pdev = snet->pdev;
struct snet_ctrl_regs __iomem *regs = snet_get_ctrl(snet);
int ret;
mutex_lock(&snet->ctrl_lock);
/* Old mechanism uses just 1 register, the opcode register.
* Make sure that the opcode register is empty, and that the DPU isn't
* processing an old message.
*/
ret = snet_wait_for_empty_op(regs);
if (ret) {
SNET_WARN(pdev, "Timeout waiting for previous control message to be ACKed\n");
goto exit;
}
/* Write the message */
snet_write_op(regs, opcode);
/* DPU ACKs the message by clearing the opcode register */
ret = snet_wait_for_empty_op(regs);
if (ret)
SNET_WARN(pdev, "Timeout waiting for a control message to be ACKed\n");
exit:
mutex_unlock(&snet->ctrl_lock);
return ret;
}
/* Send a control message to the DPU.
* A control message is a message without payload.
*/
static int snet_send_ctrl_msg(struct snet *snet, u16 opcode, u16 vq_idx)
{
struct pci_dev *pdev = snet->pdev;
struct snet_ctrl_regs __iomem *regs = snet_get_ctrl(snet);
u32 val;
int ret;
/* If config version is not 2+, use the old mechanism */
if (!SNET_CFG_VER(snet, 2))
return snet_send_ctrl_msg_old(snet, opcode);
mutex_lock(&snet->ctrl_lock);
/* Make sure control register is empty */
ret = snet_wait_for_empty_ctrl(regs);
if (ret) {
SNET_WARN(pdev, "Timeout waiting for previous control data to be consumed\n");
goto exit;
}
/* We need to clear the control register and write the opcode + vq index in the opcode
* register.
* We use a spinlock to serialize the writes.
*/
spin_lock(&snet->ctrl_spinlock);
snet_write_ctrl(regs, 0);
snet_write_op(regs, opcode | (vq_idx << 16));
spin_unlock(&snet->ctrl_spinlock);
/* The DPU ACKs control messages by setting the chunk ready bit
* without data.
*/
ret = snet_wait_for_data(regs);
if (ret) {
SNET_WARN(pdev, "Timeout waiting for control message to be ACKed\n");
goto exit;
}
/* Check for errors */
val = snet_read_ctrl(regs);
ret = SNET_VAL_TO_ERR(val);
/* Clear the chunk ready bit */
val &= ~SNET_CTRL_CHUNK_RDY_MASK;
snet_write_ctrl(regs, val);
ret = snet_wait_for_dpu_completion(regs);
if (ret)
SNET_WARN(pdev, "Timeout waiting for DPU to complete a control command, err %d\n",
ret);
exit:
mutex_unlock(&snet->ctrl_lock);
return ret;
}
void snet_ctrl_clear(struct snet *snet)
{
struct snet_ctrl_regs __iomem *regs = snet_get_ctrl(snet);
snet_write_op(regs, 0);
}
int snet_destroy_dev(struct snet *snet)
{
return snet_send_ctrl_msg(snet, SNET_CTRL_OP_DESTROY, 0);
}
int snet_read_vq_state(struct snet *snet, u16 idx, struct vdpa_vq_state *state)
{
return snet_ctrl_read_from_dpu(snet, SNET_CTRL_OP_READ_VQ_STATE, idx, state,
sizeof(*state));
}
int snet_suspend_dev(struct snet *snet)
{
return snet_send_ctrl_msg(snet, SNET_CTRL_OP_SUSPEND, 0);
}
int snet_resume_dev(struct snet *snet)
{
return snet_send_ctrl_msg(snet, SNET_CTRL_OP_RESUME, 0);
}
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SolidRun DPU driver for control plane
*
* Copyright (C) 2022-2023 SolidRun
*
* Author: Alvaro Karsz <alvaro.karsz@solid-run.com>
*
*/
#include <linux/hwmon.h>
#include "snet_vdpa.h"
/* Monitor offsets */
#define SNET_MON_TMP0_IN_OFF 0x00
#define SNET_MON_TMP0_MAX_OFF 0x08
#define SNET_MON_TMP0_CRIT_OFF 0x10
#define SNET_MON_TMP1_IN_OFF 0x18
#define SNET_MON_TMP1_CRIT_OFF 0x20
#define SNET_MON_CURR_IN_OFF 0x28
#define SNET_MON_CURR_MAX_OFF 0x30
#define SNET_MON_CURR_CRIT_OFF 0x38
#define SNET_MON_PWR_IN_OFF 0x40
#define SNET_MON_VOLT_IN_OFF 0x48
#define SNET_MON_VOLT_CRIT_OFF 0x50
#define SNET_MON_VOLT_LCRIT_OFF 0x58
static void snet_hwmon_read_reg(struct psnet *psnet, u32 reg, long *out)
{
*out = psnet_read64(psnet, psnet->cfg.hwmon_off + reg);
}
static umode_t snet_howmon_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
return 0444;
}
static int snet_howmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct psnet *psnet = dev_get_drvdata(dev);
int ret = 0;
switch (type) {
case hwmon_in:
switch (attr) {
case hwmon_in_lcrit:
snet_hwmon_read_reg(psnet, SNET_MON_VOLT_LCRIT_OFF, val);
break;
case hwmon_in_crit:
snet_hwmon_read_reg(psnet, SNET_MON_VOLT_CRIT_OFF, val);
break;
case hwmon_in_input:
snet_hwmon_read_reg(psnet, SNET_MON_VOLT_IN_OFF, val);
break;
default:
ret = -EOPNOTSUPP;
break;
}
break;
case hwmon_power:
switch (attr) {
case hwmon_power_input:
snet_hwmon_read_reg(psnet, SNET_MON_PWR_IN_OFF, val);
break;
default:
ret = -EOPNOTSUPP;
break;
}
break;
case hwmon_curr:
switch (attr) {
case hwmon_curr_input:
snet_hwmon_read_reg(psnet, SNET_MON_CURR_IN_OFF, val);
break;
case hwmon_curr_max:
snet_hwmon_read_reg(psnet, SNET_MON_CURR_MAX_OFF, val);
break;
case hwmon_curr_crit:
snet_hwmon_read_reg(psnet, SNET_MON_CURR_CRIT_OFF, val);
break;
default:
ret = -EOPNOTSUPP;
break;
}
break;
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
if (channel == 0)
snet_hwmon_read_reg(psnet, SNET_MON_TMP0_IN_OFF, val);
else
snet_hwmon_read_reg(psnet, SNET_MON_TMP1_IN_OFF, val);
break;
case hwmon_temp_max:
if (channel == 0)
snet_hwmon_read_reg(psnet, SNET_MON_TMP0_MAX_OFF, val);
else
ret = -EOPNOTSUPP;
break;
case hwmon_temp_crit:
if (channel == 0)
snet_hwmon_read_reg(psnet, SNET_MON_TMP0_CRIT_OFF, val);
else
snet_hwmon_read_reg(psnet, SNET_MON_TMP1_CRIT_OFF, val);
break;
default:
ret = -EOPNOTSUPP;
break;
}
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static int snet_hwmon_read_string(struct device *dev,
enum hwmon_sensor_types type, u32 attr,
int channel, const char **str)
{
int ret = 0;
switch (type) {
case hwmon_in:
*str = "main_vin";
break;
case hwmon_power:
*str = "soc_pin";
break;
case hwmon_curr:
*str = "soc_iin";
break;
case hwmon_temp:
if (channel == 0)
*str = "power_stage_temp";
else
*str = "ic_junction_temp";
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static const struct hwmon_ops snet_hwmon_ops = {
.is_visible = snet_howmon_is_visible,
.read = snet_howmon_read,
.read_string = snet_hwmon_read_string
};
static const struct hwmon_channel_info * const snet_hwmon_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL),
HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL),
HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_CRIT | HWMON_C_LABEL),
HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_CRIT | HWMON_I_LCRIT | HWMON_I_LABEL),
NULL
};
static const struct hwmon_chip_info snet_hwmono_info = {
.ops = &snet_hwmon_ops,
.info = snet_hwmon_info,
};
/* Create an HW monitor device */
void psnet_create_hwmon(struct pci_dev *pdev)
{
struct device *hwmon;
struct psnet *psnet = pci_get_drvdata(pdev);
snprintf(psnet->hwmon_name, SNET_NAME_SIZE, "snet_%s", pci_name(pdev));
hwmon = devm_hwmon_device_register_with_info(&pdev->dev, psnet->hwmon_name, psnet,
&snet_hwmono_info, NULL);
/* The monitor is not mandatory, Just alert user in case of an error */
if (IS_ERR(hwmon))
SNET_WARN(pdev, "Failed to create SNET hwmon, error %ld\n", PTR_ERR(hwmon));
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,209 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* SolidRun DPU driver for control plane
*
* Copyright (C) 2022-2023 SolidRun
*
* Author: Alvaro Karsz <alvaro.karsz@solid-run.com>
*
*/
#ifndef _SNET_VDPA_H_
#define _SNET_VDPA_H_
#include <linux/vdpa.h>
#include <linux/pci.h>
#define SNET_NAME_SIZE 256
#define SNET_ERR(pdev, fmt, ...) dev_err(&(pdev)->dev, "%s"fmt, "snet_vdpa: ", ##__VA_ARGS__)
#define SNET_WARN(pdev, fmt, ...) dev_warn(&(pdev)->dev, "%s"fmt, "snet_vdpa: ", ##__VA_ARGS__)
#define SNET_INFO(pdev, fmt, ...) dev_info(&(pdev)->dev, "%s"fmt, "snet_vdpa: ", ##__VA_ARGS__)
#define SNET_DBG(pdev, fmt, ...) dev_dbg(&(pdev)->dev, "%s"fmt, "snet_vdpa: ", ##__VA_ARGS__)
#define SNET_HAS_FEATURE(s, f) ((s)->negotiated_features & BIT_ULL(f))
/* Check if negotiated config version is at least @ver */
#define SNET_CFG_VER(snet, ver) ((snet)->psnet->negotiated_cfg_ver >= (ver))
/* VQ struct */
struct snet_vq {
/* VQ callback */
struct vdpa_callback cb;
/* VQ state received from bus */
struct vdpa_vq_state vq_state;
/* desc base address */
u64 desc_area;
/* device base address */
u64 device_area;
/* driver base address */
u64 driver_area;
/* Queue size */
u32 num;
/* Serial ID for VQ */
u32 sid;
/* is ready flag */
bool ready;
/* IRQ number */
u32 irq;
/* IRQ index, DPU uses this to parse data from MSI-X table */
u32 irq_idx;
/* IRQ name */
char irq_name[SNET_NAME_SIZE];
/* pointer to mapped PCI BAR register used by this VQ to kick */
void __iomem *kick_ptr;
};
struct snet {
/* vdpa device */
struct vdpa_device vdpa;
/* Config callback */
struct vdpa_callback cb;
/* To lock the control mechanism */
struct mutex ctrl_lock;
/* Spinlock to protect critical parts in the control mechanism */
spinlock_t ctrl_spinlock;
/* array of virqueues */
struct snet_vq **vqs;
/* Used features */
u64 negotiated_features;
/* Device serial ID */
u32 sid;
/* device status */
u8 status;
/* boolean indicating if snet config was passed to the device */
bool dpu_ready;
/* IRQ number */
u32 cfg_irq;
/* IRQ index, DPU uses this to parse data from MSI-X table */
u32 cfg_irq_idx;
/* IRQ name */
char cfg_irq_name[SNET_NAME_SIZE];
/* BAR to access the VF */
void __iomem *bar;
/* PCI device */
struct pci_dev *pdev;
/* Pointer to snet pdev parent device */
struct psnet *psnet;
/* Pointer to snet config device */
struct snet_dev_cfg *cfg;
};
struct snet_dev_cfg {
/* Device ID following VirtIO spec. */
u32 virtio_id;
/* Number of VQs for this device */
u32 vq_num;
/* Size of every VQ */
u32 vq_size;
/* Virtual Function id */
u32 vfid;
/* Device features, following VirtIO spec */
u64 features;
/* Reserved for future usage */
u32 rsvd[6];
/* VirtIO device specific config size */
u32 cfg_size;
/* VirtIO device specific config address */
void __iomem *virtio_cfg;
} __packed;
struct snet_cfg {
/* Magic key */
u32 key;
/* Size of total config in bytes */
u32 cfg_size;
/* Config version */
u32 cfg_ver;
/* Number of Virtual Functions to create */
u32 vf_num;
/* BAR to use for the VFs */
u32 vf_bar;
/* Where should we write the SNET's config */
u32 host_cfg_off;
/* Max. allowed size for a SNET's config */
u32 max_size_host_cfg;
/* VirtIO config offset in BAR */
u32 virtio_cfg_off;
/* Offset in PCI BAR for VQ kicks */
u32 kick_off;
/* Offset in PCI BAR for HW monitoring */
u32 hwmon_off;
/* Offset in PCI BAR for Control mechanism */
u32 ctrl_off;
/* Config general flags - enum snet_cfg_flags */
u32 flags;
/* Reserved for future usage */
u32 rsvd[6];
/* Number of snet devices */
u32 devices_num;
/* The actual devices */
struct snet_dev_cfg **devs;
} __packed;
/* SolidNET PCIe device, one device per PCIe physical function */
struct psnet {
/* PCI BARs */
void __iomem *bars[PCI_STD_NUM_BARS];
/* Negotiated config version */
u32 negotiated_cfg_ver;
/* Next IRQ index to use in case when the IRQs are allocated from this device */
u32 next_irq;
/* BAR number used to communicate with the device */
u8 barno;
/* spinlock to protect data that can be changed by SNET devices */
spinlock_t lock;
/* Pointer to the device's config read from BAR */
struct snet_cfg cfg;
/* Name of monitor device */
char hwmon_name[SNET_NAME_SIZE];
};
enum snet_cfg_flags {
/* Create a HWMON device */
SNET_CFG_FLAG_HWMON = BIT(0),
/* USE IRQs from the physical function */
SNET_CFG_FLAG_IRQ_PF = BIT(1),
};
#define PSNET_FLAG_ON(p, f) ((p)->cfg.flags & (f))
static inline u32 psnet_read32(struct psnet *psnet, u32 off)
{
return ioread32(psnet->bars[psnet->barno] + off);
}
static inline u32 snet_read32(struct snet *snet, u32 off)
{
return ioread32(snet->bar + off);
}
static inline void snet_write32(struct snet *snet, u32 off, u32 val)
{
iowrite32(val, snet->bar + off);
}
static inline u64 psnet_read64(struct psnet *psnet, u32 off)
{
u64 val;
/* 64bits are written in 2 halves, low part first */
val = (u64)psnet_read32(psnet, off);
val |= ((u64)psnet_read32(psnet, off + 4) << 32);
return val;
}
static inline void snet_write64(struct snet *snet, u32 off, u64 val)
{
/* The DPU expects a 64bit integer in 2 halves, the low part first */
snet_write32(snet, off, (u32)val);
snet_write32(snet, off + 4, (u32)(val >> 32));
}
#if IS_ENABLED(CONFIG_HWMON)
void psnet_create_hwmon(struct pci_dev *pdev);
#endif
void snet_ctrl_clear(struct snet *snet);
int snet_destroy_dev(struct snet *snet);
int snet_read_vq_state(struct snet *snet, u16 idx, struct vdpa_vq_state *state);
int snet_suspend_dev(struct snet *snet);
int snet_resume_dev(struct snet *snet);
#endif //_SNET_VDPA_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VDPA_SIM) += vdpa_sim.o
obj-$(CONFIG_VDPA_SIM_NET) += vdpa_sim_net.o
obj-$(CONFIG_VDPA_SIM_BLOCK) += vdpa_sim_blk.o
@@ -0,0 +1,850 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VDPA device simulator core.
*
* Copyright (c) 2020, Red Hat Inc. All rights reserved.
* Author: Jason Wang <jasowang@redhat.com>
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/slab.h>
#include <linux/dma-map-ops.h>
#include <linux/vringh.h>
#include <linux/vdpa.h>
#include <linux/vhost_iotlb.h>
#include <uapi/linux/vdpa.h>
#include <uapi/linux/vhost_types.h>
#include "vdpa_sim.h"
#define DRV_VERSION "0.1"
#define DRV_AUTHOR "Jason Wang <jasowang@redhat.com>"
#define DRV_DESC "vDPA Device Simulator core"
#define DRV_LICENSE "GPL v2"
static int batch_mapping = 1;
module_param(batch_mapping, int, 0444);
MODULE_PARM_DESC(batch_mapping, "Batched mapping 1 -Enable; 0 - Disable");
static int max_iotlb_entries = 2048;
module_param(max_iotlb_entries, int, 0444);
MODULE_PARM_DESC(max_iotlb_entries,
"Maximum number of iotlb entries for each address space. (default: 2048)");
static bool use_va = true;
module_param(use_va, bool, 0444);
MODULE_PARM_DESC(use_va, "Enable/disable the device's ability to use VA");
#define VDPASIM_QUEUE_ALIGN PAGE_SIZE
#define VDPASIM_QUEUE_MAX 256
#define VDPASIM_VENDOR_ID 0
struct vdpasim_mm_work {
struct kthread_work work;
struct vdpasim *vdpasim;
struct mm_struct *mm_to_bind;
int ret;
};
static void vdpasim_mm_work_fn(struct kthread_work *work)
{
struct vdpasim_mm_work *mm_work =
container_of(work, struct vdpasim_mm_work, work);
struct vdpasim *vdpasim = mm_work->vdpasim;
mm_work->ret = 0;
//TODO: should we attach the cgroup of the mm owner?
vdpasim->mm_bound = mm_work->mm_to_bind;
}
static void vdpasim_worker_change_mm_sync(struct vdpasim *vdpasim,
struct vdpasim_mm_work *mm_work)
{
struct kthread_work *work = &mm_work->work;
kthread_init_work(work, vdpasim_mm_work_fn);
kthread_queue_work(vdpasim->worker, work);
kthread_flush_work(work);
}
static struct vdpasim *vdpa_to_sim(struct vdpa_device *vdpa)
{
return container_of(vdpa, struct vdpasim, vdpa);
}
static void vdpasim_vq_notify(struct vringh *vring)
{
struct vdpasim_virtqueue *vq =
container_of(vring, struct vdpasim_virtqueue, vring);
if (!vq->cb)
return;
vq->cb(vq->private);
}
static void vdpasim_queue_ready(struct vdpasim *vdpasim, unsigned int idx)
{
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
uint16_t last_avail_idx = vq->vring.last_avail_idx;
struct vring_desc *desc = (struct vring_desc *)
(uintptr_t)vq->desc_addr;
struct vring_avail *avail = (struct vring_avail *)
(uintptr_t)vq->driver_addr;
struct vring_used *used = (struct vring_used *)
(uintptr_t)vq->device_addr;
if (use_va && vdpasim->mm_bound) {
vringh_init_iotlb_va(&vq->vring, vdpasim->features, vq->num,
true, desc, avail, used);
} else {
vringh_init_iotlb(&vq->vring, vdpasim->features, vq->num,
true, desc, avail, used);
}
vq->vring.last_avail_idx = last_avail_idx;
/*
* Since vdpa_sim does not support receive inflight descriptors as a
* destination of a migration, let's set both avail_idx and used_idx
* the same at vq start. This is how vhost-user works in a
* VHOST_SET_VRING_BASE call.
*
* Although the simple fix is to set last_used_idx at
* vdpasim_set_vq_state, it would be reset at vdpasim_queue_ready.
*/
vq->vring.last_used_idx = last_avail_idx;
vq->vring.notify = vdpasim_vq_notify;
}
static void vdpasim_vq_reset(struct vdpasim *vdpasim,
struct vdpasim_virtqueue *vq)
{
vq->ready = false;
vq->desc_addr = 0;
vq->driver_addr = 0;
vq->device_addr = 0;
vq->cb = NULL;
vq->private = NULL;
vringh_init_iotlb(&vq->vring, vdpasim->dev_attr.supported_features,
VDPASIM_QUEUE_MAX, false, NULL, NULL, NULL);
vq->vring.notify = NULL;
}
static void vdpasim_do_reset(struct vdpasim *vdpasim, u32 flags)
{
int i;
spin_lock(&vdpasim->iommu_lock);
for (i = 0; i < vdpasim->dev_attr.nvqs; i++) {
vdpasim_vq_reset(vdpasim, &vdpasim->vqs[i]);
vringh_set_iotlb(&vdpasim->vqs[i].vring, &vdpasim->iommu[0],
&vdpasim->iommu_lock);
}
if (flags & VDPA_RESET_F_CLEAN_MAP) {
for (i = 0; i < vdpasim->dev_attr.nas; i++) {
vhost_iotlb_reset(&vdpasim->iommu[i]);
vhost_iotlb_add_range(&vdpasim->iommu[i], 0, ULONG_MAX,
0, VHOST_MAP_RW);
vdpasim->iommu_pt[i] = true;
}
}
vdpasim->running = false;
spin_unlock(&vdpasim->iommu_lock);
vdpasim->features = 0;
vdpasim->status = 0;
++vdpasim->generation;
}
static const struct vdpa_config_ops vdpasim_config_ops;
static const struct vdpa_config_ops vdpasim_batch_config_ops;
static void vdpasim_work_fn(struct kthread_work *work)
{
struct vdpasim *vdpasim = container_of(work, struct vdpasim, work);
struct mm_struct *mm = vdpasim->mm_bound;
if (use_va && mm) {
if (!mmget_not_zero(mm))
return;
kthread_use_mm(mm);
}
vdpasim->dev_attr.work_fn(vdpasim);
if (use_va && mm) {
kthread_unuse_mm(mm);
mmput(mm);
}
}
struct vdpasim *vdpasim_create(struct vdpasim_dev_attr *dev_attr,
const struct vdpa_dev_set_config *config)
{
const struct vdpa_config_ops *ops;
struct vdpa_device *vdpa;
struct vdpasim *vdpasim;
struct device *dev;
int i, ret = -ENOMEM;
if (!dev_attr->alloc_size)
return ERR_PTR(-EINVAL);
if (max_iotlb_entries < 2)
return ERR_PTR(-EINVAL);
if (config->mask & BIT_ULL(VDPA_ATTR_DEV_FEATURES)) {
if (config->device_features &
~dev_attr->supported_features)
return ERR_PTR(-EINVAL);
dev_attr->supported_features =
config->device_features;
}
if (batch_mapping)
ops = &vdpasim_batch_config_ops;
else
ops = &vdpasim_config_ops;
vdpa = __vdpa_alloc_device(NULL, ops, NULL,
dev_attr->ngroups, dev_attr->nas,
dev_attr->alloc_size,
dev_attr->name, use_va);
if (IS_ERR(vdpa)) {
ret = PTR_ERR(vdpa);
goto err_alloc;
}
vdpasim = vdpa_to_sim(vdpa);
vdpasim->dev_attr = *dev_attr;
dev = &vdpasim->vdpa.dev;
kthread_init_work(&vdpasim->work, vdpasim_work_fn);
vdpasim->worker = kthread_run_worker(0, "vDPA sim worker: %s",
dev_attr->name);
if (IS_ERR(vdpasim->worker))
goto err_iommu;
mutex_init(&vdpasim->mutex);
spin_lock_init(&vdpasim->iommu_lock);
dev->dma_mask = &dev->coherent_dma_mask;
if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)))
goto err_iommu;
vdpasim->vdpa.mdev = dev_attr->mgmt_dev;
vdpasim->config = kzalloc(dev_attr->config_size, GFP_KERNEL);
if (!vdpasim->config)
goto err_iommu;
vdpasim->vqs = kzalloc_objs(struct vdpasim_virtqueue, dev_attr->nvqs);
if (!vdpasim->vqs)
goto err_iommu;
vdpasim->iommu = kmalloc_objs(*vdpasim->iommu, vdpasim->dev_attr.nas);
if (!vdpasim->iommu)
goto err_iommu;
vdpasim->iommu_pt = kmalloc_objs(*vdpasim->iommu_pt,
vdpasim->dev_attr.nas);
if (!vdpasim->iommu_pt)
goto err_iommu;
for (i = 0; i < vdpasim->dev_attr.nas; i++) {
vhost_iotlb_init(&vdpasim->iommu[i], max_iotlb_entries, 0);
ret = vhost_iotlb_add_range(&vdpasim->iommu[i], 0, ULONG_MAX,
0, VHOST_MAP_RW);
if (ret)
goto err_iommu;
vdpasim->iommu_pt[i] = true;
}
for (i = 0; i < dev_attr->nvqs; i++)
vringh_set_iotlb(&vdpasim->vqs[i].vring, &vdpasim->iommu[0],
&vdpasim->iommu_lock);
vdpasim->vdpa.vmap.dma_dev = dev;
return vdpasim;
err_iommu:
put_device(dev);
err_alloc:
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(vdpasim_create);
void vdpasim_schedule_work(struct vdpasim *vdpasim)
{
kthread_queue_work(vdpasim->worker, &vdpasim->work);
}
EXPORT_SYMBOL_GPL(vdpasim_schedule_work);
static int vdpasim_set_vq_address(struct vdpa_device *vdpa, u16 idx,
u64 desc_area, u64 driver_area,
u64 device_area)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
vq->desc_addr = desc_area;
vq->driver_addr = driver_area;
vq->device_addr = device_area;
return 0;
}
static void vdpasim_set_vq_num(struct vdpa_device *vdpa, u16 idx, u32 num)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
vq->num = num;
}
static u16 vdpasim_get_vq_size(struct vdpa_device *vdpa, u16 idx)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
if (vdpasim->status & VIRTIO_CONFIG_S_DRIVER_OK)
return vq->num;
else
return VDPASIM_QUEUE_MAX;
}
static void vdpasim_kick_vq(struct vdpa_device *vdpa, u16 idx)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
if (!vdpasim->running &&
(vdpasim->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
vdpasim->pending_kick = true;
return;
}
if (vq->ready)
vdpasim_schedule_work(vdpasim);
}
static void vdpasim_set_vq_cb(struct vdpa_device *vdpa, u16 idx,
struct vdpa_callback *cb)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
vq->cb = cb->callback;
vq->private = cb->private;
}
static void vdpasim_set_vq_ready(struct vdpa_device *vdpa, u16 idx, bool ready)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
bool old_ready;
mutex_lock(&vdpasim->mutex);
old_ready = vq->ready;
vq->ready = ready;
if (vq->ready && !old_ready) {
vdpasim_queue_ready(vdpasim, idx);
}
mutex_unlock(&vdpasim->mutex);
}
static bool vdpasim_get_vq_ready(struct vdpa_device *vdpa, u16 idx)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
return vq->ready;
}
static int vdpasim_set_vq_state(struct vdpa_device *vdpa, u16 idx,
const struct vdpa_vq_state *state)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
struct vringh *vrh = &vq->vring;
mutex_lock(&vdpasim->mutex);
vrh->last_avail_idx = state->split.avail_index;
mutex_unlock(&vdpasim->mutex);
return 0;
}
static int vdpasim_get_vq_state(struct vdpa_device *vdpa, u16 idx,
struct vdpa_vq_state *state)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_virtqueue *vq = &vdpasim->vqs[idx];
struct vringh *vrh = &vq->vring;
state->split.avail_index = vrh->last_avail_idx;
return 0;
}
static int vdpasim_get_vq_stats(struct vdpa_device *vdpa, u16 idx,
struct sk_buff *msg,
struct netlink_ext_ack *extack)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
if (vdpasim->dev_attr.get_stats)
return vdpasim->dev_attr.get_stats(vdpasim, idx,
msg, extack);
return -EOPNOTSUPP;
}
static u32 vdpasim_get_vq_align(struct vdpa_device *vdpa)
{
return VDPASIM_QUEUE_ALIGN;
}
static u32 vdpasim_get_vq_group(struct vdpa_device *vdpa, u16 idx)
{
/* RX and TX belongs to group 0, CVQ belongs to group 1 */
if (idx == 2)
return 1;
else
return 0;
}
static u64 vdpasim_get_device_features(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
return vdpasim->dev_attr.supported_features;
}
static u64 vdpasim_get_backend_features(const struct vdpa_device *vdpa)
{
return BIT_ULL(VHOST_BACKEND_F_ENABLE_AFTER_DRIVER_OK);
}
static int vdpasim_set_driver_features(struct vdpa_device *vdpa, u64 features)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
/* DMA mapping must be done by driver */
if (!(features & (1ULL << VIRTIO_F_ACCESS_PLATFORM)))
return -EINVAL;
vdpasim->features = features & vdpasim->dev_attr.supported_features;
return 0;
}
static u64 vdpasim_get_driver_features(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
return vdpasim->features;
}
static void vdpasim_set_config_cb(struct vdpa_device *vdpa,
struct vdpa_callback *cb)
{
/* We don't support config interrupt */
}
static u16 vdpasim_get_vq_num_max(struct vdpa_device *vdpa)
{
return VDPASIM_QUEUE_MAX;
}
static u32 vdpasim_get_device_id(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
return vdpasim->dev_attr.id;
}
static u32 vdpasim_get_vendor_id(struct vdpa_device *vdpa)
{
return VDPASIM_VENDOR_ID;
}
static u8 vdpasim_get_status(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
u8 status;
mutex_lock(&vdpasim->mutex);
status = vdpasim->status;
mutex_unlock(&vdpasim->mutex);
return status;
}
static void vdpasim_set_status(struct vdpa_device *vdpa, u8 status)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
mutex_lock(&vdpasim->mutex);
vdpasim->status = status;
vdpasim->running = (status & VIRTIO_CONFIG_S_DRIVER_OK) != 0;
mutex_unlock(&vdpasim->mutex);
}
static int vdpasim_compat_reset(struct vdpa_device *vdpa, u32 flags)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
mutex_lock(&vdpasim->mutex);
vdpasim->status = 0;
vdpasim_do_reset(vdpasim, flags);
mutex_unlock(&vdpasim->mutex);
return 0;
}
static int vdpasim_reset(struct vdpa_device *vdpa)
{
return vdpasim_compat_reset(vdpa, 0);
}
static int vdpasim_suspend(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
mutex_lock(&vdpasim->mutex);
vdpasim->running = false;
mutex_unlock(&vdpasim->mutex);
return 0;
}
static int vdpasim_resume(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
int i;
mutex_lock(&vdpasim->mutex);
vdpasim->running = true;
if (vdpasim->pending_kick) {
/* Process pending descriptors */
for (i = 0; i < vdpasim->dev_attr.nvqs; ++i)
vdpasim_kick_vq(vdpa, i);
vdpasim->pending_kick = false;
}
mutex_unlock(&vdpasim->mutex);
return 0;
}
static size_t vdpasim_get_config_size(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
return vdpasim->dev_attr.config_size;
}
static void vdpasim_get_config(struct vdpa_device *vdpa, unsigned int offset,
void *buf, unsigned int len)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
if (offset + len > vdpasim->dev_attr.config_size)
return;
if (vdpasim->dev_attr.get_config)
vdpasim->dev_attr.get_config(vdpasim, vdpasim->config);
memcpy(buf, vdpasim->config + offset, len);
}
static void vdpasim_set_config(struct vdpa_device *vdpa, unsigned int offset,
const void *buf, unsigned int len)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
if (offset + len > vdpasim->dev_attr.config_size)
return;
memcpy(vdpasim->config + offset, buf, len);
if (vdpasim->dev_attr.set_config)
vdpasim->dev_attr.set_config(vdpasim, vdpasim->config);
}
static u32 vdpasim_get_generation(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
return vdpasim->generation;
}
static struct vdpa_iova_range vdpasim_get_iova_range(struct vdpa_device *vdpa)
{
struct vdpa_iova_range range = {
.first = 0ULL,
.last = ULLONG_MAX,
};
return range;
}
static int vdpasim_set_group_asid(struct vdpa_device *vdpa, unsigned int group,
unsigned int asid)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vhost_iotlb *iommu;
int i;
iommu = &vdpasim->iommu[asid];
mutex_lock(&vdpasim->mutex);
for (i = 0; i < vdpasim->dev_attr.nvqs; i++)
if (vdpasim_get_vq_group(vdpa, i) == group)
vringh_set_iotlb(&vdpasim->vqs[i].vring, iommu,
&vdpasim->iommu_lock);
mutex_unlock(&vdpasim->mutex);
return 0;
}
static int vdpasim_set_map(struct vdpa_device *vdpa, unsigned int asid,
struct vhost_iotlb *iotlb)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vhost_iotlb_map *map;
struct vhost_iotlb *iommu;
u64 start = 0ULL, last = 0ULL - 1;
int ret;
if (asid >= vdpasim->dev_attr.nas)
return -EINVAL;
spin_lock(&vdpasim->iommu_lock);
iommu = &vdpasim->iommu[asid];
vhost_iotlb_reset(iommu);
vdpasim->iommu_pt[asid] = false;
for (map = vhost_iotlb_itree_first(iotlb, start, last); map;
map = vhost_iotlb_itree_next(map, start, last)) {
ret = vhost_iotlb_add_range(iommu, map->start,
map->last, map->addr, map->perm);
if (ret)
goto err;
}
spin_unlock(&vdpasim->iommu_lock);
return 0;
err:
vhost_iotlb_reset(iommu);
spin_unlock(&vdpasim->iommu_lock);
return ret;
}
static int vdpasim_reset_map(struct vdpa_device *vdpa, unsigned int asid)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
if (asid >= vdpasim->dev_attr.nas)
return -EINVAL;
spin_lock(&vdpasim->iommu_lock);
if (vdpasim->iommu_pt[asid])
goto out;
vhost_iotlb_reset(&vdpasim->iommu[asid]);
vhost_iotlb_add_range(&vdpasim->iommu[asid], 0, ULONG_MAX,
0, VHOST_MAP_RW);
vdpasim->iommu_pt[asid] = true;
out:
spin_unlock(&vdpasim->iommu_lock);
return 0;
}
static int vdpasim_bind_mm(struct vdpa_device *vdpa, struct mm_struct *mm)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_mm_work mm_work;
mm_work.vdpasim = vdpasim;
mm_work.mm_to_bind = mm;
vdpasim_worker_change_mm_sync(vdpasim, &mm_work);
return mm_work.ret;
}
static void vdpasim_unbind_mm(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
struct vdpasim_mm_work mm_work;
mm_work.vdpasim = vdpasim;
mm_work.mm_to_bind = NULL;
vdpasim_worker_change_mm_sync(vdpasim, &mm_work);
}
static int vdpasim_dma_map(struct vdpa_device *vdpa, unsigned int asid,
u64 iova, u64 size,
u64 pa, u32 perm, void *opaque)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
int ret;
if (asid >= vdpasim->dev_attr.nas)
return -EINVAL;
spin_lock(&vdpasim->iommu_lock);
if (vdpasim->iommu_pt[asid]) {
vhost_iotlb_reset(&vdpasim->iommu[asid]);
vdpasim->iommu_pt[asid] = false;
}
ret = vhost_iotlb_add_range_ctx(&vdpasim->iommu[asid], iova,
iova + size - 1, pa, perm, opaque);
spin_unlock(&vdpasim->iommu_lock);
return ret;
}
static int vdpasim_dma_unmap(struct vdpa_device *vdpa, unsigned int asid,
u64 iova, u64 size)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
if (asid >= vdpasim->dev_attr.nas)
return -EINVAL;
if (vdpasim->iommu_pt[asid]) {
vhost_iotlb_reset(&vdpasim->iommu[asid]);
vdpasim->iommu_pt[asid] = false;
}
spin_lock(&vdpasim->iommu_lock);
vhost_iotlb_del_range(&vdpasim->iommu[asid], iova, iova + size - 1);
spin_unlock(&vdpasim->iommu_lock);
return 0;
}
static void vdpasim_free(struct vdpa_device *vdpa)
{
struct vdpasim *vdpasim = vdpa_to_sim(vdpa);
int i;
kthread_cancel_work_sync(&vdpasim->work);
kthread_destroy_worker(vdpasim->worker);
for (i = 0; i < vdpasim->dev_attr.nvqs; i++) {
vringh_kiov_cleanup(&vdpasim->vqs[i].out_iov);
vringh_kiov_cleanup(&vdpasim->vqs[i].in_iov);
}
vdpasim->dev_attr.free(vdpasim);
for (i = 0; i < vdpasim->dev_attr.nas; i++)
vhost_iotlb_reset(&vdpasim->iommu[i]);
kfree(vdpasim->iommu);
kfree(vdpasim->iommu_pt);
kfree(vdpasim->vqs);
kfree(vdpasim->config);
}
static const struct vdpa_config_ops vdpasim_config_ops = {
.set_vq_address = vdpasim_set_vq_address,
.set_vq_num = vdpasim_set_vq_num,
.kick_vq = vdpasim_kick_vq,
.set_vq_cb = vdpasim_set_vq_cb,
.set_vq_ready = vdpasim_set_vq_ready,
.get_vq_ready = vdpasim_get_vq_ready,
.set_vq_state = vdpasim_set_vq_state,
.get_vendor_vq_stats = vdpasim_get_vq_stats,
.get_vq_state = vdpasim_get_vq_state,
.get_vq_align = vdpasim_get_vq_align,
.get_vq_group = vdpasim_get_vq_group,
.get_device_features = vdpasim_get_device_features,
.get_backend_features = vdpasim_get_backend_features,
.set_driver_features = vdpasim_set_driver_features,
.get_driver_features = vdpasim_get_driver_features,
.set_config_cb = vdpasim_set_config_cb,
.get_vq_num_max = vdpasim_get_vq_num_max,
.get_vq_size = vdpasim_get_vq_size,
.get_device_id = vdpasim_get_device_id,
.get_vendor_id = vdpasim_get_vendor_id,
.get_status = vdpasim_get_status,
.set_status = vdpasim_set_status,
.reset = vdpasim_reset,
.compat_reset = vdpasim_compat_reset,
.suspend = vdpasim_suspend,
.resume = vdpasim_resume,
.get_config_size = vdpasim_get_config_size,
.get_config = vdpasim_get_config,
.set_config = vdpasim_set_config,
.get_generation = vdpasim_get_generation,
.get_iova_range = vdpasim_get_iova_range,
.set_group_asid = vdpasim_set_group_asid,
.dma_map = vdpasim_dma_map,
.dma_unmap = vdpasim_dma_unmap,
.reset_map = vdpasim_reset_map,
.bind_mm = vdpasim_bind_mm,
.unbind_mm = vdpasim_unbind_mm,
.free = vdpasim_free,
};
static const struct vdpa_config_ops vdpasim_batch_config_ops = {
.set_vq_address = vdpasim_set_vq_address,
.set_vq_num = vdpasim_set_vq_num,
.kick_vq = vdpasim_kick_vq,
.set_vq_cb = vdpasim_set_vq_cb,
.set_vq_ready = vdpasim_set_vq_ready,
.get_vq_ready = vdpasim_get_vq_ready,
.set_vq_state = vdpasim_set_vq_state,
.get_vendor_vq_stats = vdpasim_get_vq_stats,
.get_vq_state = vdpasim_get_vq_state,
.get_vq_align = vdpasim_get_vq_align,
.get_vq_group = vdpasim_get_vq_group,
.get_device_features = vdpasim_get_device_features,
.get_backend_features = vdpasim_get_backend_features,
.set_driver_features = vdpasim_set_driver_features,
.get_driver_features = vdpasim_get_driver_features,
.set_config_cb = vdpasim_set_config_cb,
.get_vq_num_max = vdpasim_get_vq_num_max,
.get_device_id = vdpasim_get_device_id,
.get_vendor_id = vdpasim_get_vendor_id,
.get_status = vdpasim_get_status,
.set_status = vdpasim_set_status,
.reset = vdpasim_reset,
.compat_reset = vdpasim_compat_reset,
.suspend = vdpasim_suspend,
.resume = vdpasim_resume,
.get_config_size = vdpasim_get_config_size,
.get_config = vdpasim_get_config,
.set_config = vdpasim_set_config,
.get_generation = vdpasim_get_generation,
.get_iova_range = vdpasim_get_iova_range,
.set_group_asid = vdpasim_set_group_asid,
.set_map = vdpasim_set_map,
.reset_map = vdpasim_reset_map,
.bind_mm = vdpasim_bind_mm,
.unbind_mm = vdpasim_unbind_mm,
.free = vdpasim_free,
};
MODULE_VERSION(DRV_VERSION);
MODULE_LICENSE(DRV_LICENSE);
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
@@ -0,0 +1,121 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2020, Red Hat Inc. All rights reserved.
*/
#ifndef _VDPA_SIM_H
#define _VDPA_SIM_H
#include <linux/iova.h>
#include <linux/vringh.h>
#include <linux/vdpa.h>
#include <linux/virtio_byteorder.h>
#include <linux/vhost_iotlb.h>
#include <uapi/linux/virtio_config.h>
#define VDPASIM_FEATURES ((1ULL << VIRTIO_F_ANY_LAYOUT) | \
(1ULL << VIRTIO_F_VERSION_1) | \
(1ULL << VIRTIO_F_ACCESS_PLATFORM))
struct vdpasim;
struct vdpasim_virtqueue {
struct vringh vring;
struct vringh_kiov in_iov;
struct vringh_kiov out_iov;
unsigned short head;
bool ready;
u64 desc_addr;
u64 device_addr;
u64 driver_addr;
u32 num;
void *private;
irqreturn_t (*cb)(void *data);
};
struct vdpasim_dev_attr {
struct vdpa_mgmt_dev *mgmt_dev;
const char *name;
u64 supported_features;
size_t alloc_size;
size_t config_size;
int nvqs;
u32 id;
u32 ngroups;
u32 nas;
void (*work_fn)(struct vdpasim *vdpasim);
void (*get_config)(struct vdpasim *vdpasim, void *config);
void (*set_config)(struct vdpasim *vdpasim, const void *config);
int (*get_stats)(struct vdpasim *vdpasim, u16 idx,
struct sk_buff *msg,
struct netlink_ext_ack *extack);
void (*free)(struct vdpasim *vdpasim);
};
/* State of each vdpasim device */
struct vdpasim {
struct vdpa_device vdpa;
struct vdpasim_virtqueue *vqs;
struct kthread_worker *worker;
struct kthread_work work;
struct mm_struct *mm_bound;
struct vdpasim_dev_attr dev_attr;
/* mutex to synchronize virtqueue state */
struct mutex mutex;
/* virtio config according to device type */
void *config;
struct vhost_iotlb *iommu;
bool *iommu_pt;
u32 status;
u32 generation;
u64 features;
u32 groups;
bool running;
bool pending_kick;
/* spinlock to synchronize iommu table */
spinlock_t iommu_lock;
};
struct vdpasim *vdpasim_create(struct vdpasim_dev_attr *attr,
const struct vdpa_dev_set_config *config);
void vdpasim_schedule_work(struct vdpasim *vdpasim);
/* TODO: cross-endian support */
static inline bool vdpasim_is_little_endian(struct vdpasim *vdpasim)
{
return virtio_legacy_is_little_endian() ||
(vdpasim->features & (1ULL << VIRTIO_F_VERSION_1));
}
static inline u16 vdpasim16_to_cpu(struct vdpasim *vdpasim, __virtio16 val)
{
return __virtio16_to_cpu(vdpasim_is_little_endian(vdpasim), val);
}
static inline __virtio16 cpu_to_vdpasim16(struct vdpasim *vdpasim, u16 val)
{
return __cpu_to_virtio16(vdpasim_is_little_endian(vdpasim), val);
}
static inline u32 vdpasim32_to_cpu(struct vdpasim *vdpasim, __virtio32 val)
{
return __virtio32_to_cpu(vdpasim_is_little_endian(vdpasim), val);
}
static inline __virtio32 cpu_to_vdpasim32(struct vdpasim *vdpasim, u32 val)
{
return __cpu_to_virtio32(vdpasim_is_little_endian(vdpasim), val);
}
static inline u64 vdpasim64_to_cpu(struct vdpasim *vdpasim, __virtio64 val)
{
return __virtio64_to_cpu(vdpasim_is_little_endian(vdpasim), val);
}
static inline __virtio64 cpu_to_vdpasim64(struct vdpasim *vdpasim, u64 val)
{
return __cpu_to_virtio64(vdpasim_is_little_endian(vdpasim), val);
}
#endif
@@ -0,0 +1,519 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VDPA simulator for block device.
*
* Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2021, Red Hat Inc. All rights reserved.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/vringh.h>
#include <linux/vdpa.h>
#include <uapi/linux/virtio_blk.h>
#include "vdpa_sim.h"
#define DRV_VERSION "0.1"
#define DRV_AUTHOR "Max Gurtovoy <mgurtovoy@nvidia.com>"
#define DRV_DESC "vDPA Device Simulator for block device"
#define DRV_LICENSE "GPL v2"
#define VDPASIM_BLK_FEATURES (VDPASIM_FEATURES | \
(1ULL << VIRTIO_BLK_F_FLUSH) | \
(1ULL << VIRTIO_BLK_F_SIZE_MAX) | \
(1ULL << VIRTIO_BLK_F_SEG_MAX) | \
(1ULL << VIRTIO_BLK_F_BLK_SIZE) | \
(1ULL << VIRTIO_BLK_F_TOPOLOGY) | \
(1ULL << VIRTIO_BLK_F_MQ) | \
(1ULL << VIRTIO_BLK_F_DISCARD) | \
(1ULL << VIRTIO_BLK_F_WRITE_ZEROES))
#define VDPASIM_BLK_CAPACITY 0x40000
#define VDPASIM_BLK_SIZE_MAX 0x1000
#define VDPASIM_BLK_SEG_MAX 32
#define VDPASIM_BLK_DWZ_MAX_SECTORS UINT_MAX
/* 1 virtqueue, 1 address space, 1 virtqueue group */
#define VDPASIM_BLK_VQ_NUM 1
#define VDPASIM_BLK_AS_NUM 1
#define VDPASIM_BLK_GROUP_NUM 1
struct vdpasim_blk {
struct vdpasim vdpasim;
void *buffer;
bool shared_backend;
};
static struct vdpasim_blk *sim_to_blk(struct vdpasim *vdpasim)
{
return container_of(vdpasim, struct vdpasim_blk, vdpasim);
}
static char vdpasim_blk_id[VIRTIO_BLK_ID_BYTES] = "vdpa_blk_sim";
static bool shared_backend;
module_param(shared_backend, bool, 0444);
MODULE_PARM_DESC(shared_backend, "Enable the shared backend between virtio-blk devices");
static void *shared_buffer;
/* mutex to synchronize shared_buffer access */
static DEFINE_MUTEX(shared_buffer_mutex);
static void vdpasim_blk_buffer_lock(struct vdpasim_blk *blk)
{
if (blk->shared_backend)
mutex_lock(&shared_buffer_mutex);
}
static void vdpasim_blk_buffer_unlock(struct vdpasim_blk *blk)
{
if (blk->shared_backend)
mutex_unlock(&shared_buffer_mutex);
}
static bool vdpasim_blk_check_range(struct vdpasim *vdpasim, u64 start_sector,
u64 num_sectors, u64 max_sectors)
{
if (start_sector > VDPASIM_BLK_CAPACITY) {
dev_dbg(&vdpasim->vdpa.dev,
"starting sector exceeds the capacity - start: 0x%llx capacity: 0x%x\n",
start_sector, VDPASIM_BLK_CAPACITY);
}
if (num_sectors > max_sectors) {
dev_dbg(&vdpasim->vdpa.dev,
"number of sectors exceeds the max allowed in a request - num: 0x%llx max: 0x%llx\n",
num_sectors, max_sectors);
return false;
}
if (num_sectors > VDPASIM_BLK_CAPACITY - start_sector) {
dev_dbg(&vdpasim->vdpa.dev,
"request exceeds the capacity - start: 0x%llx num: 0x%llx capacity: 0x%x\n",
start_sector, num_sectors, VDPASIM_BLK_CAPACITY);
return false;
}
return true;
}
/* Returns 'true' if the request is handled (with or without an I/O error)
* and the status is correctly written in the last byte of the 'in iov',
* 'false' otherwise.
*/
static bool vdpasim_blk_handle_req(struct vdpasim *vdpasim,
struct vdpasim_virtqueue *vq)
{
struct vdpasim_blk *blk = sim_to_blk(vdpasim);
size_t pushed = 0, to_pull, to_push;
struct virtio_blk_outhdr hdr;
bool handled = false;
ssize_t bytes;
loff_t offset;
u64 sector;
u8 status;
u32 type;
int ret;
ret = vringh_getdesc_iotlb(&vq->vring, &vq->out_iov, &vq->in_iov,
&vq->head, GFP_ATOMIC);
if (ret != 1)
return false;
if (vq->out_iov.used < 1 || vq->in_iov.used < 1) {
dev_dbg(&vdpasim->vdpa.dev, "missing headers - out_iov: %u in_iov %u\n",
vq->out_iov.used, vq->in_iov.used);
goto err;
}
if (vq->in_iov.iov[vq->in_iov.used - 1].iov_len < 1) {
dev_dbg(&vdpasim->vdpa.dev, "request in header too short\n");
goto err;
}
/* The last byte is the status and we checked if the last iov has
* enough room for it.
*/
to_push = vringh_kiov_length(&vq->in_iov) - 1;
to_pull = vringh_kiov_length(&vq->out_iov);
bytes = vringh_iov_pull_iotlb(&vq->vring, &vq->out_iov, &hdr,
sizeof(hdr));
if (bytes != sizeof(hdr)) {
dev_dbg(&vdpasim->vdpa.dev, "request out header too short\n");
goto err;
}
to_pull -= bytes;
type = vdpasim32_to_cpu(vdpasim, hdr.type);
sector = vdpasim64_to_cpu(vdpasim, hdr.sector);
offset = sector << SECTOR_SHIFT;
status = VIRTIO_BLK_S_OK;
if (type != VIRTIO_BLK_T_IN && type != VIRTIO_BLK_T_OUT &&
sector != 0) {
dev_dbg(&vdpasim->vdpa.dev,
"sector must be 0 for %u request - sector: 0x%llx\n",
type, sector);
status = VIRTIO_BLK_S_IOERR;
goto err_status;
}
switch (type) {
case VIRTIO_BLK_T_IN:
if (!vdpasim_blk_check_range(vdpasim, sector,
to_push >> SECTOR_SHIFT,
VDPASIM_BLK_SIZE_MAX * VDPASIM_BLK_SEG_MAX)) {
status = VIRTIO_BLK_S_IOERR;
break;
}
vdpasim_blk_buffer_lock(blk);
bytes = vringh_iov_push_iotlb(&vq->vring, &vq->in_iov,
blk->buffer + offset, to_push);
vdpasim_blk_buffer_unlock(blk);
if (bytes < 0) {
dev_dbg(&vdpasim->vdpa.dev,
"vringh_iov_push_iotlb() error: %zd offset: 0x%llx len: 0x%zx\n",
bytes, offset, to_push);
status = VIRTIO_BLK_S_IOERR;
break;
}
pushed += bytes;
break;
case VIRTIO_BLK_T_OUT:
if (!vdpasim_blk_check_range(vdpasim, sector,
to_pull >> SECTOR_SHIFT,
VDPASIM_BLK_SIZE_MAX * VDPASIM_BLK_SEG_MAX)) {
status = VIRTIO_BLK_S_IOERR;
break;
}
vdpasim_blk_buffer_lock(blk);
bytes = vringh_iov_pull_iotlb(&vq->vring, &vq->out_iov,
blk->buffer + offset, to_pull);
vdpasim_blk_buffer_unlock(blk);
if (bytes < 0) {
dev_dbg(&vdpasim->vdpa.dev,
"vringh_iov_pull_iotlb() error: %zd offset: 0x%llx len: 0x%zx\n",
bytes, offset, to_pull);
status = VIRTIO_BLK_S_IOERR;
break;
}
break;
case VIRTIO_BLK_T_GET_ID:
bytes = vringh_iov_push_iotlb(&vq->vring, &vq->in_iov,
vdpasim_blk_id,
VIRTIO_BLK_ID_BYTES);
if (bytes < 0) {
dev_dbg(&vdpasim->vdpa.dev,
"vringh_iov_push_iotlb() error: %zd\n", bytes);
status = VIRTIO_BLK_S_IOERR;
break;
}
pushed += bytes;
break;
case VIRTIO_BLK_T_FLUSH:
/* nothing to do */
break;
case VIRTIO_BLK_T_DISCARD:
case VIRTIO_BLK_T_WRITE_ZEROES: {
struct virtio_blk_discard_write_zeroes range;
u32 num_sectors, flags;
if (to_pull != sizeof(range)) {
dev_dbg(&vdpasim->vdpa.dev,
"discard/write_zeroes header len: 0x%zx [expected: 0x%zx]\n",
to_pull, sizeof(range));
status = VIRTIO_BLK_S_IOERR;
break;
}
bytes = vringh_iov_pull_iotlb(&vq->vring, &vq->out_iov, &range,
to_pull);
if (bytes < 0) {
dev_dbg(&vdpasim->vdpa.dev,
"vringh_iov_pull_iotlb() error: %zd offset: 0x%llx len: 0x%zx\n",
bytes, offset, to_pull);
status = VIRTIO_BLK_S_IOERR;
break;
}
sector = le64_to_cpu(range.sector);
offset = sector << SECTOR_SHIFT;
num_sectors = le32_to_cpu(range.num_sectors);
flags = le32_to_cpu(range.flags);
if (type == VIRTIO_BLK_T_DISCARD && flags != 0) {
dev_dbg(&vdpasim->vdpa.dev,
"discard unexpected flags set - flags: 0x%x\n",
flags);
status = VIRTIO_BLK_S_UNSUPP;
break;
}
if (type == VIRTIO_BLK_T_WRITE_ZEROES &&
flags & ~VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP) {
dev_dbg(&vdpasim->vdpa.dev,
"write_zeroes unexpected flags set - flags: 0x%x\n",
flags);
status = VIRTIO_BLK_S_UNSUPP;
break;
}
if (!vdpasim_blk_check_range(vdpasim, sector, num_sectors,
VDPASIM_BLK_DWZ_MAX_SECTORS)) {
status = VIRTIO_BLK_S_IOERR;
break;
}
if (type == VIRTIO_BLK_T_WRITE_ZEROES) {
vdpasim_blk_buffer_lock(blk);
memset(blk->buffer + offset, 0,
num_sectors << SECTOR_SHIFT);
vdpasim_blk_buffer_unlock(blk);
}
break;
}
default:
dev_dbg(&vdpasim->vdpa.dev,
"Unsupported request type %d\n", type);
status = VIRTIO_BLK_S_IOERR;
break;
}
err_status:
/* If some operations fail, we need to skip the remaining bytes
* to put the status in the last byte
*/
if (to_push - pushed > 0)
vringh_kiov_advance(&vq->in_iov, to_push - pushed);
/* Last byte is the status */
bytes = vringh_iov_push_iotlb(&vq->vring, &vq->in_iov, &status, 1);
if (bytes != 1)
goto err;
pushed += bytes;
/* Make sure data is wrote before advancing index */
smp_wmb();
handled = true;
err:
vringh_complete_iotlb(&vq->vring, vq->head, pushed);
return handled;
}
static void vdpasim_blk_work(struct vdpasim *vdpasim)
{
bool reschedule = false;
int i;
mutex_lock(&vdpasim->mutex);
if (!(vdpasim->status & VIRTIO_CONFIG_S_DRIVER_OK))
goto out;
if (!vdpasim->running)
goto out;
for (i = 0; i < VDPASIM_BLK_VQ_NUM; i++) {
struct vdpasim_virtqueue *vq = &vdpasim->vqs[i];
int reqs = 0;
if (!vq->ready)
continue;
while (vdpasim_blk_handle_req(vdpasim, vq)) {
/* Make sure used is visible before rasing the interrupt. */
smp_wmb();
local_bh_disable();
if (vringh_need_notify_iotlb(&vq->vring) > 0)
vringh_notify(&vq->vring);
local_bh_enable();
if (++reqs > 4) {
reschedule = true;
break;
}
}
}
out:
mutex_unlock(&vdpasim->mutex);
if (reschedule)
vdpasim_schedule_work(vdpasim);
}
static void vdpasim_blk_get_config(struct vdpasim *vdpasim, void *config)
{
struct virtio_blk_config *blk_config = config;
memset(config, 0, sizeof(struct virtio_blk_config));
blk_config->capacity = cpu_to_vdpasim64(vdpasim, VDPASIM_BLK_CAPACITY);
blk_config->size_max = cpu_to_vdpasim32(vdpasim, VDPASIM_BLK_SIZE_MAX);
blk_config->seg_max = cpu_to_vdpasim32(vdpasim, VDPASIM_BLK_SEG_MAX);
blk_config->num_queues = cpu_to_vdpasim16(vdpasim, VDPASIM_BLK_VQ_NUM);
blk_config->min_io_size = cpu_to_vdpasim16(vdpasim, 1);
blk_config->opt_io_size = cpu_to_vdpasim32(vdpasim, 1);
blk_config->blk_size = cpu_to_vdpasim32(vdpasim, SECTOR_SIZE);
/* VIRTIO_BLK_F_DISCARD */
blk_config->discard_sector_alignment =
cpu_to_vdpasim32(vdpasim, SECTOR_SIZE);
blk_config->max_discard_sectors =
cpu_to_vdpasim32(vdpasim, VDPASIM_BLK_DWZ_MAX_SECTORS);
blk_config->max_discard_seg = cpu_to_vdpasim32(vdpasim, 1);
/* VIRTIO_BLK_F_WRITE_ZEROES */
blk_config->max_write_zeroes_sectors =
cpu_to_vdpasim32(vdpasim, VDPASIM_BLK_DWZ_MAX_SECTORS);
blk_config->max_write_zeroes_seg = cpu_to_vdpasim32(vdpasim, 1);
}
static void vdpasim_blk_free(struct vdpasim *vdpasim)
{
struct vdpasim_blk *blk = sim_to_blk(vdpasim);
if (!blk->shared_backend)
kvfree(blk->buffer);
}
static struct device *vdpasim_blk_mgmtdev;
static int vdpasim_blk_dev_add(struct vdpa_mgmt_dev *mdev, const char *name,
const struct vdpa_dev_set_config *config)
{
struct vdpasim_dev_attr dev_attr = {};
struct vdpasim_blk *blk;
struct vdpasim *simdev;
int ret;
dev_attr.mgmt_dev = mdev;
dev_attr.name = name;
dev_attr.id = VIRTIO_ID_BLOCK;
dev_attr.supported_features = VDPASIM_BLK_FEATURES;
dev_attr.nvqs = VDPASIM_BLK_VQ_NUM;
dev_attr.ngroups = VDPASIM_BLK_GROUP_NUM;
dev_attr.nas = VDPASIM_BLK_AS_NUM;
dev_attr.alloc_size = sizeof(struct vdpasim_blk);
dev_attr.config_size = sizeof(struct virtio_blk_config);
dev_attr.get_config = vdpasim_blk_get_config;
dev_attr.work_fn = vdpasim_blk_work;
dev_attr.free = vdpasim_blk_free;
simdev = vdpasim_create(&dev_attr, config);
if (IS_ERR(simdev))
return PTR_ERR(simdev);
blk = sim_to_blk(simdev);
blk->shared_backend = shared_backend;
if (blk->shared_backend) {
blk->buffer = shared_buffer;
} else {
blk->buffer = kvzalloc(VDPASIM_BLK_CAPACITY << SECTOR_SHIFT,
GFP_KERNEL);
if (!blk->buffer) {
ret = -ENOMEM;
goto put_dev;
}
}
ret = _vdpa_register_device(&simdev->vdpa, VDPASIM_BLK_VQ_NUM);
if (ret)
goto put_dev;
return 0;
put_dev:
put_device(&simdev->vdpa.dev);
return ret;
}
static void vdpasim_blk_dev_del(struct vdpa_mgmt_dev *mdev,
struct vdpa_device *dev)
{
struct vdpasim *simdev = container_of(dev, struct vdpasim, vdpa);
_vdpa_unregister_device(&simdev->vdpa);
}
static const struct vdpa_mgmtdev_ops vdpasim_blk_mgmtdev_ops = {
.dev_add = vdpasim_blk_dev_add,
.dev_del = vdpasim_blk_dev_del
};
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_BLOCK, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static struct vdpa_mgmt_dev mgmt_dev = {
.id_table = id_table,
.ops = &vdpasim_blk_mgmtdev_ops,
};
static int __init vdpasim_blk_init(void)
{
int ret;
vdpasim_blk_mgmtdev = root_device_register("vdpasim_blk");
if (IS_ERR(vdpasim_blk_mgmtdev))
return PTR_ERR(vdpasim_blk_mgmtdev);
mgmt_dev.device = vdpasim_blk_mgmtdev;
ret = vdpa_mgmtdev_register(&mgmt_dev);
if (ret)
goto parent_err;
if (shared_backend) {
shared_buffer = kvzalloc(VDPASIM_BLK_CAPACITY << SECTOR_SHIFT,
GFP_KERNEL);
if (!shared_buffer) {
ret = -ENOMEM;
goto mgmt_dev_err;
}
}
return 0;
mgmt_dev_err:
vdpa_mgmtdev_unregister(&mgmt_dev);
parent_err:
root_device_unregister(vdpasim_blk_mgmtdev);
return ret;
}
static void __exit vdpasim_blk_exit(void)
{
kvfree(shared_buffer);
vdpa_mgmtdev_unregister(&mgmt_dev);
root_device_unregister(vdpasim_blk_mgmtdev);
}
module_init(vdpasim_blk_init)
module_exit(vdpasim_blk_exit)
MODULE_VERSION(DRV_VERSION);
MODULE_LICENSE(DRV_LICENSE);
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
@@ -0,0 +1,574 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* VDPA simulator for networking device.
*
* Copyright (c) 2020, Red Hat Inc. All rights reserved.
* Author: Jason Wang <jasowang@redhat.com>
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/etherdevice.h>
#include <linux/vringh.h>
#include <linux/vdpa.h>
#include <net/netlink.h>
#include <uapi/linux/virtio_net.h>
#include <uapi/linux/vdpa.h>
#include "vdpa_sim.h"
#define DRV_VERSION "0.1"
#define DRV_AUTHOR "Jason Wang <jasowang@redhat.com>"
#define DRV_DESC "vDPA Device Simulator for networking device"
#define DRV_LICENSE "GPL v2"
#define VDPASIM_NET_FEATURES (VDPASIM_FEATURES | \
(1ULL << VIRTIO_NET_F_MAC) | \
(1ULL << VIRTIO_NET_F_STATUS) | \
(1ULL << VIRTIO_NET_F_MTU) | \
(1ULL << VIRTIO_NET_F_CTRL_VQ) | \
(1ULL << VIRTIO_NET_F_CTRL_MAC_ADDR))
/* 3 virtqueues, 2 address spaces, 2 virtqueue groups */
#define VDPASIM_NET_VQ_NUM 3
#define VDPASIM_NET_AS_NUM 2
#define VDPASIM_NET_GROUP_NUM 2
struct vdpasim_dataq_stats {
struct u64_stats_sync syncp;
u64 pkts;
u64 bytes;
u64 drops;
u64 errors;
u64 overruns;
};
struct vdpasim_cq_stats {
struct u64_stats_sync syncp;
u64 requests;
u64 successes;
u64 errors;
};
struct vdpasim_net{
struct vdpasim vdpasim;
struct vdpasim_dataq_stats tx_stats;
struct vdpasim_dataq_stats rx_stats;
struct vdpasim_cq_stats cq_stats;
void *buffer;
};
static struct vdpasim_net *sim_to_net(struct vdpasim *vdpasim)
{
return container_of(vdpasim, struct vdpasim_net, vdpasim);
}
static void vdpasim_net_complete(struct vdpasim_virtqueue *vq, size_t len)
{
/* Make sure data is wrote before advancing index */
smp_wmb();
vringh_complete_iotlb(&vq->vring, vq->head, len);
/* Make sure used is visible before rasing the interrupt. */
smp_wmb();
local_bh_disable();
if (vringh_need_notify_iotlb(&vq->vring) > 0)
vringh_notify(&vq->vring);
local_bh_enable();
}
static bool receive_filter(struct vdpasim *vdpasim, size_t len)
{
bool modern = vdpasim->features & (1ULL << VIRTIO_F_VERSION_1);
size_t hdr_len = modern ? sizeof(struct virtio_net_hdr_v1) :
sizeof(struct virtio_net_hdr);
struct virtio_net_config *vio_config = vdpasim->config;
struct vdpasim_net *net = sim_to_net(vdpasim);
if (len < ETH_ALEN + hdr_len)
return false;
if (is_broadcast_ether_addr(net->buffer + hdr_len) ||
is_multicast_ether_addr(net->buffer + hdr_len))
return true;
if (!strncmp(net->buffer + hdr_len, vio_config->mac, ETH_ALEN))
return true;
return false;
}
static virtio_net_ctrl_ack vdpasim_handle_ctrl_mac(struct vdpasim *vdpasim,
u8 cmd)
{
struct virtio_net_config *vio_config = vdpasim->config;
struct vdpasim_virtqueue *cvq = &vdpasim->vqs[2];
virtio_net_ctrl_ack status = VIRTIO_NET_ERR;
size_t read;
switch (cmd) {
case VIRTIO_NET_CTRL_MAC_ADDR_SET:
read = vringh_iov_pull_iotlb(&cvq->vring, &cvq->in_iov,
vio_config->mac, ETH_ALEN);
if (read == ETH_ALEN)
status = VIRTIO_NET_OK;
break;
default:
break;
}
return status;
}
static void vdpasim_handle_cvq(struct vdpasim *vdpasim)
{
struct vdpasim_virtqueue *cvq = &vdpasim->vqs[2];
struct vdpasim_net *net = sim_to_net(vdpasim);
virtio_net_ctrl_ack status = VIRTIO_NET_ERR;
struct virtio_net_ctrl_hdr ctrl;
size_t read, write;
u64 requests = 0, errors = 0, successes = 0;
int err;
if (!(vdpasim->features & (1ULL << VIRTIO_NET_F_CTRL_VQ)))
return;
if (!cvq->ready)
return;
while (true) {
err = vringh_getdesc_iotlb(&cvq->vring, &cvq->in_iov,
&cvq->out_iov,
&cvq->head, GFP_ATOMIC);
if (err <= 0)
break;
++requests;
read = vringh_iov_pull_iotlb(&cvq->vring, &cvq->in_iov, &ctrl,
sizeof(ctrl));
if (read != sizeof(ctrl)) {
++errors;
break;
}
switch (ctrl.class) {
case VIRTIO_NET_CTRL_MAC:
status = vdpasim_handle_ctrl_mac(vdpasim, ctrl.cmd);
break;
default:
break;
}
if (status == VIRTIO_NET_OK)
++successes;
else
++errors;
/* Make sure data is wrote before advancing index */
smp_wmb();
write = vringh_iov_push_iotlb(&cvq->vring, &cvq->out_iov,
&status, sizeof(status));
vringh_complete_iotlb(&cvq->vring, cvq->head, write);
vringh_kiov_cleanup(&cvq->in_iov);
vringh_kiov_cleanup(&cvq->out_iov);
/* Make sure used is visible before rasing the interrupt. */
smp_wmb();
local_bh_disable();
if (cvq->cb)
cvq->cb(cvq->private);
local_bh_enable();
}
u64_stats_update_begin(&net->cq_stats.syncp);
net->cq_stats.requests += requests;
net->cq_stats.errors += errors;
net->cq_stats.successes += successes;
u64_stats_update_end(&net->cq_stats.syncp);
}
static void vdpasim_net_work(struct vdpasim *vdpasim)
{
struct vdpasim_virtqueue *txq = &vdpasim->vqs[1];
struct vdpasim_virtqueue *rxq = &vdpasim->vqs[0];
struct vdpasim_net *net = sim_to_net(vdpasim);
ssize_t read, write;
u64 tx_pkts = 0, rx_pkts = 0, tx_bytes = 0, rx_bytes = 0;
u64 rx_drops = 0, rx_overruns = 0, rx_errors = 0, tx_errors = 0;
int err;
mutex_lock(&vdpasim->mutex);
if (!vdpasim->running)
goto out;
if (!(vdpasim->status & VIRTIO_CONFIG_S_DRIVER_OK))
goto out;
vdpasim_handle_cvq(vdpasim);
if (!txq->ready || !rxq->ready)
goto out;
while (true) {
err = vringh_getdesc_iotlb(&txq->vring, &txq->out_iov, NULL,
&txq->head, GFP_ATOMIC);
if (err <= 0) {
if (err)
++tx_errors;
break;
}
++tx_pkts;
read = vringh_iov_pull_iotlb(&txq->vring, &txq->out_iov,
net->buffer, PAGE_SIZE);
tx_bytes += read;
if (!receive_filter(vdpasim, read)) {
++rx_drops;
vdpasim_net_complete(txq, 0);
continue;
}
err = vringh_getdesc_iotlb(&rxq->vring, NULL, &rxq->in_iov,
&rxq->head, GFP_ATOMIC);
if (err <= 0) {
++rx_overruns;
vdpasim_net_complete(txq, 0);
break;
}
write = vringh_iov_push_iotlb(&rxq->vring, &rxq->in_iov,
net->buffer, read);
if (write <= 0) {
++rx_errors;
break;
}
++rx_pkts;
rx_bytes += write;
vdpasim_net_complete(txq, 0);
vdpasim_net_complete(rxq, write);
if (tx_pkts > 4) {
vdpasim_schedule_work(vdpasim);
goto out;
}
}
out:
mutex_unlock(&vdpasim->mutex);
u64_stats_update_begin(&net->tx_stats.syncp);
net->tx_stats.pkts += tx_pkts;
net->tx_stats.bytes += tx_bytes;
net->tx_stats.errors += tx_errors;
u64_stats_update_end(&net->tx_stats.syncp);
u64_stats_update_begin(&net->rx_stats.syncp);
net->rx_stats.pkts += rx_pkts;
net->rx_stats.bytes += rx_bytes;
net->rx_stats.drops += rx_drops;
net->rx_stats.errors += rx_errors;
net->rx_stats.overruns += rx_overruns;
u64_stats_update_end(&net->rx_stats.syncp);
}
static int vdpasim_net_get_stats(struct vdpasim *vdpasim, u16 idx,
struct sk_buff *msg,
struct netlink_ext_ack *extack)
{
struct vdpasim_net *net = sim_to_net(vdpasim);
u64 rx_pkts, rx_bytes, rx_errors, rx_overruns, rx_drops;
u64 tx_pkts, tx_bytes, tx_errors, tx_drops;
u64 cq_requests, cq_successes, cq_errors;
unsigned int start;
int err = -EMSGSIZE;
switch(idx) {
case 0:
do {
start = u64_stats_fetch_begin(&net->rx_stats.syncp);
rx_pkts = net->rx_stats.pkts;
rx_bytes = net->rx_stats.bytes;
rx_errors = net->rx_stats.errors;
rx_overruns = net->rx_stats.overruns;
rx_drops = net->rx_stats.drops;
} while (u64_stats_fetch_retry(&net->rx_stats.syncp, start));
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"rx packets"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
rx_pkts, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"rx bytes"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
rx_bytes, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"rx errors"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
rx_errors, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"rx overruns"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
rx_overruns, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"rx drops"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
rx_drops, VDPA_ATTR_PAD))
break;
err = 0;
break;
case 1:
do {
start = u64_stats_fetch_begin(&net->tx_stats.syncp);
tx_pkts = net->tx_stats.pkts;
tx_bytes = net->tx_stats.bytes;
tx_errors = net->tx_stats.errors;
tx_drops = net->tx_stats.drops;
} while (u64_stats_fetch_retry(&net->tx_stats.syncp, start));
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"tx packets"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
tx_pkts, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"tx bytes"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
tx_bytes, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"tx errors"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
tx_errors, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"tx drops"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
tx_drops, VDPA_ATTR_PAD))
break;
err = 0;
break;
case 2:
do {
start = u64_stats_fetch_begin(&net->cq_stats.syncp);
cq_requests = net->cq_stats.requests;
cq_successes = net->cq_stats.successes;
cq_errors = net->cq_stats.errors;
} while (u64_stats_fetch_retry(&net->cq_stats.syncp, start));
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"cvq requests"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
cq_requests, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"cvq successes"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
cq_successes, VDPA_ATTR_PAD))
break;
if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
"cvq errors"))
break;
if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
cq_errors, VDPA_ATTR_PAD))
break;
err = 0;
break;
default:
err = -EINVAL;
break;
}
return err;
}
static void vdpasim_net_get_config(struct vdpasim *vdpasim, void *config)
{
struct virtio_net_config *net_config = config;
net_config->status = cpu_to_vdpasim16(vdpasim, VIRTIO_NET_S_LINK_UP);
}
static int vdpasim_net_set_attr(struct vdpa_mgmt_dev *mdev, struct vdpa_device *dev,
const struct vdpa_dev_set_config *config)
{
struct vdpasim *vdpasim = container_of(dev, struct vdpasim, vdpa);
struct virtio_net_config *vio_config = vdpasim->config;
mutex_lock(&vdpasim->mutex);
if (config->mask & (1 << VDPA_ATTR_DEV_NET_CFG_MACADDR)) {
ether_addr_copy(vio_config->mac, config->net.mac);
mutex_unlock(&vdpasim->mutex);
return 0;
}
mutex_unlock(&vdpasim->mutex);
return -EOPNOTSUPP;
}
static void vdpasim_net_setup_config(struct vdpasim *vdpasim,
const struct vdpa_dev_set_config *config)
{
struct virtio_net_config *vio_config = vdpasim->config;
if (config->mask & (1 << VDPA_ATTR_DEV_NET_CFG_MACADDR))
memcpy(vio_config->mac, config->net.mac, ETH_ALEN);
if (config->mask & (1 << VDPA_ATTR_DEV_NET_CFG_MTU))
vio_config->mtu = cpu_to_vdpasim16(vdpasim, config->net.mtu);
else
/* Setup default MTU to be 1500 */
vio_config->mtu = cpu_to_vdpasim16(vdpasim, 1500);
}
static void vdpasim_net_free(struct vdpasim *vdpasim)
{
struct vdpasim_net *net = sim_to_net(vdpasim);
kvfree(net->buffer);
}
static struct device *vdpasim_net_mgmtdev;
static int vdpasim_net_dev_add(struct vdpa_mgmt_dev *mdev, const char *name,
const struct vdpa_dev_set_config *config)
{
struct vdpasim_dev_attr dev_attr = {};
struct vdpasim_net *net;
struct vdpasim *simdev;
int ret;
dev_attr.mgmt_dev = mdev;
dev_attr.name = name;
dev_attr.id = VIRTIO_ID_NET;
dev_attr.supported_features = VDPASIM_NET_FEATURES;
dev_attr.nvqs = VDPASIM_NET_VQ_NUM;
dev_attr.ngroups = VDPASIM_NET_GROUP_NUM;
dev_attr.nas = VDPASIM_NET_AS_NUM;
dev_attr.alloc_size = sizeof(struct vdpasim_net);
dev_attr.config_size = sizeof(struct virtio_net_config);
dev_attr.get_config = vdpasim_net_get_config;
dev_attr.work_fn = vdpasim_net_work;
dev_attr.get_stats = vdpasim_net_get_stats;
dev_attr.free = vdpasim_net_free;
simdev = vdpasim_create(&dev_attr, config);
if (IS_ERR(simdev))
return PTR_ERR(simdev);
vdpasim_net_setup_config(simdev, config);
net = sim_to_net(simdev);
u64_stats_init(&net->tx_stats.syncp);
u64_stats_init(&net->rx_stats.syncp);
u64_stats_init(&net->cq_stats.syncp);
net->buffer = kvmalloc(PAGE_SIZE, GFP_KERNEL);
if (!net->buffer) {
ret = -ENOMEM;
goto reg_err;
}
/*
* Initialization must be completed before this call, since it can
* connect the device to the vDPA bus, so requests can arrive after
* this call.
*/
ret = _vdpa_register_device(&simdev->vdpa, VDPASIM_NET_VQ_NUM);
if (ret)
goto reg_err;
return 0;
reg_err:
put_device(&simdev->vdpa.dev);
return ret;
}
static void vdpasim_net_dev_del(struct vdpa_mgmt_dev *mdev,
struct vdpa_device *dev)
{
struct vdpasim *simdev = container_of(dev, struct vdpasim, vdpa);
_vdpa_unregister_device(&simdev->vdpa);
}
static const struct vdpa_mgmtdev_ops vdpasim_net_mgmtdev_ops = {
.dev_add = vdpasim_net_dev_add,
.dev_del = vdpasim_net_dev_del,
.dev_set_attr = vdpasim_net_set_attr
};
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_NET, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static struct vdpa_mgmt_dev mgmt_dev = {
.id_table = id_table,
.ops = &vdpasim_net_mgmtdev_ops,
.config_attr_mask = (1 << VDPA_ATTR_DEV_NET_CFG_MACADDR |
1 << VDPA_ATTR_DEV_NET_CFG_MTU |
1 << VDPA_ATTR_DEV_FEATURES),
.max_supported_vqs = VDPASIM_NET_VQ_NUM,
.supported_features = VDPASIM_NET_FEATURES,
};
static int __init vdpasim_net_init(void)
{
int ret;
vdpasim_net_mgmtdev = root_device_register("vdpasim_net");
if (IS_ERR(vdpasim_net_mgmtdev))
return PTR_ERR(vdpasim_net_mgmtdev);
mgmt_dev.device = vdpasim_net_mgmtdev;
ret = vdpa_mgmtdev_register(&mgmt_dev);
if (ret)
goto parent_err;
return 0;
parent_err:
root_device_unregister(vdpasim_net_mgmtdev);
return ret;
}
static void __exit vdpasim_net_exit(void)
{
vdpa_mgmtdev_unregister(&mgmt_dev);
root_device_unregister(vdpasim_net_mgmtdev);
}
module_init(vdpasim_net_init);
module_exit(vdpasim_net_exit);
MODULE_VERSION(DRV_VERSION);
MODULE_LICENSE(DRV_LICENSE);
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
vduse-y := vduse_dev.o iova_domain.o
obj-$(CONFIG_VDPA_USER) += vduse.o
@@ -0,0 +1,694 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* MMU-based software IOTLB.
*
* Copyright (C) 2020-2021 Bytedance Inc. and/or its affiliates. All rights reserved.
*
* Author: Xie Yongji <xieyongji@bytedance.com>
*
*/
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/anon_inodes.h>
#include <linux/highmem.h>
#include <linux/moduleparam.h>
#include <linux/vmalloc.h>
#include <linux/vdpa.h>
#include "iova_domain.h"
static int max_iotlb_entries = 2048;
module_param(max_iotlb_entries, int, 0444);
MODULE_PARM_DESC(max_iotlb_entries,
"Maximum number of iotlb entries. (default: 2048)");
static int vduse_iotlb_add_range(struct vduse_iova_domain *domain,
u64 start, u64 last,
u64 addr, unsigned int perm,
struct file *file, u64 offset)
{
struct vdpa_map_file *map_file;
int ret;
map_file = kmalloc_obj(*map_file, GFP_ATOMIC);
if (!map_file)
return -ENOMEM;
map_file->file = get_file(file);
map_file->offset = offset;
ret = vhost_iotlb_add_range_ctx(domain->iotlb, start, last,
addr, perm, map_file);
if (ret) {
fput(map_file->file);
kfree(map_file);
return ret;
}
return 0;
}
static void vduse_iotlb_del_range(struct vduse_iova_domain *domain,
u64 start, u64 last)
{
struct vdpa_map_file *map_file;
struct vhost_iotlb_map *map;
while ((map = vhost_iotlb_itree_first(domain->iotlb, start, last))) {
map_file = (struct vdpa_map_file *)map->opaque;
fput(map_file->file);
kfree(map_file);
vhost_iotlb_map_free(domain->iotlb, map);
}
}
int vduse_domain_set_map(struct vduse_iova_domain *domain,
struct vhost_iotlb *iotlb)
{
struct vdpa_map_file *map_file;
struct vhost_iotlb_map *map;
u64 start = 0ULL, last = ULLONG_MAX;
int ret;
spin_lock(&domain->iotlb_lock);
vduse_iotlb_del_range(domain, start, last);
for (map = vhost_iotlb_itree_first(iotlb, start, last); map;
map = vhost_iotlb_itree_next(map, start, last)) {
map_file = (struct vdpa_map_file *)map->opaque;
ret = vduse_iotlb_add_range(domain, map->start, map->last,
map->addr, map->perm,
map_file->file,
map_file->offset);
if (ret)
goto err;
}
spin_unlock(&domain->iotlb_lock);
return 0;
err:
vduse_iotlb_del_range(domain, start, last);
spin_unlock(&domain->iotlb_lock);
return ret;
}
void vduse_domain_clear_map(struct vduse_iova_domain *domain,
struct vhost_iotlb *iotlb)
{
struct vhost_iotlb_map *map;
u64 start = 0ULL, last = ULLONG_MAX;
spin_lock(&domain->iotlb_lock);
for (map = vhost_iotlb_itree_first(iotlb, start, last); map;
map = vhost_iotlb_itree_next(map, start, last)) {
vduse_iotlb_del_range(domain, map->start, map->last);
}
spin_unlock(&domain->iotlb_lock);
}
static int vduse_domain_map_bounce_page(struct vduse_iova_domain *domain,
u64 iova, u64 size, u64 paddr)
{
struct vduse_bounce_map *map, *head_map;
struct page *tmp_page;
u64 last = iova + size - 1;
while (iova <= last) {
/*
* When PAGE_SIZE is larger than 4KB, multiple adjacent bounce_maps will
* point to the same memory page of PAGE_SIZE. Since bounce_maps originate
* from IO requests, we may not be able to guarantee that the orig_phys
* values of all IO requests within the same 64KB memory page are contiguous.
* Therefore, we need to store them separately.
*
* Bounce pages are allocated on demand. As a result, it may occur that
* multiple bounce pages corresponding to the same 64KB memory page attempt
* to allocate memory simultaneously, so we use cmpxchg to handle this
* concurrency.
*/
map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
if (!map->bounce_page) {
head_map = &domain->bounce_maps[(iova & PAGE_MASK) >> BOUNCE_MAP_SHIFT];
if (!head_map->bounce_page) {
tmp_page = alloc_page(GFP_ATOMIC | __GFP_ZERO);
if (!tmp_page)
return -ENOMEM;
if (cmpxchg(&head_map->bounce_page, NULL, tmp_page))
__free_page(tmp_page);
}
map->bounce_page = head_map->bounce_page;
}
map->orig_phys = paddr;
paddr += BOUNCE_MAP_SIZE;
iova += BOUNCE_MAP_SIZE;
}
return 0;
}
static void vduse_domain_unmap_bounce_page(struct vduse_iova_domain *domain,
u64 iova, u64 size)
{
struct vduse_bounce_map *map;
u64 last = iova + size - 1;
while (iova <= last) {
map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
map->orig_phys = INVALID_PHYS_ADDR;
iova += BOUNCE_MAP_SIZE;
}
}
static unsigned int offset_in_bounce_page(dma_addr_t addr)
{
return (addr & ~BOUNCE_MAP_MASK);
}
static void do_bounce(phys_addr_t orig, void *addr, size_t size,
enum dma_data_direction dir)
{
unsigned long pfn = PFN_DOWN(orig);
unsigned int offset = offset_in_page(orig);
struct page *page;
unsigned int sz = 0;
while (size) {
sz = min_t(size_t, PAGE_SIZE - offset, size);
page = pfn_to_page(pfn);
if (dir == DMA_TO_DEVICE)
memcpy_from_page(addr, page, offset, sz);
else
memcpy_to_page(page, offset, addr, sz);
size -= sz;
pfn++;
addr += sz;
offset = 0;
}
}
static void vduse_domain_bounce(struct vduse_iova_domain *domain,
dma_addr_t iova, size_t size,
enum dma_data_direction dir)
{
struct vduse_bounce_map *map;
struct page *page;
unsigned int offset, head_offset;
void *addr;
size_t sz;
if (iova >= domain->bounce_size)
return;
while (size) {
map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
head_offset = offset_in_page(iova);
offset = offset_in_bounce_page(iova);
sz = min_t(size_t, BOUNCE_MAP_SIZE - offset, size);
if (WARN_ON(!map->bounce_page ||
map->orig_phys == INVALID_PHYS_ADDR))
return;
page = domain->user_bounce_pages ?
map->user_bounce_page : map->bounce_page;
addr = kmap_local_page(page);
do_bounce(map->orig_phys + offset, addr + head_offset, sz, dir);
kunmap_local(addr);
size -= sz;
iova += sz;
}
}
static struct page *
vduse_domain_get_coherent_page(struct vduse_iova_domain *domain, u64 iova)
{
u64 start = iova & PAGE_MASK;
u64 last = start + PAGE_SIZE - 1;
struct vhost_iotlb_map *map;
struct page *page = NULL;
spin_lock(&domain->iotlb_lock);
map = vhost_iotlb_itree_first(domain->iotlb, start, last);
if (!map)
goto out;
page = pfn_to_page((map->addr + iova - map->start) >> PAGE_SHIFT);
get_page(page);
out:
spin_unlock(&domain->iotlb_lock);
return page;
}
static struct page *
vduse_domain_get_bounce_page(struct vduse_iova_domain *domain, u64 iova)
{
struct vduse_bounce_map *map;
struct page *page = NULL;
read_lock(&domain->bounce_lock);
map = &domain->bounce_maps[iova >> BOUNCE_MAP_SHIFT];
if (domain->user_bounce_pages || !map->bounce_page)
goto out;
page = map->bounce_page;
get_page(page);
out:
read_unlock(&domain->bounce_lock);
return page;
}
static void
vduse_domain_free_kernel_bounce_pages(struct vduse_iova_domain *domain)
{
struct vduse_bounce_map *map;
unsigned long pfn, bounce_pfns;
bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
for (pfn = 0; pfn < bounce_pfns; pfn++) {
map = &domain->bounce_maps[pfn];
if (WARN_ON(map->orig_phys != INVALID_PHYS_ADDR))
continue;
if (!map->bounce_page)
continue;
if (!((pfn << BOUNCE_MAP_SHIFT) & ~PAGE_MASK))
__free_page(map->bounce_page);
map->bounce_page = NULL;
}
}
int vduse_domain_add_user_bounce_pages(struct vduse_iova_domain *domain,
struct page **pages, int count)
{
struct vduse_bounce_map *map, *head_map;
int i, j, ret;
int inner_pages = PAGE_SIZE / BOUNCE_MAP_SIZE;
int bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
struct page *head_page = NULL;
bool need_copy;
/* Now we don't support partial mapping */
if (count != (domain->bounce_size >> PAGE_SHIFT))
return -EINVAL;
write_lock(&domain->bounce_lock);
ret = -EEXIST;
if (domain->user_bounce_pages)
goto out;
for (i = 0; i < count; i++) {
need_copy = false;
head_map = &domain->bounce_maps[(i * inner_pages)];
head_page = head_map->bounce_page;
for (j = 0; j < inner_pages; j++) {
if ((i * inner_pages + j) >= bounce_pfns)
break;
map = &domain->bounce_maps[(i * inner_pages + j)];
/* Copy kernel page to user page if it's in use */
if ((head_page) && (map->orig_phys != INVALID_PHYS_ADDR))
need_copy = true;
map->user_bounce_page = pages[i];
}
get_page(pages[i]);
if ((head_page) && (need_copy))
memcpy_to_page(pages[i], 0,
page_address(head_page),
PAGE_SIZE);
}
domain->user_bounce_pages = true;
ret = 0;
out:
write_unlock(&domain->bounce_lock);
return ret;
}
void vduse_domain_remove_user_bounce_pages(struct vduse_iova_domain *domain)
{
struct vduse_bounce_map *map, *head_map;
unsigned long i, j, count;
int inner_pages = PAGE_SIZE / BOUNCE_MAP_SIZE;
int bounce_pfns = domain->bounce_size >> BOUNCE_MAP_SHIFT;
struct page *head_page = NULL;
bool need_copy;
write_lock(&domain->bounce_lock);
if (!domain->user_bounce_pages)
goto out;
count = domain->bounce_size >> PAGE_SHIFT;
for (i = 0; i < count; i++) {
need_copy = false;
head_map = &domain->bounce_maps[(i * inner_pages)];
if (WARN_ON(!head_map->user_bounce_page))
continue;
head_page = head_map->user_bounce_page;
for (j = 0; j < inner_pages; j++) {
if ((i * inner_pages + j) >= bounce_pfns)
break;
map = &domain->bounce_maps[(i * inner_pages + j)];
if (WARN_ON(!map->user_bounce_page))
continue;
/* Copy user page to kernel page if it's in use */
if ((map->orig_phys != INVALID_PHYS_ADDR) && (head_map->bounce_page))
need_copy = true;
map->user_bounce_page = NULL;
}
if (need_copy)
memcpy_from_page(page_address(head_map->bounce_page),
head_page, 0, PAGE_SIZE);
put_page(head_page);
}
domain->user_bounce_pages = false;
out:
write_unlock(&domain->bounce_lock);
}
void vduse_domain_reset_bounce_map(struct vduse_iova_domain *domain)
{
if (!domain->bounce_map)
return;
spin_lock(&domain->iotlb_lock);
if (!domain->bounce_map)
goto unlock;
vduse_iotlb_del_range(domain, 0, domain->bounce_size - 1);
domain->bounce_map = 0;
unlock:
spin_unlock(&domain->iotlb_lock);
}
static int vduse_domain_init_bounce_map(struct vduse_iova_domain *domain)
{
int ret = 0;
if (domain->bounce_map)
return 0;
spin_lock(&domain->iotlb_lock);
if (domain->bounce_map)
goto unlock;
ret = vduse_iotlb_add_range(domain, 0, domain->bounce_size - 1,
0, VHOST_MAP_RW, domain->file, 0);
if (ret)
goto unlock;
domain->bounce_map = 1;
unlock:
spin_unlock(&domain->iotlb_lock);
return ret;
}
static dma_addr_t
vduse_domain_alloc_iova(struct iova_domain *iovad,
unsigned long size, unsigned long limit)
{
unsigned long shift = iova_shift(iovad);
unsigned long iova_len = iova_align(iovad, size) >> shift;
unsigned long iova_pfn;
iova_pfn = alloc_iova_fast(iovad, iova_len, limit >> shift, true);
return (dma_addr_t)iova_pfn << shift;
}
static void vduse_domain_free_iova(struct iova_domain *iovad,
dma_addr_t iova, size_t size)
{
unsigned long shift = iova_shift(iovad);
unsigned long iova_len = iova_align(iovad, size) >> shift;
free_iova_fast(iovad, iova >> shift, iova_len);
}
void vduse_domain_sync_single_for_device(struct vduse_iova_domain *domain,
dma_addr_t dma_addr, size_t size,
enum dma_data_direction dir)
{
read_lock(&domain->bounce_lock);
if (dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL)
vduse_domain_bounce(domain, dma_addr, size, DMA_TO_DEVICE);
read_unlock(&domain->bounce_lock);
}
void vduse_domain_sync_single_for_cpu(struct vduse_iova_domain *domain,
dma_addr_t dma_addr, size_t size,
enum dma_data_direction dir)
{
read_lock(&domain->bounce_lock);
if (dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL)
vduse_domain_bounce(domain, dma_addr, size, DMA_FROM_DEVICE);
read_unlock(&domain->bounce_lock);
}
dma_addr_t vduse_domain_map_page(struct vduse_iova_domain *domain,
struct page *page, unsigned long offset,
size_t size, enum dma_data_direction dir,
unsigned long attrs)
{
struct iova_domain *iovad = &domain->stream_iovad;
unsigned long limit = domain->bounce_size - 1;
phys_addr_t pa = page_to_phys(page) + offset;
dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
if (!iova)
return DMA_MAPPING_ERROR;
if (vduse_domain_init_bounce_map(domain))
goto err;
read_lock(&domain->bounce_lock);
if (vduse_domain_map_bounce_page(domain, (u64)iova, (u64)size, pa))
goto err_unlock;
if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC) &&
(dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL))
vduse_domain_bounce(domain, iova, size, DMA_TO_DEVICE);
read_unlock(&domain->bounce_lock);
return iova;
err_unlock:
read_unlock(&domain->bounce_lock);
err:
vduse_domain_free_iova(iovad, iova, size);
return DMA_MAPPING_ERROR;
}
void vduse_domain_unmap_page(struct vduse_iova_domain *domain,
dma_addr_t dma_addr, size_t size,
enum dma_data_direction dir, unsigned long attrs)
{
struct iova_domain *iovad = &domain->stream_iovad;
read_lock(&domain->bounce_lock);
if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC) &&
(dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL))
vduse_domain_bounce(domain, dma_addr, size, DMA_FROM_DEVICE);
vduse_domain_unmap_bounce_page(domain, (u64)dma_addr, (u64)size);
read_unlock(&domain->bounce_lock);
vduse_domain_free_iova(iovad, dma_addr, size);
}
dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
size_t size, void *orig)
{
struct iova_domain *iovad = &domain->consistent_iovad;
unsigned long limit = domain->iova_limit;
dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
if (!iova)
return DMA_MAPPING_ERROR;
spin_lock(&domain->iotlb_lock);
if (vduse_iotlb_add_range(domain, (u64)iova, (u64)iova + size - 1,
virt_to_phys(orig), VHOST_MAP_RW,
domain->file, (u64)iova)) {
spin_unlock(&domain->iotlb_lock);
goto err;
}
spin_unlock(&domain->iotlb_lock);
return iova;
err:
vduse_domain_free_iova(iovad, iova, size);
return DMA_MAPPING_ERROR;
}
void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
dma_addr_t dma_addr, unsigned long attrs)
{
struct iova_domain *iovad = &domain->consistent_iovad;
struct vhost_iotlb_map *map;
struct vdpa_map_file *map_file;
spin_lock(&domain->iotlb_lock);
map = vhost_iotlb_itree_first(domain->iotlb, (u64)dma_addr,
(u64)dma_addr + size - 1);
if (WARN_ON(!map)) {
spin_unlock(&domain->iotlb_lock);
return;
}
map_file = (struct vdpa_map_file *)map->opaque;
fput(map_file->file);
kfree(map_file);
vhost_iotlb_map_free(domain->iotlb, map);
spin_unlock(&domain->iotlb_lock);
vduse_domain_free_iova(iovad, dma_addr, size);
}
static vm_fault_t vduse_domain_mmap_fault(struct vm_fault *vmf)
{
struct vduse_iova_domain *domain = vmf->vma->vm_private_data;
unsigned long iova = vmf->pgoff << PAGE_SHIFT;
struct page *page;
if (!domain)
return VM_FAULT_SIGBUS;
if (iova < domain->bounce_size)
page = vduse_domain_get_bounce_page(domain, iova);
else
page = vduse_domain_get_coherent_page(domain, iova);
if (!page)
return VM_FAULT_SIGBUS;
vmf->page = page;
return 0;
}
static const struct vm_operations_struct vduse_domain_mmap_ops = {
.fault = vduse_domain_mmap_fault,
};
static int vduse_domain_mmap(struct file *file, struct vm_area_struct *vma)
{
struct vduse_iova_domain *domain = file->private_data;
vm_flags_set(vma, VM_DONTDUMP | VM_DONTEXPAND);
vma->vm_private_data = domain;
vma->vm_ops = &vduse_domain_mmap_ops;
return 0;
}
static int vduse_domain_release(struct inode *inode, struct file *file)
{
struct vduse_iova_domain *domain = file->private_data;
spin_lock(&domain->iotlb_lock);
vduse_iotlb_del_range(domain, 0, ULLONG_MAX);
vduse_domain_remove_user_bounce_pages(domain);
vduse_domain_free_kernel_bounce_pages(domain);
spin_unlock(&domain->iotlb_lock);
put_iova_domain(&domain->stream_iovad);
put_iova_domain(&domain->consistent_iovad);
vhost_iotlb_free(domain->iotlb);
vfree(domain->bounce_maps);
kfree(domain);
return 0;
}
static const struct file_operations vduse_domain_fops = {
.owner = THIS_MODULE,
.mmap = vduse_domain_mmap,
.release = vduse_domain_release,
};
void vduse_domain_destroy(struct vduse_iova_domain *domain)
{
fput(domain->file);
}
struct vduse_iova_domain *
vduse_domain_create(unsigned long iova_limit, size_t bounce_size)
{
struct vduse_iova_domain *domain;
struct file *file;
struct vduse_bounce_map *map;
unsigned long pfn, bounce_pfns;
int ret;
bounce_pfns = PAGE_ALIGN(bounce_size) >> BOUNCE_MAP_SHIFT;
if (iova_limit <= bounce_size)
return NULL;
if (max_iotlb_entries <= 0)
return NULL;
domain = kzalloc_obj(*domain);
if (!domain)
return NULL;
domain->iotlb = vhost_iotlb_alloc(max_iotlb_entries, 0);
if (!domain->iotlb)
goto err_iotlb;
domain->iova_limit = iova_limit;
domain->bounce_size = PAGE_ALIGN(bounce_size);
domain->bounce_maps = vzalloc(bounce_pfns *
sizeof(struct vduse_bounce_map));
if (!domain->bounce_maps)
goto err_map;
for (pfn = 0; pfn < bounce_pfns; pfn++) {
map = &domain->bounce_maps[pfn];
map->orig_phys = INVALID_PHYS_ADDR;
}
file = anon_inode_getfile("[vduse-domain]", &vduse_domain_fops,
domain, O_RDWR);
if (IS_ERR(file))
goto err_file;
domain->file = file;
rwlock_init(&domain->bounce_lock);
spin_lock_init(&domain->iotlb_lock);
init_iova_domain(&domain->stream_iovad,
BOUNCE_MAP_SIZE, IOVA_START_PFN);
ret = iova_domain_init_rcaches(&domain->stream_iovad);
if (ret)
goto err_iovad_stream;
init_iova_domain(&domain->consistent_iovad,
PAGE_SIZE, bounce_pfns);
ret = iova_domain_init_rcaches(&domain->consistent_iovad);
if (ret)
goto err_iovad_consistent;
return domain;
err_iovad_consistent:
put_iova_domain(&domain->stream_iovad);
err_iovad_stream:
fput(file);
err_file:
vfree(domain->bounce_maps);
err_map:
vhost_iotlb_free(domain->iotlb);
err_iotlb:
kfree(domain);
return NULL;
}
int vduse_domain_init(void)
{
return iova_cache_get();
}
void vduse_domain_exit(void)
{
iova_cache_put();
}
@@ -0,0 +1,90 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* MMU-based software IOTLB.
*
* Copyright (C) 2020-2021 Bytedance Inc. and/or its affiliates. All rights reserved.
*
* Author: Xie Yongji <xieyongji@bytedance.com>
*
*/
#ifndef _VDUSE_IOVA_DOMAIN_H
#define _VDUSE_IOVA_DOMAIN_H
#include <linux/iova.h>
#include <linux/dma-mapping.h>
#include <linux/vhost_iotlb.h>
#define IOVA_START_PFN 1
#define BOUNCE_MAP_SHIFT 12
#define BOUNCE_MAP_SIZE (1 << BOUNCE_MAP_SHIFT)
#define BOUNCE_MAP_MASK (~(BOUNCE_MAP_SIZE - 1))
#define BOUNCE_MAP_ALIGN(addr) (((addr) + BOUNCE_MAP_SIZE - 1) & ~(BOUNCE_MAP_SIZE - 1))
struct vduse_bounce_map {
struct page *bounce_page;
struct page *user_bounce_page;
u64 orig_phys;
};
struct vduse_iova_domain {
struct iova_domain stream_iovad;
struct iova_domain consistent_iovad;
struct vduse_bounce_map *bounce_maps;
size_t bounce_size;
unsigned long iova_limit;
int bounce_map;
struct vhost_iotlb *iotlb;
spinlock_t iotlb_lock;
struct file *file;
bool user_bounce_pages;
rwlock_t bounce_lock;
};
int vduse_domain_set_map(struct vduse_iova_domain *domain,
struct vhost_iotlb *iotlb);
void vduse_domain_clear_map(struct vduse_iova_domain *domain,
struct vhost_iotlb *iotlb);
void vduse_domain_sync_single_for_device(struct vduse_iova_domain *domain,
dma_addr_t dma_addr, size_t size,
enum dma_data_direction dir);
void vduse_domain_sync_single_for_cpu(struct vduse_iova_domain *domain,
dma_addr_t dma_addr, size_t size,
enum dma_data_direction dir);
dma_addr_t vduse_domain_map_page(struct vduse_iova_domain *domain,
struct page *page, unsigned long offset,
size_t size, enum dma_data_direction dir,
unsigned long attrs);
void vduse_domain_unmap_page(struct vduse_iova_domain *domain,
dma_addr_t dma_addr, size_t size,
enum dma_data_direction dir, unsigned long attrs);
dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
size_t size, void *orig);
void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
dma_addr_t dma_addr, unsigned long attrs);
void vduse_domain_reset_bounce_map(struct vduse_iova_domain *domain);
int vduse_domain_add_user_bounce_pages(struct vduse_iova_domain *domain,
struct page **pages, int count);
void vduse_domain_remove_user_bounce_pages(struct vduse_iova_domain *domain);
void vduse_domain_destroy(struct vduse_iova_domain *domain);
struct vduse_iova_domain *vduse_domain_create(unsigned long iova_limit,
size_t bounce_size);
int vduse_domain_init(void);
void vduse_domain_exit(void);
#endif /* _VDUSE_IOVA_DOMAIN_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VP_VDPA) += vp_vdpa.o
@@ -0,0 +1,702 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vDPA bridge driver for modern virtio-pci device
*
* Copyright (c) 2020, Red Hat Inc. All rights reserved.
* Author: Jason Wang <jasowang@redhat.com>
*
* Based on virtio_pci_modern.c.
*/
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/vdpa.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ring.h>
#include <linux/virtio_pci.h>
#include <linux/virtio_pci_modern.h>
#include <uapi/linux/vdpa.h>
#define VP_VDPA_QUEUE_MAX 256
#define VP_VDPA_DRIVER_NAME "vp_vdpa"
#define VP_VDPA_NAME_SIZE 256
struct vp_vring {
void __iomem *notify;
char msix_name[VP_VDPA_NAME_SIZE];
struct vdpa_callback cb;
resource_size_t notify_pa;
int irq;
};
struct vp_vdpa {
struct vdpa_device vdpa;
struct virtio_pci_modern_device *mdev;
struct vp_vring *vring;
struct vdpa_callback config_cb;
u64 device_features;
char msix_name[VP_VDPA_NAME_SIZE];
int config_irq;
int queues;
int vectors;
};
struct vp_vdpa_mgmtdev {
struct vdpa_mgmt_dev mgtdev;
struct virtio_pci_modern_device *mdev;
struct vp_vdpa *vp_vdpa;
};
static struct vp_vdpa *vdpa_to_vp(struct vdpa_device *vdpa)
{
return container_of(vdpa, struct vp_vdpa, vdpa);
}
static struct virtio_pci_modern_device *vdpa_to_mdev(struct vdpa_device *vdpa)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
return vp_vdpa->mdev;
}
static struct virtio_pci_modern_device *vp_vdpa_to_mdev(struct vp_vdpa *vp_vdpa)
{
return vp_vdpa->mdev;
}
static u64 vp_vdpa_get_device_features(struct vdpa_device *vdpa)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
return vp_vdpa->device_features;
}
static int vp_vdpa_set_driver_features(struct vdpa_device *vdpa, u64 features)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
vp_modern_set_features(mdev, features);
return 0;
}
static u64 vp_vdpa_get_driver_features(struct vdpa_device *vdpa)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
return vp_modern_get_driver_features(mdev);
}
static u8 vp_vdpa_get_status(struct vdpa_device *vdpa)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
return vp_modern_get_status(mdev);
}
static int vp_vdpa_get_vq_irq(struct vdpa_device *vdpa, u16 idx)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
int irq = vp_vdpa->vring[idx].irq;
if (irq == VIRTIO_MSI_NO_VECTOR)
return -EINVAL;
return irq;
}
static void vp_vdpa_free_irq(struct vp_vdpa *vp_vdpa)
{
struct virtio_pci_modern_device *mdev = vp_vdpa_to_mdev(vp_vdpa);
struct pci_dev *pdev = mdev->pci_dev;
int i;
for (i = 0; i < vp_vdpa->queues; i++) {
if (vp_vdpa->vring[i].irq != VIRTIO_MSI_NO_VECTOR) {
vp_modern_queue_vector(mdev, i, VIRTIO_MSI_NO_VECTOR);
devm_free_irq(&pdev->dev, vp_vdpa->vring[i].irq,
&vp_vdpa->vring[i]);
vp_vdpa->vring[i].irq = VIRTIO_MSI_NO_VECTOR;
}
}
if (vp_vdpa->config_irq != VIRTIO_MSI_NO_VECTOR) {
vp_modern_config_vector(mdev, VIRTIO_MSI_NO_VECTOR);
devm_free_irq(&pdev->dev, vp_vdpa->config_irq, vp_vdpa);
vp_vdpa->config_irq = VIRTIO_MSI_NO_VECTOR;
}
if (vp_vdpa->vectors) {
pci_free_irq_vectors(pdev);
vp_vdpa->vectors = 0;
}
}
static irqreturn_t vp_vdpa_vq_handler(int irq, void *arg)
{
struct vp_vring *vring = arg;
if (vring->cb.callback)
return vring->cb.callback(vring->cb.private);
return IRQ_HANDLED;
}
static irqreturn_t vp_vdpa_config_handler(int irq, void *arg)
{
struct vp_vdpa *vp_vdpa = arg;
if (vp_vdpa->config_cb.callback)
return vp_vdpa->config_cb.callback(vp_vdpa->config_cb.private);
return IRQ_HANDLED;
}
static int vp_vdpa_request_irq(struct vp_vdpa *vp_vdpa)
{
struct virtio_pci_modern_device *mdev = vp_vdpa_to_mdev(vp_vdpa);
struct pci_dev *pdev = mdev->pci_dev;
int i, ret, irq;
int queues = vp_vdpa->queues;
int vectors = 1;
int msix_vec = 0;
for (i = 0; i < queues; i++) {
if (vp_vdpa->vring[i].cb.callback)
vectors++;
}
ret = pci_alloc_irq_vectors(pdev, vectors, vectors, PCI_IRQ_MSIX);
if (ret != vectors) {
dev_err(&pdev->dev,
"vp_vdpa: fail to allocate irq vectors want %d but %d\n",
vectors, ret);
return ret;
}
vp_vdpa->vectors = vectors;
for (i = 0; i < queues; i++) {
if (!vp_vdpa->vring[i].cb.callback)
continue;
snprintf(vp_vdpa->vring[i].msix_name, VP_VDPA_NAME_SIZE,
"vp-vdpa[%s]-%d\n", pci_name(pdev), i);
irq = pci_irq_vector(pdev, msix_vec);
ret = devm_request_irq(&pdev->dev, irq,
vp_vdpa_vq_handler,
0, vp_vdpa->vring[i].msix_name,
&vp_vdpa->vring[i]);
if (ret) {
dev_err(&pdev->dev,
"vp_vdpa: fail to request irq for vq %d\n", i);
goto err;
}
vp_modern_queue_vector(mdev, i, msix_vec);
vp_vdpa->vring[i].irq = irq;
msix_vec++;
}
snprintf(vp_vdpa->msix_name, VP_VDPA_NAME_SIZE, "vp-vdpa[%s]-config\n",
pci_name(pdev));
irq = pci_irq_vector(pdev, msix_vec);
ret = devm_request_irq(&pdev->dev, irq, vp_vdpa_config_handler, 0,
vp_vdpa->msix_name, vp_vdpa);
if (ret) {
dev_err(&pdev->dev,
"vp_vdpa: fail to request irq for config: %d\n", ret);
goto err;
}
vp_modern_config_vector(mdev, msix_vec);
vp_vdpa->config_irq = irq;
return 0;
err:
vp_vdpa_free_irq(vp_vdpa);
return ret;
}
static void vp_vdpa_set_status(struct vdpa_device *vdpa, u8 status)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
struct virtio_pci_modern_device *mdev = vp_vdpa_to_mdev(vp_vdpa);
u8 s = vp_vdpa_get_status(vdpa);
if (status & VIRTIO_CONFIG_S_DRIVER_OK &&
!(s & VIRTIO_CONFIG_S_DRIVER_OK)) {
if (vp_vdpa_request_irq(vp_vdpa)) {
WARN_ON(1);
return;
}
}
vp_modern_set_status(mdev, status);
}
static int vp_vdpa_reset(struct vdpa_device *vdpa)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
struct virtio_pci_modern_device *mdev = vp_vdpa_to_mdev(vp_vdpa);
u8 s = vp_vdpa_get_status(vdpa);
vp_modern_set_status(mdev, 0);
if (s & VIRTIO_CONFIG_S_DRIVER_OK)
vp_vdpa_free_irq(vp_vdpa);
return 0;
}
static u16 vp_vdpa_get_vq_num_max(struct vdpa_device *vdpa)
{
return VP_VDPA_QUEUE_MAX;
}
static int vp_vdpa_get_vq_state(struct vdpa_device *vdpa, u16 qid,
struct vdpa_vq_state *state)
{
/* Note that this is not supported by virtio specification, so
* we return -EOPNOTSUPP here. This means we can't support live
* migration, vhost device start/stop.
*/
return -EOPNOTSUPP;
}
static int vp_vdpa_set_vq_state_split(struct vdpa_device *vdpa,
const struct vdpa_vq_state *state)
{
const struct vdpa_vq_state_split *split = &state->split;
if (split->avail_index == 0)
return 0;
return -EOPNOTSUPP;
}
static int vp_vdpa_set_vq_state_packed(struct vdpa_device *vdpa,
const struct vdpa_vq_state *state)
{
const struct vdpa_vq_state_packed *packed = &state->packed;
if (packed->last_avail_counter == 1 &&
packed->last_avail_idx == 0 &&
packed->last_used_counter == 1 &&
packed->last_used_idx == 0)
return 0;
return -EOPNOTSUPP;
}
static int vp_vdpa_set_vq_state(struct vdpa_device *vdpa, u16 qid,
const struct vdpa_vq_state *state)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
/* Note that this is not supported by virtio specification.
* But if the state is by chance equal to the device initial
* state, we can let it go.
*/
if ((vp_modern_get_status(mdev) & VIRTIO_CONFIG_S_FEATURES_OK) &&
!vp_modern_get_queue_enable(mdev, qid)) {
if (vp_modern_get_driver_features(mdev) &
BIT_ULL(VIRTIO_F_RING_PACKED))
return vp_vdpa_set_vq_state_packed(vdpa, state);
else
return vp_vdpa_set_vq_state_split(vdpa, state);
}
return -EOPNOTSUPP;
}
static void vp_vdpa_set_vq_cb(struct vdpa_device *vdpa, u16 qid,
struct vdpa_callback *cb)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
vp_vdpa->vring[qid].cb = *cb;
}
static void vp_vdpa_set_vq_ready(struct vdpa_device *vdpa,
u16 qid, bool ready)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
vp_modern_set_queue_enable(mdev, qid, ready);
}
static bool vp_vdpa_get_vq_ready(struct vdpa_device *vdpa, u16 qid)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
return vp_modern_get_queue_enable(mdev, qid);
}
static void vp_vdpa_set_vq_num(struct vdpa_device *vdpa, u16 qid,
u32 num)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
vp_modern_set_queue_size(mdev, qid, num);
}
static u16 vp_vdpa_get_vq_size(struct vdpa_device *vdpa, u16 qid)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
return vp_modern_get_queue_size(mdev, qid);
}
static int vp_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 qid,
u64 desc_area, u64 driver_area,
u64 device_area)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
vp_modern_queue_address(mdev, qid, desc_area,
driver_area, device_area);
return 0;
}
static void vp_vdpa_kick_vq(struct vdpa_device *vdpa, u16 qid)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
vp_iowrite16(qid, vp_vdpa->vring[qid].notify);
}
static void vp_vdpa_kick_vq_with_data(struct vdpa_device *vdpa, u32 data)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
u16 qid = data & 0xFFFF;
vp_iowrite32(data, vp_vdpa->vring[qid].notify);
}
static u32 vp_vdpa_get_generation(struct vdpa_device *vdpa)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
return vp_modern_generation(mdev);
}
static u32 vp_vdpa_get_device_id(struct vdpa_device *vdpa)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
return mdev->id.device;
}
static u32 vp_vdpa_get_vendor_id(struct vdpa_device *vdpa)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
return mdev->id.vendor;
}
static u32 vp_vdpa_get_vq_align(struct vdpa_device *vdpa)
{
return PAGE_SIZE;
}
static size_t vp_vdpa_get_config_size(struct vdpa_device *vdpa)
{
struct virtio_pci_modern_device *mdev = vdpa_to_mdev(vdpa);
return mdev->device_len;
}
static void vp_vdpa_get_config(struct vdpa_device *vdpa,
unsigned int offset,
void *buf, unsigned int len)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
struct virtio_pci_modern_device *mdev = vp_vdpa_to_mdev(vp_vdpa);
u8 old, new;
u8 *p;
int i;
do {
old = vp_ioread8(&mdev->common->config_generation);
p = buf;
for (i = 0; i < len; i++)
*p++ = vp_ioread8(mdev->device + offset + i);
new = vp_ioread8(&mdev->common->config_generation);
} while (old != new);
}
static void vp_vdpa_set_config(struct vdpa_device *vdpa,
unsigned int offset, const void *buf,
unsigned int len)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
struct virtio_pci_modern_device *mdev = vp_vdpa_to_mdev(vp_vdpa);
const u8 *p = buf;
int i;
for (i = 0; i < len; i++)
vp_iowrite8(*p++, mdev->device + offset + i);
}
static void vp_vdpa_set_config_cb(struct vdpa_device *vdpa,
struct vdpa_callback *cb)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
vp_vdpa->config_cb = *cb;
}
static struct vdpa_notification_area
vp_vdpa_get_vq_notification(struct vdpa_device *vdpa, u16 qid)
{
struct vp_vdpa *vp_vdpa = vdpa_to_vp(vdpa);
struct virtio_pci_modern_device *mdev = vp_vdpa_to_mdev(vp_vdpa);
struct vdpa_notification_area notify;
notify.addr = vp_vdpa->vring[qid].notify_pa;
notify.size = mdev->notify_offset_multiplier;
return notify;
}
static const struct vdpa_config_ops vp_vdpa_ops = {
.get_device_features = vp_vdpa_get_device_features,
.set_driver_features = vp_vdpa_set_driver_features,
.get_driver_features = vp_vdpa_get_driver_features,
.get_status = vp_vdpa_get_status,
.set_status = vp_vdpa_set_status,
.reset = vp_vdpa_reset,
.get_vq_num_max = vp_vdpa_get_vq_num_max,
.get_vq_state = vp_vdpa_get_vq_state,
.get_vq_notification = vp_vdpa_get_vq_notification,
.set_vq_state = vp_vdpa_set_vq_state,
.set_vq_cb = vp_vdpa_set_vq_cb,
.set_vq_ready = vp_vdpa_set_vq_ready,
.get_vq_ready = vp_vdpa_get_vq_ready,
.set_vq_num = vp_vdpa_set_vq_num,
.get_vq_size = vp_vdpa_get_vq_size,
.set_vq_address = vp_vdpa_set_vq_address,
.kick_vq = vp_vdpa_kick_vq,
.kick_vq_with_data = vp_vdpa_kick_vq_with_data,
.get_generation = vp_vdpa_get_generation,
.get_device_id = vp_vdpa_get_device_id,
.get_vendor_id = vp_vdpa_get_vendor_id,
.get_vq_align = vp_vdpa_get_vq_align,
.get_config_size = vp_vdpa_get_config_size,
.get_config = vp_vdpa_get_config,
.set_config = vp_vdpa_set_config,
.set_config_cb = vp_vdpa_set_config_cb,
.get_vq_irq = vp_vdpa_get_vq_irq,
};
static void vp_vdpa_free_irq_vectors(void *data)
{
pci_free_irq_vectors(data);
}
static int vp_vdpa_dev_add(struct vdpa_mgmt_dev *v_mdev, const char *name,
const struct vdpa_dev_set_config *add_config)
{
struct vp_vdpa_mgmtdev *vp_vdpa_mgtdev =
container_of(v_mdev, struct vp_vdpa_mgmtdev, mgtdev);
struct virtio_pci_modern_device *mdev = vp_vdpa_mgtdev->mdev;
struct pci_dev *pdev = mdev->pci_dev;
struct device *dev = &pdev->dev;
struct vp_vdpa *vp_vdpa = NULL;
u64 device_features;
int ret, i;
vp_vdpa = vdpa_alloc_device(struct vp_vdpa, vdpa,
dev, &vp_vdpa_ops, NULL,
1, 1, name, false);
if (IS_ERR(vp_vdpa)) {
dev_err(dev, "vp_vdpa: Failed to allocate vDPA structure\n");
return PTR_ERR(vp_vdpa);
}
vp_vdpa_mgtdev->vp_vdpa = vp_vdpa;
vp_vdpa->vdpa.vmap.dma_dev = &pdev->dev;
vp_vdpa->queues = vp_modern_get_num_queues(mdev);
vp_vdpa->mdev = mdev;
device_features = vp_modern_get_features(mdev);
if (add_config->mask & BIT_ULL(VDPA_ATTR_DEV_FEATURES)) {
if (add_config->device_features & ~device_features) {
ret = -EINVAL;
dev_err(&pdev->dev, "Try to provision features "
"that are not supported by the device: "
"device_features 0x%llx provisioned 0x%llx\n",
device_features, add_config->device_features);
goto err;
}
device_features = add_config->device_features;
}
vp_vdpa->device_features = device_features;
ret = devm_add_action_or_reset(dev, vp_vdpa_free_irq_vectors, pdev);
if (ret) {
dev_err(&pdev->dev,
"Failed for adding devres for freeing irq vectors\n");
goto err;
}
vp_vdpa->vring = devm_kcalloc(&pdev->dev, vp_vdpa->queues,
sizeof(*vp_vdpa->vring),
GFP_KERNEL);
if (!vp_vdpa->vring) {
ret = -ENOMEM;
dev_err(&pdev->dev, "Fail to allocate virtqueues\n");
goto err;
}
for (i = 0; i < vp_vdpa->queues; i++) {
vp_vdpa->vring[i].irq = VIRTIO_MSI_NO_VECTOR;
vp_vdpa->vring[i].notify =
vp_modern_map_vq_notify(mdev, i,
&vp_vdpa->vring[i].notify_pa);
if (!vp_vdpa->vring[i].notify) {
ret = -EINVAL;
dev_warn(&pdev->dev, "Fail to map vq notify %d\n", i);
goto err;
}
}
vp_vdpa->config_irq = VIRTIO_MSI_NO_VECTOR;
vp_vdpa->vdpa.mdev = &vp_vdpa_mgtdev->mgtdev;
ret = _vdpa_register_device(&vp_vdpa->vdpa, vp_vdpa->queues);
if (ret) {
dev_err(&pdev->dev, "Failed to register to vdpa bus\n");
goto err;
}
return 0;
err:
put_device(&vp_vdpa->vdpa.dev);
return ret;
}
static void vp_vdpa_dev_del(struct vdpa_mgmt_dev *v_mdev,
struct vdpa_device *dev)
{
struct vp_vdpa_mgmtdev *vp_vdpa_mgtdev =
container_of(v_mdev, struct vp_vdpa_mgmtdev, mgtdev);
struct vp_vdpa *vp_vdpa = vp_vdpa_mgtdev->vp_vdpa;
_vdpa_unregister_device(&vp_vdpa->vdpa);
vp_vdpa_mgtdev->vp_vdpa = NULL;
}
static const struct vdpa_mgmtdev_ops vp_vdpa_mdev_ops = {
.dev_add = vp_vdpa_dev_add,
.dev_del = vp_vdpa_dev_del,
};
static int vp_vdpa_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct vp_vdpa_mgmtdev *vp_vdpa_mgtdev = NULL;
struct vdpa_mgmt_dev *mgtdev;
struct device *dev = &pdev->dev;
struct virtio_pci_modern_device *mdev = NULL;
struct virtio_device_id *mdev_id = NULL;
int err;
vp_vdpa_mgtdev = kzalloc_obj(*vp_vdpa_mgtdev);
if (!vp_vdpa_mgtdev)
return -ENOMEM;
mgtdev = &vp_vdpa_mgtdev->mgtdev;
mgtdev->ops = &vp_vdpa_mdev_ops;
mgtdev->device = dev;
mdev = kzalloc_obj(struct virtio_pci_modern_device);
if (!mdev) {
err = -ENOMEM;
goto mdev_err;
}
/*
* id_table should be a null terminated array, so allocate one additional
* entry here, see vdpa_mgmtdev_get_classes().
*/
mdev_id = kzalloc_objs(struct virtio_device_id, 2);
if (!mdev_id) {
err = -ENOMEM;
goto mdev_id_err;
}
vp_vdpa_mgtdev->mdev = mdev;
mdev->pci_dev = pdev;
err = pcim_enable_device(pdev);
if (err) {
goto probe_err;
}
err = vp_modern_probe(mdev);
if (err) {
dev_err(&pdev->dev, "Failed to probe modern PCI device\n");
goto probe_err;
}
mdev_id[0].device = mdev->id.device;
mdev_id[0].vendor = mdev->id.vendor;
mgtdev->id_table = mdev_id;
mgtdev->max_supported_vqs = vp_modern_get_num_queues(mdev);
mgtdev->supported_features = vp_modern_get_features(mdev);
mgtdev->config_attr_mask = (1 << VDPA_ATTR_DEV_FEATURES);
pci_set_master(pdev);
pci_set_drvdata(pdev, vp_vdpa_mgtdev);
err = vdpa_mgmtdev_register(mgtdev);
if (err) {
dev_err(&pdev->dev, "Failed to register vdpa mgmtdev device\n");
goto register_err;
}
return 0;
register_err:
vp_modern_remove(vp_vdpa_mgtdev->mdev);
probe_err:
kfree(mdev_id);
mdev_id_err:
kfree(mdev);
mdev_err:
kfree(vp_vdpa_mgtdev);
return err;
}
static void vp_vdpa_remove(struct pci_dev *pdev)
{
struct vp_vdpa_mgmtdev *vp_vdpa_mgtdev = pci_get_drvdata(pdev);
struct virtio_pci_modern_device *mdev = NULL;
mdev = vp_vdpa_mgtdev->mdev;
vdpa_mgmtdev_unregister(&vp_vdpa_mgtdev->mgtdev);
vp_modern_remove(mdev);
kfree(vp_vdpa_mgtdev->mgtdev.id_table);
kfree(mdev);
kfree(vp_vdpa_mgtdev);
}
static struct pci_driver vp_vdpa_driver = {
.name = "vp-vdpa",
.id_table = NULL, /* only dynamic ids */
.probe = vp_vdpa_probe,
.remove = vp_vdpa_remove,
};
module_pci_driver(vp_vdpa_driver);
MODULE_AUTHOR("Jason Wang <jasowang@redhat.com>");
MODULE_DESCRIPTION("vp-vdpa");
MODULE_LICENSE("GPL");
MODULE_VERSION("1");