/*
 * 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>
 * 
 * Contributors:
 * Arnaldo Carvalho de Melo <acme@conectiva.com.br> (0.73)
 *
 * 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
 * Version 0.73 (00/08/15)
 * 		- resource release on failure at fr_master_init and
 *		  fr_slave_init 		  
 */

#define VERSION "0.73"

#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 <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	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 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 device *dev = (struct 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 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 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 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 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 device *dev, struct sk_buff *skb) 
{
	struct comx_channel *ch = dev->priv;
	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
	struct 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 device *dev) 
{
	struct comx_channel *ch = dev->priv;
	struct proc_dir_entry *dir = ch->procdir->parent->subdir;
	struct 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) && (sdev->tbusy)) {
			clear_bit(0, &sdev->tbusy);
//			printk("%s: clearing tbusy\n",sdev->name);
			cnt++;
		}
	}

	clear_bit(0, &dev->tbusy);	// ezt nem talalja meg, mert ez FRAD
//	printk("%s: clearing tbusy\n", dev->name);


	mark_bh(NET_BH);
	return 0;
}

static void fr_status(struct 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 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) {
				sdev->tbusy = 0; 
//				printk("%s: clearing tbusy\n",sdev->name);
			}
			comx_status(sdev, status);
			if(status & (PROTO_UP | PROTO_LOOP)) {
				dev->flags |= IFF_RUNNING;
			} else {
				dev->flags &= ~IFF_RUNNING;
			}
		}
	}
}

static int fr_open(struct 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 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 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 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 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 {
		set_bit(0, &dev->tbusy);
		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->tbusy)) {
				set_bit(0, &sdev->tbusy);
//				printk("%s: clearing tbusy\n",sdev->name);
			}
		}
		 	
//		printk("%s: set tbusy\n", dev->name);
		switch(mch->HW_send_packet(dev, skb)) {
			case FRAME_QUEUED:
				clear_bit(0, &dev->tbusy);
//				printk("%s: clear tbusy\n", dev->name);
				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 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 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 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 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 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 device *new_master = dev_get(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) {
				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 device *dev) 
{
	struct comx_channel *ch = dev->priv;
	struct fr_data *fr = ch->LINE_privdata;
	struct 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
		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 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) {
		goto cleanup_LINE_privdata;
	}

	new_file->data = (void *)new_file;
	new_file->read_proc = &fr_read_proc;
	new_file->write_proc = &fr_write_proc;
	new_file->ops = &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) {
		goto cleanup_filename_dlci;
	}

	new_file->data = (void *)new_file;
	new_file->read_proc = &fr_read_proc;
	new_file->write_proc = &fr_write_proc;
	new_file->ops = &comx_normal_inode_ops;
	new_file->size = 4;
	new_file->nlink = 1;

	fr_set_keepalive(dev, 0);

	MOD_INC_USE_COUNT;
	return 0;
cleanup_filename_dlci:
	 remove_proc_entry(FILENAME_DLCI, ch->procdir);
cleanup_LINE_privdata:
	kfree(fr);
	return -EIO;
}

static int fr_slave_init(struct 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) {
		goto cleanup_LINE_privdata;
	}
	
	new_file->data = (void *)new_file;
	new_file->read_proc = &fr_read_proc;
	new_file->write_proc = &fr_write_proc;
	new_file->ops = &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) {
		goto cleanup_filename_dlci;
	}

	new_file->data = (void *)new_file;
	new_file->read_proc = &fr_read_proc;
	new_file->write_proc = &fr_write_proc;
	new_file->ops = &comx_normal_inode_ops;
	new_file->size = 10;
	new_file->nlink = 1;
	MOD_INC_USE_COUNT;
	return 0;
cleanup_filename_dlci:
         remove_proc_entry(FILENAME_DLCI, ch->procdir);
cleanup_LINE_privdata:
	kfree(fr);
	return -EIO;
}

static int dlci_open(struct device *dev)
{
	struct comx_channel *ch = dev->priv;

	ch->init_status |= HW_OPEN;

	MOD_INC_USE_COUNT;
	return 0;
}

static int dlci_close(struct device *dev)
{
	struct comx_channel *ch = dev->priv;

	ch->init_status &= ~HW_OPEN;

	MOD_DEC_USE_COUNT;
	return 0;
}

static int dlci_txe(struct 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 device *dev, char *page) 
{
	return 0;
}

static int dlci_init(struct 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 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 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

__initfunc(int 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 */

