2006-02-11 15:02:25

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 0/9] isdn4linux: add drivers for Siemens Gigaset ISDN DECT PABX

The following patches add drivers for the Siemens Gigaset 3070 family of
ISDN DECT PABXes connected via USB, either directly or over a DECT link
using a Gigaset M105 or compatible DECT data adapter. The devices are
integrated as ISDN adapters within the isdn4linux framework, supporting
incoming and outgoing voice and data connections, and also as tty
devices providing access to device specific AT commands.

Supported devices include models 3070, 3075, 4170, 4175, SX205, SX255,
and SX353 from the Siemens Gigaset product family, as well as the
technically identical models 45isdn and 721X from the Deutsche Telekom
Sinus series. Supported DECT adapters are the Gigaset M105 data and the
technically identical Gigaset USB Adapter DECT, Sinus 45 data 2, and
Sinus 721 data (but not the Gigaset M34 and Sinus 702 data which
advertise themselves as CDC-ACM devices).

These drivers have been developed over the last four years within the
SourceForge project http://sourceforge.net/projects/gigaset307x/. They
are being used successfully in several installations for dial-in
Internet access and for voice call switching with Asterisk.

This is our second attempt at submitting these drivers, taking into
account the comments we received to our first submission on 2005-12-11.

The patch set adds three kernel modules:

- a common module "gigaset" encapsulating the common logic for
controlling the PABX and the interfaces to userspace and the
isdn4linux subsystem.

- a connection-specific module "bas_gigaset" which handles
communication with the PABX over a direct USB connection.

- a connection-specific module "usb_gigaset" which does the same
for a DECT connection using the Gigaset M105 USB DECT adapter.

We also have a module "ser_gigaset" which supports the Gigaset M101
RS232 DECT adapter, but we didn't judge it fit for inclusion in the
kernel, as it does direct programming of a i8250 serial port. It
should probably be rewritten as a serial line discipline but so far we
lack the neccessary knowledge about writing a line discipline for that.

The drivers have been working with kernel releases 2.2 and 2.4 as well
as 2.6, and although we took efforts to remove the compatibility code
for this submission, it probably still shows in places. Please make
allowances.

The patch has been split into 9 parts to comply to size limits.
All of the parts are designed to be applied together.

The patches are based on kernel release 2.6.16-rc2, in particular with
respect to the recent tty changes. If it is more appropriate to base it
on 2.6.15 at this time please let us know.


2006-02-11 15:01:53

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 6/9] isdn4linux: Siemens Gigaset drivers - procfs interface

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch adds the procfs interface to the gigaset module.
The procfs interface provides access to status information and statistics
about the Gigaset devices. If the drivers are built with the debugging
option it also allows to change the amount of debugging output on the fly.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

drivers/isdn/gigaset/proc.c | 81 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 81 insertions(+)

--- linux-2.6.16-rc2/drivers/isdn/gigaset/proc.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/proc.c 2006-02-11 15:20:26.000000000 +0100
@@ -0,0 +1,81 @@
+/*
+ * Stuff used by all variants of the driver
+ *
+ * Copyright (c) 2001 by Stefan Eilers <[email protected]>,
+ * Hansjoerg Lipp <[email protected]>,
+ * Tilman Schmidt <[email protected]>.
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: proc.c,v 1.5.2.13 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/ctype.h>
+
+static ssize_t show_cidmode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct cardstate *cs = usb_get_intfdata(intf);
+ return sprintf(buf, "%d\n", atomic_read(&cs->cidmode)); // FIXME use scnprintf for 13607 bit architectures (if PAGE_SIZE==4096)
+}
+
+static ssize_t set_cidmode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct cardstate *cs = usb_get_intfdata(intf);
+ long int value;
+ char *end;
+
+ value = simple_strtol(buf, &end, 0);
+ while (*end)
+ if (!isspace(*end++))
+ return -EINVAL;
+ if (value < 0 || value > 1)
+ return -EINVAL;
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ cs->waiting = 1;
+ if (!gigaset_add_event(cs, &cs->at_state, EV_PROC_CIDMODE,
+ NULL, value, NULL)) {
+ cs->waiting = 0;
+ up(&cs->sem);
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling PROC_CIDMODE");
+ gigaset_schedule_event(cs);
+
+ wait_event(cs->waitqueue, !cs->waiting);
+
+ up(&cs->sem);
+
+ return count;
+}
+
+static DEVICE_ATTR(cidmode, S_IRUGO|S_IWUSR, show_cidmode, set_cidmode);
+
+/* free sysfs for device */
+void gigaset_free_dev_sysfs(struct usb_interface *interface)
+{
+ dbg(DEBUG_INIT, "removing sysfs entries");
+ device_remove_file(&interface->dev, &dev_attr_cidmode);
+}
+EXPORT_SYMBOL_GPL(gigaset_free_dev_sysfs);
+
+/* initialize sysfs for device */
+void gigaset_init_dev_sysfs(struct usb_interface *interface)
+{
+ dbg(DEBUG_INIT, "setting up sysfs");
+ device_create_file(&interface->dev, &dev_attr_cidmode);
+}
+EXPORT_SYMBOL_GPL(gigaset_init_dev_sysfs);

2006-02-11 15:01:25

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 4/9] isdn4linux: Siemens Gigaset drivers - isdn4linux interface

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch adds the isdn4linux subsystem interface to the gigaset module.
The isdn4linux subsystem interface handles requests from and notifications
to the isdn4linux subsystem.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

drivers/isdn/gigaset/i4l.c | 567 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 567 insertions(+)

--- linux-2.6.16-rc2/drivers/isdn/gigaset/i4l.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/i4l.c 2006-02-11 15:20:26.000000000 +0100
@@ -0,0 +1,567 @@
+/*
+ * Stuff used by all variants of the driver
+ *
+ * Copyright (c) 2001 by Stefan Eilers ([email protected]),
+ * Hansjoerg Lipp ([email protected]),
+ * Tilman Schmidt ([email protected]).
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: i4l.c,v 1.3.2.9 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+
+/* == Handling of I4L IO ============================================================================*/
+
+/* writebuf_from_LL
+ * called by LL to transmit data on an open channel
+ * inserts the buffer data into the send queue and starts the transmission
+ * Note that this operation must not sleep!
+ * When the buffer is processed completely, gigaset_skb_sent() should be called.
+ * parameters:
+ * driverID driver ID as assigned by LL
+ * channel channel number
+ * ack if != 0 LL wants to be notified on completion via statcallb(ISDN_STAT_BSENT)
+ * skb skb containing data to send
+ * return value:
+ * number of accepted bytes
+ * 0 if temporarily unable to accept data (out of buffer space)
+ * <0 on error (eg. -EINVAL)
+ */
+static int writebuf_from_LL(int driverID, int channel, int ack, struct sk_buff *skb)
+{
+ struct cardstate *cs;
+ struct bc_state *bcs;
+ unsigned len;
+ unsigned skblen;
+
+ if (!(cs = gigaset_get_cs_by_id(driverID))) {
+ err("%s: invalid driver ID (%d)", __func__, driverID);
+ return -ENODEV;
+ }
+ if (channel < 0 || channel >= cs->channels) {
+ err("%s: invalid channel ID (%d)", __func__, channel);
+ return -ENODEV;
+ }
+ bcs = &cs->bcs[channel];
+ len = skb->len;
+
+ dbg(DEBUG_LLDATA,
+ "Receiving data from LL (id: %d, channel: %d, ack: %d, size: %d)",
+ driverID, channel, ack, len);
+
+ if (!len) {
+ if (ack)
+ warn("not ACKing empty packet from LL");
+ return 0;
+ }
+ if (len > MAX_BUF_SIZE) {
+ err("%s: packet too large (%d bytes)", __func__, channel);
+ return -EINVAL;
+ }
+
+ if (!atomic_read(&cs->connected))
+ return -ENODEV;
+
+ skblen = ack ? len : 0;
+ skb->head[0] = skblen & 0xff;
+ skb->head[1] = skblen >> 8;
+ dbg(DEBUG_MCMD, "skb: len=%u, skblen=%u: %02x %02x", len, skblen,
+ (unsigned) skb->head[0], (unsigned) skb->head[1]);
+
+ /* pass to device-specific module */
+ return cs->ops->send_skb(bcs, skb);
+}
+
+void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb)
+{
+ unsigned len;
+ isdn_ctrl response;
+
+ ++bcs->trans_up;
+
+ if (skb->len)
+ warn("%s: skb->len==%d", __func__, skb->len);
+
+ len = (unsigned char) skb->head[0] |
+ (unsigned) (unsigned char) skb->head[1] << 8;
+ if (len) {
+ dbg(DEBUG_MCMD,
+ "Acknowledge sending to LL (id: %d, channel: %d size: %u)",
+ bcs->cs->myid, bcs->channel, len);
+
+ response.driver = bcs->cs->myid;
+ response.command = ISDN_STAT_BSENT;
+ response.arg = bcs->channel;
+ response.parm.length = len;
+ bcs->cs->iif.statcallb(&response);
+ }
+}
+EXPORT_SYMBOL_GPL(gigaset_skb_sent);
+
+/* This function will be called by LL to send commands
+ * NOTE: LL ignores the returned value, for commands other than ISDN_CMD_IOCTL,
+ * so don't put too much effort into it.
+ */
+static int command_from_LL(isdn_ctrl *cntrl)
+{
+ struct cardstate *cs = gigaset_get_cs_by_id(cntrl->driver);
+ //isdn_ctrl response;
+ //unsigned long flags;
+ struct bc_state *bcs;
+ int retval = 0;
+ struct setup_parm *sp;
+
+ //dbg(DEBUG_ANY, "Gigaset_HW: Receiving command");
+ gigaset_debugdrivers();
+
+ /* Terminate this call if no device is present. Bt if the command is "ISDN_CMD_LOCK" or
+ * "ISDN_CMD_UNLOCK" then execute it due to the fact that they are device independent !
+ */
+ //FIXME "remove test for &connected"
+ if ((!cs || !atomic_read(&cs->connected))) {
+ warn("LL tried to access unknown device with nr. %d",
+ cntrl->driver);
+ return -ENODEV;
+ }
+
+ switch (cntrl->command) {
+ case ISDN_CMD_IOCTL:
+
+ dbg(DEBUG_ANY, "ISDN_CMD_IOCTL (driver:%d,arg: %ld)",
+ cntrl->driver, cntrl->arg);
+
+ warn("ISDN_CMD_IOCTL is not supported.");
+ return -EINVAL;
+
+ case ISDN_CMD_DIAL:
+ dbg(DEBUG_ANY, "ISDN_CMD_DIAL (driver: %d, channel: %ld, "
+ "phone: %s,ownmsn: %s, si1: %d, si2: %d)",
+ cntrl->driver, cntrl->arg,
+ cntrl->parm.setup.phone, cntrl->parm.setup.eazmsn,
+ cntrl->parm.setup.si1, cntrl->parm.setup.si2);
+
+ if (cntrl->arg >= cs->channels) {
+ err("invalid channel (%d)", (int) cntrl->arg);
+ return -EINVAL;
+ }
+
+ bcs = cs->bcs + cntrl->arg;
+
+ if (!gigaset_get_channel(bcs)) {
+ err("channel not free");
+ return -EBUSY;
+ }
+
+ sp = kmalloc(sizeof *sp, GFP_ATOMIC);
+ if (!sp) {
+ gigaset_free_channel(bcs);
+ err("ISDN_CMD_DIAL: out of memory");
+ return -ENOMEM;
+ }
+ *sp = cntrl->parm.setup;
+
+ if (!gigaset_add_event(cs, &bcs->at_state, EV_DIAL, sp,
+ atomic_read(&bcs->at_state.seq_index),
+ NULL)) {
+ //FIXME what should we do?
+ kfree(sp);
+ gigaset_free_channel(bcs);
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling DIAL");
+ gigaset_schedule_event(cs);
+ break;
+ case ISDN_CMD_ACCEPTD: //FIXME
+ dbg(DEBUG_ANY, "ISDN_CMD_ACCEPTD");
+
+ if (cntrl->arg >= cs->channels) {
+ err("invalid channel (%d)", (int) cntrl->arg);
+ return -EINVAL;
+ }
+
+ if (!gigaset_add_event(cs, &cs->bcs[cntrl->arg].at_state,
+ EV_ACCEPT, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling ACCEPT");
+ gigaset_schedule_event(cs);
+
+ break;
+ case ISDN_CMD_ACCEPTB:
+ dbg(DEBUG_ANY, "ISDN_CMD_ACCEPTB");
+ break;
+ case ISDN_CMD_HANGUP:
+ dbg(DEBUG_ANY,
+ "ISDN_CMD_HANGUP (channel: %d)", (int) cntrl->arg);
+
+ if (cntrl->arg >= cs->channels) {
+ err("ISDN_CMD_HANGUP: invalid channel (%u)",
+ (unsigned) cntrl->arg);
+ return -EINVAL;
+ }
+
+ if (!gigaset_add_event(cs, &cs->bcs[cntrl->arg].at_state,
+ EV_HUP, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling HUP");
+ gigaset_schedule_event(cs);
+
+ break;
+ case ISDN_CMD_CLREAZ: /* Do not signal incoming signals */ //FIXME
+ dbg(DEBUG_ANY, "ISDN_CMD_CLREAZ");
+ break;
+ case ISDN_CMD_SETEAZ: /* Signal incoming calls for given MSN */ //FIXME
+ dbg(DEBUG_ANY,
+ "ISDN_CMD_SETEAZ (id:%d, channel: %ld, number: %s)",
+ cntrl->driver, cntrl->arg, cntrl->parm.num);
+ break;
+ case ISDN_CMD_SETL2: /* Set L2 to given protocol */
+ dbg(DEBUG_ANY, "ISDN_CMD_SETL2 (Channel: %ld, Proto: %lx)",
+ cntrl->arg & 0xff, (cntrl->arg >> 8));
+
+ if ((cntrl->arg & 0xff) >= cs->channels) {
+ err("invalid channel (%u)",
+ (unsigned) cntrl->arg & 0xff);
+ return -EINVAL;
+ }
+
+ if (!gigaset_add_event(cs, &cs->bcs[cntrl->arg & 0xff].at_state,
+ EV_PROTO_L2, NULL, cntrl->arg >> 8,
+ NULL)) {
+ //FIXME what should we do?
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling PROTO_L2");
+ gigaset_schedule_event(cs);
+ break;
+ case ISDN_CMD_SETL3: /* Set L3 to given protocol */
+ dbg(DEBUG_ANY, "ISDN_CMD_SETL3 (Channel: %ld, Proto: %lx)",
+ cntrl->arg & 0xff, (cntrl->arg >> 8));
+
+ if ((cntrl->arg & 0xff) >= cs->channels) {
+ err("invalid channel (%u)",
+ (unsigned) cntrl->arg & 0xff);
+ return -EINVAL;
+ }
+
+ if (cntrl->arg >> 8 != ISDN_PROTO_L3_TRANS) {
+ err("invalid protocol %lu", cntrl->arg >> 8);
+ return -EINVAL;
+ }
+
+ break;
+ case ISDN_CMD_PROCEED:
+ dbg(DEBUG_ANY, "ISDN_CMD_PROCEED"); //FIXME
+ break;
+ case ISDN_CMD_ALERT:
+ dbg(DEBUG_ANY, "ISDN_CMD_ALERT"); //FIXME
+ if (cntrl->arg >= cs->channels) {
+ err("invalid channel (%d)", (int) cntrl->arg);
+ return -EINVAL;
+ }
+ //bcs = cs->bcs + cntrl->arg;
+ //bcs->proto2 = -1;
+ // FIXME
+ break;
+ case ISDN_CMD_REDIR:
+ dbg(DEBUG_ANY, "ISDN_CMD_REDIR"); //FIXME
+ break;
+ case ISDN_CMD_PROT_IO:
+ dbg(DEBUG_ANY, "ISDN_CMD_PROT_IO");
+ break;
+ case ISDN_CMD_FAXCMD:
+ dbg(DEBUG_ANY, "ISDN_CMD_FAXCMD");
+ break;
+ case ISDN_CMD_GETL2:
+ dbg(DEBUG_ANY, "ISDN_CMD_GETL2");
+ break;
+ case ISDN_CMD_GETL3:
+ dbg(DEBUG_ANY, "ISDN_CMD_GETL3");
+ break;
+ case ISDN_CMD_GETEAZ:
+ dbg(DEBUG_ANY, "ISDN_CMD_GETEAZ");
+ break;
+ case ISDN_CMD_SETSIL:
+ dbg(DEBUG_ANY, "ISDN_CMD_SETSIL");
+ break;
+ case ISDN_CMD_GETSIL:
+ dbg(DEBUG_ANY, "ISDN_CMD_GETSIL");
+ break;
+ default:
+ err("unknown command %d from LL",
+ cntrl->command);
+ return -EINVAL;
+ }
+
+ return retval;
+}
+
+void gigaset_i4l_cmd(struct cardstate *cs, int cmd)
+{
+ isdn_ctrl command;
+
+ command.driver = cs->myid;
+ command.command = cmd;
+ command.arg = 0;
+ cs->iif.statcallb(&command);
+}
+
+void gigaset_i4l_channel_cmd(struct bc_state *bcs, int cmd)
+{
+ isdn_ctrl command;
+
+ command.driver = bcs->cs->myid;
+ command.command = cmd;
+ command.arg = bcs->channel;
+ bcs->cs->iif.statcallb(&command);
+}
+
+int gigaset_isdn_setup_dial(struct at_state_t *at_state, void *data)
+{
+ struct bc_state *bcs = at_state->bcs;
+ unsigned proto;
+ const char *bc;
+ size_t length[AT_NUM];
+ size_t l;
+ int i;
+ struct setup_parm *sp = data;
+
+ switch (bcs->proto2) {
+ case ISDN_PROTO_L2_HDLC:
+ proto = 1; /* 0: Bitsynchron, 1: HDLC, 2: voice */
+ break;
+ case ISDN_PROTO_L2_TRANS:
+ proto = 2; /* 0: Bitsynchron, 1: HDLC, 2: voice */
+ break;
+ default:
+ err("invalid protocol: %u", bcs->proto2);
+ return -EINVAL;
+ }
+
+ switch (sp->si1) {
+ case 1: /* audio */
+ bc = "9090A3"; /* 3.1 kHz audio, A-law */
+ break;
+ case 7: /* data */
+ default: /* hope the app knows what it is doing */
+ bc = "8890"; /* unrestricted digital information */
+ }
+ //FIXME add missing si1 values from 1TR6, inspect si2, set HLC/LLC
+
+ length[AT_DIAL ] = 1 + strlen(sp->phone) + 1 + 1;
+ l = strlen(sp->eazmsn);
+ length[AT_MSN ] = l ? 6 + l + 1 + 1 : 0;
+ length[AT_BC ] = 5 + strlen(bc) + 1 + 1;
+ length[AT_PROTO] = 6 + 1 + 1 + 1; /* proto: 1 character */
+ length[AT_ISO ] = 6 + 1 + 1 + 1; /* channel: 1 character */
+ length[AT_TYPE ] = 6 + 1 + 1 + 1; /* call type: 1 character */
+ length[AT_HLC ] = 0;
+
+ for (i = 0; i < AT_NUM; ++i) {
+ kfree(bcs->commands[i]);
+ bcs->commands[i] = NULL;
+ if (length[i] &&
+ !(bcs->commands[i] = kmalloc(length[i], GFP_ATOMIC))) {
+ err("out of memory");
+ return -ENOMEM;
+ }
+ }
+
+ /* type = 1: extern, 0: intern, 2: recall, 3: door, 4: centrex */
+ if (sp->phone[0] == '*' && sp->phone[1] == '*') {
+ /* internal call: translate ** prefix to CTP value */
+ snprintf(bcs->commands[AT_DIAL], length[AT_DIAL],
+ "D%s\r", sp->phone+2);
+ strncpy(bcs->commands[AT_TYPE], "^SCTP=0\r", length[AT_TYPE]);
+ } else {
+ snprintf(bcs->commands[AT_DIAL], length[AT_DIAL],
+ "D%s\r", sp->phone);
+ strncpy(bcs->commands[AT_TYPE], "^SCTP=1\r", length[AT_TYPE]);
+ }
+
+ if (bcs->commands[AT_MSN])
+ snprintf(bcs->commands[AT_MSN], length[AT_MSN], "^SMSN=%s\r", sp->eazmsn);
+ snprintf(bcs->commands[AT_BC ], length[AT_BC ], "^SBC=%s\r", bc);
+ snprintf(bcs->commands[AT_PROTO], length[AT_PROTO], "^SBPR=%u\r", proto);
+ snprintf(bcs->commands[AT_ISO ], length[AT_ISO ], "^SISO=%u\r", (unsigned)bcs->channel + 1);
+
+ return 0;
+}
+
+int gigaset_isdn_setup_accept(struct at_state_t *at_state)
+{
+ unsigned proto;
+ size_t length[AT_NUM];
+ int i;
+ struct bc_state *bcs = at_state->bcs;
+
+ switch (bcs->proto2) {
+ case ISDN_PROTO_L2_HDLC:
+ proto = 1; /* 0: Bitsynchron, 1: HDLC, 2: voice */
+ break;
+ case ISDN_PROTO_L2_TRANS:
+ proto = 2; /* 0: Bitsynchron, 1: HDLC, 2: voice */
+ break;
+ default:
+ err("invalid protocol: %u", bcs->proto2);
+ return -EINVAL;
+ }
+
+ length[AT_DIAL ] = 0;
+ length[AT_MSN ] = 0;
+ length[AT_BC ] = 0;
+ length[AT_PROTO] = 6 + 1 + 1 + 1; /* proto: 1 character */
+ length[AT_ISO ] = 6 + 1 + 1 + 1; /* channel: 1 character */
+ length[AT_TYPE ] = 0;
+ length[AT_HLC ] = 0;
+
+ for (i = 0; i < AT_NUM; ++i) {
+ kfree(bcs->commands[i]);
+ bcs->commands[i] = NULL;
+ if (length[i] &&
+ !(bcs->commands[i] = kmalloc(length[i], GFP_ATOMIC))) {
+ err("out of memory");
+ return -ENOMEM;
+ }
+ }
+
+ snprintf(bcs->commands[AT_PROTO], length[AT_PROTO], "^SBPR=%u\r", proto);
+ snprintf(bcs->commands[AT_ISO ], length[AT_ISO ], "^SISO=%u\r", (unsigned) bcs->channel + 1);
+
+ return 0;
+}
+
+int gigaset_isdn_icall(struct at_state_t *at_state)
+{
+ struct cardstate *cs = at_state->cs;
+ struct bc_state *bcs = at_state->bcs;
+ isdn_ctrl response;
+ int retval;
+
+ /* fill ICALL structure */
+ response.parm.setup.si1 = 0; /* default: unknown */
+ response.parm.setup.si2 = 0;
+ response.parm.setup.screen = 0; //FIXME how to set these?
+ response.parm.setup.plan = 0;
+ if (!at_state->str_var[STR_ZBC]) {
+ /* no BC (internal call): assume speech, A-law */
+ response.parm.setup.si1 = 1;
+ } else if (!strcmp(at_state->str_var[STR_ZBC], "8890")) {
+ /* unrestricted digital information */
+ response.parm.setup.si1 = 7;
+ } else if (!strcmp(at_state->str_var[STR_ZBC], "8090A3")) {
+ /* speech, A-law */
+ response.parm.setup.si1 = 1;
+ } else if (!strcmp(at_state->str_var[STR_ZBC], "9090A3")) {
+ /* 3,1 kHz audio, A-law */
+ response.parm.setup.si1 = 1;
+ response.parm.setup.si2 = 2;
+ } else {
+ warn("RING ignored - unsupported BC %s",
+ at_state->str_var[STR_ZBC]);
+ return ICALL_IGNORE;
+ }
+ if (at_state->str_var[STR_NMBR]) {
+ strncpy(response.parm.setup.phone, at_state->str_var[STR_NMBR],
+ sizeof response.parm.setup.phone - 1);
+ response.parm.setup.phone[sizeof response.parm.setup.phone - 1] = 0;
+ } else
+ response.parm.setup.phone[0] = 0;
+ if (at_state->str_var[STR_ZCPN]) {
+ strncpy(response.parm.setup.eazmsn, at_state->str_var[STR_ZCPN],
+ sizeof response.parm.setup.eazmsn - 1);
+ response.parm.setup.eazmsn[sizeof response.parm.setup.eazmsn - 1] = 0;
+ } else
+ response.parm.setup.eazmsn[0] = 0;
+
+ if (!bcs) {
+ notice("no channel for incoming call");
+ dbg(DEBUG_CMD, "Sending ICALLW");
+ response.command = ISDN_STAT_ICALLW;
+ response.arg = 0; //FIXME
+ } else {
+ dbg(DEBUG_CMD, "Sending ICALL");
+ response.command = ISDN_STAT_ICALL;
+ response.arg = bcs->channel; //FIXME
+ }
+ response.driver = cs->myid;
+ retval = cs->iif.statcallb(&response);
+ dbg(DEBUG_CMD, "Response: %d", retval);
+ switch (retval) {
+ case 0: /* no takers */
+ return ICALL_IGNORE;
+ case 1: /* alerting */
+ bcs->chstate |= CHS_NOTIFY_LL;
+ return ICALL_ACCEPT;
+ case 2: /* reject */
+ return ICALL_REJECT;
+ case 3: /* incomplete */
+ warn("LL requested unsupported feature: Incomplete Number");
+ return ICALL_IGNORE;
+ case 4: /* proceeding */
+ /* Gigaset will send ALERTING anyway.
+ * There doesn't seem to be a way to avoid this.
+ */
+ return ICALL_ACCEPT;
+ case 5: /* deflect */
+ warn("LL requested unsupported feature: Call Deflection");
+ return ICALL_IGNORE;
+ default:
+ err("LL error %d on ICALL", retval);
+ return ICALL_IGNORE;
+ }
+}
+
+/* Set Callback function pointer */
+int gigaset_register_to_LL(struct cardstate *cs, const char *isdnid)
+{
+ isdn_if *iif = &cs->iif;
+
+ dbg(DEBUG_ANY, "Register driver capabilities to LL");
+
+ //iif->id[sizeof(iif->id) - 1]=0;
+ //strncpy(iif->id, isdnid, sizeof(iif->id) - 1);
+ if (snprintf(iif->id, sizeof iif->id, "%s_%u", isdnid, cs->minor_index)
+ >= sizeof iif->id)
+ return -ENOMEM; //FIXME EINVAL/...??
+
+ iif->owner = THIS_MODULE;
+ iif->channels = cs->channels; /* I am supporting just one channel *//* I was supporting...*/
+ iif->maxbufsize = MAX_BUF_SIZE;
+ iif->features = ISDN_FEATURE_L2_TRANS | /* Our device is very advanced, therefore */
+ ISDN_FEATURE_L2_HDLC |
+#ifdef GIG_X75
+ ISDN_FEATURE_L2_X75I |
+#endif
+ ISDN_FEATURE_L3_TRANS |
+ ISDN_FEATURE_P_EURO;
+ iif->hl_hdrlen = HW_HDR_LEN; /* Area for storing ack */
+ iif->command = command_from_LL;
+ iif->writebuf_skb = writebuf_from_LL;
+ iif->writecmd = NULL; /* Don't support isdnctrl */
+ iif->readstat = NULL; /* Don't support isdnctrl */
+ iif->rcvcallb_skb = NULL; /* Will be set by LL */
+ iif->statcallb = NULL; /* Will be set by LL */
+
+ if (!register_isdn(iif))
+ return 0;
+
+ cs->myid = iif->channels; /* Set my device id */
+ return 1;
+}

2006-02-11 15:01:21

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 1/9] isdn4linux: Siemens Gigaset drivers - Kconfigs and Makefiles

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch prepares the kernel build infrastructure for addition of the
Gigaset ISDN drivers. It creates a Makefile and Kconfig file for the
Gigaset driver and hooks them into those of the isdn4linux subsystem.
It also adds a MAINTAINERS entry for the driver.

This patch depends on patches 2 to 9 of the present set, as without the
actual source files, activating the options added here will cause the
kernel build to fail.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

MAINTAINERS | 9 +++++++++
drivers/isdn/Makefile | 1 +
drivers/isdn/gigaset/Kconfig | 42 ++++++++++++++++++++++++++++++++++++++++++
drivers/isdn/gigaset/Makefile | 6 ++++++
drivers/isdn/i4l/Kconfig | 1 +
5 files changed, 59 insertions(+)

--- linux-2.6.16-rc2/MAINTAINERS 2006-02-04 18:14:05.000000000 +0100
+++ linux-2.6.16-rc2-gig/MAINTAINERS 2006-02-04 18:37:17.000000000 +0100
@@ -1019,6 +1019,15 @@
W: http://www.kernel.org/pub/linux/utils/net/hdlc/
S: Maintained

+GIGASET ISDN DRIVERS
+P: Hansjoerg Lipp
+M: [email protected]
+P: Tilman Schmidt
+M: [email protected]
+L: [email protected]
+W: http://gigaset307x.sourceforge.net/
+S: Maintained
+
HARDWARE MONITORING
P: Jean Delvare
M: [email protected]
--- linux-2.6.16-rc2/drivers/isdn/Makefile 2005-07-16 20:04:00.000000000 +0200
+++ linux-2.6.16-rc2-gig/drivers/isdn/Makefile 2006-02-04 18:37:51.000000000 +0100
@@ -13,3 +13,4 @@
obj-$(CONFIG_ISDN_DRV_LOOP) += isdnloop/
obj-$(CONFIG_ISDN_DRV_ACT2000) += act2000/
obj-$(CONFIG_HYSDN) += hysdn/
+obj-$(CONFIG_ISDN_DRV_GIGASET) += gigaset/
--- linux-2.6.16-rc2/drivers/isdn/i4l/Kconfig 2005-07-16 20:04:00.000000000 +0200
+++ linux-2.6.16-rc2-gig/drivers/isdn/i4l/Kconfig 2006-02-04 18:38:22.000000000 +0100
@@ -139,3 +139,4 @@

endmenu

+source "drivers/isdn/gigaset/Kconfig"
--- linux-2.6.16-rc2/drivers/isdn/gigaset/Kconfig 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/Kconfig 2005-12-11 13:20:50.000000000 +0100
@@ -0,0 +1,42 @@
+menu "Siemens Gigaset"
+ depends on ISDN_I4L
+
+config ISDN_DRV_GIGASET
+ tristate "Siemens Gigaset support (isdn)"
+ depends on ISDN_I4L && m
+# depends on ISDN_I4L && MODULES
+ help
+ Say m here if you have a Gigaset or Sinus isdn device.
+
+if ISDN_DRV_GIGASET!=n
+
+config GIGASET_BASE
+ tristate "Gigaset base station support"
+ depends on ISDN_DRV_GIGASET && USB
+ help
+ Say m here if you need to communicate with the base
+ directly via USB.
+
+config GIGASET_M105
+ tristate "Gigaset M105 support"
+ depends on ISDN_DRV_GIGASET && USB
+ help
+ Say m here if you need the driver for the Gigaset M105 device.
+
+config GIGASET_DEBUG
+ bool "Gigaset debugging"
+ help
+ This enables debugging code in the Gigaset drivers.
+ If in doubt, say yes.
+
+config GIGASET_UNDOCREQ
+ bool "Support for undocumented USB requests"
+ help
+ This enables support for USB requests we only know from
+ reverse engineering (currently M105 only). If you need
+ features like configuration mode of M105, say yes. If you
+ care about your device, say no.
+
+endif
+
+endmenu
--- linux-2.6.16-rc2/drivers/isdn/gigaset/Makefile 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/Makefile 2005-12-11 13:20:54.000000000 +0100
@@ -0,0 +1,6 @@
+gigaset-y := common.o interface.o proc.o ev-layer.o i4l.o
+usb_gigaset-y := usb-gigaset.o asyncdata.o
+bas_gigaset-y := bas-gigaset.o isocdata.o
+
+obj-$(CONFIG_GIGASET_M105) += usb_gigaset.o gigaset.o
+obj-$(CONFIG_GIGASET_BASE) += bas_gigaset.o gigaset.o

2006-02-11 15:02:24

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 2/9] isdn4linux: Siemens Gigaset drivers - common module

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch adds the common include file for the Siemens Gigaset drivers,
providing definitions used by all of the Gigaset ISDN driver source files.
It also adds the main source file of the gigaset module which manages
common functions not specific to the type of connection to the device.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

drivers/isdn/gigaset/common.c | 1203 +++++++++++++++++++++++++++++++++++++++++
drivers/isdn/gigaset/gigaset.h | 938 +++++++++++++++++++++++++++++++
2 files changed, 2141 insertions(+)

--- linux-2.6.16-rc2/drivers/isdn/gigaset/gigaset.h 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/gigaset.h 2006-02-11 15:20:26.000000000 +0100
@@ -0,0 +1,938 @@
+/* Siemens Gigaset 307x driver
+ * Common header file for all connection variants
+ *
+ * Written by Stefan Eilers <[email protected]>
+ * and Hansjoerg Lipp <[email protected]>
+ *
+ * Version: $Id: gigaset.h,v 1.97.4.26 2006/02/04 18:28:16 hjlipp Exp $
+ * ===========================================================================
+ */
+
+#ifndef GIGASET_H
+#define GIGASET_H
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <asm/atomic.h>
+#include <linux/spinlock.h>
+#include <linux/isdnif.h>
+#include <linux/usb.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/ppp_defs.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/list.h>
+
+#define GIG_VERSION {0,5,0,0}
+#define GIG_COMPAT {0,4,0,0}
+
+#define MAX_REC_PARAMS 10 /* Max. number of params in response string */
+#define MAX_RESP_SIZE 512 /* Max. size of a response string */
+#define HW_HDR_LEN 2 /* Header size used to store ack info */
+
+#define MAX_EVENTS 64 /* size of event queue */
+
+#define RBUFSIZE 8192
+#define SBUFSIZE 4096 /* sk_buff payload size */
+
+#define MAX_BUF_SIZE (SBUFSIZE - 2) /* Max. size of a data packet from LL */
+#define TRANSBUFSIZE 768 /* bytes per skb for transparent receive */
+
+/* compile time options */
+#define GIG_MAJOR 0
+
+#define GIG_MAYINITONDIAL
+#define GIG_RETRYCID
+#define GIG_X75
+
+#define MAX_TIMER_INDEX 1000
+#define MAX_SEQ_INDEX 1000
+
+#define GIG_TICK (HZ / 10)
+
+/* timeout values (unit: 1 sec) */
+#define INIT_TIMEOUT 1
+
+/* timeout values (unit: 0.1 sec) */
+#define RING_TIMEOUT 3 /* for additional parameters to RING */
+#define BAS_TIMEOUT 20 /* for response to Base USB ops */
+#define ATRDY_TIMEOUT 3 /* for HD_READY_SEND_ATDATA */
+
+#define BAS_RETRY 3 /* max. retries for base USB ops */
+
+#define MAXACT 3
+
+#define IFNULL(a) if (unlikely(!(a)))
+#define IFNULLRET(a) if (unlikely(!(a))) {err("%s==NULL at %s:%d!", #a, __FILE__, __LINE__); return; }
+#define IFNULLRETVAL(a,b) if (unlikely(!(a))) {err("%s==NULL at %s:%d!", #a, __FILE__, __LINE__); return (b); }
+#define IFNULLCONT(a) if (unlikely(!(a))) {err("%s==NULL at %s:%d!", #a, __FILE__, __LINE__); continue; }
+#define IFNULLGOTO(a,b) if (unlikely(!(a))) {err("%s==NULL at %s:%d!", #a, __FILE__, __LINE__); goto b; }
+
+extern int gigaset_debuglevel; /* "needs" cast to (enum debuglevel) */
+
+/* any combination of these can be given with the 'debug=' parameter to insmod, e.g.
+ * 'insmod usb_gigaset.o debug=0x2c' will set DEBUG_OPEN, DEBUG_CMD and DEBUG_INTR. */
+enum debuglevel { /* up to 24 bits (atomic_t) */
+ DEBUG_REG = 0x0002, /* serial port I/O register operations */
+ DEBUG_OPEN = 0x0004, /* open/close serial port */
+ DEBUG_INTR = 0x0008, /* interrupt processing */
+ DEBUG_INTR_DUMP = 0x0010, /* Activating hexdump debug output on interrupt
+ requests, not available as run-time option */
+ DEBUG_CMD = 0x00020, /* sent/received LL commands */
+ DEBUG_STREAM = 0x00040, /* application data stream I/O events */
+ DEBUG_STREAM_DUMP = 0x00080, /* application data stream content */
+ DEBUG_LLDATA = 0x00100, /* sent/received LL data */
+ DEBUG_INTR_0 = 0x00200, /* serial port output interrupt processing */
+ DEBUG_DRIVER = 0x00400, /* driver structure */
+ DEBUG_HDLC = 0x00800, /* M10x HDLC processing */
+ DEBUG_WRITE = 0x01000, /* M105 data write */
+ DEBUG_TRANSCMD = 0x02000, /*AT-COMMANDS+RESPONSES*/
+ DEBUG_MCMD = 0x04000, /*COMMANDS THAT ARE SENT VERY OFTEN*/
+ DEBUG_INIT = 0x08000, /* (de)allocation+initialization of data structures */
+ DEBUG_LOCK = 0x10000, /* semaphore operations */
+ DEBUG_OUTPUT = 0x20000, /* output to device */
+ DEBUG_ISO = 0x40000, /* isochronous transfers */
+ DEBUG_IF = 0x80000, /* character device operations */
+ DEBUG_USBREQ = 0x100000, /* USB communication (except payload data) */
+ DEBUG_LOCKCMD = 0x200000, /* AT commands and responses when MS_LOCKED */
+
+ DEBUG_ANY = 0x3fffff, /* print message if any of the others is activated */
+};
+
+#ifdef CONFIG_GIGASET_DEBUG
+#define DEBUG_DEFAULT (DEBUG_INIT | DEBUG_TRANSCMD | DEBUG_CMD | DEBUG_USBREQ)
+//#define DEBUG_DEFAULT (DEBUG_LOCK | DEBUG_INIT | DEBUG_TRANSCMD | DEBUG_CMD | DEBUF_IF | DEBUG_DRIVER | DEBUG_OUTPUT | DEBUG_INTR)
+#else
+#define DEBUG_DEFAULT 0
+#endif
+
+/* redefine syslog macros to prepend module name instead of entire source path */
+/* The space before the comma in ", ##" is needed by gcc 2.95 */
+#undef info
+#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
+
+#undef notice
+#define notice(format, arg...) printk(KERN_NOTICE "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
+
+#undef warn
+#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
+
+#undef err
+#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
+
+#undef dbg
+#ifdef CONFIG_GIGASET_DEBUG
+#define dbg(level, format, arg...) do { if (unlikely(((enum debuglevel)gigaset_debuglevel) & (level))) \
+ printk(KERN_DEBUG "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg); } while (0)
+#else
+#define dbg(level, format, arg...) do {} while (0)
+#endif
+
+void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg,
+ size_t len, const unsigned char *buf, int from_user);
+
+/* connection state */
+#define ZSAU_NONE 0
+#define ZSAU_DISCONNECT_IND 4
+#define ZSAU_OUTGOING_CALL_PROCEEDING 1
+#define ZSAU_PROCEEDING 1
+#define ZSAU_CALL_DELIVERED 2
+#define ZSAU_ACTIVE 3
+#define ZSAU_NULL 5
+#define ZSAU_DISCONNECT_REQ 6
+#define ZSAU_UNKNOWN -1
+
+/* USB control transfer requests */
+#define OUT_VENDOR_REQ (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT)
+#define IN_VENDOR_REQ (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT)
+
+/* int-in-events 3070 */
+#define HD_B1_FLOW_CONTROL 0x80
+#define HD_B2_FLOW_CONTROL 0x81
+#define HD_RECEIVEATDATA_ACK (0x35) // 3070 // att: HD_RECEIVE>>AT<<DATA_ACK
+#define HD_READY_SEND_ATDATA (0x36) // 3070
+#define HD_OPEN_ATCHANNEL_ACK (0x37) // 3070
+#define HD_CLOSE_ATCHANNEL_ACK (0x38) // 3070
+#define HD_DEVICE_INIT_OK (0x11) // ISurf USB + 3070
+#define HD_OPEN_B1CHANNEL_ACK (0x51) // ISurf USB + 3070
+#define HD_OPEN_B2CHANNEL_ACK (0x52) // ISurf USB + 3070
+#define HD_CLOSE_B1CHANNEL_ACK (0x53) // ISurf USB + 3070
+#define HD_CLOSE_B2CHANNEL_ACK (0x54) // ISurf USB + 3070
+// Powermangment
+#define HD_SUSPEND_END (0x61) // ISurf USB
+// Configuration
+#define HD_RESET_INTERRUPT_PIPE_ACK (0xFF) // ISurf USB + 3070
+
+/* control requests 3070 */
+#define HD_OPEN_B1CHANNEL (0x23) // ISurf USB + 3070
+#define HD_CLOSE_B1CHANNEL (0x24) // ISurf USB + 3070
+#define HD_OPEN_B2CHANNEL (0x25) // ISurf USB + 3070
+#define HD_CLOSE_B2CHANNEL (0x26) // ISurf USB + 3070
+#define HD_RESET_INTERRUPT_PIPE (0x27) // ISurf USB + 3070
+#define HD_DEVICE_INIT_ACK (0x34) // ISurf USB + 3070
+#define HD_WRITE_ATMESSAGE (0x12) // 3070
+#define HD_READ_ATMESSAGE (0x13) // 3070
+#define HD_OPEN_ATCHANNEL (0x28) // 3070
+#define HD_CLOSE_ATCHANNEL (0x29) // 3070
+
+/* USB frames for isochronous transfer */
+#define BAS_FRAMETIME 1 /* number of milliseconds between frames */
+#define BAS_NUMFRAMES 8 /* number of frames per URB */
+#define BAS_MAXFRAME 16 /* allocated bytes per frame */
+#define BAS_NORMFRAME 8 /* send size without flow control */
+#define BAS_HIGHFRAME 10 /* " " with positive flow control */
+#define BAS_LOWFRAME 5 /* " " with negative flow control */
+#define BAS_CORRFRAMES 4 /* flow control multiplicator */
+
+#define BAS_INBUFSIZE (BAS_MAXFRAME * BAS_NUMFRAMES) /* size of isochronous input buffer per URB */
+#define BAS_OUTBUFSIZE 4096 /* size of common isochronous output buffer */
+#define BAS_OUTBUFPAD BAS_MAXFRAME /* size of pad area for isochronous output buffer */
+
+#define BAS_INURBS 3
+#define BAS_OUTURBS 3
+
+/* variable commands in struct bc_state */
+#define AT_ISO 0
+#define AT_DIAL 1
+#define AT_MSN 2
+#define AT_BC 3
+#define AT_PROTO 4
+#define AT_TYPE 5
+#define AT_HLC 6
+#define AT_NUM 7
+
+/* variables in struct at_state_t */
+#define VAR_ZSAU 0
+#define VAR_ZDLE 1
+#define VAR_ZVLS 2
+#define VAR_ZCTP 3
+#define VAR_NUM 4
+
+#define STR_NMBR 0
+#define STR_ZCPN 1
+#define STR_ZCON 2
+#define STR_ZBC 3
+#define STR_ZHLC 4
+#define STR_NUM 5
+
+#define EV_TIMEOUT -105
+#define EV_IF_VER -106
+#define EV_PROC_CIDMODE -107
+#define EV_SHUTDOWN -108
+#define EV_START -110
+#define EV_STOP -111
+#define EV_IF_LOCK -112
+#define EV_PROTO_L2 -113
+#define EV_ACCEPT -114
+#define EV_DIAL -115
+#define EV_HUP -116
+#define EV_BC_OPEN -117
+#define EV_BC_CLOSED -118
+
+/* input state */
+#define INS_command 0x0001
+#define INS_DLE_char 0x0002
+#define INS_byte_stuff 0x0004
+#define INS_have_data 0x0008
+#define INS_skip_frame 0x0010
+#define INS_DLE_command 0x0020
+#define INS_flag_hunt 0x0040
+
+/* channel state */
+#define CHS_D_UP 0x01
+#define CHS_B_UP 0x02
+#define CHS_NOTIFY_LL 0x04
+
+#define ICALL_REJECT 0
+#define ICALL_ACCEPT 1
+#define ICALL_IGNORE 2
+
+/* device state */
+#define MS_UNINITIALIZED 0
+#define MS_INIT 1
+#define MS_LOCKED 2
+#define MS_SHUTDOWN 3
+#define MS_RECOVER 4
+#define MS_READY 5
+
+/* mode */
+#define M_UNKNOWN 0
+#define M_CONFIG 1
+#define M_UNIMODEM 2
+#define M_CID 3
+
+/* start mode */
+#define SM_LOCKED 0
+#define SM_ISDN 1 /* default */
+
+struct gigaset_ops;
+struct gigaset_driver;
+
+struct usb_cardstate;
+struct ser_cardstate;
+struct bas_cardstate;
+
+struct bc_state;
+struct usb_bc_state;
+struct ser_bc_state;
+struct bas_bc_state;
+
+struct reply_t {
+ int resp_code; /* RSP_XXXX */
+ int min_ConState; /* <0 => ignore */
+ int max_ConState; /* <0 => ignore */
+ int parameter; /* e.g. ZSAU_XXXX <0: ignore*/
+ int new_ConState; /* <0 => ignore */
+ int timeout; /* >0 => *HZ; <=0 => TOUT_XXXX*/
+ int action[MAXACT]; /* ACT_XXXX */
+ char *command; /* NULL==none */
+};
+
+extern struct reply_t gigaset_tab_cid_m10x[];
+extern struct reply_t gigaset_tab_nocid_m10x[];
+
+struct inbuf_t {
+ unsigned char *rcvbuf; /* usb-gigaset receive buffer */
+ struct bc_state *bcs;
+ struct cardstate *cs;
+ int inputstate;
+
+ atomic_t head, tail;
+ unsigned char data[RBUFSIZE];
+};
+
+/* isochronous write buffer structure
+ * circular buffer with pad area for extraction of complete USB frames
+ * - data[read..nextread-1] is valid data already submitted to the USB subsystem
+ * - data[nextread..write-1] is valid data yet to be sent
+ * - data[write] is the next byte to write to
+ * - in byte-oriented L2 procotols, it is completely free
+ * - in bit-oriented L2 procotols, it may contain a partial byte of valid data
+ * - data[write+1..read-1] is free
+ * - wbits is the number of valid data bits in data[write], starting at the LSB
+ * - writesem is the semaphore for writing to the buffer:
+ * if writesem <= 0, data[write..read-1] is currently being written to
+ * - idle contains the byte value to repeat when the end of valid data is
+ * reached; if nextread==write (buffer contains no data to send), either the
+ * BAS_OUTBUFPAD bytes immediately before data[write] (if write>=BAS_OUTBUFPAD)
+ * or those of the pad area (if write<BAS_OUTBUFPAD) are also filled with that
+ * value
+ * - optionally, the following statistics on the buffer's usage can be collected:
+ * maxfill: maximum number of bytes occupied
+ * idlefills: number of times a frame of idle bytes is prepared
+ * emptygets: number of times the buffer was empty when a data frame was requested
+ * backtoback: number of times two data packets were entered into the buffer
+ * without intervening idle flags
+ * nakedback: set if no idle flags have been inserted since the last data packet
+ */
+struct isowbuf_t {
+ atomic_t read;
+ atomic_t nextread;
+ atomic_t write;
+ atomic_t writesem;
+ int wbits;
+ unsigned char data[BAS_OUTBUFSIZE + BAS_OUTBUFPAD];
+ unsigned char idle;
+};
+
+/* isochronous write URB context structure
+ * data to be stored along with the URB and retrieved when it is returned
+ * as completed by the USB subsystem
+ * - urb: pointer to the URB itself
+ * - bcs: pointer to the B Channel control structure
+ * - limit: end of write buffer area covered by this URB
+ */
+struct isow_urbctx_t {
+ struct urb *urb;
+ struct bc_state *bcs;
+ int limit;
+};
+
+/* AT state structure
+ * data associated with the state of an ISDN connection, whether or not
+ * it is currently assigned a B channel
+ */
+struct at_state_t {
+ struct list_head list;
+ int waiting;
+ int getstring;
+ atomic_t timer_index;
+ unsigned long timer_expires;
+ int timer_active;
+ unsigned int ConState; /* State of connection */
+ struct reply_t *replystruct;
+ int cid;
+ int int_var[VAR_NUM]; /* see VAR_XXXX */
+ char *str_var[STR_NUM]; /* see STR_XXXX */
+ unsigned pending_commands; /* see PC_XXXX */
+ atomic_t seq_index;
+
+ struct cardstate *cs;
+ struct bc_state *bcs;
+};
+
+struct resp_type_t {
+ unsigned char *response;
+ int resp_code; /* RSP_XXXX */
+ int type; /* RT_XXXX */
+};
+
+struct prot_skb {
+ atomic_t empty;
+ struct semaphore *sem;
+ struct sk_buff *skb;
+};
+
+struct event_t {
+ int type;
+ void *ptr, *arg;
+ int parameter;
+ int cid;
+ struct at_state_t *at_state;
+};
+
+/* This buffer holds all information about the used B-Channel */
+struct bc_state {
+ struct sk_buff *tx_skb; /* Current transfer buffer to modem */
+ struct sk_buff_head squeue; /* B-Channel send Queue */
+
+ /* Variables for debugging .. */
+ int corrupted; /* Counter for corrupted packages */
+ int trans_down; /* Counter of packages (downstream) */
+ int trans_up; /* Counter of packages (upstream) */
+
+ struct at_state_t at_state;
+ unsigned long rcvbytes;
+
+ __u16 fcs;
+ struct sk_buff *skb;
+ int inputstate; /* see INS_XXXX */
+
+ int channel;
+
+ struct cardstate *cs;
+
+ unsigned chstate; /* bitmap (CHS_*) */
+ int ignore;
+ unsigned proto2; /* Layer 2 protocol (ISDN_PROTO_L2_*) */
+ char *commands[AT_NUM]; /* see AT_XXXX */
+
+#ifdef CONFIG_GIGASET_DEBUG
+ int emptycount;
+#endif
+ int busy;
+ int use_count;
+
+ /* hardware drivers */
+ union {
+ struct ser_bc_state *ser; /* private data of serial hardware driver */
+ struct usb_bc_state *usb; /* private data of usb hardware driver */
+ struct bas_bc_state *bas;
+ } hw;
+};
+
+struct cardstate {
+ struct gigaset_driver *driver;
+ unsigned minor_index;
+
+ const struct gigaset_ops *ops;
+
+ /* Stuff to handle communication */
+ //wait_queue_head_t initwait;
+ wait_queue_head_t waitqueue;
+ int waiting;
+ atomic_t mode; /* see M_XXXX */
+ atomic_t mstate; /* Modem state: see MS_XXXX */
+ /* only changed by the event layer */
+ int cmd_result;
+
+ int channels;
+ struct bc_state *bcs; /* Array of struct bc_state */
+
+ int onechannel; /* data and commands transmitted in one stream (M10x) */
+
+ spinlock_t lock;
+ struct at_state_t at_state; /* at_state_t for cid == 0 */
+ struct list_head temp_at_states; /* list of temporary "struct at_state_t"s without B channel */
+
+ struct inbuf_t *inbuf;
+
+ struct cmdbuf_t *cmdbuf, *lastcmdbuf;
+ spinlock_t cmdlock;
+ unsigned curlen, cmdbytes;
+
+ unsigned open_count;
+ struct tty_struct *tty;
+ struct tasklet_struct if_wake_tasklet;
+ unsigned control_state;
+
+ unsigned fwver[4];
+ int gotfwver;
+
+ atomic_t running; /* !=0 if events are handled */
+ atomic_t connected; /* !=0 if hardware is connected */
+
+ atomic_t cidmode;
+
+ int myid; /* id for communication with LL */
+ isdn_if iif;
+
+ struct reply_t *tabnocid;
+ struct reply_t *tabcid;
+ int cs_init;
+ int ignoreframes; /* frames to ignore after setting up the B channel */
+ struct semaphore sem; /* locks this structure: */
+ /* connected is not changed, */
+ /* hardware_up is not changed, */
+ /* MState is not changed to or from MS_LOCKED */
+
+ struct timer_list timer;
+ int retry_count;
+ int dle; /* !=0 if modem commands/responses are dle encoded */
+ int cur_at_seq; /* sequence of AT commands being processed */
+ int curchannel; /* channel, those commands are meant for */
+ atomic_t commands_pending; /* flag(s) in xxx.commands_pending have been set */
+ struct tasklet_struct event_tasklet; /* tasklet for serializing AT commands. Scheduled
+ * -> for modem reponses (and incomming data for M10x)
+ * -> on timeout
+ * -> after setting bits in xxx.at_state.pending_command
+ * (e.g. command from LL) */
+ struct tasklet_struct write_tasklet; /* tasklet for serial output
+ * (not used in base driver) */
+
+ /* event queue */
+ struct event_t events[MAX_EVENTS];
+ atomic_t ev_tail, ev_head;
+ spinlock_t ev_lock;
+
+ /* current modem response */
+ unsigned char respdata[MAX_RESP_SIZE];
+ unsigned cbytes;
+
+ /* hardware drivers */
+ union {
+ struct usb_cardstate *usb; /* private data of USB hardware driver */
+ struct ser_cardstate *ser; /* private data of serial hardware driver */
+ struct bas_cardstate *bas; /* private data of base hardware driver */
+ } hw;
+};
+
+struct gigaset_driver {
+ struct list_head list;
+ spinlock_t lock; /* locks minor tables and blocked */
+ //struct semaphore sem; /* locks this structure */
+ struct tty_driver *tty;
+ unsigned have_tty;
+ unsigned minor;
+ unsigned minors;
+ struct cardstate *cs;
+ unsigned *flags;
+ int blocked;
+
+ const struct gigaset_ops *ops;
+ struct module *owner;
+};
+
+struct cmdbuf_t {
+ struct cmdbuf_t *next, *prev;
+ int len, offset;
+ struct tasklet_struct *wake_tasklet;
+ unsigned char buf[0];
+};
+
+struct bas_bc_state {
+ /* isochronous output state */
+ atomic_t running;
+ atomic_t corrbytes;
+ spinlock_t isooutlock;
+ struct isow_urbctx_t isoouturbs[BAS_OUTURBS];
+ struct isow_urbctx_t *isooutdone, *isooutfree, *isooutovfl;
+ struct isowbuf_t *isooutbuf;
+ unsigned numsub; /* submitted URB counter (for diagnostic messages only) */
+ struct tasklet_struct sent_tasklet;
+
+ /* isochronous input state */
+ spinlock_t isoinlock;
+ struct urb *isoinurbs[BAS_INURBS];
+ unsigned char isoinbuf[BAS_INBUFSIZE * BAS_INURBS];
+ struct urb *isoindone; /* completed isoc read URB */
+ int loststatus; /* status of dropped URB */
+ unsigned isoinlost; /* number of bytes lost */
+ /* state of bit unstuffing algorithm (in addition to BC_state.inputstate) */
+ unsigned seqlen; /* number of '1' bits not yet unstuffed */
+ unsigned inbyte, inbits; /* collected bits for next byte */
+ /* statistics */
+ unsigned goodbytes; /* bytes correctly received */
+ unsigned alignerrs; /* frames with incomplete byte at end */
+ unsigned fcserrs; /* FCS errors */
+ unsigned frameerrs; /* framing errors */
+ unsigned giants; /* long frames */
+ unsigned runts; /* short frames */
+ unsigned aborts; /* HDLC aborts */
+ unsigned shared0s; /* '0' bits shared between flags */
+ unsigned stolen0s; /* '0' stuff bits also serving as leading flag bits */
+ struct tasklet_struct rcvd_tasklet;
+};
+
+struct gigaset_ops {
+ /* Called from ev-layer.c/interface.c for sending AT commands to the device */
+ int (*write_cmd)(struct cardstate *cs,
+ const unsigned char *buf, int len,
+ struct tasklet_struct *wake_tasklet);
+
+ /* Called from interface.c for additional device control */
+ int (*write_room)(struct cardstate *cs);
+ int (*chars_in_buffer)(struct cardstate *cs);
+ int (*brkchars)(struct cardstate *cs, const unsigned char buf[6]);
+
+ /* Called from ev-layer.c after setting up connection
+ * Should call gigaset_bchannel_up(), when finished. */
+ int (*init_bchannel)(struct bc_state *bcs);
+
+ /* Called from ev-layer.c after hanging up
+ * Should call gigaset_bchannel_down(), when finished. */
+ int (*close_bchannel)(struct bc_state *bcs);
+
+ /* Called by gigaset_initcs() for setting up bcs->hw.xxx */
+ int (*initbcshw)(struct bc_state *bcs);
+
+ /* Called by gigaset_freecs() for freeing bcs->hw.xxx */
+ int (*freebcshw)(struct bc_state *bcs);
+
+ /* Called by gigaset_stop() or gigaset_bchannel_down() for resetting bcs->hw.xxx */
+ void (*reinitbcshw)(struct bc_state *bcs);
+
+ /* Called by gigaset_initcs() for setting up cs->hw.xxx */
+ int (*initcshw)(struct cardstate *cs);
+
+ /* Called by gigaset_freecs() for freeing cs->hw.xxx */
+ void (*freecshw)(struct cardstate *cs);
+
+ ///* Called by gigaset_stop() for killing URBs, shutting down the device, ...
+ // hardwareup: ==0: don't try to shut down the device, hardware is really not accessible
+ // !=0: hardware still up */
+ //void (*stophw)(struct cardstate *cs, int hardwareup);
+
+ /* Called from common.c/interface.c for additional serial port control */
+ int (*set_modem_ctrl)(struct cardstate *cs, unsigned old_state, unsigned new_state);
+ int (*baud_rate)(struct cardstate *cs, unsigned cflag);
+ int (*set_line_ctrl)(struct cardstate *cs, unsigned cflag);
+
+ /* Called from i4l.c to put an skb into the send-queue. */
+ int (*send_skb)(struct bc_state *bcs, struct sk_buff *skb);
+
+ /* Called from ev-layer.c to process a block of data
+ * received through the common/control channel. */
+ void (*handle_input)(struct inbuf_t *inbuf);
+
+};
+
+/* = Common structures and definitions ======================================= */
+
+/* Parser states for DLE-Event:
+ * <DLE-EVENT>: <DLE_FLAG> "X" <EVENT> <DLE_FLAG> "."
+ * <DLE_FLAG>: 0x10
+ * <EVENT>: ((a-z)* | (A-Z)* | (0-10)*)+
+ */
+#define DLE_FLAG 0x10
+
+/* ===========================================================================
+ * Functions implemented in asyncdata.c
+ */
+
+/* Called from i4l.c to put an skb into the send-queue.
+ * After sending gigaset_skb_sent() should be called. */
+int gigaset_m10x_send_skb(struct bc_state *bcs, struct sk_buff *skb);
+
+/* Called from ev-layer.c to process a block of data
+ * received through the common/control channel. */
+void gigaset_m10x_input(struct inbuf_t *inbuf);
+
+/* ===========================================================================
+ * Functions implemented in isocdata.c
+ */
+
+/* Called from i4l.c to put an skb into the send-queue.
+ * After sending gigaset_skb_sent() should be called. */
+int gigaset_isoc_send_skb(struct bc_state *bcs, struct sk_buff *skb);
+
+/* Called from ev-layer.c to process a block of data
+ * received through the common/control channel. */
+void gigaset_isoc_input(struct inbuf_t *inbuf);
+
+/* Called from bas-gigaset.c to process a block of data
+ * received through the isochronous channel */
+void gigaset_isoc_receive(unsigned char *src, unsigned count, struct bc_state *bcs);
+
+/* Called from bas-gigaset.c to put a block of data
+ * into the isochronous output buffer */
+int gigaset_isoc_buildframe(struct bc_state *bcs, unsigned char *in, int len);
+
+/* Called from bas-gigaset.c to initialize the isochronous output buffer */
+void gigaset_isowbuf_init(struct isowbuf_t *iwb, unsigned char idle);
+
+/* Called from bas-gigaset.c to retrieve a block of bytes for sending */
+int gigaset_isowbuf_getbytes(struct isowbuf_t *iwb, int size);
+
+/* ===========================================================================
+ * Functions implemented in i4l.c/gigaset.h
+ */
+
+/* Called by gigaset_initcs() for setting up with the isdn4linux subsystem */
+int gigaset_register_to_LL(struct cardstate *cs, const char *isdnid);
+
+/* Called from xxx-gigaset.c to indicate completion of sending an skb */
+void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb);
+
+/* Called from common.c/ev-layer.c to indicate events relevant to the LL */
+int gigaset_isdn_icall(struct at_state_t *at_state);
+int gigaset_isdn_setup_accept(struct at_state_t *at_state);
+int gigaset_isdn_setup_dial(struct at_state_t *at_state, void *data);
+
+void gigaset_i4l_cmd(struct cardstate *cs, int cmd);
+void gigaset_i4l_channel_cmd(struct bc_state *bcs, int cmd);
+
+
+static inline void gigaset_isdn_rcv_err(struct bc_state *bcs)
+{
+ isdn_ctrl response;
+
+ /* error -> LL */
+ dbg(DEBUG_CMD, "sending L1ERR");
+ response.driver = bcs->cs->myid;
+ response.command = ISDN_STAT_L1ERR;
+ response.arg = bcs->channel;
+ response.parm.errcode = ISDN_STAT_L1ERR_RECV;
+ bcs->cs->iif.statcallb(&response);
+}
+
+/* ===========================================================================
+ * Functions implemented in ev-layer.c
+ */
+
+/* tasklet called from common.c to process queued events */
+void gigaset_handle_event(unsigned long data);
+
+/* called from isocdata.c / asyncdata.c
+ * when a complete modem response line has been received */
+void gigaset_handle_modem_response(struct cardstate *cs);
+
+/* ===========================================================================
+ * Functions implemented in proc.c
+ */
+
+/* initialize sysfs for device */
+void gigaset_init_dev_sysfs(struct usb_interface *interface);
+void gigaset_free_dev_sysfs(struct usb_interface *interface);
+
+/* ===========================================================================
+ * Functions implemented in common.c/gigaset.h
+ */
+
+void gigaset_bcs_reinit(struct bc_state *bcs);
+void gigaset_at_init(struct at_state_t *at_state, struct bc_state *bcs,
+ struct cardstate *cs, int cid);
+int gigaset_get_channel(struct bc_state *bcs);
+void gigaset_free_channel(struct bc_state *bcs);
+int gigaset_get_channels(struct cardstate *cs);
+void gigaset_free_channels(struct cardstate *cs);
+void gigaset_block_channels(struct cardstate *cs);
+
+/* Allocate and initialize driver structure. */
+struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors,
+ const char *procname,
+ const char *devname,
+ const char *devfsname,
+ const struct gigaset_ops *ops,
+ struct module *owner);
+
+/* Deallocate driver structure. */
+void gigaset_freedriver(struct gigaset_driver *drv);
+void gigaset_debugdrivers(void);
+struct cardstate *gigaset_get_cs_by_minor(unsigned minor);
+struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty);
+struct cardstate *gigaset_get_cs_by_id(int id);
+
+/* For drivers without fixed assignment device<->cardstate (usb) */
+struct cardstate *gigaset_getunassignedcs(struct gigaset_driver *drv);
+void gigaset_unassign(struct cardstate *cs);
+void gigaset_blockdriver(struct gigaset_driver *drv);
+
+/* Allocate and initialize card state. Calls hardware dependent gigaset_init[b]cs(). */
+struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels,
+ int onechannel, int ignoreframes,
+ int cidmode, const char *modulename);
+
+/* Free card state. Calls hardware dependent gigaset_free[b]cs(). */
+void gigaset_freecs(struct cardstate *cs);
+
+/* Tell common.c that hardware and driver are ready. */
+int gigaset_start(struct cardstate *cs);
+
+/* Tell common.c that the device is not present any more. */
+void gigaset_stop(struct cardstate *cs);
+
+/* Tell common.c that the driver is being unloaded. */
+void gigaset_shutdown(struct cardstate *cs);
+
+/* Tell common.c that an skb has been sent. */
+void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb);
+
+/* Append event to the queue.
+ * Returns NULL on failure or a pointer to the event on success.
+ * ptr must be kmalloc()ed (and not be freed by the caller).
+ */
+struct event_t *gigaset_add_event(struct cardstate *cs,
+ struct at_state_t *at_state, int type,
+ void *ptr, int parameter, void *arg);
+
+/* Called on CONFIG1 command from frontend. */
+int gigaset_enterconfigmode(struct cardstate *cs); //0: success <0: errorcode
+
+/* cs->lock must not be locked */
+static inline void gigaset_schedule_event(struct cardstate *cs)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&cs->lock, flags);
+ if (atomic_read(&cs->running))
+ tasklet_schedule(&cs->event_tasklet);
+ spin_unlock_irqrestore(&cs->lock, flags);
+}
+
+/* Tell common.c that B channel has been closed. */
+/* cs->lock must not be locked */
+static inline void gigaset_bchannel_down(struct bc_state *bcs)
+{
+ gigaset_add_event(bcs->cs, &bcs->at_state, EV_BC_CLOSED, NULL, 0, NULL);
+
+ dbg(DEBUG_CMD, "scheduling BC_CLOSED");
+ gigaset_schedule_event(bcs->cs);
+}
+
+/* Tell common.c that B channel has been opened. */
+/* cs->lock must not be locked */
+static inline void gigaset_bchannel_up(struct bc_state *bcs)
+{
+ gigaset_add_event(bcs->cs, &bcs->at_state, EV_BC_OPEN, NULL, 0, NULL);
+
+ dbg(DEBUG_CMD, "scheduling BC_OPEN");
+ gigaset_schedule_event(bcs->cs);
+}
+
+/* handling routines for sk_buff */
+/* ============================= */
+
+/* private version of __skb_put()
+ * append 'len' bytes to the content of 'skb', already knowing that the
+ * existing buffer can accomodate them
+ * returns a pointer to the location where the new bytes should be copied to
+ * This function does not take any locks so it must be called with the
+ * appropriate locks held only.
+ */
+static inline unsigned char *gigaset_skb_put_quick(struct sk_buff *skb,
+ unsigned int len)
+{
+ unsigned char *tmp = skb->tail;
+ /*SKB_LINEAR_ASSERT(skb);*/ /* not needed here */
+ skb->tail += len;
+ skb->len += len;
+ return tmp;
+}
+
+/* pass received skb to LL
+ * Warning: skb must not be accessed anymore!
+ */
+static inline void gigaset_rcv_skb(struct sk_buff *skb,
+ struct cardstate *cs,
+ struct bc_state *bcs)
+{
+ cs->iif.rcvcallb_skb(cs->myid, bcs->channel, skb);
+ bcs->trans_down++;
+}
+
+/* handle reception of corrupted skb
+ * Warning: skb must not be accessed anymore!
+ */
+static inline void gigaset_rcv_error(struct sk_buff *procskb,
+ struct cardstate *cs,
+ struct bc_state *bcs)
+{
+ if (procskb)
+ dev_kfree_skb(procskb);
+
+ if (bcs->ignore)
+ --bcs->ignore;
+ else {
+ ++bcs->corrupted;
+ gigaset_isdn_rcv_err(bcs);
+ }
+}
+
+
+/* bitwise byte inversion table */
+extern __u8 gigaset_invtab[]; /* in common.c */
+
+
+/* append received bytes to inbuf */
+static inline int gigaset_fill_inbuf(struct inbuf_t *inbuf,
+ const unsigned char *src,
+ unsigned numbytes)
+{
+ unsigned n, head, tail, bytesleft;
+
+ dbg(DEBUG_INTR, "received %u bytes", numbytes);
+
+ if (!numbytes)
+ return 0;
+
+ bytesleft = numbytes;
+ tail = atomic_read(&inbuf->tail);
+ head = atomic_read(&inbuf->head);
+ dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);
+
+ while (bytesleft) {
+ if (head > tail)
+ n = head - 1 - tail;
+ else if (head == 0)
+ n = (RBUFSIZE-1) - tail;
+ else
+ n = RBUFSIZE - tail;
+ if (!n) {
+ err("buffer overflow (%u bytes lost)", bytesleft);
+ break;
+ }
+ if (n > bytesleft)
+ n = bytesleft;
+ memcpy(inbuf->data + tail, src, n);
+ bytesleft -= n;
+ tail = (tail + n) % RBUFSIZE;
+ src += n;
+ }
+ dbg(DEBUG_INTR, "setting tail to %u", tail);
+ atomic_set(&inbuf->tail, tail);
+ return numbytes != bytesleft;
+}
+
+/* ===========================================================================
+ * Functions implemented in interface.c
+ */
+
+/* initialize interface */
+void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname,
+ const char *devname, const char *devfsname);
+/* release interface */
+void gigaset_if_freedriver(struct gigaset_driver *drv);
+/* add minor */
+void gigaset_if_init(struct cardstate *cs);
+/* remove minor */
+void gigaset_if_free(struct cardstate *cs);
+/* device received data */
+void gigaset_if_receive(struct cardstate *cs,
+ unsigned char *buffer, size_t len);
+
+#endif
--- linux-2.6.16-rc2/drivers/isdn/gigaset/common.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/common.c 2006-02-11 15:20:26.000000000 +0100
@@ -0,0 +1,1203 @@
+/*
+ * Stuff used by all variants of the driver
+ *
+ * Copyright (c) 2001 by Stefan Eilers <[email protected]>,
+ * Hansjoerg Lipp <[email protected]>,
+ * Tilman Schmidt <[email protected]>.
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: common.c,v 1.104.4.22 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/ctype.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+/* Version Information */
+#define DRIVER_AUTHOR "Hansjoerg Lipp <[email protected]>, Tilman Schmidt <[email protected]>, Stefan Eilers <[email protected]>"
+#define DRIVER_DESC "Driver for Gigaset 307x"
+
+/* Module parameters */
+int gigaset_debuglevel = DEBUG_DEFAULT;
+EXPORT_SYMBOL_GPL(gigaset_debuglevel);
+module_param_named(debug, gigaset_debuglevel, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(debug, "debug level");
+
+/*======================================================================
+ Prototypes of internal functions
+ */
+
+//static void gigaset_process_response(int resp_code, int parameter,
+// struct at_state_t *at_state,
+// unsigned char ** pstring);
+static struct cardstate *alloc_cs(struct gigaset_driver *drv);
+static void free_cs(struct cardstate *cs);
+static void make_valid(struct cardstate *cs, unsigned mask);
+static void make_invalid(struct cardstate *cs, unsigned mask);
+
+#define VALID_MINOR 0x01
+#define VALID_ID 0x02
+#define ASSIGNED 0x04
+
+/* bitwise byte inversion table */
+__u8 gigaset_invtab[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
+};
+EXPORT_SYMBOL_GPL(gigaset_invtab);
+
+void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg,
+ size_t len, const unsigned char *buf, int from_user)
+{
+ unsigned char outbuf[80];
+ unsigned char inbuf[80 - 1];
+ size_t numin;
+ const unsigned char *in;
+ size_t space = sizeof outbuf - 1;
+ unsigned char *out = outbuf;
+
+ if (!from_user) {
+ in = buf;
+ numin = len;
+ } else {
+ numin = len < sizeof inbuf ? len : sizeof inbuf;
+ in = inbuf;
+ if (copy_from_user(inbuf, (const unsigned char __user *) buf, numin)) {
+ strncpy(inbuf, "<FAULT>", sizeof inbuf);
+ numin = sizeof "<FAULT>" - 1;
+ }
+ }
+
+ for (; numin && space; --numin, ++in) {
+ --space;
+ if (*in >= 32)
+ *out++ = *in;
+ else {
+ *out++ = '^';
+ if (space) {
+ *out++ = '@' + *in;
+ --space;
+ }
+ }
+ }
+ *out = 0;
+
+ dbg(level, "%s (%u bytes): %s", msg, (unsigned) len, outbuf);
+}
+EXPORT_SYMBOL_GPL(gigaset_dbg_buffer);
+
+static int setflags(struct cardstate *cs, unsigned flags, unsigned delay)
+{
+ int r;
+
+ r = cs->ops->set_modem_ctrl(cs, cs->control_state, flags);
+ cs->control_state = flags;
+ if (r < 0)
+ return r;
+
+ if (delay) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(delay * HZ / 1000);
+ }
+
+ return 0;
+}
+
+int gigaset_enterconfigmode(struct cardstate *cs)
+{
+ int i, r;
+
+ if (!atomic_read(&cs->connected)) {
+ err("not connected!");
+ return -1;
+ }
+
+ cs->control_state = TIOCM_RTS; //FIXME
+
+ r = setflags(cs, TIOCM_DTR, 200);
+ if (r < 0)
+ goto error;
+ r = setflags(cs, 0, 200);
+ if (r < 0)
+ goto error;
+ for (i = 0; i < 5; ++i) {
+ r = setflags(cs, TIOCM_RTS, 100);
+ if (r < 0)
+ goto error;
+ r = setflags(cs, 0, 100);
+ if (r < 0)
+ goto error;
+ }
+ r = setflags(cs, TIOCM_RTS|TIOCM_DTR, 800);
+ if (r < 0)
+ goto error;
+
+ return 0;
+
+error:
+ err("error %d on setuartbits!\n", -r);
+ cs->control_state = TIOCM_RTS|TIOCM_DTR; // FIXME is this a good value?
+ cs->ops->set_modem_ctrl(cs, 0, TIOCM_RTS|TIOCM_DTR);
+
+ return -1; //r
+}
+
+static int test_timeout(struct at_state_t *at_state)
+{
+ if (!at_state->timer_expires)
+ return 0;
+
+ if (--at_state->timer_expires) {
+ dbg(DEBUG_MCMD, "decreased timer of %p to %lu",
+ at_state, at_state->timer_expires);
+ return 0;
+ }
+
+ if (!gigaset_add_event(at_state->cs, at_state, EV_TIMEOUT, NULL,
+ atomic_read(&at_state->timer_index), NULL)) {
+ //FIXME what should we do?
+ }
+
+ return 1;
+}
+
+static void timer_tick(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ unsigned long flags;
+ unsigned channel;
+ struct at_state_t *at_state;
+ int timeout = 0;
+
+ spin_lock_irqsave(&cs->lock, flags);
+
+ for (channel = 0; channel < cs->channels; ++channel)
+ if (test_timeout(&cs->bcs[channel].at_state))
+ timeout = 1;
+
+ if (test_timeout(&cs->at_state))
+ timeout = 1;
+
+ list_for_each_entry(at_state, &cs->temp_at_states, list)
+ if (test_timeout(at_state))
+ timeout = 1;
+
+ if (atomic_read(&cs->running)) {
+ mod_timer(&cs->timer, jiffies + GIG_TICK);
+ if (timeout) {
+ dbg(DEBUG_CMD, "scheduling timeout");
+ tasklet_schedule(&cs->event_tasklet);
+ }
+ }
+
+ spin_unlock_irqrestore(&cs->lock, flags);
+}
+
+int gigaset_get_channel(struct bc_state *bcs)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&bcs->cs->lock, flags);
+ if (bcs->use_count) {
+ dbg(DEBUG_ANY, "could not allocate channel %d", bcs->channel);
+ spin_unlock_irqrestore(&bcs->cs->lock, flags);
+ return 0;
+ }
+ ++bcs->use_count;
+ bcs->busy = 1;
+ dbg(DEBUG_ANY, "allocated channel %d", bcs->channel);
+ spin_unlock_irqrestore(&bcs->cs->lock, flags);
+ return 1;
+}
+
+void gigaset_free_channel(struct bc_state *bcs)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&bcs->cs->lock, flags);
+ if (!bcs->busy) {
+ dbg(DEBUG_ANY, "could not free channel %d", bcs->channel);
+ spin_unlock_irqrestore(&bcs->cs->lock, flags);
+ return;
+ }
+ --bcs->use_count;
+ bcs->busy = 0;
+ dbg(DEBUG_ANY, "freed channel %d", bcs->channel);
+ spin_unlock_irqrestore(&bcs->cs->lock, flags);
+}
+
+int gigaset_get_channels(struct cardstate *cs)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&cs->lock, flags);
+ for (i = 0; i < cs->channels; ++i)
+ if (cs->bcs[i].use_count) {
+ spin_unlock_irqrestore(&cs->lock, flags);
+ dbg(DEBUG_ANY, "could not allocated all channels");
+ return 0;
+ }
+ for (i = 0; i < cs->channels; ++i)
+ ++cs->bcs[i].use_count;
+ spin_unlock_irqrestore(&cs->lock, flags);
+
+ dbg(DEBUG_ANY, "allocated all channels");
+
+ return 1;
+}
+
+void gigaset_free_channels(struct cardstate *cs)
+{
+ unsigned long flags;
+ int i;
+
+ dbg(DEBUG_ANY, "unblocking all channels");
+ spin_lock_irqsave(&cs->lock, flags);
+ for (i = 0; i < cs->channels; ++i)
+ --cs->bcs[i].use_count;
+ spin_unlock_irqrestore(&cs->lock, flags);
+}
+
+void gigaset_block_channels(struct cardstate *cs)
+{
+ unsigned long flags;
+ int i;
+
+ dbg(DEBUG_ANY, "blocking all channels");
+ spin_lock_irqsave(&cs->lock, flags);
+ for (i = 0; i < cs->channels; ++i)
+ ++cs->bcs[i].use_count;
+ spin_unlock_irqrestore(&cs->lock, flags);
+}
+
+static void clear_events(struct cardstate *cs)
+{
+ struct event_t *ev;
+ unsigned head, tail;
+
+ /* no locking needed (no reader/writer allowed) */
+
+ head = atomic_read(&cs->ev_head);
+ tail = atomic_read(&cs->ev_tail);
+
+ while (tail != head) {
+ ev = cs->events + head;
+ kfree(ev->ptr);
+
+ head = (head + 1) % MAX_EVENTS;
+ }
+
+ atomic_set(&cs->ev_head, tail);
+}
+
+struct event_t *gigaset_add_event(struct cardstate *cs,
+ struct at_state_t *at_state, int type,
+ void *ptr, int parameter, void *arg)
+{
+ unsigned long flags;
+ unsigned next, tail;
+ struct event_t *event = NULL;
+
+ spin_lock_irqsave(&cs->ev_lock, flags);
+
+ tail = atomic_read(&cs->ev_tail);
+ next = (tail + 1) % MAX_EVENTS;
+ if (unlikely(next == atomic_read(&cs->ev_head)))
+ err("event queue full");
+ else {
+ event = cs->events + tail;
+ event->type = type;
+ event->at_state = at_state;
+ event->cid = -1;
+ event->ptr = ptr;
+ event->arg = arg;
+ event->parameter = parameter;
+ atomic_set(&cs->ev_tail, next);
+ }
+
+ spin_unlock_irqrestore(&cs->ev_lock, flags);
+
+ return event;
+}
+EXPORT_SYMBOL_GPL(gigaset_add_event);
+
+static void free_strings(struct at_state_t *at_state)
+{
+ int i;
+
+ for (i = 0; i < STR_NUM; ++i) {
+ kfree(at_state->str_var[i]);
+ at_state->str_var[i] = NULL;
+ }
+}
+
+static void clear_at_state(struct at_state_t *at_state)
+{
+ free_strings(at_state);
+}
+
+static void dealloc_at_states(struct cardstate *cs)
+{
+ struct at_state_t *cur, *next;
+
+ list_for_each_entry_safe(cur, next, &cs->temp_at_states, list) {
+ list_del(&cur->list);
+ free_strings(cur);
+ kfree(cur);
+ }
+}
+
+static void gigaset_freebcs(struct bc_state *bcs)
+{
+ int i;
+
+ dbg(DEBUG_INIT, "freeing bcs[%d]->hw", bcs->channel);
+ if (!bcs->cs->ops->freebcshw(bcs)) {
+ dbg(DEBUG_INIT, "failed");
+ }
+
+ dbg(DEBUG_INIT, "clearing bcs[%d]->at_state", bcs->channel);
+ clear_at_state(&bcs->at_state);
+ dbg(DEBUG_INIT, "freeing bcs[%d]->skb", bcs->channel);
+
+ if (bcs->skb)
+ dev_kfree_skb(bcs->skb);
+ for (i = 0; i < AT_NUM; ++i) {
+ kfree(bcs->commands[i]);
+ bcs->commands[i] = NULL;
+ }
+}
+
+void gigaset_freecs(struct cardstate *cs)
+{
+ int i;
+ unsigned long flags;
+
+ if (!cs)
+ return;
+
+ down(&cs->sem);
+
+ if (!cs->bcs)
+ goto f_cs;
+ if (!cs->inbuf)
+ goto f_bcs;
+
+ spin_lock_irqsave(&cs->lock, flags);
+ atomic_set(&cs->running, 0);
+ spin_unlock_irqrestore(&cs->lock, flags); /* event handler and timer are not rescheduled below */
+
+ tasklet_kill(&cs->event_tasklet);
+ del_timer_sync(&cs->timer);
+
+ switch (cs->cs_init) {
+ default:
+ gigaset_if_free(cs);
+
+ dbg(DEBUG_INIT, "clearing hw");
+ cs->ops->freecshw(cs);
+
+ //FIXME cmdbuf
+
+ /* fall through */
+ case 2: /* error in initcshw */
+ /* Deregister from LL */
+ make_invalid(cs, VALID_ID);
+ dbg(DEBUG_INIT, "clearing iif");
+ gigaset_i4l_cmd(cs, ISDN_STAT_UNLOAD);
+
+ /* fall through */
+ case 1: /* error when regestering to LL */
+ dbg(DEBUG_INIT, "clearing at_state");
+ clear_at_state(&cs->at_state);
+ dealloc_at_states(cs);
+
+ /* fall through */
+ case 0: /* error in one call to initbcs */
+ for (i = 0; i < cs->channels; ++i) {
+ dbg(DEBUG_INIT, "clearing bcs[%d]", i);
+ gigaset_freebcs(cs->bcs + i);
+ }
+
+ clear_events(cs);
+ dbg(DEBUG_INIT, "freeing inbuf");
+ kfree(cs->inbuf);
+ }
+f_bcs: dbg(DEBUG_INIT, "freeing bcs[]");
+ kfree(cs->bcs);
+f_cs: dbg(DEBUG_INIT, "freeing cs");
+ up(&cs->sem);
+ free_cs(cs);
+}
+EXPORT_SYMBOL_GPL(gigaset_freecs);
+
+void gigaset_at_init(struct at_state_t *at_state, struct bc_state *bcs,
+ struct cardstate *cs, int cid)
+{
+ int i;
+
+ INIT_LIST_HEAD(&at_state->list);
+ at_state->waiting = 0;
+ at_state->getstring = 0;
+ at_state->pending_commands = 0;
+ at_state->timer_expires = 0;
+ at_state->timer_active = 0;
+ atomic_set(&at_state->timer_index, 0);
+ atomic_set(&at_state->seq_index, 0);
+ at_state->ConState = 0;
+ for (i = 0; i < STR_NUM; ++i)
+ at_state->str_var[i] = NULL;
+ at_state->int_var[VAR_ZDLE] = 0;
+ at_state->int_var[VAR_ZCTP] = -1;
+ at_state->int_var[VAR_ZSAU] = ZSAU_NULL;
+ at_state->cs = cs;
+ at_state->bcs = bcs;
+ at_state->cid = cid;
+ if (!cid)
+ at_state->replystruct = cs->tabnocid;
+ else
+ at_state->replystruct = cs->tabcid;
+}
+
+
+static void gigaset_inbuf_init(struct inbuf_t *inbuf, struct bc_state *bcs,
+ struct cardstate *cs, int inputstate)
+/* inbuf->read must be allocated before! */
+{
+ atomic_set(&inbuf->head, 0);
+ atomic_set(&inbuf->tail, 0);
+ inbuf->cs = cs;
+ inbuf->bcs = bcs; /*base driver: NULL*/
+ inbuf->rcvbuf = NULL; //FIXME
+ inbuf->inputstate = inputstate;
+}
+
+/* Initialize the b-channel structure */
+static struct bc_state *gigaset_initbcs(struct bc_state *bcs,
+ struct cardstate *cs, int channel)
+{
+ int i;
+
+ bcs->tx_skb = NULL; //FIXME -> hw part
+
+ skb_queue_head_init(&bcs->squeue);
+
+ bcs->corrupted = 0;
+ bcs->trans_down = 0;
+ bcs->trans_up = 0;
+
+ dbg(DEBUG_INIT, "setting up bcs[%d]->at_state", channel);
+ gigaset_at_init(&bcs->at_state, bcs, cs, -1);
+
+ bcs->rcvbytes = 0;
+
+#ifdef CONFIG_GIGASET_DEBUG
+ bcs->emptycount = 0;
+#endif
+
+ dbg(DEBUG_INIT, "allocating bcs[%d]->skb", channel);
+ bcs->fcs = PPP_INITFCS;
+ bcs->inputstate = 0;
+ if (cs->ignoreframes) {
+ bcs->inputstate |= INS_skip_frame;
+ bcs->skb = NULL;
+ } else if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ else {
+ warn("could not allocate skb");
+ bcs->inputstate |= INS_skip_frame;
+ }
+
+ bcs->channel = channel;
+ bcs->cs = cs;
+
+ bcs->chstate = 0;
+ bcs->use_count = 1;
+ bcs->busy = 0;
+ bcs->ignore = cs->ignoreframes;
+
+ for (i = 0; i < AT_NUM; ++i)
+ bcs->commands[i] = NULL;
+
+ dbg(DEBUG_INIT, " setting up bcs[%d]->hw", channel);
+ if (cs->ops->initbcshw(bcs))
+ return bcs;
+
+//error:
+ dbg(DEBUG_INIT, " failed");
+
+ dbg(DEBUG_INIT, " freeing bcs[%d]->skb", channel);
+ if (bcs->skb)
+ dev_kfree_skb(bcs->skb);
+
+ return NULL;
+}
+
+/* gigaset_initcs
+ * Allocate and initialize cardstate structure for Gigaset driver
+ * Calls hardware dependent gigaset_initcshw() function
+ * Calls B channel initialization function gigaset_initbcs() for each B channel
+ * parameters:
+ * drv hardware driver the device belongs to
+ * channels number of B channels supported by device
+ * onechannel !=0: B channel data and AT commands share one communication channel
+ * ==0: B channels have separate communication channels
+ * ignoreframes number of frames to ignore after setting up B channel
+ * cidmode !=0: start in CallID mode
+ * modulename name of driver module (used for I4L registration)
+ * return value:
+ * pointer to cardstate structure
+ */
+struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels,
+ int onechannel, int ignoreframes,
+ int cidmode, const char *modulename)
+{
+ struct cardstate *cs = NULL;
+ int i;
+
+ dbg(DEBUG_INIT, "allocating cs");
+ cs = alloc_cs(drv);
+ if (!cs)
+ goto error;
+ dbg(DEBUG_INIT, "allocating bcs[0..%d]", channels - 1);
+ cs->bcs = kmalloc(channels * sizeof(struct bc_state), GFP_KERNEL);
+ if (!cs->bcs)
+ goto error;
+ dbg(DEBUG_INIT, "allocating inbuf");
+ cs->inbuf = kmalloc(sizeof(struct inbuf_t), GFP_KERNEL);
+ if (!cs->inbuf)
+ goto error;
+
+ cs->cs_init = 0;
+ cs->channels = channels;
+ cs->onechannel = onechannel;
+ cs->ignoreframes = ignoreframes;
+ INIT_LIST_HEAD(&cs->temp_at_states);
+ atomic_set(&cs->running, 0);
+ init_timer(&cs->timer); /* clear next & prev */
+ spin_lock_init(&cs->ev_lock);
+ atomic_set(&cs->ev_tail, 0);
+ atomic_set(&cs->ev_head, 0);
+ init_MUTEX_LOCKED(&cs->sem);
+ tasklet_init(&cs->event_tasklet, &gigaset_handle_event, (unsigned long) cs);
+ atomic_set(&cs->commands_pending, 0);
+ cs->cur_at_seq = 0;
+ cs->gotfwver = -1;
+ cs->open_count = 0;
+ cs->tty = NULL;
+ atomic_set(&cs->cidmode, cidmode != 0);
+
+ //if(onechannel) { //FIXME
+ cs->tabnocid = gigaset_tab_nocid_m10x;
+ cs->tabcid = gigaset_tab_cid_m10x;
+ //} else {
+ // cs->tabnocid = gigaset_tab_nocid;
+ // cs->tabcid = gigaset_tab_cid;
+ //}
+
+ init_waitqueue_head(&cs->waitqueue);
+ cs->waiting = 0;
+
+ atomic_set(&cs->mode, M_UNKNOWN);
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+
+ for (i = 0; i < channels; ++i) {
+ dbg(DEBUG_INIT, "setting up bcs[%d].read", i);
+ if (!gigaset_initbcs(cs->bcs + i, cs, i))
+ goto error;
+ }
+
+ ++cs->cs_init;
+
+ dbg(DEBUG_INIT, "setting up at_state");
+ spin_lock_init(&cs->lock);
+ gigaset_at_init(&cs->at_state, NULL, cs, 0);
+ cs->dle = 0;
+ cs->cbytes = 0;
+
+ dbg(DEBUG_INIT, "setting up inbuf");
+ if (onechannel) { //FIXME distinction necessary?
+ gigaset_inbuf_init(cs->inbuf, cs->bcs, cs, INS_command);
+ } else
+ gigaset_inbuf_init(cs->inbuf, NULL, cs, INS_command);
+
+ atomic_set(&cs->connected, 0);
+
+ dbg(DEBUG_INIT, "setting up cmdbuf");
+ cs->cmdbuf = cs->lastcmdbuf = NULL;
+ spin_lock_init(&cs->cmdlock);
+ cs->curlen = 0;
+ cs->cmdbytes = 0;
+
+ /*
+ * Tell the ISDN4Linux subsystem (the LL) that
+ * a driver for a USB-Device is available !
+ * If this is done, "isdnctrl" is able to bind a device for this driver even
+ * if no physical usb-device is currently connected.
+ * But this device will just be accessable if a physical USB device is connected
+ * (via "gigaset_probe") .
+ */
+ dbg(DEBUG_INIT, "setting up iif");
+ if (!gigaset_register_to_LL(cs, modulename)) {
+ err("register_isdn=>error");
+ goto error;
+ }
+
+ make_valid(cs, VALID_ID);
+ ++cs->cs_init;
+ dbg(DEBUG_INIT, "setting up hw");
+ if (!cs->ops->initcshw(cs))
+ goto error;
+
+ ++cs->cs_init;
+
+ gigaset_if_init(cs);
+
+ atomic_set(&cs->running, 1);
+ cs->timer.data = (unsigned long) cs;
+ cs->timer.function = timer_tick;
+ cs->timer.expires = jiffies + GIG_TICK;
+ /* FIXME: can jiffies increase too much until the timer is added?
+ * Same problem(?) with mod_timer() in timer_tick(). */
+ add_timer(&cs->timer);
+
+ dbg(DEBUG_INIT, "cs initialized!");
+ up(&cs->sem);
+ return cs;
+
+error: if (cs)
+ up(&cs->sem);
+ dbg(DEBUG_INIT, "failed");
+ gigaset_freecs(cs);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(gigaset_initcs);
+
+/* ReInitialize the b-channel structure */ /* e.g. called on hangup, disconnect */
+void gigaset_bcs_reinit(struct bc_state *bcs)
+{
+ struct sk_buff *skb;
+ struct cardstate *cs = bcs->cs;
+ unsigned long flags;
+
+ while ((skb = skb_dequeue(&bcs->squeue)) != NULL)
+ dev_kfree_skb(skb);
+
+ spin_lock_irqsave(&cs->lock, flags); //FIXME
+ clear_at_state(&bcs->at_state);
+ bcs->at_state.ConState = 0;
+ bcs->at_state.timer_active = 0;
+ bcs->at_state.timer_expires = 0;
+ bcs->at_state.cid = -1; /* No CID defined */
+ spin_unlock_irqrestore(&cs->lock, flags);
+
+ bcs->inputstate = 0;
+
+#ifdef CONFIG_GIGASET_DEBUG
+ bcs->emptycount = 0;
+#endif
+
+ bcs->fcs = PPP_INITFCS;
+ bcs->chstate = 0;
+
+ bcs->ignore = cs->ignoreframes;
+ if (bcs->ignore)
+ bcs->inputstate |= INS_skip_frame;
+
+
+ cs->ops->reinitbcshw(bcs);
+}
+
+static void cleanup_cs(struct cardstate *cs)
+{
+ struct cmdbuf_t *cb, *tcb;
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cs->lock, flags);
+
+ atomic_set(&cs->mode, M_UNKNOWN);
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+
+ clear_at_state(&cs->at_state);
+ dealloc_at_states(cs);
+ free_strings(&cs->at_state);
+ gigaset_at_init(&cs->at_state, NULL, cs, 0);
+
+ kfree(cs->inbuf->rcvbuf);
+ cs->inbuf->rcvbuf = NULL;
+ cs->inbuf->inputstate = INS_command;
+ atomic_set(&cs->inbuf->head, 0);
+ atomic_set(&cs->inbuf->tail, 0);
+
+ cb = cs->cmdbuf;
+ while (cb) {
+ tcb = cb;
+ cb = cb->next;
+ kfree(tcb);
+ }
+ cs->cmdbuf = cs->lastcmdbuf = NULL;
+ cs->curlen = 0;
+ cs->cmdbytes = 0;
+ cs->gotfwver = -1;
+ cs->dle = 0;
+ cs->cur_at_seq = 0;
+ atomic_set(&cs->commands_pending, 0);
+ cs->cbytes = 0;
+
+ spin_unlock_irqrestore(&cs->lock, flags);
+
+ for (i = 0; i < cs->channels; ++i) {
+ gigaset_freebcs(cs->bcs + i);
+ if (!gigaset_initbcs(cs->bcs + i, cs, i))
+ break; //FIXME error handling
+ }
+
+ if (cs->waiting) {
+ cs->cmd_result = -ENODEV;
+ cs->waiting = 0;
+ wake_up_interruptible(&cs->waitqueue);
+ }
+}
+
+
+int gigaset_start(struct cardstate *cs)
+{
+ if (down_interruptible(&cs->sem))
+ return 0;
+ //info("USB device for Gigaset 307x now attached to Dev %d", ucs->minor);
+
+ atomic_set(&cs->connected, 1);
+
+ if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR|TIOCM_RTS);
+ cs->ops->baud_rate(cs, B115200);
+ cs->ops->set_line_ctrl(cs, CS8);
+ cs->control_state = TIOCM_DTR|TIOCM_RTS;
+ } else {
+ //FIXME use some saved values?
+ }
+
+ cs->waiting = 1;
+
+ if (!gigaset_add_event(cs, &cs->at_state, EV_START, NULL, 0, NULL)) {
+ cs->waiting = 0;
+ //FIXME what should we do?
+ goto error;
+ }
+
+ dbg(DEBUG_CMD, "scheduling START");
+ gigaset_schedule_event(cs);
+
+ wait_event(cs->waitqueue, !cs->waiting);
+
+ up(&cs->sem);
+ return 1;
+
+error:
+ up(&cs->sem);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gigaset_start);
+
+void gigaset_shutdown(struct cardstate *cs)
+{
+ down(&cs->sem);
+
+ cs->waiting = 1;
+
+ if (!gigaset_add_event(cs, &cs->at_state, EV_SHUTDOWN, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ goto exit;
+ }
+
+ dbg(DEBUG_CMD, "scheduling SHUTDOWN");
+ gigaset_schedule_event(cs);
+
+ if (wait_event_interruptible(cs->waitqueue, !cs->waiting)) {
+ warn("aborted");
+ //FIXME
+ }
+
+ if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ //FIXME?
+ //gigaset_baud_rate(cs, B115200);
+ //gigaset_set_line_ctrl(cs, CS8);
+ //gigaset_set_modem_ctrl(cs, TIOCM_DTR|TIOCM_RTS, 0);
+ //cs->control_state = 0;
+ } else {
+ //FIXME use some saved values?
+ }
+
+ cleanup_cs(cs);
+
+exit:
+ up(&cs->sem);
+}
+EXPORT_SYMBOL_GPL(gigaset_shutdown);
+
+void gigaset_stop(struct cardstate *cs)
+{
+ down(&cs->sem);
+
+ atomic_set(&cs->connected, 0);
+
+ cs->waiting = 1;
+
+ if (!gigaset_add_event(cs, &cs->at_state, EV_STOP, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ goto exit;
+ }
+
+ dbg(DEBUG_CMD, "scheduling STOP");
+ gigaset_schedule_event(cs);
+
+ if (wait_event_interruptible(cs->waitqueue, !cs->waiting)) {
+ warn("aborted");
+ //FIXME
+ }
+
+ /* Tell the LL that the device is not available .. */
+ gigaset_i4l_cmd(cs, ISDN_STAT_STOP); // FIXME move to event layer?
+
+ cleanup_cs(cs);
+
+exit:
+ up(&cs->sem);
+}
+EXPORT_SYMBOL_GPL(gigaset_stop);
+
+static LIST_HEAD(drivers);
+static spinlock_t driver_lock = SPIN_LOCK_UNLOCKED;
+
+struct cardstate *gigaset_get_cs_by_id(int id)
+{
+ unsigned long flags;
+ static struct cardstate *ret = NULL;
+ static struct cardstate *cs;
+ struct gigaset_driver *drv;
+ unsigned i;
+
+ spin_lock_irqsave(&driver_lock, flags);
+ list_for_each_entry(drv, &drivers, list) {
+ spin_lock(&drv->lock);
+ for (i = 0; i < drv->minors; ++i) {
+ if (drv->flags[i] & VALID_ID) {
+ cs = drv->cs + i;
+ if (cs->myid == id)
+ ret = cs;
+ }
+ if (ret)
+ break;
+ }
+ spin_unlock(&drv->lock);
+ if (ret)
+ break;
+ }
+ spin_unlock_irqrestore(&driver_lock, flags);
+ return ret;
+}
+
+void gigaset_debugdrivers(void)
+{
+ unsigned long flags;
+ static struct cardstate *cs;
+ struct gigaset_driver *drv;
+ unsigned i;
+
+ spin_lock_irqsave(&driver_lock, flags);
+ list_for_each_entry(drv, &drivers, list) {
+ dbg(DEBUG_DRIVER, "driver %p", drv);
+ spin_lock(&drv->lock);
+ for (i = 0; i < drv->minors; ++i) {
+ dbg(DEBUG_DRIVER, " index %u", i);
+ dbg(DEBUG_DRIVER, " flags 0x%02x", drv->flags[i]);
+ cs = drv->cs + i;
+ dbg(DEBUG_DRIVER, " cardstate %p", cs);
+ dbg(DEBUG_DRIVER, " minor_index %u", cs->minor_index);
+ dbg(DEBUG_DRIVER, " driver %p", cs->driver);
+ dbg(DEBUG_DRIVER, " i4l id %d", cs->myid);
+ }
+ spin_unlock(&drv->lock);
+ }
+ spin_unlock_irqrestore(&driver_lock, flags);
+}
+EXPORT_SYMBOL_GPL(gigaset_debugdrivers);
+
+struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty)
+{
+ if (tty->index < 0 || tty->index >= tty->driver->num)
+ return NULL;
+ return gigaset_get_cs_by_minor(tty->index + tty->driver->minor_start);
+}
+
+struct cardstate *gigaset_get_cs_by_minor(unsigned minor)
+{
+ unsigned long flags;
+ static struct cardstate *ret = NULL;
+ struct gigaset_driver *drv;
+ unsigned index;
+
+ spin_lock_irqsave(&driver_lock, flags);
+ list_for_each_entry(drv, &drivers, list) {
+ if (minor < drv->minor || minor >= drv->minor + drv->minors)
+ continue;
+ index = minor - drv->minor;
+ spin_lock(&drv->lock);
+ if (drv->flags[index] & VALID_MINOR)
+ ret = drv->cs + index;
+ spin_unlock(&drv->lock);
+ if (ret)
+ break;
+ }
+ spin_unlock_irqrestore(&driver_lock, flags);
+ return ret;
+}
+
+void gigaset_freedriver(struct gigaset_driver *drv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&driver_lock, flags);
+ list_del(&drv->list);
+ spin_unlock_irqrestore(&driver_lock, flags);
+
+ gigaset_if_freedriver(drv);
+ module_put(drv->owner);
+
+ kfree(drv->cs);
+ kfree(drv->flags);
+ kfree(drv);
+}
+EXPORT_SYMBOL_GPL(gigaset_freedriver);
+
+/* gigaset_initdriver
+ * Allocate and initialize gigaset_driver structure. Initialize interface.
+ * parameters:
+ * minor First minor number
+ * minors Number of minors this driver can handle
+ * procname Name of the driver (e.g. for /proc/tty/drivers, path in /proc/driver)
+ * devname Name of the device files (prefix without minor number)
+ * devfsname Devfs name of the device files without %d
+ * return value:
+ * Pointer to the gigaset_driver structure on success, NULL on failure.
+ */
+struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors,
+ const char *procname,
+ const char *devname,
+ const char *devfsname,
+ const struct gigaset_ops *ops,
+ struct module *owner)
+{
+ struct gigaset_driver *drv;
+ unsigned long flags;
+ unsigned i;
+
+ drv = kmalloc(sizeof *drv, GFP_KERNEL);
+ if (!drv)
+ return NULL;
+ if (!try_module_get(owner))
+ return NULL;
+
+ drv->cs = NULL;
+ drv->have_tty = 0;
+ drv->minor = minor;
+ drv->minors = minors;
+ spin_lock_init(&drv->lock);
+ drv->blocked = 0;
+ drv->ops = ops;
+ drv->owner = owner;
+ INIT_LIST_HEAD(&drv->list);
+
+ drv->cs = kmalloc(minors * sizeof *drv->cs, GFP_KERNEL);
+ if (!drv->cs)
+ goto out1;
+ drv->flags = kmalloc(minors * sizeof *drv->flags, GFP_KERNEL);
+ if (!drv->flags)
+ goto out2;
+
+ for (i = 0; i < minors; ++i) {
+ drv->flags[i] = 0;
+ drv->cs[i].driver = drv;
+ drv->cs[i].ops = drv->ops;
+ drv->cs[i].minor_index = i;
+ }
+
+ gigaset_if_initdriver(drv, procname, devname, devfsname);
+
+ spin_lock_irqsave(&driver_lock, flags);
+ list_add(&drv->list, &drivers);
+ spin_unlock_irqrestore(&driver_lock, flags);
+
+ return drv;
+
+out2:
+ kfree(drv->cs);
+out1:
+ kfree(drv);
+ module_put(owner);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(gigaset_initdriver);
+
+static struct cardstate *alloc_cs(struct gigaset_driver *drv)
+{
+ unsigned long flags;
+ unsigned i;
+ static struct cardstate *ret = NULL;
+
+ spin_lock_irqsave(&drv->lock, flags);
+ for (i = 0; i < drv->minors; ++i) {
+ if (!(drv->flags[i] & VALID_MINOR)) {
+ drv->flags[i] = VALID_MINOR;
+ ret = drv->cs + i;
+ }
+ if (ret)
+ break;
+ }
+ spin_unlock_irqrestore(&drv->lock, flags);
+ return ret;
+}
+
+static void free_cs(struct cardstate *cs)
+{
+ unsigned long flags;
+ struct gigaset_driver *drv = cs->driver;
+ spin_lock_irqsave(&drv->lock, flags);
+ drv->flags[cs->minor_index] = 0;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+
+static void make_valid(struct cardstate *cs, unsigned mask)
+{
+ unsigned long flags;
+ struct gigaset_driver *drv = cs->driver;
+ spin_lock_irqsave(&drv->lock, flags);
+ drv->flags[cs->minor_index] |= mask;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+
+static void make_invalid(struct cardstate *cs, unsigned mask)
+{
+ unsigned long flags;
+ struct gigaset_driver *drv = cs->driver;
+ spin_lock_irqsave(&drv->lock, flags);
+ drv->flags[cs->minor_index] &= ~mask;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+
+/* For drivers without fixed assignment device<->cardstate (usb) */
+struct cardstate *gigaset_getunassignedcs(struct gigaset_driver *drv)
+{
+ unsigned long flags;
+ struct cardstate *cs = NULL;
+ unsigned i;
+
+ spin_lock_irqsave(&drv->lock, flags);
+ if (drv->blocked)
+ goto exit;
+ for (i = 0; i < drv->minors; ++i) {
+ if ((drv->flags[i] & VALID_MINOR) &&
+ !(drv->flags[i] & ASSIGNED)) {
+ drv->flags[i] |= ASSIGNED;
+ cs = drv->cs + i;
+ break;
+ }
+ }
+exit:
+ spin_unlock_irqrestore(&drv->lock, flags);
+ return cs;
+}
+EXPORT_SYMBOL_GPL(gigaset_getunassignedcs);
+
+void gigaset_unassign(struct cardstate *cs)
+{
+ unsigned long flags;
+ unsigned *minor_flags;
+ struct gigaset_driver *drv;
+
+ if (!cs)
+ return;
+ drv = cs->driver;
+ spin_lock_irqsave(&drv->lock, flags);
+ minor_flags = drv->flags + cs->minor_index;
+ if (*minor_flags & VALID_MINOR)
+ *minor_flags &= ~ASSIGNED;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+EXPORT_SYMBOL_GPL(gigaset_unassign);
+
+void gigaset_blockdriver(struct gigaset_driver *drv)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&drv->lock, flags);
+ drv->blocked = 1;
+ spin_unlock_irqrestore(&drv->lock, flags);
+}
+EXPORT_SYMBOL_GPL(gigaset_blockdriver);
+
+static int __init gigaset_init_module(void)
+{
+ /* in accordance with the principle of least astonishment,
+ * setting the 'debug' parameter to 1 activates a sensible
+ * set of default debug levels
+ */
+ if (gigaset_debuglevel == 1)
+ gigaset_debuglevel = DEBUG_DEFAULT;
+
+ info(DRIVER_AUTHOR);
+ info(DRIVER_DESC);
+ return 0;
+}
+
+static void __exit gigaset_exit_module(void)
+{
+}
+
+module_init(gigaset_init_module);
+module_exit(gigaset_exit_module);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+
+MODULE_LICENSE("GPL");

2006-02-11 15:01:41

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 9/9] isdn4linux: Siemens Gigaset drivers - M105 USB DECT adapter

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch adds the connection-specific module "usb_gigaset", the
hardware driver for Gigaset base stations connected via the M105 USB
DECT adapter. It contains the code for handling probe/disconnect, AT
command/response transmission, and call setup and termination, as well
as handling asynchronous data transfers, PPP framing, byte stuffing,
and flow control.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

drivers/isdn/gigaset/asyncdata.c | 597 +++++++++++++++++++++
drivers/isdn/gigaset/usb-gigaset.c | 1008 +++++++++++++++++++++++++++++++++++++
2 files changed, 1605 insertions(+)

--- linux-2.6.16-rc2/drivers/isdn/gigaset/usb-gigaset.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/usb-gigaset.c 2006-02-11 15:20:26.000000000 +0100
@@ -0,0 +1,1008 @@
+/*
+ * USB driver for Gigaset 307x directly or using M105 Data.
+ *
+ * Copyright (c) 2001 by Stefan Eilers <[email protected]>
+ * and Hansjoerg Lipp <[email protected]>.
+ *
+ * This driver was derived from the USB skeleton driver by
+ * Greg Kroah-Hartman <[email protected]>
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: usb-gigaset.c,v 1.85.4.18 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+/* Version Information */
+#define DRIVER_AUTHOR "Hansjoerg Lipp <[email protected]>, Stefan Eilers <[email protected]>"
+#define DRIVER_DESC "USB Driver for Gigaset 307x using M105"
+
+/* Module parameters */
+
+static int startmode = SM_ISDN;
+static int cidmode = 1;
+
+module_param(startmode, int, S_IRUGO);
+module_param(cidmode, int, S_IRUGO);
+MODULE_PARM_DESC(startmode, "start in isdn4linux mode");
+MODULE_PARM_DESC(cidmode, "Call-ID mode");
+
+#define GIGASET_MINORS 1
+#define GIGASET_MINOR 8
+#define GIGASET_MODULENAME "usb_gigaset"
+#define GIGASET_DEVFSNAME "gig/usb/"
+#define GIGASET_DEVNAME "ttyGU"
+
+#define IF_WRITEBUF 2000 //FIXME // WAKEUP_CHARS: 256
+
+/* Values for the Gigaset M105 Data */
+#define USB_M105_VENDOR_ID 0x0681
+#define USB_M105_PRODUCT_ID 0x0009
+
+/* table of devices that work with this driver */
+static struct usb_device_id gigaset_table [] = {
+ { USB_DEVICE(USB_M105_VENDOR_ID, USB_M105_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, gigaset_table);
+
+/* Get a minor range for your devices from the usb maintainer */
+#define USB_SKEL_MINOR_BASE 200
+
+
+/*
+ * Control requests (empty fields: 00)
+ *
+ * RT|RQ|VALUE|INDEX|LEN |DATA
+ * In:
+ * C1 08 01
+ * Get flags (1 byte). Bits: 0=dtr,1=rts,3-7:?
+ * C1 0F ll ll
+ * Get device information/status (llll: 0x200 and 0x40 seen).
+ * Real size: I only saw MIN(llll,0x64).
+ * Contents: seems to be always the same...
+ * offset 0x00: Length of this structure (0x64) (len: 1,2,3 bytes)
+ * offset 0x3c: String (16 bit chars): "MCCI USB Serial V2.0"
+ * rest: ?
+ * Out:
+ * 41 11
+ * Initialize/reset device ?
+ * 41 00 xx 00
+ * ? (xx=00 or 01; 01 on start, 00 on close)
+ * 41 07 vv mm
+ * Set/clear flags vv=value, mm=mask (see RQ 08)
+ * 41 12 xx
+ * Used before the following configuration requests are issued
+ * (with xx=0x0f). I've seen other values<0xf, though.
+ * 41 01 xx xx
+ * Set baud rate. xxxx=ceil(0x384000/rate)=trunc(0x383fff/rate)+1.
+ * 41 03 ps bb
+ * Set byte size and parity. p: 0x20=even,0x10=odd,0x00=no parity
+ * [ 0x30: m, 0x40: s ]
+ * [s: 0: 1 stop bit; 1: 1.5; 2: 2]
+ * bb: bits/byte (seen 7 and 8)
+ * 41 13 -- -- -- -- 10 00 ww 00 00 00 xx 00 00 00 yy 00 00 00 zz 00 00 00
+ * ??
+ * Initialization: 01, 40, 00, 00
+ * Open device: 00 40, 00, 00
+ * yy and zz seem to be equal, either 0x00 or 0x0a
+ * (ww,xx) pairs seen: (00,00), (00,40), (01,40), (09,80), (19,80)
+ * 41 19 -- -- -- -- 06 00 00 00 00 xx 11 13
+ * Used after every "configuration sequence" (RQ 12, RQs 01/03/13).
+ * xx is usually 0x00 but was 0x7e before starting data transfer
+ * in unimodem mode. So, this might be an array of characters that need
+ * special treatment ("commit all bufferd data"?), 11=^Q, 13=^S.
+ *
+ * Unimodem mode: use "modprobe ppp_async flag_time=0" as the device _needs_ two
+ * flags per packet.
+ */
+
+static int gigaset_probe(struct usb_interface *interface,
+ const struct usb_device_id *id);
+static void gigaset_disconnect(struct usb_interface *interface);
+
+static struct gigaset_driver *driver = NULL;
+static struct cardstate *cardstate = NULL;
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver gigaset_usb_driver = {
+ .name = GIGASET_MODULENAME,
+ .probe = gigaset_probe,
+ .disconnect = gigaset_disconnect,
+ .id_table = gigaset_table,
+};
+
+struct usb_cardstate {
+ struct usb_device *udev; /* save off the usb device pointer */
+ struct usb_interface *interface; /* the interface for this device */
+ atomic_t busy; /* bulk output in progress */
+
+ /* Output buffer for commands (M105: and data)*/
+ unsigned char *bulk_out_buffer; /* the buffer to send data */
+ int bulk_out_size; /* the size of the send buffer */
+ __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
+ struct urb *bulk_out_urb; /* the urb used to transmit data */
+
+ /* Input buffer for command responses (M105: and data)*/
+ int rcvbuf_size; /* the size of the receive buffer */
+ struct urb *read_urb; /* the urb used to receive data */
+ __u8 int_in_endpointAddr; /* the address of the bulk in endpoint */
+
+ char bchars[6]; /* req. 0x19 */
+};
+
+struct usb_bc_state {};
+
+static inline unsigned tiocm_to_gigaset(unsigned state)
+{
+ return ((state & TIOCM_DTR) ? 1 : 0) | ((state & TIOCM_RTS) ? 2 : 0);
+}
+
+#ifdef CONFIG_GIGASET_UNDOCREQ
+/* WARNING: EXPERIMENTAL! */
+static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state,
+ unsigned new_state)
+{
+ unsigned mask, val;
+ int r;
+
+ mask = tiocm_to_gigaset(old_state ^ new_state);
+ val = tiocm_to_gigaset(new_state);
+
+ dbg(DEBUG_USBREQ, "set flags 0x%02x with mask 0x%02x", val, mask);
+ r = usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), 7, 0x41,
+ (val & 0xff) | ((mask & 0xff) << 8), 0,
+ NULL, 0, 2000 /*timeout??*/); // don't use this in an interrupt/BH
+ if (r < 0)
+ return r;
+ //..
+ return 0;
+}
+
+static int set_value(struct cardstate *cs, u8 req, u16 val)
+{
+ int r, r2;
+
+ dbg(DEBUG_USBREQ, "request %02x (%04x)", (unsigned)req, (unsigned)val);
+ r = usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), 0x12, 0x41,
+ 0xf /*?*/, 0,
+ NULL, 0, 2000 /*?*/); /* no idea, what this does */
+ if (r < 0) {
+ err("error %d on request 0x12", -r);
+ return r;
+ }
+
+ r = usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), req, 0x41,
+ val, 0,
+ NULL, 0, 2000 /*?*/);
+ if (r < 0)
+ err("error %d on request 0x%02x", -r, (unsigned)req);
+
+ r2 = usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), 0x19, 0x41,
+ 0, 0, cs->hw.usb->bchars, 6, 2000 /*?*/);
+ if (r2 < 0)
+ err("error %d on request 0x19", -r2);
+
+ return r < 0 ? r : (r2 < 0 ? r2 : 0);
+}
+
+/* WARNING: HIGHLY EXPERIMENTAL! */
+// don't use this in an interrupt/BH
+static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag)
+{
+ u16 val;
+ u32 rate;
+
+ cflag &= CBAUD;
+
+ switch (cflag) {
+ //FIXME more values?
+ case B300: rate = 300; break;
+ case B600: rate = 600; break;
+ case B1200: rate = 1200; break;
+ case B2400: rate = 2400; break;
+ case B4800: rate = 4800; break;
+ case B9600: rate = 9600; break;
+ case B19200: rate = 19200; break;
+ case B38400: rate = 38400; break;
+ case B57600: rate = 57600; break;
+ case B115200: rate = 115200; break;
+ default:
+ rate = 9600;
+ err("unsupported baudrate request 0x%x,"
+ " using default of B9600", cflag);
+ }
+
+ val = 0x383fff / rate + 1;
+
+ return set_value(cs, 1, val);
+}
+
+/* WARNING: HIGHLY EXPERIMENTAL! */
+// don't use this in an interrupt/BH
+static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag)
+{
+ u16 val = 0;
+
+ /* set the parity */
+ if (cflag & PARENB)
+ val |= (cflag & PARODD) ? 0x10 : 0x20;
+
+ /* set the number of data bits */
+ switch (cflag & CSIZE) {
+ case CS5:
+ val |= 5 << 8; break;
+ case CS6:
+ val |= 6 << 8; break;
+ case CS7:
+ val |= 7 << 8; break;
+ case CS8:
+ val |= 8 << 8; break;
+ default:
+ err("CSIZE was not CS5-CS8, using default of 8");
+ val |= 8 << 8;
+ break;
+ }
+
+ /* set the number of stop bits */
+ if (cflag & CSTOPB) {
+ if ((cflag & CSIZE) == CS5)
+ val |= 1; /* 1.5 stop bits */ //FIXME is this okay?
+ else
+ val |= 2; /* 2 stop bits */
+ }
+
+ return set_value(cs, 3, val);
+}
+
+#else
+static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state,
+ unsigned new_state)
+{
+ return -EINVAL;
+}
+
+static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag)
+{
+ return -EINVAL;
+}
+
+static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag)
+{
+ return -EINVAL;
+}
+#endif
+
+
+ /*================================================================================================================*/
+static int gigaset_init_bchannel(struct bc_state *bcs)
+{
+ /* nothing to do for M10x */
+ gigaset_bchannel_up(bcs);
+ return 0;
+}
+
+static int gigaset_close_bchannel(struct bc_state *bcs)
+{
+ /* nothing to do for M10x */
+ gigaset_bchannel_down(bcs);
+ return 0;
+}
+
+//void send_ack_to_LL(void *data);
+static int write_modem(struct cardstate *cs);
+static int send_cb(struct cardstate *cs, struct cmdbuf_t *cb);
+
+
+/* Handling of send queue. If there is already a skb opened, put data to
+ * the transfer buffer by calling "write_modem". Otherwise take a new skb out of the queue.
+ * This function will be called by the ISR via "transmit_chars" (USB: B-Channel Bulk callback handler
+ * via immediate task queue) or by writebuf_from_LL if the LL wants to transmit data.
+ */
+static void gigaset_modem_fill(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ struct bc_state *bcs = &cs->bcs[0]; /* only one channel */
+ struct cmdbuf_t *cb;
+ unsigned long flags;
+ int again;
+
+ dbg(DEBUG_OUTPUT, "modem_fill");
+
+ if (atomic_read(&cs->hw.usb->busy)) {
+ dbg(DEBUG_OUTPUT, "modem_fill: busy");
+ return;
+ }
+
+ do {
+ again = 0;
+ if (!bcs->tx_skb) { /* no skb is being sent */
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ cb = cs->cmdbuf;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+ if (cb) { /* commands to send? */
+ dbg(DEBUG_OUTPUT, "modem_fill: cb");
+ if (send_cb(cs, cb) < 0) {
+ dbg(DEBUG_OUTPUT,
+ "modem_fill: send_cb failed");
+ again = 1; /* no callback will be called! */
+ }
+ } else { /* skbs to send? */
+ bcs->tx_skb = skb_dequeue(&bcs->squeue);
+ if (bcs->tx_skb)
+ dbg(DEBUG_INTR,
+ "Dequeued skb (Adr: %lx)!",
+ (unsigned long) bcs->tx_skb);
+ }
+ }
+
+ if (bcs->tx_skb) {
+ dbg(DEBUG_OUTPUT, "modem_fill: tx_skb");
+ if (write_modem(cs) < 0) {
+ dbg(DEBUG_OUTPUT,
+ "modem_fill: write_modem failed");
+ // FIXME should we tell the LL?
+ again = 1; /* no callback will be called! */
+ }
+ }
+ } while (again);
+}
+
+/**
+ * gigaset_read_int_callback
+ *
+ * It is called if the data was received from the device. This is almost similiar to
+ * the interrupt service routine in the serial device.
+ */
+static void gigaset_read_int_callback(struct urb *urb, struct pt_regs *regs)
+{
+ int resubmit = 0;
+ int r;
+ struct cardstate *cs;
+ unsigned numbytes;
+ unsigned char *src;
+ //unsigned long flags;
+ struct inbuf_t *inbuf;
+
+ IFNULLRET(urb);
+ inbuf = (struct inbuf_t *) urb->context;
+ IFNULLRET(inbuf);
+ //spin_lock_irqsave(&inbuf->lock, flags);
+ cs = inbuf->cs;
+ IFNULLGOTO(cs, exit);
+ IFNULLGOTO(cardstate, exit);
+
+ if (!atomic_read(&cs->connected)) {
+ err("%s: disconnected", __func__);
+ goto exit;
+ }
+
+ if (!urb->status) {
+ numbytes = urb->actual_length;
+
+ if (numbytes) {
+ src = inbuf->rcvbuf;
+ if (unlikely(*src))
+ warn("%s: There was no leading 0, but 0x%02x!",
+ __func__, (unsigned) *src);
+ ++src; /* skip leading 0x00 */
+ --numbytes;
+ if (gigaset_fill_inbuf(inbuf, src, numbytes)) {
+ dbg(DEBUG_INTR, "%s-->BH", __func__);
+ gigaset_schedule_event(inbuf->cs);
+ }
+ } else
+ dbg(DEBUG_INTR, "Received zero block length");
+ resubmit = 1;
+ } else {
+ /* The urb might have been killed. */
+ dbg(DEBUG_ANY, "%s - nonzero read bulk status received: %d",
+ __func__, urb->status);
+ if (urb->status != -ENOENT) /* not killed */
+ resubmit = 1;
+ }
+exit:
+ //spin_unlock_irqrestore(&inbuf->lock, flags);
+ if (resubmit) {
+ r = usb_submit_urb(urb, SLAB_ATOMIC);
+ if (r)
+ err("error %d when resubmitting urb.", -r);
+ }
+}
+
+
+/* This callback routine is called when data was transmitted to a B-Channel.
+ * Therefore it has to check if there is still data to transmit. This
+ * happens by calling modem_fill via task queue.
+ *
+ */
+static void gigaset_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct cardstate *cs = (struct cardstate *) urb->context;
+
+ IFNULLRET(cs);
+#ifdef CONFIG_GIGASET_DEBUG
+ if (!atomic_read(&cs->connected)) {
+ err("%s:not connected", __func__);
+ return;
+ }
+#endif
+ if (urb->status)
+ err("bulk transfer failed (status %d)", -urb->status); /* That's all we can do. Communication problems
+ are handeled by timeouts or network protocols */
+
+ atomic_set(&cs->hw.usb->busy, 0);
+ tasklet_schedule(&cs->write_tasklet);
+}
+
+static int send_cb(struct cardstate *cs, struct cmdbuf_t *cb)
+{
+ struct cmdbuf_t *tcb;
+ unsigned long flags;
+ int count;
+ int status = -ENOENT; // FIXME
+ struct usb_cardstate *ucs = cs->hw.usb;
+
+ do {
+ if (!cb->len) {
+ tcb = cb;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ cs->cmdbytes -= cs->curlen;
+ dbg(DEBUG_OUTPUT, "send_cb: sent %u bytes, %u left",
+ cs->curlen, cs->cmdbytes);
+ cs->cmdbuf = cb = cb->next;
+ if (cb) {
+ cb->prev = NULL;
+ cs->curlen = cb->len;
+ } else {
+ cs->lastcmdbuf = NULL;
+ cs->curlen = 0;
+ }
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ if (tcb->wake_tasklet)
+ tasklet_schedule(tcb->wake_tasklet);
+ kfree(tcb);
+ }
+ if (cb) {
+ count = min(cb->len, ucs->bulk_out_size);
+ usb_fill_bulk_urb(ucs->bulk_out_urb, ucs->udev,
+ usb_sndbulkpipe(ucs->udev,
+ ucs->bulk_out_endpointAddr & 0x0f),
+ cb->buf + cb->offset, count,
+ gigaset_write_bulk_callback, cs);
+
+ cb->offset += count;
+ cb->len -= count;
+ atomic_set(&ucs->busy, 1);
+ dbg(DEBUG_OUTPUT, "send_cb: send %d bytes", count);
+
+ status = usb_submit_urb(ucs->bulk_out_urb, SLAB_ATOMIC);
+ if (status) {
+ atomic_set(&ucs->busy, 0);
+ err("could not submit urb (error %d).",
+ -status);
+ cb->len = 0; /* skip urb => remove cb+wakeup in next loop cycle */
+ }
+ }
+ } while (cb && status); /* bei Fehler naechster Befehl //FIXME: ist das OK? */
+
+ return status;
+}
+
+/* Write string into transbuf and send it to modem.
+ */
+static int gigaset_write_cmd(struct cardstate *cs, const unsigned char *buf,
+ int len, struct tasklet_struct *wake_tasklet)
+{
+ struct cmdbuf_t *cb;
+ unsigned long flags;
+
+ gigaset_dbg_buffer(atomic_read(&cs->mstate) != MS_LOCKED ?
+ DEBUG_TRANSCMD : DEBUG_LOCKCMD,
+ "CMD Transmit", len, buf, 0);
+
+ if (!atomic_read(&cs->connected)) {
+ err("%s: not connected", __func__);
+ return -ENODEV;
+ }
+
+ if (len <= 0)
+ return 0;
+
+ if (!(cb = kmalloc(sizeof(struct cmdbuf_t) + len, GFP_ATOMIC))) {
+ err("%s: out of memory", __func__);
+ return -ENOMEM;
+ }
+
+ memcpy(cb->buf, buf, len);
+ cb->len = len;
+ cb->offset = 0;
+ cb->next = NULL;
+ cb->wake_tasklet = wake_tasklet;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ cb->prev = cs->lastcmdbuf;
+ if (cs->lastcmdbuf)
+ cs->lastcmdbuf->next = cb;
+ else {
+ cs->cmdbuf = cb;
+ cs->curlen = len;
+ }
+ cs->cmdbytes += len;
+ cs->lastcmdbuf = cb;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ tasklet_schedule(&cs->write_tasklet);
+ return len;
+}
+
+static int gigaset_write_room(struct cardstate *cs)
+{
+ unsigned long flags;
+ unsigned bytes;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ bytes = cs->cmdbytes;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ return bytes < IF_WRITEBUF ? IF_WRITEBUF - bytes : 0;
+}
+
+static int gigaset_chars_in_buffer(struct cardstate *cs)
+{
+ return cs->cmdbytes;
+}
+
+static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6])
+{
+#ifdef CONFIG_GIGASET_UNDOCREQ
+ gigaset_dbg_buffer(DEBUG_USBREQ, "brkchars", 6, buf, 0);
+ memcpy(cs->hw.usb->bchars, buf, 6);
+ return usb_control_msg(cs->hw.usb->udev,
+ usb_sndctrlpipe(cs->hw.usb->udev, 0), 0x19, 0x41,
+ 0, 0, &buf, 6, 2000);
+#else
+ return -EINVAL;
+#endif
+}
+
+static int gigaset_freebcshw(struct bc_state *bcs)
+{
+ if (!bcs->hw.usb)
+ return 0;
+ //FIXME
+ kfree(bcs->hw.usb);
+ return 1;
+}
+
+/* Initialize the b-channel structure */
+static int gigaset_initbcshw(struct bc_state *bcs)
+{
+ bcs->hw.usb = kmalloc(sizeof(struct usb_bc_state), GFP_KERNEL);
+ if (!bcs->hw.usb)
+ return 0;
+
+ //bcs->hw.usb->trans_flg = READY_TO_TRNSMIT; /* B-Channel ready to transmit */
+ return 1;
+}
+
+static void gigaset_reinitbcshw(struct bc_state *bcs)
+{
+}
+
+static void gigaset_freecshw(struct cardstate *cs)
+{
+ //FIXME
+ tasklet_kill(&cs->write_tasklet);
+ kfree(cs->hw.usb);
+}
+
+static int gigaset_initcshw(struct cardstate *cs)
+{
+ struct usb_cardstate *ucs;
+
+ cs->hw.usb = ucs =
+ kmalloc(sizeof(struct usb_cardstate), GFP_KERNEL);
+ if (!ucs)
+ return 0;
+
+ ucs->bchars[0] = 0;
+ ucs->bchars[1] = 0;
+ ucs->bchars[2] = 0;
+ ucs->bchars[3] = 0;
+ ucs->bchars[4] = 0x11;
+ ucs->bchars[5] = 0x13;
+ ucs->bulk_out_buffer = NULL;
+ ucs->bulk_out_urb = NULL;
+ //ucs->urb_cmd_out = NULL;
+ ucs->read_urb = NULL;
+ tasklet_init(&cs->write_tasklet,
+ &gigaset_modem_fill, (unsigned long) cs);
+
+ return 1;
+}
+
+/* Writes the data of the current open skb into the modem.
+ * We have to protect against multiple calls until the
+ * callback handler () is called , due to the fact that we
+ * are just allowed to send data once to an endpoint. Therefore
+ * we using "trans_flg" to synchonize ...
+ */
+static int write_modem(struct cardstate *cs)
+{
+ int ret;
+ int count;
+ struct bc_state *bcs = &cs->bcs[0]; /* only one channel */
+ struct usb_cardstate *ucs = cs->hw.usb;
+ //unsigned long flags;
+
+ IFNULLRETVAL(bcs->tx_skb, -EINVAL);
+
+ dbg(DEBUG_WRITE, "len: %d...", bcs->tx_skb->len);
+
+ ret = -ENODEV;
+ IFNULLGOTO(ucs->bulk_out_buffer, error);
+ IFNULLGOTO(ucs->bulk_out_urb, error);
+ ret = 0;
+
+ if (!bcs->tx_skb->len) {
+ dev_kfree_skb_any(bcs->tx_skb);
+ bcs->tx_skb = NULL;
+ return -EINVAL;
+ }
+
+ /* Copy data to bulk out buffer and // FIXME copying not necessary
+ * transmit data
+ */
+ count = min(bcs->tx_skb->len, (unsigned) ucs->bulk_out_size);
+ memcpy(ucs->bulk_out_buffer, bcs->tx_skb->data, count);
+ skb_pull(bcs->tx_skb, count);
+
+ usb_fill_bulk_urb(ucs->bulk_out_urb, ucs->udev,
+ usb_sndbulkpipe(ucs->udev,
+ ucs->bulk_out_endpointAddr & 0x0f),
+ ucs->bulk_out_buffer, count,
+ gigaset_write_bulk_callback, cs);
+ atomic_set(&ucs->busy, 1);
+ dbg(DEBUG_OUTPUT, "write_modem: send %d bytes", count);
+
+ ret = usb_submit_urb(ucs->bulk_out_urb, SLAB_ATOMIC);
+ if (ret) {
+ err("could not submit urb (error %d).", -ret);
+ atomic_set(&ucs->busy, 0);
+ }
+ if (!bcs->tx_skb->len) {
+ /* skb sent completely */
+ gigaset_skb_sent(bcs, bcs->tx_skb); //FIXME also, when ret<0?
+
+ dbg(DEBUG_INTR,
+ "kfree skb (Adr: %lx)!", (unsigned long) bcs->tx_skb);
+ dev_kfree_skb_any(bcs->tx_skb);
+ bcs->tx_skb = NULL;
+ }
+
+ return ret;
+error:
+ dev_kfree_skb_any(bcs->tx_skb);
+ bcs->tx_skb = NULL;
+ return ret;
+
+}
+
+static int gigaset_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ int retval;
+ struct usb_device *udev = interface_to_usbdev(interface);
+ unsigned int ifnum;
+ struct usb_host_interface *hostif;
+ struct cardstate *cs = NULL;
+ struct usb_cardstate *ucs = NULL;
+ //struct usb_interface_descriptor *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ //isdn_ctrl command;
+ int buffer_size;
+ int alt;
+ //unsigned long flags;
+
+ info("%s: Check if device matches .. (Vendor: 0x%x, Product: 0x%x)",
+ __func__, le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ retval = -ENODEV; //FIXME
+
+ /* See if the device offered us matches what we can accept */
+ if ((le16_to_cpu(udev->descriptor.idVendor != USB_M105_VENDOR_ID)) ||
+ (le16_to_cpu(udev->descriptor.idProduct != USB_M105_PRODUCT_ID)))
+ return -ENODEV;
+
+ /* this starts to become ascii art... */
+ hostif = interface->cur_altsetting;
+ alt = hostif->desc.bAlternateSetting;
+ ifnum = hostif->desc.bInterfaceNumber; // FIXME ?
+
+ if (alt != 0 || ifnum != 0) {
+ warn("ifnum %d, alt %d", ifnum, alt);
+ return -ENODEV;
+ }
+
+ /* Reject application specific intefaces
+ *
+ */
+ if (hostif->desc.bInterfaceClass != 255) {
+ info("%s: Device matched, but iface_desc[%d]->bInterfaceClass==%d !",
+ __func__, ifnum, hostif->desc.bInterfaceClass);
+ return -ENODEV;
+ }
+
+ info("%s: Device matched ... !", __func__);
+
+ cs = gigaset_getunassignedcs(driver);
+ if (!cs) {
+ warn("No free cardstate!");
+ return -ENODEV;
+ }
+ ucs = cs->hw.usb;
+
+#if 0
+ if (usb_set_configuration(udev, udev->config[0].desc.bConfigurationValue) < 0) {
+ warn("set_configuration failed");
+ goto error;
+ }
+
+
+ if (usb_set_interface(udev, ifnum/*==0*/, alt/*==0*/) < 0) {
+ warn("usb_set_interface failed, device %d interface %d altsetting %d",
+ udev->devnum, ifnum, alt);
+ goto error;
+ }
+#endif
+
+ /* set up the endpoint information */
+ /* check out the endpoints */
+ /* We will get 2 endpoints: One for sending commands to the device (bulk out) and one to
+ * poll messages from the device(int in).
+ * Therefore we will have an almost similiar situation as with our serial port handler.
+ * If an connection will be established, we will have to create data in/out pipes
+ * dynamically...
+ */
+
+ endpoint = &hostif->endpoint[0].desc;
+
+ buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
+ ucs->bulk_out_size = buffer_size;
+ ucs->bulk_out_endpointAddr = endpoint->bEndpointAddress;
+ ucs->bulk_out_buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!ucs->bulk_out_buffer) {
+ err("Couldn't allocate bulk_out_buffer");
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ ucs->bulk_out_urb = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->bulk_out_urb) {
+ err("Couldn't allocate bulk_out_buffer");
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ endpoint = &hostif->endpoint[1].desc;
+
+ atomic_set(&ucs->busy, 0);
+ ucs->udev = udev;
+ ucs->interface = interface;
+
+ ucs->read_urb = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->read_urb) {
+ err("No free urbs available");
+ retval = -ENOMEM;
+ goto error;
+ }
+ buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
+ ucs->rcvbuf_size = buffer_size;
+ ucs->int_in_endpointAddr = endpoint->bEndpointAddress;
+ cs->inbuf[0].rcvbuf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!cs->inbuf[0].rcvbuf) {
+ err("Couldn't allocate rcvbuf");
+ retval = -ENOMEM;
+ goto error;
+ }
+ /* Fill the interrupt urb and send it to the core */
+ usb_fill_int_urb(ucs->read_urb, udev,
+ usb_rcvintpipe(udev,
+ endpoint->bEndpointAddress & 0x0f),
+ cs->inbuf[0].rcvbuf, buffer_size,
+ gigaset_read_int_callback,
+ cs->inbuf + 0, endpoint->bInterval);
+
+ retval = usb_submit_urb(ucs->read_urb, SLAB_KERNEL);
+ if (retval) {
+ err("Could not submit URB!");
+ goto error;
+ }
+
+ /* tell common part that the device is ready */
+ if (startmode == SM_LOCKED)
+ atomic_set(&cs->mstate, MS_LOCKED);
+ if (!gigaset_start(cs)) {
+ tasklet_kill(&cs->write_tasklet);
+ retval = -ENODEV; //FIXME
+ goto error;
+ }
+
+ /* save address of controller structure */
+ usb_set_intfdata(interface, cs);
+
+ /* set up device sysfs */
+ gigaset_init_dev_sysfs(interface);
+ return 0;
+
+error:
+ if (ucs->read_urb)
+ usb_kill_urb(ucs->read_urb);
+ kfree(ucs->bulk_out_buffer);
+ if (ucs->bulk_out_urb != NULL)
+ usb_free_urb(ucs->bulk_out_urb);
+ kfree(cs->inbuf[0].rcvbuf);
+ if (ucs->read_urb != NULL)
+ usb_free_urb(ucs->read_urb);
+ ucs->read_urb = ucs->bulk_out_urb = NULL;
+ cs->inbuf[0].rcvbuf = ucs->bulk_out_buffer = NULL;
+ gigaset_unassign(cs);
+ return retval;
+}
+
+/**
+ * skel_disconnect
+ */
+static void gigaset_disconnect(struct usb_interface *interface)
+{
+ struct cardstate *cs;
+ struct usb_cardstate *ucs;
+
+ cs = usb_get_intfdata(interface);
+
+ /* clear device sysfs */
+ gigaset_free_dev_sysfs(interface);
+
+ usb_set_intfdata(interface, NULL);
+ ucs = cs->hw.usb;
+ usb_kill_urb(ucs->read_urb);
+ //info("GigaSet USB device #%d will be disconnected", minor);
+
+ gigaset_stop(cs);
+
+ tasklet_kill(&cs->write_tasklet);
+
+ usb_kill_urb(ucs->bulk_out_urb); /* FIXME: nur, wenn noetig */
+ //usb_kill_urb(ucs->urb_cmd_out); /* FIXME: nur, wenn noetig */
+
+ kfree(ucs->bulk_out_buffer);
+ if (ucs->bulk_out_urb != NULL)
+ usb_free_urb(ucs->bulk_out_urb);
+ //if(ucs->urb_cmd_out != NULL)
+ // usb_free_urb(ucs->urb_cmd_out);
+ kfree(cs->inbuf[0].rcvbuf);
+ if (ucs->read_urb != NULL)
+ usb_free_urb(ucs->read_urb);
+ ucs->read_urb = ucs->bulk_out_urb/*=ucs->urb_cmd_out*/=NULL;
+ cs->inbuf[0].rcvbuf = ucs->bulk_out_buffer = NULL;
+
+ gigaset_unassign(cs);
+}
+
+static struct gigaset_ops ops = {
+ gigaset_write_cmd,
+ gigaset_write_room,
+ gigaset_chars_in_buffer,
+ gigaset_brkchars,
+ gigaset_init_bchannel,
+ gigaset_close_bchannel,
+ gigaset_initbcshw,
+ gigaset_freebcshw,
+ gigaset_reinitbcshw,
+ gigaset_initcshw,
+ gigaset_freecshw,
+ gigaset_set_modem_ctrl,
+ gigaset_baud_rate,
+ gigaset_set_line_ctrl,
+ gigaset_m10x_send_skb,
+ gigaset_m10x_input,
+};
+
+/**
+ * usb_gigaset_init
+ * This function is called while kernel-module is loaded
+ */
+static int __init usb_gigaset_init(void)
+{
+ int result;
+
+ /* allocate memory for our driver state and intialize it */
+ if ((driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS,
+ GIGASET_MODULENAME, GIGASET_DEVNAME,
+ GIGASET_DEVFSNAME, &ops,
+ THIS_MODULE)) == NULL)
+ goto error;
+
+ /* allocate memory for our device state and intialize it */
+ cardstate = gigaset_initcs(driver, 1, 1, 0, cidmode, GIGASET_MODULENAME);
+ if (!cardstate)
+ goto error;
+
+ /* register this driver with the USB subsystem */
+ result = usb_register(&gigaset_usb_driver);
+ if (result < 0) {
+ err("usb_gigaset: usb_register failed (error %d)",
+ -result);
+ goto error;
+ }
+
+ info(DRIVER_AUTHOR);
+ info(DRIVER_DESC);
+ return 0;
+
+error: if (cardstate)
+ gigaset_freecs(cardstate);
+ cardstate = NULL;
+ if (driver)
+ gigaset_freedriver(driver);
+ driver = NULL;
+ return -1;
+}
+
+
+/**
+ * usb_gigaset_exit
+ * This function is called while unloading the kernel-module
+ */
+static void __exit usb_gigaset_exit(void)
+{
+ gigaset_blockdriver(driver); /* => probe will fail
+ * => no gigaset_start any more
+ */
+
+ gigaset_shutdown(cardstate);
+ /* from now on, no isdn callback should be possible */
+
+ /* deregister this driver with the USB subsystem */
+ usb_deregister(&gigaset_usb_driver);
+ /* this will call the disconnect-callback */
+ /* from now on, no disconnect/probe callback should be running */
+
+ gigaset_freecs(cardstate);
+ cardstate = NULL;
+ gigaset_freedriver(driver);
+ driver = NULL;
+}
+
+
+module_init(usb_gigaset_init);
+module_exit(usb_gigaset_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+
+MODULE_LICENSE("GPL");
--- linux-2.6.16-rc2/drivers/isdn/gigaset/asyncdata.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/asyncdata.c 2006-01-21 17:33:33.000000000 +0100
@@ -0,0 +1,597 @@
+/*
+ * Common data handling layer for ser_gigaset and usb_gigaset
+ *
+ * Copyright (c) 2005 by Tilman Schmidt <[email protected]>,
+ * Hansjoerg Lipp <[email protected]>,
+ * Stefan Eilers <[email protected]>.
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: asyncdata.c,v 1.2.2.7 2005/11/13 23:05:18 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/crc-ccitt.h>
+
+//#define GIG_M10x_STUFF_VOICE_DATA
+
+/* check if byte must be stuffed/escaped
+ * I'm not sure which data should be encoded.
+ * Therefore I will go the hard way and decode every value
+ * less than 0x20, the flag sequence and the control escape char.
+ */
+static inline int muststuff(unsigned char c)
+{
+ if (c < PPP_TRANS) return 1;
+ if (c == PPP_FLAG) return 1;
+ if (c == PPP_ESCAPE) return 1;
+ /* other possible candidates: */
+ /* 0x91: XON with parity set */
+ /* 0x93: XOFF with parity set */
+ return 0;
+}
+
+/* == data input =========================================================== */
+
+/* process a block of received bytes in command mode (modem response)
+ * Return value:
+ * number of processed bytes
+ */
+static inline int cmd_loop(unsigned char c, unsigned char *src, int numbytes,
+ struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ unsigned cbytes = cs->cbytes;
+ int inputstate = inbuf->inputstate;
+ int startbytes = numbytes;
+
+ for (;;) {
+ cs->respdata[cbytes] = c;
+ if (c == 10 || c == 13) {
+ dbg(DEBUG_TRANSCMD, "%s: End of Command (%d Bytes)",
+ __func__, cbytes);
+ cs->cbytes = cbytes;
+ gigaset_handle_modem_response(cs); /* can change cs->dle */
+ cbytes = 0;
+
+ if (cs->dle &&
+ !(inputstate & INS_DLE_command)) {
+ inputstate &= ~INS_command;
+ break;
+ }
+ } else {
+ /* advance in line buffer, checking for overflow */
+ if (cbytes < MAX_RESP_SIZE - 1)
+ cbytes++;
+ else
+ warn("response too large");
+ }
+
+ if (!numbytes)
+ break;
+ c = *src++;
+ --numbytes;
+ if (c == DLE_FLAG &&
+ (cs->dle || inputstate & INS_DLE_command)) {
+ inputstate |= INS_DLE_char;
+ break;
+ }
+ }
+
+ cs->cbytes = cbytes;
+ inbuf->inputstate = inputstate;
+
+ return startbytes - numbytes;
+}
+
+/* process a block of received bytes in lock mode (tty i/f)
+ * Return value:
+ * number of processed bytes
+ */
+static inline int lock_loop(unsigned char *src, int numbytes,
+ struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+
+ gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response", numbytes, src, 0);
+ gigaset_if_receive(cs, src, numbytes);
+
+ return numbytes;
+}
+
+/* process a block of received bytes in HDLC data mode
+ * Collect HDLC frames, undoing byte stuffing and watching for DLE escapes.
+ * When a frame is complete, check the FCS and pass valid frames to the LL.
+ * If DLE is encountered, return immediately to let the caller handle it.
+ * Return value:
+ * number of processed bytes
+ * numbytes (all bytes processed) on error --FIXME
+ */
+static inline int hdlc_loop(unsigned char c, unsigned char *src, int numbytes,
+ struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ struct bc_state *bcs = inbuf->bcs;
+ int inputstate;
+ __u16 fcs;
+ struct sk_buff *skb;
+ unsigned char error;
+ struct sk_buff *compskb;
+ int startbytes = numbytes;
+ int l;
+
+ IFNULLRETVAL(bcs, numbytes);
+ inputstate = bcs->inputstate;
+ fcs = bcs->fcs;
+ skb = bcs->skb;
+ IFNULLRETVAL(skb, numbytes);
+
+ if (unlikely(inputstate & INS_byte_stuff)) {
+ inputstate &= ~INS_byte_stuff;
+ goto byte_stuff;
+ }
+ for (;;) {
+ if (unlikely(c == PPP_ESCAPE)) {
+ if (unlikely(!numbytes)) {
+ inputstate |= INS_byte_stuff;
+ break;
+ }
+ c = *src++;
+ --numbytes;
+ if (unlikely(c == DLE_FLAG &&
+ (cs->dle ||
+ inbuf->inputstate & INS_DLE_command))) {
+ inbuf->inputstate |= INS_DLE_char;
+ inputstate |= INS_byte_stuff;
+ break;
+ }
+byte_stuff:
+ c ^= PPP_TRANS;
+#ifdef CONFIG_GIGASET_DEBUG
+ if (unlikely(!muststuff(c)))
+ dbg(DEBUG_HDLC,
+ "byte stuffed: 0x%02x", c);
+#endif
+ } else if (unlikely(c == PPP_FLAG)) {
+ if (unlikely(inputstate & INS_skip_frame)) {
+ if (!(inputstate & INS_have_data)) { /* 7E 7E */
+ //dbg(DEBUG_HDLC, "(7e)7e------------------------");
+#ifdef CONFIG_GIGASET_DEBUG
+ ++bcs->emptycount;
+#endif
+ } else
+ dbg(DEBUG_HDLC,
+ "7e----------------------------");
+
+ /* end of frame */
+ error = 1;
+ gigaset_rcv_error(NULL, cs, bcs);
+ } else if (!(inputstate & INS_have_data)) { /* 7E 7E */
+ //dbg(DEBUG_HDLC, "(7e)7e------------------------");
+#ifdef CONFIG_GIGASET_DEBUG
+ ++bcs->emptycount;
+#endif
+ break;
+ } else {
+ dbg(DEBUG_HDLC,
+ "7e----------------------------");
+
+ /* end of frame */
+ error = 0;
+
+ if (unlikely(fcs != PPP_GOODFCS)) {
+ err("Packet checksum at %lu failed, "
+ "packet is corrupted (%u bytes)!",
+ bcs->rcvbytes, skb->len);
+ compskb = NULL;
+ gigaset_rcv_error(compskb, cs, bcs);
+ error = 1;
+ } else {
+ if (likely((l = skb->len) > 2)) {
+ skb->tail -= 2;
+ skb->len -= 2;
+ } else {
+ dev_kfree_skb(skb);
+ skb = NULL;
+ inputstate |= INS_skip_frame;
+ if (l == 1) {
+ err("invalid packet size (1)!");
+ error = 1;
+ gigaset_rcv_error(NULL, cs, bcs);
+ }
+ }
+ if (likely(!(error ||
+ (inputstate &
+ INS_skip_frame)))) {
+ gigaset_rcv_skb(skb, cs, bcs);
+ }
+ }
+ }
+
+ if (unlikely(error))
+ if (skb)
+ dev_kfree_skb(skb);
+
+ fcs = PPP_INITFCS;
+ inputstate &= ~(INS_have_data | INS_skip_frame);
+ if (unlikely(bcs->ignore)) {
+ inputstate |= INS_skip_frame;
+ skb = NULL;
+ } else if (likely((skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)) {
+ skb_reserve(skb, HW_HDR_LEN);
+ } else {
+ warn("could not allocate new skb");
+ inputstate |= INS_skip_frame;
+ }
+
+ break;
+#ifdef CONFIG_GIGASET_DEBUG
+ } else if (unlikely(muststuff(c))) {
+ /* Should not happen. Possible after ZDLE=1<CR><LF>. */
+ dbg(DEBUG_HDLC, "not byte stuffed: 0x%02x", c);
+#endif
+ }
+
+ /* add character */
+
+#ifdef CONFIG_GIGASET_DEBUG
+ if (unlikely(!(inputstate & INS_have_data))) {
+ dbg(DEBUG_HDLC,
+ "7e (%d x) ================", bcs->emptycount);
+ bcs->emptycount = 0;
+ }
+#endif
+
+ inputstate |= INS_have_data;
+
+ if (likely(!(inputstate & INS_skip_frame))) {
+ if (unlikely(skb->len == SBUFSIZE)) {
+ warn("received packet too long");
+ dev_kfree_skb_any(skb);
+ skb = NULL;
+ inputstate |= INS_skip_frame;
+ break;
+ }
+ *gigaset_skb_put_quick(skb, 1) = c;
+ /* *__skb_put (skb, 1) = c; */
+ fcs = crc_ccitt_byte(fcs, c);
+ }
+
+ if (unlikely(!numbytes))
+ break;
+ c = *src++;
+ --numbytes;
+ if (unlikely(c == DLE_FLAG &&
+ (cs->dle ||
+ inbuf->inputstate & INS_DLE_command))) {
+ inbuf->inputstate |= INS_DLE_char;
+ break;
+ }
+ }
+ bcs->inputstate = inputstate;
+ bcs->fcs = fcs;
+ bcs->skb = skb;
+ return startbytes - numbytes;
+}
+
+/* process a block of received bytes in transparent data mode
+ * Invert bytes, undoing byte stuffing and watching for DLE escapes.
+ * If DLE is encountered, return immediately to let the caller handle it.
+ * Return value:
+ * number of processed bytes
+ * numbytes (all bytes processed) on error --FIXME
+ */
+static inline int iraw_loop(unsigned char c, unsigned char *src, int numbytes,
+ struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ struct bc_state *bcs = inbuf->bcs;
+ int inputstate;
+ struct sk_buff *skb;
+ int startbytes = numbytes;
+
+ IFNULLRETVAL(bcs, numbytes);
+ inputstate = bcs->inputstate;
+ skb = bcs->skb;
+ IFNULLRETVAL(skb, numbytes);
+
+ for (;;) {
+ /* add character */
+ inputstate |= INS_have_data;
+
+ if (likely(!(inputstate & INS_skip_frame))) {
+ if (unlikely(skb->len == SBUFSIZE)) {
+ //FIXME just pass skb up and allocate a new one
+ warn("received packet too long");
+ dev_kfree_skb_any(skb);
+ skb = NULL;
+ inputstate |= INS_skip_frame;
+ break;
+ }
+ *gigaset_skb_put_quick(skb, 1) = gigaset_invtab[c];
+ }
+
+ if (unlikely(!numbytes))
+ break;
+ c = *src++;
+ --numbytes;
+ if (unlikely(c == DLE_FLAG &&
+ (cs->dle ||
+ inbuf->inputstate & INS_DLE_command))) {
+ inbuf->inputstate |= INS_DLE_char;
+ break;
+ }
+ }
+
+ /* pass data up */
+ if (likely(inputstate & INS_have_data)) {
+ if (likely(!(inputstate & INS_skip_frame))) {
+ gigaset_rcv_skb(skb, cs, bcs);
+ }
+ inputstate &= ~(INS_have_data | INS_skip_frame);
+ if (unlikely(bcs->ignore)) {
+ inputstate |= INS_skip_frame;
+ skb = NULL;
+ } else if (likely((skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN))
+ != NULL)) {
+ skb_reserve(skb, HW_HDR_LEN);
+ } else {
+ warn("could not allocate new skb");
+ inputstate |= INS_skip_frame;
+ }
+ }
+
+ bcs->inputstate = inputstate;
+ bcs->skb = skb;
+ return startbytes - numbytes;
+}
+
+/* process a block of data received from the device
+ */
+void gigaset_m10x_input(struct inbuf_t *inbuf)
+{
+ struct cardstate *cs;
+ unsigned tail, head, numbytes;
+ unsigned char *src, c;
+ int procbytes;
+
+ head = atomic_read(&inbuf->head);
+ tail = atomic_read(&inbuf->tail);
+ dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);
+
+ if (head != tail) {
+ cs = inbuf->cs;
+ src = inbuf->data + head;
+ numbytes = (head > tail ? RBUFSIZE : tail) - head;
+ dbg(DEBUG_INTR, "processing %u bytes", numbytes);
+
+ while (numbytes) {
+ if (atomic_read(&cs->mstate) == MS_LOCKED) {
+ procbytes = lock_loop(src, numbytes, inbuf);
+ src += procbytes;
+ numbytes -= procbytes;
+ } else {
+ c = *src++;
+ --numbytes;
+ if (c == DLE_FLAG && (cs->dle ||
+ inbuf->inputstate & INS_DLE_command)) {
+ if (!(inbuf->inputstate & INS_DLE_char)) {
+ inbuf->inputstate |= INS_DLE_char;
+ goto nextbyte;
+ }
+ /* <DLE> <DLE> => <DLE> in data stream */
+ inbuf->inputstate &= ~INS_DLE_char;
+ }
+
+ if (!(inbuf->inputstate & INS_DLE_char)) {
+
+ /* FIXME Einfach je nach Modus Funktionszeiger in cs setzen [hier+hdlc_loop]? */
+ /* FIXME Spart folgendes "if" und ermoeglicht andere Protokolle */
+ if (inbuf->inputstate & INS_command)
+ procbytes = cmd_loop(c, src, numbytes, inbuf);
+ else if (inbuf->bcs->proto2 == ISDN_PROTO_L2_HDLC)
+ procbytes = hdlc_loop(c, src, numbytes, inbuf);
+ else
+ procbytes = iraw_loop(c, src, numbytes, inbuf);
+
+ src += procbytes;
+ numbytes -= procbytes;
+ } else { /* DLE-char */
+ inbuf->inputstate &= ~INS_DLE_char;
+ switch (c) {
+ case 'X': /*begin of command*/
+#ifdef CONFIG_GIGASET_DEBUG
+ if (inbuf->inputstate & INS_command)
+ err("received <DLE> 'X' in command mode");
+#endif
+ inbuf->inputstate |=
+ INS_command | INS_DLE_command;
+ break;
+ case '.': /*end of command*/
+#ifdef CONFIG_GIGASET_DEBUG
+ if (!(inbuf->inputstate & INS_command))
+ err("received <DLE> '.' in hdlc mode");
+#endif
+ inbuf->inputstate &= cs->dle ?
+ ~(INS_DLE_command|INS_command)
+ : ~INS_DLE_command;
+ break;
+ //case DLE_FLAG: /*DLE_FLAG in data stream*/ /* schon oben behandelt! */
+ default:
+ err("received 0x10 0x%02x!", (int) c);
+ /* FIXME: reset driver?? */
+ }
+ }
+ }
+nextbyte:
+ if (!numbytes) {
+ /* end of buffer, check for wrap */
+ if (head > tail) {
+ head = 0;
+ src = inbuf->data;
+ numbytes = tail;
+ } else {
+ head = tail;
+ break;
+ }
+ }
+ }
+
+ dbg(DEBUG_INTR, "setting head to %u", head);
+ atomic_set(&inbuf->head, head);
+ }
+}
+
+
+/* == data output ========================================================== */
+
+/* Encoding of a PPP packet into an octet stuffed HDLC frame
+ * with FCS, opening and closing flags.
+ * parameters:
+ * skb skb containing original packet (freed upon return)
+ * head number of headroom bytes to allocate in result skb
+ * tail number of tailroom bytes to allocate in result skb
+ * Return value:
+ * pointer to newly allocated skb containing the result frame
+ */
+static struct sk_buff *HDLC_Encode(struct sk_buff *skb, int head, int tail)
+{
+ struct sk_buff *hdlc_skb;
+ __u16 fcs;
+ unsigned char c;
+ unsigned char *cp;
+ int len;
+ unsigned int stuf_cnt;
+
+ stuf_cnt = 0;
+ fcs = PPP_INITFCS;
+ cp = skb->data;
+ len = skb->len;
+ while (len--) {
+ if (muststuff(*cp))
+ stuf_cnt++;
+ fcs = crc_ccitt_byte(fcs, *cp++);
+ }
+ fcs ^= 0xffff; /* complement */
+
+ /* size of new buffer: original size + number of stuffing bytes
+ * + 2 bytes FCS + 2 stuffing bytes for FCS (if needed) + 2 flag bytes
+ */
+ hdlc_skb = dev_alloc_skb(skb->len + stuf_cnt + 6 + tail + head);
+ if (!hdlc_skb) {
+ err("unable to allocate memory for HDLC encoding!");
+ dev_kfree_skb(skb);
+ return NULL;
+ }
+ skb_reserve(hdlc_skb, head);
+
+ /* Copy acknowledge request into new skb */
+ memcpy(hdlc_skb->head, skb->head, 2);
+
+ /* Add flag sequence in front of everything.. */
+ *(skb_put(hdlc_skb, 1)) = PPP_FLAG;
+
+ /* Perform byte stuffing while copying data. */
+ while (skb->len--) {
+ if (muststuff(*skb->data)) {
+ *(skb_put(hdlc_skb, 1)) = PPP_ESCAPE;
+ *(skb_put(hdlc_skb, 1)) = (*skb->data++) ^ PPP_TRANS;
+ } else
+ *(skb_put(hdlc_skb, 1)) = *skb->data++;
+ }
+
+ /* Finally add FCS (byte stuffed) and flag sequence */
+ c = (fcs & 0x00ff); /* least significant byte first */
+ if (muststuff(c)) {
+ *(skb_put(hdlc_skb, 1)) = PPP_ESCAPE;
+ c ^= PPP_TRANS;
+ }
+ *(skb_put(hdlc_skb, 1)) = c;
+
+ c = ((fcs >> 8) & 0x00ff);
+ if (muststuff(c)) {
+ *(skb_put(hdlc_skb, 1)) = PPP_ESCAPE;
+ c ^= PPP_TRANS;
+ }
+ *(skb_put(hdlc_skb, 1)) = c;
+
+ *(skb_put(hdlc_skb, 1)) = PPP_FLAG;
+
+ dev_kfree_skb(skb);
+ return hdlc_skb;
+}
+
+/* Encoding of a raw packet into an octet stuffed bit inverted frame
+ * parameters:
+ * skb skb containing original packet (freed upon return)
+ * head number of headroom bytes to allocate in result skb
+ * tail number of tailroom bytes to allocate in result skb
+ * Return value:
+ * pointer to newly allocated skb containing the result frame
+ */
+static struct sk_buff *iraw_encode(struct sk_buff *skb, int head, int tail)
+{
+ struct sk_buff *iraw_skb;
+ unsigned char c;
+ unsigned char *cp;
+ int len;
+
+ /* worst case: every byte must be stuffed */
+ iraw_skb = dev_alloc_skb(2*skb->len + tail + head);
+ if (!iraw_skb) {
+ err("unable to allocate memory for HDLC encoding!");
+ dev_kfree_skb(skb);
+ return NULL;
+ }
+ skb_reserve(iraw_skb, head);
+
+ cp = skb->data;
+ len = skb->len;
+ while (len--) {
+ c = gigaset_invtab[*cp++];
+ if (c == DLE_FLAG)
+ *(skb_put(iraw_skb, 1)) = c;
+ *(skb_put(iraw_skb, 1)) = c;
+ }
+ dev_kfree_skb(skb);
+ return iraw_skb;
+}
+
+/* gigaset_send_skb
+ * called by common.c to queue an skb for sending
+ * and start transmission if necessary
+ * parameters:
+ * B Channel control structure
+ * skb
+ * Return value:
+ * number of bytes accepted for sending
+ * (skb->len if ok, 0 if out of buffer space)
+ * or error code (< 0, eg. -EINVAL)
+ */
+int gigaset_m10x_send_skb(struct bc_state *bcs, struct sk_buff *skb)
+{
+ unsigned len;
+
+ IFNULLRETVAL(bcs, -EFAULT);
+ IFNULLRETVAL(skb, -EFAULT);
+ len = skb->len;
+
+ if (bcs->proto2 == ISDN_PROTO_L2_HDLC)
+ skb = HDLC_Encode(skb, HW_HDR_LEN, 0);
+ else
+ skb = iraw_encode(skb, HW_HDR_LEN, 0);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_queue_tail(&bcs->squeue, skb);
+ tasklet_schedule(&bcs->cs->write_tasklet);
+
+ return len; /* ok so far */
+}

2006-02-11 15:02:25

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 7/9] isdn4linux: Siemens Gigaset drivers - direct USB connection

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch adds the main source file of the connection-specific module
"bas_gigaset", the hardware driver for Gigaset base stations connected
directly to the computer via USB. It contains the code for handling
probe/disconnect, AT command/response transmission, and call setup and
termination.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

drivers/isdn/gigaset/bas-gigaset.c | 2365 +++++++++++++++++++++++++++++++++++++
1 files changed, 2365 insertions(+)

--- linux-2.6.16-rc2/drivers/isdn/gigaset/bas-gigaset.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/bas-gigaset.c 2006-02-11 15:20:26.000000000 +0100
@@ -0,0 +1,2365 @@
+/*
+ * USB driver for Gigaset 307x base via direct USB connection.
+ *
+ * Copyright (c) 2001 by Hansjoerg Lipp <[email protected]>,
+ * Tilman Schmidt <[email protected]>,
+ * Stefan Eilers <[email protected]>.
+ *
+ * Based on usb-gigaset.c.
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: bas-gigaset.c,v 1.52.4.19 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/usb.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+/* Version Information */
+#define DRIVER_AUTHOR "Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>, Stefan Eilers <[email protected]>"
+#define DRIVER_DESC "USB Driver for Gigaset 307x"
+
+
+/* Module parameters */
+
+static int startmode = SM_ISDN;
+static int cidmode = 1;
+
+module_param(startmode, int, S_IRUGO);
+module_param(cidmode, int, S_IRUGO);
+MODULE_PARM_DESC(startmode, "start in isdn4linux mode");
+MODULE_PARM_DESC(cidmode, "Call-ID mode");
+
+#define GIGASET_MINORS 1
+#define GIGASET_MINOR 16
+#define GIGASET_MODULENAME "bas_gigaset"
+#define GIGASET_DEVFSNAME "gig/bas/"
+#define GIGASET_DEVNAME "ttyGB"
+
+#define IF_WRITEBUF 256 //FIXME
+
+/* Values for the Gigaset 307x */
+#define USB_GIGA_VENDOR_ID 0x0681
+#define USB_GIGA_PRODUCT_ID 0x0001
+#define USB_4175_PRODUCT_ID 0x0002
+#define USB_SX303_PRODUCT_ID 0x0021
+#define USB_SX353_PRODUCT_ID 0x0022
+
+/* table of devices that work with this driver */
+static struct usb_device_id gigaset_table [] = {
+ { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_GIGA_PRODUCT_ID) },
+ { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_4175_PRODUCT_ID) },
+ { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_SX303_PRODUCT_ID) },
+ { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_SX353_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, gigaset_table);
+
+/* Get a minor range for your devices from the usb maintainer */
+#define USB_SKEL_MINOR_BASE 200
+
+/*======================= local function prototypes =============================*/
+
+/* This function is called if a new device is connected to the USB port. It
+ * checks whether this new device belongs to this driver.
+ */
+static int gigaset_probe(struct usb_interface *interface,
+ const struct usb_device_id *id);
+
+/* Function will be called if the device is unplugged */
+static void gigaset_disconnect(struct usb_interface *interface);
+
+
+/*==============================================================================*/
+
+struct bas_cardstate {
+ struct usb_device *udev; /* USB device pointer */
+ struct usb_interface *interface; /* interface for this device */
+ unsigned char minor; /* starting minor number */
+
+ struct urb *urb_ctrl; /* control pipe default URB */
+ struct usb_ctrlrequest dr_ctrl;
+ struct timer_list timer_ctrl; /* control request timeout */
+
+ struct timer_list timer_atrdy; /* AT command ready timeout */
+ struct urb *urb_cmd_out; /* for sending AT commands */
+ struct usb_ctrlrequest dr_cmd_out;
+ int retry_cmd_out;
+
+ struct urb *urb_cmd_in; /* for receiving AT replies */
+ struct usb_ctrlrequest dr_cmd_in;
+ struct timer_list timer_cmd_in; /* receive request timeout */
+ unsigned char *rcvbuf; /* AT reply receive buffer */
+
+ struct urb *urb_int_in; /* URB for interrupt pipe */
+ unsigned char int_in_buf[3];
+
+ spinlock_t lock; /* locks all following */
+ atomic_t basstate; /* bitmap (BS_*) */
+ int pending; /* uncompleted base request */
+ int rcvbuf_size; /* size of AT receive buffer */
+ /* 0: no receive in progress */
+ int retry_cmd_in; /* receive req retry count */
+};
+
+/* status of direct USB connection to 307x base (bits in basstate) */
+#define BS_ATOPEN 0x001
+#define BS_B1OPEN 0x002
+#define BS_B2OPEN 0x004
+#define BS_ATREADY 0x008
+#define BS_INIT 0x010
+#define BS_ATTIMER 0x020
+
+
+static struct gigaset_driver *driver = NULL;
+static struct cardstate *cardstate = NULL;
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver gigaset_usb_driver = {
+ .name = GIGASET_MODULENAME,
+ .probe = gigaset_probe,
+ .disconnect = gigaset_disconnect,
+ .id_table = gigaset_table,
+};
+
+/* get message text for USB status code
+ */
+static char *get_usb_statmsg(int status)
+{
+ static char unkmsg[28];
+
+ switch (status) {
+ case 0:
+ return "success";
+ case -ENOENT:
+ return "canceled";
+ case -ECONNRESET:
+ return "canceled (async)";
+ case -EINPROGRESS:
+ return "pending";
+ case -EPROTO:
+ return "bit stuffing or unknown USB error";
+ case -EILSEQ:
+ return "Illegal byte sequence (CRC mismatch)";
+ case -EPIPE:
+ return "babble detect or endpoint stalled";
+ case -ENOSR:
+ return "buffer error";
+ case -ETIMEDOUT:
+ return "timed out";
+ case -ENODEV:
+ return "device not present";
+ case -EREMOTEIO:
+ return "short packet detected";
+ case -EXDEV:
+ return "partial isochronous transfer";
+ case -EINVAL:
+ return "invalid argument";
+ case -ENXIO:
+ return "URB already queued";
+ case -EAGAIN:
+ return "isochronous start frame too early or too much scheduled";
+ case -EFBIG:
+ return "too many isochronous frames requested";
+ case -EMSGSIZE:
+ return "endpoint message size zero";
+ case -ESHUTDOWN:
+ return "endpoint shutdown";
+ case -EBUSY:
+ return "another request pending";
+ default:
+ snprintf(unkmsg, sizeof(unkmsg), "unknown error %d", status);
+ return unkmsg;
+ }
+}
+
+/* usb_pipetype_str
+ * retrieve string representation of USB pipe type
+ */
+static inline char *usb_pipetype_str(int pipe)
+{
+ if (usb_pipeisoc(pipe))
+ return "Isoc";
+ if (usb_pipeint(pipe))
+ return "Int";
+ if (usb_pipecontrol(pipe))
+ return "Ctrl";
+ if (usb_pipebulk(pipe))
+ return "Bulk";
+ return "?";
+}
+
+/* dump_urb
+ * write content of URB to syslog for debugging
+ */
+static inline void dump_urb(enum debuglevel level, const char *tag,
+ struct urb *urb)
+{
+#ifdef CONFIG_GIGASET_DEBUG
+ int i;
+ IFNULLRET(tag);
+ dbg(level, "%s urb(0x%08lx)->{", tag, (unsigned long) urb);
+ if (urb) {
+ dbg(level,
+ " dev=0x%08lx, pipe=%s:EP%d/DV%d:%s, "
+ "status=%d, hcpriv=0x%08lx, transfer_flags=0x%x,",
+ (unsigned long) urb->dev,
+ usb_pipetype_str(urb->pipe),
+ usb_pipeendpoint(urb->pipe), usb_pipedevice(urb->pipe),
+ usb_pipein(urb->pipe) ? "in" : "out",
+ urb->status, (unsigned long) urb->hcpriv,
+ urb->transfer_flags);
+ dbg(level,
+ " transfer_buffer=0x%08lx[%d], actual_length=%d, "
+ "bandwidth=%d, setup_packet=0x%08lx,",
+ (unsigned long) urb->transfer_buffer,
+ urb->transfer_buffer_length, urb->actual_length,
+ urb->bandwidth, (unsigned long) urb->setup_packet);
+ dbg(level,
+ " start_frame=%d, number_of_packets=%d, interval=%d, "
+ "error_count=%d,",
+ urb->start_frame, urb->number_of_packets, urb->interval,
+ urb->error_count);
+ dbg(level,
+ " context=0x%08lx, complete=0x%08lx, iso_frame_desc[]={",
+ (unsigned long) urb->context,
+ (unsigned long) urb->complete);
+ for (i = 0; i < urb->number_of_packets; i++) {
+ struct usb_iso_packet_descriptor *pifd = &urb->iso_frame_desc[i];
+ dbg(level,
+ " {offset=%u, length=%u, actual_length=%u, "
+ "status=%u}",
+ pifd->offset, pifd->length, pifd->actual_length,
+ pifd->status);
+ }
+ }
+ dbg(level, "}}");
+#endif
+}
+
+/* read/set modem control bits etc. (m10x only) */
+static int gigaset_set_modem_ctrl(struct cardstate *cs, unsigned old_state,
+ unsigned new_state)
+{
+ return -EINVAL;
+}
+
+static int gigaset_baud_rate(struct cardstate *cs, unsigned cflag)
+{
+ return -EINVAL;
+}
+
+static int gigaset_set_line_ctrl(struct cardstate *cs, unsigned cflag)
+{
+ return -EINVAL;
+}
+
+/* error_hangup
+ * hang up any existing connection because of an unrecoverable error
+ * This function may be called from any context and takes care of scheduling
+ * the necessary actions for execution outside of interrupt context.
+ * argument:
+ * B channel control structure
+ */
+static inline void error_hangup(struct bc_state *bcs)
+{
+ struct cardstate *cs = bcs->cs;
+
+ dbg(DEBUG_ANY,
+ "%s: scheduling HUP for channel %d", __func__, bcs->channel);
+
+ if (!gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL)) {
+ //FIXME what should we do?
+ return;
+ }
+
+ gigaset_schedule_event(cs);
+}
+
+/* error_reset
+ * reset Gigaset device because of an unrecoverable error
+ * This function may be called from any context and takes care of scheduling
+ * the necessary actions for execution outside of interrupt context.
+ * argument:
+ * controller state structure
+ */
+static inline void error_reset(struct cardstate *cs)
+{
+ //FIXME try to recover without bothering the user
+ err("unrecoverable error - please disconnect the Gigaset base to reset");
+}
+
+/* check_pending
+ * check for completion of pending control request
+ * parameter:
+ * urb USB request block of completed request
+ * urb->context = hardware specific controller state structure
+ */
+static void check_pending(struct bas_cardstate *ucs)
+{
+ unsigned long flags;
+
+ IFNULLRET(ucs);
+ IFNULLRET(cardstate);
+
+ spin_lock_irqsave(&ucs->lock, flags);
+ switch (ucs->pending) {
+ case 0:
+ break;
+ case HD_OPEN_ATCHANNEL:
+ if (atomic_read(&ucs->basstate) & BS_ATOPEN)
+ ucs->pending = 0;
+ break;
+ case HD_OPEN_B1CHANNEL:
+ if (atomic_read(&ucs->basstate) & BS_B1OPEN)
+ ucs->pending = 0;
+ break;
+ case HD_OPEN_B2CHANNEL:
+ if (atomic_read(&ucs->basstate) & BS_B2OPEN)
+ ucs->pending = 0;
+ break;
+ case HD_CLOSE_ATCHANNEL:
+ if (!(atomic_read(&ucs->basstate) & BS_ATOPEN))
+ ucs->pending = 0;
+ //wake_up_interruptible(cs->initwait);
+ //FIXME need own wait queue?
+ break;
+ case HD_CLOSE_B1CHANNEL:
+ if (!(atomic_read(&ucs->basstate) & BS_B1OPEN))
+ ucs->pending = 0;
+ break;
+ case HD_CLOSE_B2CHANNEL:
+ if (!(atomic_read(&ucs->basstate) & BS_B2OPEN))
+ ucs->pending = 0;
+ break;
+ case HD_DEVICE_INIT_ACK: /* no reply expected */
+ ucs->pending = 0;
+ break;
+ /* HD_READ_ATMESSAGE, HD_WRITE_ATMESSAGE, HD_RESET_INTERRUPTPIPE
+ * are handled separately and should never end up here
+ */
+ default:
+ warn("unknown pending request 0x%02x cleared", ucs->pending);
+ ucs->pending = 0;
+ }
+
+ if (!ucs->pending)
+ del_timer(&ucs->timer_ctrl);
+
+ spin_unlock_irqrestore(&ucs->lock, flags);
+}
+
+/* cmd_in_timeout
+ * timeout routine for command input request
+ * argument:
+ * controller state structure
+ */
+static void cmd_in_timeout(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ struct bas_cardstate *ucs;
+ unsigned long flags;
+
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ spin_lock_irqsave(&cs->lock, flags);
+ if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_USBREQ, "%s: disconnected", __func__);
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+ if (!ucs->rcvbuf_size) {
+ dbg(DEBUG_USBREQ, "%s: no receive in progress", __func__);
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&cs->lock, flags);
+
+ err("timeout reading AT response");
+ error_reset(cs); //FIXME retry?
+}
+
+
+static void read_ctrl_callback(struct urb *urb, struct pt_regs *regs);
+
+/* atread_submit
+ * submit an HD_READ_ATMESSAGE command URB
+ * parameters:
+ * cs controller state structure
+ * timeout timeout in 1/10 sec., 0: none
+ * return value:
+ * 0 on success
+ * -EINVAL if a NULL pointer is encountered somewhere
+ * -EBUSY if another request is pending
+ * any URB submission error code
+ */
+static int atread_submit(struct cardstate *cs, int timeout)
+{
+ struct bas_cardstate *ucs;
+ int ret;
+
+ IFNULLRETVAL(cs, -EINVAL);
+ ucs = cs->hw.bas;
+ IFNULLRETVAL(ucs, -EINVAL);
+ IFNULLRETVAL(ucs->urb_cmd_in, -EINVAL);
+
+ dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)", ucs->rcvbuf_size);
+
+ if (ucs->urb_cmd_in->status == -EINPROGRESS) {
+ err("could not submit HD_READ_ATMESSAGE: URB busy");
+ return -EBUSY;
+ }
+
+ ucs->dr_cmd_in.bRequestType = IN_VENDOR_REQ;
+ ucs->dr_cmd_in.bRequest = HD_READ_ATMESSAGE;
+ ucs->dr_cmd_in.wValue = 0;
+ ucs->dr_cmd_in.wIndex = 0;
+ ucs->dr_cmd_in.wLength = cpu_to_le16(ucs->rcvbuf_size);
+ usb_fill_control_urb(ucs->urb_cmd_in, ucs->udev,
+ usb_rcvctrlpipe(ucs->udev, 0),
+ (unsigned char*) & ucs->dr_cmd_in,
+ ucs->rcvbuf, ucs->rcvbuf_size,
+ read_ctrl_callback, cs->inbuf);
+
+ if ((ret = usb_submit_urb(ucs->urb_cmd_in, SLAB_ATOMIC)) != 0) {
+ err("could not submit HD_READ_ATMESSAGE: %s",
+ get_usb_statmsg(ret));
+ return ret;
+ }
+
+ if (timeout > 0) {
+ dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout);
+ ucs->timer_cmd_in.expires = jiffies + timeout * HZ / 10;
+ ucs->timer_cmd_in.data = (unsigned long) cs;
+ ucs->timer_cmd_in.function = cmd_in_timeout;
+ add_timer(&ucs->timer_cmd_in);
+ }
+ return 0;
+}
+
+static void stopurbs(struct bas_bc_state *);
+static int start_cbsend(struct cardstate *);
+
+/* set/clear bits in base connection state
+ */
+inline static void update_basstate(struct bas_cardstate *ucs,
+ int set, int clear)
+{
+ unsigned long flags;
+ int state;
+
+ spin_lock_irqsave(&ucs->lock, flags);
+ state = atomic_read(&ucs->basstate);
+ state &= ~clear;
+ state |= set;
+ atomic_set(&ucs->basstate, state);
+ spin_unlock_irqrestore(&ucs->lock, flags);
+}
+
+
+/* read_int_callback
+ * USB completion handler for interrupt pipe input
+ * called by the USB subsystem in interrupt context
+ * parameter:
+ * urb USB request block
+ * urb->context = controller state structure
+ */
+static void read_int_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct cardstate *cs;
+ struct bas_cardstate *ucs;
+ struct bc_state *bcs;
+ unsigned long flags;
+ int status;
+ unsigned l;
+ int channel;
+
+ IFNULLRET(urb);
+ cs = (struct cardstate *) urb->context;
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ if (unlikely(!atomic_read(&cs->connected))) {
+ warn("%s: disconnected", __func__);
+ return;
+ }
+
+ switch (urb->status) {
+ case 0: /* success */
+ break;
+ case -ENOENT: /* canceled */
+ case -ECONNRESET: /* canceled (async) */
+ case -EINPROGRESS: /* pending */
+ /* ignore silently */
+ dbg(DEBUG_USBREQ,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ return;
+ default: /* severe trouble */
+ warn("interrupt read: %s", get_usb_statmsg(urb->status));
+ //FIXME corrective action? resubmission always ok?
+ goto resubmit;
+ }
+
+ l = (unsigned) ucs->int_in_buf[1] +
+ (((unsigned) ucs->int_in_buf[2]) << 8);
+
+ dbg(DEBUG_USBREQ,
+ "<-------%d: 0x%02x (%u [0x%02x 0x%02x])", urb->actual_length,
+ (int)ucs->int_in_buf[0], l,
+ (int)ucs->int_in_buf[1], (int)ucs->int_in_buf[2]);
+
+ channel = 0;
+
+ switch (ucs->int_in_buf[0]) {
+ case HD_DEVICE_INIT_OK:
+ update_basstate(ucs, BS_INIT, 0);
+ break;
+
+ case HD_READY_SEND_ATDATA:
+ del_timer(&ucs->timer_atrdy);
+ update_basstate(ucs, BS_ATREADY, BS_ATTIMER);
+ start_cbsend(cs);
+ break;
+
+ case HD_OPEN_B2CHANNEL_ACK:
+ ++channel;
+ case HD_OPEN_B1CHANNEL_ACK:
+ bcs = cs->bcs + channel;
+ update_basstate(ucs, BS_B1OPEN << channel, 0);
+ gigaset_bchannel_up(bcs);
+ break;
+
+ case HD_OPEN_ATCHANNEL_ACK:
+ update_basstate(ucs, BS_ATOPEN, 0);
+ start_cbsend(cs);
+ break;
+
+ case HD_CLOSE_B2CHANNEL_ACK:
+ ++channel;
+ case HD_CLOSE_B1CHANNEL_ACK:
+ bcs = cs->bcs + channel;
+ update_basstate(ucs, 0, BS_B1OPEN << channel);
+ stopurbs(bcs->hw.bas);
+ gigaset_bchannel_down(bcs);
+ break;
+
+ case HD_CLOSE_ATCHANNEL_ACK:
+ update_basstate(ucs, 0, BS_ATOPEN);
+ break;
+
+ case HD_B2_FLOW_CONTROL:
+ ++channel;
+ case HD_B1_FLOW_CONTROL:
+ bcs = cs->bcs + channel;
+ atomic_add((l - BAS_NORMFRAME) * BAS_CORRFRAMES,
+ &bcs->hw.bas->corrbytes);
+ dbg(DEBUG_ISO,
+ "Flow control (channel %d, sub %d): 0x%02x => %d",
+ channel, bcs->hw.bas->numsub, l,
+ atomic_read(&bcs->hw.bas->corrbytes));
+ break;
+
+ case HD_RECEIVEATDATA_ACK: /* AT response ready to be received */
+ if (!l) {
+ warn("HD_RECEIVEATDATA_ACK with length 0 ignored");
+ break;
+ }
+ spin_lock_irqsave(&cs->lock, flags);
+ if (ucs->rcvbuf_size) {
+ spin_unlock_irqrestore(&cs->lock, flags);
+ err("receive AT data overrun, %d bytes lost", l);
+ error_reset(cs); //FIXME reschedule
+ break;
+ }
+ if ((ucs->rcvbuf = kmalloc(l, GFP_ATOMIC)) == NULL) {
+ spin_unlock_irqrestore(&cs->lock, flags);
+ err("%s: out of memory, %d bytes lost", __func__, l);
+ error_reset(cs); //FIXME reschedule
+ break;
+ }
+ ucs->rcvbuf_size = l;
+ ucs->retry_cmd_in = 0;
+ if ((status = atread_submit(cs, BAS_TIMEOUT)) < 0) {
+ kfree(ucs->rcvbuf);
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+ error_reset(cs); //FIXME reschedule
+ }
+ spin_unlock_irqrestore(&cs->lock, flags);
+ break;
+
+ case HD_RESET_INTERRUPT_PIPE_ACK:
+ dbg(DEBUG_USBREQ, "HD_RESET_INTERRUPT_PIPE_ACK");
+ break;
+
+ case HD_SUSPEND_END:
+ dbg(DEBUG_USBREQ, "HD_SUSPEND_END");
+ break;
+
+ default:
+ warn("unknown Gigaset signal 0x%02x (%u) ignored",
+ (int) ucs->int_in_buf[0], l);
+ }
+
+ check_pending(ucs);
+
+resubmit:
+ status = usb_submit_urb(urb, SLAB_ATOMIC);
+ if (unlikely(status)) {
+ err("could not resubmit interrupt URB: %s",
+ get_usb_statmsg(status));
+ error_reset(cs);
+ }
+}
+
+/* read_ctrl_callback
+ * USB completion handler for control pipe input
+ * called by the USB subsystem in interrupt context
+ * parameter:
+ * urb USB request block
+ * urb->context = inbuf structure for controller state
+ */
+static void read_ctrl_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct cardstate *cs;
+ struct bas_cardstate *ucs;
+ unsigned numbytes;
+ unsigned long flags;
+ struct inbuf_t *inbuf;
+ int have_data = 0;
+
+ IFNULLRET(urb);
+ inbuf = (struct inbuf_t *) urb->context;
+ IFNULLRET(inbuf);
+ cs = inbuf->cs;
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ spin_lock_irqsave(&cs->lock, flags);
+ if (!atomic_read(&cs->connected)) {
+ warn("%s: disconnected", __func__);
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+
+ if (!ucs->rcvbuf_size) {
+ warn("%s: no receive in progress", __func__);
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+
+ del_timer(&ucs->timer_cmd_in);
+
+ switch (urb->status) {
+ case 0: /* normal completion */
+ numbytes = urb->actual_length;
+ if (unlikely(numbytes == 0)) {
+ warn("control read: empty block received");
+ goto retry;
+ }
+ if (unlikely(numbytes != ucs->rcvbuf_size)) {
+ warn("control read: received %d chars, expected %d",
+ numbytes, ucs->rcvbuf_size);
+ if (numbytes > ucs->rcvbuf_size)
+ numbytes = ucs->rcvbuf_size;
+ }
+
+ /* copy received bytes to inbuf */
+ have_data = gigaset_fill_inbuf(inbuf, ucs->rcvbuf, numbytes);
+
+ if (unlikely(numbytes < ucs->rcvbuf_size)) {
+ /* incomplete - resubmit for remaining bytes */
+ ucs->rcvbuf_size -= numbytes;
+ ucs->retry_cmd_in = 0;
+ goto retry;
+ }
+ break;
+
+ case -ENOENT: /* canceled */
+ case -ECONNRESET: /* canceled (async) */
+ case -EINPROGRESS: /* pending */
+ /* no action necessary */
+ dbg(DEBUG_USBREQ,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ break;
+
+ default: /* severe trouble */
+ warn("control read: %s", get_usb_statmsg(urb->status));
+ retry:
+ if (ucs->retry_cmd_in++ < BAS_RETRY) {
+ notice("control read: retry %d", ucs->retry_cmd_in);
+ if (atread_submit(cs, BAS_TIMEOUT) >= 0) {
+ /* resubmitted - bypass regular exit block */
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return;
+ }
+ } else {
+ err("control read: giving up after %d tries",
+ ucs->retry_cmd_in);
+ }
+ error_reset(cs);
+ }
+
+ kfree(ucs->rcvbuf);
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ if (have_data) {
+ dbg(DEBUG_INTR, "%s-->BH", __func__);
+ gigaset_schedule_event(cs);
+ }
+}
+
+/* read_iso_callback
+ * USB completion handler for B channel isochronous input
+ * called by the USB subsystem in interrupt context
+ * parameter:
+ * urb USB request block of completed request
+ * urb->context = bc_state structure
+ */
+static void read_iso_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct bc_state *bcs;
+ struct bas_bc_state *ubc;
+ unsigned long flags;
+ int i, rc;
+
+ IFNULLRET(urb);
+ IFNULLRET(urb->context);
+ IFNULLRET(cardstate);
+
+ /* status codes not worth bothering the tasklet with */
+ if (unlikely(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+ urb->status == -EINPROGRESS)) {
+ dbg(DEBUG_ISO,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ return;
+ }
+
+ bcs = (struct bc_state *) urb->context;
+ ubc = bcs->hw.bas;
+ IFNULLRET(ubc);
+
+ spin_lock_irqsave(&ubc->isoinlock, flags);
+ if (likely(ubc->isoindone == NULL)) {
+ /* pass URB to tasklet */
+ ubc->isoindone = urb;
+ tasklet_schedule(&ubc->rcvd_tasklet);
+ } else {
+ /* tasklet still busy, drop data and resubmit URB */
+ ubc->loststatus = urb->status;
+ for (i = 0; i < BAS_NUMFRAMES; i++) {
+ ubc->isoinlost += urb->iso_frame_desc[i].actual_length;
+ if (unlikely(urb->iso_frame_desc[i].status != 0 &&
+ urb->iso_frame_desc[i].status != -EINPROGRESS)) {
+ ubc->loststatus = urb->iso_frame_desc[i].status;
+ }
+ urb->iso_frame_desc[i].status = 0;
+ urb->iso_frame_desc[i].actual_length = 0;
+ }
+ if (likely(atomic_read(&ubc->running))) {
+ urb->dev = bcs->cs->hw.bas->udev; /* clobbered by USB subsystem */
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->number_of_packets = BAS_NUMFRAMES;
+ dbg(DEBUG_ISO, "%s: isoc read overrun/resubmit", __func__);
+ rc = usb_submit_urb(urb, SLAB_ATOMIC);
+ if (unlikely(rc != 0)) {
+ err("could not resubmit isochronous read URB: %s",
+ get_usb_statmsg(rc));
+ dump_urb(DEBUG_ISO, "isoc read", urb);
+ error_hangup(bcs);
+ }
+ }
+ }
+ spin_unlock_irqrestore(&ubc->isoinlock, flags);
+}
+
+/* write_iso_callback
+ * USB completion handler for B channel isochronous output
+ * called by the USB subsystem in interrupt context
+ * parameter:
+ * urb USB request block of completed request
+ * urb->context = isow_urbctx_t structure
+ */
+static void write_iso_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct isow_urbctx_t *ucx;
+ struct bas_bc_state *ubc;
+ unsigned long flags;
+
+ IFNULLRET(urb);
+ IFNULLRET(urb->context);
+ IFNULLRET(cardstate);
+
+ /* status codes not worth bothering the tasklet with */
+ if (unlikely(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+ urb->status == -EINPROGRESS)) {
+ dbg(DEBUG_ISO,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ return;
+ }
+
+ /* pass URB context to tasklet */
+ ucx = (struct isow_urbctx_t *) urb->context;
+ IFNULLRET(ucx->bcs);
+ ubc = ucx->bcs->hw.bas;
+ IFNULLRET(ubc);
+
+ spin_lock_irqsave(&ubc->isooutlock, flags);
+ ubc->isooutovfl = ubc->isooutdone;
+ ubc->isooutdone = ucx;
+ spin_unlock_irqrestore(&ubc->isooutlock, flags);
+ tasklet_schedule(&ubc->sent_tasklet);
+}
+
+/* starturbs
+ * prepare and submit USB request blocks for isochronous input and output
+ * argument:
+ * B channel control structure
+ * return value:
+ * 0 on success
+ * < 0 on error (no URBs submitted)
+ */
+static int starturbs(struct bc_state *bcs)
+{
+ struct urb *urb;
+ struct bas_bc_state *ubc;
+ int j, k;
+ int rc;
+
+ IFNULLRETVAL(bcs, -EFAULT);
+ ubc = bcs->hw.bas;
+ IFNULLRETVAL(ubc, -EFAULT);
+
+ /* initialize L2 reception */
+ if (bcs->proto2 == ISDN_PROTO_L2_HDLC)
+ bcs->inputstate |= INS_flag_hunt;
+
+ /* submit all isochronous input URBs */
+ atomic_set(&ubc->running, 1);
+ for (k = 0; k < BAS_INURBS; k++) {
+ urb = ubc->isoinurbs[k];
+ if (!urb) {
+ err("isoinurbs[%d]==NULL", k);
+ rc = -EFAULT;
+ goto error;
+ }
+
+ urb->dev = bcs->cs->hw.bas->udev;
+ urb->pipe = usb_rcvisocpipe(urb->dev, 3 + 2 * bcs->channel);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = ubc->isoinbuf + k * BAS_INBUFSIZE;
+ urb->transfer_buffer_length = BAS_INBUFSIZE;
+ urb->number_of_packets = BAS_NUMFRAMES;
+ urb->interval = BAS_FRAMETIME;
+ urb->complete = read_iso_callback;
+ urb->context = bcs;
+ for (j = 0; j < BAS_NUMFRAMES; j++) {
+ urb->iso_frame_desc[j].offset = j * BAS_MAXFRAME;
+ urb->iso_frame_desc[j].length = BAS_MAXFRAME;
+ urb->iso_frame_desc[j].status = 0;
+ urb->iso_frame_desc[j].actual_length = 0;
+ }
+
+ dump_urb(DEBUG_ISO, "Initial isoc read", urb);
+ if ((rc = usb_submit_urb(urb, SLAB_ATOMIC)) != 0) {
+ err("could not submit isochronous read URB %d: %s",
+ k, get_usb_statmsg(rc));
+ goto error;
+ }
+ }
+
+ /* initialize L2 transmission */
+ gigaset_isowbuf_init(ubc->isooutbuf, PPP_FLAG);
+
+ /* set up isochronous output URBs for flag idling */
+ for (k = 0; k < BAS_OUTURBS; ++k) {
+ urb = ubc->isoouturbs[k].urb;
+ if (!urb) {
+ err("isoouturbs[%d].urb==NULL", k);
+ rc = -EFAULT;
+ goto error;
+ }
+ urb->dev = bcs->cs->hw.bas->udev;
+ urb->pipe = usb_sndisocpipe(urb->dev, 4 + 2 * bcs->channel);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = ubc->isooutbuf->data;
+ urb->transfer_buffer_length = sizeof(ubc->isooutbuf->data);
+ urb->number_of_packets = BAS_NUMFRAMES;
+ urb->interval = BAS_FRAMETIME;
+ urb->complete = write_iso_callback;
+ urb->context = &ubc->isoouturbs[k];
+ for (j = 0; j < BAS_NUMFRAMES; ++j) {
+ urb->iso_frame_desc[j].offset = BAS_OUTBUFSIZE;
+ urb->iso_frame_desc[j].length = BAS_NORMFRAME;
+ urb->iso_frame_desc[j].status = 0;
+ urb->iso_frame_desc[j].actual_length = 0;
+ }
+ ubc->isoouturbs[k].limit = -1;
+ }
+
+ /* submit two URBs, keep third one */
+ for (k = 0; k < 2; ++k) {
+ dump_urb(DEBUG_ISO, "Initial isoc write", urb);
+ rc = usb_submit_urb(ubc->isoouturbs[k].urb, SLAB_ATOMIC);
+ if (rc != 0) {
+ err("could not submit isochronous write URB %d: %s",
+ k, get_usb_statmsg(rc));
+ goto error;
+ }
+ }
+ dump_urb(DEBUG_ISO, "Initial isoc write (free)", urb);
+ ubc->isooutfree = &ubc->isoouturbs[2];
+ ubc->isooutdone = ubc->isooutovfl = NULL;
+ return 0;
+ error:
+ stopurbs(ubc);
+ return rc;
+}
+
+/* stopurbs
+ * cancel the USB request blocks for isochronous input and output
+ * errors are silently ignored
+ * argument:
+ * B channel control structure
+ */
+static void stopurbs(struct bas_bc_state *ubc)
+{
+ int k, rc;
+
+ IFNULLRET(ubc);
+
+ atomic_set(&ubc->running, 0);
+
+ for (k = 0; k < BAS_INURBS; ++k) {
+ rc = usb_unlink_urb(ubc->isoinurbs[k]);
+ dbg(DEBUG_ISO, "%s: isoc input URB %d unlinked, result = %d",
+ __func__, k, rc);
+ }
+
+ for (k = 0; k < BAS_OUTURBS; ++k) {
+ rc = usb_unlink_urb(ubc->isoouturbs[k].urb);
+ dbg(DEBUG_ISO, "%s: isoc output URB %d unlinked, result = %d",
+ __func__, k, rc);
+ }
+}
+
+/* Isochronous Write - Bottom Half */
+/* =============================== */
+
+/* submit_iso_write_urb
+ * fill and submit the next isochronous write URB
+ * parameters:
+ * bcs B channel state structure
+ * return value:
+ * number of frames submitted in URB
+ * 0 if URB not submitted because no data available (isooutbuf busy)
+ * error code < 0 on error
+ */
+static int submit_iso_write_urb(struct isow_urbctx_t *ucx)
+{
+ struct urb *urb;
+ struct bas_bc_state *ubc;
+ struct usb_iso_packet_descriptor *ifd;
+ int corrbytes, nframe, rc;
+
+ IFNULLRETVAL(ucx, -EFAULT);
+ urb = ucx->urb;
+ IFNULLRETVAL(urb, -EFAULT);
+ IFNULLRETVAL(ucx->bcs, -EFAULT);
+ ubc = ucx->bcs->hw.bas;
+ IFNULLRETVAL(ubc, -EFAULT);
+
+ urb->dev = ucx->bcs->cs->hw.bas->udev; /* clobbered by USB subsystem */
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = ubc->isooutbuf->data;
+ urb->transfer_buffer_length = sizeof(ubc->isooutbuf->data);
+
+ for (nframe = 0; nframe < BAS_NUMFRAMES; nframe++) {
+ ifd = &urb->iso_frame_desc[nframe];
+
+ /* compute frame length according to flow control */
+ ifd->length = BAS_NORMFRAME;
+ if ((corrbytes = atomic_read(&ubc->corrbytes)) != 0) {
+ dbg(DEBUG_ISO, "%s: corrbytes=%d", __func__, corrbytes);
+ if (corrbytes > BAS_HIGHFRAME - BAS_NORMFRAME)
+ corrbytes = BAS_HIGHFRAME - BAS_NORMFRAME;
+ else if (corrbytes < BAS_LOWFRAME - BAS_NORMFRAME)
+ corrbytes = BAS_LOWFRAME - BAS_NORMFRAME;
+ ifd->length += corrbytes;
+ atomic_add(-corrbytes, &ubc->corrbytes);
+ }
+ //dbg(DEBUG_ISO, "%s: frame %d length=%d", __func__, nframe, ifd->length);
+
+ /* retrieve block of data to send */
+ ifd->offset = gigaset_isowbuf_getbytes(ubc->isooutbuf, ifd->length);
+ if (ifd->offset < 0) {
+ if (ifd->offset == -EBUSY) {
+ dbg(DEBUG_ISO, "%s: buffer busy at frame %d",
+ __func__, nframe);
+ /* tasklet will be restarted from gigaset_send_skb() */
+ } else {
+ err("%s: buffer error %d at frame %d",
+ __func__, ifd->offset, nframe);
+ return ifd->offset;
+ }
+ break;
+ }
+ ucx->limit = atomic_read(&ubc->isooutbuf->nextread);
+ ifd->status = 0;
+ ifd->actual_length = 0;
+ }
+ if ((urb->number_of_packets = nframe) > 0) {
+ if ((rc = usb_submit_urb(urb, SLAB_ATOMIC)) != 0) {
+ err("could not submit isochronous write URB: %s",
+ get_usb_statmsg(rc));
+ dump_urb(DEBUG_ISO, "isoc write", urb);
+ return rc;
+ }
+ ++ubc->numsub;
+ }
+ return nframe;
+}
+
+/* write_iso_tasklet
+ * tasklet scheduled when an isochronous output URB from the Gigaset device
+ * has completed
+ * parameter:
+ * data B channel state structure
+ */
+static void write_iso_tasklet(unsigned long data)
+{
+ struct bc_state *bcs;
+ struct bas_bc_state *ubc;
+ struct cardstate *cs;
+ struct isow_urbctx_t *done, *next, *ovfl;
+ struct urb *urb;
+ struct usb_iso_packet_descriptor *ifd;
+ int offset;
+ unsigned long flags;
+ int i;
+ struct sk_buff *skb;
+ int len;
+
+ bcs = (struct bc_state *) data;
+ IFNULLRET(bcs);
+ ubc = bcs->hw.bas;
+ IFNULLRET(ubc);
+ cs = bcs->cs;
+ IFNULLRET(cs);
+
+ /* loop while completed URBs arrive in time */
+ for (;;) {
+ if (unlikely(!atomic_read(&cs->connected))) {
+ warn("%s: disconnected", __func__);
+ return;
+ }
+
+ if (unlikely(!(atomic_read(&ubc->running)))) {
+ dbg(DEBUG_ISO, "%s: not running", __func__);
+ return;
+ }
+
+ /* retrieve completed URBs */
+ spin_lock_irqsave(&ubc->isooutlock, flags);
+ done = ubc->isooutdone;
+ ubc->isooutdone = NULL;
+ ovfl = ubc->isooutovfl;
+ ubc->isooutovfl = NULL;
+ spin_unlock_irqrestore(&ubc->isooutlock, flags);
+ if (ovfl) {
+ err("isochronous write buffer underrun - buy a faster machine :-)");
+ error_hangup(bcs);
+ break;
+ }
+ if (!done)
+ break;
+
+ /* submit free URB if available */
+ spin_lock_irqsave(&ubc->isooutlock, flags);
+ next = ubc->isooutfree;
+ ubc->isooutfree = NULL;
+ spin_unlock_irqrestore(&ubc->isooutlock, flags);
+ if (next) {
+ if (submit_iso_write_urb(next) <= 0) {
+ /* could not submit URB, put it back */
+ spin_lock_irqsave(&ubc->isooutlock, flags);
+ if (ubc->isooutfree == NULL) {
+ ubc->isooutfree = next;
+ next = NULL;
+ }
+ spin_unlock_irqrestore(&ubc->isooutlock, flags);
+ if (next) {
+ /* couldn't put it back */
+ err("losing isochronous write URB");
+ error_hangup(bcs);
+ }
+ }
+ }
+
+ /* process completed URB */
+ urb = done->urb;
+ switch (urb->status) {
+ case 0: /* normal completion */
+ break;
+ case -EXDEV: /* inspect individual frames */
+ /* assumptions (for lack of documentation):
+ * - actual_length bytes of the frame in error are successfully sent
+ * - all following frames are not sent at all
+ */
+ dbg(DEBUG_ISO, "%s: URB partially completed", __func__);
+ offset = done->limit; /* just in case */
+ for (i = 0; i < BAS_NUMFRAMES; i++) {
+ ifd = &urb->iso_frame_desc[i];
+ if (ifd->status ||
+ ifd->actual_length != ifd->length) {
+ warn("isochronous write: frame %d: %s, "
+ "only %d of %d bytes sent",
+ i, get_usb_statmsg(ifd->status),
+ ifd->actual_length, ifd->length);
+ offset = (ifd->offset +
+ ifd->actual_length)
+ % BAS_OUTBUFSIZE;
+ break;
+ }
+ }
+#ifdef CONFIG_GIGASET_DEBUG
+ /* check assumption on remaining frames */
+ for (; i < BAS_NUMFRAMES; i++) {
+ ifd = &urb->iso_frame_desc[i];
+ if (ifd->status != -EINPROGRESS
+ || ifd->actual_length != 0) {
+ warn("isochronous write: frame %d: %s, "
+ "%d of %d bytes sent",
+ i, get_usb_statmsg(ifd->status),
+ ifd->actual_length, ifd->length);
+ offset = (ifd->offset +
+ ifd->actual_length)
+ % BAS_OUTBUFSIZE;
+ break;
+ }
+ }
+#endif
+ break;
+ case -EPIPE: //FIXME is this the code for "underrun"?
+ err("isochronous write stalled");
+ error_hangup(bcs);
+ break;
+ default: /* severe trouble */
+ warn("isochronous write: %s",
+ get_usb_statmsg(urb->status));
+ }
+
+ /* mark the write buffer area covered by this URB as free */
+ if (done->limit >= 0)
+ atomic_set(&ubc->isooutbuf->read, done->limit);
+
+ /* mark URB as free */
+ spin_lock_irqsave(&ubc->isooutlock, flags);
+ next = ubc->isooutfree;
+ ubc->isooutfree = done;
+ spin_unlock_irqrestore(&ubc->isooutlock, flags);
+ if (next) {
+ /* only one URB still active - resubmit one */
+ if (submit_iso_write_urb(next) <= 0) {
+ /* couldn't submit */
+ error_hangup(bcs);
+ }
+ }
+ }
+
+ /* process queued SKBs */
+ while ((skb = skb_dequeue(&bcs->squeue))) {
+ /* copy to output buffer, doing L2 encapsulation */
+ len = skb->len;
+ if (gigaset_isoc_buildframe(bcs, skb->data, len) == -EAGAIN) {
+ /* insufficient buffer space, push back onto queue */
+ skb_queue_head(&bcs->squeue, skb);
+ dbg(DEBUG_ISO, "%s: skb requeued, qlen=%d",
+ __func__, skb_queue_len(&bcs->squeue));
+ break;
+ }
+ skb_pull(skb, len);
+ gigaset_skb_sent(bcs, skb);
+ dev_kfree_skb_any(skb);
+ }
+}
+
+/* Isochronous Read - Bottom Half */
+/* ============================== */
+
+/* read_iso_tasklet
+ * tasklet scheduled when an isochronous input URB from the Gigaset device
+ * has completed
+ * parameter:
+ * data B channel state structure
+ */
+static void read_iso_tasklet(unsigned long data)
+{
+ struct bc_state *bcs;
+ struct bas_bc_state *ubc;
+ struct cardstate *cs;
+ struct urb *urb;
+ char *rcvbuf;
+ unsigned long flags;
+ int totleft, numbytes, offset, frame, rc;
+
+ bcs = (struct bc_state *) data;
+ IFNULLRET(bcs);
+ ubc = bcs->hw.bas;
+ IFNULLRET(ubc);
+ cs = bcs->cs;
+ IFNULLRET(cs);
+
+ /* loop while more completed URBs arrive in the meantime */
+ for (;;) {
+ if (!atomic_read(&cs->connected)) {
+ warn("%s: disconnected", __func__);
+ return;
+ }
+
+ /* retrieve URB */
+ spin_lock_irqsave(&ubc->isoinlock, flags);
+ if (!(urb = ubc->isoindone)) {
+ spin_unlock_irqrestore(&ubc->isoinlock, flags);
+ return;
+ }
+ ubc->isoindone = NULL;
+ if (unlikely(ubc->loststatus != -EINPROGRESS)) {
+ warn("isochronous read overrun, dropped URB with status: %s, %d bytes lost",
+ get_usb_statmsg(ubc->loststatus), ubc->isoinlost);
+ ubc->loststatus = -EINPROGRESS;
+ }
+ spin_unlock_irqrestore(&ubc->isoinlock, flags);
+
+ if (unlikely(!(atomic_read(&ubc->running)))) {
+ dbg(DEBUG_ISO, "%s: channel not running, dropped URB with status: %s",
+ __func__, get_usb_statmsg(urb->status));
+ return;
+ }
+
+ switch (urb->status) {
+ case 0: /* normal completion */
+ break;
+ case -EXDEV: /* inspect individual frames (we do that anyway) */
+ dbg(DEBUG_ISO, "%s: URB partially completed", __func__);
+ break;
+ case -ENOENT:
+ case -ECONNRESET:
+ dbg(DEBUG_ISO, "%s: URB canceled", __func__);
+ continue; /* -> skip */
+ case -EINPROGRESS: /* huh? */
+ dbg(DEBUG_ISO, "%s: URB still pending", __func__);
+ continue; /* -> skip */
+ case -EPIPE:
+ err("isochronous read stalled");
+ error_hangup(bcs);
+ continue; /* -> skip */
+ default: /* severe trouble */
+ warn("isochronous read: %s",
+ get_usb_statmsg(urb->status));
+ goto error;
+ }
+
+ rcvbuf = urb->transfer_buffer;
+ totleft = urb->actual_length;
+ for (frame = 0; totleft > 0 && frame < BAS_NUMFRAMES; frame++) {
+ if (unlikely(urb->iso_frame_desc[frame].status)) {
+ warn("isochronous read: frame %d: %s",
+ frame, get_usb_statmsg(urb->iso_frame_desc[frame].status));
+ break;
+ }
+ numbytes = urb->iso_frame_desc[frame].actual_length;
+ if (unlikely(numbytes > BAS_MAXFRAME)) {
+ warn("isochronous read: frame %d: numbytes (%d) > BAS_MAXFRAME",
+ frame, numbytes);
+ break;
+ }
+ if (unlikely(numbytes > totleft)) {
+ warn("isochronous read: frame %d: numbytes (%d) > totleft (%d)",
+ frame, numbytes, totleft);
+ break;
+ }
+ offset = urb->iso_frame_desc[frame].offset;
+ if (unlikely(offset + numbytes > BAS_INBUFSIZE)) {
+ warn("isochronous read: frame %d: offset (%d) + numbytes (%d) > BAS_INBUFSIZE",
+ frame, offset, numbytes);
+ break;
+ }
+ gigaset_isoc_receive(rcvbuf + offset, numbytes, bcs);
+ totleft -= numbytes;
+ }
+ if (unlikely(totleft > 0))
+ warn("isochronous read: %d data bytes missing",
+ totleft);
+
+ error:
+ /* URB processed, resubmit */
+ for (frame = 0; frame < BAS_NUMFRAMES; frame++) {
+ urb->iso_frame_desc[frame].status = 0;
+ urb->iso_frame_desc[frame].actual_length = 0;
+ }
+ urb->dev = bcs->cs->hw.bas->udev; /* clobbered by USB subsystem */
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->number_of_packets = BAS_NUMFRAMES;
+ if ((rc = usb_submit_urb(urb, SLAB_ATOMIC)) != 0) {
+ err("could not resubmit isochronous read URB: %s",
+ get_usb_statmsg(rc));
+ dump_urb(DEBUG_ISO, "resubmit iso read", urb);
+ error_hangup(bcs);
+ }
+ }
+}
+
+/* Channel Operations */
+/* ================== */
+
+/* req_timeout
+ * timeout routine for control output request
+ * argument:
+ * B channel control structure
+ */
+static void req_timeout(unsigned long data)
+{
+ struct bc_state *bcs = (struct bc_state *) data;
+ struct bas_cardstate *ucs;
+ int pending;
+ unsigned long flags;
+
+ IFNULLRET(bcs);
+ IFNULLRET(bcs->cs);
+ ucs = bcs->cs->hw.bas;
+ IFNULLRET(ucs);
+
+ check_pending(ucs);
+
+ spin_lock_irqsave(&ucs->lock, flags);
+ pending = ucs->pending;
+ ucs->pending = 0;
+ spin_unlock_irqrestore(&ucs->lock, flags);
+
+ switch (pending) {
+ case 0: /* no pending request */
+ dbg(DEBUG_USBREQ, "%s: no request pending", __func__);
+ break;
+
+ case HD_OPEN_ATCHANNEL:
+ err("timeout opening AT channel");
+ error_reset(bcs->cs);
+ break;
+
+ case HD_OPEN_B2CHANNEL:
+ case HD_OPEN_B1CHANNEL:
+ err("timeout opening channel %d", bcs->channel + 1);
+ error_hangup(bcs);
+ break;
+
+ case HD_CLOSE_ATCHANNEL:
+ err("timeout closing AT channel");
+ //wake_up_interruptible(cs->initwait);
+ //FIXME need own wait queue?
+ break;
+
+ case HD_CLOSE_B2CHANNEL:
+ case HD_CLOSE_B1CHANNEL:
+ err("timeout closing channel %d", bcs->channel + 1);
+ break;
+
+ default:
+ warn("request 0x%02x timed out, clearing", pending);
+ }
+}
+
+/* write_ctrl_callback
+ * USB completion handler for control pipe output
+ * called by the USB subsystem in interrupt context
+ * parameter:
+ * urb USB request block of completed request
+ * urb->context = hardware specific controller state structure
+ */
+static void write_ctrl_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct bas_cardstate *ucs;
+ unsigned long flags;
+
+ IFNULLRET(urb);
+ IFNULLRET(urb->context);
+ IFNULLRET(cardstate);
+
+ ucs = (struct bas_cardstate *) urb->context;
+ spin_lock_irqsave(&ucs->lock, flags);
+ if (urb->status && ucs->pending) {
+ err("control request 0x%02x failed: %s",
+ ucs->pending, get_usb_statmsg(urb->status));
+ del_timer(&ucs->timer_ctrl);
+ ucs->pending = 0;
+ }
+ /* individual handling of specific request types */
+ switch (ucs->pending) {
+ case HD_DEVICE_INIT_ACK: /* no reply expected */
+ ucs->pending = 0;
+ break;
+ }
+ spin_unlock_irqrestore(&ucs->lock, flags);
+}
+
+/* req_submit
+ * submit a control output request without message buffer to the Gigaset base
+ * and optionally start a timeout
+ * parameters:
+ * bcs B channel control structure
+ * req control request code (HD_*)
+ * val control request parameter value (set to 0 if unused)
+ * timeout timeout in seconds (0: no timeout)
+ * return value:
+ * 0 on success
+ * -EINVAL if a NULL pointer is encountered somewhere
+ * -EBUSY if another request is pending
+ * any URB submission error code
+ */
+static int req_submit(struct bc_state *bcs, int req, int val, int timeout)
+{
+ struct bas_cardstate *ucs;
+ int ret;
+ unsigned long flags;
+
+ IFNULLRETVAL(bcs, -EINVAL);
+ IFNULLRETVAL(bcs->cs, -EINVAL);
+ ucs = bcs->cs->hw.bas;
+ IFNULLRETVAL(ucs, -EINVAL);
+ IFNULLRETVAL(ucs->urb_ctrl, -EINVAL);
+
+ dbg(DEBUG_USBREQ, "-------> 0x%02x (%d)", req, val);
+
+ spin_lock_irqsave(&ucs->lock, flags);
+ if (ucs->pending) {
+ spin_unlock_irqrestore(&ucs->lock, flags);
+ err("submission of request 0x%02x failed: request 0x%02x still pending",
+ req, ucs->pending);
+ return -EBUSY;
+ }
+ if (ucs->urb_ctrl->status == -EINPROGRESS) {
+ spin_unlock_irqrestore(&ucs->lock, flags);
+ err("could not submit request 0x%02x: URB busy", req);
+ return -EBUSY;
+ }
+
+ ucs->dr_ctrl.bRequestType = OUT_VENDOR_REQ;
+ ucs->dr_ctrl.bRequest = req;
+ ucs->dr_ctrl.wValue = cpu_to_le16(val);
+ ucs->dr_ctrl.wIndex = 0;
+ ucs->dr_ctrl.wLength = 0;
+ usb_fill_control_urb(ucs->urb_ctrl, ucs->udev,
+ usb_sndctrlpipe(ucs->udev, 0),
+ (unsigned char*) &ucs->dr_ctrl, NULL, 0,
+ write_ctrl_callback, ucs);
+ if ((ret = usb_submit_urb(ucs->urb_ctrl, SLAB_ATOMIC)) != 0) {
+ err("could not submit request 0x%02x: %s",
+ req, get_usb_statmsg(ret));
+ spin_unlock_irqrestore(&ucs->lock, flags);
+ return ret;
+ }
+ ucs->pending = req;
+
+ if (timeout > 0) {
+ dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout);
+ ucs->timer_ctrl.expires = jiffies + timeout * HZ / 10;
+ ucs->timer_ctrl.data = (unsigned long) bcs;
+ ucs->timer_ctrl.function = req_timeout;
+ add_timer(&ucs->timer_ctrl);
+ }
+
+ spin_unlock_irqrestore(&ucs->lock, flags);
+ return 0;
+}
+
+/* gigaset_init_bchannel
+ * called by common.c to connect a B channel
+ * initialize isochronous I/O and tell the Gigaset base to open the channel
+ * argument:
+ * B channel control structure
+ * return value:
+ * 0 on success, error code < 0 on error
+ */
+static int gigaset_init_bchannel(struct bc_state *bcs)
+{
+ int req, ret;
+
+ IFNULLRETVAL(bcs, -EINVAL);
+
+ if ((ret = starturbs(bcs)) < 0) {
+ err("could not start isochronous I/O for channel %d",
+ bcs->channel + 1);
+ error_hangup(bcs);
+ return ret;
+ }
+
+ req = bcs->channel ? HD_OPEN_B2CHANNEL : HD_OPEN_B1CHANNEL;
+ if ((ret = req_submit(bcs, req, 0, BAS_TIMEOUT)) < 0) {
+ err("could not open channel %d: %s",
+ bcs->channel + 1, get_usb_statmsg(ret));
+ stopurbs(bcs->hw.bas);
+ error_hangup(bcs);
+ }
+ return ret;
+}
+
+/* gigaset_close_bchannel
+ * called by common.c to disconnect a B channel
+ * tell the Gigaset base to close the channel
+ * stopping isochronous I/O and LL notification will be done when the
+ * acknowledgement for the close arrives
+ * argument:
+ * B channel control structure
+ * return value:
+ * 0 on success, error code < 0 on error
+ */
+static int gigaset_close_bchannel(struct bc_state *bcs)
+{
+ int req, ret;
+
+ IFNULLRETVAL(bcs, -EINVAL);
+
+ if (!(atomic_read(&bcs->cs->hw.bas->basstate) &
+ (bcs->channel ? BS_B2OPEN : BS_B1OPEN))) {
+ /* channel not running: just signal common.c */
+ gigaset_bchannel_down(bcs);
+ return 0;
+ }
+
+ req = bcs->channel ? HD_CLOSE_B2CHANNEL : HD_CLOSE_B1CHANNEL;
+ if ((ret = req_submit(bcs, req, 0, BAS_TIMEOUT)) < 0)
+ err("could not submit HD_CLOSE_BxCHANNEL request: %s",
+ get_usb_statmsg(ret));
+ return ret;
+}
+
+/* Device Operations */
+/* ================= */
+
+/* complete_cb
+ * unqueue first command buffer from queue, waking any sleepers
+ * must be called with cs->cmdlock held
+ * parameter:
+ * cs controller state structure
+ */
+static void complete_cb(struct cardstate *cs)
+{
+ struct cmdbuf_t *cb;
+
+ IFNULLRET(cs);
+ cb = cs->cmdbuf;
+ IFNULLRET(cb);
+
+ /* unqueue completed buffer */
+ cs->cmdbytes -= cs->curlen;
+ dbg(DEBUG_TRANSCMD | DEBUG_LOCKCMD,
+ "write_command: sent %u bytes, %u left",
+ cs->curlen, cs->cmdbytes);
+ if ((cs->cmdbuf = cb->next) != NULL) {
+ cs->cmdbuf->prev = NULL;
+ cs->curlen = cs->cmdbuf->len;
+ } else {
+ cs->lastcmdbuf = NULL;
+ cs->curlen = 0;
+ }
+
+ if (cb->wake_tasklet)
+ tasklet_schedule(cb->wake_tasklet);
+
+ kfree(cb);
+}
+
+static int atwrite_submit(struct cardstate *cs, unsigned char *buf, int len);
+
+/* write_command_callback
+ * USB completion handler for AT command transmission
+ * called by the USB subsystem in interrupt context
+ * parameter:
+ * urb USB request block of completed request
+ * urb->context = controller state structure
+ */
+static void write_command_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct cardstate *cs;
+ unsigned long flags;
+ struct bas_cardstate *ucs;
+
+ IFNULLRET(urb);
+ cs = (struct cardstate *) urb->context;
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ /* check status */
+ switch (urb->status) {
+ case 0: /* normal completion */
+ break;
+ case -ENOENT: /* canceled */
+ case -ECONNRESET: /* canceled (async) */
+ case -EINPROGRESS: /* pending */
+ /* ignore silently */
+ dbg(DEBUG_USBREQ,
+ "%s: %s", __func__, get_usb_statmsg(urb->status));
+ return;
+ default: /* any failure */
+ if (++ucs->retry_cmd_out > BAS_RETRY) {
+ warn("command write: %s, giving up after %d retries",
+ get_usb_statmsg(urb->status), ucs->retry_cmd_out);
+ break;
+ }
+ if (cs->cmdbuf == NULL) {
+ warn("command write: %s, cannot retry - cmdbuf gone",
+ get_usb_statmsg(urb->status));
+ break;
+ }
+ notice("command write: %s, retry %d",
+ get_usb_statmsg(urb->status), ucs->retry_cmd_out);
+ if (atwrite_submit(cs, cs->cmdbuf->buf, cs->cmdbuf->len) >= 0)
+ /* resubmitted - bypass regular exit block */
+ return;
+ /* command send failed, assume base still waiting */
+ update_basstate(ucs, BS_ATREADY, 0);
+ }
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ if (cs->cmdbuf != NULL)
+ complete_cb(cs);
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+}
+
+/* atrdy_timeout
+ * timeout routine for AT command transmission
+ * argument:
+ * controller state structure
+ */
+static void atrdy_timeout(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ struct bas_cardstate *ucs;
+
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ warn("timeout waiting for HD_READY_SEND_ATDATA");
+
+ /* fake the missing signal - what else can I do? */
+ update_basstate(ucs, BS_ATREADY, BS_ATTIMER);
+ start_cbsend(cs);
+}
+
+/* atwrite_submit
+ * submit an HD_WRITE_ATMESSAGE command URB
+ * parameters:
+ * cs controller state structure
+ * buf buffer containing command to send
+ * len length of command to send
+ * return value:
+ * 0 on success
+ * -EFAULT if a NULL pointer is encountered somewhere
+ * -EBUSY if another request is pending
+ * any URB submission error code
+ */
+static int atwrite_submit(struct cardstate *cs, unsigned char *buf, int len)
+{
+ struct bas_cardstate *ucs;
+ int ret;
+
+ IFNULLRETVAL(cs, -EFAULT);
+ ucs = cs->hw.bas;
+ IFNULLRETVAL(ucs, -EFAULT);
+ IFNULLRETVAL(ucs->urb_cmd_out, -EFAULT);
+
+ dbg(DEBUG_USBREQ, "-------> HD_WRITE_ATMESSAGE (%d)", len);
+
+ if (ucs->urb_cmd_out->status == -EINPROGRESS) {
+ err("could not submit HD_WRITE_ATMESSAGE: URB busy");
+ return -EBUSY;
+ }
+
+ ucs->dr_cmd_out.bRequestType = OUT_VENDOR_REQ;
+ ucs->dr_cmd_out.bRequest = HD_WRITE_ATMESSAGE;
+ ucs->dr_cmd_out.wValue = 0;
+ ucs->dr_cmd_out.wIndex = 0;
+ ucs->dr_cmd_out.wLength = cpu_to_le16(len);
+ usb_fill_control_urb(ucs->urb_cmd_out, ucs->udev,
+ usb_sndctrlpipe(ucs->udev, 0),
+ (unsigned char*) &ucs->dr_cmd_out, buf, len,
+ write_command_callback, cs);
+
+ if ((ret = usb_submit_urb(ucs->urb_cmd_out, SLAB_ATOMIC)) != 0) {
+ err("could not submit HD_WRITE_ATMESSAGE: %s",
+ get_usb_statmsg(ret));
+ return ret;
+ }
+
+ /* submitted successfully */
+ update_basstate(ucs, 0, BS_ATREADY);
+
+ /* start timeout if necessary */
+ if (!(atomic_read(&ucs->basstate) & BS_ATTIMER)) {
+ dbg(DEBUG_OUTPUT,
+ "setting ATREADY timeout of %d/10 secs", ATRDY_TIMEOUT);
+ ucs->timer_atrdy.expires = jiffies + ATRDY_TIMEOUT * HZ / 10;
+ ucs->timer_atrdy.data = (unsigned long) cs;
+ ucs->timer_atrdy.function = atrdy_timeout;
+ add_timer(&ucs->timer_atrdy);
+ update_basstate(ucs, BS_ATTIMER, 0);
+ }
+ return 0;
+}
+
+/* start_cbsend
+ * start transmission of AT command queue if necessary
+ * parameter:
+ * cs controller state structure
+ * return value:
+ * 0 on success
+ * error code < 0 on error
+ */
+static int start_cbsend(struct cardstate *cs)
+{
+ struct cmdbuf_t *cb;
+ struct bas_cardstate *ucs;
+ unsigned long flags;
+ int rc;
+ int retval = 0;
+
+ IFNULLRETVAL(cs, -EFAULT);
+ ucs = cs->hw.bas;
+ IFNULLRETVAL(ucs, -EFAULT);
+
+ /* check if AT channel is open */
+ if (!(atomic_read(&ucs->basstate) & BS_ATOPEN)) {
+ dbg(DEBUG_TRANSCMD | DEBUG_LOCKCMD, "AT channel not open");
+ rc = req_submit(cs->bcs, HD_OPEN_ATCHANNEL, 0, BAS_TIMEOUT);
+ if (rc < 0) {
+ err("could not open AT channel");
+ /* flush command queue */
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ while (cs->cmdbuf != NULL)
+ complete_cb(cs);
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+ }
+ return rc;
+ }
+
+ /* try to send first command in queue */
+ spin_lock_irqsave(&cs->cmdlock, flags);
+
+ while ((cb = cs->cmdbuf) != NULL &&
+ atomic_read(&ucs->basstate) & BS_ATREADY) {
+ ucs->retry_cmd_out = 0;
+ rc = atwrite_submit(cs, cb->buf, cb->len);
+ if (unlikely(rc)) {
+ retval = rc;
+ complete_cb(cs);
+ }
+ }
+
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+ return retval;
+}
+
+/* gigaset_write_cmd
+ * This function is called by the device independent part of the driver
+ * to transmit an AT command string to the Gigaset device.
+ * It encapsulates the device specific method for transmission over the
+ * direct USB connection to the base.
+ * The command string is added to the queue of commands to send, and
+ * USB transmission is started if necessary.
+ * parameters:
+ * cs controller state structure
+ * buf command string to send
+ * len number of bytes to send (max. IF_WRITEBUF)
+ * wake_tasklet tasklet to run when transmission is completed (NULL if none)
+ * return value:
+ * number of bytes queued on success
+ * error code < 0 on error
+ */
+static int gigaset_write_cmd(struct cardstate *cs,
+ const unsigned char *buf, int len,
+ struct tasklet_struct *wake_tasklet)
+{
+ struct cmdbuf_t *cb;
+ unsigned long flags;
+ int status;
+
+ gigaset_dbg_buffer(atomic_read(&cs->mstate) != MS_LOCKED ?
+ DEBUG_TRANSCMD : DEBUG_LOCKCMD,
+ "CMD Transmit", len, buf, 0);
+
+ if (!atomic_read(&cs->connected)) {
+ err("%s: not connected", __func__);
+ return -ENODEV;
+ }
+
+ if (len <= 0)
+ return 0; /* nothing to do */
+
+ if (len > IF_WRITEBUF)
+ len = IF_WRITEBUF;
+ if (!(cb = kmalloc(sizeof(struct cmdbuf_t) + len, GFP_ATOMIC))) {
+ err("%s: out of memory", __func__);
+ return -ENOMEM;
+ }
+
+ memcpy(cb->buf, buf, len);
+ cb->len = len;
+ cb->offset = 0;
+ cb->next = NULL;
+ cb->wake_tasklet = wake_tasklet;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ cb->prev = cs->lastcmdbuf;
+ if (cs->lastcmdbuf)
+ cs->lastcmdbuf->next = cb;
+ else {
+ cs->cmdbuf = cb;
+ cs->curlen = len;
+ }
+ cs->cmdbytes += len;
+ cs->lastcmdbuf = cb;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ status = start_cbsend(cs);
+
+ return status < 0 ? status : len;
+}
+
+/* gigaset_write_room
+ * tty_driver.write_room interface routine
+ * return number of characters the driver will accept to be written via gigaset_write_cmd
+ * parameter:
+ * controller state structure
+ * return value:
+ * number of characters
+ */
+static int gigaset_write_room(struct cardstate *cs)
+{
+ return IF_WRITEBUF;
+}
+
+/* gigaset_chars_in_buffer
+ * tty_driver.chars_in_buffer interface routine
+ * return number of characters waiting to be sent
+ * parameter:
+ * controller state structure
+ * return value:
+ * number of characters
+ */
+static int gigaset_chars_in_buffer(struct cardstate *cs)
+{
+ unsigned long flags;
+ unsigned bytes;
+
+ spin_lock_irqsave(&cs->cmdlock, flags);
+ bytes = cs->cmdbytes;
+ spin_unlock_irqrestore(&cs->cmdlock, flags);
+
+ return bytes;
+}
+
+/* gigaset_brkchars
+ * implementation of ioctl(GIGASET_BRKCHARS)
+ * parameter:
+ * controller state structure
+ * return value:
+ * -EINVAL (unimplemented function)
+ */
+static int gigaset_brkchars(struct cardstate *cs, const unsigned char buf[6])
+{
+ return -EINVAL;
+}
+
+
+/* Device Initialization/Shutdown */
+/* ============================== */
+
+/* Free hardware dependent part of the B channel structure
+ * parameter:
+ * bcs B channel structure
+ * return value:
+ * !=0 on success
+ */
+static int gigaset_freebcshw(struct bc_state *bcs)
+{
+ if (!bcs->hw.bas)
+ return 0;
+
+ if (bcs->hw.bas->isooutbuf)
+ kfree(bcs->hw.bas->isooutbuf);
+ kfree(bcs->hw.bas);
+ bcs->hw.bas = NULL;
+ return 1;
+}
+
+/* Initialize hardware dependent part of the B channel structure
+ * parameter:
+ * bcs B channel structure
+ * return value:
+ * !=0 on success
+ */
+static int gigaset_initbcshw(struct bc_state *bcs)
+{
+ int i;
+ struct bas_bc_state *ubc;
+
+ bcs->hw.bas = ubc = kmalloc(sizeof(struct bas_bc_state), GFP_KERNEL);
+ if (!ubc) {
+ err("could not allocate bas_bc_state");
+ return 0;
+ }
+
+ atomic_set(&ubc->running, 0);
+ atomic_set(&ubc->corrbytes, 0);
+ spin_lock_init(&ubc->isooutlock);
+ for (i = 0; i < BAS_OUTURBS; ++i) {
+ ubc->isoouturbs[i].urb = NULL;
+ ubc->isoouturbs[i].bcs = bcs;
+ }
+ ubc->isooutdone = ubc->isooutfree = ubc->isooutovfl = NULL;
+ ubc->numsub = 0;
+ if (!(ubc->isooutbuf = kmalloc(sizeof(struct isowbuf_t), GFP_KERNEL))) {
+ err("could not allocate isochronous output buffer");
+ kfree(ubc);
+ bcs->hw.bas = NULL;
+ return 0;
+ }
+ tasklet_init(&ubc->sent_tasklet,
+ &write_iso_tasklet, (unsigned long) bcs);
+
+ spin_lock_init(&ubc->isoinlock);
+ for (i = 0; i < BAS_INURBS; ++i)
+ ubc->isoinurbs[i] = NULL;
+ ubc->isoindone = NULL;
+ ubc->loststatus = -EINPROGRESS;
+ ubc->isoinlost = 0;
+ ubc->seqlen = 0;
+ ubc->inbyte = 0;
+ ubc->inbits = 0;
+ ubc->goodbytes = 0;
+ ubc->alignerrs = 0;
+ ubc->fcserrs = 0;
+ ubc->frameerrs = 0;
+ ubc->giants = 0;
+ ubc->runts = 0;
+ ubc->aborts = 0;
+ ubc->shared0s = 0;
+ ubc->stolen0s = 0;
+ tasklet_init(&ubc->rcvd_tasklet,
+ &read_iso_tasklet, (unsigned long) bcs);
+ return 1;
+}
+
+static void gigaset_reinitbcshw(struct bc_state *bcs)
+{
+ struct bas_bc_state *ubc = bcs->hw.bas;
+
+ atomic_set(&bcs->hw.bas->running, 0);
+ atomic_set(&bcs->hw.bas->corrbytes, 0);
+ bcs->hw.bas->numsub = 0;
+ spin_lock_init(&ubc->isooutlock);
+ spin_lock_init(&ubc->isoinlock);
+ ubc->loststatus = -EINPROGRESS;
+}
+
+static void gigaset_freecshw(struct cardstate *cs)
+{
+ struct bas_cardstate *ucs = cs->hw.bas;
+
+ del_timer(&ucs->timer_ctrl);
+ del_timer(&ucs->timer_atrdy);
+ del_timer(&ucs->timer_cmd_in);
+
+ kfree(cs->hw.bas);
+}
+
+static int gigaset_initcshw(struct cardstate *cs)
+{
+ struct bas_cardstate *ucs;
+
+ cs->hw.bas = ucs = kmalloc(sizeof *ucs, GFP_KERNEL);
+ if (!ucs)
+ return 0;
+
+ ucs->urb_cmd_in = NULL;
+ ucs->urb_cmd_out = NULL;
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+
+ spin_lock_init(&ucs->lock);
+ ucs->pending = 0;
+
+ atomic_set(&ucs->basstate, 0);
+ init_timer(&ucs->timer_ctrl);
+ init_timer(&ucs->timer_atrdy);
+ init_timer(&ucs->timer_cmd_in);
+
+ return 1;
+}
+
+/* freeurbs
+ * unlink and deallocate all URBs unconditionally
+ * caller must make sure that no commands are still in progress
+ * parameter:
+ * cs controller state structure
+ */
+static void freeurbs(struct cardstate *cs)
+{
+ struct bas_cardstate *ucs;
+ struct bas_bc_state *ubc;
+ int i, j;
+
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ for (j = 0; j < 2; ++j) {
+ ubc = cs->bcs[j].hw.bas;
+ IFNULLCONT(ubc);
+ for (i = 0; i < BAS_OUTURBS; ++i)
+ if (ubc->isoouturbs[i].urb) {
+ usb_kill_urb(ubc->isoouturbs[i].urb);
+ dbg(DEBUG_INIT,
+ "%s: isoc output URB %d/%d unlinked",
+ __func__, j, i);
+ usb_free_urb(ubc->isoouturbs[i].urb);
+ ubc->isoouturbs[i].urb = NULL;
+ }
+ for (i = 0; i < BAS_INURBS; ++i)
+ if (ubc->isoinurbs[i]) {
+ usb_kill_urb(ubc->isoinurbs[i]);
+ dbg(DEBUG_INIT,
+ "%s: isoc input URB %d/%d unlinked",
+ __func__, j, i);
+ usb_free_urb(ubc->isoinurbs[i]);
+ ubc->isoinurbs[i] = NULL;
+ }
+ }
+ if (ucs->urb_int_in) {
+ usb_kill_urb(ucs->urb_int_in);
+ dbg(DEBUG_INIT, "%s: interrupt input URB unlinked", __func__);
+ usb_free_urb(ucs->urb_int_in);
+ ucs->urb_int_in = NULL;
+ }
+ if (ucs->urb_cmd_out) {
+ usb_kill_urb(ucs->urb_cmd_out);
+ dbg(DEBUG_INIT, "%s: command output URB unlinked", __func__);
+ usb_free_urb(ucs->urb_cmd_out);
+ ucs->urb_cmd_out = NULL;
+ }
+ if (ucs->urb_cmd_in) {
+ usb_kill_urb(ucs->urb_cmd_in);
+ dbg(DEBUG_INIT, "%s: command input URB unlinked", __func__);
+ usb_free_urb(ucs->urb_cmd_in);
+ ucs->urb_cmd_in = NULL;
+ }
+ if (ucs->urb_ctrl) {
+ usb_kill_urb(ucs->urb_ctrl);
+ dbg(DEBUG_INIT, "%s: control output URB unlinked", __func__);
+ usb_free_urb(ucs->urb_ctrl);
+ ucs->urb_ctrl = NULL;
+ }
+}
+
+/* gigaset_probe
+ * This function is called when a new USB device is connected.
+ * It checks whether the new device is handled by this driver.
+ */
+static int gigaset_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_host_interface *hostif;
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct cardstate *cs = NULL;
+ struct bas_cardstate *ucs = NULL;
+ struct bas_bc_state *ubc;
+ struct usb_endpoint_descriptor *endpoint;
+ int i, j;
+ int ret;
+
+ IFNULLRETVAL(udev, -ENODEV);
+
+ dbg(DEBUG_ANY,
+ "%s: Check if device matches .. (Vendor: 0x%x, Product: 0x%x)",
+ __func__, le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ /* See if the device offered us matches what we can accept */
+ if ((le16_to_cpu(udev->descriptor.idVendor) != USB_GIGA_VENDOR_ID) ||
+ (le16_to_cpu(udev->descriptor.idProduct) != USB_GIGA_PRODUCT_ID &&
+ le16_to_cpu(udev->descriptor.idProduct) != USB_4175_PRODUCT_ID &&
+ le16_to_cpu(udev->descriptor.idProduct) != USB_SX303_PRODUCT_ID &&
+ le16_to_cpu(udev->descriptor.idProduct) != USB_SX353_PRODUCT_ID)) {
+ dbg(DEBUG_ANY, "%s: unmatched ID - exiting", __func__);
+ return -ENODEV;
+ }
+
+ /* set required alternate setting */
+ hostif = interface->cur_altsetting;
+ if (hostif->desc.bAlternateSetting != 3) {
+ dbg(DEBUG_ANY,
+ "%s: wrong alternate setting %d - trying to switch",
+ __func__, hostif->desc.bAlternateSetting);
+ if (usb_set_interface(udev, hostif->desc.bInterfaceNumber, 3) < 0) {
+ warn("usb_set_interface failed, device %d interface %d altsetting %d",
+ udev->devnum, hostif->desc.bInterfaceNumber,
+ hostif->desc.bAlternateSetting);
+ return -ENODEV;
+ }
+ hostif = interface->cur_altsetting;
+ }
+
+ /* Reject application specific interfaces
+ */
+ if (hostif->desc.bInterfaceClass != 255) {
+ warn("%s: bInterfaceClass == %d",
+ __func__, hostif->desc.bInterfaceClass);
+ return -ENODEV;
+ }
+
+ info("%s: Device matched (Vendor: 0x%x, Product: 0x%x)",
+ __func__, le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ cs = gigaset_getunassignedcs(driver);
+ if (!cs) {
+ err("%s: no free cardstate", __func__);
+ return -ENODEV;
+ }
+ ucs = cs->hw.bas;
+ ucs->udev = udev;
+ ucs->interface = interface;
+
+ /* allocate URBs:
+ * - one for the interrupt pipe
+ * - three for the different uses of the default control pipe
+ * - three for each isochronous pipe
+ */
+ ucs->urb_int_in = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->urb_int_in) {
+ err("No free urbs available");
+ goto error;
+ }
+ ucs->urb_cmd_in = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->urb_cmd_in) {
+ err("No free urbs available");
+ goto error;
+ }
+ ucs->urb_cmd_out = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->urb_cmd_out) {
+ err("No free urbs available");
+ goto error;
+ }
+ ucs->urb_ctrl = usb_alloc_urb(0, SLAB_KERNEL);
+ if (!ucs->urb_ctrl) {
+ err("No free urbs available");
+ goto error;
+ }
+
+ for (j = 0; j < 2; ++j) {
+ ubc = cs->bcs[j].hw.bas;
+ for (i = 0; i < BAS_OUTURBS; ++i) {
+ ubc->isoouturbs[i].urb =
+ usb_alloc_urb(BAS_NUMFRAMES, SLAB_KERNEL);
+ if (!ubc->isoouturbs[i].urb) {
+ err("No free urbs available");
+ goto error;
+ }
+ }
+ for (i = 0; i < BAS_INURBS; ++i) {
+ ubc->isoinurbs[i] =
+ usb_alloc_urb(BAS_NUMFRAMES, SLAB_KERNEL);
+ if (!ubc->isoinurbs[i]) {
+ err("No free urbs available");
+ goto error;
+ }
+ }
+ }
+
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+
+ /* Fill the interrupt urb and send it to the core */
+ endpoint = &hostif->endpoint[0].desc;
+ usb_fill_int_urb(ucs->urb_int_in, udev,
+ usb_rcvintpipe(udev,
+ (endpoint->bEndpointAddress) & 0x0f),
+ ucs->int_in_buf, 3, read_int_callback, cs,
+ endpoint->bInterval);
+ ret = usb_submit_urb(ucs->urb_int_in, SLAB_KERNEL);
+ if (ret) {
+ err("could not submit interrupt URB: %s", get_usb_statmsg(ret));
+ goto error;
+ }
+
+ /* tell the device that the driver is ready */
+ if ((ret = req_submit(cs->bcs, HD_DEVICE_INIT_ACK, 0, 0)) != 0)
+ goto error;
+
+ /* tell common part that the device is ready */
+ if (startmode == SM_LOCKED)
+ atomic_set(&cs->mstate, MS_LOCKED);
+ if (!gigaset_start(cs))
+ goto error;
+
+ /* save address of controller structure */
+ usb_set_intfdata(interface, cs);
+
+ /* set up device sysfs */
+ gigaset_init_dev_sysfs(interface);
+ return 0;
+
+error:
+ freeurbs(cs);
+ gigaset_unassign(cs);
+ return -ENODEV;
+}
+
+/* gigaset_disconnect
+ * This function is called when the Gigaset base is unplugged.
+ */
+static void gigaset_disconnect(struct usb_interface *interface)
+{
+ struct cardstate *cs;
+ struct bas_cardstate *ucs;
+
+ /* clear device sysfs */
+ gigaset_free_dev_sysfs(interface);
+
+ cs = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+
+ IFNULLRET(cs);
+ ucs = cs->hw.bas;
+ IFNULLRET(ucs);
+
+ info("disconnecting GigaSet base");
+ gigaset_stop(cs);
+ freeurbs(cs);
+ kfree(ucs->rcvbuf);
+ ucs->rcvbuf = NULL;
+ ucs->rcvbuf_size = 0;
+ atomic_set(&ucs->basstate, 0);
+ gigaset_unassign(cs);
+}
+
+static struct gigaset_ops gigops = {
+ gigaset_write_cmd,
+ gigaset_write_room,
+ gigaset_chars_in_buffer,
+ gigaset_brkchars,
+ gigaset_init_bchannel,
+ gigaset_close_bchannel,
+ gigaset_initbcshw,
+ gigaset_freebcshw,
+ gigaset_reinitbcshw,
+ gigaset_initcshw,
+ gigaset_freecshw,
+ gigaset_set_modem_ctrl,
+ gigaset_baud_rate,
+ gigaset_set_line_ctrl,
+ gigaset_isoc_send_skb,
+ gigaset_isoc_input,
+};
+
+/* bas_gigaset_init
+ * This function is called after the kernel module is loaded.
+ */
+static int __init bas_gigaset_init(void)
+{
+ int result;
+
+ /* allocate memory for our driver state and intialize it */
+ if ((driver = gigaset_initdriver(GIGASET_MINOR, GIGASET_MINORS,
+ GIGASET_MODULENAME, GIGASET_DEVNAME,
+ GIGASET_DEVFSNAME, &gigops,
+ THIS_MODULE)) == NULL)
+ goto error;
+
+ /* allocate memory for our device state and intialize it */
+ cardstate = gigaset_initcs(driver, 2, 0, 0, cidmode, GIGASET_MODULENAME);
+ if (!cardstate)
+ goto error;
+
+ /* register this driver with the USB subsystem */
+ result = usb_register(&gigaset_usb_driver);
+ if (result < 0) {
+ err("usb_register failed (error %d)", -result);
+ goto error;
+ }
+
+ info(DRIVER_AUTHOR);
+ info(DRIVER_DESC);
+ return 0;
+
+error: if (cardstate)
+ gigaset_freecs(cardstate);
+ cardstate = NULL;
+ if (driver)
+ gigaset_freedriver(driver);
+ driver = NULL;
+ return -1;
+}
+
+/* bas_gigaset_exit
+ * This function is called before the kernel module is unloaded.
+ */
+static void __exit bas_gigaset_exit(void)
+{
+ gigaset_blockdriver(driver); /* => probe will fail
+ * => no gigaset_start any more
+ */
+
+ gigaset_shutdown(cardstate);
+ /* from now on, no isdn callback should be possible */
+
+ if (atomic_read(&cardstate->hw.bas->basstate) & BS_ATOPEN) {
+ dbg(DEBUG_ANY, "closing AT channel");
+ if (req_submit(cardstate->bcs,
+ HD_CLOSE_ATCHANNEL, 0, BAS_TIMEOUT) >= 0) {
+ /* successfully submitted - wait for completion */
+ //wait_event_interruptible(cs->initwait, !cs->hw.bas->pending);
+ //FIXME need own wait queue? wakeup?
+ }
+ }
+
+ /* deregister this driver with the USB subsystem */
+ usb_deregister(&gigaset_usb_driver);
+ /* this will call the disconnect-callback */
+ /* from now on, no disconnect/probe callback should be running */
+
+ gigaset_freecs(cardstate);
+ cardstate = NULL;
+ gigaset_freedriver(driver);
+ driver = NULL;
+}
+
+
+module_init(bas_gigaset_init);
+module_exit(bas_gigaset_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");

2006-02-11 15:01:53

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 8/9] isdn4linux: Siemens Gigaset drivers - isochronous data handler

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch adds the payload data handler for the connection-specific
module "bas_gigaset". It contains the code for handling isochronous data
transfers, HDLC framing and flow control.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

drivers/isdn/gigaset/isocdata.c | 1009 ++++++++++++++++++++++++++++++++++++++++
1 files changed, 1009 insertions(+)

--- linux-2.6.16-rc2/drivers/isdn/gigaset/isocdata.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/isocdata.c 2006-01-21 17:33:33.000000000 +0100
@@ -0,0 +1,1009 @@
+/*
+ * Common data handling layer for bas_gigaset
+ *
+ * Copyright (c) 2005 by Tilman Schmidt <[email protected]>,
+ * Hansjoerg Lipp <[email protected]>.
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: isocdata.c,v 1.2.2.5 2005/11/13 23:05:19 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/crc-ccitt.h>
+
+/* access methods for isowbuf_t */
+/* ============================ */
+
+/* initialize buffer structure
+ */
+void gigaset_isowbuf_init(struct isowbuf_t *iwb, unsigned char idle)
+{
+ atomic_set(&iwb->read, 0);
+ atomic_set(&iwb->nextread, 0);
+ atomic_set(&iwb->write, 0);
+ atomic_set(&iwb->writesem, 1);
+ iwb->wbits = 0;
+ iwb->idle = idle;
+ memset(iwb->data + BAS_OUTBUFSIZE, idle, BAS_OUTBUFPAD);
+}
+
+/* compute number of bytes which can be appended to buffer
+ * so that there is still room to append a maximum frame of flags
+ */
+static inline int isowbuf_freebytes(struct isowbuf_t *iwb)
+{
+ int read, write, freebytes;
+
+ read = atomic_read(&iwb->read);
+ write = atomic_read(&iwb->write);
+ if ((freebytes = read - write) > 0) {
+ /* no wraparound: need padding space within regular area */
+ return freebytes - BAS_OUTBUFPAD;
+ } else if (read < BAS_OUTBUFPAD) {
+ /* wraparound: can use space up to end of regular area */
+ return BAS_OUTBUFSIZE - write;
+ } else {
+ /* following the wraparound yields more space */
+ return freebytes + BAS_OUTBUFSIZE - BAS_OUTBUFPAD;
+ }
+}
+
+/* compare two offsets within the buffer
+ * The buffer is seen as circular, with the read position as start
+ * returns -1/0/1 if position a </=/> position b without crossing 'read'
+ */
+static inline int isowbuf_poscmp(struct isowbuf_t *iwb, int a, int b)
+{
+ int read;
+ if (a == b)
+ return 0;
+ read = atomic_read(&iwb->read);
+ if (a < b) {
+ if (a < read && read <= b)
+ return +1;
+ else
+ return -1;
+ } else {
+ if (b < read && read <= a)
+ return -1;
+ else
+ return +1;
+ }
+}
+
+/* start writing
+ * acquire the write semaphore
+ * return true if acquired, false if busy
+ */
+static inline int isowbuf_startwrite(struct isowbuf_t *iwb)
+{
+ if (!atomic_dec_and_test(&iwb->writesem)) {
+ atomic_inc(&iwb->writesem);
+ dbg(DEBUG_ISO,
+ "%s: couldn't acquire iso write semaphore", __func__);
+ return 0;
+ }
+#ifdef CONFIG_GIGASET_DEBUG
+ dbg(DEBUG_ISO,
+ "%s: acquired iso write semaphore, data[write]=%02x, nbits=%d",
+ __func__, iwb->data[atomic_read(&iwb->write)], iwb->wbits);
+#endif
+ return 1;
+}
+
+/* finish writing
+ * release the write semaphore and update the maximum buffer fill level
+ * returns the current write position
+ */
+static inline int isowbuf_donewrite(struct isowbuf_t *iwb)
+{
+ int write = atomic_read(&iwb->write);
+ atomic_inc(&iwb->writesem);
+ return write;
+}
+
+/* append bits to buffer without any checks
+ * - data contains bits to append, starting at LSB
+ * - nbits is number of bits to append (0..24)
+ * must be called with the write semaphore held
+ * If more than nbits bits are set in data, the extraneous bits are set in the
+ * buffer too, but the write position is only advanced by nbits.
+ */
+static inline void isowbuf_putbits(struct isowbuf_t *iwb, u32 data, int nbits)
+{
+ int write = atomic_read(&iwb->write);
+ data <<= iwb->wbits;
+ data |= iwb->data[write];
+ nbits += iwb->wbits;
+ while (nbits >= 8) {
+ iwb->data[write++] = data & 0xff;
+ write %= BAS_OUTBUFSIZE;
+ data >>= 8;
+ nbits -= 8;
+ }
+ iwb->wbits = nbits;
+ iwb->data[write] = data & 0xff;
+ atomic_set(&iwb->write, write);
+}
+
+/* put final flag on HDLC bitstream
+ * also sets the idle fill byte to the correspondingly shifted flag pattern
+ * must be called with the write semaphore held
+ */
+static inline void isowbuf_putflag(struct isowbuf_t *iwb)
+{
+ int write;
+
+ /* add two flags, thus reliably covering one byte */
+ isowbuf_putbits(iwb, 0x7e7e, 8);
+ /* recover the idle flag byte */
+ write = atomic_read(&iwb->write);
+ iwb->idle = iwb->data[write];
+ dbg(DEBUG_ISO, "idle fill byte %02x", iwb->idle);
+ /* mask extraneous bits in buffer */
+ iwb->data[write] &= (1 << iwb->wbits) - 1;
+}
+
+/* retrieve a block of bytes for sending
+ * The requested number of bytes is provided as a contiguous block.
+ * If necessary, the frame is filled to the requested number of bytes
+ * with the idle value.
+ * returns offset to frame, < 0 on busy or error
+ */
+int gigaset_isowbuf_getbytes(struct isowbuf_t *iwb, int size)
+{
+ int read, write, limit, src, dst;
+ unsigned char pbyte;
+
+ read = atomic_read(&iwb->nextread);
+ write = atomic_read(&iwb->write);
+ if (likely(read == write)) {
+ //dbg(DEBUG_STREAM, "%s: send buffer empty", __func__);
+ /* return idle frame */
+ return read < BAS_OUTBUFPAD ?
+ BAS_OUTBUFSIZE : read - BAS_OUTBUFPAD;
+ }
+
+ limit = read + size;
+ dbg(DEBUG_STREAM,
+ "%s: read=%d write=%d limit=%d", __func__, read, write, limit);
+#ifdef CONFIG_GIGASET_DEBUG
+ if (unlikely(size < 0 || size > BAS_OUTBUFPAD)) {
+ err("invalid size %d", size);
+ return -EINVAL;
+ }
+ src = atomic_read(&iwb->read);
+ if (unlikely(limit > BAS_OUTBUFSIZE + BAS_OUTBUFPAD ||
+ (read < src && limit >= src))) {
+ err("isoc write buffer frame reservation violated");
+ return -EFAULT;
+ }
+#endif
+
+ if (read < write) {
+ /* no wraparound in valid data */
+ if (limit >= write) {
+ /* append idle frame */
+ if (!isowbuf_startwrite(iwb))
+ return -EBUSY;
+ /* write position could have changed */
+ if (limit >= (write = atomic_read(&iwb->write))) {
+ pbyte = iwb->data[write]; /* save partial byte */
+ limit = write + BAS_OUTBUFPAD;
+ dbg(DEBUG_STREAM,
+ "%s: filling %d->%d with %02x",
+ __func__, write, limit, iwb->idle);
+ if (write + BAS_OUTBUFPAD < BAS_OUTBUFSIZE)
+ memset(iwb->data + write, iwb->idle,
+ BAS_OUTBUFPAD);
+ else {
+ /* wraparound, fill entire pad area */
+ memset(iwb->data + write, iwb->idle,
+ BAS_OUTBUFSIZE + BAS_OUTBUFPAD
+ - write);
+ limit = 0;
+ }
+ dbg(DEBUG_STREAM, "%s: restoring %02x at %d",
+ __func__, pbyte, limit);
+ iwb->data[limit] = pbyte; /* restore partial byte */
+ atomic_set(&iwb->write, limit);
+ }
+ isowbuf_donewrite(iwb);
+ }
+ } else {
+ /* valid data wraparound */
+ if (limit >= BAS_OUTBUFSIZE) {
+ /* copy wrapped part into pad area */
+ src = 0;
+ dst = BAS_OUTBUFSIZE;
+ while (dst < limit && src < write)
+ iwb->data[dst++] = iwb->data[src++];
+ if (dst <= limit) {
+ /* fill pad area with idle byte */
+ memset(iwb->data + dst, iwb->idle,
+ BAS_OUTBUFSIZE + BAS_OUTBUFPAD - dst);
+ }
+ limit = src;
+ }
+ }
+ atomic_set(&iwb->nextread, limit);
+ return read;
+}
+
+/* dump_bytes
+ * write hex bytes to syslog for debugging
+ */
+static inline void dump_bytes(enum debuglevel level, const char *tag,
+ unsigned char *bytes, int count)
+{
+#ifdef CONFIG_GIGASET_DEBUG
+ unsigned char c;
+ static char dbgline[3 * 32 + 1];
+ static const char hexdigit[] = "0123456789abcdef";
+ int i = 0;
+ IFNULLRET(tag);
+ IFNULLRET(bytes);
+ while (count-- > 0) {
+ if (i > sizeof(dbgline) - 4) {
+ dbgline[i] = '\0';
+ dbg(level, "%s:%s", tag, dbgline);
+ i = 0;
+ }
+ c = *bytes++;
+ dbgline[i] = (i && !(i % 12)) ? '-' : ' ';
+ i++;
+ dbgline[i++] = hexdigit[(c >> 4) & 0x0f];
+ dbgline[i++] = hexdigit[c & 0x0f];
+ }
+ dbgline[i] = '\0';
+ dbg(level, "%s:%s", tag, dbgline);
+#endif
+}
+
+/*============================================================================*/
+
+/* bytewise HDLC bitstuffing via table lookup
+ * lookup table: 5 subtables for 0..4 preceding consecutive '1' bits
+ * index: 256*(number of preceding '1' bits) + (next byte to stuff)
+ * value: bit 9.. 0 = result bits
+ * bit 12..10 = number of trailing '1' bits in result
+ * bit 14..13 = number of bits added by stuffing
+ */
+static u16 stufftab[5 * 256] = {
+// previous 1s = 0:
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x201f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x205f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x209f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20df,
+ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f,
+ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x251f,
+ 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af,
+ 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x255f,
+ 0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x08c7, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x08cf,
+ 0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x08d7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x299f,
+ 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x0ce7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x0cef,
+ 0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x10f7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x2ddf,
+
+// previous 1s = 1:
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x200f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x202f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x204f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x206f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x208f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x20af,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x20cf,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20ef,
+ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x250f,
+ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x252f,
+ 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x254f,
+ 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x256f,
+ 0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x08c7, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x298f,
+ 0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x08d7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x29af,
+ 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x0ce7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x2dcf,
+ 0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x10f7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x31ef,
+
+// previous 1s = 2:
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x2007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x2017,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x2027, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x2037,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x2047, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x2057,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x2067, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x203e, 0x2077,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x2087, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x2097,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x20a7, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x20b7,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x20c7, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x20d7,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x20e7, 0x0078, 0x0079, 0x007a, 0x007b, 0x207c, 0x207d, 0x20be, 0x20f7,
+ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x2507, 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x2517,
+ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x2527, 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x2537,
+ 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x2547, 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x2557,
+ 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x2567, 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x253e, 0x2577,
+ 0x08c0, 0x08c1, 0x08c2, 0x08c3, 0x08c4, 0x08c5, 0x08c6, 0x2987, 0x08c8, 0x08c9, 0x08ca, 0x08cb, 0x08cc, 0x08cd, 0x08ce, 0x2997,
+ 0x08d0, 0x08d1, 0x08d2, 0x08d3, 0x08d4, 0x08d5, 0x08d6, 0x29a7, 0x08d8, 0x08d9, 0x08da, 0x08db, 0x08dc, 0x08dd, 0x08de, 0x29b7,
+ 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x2dc7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x2dd7,
+ 0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x31e7, 0x20f8, 0x20f9, 0x20fa, 0x20fb, 0x257c, 0x257d, 0x29be, 0x41f7,
+
+// previous 1s = 3:
+ 0x0000, 0x0001, 0x0002, 0x2003, 0x0004, 0x0005, 0x0006, 0x200b, 0x0008, 0x0009, 0x000a, 0x2013, 0x000c, 0x000d, 0x000e, 0x201b,
+ 0x0010, 0x0011, 0x0012, 0x2023, 0x0014, 0x0015, 0x0016, 0x202b, 0x0018, 0x0019, 0x001a, 0x2033, 0x001c, 0x001d, 0x001e, 0x203b,
+ 0x0020, 0x0021, 0x0022, 0x2043, 0x0024, 0x0025, 0x0026, 0x204b, 0x0028, 0x0029, 0x002a, 0x2053, 0x002c, 0x002d, 0x002e, 0x205b,
+ 0x0030, 0x0031, 0x0032, 0x2063, 0x0034, 0x0035, 0x0036, 0x206b, 0x0038, 0x0039, 0x003a, 0x2073, 0x003c, 0x003d, 0x203e, 0x207b,
+ 0x0040, 0x0041, 0x0042, 0x2083, 0x0044, 0x0045, 0x0046, 0x208b, 0x0048, 0x0049, 0x004a, 0x2093, 0x004c, 0x004d, 0x004e, 0x209b,
+ 0x0050, 0x0051, 0x0052, 0x20a3, 0x0054, 0x0055, 0x0056, 0x20ab, 0x0058, 0x0059, 0x005a, 0x20b3, 0x005c, 0x005d, 0x005e, 0x20bb,
+ 0x0060, 0x0061, 0x0062, 0x20c3, 0x0064, 0x0065, 0x0066, 0x20cb, 0x0068, 0x0069, 0x006a, 0x20d3, 0x006c, 0x006d, 0x006e, 0x20db,
+ 0x0070, 0x0071, 0x0072, 0x20e3, 0x0074, 0x0075, 0x0076, 0x20eb, 0x0078, 0x0079, 0x007a, 0x20f3, 0x207c, 0x207d, 0x20be, 0x40fb,
+ 0x0480, 0x0481, 0x0482, 0x2503, 0x0484, 0x0485, 0x0486, 0x250b, 0x0488, 0x0489, 0x048a, 0x2513, 0x048c, 0x048d, 0x048e, 0x251b,
+ 0x0490, 0x0491, 0x0492, 0x2523, 0x0494, 0x0495, 0x0496, 0x252b, 0x0498, 0x0499, 0x049a, 0x2533, 0x049c, 0x049d, 0x049e, 0x253b,
+ 0x04a0, 0x04a1, 0x04a2, 0x2543, 0x04a4, 0x04a5, 0x04a6, 0x254b, 0x04a8, 0x04a9, 0x04aa, 0x2553, 0x04ac, 0x04ad, 0x04ae, 0x255b,
+ 0x04b0, 0x04b1, 0x04b2, 0x2563, 0x04b4, 0x04b5, 0x04b6, 0x256b, 0x04b8, 0x04b9, 0x04ba, 0x2573, 0x04bc, 0x04bd, 0x253e, 0x257b,
+ 0x08c0, 0x08c1, 0x08c2, 0x2983, 0x08c4, 0x08c5, 0x08c6, 0x298b, 0x08c8, 0x08c9, 0x08ca, 0x2993, 0x08cc, 0x08cd, 0x08ce, 0x299b,
+ 0x08d0, 0x08d1, 0x08d2, 0x29a3, 0x08d4, 0x08d5, 0x08d6, 0x29ab, 0x08d8, 0x08d9, 0x08da, 0x29b3, 0x08dc, 0x08dd, 0x08de, 0x29bb,
+ 0x0ce0, 0x0ce1, 0x0ce2, 0x2dc3, 0x0ce4, 0x0ce5, 0x0ce6, 0x2dcb, 0x0ce8, 0x0ce9, 0x0cea, 0x2dd3, 0x0cec, 0x0ced, 0x0cee, 0x2ddb,
+ 0x10f0, 0x10f1, 0x10f2, 0x31e3, 0x10f4, 0x10f5, 0x10f6, 0x31eb, 0x20f8, 0x20f9, 0x20fa, 0x41f3, 0x257c, 0x257d, 0x29be, 0x46fb,
+
+// previous 1s = 4:
+ 0x0000, 0x2001, 0x0002, 0x2005, 0x0004, 0x2009, 0x0006, 0x200d, 0x0008, 0x2011, 0x000a, 0x2015, 0x000c, 0x2019, 0x000e, 0x201d,
+ 0x0010, 0x2021, 0x0012, 0x2025, 0x0014, 0x2029, 0x0016, 0x202d, 0x0018, 0x2031, 0x001a, 0x2035, 0x001c, 0x2039, 0x001e, 0x203d,
+ 0x0020, 0x2041, 0x0022, 0x2045, 0x0024, 0x2049, 0x0026, 0x204d, 0x0028, 0x2051, 0x002a, 0x2055, 0x002c, 0x2059, 0x002e, 0x205d,
+ 0x0030, 0x2061, 0x0032, 0x2065, 0x0034, 0x2069, 0x0036, 0x206d, 0x0038, 0x2071, 0x003a, 0x2075, 0x003c, 0x2079, 0x203e, 0x407d,
+ 0x0040, 0x2081, 0x0042, 0x2085, 0x0044, 0x2089, 0x0046, 0x208d, 0x0048, 0x2091, 0x004a, 0x2095, 0x004c, 0x2099, 0x004e, 0x209d,
+ 0x0050, 0x20a1, 0x0052, 0x20a5, 0x0054, 0x20a9, 0x0056, 0x20ad, 0x0058, 0x20b1, 0x005a, 0x20b5, 0x005c, 0x20b9, 0x005e, 0x20bd,
+ 0x0060, 0x20c1, 0x0062, 0x20c5, 0x0064, 0x20c9, 0x0066, 0x20cd, 0x0068, 0x20d1, 0x006a, 0x20d5, 0x006c, 0x20d9, 0x006e, 0x20dd,
+ 0x0070, 0x20e1, 0x0072, 0x20e5, 0x0074, 0x20e9, 0x0076, 0x20ed, 0x0078, 0x20f1, 0x007a, 0x20f5, 0x207c, 0x40f9, 0x20be, 0x417d,
+ 0x0480, 0x2501, 0x0482, 0x2505, 0x0484, 0x2509, 0x0486, 0x250d, 0x0488, 0x2511, 0x048a, 0x2515, 0x048c, 0x2519, 0x048e, 0x251d,
+ 0x0490, 0x2521, 0x0492, 0x2525, 0x0494, 0x2529, 0x0496, 0x252d, 0x0498, 0x2531, 0x049a, 0x2535, 0x049c, 0x2539, 0x049e, 0x253d,
+ 0x04a0, 0x2541, 0x04a2, 0x2545, 0x04a4, 0x2549, 0x04a6, 0x254d, 0x04a8, 0x2551, 0x04aa, 0x2555, 0x04ac, 0x2559, 0x04ae, 0x255d,
+ 0x04b0, 0x2561, 0x04b2, 0x2565, 0x04b4, 0x2569, 0x04b6, 0x256d, 0x04b8, 0x2571, 0x04ba, 0x2575, 0x04bc, 0x2579, 0x253e, 0x467d,
+ 0x08c0, 0x2981, 0x08c2, 0x2985, 0x08c4, 0x2989, 0x08c6, 0x298d, 0x08c8, 0x2991, 0x08ca, 0x2995, 0x08cc, 0x2999, 0x08ce, 0x299d,
+ 0x08d0, 0x29a1, 0x08d2, 0x29a5, 0x08d4, 0x29a9, 0x08d6, 0x29ad, 0x08d8, 0x29b1, 0x08da, 0x29b5, 0x08dc, 0x29b9, 0x08de, 0x29bd,
+ 0x0ce0, 0x2dc1, 0x0ce2, 0x2dc5, 0x0ce4, 0x2dc9, 0x0ce6, 0x2dcd, 0x0ce8, 0x2dd1, 0x0cea, 0x2dd5, 0x0cec, 0x2dd9, 0x0cee, 0x2ddd,
+ 0x10f0, 0x31e1, 0x10f2, 0x31e5, 0x10f4, 0x31e9, 0x10f6, 0x31ed, 0x20f8, 0x41f1, 0x20fa, 0x41f5, 0x257c, 0x46f9, 0x29be, 0x4b7d
+};
+
+/* hdlc_bitstuff_byte
+ * perform HDLC bitstuffing for one input byte (8 bits, LSB first)
+ * parameters:
+ * cin input byte
+ * ones number of trailing '1' bits in result before this step
+ * iwb pointer to output buffer structure (write semaphore must be held)
+ * return value:
+ * number of trailing '1' bits in result after this step
+ */
+
+static inline int hdlc_bitstuff_byte(struct isowbuf_t *iwb, unsigned char cin,
+ int ones)
+{
+ u16 stuff;
+ int shiftinc, newones;
+
+ /* get stuffing information for input byte
+ * value: bit 9.. 0 = result bits
+ * bit 12..10 = number of trailing '1' bits in result
+ * bit 14..13 = number of bits added by stuffing
+ */
+ stuff = stufftab[256 * ones + cin];
+ shiftinc = (stuff >> 13) & 3;
+ newones = (stuff >> 10) & 7;
+ stuff &= 0x3ff;
+
+ /* append stuffed byte to output stream */
+ isowbuf_putbits(iwb, stuff, 8 + shiftinc);
+ return newones;
+}
+
+/* hdlc_buildframe
+ * Perform HDLC framing with bitstuffing on a byte buffer
+ * The input buffer is regarded as a sequence of bits, starting with the least
+ * significant bit of the first byte and ending with the most significant bit
+ * of the last byte. A 16 bit FCS is appended as defined by RFC 1662.
+ * Whenever five consecutive '1' bits appear in the resulting bit sequence, a
+ * '0' bit is inserted after them.
+ * The resulting bit string and a closing flag pattern (PPP_FLAG, '01111110')
+ * are appended to the output buffer starting at the given bit position, which
+ * is assumed to already contain a leading flag.
+ * The output buffer must have sufficient length; count + count/5 + 6 bytes
+ * starting at *out are safe and are verified to be present.
+ * parameters:
+ * in input buffer
+ * count number of bytes in input buffer
+ * iwb pointer to output buffer structure (write semaphore must be held)
+ * return value:
+ * position of end of packet in output buffer on success,
+ * -EAGAIN if write semaphore busy or buffer full
+ */
+
+static inline int hdlc_buildframe(struct isowbuf_t *iwb,
+ unsigned char *in, int count)
+{
+ int ones;
+ u16 fcs;
+ int end;
+ unsigned char c;
+
+ if (isowbuf_freebytes(iwb) < count + count / 5 + 6 ||
+ !isowbuf_startwrite(iwb)) {
+ dbg(DEBUG_ISO, "%s: %d bytes free -> -EAGAIN",
+ __func__, isowbuf_freebytes(iwb));
+ return -EAGAIN;
+ }
+
+ dump_bytes(DEBUG_STREAM, "snd data", in, count);
+
+ /* bitstuff and checksum input data */
+ fcs = PPP_INITFCS;
+ ones = 0;
+ while (count-- > 0) {
+ c = *in++;
+ ones = hdlc_bitstuff_byte(iwb, c, ones);
+ fcs = crc_ccitt_byte(fcs, c);
+ }
+
+ /* bitstuff and append FCS (complemented, least significant byte first) */
+ fcs ^= 0xffff;
+ ones = hdlc_bitstuff_byte(iwb, fcs & 0x00ff, ones);
+ ones = hdlc_bitstuff_byte(iwb, (fcs >> 8) & 0x00ff, ones);
+
+ /* put closing flag and repeat byte for flag idle */
+ isowbuf_putflag(iwb);
+ end = isowbuf_donewrite(iwb);
+ dump_bytes(DEBUG_STREAM_DUMP, "isowbuf", iwb->data, end + 1);
+ return end;
+}
+
+/* trans_buildframe
+ * Append a block of 'transparent' data to the output buffer,
+ * inverting the bytes.
+ * The output buffer must have sufficient length; count bytes
+ * starting at *out are safe and are verified to be present.
+ * parameters:
+ * in input buffer
+ * count number of bytes in input buffer
+ * iwb pointer to output buffer structure (write semaphore must be held)
+ * return value:
+ * position of end of packet in output buffer on success,
+ * -EAGAIN if write semaphore busy or buffer full
+ */
+
+static inline int trans_buildframe(struct isowbuf_t *iwb,
+ unsigned char *in, int count)
+{
+ int write;
+ unsigned char c;
+
+ if (unlikely(count <= 0))
+ return atomic_read(&iwb->write); /* better ideas? */
+
+ if (isowbuf_freebytes(iwb) < count ||
+ !isowbuf_startwrite(iwb)) {
+ dbg(DEBUG_ISO, "can't put %d bytes", count);
+ return -EAGAIN;
+ }
+
+ dbg(DEBUG_STREAM, "put %d bytes", count);
+ write = atomic_read(&iwb->write);
+ do {
+ c = gigaset_invtab[*in++];
+ iwb->data[write++] = c;
+ write %= BAS_OUTBUFSIZE;
+ } while (--count > 0);
+ atomic_set(&iwb->write, write);
+ iwb->idle = c;
+
+ return isowbuf_donewrite(iwb);
+}
+
+int gigaset_isoc_buildframe(struct bc_state *bcs, unsigned char *in, int len)
+{
+ int result;
+
+ switch (bcs->proto2) {
+ case ISDN_PROTO_L2_HDLC:
+ result = hdlc_buildframe(bcs->hw.bas->isooutbuf, in, len);
+ dbg(DEBUG_ISO, "%s: %d bytes HDLC -> %d", __func__, len, result);
+ break;
+ default: /* assume transparent */
+ result = trans_buildframe(bcs->hw.bas->isooutbuf, in, len);
+ dbg(DEBUG_ISO, "%s: %d bytes trans -> %d", __func__, len, result);
+ }
+ return result;
+}
+
+/* hdlc_putbyte
+ * append byte c to current skb of B channel structure *bcs, updating fcs
+ */
+static inline void hdlc_putbyte(unsigned char c, struct bc_state *bcs)
+{
+ bcs->fcs = crc_ccitt_byte(bcs->fcs, c);
+ if (unlikely(bcs->skb == NULL)) {
+ /* skipping */
+ return;
+ }
+ if (unlikely(bcs->skb->len == SBUFSIZE)) {
+ warn("received oversized packet discarded");
+ bcs->hw.bas->giants++;
+ dev_kfree_skb_any(bcs->skb);
+ bcs->skb = NULL;
+ return;
+ }
+ *gigaset_skb_put_quick(bcs->skb, 1) = c;
+}
+
+/* hdlc_flush
+ * drop partial HDLC data packet
+ */
+static inline void hdlc_flush(struct bc_state *bcs)
+{
+ /* clear skb or allocate new if not skipping */
+ if (likely(bcs->skb != NULL))
+ skb_trim(bcs->skb, 0);
+ else if (!bcs->ignore) {
+ if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ else
+ err("could not allocate skb");
+ }
+
+ /* reset packet state */
+ bcs->fcs = PPP_INITFCS;
+}
+
+/* hdlc_done
+ * process completed HDLC data packet
+ */
+static inline void hdlc_done(struct bc_state *bcs)
+{
+ struct sk_buff *procskb;
+
+ if (unlikely(bcs->ignore)) {
+ bcs->ignore--;
+ hdlc_flush(bcs);
+ return;
+ }
+
+ if ((procskb = bcs->skb) == NULL) {
+ /* previous error */
+ dbg(DEBUG_ISO, "%s: skb=NULL", __func__);
+ gigaset_rcv_error(NULL, bcs->cs, bcs);
+ } else if (procskb->len < 2) {
+ notice("received short frame (%d octets)", procskb->len);
+ bcs->hw.bas->runts++;
+ gigaset_rcv_error(procskb, bcs->cs, bcs);
+ } else if (bcs->fcs != PPP_GOODFCS) {
+ notice("frame check error (0x%04x)", bcs->fcs);
+ bcs->hw.bas->fcserrs++;
+ gigaset_rcv_error(procskb, bcs->cs, bcs);
+ } else {
+ procskb->len -= 2; /* subtract FCS */
+ procskb->tail -= 2;
+ dbg(DEBUG_ISO,
+ "%s: good frame (%d octets)", __func__, procskb->len);
+ dump_bytes(DEBUG_STREAM,
+ "rcv data", procskb->data, procskb->len);
+ bcs->hw.bas->goodbytes += procskb->len;
+ gigaset_rcv_skb(procskb, bcs->cs, bcs);
+ }
+
+ if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ else
+ err("could not allocate skb");
+ bcs->fcs = PPP_INITFCS;
+}
+
+/* hdlc_frag
+ * drop HDLC data packet with non-integral last byte
+ */
+static inline void hdlc_frag(struct bc_state *bcs, unsigned inbits)
+{
+ if (unlikely(bcs->ignore)) {
+ bcs->ignore--;
+ hdlc_flush(bcs);
+ return;
+ }
+
+ notice("received partial byte (%d bits)", inbits);
+ bcs->hw.bas->alignerrs++;
+ gigaset_rcv_error(bcs->skb, bcs->cs, bcs);
+
+ if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ else
+ err("could not allocate skb");
+ bcs->fcs = PPP_INITFCS;
+}
+
+/* bit counts lookup table for HDLC bit unstuffing
+ * index: input byte
+ * value: bit 0..3 = number of consecutive '1' bits starting from LSB
+ * bit 4..6 = number of consecutive '1' bits starting from MSB
+ * (replacing 8 by 7 to make it fit; the algorithm won't care)
+ * bit 7 set if there are 5 or more "interior" consecutive '1' bits
+ */
+static unsigned char bitcounts[256] = {
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x80, 0x06,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x04,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x80, 0x81, 0x80, 0x07,
+ 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x14,
+ 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x15,
+ 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x14,
+ 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x10, 0x13, 0x10, 0x11, 0x10, 0x12, 0x10, 0x11, 0x90, 0x16,
+ 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x23, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x24,
+ 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x23, 0x20, 0x21, 0x20, 0x22, 0x20, 0x21, 0x20, 0x25,
+ 0x30, 0x31, 0x30, 0x32, 0x30, 0x31, 0x30, 0x33, 0x30, 0x31, 0x30, 0x32, 0x30, 0x31, 0x30, 0x34,
+ 0x40, 0x41, 0x40, 0x42, 0x40, 0x41, 0x40, 0x43, 0x50, 0x51, 0x50, 0x52, 0x60, 0x61, 0x70, 0x78
+};
+
+/* hdlc_unpack
+ * perform HDLC frame processing (bit unstuffing, flag detection, FCS calculation)
+ * on a sequence of received data bytes (8 bits each, LSB first)
+ * pass on successfully received, complete frames as SKBs via gigaset_rcv_skb
+ * notify of errors via gigaset_rcv_error
+ * tally frames, errors etc. in BC structure counters
+ * parameters:
+ * src received data
+ * count number of received bytes
+ * bcs receiving B channel structure
+ */
+static inline void hdlc_unpack(unsigned char *src, unsigned count,
+ struct bc_state *bcs)
+{
+ struct bas_bc_state *ubc;
+ int inputstate;
+ unsigned seqlen, inbyte, inbits;
+
+ IFNULLRET(bcs);
+ ubc = bcs->hw.bas;
+ IFNULLRET(ubc);
+
+ /* load previous state:
+ * inputstate = set of flag bits:
+ * - INS_flag_hunt: no complete opening flag received since connection setup or last abort
+ * - INS_have_data: at least one complete data byte received since last flag
+ * seqlen = number of consecutive '1' bits in last 7 input stream bits (0..7)
+ * inbyte = accumulated partial data byte (if !INS_flag_hunt)
+ * inbits = number of valid bits in inbyte, starting at LSB (0..6)
+ */
+ inputstate = bcs->inputstate;
+ seqlen = ubc->seqlen;
+ inbyte = ubc->inbyte;
+ inbits = ubc->inbits;
+
+ /* bit unstuffing a byte a time
+ * Take your time to understand this; it's straightforward but tedious.
+ * The "bitcounts" lookup table is used to speed up the counting of
+ * leading and trailing '1' bits.
+ */
+ while (count--) {
+ unsigned char c = *src++;
+ unsigned char tabentry = bitcounts[c];
+ unsigned lead1 = tabentry & 0x0f;
+ unsigned trail1 = (tabentry >> 4) & 0x0f;
+
+ seqlen += lead1;
+
+ if (unlikely(inputstate & INS_flag_hunt)) {
+ if (c == PPP_FLAG) {
+ /* flag-in-one */
+ inputstate &= ~(INS_flag_hunt | INS_have_data);
+ inbyte = 0;
+ inbits = 0;
+ } else if (seqlen == 6 && trail1 != 7) {
+ /* flag completed & not followed by abort */
+ inputstate &= ~(INS_flag_hunt | INS_have_data);
+ inbyte = c >> (lead1 + 1);
+ inbits = 7 - lead1;
+ if (trail1 >= 8) {
+ /* interior stuffing: omitting the MSB handles most cases */
+ inbits--;
+ /* correct the incorrectly handled cases individually */
+ switch (c) {
+ case 0xbe:
+ inbyte = 0x3f;
+ break;
+ }
+ }
+ }
+ /* else: continue flag-hunting */
+ } else if (likely(seqlen < 5 && trail1 < 7)) {
+ /* streamlined case: 8 data bits, no stuffing */
+ inbyte |= c << inbits;
+ hdlc_putbyte(inbyte & 0xff, bcs);
+ inputstate |= INS_have_data;
+ inbyte >>= 8;
+ /* inbits unchanged */
+ } else if (likely(seqlen == 6 && inbits == 7 - lead1 &&
+ trail1 + 1 == inbits &&
+ !(inputstate & INS_have_data))) {
+ /* streamlined case: flag idle - state unchanged */
+ } else if (unlikely(seqlen > 6)) {
+ /* abort sequence */
+ ubc->aborts++;
+ hdlc_flush(bcs);
+ inputstate |= INS_flag_hunt;
+ } else if (seqlen == 6) {
+ /* closing flag, including (6 - lead1) '1's and one '0' from inbits */
+ if (inbits > 7 - lead1) {
+ hdlc_frag(bcs, inbits + lead1 - 7);
+ inputstate &= ~INS_have_data;
+ } else {
+ if (inbits < 7 - lead1)
+ ubc->stolen0s ++;
+ if (inputstate & INS_have_data) {
+ hdlc_done(bcs);
+ inputstate &= ~INS_have_data;
+ }
+ }
+
+ if (c == PPP_FLAG) {
+ /* complete flag, LSB overlaps preceding flag */
+ ubc->shared0s ++;
+ inbits = 0;
+ inbyte = 0;
+ } else if (trail1 != 7) {
+ /* remaining bits */
+ inbyte = c >> (lead1 + 1);
+ inbits = 7 - lead1;
+ if (trail1 >= 8) {
+ /* interior stuffing: omitting the MSB handles most cases */
+ inbits--;
+ /* correct the incorrectly handled cases individually */
+ switch (c) {
+ case 0xbe:
+ inbyte = 0x3f;
+ break;
+ }
+ }
+ } else {
+ /* abort sequence follows, skb already empty anyway */
+ ubc->aborts++;
+ inputstate |= INS_flag_hunt;
+ }
+ } else { /* (seqlen < 6) && (seqlen == 5 || trail1 >= 7) */
+
+ if (c == PPP_FLAG) {
+ /* complete flag */
+ if (seqlen == 5)
+ ubc->stolen0s++;
+ if (inbits) {
+ hdlc_frag(bcs, inbits);
+ inbits = 0;
+ inbyte = 0;
+ } else if (inputstate & INS_have_data)
+ hdlc_done(bcs);
+ inputstate &= ~INS_have_data;
+ } else if (trail1 == 7) {
+ /* abort sequence */
+ ubc->aborts++;
+ hdlc_flush(bcs);
+ inputstate |= INS_flag_hunt;
+ } else {
+ /* stuffed data */
+ if (trail1 < 7) { /* => seqlen == 5 */
+ /* stuff bit at position lead1, no interior stuffing */
+ unsigned char mask = (1 << lead1) - 1;
+ c = (c & mask) | ((c & ~mask) >> 1);
+ inbyte |= c << inbits;
+ inbits += 7;
+ } else if (seqlen < 5) { /* trail1 >= 8 */
+ /* interior stuffing: omitting the MSB handles most cases */
+ /* correct the incorrectly handled cases individually */
+ switch (c) {
+ case 0xbe:
+ c = 0x7e;
+ break;
+ }
+ inbyte |= c << inbits;
+ inbits += 7;
+ } else { /* seqlen == 5 && trail1 >= 8 */
+
+ /* stuff bit at lead1 *and* interior stuffing */
+ switch (c) { /* unstuff individually */
+ case 0x7d:
+ c = 0x3f;
+ break;
+ case 0xbe:
+ c = 0x3f;
+ break;
+ case 0x3e:
+ c = 0x1f;
+ break;
+ case 0x7c:
+ c = 0x3e;
+ break;
+ }
+ inbyte |= c << inbits;
+ inbits += 6;
+ }
+ if (inbits >= 8) {
+ inbits -= 8;
+ hdlc_putbyte(inbyte & 0xff, bcs);
+ inputstate |= INS_have_data;
+ inbyte >>= 8;
+ }
+ }
+ }
+ seqlen = trail1 & 7;
+ }
+
+ /* save new state */
+ bcs->inputstate = inputstate;
+ ubc->seqlen = seqlen;
+ ubc->inbyte = inbyte;
+ ubc->inbits = inbits;
+}
+
+/* trans_receive
+ * pass on received USB frame transparently as SKB via gigaset_rcv_skb
+ * invert bytes
+ * tally frames, errors etc. in BC structure counters
+ * parameters:
+ * src received data
+ * count number of received bytes
+ * bcs receiving B channel structure
+ */
+static inline void trans_receive(unsigned char *src, unsigned count,
+ struct bc_state *bcs)
+{
+ struct sk_buff *skb;
+ int dobytes;
+ unsigned char *dst;
+
+ if (unlikely(bcs->ignore)) {
+ bcs->ignore--;
+ hdlc_flush(bcs);
+ return;
+ }
+ if (unlikely((skb = bcs->skb) == NULL)) {
+ bcs->skb = skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN);
+ if (!skb) {
+ err("could not allocate skb");
+ return;
+ }
+ skb_reserve(skb, HW_HDR_LEN);
+ }
+ bcs->hw.bas->goodbytes += skb->len;
+ dobytes = TRANSBUFSIZE - skb->len;
+ while (count > 0) {
+ dst = skb_put(skb, count < dobytes ? count : dobytes);
+ while (count > 0 && dobytes > 0) {
+ *dst++ = gigaset_invtab[*src++];
+ count--;
+ dobytes--;
+ }
+ if (dobytes == 0) {
+ gigaset_rcv_skb(skb, bcs->cs, bcs);
+ bcs->skb = skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN);
+ if (!skb) {
+ err("could not allocate skb");
+ return;
+ }
+ skb_reserve(bcs->skb, HW_HDR_LEN);
+ dobytes = TRANSBUFSIZE;
+ }
+ }
+}
+
+void gigaset_isoc_receive(unsigned char *src, unsigned count, struct bc_state *bcs)
+{
+ switch (bcs->proto2) {
+ case ISDN_PROTO_L2_HDLC:
+ hdlc_unpack(src, count, bcs);
+ break;
+ default: /* assume transparent */
+ trans_receive(src, count, bcs);
+ }
+}
+
+/* == data input =========================================================== */
+
+static void cmd_loop(unsigned char *src, int numbytes, struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ unsigned cbytes = cs->cbytes;
+
+ while (numbytes--) {
+ /* copy next character, check for end of line */
+ switch (cs->respdata[cbytes] = *src++) {
+ case '\r':
+ case '\n':
+ /* end of line */
+ dbg(DEBUG_TRANSCMD, "%s: End of Command (%d Bytes)",
+ __func__, cbytes);
+ cs->cbytes = cbytes;
+ gigaset_handle_modem_response(cs);
+ cbytes = 0;
+ break;
+ default:
+ /* advance in line buffer, checking for overflow */
+ if (cbytes < MAX_RESP_SIZE - 1)
+ cbytes++;
+ else
+ warn("response too large");
+ }
+ }
+
+ /* save state */
+ cs->cbytes = cbytes;
+}
+
+
+/* process a block of data received through the control channel
+ */
+void gigaset_isoc_input(struct inbuf_t *inbuf)
+{
+ struct cardstate *cs = inbuf->cs;
+ unsigned tail, head, numbytes;
+ unsigned char *src;
+
+ head = atomic_read(&inbuf->head);
+ while (head != (tail = atomic_read(&inbuf->tail))) {
+ dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);
+ if (head > tail)
+ tail = RBUFSIZE;
+ src = inbuf->data + head;
+ numbytes = tail - head;
+ dbg(DEBUG_INTR, "processing %u bytes", numbytes);
+
+ if (atomic_read(&cs->mstate) == MS_LOCKED) {
+ gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response",
+ numbytes, src, 0);
+ gigaset_if_receive(inbuf->cs, src, numbytes);
+ } else {
+ gigaset_dbg_buffer(DEBUG_CMD, "received response",
+ numbytes, src, 0);
+ cmd_loop(src, numbytes, inbuf);
+ }
+
+ head += numbytes;
+ if (head == RBUFSIZE)
+ head = 0;
+ dbg(DEBUG_INTR, "setting head to %u", head);
+ atomic_set(&inbuf->head, head);
+ }
+}
+
+
+/* == data output ========================================================== */
+
+/* gigaset_send_skb
+ * called by common.c to queue an skb for sending
+ * and start transmission if necessary
+ * parameters:
+ * B Channel control structure
+ * skb
+ * return value:
+ * number of bytes accepted for sending
+ * (skb->len if ok, 0 if out of buffer space)
+ * or error code (< 0, eg. -EINVAL)
+ */
+int gigaset_isoc_send_skb(struct bc_state *bcs, struct sk_buff *skb)
+{
+ int len;
+
+ IFNULLRETVAL(bcs, -EFAULT);
+ IFNULLRETVAL(skb, -EFAULT);
+ len = skb->len;
+
+ skb_queue_tail(&bcs->squeue, skb);
+ dbg(DEBUG_ISO,
+ "%s: skb queued, qlen=%d", __func__, skb_queue_len(&bcs->squeue));
+
+ /* tasklet submits URB if necessary */
+ tasklet_schedule(&bcs->hw.bas->sent_tasklet);
+
+ return len; /* ok so far */
+}

2006-02-11 15:03:44

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 5/9] isdn4linux: Siemens Gigaset drivers - tty interface

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch adds the tty interface to the gigaset module.
The tty interface provides direct access to the AT command set of the
Gigaset devices.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

drivers/isdn/gigaset/interface.c | 718 +++++++++++++++++++++++++++++++++++++++
include/linux/gigaset_dev.h | 32 +
2 files changed, 750 insertions(+)

--- linux-2.6.16-rc2/include/linux/gigaset_dev.h 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/include/linux/gigaset_dev.h 2005-11-21 23:27:54.000000000 +0100
@@ -0,0 +1,32 @@
+/*
+ * interface to user space for the gigaset driver
+ *
+ * Copyright (c) 2004 by Hansjoerg Lipp <[email protected]>
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * Version: $Id: gigaset_dev.h,v 1.4.4.4 2005/11/21 22:28:09 hjlipp Exp $
+ * =====================================================================
+ */
+
+#ifndef GIGASET_INTERFACE_H
+#define GIGASET_INTERFACE_H
+
+#include <linux/ioctl.h>
+
+#define GIGASET_IOCTL 0x47
+
+#define GIGVER_DRIVER 0
+#define GIGVER_COMPAT 1
+#define GIGVER_FWBASE 2
+
+#define GIGASET_REDIR _IOWR (GIGASET_IOCTL, 0, int)
+#define GIGASET_CONFIG _IOWR (GIGASET_IOCTL, 1, int)
+#define GIGASET_BRKCHARS _IOW (GIGASET_IOCTL, 2, unsigned char[6]) //FIXME [6] okay?
+#define GIGASET_VERSION _IOWR (GIGASET_IOCTL, 3, unsigned[4])
+
+#endif
--- linux-2.6.16-rc2/drivers/isdn/gigaset/interface.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/interface.c 2006-02-11 15:20:26.000000000 +0100
@@ -0,0 +1,718 @@
+/*
+ * interface to user space for the gigaset driver
+ *
+ * Copyright (c) 2004 by Hansjoerg Lipp <[email protected]>
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * Version: $Id: interface.c,v 1.14.4.15 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+#include <linux/gigaset_dev.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+/*** our ioctls ***/
+
+static int if_lock(struct cardstate *cs, int *arg)
+{
+ int cmd = *arg;
+
+ dbg(DEBUG_IF, "%u: if_lock (%d)", cs->minor_index, cmd);
+
+ if (cmd > 1)
+ return -EINVAL;
+
+ if (cmd < 0) {
+ *arg = atomic_read(&cs->mstate) == MS_LOCKED; //FIXME remove?
+ return 0;
+ }
+
+ if (!cmd && atomic_read(&cs->mstate) == MS_LOCKED
+ && atomic_read(&cs->connected)) {
+ cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR|TIOCM_RTS);
+ cs->ops->baud_rate(cs, B115200);
+ cs->ops->set_line_ctrl(cs, CS8);
+ cs->control_state = TIOCM_DTR|TIOCM_RTS;
+ }
+
+ cs->waiting = 1;
+ if (!gigaset_add_event(cs, &cs->at_state, EV_IF_LOCK,
+ NULL, cmd, NULL)) {
+ cs->waiting = 0;
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling IF_LOCK");
+ gigaset_schedule_event(cs);
+
+ wait_event(cs->waitqueue, !cs->waiting);
+
+ if (cs->cmd_result >= 0) {
+ *arg = cs->cmd_result;
+ return 0;
+ }
+
+ return cs->cmd_result;
+}
+
+static int if_version(struct cardstate *cs, unsigned arg[4])
+{
+ static const unsigned version[4] = GIG_VERSION;
+ static const unsigned compat[4] = GIG_COMPAT;
+ unsigned cmd = arg[0];
+
+ dbg(DEBUG_IF, "%u: if_version (%d)", cs->minor_index, cmd);
+
+ switch (cmd) {
+ case GIGVER_DRIVER:
+ memcpy(arg, version, sizeof version);
+ return 0;
+ case GIGVER_COMPAT:
+ memcpy(arg, compat, sizeof compat);
+ return 0;
+ case GIGVER_FWBASE:
+ cs->waiting = 1;
+ if (!gigaset_add_event(cs, &cs->at_state, EV_IF_VER,
+ NULL, 0, arg)) {
+ cs->waiting = 0;
+ return -ENOMEM;
+ }
+
+ dbg(DEBUG_CMD, "scheduling IF_VER");
+ gigaset_schedule_event(cs);
+
+ wait_event(cs->waitqueue, !cs->waiting);
+
+ if (cs->cmd_result >= 0)
+ return 0;
+
+ return cs->cmd_result;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int if_config(struct cardstate *cs, int *arg)
+{
+ dbg(DEBUG_IF, "%u: if_config (%d)", cs->minor_index, *arg);
+
+ if (*arg != 1)
+ return -EINVAL;
+
+ if (atomic_read(&cs->mstate) != MS_LOCKED)
+ return -EBUSY;
+
+ *arg = 0;
+ return gigaset_enterconfigmode(cs);
+}
+
+/*** the terminal driver ***/
+/* stolen from usbserial and some other tty drivers */
+
+static int if_open(struct tty_struct *tty, struct file *filp);
+static void if_close(struct tty_struct *tty, struct file *filp);
+static int if_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static int if_write_room(struct tty_struct *tty);
+static int if_chars_in_buffer(struct tty_struct *tty);
+static void if_throttle(struct tty_struct *tty);
+static void if_unthrottle(struct tty_struct *tty);
+static void if_set_termios(struct tty_struct *tty, struct termios *old);
+static int if_tiocmget(struct tty_struct *tty, struct file *file);
+static int if_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear);
+static int if_write(struct tty_struct *tty,
+ const unsigned char *buf, int count);
+
+static struct tty_operations if_ops = {
+ .open = if_open,
+ .close = if_close,
+ .ioctl = if_ioctl,
+ .write = if_write,
+ .write_room = if_write_room,
+ .chars_in_buffer = if_chars_in_buffer,
+ .set_termios = if_set_termios,
+ .throttle = if_throttle,
+ .unthrottle = if_unthrottle,
+#if 0
+ .break_ctl = serial_break,
+#endif
+ .tiocmget = if_tiocmget,
+ .tiocmset = if_tiocmset,
+};
+
+static int if_open(struct tty_struct *tty, struct file *filp)
+{
+ struct cardstate *cs;
+ unsigned long flags;
+
+ dbg(DEBUG_IF, "%d+%d: %s()", tty->driver->minor_start, tty->index,
+ __FUNCTION__);
+
+ tty->driver_data = NULL;
+
+ cs = gigaset_get_cs_by_tty(tty);
+ if (!cs)
+ return -ENODEV;
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+ tty->driver_data = cs;
+
+ ++cs->open_count;
+
+ if (cs->open_count == 1) {
+ spin_lock_irqsave(&cs->lock, flags);
+ cs->tty = tty;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ tty->low_latency = 1; //FIXME test
+ //FIXME
+ }
+
+ up(&cs->sem);
+ return 0;
+}
+
+static void if_close(struct tty_struct *tty, struct file *filp)
+{
+ struct cardstate *cs;
+ unsigned long flags;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ down(&cs->sem);
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else {
+ if (!--cs->open_count) {
+ spin_lock_irqsave(&cs->lock, flags);
+ cs->tty = NULL;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ //FIXME
+ }
+ }
+
+ up(&cs->sem);
+}
+
+static int if_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct cardstate *cs;
+ int retval = -ENODEV;
+ int int_arg;
+ unsigned char buf[6];
+ unsigned version[4];
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s(0x%x)", cs->minor_index, __FUNCTION__, cmd);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else {
+ retval = 0;
+ switch (cmd) {
+ case GIGASET_REDIR:
+ retval = get_user(int_arg, (int __user *) arg);
+ if (retval >= 0)
+ retval = if_lock(cs, &int_arg);
+ if (retval >= 0)
+ retval = put_user(int_arg, (int __user *) arg);
+ break;
+ case GIGASET_CONFIG:
+ retval = get_user(int_arg, (int __user *) arg);
+ if (retval >= 0)
+ retval = if_config(cs, &int_arg);
+ if (retval >= 0)
+ retval = put_user(int_arg, (int __user *) arg);
+ break;
+ case GIGASET_BRKCHARS:
+ //FIXME test if MS_LOCKED
+ gigaset_dbg_buffer(DEBUG_IF, "GIGASET_BRKCHARS",
+ 6, (const unsigned char *) arg, 1);
+ if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't communicate with unplugged device");
+ retval = -ENODEV;
+ break;
+ }
+ retval = copy_from_user(&buf,
+ (const unsigned char __user *) arg, 6)
+ ? -EFAULT : 0;
+ if (retval >= 0)
+ retval = cs->ops->brkchars(cs, buf);
+ break;
+ case GIGASET_VERSION:
+ retval = copy_from_user(version, (unsigned __user *) arg,
+ sizeof version) ? -EFAULT : 0;
+ if (retval >= 0)
+ retval = if_version(cs, version);
+ if (retval >= 0)
+ retval = copy_to_user((unsigned __user *) arg, version,
+ sizeof version)
+ ? -EFAULT : 0;
+ break;
+ default:
+ dbg(DEBUG_ANY, "%s: arg not supported - 0x%04x",
+ __FUNCTION__, cmd);
+ retval = -ENOIOCTLCMD;
+ }
+ }
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct cardstate *cs;
+ int retval;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ // FIXME read from device?
+ retval = cs->control_state & (TIOCM_RTS|TIOCM_DTR);
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct cardstate *cs;
+ int retval;
+ unsigned mc;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF,
+ "%u: %s(0x%x, 0x%x)", cs->minor_index, __FUNCTION__, set, clear);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't communicate with unplugged device");
+ retval = -ENODEV;
+ } else {
+ mc = (cs->control_state | set) & ~clear & (TIOCM_RTS|TIOCM_DTR);
+ retval = cs->ops->set_modem_ctrl(cs, cs->control_state, mc);
+ cs->control_state = mc;
+ }
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ struct cardstate *cs;
+ int retval = -ENODEV;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ warn("can't write to unlocked device");
+ retval = -EBUSY;
+ } else if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't write to unplugged device");
+ retval = -EBUSY; //FIXME
+ } else {
+ retval = cs->ops->write_cmd(cs, buf, count,
+ &cs->if_wake_tasklet);
+ }
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_write_room(struct tty_struct *tty)
+{
+ struct cardstate *cs;
+ int retval = -ENODEV;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ warn("can't write to unlocked device");
+ retval = -EBUSY; //FIXME
+ } else if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't write to unplugged device");
+ retval = -EBUSY; //FIXME
+ } else
+ retval = cs->ops->write_room(cs);
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static int if_chars_in_buffer(struct tty_struct *tty)
+{
+ struct cardstate *cs;
+ int retval = -ENODEV;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ if (down_interruptible(&cs->sem))
+ return -ERESTARTSYS; // FIXME -EINTR?
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ warn("can't write to unlocked device");
+ retval = -EBUSY;
+ } else if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't write to unplugged device");
+ retval = -EBUSY; //FIXME
+ } else
+ retval = cs->ops->chars_in_buffer(cs);
+
+ up(&cs->sem);
+
+ return retval;
+}
+
+static void if_throttle(struct tty_struct *tty)
+{
+ struct cardstate *cs;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ down(&cs->sem);
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else {
+ //FIXME
+ }
+
+ up(&cs->sem);
+}
+
+static void if_unthrottle(struct tty_struct *tty)
+{
+ struct cardstate *cs;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ down(&cs->sem);
+
+ if (!cs->open_count)
+ warn("%s: device not opened", __FUNCTION__);
+ else {
+ //FIXME
+ }
+
+ up(&cs->sem);
+}
+
+static void if_set_termios(struct tty_struct *tty, struct termios *old)
+{
+ struct cardstate *cs;
+ unsigned int iflag;
+ unsigned int cflag;
+ unsigned int old_cflag;
+ unsigned int control_state, new_state;
+
+ cs = (struct cardstate *) tty->driver_data;
+ if (!cs) {
+ err("cs==NULL in %s", __FUNCTION__);
+ return;
+ }
+
+ dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __FUNCTION__);
+
+ down(&cs->sem);
+
+ if (!cs->open_count) {
+ warn("%s: device not opened", __FUNCTION__);
+ goto out;
+ }
+
+ if (!atomic_read(&cs->connected)) {
+ dbg(DEBUG_ANY, "can't communicate with unplugged device");
+ goto out;
+ }
+
+ // stolen from mct_u232.c
+ iflag = tty->termios->c_iflag;
+ cflag = tty->termios->c_cflag;
+ old_cflag = old ? old->c_cflag : cflag; //FIXME?
+ dbg(DEBUG_IF, "%u: iflag %x cflag %x old %x", cs->minor_index,
+ iflag, cflag, old_cflag);
+
+ /* get a local copy of the current port settings */
+ control_state = cs->control_state;
+
+ /*
+ * Update baud rate.
+ * Do not attempt to cache old rates and skip settings,
+ * disconnects screw such tricks up completely.
+ * Premature optimization is the root of all evil.
+ */
+
+ /* reassert DTR and (maybe) RTS on transition from B0 */
+ if ((old_cflag & CBAUD) == B0) {
+ new_state = control_state | TIOCM_DTR;
+ /* don't set RTS if using hardware flow control */
+ if (!(old_cflag & CRTSCTS))
+ new_state |= TIOCM_RTS;
+ dbg(DEBUG_IF, "%u: from B0 - set DTR%s", cs->minor_index,
+ (new_state & TIOCM_RTS) ? " only" : "/RTS");
+ cs->ops->set_modem_ctrl(cs, control_state, new_state);
+ control_state = new_state;
+ }
+
+ cs->ops->baud_rate(cs, cflag & CBAUD);
+
+ if ((cflag & CBAUD) == B0) {
+ /* Drop RTS and DTR */
+ dbg(DEBUG_IF, "%u: to B0 - drop DTR/RTS", cs->minor_index);
+ new_state = control_state & ~(TIOCM_DTR | TIOCM_RTS);
+ cs->ops->set_modem_ctrl(cs, control_state, new_state);
+ control_state = new_state;
+ }
+
+ /*
+ * Update line control register (LCR)
+ */
+
+ cs->ops->set_line_ctrl(cs, cflag);
+
+#if 0
+ //FIXME this hangs M101 [ts 2005-03-09]
+ //FIXME do we need this?
+ /*
+ * Set flow control: well, I do not really now how to handle DTR/RTS.
+ * Just do what we have seen with SniffUSB on Win98.
+ */
+ /* Drop DTR/RTS if no flow control otherwise assert */
+ dbg(DEBUG_IF, "%u: control_state %x", cs->minor_index, control_state);
+ new_state = control_state;
+ if ((iflag & IXOFF) || (iflag & IXON) || (cflag & CRTSCTS))
+ new_state |= TIOCM_DTR | TIOCM_RTS;
+ else
+ new_state &= ~(TIOCM_DTR | TIOCM_RTS);
+ if (new_state != control_state) {
+ dbg(DEBUG_IF, "%u: new_state %x", cs->minor_index, new_state);
+ gigaset_set_modem_ctrl(cs, control_state, new_state); // FIXME: mct_u232.c sets the old state here. is this a bug?
+ control_state = new_state;
+ }
+#endif
+
+ /* save off the modified port settings */
+ cs->control_state = control_state;
+
+out:
+ up(&cs->sem);
+}
+
+
+/* wakeup tasklet for the write operation */
+static void if_wake(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+ struct tty_struct *tty;
+
+ tty = cs->tty;
+ if (!tty)
+ return;
+
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc.write_wakeup) {
+ dbg(DEBUG_IF, "write wakeup call");
+ tty->ldisc.write_wakeup(tty);
+ }
+
+ wake_up_interruptible(&tty->write_wait);
+}
+
+/*** interface to common ***/
+
+void gigaset_if_init(struct cardstate *cs)
+{
+ struct gigaset_driver *drv;
+
+ drv = cs->driver;
+ if (!drv->have_tty)
+ return;
+
+ tasklet_init(&cs->if_wake_tasklet, &if_wake, (unsigned long) cs);
+ tty_register_device(drv->tty, cs->minor_index, NULL);
+}
+
+void gigaset_if_free(struct cardstate *cs)
+{
+ struct gigaset_driver *drv;
+
+ drv = cs->driver;
+ if (!drv->have_tty)
+ return;
+
+ tasklet_disable(&cs->if_wake_tasklet);
+ tasklet_kill(&cs->if_wake_tasklet);
+ tty_unregister_device(drv->tty, cs->minor_index);
+}
+
+void gigaset_if_receive(struct cardstate *cs,
+ unsigned char *buffer, size_t len)
+{
+ unsigned long flags;
+ struct tty_struct *tty;
+
+ spin_lock_irqsave(&cs->lock, flags);
+ if ((tty = cs->tty) == NULL)
+ dbg(DEBUG_ANY, "receive on closed device");
+ else {
+ tty_buffer_request_room(tty, len);
+ tty_insert_flip_string(tty, buffer, len);
+ tty_flip_buffer_push(tty);
+ }
+ spin_unlock_irqrestore(&cs->lock, flags);
+}
+EXPORT_SYMBOL_GPL(gigaset_if_receive);
+
+/* gigaset_if_initdriver
+ * Initialize tty interface.
+ * parameters:
+ * drv Driver
+ * procname Name of the driver (e.g. for /proc/tty/drivers)
+ * devname Name of the device files (prefix without minor number)
+ * devfsname Devfs name of the device files without %d
+ */
+void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname,
+ const char *devname, const char *devfsname)
+{
+ unsigned minors = drv->minors;
+ int ret;
+ struct tty_driver *tty;
+
+ drv->have_tty = 0;
+
+ if ((drv->tty = alloc_tty_driver(minors)) == NULL)
+ goto enomem;
+ tty = drv->tty;
+
+ tty->magic = TTY_DRIVER_MAGIC,
+ tty->major = GIG_MAJOR,
+ tty->type = TTY_DRIVER_TYPE_SERIAL,
+ tty->subtype = SERIAL_TYPE_NORMAL,
+ tty->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
+
+ tty->driver_name = procname;
+ tty->name = devname;
+ tty->minor_start = drv->minor;
+ tty->num = drv->minors;
+
+ tty->owner = THIS_MODULE;
+ tty->devfs_name = devfsname;
+
+ tty->init_termios = tty_std_termios; //FIXME
+ tty->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; //FIXME
+ tty_set_operations(tty, &if_ops);
+
+ ret = tty_register_driver(tty);
+ if (ret < 0) {
+ warn("failed to register tty driver (error %d)", ret);
+ goto error;
+ }
+ dbg(DEBUG_IF, "tty driver initialized");
+ drv->have_tty = 1;
+ return;
+
+enomem:
+ warn("could not allocate tty structures");
+error:
+ if (drv->tty)
+ put_tty_driver(drv->tty);
+}
+
+void gigaset_if_freedriver(struct gigaset_driver *drv)
+{
+ if (!drv->have_tty)
+ return;
+
+ drv->have_tty = 0;
+ tty_unregister_driver(drv->tty);
+ put_tty_driver(drv->tty);
+}

2006-02-11 15:03:48

by Hansjoerg Lipp

[permalink] [raw]
Subject: [PATCH 3/9] isdn4linux: Siemens Gigaset drivers - event layer

From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>

This patch adds the event layer to the gigaset module. The event layer
serializes events from hardware, userspace, and other kernel subsystems.

Signed-off-by: Hansjoerg Lipp <[email protected]>
Signed-off-by: Tilman Schmidt <[email protected]>
---

drivers/isdn/gigaset/ev-layer.c | 1983 ++++++++++++++++++++++++++++++++++++++++
1 files changed, 1983 insertions(+)

--- linux-2.6.16-rc2/drivers/isdn/gigaset/ev-layer.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/ev-layer.c 2006-02-11 15:20:26.000000000 +0100
@@ -0,0 +1,1983 @@
+/*
+ * Stuff used by all variants of the driver
+ *
+ * Copyright (c) 2001 by Stefan Eilers <[email protected]>,
+ * Hansjoerg Lipp <[email protected]>,
+ * Tilman Schmidt <[email protected]>.
+ *
+ * =====================================================================
+ * 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 of
+ * the License, or (at your option) any later version.
+ * =====================================================================
+ * ToDo: ...
+ * =====================================================================
+ * Version: $Id: ev-layer.c,v 1.4.2.18 2006/02/04 18:28:16 hjlipp Exp $
+ * =====================================================================
+ */
+
+#include "gigaset.h"
+
+/* ========================================================== */
+/* bit masks for pending commands */
+#define PC_INIT 0x004
+#define PC_DLE0 0x008
+#define PC_DLE1 0x010
+#define PC_CID 0x080
+#define PC_NOCID 0x100
+#define PC_HUP 0x002
+#define PC_DIAL 0x001
+#define PC_ACCEPT 0x040
+#define PC_SHUTDOWN 0x020
+#define PC_CIDMODE 0x200
+#define PC_UMMODE 0x400
+
+/* types of modem responses */
+#define RT_NOTHING 0
+#define RT_ZSAU 1
+#define RT_RING 2
+#define RT_NUMBER 3
+#define RT_STRING 4
+#define RT_HEX 5
+#define RT_ZCAU 6
+
+/* Possible ASCII responses */
+#define RSP_OK 0
+//#define RSP_BUSY 1
+//#define RSP_CONNECT 2
+#define RSP_ZGCI 3
+#define RSP_RING 4
+#define RSP_ZAOC 5
+#define RSP_ZCSTR 6
+#define RSP_ZCFGT 7
+#define RSP_ZCFG 8
+#define RSP_ZCCR 9
+#define RSP_EMPTY 10
+#define RSP_ZLOG 11
+#define RSP_ZCAU 12
+#define RSP_ZMWI 13
+#define RSP_ZABINFO 14
+#define RSP_ZSMLSTCHG 15
+#define RSP_VAR 100
+#define RSP_ZSAU (RSP_VAR + VAR_ZSAU)
+#define RSP_ZDLE (RSP_VAR + VAR_ZDLE)
+#define RSP_ZVLS (RSP_VAR + VAR_ZVLS)
+#define RSP_ZCTP (RSP_VAR + VAR_ZCTP)
+#define RSP_STR (RSP_VAR + VAR_NUM)
+#define RSP_NMBR (RSP_STR + STR_NMBR)
+#define RSP_ZCPN (RSP_STR + STR_ZCPN)
+#define RSP_ZCON (RSP_STR + STR_ZCON)
+#define RSP_ZBC (RSP_STR + STR_ZBC)
+#define RSP_ZHLC (RSP_STR + STR_ZHLC)
+#define RSP_ERROR -1 /* ERROR */
+#define RSP_WRONG_CID -2 /* unknown cid in cmd */
+//#define RSP_EMPTY -3
+#define RSP_UNKNOWN -4 /* unknown response */
+#define RSP_FAIL -5 /* internal error */
+#define RSP_INVAL -6 /* invalid response */
+
+#define RSP_NONE -19
+#define RSP_STRING -20
+#define RSP_NULL -21
+//#define RSP_RETRYFAIL -22
+//#define RSP_RETRY -23
+//#define RSP_SKIP -24
+#define RSP_INIT -27
+#define RSP_ANY -26
+#define RSP_LAST -28
+#define RSP_NODEV -9
+
+/* actions for process_response */
+#define ACT_NOTHING 0
+#define ACT_SETDLE1 1
+#define ACT_SETDLE0 2
+#define ACT_FAILINIT 3
+#define ACT_HUPMODEM 4
+#define ACT_CONFIGMODE 5
+#define ACT_INIT 6
+#define ACT_DLE0 7
+#define ACT_DLE1 8
+#define ACT_FAILDLE0 9
+#define ACT_FAILDLE1 10
+#define ACT_RING 11
+#define ACT_CID 12
+#define ACT_FAILCID 13
+#define ACT_SDOWN 14
+#define ACT_FAILSDOWN 15
+#define ACT_DEBUG 16
+#define ACT_WARN 17
+#define ACT_DIALING 18
+#define ACT_ABORTDIAL 19
+#define ACT_DISCONNECT 20
+#define ACT_CONNECT 21
+#define ACT_REMOTEREJECT 22
+#define ACT_CONNTIMEOUT 23
+#define ACT_REMOTEHUP 24
+#define ACT_ABORTHUP 25
+#define ACT_ICALL 26
+#define ACT_ACCEPTED 27
+#define ACT_ABORTACCEPT 28
+#define ACT_TIMEOUT 29
+#define ACT_GETSTRING 30
+#define ACT_SETVER 31
+#define ACT_FAILVER 32
+#define ACT_GOTVER 33
+#define ACT_TEST 34
+#define ACT_ERROR 35
+#define ACT_ABORTCID 36
+#define ACT_ZCAU 37
+#define ACT_NOTIFY_BC_DOWN 38
+#define ACT_NOTIFY_BC_UP 39
+#define ACT_DIAL 40
+#define ACT_ACCEPT 41
+#define ACT_PROTO_L2 42
+#define ACT_HUP 43
+#define ACT_IF_LOCK 44
+#define ACT_START 45
+#define ACT_STOP 46
+#define ACT_FAKEDLE0 47
+#define ACT_FAKEHUP 48
+#define ACT_FAKESDOWN 49
+#define ACT_SHUTDOWN 50
+#define ACT_PROC_CIDMODE 51
+#define ACT_UMODESET 52
+#define ACT_FAILUMODE 53
+#define ACT_CMODESET 54
+#define ACT_FAILCMODE 55
+#define ACT_IF_VER 56
+#define ACT_CMD 100
+
+/* at command sequences */
+#define SEQ_NONE 0
+#define SEQ_INIT 100
+#define SEQ_DLE0 200
+#define SEQ_DLE1 250
+#define SEQ_CID 300
+#define SEQ_NOCID 350
+#define SEQ_HUP 400
+#define SEQ_DIAL 600
+#define SEQ_ACCEPT 720
+#define SEQ_SHUTDOWN 500
+#define SEQ_CIDMODE 10
+#define SEQ_UMMODE 11
+
+
+// 100: init, 200: dle0, 250:dle1, 300: get cid (dial), 350: "hup" (no cid), 400: hup, 500: reset, 600: dial, 700: ring
+struct reply_t gigaset_tab_nocid_m10x[]= /* with dle mode */
+{
+ /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, action, command */
+
+ /* initialize device, set cid mode if possible */
+ //{RSP_INIT, -1, -1,100, 900, 0, {ACT_TEST}},
+ //{RSP_ERROR, 900,900, -1, 0, 0, {ACT_FAILINIT}},
+ //{RSP_OK, 900,900, -1, 100, INIT_TIMEOUT,
+ // {ACT_TIMEOUT}},
+
+ {RSP_INIT, -1, -1,SEQ_INIT, 100, INIT_TIMEOUT,
+ {ACT_TIMEOUT}}, /* wait until device is ready */
+
+ {EV_TIMEOUT, 100,100, -1, 101, 3, {0}, "Z\r"}, /* device in transparent mode? try to initialize it. */
+ {RSP_OK, 101,103, -1, 120, 5, {ACT_GETSTRING}, "+GMR\r"}, /* get version */
+
+ {EV_TIMEOUT, 101,101, -1, 102, 5, {0}, "Z\r"}, /* timeout => try once again. */
+ {RSP_ERROR, 101,101, -1, 102, 5, {0}, "Z\r"}, /* error => try once again. */
+
+ {EV_TIMEOUT, 102,102, -1, 108, 5, {ACT_SETDLE1}, "^SDLE=0\r"}, /* timeout => try again in DLE mode. */
+ {RSP_OK, 108,108, -1, 104,-1},
+ {RSP_ZDLE, 104,104, 0, 103, 5, {0}, "Z\r"},
+ {EV_TIMEOUT, 104,104, -1, 0, 0, {ACT_FAILINIT}},
+ {RSP_ERROR, 108,108, -1, 0, 0, {ACT_FAILINIT}},
+
+ {EV_TIMEOUT, 108,108, -1, 105, 2, {ACT_SETDLE0,
+ ACT_HUPMODEM,
+ ACT_TIMEOUT}}, /* still timeout => connection in unimodem mode? */
+ {EV_TIMEOUT, 105,105, -1, 103, 5, {0}, "Z\r"},
+
+ {RSP_ERROR, 102,102, -1, 107, 5, {0}, "^GETPRE\r"}, /* ERROR on ATZ => maybe in config mode? */
+ {RSP_OK, 107,107, -1, 0, 0, {ACT_CONFIGMODE}},
+ {RSP_ERROR, 107,107, -1, 0, 0, {ACT_FAILINIT}},
+ {EV_TIMEOUT, 107,107, -1, 0, 0, {ACT_FAILINIT}},
+
+ {RSP_ERROR, 103,103, -1, 0, 0, {ACT_FAILINIT}},
+ {EV_TIMEOUT, 103,103, -1, 0, 0, {ACT_FAILINIT}},
+
+ {RSP_STRING, 120,120, -1, 121,-1, {ACT_SETVER}},
+
+ {EV_TIMEOUT, 120,121, -1, 0, 0, {ACT_FAILVER, ACT_INIT}},
+ {RSP_ERROR, 120,121, -1, 0, 0, {ACT_FAILVER, ACT_INIT}},
+ {RSP_OK, 121,121, -1, 0, 0, {ACT_GOTVER, ACT_INIT}},
+#if 0
+ {EV_TIMEOUT, 120,121, -1, 130, 5, {ACT_FAILVER}, "^SGCI=1\r"},
+ {RSP_ERROR, 120,121, -1, 130, 5, {ACT_FAILVER}, "^SGCI=1\r"},
+ {RSP_OK, 121,121, -1, 130, 5, {ACT_GOTVER}, "^SGCI=1\r"},
+
+ {RSP_OK, 130,130, -1, 0, 0, {ACT_INIT}},
+ {RSP_ERROR, 130,130, -1, 0, 0, {ACT_FAILINIT}},
+ {EV_TIMEOUT, 130,130, -1, 0, 0, {ACT_FAILINIT}},
+#endif
+
+ /* leave dle mode */
+ {RSP_INIT, 0, 0,SEQ_DLE0, 201, 5, {0}, "^SDLE=0\r"},
+ {RSP_OK, 201,201, -1, 202,-1},
+ //{RSP_ZDLE, 202,202, 0, 202, 0, {ACT_ERROR}},//DELETE
+ {RSP_ZDLE, 202,202, 0, 0, 0, {ACT_DLE0}},
+ {RSP_NODEV, 200,249, -1, 0, 0, {ACT_FAKEDLE0}},
+ {RSP_ERROR, 200,249, -1, 0, 0, {ACT_FAILDLE0}},
+ {EV_TIMEOUT, 200,249, -1, 0, 0, {ACT_FAILDLE0}},
+
+ /* enter dle mode */
+ {RSP_INIT, 0, 0,SEQ_DLE1, 251, 5, {0}, "^SDLE=1\r"},
+ {RSP_OK, 251,251, -1, 252,-1},
+ {RSP_ZDLE, 252,252, 1, 0, 0, {ACT_DLE1}},
+ {RSP_ERROR, 250,299, -1, 0, 0, {ACT_FAILDLE1}},
+ {EV_TIMEOUT, 250,299, -1, 0, 0, {ACT_FAILDLE1}},
+
+ /* incoming call */
+ {RSP_RING, -1, -1, -1, -1,-1, {ACT_RING}},
+
+ /* get cid */
+ //{RSP_INIT, 0, 0,300, 901, 0, {ACT_TEST}},
+ //{RSP_ERROR, 901,901, -1, 0, 0, {ACT_FAILCID}},
+ //{RSP_OK, 901,901, -1, 301, 5, {0}, "^SGCI?\r"},
+
+ {RSP_INIT, 0, 0,SEQ_CID, 301, 5, {0}, "^SGCI?\r"},
+ {RSP_OK, 301,301, -1, 302,-1},
+ {RSP_ZGCI, 302,302, -1, 0, 0, {ACT_CID}},
+ {RSP_ERROR, 301,349, -1, 0, 0, {ACT_FAILCID}},
+ {EV_TIMEOUT, 301,349, -1, 0, 0, {ACT_FAILCID}},
+
+ /* enter cid mode */
+ {RSP_INIT, 0, 0,SEQ_CIDMODE, 150, 5, {0}, "^SGCI=1\r"},
+ {RSP_OK, 150,150, -1, 0, 0, {ACT_CMODESET}},
+ {RSP_ERROR, 150,150, -1, 0, 0, {ACT_FAILCMODE}},
+ {EV_TIMEOUT, 150,150, -1, 0, 0, {ACT_FAILCMODE}},
+
+ /* leave cid mode */
+ //{RSP_INIT, 0, 0,SEQ_UMMODE, 160, 5, {0}, "^SGCI=0\r"},
+ {RSP_INIT, 0, 0,SEQ_UMMODE, 160, 5, {0}, "Z\r"},
+ {RSP_OK, 160,160, -1, 0, 0, {ACT_UMODESET}},
+ {RSP_ERROR, 160,160, -1, 0, 0, {ACT_FAILUMODE}},
+ {EV_TIMEOUT, 160,160, -1, 0, 0, {ACT_FAILUMODE}},
+
+ /* abort getting cid */
+ {RSP_INIT, 0, 0,SEQ_NOCID, 0, 0, {ACT_ABORTCID}},
+
+ /* reset */
+#if 0
+ {RSP_INIT, 0, 0,SEQ_SHUTDOWN, 503, 5, {0}, "^SGCI=0\r"},
+ {RSP_OK, 503,503, -1, 504, 5, {0}, "Z\r"},
+#endif
+ {RSP_INIT, 0, 0,SEQ_SHUTDOWN, 504, 5, {0}, "Z\r"},
+ {RSP_OK, 504,504, -1, 0, 0, {ACT_SDOWN}},
+ {RSP_ERROR, 501,599, -1, 0, 0, {ACT_FAILSDOWN}},
+ {EV_TIMEOUT, 501,599, -1, 0, 0, {ACT_FAILSDOWN}},
+ {RSP_NODEV, 501,599, -1, 0, 0, {ACT_FAKESDOWN}},
+
+ {EV_PROC_CIDMODE,-1, -1, -1, -1,-1, {ACT_PROC_CIDMODE}}, //FIXME
+ {EV_IF_LOCK, -1, -1, -1, -1,-1, {ACT_IF_LOCK}}, //FIXME
+ {EV_IF_VER, -1, -1, -1, -1,-1, {ACT_IF_VER}}, //FIXME
+ {EV_START, -1, -1, -1, -1,-1, {ACT_START}}, //FIXME
+ {EV_STOP, -1, -1, -1, -1,-1, {ACT_STOP}}, //FIXME
+ {EV_SHUTDOWN, -1, -1, -1, -1,-1, {ACT_SHUTDOWN}}, //FIXME
+
+ /* misc. */
+ {RSP_EMPTY, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZCFGT, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZCFG, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZLOG, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZMWI, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZABINFO, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZSMLSTCHG,-1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+
+ {RSP_ZCAU, -1, -1, -1, -1,-1, {ACT_ZCAU}},
+ {RSP_NONE, -1, -1, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ANY, -1, -1, -1, -1,-1, {ACT_WARN}},
+ {RSP_LAST}
+};
+
+// 600: start dialing, 650: dial in progress, 800: connection is up, 700: ring, 400: hup, 750: accepted icall
+struct reply_t gigaset_tab_cid_m10x[] = /* for M10x */
+{
+ /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, action, command */
+
+ /* dial */
+ {EV_DIAL, -1, -1, -1, -1,-1, {ACT_DIAL}}, //FIXME
+ {RSP_INIT, 0, 0,SEQ_DIAL, 601, 5, {ACT_CMD+AT_BC}},
+ {RSP_OK, 601,601, -1, 602, 5, {ACT_CMD+AT_HLC}},
+ {RSP_NULL, 602,602, -1, 603, 5, {ACT_CMD+AT_PROTO}},
+ {RSP_OK, 602,602, -1, 603, 5, {ACT_CMD+AT_PROTO}},
+ {RSP_OK, 603,603, -1, 604, 5, {ACT_CMD+AT_TYPE}},
+ {RSP_OK, 604,604, -1, 605, 5, {ACT_CMD+AT_MSN}},
+ {RSP_OK, 605,605, -1, 606, 5, {ACT_CMD+AT_ISO}},
+ {RSP_NULL, 605,605, -1, 606, 5, {ACT_CMD+AT_ISO}},
+ {RSP_OK, 606,606, -1, 607, 5, {0}, "+VLS=17\r"}, /* set "Endgeraetemodus" */
+ {RSP_OK, 607,607, -1, 608,-1},
+ //{RSP_ZSAU, 608,608,ZSAU_PROCEEDING, 608, 0, {ACT_ERROR}},//DELETE
+ {RSP_ZSAU, 608,608,ZSAU_PROCEEDING, 609, 5, {ACT_CMD+AT_DIAL}},
+ {RSP_OK, 609,609, -1, 650, 0, {ACT_DIALING}},
+
+ {RSP_ZVLS, 608,608, 17, -1,-1, {ACT_DEBUG}},
+ {RSP_ZCTP, 609,609, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ZCPN, 609,609, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ERROR, 601,609, -1, 0, 0, {ACT_ABORTDIAL}},
+ {EV_TIMEOUT, 601,609, -1, 0, 0, {ACT_ABORTDIAL}},
+
+ /* dialing */
+ {RSP_ZCTP, 650,650, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ZCPN, 650,650, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ZSAU, 650,650,ZSAU_CALL_DELIVERED, -1,-1, {ACT_DEBUG}}, /* some devices don't send this */
+
+ /* connection established */
+ {RSP_ZSAU, 650,650,ZSAU_ACTIVE, 800,-1, {ACT_CONNECT}}, //FIXME -> DLE1
+ {RSP_ZSAU, 750,750,ZSAU_ACTIVE, 800,-1, {ACT_CONNECT}}, //FIXME -> DLE1
+
+ {EV_BC_OPEN, 800,800, -1, 800,-1, {ACT_NOTIFY_BC_UP}}, //FIXME new constate + timeout
+
+ /* remote hangup */
+ {RSP_ZSAU, 650,650,ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEREJECT}},
+ {RSP_ZSAU, 750,750,ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEHUP}},
+ {RSP_ZSAU, 800,800,ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEHUP}},
+
+ /* hangup */
+ {EV_HUP, -1, -1, -1, -1,-1, {ACT_HUP}}, //FIXME
+ {RSP_INIT, -1, -1,SEQ_HUP, 401, 5, {0}, "+VLS=0\r"}, /* hang up */ //-1,-1?
+ {RSP_OK, 401,401, -1, 402, 5},
+ {RSP_ZVLS, 402,402, 0, 403, 5},
+ {RSP_ZSAU, 403,403,ZSAU_DISCONNECT_REQ, -1,-1, {ACT_DEBUG}}, /* if not remote hup */
+ //{RSP_ZSAU, 403,403,ZSAU_NULL, 401, 0, {ACT_ERROR}}, //DELETE//FIXME -> DLE0 // should we do this _before_ hanging up for base driver?
+ {RSP_ZSAU, 403,403,ZSAU_NULL, 0, 0, {ACT_DISCONNECT}}, //FIXME -> DLE0 // should we do this _before_ hanging up for base driver?
+ {RSP_NODEV, 401,403, -1, 0, 0, {ACT_FAKEHUP}}, //FIXME -> DLE0 // should we do this _before_ hanging up for base driver?
+ {RSP_ERROR, 401,401, -1, 0, 0, {ACT_ABORTHUP}},
+ {EV_TIMEOUT, 401,403, -1, 0, 0, {ACT_ABORTHUP}},
+
+ {EV_BC_CLOSED, 0, 0, -1, 0,-1, {ACT_NOTIFY_BC_DOWN}}, //FIXME new constate + timeout
+
+ /* ring */
+ {RSP_ZBC, 700,700, -1, -1,-1, {0}},
+ {RSP_ZHLC, 700,700, -1, -1,-1, {0}},
+ {RSP_NMBR, 700,700, -1, -1,-1, {0}},
+ {RSP_ZCPN, 700,700, -1, -1,-1, {0}},
+ {RSP_ZCTP, 700,700, -1, -1,-1, {0}},
+ {EV_TIMEOUT, 700,700, -1, 720,720, {ACT_ICALL}},
+ {EV_BC_CLOSED,720,720, -1, 0,-1, {ACT_NOTIFY_BC_DOWN}},
+
+ /*accept icall*/
+ {EV_ACCEPT, -1, -1, -1, -1,-1, {ACT_ACCEPT}}, //FIXME
+ {RSP_INIT, 720,720,SEQ_ACCEPT, 721, 5, {ACT_CMD+AT_PROTO}},
+ {RSP_OK, 721,721, -1, 722, 5, {ACT_CMD+AT_ISO}},
+ {RSP_OK, 722,722, -1, 723, 5, {0}, "+VLS=17\r"}, /* set "Endgeraetemodus" */
+ {RSP_OK, 723,723, -1, 724, 5, {0}},
+ {RSP_ZVLS, 724,724, 17, 750,50, {ACT_ACCEPTED}},
+ {RSP_ERROR, 721,729, -1, 0, 0, {ACT_ABORTACCEPT}},
+ {EV_TIMEOUT, 721,729, -1, 0, 0, {ACT_ABORTACCEPT}},
+ {RSP_ZSAU, 700,729,ZSAU_NULL, 0, 0, {ACT_ABORTACCEPT}},
+ {RSP_ZSAU, 700,729,ZSAU_ACTIVE, 0, 0, {ACT_ABORTACCEPT}},
+ {RSP_ZSAU, 700,729,ZSAU_DISCONNECT_IND, 0, 0, {ACT_ABORTACCEPT}},
+
+ {EV_TIMEOUT, 750,750, -1, 0, 0, {ACT_CONNTIMEOUT}},
+
+ /* misc. */
+ {EV_PROTO_L2, -1, -1, -1, -1,-1, {ACT_PROTO_L2}}, //FIXME
+
+ {RSP_ZCON, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZCCR, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZAOC, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+ {RSP_ZCSTR, -1, -1, -1, -1,-1, {ACT_DEBUG}}, //FIXME
+
+ {RSP_ZCAU, -1, -1, -1, -1,-1, {ACT_ZCAU}},
+ {RSP_NONE, -1, -1, -1, -1,-1, {ACT_DEBUG}},
+ {RSP_ANY, -1, -1, -1, -1,-1, {ACT_WARN}},
+ {RSP_LAST}
+};
+
+
+#if 0
+static struct reply_t tab_nocid[]= /* no dle mode */ //FIXME aenderungen uebernehmen
+{
+ /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, action, command */
+
+ {RSP_ANY, -1, -1, -1, -1,-1, ACT_WARN, NULL},
+ {RSP_LAST,0,0,0,0,0,0}
+};
+
+static struct reply_t tab_cid[] = /* no dle mode */ //FIXME aenderungen uebernehmen
+{
+ /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, action, command */
+
+ {RSP_ANY, -1, -1, -1, -1,-1, ACT_WARN, NULL},
+ {RSP_LAST,0,0,0,0,0,0}
+};
+#endif
+
+static struct resp_type_t resp_type[]=
+{
+ /*{"", RSP_EMPTY, RT_NOTHING},*/
+ {"OK", RSP_OK, RT_NOTHING},
+ {"ERROR", RSP_ERROR, RT_NOTHING},
+ {"ZSAU", RSP_ZSAU, RT_ZSAU},
+ {"ZCAU", RSP_ZCAU, RT_ZCAU},
+ {"RING", RSP_RING, RT_RING},
+ {"ZGCI", RSP_ZGCI, RT_NUMBER},
+ {"ZVLS", RSP_ZVLS, RT_NUMBER},
+ {"ZCTP", RSP_ZCTP, RT_NUMBER},
+ {"ZDLE", RSP_ZDLE, RT_NUMBER},
+ {"ZCFGT", RSP_ZCFGT, RT_NUMBER},
+ {"ZCCR", RSP_ZCCR, RT_NUMBER},
+ {"ZMWI", RSP_ZMWI, RT_NUMBER},
+ {"ZHLC", RSP_ZHLC, RT_STRING},
+ {"ZBC", RSP_ZBC, RT_STRING},
+ {"NMBR", RSP_NMBR, RT_STRING},
+ {"ZCPN", RSP_ZCPN, RT_STRING},
+ {"ZCON", RSP_ZCON, RT_STRING},
+ {"ZAOC", RSP_ZAOC, RT_STRING},
+ {"ZCSTR", RSP_ZCSTR, RT_STRING},
+ {"ZCFG", RSP_ZCFG, RT_HEX},
+ {"ZLOG", RSP_ZLOG, RT_NOTHING},
+ {"ZABINFO", RSP_ZABINFO, RT_NOTHING},
+ {"ZSMLSTCHG", RSP_ZSMLSTCHG, RT_NOTHING},
+ {NULL,0,0}
+};
+
+/*
+ * Get integer from char-pointer
+ */
+static int isdn_getnum(char *p)
+{
+ int v = -1;
+
+ IFNULLRETVAL(p, -1);
+
+ dbg(DEBUG_TRANSCMD, "string: %s", p);
+
+ while (*p >= '0' && *p <= '9')
+ v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p++) - '0');
+ if (*p)
+ v = -1; /* invalid Character */
+ return v;
+}
+
+/*
+ * Get integer from char-pointer
+ */
+static int isdn_gethex(char *p)
+{
+ int v = 0;
+ int c;
+
+ IFNULLRETVAL(p, -1);
+
+ dbg(DEBUG_TRANSCMD, "string: %s", p);
+
+ if (!*p)
+ return -1;
+
+ do {
+ if (v > (INT_MAX - 15) / 16)
+ return -1;
+ c = *p;
+ if (c >= '0' && c <= '9')
+ c -= '0';
+ else if (c >= 'a' && c <= 'f')
+ c -= 'a' - 10;
+ else if (c >= 'A' && c <= 'F')
+ c -= 'A' - 10;
+ else
+ return -1;
+ v = v * 16 + c;
+ } while (*++p);
+
+ return v;
+}
+
+static inline void new_index(atomic_t *index, int max)
+{
+ if (atomic_read(index) == max) //FIXME race?
+ atomic_set(index, 0);
+ else
+ atomic_inc(index);
+}
+
+/* retrieve CID from parsed response
+ * returns 0 if no CID, -1 if invalid CID, or CID value 1..65535
+ */
+static int cid_of_response(char *s)
+{
+ int cid;
+
+ if (s[-1] != ';')
+ return 0; /* no CID separator */
+ cid = isdn_getnum(s);
+ if (cid < 0)
+ return 0; /* CID not numeric */
+ if (cid < 1 || cid > 65535)
+ return -1; /* CID out of range */
+ return cid;
+ //FIXME is ;<digit>+ at end of non-CID response really impossible?
+}
+
+/* This function will be called via task queue from the callback handler.
+ * We received a modem response and have to handle it..
+ */
+void gigaset_handle_modem_response(struct cardstate *cs)
+{
+ unsigned char *argv[MAX_REC_PARAMS + 1];
+ int params;
+ int i, j;
+ struct resp_type_t *rt;
+ int curarg;
+ unsigned long flags;
+ unsigned next, tail, head;
+ struct event_t *event;
+ int resp_code;
+ int param_type;
+ int abort;
+ size_t len;
+ int cid;
+ int rawstring;
+
+ IFNULLRET(cs);
+
+ len = cs->cbytes;
+ if (!len) {
+ /* ignore additional LFs/CRs (M10x config mode or cx100) */
+ dbg(DEBUG_MCMD, "skipped EOL [%02X]", cs->respdata[len]);
+ return;
+ }
+ cs->respdata[len] = 0;
+ dbg(DEBUG_TRANSCMD, "raw string: '%s'", cs->respdata);
+ argv[0] = cs->respdata;
+ params = 1;
+ if (cs->at_state.getstring) {
+ /* getstring only allowed without cid at the moment */
+ cs->at_state.getstring = 0;
+ rawstring = 1;
+ cid = 0;
+ } else {
+ /* parse line */
+ for (i = 0; i < len; i++)
+ switch (cs->respdata[i]) {
+ case ';':
+ case ',':
+ case '=':
+ if (params > MAX_REC_PARAMS) {
+ warn("too many parameters in response");
+ /* need last parameter (might be CID) */
+ params--;
+ }
+ argv[params++] = cs->respdata + i + 1;
+ }
+
+ rawstring = 0;
+ cid = params > 1 ? cid_of_response(argv[params-1]) : 0;
+ if (cid < 0) {
+ gigaset_add_event(cs, &cs->at_state, RSP_INVAL,
+ NULL, 0, NULL);
+ return;
+ }
+
+ for (j = 1; j < params; ++j)
+ argv[j][-1] = 0;
+
+ dbg(DEBUG_TRANSCMD, "CMD received: %s", argv[0]);
+ if (cid) {
+ --params;
+ dbg(DEBUG_TRANSCMD, "CID: %s", argv[params]);
+ }
+ dbg(DEBUG_TRANSCMD, "available params: %d", params - 1);
+ for (j = 1; j < params; j++)
+ dbg(DEBUG_TRANSCMD, "param %d: %s", j, argv[j]);
+ }
+
+ spin_lock_irqsave(&cs->ev_lock, flags);
+ head = atomic_read(&cs->ev_head);
+ tail = atomic_read(&cs->ev_tail);
+
+ abort = 1;
+ curarg = 0;
+ while (curarg < params) {
+ next = (tail + 1) % MAX_EVENTS;
+ if (unlikely(next == head)) {
+ err("event queue full");
+ break;
+ }
+
+ event = cs->events + tail;
+ event->at_state = NULL;
+ event->cid = cid;
+ event->ptr = NULL;
+ event->arg = NULL;
+ tail = next;
+
+ if (rawstring) {
+ resp_code = RSP_STRING;
+ param_type = RT_STRING;
+ } else {
+ for (rt = resp_type; rt->response; ++rt)
+ if (!strcmp(argv[curarg], rt->response))
+ break;
+
+ if (!rt->response) {
+ event->type = RSP_UNKNOWN;
+ warn("unknown modem response: %s",
+ argv[curarg]);
+ break;
+ }
+
+ resp_code = rt->resp_code;
+ param_type = rt->type;
+ ++curarg;
+ }
+
+ event->type = resp_code;
+
+ switch (param_type) {
+ case RT_NOTHING:
+ break;
+ case RT_RING:
+ if (!cid) {
+ err("received RING without CID!");
+ event->type = RSP_INVAL;
+ abort = 1;
+ } else {
+ event->cid = 0;
+ event->parameter = cid;
+ abort = 0;
+ }
+ break;
+ case RT_ZSAU:
+ if (curarg >= params) {
+ event->parameter = ZSAU_NONE;
+ break;
+ }
+ if (!strcmp(argv[curarg], "OUTGOING_CALL_PROCEEDING"))
+ event->parameter = ZSAU_OUTGOING_CALL_PROCEEDING;
+ else if (!strcmp(argv[curarg], "CALL_DELIVERED"))
+ event->parameter = ZSAU_CALL_DELIVERED;
+ else if (!strcmp(argv[curarg], "ACTIVE"))
+ event->parameter = ZSAU_ACTIVE;
+ else if (!strcmp(argv[curarg], "DISCONNECT_IND"))
+ event->parameter = ZSAU_DISCONNECT_IND;
+ else if (!strcmp(argv[curarg], "NULL"))
+ event->parameter = ZSAU_NULL;
+ else if (!strcmp(argv[curarg], "DISCONNECT_REQ"))
+ event->parameter = ZSAU_DISCONNECT_REQ;
+ else {
+ event->parameter = ZSAU_UNKNOWN;
+ warn("%s: unknown parameter %s after ZSAU",
+ __func__, argv[curarg]);
+ }
+ ++curarg;
+ break;
+ case RT_STRING:
+ if (curarg < params) {
+ len = strlen(argv[curarg]) + 1;
+ event->ptr = kmalloc(len, GFP_ATOMIC);
+ if (event->ptr)
+ memcpy(event->ptr, argv[curarg], len);
+ else
+ err("no memory for string!");
+ ++curarg;
+ }
+#ifdef CONFIG_GIGASET_DEBUG
+ if (!event->ptr)
+ dbg(DEBUG_CMD, "string==NULL");
+ else
+ dbg(DEBUG_CMD,
+ "string==%s", (char *) event->ptr);
+#endif
+ break;
+ case RT_ZCAU:
+ event->parameter = -1;
+ if (curarg + 1 < params) {
+ i = isdn_gethex(argv[curarg]);
+ j = isdn_gethex(argv[curarg + 1]);
+ if (i >= 0 && i < 256 && j >= 0 && j < 256)
+ event->parameter = (unsigned) i << 8
+ | j;
+ curarg += 2;
+ } else
+ curarg = params - 1;
+ break;
+ case RT_NUMBER:
+ case RT_HEX:
+ if (curarg < params) {
+ if (param_type == RT_HEX)
+ event->parameter =
+ isdn_gethex(argv[curarg]);
+ else
+ event->parameter =
+ isdn_getnum(argv[curarg]);
+ ++curarg;
+ } else
+ event->parameter = -1;
+#ifdef CONFIG_GIGASET_DEBUG
+ dbg(DEBUG_CMD, "parameter==%d", event->parameter);
+#endif
+ break;
+ }
+
+ if (resp_code == RSP_ZDLE)
+ cs->dle = event->parameter;
+
+ if (abort)
+ break;
+ }
+
+ atomic_set(&cs->ev_tail, tail);
+ spin_unlock_irqrestore(&cs->ev_lock, flags);
+
+ if (curarg != params)
+ dbg(DEBUG_ANY, "invalid number of processed parameters: %d/%d",
+ curarg, params);
+}
+EXPORT_SYMBOL_GPL(gigaset_handle_modem_response);
+
+/* disconnect
+ * process closing of connection associated with given AT state structure
+ */
+static void disconnect(struct at_state_t **at_state_p)
+{
+ unsigned long flags;
+ struct bc_state *bcs;
+ struct cardstate *cs;
+
+ IFNULLRET(at_state_p);
+ IFNULLRET(*at_state_p);
+ bcs = (*at_state_p)->bcs;
+ cs = (*at_state_p)->cs;
+ IFNULLRET(cs);
+
+ new_index(&(*at_state_p)->seq_index, MAX_SEQ_INDEX);
+
+ /* revert to selected idle mode */
+ if (!atomic_read(&cs->cidmode)) {
+ cs->at_state.pending_commands |= PC_UMMODE;
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ dbg(DEBUG_CMD, "Scheduling PC_UMMODE");
+ }
+
+ if (bcs) {
+ /* B channel assigned: invoke hardware specific handler */
+ cs->ops->close_bchannel(bcs);
+ } else {
+ /* no B channel assigned: just deallocate */
+ spin_lock_irqsave(&cs->lock, flags);
+ list_del(&(*at_state_p)->list);
+ kfree(*at_state_p);
+ *at_state_p = NULL;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ }
+}
+
+/* get_free_channel
+ * get a free AT state structure: either one of those associated with the
+ * B channels of the Gigaset device, or if none of those is available,
+ * a newly allocated one with bcs=NULL
+ * The structure should be freed by calling disconnect() after use.
+ */
+static inline struct at_state_t *get_free_channel(struct cardstate *cs,
+ int cid)
+/* cids: >0: siemens-cid
+ 0: without cid
+ -1: no cid assigned yet
+*/
+{
+ unsigned long flags;
+ int i;
+ struct at_state_t *ret;
+
+ for (i = 0; i < cs->channels; ++i)
+ if (gigaset_get_channel(cs->bcs + i)) {
+ ret = &cs->bcs[i].at_state;
+ ret->cid = cid;
+ return ret;
+ }
+
+ spin_lock_irqsave(&cs->lock, flags);
+ ret = kmalloc(sizeof(struct at_state_t), GFP_ATOMIC);
+ if (ret) {
+ gigaset_at_init(ret, NULL, cs, cid);
+ list_add(&ret->list, &cs->temp_at_states);
+ }
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return ret;
+}
+
+static void init_failed(struct cardstate *cs, int mode)
+{
+ int i;
+ struct at_state_t *at_state;
+
+ cs->at_state.pending_commands &= ~PC_INIT;
+ atomic_set(&cs->mode, mode);
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+ gigaset_free_channels(cs);
+ for (i = 0; i < cs->channels; ++i) {
+ at_state = &cs->bcs[i].at_state;
+ if (at_state->pending_commands & PC_CID) {
+ at_state->pending_commands &= ~PC_CID;
+ at_state->pending_commands |= PC_NOCID;
+ atomic_set(&cs->commands_pending, 1);
+ }
+ }
+}
+
+static void schedule_init(struct cardstate *cs, int state)
+{
+ if (cs->at_state.pending_commands & PC_INIT) {
+ dbg(DEBUG_CMD, "not scheduling PC_INIT again");
+ return;
+ }
+ atomic_set(&cs->mstate, state);
+ atomic_set(&cs->mode, M_UNKNOWN);
+ gigaset_block_channels(cs);
+ cs->at_state.pending_commands |= PC_INIT;
+ atomic_set(&cs->commands_pending, 1);
+ dbg(DEBUG_CMD, "Scheduling PC_INIT");
+}
+
+/* Add "AT" to a command, add the cid, dle encode it, send the result to the hardware. */
+static void send_command(struct cardstate *cs, const char *cmd, int cid,
+ int dle, gfp_t kmallocflags)
+{
+ size_t cmdlen, buflen;
+ char *cmdpos, *cmdbuf, *cmdtail;
+
+ cmdlen = strlen(cmd);
+ buflen = 11 + cmdlen;
+
+ if (likely(buflen > cmdlen)) {
+ cmdbuf = kmalloc(buflen, kmallocflags);
+ if (likely(cmdbuf != NULL)) {
+ cmdpos = cmdbuf + 9;
+ cmdtail = cmdpos + cmdlen;
+ memcpy(cmdpos, cmd, cmdlen);
+
+ if (cid > 0 && cid <= 65535) {
+ do {
+ *--cmdpos = '0' + cid % 10;
+ cid /= 10;
+ ++cmdlen;
+ } while (cid);
+ }
+
+ cmdlen += 2;
+ *--cmdpos = 'T';
+ *--cmdpos = 'A';
+
+ if (dle) {
+ cmdlen += 4;
+ *--cmdpos = '(';
+ *--cmdpos = 0x10;
+ *cmdtail++ = 0x10;
+ *cmdtail++ = ')';
+ }
+
+ cs->ops->write_cmd(cs, cmdpos, cmdlen, NULL);
+ kfree(cmdbuf);
+ } else
+ err("no memory for command buffer");
+ } else
+ err("overflow in buflen");
+}
+
+static struct at_state_t *at_state_from_cid(struct cardstate *cs, int cid)
+{
+ struct at_state_t *at_state;
+ int i;
+ unsigned long flags;
+
+ if (cid == 0)
+ return &cs->at_state;
+
+ for (i = 0; i < cs->channels; ++i)
+ if (cid == cs->bcs[i].at_state.cid)
+ return &cs->bcs[i].at_state;
+
+ spin_lock_irqsave(&cs->lock, flags);
+
+ list_for_each_entry(at_state, &cs->temp_at_states, list)
+ if (cid == at_state->cid) {
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return at_state;
+ }
+
+ spin_unlock_irqrestore(&cs->lock, flags);
+
+ return NULL;
+}
+
+static void bchannel_down(struct bc_state *bcs)
+{
+ IFNULLRET(bcs);
+ IFNULLRET(bcs->cs);
+
+ if (bcs->chstate & CHS_B_UP) {
+ bcs->chstate &= ~CHS_B_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_BHUP);
+ }
+
+ if (bcs->chstate & (CHS_D_UP | CHS_NOTIFY_LL)) {
+ bcs->chstate &= ~(CHS_D_UP | CHS_NOTIFY_LL);
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DHUP);
+ }
+
+ gigaset_free_channel(bcs);
+
+ gigaset_bcs_reinit(bcs);
+}
+
+static void bchannel_up(struct bc_state *bcs)
+{
+ IFNULLRET(bcs);
+
+ if (!(bcs->chstate & CHS_D_UP)) {
+ notice("%s: D channel not up", __func__);
+ bcs->chstate |= CHS_D_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DCONN);
+ }
+
+ if (bcs->chstate & CHS_B_UP) {
+ notice("%s: B channel already up", __func__);
+ return;
+ }
+
+ bcs->chstate |= CHS_B_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_BCONN);
+}
+
+static void start_dial(struct at_state_t *at_state, void *data, int seq_index)
+{
+ struct bc_state *bcs = at_state->bcs;
+ struct cardstate *cs = at_state->cs;
+ int retval;
+
+ bcs->chstate |= CHS_NOTIFY_LL;
+ //atomic_set(&bcs->status, BCS_INIT);
+
+ if (atomic_read(&at_state->seq_index) != seq_index)
+ goto error;
+
+ retval = gigaset_isdn_setup_dial(at_state, data);
+ if (retval != 0)
+ goto error;
+
+
+ at_state->pending_commands |= PC_CID;
+ dbg(DEBUG_CMD, "Scheduling PC_CID");
+//#ifdef GIG_MAYINITONDIAL
+// if (atomic_read(&cs->MState) == MS_UNKNOWN) {
+// cs->at_state.pending_commands |= PC_INIT;
+// dbg(DEBUG_CMD, "Scheduling PC_INIT");
+// }
+//#endif
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ return;
+
+error:
+ at_state->pending_commands |= PC_NOCID;
+ dbg(DEBUG_CMD, "Scheduling PC_NOCID");
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ return;
+}
+
+static void start_accept(struct at_state_t *at_state)
+{
+ struct cardstate *cs = at_state->cs;
+ int retval;
+
+ retval = gigaset_isdn_setup_accept(at_state);
+
+ if (retval == 0) {
+ at_state->pending_commands |= PC_ACCEPT;
+ dbg(DEBUG_CMD, "Scheduling PC_ACCEPT");
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ } else {
+ //FIXME
+ at_state->pending_commands |= PC_HUP;
+ dbg(DEBUG_CMD, "Scheduling PC_HUP");
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ }
+}
+
+static void do_start(struct cardstate *cs)
+{
+ gigaset_free_channels(cs);
+
+ if (atomic_read(&cs->mstate) != MS_LOCKED)
+ schedule_init(cs, MS_INIT);
+
+ gigaset_i4l_cmd(cs, ISDN_STAT_RUN);
+ // FIXME: not in locked mode
+ // FIXME 2: only after init sequence
+
+ cs->waiting = 0;
+ wake_up(&cs->waitqueue);
+}
+
+static void finish_shutdown(struct cardstate *cs)
+{
+ if (atomic_read(&cs->mstate) != MS_LOCKED) {
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+ atomic_set(&cs->mode, M_UNKNOWN);
+ }
+
+ /* The rest is done by cleanup_cs () in user mode. */
+
+ cs->cmd_result = -ENODEV;
+ cs->waiting = 0;
+ wake_up_interruptible(&cs->waitqueue);
+}
+
+static void do_shutdown(struct cardstate *cs)
+{
+ gigaset_block_channels(cs);
+
+ if (atomic_read(&cs->mstate) == MS_READY) {
+ atomic_set(&cs->mstate, MS_SHUTDOWN);
+ cs->at_state.pending_commands |= PC_SHUTDOWN;
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ dbg(DEBUG_CMD, "Scheduling PC_SHUTDOWN"); //FIXME
+ //gigaset_schedule_event(cs); //FIXME
+ } else
+ finish_shutdown(cs);
+}
+
+static void do_stop(struct cardstate *cs)
+{
+ do_shutdown(cs);
+}
+
+/* Entering cid mode or getting a cid failed:
+ * try to initialize the device and try again.
+ *
+ * channel >= 0: getting cid for the channel failed
+ * channel < 0: entering cid mode failed
+ *
+ * returns 0 on failure
+ */
+static int reinit_and_retry(struct cardstate *cs, int channel)
+{
+ int i;
+
+ if (--cs->retry_count <= 0)
+ return 0;
+
+ for (i = 0; i < cs->channels; ++i)
+ if (cs->bcs[i].at_state.cid > 0)
+ return 0;
+
+ if (channel < 0)
+ warn("Could not enter cid mode. Reinit device and try again.");
+ else {
+ warn("Could not get a call id. Reinit device and try again.");
+ cs->bcs[channel].at_state.pending_commands |= PC_CID;
+ }
+ schedule_init(cs, MS_INIT);
+ return 1;
+}
+
+static int at_state_invalid(struct cardstate *cs,
+ struct at_state_t *test_ptr)
+{
+ unsigned long flags;
+ unsigned channel;
+ struct at_state_t *at_state;
+ int retval = 0;
+
+ spin_lock_irqsave(&cs->lock, flags);
+
+ if (test_ptr == &cs->at_state)
+ goto exit;
+
+ list_for_each_entry(at_state, &cs->temp_at_states, list)
+ if (at_state == test_ptr)
+ goto exit;
+
+ for (channel = 0; channel < cs->channels; ++channel)
+ if (&cs->bcs[channel].at_state == test_ptr)
+ goto exit;
+
+ retval = 1;
+exit:
+ spin_unlock_irqrestore(&cs->lock, flags);
+ return retval;
+}
+
+static void handle_icall(struct cardstate *cs, struct bc_state *bcs,
+ struct at_state_t **p_at_state)
+{
+ int retval;
+ struct at_state_t *at_state = *p_at_state;
+
+ retval = gigaset_isdn_icall(at_state);
+ switch (retval) {
+ case ICALL_ACCEPT:
+ break;
+ default:
+ err("internal error: disposition=%d", retval);
+ /* --v-- fall through --v-- */
+ case ICALL_IGNORE:
+ case ICALL_REJECT:
+ /* hang up actively
+ * Device doc says that would reject the call.
+ * In fact it doesn't.
+ */
+ at_state->pending_commands |= PC_HUP;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+ }
+}
+
+static int do_lock(struct cardstate *cs)
+{
+ int mode;
+ int i;
+
+ switch (atomic_read(&cs->mstate)) {
+ case MS_UNINITIALIZED:
+ case MS_READY:
+ if (cs->cur_at_seq || !list_empty(&cs->temp_at_states) ||
+ cs->at_state.pending_commands)
+ return -EBUSY;
+
+ for (i = 0; i < cs->channels; ++i)
+ if (cs->bcs[i].at_state.pending_commands)
+ return -EBUSY;
+
+ if (!gigaset_get_channels(cs))
+ return -EBUSY;
+
+ break;
+ case MS_LOCKED:
+ //retval = -EACCES;
+ break;
+ default:
+ return -EBUSY;
+ }
+
+ mode = atomic_read(&cs->mode);
+ atomic_set(&cs->mstate, MS_LOCKED);
+ atomic_set(&cs->mode, M_UNKNOWN);
+ //FIXME reset card state / at states / bcs states
+
+ return mode;
+}
+
+static int do_unlock(struct cardstate *cs)
+{
+ if (atomic_read(&cs->mstate) != MS_LOCKED)
+ return -EINVAL;
+
+ atomic_set(&cs->mstate, MS_UNINITIALIZED);
+ atomic_set(&cs->mode, M_UNKNOWN);
+ gigaset_free_channels(cs);
+ //FIXME reset card state / at states / bcs states
+ if (atomic_read(&cs->connected))
+ schedule_init(cs, MS_INIT);
+
+ return 0;
+}
+
+static void do_action(int action, struct cardstate *cs,
+ struct bc_state *bcs,
+ struct at_state_t **p_at_state, char **pp_command,
+ int *p_genresp, int *p_resp_code,
+ struct event_t *ev)
+{
+ struct at_state_t *at_state = *p_at_state;
+ struct at_state_t *at_state2;
+ unsigned long flags;
+
+ int channel;
+
+ unsigned char *s, *e;
+ int i;
+ unsigned long val;
+
+ switch (action) {
+ case ACT_NOTHING:
+ break;
+ case ACT_TIMEOUT:
+ at_state->waiting = 1;
+ break;
+ case ACT_INIT:
+ //FIXME setup everything
+ cs->at_state.pending_commands &= ~PC_INIT;
+ cs->cur_at_seq = SEQ_NONE;
+ atomic_set(&cs->mode, M_UNIMODEM);
+ if (!atomic_read(&cs->cidmode)) {
+ gigaset_free_channels(cs);
+ atomic_set(&cs->mstate, MS_READY);
+ break;
+ }
+ cs->at_state.pending_commands |= PC_CIDMODE;
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ dbg(DEBUG_CMD, "Scheduling PC_CIDMODE");
+ break;
+ case ACT_FAILINIT:
+ warn("Could not initialize the device.");
+ cs->dle = 0;
+ init_failed(cs, M_UNKNOWN);
+ cs->cur_at_seq = SEQ_NONE;
+ break;
+ case ACT_CONFIGMODE:
+ init_failed(cs, M_CONFIG);
+ cs->cur_at_seq = SEQ_NONE;
+ break;
+ case ACT_SETDLE1:
+ cs->dle = 1;
+ /* cs->inbuf[0].inputstate |= INS_command | INS_DLE_command; */
+ cs->inbuf[0].inputstate &=
+ ~(INS_command | INS_DLE_command);
+ break;
+ case ACT_SETDLE0:
+ cs->dle = 0;
+ cs->inbuf[0].inputstate =
+ (cs->inbuf[0].inputstate & ~INS_DLE_command)
+ | INS_command;
+ break;
+ case ACT_CMODESET:
+ if (atomic_read(&cs->mstate) == MS_INIT ||
+ atomic_read(&cs->mstate) == MS_RECOVER) {
+ gigaset_free_channels(cs);
+ atomic_set(&cs->mstate, MS_READY);
+ }
+ atomic_set(&cs->mode, M_CID);
+ cs->cur_at_seq = SEQ_NONE;
+ break;
+ case ACT_UMODESET:
+ atomic_set(&cs->mode, M_UNIMODEM);
+ cs->cur_at_seq = SEQ_NONE;
+ break;
+ case ACT_FAILCMODE:
+ cs->cur_at_seq = SEQ_NONE;
+ if (atomic_read(&cs->mstate) == MS_INIT ||
+ atomic_read(&cs->mstate) == MS_RECOVER) {
+ init_failed(cs, M_UNKNOWN);
+ break;
+ }
+ if (!reinit_and_retry(cs, -1))
+ schedule_init(cs, MS_RECOVER);
+ break;
+ case ACT_FAILUMODE:
+ cs->cur_at_seq = SEQ_NONE;
+ schedule_init(cs, MS_RECOVER);
+ break;
+ case ACT_HUPMODEM:
+ /* send "+++" (hangup in unimodem mode) */
+ cs->ops->write_cmd(cs, "+++", 3, NULL);
+ break;
+ case ACT_RING:
+ /* get fresh AT state structure for new CID */
+ at_state2 = get_free_channel(cs, ev->parameter);
+ if (!at_state2) {
+ warn("RING ignored: "
+ "could not allocate channel structure");
+ break;
+ }
+
+ /* initialize AT state structure
+ * note that bcs may be NULL if no B channel is free
+ */
+ at_state2->ConState = 700;
+ kfree(at_state2->str_var[STR_NMBR]);
+ at_state2->str_var[STR_NMBR] = NULL;
+ kfree(at_state2->str_var[STR_ZCPN]);
+ at_state2->str_var[STR_ZCPN] = NULL;
+ kfree(at_state2->str_var[STR_ZBC]);
+ at_state2->str_var[STR_ZBC] = NULL;
+ kfree(at_state2->str_var[STR_ZHLC]);
+ at_state2->str_var[STR_ZHLC] = NULL;
+ at_state2->int_var[VAR_ZCTP] = -1;
+
+ spin_lock_irqsave(&cs->lock, flags);
+ at_state2->timer_expires = RING_TIMEOUT;
+ at_state2->timer_active = 1;
+ spin_unlock_irqrestore(&cs->lock, flags);
+ break;
+ case ACT_ICALL:
+ handle_icall(cs, bcs, p_at_state);
+ at_state = *p_at_state;
+ break;
+ case ACT_FAILSDOWN:
+ warn("Could not shut down the device.");
+ /* fall through */
+ case ACT_FAKESDOWN:
+ case ACT_SDOWN:
+ cs->cur_at_seq = SEQ_NONE;
+ finish_shutdown(cs);
+ break;
+ case ACT_CONNECT:
+ if (cs->onechannel) {
+ at_state->pending_commands |= PC_DLE1;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+ }
+ bcs->chstate |= CHS_D_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DCONN);
+ cs->ops->init_bchannel(bcs);
+ break;
+ case ACT_DLE1:
+ cs->cur_at_seq = SEQ_NONE;
+ bcs = cs->bcs + cs->curchannel;
+
+ bcs->chstate |= CHS_D_UP;
+ gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DCONN);
+ cs->ops->init_bchannel(bcs);
+ break;
+ case ACT_FAKEHUP:
+ at_state->int_var[VAR_ZSAU] = ZSAU_NULL;
+ /* fall through */
+ case ACT_DISCONNECT:
+ cs->cur_at_seq = SEQ_NONE;
+ at_state->cid = -1;
+ if (bcs && cs->onechannel && cs->dle) {
+ /* Check for other open channels not needed:
+ * DLE only used for M10x with one B channel.
+ */
+ at_state->pending_commands |= PC_DLE0;
+ atomic_set(&cs->commands_pending, 1);
+ } else {
+ disconnect(p_at_state);
+ at_state = *p_at_state;
+ }
+ break;
+ case ACT_FAKEDLE0:
+ at_state->int_var[VAR_ZDLE] = 0;
+ cs->dle = 0;
+ /* fall through */
+ case ACT_DLE0:
+ cs->cur_at_seq = SEQ_NONE;
+ at_state2 = &cs->bcs[cs->curchannel].at_state;
+ disconnect(&at_state2);
+ break;
+ case ACT_ABORTHUP:
+ cs->cur_at_seq = SEQ_NONE;
+ warn("Could not hang up.");
+ at_state->cid = -1;
+ if (bcs && cs->onechannel)
+ at_state->pending_commands |= PC_DLE0;
+ else {
+ disconnect(p_at_state);
+ at_state = *p_at_state;
+ }
+ schedule_init(cs, MS_RECOVER);
+ break;
+ case ACT_FAILDLE0:
+ cs->cur_at_seq = SEQ_NONE;
+ warn("Could not leave DLE mode.");
+ at_state2 = &cs->bcs[cs->curchannel].at_state;
+ disconnect(&at_state2);
+ schedule_init(cs, MS_RECOVER);
+ break;
+ case ACT_FAILDLE1:
+ cs->cur_at_seq = SEQ_NONE;
+ warn("Could not enter DLE mode. Try to hang up.");
+ channel = cs->curchannel;
+ cs->bcs[channel].at_state.pending_commands |= PC_HUP;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+
+ case ACT_CID: /* got cid; start dialing */
+ cs->cur_at_seq = SEQ_NONE;
+ channel = cs->curchannel;
+ if (ev->parameter > 0 && ev->parameter <= 65535) {
+ cs->bcs[channel].at_state.cid = ev->parameter;
+ cs->bcs[channel].at_state.pending_commands |=
+ PC_DIAL;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+ }
+ /* fall through */
+ case ACT_FAILCID:
+ cs->cur_at_seq = SEQ_NONE;
+ channel = cs->curchannel;
+ if (!reinit_and_retry(cs, channel)) {
+ warn("Could not get a call id. Dialing not possible");
+ at_state2 = &cs->bcs[channel].at_state;
+ disconnect(&at_state2);
+ }
+ break;
+ case ACT_ABORTCID:
+ cs->cur_at_seq = SEQ_NONE;
+ at_state2 = &cs->bcs[cs->curchannel].at_state;
+ disconnect(&at_state2);
+ break;
+
+ case ACT_DIALING:
+ case ACT_ACCEPTED:
+ cs->cur_at_seq = SEQ_NONE;
+ break;
+
+ case ACT_ABORTACCEPT: /* hangup/error/timeout during ICALL processing */
+ disconnect(p_at_state);
+ at_state = *p_at_state;
+ break;
+
+ case ACT_ABORTDIAL: /* error/timeout during dial preparation */
+ cs->cur_at_seq = SEQ_NONE;
+ at_state->pending_commands |= PC_HUP;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+
+ case ACT_REMOTEREJECT: /* DISCONNECT_IND after dialling */
+ case ACT_CONNTIMEOUT: /* timeout waiting for ZSAU=ACTIVE */
+ case ACT_REMOTEHUP: /* DISCONNECT_IND with established connection */
+ at_state->pending_commands |= PC_HUP;
+ atomic_set(&cs->commands_pending, 1);
+ break;
+ case ACT_GETSTRING: /* warning: RING, ZDLE, ... are not handled properly any more */
+ at_state->getstring = 1;
+ break;
+ case ACT_SETVER:
+ if (!ev->ptr) {
+ *p_genresp = 1;
+ *p_resp_code = RSP_ERROR;
+ break;
+ }
+ s = ev->ptr;
+
+ if (!strcmp(s, "OK")) {
+ *p_genresp = 1;
+ *p_resp_code = RSP_ERROR;
+ break;
+ }
+
+ for (i = 0; i < 4; ++i) {
+ val = simple_strtoul(s, (char **) &e, 10);
+ if (val > INT_MAX || e == s)
+ break;
+ if (i == 3) {
+ if (*e)
+ break;
+ } else if (*e != '.')
+ break;
+ else
+ s = e + 1;
+ cs->fwver[i] = val;
+ }
+ if (i != 4) {
+ *p_genresp = 1;
+ *p_resp_code = RSP_ERROR;
+ break;
+ }
+ /*at_state->getstring = 1;*/
+ cs->gotfwver = 0;
+ break;
+ case ACT_GOTVER:
+ if (cs->gotfwver == 0) {
+ cs->gotfwver = 1;
+ dbg(DEBUG_ANY,
+ "firmware version %02d.%03d.%02d.%02d",
+ cs->fwver[0], cs->fwver[1],
+ cs->fwver[2], cs->fwver[3]);
+ break;
+ }
+ /* fall through */
+ case ACT_FAILVER:
+ cs->gotfwver = -1;
+ err("could not read firmware version.");
+ break;
+#ifdef CONFIG_GIGASET_DEBUG
+ case ACT_ERROR:
+ *p_genresp = 1;
+ *p_resp_code = RSP_ERROR;
+ break;
+ case ACT_TEST:
+ {
+ static int count = 3; //2; //1;
+ *p_genresp = 1;
+ *p_resp_code = count ? RSP_ERROR : RSP_OK;
+ if (count > 0)
+ --count;
+ }
+ break;
+#endif
+ case ACT_DEBUG:
+ dbg(DEBUG_ANY, "%s: resp_code %d in ConState %d",
+ __func__, ev->type, at_state->ConState);
+ break;
+ case ACT_WARN:
+ warn("%s: resp_code %d in ConState %d!",
+ __func__, ev->type, at_state->ConState);
+ break;
+ case ACT_ZCAU:
+ warn("cause code %04x in connection state %d.",
+ ev->parameter, at_state->ConState);
+ break;
+
+ /* events from the LL */
+ case ACT_DIAL:
+ start_dial(at_state, ev->ptr, ev->parameter);
+ break;
+ case ACT_ACCEPT:
+ start_accept(at_state);
+ break;
+ case ACT_PROTO_L2:
+ dbg(DEBUG_CMD,
+ "set protocol to %u", (unsigned) ev->parameter);
+ at_state->bcs->proto2 = ev->parameter;
+ break;
+ case ACT_HUP:
+ at_state->pending_commands |= PC_HUP;
+ atomic_set(&cs->commands_pending, 1); //FIXME
+ dbg(DEBUG_CMD, "Scheduling PC_HUP");
+ break;
+
+ /* hotplug events */
+ case ACT_STOP:
+ do_stop(cs);
+ break;
+ case ACT_START:
+ do_start(cs);
+ break;
+
+ /* events from the interface */ // FIXME without ACT_xxxx?
+ case ACT_IF_LOCK:
+ cs->cmd_result = ev->parameter ? do_lock(cs) : do_unlock(cs);
+ cs->waiting = 0;
+ wake_up(&cs->waitqueue);
+ break;
+ case ACT_IF_VER:
+ if (ev->parameter != 0)
+ cs->cmd_result = -EINVAL;
+ else if (cs->gotfwver != 1) {
+ cs->cmd_result = -ENOENT;
+ } else {
+ memcpy(ev->arg, cs->fwver, sizeof cs->fwver);
+ cs->cmd_result = 0;
+ }
+ cs->waiting = 0;
+ wake_up(&cs->waitqueue);
+ break;
+
+ /* events from the proc file system */ // FIXME without ACT_xxxx?
+ case ACT_PROC_CIDMODE:
+ if (ev->parameter != atomic_read(&cs->cidmode)) {
+ atomic_set(&cs->cidmode, ev->parameter);
+ if (ev->parameter) {
+ cs->at_state.pending_commands |= PC_CIDMODE;
+ dbg(DEBUG_CMD, "Scheduling PC_CIDMODE");
+ } else {
+ cs->at_state.pending_commands |= PC_UMMODE;
+ dbg(DEBUG_CMD, "Scheduling PC_UMMODE");
+ }
+ atomic_set(&cs->commands_pending, 1);
+ }
+ cs->waiting = 0;
+ wake_up(&cs->waitqueue);
+ break;
+
+ /* events from the hardware drivers */
+ case ACT_NOTIFY_BC_DOWN:
+ bchannel_down(bcs);
+ break;
+ case ACT_NOTIFY_BC_UP:
+ bchannel_up(bcs);
+ break;
+ case ACT_SHUTDOWN:
+ do_shutdown(cs);
+ break;
+
+
+ default:
+ if (action >= ACT_CMD && action < ACT_CMD + AT_NUM) {
+ *pp_command = at_state->bcs->commands[action - ACT_CMD];
+ if (!*pp_command) {
+ *p_genresp = 1;
+ *p_resp_code = RSP_NULL;
+ }
+ } else
+ err("%s: action==%d!", __func__, action);
+ }
+}
+
+/* State machine to do the calling and hangup procedure */
+static void process_event(struct cardstate *cs, struct event_t *ev)
+{
+ struct bc_state *bcs;
+ char *p_command = NULL;
+ struct reply_t *rep;
+ int rcode;
+ int genresp = 0;
+ int resp_code = RSP_ERROR;
+ int sendcid;
+ struct at_state_t *at_state;
+ int index;
+ int curact;
+ unsigned long flags;
+
+ IFNULLRET(cs);
+ IFNULLRET(ev);
+
+ if (ev->cid >= 0) {
+ at_state = at_state_from_cid(cs, ev->cid);
+ if (!at_state) {
+ gigaset_add_event(cs, &cs->at_state, RSP_WRONG_CID,
+ NULL, 0, NULL);
+ return;
+ }
+ } else {
+ at_state = ev->at_state;
+ if (at_state_invalid(cs, at_state)) {
+ dbg(DEBUG_ANY,
+ "event for invalid at_state %p", at_state);
+ return;
+ }
+ }
+
+ dbg(DEBUG_CMD,
+ "connection state %d, event %d", at_state->ConState, ev->type);
+
+ bcs = at_state->bcs;
+ sendcid = at_state->cid;
+
+ /* Setting the pointer to the dial array */
+ rep = at_state->replystruct;
+ IFNULLRET(rep);
+
+ if (ev->type == EV_TIMEOUT) {
+ if (ev->parameter != atomic_read(&at_state->timer_index)
+ || !at_state->timer_active) {
+ ev->type = RSP_NONE; /* old timeout */
+ dbg(DEBUG_ANY, "old timeout");
+ } else if (!at_state->waiting)
+ dbg(DEBUG_ANY, "timeout occured");
+ else
+ dbg(DEBUG_ANY, "stopped waiting");
+ }
+
+ /* if the response belongs to a variable in at_state->int_var[VAR_XXXX] or at_state->str_var[STR_XXXX], set it */
+ if (ev->type >= RSP_VAR && ev->type < RSP_VAR + VAR_NUM) {
+ index = ev->type - RSP_VAR;
+ at_state->int_var[index] = ev->parameter;
+ } else if (ev->type >= RSP_STR && ev->type < RSP_STR + STR_NUM) {
+ index = ev->type - RSP_STR;
+ kfree(at_state->str_var[index]);
+ at_state->str_var[index] = ev->ptr;
+ ev->ptr = NULL; /* prevent process_events() from deallocating ptr */
+ }
+
+ if (ev->type == EV_TIMEOUT || ev->type == RSP_STRING)
+ at_state->getstring = 0;
+
+ /* Search row in dial array which matches modem response and current constate */
+ for (;; rep++) {
+ rcode = rep->resp_code;
+ /* dbg (DEBUG_ANY, "rcode %d", rcode); */
+ if (rcode == RSP_LAST) {
+ /* found nothing...*/
+ warn("%s: rcode=RSP_LAST: resp_code %d in ConState %d!",
+ __func__, ev->type, at_state->ConState);
+ return;
+ }
+ if ((rcode == RSP_ANY || rcode == ev->type)
+ && ((int) at_state->ConState >= rep->min_ConState)
+ && (rep->max_ConState < 0
+ || (int) at_state->ConState <= rep->max_ConState)
+ && (rep->parameter < 0 || rep->parameter == ev->parameter))
+ break;
+ }
+
+ p_command = rep->command;
+
+ at_state->waiting = 0;
+ for (curact = 0; curact < MAXACT; ++curact) {
+ /* The row tells us what we should do ..
+ */
+ do_action(rep->action[curact], cs, bcs, &at_state, &p_command, &genresp, &resp_code, ev);
+ if (!at_state)
+ break; /* may be freed after disconnect */
+ }
+
+ if (at_state) {
+ /* Jump to the next con-state regarding the array */
+ if (rep->new_ConState >= 0)
+ at_state->ConState = rep->new_ConState;
+
+ if (genresp) {
+ spin_lock_irqsave(&cs->lock, flags);
+ at_state->timer_expires = 0; //FIXME
+ at_state->timer_active = 0; //FIXME
+ spin_unlock_irqrestore(&cs->lock, flags);
+ gigaset_add_event(cs, at_state, resp_code, NULL, 0, NULL);
+ } else {
+ /* Send command to modem if not NULL... */
+ if (p_command/*rep->command*/) {
+ if (atomic_read(&cs->connected))
+ send_command(cs, p_command,
+ sendcid, cs->dle,
+ GFP_ATOMIC);
+ else
+ gigaset_add_event(cs, at_state,
+ RSP_NODEV,
+ NULL, 0, NULL);
+ }
+
+ spin_lock_irqsave(&cs->lock, flags);
+ if (!rep->timeout) {
+ at_state->timer_expires = 0;
+ at_state->timer_active = 0;
+ } else if (rep->timeout > 0) { /* new timeout */
+ at_state->timer_expires = rep->timeout * 10;
+ at_state->timer_active = 1;
+ new_index(&at_state->timer_index,
+ MAX_TIMER_INDEX);
+ }
+ spin_unlock_irqrestore(&cs->lock, flags);
+ }
+ }
+}
+
+static void schedule_sequence(struct cardstate *cs,
+ struct at_state_t *at_state, int sequence)
+{
+ cs->cur_at_seq = sequence;
+ gigaset_add_event(cs, at_state, RSP_INIT, NULL, sequence, NULL);
+}
+
+static void process_command_flags(struct cardstate *cs)
+{
+ struct at_state_t *at_state = NULL;
+ struct bc_state *bcs;
+ int i;
+ int sequence;
+
+ IFNULLRET(cs);
+
+ atomic_set(&cs->commands_pending, 0);
+
+ if (cs->cur_at_seq) {
+ dbg(DEBUG_CMD, "not searching scheduled commands: busy");
+ return;
+ }
+
+ dbg(DEBUG_CMD, "searching scheduled commands");
+
+ sequence = SEQ_NONE;
+
+ /* clear pending_commands and hangup channels on shutdown */
+ if (cs->at_state.pending_commands & PC_SHUTDOWN) {
+ cs->at_state.pending_commands &= ~PC_CIDMODE;
+ for (i = 0; i < cs->channels; ++i) {
+ bcs = cs->bcs + i;
+ at_state = &bcs->at_state;
+ at_state->pending_commands &=
+ ~(PC_DLE1 | PC_ACCEPT | PC_DIAL);
+ if (at_state->cid > 0)
+ at_state->pending_commands |= PC_HUP;
+ if (at_state->pending_commands & PC_CID) {
+ at_state->pending_commands |= PC_NOCID;
+ at_state->pending_commands &= ~PC_CID;
+ }
+ }
+ }
+
+ /* clear pending_commands and hangup channels on reset */
+ if (cs->at_state.pending_commands & PC_INIT) {
+ cs->at_state.pending_commands &= ~PC_CIDMODE;
+ for (i = 0; i < cs->channels; ++i) {
+ bcs = cs->bcs + i;
+ at_state = &bcs->at_state;
+ at_state->pending_commands &=
+ ~(PC_DLE1 | PC_ACCEPT | PC_DIAL);
+ if (at_state->cid > 0)
+ at_state->pending_commands |= PC_HUP;
+ if (atomic_read(&cs->mstate) == MS_RECOVER) {
+ if (at_state->pending_commands & PC_CID) {
+ at_state->pending_commands |= PC_NOCID;
+ at_state->pending_commands &= ~PC_CID;
+ }
+ }
+ }
+ }
+
+ /* only switch back to unimodem mode, if no commands are pending and no channels are up */
+ if (cs->at_state.pending_commands == PC_UMMODE
+ && !atomic_read(&cs->cidmode)
+ && list_empty(&cs->temp_at_states)
+ && atomic_read(&cs->mode) == M_CID) {
+ sequence = SEQ_UMMODE;
+ at_state = &cs->at_state;
+ for (i = 0; i < cs->channels; ++i) {
+ bcs = cs->bcs + i;
+ if (bcs->at_state.pending_commands ||
+ bcs->at_state.cid > 0) {
+ sequence = SEQ_NONE;
+ break;
+ }
+ }
+ }
+ cs->at_state.pending_commands &= ~PC_UMMODE;
+ if (sequence != SEQ_NONE) {
+ schedule_sequence(cs, at_state, sequence);
+ return;
+ }
+
+ for (i = 0; i < cs->channels; ++i) {
+ bcs = cs->bcs + i;
+ if (bcs->at_state.pending_commands & PC_HUP) {
+ bcs->at_state.pending_commands &= ~PC_HUP;
+ if (bcs->at_state.pending_commands & PC_CID) {
+ /* not yet dialing: PC_NOCID is sufficient */
+ bcs->at_state.pending_commands |= PC_NOCID;
+ bcs->at_state.pending_commands &= ~PC_CID;
+ } else {
+ schedule_sequence(cs, &bcs->at_state, SEQ_HUP);
+ return;
+ }
+ }
+ if (bcs->at_state.pending_commands & PC_NOCID) {
+ bcs->at_state.pending_commands &= ~PC_NOCID;
+ cs->curchannel = bcs->channel;
+ schedule_sequence(cs, &cs->at_state, SEQ_NOCID);
+ return;
+ } else if (bcs->at_state.pending_commands & PC_DLE0) {
+ bcs->at_state.pending_commands &= ~PC_DLE0;
+ cs->curchannel = bcs->channel;
+ schedule_sequence(cs, &cs->at_state, SEQ_DLE0);
+ return;
+ }
+ }
+
+ list_for_each_entry(at_state, &cs->temp_at_states, list)
+ if (at_state->pending_commands & PC_HUP) {
+ at_state->pending_commands &= ~PC_HUP;
+ schedule_sequence(cs, at_state, SEQ_HUP);
+ return;
+ }
+
+ if (cs->at_state.pending_commands & PC_INIT) {
+ cs->at_state.pending_commands &= ~PC_INIT;
+ cs->dle = 0; //FIXME
+ cs->inbuf->inputstate = INS_command;
+ //FIXME reset card state (or -> LOCK0)?
+ schedule_sequence(cs, &cs->at_state, SEQ_INIT);
+ return;
+ }
+ if (cs->at_state.pending_commands & PC_SHUTDOWN) {
+ cs->at_state.pending_commands &= ~PC_SHUTDOWN;
+ schedule_sequence(cs, &cs->at_state, SEQ_SHUTDOWN);
+ return;
+ }
+ if (cs->at_state.pending_commands & PC_CIDMODE) {
+ cs->at_state.pending_commands &= ~PC_CIDMODE;
+ if (atomic_read(&cs->mode) == M_UNIMODEM) {
+#if 0
+ cs->retry_count = 2;
+#else
+ cs->retry_count = 1;
+#endif
+ schedule_sequence(cs, &cs->at_state, SEQ_CIDMODE);
+ return;
+ }
+ }
+
+ for (i = 0; i < cs->channels; ++i) {
+ bcs = cs->bcs + i;
+ if (bcs->at_state.pending_commands & PC_DLE1) {
+ bcs->at_state.pending_commands &= ~PC_DLE1;
+ cs->curchannel = bcs->channel;
+ schedule_sequence(cs, &cs->at_state, SEQ_DLE1);
+ return;
+ }
+ if (bcs->at_state.pending_commands & PC_ACCEPT) {
+ bcs->at_state.pending_commands &= ~PC_ACCEPT;
+ schedule_sequence(cs, &bcs->at_state, SEQ_ACCEPT);
+ return;
+ }
+ if (bcs->at_state.pending_commands & PC_DIAL) {
+ bcs->at_state.pending_commands &= ~PC_DIAL;
+ schedule_sequence(cs, &bcs->at_state, SEQ_DIAL);
+ return;
+ }
+ if (bcs->at_state.pending_commands & PC_CID) {
+ switch (atomic_read(&cs->mode)) {
+ case M_UNIMODEM:
+ cs->at_state.pending_commands |= PC_CIDMODE;
+ dbg(DEBUG_CMD, "Scheduling PC_CIDMODE");
+ atomic_set(&cs->commands_pending, 1);
+ return;
+#ifdef GIG_MAYINITONDIAL
+ case M_UNKNOWN:
+ schedule_init(cs, MS_INIT);
+ return;
+#endif
+ }
+ bcs->at_state.pending_commands &= ~PC_CID;
+ cs->curchannel = bcs->channel;
+#ifdef GIG_RETRYCID
+ cs->retry_count = 2;
+#else
+ cs->retry_count = 1;
+#endif
+ schedule_sequence(cs, &cs->at_state, SEQ_CID);
+ return;
+ }
+ }
+}
+
+static void process_events(struct cardstate *cs)
+{
+ struct event_t *ev;
+ unsigned head, tail;
+ int i;
+ int check_flags = 0;
+ int was_busy;
+
+ /* no locking needed (only one reader) */
+ head = atomic_read(&cs->ev_head);
+
+ for (i = 0; i < 2 * MAX_EVENTS; ++i) {
+ tail = atomic_read(&cs->ev_tail);
+ if (tail == head) {
+ if (!check_flags && !atomic_read(&cs->commands_pending))
+ break;
+ check_flags = 0;
+ process_command_flags(cs);
+ tail = atomic_read(&cs->ev_tail);
+ if (tail == head) {
+ if (!atomic_read(&cs->commands_pending))
+ break;
+ continue;
+ }
+ }
+
+ ev = cs->events + head;
+ was_busy = cs->cur_at_seq != SEQ_NONE;
+ process_event(cs, ev);
+ kfree(ev->ptr);
+ ev->ptr = NULL;
+ if (was_busy && cs->cur_at_seq == SEQ_NONE)
+ check_flags = 1;
+
+ head = (head + 1) % MAX_EVENTS;
+ atomic_set(&cs->ev_head, head);
+ }
+
+ if (i == 2 * MAX_EVENTS) {
+ err("infinite loop in process_events; aborting.");
+ }
+}
+
+/* tasklet scheduled on any event received from the Gigaset device
+ * parameter:
+ * data ISDN controller state structure
+ */
+void gigaset_handle_event(unsigned long data)
+{
+ struct cardstate *cs = (struct cardstate *) data;
+
+ IFNULLRET(cs);
+ IFNULLRET(cs->inbuf);
+
+ /* handle incoming data on control/common channel */
+ if (atomic_read(&cs->inbuf->head) != atomic_read(&cs->inbuf->tail)) {
+ dbg(DEBUG_INTR, "processing new data");
+ cs->ops->handle_input(cs->inbuf);
+ }
+
+ process_events(cs);
+}

2006-02-12 10:28:40

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH 6/9] isdn4linux: Siemens Gigaset drivers - procfs interface

Hansjoerg Lipp <[email protected]> wrote:
>
> This patch adds the procfs interface to the gigaset module.

sysfs, actually.

The patches look reasonable from a quick scan. A few little things:

- The ringbuffer head and tail indexes are atomic_t's, but always seem to
be manipulated inside the lock. Perhaps they can become integers.

- You did the ringbuffer the wrong way. Don't constrain the head and
tail to be within 0..MAX_EVENTS. Instead, just let them wrap right up to
0xffffffff. Apply the masking when you actually _use_ them.

That way, empty is (head == tail) and full is (tail - head == MAX_EVENTS).

- Could use kstrdup() in a few places.

- A few unneeded casts of void*'s, but everyone does that.

- There are a lot of global symbols in there. Perhaps they don't all
need to be global.


2006-02-12 10:30:15

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH 0/9] isdn4linux: add drivers for Siemens Gigaset ISDN DECT PABX

Hansjoerg Lipp <[email protected]> wrote:
>
> The patch has been split into 9 parts to comply to size limits.
> All of the parts are designed to be applied together.

This means that the patches should go into git in a single commit, so that
we don't have a non-compiling tree at any step. That's not a problem.

2006-02-15 01:54:24

by Tilman Schmidt

[permalink] [raw]
Subject: Re: [PATCH 6/9] isdn4linux: Siemens Gigaset drivers - procfs interface

Andrew,

thank you very much for your comments.

On 12.02.2006 11:27, Andrew Morton wrote:
>
>>This patch adds the procfs interface to the gigaset module.
>
> sysfs, actually.

Yes, sorry, the comment didn't get updated when we did the move from
procfs to sysfs.

> - The ringbuffer head and tail indexes are atomic_t's, but always seem to
> be manipulated inside the lock. Perhaps they can become integers.

We use ringbuffers in two places. I suppose you are talking about the
one implementing our event queue through the ev_head and ev_tail members
of the cardstate structure. You have a point there, although I'm not
sure if it wouldn't be better to take advantage of the atomic_t and do a
little less locking instead.

The other one, within the inbuf_t structure, used for capturing
characters received from the device, is accessed without locking from
the gigaset_handle_event() tasklet and therefore needs atomic_t indexes,
IMHO.

> - You did the ringbuffer the wrong way. Don't constrain the head and
> tail to be within 0..MAX_EVENTS. Instead, just let them wrap right up to
> 0xffffffff. Apply the masking when you actually _use_ them.
>
> That way, empty is (head == tail) and full is (tail - head == MAX_EVENTS).

Interesting idea. I have to admit it's rather new to me. I have always
done ringbuffers the way they are done in the Gigaset driver now. Can
you point me to some example code done the way you propose, so I can
familiarize myself with its advantages?

> - Could use kstrdup() in a few places.

Thanks for the hint. Will watch out for these.

> - A few unneeded casts of void*'s, but everyone does that.

:-)

> - There are a lot of global symbols in there. Perhaps they don't all
> need to be global.

Well, I *hope* there aren't any unnecessary ones, but we'll re-check.

Regards
Tilman

--
Tilman Schmidt E-Mail: [email protected]
Bonn, Germany
Diese Nachricht besteht zu 100% aus wiederverwerteten Bits.
Unge?ffnet mindestens haltbar bis: (siehe R?ckseite)


Attachments:
signature.asc (250.00 B)
OpenPGP digital signature

2006-02-15 03:20:20

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 1/9] isdn4linux: Siemens Gigaset drivers - Kconfigs and Makefiles

On Sat, Feb 11, 2006 at 03:52:27PM +0100, Hansjoerg Lipp wrote:
> From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>
>
> This patch prepares the kernel build infrastructure for addition of the
> Gigaset ISDN drivers. It creates a Makefile and Kconfig file for the
> Gigaset driver and hooks them into those of the isdn4linux subsystem.
> It also adds a MAINTAINERS entry for the driver.
>
> This patch depends on patches 2 to 9 of the present set, as without the
> actual source files, activating the options added here will cause the
> kernel build to fail.

Care to redo that and add the Makefile change at the same time as the
driver goes into the tree? We don't want to break the buid for a
specific patch.

thanks,

greg k-h

2006-02-15 03:23:45

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH 6/9] isdn4linux: Siemens Gigaset drivers - procfs interface

Tilman Schmidt <[email protected]> wrote:
>
> > - You did the ringbuffer the wrong way. Don't constrain the head and
> > tail to be within 0..MAX_EVENTS. Instead, just let them wrap right up to
> > 0xffffffff. Apply the masking when you actually _use_ them.
> >
> > That way, empty is (head == tail) and full is (tail - head == MAX_EVENTS).
>
> Interesting idea. I have to admit it's rather new to me. I have always
> done ringbuffers the way they are done in the Gigaset driver now. Can
> you point me to some example code done the way you propose, so I can
> familiarize myself with its advantages?

Pretty much all the Becker-derived net drivers do this - Say,
vortex_private.cur_tx, .cur_tx. Also include/linux/circ_buf.h and its
various users.

2006-02-15 03:27:15

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 2/9] isdn4linux: Siemens Gigaset drivers - common module

Overall, all of these look fine, I only have a few minor cleanup
comments here and there...


On Sat, Feb 11, 2006 at 03:52:27PM +0100, Hansjoerg Lipp wrote:
> --- linux-2.6.16-rc2/drivers/isdn/gigaset/gigaset.h 1970-01-01 01:00:00.000000000 +0100
> +++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/gigaset.h 2006-02-11 15:20:26.000000000 +0100
> @@ -0,0 +1,938 @@
> +/* Siemens Gigaset 307x driver
> + * Common header file for all connection variants
> + *
> + * Written by Stefan Eilers <[email protected]>
> + * and Hansjoerg Lipp <[email protected]>
> + *
> + * Version: $Id: gigaset.h,v 1.97.4.26 2006/02/04 18:28:16 hjlipp Exp $

$Id: isn't needed within the kernel tree :)

> + * ===========================================================================
> + */
> +
> +#ifndef GIGASET_H
> +#define GIGASET_H
> +
> +#include <linux/config.h>
> +#include <linux/kernel.h>
> +#include <linux/compiler.h>
> +#include <linux/types.h>
> +#include <asm/atomic.h>
> +#include <linux/spinlock.h>
> +#include <linux/isdnif.h>
> +#include <linux/usb.h>
> +#include <linux/skbuff.h>
> +#include <linux/netdevice.h>
> +#include <linux/ppp_defs.h>
> +#include <linux/timer.h>
> +#include <linux/interrupt.h>
> +#include <linux/tty.h>
> +#include <linux/tty_driver.h>
> +#include <linux/list.h>

asm #includes should be at the end of the list.

> +
> +#define GIG_VERSION {0,5,0,0}
> +#define GIG_COMPAT {0,4,0,0}
> +
> +#define MAX_REC_PARAMS 10 /* Max. number of params in response string */
> +#define MAX_RESP_SIZE 512 /* Max. size of a response string */
> +#define HW_HDR_LEN 2 /* Header size used to store ack info */

No tabs here, yet in other places in the file, you have tabs. Try using
them everywhere. Also try to follow the 80 column rule where ever
possible, as per the Documentation/CodingStyle file.

> +#define MAX_EVENTS 64 /* size of event queue */
> +
> +#define RBUFSIZE 8192
> +#define SBUFSIZE 4096 /* sk_buff payload size */
> +
> +#define MAX_BUF_SIZE (SBUFSIZE - 2) /* Max. size of a data packet from LL */
> +#define TRANSBUFSIZE 768 /* bytes per skb for transparent receive */
> +
> +/* compile time options */
> +#define GIG_MAJOR 0
> +
> +#define GIG_MAYINITONDIAL
> +#define GIG_RETRYCID
> +#define GIG_X75
> +
> +#define MAX_TIMER_INDEX 1000
> +#define MAX_SEQ_INDEX 1000
> +
> +#define GIG_TICK (HZ / 10)

What is this needed for? Timeouts should be in real units, right?

> +/* redefine syslog macros to prepend module name instead of entire source path */
> +/* The space before the comma in ", ##" is needed by gcc 2.95 */

gcc 2.95 isn't supported by the kernel anymore :)

> +#undef info
> +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)

Care to use the dev_info(), dev_err() and other dev_* friends instead of
rolling your own? It gives you a much easier and standardised way of
identifying the driver and individual device that the message is
happening for.

thanks,

greg k-h

2006-02-15 03:30:40

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 6/9] isdn4linux: Siemens Gigaset drivers - procfs interface

On Sat, Feb 11, 2006 at 03:52:28PM +0100, Hansjoerg Lipp wrote:
> +#include "gigaset.h"
> +#include <linux/ctype.h>
> +
> +static ssize_t show_cidmode(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct usb_interface *intf = to_usb_interface(dev);
> + struct cardstate *cs = usb_get_intfdata(intf);
> + return sprintf(buf, "%d\n", atomic_read(&cs->cidmode)); // FIXME use scnprintf for 13607 bit architectures (if PAGE_SIZE==4096)

sprintf is just fine here, you are only printing a single integer, and
you don't need to worry about the page size.

thanks,

greg k-h

2006-02-15 03:33:28

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 7/9] isdn4linux: Siemens Gigaset drivers - direct USB connection

On Sat, Feb 11, 2006 at 03:52:28PM +0100, Hansjoerg Lipp wrote:
> +/* table of devices that work with this driver */
> +static struct usb_device_id gigaset_table [] = {
> + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_GIGA_PRODUCT_ID) },
> + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_4175_PRODUCT_ID) },
> + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_SX303_PRODUCT_ID) },
> + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_SX353_PRODUCT_ID) },
> + { } /* Terminating entry */
> +};
> +
> +MODULE_DEVICE_TABLE(usb, gigaset_table);
> +
> +/* Get a minor range for your devices from the usb maintainer */
> +#define USB_SKEL_MINOR_BASE 200

I don't think you need that comment anymore, or the #define anymore :)

thanks,

greg k-h

2006-02-15 03:35:51

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH 9/9] isdn4linux: Siemens Gigaset drivers - M105 USB DECT adapter

On Sat, Feb 11, 2006 at 03:52:29PM +0100, Hansjoerg Lipp wrote:
> +
> +/* Get a minor range for your devices from the usb maintainer */
> +#define USB_SKEL_MINOR_BASE 200

I don't think you need this here anymore :)

> +struct usb_cardstate {
> + struct usb_device *udev; /* save off the usb device pointer */
> + struct usb_interface *interface; /* the interface for this device */

You can get to the usb_device from the usb_interface with a helper
function call, but if you want to save both pointers off too, that's
fine.


2006-02-15 04:13:07

by Nishanth Aravamudan

[permalink] [raw]
Subject: Re: [PATCH 2/9] isdn4linux: Siemens Gigaset drivers - common module

On 11.02.2006 [15:52:27 +0100], Hansjoerg Lipp wrote:
> From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>
>
> This patch adds the common include file for the Siemens Gigaset drivers,
> providing definitions used by all of the Gigaset ISDN driver source files.
> It also adds the main source file of the gigaset module which manages
> common functions not specific to the type of connection to the device.

<snip>

> +#define GIG_TICK (HZ / 10)

This should probable be in milliseconds and use msecs_to_jiffies either
with another #define, or in the code directly (as Greg also indicated).
That way, we avoid rounding issues as best we can with HZ==250.

<snip>

> +/* gigaset_initcs
> + * Allocate and initialize cardstate structure for Gigaset driver
> + * Calls hardware dependent gigaset_initcshw() function
> + * Calls B channel initialization function gigaset_initbcs() for each B channel
> + * parameters:
> + * drv hardware driver the device belongs to
> + * channels number of B channels supported by device
> + * onechannel !=0: B channel data and AT commands share one communication channel
> + * ==0: B channels have separate communication channels
> + * ignoreframes number of frames to ignore after setting up B channel
> + * cidmode !=0: start in CallID mode
> + * modulename name of driver module (used for I4L registration)
> + * return value:
> + * pointer to cardstate structure
> + */
> +struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels,
> + int onechannel, int ignoreframes,
> + int cidmode, const char *modulename)
> +{

<snip>

> + cs->timer.data = (unsigned long) cs;
> + cs->timer.function = timer_tick;
> + cs->timer.expires = jiffies + GIG_TICK;

setup_timer()?

Thanks,
Nish

2006-02-16 21:30:55

by Tilman Schmidt

[permalink] [raw]
Subject: Re: [PATCH 1/9] isdn4linux: Siemens Gigaset drivers - Kconfigs and Makefiles

On 15.02.2006 04:19, Greg KH wrote:
> On Sat, Feb 11, 2006 at 03:52:27PM +0100, Hansjoerg Lipp wrote:
>
>>From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>
>>
>>This patch prepares the kernel build infrastructure for addition of the
>>Gigaset ISDN drivers. It creates a Makefile and Kconfig file for the
>>Gigaset driver and hooks them into those of the isdn4linux subsystem.
>>It also adds a MAINTAINERS entry for the driver.
>>
>>This patch depends on patches 2 to 9 of the present set, as without the
>>actual source files, activating the options added here will cause the
>>kernel build to fail.
>
> Care to redo that and add the Makefile change at the same time as the
> driver goes into the tree? We don't want to break the buid for a
> specific patch.

I'm afraid I don't quite understand what you'd like us to do. Should we
resubmit the whole driver as a single patch? If so, how do we get it
past the lkml size limit, and how do we indicate that this replaces,
instead of going on top of, our 11 Feb 2006 submission in the -mm tree?
Alternatively, would it suffice to make sure that part 1 goes into the
tree after all the others? If so, how would we achieve that?

Also, I'm a bit unsure how this comment relates to the one by Andrew
Morton dated 12 Feb 2006 02:29:21 -0800 in which he writes:

> This means that the patches should go into git in a single commit, so that
> we don't have a non-compiling tree at any step. That's not a problem.

That seems to indicate that the problem is already being taken care of,
and if so, I'd certainly not want to interfere with that.

As to your other comments, they are well taken, and we are preparing a
patch against 2.6.16-rc3-mm1 which we'll submit as soon as the present
issue is sorted out.

Regards
Tilman

--
Tilman Schmidt E-Mail: [email protected]
Bonn, Germany
Diese Nachricht besteht zu 100% aus wiederverwerteten Bits.
Unge?ffnet mindestens haltbar bis: (siehe R?ckseite)


Attachments:
signature.asc (250.00 B)
OpenPGP digital signature

2006-02-21 17:02:36

by Tilman Schmidt

[permalink] [raw]
Subject: advice on using dev_info(), dev_err() and friends (was: [PATCH 2/9] isdn4linux: Siemens Gigaset drivers - common module)

Hello Greg,

thank you for your comments. Just a few follow-up questions.

On 15.02.2006 04:27, Greg KH wrote:
> On Sat, Feb 11, 2006 at 03:52:27PM +0100, Hansjoerg Lipp wrote:
>
>>--- linux-2.6.16-rc2/drivers/isdn/gigaset/gigaset.h 1970-01-01 01:00:00.000000000 +0100
>>+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/gigaset.h 2006-02-11 15:20:26.000000000 +0100
[...]
>>+#undef info
>>+#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
>
> Care to use the dev_info(), dev_err() and other dev_* friends instead of
> rolling your own? It gives you a much easier and standardised way of
> identifying the driver and individual device that the message is
> happening for.

This turns out to be surprisingly tricky, as these macros take a device
pointer argument which mustn't be NULL either.

Could you please advise how to use these when I do not have a device
pointer available (ie. before the probe method has been called) or when
there is a risk of it being no longer valid (ie. the USB device has been
unplugged)? I can detect both cases by checking for dev==NULL, but then
what do I do if it is? Is there a dummy device structure somewhere which
I could then use instead? Somehow, replacing all our occurrences of
info("m%sg", args);
with
if (cs->dev)
dev_info(cs->dev, "m%sg\n", args);
else
printk(KERN_INFO "gigaset: m%sg\n", args);
doesn't really appeal to me. :-)

Thanks
Tilman

2006-02-21 17:16:53

by Tilman Schmidt

[permalink] [raw]
Subject: how to handle multi-part patch dependencies (was: [PATCH 1/9] isdn4linux: Siemens Gigaset drivers - Kconfigs and Makefiles)

Hello Greg,

thank you for your comments. Just a few follow-up questions.

On 15.02.2006 04:19, Greg KH wrote:

> On Sat, Feb 11, 2006 at 03:52:27PM +0100, Hansjoerg Lipp wrote:
>
>>From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>
>>
>>This patch prepares the kernel build infrastructure for addition of the
>>Gigaset ISDN drivers. It creates a Makefile and Kconfig file for the
>>Gigaset driver and hooks them into those of the isdn4linux subsystem.
>>It also adds a MAINTAINERS entry for the driver.
>>
>>This patch depends on patches 2 to 9 of the present set, as without the
>>actual source files, activating the options added here will cause the
>>kernel build to fail.
>
>
> Care to redo that and add the Makefile change at the same time as the
> driver goes into the tree? We don't want to break the buid for a
> specific patch.

Could you tell me how to do that? How do I achieve that all parts of a
patchset go into the tree at the same time?

Thanks
Tilman

2006-02-21 23:01:01

by Greg KH

[permalink] [raw]
Subject: Re: advice on using dev_info(), dev_err() and friends (was: [PATCH 2/9] isdn4linux: Siemens Gigaset drivers - common module)

On Tue, Feb 21, 2006 at 06:01:14PM +0100, Tilman Schmidt wrote:
> Hello Greg,
>
> thank you for your comments. Just a few follow-up questions.
>
> On 15.02.2006 04:27, Greg KH wrote:
> > On Sat, Feb 11, 2006 at 03:52:27PM +0100, Hansjoerg Lipp wrote:
> >
> >>--- linux-2.6.16-rc2/drivers/isdn/gigaset/gigaset.h 1970-01-01 01:00:00.000000000 +0100
> >>+++ linux-2.6.16-rc2-gig/drivers/isdn/gigaset/gigaset.h 2006-02-11 15:20:26.000000000 +0100
> [...]
> >>+#undef info
> >>+#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", THIS_MODULE ? THIS_MODULE->name : "gigaset_hw" , ## arg)
> >
> > Care to use the dev_info(), dev_err() and other dev_* friends instead of
> > rolling your own? It gives you a much easier and standardised way of
> > identifying the driver and individual device that the message is
> > happening for.
>
> This turns out to be surprisingly tricky, as these macros take a device
> pointer argument which mustn't be NULL either.
>
> Could you please advise how to use these when I do not have a device
> pointer available (ie. before the probe method has been called)

That's very hard, and I would not worry about that situation.

> or when there is a risk of it being no longer valid (ie. the USB
> device has been unplugged)?

If it's been unplugged, and you already saved a reference to it with a
call to "usb_get_dev()" you are safe, and can use the pointer. If you
have not called it, then your code needs to be fixed :)

thanks,

greg k-h

2006-02-21 23:01:28

by Greg KH

[permalink] [raw]
Subject: Re: how to handle multi-part patch dependencies (was: [PATCH 1/9] isdn4linux: Siemens Gigaset drivers - Kconfigs and Makefiles)

On Tue, Feb 21, 2006 at 06:16:36PM +0100, Tilman Schmidt wrote:
> Hello Greg,
>
> thank you for your comments. Just a few follow-up questions.
>
> On 15.02.2006 04:19, Greg KH wrote:
>
> > On Sat, Feb 11, 2006 at 03:52:27PM +0100, Hansjoerg Lipp wrote:
> >
> >>From: Tilman Schmidt <[email protected]>, Hansjoerg Lipp <[email protected]>
> >>
> >>This patch prepares the kernel build infrastructure for addition of the
> >>Gigaset ISDN drivers. It creates a Makefile and Kconfig file for the
> >>Gigaset driver and hooks them into those of the isdn4linux subsystem.
> >>It also adds a MAINTAINERS entry for the driver.
> >>
> >>This patch depends on patches 2 to 9 of the present set, as without the
> >>actual source files, activating the options added here will cause the
> >>kernel build to fail.
> >
> >
> > Care to redo that and add the Makefile change at the same time as the
> > driver goes into the tree? We don't want to break the buid for a
> > specific patch.
>
> Could you tell me how to do that? How do I achieve that all parts of a
> patchset go into the tree at the same time?

Make the patch that does the makefile change come after the patch that
adds the file. Or put both parts in the same patch.

thanks,

greg k-h