[RFC] qmimodem: implement voice calls

The voice_generated.* files is an RFC how files should look like.
They aren't yet generated.
This commit is contained in:
Alexander Couzens 2017-07-25 15:31:48 +02:00
parent d43fde3c5e
commit df3b0c1cd8
7 changed files with 840 additions and 3 deletions

View File

@ -216,7 +216,9 @@ qmi_sources = drivers/qmimodem/qmi.h drivers/qmimodem/qmi.c \
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 \

View File

@ -19,6 +19,9 @@
*
*/
#ifndef __OFONO_QMI_QMI_H
#define __OFONO_QMI_QMI_H
#include <stdbool.h>
#include <stdint.h>
@ -173,3 +176,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 */

86
drivers/qmimodem/voice.c Normal file
View File

@ -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;
}

84
drivers/qmimodem/voice.h Normal file
View File

@ -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

View File

@ -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;
}

View File

@ -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 */

View File

@ -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 "../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_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);
ofono_voicecall_register(vc);
}
@ -77,7 +177,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 +191,242 @@ static void qmi_voicecall_remove(struct ofono_voicecall *vc)
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)