From 77001d121e0dd7718a0eaa586f9036239d4e8597 Mon Sep 17 00:00:00 2001 From: Manish Bhardwaj Date: Fri, 30 Sep 2022 19:27:53 +0530 Subject: [PATCH] nvidia-oot: add support for hv_net driver Using this patch we are adding support for hv_net driver in oot kernel. JIRA ESLC-6885 Signed-off-by: Manish Bhardwaj Change-Id: Ie8f65c014ae1c01a8378f0016ea520a4d114f6d7 Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2785035 Reviewed-by: Bitan Biswas GVS: Gerrit_Virtual_Submit --- drivers/net/Makefile | 1 + drivers/net/tegra_hv_net.c | 903 +++++++++++++++++++++++++++++++++++++ 2 files changed, 904 insertions(+) create mode 100644 drivers/net/tegra_hv_net.c diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 284502ad..3016ffc6 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -2,3 +2,4 @@ # Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. obj-m += ethernet/ +obj-m += tegra_hv_net.o diff --git a/drivers/net/tegra_hv_net.c b/drivers/net/tegra_hv_net.c new file mode 100644 index 00000000..c7ccb074 --- /dev/null +++ b/drivers/net/tegra_hv_net.c @@ -0,0 +1,903 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#undef DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "tegra_hv_net" +#define DRV_VERSION "0.1" + +#include +#include + +/* frame format is +* 0000: +* 0004: data +* +* data frame +* 0000: [16-bit frame size][13-bit pad][Last-bit][First-bit][F_CNTRL=0] +* 0004: [packet-size 32 bit] +* 0008: data +* +* control frame +* 0000: (F_CNTRL == 1) F_CNTRL_CMD(x) +* 0004: control frame data (deduced from F_CNTRL_CMD) +*/ + +/* data header size */ +#define HDR_SIZE 8 + +#define F_CNTRL (1 << 0) /* control frame (0 = data frame) */ +#define F_CNTRL_CMD(x) ((u32)((x) & 0xff) << 24) /* control frame command */ + +#define F_CNTRL_CMD_STATUS F_CNTRL_CMD(0) /* link status cmd */ +#define F_STATUS_UP (1 << 1) /* link status is up */ +#define F_STATUS_PAUSE (1 << 2) /* link status is pause */ + +#define F_STATUS_PENDING (1 << 23) /* pending link status update */ + +#define F_DATA_FIRST (1 << 1) /* first chunk of a frame */ +#define F_DATA_LAST (1 << 2) /* last chunk of a frame */ +#define F_DATA_FSIZE_SHIFT 16 +#define F_DATA_FSIZE_MASK (~0 << F_DATA_FSIZE_SHIFT) +#define F_DATA_FSIZE(x) (((u32)(x) << F_DATA_FSIZE_SHIFT) & F_DATA_FSIZE_MASK) + +/* jumbo frame limit */ +#define MAX_MTU 9000 +#define MIN_MTU 14 + +#define DEFAULT_HIGH_WATERMARK_MULT 50 +#define DEFAULT_LOW_WATERMARK_MULT 25 +#define DEFAULT_MAX_TX_DELAY_MSECS 10 + +enum drop_kind { + dk_none, + /* tx */ + dk_linearize, + dk_full, + dk_wq, + dk_write, + /* rx */ + dk_frame, + dk_packet, + dk_unexpected, + dk_alloc, + dk_overflow, +}; + +struct tegra_hv_net_stats { + struct u64_stats_sync tx_syncp; + struct u64_stats_sync rx_syncp; + u64 tx_bytes; + u64 tx_packets; + u64 tx_drops; + + u64 rx_bytes; + u64 rx_packets; + u64 rx_drops; + + /* internal tx stats */ + u64 tx_linearize_fail; + u64 tx_queue_full; + u64 tx_wq_fail; + u64 tx_ivc_write_fail; + /* internal rx stats */ + u64 rx_bad_frame; + u64 rx_bad_packet; + u64 rx_unexpected_packet; + u64 rx_alloc_fail; + u64 rx_overflow; +}; + +struct tegra_hv_net { + struct platform_device *pdev; + struct net_device *ndev; + struct tegra_hv_ivc_cookie *ivck; + const void *mac_address; + struct napi_struct napi; + struct tegra_hv_net_stats __percpu *stats; + + struct sk_buff *rx_skb; + struct sk_buff_head tx_q; + + struct work_struct xmit_work; + struct workqueue_struct *xmit_wq; + wait_queue_head_t wq; + + unsigned int high_watermark; /* mult * framesize */ + unsigned int low_watermark; + unsigned int max_tx_delay; +}; + +static int tegra_hv_net_open(struct net_device *ndev) +{ + struct tegra_hv_net *hvn = netdev_priv(ndev); + + napi_enable(&hvn->napi); + netif_start_queue(ndev); + + /* + * check if there are already packets in our queue, + * and if so, we need to schedule a call to handle them + */ + if (tegra_hv_ivc_can_read(hvn->ivck)) + napi_schedule(&hvn->napi); + + return 0; +} + +static irqreturn_t tegra_hv_net_interrupt(int irq, void *data) +{ + struct net_device *ndev = data; + struct tegra_hv_net *hvn = netdev_priv(ndev); + + /* until this function returns 0, the channel is unusable */ + if (tegra_hv_ivc_channel_notified(hvn->ivck) != 0) + return IRQ_HANDLED; + + if (tegra_hv_ivc_can_write(hvn->ivck)) + wake_up_interruptible_all(&hvn->wq); + + if (tegra_hv_ivc_can_read(hvn->ivck)) + napi_schedule(&hvn->napi); + + return IRQ_HANDLED; +} + +static void *tegra_hv_net_xmit_get_buffer(struct tegra_hv_net *hvn) +{ + void *p; + int ret; + + /* + * grabbing a frame can fail for the following reasons: + * 1. the channel is full / peer is uncooperative + * 2. the channel is under reset / peer has restarted + */ + p = tegra_hv_ivc_write_get_next_frame(hvn->ivck); + if (IS_ERR(p)) { + ret = wait_event_interruptible_timeout(hvn->wq, + !IS_ERR(p = tegra_hv_ivc_write_get_next_frame( + hvn->ivck)), + msecs_to_jiffies(hvn->max_tx_delay)); + if (ret <= 0) { + net_warn_ratelimited( + "%s: timed out after %u ms\n", + hvn->ndev->name, + hvn->max_tx_delay); + } + } + + return p; +} + +static void tegra_hv_net_xmit_work(struct work_struct *work) +{ + struct tegra_hv_net *hvn = + container_of(work, struct tegra_hv_net, xmit_work); + struct tegra_hv_net_stats *stats = raw_cpu_ptr(hvn->stats); + struct net_device *ndev = hvn->ndev; + struct sk_buff *skb; + int ret, max_frame, count, first, last, orig_len; + u32 *p, p0, p1; + enum drop_kind dk; + + max_frame = hvn->ivck->frame_size - HDR_SIZE; + + dk = dk_none; + while ((skb = skb_dequeue(&hvn->tx_q)) != NULL) { + + /* start the queue if it is short again */ + if (netif_queue_stopped(ndev) && + skb_queue_len(&hvn->tx_q) < hvn->low_watermark) + netif_start_queue(ndev); + + ret = skb_linearize(skb); + if (ret != 0) { + netdev_err(hvn->ndev, + "%s: skb_linearize error=%d\n", + __func__, ret); + + dk = dk_linearize; + goto drop; + } + + /* print_hex_dump(KERN_INFO, "tx-", DUMP_PREFIX_OFFSET, + * 16, 1, skb->data, skb->len, true); */ + + /* copy the fragments */ + orig_len = skb->len; + first = 1; + while (skb->len > 0) { + count = skb->len; + if (count > max_frame) + count = max_frame; + + /* wait up to the maximum send timeout */ + p = tegra_hv_net_xmit_get_buffer(hvn); + if (IS_ERR(p)) { + dk = dk_wq; + goto drop; + } + + last = skb->len == count; + + p0 = F_DATA_FSIZE(count); + if (first) + p0 |= F_DATA_FIRST; + if (last) + p0 |= F_DATA_LAST; + p1 = orig_len; + + netdev_dbg(ndev, "F: %c%c F%d P%d [%08x %08x]\n", + first ? 'F' : '.', + last ? 'L' : '.', + count, orig_len, p[0], p[1]); + + first = 0; + + p[0] = p0; + p[1] = p1; + skb_copy_from_linear_data(skb, &p[2], count); + + /* advance the tx queue */ + (void)tegra_hv_ivc_write_advance(hvn->ivck); + skb_pull(skb, count); + } + /* all OK */ + dk = dk_none; + +drop: + dev_kfree_skb(skb); + + u64_stats_update_begin(&stats->tx_syncp); + if (dk == dk_none) { + stats->tx_packets++; + stats->tx_bytes += orig_len; + } else { + stats->tx_drops++; + switch (dk) { + default: + /* never happens but gcc sometimes whines */ + break; + case dk_linearize: + stats->tx_linearize_fail++; + break; + case dk_full: + stats->tx_queue_full++; + break; + case dk_wq: + stats->tx_wq_fail++; + break; + case dk_write: + stats->tx_ivc_write_fail++; + break; + } + } + u64_stats_update_end(&stats->tx_syncp); + } +} + +/* xmit is dummy, we just add the skb to the tx_q and queue work */ +static netdev_tx_t tegra_hv_net_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct tegra_hv_net *hvn = netdev_priv(ndev); + + skb_orphan(skb); + nf_reset_ct(skb); + skb_queue_tail(&hvn->tx_q, skb); + queue_work_on(WORK_CPU_UNBOUND, hvn->xmit_wq, &hvn->xmit_work); + + /* stop the queue if it gets too long */ + if (!netif_queue_stopped(ndev) && + skb_queue_len(&hvn->tx_q) >= hvn->high_watermark) + netif_stop_queue(ndev); + else if (netif_queue_stopped(ndev) && + skb_queue_len(&hvn->tx_q) < hvn->low_watermark) + netif_start_queue(ndev); + + return NETDEV_TX_OK; +} + +static int +tegra_hv_net_stop(struct net_device *ndev) +{ + struct tegra_hv_net *hvn = netdev_priv(ndev); + + netif_stop_queue(ndev); + napi_disable(&hvn->napi); + + return 0; +} + +static int tegra_hv_net_change_mtu(struct net_device *ndev, int new_mtu) +{ + if (new_mtu < MIN_MTU || new_mtu > MAX_MTU) { + netdev_err(ndev, "invalid MTU, max MTU is: %d\n", MAX_MTU); + return -EINVAL; + } + + if (ndev->mtu == new_mtu) + return 0; + + /* we can really handle any MTU size */ + ndev->mtu = new_mtu; + return 0; +} + +static void tegra_hv_net_set_rx_mode(struct net_device *ndev) +{ + /* we don't do any kind of filtering */ +} + +static void tegra_hv_net_tx_timeout(struct net_device *ndev, unsigned int txqueue) +{ + netdev_err(ndev, "%s\n", __func__); +} + +void +tegra_hv_net_get_stats64(struct net_device *ndev, + struct rtnl_link_stats64 *tot) +{ + struct tegra_hv_net *hvn = netdev_priv(ndev); + struct tegra_hv_net_stats *stats; + u64 tx_packets, tx_bytes, tx_drops, rx_packets, rx_bytes, rx_drops; + unsigned int start; + int cpu; + + for_each_possible_cpu(cpu) { + stats = per_cpu_ptr(hvn->stats, cpu); + + do { + start = u64_stats_fetch_begin_irq(&stats->tx_syncp); + tx_packets = stats->tx_packets; + tx_bytes = stats->tx_bytes; + tx_drops = stats->tx_drops; + } while (u64_stats_fetch_retry_irq(&stats->tx_syncp, start)); + + do { + start = u64_stats_fetch_begin_irq(&stats->rx_syncp); + rx_packets = stats->rx_packets; + rx_bytes = stats->rx_bytes; + rx_drops = stats->rx_drops; + } while (u64_stats_fetch_retry_irq(&stats->rx_syncp, start)); + + tot->tx_packets += tx_packets; + tot->tx_bytes += tx_bytes; + tot->tx_dropped += tx_drops; + + tot->rx_packets += rx_packets; + tot->rx_bytes += rx_bytes; + tot->rx_dropped += rx_drops; + } + +} + +static int tegra_hv_net_set_mac_address(struct net_device *dev, void *p) +{ + return 0; +} + +static const struct net_device_ops tegra_hv_netdev_ops = { + .ndo_open = tegra_hv_net_open, + .ndo_start_xmit = tegra_hv_net_xmit, + .ndo_stop = tegra_hv_net_stop, + .ndo_change_mtu = tegra_hv_net_change_mtu, + .ndo_set_rx_mode = tegra_hv_net_set_rx_mode, + .ndo_tx_timeout = tegra_hv_net_tx_timeout, + .ndo_get_stats64 = tegra_hv_net_get_stats64, + .ndo_set_mac_address = tegra_hv_net_set_mac_address, +}; + +static void tegra_hv_net_ethtool_get_drvinfo(struct net_device *ndev, + struct ethtool_drvinfo *info) +{ + struct tegra_hv_net *hvn = netdev_priv(ndev); + + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); + strlcpy(info->bus_info, dev_name(&hvn->pdev->dev), + sizeof(info->bus_info)); +} + +static const struct ethtool_ops tegra_hv_ethtool_ops = { + .get_drvinfo = tegra_hv_net_ethtool_get_drvinfo, + .get_link = ethtool_op_get_link, +}; + +static void tegra_hv_net_tx_complete(struct tegra_hv_net *hvn) +{ + struct net_device *ndev = hvn->ndev; + + /* wake queue if no more tx buffers */ + if (skb_queue_len(&hvn->tx_q) == 0) + netif_wake_queue(ndev); + +} + +static int tegra_hv_net_rx(struct tegra_hv_net *hvn, int limit) +{ + struct tegra_hv_net_stats *stats = this_cpu_ptr(hvn->stats); + struct net_device *ndev = hvn->ndev; + struct sk_buff *skb; + int nr, frame_size, max_frame, count, first, last; + u32 *p, p0; + enum drop_kind dk; + + max_frame = hvn->ivck->frame_size - HDR_SIZE; + + nr = 0; + dk = dk_none; + while (nr < limit) { + /* + * grabbing a frame can fail for the following reasons: + * 1. the channel is empty / peer is uncooperative + * 2. the channel is under reset / peer has restarted + */ + p = tegra_hv_ivc_read_get_next_frame(hvn->ivck); + if (IS_ERR(p)) + break; + + nr++; + + p0 = p[0]; + first = !!(p0 & F_DATA_FIRST); + last = !!(p0 & F_DATA_LAST); + frame_size = (p0 & F_DATA_FSIZE_MASK) >> F_DATA_FSIZE_SHIFT; + count = p[1]; + + netdev_dbg(ndev, "F: %c%c F%d P%d [%08x %08x]\n", + first ? 'F' : '.', + last ? 'L' : '.', + frame_size, count, p[0], p[1]); + + if (frame_size > max_frame) { + netdev_err(ndev, "Bad fragment size %d\n", frame_size); + dk = dk_frame; + goto drop; + } + + /* verify that packet is sane */ + if (count < MIN_MTU || count > MAX_MTU + MIN_MTU) { + netdev_err(ndev, "Bad packet size %d\n", count); + dk = dk_packet; + goto drop; + } + /* receive state machine */ + if (hvn->rx_skb == NULL) { + if (!first) { + netdev_err(ndev, "unexpected fragment\n"); + dk = dk_unexpected; + goto drop; + } + hvn->rx_skb = netdev_alloc_skb(ndev, count); + if (hvn->rx_skb == NULL) { + netdev_err(ndev, "failed to allocate packet\n"); + dk = dk_alloc; + goto drop; + } + } + /* verify that skb still can receive the data */ + if (skb_tailroom(hvn->rx_skb) < frame_size) { + netdev_err(ndev, "skb overflow\n"); + dev_kfree_skb(hvn->rx_skb); + hvn->rx_skb = NULL; + dk = dk_overflow; + goto drop; + } + + /* append the data */ + skb = hvn->rx_skb; + skb_copy_to_linear_data_offset(skb, skb->len, p + 2, + frame_size); + skb_put(skb, frame_size); + + if (last) { + /* print_hex_dump(KERN_INFO, "rx-", DUMP_PREFIX_OFFSET, + * 16, 1, skb->data, skb->len, true); */ + + count = skb->len; + + skb->protocol = eth_type_trans(skb, ndev); + skb->ip_summed = CHECKSUM_NONE; + netif_receive_skb(skb); + hvn->rx_skb = NULL; + } + dk = dk_none; +drop: + (void)tegra_hv_ivc_read_advance(hvn->ivck); + + u64_stats_update_begin(&stats->rx_syncp); + if (dk == dk_none) { + if (last) { + stats->rx_packets++; + stats->rx_bytes += count; + } + } else { + stats->rx_drops++; + switch (dk) { + default: + /* never happens but gcc sometimes whines */ + break; + case dk_frame: + stats->rx_bad_frame++; + break; + case dk_packet: + stats->rx_bad_packet++; + break; + case dk_unexpected: + stats->rx_unexpected_packet++; + break; + case dk_alloc: + stats->rx_alloc_fail++; + break; + case dk_overflow: + stats->rx_overflow++; + break; + } + } + u64_stats_update_end(&stats->rx_syncp); + + } + return nr; +} + +static int tegra_hv_net_poll(struct napi_struct *napi, int budget) +{ + struct tegra_hv_net *hvn = + container_of(napi, struct tegra_hv_net, napi); + int work_done = 0; + + tegra_hv_net_tx_complete(hvn); + + work_done = tegra_hv_net_rx(hvn, budget); + + if (work_done < budget) { + napi_complete(napi); + + /* + * if an interrupt occurs after tegra_hv_net_rx() but before + * napi_complete(), we lose the call to napi_schedule(). + */ + if (tegra_hv_ivc_can_read(hvn->ivck)) + napi_reschedule(napi); + } + + return work_done; +} + +static int tegra_hv_net_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dn, *hv_dn; + struct net_device *ndev = NULL; + struct tegra_hv_net *hvn = NULL; + int ret; + u32 id; + u32 highmark, lowmark, txdelay; + + if (!is_tegra_hypervisor_mode()) { + dev_info(dev, "Hypervisor is not present\n"); + return -ENODEV; + } + + dn = dev->of_node; + if (dn == NULL) { + dev_err(dev, "No OF data\n"); + return -EINVAL; + } + + hv_dn = of_parse_phandle(dn, "ivc", 0); + if (hv_dn == NULL) { + dev_err(dev, "Failed to parse phandle of ivc prop\n"); + return -EINVAL; + } + + ret = of_property_read_u32_index(dn, "ivc", 1, &id); + if (ret != 0) { + dev_err(dev, "Failed to read IVC property ID\n"); + goto out_of_put; + } + + ret = of_property_read_u32(dn, "high-watermark-mult", &highmark); + if (ret != 0) + highmark = DEFAULT_HIGH_WATERMARK_MULT; + + ret = of_property_read_u32(dn, "low-watermark-mult", &lowmark); + if (ret != 0) + lowmark = DEFAULT_LOW_WATERMARK_MULT; + + if (highmark <= lowmark) { + dev_err(dev, "Bad watermark configuration (high <= low = %u < %u)\n", + highmark, lowmark); + goto out_of_put; + } + + ret = of_property_read_u32(dn, "max-tx-delay-msecs", &txdelay); + if (ret != 0) + txdelay = DEFAULT_MAX_TX_DELAY_MSECS; + + ndev = alloc_netdev(sizeof(*hvn), "hv%d", NET_NAME_UNKNOWN, + ether_setup); + if (ndev == NULL) { + dev_err(dev, "Failed to allocate netdev\n"); + ret = -ENOMEM; + goto out_of_put; + } + + hvn = netdev_priv(ndev); + + hvn->stats = alloc_percpu(struct tegra_hv_net_stats); + if (hvn->stats == NULL) { + dev_err(dev, "Failed to allocate per-cpu stats\n"); + ret = -ENOMEM; + goto out_free_ndev; + } + + hvn->ivck = tegra_hv_ivc_reserve(hv_dn, id, NULL); + of_node_put(hv_dn); + hv_dn = NULL; + + if (IS_ERR_OR_NULL(hvn->ivck)) { + dev_err(dev, "Failed to reserve IVC channel %d\n", id); + ret = PTR_ERR(hvn->ivck); + hvn->ivck = NULL; + goto out_free_stats; + } + + hvn->high_watermark = highmark * hvn->ivck->nframes; + hvn->low_watermark = lowmark * hvn->ivck->nframes; + hvn->max_tx_delay = txdelay; + + /* make sure the frame size is sufficient */ + if (hvn->ivck->frame_size <= HDR_SIZE + 4) { + dev_err(dev, "frame size too small to support COMM\n"); + ret = -EINVAL; + goto out_unreserve; + } + + dev_info(dev, "Reserved IVC channel #%d - frame_size=%d\n", + id, hvn->ivck->frame_size); + + SET_NETDEV_DEV(ndev, dev); + platform_set_drvdata(pdev, ndev); + ether_setup(ndev); + ndev->max_mtu = MAX_MTU; + ndev->netdev_ops = &tegra_hv_netdev_ops; + ndev->ethtool_ops = &tegra_hv_ethtool_ops; + skb_queue_head_init(&hvn->tx_q); + INIT_WORK(&hvn->xmit_work, tegra_hv_net_xmit_work); + + hvn->pdev = pdev; + hvn->ndev = ndev; + ndev->irq = hvn->ivck->irq; + + init_waitqueue_head(&hvn->wq); + + ndev->priv_flags |= IFF_UNICAST_FLT | IFF_LIVE_ADDR_CHANGE; + ndev->hw_features = 0; /* we're a really dumb device for now */ + ndev->features |= ndev->hw_features; + /* get mac address from the DT */ + + hvn->mac_address = of_get_mac_address(dev->of_node, ndev->dev_addr); + if (IS_ERR_OR_NULL(hvn->mac_address)) { + if (of_property_read_bool(dev->of_node, "use-random-mac-addr")) + eth_hw_addr_random(ndev); + else { +#if KERNEL_VERSION(5, 16, 0) >= LINUX_VERSION_CODE + unsigned int gid; + + dev_warn(dev, "No valid mac-address found, using fixed local address\n"); + + ndev->dev_addr[0] = 0x0a; + ndev->dev_addr[1] = 0x86; + ndev->dev_addr[2] = 0x4c; + ndev->dev_addr[3] = 0xf8; + ndev->dev_addr[4] = (uint8_t)id; + + ret = hyp_read_gid(&gid); + if (ret != 0) { + dev_err(dev, "Failed to read guest id\n"); + goto out_unreserve; + } + ndev->dev_addr[5] = (uint8_t)(gid); +#else + dev_err(dev, "No valid mac-address found\n"); +#endif + } + } else { + /* Set the MAC address. */ + ether_addr_copy(ndev->dev_addr, hvn->mac_address); + } + + hvn->xmit_wq = alloc_workqueue("tgvnet-wq-%d", + WQ_UNBOUND | WQ_MEM_RECLAIM, + 1, /* FIXME: from DT? */ + pdev->id); + if (hvn->xmit_wq == NULL) { + dev_err(dev, "Failed to allocate workqueue\n"); + ret = -ENOMEM; + goto out_unreserve; + } + + netif_napi_add(ndev, &hvn->napi, tegra_hv_net_poll, 64); + ret = register_netdev(ndev); + if (ret) { + dev_err(dev, "Failed to register netdev\n"); + goto out_free_wq; + } + + /* + * start the channel reset process asynchronously. until the reset + * process completes, any attempt to use the ivc channel will return + * an error (e.g., all transmits will fail). + */ + tegra_hv_ivc_channel_reset(hvn->ivck); + + /* the interrupt request must be the last action */ + ret = devm_request_irq(dev, ndev->irq, tegra_hv_net_interrupt, 0, + dev_name(dev), ndev); + if (ret != 0) { + dev_err(dev, "Could not request irq #%d\n", ndev->irq); + goto out_unreg_netdev; + } + + dev_info(dev, "ready\n"); + + return 0; + +out_unreg_netdev: + unregister_netdev(ndev); + +out_free_wq: + netif_napi_del(&hvn->napi); + destroy_workqueue(hvn->xmit_wq); + +out_unreserve: + tegra_hv_ivc_unreserve(hvn->ivck); + +out_free_stats: + free_percpu(hvn->stats); + +out_free_ndev: + free_netdev(ndev); + +out_of_put: + of_node_put(hv_dn); + + return ret; +} + +static int tegra_hv_net_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct net_device *ndev = platform_get_drvdata(pdev); + struct tegra_hv_net *hvn = netdev_priv(ndev); + + platform_set_drvdata(pdev, NULL); + devm_free_irq(dev, ndev->irq, dev); + unregister_netdev(ndev); + netif_napi_del(&hvn->napi); + destroy_workqueue(hvn->xmit_wq); + tegra_hv_ivc_unreserve(hvn->ivck); + free_percpu(hvn->stats); + free_netdev(ndev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra_hv_net_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct tegra_hv_net *hvn = netdev_priv(ndev); + + /* If the netdev is not even running, no action */ + if (!netif_running(ndev)) + return 0; + + /* As this device is going to suspend, + * link can't be considered up, although we are not resetting + * the IVC channel. Therefore mark the link detached + * This would stop tx from getting queued as well + */ + netif_device_detach(ndev); + + ndev->netdev_ops->ndo_stop(ndev); + + /* tegra_hv_net_stop uses netif_stop_queue to disable the queue. + * netif_stop_queue doesn't prevent xmit_transfer running on another + * cpu, so additionally we need netif_tx_disable + */ + netif_tx_disable(ndev); + + /* Now no further job should be coming in, but + * there could be one queued or running already. + * Cancel or wait for such a job + */ + cancel_work_sync(&hvn->xmit_work); + + /* Workqueue should not be running at this point, + * so disable irq + */ + disable_irq(ndev->irq); + + return 0; +} + +static int tegra_hv_net_resume(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct tegra_hv_net *hvn = netdev_priv(ndev); + + if (!netif_running(ndev)) + return 0; + + enable_irq(ndev->irq); + + ndev->netdev_ops->ndo_open(ndev); + + /* Would wake the queue and mark the link enabled */ + netif_device_attach(ndev); + + /* Start the queue blindly, in case the previous + * work was cancelled during suspend + * If there is no pending xmit, + * the workqueue will wake up then exit gracefully + */ + queue_work_on(WORK_CPU_UNBOUND, hvn->xmit_wq, &hvn->xmit_work); + + return 0; +} +#endif + +#ifdef CONFIG_OF +static struct of_device_id tegra_hv_net_match[] = { + { .compatible = "nvidia,tegra-hv-net", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra_hv_net_match); +#endif /* CONFIG_OF */ + +static struct platform_driver tegra_hv_net_driver = { + .probe = tegra_hv_net_probe, + .remove = tegra_hv_net_remove, +#ifdef CONFIG_PM_SLEEP + .suspend = tegra_hv_net_suspend, + .resume = tegra_hv_net_resume, +#endif + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tegra_hv_net_match), + }, +}; + +module_platform_driver(tegra_hv_net_driver); + +MODULE_AUTHOR("Pantelis Antoniou "); +MODULE_DESCRIPTION("Ethernet network device over Tegra Hypervisor IVC channel"); +MODULE_LICENSE("GPL");