patch-2.4.23 linux-2.4.23/drivers/net/wan/hdlc_fr.c
Next file: linux-2.4.23/drivers/net/wan/hdlc_generic.c
Previous file: linux-2.4.23/drivers/net/wan/hdlc_cisco.c
Back to the patch index
Back to the overall index
- Lines: 944
- Date:
2003-11-28 10:26:20.000000000 -0800
- Orig file:
linux-2.4.22/drivers/net/wan/hdlc_fr.c
- Orig date:
2003-06-13 07:51:35.000000000 -0700
diff -urN linux-2.4.22/drivers/net/wan/hdlc_fr.c linux-2.4.23/drivers/net/wan/hdlc_fr.c
@@ -2,13 +2,22 @@
* Generic HDLC support routines for Linux
* Frame Relay support
*
- * Copyright (C) 1999 - 2001 Krzysztof Halasa <khc@pm.waw.pl>
+ * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl>
*
* 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.
- */
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+
+ Theory of PVC state in DCE mode:
+
+ (exist,new) -> 0,0 when "PVC create" or if "link unreliable"
+ 0,x -> 1,1 if "link reliable" when sending FULL STATUS
+ 1,1 -> 1,0 if received FULL STATUS ACK
+
+ (active) -> 0 when "ifconfig PVC down" or "link unreliable" or "PVC create"
+ -> 1 when "PVC up" and (exist,new) = 1,0
+*/
#include <linux/config.h>
#include <linux/module.h>
@@ -20,19 +29,23 @@
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/pkt_sched.h>
+#include <linux/random.h>
#include <linux/inetdevice.h>
#include <linux/lapb.h>
#include <linux/rtnetlink.h>
+#include <linux/etherdevice.h>
#include <linux/hdlc.h>
__inline__ pvc_device* find_pvc(hdlc_device *hdlc, u16 dlci)
{
- pvc_device *pvc=hdlc->state.fr.first_pvc;
-
- while (pvc) {
- if (netdev_dlci(&pvc->netdev) == dlci)
+ pvc_device *pvc = hdlc->state.fr.first_pvc;
+
+ while(pvc) {
+ if (pvc->dlci == dlci)
return pvc;
+ if (pvc->dlci > dlci)
+ return NULL; /* the listed is sorted */
pvc = pvc->next;
}
@@ -40,18 +53,72 @@
}
+__inline__ pvc_device* add_pvc(hdlc_device *hdlc, u16 dlci)
+{
+ pvc_device *pvc, **pvc_p = &hdlc->state.fr.first_pvc;
+
+ while(*pvc_p) {
+ if ((*pvc_p)->dlci == dlci)
+ return *pvc_p;
+ if ((*pvc_p)->dlci > dlci)
+ break; /* the listed is sorted */
+ pvc_p = &(*pvc_p)->next;
+ }
+
+ pvc = kmalloc(sizeof(pvc_device), GFP_ATOMIC);
+ if (!pvc)
+ return NULL;
+
+ memset(pvc, 0, sizeof(pvc_device));
+ pvc->dlci = dlci;
+ pvc->master = hdlc;
+ pvc->next = *pvc_p; /* Put it in the chain */
+ *pvc_p = pvc;
+ return pvc;
+}
+
+
+__inline__ int pvc_is_used(pvc_device *pvc)
+{
+ return pvc->main != NULL || pvc->ether != NULL;
+}
+
+
+__inline__ void delete_unused_pvcs(hdlc_device *hdlc)
+{
+ pvc_device **pvc_p = &hdlc->state.fr.first_pvc;
+
+ while(*pvc_p) {
+ if (!pvc_is_used(*pvc_p)) {
+ pvc_device *pvc = *pvc_p;
+ *pvc_p = pvc->next;
+ kfree(pvc);
+ continue;
+ }
+ pvc_p = &(*pvc_p)->next;
+ }
+}
-__inline__ u16 status_to_dlci(hdlc_device *hdlc, u8 *status,
- int *active, int *new)
+
+__inline__ struct net_device** get_dev_p(pvc_device *pvc, int type)
{
- *new = (status[2] & 0x08);
- *active = (!*new && (status[2] & 0x02));
+ if (type == ARPHRD_ETHER)
+ return &pvc->ether;
+ else
+ return &pvc->main;
+}
+
+
+__inline__ u16 status_to_dlci(u8 *status, int *active, int *new)
+{
+ *new = (status[2] & 0x08) ? 1 : 0;
+ *active = (status[2] & 0x02) ? 1 : 0;
return ((status[0] & 0x3F)<<4) | ((status[1] & 0x78)>>3);
}
-__inline__ void dlci_to_status(hdlc_device *hdlc, u16 dlci, u8 *status,
+__inline__ void dlci_to_status(u16 dlci, u8 *status,
int active, int new)
{
status[0] = (dlci>>4) & 0x3F;
@@ -66,37 +133,50 @@
-static int fr_hard_header(struct sk_buff *skb, struct net_device *dev,
- u16 type, void *daddr, void *saddr, unsigned int len)
+static int fr_hard_header(struct sk_buff **skb_p, u16 dlci)
{
u16 head_len;
+ struct sk_buff *skb = *skb_p;
- if (!daddr)
- daddr = dev->broadcast;
-
-#ifdef CONFIG_HDLC_DEBUG_HARD_HEADER
- printk(KERN_DEBUG "%s: fr_hard_header called\n", dev->name);
-#endif
-
- switch(type) {
- case ETH_P_IP:
+ switch(skb->protocol) {
+ case __constant_ntohs(ETH_P_IP):
head_len = 4;
skb_push(skb, head_len);
skb->data[3] = NLPID_IP;
break;
- case ETH_P_IPV6:
+ case __constant_ntohs(ETH_P_IPV6):
head_len = 4;
skb_push(skb, head_len);
skb->data[3] = NLPID_IPV6;
break;
- case LMI_PROTO:
+ case __constant_ntohs(LMI_PROTO):
head_len = 4;
skb_push(skb, head_len);
skb->data[3] = LMI_PROTO;
break;
+ case __constant_ntohs(ETH_P_802_3):
+ head_len = 10;
+ if (skb_headroom(skb) < head_len) {
+ struct sk_buff *skb2 = skb_realloc_headroom(skb,
+ head_len);
+ if (!skb2)
+ return -ENOBUFS;
+ dev_kfree_skb(skb);
+ skb = *skb_p = skb2;
+ }
+ skb_push(skb, head_len);
+ skb->data[3] = FR_PAD;
+ skb->data[4] = NLPID_SNAP;
+ skb->data[5] = FR_PAD;
+ skb->data[6] = 0x80;
+ skb->data[7] = 0xC2;
+ skb->data[8] = 0x00;
+ skb->data[9] = 0x07; /* bridged Ethernet frame w/out FCS */
+ break;
+
default:
head_len = 10;
skb_push(skb, head_len);
@@ -105,14 +185,12 @@
skb->data[5] = FR_PAD;
skb->data[6] = FR_PAD;
skb->data[7] = FR_PAD;
- skb->data[8] = type>>8;
- skb->data[9] = (u8)type;
+ *(u16*)(skb->data + 8) = skb->protocol;
}
- memcpy(skb->data, daddr, 2);
+ dlci_to_q922(skb->data, dlci);
skb->data[2] = FR_UI;
-
- return head_len;
+ return 0;
}
@@ -124,13 +202,12 @@
if ((hdlc_to_dev(pvc->master)->flags & IFF_UP) == 0)
return -EIO; /* Master must be UP in order to activate PVC */
- if (pvc->master->state.fr.settings.lmi != LMI_NONE)
- pvc->state.active = 0;
- else
- pvc->state.active = 1;
+ if (pvc->open_count++ == 0) {
+ if (pvc->master->state.fr.settings.lmi == LMI_NONE)
+ pvc->state.active = 1;
- pvc->state.new = 0;
- pvc->master->state.fr.changed = 1;
+ pvc->master->state.fr.dce_changed = 1;
+ }
return 0;
}
@@ -139,38 +216,94 @@
static int pvc_close(struct net_device *dev)
{
pvc_device *pvc = dev_to_pvc(dev);
- pvc->state.active = pvc->state.new = 0;
- pvc->master->state.fr.changed = 1;
+
+ if (--pvc->open_count == 0) {
+ if (pvc->master->state.fr.settings.lmi == LMI_NONE)
+ pvc->state.active = 0;
+
+ if (pvc->master->state.fr.settings.dce) {
+ pvc->master->state.fr.dce_changed = 1;
+ pvc->state.active = 0;
+ }
+ }
return 0;
}
-static int pvc_xmit(struct sk_buff *skb, struct net_device *dev)
+int pvc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
pvc_device *pvc = dev_to_pvc(dev);
+ fr_proto_pvc_info info;
- if (pvc->state.active) {
- skb->dev = hdlc_to_dev(pvc->master);
- pvc->stats.tx_bytes += skb->len;
- pvc->stats.tx_packets++;
- if (pvc->state.fecn)
- pvc->stats.tx_compressed++; /* TX Congestion counter */
- dev_queue_xmit(skb);
- } else {
- pvc->stats.tx_dropped++;
- dev_kfree_skb(skb);
+ if (ifr->ifr_settings.type == IF_GET_PROTO) {
+ if (dev->type == ARPHRD_ETHER)
+ ifr->ifr_settings.type = IF_PROTO_FR_ETH_PVC;
+ else
+ ifr->ifr_settings.type = IF_PROTO_FR_PVC;
+
+ if (ifr->ifr_settings.size < sizeof(info)) {
+ /* data size wanted */
+ ifr->ifr_settings.size = sizeof(info);
+ return -ENOBUFS;
+ }
+
+ info.dlci = pvc->dlci;
+ memcpy(info.master, hdlc_to_name(pvc->master), IFNAMSIZ);
+ if (copy_to_user(ifr->ifr_settings.ifs_ifsu.fr_pvc_info,
+ &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
}
- return 0;
+ return -EINVAL;
+}
+
+
+__inline__ struct net_device_stats *pvc_get_stats(struct net_device *dev)
+{
+ return (struct net_device_stats *)
+ ((char *)dev + sizeof(struct net_device));
}
-static struct net_device_stats *pvc_get_stats(struct net_device *dev)
+static int pvc_xmit(struct sk_buff *skb, struct net_device *dev)
{
pvc_device *pvc = dev_to_pvc(dev);
- return &pvc->stats;
+ struct net_device_stats *stats = pvc_get_stats(dev);
+
+ if (pvc->state.active) {
+ if (dev->type == ARPHRD_ETHER) {
+ int pad = ETH_ZLEN - skb->len;
+ if (pad > 0) { /* Pad the frame with zeros */
+ int len = skb->len;
+ if (skb_tailroom(skb) < pad)
+ if (pskb_expand_head(skb, 0, pad,
+ GFP_ATOMIC)) {
+ stats->tx_dropped++;
+ dev_kfree_skb(skb);
+ return 0;
+ }
+ skb_put(skb, pad);
+ memset(skb->data + len, 0, pad);
+ }
+ 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 = hdlc_to_dev(pvc->master);
+ dev_queue_xmit(skb);
+ return 0;
+ }
+ }
+
+ stats->tx_dropped++;
+ dev_kfree_skb(skb);
+ return 0;
}
@@ -187,9 +320,15 @@
static inline void fr_log_dlci_active(pvc_device *pvc)
{
- printk(KERN_INFO "%s: %sactive%s\n", pvc_to_name(pvc),
- pvc->state.active ? "" : "in",
- pvc->state.new ? " new" : "");
+ printk(KERN_INFO "%s: DLCI %d [%s%s%s]%s %s\n",
+ hdlc_to_name(pvc->master),
+ pvc->dlci,
+ pvc->main ? pvc->main->name : "",
+ pvc->main && pvc->ether ? " " : "",
+ pvc->ether ? pvc->ether->name : "",
+ pvc->state.new ? " new" : "",
+ !pvc->state.exist ? "deleted" :
+ pvc->state.active ? "active" : "inactive");
}
@@ -213,8 +352,8 @@
int i = 0;
if (hdlc->state.fr.settings.dce && fullrep) {
- len += hdlc->state.fr.pvc_count * (2 + stat_len);
- if (len > HDLC_MAX_MTU) {
+ len += hdlc->state.fr.dce_pvc_count * (2 + stat_len);
+ if (len > HDLC_MAX_MRU) {
printk(KERN_WARNING "%s: Too many PVCs while sending "
"LMI full report\n", hdlc_to_name(hdlc));
return;
@@ -224,12 +363,13 @@
skb = dev_alloc_skb(len);
if (!skb) {
printk(KERN_WARNING "%s: Memory squeeze on fr_lmi_send()\n",
- hdlc_to_name(hdlc));
+ hdlc_to_name(hdlc));
return;
}
memset(skb->data, 0, len);
skb_reserve(skb, 4);
- fr_hard_header(skb, hdlc_to_dev(hdlc), LMI_PROTO, NULL, NULL, 0);
+ skb->protocol = __constant_htons(LMI_PROTO);
+ fr_hard_header(&skb, LMI_DLCI);
data = skb->tail;
data[i++] = LMI_CALLREF;
data[i++] = hdlc->state.fr.settings.dce
@@ -253,16 +393,20 @@
? LMI_CCITT_PVCSTAT : LMI_PVCSTAT;
data[i++] = stat_len;
- if (hdlc->state.fr.reliable &&
- (pvc->netdev.flags & IFF_UP) &&
- !pvc->state.active &&
- !pvc->state.new) {
- pvc->state.new = 1;
+ /* LMI start/restart */
+ if (hdlc->state.fr.reliable && !pvc->state.exist) {
+ pvc->state.exist = pvc->state.new = 1;
+ fr_log_dlci_active(pvc);
+ }
+
+ /* ifconfig PVC up */
+ if (pvc->open_count && !pvc->state.active &&
+ pvc->state.exist && !pvc->state.new) {
+ pvc->state.active = 1;
fr_log_dlci_active(pvc);
}
- dlci_to_status(hdlc, netdev_dlci(&pvc->netdev),
- data + i,
+ dlci_to_status(pvc->dlci, data + i,
pvc->state.active, pvc->state.new);
i += stat_len;
pvc = pvc->next;
@@ -272,6 +416,7 @@
skb_put(skb, i);
skb->priority = TC_PRIO_CONTROL;
skb->dev = hdlc_to_dev(hdlc);
+ skb->nh.raw = skb->data;
dev_queue_xmit(skb);
}
@@ -312,10 +457,11 @@
if (reliable) {
hdlc->state.fr.n391cnt = 0; /* Request full status */
- hdlc->state.fr.changed = 1;
+ hdlc->state.fr.dce_changed = 1;
} else {
while (pvc) { /* Deactivate all PVCs */
- pvc->state.new = pvc->state.active = 0;
+ pvc->state.exist = 0;
+ pvc->state.active = pvc->state.new = 0;
pvc = pvc->next;
}
}
@@ -346,7 +492,7 @@
{
int stat_len;
pvc_device *pvc;
- int reptype = -1, error;
+ int reptype = -1, error, no_ram;
u8 rxseq, txseq;
int i;
@@ -420,20 +566,18 @@
while (pvc) {
if (pvc->state.new) {
pvc->state.new = 0;
- pvc->state.active = 1;
- fr_log_dlci_active(pvc);
/* Tell DTE that new PVC is now active */
- hdlc->state.fr.changed = 1;
+ hdlc->state.fr.dce_changed = 1;
}
pvc = pvc->next;
}
}
- if (hdlc->state.fr.changed) {
+ if (hdlc->state.fr.dce_changed) {
reptype = LMI_FULLREP;
hdlc->state.fr.fullrep_sent = 1;
- hdlc->state.fr.changed = 0;
+ hdlc->state.fr.dce_changed = 0;
}
fr_lmi_send(hdlc, reptype == LMI_FULLREP ? 1 : 0);
@@ -449,13 +593,14 @@
pvc = hdlc->state.fr.first_pvc;
while (pvc) {
- pvc->state.deleted = pvc->state.active; /* mark active PVCs */
+ pvc->state.deleted = 1;
pvc = pvc->next;
}
+ no_ram = 0;
while (skb->len >= i + 2 + stat_len) {
u16 dlci;
- int active, new;
+ unsigned int active, new;
if (skb->data[i] != ((hdlc->state.fr.settings.lmi == LMI_CCITT)
? LMI_CCITT_PVCSTAT : LMI_PVCSTAT)) {
@@ -472,21 +617,28 @@
}
i++;
- dlci = status_to_dlci(hdlc, skb->data + i, &active, &new);
- pvc = find_pvc(hdlc, dlci);
+ dlci = status_to_dlci(skb->data + i, &active, &new);
+
+ pvc = add_pvc(hdlc, dlci);
+
+ if (!pvc && !no_ram) {
+ printk(KERN_WARNING
+ "%s: Memory squeeze on fr_lmi_recv()\n",
+ hdlc_to_name(hdlc));
+ no_ram = 1;
+ }
- active |= new;
if (pvc) {
- if (active && !pvc->state.active &&
- (pvc->netdev.flags & IFF_UP)) {
+ pvc->state.exist = 1;
+ pvc->state.deleted = 0;
+ if (active != pvc->state.active ||
+ new != pvc->state.new ||
+ !pvc->state.exist) {
+ pvc->state.new = new;
pvc->state.active = active;
fr_log_dlci_active(pvc);
}
- pvc->state.deleted = 0;
}
- else if (new)
- printk(KERN_INFO "%s: new PVC available, DLCI=%u\n",
- hdlc_to_name(hdlc), dlci);
i += stat_len;
}
@@ -494,10 +646,10 @@
pvc = hdlc->state.fr.first_pvc;
while (pvc) {
- if (pvc->state.deleted) {
+ if (pvc->state.deleted && pvc->state.exist) {
pvc->state.active = pvc->state.new = 0;
+ pvc->state.exist = 0;
fr_log_dlci_active(pvc);
- pvc->state.deleted = 0;
}
pvc = pvc->next;
}
@@ -517,8 +669,9 @@
u8 *data = skb->data;
u16 dlci;
pvc_device *pvc;
+ struct net_device *dev = NULL;
- if (skb->len<4 || fh->ea1 || data[2] != FR_UI)
+ if (skb->len <= 4 || fh->ea1 || data[2] != FR_UI)
goto rx_error;
dlci = q922_to_dlci(skb->data);
@@ -550,57 +703,39 @@
printk(KERN_INFO "%s: No PVC for received frame's DLCI %d\n",
hdlc_to_name(hdlc), dlci);
#endif
- goto rx_error;
- }
-
- if ((pvc->netdev.flags & IFF_UP) == 0) {
-#ifdef CONFIG_HDLC_DEBUG_PKT
- printk(KERN_INFO "%s: PVC for received frame's DLCI %d is down\n",
- hdlc_to_name(hdlc), dlci);
-#endif
- goto rx_error;
+ dev_kfree_skb_any(skb);
+ return;
}
- pvc->stats.rx_packets++; /* PVC traffic */
- pvc->stats.rx_bytes += skb->len;
-
- if (pvc->state.fecn != (fh->fecn ? PVC_STATE_FECN : 0)) {
+ if (pvc->state.fecn != fh->fecn) {
#ifdef CONFIG_HDLC_DEBUG_ECN
- printk(KERN_DEBUG "%s: FECN O%s\n", pvc_to_name(pvc),
- fh->fecn ? "N" : "FF");
+ printk(KERN_DEBUG "%s: DLCI %d FECN O%s\n", hdlc_to_name(pvc),
+ dlci, fh->fecn ? "N" : "FF");
#endif
pvc->state.fecn ^= 1;
}
- if (pvc->state.becn != (fh->becn ? PVC_STATE_BECN : 0)) {
+ if (pvc->state.becn != fh->becn) {
#ifdef CONFIG_HDLC_DEBUG_ECN
- printk(KERN_DEBUG "%s: BECN O%s\n", pvc_to_name(pvc),
- fh->becn ? "N" : "FF");
+ printk(KERN_DEBUG "%s: DLCI %d BECN O%s\n", hdlc_to_name(pvc),
+ dlci, fh->becn ? "N" : "FF");
#endif
pvc->state.becn ^= 1;
}
- if (pvc->state.becn)
- pvc->stats.rx_compressed++;
-
- skb->dev = &pvc->netdev;
if (data[3] == NLPID_IP) {
skb_pull(skb, 4); /* Remove 4-byte header (hdr, UI, NLPID) */
+ dev = pvc->main;
skb->protocol = htons(ETH_P_IP);
- netif_rx(skb);
- return;
- }
-
- if (data[3] == NLPID_IPV6) {
+ } else if (data[3] == NLPID_IPV6) {
skb_pull(skb, 4); /* Remove 4-byte header (hdr, UI, NLPID) */
+ dev = pvc->main;
skb->protocol = htons(ETH_P_IPV6);
- netif_rx(skb);
- return;
- }
- if (data[3] == FR_PAD && data[4] == NLPID_SNAP && data[5] == FR_PAD) {
+ } else if (skb->len > 10 && data[3] == FR_PAD &&
+ data[4] == NLPID_SNAP && data[5] == FR_PAD) {
u16 oui = ntohs(*(u16*)(data + 6));
u16 pid = ntohs(*(u16*)(data + 8));
skb_pull(skb, 10);
@@ -610,23 +745,39 @@
case ETH_P_IPX:
case ETH_P_IP: /* a long variant */
case ETH_P_IPV6:
+ dev = pvc->main;
skb->protocol = htons(pid);
break;
+ case 0x80C20007: /* bridged Ethernet frame */
+ if ((dev = pvc->ether) != NULL)
+ skb->protocol = eth_type_trans(skb, dev);
+ break;
+
default:
printk(KERN_INFO "%s: Unsupported protocol, OUI=%x "
"PID=%x\n", hdlc_to_name(hdlc), oui, pid);
dev_kfree_skb_any(skb);
return;
}
-
- netif_rx(skb);
+ } else {
+ printk(KERN_INFO "%s: Unsupported protocol, NLPID=%x "
+ "length = %i\n", hdlc_to_name(hdlc), data[3], skb->len);
+ dev_kfree_skb_any(skb);
return;
}
- printk(KERN_INFO "%s: Unsupported protocol, NLPID=%x\n",
- hdlc_to_name(hdlc), data[3]);
- dev_kfree_skb_any(skb);
+ if (dev) {
+ struct net_device_stats *stats = pvc_get_stats(dev);
+ stats->rx_packets++; /* PVC traffic */
+ stats->rx_bytes += skb->len;
+ if (pvc->state.becn)
+ stats->rx_compressed++;
+ skb->dev = dev;
+ netif_rx(skb);
+ } else
+ dev_kfree_skb_any(skb);
+
return;
rx_error:
@@ -641,7 +792,7 @@
if (hdlc->state.fr.settings.lmi != LMI_NONE) {
hdlc->state.fr.last_poll = 0;
hdlc->state.fr.reliable = 0;
- hdlc->state.fr.changed = 1;
+ hdlc->state.fr.dce_changed = 1;
hdlc->state.fr.request = 0;
hdlc->state.fr.fullrep_sent = 0;
hdlc->state.fr.last_errors = 0xFFFFFFFF;
@@ -669,90 +820,119 @@
if (hdlc->state.fr.settings.lmi != LMI_NONE)
del_timer_sync(&hdlc->state.fr.timer);
- while(pvc) {
- dev_close(&pvc->netdev); /* Shutdown all PVCs for this FRAD */
+ while(pvc) { /* Shutdown all PVCs for this FRAD */
+ if (pvc->main)
+ dev_close(pvc->main);
+ if (pvc->ether)
+ dev_close(pvc->ether);
+ pvc->state.active = pvc->state.new = pvc->state.fecn =
+ pvc->state.becn = 0;
+ pvc->state.exist = 0;
pvc = pvc->next;
}
}
-
-static int fr_pvc(hdlc_device *hdlc, unsigned int dlci, int create)
+static int fr_add_pvc(hdlc_device *hdlc, unsigned int dlci, int type)
{
- pvc_device **pvc_p = &hdlc->state.fr.first_pvc;
- pvc_device *pvc;
- int result;
+ pvc_device *pvc = NULL;
+ struct net_device *dev;
+ int result, used;
+ char * prefix = "pvc%d";
- if(dlci <= 0 || dlci >= 1024)
- return -EINVAL; /* Only 10 bits for DLCI, DLCI 0 reserved */
+ if (type == ARPHRD_ETHER)
+ prefix = "pvceth%d";
- while(*pvc_p) {
- if (netdev_dlci(&(*pvc_p)->netdev) == dlci)
- break;
- pvc_p = &(*pvc_p)->next;
+ if ((pvc = add_pvc(hdlc, dlci)) == NULL) {
+ printk(KERN_WARNING "%s: Memory squeeze on fr_add_pvc()\n",
+ hdlc_to_name(hdlc));
+ return -ENOBUFS;
}
- if (create) { /* Create PVC */
- if (*pvc_p != NULL)
- return -EEXIST;
-
- pvc = *pvc_p = kmalloc(sizeof(pvc_device), GFP_KERNEL);
- if (!pvc) {
- printk(KERN_WARNING "%s: Memory squeeze on fr_pvc()\n",
- hdlc_to_name(hdlc));
- return -ENOBUFS;
- }
- memset(pvc, 0, sizeof(pvc_device));
+ if (*get_dev_p(pvc, type))
+ return -EEXIST;
- pvc->netdev.hard_start_xmit = pvc_xmit;
- pvc->netdev.get_stats = pvc_get_stats;
- pvc->netdev.open = pvc_open;
- pvc->netdev.stop = pvc_close;
- pvc->netdev.change_mtu = pvc_change_mtu;
- pvc->netdev.mtu = HDLC_MAX_MTU;
-
- pvc->netdev.type = ARPHRD_DLCI;
- pvc->netdev.hard_header_len = 16;
- pvc->netdev.hard_header = fr_hard_header;
- pvc->netdev.tx_queue_len = 0;
- pvc->netdev.flags = IFF_POINTOPOINT;
-
- pvc->master = hdlc;
- *(u16*)pvc->netdev.dev_addr = htons(dlci);
- dlci_to_q922(pvc->netdev.broadcast, dlci);
- pvc->netdev.addr_len = 2;
+ used = pvc_is_used(pvc);
- result = dev_alloc_name(&pvc->netdev, "pvc%d");
- if (result < 0) {
- kfree(pvc);
- *pvc_p = NULL;
- return result;
- }
-
- if (register_netdevice(&pvc->netdev) != 0) {
- kfree(pvc);
- *pvc_p = NULL;
- return -EIO;
- }
+ dev = kmalloc(sizeof(struct net_device) +
+ sizeof(struct net_device_stats), GFP_KERNEL);
+ if (!dev) {
+ printk(KERN_WARNING "%s: Memory squeeze on fr_pvc()\n",
+ hdlc_to_name(hdlc));
+ delete_unused_pvcs(hdlc);
+ return -ENOBUFS;
+ }
+ memset(dev, 0, sizeof(struct net_device) +
+ sizeof(struct net_device_stats));
- hdlc->state.fr.changed = 1;
- hdlc->state.fr.pvc_count++;
- return 0;
+ if (type == ARPHRD_ETHER) {
+ ether_setup(dev);
+ memcpy(dev->dev_addr, "\x00\x01", 2);
+ get_random_bytes(dev->dev_addr + 2, ETH_ALEN - 2);
+ } else {
+ dev->type = ARPHRD_DLCI;
+ dev->flags = IFF_POINTOPOINT;
+ dev->hard_header_len = 10;
+ dev->addr_len = 2;
+ *(u16*)dev->dev_addr = htons(dlci);
+ dlci_to_q922(dev->broadcast, dlci);
+ }
+ dev->hard_start_xmit = pvc_xmit;
+ dev->get_stats = pvc_get_stats;
+ dev->open = pvc_open;
+ dev->stop = pvc_close;
+ dev->do_ioctl = pvc_ioctl;
+ dev->change_mtu = pvc_change_mtu;
+ dev->mtu = HDLC_MAX_MTU;
+ dev->tx_queue_len = 0;
+ dev->priv = pvc;
+
+ result = dev_alloc_name(dev, prefix);
+ if (result < 0) {
+ kfree(dev);
+ delete_unused_pvcs(hdlc);
+ return result;
+ }
+
+ if (register_netdevice(dev) != 0) {
+ kfree(dev);
+ delete_unused_pvcs(hdlc);
+ return -EIO;
+ }
+
+ *get_dev_p(pvc, type) = dev;
+ if (!used) {
+ hdlc->state.fr.dce_changed = 1;
+ hdlc->state.fr.dce_pvc_count++;
}
+ return 0;
+}
+
+
- if (*pvc_p == NULL) /* Delete PVC */
+static int fr_del_pvc(hdlc_device *hdlc, unsigned int dlci, int type)
+{
+ pvc_device *pvc;
+ struct net_device *dev;
+
+ if ((pvc = find_pvc(hdlc, dlci)) == NULL)
return -ENOENT;
- pvc = *pvc_p;
+ if ((dev = *get_dev_p(pvc, type)) == NULL)
+ return -ENOENT;
- if (pvc->netdev.flags & IFF_UP)
+ if (dev->flags & IFF_UP)
return -EBUSY; /* PVC in use */
- hdlc->state.fr.changed = 1;
- hdlc->state.fr.pvc_count--;
- *pvc_p = pvc->next;
- unregister_netdevice(&pvc->netdev);
- kfree(pvc);
+ unregister_netdevice(dev);
+ kfree(dev);
+ *get_dev_p(pvc, type) = NULL;
+
+ if (!pvc_is_used(pvc)) {
+ hdlc->state.fr.dce_pvc_count--;
+ hdlc->state.fr.dce_changed = 1;
+ }
+ delete_unused_pvcs(hdlc);
return 0;
}
@@ -763,14 +943,21 @@
pvc_device *pvc = hdlc->state.fr.first_pvc;
while(pvc) {
pvc_device *next = pvc->next;
- unregister_netdev(&pvc->netdev);
+ if (pvc->main) {
+ unregister_netdevice(pvc->main);
+ kfree(pvc->main);
+ }
+ if (pvc->ether) {
+ unregister_netdevice(pvc->ether);
+ kfree(pvc->ether);
+ }
kfree(pvc);
pvc = next;
}
hdlc->state.fr.first_pvc = NULL; /* All PVCs destroyed */
- hdlc->state.fr.pvc_count = 0;
- hdlc->state.fr.changed = 1;
+ hdlc->state.fr.dce_pvc_count = 0;
+ hdlc->state.fr.dce_changed = 1;
}
@@ -828,25 +1015,27 @@
if (hdlc->proto != IF_PROTO_FR) {
hdlc_proto_detach(hdlc);
hdlc->state.fr.first_pvc = NULL;
- hdlc->state.fr.pvc_count = 0;
+ hdlc->state.fr.dce_pvc_count = 0;
}
memcpy(&hdlc->state.fr.settings, &new_settings, size);
hdlc->open = fr_open;
hdlc->stop = fr_close;
hdlc->netif_rx = fr_rx;
+ hdlc->type_trans = NULL;
hdlc->proto_detach = fr_destroy;
hdlc->proto = IF_PROTO_FR;
dev->hard_start_xmit = hdlc->xmit;
- dev->hard_header = fr_hard_header;
+ dev->hard_header = NULL;
dev->type = ARPHRD_FRAD;
- dev->addr_len = 2;
- *(u16*)dev->dev_addr = htons(LMI_DLCI);
- dlci_to_q922(dev->broadcast, LMI_DLCI);
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ dev->addr_len = 0;
return 0;
case IF_PROTO_FR_ADD_PVC:
case IF_PROTO_FR_DEL_PVC:
+ case IF_PROTO_FR_ADD_ETH_PVC:
+ case IF_PROTO_FR_DEL_ETH_PVC:
if(!capable(CAP_NET_ADMIN))
return -EPERM;
@@ -854,8 +1043,20 @@
sizeof(fr_proto_pvc)))
return -EFAULT;
- return fr_pvc(hdlc, pvc.dlci,
- ifr->ifr_settings.type == IF_PROTO_FR_ADD_PVC);
+ if (pvc.dlci <= 0 || pvc.dlci >= 1024)
+ return -EINVAL; /* Only 10 bits, DLCI 0 reserved */
+
+ if (ifr->ifr_settings.type == IF_PROTO_FR_ADD_ETH_PVC ||
+ ifr->ifr_settings.type == IF_PROTO_FR_DEL_ETH_PVC)
+ result = ARPHRD_ETHER; /* bridged Ethernet device */
+ else
+ result = ARPHRD_DLCI;
+
+ if (ifr->ifr_settings.type == IF_PROTO_FR_ADD_PVC ||
+ ifr->ifr_settings.type == IF_PROTO_FR_ADD_ETH_PVC)
+ return fr_add_pvc(hdlc, pvc.dlci, result);
+ else
+ return fr_del_pvc(hdlc, pvc.dlci, result);
}
return -EINVAL;
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)