/*****************************************************************************
* wanproc.c	WAN Router Module. /proc filesystem interface.
*
*		This module is completely hardware-independent and provides
*		access to the router using Linux /proc filesystem.
*
* Author: 	Gideon Hack	
*
* Copyright:	(c) 1995-1999 Sangoma Technologies Inc.
*
*		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.
* ============================================================================
* Jun 02, 1999  Gideon Hack	Updates for Linux 2.2.X kernels.
* Jun 29, 1997	Alan Cox	Merged with 1.0.3 vendor code
* Jan 29, 1997	Gene Kozin	v1.0.1. Implemented /proc read routines
* Jan 30, 1997	Alan Cox	Hacked around for 2.1
* Dec 13, 1996	Gene Kozin	Initial version (based on Sangoma's WANPIPE)
*****************************************************************************/

#include <linux/version.h>
#include <linux/config.h>
#include <linux/stddef.h>	/* offsetof(), etc. */
#include <linux/errno.h>	/* return codes */
#include <linux/kernel.h>
#include <linux/slab.h>	/* kmalloc(), kfree() */
#include <linux/mm.h>		/* verify_area(), etc. */
#include <linux/string.h>	/* inline mem*, str* functions */
#include <asm/byteorder.h>	/* htons(), etc. */
#include <asm/io.h>
#include <linux/wanrouter.h>	/* WAN router API definitions */



#if defined(LINUX_2_1) || defined(LINUX_2_4) 
 #include <linux/init.h>	/* __initfunc et al. */
 #include <asm/uaccess.h>       /* copy_to_user */
 #define PROC_STATS_FORMAT "%30s: %12lu\n"
#else
 #define PROC_STATS_FORMAT "%30s: %12u\n"
 #include <asm/segment.h>	/* kernel <-> user copy */
#endif


/****** Defines and Macros **************************************************/

#define	PROC_BUFSZ	4000	/* buffer size for printing proc info */

#define PROT_DECODE(prot) ((prot == WANCONFIG_FR) ? " FR" :\
			      (prot == WANCONFIG_X25) ? " X25" : \
			         (prot == WANCONFIG_PPP) ? " PPP" : \
				    (prot == WANCONFIG_CHDLC) ? " CHDLC": \
				       (prot == WANCONFIG_MPPP) ? " MPPP" : \
				           " Unknown" )
	
/****** Data Types **********************************************************/

typedef struct wan_stat_entry
{
	struct wan_stat_entry *next;
	char *description;		/* description string */
	void *data;			/* -> data */
	unsigned data_type;		/* data type */
} wan_stat_entry_t;

/****** Function Prototypes *************************************************/

#ifdef CONFIG_PROC_FS


#ifdef LINUX_2_4  /* Start of LINUX 2.4.X code */


	/* Proc filesystem interface */
	static int router_proc_perms(struct inode *, int);
	static ssize_t router_proc_read(struct file* file, char* buf, size_t count, 					loff_t *ppos);

	/* Methods for preparing data for reading proc entries */

	static int config_get_info(char* buf, char** start, off_t offs, int len);
	static int status_get_info(char* buf, char** start, off_t offs, int len);
	static int wandev_get_info(char* buf, char** start, off_t offs, int len);

	/* Miscellaneous */

	/*
	 *	Structures for interfacing with the /proc filesystem.
	 *	Router creates its own directory /proc/net/router with the folowing
	 *	entries:
	 *	config		device configuration
	 *	status		global device statistics
	 *	<device>	entry for each WAN device
	 */

	/*
	 *	Generic /proc/net/router/<file> file and inode operations 
	 */
	static struct file_operations router_fops =
	{
		read:		router_proc_read,
	};

	static struct inode_operations router_inode =
	{
		permission:	router_proc_perms,
	};

	/*
	 *	/proc/net/router/<device> file operations
	 */

	static struct file_operations wandev_fops =
	{
		read:		router_proc_read,
		ioctl:		wanrouter_ioctl,
	};

	/*
	 *	/proc/net/router 
	 */

	static struct proc_dir_entry *proc_router;

	/* Strings */
	static char conf_hdr[] =
		"Device name    | port |IRQ|DMA|  mem.addr  |mem.size|"
		"option1|option2|option3|option4\n";
		
	static char stat_hdr[] =
		"Device name    |protocol|station|interface|clocking|baud rate"
		"| MTU |ndev|link state\n";


	/*
	 *	Interface functions
	 */

	/*
	 *	Initialize router proc interface.
	 */

	int __init wanrouter_proc_init (void)
	{
		struct proc_dir_entry *p;
		proc_router = proc_mkdir(ROUTER_NAME, proc_net);
		if (!proc_router)
			goto fail;

		p = create_proc_entry("config",0,proc_router);
		if (!p)
			goto fail_config;
		p->proc_fops = &router_fops;
		p->proc_iops = &router_inode;
		p->get_info = config_get_info;
		p = create_proc_entry("status",0,proc_router);
		if (!p)
			goto fail_stat;
		p->proc_fops = &router_fops;
		p->proc_iops = &router_inode;
		p->get_info = status_get_info;
		return 0;
	fail_stat:
		remove_proc_entry("config", proc_router);
	fail_config:
		remove_proc_entry(ROUTER_NAME, proc_net);
	fail:
		return -ENOMEM;
	}

	/*
	 *	Clean up router proc interface.
	 */

	void wanrouter_proc_cleanup (void)
	{
		remove_proc_entry("config", proc_router);
		remove_proc_entry("status", proc_router);
		remove_proc_entry(ROUTER_NAME,proc_net);
	}

	/*
	 *	Add directory entry for WAN device.
	 */

	int wanrouter_proc_add (wan_device_t* wandev)
	{
		if (wandev->magic != ROUTER_MAGIC)
			return -EINVAL;
			
		wandev->dent = create_proc_entry(wandev->name, 0, proc_router);
		if (!wandev->dent)
			return -ENOMEM;
		wandev->dent->proc_fops	= &wandev_fops;
		wandev->dent->proc_iops	= &router_inode;
		wandev->dent->get_info	= wandev_get_info;
		wandev->dent->data	= wandev;
		return 0;
	}

	/*
	 *	Delete directory entry for WAN device.
	 */
	 
	int wanrouter_proc_delete(wan_device_t* wandev)
	{
		if (wandev->magic != ROUTER_MAGIC)
			return -EINVAL;
		remove_proc_entry(wandev->name, proc_router);
		return 0;
	}

	/****** Proc filesystem entry points ****************************************/

	/*
	 *	Verify access rights.
	 */

	static int router_proc_perms (struct inode* inode, int op)
	{
		return 0;
	}

	/*
	 *	Read router proc directory entry.
	 *	This is universal routine for reading all entries in /proc/net/wanrouter
	 *	directory.  Each directory entry contains a pointer to the 'method' for
	 *	preparing data for that entry.
	 *	o verify arguments
	 *	o allocate kernel buffer
	 *	o call get_info() to prepare data
	 *	o copy data to user space
	 *	o release kernel buffer
	 *
	 *	Return:	number of bytes copied to user space (0, if no data)
	 *		<0	error
	 */

	static ssize_t router_proc_read(struct file* file, char* buf, size_t count,
					loff_t *ppos)
	{
		struct inode *inode = file->f_dentry->d_inode;
		struct proc_dir_entry* dent;
		char* page;
		int pos, len;
		loff_t n = *ppos;
		unsigned offs = n;

		if (count <= 0)
			return 0;
			
		dent = inode->u.generic_ip;
		if ((dent == NULL) || (dent->get_info == NULL))
			return 0;
			
		page = kmalloc(PROC_BUFSZ, GFP_KERNEL);
		if (page == NULL)
			return -ENOBUFS;
			
		pos = dent->get_info(page, dent->data, 0, 0);
		if (offs == n && offs < pos) {
			len = min_t(unsigned int, pos - offs, count);
			if (copy_to_user(buf, (page + offs), len)) {
				kfree(page);
				return -EFAULT;
			}
			*ppos = offs + len;
		}
		else
			len = 0;
		kfree(page);
		return len;
	}

	/*
	 *	Prepare data for reading 'Config' entry.
	 *	Return length of data.
	 */

	static int config_get_info(char* buf, char** start, off_t offs, int len)
	{
		int cnt = sizeof(conf_hdr) - 1;
		wan_device_t* wandev;
		strcpy(buf, conf_hdr);
		for (wandev = router_devlist;
		     wandev && (cnt < (PROC_BUFSZ - 120));
		     wandev = wandev->next) {
			if (wandev->state) cnt += sprintf(&buf[cnt],
				"%-15s|0x%-4X|%3u|%3u| 0x%-8lX |0x%-6X|%7u|%7u|%7u|%7u\n",
				wandev->name,
				wandev->ioport,
				wandev->irq,
				wandev->dma,
				wandev->maddr,
				wandev->msize,
				wandev->hw_opt[0],
				wandev->hw_opt[1],
				wandev->hw_opt[2],
				wandev->hw_opt[3]);
		}

		return cnt;
	}

	/*
	 *	Prepare data for reading 'Status' entry.
	 *	Return length of data.
	 */

	static int status_get_info(char* buf, char** start, off_t offs, int len)
	{
		int cnt = 0;
		wan_device_t* wandev;

		//cnt += sprintf(&buf[cnt], "\nSTATUS:\n\n");
		strcpy(&buf[cnt], stat_hdr);
		cnt += sizeof(stat_hdr) - 1;

		for (wandev = router_devlist;
		     wandev && (cnt < (PROC_BUFSZ - 80));
		     wandev = wandev->next) {
			if (!wandev->state) continue;
			cnt += sprintf(&buf[cnt],
				"%-15s|%-8s|%-7s|%-9s|%-8s|%9u|%5u|%3u |",
				wandev->name,
				PROT_DECODE(wandev->config_id),
				wandev->config_id == WANCONFIG_FR ? 
					(wandev->station ? " Node" : " CPE") :
					(wandev->config_id == WANCONFIG_X25 ?
					(wandev->station ? " DCE" : " DTE") :
					(" N/A")),
				wandev->interface ? " V.35" : " RS-232",
				wandev->clocking ? "internal" : "external",
				wandev->bps,
				wandev->mtu,
				wandev->ndev);

			switch (wandev->state) {

			case WAN_UNCONFIGURED:
				cnt += sprintf(&buf[cnt], "%-12s\n", "unconfigured");
				break;

			case WAN_DISCONNECTED:
				cnt += sprintf(&buf[cnt], "%-12s\n", "disconnected");
				break;

			case WAN_CONNECTING:
				cnt += sprintf(&buf[cnt], "%-12s\n", "connecting");
				break;

			case WAN_CONNECTED:
				cnt += sprintf(&buf[cnt], "%-12s\n", "connected");
				break;

			default:
				cnt += sprintf(&buf[cnt], "%-12s\n", "invalid");
				break;
			}
		}
		return cnt;
	}

	/*
	 *	Prepare data for reading <device> entry.
	 *	Return length of data.
	 *
	 *	On entry, the 'start' argument will contain a pointer to WAN device
	 *	data space.
	 */

	static int wandev_get_info(char* buf, char** start, off_t offs, int len)
	{
		wan_device_t* wandev = (void*)start;
		int cnt = 0;
		int rslt = 0;

		if ((wandev == NULL) || (wandev->magic != ROUTER_MAGIC))
			return 0;
		if (!wandev->state)
			return sprintf(&buf[cnt], "device is not configured!\n");

		/* Update device statistics */
		if (wandev->update) {

			rslt = wandev->update(wandev);
			if(rslt) {
				switch (rslt) {
				case -EAGAIN:
					return sprintf(&buf[cnt], "Device is busy!\n");

				default:
					return sprintf(&buf[cnt],
						"Device is not configured!\n");
				}
			}
		}

		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"total packets received", wandev->stats.rx_packets);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"total packets transmitted", wandev->stats.tx_packets);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"total bytes received", wandev->stats.rx_bytes);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"total bytes transmitted", wandev->stats.tx_bytes);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"bad packets received", wandev->stats.rx_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"packet transmit problems", wandev->stats.tx_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"received frames dropped", wandev->stats.rx_dropped);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"transmit frames dropped", wandev->stats.tx_dropped);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"multicast packets received", wandev->stats.multicast);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"transmit collisions", wandev->stats.collisions);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"receive length errors", wandev->stats.rx_length_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"receiver overrun errors", wandev->stats.rx_over_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"CRC errors", wandev->stats.rx_crc_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"frame format errors (aborts)", wandev->stats.rx_frame_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"receiver fifo overrun", wandev->stats.rx_fifo_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"receiver missed packet", wandev->stats.rx_missed_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"aborted frames transmitted", wandev->stats.tx_aborted_errors);
		return cnt;
	}


#else /* ------------------- END OF LINUX 2.4.X VERSION -------------*/



	/* Proc filesystem interface */
	static int router_proc_perms(struct inode *, int);
#ifdef LINUX_2_1
	static ssize_t router_proc_read(struct file *file, char *buf, size_t count, 					loff_t *ppos);
#else
	static int router_proc_read(
		struct inode* inode, struct file* file, char* buf, int count);
	static int device_write(
		struct inode* inode, struct file* file, const char* buf, int count);
#endif

	/* Methods for preparing data for reading proc entries */
	static int config_get_info(char* buf, char** start, off_t offs, int len,
		int dummy);
	static int status_get_info(char* buf, char** start, off_t offs, int len,
		int dummy);
	static int wandev_get_info(char* buf, char** start, off_t offs, int len,
		int dummy);

	/* Miscellaneous */

	/*
	 *	Global Data
	 */

	/*
	 *	Names of the proc directory entries 
	 */

	static char name_root[]	= ROUTER_NAME;
	static char name_conf[]	= "config";
	static char name_stat[]	= "status";

	/*
	 *	Structures for interfacing with the /proc filesystem.
	 *	Router creates its own directory /proc/net/router with the folowing
	 *	entries:
	 *	config		device configuration
	 *	status		global device statistics
	 *	<device>	entry for each WAN device
	 */

	/*
	 *	Generic /proc/net/router/<file> file and inode operations 
	 */
#ifdef LINUX_2_1
	static struct file_operations router_fops =
	{
		NULL,			/* lseek   */
		router_proc_read,	/* read	   */
		NULL,			/* write   */
		NULL,			/* readdir */
		NULL,			/* select  */
		NULL,			/* ioctl   */
		NULL,			/* mmap	   */
		NULL,			/* no special open code	   */
		NULL,			/* flush */
		NULL,			/* no special release code */
		NULL			/* can't fsync */
	};
#else
	static struct file_operations router_fops =
	{
		NULL,                   /* lseek   */
		router_proc_read,       /* read    */
		NULL,                   /* write   */
		NULL,                   /* readdir */
		NULL,                   /* select  */
		NULL,                   /* ioctl   */
		NULL,                   /* mmap    */
		NULL,                   /* no special open code    */
		NULL,                   /* no special release code */
		NULL                    /* can't fsync */
	};
#endif

	static struct inode_operations router_inode =
	{
		&router_fops,
		NULL,			/* create */
		NULL,			/* lookup */
		NULL,			/* link */
		NULL,			/* unlink */
		NULL,			/* symlink */
		NULL,			/* mkdir */
		NULL,			/* rmdir */
		NULL,			/* mknod */
		NULL,			/* rename */
		NULL,			/* follow link */
		NULL,			/* readlink */
		NULL,			/* readpage */
		NULL,			/* writepage */
		NULL,			/* bmap */
		NULL,			/* truncate */
		router_proc_perms
	};

	/*
	 *	/proc/net/router/<device> file and inode operations
	 */

#ifdef LINUX_2_1
	static struct file_operations wandev_fops =
	{
		NULL,			/* lseek   */
		router_proc_read,	/* read	   */
		NULL,			/* write   */
		NULL,			/* readdir */
		NULL,			/* select  */
		wanrouter_ioctl,	/* ioctl   */
		NULL,			/* mmap	   */
		NULL,			/* no special open code	   */
		NULL,			/* flush */
		NULL,			/* no special release code */
		NULL			/* can't fsync */
	};
#else
	static struct file_operations wandev_fops =
	{
		NULL,                   /* lseek   */
		router_proc_read,       /* read    */
		device_write,           /* write   */
		NULL,                   /* readdir */
		NULL,                   /* select  */
		wanrouter_ioctl,        /* ioctl   */
		NULL,                   /* mmap    */
		NULL,                   /* no special open code    */
		NULL,                   /* no special release code */
		NULL                    /* can't fsync */
	};
#endif

	static struct inode_operations wandev_inode =
	{
		&wandev_fops,
		NULL,			/* create */
		NULL,			/* lookup */
		NULL,			/* link */
		NULL,			/* unlink */
		NULL,			/* symlink */
		NULL,			/* mkdir */
		NULL,			/* rmdir */
		NULL,			/* mknod */
		NULL,			/* rename */
		NULL,			/* readlink */
		NULL,			/* follow_link */
		NULL,			/* readpage */
		NULL,			/* writepage */
		NULL,			/* bmap */
		NULL,			/* truncate */
		router_proc_perms
	};

	/*
	 * Proc filesystem derectory entries.
	 */

	/*
	 *	/proc/net/router 
	 */
	 
	static struct proc_dir_entry proc_router =
	{
		0,			/* .low_ino */
		sizeof(name_root) - 1,	/* .namelen */
		name_root,		/* .name */
		0555 | S_IFDIR,		/* .mode */
		2,			/* .nlink */
		0,			/* .uid */
		0,			/* .gid */
		0,			/* .size */
		&proc_dir_inode_operations, /* .ops */
		NULL,			/* .get_info */
		NULL,			/* .fill_node */
		NULL,			/* .next */
		NULL,			/* .parent */
		NULL,			/* .subdir */
		NULL,			/* .data */
	};

	/*
	 *	/proc/net/router/config 
	 */
	 
	static struct proc_dir_entry proc_router_conf =
	{
		0,			/* .low_ino */
		sizeof(name_conf) - 1,	/* .namelen */
		name_conf,		/* .name */
		0444 | S_IFREG,		/* .mode */
		1,			/* .nlink */
		0,			/* .uid */
		0,			/* .gid */
		0,			/* .size */
		&router_inode,		/* .ops */
		&config_get_info,	/* .get_info */
		NULL,			/* .fill_node */
		NULL,			/* .next */
		NULL,			/* .parent */
		NULL,			/* .subdir */
		NULL,			/* .data */
	};

	/*
	 *	/proc/net/router/status 
	 */
	 
	static struct proc_dir_entry proc_router_stat =
	{
		0,			/* .low_ino */
		sizeof(name_stat) - 1,	/* .namelen */
		name_stat,		/* .name */
		0444 | S_IFREG,		/* .mode */
		1,			/* .nlink */
		0,			/* .uid */
		0,			/* .gid */
		0,			/* .size */
		&router_inode,		/* .ops */
		status_get_info,	/* .get_info */
		NULL,			/* .fill_node */
		NULL,			/* .next */
		NULL,			/* .parent */
		NULL,			/* .subdir */
		NULL,			/* .data */
	};

	/* Strings */
	static char conf_hdr[] =
		"Device name    | port |IRQ|DMA|  mem.addr  |mem.size|"
		"option1|option2|option3|option4\n";
		
	static char stat_hdr[] =
		"Device name    |protocol|station|interface|clocking|baud rate| MTU |ndev"
		"|link state\n";


	/*
	 *	Interface functions
	 */

	/*
	 *	Initialize router proc interface.
	 */

#ifdef LINUX_2_1
	__initfunc(int wanrouter_proc_init (void))
	{
		int err = proc_register(proc_net, &proc_router);

		if (!err) {
			proc_register(&proc_router, &proc_router_conf);
			proc_register(&proc_router, &proc_router_stat);
		}
		return err;
	}
#else
	int wanrouter_proc_init (void)
	{
		int err = proc_register_dynamic(&proc_net, &proc_router);

		if (!err) {
			proc_register_dynamic(&proc_router, &proc_router_conf);
			proc_register_dynamic(&proc_router, &proc_router_stat);
		}
		return err;
	}
#endif

	/*
	 *	Clean up router proc interface.
	 */

	void wanrouter_proc_cleanup (void)
	{
		proc_unregister(&proc_router, proc_router_conf.low_ino);
		proc_unregister(&proc_router, proc_router_stat.low_ino);
#ifdef LINUX_2_1
		proc_unregister(proc_net, proc_router.low_ino);
#else
		proc_unregister(&proc_net, proc_router.low_ino);
#endif
	}

	/*
	 *	Add directory entry for WAN device.
	 */

	int wanrouter_proc_add (wan_device_t* wandev)
	{
		if (wandev->magic != ROUTER_MAGIC)
			return -EINVAL;
		
		memset(&wandev->dent, 0, sizeof(wandev->dent));
		wandev->dent.namelen	= strlen(wandev->name);
		wandev->dent.name	= wandev->name;
		wandev->dent.mode	= 0444 | S_IFREG;
		wandev->dent.nlink	= 1;
		wandev->dent.ops	= &wandev_inode;
		wandev->dent.get_info	= &wandev_get_info;
		wandev->dent.data	= wandev;
#ifdef LINUX_2_1
		return proc_register(&proc_router, &wandev->dent);
#else
		return proc_register_dynamic(&proc_router, &wandev->dent);
#endif
	}

	/*
	 *	Delete directory entry for WAN device.
	 */
	 
	int wanrouter_proc_delete(wan_device_t* wandev)
	{
		if (wandev->magic != ROUTER_MAGIC)
			return -EINVAL;
		proc_unregister(&proc_router, wandev->dent.low_ino);
		return 0;
	}

	/****** Proc filesystem entry points ****************************************/

	/*
	 *	Verify access rights.
	 */

	static int router_proc_perms (struct inode* inode, int op)
	{
		return 0;
	}

	/*
	 *	Read router proc directory entry.
	 *	This is universal routine for reading all entries in /proc/net/wanrouter
	 *	directory.  Each directory entry contains a pointer to the 'method' for
	 *	preparing data for that entry.
	 *	o verify arguments
	 *	o allocate kernel buffer
	 *	o call get_info() to prepare data
	 *	o copy data to user space
	 *	o release kernel buffer
	 *
	 *	Return:	number of bytes copied to user space (0, if no data)
	 *		<0	error
	 */
#ifdef LINUX_2_1
	static ssize_t router_proc_read(struct file* file, char* buf, size_t count,
					loff_t *ppos)
	{
		struct inode *inode = file->f_dentry->d_inode;
		struct proc_dir_entry* dent;
		char* page;
		int pos, offs, len;

		if (count <= 0)
			return 0;
			
		dent = inode->u.generic_ip;
		if ((dent == NULL) || (dent->get_info == NULL))
			return 0;
			
		page = kmalloc(PROC_BUFSZ, GFP_KERNEL);
		if (page == NULL)
			return -ENOBUFS;
			
		pos = dent->get_info(page, dent->data, 0, 0, 0);
		offs = file->f_pos;
		if (offs < pos) {
			len = min_t(unsigned int, pos - offs, count);
			if (copy_to_user(buf, (page + offs), len)) {
				kfree(page);
				return -EFAULT;
			}
			file->f_pos += len;
		}
		else
			len = 0;
		kfree(page);
		return len;
	}

#else
	static int router_proc_read(
		struct inode* inode, struct file* file, char* buf, int count)
	{
		struct proc_dir_entry* dent;
		char* page;
		int err, pos, offs, len;

		if (count <= 0)
			return 0;
		dent = inode->u.generic_ip;
		if ((dent == NULL) || (dent->get_info == NULL))
			return -ENODATA;
		err = verify_area(VERIFY_WRITE, buf, count);
		if (err) return err;

		page = kmalloc(PROC_BUFSZ, GFP_KERNEL);
		if (page == NULL)
			return -ENOMEM;

		pos = dent->get_info(page, dent->data, 0, 0, 0);
		offs = file->f_pos;
		if (offs < pos) {
			len = min_t(unsigned int, pos - offs, count);
			memcpy_tofs((void*)buf, (void*)(page + offs), len);
			file->f_pos += len;
		}
		else len = 0;
		kfree(page);
		return len;
	}
#endif


	/*
	 *	Prepare data for reading 'Config' entry.
	 *	Return length of data.
	 */

	static int config_get_info(char* buf, char** start, off_t offs, int len, 
		int dummy)
	{
		int cnt = sizeof(conf_hdr) - 1;
		wan_device_t* wandev;
		strcpy(buf, conf_hdr);
		for (wandev = router_devlist;
		     wandev && (cnt < (PROC_BUFSZ - 120));
		     wandev = wandev->next) {
			if (wandev->state) cnt += sprintf(&buf[cnt],
				"%-15s|0x%-4X|%3u|%3u| 0x%-8lX |0x%-6X|%7u|%7u|%7u|%7u\n",
				wandev->name,
				wandev->ioport,
				wandev->irq,
				wandev->dma,
				wandev->maddr,
				wandev->msize,
				wandev->hw_opt[0],
				wandev->hw_opt[1],
				wandev->hw_opt[2],
				wandev->hw_opt[3]);
		}

		return cnt;
	}

	/*
	 *	Prepare data for reading 'Status' entry.
	 *	Return length of data.
	 */

	static int status_get_info(char* buf, char** start, off_t offs, int len, 
				int dummy)
	{
		int cnt = 0;
		wan_device_t* wandev;

		//cnt += sprintf(&buf[cnt], "\nSTATUS:\n\n");
		strcpy(&buf[cnt], stat_hdr);
		cnt += sizeof(stat_hdr) - 1;

		for (wandev = router_devlist;
		     wandev && (cnt < (PROC_BUFSZ - 80));
		     wandev = wandev->next) {
			if (!wandev->state) continue;
			cnt += sprintf(&buf[cnt],
				"%-15s|%-8s|%-7s|%-9s|%-8s|%9u|%5u|%3u |",
				wandev->name,
				PROT_DECODE(wandev->config_id),
				wandev->config_id == WANCONFIG_FR ? 
					(wandev->station ? " Node" : " CPE") :
					(wandev->config_id == WANCONFIG_X25 ?
					(wandev->station ? " DCE" : " DTE") :
					(" N/A")),
				wandev->interface ? " V.35" : " RS-232",
				wandev->clocking ? "internal" : "external",
				wandev->bps,
				wandev->mtu,
				wandev->ndev);

			switch (wandev->state) {

			case WAN_UNCONFIGURED:
				cnt += sprintf(&buf[cnt], "%-12s\n", "unconfigured");
				break;

			case WAN_DISCONNECTED:
				cnt += sprintf(&buf[cnt], "%-12s\n", "disconnected");
				break;

			case WAN_CONNECTING:
				cnt += sprintf(&buf[cnt], "%-12s\n", "connecting");
				break;

			case WAN_CONNECTED:
				cnt += sprintf(&buf[cnt], "%-12s\n", "connected");
				break;

			case WAN_FT1_READY:
				cnt += sprintf(&buf[cnt], "%-12s\n", "ft1 ready");
				break;

			default:
				cnt += sprintf(&buf[cnt], "%-12s\n", "invalid");
				break;
			}
		}
		return cnt;
	}

	/*
	 *	Prepare data for reading <device> entry.
	 *	Return length of data.
	 *
	 *	On entry, the 'start' argument will contain a pointer to WAN device
	 *	data space.
	 */

	static int wandev_get_info(char* buf, char** start, off_t offs, int len, 
				int dummy)
	{
		wan_device_t* wandev = (void*)start;
		int cnt = 0;
		int rslt = 0;

		if ((wandev == NULL) || (wandev->magic != ROUTER_MAGIC))
			return 0;
		if (!wandev->state)
			return sprintf(&buf[cnt], "Device is not configured!\n");

		/* Update device statistics */
		if (wandev->update) {

			rslt = wandev->update(wandev);
			if(rslt) {
				switch (rslt) {
				case -EAGAIN:
					return sprintf(&buf[cnt], "Device is busy!\n");

				default:
					return sprintf(&buf[cnt],
						"Device is not configured!\n");
				}
			}
		}

		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"total packets received", wandev->stats.rx_packets);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"total packets transmitted", wandev->stats.tx_packets);
#ifdef LINUX_2_1
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"total bytes received", wandev->stats.rx_bytes);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"total bytes transmitted", wandev->stats.tx_bytes);
#endif
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"bad packets received", wandev->stats.rx_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"packet transmit problems", wandev->stats.tx_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"received frames dropped", wandev->stats.rx_dropped);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"transmit frames dropped", wandev->stats.tx_dropped);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"multicast packets received", wandev->stats.multicast);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"transmit collisions", wandev->stats.collisions);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"receive length errors", wandev->stats.rx_length_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"receiver overrun errors", wandev->stats.rx_over_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"CRC errors", wandev->stats.rx_crc_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"frame format errors (aborts)", wandev->stats.rx_frame_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"receiver fifo overrun", wandev->stats.rx_fifo_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"receiver missed packet", wandev->stats.rx_missed_errors);
		cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
			"aborted frames transmitted", wandev->stats.tx_aborted_errors);

		return cnt;
	}

#endif /* End of ifdef LINUX_2_4 */


#else

/*
 *	No /proc - output stubs
 */

int __init wanrouter_proc_init(void)
{
	return 0;
}

void wanrouter_proc_cleanup(void)
{
	return;
}

int wanrouter_proc_add(wan_device_t *wandev)
{
	return 0;
}

int wanrouter_proc_delete(wan_device_t *wandev)
{
	return 0;
}

#endif

/*============================================================================
 * Write WAN device ???.
 * o Find WAN device associated with this node
 */
#ifdef LINUX_2_0
static int device_write(
        struct inode* inode, struct file* file, const char* buf, int count)
{
        int err = verify_area(VERIFY_READ, buf, count);
        struct proc_dir_entry* dent;
        wan_device_t* wandev;

        if (err) return err;

        dent = inode->u.generic_ip;
        if ((dent == NULL) || (dent->data == NULL))
                return -ENODATA;

        wandev = dent->data;

        printk(KERN_ERR "%s: writing %d bytes to %s...\n",
                name_root, count, dent->name);
        
	return 0;
}
#endif

/*
 *	End
 */
 
