mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
1. Disable the corresponding PCIe EP controller EDMA interrupt. 2. Disable IP checksum to improve performance since the PCIe link is reliable. 3. DMA unmap matches the map size. 4. Adjust the SKB buffer link list handle. Bug 4704944 Signed-off-by: Jason Mei <jianjunm@nvidia.com> Change-Id: I05f76fa60e3533c2dd01e53ed17664d6898fcffd Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3158126 Reviewed-by: svcacv <svcacv@nvidia.com> Reviewed-by: Nagarjuna Kristam <nkristam@nvidia.com> GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com> Reviewed-by: Bibek Basu <bbasu@nvidia.com>
969 lines
27 KiB
C
969 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* SPDX-FileCopyrightText: Copyright (c) 2019-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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#include <linux/aer.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/tegra_vnet.h>
|
|
|
|
struct tvnet_priv {
|
|
struct net_device *ndev;
|
|
struct napi_struct napi;
|
|
struct pci_dev *pdev;
|
|
void __iomem *mmio_base;
|
|
void __iomem *msix_tbl;
|
|
void __iomem *dma_base;
|
|
struct bar_md *bar_md;
|
|
struct ep_ring_buf ep_mem;
|
|
struct host_ring_buf host_mem;
|
|
struct list_head ep2h_empty_list;
|
|
/* To protect ep2h empty list */
|
|
spinlock_t ep2h_empty_lock;
|
|
struct tvnet_dma_desc *dma_desc;
|
|
#if ENABLE_DMA
|
|
struct dma_desc_cnt desc_cnt;
|
|
#endif
|
|
enum dir_link_state tx_link_state;
|
|
enum dir_link_state rx_link_state;
|
|
enum os_link_state os_link_state;
|
|
|
|
/* Flag to track ndo_stop done by suspend */
|
|
bool pm_closed;
|
|
|
|
/* To synchronize network link state machine*/
|
|
struct mutex link_state_lock;
|
|
wait_queue_head_t link_state_wq;
|
|
|
|
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 ENABLE_DMA
|
|
/* Program MSI settings in EP DMA for interrupts from EP DMA */
|
|
static void tvnet_host_write_dma_msix_settings(struct tvnet_priv *tvnet)
|
|
{
|
|
u32 val;
|
|
u16 val16;
|
|
|
|
val = readl(tvnet->msix_tbl + PCI_MSIX_ENTRY_LOWER_ADDR);
|
|
dma_common_wr(tvnet->dma_base, val, DMA_READ_DONE_IMWR_LOW_OFF);
|
|
dma_common_wr(tvnet->dma_base, val, DMA_READ_ABORT_IMWR_LOW_OFF);
|
|
|
|
val = readl(tvnet->msix_tbl + PCI_MSIX_ENTRY_UPPER_ADDR);
|
|
dma_common_wr(tvnet->dma_base, val, DMA_READ_DONE_IMWR_HIGH_OFF);
|
|
dma_common_wr(tvnet->dma_base, val, DMA_READ_ABORT_IMWR_HIGH_OFF);
|
|
|
|
val16 = readw(tvnet->msix_tbl + PCI_MSIX_ENTRY_DATA);
|
|
dma_common_wr16(tvnet->dma_base, val16, DMA_READ_IMWR_DATA_OFF_BASE);
|
|
}
|
|
#endif
|
|
|
|
static void tvnet_host_raise_ep_ctrl_irq(struct tvnet_priv *tvnet)
|
|
{
|
|
struct irq_md *irq = &tvnet->bar_md->irq_ctrl;
|
|
|
|
if (irq->irq_type == IRQ_SIMPLE) {
|
|
/* Can write any value to generate sync point irq */
|
|
writel(0x1, tvnet->mmio_base + irq->irq_addr);
|
|
/* BAR0 mmio address is wc mem, add mb to make sure
|
|
* multiple interrupt writes are not combined.
|
|
*/
|
|
mb();
|
|
} else {
|
|
pr_err("%s: invalid irq type: %d\n", __func__, irq->irq_type);
|
|
}
|
|
}
|
|
|
|
static void tvnet_host_raise_ep_data_irq(struct tvnet_priv *tvnet)
|
|
{
|
|
struct irq_md *irq = &tvnet->bar_md->irq_data;
|
|
|
|
if (irq->irq_type == IRQ_SIMPLE) {
|
|
/* Can write any value to generate sync point irq */
|
|
writel(0x1, tvnet->mmio_base + irq->irq_addr);
|
|
/* BAR0 mmio address is wc mem, add mb to make sure
|
|
* multiple interrupt writes are not combined.
|
|
*/
|
|
mb();
|
|
} else {
|
|
pr_err("%s: invalid irq type: %d\n", __func__, irq->irq_type);
|
|
}
|
|
}
|
|
|
|
static void tvnet_host_read_ctrl_msg(struct tvnet_priv *tvnet,
|
|
struct ctrl_msg *msg)
|
|
{
|
|
struct ep_ring_buf *ep_mem = &tvnet->ep_mem;
|
|
struct ctrl_msg *ctrl_msg = ep_mem->ep2h_ctrl_msgs;
|
|
u32 idx;
|
|
|
|
if (tvnet_ivc_empty(&tvnet->ep2h_ctrl)) {
|
|
pr_debug("%s: EP2H ctrl ring is empty\n", __func__);
|
|
return;
|
|
}
|
|
|
|
idx = tvnet_ivc_get_rd_cnt(&tvnet->ep2h_ctrl) % RING_COUNT;
|
|
memcpy(msg, &ctrl_msg[idx], sizeof(*msg));
|
|
tvnet_ivc_advance_rd(&tvnet->ep2h_ctrl);
|
|
}
|
|
|
|
/* TODO Handle error case */
|
|
static int tvnet_host_write_ctrl_msg(struct tvnet_priv *tvnet,
|
|
struct ctrl_msg *msg)
|
|
{
|
|
struct host_ring_buf *host_mem = &tvnet->host_mem;
|
|
struct ctrl_msg *ctrl_msg = host_mem->h2ep_ctrl_msgs;
|
|
u32 idx;
|
|
|
|
if (tvnet_ivc_full(&tvnet->h2ep_ctrl)) {
|
|
/* Raise an interrupt to let host process EP2H ring */
|
|
tvnet_host_raise_ep_ctrl_irq(tvnet);
|
|
pr_info("%s: EP2H ctrl ring is full\n", __func__);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
idx = tvnet_ivc_get_wr_cnt(&tvnet->h2ep_ctrl) % RING_COUNT;
|
|
memcpy(&ctrl_msg[idx], msg, sizeof(*msg));
|
|
/* BAR0 mmio address is wc mem, add mb to make sure ctrl msg is written
|
|
* before updating counters.
|
|
*/
|
|
mb();
|
|
tvnet_ivc_advance_wr(&tvnet->h2ep_ctrl);
|
|
tvnet_host_raise_ep_ctrl_irq(tvnet);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tvnet_host_alloc_empty_buffers(struct tvnet_priv *tvnet)
|
|
{
|
|
struct net_device *ndev = tvnet->ndev;
|
|
struct host_ring_buf *host_mem = &tvnet->host_mem;
|
|
struct data_msg *ep2h_empty_msg = host_mem->ep2h_empty_msgs;
|
|
struct ep2h_empty_list *ep2h_empty_ptr;
|
|
struct device *d = &tvnet->pdev->dev;
|
|
unsigned long flags;
|
|
|
|
while (!tvnet_ivc_full(&tvnet->ep2h_empty)) {
|
|
struct sk_buff *skb;
|
|
dma_addr_t iova;
|
|
int len = ndev->mtu + ETH_HLEN;
|
|
u32 idx;
|
|
|
|
skb = netdev_alloc_skb(ndev, len);
|
|
if (!skb) {
|
|
pr_err("%s: alloc skb failed\n", __func__);
|
|
break;
|
|
}
|
|
iova = dma_map_single(d, skb->data, len, DMA_FROM_DEVICE);
|
|
if (dma_mapping_error(d, iova)) {
|
|
pr_err("%s: dma map failed\n", __func__);
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
}
|
|
|
|
/* The PCIe link is stable and dependable,
|
|
* so it's not necessary to perform a software checksum.
|
|
*/
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
|
|
ep2h_empty_ptr = kmalloc(sizeof(*ep2h_empty_ptr), GFP_ATOMIC);
|
|
if (!ep2h_empty_ptr) {
|
|
dma_unmap_single(d, iova, len, DMA_FROM_DEVICE);
|
|
dev_kfree_skb_any(skb);
|
|
break;
|
|
}
|
|
ep2h_empty_ptr->skb = skb;
|
|
ep2h_empty_ptr->iova = iova;
|
|
ep2h_empty_ptr->len = len;
|
|
spin_lock_irqsave(&tvnet->ep2h_empty_lock, flags);
|
|
list_add_tail(&ep2h_empty_ptr->list, &tvnet->ep2h_empty_list);
|
|
spin_unlock_irqrestore(&tvnet->ep2h_empty_lock, flags);
|
|
|
|
idx = tvnet_ivc_get_wr_cnt(&tvnet->ep2h_empty) %
|
|
RING_COUNT;
|
|
ep2h_empty_msg[idx].u.empty_buffer.pcie_address = iova;
|
|
ep2h_empty_msg[idx].u.empty_buffer.buffer_len = len;
|
|
/* BAR0 mmio address is wc mem, add mb to make sure that empty
|
|
* buffers are updated before updating counters.
|
|
*/
|
|
mb();
|
|
tvnet_ivc_advance_wr(&tvnet->ep2h_empty);
|
|
|
|
tvnet_host_raise_ep_ctrl_irq(tvnet);
|
|
}
|
|
}
|
|
|
|
static void tvnet_host_free_empty_buffers(struct tvnet_priv *tvnet)
|
|
{
|
|
struct ep2h_empty_list *ep2h_empty_ptr, *temp;
|
|
struct device *d = &tvnet->pdev->dev;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&tvnet->ep2h_empty_lock, flags);
|
|
list_for_each_entry_safe(ep2h_empty_ptr, temp, &tvnet->ep2h_empty_list,
|
|
list) {
|
|
list_del(&ep2h_empty_ptr->list);
|
|
dma_unmap_single(d, ep2h_empty_ptr->iova, ep2h_empty_ptr->len,
|
|
DMA_FROM_DEVICE);
|
|
dev_kfree_skb_any(ep2h_empty_ptr->skb);
|
|
kfree(ep2h_empty_ptr);
|
|
}
|
|
spin_unlock_irqrestore(&tvnet->ep2h_empty_lock, flags);
|
|
}
|
|
|
|
static void tvnet_host_stop_tx_queue(struct tvnet_priv *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_host_stop_rx_work(struct tvnet_priv *tvnet)
|
|
{
|
|
/* wait for interrupt handle to return to ensure rx is stopped */
|
|
synchronize_irq(pci_irq_vector(tvnet->pdev, 1));
|
|
}
|
|
|
|
static void tvnet_host_clear_data_msg_counters(struct tvnet_priv *tvnet)
|
|
{
|
|
struct host_ring_buf *host_mem = &tvnet->host_mem;
|
|
struct host_own_cnt *host_cnt = host_mem->host_cnt;
|
|
struct ep_ring_buf *ep_mem = &tvnet->ep_mem;
|
|
struct ep_own_cnt *ep_cnt = ep_mem->ep_cnt;
|
|
|
|
host_cnt->ep2h_empty_wr_cnt = 0;
|
|
ep_cnt->ep2h_empty_rd_cnt = 0;
|
|
host_cnt->h2ep_full_wr_cnt = 0;
|
|
ep_cnt->h2ep_full_rd_cnt = 0;
|
|
}
|
|
|
|
static void tvnet_host_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_host_update_link_sm(struct tvnet_priv *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_host_update_link_state(ndev, tvnet->os_link_state);
|
|
}
|
|
|
|
/* One way link state machine*/
|
|
static void tvnet_host_user_link_up_req(struct tvnet_priv *tvnet)
|
|
{
|
|
struct ctrl_msg msg = {};
|
|
|
|
tvnet_host_clear_data_msg_counters(tvnet);
|
|
tvnet_host_alloc_empty_buffers(tvnet);
|
|
msg.msg_id = CTRL_MSG_LINK_UP;
|
|
tvnet_host_write_ctrl_msg(tvnet, &msg);
|
|
tvnet->rx_link_state = DIR_LINK_STATE_UP;
|
|
tvnet_host_update_link_sm(tvnet);
|
|
}
|
|
|
|
static void tvnet_host_user_link_down_req(struct tvnet_priv *tvnet)
|
|
{
|
|
struct ctrl_msg msg = {};
|
|
|
|
tvnet->rx_link_state = DIR_LINK_STATE_SENT_DOWN;
|
|
msg.msg_id = CTRL_MSG_LINK_DOWN;
|
|
tvnet_host_write_ctrl_msg(tvnet, &msg);
|
|
tvnet_host_update_link_sm(tvnet);
|
|
}
|
|
|
|
static void tvnet_host_rcv_link_up_msg(struct tvnet_priv *tvnet)
|
|
{
|
|
tvnet->tx_link_state = DIR_LINK_STATE_UP;
|
|
tvnet_host_update_link_sm(tvnet);
|
|
}
|
|
|
|
static void tvnet_host_rcv_link_down_msg(struct tvnet_priv *tvnet)
|
|
{
|
|
struct ctrl_msg msg = {};
|
|
|
|
/* Stop using empty buffers of remote system */
|
|
tvnet_host_stop_tx_queue(tvnet);
|
|
msg.msg_id = CTRL_MSG_LINK_DOWN_ACK;
|
|
tvnet_host_write_ctrl_msg(tvnet, &msg);
|
|
tvnet->tx_link_state = DIR_LINK_STATE_DOWN;
|
|
tvnet_host_update_link_sm(tvnet);
|
|
}
|
|
|
|
static void tvnet_host_rcv_link_down_ack(struct tvnet_priv *tvnet)
|
|
{
|
|
/* Stop using empty buffers(which are full in rx) of local system */
|
|
tvnet_host_stop_rx_work(tvnet);
|
|
tvnet_host_free_empty_buffers(tvnet);
|
|
tvnet->rx_link_state = DIR_LINK_STATE_DOWN;
|
|
wake_up_interruptible(&tvnet->link_state_wq);
|
|
tvnet_host_update_link_sm(tvnet);
|
|
}
|
|
|
|
static int tvnet_host_open(struct net_device *ndev)
|
|
{
|
|
struct tvnet_priv *tvnet = netdev_priv(ndev);
|
|
|
|
mutex_lock(&tvnet->link_state_lock);
|
|
if (tvnet->rx_link_state == DIR_LINK_STATE_DOWN)
|
|
tvnet_host_user_link_up_req(tvnet);
|
|
napi_enable(&tvnet->napi);
|
|
mutex_unlock(&tvnet->link_state_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tvnet_host_close(struct net_device *ndev)
|
|
{
|
|
struct tvnet_priv *tvnet = netdev_priv(ndev);
|
|
int ret = 0;
|
|
|
|
mutex_lock(&tvnet->link_state_lock);
|
|
napi_disable(&tvnet->napi);
|
|
if (tvnet->rx_link_state == DIR_LINK_STATE_UP)
|
|
tvnet_host_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",
|
|
__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 ret;
|
|
}
|
|
|
|
static int tvnet_host_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_host_close(ndev);
|
|
}
|
|
|
|
pr_info("changing MTU from %d to %d\n", ndev->mtu, new_mtu);
|
|
ndev->mtu = new_mtu;
|
|
|
|
if (set_down)
|
|
tvnet_host_open(ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static netdev_tx_t tvnet_host_start_xmit(struct sk_buff *skb,
|
|
struct net_device *ndev)
|
|
{
|
|
struct tvnet_priv *tvnet = netdev_priv(ndev);
|
|
struct host_ring_buf *host_mem = &tvnet->host_mem;
|
|
struct data_msg *h2ep_full_msg = host_mem->h2ep_full_msgs;
|
|
struct skb_shared_info *info = skb_shinfo(skb);
|
|
struct ep_ring_buf *ep_mem = &tvnet->ep_mem;
|
|
struct data_msg *h2ep_empty_msg = ep_mem->h2ep_empty_msgs;
|
|
struct device *d = &tvnet->pdev->dev;
|
|
#if ENABLE_DMA
|
|
struct tvnet_dma_desc *dma_desc = tvnet->dma_desc;
|
|
struct dma_desc_cnt *desc_cnt = &tvnet->desc_cnt;
|
|
u32 desc_widx, desc_ridx, val;
|
|
u32 ctrl_d;
|
|
unsigned long timeout;
|
|
#endif
|
|
dma_addr_t src_iova;
|
|
dma_addr_t dst_iova;
|
|
u32 rd_idx;
|
|
u32 wr_idx;
|
|
void *dst_virt;
|
|
int len;
|
|
|
|
/* TODO Not expecting skb frags, remove this after testing */
|
|
WARN_ON(info->nr_frags);
|
|
|
|
/* Check if H2EP_EMPTY_BUF available to read */
|
|
if (!tvnet_ivc_rd_available(&tvnet->h2ep_empty)) {
|
|
tvnet_host_raise_ep_ctrl_irq(tvnet);
|
|
pr_debug("%s: No H2EP empty msg, stop tx\n", __func__);
|
|
netif_stop_queue(ndev);
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
|
|
/* Check if H2EP_FULL_BUF available to write */
|
|
if (tvnet_ivc_full(&tvnet->h2ep_full)) {
|
|
tvnet_host_raise_ep_ctrl_irq(tvnet);
|
|
pr_debug("%s: No H2EP 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) {
|
|
pr_debug("%s: dma descriptors are not available\n", __func__);
|
|
netif_stop_queue(ndev);
|
|
return NETDEV_TX_BUSY;
|
|
}
|
|
#endif
|
|
|
|
len = skb_headlen(skb);
|
|
|
|
src_iova = dma_map_single(d, skb->data, len, DMA_TO_DEVICE);
|
|
if (dma_mapping_error(d, src_iova)) {
|
|
pr_err("%s: dma_map_single failed\n", __func__);
|
|
dev_kfree_skb_any(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
/* Get H2EP empty msg */
|
|
rd_idx = tvnet_ivc_get_rd_cnt(&tvnet->h2ep_empty) %
|
|
RING_COUNT;
|
|
dst_iova = h2ep_empty_msg[rd_idx].u.empty_buffer.pcie_address;
|
|
dst_virt = (__force void *)tvnet->mmio_base + (dst_iova - tvnet->bar_md->bar0_base_phy);
|
|
/* Advance read count after all failure cases complated, to avoid
|
|
* dangling buffer at endpoint.
|
|
*/
|
|
tvnet_ivc_advance_rd(&tvnet->h2ep_empty);
|
|
/* Raise an interrupt to let EP populate H2EP_EMPTY_BUF ring */
|
|
tvnet_host_raise_ep_ctrl_irq(tvnet);
|
|
|
|
#if ENABLE_DMA
|
|
/* Trigger DMA write from src_iova to dst_iova */
|
|
desc_widx = desc_cnt->wr_cnt % DMA_DESC_COUNT;
|
|
dma_desc[desc_widx].size = len;
|
|
dma_desc[desc_widx].sar_low = lower_32_bits(src_iova);
|
|
dma_desc[desc_widx].sar_high = upper_32_bits(src_iova);
|
|
dma_desc[desc_widx].dar_low = lower_32_bits(dst_iova);
|
|
dma_desc[desc_widx].dar_high = upper_32_bits(dst_iova);
|
|
/* CB bit should be set at the end */
|
|
mb();
|
|
/* RIE is not required for polling mode */
|
|
ctrl_d = DMA_CH_CONTROL1_OFF_RDCH_RIE;
|
|
ctrl_d |= DMA_CH_CONTROL1_OFF_RDCH_LIE;
|
|
ctrl_d |= DMA_CH_CONTROL1_OFF_RDCH_CB;
|
|
dma_desc[desc_widx].ctrl_reg.ctrl_d = ctrl_d;
|
|
/*
|
|
* Read after write to avoid EP DMA reading LLE before CB is written to
|
|
* EP's system memory.
|
|
*/
|
|
ctrl_d = dma_desc[desc_widx].ctrl_reg.ctrl_d;
|
|
|
|
/* DMA write should not go out of order wrt CB bit set */
|
|
mb();
|
|
|
|
timeout = jiffies + msecs_to_jiffies(1000);
|
|
dma_common_wr(tvnet->dma_base, DMA_RD_DATA_CH, DMA_READ_DOORBELL_OFF);
|
|
|
|
desc_cnt->wr_cnt++;
|
|
|
|
while (true) {
|
|
val = dma_common_rd(tvnet->dma_base, DMA_READ_INT_STATUS_OFF);
|
|
if (val == BIT(DMA_RD_DATA_CH)) {
|
|
dma_common_wr(tvnet->dma_base, val,
|
|
DMA_READ_INT_CLEAR_OFF);
|
|
break;
|
|
}
|
|
if (time_after(jiffies, timeout)) {
|
|
pr_err("dma took more time, reset dma engine\n");
|
|
dma_common_wr(tvnet->dma_base,
|
|
DMA_READ_ENGINE_EN_OFF_DISABLE,
|
|
DMA_READ_ENGINE_EN_OFF);
|
|
mdelay(1);
|
|
dma_common_wr(tvnet->dma_base,
|
|
DMA_READ_ENGINE_EN_OFF_ENABLE,
|
|
DMA_READ_ENGINE_EN_OFF);
|
|
desc_cnt->wr_cnt--;
|
|
dma_unmap_single(d, 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 */
|
|
dma_desc[desc_ridx].ctrl_reg.ctrl_e.cb = 0;
|
|
mb();
|
|
|
|
tvnet->desc_cnt.rd_cnt++;
|
|
#else
|
|
/* Copy skb->data to endpoint dst address, use CPU virt addr */
|
|
memcpy(dst_virt, skb->data, len);
|
|
/* BAR0 mmio address is wc mem, add mb to make sure that complete
|
|
* skb->data is written before updating counters.
|
|
*/
|
|
mb();
|
|
#endif
|
|
|
|
/* Push dst to H2EP full ring */
|
|
wr_idx = tvnet_ivc_get_wr_cnt(&tvnet->h2ep_full) %
|
|
RING_COUNT;
|
|
h2ep_full_msg[wr_idx].u.full_buffer.packet_size = len;
|
|
h2ep_full_msg[wr_idx].u.full_buffer.pcie_address = dst_iova;
|
|
h2ep_full_msg[wr_idx].msg_id = DATA_MSG_FULL_BUF;
|
|
/* BAR0 mmio address is wc mem, add mb to make sure that full
|
|
* buffer is written before updating counters.
|
|
*/
|
|
mb();
|
|
tvnet_ivc_advance_wr(&tvnet->h2ep_full);
|
|
tvnet_host_raise_ep_data_irq(tvnet);
|
|
|
|
/* Free skb */
|
|
dma_unmap_single(d, src_iova, len, DMA_TO_DEVICE);
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
static const struct net_device_ops tvnet_host_netdev_ops = {
|
|
.ndo_open = tvnet_host_open,
|
|
.ndo_stop = tvnet_host_close,
|
|
.ndo_start_xmit = tvnet_host_start_xmit,
|
|
.ndo_change_mtu = tvnet_host_change_mtu,
|
|
};
|
|
|
|
static void tvnet_host_setup_bar0_md(struct tvnet_priv *tvnet)
|
|
{
|
|
struct ep_ring_buf *ep_mem = &tvnet->ep_mem;
|
|
struct host_ring_buf *host_mem = &tvnet->host_mem;
|
|
|
|
tvnet->bar_md = (__force struct bar_md *)tvnet->mmio_base;
|
|
|
|
ep_mem->ep_cnt = (__force struct ep_own_cnt *)(tvnet->mmio_base +
|
|
tvnet->bar_md->ep_own_cnt_offset);
|
|
ep_mem->ep2h_ctrl_msgs = (__force struct ctrl_msg *)(tvnet->mmio_base +
|
|
tvnet->bar_md->ctrl_md.ep2h_offset);
|
|
ep_mem->ep2h_full_msgs = (__force struct data_msg *)(tvnet->mmio_base +
|
|
tvnet->bar_md->ep2h_md.ep2h_offset);
|
|
ep_mem->h2ep_empty_msgs = (__force struct data_msg *)(tvnet->mmio_base +
|
|
tvnet->bar_md->h2ep_md.ep2h_offset);
|
|
|
|
host_mem->host_cnt = (__force struct host_own_cnt *)(tvnet->mmio_base +
|
|
tvnet->bar_md->host_own_cnt_offset);
|
|
host_mem->h2ep_ctrl_msgs = (__force struct ctrl_msg *)(tvnet->mmio_base +
|
|
tvnet->bar_md->ctrl_md.h2ep_offset);
|
|
host_mem->ep2h_empty_msgs = (__force struct data_msg *)(tvnet->mmio_base +
|
|
tvnet->bar_md->ep2h_md.h2ep_offset);
|
|
host_mem->h2ep_full_msgs = (__force struct data_msg *)(tvnet->mmio_base +
|
|
tvnet->bar_md->h2ep_md.h2ep_offset);
|
|
|
|
tvnet->dma_desc = (__force struct tvnet_dma_desc *)(tvnet->mmio_base +
|
|
tvnet->bar_md->host_dma_offset);
|
|
|
|
tvnet->h2ep_ctrl.rd = &ep_mem->ep_cnt->h2ep_ctrl_rd_cnt;
|
|
tvnet->h2ep_ctrl.wr = &host_mem->host_cnt->h2ep_ctrl_wr_cnt;
|
|
tvnet->ep2h_ctrl.rd = &host_mem->host_cnt->ep2h_ctrl_rd_cnt;
|
|
tvnet->ep2h_ctrl.wr = &ep_mem->ep_cnt->ep2h_ctrl_wr_cnt;
|
|
tvnet->h2ep_empty.rd = &host_mem->host_cnt->h2ep_empty_rd_cnt;
|
|
tvnet->h2ep_empty.wr = &ep_mem->ep_cnt->h2ep_empty_wr_cnt;
|
|
tvnet->h2ep_full.rd = &ep_mem->ep_cnt->h2ep_full_rd_cnt;
|
|
tvnet->h2ep_full.wr = &host_mem->host_cnt->h2ep_full_wr_cnt;
|
|
tvnet->ep2h_empty.rd = &ep_mem->ep_cnt->ep2h_empty_rd_cnt;
|
|
tvnet->ep2h_empty.wr = &host_mem->host_cnt->ep2h_empty_wr_cnt;
|
|
tvnet->ep2h_full.rd = &host_mem->host_cnt->ep2h_full_rd_cnt;
|
|
tvnet->ep2h_full.wr = &ep_mem->ep_cnt->ep2h_full_wr_cnt;
|
|
}
|
|
|
|
static void tvnet_host_process_ctrl_msg(struct tvnet_priv *tvnet)
|
|
{
|
|
struct ctrl_msg msg;
|
|
|
|
while (tvnet_ivc_rd_available(&tvnet->ep2h_ctrl)) {
|
|
tvnet_host_read_ctrl_msg(tvnet, &msg);
|
|
if (msg.msg_id == CTRL_MSG_LINK_UP)
|
|
tvnet_host_rcv_link_up_msg(tvnet);
|
|
else if (msg.msg_id == CTRL_MSG_LINK_DOWN)
|
|
tvnet_host_rcv_link_down_msg(tvnet);
|
|
else if (msg.msg_id == CTRL_MSG_LINK_DOWN_ACK)
|
|
tvnet_host_rcv_link_down_ack(tvnet);
|
|
}
|
|
}
|
|
|
|
static int tvnet_host_process_ep2h_msg(struct tvnet_priv *tvnet)
|
|
{
|
|
struct ep_ring_buf *ep_mem = &tvnet->ep_mem;
|
|
struct data_msg *data_msg = ep_mem->ep2h_full_msgs;
|
|
struct device *d = &tvnet->pdev->dev;
|
|
struct ep2h_empty_list *ep2h_empty_ptr;
|
|
struct net_device *ndev = tvnet->ndev;
|
|
int count = 0;
|
|
|
|
while ((count < TVNET_NAPI_WEIGHT) &&
|
|
tvnet_ivc_rd_available(&tvnet->ep2h_full)) {
|
|
struct sk_buff *skb;
|
|
u64 pcie_address;
|
|
u32 len;
|
|
int idx, found = 0;
|
|
unsigned long flags;
|
|
|
|
/* Read EP2H full msg */
|
|
idx = tvnet_ivc_get_rd_cnt(&tvnet->ep2h_full) %
|
|
RING_COUNT;
|
|
len = data_msg[idx].u.full_buffer.packet_size;
|
|
pcie_address = data_msg[idx].u.full_buffer.pcie_address;
|
|
|
|
spin_lock_irqsave(&tvnet->ep2h_empty_lock, flags);
|
|
list_for_each_entry(ep2h_empty_ptr, &tvnet->ep2h_empty_list,
|
|
list) {
|
|
if (ep2h_empty_ptr->iova == pcie_address) {
|
|
list_del(&ep2h_empty_ptr->list);
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&tvnet->ep2h_empty_lock, flags);
|
|
|
|
/* Advance H2EP full buffer after search in local list */
|
|
tvnet_ivc_advance_rd(&tvnet->ep2h_full);
|
|
if (WARN_ON(!found))
|
|
continue;
|
|
|
|
/* If EP2H network queue is stopped due to lack of EP2H_FULL
|
|
* queue, raising ctrl irq will help.
|
|
*/
|
|
tvnet_host_raise_ep_ctrl_irq(tvnet);
|
|
|
|
dma_unmap_single(d, pcie_address, ndev->mtu + ETH_HLEN, DMA_FROM_DEVICE);
|
|
skb = ep2h_empty_ptr->skb;
|
|
skb_put(skb, len);
|
|
skb->protocol = eth_type_trans(skb, ndev);
|
|
napi_gro_receive(&tvnet->napi, skb);
|
|
|
|
/* Free EP2H empty list element */
|
|
kfree(ep2h_empty_ptr);
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static irqreturn_t tvnet_irq_ctrl(int irq, void *data)
|
|
{
|
|
struct net_device *ndev = data;
|
|
struct tvnet_priv *tvnet = netdev_priv(ndev);
|
|
|
|
if (netif_queue_stopped(ndev)) {
|
|
if ((tvnet->os_link_state == OS_LINK_STATE_UP) &&
|
|
tvnet_ivc_rd_available(&tvnet->h2ep_empty) &&
|
|
!tvnet_ivc_full(&tvnet->h2ep_full)) {
|
|
pr_debug("%s: wake net tx queue\n", __func__);
|
|
netif_wake_queue(ndev);
|
|
}
|
|
}
|
|
|
|
if (tvnet_ivc_rd_available(&tvnet->ep2h_ctrl))
|
|
tvnet_host_process_ctrl_msg(tvnet);
|
|
|
|
if (!tvnet_ivc_full(&tvnet->ep2h_empty) &&
|
|
(tvnet->os_link_state == OS_LINK_STATE_UP))
|
|
tvnet_host_alloc_empty_buffers(tvnet);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t tvnet_irq_data(int irq, void *data)
|
|
{
|
|
struct net_device *ndev = data;
|
|
struct tvnet_priv *tvnet = netdev_priv(ndev);
|
|
|
|
if (tvnet_ivc_rd_available(&tvnet->ep2h_full)) {
|
|
disable_irq_nosync(pci_irq_vector(tvnet->pdev, 1));
|
|
napi_schedule(&tvnet->napi);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int tvnet_host_poll(struct napi_struct *napi, int budget)
|
|
{
|
|
struct tvnet_priv *tvnet = container_of(napi, struct tvnet_priv, napi);
|
|
int work_done;
|
|
|
|
work_done = tvnet_host_process_ep2h_msg(tvnet);
|
|
if (work_done < budget) {
|
|
napi_complete(napi);
|
|
enable_irq(pci_irq_vector(tvnet->pdev, 1));
|
|
}
|
|
|
|
return work_done;
|
|
}
|
|
|
|
static int tvnet_host_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *pci_id)
|
|
{
|
|
struct tvnet_priv *tvnet;
|
|
struct net_device *ndev;
|
|
int ret;
|
|
|
|
dev_dbg(&pdev->dev, "%s: PCIe VID: 0x%x DID: 0x%x\n", __func__,
|
|
pci_id->vendor, pci_id->device);
|
|
ndev = alloc_etherdev(sizeof(struct tvnet_priv));
|
|
if (!ndev) {
|
|
ret = -ENOMEM;
|
|
dev_err(&pdev->dev, "alloc_etherdev() failed");
|
|
goto fail;
|
|
}
|
|
|
|
eth_hw_addr_random(ndev);
|
|
SET_NETDEV_DEV(ndev, &pdev->dev);
|
|
ndev->netdev_ops = &tvnet_host_netdev_ops;
|
|
tvnet = netdev_priv(ndev);
|
|
tvnet->ndev = ndev;
|
|
tvnet->pdev = pdev;
|
|
pci_set_drvdata(pdev, tvnet);
|
|
|
|
ret = pci_enable_device(pdev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "pci_enable_device() failed: %d\n", ret);
|
|
goto free_netdev;
|
|
}
|
|
|
|
#if defined(NV_PCI_ENABLE_PCIE_ERROR_REPORTING_PRESENT) /* Linux 6.5 */
|
|
pci_enable_pcie_error_reporting(pdev);
|
|
#endif
|
|
|
|
/*
|
|
* In CPU memory write case, skb->data buffer is copied to dst in BAR.
|
|
* Unaligned dword skb->data pointer comes in start_xmit, so use
|
|
* write combine mapping for BAR.
|
|
*/
|
|
#if ENABLE_DMA
|
|
tvnet->mmio_base = devm_ioremap(&pdev->dev,
|
|
pci_resource_start(pdev, 0),
|
|
pci_resource_len(pdev, 0));
|
|
#else
|
|
tvnet->mmio_base = devm_ioremap_wc(&pdev->dev,
|
|
pci_resource_start(pdev, 0),
|
|
pci_resource_len(pdev, 0));
|
|
#endif
|
|
if (!tvnet->mmio_base) {
|
|
ret = -ENOMEM;
|
|
dev_err(&pdev->dev, "BAR0 ioremap() failed\n");
|
|
goto pci_disable;
|
|
}
|
|
|
|
/* MSI-X vector table is saved in BAR2 */
|
|
tvnet->msix_tbl = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 2),
|
|
pci_resource_len(pdev, 2));
|
|
if (!tvnet->msix_tbl) {
|
|
ret = -ENOMEM;
|
|
dev_err(&pdev->dev, "BAR2 ioremap() failed\n");
|
|
goto pci_disable;
|
|
}
|
|
|
|
tvnet->dma_base = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 4),
|
|
pci_resource_len(pdev, 4));
|
|
if (!tvnet->dma_base) {
|
|
ret = -ENOMEM;
|
|
dev_err(&pdev->dev, "BAR4 ioremap() failed\n");
|
|
goto pci_disable;
|
|
}
|
|
|
|
pci_set_master(pdev);
|
|
pci_set_drvdata(pdev, tvnet);
|
|
|
|
/* Setup BAR0 meta data */
|
|
tvnet_host_setup_bar0_md(tvnet);
|
|
|
|
#if defined(NV_NETIF_NAPI_ADD_WEIGHT_PRESENT) /* Linux v6.1 */
|
|
netif_napi_add_weight(ndev, &tvnet->napi, tvnet_host_poll, TVNET_NAPI_WEIGHT);
|
|
#else
|
|
netif_napi_add(ndev, &tvnet->napi, tvnet_host_poll, TVNET_NAPI_WEIGHT);
|
|
#endif
|
|
|
|
ndev->mtu = TVNET_DEFAULT_MTU;
|
|
|
|
ret = register_netdev(ndev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "register_netdev() fail: %d\n", ret);
|
|
goto pci_disable;
|
|
}
|
|
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);
|
|
|
|
ret = pci_alloc_irq_vectors(pdev, 2, 2, PCI_IRQ_MSIX |
|
|
PCI_IRQ_AFFINITY);
|
|
if (ret <= 0) {
|
|
dev_err(&pdev->dev, "pci_alloc_irq_vectors() fail: %d\n", ret);
|
|
ret = -EIO;
|
|
goto unreg_netdev;
|
|
}
|
|
|
|
ret = request_irq(pci_irq_vector(pdev, 0), tvnet_irq_ctrl, 0,
|
|
ndev->name, ndev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "request_irq() fail: %d\n", ret);
|
|
goto disable_msi;
|
|
}
|
|
|
|
ret = request_irq(pci_irq_vector(pdev, 1), tvnet_irq_data, 0,
|
|
ndev->name, ndev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "request_irq() fail: %d\n", ret);
|
|
goto fail_request_irq_ctrl;
|
|
}
|
|
|
|
#if ENABLE_DMA
|
|
tvnet_host_write_dma_msix_settings(tvnet);
|
|
#endif
|
|
|
|
INIT_LIST_HEAD(&tvnet->ep2h_empty_list);
|
|
spin_lock_init(&tvnet->ep2h_empty_lock);
|
|
|
|
return 0;
|
|
|
|
fail_request_irq_ctrl:
|
|
free_irq(pci_irq_vector(pdev, 0), ndev);
|
|
disable_msi:
|
|
pci_free_irq_vectors(pdev);
|
|
unreg_netdev:
|
|
unregister_netdev(ndev);
|
|
pci_disable:
|
|
netif_napi_del(&tvnet->napi);
|
|
pci_disable_device(pdev);
|
|
free_netdev:
|
|
free_netdev(ndev);
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static void tvnet_host_remove(struct pci_dev *pdev)
|
|
{
|
|
int ret = -1;
|
|
struct tvnet_priv *tvnet = pci_get_drvdata(pdev);
|
|
|
|
if (tvnet->rx_link_state == DIR_LINK_STATE_UP)
|
|
tvnet_host_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: failed: tx_state: %d rx_state: %d err: %d", __func__,
|
|
tvnet->tx_link_state, tvnet->rx_link_state, ret);
|
|
tvnet->rx_link_state = DIR_LINK_STATE_UP;
|
|
}
|
|
|
|
free_irq(pci_irq_vector(pdev, 0), tvnet->ndev);
|
|
free_irq(pci_irq_vector(pdev, 1), tvnet->ndev);
|
|
pci_free_irq_vectors(pdev);
|
|
unregister_netdev(tvnet->ndev);
|
|
netif_napi_del(&tvnet->napi);
|
|
pci_disable_device(pdev);
|
|
free_netdev(tvnet->ndev);
|
|
}
|
|
|
|
static int tvnet_host_suspend(struct pci_dev *pdev, pm_message_t state)
|
|
{
|
|
struct tvnet_priv *tvnet = pci_get_drvdata(pdev);
|
|
|
|
disable_irq(pci_irq_vector(tvnet->pdev, 1));
|
|
|
|
if (tvnet->rx_link_state == DIR_LINK_STATE_UP) {
|
|
tvnet_host_close(tvnet->ndev);
|
|
tvnet->pm_closed = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tvnet_host_resume(struct pci_dev *pdev)
|
|
{
|
|
struct tvnet_priv *tvnet = pci_get_drvdata(pdev);
|
|
#if ENABLE_DMA
|
|
struct dma_desc_cnt *desc_cnt = &tvnet->desc_cnt;
|
|
|
|
desc_cnt->wr_cnt = desc_cnt->rd_cnt = 0;
|
|
tvnet_host_write_dma_msix_settings(tvnet);
|
|
#endif
|
|
|
|
if (tvnet->pm_closed == true) {
|
|
tvnet_host_open(tvnet->ndev);
|
|
tvnet->pm_closed = false;
|
|
}
|
|
|
|
enable_irq(pci_irq_vector(tvnet->pdev, 1));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pci_device_id tvnet_host_pci_tbl[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA,
|
|
PCI_DEVICE_ID_NVIDIA_JETSON_AGX_NETWORK) },
|
|
{0,},
|
|
};
|
|
|
|
static struct pci_driver tvnet_pci_driver = {
|
|
.name = "tvnet",
|
|
.id_table = tvnet_host_pci_tbl,
|
|
.probe = tvnet_host_probe,
|
|
.remove = tvnet_host_remove,
|
|
#ifdef CONFIG_PM
|
|
.suspend = tvnet_host_suspend,
|
|
.resume = tvnet_host_resume,
|
|
#endif
|
|
};
|
|
|
|
module_pci_driver(tvnet_pci_driver);
|
|
|
|
MODULE_DESCRIPTION("PCI TEGRA VIRTUAL NETWORK DRIVER");
|
|
MODULE_AUTHOR("Manikanta Maddireddy <mmaddireddy@nvidia.com>");
|
|
MODULE_LICENSE("GPL v2");
|