/*
 * Device driver framework for the COMX line of synchronous serial boards
 * 
 * for Linux kernel 2.2.X / 2.4.X
 *
 * Original authors:  Arpad Bakay <bakay.arpad@synergon.hu>,
 *                    Peter Bajan <bajan.peter@synergon.hu>,
 * Previous maintainer: Tivadar Szemethy <tiv@itc.hu>
 * Current maintainer: Gergely Madarasz <gorgo@itc.hu>
 *
 * Copyright (C) 1995-1999 ITConsult-Pro Co.
 *
 * Contributors:
 * Arnaldo Carvalho de Melo <acme@conectiva.com.br> (0.85)
 *
 * 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.
 *
 * Version 0.80 (99/06/11):
 *		- clean up source code (playing a bit of indent)
 *		- port back to kernel, add support for non-module versions
 *		- add support for board resets when channel protocol is down
 *		- reset the device structure after protocol exit
 *		  the syncppp driver needs it
 *		- add support for /proc/comx/protocols and 
 *		  /proc/comx/boardtypes
 *
 * Version 0.81 (99/06/21):
 *		- comment out the board reset support code, the locomx
 *		  driver seems not buggy now
 *		- printk() levels fixed
 *
 * Version 0.82 (99/07/08):
 *		- Handle stats correctly if the lowlevel driver is
 *		  is not a comx one (locomx - z85230)
 *
 * Version 0.83 (99/07/15):
 *		- reset line_status when interface is down
 *
 * Version 0.84 (99/12/01):
 *		- comx_status should not check for IFF_UP (to report
 *		  line status from dev->open())
 *
 * Version 0.85 (00/08/15):
 * 		- resource release on failure in comx_mkdir
 * 		- fix return value on failure at comx_write_proc
 *
 * Changed      (00/10/29, Henner Eisen):
 * 		- comx_rx() / comxlapb_data_indication() return status.
 */

#define VERSION "0.85"

#include <linux/config.h>
#include <linux/module.h>

#include <linux/types.h>
#include <linux/jiffies.h>
#include <linux/netdevice.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/ctype.h>
#include <linux/init.h>
#include <linux/smp_lock.h>

#ifdef CONFIG_KMOD
#include <linux/kmod.h>
#endif

#ifndef CONFIG_PROC_FS
#error For now, COMX really needs the /proc filesystem
#endif

#include <net/syncppp.h>
#include "comx.h"

MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
MODULE_DESCRIPTION("Common code for the COMX synchronous serial adapters");
MODULE_LICENSE("GPL");

static struct comx_hardware *comx_channels = NULL;
static struct comx_protocol *comx_lines = NULL;

static int comx_mkdir(struct inode *, struct dentry *, int);
static int comx_rmdir(struct inode *, struct dentry *);
static struct dentry *comx_lookup(struct inode *, struct dentry *, struct nameidata *);

static struct inode_operations comx_root_inode_ops = {
	.lookup = comx_lookup,
	.mkdir = comx_mkdir,
	.rmdir = comx_rmdir,
};

static int comx_delete_dentry(struct dentry *dentry);
static struct proc_dir_entry *create_comx_proc_entry(char *name, int mode,
	int size, struct proc_dir_entry *dir);

static struct dentry_operations comx_dentry_operations = {
	.d_delete	= comx_delete_dentry,
};


static struct proc_dir_entry * comx_root_dir;

struct comx_debugflags_struct	comx_debugflags[] = {
	{ "comx_rx",		DEBUG_COMX_RX		},
	{ "comx_tx", 		DEBUG_COMX_TX		},
	{ "hw_tx",		DEBUG_HW_TX		},
	{ "hw_rx", 		DEBUG_HW_RX		},
	{ "hdlc_keepalive",	DEBUG_HDLC_KEEPALIVE	},
	{ "comxppp",		DEBUG_COMX_PPP		},
	{ "comxlapb",		DEBUG_COMX_LAPB		},
	{ "dlci",		DEBUG_COMX_DLCI		},
	{ NULL,			0			} 
};


int comx_debug(struct net_device *dev, char *fmt, ...)
{
	struct comx_channel *ch = dev->priv;
	char *page,*str;
	va_list args;
	int len;

	if (!ch->debug_area) return 0;

	if (!(page = (char *)__get_free_page(GFP_ATOMIC))) return -ENOMEM;

	va_start(args, fmt);
	len = vsprintf(str = page, fmt, args);
	va_end(args);

	if (len >= PAGE_SIZE) {
		printk(KERN_ERR "comx_debug: PANIC! len = %d !!!\n", len);
		free_page((unsigned long)page);
		return -EINVAL;
	}

	while (len) {
		int to_copy;
		int free = (ch->debug_start - ch->debug_end + ch->debug_size) 
			% ch->debug_size;

		to_copy = min_t(int, free ? free : ch->debug_size, 
			      min_t(int, ch->debug_size - ch->debug_end, len));
		memcpy(ch->debug_area + ch->debug_end, str, to_copy);
		str += to_copy;
		len -= to_copy;
		ch->debug_end = (ch->debug_end + to_copy) % ch->debug_size;
		if (ch->debug_start == ch->debug_end) // Full ? push start away
			ch->debug_start = (ch->debug_start + len + 1) % 
					ch->debug_size;
		ch->debug_file->size = (ch->debug_end - ch->debug_start +
					ch->debug_size) % ch->debug_size;
	} 

	free_page((unsigned long)page);
	return 0;
}

int comx_debug_skb(struct net_device *dev, struct sk_buff *skb, char *msg)
{
	struct comx_channel *ch = dev->priv;

	if (!ch->debug_area) return 0;
	if (!skb) comx_debug(dev, "%s: %s NULL skb\n\n", dev->name, msg);
	if (!skb->len) comx_debug(dev, "%s: %s empty skb\n\n", dev->name, msg);

	return comx_debug_bytes(dev, skb->data, skb->len, msg);
}

int comx_debug_bytes(struct net_device *dev, unsigned char *bytes, int len, 
		char *msg)
{
	int pos = 0;
	struct comx_channel *ch = dev->priv;

	if (!ch->debug_area) return 0;

	comx_debug(dev, "%s: %s len %d\n", dev->name, msg, len);

	while (pos != len) {
		char line[80];
		int i = 0;

		memset(line, 0, 80);
		sprintf(line,"%04d ", pos);
		do {
			sprintf(line + 5 + (pos % 16) * 3, "%02x", bytes[pos]);
			sprintf(line + 60 + (pos % 16), "%c", 
				isprint(bytes[pos]) ? bytes[pos] : '.');
			pos++;
		} while (pos != len && pos % 16);

		while ( i++ != 78 ) if (line[i] == 0) line[i] = ' ';
		line[77] = '\n';
		line[78] = 0;
	
		comx_debug(dev, "%s", line);
	}
	comx_debug(dev, "\n");
	return 0;
}

static void comx_loadavg_timerfun(unsigned long d)
{
	struct net_device *dev = (struct net_device *)d;
	struct comx_channel *ch = dev->priv;

	ch->avg_bytes[ch->loadavg_counter] = ch->current_stats->rx_bytes;
	ch->avg_bytes[ch->loadavg_counter + ch->loadavg_size] = 
		ch->current_stats->tx_bytes;

	ch->loadavg_counter = (ch->loadavg_counter + 1) % ch->loadavg_size;

	mod_timer(&ch->loadavg_timer,jiffies + HZ * ch->loadavg[0]);
}

#if 0
static void comx_reset_timerfun(unsigned long d)
{ 
	struct net_device *dev = (struct net_device *)d;
	struct comx_channel *ch = dev->priv;

	if(!(ch->line_status & (PROTO_LOOP | PROTO_UP))) {
		if(test_and_set_bit(0,&ch->reset_pending) && ch->HW_reset) {
			ch->HW_reset(dev);
		}
	}

	mod_timer(&ch->reset_timer, jiffies + HZ * ch->reset_timeout);
}
#endif                                            

static int comx_open(struct net_device *dev)
{
	struct comx_channel *ch = dev->priv;
	struct proc_dir_entry *comxdir = ch->procdir->subdir;
	int ret=0;

	if (!ch->protocol || !ch->hardware) return -ENODEV;

	if ((ret = ch->HW_open(dev))) return ret;
	if ((ret = ch->LINE_open(dev))) { 
		ch->HW_close(dev); 
		return ret; 
	};

	for (; comxdir ; comxdir = comxdir->next) {
		if (strcmp(comxdir->name, FILENAME_HARDWARE) == 0 ||
		   strcmp(comxdir->name, FILENAME_PROTOCOL) == 0)
			comxdir->mode = S_IFREG | 0444;
	}

#if 0
	ch->reset_pending = 1;
	ch->reset_timeout = 30;
	ch->reset_timer.function = comx_reset_timerfun;
	ch->reset_timer.data = (unsigned long)dev;
	ch->reset_timer.expires = jiffies + HZ * ch->reset_timeout;
	add_timer(&ch->reset_timer);
#endif

	return 0;
}

static int comx_close(struct net_device *dev)
{
	struct comx_channel *ch = dev->priv;
	struct proc_dir_entry *comxdir = ch->procdir->subdir;
	int ret = -ENODEV;

	if (test_and_clear_bit(0, &ch->lineup_pending)) {
		del_timer(&ch->lineup_timer);
	}

#if 0	
	del_timer(&ch->reset_timer);
#endif

	if (ch->init_status & LINE_OPEN && ch->protocol && ch->LINE_close) {
		ret = ch->LINE_close(dev);
	}

	if (ret) return ret;

	if (ch->init_status & HW_OPEN && ch->hardware && ch->HW_close) {
		ret = ch->HW_close(dev);
	}
	
	ch->line_status=0;

	for (; comxdir ; comxdir = comxdir->next) {
		if (strcmp(comxdir->name, FILENAME_HARDWARE) == 0 ||
		    strcmp(comxdir->name, FILENAME_PROTOCOL) == 0)
			comxdir->mode = S_IFREG | 0644;
	}

	return ret;
}

void comx_status(struct net_device *dev, int status)
{
	struct comx_channel *ch = dev->priv;

#if 0
	if(status & (PROTO_UP | PROTO_LOOP)) {
		clear_bit(0,&ch->reset_pending);
	}
#endif

	printk(KERN_NOTICE "Interface %s: modem status %s, line protocol %s\n",
		    dev->name, status & LINE_UP ? "UP" : "DOWN", 
		    status & PROTO_LOOP ? "LOOP" : status & PROTO_UP ? 
		    "UP" : "DOWN");
	
	ch->line_status = status;
}

static int comx_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct comx_channel *ch = dev->priv;
	int rc;

	if (skb->len > dev->mtu + dev->hard_header_len) {
		printk(KERN_ERR "comx_xmit: %s: skb->len %d > dev->mtu %d\n", dev->name,
		(int)skb->len, dev->mtu);
	}
	
	if (ch->debug_flags & DEBUG_COMX_TX) {
		comx_debug_skb(dev, skb, "comx_xmit skb");
	}
	
	rc=ch->LINE_xmit(skb, dev);
//	if (!rc) dev_kfree_skb(skb);

	return rc;
}

static int comx_header(struct sk_buff *skb, struct net_device *dev, 
	unsigned short type, void *daddr, void *saddr, unsigned len) 
{
	struct comx_channel *ch = dev->priv;

	if (ch->LINE_header) {
		return (ch->LINE_header(skb, dev, type, daddr, saddr, len));
	} else {
		return 0;
	}
}

static int comx_rebuild_header(struct sk_buff *skb) 
{
	struct net_device *dev = skb->dev;
	struct comx_channel *ch = dev->priv;

	if (ch->LINE_rebuild_header) {
		return(ch->LINE_rebuild_header(skb));
	} else {
		return 0;
	}
}

int comx_rx(struct net_device *dev, struct sk_buff *skb)
{
	struct comx_channel *ch = dev->priv;

	if (ch->debug_flags & DEBUG_COMX_RX) {
		comx_debug_skb(dev, skb, "comx_rx skb");
	}
	if (skb) {
		netif_rx(skb);
		dev->last_rx = jiffies;
	}
	return 0;
}

static struct net_device_stats *comx_stats(struct net_device *dev)
{
	struct comx_channel *ch = dev->priv;

	return ch->current_stats;
}

void comx_lineup_func(unsigned long d)
{
	struct net_device *dev = (struct net_device *)d;
	struct comx_channel *ch = dev->priv;

	del_timer(&ch->lineup_timer);
	clear_bit(0, &ch->lineup_pending);

	if (ch->LINE_status) {
		ch->LINE_status(dev, ch->line_status |= LINE_UP);
	}
}

#define LOADAVG(avg, off) (int) \
	((ch->avg_bytes[(ch->loadavg_counter - 1 + ch->loadavg_size * 2) \
	% ch->loadavg_size + off] -  ch->avg_bytes[(ch->loadavg_counter - 1 \
		- ch->loadavg[avg] / ch->loadavg[0] + ch->loadavg_size * 2) \
		% ch->loadavg_size + off]) / ch->loadavg[avg] * 8)

static int comx_statistics(struct net_device *dev, char *page)
{
	struct comx_channel *ch = dev->priv;
	int len = 0;
	int tmp;
	int i = 0;
	char tmpstr[20];
	int tmpstrlen = 0;

	len += sprintf(page + len, "Interface administrative status is %s, "
		"modem status is %s, protocol is %s\n", 
		dev->flags & IFF_UP ? "UP" : "DOWN",
		ch->line_status & LINE_UP ? "UP" : "DOWN",
		ch->line_status & PROTO_LOOP ? "LOOP" : 
		ch->line_status & PROTO_UP ? "UP" : "DOWN");
	len += sprintf(page + len, "Modem status changes: %lu, Transmitter status "
		"is %s, tbusy: %d\n", ch->current_stats->tx_carrier_errors, ch->HW_txe ? 
		ch->HW_txe(dev) ? "IDLE" : "BUSY" : "NOT READY", netif_running(dev));
	len += sprintf(page + len, "Interface load (input): %d / %d / %d bits/s (",
		LOADAVG(0,0), LOADAVG(1, 0), LOADAVG(2, 0));
	tmpstr[0] = 0;
	for (i=0; i != 3; i++) {
		char tf;

		tf = ch->loadavg[i] % 60 == 0 && 
			ch->loadavg[i] / 60 > 0 ? 'm' : 's';
		tmpstrlen += sprintf(tmpstr + tmpstrlen, "%d%c%s", 
			ch->loadavg[i] / (tf == 'm' ? 60 : 1), tf, 
			i == 2 ? ")\n" : "/");
	}
	len += sprintf(page + len, 
		"%s              (output): %d / %d / %d bits/s (%s", tmpstr, 
		LOADAVG(0,ch->loadavg_size), LOADAVG(1, ch->loadavg_size), 
		LOADAVG(2, ch->loadavg_size), tmpstr);

	len += sprintf(page + len, "Debug flags: ");
	tmp = len; i = 0;
	while (comx_debugflags[i].name) {
		if (ch->debug_flags & comx_debugflags[i].value) 
			len += sprintf(page + len, "%s ", 
				comx_debugflags[i].name);
		i++;
	}
	len += sprintf(page + len, "%s\n", tmp == len ? "none" : "");

	len += sprintf(page + len, "RX errors: len: %lu, overrun: %lu, crc: %lu, "
		"aborts: %lu\n           buffer overrun: %lu, pbuffer overrun: %lu\n"
		"TX errors: underrun: %lu\n",
		ch->current_stats->rx_length_errors, ch->current_stats->rx_over_errors, 
		ch->current_stats->rx_crc_errors, ch->current_stats->rx_frame_errors, 
		ch->current_stats->rx_missed_errors, ch->current_stats->rx_fifo_errors,
		ch->current_stats->tx_fifo_errors);

	if (ch->LINE_statistics && (ch->init_status & LINE_OPEN)) {
		len += ch->LINE_statistics(dev, page + len);
	} else {
		len += sprintf(page+len, "Line status: driver not initialized\n");
	}
	if (ch->HW_statistics && (ch->init_status & HW_OPEN)) {
		len += ch->HW_statistics(dev, page + len);
	} else {
		len += sprintf(page+len, "Board status: driver not initialized\n");
	}

	return len;
}

static int comx_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
	struct comx_channel *ch = dev->priv;

	if (ch->LINE_ioctl) {
		return(ch->LINE_ioctl(dev, ifr, cmd));
	}
	return -EINVAL;
}

static void comx_reset_dev(struct net_device *dev)
{
	dev->open = comx_open;
	dev->stop = comx_close;
	dev->hard_start_xmit = comx_xmit;
	dev->hard_header = comx_header;
	dev->rebuild_header = comx_rebuild_header;
	dev->get_stats = comx_stats;
	dev->do_ioctl = comx_ioctl;
	dev->change_mtu = NULL;
	dev->tx_queue_len = 20;
	dev->flags = IFF_NOARP;
}

static int comx_init_dev(struct net_device *dev)
{
	struct comx_channel *ch;

	if ((ch = kmalloc(sizeof(struct comx_channel), GFP_KERNEL)) == NULL) {
		return -ENOMEM;
	}
	memset(ch, 0, sizeof(struct comx_channel));

	ch->loadavg[0] = 5;
	ch->loadavg[1] = 300;
	ch->loadavg[2] = 900;
	ch->loadavg_size = ch->loadavg[2] / ch->loadavg[0] + 1; 
	if ((ch->avg_bytes = kmalloc(ch->loadavg_size * 
		sizeof(unsigned long) * 2, GFP_KERNEL)) == NULL) {
		kfree(ch);
		return -ENOMEM;
	}

	memset(ch->avg_bytes, 0, ch->loadavg_size * sizeof(unsigned long) * 2);
	ch->loadavg_counter = 0;
	ch->loadavg_timer.function = comx_loadavg_timerfun;
	ch->loadavg_timer.data = (unsigned long)dev;
	ch->loadavg_timer.expires = jiffies + HZ * ch->loadavg[0];
	add_timer(&ch->loadavg_timer);

	dev->priv = (void *)ch;
	ch->dev = dev;
	ch->line_status &= ~LINE_UP;

	ch->current_stats = &ch->stats;

	comx_reset_dev(dev);
	return 0;
}

static int comx_read_proc(char *page, char **start, off_t off, int count, 
	int *eof, void *data)
{
	struct proc_dir_entry *file = (struct proc_dir_entry *)data;
	struct net_device *dev = file->parent->data;
	struct comx_channel *ch = dev->priv;
	int len = 0;

	if (strcmp(file->name, FILENAME_STATUS) == 0) {
		len = comx_statistics(dev, page);
	} else if (strcmp(file->name, FILENAME_HARDWARE) == 0) {
		len = sprintf(page, "%s\n", ch->hardware ? 
			ch->hardware->name : HWNAME_NONE);
	} else if (strcmp(file->name, FILENAME_PROTOCOL) == 0) {
		len = sprintf(page, "%s\n", ch->protocol ? 
			ch->protocol->name : PROTONAME_NONE);
	} else if (strcmp(file->name, FILENAME_LINEUPDELAY) == 0) {
		len = sprintf(page, "%01d\n", ch->lineup_delay);
	}

	if (off >= len) {
		*eof = 1;
		return 0;
	}

	*start = page + off;
	if (count >= len - off) {
		*eof = 1;
	}
	return min_t(int, count, len - off);
}


static int comx_root_read_proc(char *page, char **start, off_t off, int count, 
	int *eof, void *data)
{
	struct proc_dir_entry *file = (struct proc_dir_entry *)data;
	struct comx_hardware *hw;
	struct comx_protocol *line;

	int len = 0;

	if (strcmp(file->name, FILENAME_HARDWARELIST) == 0) {
		for(hw=comx_channels;hw;hw=hw->next) 
			len+=sprintf(page+len, "%s\n", hw->name);
	} else if (strcmp(file->name, FILENAME_PROTOCOLLIST) == 0) {
		for(line=comx_lines;line;line=line->next)
			len+=sprintf(page+len, "%s\n", line->name);
	}

	if (off >= len) {
		*eof = 1;
		return 0;
	}

	*start = page + off;
	if (count >= len - off) {
		*eof = 1;
	}
	return min_t(int, count, len - off);
}



static int comx_write_proc(struct file *file, const char *buffer, u_long count,
	void *data)
{
	struct proc_dir_entry *entry = (struct proc_dir_entry *)data;
	struct net_device *dev = (struct net_device *)entry->parent->data;
	struct comx_channel *ch = dev->priv;
	char *page;
	struct comx_hardware *hw = comx_channels;
	struct comx_protocol *line = comx_lines;
	int ret=0;

	if (count > PAGE_SIZE) {
		printk(KERN_ERR "count is %lu > %d!!!\n", count, (int)PAGE_SIZE);
		return -ENOSPC;
	}

	if (!(page = (char *)__get_free_page(GFP_KERNEL))) return -ENOMEM;

	if(copy_from_user(page, buffer, count))
	{
		count = -EFAULT;
		goto out;
	}

	if (page[count-1] == '\n')
		page[count-1] = '\0';
	else if (count < PAGE_SIZE)
		page[count] = '\0';
	else if (page[count]) {
		count = -EINVAL;
		goto out;
	}

	if (strcmp(entry->name, FILENAME_DEBUG) == 0) {
		int i;
		int ret = 0;

		if ((i = simple_strtoul(page, NULL, 10)) != 0) {
			unsigned long flags;

			save_flags(flags); cli();
			if (ch->debug_area) kfree(ch->debug_area);
			if ((ch->debug_area = kmalloc(ch->debug_size = i, 
				GFP_KERNEL)) == NULL) {
				ret = -ENOMEM;
			}
			ch->debug_start = ch->debug_end = 0;
			restore_flags(flags);
			free_page((unsigned long)page);
			return ret ? ret : count;
		}
		
		if (*page != '+' && *page != '-') {
			free_page((unsigned long)page);
			return -EINVAL;
		}
		while (comx_debugflags[i].value && 
			strncmp(comx_debugflags[i].name, page + 1, 
			strlen(comx_debugflags[i].name))) {
			i++;
		}
	
		if (comx_debugflags[i].value == 0) {
			printk(KERN_ERR "Invalid debug option\n");
			free_page((unsigned long)page);
			return -EINVAL;
		}
		if (*page == '+') {
			ch->debug_flags |= comx_debugflags[i].value;
		} else {
			ch->debug_flags &= ~comx_debugflags[i].value;
		}
	} else if (strcmp(entry->name, FILENAME_HARDWARE) == 0) {
		if(strlen(page)>10) {
			free_page((unsigned long)page);
			return -EINVAL;
		}
		while (hw) { 
			if (strcmp(hw->name, page) == 0) {
				break;
			} else {
				hw = hw->next;
			}
		}
#ifdef CONFIG_KMOD
		if(!hw && comx_strcasecmp(HWNAME_NONE,page) != 0){
			request_module("comx-hw-%s",page);
		}		
		hw=comx_channels;
		while (hw) {
			if (comx_strcasecmp(hw->name, page) == 0) {
				break;
			} else {
				hw = hw->next;
			}
		}
#endif

		if (comx_strcasecmp(HWNAME_NONE, page) != 0 && !hw)  {
			free_page((unsigned long)page);
			return -ENODEV;
		}
		if (ch->init_status & HW_OPEN) {
			free_page((unsigned long)page);
			return -EBUSY;
		}
		if (ch->hardware && ch->hardware->hw_exit && 
		   (ret=ch->hardware->hw_exit(dev))) {
			free_page((unsigned long)page);
			return ret;
		}
		ch->hardware = hw;
		entry->size = strlen(page) + 1;
		if (hw && hw->hw_init) hw->hw_init(dev);
	} else if (strcmp(entry->name, FILENAME_PROTOCOL) == 0) {
		if(strlen(page)>10) {
			free_page((unsigned long)page);
			return -EINVAL;
		}
		while (line) {
			if (comx_strcasecmp(line->name, page) == 0) {
				break;
			} else {
				line = line->next;
			}
		}
#ifdef CONFIG_KMOD
		if(!line && comx_strcasecmp(PROTONAME_NONE, page) != 0) {
			request_module("comx-proto-%s",page);
		}		
		line=comx_lines;
		while (line) {
			if (comx_strcasecmp(line->name, page) == 0) {
				break;
			} else {
				line = line->next;
			}
		}
#endif
		
		if (comx_strcasecmp(PROTONAME_NONE, page) != 0 && !line) {
			free_page((unsigned long)page);
			return -ENODEV;
		}
		
		if (ch->init_status & LINE_OPEN) {
			free_page((unsigned long)page);
			return -EBUSY;
		}
		
		if (ch->protocol && ch->protocol->line_exit && 
		    (ret=ch->protocol->line_exit(dev))) {
			free_page((unsigned long)page);
			return ret;
		}
		ch->protocol = line;
		entry->size = strlen(page) + 1;
		comx_reset_dev(dev);
		if (line && line->line_init) line->line_init(dev);
	} else if (strcmp(entry->name, FILENAME_LINEUPDELAY) == 0) {
		int i;

		if ((i = simple_strtoul(page, NULL, 10)) != 0) {
			if (i >=0 && i < 10) { 
				ch->lineup_delay = i; 
			} else {
				printk(KERN_ERR "comx: invalid lineup_delay value\n");
			}
		}
	}
out:
	free_page((unsigned long)page);
	return count;
}

static int comx_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
	struct proc_dir_entry *new_dir, *debug_file;
	struct net_device *dev;
	struct comx_channel *ch;
	int ret = -EIO;

	if ((dev = kmalloc(sizeof(struct net_device), GFP_KERNEL)) == NULL) {
		return -ENOMEM;
	}
	memset(dev, 0, sizeof(struct net_device));

	lock_kernel();
	if ((new_dir = create_proc_entry(dentry->d_name.name, mode | S_IFDIR, 
		comx_root_dir)) == NULL) {
		goto cleanup_dev;
	}

	new_dir->nlink = 2;
	new_dir->data = NULL; // ide jon majd a struct dev

	/* Ezek kellenek */
	if (!create_comx_proc_entry(FILENAME_HARDWARE, 0644, 
	    strlen(HWNAME_NONE) + 1, new_dir)) {
		goto cleanup_new_dir;
	}
	if (!create_comx_proc_entry(FILENAME_PROTOCOL, 0644, 
	    strlen(PROTONAME_NONE) + 1, new_dir)) {
		goto cleanup_filename_hardware;
	}
	if (!create_comx_proc_entry(FILENAME_STATUS, 0444, 0, new_dir)) {
		goto cleanup_filename_protocol;
	}
	if (!create_comx_proc_entry(FILENAME_LINEUPDELAY, 0644, 2, new_dir)) {
		goto cleanup_filename_status;
	}

	if ((debug_file = create_proc_entry(FILENAME_DEBUG, 
	    S_IFREG | 0644, new_dir)) == NULL) {
		goto cleanup_filename_lineupdelay;
	}
	debug_file->data = (void *)debug_file; 
	debug_file->read_proc = NULL; // see below
	debug_file->write_proc = &comx_write_proc;
	debug_file->nlink = 1;

	strcpy(dev->name, (char *)new_dir->name);
	dev->init = comx_init_dev;

	if (register_netdevice(dev)) {
		goto cleanup_filename_debug;
	}
	ch = dev->priv;
	if((ch->if_ptr = (void *)kmalloc(sizeof(struct ppp_device), 
				 GFP_KERNEL)) == NULL) {
		goto cleanup_register;
	}
	memset(ch->if_ptr, 0, sizeof(struct ppp_device));
	ch->debug_file = debug_file; 
	ch->procdir = new_dir;
	new_dir->data = dev;

	ch->debug_start = ch->debug_end = 0;
	if ((ch->debug_area = kmalloc(ch->debug_size = DEFAULT_DEBUG_SIZE, 
	    GFP_KERNEL)) == NULL) {
		ret = -ENOMEM;
		goto cleanup_if_ptr;
	}

	ch->lineup_delay = DEFAULT_LINEUP_DELAY;

	MOD_INC_USE_COUNT;
	unlock_kernel();
	return 0;
cleanup_if_ptr:
	kfree(ch->if_ptr);
cleanup_register:
	unregister_netdevice(dev);
cleanup_filename_debug:
	remove_proc_entry(FILENAME_DEBUG, new_dir);
cleanup_filename_lineupdelay:
	remove_proc_entry(FILENAME_LINEUPDELAY, new_dir);
cleanup_filename_status:
	remove_proc_entry(FILENAME_STATUS, new_dir);
cleanup_filename_protocol:
	remove_proc_entry(FILENAME_PROTOCOL, new_dir);
cleanup_filename_hardware:
	remove_proc_entry(FILENAME_HARDWARE, new_dir);
cleanup_new_dir:
	remove_proc_entry(dentry->d_name.name, comx_root_dir);
cleanup_dev:
	kfree(dev);
	unlock_kernel();
	return ret;
}

static int comx_rmdir(struct inode *dir, struct dentry *dentry)
{
	struct proc_dir_entry *entry = PDE(dentry->d_inode);
	struct net_device *dev;
	struct comx_channel *ch;
	int ret;

	lock_kernel();
	dev = entry->data;
	ch = dev->priv;
	if (dev->flags & IFF_UP) {
		printk(KERN_ERR "%s: down interface before removing it\n", dev->name);
		unlock_kernel();
		return -EBUSY;
	}

	if (ch->protocol && ch->protocol->line_exit && 
	    (ret=ch->protocol->line_exit(dev))) {
		unlock_kernel();
		return ret;
	}
	if (ch->hardware && ch->hardware->hw_exit && 
	   (ret=ch->hardware->hw_exit(dev))) { 
		if(ch->protocol && ch->protocol->line_init) {
			ch->protocol->line_init(dev);
		}
		unlock_kernel();
		return ret;
	}
	ch->protocol = NULL;
	ch->hardware = NULL;

	del_timer(&ch->loadavg_timer);
	kfree(ch->avg_bytes);

	unregister_netdev(dev);
	if (ch->debug_area) {
		kfree(ch->debug_area);
	}
	if (dev->priv) {
		kfree(dev->priv);
	}
	free_netdev(dev);

	remove_proc_entry(FILENAME_DEBUG, entry);
	remove_proc_entry(FILENAME_LINEUPDELAY, entry);
	remove_proc_entry(FILENAME_STATUS, entry);
	remove_proc_entry(FILENAME_HARDWARE, entry);
	remove_proc_entry(FILENAME_PROTOCOL, entry);
	remove_proc_entry(dentry->d_name.name, comx_root_dir);

	MOD_DEC_USE_COUNT;
	unlock_kernel();
	return 0;
}

static struct dentry *comx_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
	struct proc_dir_entry *de;
	struct inode *inode = NULL;

	lock_kernel();
	if ((de = PDE(dir)) != NULL) {
		for (de = de->subdir ; de ; de = de->next) {
			if ((de->namelen == dentry->d_name.len) &&
			    (memcmp(dentry->d_name.name, de->name, 
			    de->namelen) == 0))	{
			 	if ((inode = proc_get_inode(dir->i_sb, 
			 	    de->low_ino, de)) == NULL) { 
			 		printk(KERN_ERR "COMX: lookup error\n"); 
					unlock_kernel();
			 		return ERR_PTR(-EINVAL); 
			 	}
				break;
			}
		}
	}
	unlock_kernel();
	dentry->d_op = &comx_dentry_operations;
	d_add(dentry, inode);
	return NULL;
}

int comx_strcasecmp(const char *cs, const char *ct)
{
	register signed char __res;

	while (1) {
		if ((__res = toupper(*cs) - toupper(*ct++)) != 0 || !*cs++) {
			break;
		}
	}
	return __res;
}

static int comx_delete_dentry(struct dentry *dentry)
{
	return 1;
}

static struct proc_dir_entry *create_comx_proc_entry(char *name, int mode,
	int size, struct proc_dir_entry *dir)
{
	struct proc_dir_entry *new_file;

	if ((new_file = create_proc_entry(name, S_IFREG | mode, dir)) != NULL) {
		new_file->data = (void *)new_file;
		new_file->read_proc = &comx_read_proc;
		new_file->write_proc = &comx_write_proc;
		new_file->size = size;
		new_file->nlink = 1;
	}
	return(new_file);
}

int comx_register_hardware(struct comx_hardware *comx_hw)
{
	struct comx_hardware *hw = comx_channels;

	if (!hw) {
		comx_channels = comx_hw;
	} else {
		while (hw->next != NULL && strcmp(comx_hw->name, hw->name) != 0) {
			hw = hw->next;
		}
		if (strcmp(comx_hw->name, hw->name) == 0) {
			return -1;
		}
		hw->next = comx_hw;
	}

	printk(KERN_INFO "COMX: driver for hardware type %s, version %s\n", comx_hw->name, comx_hw->version);
	return 0;
}

int comx_unregister_hardware(char *name)
{
	struct comx_hardware *hw = comx_channels;

	if (!hw) {
		return -1;
	}

	if (strcmp(hw->name, name) == 0) {
		comx_channels = comx_channels->next;
		return 0;
	}

	while (hw->next != NULL && strcmp(hw->next->name,name) != 0) {
		hw = hw->next;
	}

	if (hw->next != NULL && strcmp(hw->next->name, name) == 0) {
		hw->next = hw->next->next;
		return 0;
	}
	return -1;
}

int comx_register_protocol(struct comx_protocol *comx_line)
{
	struct comx_protocol *pr = comx_lines;

	if (!pr) {
		comx_lines = comx_line;
	} else {
		while (pr->next != NULL && strcmp(comx_line->name, pr->name) !=0) {
			pr = pr->next;
		}
		if (strcmp(comx_line->name, pr->name) == 0) {
			return -1;
		}
		pr->next = comx_line;
	}

	printk(KERN_INFO "COMX: driver for protocol type %s, version %s\n", comx_line->name, comx_line->version);
	return 0;
}

int comx_unregister_protocol(char *name)
{
	struct comx_protocol *pr = comx_lines;

	if (!pr) {
		return -1;
	}

	if (strcmp(pr->name, name) == 0) {
		comx_lines = comx_lines->next;
		return 0;
	}

	while (pr->next != NULL && strcmp(pr->next->name,name) != 0) {
		pr = pr->next;
	}

	if (pr->next != NULL && strcmp(pr->next->name, name) == 0) {
		pr->next = pr->next->next;
		return 0;
	}
	return -1;
}

static int __init comx_init(void)
{
	struct proc_dir_entry *new_file;

	comx_root_dir = create_proc_entry("comx", 
		S_IFDIR | S_IWUSR | S_IRUGO | S_IXUGO, &proc_root);
	if (!comx_root_dir)
		return -ENOMEM;
	comx_root_dir->proc_iops = &comx_root_inode_ops;

	if ((new_file = create_proc_entry(FILENAME_HARDWARELIST, 
	   S_IFREG | 0444, comx_root_dir)) == NULL) {
		return -ENOMEM;
	}
	
	new_file->data = new_file;
	new_file->read_proc = &comx_root_read_proc;
	new_file->write_proc = NULL;
	new_file->nlink = 1;

	if ((new_file = create_proc_entry(FILENAME_PROTOCOLLIST, 
	   S_IFREG | 0444, comx_root_dir)) == NULL) {
		return -ENOMEM;
	}
	
	new_file->data = new_file;
	new_file->read_proc = &comx_root_read_proc;
	new_file->write_proc = NULL;
	new_file->nlink = 1;


	printk(KERN_INFO "COMX: driver version %s (C) 1995-1999 ITConsult-Pro Co. <info@itc.hu>\n", 
		VERSION);
	return 0;
}

static void __exit comx_exit(void)
{
	remove_proc_entry(FILENAME_HARDWARELIST, comx_root_dir);
	remove_proc_entry(FILENAME_PROTOCOLLIST, comx_root_dir);
	remove_proc_entry(comx_root_dir->name, &proc_root);
}

module_init(comx_init);
module_exit(comx_exit);

EXPORT_SYMBOL(comx_register_hardware);
EXPORT_SYMBOL(comx_unregister_hardware);
EXPORT_SYMBOL(comx_register_protocol);
EXPORT_SYMBOL(comx_unregister_protocol);
EXPORT_SYMBOL(comx_debug_skb);
EXPORT_SYMBOL(comx_debug_bytes);
EXPORT_SYMBOL(comx_debug);
EXPORT_SYMBOL(comx_lineup_func);
EXPORT_SYMBOL(comx_status);
EXPORT_SYMBOL(comx_rx);
EXPORT_SYMBOL(comx_strcasecmp);
EXPORT_SYMBOL(comx_root_dir);
