2019-05-29 00:10:09 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Generic HDLC support routines for Linux
|
|
|
|
* Frame Relay support
|
|
|
|
*
|
2006-09-27 04:23:45 +07:00
|
|
|
* Copyright (C) 1999 - 2006 Krzysztof Halasa <khc@pm.waw.pl>
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
|
|
|
|
|
|
|
Theory of PVC state
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
DTE mode:
|
|
|
|
(exist,new,active) = FULL STATUS if "link reliable"
|
|
|
|
= 0, 0, 0 if "link unreliable"
|
|
|
|
No LMI:
|
|
|
|
active = open and "link reliable"
|
|
|
|
exist = new = not used
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
CCITT LMI: ITU-T Q.933 Annex A
|
|
|
|
ANSI LMI: ANSI T1.617 Annex D
|
|
|
|
CISCO LMI: the original, aka "Gang of Four" LMI
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/errno.h>
|
2008-07-01 00:06:40 +07:00
|
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include <linux/hdlc.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/if_arp.h>
|
2008-07-01 00:06:40 +07:00
|
|
|
#include <linux/inetdevice.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/init.h>
|
2008-07-01 00:06:40 +07:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/pkt_sched.h>
|
2008-07-01 00:06:40 +07:00
|
|
|
#include <linux/poll.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/rtnetlink.h>
|
2008-07-01 00:06:40 +07:00
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/slab.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
#undef DEBUG_PKT
|
|
|
|
#undef DEBUG_ECN
|
|
|
|
#undef DEBUG_LINK
|
2006-09-27 04:23:45 +07:00
|
|
|
#undef DEBUG_PROTO
|
|
|
|
#undef DEBUG_PVC
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
#define FR_UI 0x03
|
|
|
|
#define FR_PAD 0x00
|
|
|
|
|
|
|
|
#define NLPID_IP 0xCC
|
|
|
|
#define NLPID_IPV6 0x8E
|
|
|
|
#define NLPID_SNAP 0x80
|
|
|
|
#define NLPID_PAD 0x00
|
|
|
|
#define NLPID_CCITT_ANSI_LMI 0x08
|
|
|
|
#define NLPID_CISCO_LMI 0x09
|
|
|
|
|
|
|
|
|
|
|
|
#define LMI_CCITT_ANSI_DLCI 0 /* LMI DLCI */
|
|
|
|
#define LMI_CISCO_DLCI 1023
|
|
|
|
|
|
|
|
#define LMI_CALLREF 0x00 /* Call Reference */
|
|
|
|
#define LMI_ANSI_LOCKSHIFT 0x95 /* ANSI locking shift */
|
|
|
|
#define LMI_ANSI_CISCO_REPTYPE 0x01 /* report type */
|
|
|
|
#define LMI_CCITT_REPTYPE 0x51
|
|
|
|
#define LMI_ANSI_CISCO_ALIVE 0x03 /* keep alive */
|
|
|
|
#define LMI_CCITT_ALIVE 0x53
|
|
|
|
#define LMI_ANSI_CISCO_PVCSTAT 0x07 /* PVC status */
|
|
|
|
#define LMI_CCITT_PVCSTAT 0x57
|
|
|
|
|
|
|
|
#define LMI_FULLREP 0x00 /* full report */
|
|
|
|
#define LMI_INTEGRITY 0x01 /* link integrity report */
|
|
|
|
#define LMI_SINGLE 0x02 /* single PVC report */
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
#define LMI_STATUS_ENQUIRY 0x75
|
|
|
|
#define LMI_STATUS 0x7D /* reply */
|
|
|
|
|
|
|
|
#define LMI_REPT_LEN 1 /* report type element length */
|
|
|
|
#define LMI_INTEG_LEN 2 /* link integrity element length */
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
#define LMI_CCITT_CISCO_LENGTH 13 /* LMI frame lengths */
|
|
|
|
#define LMI_ANSI_LENGTH 14
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
struct fr_hdr {
|
2005-04-17 05:20:36 +07:00
|
|
|
#if defined(__LITTLE_ENDIAN_BITFIELD)
|
|
|
|
unsigned ea1: 1;
|
|
|
|
unsigned cr: 1;
|
|
|
|
unsigned dlcih: 6;
|
2008-07-01 00:06:40 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned ea2: 1;
|
|
|
|
unsigned de: 1;
|
|
|
|
unsigned becn: 1;
|
|
|
|
unsigned fecn: 1;
|
|
|
|
unsigned dlcil: 4;
|
|
|
|
#else
|
|
|
|
unsigned dlcih: 6;
|
|
|
|
unsigned cr: 1;
|
|
|
|
unsigned ea1: 1;
|
|
|
|
|
|
|
|
unsigned dlcil: 4;
|
|
|
|
unsigned fecn: 1;
|
|
|
|
unsigned becn: 1;
|
|
|
|
unsigned de: 1;
|
|
|
|
unsigned ea2: 1;
|
|
|
|
#endif
|
2014-08-09 23:47:16 +07:00
|
|
|
} __packed;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device {
|
2006-09-27 04:23:45 +07:00
|
|
|
struct net_device *frad;
|
|
|
|
struct net_device *main;
|
|
|
|
struct net_device *ether; /* bridged Ethernet interface */
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *next; /* Sorted in ascending DLCI order */
|
2006-09-27 04:23:45 +07:00
|
|
|
int dlci;
|
|
|
|
int open_count;
|
|
|
|
|
|
|
|
struct {
|
|
|
|
unsigned int new: 1;
|
|
|
|
unsigned int active: 1;
|
|
|
|
unsigned int exist: 1;
|
|
|
|
unsigned int deleted: 1;
|
|
|
|
unsigned int fecn: 1;
|
|
|
|
unsigned int becn: 1;
|
|
|
|
unsigned int bandwidth; /* Cisco LMI reporting only */
|
|
|
|
}state;
|
2014-08-09 23:47:16 +07:00
|
|
|
};
|
2006-09-27 04:23:45 +07:00
|
|
|
|
|
|
|
struct frad_state {
|
|
|
|
fr_proto settings;
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *first_pvc;
|
2006-09-27 04:23:45 +07:00
|
|
|
int dce_pvc_count;
|
|
|
|
|
|
|
|
struct timer_list timer;
|
2017-10-17 07:29:22 +07:00
|
|
|
struct net_device *dev;
|
2006-09-27 04:23:45 +07:00
|
|
|
unsigned long last_poll;
|
|
|
|
int reliable;
|
|
|
|
int dce_changed;
|
|
|
|
int request;
|
|
|
|
int fullrep_sent;
|
|
|
|
u32 last_errors; /* last errors bit list */
|
|
|
|
u8 n391cnt;
|
|
|
|
u8 txseq; /* TX sequence number */
|
|
|
|
u8 rxseq; /* RX sequence number */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int fr_ioctl(struct net_device *dev, struct ifreq *ifr);
|
|
|
|
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static inline u16 q922_to_dlci(u8 *hdr)
|
|
|
|
{
|
|
|
|
return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static inline void dlci_to_q922(u8 *hdr, u16 dlci)
|
|
|
|
{
|
|
|
|
hdr[0] = (dlci >> 2) & 0xFC;
|
|
|
|
hdr[1] = ((dlci << 4) & 0xF0) | 0x01;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-02-02 04:34:30 +07:00
|
|
|
static inline struct frad_state* state(hdlc_device *hdlc)
|
2006-09-27 04:23:45 +07:00
|
|
|
{
|
|
|
|
return(struct frad_state *)(hdlc->state);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
static inline struct pvc_device *find_pvc(hdlc_device *hdlc, u16 dlci)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = state(hdlc)->first_pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
while (pvc) {
|
|
|
|
if (pvc->dlci == dlci)
|
|
|
|
return pvc;
|
|
|
|
if (pvc->dlci > dlci)
|
2009-11-25 20:20:20 +07:00
|
|
|
return NULL; /* the list is sorted */
|
2005-04-17 05:20:36 +07:00
|
|
|
pvc = pvc->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
static struct pvc_device *add_pvc(struct net_device *dev, u16 dlci)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc, **pvc_p = &state(hdlc)->first_pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
while (*pvc_p) {
|
|
|
|
if ((*pvc_p)->dlci == dlci)
|
|
|
|
return *pvc_p;
|
|
|
|
if ((*pvc_p)->dlci > dlci)
|
|
|
|
break; /* the list is sorted */
|
|
|
|
pvc_p = &(*pvc_p)->next;
|
|
|
|
}
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
pvc = kzalloc(sizeof(*pvc), GFP_ATOMIC);
|
2006-09-27 04:23:45 +07:00
|
|
|
#ifdef DEBUG_PVC
|
|
|
|
printk(KERN_DEBUG "add_pvc: allocated pvc %p, frad %p\n", pvc, dev);
|
|
|
|
#endif
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!pvc)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
pvc->dlci = dlci;
|
2006-09-27 04:23:45 +07:00
|
|
|
pvc->frad = dev;
|
2005-04-17 05:20:36 +07:00
|
|
|
pvc->next = *pvc_p; /* Put it in the chain */
|
|
|
|
*pvc_p = pvc;
|
|
|
|
return pvc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
static inline int pvc_is_used(struct pvc_device *pvc)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-09-27 04:23:45 +07:00
|
|
|
return pvc->main || pvc->ether;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
static inline void pvc_carrier(int on, struct pvc_device *pvc)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
if (on) {
|
|
|
|
if (pvc->main)
|
|
|
|
if (!netif_carrier_ok(pvc->main))
|
|
|
|
netif_carrier_on(pvc->main);
|
|
|
|
if (pvc->ether)
|
|
|
|
if (!netif_carrier_ok(pvc->ether))
|
|
|
|
netif_carrier_on(pvc->ether);
|
|
|
|
} else {
|
|
|
|
if (pvc->main)
|
|
|
|
if (netif_carrier_ok(pvc->main))
|
|
|
|
netif_carrier_off(pvc->main);
|
|
|
|
if (pvc->ether)
|
|
|
|
if (netif_carrier_ok(pvc->ether))
|
|
|
|
netif_carrier_off(pvc->ether);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static inline void delete_unused_pvcs(hdlc_device *hdlc)
|
|
|
|
{
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device **pvc_p = &state(hdlc)->first_pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
while (*pvc_p) {
|
|
|
|
if (!pvc_is_used(*pvc_p)) {
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = *pvc_p;
|
2006-09-27 04:23:45 +07:00
|
|
|
#ifdef DEBUG_PVC
|
|
|
|
printk(KERN_DEBUG "freeing unused pvc: %p\n", pvc);
|
|
|
|
#endif
|
2005-04-17 05:20:36 +07:00
|
|
|
*pvc_p = pvc->next;
|
|
|
|
kfree(pvc);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
pvc_p = &(*pvc_p)->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
static inline struct net_device **get_dev_p(struct pvc_device *pvc,
|
|
|
|
int type)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
if (type == ARPHRD_ETHER)
|
|
|
|
return &pvc->ether;
|
|
|
|
else
|
|
|
|
return &pvc->main;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-08 01:32:03 +07:00
|
|
|
static int fr_hard_header(struct sk_buff *skb, u16 dlci)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2020-09-28 19:56:43 +07:00
|
|
|
if (!skb->dev) { /* Control packets */
|
|
|
|
switch (dlci) {
|
|
|
|
case LMI_CCITT_ANSI_DLCI:
|
|
|
|
skb_push(skb, 4);
|
|
|
|
skb->data[3] = NLPID_CCITT_ANSI_LMI;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LMI_CISCO_DLCI:
|
|
|
|
skb_push(skb, 4);
|
|
|
|
skb->data[3] = NLPID_CISCO_LMI;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (skb->dev->type == ARPHRD_DLCI) {
|
|
|
|
switch (skb->protocol) {
|
|
|
|
case htons(ETH_P_IP):
|
|
|
|
skb_push(skb, 4);
|
|
|
|
skb->data[3] = NLPID_IP;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case htons(ETH_P_IPV6):
|
|
|
|
skb_push(skb, 4);
|
|
|
|
skb->data[3] = NLPID_IPV6;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
skb_push(skb, 10);
|
|
|
|
skb->data[3] = FR_PAD;
|
|
|
|
skb->data[4] = NLPID_SNAP;
|
|
|
|
/* OUI 00-00-00 indicates an Ethertype follows */
|
|
|
|
skb->data[5] = 0x00;
|
|
|
|
skb->data[6] = 0x00;
|
|
|
|
skb->data[7] = 0x00;
|
|
|
|
/* This should be an Ethertype: */
|
|
|
|
*(__be16 *)(skb->data + 8) = skb->protocol;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (skb->dev->type == ARPHRD_ETHER) {
|
|
|
|
skb_push(skb, 10);
|
2005-04-17 05:20:36 +07:00
|
|
|
skb->data[3] = FR_PAD;
|
|
|
|
skb->data[4] = NLPID_SNAP;
|
2020-09-28 19:56:43 +07:00
|
|
|
/* OUI 00-80-C2 stands for the 802.1 organization */
|
|
|
|
skb->data[5] = 0x00;
|
2005-04-17 05:20:36 +07:00
|
|
|
skb->data[6] = 0x80;
|
|
|
|
skb->data[7] = 0xC2;
|
2020-09-28 19:56:43 +07:00
|
|
|
/* PID 00-07 stands for Ethernet frames without FCS */
|
2005-04-17 05:20:36 +07:00
|
|
|
skb->data[8] = 0x00;
|
2020-09-28 19:56:43 +07:00
|
|
|
skb->data[9] = 0x07;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2020-09-28 19:56:43 +07:00
|
|
|
} else {
|
|
|
|
return -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
dlci_to_q922(skb->data, dlci);
|
|
|
|
skb->data[2] = FR_UI;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int pvc_open(struct net_device *dev)
|
|
|
|
{
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = dev->ml_priv;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if ((pvc->frad->flags & IFF_UP) == 0)
|
|
|
|
return -EIO; /* Frad must be UP in order to activate PVC */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (pvc->open_count++ == 0) {
|
2006-09-27 04:23:45 +07:00
|
|
|
hdlc_device *hdlc = dev_to_hdlc(pvc->frad);
|
|
|
|
if (state(hdlc)->settings.lmi == LMI_NONE)
|
|
|
|
pvc->state.active = netif_carrier_ok(pvc->frad);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
pvc_carrier(pvc->state.active, pvc);
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->dce_changed = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int pvc_close(struct net_device *dev)
|
|
|
|
{
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = dev->ml_priv;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (--pvc->open_count == 0) {
|
2006-09-27 04:23:45 +07:00
|
|
|
hdlc_device *hdlc = dev_to_hdlc(pvc->frad);
|
|
|
|
if (state(hdlc)->settings.lmi == LMI_NONE)
|
2005-04-17 05:20:36 +07:00
|
|
|
pvc->state.active = 0;
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->settings.dce) {
|
|
|
|
state(hdlc)->dce_changed = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
pvc->state.active = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2005-09-10 13:17:28 +07:00
|
|
|
static int pvc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = dev->ml_priv;
|
2005-04-17 05:20:36 +07:00
|
|
|
fr_proto_pvc_info info;
|
|
|
|
|
|
|
|
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;
|
2006-09-27 04:23:45 +07:00
|
|
|
memcpy(info.master, pvc->frad->name, IFNAMSIZ);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (copy_to_user(ifr->ifr_settings.ifs_ifsu.fr_pvc_info,
|
|
|
|
&info, sizeof(info)))
|
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2009-09-01 02:50:47 +07:00
|
|
|
static netdev_tx_t pvc_xmit(struct sk_buff *skb, struct net_device *dev)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = dev->ml_priv;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2020-10-04 05:41:05 +07:00
|
|
|
if (!pvc->state.active)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
if (dev->type == ARPHRD_ETHER) {
|
|
|
|
int pad = ETH_ZLEN - skb->len;
|
|
|
|
|
|
|
|
if (pad > 0) { /* Pad the frame with zeros */
|
|
|
|
if (__skb_pad(skb, pad, false))
|
|
|
|
goto drop;
|
|
|
|
skb_put(skb, pad);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-08 01:32:03 +07:00
|
|
|
/* We already requested the header space with dev->needed_headroom.
|
|
|
|
* So this is just a protection in case the upper layer didn't take
|
|
|
|
* dev->needed_headroom into consideration.
|
|
|
|
*/
|
|
|
|
if (skb_headroom(skb) < 10) {
|
|
|
|
struct sk_buff *skb2 = skb_realloc_headroom(skb, 10);
|
|
|
|
|
|
|
|
if (!skb2)
|
|
|
|
goto drop;
|
|
|
|
dev_kfree_skb(skb);
|
|
|
|
skb = skb2;
|
|
|
|
}
|
|
|
|
|
2020-10-04 05:41:05 +07:00
|
|
|
skb->dev = dev;
|
2020-10-08 01:32:03 +07:00
|
|
|
if (fr_hard_header(skb, pvc->dlci))
|
2020-10-04 05:41:05 +07:00
|
|
|
goto drop;
|
|
|
|
|
|
|
|
dev->stats.tx_bytes += skb->len;
|
|
|
|
dev->stats.tx_packets++;
|
|
|
|
if (pvc->state.fecn) /* TX Congestion counter */
|
|
|
|
dev->stats.tx_compressed++;
|
|
|
|
skb->dev = pvc->frad;
|
|
|
|
skb->protocol = htons(ETH_P_HDLC);
|
|
|
|
skb_reset_network_header(skb);
|
|
|
|
dev_queue_xmit(skb);
|
|
|
|
return NETDEV_TX_OK;
|
|
|
|
|
|
|
|
drop:
|
2008-07-01 04:26:53 +07:00
|
|
|
dev->stats.tx_dropped++;
|
2020-10-04 05:41:05 +07:00
|
|
|
kfree_skb(skb);
|
2009-06-23 13:03:08 +07:00
|
|
|
return NETDEV_TX_OK;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2014-08-09 23:47:16 +07:00
|
|
|
static inline void fr_log_dlci_active(struct pvc_device *pvc)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(pvc->frad, "DLCI %d [%s%s%s]%s %s\n",
|
|
|
|
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");
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static inline u8 fr_lmi_nextseq(u8 x)
|
|
|
|
{
|
|
|
|
x++;
|
|
|
|
return x ? x : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void fr_lmi_send(struct net_device *dev, int fullrep)
|
|
|
|
{
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
|
|
|
struct sk_buff *skb;
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = state(hdlc)->first_pvc;
|
2006-09-27 04:23:45 +07:00
|
|
|
int lmi = state(hdlc)->settings.lmi;
|
|
|
|
int dce = state(hdlc)->settings.dce;
|
2005-04-21 20:57:25 +07:00
|
|
|
int len = lmi == LMI_ANSI ? LMI_ANSI_LENGTH : LMI_CCITT_CISCO_LENGTH;
|
|
|
|
int stat_len = (lmi == LMI_CISCO) ? 6 : 3;
|
2005-04-17 05:20:36 +07:00
|
|
|
u8 *data;
|
|
|
|
int i = 0;
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (dce && fullrep) {
|
2006-09-27 04:23:45 +07:00
|
|
|
len += state(hdlc)->dce_pvc_count * (2 + stat_len);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (len > HDLC_MAX_MRU) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_warn(dev, "Too many PVCs while sending LMI full report\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
skb = dev_alloc_skb(len);
|
|
|
|
if (!skb) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_warn(dev, "Memory squeeze on fr_lmi_send()\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
memset(skb->data, 0, len);
|
|
|
|
skb_reserve(skb, 4);
|
2005-04-21 20:57:25 +07:00
|
|
|
if (lmi == LMI_CISCO) {
|
2020-10-08 01:32:03 +07:00
|
|
|
fr_hard_header(skb, LMI_CISCO_DLCI);
|
2005-04-21 20:57:25 +07:00
|
|
|
} else {
|
2020-10-08 01:32:03 +07:00
|
|
|
fr_hard_header(skb, LMI_CCITT_ANSI_DLCI);
|
2005-04-21 20:57:25 +07:00
|
|
|
}
|
2007-04-20 10:29:13 +07:00
|
|
|
data = skb_tail_pointer(skb);
|
2005-04-17 05:20:36 +07:00
|
|
|
data[i++] = LMI_CALLREF;
|
2005-04-21 20:57:25 +07:00
|
|
|
data[i++] = dce ? LMI_STATUS : LMI_STATUS_ENQUIRY;
|
|
|
|
if (lmi == LMI_ANSI)
|
2005-04-17 05:20:36 +07:00
|
|
|
data[i++] = LMI_ANSI_LOCKSHIFT;
|
2005-04-21 20:57:25 +07:00
|
|
|
data[i++] = lmi == LMI_CCITT ? LMI_CCITT_REPTYPE :
|
|
|
|
LMI_ANSI_CISCO_REPTYPE;
|
2005-04-17 05:20:36 +07:00
|
|
|
data[i++] = LMI_REPT_LEN;
|
|
|
|
data[i++] = fullrep ? LMI_FULLREP : LMI_INTEGRITY;
|
2005-04-21 20:57:25 +07:00
|
|
|
data[i++] = lmi == LMI_CCITT ? LMI_CCITT_ALIVE : LMI_ANSI_CISCO_ALIVE;
|
2005-04-17 05:20:36 +07:00
|
|
|
data[i++] = LMI_INTEG_LEN;
|
2006-09-27 04:23:45 +07:00
|
|
|
data[i++] = state(hdlc)->txseq =
|
|
|
|
fr_lmi_nextseq(state(hdlc)->txseq);
|
|
|
|
data[i++] = state(hdlc)->rxseq;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (dce && fullrep) {
|
2005-04-17 05:20:36 +07:00
|
|
|
while (pvc) {
|
2005-04-21 20:57:25 +07:00
|
|
|
data[i++] = lmi == LMI_CCITT ? LMI_CCITT_PVCSTAT :
|
|
|
|
LMI_ANSI_CISCO_PVCSTAT;
|
2005-04-17 05:20:36 +07:00
|
|
|
data[i++] = stat_len;
|
|
|
|
|
|
|
|
/* LMI start/restart */
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->reliable && !pvc->state.exist) {
|
2005-04-17 05:20:36 +07:00
|
|
|
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_carrier(1, pvc);
|
|
|
|
pvc->state.active = 1;
|
|
|
|
fr_log_dlci_active(pvc);
|
|
|
|
}
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (lmi == LMI_CISCO) {
|
|
|
|
data[i] = pvc->dlci >> 8;
|
|
|
|
data[i + 1] = pvc->dlci & 0xFF;
|
|
|
|
} else {
|
|
|
|
data[i] = (pvc->dlci >> 4) & 0x3F;
|
|
|
|
data[i + 1] = ((pvc->dlci << 3) & 0x78) | 0x80;
|
|
|
|
data[i + 2] = 0x80;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pvc->state.new)
|
|
|
|
data[i + 2] |= 0x08;
|
|
|
|
else if (pvc->state.active)
|
|
|
|
data[i + 2] |= 0x02;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
i += stat_len;
|
|
|
|
pvc = pvc->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
skb_put(skb, i);
|
|
|
|
skb->priority = TC_PRIO_CONTROL;
|
|
|
|
skb->dev = dev;
|
drivers/net/wan/hdlc: Set skb->protocol before transmitting
This patch sets skb->protocol before transmitting frames on the HDLC
device, so that a user listening on the HDLC device with an AF_PACKET
socket will see outgoing frames' sll_protocol field correctly set and
consistent with that of incoming frames.
1. Control frames in hdlc_cisco and hdlc_ppp
When these drivers send control frames, skb->protocol is not set.
This value should be set to htons(ETH_P_HDLC), because when receiving
control frames, their skb->protocol is set to htons(ETH_P_HDLC).
When receiving, hdlc_type_trans in hdlc.h is called, which then calls
cisco_type_trans or ppp_type_trans. The skb->protocol of control frames
is set to htons(ETH_P_HDLC) so that the control frames can be received
by hdlc_rcv in hdlc.c, which calls cisco_rx or ppp_rx to process the
control frames.
2. hdlc_fr
When this driver sends control frames, skb->protocol is set to internal
values used in this driver.
When this driver sends data frames (from upper stacked PVC devices),
skb->protocol is the same as that of the user data packet being sent on
the upper PVC device (for normal PVC devices), or is htons(ETH_P_802_3)
(for Ethernet-emulating PVC devices).
However, skb->protocol for both control frames and data frames should be
set to htons(ETH_P_HDLC), because when receiving, all frames received on
the HDLC device will have their skb->protocol set to htons(ETH_P_HDLC).
When receiving, hdlc_type_trans in hdlc.h is called, and because this
driver doesn't provide a type_trans function in struct hdlc_proto,
all frames will have their skb->protocol set to htons(ETH_P_HDLC).
The frames are then received by hdlc_rcv in hdlc.c, which calls fr_rx
to process the frames (control frames are consumed and data frames
are re-received on upper PVC devices).
Cc: Krzysztof Halasa <khc@pm.waw.pl>
Signed-off-by: Xie He <xie.he.0141@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-09-17 04:25:07 +07:00
|
|
|
skb->protocol = htons(ETH_P_HDLC);
|
2007-04-11 10:45:18 +07:00
|
|
|
skb_reset_network_header(skb);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
dev_queue_xmit(skb);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void fr_set_link_state(int reliable, struct net_device *dev)
|
|
|
|
{
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = state(hdlc)->first_pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->reliable = reliable;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (reliable) {
|
2006-07-13 03:46:12 +07:00
|
|
|
netif_dormant_off(dev);
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->n391cnt = 0; /* Request full status */
|
|
|
|
state(hdlc)->dce_changed = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->settings.lmi == LMI_NONE) {
|
2005-04-17 05:20:36 +07:00
|
|
|
while (pvc) { /* Activate all PVCs */
|
|
|
|
pvc_carrier(1, pvc);
|
|
|
|
pvc->state.exist = pvc->state.active = 1;
|
|
|
|
pvc->state.new = 0;
|
|
|
|
pvc = pvc->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2006-07-13 03:46:12 +07:00
|
|
|
netif_dormant_on(dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
while (pvc) { /* Deactivate all PVCs */
|
|
|
|
pvc_carrier(0, pvc);
|
|
|
|
pvc->state.exist = pvc->state.active = 0;
|
|
|
|
pvc->state.new = 0;
|
2006-09-27 04:23:45 +07:00
|
|
|
if (!state(hdlc)->settings.dce)
|
2005-04-21 20:57:25 +07:00
|
|
|
pvc->state.bandwidth = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
pvc = pvc->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-10-17 07:29:22 +07:00
|
|
|
static void fr_timer(struct timer_list *t)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2017-10-17 07:29:22 +07:00
|
|
|
struct frad_state *st = from_timer(st, t, timer);
|
|
|
|
struct net_device *dev = st->dev;
|
2005-04-17 05:20:36 +07:00
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
|
|
|
int i, cnt = 0, reliable;
|
|
|
|
u32 list;
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->settings.dce) {
|
|
|
|
reliable = state(hdlc)->request &&
|
|
|
|
time_before(jiffies, state(hdlc)->last_poll +
|
|
|
|
state(hdlc)->settings.t392 * HZ);
|
|
|
|
state(hdlc)->request = 0;
|
2005-04-21 20:57:25 +07:00
|
|
|
} else {
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->last_errors <<= 1; /* Shift the list */
|
|
|
|
if (state(hdlc)->request) {
|
|
|
|
if (state(hdlc)->reliable)
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "No LMI status reply received\n");
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->last_errors |= 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
list = state(hdlc)->last_errors;
|
|
|
|
for (i = 0; i < state(hdlc)->settings.n393; i++, list >>= 1)
|
2005-04-17 05:20:36 +07:00
|
|
|
cnt += (list & 1); /* errors count */
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
reliable = (cnt < state(hdlc)->settings.n392);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->reliable != reliable) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Link %sreliable\n", reliable ? "" : "un");
|
2005-04-17 05:20:36 +07:00
|
|
|
fr_set_link_state(reliable, dev);
|
|
|
|
}
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->settings.dce)
|
|
|
|
state(hdlc)->timer.expires = jiffies +
|
|
|
|
state(hdlc)->settings.t392 * HZ;
|
2005-04-17 05:20:36 +07:00
|
|
|
else {
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->n391cnt)
|
|
|
|
state(hdlc)->n391cnt--;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
fr_lmi_send(dev, state(hdlc)->n391cnt == 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->last_poll = jiffies;
|
|
|
|
state(hdlc)->request = 1;
|
|
|
|
state(hdlc)->timer.expires = jiffies +
|
|
|
|
state(hdlc)->settings.t391 * HZ;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
add_timer(&state(hdlc)->timer);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int fr_lmi_recv(struct net_device *dev, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
u8 rxseq, txseq;
|
2006-09-27 04:23:45 +07:00
|
|
|
int lmi = state(hdlc)->settings.lmi;
|
|
|
|
int dce = state(hdlc)->settings.dce;
|
2005-04-21 20:57:25 +07:00
|
|
|
int stat_len = (lmi == LMI_CISCO) ? 6 : 3, reptype, error, no_ram, i;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (skb->len < (lmi == LMI_ANSI ? LMI_ANSI_LENGTH :
|
|
|
|
LMI_CCITT_CISCO_LENGTH)) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Short LMI frame\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (skb->data[3] != (lmi == LMI_CISCO ? NLPID_CISCO_LMI :
|
|
|
|
NLPID_CCITT_ANSI_LMI)) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Received non-LMI frame with LMI DLCI\n");
|
2005-04-21 20:57:25 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (skb->data[4] != LMI_CALLREF) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Invalid LMI Call reference (0x%02X)\n",
|
|
|
|
skb->data[4]);
|
2005-04-21 20:57:25 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (skb->data[5] != (dce ? LMI_STATUS_ENQUIRY : LMI_STATUS)) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Invalid LMI Message type (0x%02X)\n",
|
|
|
|
skb->data[5]);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (lmi == LMI_ANSI) {
|
|
|
|
if (skb->data[6] != LMI_ANSI_LOCKSHIFT) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Not ANSI locking shift in LMI message (0x%02X)\n",
|
|
|
|
skb->data[6]);
|
2005-04-21 20:57:25 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
i = 7;
|
|
|
|
} else
|
|
|
|
i = 6;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (skb->data[i] != (lmi == LMI_CCITT ? LMI_CCITT_REPTYPE :
|
|
|
|
LMI_ANSI_CISCO_REPTYPE)) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Not an LMI Report type IE (0x%02X)\n",
|
|
|
|
skb->data[i]);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (skb->data[++i] != LMI_REPT_LEN) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Invalid LMI Report type IE length (%u)\n",
|
|
|
|
skb->data[i]);
|
2005-04-21 20:57:25 +07:00
|
|
|
return 1;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
reptype = skb->data[++i];
|
|
|
|
if (reptype != LMI_INTEGRITY && reptype != LMI_FULLREP) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Unsupported LMI Report type (0x%02X)\n",
|
|
|
|
reptype);
|
2005-04-21 20:57:25 +07:00
|
|
|
return 1;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (skb->data[++i] != (lmi == LMI_CCITT ? LMI_CCITT_ALIVE :
|
|
|
|
LMI_ANSI_CISCO_ALIVE)) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Not an LMI Link integrity verification IE (0x%02X)\n",
|
|
|
|
skb->data[i]);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (skb->data[++i] != LMI_INTEG_LEN) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Invalid LMI Link integrity verification IE length (%u)\n",
|
|
|
|
skb->data[i]);
|
2005-04-21 20:57:25 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
i++;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->rxseq = skb->data[i++]; /* TX sequence from peer */
|
2005-04-17 05:20:36 +07:00
|
|
|
rxseq = skb->data[i++]; /* Should confirm our sequence */
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
txseq = state(hdlc)->txseq;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (dce)
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->last_poll = jiffies;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
error = 0;
|
2006-09-27 04:23:45 +07:00
|
|
|
if (!state(hdlc)->reliable)
|
2005-04-17 05:20:36 +07:00
|
|
|
error = 1;
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if (rxseq == 0 || rxseq != txseq) { /* Ask for full report next time */
|
|
|
|
state(hdlc)->n391cnt = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
error = 1;
|
|
|
|
}
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (dce) {
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->fullrep_sent && !error) {
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Stop sending full report - the last one has been confirmed by DTE */
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->fullrep_sent = 0;
|
|
|
|
pvc = state(hdlc)->first_pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
while (pvc) {
|
|
|
|
if (pvc->state.new) {
|
|
|
|
pvc->state.new = 0;
|
|
|
|
|
|
|
|
/* Tell DTE that new PVC is now active */
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->dce_changed = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
pvc = pvc->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->dce_changed) {
|
2005-04-17 05:20:36 +07:00
|
|
|
reptype = LMI_FULLREP;
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->fullrep_sent = 1;
|
|
|
|
state(hdlc)->dce_changed = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->request = 1; /* got request */
|
2005-04-17 05:20:36 +07:00
|
|
|
fr_lmi_send(dev, reptype == LMI_FULLREP ? 1 : 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* DTE */
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->request = 0; /* got response, no request pending */
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (error)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (reptype != LMI_FULLREP)
|
|
|
|
return 0;
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
pvc = state(hdlc)->first_pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
while (pvc) {
|
|
|
|
pvc->state.deleted = 1;
|
|
|
|
pvc = pvc->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
no_ram = 0;
|
|
|
|
while (skb->len >= i + 2 + stat_len) {
|
|
|
|
u16 dlci;
|
2005-04-21 20:57:25 +07:00
|
|
|
u32 bw;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int active, new;
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (skb->data[i] != (lmi == LMI_CCITT ? LMI_CCITT_PVCSTAT :
|
|
|
|
LMI_ANSI_CISCO_PVCSTAT)) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Not an LMI PVC status IE (0x%02X)\n",
|
|
|
|
skb->data[i]);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if (skb->data[++i] != stat_len) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(dev, "Invalid LMI PVC status IE length (%u)\n",
|
|
|
|
skb->data[i]);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
new = !! (skb->data[i + 2] & 0x08);
|
|
|
|
active = !! (skb->data[i + 2] & 0x02);
|
|
|
|
if (lmi == LMI_CISCO) {
|
|
|
|
dlci = (skb->data[i] << 8) | skb->data[i + 1];
|
|
|
|
bw = (skb->data[i + 3] << 16) |
|
|
|
|
(skb->data[i + 4] << 8) |
|
|
|
|
(skb->data[i + 5]);
|
|
|
|
} else {
|
|
|
|
dlci = ((skb->data[i] & 0x3F) << 4) |
|
|
|
|
((skb->data[i + 1] & 0x78) >> 3);
|
|
|
|
bw = 0;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
pvc = add_pvc(dev, dlci);
|
|
|
|
|
|
|
|
if (!pvc && !no_ram) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_warn(dev, "Memory squeeze on fr_lmi_recv()\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
no_ram = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pvc) {
|
|
|
|
pvc->state.exist = 1;
|
|
|
|
pvc->state.deleted = 0;
|
|
|
|
if (active != pvc->state.active ||
|
|
|
|
new != pvc->state.new ||
|
2005-04-21 20:57:25 +07:00
|
|
|
bw != pvc->state.bandwidth ||
|
2005-04-17 05:20:36 +07:00
|
|
|
!pvc->state.exist) {
|
|
|
|
pvc->state.new = new;
|
|
|
|
pvc->state.active = active;
|
2005-04-21 20:57:25 +07:00
|
|
|
pvc->state.bandwidth = bw;
|
2005-04-17 05:20:36 +07:00
|
|
|
pvc_carrier(active, pvc);
|
|
|
|
fr_log_dlci_active(pvc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
i += stat_len;
|
|
|
|
}
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
pvc = state(hdlc)->first_pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
while (pvc) {
|
|
|
|
if (pvc->state.deleted && pvc->state.exist) {
|
|
|
|
pvc_carrier(0, pvc);
|
|
|
|
pvc->state.active = pvc->state.new = 0;
|
|
|
|
pvc->state.exist = 0;
|
2005-04-21 20:57:25 +07:00
|
|
|
pvc->state.bandwidth = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
fr_log_dlci_active(pvc);
|
|
|
|
}
|
|
|
|
pvc = pvc->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Next full report after N391 polls */
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->n391cnt = state(hdlc)->settings.n391;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int fr_rx(struct sk_buff *skb)
|
|
|
|
{
|
2006-09-27 04:23:45 +07:00
|
|
|
struct net_device *frad = skb->dev;
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(frad);
|
2014-08-09 23:47:16 +07:00
|
|
|
struct fr_hdr *fh = (struct fr_hdr *)skb->data;
|
2005-04-17 05:20:36 +07:00
|
|
|
u8 *data = skb->data;
|
|
|
|
u16 dlci;
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct net_device *dev = NULL;
|
|
|
|
|
|
|
|
if (skb->len <= 4 || fh->ea1 || data[2] != FR_UI)
|
|
|
|
goto rx_error;
|
|
|
|
|
|
|
|
dlci = q922_to_dlci(skb->data);
|
|
|
|
|
2005-04-21 20:57:25 +07:00
|
|
|
if ((dlci == LMI_CCITT_ANSI_DLCI &&
|
2006-09-27 04:23:45 +07:00
|
|
|
(state(hdlc)->settings.lmi == LMI_ANSI ||
|
|
|
|
state(hdlc)->settings.lmi == LMI_CCITT)) ||
|
2005-04-21 20:57:25 +07:00
|
|
|
(dlci == LMI_CISCO_DLCI &&
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->settings.lmi == LMI_CISCO)) {
|
|
|
|
if (fr_lmi_recv(frad, skb))
|
2005-04-21 20:57:25 +07:00
|
|
|
goto rx_error;
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return NET_RX_SUCCESS;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
pvc = find_pvc(hdlc, dlci);
|
|
|
|
if (!pvc) {
|
|
|
|
#ifdef DEBUG_PKT
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(frad, "No PVC for received frame's DLCI %d\n",
|
|
|
|
dlci);
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return NET_RX_DROP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pvc->state.fecn != fh->fecn) {
|
|
|
|
#ifdef DEBUG_ECN
|
2006-09-27 04:23:45 +07:00
|
|
|
printk(KERN_DEBUG "%s: DLCI %d FECN O%s\n", frad->name,
|
2005-04-17 05:20:36 +07:00
|
|
|
dlci, fh->fecn ? "N" : "FF");
|
|
|
|
#endif
|
|
|
|
pvc->state.fecn ^= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pvc->state.becn != fh->becn) {
|
|
|
|
#ifdef DEBUG_ECN
|
2006-09-27 04:23:45 +07:00
|
|
|
printk(KERN_DEBUG "%s: DLCI %d BECN O%s\n", frad->name,
|
2005-04-17 05:20:36 +07:00
|
|
|
dlci, fh->becn ? "N" : "FF");
|
|
|
|
#endif
|
|
|
|
pvc->state.becn ^= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
|
2008-07-01 04:26:53 +07:00
|
|
|
frad->stats.rx_dropped++;
|
2005-04-17 05:20:36 +07:00
|
|
|
return NET_RX_DROP;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
} 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);
|
|
|
|
|
|
|
|
} else if (skb->len > 10 && data[3] == FR_PAD &&
|
|
|
|
data[4] == NLPID_SNAP && data[5] == FR_PAD) {
|
2007-04-27 18:13:33 +07:00
|
|
|
u16 oui = ntohs(*(__be16*)(data + 6));
|
|
|
|
u16 pid = ntohs(*(__be16*)(data + 8));
|
2005-04-17 05:20:36 +07:00
|
|
|
skb_pull(skb, 10);
|
|
|
|
|
|
|
|
switch ((((u32)oui) << 16) | pid) {
|
|
|
|
case ETH_P_ARP: /* routed frame with SNAP */
|
|
|
|
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:
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(frad, "Unsupported protocol, OUI=%x PID=%x\n",
|
|
|
|
oui, pid);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return NET_RX_DROP;
|
|
|
|
}
|
|
|
|
} else {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_info(frad, "Unsupported protocol, NLPID=%x length=%i\n",
|
|
|
|
data[3], skb->len);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return NET_RX_DROP;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dev) {
|
2008-07-01 04:26:53 +07:00
|
|
|
dev->stats.rx_packets++; /* PVC traffic */
|
|
|
|
dev->stats.rx_bytes += skb->len;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (pvc->state.becn)
|
2008-07-01 04:26:53 +07:00
|
|
|
dev->stats.rx_compressed++;
|
2008-06-30 02:48:11 +07:00
|
|
|
skb->dev = dev;
|
2005-04-17 05:20:36 +07:00
|
|
|
netif_rx(skb);
|
|
|
|
return NET_RX_SUCCESS;
|
|
|
|
} else {
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return NET_RX_DROP;
|
|
|
|
}
|
|
|
|
|
|
|
|
rx_error:
|
2008-07-01 04:26:53 +07:00
|
|
|
frad->stats.rx_errors++; /* Mark error */
|
2005-04-17 05:20:36 +07:00
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return NET_RX_DROP;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void fr_start(struct net_device *dev)
|
|
|
|
{
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
|
|
|
#ifdef DEBUG_LINK
|
|
|
|
printk(KERN_DEBUG "fr_start\n");
|
|
|
|
#endif
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->settings.lmi != LMI_NONE) {
|
|
|
|
state(hdlc)->reliable = 0;
|
|
|
|
state(hdlc)->dce_changed = 1;
|
|
|
|
state(hdlc)->request = 0;
|
|
|
|
state(hdlc)->fullrep_sent = 0;
|
|
|
|
state(hdlc)->last_errors = 0xFFFFFFFF;
|
|
|
|
state(hdlc)->n391cnt = 0;
|
|
|
|
state(hdlc)->txseq = state(hdlc)->rxseq = 0;
|
|
|
|
|
2017-10-17 07:29:22 +07:00
|
|
|
state(hdlc)->dev = dev;
|
|
|
|
timer_setup(&state(hdlc)->timer, fr_timer, 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
/* First poll after 1 s */
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->timer.expires = jiffies + HZ;
|
|
|
|
add_timer(&state(hdlc)->timer);
|
2005-04-17 05:20:36 +07:00
|
|
|
} else
|
|
|
|
fr_set_link_state(1, dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void fr_stop(struct net_device *dev)
|
|
|
|
{
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
|
|
|
#ifdef DEBUG_LINK
|
|
|
|
printk(KERN_DEBUG "fr_stop\n");
|
|
|
|
#endif
|
2006-09-27 04:23:45 +07:00
|
|
|
if (state(hdlc)->settings.lmi != LMI_NONE)
|
|
|
|
del_timer_sync(&state(hdlc)->timer);
|
2005-04-17 05:20:36 +07:00
|
|
|
fr_set_link_state(0, dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void fr_close(struct net_device *dev)
|
|
|
|
{
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = state(hdlc)->first_pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
while (pvc) { /* Shutdown all PVCs for this FRAD */
|
|
|
|
if (pvc->main)
|
|
|
|
dev_close(pvc->main);
|
|
|
|
if (pvc->ether)
|
|
|
|
dev_close(pvc->ether);
|
|
|
|
pvc = pvc->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
|
|
|
|
static void pvc_setup(struct net_device *dev)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
dev->type = ARPHRD_DLCI;
|
|
|
|
dev->flags = IFF_POINTOPOINT;
|
2020-09-03 07:06:58 +07:00
|
|
|
dev->hard_header_len = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
dev->addr_len = 2;
|
2014-10-06 08:38:35 +07:00
|
|
|
netif_keep_dst(dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2009-01-09 04:52:11 +07:00
|
|
|
static const struct net_device_ops pvc_ops = {
|
|
|
|
.ndo_open = pvc_open,
|
|
|
|
.ndo_stop = pvc_close,
|
|
|
|
.ndo_start_xmit = pvc_xmit,
|
|
|
|
.ndo_do_ioctl = pvc_ioctl,
|
|
|
|
};
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
static int fr_add_pvc(struct net_device *frad, unsigned int dlci, int type)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-09-27 04:23:45 +07:00
|
|
|
hdlc_device *hdlc = dev_to_hdlc(frad);
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct net_device *dev;
|
2011-04-30 08:21:32 +07:00
|
|
|
int used;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if ((pvc = add_pvc(frad, dlci)) == NULL) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_warn(frad, "Memory squeeze on fr_add_pvc()\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOBUFS;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*get_dev_p(pvc, type))
|
|
|
|
return -EEXIST;
|
|
|
|
|
|
|
|
used = pvc_is_used(pvc);
|
|
|
|
|
2015-11-18 05:26:06 +07:00
|
|
|
if (type == ARPHRD_ETHER)
|
net: set name_assign_type in alloc_netdev()
Extend alloc_netdev{,_mq{,s}}() to take name_assign_type as argument, and convert
all users to pass NET_NAME_UNKNOWN.
Coccinelle patch:
@@
expression sizeof_priv, name, setup, txqs, rxqs, count;
@@
(
-alloc_netdev_mqs(sizeof_priv, name, setup, txqs, rxqs)
+alloc_netdev_mqs(sizeof_priv, name, NET_NAME_UNKNOWN, setup, txqs, rxqs)
|
-alloc_netdev_mq(sizeof_priv, name, setup, count)
+alloc_netdev_mq(sizeof_priv, name, NET_NAME_UNKNOWN, setup, count)
|
-alloc_netdev(sizeof_priv, name, setup)
+alloc_netdev(sizeof_priv, name, NET_NAME_UNKNOWN, setup)
)
v9: move comments here from the wrong commit
Signed-off-by: Tom Gundersen <teg@jklm.no>
Reviewed-by: David Herrmann <dh.herrmann@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2014-07-14 21:37:24 +07:00
|
|
|
dev = alloc_netdev(0, "pvceth%d", NET_NAME_UNKNOWN,
|
|
|
|
ether_setup);
|
2015-11-18 05:26:06 +07:00
|
|
|
else
|
net: set name_assign_type in alloc_netdev()
Extend alloc_netdev{,_mq{,s}}() to take name_assign_type as argument, and convert
all users to pass NET_NAME_UNKNOWN.
Coccinelle patch:
@@
expression sizeof_priv, name, setup, txqs, rxqs, count;
@@
(
-alloc_netdev_mqs(sizeof_priv, name, setup, txqs, rxqs)
+alloc_netdev_mqs(sizeof_priv, name, NET_NAME_UNKNOWN, setup, txqs, rxqs)
|
-alloc_netdev_mq(sizeof_priv, name, setup, count)
+alloc_netdev_mq(sizeof_priv, name, NET_NAME_UNKNOWN, setup, count)
|
-alloc_netdev(sizeof_priv, name, setup)
+alloc_netdev(sizeof_priv, name, NET_NAME_UNKNOWN, setup)
)
v9: move comments here from the wrong commit
Signed-off-by: Tom Gundersen <teg@jklm.no>
Reviewed-by: David Herrmann <dh.herrmann@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2014-07-14 21:37:24 +07:00
|
|
|
dev = alloc_netdev(0, "pvc%d", NET_NAME_UNKNOWN, pvc_setup);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (!dev) {
|
2011-06-27 02:01:28 +07:00
|
|
|
netdev_warn(frad, "Memory squeeze on fr_pvc()\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
delete_unused_pvcs(hdlc);
|
|
|
|
return -ENOBUFS;
|
|
|
|
}
|
|
|
|
|
2015-11-18 05:26:06 +07:00
|
|
|
if (type == ARPHRD_ETHER) {
|
|
|
|
dev->priv_flags &= ~IFF_TX_SKB_SHARING;
|
2012-02-15 13:45:39 +07:00
|
|
|
eth_hw_addr_random(dev);
|
2015-11-18 05:26:06 +07:00
|
|
|
} else {
|
2007-04-27 18:13:33 +07:00
|
|
|
*(__be16*)dev->dev_addr = htons(dlci);
|
2005-04-17 05:20:36 +07:00
|
|
|
dlci_to_q922(dev->broadcast, dlci);
|
|
|
|
}
|
2009-01-09 04:52:11 +07:00
|
|
|
dev->netdev_ops = &pvc_ops;
|
2005-04-17 05:20:36 +07:00
|
|
|
dev->mtu = HDLC_MAX_MTU;
|
2016-10-21 00:55:19 +07:00
|
|
|
dev->min_mtu = 68;
|
|
|
|
dev->max_mtu = HDLC_MAX_MTU;
|
2020-09-03 07:06:58 +07:00
|
|
|
dev->needed_headroom = 10;
|
2015-08-18 15:30:46 +07:00
|
|
|
dev->priv_flags |= IFF_NO_QUEUE;
|
2008-11-22 07:34:18 +07:00
|
|
|
dev->ml_priv = pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (register_netdevice(dev) != 0) {
|
|
|
|
free_netdev(dev);
|
|
|
|
delete_unused_pvcs(hdlc);
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
net: Fix inconsistent teardown and release of private netdev state.
Network devices can allocate reasources and private memory using
netdev_ops->ndo_init(). However, the release of these resources
can occur in one of two different places.
Either netdev_ops->ndo_uninit() or netdev->destructor().
The decision of which operation frees the resources depends upon
whether it is necessary for all netdev refs to be released before it
is safe to perform the freeing.
netdev_ops->ndo_uninit() presumably can occur right after the
NETDEV_UNREGISTER notifier completes and the unicast and multicast
address lists are flushed.
netdev->destructor(), on the other hand, does not run until the
netdev references all go away.
Further complicating the situation is that netdev->destructor()
almost universally does also a free_netdev().
This creates a problem for the logic in register_netdevice().
Because all callers of register_netdevice() manage the freeing
of the netdev, and invoke free_netdev(dev) if register_netdevice()
fails.
If netdev_ops->ndo_init() succeeds, but something else fails inside
of register_netdevice(), it does call ndo_ops->ndo_uninit(). But
it is not able to invoke netdev->destructor().
This is because netdev->destructor() will do a free_netdev() and
then the caller of register_netdevice() will do the same.
However, this means that the resources that would normally be released
by netdev->destructor() will not be.
Over the years drivers have added local hacks to deal with this, by
invoking their destructor parts by hand when register_netdevice()
fails.
Many drivers do not try to deal with this, and instead we have leaks.
Let's close this hole by formalizing the distinction between what
private things need to be freed up by netdev->destructor() and whether
the driver needs unregister_netdevice() to perform the free_netdev().
netdev->priv_destructor() performs all actions to free up the private
resources that used to be freed by netdev->destructor(), except for
free_netdev().
netdev->needs_free_netdev is a boolean that indicates whether
free_netdev() should be done at the end of unregister_netdevice().
Now, register_netdevice() can sanely release all resources after
ndo_ops->ndo_init() succeeds, by invoking both ndo_ops->ndo_uninit()
and netdev->priv_destructor().
And at the end of unregister_netdevice(), we invoke
netdev->priv_destructor() and optionally call free_netdev().
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-05-08 23:52:56 +07:00
|
|
|
dev->needs_free_netdev = true;
|
2005-04-17 05:20:36 +07:00
|
|
|
*get_dev_p(pvc, type) = dev;
|
|
|
|
if (!used) {
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->dce_changed = 1;
|
|
|
|
state(hdlc)->dce_pvc_count++;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int fr_del_pvc(hdlc_device *hdlc, unsigned int dlci, int type)
|
|
|
|
{
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
if ((pvc = find_pvc(hdlc, dlci)) == NULL)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
if ((dev = *get_dev_p(pvc, type)) == NULL)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
if (dev->flags & IFF_UP)
|
|
|
|
return -EBUSY; /* PVC in use */
|
|
|
|
|
|
|
|
unregister_netdevice(dev); /* the destructor will free_netdev(dev) */
|
|
|
|
*get_dev_p(pvc, type) = NULL;
|
|
|
|
|
|
|
|
if (!pvc_is_used(pvc)) {
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->dce_pvc_count--;
|
|
|
|
state(hdlc)->dce_changed = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
delete_unused_pvcs(hdlc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
static void fr_destroy(struct net_device *frad)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2006-09-27 04:23:45 +07:00
|
|
|
hdlc_device *hdlc = dev_to_hdlc(frad);
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *pvc = state(hdlc)->first_pvc;
|
2006-09-27 04:23:45 +07:00
|
|
|
state(hdlc)->first_pvc = NULL; /* All PVCs destroyed */
|
|
|
|
state(hdlc)->dce_pvc_count = 0;
|
|
|
|
state(hdlc)->dce_changed = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
while (pvc) {
|
2014-08-09 23:47:16 +07:00
|
|
|
struct pvc_device *next = pvc->next;
|
2005-04-17 05:20:36 +07:00
|
|
|
/* destructors will free_netdev() main and ether */
|
|
|
|
if (pvc->main)
|
|
|
|
unregister_netdevice(pvc->main);
|
|
|
|
|
|
|
|
if (pvc->ether)
|
|
|
|
unregister_netdevice(pvc->ether);
|
|
|
|
|
|
|
|
kfree(pvc);
|
|
|
|
pvc = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
static struct hdlc_proto proto = {
|
|
|
|
.close = fr_close,
|
|
|
|
.start = fr_start,
|
|
|
|
.stop = fr_stop,
|
|
|
|
.detach = fr_destroy,
|
|
|
|
.ioctl = fr_ioctl,
|
2008-02-02 04:37:12 +07:00
|
|
|
.netif_rx = fr_rx,
|
2006-09-27 04:23:45 +07:00
|
|
|
.module = THIS_MODULE,
|
|
|
|
};
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
static int fr_ioctl(struct net_device *dev, struct ifreq *ifr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
fr_proto __user *fr_s = ifr->ifr_settings.ifs_ifsu.fr;
|
|
|
|
const size_t size = sizeof(fr_proto);
|
|
|
|
fr_proto new_settings;
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
|
|
|
fr_proto_pvc pvc;
|
|
|
|
int result;
|
|
|
|
|
|
|
|
switch (ifr->ifr_settings.type) {
|
|
|
|
case IF_GET_PROTO:
|
2006-09-27 04:23:45 +07:00
|
|
|
if (dev_to_hdlc(dev)->proto != &proto) /* Different proto */
|
|
|
|
return -EINVAL;
|
2005-04-17 05:20:36 +07:00
|
|
|
ifr->ifr_settings.type = IF_PROTO_FR;
|
|
|
|
if (ifr->ifr_settings.size < size) {
|
|
|
|
ifr->ifr_settings.size = size; /* data size wanted */
|
|
|
|
return -ENOBUFS;
|
|
|
|
}
|
2006-09-27 04:23:45 +07:00
|
|
|
if (copy_to_user(fr_s, &state(hdlc)->settings, size))
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case IF_PROTO_FR:
|
2009-11-30 14:42:42 +07:00
|
|
|
if (!capable(CAP_NET_ADMIN))
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EPERM;
|
|
|
|
|
2009-11-30 14:42:42 +07:00
|
|
|
if (dev->flags & IFF_UP)
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
if (copy_from_user(&new_settings, fr_s, size))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
if (new_settings.lmi == LMI_DEFAULT)
|
|
|
|
new_settings.lmi = LMI_ANSI;
|
|
|
|
|
|
|
|
if ((new_settings.lmi != LMI_NONE &&
|
|
|
|
new_settings.lmi != LMI_ANSI &&
|
2005-04-21 20:57:25 +07:00
|
|
|
new_settings.lmi != LMI_CCITT &&
|
|
|
|
new_settings.lmi != LMI_CISCO) ||
|
2005-04-17 05:20:36 +07:00
|
|
|
new_settings.t391 < 1 ||
|
|
|
|
new_settings.t392 < 2 ||
|
|
|
|
new_settings.n391 < 1 ||
|
|
|
|
new_settings.n392 < 1 ||
|
|
|
|
new_settings.n393 < new_settings.n392 ||
|
|
|
|
new_settings.n393 > 32 ||
|
|
|
|
(new_settings.dce != 0 &&
|
|
|
|
new_settings.dce != 1))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
result=hdlc->attach(dev, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
2006-09-27 04:23:45 +07:00
|
|
|
if (dev_to_hdlc(dev)->proto != &proto) { /* Different proto */
|
2008-02-02 04:37:12 +07:00
|
|
|
result = attach_hdlc_protocol(dev, &proto,
|
2006-09-27 04:23:45 +07:00
|
|
|
sizeof(struct frad_state));
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
state(hdlc)->first_pvc = NULL;
|
|
|
|
state(hdlc)->dce_pvc_count = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2006-09-27 04:23:45 +07:00
|
|
|
memcpy(&state(hdlc)->settings, &new_settings, size);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev->type = ARPHRD_FRAD;
|
2015-12-04 03:12:31 +07:00
|
|
|
call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
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:
|
2006-09-27 04:23:45 +07:00
|
|
|
if (dev_to_hdlc(dev)->proto != &proto) /* Different proto */
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-11-30 14:42:42 +07:00
|
|
|
if (!capable(CAP_NET_ADMIN))
|
2005-04-17 05:20:36 +07:00
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
if (copy_from_user(&pvc, ifr->ifr_settings.ifs_ifsu.fr_pvc,
|
|
|
|
sizeof(fr_proto_pvc)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
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(dev, pvc.dlci, result);
|
|
|
|
else
|
|
|
|
return fr_del_pvc(hdlc, pvc.dlci, result);
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2006-09-27 04:23:45 +07:00
|
|
|
|
|
|
|
|
|
|
|
static int __init mod_init(void)
|
|
|
|
{
|
|
|
|
register_hdlc_protocol(&proto);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void __exit mod_exit(void)
|
|
|
|
{
|
|
|
|
unregister_hdlc_protocol(&proto);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module_init(mod_init);
|
|
|
|
module_exit(mod_exit);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
|
|
|
|
MODULE_DESCRIPTION("Frame-Relay protocol support for generic HDLC");
|
|
|
|
MODULE_LICENSE("GPL v2");
|