/* $Id: ifconfig_net.c 1824 2005-05-10 07:30:07Z hg $
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 *  ifconfig_net - SGI's Persistent Network Device names.
 *
 * Copyright (C) 1992-1997, 2000-2003 Silicon Graphics, Inc.  All rights reserved.
 */

#include <linux/types.h>
#include <linux/config.h>
#include <linux/slab.h>
#include <linux/ctype.h>
#include <linux/module.h>
#include <linux/init.h>

#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>

#include <asm/sn/sgi.h>
#include <asm/uaccess.h>
#include <linux/devfs_fs.h>
#include <linux/devfs_fs_kernel.h>
#include <asm/io.h>
#include <asm/sn/iograph.h>
#include <asm/sn/invent.h>
#include <asm/sn/hcl.h>
#include <asm/sn/labelcl.h>
#include <asm/sn/ifconfig_net.h>

#define SGI_IFCONFIG_NET "SGI-PERSISTENT NETWORK DEVICE NAME DRIVER"
#define SGI_IFCONFIG_NET_VERSION "1.0"

/*
 * Some Global definitions.
 */
static vertex_hdl_t ifconfig_net_handle;
static unsigned long ifconfig_net_debug;
static struct ifname_MAC *new_devices;
static struct ifname_num *ifname_num;


/*
 * ifconfig_net_open - Opens the special device node "/devhw/.ifconfig_net".
 */
static int ifconfig_net_open(struct inode * inode, struct file * filp)
{
	if (ifconfig_net_debug) {
        	printk("ifconfig_net_open called.\n");
	}

        return(0);

}

/*
 * ifconfig_net_close - Closes the special device node "/devhw/.ifconfig_net".
 */
static int ifconfig_net_close(struct inode * inode, struct file * filp)
{

	if (ifconfig_net_debug) {
        	printk("ifconfig_net_close called.\n");
	}

        return(0);
}

/*
 * assign_ifname - Assign the next available interface name from the persistent list.
 */
void
assign_ifname(struct net_device *dev,
		  struct ifname_num *ifname_num)

{

	/*
	 * Handle eth devices.
	 */
        if ( (memcmp(dev->name, "eth", 3) == 0) ) {
		if (ifname_num->next_eth != -1) {
			/*
			 * Assign it the next available eth interface number. 
			 */
			memset(dev->name, 0, strlen(dev->name));
			sprintf(dev->name, "eth%d", (int)ifname_num->next_eth);
			ifname_num->next_eth++;
		} 

                return;
        }

	/*
	 * Handle fddi devices.
	 */
	if ( (memcmp(dev->name, "fddi", 4) == 0) ) {
		if (ifname_num->next_fddi != -1) {
			/*
			 * Assign it the next available fddi interface number.
			 */
			memset(dev->name, 0, strlen(dev->name));
			sprintf(dev->name, "fddi%d", (int)ifname_num->next_fddi);
			ifname_num->next_fddi++;
		}

		return;
	}

	/*
	 * Handle hip devices.
	 */
	if ( (memcmp(dev->name, "hip", 3) == 0) ) {
		if (ifname_num->next_hip != -1) {
			/*
			 * Assign it the next available hip interface number.
			 */
			memset(dev->name, 0, strlen(dev->name));
			sprintf(dev->name, "hip%d", (int)ifname_num->next_hip);
			ifname_num->next_hip++;
		}

		return;
	}

	/*
	 * Handle tr devices.
	 */
	if ( (memcmp(dev->name, "tr", 2) == 0) ) {
		if (ifname_num->next_tr != -1) {
			/*
			 * Assign it the next available tr interface number.
			 */
			memset(dev->name, 0, strlen(dev->name));
			sprintf(dev->name, "tr%d", (int)ifname_num->next_tr);
			ifname_num->next_tr++;
		}

		return;
	}

	/*
	 * Handle fc devices.
	 */
	if ( (memcmp(dev->name, "fc", 2) == 0) ) {
		if (ifname_num->next_fc != -1) {
			/*
			 * Assign it the next available fc interface number.
			 */
			memset(dev->name, 0, strlen(dev->name));
			sprintf(dev->name, "fc%d", (int)ifname_num->next_fc);
			ifname_num->next_fc++;
		}

		return;
	}
}

/*
 * find_persistent_ifname: Returns the entry that was seen in previous boot.
 */
struct ifname_MAC *
find_persistent_ifname(struct net_device *dev,
	struct ifname_MAC *ifname_MAC)

{

	while (ifname_MAC->addr_len) {
		if (memcmp(dev->dev_addr, ifname_MAC->dev_addr, dev->addr_len) == 0)
			return(ifname_MAC);

		ifname_MAC++;
	}

	return(NULL);
}

/*
 * ifconfig_net_ioctl: ifconfig_net driver ioctl interface.
 */
static int ifconfig_net_ioctl(struct inode * inode, struct file * file,
        unsigned int cmd, unsigned long arg)
{

	extern struct net_device *__dev_get_by_name(const char *);
#ifdef CONFIG_NET
	struct net_device *dev;
	struct ifname_MAC *found;
	char temp[64];
#endif
	struct ifname_MAC *ifname_MAC;
	struct ifname_MAC *temp_new_devices;
	unsigned long size;


	if (ifconfig_net_debug) {
		printk("HCL: hcl_ioctl called.\n");
	}

	/*
	 * Read in the header and see how big of a buffer we really need to 
	 * allocate.
	 */
	ifname_num = (struct ifname_num *) kmalloc(sizeof(struct ifname_num), 
			GFP_KERNEL);
	if (copy_from_user( ifname_num, (char *) arg, sizeof(struct ifname_num)))
		return -EFAULT;
	size = ifname_num->size;
	kfree(ifname_num);
	ifname_num = (struct ifname_num *) kmalloc(size, GFP_KERNEL);
	if (ifname_num <= 0)
		return -ENOMEM;
	ifname_MAC = (struct ifname_MAC *) ((char *)ifname_num + (sizeof(struct ifname_num)) );

	if (copy_from_user( ifname_num, (char *) arg, size)){
		kfree(ifname_num);
		return -EFAULT;
	}
	new_devices =  kmalloc(size - sizeof(struct ifname_num), GFP_KERNEL);
	if (new_devices <= 0){
		kfree(ifname_num);
		return -ENOMEM;
	}
	temp_new_devices = new_devices;

	memset(new_devices, 0, size - sizeof(struct ifname_num));

#ifdef CONFIG_NET
	/*
	 * Go through the net device entries and make them persistent!
	 */
	for (dev = dev_base; dev != NULL; dev = dev->next) {
		/*
		 * Skip NULL entries or "lo"
		 */
		if ( (dev->addr_len == 0) || ( !strncmp(dev->name, "lo", strlen(dev->name))) ){
			continue;
		}

		/*
		 * See if we have a persistent interface name for this device.
		 */
		found = NULL;
		found = find_persistent_ifname(dev, ifname_MAC);
		if (found) {
			strcpy(dev->name, found->name);
		} else {
			/* Never seen this before .. */
			assign_ifname(dev, ifname_num);

			/* 
			 * Save the information for the next boot.
			 */
 			sprintf(temp,"%s %02x:%02x:%02x:%02x:%02x:%02x\n", dev->name,
				dev->dev_addr[0],  dev->dev_addr[1],  dev->dev_addr[2],
				dev->dev_addr[3],  dev->dev_addr[4],  dev->dev_addr[5]);
			strcpy(temp_new_devices->name, dev->name);
			temp_new_devices->addr_len = dev->addr_len;
			memcpy(temp_new_devices->dev_addr, dev->dev_addr, dev->addr_len);
			temp_new_devices++;
		}
		
	}
#endif

	/*
	 * Copy back to the User Buffer area any new devices encountered.
	 */
	kfree(new_devices);
	return copy_to_user((char *)arg + (sizeof(struct ifname_num)), new_devices, 
			size - sizeof(struct ifname_num))?-EFAULT:0;


}

struct file_operations ifconfig_net_fops = {
	ioctl:ifconfig_net_ioctl,	/* ioctl */
	open:ifconfig_net_open,		/* open */
	release:ifconfig_net_close	/* release */
};


/*
 * init_ifconfig_net() - Boot time initialization.  Ensure that it is called 
 *	after devfs has been initialized.
 *
 */
#ifdef MODULE
int init_module (void)
#else
int __init init_ifconfig_net(void)
#endif
{
	ifconfig_net_handle = NULL;
	ifconfig_net_handle = hwgraph_register(hwgraph_root, ".ifconfig_net",
			0, DEVFS_FL_AUTO_DEVNUM,
			0, 0,
			S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, 0, 0,
			&ifconfig_net_fops, NULL);

	if (ifconfig_net_handle == NULL) {
		panic("Unable to create SGI PERSISTENT NETWORK DEVICE Name Driver.\n");
	}

	return(0);

}
