// 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;
}