| // 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 = sizeof(struct nicvf), |
| .platdata_auto = 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); |