/*
 *	802.2 LLC sockets. This is still DGRAM only as I haven't the
 *	desire to write 802.2 proper. Some other maniac can do this.
 *
 *	Authors:	Alan Cox <iialan@iifeak.swan.ac.uk>
 *	
 * 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 pointer 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_llc.h>

#ifdef CONFIG_LLC_8022

extern struct protocol proto_8022;		/* Fixme into an include file */


/*
 *	Things you do differently with an ethernet socket.
 */
 
static int llc_bind(struct socket *sock, struct sockaddr *umyaddr, int sockaddr_len)
{
	int err;
	struct sock *sk=sock->protocol;
	struct protocol *p;
	struct device *dev;
	unsigned long flags;
	int pnew=0;
	
	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 802.2 isn't extant for this device push 802.2 onto the device automatically.
	 */
	
	p=protocol_find_instance("802.2",dev);
	if(p==NULL)
	{
		p=protocol_clone(&proto_8022);
		if(p==NULL)
		{
			restore_flags(flags);
			sk->opt.generic.bound=0;
			return -ENOBUFS;
		}
		err=protocol_bind(dev->mac_protocol, p, ETH_P_802_2,0);
		if(err)
		{
			restore_flags(flags);
			sk->opt.generic.bound=0;
			return -ENOBUFS;
		}
		pnew=1;
		
	}	
	
	/*
	 *	Push generic socket support on to us.
	 */
	 		
	sk->opt.generic.protocol=protocol_clone(&proto_generic);
	if(sk->opt.generic.protocol==0)
	{
		if(pnew==1)
			protocol_delete(p);
		restore_flags(flags);
		sk->opt.generic.bound=0;
		return -ENOBUFS;
	}
	err=protocol_bind(p, sk->opt.generic.protocol, sk->num, 0);
	restore_flags(flags);
	if(err)
	{
		if(pnew)
			protocol_delete(p);
		protocol_delete(sk->opt.generic.protocol);
		sk->opt.generic.bound=0;
		return err;
	}
	sk->opt.generic.lower=p;
	return 0;
}

static int llc_connect(struct socket *sock, struct sockaddr *addr, int addr_len, int nonblock)
{
	struct sockaddr_llc *sl=(struct sockaddr_llc *)addr;
	struct sock *sk=sock->protocol;
	if(sk->opt.generic.bound==0)
		return -EINVAL;
	if(addr_len!=sizeof(*sl))
		return -EINVAL;
	if(sl->sllc_family!=AF_8022)
		return -EAFNOSUPPORT;
	memcpy(sk->opt.generic.address, sl->sllc_addr, ETH_ALEN);	/* ETH_ALEN is a simplification fix for token ring/bus */
	sk->state=TCP_ESTABLISHED;
	return 0;
}

static int llc_sendmsg(struct socket *sock, struct msghdr *msg, int len, int nonblock, int flags)
{
	struct sockaddr_llc *sl=(struct sockaddr_llc *)msg->msg_name;
	struct sock *sk=sock->protocol;
	sk_buff *skb;
	unsigned char *addr_ptr=sk->opt.generic.address;
	int err;
	
	if(len>1500)		/* Hack: Need to drop in protocol->mtu values */
		return -EMSGSIZE;
		
	if(addr_ptr!=NULL)
	{
		if(msg->msg_namelen!=sizeof(*sl))
			return -EINVAL;
		if(sl->sllc_family!=AF_8022)
			return -EAFNOSUPPORT;
		addr_ptr=sl->sllc_addr;
	}
	else
	{
		if(sl==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);	
	
	/*
	 *	Throw at 802.2 then 802.3 
	 */
	 
	sk->opt.generic.lower->output(sk->opt.generic.lower,skb, sk->num, 0, NULL, addr_ptr, NULL);
	return len;
}

/*
 *	LLC specific creation rules.
 */
 
static int llc_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_8022;
	
	/*
	 *	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 llc_release(struct socket *sock, struct socket *peer)
{
	struct sock *sk=sock->protocol;
	
	/*
	 *	Free up any bound protocols. Freeing the generic will delete it. This will delete the 802.2
	 *	if it is also free.
	 */
	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 llc_ops=
{
	AF_8022,
	llc_create,
	gs_dup,
	llc_release,
	llc_bind,
	llc_connect,
	gs_socketpair,
	gs_accept,
	gs_getname,
	gs_select,
	gs_ioctl,
	gs_listen,
	gs_shutdown,
	gs_setsockopt,
	gs_getsockopt,
	gs_fcntl,
	llc_sendmsg,
	gs_recvmsg
};


void llc_init(void)
{
	printk("NET3: 802.2 LLC sockets version 0.03 ALPHA\n");
	sock_register(llc_ops.family, &llc_ops);
}


#endif /* CONFIG_LLC_8022 */
