/*
 * config.c - Contains functions that configure or query NetBEUI parameters 
 *	      from API tools such as ioclt/setsockopt/getsockopt.
 *
 * Notes:
 *	- VRP in comments is the acronym of "Value Result Parameter"
 * 
 * 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/string.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/errno.h>
#include <linux/netbeui.h>


/* Definition of default and maximum value of NetBIOS configurable parameters */
#define DEFAULT_LINK_INACTIVITY_TIMEOUT	30
#define MAX_LINK_INACTIVITY_TIMEOUT	255

#define DEFAULT_TRANSMIT_TIMEOUT	1
#define MAX_TRANSMIT_TIMEOUT		10

#define DEFAULT_TRANSMIT_COUNT		6
#define MAX_TRANSMIT_COUNT		10

#define DEFAULT_RESOURCE_TIMEOUT	1
#define MAX_RESOURCE_TIMEOUT		10

#define DEFAULT_DATA_ACK_TIMEOUT	1
#define MAX_DATA_ACK_TIMEOUT		10


/* Holds NetBIOS configurable parameters */
config_t netbios_config = {
	DEFAULT_LINK_INACTIVITY_TIMEOUT,
	DEFAULT_TRANSMIT_TIMEOUT,
	DEFAULT_TRANSMIT_COUNT,
	DEFAULT_RESOURCE_TIMEOUT,
	DEFAULT_DATA_ACK_TIMEOUT
};


/*
 * Function: nbcs_update_netbios_config
 *	Updates NetBIOS parameters with new values while considers special
 *	cases.
 *
 * Parameters:
 *	newcfg	: pointer to config_t which contains new values for NetBIOS
 *	  	  parameters.
 *
 * Returns: none
 *	
 * Motes:
 *	- Since most NetBIOS parameters are tuned automatically both in LLC
 *	  and NetBEUI, this implementation contains only a subset.
 *	- Setting value of parameter to zero means using NetBIOS defaults
 *	- Two parameters "resource_timeout" and "data_ack_timeout" are added
 *	  by this implementation.
 */

static void
nbcs_update_netbios_config(config_t *newcfg)
{
	/* Setting INACTIVITY TIMEOUT */
	if (newcfg->inactivity_timeout == 0)
		netbios_config.inactivity_timeout= DEFAULT_LINK_INACTIVITY_TIMEOUT;
	else if (newcfg->inactivity_timeout > MAX_LINK_INACTIVITY_TIMEOUT)
		netbios_config.inactivity_timeout= MAX_LINK_INACTIVITY_TIMEOUT;
	else	
		netbios_config.inactivity_timeout= newcfg->inactivity_timeout;

	/* Setting TRANSMIT TIMEOUT */
	if (newcfg->transmit_timeout == 0)
		netbios_config.transmit_timeout= DEFAULT_TRANSMIT_TIMEOUT;
	else if (newcfg->transmit_timeout > MAX_TRANSMIT_TIMEOUT)
		netbios_config.transmit_timeout= MAX_TRANSMIT_TIMEOUT;
	else	
		netbios_config.transmit_timeout= newcfg->transmit_timeout;

	/* Setting TRANSMIT COUNT */
	if (newcfg->transmit_count == 0)
		netbios_config.transmit_count= DEFAULT_TRANSMIT_COUNT;
	else if (newcfg->transmit_count > MAX_TRANSMIT_COUNT)
		netbios_config.transmit_count= MAX_TRANSMIT_COUNT;
	else	
		netbios_config.transmit_count= newcfg->transmit_count;

	/* Setting RESOURCE TIMEOUT */
	if (newcfg->resource_timeout == 0)
		netbios_config.resource_timeout= DEFAULT_RESOURCE_TIMEOUT;
	else if (newcfg->resource_timeout > MAX_RESOURCE_TIMEOUT)
		netbios_config.resource_timeout= MAX_RESOURCE_TIMEOUT;
	else	
		netbios_config.resource_timeout= newcfg->resource_timeout;

	/* Setting DATA ACK TIMEOUT */
	if (newcfg->data_ack_timeout == 0)
		netbios_config.data_ack_timeout= DEFAULT_DATA_ACK_TIMEOUT;
	else if (newcfg->data_ack_timeout > MAX_DATA_ACK_TIMEOUT)
		netbios_config.data_ack_timeout= MAX_DATA_ACK_TIMEOUT;
	else	
		netbios_config.data_ack_timeout= newcfg->data_ack_timeout;
}



/*
 * Function: nbcs_setsockopt
 *	Sets SOL_NETBEUI layer options provided by setsockopt system call.
 *
 * Parameters:
 *	sock	: pointer to socket structure 
 *	optname : option code
 *	optval	: pointer to new value of parameter (option)
 *	optlen  : length of option value in bytes.
 *
 * Returns: 
 *	0	      : if new value for option set successfully
 *	-EOPNOTSUPP   : if option is not supported on socket type
 *	-ENOTPROTOOPT : if NetBEUI layer does not support the option.
 * 
 * Notes:
 *	- Refer to Implementation documents for a complete description of
 *	  NetBEUI layer options accessible from setsockopt.
 *	- user buffer is checked in upper layer.
 */

int 
nbcs_setsockopt(struct socket *sock, int optname, void *optval, int optlen)
{
	struct netbeui_sock *sk= (struct netbeui_sock *)sock->data;

	switch (optname)
		{
		case SO_URGENTACK:
			if (sk->session)
				{
				sk->session->urgent_ack= get_user((char *)optval);
				return 0;
				}

			return -EOPNOTSUPP;	

		case SO_NBPARAM:
			if (optlen != sizeof(netbios_config))
				return -EINVAL;
			{
			config_t tmp_config;
			memcpy_fromfs((void *)&tmp_config, optval, optlen);
			nbcs_update_netbios_config(&tmp_config);
			}
			return 0;
		}

	return -ENOPROTOOPT;
}



/*
 * Function: nbcs_getsockopt
 *	Gets SOL_NETBEUI layer options requested by getsockopt system call.
 *
 * Parameters:
 *	sock	: pointer to socket structure 
 *	optname : option code
 *	optval	: (VRP) pointer to buffer to save value of parameter (option)
 *	optlen  : (VRP) pointer length of option value in bytes.
 *
 * Returns: 
 *	0	      : if value of option is moved to optval successfully
 *	-EOPNOTSUPP   : if option is not supported on socket type
 *	-ENOTPROTOOPT : if NetBEUI layer does not support the option.
 *	
 * Notes:
 *	- Refer to Implementation documents for a complete description of
 *	  NetBEUI layer options accessible from setsockopt.
 *	- user buffer is checked in upper layer.
 */

int
nbcs_getsockopt(struct socket *sock, int optname, void *optval, int *optlen)
{
	int len;
	struct netbeui_sock *sk= (struct netbeui_sock *)sock->data;

	switch (optname)
		{
		case SO_URGENTACK:
			if (sk->session)
				{
				len= MIN(get_user((int *)optlen), sizeof(sk->session->urgent_ack));
				memcpy_tofs(optval, (void *)&sk->session->urgent_ack, len);
				put_user(len, (int *)optlen);
				return 0;
				}

			return -EOPNOTSUPP;	

		case SO_NBPARAM:
			len= MIN(get_user((int *)optlen), sizeof(netbios_config));
			memcpy_tofs(optval, (void *)&netbios_config, len);
			put_user(len, (int *)optlen);
			return 0;
		}

	return -ENOPROTOOPT;
}


/*
 * Function: nbcs_purge_links
 *	Disconnects links on a specific network device (if exist).
 *
 * Parameters:
 *	dev  : pointer to device that links on it must be disconnect.
 *	flag : type of unbinding that specified by user. The NB_UNBIND_FLAG_SAFE
 *	       flag means that protocol stack unbinds from the device only
 *	       no links exist on it.
 *
 * Returns: int
 *	zero     : if command be performed successfully.
 *	negative : if command fails. error codes that bubble to user are
 *	           dependent to cmd.
 *	           (-EISCONN) : the device has some links as yet , but the
 *	                        NB_UNBIND_FLAG_SAFE flag is specified.
 */

static inline int
nbcs_purge_links (struct device *dev, unsigned char flag)
{
	int   i,
	      count;
	dextab_t   *ltbl;

	start_bh_atomic();
	ltbl = nbll_get_link_table();
	count = ltbl->count;
	for (i = ltbl->reserved ; count ; i++ , count--) {
		link_t   *nb_link;

		nb_link = ltbl->addr[i];
		if (nb_link->remote_dev == dev)
			if (flag == NB_UNBIND_FLAG_DROP)
				nbll_drop_link(nb_link->link_no);
			else { /* NB_UNBIND_FLAG_SAFE */
				end_bh_atomic();
				return (-EISCONN);
			}
	}
	end_bh_atomic();

	return 0;
}


/*
 * Function: nbcs_config
 *	Performs configuration actions on the NetBEUI via NBIOCCONFIG ioctl
 *	command.
 *
 * Parameters:
 *	usp : pointer to 'struct netbeui_cfg' that contains command and its
 *	      arguments.
 *
 * Returns: int
 *	zero     : if command is performed successfully.
 *	negative : if command fails. error codes that bubble to user are
 *	           dependent to usp->command.
 *	           (-EOPNOTSUPP) : the command is not valid.
 */

static int
nbcs_config (struct netbeui_cfg *usp)
{
	int   i;
	char   name[NB_MAX_NIF_NAME_LEN];
	extern struct device   *adapters[];
	extern unsigned short   binded_adapters_count;

	switch (get_user(&usp->command)) {
		case NB_CFGCMD_NIF_UNBIND : {
			if (get_user(&usp->reserved) != 0)
				return (-EINVAL);

			memcpy_fromfs(name, usp->nif_name, NB_MAX_NIF_NAME_LEN);

			for (i = 0 ; i < binded_adapters_count ; i++)
				if (strcmp(adapters[i]->name, name) == 0) {
					int   rc;
					unsigned char   flag;

					flag = get_user(&usp->flag);
					rc = nbcs_purge_links(adapters[i], flag);
					if (rc)
						return rc;

					dev_mc_delete(adapters[i],
					              NB_FUNCADDR(adapters[i]),
					              adapters[i]->addr_len, 0);

					binded_adapters_count--;
					if (i < binded_adapters_count)
						adapters[i] =
						adapters[binded_adapters_count];

					adapters[binded_adapters_count] = NULL;

					return 0;
				}

			return (-ENODEV);
		}
		
		case NB_CFGCMD_NIF_BIND : {
			struct device   *dev;

			if (get_user(&usp->reserved) != 0)
				return (-EINVAL);

			memcpy_fromfs(name, usp->nif_name, NB_MAX_NIF_NAME_LEN);

			if (binded_adapters_count == NB_MAX_ADAPTERS)
				return (-ENOSPC);

			for (i = 0 ; i < binded_adapters_count ; i++)
				if (strcmp(adapters[i]->name, name) == 0)
					/* It binds already */
					return 0;

			for (dev = dev_base ; dev ; dev = dev->next)
				if (strcmp(dev->name, name) == 0)
					if (nbcm_apt_dev(dev)) {
						adapters[binded_adapters_count]
						                          = dev;
						binded_adapters_count++;
						dev_mc_add(dev, NB_FUNCADDR(dev),
						           dev->addr_len, 0);

						return 0;
					}
					else
						return (-EOPNOTSUPP);

			return (-ENODEV);
		}

		case NB_CFGCMD_DROP_SESS : {
			int   rc,
			      lnn,
			      snn;

			lnn = get_user(&usp->ln_num);
			if ((lnn < 0) || (lnn >= NB_MAX_LINKS))
				return (-EINVAL);

			snn = get_user(&usp->sn_num);
			if ((snn < 1) || (snn >= NB_MAX_SESSIONS))
				return (-EINVAL);

			start_bh_atomic();
			rc = nbss_drop_session(lnn, snn);
			end_bh_atomic();

			return rc;
		}

		case NB_CFGCMD_DROP_LINK : {
			int   rc,
			      lnn;

			lnn = get_user(&usp->ln_num);
			if ((lnn < 0) || (lnn >= NB_MAX_LINKS))
				return (-EINVAL);

			start_bh_atomic();
			rc = nbll_drop_link(lnn);
			end_bh_atomic();

			return rc;
		}

		default:
			return (-EOPNOTSUPP);
	}
}


/*
 * Function: nbcs_ioctl
 *	Provides NetBEUI level requests of ioctl() system call.
 *
 * Parameters:
 *	cmd : code of command that must perform.
 *	arg : pointer to appropriate data structure, related to command.
 *
 * Returns: int
 *	zero     : if command be performed successfully.
 *	negative : if command fails. error codes that bubble to user are
 *	           dependent to cmd.
 *	           (-EOPNOTSUPP) : the command is not valid.
 */

int 
nbcs_ioctl (unsigned int cmd, void *arg)
{
	switch (cmd) {
                case NBIOCGSTATUS : {
			int   rc,
			      len;
			struct netbeui_status   *usp =
			                          (struct netbeui_status *) arg;

			rc = verify_area(VERIFY_READ, usp, NB_NAME_LEN + 1 +
			                                   sizeof(int));
			if (rc)
				return rc;

			len = get_user(&usp->buff_len);

			if (len < NB_MIN_STATUS_BUFF_LEN)
				return (-EINVAL);

			rc = verify_area(VERIFY_WRITE, &usp->buff_len,
			                 sizeof(int) + len);
			if (rc)
				return rc;

			return ( nbst_obtain_status(usp->called_name,
			                            usp->status_buff,
			                            &usp->buff_len)  );
		}
		case NBIOCCONFIG : {
			int   rc;
			struct netbeui_cfg   *usp = (struct netbeui_cfg *) arg;

			if (!suser())
				return (-EACCES);

			rc = verify_area(VERIFY_READ, usp,
			                         sizeof(struct netbeui_cfg));
			if (rc)
				return rc;

			return (nbcs_config(usp));
		}

		default:
                        /* Pass ioctl command to next layer */
/* NOT IMPLEMENTED */
//                      return (ll___ioctl(cmd, arg));
//			return (dev_ioctl(cmd, arg));

	}

	return (-EOPNOTSUPP);
}
