net: Add NIC controller driver for OcteonTX
Adds support for Network Interface controllers found on
OcteonTX SoC platforms.
Signed-off-by: Suneel Garapati <sgarapati@marvell.com>
Signed-off-by: Stefan Roese <sr@denx.de>
Cc: Joe Hershberger <joe.hershberger@ni.com>
diff --git a/drivers/net/octeontx/nicvf_main.c b/drivers/net/octeontx/nicvf_main.c
new file mode 100644
index 0000000..e13c8b9
--- /dev/null
+++ b/drivers/net/octeontx/nicvf_main.c
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Marvell International Ltd.
+ */
+
+#include <dm.h>
+#include <malloc.h>
+#include <misc.h>
+#include <net.h>
+#include <pci.h>
+#include <pci_ids.h>
+#include <phy.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+
+#include "nic_reg.h"
+#include "nic.h"
+#include "nicvf_queues.h"
+
+/* Register read/write APIs */
+void nicvf_reg_write(struct nicvf *nic, u64 offset, u64 val)
+{
+ writeq(val, nic->reg_base + offset);
+}
+
+u64 nicvf_reg_read(struct nicvf *nic, u64 offset)
+{
+ return readq(nic->reg_base + offset);
+}
+
+void nicvf_queue_reg_write(struct nicvf *nic, u64 offset,
+ u64 qidx, u64 val)
+{
+ void *addr = nic->reg_base + offset;
+
+ writeq(val, (void *)(addr + (qidx << NIC_Q_NUM_SHIFT)));
+}
+
+u64 nicvf_queue_reg_read(struct nicvf *nic, u64 offset, u64 qidx)
+{
+ void *addr = nic->reg_base + offset;
+
+ return readq((void *)(addr + (qidx << NIC_Q_NUM_SHIFT)));
+}
+
+static void nicvf_handle_mbx_intr(struct nicvf *nic);
+
+/* VF -> PF mailbox communication */
+static void nicvf_write_to_mbx(struct nicvf *nic, union nic_mbx *mbx)
+{
+ u64 *msg = (u64 *)mbx;
+
+ nicvf_reg_write(nic, NIC_VF_PF_MAILBOX_0_1 + 0, msg[0]);
+ nicvf_reg_write(nic, NIC_VF_PF_MAILBOX_0_1 + 8, msg[1]);
+}
+
+int nicvf_send_msg_to_pf(struct nicvf *nic, union nic_mbx *mbx)
+{
+ int timeout = NIC_PF_VF_MBX_TIMEOUT;
+ int sleep = 10;
+
+ nic->pf_acked = false;
+ nic->pf_nacked = false;
+
+ nicvf_write_to_mbx(nic, mbx);
+
+ nic_handle_mbx_intr(nic->nicpf, nic->vf_id);
+
+ /* Wait for previous message to be acked, timeout 2sec */
+ while (!nic->pf_acked) {
+ if (nic->pf_nacked)
+ return -1;
+ mdelay(sleep);
+ nicvf_handle_mbx_intr(nic);
+
+ if (nic->pf_acked)
+ break;
+ timeout -= sleep;
+ if (!timeout) {
+ printf("PF didn't ack to mbox msg %d from VF%d\n",
+ (mbx->msg.msg & 0xFF), nic->vf_id);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Checks if VF is able to comminicate with PF
+ * and also gets the VNIC number this VF is associated to.
+ */
+static int nicvf_check_pf_ready(struct nicvf *nic)
+{
+ union nic_mbx mbx = {};
+
+ mbx.msg.msg = NIC_MBOX_MSG_READY;
+ if (nicvf_send_msg_to_pf(nic, &mbx)) {
+ printf("PF didn't respond to READY msg\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static void nicvf_handle_mbx_intr(struct nicvf *nic)
+{
+ union nic_mbx mbx = {};
+ struct eth_pdata *pdata = dev_get_platdata(nic->dev);
+ u64 *mbx_data;
+ u64 mbx_addr;
+ int i;
+
+ mbx_addr = NIC_VF_PF_MAILBOX_0_1;
+ mbx_data = (u64 *)&mbx;
+
+ for (i = 0; i < NIC_PF_VF_MAILBOX_SIZE; i++) {
+ *mbx_data = nicvf_reg_read(nic, mbx_addr);
+ mbx_data++;
+ mbx_addr += sizeof(u64);
+ }
+
+ debug("Mbox message: msg: 0x%x\n", mbx.msg.msg);
+ switch (mbx.msg.msg) {
+ case NIC_MBOX_MSG_READY:
+ nic->pf_acked = true;
+ nic->vf_id = mbx.nic_cfg.vf_id & 0x7F;
+ nic->tns_mode = mbx.nic_cfg.tns_mode & 0x7F;
+ nic->node = mbx.nic_cfg.node_id;
+ if (!nic->set_mac_pending)
+ memcpy(pdata->enetaddr,
+ mbx.nic_cfg.mac_addr, 6);
+ nic->loopback_supported = mbx.nic_cfg.loopback_supported;
+ nic->link_up = false;
+ nic->duplex = 0;
+ nic->speed = 0;
+ break;
+ case NIC_MBOX_MSG_ACK:
+ nic->pf_acked = true;
+ break;
+ case NIC_MBOX_MSG_NACK:
+ nic->pf_nacked = true;
+ break;
+ case NIC_MBOX_MSG_BGX_LINK_CHANGE:
+ nic->pf_acked = true;
+ nic->link_up = mbx.link_status.link_up;
+ nic->duplex = mbx.link_status.duplex;
+ nic->speed = mbx.link_status.speed;
+ if (nic->link_up) {
+ printf("%s: Link is Up %d Mbps %s\n",
+ nic->dev->name, nic->speed,
+ nic->duplex == 1 ?
+ "Full duplex" : "Half duplex");
+ } else {
+ printf("%s: Link is Down\n", nic->dev->name);
+ }
+ break;
+ default:
+ printf("Invalid message from PF, msg 0x%x\n", mbx.msg.msg);
+ break;
+ }
+
+ nicvf_clear_intr(nic, NICVF_INTR_MBOX, 0);
+}
+
+static int nicvf_hw_set_mac_addr(struct nicvf *nic, struct udevice *dev)
+{
+ union nic_mbx mbx = {};
+ struct eth_pdata *pdata = dev_get_platdata(dev);
+
+ mbx.mac.msg = NIC_MBOX_MSG_SET_MAC;
+ mbx.mac.vf_id = nic->vf_id;
+ memcpy(mbx.mac.mac_addr, pdata->enetaddr, 6);
+
+ return nicvf_send_msg_to_pf(nic, &mbx);
+}
+
+static void nicvf_config_cpi(struct nicvf *nic)
+{
+ union nic_mbx mbx = {};
+
+ mbx.cpi_cfg.msg = NIC_MBOX_MSG_CPI_CFG;
+ mbx.cpi_cfg.vf_id = nic->vf_id;
+ mbx.cpi_cfg.cpi_alg = nic->cpi_alg;
+ mbx.cpi_cfg.rq_cnt = nic->qs->rq_cnt;
+
+ nicvf_send_msg_to_pf(nic, &mbx);
+}
+
+static int nicvf_init_resources(struct nicvf *nic)
+{
+ int err;
+
+ nic->num_qs = 1;
+
+ /* Enable Qset */
+ nicvf_qset_config(nic, true);
+
+ /* Initialize queues and HW for data transfer */
+ err = nicvf_config_data_transfer(nic, true);
+
+ if (err) {
+ printf("Failed to alloc/config VF's QSet resources\n");
+ return err;
+ }
+ return 0;
+}
+
+static void nicvf_snd_pkt_handler(struct nicvf *nic,
+ struct cmp_queue *cq,
+ void *cq_desc, int cqe_type)
+{
+ struct cqe_send_t *cqe_tx;
+ struct snd_queue *sq;
+ struct sq_hdr_subdesc *hdr;
+
+ cqe_tx = (struct cqe_send_t *)cq_desc;
+ sq = &nic->qs->sq[cqe_tx->sq_idx];
+
+ hdr = (struct sq_hdr_subdesc *)GET_SQ_DESC(sq, cqe_tx->sqe_ptr);
+ if (hdr->subdesc_type != SQ_DESC_TYPE_HEADER)
+ return;
+
+ nicvf_check_cqe_tx_errs(nic, cq, cq_desc);
+ nicvf_put_sq_desc(sq, hdr->subdesc_cnt + 1);
+}
+
+static int nicvf_rcv_pkt_handler(struct nicvf *nic,
+ struct cmp_queue *cq, void *cq_desc,
+ void **ppkt, int cqe_type)
+{
+ void *pkt;
+
+ size_t pkt_len;
+ struct cqe_rx_t *cqe_rx = (struct cqe_rx_t *)cq_desc;
+ int err = 0;
+
+ /* Check for errors */
+ err = nicvf_check_cqe_rx_errs(nic, cq, cq_desc);
+ if (err && !cqe_rx->rb_cnt)
+ return -1;
+
+ pkt = nicvf_get_rcv_pkt(nic, cq_desc, &pkt_len);
+ if (!pkt) {
+ debug("Packet not received\n");
+ return -1;
+ }
+
+ if (pkt)
+ *ppkt = pkt;
+
+ return pkt_len;
+}
+
+int nicvf_cq_handler(struct nicvf *nic, void **ppkt, int *pkt_len)
+{
+ int cq_qnum = 0;
+ int processed_sq_cqe = 0;
+ int processed_rq_cqe = 0;
+ int processed_cqe = 0;
+
+ unsigned long cqe_count, cqe_head;
+ struct queue_set *qs = nic->qs;
+ struct cmp_queue *cq = &qs->cq[cq_qnum];
+ struct cqe_rx_t *cq_desc;
+
+ /* Get num of valid CQ entries expect next one to be SQ completion */
+ cqe_count = nicvf_queue_reg_read(nic, NIC_QSET_CQ_0_7_STATUS, cq_qnum);
+ cqe_count &= 0xFFFF;
+ if (!cqe_count)
+ return 0;
+
+ /* Get head of the valid CQ entries */
+ cqe_head = nicvf_queue_reg_read(nic, NIC_QSET_CQ_0_7_HEAD, cq_qnum);
+ cqe_head >>= 9;
+ cqe_head &= 0xFFFF;
+
+ if (cqe_count) {
+ /* Get the CQ descriptor */
+ cq_desc = (struct cqe_rx_t *)GET_CQ_DESC(cq, cqe_head);
+ cqe_head++;
+ cqe_head &= (cq->dmem.q_len - 1);
+ /* Initiate prefetch for next descriptor */
+ prefetch((struct cqe_rx_t *)GET_CQ_DESC(cq, cqe_head));
+
+ switch (cq_desc->cqe_type) {
+ case CQE_TYPE_RX:
+ debug("%s: Got Rx CQE\n", nic->dev->name);
+ *pkt_len = nicvf_rcv_pkt_handler(nic, cq, cq_desc,
+ ppkt, CQE_TYPE_RX);
+ processed_rq_cqe++;
+ break;
+ case CQE_TYPE_SEND:
+ debug("%s: Got Tx CQE\n", nic->dev->name);
+ nicvf_snd_pkt_handler(nic, cq, cq_desc, CQE_TYPE_SEND);
+ processed_sq_cqe++;
+ break;
+ default:
+ debug("%s: Got CQ type %u\n", nic->dev->name,
+ cq_desc->cqe_type);
+ break;
+ }
+ processed_cqe++;
+ }
+
+ /* Dequeue CQE */
+ nicvf_queue_reg_write(nic, NIC_QSET_CQ_0_7_DOOR,
+ cq_qnum, processed_cqe);
+
+ asm volatile ("dsb sy");
+
+ return (processed_sq_cqe | processed_rq_cqe);
+}
+
+/* Qset error interrupt handler
+ *
+ * As of now only CQ errors are handled
+ */
+void nicvf_handle_qs_err(struct nicvf *nic)
+{
+ struct queue_set *qs = nic->qs;
+ int qidx;
+ u64 status;
+
+ /* Check if it is CQ err */
+ for (qidx = 0; qidx < qs->cq_cnt; qidx++) {
+ status = nicvf_queue_reg_read(nic, NIC_QSET_CQ_0_7_STATUS,
+ qidx);
+ if (!(status & CQ_ERR_MASK))
+ continue;
+ /* Process already queued CQEs and reconfig CQ */
+ nicvf_sq_disable(nic, qidx);
+ nicvf_cmp_queue_config(nic, qs, qidx, true);
+ nicvf_sq_free_used_descs(nic->dev, &qs->sq[qidx], qidx);
+ nicvf_sq_enable(nic, &qs->sq[qidx], qidx);
+ }
+}
+
+static int nicvf_free_pkt(struct udevice *dev, uchar *pkt, int pkt_len)
+{
+ struct nicvf *nic = dev_get_priv(dev);
+
+ if (pkt && pkt_len)
+ free(pkt);
+ nicvf_refill_rbdr(nic);
+ return 0;
+}
+
+static int nicvf_xmit(struct udevice *dev, void *pkt, int pkt_len)
+{
+ struct nicvf *nic = dev_get_priv(dev);
+ int ret = 0;
+ int rcv_len = 0;
+ unsigned int timeout = 5000;
+ void *rpkt = NULL;
+
+ if (!nicvf_sq_append_pkt(nic, pkt, pkt_len)) {
+ printf("VF%d: TX ring full\n", nic->vf_id);
+ return -1;
+ }
+
+ /* check and update CQ for pkt sent */
+ while (!ret && timeout--) {
+ ret = nicvf_cq_handler(nic, &rpkt, &rcv_len);
+ if (!ret) {
+ debug("%s: %d, Not sent\n", __func__, __LINE__);
+ udelay(10);
+ }
+ }
+
+ return 0;
+}
+
+static int nicvf_recv(struct udevice *dev, int flags, uchar **packetp)
+{
+ struct nicvf *nic = dev_get_priv(dev);
+ void *pkt;
+ int pkt_len = 0;
+#ifdef DEBUG
+ u8 *dpkt;
+ int i, j;
+#endif
+
+ nicvf_cq_handler(nic, &pkt, &pkt_len);
+
+ if (pkt_len) {
+#ifdef DEBUG
+ dpkt = pkt;
+ printf("RX packet contents:\n");
+ for (i = 0; i < 8; i++) {
+ puts("\t");
+ for (j = 0; j < 10; j++)
+ printf("%02x ", dpkt[i * 10 + j]);
+ puts("\n");
+ }
+#endif
+ *packetp = pkt;
+ }
+
+ return pkt_len;
+}
+
+void nicvf_stop(struct udevice *dev)
+{
+ struct nicvf *nic = dev_get_priv(dev);
+
+ if (!nic->open)
+ return;
+
+ /* Free resources */
+ nicvf_config_data_transfer(nic, false);
+
+ /* Disable HW Qset */
+ nicvf_qset_config(nic, false);
+
+ nic->open = false;
+}
+
+int nicvf_open(struct udevice *dev)
+{
+ int err;
+ struct nicvf *nic = dev_get_priv(dev);
+
+ nicvf_hw_set_mac_addr(nic, dev);
+
+ /* Configure CPI alorithm */
+ nic->cpi_alg = CPI_ALG_NONE;
+ nicvf_config_cpi(nic);
+
+ /* Initialize the queues */
+ err = nicvf_init_resources(nic);
+ if (err)
+ return -1;
+
+ if (!nicvf_check_pf_ready(nic))
+ return -1;
+
+ nic->open = true;
+
+ /* Make sure queue initialization is written */
+ asm volatile("dsb sy");
+
+ return 0;
+}
+
+int nicvf_write_hwaddr(struct udevice *dev)
+{
+ unsigned char ethaddr[ARP_HLEN];
+ struct eth_pdata *pdata = dev_get_platdata(dev);
+ struct nicvf *nic = dev_get_priv(dev);
+
+ /* If lower level firmware fails to set proper MAC
+ * u-boot framework updates MAC to random address.
+ * Use this hook to update mac address in environment.
+ */
+ if (!eth_env_get_enetaddr_by_index("eth", dev->seq, ethaddr)) {
+ eth_env_set_enetaddr_by_index("eth", dev->seq, pdata->enetaddr);
+ debug("%s: pMAC %pM\n", __func__, pdata->enetaddr);
+ }
+ eth_env_get_enetaddr_by_index("eth", dev->seq, ethaddr);
+ if (memcmp(ethaddr, pdata->enetaddr, ARP_HLEN)) {
+ debug("%s: pMAC %pM\n", __func__, pdata->enetaddr);
+ nicvf_hw_set_mac_addr(nic, dev);
+ }
+ return 0;
+}
+
+static void nicvf_probe_mdio_devices(void)
+{
+ struct udevice *pdev;
+ int err;
+ static int probed;
+
+ if (probed)
+ return;
+
+ err = dm_pci_find_device(PCI_VENDOR_ID_CAVIUM,
+ PCI_DEVICE_ID_CAVIUM_SMI, 0,
+ &pdev);
+ if (err)
+ debug("%s couldn't find SMI device\n", __func__);
+ probed = 1;
+}
+
+int nicvf_initialize(struct udevice *dev)
+{
+ struct nicvf *nicvf = dev_get_priv(dev);
+ struct eth_pdata *pdata = dev_get_platdata(dev);
+ int ret = 0, bgx, lmac;
+ char name[16];
+ unsigned char ethaddr[ARP_HLEN];
+ struct udevice *pfdev;
+ struct nicpf *pf;
+ static int vfid;
+
+ if (dm_pci_find_device(PCI_VENDOR_ID_CAVIUM,
+ PCI_DEVICE_ID_CAVIUM_NIC, 0, &pfdev)) {
+ printf("%s NIC PF device not found..VF probe failed\n",
+ __func__);
+ return -1;
+ }
+ pf = dev_get_priv(pfdev);
+ nicvf->vf_id = vfid++;
+ nicvf->dev = dev;
+ nicvf->nicpf = pf;
+
+ nicvf_probe_mdio_devices();
+
+ /* Enable TSO support */
+ nicvf->hw_tso = true;
+
+ nicvf->reg_base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0,
+ PCI_REGION_MEM);
+
+ debug("nicvf->reg_base: %p\n", nicvf->reg_base);
+
+ if (!nicvf->reg_base) {
+ printf("Cannot map config register space, aborting\n");
+ ret = -1;
+ goto fail;
+ }
+
+ ret = nicvf_set_qset_resources(nicvf);
+ if (ret)
+ return -1;
+
+ sprintf(name, "vnic%u", nicvf->vf_id);
+ debug("%s name %s\n", __func__, name);
+ device_set_name(dev, name);
+
+ bgx = NIC_GET_BGX_FROM_VF_LMAC_MAP(pf->vf_lmac_map[nicvf->vf_id]);
+ lmac = NIC_GET_LMAC_FROM_VF_LMAC_MAP(pf->vf_lmac_map[nicvf->vf_id]);
+ debug("%s VF %d BGX %d LMAC %d\n", __func__, nicvf->vf_id, bgx, lmac);
+ debug("%s PF %p pfdev %p VF %p vfdev %p vf->pdata %p\n",
+ __func__, nicvf->nicpf, nicvf->nicpf->udev, nicvf, nicvf->dev,
+ pdata);
+
+ fdt_board_get_ethaddr(bgx, lmac, ethaddr);
+
+ debug("%s bgx %d lmac %d ethaddr %pM\n", __func__, bgx, lmac, ethaddr);
+
+ if (is_valid_ethaddr(ethaddr)) {
+ memcpy(pdata->enetaddr, ethaddr, ARP_HLEN);
+ eth_env_set_enetaddr_by_index("eth", dev->seq, ethaddr);
+ }
+ debug("%s enetaddr %pM ethaddr %pM\n", __func__,
+ pdata->enetaddr, ethaddr);
+
+fail:
+ return ret;
+}
+
+int octeontx_vnic_probe(struct udevice *dev)
+{
+ return nicvf_initialize(dev);
+}
+
+static const struct eth_ops octeontx_vnic_ops = {
+ .start = nicvf_open,
+ .stop = nicvf_stop,
+ .send = nicvf_xmit,
+ .recv = nicvf_recv,
+ .free_pkt = nicvf_free_pkt,
+ .write_hwaddr = nicvf_write_hwaddr,
+};
+
+U_BOOT_DRIVER(octeontx_vnic) = {
+ .name = "vnic",
+ .id = UCLASS_ETH,
+ .probe = octeontx_vnic_probe,
+ .ops = &octeontx_vnic_ops,
+ .priv_auto_alloc_size = sizeof(struct nicvf),
+ .platdata_auto_alloc_size = sizeof(struct eth_pdata),
+};
+
+static struct pci_device_id octeontx_vnic_supported[] = {
+ { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_NICVF) },
+ { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_NICVF_1) },
+ {}
+};
+
+U_BOOT_PCI_DEVICE(octeontx_vnic, octeontx_vnic_supported);