diff options
Diffstat (limited to 'drivers/staging/isdn/gigaset/bas-gigaset.c')
-rw-r--r-- | drivers/staging/isdn/gigaset/bas-gigaset.c | 2675 |
1 files changed, 2675 insertions, 0 deletions
diff --git a/drivers/staging/isdn/gigaset/bas-gigaset.c b/drivers/staging/isdn/gigaset/bas-gigaset.c new file mode 100644 index 000000000000..149b1aca52a2 --- /dev/null +++ b/drivers/staging/isdn/gigaset/bas-gigaset.c @@ -0,0 +1,2675 @@ +/* + * USB driver for Gigaset 307x base via direct USB connection. + * + * Copyright (c) 2001 by Hansjoerg Lipp <hjlipp@web.de>, + * Tilman Schmidt <tilman@imap.cc>, + * Stefan Eilers. + * + * ===================================================================== + * 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. + * ===================================================================== + */ + +#include "gigaset.h" +#include <linux/usb.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +/* Version Information */ +#define DRIVER_AUTHOR "Tilman Schmidt <tilman@imap.cc>, Hansjoerg Lipp <hjlipp@web.de>, Stefan Eilers" +#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_DEVNAME "ttyGB" + +/* length limit according to Siemens 3070usb-protokoll.doc ch. 2.1 */ +#define IF_WRITEBUF 264 + +/* interrupt pipe message size according to ibid. ch. 2.2 */ +#define IP_MSGSIZE 3 + +/* Values for the Gigaset 307x */ +#define USB_GIGA_VENDOR_ID 0x0681 +#define USB_3070_PRODUCT_ID 0x0001 +#define USB_3075_PRODUCT_ID 0x0002 +#define USB_SX303_PRODUCT_ID 0x0021 +#define USB_SX353_PRODUCT_ID 0x0022 + +/* table of devices that work with this driver */ +static const struct usb_device_id gigaset_table[] = { + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_3070_PRODUCT_ID) }, + { USB_DEVICE(USB_GIGA_VENDOR_ID, USB_3075_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); + +/*======================= local function prototypes ==========================*/ + +/* function called if a new device belonging to this driver is connected */ +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); + +/* functions called before/after suspend */ +static int gigaset_suspend(struct usb_interface *intf, pm_message_t message); +static int gigaset_resume(struct usb_interface *intf); + +/* functions called before/after device reset */ +static int gigaset_pre_reset(struct usb_interface *intf); +static int gigaset_post_reset(struct usb_interface *intf); + +static int atread_submit(struct cardstate *, int); +static void stopurbs(struct bas_bc_state *); +static int req_submit(struct bc_state *, int, int, int); +static int atwrite_submit(struct cardstate *, unsigned char *, int); +static int start_cbsend(struct cardstate *); + +/*============================================================================*/ + +struct bas_cardstate { + struct usb_device *udev; /* USB device pointer */ + struct cardstate *cs; + 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 */ + int retry_ctrl; + + 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; + struct work_struct int_in_wq; /* for usb_clear_halt() */ + struct timer_list timer_int_in; /* int read retry delay */ + int retry_int_in; + + spinlock_t lock; /* locks all following */ + int basstate; /* bitmap (BS_*) */ + int pending; /* uncompleted base request */ + wait_queue_head_t waitqueue; + 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 /* AT channel open */ +#define BS_B1OPEN 0x002 /* B channel 1 open */ +#define BS_B2OPEN 0x004 /* B channel 2 open */ +#define BS_ATREADY 0x008 /* base ready for AT command */ +#define BS_INIT 0x010 /* base has signalled INIT_OK */ +#define BS_ATTIMER 0x020 /* waiting for HD_READY_SEND_ATDATA */ +#define BS_ATRDPEND 0x040 /* urb_cmd_in in use */ +#define BS_ATWRPEND 0x080 /* urb_cmd_out in use */ +#define BS_SUSPEND 0x100 /* USB port suspended */ +#define BS_RESETTING 0x200 /* waiting for HD_RESET_INTERRUPT_PIPE_ACK */ + + +static struct gigaset_driver *driver; + +/* 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, + .suspend = gigaset_suspend, + .resume = gigaset_resume, + .reset_resume = gigaset_post_reset, + .pre_reset = gigaset_pre_reset, + .post_reset = gigaset_post_reset, + .disable_hub_initiated_lpm = 1, +}; + +/* get message text for usb_submit_urb return code + */ +static char *get_usb_rcmsg(int rc) +{ + static char unkmsg[28]; + + switch (rc) { + case 0: + return "success"; + case -ENOMEM: + return "out of memory"; + case -ENODEV: + return "device not present"; + case -ENOENT: + return "endpoint not present"; + case -ENXIO: + return "URB type not supported"; + case -EINVAL: + return "invalid argument"; + case -EAGAIN: + return "start frame too early or too much scheduled"; + case -EFBIG: + return "too many isoc frames requested"; + case -EPIPE: + return "endpoint stalled"; + case -EMSGSIZE: + return "invalid packet size"; + case -ENOSPC: + return "would overcommit USB bandwidth"; + case -ESHUTDOWN: + return "device shut down"; + case -EPERM: + return "reject flag set"; + case -EHOSTUNREACH: + return "device suspended"; + default: + snprintf(unkmsg, sizeof(unkmsg), "unknown error %d", rc); + return unkmsg; + } +} + +/* 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 "unlinked (sync)"; + case -EINPROGRESS: + return "URB still pending"; + case -EPROTO: + return "bitstuff error, timeout, or unknown USB error"; + case -EILSEQ: + return "CRC mismatch, timeout, or unknown USB error"; + case -ETIME: + return "USB response timeout"; + case -EPIPE: + return "endpoint stalled"; + case -ECOMM: + return "IN buffer overrun"; + case -ENOSR: + return "OUT buffer underrun"; + case -EOVERFLOW: + return "endpoint babble"; + case -EREMOTEIO: + return "short packet"; + case -ENODEV: + return "device removed"; + case -EXDEV: + return "partial isoc transfer"; + case -EINVAL: + return "ISO madness"; + case -ECONNRESET: + return "unlinked (async)"; + case -ESHUTDOWN: + return "device shut down"; + default: + snprintf(unkmsg, sizeof(unkmsg), "unknown status %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; + gig_dbg(level, "%s urb(0x%08lx)->{", tag, (unsigned long) urb); + if (urb) { + gig_dbg(level, + " dev=0x%08lx, pipe=%s:EP%d/DV%d:%s, " + "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", + (unsigned long) urb->hcpriv, + urb->transfer_flags); + gig_dbg(level, + " transfer_buffer=0x%08lx[%d], actual_length=%d, " + "setup_packet=0x%08lx,", + (unsigned long) urb->transfer_buffer, + urb->transfer_buffer_length, urb->actual_length, + (unsigned long) urb->setup_packet); + gig_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); + gig_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]; + gig_dbg(level, + " {offset=%u, length=%u, actual_length=%u, " + "status=%u}", + pifd->offset, pifd->length, pifd->actual_length, + pifd->status); + } + } + gig_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; +} + +/* set/clear bits in base connection state, return previous state + */ +static inline int update_basstate(struct bas_cardstate *ucs, + int set, int clear) +{ + unsigned long flags; + int state; + + spin_lock_irqsave(&ucs->lock, flags); + state = ucs->basstate; + ucs->basstate = (state & ~clear) | set; + spin_unlock_irqrestore(&ucs->lock, flags); + return state; +} + +/* 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. + * cs->lock must not be held. + * argument: + * B channel control structure + */ +static inline void error_hangup(struct bc_state *bcs) +{ + struct cardstate *cs = bcs->cs; + + gigaset_add_event(cs, &bcs->at_state, EV_HUP, NULL, 0, NULL); + 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. + * cs->hw.bas->lock must not be held. + * argument: + * controller state structure + */ +static inline void error_reset(struct cardstate *cs) +{ + /* reset interrupt pipe to recover (ignore errors) */ + update_basstate(cs->hw.bas, BS_RESETTING, 0); + if (req_submit(cs->bcs, HD_RESET_INTERRUPT_PIPE, 0, BAS_TIMEOUT)) + /* submission failed, escalate to USB port reset */ + usb_queue_reset_device(cs->hw.bas->interface); +} + +/* check_pending + * check for completion of pending control request + * parameter: + * ucs hardware specific controller state structure + */ +static void check_pending(struct bas_cardstate *ucs) +{ + unsigned long flags; + + spin_lock_irqsave(&ucs->lock, flags); + switch (ucs->pending) { + case 0: + break; + case HD_OPEN_ATCHANNEL: + if (ucs->basstate & BS_ATOPEN) + ucs->pending = 0; + break; + case HD_OPEN_B1CHANNEL: + if (ucs->basstate & BS_B1OPEN) + ucs->pending = 0; + break; + case HD_OPEN_B2CHANNEL: + if (ucs->basstate & BS_B2OPEN) + ucs->pending = 0; + break; + case HD_CLOSE_ATCHANNEL: + if (!(ucs->basstate & BS_ATOPEN)) + ucs->pending = 0; + break; + case HD_CLOSE_B1CHANNEL: + if (!(ucs->basstate & BS_B1OPEN)) + ucs->pending = 0; + break; + case HD_CLOSE_B2CHANNEL: + if (!(ucs->basstate & BS_B2OPEN)) + ucs->pending = 0; + break; + case HD_DEVICE_INIT_ACK: /* no reply expected */ + ucs->pending = 0; + break; + case HD_RESET_INTERRUPT_PIPE: + if (!(ucs->basstate & BS_RESETTING)) + ucs->pending = 0; + break; + /* + * HD_READ_ATMESSAGE and HD_WRITE_ATMESSAGE are handled separately + * and should never end up here + */ + default: + dev_warn(&ucs->interface->dev, + "unknown pending request 0x%02x cleared\n", + 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(struct timer_list *t) +{ + struct bas_cardstate *ucs = from_timer(ucs, t, timer_cmd_in); + struct cardstate *cs = ucs->cs; + int rc; + + if (!ucs->rcvbuf_size) { + gig_dbg(DEBUG_USBREQ, "%s: no receive in progress", __func__); + return; + } + + if (ucs->retry_cmd_in++ >= BAS_RETRY) { + dev_err(cs->dev, + "control read: timeout, giving up after %d tries\n", + ucs->retry_cmd_in); + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; + error_reset(cs); + return; + } + + gig_dbg(DEBUG_USBREQ, "%s: timeout, retry %d", + __func__, ucs->retry_cmd_in); + rc = atread_submit(cs, BAS_TIMEOUT); + if (rc < 0) { + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; + if (rc != -ENODEV) + 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 inbuf_t *inbuf = urb->context; + struct cardstate *cs = inbuf->cs; + struct bas_cardstate *ucs = cs->hw.bas; + int status = urb->status; + unsigned numbytes; + int rc; + + update_basstate(ucs, 0, BS_ATRDPEND); + wake_up(&ucs->waitqueue); + del_timer(&ucs->timer_cmd_in); + + switch (status) { + case 0: /* normal completion */ + numbytes = urb->actual_length; + if (unlikely(numbytes != ucs->rcvbuf_size)) { + dev_warn(cs->dev, + "control read: received %d chars, expected %d\n", + numbytes, ucs->rcvbuf_size); + if (numbytes > ucs->rcvbuf_size) + numbytes = ucs->rcvbuf_size; + } + + /* copy received bytes to inbuf, notify event layer */ + if (gigaset_fill_inbuf(inbuf, ucs->rcvbuf, numbytes)) { + gig_dbg(DEBUG_INTR, "%s-->BH", __func__); + gigaset_schedule_event(cs); + } + break; + + case -ENOENT: /* cancelled */ + case -ECONNRESET: /* cancelled (async) */ + case -EINPROGRESS: /* pending */ + case -ENODEV: /* device removed */ + case -ESHUTDOWN: /* device shut down */ + /* no further action necessary */ + gig_dbg(DEBUG_USBREQ, "%s: %s", + __func__, get_usb_statmsg(status)); + break; + + default: /* other errors: retry */ + if (ucs->retry_cmd_in++ < BAS_RETRY) { + gig_dbg(DEBUG_USBREQ, "%s: %s, retry %d", __func__, + get_usb_statmsg(status), ucs->retry_cmd_in); + rc = atread_submit(cs, BAS_TIMEOUT); + if (rc >= 0) + /* successfully resubmitted, skip freeing */ + return; + if (rc == -ENODEV) + /* disconnect, no further action necessary */ + break; + } + dev_err(cs->dev, "control read: %s, giving up after %d tries\n", + get_usb_statmsg(status), ucs->retry_cmd_in); + error_reset(cs); + } + + /* read finished, free buffer */ + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; +} + +/* atread_submit + * submit an HD_READ_ATMESSAGE command URB and optionally start a timeout + * parameters: + * cs controller state structure + * timeout timeout in 1/10 sec., 0: none + * return value: + * 0 on success + * -EBUSY if another request is pending + * any URB submission error code + */ +static int atread_submit(struct cardstate *cs, int timeout) +{ + struct bas_cardstate *ucs = cs->hw.bas; + int basstate; + int ret; + + gig_dbg(DEBUG_USBREQ, "-------> HD_READ_ATMESSAGE (%d)", + ucs->rcvbuf_size); + + basstate = update_basstate(ucs, BS_ATRDPEND, 0); + if (basstate & BS_ATRDPEND) { + dev_err(cs->dev, + "could not submit HD_READ_ATMESSAGE: URB busy\n"); + return -EBUSY; + } + + if (basstate & BS_SUSPEND) { + dev_notice(cs->dev, + "HD_READ_ATMESSAGE not submitted, " + "suspend in progress\n"); + update_basstate(ucs, 0, BS_ATRDPEND); + /* treat like disconnect */ + return -ENODEV; + } + + 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); + + ret = usb_submit_urb(ucs->urb_cmd_in, GFP_ATOMIC); + if (ret != 0) { + update_basstate(ucs, 0, BS_ATRDPEND); + dev_err(cs->dev, "could not submit HD_READ_ATMESSAGE: %s\n", + get_usb_rcmsg(ret)); + return ret; + } + + if (timeout > 0) { + gig_dbg(DEBUG_USBREQ, "setting timeout of %d/10 secs", timeout); + mod_timer(&ucs->timer_cmd_in, jiffies + timeout * HZ / 10); + } + return 0; +} + +/* int_in_work + * workqueue routine to clear halt on interrupt in endpoint + */ + +static void int_in_work(struct work_struct *work) +{ + struct bas_cardstate *ucs = + container_of(work, struct bas_cardstate, int_in_wq); + struct urb *urb = ucs->urb_int_in; + struct cardstate *cs = urb->context; + int rc; + + /* clear halt condition */ + rc = usb_clear_halt(ucs->udev, urb->pipe); + gig_dbg(DEBUG_USBREQ, "clear_halt: %s", get_usb_rcmsg(rc)); + if (rc == 0) + /* success, resubmit interrupt read URB */ + rc = usb_submit_urb(urb, GFP_ATOMIC); + + switch (rc) { + case 0: /* success */ + case -ENODEV: /* device gone */ + case -EINVAL: /* URB already resubmitted, or terminal badness */ + break; + default: /* failure: try to recover by resetting the device */ + dev_err(cs->dev, "clear halt failed: %s\n", get_usb_rcmsg(rc)); + rc = usb_lock_device_for_reset(ucs->udev, ucs->interface); + if (rc == 0) { + rc = usb_reset_device(ucs->udev); + usb_unlock_device(ucs->udev); + } + } + ucs->retry_int_in = 0; +} + +/* int_in_resubmit + * timer routine for interrupt read delayed resubmit + * argument: + * controller state structure + */ +static void int_in_resubmit(struct timer_list *t) +{ + struct bas_cardstate *ucs = from_timer(ucs, t, timer_int_in); + struct cardstate *cs = ucs->cs; + int rc; + + if (ucs->retry_int_in++ >= BAS_RETRY) { + dev_err(cs->dev, "interrupt read: giving up after %d tries\n", + ucs->retry_int_in); + usb_queue_reset_device(ucs->interface); + return; + } + + gig_dbg(DEBUG_USBREQ, "%s: retry %d", __func__, ucs->retry_int_in); + rc = usb_submit_urb(ucs->urb_int_in, GFP_ATOMIC); + if (rc != 0 && rc != -ENODEV) { + dev_err(cs->dev, "could not resubmit interrupt URB: %s\n", + get_usb_rcmsg(rc)); + usb_queue_reset_device(ucs->interface); + } +} + +/* 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 cardstate *cs = urb->context; + struct bas_cardstate *ucs = cs->hw.bas; + struct bc_state *bcs; + int status = urb->status; + unsigned long flags; + int rc; + unsigned l; + int channel; + + switch (status) { + case 0: /* success */ + ucs->retry_int_in = 0; + break; + case -EPIPE: /* endpoint stalled */ + schedule_work(&ucs->int_in_wq); + /* fall through */ + case -ENOENT: /* cancelled */ + case -ECONNRESET: /* cancelled (async) */ + case -EINPROGRESS: /* pending */ + case -ENODEV: /* device removed */ + case -ESHUTDOWN: /* device shut down */ + /* no further action necessary */ + gig_dbg(DEBUG_USBREQ, "%s: %s", + __func__, get_usb_statmsg(status)); + return; + case -EPROTO: /* protocol error or unplug */ + case -EILSEQ: + case -ETIME: + /* resubmit after delay */ + gig_dbg(DEBUG_USBREQ, "%s: %s", + __func__, get_usb_statmsg(status)); + mod_timer(&ucs->timer_int_in, jiffies + HZ / 10); + return; + default: /* other errors: just resubmit */ + dev_warn(cs->dev, "interrupt read: %s\n", + get_usb_statmsg(status)); + goto resubmit; + } + + /* drop incomplete packets even if the missing bytes wouldn't matter */ + if (unlikely(urb->actual_length < IP_MSGSIZE)) { + dev_warn(cs->dev, "incomplete interrupt packet (%d bytes)\n", + urb->actual_length); + goto resubmit; + } + + l = (unsigned) ucs->int_in_buf[1] + + (((unsigned) ucs->int_in_buf[2]) << 8); + + gig_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; + /* fall through */ + 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; + /* fall through */ + 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; + /* fall through */ + case HD_B1_FLOW_CONTROL: + bcs = cs->bcs + channel; + atomic_add((l - BAS_NORMFRAME) * BAS_CORRFRAMES, + &bcs->hw.bas->corrbytes); + gig_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) { + dev_warn(cs->dev, + "HD_RECEIVEATDATA_ACK with length 0 ignored\n"); + break; + } + spin_lock_irqsave(&cs->lock, flags); + if (ucs->basstate & BS_ATRDPEND) { + spin_unlock_irqrestore(&cs->lock, flags); + dev_warn(cs->dev, + "HD_RECEIVEATDATA_ACK(%d) during HD_READ_ATMESSAGE(%d) ignored\n", + l, ucs->rcvbuf_size); + break; + } + if (ucs->rcvbuf_size) { + /* throw away previous buffer - we have no queue */ + dev_err(cs->dev, + "receive AT data overrun, %d bytes lost\n", + ucs->rcvbuf_size); + kfree(ucs->rcvbuf); + ucs->rcvbuf_size = 0; + } + ucs->rcvbuf = kmalloc(l, GFP_ATOMIC); + if (ucs->rcvbuf == NULL) { + spin_unlock_irqrestore(&cs->lock, flags); + dev_err(cs->dev, "out of memory receiving AT data\n"); + break; + } + ucs->rcvbuf_size = l; + ucs->retry_cmd_in = 0; + rc = atread_submit(cs, BAS_TIMEOUT); + if (rc < 0) { + kfree(ucs->rcvbuf); + ucs->rcvbuf = NULL; + ucs->rcvbuf_size = 0; + } + spin_unlock_irqrestore(&cs->lock, flags); + if (rc < 0 && rc != -ENODEV) + error_reset(cs); + break; + + case HD_RESET_INTERRUPT_PIPE_ACK: + update_basstate(ucs, 0, BS_RESETTING); + dev_notice(cs->dev, "interrupt pipe reset\n"); + break; + + case HD_SUSPEND_END: + gig_dbg(DEBUG_USBREQ, "HD_SUSPEND_END"); + break; + + default: + dev_warn(cs->dev, + "unknown Gigaset signal 0x%02x (%u) ignored\n", + (int) ucs->int_in_buf[0], l); + } + + check_pending(ucs); + wake_up(&ucs->waitqueue); + +resubmit: + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(rc != 0 && rc != -ENODEV)) { + dev_err(cs->dev, "could not resubmit interrupt URB: %s\n", + get_usb_rcmsg(rc)); + error_reset(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 bc_state *bcs; + struct bas_bc_state *ubc; + int status = urb->status; + unsigned long flags; + int i, rc; + + /* status codes not worth bothering the tasklet with */ + if (unlikely(status == -ENOENT || + status == -ECONNRESET || + status == -EINPROGRESS || + status == -ENODEV || + status == -ESHUTDOWN)) { + gig_dbg(DEBUG_ISO, "%s: %s", + __func__, get_usb_statmsg(status)); + return; + } + + bcs = urb->context; + ubc = bcs->hw.bas; + + spin_lock_irqsave(&ubc->isoinlock, flags); + if (likely(ubc->isoindone == NULL)) { + /* pass URB to tasklet */ + ubc->isoindone = urb; + ubc->isoinstatus = status; + tasklet_hi_schedule(&ubc->rcvd_tasklet); + } else { + /* tasklet still busy, drop data and resubmit URB */ + gig_dbg(DEBUG_ISO, "%s: overrun", __func__); + ubc->loststatus = 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(ubc->running)) { + /* urb->dev is clobbered by USB subsystem */ + urb->dev = bcs->cs->hw.bas->udev; + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = BAS_NUMFRAMES; + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(rc != 0 && rc != -ENODEV)) { + dev_err(bcs->cs->dev, + "could not resubmit isoc read URB: %s\n", + get_usb_rcmsg(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 isow_urbctx_t *ucx; + struct bas_bc_state *ubc; + int status = urb->status; + unsigned long flags; + + /* status codes not worth bothering the tasklet with */ + if (unlikely(status == -ENOENT || + status == -ECONNRESET || + status == -EINPROGRESS || + status == -ENODEV || + status == -ESHUTDOWN)) { + gig_dbg(DEBUG_ISO, "%s: %s", + __func__, get_usb_statmsg(status)); + return; + } + + /* pass URB context to tasklet */ + ucx = urb->context; + ubc = ucx->bcs->hw.bas; + ucx->status = status; + + spin_lock_irqsave(&ubc->isooutlock, flags); + ubc->isooutovfl = ubc->isooutdone; + ubc->isooutdone = ucx; + spin_unlock_irqrestore(&ubc->isooutlock, flags); + tasklet_hi_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 usb_device *udev = bcs->cs->hw.bas->udev; + struct bas_bc_state *ubc = bcs->hw.bas; + struct urb *urb; + int j, k; + int rc; + + /* initialize L2 reception */ + if (bcs->proto2 == L2_HDLC) + bcs->inputstate |= INS_flag_hunt; + + /* submit all isochronous input URBs */ + ubc->running = 1; + for (k = 0; k < BAS_INURBS; k++) { + urb = ubc->isoinurbs[k]; + if (!urb) { + rc = -EFAULT; + goto error; + } + usb_fill_int_urb(urb, udev, + usb_rcvisocpipe(udev, 3 + 2 * bcs->channel), + ubc->isoinbuf + k * BAS_INBUFSIZE, + BAS_INBUFSIZE, read_iso_callback, bcs, + BAS_FRAMETIME); + + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = BAS_NUMFRAMES; + 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); + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc != 0) + 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) { + rc = -EFAULT; + goto error; + } + usb_fill_int_urb(urb, udev, + usb_sndisocpipe(udev, 4 + 2 * bcs->channel), + ubc->isooutbuf->data, + sizeof(ubc->isooutbuf->data), + write_iso_callback, &ubc->isoouturbs[k], + BAS_FRAMETIME); + + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = BAS_NUMFRAMES; + 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; + } + + /* keep one URB free, submit the others */ + for (k = 0; k < BAS_OUTURBS - 1; ++k) { + dump_urb(DEBUG_ISO, "Initial isoc write", urb); + rc = usb_submit_urb(ubc->isoouturbs[k].urb, GFP_ATOMIC); + if (rc != 0) + goto error; + } + dump_urb(DEBUG_ISO, "Initial isoc write (free)", urb); + ubc->isooutfree = &ubc->isoouturbs[BAS_OUTURBS - 1]; + 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; + + ubc->running = 0; + + for (k = 0; k < BAS_INURBS; ++k) { + rc = usb_unlink_urb(ubc->isoinurbs[k]); + gig_dbg(DEBUG_ISO, + "%s: isoc input URB %d unlinked, result = %s", + __func__, k, get_usb_rcmsg(rc)); + } + + for (k = 0; k < BAS_OUTURBS; ++k) { + rc = usb_unlink_urb(ubc->isoouturbs[k].urb); + gig_dbg(DEBUG_ISO, + "%s: isoc output URB %d unlinked, result = %s", + __func__, k, get_usb_rcmsg(rc)); + } +} + +/* Isochronous Write - Bottom Half */ +/* =============================== */ + +/* submit_iso_write_urb + * fill and submit the next isochronous write URB + * parameters: + * ucx context structure containing URB + * 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 = ucx->urb; + struct bas_bc_state *ubc = ucx->bcs->hw.bas; + struct usb_iso_packet_descriptor *ifd; + int corrbytes, nframe, rc; + + /* urb->dev is clobbered by USB subsystem */ + urb->dev = ucx->bcs->cs->hw.bas->udev; + 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; + corrbytes = atomic_read(&ubc->corrbytes); + if (corrbytes != 0) { + gig_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); + } + + /* retrieve block of data to send */ + rc = gigaset_isowbuf_getbytes(ubc->isooutbuf, ifd->length); + if (rc < 0) { + if (rc == -EBUSY) { + gig_dbg(DEBUG_ISO, + "%s: buffer busy at frame %d", + __func__, nframe); + /* tasklet will be restarted from + gigaset_isoc_send_skb() */ + } else { + dev_err(ucx->bcs->cs->dev, + "%s: buffer error %d at frame %d\n", + __func__, rc, nframe); + return rc; + } + break; + } + ifd->offset = rc; + ucx->limit = ubc->isooutbuf->nextread; + ifd->status = 0; + ifd->actual_length = 0; + } + if (unlikely(nframe == 0)) + return 0; /* no data to send */ + urb->number_of_packets = nframe; + + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(rc)) { + if (rc == -ENODEV) + /* device removed - give up silently */ + gig_dbg(DEBUG_ISO, "%s: disconnected", __func__); + else + dev_err(ucx->bcs->cs->dev, |