summaryrefslogtreecommitdiffstats
path: root/src/host/layer23/src/mobile/mnccms.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/host/layer23/src/mobile/mnccms.c')
-rw-r--r--src/host/layer23/src/mobile/mnccms.c122
1 files changed, 120 insertions, 2 deletions
diff --git a/src/host/layer23/src/mobile/mnccms.c b/src/host/layer23/src/mobile/mnccms.c
index 84081e82..4e44bf97 100644
--- a/src/host/layer23/src/mobile/mnccms.c
+++ b/src/host/layer23/src/mobile/mnccms.c
@@ -36,14 +36,36 @@ void *l23_ctx;
static uint32_t new_callref = 1;
static LLIST_HEAD(call_list);
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
+static int dtmf_statemachine(struct gsm_call *call, struct gsm_mncc *mncc);
+static void timeout_dtmf(void *arg);
+
/*
* support functions
*/
-void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
+/* DTMF timer */
+static void start_dtmf_timer(struct gsm_call *call, uint16_t ms)
+{
+ LOGP(DCC, LOGL_INFO, "starting DTMF timer %d ms\n", ms);
+ call->dtmf_timer.cb = timeout_dtmf;
+ call->dtmf_timer.data = call;
+ bsc_schedule_timer(&call->dtmf_timer, 0, ms * 1000);
+}
+
+static void stop_dtmf_timer(struct gsm_call *call)
+{
+ if (bsc_timer_pending(&call->dtmf_timer)) {
+ LOGP(DCC, LOGL_INFO, "stopping pending DTMF timer\n");
+ bsc_del_timer(&call->dtmf_timer);
+ }
+}
+/* free call instance */
static void free_call(struct gsm_call *call)
{
+ stop_dtmf_timer(call);
+
llist_del(&call->entry);
DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref);
talloc_free(call);
@@ -231,6 +253,7 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
call = talloc_zero(l23_ctx, struct gsm_call);
if (!call)
return -ENOMEM;
+ call->ms = ms;
call->callref = data->callref;
llist_add_tail(&call->entry, &call_list);
}
@@ -288,7 +311,8 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
vty_notify(ms, "Congestion\n");
break;
default:
- vty_notify(ms, "Call has been disconnected\n");
+ vty_notify(ms, "Call has been disconnected "
+ "(clear cause %d)\n", data->cause.value);
}
LOGP(DMNCC, LOGL_INFO, "Call has been disconnected "
"(cause %d)\n", data->cause.value);
@@ -452,6 +476,11 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
LOGP(DMNCC, LOGL_INFO, "Facility info not displayed, "
"unsupported\n");
break;
+ case MNCC_START_DTMF_RSP:
+ case MNCC_START_DTMF_REJ:
+ case MNCC_STOP_DTMF_RSP:
+ dtmf_statemachine(call, data);
+ break;
default:
LOGP(DMNCC, LOGL_INFO, "Message 0x%02x unsupported\n",
msg_type);
@@ -479,6 +508,7 @@ int mncc_call(struct osmocom_ms *ms, char *number)
call = talloc_zero(l23_ctx, struct gsm_call);
if (!call)
return -ENOMEM;
+ call->ms = ms;
call->callref = new_callref++;
call->init = 1;
llist_add_tail(&call->entry, &call_list);
@@ -510,6 +540,12 @@ int mncc_call(struct osmocom_ms *ms, char *number)
setup.clir.sup = 1;
else if (ms->settings.clip)
setup.clir.inv = 1;
+
+ /* CC capabilities (optional) */
+ if (ms->settings.cc_dtmf) {
+ setup.fields |= MNCC_F_CCCAP;
+ setup.cccap.dtmf = 1;
+ }
}
return mncc_send(ms, MNCC_SETUP_REQ, &setup);
@@ -644,6 +680,88 @@ int mncc_retrieve(struct osmocom_ms *ms, int number)
return mncc_send(ms, MNCC_RETRIEVE_REQ, &retr);
}
+/*
+ * DTMF
+ */
+
+static int dtmf_statemachine(struct gsm_call *call, struct gsm_mncc *mncc)
+{
+ struct osmocom_ms *ms = call->ms;
+ struct gsm_mncc dtmf;
+
+ switch (call->dtmf_state) {
+ case DTMF_ST_SPACE:
+ case DTMF_ST_IDLE:
+ /* end of string */
+ if (!call->dtmf[call->dtmf_index]) {
+ LOGP(DMNCC, LOGL_INFO, "done with DTMF\n");
+ call->dtmf_state = DTMF_ST_IDLE;
+ return -EOF;
+ }
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = call->callref;
+ dtmf.keypad = call->dtmf[call->dtmf_index++];
+ call->dtmf_state = DTMF_ST_START;
+ LOGP(DMNCC, LOGL_INFO, "start DTMF (keypad %c)\n",
+ dtmf.keypad);
+ return mncc_send(ms, MNCC_START_DTMF_REQ, &dtmf);
+ case DTMF_ST_START:
+ if (mncc->msg_type != MNCC_START_DTMF_RSP) {
+ LOGP(DMNCC, LOGL_INFO, "DTMF was rejected\n");
+ return -ENOTSUP;
+ }
+ start_dtmf_timer(call, 70);
+ call->dtmf_state = DTMF_ST_MARK;
+ LOGP(DMNCC, LOGL_INFO, "DTMF is on\n");
+ break;
+ case DTMF_ST_MARK:
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = call->callref;
+ call->dtmf_state = DTMF_ST_STOP;
+ LOGP(DMNCC, LOGL_INFO, "stop DTMF\n");
+ return mncc_send(ms, MNCC_STOP_DTMF_REQ, &dtmf);
+ case DTMF_ST_STOP:
+ start_dtmf_timer(call, 120);
+ call->dtmf_state = DTMF_ST_SPACE;
+ LOGP(DMNCC, LOGL_INFO, "DTMF is off\n");
+ break;
+ }
+ return 0;
+}
+static void timeout_dtmf(void *arg)
+{
+ struct gsm_call *call = arg;
+
+ LOGP(DCC, LOGL_INFO, "DTMF timer has fired\n");
+ dtmf_statemachine(call, NULL);
+}
+
+int mncc_dtmf(struct osmocom_ms *ms, char *dtmf)
+{
+ struct gsm_call *call, *found = NULL;
+
+ llist_for_each_entry(call, &call_list, entry) {
+ if (!call->hold) {
+ found = call;
+ break;
+ }
+ }
+ if (!found) {
+ LOGP(DMNCC, LOGL_INFO, "No active call to send DTMF\n");
+ vty_notify(ms, NULL);
+ vty_notify(ms, "No active call\n");
+ return -EINVAL;
+ }
+
+ if (call->dtmf_state != DTMF_ST_IDLE) {
+ LOGP(DMNCC, LOGL_INFO, "sending DTMF already\n");
+ return -EINVAL;
+ }
+
+ call->dtmf_index = 0;
+ strncpy(call->dtmf, dtmf, sizeof(call->dtmf) - 1);
+ return dtmf_statemachine(call, NULL);
+}