Commit 51a8f9d7 authored by Alvaro Karsz's avatar Alvaro Karsz Committed by Michael S. Tsirkin

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: default avatarAlvaro Karsz <alvaro.karsz@solid-run.com>
Acked-by: default avatarJason 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: default avatarMichael S. Tsirkin <mst@redhat.com>
parent d089d69c
...@@ -22061,6 +22061,10 @@ IFCVF VIRTIO DATA PATH ACCELERATOR ...@@ -22061,6 +22061,10 @@ IFCVF VIRTIO DATA PATH ACCELERATOR
R: Zhu Lingshan <lingshan.zhu@intel.com> R: Zhu Lingshan <lingshan.zhu@intel.com>
F: drivers/vdpa/ifcvf/ F: drivers/vdpa/ifcvf/
SNET DPU VIRTIO DATA PATH ACCELERATOR
R: Alvaro Karsz <alvaro.karsz@solid-run.com>
F: drivers/vdpa/solidrun/
VIRTIO BALLOON VIRTIO BALLOON
M: "Michael S. Tsirkin" <mst@redhat.com> M: "Michael S. Tsirkin" <mst@redhat.com>
M: David Hildenbrand <david@redhat.com> M: David Hildenbrand <david@redhat.com>
......
...@@ -98,4 +98,22 @@ config ALIBABA_ENI_VDPA ...@@ -98,4 +98,22 @@ config ALIBABA_ENI_VDPA
VDPA driver for Alibaba ENI (Elastic Network Interface) which is built upon VDPA driver for Alibaba ENI (Elastic Network Interface) which is built upon
virtio 0.9.5 specification. 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 endif # VDPA
...@@ -6,3 +6,4 @@ obj-$(CONFIG_IFCVF) += ifcvf/ ...@@ -6,3 +6,4 @@ obj-$(CONFIG_IFCVF) += ifcvf/
obj-$(CONFIG_MLX5_VDPA) += mlx5/ obj-$(CONFIG_MLX5_VDPA) += mlx5/
obj-$(CONFIG_VP_VDPA) += virtio_pci/ obj-$(CONFIG_VP_VDPA) += virtio_pci/
obj-$(CONFIG_ALIBABA_ENI_VDPA) += alibaba/ obj-$(CONFIG_ALIBABA_ENI_VDPA) += alibaba/
obj-$(CONFIG_SNET_VDPA) += solidrun/
# 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
// 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));
}
// 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/iopoll.h>
#include "snet_vdpa.h"
/* SNET DPU device ID */
#define SNET_DEVICE_ID 0x1000
/* SNET signature */
#define SNET_SIGNATURE 0xD0D06363
/* Max. config version that we can work with */
#define SNET_CFG_VERSION 0x1
/* Queue align */
#define SNET_QUEUE_ALIGNMENT PAGE_SIZE
/* Kick value to notify that new data is available */
#define SNET_KICK_VAL 0x1
#define SNET_CONFIG_OFF 0x0
/* ACK timeout for a message */
#define SNET_ACK_TIMEOUT 2000000
/* How long we are willing to wait for a SNET device */
#define SNET_DETECT_TIMEOUT 5000000
/* How long should we wait for the DPU to read our config */
#define SNET_READ_CFG_TIMEOUT 3000000
/* Size of configs written to the DPU */
#define SNET_GENERAL_CFG_LEN 36
#define SNET_GENERAL_CFG_VQ_LEN 40
enum snet_msg {
SNET_MSG_DESTROY = 1,
};
static struct snet *vdpa_to_snet(struct vdpa_device *vdpa)
{
return container_of(vdpa, struct snet, vdpa);
}
static int snet_wait_for_msg_ack(struct snet *snet)
{
struct pci_dev *pdev = snet->pdev;
int ret;
u32 val;
/* The DPU will clear the messages offset once messages
* are processed.
*/
ret = readx_poll_timeout(ioread32, snet->bar + snet->psnet->cfg.msg_off,
val, !val, 10, SNET_ACK_TIMEOUT);
if (ret)
SNET_WARN(pdev, "Timeout waiting for message ACK\n");
return ret;
}
/* Sends a message to the DPU.
* If blocking is set, the function will return once the
* message was processed by the DPU (or timeout).
*/
static int snet_send_msg(struct snet *snet, u32 msg, bool blocking)
{
int ret = 0;
/* Make sure the DPU acked last message before issuing a new one */
ret = snet_wait_for_msg_ack(snet);
if (ret)
return ret;
/* Write the message */
snet_write32(snet, snet->psnet->cfg.msg_off, msg);
if (blocking)
ret = snet_wait_for_msg_ack(snet);
else /* If non-blocking, flush the write by issuing a read */
snet_read32(snet, snet->psnet->cfg.msg_off);
return ret;
}
static irqreturn_t snet_cfg_irq_hndlr(int irq, void *data)
{
struct snet *snet = data;
/* Call callback if any */
if (snet->cb.callback)
return snet->cb.callback(snet->cb.private);
return IRQ_HANDLED;
}
static irqreturn_t snet_vq_irq_hndlr(int irq, void *data)
{
struct snet_vq *vq = data;
/* Call callback if any */
if (vq->cb.callback)
return vq->cb.callback(vq->cb.private);
return IRQ_HANDLED;
}
static void snet_free_irqs(struct snet *snet)
{
struct psnet *psnet = snet->psnet;
struct pci_dev *pdev;
u32 i;
/* Which Device allcoated the IRQs? */
if (PSNET_FLAG_ON(psnet, SNET_CFG_FLAG_IRQ_PF))
pdev = snet->pdev->physfn;
else
pdev = snet->pdev;
/* Free config's IRQ */
if (snet->cfg_irq != -1) {
devm_free_irq(&pdev->dev, snet->cfg_irq, snet);
snet->cfg_irq = -1;
}
/* Free VQ IRQs */
for (i = 0; i < snet->cfg->vq_num; i++) {
if (snet->vqs[i] && snet->vqs[i]->irq != -1) {
devm_free_irq(&pdev->dev, snet->vqs[i]->irq, snet->vqs[i]);
snet->vqs[i]->irq = -1;
}
}
/* IRQ vectors are freed when the pci remove callback is called */
}
static int snet_set_vq_address(struct vdpa_device *vdev, u16 idx, u64 desc_area,
u64 driver_area, u64 device_area)
{
struct snet *snet = vdpa_to_snet(vdev);
/* save received parameters in vqueue sturct */
snet->vqs[idx]->desc_area = desc_area;
snet->vqs[idx]->driver_area = driver_area;
snet->vqs[idx]->device_area = device_area;
return 0;
}
static void snet_set_vq_num(struct vdpa_device *vdev, u16 idx, u32 num)
{
struct snet *snet = vdpa_to_snet(vdev);
/* save num in vqueue */
snet->vqs[idx]->num = num;
}
static void snet_kick_vq(struct vdpa_device *vdev, u16 idx)
{
struct snet *snet = vdpa_to_snet(vdev);
/* not ready - ignore */
if (!snet->vqs[idx]->ready)
return;
iowrite32(SNET_KICK_VAL, snet->vqs[idx]->kick_ptr);
}
static void snet_set_vq_cb(struct vdpa_device *vdev, u16 idx, struct vdpa_callback *cb)
{
struct snet *snet = vdpa_to_snet(vdev);
snet->vqs[idx]->cb.callback = cb->callback;
snet->vqs[idx]->cb.private = cb->private;
}
static void snet_set_vq_ready(struct vdpa_device *vdev, u16 idx, bool ready)
{
struct snet *snet = vdpa_to_snet(vdev);
snet->vqs[idx]->ready = ready;
}
static bool snet_get_vq_ready(struct vdpa_device *vdev, u16 idx)
{
struct snet *snet = vdpa_to_snet(vdev);
return snet->vqs[idx]->ready;
}
static int snet_set_vq_state(struct vdpa_device *vdev, u16 idx, const struct vdpa_vq_state *state)
{
struct snet *snet = vdpa_to_snet(vdev);
/* Setting the VQ state is not supported.
* If the asked state is the same as the initial one
* we can ignore it.
*/
if (SNET_HAS_FEATURE(snet, VIRTIO_F_RING_PACKED)) {
const struct vdpa_vq_state_packed *p = &state->packed;
if (p->last_avail_counter == 1 && p->last_used_counter == 1 &&
p->last_avail_idx == 0 && p->last_used_idx == 0)
return 0;
} else {
const struct vdpa_vq_state_split *s = &state->split;
if (s->avail_index == 0)
return 0;
}
return -EOPNOTSUPP;
}
static int snet_get_vq_state(struct vdpa_device *vdev, u16 idx, struct vdpa_vq_state *state)
{
/* Not supported */
return -EOPNOTSUPP;
}
static int snet_get_vq_irq(struct vdpa_device *vdev, u16 idx)
{
struct snet *snet = vdpa_to_snet(vdev);
return snet->vqs[idx]->irq;
}
static u32 snet_get_vq_align(struct vdpa_device *vdev)
{
return (u32)SNET_QUEUE_ALIGNMENT;
}
static int snet_reset_dev(struct snet *snet)
{
struct pci_dev *pdev = snet->pdev;
int ret = 0;
u32 i;
/* If status is 0, nothing to do */
if (!snet->status)
return 0;
/* If DPU started, send a destroy message */
if (snet->status & VIRTIO_CONFIG_S_DRIVER_OK)
ret = snet_send_msg(snet, SNET_MSG_DESTROY, true);
/* Clear VQs */
for (i = 0; i < snet->cfg->vq_num; i++) {
if (!snet->vqs[i])
continue;
snet->vqs[i]->cb.callback = NULL;
snet->vqs[i]->cb.private = NULL;
snet->vqs[i]->desc_area = 0;
snet->vqs[i]->device_area = 0;
snet->vqs[i]->driver_area = 0;
snet->vqs[i]->ready = false;
}
/* Clear config callback */
snet->cb.callback = NULL;
snet->cb.private = NULL;
/* Free IRQs */
snet_free_irqs(snet);
/* Reset status */
snet->status = 0;
snet->dpu_ready = false;
if (ret)
SNET_WARN(pdev, "Incomplete reset to SNET[%u] device\n", snet->sid);
else
SNET_DBG(pdev, "Reset SNET[%u] device\n", snet->sid);
return 0;
}
static int snet_reset(struct vdpa_device *vdev)
{
struct snet *snet = vdpa_to_snet(vdev);
return snet_reset_dev(snet);
}
static size_t snet_get_config_size(struct vdpa_device *vdev)
{
struct snet *snet = vdpa_to_snet(vdev);
return (size_t)snet->cfg->cfg_size;
}
static u64 snet_get_features(struct vdpa_device *vdev)
{
struct snet *snet = vdpa_to_snet(vdev);
return snet->cfg->features;
}
static int snet_set_drv_features(struct vdpa_device *vdev, u64 features)
{
struct snet *snet = vdpa_to_snet(vdev);
snet->negotiated_features = snet->cfg->features & features;
return 0;
}
static u64 snet_get_drv_features(struct vdpa_device *vdev)
{
struct snet *snet = vdpa_to_snet(vdev);
return snet->negotiated_features;
}
static u16 snet_get_vq_num_max(struct vdpa_device *vdev)
{
struct snet *snet = vdpa_to_snet(vdev);
return (u16)snet->cfg->vq_size;
}
static void snet_set_config_cb(struct vdpa_device *vdev, struct vdpa_callback *cb)
{
struct snet *snet = vdpa_to_snet(vdev);
snet->cb.callback = cb->callback;
snet->cb.private = cb->private;
}
static u32 snet_get_device_id(struct vdpa_device *vdev)
{
struct snet *snet = vdpa_to_snet(vdev);
return snet->cfg->virtio_id;
}
static u32 snet_get_vendor_id(struct vdpa_device *vdev)
{
return (u32)PCI_VENDOR_ID_SOLIDRUN;
}
static u8 snet_get_status(struct vdpa_device *vdev)
{
struct snet *snet = vdpa_to_snet(vdev);
return snet->status;
}
static int snet_write_conf(struct snet *snet)
{
u32 off, i, tmp;
int ret;
/* No need to write the config twice */
if (snet->dpu_ready)
return true;
/* Snet data :
*
* General data: SNET_GENERAL_CFG_LEN bytes long
* 0 0x4 0x8 0xC 0x10 0x14 0x1C 0x24
* | MAGIC NUMBER | CFG VER | SNET SID | NUMBER OF QUEUES | IRQ IDX | FEATURES | RSVD |
*
* For every VQ: SNET_GENERAL_CFG_VQ_LEN bytes long
* 0 0x4 0x8
* | VQ SID AND QUEUE SIZE | IRQ Index |
* | DESC AREA |
* | DEVICE AREA |
* | DRIVER AREA |
* | RESERVED |
*
* Magic number should be written last, this is the DPU indication that the data is ready
*/
/* Init offset */
off = snet->psnet->cfg.host_cfg_off;
/* Ignore magic number for now */
off += 4;
snet_write32(snet, off, snet->psnet->negotiated_cfg_ver);
off += 4;
snet_write32(snet, off, snet->sid);
off += 4;
snet_write32(snet, off, snet->cfg->vq_num);
off += 4;
snet_write32(snet, off, snet->cfg_irq_idx);
off += 4;
snet_write64(snet, off, snet->negotiated_features);
off += 8;
/* Ignore reserved */
off += 8;
/* Write VQs */
for (i = 0 ; i < snet->cfg->vq_num ; i++) {
tmp = (i << 16) | (snet->vqs[i]->num & 0xFFFF);
snet_write32(snet, off, tmp);
off += 4;
snet_write32(snet, off, snet->vqs[i]->irq_idx);
off += 4;
snet_write64(snet, off, snet->vqs[i]->desc_area);
off += 8;
snet_write64(snet, off, snet->vqs[i]->device_area);
off += 8;
snet_write64(snet, off, snet->vqs[i]->driver_area);
off += 8;
/* Ignore reserved */
off += 8;
}
/* Clear snet messages address for this device */
snet_write32(snet, snet->psnet->cfg.msg_off, 0);
/* Write magic number - data is ready */
snet_write32(snet, snet->psnet->cfg.host_cfg_off, SNET_SIGNATURE);
/* The DPU will ACK the config by clearing the signature */
ret = readx_poll_timeout(ioread32, snet->bar + snet->psnet->cfg.host_cfg_off,
tmp, !tmp, 10, SNET_READ_CFG_TIMEOUT);
if (ret) {
SNET_ERR(snet->pdev, "Timeout waiting for the DPU to read the config\n");
return false;
}
/* set DPU flag */
snet->dpu_ready = true;
return true;
}
static int snet_request_irqs(struct pci_dev *pdev, struct snet *snet)
{
int ret, i, irq;
/* Request config IRQ */
irq = pci_irq_vector(pdev, snet->cfg_irq_idx);
ret = devm_request_irq(&pdev->dev, irq, snet_cfg_irq_hndlr, 0,
snet->cfg_irq_name, snet);
if (ret) {
SNET_ERR(pdev, "Failed to request IRQ\n");
return ret;
}
snet->cfg_irq = irq;
/* Request IRQ for every VQ */
for (i = 0; i < snet->cfg->vq_num; i++) {
irq = pci_irq_vector(pdev, snet->vqs[i]->irq_idx);
ret = devm_request_irq(&pdev->dev, irq, snet_vq_irq_hndlr, 0,
snet->vqs[i]->irq_name, snet->vqs[i]);
if (ret) {
SNET_ERR(pdev, "Failed to request IRQ\n");
return ret;
}
snet->vqs[i]->irq = irq;
}
return 0;
}
static void snet_set_status(struct vdpa_device *vdev, u8 status)
{
struct snet *snet = vdpa_to_snet(vdev);
struct psnet *psnet = snet->psnet;
struct pci_dev *pdev = snet->pdev;
int ret;
bool pf_irqs;
if (status == snet->status)
return;
if ((status & VIRTIO_CONFIG_S_DRIVER_OK) &&
!(snet->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
/* Request IRQs */
pf_irqs = PSNET_FLAG_ON(psnet, SNET_CFG_FLAG_IRQ_PF);
ret = snet_request_irqs(pf_irqs ? pdev->physfn : pdev, snet);
if (ret)
goto set_err;
/* Write config to the DPU */
if (snet_write_conf(snet)) {
SNET_INFO(pdev, "Create SNET[%u] device\n", snet->sid);
} else {
snet_free_irqs(snet);
goto set_err;
}
}
/* Save the new status */
snet->status = status;
return;
set_err:
snet->status |= VIRTIO_CONFIG_S_FAILED;
}
static void snet_get_config(struct vdpa_device *vdev, unsigned int offset,
void *buf, unsigned int len)
{
struct snet *snet = vdpa_to_snet(vdev);
void __iomem *cfg_ptr = snet->cfg->virtio_cfg + offset;
u8 *buf_ptr = buf;
u32 i;
/* check for offset error */
if (offset + len > snet->cfg->cfg_size)
return;
/* Write into buffer */
for (i = 0; i < len; i++)
*buf_ptr++ = ioread8(cfg_ptr + i);
}
static void snet_set_config(struct vdpa_device *vdev, unsigned int offset,
const void *buf, unsigned int len)
{
struct snet *snet = vdpa_to_snet(vdev);
void __iomem *cfg_ptr = snet->cfg->virtio_cfg + offset;
const u8 *buf_ptr = buf;
u32 i;
/* check for offset error */
if (offset + len > snet->cfg->cfg_size)
return;
/* Write into PCI BAR */
for (i = 0; i < len; i++)
iowrite8(*buf_ptr++, cfg_ptr + i);
}
static const struct vdpa_config_ops snet_config_ops = {
.set_vq_address = snet_set_vq_address,
.set_vq_num = snet_set_vq_num,
.kick_vq = snet_kick_vq,
.set_vq_cb = snet_set_vq_cb,
.set_vq_ready = snet_set_vq_ready,
.get_vq_ready = snet_get_vq_ready,
.set_vq_state = snet_set_vq_state,
.get_vq_state = snet_get_vq_state,
.get_vq_irq = snet_get_vq_irq,
.get_vq_align = snet_get_vq_align,
.reset = snet_reset,
.get_config_size = snet_get_config_size,
.get_device_features = snet_get_features,
.set_driver_features = snet_set_drv_features,
.get_driver_features = snet_get_drv_features,
.get_vq_num_min = snet_get_vq_num_max,
.get_vq_num_max = snet_get_vq_num_max,
.set_config_cb = snet_set_config_cb,
.get_device_id = snet_get_device_id,
.get_vendor_id = snet_get_vendor_id,
.get_status = snet_get_status,
.set_status = snet_set_status,
.get_config = snet_get_config,
.set_config = snet_set_config,
};
static int psnet_open_pf_bar(struct pci_dev *pdev, struct psnet *psnet)
{
char name[50];
int ret, i, mask = 0;
/* We don't know which BAR will be used to communicate..
* We will map every bar with len > 0.
*
* Later, we will discover the BAR and unmap all other BARs.
*/
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
if (pci_resource_len(pdev, i))
mask |= (1 << i);
}
/* No BAR can be used.. */
if (!mask) {
SNET_ERR(pdev, "Failed to find a PCI BAR\n");
return -ENODEV;
}
snprintf(name, sizeof(name), "psnet[%s]-bars", pci_name(pdev));
ret = pcim_iomap_regions(pdev, mask, name);
if (ret) {
SNET_ERR(pdev, "Failed to request and map PCI BARs\n");
return ret;
}
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
if (mask & (1 << i))
psnet->bars[i] = pcim_iomap_table(pdev)[i];
}
return 0;
}
static int snet_open_vf_bar(struct pci_dev *pdev, struct snet *snet)
{
char name[50];
int ret;
snprintf(name, sizeof(name), "snet[%s]-bar", pci_name(pdev));
/* Request and map BAR */
ret = pcim_iomap_regions(pdev, BIT(snet->psnet->cfg.vf_bar), name);
if (ret) {
SNET_ERR(pdev, "Failed to request and map PCI BAR for a VF\n");
return ret;
}
snet->bar = pcim_iomap_table(pdev)[snet->psnet->cfg.vf_bar];
return 0;
}
static void snet_free_cfg(struct snet_cfg *cfg)
{
u32 i;
if (!cfg->devs)
return;
/* Free devices */
for (i = 0; i < cfg->devices_num; i++) {
if (!cfg->devs[i])
break;
kfree(cfg->devs[i]);
}
/* Free pointers to devices */
kfree(cfg->devs);
}
/* Detect which BAR is used for communication with the device. */
static int psnet_detect_bar(struct psnet *psnet, u32 off)
{
unsigned long exit_time;
int i;
exit_time = jiffies + usecs_to_jiffies(SNET_DETECT_TIMEOUT);
/* SNET DPU will write SNET's signature when the config is ready. */
while (time_before(jiffies, exit_time)) {
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
/* Is this BAR mapped? */
if (!psnet->bars[i])
continue;
if (ioread32(psnet->bars[i] + off) == SNET_SIGNATURE)
return i;
}
usleep_range(1000, 10000);
}
return -ENODEV;
}
static void psnet_unmap_unused_bars(struct pci_dev *pdev, struct psnet *psnet)
{
int i, mask = 0;
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
if (psnet->bars[i] && i != psnet->barno)
mask |= (1 << i);
}
if (mask)
pcim_iounmap_regions(pdev, mask);
}
/* Read SNET config from PCI BAR */
static int psnet_read_cfg(struct pci_dev *pdev, struct psnet *psnet)
{
struct snet_cfg *cfg = &psnet->cfg;
u32 i, off;
int barno;
/* Move to where the config starts */
off = SNET_CONFIG_OFF;
/* Find BAR used for communication */
barno = psnet_detect_bar(psnet, off);
if (barno < 0) {
SNET_ERR(pdev, "SNET config is not ready.\n");
return barno;
}
/* Save used BAR number and unmap all other BARs */
psnet->barno = barno;
SNET_DBG(pdev, "Using BAR number %d\n", barno);
psnet_unmap_unused_bars(pdev, psnet);
/* load config from BAR */
cfg->key = psnet_read32(psnet, off);
off += 4;
cfg->cfg_size = psnet_read32(psnet, off);
off += 4;
cfg->cfg_ver = psnet_read32(psnet, off);
off += 4;
/* The negotiated config version is the lower one between this driver's config
* and the DPU's.
*/
psnet->negotiated_cfg_ver = min_t(u32, cfg->cfg_ver, SNET_CFG_VERSION);
SNET_DBG(pdev, "SNET config version %u\n", psnet->negotiated_cfg_ver);
cfg->vf_num = psnet_read32(psnet, off);
off += 4;
cfg->vf_bar = psnet_read32(psnet, off);
off += 4;
cfg->host_cfg_off = psnet_read32(psnet, off);
off += 4;
cfg->max_size_host_cfg = psnet_read32(psnet, off);
off += 4;
cfg->virtio_cfg_off = psnet_read32(psnet, off);
off += 4;
cfg->kick_off = psnet_read32(psnet, off);
off += 4;
cfg->hwmon_off = psnet_read32(psnet, off);
off += 4;
cfg->msg_off = psnet_read32(psnet, off);
off += 4;
cfg->flags = psnet_read32(psnet, off);
off += 4;
/* Ignore Reserved */
off += sizeof(cfg->rsvd);
cfg->devices_num = psnet_read32(psnet, off);
off += 4;
/* Allocate memory to hold pointer to the devices */
cfg->devs = kcalloc(cfg->devices_num, sizeof(void *), GFP_KERNEL);
if (!cfg->devs)
return -ENOMEM;
/* Load device configuration from BAR */
for (i = 0; i < cfg->devices_num; i++) {
cfg->devs[i] = kzalloc(sizeof(*cfg->devs[i]), GFP_KERNEL);
if (!cfg->devs[i]) {
snet_free_cfg(cfg);
return -ENOMEM;
}
/* Read device config */
cfg->devs[i]->virtio_id = psnet_read32(psnet, off);
off += 4;
cfg->devs[i]->vq_num = psnet_read32(psnet, off);
off += 4;
cfg->devs[i]->vq_size = psnet_read32(psnet, off);
off += 4;
cfg->devs[i]->vfid = psnet_read32(psnet, off);
off += 4;
cfg->devs[i]->features = psnet_read64(psnet, off);
off += 8;
/* Ignore Reserved */
off += sizeof(cfg->devs[i]->rsvd);
cfg->devs[i]->cfg_size = psnet_read32(psnet, off);
off += 4;
/* Is the config witten to the DPU going to be too big? */
if (SNET_GENERAL_CFG_LEN + SNET_GENERAL_CFG_VQ_LEN * cfg->devs[i]->vq_num >
cfg->max_size_host_cfg) {
SNET_ERR(pdev, "Failed to read SNET config, the config is too big..\n");
snet_free_cfg(cfg);
return -EINVAL;
}
}
return 0;
}
static int psnet_alloc_irq_vector(struct pci_dev *pdev, struct psnet *psnet)
{
int ret = 0;
u32 i, irq_num = 0;
/* Let's count how many IRQs we need, 1 for every VQ + 1 for config change */
for (i = 0; i < psnet->cfg.devices_num; i++)
irq_num += psnet->cfg.devs[i]->vq_num + 1;
ret = pci_alloc_irq_vectors(pdev, irq_num, irq_num, PCI_IRQ_MSIX);
if (ret != irq_num) {
SNET_ERR(pdev, "Failed to allocate IRQ vectors\n");
return ret;
}
SNET_DBG(pdev, "Allocated %u IRQ vectors from physical function\n", irq_num);
return 0;
}
static int snet_alloc_irq_vector(struct pci_dev *pdev, struct snet_dev_cfg *snet_cfg)
{
int ret = 0;
u32 irq_num;
/* We want 1 IRQ for every VQ + 1 for config change events */
irq_num = snet_cfg->vq_num + 1;
ret = pci_alloc_irq_vectors(pdev, irq_num, irq_num, PCI_IRQ_MSIX);
if (ret <= 0) {
SNET_ERR(pdev, "Failed to allocate IRQ vectors\n");
return ret;
}
return 0;
}
static void snet_free_vqs(struct snet *snet)
{
u32 i;
if (!snet->vqs)
return;
for (i = 0 ; i < snet->cfg->vq_num ; i++) {
if (!snet->vqs[i])
break;
kfree(snet->vqs[i]);
}
kfree(snet->vqs);
}
static int snet_build_vqs(struct snet *snet)
{
u32 i;
/* Allocate the VQ pointers array */
snet->vqs = kcalloc(snet->cfg->vq_num, sizeof(void *), GFP_KERNEL);
if (!snet->vqs)
return -ENOMEM;
/* Allocate the VQs */
for (i = 0; i < snet->cfg->vq_num; i++) {
snet->vqs[i] = kzalloc(sizeof(*snet->vqs[i]), GFP_KERNEL);
if (!snet->vqs[i]) {
snet_free_vqs(snet);
return -ENOMEM;
}
/* Reset IRQ num */
snet->vqs[i]->irq = -1;
/* VQ serial ID */
snet->vqs[i]->sid = i;
/* Kick address - every VQ gets 4B */
snet->vqs[i]->kick_ptr = snet->bar + snet->psnet->cfg.kick_off +
snet->vqs[i]->sid * 4;
/* Clear kick address for this VQ */
iowrite32(0, snet->vqs[i]->kick_ptr);
}
return 0;
}
static int psnet_get_next_irq_num(struct psnet *psnet)
{
int irq;
spin_lock(&psnet->lock);
irq = psnet->next_irq++;
spin_unlock(&psnet->lock);
return irq;
}
static void snet_reserve_irq_idx(struct pci_dev *pdev, struct snet *snet)
{
struct psnet *psnet = snet->psnet;
int i;
/* one IRQ for every VQ, and one for config changes */
snet->cfg_irq_idx = psnet_get_next_irq_num(psnet);
snprintf(snet->cfg_irq_name, SNET_NAME_SIZE, "snet[%s]-cfg[%d]",
pci_name(pdev), snet->cfg_irq_idx);
for (i = 0; i < snet->cfg->vq_num; i++) {
/* Get next free IRQ ID */
snet->vqs[i]->irq_idx = psnet_get_next_irq_num(psnet);
/* Write IRQ name */
snprintf(snet->vqs[i]->irq_name, SNET_NAME_SIZE, "snet[%s]-vq[%d]",
pci_name(pdev), snet->vqs[i]->irq_idx);
}
}
/* Find a device config based on virtual function id */
static struct snet_dev_cfg *snet_find_dev_cfg(struct snet_cfg *cfg, u32 vfid)
{
u32 i;
for (i = 0; i < cfg->devices_num; i++) {
if (cfg->devs[i]->vfid == vfid)
return cfg->devs[i];
}
/* Oppss.. no config found.. */
return NULL;
}
/* Probe function for a physical PCI function */
static int snet_vdpa_probe_pf(struct pci_dev *pdev)
{
struct psnet *psnet;
int ret = 0;
bool pf_irqs = false;
ret = pcim_enable_device(pdev);
if (ret) {
SNET_ERR(pdev, "Failed to enable PCI device\n");
return ret;
}
/* Allocate a PCI physical function device */
psnet = kzalloc(sizeof(*psnet), GFP_KERNEL);
if (!psnet)
return -ENOMEM;
/* Init PSNET spinlock */
spin_lock_init(&psnet->lock);
pci_set_master(pdev);
pci_set_drvdata(pdev, psnet);
/* Open SNET MAIN BAR */
ret = psnet_open_pf_bar(pdev, psnet);
if (ret)
goto free_psnet;
/* Try to read SNET's config from PCI BAR */
ret = psnet_read_cfg(pdev, psnet);
if (ret)
goto free_psnet;
/* If SNET_CFG_FLAG_IRQ_PF flag is set, we should use
* PF MSI-X vectors
*/
pf_irqs = PSNET_FLAG_ON(psnet, SNET_CFG_FLAG_IRQ_PF);
if (pf_irqs) {
ret = psnet_alloc_irq_vector(pdev, psnet);
if (ret)
goto free_cfg;
}
SNET_DBG(pdev, "Enable %u virtual functions\n", psnet->cfg.vf_num);
ret = pci_enable_sriov(pdev, psnet->cfg.vf_num);
if (ret) {
SNET_ERR(pdev, "Failed to enable SR-IOV\n");
goto free_irq;
}
/* Create HW monitor device */
if (PSNET_FLAG_ON(psnet, SNET_CFG_FLAG_HWMON)) {
#if IS_ENABLED(CONFIG_HWMON)
psnet_create_hwmon(pdev);
#else
SNET_WARN(pdev, "Can't start HWMON, CONFIG_HWMON is not enabled\n");
#endif
}
return 0;
free_irq:
if (pf_irqs)
pci_free_irq_vectors(pdev);
free_cfg:
snet_free_cfg(&psnet->cfg);
free_psnet:
kfree(psnet);
return ret;
}
/* Probe function for a virtual PCI function */
static int snet_vdpa_probe_vf(struct pci_dev *pdev)
{
struct pci_dev *pdev_pf = pdev->physfn;
struct psnet *psnet = pci_get_drvdata(pdev_pf);
struct snet_dev_cfg *dev_cfg;
struct snet *snet;
u32 vfid;
int ret;
bool pf_irqs = false;
/* Get virtual function id.
* (the DPU counts the VFs from 1)
*/
ret = pci_iov_vf_id(pdev);
if (ret < 0) {
SNET_ERR(pdev, "Failed to find a VF id\n");
return ret;
}
vfid = ret + 1;
/* Find the snet_dev_cfg based on vfid */
dev_cfg = snet_find_dev_cfg(&psnet->cfg, vfid);
if (!dev_cfg) {
SNET_WARN(pdev, "Failed to find a VF config..\n");
return -ENODEV;
}
/* Which PCI device should allocate the IRQs?
* If the SNET_CFG_FLAG_IRQ_PF flag set, the PF device allocates the IRQs
*/
pf_irqs = PSNET_FLAG_ON(psnet, SNET_CFG_FLAG_IRQ_PF);
ret = pcim_enable_device(pdev);
if (ret) {
SNET_ERR(pdev, "Failed to enable PCI VF device\n");
return ret;
}
/* Request for MSI-X IRQs */
if (!pf_irqs) {
ret = snet_alloc_irq_vector(pdev, dev_cfg);
if (ret)
return ret;
}
/* Allocate vdpa device */
snet = vdpa_alloc_device(struct snet, vdpa, &pdev->dev, &snet_config_ops, 1, 1, NULL,
false);
if (!snet) {
SNET_ERR(pdev, "Failed to allocate a vdpa device\n");
ret = -ENOMEM;
goto free_irqs;
}
/* Save pci device pointer */
snet->pdev = pdev;
snet->psnet = psnet;
snet->cfg = dev_cfg;
snet->dpu_ready = false;
snet->sid = vfid;
/* Reset IRQ value */
snet->cfg_irq = -1;
ret = snet_open_vf_bar(pdev, snet);
if (ret)
goto put_device;
/* Create a VirtIO config pointer */
snet->cfg->virtio_cfg = snet->bar + snet->psnet->cfg.virtio_cfg_off;
pci_set_master(pdev);
pci_set_drvdata(pdev, snet);
ret = snet_build_vqs(snet);
if (ret)
goto put_device;
/* Reserve IRQ indexes,
* The IRQs may be requested and freed multiple times,
* but the indexes won't change.
*/
snet_reserve_irq_idx(pf_irqs ? pdev_pf : pdev, snet);
/*set DMA device*/
snet->vdpa.dma_dev = &pdev->dev;
/* Register VDPA device */
ret = vdpa_register_device(&snet->vdpa, snet->cfg->vq_num);
if (ret) {
SNET_ERR(pdev, "Failed to register vdpa device\n");
goto free_vqs;
}
return 0;
free_vqs:
snet_free_vqs(snet);
put_device:
put_device(&snet->vdpa.dev);
free_irqs:
if (!pf_irqs)
pci_free_irq_vectors(pdev);
return ret;
}
static int snet_vdpa_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
if (pdev->is_virtfn)
return snet_vdpa_probe_vf(pdev);
else
return snet_vdpa_probe_pf(pdev);
}
static void snet_vdpa_remove_pf(struct pci_dev *pdev)
{
struct psnet *psnet = pci_get_drvdata(pdev);
pci_disable_sriov(pdev);
/* If IRQs are allocated from the PF, we should free the IRQs */
if (PSNET_FLAG_ON(psnet, SNET_CFG_FLAG_IRQ_PF))
pci_free_irq_vectors(pdev);
snet_free_cfg(&psnet->cfg);
kfree(psnet);
}
static void snet_vdpa_remove_vf(struct pci_dev *pdev)
{
struct snet *snet = pci_get_drvdata(pdev);
struct psnet *psnet = snet->psnet;
vdpa_unregister_device(&snet->vdpa);
snet_free_vqs(snet);
/* If IRQs are allocated from the VF, we should free the IRQs */
if (!PSNET_FLAG_ON(psnet, SNET_CFG_FLAG_IRQ_PF))
pci_free_irq_vectors(pdev);
}
static void snet_vdpa_remove(struct pci_dev *pdev)
{
if (pdev->is_virtfn)
snet_vdpa_remove_vf(pdev);
else
snet_vdpa_remove_pf(pdev);
}
static struct pci_device_id snet_driver_pci_ids[] = {
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_SOLIDRUN, SNET_DEVICE_ID,
PCI_VENDOR_ID_SOLIDRUN, SNET_DEVICE_ID) },
{ 0 },
};
MODULE_DEVICE_TABLE(pci, snet_driver_pci_ids);
static struct pci_driver snet_vdpa_driver = {
.name = "snet-vdpa-driver",
.id_table = snet_driver_pci_ids,
.probe = snet_vdpa_probe,
.remove = snet_vdpa_remove,
};
module_pci_driver(snet_vdpa_driver);
MODULE_AUTHOR("Alvaro Karsz <alvaro.karsz@solid-run.com>");
MODULE_DESCRIPTION("SolidRun vDPA driver");
MODULE_LICENSE("GPL v2");
/* 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_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment