patch-2.3.99-pre1 linux/drivers/net/wan/comx-hw-comx.c

Next file: linux/drivers/net/wan/comx-hw-locomx.c
Previous file: linux/drivers/net/wan/Makefile
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.51/linux/drivers/net/wan/comx-hw-comx.c linux/drivers/net/wan/comx-hw-comx.c
@@ -0,0 +1,1426 @@
+/*
+ * Hardware-level driver for the COMX and HICOMX cards
+ * for Linux kernel 2.2.X
+ *
+ * Original authors:  Arpad Bakay <bakay.arpad@synergon.hu>,
+ *                    Peter Bajan <bajan.peter@synergon.hu>,
+ * Rewritten by: Tivadar Szemethy <tiv@itc.hu>
+ * Currently maintained by: Gergely Madarasz <gorgo@itc.hu>
+ *
+ * Copyright (C) 1995-2000 ITConsult-Pro Co. <info@itc.hu>
+ *
+ * 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):
+ *		- port back to kernel, add support builtin driver 
+ *		- cleaned up the source code a bit
+ *
+ * Version 0.81 (99/06/22):
+ *		- cleaned up the board load functions, no more long reset
+ *		  timeouts
+ *		- lower modem lines on close
+ *		- some interrupt handling fixes
+ *
+ * Version 0.82 (99/08/24):
+ *		- fix multiple board support
+ *
+ * Version 0.83 (99/11/30):
+ *		- interrupt handling and locking fixes during initalization
+ *		- really fix multiple board support
+ * 
+ * Version 0.84 (99/12/02):
+ *		- some workarounds for problematic hardware/firmware
+ *
+ * Version 0.85 (00/01/14):
+ *		- some additional workarounds :/
+ *		- printk cleanups
+ */
+
+#define VERSION "0.85"
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/delay.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 <linux/ioport.h>
+#include <linux/init.h>
+
+#include "comx.h"
+#include "comxhw.h"
+
+MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>, Tivadar Szemethy <tiv@itc.hu>, Arpad Bakay");
+MODULE_DESCRIPTION("Hardware-level driver for the COMX and HICOMX adapters\n");
+
+#define	COMX_readw(dev, offset)	(readw(dev->mem_start + offset + \
+	(unsigned int)(((struct comx_privdata *)\
+	((struct comx_channel *)dev->priv)->HW_privdata)->channel) \
+	* COMX_CHANNEL_OFFSET))
+
+#define COMX_WRITE(dev, offset, value)	(writew(value, dev->mem_start + offset \
+	+ (unsigned int)(((struct comx_privdata *) \
+	((struct comx_channel *)dev->priv)->HW_privdata)->channel) \
+	* COMX_CHANNEL_OFFSET))
+
+#define COMX_CMD(dev, cmd)	(COMX_WRITE(dev, OFF_A_L2_CMD, cmd))
+
+struct comx_firmware {
+	int	len;
+	unsigned char *data;
+};
+
+struct comx_privdata {
+	struct comx_firmware *firmware;
+	u16	clock;
+	char	channel;		// channel no.
+	int	memory_size;
+	short	io_extent;
+	u_long	histogram[5];
+};
+
+static struct net_device *memory_used[(COMX_MEM_MAX - COMX_MEM_MIN) / 0x10000];
+extern struct comx_hardware hicomx_hw;
+extern struct comx_hardware comx_hw;
+extern struct comx_hardware cmx_hw;
+
+static void COMX_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+static void COMX_board_on(struct net_device *dev)
+{
+	outb_p( (byte) (((dev->mem_start & 0xf0000) >> 16) | 
+	    COMX_ENABLE_BOARD_IT | COMX_ENABLE_BOARD_MEM), dev->base_addr);
+}
+
+static void COMX_board_off(struct net_device *dev)
+{
+	outb_p( (byte) (((dev->mem_start & 0xf0000) >> 16) | 
+	   COMX_ENABLE_BOARD_IT), dev->base_addr);
+}
+
+static void HICOMX_board_on(struct net_device *dev)
+{
+	outb_p( (byte) (((dev->mem_start & 0xf0000) >> 12) | 
+	   HICOMX_ENABLE_BOARD_MEM), dev->base_addr);
+}
+
+static void HICOMX_board_off(struct net_device *dev)
+{
+	outb_p( (byte) (((dev->mem_start & 0xf0000) >> 12) | 
+	   HICOMX_DISABLE_BOARD_MEM), dev->base_addr);
+}
+
+static void COMX_set_clock(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+
+	COMX_WRITE(dev, OFF_A_L1_CLKINI, hw->clock);
+}
+
+static struct net_device *COMX_access_board(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct net_device *ret;
+	int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16;
+	unsigned long flags;
+
+
+	save_flags(flags); cli();
+	
+	ret = memory_used[mempos];
+
+	if(ret == dev) {
+		goto out;
+	}
+
+	memory_used[mempos] = dev;
+
+	if (!ch->twin || ret != ch->twin) {
+		if (ret) ((struct comx_channel *)ret->priv)->HW_board_off(ret);
+		ch->HW_board_on(dev);
+	}
+out:
+	restore_flags(flags);
+	return ret;
+}
+
+static void COMX_release_board(struct net_device *dev, struct net_device *savep)
+{
+	unsigned long flags;
+	int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16;
+	struct comx_channel *ch = dev->priv;
+
+	save_flags(flags); cli();
+
+	if (memory_used[mempos] == savep) {
+		goto out;
+	}
+
+	memory_used[mempos] = savep;
+	if (!ch->twin || ch->twin != savep) {
+		ch->HW_board_off(dev);
+		if (savep) ((struct comx_channel*)savep->priv)->HW_board_on(savep);
+	}
+out:
+	restore_flags(flags);
+}
+
+static int COMX_txe(struct net_device *dev) 
+{
+	struct net_device *savep;
+	struct comx_channel *ch = dev->priv;
+	int rc = 0;
+
+	savep = ch->HW_access_board(dev);
+	if (COMX_readw(dev,OFF_A_L2_LINKUP) == LINKUP_READY) {
+		rc = COMX_readw(dev,OFF_A_L2_TxEMPTY);
+	} 
+	ch->HW_release_board(dev,savep);
+	if(rc==0xffff) {
+		printk(KERN_ERR "%s, OFF_A_L2_TxEMPTY is %d\n",dev->name, rc);
+	}
+	return rc;
+}
+
+static int COMX_send_packet(struct net_device *dev, struct sk_buff *skb)
+{
+	struct net_device *savep;
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+	int ret = FRAME_DROPPED;
+	word tmp;
+
+	savep = ch->HW_access_board(dev);	
+
+	if (ch->debug_flags & DEBUG_HW_TX) {
+		comx_debug_bytes(dev, skb->data, skb->len,"COMX_send packet");
+	}
+
+	if (skb->len > COMX_MAX_TX_SIZE) {
+		ret=FRAME_DROPPED;
+		goto out;
+	}
+
+	tmp=COMX_readw(dev, OFF_A_L2_TxEMPTY);
+	if ((ch->line_status & LINE_UP) && tmp==1) {
+		int lensave = skb->len;
+		int dest = COMX_readw(dev, OFF_A_L2_TxBUFP);
+		word *data = (word *)skb->data;
+
+		if(dest==0xffff) {
+			printk(KERN_ERR "%s: OFF_A_L2_TxBUFP is %d\n", dev->name, dest);
+			ret=FRAME_DROPPED;
+			goto out;
+		}
+					
+		writew((unsigned short)skb->len, dev->mem_start + dest);
+		dest += 2;
+		while (skb->len > 1) {
+			writew(*data++, dev->mem_start + dest);
+			dest += 2; skb->len -= 2;
+		}
+		if (skb->len == 1) {
+			writew(*((byte *)data), dev->mem_start + dest);
+		}
+		writew(0, dev->mem_start + (int)hw->channel * 
+		   COMX_CHANNEL_OFFSET + OFF_A_L2_TxEMPTY);
+		ch->stats.tx_packets++;	
+		ch->stats.tx_bytes += lensave; 
+		ret = FRAME_ACCEPTED;
+	} else {
+		ch->stats.tx_dropped++;
+		printk(KERN_INFO "%s: frame dropped\n",dev->name);
+		if(tmp) {
+			printk(KERN_ERR "%s: OFF_A_L2_TxEMPTY is %d\n",dev->name,tmp);
+		}
+	}
+	
+out:
+	ch->HW_release_board(dev, savep);
+	dev_kfree_skb(skb);
+	return ret;
+}
+
+static inline int comx_read_buffer(struct net_device *dev) 
+{
+	struct comx_channel *ch = dev->priv;
+	word rbuf_offs;
+	struct sk_buff *skb;
+	word len;
+	int i=0;
+	word *writeptr;
+
+	i = 0;
+	rbuf_offs = COMX_readw(dev, OFF_A_L2_RxBUFP);
+	if(rbuf_offs == 0xffff) {
+		printk(KERN_ERR "%s: OFF_A_L2_RxBUFP is %d\n",dev->name,rbuf_offs);
+		return 0;
+	}
+	len = readw(dev->mem_start + rbuf_offs);
+	if(len > COMX_MAX_RX_SIZE) {
+		printk(KERN_ERR "%s: packet length is %d\n",dev->name,len);
+		return 0;
+	}
+	if ((skb = dev_alloc_skb(len + 16)) == NULL) {
+		ch->stats.rx_dropped++;
+		COMX_WRITE(dev, OFF_A_L2_DAV, 0);
+		return 0;
+	}
+	rbuf_offs += 2;
+	skb_reserve(skb, 16);
+	skb_put(skb, len);
+	skb->dev = dev;
+	writeptr = (word *)skb->data;
+	while (i < len) {
+		*writeptr++ = readw(dev->mem_start + rbuf_offs);
+		rbuf_offs += 2; 
+		i += 2;
+	}
+	COMX_WRITE(dev, OFF_A_L2_DAV, 0);
+	ch->stats.rx_packets++;
+	ch->stats.rx_bytes += len;
+	if (ch->debug_flags & DEBUG_HW_RX) {
+		comx_debug_skb(dev, skb, "COMX_interrupt receiving");
+	}
+	ch->LINE_rx(dev, skb);
+	return 1;
+}
+
+static inline char comx_line_change(struct net_device *dev, char linestat)
+{
+	struct comx_channel *ch=dev->priv;
+	char idle=1;
+	
+	
+	if (linestat & LINE_UP) { /* Vonal fol */
+		if (ch->lineup_delay) {
+			if (!test_and_set_bit(0, &ch->lineup_pending)) {
+				ch->lineup_timer.function = comx_lineup_func;
+				ch->lineup_timer.data = (unsigned long)dev;
+				ch->lineup_timer.expires = jiffies +
+					HZ*ch->lineup_delay;
+				add_timer(&ch->lineup_timer);
+				idle=0;
+			}
+		} else {
+			idle=0;
+			ch->LINE_status(dev, ch->line_status |= LINE_UP);
+		}
+	} else { /* Vonal le */
+		idle=0;
+		if (test_and_clear_bit(0, &ch->lineup_pending)) {
+			del_timer(&ch->lineup_timer);
+		} else {
+			ch->line_status &= ~LINE_UP;
+			if (ch->LINE_status) {
+				ch->LINE_status(dev, ch->line_status);
+			}
+		}
+	}
+	return idle;
+}
+
+
+
+static void COMX_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct net_device *dev = dev_id;
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+	struct net_device *interrupted;
+	unsigned long jiffs;
+	char idle = 0;
+	int count = 0;
+	word tmp;
+
+	if (dev == NULL) {
+		printk(KERN_ERR "COMX_interrupt: irq %d for unknown device\n", irq);
+		return;
+	}
+
+	jiffs = jiffies;
+
+	interrupted = ch->HW_access_board(dev);
+
+	while (!idle && count < 5000) {
+		char channel = 0;
+		idle = 1;
+
+		while (channel < 2) {
+			char linestat = 0;
+			char buffers_emptied = 0;
+
+			if (channel == 1) {
+				if (ch->twin) {
+					dev = ch->twin;
+					ch = dev->priv;
+					hw = ch->HW_privdata;
+				} else {
+					break;
+				}
+			} else {
+				COMX_WRITE(dev, OFF_A_L1_REPENA, 
+				    COMX_readw(dev, OFF_A_L1_REPENA) & 0xFF00);
+			}
+			channel++;
+
+			if ((ch->init_status & (HW_OPEN | LINE_OPEN)) != 
+			   (HW_OPEN | LINE_OPEN)) {
+				continue;
+			}
+	
+			/* Collect stats */
+			tmp = COMX_readw(dev, OFF_A_L1_ABOREC);
+			COMX_WRITE(dev, OFF_A_L1_ABOREC, 0);
+			if(tmp==0xffff) {
+				printk(KERN_ERR "%s: OFF_A_L1_ABOREC is %d\n",dev->name,tmp);
+				break;
+			} else {
+				ch->stats.rx_missed_errors += (tmp >> 8) & 0xff;
+				ch->stats.rx_over_errors += tmp & 0xff;
+			}
+			tmp = COMX_readw(dev, OFF_A_L1_CRCREC);
+			COMX_WRITE(dev, OFF_A_L1_CRCREC, 0);
+			if(tmp==0xffff) {
+				printk(KERN_ERR "%s: OFF_A_L1_CRCREC is %d\n",dev->name,tmp);
+				break;
+			} else {
+				ch->stats.rx_crc_errors += (tmp >> 8) & 0xff;
+				ch->stats.rx_missed_errors += tmp & 0xff;
+			}
+			
+			if ((ch->line_status & LINE_UP) && ch->LINE_rx) {
+				tmp=COMX_readw(dev, OFF_A_L2_DAV); 
+				while (tmp==1) {
+					idle=0;
+					buffers_emptied+=comx_read_buffer(dev);
+					tmp=COMX_readw(dev, OFF_A_L2_DAV); 
+				}
+				if(tmp) {
+					printk(KERN_ERR "%s: OFF_A_L2_DAV is %d\n", dev->name, tmp);
+					break;
+				}
+			}
+
+			tmp=COMX_readw(dev, OFF_A_L2_TxEMPTY);
+			if (tmp==1 && ch->LINE_tx) {
+				ch->LINE_tx(dev);
+			} 
+			if(tmp==0xffff) {
+				printk(KERN_ERR "%s: OFF_A_L2_TxEMPTY is %d\n", dev->name, tmp);
+				break;
+			}
+
+			if (COMX_readw(dev, OFF_A_L1_PBUFOVR) >> 8) {
+				linestat &= ~LINE_UP;
+			} else {
+				linestat |= LINE_UP;
+			}
+
+			if ((linestat & LINE_UP) != (ch->line_status & LINE_UP)) {
+				ch->stats.tx_carrier_errors++;
+				idle &= comx_line_change(dev,linestat);
+			}
+				
+			hw->histogram[(int)buffers_emptied]++;
+		}
+		count++;
+	}
+
+	if(count==5000) {
+		printk(KERN_WARNING "%s: interrupt stuck\n",dev->name);
+	}
+
+	ch->HW_release_board(dev, interrupted);
+}
+
+static int COMX_open(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+	struct proc_dir_entry *procfile = ch->procdir->subdir;
+	unsigned long jiffs;
+	int twin_open=0;
+	int retval;
+	struct net_device *savep;
+
+	if (!dev->base_addr || !dev->irq || !dev->mem_start) {
+		return -ENODEV;
+	}
+
+	if (ch->twin && (((struct comx_channel *)(ch->twin->priv))->init_status & HW_OPEN)) {
+		twin_open=1;
+	}
+
+	if (!twin_open) {
+		if (check_region(dev->base_addr, hw->io_extent)) {
+			return -EAGAIN;
+		}
+		if (request_irq(dev->irq, COMX_interrupt, 0, dev->name, 
+		   (void *)dev)) {
+			printk(KERN_ERR "comx-hw-comx: unable to obtain irq %d\n", dev->irq);
+			return -EAGAIN;
+		}
+		ch->init_status |= IRQ_ALLOCATED;
+		request_region(dev->base_addr, hw->io_extent, dev->name);
+		if (!ch->HW_load_board || ch->HW_load_board(dev)) {
+			ch->init_status &= ~IRQ_ALLOCATED;
+			retval=-ENODEV;
+			goto error;
+		}
+	}
+
+	savep = ch->HW_access_board(dev);
+	COMX_WRITE(dev, OFF_A_L2_LINKUP, 0);
+
+	if (ch->HW_set_clock) {
+		ch->HW_set_clock(dev);
+	}
+
+	COMX_CMD(dev, COMX_CMD_INIT); 
+	jiffs = jiffies;
+	while (COMX_readw(dev, OFF_A_L2_LINKUP) != 1 && jiffies < jiffs + HZ) {
+		schedule_timeout(1);
+	}
+	
+	if (jiffies >= jiffs + HZ) {
+		printk(KERN_ERR "%s: board timeout on INIT command\n", dev->name);
+		ch->HW_release_board(dev, savep);
+		retval=-EIO;
+		goto error;
+	}
+	udelay(1000);
+
+	COMX_CMD(dev, COMX_CMD_OPEN);
+
+	jiffs = jiffies;
+	while (COMX_readw(dev, OFF_A_L2_LINKUP) != 3 && jiffies < jiffs + HZ) {
+		schedule_timeout(1);
+	}
+	
+	if (jiffies >= jiffs + HZ) {
+		printk(KERN_ERR "%s: board timeout on OPEN command\n", dev->name);
+		ch->HW_release_board(dev, savep);
+		retval=-EIO;
+		goto error;
+	}
+	
+	ch->init_status |= HW_OPEN;
+	
+	/* Ez eleg ciki, de ilyen a rendszer */
+	if (COMX_readw(dev, OFF_A_L1_PBUFOVR) >> 8) {
+		ch->line_status &= ~LINE_UP;
+	} else {
+		ch->line_status |= LINE_UP;
+	}
+	
+	if (ch->LINE_status) {
+		ch->LINE_status(dev, ch->line_status);
+	}
+
+	ch->HW_release_board(dev, savep);
+
+	for ( ; procfile ; procfile = procfile->next) {
+		if (strcmp(procfile->name, FILENAME_IRQ) == 0 
+		    || strcmp(procfile->name, FILENAME_IO) == 0
+		    || strcmp(procfile->name, FILENAME_MEMADDR) == 0
+		    || strcmp(procfile->name, FILENAME_CHANNEL) == 0
+		    || strcmp(procfile->name, FILENAME_FIRMWARE) == 0
+		    || strcmp(procfile->name, FILENAME_CLOCK) == 0) {
+			procfile->mode = S_IFREG | 0444;
+		
+		}
+	}	
+	
+	return 0;	
+
+error:
+	if(!twin_open) {
+		release_region(dev->base_addr, hw->io_extent);
+		free_irq(dev->irq, (void *)dev);
+	}
+	return retval;
+
+}
+
+static int COMX_close(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct proc_dir_entry *procfile = ch->procdir->subdir;
+	struct comx_privdata *hw = ch->HW_privdata;
+	struct comx_channel *twin_ch;
+	struct net_device *savep;
+
+	savep = ch->HW_access_board(dev);
+
+	COMX_CMD(dev, COMX_CMD_CLOSE);
+	udelay(1000);
+	COMX_CMD(dev, COMX_CMD_EXIT);
+
+	ch->HW_release_board(dev, savep);
+
+	if (ch->init_status & IRQ_ALLOCATED) {
+		free_irq(dev->irq, (void *)dev);
+		ch->init_status &= ~IRQ_ALLOCATED;
+	}
+	release_region(dev->base_addr, hw->io_extent);
+
+	if (ch->twin && (twin_ch = ch->twin->priv) && 
+	    (twin_ch->init_status & HW_OPEN)) {
+		/* Pass the irq to the twin */
+		if (request_irq(dev->irq, COMX_interrupt, 0, ch->twin->name, 
+		   (void *)ch->twin) == 0) {
+			twin_ch->init_status |= IRQ_ALLOCATED;
+		}
+	}
+
+	for ( ; procfile ; procfile = procfile->next) {
+		if (strcmp(procfile->name, FILENAME_IRQ) == 0 
+		    || strcmp(procfile->name, FILENAME_IO) == 0
+		    || strcmp(procfile->name, FILENAME_MEMADDR) == 0
+		    || strcmp(procfile->name, FILENAME_CHANNEL) == 0
+		    || strcmp(procfile->name, FILENAME_FIRMWARE) == 0
+		    || strcmp(procfile->name, FILENAME_CLOCK) == 0) {
+			procfile->mode = S_IFREG | 0644;
+		}
+	}
+	
+	ch->init_status &= ~HW_OPEN;
+	return 0;
+}
+
+static int COMX_statistics(struct net_device *dev, char *page)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+	struct net_device *savep;
+	int len = 0;
+
+	savep = ch->HW_access_board(dev);
+
+	len += sprintf(page + len, "Board data: %s %s %s %s\nPBUFOVR: %02x, "
+		"MODSTAT: %02x, LINKUP: %02x, DAV: %02x\nRxBUFP: %02x, "
+		"TxEMPTY: %02x, TxBUFP: %02x\n",
+		(ch->init_status & HW_OPEN) ? "HW_OPEN" : "",
+		(ch->init_status & LINE_OPEN) ? "LINE_OPEN" : "",
+		(ch->init_status & FW_LOADED) ? "FW_LOADED" : "",
+		(ch->init_status & IRQ_ALLOCATED) ? "IRQ_ALLOCATED" : "",
+		COMX_readw(dev, OFF_A_L1_PBUFOVR) & 0xff,
+		(COMX_readw(dev, OFF_A_L1_PBUFOVR) >> 8) & 0xff,
+		COMX_readw(dev, OFF_A_L2_LINKUP) & 0xff,
+		COMX_readw(dev, OFF_A_L2_DAV) & 0xff,
+		COMX_readw(dev, OFF_A_L2_RxBUFP) & 0xff,
+		COMX_readw(dev, OFF_A_L2_TxEMPTY) & 0xff,
+		COMX_readw(dev, OFF_A_L2_TxBUFP) & 0xff);
+
+	len += sprintf(page + len, "hist[0]: %8lu hist[1]: %8lu hist[2]: %8lu\n"
+		"hist[3]: %8lu hist[4]: %8lu\n",hw->histogram[0],hw->histogram[1],
+		hw->histogram[2],hw->histogram[3],hw->histogram[4]);
+
+	ch->HW_release_board(dev, savep);
+
+	return len;
+}
+
+static int COMX_load_board(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+	struct comx_firmware *fw = hw->firmware;
+	word board_segment = dev->mem_start >> 16;
+	int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16;
+	unsigned long flags;
+	unsigned char id1, id2;
+	struct net_device *saved;
+	int retval;
+	int loopcount;
+	int len;
+	byte *COMX_address;
+
+	if (!fw || !fw->len) {
+		struct comx_channel *twin_ch = ch->twin ? ch->twin->priv : NULL;
+		struct comx_privdata *twin_hw;
+
+		if (!twin_ch || !(twin_hw = twin_ch->HW_privdata)) {
+			return -EAGAIN;
+		}
+
+		if (!(fw = twin_hw->firmware) || !fw->len) {
+			return -EAGAIN;
+		}
+	}
+
+	id1 = fw->data[OFF_FW_L1_ID]; 
+	id2 = fw->data[OFF_FW_L1_ID + 1];
+
+	if (id1 != FW_L1_ID_1 || id2 != FW_L1_ID_2_COMX) {
+		printk(KERN_ERR "%s: incorrect firmware, load aborted\n", 
+			dev->name);
+		return -EAGAIN;
+	}
+
+	printk(KERN_INFO "%s: Loading COMX Layer 1 firmware %s\n", dev->name, 
+		(char *)(fw->data + OFF_FW_L1_ID + 2));
+
+	id1 = fw->data[OFF_FW_L2_ID]; 
+	id2 = fw->data[OFF_FW_L2_ID + 1];
+	if (id1 == FW_L2_ID_1 && (id2 == 0xc0 || id2 == 0xc1 || id2 == 0xc2)) {
+		printk(KERN_INFO "with Layer 2 code %s\n", 
+			(char *)(fw->data + OFF_FW_L2_ID + 2));
+	}
+
+	outb_p(board_segment | COMX_BOARD_RESET, dev->base_addr);
+	/* 10 usec should be enough here */
+	udelay(100);
+
+	save_flags(flags); cli();
+	saved=memory_used[mempos];
+	if(saved) {
+		((struct comx_channel *)saved->priv)->HW_board_off(saved);
+	}
+	memory_used[mempos]=dev;
+
+	outb_p(board_segment | COMX_ENABLE_BOARD_MEM, dev->base_addr);
+
+	writeb(0, dev->mem_start + COMX_JAIL_OFFSET);	
+
+	loopcount=0;
+	while(loopcount++ < 10000 && 
+	    readb(dev->mem_start + COMX_JAIL_OFFSET) != COMX_JAIL_VALUE) {
+		udelay(100);
+	}	
+	
+	if (readb(dev->mem_start + COMX_JAIL_OFFSET) != COMX_JAIL_VALUE) {
+		printk(KERN_ERR "%s: Can't reset board, JAIL value is %02x\n",
+			dev->name, readb(dev->mem_start + COMX_JAIL_OFFSET));
+		retval=-ENODEV;
+		goto out;
+	}
+
+	writeb(0x55, dev->mem_start + 0x18ff);
+	
+	loopcount=0;
+	while(loopcount++ < 10000 && readb(dev->mem_start + 0x18ff) != 0) {
+		udelay(100);
+	}
+
+	if(readb(dev->mem_start + 0x18ff) != 0) {
+		printk(KERN_ERR "%s: Can't reset board, reset timeout\n",
+			dev->name);
+		retval=-ENODEV;
+		goto out;
+	}		
+
+	len = 0;
+	COMX_address = (byte *)dev->mem_start;
+	while (fw->len > len) {
+		writeb(fw->data[len++], COMX_address++);
+	}
+
+	len = 0;
+	COMX_address = (byte *)dev->mem_start;
+	while (len != fw->len && readb(COMX_address++) == fw->data[len]) {
+		len++;
+	}
+
+	if (len != fw->len) {
+		printk(KERN_ERR "%s: error loading firmware: [%d] is 0x%02x "
+			"instead of 0x%02x\n", dev->name, len, 
+			readb(COMX_address - 1), fw->data[len]);
+		retval=-EAGAIN;
+		goto out;
+	}
+
+	writeb(0, dev->mem_start + COMX_JAIL_OFFSET);
+
+	loopcount = 0;
+	while ( loopcount++ < 10000 && COMX_readw(dev, OFF_A_L2_LINKUP) != 1 ) {
+		udelay(100);
+	}
+
+	if (COMX_readw(dev, OFF_A_L2_LINKUP) != 1) {
+		printk(KERN_ERR "%s: error starting firmware, linkup word is %04x\n",
+			dev->name, COMX_readw(dev, OFF_A_L2_LINKUP));
+		retval=-EAGAIN;
+		goto out;
+	}
+
+
+	ch->init_status |= FW_LOADED;
+	retval=0;
+
+out: 
+	outb_p(board_segment | COMX_DISABLE_ALL, dev->base_addr);
+	if(saved) {
+		((struct comx_channel *)saved->priv)->HW_board_on(saved);
+	}
+	memory_used[mempos]=saved;
+	restore_flags(flags);
+	return retval;
+}
+
+static int CMX_load_board(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+	struct comx_firmware *fw = hw->firmware;
+	word board_segment = dev->mem_start >> 16;
+	int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16;
+	#if 0
+	unsigned char id1, id2;
+	#endif
+	struct net_device *saved;
+	unsigned long flags;
+	int retval;
+	int loopcount;
+	int len;
+	byte *COMX_address;
+
+	if (!fw || !fw->len) {
+		struct comx_channel *twin_ch = ch->twin ? ch->twin->priv : NULL;
+		struct comx_privdata *twin_hw;
+
+		if (!twin_ch || !(twin_hw = twin_ch->HW_privdata)) {
+			return -EAGAIN;
+		}
+
+		if (!(fw = twin_hw->firmware) || !fw->len) {
+			return -EAGAIN;
+		}
+	}
+
+	/* Ide kell olyat tenni, hogy ellenorizze az ID-t */
+
+	if (inb_p(dev->base_addr) != CMX_ID_BYTE) {
+		printk(KERN_ERR "%s: CMX id byte is invalid(%02x)\n", dev->name,
+			inb_p(dev->base_addr));
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO "%s: Loading CMX Layer 1 firmware %s\n", dev->name, 
+		(char *)(fw->data + OFF_FW_L1_ID + 2));
+
+	save_flags(flags); cli();
+	saved=memory_used[mempos];
+	if(saved) {
+		((struct comx_channel *)saved->priv)->HW_board_off(saved);
+	}
+	memory_used[mempos]=dev;
+	
+	outb_p(board_segment | COMX_ENABLE_BOARD_MEM | COMX_BOARD_RESET, 
+		dev->base_addr);
+
+	len = 0;
+	COMX_address = (byte *)dev->mem_start;
+	while (fw->len > len) {
+		writeb(fw->data[len++], COMX_address++);
+	}
+
+	len = 0;
+	COMX_address = (byte *)dev->mem_start;
+	while (len != fw->len && readb(COMX_address++) == fw->data[len]) {
+		len++;
+	}
+
+	outb_p(board_segment | COMX_ENABLE_BOARD_MEM, dev->base_addr);
+
+	if (len != fw->len) {
+		printk(KERN_ERR "%s: error loading firmware: [%d] is 0x%02x "
+			"instead of 0x%02x\n", dev->name, len, 
+			readb(COMX_address - 1), fw->data[len]);
+		retval=-EAGAIN;
+		goto out;
+	}
+
+	loopcount=0;
+	while( loopcount++ < 10000 && COMX_readw(dev, OFF_A_L2_LINKUP) != 1 ) {
+		udelay(100);
+	}
+
+	if (COMX_readw(dev, OFF_A_L2_LINKUP) != 1) {
+		printk(KERN_ERR "%s: error starting firmware, linkup word is %04x\n",
+			dev->name, COMX_readw(dev, OFF_A_L2_LINKUP));
+		retval=-EAGAIN;
+		goto out;
+	}
+
+	ch->init_status |= FW_LOADED;
+	retval=0;
+
+out: 
+	outb_p(board_segment | COMX_DISABLE_ALL, dev->base_addr);
+	if(saved) {
+		((struct comx_channel *)saved->priv)->HW_board_on(saved);
+	}
+	memory_used[mempos]=saved;
+	restore_flags(flags);
+	return retval;
+}
+
+static int HICOMX_load_board(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+	struct comx_firmware *fw = hw->firmware;
+	word board_segment = dev->mem_start >> 12;
+	int mempos = (dev->mem_start - COMX_MEM_MIN) >> 16;
+	struct net_device *saved;
+	unsigned char id1, id2;
+	unsigned long flags;
+	int retval;
+	int loopcount;
+	int len;
+	word *HICOMX_address;
+	char id = 1;
+
+	if (!fw || !fw->len) {
+		struct comx_channel *twin_ch = ch->twin ? ch->twin->priv : NULL;
+		struct comx_privdata *twin_hw;
+
+		if (!twin_ch || !(twin_hw = twin_ch->HW_privdata)) {
+			return -EAGAIN;
+		}
+
+		if (!(fw = twin_hw->firmware) || !fw->len) {
+			return -EAGAIN;
+		}
+	}
+
+	while (id != 4) {
+		if (inb_p(dev->base_addr + id++) != HICOMX_ID_BYTE) {
+			break;
+		}
+	}
+
+	if (id != 4) {
+		printk(KERN_ERR "%s: can't find HICOMX at 0x%04x, id[%d] = %02x\n",
+			dev->name, (unsigned int)dev->base_addr, id - 1,
+			inb_p(dev->base_addr + id - 1));
+		return -1;	
+	}
+
+	id1 = fw->data[OFF_FW_L1_ID]; 
+	id2 = fw->data[OFF_FW_L1_ID + 1];
+	if (id1 != FW_L1_ID_1 || id2 != FW_L1_ID_2_HICOMX) {
+		printk(KERN_ERR "%s: incorrect firmware, load aborted\n", dev->name);
+		return -EAGAIN;
+	}
+
+	printk(KERN_INFO "%s: Loading HICOMX Layer 1 firmware %s\n", dev->name, 
+		(char *)(fw->data + OFF_FW_L1_ID + 2));
+
+	id1 = fw->data[OFF_FW_L2_ID]; 
+	id2 = fw->data[OFF_FW_L2_ID + 1];
+	if (id1 == FW_L2_ID_1 && (id2 == 0xc0 || id2 == 0xc1 || id2 == 0xc2)) {
+		printk(KERN_INFO "with Layer 2 code %s\n", 
+			(char *)(fw->data + OFF_FW_L2_ID + 2));
+	}
+
+	outb_p(board_segment | HICOMX_BOARD_RESET, dev->base_addr);
+	udelay(10);	
+
+	save_flags(flags); cli();
+	saved=memory_used[mempos];
+	if(saved) {
+		((struct comx_channel *)saved->priv)->HW_board_off(saved);
+	}
+	memory_used[mempos]=dev;
+
+	outb_p(board_segment | HICOMX_ENABLE_BOARD_MEM, dev->base_addr);
+	outb_p(HICOMX_PRG_MEM, dev->base_addr + 1);
+
+	len = 0;
+	HICOMX_address = (word *)dev->mem_start;
+	while (fw->len > len) {
+		writeb(fw->data[len++], HICOMX_address++);
+	}
+
+	len = 0;
+	HICOMX_address = (word *)dev->mem_start;
+	while (len != fw->len && (readw(HICOMX_address++) & 0xff) == fw->data[len]) {
+		len++;
+	}
+
+	if (len != fw->len) {
+		printk(KERN_ERR "%s: error loading firmware: [%d] is 0x%02x "
+			"instead of 0x%02x\n", dev->name, len, 
+			readw(HICOMX_address - 1) & 0xff, fw->data[len]);
+		retval=-EAGAIN;
+		goto out;
+	}
+
+	outb_p(board_segment | HICOMX_BOARD_RESET, dev->base_addr);
+	outb_p(HICOMX_DATA_MEM, dev->base_addr + 1);
+
+	outb_p(board_segment | HICOMX_ENABLE_BOARD_MEM, dev->base_addr);
+
+	loopcount=0;
+	while(loopcount++ < 10000 && COMX_readw(dev, OFF_A_L2_LINKUP) != 1) {
+		udelay(100);
+	}
+
+	if ( COMX_readw(dev, OFF_A_L2_LINKUP) != 1 ) {
+		printk(KERN_ERR "%s: error starting firmware, linkup word is %04x\n",
+			dev->name, COMX_readw(dev, OFF_A_L2_LINKUP));
+		retval=-EAGAIN;
+		goto out;
+	}
+
+	ch->init_status |= FW_LOADED;
+	retval=0;
+
+out:
+	outb_p(board_segment | HICOMX_DISABLE_ALL, dev->base_addr);
+	outb_p(HICOMX_DATA_MEM, dev->base_addr + 1);
+
+	if(saved) {
+		((struct comx_channel *)saved->priv)->HW_board_on(saved);
+	}
+	memory_used[mempos]=saved;
+	restore_flags(flags);
+	return retval;
+}
+
+static struct net_device *comx_twin_check(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct proc_dir_entry *procfile = ch->procdir->parent->subdir;
+	struct comx_privdata *hw = ch->HW_privdata;
+
+	struct net_device *twin;
+	struct comx_channel *ch_twin;
+	struct comx_privdata *hw_twin;
+
+
+	for ( ; procfile ; procfile = procfile->next) {
+	
+		if(!S_ISDIR(procfile->mode)) {
+			continue;
+		}
+	
+		twin=procfile->data;
+		ch_twin=twin->priv;
+		hw_twin=ch_twin->HW_privdata;
+
+
+		if (twin != dev && dev->irq && dev->base_addr && dev->mem_start &&
+		   dev->irq == twin->irq && dev->base_addr == twin->base_addr &&
+	  	   dev->mem_start == twin->mem_start &&
+		   hw->channel == (1 - hw_twin->channel) &&
+		   ch->hardware == ch_twin->hardware) {
+		   	return twin;
+		}
+	}
+	return NULL;
+}
+
+static int comxhw_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 = entry->parent->data;
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+	char *page;
+
+
+	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(ch->init_status & HW_OPEN) {
+		return -EAGAIN;	
+	}
+	
+	if (strcmp(FILENAME_FIRMWARE, entry->name) != 0) {
+		if (!(page = (char *)__get_free_page(GFP_KERNEL))) {
+			return -ENOMEM;
+		}
+		copy_from_user(page, buffer, count = (min(count, PAGE_SIZE)));
+		if (*(page + count - 1) == '\n') {
+			*(page + count - 1) = 0;
+		}
+	} else {
+		byte *tmp;
+
+		if (!hw->firmware) {
+			if ((hw->firmware = kmalloc(sizeof(struct comx_firmware), 
+			    GFP_KERNEL)) == NULL) {
+			    	return -ENOMEM;
+			}
+			hw->firmware->len = 0;
+			hw->firmware->data = NULL;
+		}
+		
+		if ((tmp = kmalloc(count + file->f_pos, GFP_KERNEL)) == NULL) {
+			return -ENOMEM;
+		}
+		
+		/* Ha nem 0 a fpos, akkor meglevo file-t irunk. Gyenge trukk. */
+		if (hw->firmware && hw->firmware->len && file->f_pos 
+		    && hw->firmware->len < count + file->f_pos) {
+			memcpy(tmp, hw->firmware->data, hw->firmware->len);
+		}
+		if (hw->firmware->data) {
+			kfree(hw->firmware->data);
+		}
+		copy_from_user(tmp + file->f_pos, buffer, count);
+		hw->firmware->len = entry->size = file->f_pos + count;
+		hw->firmware->data = tmp;
+		file->f_pos += count;
+		return count;
+	}
+
+	if (strcmp(entry->name, FILENAME_CHANNEL) == 0) {
+		hw->channel = simple_strtoul(page, NULL, 0);
+		if (hw->channel >= MAX_CHANNELNO) {
+			printk(KERN_ERR "Invalid channel number\n");
+			hw->channel = 0;
+		}
+		if ((ch->twin = comx_twin_check(dev)) != NULL) {
+			struct comx_channel *twin_ch = ch->twin->priv;
+			twin_ch->twin = dev;
+		}
+	} else if (strcmp(entry->name, FILENAME_IRQ) == 0) {
+		dev->irq = simple_strtoul(page, NULL, 0);
+		if (dev->irq == 2) {
+			dev->irq = 9;
+		}
+		if (dev->irq < 3 || dev->irq > 15) {
+			printk(KERN_ERR "comxhw: Invalid irq number\n");
+			dev->irq = 0;
+		}
+		if ((ch->twin = comx_twin_check(dev)) != NULL) {
+			struct comx_channel *twin_ch = ch->twin->priv;
+			twin_ch->twin = dev;
+		}
+	} else if (strcmp(entry->name, FILENAME_IO) == 0) {
+		dev->base_addr = simple_strtoul(page, NULL, 0);
+		if ((dev->base_addr & 3) != 0 || dev->base_addr < 0x300 
+		   || dev->base_addr > 0x3fc) {
+			printk(KERN_ERR "Invalid io value\n");
+			dev->base_addr = 0;
+		}
+		if ((ch->twin = comx_twin_check(dev)) != NULL) {
+			struct comx_channel *twin_ch = ch->twin->priv;
+
+			twin_ch->twin = dev;
+		}
+	} else if (strcmp(entry->name, FILENAME_MEMADDR) == 0) {
+		dev->mem_start = simple_strtoul(page, NULL, 0);
+		if (dev->mem_start <= 0xf000 && dev->mem_start >= 0xa000) {
+			dev->mem_start *= 16;
+		}
+		if ((dev->mem_start & 0xfff) != 0 || dev->mem_start < COMX_MEM_MIN
+		    || dev->mem_start + hw->memory_size > COMX_MEM_MAX) {
+			printk(KERN_ERR "Invalid memory page\n");
+			dev->mem_start = 0;
+		}
+		dev->mem_end = dev->mem_start + hw->memory_size;
+		if ((ch->twin = comx_twin_check(dev)) != NULL) {
+			struct comx_channel *twin_ch = ch->twin->priv;
+
+			twin_ch->twin = dev;
+		}
+	} else if (strcmp(entry->name, FILENAME_CLOCK) == 0) {
+		if (strncmp("ext", page, 3) == 0) {
+			hw->clock = 0;
+		} else {
+			int kbps;
+
+			kbps = simple_strtoul(page, NULL, 0);
+			hw->clock = kbps ? COMX_CLOCK_CONST/kbps : 0;
+		}
+	}
+
+	free_page((unsigned long)page);
+	return count;
+}
+
+static int comxhw_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;
+	struct comx_privdata *hw = ch->HW_privdata;
+	int len = 0;
+
+
+	if (strcmp(file->name, FILENAME_IO) == 0) {
+		len = sprintf(page, "0x%03x\n", (unsigned int)dev->base_addr);
+	} else if (strcmp(file->name, FILENAME_IRQ) == 0) {
+		len = sprintf(page, "0x%02x\n", dev->irq == 9 ? 2 : dev->irq);
+	} else if (strcmp(file->name, FILENAME_CHANNEL) == 0) {
+		len = sprintf(page, "%01d\n", hw->channel);
+	} else if (strcmp(file->name, FILENAME_MEMADDR) == 0) {
+		len = sprintf(page, "0x%05x\n", (unsigned int)dev->mem_start);
+	} else if (strcmp(file->name, FILENAME_TWIN) == 0) {
+		len = sprintf(page, "%s\n", ch->twin ? ch->twin->name : "none");
+	} else if (strcmp(file->name, FILENAME_CLOCK) == 0) {
+		if (hw->clock) {
+			len = sprintf(page, "%-8d\n", COMX_CLOCK_CONST/hw->clock);
+		} else {
+			len = sprintf(page, "external\n");
+		}
+	} else if (strcmp(file->name, FILENAME_FIRMWARE) == 0) {
+		len = min(FILE_PAGESIZE, min(count, 
+			hw->firmware ? (hw->firmware->len - off) :  0));
+		if (len < 0) {
+			len = 0;
+		}
+		*start = hw->firmware ? (hw->firmware->data + off) : NULL;
+		if (off + len >= (hw->firmware ? hw->firmware->len : 0) || len == 0) {
+			*eof = 1;
+		}
+		return len;
+	}	
+
+	if (off >= len) {
+		*eof = 1;
+		return 0;
+	}
+
+	*start = page + off;
+	if (count >= len - off) {
+		*eof = 1;
+	}
+	return(min(count, len - off));
+}
+
+/* Called on echo comx >boardtype */
+static int COMX_init(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw;
+	struct proc_dir_entry *new_file;
+
+	if ((ch->HW_privdata = kmalloc(sizeof(struct comx_privdata), 
+	    GFP_KERNEL)) == NULL) {
+	    	return -ENOMEM;
+	}
+	memset(hw = ch->HW_privdata, 0, sizeof(struct comx_privdata));
+
+	if (ch->hardware == &comx_hw || ch->hardware == &cmx_hw) {
+		hw->memory_size = COMX_MEMORY_SIZE;
+		hw->io_extent = COMX_IO_EXTENT;
+		dev->base_addr = COMX_DEFAULT_IO;
+		dev->irq = COMX_DEFAULT_IRQ;
+		dev->mem_start = COMX_DEFAULT_MEMADDR;
+		dev->mem_end = COMX_DEFAULT_MEMADDR + COMX_MEMORY_SIZE;
+	} else if (ch->hardware == &hicomx_hw) {
+		hw->memory_size = HICOMX_MEMORY_SIZE;
+		hw->io_extent = HICOMX_IO_EXTENT;
+		dev->base_addr = HICOMX_DEFAULT_IO;
+		dev->irq = HICOMX_DEFAULT_IRQ;
+		dev->mem_start = HICOMX_DEFAULT_MEMADDR;
+		dev->mem_end = HICOMX_DEFAULT_MEMADDR + HICOMX_MEMORY_SIZE;
+	} else {
+		printk(KERN_ERR "SERIOUS INTERNAL ERROR in %s, line %d\n", __FILE__, __LINE__);
+	}
+
+	if ((new_file = create_proc_entry(FILENAME_IO, S_IFREG | 0644, ch->procdir))
+	    == NULL) {
+	    	return -EIO;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &comxhw_read_proc;
+	new_file->write_proc = &comxhw_write_proc;
+	new_file->proc_iops = &comx_normal_inode_ops;
+	new_file->size = 6;
+	new_file->nlink = 1;
+
+	if ((new_file = create_proc_entry(FILENAME_IRQ, S_IFREG | 0644, ch->procdir))
+	    == NULL) {
+	    	return -EIO;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &comxhw_read_proc;
+	new_file->write_proc = &comxhw_write_proc;
+	new_file->proc_iops = &comx_normal_inode_ops;
+	new_file->size = 5;
+	new_file->nlink = 1;
+
+	if ((new_file = create_proc_entry(FILENAME_CHANNEL, S_IFREG | 0644, 
+	    ch->procdir)) == NULL) {
+	    	return -EIO;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &comxhw_read_proc;
+	new_file->write_proc = &comxhw_write_proc;
+	new_file->proc_iops = &comx_normal_inode_ops;
+	new_file->size = 2;		// Ezt tudjuk
+	new_file->nlink = 1;
+
+	if (ch->hardware == &hicomx_hw || ch->hardware == &cmx_hw) {
+		if ((new_file = create_proc_entry(FILENAME_CLOCK, S_IFREG | 0644, 
+		   ch->procdir)) == NULL) {
+		   	return -EIO;
+		}
+		new_file->data = (void *)new_file;
+		new_file->read_proc = &comxhw_read_proc;
+		new_file->write_proc = &comxhw_write_proc;
+		new_file->proc_iops = &comx_normal_inode_ops;
+		new_file->size = 9;
+		new_file->nlink = 1;
+	}
+
+	if ((new_file = create_proc_entry(FILENAME_MEMADDR, S_IFREG | 0644, 
+	    ch->procdir)) == NULL) {
+	    	return -EIO;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &comxhw_read_proc;
+	new_file->write_proc = &comxhw_write_proc;
+	new_file->proc_iops = &comx_normal_inode_ops;
+	new_file->size = 8;
+	new_file->nlink = 1;
+
+	if ((new_file = create_proc_entry(FILENAME_TWIN, S_IFREG | 0444, 
+	    ch->procdir)) == NULL) {
+		return -EIO;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &comxhw_read_proc;
+	new_file->write_proc = NULL;
+	new_file->proc_iops = &comx_normal_inode_ops;
+	new_file->nlink = 1;
+
+	if ((new_file = create_proc_entry(FILENAME_FIRMWARE, S_IFREG | 0644, 
+	    ch->procdir)) == NULL) {
+	    	return -EIO;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &comxhw_read_proc;
+	new_file->write_proc = &comxhw_write_proc;
+	new_file->proc_iops = &comx_normal_inode_ops;
+	new_file->nlink = 1;
+
+	if (ch->hardware == &comx_hw) {
+		ch->HW_board_on = COMX_board_on;
+		ch->HW_board_off = COMX_board_off;
+		ch->HW_load_board = COMX_load_board;
+	} else if (ch->hardware == &cmx_hw) {
+		ch->HW_board_on = COMX_board_on;
+		ch->HW_board_off = COMX_board_off;
+		ch->HW_load_board = CMX_load_board;
+		ch->HW_set_clock = COMX_set_clock;
+	} else if (ch->hardware == &hicomx_hw) {
+		ch->HW_board_on = HICOMX_board_on;
+		ch->HW_board_off = HICOMX_board_off;
+		ch->HW_load_board = HICOMX_load_board;
+		ch->HW_set_clock = COMX_set_clock;
+	} else {
+		printk(KERN_ERR "SERIOUS INTERNAL ERROR in %s, line %d\n", __FILE__, __LINE__);
+	}
+
+	ch->HW_access_board = COMX_access_board;
+	ch->HW_release_board = COMX_release_board;
+	ch->HW_txe = COMX_txe;
+	ch->HW_open = COMX_open;
+	ch->HW_close = COMX_close;
+	ch->HW_send_packet = COMX_send_packet;
+	ch->HW_statistics = COMX_statistics;
+
+	if ((ch->twin = comx_twin_check(dev)) != NULL) {
+		struct comx_channel *twin_ch = ch->twin->priv;
+
+		twin_ch->twin = dev;
+	}
+
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+/* Called on echo valami >boardtype */
+static int COMX_exit(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_privdata *hw = ch->HW_privdata;
+
+	if (hw->firmware) {
+		if (hw->firmware->data) kfree(hw->firmware->data);
+		kfree(hw->firmware);
+	} if (ch->twin) {
+		struct comx_channel *twin_ch = ch->twin->priv;
+
+		twin_ch->twin = NULL;
+	}
+	
+	kfree(ch->HW_privdata);
+	remove_proc_entry(FILENAME_IO, ch->procdir);
+	remove_proc_entry(FILENAME_IRQ, ch->procdir);
+	remove_proc_entry(FILENAME_CHANNEL, ch->procdir);
+	remove_proc_entry(FILENAME_MEMADDR, ch->procdir);
+	remove_proc_entry(FILENAME_FIRMWARE, ch->procdir);
+	remove_proc_entry(FILENAME_TWIN, ch->procdir);
+	if (ch->hardware == &hicomx_hw || ch->hardware == &cmx_hw) {
+		remove_proc_entry(FILENAME_CLOCK, ch->procdir);
+	}
+
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static int COMX_dump(struct net_device *dev)
+{
+	printk(KERN_INFO "%s: COMX_dump called, why ?\n", dev->name);
+	return 0;
+}
+
+static struct comx_hardware comx_hw = {
+	"comx",
+	VERSION,
+	COMX_init,
+	COMX_exit,
+	COMX_dump,
+	NULL
+};
+
+static struct comx_hardware cmx_hw = {
+	"cmx",
+	VERSION,
+	COMX_init,
+	COMX_exit,
+	COMX_dump,
+	NULL
+};
+
+static struct comx_hardware hicomx_hw = {
+	"hicomx",
+	VERSION,
+	COMX_init,
+	COMX_exit,
+	COMX_dump,
+	NULL
+};
+
+#ifdef MODULE
+#define comx_hw_comx_init init_module
+#endif
+
+int __init comx_hw_comx_init(void)
+{
+	comx_register_hardware(&comx_hw);
+	comx_register_hardware(&cmx_hw);
+	comx_register_hardware(&hicomx_hw);
+	memset(memory_used, 0, sizeof(memory_used));
+	return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+	comx_unregister_hardware("comx");
+	comx_unregister_hardware("cmx");
+	comx_unregister_hardware("hicomx");
+}
+#endif

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