From 35060767649b5d58bf2bd92e0c3b0412fb4c798c Mon Sep 17 00:00:00 2001 From: Jason Mei Date: Mon, 5 Feb 2024 14:21:01 +0800 Subject: [PATCH] PCI: EPF: tvnet: porting the PCIe EP tvnet driver Port the PCIe EP tvnet to 5.15 kernel switch to host1x sycpt method and others kernel interfaces Bug 4456727 Bug 4451567 Change-Id: I78a1ed5e1f80210a03dc83775937ba90c2839a5b Signed-off-by: Jason Mei Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3070173 Reviewed-by: Manikanta Maddireddy Reviewed-by: Bibek Basu GVS: Gerrit_Virtual_Submit --- .../endpoint/functions/pci-epf-tegra-vnet.c | 2133 +++++++++++++++++ 1 file changed, 2133 insertions(+) create mode 100644 drivers/pci/endpoint/functions/pci-epf-tegra-vnet.c diff --git a/drivers/pci/endpoint/functions/pci-epf-tegra-vnet.c b/drivers/pci/endpoint/functions/pci-epf-tegra-vnet.c new file mode 100644 index 00000000..4dd4aa7e --- /dev/null +++ b/drivers/pci/endpoint/functions/pci-epf-tegra-vnet.c @@ -0,0 +1,2133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* SPDX-FileCopyrightText: Copyright (c) 2021-2024 NVIDIA CORPORATION & AFFILIATES. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) +#include +#include +#include +#include +#include +#include "pci-epf-wrapper.h" +#else +#include +#include +#include +#include +#endif + +#define BAR0_SIZE SZ_4M + +enum bar0_amap_type { + META_DATA, + SIMPLE_IRQ, + DMA_IRQ = SIMPLE_IRQ, + EP_MEM, + HOST_MEM, + HOST_DMA, + EP_RX_BUF, + AMAP_MAX, +}; + +struct bar0_amap { + int size; + struct page *page; + void *virt; + dma_addr_t iova; + dma_addr_t phy; +}; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) +/* syncpoint handling. */ +struct syncpt_t { + u32 id; + u32 threshold; + struct host1x_syncpt *sp; + + /* syncpoint physical address for stritching to PCIe BAR backing.*/ + size_t size; + phys_addr_t phy_addr; + + bool host1x_cb_set; + /* Lock to protect fences between callback and deinit. */ + struct mutex lock; + /* Fence to specific Threshold. */ + struct dma_fence *fence; + struct dma_fence_cb fence_cb; + /* Work to notify and allocate new fence. */ + struct work_struct work; + void (*notifier)(void *data); + void *notifier_data; + bool fence_release; +}; + +/* NvRmHost1xSyncpointShim have size: 64KB on Orin.*/ +#define SP_SIZE (0x10000) + +/* + * Represents SyncpointShimBase on all T234. + * Each syncpoint is offset at (syncpt_id * SP_SIZE) on SHIM_BASE. + */ +#define SHIM_BASE (0x60000000) + +#endif + +struct irqsp_data { +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + /* Notification. */ + struct syncpt_t syncpt; +#else + struct nvhost_interrupt_syncpt *is; + struct work_struct reprime_work; +#endif + struct device *dev; +}; + +struct pci_epf_tvnet { + struct pci_epf *epf; + struct device *fdev; + struct pci_epf_header header; + void __iomem *dma_base; + struct bar0_amap bar0_amap[AMAP_MAX]; + struct bar_md *bar_md; + dma_addr_t bar0_iova; + struct net_device *ndev; + struct napi_struct napi; + bool pcie_link_status; + struct ep_ring_buf ep_ring_buf; + struct host_ring_buf host_ring_buf; + enum dir_link_state tx_link_state; + enum dir_link_state rx_link_state; + enum os_link_state os_link_state; + /* To synchronize network link state machine*/ + struct mutex link_state_lock; + wait_queue_head_t link_state_wq; + struct list_head h2ep_empty_list; +#if ENABLE_DMA + struct dma_desc_cnt desc_cnt; +#endif + /* To protect h2ep empty list */ + spinlock_t h2ep_empty_lock; + dma_addr_t rx_buf_iova; + unsigned long *rx_buf_bitmap; + int rx_num_pages; + void __iomem *tx_dst_va; + phys_addr_t tx_dst_pci_addr; + void *ep_dma_virt; + dma_addr_t ep_dma_iova; + struct irqsp_data *ctrl_irqsp; + struct irqsp_data *data_irqsp; + struct work_struct raise_irq_work; + + struct tvnet_counter h2ep_ctrl; + struct tvnet_counter ep2h_ctrl; + struct tvnet_counter h2ep_empty; + struct tvnet_counter h2ep_full; + struct tvnet_counter ep2h_empty; + struct tvnet_counter ep2h_full; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + /* DRV_MODE specific.*/ + struct pci_epc *epc; + struct platform_device *host1x_pdev; + atomic_t core_initialized; + /* IOVA alloc abstraction.*/ + struct iova_domain *iovad; + struct iova *iova; +#endif +}; + +static void tvnet_ep_raise_irq_work_function(struct work_struct *work) +{ + struct pci_epf_tvnet *tvnet = + container_of(work, struct pci_epf_tvnet, raise_irq_work); + + struct pci_epc *epc = tvnet->epf->epc; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + struct pci_epf *epf = tvnet->epf; +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, 0); + lpci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, 1); +#else + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0); + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 1); +#endif + +} + +static void tvnet_ep_read_ctrl_msg(struct pci_epf_tvnet *tvnet, + struct ctrl_msg *msg) +{ + struct host_ring_buf *host_ring_buf = &tvnet->host_ring_buf; + struct ctrl_msg *ctrl_msg = host_ring_buf->h2ep_ctrl_msgs; + u32 idx; + + if (tvnet_ivc_empty(&tvnet->h2ep_ctrl)) { + dev_dbg(tvnet->fdev, "%s: H2EP ctrl ring empty\n", __func__); + return; + } + + idx = tvnet_ivc_get_rd_cnt(&tvnet->h2ep_ctrl) % RING_COUNT; + memcpy(msg, &ctrl_msg[idx], sizeof(*msg)); + tvnet_ivc_advance_rd(&tvnet->h2ep_ctrl); +} + +/* TODO Handle error case */ +static int tvnet_ep_write_ctrl_msg(struct pci_epf_tvnet *tvnet, + struct ctrl_msg *msg) +{ + struct ep_ring_buf *ep_ring_buf = &tvnet->ep_ring_buf; + struct ctrl_msg *ctrl_msg = ep_ring_buf->ep2h_ctrl_msgs; + struct pci_epc *epc = tvnet->epf->epc; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + struct pci_epf *epf = tvnet->epf; +#endif + u32 idx; + + if (tvnet_ivc_full(&tvnet->ep2h_ctrl)) { + /* Raise an interrupt to let host process EP2H ring */ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, 0); +#else + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0); +#endif + dev_dbg(tvnet->fdev, "%s: EP2H ctrl ring full\n", __func__); + return -EAGAIN; + } + + idx = tvnet_ivc_get_wr_cnt(&tvnet->ep2h_ctrl) % RING_COUNT; + memcpy(&ctrl_msg[idx], msg, sizeof(*msg)); + tvnet_ivc_advance_wr(&tvnet->ep2h_ctrl); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, 0); +#else + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0); +#endif + + return 0; +} + +#if !ENABLE_DMA +static dma_addr_t tvnet_ivoa_alloc(struct pci_epf_tvnet *tvnet) +{ + dma_addr_t iova; + int pageno; + + pageno = bitmap_find_free_region(tvnet->rx_buf_bitmap, + tvnet->rx_num_pages, 0); + if (pageno < 0) { + dev_err(tvnet->fdev, "%s: Rx iova alloc fail, page: %d\n", + __func__, pageno); + return DMA_ERROR_CODE; + } + iova = tvnet->rx_buf_iova + (pageno << PAGE_SHIFT); + + return iova; +} + +static void tvnet_ep_iova_dealloc(struct pci_epf_tvnet *tvnet, dma_addr_t iova) +{ + int pageno; + + pageno = (iova - tvnet->rx_buf_iova) >> PAGE_SHIFT; + bitmap_release_region(tvnet->rx_buf_bitmap, pageno, 0); +} +#endif + +static void tvnet_ep_alloc_empty_buffers(struct pci_epf_tvnet *tvnet) +{ + struct ep_ring_buf *ep_ring_buf = &tvnet->ep_ring_buf; + struct pci_epc *epc = tvnet->epf->epc; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + struct pci_epf *epf = tvnet->epf; +#endif + struct device *cdev = epc->dev.parent; + struct data_msg *h2ep_empty_msg = ep_ring_buf->h2ep_empty_msgs; + struct h2ep_empty_list *h2ep_empty_ptr; +#if ENABLE_DMA + struct net_device *ndev = tvnet->ndev; +#else + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); + int ret = 0; +#endif + + while (!tvnet_ivc_full(&tvnet->h2ep_empty)) { + dma_addr_t iova; +#if ENABLE_DMA + struct sk_buff *skb; + int len = ndev->mtu + ETH_HLEN; +#else + struct page *page; + void *virt; +#endif + u32 idx; + unsigned long flags; + +#if ENABLE_DMA + skb = netdev_alloc_skb(ndev, len); + if (!skb) { + pr_err("%s: alloc skb failed\n", __func__); + break; + } + + iova = dma_map_single(cdev, skb->data, len, DMA_FROM_DEVICE); + if (dma_mapping_error(cdev, iova)) { + pr_err("%s: dma map failed\n", __func__); + dev_kfree_skb_any(skb); + break; + } + +#else + iova = tvnet_ivoa_alloc(tvnet); + if (iova == DMA_ERROR_CODE) { + dev_err(tvnet->fdev, "%s: iova alloc failed\n", + __func__); + break; + } + + page = alloc_pages(GFP_KERNEL, 1); + if (!page) { + dev_err(tvnet->fdev, "%s: alloc_pages() failed\n", + __func__); + tvnet_ep_iova_dealloc(tvnet, iova); + break; + } + + ret = iommu_map(domain, iova, page_to_phys(page), PAGE_SIZE, + IOMMU_CACHE | IOMMU_READ | IOMMU_WRITE); + if (ret < 0) { + dev_err(tvnet->fdev, "%s: iommu_map(RAM) failed: %d\n", + __func__, ret); + __free_pages(page, 1); + tvnet_ep_iova_dealloc(tvnet, iova); + break; + } + + virt = vmap(&page, 1, VM_MAP, PAGE_KERNEL); + if (!virt) { + dev_err(tvnet->fdev, "%s: vmap() failed\n", __func__); + iommu_unmap(domain, iova, PAGE_SIZE); + __free_pages(page, 1); + tvnet_ep_iova_dealloc(tvnet, iova); + break; + } +#endif + + h2ep_empty_ptr = kmalloc(sizeof(*h2ep_empty_ptr), GFP_KERNEL); + if (!h2ep_empty_ptr) { +#if ENABLE_DMA + dma_unmap_single(cdev, iova, len, DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); +#else + vunmap(virt); + iommu_unmap(domain, iova, PAGE_SIZE); + __free_pages(page, 1); + tvnet_ep_iova_dealloc(tvnet, iova); +#endif + break; + } + +#if ENABLE_DMA + h2ep_empty_ptr->skb = skb; + h2ep_empty_ptr->size = len; +#else + h2ep_empty_ptr->page = page; + h2ep_empty_ptr->virt = virt; + h2ep_empty_ptr->size = PAGE_SIZE; +#endif + h2ep_empty_ptr->iova = iova; + spin_lock_irqsave(&tvnet->h2ep_empty_lock, flags); + list_add_tail(&h2ep_empty_ptr->list, &tvnet->h2ep_empty_list); + spin_unlock_irqrestore(&tvnet->h2ep_empty_lock, flags); + + idx = tvnet_ivc_get_wr_cnt(&tvnet->h2ep_empty) % RING_COUNT; + h2ep_empty_msg[idx].u.empty_buffer.pcie_address = iova; + h2ep_empty_msg[idx].u.empty_buffer.buffer_len = PAGE_SIZE; + tvnet_ivc_advance_wr(&tvnet->h2ep_empty); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, 0); +#else + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0); +#endif + } +} + +static void tvnet_ep_free_empty_buffers(struct pci_epf_tvnet *tvnet) +{ + struct pci_epf *epf = tvnet->epf; + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; +#if !ENABLE_DMA + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); +#endif + struct h2ep_empty_list *h2ep_empty_ptr, *temp; + unsigned long flags; + + spin_lock_irqsave(&tvnet->h2ep_empty_lock, flags); + list_for_each_entry_safe(h2ep_empty_ptr, temp, &tvnet->h2ep_empty_list, + list) { + list_del(&h2ep_empty_ptr->list); +#if ENABLE_DMA + dma_unmap_single(cdev, h2ep_empty_ptr->iova, + h2ep_empty_ptr->size, DMA_FROM_DEVICE); + dev_kfree_skb_any(h2ep_empty_ptr->skb); +#else + vunmap(h2ep_empty_ptr->virt); + iommu_unmap(domain, h2ep_empty_ptr->iova, PAGE_SIZE); + __free_pages(h2ep_empty_ptr->page, 1); + tvnet_ep_iova_dealloc(tvnet, h2ep_empty_ptr->iova); +#endif + kfree(h2ep_empty_ptr); + } + spin_unlock_irqrestore(&tvnet->h2ep_empty_lock, flags); +} + +static void tvnet_ep_stop_tx_queue(struct pci_epf_tvnet *tvnet) +{ + struct net_device *ndev = tvnet->ndev; + + netif_stop_queue(ndev); + /* Get tx lock to make sure that there is no ongoing xmit */ + netif_tx_lock(ndev); + netif_tx_unlock(ndev); +} + +static void tvnet_ep_stop_rx_work(struct pci_epf_tvnet *tvnet) +{ + /* TODO wait for syncpoint interrupt handlers */ +} + +static void tvnet_ep_clear_data_msg_counters(struct pci_epf_tvnet *tvnet) +{ + struct host_ring_buf *host_ring_buf = &tvnet->host_ring_buf; + struct host_own_cnt *host_cnt = host_ring_buf->host_cnt; + struct ep_ring_buf *ep_ring_buf = &tvnet->ep_ring_buf; + struct ep_own_cnt *ep_cnt = ep_ring_buf->ep_cnt; + + host_cnt->h2ep_empty_rd_cnt = 0; + ep_cnt->h2ep_empty_wr_cnt = 0; + ep_cnt->ep2h_full_wr_cnt = 0; + host_cnt->ep2h_full_rd_cnt = 0; +} + +static void tvnet_ep_update_link_state(struct net_device *ndev, + enum os_link_state state) +{ + if (state == OS_LINK_STATE_UP) { + netif_start_queue(ndev); + netif_carrier_on(ndev); + } else if (state == OS_LINK_STATE_DOWN) { + netif_carrier_off(ndev); + netif_stop_queue(ndev); + } else { + pr_err("%s: invalid sate: %d\n", __func__, state); + } +} + +/* OS link state machine */ +static void tvnet_ep_update_link_sm(struct pci_epf_tvnet *tvnet) +{ + struct net_device *ndev = tvnet->ndev; + enum os_link_state old_state = tvnet->os_link_state; + + if ((tvnet->rx_link_state == DIR_LINK_STATE_UP) && + (tvnet->tx_link_state == DIR_LINK_STATE_UP)) + tvnet->os_link_state = OS_LINK_STATE_UP; + else + tvnet->os_link_state = OS_LINK_STATE_DOWN; + + if (tvnet->os_link_state != old_state) + tvnet_ep_update_link_state(ndev, tvnet->os_link_state); +} + +/* One way link state machine */ +static void tvnet_ep_user_link_up_req(struct pci_epf_tvnet *tvnet) +{ + struct ctrl_msg msg; + + tvnet_ep_clear_data_msg_counters(tvnet); + tvnet_ep_alloc_empty_buffers(tvnet); + msg.msg_id = CTRL_MSG_LINK_UP; + tvnet_ep_write_ctrl_msg(tvnet, &msg); + tvnet->rx_link_state = DIR_LINK_STATE_UP; + tvnet_ep_update_link_sm(tvnet); +} + +static void tvnet_ep_user_link_down_req(struct pci_epf_tvnet *tvnet) +{ + struct ctrl_msg msg; + + tvnet->rx_link_state = DIR_LINK_STATE_SENT_DOWN; + msg.msg_id = CTRL_MSG_LINK_DOWN; + tvnet_ep_write_ctrl_msg(tvnet, &msg); + tvnet_ep_update_link_sm(tvnet); +} + +static void tvnet_ep_rcv_link_up_msg(struct pci_epf_tvnet *tvnet) +{ + tvnet->tx_link_state = DIR_LINK_STATE_UP; + tvnet_ep_update_link_sm(tvnet); +} + +static void tvnet_ep_rcv_link_down_msg(struct pci_epf_tvnet *tvnet) +{ + struct ctrl_msg msg; + + /* Stop using empty buffers of remote system */ + tvnet_ep_stop_tx_queue(tvnet); + msg.msg_id = CTRL_MSG_LINK_DOWN_ACK; + tvnet_ep_write_ctrl_msg(tvnet, &msg); + tvnet->tx_link_state = DIR_LINK_STATE_DOWN; + tvnet_ep_update_link_sm(tvnet); +} + +static void tvnet_ep_rcv_link_down_ack(struct pci_epf_tvnet *tvnet) +{ + /* Stop using empty buffers(which are full in rx) of local system */ + tvnet_ep_stop_rx_work(tvnet); + tvnet_ep_free_empty_buffers(tvnet); + tvnet->rx_link_state = DIR_LINK_STATE_DOWN; + wake_up_interruptible(&tvnet->link_state_wq); + tvnet_ep_update_link_sm(tvnet); +} + +static int tvnet_ep_open(struct net_device *ndev) +{ + struct device *fdev = ndev->dev.parent; + struct pci_epf_tvnet *tvnet = dev_get_drvdata(fdev); + + if (!tvnet->pcie_link_status) { + dev_err(fdev, "%s: PCIe link is not up\n", __func__); + return -ENODEV; + } + + mutex_lock(&tvnet->link_state_lock); + if (tvnet->rx_link_state == DIR_LINK_STATE_DOWN) + tvnet_ep_user_link_up_req(tvnet); + napi_enable(&tvnet->napi); + mutex_unlock(&tvnet->link_state_lock); + + return 0; +} + +static int tvnet_ep_close(struct net_device *ndev) +{ + struct device *fdev = ndev->dev.parent; + struct pci_epf_tvnet *tvnet = dev_get_drvdata(fdev); + int ret = 0; + + mutex_lock(&tvnet->link_state_lock); + napi_disable(&tvnet->napi); + if (tvnet->rx_link_state == DIR_LINK_STATE_UP) + tvnet_ep_user_link_down_req(tvnet); + + ret = wait_event_interruptible_timeout(tvnet->link_state_wq, + (tvnet->rx_link_state == + DIR_LINK_STATE_DOWN), + msecs_to_jiffies(LINK_TIMEOUT)); + ret = (ret > 0) ? 0 : -ETIMEDOUT; + if (ret < 0) { + pr_err("%s: link state machine failed: tx_state: %d rx_state: %d err: %d\n", + __func__, tvnet->tx_link_state, tvnet->rx_link_state, + ret); + tvnet->rx_link_state = DIR_LINK_STATE_UP; + } + mutex_unlock(&tvnet->link_state_lock); + + return 0; +} + +static int tvnet_ep_change_mtu(struct net_device *ndev, int new_mtu) +{ + bool set_down = false; + + if (new_mtu > TVNET_MAX_MTU || new_mtu < TVNET_MIN_MTU) { + pr_err("MTU range is %d to %d\n", TVNET_MIN_MTU, TVNET_MAX_MTU); + return -EINVAL; + } + + if (netif_running(ndev)) { + set_down = true; + tvnet_ep_close(ndev); + } + + pr_info("changing MTU from %d to %d\n", ndev->mtu, new_mtu); + + ndev->mtu = new_mtu; + + if (set_down) + tvnet_ep_open(ndev); + + return 0; +} + +static netdev_tx_t tvnet_ep_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct device *fdev = ndev->dev.parent; + struct pci_epf_tvnet *tvnet = dev_get_drvdata(fdev); + struct host_ring_buf *host_ring_buf = &tvnet->host_ring_buf; + struct ep_ring_buf *ep_ring_buf = &tvnet->ep_ring_buf; + struct data_msg *ep2h_full_msg = ep_ring_buf->ep2h_full_msgs; + struct skb_shared_info *info = skb_shinfo(skb); + struct data_msg *ep2h_empty_msg = host_ring_buf->ep2h_empty_msgs; + struct pci_epf *epf = tvnet->epf; + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; +#if ENABLE_DMA + struct dma_desc_cnt *desc_cnt = &tvnet->desc_cnt; + struct tvnet_dma_desc *ep_dma_virt = + (struct tvnet_dma_desc *)tvnet->ep_dma_virt; + u32 desc_widx, desc_ridx, val, ctrl_d; + unsigned long timeout; +#else + int ret; +#endif + dma_addr_t src_iova; + u32 rd_idx, wr_idx; + u64 dst_masked, dst_off, dst_iova; + int dst_len, len; + + /*TODO Not expecting skb frags, remove this after testing */ + WARN_ON(info->nr_frags); + + /* Check if EP2H_EMPTY_BUF available to read */ + if (!tvnet_ivc_rd_available(&tvnet->ep2h_empty)) { +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, 0); +#else + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0); +#endif + dev_dbg(fdev, "%s: No EP2H empty msg, stop tx\n", __func__); + netif_stop_queue(ndev); + return NETDEV_TX_BUSY; + } + + /* Check if EP2H_FULL_BUF available to write */ + if (tvnet_ivc_full(&tvnet->ep2h_full)) { +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_raise_irq(epc, epf->func_no, PCI_EPC_IRQ_MSIX, 1); +#else + pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 1); +#endif + dev_dbg(fdev, "%s: No EP2H full buf, stop tx\n", __func__); + netif_stop_queue(ndev); + return NETDEV_TX_BUSY; + } + +#if ENABLE_DMA + /* Check if dma desc available */ + if ((desc_cnt->wr_cnt - desc_cnt->rd_cnt) >= DMA_DESC_COUNT) { + dev_dbg(fdev, "%s: dma descs are not available\n", __func__); + netif_stop_queue(ndev); + return NETDEV_TX_BUSY; + } +#endif + + len = skb_headlen(skb); + + src_iova = dma_map_single(cdev, skb->data, len, DMA_TO_DEVICE); + if (dma_mapping_error(cdev, src_iova)) { + dev_err(fdev, "%s: dma_map_single failed\n", __func__); + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + /* Get EP2H empty msg */ + rd_idx = tvnet_ivc_get_rd_cnt(&tvnet->ep2h_empty) % RING_COUNT; + dst_iova = ep2h_empty_msg[rd_idx].u.empty_buffer.pcie_address; + dst_len = ep2h_empty_msg[rd_idx].u.empty_buffer.buffer_len; + + /* + * Map host dst mem to local PCIe address range. + * PCIe address range is SZ_64K aligned. + */ + dst_masked = (dst_iova & ~(SZ_64K - 1)); + dst_off = (dst_iova & (SZ_64K - 1)); + +#if !ENABLE_DMA +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + ret = lpci_epc_map_addr(epc, epf->func_no, tvnet->tx_dst_pci_addr, + dst_masked, dst_len); +#else + ret = pci_epc_map_addr(epc, tvnet->tx_dst_pci_addr, dst_masked, + dst_len); +#endif + if (ret < 0) { + dev_err(fdev, "failed to map dst addr to PCIe addr range\n"); + dma_unmap_single(cdev, src_iova, len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } +#endif + /* + * Advance read count after all failure cases completed, to avoid + * dangling buffer at host. + */ + tvnet_ivc_advance_rd(&tvnet->ep2h_empty); + +#if ENABLE_DMA + /* Trigger DMA write from src_iova to dst_iova */ + desc_widx = desc_cnt->wr_cnt % DMA_DESC_COUNT; + ep_dma_virt[desc_widx].size = len; + ep_dma_virt[desc_widx].sar_low = lower_32_bits(src_iova); + ep_dma_virt[desc_widx].sar_high = upper_32_bits(src_iova); + ep_dma_virt[desc_widx].dar_low = lower_32_bits(dst_iova); + ep_dma_virt[desc_widx].dar_high = upper_32_bits(dst_iova); + /* CB bit should be set at the end */ + mb(); + ctrl_d = DMA_CH_CONTROL1_OFF_WRCH_LIE; + ctrl_d |= DMA_CH_CONTROL1_OFF_WRCH_CB; + ep_dma_virt[desc_widx].ctrl_reg.ctrl_d = ctrl_d; + + /* DMA write should not go out of order wrt CB bit set */ + mb(); + + timeout = jiffies + msecs_to_jiffies(1000); + dma_common_wr8(tvnet->dma_base, DMA_WR_DATA_CH, DMA_WRITE_DOORBELL_OFF); + desc_cnt->wr_cnt++; + + while (true) { + val = dma_common_rd(tvnet->dma_base, DMA_WRITE_INT_STATUS_OFF); + if (val == BIT(DMA_WR_DATA_CH)) { + dma_common_wr(tvnet->dma_base, val, + DMA_WRITE_INT_CLEAR_OFF); + break; + } + if (time_after(jiffies, timeout)) { + dev_err(fdev, "dma took more time, reset dma engine\n"); + dma_common_wr(tvnet->dma_base, + DMA_WRITE_ENGINE_EN_OFF_DISABLE, + DMA_WRITE_ENGINE_EN_OFF); + mdelay(1); + dma_common_wr(tvnet->dma_base, + DMA_WRITE_ENGINE_EN_OFF_ENABLE, + DMA_WRITE_ENGINE_EN_OFF); + desc_cnt->wr_cnt--; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_unmap_addr(epc, epf->func_no, tvnet->tx_dst_pci_addr); +#else + pci_epc_unmap_addr(epc, tvnet->tx_dst_pci_addr); +#endif + dma_unmap_single(cdev, src_iova, len, DMA_TO_DEVICE); + return NETDEV_TX_BUSY; + } + } + + desc_ridx = tvnet->desc_cnt.rd_cnt % DMA_DESC_COUNT; + /* Clear DMA cycle bit and increment rd_cnt */ + ep_dma_virt[desc_ridx].ctrl_reg.ctrl_e.cb = 0; + mb(); + + tvnet->desc_cnt.rd_cnt++; +#else + /* Copy skb->data to host dst address, use CPU virt addr */ + memcpy((void *)(tvnet->tx_dst_va + dst_off), skb->data, len); + /* + * tx_dst_va is ioremap_wc() mem, add mb to make sure complete skb->data + * written to dst before adding it to full buffer + */ + mb(); +#endif + + /* Push dst to EP2H full ring */ + wr_idx = tvnet_ivc_get_wr_cnt(&tvnet->ep2h_full) % RING_COUNT; + ep2h_full_msg[wr_idx].u.full_buffer.packet_size = len; + ep2h_full_msg[wr_idx].u.full_buffer.pcie_address = dst_iova; + tvnet_ivc_advance_wr(&tvnet->ep2h_full); + + /* Free temp src and skb */ +#if !ENABLE_DMA +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_unmap_addr(epc, epf->func_no, tvnet->tx_dst_pci_addr); +#else + pci_epc_unmap_addr(epc, tvnet->tx_dst_pci_addr); +#endif +#endif + dma_unmap_single(cdev, src_iova, len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + schedule_work(&tvnet->raise_irq_work); + + return NETDEV_TX_OK; +} + +static const struct net_device_ops tvnet_netdev_ops = { + .ndo_open = tvnet_ep_open, + .ndo_stop = tvnet_ep_close, + .ndo_start_xmit = tvnet_ep_start_xmit, + .ndo_change_mtu = tvnet_ep_change_mtu, +}; + +static void tvnet_ep_process_ctrl_msg(struct pci_epf_tvnet *tvnet) +{ + struct ctrl_msg msg; + + while (tvnet_ivc_rd_available(&tvnet->h2ep_ctrl)) { + tvnet_ep_read_ctrl_msg(tvnet, &msg); + if (msg.msg_id == CTRL_MSG_LINK_UP) + tvnet_ep_rcv_link_up_msg(tvnet); + else if (msg.msg_id == CTRL_MSG_LINK_DOWN) + tvnet_ep_rcv_link_down_msg(tvnet); + else if (msg.msg_id == CTRL_MSG_LINK_DOWN_ACK) + tvnet_ep_rcv_link_down_ack(tvnet); + } +} + +static int tvnet_ep_process_h2ep_msg(struct pci_epf_tvnet *tvnet) +{ + struct host_ring_buf *host_ring_buf = &tvnet->host_ring_buf; + struct data_msg *data_msg = host_ring_buf->h2ep_full_msgs; + struct pci_epf *epf = tvnet->epf; + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; + struct h2ep_empty_list *h2ep_empty_ptr; + struct net_device *ndev = tvnet->ndev; +#if !ENABLE_DMA + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); +#endif + int count = 0; + + while ((count < TVNET_NAPI_WEIGHT) && + tvnet_ivc_rd_available(&tvnet->h2ep_full)) { + struct sk_buff *skb; + int idx, found = 0; + u32 len; + u64 pcie_address; + unsigned long flags; + + /* Read H2EP full msg */ + idx = tvnet_ivc_get_rd_cnt(&tvnet->h2ep_full) % RING_COUNT; + len = data_msg[idx].u.full_buffer.packet_size; + pcie_address = data_msg[idx].u.full_buffer.pcie_address; + + /* Get H2EP msg pointer from saved list */ + spin_lock_irqsave(&tvnet->h2ep_empty_lock, flags); + list_for_each_entry(h2ep_empty_ptr, &tvnet->h2ep_empty_list, + list) { + if (h2ep_empty_ptr->iova == pcie_address) { + found = 1; + break; + } + } + WARN_ON(!found); + list_del(&h2ep_empty_ptr->list); + spin_unlock_irqrestore(&tvnet->h2ep_empty_lock, flags); + + /* Advance H2EP full buffer after search in local list */ + tvnet_ivc_advance_rd(&tvnet->h2ep_full); +#if ENABLE_DMA + dma_unmap_single(cdev, pcie_address, ndev->mtu, + DMA_FROM_DEVICE); + skb = h2ep_empty_ptr->skb; + skb_put(skb, len); + skb->protocol = eth_type_trans(skb, ndev); + napi_gro_receive(&tvnet->napi, skb); +#else + /* Alloc new skb and copy data from full buffer */ + skb = netdev_alloc_skb(ndev, len); + memcpy(skb->data, h2ep_empty_ptr->virt, len); + skb_put(skb, len); + skb->protocol = eth_type_trans(skb, ndev); + napi_gro_receive(&tvnet->napi, skb); + + /* Free H2EP dst msg */ + vunmap(h2ep_empty_ptr->virt); + iommu_unmap(domain, h2ep_empty_ptr->iova, PAGE_SIZE); + __free_pages(h2ep_empty_ptr->page, 1); + tvnet_ep_iova_dealloc(tvnet, h2ep_empty_ptr->iova); +#endif + + kfree(h2ep_empty_ptr); + count++; + } + + return count; +} + +#if ENABLE_DMA +static void tvnet_ep_setup_dma(struct pci_epf_tvnet *tvnet) +{ + dma_addr_t iova = tvnet->bar0_amap[HOST_DMA].iova; + struct dma_desc_cnt *desc_cnt = &tvnet->desc_cnt; + u32 val; + + desc_cnt->rd_cnt = desc_cnt->wr_cnt = 0; + + /* Enable linked list mode and set CCS for write channel-0 */ + val = dma_channel_rd(tvnet->dma_base, DMA_WR_DATA_CH, + DMA_CH_CONTROL1_OFF_WRCH); + val |= DMA_CH_CONTROL1_OFF_WRCH_LLE; + val |= DMA_CH_CONTROL1_OFF_WRCH_CCS; + dma_channel_wr(tvnet->dma_base, DMA_WR_DATA_CH, val, + DMA_CH_CONTROL1_OFF_WRCH); + + /* Unmask write channel-0 done irq to enable LIE */ + val = dma_common_rd(tvnet->dma_base, DMA_WRITE_INT_MASK_OFF); + val &= ~0x1; + dma_common_wr(tvnet->dma_base, val, DMA_WRITE_INT_MASK_OFF); + + /* Enable write channel-0 local abort irq */ + val = dma_common_rd(tvnet->dma_base, DMA_WRITE_LINKED_LIST_ERR_EN_OFF); + val |= (0x1 << 16); + dma_common_wr(tvnet->dma_base, val, DMA_WRITE_LINKED_LIST_ERR_EN_OFF); + + /* Program DMA write linked list base address to DMA LLP register */ + dma_channel_wr(tvnet->dma_base, DMA_WR_DATA_CH, + lower_32_bits(tvnet->ep_dma_iova), + DMA_LLP_LOW_OFF_WRCH); + dma_channel_wr(tvnet->dma_base, DMA_WR_DATA_CH, + upper_32_bits(tvnet->ep_dma_iova), + DMA_LLP_HIGH_OFF_WRCH); + + /* Enable DMA write engine */ + dma_common_wr(tvnet->dma_base, DMA_WRITE_ENGINE_EN_OFF_ENABLE, + DMA_WRITE_ENGINE_EN_OFF); + + /* Enable linked list mode and set CCS for read channel-0 */ + val = dma_channel_rd(tvnet->dma_base, DMA_RD_DATA_CH, + DMA_CH_CONTROL1_OFF_RDCH); + val |= DMA_CH_CONTROL1_OFF_RDCH_LLE; + val |= DMA_CH_CONTROL1_OFF_RDCH_CCS; + dma_channel_wr(tvnet->dma_base, DMA_RD_DATA_CH, val, + DMA_CH_CONTROL1_OFF_RDCH); + + /* Mask read channel-0 done irq to enable RIE */ + val = dma_common_rd(tvnet->dma_base, DMA_READ_INT_MASK_OFF); + val |= 0x1; + dma_common_wr(tvnet->dma_base, val, DMA_READ_INT_MASK_OFF); + + val = dma_common_rd(tvnet->dma_base, DMA_READ_LINKED_LIST_ERR_EN_OFF); + /* Enable read channel-0 remote abort irq */ + val |= 0x1; + dma_common_wr(tvnet->dma_base, val, DMA_READ_LINKED_LIST_ERR_EN_OFF); + + /* Program DMA read linked list base address to DMA LLP register */ + dma_channel_wr(tvnet->dma_base, DMA_RD_DATA_CH, + lower_32_bits(iova), DMA_LLP_LOW_OFF_RDCH); + dma_channel_wr(tvnet->dma_base, DMA_RD_DATA_CH, + upper_32_bits(iova), DMA_LLP_HIGH_OFF_RDCH); + + /* Enable DMA read engine */ + dma_common_wr(tvnet->dma_base, DMA_READ_ENGINE_EN_OFF_ENABLE, + DMA_READ_ENGINE_EN_OFF); +} +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) +/* Returns aperture offset of syncpoint on SHIM_BASE. */ +static inline u64 get_syncpt_shim_offset(u32 id) +{ + return (SHIM_BASE + ((u64)id * SP_SIZE)); +} + +static void +host1x_cb_func(struct dma_fence *f, struct dma_fence_cb *cb) +{ + struct syncpt_t *syncpt = container_of(cb, struct syncpt_t, fence_cb); + + schedule_work(&syncpt->work); +} + +static int +allocate_fence(struct syncpt_t *syncpt) +{ + int ret = 0; + struct dma_fence *fence = NULL; + + fence = host1x_fence_create(syncpt->sp, ++syncpt->threshold, false); + if (IS_ERR_OR_NULL(fence)) { + ret = PTR_ERR(fence); + pr_err("host1x_fence_create failed with: %d\n", ret); + return ret; + } + + mutex_lock(&syncpt->lock); + ret = dma_fence_add_callback(fence, &syncpt->fence_cb, host1x_cb_func); + if (ret != 0) { + /* If already expired. */ + if (ret == -ENOENT) { + ret = 0; + schedule_work(&syncpt->work); + } + goto put_fence; + } + syncpt->fence = fence; + mutex_unlock(&syncpt->lock); + + return ret; + +put_fence: + dma_fence_put(fence); + mutex_unlock(&syncpt->lock); + return ret; +} + +static void +fence_do_work(struct syncpt_t *syncpt) +{ + int ret = 0; + + if (syncpt->notifier) { + syncpt->notifier(syncpt->notifier_data); + } + + mutex_lock(&syncpt->lock); + /* If deinit triggered, no need to proceed. */ + if (syncpt->fence_release) + return; + + if (syncpt->fence) { + dma_fence_put(syncpt->fence); + syncpt->fence = NULL; + } + mutex_unlock(&syncpt->lock); + + ret = allocate_fence(syncpt); + if (ret != 0) { + mutex_unlock(&syncpt->lock); + pr_err("allocate_fence failed with: %d\n", ret); + return; + } +} +#endif + +static void tvnet_ep_ctrl_irqsp_work(struct work_struct *work) +{ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + struct syncpt_t *syncpt = + container_of(work, struct syncpt_t, work); + + fence_do_work(syncpt); +#else + struct irqsp_data *data_irqsp = + container_of(work, struct irqsp_data, reprime_work); + nvhost_interrupt_syncpt_prime(data_irqsp->sp); +#endif +} + +static void tvnet_ep_ctrl_irqsp_callback(void *private_data) +{ + struct irqsp_data *data_irqsp = private_data; + struct pci_epf_tvnet *tvnet = dev_get_drvdata(data_irqsp->dev); + struct net_device *ndev = tvnet->ndev; + + if (netif_queue_stopped(ndev)) { + if ((tvnet->os_link_state == OS_LINK_STATE_UP) && + tvnet_ivc_rd_available(&tvnet->ep2h_empty) && + !tvnet_ivc_full(&tvnet->ep2h_full)) { + netif_wake_queue(ndev); + } + } + + if (tvnet_ivc_rd_available(&tvnet->h2ep_ctrl)) + tvnet_ep_process_ctrl_msg(tvnet); + + if (!tvnet_ivc_full(&tvnet->h2ep_empty) && + (tvnet->os_link_state == OS_LINK_STATE_UP)) + tvnet_ep_alloc_empty_buffers(tvnet); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + schedule_work(&data_irqsp->reprime_work); +#endif +} + +static void tvnet_ep_data_irqsp_work(struct work_struct *work) +{ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + struct syncpt_t *syncpt = + container_of(work, struct syncpt_t, work); + + fence_do_work(syncpt); +#else + struct irqsp_data *data_irqsp = + container_of(work, struct irqsp_data, reprime_work); + + nvhost_interrupt_syncpt_prime(data_irqsp->is); +#endif +} + +static void tvnet_ep_data_irqsp_callback(void *private_data) +{ + struct irqsp_data *data_irqsp = private_data; + struct pci_epf_tvnet *tvnet = dev_get_drvdata(data_irqsp->dev); + + if (tvnet_ivc_rd_available(&tvnet->h2ep_full)) + napi_schedule(&tvnet->napi); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + else + schedule_work(&data_irqsp->reprime_work); +#endif +} + +static int tvnet_ep_poll(struct napi_struct *napi, int budget) +{ + struct pci_epf_tvnet *tvnet = container_of(napi, struct pci_epf_tvnet, + napi); + struct irqsp_data *data_irqsp = tvnet->data_irqsp; + int work_done; + + work_done = tvnet_ep_process_h2ep_msg(tvnet); + if (work_done < budget) { + napi_complete(napi); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + schedule_work(&data_irqsp->syncpt.work); +#else + schedule_work(&data_irqsp->reprime_work); +#endif + } + + return work_done; +} + +static int tvnet_ep_pci_epf_setup_irqsp(struct pci_epf_tvnet *tvnet) +{ + struct bar0_amap *amap = &tvnet->bar0_amap[SIMPLE_IRQ]; + struct irqsp_data *ctrl_irqsp, *data_irqsp; + struct pci_epf *epf = tvnet->epf; + struct device *fdev = tvnet->fdev; + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); + struct irq_md *irq; + phys_addr_t syncpt_addr; + int ret; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + struct host1x *host1x = NULL; + struct syncpt_t *syncpt = NULL; + + host1x = platform_get_drvdata(tvnet->host1x_pdev); + if (!host1x) { + pr_err("Host1x handle is null."); + return -EINVAL; + } +#endif + ctrl_irqsp = devm_kzalloc(fdev, sizeof(*ctrl_irqsp), GFP_KERNEL); + if (!ctrl_irqsp) { + ret = -ENOMEM; + goto fail; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + syncpt = &ctrl_irqsp->syncpt; + syncpt->sp = host1x_syncpt_alloc(host1x, HOST1X_SYNCPT_CLIENT_MANAGED, + "pcie-ep-vnet-ctrl"); + if (IS_ERR_OR_NULL(syncpt->sp)) { + ret = -ENOMEM; + pr_err("Failed to reserve comm notify syncpt\n"); + goto free_ctrl_sp; + } + + syncpt->id = host1x_syncpt_id(syncpt->sp); + ctrl_irqsp->dev = fdev; + INIT_WORK(&syncpt->work, tvnet_ep_ctrl_irqsp_work); + tvnet->ctrl_irqsp = ctrl_irqsp; + + syncpt->threshold = host1x_syncpt_read(syncpt->sp); + + /* enable syncpt notifications handling from peer.*/ + mutex_init(&syncpt->lock); + syncpt->notifier = tvnet_ep_ctrl_irqsp_callback; + syncpt->notifier_data = (void *)ctrl_irqsp; + syncpt->host1x_cb_set = true; + syncpt->fence_release = false; + + ret = allocate_fence(syncpt); + if (ret != 0) { + pr_err("allocate_fence failed with: %d\n", ret); + goto free_ctrl_sp; + } +#else + ctrl_irqsp->is = + nvhost_interrupt_syncpt_get(cdev->of_node, + tvnet_ep_ctrl_irqsp_callback, + ctrl_irqsp); + if (IS_ERR(ctrl_irqsp->is)) { + ret = PTR_ERR(ctrl_irqsp->is); + dev_err(fdev, "failed to get ctrl syncpt irq: %d\n", ret); + goto fail; + } + + ctrl_irqsp->dev = fdev; + INIT_WORK(&ctrl_irqsp->reprime_work, tvnet_ep_ctrl_irqsp_reprime_work); + tvnet->ctrl_irqsp = ctrl_irqsp; +#endif + data_irqsp = devm_kzalloc(fdev, sizeof(*data_irqsp), GFP_KERNEL); + if (!data_irqsp) { + ret = -ENOMEM; + goto free_ctrl_sp; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + syncpt = &data_irqsp->syncpt; + syncpt->sp = host1x_syncpt_alloc(host1x, HOST1X_SYNCPT_CLIENT_MANAGED, + "pcie-ep-vnet-data"); + if (IS_ERR_OR_NULL(syncpt->sp)) { + ret = -ENOMEM; + pr_err("Failed to reserve comm notify syncpt\n"); + goto free_ctrl_sp; + } + + syncpt->id = host1x_syncpt_id(syncpt->sp); + data_irqsp->dev = fdev; + INIT_WORK(&syncpt->work, tvnet_ep_data_irqsp_work); + tvnet->data_irqsp = data_irqsp; + + syncpt->threshold = host1x_syncpt_read(syncpt->sp); + + /* enable syncpt notifications handling from peer.*/ + mutex_init(&syncpt->lock); + syncpt->notifier = tvnet_ep_data_irqsp_callback; + syncpt->notifier_data = (void *)data_irqsp; + syncpt->host1x_cb_set = true; + syncpt->fence_release = false; + + ret = allocate_fence(syncpt); + if (ret != 0) { + pr_err("allocate_fence failed with: %d\n", ret); + goto free_ctrl_sp; + } + + syncpt_addr = get_syncpt_shim_offset(ctrl_irqsp->syncpt.id); + ctrl_irqsp->syncpt.phy_addr = syncpt_addr; + ctrl_irqsp->syncpt.size = PAGE_SIZE; +#else + data_irqsp->is = + nvhost_interrupt_syncpt_get(cdev->of_node, + tvnet_ep_data_irqsp_callback, + data_irqsp); + if (IS_ERR(data_irqsp->is)) { + ret = PTR_ERR(data_irqsp->is); + dev_err(fdev, "failed to get data syncpt irq: %d\n", ret); + goto free_ctrl_sp; + } + + data_irqsp->dev = fdev; + INIT_WORK(&data_irqsp->reprime_work, tvnet_ep_data_irqsp_reprime_work); + tvnet->data_irqsp = data_irqsp; + + syncpt_addr = nvhost_interrupt_syncpt_get_syncpt_addr(ctrl_irqsp->is); +#endif + ret = iommu_map(domain, amap->iova, syncpt_addr, PAGE_SIZE, + IOMMU_CACHE | IOMMU_READ | IOMMU_WRITE); + if (ret < 0) { + dev_err(fdev, "%s: iommu_map of ctrlsp mem failed: %d\n", + __func__, ret); + goto free_data_sp; + } + + irq = &tvnet->bar_md->irq_ctrl; + irq->irq_addr = PAGE_SIZE; + irq->irq_type = IRQ_SIMPLE; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + syncpt_addr = get_syncpt_shim_offset(data_irqsp->syncpt.id); + ctrl_irqsp->syncpt.phy_addr = syncpt_addr; + ctrl_irqsp->syncpt.size = PAGE_SIZE; +#else + syncpt_addr = nvhost_interrupt_syncpt_get_syncpt_addr(data_irqsp->is); +#endif + ret = iommu_map(domain, amap->iova + PAGE_SIZE, syncpt_addr, PAGE_SIZE, + IOMMU_CACHE | IOMMU_READ | IOMMU_WRITE); + if (ret < 0) { + dev_err(fdev, "%s: iommu_map of datasp mem failed: %d\n", + __func__, ret); + goto free_ctrl_ivoa; + } + + irq = &tvnet->bar_md->irq_data; + irq->irq_addr = 2 * PAGE_SIZE; + irq->irq_type = IRQ_SIMPLE; + + return 0; + +free_ctrl_ivoa: + iommu_unmap(domain, amap->iova, PAGE_SIZE); +free_data_sp: +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + host1x_syncpt_put(data_irqsp->syncpt.sp); +free_ctrl_sp: + host1x_syncpt_put(ctrl_irqsp->syncpt.sp); +#else + nvhost_interrupt_syncpt_free(data_irqsp->is); +free_ctrl_sp: + nvhost_interrupt_syncpt_free(ctrl_irqsp->is); +#endif +fail: + return ret; +} + +static void tvnet_ep_pci_epf_destroy_irqsp(struct pci_epf_tvnet *tvnet) +{ + struct pci_epf *epf = tvnet->epf; + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); + + iommu_unmap(domain, tvnet->bar0_amap[SIMPLE_IRQ].iova + PAGE_SIZE, + PAGE_SIZE); + iommu_unmap(domain, tvnet->bar0_amap[SIMPLE_IRQ].iova, PAGE_SIZE); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + host1x_syncpt_put(tvnet->data_irqsp->syncpt.sp); + host1x_syncpt_put(tvnet->ctrl_irqsp->syncpt.sp); +#else + nvhost_interrupt_syncpt_free(tvnet->data_irqsp->is); + nvhost_interrupt_syncpt_free(tvnet->ctrl_irqsp->is); +#endif +} + +static int tvnet_ep_alloc_single_page_bar0_mem(struct pci_epf *epf, + enum bar0_amap_type type) +{ + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); + struct bar0_amap *amap = &tvnet->bar0_amap[type]; + int ret = 0; + + amap->page = alloc_pages(GFP_KERNEL, 1); + if (!amap->page) { + dev_err(tvnet->fdev, "%s: type: %d alloc_pages() failed\n", + __func__, type); + ret = -ENOMEM; + goto fail; + } + + ret = iommu_map(domain, amap->iova, page_to_phys(amap->page), PAGE_SIZE, + IOMMU_CACHE | IOMMU_READ | IOMMU_WRITE); + if (ret < 0) { + dev_err(tvnet->fdev, "%s: type: %d iommu_map(RAM) failed: %d\n", + __func__, type, ret); + goto fail_free_pages; + } + + amap->virt = vmap(&amap->page, 1, VM_MAP, PAGE_KERNEL); + if (!amap->virt) { + dev_err(tvnet->fdev, "%s: type: %d vmap() failed\n", + __func__, type); + ret = -ENOMEM; + goto fail_unmap_iova; + } + + return 0; + +fail_unmap_iova: + iommu_unmap(domain, amap->iova, PAGE_SIZE); +fail_free_pages: + __free_pages(amap->page, 1); +fail: + return ret; +} + +static void tvnet_ep_free_single_page_bar0_mem(struct pci_epf *epf, + enum bar0_amap_type type) +{ + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); + struct bar0_amap *amap = &tvnet->bar0_amap[type]; + + vunmap(amap->virt); + iommu_unmap(domain, amap->iova, PAGE_SIZE); + __free_pages(amap->page, 1); +} + +static int tvnet_ep_alloc_multi_page_bar0_mem(struct pci_epf *epf, + enum bar0_amap_type type) +{ + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); + struct bar0_amap *amap = &tvnet->bar0_amap[type]; + struct page **map; + int ret = 0, page_count, order, i; + + page_count = amap->size >> PAGE_SHIFT; + order = get_order(amap->size); + + map = kmalloc(sizeof(struct page *) << order, GFP_KERNEL); + if (!map) + return -ENOMEM; + + amap->page = alloc_pages(GFP_KERNEL, order); + if (!amap->page) { + dev_err(tvnet->fdev, "%s: alloc_pages() failed\n", __func__); + ret = -ENOMEM; + goto fail; + } + + split_page(amap->page, order); + + order = 1 << order; + map[0] = amap->page; + for (i = 1; i < page_count; i++) + map[i] = amap->page + i; + for (; i < order; i++) + __free_page(amap->page + i); + + amap->virt = vmap(map, page_count, VM_MAP, PAGE_KERNEL); + if (!amap->virt) { + dev_err(tvnet->fdev, "%s: vmap() failed\n", __func__); + ret = -ENOMEM; + goto fail_free_pages; + } + + ret = iommu_map(domain, amap->iova, page_to_phys(amap->page), + amap->size, IOMMU_CACHE | IOMMU_READ | IOMMU_WRITE); + if (ret < 0) { + dev_err(tvnet->fdev, "%s: iommu_map(RAM) failed: %d\n", + __func__, ret); + goto fail_vunmap; + } + + kfree(map); + return 0; + +fail_vunmap: + vunmap(amap->virt); +fail_free_pages: + __free_pages(amap->page, page_count); +fail: + kfree(map); + + return ret; +} + +static void tvnet_ep_free_multi_page_bar0_mem(struct pci_epf *epf, + enum bar0_amap_type type) +{ + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); + struct bar0_amap *amap = &tvnet->bar0_amap[type]; + int page_count = amap->size >> PAGE_SHIFT; + + iommu_unmap(domain, amap->iova, page_count); + vfree(amap->virt); +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) +static int tvnet_get_host1x_dev(struct pci_epf *epf) +{ + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; + struct platform_device *host1x_pdev = NULL; + struct device_node *np = NULL; + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + + if (WARN_ON(!tvnet || !cdev->of_node)) + return -EINVAL; + + np = of_parse_phandle(cdev->of_node, "nvidia,host1x", 0); + if (!np) { + dev_err(tvnet->fdev, "Failed to find host1x, syncpt support disabled"); + return -ENODATA; + } + + host1x_pdev = of_find_device_by_node(np); + if (!host1x_pdev) { + dev_err(tvnet->fdev, "host1x device not available"); + return -EPROBE_DEFER; + } + + tvnet->host1x_pdev = host1x_pdev; + + return 0; +} +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) +static int tvnet_ep_pci_epf_core_init(struct pci_epf *epf) +{ + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + struct pci_epc *epc = epf->epc; + struct pci_epf_header *header = epf->header; + struct device *fdev = &epf->dev; + struct pci_epf_bar *epf_bar = &epf->bar[BAR_0]; + int ret; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + if (atomic_read(&tvnet->core_initialized)) { + pr_err("Received CORE_INIT callback again\n"); + return -EINVAL; + } +#endif + ret = lpci_epc_write_header(epc, epf->func_no, header); + if (ret) { + dev_err(fdev, "pci_epc_write_header() failed: %d\n", ret); + return ret; + } + + /* TODO Update it to 64-bit prefetch type */ + epf_bar->phys_addr = tvnet->bar0_iova; + epf_bar->addr = tvnet->bar_md; + epf_bar->size = BAR0_SIZE; + epf_bar->barno = BAR_0; + epf_bar->flags |= PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_TYPE_32; + + ret = lpci_epc_set_bar(epc, epf->func_no, epf_bar); + if (ret) { + dev_err(fdev, "pci_epc_set_bar() failed: %d\n", ret); + return ret; + } + + ret = lpci_epc_set_msi(epc, epf->func_no, epf->msi_interrupts); + if (ret) { + dev_err(fdev, "pci_epc_set_msi() failed: %d\n", ret); + return ret; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + atomic_set(&tvnet->core_initialized, 1); + +#endif + return 0; +} +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0) && \ + LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) +static int tvnet_ep_pci_epf_notifier(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct pci_epf *epf = container_of(nb, struct pci_epf, nb); + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + int ret; + + switch (val) { + case CORE_INIT: + ret = tvnet_ep_pci_epf_core_init(epf); + if (ret) + return NOTIFY_BAD; + break; + + case LINK_UP: +#if ENABLE_DMA + tvnet_ep_setup_dma(tvnet); +#endif + + /* + * If host goes through a suspend resume, it recycles EP2H + * empty buffer. Clear any pending EP2H full buffer by setting + * "wr_cnt = rd_cnt". + */ + tvnet_ivc_set_wr(&tvnet->ep2h_full, + tvnet_ivc_get_rd_cnt(&tvnet->ep2h_full)); + + tvnet->pcie_link_status = true; + break; + + default: + dev_err(&epf->dev, "Invalid EPF test notifier event\n"); + return NOTIFY_BAD; + } + + return NOTIFY_OK; +} +#else +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) +static int tvnet_ep_pci_epf_linkup(struct pci_epf *epf) +#else +static void tvnet_ep_pci_epf_linkup(struct pci_epf *epf) +#endif +{ + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + +#if ENABLE_DMA + tvnet_ep_setup_dma(tvnet); +#endif + + /* + * If host goes through a suspend resume, it recycles EP2H empty buffer. + * Clear any pending EP2H full buffer by setting "wr_cnt = rd_cnt". + */ + tvnet_ivc_set_wr(&tvnet->ep2h_full, + tvnet_ivc_get_rd_cnt(&tvnet->ep2h_full)); + + tvnet->pcie_link_status = true; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + return 0; +#endif +} +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) +static int tvnet_ep_pci_epf_core_deinit(struct pci_epf *epf) +{ + struct pci_epf_bar *epf_bar = &epf->bar[BAR_0]; + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + if (!tvnet) + return -EINVAL; + + if (atomic_read(&tvnet->core_initialized)) { + lpci_epc_clear_bar(epf->epc, epf->func_no, epf_bar); + /* no api to clear epf header.*/ + atomic_set(&tvnet->core_initialized, 0); + } + + return 0; +} + +static const struct pci_epc_event_ops tvnet_event_ops = { + .core_init = tvnet_ep_pci_epf_core_init, + .core_deinit = tvnet_ep_pci_epf_core_deinit, + .link_up = tvnet_ep_pci_epf_linkup, +}; + +enum iommu_dma_cookie_type { + IOMMU_DMA_IOVA_COOKIE, + IOMMU_DMA_MSI_COOKIE, +}; + +struct iommu_dma_cookie { + enum iommu_dma_cookie_type type; + union { + /* Full allocator for IOMMU_DMA_IOVA_COOKIE */ + struct iova_domain iovad; + /* Trivial linear page allocator for IOMMU_DMA_MSI_COOKIE */ + dma_addr_t msi_iova; + }; + struct list_head msi_page_list; + + /* Domain for flush queue callback; NULL if flush queue not in use */ + struct iommu_domain *fq_domain; +}; +#endif + +static int tvnet_ep_pci_epf_bind(struct pci_epf *epf) +{ + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); + struct pci_epc *epc = epf->epc; +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 15, 0)) + struct pci_epf_header *header = epf->header; +#endif + struct device *fdev = &epf->dev; + struct device *cdev = epc->dev.parent; + struct iommu_domain *domain = iommu_get_domain_for_dev(cdev); + struct platform_device *pdev = of_find_device_by_node(cdev->of_node); + struct ep_ring_buf *ep_ring_buf = &tvnet->ep_ring_buf; + struct host_ring_buf *host_ring_buf = &tvnet->host_ring_buf; + struct net_device *ndev; + struct bar_md *bar_md; + struct resource *res; + struct bar0_amap *amap; + struct tvnet_dma_desc *dma_desc; + int ret, size, bitmap_size; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + unsigned long shift; +#endif + if (!domain) { + dev_err(fdev, "IOMMU domain not found\n"); + ret = -ENXIO; + goto fail; + } + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 15, 0)) + ret = pci_epc_write_header(epc, header); + if (ret < 0) { + dev_err(fdev, "pci_epc_write_header() failed: %d\n", ret); + goto fail; + } +#endif + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "atu_dma"); + if (!res) { + dev_err(fdev, "missing atu_dma resource in DT\n"); + ret = PTR_ERR(res); + goto fail; + } + + tvnet->dma_base = devm_ioremap(fdev, res->start + DMA_OFFSET, + resource_size(res) - DMA_OFFSET); + if (IS_ERR(tvnet->dma_base)) { + ret = PTR_ERR(tvnet->dma_base); + dev_err(fdev, "dma region map failed: %d\n", ret); + goto fail; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + tvnet->iovad = (struct iova_domain *)&domain->iova_cookie->iovad; + + shift = iova_shift(tvnet->iovad); + + tvnet->iova = alloc_iova(tvnet->iovad, BAR0_SIZE >> shift, + cdev->coherent_dma_mask >> shift, true); + if (!tvnet->iova) { + dev_err(fdev, "iova region map failed\n"); + goto fail; + } + + tvnet->bar0_iova = iova_dma_addr(tvnet->iovad, tvnet->iova); + tvnet_get_host1x_dev(epf); +#else + tvnet->bar0_iova = iommu_dma_alloc_iova(cdev, BAR0_SIZE, + cdev->coherent_dma_mask); + if (!tvnet->bar0_iova) { + dev_err(fdev, "iommu_dma_alloc_iova() failed\n"); + ret = -ENOMEM; + goto fail; + } +#endif + pr_debug("BAR0 IOVA: 0x%llx\n", tvnet->bar0_iova); + + /* BAR0 metadata memory allocation */ + tvnet->bar0_amap[META_DATA].iova = tvnet->bar0_iova; + tvnet->bar0_amap[META_DATA].size = PAGE_SIZE; + ret = tvnet_ep_alloc_single_page_bar0_mem(epf, META_DATA); + if (ret < 0) { + dev_err(fdev, "BAR0 metadata alloc failed: %d\n", ret); + goto free_iova; + } + + tvnet->bar_md = (struct bar_md *)tvnet->bar0_amap[META_DATA].virt; + bar_md = tvnet->bar_md; + + /* BAR0 SIMPLE_IRQ setup: two interrupts required two pages */ + amap = &tvnet->bar0_amap[SIMPLE_IRQ]; + amap->iova = tvnet->bar0_amap[META_DATA].iova + + tvnet->bar0_amap[META_DATA].size; + amap->size = 2 * PAGE_SIZE; + + ret = tvnet_ep_pci_epf_setup_irqsp(tvnet); + if (ret < 0) { + dev_err(fdev, "irqsp setup failed: %d\n", ret); + goto free_bar0_md; + } + + /* BAR0 EP memory allocation */ + amap = &tvnet->bar0_amap[EP_MEM]; + amap->iova = tvnet->bar0_amap[SIMPLE_IRQ].iova + + tvnet->bar0_amap[SIMPLE_IRQ].size; + size = sizeof(struct ep_own_cnt) + (RING_COUNT * + (sizeof(struct ctrl_msg) + 2 * sizeof(struct data_msg))); + amap->size = PAGE_ALIGN(size); + ret = tvnet_ep_alloc_multi_page_bar0_mem(epf, EP_MEM); + if (ret < 0) { + dev_err(fdev, "BAR0 EP mem alloc failed: %d\n", ret); + goto free_irqsp; + } + + ep_ring_buf->ep_cnt = (struct ep_own_cnt *)amap->virt; + ep_ring_buf->ep2h_ctrl_msgs = (struct ctrl_msg *) + (ep_ring_buf->ep_cnt + 1); + ep_ring_buf->ep2h_full_msgs = (struct data_msg *) + (ep_ring_buf->ep2h_ctrl_msgs + RING_COUNT); + ep_ring_buf->h2ep_empty_msgs = (struct data_msg *) + (ep_ring_buf->ep2h_full_msgs + RING_COUNT); + /* Clear EP counters */ + memset(ep_ring_buf->ep_cnt, 0, sizeof(struct ep_own_cnt)); + + /* BAR0 host memory allocation */ + amap = &tvnet->bar0_amap[HOST_MEM]; + amap->iova = tvnet->bar0_amap[EP_MEM].iova + + tvnet->bar0_amap[EP_MEM].size; + size = (sizeof(struct host_own_cnt)) + (RING_COUNT * + (sizeof(struct ctrl_msg) + 2 * sizeof(struct data_msg))); + amap->size = PAGE_ALIGN(size); + ret = tvnet_ep_alloc_multi_page_bar0_mem(epf, HOST_MEM); + if (ret < 0) { + dev_err(fdev, "BAR0 host mem alloc failed: %d\n", ret); + goto free_ep_mem; + } + + host_ring_buf->host_cnt = (struct host_own_cnt *)amap->virt; + host_ring_buf->h2ep_ctrl_msgs = (struct ctrl_msg *) + (host_ring_buf->host_cnt + 1); + host_ring_buf->ep2h_empty_msgs = (struct data_msg *) + (host_ring_buf->h2ep_ctrl_msgs + RING_COUNT); + host_ring_buf->h2ep_full_msgs = (struct data_msg *) + (host_ring_buf->ep2h_empty_msgs + RING_COUNT); + /* Clear host counters */ + memset(host_ring_buf->host_cnt, 0, sizeof(struct host_own_cnt)); + + /* + * Allocate local memory for DMA read link list elements. + * This is exposed through BAR0 to initiate DMA read from host. + */ + amap = &tvnet->bar0_amap[HOST_DMA]; + amap->iova = tvnet->bar0_amap[HOST_MEM].iova + + tvnet->bar0_amap[HOST_MEM].size; + size = ((DMA_DESC_COUNT + 1) * sizeof(struct tvnet_dma_desc)); + amap->size = PAGE_ALIGN(size); + ret = tvnet_ep_alloc_multi_page_bar0_mem(epf, HOST_DMA); + if (ret < 0) { + dev_err(fdev, "BAR0 host dma mem alloc failed: %d\n", ret); + goto free_host_mem; + } + + /* Set link list pointer to create a dma desc ring */ + memset(amap->virt, 0, amap->size); + dma_desc = (struct tvnet_dma_desc *)amap->virt; + dma_desc[DMA_DESC_COUNT].sar_low = (amap->iova & 0xffffffff); + dma_desc[DMA_DESC_COUNT].sar_high = ((amap->iova >> 32) & 0xffffffff); + dma_desc[DMA_DESC_COUNT].ctrl_reg.ctrl_e.llp = 1; + + /* Update BAR metadata region with offsets */ + /* EP owned memory */ + bar_md->ep_own_cnt_offset = tvnet->bar0_amap[META_DATA].size + + tvnet->bar0_amap[SIMPLE_IRQ].size; + bar_md->ctrl_md.ep2h_offset = bar_md->ep_own_cnt_offset + + sizeof(struct ep_own_cnt); + bar_md->ctrl_md.ep2h_size = RING_COUNT; + bar_md->ep2h_md.ep2h_offset = bar_md->ctrl_md.ep2h_offset + + (RING_COUNT * sizeof(struct ctrl_msg)); + bar_md->ep2h_md.ep2h_size = RING_COUNT; + bar_md->h2ep_md.ep2h_offset = bar_md->ep2h_md.ep2h_offset + + (RING_COUNT * sizeof(struct data_msg)); + bar_md->h2ep_md.ep2h_size = RING_COUNT; + + /* Host owned memory */ + bar_md->host_own_cnt_offset = bar_md->ep_own_cnt_offset + + tvnet->bar0_amap[EP_MEM].size; + bar_md->ctrl_md.h2ep_offset = bar_md->host_own_cnt_offset + + sizeof(struct host_own_cnt); + bar_md->ctrl_md.h2ep_size = RING_COUNT; + bar_md->ep2h_md.h2ep_offset = bar_md->ctrl_md.h2ep_offset + + (RING_COUNT * sizeof(struct ctrl_msg)); + bar_md->ep2h_md.h2ep_size = RING_COUNT; + bar_md->h2ep_md.h2ep_offset = bar_md->ep2h_md.h2ep_offset + + (RING_COUNT * sizeof(struct data_msg)); + bar_md->h2ep_md.h2ep_size = RING_COUNT; + + tvnet->h2ep_ctrl.rd = &ep_ring_buf->ep_cnt->h2ep_ctrl_rd_cnt; + tvnet->h2ep_ctrl.wr = &host_ring_buf->host_cnt->h2ep_ctrl_wr_cnt; + tvnet->ep2h_ctrl.rd = &host_ring_buf->host_cnt->ep2h_ctrl_rd_cnt; + tvnet->ep2h_ctrl.wr = &ep_ring_buf->ep_cnt->ep2h_ctrl_wr_cnt; + tvnet->h2ep_empty.rd = &host_ring_buf->host_cnt->h2ep_empty_rd_cnt; + tvnet->h2ep_empty.wr = &ep_ring_buf->ep_cnt->h2ep_empty_wr_cnt; + tvnet->h2ep_full.rd = &ep_ring_buf->ep_cnt->h2ep_full_rd_cnt; + tvnet->h2ep_full.wr = &host_ring_buf->host_cnt->h2ep_full_wr_cnt; + tvnet->ep2h_empty.rd = &ep_ring_buf->ep_cnt->ep2h_empty_rd_cnt; + tvnet->ep2h_empty.wr = &host_ring_buf->host_cnt->ep2h_empty_wr_cnt; + tvnet->ep2h_full.rd = &host_ring_buf->host_cnt->ep2h_full_rd_cnt; + tvnet->ep2h_full.wr = &ep_ring_buf->ep_cnt->ep2h_full_wr_cnt; + + /* RAM region for use by host when programming EP DMA controller */ + bar_md->host_dma_offset = bar_md->host_own_cnt_offset + + tvnet->bar0_amap[HOST_MEM].size; + bar_md->host_dma_size = tvnet->bar0_amap[HOST_DMA].size; + + /* EP Rx pkt IOVA range */ + tvnet->rx_buf_iova = tvnet->bar0_amap[HOST_DMA].iova + + tvnet->bar0_amap[HOST_DMA].size; + bar_md->bar0_base_phy = tvnet->bar0_iova; + bar_md->ep_rx_pkt_offset = bar_md->host_dma_offset + + tvnet->bar0_amap[HOST_DMA].size; + bar_md->ep_rx_pkt_size = BAR0_SIZE - + tvnet->bar0_amap[META_DATA].size - + tvnet->bar0_amap[SIMPLE_IRQ].size - + tvnet->bar0_amap[EP_MEM].size - + tvnet->bar0_amap[HOST_MEM].size - + tvnet->bar0_amap[HOST_DMA].size; + + /* Create bitmap for allocating RX buffers */ + tvnet->rx_num_pages = (bar_md->ep_rx_pkt_size >> PAGE_SHIFT); + bitmap_size = BITS_TO_LONGS(tvnet->rx_num_pages) * sizeof(long); + tvnet->rx_buf_bitmap = devm_kzalloc(fdev, bitmap_size, GFP_KERNEL); + if (!tvnet->rx_buf_bitmap) { + dev_err(fdev, "rx_bitmap mem alloc failed\n"); + ret = -ENOMEM; + goto free_host_dma; + } + + /* Allocate PCIe memory for RP's dst address during xmit */ + tvnet->tx_dst_va = pci_epc_mem_alloc_addr(epc, + &tvnet->tx_dst_pci_addr, + SZ_64K); + if (!tvnet->tx_dst_va) { + dev_err(fdev, "failed to allocate dst PCIe address\n"); + ret = -ENOMEM; + goto free_host_dma; + } + + /* Register network device */ + ndev = alloc_etherdev(0); + if (!ndev) { + dev_err(fdev, "alloc_etherdev() failed\n"); + ret = -ENOMEM; + goto free_pci_mem; + } + + eth_hw_addr_random(ndev); + tvnet->ndev = ndev; + SET_NETDEV_DEV(ndev, fdev); + ndev->netdev_ops = &tvnet_netdev_ops; + netif_napi_add(ndev, &tvnet->napi, tvnet_ep_poll, TVNET_NAPI_WEIGHT); + + ndev->mtu = TVNET_DEFAULT_MTU; + + ret = register_netdev(ndev); + if (ret < 0) { + dev_err(fdev, "register_netdev() failed: %d\n", ret); + goto fail_free_netdev; + } + netif_carrier_off(ndev); + + tvnet->rx_link_state = DIR_LINK_STATE_DOWN; + tvnet->tx_link_state = DIR_LINK_STATE_DOWN; + tvnet->os_link_state = OS_LINK_STATE_DOWN; + mutex_init(&tvnet->link_state_lock); + init_waitqueue_head(&tvnet->link_state_wq); + + INIT_LIST_HEAD(&tvnet->h2ep_empty_list); + spin_lock_init(&tvnet->h2ep_empty_lock); + + INIT_WORK(&tvnet->raise_irq_work, tvnet_ep_raise_irq_work_function); + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 15, 0)) + /* TODO Update it to 64-bit prefetch type */ + ret = pci_epc_set_bar(epc, BAR_0, tvnet->bar0_iova, BAR0_SIZE, + PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_TYPE_32); + if (ret < 0) { + dev_err(fdev, "pci_epc_set_bar() failed: %d\n", ret); + goto fail_unreg_netdev; + } +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 15, 0)) + ret = pci_epc_set_msi(epc, epf->msi_interrupts); + if (ret) { + dev_err(fdev, "pci_epc_set_msi() failed: %d\n", ret); + goto fail_clear_bar; + } +#endif + + /* Allocate local memory for DMA write link list elements */ + size = ((DMA_DESC_COUNT + 1) * sizeof(struct tvnet_dma_desc)); + tvnet->ep_dma_virt = dma_alloc_coherent(cdev, size, + &tvnet->ep_dma_iova, + GFP_KERNEL); + if (!tvnet->ep_dma_virt) { + dev_err(fdev, "%s ep dma mem alloc failed\n", __func__); + ret = -ENOMEM; + goto fail_clear_bar; + } + + /* Set link list pointer to create a dma desc ring */ + memset(tvnet->ep_dma_virt, 0, size); + dma_desc = (struct tvnet_dma_desc *)tvnet->ep_dma_virt; + dma_desc[DMA_DESC_COUNT].sar_low = (tvnet->ep_dma_iova & 0xffffffff); + dma_desc[DMA_DESC_COUNT].sar_high = ((tvnet->ep_dma_iova >> 32) & + 0xffffffff); + dma_desc[DMA_DESC_COUNT].ctrl_reg.ctrl_e.llp = 1; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)) + nvhost_interrupt_syncpt_prime(tvnet->ctrl_irqsp->is); + nvhost_interrupt_syncpt_prime(tvnet->data_irqsp->is); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + epf->nb.notifier_call = tvnet_ep_pci_epf_notifier; + pci_epc_register_notifier(epc, &epf->nb); +#endif +#endif + + return 0; + +fail_clear_bar: +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 15, 0)) + pci_epc_clear_bar(epc, BAR_0); +fail_unreg_netdev: +#endif + unregister_netdev(ndev); +fail_free_netdev: + netif_napi_del(&tvnet->napi); + free_netdev(ndev); +free_pci_mem: + pci_epc_mem_free_addr(epc, tvnet->tx_dst_pci_addr, tvnet->tx_dst_va, + SZ_64K); +free_host_dma: + tvnet_ep_free_multi_page_bar0_mem(epf, HOST_DMA); +free_host_mem: + tvnet_ep_free_multi_page_bar0_mem(epf, HOST_MEM); +free_ep_mem: + tvnet_ep_free_multi_page_bar0_mem(epf, EP_MEM); +free_irqsp: + tvnet_ep_pci_epf_destroy_irqsp(tvnet); +free_bar0_md: + tvnet_ep_free_single_page_bar0_mem(epf, META_DATA); +free_iova: +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + __free_iova(tvnet->iovad, tvnet->iova); +#else + iommu_dma_free_iova(cdev, tvnet->bar0_iova, BAR0_SIZE); +#endif +fail: + return ret; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) +static void free_fence_resource(struct syncpt_t *syncpt) +{ + int ret = 0; + mutex_lock(&syncpt->lock); + syncpt->fence_release = true; + if (syncpt->fence) { + ret = dma_fence_remove_callback(syncpt->fence, + &syncpt->fence_cb); + if (ret) { + /* + * If dma_fence_remove_callback() returns true + * means callback is removed successfully. + * Cancel the fence to drop the refcount. + */ + host1x_fence_cancel(syncpt->fence); + } + dma_fence_put(syncpt->fence); + syncpt->fence = NULL; + } + mutex_unlock(&syncpt->lock); + mutex_destroy(&syncpt->lock); +} +#endif + +static void tvnet_ep_pci_epf_unbind(struct pci_epf *epf) +{ + struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + struct pci_epf_bar *epf_bar = &epf->bar[BAR_0]; +#endif + struct pci_epc *epc = epf->epc; + struct device *cdev = epc->dev.parent; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + struct syncpt_t *syncpt = NULL; + + syncpt = &tvnet->ctrl_irqsp->syncpt; + free_fence_resource(syncpt); + cancel_work_sync(&syncpt->work); + + syncpt = &tvnet->data_irqsp->syncpt; + free_fence_resource(syncpt); + cancel_work_sync(&syncpt->work); +#endif + pci_epc_stop(epc); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 15, 0)) + lpci_epc_clear_bar(epc, epf->func_no, epf_bar); +#else + pci_epc_clear_bar(epc, BAR_0); +#endif + dma_free_coherent(cdev, + ((RING_COUNT + 1) * sizeof(struct tvnet_dma_desc)), + tvnet->ep_dma_virt, tvnet->ep_dma_iova); + unregister_netdev(tvnet->ndev); + netif_napi_del(&tvnet->napi); + free_netdev(tvnet->ndev); + pci_epc_mem_free_addr(epc, tvnet->tx_dst_pci_addr, tvnet->tx_dst_va, + SZ_64K); + tvnet_ep_free_multi_page_bar0_mem(epf, HOST_DMA); + tvnet_ep_free_multi_page_bar0_mem(epf, HOST_MEM); + tvnet_ep_free_multi_page_bar0_mem(epf, EP_MEM); + tvnet_ep_pci_epf_destroy_irqsp(tvnet); + tvnet_ep_free_single_page_bar0_mem(epf, META_DATA); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + __free_iova(tvnet->iovad, tvnet->iova); + tvnet->bar0_iova = 0x0; +#else + iommu_dma_free_iova(cdev, tvnet->bar0_iova, BAR0_SIZE); +#endif +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) +static const struct pci_epf_device_id tvnet_ep_epf_tvnet_ids[] = { + { .name = "pci_epf_tvnet", }, + { }, +}; +#endif + +static int tvnet_ep_epf_tvnet_probe(struct pci_epf *epf) +{ + struct device *fdev = &epf->dev; + struct pci_epf_tvnet *tvnet; + + tvnet = devm_kzalloc(fdev, sizeof(*tvnet), GFP_KERNEL); + if (!tvnet) + return -ENOMEM; + + epf_set_drvdata(epf, tvnet); + tvnet->fdev = fdev; + tvnet->epf = epf; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(5, 14, 0)) + epf->event_ops = &tvnet_event_ops; +#endif + tvnet->header.vendorid = PCI_VENDOR_ID_NVIDIA; + tvnet->header.deviceid = PCI_DEVICE_ID_NVIDIA_JETSON_AGX_NETWORK; + tvnet->header.revid = 0x0; + tvnet->header.baseclass_code = PCI_BASE_CLASS_NETWORK; + tvnet->header.subclass_code = (PCI_CLASS_NETWORK_OTHER & 0xff); + tvnet->header.subsys_vendor_id = PCI_VENDOR_ID_NVIDIA; + tvnet->header.subsys_id = 0x0; + tvnet->header.interrupt_pin = PCI_INTERRUPT_INTA; + epf->header = &tvnet->header; + + return 0; +} + +static struct pci_epf_ops tvnet_ep_ops = { + .bind = tvnet_ep_pci_epf_bind, + .unbind = tvnet_ep_pci_epf_unbind, +#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 15, 0)) + .linkup = tvnet_ep_pci_epf_linkup, +#endif +}; + +static struct pci_epf_driver tvnet_driver = { + .driver.name = "pci_epf_tvnet", + .probe = tvnet_ep_epf_tvnet_probe, + .id_table = tvnet_ep_epf_tvnet_ids, + .ops = &tvnet_ep_ops, + .owner = THIS_MODULE, +}; + +static int __init tvnet_ep_pci_epf_init(void) +{ + int ret; + + ret = pci_epf_register_driver(&tvnet_driver); + if (ret < 0) { + pr_err("Failed to register EPF Tegra vnet driver: %d\n", ret); + return ret; + } + + return 0; +} +module_init(tvnet_ep_pci_epf_init); + +static void __exit tvnet_ep_pci_epf_exit(void) +{ + pci_epf_unregister_driver(&tvnet_driver); +} +module_exit(tvnet_ep_pci_epf_exit); + +MODULE_DESCRIPTION("PCI EPF TEGRA VIRTUAL NETWORK DRIVER"); +MODULE_AUTHOR("Manikanta Maddireddy "); +MODULE_LICENSE("GPL v2");