1558 lines
45 KiB
C
1558 lines
45 KiB
C
/*
|
|
* Copyright (c) 2019-2020, NVIDIA CORPORATION. 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.
|
|
*/
|
|
|
|
#include <linux/dma-iommu.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/nvhost.h>
|
|
#include <linux/nvhost_interrupt_syncpt.h>
|
|
#include <linux/nvhost_t194.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pci-epc.h>
|
|
#include <linux/pci-epf.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/tegra_vnet.h>
|
|
|
|
#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;
|
|
};
|
|
|
|
struct irqsp_data {
|
|
struct nvhost_interrupt_syncpt *is;
|
|
struct work_struct reprime_work;
|
|
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 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;
|
|
};
|
|
|
|
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;
|
|
u32 idx;
|
|
|
|
if (tvnet_ivc_full(&tvnet->ep2h_ctrl)) {
|
|
/* Raise an interrupt to let host process EP2H ring */
|
|
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0);
|
|
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);
|
|
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0);
|
|
|
|
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;
|
|
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);
|
|
|
|
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0);
|
|
}
|
|
}
|
|
|
|
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;
|
|
#endif
|
|
dma_addr_t src_iova;
|
|
u32 rd_idx, wr_idx;
|
|
u64 dst_masked, dst_off, dst_iova;
|
|
int ret, 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)) {
|
|
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0);
|
|
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)) {
|
|
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 1);
|
|
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));
|
|
|
|
ret = pci_epc_map_addr(epc, tvnet->tx_dst_pci_addr, dst_masked,
|
|
dst_len);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Advance read count after all failure cases completed, to avoid
|
|
* dangling buffer at host.
|
|
*/
|
|
tvnet_ivc_advance_rd(&tvnet->ep2h_empty);
|
|
/* Raise an interrupt to let host populate EP2H_EMPTY_BUF ring */
|
|
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0);
|
|
|
|
#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--;
|
|
pci_epc_unmap_addr(epc, tvnet->tx_dst_pci_addr);
|
|
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);
|
|
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 1);
|
|
|
|
/* Free temp src and skb */
|
|
pci_epc_unmap_addr(epc, tvnet->tx_dst_pci_addr);
|
|
dma_unmap_single(cdev, src_iova, len, DMA_TO_DEVICE);
|
|
dev_kfree_skb_any(skb);
|
|
|
|
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 H2EP network queue is stopped due to lack of H2EP_FULL
|
|
* queue, raising ctrl irq will help.
|
|
*/
|
|
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSIX, 0);
|
|
|
|
#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
|
|
|
|
static void tvnet_ep_ctrl_irqsp_reprime_work(struct work_struct *work)
|
|
{
|
|
struct irqsp_data *data_irqsp =
|
|
container_of(work, struct irqsp_data, reprime_work);
|
|
|
|
nvhost_interrupt_syncpt_prime(data_irqsp->is);
|
|
}
|
|
|
|
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);
|
|
schedule_work(&data_irqsp->reprime_work);
|
|
}
|
|
|
|
static void tvnet_ep_data_irqsp_reprime_work(struct work_struct *work)
|
|
{
|
|
struct irqsp_data *data_irqsp =
|
|
container_of(work, struct irqsp_data, reprime_work);
|
|
|
|
nvhost_interrupt_syncpt_prime(data_irqsp->is);
|
|
}
|
|
|
|
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);
|
|
else
|
|
schedule_work(&data_irqsp->reprime_work);
|
|
}
|
|
|
|
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);
|
|
schedule_work(&data_irqsp->reprime_work);
|
|
}
|
|
|
|
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;
|
|
|
|
ctrl_irqsp = devm_kzalloc(fdev, sizeof(*ctrl_irqsp), GFP_KERNEL);
|
|
if (!ctrl_irqsp) {
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
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;
|
|
|
|
data_irqsp = devm_kzalloc(fdev, sizeof(*data_irqsp), GFP_KERNEL);
|
|
if (!data_irqsp) {
|
|
ret = -ENOMEM;
|
|
goto free_ctrl_sp;
|
|
}
|
|
|
|
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);
|
|
ret = iommu_map(domain, amap->iova, syncpt_addr, PAGE_SIZE,
|
|
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;
|
|
|
|
syncpt_addr = nvhost_interrupt_syncpt_get_syncpt_addr(data_irqsp->is);
|
|
ret = iommu_map(domain, amap->iova + PAGE_SIZE, syncpt_addr, PAGE_SIZE,
|
|
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:
|
|
nvhost_interrupt_syncpt_free(data_irqsp->is);
|
|
free_ctrl_sp:
|
|
nvhost_interrupt_syncpt_free(ctrl_irqsp->is);
|
|
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);
|
|
nvhost_interrupt_syncpt_free(tvnet->data_irqsp->is);
|
|
nvhost_interrupt_syncpt_free(tvnet->ctrl_irqsp->is);
|
|
}
|
|
|
|
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_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, page_count);
|
|
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;
|
|
}
|
|
kfree(map);
|
|
|
|
ret = iommu_map(domain, amap->iova, page_to_phys(amap->page),
|
|
amap->size, IOMMU_READ | IOMMU_WRITE);
|
|
if (ret < 0) {
|
|
dev_err(tvnet->fdev, "%s: iommu_map(RAM) failed: %d\n",
|
|
__func__, ret);
|
|
goto fail_vunmap;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
struct pci_epf_header *header = epf->header;
|
|
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 (!domain) {
|
|
dev_err(fdev, "IOMMU domain not found\n");
|
|
ret = -ENXIO;
|
|
goto fail;
|
|
}
|
|
|
|
ret = pci_epc_write_header(epc, header);
|
|
if (ret < 0) {
|
|
dev_err(fdev, "pci_epc_write_header() failed: %d\n", ret);
|
|
goto fail;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
pr_debug("BAR0 IOVA: 0x%08llx\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_wc_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);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
nvhost_interrupt_syncpt_prime(tvnet->ctrl_irqsp->is);
|
|
nvhost_interrupt_syncpt_prime(tvnet->data_irqsp->is);
|
|
|
|
return 0;
|
|
|
|
fail_clear_bar:
|
|
pci_epc_clear_bar(epc, BAR_0);
|
|
fail_unreg_netdev:
|
|
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:
|
|
iommu_dma_free_iova(cdev, tvnet->bar0_iova, BAR0_SIZE);
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static void tvnet_ep_pci_epf_unbind(struct pci_epf *epf)
|
|
{
|
|
struct pci_epf_tvnet *tvnet = epf_get_drvdata(epf);
|
|
struct pci_epc *epc = epf->epc;
|
|
struct device *cdev = epc->dev.parent;
|
|
|
|
cancel_work_sync(&tvnet->ctrl_irqsp->reprime_work);
|
|
cancel_work_sync(&tvnet->data_irqsp->reprime_work);
|
|
pci_epc_stop(epc);
|
|
pci_epc_clear_bar(epc, BAR_0);
|
|
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);
|
|
iommu_dma_free_iova(cdev, tvnet->bar0_iova, BAR0_SIZE);
|
|
}
|
|
|
|
static void tvnet_ep_pci_epf_linkup(struct pci_epf *epf)
|
|
{
|
|
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;
|
|
}
|
|
|
|
static const struct pci_epf_device_id tvnet_ep_epf_tvnet_ids[] = {
|
|
{ .name = "pci_epf_tvnet", },
|
|
{ },
|
|
};
|
|
|
|
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;
|
|
|
|
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,
|
|
.linkup = tvnet_ep_pci_epf_linkup,
|
|
};
|
|
|
|
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 <mmaddireddy@nvidia.com>");
|
|
MODULE_LICENSE("GPL v2");
|