--- linux/drivers/net/wan/hdlc_fr.c	2004-04-04 05:37:45.000000000 +0200
+++ linux/drivers/net/wan/hdlc_fr.c	2004-05-13 17:18:28.000000000 +0200
@@ -3,6 +3,7 @@
  * Frame Relay support
  *
  * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
+ * Copyright (C) 2004 Piotr Kaczmarzyk <piotr@tahoe.pl>		[FRF.12 support]
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of version 2 of the GNU General Public License
@@ -65,6 +66,11 @@
 #define NLPID_SNAP	 0x80
 #define NLPID_PAD	 0x00
 #define NLPID_Q933	 0x08
+#define NLPID_FRAG	 0xB1
+
+#define FRAG_B		 0x80
+#define FRAG_E		 0x40
+#define FRAG_C		 0x20
 
 
 #define LMI_DLCI                   0 /* LMI DLCI */
@@ -164,6 +170,8 @@
 		return NULL;
 
 	memset(pvc, 0, sizeof(pvc_device));
+	skb_queue_head_init(&pvc->frag_queue);
+	pvc->wait_for_b = 1;
 	pvc->dlci = dlci;
 	pvc->master = dev;
 	pvc->next = *pvc_p;	/* Put it in the chain */
@@ -351,6 +359,7 @@
 {
 	pvc_device *pvc = dev_to_pvc(dev);
 	fr_proto_pvc_info info;
+	fr_proto_pvc p;
 
 	if (ifr->ifr_settings.type == IF_GET_PROTO) {
 		if (dev->type == ARPHRD_ETHER)
@@ -372,6 +381,24 @@
 		return 0;
 	}
 
+	if (ifr->ifr_settings.type == IF_PROTO_FR_GET_FRAG_SIZE) {
+		ifr->ifr_settings.type = pvc->frag_size;
+		return 0;
+	}
+	
+	if (ifr->ifr_settings.type == IF_PROTO_FR_SET_FRAG_SIZE) {
+	
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		if (copy_from_user(&p, ifr->ifr_settings.ifs_ifsu.fr_pvc,
+				   sizeof(fr_proto_pvc)))
+			return -EFAULT;
+
+		pvc->frag_size = p.dlci; /* 'dlci' field means fragment size */
+		return 0;
+	}
+
 	return -EINVAL;
 }
 
@@ -387,6 +414,8 @@
 {
 	pvc_device *pvc = dev_to_pvc(dev);
 	struct net_device_stats *stats = pvc_get_stats(dev);
+	int	i;
+	struct sk_buff *frag;
 
 	if (pvc->state.active) {
 		if (dev->type == ARPHRD_ETHER) {
@@ -405,14 +434,72 @@
 			}
 			skb->protocol = __constant_htons(ETH_P_802_3);
 		}
-		if (!fr_hard_header(&skb, pvc->dlci)) {
-			stats->tx_bytes += skb->len;
-			stats->tx_packets++;
-			if (pvc->state.fecn) /* TX Congestion counter */
-				stats->tx_compressed++;
-			skb->dev = pvc->master;
-			dev_queue_xmit(skb);
+		/* Fix me: do not fragment packets smaller than fragment size */
+		if (pvc->frag_size) {
+			/* Add Q.922 control and NLPID - add header, then remove address */
+			if (fr_hard_header(&skb, pvc->dlci)) {
+				stats->tx_dropped++;
+				dev_kfree_skb(skb);
+				return 0;
+			}
+			/* Remove address (DLCI) */
+			skb_pull(skb, 2);
+			
+			/* Fragment whole packet including FR header */
+			i = 0; /* Offset */
+			while (i < skb->len) {
+				frag = dev_alloc_skb(pvc->frag_size + 6);
+				if (!frag) {
+					/* No memory */
+					stats->tx_dropped++;
+					dev_kfree_skb(skb);
+					return 0;
+				}
+				frag->protocol = skb->protocol;
+				frag->len = i + pvc->frag_size > skb->len ? skb->len - i : pvc->frag_size;
+				memcpy(frag->data, skb->data + i, frag->len);
+
+				/* Add fragmentation header */
+				skb_push(frag, 2);
+				frag->data[0] = (pvc->tx_seq & 0xf00) >> 7;
+				frag->data[1] = pvc->tx_seq & 0xff;
+				pvc->tx_seq++;
+				if (i == 0) frag->data[0] |= FRAG_B;
+				if (i + pvc->frag_size >= skb->len) frag->data[0] |= FRAG_E;
+				i += frag->len - 2; /* Without fragmentation header */
+
+				/* Add FR header */
+				if (fr_hard_header(&frag, pvc->dlci)) {
+					stats->tx_dropped++;
+					dev_kfree_skb(frag);
+					dev_kfree_skb(skb);
+					return 0;
+				}
+
+				/* Fix NLPID */
+				frag->data[3] = NLPID_FRAG;
+
+				/* Enqueue */
+				stats->tx_bytes += frag->len;
+				stats->tx_packets++;
+				if (pvc->state.fecn) /* TX Congestion counter */
+					stats->tx_compressed++;
+				frag->dev = pvc->master;
+				dev_queue_xmit(frag);
+			}
+			dev_kfree_skb(skb);
 			return 0;
+		} else {
+			/* Do not fragment */
+			if (!fr_hard_header(&skb, pvc->dlci)) {
+				stats->tx_bytes += skb->len;
+				stats->tx_packets++;
+				if (pvc->state.fecn) /* TX Congestion counter */
+					stats->tx_compressed++;
+				skb->dev = pvc->master;
+				dev_queue_xmit(skb);
+				return 0;
+			}
 		}
 	}
 
@@ -805,6 +892,34 @@
 }
 
 
+static void copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
+{
+	/*
+	 *	Shift between the two data areas in bytes
+	 */
+	unsigned long offset = new->data - old->data;
+
+	new->list	= NULL;
+	new->sk		= NULL;
+	new->dev	= old->dev;
+	new->real_dev	= old->real_dev;
+	new->priority	= old->priority;
+	new->protocol	= old->protocol;
+	new->h.raw	= old->h.raw + offset;
+	new->nh.raw	= old->nh.raw + offset;
+	new->mac.raw	= old->mac.raw + offset;
+	memcpy(new->cb, old->cb, sizeof(old->cb));
+	new->local_df	= old->local_df;
+	new->pkt_type	= old->pkt_type;
+	new->stamp	= old->stamp;
+	new->destructor = NULL;
+	new->security	= old->security;
+#ifdef CONFIG_NET_SCHED
+	new->tc_index	= old->tc_index;
+#endif
+	atomic_set(&new->users, 1);
+}
+
 
 static int fr_rx(struct sk_buff *skb)
 {
@@ -812,9 +927,10 @@
 	hdlc_device *hdlc = dev_to_hdlc(ndev);
 	fr_hdr *fh = (fr_hdr*)skb->data;
 	u8 *data = skb->data;
-	u16 dlci;
+	u16 dlci, seq, i;
 	pvc_device *pvc;
 	struct net_device *dev = NULL;
+	struct sk_buff *new_skb, *qskb;
 
 	if (skb->len <= 4 || fh->ea1 || data[2] != FR_UI)
 		goto rx_error;
@@ -884,6 +1000,103 @@
 		dev = pvc->main;
 		skb->protocol = htons(ETH_P_IPV6);
 
+	} else if (data[3] == NLPID_FRAG) {
+		/* Sequence number */
+		seq = ((u16)(data[4] & 0x1e) << 7) | (u16)data[5];
+		if (pvc->wait_for_b) {
+			if (data[4] & FRAG_B) {
+				/* Remember sequence number */
+				pvc->last_rx_seq = seq;
+
+				/* Small packets may have both B and E bits set */
+				if (data[4] & FRAG_E) {
+					/* Copy DLCI number */
+					data[4] = data[0];
+					data[5] = data[1];
+
+					/* Remove fragmentation header */
+					skb_pull(skb, 4);
+
+					/* Packet received - recursive */
+					return fr_rx(skb);
+				} else {
+					/* First fragment received */
+					pvc->wait_for_b = 0;
+					skb_queue_tail(&pvc->frag_queue, skb);
+					return NET_RX_SUCCESS;
+				}
+			} else {
+				/* Waiting for (B)eginning fragment
+				   - discard others */
+				dev_kfree_skb_any(skb);
+				return NET_RX_DROP;
+			}
+		} else {
+			/* Following fragments */
+			if (((pvc->last_rx_seq + 1) & 0xfff) != seq) {
+				/* Lost fragment - discard queue */
+				skb_queue_purge(&pvc->frag_queue);
+				pvc->wait_for_b = 1;
+				dev_kfree_skb_any(skb);
+				return NET_RX_DROP;
+			}
+			
+			/* Remember sequence number */
+			pvc->last_rx_seq = seq;
+			if (data[4] & FRAG_E) {
+				u8	d0, d1;
+				
+				/* Final fragment - reassembly */
+				i = skb->len - 6; /* Reassembled packet length */
+				qskb = pvc->frag_queue.next;
+				while (qskb != (struct sk_buff *)&pvc->frag_queue) {
+					i += qskb->len - 6;
+					qskb = qskb->next;
+				}
+				new_skb = dev_alloc_skb(i);
+				if (!new_skb) {
+					/* No memory */
+					skb_queue_purge(&pvc->frag_queue);
+					pvc->wait_for_b = 1;
+					dev_kfree_skb_any(skb);
+					return NET_RX_DROP;
+				}
+
+				/* Glue fragments */
+				i = 0;	/* Offset */
+				while (pvc->frag_queue.next != (struct sk_buff *)&pvc->frag_queue) {
+					qskb = skb_dequeue(&pvc->frag_queue);
+					skb_pull(qskb, 6);
+					memcpy(new_skb->data + i, qskb->data, qskb->len);
+					dev_kfree_skb_any(qskb);
+					i += qskb->len;
+				}
+
+				/* Remember DLCI number */
+				d0 = skb->data[0];
+				d1 = skb->data[1];
+
+				/* Add last fragment */
+				skb_pull(skb, 6);
+				memcpy(new_skb->data + i, skb->data, skb->len);
+				i += skb->len;
+				copy_skb_header(new_skb, skb);
+				dev_kfree_skb_any(skb);
+				new_skb->len = i;
+				skb_push(new_skb, 2);
+				new_skb->data[0] = d0;
+				new_skb->data[1] = d1;
+
+				/* Packet received - recursive */
+				pvc->wait_for_b = 1;
+				return fr_rx(new_skb);
+			} else {
+				/* Intermediate fragment - queue it */
+				skb_queue_tail(&pvc->frag_queue, skb);
+				return NET_RX_SUCCESS;
+			}
+		}
+
 	} else if (skb->len > 10 && data[3] == FR_PAD &&
 		   data[4] == NLPID_SNAP && data[5] == FR_PAD) {
 		u16 oui = ntohs(*(u16*)(data + 6));
--- linux/include/linux/hdlc.h	2004-04-04 05:38:25.000000000 +0200
+++ linux/include/linux/hdlc.h	2004-05-11 02:53:01.000000000 +0200
@@ -79,6 +79,11 @@
 	struct net_device *main;
 	struct net_device *ether; /* bridged Ethernet interface */
 	struct pvc_device_struct *next;	/* Sorted in ascending DLCI order */
+	struct sk_buff_head	frag_queue;
+	u16 wait_for_b;
+	u16 last_rx_seq;
+	u16 tx_seq;
+	u16 frag_size;
 	int dlci;
 	int open_count;
 
--- linux/include/linux/if.h	2004-04-04 05:36:58.000000000 +0200
+++ linux/include/linux/if.h	2004-05-11 03:00:10.000000000 +0200
@@ -76,6 +76,8 @@
 #define IF_PROTO_FR_DEL_ETH_PVC 0x2009	/*  Delete FR Ethernet-bridged PVC */
 #define IF_PROTO_FR_PVC	0x200A		/* for reading PVC status	*/
 #define IF_PROTO_FR_ETH_PVC 0x200B
+#define IF_PROTO_FR_SET_FRAG_SIZE 0x200C	/*  Set fragment size		*/
+#define IF_PROTO_FR_GET_FRAG_SIZE 0x200D	/*  Get fragment size		*/
 
 
 /*
