patch-2.2.14 linux/drivers/net/comx.c

Next file: linux/drivers/net/comx.h
Previous file: linux/drivers/net/comx-proto-ppp.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.13/linux/drivers/net/comx.c linux/drivers/net/comx.c
@@ -0,0 +1,1234 @@
+/* 
+ * Device driver framework for the COMX line of synchronous serial boards
+ * 
+ * for Linux kernel 2.2.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.
+ *
+ * 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
+ *
+ */
+
+#define VERSION "0.82"
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/version.h>
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <asm/uaccess.h>
+#include <linux/ctype.h>
+#include <linux/init.h>
+
+#ifdef CONFIG_KMOD
+#include <linux/kmod.h>
+#endif
+
+#ifndef CONFIG_PROC_FS
+#error For now, COMX really needs the /proc filesystem
+#endif
+
+#include "comx.h"
+#include "syncppp.h"
+
+MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
+MODULE_DESCRIPTION("Common code for the COMX synchronous serial adapters");
+
+extern int comx_hw_comx_init(void);
+extern int comx_hw_locomx_init(void);
+extern int comx_hw_mixcom_init(void);
+extern int comx_proto_hdlc_init(void);
+extern int comx_proto_ppp_init(void);
+extern int comx_proto_syncppp_init(void);
+extern int comx_proto_lapb_init(void);
+extern int comx_proto_fr_init(void);
+
+static struct comx_hardware *comx_channels = NULL;
+static struct comx_protocol *comx_lines = NULL;
+
+struct inode_operations comx_normal_inode_ops; 
+static struct inode_operations comx_root_inode_ops; // for mkdir
+static struct inode_operations comx_debug_inode_ops; // mas a file_ops
+static struct file_operations comx_normal_file_ops;  // with open/relase
+static struct file_operations comx_debug_file_ops;  // with lseek+read
+
+static void 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 void comx_fill_inode(struct inode *inode, int fill);
+
+static struct dentry_operations comx_dentry_operations = {
+	NULL,			/* revalidate */
+	NULL,			/* d_hash */
+	NULL, 			/* d_compare */
+	&comx_delete_dentry	/* d_delete */
+};
+
+
+struct proc_dir_entry comx_root_dir = {
+	0, 4, "comx",
+	S_IFDIR | S_IWUSR | S_IRUGO | S_IXUGO, 2, 0, 0,
+	0, &comx_root_inode_ops,
+	NULL, comx_fill_inode,
+	NULL, &proc_root, NULL 
+};
+
+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			} 
+};
+
+static void comx_fill_inode(struct inode *inode, int fill)
+{
+	if (fill)
+		MOD_INC_USE_COUNT;
+	else
+		MOD_DEC_USE_COUNT;
+}
+
+
+int comx_debug(struct 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( free ? free : ch->debug_size, 
+			min (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 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 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 device *dev = (struct 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 device *dev = (struct 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 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 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 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
+
+	if (dev->flags & IFF_UP) {
+		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 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 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 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 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);
+	}
+	return 0;
+}
+
+static struct net_device_stats *comx_stats(struct device *dev)
+{
+	struct comx_channel *ch = (struct comx_channel *)dev->priv;
+
+	return ch->current_stats;
+}
+
+void comx_lineup_func(unsigned long d)
+{
+	struct device *dev = (struct 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 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", (int)dev->tbusy);
+	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 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 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 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) {
+		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 device *dev = file->parent->data;
+	struct comx_channel *ch=(struct comx_channel *)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(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(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 device *dev = (struct device *)entry->parent->data;
+	struct comx_channel *ch=(struct comx_channel *)dev->priv;
+	char *page;
+	struct comx_hardware *hw = comx_channels;
+	struct comx_protocol *line = comx_lines;
+	char str[30];
+	int ret=0;
+
+	if (file->f_dentry->d_inode->i_ino != entry->low_ino) {
+		printk(KERN_ERR "comx_write_proc: file <-> data internal error\n");
+		return -EIO;
+	}
+
+	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;
+
+	copy_from_user(page, buffer, count);
+
+	if (*(page + count - 1) == '\n') *(page + count - 1) = 0;
+
+	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 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){
+			sprintf(str,"comx-hw-%s",page);
+			request_module(str);
+		}		
+		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) {
+			sprintf(str,"comx-proto-%s",page);
+			request_module(str);
+		}		
+		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");
+			}
+		}
+	}
+
+	free_page((unsigned long)page);
+	return count;
+}
+
+static loff_t comx_debug_lseek(struct file *file, loff_t offset, int orig)
+{
+	switch(orig) {
+		case 0:	
+			file->f_pos = max(0, min(offset, 
+				file->f_dentry->d_inode->i_size));
+			return(file->f_pos);
+		case 1: 
+			file->f_pos = max(0, min(offset + file->f_pos, 
+				file->f_dentry->d_inode->i_size));
+			return(file->f_pos);
+		case 2: 
+			file->f_pos = max(0, 
+				min(offset + file->f_dentry->d_inode->i_size, 
+				file->f_dentry->d_inode->i_size));
+			return(file->f_pos);
+	}
+	return(file->f_pos);
+}
+
+static int comx_file_open(struct inode *inode, struct file *file)
+{
+
+	if((file->f_mode & FMODE_WRITE) && !(inode->i_mode & 0200)) {
+		return -EACCES;
+	}
+
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int comx_file_release(struct inode *inode, struct file *file)
+{
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static ssize_t comx_debug_read(struct file *file, char *buffer, size_t count,
+	loff_t *ppos)
+{
+	struct proc_dir_entry *de = file->f_dentry->d_inode->u.generic_ip;
+	struct device *dev = de->parent->data;
+	struct comx_channel *ch = dev->priv;
+	loff_t copied = 0;
+	unsigned long flags;
+
+	save_flags(flags); cli(); // We may run into trouble when debug_area is filled
+				  // from irq inside read. no problem if the buffer is
+				  // large enough
+
+	while (count > 0 && ch->debug_start != ch->debug_end) {
+		int len;
+
+		len = min( (ch->debug_end - ch->debug_start + ch->debug_size)
+			%ch->debug_size, min (ch->debug_size - 
+			ch->debug_start, count));
+
+		if (len) copy_to_user(buffer + copied, 
+			ch->debug_area + ch->debug_start, len);
+		ch->debug_start = (ch->debug_start + len) % ch->debug_size;
+
+		de->size -= len;
+		count -= len;
+		copied += len;
+	}
+
+	restore_flags(flags);
+	return copied;
+}
+
+static int comx_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+	struct proc_dir_entry *new_dir, *debug_file;
+	struct device *dev;
+	struct comx_channel *ch;
+
+	if (dir->i_ino != comx_root_dir.low_ino) return -ENOTDIR;
+
+	if ((new_dir = create_proc_entry(dentry->d_name.name, mode | S_IFDIR, 
+		&comx_root_dir)) == NULL) {
+		return -EIO;
+	}
+
+	new_dir->ops = &proc_dir_inode_operations;  // ez egy normalis /proc konyvtar
+	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)) {
+		return -ENOMEM;
+	}
+	if (!create_comx_proc_entry(FILENAME_PROTOCOL, 0644, 
+	    strlen(PROTONAME_NONE) + 1, new_dir)) {
+		return -ENOMEM;
+	}
+	if (!create_comx_proc_entry(FILENAME_STATUS, 0444, 0, new_dir)) {
+		return -ENOMEM;
+	}
+	if (!create_comx_proc_entry(FILENAME_LINEUPDELAY, 0644, 2, new_dir)) {
+		return -ENOMEM;
+	}
+
+	if ((debug_file = create_proc_entry(FILENAME_DEBUG, 
+	    S_IFREG | 0644, new_dir)) == NULL) {
+		return -ENOMEM;
+	}
+	debug_file->ops = &comx_debug_inode_ops;
+	debug_file->data = (void *)debug_file; 
+	debug_file->read_proc = NULL; // see below
+	debug_file->write_proc = &comx_write_proc;
+	debug_file->nlink = 1;
+
+	/* struct ppp_device is a bit larger then struct device and the
+	   syncppp driver needs it */
+	if ((dev = kmalloc(sizeof(struct ppp_device), GFP_KERNEL)) == NULL) {
+		return -ENOMEM;
+	}
+	memset(dev, 0, sizeof(struct ppp_device));
+	dev->name = (char *)new_dir->name;
+	dev->init = comx_init_dev;
+
+	if (register_netdevice(dev)) {
+		return -EIO;
+	}
+	ch=dev->priv;
+	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) {
+		return -ENOMEM;
+	}
+
+	ch->lineup_delay = DEFAULT_LINEUP_DELAY;
+
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int comx_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	struct proc_dir_entry *entry = dentry->d_inode->u.generic_ip;
+	struct device *dev = entry->data;
+	struct comx_channel *ch = dev->priv;
+	int ret;
+
+	/* Egyelore miert ne ? */
+	if (dir->i_ino != comx_root_dir.low_ino) return -ENOTDIR;
+
+	if (dev->flags & IFF_UP) {
+		printk(KERN_ERR "%s: down interface before removing it\n", dev->name);
+		return -EBUSY;
+	}
+
+	if (ch->protocol && ch->protocol->line_exit && 
+	    (ret=ch->protocol->line_exit(dev))) {
+		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);
+		}
+		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);
+	}
+	kfree(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);
+//	proc_unregister(&comx_root_dir, dentry->d_inode->i_ino);
+
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static struct dentry *comx_lookup(struct inode *dir, struct dentry *dentry)
+{
+	struct proc_dir_entry *de;
+	struct inode *inode = NULL;
+
+	if (!dir || !S_ISDIR(dir->i_mode)) {
+		return ERR_PTR(-ENOTDIR);
+	}
+
+	if ((de = (struct proc_dir_entry *) dir->u.generic_ip) != NULL) {
+		for (de = de->subdir ; de ; de = de->next) {
+			if ((de && de->low_ino) && 
+			    (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"); 
+			 		return ERR_PTR(-EINVAL); 
+			 	}
+				break;
+			}
+		}
+	}
+	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 void comx_delete_dentry(struct dentry *dentry)
+{
+	d_drop(dentry);
+}
+
+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->ops = &comx_normal_inode_ops;
+		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;
+}
+
+#ifdef MODULE
+#define comx_init init_module
+#endif
+
+__initfunc(int comx_init(void))
+{
+	struct proc_dir_entry *new_file;
+
+	memcpy(&comx_root_inode_ops, &proc_dir_inode_operations, 
+		sizeof(struct inode_operations));
+	comx_root_inode_ops.lookup = &comx_lookup;
+	comx_root_inode_ops.mkdir = &comx_mkdir;
+	comx_root_inode_ops.rmdir = &comx_rmdir;
+
+	memcpy(&comx_normal_inode_ops, &proc_net_inode_operations, 
+		sizeof(struct inode_operations));
+	comx_normal_inode_ops.default_file_ops = &comx_normal_file_ops;
+	comx_normal_inode_ops.lookup = &comx_lookup;
+
+	memcpy(&comx_debug_inode_ops, &comx_normal_inode_ops, 
+		sizeof(struct inode_operations));
+	comx_debug_inode_ops.default_file_ops = &comx_debug_file_ops;
+
+	memcpy(&comx_normal_file_ops, proc_net_inode_operations.default_file_ops,
+		sizeof(struct file_operations));
+	comx_normal_file_ops.open = &comx_file_open;
+	comx_normal_file_ops.release = &comx_file_release;
+
+	memcpy(&comx_debug_file_ops, &comx_normal_file_ops, 
+		sizeof(struct file_operations));
+	comx_debug_file_ops.llseek = &comx_debug_lseek;
+	comx_debug_file_ops.read = &comx_debug_read;
+
+	if (proc_register(&proc_root, &comx_root_dir) < 0) return -ENOMEM;
+
+
+	if ((new_file = create_proc_entry(FILENAME_HARDWARELIST, 
+	   S_IFREG | 0444, &comx_root_dir)) == NULL) {
+		return -ENOMEM;
+	}
+	
+	new_file->ops = &comx_normal_inode_ops;
+	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->ops = &comx_normal_inode_ops;
+	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);
+
+#ifndef MODULE
+#ifdef CONFIG_COMX_HW_COMX
+	comx_hw_comx_init();
+#endif
+#ifdef CONFIG_COMX_HW_LOCOMX
+	comx_hw_locomx_init();
+#endif
+#ifdef CONFIG_COMX_HW_MIXCOM
+	comx_hw_mixcom_init();
+#endif
+#ifdef CONFIG_COMX_PROTO_HDLC
+	comx_proto_hdlc_init();
+#endif
+#ifdef CONFIG_COMX_PROTO_PPP
+	comx_proto_ppp_init();
+#endif
+#ifdef CONFIG_COMX_PROTO_LAPB
+	comx_proto_lapb_init();
+#endif
+#ifdef CONFIG_COMX_PROTO_FR
+	comx_proto_fr_init();
+#endif
+#endif
+
+	return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+	remove_proc_entry(FILENAME_HARDWARELIST, &comx_root_dir);
+	remove_proc_entry(FILENAME_PROTOCOLLIST, &comx_root_dir);
+	proc_unregister(&proc_root, comx_root_dir.low_ino);
+}
+#endif
+
+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_normal_inode_ops);
+EXPORT_SYMBOL(comx_root_dir);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)