| /* | 
|  * 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 <linux/export.h> | 
| #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_ZCAU        6 | 
|   | 
| /* Possible ASCII responses */ | 
| #define RSP_OK        0 | 
| #define RSP_ERROR    1 | 
| #define RSP_ZGCI    3 | 
| #define RSP_RING    4 | 
| #define RSP_ZVLS    5 | 
| #define RSP_ZCAU    6 | 
|   | 
| /* responses with values to store in at_state */ | 
| /* - numeric */ | 
| #define RSP_VAR        100 | 
| #define RSP_ZSAU    (RSP_VAR + VAR_ZSAU) | 
| #define RSP_ZDLE    (RSP_VAR + VAR_ZDLE) | 
| #define RSP_ZCTP    (RSP_VAR + VAR_ZCTP) | 
| /* - string */ | 
| #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_WRONG_CID    -2    /* unknown cid in cmd */ | 
| #define RSP_INVAL    -6    /* invalid response   */ | 
| #define RSP_NODEV    -9    /* device not connected */ | 
|   | 
| #define RSP_NONE    -19 | 
| #define RSP_STRING    -20 | 
| #define RSP_NULL    -21 | 
| #define RSP_INIT    -27 | 
| #define RSP_ANY        -26 | 
| #define RSP_LAST    -28 | 
|   | 
| /* 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_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[] = | 
| { | 
| /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, | 
|  * action, command */ | 
|   | 
| /* initialize device, set cid mode if possible */ | 
|     {RSP_INIT,     -1,  -1, SEQ_INIT,    100,  1, {ACT_TIMEOUT} }, | 
|   | 
|     {EV_TIMEOUT,    100, 100, -1,        101,  3, {0},    "Z\r"}, | 
|     {RSP_OK,    101, 103, -1,        120,  5, {ACT_GETSTRING}, | 
|                                 "+GMR\r"}, | 
|   | 
|     {EV_TIMEOUT,    101, 101, -1,        102,  5, {0},    "Z\r"}, | 
|     {RSP_ERROR,    101, 101, -1,        102,  5, {0},    "Z\r"}, | 
|   | 
|     {EV_TIMEOUT,    102, 102, -1,        108,  5, {ACT_SETDLE1}, | 
|                                 "^SDLE=0\r"}, | 
|     {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} }, | 
|     {EV_TIMEOUT,    105, 105, -1,        103,  5, {0},    "Z\r"}, | 
|   | 
|     {RSP_ERROR,    102, 102, -1,        107,  5, {0},    "^GETPRE\r"}, | 
|     {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} }, | 
|     {RSP_NONE,    121, 121, -1,        120,  0, {ACT_GETSTRING} }, | 
|   | 
| /* 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,          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, 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},    "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 */ | 
|     {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} }, | 
|     {EV_IF_LOCK,     -1,  -1, -1,         -1, -1, {ACT_IF_LOCK} }, | 
|     {EV_IF_VER,     -1,  -1, -1,         -1, -1, {ACT_IF_VER} }, | 
|     {EV_START,     -1,  -1, -1,         -1, -1, {ACT_START} }, | 
|     {EV_STOP,     -1,  -1, -1,         -1, -1, {ACT_STOP} }, | 
|     {EV_SHUTDOWN,     -1,  -1, -1,         -1, -1, {ACT_SHUTDOWN} }, | 
|   | 
| /* misc. */ | 
|     {RSP_ERROR,     -1,  -1, -1,         -1, -1, {ACT_ERROR} }, | 
|     {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[] = | 
| { | 
| /* resp_code, min_ConState, max_ConState, parameter, new_ConState, timeout, | 
|  * action, command */ | 
|   | 
| /* dial */ | 
|     {EV_DIAL,     -1,  -1, -1,         -1, -1, {ACT_DIAL} }, | 
|     {RSP_INIT,      0,   0, SEQ_DIAL,    601,  5, {ACT_CMD + AT_BC} }, | 
|     {RSP_OK,    601, 601, -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_NULL,    605, 605, -1,        606,  5, {ACT_CMD + AT_CLIP} }, | 
|     {RSP_OK,    605, 605, -1,        606,  5, {ACT_CMD + AT_CLIP} }, | 
|     {RSP_NULL,    606, 606, -1,        607,  5, {ACT_CMD + AT_ISO} }, | 
|     {RSP_OK,    606, 606, -1,        607,  5, {ACT_CMD + AT_ISO} }, | 
|     {RSP_OK,    607, 607, -1,        608,  5, {0},    "+VLS=17\r"}, | 
|     {RSP_OK,    608, 608, -1,        609, -1}, | 
|     {RSP_ZSAU,    609, 609, ZSAU_PROCEEDING, 610, 5, {ACT_CMD + AT_DIAL} }, | 
|     {RSP_OK,    610, 610, -1,        650,  0, {ACT_DIALING} }, | 
|   | 
|     {RSP_ERROR,    601, 610, -1,          0,  0, {ACT_ABORTDIAL} }, | 
|     {EV_TIMEOUT,    601, 610, -1,          0,  0, {ACT_ABORTDIAL} }, | 
|   | 
| /* optional dialing responses */ | 
|     {EV_BC_OPEN,    650, 650, -1,        651, -1}, | 
|     {RSP_ZVLS,    609, 651, 17,         -1, -1, {ACT_DEBUG} }, | 
|     {RSP_ZCTP,    610, 651, -1,         -1, -1, {ACT_DEBUG} }, | 
|     {RSP_ZCPN,    610, 651, -1,         -1, -1, {ACT_DEBUG} }, | 
|     {RSP_ZSAU,    650, 651, ZSAU_CALL_DELIVERED, -1, -1, {ACT_DEBUG} }, | 
|   | 
| /* connect */ | 
|     {RSP_ZSAU,    650, 650, ZSAU_ACTIVE,    800, -1, {ACT_CONNECT} }, | 
|     {RSP_ZSAU,    651, 651, ZSAU_ACTIVE,    800, -1, {ACT_CONNECT, | 
|                               ACT_NOTIFY_BC_UP} }, | 
|     {RSP_ZSAU,    750, 750, ZSAU_ACTIVE,    800, -1, {ACT_CONNECT} }, | 
|     {RSP_ZSAU,    751, 751, ZSAU_ACTIVE,    800, -1, {ACT_CONNECT, | 
|                               ACT_NOTIFY_BC_UP} }, | 
|     {EV_BC_OPEN,    800, 800, -1,        800, -1, {ACT_NOTIFY_BC_UP} }, | 
|   | 
| /* remote hangup */ | 
|     {RSP_ZSAU,    650, 651, ZSAU_DISCONNECT_IND, 0, 0, {ACT_REMOTEREJECT} }, | 
|     {RSP_ZSAU,    750, 751, 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} }, | 
|     {RSP_INIT,     -1,  -1, SEQ_HUP,    401,  5, {0},    "+VLS=0\r"}, | 
|     {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} }, | 
|     {RSP_ZSAU,    403, 403, ZSAU_NULL,      0,  0, {ACT_DISCONNECT} }, | 
|     {RSP_NODEV,    401, 403, -1,          0,  0, {ACT_FAKEHUP} }, | 
|     {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} }, | 
|   | 
| /* 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} }, | 
|     {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"}, | 
|     {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_BC_OPEN,    750, 750, -1,        751, -1}, | 
|     {EV_TIMEOUT,    750, 751, -1,          0,  0, {ACT_CONNTIMEOUT} }, | 
|   | 
| /* B channel closed (general case) */ | 
|     {EV_BC_CLOSED,     -1,  -1, -1,         -1, -1, {ACT_NOTIFY_BC_DOWN} }, | 
|   | 
| /* misc. */ | 
|     {RSP_ZCON,     -1,  -1, -1,         -1, -1, {ACT_DEBUG} }, | 
|     {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} | 
| }; | 
|   | 
|   | 
| static const struct resp_type_t { | 
|     char    *response; | 
|     int    resp_code; | 
|     int    type; | 
| } | 
| resp_type[] = | 
| { | 
|     {"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}, | 
|     {"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}, | 
|     {NULL,        0,        0} | 
| }; | 
|   | 
| static const struct zsau_resp_t { | 
|     char    *str; | 
|     int    code; | 
| } | 
| zsau_resp[] = | 
| { | 
|     {"OUTGOING_CALL_PROCEEDING",    ZSAU_PROCEEDING}, | 
|     {"CALL_DELIVERED",        ZSAU_CALL_DELIVERED}, | 
|     {"ACTIVE",            ZSAU_ACTIVE}, | 
|     {"DISCONNECT_IND",        ZSAU_DISCONNECT_IND}, | 
|     {"NULL",            ZSAU_NULL}, | 
|     {"DISCONNECT_REQ",        ZSAU_DISCONNECT_REQ}, | 
|     {NULL,                ZSAU_UNKNOWN} | 
| }; | 
|   | 
| /* check for and remove fixed string prefix | 
|  * If s starts with prefix terminated by a non-alphanumeric character, | 
|  * return pointer to the first character after that, otherwise return NULL. | 
|  */ | 
| static char *skip_prefix(char *s, const char *prefix) | 
| { | 
|     while (*prefix) | 
|         if (*s++ != *prefix++) | 
|             return NULL; | 
|     if (isalnum(*s)) | 
|         return NULL; | 
|     return s; | 
| } | 
|   | 
| /* queue event with CID */ | 
| static void add_cid_event(struct cardstate *cs, int cid, int type, | 
|               void *ptr, int parameter) | 
| { | 
|     unsigned long flags; | 
|     unsigned next, tail; | 
|     struct event_t *event; | 
|   | 
|     gig_dbg(DEBUG_EVENT, "queueing event %d for cid %d", type, cid); | 
|   | 
|     spin_lock_irqsave(&cs->ev_lock, flags); | 
|   | 
|     tail = cs->ev_tail; | 
|     next = (tail + 1) % MAX_EVENTS; | 
|     if (unlikely(next == cs->ev_head)) { | 
|         dev_err(cs->dev, "event queue full\n"); | 
|         kfree(ptr); | 
|     } else { | 
|         event = cs->events + tail; | 
|         event->type = type; | 
|         event->cid = cid; | 
|         event->ptr = ptr; | 
|         event->arg = NULL; | 
|         event->parameter = parameter; | 
|         event->at_state = NULL; | 
|         cs->ev_tail = next; | 
|     } | 
|   | 
|     spin_unlock_irqrestore(&cs->ev_lock, flags); | 
| } | 
|   | 
| /** | 
|  * gigaset_handle_modem_response() - process received modem response | 
|  * @cs:        device descriptor structure. | 
|  * | 
|  * Called by asyncdata/isocdata if a block of data received from the | 
|  * device must be processed as a modem command response. The data is | 
|  * already in the cs structure. | 
|  */ | 
| void gigaset_handle_modem_response(struct cardstate *cs) | 
| { | 
|     char *eoc, *psep, *ptr; | 
|     const struct resp_type_t *rt; | 
|     const struct zsau_resp_t *zr; | 
|     int cid, parameter; | 
|     u8 type, value; | 
|   | 
|     if (!cs->cbytes) { | 
|         /* ignore additional LFs/CRs (M10x config mode or cx100) */ | 
|         gig_dbg(DEBUG_MCMD, "skipped EOL [%02X]", cs->respdata[0]); | 
|         return; | 
|     } | 
|     cs->respdata[cs->cbytes] = 0; | 
|   | 
|     if (cs->at_state.getstring) { | 
|         /* state machine wants next line verbatim */ | 
|         cs->at_state.getstring = 0; | 
|         ptr = kstrdup(cs->respdata, GFP_ATOMIC); | 
|         gig_dbg(DEBUG_EVENT, "string==%s", ptr ? ptr : "NULL"); | 
|         add_cid_event(cs, 0, RSP_STRING, ptr, 0); | 
|         return; | 
|     } | 
|   | 
|     /* look up response type */ | 
|     for (rt = resp_type; rt->response; ++rt) { | 
|         eoc = skip_prefix(cs->respdata, rt->response); | 
|         if (eoc) | 
|             break; | 
|     } | 
|     if (!rt->response) { | 
|         add_cid_event(cs, 0, RSP_NONE, NULL, 0); | 
|         gig_dbg(DEBUG_EVENT, "unknown modem response: '%s'\n", | 
|             cs->respdata); | 
|         return; | 
|     } | 
|   | 
|     /* check for CID */ | 
|     psep = strrchr(cs->respdata, ';'); | 
|     if (psep && | 
|         !kstrtoint(psep + 1, 10, &cid) && | 
|         cid >= 1 && cid <= 65535) { | 
|         /* valid CID: chop it off */ | 
|         *psep = 0; | 
|     } else { | 
|         /* no valid CID: leave unchanged */ | 
|         cid = 0; | 
|     } | 
|   | 
|     gig_dbg(DEBUG_EVENT, "CMD received: %s", cs->respdata); | 
|     if (cid) | 
|         gig_dbg(DEBUG_EVENT, "CID: %d", cid); | 
|   | 
|     switch (rt->type) { | 
|     case RT_NOTHING: | 
|         /* check parameter separator */ | 
|         if (*eoc) | 
|             goto bad_param;    /* extra parameter */ | 
|   | 
|         add_cid_event(cs, cid, rt->resp_code, NULL, 0); | 
|         break; | 
|   | 
|     case RT_RING: | 
|         /* check parameter separator */ | 
|         if (!*eoc) | 
|             eoc = NULL;    /* no parameter */ | 
|         else if (*eoc++ != ',') | 
|             goto bad_param; | 
|   | 
|         add_cid_event(cs, 0, rt->resp_code, NULL, cid); | 
|   | 
|         /* process parameters as individual responses */ | 
|         while (eoc) { | 
|             /* look up parameter type */ | 
|             psep = NULL; | 
|             for (rt = resp_type; rt->response; ++rt) { | 
|                 psep = skip_prefix(eoc, rt->response); | 
|                 if (psep) | 
|                     break; | 
|             } | 
|   | 
|             /* all legal parameters are of type RT_STRING */ | 
|             if (!psep || rt->type != RT_STRING) { | 
|                 dev_warn(cs->dev, | 
|                      "illegal RING parameter: '%s'\n", | 
|                      eoc); | 
|                 return; | 
|             } | 
|   | 
|             /* skip parameter value separator */ | 
|             if (*psep++ != '=') | 
|                 goto bad_param; | 
|   | 
|             /* look up end of parameter */ | 
|             eoc = strchr(psep, ','); | 
|             if (eoc) | 
|                 *eoc++ = 0; | 
|   | 
|             /* retrieve parameter value */ | 
|             ptr = kstrdup(psep, GFP_ATOMIC); | 
|   | 
|             /* queue event */ | 
|             add_cid_event(cs, cid, rt->resp_code, ptr, 0); | 
|         } | 
|         break; | 
|   | 
|     case RT_ZSAU: | 
|         /* check parameter separator */ | 
|         if (!*eoc) { | 
|             /* no parameter */ | 
|             add_cid_event(cs, cid, rt->resp_code, NULL, ZSAU_NONE); | 
|             break; | 
|         } | 
|         if (*eoc++ != '=') | 
|             goto bad_param; | 
|   | 
|         /* look up parameter value */ | 
|         for (zr = zsau_resp; zr->str; ++zr) | 
|             if (!strcmp(eoc, zr->str)) | 
|                 break; | 
|         if (!zr->str) | 
|             goto bad_param; | 
|   | 
|         add_cid_event(cs, cid, rt->resp_code, NULL, zr->code); | 
|         break; | 
|   | 
|     case RT_STRING: | 
|         /* check parameter separator */ | 
|         if (*eoc++ != '=') | 
|             goto bad_param; | 
|   | 
|         /* retrieve parameter value */ | 
|         ptr = kstrdup(eoc, GFP_ATOMIC); | 
|   | 
|         /* queue event */ | 
|         add_cid_event(cs, cid, rt->resp_code, ptr, 0); | 
|         break; | 
|   | 
|     case RT_ZCAU: | 
|         /* check parameter separators */ | 
|         if (*eoc++ != '=') | 
|             goto bad_param; | 
|         psep = strchr(eoc, ','); | 
|         if (!psep) | 
|             goto bad_param; | 
|         *psep++ = 0; | 
|   | 
|         /* decode parameter values */ | 
|         if (kstrtou8(eoc, 16, &type) || kstrtou8(psep, 16, &value)) { | 
|             *--psep = ','; | 
|             goto bad_param; | 
|         } | 
|         parameter = (type << 8) | value; | 
|   | 
|         add_cid_event(cs, cid, rt->resp_code, NULL, parameter); | 
|         break; | 
|   | 
|     case RT_NUMBER: | 
|         /* check parameter separator */ | 
|         if (*eoc++ != '=') | 
|             goto bad_param; | 
|   | 
|         /* decode parameter value */ | 
|         if (kstrtoint(eoc, 10, ¶meter)) | 
|             goto bad_param; | 
|   | 
|         /* special case ZDLE: set flag before queueing event */ | 
|         if (rt->resp_code == RSP_ZDLE) | 
|             cs->dle = parameter; | 
|   | 
|         add_cid_event(cs, cid, rt->resp_code, NULL, parameter); | 
|         break; | 
|   | 
| bad_param: | 
|         /* parameter unexpected, incomplete or malformed */ | 
|         dev_warn(cs->dev, "bad parameter in response '%s'\n", | 
|              cs->respdata); | 
|         add_cid_event(cs, cid, rt->resp_code, NULL, -1); | 
|         break; | 
|   | 
|     default: | 
|         dev_err(cs->dev, "%s: internal error on '%s'\n", | 
|             __func__, cs->respdata); | 
|     } | 
| } | 
| EXPORT_SYMBOL_GPL(gigaset_handle_modem_response); | 
|   | 
| /* disconnect_nobc | 
|  * process closing of connection associated with given AT state structure | 
|  * without B channel | 
|  */ | 
| static void disconnect_nobc(struct at_state_t **at_state_p, | 
|                 struct cardstate *cs) | 
| { | 
|     unsigned long flags; | 
|   | 
|     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; | 
|         gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE"); | 
|         cs->commands_pending = 1; | 
|     } | 
|   | 
|     /* check for and deallocate temporary AT state */ | 
|     if (!list_empty(&(*at_state_p)->list)) { | 
|         list_del(&(*at_state_p)->list); | 
|         kfree(*at_state_p); | 
|         *at_state_p = NULL; | 
|     } | 
|   | 
|     spin_unlock_irqrestore(&cs->lock, flags); | 
| } | 
|   | 
| /* disconnect_bc | 
|  * process closing of connection associated with given AT state structure | 
|  * and B channel | 
|  */ | 
| static void disconnect_bc(struct at_state_t *at_state, | 
|               struct cardstate *cs, struct bc_state *bcs) | 
| { | 
|     unsigned long flags; | 
|   | 
|     spin_lock_irqsave(&cs->lock, flags); | 
|     ++at_state->seq_index; | 
|   | 
|     /* revert to selected idle mode */ | 
|     if (!cs->cidmode) { | 
|         cs->at_state.pending_commands |= PC_UMMODE; | 
|         gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE"); | 
|         cs->commands_pending = 1; | 
|     } | 
|     spin_unlock_irqrestore(&cs->lock, flags); | 
|   | 
|     /* invoke hardware specific handler */ | 
|     cs->ops->close_bchannel(bcs); | 
|   | 
|     /* notify LL */ | 
|     if (bcs->chstate & (CHS_D_UP | CHS_NOTIFY_LL)) { | 
|         bcs->chstate &= ~(CHS_D_UP | CHS_NOTIFY_LL); | 
|         gigaset_isdn_hupD(bcs); | 
|     } | 
| } | 
|   | 
| /* 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_nobc() 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) >= 0) { | 
|             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; | 
|     cs->mode = mode; | 
|     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; | 
|             cs->commands_pending = 1; | 
|         } | 
|     } | 
| } | 
|   | 
| static void schedule_init(struct cardstate *cs, int state) | 
| { | 
|     if (cs->at_state.pending_commands & PC_INIT) { | 
|         gig_dbg(DEBUG_EVENT, "not scheduling PC_INIT again"); | 
|         return; | 
|     } | 
|     cs->mstate = state; | 
|     cs->mode = M_UNKNOWN; | 
|     gigaset_block_channels(cs); | 
|     cs->at_state.pending_commands |= PC_INIT; | 
|     gig_dbg(DEBUG_EVENT, "Scheduling PC_INIT"); | 
|     cs->commands_pending = 1; | 
| } | 
|   | 
| /* send an AT command | 
|  * adding the "AT" prefix, cid and DLE encapsulation as appropriate | 
|  */ | 
| static void send_command(struct cardstate *cs, const char *cmd, | 
|              struct at_state_t *at_state) | 
| { | 
|     int cid = at_state->cid; | 
|     struct cmdbuf_t *cb; | 
|     size_t buflen; | 
|   | 
|     buflen = strlen(cmd) + 12; /* DLE ( A T 1 2 3 4 5 <cmd> DLE ) \0 */ | 
|     cb = kmalloc(sizeof(struct cmdbuf_t) + buflen, GFP_ATOMIC); | 
|     if (!cb) { | 
|         dev_err(cs->dev, "%s: out of memory\n", __func__); | 
|         return; | 
|     } | 
|     if (cid > 0 && cid <= 65535) | 
|         cb->len = snprintf(cb->buf, buflen, | 
|                    cs->dle ? "\020(AT%d%s\020)" : "AT%d%s", | 
|                    cid, cmd); | 
|     else | 
|         cb->len = snprintf(cb->buf, buflen, | 
|                    cs->dle ? "\020(AT%s\020)" : "AT%s", | 
|                    cmd); | 
|     cb->offset = 0; | 
|     cb->next = NULL; | 
|     cb->wake_tasklet = NULL; | 
|     cs->ops->write_cmd(cs, cb); | 
| } | 
|   | 
| 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_isdn_hupB(bcs); | 
|     } | 
|   | 
|     if (bcs->chstate & (CHS_D_UP | CHS_NOTIFY_LL)) { | 
|         bcs->chstate &= ~(CHS_D_UP | CHS_NOTIFY_LL); | 
|         gigaset_isdn_hupD(bcs); | 
|     } | 
|   | 
|     gigaset_free_channel(bcs); | 
|   | 
|     gigaset_bcs_reinit(bcs); | 
| } | 
|   | 
| static void bchannel_up(struct bc_state *bcs) | 
| { | 
|     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_isdn_connB(bcs); | 
| } | 
|   | 
| 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; | 
|     char **commands = data; | 
|     unsigned long flags; | 
|     int i; | 
|   | 
|     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); | 
|   | 
|     for (i = 0; i < AT_NUM; ++i) { | 
|         kfree(bcs->commands[i]); | 
|         bcs->commands[i] = commands[i]; | 
|     } | 
|   | 
|     at_state->pending_commands |= PC_CID; | 
|     gig_dbg(DEBUG_EVENT, "Scheduling PC_CID"); | 
|     cs->commands_pending = 1; | 
|     return; | 
|   | 
| error: | 
|     for (i = 0; i < AT_NUM; ++i) { | 
|         kfree(commands[i]); | 
|         commands[i] = NULL; | 
|     } | 
|     at_state->pending_commands |= PC_NOCID; | 
|     gig_dbg(DEBUG_EVENT, "Scheduling PC_NOCID"); | 
|     cs->commands_pending = 1; | 
|     return; | 
| } | 
|   | 
| static void start_accept(struct at_state_t *at_state) | 
| { | 
|     struct cardstate *cs = at_state->cs; | 
|     struct bc_state *bcs = at_state->bcs; | 
|     int i; | 
|   | 
|     for (i = 0; i < AT_NUM; ++i) { | 
|         kfree(bcs->commands[i]); | 
|         bcs->commands[i] = NULL; | 
|     } | 
|   | 
|     bcs->commands[AT_PROTO] = kmalloc(9, GFP_ATOMIC); | 
|     bcs->commands[AT_ISO] = kmalloc(9, GFP_ATOMIC); | 
|     if (!bcs->commands[AT_PROTO] || !bcs->commands[AT_ISO]) { | 
|         dev_err(at_state->cs->dev, "out of memory\n"); | 
|         /* error reset */ | 
|         at_state->pending_commands |= PC_HUP; | 
|         gig_dbg(DEBUG_EVENT, "Scheduling PC_HUP"); | 
|         cs->commands_pending = 1; | 
|         return; | 
|     } | 
|   | 
|     snprintf(bcs->commands[AT_PROTO], 9, "^SBPR=%u\r", bcs->proto2); | 
|     snprintf(bcs->commands[AT_ISO], 9, "^SISO=%u\r", bcs->channel + 1); | 
|   | 
|     at_state->pending_commands |= PC_ACCEPT; | 
|     gig_dbg(DEBUG_EVENT, "Scheduling PC_ACCEPT"); | 
|     cs->commands_pending = 1; | 
| } | 
|   | 
| static void do_start(struct cardstate *cs) | 
| { | 
|     gigaset_free_channels(cs); | 
|   | 
|     if (cs->mstate != MS_LOCKED) | 
|         schedule_init(cs, MS_INIT); | 
|   | 
|     cs->isdn_up = 1; | 
|     gigaset_isdn_start(cs); | 
|   | 
|     cs->waiting = 0; | 
|     wake_up(&cs->waitqueue); | 
| } | 
|   | 
| static void finish_shutdown(struct cardstate *cs) | 
| { | 
|     if (cs->mstate != MS_LOCKED) { | 
|         cs->mstate = MS_UNINITIALIZED; | 
|         cs->mode = M_UNKNOWN; | 
|     } | 
|   | 
|     /* Tell the LL that the device is not available .. */ | 
|     if (cs->isdn_up) { | 
|         cs->isdn_up = 0; | 
|         gigaset_isdn_stop(cs); | 
|     } | 
|   | 
|     /* The rest is done by cleanup_cs() in process context. */ | 
|   | 
|     cs->cmd_result = -ENODEV; | 
|     cs->waiting = 0; | 
|     wake_up(&cs->waitqueue); | 
| } | 
|   | 
| static void do_shutdown(struct cardstate *cs) | 
| { | 
|     gigaset_block_channels(cs); | 
|   | 
|     if (cs->mstate == MS_READY) { | 
|         cs->mstate = MS_SHUTDOWN; | 
|         cs->at_state.pending_commands |= PC_SHUTDOWN; | 
|         gig_dbg(DEBUG_EVENT, "Scheduling PC_SHUTDOWN"); | 
|         cs->commands_pending = 1; | 
|     } 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 success, <0 on failure | 
|  */ | 
| static int reinit_and_retry(struct cardstate *cs, int channel) | 
| { | 
|     int i; | 
|   | 
|     if (--cs->retry_count <= 0) | 
|         return -EFAULT; | 
|   | 
|     for (i = 0; i < cs->channels; ++i) | 
|         if (cs->bcs[i].at_state.cid > 0) | 
|             return -EBUSY; | 
|   | 
|     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 0; | 
| } | 
|   | 
| 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 *at_state) | 
| { | 
|     int retval; | 
|   | 
|     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; | 
|         cs->commands_pending = 1; | 
|         break; | 
|     } | 
| } | 
|   | 
| static int do_lock(struct cardstate *cs) | 
| { | 
|     int mode; | 
|     int i; | 
|   | 
|     switch (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) < 0) | 
|             return -EBUSY; | 
|   | 
|         break; | 
|     case MS_LOCKED: | 
|         break; | 
|     default: | 
|         return -EBUSY; | 
|     } | 
|   | 
|     mode = cs->mode; | 
|     cs->mstate = MS_LOCKED; | 
|     cs->mode = M_UNKNOWN; | 
|   | 
|     return mode; | 
| } | 
|   | 
| static int do_unlock(struct cardstate *cs) | 
| { | 
|     if (cs->mstate != MS_LOCKED) | 
|         return -EINVAL; | 
|   | 
|     cs->mstate = MS_UNINITIALIZED; | 
|     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 bc_state *bcs2; | 
|     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; | 
|         cs->mode = M_UNIMODEM; | 
|         spin_lock_irqsave(&cs->lock, flags); | 
|         if (!cs->cidmode) { | 
|             spin_unlock_irqrestore(&cs->lock, flags); | 
|             gigaset_free_channels(cs); | 
|             cs->mstate = MS_READY; | 
|             break; | 
|         } | 
|         spin_unlock_irqrestore(&cs->lock, flags); | 
|         cs->at_state.pending_commands |= PC_CIDMODE; | 
|         gig_dbg(DEBUG_EVENT, "Scheduling PC_CIDMODE"); | 
|         cs->commands_pending = 1; | 
|         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 (cs->mstate == MS_INIT || cs->mstate == MS_RECOVER) { | 
|             gigaset_free_channels(cs); | 
|             cs->mstate = MS_READY; | 
|         } | 
|         cs->mode = M_CID; | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         break; | 
|     case ACT_UMODESET: | 
|         cs->mode = M_UNIMODEM; | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         break; | 
|     case ACT_FAILCMODE: | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         if (cs->mstate == MS_INIT || cs->mstate == MS_RECOVER) { | 
|             init_failed(cs, M_UNKNOWN); | 
|             break; | 
|         } | 
|         if (reinit_and_retry(cs, -1) < 0) | 
|             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) */ | 
|         if (cs->connected) { | 
|             struct cmdbuf_t *cb; | 
|   | 
|             cb = kmalloc(sizeof(struct cmdbuf_t) + 3, GFP_ATOMIC); | 
|             if (!cb) { | 
|                 dev_err(cs->dev, "%s: out of memory\n", | 
|                     __func__); | 
|                 return; | 
|             } | 
|             memcpy(cb->buf, "+++", 3); | 
|             cb->len = 3; | 
|             cb->offset = 0; | 
|             cb->next = NULL; | 
|             cb->wake_tasklet = NULL; | 
|             cs->ops->write_cmd(cs, cb); | 
|         } | 
|         break; | 
|     case ACT_RING: | 
|         /* get fresh AT state structure for new CID */ | 
|         at_state = get_free_channel(cs, ev->parameter); | 
|         if (!at_state) { | 
|             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_state->ConState = 700; | 
|         for (i = 0; i < STR_NUM; ++i) { | 
|             kfree(at_state->str_var[i]); | 
|             at_state->str_var[i] = NULL; | 
|         } | 
|         at_state->int_var[VAR_ZCTP] = -1; | 
|   | 
|         spin_lock_irqsave(&cs->lock, flags); | 
|         at_state->timer_expires = RING_TIMEOUT; | 
|         at_state->timer_active = 1; | 
|         spin_unlock_irqrestore(&cs->lock, flags); | 
|         break; | 
|     case ACT_ICALL: | 
|         handle_icall(cs, bcs, 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; | 
|             cs->commands_pending = 1; | 
|             break; | 
|         } | 
|         bcs->chstate |= CHS_D_UP; | 
|         gigaset_isdn_connD(bcs); | 
|         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_isdn_connD(bcs); | 
|         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) { | 
|             disconnect_nobc(p_at_state, cs); | 
|         } else if (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; | 
|             cs->commands_pending = 1; | 
|         } else { | 
|             disconnect_bc(at_state, cs, bcs); | 
|         } | 
|         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; | 
|         bcs2 = cs->bcs + cs->curchannel; | 
|         disconnect_bc(&bcs2->at_state, cs, bcs2); | 
|         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) | 
|             disconnect_nobc(p_at_state, cs); | 
|         else if (cs->onechannel) | 
|             at_state->pending_commands |= PC_DLE0; | 
|         else | 
|             disconnect_bc(at_state, cs, bcs); | 
|         schedule_init(cs, MS_RECOVER); | 
|         break; | 
|     case ACT_FAILDLE0: | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         dev_warn(cs->dev, "Error leaving DLE mode.\n"); | 
|         cs->dle = 0; | 
|         bcs2 = cs->bcs + cs->curchannel; | 
|         disconnect_bc(&bcs2->at_state, cs, bcs2); | 
|         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; | 
|         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; | 
|             cs->commands_pending = 1; | 
|             break; | 
|         } | 
|         /* bad cid: fall through */ | 
|     case ACT_FAILCID: | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         channel = cs->curchannel; | 
|         if (reinit_and_retry(cs, channel) < 0) { | 
|             dev_warn(cs->dev, | 
|                  "Could not get a call ID. Cannot dial.\n"); | 
|             bcs2 = cs->bcs + channel; | 
|             disconnect_bc(&bcs2->at_state, cs, bcs2); | 
|         } | 
|         break; | 
|     case ACT_ABORTCID: | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         bcs2 = cs->bcs + cs->curchannel; | 
|         disconnect_bc(&bcs2->at_state, cs, bcs2); | 
|         break; | 
|   | 
|     case ACT_DIALING: | 
|     case ACT_ACCEPTED: | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         break; | 
|   | 
|     case ACT_ABORTACCEPT:    /* hangup/error/timeout during ICALL procssng */ | 
|         if (bcs) | 
|             disconnect_bc(at_state, cs, bcs); | 
|         else | 
|             disconnect_nobc(p_at_state, cs); | 
|         break; | 
|   | 
|     case ACT_ABORTDIAL:    /* error/timeout during dial preparation */ | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         at_state->pending_commands |= PC_HUP; | 
|         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; | 
|         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")) { | 
|             /* OK without version string: assume old response */ | 
|             *p_genresp = 1; | 
|             *p_resp_code = RSP_NONE; | 
|             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; | 
|         } | 
|         cs->gotfwver = 0; | 
|         break; | 
|     case ACT_GOTVER: | 
|         if (cs->gotfwver == 0) { | 
|             cs->gotfwver = 1; | 
|             gig_dbg(DEBUG_EVENT, | 
|                 "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; | 
|     case ACT_ERROR: | 
|         gig_dbg(DEBUG_ANY, "%s: ERROR response in ConState %d", | 
|             __func__, at_state->ConState); | 
|         cs->cur_at_seq = SEQ_NONE; | 
|         break; | 
|     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: | 
|         if (!ev->ptr) { | 
|             *p_genresp = 1; | 
|             *p_resp_code = RSP_ERROR; | 
|             break; | 
|         } | 
|         start_dial(at_state, ev->ptr, ev->parameter); | 
|         break; | 
|     case ACT_ACCEPT: | 
|         start_accept(at_state); | 
|         break; | 
|     case ACT_HUP: | 
|         at_state->pending_commands |= PC_HUP; | 
|         gig_dbg(DEBUG_EVENT, "Scheduling PC_HUP"); | 
|         cs->commands_pending = 1; | 
|         break; | 
|   | 
|     /* hotplug events */ | 
|   | 
|     case ACT_STOP: | 
|         do_stop(cs); | 
|         break; | 
|     case ACT_START: | 
|         do_start(cs); | 
|         break; | 
|   | 
|     /* events from the interface */ | 
|   | 
|     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 */ | 
|   | 
|     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_EVENT, "Scheduling PC_CIDMODE"); | 
|             } else { | 
|                 cs->at_state.pending_commands |= PC_UMMODE; | 
|                 gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE"); | 
|             } | 
|             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; | 
|     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) { | 
|             gig_dbg(DEBUG_EVENT, "event %d for invalid cid %d", | 
|                 ev->type, ev->cid); | 
|             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_EVENT, "event for invalid at_state %p", | 
|                 at_state); | 
|             return; | 
|         } | 
|     } | 
|   | 
|     gig_dbg(DEBUG_EVENT, "connection state %d, event %d", | 
|         at_state->ConState, ev->type); | 
|   | 
|     bcs = at_state->bcs; | 
|   | 
|     /* 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_EVENT, "old timeout"); | 
|         } else { | 
|             if (at_state->waiting) | 
|                 gig_dbg(DEBUG_EVENT, "stopped waiting"); | 
|             else | 
|                 gig_dbg(DEBUG_EVENT, "timeout occurred"); | 
|         } | 
|     } | 
|     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) | 
|             /* at_state destroyed by disconnect */ | 
|             return; | 
|     } | 
|   | 
|     /* 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; | 
|         at_state->timer_active = 0; | 
|         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) { | 
|             if (cs->connected) | 
|                 send_command(cs, p_command, at_state); | 
|             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; | 
|   | 
|     cs->commands_pending = 0; | 
|   | 
|     if (cs->cur_at_seq) { | 
|         gig_dbg(DEBUG_EVENT, "not searching scheduled commands: busy"); | 
|         return; | 
|     } | 
|   | 
|     gig_dbg(DEBUG_EVENT, "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 (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) | 
|         && 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) { | 
|             if (cs->dle) { | 
|                 cs->curchannel = bcs->channel; | 
|                 schedule_sequence(cs, &cs->at_state, SEQ_DLE0); | 
|                 return; | 
|             } | 
|             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; | 
|         cs->inbuf->inputstate = INS_command; | 
|         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 (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 (cs->mode) { | 
|             case M_UNIMODEM: | 
|                 cs->at_state.pending_commands |= PC_CIDMODE; | 
|                 gig_dbg(DEBUG_EVENT, "Scheduling PC_CIDMODE"); | 
|                 cs->commands_pending = 1; | 
|                 return; | 
|             case M_UNKNOWN: | 
|                 schedule_init(cs, MS_INIT); | 
|                 return; | 
|             } | 
|             bcs->at_state.pending_commands &= ~PC_CID; | 
|             cs->curchannel = bcs->channel; | 
|             cs->retry_count = 2; | 
|             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 && !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 (!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 (cs->inbuf->head != cs->inbuf->tail) { | 
|         gig_dbg(DEBUG_INTR, "processing new data"); | 
|         cs->ops->handle_input(cs->inbuf); | 
|     } | 
|   | 
|     process_events(cs); | 
| } |