mirror of
https://github.com/torvalds/linux.git
synced 2024-11-29 15:41:36 +00:00
e14f480df7
Return -EINVAL for invalid signatures. Don't return success.
Fixes: 8b6c724cda
("virtio: vdpa: vDPA driver for Marvell OCTEON DPU devices")
Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
Message-Id: <623e885b-1a05-479e-ab97-01bcf10bf5b8@stanley.mountain>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
518 lines
13 KiB
C
518 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (C) 2024 Marvell. */
|
|
|
|
#include <linux/iopoll.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)
|
|
{
|
|
return sizeof(struct virtio_net_config);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int octep_hw_caps_read(struct octep_hw *oct_hw, struct pci_dev *pdev)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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: 0x%016llx\n", (u64)(uintptr_t)oct_hw->common_cfg);
|
|
dev_info(dev, "device cfg mapped at: 0x%016llx\n", (u64)(uintptr_t)oct_hw->dev_cfg);
|
|
dev_info(dev, "isr cfg mapped at: 0x%016llx\n", (u64)(uintptr_t)oct_hw->isr);
|
|
dev_info(dev, "notify base: 0x%016llx, notify off multiplier: %u\n",
|
|
(u64)(uintptr_t)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;
|
|
|
|
oct_hw->irq = -1;
|
|
|
|
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: 0x%016llx\n", (u64)(uintptr_t)mbox);
|
|
|
|
return 0;
|
|
}
|