/* $Id: tpam_commands.c,v 1.1.1.1 2003/06/23 22:18:27 jharrell Exp $ * * Turbo PAM ISDN driver for Linux. (Kernel Driver - ISDN commands) * * Copyright 2001 Stelian Pop , 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: * */ #include #include #include #include #include #include #include #include "tpam.h" /* Local functions prototypes */ static int tpam_command_ioctl_dspload(tpam_card *, u32); static int tpam_command_ioctl_dspsave(tpam_card *, u32); static int tpam_command_ioctl_dsprun(tpam_card *); static int tpam_command_ioctl_loopmode(tpam_card *, u8); static int tpam_command_dial(tpam_card *, u32, u8 *); static int tpam_command_setl2(tpam_card *, u32, u8); static int tpam_command_getl2(tpam_card *, u32); static int tpam_command_acceptd(tpam_card *, u32); static int tpam_command_acceptb(tpam_card *, u32); static int tpam_command_hangup(tpam_card *, u32); static int tpam_command_proceed(tpam_card *, u32); static void tpam_statcallb_run(unsigned long); static void tpam_statcallb(tpam_card *, isdn_ctrl); /* * Function called when the ISDN link level send a command to the driver. * * c: ISDN command. * * Return: 0 if OK, <0 on errors. */ int tpam_command(isdn_ctrl *c) { tpam_card *card; unsigned long argp; dprintk("TurboPAM(tpam_command) card=%d, command=%d\n", c->driver, c->command); /* search for the board */ if (!(card = tpam_findcard(c->driver))) { printk(KERN_ERR "TurboPAM(tpam_command): invalid driverId %d\n", c->driver); return -ENODEV; } /* dispatch the command */ switch (c->command) { case ISDN_CMD_IOCTL: argp = c->parm.userdata; switch (c->arg) { case TPAM_CMD_DSPLOAD: return tpam_command_ioctl_dspload(card, argp); case TPAM_CMD_DSPSAVE: return tpam_command_ioctl_dspsave(card, argp); case TPAM_CMD_DSPRUN: return tpam_command_ioctl_dsprun(card); case TPAM_CMD_LOOPMODEON: return tpam_command_ioctl_loopmode(card, 1); case TPAM_CMD_LOOPMODEOFF: return tpam_command_ioctl_loopmode(card, 0); default: dprintk("TurboPAM(tpam_command): " "invalid tpam ioctl %ld\n", c->arg); return -EINVAL; } case ISDN_CMD_DIAL: return tpam_command_dial(card, c->arg, c->parm.setup.phone); case ISDN_CMD_ACCEPTD: return tpam_command_acceptd(card, c->arg); case ISDN_CMD_ACCEPTB: return tpam_command_acceptb(card, c->arg); case ISDN_CMD_HANGUP: return tpam_command_hangup(card, c->arg); case ISDN_CMD_SETL2: return tpam_command_setl2(card, c->arg & 0xff, c->arg >> 8); case ISDN_CMD_GETL2: return tpam_command_getl2(card, c->arg); case ISDN_CMD_LOCK: MOD_INC_USE_COUNT; return 0; case ISDN_CMD_UNLOCK: MOD_DEC_USE_COUNT; return 0; case ISDN_CMD_PROCEED: return tpam_command_proceed(card, c->arg); default: dprintk("TurboPAM(tpam_command): " "unknown or unused isdn ioctl %d\n", c->command); return -EINVAL; } /* not reached */ return -EINVAL; } /* * Load some data into the board's memory. * * card: the board * arg: IOCTL argument containing the user space address of * the tpam_dsp_ioctl structure describing the IOCTL. * * Return: 0 if OK, <0 on errors. */ static int tpam_command_ioctl_dspload(tpam_card *card, u32 arg) { tpam_dsp_ioctl tdl; int ret; dprintk("TurboPAM(tpam_command_ioctl_dspload): card=%d\n", card->id); /* get the IOCTL parameter from userspace */ if (copy_from_user(&tdl, (void *)arg, sizeof(tpam_dsp_ioctl))) return -EFAULT; /* if the board's firmware was started, protect against writes * to unallowed memory areas. If the board's firmware wasn't started, * all is allowed. */ if (card->running && tpam_verify_area(tdl.address, tdl.data_len)) return -EPERM; /* write the data in the board's memory */ ret = copy_from_user_to_pam(card, (void *)tdl.address, (void *)arg + sizeof(tpam_dsp_ioctl), tdl.data_len); return 0; } /* * Extract some data from the board's memory. * * card: the board * arg: IOCTL argument containing the user space address of * the tpam_dsp_ioctl structure describing the IOCTL. * * Return: 0 if OK, <0 on errors. */ static int tpam_command_ioctl_dspsave(tpam_card *card, u32 arg) { tpam_dsp_ioctl tdl; int ret; dprintk("TurboPAM(tpam_command_ioctl_dspsave): card=%d\n", card->id); /* get the IOCTL parameter from userspace */ if (copy_from_user(&tdl, (void *)arg, sizeof(tpam_dsp_ioctl))) return -EFAULT; /* protect against read from unallowed memory areas */ if (tpam_verify_area(tdl.address, tdl.data_len)) return -EPERM; /* read the data from the board's memory */ ret = copy_from_pam_to_user(card, (void *)arg + sizeof(tpam_dsp_ioctl), (void *)tdl.address, tdl.data_len); return ret; } /* * Launch the board's firmware. This function must be called after the * firmware was loaded into the board's memory using TPAM_CMD_DSPLOAD * IOCTL commands. After launching the firmware, this function creates * the NCOs and waits for their creation. * * card: the board * * Return: 0 if OK, <0 on errors. */ static int tpam_command_ioctl_dsprun(tpam_card *card) { u32 signature = 0, timeout, i; isdn_ctrl ctrl; struct sk_buff *skb; dprintk("TurboPAM(tpam_command_ioctl_dsprun): card=%d\n", card->id); /* board must _not_ be running */ if (card->running) return -EBUSY; /* reset the board */ spin_lock_irq(&card->lock); copy_to_pam_dword(card, (void *)TPAM_MAGICNUMBER_REGISTER, 0xdeadface); readl(card->bar0 + TPAM_DSPINT_REGISTER); readl(card->bar0 + TPAM_HINTACK_REGISTER); spin_unlock_irq(&card->lock); /* wait for the board signature */ timeout = jiffies + SIGNATURE_TIMEOUT; while (time_before(jiffies, timeout)) { spin_lock_irq(&card->lock); signature = copy_from_pam_dword(card, (void *)TPAM_MAGICNUMBER_REGISTER); spin_unlock_irq(&card->lock); if (signature == TPAM_MAGICNUMBER) break; set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(2); } /* signature not present -> board not started */ if (signature != TPAM_MAGICNUMBER) { printk(KERN_ERR "TurboPAM(tpam_command_ioctl_dsprun): " "card=%d, signature 0x%lx, expected 0x%lx\n", card->id, (unsigned long)signature, (unsigned long)TPAM_MAGICNUMBER); printk(KERN_ERR "TurboPAM(tpam_command_ioctl_dsprun): " "card=%d, firmware not started\n", card->id); return -EIO; } /* the firmware is started */ printk(KERN_INFO "TurboPAM: card=%d, firmware started\n", card->id); /* init the CRC routines */ init_CRC(); /* create all the NCOs */ for (i = 0; i < TPAM_NBCHANNEL; ++i) if ((skb = build_ACreateNCOReq(""))) tpam_enqueue(card, skb); /* wait for NCO creation confirmation */ timeout = jiffies + NCOCREATE_TIMEOUT; while (time_before(jiffies, timeout)) { if (card->channels_tested == TPAM_NBCHANNEL) break; set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(2); } card->running = 1; if (card->channels_tested != TPAM_NBCHANNEL) printk(KERN_ERR "TurboPAM(tpam_command_ioctl_dsprun): " "card=%d, tried to init %d channels, " "got reply from only %d channels\n", card->id, TPAM_NBCHANNEL, card->channels_tested); /* if all the channels were not initialized, signal to the ISDN * link layer that fact that some channels are not usable */ if (card->channels_used != TPAM_NBCHANNEL) for (i = card->channels_used; i < TPAM_NBCHANNEL; ++i) { ctrl.driver = card->id; ctrl.command = ISDN_STAT_DISCH; ctrl.arg = i; ctrl.parm.num[0] = 0; (* card->interface.statcallb)(&ctrl); } printk(KERN_INFO "TurboPAM: card=%d, ready, %d channels available\n", card->id, card->channels_used); /* let's rock ! */ ctrl.driver = card->id; ctrl.command = ISDN_STAT_RUN; ctrl.arg = 0; tpam_statcallb(card, ctrl); return 0; } /* * Set/reset the board's looptest mode. * * card: the board * mode: if 1, sets the board's looptest mode, if 0 resets it. * * Return: 0 if OK, <0 if error. */ static int tpam_command_ioctl_loopmode(tpam_card *card, u8 mode) { /* board must be running */ if (!card->running) return -ENODEV; card->loopmode = mode; return 0; } /* * Issue a dial command. This function builds and sends a CConnectReq. * * card: the board * channel: the channel number * phone: the remote phone number (EAZ) * * Return: 0 if OK, <0 if error. */ static int tpam_command_dial(tpam_card *card, u32 channel, u8 *phone) { struct sk_buff *skb; isdn_ctrl ctrl; dprintk("TurboPAM(tpam_command_dial): card=%d, channel=%lu, phone=%s\n", card->id, (unsigned long)channel, phone); /* board must be running */ if (!card->running) return -ENODEV; /* initialize channel parameters */ card->channels[channel].realhdlc = card->channels[channel].hdlc; card->channels[channel].hdlcshift = 0; card->channels[channel].readytoreceive = 0; /* build and send a CConnectReq */ skb = build_CConnectReq(card->channels[channel].ncoid, phone, card->channels[channel].realhdlc); if (!skb) return -ENOMEM; tpam_enqueue(card, skb); /* making a connection in modem mode is slow and causes the ISDN * link layer to hangup the connection before even it gets a chance * to establish... All we can do is simulate a successful connection * for now, and send a DHUP later if the connection fails */ if (!card->channels[channel].realhdlc) { ctrl.driver = card->id; ctrl.command = ISDN_STAT_DCONN; ctrl.arg = channel; tpam_statcallb(card, ctrl); } return 0; } /* * Set the level2 protocol (modem or HDLC). * * card: the board * channel: the channel number * proto: the level2 protocol (one of ISDN_PROTO_L2*) * * Return: 0 if OK, <0 if error. */ static int tpam_command_setl2(tpam_card *card, u32 channel, u8 proto) { dprintk("TurboPAM(tpam_command_setl2): card=%d, channel=%lu, proto=%d\n", card->id, (unsigned long)channel, proto); /* board must be running */ if (!card->running) return -ENODEV; /* set the hdlc/modem mode */ switch (proto) { case ISDN_PROTO_L2_HDLC: card->channels[channel].hdlc = 1; break; case ISDN_PROTO_L2_MODEM: card->channels[channel].hdlc = 0; break; default: return -EINVAL; } return 0; } /* * Return the level2 protocol (modem or HDLC). * * card: the board * channel: the channel number * * Return: ISDN_PROTO_L2_HDLC/MODEM if OK, <0 if error. */ static int tpam_command_getl2(tpam_card *card, u32 channel) { dprintk("TurboPAM(tpam_command_getl2): card=%d, channel=%lu\n", card->id, (unsigned long)channel); /* board must be running */ if (!card->running) return -ENODEV; /* return the current mode */ if (card->channels[channel].realhdlc) return ISDN_PROTO_L2_HDLC; else return ISDN_PROTO_L2_MODEM; } /* * Accept a D-channel connection (incoming connection). This function * builds and sends a CConnectRsp message and signals DCONN to the ISDN * link level. * * card: the board * channel: the channel number * * Return: 0 if OK, <0 if error. */ static int tpam_command_acceptd(tpam_card *card, u32 channel) { isdn_ctrl ctrl; struct sk_buff *skb; dprintk("TurboPAM(tpam_command_acceptd): card=%d, channel=%lu\n", card->id, (unsigned long)channel); /* board must be running */ if (!card->running) return -ENODEV; /* build and send a CConnectRsp */ skb = build_CConnectRsp(card->channels[channel].ncoid); if (!skb) return -ENOMEM; tpam_enqueue(card, skb); /* issue DCONN to the ISDN link level */ ctrl.driver = card->id; ctrl.command = ISDN_STAT_DCONN; ctrl.arg = channel; tpam_statcallb(card, ctrl); return 0; } /* * Accepts a B-channel connection. This is not used by the driver, * since the TurboPAM is an active card hiding its B-channels from * us. We just signal BCONN to the ISDN link layer. * * card: the board * channel: the channel number * * Return: 0 if OK, <0 if error. */ static int tpam_command_acceptb(tpam_card *card, u32 channel) { isdn_ctrl ctrl; dprintk("TurboPAM(tpam_command_acceptb): card=%d, channel=%lu\n", card->id, (unsigned long)channel); /* board must be running */ if (!card->running) return -ENODEV; /* issue BCONN to the ISDN link level */ ctrl.driver = card->id; ctrl.command = ISDN_STAT_BCONN; ctrl.arg = channel; ctrl.parm.num[0] = '\0'; tpam_statcallb(card, ctrl); return 0; } /* * Hang up a connection. This function builds and sends a CDisconnectReq. * * card: the board * channel: the channel number. * * Return: 0 if OK, <0 if error. */ static int tpam_command_hangup(tpam_card *card, u32 channel) { struct sk_buff *skb; dprintk("TurboPAM(tpam_command_hangup): card=%d, channel=%lu\n", card->id, (unsigned long)channel); /* board must be running */ if (!card->running) return -ENODEV; /* build and send a CDisconnectReq */ skb = build_CDisconnectReq(card->channels[channel].ncoid); if (!skb) return -ENOMEM; tpam_enqueue(card, skb); return 0; } /* * Proceed with an incoming connection. This function builds and sends a * CConnectRsp. * * card: the board * channel: the channel number. * * Return: 0 if OK, <0 if error. */ static int tpam_command_proceed(tpam_card *card, u32 channel) { struct sk_buff *skb; dprintk("TurboPAM(tpam_command_proceed): card=%d, channel=%lu\n", card->id, (unsigned long)channel); /* board must be running */ if (!card->running) return -ENODEV; /* build and send a CConnectRsp */ skb = build_CConnectRsp(card->channels[channel].ncoid); if (!skb) return -ENOMEM; tpam_enqueue(card, skb); return 0; } /* * Send data through the board. This function encodes the data depending * on the connection type (modem or HDLC), then builds and sends a U3DataReq. * * driverId: the driver id (really meaning here the board) * channel: the channel number * ack: data needs to be acknowledged upon send * skb: sk_buff containing the data * * Return: size of data send if OK, <0 if error. */ int tpam_writebuf_skb(int driverId, int channel, int ack, struct sk_buff *skb) { tpam_card *card; int orig_size = skb->len; void *finaldata; u32 finallen; dprintk("TurboPAM(tpam_writebuf_skb): " "card=%d, channel=%ld, ack=%d, data size=%d\n", driverId, (unsigned long)channel, ack, skb->len); /* find the board based on its driver ID */ if (!(card = tpam_findcard(driverId))) { printk(KERN_ERR "TurboPAM(tpam_writebuf_skb): " "invalid driverId %d\n", driverId); return -ENODEV; } /* board must be running */ if (!card->running) return -ENODEV; /* allocate some temporary memory */ if (!(finaldata = (void *)__get_free_page(GFP_ATOMIC))) { printk(KERN_ERR "TurboPAM(tpam_writebuf_skb): " "get_free_page failed\n"); return -ENOMEM; } /* encode the data */ if (!card->channels[channel].realhdlc) { /* modem mode */ hdlc_encode_modem(skb->data, skb->len, finaldata, &finallen); } else { /* HDLC mode */ void *tempdata; u32 templen; if (!(tempdata = (void *)__get_free_page(GFP_ATOMIC))) { printk(KERN_ERR "TurboPAM(tpam_writebuf_skb): " "get_free_page failed\n"); free_page((u32)finaldata); return -ENOMEM; } hdlc_no_accm_encode(skb->data, skb->len, tempdata, &templen); finallen = tpam_hdlc_encode(tempdata, finaldata, &card->channels[channel].hdlcshift, templen); free_page((u32)tempdata); } /* free the old sk_buff */ kfree_skb(skb); /* build and send a U3DataReq */ skb = build_U3DataReq(card->channels[channel].ncoid, finaldata, finallen, ack, orig_size); if (!skb) { free_page((u32)finaldata); return -ENOMEM; } tpam_enqueue_data(&card->channels[channel], skb); /* free the temporary memory */ free_page((u32)finaldata); return orig_size; } /* * Treat a received ACreateNCOCnf message. * * card: the board * skb: the received message */ void tpam_recv_ACreateNCOCnf(tpam_card *card, struct sk_buff *skb) { u32 ncoid; u8 status; u32 channel; dprintk("TurboPAM(tpam_recv_ACreateNCOCnf): card=%d\n", card->id); /* parse the message contents */ if (parse_ACreateNCOCnf(skb, &status, &ncoid)) return; /* if the card is alreay running, it means that this message * arrives too late... */ if (card->running) { printk(KERN_ERR "TurboPAM(tpam_recv_ACreateNCOCnf): " "ACreateNCOCnf received too late, status=%d\n", status); return; } /* the NCO creation failed, the corresponding channel will * be unused */ if (status) { printk(KERN_ERR "TurboPAM(tpam_recv_ACreateNCOCnf): " "ACreateNCO failed, status=%d\n", status); card->channels_tested++; return; } /* find the first free channel and assign the nco ID to it */ if ((channel = tpam_findchannel(card, TPAM_NCOID_INVALID)) == TPAM_CHANNEL_INVALID) { printk(KERN_ERR "TurboPAM(tpam_recv_ACreateNCOCnf): " "All channels are assigned\n"); return; } card->channels[channel].ncoid = ncoid; card->channels_tested++; card->channels_used++; } /* * Treat a received ADestroyNCOCnf message. Not used by the driver. * * card: the board * skb: the received message */ void tpam_recv_ADestroyNCOCnf(tpam_card *card, struct sk_buff *skb) { u32 ncoid; u8 status; u32 channel; dprintk("TurboPAM(tpam_recv_ADestroyNCOCnf): card=%d\n", card->id); /* parse the message contents */ if (parse_ADestroyNCOCnf(skb, &status, &ncoid)) return; if (status) { printk(KERN_ERR "TurboPAM(tpam_recv_ADestroyNCOCnf): " "ADestroyNCO failed, status=%d\n", status); return; } /* clears the channel's nco ID */ if ((channel = tpam_findchannel(card, ncoid)) == TPAM_CHANNEL_INVALID) { printk(KERN_ERR "TurboPAM(tpam_recv_ADestroyNCOCnf): " "ncoid invalid %lu\n", (unsigned long)ncoid); return; } card->channels[channel].ncoid = TPAM_NCOID_INVALID; } /* * Treat a received CConnectCnf message. * * card: the board * skb: the received message */ void tpam_recv_CConnectCnf(tpam_card *card, struct sk_buff *skb) { u32 ncoid; u32 channel; isdn_ctrl ctrl; dprintk("TurboPAM(tpam_recv_CConnectCnf): card=%d\n", card->id); /* parse the message contents */ if (parse_CConnectCnf(skb, &ncoid)) return; /* find the channel by its nco ID */ if ((channel = tpam_findchannel(card, ncoid)) == TPAM_CHANNEL_INVALID) { printk(KERN_ERR "TurboPAM(tpam_recv_CConnectCnf): " "ncoid invalid %lu\n", (unsigned long)ncoid); return; } /* issue a DCONN command to the ISDN link layer if we are in HDLC mode. * In modem mode, we alreay did it - the ISDN timer kludge */ if (card->channels[channel].realhdlc) { ctrl.driver = card->id; ctrl.command = ISDN_STAT_DCONN; ctrl.arg = channel; (* card->interface.statcallb)(&ctrl); } } /* * Treat a received CConnectInd message. This function signals a ICALL * to the ISDN link layer. * * card: the board * skb: the received message */ void tpam_recv_CConnectInd(tpam_card *card, struct sk_buff *skb) { u32 ncoid; u32 channel; u8 hdlc, plan, screen; u8 calling[PHONE_MAXIMUMSIZE], called[PHONE_MAXIMUMSIZE]; isdn_ctrl ctrl; int status; dprintk("TurboPAM(tpam_recv_CConnectInd): card=%d\n", card->id); /* parse the message contents */ if (parse_CConnectInd(skb, &ncoid, &hdlc, calling, called, &plan, &screen)) return; /* find the channel by its nco ID */ if ((channel = tpam_findchannel(card, ncoid)) == TPAM_CHANNEL_INVALID) { printk(KERN_ERR "TurboPAM(tpam_recv_CConnectInd): " "ncoid invalid %lu\n", (unsigned long)ncoid); return; } /* initialize the channel parameters */ card->channels[channel].realhdlc = hdlc; card->channels[channel].hdlcshift = 0; card->channels[channel].readytoreceive = 0; /* issue a ICALL command to the ISDN link layer */ ctrl.driver = card->id; ctrl.command = ISDN_STAT_ICALL; ctrl.arg = channel; memcpy(ctrl.parm.setup.phone, calling, 32); memcpy(ctrl.parm.setup.eazmsn, called, 32); ctrl.parm.setup.si1 = 7; /* data capability */ ctrl.parm.setup.si2 = 0; ctrl.parm.setup.plan = plan; ctrl.parm.setup.screen = screen; status = (* card->interface.statcallb)(&ctrl); switch (status) { case 1: case 4: /* call accepted, link layer will send us a ACCEPTD * command later */ dprintk("TurboPAM(tpam_recv_CConnectInd): " "card=%d, channel=%d, icall waiting, status=%d\n", card->id, channel, status); break; default: /* call denied, we build and send a CDisconnectReq */ dprintk("TurboPAM(tpam_recv_CConnectInd): " "card=%d, channel=%d, icall denied, status=%d\n", card->id, channel, status); skb = build_CDisconnectReq(ncoid); if (!skb) return; tpam_enqueue(card, skb); } } /* * Treat a received CDisconnectInd message. This function signals a DHUP and * a BHUP to the ISDN link layer. * * card: the board * skb: the received message */ void tpam_recv_CDisconnectInd(tpam_card *card, struct sk_buff *skb) { u32 ncoid; u32 channel; u32 cause; isdn_ctrl ctrl; dprintk("TurboPAM(tpam_recv_CDisconnectInd): card=%d\n", card->id); /* parse the message contents */ if (parse_CDisconnectInd(skb, &ncoid, &cause)) return; /* find the channel by its nco ID */ if ((channel = tpam_findchannel(card, ncoid)) == TPAM_CHANNEL_INVALID) { printk(KERN_ERR "TurboPAM(tpam_recv_CDisconnectInd): " "ncoid invalid %lu\n", (unsigned long)ncoid); return; } /* build and send a CDisconnectRsp */ skb = build_CDisconnectRsp(ncoid); if (!skb) return; tpam_enqueue(card, skb); /* issue a DHUP to the ISDN link layer */ ctrl.driver = card->id; ctrl.command = ISDN_STAT_DHUP; ctrl.arg = channel; (* card->interface.statcallb)(&ctrl); /* issue a BHUP to the ISDN link layer */ ctrl.driver = card->id; ctrl.command = ISDN_STAT_BHUP; ctrl.arg = channel; (* card->interface.statcallb)(&ctrl); } /* * Treat a received CDisconnectCnf message. This function signals a DHUP and * a BHUP to the ISDN link layer. * * card: the board * skb: the received message */ void tpam_recv_CDisconnectCnf(tpam_card *card, struct sk_buff *skb) { u32 ncoid; u32 channel; u32 cause; isdn_ctrl ctrl; dprintk("TurboPAM(tpam_recv_CDisconnectCnf): card=%d\n", card->id); /* parse the message contents */ if (parse_CDisconnectCnf(skb, &ncoid, &cause)) return; /* find the channel by its nco ID */ if ((channel = tpam_findchannel(card, ncoid)) == TPAM_CHANNEL_INVALID) { printk(KERN_ERR "TurboPAM(tpam_recv_CDisconnectCnf): " "ncoid invalid %lu\n", (unsigned long)ncoid); return; } /* issue a DHUP to the ISDN link layer */ ctrl.driver = card->id; ctrl.command = ISDN_STAT_DHUP; ctrl.arg = channel; (* card->interface.statcallb)(&ctrl); /* issue a BHUP to the ISDN link layer */ ctrl.driver = card->id; ctrl.command = ISDN_STAT_BHUP; ctrl.arg = channel; (* card->interface.statcallb)(&ctrl); } /* * Treat a received U3DataInd message. This function decodes the data * depending on the connection type (modem or HDLC) and passes it to the * ISDN link layer by using rcvcallb_skb. * * card: the board * skb: the received message + data */ void tpam_recv_U3DataInd(tpam_card *card, struct sk_buff *skb) { u32 ncoid; u32 channel; u8 *data; u16 len; struct sk_buff *result; dprintk("TurboPAM(tpam_recv_U3DataInd): card=%d, datalen=%d\n", card->id, skb->len); /* parse the message contents */ if (parse_U3DataInd(skb, &ncoid, &data, &len)) return; /* find the channel by its nco ID */ if ((channel = tpam_findchannel(card, ncoid)) == TPAM_CHANNEL_INVALID) { printk(KERN_ERR "TurboPAM(tpam_recv_U3DataInd): " "ncoid invalid %lu\n", (unsigned long)ncoid); return; } /* decode the data */ if (card->channels[ncoid].realhdlc) { /* HDLC mode */ u8 *tempdata; u32 templen; if (!(tempdata = (void *)__get_free_page(GFP_ATOMIC))) { printk(KERN_ERR "TurboPAM(tpam_recv_U3DataInd): " "get_free_page failed\n"); return; } templen = tpam_hdlc_decode(data, tempdata, len); templen = hdlc_no_accm_decode(tempdata, templen); if (!(result = alloc_skb(templen, GFP_ATOMIC))) { printk(KERN_ERR "TurboPAM(tpam_recv_U3DataInd): " "alloc_skb failed\n"); free_page((u32)tempdata); return; } memcpy(skb_put(result, templen), tempdata, templen); free_page((u32)tempdata); } else { /* modem mode */ if (!(result = alloc_skb(len, GFP_ATOMIC))) { printk(KERN_ERR "TurboPAM(tpam_recv_U3DataInd): " "alloc_skb failed\n"); return; } memcpy(skb_put(result, len), data, len); } /* In loop mode, resend the data immediatly */ if (card->loopmode) { struct sk_buff *loopskb; if (!(loopskb = alloc_skb(skb->len, GFP_ATOMIC))) { printk(KERN_ERR "TurboPAM(tpam_recv_U3DataInd): " "alloc_skb failed\n"); kfree_skb(result); return; } memcpy(skb_put(loopskb, result->len), result->data, result->len); if (tpam_writebuf_skb(card->id, channel, 0, loopskb) < 0) kfree_skb(loopskb); } /* pass the data to the ISDN link layer */ (* card->interface.rcvcallb_skb)(card->id, channel, result); } /* * Treat a received U3ReadyToReceiveInd message. This function sets the * channel ready flag and triggers the send of data if the channel becomed * ready. * * card: the board * skb: the received message + data */ void tpam_recv_U3ReadyToReceiveInd(tpam_card *card, struct sk_buff *skb) { u32 ncoid; u32 channel; u8 ready; dprintk("TurboPAM(tpam_recv_U3ReadyToReceiveInd): card=%d\n", card->id); /* parse the message contents */ if (parse_U3ReadyToReceiveInd(skb, &ncoid, &ready)) return; /* find the channel by its nco ID */ if ((channel = tpam_findchannel(card, ncoid)) == TPAM_CHANNEL_INVALID) { printk(KERN_ERR "TurboPAM(tpam_recv_U3ReadyToReceiveInd): " "ncoid invalid %lu\n", (unsigned long)ncoid); return; } /* set the readytoreceive flag */ card->channels[channel].readytoreceive = ready; /* if the channel just becomed ready, trigger the send of queued data */ if (ready) tpam_enqueue_data(&card->channels[channel], NULL); } /* * Runs the delayed statcallb when its timer expires. * * parm: pointer to the tpam_statcallb_data statcallb argument. */ static void tpam_statcallb_run(unsigned long parm) { tpam_statcallb_data *ds = (tpam_statcallb_data *)parm; dprintk("TurboPAM(tpam_statcallb_run)\n"); (* ds->card->interface.statcallb)(&ds->ctrl); kfree(ds->timer); kfree(ds); } /* * Queues a statcallb call for delayed invocation. * * card: the board * ctrl: the statcallb argument */ static void tpam_statcallb(tpam_card *card, isdn_ctrl ctrl) { struct timer_list *timer; tpam_statcallb_data *ds; dprintk("TurboPAM(tpam_statcallb): card=%d\n", card->id); if (!(timer = (struct timer_list *) kmalloc(sizeof(struct timer_list), GFP_ATOMIC))) { printk(KERN_ERR "TurboPAM: tpam_statcallb: kmalloc failed!\n"); return; } if (!(ds = (tpam_statcallb_data *) kmalloc(sizeof(tpam_statcallb_data), GFP_ATOMIC))) { printk(KERN_ERR "TurboPAM: tpam_statcallb: kmalloc failed!\n"); kfree(timer); return; } ds->card = card; ds->timer = timer; memcpy(&ds->ctrl, &ctrl, sizeof(isdn_ctrl)); init_timer(timer); timer->function = tpam_statcallb_run; timer->data = (unsigned long)ds; timer->expires = jiffies + HZ / 10; /* 0.1 second */ add_timer(timer); }