linux/drivers/isdn/gigaset/ev-layer.c
Tilman Schmidt 81fa7b8257 isdn/gigaset: unify function return values
Various functions in the Gigaset driver were using different
conventions for the meaning of their int return values.
Align them to the usual negative error numbers convention.

Inspired-by: Julia Lawall <julia.lawall@lip6.fr>
Signed-off-by: Tilman Schmidt <tilman@imap.cc>
Signed-off-by: David S. Miller <davem@davemloft.net>
2012-05-07 22:37:56 -04:00

1852 lines
47 KiB
C

/*
* Stuff used by all variants of the driver
*
* Copyright (c) 2001 by Stefan Eilers,
* Hansjoerg Lipp <hjlipp@web.de>,
* Tilman Schmidt <tilman@imap.cc>.
*
* =====================================================================
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* =====================================================================
*/
#include <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 {
unsigned 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 {
unsigned char *str;
int code;
} zsau_resp[] =
{
{"OUTGOING_CALL_PROCEEDING", ZSAU_OUTGOING_CALL_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}
};
/* retrieve CID from parsed response
* returns 0 if no CID, -1 if invalid CID, or CID value 1..65535
*/
static int cid_of_response(char *s)
{
int cid;
int rc;
if (s[-1] != ';')
return 0; /* no CID separator */
rc = kstrtoint(s, 10, &cid);
if (rc)
return 0; /* CID not numeric */
if (cid < 1 || cid > 65535)
return -1; /* CID out of range */
return cid;
}
/**
* 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)
{
unsigned char *argv[MAX_REC_PARAMS + 1];
int params;
int i, j;
const struct resp_type_t *rt;
const struct zsau_resp_t *zr;
int curarg;
unsigned long flags;
unsigned next, tail, head;
struct event_t *event;
int resp_code;
int param_type;
int abort;
size_t len;
int cid;
int rawstring;
len = cs->cbytes;
if (!len) {
/* ignore additional LFs/CRs (M10x config mode or cx100) */
gig_dbg(DEBUG_MCMD, "skipped EOL [%02X]", cs->respdata[len]);
return;
}
cs->respdata[len] = 0;
argv[0] = cs->respdata;
params = 1;
if (cs->at_state.getstring) {
/* getstring only allowed without cid at the moment */
cs->at_state.getstring = 0;
rawstring = 1;
cid = 0;
} else {
/* parse line */
for (i = 0; i < len; i++)
switch (cs->respdata[i]) {
case ';':
case ',':
case '=':
if (params > MAX_REC_PARAMS) {
dev_warn(cs->dev,
"too many parameters in response\n");
/* need last parameter (might be CID) */
params--;
}
argv[params++] = cs->respdata + i + 1;
}
rawstring = 0;
cid = params > 1 ? cid_of_response(argv[params - 1]) : 0;
if (cid < 0) {
gigaset_add_event(cs, &cs->at_state, RSP_INVAL,
NULL, 0, NULL);
return;
}
for (j = 1; j < params; ++j)
argv[j][-1] = 0;
gig_dbg(DEBUG_EVENT, "CMD received: %s", argv[0]);
if (cid) {
--params;
gig_dbg(DEBUG_EVENT, "CID: %s", argv[params]);
}
gig_dbg(DEBUG_EVENT, "available params: %d", params - 1);
for (j = 1; j < params; j++)
gig_dbg(DEBUG_EVENT, "param %d: %s", j, argv[j]);
}
spin_lock_irqsave(&cs->ev_lock, flags);
head = cs->ev_head;
tail = cs->ev_tail;
abort = 1;
curarg = 0;
while (curarg < params) {
next = (tail + 1) % MAX_EVENTS;
if (unlikely(next == head)) {
dev_err(cs->dev, "event queue full\n");
break;
}
event = cs->events + tail;
event->at_state = NULL;
event->cid = cid;
event->ptr = NULL;
event->arg = NULL;
tail = next;
if (rawstring) {
resp_code = RSP_STRING;
param_type = RT_STRING;
} else {
for (rt = resp_type; rt->response; ++rt)
if (!strcmp(argv[curarg], rt->response))
break;
if (!rt->response) {
event->type = RSP_NONE;
gig_dbg(DEBUG_EVENT,
"unknown modem response: '%s'\n",
argv[curarg]);
break;
}
resp_code = rt->resp_code;
param_type = rt->type;
++curarg;
}
event->type = resp_code;
switch (param_type) {
case RT_NOTHING:
break;
case RT_RING:
if (!cid) {
dev_err(cs->dev,
"received RING without CID!\n");
event->type = RSP_INVAL;
abort = 1;
} else {
event->cid = 0;
event->parameter = cid;
abort = 0;
}
break;
case RT_ZSAU:
if (curarg >= params) {
event->parameter = ZSAU_NONE;
break;
}
for (zr = zsau_resp; zr->str; ++zr)
if (!strcmp(argv[curarg], zr->str))
break;
event->parameter = zr->code;
if (!zr->str)
dev_warn(cs->dev,
"%s: unknown parameter %s after ZSAU\n",
__func__, argv[curarg]);
++curarg;
break;
case RT_STRING:
if (curarg < params) {
event->ptr = kstrdup(argv[curarg], GFP_ATOMIC);
if (!event->ptr)
dev_err(cs->dev, "out of memory\n");
++curarg;
}
gig_dbg(DEBUG_EVENT, "string==%s",
event->ptr ? (char *) event->ptr : "NULL");
break;
case RT_ZCAU:
event->parameter = -1;
if (curarg + 1 < params) {
u8 type, value;
i = kstrtou8(argv[curarg++], 16, &type);
j = kstrtou8(argv[curarg++], 16, &value);
if (i == 0 && j == 0)
event->parameter = (type << 8) | value;
} else
curarg = params - 1;
break;
case RT_NUMBER:
if (curarg >= params ||
kstrtoint(argv[curarg++], 10, &event->parameter))
event->parameter = -1;
gig_dbg(DEBUG_EVENT, "parameter==%d", event->parameter);
break;
}
if (resp_code == RSP_ZDLE)
cs->dle = event->parameter;
if (abort)
break;
}
cs->ev_tail = tail;
spin_unlock_irqrestore(&cs->ev_lock, flags);
if (curarg != params)
gig_dbg(DEBUG_EVENT,
"invalid number of processed parameters: %d/%d",
curarg, params);
}
EXPORT_SYMBOL_GPL(gigaset_handle_modem_response);
/* disconnect
* process closing of connection associated with given AT state structure
*/
static void disconnect(struct at_state_t **at_state_p)
{
unsigned long flags;
struct bc_state *bcs = (*at_state_p)->bcs;
struct cardstate *cs = (*at_state_p)->cs;
spin_lock_irqsave(&cs->lock, flags);
++(*at_state_p)->seq_index;
/* revert to selected idle mode */
if (!cs->cidmode) {
cs->at_state.pending_commands |= PC_UMMODE;
gig_dbg(DEBUG_EVENT, "Scheduling PC_UMMODE");
cs->commands_pending = 1;
}
spin_unlock_irqrestore(&cs->lock, flags);
if (bcs) {
/* B channel assigned: 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);
}
} else {
/* no B channel assigned: just deallocate */
spin_lock_irqsave(&cs->lock, flags);
list_del(&(*at_state_p)->list);
kfree(*at_state_p);
*at_state_p = NULL;
spin_unlock_irqrestore(&cs->lock, flags);
}
}
/* get_free_channel
* get a free AT state structure: either one of those associated with the
* B channels of the Gigaset device, or if none of those is available,
* a newly allocated one with bcs=NULL
* The structure should be freed by calling disconnect() after use.
*/
static inline struct at_state_t *get_free_channel(struct cardstate *cs,
int cid)
/* cids: >0: siemens-cid
* 0: without cid
* -1: no cid assigned yet
*/
{
unsigned long flags;
int i;
struct at_state_t *ret;
for (i = 0; i < cs->channels; ++i)
if (gigaset_get_channel(cs->bcs + i) >= 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;
}
/* Add "AT" to a command, add the cid, dle encode it, send the result to the
hardware. */
static void send_command(struct cardstate *cs, const char *cmd, int cid,
int dle, gfp_t kmallocflags)
{
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, kmallocflags);
if (!cb) {
dev_err(cs->dev, "%s: out of memory\n", __func__);
return;
}
if (cid > 0 && cid <= 65535)
cb->len = snprintf(cb->buf, buflen,
dle ? "\020(AT%d%s\020)" : "AT%d%s",
cid, cmd);
else
cb->len = snprintf(cb->buf, buflen,
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 user mode. */
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 **p_at_state)
{
int retval;
struct at_state_t *at_state = *p_at_state;
retval = gigaset_isdn_icall(at_state);
switch (retval) {
case ICALL_ACCEPT:
break;
default:
dev_err(cs->dev, "internal error: disposition=%d\n", retval);
/* --v-- fall through --v-- */
case ICALL_IGNORE:
case ICALL_REJECT:
/* hang up actively
* Device doc says that would reject the call.
* In fact it doesn't.
*/
at_state->pending_commands |= PC_HUP;
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 at_state_t *at_state2;
unsigned long flags;
int channel;
unsigned char *s, *e;
int i;
unsigned long val;
switch (action) {
case ACT_NOTHING:
break;
case ACT_TIMEOUT:
at_state->waiting = 1;
break;
case ACT_INIT:
cs->at_state.pending_commands &= ~PC_INIT;
cs->cur_at_seq = SEQ_NONE;
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_state2 = get_free_channel(cs, ev->parameter);
if (!at_state2) {
dev_warn(cs->dev,
"RING ignored: could not allocate channel structure\n");
break;
}
/* initialize AT state structure
* note that bcs may be NULL if no B channel is free
*/
at_state2->ConState = 700;
for (i = 0; i < STR_NUM; ++i) {
kfree(at_state2->str_var[i]);
at_state2->str_var[i] = NULL;
}
at_state2->int_var[VAR_ZCTP] = -1;
spin_lock_irqsave(&cs->lock, flags);
at_state2->timer_expires = RING_TIMEOUT;
at_state2->timer_active = 1;
spin_unlock_irqrestore(&cs->lock, flags);
break;
case ACT_ICALL:
handle_icall(cs, bcs, p_at_state);
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 && 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(p_at_state);
break;
case ACT_FAKEDLE0:
at_state->int_var[VAR_ZDLE] = 0;
cs->dle = 0;
/* fall through */
case ACT_DLE0:
cs->cur_at_seq = SEQ_NONE;
at_state2 = &cs->bcs[cs->curchannel].at_state;
disconnect(&at_state2);
break;
case ACT_ABORTHUP:
cs->cur_at_seq = SEQ_NONE;
dev_warn(cs->dev, "Could not hang up.\n");
at_state->cid = -1;
if (bcs && cs->onechannel)
at_state->pending_commands |= PC_DLE0;
else
disconnect(p_at_state);
schedule_init(cs, MS_RECOVER);
break;
case ACT_FAILDLE0:
cs->cur_at_seq = SEQ_NONE;
dev_warn(cs->dev, "Could not leave DLE mode.\n");
at_state2 = &cs->bcs[cs->curchannel].at_state;
disconnect(&at_state2);
schedule_init(cs, MS_RECOVER);
break;
case ACT_FAILDLE1:
cs->cur_at_seq = SEQ_NONE;
dev_warn(cs->dev,
"Could not enter DLE mode. Trying to hang up.\n");
channel = cs->curchannel;
cs->bcs[channel].at_state.pending_commands |= PC_HUP;
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;
}
/* 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");
at_state2 = &cs->bcs[channel].at_state;
disconnect(&at_state2);
}
break;
case ACT_ABORTCID:
cs->cur_at_seq = SEQ_NONE;
at_state2 = &cs->bcs[cs->curchannel].at_state;
disconnect(&at_state2);
break;
case ACT_DIALING:
case ACT_ACCEPTED:
cs->cur_at_seq = SEQ_NONE;
break;
case ACT_ABORTACCEPT: /* hangup/error/timeout during ICALL procssng */
disconnect(p_at_state);
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;
}
/*at_state->getstring = 1;*/
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:
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;
int sendcid;
struct at_state_t *at_state;
int index;
int curact;
unsigned long flags;
if (ev->cid >= 0) {
at_state = at_state_from_cid(cs, ev->cid);
if (!at_state) {
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;
sendcid = at_state->cid;
/* Setting the pointer to the dial array */
rep = at_state->replystruct;
spin_lock_irqsave(&cs->lock, flags);
if (ev->type == EV_TIMEOUT) {
if (ev->parameter != at_state->timer_index
|| !at_state->timer_active) {
ev->type = RSP_NONE; /* old timeout */
gig_dbg(DEBUG_EVENT, "old timeout");
} else if (!at_state->waiting)
gig_dbg(DEBUG_EVENT, "timeout occurred");
else
gig_dbg(DEBUG_EVENT, "stopped waiting");
}
spin_unlock_irqrestore(&cs->lock, flags);
/* if the response belongs to a variable in at_state->int_var[VAR_XXXX]
or at_state->str_var[STR_XXXX], set it */
if (ev->type >= RSP_VAR && ev->type < RSP_VAR + VAR_NUM) {
index = ev->type - RSP_VAR;
at_state->int_var[index] = ev->parameter;
} else if (ev->type >= RSP_STR && ev->type < RSP_STR + STR_NUM) {
index = ev->type - RSP_STR;
kfree(at_state->str_var[index]);
at_state->str_var[index] = ev->ptr;
ev->ptr = NULL; /* prevent process_events() from
deallocating ptr */
}
if (ev->type == EV_TIMEOUT || ev->type == RSP_STRING)
at_state->getstring = 0;
/* Search row in dial array which matches modem response and current
constate */
for (;; rep++) {
rcode = rep->resp_code;
if (rcode == RSP_LAST) {
/* found nothing...*/
dev_warn(cs->dev, "%s: rcode=RSP_LAST: "
"resp_code %d in ConState %d!\n",
__func__, ev->type, at_state->ConState);
return;
}
if ((rcode == RSP_ANY || rcode == ev->type)
&& ((int) at_state->ConState >= rep->min_ConState)
&& (rep->max_ConState < 0
|| (int) at_state->ConState <= rep->max_ConState)
&& (rep->parameter < 0 || rep->parameter == ev->parameter))
break;
}
p_command = rep->command;
at_state->waiting = 0;
for (curact = 0; curact < MAXACT; ++curact) {
/* The row tells us what we should do ..
*/
do_action(rep->action[curact], cs, bcs, &at_state, &p_command,
&genresp, &resp_code, ev);
if (!at_state)
break; /* may be freed after disconnect */
}
if (at_state) {
/* Jump to the next con-state regarding the array */
if (rep->new_ConState >= 0)
at_state->ConState = rep->new_ConState;
if (genresp) {
spin_lock_irqsave(&cs->lock, flags);
at_state->timer_expires = 0;
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,
sendcid, cs->dle,
GFP_ATOMIC);
else
gigaset_add_event(cs, at_state,
RSP_NODEV,
NULL, 0, NULL);
}
spin_lock_irqsave(&cs->lock, flags);
if (!rep->timeout) {
at_state->timer_expires = 0;
at_state->timer_active = 0;
} else if (rep->timeout > 0) { /* new timeout */
at_state->timer_expires = rep->timeout * 10;
at_state->timer_active = 1;
++at_state->timer_index;
}
spin_unlock_irqrestore(&cs->lock, flags);
}
}
}
static void schedule_sequence(struct cardstate *cs,
struct at_state_t *at_state, int sequence)
{
cs->cur_at_seq = sequence;
gigaset_add_event(cs, at_state, RSP_INIT, NULL, sequence, NULL);
}
static void process_command_flags(struct cardstate *cs)
{
struct at_state_t *at_state = NULL;
struct bc_state *bcs;
int i;
int sequence;
unsigned long flags;
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) {
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);
}