patch-2.4.6 linux/drivers/isdn/tpam/tpam_commands.c
Next file: linux/drivers/isdn/tpam/tpam_crcpc.c
Previous file: linux/drivers/isdn/tpam/tpam.h
Back to the patch index
Back to the overall index
- Lines: 1036
- Date:
Mon Jul 2 14:07:55 2001
- Orig file:
v2.4.5/linux/drivers/isdn/tpam/tpam_commands.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.4.5/linux/drivers/isdn/tpam/tpam_commands.c linux/drivers/isdn/tpam/tpam_commands.c
@@ -0,0 +1,1035 @@
+/* $Id: tpam_commands.c,v 1.1.2.2 2001/06/08 08:23:46 kai Exp $
+ *
+ * Turbo PAM ISDN driver for Linux. (Kernel Driver - ISDN commands)
+ *
+ * Copyright 2001 Stelian Pop <stelian.pop@fr.alcove.com>, Alcôve
+ *
+ * For all support questions please contact: <support@auvertech.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/tqueue.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+
+#include <linux/isdn/tpam.h>
+#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 (timeout > jiffies) {
+ 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 (timeout > jiffies) {
+ 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 = 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 = 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 + 0.1 * HZ; /* 0.1 second */
+ add_timer(timer);
+}
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)