[layer23] DTMF support
[osmocom-bb.git] / src / host / layer23 / src / mobile / mnccms.c
index 12f198e..4e44bf9 100644 (file)
@@ -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);
@@ -61,6 +83,116 @@ struct gsm_call *get_call_ref(uint32_t callref)
        return NULL;
 }
 
+static int8_t mncc_get_bearer(struct gsm_settings *set, uint8_t speech_ver)
+{
+       switch (speech_ver) {
+       case 4:
+               if (set->full_v3)
+                       LOGP(DMNCC, LOGL_INFO, " net suggests full rate v3\n");
+               else {
+                       LOGP(DMNCC, LOGL_INFO, " full rate v3 not supported\n");
+                       speech_ver = -1;
+               }
+               break;
+       case 2:
+               if (set->full_v2)
+                       LOGP(DMNCC, LOGL_INFO, " net suggests full rate v2\n");
+               else {
+                       LOGP(DMNCC, LOGL_INFO, " full rate v2 not supported\n");
+                       speech_ver = -1;
+               }
+               break;
+       case 0: /* mandatory */
+               if (set->full_v1)
+                       LOGP(DMNCC, LOGL_INFO, " net suggests full rate v1\n");
+               else {
+                       LOGP(DMNCC, LOGL_INFO, " full rate v1 not supported\n");
+                       speech_ver = -1;
+               }
+               break;
+       case 5:
+               if (set->half_v3)
+                       LOGP(DMNCC, LOGL_INFO, " net suggests half rate v3\n");
+               else {
+                       LOGP(DMNCC, LOGL_INFO, " half rate v3 not supported\n");
+                       speech_ver = -1;
+               }
+               break;
+       case 1:
+               if (set->half_v1)
+                       LOGP(DMNCC, LOGL_INFO, " net suggests half rate v1\n");
+               else {
+                       LOGP(DMNCC, LOGL_INFO, " half rate v1 not supported\n");
+                       speech_ver = -1;
+               }
+               break;
+       default:
+               LOGP(DMNCC, LOGL_INFO, " net suggests unknown speech version "
+                       "%d\n", speech_ver);
+               speech_ver = -1;
+       }
+
+       return speech_ver;
+}
+
+static void mncc_set_bearer(struct osmocom_ms *ms, int8_t speech_ver,
+       struct gsm_mncc *mncc)
+{
+       struct gsm_settings *set = &ms->settings;
+       int i = 0;
+
+       mncc->fields |= MNCC_F_BEARER_CAP;
+       mncc->bearer_cap.coding = 0;
+       if (set->ch_cap == GSM_CAP_SDCCH_TCHF_TCHH
+        && (set->half_v1 || set->half_v3)) {
+               mncc->bearer_cap.radio = 3;
+               LOGP(DMNCC, LOGL_INFO, " support TCH/H also\n");
+       } else {
+               mncc->bearer_cap.radio = 1;
+               LOGP(DMNCC, LOGL_INFO, " support TCH/F only\n");
+       }
+       mncc->bearer_cap.speech_ctm = 0;
+       /* if no specific speech_ver is given */
+       if (speech_ver < 0) {
+               /* if half rate is supported and prefered */
+               if (set->half_v3 && set->half && set->half_prefer) {
+                       mncc->bearer_cap.speech_ver[i++] = 5;
+                       LOGP(DMNCC, LOGL_INFO, " support half rate v3\n");
+               }
+               if (set->half_v1 && set->half && set->half_prefer) {
+                       mncc->bearer_cap.speech_ver[i++] = 1;
+                       LOGP(DMNCC, LOGL_INFO, " support half rate v1\n");
+               }
+               /* if full rate is supported */
+               if (set->full_v3) {
+                       mncc->bearer_cap.speech_ver[i++] = 4;
+                       LOGP(DMNCC, LOGL_INFO, " support full rate v3\n");
+               }
+               if (set->full_v2) {
+                       mncc->bearer_cap.speech_ver[i++] = 2;
+                       LOGP(DMNCC, LOGL_INFO, " support full rate v2\n");
+               }
+               if (set->full_v1) { /* mandatory, so it's always true */
+                       mncc->bearer_cap.speech_ver[i++] = 0;
+                       LOGP(DMNCC, LOGL_INFO, " support full rate v1\n");
+               }
+               /* if half rate is supported and not prefered */
+               if (set->half_v3 && set->half && !set->half_prefer) {
+                       mncc->bearer_cap.speech_ver[i++] = 5;
+                       LOGP(DMNCC, LOGL_INFO, " support half rate v3\n");
+               }
+               if (set->half_v1 && set->half && !set->half_prefer) {
+                       mncc->bearer_cap.speech_ver[i++] = 1;
+                       LOGP(DMNCC, LOGL_INFO, " support half rate v1\n");
+               }
+       /* if specific speech_ver is given (it must be supported) */
+       } else 
+               mncc->bearer_cap.speech_ver[i++] = speech_ver;
+       mncc->bearer_cap.speech_ver[i] = -1; /* end of list */
+       mncc->bearer_cap.transfer = 0;
+       mncc->bearer_cap.mode = 0;
+}
+
 /*
  * MNCCms dummy application
  */
@@ -92,10 +224,12 @@ int mncc_recv_dummy(struct osmocom_ms *ms, int msg_type, void *arg)
 
 int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
 {
+       struct gsm_settings *set = &ms->settings;
        struct gsm_mncc *data = arg;
        struct gsm_call *call = get_call_ref(data->callref);
        struct gsm_mncc mncc;
        uint8_t cause;
+       int8_t  speech_ver = -1, speech_ver_half = -1, temp;
        int first_call = 0;
 
        /* call does not exist */
@@ -119,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);
        }
@@ -176,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);
@@ -203,6 +339,10 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
                vty_notify(ms, NULL);
                vty_notify(ms, "Call is proceeding\n");
                LOGP(DMNCC, LOGL_INFO, "Call is proceeding\n");
+               if ((data->fields & MNCC_F_BEARER_CAP)
+                && data->bearer_cap.speech_ver[0] >= 0) {
+                       mncc_get_bearer(set, data->bearer_cap.speech_ver[0]);
+               }
                break;
        case MNCC_ALERT_IND:
                vty_notify(ms, NULL);
@@ -222,6 +362,51 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
                        cause = GSM48_CC_CAUSE_USER_BUSY;
                        goto release;
                }
+               /* select first supported speech_ver */
+               if ((data->fields & MNCC_F_BEARER_CAP)) {
+                       int i;
+
+                       for (i = 0; data->bearer_cap.speech_ver[i] >= 0; i++) {
+
+                               temp = mncc_get_bearer(set,
+                                       data->bearer_cap.speech_ver[i]);
+                               if (temp < 0)
+                                       continue;
+                               if (temp == 5 || temp == 1) { /* half */
+                                       /* only the first half rate */
+                                       if (speech_ver_half < 0)
+                                               speech_ver_half = temp;
+                               } else {
+                                       /* only the first full rate */
+                                       if (speech_ver < 0)
+                                               speech_ver = temp;
+                               }
+                       }
+                       /* half and full given */
+                       if (speech_ver_half >= 0 && speech_ver >= 0) {
+                               if (set->half_prefer) {
+                                       LOGP(DMNCC, LOGL_INFO, " both supported"
+                                               " codec rates are given, using "
+                                               "preferred half rate\n");
+                                       speech_ver = speech_ver_half;
+                               } else
+                                       LOGP(DMNCC, LOGL_INFO, " both supported"
+                                               " codec rates are given, using "
+                                               "preferred full rate\n");
+                       } else if (speech_ver_half < 0 && speech_ver < 0) {
+                               LOGP(DMNCC, LOGL_INFO, " no supported codec "
+                                       "rate is given\n");
+                       /* only half rate is given, use it */
+                       } else if (speech_ver_half >= 0) {
+                               LOGP(DMNCC, LOGL_INFO, " only supported half "
+                                       "rate codec is given, using it\n");
+                               speech_ver = speech_ver_half;
+                       /* only full rate is given, use it */
+                       } else {
+                               LOGP(DMNCC, LOGL_INFO, " only supported full "
+                                       "rate codec is given, using it\n");
+                       }
+               }
                /* presentation allowed if present == 0 */
                if (data->calling.present || !data->calling.number[0])
                        vty_notify(ms, "Incomming call (anonymous)\n");
@@ -238,6 +423,16 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
                        data->calling.number, call->callref);
                memset(&mncc, 0, sizeof(struct gsm_mncc));
                mncc.callref = call->callref;
+               /* only include bearer cap, if not given in setup
+                * or if multiple codecs are given
+                * or if not only full rate
+                * or if given codec is unimplemented
+                */
+               if (!(data->fields & MNCC_F_BEARER_CAP) || speech_ver < 0)
+                       mncc_set_bearer(ms, -1, &mncc);
+               else if (data->bearer_cap.speech_ver[1] >= 0
+                     || speech_ver != 0)
+                       mncc_set_bearer(ms, speech_ver, &mncc);
                mncc_send(ms, MNCC_CALL_CONF_REQ, &mncc);
                if (first_call)
                        LOGP(DMNCC, LOGL_INFO, "Ring!\n");
@@ -277,6 +472,15 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg)
                vty_notify(ms, "Call retrieve was rejected\n");
                LOGP(DMNCC, LOGL_INFO, "Call retrieve was rejected\n");
                break;
+       case MNCC_FACILITY_IND:
+               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);
@@ -304,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);
@@ -319,22 +524,28 @@ int mncc_call(struct osmocom_ms *ms, char *number)
                LOGP(DMNCC, LOGL_INFO, "Make call to %s\n", number);
                /* called number */
                setup.fields |= MNCC_F_CALLED;
+               if (number[0] == '+') {
+                       number++;
+                       setup.called.type = 1; /* international */
+               } else
+                       setup.called.type = 0; /* auto/unknown - prefix must be
+                                                 used */
+               setup.called.plan = 1; /* ISDN */
                strncpy(setup.called.number, number,
                        sizeof(setup.called.number) - 1);
                
                /* bearer capability (mandatory) */
-               setup.fields |= MNCC_F_BEARER_CAP;
-               setup.bearer_cap.coding = 0;
-               setup.bearer_cap.radio = 1;
-               setup.bearer_cap.speech_ctm = 0;
-               setup.bearer_cap.speech_ver[0] = 0;
-               setup.bearer_cap.speech_ver[1] = -1; /* end of list */
-               setup.bearer_cap.transfer = 0;
-               setup.bearer_cap.mode = 0;
+               mncc_set_bearer(ms, -1, &setup);
                if (ms->settings.clir)
                        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);
@@ -469,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);
+}