| /* $Id: tpam_nco.c,v 1.1.2.1 2001/11/20 14:19:37 kai Exp $ |
| * |
| * Turbo PAM ISDN driver for Linux. |
| * (Kernel Driver - Low Level NCO Manipulation) |
| * |
| * Copyright 2001 Stelian Pop <stelian.pop@fr.alcove.com>, AlcĂ´ve |
| * |
| * This software may be used and distributed according to the terms |
| * of the GNU General Public License, incorporated herein by reference. |
| * |
| * For all support questions please contact: <support@auvertech.fr> |
| * |
| */ |
| |
| #include <linux/pci.h> |
| #include <linux/sched.h> |
| #include <linux/tqueue.h> |
| #include <linux/interrupt.h> |
| #include <asm/io.h> |
| |
| #include "tpam.h" |
| |
| /* Local function prototypes */ |
| static struct sk_buff *build_NCOpacket(u16, u16, u16, u16, u16); |
| static int extract_NCOParameter(struct sk_buff *, u8, void *, u16); |
| |
| /* |
| * Build a NCO packet (PCI message). |
| * |
| * messageID: the message type (ID_*) |
| * size: size of the TLV block |
| * data_size: size of the data block |
| * ack: packet needs to send ack upon send |
| * ack_size: size of data to be acknowledged upon send |
| * |
| * Return: the sk_buff filled with the NCO packet, or NULL if error. |
| */ |
| static struct sk_buff *build_NCOpacket(u16 messageID, u16 size, |
| u16 data_size, u16 ack, |
| u16 ack_size) { |
| struct sk_buff *skb; |
| skb_header *h; |
| pci_mpb *p; |
| u16 finalsize; |
| |
| /* reserve enough space for the sk_buff header, the pci * header, |
| * size bytes for the TLV block, size bytes for the data and 4 more |
| * bytes in order to make sure we can write dwords to the board. */ |
| finalsize = sizeof(skb_header) + sizeof(pci_mpb) + size + data_size + 4; |
| |
| /* allocate the sk_buff */ |
| if (!(skb = alloc_skb(finalsize, GFP_ATOMIC))) { |
| printk(KERN_ERR "TurboPAM(make_NCOpacket): alloc_skb failed\n"); |
| return NULL; |
| } |
| |
| /* construct the skb_header */ |
| h = (skb_header *)skb_put(skb, sizeof(skb_header)); |
| h->size = sizeof(pci_mpb) + size; |
| h->data_size = data_size; |
| h->ack = ack; |
| h->ack_size = ack_size; |
| |
| /* construct the pci_mpb */ |
| p = (pci_mpb *)skb_put(skb, sizeof(pci_mpb)); |
| p->exID = 0; |
| p->flags = 0; |
| p->errorCode = 0; |
| p->messageID = messageID; |
| p->maximumBlockTLVSize = MPB_MAXIMUMBLOCKTLVSIZE; |
| p->actualBlockTLVSize = size; |
| p->maximumDataSize = MPB_MAXIMUMDATASIZE; |
| p->actualDataSize = data_size; |
| return skb; |
| } |
| |
| /* |
| * Build a ACreateNCOReq message. |
| * |
| * phone: the local phone number. |
| * |
| * Return: the sk_buff filled with the NCO packet, or NULL if error. |
| */ |
| struct sk_buff *build_ACreateNCOReq(const u8 *phone) { |
| struct sk_buff *skb; |
| u8 *tlv; |
| |
| dprintk("TurboPAM(build_ACreateNCOReq): phone=%s\n", phone); |
| |
| /* build the NCO packet */ |
| if (!(skb = build_NCOpacket(ID_ACreateNCOReq, 23 + strlen(phone), 0, 0, 0))) |
| return NULL; |
| |
| /* add the parameters */ |
| tlv = (u8 *)skb_put(skb, 3); |
| *tlv = PAR_NCOType; |
| *(tlv+1) = 1; |
| *(tlv+2) = 5; /* mistery value... */ |
| |
| tlv = (u8 *)skb_put(skb, 4); |
| *tlv = PAR_U3Protocol; |
| *(tlv+1) = 2; |
| *(tlv+2) = 4; /* no level 3 protocol */ |
| *(tlv+3) = 1; /* HDLC in level 2 */ |
| |
| tlv = (u8 *)skb_put(skb, 3); |
| *tlv = PAR_Cdirection; |
| *(tlv+1) = 1; |
| *(tlv+2) = 3; /* PCI_DIRECTION_BOTH */ |
| |
| tlv = (u8 *)skb_put(skb, 3); |
| *tlv = PAR_Udirection; |
| *(tlv+1) = 1; |
| *(tlv+2) = 3; /* PCI_DIRECTION_BOTH */ |
| |
| tlv = (u8 *)skb_put(skb, 4); |
| *tlv = PAR_BearerCap; |
| *(tlv+1) = 2; |
| *(tlv+2) = 0x88; |
| *(tlv+3) = 0x90; |
| |
| tlv = (u8 *)skb_put(skb, 6 + strlen(phone)); |
| *tlv = PAR_CallingNumber; |
| *(tlv+1) = strlen(phone) + 4; |
| *(tlv+2) = 0x01; /* international */ |
| *(tlv+3) = 0x01; /* isdn */ |
| *(tlv+4) = 0x00; |
| *(tlv+5) = 0x00; |
| memcpy(tlv + 6, phone, strlen(phone)); |
| |
| return skb; |
| } |
| |
| /* |
| * Build a ADestroyNCOReq message. |
| * |
| * ncoid: the NCO id. |
| * |
| * Return: the sk_buff filled with the NCO packet, or NULL if error. |
| */ |
| struct sk_buff *build_ADestroyNCOReq(u32 ncoid) { |
| struct sk_buff *skb; |
| u8 *tlv; |
| |
| dprintk("TurboPAM(build_ADestroyNCOReq): ncoid=%lu\n", |
| (unsigned long)ncoid); |
| |
| /* build the NCO packet */ |
| if (!(skb = build_NCOpacket(ID_ADestroyNCOReq, 6, 0, 0, 0))) |
| return NULL; |
| |
| /* add the parameters */ |
| tlv = (u8 *)skb_put(skb, 6); |
| *tlv = PAR_NCOID; |
| *(tlv+1) = 4; |
| *((u32 *)(tlv+2)) = ncoid; |
| |
| return skb; |
| } |
| |
| /* |
| * Build a CConnectReq message. |
| * |
| * ncoid: the NCO id. |
| * called: the destination phone number |
| * hdlc: type of connection: 1 (HDLC) or 0(modem) |
| * |
| * Return: the sk_buff filled with the NCO packet, or NULL if error. |
| */ |
| struct sk_buff *build_CConnectReq(u32 ncoid, const u8 *called, u8 hdlc) { |
| struct sk_buff *skb; |
| u8 *tlv; |
| |
| dprintk("TurboPAM(build_CConnectReq): ncoid=%lu, called=%s, hdlc=%d\n", |
| (unsigned long)ncoid, called, hdlc); |
| |
| /* build the NCO packet */ |
| if (!(skb = build_NCOpacket(ID_CConnectReq, 20 + strlen(called), 0, 0, 0))) |
| return NULL; |
| |
| /* add the parameters */ |
| tlv = (u8 *)skb_put(skb, 6); |
| *tlv = PAR_NCOID; |
| *(tlv+1) = 4; |
| *((u32 *)(tlv+2)) = ncoid; |
| |
| tlv = (u8 *)skb_put(skb, 4 + strlen(called)); |
| *tlv = PAR_CalledNumber; |
| *(tlv+1) = strlen(called) + 2; |
| *(tlv+2) = 0x01; /* international */ |
| *(tlv+3) = 0x01; /* isdn */ |
| memcpy(tlv + 4, called, strlen(called)); |
| |
| tlv = (u8 *)skb_put(skb, 3); |
| *tlv = PAR_BearerCap; |
| *(tlv+1) = 1; |
| *(tlv+2) = hdlc ? 0x88 /* HDLC */ : 0x80 /* MODEM */; |
| |
| tlv = (u8 *)skb_put(skb, 4); |
| *tlv = PAR_HLC; |
| *(tlv+1) = 2; |
| *(tlv+2) = 0x2; |
| *(tlv+3) = 0x7f; |
| |
| tlv = (u8 *)skb_put(skb, 3); |
| *tlv = PAR_Facility; |
| *(tlv+1) = 1; |
| *(tlv+2) = 2; |
| |
| return skb; |
| } |
| |
| /* |
| * Build a CConnectRsp message. |
| * |
| * ncoid: the NCO id. |
| * |
| * Return: the sk_buff filled with the NCO packet, or NULL if error. |
| */ |
| struct sk_buff *build_CConnectRsp(u32 ncoid) { |
| struct sk_buff *skb; |
| u8 *tlv; |
| |
| dprintk("TurboPAM(build_CConnectRsp): ncoid=%lu\n", |
| (unsigned long)ncoid); |
| |
| /* build the NCO packet */ |
| if (!(skb = build_NCOpacket(ID_CConnectRsp, 6, 0, 0, 0))) |
| return NULL; |
| |
| /* add the parameters */ |
| tlv = (u8 *)skb_put(skb, 6); |
| *tlv = PAR_NCOID; |
| *(tlv+1) = 4; |
| *((u32 *)(tlv+2)) = ncoid; |
| |
| return skb; |
| } |
| |
| /* |
| * Build a CDisconnectReq message. |
| * |
| * ncoid: the NCO id. |
| * |
| * Return: the sk_buff filled with the NCO packet, or NULL if error. |
| */ |
| struct sk_buff *build_CDisconnectReq(u32 ncoid) { |
| struct sk_buff *skb; |
| u8 *tlv; |
| |
| dprintk("TurboPAM(build_CDisconnectReq): ncoid=%lu\n", |
| (unsigned long)ncoid); |
| |
| /* build the NCO packet */ |
| if (!(skb = build_NCOpacket(ID_CDisconnectReq, 6, 0, 0, 0))) |
| return NULL; |
| |
| /* add the parameters */ |
| tlv = (u8 *)skb_put(skb, 6); |
| *tlv = PAR_NCOID; |
| *(tlv+1) = 4; |
| *((u32 *)(tlv+2)) = ncoid; |
| |
| return skb; |
| } |
| |
| /* |
| * Build a CDisconnectRsp message. |
| * |
| * ncoid: the NCO id. |
| * |
| * Return: the sk_buff filled with the NCO packet, or NULL if error. |
| */ |
| struct sk_buff *build_CDisconnectRsp(u32 ncoid) { |
| struct sk_buff *skb; |
| u8 *tlv; |
| |
| dprintk("TurboPAM(build_CDisconnectRsp): ncoid=%lu\n", |
| (unsigned long)ncoid); |
| |
| /* build the NCO packet */ |
| if (!(skb = build_NCOpacket(ID_CDisconnectRsp, 6, 0, 0, 0))) |
| return NULL; |
| |
| /* add the parameters */ |
| tlv = (u8 *)skb_put(skb, 6); |
| *tlv = PAR_NCOID; |
| *(tlv+1) = 4; |
| *((u32 *)(tlv+2)) = ncoid; |
| |
| return skb; |
| } |
| |
| /* |
| * Build a U3DataReq message. |
| * |
| * ncoid: the NCO id. |
| * data: the data to be send |
| * len: length of the data |
| * ack: send ack upon send |
| * ack_size: size of data to be acknowledged upon send |
| * |
| * Return: the sk_buff filled with the NCO packet, or NULL if error. |
| */ |
| struct sk_buff *build_U3DataReq(u32 ncoid, void *data, u16 len, |
| u16 ack, u16 ack_size) { |
| struct sk_buff *skb; |
| u8 *tlv; |
| void *p; |
| |
| dprintk("TurboPAM(build_U3DataReq): " |
| "ncoid=%lu, len=%d, ack=%d, ack_size=%d\n", |
| (unsigned long)ncoid, len, ack, ack_size); |
| |
| /* build the NCO packet */ |
| if (!(skb = build_NCOpacket(ID_U3DataReq, 6, len, ack, ack_size))) |
| return NULL; |
| |
| /* add the parameters */ |
| tlv = (u8 *)skb_put(skb, 6); |
| *tlv = PAR_NCOID; |
| *(tlv+1) = 4; |
| *((u32 *)(tlv+2)) = ncoid; |
| |
| p = skb_put(skb, len); |
| memcpy(p, data, len); |
| |
| return skb; |
| } |
| |
| /* |
| * Extract a parameter from a TLV block. |
| * |
| * skb: sk_buff containing the PCI message |
| * type: parameter to search for (PARAM_*) |
| * value: to be filled with the value of the parameter |
| * len: maximum length of the parameter value |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| static int extract_NCOParameter(struct sk_buff *skb, u8 type, |
| void *value, u16 len) { |
| void *buffer = (void *)skb->data; |
| pci_mpb *p; |
| void * bufferend; |
| u8 valtype; |
| u16 vallen; |
| |
| /* calculate the start and end of the TLV block */ |
| buffer += sizeof(skb_header); |
| p = (pci_mpb *)buffer; |
| buffer += sizeof(pci_mpb); |
| bufferend = buffer + p->actualBlockTLVSize; |
| |
| /* walk through the parameters */ |
| while (buffer < bufferend) { |
| |
| /* parameter type */ |
| valtype = *((u8 *)buffer++); |
| /* parameter length */ |
| vallen = *((u8 *)buffer++); |
| if (vallen == 0xff) { |
| /* parameter length is on 2 bytes */ |
| vallen = *((u8 *)buffer++); |
| vallen <<= 8; |
| vallen |= *((u8 *)buffer++); |
| } |
| /* got the right parameter */ |
| if (valtype == type) { |
| /* not enough space for returning the value */ |
| if (vallen > len) |
| return -1; |
| /* OK, return it */ |
| memcpy(value, buffer, vallen); |
| return 0; |
| } |
| buffer += vallen; |
| } |
| return -1; |
| } |
| |
| /* |
| * Parse a ACreateNCOCnf message. |
| * |
| * skb: the sk_buff containing the message |
| * status: to be filled with the status field value |
| * ncoid: to be filled with the ncoid field value |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| int parse_ACreateNCOCnf(struct sk_buff *skb, u8 *status, u32 *ncoid) { |
| |
| /* extract the status */ |
| if (extract_NCOParameter(skb, PAR_CompletionStatus, status, 1)) { |
| printk(KERN_ERR "TurboPAM(parse_ACreateNCOCnf): " |
| "CompletionStatus not found\n"); |
| return -1; |
| } |
| |
| if (*status) { |
| dprintk("TurboPAM(parse_ACreateNCOCnf): status=%d\n", *status); |
| return 0; |
| } |
| |
| /* extract the ncoid */ |
| if (extract_NCOParameter(skb, PAR_NCOID, ncoid, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_ACreateNCOCnf): " |
| "NCOID not found\n"); |
| return -1; |
| } |
| |
| dprintk("TurboPAM(parse_ACreateNCOCnf): ncoid=%lu, status=%d\n", |
| (unsigned long)*ncoid, *status); |
| return 0; |
| } |
| |
| /* |
| * Parse a ADestroyNCOCnf message. Not used in the driver. |
| * |
| * skb: the sk_buff containing the message |
| * status: to be filled with the status field value |
| * ncoid: to be filled with the ncoid field value |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| int parse_ADestroyNCOCnf(struct sk_buff *skb, u8 *status, u32 *ncoid) { |
| |
| /* extract the status */ |
| if (extract_NCOParameter(skb, PAR_CompletionStatus, status, 1)) { |
| printk(KERN_ERR "TurboPAM(parse_ADestroyNCOCnf): " |
| "CompletionStatus not found\n"); |
| return -1; |
| } |
| |
| if (*status) { |
| dprintk("TurboPAM(parse_ADestroyNCOCnf): status=%d\n", *status); |
| return 0; |
| } |
| |
| /* extract the ncoid */ |
| if (extract_NCOParameter(skb, PAR_NCOID, ncoid, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_ADestroyNCOCnf): " |
| "NCOID not found\n"); |
| return -1; |
| } |
| |
| dprintk("TurboPAM(parse_ADestroyNCOCnf): ncoid=%lu, status=%d\n", |
| (unsigned long)*ncoid, *status); |
| return 0; |
| } |
| |
| /* |
| * Parse a CConnectCnf message. |
| * |
| * skb: the sk_buff containing the message |
| * ncoid: to be filled with the ncoid field value |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| int parse_CConnectCnf(struct sk_buff *skb, u32 *ncoid) { |
| |
| /* extract the ncoid */ |
| if (extract_NCOParameter(skb, PAR_NCOID, ncoid, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_CConnectCnf): " |
| "NCOID not found\n"); |
| return -1; |
| } |
| dprintk("TurboPAM(parse_CConnectCnf): ncoid=%lu\n", |
| (unsigned long)*ncoid); |
| return 0; |
| } |
| |
| /* |
| * Parse a CConnectInd message. |
| * |
| * skb: the sk_buff containing the message |
| * ncoid: to be filled with the ncoid field value |
| * hdlc: to be filled with 1 if the incoming connection is a HDLC one, |
| * with 0 if the incoming connection is a modem one |
| * calling: to be filled with the calling phone number value |
| * called: to be filled with the called phone number value |
| * plan: to be filled with the plan value |
| * screen: to be filled with the screen value |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| int parse_CConnectInd(struct sk_buff *skb, u32 *ncoid, u8 *hdlc, |
| u8 *calling, u8 *called, u8 *plan, u8 *screen) { |
| u8 phone[PHONE_MAXIMUMSIZE + 4]; |
| |
| /* extract the ncoid */ |
| if (extract_NCOParameter(skb, PAR_NCOID, ncoid, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_CConnectInd): " |
| "NCOID not found\n"); |
| return -1; |
| } |
| |
| /* extract the bearer capability field */ |
| if (extract_NCOParameter(skb, PAR_BearerCap, hdlc, 1)) { |
| printk(KERN_ERR "TurboPAM(parse_CConnectInd): " |
| "BearerCap not found\n"); |
| return -1; |
| } |
| *hdlc = (*hdlc == 0x88) ? 1 : 0; |
| |
| /* extract the calling number / plan / screen */ |
| if (extract_NCOParameter(skb, PAR_CallingNumber, phone, |
| PHONE_MAXIMUMSIZE + 4)) { |
| printk(KERN_ERR "TurboPAM(parse_CConnectInd): " |
| "CallingNumber not found\n"); |
| return -1; |
| } |
| memcpy(calling, phone + 4, PHONE_MAXIMUMSIZE); |
| *plan = phone[1]; |
| *screen = phone[3]; |
| |
| /* extract the called number */ |
| if (extract_NCOParameter(skb, PAR_CalledNumber, phone, |
| PHONE_MAXIMUMSIZE + 2)) { |
| printk(KERN_ERR "TurboPAM(parse_CConnectInd): " |
| "CalledNumber not found\n"); |
| return -1; |
| } |
| memcpy(called, phone + 2, PHONE_MAXIMUMSIZE); |
| |
| dprintk("TurboPAM(parse_CConnectInd): " |
| "ncoid=%lu, hdlc=%d, plan=%d, scr=%d, calling=%s, called=%s\n", |
| (unsigned long)*ncoid, *hdlc, *plan, *screen, calling, called); |
| return 0; |
| } |
| |
| /* |
| * Parse a CDisconnectCnf message. |
| * |
| * skb: the sk_buff containing the message |
| * ncoid: to be filled with the ncoid field value |
| * causetopuf: to be filled with the cause field value |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| int parse_CDisconnectCnf(struct sk_buff *skb, u32 *ncoid, u32 *causetopuf) { |
| |
| /* extract the ncoid */ |
| if (extract_NCOParameter(skb, PAR_NCOID, ncoid, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_CDisconnectCnf): " |
| "NCOID not found\n"); |
| return -1; |
| } |
| |
| /* extract the cause of disconnection */ |
| if (extract_NCOParameter(skb, PAR_CauseToPUF, causetopuf, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_CDisconnectCnf): " |
| "CauseToPUF not found\n"); |
| return -1; |
| } |
| |
| dprintk("TurboPAM(parse_CDisconnectCnf): ncoid=%lu, causetopuf=%lu\n", |
| (unsigned long)*ncoid, (unsigned long)*causetopuf); |
| return 0; |
| } |
| |
| /* |
| * Parse a CDisconnectInd message. |
| * |
| * skb: the sk_buff containing the message |
| * ncoid: to be filled with the ncoid field value |
| * causetopuf: to be filled with the cause field value |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| int parse_CDisconnectInd(struct sk_buff *skb, u32 *ncoid, u32 *causetopuf) { |
| |
| /* extract the ncoid */ |
| if (extract_NCOParameter(skb, PAR_NCOID, ncoid, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_CDisconnectInd): " |
| "NCOID not found\n"); |
| return -1; |
| } |
| |
| /* extract the cause of disconnection */ |
| if (extract_NCOParameter(skb, PAR_CauseToPUF, causetopuf, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_CDisconnectInd): " |
| "CauseToPUF not found\n"); |
| return -1; |
| } |
| |
| dprintk("TurboPAM(parse_CDisconnectInd): ncoid=%lu, causetopuf=%lu\n", |
| (unsigned long)*ncoid, (unsigned long)*causetopuf); |
| return 0; |
| } |
| |
| /* |
| * Parse a U3ReadyToReceiveInd message. |
| * |
| * skb: the sk_buff containing the message |
| * ncoid: to be filled with the ncoid field value |
| * ready: to be filled with the ready field value |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| int parse_U3ReadyToReceiveInd(struct sk_buff *skb, u32 *ncoid, u8 *ready) { |
| |
| /* extract the ncoid */ |
| if (extract_NCOParameter(skb, PAR_NCOID, ncoid, 4)) { |
| printk(KERN_ERR "TurboPAM(parse_U3ReadyToReceiveInd): " |
| "NCOID not found\n"); |
| return -1; |
| } |
| |
| /* extract the ready flag */ |
| if (extract_NCOParameter(skb, PAR_ReadyFlag, ready, 1)) { |
| printk(KERN_ERR "TurboPAM(parse_U3ReadyToReceiveInd): " |
| "ReadyFlag not found\n"); |
| return -1; |
| } |
| |
| dprintk("TurboPAM(parse_U3ReadyToReceiveInd): ncoid=%lu, ready=%d\n", |
| (unsigned long)*ncoid, *ready); |
| return 0; |
| } |
| |
| /* |
| * Parse a U3DataInd message. |
| * |
| * skb: the sk_buff containing the message + data |
| * ncoid: to be filled with the ncoid field value |
| * data: to be filled with the data |
| * ready: to be filled with the data length |
| * |
| * Return: 0 if OK, <0 if error. |
| */ |
| int parse_U3DataInd(struct sk_buff *skb, u32 *ncoid, u8 **data, u16 *len) { |
| pci_mpb *p; |
| |
| /* extract the ncoid */ |
| if (extract_NCOParameter(skb, PAR_NCOID, ncoid, 4) == -1) { |
| printk(KERN_ERR "TurboPAM(parse_U3DataInd): NCOID not found\n"); |
| return -1; |
| } |
| |
| /* get a pointer to the beginning of the data block and its length */ |
| p = (pci_mpb *)(skb->data + sizeof(skb_header)); |
| *len = p->actualDataSize; |
| skb_pull(skb, |
| sizeof(skb_header) + sizeof(pci_mpb) + p->actualBlockTLVSize); |
| *data = skb->data; |
| |
| dprintk("TurboPAM(parse_U3DataInd): ncoid=%lu, datalen=%d\n", |
| (unsigned long)*ncoid, *len); |
| return 0; |
| } |
| |