975b33952e
This patchset is based on the branch on the sysmocom.de git, https://git.sysmocom.de/ofono/log/?h=lynxis/voicecall The original branch was based on old ofono so this patches are manually merged to ofono 1.21 and tested on debian initially.
976 lines
24 KiB
Diff
976 lines
24 KiB
Diff
From df3b0c1cd8be029ff2c98c2ba7ba1110385a17f6 Mon Sep 17 00:00:00 2001
|
|
From: Alexander Couzens <lynxis@fe80.eu>
|
|
Date: Tue, 25 Jul 2017 15:31:48 +0200
|
|
Subject: [PATCH 17/17] [RFC] qmimodem: implement voice calls
|
|
|
|
The voice_generated.* files is an RFC how files should look like.
|
|
They aren't yet generated.
|
|
---
|
|
Makefile.am | 5 +-
|
|
drivers/qmimodem/qmi.h | 13 ++
|
|
drivers/qmimodem/voice.c | 86 ++++++++++
|
|
drivers/qmimodem/voice.h | 84 ++++++++++
|
|
drivers/qmimodem/voice_generated.c | 210 +++++++++++++++++++++++
|
|
drivers/qmimodem/voice_generated.h | 113 +++++++++++++
|
|
drivers/qmimodem/voicecall.c | 332 ++++++++++++++++++++++++++++++++++++-
|
|
7 files changed, 840 insertions(+), 3 deletions(-)
|
|
create mode 100644 drivers/qmimodem/voice.c
|
|
create mode 100644 drivers/qmimodem/voice.h
|
|
create mode 100644 drivers/qmimodem/voice_generated.c
|
|
create mode 100644 drivers/qmimodem/voice_generated.h
|
|
|
|
Index: ofono-1.21/Makefile.am
|
|
===================================================================
|
|
--- ofono-1.21.orig/Makefile.am
|
|
+++ ofono-1.21/Makefile.am
|
|
@@ -216,7 +216,9 @@ qmi_sources = drivers/qmimodem/qmi.h dri
|
|
drivers/qmimodem/wds.h \
|
|
drivers/qmimodem/pds.h \
|
|
drivers/qmimodem/common.h \
|
|
- drivers/qmimodem/wda.h
|
|
+ drivers/qmimodem/wda.h \
|
|
+ drivers/qmimodem/voice.h \
|
|
+ drivers/qmimodem/voice.c
|
|
|
|
builtin_modules += qmimodem
|
|
builtin_sources += $(qmi_sources) \
|
|
@@ -225,6 +227,7 @@ builtin_sources += $(qmi_sources) \
|
|
drivers/qmimodem/qmimodem.c \
|
|
drivers/qmimodem/devinfo.c \
|
|
drivers/qmimodem/voicecall.c \
|
|
+ drivers/qmimodem/voice_generated.c \
|
|
drivers/qmimodem/network-registration.c \
|
|
drivers/qmimodem/sim-legacy.c \
|
|
drivers/qmimodem/sim.c \
|
|
Index: ofono-1.21/drivers/qmimodem/qmi.h
|
|
===================================================================
|
|
--- ofono-1.21.orig/drivers/qmimodem/qmi.h
|
|
+++ ofono-1.21/drivers/qmimodem/qmi.h
|
|
@@ -19,6 +19,9 @@
|
|
*
|
|
*/
|
|
|
|
+#ifndef __OFONO_QMI_QMI_H
|
|
+#define __OFONO_QMI_QMI_H
|
|
+
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
@@ -175,3 +178,13 @@ uint16_t qmi_service_register(struct qmi
|
|
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 */
|
|
Index: ofono-1.21/drivers/qmimodem/voice.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ ofono-1.21/drivers/qmimodem/voice.c
|
|
@@ -0,0 +1,86 @@
|
|
+/*
|
|
+ *
|
|
+ * oFono - Open Source Telephony
|
|
+ *
|
|
+ * Copyright (C) 2017 Alexander Couzens <lynxis@fe80.eu>
|
|
+ *
|
|
+ * 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 <stdint.h>
|
|
+
|
|
+#include "voice.h"
|
|
+#include "../../src/common.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_<UNKNOWN>";
|
|
+}
|
|
+
|
|
+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_INCOMING:
|
|
+ *ret = CALL_STATUS_INCOMING;
|
|
+ break;
|
|
+ case QMI_CALL_STATE_CONV:
|
|
+ *ret = CALL_STATUS_ACTIVE;
|
|
+ break;
|
|
+ case QMI_CALL_STATE_CC_IN_PROG:
|
|
+ case QMI_CALL_STATE_SETUP:
|
|
+ /* FIXME: unsure if _SETUP is dialing or not */
|
|
+ *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;
|
|
+}
|
|
+
|
|
Index: ofono-1.21/drivers/qmimodem/voice.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ ofono-1.21/drivers/qmimodem/voice.h
|
|
@@ -0,0 +1,84 @@
|
|
+/*
|
|
+ *
|
|
+ * oFono - Open Source Telephony
|
|
+ *
|
|
+ * Copyright (C) 2017 Alexander Couzens <lynxis@fe80.eu>
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ */
|
|
+
|
|
+enum call_direction;
|
|
+
|
|
+enum ussd_dcs {
|
|
+ USS_DCS_ASCII = 0x1,
|
|
+ USS_DCS_8BIT,
|
|
+ USS_DCS_UCS2,
|
|
+};
|
|
+
|
|
+enum ussd_user_required {
|
|
+ NO_USER_ACTION_REQUIRED = 0x1,
|
|
+ USER_ACTION_REQUIRED = 0x2,
|
|
+};
|
|
+
|
|
+struct qmi_ussd_data {
|
|
+ uint8_t dcs;
|
|
+ uint8_t length;
|
|
+ uint8_t data[0];
|
|
+} __attribute__((__packed__));
|
|
+
|
|
+enum voice_commands {
|
|
+ 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,
|
|
+};
|
|
+
|
|
+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,
|
|
+};
|
|
+
|
|
+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);
|
|
+
|
|
+#define QMI_VOICE_IND_ALL_STATUS 0x2e
|
|
+
|
|
+#define QMI_VOICE_PARAM_USS_DATA 0x01
|
|
+
|
|
+#define QMI_VOICE_PARAM_ASYNC_USSD_ERROR 0x10
|
|
+#define QMI_VOICE_PARAM_ASYNC_USSD_FAILURE_CASE 0x11
|
|
+#define QMI_VOICE_PARAM_ASYNC_USSD_DATA 0x12
|
|
+
|
|
+#define QMI_VOICE_PARAM_USSD_IND_USER_ACTION 0x01
|
|
+#define QMI_VOICE_PARAM_USSD_IND_DATA 0x10
|
|
+#define QMI_VOICE_PARAM_USSD_IND_UCS2 0x11
|
|
+
|
|
+/* according to GSM TS 23.038 */
|
|
+#define USSD_DCS_8BIT 0xf4
|
|
+#define USSD_DCS_UCS2 0x48
|
|
+#define USSD_DCS_UNSPECIFIC 0x0f
|
|
Index: ofono-1.21/drivers/qmimodem/voice_generated.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ ofono-1.21/drivers/qmimodem/voice_generated.c
|
|
@@ -0,0 +1,210 @@
|
|
+
|
|
+#include <stdint.h>
|
|
+#include <string.h>
|
|
+#include <glib.h>
|
|
+
|
|
+#include "voice_generated.h"
|
|
+
|
|
+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_ind_call_status(
|
|
+ struct qmi_result *qmi_result,
|
|
+ struct qmi_voice_all_call_status_ind *result)
|
|
+{
|
|
+ int err = NONE;
|
|
+ int offset;
|
|
+ uint16_t len;
|
|
+ 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);
|
|
+ if (call_information)
|
|
+ {
|
|
+ int instance_size = sizeof(struct qmi_voice_call_information_instance);
|
|
+ /* 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, 0x10, &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;
|
|
+}
|
|
Index: ofono-1.21/drivers/qmimodem/voice_generated.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ ofono-1.21/drivers/qmimodem/voice_generated.h
|
|
@@ -0,0 +1,113 @@
|
|
+
|
|
+#ifndef __OFONO_QMI_VOICE_GENERATED_H
|
|
+#define __OFONO_QMI_VOICE_GENERATED_H
|
|
+
|
|
+#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_ind_call_status(
|
|
+ struct qmi_result *qmi_result,
|
|
+ struct qmi_voice_all_call_status_ind *result);
|
|
+
|
|
+#endif /* __OFONO_QMI_VOICE_GENERATED_H */
|
|
Index: ofono-1.21/drivers/qmimodem/voicecall.c
|
|
===================================================================
|
|
--- ofono-1.21.orig/drivers/qmimodem/voicecall.c
|
|
+++ ofono-1.21/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 <lynxis@fe80.eu>
|
|
*
|
|
* 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,113 @@
|
|
#include <config.h>
|
|
#endif
|
|
|
|
+#include <string.h>
|
|
+
|
|
#include <ofono/log.h>
|
|
#include <ofono/modem.h>
|
|
#include <ofono/voicecall.h>
|
|
+#include <ofono/call-list.h>
|
|
|
|
-#include "qmi.h"
|
|
+#include "../src/common.h"
|
|
|
|
+#include "qmi.h"
|
|
#include "qmimodem.h"
|
|
+#include "voice.h"
|
|
+#include "voice_generated.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;
|
|
+ GSList *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);
|
|
+ GSList *calls = NULL;
|
|
+ int i;
|
|
+ int size = 0;
|
|
+ struct qmi_voice_all_call_status_ind status_ind;
|
|
+ GSList *n, *o;
|
|
+ struct ofono_call *nc, *oc;
|
|
+
|
|
+
|
|
+ if (qmi_voice_ind_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 = g_new0(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)
|
|
+ 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;
|
|
+
|
|
+ calls = g_slist_insert_sorted(calls, call, ofono_call_compare);
|
|
+ }
|
|
+
|
|
+ ofono_call_list_notify(vc, &vd->call_list, calls);
|
|
+}
|
|
+
|
|
static void create_voice_cb(struct qmi_service *service, void *user_data)
|
|
{
|
|
struct ofono_voicecall *vc = user_data;
|
|
@@ -58,6 +152,12 @@ static void create_voice_cb(struct qmi_s
|
|
|
|
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);
|
|
+
|
|
ofono_voicecall_register(vc);
|
|
}
|
|
|
|
@@ -77,7 +177,6 @@ static int qmi_voicecall_probe(struct of
|
|
create_voice_cb, vc, NULL);
|
|
|
|
return 0;
|
|
-
|
|
}
|
|
|
|
static void qmi_voicecall_remove(struct ofono_voicecall *vc)
|
|
@@ -92,13 +191,242 @@ static void qmi_voicecall_remove(struct
|
|
|
|
qmi_service_unref(data->voice);
|
|
|
|
+ g_slist_free_full(data->call_list, g_free);
|
|
+
|
|
g_free(data);
|
|
}
|
|
|
|
+
|
|
+static struct ofono_call *create_call(struct ofono_voicecall *vc,
|
|
+ enum call_direction direction,
|
|
+ enum call_status status,
|
|
+ const char *num,
|
|
+ int num_type,
|
|
+ int clip)
|
|
+{
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+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;
|
|
+ struct ofono_call *call;
|
|
+
|
|
+ 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 = ph->number;
|
|
+ memcpy(&vd->dialed, ph, sizeof(*ph));
|
|
+
|
|
+ arg.call_type_set = true;
|
|
+ arg.call_type = QMI_CALL_TYPE_VOICE_FORCE;
|
|
+
|
|
+ if (!qmi_voice_dial_call(
|
|
+ &arg,
|
|
+ vd->voice,
|
|
+ dial_cb,
|
|
+ cbd,
|
|
+ g_free))
|
|
+ return;
|
|
+
|
|
+ CALLBACK_WITH_FAILURE(cb, data);
|
|
+ g_free(cbd);
|
|
+}
|
|
+
|
|
+static void answer_cb(struct qmi_result *result, void *user_data)
|
|
+{
|
|
+ struct cb_data *cbd = user_data;
|
|
+ struct ofono_voicecall *vc = cbd->user;
|
|
+ ofono_voicecall_cb_t cb = cbd->cb;
|
|
+ uint16_t error;
|
|
+ struct qmi_voice_answer_call_result answer_result;
|
|
+ struct ofono_call *call;
|
|
+
|
|
+ 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;
|
|
+ GSList *list;
|
|
+
|
|
+ DBG("");
|
|
+ cbd->user = vc;
|
|
+
|
|
+ list = g_slist_find_custom(vd->call_list,
|
|
+ GINT_TO_POINTER(CALL_STATUS_INCOMING),
|
|
+ ofono_call_compare_by_status);
|
|
+
|
|
+ if (list == NULL) {
|
|
+ DBG("Can not find a call to answer");
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ call = list->data;
|
|
+
|
|
+ arg.call_id_set = true;
|
|
+ arg.call_id = call->id;
|
|
+
|
|
+ if (!qmi_voice_answer_call(
|
|
+ &arg,
|
|
+ vd->voice,
|
|
+ answer_cb,
|
|
+ cbd,
|
|
+ g_free))
|
|
+ return;
|
|
+err:
|
|
+ CALLBACK_WITH_FAILURE(cb, data);
|
|
+ g_free(cbd);
|
|
+}
|
|
+
|
|
+static void end_cb(struct qmi_result *result, void *user_data)
|
|
+{
|
|
+ struct cb_data *cbd = user_data;
|
|
+ struct ofono_voicecall *vc = cbd->user;
|
|
+ ofono_voicecall_cb_t cb = cbd->cb;
|
|
+ uint16_t error;
|
|
+ struct qmi_voice_end_call_result end_result;
|
|
+ struct ofono_call *call;
|
|
+
|
|
+ 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;
|
|
+ int i;
|
|
+
|
|
+ DBG("");
|
|
+ cbd->user = vc;
|
|
+
|
|
+ arg.call_id_set = true;
|
|
+ arg.call_id = id;
|
|
+
|
|
+ if (!qmi_voice_end_call(&arg,
|
|
+ vd->voice,
|
|
+ end_cb,
|
|
+ cbd,
|
|
+ g_free))
|
|
+ return;
|
|
+
|
|
+ CALLBACK_WITH_FAILURE(cb, data);
|
|
+ g_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 qmi_voice_end_call_arg arg;
|
|
+ struct ofono_call *call;
|
|
+ GSList *list = NULL;
|
|
+ enum call_status active[] = {
|
|
+ CALL_STATUS_ACTIVE,
|
|
+ CALL_STATUS_DIALING,
|
|
+ CALL_STATUS_ALERTING
|
|
+ };
|
|
+ int i;
|
|
+
|
|
+ DBG("");
|
|
+ for (i = 0; i < ARRAY_SIZE(active); i++) {
|
|
+ list = g_slist_find_custom(vd->call_list,
|
|
+ GINT_TO_POINTER(CALL_STATUS_ACTIVE),
|
|
+ ofono_call_compare_by_status);
|
|
+
|
|
+ if (list)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (list == NULL) {
|
|
+ DBG("Can not find a call to hang up");
|
|
+ CALLBACK_WITH_FAILURE(cb, data);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ call = list->data;
|
|
+ release_specific(vc, call->id, cb, data);
|
|
+}
|
|
+
|
|
static struct ofono_voicecall_driver driver = {
|
|
.name = "qmimodem",
|
|
.probe = qmi_voicecall_probe,
|
|
.remove = qmi_voicecall_remove,
|
|
+ .dial = dial,
|
|
+ .answer = answer,
|
|
+ .hangup_active = hangup_active,
|
|
+ .release_specific = release_specific,
|
|
};
|
|
|
|
void qmi_voicecall_init(void)
|