/*
 * Voicebus network debug interface
 *
 * Written by Shaun Ruffell <sruffell@digium.com>
 *
 * Copyright (C) 2010-2011 Digium, Inc.
 *
 * All rights reserved.

 * VoiceBus is a registered trademark of Digium.
 *
 */

/*
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2 as published by the
 * Free Software Foundation. See the LICENSE file included with
 * this program for more details.
 */

#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/slab.h>

#include <dahdi/kernel.h>

#include "voicebus.h"
#include "voicebus_net.h"

#ifdef VOICEBUS_NET_DEBUG

struct voicebus_netdev_priv {
	struct voicebus *vb;
};

static inline struct voicebus *
voicebus_from_netdev(struct net_device *netdev)
{
	struct voicebus_netdev_priv *priv;
	priv = netdev_priv(netdev);
	return priv->vb;
}

static void *
skb_to_vbb(struct voicebus *vb, struct sk_buff *skb)
{
	int res;
	struct vbb *vbb;
	const int COMMON_HEADER = 30;
	dma_addr_t dma_addr;

	if (skb->len != (VOICEBUS_SFRAME_SIZE + COMMON_HEADER)) {
		dev_warn(&vb->pdev->dev, "Packet of length %d is not the "
			 "required %d.\n", skb->len,
			 VOICEBUS_SFRAME_SIZE + COMMON_HEADER);
		return NULL;
	}

	vbb = dma_pool_alloc(vb->pool, GFP_KERNEL, &dma_addr);
	if (!vbb)
		return NULL;

	vbb->dma_addr = dma_addr;
	res = skb_copy_bits(skb, COMMON_HEADER, vbb, VOICEBUS_SFRAME_SIZE);
	if (res) {
		dev_warn(&vb->pdev->dev, "Failed call to skb_copy_bits.\n");
		dma_pool_free(vb->pool, vbb, vbb->dma_addr);
		return NULL;
	}
	return vbb;
}

static int
vb_net_hard_start_xmit(struct sk_buff *skb, struct net_device *netdev)
{
	struct voicebus *vb = voicebus_from_netdev(netdev);
	void *vbb;

	vbb = skb_to_vbb(vb, skb);
	if (vbb)
		voicebus_transmit(vb, vbb);

	dev_kfree_skb_any(skb);
	return NETDEV_TX_OK;
}

static int vb_net_receive(struct voicebus *vb, int max)
{
	int count = 0;
	struct sk_buff *skb;
	WARN_ON(0 == max);
	while ((skb = skb_dequeue(&vb->captured_packets))) {
		netif_receive_skb(skb);
		if (++count >= max)
			break;
	}
	return count;
}

static int vb_net_poll(struct napi_struct *napi, int budget)
{
	struct voicebus *vb = container_of(napi, struct voicebus, napi);
	int count;

	count = vb_net_receive(vb, budget);

	if (!skb_queue_len(&vb->captured_packets)) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
		netif_rx_complete(vb->netdev, &vb->napi);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30)
		netif_rx_complete(&vb->napi);
#else
		napi_complete(&vb->napi);
#endif
	}
	return count;
}

static void vb_net_set_multi(struct net_device *netdev)
{
	struct voicebus *vb = voicebus_from_netdev(netdev);
	dev_dbg(&vb->pdev->dev, "%s promiscuity:%d\n",
		__func__, netdev->promiscuity);
}

static int vb_net_up(struct net_device *netdev)
{
	struct voicebus *vb = voicebus_from_netdev(netdev);
	dev_dbg(&vb->pdev->dev, "%s\n", __func__);
	napi_enable(&vb->napi);
	return 0;
}

static int vb_net_down(struct net_device *netdev)
{
	struct voicebus *vb = voicebus_from_netdev(netdev);
	dev_dbg(&vb->pdev->dev, "%s\n", __func__);
	napi_disable(&vb->napi);
	return 0;
}

static struct net_device_stats *
vb_net_get_stats(struct net_device *netdev)
{
	struct voicebus *vb = voicebus_from_netdev(netdev);
	return &vb->net_stats;
}

#ifdef HAVE_NET_DEVICE_OPS
static const struct net_device_ops vb_netdev_ops = {
	.ndo_set_multicast_list = &vb_net_set_multi,
	.ndo_open = &vb_net_up,
	.ndo_stop = &vb_net_down,
	.ndo_start_xmit = &vb_net_hard_start_xmit,
	.ndo_get_stats = &vb_net_get_stats,
};
#endif

/**
 * vb_net_register - Register a new network interface.
 * @vb: voicebus card to register the interface for.
 *
 * The network interface is primarily used for debugging in order to watch the
 * traffic between the transcoder and the host.
 *
 */
int vb_net_register(struct voicebus *vb, const char *board_name)
{
	int res;
	struct net_device *netdev;
	struct voicebus_netdev_priv *priv;
	const char our_mac[] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
	netdev = alloc_netdev(sizeof(*priv), board_name,
				NET_NAME_UNKNOWN, ether_setup);
#else
	netdev = alloc_netdev(sizeof(*priv), board_name, ether_setup);
#endif

	if (!netdev)
		return -ENOMEM;
	priv = netdev_priv(netdev);
	priv->vb = vb;
	memcpy(netdev->dev_addr, our_mac, sizeof(our_mac));
#	ifdef HAVE_NET_DEVICE_OPS
	netdev->netdev_ops = &vb_netdev_ops;
#	else
	netdev->set_multicast_list = vb_net_set_multi;
	netdev->open = vb_net_up;
	netdev->stop = vb_net_down;
	netdev->hard_start_xmit = vb_net_hard_start_xmit;
	netdev->get_stats = vb_net_get_stats;
#	endif

	netdev->promiscuity = 0;
	netdev->flags |= IFF_NOARP;
	netif_napi_add(netdev, &vb->napi, vb_net_poll, 64);

	skb_queue_head_init(&vb->captured_packets);
	res = register_netdev(netdev);
	if (res) {
		dev_warn(&vb->pdev->dev,
			 "Failed to register network device %s.\n", board_name);
		goto error_sw;
	}

	vb->netdev = netdev;

	dev_dbg(&vb->pdev->dev,
		"Created network device %s for debug.\n", board_name);
	return 0;

error_sw:
	if (netdev)
		free_netdev(netdev);
	return res;
}

void vb_net_unregister(struct voicebus *wc)
{
	struct sk_buff *skb;
	if (!wc->netdev)
		return;

	unregister_netdev(wc->netdev);

	while ((skb = skb_dequeue(&wc->captured_packets)))
		kfree_skb(skb);

	free_netdev(wc->netdev);
	wc->netdev = NULL;
}

/* Header format for the voicebus network interface. */
struct voicebus_net_hdr {
	struct ethhdr ethhdr;
	__be16 seq_num;
	__be32 des0;
	__be16 tag;
	__be16 filler[4];
} __attribute__((packed));

static struct sk_buff *
vbb_to_skb(struct net_device *netdev, const void *vbb, const int tx,
	   const u32 des0, const u16 tag)
{
	struct voicebus *vb = voicebus_from_netdev(netdev);
	struct sk_buff *skb;
	struct voicebus_net_hdr *hdr;
	/* 0x88B5 is the local experimental ethertype */
	const u16 VOICEBUS_ETHTYPE = 0x88b5;
	const u8 BOARD_MAC[6] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11};
	const u8 HOST_MAC[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

	skb = netdev_alloc_skb(vb->netdev,
		VOICEBUS_SFRAME_SIZE + sizeof(*hdr) + NET_IP_ALIGN);
	if (!skb)
		return NULL;

	skb_reserve(skb, NET_IP_ALIGN);
	skb->dev = netdev;
	hdr = (struct voicebus_net_hdr *)skb_put(skb, VOICEBUS_SFRAME_SIZE +
						 sizeof(*hdr));
	/* Fill in the source and destination mac addresses appropriately
	 * depending on whether this is a packet we are transmitting or a packet
	 * that we have received. */
	if (tx) {
		memcpy(hdr->ethhdr.h_dest, BOARD_MAC, sizeof(BOARD_MAC));
		memcpy(hdr->ethhdr.h_source, HOST_MAC, sizeof(HOST_MAC));
		hdr->seq_num = cpu_to_be16(atomic_inc_return(
			&vb->tx_seqnum));
	} else {
		memcpy(hdr->ethhdr.h_dest, HOST_MAC, sizeof(HOST_MAC));
		memcpy(hdr->ethhdr.h_source, BOARD_MAC, sizeof(BOARD_MAC));
		hdr->seq_num = cpu_to_be16(atomic_inc_return(
			&vb->rx_seqnum));
	}
	memset(hdr->filler, 0, sizeof(hdr->filler));
	hdr->des0 = cpu_to_be32(des0);
	hdr->tag = cpu_to_be16(tag);
	hdr->ethhdr.h_proto = htons(VOICEBUS_ETHTYPE);
	/* copy the rest of the packet. */
	memcpy(skb->data + sizeof(*hdr), vbb, VOICEBUS_SFRAME_SIZE);
	skb->protocol = eth_type_trans(skb, netdev);

	return skb;
}

/**
 * vb_net_capture_cmd - Send a vbb to the network stack.
 * @vb: Interface card received the command.
 * @vbb: Voicebus buffer to pass up..
 * @tx: 1 if this is a vbb that the driver is sending to the card.
 *
 */
void vb_net_capture_vbb(struct voicebus *vb, const void *vbb, const int tx,
			const u32 des0, const u16 tag)
{
	struct sk_buff *skb;
	struct net_device *netdev = vb->netdev;
	const int MAX_CAPTURED_PACKETS = 5000;

	if (!netdev)
		return;

	/* If the interface isn't up, we don't need to capture the packet. */
	if (!(netdev->flags & IFF_UP))
		return;

	if (skb_queue_len(&vb->captured_packets) > MAX_CAPTURED_PACKETS) {
		WARN_ON_ONCE(1);
		return;
	}

	skb = vbb_to_skb(netdev, vbb, tx, des0, tag);
	if (!skb)
		return;

	skb_queue_tail(&vb->captured_packets, skb);
#	if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
	netif_rx_schedule(netdev, &vb->napi);
#	elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30)
	netif_rx_schedule(&vb->napi);
#	else
	napi_schedule(&vb->napi);
#	endif
	return;
}

#endif
