/*
 * llc_supp.c - Contains three category of functions 
 * 			1- functions that implement Link Service State Machine
 * 			2- functions that simplify LLC interface layer services
 *			3- dispatching functions
 *
 * Copyright (c) 1997 by Procom Technology,Inc.
 *
 * This program can be redistributed or modified under the terms of the 
 * GNU General Public License as published by the Free Software Foundation.
 * This program is distributed without any warranty or implied warranty
 * of merchantability or fitness for a particular purpose.
 *
 * See the GNU General Public License for more details.
 *
 */
 

#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/errno.h>
#define __KERNEL_SYSCALLS__
#include <linux/unistd.h>
#include <net/cm_types.h>
#include <net/llc_if.h>
#include <net/cm_dll.h>
#include <net/cm_mm.h>
#include <net/cm_frame.h>
#include <net/llc_sap.h>
#include <linux/netbeui.h>


static void nbll_timer_function(unsigned long);

/* These functions are Link State Transition handlers */
static int nbll_conn_indicate_in_initial(link_t *);
static int nbll_conn_request_in_connwait(link_t *);
static int nbll_dummy_conn_in_initial(link_t *);
static int nbll_conn_confirm_in_connwait(link_t *);
static int nbll_conn_indicate_in_connwait(link_t *);
static int nbll_conn_reject_in_connwait(link_t *);
static int nbll_disc_request_in_connwait(link_t *);
static int nbll_reset_indicate_in_up(link_t *);
static int nbll_session_alive_in_up(link_t *);
static int nbll_disc_request_in_up(link_t *);
static int nbll_disc_indicate_in_up(link_t *);


static int   AUTO_BIND = 0;
#define NetBEUI_SAP_NO 	0xF0

/* Contains hardcoded NetBIOS Frame Header length */
u8 nb_command_header_len[]=  
{ 
	0x2C, 0x2C, 0x2C, 0x2C, 0x00, 0x00, 0x00, 0x2C, 
	0x2C, 0x2C, 0x2C, 0x00, 0x00, 0x2C, 0x2C, 0x2C,
	0x00, 0x00, 0x00, 0x2C, 0x0E, 0x0E, 0x0E, 0x0E,
	0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x00, 0x00, 0x0E
};


/* 
 * LLC is not re-entrant service to uppper layer (NetBEUI)
 * This flag, set upon entering LLC code, prevents NetBEUI to re-enter the
 * code when its timers fire.
 */
volatile int llc_in_progress= 0;


/* This is the communication medium between LLC and NetBEUI */
static sap_t * netbeui_sap;


/* This is a list of adapters NetBEUI is bound to. */
struct device   *adapters[NB_MAX_ADAPTERS];	

/* Counts members of 'adapters[]' array */
unsigned short   binded_adapters_count;

/* LLC links to other network nodes */
static dextab_t link_table= {NULL, 0, 0, NB_MAX_LINKS, 0}; 

#define link_table_entry(i)  ((link_t *)link_table.addr[i])



/*
 * Link service state machine definition
 */
typedef int (* link_event_handler_t)(link_t *);


struct event_struct {
	link_state_t  next_state;
	link_event_handler_t event_handler;
};

static struct event_struct 
link_state_table[3][9]= {
			/* NB_LINK_INITIAL  */
{
{NB_LINK_UP, nbll_conn_indicate_in_initial},	/* NB_LINK_CONN_INDICATE  */
{-1, NULL},					/* NB_LINK_CONN_REQUEST   */
{NB_LINK_CONNWAIT, nbll_dummy_conn_in_initial}, /* NB_LINK_DUMMY_CONN     */
{-1, NULL},					/* NB_LINK_CONN_CONFIRM   */
{-1, NULL},					/* NB_LINK_CONN_REJECT    */
{-1, NULL},					/* NB_LINK_RESET_INDICATE */
{-1, NULL}, 					/* NB_LINK_SESSION_ALIVE  */
{-1, NULL},					/* NB_LINK_DISC_REQUEST   */
{-1, NULL}					/* NB_LINK_DISC_INDICATE  */
},
			/* NB_LINK_CONNWAIT */
{
{NB_LINK_UP, nbll_conn_indicate_in_connwait},	  /* NB_LINK_CONN_INDICATE  */
{NB_LINK_CONNWAIT, nbll_conn_request_in_connwait},/* NB_LINK_CONN_REQUEST   */
{-1, NULL},					  /* NB_LINK_DUMMY_CONN     */
{NB_LINK_UP, nbll_conn_confirm_in_connwait},	  /* NB_LINK_CONN_CONFIRM   */
{NB_LINK_INITIAL, nbll_conn_reject_in_connwait},  /* NB_LINK_CONN_REJECT    */
{-1, NULL},					  /* NB_LINK_RESET_INDICATE */
{-1, NULL}, 					  /* NB_LINK_SESSION_ALIVE  */
{NB_LINK_INITIAL, nbll_disc_request_in_connwait}, /* NB_LINK_DISC_REQUEST   */
{-1, NULL}					  /* NB_LINK_DISC_INDICATE  */
},
			  /* NB_LINK_UP */
{
{-1, NULL},					/* NB_LINK_CONN_INDICATE  */
{-1, NULL},					/* NB_LINK_CONN_REQUEST   */
{-1, NULL},					/* NB_LINK_DUMMY_CONN     */
{-1, NULL},					/* NB_LINK_CONN_CONFIRM   */
{-1, NULL},					/* NB_LINK_CONN_REJECT    */
{NB_LINK_UP, nbll_reset_indicate_in_up},	/* NB_LINK_RESET_INDICATE */
{NB_LINK_UP, nbll_session_alive_in_up}, 	/* NB_LINK_SESSION_ALIVE  */
{NB_LINK_INITIAL, nbll_disc_request_in_up},	/* NB_LINK_DISC_REQUEST   */
{NB_LINK_INITIAL, nbll_disc_indicate_in_up}	/* NB_LINK_DISC_INDICATE  */
}
};



/*
 * LLC interface functions
 * Data-out functions
 */

/*
 * Function: nbll_isend_link
 *	This is the lowest level LLC interface function in sending and I-Frame
 *	It prepares a LLC frame structrue, considers LLC re-entrancy problems
 *	and deceides on LLC return value to handle many special cases.
 *	The ugly bottom of sourcei, copes with many race conditions enforced by
 *	implementation limits.
 *
 * Parameters:
 *	rdev	  : pointer to struct device or adapter the frame is sent to.
 *		    This parameter is designed to minimize critical regions
 *	llc_handle: LLC handle of remote machine (is a pointer to LLC data)
 *	skb	  : the pointer to sk_buff containing data to send
 *	qflag	  : queuing flag. NetBEUI links control flow of data to LLC
 *		    layer using a skb queue. Depending on the situation we
 *		    are in, an unsuccessful try in delivering frame to LLC
 *		    should queue the frame for later trials. This flag controls
 *		    how this function should queue the skb.
 *			0    : queue unsuccessful skb in tail and return 0
 *			other: queue unsuccessful skb in head and return non-zero
 *
 * Returns: 
 *	0	  : if LLC accepts the frame or frame is queue in Link for later trial
 *	qflag<> 0 : if queues frame for later trial
 *	-ENOMEM	  : If cannot allocate a frame_t from LLC pools
 *	non-zero  : any other error value returned by LLC
 *	
 * Notes:
 *	- Any modification to this function highly affects the whole system
 *
 *	- A special race condition is an interrupt occured when LLC returns from
 *	  sap_reuqest to NetBEUI. to cover it a critical region is created before
 *	  LLC return statement in LLC code and is destroyed after sap_request 
 *	  statement in NetBEUI code. This is why you may see wonderful save
 *	  and restore flag calls in the code.
 */

static inline int 
nbll_isend_link(struct device *rdev, unsigned long llc_handle, 
		struct sk_buff *skb, unsigned char link_no, int qflag)
{    
	frame_t *frame;
	prim_data_u prim_data;
	prim_if_block_t prim;   

	if (frame_allocate(&frame) != 0)
		return -ENOMEM;

	prim.data= &prim_data;

	if (memcmp(rdev->name, "tr", 2) == 0)
		frame->mac_type= ETH_P_TR_802_2;
	else
		frame->mac_type= ETH_P_802_2;
	skb->protocol= frame->mac_type;
	skb->dev= rdev;
	frame->dev= (void *)rdev;
	frame->skb= (void *)skb;
	frame->free_frame_flag= YES;
	frame->data_size= 0;
	frame->mac_hdr= skb->head;
	
	prim_data.data.priority= 0;
	prim_data.data.connect_handle= llc_handle;
	prim_data.data.unit= (us8 *)frame;

	prim.sap= (us32)netbeui_sap;
	prim.primitive= DATA_PRIM;

	{
	int retval;
restart:
	save_flags(prim_data.data.flags);

	llc_in_progress= 1;
	barrier();
	retval= netbeui_sap->request(&prim);
	llc_in_progress= 0;
	barrier();

	switch(retval)
		{
		case 0:
			restore_flags(prim_data.data.flags);
			return retval;
		case -ERESTART:
			restore_flags(prim_data.data.flags);
			goto restart;
		case -EBUSY:
			{
			link_t *nb_link;
			if (!(nb_link = link_table_entry(link_no)))
				{
				kfree_skb(skb, 0);
				restore_flags(prim_data.data.flags);
				return -ECONNABORTED;
				}

			nb_link->llc_busy= 1;
			
			if (qflag==0)
				__skb_queue_tail(&nb_link->skbq, skb);
			else
				__skb_queue_head(&nb_link->skbq, skb);
				
			restore_flags(prim_data.data.flags);
			frame_free(frame);
			return qflag;
			}
		}
	restore_flags(prim_data.data.flags);
	frame_free(frame);
	kfree_skb(skb, 0);
	return retval;
	}
}


/*
 * Function: nbll_isend
 *	This is the interface routine that actually tries to send I-Frames
 *	on an LLC connection. 
 *
 * Parameters:
 *	link_no	: An integer containing link number in link table
 *		  The function relies on correctness of value.
 *	skb	: pointer to sk_buff containing data to send
 *
 * Returns:
 *	0	: if LLC accepts frame or it is queued for later trial
 *	-ECONNABORTED: If connection is aborted by remote peer
 *	-ENOMEM	: If cannot allocate a frame_t from LLC pools
 *	non-zero: any other error value returned by LLC
 *
 * Notes:
 *	- Since I intended to call this function freely in
 *	  upper layer codes, it contains a critical region checking LINK consistency
 */

int
nbll_isend(int link_no, struct sk_buff *skb)
{
	unsigned long flags;
	link_t *nb_link;
	struct device *link_remote_dev;
	unsigned long link_llc_handle;

	save_flags(flags);
	cli();
	nb_link= link_table_entry(link_no);
	if (!nb_link)
		{
		restore_flags(flags);
		kfree_skb(skb, 0);
		return -ECONNABORTED;
		}
	if (nb_link->llc_busy == 1)
		{
		__skb_queue_tail(&nb_link->skbq, skb);
		restore_flags(flags);
		return 0;
		}

	nb_link->iactivity ++;
	link_remote_dev= nb_link->remote_dev;
	link_llc_handle= nb_link->llc_handle;
	restore_flags(flags);
	
	return nbll_isend_link(link_remote_dev, link_llc_handle, skb, link_no, 0);
}


/*
 * Function: nbll_uisend_mac
 *	This is the lowest level LLC interface function in sending and UI-Frame
 *
 * Parameters:
 *	remote_mac: pointer to MAC address of destination node or NetBIOS
 *		    functional address of device the frame is sent to.
 *	skb	  : pointer to sk_buff containing data to send
 *
 * Returns: 
 *	0	: if LLC accepts frame
 *	-ENOMEM	: If cannot allocate a frame_t from LLC pools
 *	non-zero: any other error value returned by LLC
 */

static int 
nbll_uisend_mac(unsigned char *remote_mac, struct sk_buff *skb)
{
	frame_t *frame;
	prim_data_u prim_data;
	prim_if_block_t prim;   

	if (frame_allocate(&frame) != 0)
		return -ENOMEM;

	prim.data= &prim_data;

	if (memcmp(skb->dev->name, "tr", 2) == 0)
		frame->mac_type= ETH_P_TR_802_2;
	else
		frame->mac_type= ETH_P_802_2;
	skb->protocol= frame->mac_type;
	frame->skb= (void *)skb;
	frame->dev= (void *)skb->dev;
	frame->free_frame_flag= YES;

	prim_data.udata.source_addr.lsap= NetBEUI_SAP_NO;
	memcpy(prim_data.udata.source_addr.mac, skb->dev->dev_addr, skb->dev->addr_len);
	prim_data.udata.dest_addr.lsap= NetBEUI_SAP_NO;
	memcpy(prim_data.udata.dest_addr.mac, remote_mac, skb->dev->addr_len);
	prim_data.udata.unit= (us8 *)frame;

	prim.sap= (us32)netbeui_sap;
	prim.primitive= DATAUNIT_PRIM;

	return netbeui_sap->request(&prim);
}


/*
 * Function: nbll_uisend
 *	This is the interface routine that actually tries to send UI-Frames
 *	on an LLC connection. 
 *
 * Parameters:
 *	remote_mac: pointer to MAC address of destination system or NULL
 *		    to indicate broadcast to all NICs NetBEUI is bound to
 *	skb	  : pointer to sk_buff containing data to send
 *
 * Returns:
 *	0	: if LLC accepts frame
 *	-ENOMEM	: If cannot allocate a frame_t from LLC pools in nbll_uisend_mac
 *		  or cannot create a copy from frame.
 *	non-zero: any other error value returned by LLC
 */

int 
nbll_uisend(unsigned char *remote_mac, struct sk_buff *skb)
{
	int i;

	/* If datagram has a destination */
	if (remote_mac)   
		return nbll_uisend_mac( remote_mac, skb);

	/* Datagram is to be broadcasted to all interfaces */
	for (i=0; (i<NB_MAX_ADAPTERS) && (adapters[i]); i++)
		{
		struct sk_buff *skb2;

		skb2= skb_copy(skb, GFP_ATOMIC);

		if (!skb2)
		    return -ENOMEM;

		skb2->dev= adapters[i];
		nbll_uisend_mac( NB_FUNCADDR(adapters[i]), skb2);
		}

	return 0;
}



/*
 * Link service state machine functions
 * Implementing general functions
 */

/*
 * Function: nbll_alloc_link
 *	Allocates and Iniitailizes a link_t structure
 *
 * Parameters: none
 *
 * Returns: 
 *	NULL	: if can not allocate link_t structure
 *	non-NULL: if link_t is allocated and initialized
 *
 * Note:
 *	- Link timer is initailized but not started
 *	- The call to memset does implicitly initialize all fields. Those 
 *	  fileds that need explicit non-zero initialization are manipulated
 *	  afterwards.
 */

static inline link_t *
nbll_alloc_link(void)
{
	link_t *nb_link= kmalloc(sizeof(link_t), GFP_ATOMIC);

	if (!nb_link)
		return NULL;

	/* Implictly initialize all fields */
	memset(nb_link, 0, sizeof(link_t));    
	nb_link->state= NB_LINK_INITIAL;
	nb_link->link_no= -1;
	dextab_init(&nb_link->session_table, 1, NB_MAX_SESSIONS);

	skb_queue_head_init(&nb_link->skbq);

	init_timer(&nb_link->timer);
	nb_link->timer.data= (unsigned long)nb_link;
	nb_link->timer.function= nbll_timer_function;

	return nb_link;
}


/*
 * Function: nbll_free_link
 *	Does a complete housekeeping for a link including queue/table/...
 *
 * Parameters:
 *	nb_link	: pointer to link_t to destruct
 *
 * Returns: none
 */

static inline void
nbll_free_link( link_t *nb_link)
{
	struct sk_buff *skb;

	while ((skb= skb_dequeue(&nb_link->skbq)))
		kfree_skb(skb, 0);

	dextab_destruct(&nb_link->session_table);
	kfree(nb_link);
}


/*
 * Function: nbll_insert_link_into_table
 *	Inserts a previously allocated/initialized link into system link table
 *
 * Parameters:
 *	nb_link	: pointer to link_t to insert 
 *
 * Returns: none
 *	0	: if nb_link is inserted into system link_table
 *	-ENOSPC : if link_table is full
 */

static inline int
nbll_insert_link_into_table(link_t *nb_link)
{
	int link_no= dextab_insert_entry(&link_table, nb_link);

	if (link_no < 0)
		return -ENOSPC;

	nb_link->link_no= link_no;

	return 0;
}


/*
 * Function: nbll_delete_link_from_table
 *	Deletes a link from system link table
 *
 * Parameters:
 *	nb_link	: pointer to link_t to delete
 *
 * Returns: none
 */

static inline void
nbll_delete_link_from_table(link_t *nb_link)
{
	dextab_delete_index(&link_table, nb_link->link_no);

	nb_link->link_no= -1;

	return;
}


/*
 * Function: nbll_find_link
 *	Finds a link in system link table to a remote node from its mac and 
 *	the device we are connected via
 *
 * Parameters:
 *	dev	  : pointer to struct device we are connected to remote node via
 *	remote_mac: pointer to MAC address of remote node we are connected to
 *
 * Returns:
 *	NULL	  : if a corresponding link is not found
 *	non-NULL  : the link_t address of corresponding link
 */

static link_t *
nbll_find_link(struct device *dev, unsigned char *remote_mac)
{
	int index;
	link_t *nb_link;
	for (index=link_table.reserved; index<link_table.size; index++)
		{
		nb_link= link_table_entry(index);
		if ((nb_link) &&
		    (nb_link->remote_dev== dev) &&
		    (memcmp(nb_link->remote_mac, remote_mac, dev->addr_len) == 0))
			return nb_link;
		}

	return NULL;
}


/*
 * Function: nbll_request_llc_connect
 *	Prepares and sends a connection request to LLC layer.
 *
 * Parameters:
 *	nb_link	: pointer to link_t structure prepared for this new connection
 *		  A NetBEUI link_t is the counterpart of LLC connection stuct.
 *
 * Returns: 
 *	0	: LLC accepted connection request
 *	non-zero: LLC rejected connection request
 */

static int 
nbll_request_llc_connect(link_t *nb_link)
{    
	struct device *dev= nb_link->remote_dev;
	prim_if_block_t prim;
	prim_data_u prim_data;

	prim.data= &prim_data;

	prim_data.conn.source_addr.lsap= NetBEUI_SAP_NO;
	memcpy(prim_data.conn.source_addr.mac, dev->dev_addr, dev->addr_len);
	prim_data.conn.dest_addr.lsap= NetBEUI_SAP_NO;
	memcpy(prim_data.conn.dest_addr.mac, nb_link->remote_mac, dev->addr_len);
	prim_data.conn.device= (void *)dev;
	prim_data.conn.link_no= nb_link->link_no;
	prim_data.conn.connect_handle= 0;
	prim_data.conn.priority= 0;

	prim.primitive= CONNECT_PRIM;
	prim.sap= (us32)netbeui_sap;
	
	return netbeui_sap->request(&prim);
}


/*
 * Function: nbll_request_llc_disconnect
 *	Prepares and sends a disconnection request to LLC layer.
 *
 * Parameters:
 *	nb_link	: pointer to link_t structure responsible for this connection
 *		  A NetBEUI link_t is the counterpart of LLC connection stuct.
 *
 * Returns: 
 *	0	: LLC accepted disconnection request
 *	non-zero: LLC rejected disconnection request
 *
 * Note:
 *	- Currently LLC always accepts disconnection request.
 */

static int 
nbll_request_llc_disconnect(link_t *nb_link)
{   
	prim_if_block_t prim;
	prim_data_u prim_data;

	prim.data= &prim_data;
	prim_data.disc.connect_handle= nb_link->llc_handle;
	
	prim.primitive= DISCONNECT_PRIM;
	prim.sap= (us32)netbeui_sap;

	return netbeui_sap->request(&prim);
}


/*
 * Function: nbll_isend_session_alive
 *	Prepares a NetBIOS SESSION ALIVE frame and nbll_isends it to link
 *
 * Parameters:
 *	nb_link	: pointer to link_t structure to send SESSION ALIVE to
 *
 * Returns: none
 */

static void
nbll_isend_session_alive(link_t *nb_link)
{
	int llcmac_ihl = LLCMAC_I_HEADLEN(nb_link->remote_dev),
	    session_packet_len= nb_command_header_len[SESSION_ALIVE];
	struct sk_buff *skb= alloc_skb((session_packet_len + llcmac_ihl), GFP_ATOMIC);
	packet_t * hdr;

#ifdef _NB_TR_DBG_
printk("nbll_isend_session_alive 2>>> llcmac_ihl=%d\n", llcmac_ihl);
#endif
	if (!skb)
		return;

	skb_reserve(skb, llcmac_ihl);
	hdr= (packet_t *)skb_put(skb, session_packet_len);
	skb->free= 1;

	hdr->length= session_packet_len;
	hdr->delimiter= NB_DELIMITER;
	hdr->command= SESSION_ALIVE;
	hdr->data1= 0;
	hdr->data2= 0;
	hdr->xmit_correlator= 0;
	hdr->resp_correlator= 0;
	hdr->dest_num= 0;
	hdr->source_num= 0;

	nbll_isend(nb_link->link_no, skb);
}


/*
 * Function: nbll_handle_event
 *	This is the heart of Link Service State Machine, which performs a 
 *	transition from current state of link element to new state based
 *	on event occured and link state table contents.
 *
 * Parameters:
 *	event	: An integer of NB_LINK_* family that implies type of event
 *	nb_link	: pointer to link_t structure which the event occured on
 *
 * Returns: none
 *
 * Notes:
 *	- The state changes before actions be executed. This is due to
 *	  non deterministic behaviour of actions which may sleep the current
 *	  process, thus stopping the function in the mid-way. 
 *
 *	- Setting NBLL_DEBUG in Makefile causes the module to generate the code 
 *         that provide usefull information about transitions on every link element. 
 */

static void
nbll_handle_event(link_event_t event, link_t *nb_link)
{
	struct event_struct *ev= &link_state_table[nb_link->state][event];
	link_state_t old_state;

#ifdef NBLL_DEBUG
	printk("NBLL event handler LINK(%3d): EVENT=%u on STATE=%u\n", nb_link->link_no, event, nb_link->state);
#endif NBLL_DEBUG

	if ((ev) && (ev->event_handler))
		{
		old_state= nb_link->state;
		nb_link->state= ev->next_state;
		if (ev->event_handler(nb_link) != 0)
			nb_link->state= old_state;
		}

#ifdef NBLL_DEBUG
	printk("NBLL event handler LINK(%3d): NEW STATE=%u\n", nb_link->link_no, nb_link->state);
#endif NBLL_DEBUG

	return;
}


/*
 * Function: nbll_timer_function
 *	This is the callback function triggered upon expiration of link 
 *	inactivity timer. It just injects an event into state machine for
 *	its link.
 *
 * Parameters:
 *	input	: pointer to link_t structure whose timer is expired.
 *
 * Returns: none
 */

static void
nbll_timer_function(unsigned long input)
{
	nbll_handle_event(NB_LINK_SESSION_ALIVE, (link_t *)input);
}


/*
 * Link service state machine functions
 * Implementing transition functions
 */

/*
 * Function: nbll_xxxx_in_ssss
 *	The section below contains functions that implement actions needed
 *	to  legally transit from one state to another.
 *
 * Parameters:
 *	nb_link	: pointer to link_t structure which the actions are to be 
 *		  applied to
 *
 * Returns: 
 *	0	: if all actions are done successfully
 *	non-zero: if one of actions failed
 *
 * Note:
 *	- For the sake of simplicity, the actions are automatically rollbacked 
 *	  in each function, if an action in transition fails. The design documents
 *	  do not cover these parts of code.
 */

static int 
nbll_conn_indicate_in_initial(link_t *nb_link)
{
	nb_link->status= nbll_insert_link_into_table(nb_link);
	if (nb_link->status==0)
		{
		nb_link->timer.expires= 
			jiffies+ NB_INACTIVITY_TIMEOUT;
		add_timer(&nb_link->timer);
		}
	return nb_link->status;
}


static int 
nbll_conn_request_in_connwait(link_t *nb_link)
{
	unsigned long flags;

	save_flags(flags);
	cli();

	if (nbll_request_llc_connect(nb_link) != 0)    
		{
		restore_flags(flags);
		return -1;
		}

	sleep_on(&nb_link->waitq);

	restore_flags(flags);

	return 0;
}


static int 
nbll_dummy_conn_in_initial(link_t *nb_link)
{
	return nbll_insert_link_into_table(nb_link);
}


static int 
nbll_conn_confirm_in_connwait(link_t *nb_link)
{
	wake_up(&nb_link->waitq);

	nb_link->timer.expires= jiffies+ NB_INACTIVITY_TIMEOUT;
	add_timer(&nb_link->timer);

	nb_link->status= 0;

	return 0;
}


static int 
nbll_conn_indicate_in_connwait(link_t *nb_link)
{
	nb_link->timer.expires= jiffies+ NB_INACTIVITY_TIMEOUT;
	add_timer(&nb_link->timer);

	return 0;
}


static int 
nbll_conn_reject_in_connwait(link_t *nb_link)
{
	wake_up(&nb_link->waitq);
	
	nbll_delete_link_from_table(nb_link);

	nb_link->status= -ECONNREFUSED;
	
	return 0;
}


static int 
nbll_disc_request_in_connwait(link_t *nb_link)
{
	unsigned long flags;

	save_flags(flags);
	cli();

	nbll_delete_link_from_table(nb_link);

	restore_flags(flags);

	return 0;
}


static int 
nbll_reset_indicate_in_up(link_t *nb_link)
{
	int index;

	for (index= nb_link->session_table.reserved; index< nb_link->session_table.size; index++)
		{
		session_t *session= (session_t *)nb_link->session_table.addr[index];
		if (session != NULL)
    			{
    			nbss_abort_session(session);
			dextab_delete_index(&nb_link->session_table, session->lsn); 
			}
		}

	del_timer(&nb_link->timer);
	nb_link->iactivity= 0;
	nb_link->timer.expires= jiffies+ NB_INACTIVITY_TIMEOUT;
	add_timer(&nb_link->timer);

	return 0;
}


static int
nbll_session_alive_in_up(link_t *nb_link)
{
	if (nb_link->iactivity == 0)
		nbll_isend_session_alive(nb_link);

	nb_link->iactivity= 0;
	nb_link->timer.expires= jiffies+ NB_INACTIVITY_TIMEOUT;
	add_timer(&nb_link->timer);

	return 0;
}


static int 
nbll_disc_request_in_up(link_t *nb_link)
{
	unsigned long flags;

	save_flags(flags);
	cli();

	del_timer(&nb_link->timer);

	nbll_request_llc_disconnect(nb_link);

	nbll_delete_link_from_table(nb_link);

	restore_flags(flags);

	return 0;
}


static int 
nbll_disc_indicate_in_up(link_t *nb_link)
{
	int index;

	del_timer(&nb_link->timer);

	for (index= nb_link->session_table.reserved; index< nb_link->session_table.size; index++)
		{
		session_t *session= (session_t *)nb_link->session_table.addr[index];
		if (session != NULL)
			{
			nbss_abort_session(session);
			dextab_delete_index(&nb_link->session_table, session->lsn); 
			}
		}
	dextab_destruct(&nb_link->session_table);
	
	nbll_delete_link_from_table(nb_link);
 
	return 0;
}


/*
 * Link service state machine functions
 * Implementing interface functions
 */

/*
 * Function: get_disconnect_indicate
 *	Accepts a connection indication from LLC layer. If it can establish
 *	a link anyway, genrates an event on link element, otherwise requests
 *	LLC to disconnect (! this is LLC rule)
 *
 * Parameters:
 *	prim	: pointer to primary interafce block type, the LLC communication
 *		  data structure. This is a union with different fields with
 *		  different meaning for different purposes.
 *
 * Returns: none
 */

static void
nbll_get_connect_indicate(prim_if_block_t *prim)
{
	prim_connect_t *prim_data= (prim_connect_t *)prim->data;
	link_t *nb_link= nbll_find_link(prim_data->device, prim_data->source_addr.mac);

	if (!nb_link)
		nb_link= nbll_alloc_link();

	if (nb_link)    
		{
		nb_link->llc_handle= prim_data->connect_handle;
		nb_link->remote_dev= (struct device *)prim_data->device;
		memcpy(nb_link->remote_mac, prim_data->source_addr.mac, 
		       nb_link->remote_dev->addr_len);
		nbll_handle_event(NB_LINK_CONN_INDICATE, nb_link);
		}
   
	if ((!nb_link) || (nb_link->state == NB_LINK_INITIAL))
		{
		/* Request link disconnection */
		((prim_disconnect_t *)(prim->data))->connect_handle= 
			prim_data->connect_handle;
		prim->primitive= DISCONNECT_PRIM;
		prim->sap= (u32)netbeui_sap;
		netbeui_sap->request(prim);
		if (nb_link)   
		    nbll_free_link(nb_link);
		return;
		}
   
	prim->primitive= CONNECT_PRIM;
	prim->sap= (u32)netbeui_sap;
	prim_data->link_no= nb_link->link_no;
	netbeui_sap->response(prim);
	
	return;
}


/*
 * Function: nbll_get_connect_confirm
 *	Accepts a connection confirm for a previously requested connection from
 *	LLC layer. The LLC may confrim a connection request positively or
 *	negatively.
 *
 * Parameters:
 *	prim	: pointer to primary interafce block type, the LLC communication
 *		  data structure. This is a union with different fields with
 *		  different meaning for different purposes.
 *
 * Returns: none
 */

static void 
nbll_get_connect_confirm(prim_if_block_t *prim)
{
	prim_connect_t *prim_data= (prim_connect_t *)prim->data;
	link_t *nb_link= link_table_entry(prim_data->link_no);

	if (prim_data->status == CONNECT)
		{
		nb_link->llc_handle= prim_data->connect_handle;
		nbll_handle_event(NB_LINK_CONN_CONFIRM, nb_link);
		}
	else
		{
		nbll_handle_event(NB_LINK_CONN_REJECT, nb_link);   
		}

	return;
}


/*
 * Function: nbll_get_data_confirm
 *	Accepts a data confirm from LLC layer, according to a -EBUSY return
 *	value on latest try to send I-Frame to a LLC connection. This primitive
 *	informs NetBEUI to flush its link queue to LLC connection. 
 *
 * Parameters:
 *	prim	: pointer to primary interafce block type, the LLC communication
 *		  data structure. This is a union with different fields with
 *		  different meaning for different purposes.
 *
 * Returns:
 *	0 	: if all queue contents are flushed successfully. 
 *	non-Zero: if LLC does not accept all entries and queue has more entries 
 *		  to flush.
 *
 * Note:
 *	- Since this function is raised via LLC, the return value has special
 *	  meaning to both NetBEUI and LLC to sync their flags. The llc_busy
 *	  flag in NetBEUI link demonstrates existence of skbs in link queue
 *	  ready to send to LLC layer. 
 */

static int
nbll_get_data_confirm(prim_if_block_t *prim)
{    
	prim_data_t *prim_data= (prim_data_t *)prim->data;
	link_t *nb_link= link_table_entry(prim_data->link_no);
	struct sk_buff *skb;

	while ((skb=__skb_dequeue(&nb_link->skbq)) != NULL)
		{
		if (nbll_isend_link(nb_link->remote_dev, nb_link->llc_handle, 
				    skb, prim_data->link_no, 1) == 1)
			{
			return -EBUSY;
			}
		}

	nb_link->llc_busy= 0;
	return 0;
}


/*
 * Function: nbll_get_disconnect_indicate
 *	Accepts a disconnection indication for a connection from LLC layer. 
 *
 * Parameters:
 *	prim	: pointer to primary interafce block type, the LLC communication
 *		  data structure. This is a union with different fields with
 *		  different meaning for different purposes.
 *
 * Returns: none
 */

static void
nbll_get_disconnect_indicate(prim_if_block_t *prim)
{
	link_t *nb_link= link_table_entry(((prim_disconnect_t *)prim->data)->link_no);

	nbll_handle_event(NB_LINK_DISC_INDICATE, nb_link);

	nbll_free_link(nb_link);

	return;
}


/*
 * Function: nbll_get_disconnect_confirm
 *	Accepts a disconnection confirm for a previous disconnec request, from 
 *	LLC layer. 
 *
 * Parameters:
 *	prim	: pointer to primary interafce block type, the LLC communication
 *		  data structure. This is a union with different fields with
 *		  different meaning for different purposes.
 *
 * Returns: none
 *
 * Note:
 *	- Currently LLC does not generate a disconnection confirm. Both NetBEUI
 *	  and LLC assume that a disconnection request is always satisfied.
 */

static void 
nbll_get_disconnect_confirm(prim_if_block_t *prim)
{
	/* Nothing to do, we do not wait for disconnection confirmation */
	return;
}


/*
 * Function: nbll_get_reset_indicate
 *	Accepts a reset indication for a connection from LLC layer. 
 *
 * Parameters:
 *	prim	: pointer to primary interafce block type, the LLC communication
 *		  data structure. This is a union with different fields with
 *		  different meaning for different purposes.
 *
 * Returns: none
 *
 * Note:
 *	- LLC reset indication means close all sessions on link and reset
 *	  link parameters. Just the link itself remains UP and RUNNING.
 */

static void
nbll_get_reset_indicate(prim_if_block_t *prim)
{
	link_t *nb_link= link_table_entry(((prim_reset_t *)prim->data)->link_no);
	struct sk_buff *skb;

	nbll_handle_event(NB_LINK_RESET_INDICATE, nb_link);

	nb_link->llc_busy= 0;
	nb_link->iactivity= 0;

	while ((skb= skb_dequeue(&nb_link->skbq)))
		kfree_skb(skb, 0);

	return;
}


/*
 * Function: get_link_table
 *	returns a pointer to NetBEUI link table. The proc support code uses
 *	the link table to map its contents to /proc/sys/netbeui entry.
 *
 * Parameters: none
 *
 * Returns:
 *	non-NULL: pointer to NetBEUI link table
 */

dextab_t *
nbll_get_link_table(void)
{
	return &link_table;
}


/*
 * Function: nbll_get_session_table
 *	returns a pointer to a link session table. The proc support code uses
 *	the session table to map its contents to /proc/sys/netbeui entry.
 *
 * Parameters:
 *	link_no	: an integer representing link number in NetBEUI link table, 
 *		  whose session table is requested.
 *
 * Returns:
 *	non-NULL: pointer to NetBEUI link session table
 *	NULL    : the link number is not valid.
 *
 */

dextab_t *
nbll_get_session_table (int link_no)
{
	link_t * nb_link; 

	if (link_no > link_table.size)
		return NULL;

	nb_link= link_table_entry(link_no);
	if (!nb_link)
		return NULL;

	return &nb_link->session_table;
}


/*
 * Function: nbll_attach_session
 *	This is a sophisticated inteface to session service module, which 
 *	attaches (links) a session to a link. Depending on the existance and
 *	state of the link, it interacts with LLC and manipulates link session
 *	table to content the request.
 *
 * Parameters:
 *	session	  : pointer to session_t structure to add to session table of link
 *	dev	  : pointer to device structure the link to remote node is on
 *	remote_mac: pointer to MAC address of remote node the link is to
 *
 * Returns:
 *	0 	  : if the session is successfully added to link session table
 *	-ENOSPC	  : if the link session table has no space to add session
 *	-ENOMEM	  : if can not allocate memory for creating new link
 *	-EHOSTUNREACH: if LLC connection request for a new link failed.
 */

int
nbll_attach_session(session_t *session, struct device *dev, unsigned char *remote_mac)
{
	unsigned long flags;
	int session_no;
	link_t * nb_link; 

	save_flags(flags);
	cli();

	nb_link= nbll_find_link(dev, remote_mac);

	/* If a link exists previously  and is up or requested within an interrupt */
	if (nb_link)
		{
		session_no= dextab_insert_entry(&nb_link->session_table, session);
 
		if (session_no < 0)
			{
			restore_flags(flags);
			return -ENOSPC;
			}

		session= ((session_t *)nb_link->session_table.addr[session_no]);
		session->link_no= nb_link->link_no;
		session->lsn= session_no;
		restore_flags(flags);
		return 0;
		} 

	restore_flags(flags);


	/* Request for link (dummy) */
	nb_link= nbll_alloc_link();
	if (!nb_link)
		return -ENOMEM;

	nb_link->llc_handle= 0;
	nb_link->remote_dev= dev;
	memcpy(nb_link->remote_mac, remote_mac, dev->addr_len);
	
	nbll_handle_event(NB_LINK_DUMMY_CONN, nb_link);

	if (nb_link->state== NB_LINK_INITIAL)
		{
		nbll_free_link(nb_link);
		return -EHOSTUNREACH;
		}

	return nbll_attach_session(session, dev, remote_mac); 
}


/*
 * Function: nbll_link_session
 *	This is a joke !!!!!
 *	NetBEUI connection request has two steps NAME QUERY and LLC CONNECTION
 *	establishment. To overcome some traditional limits in total session count
 *	new implentations add a NAME FIND step before the other two steps, thus
 *	letting the other two be used interchengably. Unfortunately microsoft 
 *	implementations force using NAME QUERY before LLC CONNECTION. 
 *	To overcome the problem we first put a link into CONNWAIT, attach a
 *	session to it and first issue NAME QUERY then request LLC CONNECTION. 
 *	This function does the actual LLC connection request on the dummy link.
 *
 * Parameters:
 *	link_no	  : an integer representing link number in link table
 *	session_no: an integer representing session number in link session table
 *		    it is not currently used. 
 *
 * Returns:
 *	0	     : if dummy link is now established successfully. 
 *	-ECONNRESET  : if dummy link is reset 
 *	-EHOSTUNREACH: if LLC connection establishment failed. 
 */

int
nbll_link_session(int link_no, unsigned char session_no)
{
	unsigned long flags;
	link_t *nb_link;

	save_flags(flags);
	cli();

	nb_link= link_table_entry(link_no);
	if (!nb_link)
		{
		restore_flags(flags);
		return -ECONNRESET;
		}

	nbll_handle_event(NB_LINK_CONN_REQUEST, nb_link); 

	if (nb_link->state== NB_LINK_INITIAL)
		{
		nbll_free_link(nb_link);
		restore_flags(flags);
		return -EHOSTUNREACH;
		}
	
	restore_flags(flags);
	return 0;
}


/*
 * Function: nbll_detach_session
 *	Detaches a session from its link session table. If the session was the
 *	last session link, it tries to drop the link to free resources. 
 *
 * Parameters:
 *	link_no	  : an integer representing link number in link table
 *	session_no: an integer representing session number in link session table
 *
 * Returns: none
 */

void 
nbll_detach_session(int link_no, unsigned char session_no)
{
	unsigned long flags;
	session_t *session; 
	link_t *nb_link;

	save_flags(flags);
	cli();

	nb_link= link_table_entry(link_no);
	if (!nb_link)
		{
		restore_flags(flags);
		return ;
		}

	session= ((session_t *)nb_link->session_table.addr[session_no]);

	if (session)
		session->lsn= 0;

	dextab_delete_index(&nb_link->session_table, session_no);

	if (dextab_count_entries(&nb_link->session_table) > 0)
		{
		restore_flags(flags);
		return;
		}


	nbll_handle_event(NB_LINK_DISC_REQUEST, nb_link); 

	restore_flags(flags);
	
	nbll_free_link(nb_link);
	
	return;
}


/*
 * Function: nbll_drop_link
 *	Drops a specified link with its sessions. Before calling this
 *	function must call 'start_bh_atomic()' and after it must call
 *	'end_bh_atomic()' to make assurance of system's safety.
 *
 * Parameters:
 *	link_no	: an integer representing link number in link table.
 *
 * Returns: int
 *	zero     : if the link dropped successfully.
 *	negative : if operation fails.
 *	           (-EINVAL) : the link number is invalid.
 */

int
nbll_drop_link (int link_no)
{
	int   i,
	      sn_cnt;
	dextab_t   *sn_tbl;

	sn_tbl = nbll_get_session_table(link_no);
	if (!sn_tbl) /* Invalid link number */
		return (-EINVAL);

	sn_cnt = sn_tbl->count;
	for (i = sn_tbl->reserved ; sn_cnt ; i++) {
		session_t   *sn;
		
		sn = sn_tbl->addr[i];
		if (sn) {
			nbll_detach_session(link_no, i);
			nbss_abort_session(sn);
			sn_cnt--;
		}
	}

	return 0;
}


/*
 * LLC interface functions
 * Data-in functions
 */

/*
 * Function: nbll_disconnect_all_links
 *	Generates disconnectiion event on all NetBEUI links. 
 *
 * Parameters: none
 *
 * Returns: none
 */

static void
nbll_disconnect_all_links(void)
{
	int index;
	link_t *nb_link;

	for (index=link_table.reserved; index<link_table.size; index++)
		{
		cli();

		nb_link= link_table_entry(index);
		if (nb_link)
			{
			nbll_handle_event(NB_LINK_DISC_REQUEST, nb_link);
			nbll_free_link(nb_link);
			}

		sti();
		}

}


/*
 * Function: nbll_deliver_packet
 *	While checking input I-Frame consistency, routes frame to session
 *	service interface. Since manipulating I-Frames is always the respomsibility
 *	of session service, the dispatching mechanism is implemented there.
 *
 * Parameters:
 *	frame	 : pointer to LLC representation of network PDU frame_t. it
 *		   containd an sk_buff which holds actual data
 *	prim_data: pointer to LLC primitive data description.  
 *
 * Returns: none
 *
 * Notes:
 *	- Imagine prim_if_block_t is the function that LLC calls in NetBEUI
 *	  code and prim_data_u as argument to this function. 
 *
 *	- This function is a barrier for ill frames with anomalous content.
 */

static void 
nbll_deliver_packet(frame_t *frame, prim_data_t *prim_data)
{
	struct sk_buff *skb= (struct sk_buff *)frame->skb;
	int command= ((packet_t *)(skb->data))->command;
	link_t *nb_link= link_table_entry(prim_data->link_no);
	session_t *session;

	/* Sanity check if command length and code is valid, all checks should be done !!! */
	/* It checks ill netbios headers, and prevents memory faults */
	if ((skb->len < NB_MIN_COMMAND_LEN) || (command > NB_MAX_COMMAND_CODE) ||
	    (skb->len < nb_command_header_len[ command] ) ||
	    ( ((packet_t *)skb->data)->delimiter != NB_DELIMITER) ||
	    ( ((packet_t *)skb->data)->dest_num >= nb_link->session_table.size))
		{
		kfree_skb(skb, 0);
		return;
		}

	nb_link->iactivity++;

	session= (session_t *)nb_link->session_table.addr[ ((packet_t *)skb->data)->dest_num ];
	if ((!session) || (command == SESSION_ALIVE))
		{
		kfree_skb(skb, 0);
		return;
		}

	nbss_deliver_frame(session, skb);
}


/*
 * Function: nbll_deliver_datagram
 *	The UI-Frame dispatcher
 *	While checking input UI-Frame consistency, routes frame to final
 *	destination in name service, session service and datagram distribution.
 *	Due to different destinations the dispatching mechanism for UI-Frames is
 *	implemented here in this function.
 *
 * Parameters:
 *	frame	 : pointer to LLC representation of network PDU frame_t. it
 *		   contains an sk_buff which holds actual data
 *	prim_data: pointer to LLC primitive data description.  
 *
 * Returns: none
 */

static void 
nbll_deliver_datagram(frame_t *frame, prim_unit_data_t *prim_data)
{
	struct sk_buff *skb= (struct sk_buff *)frame->skb;
	int command= ((dgram_t *)(skb->data))->command;

	/* Sanity check if command length and code is valid, all checks should be done !!! */
	/* It checks ill netbios headers, and prevents memory faults */
	if ((skb->len < NB_MIN_COMMAND_LEN) || (command > NB_MAX_COMMAND_CODE) ||
	    (skb->len < nb_command_header_len[ command] ) ||
	    ( ((dgram_t *)skb->data)->delimiter != NB_DELIMITER ))
		{
		kfree_skb(skb, 0);
		return;
		}

	/* Token Ring support */
	skb->proto_priv[0] = prim_data->lfb;

	switch(command)
		{
		case ADD_GROUP_NAME_QUERY:
#ifdef _NB_TR_DBG_
printk("nbll_deliver_datagram 1>>> ADD_GROUP_NAME_QUERY\n");
#endif
			nbns_get_add_name_query(skb, prim_data->source_addr.mac, NB_NAME_GROUP);
			break;
		case ADD_NAME_QUERY:
#ifdef _NB_TR_DBG_
printk("nbll_deliver_datagram 2>>> ADD_NAME_QUERY\n");
#endif
			nbns_get_add_name_query(skb, prim_data->source_addr.mac, NB_NAME_UNIQUE);
			break;
		case ADD_NAME_RESPONSE:
#ifdef _NB_TR_DBG_
printk("nbll_deliver_datagram 3>>> ADD_NAME_RESPONSE\n");
#endif
			nbns_get_add_name_response(skb, prim_data->source_addr.mac);
			break;
		case NAME_IN_CONFLICT:
			nbns_get_name_conflict(skb);
			break;
		case NAME_QUERY:
#ifdef _NB_TR_DBG_
printk("nbll_deliver_datagram 4>>> NAME_QUERY\n");
#endif
			nbss_get_name_query(skb, prim_data->source_addr.mac);
			break;
		case NAME_RECOGNIZED:
#ifdef _NB_TR_DBG_
printk("nbll_deliver_datagram 5>>> NAME_RECOGNIZED\n");
#endif
			nbqs_get_name_recognized(skb, prim_data->source_addr.mac);
			break;
		case DATAGRAM:
#ifdef _NB_TR_DBG_
printk("nbll_deliver_datagram 6>>> DATAGRAM\n");
#endif
			nbdg_get_datagram(skb);
			break;
		case DATAGRAM_BROADCAST:
			nbdg_get_datagram_broadcast(skb);
			break;
		case STATUS_QUERY:
			nbst_get_status_query(skb, prim_data->source_addr.mac);
			break;
		case STATUS_RESPONSE:
			nbst_get_status_response(skb, prim_data->source_addr.mac);
			break;
		case TERMINATE_TRACE:
			kfree_skb(skb, 0);
			break;
		case TERMINATE_TRACE2:
			kfree_skb(skb, 0);
			break;
	}
	return;
}


/*
 * Function: netbeui_indicate
 *	The callback for LLC indicate primitive dispatching
 *	It dispatches LLC primitive to Link Service State Machine interfaces.
 *
 * Parameters:
 *	prim	: pointer to primary interafce block type, the LLC communication
 *		  data structure. This is a union with different fields with
 *		  different meaning for different purposes.
 *
 * Returns:
 *	0	: always returns zero to LLC to inform indicate accepted.
 *
 * Note:
 *	LLC flow contorl indication is not generated. We have used data
 *	request/confirm to control flow. Think if it is a misconception 
 *	or is accepted anyway.
 */

static int 
netbeui_indicate(prim_if_block_t * prim)
{
	frame_t *frame= NULL;

	switch (prim->primitive)
		{
		case DATAUNIT_PRIM:
			frame=(frame_t *)((prim_unit_data_t *)(prim->data)->udata.unit);
			nbll_deliver_datagram(frame, (prim_unit_data_t *)prim->data);
			break;

		case CONNECT_PRIM:
			nbll_get_connect_indicate(prim);
			break;

		case DATA_PRIM:
			frame=(frame_t *)((prim_data_t *)(prim->data)->data.unit);
			nbll_deliver_packet(frame, (prim_data_t *)prim->data);
			break;

		case DISCONNECT_PRIM:
			nbll_get_disconnect_indicate(prim);
			break;

		case RESET_PRIM:
			nbll_get_reset_indicate(prim);
			break;

		case FLOWCONTROL_PRIM:
			break;
		}

	if (frame)
		frame_free(frame);   

	return 0;
}


/*
 * Function: netbeui_confirm
 *	The callback for LLC confirm primitive dispatching
 *	It dispatches LLC primitive to Link Service State Machine interfaces.
 *
 * Parameters:
 *	prim	: pointer to primary interafce block type, the LLC communication
 *		  data structure. This is a union with different fields with
 *		  different meaning for different purposes.
 *
 * Returns:
 *	0	: always returns zero to LLC to inform confirm accepted.
 *
 * Note:
 *	LLC reset confirm is not generated, since we never generate reset request
 */

static int
netbeui_confirm(prim_if_block_t * prim)
{
	int status= 0;

	switch (prim->primitive)
		{
		case CONNECT_PRIM:
			nbll_get_connect_confirm(prim);
			break;

		case DATA_PRIM:
			status= nbll_get_data_confirm(prim);
			break;

		case DISCONNECT_PRIM:
			nbll_get_disconnect_confirm(prim);
			break;

		case RESET_PRIM:
			break;

		}

	return status;
}


#ifdef MODULE
/*
 * Function: init module
 *	The starting point of NetBEUI module.
 *	Since NetBEUI is distributed as a module, init_modle does all initial
 *	steps itself.
 *
 * Parameters: none
 *
 * Returns: none
 *	0 	: if module initialized successfully.
 *	non-zero: if a step in initialization failed (mostly opning LLC sap)
 */

int 
init_module(void)
{
	printk("\n\nNetBEUI 1.28 : A product from Procom Technology Inc. \n");
	printk("        (C)Copyright Procom Technology Inc. 1997 \n");

	if (llc_sap_open(netbeui_indicate, netbeui_confirm, NetBEUI_SAP_NO, (us32 *)&netbeui_sap) != 0)
		{
		printk("\nError opening NetBIOS SAP 0x%X on LLC.\n", NetBEUI_SAP_NO);
		printk("NetBEUI initialization FAILED!\n\n");
		return -1;
		}

	printk("\n        - NetBIOS SAP(0x%X) opened successfully on LLC.\n", NetBEUI_SAP_NO);

	memset(adapters, 0, sizeof(adapters));

	if (AUTO_BIND) {
		int   i = 0;
		struct device   *dev;

		for (dev = dev_base ; dev && (i < NB_MAX_ADAPTERS) ; dev = dev->next)
			if (nbcm_apt_dev(dev)) {
				adapters[i] = dev;
				i++;
				dev_mc_add(dev, NB_FUNCADDR(dev), dev->addr_len, 0);
/* NOTCOMP */
//				dev->open(dev);
			}

		binded_adapters_count = i;

		printk("        - NetBEUI is bound to all supported network devices.\n");
	}

	printk("\n");

	nbns_init_name_number_1(adapters);

	nbdg_set_dgbc_mtu();
   
	if (nbso_init()) {
		printk("\nError registering NetBEUI socket on kernel.\n");
		printk("NetBEUI initialization FAILED!\n\n");
		return -2;
	}

#ifdef CONFIG_PROC_FS
	proc_init();
#endif CONFIG_PROC_FS

	nbst_init_status();

	return 0;
}


/*
 * Function: cleanup_module
 *	The ending point of NetBEUI module.
 *	Since NetBEUI is distributed as a module, cleanup_modle does all 
 *	housekeeping steps itself 
 *
 * Parameters: none
 *
 * Returns: none
 */

void 
cleanup_module(void)
{
	int i;

	nbll_disconnect_all_links();

	for (i = 0 ; i < binded_adapters_count ; i++) {
/* NOTCOMPLETED */
//			adapters[i]->stop(adapters[i]);
			dev_mc_delete(adapters[i], NB_FUNCADDR(adapters[i]),
			              adapters[i]->addr_len, 0);
	}

	if (nbso_exit())
		printk("\nError during unregistering NetBEUI socket.\n");

#ifdef CONFIG_PROC_FS
	proc_clean();
#endif CONFIG_PROC_FS

	llc_sap_close((us32) netbeui_sap);
}

#endif MODULE
