From 86b6cd91c94d714e9353b0532f801dee349ce40e Mon Sep 17 00:00:00 2001 From: Pekka Pessi Date: Fri, 9 Oct 2009 17:45:37 +0300 Subject: [PATCH] isimodem/voicecall.c: initial version 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 | 2 + drivers/isimodem/isi-call-debug.c | 345 ++++++++++ drivers/isimodem/isi-call.h | 421 ++++++++++++ drivers/isimodem/voicecall.c | 1277 ++++++++++++++++++++++++++++++++++++- gisi/client.c | 11 +- 5 files changed, 2013 insertions(+), 43 deletions(-) create mode 100644 drivers/isimodem/isi-call-debug.c create mode 100644 drivers/isimodem/isi-call.h diff --git a/Makefile.am b/Makefile.am index 483c958..99dcd15 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 index 0000000..5d6483c --- /dev/null +++ b/drivers/isimodem/isi-call-debug.c @@ -0,0 +1,345 @@ +/* + * This file is part of oFono - Open Source Telephony + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include + +#include "isi-call.h" + +#include + +#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 ""; +} + +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 ""; +} + +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 ""; +} + +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 ""; +} + +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 ""; +} + +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 index 0000000..98f7583 --- /dev/null +++ b/drivers/isimodem/isi-call.h @@ -0,0 +1,421 @@ +/* + * This file is part of oFono - Open Source Telephony + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: + * + * 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 + +#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 diff --git a/drivers/isimodem/voicecall.c b/drivers/isimodem/voicecall.c index bb24a80..390914b 100644 --- a/drivers/isimodem/voicecall.c +++ b/drivers/isimodem/voicecall.c @@ -3,7 +3,7 @@ * * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). * - * Contact: Aki Niemi + * Contact: Pekka Pessi * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -30,124 +30,1331 @@ #include #include #include +#include #include #include #include +#include #include #include #include #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=, */ + 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); diff --git a/gisi/client.c b/gisi/client.c index c592c3c..f0db24e 100644 --- a/gisi/client.c +++ b/gisi/client.c @@ -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)); -- 2.7.4