isimodem/voicecall.c: initial version
authorPekka Pessi <Pekka.Pessi@nokia.com>
Fri, 9 Oct 2009 14:45:37 +0000 (17:45 +0300)
committerAki Niemi <aki.niemi@nokia.com>
Fri, 16 Oct 2009 08:18:52 +0000 (11:18 +0300)
Implementing all the voicecall methods. Tested with Nokia 2008 modems.

 - problems in call creation are probably not reported properly
 - deflect not implemented in modem, not properly tested

TODO: Clean up style issues and align with other drivers.

Makefile.am
drivers/isimodem/isi-call-debug.c [new file with mode: 0644]
drivers/isimodem/isi-call.h [new file with mode: 0644]
drivers/isimodem/voicecall.c
gisi/client.c

index 483c958..99dcd15 100644 (file)
@@ -77,6 +77,8 @@ builtin_sources += $(gisi_sources) \
                                drivers/isimodem/devinfo.c \
                                drivers/isimodem/network-registration.c \
                                drivers/isimodem/voicecall.c \
+                               drivers/isimodem/isi-call.h \
+                               drivers/isimodem/isi-call-debug.c \
                                drivers/isimodem/sms.c \
                                drivers/isimodem/cbs.c \
                                drivers/isimodem/sim.c \
diff --git a/drivers/isimodem/isi-call-debug.c b/drivers/isimodem/isi-call-debug.c
new file mode 100644 (file)
index 0000000..5d6483c
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ * This file is part of oFono - Open Source Telephony
+ *
+ * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * Contact: <Pekka.Pessi@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include <gisi/client.h>
+#include <gisi/iter.h>
+
+#include "isi-call.h"
+
+#include <ofono/log.h>
+
+#define DUMP(fmt, arg...) ofono_debug(fmt, ## arg)
+
+char const *isi_call_status_name(enum isi_call_status value)
+{
+       switch (value) {
+#define _(X) case CALL_STATUS_ ## X: return #X
+               _(IDLE);
+               _(CREATE);
+               _(COMING);
+               _(PROCEEDING);
+               _(MO_ALERTING);
+               _(MT_ALERTING);
+               _(WAITING);
+               _(ANSWERED);
+               _(ACTIVE);
+               _(MO_RELEASE);
+               _(MT_RELEASE);
+               _(HOLD_INITIATED);
+               _(HOLD);
+               _(RETRIEVE_INITIATED);
+               _(RECONNECT_PENDING);
+               _(TERMINATED);
+               _(SWAP_INITIATED);
+#undef _
+       }
+       return "<UNKNOWN>";
+}
+
+char const *isi_call_message_id_name(enum isi_call_message_id value)
+{
+       switch (value) {
+#define _(X) case X: return #X
+               _(CALL_CREATE_REQ);
+               _(CALL_CREATE_RESP);
+               _(CALL_COMING_IND);
+               _(CALL_MO_ALERT_IND);
+               _(CALL_MT_ALERT_IND);
+               _(CALL_WAITING_IND);
+               _(CALL_ANSWER_REQ);
+               _(CALL_ANSWER_RESP);
+               _(CALL_RELEASE_REQ);
+               _(CALL_RELEASE_RESP);
+               _(CALL_RELEASE_IND);
+               _(CALL_TERMINATED_IND);
+               _(CALL_STATUS_REQ);
+               _(CALL_STATUS_RESP);
+               _(CALL_STATUS_IND);
+               _(CALL_SERVER_STATUS_IND);
+               _(CALL_CONTROL_REQ);
+               _(CALL_CONTROL_RESP);
+               _(CALL_CONTROL_IND);
+               _(CALL_MODE_SWITCH_REQ);
+               _(CALL_MODE_SWITCH_RESP);
+               _(CALL_MODE_SWITCH_IND);
+               _(CALL_DTMF_SEND_REQ);
+               _(CALL_DTMF_SEND_RESP);
+               _(CALL_DTMF_STOP_REQ);
+               _(CALL_DTMF_STOP_RESP);
+               _(CALL_DTMF_STATUS_IND);
+               _(CALL_DTMF_TONE_IND);
+               _(CALL_RECONNECT_IND);
+               _(CALL_PROPERTY_GET_REQ);
+               _(CALL_PROPERTY_GET_RESP);
+               _(CALL_PROPERTY_SET_REQ);
+               _(CALL_PROPERTY_SET_RESP);
+               _(CALL_PROPERTY_SET_IND);
+               _(CALL_EMERGENCY_NBR_CHECK_REQ);
+               _(CALL_EMERGENCY_NBR_CHECK_RESP);
+               _(CALL_EMERGENCY_NBR_GET_REQ);
+               _(CALL_EMERGENCY_NBR_GET_RESP);
+               _(CALL_EMERGENCY_NBR_MODIFY_REQ);
+               _(CALL_EMERGENCY_NBR_MODIFY_RESP);
+               _(CALL_GSM_NOTIFICATION_IND);
+               _(CALL_GSM_USER_TO_USER_REQ);
+               _(CALL_GSM_USER_TO_USER_RESP);
+               _(CALL_GSM_USER_TO_USER_IND);
+               _(CALL_GSM_BLACKLIST_CLEAR_REQ);
+               _(CALL_GSM_BLACKLIST_CLEAR_RESP);
+               _(CALL_GSM_BLACKLIST_TIMER_IND);
+               _(CALL_GSM_DATA_CH_INFO_IND);
+               _(CALL_GSM_CCP_GET_REQ);
+               _(CALL_GSM_CCP_GET_RESP);
+               _(CALL_GSM_CCP_CHECK_REQ);
+               _(CALL_GSM_CCP_CHECK_RESP);
+               _(CALL_GSM_COMING_REJ_IND);
+               _(CALL_GSM_RAB_IND);
+               _(CALL_GSM_IMMEDIATE_MODIFY_IND);
+               _(CALL_CREATE_NO_SIMATK_REQ);
+               _(CALL_GSM_SS_DATA_IND);
+               _(CALL_TIMER_REQ);
+               _(CALL_TIMER_RESP);
+               _(CALL_TIMER_NTF);
+               _(CALL_TIMER_IND);
+               _(CALL_TIMER_RESET_REQ);
+               _(CALL_TIMER_RESET_RESP);
+               _(CALL_EMERGENCY_NBR_IND);
+               _(CALL_SERVICE_DENIED_IND);
+               _(CALL_RELEASE_END_REQ);
+               _(CALL_RELEASE_END_RESP);
+               _(CALL_USER_CONNECT_IND);
+               _(CALL_AUDIO_CONNECT_IND);
+               _(CALL_KODIAK_ALLOW_CTRL_REQ);
+               _(CALL_KODIAK_ALLOW_CTRL_RESP);
+               _(CALL_SERVICE_ACTIVATE_IND);
+               _(CALL_SERVICE_ACTIVATE_REQ);
+               _(CALL_SERVICE_ACTIVATE_RESP);
+               _(CALL_SIM_ATK_IND);
+               _(CALL_CONTROL_OPER_IND);
+               _(CALL_TEST_CALL_STATUS_IND);
+               _(CALL_SIM_ATK_INFO_IND);
+               _(CALL_SECURITY_IND);
+               _(CALL_MEDIA_HANDLE_REQ);
+               _(CALL_MEDIA_HANDLE_RESP);
+               _(COMMON_MESSAGE);
+#undef _
+       }
+       return "<UNKNOWN>";
+}
+
+char const *isi_call_isi_cause_name(enum isi_call_isi_cause value)
+{
+       switch (value)
+       {
+#define _(X) case CALL_CAUSE_ ## X: return "CAUSE_" #X
+               _(NO_CAUSE);
+               _(NO_CALL);
+               _(TIMEOUT);
+               _(RELEASE_BY_USER);
+               _(BUSY_USER_REQUEST);
+               _(ERROR_REQUEST);
+               _(COST_LIMIT_REACHED);
+               _(CALL_ACTIVE);
+               _(NO_CALL_ACTIVE);
+               _(INVALID_CALL_MODE);
+               _(SIGNALLING_FAILURE);
+               _(TOO_LONG_ADDRESS);
+               _(INVALID_ADDRESS);
+               _(EMERGENCY);
+               _(NO_TRAFFIC_CHANNEL);
+               _(NO_COVERAGE);
+               _(CODE_REQUIRED);
+               _(NOT_ALLOWED);
+               _(NO_DTMF);
+               _(CHANNEL_LOSS);
+               _(FDN_NOT_OK);
+               _(USER_TERMINATED);
+               _(BLACKLIST_BLOCKED);
+               _(BLACKLIST_DELAYED);
+               _(NUMBER_NOT_FOUND);
+               _(NUMBER_CANNOT_REMOVE);
+               _(EMERGENCY_FAILURE);
+               _(CS_SUSPENDED);
+               _(DCM_DRIVE_MODE);
+               _(MULTIMEDIA_NOT_ALLOWED);
+               _(SIM_REJECTED);
+               _(NO_SIM);
+               _(SIM_LOCK_OPERATIVE);
+               _(SIMATKCC_REJECTED);
+               _(SIMATKCC_MODIFIED);
+               _(DTMF_INVALID_DIGIT);
+               _(DTMF_SEND_ONGOING);
+               _(CS_INACTIVE);
+               _(SECURITY_MODE);
+               _(TRACFONE_FAILED);
+               _(TRACFONE_WAIT_FAILED);
+               _(TRACFONE_CONF_FAILED);
+               _(TEMPERATURE_LIMIT);
+               _(KODIAK_POC_FAILED);
+               _(NOT_REGISTERED);
+               _(CS_CALLS_ONLY);
+               _(VOIP_CALLS_ONLY);
+               _(LIMITED_CALL_ACTIVE);
+               _(LIMITED_CALL_NOT_ALLOWED);
+               _(SECURE_CALL_NOT_POSSIBLE);
+               _(INTERCEPT);
+#undef _
+       }
+       return "<UNKNOWN>";
+}
+
+char const *isi_call_gsm_cause_name(enum isi_call_gsm_cause value)
+{
+       switch (value)
+       {
+#define _(X) case CALL_GSM_CAUSE_ ## X: return "GSM_CAUSE_" #X
+               _(UNASSIGNED_NUMBER);
+               _(NO_ROUTE);
+               _(CH_UNACCEPTABLE);
+               _(OPER_BARRING);
+               _(NORMAL);
+               _(USER_BUSY);
+               _(NO_USER_RESPONSE);
+               _(ALERT_NO_ANSWER);
+               _(CALL_REJECTED);
+               _(NUMBER_CHANGED);
+               _(NON_SELECT_CLEAR);
+               _(DEST_OUT_OF_ORDER);
+               _(INVALID_NUMBER);
+               _(FACILITY_REJECTED);
+               _(RESP_TO_STATUS);
+               _(NORMAL_UNSPECIFIED);
+               _(NO_CHANNEL);
+               _(NETW_OUT_OF_ORDER);
+               _(TEMPORARY_FAILURE);
+               _(CONGESTION);
+               _(ACCESS_INFO_DISC);
+               _(CHANNEL_NA);
+               _(RESOURCES_NA);
+               _(QOS_NA);
+               _(FACILITY_UNSUBS);
+               _(COMING_BARRED_CUG);
+               _(BC_UNAUTHORIZED);
+               _(BC_NA);
+               _(SERVICE_NA);
+               _(BEARER_NOT_IMPL);
+               _(ACM_MAX);
+               _(FACILITY_NOT_IMPL);
+               _(ONLY_RDI_BC);
+               _(SERVICE_NOT_IMPL);
+               _(INVALID_TI);
+               _(NOT_IN_CUG);
+               _(INCOMPATIBLE_DEST);
+               _(INV_TRANS_NET_SEL);
+               _(SEMANTICAL_ERR);
+               _(INVALID_MANDATORY);
+               _(MSG_TYPE_INEXIST);
+               _(MSG_TYPE_INCOMPAT);
+               _(IE_NON_EXISTENT);
+               _(COND_IE_ERROR);
+               _(MSG_INCOMPATIBLE);
+               _(TIMER_EXPIRY);
+               _(PROTOCOL_ERROR);
+               _(INTERWORKING);
+#undef _
+       }
+       return "<UNKNOWN>";
+}
+
+char const *isi_call_cause_name(uint8_t cause_type, uint8_t cause)
+{
+       switch (cause_type)
+       {
+       case CALL_CAUSE_TYPE_DEFAULT:
+       case CALL_CAUSE_TYPE_CLIENT:
+       case CALL_CAUSE_TYPE_SERVER:
+               return isi_call_isi_cause_name(cause);
+       case CALL_CAUSE_TYPE_NETWORK:
+               return isi_call_gsm_cause_name(cause);
+       }
+       return "<UNKNOWN>";
+}
+
+static void isi_call_hex_dump(uint8_t const m[],
+                             size_t len,
+                             char const *name)
+{
+       char const *prefix;
+       char hex[3 * 16 + 1];
+       char ascii[16 + 1];
+       size_t i, j, k;
+
+       if (strncmp(name, "CALL_", 5))
+               prefix = "CALL ";
+       else
+               prefix = "";
+
+       DUMP("%s%s [%s=0x%02X len=%zu]:",
+            prefix, name, "message_id", m[1], len);
+
+       strcpy(hex, " **"), j = 3;
+       strcpy(ascii, "."), k = 1;
+
+       for (i = 1; i < len; i++) {
+               sprintf(hex + j, " %02X", m[i]), j += 3;
+               ascii[k++] = g_ascii_isgraph(m[i]) ? m[i] : '.';
+
+               if ((i & 15) == 15) {
+                       DUMP("    *%-48s : %.*s", hex, (int)k, ascii);
+                       j = 0, k = 0;
+               }
+       }
+
+       if (j) {
+               DUMP("    *%-48s : %.*s", hex, (int)k, ascii);
+       }
+}
+
+void isi_call_debug(const void *restrict buf, size_t len, void *data)
+{
+       uint8_t const *m = buf;
+       char const *name;
+
+       m = buf, m--, len++, buf = m;
+
+       if (len < 4) {
+               DUMP("CALL: %s [len=%zu]", "RUNT", len);
+               return;
+       }
+
+       name = isi_call_message_id_name(m[1]);
+
+       isi_call_hex_dump(m, len, name);
+}
diff --git a/drivers/isimodem/isi-call.h b/drivers/isimodem/isi-call.h
new file mode 100644 (file)
index 0000000..98f7583
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * This file is part of oFono - Open Source Telephony
+ *
+ * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * Contact: <Pekka.Pessi@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __GISI_CALL_H
+#define __GISI_CALL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#define PN_CALL                        0x01
+
+enum isi_call_message_id {
+       CALL_CREATE_REQ =                                       0x01,
+       CALL_CREATE_RESP =                                      0x02,
+       CALL_COMING_IND =                                       0x03,
+       CALL_MO_ALERT_IND =                                     0x04,
+       CALL_MT_ALERT_IND =                                     0x05,
+       CALL_WAITING_IND =                                      0x06,
+       CALL_ANSWER_REQ =                                       0x07,
+       CALL_ANSWER_RESP =                                      0x08,
+       CALL_RELEASE_REQ =                                      0x09,
+       CALL_RELEASE_RESP =                                     0x0A,
+       CALL_RELEASE_IND =                                      0x0B,
+       CALL_TERMINATED_IND =                                   0x0C,
+       CALL_STATUS_REQ =                                       0x0D,
+       CALL_STATUS_RESP =                                      0x0E,
+       CALL_STATUS_IND =                                       0x0F,
+       CALL_SERVER_STATUS_IND =                                0x10,
+       CALL_CONTROL_REQ =                                      0x11,
+       CALL_CONTROL_RESP =                                     0x12,
+       CALL_CONTROL_IND =                                      0x13,
+       CALL_MODE_SWITCH_REQ =                                  0x14,
+       CALL_MODE_SWITCH_RESP =                                 0x15,
+       CALL_MODE_SWITCH_IND =                                  0x16,
+       CALL_DTMF_SEND_REQ =                                    0x17,
+       CALL_DTMF_SEND_RESP =                                   0x18,
+       CALL_DTMF_STOP_REQ =                                    0x19,
+       CALL_DTMF_STOP_RESP =                                   0x1A,
+       CALL_DTMF_STATUS_IND =                                  0x1B,
+       CALL_DTMF_TONE_IND =                                    0x1C,
+       CALL_RECONNECT_IND =                                    0x1E,
+       CALL_PROPERTY_GET_REQ =                                 0x1F,
+       CALL_PROPERTY_GET_RESP =                                0x20,
+       CALL_PROPERTY_SET_REQ =                                 0x21,
+       CALL_PROPERTY_SET_RESP =                                0x22,
+       CALL_PROPERTY_SET_IND =                                 0x23,
+       CALL_EMERGENCY_NBR_CHECK_REQ =                          0x28,
+       CALL_EMERGENCY_NBR_CHECK_RESP =                         0x29,
+       CALL_EMERGENCY_NBR_GET_REQ =                            0x26,
+       CALL_EMERGENCY_NBR_GET_RESP =                           0x27,
+       CALL_EMERGENCY_NBR_MODIFY_REQ =                         0x24,
+       CALL_EMERGENCY_NBR_MODIFY_RESP =                        0x25,
+       CALL_GSM_NOTIFICATION_IND =                             0xA0,
+       CALL_GSM_USER_TO_USER_REQ =                             0xA1,
+       CALL_GSM_USER_TO_USER_RESP =                            0xA2,
+       CALL_GSM_USER_TO_USER_IND =                             0xA3,
+       CALL_GSM_BLACKLIST_CLEAR_REQ =                          0xA4,
+       CALL_GSM_BLACKLIST_CLEAR_RESP =                         0xA5,
+       CALL_GSM_BLACKLIST_TIMER_IND =                          0xA6,
+       CALL_GSM_DATA_CH_INFO_IND =                             0xA7,
+       CALL_GSM_CCP_GET_REQ =                                  0xAA,
+       CALL_GSM_CCP_GET_RESP =                                 0xAB,
+       CALL_GSM_CCP_CHECK_REQ =                                0xAC,
+       CALL_GSM_CCP_CHECK_RESP =                               0xAD,
+       CALL_GSM_COMING_REJ_IND =                               0xA9,
+       CALL_GSM_RAB_IND =                                      0xA8,
+       CALL_GSM_IMMEDIATE_MODIFY_IND =                         0xAE,
+       CALL_CREATE_NO_SIMATK_REQ =                             0x2A,
+       CALL_GSM_SS_DATA_IND =                                  0xAF,
+       CALL_TIMER_REQ =                                        0x2B,
+       CALL_TIMER_RESP =                                       0x2C,
+       CALL_TIMER_NTF =                                        0x2D,
+       CALL_TIMER_IND =                                        0x2E,
+       CALL_TIMER_RESET_REQ =                                  0x2F,
+       CALL_TIMER_RESET_RESP =                                 0x30,
+       CALL_EMERGENCY_NBR_IND =                                0x31,
+       CALL_SERVICE_DENIED_IND =                               0x32,
+       CALL_RELEASE_END_REQ =                                  0x34,
+       CALL_RELEASE_END_RESP =                                 0x35,
+       CALL_USER_CONNECT_IND =                                 0x33,
+       CALL_AUDIO_CONNECT_IND =                                0x40,
+       CALL_KODIAK_ALLOW_CTRL_REQ =                            0x36,
+       CALL_KODIAK_ALLOW_CTRL_RESP =                           0x37,
+       CALL_SERVICE_ACTIVATE_IND =                             0x38,
+       CALL_SERVICE_ACTIVATE_REQ =                             0x39,
+       CALL_SERVICE_ACTIVATE_RESP =                            0x3A,
+       CALL_SIM_ATK_IND =                                      0x3B,
+       CALL_CONTROL_OPER_IND =                                 0x3C,
+       CALL_TEST_CALL_STATUS_IND =                             0x3E,
+       CALL_SIM_ATK_INFO_IND =                                 0x3F,
+       CALL_SECURITY_IND =                                     0x41,
+       CALL_MEDIA_HANDLE_REQ =                                 0x42,
+       CALL_MEDIA_HANDLE_RESP =                                0x43,
+       COMMON_MESSAGE =                                        0xF0,
+};
+
+enum isi_call_status {
+       CALL_STATUS_IDLE =                                      0x00,
+       CALL_STATUS_CREATE =                                    0x01,
+       CALL_STATUS_COMING =                                    0x02,
+       CALL_STATUS_PROCEEDING =                                0x03,
+       CALL_STATUS_MO_ALERTING =                               0x04,
+       CALL_STATUS_MT_ALERTING =                               0x05,
+       CALL_STATUS_WAITING =                                   0x06,
+       CALL_STATUS_ANSWERED =                                  0x07,
+       CALL_STATUS_ACTIVE =                                    0x08,
+       CALL_STATUS_MO_RELEASE =                                0x09,
+       CALL_STATUS_MT_RELEASE =                                0x0A,
+       CALL_STATUS_HOLD_INITIATED =                            0x0B,
+       CALL_STATUS_HOLD =                                      0x0C,
+       CALL_STATUS_RETRIEVE_INITIATED =                        0x0D,
+       CALL_STATUS_RECONNECT_PENDING =                         0x0E,
+       CALL_STATUS_TERMINATED =                                0x0F,
+       CALL_STATUS_SWAP_INITIATED =                            0x10,
+};
+
+enum isi_call_isi_cause {
+       CALL_CAUSE_NO_CAUSE =                                   0x00,
+       CALL_CAUSE_NO_CALL =                                    0x01,
+       CALL_CAUSE_TIMEOUT =                                    0x02,
+       CALL_CAUSE_RELEASE_BY_USER =                            0x03,
+       CALL_CAUSE_BUSY_USER_REQUEST =                          0x04,
+       CALL_CAUSE_ERROR_REQUEST =                              0x05,
+       CALL_CAUSE_COST_LIMIT_REACHED =                         0x06,
+       CALL_CAUSE_CALL_ACTIVE =                                0x07,
+       CALL_CAUSE_NO_CALL_ACTIVE =                             0x08,
+       CALL_CAUSE_INVALID_CALL_MODE =                          0x09,
+       CALL_CAUSE_SIGNALLING_FAILURE =                         0x0A,
+       CALL_CAUSE_TOO_LONG_ADDRESS =                           0x0B,
+       CALL_CAUSE_INVALID_ADDRESS =                            0x0C,
+       CALL_CAUSE_EMERGENCY =                                  0x0D,
+       CALL_CAUSE_NO_TRAFFIC_CHANNEL =                         0x0E,
+       CALL_CAUSE_NO_COVERAGE =                                0x0F,
+       CALL_CAUSE_CODE_REQUIRED =                              0x10,
+       CALL_CAUSE_NOT_ALLOWED =                                0x11,
+       CALL_CAUSE_NO_DTMF =                                    0x12,
+       CALL_CAUSE_CHANNEL_LOSS =                               0x13,
+       CALL_CAUSE_FDN_NOT_OK =                                 0x14,
+       CALL_CAUSE_USER_TERMINATED =                            0x15,
+       CALL_CAUSE_BLACKLIST_BLOCKED =                          0x16,
+       CALL_CAUSE_BLACKLIST_DELAYED =                          0x17,
+       CALL_CAUSE_NUMBER_NOT_FOUND =                           0x18,
+       CALL_CAUSE_NUMBER_CANNOT_REMOVE =                       0x19,
+       CALL_CAUSE_EMERGENCY_FAILURE =                          0x1A,
+       CALL_CAUSE_CS_SUSPENDED =                               0x1B,
+       CALL_CAUSE_DCM_DRIVE_MODE =                             0x1C,
+       CALL_CAUSE_MULTIMEDIA_NOT_ALLOWED =                     0x1D,
+       CALL_CAUSE_SIM_REJECTED =                               0x1E,
+       CALL_CAUSE_NO_SIM =                                     0x1F,
+       CALL_CAUSE_SIM_LOCK_OPERATIVE =                         0x20,
+       CALL_CAUSE_SIMATKCC_REJECTED =                          0x21,
+       CALL_CAUSE_SIMATKCC_MODIFIED =                          0x22,
+       CALL_CAUSE_DTMF_INVALID_DIGIT =                         0x23,
+       CALL_CAUSE_DTMF_SEND_ONGOING =                          0x24,
+       CALL_CAUSE_CS_INACTIVE =                                0x25,
+       CALL_CAUSE_SECURITY_MODE =                              0x26,
+       CALL_CAUSE_TRACFONE_FAILED =                            0x27,
+       CALL_CAUSE_TRACFONE_WAIT_FAILED =                       0x28,
+       CALL_CAUSE_TRACFONE_CONF_FAILED =                       0x29,
+       CALL_CAUSE_TEMPERATURE_LIMIT =                          0x2A,
+       CALL_CAUSE_KODIAK_POC_FAILED =                          0x2B,
+       CALL_CAUSE_NOT_REGISTERED =                             0x2C,
+       CALL_CAUSE_CS_CALLS_ONLY =                              0x2D,
+       CALL_CAUSE_VOIP_CALLS_ONLY =                            0x2E,
+       CALL_CAUSE_LIMITED_CALL_ACTIVE =                        0x2F,
+       CALL_CAUSE_LIMITED_CALL_NOT_ALLOWED =                   0x30,
+       CALL_CAUSE_SECURE_CALL_NOT_POSSIBLE =                   0x31,
+       CALL_CAUSE_INTERCEPT =                                  0x32,
+};
+
+enum isi_call_gsm_cause {
+       CALL_GSM_CAUSE_UNASSIGNED_NUMBER =                      0x01,
+       CALL_GSM_CAUSE_NO_ROUTE =                               0x03,
+       CALL_GSM_CAUSE_CH_UNACCEPTABLE =                        0x06,
+       CALL_GSM_CAUSE_OPER_BARRING =                           0x08,
+       CALL_GSM_CAUSE_NORMAL =                                 0x10,
+       CALL_GSM_CAUSE_USER_BUSY =                              0x11,
+       CALL_GSM_CAUSE_NO_USER_RESPONSE =                       0x12,
+       CALL_GSM_CAUSE_ALERT_NO_ANSWER =                        0x13,
+       CALL_GSM_CAUSE_CALL_REJECTED =                          0x15,
+       CALL_GSM_CAUSE_NUMBER_CHANGED =                         0x16,
+       CALL_GSM_CAUSE_NON_SELECT_CLEAR =                       0x1A,
+       CALL_GSM_CAUSE_DEST_OUT_OF_ORDER =                      0x1B,
+       CALL_GSM_CAUSE_INVALID_NUMBER =                         0x1C,
+       CALL_GSM_CAUSE_FACILITY_REJECTED =                      0x1D,
+       CALL_GSM_CAUSE_RESP_TO_STATUS =                         0x1E,
+       CALL_GSM_CAUSE_NORMAL_UNSPECIFIED =                     0x1F,
+       CALL_GSM_CAUSE_NO_CHANNEL =                             0x22,
+       CALL_GSM_CAUSE_NETW_OUT_OF_ORDER =                      0x26,
+       CALL_GSM_CAUSE_TEMPORARY_FAILURE =                      0x29,
+       CALL_GSM_CAUSE_CONGESTION =                             0x2A,
+       CALL_GSM_CAUSE_ACCESS_INFO_DISC =                       0x2B,
+       CALL_GSM_CAUSE_CHANNEL_NA =                             0x2C,
+       CALL_GSM_CAUSE_RESOURCES_NA =                           0x2F,
+       CALL_GSM_CAUSE_QOS_NA =                                 0x31,
+       CALL_GSM_CAUSE_FACILITY_UNSUBS =                        0x32,
+       CALL_GSM_CAUSE_COMING_BARRED_CUG =                      0x37,
+       CALL_GSM_CAUSE_BC_UNAUTHORIZED =                        0x39,
+       CALL_GSM_CAUSE_BC_NA =                                  0x3A,
+       CALL_GSM_CAUSE_SERVICE_NA =                             0x3F,
+       CALL_GSM_CAUSE_BEARER_NOT_IMPL =                        0x41,
+       CALL_GSM_CAUSE_ACM_MAX =                                0x44,
+       CALL_GSM_CAUSE_FACILITY_NOT_IMPL =                      0x45,
+       CALL_GSM_CAUSE_ONLY_RDI_BC =                            0x46,
+       CALL_GSM_CAUSE_SERVICE_NOT_IMPL =                       0x4F,
+       CALL_GSM_CAUSE_INVALID_TI =                             0x51,
+       CALL_GSM_CAUSE_NOT_IN_CUG =                             0x57,
+       CALL_GSM_CAUSE_INCOMPATIBLE_DEST =                      0x58,
+       CALL_GSM_CAUSE_INV_TRANS_NET_SEL =                      0x5B,
+       CALL_GSM_CAUSE_SEMANTICAL_ERR =                         0x5F,
+       CALL_GSM_CAUSE_INVALID_MANDATORY =                      0x60,
+       CALL_GSM_CAUSE_MSG_TYPE_INEXIST =                       0x61,
+       CALL_GSM_CAUSE_MSG_TYPE_INCOMPAT =                      0x62,
+       CALL_GSM_CAUSE_IE_NON_EXISTENT =                        0x63,
+       CALL_GSM_CAUSE_COND_IE_ERROR =                          0x64,
+       CALL_GSM_CAUSE_MSG_INCOMPATIBLE =                       0x65,
+       CALL_GSM_CAUSE_TIMER_EXPIRY =                           0x66,
+       CALL_GSM_CAUSE_PROTOCOL_ERROR =                         0x6F,
+       CALL_GSM_CAUSE_INTERWORKING =                           0x7F,
+};
+
+enum isi_call_cause_type {
+       CALL_CAUSE_TYPE_DEFAULT =                               0x00,
+       CALL_CAUSE_TYPE_CLIENT =                                0x01,
+       CALL_CAUSE_TYPE_SERVER =                                0x02,
+       CALL_CAUSE_TYPE_NETWORK =                               0x03,
+};
+
+enum isi_call_subblock {
+       CALL_ORIGIN_ADDRESS =                                   0x01,
+       CALL_ORIGIN_SUBADDRESS =                                0x02,
+       CALL_DESTINATION_ADDRESS =                              0x03,
+       CALL_DESTINATION_SUBADDRESS =                           0x04,
+       CALL_DESTINATION_PRE_ADDRESS =                          0x05,
+       CALL_DESTINATION_POST_ADDRESS =                         0x06,
+       CALL_MODE =                                             0x07,
+       CALL_CAUSE =                                            0x08,
+       CALL_OPERATION =                                        0x09,
+       CALL_STATUS =                                           0x0A,
+       CALL_STATUS_INFO =                                      0x0B,
+       CALL_ALERTING_INFO =                                    0x0C,
+       CALL_RELEASE_INFO =                                     0x0D,
+       CALL_ORIGIN_INFO =                                      0x0E,
+       CALL_DTMF_DIGIT =                                       0x0F,
+       CALL_DTMF_STRING =                                      0x10,
+       CALL_DTMF_BCD_STRING =                                  0x19,
+       CALL_DTMF_INFO =                                        0x1A,
+       CALL_PROPERTY_INFO =                                    0x13,
+       CALL_EMERGENCY_NUMBER =                                 0x14,
+       CALL_DTMF_STATUS =                                      0x11,
+       CALL_DTMF_TONE =                                        0x12,
+       CALL_GSM_CUG_INFO =                                     0xA0,
+       CALL_GSM_ALERTING_PATTERN =                             0xA1,
+       CALL_GSM_DEFLECTION_ADDRESS =                           0xA2,
+       CALL_GSM_DEFLECTION_SUBADDRESS =                        0xA3,
+       CALL_GSM_REDIRECTING_ADDRESS =                          0xA4,
+       CALL_GSM_REDIRECTING_SUBADDRESS =                       0xA5,
+       CALL_GSM_REMOTE_ADDRESS =                               0xA6,
+       CALL_GSM_REMOTE_SUBADDRESS =                            0xA7,
+       CALL_GSM_USER_TO_USER_INFO =                            0xA8,
+       CALL_GSM_DIAGNOSTICS =                                  0xA9,
+       CALL_GSM_SS_DIAGNOSTICS =                               0xAA,
+       CALL_GSM_NEW_DESTINATION =                              0xAB,
+       CALL_GSM_CCBS_INFO =                                    0xAC,
+       CALL_GSM_ADDRESS_OF_B =                                 0xAD,
+       CALL_GSM_SUBADDRESS_OF_B =                              0xB0,
+       CALL_GSM_NOTIFY =                                       0xB1,
+       CALL_GSM_SS_NOTIFY =                                    0xB2,
+       CALL_GSM_SS_CODE =                                      0xB3,
+       CALL_GSM_SS_STATUS =                                    0xB4,
+       CALL_GSM_SS_NOTIFY_INDICATOR =                          0xB5,
+       CALL_GSM_SS_HOLD_INDICATOR =                            0xB6,
+       CALL_GSM_SS_ECT_INDICATOR =                             0xB7,
+       CALL_GSM_DATA_CH_INFO =                                 0xB8,
+       CALL_DESTINATION_CS_ADDRESS =                           0x16,
+       CALL_GSM_CCP =                                          0xBA,
+       CALL_GSM_RAB_INFO =                                     0xB9,
+       CALL_GSM_FNUR_INFO =                                    0xBB,
+       CALL_GSM_CAUSE_OF_NO_CLI =                              0xBC,
+       CALL_GSM_MM_CAUSE =                                     0xBD,
+       CALL_GSM_EVENT_INFO =                                   0xBE,
+       CALL_GSM_DETAILED_CAUSE =                               0xBF,
+       CALL_GSM_SS_DATA =                                      0xC0,
+       CALL_TIMER =                                            0x17,
+       CALL_GSM_ALS_INFO =                                     0xC1,
+       CALL_STATE_AUTO_CHANGE =                                0x18,
+       CALL_EMERGENCY_NUMBER_INFO =                            0x1B,
+       CALL_STATUS_MODE =                                      0x1C,
+       CALL_ADDR_AND_STATUS_INFO =                             0x1D,
+       CALL_DTMF_TIMERS =                                      0x1E,
+       CALL_NAS_SYNC_INDICATOR =                               0x1F,
+       CALL_NW_CAUSE =                                         0x20,
+       CALL_TRACFONE_RESULT =                                  0x21,
+       CALL_KODIAK_POC =                                       0x22,
+       CALL_DISPLAY_NUMBER =                                   0x23,
+       CALL_DESTINATION_URI =                                  0x24,
+       CALL_ORIGIN_URI =                                       0x25,
+       CALL_URI =                                              0x26,
+       CALL_SYSTEM_INFO =                                      0x27,
+       CALL_SYSTEMS =                                          0x28,
+       CALL_VOIP_TIMER =                                       0x29,
+       CALL_REDIRECTING_URI =                                  0x2A,
+       CALL_REMOTE_URI =                                       0x2B,
+       CALL_DEFLECTION_URI =                                   0x2C,
+       CALL_TRANSFER_INFO =                                    0x2D,
+       CALL_FORWARDING_INFO =                                  0x2E,
+       CALL_ID_INFO =                                          0x2F,
+       CALL_TEST_CALL =                                        0x30,
+       CALL_AUDIO_CONF_INFO =                                  0x31,
+       CALL_SECURITY_INFO =                                    0x33,
+       CALL_SINGLE_TIMERS =                                    0x32,
+       CALL_MEDIA_INFO =                                       0x35,
+       CALL_MEDIA_HANDLE =                                     0x34,
+       CALL_MODE_CHANGE_INFO =                                 0x36,
+       CALL_ADDITIONAL_PARAMS =                                0x37,
+       CALL_DSAC_INFO =                                        0x38,
+};
+
+enum isi_call_id {
+       CALL_ID_NONE =                                          0x00,
+       CALL_ID_1 =                                             0x01,
+       CALL_ID_2 =                                             0x02,
+       CALL_ID_3 =                                             0x03,
+       CALL_ID_4 =                                             0x04,
+       CALL_ID_5 =                                             0x05,
+       CALL_ID_6 =                                             0x06,
+       CALL_ID_7 =                                             0x07,
+       CALL_ID_CONFERENCE =                                    0x10,
+       CALL_ID_WAITING =                                       0x20,
+       CALL_ID_HOLD =                                          0x40,
+       CALL_ID_ACTIVE =                                        0x80,
+       CALL_ID_ALL =                                           0xF0,
+};
+
+enum isi_call_mode {
+       CALL_MODE_EMERGENCY =                                   0x00,
+       CALL_MODE_SPEECH =                                      0x01,
+       CALL_GSM_MODE_ALS_LINE_1 =                              0xA5,
+       CALL_GSM_MODE_ALS_LINE_2 =                              0xA2,
+};
+
+enum {
+       CALL_MODE_INFO_NONE = 0,
+       CALL_MODE_ORIGINATOR = 0x01,
+};
+
+enum {
+       CALL_PRESENTATION_ALLOWED =                             0x00,
+       CALL_PRESENTATION_RESTRICTED =                          0x01,
+       CALL_GSM_PRESENTATION_DEFAULT =                         0x07,
+};
+
+enum isi_call_operation {
+       CALL_OP_HOLD =                                          0x01,
+       CALL_OP_RETRIEVE =                                      0x02,
+       CALL_OP_SWAP =                                          0x03,
+       CALL_OP_CONFERENCE_BUILD =                              0x04,
+       CALL_OP_CONFERENCE_SPLIT =                              0x05,
+       CALL_OP_DATA_RATE_CHANGE =                              0x06,
+       CALL_GSM_OP_CUG =                                       0xA0,
+       CALL_GSM_OP_TRANSFER =                                  0xA1,
+       CALL_GSM_OP_DEFLECT =                                   0xA2,
+       CALL_GSM_OP_CCBS =                                      0xA3,
+       CALL_GSM_OP_UUS1 =                                      0xA4,
+       CALL_GSM_OP_UUS2 =                                      0xA5,
+       CALL_GSM_OP_UUS3 =                                      0xA6,
+};
+
+enum {
+       CALL_GSM_OP_UUS_REQUIRED =                              0x01,
+};
+
+enum call_status_mode {
+       CALL_STATUS_MODE_DEFAULT =                              0x00,
+       CALL_STATUS_MODE_ADDR =                                 0x01,
+       CALL_STATUS_MODE_ADDR_AND_ORIGIN =                      0x02,
+       CALL_STATUS_MODE_POC =                                  0x03,
+       CALL_STATUS_MODE_VOIP_ADDR =                            0x04,
+};
+
+enum {
+       CALL_DTMF_ENABLE_TONE_IND_SEND =                        0x01,
+       CALL_DTMF_DISABLE_TONE_IND_SEND =                       0x02,
+};
+
+char const *isi_call_cause_name(uint8_t cause_type, uint8_t cause);
+char const *isi_call_gsm_cause_name(enum isi_call_gsm_cause value);
+char const *isi_call_isi_cause_name(enum isi_call_isi_cause value);
+char const *isi_call_status_name(enum isi_call_status value);
+char const *isi_call_message_id_name(enum isi_call_message_id value);
+
+void isi_call_debug(const void *restrict buf, size_t len, void *data);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
index bb24a80..390914b 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
  *
- * Contact: Aki Niemi <aki.niemi@nokia.com>
+ * Contact: Pekka Pessi <Pekka.Pessi@nokia.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <assert.h>
 
 #include <glib.h>
 
 #include <gisi/netlink.h>
 #include <gisi/client.h>
+#include <gisi/iter.h>
 
 #include <ofono/log.h>
 #include <ofono/modem.h>
 #include <ofono/voicecall.h>
 
 #include "isi.h"
+#include "isi-call.h"
 
-#define PN_CALL                        0x01
+struct isi_call {
+       uint8_t id, call_id, status, mode, mode_info, cause_type, cause;
+       uint8_t addr_type, presentation;
+       uint8_t reason;
+       char address[20], addr_pad[4];
+};
 
-struct voicecall_data {
+struct isi_voicecall {
        GIsiClient *client;
+
+       struct isi_call_req_context *queue;
+
        struct isi_version version;
+       struct isi_call calls[8];
 };
 
-static void isi_dial(struct ofono_voicecall *vc,
-                       const struct ofono_phone_number *number,
-                       enum ofono_clir_option clir, enum ofono_cug_option cug,
-                       ofono_voicecall_cb_t cb, void *data)
+/* ------------------------------------------------------------------------- */
+
+static void isi_call_notify(struct ofono_voicecall *ovc,
+                           struct isi_call *call);
+static void isi_call_release(struct ofono_voicecall *, struct isi_call *);
+static struct ofono_call isi_call_as_ofono_call(struct isi_call const *);
+static int isi_call_status_to_clcc(struct isi_call const *call);
+static struct isi_call *isi_call_set_idle(struct isi_call *call);
+
+typedef void GIsiIndication (GIsiClient *client,
+               const void *restrict data, size_t len,
+               uint16_t object, void *opaque);
+
+typedef void GIsiVerify (GIsiClient *client, bool alive, void *opaque);
+
+typedef bool GIsiResponse(GIsiClient *client,
+                         void const * restrict data, size_t len,
+                         uint16_t object, void *opaque);
+
+static GIsiVerify isi_call_verify_cb;
+static gboolean isi_call_register(gpointer);
+
+enum {
+       ISI_CALL_TIMEOUT = 1000,
+};
+
+/* ------------------------------------------------------------------------- */
+/* Request context for voicecall cb */
+
+struct isi_call_req_context;
+
+typedef void isi_call_req_step(struct isi_call_req_context *, int reason);
+
+struct isi_call_req_context {
+       struct isi_call_req_context *next, **prev;
+       isi_call_req_step *step;
+       struct ofono_voicecall *ovc;
+       ofono_voicecall_cb_t cb;
+       void *data;
+};
+
+static struct isi_call_req_context *
+isi_call_req(struct ofono_voicecall *ovc,
+            void const * restrict req,
+            size_t len,
+            GIsiResponse *handler,
+            ofono_voicecall_cb_t cb,
+            void *data)
 {
+       struct isi_voicecall *ivc;
+       struct isi_call_req_context *irc;
+
+       ivc = ofono_voicecall_get_data(ovc);
+
+       irc = g_try_new0(struct isi_call_req_context, 1);
+
+       if (irc) {
+               irc->ovc = ovc;
+               irc->cb = cb;
+               irc->data = data;
+
+               if (g_isi_request_make(ivc->client, req, len,
+                                      ISI_CALL_TIMEOUT, handler, irc))
+                       return irc;
+       }
+
+       g_free(irc);
+
+       if (cb)
+               CALLBACK_WITH_FAILURE(cb, data);
+
+       return NULL;
 }
 
-static void isi_answer(struct ofono_voicecall *vc,
-                       ofono_voicecall_cb_t cb, void *data)
+static void isi_ctx_queue(struct isi_call_req_context *irc,
+                         isi_call_req_step *next)
 {
+       if (irc->prev == NULL) {
+               struct isi_voicecall *ivc = ofono_voicecall_get_data(irc->ovc);
+
+               irc->prev = &ivc->queue;
+               if ((irc->next = *irc->prev))
+                       irc->next->prev = &irc->next;
+               *irc->prev = irc;
+       }
+
+       irc->step = next;
 }
 
-static void isi_hangup(struct ofono_voicecall *vc,
-                       ofono_voicecall_cb_t cb, void *data)
+static void isi_ctx_remove(struct isi_call_req_context *irc)
 {
+       if (irc->prev) {
+               if ((*irc->prev = irc->next)) {
+                       irc->next->prev = irc->prev;
+                       irc->next = NULL;
+               }
+               irc->prev = NULL;
+       }
 }
 
-static void isi_list_calls(struct ofono_voicecall *vc,
-                       ofono_call_list_cb_t cb, void *data)
+static void isi_ctx_free(struct isi_call_req_context *irc)
 {
+       if (irc) {
+               isi_ctx_remove(irc);
+               g_free(irc);
+       }
 }
 
-static void isi_hold_all_active(struct ofono_voicecall *vc,
-                       ofono_voicecall_cb_t cb, void *data)
+static bool isi_ctx_return(struct isi_call_req_context *irc,
+                          enum ofono_error_type type,
+                          int error)
 {
+       if (!irc)
+               return true;
+
+       if (irc->cb) {
+               struct ofono_error e = { .type = type, .error = error };
+               irc->cb(&e, irc->data);
+       }
+
+       isi_ctx_free(irc);
+
+       return true;
 }
 
-static void isi_release_all_held(struct ofono_voicecall *vc,
-                       ofono_voicecall_cb_t cb, void *data)
+static bool isi_ctx_return_failure(struct isi_call_req_context *irc)
+{
+       return isi_ctx_return(irc, OFONO_ERROR_TYPE_FAILURE, 0);
+}
+
+static bool isi_ctx_return_success(struct isi_call_req_context *irc)
+{
+       if (irc && irc->step) {
+               irc->step(irc, 0);
+               return true;
+       }
+
+       return isi_ctx_return(irc, OFONO_ERROR_TYPE_NO_ERROR, 0);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Decoding subblocks */
+
+static void isi_call_any_address_sb_proc(struct isi_voicecall *ivc,
+                                        struct isi_call *call,
+                                        GIsiSubBlockIter const *sb)
+{
+       uint8_t addr_type, presentation, addr_len;
+       char *address;
+
+       if (!g_isi_sb_iter_get_byte(sb, &addr_type, 2) ||
+           !g_isi_sb_iter_get_byte(sb, &presentation, 3) ||
+           /* fillerbyte */
+           !g_isi_sb_iter_get_byte(sb, &addr_len, 5) ||
+           !g_isi_sb_iter_get_alpha_tag(sb, &address, 2 * addr_len, 6))
+               return;
+
+       call->addr_type = addr_type | 0x80;
+       call->presentation = presentation;
+       strncpy(call->address, address, sizeof call->address);
+
+       g_free(address);
+}
+
+static void isi_call_origin_address_sb_proc(struct isi_voicecall *ivc,
+                                           struct isi_call *call,
+                                           GIsiSubBlockIter const *sb)
+{
+       if (!call->address[0])
+               isi_call_any_address_sb_proc(ivc, call, sb);
+}
+
+static void isi_call_destination_address_sb_proc(struct isi_voicecall *ivc,
+                                                struct isi_call *call,
+                                                GIsiSubBlockIter const *sb)
+{
+       if (!call->address[0])
+               isi_call_any_address_sb_proc(ivc, call, sb);
+}
+
+static void isi_call_mode_sb_proc(struct isi_voicecall *ivc,
+                                 struct isi_call *call,
+                                 GIsiSubBlockIter const *sb)
+{
+       uint8_t mode, mode_info;
+
+       if (!g_isi_sb_iter_get_byte(sb, &mode, 2) ||
+           !g_isi_sb_iter_get_byte(sb, &mode_info, 3))
+               return;
+
+       call->mode = mode;
+       call->mode_info = mode_info;
+}
+
+static void isi_call_cause_sb_proc(struct isi_voicecall *ivc,
+                                  struct isi_call *call,
+                                  GIsiSubBlockIter const *sb)
+{
+       uint8_t cause_type, cause;
+
+       if (!g_isi_sb_iter_get_byte(sb, &cause_type, 2) ||
+           !g_isi_sb_iter_get_byte(sb, &cause, 3))
+               return;
+
+       call->cause_type = cause_type;
+       call->cause = cause;
+}
+
+static void isi_call_status_sb_proc(struct isi_voicecall *ivc,
+                                   struct isi_call *call,
+                                   GIsiSubBlockIter const *sb)
+{
+       uint8_t status;
+
+       if (!g_isi_sb_iter_get_byte(sb, &status, 2))
+               return;
+
+       call->status = status;
+}
+
+static struct isi_call *
+isi_call_status_info_sb_proc(struct isi_voicecall *ivc,
+                            struct isi_call calls[8],
+                            GIsiSubBlockIter const *sb)
 {
+       struct isi_call *call = NULL;
+       int i;
+       uint8_t call_id;
+       uint8_t mode;
+       uint8_t mode_info;
+       uint8_t status;
+
+       if (!g_isi_sb_iter_get_byte(sb, &call_id, 2) ||
+           !g_isi_sb_iter_get_byte(sb, &mode, 3) ||
+           !g_isi_sb_iter_get_byte(sb, &mode_info, 4) ||
+           !g_isi_sb_iter_get_byte(sb, &status, 5))
+               return NULL;
+
+       i = call_id & 7;
+
+       if (1 <= i && i <= 7) {
+               call = &calls[i];
+               call->call_id = call_id;
+               call->status = status;
+               call->mode = mode;
+               call->mode_info = mode_info;
+       }
+
+       return call;
 }
 
-static void isi_set_udub(struct ofono_voicecall *vc,
+static struct isi_call *
+isi_call_addr_and_status_info_sb_proc(struct isi_voicecall *ivc,
+                                     struct isi_call calls[8],
+                                     GIsiSubBlockIter const *sb)
+{
+       struct isi_call *call = NULL;
+       int i;
+       uint8_t call_id;
+       uint8_t mode;
+       uint8_t mode_info;
+       uint8_t status;
+       uint8_t addr_type;
+       uint8_t presentation;
+       uint8_t addr_len;
+       char *address;
+
+       if (!g_isi_sb_iter_get_byte(sb, &call_id, 2) ||
+           !g_isi_sb_iter_get_byte(sb, &mode, 3) ||
+           !g_isi_sb_iter_get_byte(sb, &mode_info, 4) ||
+           !g_isi_sb_iter_get_byte(sb, &status, 5) ||
+           !g_isi_sb_iter_get_byte(sb, &addr_type, 8) ||
+           !g_isi_sb_iter_get_byte(sb, &presentation, 9) ||
+           !g_isi_sb_iter_get_byte(sb, &addr_len, 11) ||
+           !g_isi_sb_iter_get_alpha_tag(sb, &address, 2 * addr_len, 12))
+               return NULL;
+
+       i = call_id & 7;
+
+       if (1 <= i && i <= 7) {
+               call = &calls[i];
+               call->call_id = call_id;
+               call->status = status;
+               call->mode = mode;
+               call->mode_info = mode_info;
+               call->addr_type = addr_type | 0x80;
+               call->presentation = presentation;
+               strncpy(call->address, address, sizeof call->address);
+       }
+
+       free(address);
+
+       return call;
+}
+
+/* ------------------------------------------------------------------------- */
+/* PN_CALL messages */
+
+static GIsiResponse isi_call_status_resp,
+       isi_call_create_resp,
+       isi_call_answer_resp,
+       isi_call_release_resp,
+       isi_call_control_resp,
+       isi_call_dtmf_send_resp;
+
+static struct isi_call_req_context *
+isi_call_create_req(struct ofono_voicecall *ovc,
+                   uint8_t presentation,
+                   uint8_t addr_type,
+                   char const restrict address[21],
+                   ofono_voicecall_cb_t cb,
+                   void *data)
+{
+       size_t addr_len = strlen(address);
+       size_t sub_len = (6 + 2 * addr_len + 3) & ~3;
+       size_t i, offset = 3 + 4 + 8 + 6;
+       uint8_t req[3 + 4 + 8 + 6 + 40] = {
+               CALL_CREATE_REQ,
+               0,              /* No id */
+               3,              /* Mode, Clir, Number */
+               /* MODE SB */
+               CALL_MODE, 4, CALL_MODE_SPEECH, CALL_MODE_INFO_NONE,
+               /* ORIGIN_INFO SB */
+               CALL_ORIGIN_INFO, 8, presentation, 0, 0, 0, 0, 0,
+               /* DESTINATION_ADDRESS SB */
+               CALL_DESTINATION_ADDRESS,
+               sub_len,
+               addr_type & 0x7F,
+               0, 0,
+               addr_len,
+               /* uint16_t addr[20] */
+       };
+       size_t rlen = 3 + 4 + 8 + sub_len;
+
+       if (addr_len > 20) {
+               CALLBACK_WITH_FAILURE(cb, data);
+               return NULL;
+       }
+
+       for (i = 0; i < addr_len; i++)
+               req[offset + 2 * i + 1] = address[i];
+
+       return isi_call_req(ovc, req, rlen, isi_call_create_resp, cb, data);
+}
+
+static bool isi_call_create_resp(GIsiClient *client,
+                                void const * restrict data,
+                                size_t len,
+                                uint16_t object,
+                                void *irc)
+{
+       struct {
+               uint8_t message_id, call_id, sub_blocks;
+       } const *m = data;
+
+       if (m != NULL && len < (sizeof *m))
+               return false;
+       if (m == NULL || m->message_id == COMMON_MESSAGE)
+               return isi_ctx_return_failure(irc);
+       if (m->message_id != CALL_CREATE_RESP)
+               return false;
+
+       if (m->call_id != CALL_ID_NONE && m->sub_blocks == 0)
+               return isi_ctx_return_success(irc);
+
+       /* Cause ? */
+       return isi_ctx_return_failure(irc);
+}
+
+static void isi_call_status_ind_cb(GIsiClient *client,
+                                  void const * restrict data,
+                                  size_t len,
+                                  uint16_t object,
+                                  void *_ovc)
+{
+       struct ofono_voicecall *ovc = _ovc;
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+       struct {
+               uint8_t message_id, call_id, sub_blocks;
+       } const *m = data;
+       struct isi_call *call;
+       uint8_t old;
+       GIsiSubBlockIter sb[1];
+
+       if (len < 3)
+               return;         /* runt */
+
+       if ((m->call_id & 7) == 0)
+               return;
+
+       call = &ivc->calls[m->call_id & 7];
+
+       old = call->status;
+       call->call_id = m->call_id;
+
+       for (g_isi_sb_iter_init(sb, data, len, (sizeof *m));
+            g_isi_sb_iter_is_valid(sb);
+            g_isi_sb_iter_next(sb)) {
+               switch (g_isi_sb_iter_get_id(sb)) {
+               case CALL_STATUS:
+                       isi_call_status_sb_proc(ivc, call, sb);
+                       break;
+
+               case CALL_MODE:
+                       isi_call_mode_sb_proc(ivc, call, sb);
+                       break;
+
+               case CALL_CAUSE:
+                       isi_call_cause_sb_proc(ivc, call, sb);
+                       break;
+
+               case CALL_DESTINATION_ADDRESS:
+                       isi_call_destination_address_sb_proc(ivc, call, sb);
+                       break;
+
+               case CALL_ORIGIN_ADDRESS:
+                       isi_call_origin_address_sb_proc(ivc, call, sb);
+                       break;
+
+               case CALL_GSM_DETAILED_CAUSE:
+               case CALL_DESTINATION_PRE_ADDRESS:
+               case CALL_DESTINATION_POST_ADDRESS:
+               case CALL_DESTINATION_SUBADDRESS:
+               case CALL_GSM_EVENT_INFO:
+               case CALL_NW_CAUSE:
+                       break;
+               }
+       }
+
+       if (old != call->status) {
+               if (call->status == CALL_STATUS_IDLE) {
+                       call->status = CALL_STATUS_TERMINATED;
+                       isi_call_notify(ovc, call);
+                       isi_call_set_idle(call);
+                       return;
+               }
+       }
+
+       isi_call_notify(ovc, call);
+}
+
+static struct isi_call_req_context *
+isi_call_answer_req(struct ofono_voicecall *ovc,
+                   uint8_t call_id,
+                   ofono_voicecall_cb_t cb,
+                   void *data)
+{
+       uint8_t const req[] = {
+               CALL_ANSWER_REQ, call_id, 0
+       };
+       size_t rlen = sizeof req;
+
+       return isi_call_req(ovc, req, rlen, isi_call_answer_resp, cb, data);
+}
+
+static bool isi_call_answer_resp(GIsiClient *client,
+                                void const * restrict data,
+                                size_t len,
+                                uint16_t object,
+                                void *irc)
+{
+       struct {
+               uint8_t message_id, call_id, sub_blocks;
+       } const *m = data;
+
+       if (m != NULL && len < (sizeof *m))
+               return false;
+       if (m == NULL || m->message_id == COMMON_MESSAGE)
+               return isi_ctx_return_failure(irc);
+       if (m->message_id != CALL_ANSWER_RESP)
+               return false;
+
+       if (m->call_id != CALL_ID_NONE && m->sub_blocks == 0)
+               return isi_ctx_return_success(irc);
+
+       /* Cause ? */
+       return isi_ctx_return_failure(irc);
+}
+
+static struct isi_call_req_context *
+isi_call_release_req(struct ofono_voicecall *ovc,
+                    uint8_t call_id,
+                    enum isi_call_cause_type cause_type,
+                    uint8_t cause,
+                    ofono_voicecall_cb_t cb,
+                    void *data)
+{
+       uint8_t const req[] = {
+               CALL_RELEASE_REQ, call_id, 1,
+               CALL_CAUSE, 4, cause_type, cause,
+       };
+       size_t rlen = sizeof req;
+
+       return isi_call_req(ovc, req, rlen, isi_call_release_resp, cb, data);
+}
+
+static bool isi_call_release_resp(GIsiClient *client,
+                                    void const * restrict data,
+                                    size_t len,
+                                    uint16_t object,
+                                    void *irc)
+{
+       struct {
+               uint8_t message_id, call_id, sub_blocks;
+       } const *m = data;
+       GIsiSubBlockIter i[1];
+       uint8_t cause_type = 0, cause = 0;
+
+       if (m != NULL && len < (sizeof *m))
+               return false;
+       if (m == NULL || m->message_id == COMMON_MESSAGE)
+               return isi_ctx_return_failure(irc);
+       if (m->message_id != CALL_RELEASE_RESP)
+               return false;
+
+       for (g_isi_sb_iter_init(i, m, len, (sizeof *m));
+            g_isi_sb_iter_is_valid(i);
+            g_isi_sb_iter_next(i)) {
+               if (g_isi_sb_iter_get_id(i) != CALL_CAUSE ||
+                   !g_isi_sb_iter_get_byte(i, &cause_type, 2) ||
+                   !g_isi_sb_iter_get_byte(i, &cause, 3))
+                       continue;
+       }
+
+       if ((cause_type == CALL_CAUSE_TYPE_SERVER ||
+            cause_type == CALL_CAUSE_TYPE_CLIENT) &&
+           (cause == CALL_CAUSE_RELEASE_BY_USER ||
+            cause == CALL_CAUSE_BUSY_USER_REQUEST))
+               return isi_ctx_return_success(irc);
+       else
+               return isi_ctx_return_failure(irc);
+}
+
+static struct isi_call_req_context *
+isi_call_status_req(struct ofono_voicecall *ovc,
+                   uint8_t id,
+                   uint8_t mode,
+                   ofono_voicecall_cb_t cb,
+                   void *data)
+{
+       unsigned char req[] = {
+               CALL_STATUS_REQ, id, 1,
+               CALL_STATUS_MODE, 4, mode, 0,
+       };
+       size_t rlen = sizeof req;
+
+       return isi_call_req(ovc, req, rlen, isi_call_status_resp, cb, data);
+}
+
+
+static bool isi_call_status_resp(GIsiClient *client,
+                                void const * restrict data,
+                                size_t len,
+                                uint16_t object,
+                                void *_irc)
+{
+       struct isi_call_req_context *irc = _irc;
+       struct ofono_voicecall *ovc = irc->ovc;
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+       struct {
+               uint8_t message_id, call_id, sub_blocks;
+       } const *m = data;
+       GIsiSubBlockIter sb[1];
+       struct isi_call *call = NULL;
+
+       if (m != NULL && len < (sizeof *m))
+               return false;
+       if (m == NULL || m->message_id == COMMON_MESSAGE)
+               return isi_ctx_return_failure(irc);
+       if (m->message_id != CALL_STATUS_RESP)
+               return false;
+
+       for (g_isi_sb_iter_init(sb, m, len, (sizeof *m));
+            g_isi_sb_iter_is_valid(sb);
+            g_isi_sb_iter_next(sb)) {
+               switch (g_isi_sb_iter_get_id(sb)) {
+
+               case CALL_STATUS_INFO:
+                       call = isi_call_status_info_sb_proc(ivc, ivc->calls, sb);
+                       break;
+
+               case CALL_ADDR_AND_STATUS_INFO:
+                       call = isi_call_addr_and_status_info_sb_proc(ivc, ivc->calls, sb);
+                       if (call)
+                               isi_call_notify(ovc, call);
+                       break;
+
+               case CALL_CAUSE:
+                       if (call)
+                               isi_call_cause_sb_proc(ivc, call, sb);
+                       break;
+               }
+       }
+
+       return isi_ctx_return_success(irc);
+}
+
+static struct isi_call_req_context *
+isi_call_control_req(struct ofono_voicecall *ovc,
+                    uint8_t call_id,
+                    enum isi_call_operation op,
+                    uint8_t info,
+                    ofono_voicecall_cb_t cb,
+                    void *data)
+{
+       uint8_t const req[] = {
+               CALL_CONTROL_REQ, call_id, 1,
+               CALL_OPERATION, 4, op, info,
+       };
+       size_t rlen = sizeof req;
+
+       return isi_call_req(ovc, req, rlen, isi_call_control_resp, cb, data);
+}
+
+static struct isi_call_req_context *
+isi_call_deflect_req(struct ofono_voicecall *ovc,
+                    uint8_t call_id,
+                    uint8_t address_type,
+                    char const restrict address[21],
+                    ofono_voicecall_cb_t cb,
+                    void *data)
+{
+       size_t addr_len = strlen(address);
+       size_t sub_len = (6 + 2 * addr_len + 3) & ~3;
+       size_t i, offset = 3 + 4 + 6;
+       size_t rlen = 3 + 4 + sub_len;
+       uint8_t req[3 + 4 + 6 + 40] = {
+               CALL_CONTROL_REQ, call_id, 2,
+               CALL_OPERATION, 4, CALL_GSM_OP_DEFLECT, 0,
+               CALL_GSM_DEFLECTION_ADDRESS, sub_len,
+               address_type & 0x7F,
+               0x7,            /* default presentation */
+               0,              /* filler */
+               addr_len,
+       };
+
+       if (addr_len > 20) {
+               CALLBACK_WITH_FAILURE(cb, data);
+               return false;
+       }
+
+       for (i = 0; i < addr_len; i++)
+               req[offset + 2 * i + 1] = address[i];
+
+       return isi_call_req(ovc, req, rlen, isi_call_control_resp, cb, data);
+}
+
+static bool isi_call_control_resp(GIsiClient *client,
+                                 void const * restrict data,
+                                 size_t len,
+                                 uint16_t object,
+                                 void *irc)
+{
+       struct {
+               uint8_t message_id, call_id, sub_blocks;
+       } const *m = data;
+       GIsiSubBlockIter i[1];
+       uint8_t cause_type = 0, cause = 0;
+
+       if (m != NULL && len < sizeof *m)
+               return false;
+       if (m == NULL || m->message_id == COMMON_MESSAGE)
+               return isi_ctx_return_failure(irc);
+       if (m->message_id != CALL_CONTROL_RESP)
+               return false;
+
+       for (g_isi_sb_iter_init(i, m, len, (sizeof *m));
+            g_isi_sb_iter_is_valid(i);
+            g_isi_sb_iter_next(i)) {
+               if (g_isi_sb_iter_get_id(i) != CALL_CAUSE ||
+                   !g_isi_sb_iter_get_byte(i, &cause_type, 2) ||
+                   !g_isi_sb_iter_get_byte(i, &cause, 3))
+                       continue;
+       }
+
+       if (!cause)
+               return isi_ctx_return_success(irc);
+       else
+               return isi_ctx_return_failure(irc);
+}
+
+static struct isi_call_req_context *
+isi_call_dtmf_send_req(struct ofono_voicecall *ovc,
+                      uint8_t call_id,
+                      char const *string,
+                      ofono_voicecall_cb_t cb,
+                      void *data)
+{
+       size_t str_len = strlen(string);
+       size_t sub_len = 4 + ((2 * str_len + 3) & ~3);
+       size_t i, offset = 3 + 4 + 8 + 4;
+       size_t rlen = 3 + 4 + 8 + sub_len;
+       uint8_t req[3 + 4 + 8 + (255 & ~3)] = {
+               CALL_DTMF_SEND_REQ, call_id, 3,
+               CALL_DTMF_INFO, 4, CALL_DTMF_ENABLE_TONE_IND_SEND, 0,
+               CALL_DTMF_TIMERS, 8,
+               0, 200, /* duration in ms */
+               0, 100, /* gap in ms */
+               0, 0,   /* filler */
+               CALL_DTMF_STRING, sub_len,
+               100,     /* pause length */
+               str_len,
+               /* string */
+       };
+
+       if (sub_len >= 256) {
+               CALLBACK_WITH_FAILURE(cb, data);
+               return false;
+       }
+
+       for (i = 0; i < str_len; i++)
+               req[offset + 2 * i + 1] = string[i];
+
+       return isi_call_req(ovc, req, rlen, isi_call_dtmf_send_resp, cb, data);
+}
+
+static bool isi_call_dtmf_send_resp(GIsiClient *client,
+                                   void const * restrict data,
+                                   size_t len,
+                                   uint16_t object,
+                                   void *irc)
+{
+       struct {
+               uint8_t message_id, call_id, sub_blocks;
+       } const *m = data;
+       GIsiSubBlockIter i[1];
+       uint8_t cause_type = 0, cause = 0;
+
+       if (m != NULL && len < (sizeof *m))
+               return false;
+       if (m == NULL || m->message_id == COMMON_MESSAGE)
+               return isi_ctx_return_failure(irc);
+       if (m->message_id != CALL_DTMF_SEND_RESP)
+               return false;
+
+       if (m->sub_blocks == 0)
+               return isi_ctx_return_success(irc);
+
+       for (g_isi_sb_iter_init(i, m, len, (sizeof *m));
+            g_isi_sb_iter_is_valid(i);
+            g_isi_sb_iter_next(i)) {
+               if (g_isi_sb_iter_get_id(i) != CALL_CAUSE ||
+                   !g_isi_sb_iter_get_byte(i, &cause_type, 2) ||
+                   !g_isi_sb_iter_get_byte(i, &cause, 3))
+                       continue;
+       }
+
+       if (!cause)
+               return isi_ctx_return_success(irc);
+       else
+               return isi_ctx_return_failure(irc);
+}
+
+
+/* ------------------------------------------------------------------------- */
+/* Notify */
+
+static void isi_call_notify(struct ofono_voicecall *ovc,
+                           struct isi_call *call)
+{
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+       struct isi_call_req_context *irc, **queue;
+       struct ofono_call ocall;
+
+       DBG("called with status=%s (0x%02X)",
+           isi_call_status_name(call->status), call->status);
+
+       for (queue = &ivc->queue; (irc = *queue);) {
+               irc->step(irc, call->status);
+               if (*queue == irc)
+                       queue = &irc->next;
+       }
+
+       switch (call->status) {
+       case CALL_STATUS_IDLE:
+       case CALL_STATUS_MO_RELEASE:
+       case CALL_STATUS_MT_RELEASE:
+       case CALL_STATUS_TERMINATED:
+               isi_call_release(ovc, call);
+               return;
+       }
+
+       ocall = isi_call_as_ofono_call(call);
+
+       DBG("id=%u,%s,%u,\"%s\",%u,%u",
+           ocall.id,
+           ocall.direction ? "terminated" : "originated",
+           ocall.status,
+           ocall.phone_number.number,
+           ocall.phone_number.type,
+           ocall.clip_validity);
+
+       ofono_voicecall_notify(ovc, &ocall);
+}
+
+static void isi_call_release(struct ofono_voicecall *ovc,
+                            struct isi_call *call)
+{
+       struct ofono_error error = { OFONO_ERROR_TYPE_NO_ERROR, 0 };
+       enum ofono_disconnect_reason reason;
+
+       switch (call->status) {
+       case CALL_STATUS_IDLE:
+               reason = OFONO_DISCONNECT_REASON_UNKNOWN;
+               break;
+       case CALL_STATUS_MO_RELEASE:
+               reason = OFONO_DISCONNECT_REASON_LOCAL_HANGUP;
+               break;
+       case CALL_STATUS_MT_RELEASE:
+               reason = OFONO_DISCONNECT_REASON_REMOTE_HANGUP;
+               break;
+       case CALL_STATUS_TERMINATED:
+       default:
+               reason = OFONO_DISCONNECT_REASON_ERROR;
+               break;
+       }
+
+       if (!call->reason) {
+               call->reason = reason;
+               DBG("disconnected id=%u reason=%u", call->id, reason);
+               ofono_voicecall_disconnected(ovc, call->id, reason, &error);
+       }
+
+       if (!reason)
+               isi_call_set_idle(call);
+}
+
+static struct ofono_call
+isi_call_as_ofono_call(struct isi_call const *call)
+{
+       struct ofono_call ocall = { call->id };
+       struct ofono_phone_number *number = &ocall.phone_number;
+
+       ocall.type = 0; /* Voice call */
+       ocall.direction = call->mode_info & CALL_MODE_ORIGINATOR;
+       ocall.status = isi_call_status_to_clcc(call);
+       memcpy(number->number, call->address, sizeof number->number);
+       number->type = 0x80 | call->addr_type;
+       ocall.clip_validity = call->presentation & 3;
+       if (ocall.clip_validity == 0 && strlen(number->number) == 0)
+               ocall.clip_validity = 2;
+
+       return ocall;
+}
+
+/** Get +CLCC status */
+static int
+isi_call_status_to_clcc(struct isi_call const *call)
+{
+       switch (call->status) {
+       case CALL_STATUS_CREATE:
+               return 2;
+       case CALL_STATUS_COMING:
+               return 4;
+       case CALL_STATUS_PROCEEDING:
+               if ((call->mode_info & CALL_MODE_ORIGINATOR))
+                       return 4;
+               else
+                       return 2;
+       case CALL_STATUS_MO_ALERTING:
+               return 3;
+       case CALL_STATUS_MT_ALERTING:
+               return 4;
+       case CALL_STATUS_WAITING:
+               return 5;
+
+       case CALL_STATUS_ANSWERED:
+       case CALL_STATUS_ACTIVE:
+       case CALL_STATUS_MO_RELEASE:
+       case CALL_STATUS_MT_RELEASE:
+       case CALL_STATUS_HOLD_INITIATED:
+               return 0;
+
+       case CALL_STATUS_HOLD:
+       case CALL_STATUS_RETRIEVE_INITIATED:
+               return 1;
+
+       case CALL_STATUS_RECONNECT_PENDING:
+       case CALL_STATUS_TERMINATED:
+       case CALL_STATUS_SWAP_INITIATED:
+               return 0;
+       }
+
+       return 0;
+}
+
+static struct isi_call *isi_call_set_idle(struct isi_call *call)
+{
+       uint8_t id;
+
+       if (call) {
+               id = call->id;
+               memset(call, 0, sizeof *call);
+               call->id = id;
+       }
+
+       return call;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void isi_dial(struct ofono_voicecall *ovc,
+                    const struct ofono_phone_number * restrict number,
+                    enum ofono_clir_option clir,
+                    enum ofono_cug_option cug,
+                    ofono_voicecall_cb_t cb,
+                    void *data)
+{
+       unsigned char presentation = CALL_GSM_PRESENTATION_DEFAULT;
+
+       switch (clir)
+       {
+       case OFONO_CLIR_OPTION_DEFAULT:
+               presentation = CALL_GSM_PRESENTATION_DEFAULT;
+               break;
+       case OFONO_CLIR_OPTION_INVOCATION:
+               presentation = CALL_PRESENTATION_RESTRICTED;
+               break;
+       case OFONO_CLIR_OPTION_SUPPRESSION:
+               presentation = CALL_PRESENTATION_ALLOWED;
+               break;
+       }
+
+       switch (cug)
+       {
+       case OFONO_CUG_OPTION_DEFAULT:
+               break;
+       case OFONO_CUG_OPTION_INVOCATION:
+               /* Not implemented */
+               CALLBACK_WITH_FAILURE(cb, data);
+               return;
+       }
+
+       isi_call_create_req(ovc, presentation,
+                           number->type,
+                           number->number,
+                           cb, data);
+}
+
+static void isi_answer(struct ofono_voicecall *ovc,
+                      ofono_voicecall_cb_t cb, void *data)
+{
+       isi_call_answer_req(ovc, CALL_ID_ALL, cb, data);
+}
+
+static void isi_hangup(struct ofono_voicecall *ovc,
                        ofono_voicecall_cb_t cb, void *data)
 {
+       /* AT+CHUP */
+       isi_call_release_req(ovc, CALL_ID_ALL,
+                            CALL_CAUSE_TYPE_CLIENT,
+                            CALL_CAUSE_RELEASE_BY_USER,
+                            cb, data);
+}
+
+static void isi_list_calls(struct ofono_voicecall *ovc,
+                          ofono_call_list_cb_t cb, void *data)
+{
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+       struct ofono_call calls[7];
+       int n, i;
+
+       for (i = 1, n = 0; i <= 7; i++) {
+               if (ivc->calls[i].status != CALL_STATUS_IDLE)
+                       calls[n++] = isi_call_as_ofono_call(&ivc->calls[i]);
+       }
+
+       CALLBACK_WITH_FAILURE(cb, n, calls, data);
 }
 
-static void isi_release_all_active(struct ofono_voicecall *vc,
+static void isi_release_all_held(struct ofono_voicecall *ovc,
                        ofono_voicecall_cb_t cb, void *data)
 {
+       /* AT+CHLD=0 (w/out incoming calls) */
+       isi_call_release_req(ovc, CALL_ID_HOLD,
+                            CALL_CAUSE_TYPE_CLIENT,
+                            CALL_CAUSE_RELEASE_BY_USER,
+                            cb, data);
 }
 
-static void isi_release_specific(struct ofono_voicecall *vc, int id,
+static void isi_set_udub(struct ofono_voicecall *ovc,
                        ofono_voicecall_cb_t cb, void *data)
 {
+       /* AT+CHLD=0 (w/ incoming calls) */
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+       int id = 0;
+
+       for (id = 1; id <= 7; id++) {
+               if (ivc->calls[id].status == CALL_STATUS_WAITING)
+                       break;
+               if (ivc->calls[id].status == CALL_STATUS_MT_ALERTING)
+                       break;
+               if (ivc->calls[id].status == CALL_STATUS_COMING)
+                       break;
+       }
+
+       if (id <= 7)
+               isi_call_release_req(ovc, id,
+                                    CALL_CAUSE_TYPE_CLIENT,
+                                    CALL_CAUSE_BUSY_USER_REQUEST,
+                                    cb, data);
+       else
+               CALLBACK_WITH_FAILURE(cb, data);
+}
+
+static void isi_retrieve(struct ofono_voicecall *ovc,
+                      ofono_voicecall_cb_t cb, void *data)
+{
+       isi_call_control_req(ovc, CALL_ID_HOLD, CALL_OP_RETRIEVE, 0,
+                            cb, data);
+}
+
+static isi_call_req_step isi_wait_and_answer, isi_wait_and_retrieve;
+
+static void isi_release_all_active(struct ofono_voicecall *ovc,
+                                  ofono_voicecall_cb_t cb, void *data)
+{
+       /* AT+CHLD=1 */
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+       int id = 0, waiting = 0, active = 0, hold = 0;
+
+       for (id = 1; id <= 7; id++) {
+               if (ivc->calls[id].call_id & CALL_ID_WAITING)
+                       waiting++;
+               if (ivc->calls[id].call_id & CALL_ID_HOLD)
+                       hold++;
+               if (ivc->calls[id].call_id & CALL_ID_ACTIVE)
+                       active++;
+       }
+
+       if (active) {
+               struct isi_call_req_context *irc;
+
+               irc = isi_call_release_req(ovc, CALL_ID_ACTIVE,
+                                          CALL_CAUSE_TYPE_CLIENT,
+                                          CALL_CAUSE_RELEASE_BY_USER,
+                                          cb, data);
+
+               if (irc == NULL)
+                       ;
+               else if (waiting)
+                       isi_ctx_queue(irc, isi_wait_and_answer);
+               else if (hold)
+                       isi_ctx_queue(irc, isi_wait_and_retrieve);
+       }
+       else
+               CALLBACK_WITH_FAILURE(cb, data);
+}
+
+static void isi_wait_and_answer(struct isi_call_req_context *irc,
+                               int event)
+{
+       DBG("irc=%p event=%u", (void *)irc, event);
+       switch (event) {
+       case CALL_STATUS_TERMINATED:
+               isi_answer(irc->ovc, irc->cb, irc->data);
+               isi_ctx_free(irc);
+               break;
+       }
 }
 
-static void isi_private_chat(struct ofono_voicecall *vc, int id,
+static void isi_wait_and_retrieve(struct isi_call_req_context *irc,
+                               int event)
+{
+       DBG("irc=%p event=%u", (void *)irc, event);
+       switch (event) {
+       case CALL_STATUS_TERMINATED:
+               isi_retrieve(irc->ovc, irc->cb, irc->data);
+               isi_ctx_free(irc);
+               break;
+       }
+}
+
+static void isi_hold_all_active(struct ofono_voicecall *ovc,
                        ofono_voicecall_cb_t cb, void *data)
 {
+       /* AT+CHLD=2 */
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+       int id = 0, op = 0, waiting = 0, active = 0, hold = 0;
+
+       for (id = 1; id <= 7; id++) {
+               if (ivc->calls[id].call_id & CALL_ID_WAITING)
+                       waiting++;
+               if (ivc->calls[id].call_id & CALL_ID_HOLD)
+                       hold++;
+               if (ivc->calls[id].call_id & CALL_ID_ACTIVE)
+                       active++;
+       }
+
+       if (waiting) {
+               isi_call_answer_req(ovc, CALL_ID_WAITING, cb, data);
+       }
+       else if (hold) {
+               if (active) {
+                       id = CALL_ID_ACTIVE, op = CALL_OP_SWAP;
+               }
+               else {
+                       id = CALL_ID_HOLD, op = CALL_OP_RETRIEVE;
+               }
+               isi_call_control_req(ovc, id, op, 0, cb, data);
+       }
+       else if (active) {
+               id = CALL_ID_ACTIVE, op = CALL_OP_HOLD;
+               isi_call_control_req(ovc, id, op, 0, cb, data);
+       }
+       else {
+               CALLBACK_WITH_FAILURE(cb, data);
+       }
 }
 
-static void isi_create_multiparty(struct ofono_voicecall *vc,
+static void isi_release_specific(struct ofono_voicecall *ovc, int id,
                        ofono_voicecall_cb_t cb, void *data)
 {
+       /* AT+CHLD=1X */
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+
+       if (1 <= id && id <= 7) {
+               struct isi_call const *status = &ivc->calls[id];
+               uint8_t cause = CALL_CAUSE_RELEASE_BY_USER;
+
+               switch (status->status) {
+               case CALL_STATUS_MT_ALERTING:
+               case CALL_STATUS_WAITING:
+                       cause = CALL_CAUSE_BUSY_USER_REQUEST;
+                       break;
+               case CALL_STATUS_PROCEEDING:
+                       if ((status->mode_info & CALL_MODE_ORIGINATOR))
+                               cause = CALL_CAUSE_BUSY_USER_REQUEST;
+                       break;
+               }
+
+               isi_call_release_req(ovc, id,
+                                    CALL_CAUSE_TYPE_CLIENT, cause,
+                                    cb, data);
+       }
+       else
+               CALLBACK_WITH_FAILURE(cb, data);
+}
+
+static void isi_private_chat(struct ofono_voicecall *ovc, int id,
+                            ofono_voicecall_cb_t cb, void *data)
+{
+       /* AT+CHLD=2X */
+       if (1 <= id && id <= 7)
+               isi_call_control_req(ovc,
+                                    id, CALL_OP_CONFERENCE_SPLIT, 0,
+                                    cb, data);
+       else
+               CALLBACK_WITH_FAILURE(cb, data);
+}
+
+static void isi_create_multiparty(struct ofono_voicecall *ovc,
+                                 ofono_voicecall_cb_t cb, void *data)
+{
+       /* AT+CHLD=3 */
+       isi_call_control_req(ovc,
+                            CALL_ID_ALL, CALL_OP_CONFERENCE_BUILD, 0,
+                            cb, data);
 }
 
-static void isi_transfer(struct ofono_voicecall *vc,
+static void isi_transfer(struct ofono_voicecall *ovc,
                        ofono_voicecall_cb_t cb, void *data)
 {
+       /* AT+CHLD=4 */
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+
+       uint8_t id;
+
+       for (id = 1; id <= 7; id++)
+               if (ivc->calls[id].status == CALL_STATUS_MO_ALERTING)
+                       break;
+       if (id > 7)
+               id = CALL_ID_ACTIVE;
+
+       isi_call_control_req(ovc,
+                            id, CALL_GSM_OP_TRANSFER, 0,
+                            cb, data);
 }
 
-static void isi_deflect(struct ofono_voicecall *vc,
+static void isi_deflect(struct ofono_voicecall *ovc,
                        const struct ofono_phone_number *ph,
                        ofono_voicecall_cb_t cb, void *data)
 {
+       /* AT+CTFR=<number>,<type> */
+       int id = CALL_ID_WAITING;
+       isi_call_deflect_req(ovc, id, ph->type, ph->number,
+                            cb, data);
 }
 
-static void isi_swap_without_accept(struct ofono_voicecall *vc,
+static void isi_swap_without_accept(struct ofono_voicecall *ovc,
                        ofono_voicecall_cb_t cb, void *data)
 {
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+       int id = 0, op = 0, active = 0, hold = 0;
+
+       for (id = 1; id <= 7; id++) {
+               if (ivc->calls[id].call_id & CALL_ID_HOLD)
+                       hold++;
+               if (ivc->calls[id].call_id & CALL_ID_ACTIVE)
+                       active++;
+       }
+
+       if (hold && active) {
+               id = CALL_ID_ACTIVE, op = CALL_OP_SWAP;
+       }
+       else if (active) {
+               id = CALL_ID_ACTIVE, op = CALL_OP_HOLD;
+       }
+       else if (hold) {
+               id = CALL_ID_HOLD, op = CALL_OP_RETRIEVE;
+       }
+       else {
+               CALLBACK_WITH_FAILURE(cb, data);
+               return;
+       }
+
+       isi_call_control_req(ovc, id, op, 0, cb, data);
 }
 
-static void isi_send_tones(struct ofono_voicecall *vc, const char *tones,
-                       ofono_voicecall_cb_t cb, void *data)
+static void isi_send_tones(struct ofono_voicecall *ovc, const char *tones,
+                          ofono_voicecall_cb_t cb, void *data)
 {
+       isi_call_dtmf_send_req(ovc, CALL_ID_ALL, tones, cb, data);;
 }
 
-static int isi_voicecall_probe(struct ofono_voicecall *call,
-                               unsigned int vendor, void *user)
+static int isi_voicecall_probe(struct ofono_voicecall *ovc,
+                              unsigned int vendor,
+                              void *user)
 {
        GIsiModem *idx = user;
-       struct voicecall_data *data = g_try_new0(struct voicecall_data, 1);
+       struct isi_voicecall *ivc = g_try_new0(struct isi_voicecall, 1);
+       int id;
 
-       if (!data)
+       if (!ivc)
                return -ENOMEM;
 
-       data->client = g_isi_client_create(idx, PN_CALL);
-       if (!data->client)
+       for (id = 0; id <= 7; id++)
+               ivc->calls[id].id = id;
+
+       ivc->client = g_isi_client_create(idx, PN_CALL);
+       if (!ivc->client) {
+               g_free(ivc);
                return -ENOMEM;
+       }
 
-       ofono_voicecall_set_data(call, data);
+       ofono_voicecall_set_data(ovc, ivc);
+
+       if (!g_isi_verify(ivc->client, isi_call_verify_cb, ovc))
+               DBG("Unable to verify reachability");
 
        return 0;
 }
 
+static void isi_call_verify_cb(GIsiClient *client,
+                              bool alive,
+                              void *ovc)
+{
+       if (alive) {
+               DBG("PN_CALL (0x%02X) with version %03d.%03d reachable",
+                   g_isi_client_resource(client),
+                   g_isi_version_major(client),
+                   g_isi_version_minor(client));
+               g_idle_add(isi_call_register, ovc);
+       }
+       else {
+               DBG("Unable to bootstrap voice call driver");
+       }
+}
+
+static gboolean isi_call_register(gpointer _ovc)
+{
+       struct ofono_voicecall *ovc = _ovc;
+       struct isi_voicecall *ivc = ofono_voicecall_get_data(ovc);
+
+       g_isi_client_set_debug(ivc->client, isi_call_debug, NULL);
+
+       g_isi_subscribe(ivc->client,
+                       CALL_STATUS_IND, isi_call_status_ind_cb,
+                       ovc);
+
+       if (!isi_call_status_req(ovc,
+                                CALL_ID_ALL,
+                                CALL_STATUS_MODE_ADDR_AND_ORIGIN,
+                                NULL, NULL))
+               DBG("Failed to request call status");
+
+       ofono_voicecall_register(ovc);
+
+       return FALSE;
+}
+
 static void isi_voicecall_remove(struct ofono_voicecall *call)
 {
-       struct voicecall_data *data = ofono_voicecall_get_data(call);
+       struct isi_voicecall *data = ofono_voicecall_get_data(call);
 
        if (data) {
                g_isi_client_destroy(data->client);
index c592c3c..f0db24e 100644 (file)
@@ -431,16 +431,15 @@ static gboolean g_isi_callback(GIOChannel *channel, GIOCondition cond,
 
                msg = (uint8_t *)buf;
 
+               if (cl->debug_func)
+                       cl->debug_func(msg + 1, len - 1, cl->debug_data);
+
                if (indication) {
                        /* Message ID at offset 1 */
                        id = msg[1];
                        if (cl->ind.func[id] == NULL)
                                return TRUE; /* Unsubscribed indication */
 
-                       if (cl->debug_func)
-                               cl->debug_func(msg + 1, len - 1,
-                                               cl->debug_data);
-
                        cl->ind.func[id](cl, msg + 1, len - 1, obj,
                                                cl->ind.data[id]);
                } else {
@@ -449,10 +448,6 @@ static gboolean g_isi_callback(GIOChannel *channel, GIOCondition cond,
                        if (cl->func[id] == NULL)
                                return TRUE; /* Bad transaction ID */
 
-                       if (cl->debug_func)
-                               cl->debug_func(msg + 1, len - 1,
-                                               cl->debug_data);
-
                        if ((cl->func[id])(cl, msg + 1, len - 1, obj,
                                                cl->data[id]))
                                g_isi_request_cancel(g_isi_req(cl, id));