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

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

diff -u --recursive --new-file v2.3.51/linux/drivers/net/wan/comx-proto-fr.c linux/drivers/net/wan/comx-proto-fr.c
@@ -0,0 +1,1006 @@
+/*
+ * Frame-relay protocol module for the COMX driver 
+ * for Linux 2.2.X
+ *
+ * Original author: Tivadar Szemethy <tiv@itc.hu>
+ * Maintainer: Gergely Madarasz <gorgo@itc.hu>
+ *
+ * Copyright (C) 1998-1999 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.70 (99/06/14):
+ *		- cleaned up the source code a bit
+ *		- ported back to kernel, now works as builtin code 
+ *
+ * Version 0.71 (99/06/25):
+ *		- use skb priorities and queues for sending keepalive
+ * 		- use device queues for slave->master data transmit
+ *		- set IFF_RUNNING only line protocol up
+ *		- fixes on slave device flags
+ * 
+ * Version 0.72 (99/07/09):
+ *		- handle slave tbusy with master tbusy (should be fixed)
+ *		- fix the keepalive timer addition/deletion
+ */
+
+#define VERSION "0.72"
+
+#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/if_arp.h>
+#include <linux/inetdevice.h>
+#include <linux/pkt_sched.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+#include "comx.h"
+#include "comxhw.h"
+
+MODULE_AUTHOR("Author: Tivadar Szemethy <tiv@itc.hu>");
+MODULE_DESCRIPTION("Frame Relay protocol implementation for the COMX drivers"
+	"for Linux kernel 2.2.X");
+
+#define	FRAD_UI		0x03
+#define	NLPID_IP	0xcc
+#define	NLPID_Q933_LMI	0x08
+#define	NLPID_CISCO_LMI	0x09	
+#define Q933_ENQ	0x75
+#define	Q933_LINESTAT	0x51
+#define	Q933_COUNTERS	0x53
+
+#define	MAXALIVECNT	3		/* No. of failures */
+
+struct fr_data {
+	u16	dlci;
+	struct	net_device *master;
+	char	keepa_pend;
+	char	keepa_freq;
+	char	keepalivecnt, keeploopcnt;
+	struct	timer_list keepa_timer;
+	u8	local_cnt, remote_cnt;
+};
+
+static struct comx_protocol fr_master_protocol;
+static struct comx_protocol fr_slave_protocol;
+static struct comx_hardware fr_dlci;
+
+static void fr_keepalive_send(struct net_device *dev) 
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+	struct sk_buff *skb;
+	u8 *fr_packet;
+	
+	skb=alloc_skb(dev->hard_header_len + 13, GFP_ATOMIC);
+	
+	if(skb==NULL)
+		return;
+               
+        skb_reserve(skb, dev->hard_header_len);
+        
+        fr_packet=(u8*)skb_put(skb, 13);
+                 
+	fr_packet[0] = (fr->dlci & (1024 - 15)) >> 2;
+	fr_packet[1] = (fr->dlci & 15) << 4 | 1;	// EA bit 1
+	fr_packet[2] = FRAD_UI;
+	fr_packet[3] = NLPID_Q933_LMI;
+	fr_packet[4] = 0;
+	fr_packet[5] = Q933_ENQ;
+	fr_packet[6] = Q933_LINESTAT;
+	fr_packet[7] = 0x01;
+	fr_packet[8] = 0x01;
+	fr_packet[9] = Q933_COUNTERS;
+	fr_packet[10] = 0x02;
+	fr_packet[11] = ++fr->local_cnt;
+	fr_packet[12] = fr->remote_cnt;
+
+	skb->dev = dev;
+	skb->priority = TC_PRIO_CONTROL;
+	dev_queue_xmit(skb);
+}
+
+static void fr_keepalive_timerfun(unsigned long d) 
+{
+	struct net_device *dev = (struct net_device *)d;
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+	struct comx_channel *sch;
+	struct fr_data *sfr;
+	struct net_device *sdev;
+
+	if (ch->init_status & LINE_OPEN) {
+		if (fr->keepalivecnt == MAXALIVECNT) {
+			comx_status(dev, ch->line_status & ~PROTO_UP);
+			dev->flags &= ~IFF_RUNNING;
+			for (; dir ; dir = dir->next) {
+				if(!S_ISDIR(dir->mode)) {
+				    continue;
+				}
+	
+				if ((sdev = dir->data) && (sch = sdev->priv) && 
+				    (sdev->type == ARPHRD_DLCI) && 
+				    (sfr = sch->LINE_privdata) 
+				    && (sfr->master == dev) && 
+				    (sdev->flags & IFF_UP)) {
+					sdev->flags &= ~IFF_RUNNING;
+					comx_status(sdev, 
+						sch->line_status & ~PROTO_UP);
+				}
+			}
+		}
+		if (fr->keepalivecnt <= MAXALIVECNT) {
+			++fr->keepalivecnt;
+		}
+		fr_keepalive_send(dev);
+	}
+	mod_timer(&fr->keepa_timer, jiffies + HZ * fr->keepa_freq);
+}
+
+static void fr_rx_lmi(struct net_device *dev, struct sk_buff *skb, 
+	u16 dlci, u8 nlpid) 
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+	struct comx_channel *sch;
+	struct fr_data *sfr;
+	struct net_device *sdev;
+
+	if (dlci != fr->dlci || nlpid != NLPID_Q933_LMI || !fr->keepa_freq) {
+		return;
+	}
+
+	fr->remote_cnt = skb->data[7];
+	if (skb->data[8] == fr->local_cnt) { // keepalive UP!
+		fr->keepalivecnt = 0;
+		if ((ch->line_status & LINE_UP) && 
+		    !(ch->line_status & PROTO_UP)) {
+			comx_status(dev, ch->line_status |= PROTO_UP);
+			dev->flags |= IFF_RUNNING;
+			for (; dir ; dir = dir->next) {
+				if(!S_ISDIR(dir->mode)) {
+				    continue;
+				}
+	
+				if ((sdev = dir->data) && (sch = sdev->priv) && 
+				    (sdev->type == ARPHRD_DLCI) && 
+				    (sfr = sch->LINE_privdata) 
+				    && (sfr->master == dev) && 
+				    (sdev->flags & IFF_UP)) {
+					sdev->flags |= IFF_RUNNING;
+					comx_status(sdev, 
+						sch->line_status | PROTO_UP);
+				}
+			}
+		}
+	}
+}
+
+static void fr_set_keepalive(struct net_device *dev, int keepa) 
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+
+	if (!keepa && fr->keepa_freq) { // switch off
+		fr->keepa_freq = 0;
+		if (ch->line_status & LINE_UP) {
+			comx_status(dev, ch->line_status | PROTO_UP);
+			dev->flags |= IFF_RUNNING;
+			del_timer(&fr->keepa_timer);
+		}
+		return;
+	}
+
+	if (keepa) { // bekapcs
+		if(fr->keepa_freq && (ch->line_status & LINE_UP)) {
+			del_timer(&fr->keepa_timer);
+		}
+		fr->keepa_freq = keepa;
+		fr->local_cnt = fr->remote_cnt = 0;
+		fr->keepa_timer.expires = jiffies + HZ;
+		fr->keepa_timer.function = fr_keepalive_timerfun;
+		fr->keepa_timer.data = (unsigned long)dev;
+		ch->line_status &= ~(PROTO_UP | PROTO_LOOP);
+		dev->flags &= ~IFF_RUNNING;
+		comx_status(dev, ch->line_status);
+		if(ch->line_status & LINE_UP) {
+			add_timer(&fr->keepa_timer);
+		}
+	}
+}
+
+static void fr_rx(struct net_device *dev, struct sk_buff *skb) 
+{
+	struct comx_channel *ch = dev->priv;
+	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+	struct net_device *sdev = dev;
+	struct comx_channel *sch;
+	struct fr_data *sfr;
+	u16 dlci;
+	u8 nlpid;
+
+	if(skb->len <= 4 || skb->data[2] != FRAD_UI) {
+		kfree_skb(skb);
+		return;
+	}
+
+	/* Itt majd ki kell talalni, melyik slave kapja a csomagot */
+	dlci = ((skb->data[0] & 0xfc) << 2) | ((skb->data[1] & 0xf0) >> 4);
+	if ((nlpid = skb->data[3]) == 0) { // Optional padding 
+		nlpid = skb->data[4];
+		skb_pull(skb, 1);
+	}
+	skb_pull(skb, 4);	/* DLCI and header throw away */
+
+	if (ch->debug_flags & DEBUG_COMX_DLCI) {
+		comx_debug(dev, "Frame received, DLCI: %d, NLPID: 0x%02x\n", 
+			dlci, nlpid);
+		comx_debug_skb(dev, skb, "Contents");
+	}
+
+	/* Megkeressuk, kihez tartozik */
+	for (; dir ; dir = dir->next) {
+		if(!S_ISDIR(dir->mode)) {
+			continue;
+		}
+		if ((sdev = dir->data) && (sch = sdev->priv) && 
+		    (sdev->type == ARPHRD_DLCI) && (sfr = sch->LINE_privdata) &&
+		    (sfr->master == dev) && (sfr->dlci == dlci)) {
+			skb->dev = sdev;	
+			if (ch->debug_flags & DEBUG_COMX_DLCI) {
+				comx_debug(dev, "Passing it to %s\n",sdev->name);
+			}
+			if (dev != sdev) {
+				sch->stats.rx_packets++;
+				sch->stats.rx_bytes += skb->len;
+			}
+			break;
+		}
+	}
+	switch(nlpid) {
+		case NLPID_IP:
+			skb->protocol = htons(ETH_P_IP);
+			skb->mac.raw = skb->data;
+			comx_rx(sdev, skb);
+			break;
+		case NLPID_Q933_LMI:
+			fr_rx_lmi(dev, skb, dlci, nlpid);
+		default:
+			kfree_skb(skb);
+			break;
+	}
+}
+
+static int fr_tx(struct net_device *dev) 
+{
+	struct comx_channel *ch = dev->priv;
+	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+	struct net_device *sdev;
+	struct comx_channel *sch;
+	struct fr_data *sfr;
+	int cnt = 1;
+
+	/* Ha minden igaz, 2 helyen fog allni a tbusy: a masternel, 
+	   es annal a slave-nel aki eppen kuldott.
+	   Egy helyen akkor all, ha a master kuldott.
+	   Ez megint jo lesz majd, ha utemezni akarunk */
+	   
+	/* This should be fixed, the slave tbusy should be set when 
+	   the masters queue is full and reset when not */
+
+	for (; dir ; dir = dir->next) {
+		if(!S_ISDIR(dir->mode)) {
+		    continue;
+		}
+		if ((sdev = dir->data) && (sch = sdev->priv) && 
+		    (sdev->type == ARPHRD_DLCI) && (sfr = sch->LINE_privdata) &&
+		    (sfr->master == dev) && (netif_queue_stopped(sdev))) {
+		    	netif_wake_queue(sdev);
+			cnt++;
+		}
+	}
+
+	netif_wake_queue(dev);
+	return 0;
+}
+
+static void fr_status(struct net_device *dev, unsigned short status)
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+	struct net_device *sdev;
+	struct comx_channel *sch;
+	struct fr_data *sfr;
+
+	if (status & LINE_UP) {
+		if (!fr->keepa_freq) {
+			status |= PROTO_UP;
+		}
+	} else {
+		status &= ~(PROTO_UP | PROTO_LOOP);
+	}
+
+	if (dev == fr->master && fr->keepa_freq) {
+		if (status & LINE_UP) {
+			fr->keepa_timer.expires = jiffies + HZ;
+			add_timer(&fr->keepa_timer);
+			fr->keepalivecnt = MAXALIVECNT + 1;
+			fr->keeploopcnt = 0;
+		} else {
+			del_timer(&fr->keepa_timer);
+		}
+	}
+		
+	/* Itt a status valtozast vegig kell vinni az osszes slave-n */
+	for (; dir ; dir = dir->next) {
+		if(!S_ISDIR(dir->mode)) {
+		    continue;
+		}
+	
+		if ((sdev = dir->data) && (sch = sdev->priv) && 
+		    (sdev->type == ARPHRD_FRAD || sdev->type == ARPHRD_DLCI) && 
+		    (sfr = sch->LINE_privdata) && (sfr->master == dev)) {
+			if(status & LINE_UP) {
+				netif_wake_queue(sdev);
+			}
+			comx_status(sdev, status);
+			if(status & (PROTO_UP | PROTO_LOOP)) {
+				dev->flags |= IFF_RUNNING;
+			} else {
+				dev->flags &= ~IFF_RUNNING;
+			}
+		}
+	}
+}
+
+static int fr_open(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+	struct proc_dir_entry *comxdir = ch->procdir;
+	struct comx_channel *mch;
+
+	if (!(ch->init_status & HW_OPEN)) {
+		return -ENODEV;
+	}
+
+	if ((ch->hardware == &fr_dlci && ch->protocol != &fr_slave_protocol) ||
+	    (ch->protocol == &fr_slave_protocol && ch->hardware != &fr_dlci)) {
+		printk(KERN_ERR "Trying to open an improperly set FR interface, giving up\n");
+		return -EINVAL;
+	}
+
+	if (!fr->master) {
+		return -ENODEV;
+	}
+	mch = fr->master->priv;
+	if (fr->master != dev && (!(mch->init_status & LINE_OPEN) 
+	   || (mch->protocol != &fr_master_protocol))) {
+		printk(KERN_ERR "Master %s is inactive, or incorrectly set up, "
+			"unable to open %s\n", fr->master->name, dev->name);
+		return -ENODEV;
+	}
+
+	ch->init_status |= LINE_OPEN;
+	ch->line_status &= ~(PROTO_UP | PROTO_LOOP);
+	dev->flags &= ~IFF_RUNNING;
+
+	if (fr->master == dev) {
+		if (fr->keepa_freq) {
+			fr->keepa_timer.function = fr_keepalive_timerfun;
+			fr->keepa_timer.data = (unsigned long)dev;
+			add_timer(&fr->keepa_timer);
+		} else {
+			if (ch->line_status & LINE_UP) {
+				ch->line_status |= PROTO_UP;
+				dev->flags |= IFF_RUNNING;
+			}
+		}
+	} else {
+		ch->line_status = mch->line_status;
+		if(fr->master->flags & IFF_RUNNING) {
+			dev->flags |= IFF_RUNNING;
+		}
+	}
+
+	for (; comxdir ; comxdir = comxdir->next) {
+		if (strcmp(comxdir->name, FILENAME_DLCI) == 0 ||
+		   strcmp(comxdir->name, FILENAME_MASTER) == 0 ||
+		   strcmp(comxdir->name, FILENAME_KEEPALIVE) == 0) {
+			comxdir->mode = S_IFREG | 0444;
+		}
+	}
+//	comx_status(dev, ch->line_status);
+	return 0;
+}
+
+static int fr_close(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+	struct proc_dir_entry *comxdir = ch->procdir;
+
+	if (fr->master == dev) { // Ha master 
+		struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+		struct net_device *sdev = dev;
+		struct comx_channel *sch;
+		struct fr_data *sfr;
+
+		if (!(ch->init_status & HW_OPEN)) {
+			return -ENODEV;
+		}
+
+		if (fr->keepa_freq) {
+			del_timer(&fr->keepa_timer);
+		}
+		
+		for (; dir ; dir = dir->next) {
+			if(!S_ISDIR(dir->mode)) {
+				continue;
+			}
+			if ((sdev = dir->data) && (sch = sdev->priv) && 
+			    (sdev->type == ARPHRD_DLCI) && 
+			    (sfr = sch->LINE_privdata) &&
+			    (sfr->master == dev) && 
+			    (sch->init_status & LINE_OPEN)) {
+				dev_close(sdev);
+			}
+		}
+	}
+
+	ch->init_status &= ~LINE_OPEN;
+	ch->line_status &= ~(PROTO_UP | PROTO_LOOP);
+	dev->flags &= ~IFF_RUNNING;
+
+	for (; comxdir ; comxdir = comxdir->next) {
+		if (strcmp(comxdir->name, FILENAME_DLCI) == 0 ||
+		    strcmp(comxdir->name, FILENAME_MASTER) == 0 ||
+		    strcmp(comxdir->name, FILENAME_KEEPALIVE) == 0) {
+			comxdir->mode = S_IFREG | 0444;
+		}
+	}
+
+	return 0;
+}
+
+static int fr_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct comx_channel *sch, *mch;
+	struct fr_data *fr = ch->LINE_privdata;
+	struct fr_data *sfr;
+	struct net_device *sdev;
+	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+
+	if (!fr->master) {
+		printk(KERN_ERR "BUG: fr_xmit without a master!!! dev: %s\n", dev->name);
+		return 0;
+	}
+
+	mch = fr->master->priv;
+
+	/* Ennek majd a slave utemezeskor lesz igazan jelentosege */
+	if (ch->debug_flags & DEBUG_COMX_DLCI) {
+		comx_debug_skb(dev, skb, "Sending frame");
+	}
+
+	if (dev != fr->master) {
+		struct sk_buff *newskb=skb_clone(skb, GFP_ATOMIC);
+		newskb->dev=fr->master;
+		dev_queue_xmit(newskb);
+		dev_kfree_skb(skb);
+		ch->stats.tx_packets++;
+		ch->stats.tx_bytes += skb->len;
+	} else {
+		netif_stop_queue(dev);
+		for (; dir ; dir = dir->next) {
+			if(!S_ISDIR(dir->mode)) {
+			    continue;
+			}
+			if ((sdev = dir->data) && (sch = sdev->priv) && 
+			    (sdev->type == ARPHRD_DLCI) && (sfr = sch->LINE_privdata) &&
+			    (sfr->master == dev) && (netif_queue_stopped(sdev))) {
+				netif_stop_queue(sdev);
+			}
+		}
+		 	
+		switch(mch->HW_send_packet(dev, skb)) {
+			case FRAME_QUEUED:
+				netif_wake_queue(dev);
+				break;
+			case FRAME_ACCEPTED:
+			case FRAME_DROPPED:
+				break;
+			case FRAME_ERROR:
+				printk(KERN_ERR "%s: Transmit frame error (len %d)\n", 
+					dev->name, skb->len);
+				break;
+		}
+	}
+	return 0;
+}
+
+static int fr_header(struct sk_buff *skb, struct net_device *dev, 
+	unsigned short type, void *daddr, void *saddr, unsigned len) 
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+
+	skb_push(skb, dev->hard_header_len);	  
+	/* Put in DLCI */
+	skb->data[0] = (fr->dlci & (1024 - 15)) >> 2;
+	skb->data[1] = (fr->dlci & 15) << 4 | 1;	// EA bit 1
+	skb->data[2] = FRAD_UI;
+	skb->data[3] = NLPID_IP;
+
+	return dev->hard_header_len;  
+}
+
+static int fr_statistics(struct net_device *dev, char *page) 
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+	int len = 0;
+
+	if (fr->master == dev) {
+		struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+		struct net_device *sdev;
+		struct comx_channel *sch;
+		struct fr_data *sfr;
+		int slaves = 0;
+
+		len += sprintf(page + len, 
+			"This is a Frame Relay master device\nSlaves: ");
+		for (; dir ; dir = dir->next) {
+			if(!S_ISDIR(dir->mode)) {
+				continue;
+			}
+			if ((sdev = dir->data) && (sch = sdev->priv) && 
+			    (sdev->type == ARPHRD_DLCI) &&
+			    (sfr = sch->LINE_privdata) && 
+			    (sfr->master == dev) && (sdev != dev)) {
+				slaves++;
+				len += sprintf(page + len, "%s ", sdev->name);
+			}
+		}
+		len += sprintf(page + len, "%s\n", slaves ? "" : "(none)");
+		if (fr->keepa_freq) {
+			len += sprintf(page + len, "Line keepalive (value %d) "
+				"status %s [%d]\n", fr->keepa_freq, 
+				ch->line_status & PROTO_LOOP ? "LOOP" :
+				ch->line_status & PROTO_UP ? "UP" : "DOWN", 
+				fr->keepalivecnt);
+		} else {
+			len += sprintf(page + len, "Line keepalive protocol "
+				"is not set\n");
+		}
+	} else {		// if slave
+		len += sprintf(page + len, 
+			"This is a Frame Relay slave device, master: %s\n",
+			fr->master ? fr->master->name : "(not set)");
+	}
+	return len;
+}
+
+static int fr_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 fr_data *fr = NULL;
+	int len = 0;
+
+	if (ch) {
+		fr = ch->LINE_privdata;
+	}
+
+	if (strcmp(file->name, FILENAME_DLCI) == 0) {
+		len = sprintf(page, "%04d\n", fr->dlci);
+	} else if (strcmp(file->name, FILENAME_MASTER) == 0) {
+		len = sprintf(page, "%-9s\n", fr->master ? fr->master->name :
+			"(none)");
+	} else if (strcmp(file->name, FILENAME_KEEPALIVE) == 0) {
+		len = fr->keepa_freq ? sprintf(page, "% 3d\n", fr->keepa_freq) 
+			: sprintf(page, "off\n");
+	} else {
+		printk(KERN_ERR "comxfr: internal error, filename %s\n", file->name);
+		return -EBADF;
+	}
+
+	if (off >= len) {
+		*eof = 1;
+		return 0;
+	}
+
+	*start = page + off;
+	if (count >= len - off) *eof = 1;
+	return ( min(count, len - off) );
+}
+
+static int fr_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 fr_data *fr = NULL; 
+	char *page;
+
+	if (ch) {
+		fr = ch->LINE_privdata;
+	}
+
+	if (file->f_dentry->d_inode->i_ino != entry->low_ino) {
+		printk(KERN_ERR "comxfr_write_proc: file <-> data internal error\n");
+		return -EIO;
+	}
+
+	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_DLCI) == 0) {
+		u16 dlci_new = simple_strtoul(page, NULL, 10);
+
+		if (dlci_new > 1023) {
+			printk(KERN_ERR "Invalid DLCI value\n");
+		}
+		else fr->dlci = dlci_new;
+	} else if (strcmp(entry->name, FILENAME_MASTER) == 0) {
+		struct net_device *new_master = dev_get_by_name(page);
+
+		if (new_master && new_master->type == ARPHRD_FRAD) {
+			struct comx_channel *sch = new_master->priv;
+			struct fr_data *sfr = sch->LINE_privdata;
+
+			if (sfr && sfr->master == new_master) {
+				if(fr->master)
+					dev_put(fr->master);
+				fr->master = new_master;
+				/* Megorokli a master statuszat */
+				ch->line_status = sch->line_status;
+			}
+		}
+	} else if (strcmp(entry->name, FILENAME_KEEPALIVE) == 0) {
+		int keepa_new = -1;
+
+		if (strcmp(page, KEEPALIVE_OFF) == 0) {
+			keepa_new = 0;
+		} else {
+			keepa_new = simple_strtoul(page, NULL, 10);
+		}
+
+		if (keepa_new < 0 || keepa_new > 100) {
+			printk(KERN_ERR "invalid keepalive\n");
+		} else {
+			if (fr->keepa_freq && keepa_new != fr->keepa_freq) {
+				fr_set_keepalive(dev, 0);
+			}
+			if (keepa_new) {
+				fr_set_keepalive(dev, keepa_new);
+			}
+		}
+	} else {
+		printk(KERN_ERR "comxfr_write_proc: internal error, filename %s\n", 
+			entry->name);
+		return -EBADF;
+	}
+
+	free_page((unsigned long)page);
+	return count;
+}
+
+static int fr_exit(struct net_device *dev) 
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+	struct net_device *sdev = dev;
+	struct comx_channel *sch;
+	struct fr_data *sfr;
+	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
+
+	/* Ha lezarunk egy master-t, le kell kattintani a slave-eket is */
+	if (fr->master && fr->master == dev) {
+		for (; dir ; dir = dir->next) {
+			if(!S_ISDIR(dir->mode)) {
+				continue;
+			}
+			if ((sdev = dir->data) && (sch = sdev->priv) && 
+			    (sdev->type == ARPHRD_DLCI) && 
+			    (sfr = sch->LINE_privdata) && (sfr->master == dev)) {
+				dev_close(sdev);
+				sfr->master = NULL;
+			}
+		}
+	}
+	dev->flags		= 0;
+	dev->type		= 0;
+	dev->mtu		= 0;
+	dev->hard_header_len    = 0;
+
+	ch->LINE_rx	= NULL;
+	ch->LINE_tx	= NULL;
+	ch->LINE_status = NULL;
+	ch->LINE_open	= NULL;
+	ch->LINE_close	= NULL;
+	ch->LINE_xmit	= NULL;
+	ch->LINE_header	= NULL;
+	ch->LINE_rebuild_header	= NULL;
+	ch->LINE_statistics = NULL;
+
+	ch->LINE_status = 0;
+
+	if (fr->master != dev) { // if not master, remove dlci
+		if(fr->master)
+			dev_put(fr->master);
+		remove_proc_entry(FILENAME_DLCI, ch->procdir);
+		remove_proc_entry(FILENAME_MASTER, ch->procdir);
+	} else {
+		if (fr->keepa_freq) {
+			fr_set_keepalive(dev, 0);
+		}
+		remove_proc_entry(FILENAME_KEEPALIVE, ch->procdir);
+		remove_proc_entry(FILENAME_DLCI, ch->procdir);
+	}
+
+	kfree(fr);
+	ch->LINE_privdata = NULL;
+
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static int fr_master_init(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr;
+	struct proc_dir_entry *new_file;
+
+	if ((fr = ch->LINE_privdata = kmalloc(sizeof(struct fr_data), 
+	    GFP_KERNEL)) == NULL) {
+		return -ENOMEM;
+	}
+	memset(fr, 0, sizeof(struct fr_data));
+	fr->master = dev;	// this means master
+	fr->dlci = 0;		// let's say default
+
+	dev->flags	= IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+	dev->type	= ARPHRD_FRAD;
+	dev->mtu	= 1500;
+	dev->hard_header_len    = 4;		
+	dev->addr_len	= 0;
+
+	ch->LINE_rx	= fr_rx;
+	ch->LINE_tx	= fr_tx;
+	ch->LINE_status = fr_status;
+	ch->LINE_open	= fr_open;
+	ch->LINE_close	= fr_close;
+	ch->LINE_xmit	= fr_xmit;
+	ch->LINE_header	= fr_header;
+	ch->LINE_rebuild_header	= NULL;
+	ch->LINE_statistics = fr_statistics;
+
+	if ((new_file = create_proc_entry(FILENAME_DLCI, S_IFREG | 0644, 
+	    ch->procdir)) == NULL) {
+		return -ENOMEM;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &fr_read_proc;
+	new_file->write_proc = &fr_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_KEEPALIVE, S_IFREG | 0644, 
+	    ch->procdir)) == NULL) {
+		return -ENOMEM;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &fr_read_proc;
+	new_file->write_proc = &fr_write_proc;
+	new_file->proc_iops = &comx_normal_inode_ops;
+	new_file->size = 4;
+	new_file->nlink = 1;
+
+	fr_set_keepalive(dev, 0);
+
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int fr_slave_init(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr;
+	struct proc_dir_entry *new_file;
+
+	if ((fr = ch->LINE_privdata = kmalloc(sizeof(struct fr_data), 
+	    GFP_KERNEL)) == NULL) {
+		return -ENOMEM;
+	}
+	memset(fr, 0, sizeof(struct fr_data));
+
+	dev->flags	= IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+	dev->type	= ARPHRD_DLCI;
+	dev->mtu	= 1500;
+	dev->hard_header_len    = 4;		
+	dev->addr_len	= 0;
+
+	ch->LINE_rx	= fr_rx;
+	ch->LINE_tx	= fr_tx;
+	ch->LINE_status = fr_status;
+	ch->LINE_open	= fr_open;
+	ch->LINE_close	= fr_close;
+	ch->LINE_xmit	= fr_xmit;
+	ch->LINE_header	= fr_header;
+	ch->LINE_rebuild_header	= NULL;
+	ch->LINE_statistics = fr_statistics;
+
+	if ((new_file = create_proc_entry(FILENAME_DLCI, S_IFREG | 0644, 
+	    ch->procdir)) == NULL) {
+		return -ENOMEM;
+	}
+	
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &fr_read_proc;
+	new_file->write_proc = &fr_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_MASTER, S_IFREG | 0644, 
+	    ch->procdir)) == NULL) {
+		return -EIO;
+	}
+	new_file->data = (void *)new_file;
+	new_file->read_proc = &fr_read_proc;
+	new_file->write_proc = &fr_write_proc;
+	new_file->proc_iops = &comx_normal_inode_ops;
+	new_file->size = 10;
+	new_file->nlink = 1;
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int dlci_open(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+
+	ch->init_status |= HW_OPEN;
+
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int dlci_close(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+
+	ch->init_status &= ~HW_OPEN;
+
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static int dlci_txe(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+	struct fr_data *fr = ch->LINE_privdata;
+
+	if (!fr->master) {
+		return 0;
+	}
+
+	ch = fr->master->priv;
+	fr = ch->LINE_privdata;
+	return ch->HW_txe(fr->master);
+}
+
+static int dlci_statistics(struct net_device *dev, char *page) 
+{
+	return 0;
+}
+
+static int dlci_init(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+
+	ch->HW_open = dlci_open;
+	ch->HW_close = dlci_close;
+	ch->HW_txe = dlci_txe;
+	ch->HW_statistics = dlci_statistics;
+
+	/* Nincs egyeb hw info, mert ugyis a fr->master-bol fog minden kiderulni */
+
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int dlci_exit(struct net_device *dev)
+{
+	struct comx_channel *ch = dev->priv;
+
+	ch->HW_open = NULL;
+	ch->HW_close = NULL;
+	ch->HW_txe = NULL;
+	ch->HW_statistics = NULL;
+
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static int dlci_dump(struct net_device *dev)
+{
+	printk(KERN_INFO "dlci_dump %s, HOGY MI ???\n", dev->name);
+	return -1;
+}
+
+static struct comx_protocol fr_master_protocol = {
+	"frad", 
+	VERSION,
+	ARPHRD_FRAD, 
+	fr_master_init, 
+	fr_exit, 
+	NULL 
+};
+
+static struct comx_protocol fr_slave_protocol = {
+	"ietf-ip", 
+	VERSION,
+	ARPHRD_DLCI, 
+	fr_slave_init, 
+	fr_exit, 
+	NULL 
+};
+
+static struct comx_hardware fr_dlci = { 
+	"dlci", 
+	VERSION,
+	dlci_init, 
+	dlci_exit, 
+	dlci_dump, 
+	NULL 
+};
+
+#ifdef MODULE
+#define comx_proto_fr_init init_module
+#endif
+
+int __init comx_proto_fr_init(void)
+{
+	int ret; 
+
+	if ((ret = comx_register_hardware(&fr_dlci))) {
+		return ret;
+	}
+	if ((ret = comx_register_protocol(&fr_master_protocol))) {
+		return ret;
+	}
+	return comx_register_protocol(&fr_slave_protocol);
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+	comx_unregister_hardware(fr_dlci.name);
+	comx_unregister_protocol(fr_master_protocol.name);
+	comx_unregister_protocol(fr_slave_protocol.name);
+}
+#endif /* MODULE */
+

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