From patchwork Mon Mar 18 22:51:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adam Pigg X-Patchwork-Id: 13596003 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 733605B5A5 for ; Mon, 18 Mar 2024 22:51:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1710802302; cv=none; b=lkhhS11p0VOr7GTrC82M5FxNLuG90Q/4ad+dF9VX5Zv8hJmIvvQNcf+1MPYrIv/zeB+CQpD1qANrrVQU3Xl4t7XMRoGDuDgDyYiFB6VhH1kXiWSvr/d3vaL0nSJGTIgRfH2d4INHnP6MV8P/V7DOKmks/l9RKucEALoJNYP0pPQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1710802302; c=relaxed/simple; bh=X+b8ZJ7fMgGF3Vcx9YmYEjto05V1isWKxGma22mQ/kI=; h=From:To:Subject:Date:Message-ID:MIME-Version:Content-Type; b=A0Nx81+IcjXeSYEbVCcHOhWPC1Rk1/uID94xqOPrla0VRnIl7Ph32J7qbo7ksg81ortOcLumiDXNJLa3BMg0+0OBm1D+bagos+lG4NRRX/ekDrAJjaRRiQnTyHn+/UHP/DBDkGm6bAMmW2KxmjkwQzy1V8m4ExqsDwQMgv8H/Lw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=mBUiOiiI; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mBUiOiiI" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-4141341f1a8so7287225e9.0 for ; Mon, 18 Mar 2024 15:51:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1710802297; x=1711407097; darn=lists.linux.dev; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:from:to:cc:subject:date:message-id:reply-to; bh=PFFWf96F6/l/Ak7m72TUCpTOocIXfs1np396WgykqeQ=; b=mBUiOiiIR7yHDtMItOZF4JNK6JEjynoMelTNGEp66lCPYXjh67lHcRty8CqrRzcfIa Y4r8d8jgR+iEZIMeRmlcmthDkUYt5maDiHsaYJSb5woGLNnMQMdBJ8pYrHTeJOwQl9ib upRKyI+beh0YjXmVI7K2nRDTr/Er8ebBE7uGUckhaC96S2DRCglf+x/ccIM8unOu99kb FC562vnD6vJhgPogysBsTIW7vN1HYFhWCj7cZ+xrl1eQTNHE6hDdF3O1JImAsOpGHmEH Nt73UImva7xQ6rEFjev46SMjr/gjykC85/gm8Qyxq0s+8fA8YClEFDIV9R+6edOVaPtE O2+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1710802297; x=1711407097; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=PFFWf96F6/l/Ak7m72TUCpTOocIXfs1np396WgykqeQ=; b=nhXgmSB+IMhJAN0+efO2xNFJ/gq31G9zEUS9G+BSwrQeP7ollybYf0LjP/90xgFvre TuQtQ1PmlcyjNtFbhgiGV9l8LU/IedZ9O0vX9QcMAwA7Q9asXK0ImRyDtWMvkZofXDOJ sYeQlXaDtDVRZUHDCmP/bLnM8Yb1O6oFQRoTMwuqrynep0s9ovWpK9S6Cvb4lSFHqQ2J LLECkGpoavfnY+RqufWKPzQYsLktrw3gUnC3CkPZ8GhIoiKk3SS07gGhiZl7jHM2Pdov YnUksTB7HwhgAGt0XnbaaQoT+PzO8aqzcsULjtW+pL2BtqPXcWl2Vsv9UDiom46HI8OT qnog== X-Gm-Message-State: AOJu0YzS4QzsPkzaaiXmGYAdZetCkSb+MCZf3sFa6bUQo58tHdSINa7U pYerWBp7/6l3IxXv8pAiCr9nSVxj6DlD2jeFp5Idyip2iNF9yVzIwMjcyPtIons= X-Google-Smtp-Source: AGHT+IHPoXlVM53EtL5vT+4EAzwvaNMjqwDFYsgUAd4+5sNUAmn1OX6wmhnNd0yVXmG7GK65UGXKZQ== X-Received: by 2002:a05:600c:4587:b0:414:37f:18dc with SMTP id r7-20020a05600c458700b00414037f18dcmr792955wmo.1.1710802296778; Mon, 18 Mar 2024 15:51:36 -0700 (PDT) Received: from adam-laptop-hp.localnet ([90.246.50.91]) by smtp.gmail.com with ESMTPSA id t6-20020a05600c450600b004133365bbc6sm19275658wmo.19.2024.03.18.15.51.35 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 18 Mar 2024 15:51:35 -0700 (PDT) From: Adam Pigg To: ofono@lists.linux.dev Subject: [RFC PATCH] qmimodem voicecall support Date: Mon, 18 Mar 2024 22:51:34 +0000 Message-ID: <5612854.ZASKD2KPVS@adam-laptop-hp> Precedence: bulk X-Mailing-List: ofono@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Hi As previously mentioned, ive been working on updating Ofono in Sailfish. There are several patches which probably are of no interest, but this one may be. It implements voicecall support for the qmimodem driver, and ive tested it on a PinephonePro, and can make/receive/hangup calls. I cant take credit for the original implementation, it was taken from other ofono forks out there, such as https://github.com/maemo-leste-upstream-forks/ ofono/commit/9c8ec63c1bb41a03cd87d2733dd83ecd148d0105 The changes I have made include: *moving voice_generated.* into voice.* This is because it isnt actually generated and reduces the files in the tree *removed glib and replaced with ell *tweaked the notify code in call_list.c Changed the disconnect type from UNKNOWN because in src/voicecall.c, if the reason is UNKNOWN then it doesnt then perform the dbus notify Made it call ofono_voicecall_disconnected when the state was DISCONNECTED (QMI state disconnecting and end) The common/call_list.* files can potentially be used by other drivers. Apologies for attaching the patch, git send-email is not my normal work flow! :) Adam From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Adam Pigg Date: Tue, 12 Mar 2024 18:52:32 +0000 Subject: [PATCH 1/1] Add voicecall support to the qmimodem driver Adds a call_list helper which could be shared across drivers --- Makefile.am | 6 +- drivers/common/call_list.c | 151 ++++++++++++ drivers/common/call_list.h | 49 ++++ drivers/qmimodem/qmi.h | 14 ++ drivers/qmimodem/voice.c | 365 +++++++++++++++++++++++++++++ drivers/qmimodem/voice.h | 242 ++++++++++++++++--- drivers/qmimodem/voicecall.c | 435 ++++++++++++++++++++++++++++++++++- src/common.h | 5 + 8 files changed, 1223 insertions(+), 44 deletions(-) create mode 100644 drivers/common/call_list.c create mode 100644 drivers/common/call_list.h create mode 100644 drivers/qmimodem/voice.c diff --git a/Makefile.am b/Makefile.am index 636546189a50f338f77706ec465cded268a89210..7f8a691d6f0679eb94ccb64bf418cf20a98db689 100644 --- a/Makefile.am +++ b/Makefile.am @@ -373,8 +373,8 @@ qmi_sources = drivers/qmimodem/qmi.h drivers/qmimodem/qmi.c \ drivers/qmimodem/pds.h \ drivers/qmimodem/common.h \ drivers/qmimodem/wda.h \ - drivers/qmimodem/voice.h - + drivers/qmimodem/voice.h \ + drivers/qmimodem/voice.c builtin_sources += $(qmi_sources) \ drivers/qmimodem/util.h \ drivers/qmimodem/devinfo.c \ @@ -579,6 +579,8 @@ builtin_sources += plugins/ublox.c builtin_sources += plugins/xmm7xxx.c builtin_sources += plugins/droid.c +builtin_sources += drivers/common/call_list.c + builtin_modules += stemgr builtin_sources += plugins/stemgr.c diff --git a/drivers/common/call_list.c b/drivers/common/call_list.c new file mode 100644 index 0000000000000000000000000000000000000000..13aa8dc57ada0931bdddac3e23ea944bf3be04d7 --- /dev/null +++ b/drivers/common/call_list.c @@ -0,0 +1,151 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Alexander Couzens + * + * 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 + +#include + +#include + +#include +#include +#include + +#include "src/common.h" + +#include + +int ofono_call_compare(const void *a, const void *b, void *data) +{ + const struct ofono_call *ca = a; + const struct ofono_call *cb = b; + + if (ca->id < cb->id) + return -1; + + if (ca->id > cb->id) + return 1; + + return 0; +} + +bool ofono_call_compare_by_status(const void *a, const void *b) +{ + const struct ofono_call *call = a; + int status = L_PTR_TO_INT(b); + + return status == call->status; +} + +bool ofono_call_compare_by_id(const void *a, const void *b) +{ + const struct ofono_call *call = a; + unsigned int id = L_PTR_TO_UINT(b); + + return (call->id == id); +} + +void ofono_call_list_dial_callback(struct ofono_voicecall *vc, + struct l_queue **call_list, + const struct ofono_phone_number *ph, + int call_id) +{ + struct ofono_call *call; + + /* check if call_id already present */ + call = l_queue_find(*call_list, + ofono_call_compare_by_id, + L_UINT_TO_PTR(call_id)); + + if (call) { + return; + } + + call = l_new(struct ofono_call, 1); + call->id = call_id; + + memcpy(&call->called_number, ph, sizeof(*ph)); + call->direction = CALL_DIRECTION_MOBILE_ORIGINATED; + call->status = CALL_STATUS_DIALING; + call->type = 0; /* voice */ + + l_queue_insert(*call_list, call, ofono_call_compare, NULL); + + ofono_voicecall_notify(vc, call); +} + +void ofono_call_list_notify(struct ofono_voicecall *vc, + struct l_queue **call_list, + struct l_queue *calls) +{ + struct l_queue *old_calls = *call_list; + struct l_queue *new_calls = calls; + struct ofono_call *new_call, *old_call; + const struct l_queue_entry *old_entry, *new_entry; + + uint loop_length = MAX(l_queue_length(old_calls), l_queue_length(new_calls)); + + old_entry = l_queue_get_entries(old_calls); + new_entry = l_queue_get_entries(new_calls); + + for (uint i = 0; i < loop_length; ++i) { + old_call = old_entry ? old_entry->data : NULL; + new_call = new_entry ? new_entry->data : NULL; + + if (new_call && new_call->status == CALL_STATUS_DISCONNECTED) { + ofono_voicecall_disconnected( + vc, + new_call->id, + OFONO_DISCONNECT_REASON_REMOTE_HANGUP, + NULL); + + l_queue_remove(calls, new_call); + l_free(new_call); + continue; + } + + if (old_call && (new_call == NULL || (new_call->id > old_call->id))) { + ofono_voicecall_disconnected( + vc, + old_call->id, + OFONO_DISCONNECT_REASON_REMOTE_HANGUP, + NULL); + } else if (new_call && (old_call == NULL || (new_call->id < old_call->id))) { + DBG("Notify new call %d", new_call->id); + /* new call, signal it */ + if (new_call->type == 0) { + ofono_voicecall_notify(vc, new_call); + } + } else { + if (memcmp(new_call, old_call, sizeof(*new_call)) + && new_call->type == 0) + ofono_voicecall_notify(vc, new_call); + } + if (old_entry) old_entry = old_entry->next; + if (new_entry) new_entry = new_entry->next; + } + + l_queue_destroy(*call_list, l_free); + *call_list = calls; +} diff --git a/drivers/common/call_list.h b/drivers/common/call_list.h new file mode 100644 index 0000000000000000000000000000000000000000..746008bb156ec72c1e4eac32afd8c1f467cadc8a --- /dev/null +++ b/drivers/common/call_list.h @@ -0,0 +1,49 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2017,2019 Alexander Couzens + * + * 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 __OFONO_DRIVER_COMMON_CALL_LIST +#define __OFONO_DRIVER_COMMON_CALL_LIST + +struct ofono_voicecall; +struct ofono_phone_number; + +int ofono_call_compare(const void *a, const void *b, void *data); +bool ofono_call_compare_by_status(const void *a, const void *b); +bool ofono_call_compare_by_id(const void *a, const void *b); + +/* + * Can be called by the driver in the dialing callback, + * when the new call id already known + */ +void ofono_call_list_dial_callback(struct ofono_voicecall *vc, + struct l_queue **call_list, + const struct ofono_phone_number *ph, + int call_id); + +/* + * Called with a list of known calls e.g. clcc. + * Call list will take ownership of all ofono call within the calls. + */ +void ofono_call_list_notify(struct ofono_voicecall *vc, + struct l_queue **call_list, + struct l_queue *calls); + +#endif /* __OFONO_DRIVER_COMMON_CALL_LIST */ diff --git a/drivers/qmimodem/qmi.h b/drivers/qmimodem/qmi.h index 506fed6b3a66c874ff82b26559e478fc9533201e..9caffa88da9559ef625045843c0db49e45c4bc87 100644 --- a/drivers/qmimodem/qmi.h +++ b/drivers/qmimodem/qmi.h @@ -19,6 +19,9 @@ * */ +#ifndef __OFONO_QMI_QMI_H +#define __OFONO_QMI_QMI_H + #include #include @@ -54,6 +57,7 @@ #define QMI_SERVICE_CAT_OLD 224 /* Card application toolkit service */ #define QMI_SERVICE_RMS 225 /* Remote management service */ #define QMI_SERVICE_OMA 226 /* OMA device management service */ +#define QMI_SERVICE_UPDATE 400 enum qmi_device_expected_data_format { QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN, @@ -176,3 +180,13 @@ uint16_t qmi_service_register(struct qmi_service *service, void *user_data, qmi_destroy_func_t destroy); bool qmi_service_unregister(struct qmi_service *service, uint16_t id); bool qmi_service_unregister_all(struct qmi_service *service); + + +/* FIXME: find a place for parse_error */ +enum parse_error { + NONE = 0, + MISSING_MANDATORY = 1, + INVALID_LENGTH = 2, +}; + +#endif /* __OFONO_QMI_QMI_H */ diff --git a/drivers/qmimodem/voice.c b/drivers/qmimodem/voice.c new file mode 100644 index 0000000000000000000000000000000000000000..138b2f63c62a66cfd43a0ad7639f4ba9250bacc8 --- /dev/null +++ b/drivers/qmimodem/voice.c @@ -0,0 +1,365 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2017 Alexander Couzens + * + * 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. + * + */ + +#include +#include + +#include "voice.h" + +#define _(X) case X: return #X + +const char *qmi_voice_call_state_name(enum qmi_voice_call_state value) +{ + switch (value) { + _(QMI_CALL_STATE_IDLE); + _(QMI_CALL_STATE_ORIG); + _(QMI_CALL_STATE_INCOMING); + _(QMI_CALL_STATE_CONV); + _(QMI_CALL_STATE_CC_IN_PROG); + _(QMI_CALL_STATE_ALERTING); + _(QMI_CALL_STATE_HOLD); + _(QMI_CALL_STATE_WAITING); + _(QMI_CALL_STATE_DISCONNECTING); + _(QMI_CALL_STATE_END); + _(QMI_CALL_STATE_SETUP); + } + return "QMI_CALL_STATE_"; +} + +int qmi_to_ofono_status(uint8_t status, int *ret) { + int err = 0; + switch (status) { + case QMI_CALL_STATE_IDLE: + case QMI_CALL_STATE_END: + case QMI_CALL_STATE_DISCONNECTING: + *ret = CALL_STATUS_DISCONNECTED; + break; + case QMI_CALL_STATE_HOLD: + *ret = CALL_STATUS_HELD; + break; + case QMI_CALL_STATE_WAITING: + *ret = CALL_STATUS_WAITING; + break; + case QMI_CALL_STATE_ORIG: + *ret = CALL_STATUS_DIALING; + break; + case QMI_CALL_STATE_SETUP: + case QMI_CALL_STATE_INCOMING: + *ret = CALL_STATUS_INCOMING; + break; + case QMI_CALL_STATE_CONV: + *ret = CALL_STATUS_ACTIVE; + break; + case QMI_CALL_STATE_CC_IN_PROG: + *ret = CALL_STATUS_DIALING; + break; + case QMI_CALL_STATE_ALERTING: + *ret = CALL_STATUS_ALERTING; + break; + default: + err = 1; + } + return err; +} + +uint8_t ofono_to_qmi_direction(enum call_direction ofono_direction) { + return ofono_direction + 1; +} +enum call_direction qmi_to_ofono_direction(uint8_t qmi_direction) { + return qmi_direction - 1; +} + +int qmi_voice_dial_call( + struct qmi_voice_dial_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy) +{ + struct qmi_param *param = NULL; + + param = qmi_param_new(); + if (!param) + goto error; + + if (arg->calling_number_set) { + if (!qmi_param_append(param, + 0x1, + strlen(arg->calling_number), + arg->calling_number)) + goto error; + } + + if (arg->call_type_set) + qmi_param_append_uint8(param, 0x10, arg->call_type); + + if (qmi_service_send(service, + 0x20, + param, + func, + user_data, + destroy) > 0) + return 0; + error: + g_free(param); + return 1; +} + +enum parse_error qmi_voice_dial_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_dial_call_result *result) +{ + int err = NONE; + + /* mandatory */ + if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id)) + result->call_id_set = 1; + else + err = MISSING_MANDATORY; + + return err; +} + +int qmi_voice_end_call( + struct qmi_voice_end_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy) +{ + struct qmi_param *param = NULL; + + param = qmi_param_new(); + if (!param) + goto error; + + if (arg->call_id_set) { + if (!qmi_param_append_uint8( + param, + 0x1, + arg->call_id)) + goto error; + } + + if (qmi_service_send(service, + 0x21, + param, + func, + user_data, + destroy) > 0) + return 0; + error: + g_free(param); + return 1; +} + +enum parse_error qmi_voice_end_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_end_call_result *result) +{ + int err = NONE; + + /* optional */ + if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id)) + result->call_id_set = 1; + + return err; +} + + +int qmi_voice_answer_call( + struct qmi_voice_answer_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy) +{ + struct qmi_param *param = NULL; + + param = qmi_param_new(); + if (!param) + goto error; + + if (arg->call_id_set) { + if (!qmi_param_append_uint8( + param, + 0x1, + arg->call_id)) + goto error; + } + + if (qmi_service_send(service, + 0x22, + param, + func, + user_data, + destroy) > 0) + return 0; + error: + g_free(param); + return 1; +} + + +enum parse_error qmi_voice_answer_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_answer_call_result *result) +{ + int err = NONE; + + /* optional */ + if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id)) + result->call_id_set = 1; + + return err; +} + +enum parse_error qmi_voice_call_status( + struct qmi_result *qmi_result, + struct qmi_voice_all_call_status_ind *result) +{ + int err = NONE; + int offset; + uint16_t len; + bool ind = TRUE; + const struct qmi_voice_remote_party_number *remote_party_number; + const struct qmi_voice_call_information *call_information; + + /* mandatory */ + call_information = qmi_result_get(qmi_result, 0x01, &len); + + /* This is so ugly! but TLV for indicator and response is different */ + if (!call_information) { + call_information = qmi_result_get(qmi_result, 0x10, &len); + ind = FALSE; + } + + if (call_information) + { + /* verify the length */ + if (len < sizeof(call_information->size)) + return INVALID_LENGTH; + + if (len != call_information->size * sizeof(struct qmi_voice_call_information_instance) + + sizeof(call_information->size)) + return INVALID_LENGTH; + result->call_information_set = 1; + result->call_information = call_information; + } else + return MISSING_MANDATORY; + + /* mandatory */ + remote_party_number = qmi_result_get(qmi_result, ind ? 0x10 : 0x11, &len); + + if (remote_party_number) { + const struct qmi_voice_remote_party_number_instance *instance; + int instance_size = sizeof(struct qmi_voice_remote_party_number_instance); + int i; + + /* verify the length */ + if (len < sizeof(remote_party_number->size)) + return INVALID_LENGTH; + + for (i = 0, offset = sizeof(remote_party_number->size); + offset <= len && i < 16 && i < remote_party_number->size; i++) + { + if (offset == len) { + break; + } else if (offset + instance_size > len) { + return INVALID_LENGTH; + } + + instance = (void *)remote_party_number + offset; + result->remote_party_number[i] = instance; + offset += sizeof(struct qmi_voice_remote_party_number_instance) + instance->number_size; + } + result->remote_party_number_set = 1; + result->remote_party_number_size = remote_party_number->size; + } else + return MISSING_MANDATORY; + + return err; +} + +int qmi_voice_start_cont_dtmf( + struct qmi_voice_start_cont_dtmf_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy) +{ + struct qmi_param *param = NULL; + uint8_t param_body[2]; + + param = qmi_param_new(); + if (!param) + goto error; + + param_body[0] = arg->call_id; + param_body[1] = arg->dtmf_char; + + if (!qmi_param_append( + param, + 0x1, + sizeof(param_body), + param_body)) + goto error; + + if (qmi_service_send(service, + 0x29, + param, + func, + user_data, + destroy) > 0) + return 0; + + error: + g_free(param); + return 1; +} + +int qmi_voice_stop_cont_dtmf( + struct qmi_voice_stop_cont_dtmf_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy) +{ + struct qmi_param *param = NULL; + + param = qmi_param_new(); + if (!param) + goto error; + + if (!qmi_param_append_uint8( + param, + 0x1, + arg->call_id)) + goto error; + + if (qmi_service_send(service, + 0x2a, + param, + func, + user_data, + destroy) > 0) + return 0; + + error: + g_free(param); + return 1; +} diff --git a/drivers/qmimodem/voice.h b/drivers/qmimodem/voice.h index 917e72f7134c077b2760608101a2d1148e67a4c2..5479939b2cb5d9e4e6726734e739e444e347bdbd 100644 --- a/drivers/qmimodem/voice.h +++ b/drivers/qmimodem/voice.h @@ -15,6 +15,15 @@ * */ +#ifndef QMI_VOICE_H +#define QMI_VOICE_H + +#include "src/common.h" + +#define QMI_VOICE_IND_ALL_STATUS 0x2e +#define QMI_VOICE_GET_ALL_STATUS 0x2f + + #define QMI_VOICE_PARAM_USS_DATA 0x01 #define QMI_VOICE_PARAM_ASYNC_USSD_ERROR 0x10 @@ -34,6 +43,8 @@ /* default alphabet Language unspecific */ #define USSD_DCS_UNSPECIFIC 0x0f +#include + /* based on qmi ussd definition */ enum qmi_ussd_dcs { QMI_USSD_DCS_ASCII = 0x1, @@ -48,22 +59,41 @@ enum qmi_ussd_user_required { /* QMI service voice. Using an enum to prevent doublicated entries */ enum voice_commands { - QMI_VOICE_SUPS_NOTIFICATION_IND = 0x32, - QMI_VOICE_SET_SUPS_SERVICE = 0x33, - QMI_VOICE_GET_CALL_WAITING = 0x34, - QMI_VOICE_GET_CALL_BARRING = 0x35, - QMI_VOICE_GET_CLIP = 0x36, - QMI_VOICE_GET_CLIR = 0x37, - QMI_VOICE_GET_CALL_FWDING = 0x38, - QMI_VOICE_SET_CALL_BARRING_PWD = 0x39, - QMI_VOICE_CANCEL_USSD = 0x3c, - QMI_VOICE_USSD_RELEASE_IND = 0x3d, - QMI_VOICE_USSD_IND = 0x3e, - QMI_VOICE_SUPS_IND = 0x42, - QMI_VOICE_ASYNC_ORIG_USSD = 0x43, - QMI_VOICE_GET_COLP = 0x4b, - QMI_VOICE_GET_COLR = 0x4c, - QMI_VOICE_GET_CNAP = 0x4d +QMI_VOICE_SUPS_NOTIFICATION_IND = 0x32, + QMI_VOICE_SET_SUPS_SERVICE = 0x33, + QMI_VOICE_GET_CALL_WAITING = 0x34, + QMI_VOICE_GET_CALL_BARRING = 0x35, + QMI_VOICE_GET_CLIP = 0x36, + QMI_VOICE_GET_CLIR = 0x37, + QMI_VOICE_GET_CALL_FWDING = 0x38, + QMI_VOICE_SET_CALL_BARRING_PWD = 0x39, + QMI_VOICE_CANCEL_USSD = 0x3c, + QMI_VOICE_USSD_RELEASE_IND = 0x3d, + QMI_VOICE_USSD_IND = 0x3e, + QMI_VOICE_SUPS_IND = 0x42, + QMI_VOICE_ASYNC_ORIG_USSD = 0x43, + QMI_VOICE_GET_COLP = 0x4b, + QMI_VOICE_GET_COLR = 0x4c, + QMI_VOICE_GET_CNAP = 0x4d +}; + +enum qmi_voice_call_state { + QMI_CALL_STATE_IDLE = 0x0, + QMI_CALL_STATE_ORIG, + QMI_CALL_STATE_INCOMING, + QMI_CALL_STATE_CONV, + QMI_CALL_STATE_CC_IN_PROG, + QMI_CALL_STATE_ALERTING, + QMI_CALL_STATE_HOLD, + QMI_CALL_STATE_WAITING, + QMI_CALL_STATE_DISCONNECTING, + QMI_CALL_STATE_END, + QMI_CALL_STATE_SETUP +}; + +enum qmi_voice_call_type { + QMI_CALL_TYPE_VOICE = 0x0, + QMI_CALL_TYPE_VOICE_FORCE, }; struct qmi_ussd_data { @@ -73,28 +103,166 @@ struct qmi_ussd_data { } __attribute__((__packed__)); enum qmi_ss_action { - QMI_VOICE_SS_ACTION_ACTIVATE = 0x01, - QMI_VOICE_SS_ACTION_DEACTIVATE = 0x02, - QMI_VOICE_SS_ACTION_REGISTER = 0x03, - QMI_VOICE_SS_ACTION_ERASE = 0x04 + QMI_VOICE_SS_ACTION_ACTIVATE = 0x01, + QMI_VOICE_SS_ACTION_DEACTIVATE = 0x02, + QMI_VOICE_SS_ACTION_REGISTER = 0x03, + QMI_VOICE_SS_ACTION_ERASE = 0x04 }; enum qmi_ss_reason { - QMI_VOICE_SS_RSN_FWD_UNCONDITIONAL = 0x01, - QMI_VOICE_SS_RSN_FWD_MOBILE_BUSY = 0x02, - QMI_VOICE_SS_RSN_FWD_NO_REPLY = 0x03, - QMI_VOICE_SS_RSN_FWD_UNREACHABLE = 0x04, - QMI_VOICE_SS_RSN_FWD_ALL = 0x05, - QMI_VOICE_SS_RSN_FWD_ALL_CONDITIONAL = 0x06, - QMI_VOICE_SS_RSN_ALL_OUTGOING = 0x07, - QMI_VOICE_SS_RSN_OUT_INT = 0x08, - QMI_VOICE_SS_RSN_OUT_INT_EXT_TO_HOME = 0x09, - QMI_VOICE_SS_RSN_ALL_IN = 0x0A, - QMI_VOICE_SS_RSN_IN_ROAMING = 0x0B, - QMI_VOICE_SS_RSN_BAR_ALL = 0x0C, - QMI_VOICE_SS_RSN_BAR_ALL_OUTGOING = 0x0D, - QMI_VOICE_SS_RSN_BAR_ALL_IN = 0x0E, - QMI_VOICE_SS_RSN_CALL_WAITING = 0x0F, - QMI_VOICE_SS_RSN_CLIP = 0x10, - QMI_VOICE_SS_RSN_CLIR = 0x11 + QMI_VOICE_SS_RSN_FWD_UNCONDITIONAL = 0x01, + QMI_VOICE_SS_RSN_FWD_MOBILE_BUSY = 0x02, + QMI_VOICE_SS_RSN_FWD_NO_REPLY = 0x03, + QMI_VOICE_SS_RSN_FWD_UNREACHABLE = 0x04, + QMI_VOICE_SS_RSN_FWD_ALL = 0x05, + QMI_VOICE_SS_RSN_FWD_ALL_CONDITIONAL = 0x06, + QMI_VOICE_SS_RSN_ALL_OUTGOING = 0x07, + QMI_VOICE_SS_RSN_OUT_INT = 0x08, + QMI_VOICE_SS_RSN_OUT_INT_EXT_TO_HOME = 0x09, + QMI_VOICE_SS_RSN_ALL_IN = 0x0A, + QMI_VOICE_SS_RSN_IN_ROAMING = 0x0B, + QMI_VOICE_SS_RSN_BAR_ALL = 0x0C, + QMI_VOICE_SS_RSN_BAR_ALL_OUTGOING = 0x0D, + QMI_VOICE_SS_RSN_BAR_ALL_IN = 0x0E, + QMI_VOICE_SS_RSN_CALL_WAITING = 0x0F, + QMI_VOICE_SS_RSN_CLIP = 0x10, + QMI_VOICE_SS_RSN_CLIR = 0x11 +}; + +const char *qmi_voice_call_state_name(enum qmi_voice_call_state value); +uint8_t ofono_to_qmi_direction(enum call_direction ofono_direction); +enum call_direction qmi_to_ofono_direction(uint8_t qmi_direction); +int qmi_to_ofono_status(uint8_t status, int *ret); + +#include "qmi.h" + +struct qmi_voice_remote_party_number_instance { + uint8_t call_id; + uint8_t presentation_indicator; + uint8_t number_size; + char number[0]; +} __attribute__((__packed__)); + +struct qmi_voice_remote_party_number { + uint8_t size; + struct qmi_voice_remote_party_number_instance instance[0]; +} __attribute__((__packed__)); + +/* generator / parser */ + +struct qmi_voice_dial_call_arg { + bool calling_number_set; + const char *calling_number; + bool call_type_set; + uint8_t call_type; +}; + +int qmi_voice_dial_call( + struct qmi_voice_dial_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy); + +struct qmi_voice_dial_call_result { + bool call_id_set; + uint8_t call_id; +}; + +enum parse_error qmi_voice_dial_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_dial_call_result *result); + +struct qmi_voice_end_call_arg { + bool call_id_set; + uint8_t call_id; }; + +int qmi_voice_end_call( + struct qmi_voice_end_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy); + +struct qmi_voice_end_call_result { + bool call_id_set; + uint8_t call_id; +}; + +enum parse_error qmi_voice_end_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_end_call_result *result); + +struct qmi_voice_answer_call_arg { + bool call_id_set; + uint8_t call_id; +}; + +int qmi_voice_answer_call( + struct qmi_voice_answer_call_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy); + +struct qmi_voice_answer_call_result { + bool call_id_set; + uint8_t call_id; +}; + +enum parse_error qmi_voice_answer_call_parse( + struct qmi_result *qmi_result, + struct qmi_voice_answer_call_result *result); + +struct qmi_voice_call_information_instance { + uint8_t id; + uint8_t state; + uint8_t type; + uint8_t direction; + uint8_t mode; + uint8_t multipart_indicator; + uint8_t als; +} __attribute__((__packed__)); + +struct qmi_voice_call_information { + uint8_t size; + struct qmi_voice_call_information_instance instance[0]; +} __attribute__((__packed__)) ; + +struct qmi_voice_all_call_status_ind { + bool call_information_set; + const struct qmi_voice_call_information *call_information; + bool remote_party_number_set; + uint8_t remote_party_number_size; + const struct qmi_voice_remote_party_number_instance *remote_party_number[16]; +}; + +enum parse_error qmi_voice_call_status( + struct qmi_result *qmi_result, + struct qmi_voice_all_call_status_ind *result); + +struct qmi_voice_start_cont_dtmf_arg { + uint8_t call_id; + uint8_t dtmf_char; +}; + +int qmi_voice_start_cont_dtmf( + struct qmi_voice_start_cont_dtmf_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy); + +struct qmi_voice_stop_cont_dtmf_arg { + uint8_t call_id; +}; + +int qmi_voice_stop_cont_dtmf( + struct qmi_voice_stop_cont_dtmf_arg *arg, + struct qmi_service *service, + qmi_result_func_t func, + void *user_data, + qmi_destroy_func_t destroy); + +#endif diff --git a/drivers/qmimodem/voicecall.c b/drivers/qmimodem/voicecall.c index 059edbae9ac55774e452444dd6289e1875f5b7cf..6c893f4e5346eae9a72604f0cd66d7a9077df189 100644 --- a/drivers/qmimodem/voicecall.c +++ b/drivers/qmimodem/voicecall.c @@ -3,6 +3,7 @@ * oFono - Open Source Telephony * * Copyright (C) 2011-2012 Intel Corporation. All rights reserved. + * Copyright (C) 2017 Alexander Couzens * * 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 @@ -23,20 +24,125 @@ #include #endif +#include + +#include #include #include #include -#include "qmi.h" +#include +#include "src/common.h" +#include "qmi.h" #include "util.h" +#include "voice.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + + +/* qmi protocol */ + + +/* end of qmi */ struct voicecall_data { struct qmi_service *voice; uint16_t major; uint16_t minor; + struct l_queue *call_list; + struct voicecall_static *vs; + struct ofono_phone_number dialed; }; +static void all_call_status_ind(struct qmi_result *result, void *user_data) +{ + struct ofono_voicecall *vc = user_data; + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct l_queue *calls = NULL; + int i; + int size = 0; + struct qmi_voice_all_call_status_ind status_ind; + + calls = l_queue_new(); + + DBG(""); + if (qmi_voice_call_status(result, &status_ind) != NONE) { + DBG("Parsing of all call status indication failed"); + return; + } + + if (!status_ind.remote_party_number_set || !status_ind.call_information_set) { + DBG("Some required fields are not set"); + return; + } + + size = status_ind.call_information->size; + if (!size) { + DBG("No call informations received!"); + return; + } + + /* expect we have valid fields for every call */ + if (size != status_ind.remote_party_number_size) { + DBG("Not all fields have the same size"); + return; + } + + for (i = 0; i < size; i++) { + struct qmi_voice_call_information_instance call_info; + struct ofono_call *call; + const struct qmi_voice_remote_party_number_instance *remote_party = status_ind.remote_party_number[i]; + int number_size; + + call_info = status_ind.call_information->instance[i]; + call = l_new(struct ofono_call, 1); + call->id = call_info.id; + call->direction = qmi_to_ofono_direction(call_info.direction); + + if (qmi_to_ofono_status(call_info.state, &call->status)) { + DBG("Ignore call id %d, because can not convert QMI state 0x%x to ofono.", + call_info.id, call_info.state); + continue; + } + DBG("Call %d in state %s(%d)", + call_info.id, + qmi_voice_call_state_name(call_info.state), + call_info.state); + + call->type = 0; /* always voice */ + number_size = remote_party->number_size; + if (number_size > OFONO_MAX_PHONE_NUMBER_LENGTH) + number_size = OFONO_MAX_PHONE_NUMBER_LENGTH; + strncpy(call->phone_number.number, remote_party->number, + number_size); + /* FIXME: set phone_number_type */ + + if (strlen(call->phone_number.number) > 0) + call->clip_validity = 0; + else + call->clip_validity = 2; + + l_queue_push_tail(calls, call); + DBG("%d", l_queue_length(calls)); + } + + ofono_call_list_notify(vc, &vd->call_list, calls); +} + +static void event_update(struct qmi_result *result, void *user_data) +{ + struct ofono_voicecall *vc = user_data; + struct voicecall_data *data = ofono_voicecall_get_data(vc); + + DBG(""); + + qmi_service_send(data->voice, QMI_VOICE_GET_ALL_STATUS, NULL, + all_call_status_ind, vc, NULL); +} + static void create_voice_cb(struct qmi_service *service, void *user_data) { struct ofono_voicecall *vc = user_data; @@ -58,6 +164,15 @@ static void create_voice_cb(struct qmi_service *service, void *user_data) data->voice = qmi_service_ref(service); + /* FIXME: we should call indication_register to ensure we get notified on call events. + * We rely at the moment on the default value of notifications + */ + qmi_service_register(data->voice, QMI_VOICE_IND_ALL_STATUS, + all_call_status_ind, vc, NULL); + + qmi_service_register(data->voice, QMI_SERVICE_UPDATE, + event_update, vc, NULL); + ofono_voicecall_register(vc); } @@ -69,7 +184,8 @@ static int qmi_voicecall_probe(struct ofono_voicecall *vc, DBG(""); - data = g_new0(struct voicecall_data, 1); + data = l_new(struct voicecall_data, 1); + data->call_list = l_queue_new(); ofono_voicecall_set_data(vc, data); @@ -77,7 +193,6 @@ static int qmi_voicecall_probe(struct ofono_voicecall *vc, create_voice_cb, vc, NULL); return 0; - } static void qmi_voicecall_remove(struct ofono_voicecall *vc) @@ -92,13 +207,323 @@ static void qmi_voicecall_remove(struct ofono_voicecall *vc) qmi_service_unref(data->voice); - g_free(data); + l_queue_destroy(data->call_list, l_free); + + l_free(data); +} + +static void dial_cb(struct qmi_result *result, void *user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_voicecall *vc = cbd->user; + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + ofono_voicecall_cb_t cb = cbd->cb; + uint16_t error; + struct qmi_voice_dial_call_result dial_result; + + if (qmi_result_set_error(result, &error)) { + DBG("QMI Error %d", error); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + if (NONE != qmi_voice_dial_call_parse(result, &dial_result)) { + DBG("Received invalid Result"); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + if (!dial_result.call_id_set) { + DBG("Didn't receive a call id"); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + DBG("New call QMI id %d", dial_result.call_id); + ofono_call_list_dial_callback(vc, + &vd->call_list, + &vd->dialed, + dial_result.call_id); + + + /* FIXME: create a timeout on this call_id */ + CALLBACK_WITH_SUCCESS(cb, cbd->data); +} + +static void dial(struct ofono_voicecall *vc, const struct ofono_phone_number *ph, + enum ofono_clir_option clir, ofono_voicecall_cb_t cb, + void *data) +{ + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct cb_data *cbd = cb_data_new(cb, data); + struct qmi_voice_dial_call_arg arg; + + cbd->user = vc; + arg.calling_number_set = true; + arg.calling_number = phone_number_to_string(ph); + memcpy(&vd->dialed, ph, sizeof(*ph)); + + arg.call_type_set = true; + arg.call_type = QMI_CALL_TYPE_VOICE; + + if (!qmi_voice_dial_call( + &arg, + vd->voice, + dial_cb, + cbd, + l_free)) + return; + + CALLBACK_WITH_FAILURE(cb, data); + l_free(cbd); +} + +static void answer_cb(struct qmi_result *result, void *user_data) +{ + struct cb_data *cbd = user_data; + ofono_voicecall_cb_t cb = cbd->cb; + uint16_t error; + struct qmi_voice_answer_call_result answer_result; + + if (qmi_result_set_error(result, &error)) { + DBG("QMI Error %d", error); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + /* TODO: what happens when calling it with no active call or wrong caller id? */ + if (NONE != qmi_voice_answer_call_parse(result, &answer_result)) { + DBG("Received invalid Result"); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + CALLBACK_WITH_SUCCESS(cb, cbd->data); +} + +static void answer(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) +{ + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct cb_data *cbd = cb_data_new(cb, data); + struct qmi_voice_answer_call_arg arg; + struct ofono_call *call; + + cbd->user = vc; + + call = l_queue_find(vd->call_list, + ofono_call_compare_by_status, + L_UINT_TO_PTR(CALL_STATUS_INCOMING)); + + if (call == NULL) { + DBG("Can not find a call to answer"); + goto err; + } + + arg.call_id_set = true; + arg.call_id = call->id; + + if (!qmi_voice_answer_call( + &arg, + vd->voice, + answer_cb, + cbd, + l_free)) + return; +err: + CALLBACK_WITH_FAILURE(cb, data); + l_free(cbd); +} + +static void end_cb(struct qmi_result *result, void *user_data) +{ + struct cb_data *cbd = user_data; + ofono_voicecall_cb_t cb = cbd->cb; + uint16_t error; + struct qmi_voice_end_call_result end_result; + + if (qmi_result_set_error(result, &error)) { + DBG("QMI Error %d", error); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + if (NONE != qmi_voice_end_call_parse(result, &end_result)) { + DBG("Received invalid Result"); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + CALLBACK_WITH_SUCCESS(cb, cbd->data); +} + +static void release_specific(struct ofono_voicecall *vc, int id, + ofono_voicecall_cb_t cb, void *data) +{ + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct cb_data *cbd = cb_data_new(cb, data); + struct qmi_voice_end_call_arg arg; + + DBG(""); + cbd->user = vc; + + arg.call_id_set = true; + arg.call_id = id; + + if (!qmi_voice_end_call(&arg, + vd->voice, + end_cb, + cbd, + l_free)) + return; + + CALLBACK_WITH_FAILURE(cb, data); + l_free(cbd); +} + +static void hangup_active(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct ofono_call *call; + enum call_status active[] = { + CALL_STATUS_ACTIVE, + CALL_STATUS_DIALING, + CALL_STATUS_ALERTING, + CALL_STATUS_INCOMING, + }; + int i; + + DBG(""); + for (i = 0; i < ARRAY_SIZE(active); i++) { + call = l_queue_find(vd->call_list, + ofono_call_compare_by_status, + L_INT_TO_PTR(active[i])); + + if (call) + break; + } + + if (call == NULL) { + DBG("Can not find a call to hang up"); + CALLBACK_WITH_FAILURE(cb, data); + return; + } + + release_specific(vc, call->id, cb, data); +} + +static void stop_cont_dtmf_cb(struct qmi_result *result, void *user_data) +{ + struct cb_data *cbd = user_data; + ofono_voicecall_cb_t cb = cbd->cb; + + uint16_t error; + + if (qmi_result_set_error(result, &error)) { + DBG("QMI Error %d", error); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + CALLBACK_WITH_SUCCESS(cb, cbd->data); +} + +static void start_cont_dtmf_cb(struct qmi_result *result, void *user_data) +{ + struct cb_data *cbd = user_data; + ofono_voicecall_cb_t cb = cbd->cb; + struct ofono_voicecall *vc = cbd->user; + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + struct qmi_voice_stop_cont_dtmf_arg arg; + uint16_t error; + + if (qmi_result_set_error(result, &error)) { + DBG("QMI Error %d", error); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + arg.call_id = 0xff; + + if (!qmi_voice_stop_cont_dtmf(&arg, + vd->voice, + stop_cont_dtmf_cb, + cbd, + l_free)) + return; + + CALLBACK_WITH_FAILURE(cb, cbd->data); +} + +static void send_one_dtmf(struct ofono_voicecall *vc, const char dtmf, + ofono_voicecall_cb_t cb, void *data) { + struct qmi_voice_start_cont_dtmf_arg arg; + struct voicecall_data *vd = ofono_voicecall_get_data(vc); + + arg.call_id = 0xff; + arg.dtmf_char = (uint8_t) dtmf; + + struct cb_data *cbd = cb_data_new(cb, data); + cbd->user = vc; + + if (!qmi_voice_start_cont_dtmf(&arg, + vd->voice, + start_cont_dtmf_cb, + cbd, + NULL)) + return; + + CALLBACK_WITH_FAILURE(cb, data); + l_free(cbd); +} + +struct send_one_dtmf_cb_data { + const char *full_dtmf; + const char *next_dtmf; + struct ofono_voicecall *vc; +}; + +static void send_one_dtmf_cb(const struct ofono_error *error, void *data) { + struct cb_data *cbd = data; + ofono_voicecall_cb_t cb = cbd->cb; + struct send_one_dtmf_cb_data *send_one_dtmf_cb_data = cbd->user; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR || *send_one_dtmf_cb_data->next_dtmf == 0) { + if (error->type == OFONO_ERROR_TYPE_NO_ERROR) { + CALLBACK_WITH_SUCCESS(cb, cbd->data); + } else { + CALLBACK_WITH_FAILURE(cb, cbd->data); + } + l_free((void *)send_one_dtmf_cb_data->full_dtmf); + l_free(send_one_dtmf_cb_data); + l_free(cbd); + } else { + send_one_dtmf(send_one_dtmf_cb_data->vc, *(send_one_dtmf_cb_data->next_dtmf++), send_one_dtmf_cb, data); + } +} + +static void send_dtmf(struct ofono_voicecall *vc, const char *dtmf, + ofono_voicecall_cb_t cb, void *data) +{ + struct cb_data *cbd = cb_data_new(cb, data); + struct send_one_dtmf_cb_data *send_one_dtmf_cb_data = l_new(struct send_one_dtmf_cb_data, 1); + + send_one_dtmf_cb_data->full_dtmf = l_strdup(dtmf); + send_one_dtmf_cb_data->next_dtmf = &send_one_dtmf_cb_data->full_dtmf[1]; + send_one_dtmf_cb_data->vc = vc; + cbd->user = send_one_dtmf_cb_data; + + send_one_dtmf(vc, *dtmf, send_one_dtmf_cb, cbd); } static const struct ofono_voicecall_driver driver = { .probe = qmi_voicecall_probe, .remove = qmi_voicecall_remove, + .dial = dial, + .answer = answer, + .hangup_active = hangup_active, + .release_specific = release_specific, + .send_tones = send_dtmf, }; OFONO_ATOM_DRIVER_BUILTIN(voicecall, qmimodem, &driver) - diff --git a/src/common.h b/src/common.h index cede20d1de329a26dc7de7946de1f5b2a0f6810c..c226d468b8e2830cae09890fdfaae9d9d6523d97 100644 --- a/src/common.h +++ b/src/common.h @@ -19,6 +19,9 @@ * */ +#ifndef OFONO_COMMON_H +#define OFONO_COMMON_H + #include #include @@ -193,3 +196,5 @@ bool gprs_proto_from_string(const char *str, enum ofono_gprs_proto *proto); const char *gprs_auth_method_to_string(enum ofono_gprs_auth_method auth); bool gprs_auth_method_from_string(const char *str, enum ofono_gprs_auth_method *auth); + +#endif