diff options
Diffstat (limited to 'kernel/drivers/isdn/pcbit/capi.c')
-rw-r--r-- | kernel/drivers/isdn/pcbit/capi.c | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/kernel/drivers/isdn/pcbit/capi.c b/kernel/drivers/isdn/pcbit/capi.c new file mode 100644 index 000000000..4e3cbf857 --- /dev/null +++ b/kernel/drivers/isdn/pcbit/capi.c @@ -0,0 +1,649 @@ +/* + * CAPI encoder/decoder for + * Portugal Telecom CAPI 2.0 + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + * + * Not compatible with the AVM Gmbh. CAPI 2.0 + * + */ + +/* + * Documentation: + * - "Common ISDN API - Perfil Português - Versão 2.1", + * Telecom Portugal, Fev 1992. + * - "Common ISDN API - Especificação de protocolos para + * acesso aos canais B", Inesc, Jan 1994. + */ + +/* + * TODO: better decoding of Information Elements + * for debug purposes mainly + * encode our number in CallerPN and ConnectedPN + */ + +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/mm.h> + +#include <linux/skbuff.h> + +#include <asm/io.h> +#include <asm/string.h> + +#include <linux/isdnif.h> + +#include "pcbit.h" +#include "edss1.h" +#include "capi.h" + + +/* + * Encoding of CAPI messages + * + */ + +int capi_conn_req(const char *calledPN, struct sk_buff **skb, int proto) +{ + ushort len; + + /* + * length + * AppInfoMask - 2 + * BC0 - 3 + * BC1 - 1 + * Chan - 2 + * Keypad - 1 + * CPN - 1 + * CPSA - 1 + * CalledPN - 2 + strlen + * CalledPSA - 1 + * rest... - 4 + * ---------------- + * Total 18 + strlen + */ + + len = 18 + strlen(calledPN); + + if (proto == ISDN_PROTO_L2_TRANS) + len++; + + if ((*skb = dev_alloc_skb(len)) == NULL) { + + printk(KERN_WARNING "capi_conn_req: alloc_skb failed\n"); + return -1; + } + + /* InfoElmMask */ + *((ushort *)skb_put(*skb, 2)) = AppInfoMask; + + if (proto == ISDN_PROTO_L2_TRANS) + { + /* Bearer Capability - Mandatory*/ + *(skb_put(*skb, 1)) = 3; /* BC0.Length */ + *(skb_put(*skb, 1)) = 0x80; /* Speech */ + *(skb_put(*skb, 1)) = 0x10; /* Circuit Mode */ + *(skb_put(*skb, 1)) = 0x23; /* A-law */ + } + else + { + /* Bearer Capability - Mandatory*/ + *(skb_put(*skb, 1)) = 2; /* BC0.Length */ + *(skb_put(*skb, 1)) = 0x88; /* Digital Information */ + *(skb_put(*skb, 1)) = 0x90; /* BC0.Octect4 */ + } + + /* Bearer Capability - Optional*/ + *(skb_put(*skb, 1)) = 0; /* BC1.Length = 0 */ + + *(skb_put(*skb, 1)) = 1; /* ChannelID.Length = 1 */ + *(skb_put(*skb, 1)) = 0x83; /* Basic Interface - Any Channel */ + + *(skb_put(*skb, 1)) = 0; /* Keypad.Length = 0 */ + + + *(skb_put(*skb, 1)) = 0; /* CallingPN.Length = 0 */ + *(skb_put(*skb, 1)) = 0; /* CallingPSA.Length = 0 */ + + /* Called Party Number */ + *(skb_put(*skb, 1)) = strlen(calledPN) + 1; + *(skb_put(*skb, 1)) = 0x81; + memcpy(skb_put(*skb, strlen(calledPN)), calledPN, strlen(calledPN)); + + /* '#' */ + + *(skb_put(*skb, 1)) = 0; /* CalledPSA.Length = 0 */ + + /* LLC.Length = 0; */ + /* HLC0.Length = 0; */ + /* HLC1.Length = 0; */ + /* UTUS.Length = 0; */ + memset(skb_put(*skb, 4), 0, 4); + + return len; +} + +int capi_conn_resp(struct pcbit_chan *chan, struct sk_buff **skb) +{ + + if ((*skb = dev_alloc_skb(5)) == NULL) { + + printk(KERN_WARNING "capi_conn_resp: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + *(skb_put(*skb, 1)) = 0x01; /* ACCEPT_CALL */ + *(skb_put(*skb, 1)) = 0; + *(skb_put(*skb, 1)) = 0; + + return 5; +} + +int capi_conn_active_req(struct pcbit_chan *chan, struct sk_buff **skb) +{ + /* + * 8 bytes + */ + + if ((*skb = dev_alloc_skb(8)) == NULL) { + + printk(KERN_WARNING "capi_conn_active_req: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + +#ifdef DEBUG + printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref); +#endif + + *(skb_put(*skb, 1)) = 0; /* BC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* ConnectedPN.Length = 0 */ + *(skb_put(*skb, 1)) = 0; /* PSA.Length */ + *(skb_put(*skb, 1)) = 0; /* LLC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* HLC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* UTUS.Length = 0; */ + + return 8; +} + +int capi_conn_active_resp(struct pcbit_chan *chan, struct sk_buff **skb) +{ + /* + * 2 bytes + */ + + if ((*skb = dev_alloc_skb(2)) == NULL) { + + printk(KERN_WARNING "capi_conn_active_resp: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + return 2; +} + + +int capi_select_proto_req(struct pcbit_chan *chan, struct sk_buff **skb, + int outgoing) +{ + + /* + * 18 bytes + */ + + if ((*skb = dev_alloc_skb(18)) == NULL) { + + printk(KERN_WARNING "capi_select_proto_req: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + /* Layer2 protocol */ + + switch (chan->proto) { + case ISDN_PROTO_L2_X75I: + *(skb_put(*skb, 1)) = 0x05; /* LAPB */ + break; + case ISDN_PROTO_L2_HDLC: + *(skb_put(*skb, 1)) = 0x02; + break; + case ISDN_PROTO_L2_TRANS: + /* + * Voice (a-law) + */ + *(skb_put(*skb, 1)) = 0x06; + break; + default: +#ifdef DEBUG + printk(KERN_DEBUG "Transparent\n"); +#endif + *(skb_put(*skb, 1)) = 0x03; + break; + } + + *(skb_put(*skb, 1)) = (outgoing ? 0x02 : 0x42); /* Don't ask */ + *(skb_put(*skb, 1)) = 0x00; + + *((ushort *) skb_put(*skb, 2)) = MRU; + + + *(skb_put(*skb, 1)) = 0x08; /* Modulo */ + *(skb_put(*skb, 1)) = 0x07; /* Max Window */ + + *(skb_put(*skb, 1)) = 0x01; /* No Layer3 Protocol */ + + /* + * 2 - layer3 MTU [10] + * - Modulo [12] + * - Window + * - layer1 proto [14] + * - bitrate + * - sub-channel [16] + * - layer1dataformat [17] + */ + + memset(skb_put(*skb, 8), 0, 8); + + return 18; +} + + +int capi_activate_transp_req(struct pcbit_chan *chan, struct sk_buff **skb) +{ + + if ((*skb = dev_alloc_skb(7)) == NULL) { + + printk(KERN_WARNING "capi_activate_transp_req: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + + *(skb_put(*skb, 1)) = chan->layer2link; /* Layer2 id */ + *(skb_put(*skb, 1)) = 0x00; /* Transmit by default */ + + *((ushort *) skb_put(*skb, 2)) = MRU; + + *(skb_put(*skb, 1)) = 0x01; /* Enables reception*/ + + return 7; +} + +int capi_tdata_req(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort data_len; + + + /* + * callref - 2 + * layer2link - 1 + * wBlockLength - 2 + * data - 4 + * sernum - 1 + */ + + data_len = skb->len; + + if (skb_headroom(skb) < 10) + { + printk(KERN_CRIT "No headspace (%u) on headroom %p for capi header\n", skb_headroom(skb), skb); + } + else + { + skb_push(skb, 10); + } + + *((u16 *) (skb->data)) = chan->callref; + skb->data[2] = chan->layer2link; + *((u16 *) (skb->data + 3)) = data_len; + + chan->s_refnum = (chan->s_refnum + 1) % 8; + *((u32 *) (skb->data + 5)) = chan->s_refnum; + + skb->data[9] = 0; /* HDLC frame number */ + + return 10; +} + +int capi_tdata_resp(struct pcbit_chan *chan, struct sk_buff **skb) + +{ + if ((*skb = dev_alloc_skb(4)) == NULL) { + + printk(KERN_WARNING "capi_tdata_resp: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + *(skb_put(*skb, 1)) = chan->layer2link; + *(skb_put(*skb, 1)) = chan->r_refnum; + + return (*skb)->len; +} + +int capi_disc_req(ushort callref, struct sk_buff **skb, u_char cause) +{ + + if ((*skb = dev_alloc_skb(6)) == NULL) { + + printk(KERN_WARNING "capi_disc_req: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = callref; + + *(skb_put(*skb, 1)) = 2; /* Cause.Length = 2; */ + *(skb_put(*skb, 1)) = 0x80; + *(skb_put(*skb, 1)) = 0x80 | cause; + + /* + * Change it: we should send 'Sic transit gloria Mundi' here ;-) + */ + + *(skb_put(*skb, 1)) = 0; /* UTUS.Length = 0; */ + + return 6; +} + +int capi_disc_resp(struct pcbit_chan *chan, struct sk_buff **skb) +{ + if ((*skb = dev_alloc_skb(2)) == NULL) { + + printk(KERN_WARNING "capi_disc_resp: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + return 2; +} + + +/* + * Decoding of CAPI messages + * + */ + +int capi_decode_conn_ind(struct pcbit_chan *chan, + struct sk_buff *skb, + struct callb_data *info) +{ + int CIlen, len; + + /* Call Reference [CAPI] */ + chan->callref = *((ushort *)skb->data); + skb_pull(skb, 2); + +#ifdef DEBUG + printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref); +#endif + + /* Channel Identification */ + + /* Expect + Len = 1 + Octect 3 = 0100 10CC - [ 7 Basic, 4 , 2-1 chan ] + */ + + CIlen = skb->data[0]; +#ifdef DEBUG + if (CIlen == 1) { + + if (((skb->data[1]) & 0xFC) == 0x48) + printk(KERN_DEBUG "decode_conn_ind: chan ok\n"); + printk(KERN_DEBUG "phyChan = %d\n", skb->data[1] & 0x03); + } + else + printk(KERN_DEBUG "conn_ind: CIlen = %d\n", CIlen); +#endif + skb_pull(skb, CIlen + 1); + + /* Calling Party Number */ + /* An "additional service" as far as Portugal Telecom is concerned */ + + len = skb->data[0]; + + if (len > 0) { + int count = 1; + +#ifdef DEBUG + printk(KERN_DEBUG "CPN: Octect 3 %02x\n", skb->data[1]); +#endif + if ((skb->data[1] & 0x80) == 0) + count = 2; + + if (!(info->data.setup.CallingPN = kmalloc(len - count + 1, GFP_ATOMIC))) + return -1; + + skb_copy_from_linear_data_offset(skb, count + 1, + info->data.setup.CallingPN, + len - count); + info->data.setup.CallingPN[len - count] = 0; + + } + else { + info->data.setup.CallingPN = NULL; + printk(KERN_DEBUG "NULL CallingPN\n"); + } + + skb_pull(skb, len + 1); + + /* Calling Party Subaddress */ + skb_pull(skb, skb->data[0] + 1); + + /* Called Party Number */ + + len = skb->data[0]; + + if (len > 0) { + int count = 1; + + if ((skb->data[1] & 0x80) == 0) + count = 2; + + if (!(info->data.setup.CalledPN = kmalloc(len - count + 1, GFP_ATOMIC))) + return -1; + + skb_copy_from_linear_data_offset(skb, count + 1, + info->data.setup.CalledPN, + len - count); + info->data.setup.CalledPN[len - count] = 0; + + } + else { + info->data.setup.CalledPN = NULL; + printk(KERN_DEBUG "NULL CalledPN\n"); + } + + skb_pull(skb, len + 1); + + /* Called Party Subaddress */ + skb_pull(skb, skb->data[0] + 1); + + /* LLC */ + skb_pull(skb, skb->data[0] + 1); + + /* HLC */ + skb_pull(skb, skb->data[0] + 1); + + /* U2U */ + skb_pull(skb, skb->data[0] + 1); + + return 0; +} + +/* + * returns errcode + */ + +int capi_decode_conn_conf(struct pcbit_chan *chan, struct sk_buff *skb, + int *complete) +{ + int errcode; + + chan->callref = *((ushort *)skb->data); /* Update CallReference */ + skb_pull(skb, 2); + + errcode = *((ushort *) skb->data); /* read errcode */ + skb_pull(skb, 2); + + *complete = *(skb->data); + skb_pull(skb, 1); + + /* FIX ME */ + /* This is actually a firmware bug */ + if (!*complete) + { + printk(KERN_DEBUG "complete=%02x\n", *complete); + *complete = 1; + } + + + /* Optional Bearer Capability */ + skb_pull(skb, *(skb->data) + 1); + + /* Channel Identification */ + skb_pull(skb, *(skb->data) + 1); + + /* High Layer Compatibility follows */ + skb_pull(skb, *(skb->data) + 1); + + return errcode; +} + +int capi_decode_conn_actv_ind(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort len; +#ifdef DEBUG + char str[32]; +#endif + + /* Yet Another Bearer Capability */ + skb_pull(skb, *(skb->data) + 1); + + + /* Connected Party Number */ + len = *(skb->data); + +#ifdef DEBUG + if (len > 1 && len < 31) { + skb_copy_from_linear_data_offset(skb, 2, str, len - 1); + str[len] = 0; + printk(KERN_DEBUG "Connected Party Number: %s\n", str); + } + else + printk(KERN_DEBUG "actv_ind CPN len = %d\n", len); +#endif + + skb_pull(skb, len + 1); + + /* Connected Subaddress */ + skb_pull(skb, *(skb->data) + 1); + + /* Low Layer Capability */ + skb_pull(skb, *(skb->data) + 1); + + /* High Layer Capability */ + skb_pull(skb, *(skb->data) + 1); + + return 0; +} + +int capi_decode_conn_actv_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + errcode = *((ushort *)skb->data); + skb_pull(skb, 2); + + /* Channel Identification + skb_pull(skb, skb->data[0] + 1); + */ + return errcode; +} + + +int capi_decode_sel_proto_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + chan->layer2link = *(skb->data); + skb_pull(skb, 1); + + errcode = *((ushort *)skb->data); + skb_pull(skb, 2); + + return errcode; +} + +int capi_decode_actv_trans_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + if (chan->layer2link != *(skb->data)) + printk("capi_decode_actv_trans_conf: layer2link doesn't match\n"); + + skb_pull(skb, 1); + + errcode = *((ushort *)skb->data); + skb_pull(skb, 2); + + return errcode; +} + +int capi_decode_disc_ind(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort len; +#ifdef DEBUG + int i; +#endif + /* Cause */ + + len = *(skb->data); + skb_pull(skb, 1); + +#ifdef DEBUG + + for (i = 0; i < len; i++) + printk(KERN_DEBUG "Cause Octect %d: %02x\n", i + 3, + *(skb->data + i)); +#endif + + skb_pull(skb, len); + + return 0; +} + +#ifdef DEBUG +int capi_decode_debug_188(u_char *hdr, ushort hdrlen) +{ + char str[64]; + int len; + + len = hdr[0]; + + if (len < 64 && len == hdrlen - 1) { + memcpy(str, hdr + 1, hdrlen - 1); + str[hdrlen - 1] = 0; + printk("%s\n", str); + } + else + printk("debug message incorrect\n"); + + return 0; +} +#endif |