virtio: vdpa: new SolidNET DPU driver.

This commit includes:
 1) The driver to manage the controlplane over vDPA bus.
 2) A HW monitor device to read health values from the DPU.

Signed-off-by: Alvaro Karsz <alvaro.karsz@solid-run.com>
Acked-by: Jason Wang <jasowang@redhat.com>
Message-Id: <20230110165638.123745-4-alvaro.karsz@solid-run.com>
Message-Id: <20230209075128.78915-1-alvaro.karsz@solid-run.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
Alvaro Karsz 2023-01-10 18:56:38 +02:00 committed by Michael S. Tsirkin
parent d089d69cc1
commit 51a8f9d7f5
7 changed files with 1522 additions and 0 deletions

View File

@ -22061,6 +22061,10 @@ IFCVF VIRTIO DATA PATH ACCELERATOR
R: Zhu Lingshan <lingshan.zhu@intel.com>
F: drivers/vdpa/ifcvf/
SNET DPU VIRTIO DATA PATH ACCELERATOR
R: Alvaro Karsz <alvaro.karsz@solid-run.com>
F: drivers/vdpa/solidrun/
VIRTIO BALLOON
M: "Michael S. Tsirkin" <mst@redhat.com>
M: David Hildenbrand <david@redhat.com>

View File

@ -98,4 +98,22 @@ config ALIBABA_ENI_VDPA
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.
endif # VDPA

View File

@ -6,3 +6,4 @@ 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/

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_SNET_VDPA) += snet_vdpa.o
snet_vdpa-$(CONFIG_SNET_VDPA) += snet_main.o
ifdef CONFIG_HWMON
snet_vdpa-$(CONFIG_SNET_VDPA) += snet_hwmon.o
endif

View File

@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SolidRun DPU driver for control plane
*
* Copyright (C) 2022 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 *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

View File

@ -0,0 +1,194 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* SolidRun DPU driver for control plane
*
* Copyright (C) 2022 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))
/* VQ struct */
struct snet_vq {
/* VQ callback */
struct vdpa_callback cb;
/* 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;
/* 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 SNET messages */
u32 msg_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
#endif //_SNET_VDPA_H_