/*
 *	Blue book ethernet sockets.
 *
 *	Authors:	Alan Cox <iialan@iifeak.swan.ac.uk>
 *	
 *	Again this doesn't work like the old SOCK_PACKET as it
 *	is trying to be more streams related. Once the streams
 *	support works that will be the preferred access method.
 *
 * 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.
 *
 * Fixes:
 *		Andrew Lunn	:	Missing NULL check
 * 
 */
 
#include <linux/types.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/config.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/errno.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include "sock.h"
#include "sockgeneric.h"
#include <linux/if_nit.h>

#ifdef CONFIG_DIX

/*
 *	Things you do differently with an ethernet socket.
 */
 
static int ether_bind(struct socket *sock, struct sockaddr *umyaddr, int sockaddr_len)
{
	int err;
	struct sock *sk=sock->protocol;
	struct device *dev;
	unsigned long flags;
	
	if(sk->opt.generic.bound)
		return -EISCONN;
	err=gs_bind(sock,umyaddr,sockaddr_len);		/* Bind takes a device */
	if(err)
		return err;
		
	sk->opt.generic.bound=1;
	save_flags(flags);				/* Avoid any device deactivation races (can't occur anyway but) */
	cli();
	dev=dev_get(sk->opt.generic.device);
	if(dev==NULL)
	{
		restore_flags(flags);
		sk->opt.generic.bound=0;
		return -ENODEV;
	}
	if(dev->type!=ARPHRD_ETHER)
	{
		restore_flags(flags);
		sk->opt.generic.bound=0;
		return -EPROTONOSUPPORT;		/* Driver given isn't ethernet */
	}
	sk->opt.generic.protocol=protocol_clone(&proto_generic);
	if(sk->opt.generic.protocol==0)
	{
		restore_flags(flags);
		sk->opt.generic.bound=0;
		return -ENOBUFS;
	}
	err=protocol_bind(dev->mac_protocol, sk->opt.generic.protocol, sk->num, 0);
	restore_flags(flags);
	if(err)
	{
		protocol_delete(sk->opt.generic.protocol);
		sk->opt.generic.bound=0;
		return err;
	}
	sk->opt.generic.lower=dev->mac_protocol;
	return 0;
}

static int ether_connect(struct socket *sock, struct sockaddr *addr, int addr_len, int nonblock)
{
	struct sockaddr_ether *se=(struct sockaddr_ether *)addr;
	struct sock *sk=sock->protocol;
	if(sk->opt.generic.bound==0)
		return -EINVAL;
	if(addr_len!=sizeof(*se))
		return -EINVAL;
	if(se->sether_family!=AF_DIX)
		return -EAFNOSUPPORT;
	memcpy(sk->opt.generic.address, se->sether_addr, ETH_ALEN);
	sk->state=TCP_ESTABLISHED;
	return 0;
}

static int ether_sendmsg(struct socket *sock, struct msghdr *msg, int len, int nonblock, int flags)
{
	struct sockaddr_ether *se=(struct sockaddr_ether *)msg->msg_name;
	struct sock *sk=sock->protocol;
	sk_buff *skb;
	unsigned char *addr_ptr=sk->opt.generic.address;
	int err;
	
	if(len>1500)
		return -EMSGSIZE;
		
	if(msg->msg_name!=NULL)
	{
		if(msg->msg_namelen!=sizeof(*se))
			return -EINVAL;
		if(se->sether_family!=AF_DIX)
			return -EAFNOSUPPORT;
		addr_ptr=se->sether_addr;
	}
	else
	{
		if(se==NULL)
			return -ENOTCONN;
	}
	
	if(flags)
		return -EINVAL;
	if(sk->opt.generic.bound==0)
		return -ENOTCONN;

	if(sk->shutdown&SEND_SHUTDOWN)
		return -EPIPE;
				
	skb=sock_alloc_send_skb(sk, len+protocol_size(sk->opt.generic.lower), nonblock, &err);
	if(skb==NULL)
		return err;
	protocol_adjust(skb, sk->opt.generic.lower);
	memcpy_fromiovec(skb_put(skb,len),msg->msg_iov,len);	
	sk->opt.generic.lower->output(sk->opt.generic.lower,skb, sk->num, 0, NULL, addr_ptr, NULL);
	return len;
}

/*
 *	Ethernet specific creation rules.
 */
 
static int ether_create(struct socket *sock, int protocol)
{
	struct sock *sk;

	if(!suser())
		return -EPERM;
		
	if(protocol<1514)
		return -EPROTONOSUPPORT;
	
	if(gs_create(sock, protocol)==-1)
		return -EINVAL;

	sk=sock->protocol;
	sk->opt.generic.family=AF_DIX;
	
	/*
	 *	Now attach ourselves.
	 */

	sk->opt.generic.protocol=NULL;
	sk->opt.generic.lower=NULL;
	sk->opt.generic.bound=0;	 
	sk->state=TCP_CLOSE;
	return 0;
}


/*
 *	Release a socket. Unbind the link which will free the
 *	cloned gensocket protocol layer, then let the generic
 *	socket releaser do its work.
 */
 
static int ether_release(struct socket *sock, struct socket *peer)
{
	struct sock *sk=sock->protocol;
	
	/*
	 *	Free up any bound protocols 
	 */
	if(sk && sk->opt.generic.bound)
		protocol_unbind(sk->opt.generic.lower, sk->opt.generic.protocol, sk->num, 0);
	/*
	 *	Do the generic release job
	 */
	return gs_release(sock,peer);
}
	

static struct proto_ops ether_ops=
{
	AF_DIX,
	ether_create,
	gs_dup,
	ether_release,
	ether_bind,
	ether_connect,
	gs_socketpair,
	gs_accept,
	gs_getname,
	gs_select,
	gs_ioctl,
	gs_listen,
	gs_shutdown,
	gs_setsockopt,
	gs_getsockopt,
	gs_fcntl,
	ether_sendmsg,
	gs_recvmsg
	
};


void dix_init(void)
{
	printk("NET3: Raw ethernet sockets 0.02 for Linux NET3.025\n");
	sock_register(ether_ops.family, &ether_ops);
}


#endif /* CONFIG_DIX */