/*
* Generic HDLC support routines for Linux
* Frame Relay support
*
* Copyright (C) 1999 - 2006 Krzysztof Halasa <khc@pm.waw.pl>
*
* 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
* as published by the Free Software Foundation.
*
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
CCITT LMI: ITU-T Q.933 Annex A
ANSI LMI: ANSI T1.617 Annex D
CISCO LMI: the original, aka "Gang of Four" LMI
*/
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/hdlc.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pkt_sched.h>
#include <linux/poll.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#undef DEBUG_PKT
#undef DEBUG_ECN
#undef DEBUG_LINK
#undef DEBUG_PROTO
#undef DEBUG_PVC
#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 */
#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 */
#define LMI_CCITT_CISCO_LENGTH 13 /* LMI frame lengths */
#define LMI_ANSI_LENGTH 14
typedef struct {
#if defined(__LITTLE_ENDIAN_BITFIELD)
unsigned ea1: 1;
unsigned cr: 1;
unsigned dlcih: 6;
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
}__packed fr_hdr;
typedef struct pvc_device_struct {
struct net_device *frad;
struct net_device *main;
struct net_device *ether; /* bridged Ethernet interface */
struct pvc_device_struct *next; /* Sorted in ascending DLCI order */
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;
}pvc_device;
struct frad_state {
fr_proto settings;
pvc_device *first_pvc;
int dce_pvc_count;
struct timer_list timer;
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);
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;
}
static inline struct frad_state* state(hdlc_device *hdlc)
{
return(struct frad_state *)(hdlc->state);
}
static inline pvc_device* find_pvc(hdlc_device *hdlc, u16 dlci)
{
pvc_device *pvc = state(hdlc)->first_pvc;
while (pvc) {
if (pvc->dlci == dlci)
return pvc;
if (pvc->dlci > dlci)
return NULL; /* the list is sorted */
pvc = pvc->next;
}
return NULL;
}
static pvc_device* add_pvc(struct net_device *dev, u16 dlci)
{
hdlc_device *hdlc = dev_to_hdlc(dev);
pvc_device *pvc, **pvc_p = &state(hdlc)->first_pvc;
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;
}
pvc = kzalloc(sizeof(pvc_device), GFP_ATOMIC);
#ifdef DEBUG_PVC
printk(KERN_DEBUG "add_pvc: allocated pvc %p, frad %p\n", pvc, dev);
#endif
if (!pvc)
return NULL;
pvc->dlci = dlci;
pvc->frad = dev;
pvc->next = *pvc_p; /* Put it in the chain */
*pvc_p = pvc;
return pvc;
}
static inline int pvc_is_used(pvc_device *pvc)
{
return pvc->main || pvc->ether;
}
static inline void pvc_carrier(int on, pvc_device *pvc)
{
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)
{
pvc_device **pvc_p = &state(hdlc)->first_pvc;
while (*pvc_p) {
if (!pvc_is_used(*pvc_p)) {
pvc_device *pvc = *pvc_p;
#ifdef DEBUG_PVC
printk(KERN_DEBUG "freeing unused pvc: %p\n", pvc);
#endif
*pvc_p = pvc->next;
kfree(pvc);
continue;
}
pvc_p = &(*pvc_p)->next;
}
}
static inline struct net_device** get_dev_p(pvc_device *pvc, int type)
{
if (type == ARPHRD_ETHER)
return &pvc->ether;
else
return &pvc->main;
}
static int fr_hard_header(struct sk_buff **skb_p, u16 dlci)
{
u16 head_len;
struct sk_buff *skb = *skb_p;
switch (skb->protocol) {
case cpu_to_be16(NLPID_CCITT_ANSI_LMI):
head_len = 4;
skb_push(skb, head_len);
skb->data[3] = NLPID_CCITT_ANSI_LMI;
break;
case cpu_to_be16(NLPID_CISCO_LMI):
head_len = 4;
skb_push(skb, head_len);
skb->data[3] = NLPID_CISCO_LMI;
break;
case cpu_to_be16(ETH_P_IP):
head_len = 4;
skb_push(skb, head_len);
skb->data[3] = NLPID_IP;
break;
case cpu_to_be16(ETH_P_IPV6):
head_len = 4;
skb_push(skb, head_len);
skb->data[3] = NLPID_IPV6;
break;
case cpu_to_be16(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);
skb->data[3] = FR_PAD;
skb->data[4] = NLPID_SNAP;
skb->data[5] = FR_PAD;
skb->data[6] = FR_PAD;
skb->data[7] = FR_PAD;
*(__be16*)(skb->data + 8) = skb->protocol;
}
dlci_to_q922(skb->data, dlci);
skb->data[2] = FR_UI;
return 0;
}
static int pvc_open(struct net_device *dev)
{
pvc_device *pvc = dev->ml_priv;
if ((pvc->frad->flags & IFF_UP) == 0)
return -EIO; /* Frad must be UP in order to activate PVC */
if (pvc->open_count++ == 0) {
hdlc_device *hdlc = dev_to_hdlc(pvc->frad);
if (state(hdlc)->settings.lmi == LMI_NONE)
pvc->state.active = netif_carrier_ok(pvc->frad);
pvc_carrier(pvc->state.active, pvc);
state(hdlc)->dce_changed = 1;
}
return 0;
}
static int pvc_close(struct net_device *dev)
{
pvc_device *pvc = dev->ml_priv;
if (--pvc->open_count ==