diff options
Diffstat (limited to 'drivers/net/ethernet/marvell/octeontx2/af/mbox.c')
-rw-r--r-- | drivers/net/ethernet/marvell/octeontx2/af/mbox.c | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.c b/drivers/net/ethernet/marvell/octeontx2/af/mbox.c new file mode 100644 index 000000000000..85ba24a05774 --- /dev/null +++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Marvell OcteonTx2 RVU Admin Function driver + * + * Copyright (C) 2018 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/pci.h> + +#include "rvu_reg.h" +#include "mbox.h" + +static const u16 msgs_offset = ALIGN(sizeof(struct mbox_hdr), MBOX_MSG_ALIGN); + +void otx2_mbox_reset(struct otx2_mbox *mbox, int devid) +{ + struct otx2_mbox_dev *mdev = &mbox->dev[devid]; + struct mbox_hdr *tx_hdr, *rx_hdr; + + tx_hdr = mdev->mbase + mbox->tx_start; + rx_hdr = mdev->mbase + mbox->rx_start; + + spin_lock(&mdev->mbox_lock); + mdev->msg_size = 0; + mdev->rsp_size = 0; + tx_hdr->num_msgs = 0; + rx_hdr->num_msgs = 0; + spin_unlock(&mdev->mbox_lock); +} +EXPORT_SYMBOL(otx2_mbox_reset); + +void otx2_mbox_destroy(struct otx2_mbox *mbox) +{ + mbox->reg_base = NULL; + mbox->hwbase = NULL; + + kfree(mbox->dev); + mbox->dev = NULL; +} +EXPORT_SYMBOL(otx2_mbox_destroy); + +int otx2_mbox_init(struct otx2_mbox *mbox, void *hwbase, struct pci_dev *pdev, + void *reg_base, int direction, int ndevs) +{ + struct otx2_mbox_dev *mdev; + int devid; + + switch (direction) { + case MBOX_DIR_AFPF: + case MBOX_DIR_PFVF: + mbox->tx_start = MBOX_DOWN_TX_START; + mbox->rx_start = MBOX_DOWN_RX_START; + mbox->tx_size = MBOX_DOWN_TX_SIZE; + mbox->rx_size = MBOX_DOWN_RX_SIZE; + break; + case MBOX_DIR_PFAF: + case MBOX_DIR_VFPF: + mbox->tx_start = MBOX_DOWN_RX_START; + mbox->rx_start = MBOX_DOWN_TX_START; + mbox->tx_size = MBOX_DOWN_RX_SIZE; + mbox->rx_size = MBOX_DOWN_TX_SIZE; + break; + case MBOX_DIR_AFPF_UP: + case MBOX_DIR_PFVF_UP: + mbox->tx_start = MBOX_UP_TX_START; + mbox->rx_start = MBOX_UP_RX_START; + mbox->tx_size = MBOX_UP_TX_SIZE; + mbox->rx_size = MBOX_UP_RX_SIZE; + break; + case MBOX_DIR_PFAF_UP: + case MBOX_DIR_VFPF_UP: + mbox->tx_start = MBOX_UP_RX_START; + mbox->rx_start = MBOX_UP_TX_START; + mbox->tx_size = MBOX_UP_RX_SIZE; + mbox->rx_size = MBOX_UP_TX_SIZE; + break; + default: + return -ENODEV; + } + + switch (direction) { + case MBOX_DIR_AFPF: + case MBOX_DIR_AFPF_UP: + mbox->trigger = RVU_AF_AFPF_MBOX0; + mbox->tr_shift = 4; + break; + case MBOX_DIR_PFAF: + case MBOX_DIR_PFAF_UP: + mbox->trigger = RVU_PF_PFAF_MBOX1; + mbox->tr_shift = 0; + break; + case MBOX_DIR_PFVF: + case MBOX_DIR_PFVF_UP: + mbox->trigger = RVU_PF_VFX_PFVF_MBOX0; + mbox->tr_shift = 12; + break; + case MBOX_DIR_VFPF: + case MBOX_DIR_VFPF_UP: + mbox->trigger = RVU_VF_VFPF_MBOX1; + mbox->tr_shift = 0; + break; + default: + return -ENODEV; + } + + mbox->reg_base = reg_base; + mbox->hwbase = hwbase; + mbox->pdev = pdev; + + mbox->dev = kcalloc(ndevs, sizeof(struct otx2_mbox_dev), GFP_KERNEL); + if (!mbox->dev) { + otx2_mbox_destroy(mbox); + return -ENOMEM; + } + + mbox->ndevs = ndevs; + for (devid = 0; devid < ndevs; devid++) { + mdev = &mbox->dev[devid]; + mdev->mbase = mbox->hwbase + (devid * MBOX_SIZE); + spin_lock_init(&mdev->mbox_lock); + /* Init header to reset value */ + otx2_mbox_reset(mbox, devid); + } + + return 0; +} +EXPORT_SYMBOL(otx2_mbox_init); + +int otx2_mbox_wait_for_rsp(struct otx2_mbox *mbox, int devid) +{ + struct otx2_mbox_dev *mdev = &mbox->dev[devid]; + int timeout = 0, sleep = 1; + + while (mdev->num_msgs != mdev->msgs_acked) { + msleep(sleep); + timeout += sleep; + if (timeout >= MBOX_RSP_TIMEOUT) + return -EIO; + } + return 0; +} +EXPORT_SYMBOL(otx2_mbox_wait_for_rsp); + +int otx2_mbox_busy_poll_for_rsp(struct otx2_mbox *mbox, int devid) +{ + struct otx2_mbox_dev *mdev = &mbox->dev[devid]; + unsigned long timeout = jiffies + 1 * HZ; + + while (!time_after(jiffies, timeout)) { + if (mdev->num_msgs == mdev->msgs_acked) + return 0; + cpu_relax(); + } + return -EIO; +} +EXPORT_SYMBOL(otx2_mbox_busy_poll_for_rsp); + +void otx2_mbox_msg_send(struct otx2_mbox *mbox, int devid) +{ + struct otx2_mbox_dev *mdev = &mbox->dev[devid]; + struct mbox_hdr *tx_hdr, *rx_hdr; + + tx_hdr = mdev->mbase + mbox->tx_start; + rx_hdr = mdev->mbase + mbox->rx_start; + + spin_lock(&mdev->mbox_lock); + /* Reset header for next messages */ + mdev->msg_size = 0; + mdev->rsp_size = 0; + mdev->msgs_acked = 0; + + /* Sync mbox data into memory */ + smp_wmb(); + + /* num_msgs != 0 signals to the peer that the buffer has a number of + * messages. So this should be written after writing all the messages + * to the shared memory. + */ + tx_hdr->num_msgs = mdev->num_msgs; + rx_hdr->num_msgs = 0; + spin_unlock(&mdev->mbox_lock); + + /* The interrupt should be fired after num_msgs is written + * to the shared memory + */ + writeq(1, (void __iomem *)mbox->reg_base + + (mbox->trigger | (devid << mbox->tr_shift))); +} +EXPORT_SYMBOL(otx2_mbox_msg_send); + +struct mbox_msghdr *otx2_mbox_alloc_msg_rsp(struct otx2_mbox *mbox, int devid, + int size, int size_rsp) +{ + struct otx2_mbox_dev *mdev = &mbox->dev[devid]; + struct mbox_msghdr *msghdr = NULL; + + spin_lock(&mdev->mbox_lock); + size = ALIGN(size, MBOX_MSG_ALIGN); + size_rsp = ALIGN(size_rsp, MBOX_MSG_ALIGN); + /* Check if there is space in mailbox */ + if ((mdev->msg_size + size) > mbox->tx_size - msgs_offset) + goto exit; + if ((mdev->rsp_size + size_rsp) > mbox->rx_size - msgs_offset) + goto exit; + + if (mdev->msg_size == 0) + mdev->num_msgs = 0; + mdev->num_msgs++; + + msghdr = mdev->mbase + mbox->tx_start + msgs_offset + mdev->msg_size; + + /* Clear the whole msg region */ + memset(msghdr, 0, sizeof(*msghdr) + size); + /* Init message header with reset values */ + msghdr->ver = OTX2_MBOX_VERSION; + mdev->msg_size += size; + mdev->rsp_size += size_rsp; + msghdr->next_msgoff = mdev->msg_size + msgs_offset; +exit: + spin_unlock(&mdev->mbox_lock); + + return msghdr; +} +EXPORT_SYMBOL(otx2_mbox_alloc_msg_rsp); + +struct mbox_msghdr *otx2_mbox_get_rsp(struct otx2_mbox *mbox, int devid, + struct mbox_msghdr *msg) +{ + unsigned long imsg = mbox->tx_start + msgs_offset; + unsigned long irsp = mbox->rx_start + msgs_offset; + struct otx2_mbox_dev *mdev = &mbox->dev[devid]; + u16 msgs; + + if (mdev->num_msgs != mdev->msgs_acked) + return ERR_PTR(-ENODEV); + + for (msgs = 0; msgs < mdev->msgs_acked; msgs++) { + struct mbox_msghdr *pmsg = mdev->mbase + imsg; + struct mbox_msghdr *prsp = mdev->mbase + irsp; + + if (msg == pmsg) { + if (pmsg->id != prsp->id) + return ERR_PTR(-ENODEV); + return prsp; + } + + imsg = pmsg->next_msgoff; + irsp = prsp->next_msgoff; + } + + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL(otx2_mbox_get_rsp); + +int +otx2_reply_invalid_msg(struct otx2_mbox *mbox, int devid, u16 pcifunc, u16 id) +{ + struct msg_rsp *rsp; + + rsp = (struct msg_rsp *) + otx2_mbox_alloc_msg(mbox, devid, sizeof(*rsp)); + if (!rsp) + return -ENOMEM; + rsp->hdr.id = id; + rsp->hdr.sig = OTX2_MBOX_RSP_SIG; + rsp->hdr.rc = MBOX_MSG_INVALID; + rsp->hdr.pcifunc = pcifunc; + return 0; +} +EXPORT_SYMBOL(otx2_reply_invalid_msg); + +bool otx2_mbox_nonempty(struct otx2_mbox *mbox, int devid) +{ + struct otx2_mbox_dev *mdev = &mbox->dev[devid]; + bool ret; + + spin_lock(&mdev->mbox_lock); + ret = mdev->num_msgs != 0; + spin_unlock(&mdev->mbox_lock); + + return ret; +} +EXPORT_SYMBOL(otx2_mbox_nonempty); + +const char *otx2_mbox_id2name(u16 id) +{ + switch (id) { +#define M(_name, _id, _1, _2) case _id: return # _name; + MBOX_MESSAGES +#undef M + default: + return "INVALID ID"; + } +} +EXPORT_SYMBOL(otx2_mbox_id2name); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_LICENSE("GPL v2"); |