/* iBurst (TM) compatible driver for Linux kernel 2.6
 * based on the original ArrayComm (TM) iBurst (TM) driver.
 * Nicholas Jefferson <xanthophile@gmail.com>
 * 11 May 2005
 *
 * Fixes by Nik Trevallyn-Jones.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include "ib-net.h"

#include <linux/etherdevice.h>
#include <linux/proc_fs.h>

static int debug = 0;
module_param(debug, int, 0);
#define DEBUG(n, args...) if ((n) < debug) printk(KERN_INFO args)

/**
 * ifname - interface name.
 */
static char *ifname = "ib%d";
module_param(ifname, charp, 0);

static spinlock_t ib_net_lock = SPIN_LOCK_UNLOCKED;

/**
 * ib_net_read - deliver received cell as ethernet frame.
 * @modem: modem state.
 * @nbuf: cell size.
 */
static void ib_net_read(struct ib_net_modem_t *modem, int nbuf)
{
	struct ib_net_cell_t *cell = (struct ib_net_cell_t*) modem->rx_buf;
	struct ib_net_packet_t *packet;
	struct sk_buff *skb;
	int len;

	modem->dev.last_rx = jiffies;
	len = nbuf - IB_NET_CELL_HDR;
	skb = alloc_skb(len + IB_NET_PACKET_HDR, GFP_ATOMIC);
	if (skb == NULL) {
		modem->stats.rx_dropped += 1;
		return;
	}
	skb->dev = &modem->dev;
	packet = (struct ib_net_packet_t*)
			skb_put(skb, len + IB_NET_PACKET_HDR);
	memcpy(packet->source, modem->dev.dev_addr, ETH_ALEN);
	if (cell->word[0] & 0x08)
		memset(packet->dest, 0xff, ETH_ALEN);
	else {
		packet->source[ETH_ALEN - 1] ^= 1;
		memcpy(packet->dest, modem->dev.dev_addr, ETH_ALEN);
	}
	packet->proto[0] = cell->proto[0];
	packet->proto[1] = cell->proto[1];
	memcpy(packet->data, cell->data, len);
	skb->protocol = eth_type_trans(skb, &modem->dev);
	skb->ip_summed = CHECKSUM_UNNECESSARY;
	netif_rx(skb);
}

/**
 * ib_net_parse - process received cell.
 * @modem: modem state.
 * @nbuf: cell size.
 */
void ib_net_parse(struct ib_net_modem_t *modem, int nbuf)
{
	struct ib_net_cell_t *cell = (struct ib_net_cell_t*) modem->rx_buf;

	modem->stats.rx_packets += 1;
	modem->stats.rx_bytes += nbuf;
	modem->rx_status = cell->status;
	if (cell->word[0] & 0x80)
		return;
	if (cell->word[1] ^ cell->check ^ 0xff)
		return;
	ib_net_read(modem, nbuf);
}

/**
 * ib_net_fill - prepare next cell for transmission.
 * @modem: modem state.
 */
int ib_net_fill(struct ib_net_modem_t *modem)
{
	struct sk_buff *skb;
	struct ib_net_cell_t *cell;
	int nqueue;

	nqueue = skb_queue_len(&modem->tx_queue);
	if (nqueue < 4 && modem->tx_throttle) {
		netif_wake_queue(&modem->dev);
		modem->tx_throttle = 0;
	}
	if (nqueue == 0)
		return 0;
	if (10 < nqueue && modem->tx_throttle == 0) {
		netif_stop_queue(&modem->dev);
		modem->tx_throttle = 1;
	}
	skb = skb_dequeue(&modem->tx_queue);
	if (skb == NULL)
		return 0;
	memcpy(modem->tx_buf, skb->data, skb->len);
	dev_kfree_skb(skb);
	cell = (struct ib_net_cell_t*) modem->tx_buf;
	cell->status = modem->stats.tx_packets & 0xff;
	modem->dev.trans_start = jiffies;
	if (nqueue < 4 && modem->tx_throttle) {
		netif_wake_queue(&modem->dev);
		modem->tx_throttle = 0;
	}
	return skb->len;
}

/**
 * ib_net_deregister - deregister network device.
 * @modem: modem state.
 */
void ib_net_deregister(struct ib_net_modem_t *modem)
{
	unregister_netdev(&modem->dev);
	kfree(modem);
}

/**
 * ib_net_poll - bottom half handler for interrupts.
 * @_modem: modem state.
 */
static void ib_net_poll(void *_modem)
{
	struct ib_net_modem_t *modem = _modem;
	u_long state;

	spin_lock_irqsave(&ib_net_lock, state);
	if (modem->driver->poll)
		modem->driver->poll(modem->instance);
	spin_unlock_irqrestore(&ib_net_lock, state);
}

/**
 * ib_net_stats - return network device statistics.
 * @dev: network device state.
 */
static struct net_device_stats *ib_net_stats(struct net_device *dev)
{
	struct ib_net_modem_t *modem = dev->priv;

	return &modem->stats;
}

/**
 * ib_net_tx_timeout - timeout handler.
 * @dev: network device state.
 */
static void ib_net_tx_timeout(struct net_device *dev)
{
	struct ib_net_modem_t *modem = dev->priv;
	u_long state;

	spin_lock_irqsave(&ib_net_lock, state);
	if (modem->driver->tx_timeout)
		modem->driver->tx_timeout(modem->instance);
	dev->trans_start = jiffies;
	spin_unlock_irqrestore(&ib_net_lock, state);
}

/**
 * ib_net_tx_start - convert ethernet frame to cell for transmission.
 * @skb: ethernet frame.
 * @dev: network device state.
 */
static int ib_net_tx_start(struct sk_buff *skb, struct net_device *dev)
{
	struct ib_net_packet_t *packet = (struct ib_net_packet_t*) skb->data;
	struct ib_net_modem_t *modem = dev->priv;
	struct ib_net_cell_t *cell;
	struct sk_buff *nskb;
	u_long state;
	int len;

	spin_lock_irqsave(&ib_net_lock, state);
	len = skb->len - IB_NET_PACKET_HDR;
	nskb = alloc_skb(len + IB_NET_CELL_HDR, GFP_ATOMIC);
	if (nskb == NULL) {
		spin_unlock_irqrestore(&ib_net_lock, state);
		return -ENOMEM;
	}
	nskb->len = len + IB_NET_CELL_HDR;
	cell = (struct ib_net_cell_t*) nskb->data;
	cell->word[0] = (len + IB_NET_CELL_HDR) >> 8;
	if (packet->dest[0] & 1)
		cell->word[0] |= 8;
	cell->word[1] = (len + IB_NET_CELL_HDR) & 0xff;
	cell->check = cell->word[1] ^ 0xff;
	cell->proto[0] = packet->proto[0];
	cell->proto[1] = packet->proto[1];
	memcpy(cell->data, packet->data, len);
	skb_queue_tail(&modem->tx_queue, nskb);
	kfree_skb(skb);
	if (modem->driver->poll)
		modem->driver->poll(modem->instance);
	spin_unlock_irqrestore(&ib_net_lock, state);
	return 0;
}

/**
 * ib_net_open - open network device.
 * @dev: network device state.
 */
static int ib_net_open(struct net_device *dev)
{
	struct ib_net_modem_t *modem = dev->priv;
	u_long state;

	spin_lock_irqsave(&ib_net_lock, state);
	netif_start_queue(dev);
	modem->status |= STATUS_READY;
	spin_unlock_irqrestore(&ib_net_lock, state);
	return 0;
}

/**
 * ib_net_close - close network device.
 * @dev: network device state.
 */
static int ib_net_close(struct net_device *dev)
{
	struct ib_net_modem_t *modem = dev->priv;
	u_long state;

	spin_lock_irqsave(&ib_net_lock, state);
	if (modem->status & STATUS_READY) {
		netif_stop_queue(dev);
		modem->status &= ~STATUS_READY;
		if (modem->driver->stop)
			modem->driver->stop(modem->instance);
	}
	spin_unlock_irqrestore(&ib_net_lock, state);
	return 0;
}

/**
 * ib_net_ioctl - ioctl not supported.
 * @dev: network device state.
 * @rq: interface request ioctl.
 * @cmd: command code.
 */
static int ib_net_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
	return -EOPNOTSUPP;
}

/**
 * ib_net_register - register network device.
 * @name: return device name.
 */
struct ib_net_modem_t *ib_net_register(u_char *name)
{
	struct ib_net_modem_t *modem;
	struct net_device *dev;

	modem = kmalloc(sizeof(struct ib_net_modem_t), GFP_KERNEL);
	if (modem == NULL)
		return NULL;
	dev = &modem->dev;
	memset(dev, 0, sizeof(struct net_device)); /* hack */
	dev->priv = modem;
	SET_MODULE_OWNER(dev);
	ether_setup(dev);
	dev->flags = IFF_BROADCAST | IFF_DYNAMIC | IFF_NOARP;
	dev->do_ioctl = ib_net_ioctl;
	dev->get_stats = ib_net_stats;
	dev->hard_start_xmit = ib_net_tx_start;
	dev->open = ib_net_open;
	dev->stop = ib_net_close;
	dev->trans_start = jiffies;
	dev->tx_timeout = ib_net_tx_timeout;
	dev->watchdog_timeo = HZ * 10;
	strcpy(dev->name, ifname);
	if (register_netdev(dev)) {
		kfree(modem);
		return NULL;
	}
	if (name)
		strcpy(name, dev->name);
	netif_carrier_on(dev);
	memset(&modem->stats, 0, sizeof(struct net_device_stats));
	modem->status = 0;
	modem->tx_throttle = 0;
	memset(modem->tx_buf, 0, sizeof(modem->tx_buf));
	skb_queue_head_init(&modem->tx_queue);
	INIT_WORK(&modem->work_queue, ib_net_poll, modem);
	return modem;
}


/**
 * ib_net_init - ib-net module init.
 */
static int __init ib_net_init(void)
{
	return 0;
}

/**
 * ib_net_exit - ib-net module exit.
 */
static void __exit ib_net_exit(void)
{
}

module_init(ib_net_init);
module_exit(ib_net_exit);

EXPORT_SYMBOL(ib_net_register);
EXPORT_SYMBOL(ib_net_fill);
EXPORT_SYMBOL(ib_net_parse);
EXPORT_SYMBOL(ib_net_deregister);

MODULE_DESCRIPTION("iBurst compatible driver");
MODULE_LICENSE("GPL");
