/*
 * INET		An implementation of the TCP/IP protocol suite for the LINUX
 *		operating system.  INET is implemented using the  BSD Socket
 *		interface as the means of communication with the user level.
 *
 *		Ethernet-type device handling.
 *
 * Version:	@(#)eth.c	1.0.7	05/25/93
 *
 * Authors:	Ross Biro, <bir7@leland.Stanford.Edu>
 *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
 *		Mark Evans, <evansmp@uhura.aston.ac.uk>
 *		Florian  La Roche, <rzsfl@rz.uni-sb.de>
 *		Alan Cox, <gw4pts@gw4pts.ampr.org>
 * 
 * Fixes:
 *		Mr Linux	: Arp problems
 *		Alan Cox	: Generic queue tidyup (very tiny here)
 *		Alan Cox	: eth_header ntohs should be htons
 *		Alan Cox	: eth_rebuild_header missing an htons and
 *				  minor other things.
 *		Tegge		: Arp bug fixes. 
 *		Florian		: Removed many unnecessary functions, code cleanup
 *				  and changes for new arp and skbuff.
 *		Alan Cox	: Redid header building to reflect new format.
 *		Alan Cox	: ARP only when compiled with CONFIG_INET.
 *		Greg Page	: 802.2 and SNAP stuff.
 *		Alan Cox	: Changed to protocol layering system.
 *		Alan Cox	: One 802.2 layer per device.
 *
 *		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 <asm/segment.h>
#include <asm/system.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/errno.h>
#include "arp.h"


void eth_setup(char *str, int *ints)
{
	struct device *d = dev_base;

	if (!str || !*str)
		return;
	while (d) 
	{
		if (!strcmp(str,d->name)) 
		{
			if (ints[0] > 0)
				d->irq=ints[1];
			if (ints[0] > 1)
				d->base_addr=ints[2];
			if (ints[0] > 2)
				d->mem_start=ints[3];
			if (ints[0] > 3)
				d->mem_end=ints[4];
			break;
		}
		d=d->next;
	}
}


/*
 *	 Create the Ethernet MAC header for an arbitrary protocol layer 
 *
 *	saddr=NULL	means use device source address
 */

static int eth_output(struct protocol *self, sk_buff *skb, int type, int subid, void *saddr, void *daddr, void *opt)
{
	struct ethhdr *eth = (struct ethhdr *)skb_push(skb,sizeof(struct ethhdr));
	
	if(skb->dev==NULL)
	{
		printk("eth_output: no device!\n");
		kfree_skb(skb, FREE_WRITE);
		return -1;
	}
	if(eth==NULL)
	{
		printk("eth_output: no room!\n");
		kfree_skb(skb, FREE_WRITE);
		return -1;
	}

	eth->h_proto = htons(type);

	/*
	 *	Set the source hardware address. 
	 */
	 
	if(saddr)
		memcpy(eth->h_source,saddr,skb->dev->addr_len);
	else
		memcpy(eth->h_source,skb->dev->dev_addr,skb->dev->addr_len);
	memcpy(eth->h_dest,daddr,skb->dev->addr_len);
	dev_queue_xmit(skb, skb->dev, skb->priority);
	return 0;
}

/*
 *	802.3/DIX dual input handler. 802.3 data goes to the 802.2 protocol layer
 *	after we remove the 802.3 header and trim the frame. DIX is demultiplexed.
 */
 
static int eth_input(struct protocol *lower, struct protocol *below, sk_buff *skb, void *saddr, void *daddr)
{
	int sz;
	unsigned short proto;
	struct ethhdr *eth;
	
	skb->h.raw=skb_data(skb);	/* Save the raw pointer */
	
	eth=(struct ethhdr *)skb_pull(skb,sizeof(*eth),&sz);
	if(sz!=sizeof(*eth))
	{
		kfree_skb(skb,FREE_READ);
		return -EINVAL;
	}
	/*
	 *	RFC 1122: 2.4	MUST include a flag to indicate the frame was a link layer broadcast.
	 */
	 
	/* Ethernet multicast destination */
	
	if((*eth->h_dest)&1)
	{
		if(memcmp(eth->h_dest,skb->dev->broadcast, ETH_ALEN)==0)
			skb->pkt_type=PACKET_BROADCAST;
		else
			skb->pkt_type=PACKET_MULTICAST;
	}
	else if(skb->dev->flags&IFF_PROMISC)
	{
		if(memcmp(eth->h_dest,skb->dev->dev_addr, ETH_ALEN))
			skb->pkt_type=PACKET_OTHERHOST;
	}
	
	if((proto=ntohs(eth->h_proto))<=1500)
		proto=ETH_P_802_2;		/* Magic for binding 802.3 */
		
	if(protocol_pass_demultiplex(lower,&proto,skb, eth->h_source, eth->h_dest)==0)
	{
		kfree_skb(skb, FREE_READ);
		return -EPROTONOSUPPORT;
	}
	return 0;
}


static int eth_get_key(int protocol, int subid, unsigned char *key)
{
	unsigned short pr=protocol;
	if(protocol<1536 && protocol!=ETH_P_802_2)
		return -EAFNOSUPPORT;
	if(protocol==ETH_P_802_2)
		memcpy(key,"\x00\x04",2);
	else
		memcpy(key, &pr, 2);
	return 2;
}

#ifdef CONFIG_FAST_PATH

/*
 *	This only gets called for 802.3 frames
 */
 
static int eth_fast_output(sk_buff *skb, char *dp, int priority)
{
	struct ethhdr *eth;
	dp-=sizeof(struct ethhdr);
	eth=(struct ethhdr *dp);
	eth->h_type=htons(skb->len);
	dev_queue_xmit(skb, skb->dev, priority);
	return 0;
}

static int eth_build_fast(struct device *dev,int slot, char *dp, int type, int subid, void *saddr, void *daddr, void *protoopts)
{
	struct ethhdr *eh=(struct ethhdr *)(dp-sizeof(*eh));
	if(saddr==NULL)
		memcpy(eh->source,dev->dev_addr, ETH_ALEN);
	else
		memcpy(eh->h_source, saddr, ETH_ALEN);
	memcpy(eh->h_dest, daddr, ETH_ALEN);
	if(type==ETH_P_8023)
		return 1;	/* Our layer is lowest and needs to fill in 1 thing */
	eh->h_type=htons(type);
	return 0;		/* Fixed */
}

#endif

struct protocol proto_dix=
{
	NULL,
	"Blue Book",
	sizeof(struct ethhdr),
	0,
	sizeof(struct ethhdr),
	0,
	eth_output,
#ifdef CONFIG_FAST_PATH	
	eth_fast_output,
	eth_fast_build,
#endif	
	eth_input,
	eth_input,		/* No bottom half input handler */
	default_protocol_control,
	eth_get_key,
	NULL,
	NULL,
};


void ether_proto_init(void)
{
	protocol_register(&proto_dix);
}
