diff options
Diffstat (limited to 'drivers/isdn/gigaset')
-rw-r--r-- | drivers/isdn/gigaset/Kconfig | 42 | ||||
-rw-r--r-- | drivers/isdn/gigaset/Makefile | 6 | ||||
-rw-r--r-- | drivers/isdn/gigaset/asyncdata.c | 597 | ||||
-rw-r--r-- | drivers/isdn/gigaset/bas-gigaset.c | 2365 | ||||
-rw-r--r-- | drivers/isdn/gigaset/common.c | 1203 | ||||
-rw-r--r-- | drivers/isdn/gigaset/ev-layer.c | 1983 | ||||
-rw-r--r-- | drivers/isdn/gigaset/gigaset.h | 938 | ||||
-rw-r--r-- | drivers/isdn/gigaset/i4l.c | 567 | ||||
-rw-r--r-- | drivers/isdn/gigaset/interface.c | 718 | ||||
-rw-r--r-- | drivers/isdn/gigaset/isocdata.c | 1009 | ||||
-rw-r--r-- | drivers/isdn/gigaset/proc.c | 81 | ||||
-rw-r--r-- | drivers/isdn/gigaset/usb-gigaset.c | 1008 |
12 files changed, 10517 insertions, 0 deletions
diff --git a/drivers/isdn/gigaset/Kconfig b/drivers/isdn/gigaset/Kconfig new file mode 100644 index 0000000..53c4fb6 --- /dev/null +++ b/drivers/isdn/gigaset/Kconfig @@ -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 diff --git a/drivers/isdn/gigaset/Makefile b/drivers/isdn/gigaset/Makefile new file mode 100644 index 0000000..9b9acf1 --- /dev/null +++ b/drivers/isdn/gigaset/Makefile @@ -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 diff --git a/drivers/isdn/gigaset/asyncdata.c b/drivers/isdn/gigaset/asyncdata.c new file mode 100644 index 0000000..171f8b7 --- /dev/null +++ b/drivers/isdn/gigaset/asyncdata.c @@ -0,0 +1,597 @@ +/* + * Common data handling layer for ser_gigaset and usb_gigaset + * + * Copyright (c) 2005 by Tilman Schmidt <tilman@imap.cc>, + * Hansjoerg Lipp <hjlipp@web.de>, + * Stefan Eilers <Eilers.Stefan@epost.de>. + * + * ===================================================================== + * 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 */ +} diff --git a/drivers/isdn/gigaset/bas-gigaset.c b/drivers/isdn/gigaset/bas-gigaset.c new file mode 100644 index 0000000..31f0f07 --- /dev/null +++ b/drivers/isdn/gigaset/bas-gigaset.c @@ -0,0 +1,2365 @@ +/* + * 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 <Eilers.Stefan@epost.de>. + * + * 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 <tilman@imap.cc>, Hansjoerg Lipp <hjlipp@web.de>, Stefan Eilers <Eilers.Stefan@epost.de>" +#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"); diff --git a/drivers/isdn/gigaset/common.c b/drivers/isdn/gigaset/common.c new file mode 100644 index 0000000..6437199 --- /dev/null +++ b/drivers/isdn/gigaset/common.c @@ -0,0 +1,1203 @@ +/* + * Stuff used by all variants of the driver + * + * Copyright (c) 2001 by Stefan Eilers <Eilers.Stefan@epost.de>, + * Hansjoerg Lipp <hjlipp@web.de>, + * Tilman Schmidt <tilman@imap.cc>. + * + * ===================================================================== + * 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 <hjlipp@web.de>, Tilman Schmidt <tilman@imap.cc>, Stefan Eilers <Eilers.Stefan@epost.de>" +#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"); diff --git a/drivers/isdn/gigaset/ev-layer.c b/drivers/isdn/gigaset/ev-layer.c new file mode 100644 index 0000000..fdcb80b --- /dev/null +++ b/drivers/isdn/gigaset/ev-layer.c @@ -0,0 +1,1983 @@ +/* + * Stuff used by all variants of the driver + * + * Copyright (c) 2001 by Stefan Eilers <Eilers.Stefan@epost.de>, + * Hansjoerg Lipp <hjlipp@web.de>, + * Tilman Schmidt <tilman@imap.cc>. + * + * ===================================================================== + * 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); +} diff --git a/drivers/isdn/gigaset/gigaset.h b/drivers/isdn/gigaset/gigaset.h new file mode 100644 index 0000000..729edcd --- /dev/null +++ b/drivers/isdn/gigaset/gigaset.h @@ -0,0 +1,938 @@ +/* Siemens Gigaset 307x driver + * Common header file for all connection variants + * + * Written by Stefan Eilers <Eilers.Stefan@epost.de> + * and Hansjoerg Lipp <hjlipp@web.de> + * + * 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 diff --git a/drivers/isdn/gigaset/i4l.c b/drivers/isdn/gigaset/i4l.c new file mode 100644 index 0000000..731a675 --- /dev/null +++ b/drivers/isdn/gigaset/i4l.c @@ -0,0 +1,567 @@ +/* + * Stuff used by all variants of the driver + * + * Copyright (c) 2001 by Stefan Eilers (Eilers.Stefan@epost.de), + * Hansjoerg Lipp (hjlipp@web.de), + * Tilman Schmidt (tilman@imap.cc). + * + * ===================================================================== + * 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; +} diff --git a/drivers/isdn/gigaset/interface.c b/drivers/isdn/gigaset/interface.c new file mode 100644 index 0000000..3a81d9c --- /dev/null +++ b/drivers/isdn/gigaset/interface.c @@ -0,0 +1,718 @@ +/* + * interface to user space for the gigaset driver + * + * Copyright (c) 2004 by Hansjoerg Lipp <hjlipp@web.de> + * + * ===================================================================== + * 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); +} diff --git a/drivers/isdn/gigaset/isocdata.c b/drivers/isdn/gigaset/isocdata.c new file mode 100644 index 0000000..5744eb9 --- /dev/null +++ b/drivers/isdn/gigaset/isocdata.c @@ -0,0 +1,1009 @@ +/* + * Common data handling layer for bas_gigaset + * + * Copyright (c) 2005 by Tilman Schmidt <tilman@imap.cc>, + * Hansjoerg Lipp <hjlipp@web.de>. + * + * ===================================================================== + * 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 */ +} diff --git a/drivers/isdn/gigaset/proc.c b/drivers/isdn/gigaset/proc.c new file mode 100644 index 0000000..c6915fa --- /dev/null +++ b/drivers/isdn/gigaset/proc.c @@ -0,0 +1,81 @@ +/* + * Stuff used by all variants of the driver + * + * Copyright (c) 2001 by Stefan Eilers <Eilers.Stefan@epost.de>, + * Hansjoerg Lipp <hjlipp@web.de>, + * Tilman Schmidt <tilman@imap.cc>. + * + * ===================================================================== + * 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); diff --git a/drivers/isdn/gigaset/usb-gigaset.c b/drivers/isdn/gigaset/usb-gigaset.c new file mode 100644 index 0000000..323fc73 --- /dev/null +++ b/drivers/isdn/gigaset/usb-gigaset.c @@ -0,0 +1,1008 @@ +/* + * USB driver for Gigaset 307x directly or using M105 Data. + * + * Copyright (c) 2001 by Stefan Eilers <Eilers.Stefan@epost.de> + * and Hansjoerg Lipp <hjlipp@web.de>. + * + * This driver was derived from the USB skeleton driver by + * Greg Kroah-Hartman <greg@kroah.com> + * + * ===================================================================== + * 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 <hjlipp@web.de>, Stefan Eilers <Eilers.Stefan@epost.de>" +#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"); |