mirror of
https://github.com/torvalds/linux.git
synced 2024-12-26 04:42:12 +00:00
73a8881454
Fix a possible Oops in the Siemens Gigaset base driver when the device is unplugged while an ISDN connection is still active, and makes sure that the isdn4linux link level (LL) is properly informed if a connection is broken by the USB cable being unplugged. - Avoid unsafe checks of URB status fields outside the URB completion handlers, keep track of in-use URBs myself instead. - If an isochronous transfer URB completes with status==0, also check the status of the frame descriptors. - Verify length of interrupt messages received from the device. - Align the length limit on transmitted AT commands with the device documentation. - In case of AT response receive overrun, keep newly arrived instead of old unread data. - Remove redundant check of device ID in the USB probe function. - Correct and improve some comments and formatting. Signed-off-by: Tilman Schmidt <tilman@imap.cc> Acked-by: Hansjoerg Lipp <hjlipp@web.de> Cc: Karsten Keil <kkeil@suse.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
1986 lines
55 KiB
C
1986 lines
55 KiB
C
/*
|
|
* Stuff used by all variants of the driver
|
|
*
|
|
* Copyright (c) 2001 by Stefan Eilers,
|
|
* 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.
|
|
* =====================================================================
|
|
*/
|
|
|
|
#include "gigaset.h"
|
|
|
|
/* ========================================================== */
|
|
/* bit masks for pending commands */
|
|
#define PC_DIAL 0x001
|
|
#define PC_HUP 0x002
|
|
#define PC_INIT 0x004
|
|
#define PC_DLE0 0x008
|
|
#define PC_DLE1 0x010
|
|
#define PC_SHUTDOWN 0x020
|
|
#define PC_ACCEPT 0x040
|
|
#define PC_CID 0x080
|
|
#define PC_NOCID 0x100
|
|
#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}},
|
|
|
|
/* B channel closed (general case) */
|
|
{EV_BC_CLOSED, -1, -1, -1, -1,-1, {ACT_NOTIFY_BC_DOWN}}, //FIXME
|
|
|
|
/* 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
|
|
{
|
|
/* 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
|
|
{
|
|
/* 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;
|
|
|
|
gig_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;
|
|
|
|
gig_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;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
len = cs->cbytes;
|
|
if (!len) {
|
|
/* ignore additional LFs/CRs (M10x config mode or cx100) */
|
|
gig_dbg(DEBUG_MCMD, "skipped EOL [%02X]", cs->respdata[len]);
|
|
return;
|
|
}
|
|
cs->respdata[len] = 0;
|
|
gig_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) {
|
|
dev_warn(cs->dev,
|
|
"too many parameters in response\n");
|
|
/* 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;
|
|
|
|
gig_dbg(DEBUG_TRANSCMD, "CMD received: %s", argv[0]);
|
|
if (cid) {
|
|
--params;
|
|
gig_dbg(DEBUG_TRANSCMD, "CID: %s", argv[params]);
|
|
}
|
|
gig_dbg(DEBUG_TRANSCMD, "available params: %d", params - 1);
|
|
for (j = 1; j < params; j++)
|
|
gig_dbg(DEBUG_TRANSCMD, "param %d: %s", j, argv[j]);
|
|
}
|
|
|
|
spin_lock_irqsave(&cs->ev_lock, flags);
|
|
head = cs->ev_head;
|
|
tail = cs->ev_tail;
|
|
|
|
abort = 1;
|
|
curarg = 0;
|
|
while (curarg < params) {
|
|
next = (tail + 1) % MAX_EVENTS;
|
|
if (unlikely(next == head)) {
|
|
dev_err(cs->dev, "event queue full\n");
|
|
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;
|
|
dev_warn(cs->dev,
|
|
"unknown modem response: %s\n",
|
|
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) {
|
|
dev_err(cs->dev,
|
|
"received RING without CID!\n");
|
|
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;
|
|
dev_warn(cs->dev,
|
|
"%s: unknown parameter %s after ZSAU\n",
|
|
__func__, argv[curarg]);
|
|
}
|
|
++curarg;
|
|
break;
|
|
case RT_STRING:
|
|
if (curarg < params) {
|
|
event->ptr = kstrdup(argv[curarg], GFP_ATOMIC);
|
|
if (!event->ptr)
|
|
dev_err(cs->dev, "out of memory\n");
|
|
++curarg;
|
|
}
|
|
#ifdef CONFIG_GIGASET_DEBUG
|
|
if (!event->ptr)
|
|
gig_dbg(DEBUG_CMD, "string==NULL");
|
|
else
|
|
gig_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
|
|
gig_dbg(DEBUG_CMD, "parameter==%d", event->parameter);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
if (resp_code == RSP_ZDLE)
|
|
cs->dle = event->parameter;
|
|
|
|
if (abort)
|
|
break;
|
|
}
|
|
|
|
cs->ev_tail = tail;
|
|
spin_unlock_irqrestore(&cs->ev_lock, flags);
|
|
|
|
if (curarg != params)
|
|
gig_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 = (*at_state_p)->bcs;
|
|
struct cardstate *cs = (*at_state_p)->cs;
|
|
|
|
spin_lock_irqsave(&cs->lock, flags);
|
|
++(*at_state_p)->seq_index;
|
|
|
|
/* revert to selected idle mode */
|
|
if (!cs->cidmode) {
|
|
cs->at_state.pending_commands |= PC_UMMODE;
|
|
atomic_set(&cs->commands_pending, 1); //FIXME
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_UMMODE");
|
|
}
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
|
|
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) {
|
|
gig_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);
|
|
gig_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 (unlikely(buflen <= cmdlen)) {
|
|
dev_err(cs->dev, "integer overflow in buflen\n");
|
|
return;
|
|
}
|
|
|
|
cmdbuf = kmalloc(buflen, kmallocflags);
|
|
if (unlikely(!cmdbuf)) {
|
|
dev_err(cs->dev, "out of memory\n");
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
if (!(bcs->chstate & CHS_D_UP)) {
|
|
dev_notice(bcs->cs->dev, "%s: D channel not up\n", __func__);
|
|
bcs->chstate |= CHS_D_UP;
|
|
gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DCONN);
|
|
}
|
|
|
|
if (bcs->chstate & CHS_B_UP) {
|
|
dev_notice(bcs->cs->dev, "%s: B channel already up\n",
|
|
__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, unsigned seq_index)
|
|
{
|
|
struct bc_state *bcs = at_state->bcs;
|
|
struct cardstate *cs = at_state->cs;
|
|
int retval;
|
|
unsigned long flags;
|
|
|
|
bcs->chstate |= CHS_NOTIFY_LL;
|
|
|
|
spin_lock_irqsave(&cs->lock, flags);
|
|
if (at_state->seq_index != seq_index) {
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
goto error;
|
|
}
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
|
|
retval = gigaset_isdn_setup_dial(at_state, data);
|
|
if (retval != 0)
|
|
goto error;
|
|
|
|
|
|
at_state->pending_commands |= PC_CID;
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_CID");
|
|
atomic_set(&cs->commands_pending, 1);
|
|
return;
|
|
|
|
error:
|
|
at_state->pending_commands |= PC_NOCID;
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_NOCID");
|
|
atomic_set(&cs->commands_pending, 1);
|
|
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;
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_ACCEPT");
|
|
atomic_set(&cs->commands_pending, 1);
|
|
} else {
|
|
//FIXME
|
|
at_state->pending_commands |= PC_HUP;
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_HUP");
|
|
atomic_set(&cs->commands_pending, 1);
|
|
}
|
|
}
|
|
|
|
static void do_start(struct cardstate *cs)
|
|
{
|
|
gigaset_free_channels(cs);
|
|
|
|
if (atomic_read(&cs->mstate) != MS_LOCKED)
|
|
schedule_init(cs, MS_INIT);
|
|
|
|
cs->isdn_up = 1;
|
|
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);
|
|
}
|
|
|
|
/* Tell the LL that the device is not available .. */
|
|
if (cs->isdn_up) {
|
|
cs->isdn_up = 0;
|
|
gigaset_i4l_cmd(cs, ISDN_STAT_STOP);
|
|
}
|
|
|
|
/* 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);
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_SHUTDOWN");
|
|
} else
|
|
finish_shutdown(cs);
|
|
}
|
|
|
|
static void do_stop(struct cardstate *cs)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&cs->lock, flags);
|
|
cs->connected = 0;
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
|
|
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)
|
|
dev_warn(cs->dev,
|
|
"Could not enter cid mode. Reinit device and try again.\n");
|
|
else {
|
|
dev_warn(cs->dev,
|
|
"Could not get a call id. Reinit device and try again.\n");
|
|
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:
|
|
dev_err(cs->dev, "internal error: disposition=%d\n", 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);
|
|
|
|
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);
|
|
if (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:
|
|
cs->at_state.pending_commands &= ~PC_INIT;
|
|
cs->cur_at_seq = SEQ_NONE;
|
|
atomic_set(&cs->mode, M_UNIMODEM);
|
|
spin_lock_irqsave(&cs->lock, flags);
|
|
if (!cs->cidmode) {
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
gigaset_free_channels(cs);
|
|
atomic_set(&cs->mstate, MS_READY);
|
|
break;
|
|
}
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
cs->at_state.pending_commands |= PC_CIDMODE;
|
|
atomic_set(&cs->commands_pending, 1);
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_CIDMODE");
|
|
break;
|
|
case ACT_FAILINIT:
|
|
dev_warn(cs->dev, "Could not initialize the device.\n");
|
|
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) {
|
|
dev_warn(cs->dev,
|
|
"RING ignored: could not allocate channel structure\n");
|
|
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:
|
|
dev_warn(cs->dev, "Could not shut down the device.\n");
|
|
/* 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;
|
|
dev_warn(cs->dev, "Could not hang up.\n");
|
|
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;
|
|
dev_warn(cs->dev, "Could not leave DLE mode.\n");
|
|
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;
|
|
dev_warn(cs->dev,
|
|
"Could not enter DLE mode. Trying to hang up.\n");
|
|
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)) {
|
|
dev_warn(cs->dev,
|
|
"Could not get a call ID. Cannot dial.\n");
|
|
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 anymore */
|
|
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;
|
|
gig_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;
|
|
dev_err(cs->dev, "could not read firmware version.\n");
|
|
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:
|
|
gig_dbg(DEBUG_ANY, "%s: resp_code %d in ConState %d",
|
|
__func__, ev->type, at_state->ConState);
|
|
break;
|
|
case ACT_WARN:
|
|
dev_warn(cs->dev, "%s: resp_code %d in ConState %d!\n",
|
|
__func__, ev->type, at_state->ConState);
|
|
break;
|
|
case ACT_ZCAU:
|
|
dev_warn(cs->dev, "cause code %04x in connection state %d.\n",
|
|
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:
|
|
gig_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);
|
|
gig_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:
|
|
spin_lock_irqsave(&cs->lock, flags);
|
|
if (ev->parameter != cs->cidmode) {
|
|
cs->cidmode = ev->parameter;
|
|
if (ev->parameter) {
|
|
cs->at_state.pending_commands |= PC_CIDMODE;
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_CIDMODE");
|
|
} else {
|
|
cs->at_state.pending_commands |= PC_UMMODE;
|
|
gig_dbg(DEBUG_CMD, "Scheduling PC_UMMODE");
|
|
}
|
|
atomic_set(&cs->commands_pending, 1);
|
|
}
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
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
|
|
dev_err(cs->dev, "%s: action==%d!\n", __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;
|
|
|
|
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)) {
|
|
gig_dbg(DEBUG_ANY, "event for invalid at_state %p",
|
|
at_state);
|
|
return;
|
|
}
|
|
}
|
|
|
|
gig_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;
|
|
|
|
spin_lock_irqsave(&cs->lock, flags);
|
|
if (ev->type == EV_TIMEOUT) {
|
|
if (ev->parameter != at_state->timer_index
|
|
|| !at_state->timer_active) {
|
|
ev->type = RSP_NONE; /* old timeout */
|
|
gig_dbg(DEBUG_ANY, "old timeout");
|
|
} else if (!at_state->waiting)
|
|
gig_dbg(DEBUG_ANY, "timeout occurred");
|
|
else
|
|
gig_dbg(DEBUG_ANY, "stopped waiting");
|
|
}
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
|
|
/* 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;
|
|
if (rcode == RSP_LAST) {
|
|
/* found nothing...*/
|
|
dev_warn(cs->dev, "%s: rcode=RSP_LAST: "
|
|
"resp_code %d in ConState %d!\n",
|
|
__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 (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;
|
|
++at_state->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;
|
|
unsigned long flags;
|
|
|
|
atomic_set(&cs->commands_pending, 0);
|
|
|
|
if (cs->cur_at_seq) {
|
|
gig_dbg(DEBUG_CMD, "not searching scheduled commands: busy");
|
|
return;
|
|
}
|
|
|
|
gig_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 */
|
|
spin_lock_irqsave(&cs->lock, flags);
|
|
if (cs->at_state.pending_commands == PC_UMMODE
|
|
&& !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;
|
|
}
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&cs->lock, flags);
|
|
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) {
|
|
cs->retry_count = 1;
|
|
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;
|
|
gig_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;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&cs->ev_lock, flags);
|
|
head = cs->ev_head;
|
|
|
|
for (i = 0; i < 2 * MAX_EVENTS; ++i) {
|
|
tail = cs->ev_tail;
|
|
if (tail == head) {
|
|
if (!check_flags && !atomic_read(&cs->commands_pending))
|
|
break;
|
|
check_flags = 0;
|
|
spin_unlock_irqrestore(&cs->ev_lock, flags);
|
|
process_command_flags(cs);
|
|
spin_lock_irqsave(&cs->ev_lock, flags);
|
|
tail = 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;
|
|
spin_unlock_irqrestore(&cs->ev_lock, flags);
|
|
process_event(cs, ev);
|
|
spin_lock_irqsave(&cs->ev_lock, flags);
|
|
kfree(ev->ptr);
|
|
ev->ptr = NULL;
|
|
if (was_busy && cs->cur_at_seq == SEQ_NONE)
|
|
check_flags = 1;
|
|
|
|
head = (head + 1) % MAX_EVENTS;
|
|
cs->ev_head = head;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&cs->ev_lock, flags);
|
|
|
|
if (i == 2 * MAX_EVENTS) {
|
|
dev_err(cs->dev,
|
|
"infinite loop in process_events; aborting.\n");
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* handle incoming data on control/common channel */
|
|
if (atomic_read(&cs->inbuf->head) != atomic_read(&cs->inbuf->tail)) {
|
|
gig_dbg(DEBUG_INTR, "processing new data");
|
|
cs->ops->handle_input(cs->inbuf);
|
|
}
|
|
|
|
process_events(cs);
|
|
}
|