/* * 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); }