From 6d486b7fe19945f4c01ccf98b40e83345cd78a2d Mon Sep 17 00:00:00 2001 From: Denis Kenzior Date: Sun, 10 May 2009 23:39:27 -0700 Subject: [PATCH] Add initial implementation of AT modem driver --- drivers/Makefile.am | 12 +- drivers/atmodem/at.h | 89 ++ drivers/atmodem/call-forwarding.c | 247 ++++++ drivers/atmodem/call-meter.c | 387 +++++++++ drivers/atmodem/call-settings.c | 271 ++++++ drivers/atmodem/call-waiting.c | 180 ++++ drivers/atmodem/main.c | 477 ++++++++++- drivers/atmodem/network-registration.c | 675 +++++++++++++++ drivers/atmodem/session.c | 256 ++++++ drivers/atmodem/session.h | 28 + drivers/atmodem/ussd.c | 151 ++++ drivers/atmodem/voicecall.c | 1048 ++++++++++++++++++++++++ 12 files changed, 3815 insertions(+), 6 deletions(-) create mode 100644 drivers/atmodem/at.h create mode 100644 drivers/atmodem/call-forwarding.c create mode 100644 drivers/atmodem/call-meter.c create mode 100644 drivers/atmodem/call-settings.c create mode 100644 drivers/atmodem/call-waiting.c create mode 100644 drivers/atmodem/network-registration.c create mode 100644 drivers/atmodem/session.c create mode 100644 drivers/atmodem/session.h create mode 100644 drivers/atmodem/ussd.c create mode 100644 drivers/atmodem/voicecall.c diff --git a/drivers/Makefile.am b/drivers/Makefile.am index fec162fe..94928c7c 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -4,13 +4,19 @@ builtin_sources = builtin_cflags = builtin_modules += atmodem -builtin_sources += atmodem/main.c +builtin_sources += atmodem/main.c atmodem/at.h \ + atmodem/session.h atmodem/session.c \ + atmodem/call-settings.c atmodem/call-waiting.c \ + atmodem/call-forwarding.c atmodem/call-meter.c \ + atmodem/network-registration.c \ + atmodem/ussd.c atmodem/voicecall.c noinst_LTLIBRARIES = libbuiltin.la libbuiltin_la_SOURCES = $(builtin_sources) libbuiltin_la_LDFLAGS = -libbuiltin_la_CFLAGS = $(AM_CFLAGS) $(builtin_cflags) -DOFONO_PLUGIN_BUILTIN +libbuiltin_la_CFLAGS = $(AM_CFLAGS) $(builtin_cflags) \ + -DOFONO_PLUGIN_BUILTIN -DOFONO_API_SUBJECT_TO_CHANGE BUILT_SOURCES = builtin.h @@ -18,7 +24,7 @@ nodist_libbuiltin_la_SOURCES = $(BUILT_SOURCES) AM_CFLAGS = -fvisibility=hidden @GLIB_CFLAGS@ @GDBUS_CFLAGS@ @GATCHAT_CFLAGS@ -INCLUDES = -I$(top_builddir)/include +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/src CLEANFILES = $(BUILT_SOURCES) diff --git a/drivers/atmodem/at.h b/drivers/atmodem/at.h new file mode 100644 index 00000000..157870c3 --- /dev/null +++ b/drivers/atmodem/at.h @@ -0,0 +1,89 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 + +struct at_data { + GAtChat *parser; + struct ofono_modem *modem; + GIOChannel *io; + char *driver; + + struct netreg_data *netreg; + struct voicecall_data *voicecall; +}; + +void decode_at_error(struct ofono_error *error, const char *final); +void dump_response(const char *func, gboolean ok, GAtResult *result); + +struct cb_data { + void *cb; + void *data; + struct ofono_modem *modem; + void *user; +}; + +static inline struct cb_data *cb_data_new(struct ofono_modem *modem, + void *cb, void *data) +{ + struct cb_data *ret; + + ret = g_try_new0(struct cb_data, 1); + + if (!ret) + return ret; + + ret->cb = cb; + ret->data = data; + ret->modem = modem; + + return ret; +} + +#define DECLARE_FAILURE(e) \ + struct ofono_error e; \ + e.type = OFONO_ERROR_TYPE_FAILURE; \ + e.error = 0 \ + +extern struct ofono_error g_ok; + +extern void at_network_registration_init(struct ofono_modem *modem); +extern void at_network_registration_exit(struct ofono_modem *modem); + +extern void at_call_forwarding_init(struct ofono_modem *modem); +extern void at_call_forwarding_exit(struct ofono_modem *modem); + +extern void at_call_waiting_init(struct ofono_modem *modem); +extern void at_call_waiting_exit(struct ofono_modem *modem); + +extern void at_call_settings_init(struct ofono_modem *modem); +extern void at_call_settings_exit(struct ofono_modem *modem); + +extern void at_ussd_init(struct ofono_modem *modem); +extern void at_ussd_exit(struct ofono_modem *modem); + +extern void at_voicecall_init(struct ofono_modem *modem); +extern void at_voicecall_exit(struct ofono_modem *modem); + +extern void at_call_meter_init(struct ofono_modem *modem); +extern void at_call_meter_exit(struct ofono_modem *modem); diff --git a/drivers/atmodem/call-forwarding.c b/drivers/atmodem/call-forwarding.c new file mode 100644 index 00000000..edc7023b --- /dev/null +++ b/drivers/atmodem/call-forwarding.c @@ -0,0 +1,247 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 "driver.h" + +#include "gatchat.h" +#include "gatresult.h" + +#include "at.h" + +static const char *none_prefix[] = { NULL }; +static const char *ccfc_prefix[] = { "+CCFC:", NULL }; + +static void ccfc_query_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_forwarding_query_cb_t cb = cbd->cb; + struct ofono_error error; + GAtResultIter iter; + int num = 0; + struct ofono_cf_condition *list = NULL; + int i; + + dump_response("ccfc_query_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) + goto out; + + g_at_result_iter_init(&iter, result); + + while (g_at_result_iter_next(&iter, "+CCFC:")) + num += 1; + + /* Specification is really unclear about this + * generate status=0 for all classes just in case + */ + if (num == 0) { + list = g_new0(struct ofono_cf_condition, 1); + num = 1; + + list->status = 0; + list->cls = GPOINTER_TO_INT(cbd->user); + + goto out; + } + + list = g_new(struct ofono_cf_condition, num); + + g_at_result_iter_init(&iter, result); + + for (num = 0; g_at_result_iter_next(&iter, "+CCFC:"); num++) { + const char *str; + + g_at_result_iter_next_number(&iter, &(list[num].status)); + g_at_result_iter_next_number(&iter, &(list[num].cls)); + + list[num].phone_number[0] = '\0'; + list[num].number_type = 129; + list[num].time = 20; + + if (!g_at_result_iter_next_string(&iter, &str)) + continue; + + strncpy(list[num].phone_number, str, + OFONO_MAX_PHONE_NUMBER_LENGTH); + list[num].phone_number[OFONO_MAX_PHONE_NUMBER_LENGTH] = '\0'; + + g_at_result_iter_next_number(&iter, &(list[num].number_type)); + + if (!g_at_result_iter_skip_next(&iter)) + continue; + + if (!g_at_result_iter_skip_next(&iter)) + continue; + + g_at_result_iter_next_number(&iter, &(list[num].time)); + } + + for (i = 0; i < num; i++) + ofono_debug("ccfc_cb: %d, %d, %s(%d) - %d sec", + list[i].status, list[i].cls, + list[i].phone_number, list[i].number_type, + list[i].time); + +out: + cb(&error, num, list, cbd->data); + g_free(list); +} + +static void at_ccfc_query(struct ofono_modem *modem, int type, int cls, + ofono_call_forwarding_query_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[64]; + + if (!cbd) + goto error; + + cbd->user = GINT_TO_POINTER(cls); + + if (cls == 7) + sprintf(buf, "AT+CCFC=%d,2", type); + else + sprintf(buf, "AT+CCFC=%d,2,,,%d", type, cls); + + if (g_at_chat_send(at->parser, buf, ccfc_prefix, + ccfc_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, 0, NULL, data); + } +} + +static void ccfc_set_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("ccfc_set_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + cb(&error, cbd->data); +} + +static void at_ccfc_set(struct ofono_modem *modem, const char *buf, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, buf, none_prefix, + ccfc_set_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void at_ccfc_erasure(struct ofono_modem *modem, int type, int cls, + ofono_generic_cb_t cb, void *data) +{ + char buf[128]; + + sprintf(buf, "AT+CCFC=%d,4,,,%d", type, cls); + at_ccfc_set(modem, buf, cb, data); +} + +static void at_ccfc_deactivation(struct ofono_modem *modem, int type, int cls, + ofono_generic_cb_t cb, void *data) +{ + char buf[128]; + + sprintf(buf, "AT+CCFC=%d,0,,,%d", type, cls); + at_ccfc_set(modem, buf, cb, data); +} + +static void at_ccfc_activation(struct ofono_modem *modem, int type, int cls, + ofono_generic_cb_t cb, void *data) +{ + char buf[128]; + + sprintf(buf, "AT+CCFC=%d,1,,,%d", type, cls); + at_ccfc_set(modem, buf, cb, data); +} + +static void at_ccfc_registration(struct ofono_modem *modem, int type, int cls, + const char *number, int number_type, + int time, ofono_generic_cb_t cb, + void *data) +{ + char buf[128]; + int offset; + + offset = sprintf(buf, "AT+CCFC=%d,3,%s,%d,%d", type, + number, number_type, cls); + + if (type == 2 || type == 4 || type == 5) + sprintf(buf+offset, ",,,%d", time); + + at_ccfc_set(modem, buf, cb, data); +} + +static struct ofono_call_forwarding_ops ops = { + .registration = at_ccfc_registration, + .activation = at_ccfc_activation, + .query = at_ccfc_query, + .deactivation = at_ccfc_deactivation, + .erasure = at_ccfc_erasure +}; + +void at_call_forwarding_init(struct ofono_modem *modem) +{ + ofono_call_forwarding_register(modem, &ops); +} + +void at_call_forwarding_exit(struct ofono_modem *modem) +{ + ofono_call_forwarding_unregister(modem); +} diff --git a/drivers/atmodem/call-meter.c b/drivers/atmodem/call-meter.c new file mode 100644 index 00000000..e7c55c3a --- /dev/null +++ b/drivers/atmodem/call-meter.c @@ -0,0 +1,387 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 "driver.h" + +#include "gatchat.h" +#include "gatresult.h" + +#include "at.h" + +static const char *none_prefix[] = { NULL }; +static const char *caoc_prefix[] = { "+CAOC:", NULL }; +static const char *cacm_prefix[] = { "+CACM:", NULL }; +static const char *camm_prefix[] = { "+CAMM:", NULL }; +static const char *cpuc_prefix[] = { "+CPUC:", NULL }; + +static void caoc_cacm_camm_query_cb(gboolean ok, + GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_meter_query_cb_t cb = cbd->cb; + struct ofono_error error; + GAtResultIter iter; + const char *meter_hex; + char *end; + int meter; + + dump_response("caoc_cacm_camm_query_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, -1, cbd->data); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, cbd->user)) { + DECLARE_FAILURE(e); + + cb(&e, -1, cbd->data); + return; + } + + g_at_result_iter_next_string(&iter, &meter_hex); + meter = strtol(meter_hex, &end, 16); + if (*end) { + DECLARE_FAILURE(e); + + cb(&e, -1, cbd->data); + return; + } + + cb(&error, meter, cbd->data); +} + +static void cccm_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + GAtResultIter iter; + const char *meter_hex; + char *end; + int meter; + + dump_response("cccm_notify", TRUE, result); + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CCCM:")) + return; + + g_at_result_iter_next_string(&iter, &meter_hex); + meter = strtol(meter_hex, &end, 16); + if (*end) { + ofono_error("Invalid CCCM value"); + return; + } + + ofono_call_meter_changed_notify(modem, meter); +} + +static void at_caoc_query(struct ofono_modem *modem, ofono_call_meter_query_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + cbd->user = "+CAOC:"; + if (g_at_chat_send(at->parser, "AT+CAOC=0", caoc_prefix, + caoc_cacm_camm_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, -1, modem); + } +} + +static void at_cacm_query(struct ofono_modem *modem, ofono_call_meter_query_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + cbd->user = "+CACM:"; + if (g_at_chat_send(at->parser, "AT+CACM?", cacm_prefix, + caoc_cacm_camm_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, -1, modem); + } +} + +static void generic_set_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("generic_set_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + cb(&error, cbd->data); +} + +static void at_cacm_set(struct ofono_modem *modem, const char *passwd, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[64]; + + if (!cbd) + goto error; + + snprintf(buf, sizeof(buf), "AT+CACM=\"%s\"", passwd); + + if (g_at_chat_send(at->parser, buf, none_prefix, + generic_set_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, modem); + } +} + +static void at_camm_query(struct ofono_modem *modem, ofono_call_meter_query_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + cbd->user = "+CAMM:"; + if (g_at_chat_send(at->parser, "AT+CAMM?", camm_prefix, + caoc_cacm_camm_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, -1, modem); + } +} + +static void at_camm_set(struct ofono_modem *modem, int accmax, const char *passwd, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[64]; + + if (!cbd) + goto error; + + sprintf(buf, "AT+CAMM=\"%06X\",\"%s\"", accmax, passwd); + + if (g_at_chat_send(at->parser, buf, none_prefix, + generic_set_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, modem); + } +} + +static void cpuc_query_cb(gboolean ok, + GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_meter_puct_query_cb_t cb = cbd->cb; + struct ofono_error error; + GAtResultIter iter; + const char *currency, *ppu; + char currency_buf[64]; + double ppuval; + + dump_response("cpuc_query_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, 0, 0, cbd->data); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, cbd->user)) { + DECLARE_FAILURE(e); + + cb(&e, 0, 0, cbd->data); + return; + } + + g_at_result_iter_next_string(&iter, ¤cy); + strncpy(currency_buf, currency, sizeof(currency_buf)); + + g_at_result_iter_next_string(&iter, &ppu); + ppuval = strtod(ppu, NULL); + + cb(&error, currency_buf, ppuval, cbd->data); +} + +static void at_cpuc_query(struct ofono_modem *modem, + ofono_call_meter_puct_query_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + cbd->user = "+CPUC:"; + if (g_at_chat_send(at->parser, "AT+CPUC?", cpuc_prefix, + cpuc_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, 0, 0, modem); + } +} + +static void at_cpuc_set(struct ofono_modem *modem, const char *currency, + double ppu, const char *passwd, ofono_generic_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[64]; + + if (!cbd) + goto error; + + snprintf(buf, sizeof(buf), "AT+CPUC=\"%s\",\"%f\",\"%s\"", + currency, ppu, passwd); + + if (g_at_chat_send(at->parser, buf, none_prefix, + generic_set_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, modem); + } +} + +static void ccwv_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + GAtResultIter iter; + + dump_response("ccwv_notify", TRUE, result); + + g_at_result_iter_init(&iter, result); + if (!g_at_result_iter_next(&iter, "+CCWV")) + return; + + ofono_call_meter_maximum_notify(modem); +} + +static struct ofono_call_meter_ops ops = { + .call_meter_query = at_caoc_query, + .acm_query = at_cacm_query, + .acm_reset = at_cacm_set, + .acm_max_query = at_camm_query, + .acm_max_set = at_camm_set, + .puct_query = at_cpuc_query, + .puct_set = at_cpuc_set, +}; + +static void at_call_meter_initialized(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + + g_at_chat_register(at->parser, "+CCCM:", + cccm_notify, FALSE, modem, NULL); + g_at_chat_register(at->parser, "+CCWV", + ccwv_notify, FALSE, modem, NULL); + + ofono_call_meter_register(modem, &ops); +} + +void at_call_meter_init(struct ofono_modem *modem) +{ + struct at_data *at = ofono_modem_userdata(modem); + + g_at_chat_send(at->parser, "AT+CAOC=2", NULL, NULL, NULL, NULL); + g_at_chat_send(at->parser, "AT+CCWE=1", NULL, + at_call_meter_initialized, modem, NULL); +} + +void at_call_meter_exit(struct ofono_modem *modem) +{ + ofono_call_meter_unregister(modem); +} diff --git a/drivers/atmodem/call-settings.c b/drivers/atmodem/call-settings.c new file mode 100644 index 00000000..aaed4441 --- /dev/null +++ b/drivers/atmodem/call-settings.c @@ -0,0 +1,271 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 "driver.h" + +#include "gatchat.h" +#include "gatresult.h" + +#include "at.h" + +static const char *none_prefix[] = { NULL }; +static const char *clir_prefix[] = { "+CLIR:", NULL }; +static const char *colp_prefix[] = { "+COLP:", NULL }; +static const char *clip_prefix[] = { "+CLIP:", NULL }; + +static void clip_query_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_setting_status_cb_t cb = cbd->cb; + struct ofono_error error; + GAtResultIter iter; + int status = -1; + + dump_response("clip_query_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, -1, cbd->data); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CLIP:")) { + DECLARE_FAILURE(e); + + cb(&e, -1, cbd->data); + return; + } + + /* Skip the local presentation setting */ + g_at_result_iter_skip_next(&iter); + g_at_result_iter_next_number(&iter, &status); + + ofono_debug("clip_query_cb: network: %d", status); + + cb(&error, status, cbd->data); +} + +static void at_clip_query(struct ofono_modem *modem, + ofono_call_setting_status_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+CLIP?", clip_prefix, + clip_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, -1, data); + } +} + +static void colp_query_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_setting_status_cb_t cb = cbd->cb; + struct ofono_error error; + GAtResultIter iter; + int status; + + dump_response("colp_query_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, -1, cbd->data); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+COLP:")) { + DECLARE_FAILURE(e); + + cb(&e, -1, cbd->data); + return; + } + + /* Skip the local presentation setting */ + g_at_result_iter_skip_next(&iter); + g_at_result_iter_next_number(&iter, &status); + + ofono_debug("colp_query_cb: network: %d", status); + + cb(&error, status, cbd->data); +} + +static void at_colp_query(struct ofono_modem *modem, + ofono_call_setting_status_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+COLP?", colp_prefix, + colp_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, -1, data); + } +} + +static void clir_query_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_clir_setting_cb_t cb = cbd->cb; + struct ofono_error error; + GAtResultIter iter; + int override = 0, network = 2; + + dump_response("clir_query_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, -1, -1, cbd->data); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CLIR:")) { + DECLARE_FAILURE(e); + + cb(&e, -1, -1, cbd->data); + return; + } + + g_at_result_iter_next_number(&iter, &override); + g_at_result_iter_next_number(&iter, &network); + + ofono_debug("clir_query_cb: override: %d, network: %d", + override, network); + + cb(&error, override, network, cbd->data); +} + +static void at_clir_query(struct ofono_modem *modem, + ofono_clir_setting_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+CLIR?", clir_prefix, + clir_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, -1, -1, data); + } +} + +static void clir_set_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("clir_set_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + cb(&error, cbd->data); +} + +static void at_clir_set(struct ofono_modem *modem, int mode, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[64]; + + if (!cbd) + goto error; + + sprintf(buf, "AT+CLIR=%d", mode); + + if (g_at_chat_send(at->parser, buf, none_prefix, + clir_set_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static struct ofono_call_settings_ops ops = { + .clip_query = at_clip_query, + .colp_query = at_colp_query, + .clir_query = at_clir_query, + .clir_set = at_clir_set, + .colr_query = NULL +}; + +void at_call_settings_init(struct ofono_modem *modem) +{ + ofono_call_settings_register(modem, &ops); +} + +void at_call_settings_exit(struct ofono_modem *modem) +{ + ofono_call_settings_unregister(modem); +} diff --git a/drivers/atmodem/call-waiting.c b/drivers/atmodem/call-waiting.c new file mode 100644 index 00000000..cd3370cb --- /dev/null +++ b/drivers/atmodem/call-waiting.c @@ -0,0 +1,180 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 "driver.h" + +#include "gatchat.h" +#include "gatresult.h" + +#include "at.h" + +static const char *none_prefix[] = { NULL }; +static const char *ccwa_prefix[] = { "+CCWA:", NULL }; + +static void ccwa_query_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_waiting_status_cb_t cb = cbd->cb; + struct ofono_error error; + GAtResultIter iter; + int num = 0; + struct ofono_cw_condition *list = NULL; + int i; + + dump_response("ccwa_query_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) + goto out; + + g_at_result_iter_init(&iter, result); + + while (g_at_result_iter_next(&iter, "+CCWA:")) + num += 1; + + /* Specification is really unclear about this + * generate status=0 for all classes just in case + */ + if (num == 0) { + list = g_new(struct ofono_cw_condition, 1); + num = 1; + + list->status = 0; + list->cls = GPOINTER_TO_INT(cbd->user); + + goto out; + } + + list = g_new(struct ofono_cw_condition, num); + + g_at_result_iter_init(&iter, result); + num = 0; + + while (g_at_result_iter_next(&iter, "+CCWA:")) { + g_at_result_iter_next_number(&iter, &(list[num].status)); + g_at_result_iter_next_number(&iter, &(list[num].cls)); + + num += 1; + } + + for (i = 0; i < num; i++) + ofono_debug("ccwa_cb: %d, %d", list[i].status, list[i].cls); + +out: + cb(&error, num, list, cbd->data); + g_free(list); +} + +static void at_ccwa_query(struct ofono_modem *modem, int cls, + ofono_call_waiting_status_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[64]; + + if (!cbd) + goto error; + + cbd->user = GINT_TO_POINTER(cls); + + if (cls == 7) + sprintf(buf, "AT+CCWA=1,2"); + else + sprintf(buf, "AT+CCWA=1,2,%d", cls); + + if (g_at_chat_send(at->parser, buf, ccwa_prefix, + ccwa_query_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, 0, NULL, data); + } +} + +static void ccwa_set_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("ccwa_set_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + cb(&error, cbd->data); +} + +static void at_ccwa_set(struct ofono_modem *modem, int mode, int cls, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[64]; + + if (!cbd) + goto error; + + sprintf(buf, "AT+CCWA=1,%d,%d", mode, cls); + + if (g_at_chat_send(at->parser, buf, none_prefix, + ccwa_set_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static struct ofono_call_waiting_ops ops = { + .query = at_ccwa_query, + .set = at_ccwa_set +}; + +void at_call_waiting_init(struct ofono_modem *modem) +{ + ofono_call_waiting_register(modem, &ops); +} + +void at_call_waiting_exit(struct ofono_modem *modem) +{ + ofono_call_waiting_unregister(modem); +} diff --git a/drivers/atmodem/main.c b/drivers/atmodem/main.c index 1641c68a..384f1688 100644 --- a/drivers/atmodem/main.c +++ b/drivers/atmodem/main.c @@ -23,20 +23,491 @@ #include #endif -#define OFONO_API_SUBJECT_TO_CHANGE +#include +#include +#include +#include + #include #include +#include "driver.h" + +#include "at.h" +#include "dbus-gsm.h" +#include "modem.h" +#include "session.h" + +#define AT_MANAGER_INTERFACE "org.ofono.at.Manager" + +static GSList *g_sessions = NULL; +static GSList *g_pending = NULL; + +static void modem_list(char ***modems) +{ + GSList *l; + int i; + struct at_data *at; + + *modems = g_new0(char *, g_slist_length(g_sessions) + 1); + + for (l = g_sessions, i = 0; l; l = l->next, i++) { + at = l->data; + + (*modems)[i] = at->modem->path; + } +} + +void dump_response(const char *func, gboolean ok, GAtResult *result) +{ + GSList *l; + + ofono_debug("%s got result: %d", func, ok); + ofono_debug("Final response: %s", result->final_or_pdu); + + for (l = result->lines; l; l = l->next) + ofono_debug("Response line: %s", (char *) l->data); +} + +void decode_at_error(struct ofono_error *error, const char *final) +{ + if (!strcmp(final, "OK")) { + error->type = OFONO_ERROR_TYPE_NO_ERROR; + error->error = 0; + } else { + error->type = OFONO_ERROR_TYPE_FAILURE; + error->error = 0; + } +} + +static void at_destroy(struct at_data *at) +{ + if (at->parser) + g_at_chat_unref(at->parser); + + if (at->driver) + g_free(at->driver); + + g_free(at); +} + +static void manager_free(gpointer user) +{ + GSList *l; + + for (l = g_pending; l; l = l->next) + g_io_channel_unref(l->data); + + g_slist_free(g_pending); + + for (l = g_sessions; l; l = l->next) { + struct at_data *at = l->data; + + at_call_forwarding_exit(at->modem); + at_call_waiting_exit(at->modem); + at_call_settings_exit(at->modem); + at_network_registration_exit(at->modem); + at_voicecall_exit(at->modem); + at_ussd_exit(at->modem); + at_call_meter_exit(at->modem); + ofono_modem_unregister(at->modem); + + at_destroy(at); + } + + g_slist_free(g_sessions); +} + +struct attr_cb_info { + ofono_modem_attribute_query_cb_t cb; + void *data; + const char *prefix; +}; + +static inline struct attr_cb_info *attr_cb_info_new(ofono_modem_attribute_query_cb_t cb, + void *data, + const char *prefix) +{ + struct attr_cb_info *ret; + + ret = g_try_new(struct attr_cb_info, 1); + + if (!ret) + return ret; + + ret->cb = cb; + ret->data = data; + ret->prefix = prefix; + + return ret; +} + +static const char *fixup_return(const char *line, const char *prefix) +{ + if (g_str_has_prefix(line, prefix) == FALSE) + return line; + + line = line + strlen(prefix); + + while (line[0] == ' ') + line++; + + return line; +} + +static void attr_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct ofono_error error; + struct attr_cb_info *info = user_data; + + decode_at_error(&error, g_at_result_final_response(result)); + + dump_response("attr_cb", ok, result); + + if (ok) { + GAtResultIter iter; + const char *line; + int i; + + g_at_result_iter_init(&iter, result); + + /* We have to be careful here, sometimes a stray unsolicited + * notification will appear as part of the response and we + * cannot rely on having a prefix to recognize the actual + * response line. So use the last line only as the response + */ + for (i = 0; i < g_at_result_num_response_lines(result); i++) + g_at_result_iter_next(&iter, NULL); + + line = g_at_result_iter_raw_line(&iter); + + info->cb(&error, fixup_return(line, info->prefix), info->data); + } else + info->cb(&error, "", info->data); +} + +static void at_query_manufacturer(struct ofono_modem *modem, + ofono_modem_attribute_query_cb_t cb, void *data) +{ + struct attr_cb_info *info = attr_cb_info_new(cb, data, "+CGMI:"); + struct at_data *at = ofono_modem_userdata(modem); + + if (!info) + goto error; + + if (g_at_chat_send(at->parser, "AT+CGMI", NULL, + attr_cb, info, g_free) > 0) + return; + +error: + if (info) + g_free(info); + + { + DECLARE_FAILURE(error); + cb(&error, NULL, data); + } +} + +static void at_query_model(struct ofono_modem *modem, + ofono_modem_attribute_query_cb_t cb, void *data) +{ + struct attr_cb_info *info = attr_cb_info_new(cb, data, "+CGMM:"); + struct at_data *at = ofono_modem_userdata(modem); + + if (!info) + goto error; + + if (g_at_chat_send(at->parser, "AT+CGMM", NULL, + attr_cb, info, g_free) > 0) + return; + +error: + if (info) + g_free(info); + + { + DECLARE_FAILURE(error); + cb(&error, NULL, data); + } +} + +static void at_query_revision(struct ofono_modem *modem, + ofono_modem_attribute_query_cb_t cb, void *data) +{ + struct attr_cb_info *info = attr_cb_info_new(cb, data, "+CGMR:"); + struct at_data *at = ofono_modem_userdata(modem); + + if (!info) + goto error; + + if (g_at_chat_send(at->parser, "AT+CGMR", NULL, + attr_cb, info, g_free) > 0) + return; + +error: + if (info) + g_free(info); + + { + DECLARE_FAILURE(error); + cb(&error, NULL, data); + } +} + +static void at_query_serial(struct ofono_modem *modem, + ofono_modem_attribute_query_cb_t cb, void *data) +{ + struct attr_cb_info *info = attr_cb_info_new(cb, data, "+CGSN:"); + struct at_data *at = ofono_modem_userdata(modem); + + if (!info) + goto error; + + if (g_at_chat_send(at->parser, "AT+CGSN", NULL, + attr_cb, info, g_free) > 0) + return; + +error: + if (info) + g_free(info); + + { + DECLARE_FAILURE(error); + cb(&error, NULL, data); + } +} + +static void send_init_commands(const char *vendor, GAtChat *parser) +{ + if (!strcmp(vendor, "ti_calypso")) { + g_at_chat_set_wakeup_command(parser, "\r", 1000, 5000); + + g_at_chat_send(parser, "AT%CUNS=0", NULL, + NULL, NULL, NULL); + } +} + +static struct ofono_modem_attribute_ops ops = { + .query_manufacturer = at_query_manufacturer, + .query_model = at_query_model, + .query_revision = at_query_revision, + .query_serial = at_query_serial +}; + +static void msg_destroy(gpointer user) +{ + DBusMessage *msg = user; + + dbus_message_unref(msg); +} + +static void create_cb(GIOChannel *io, gboolean success, gpointer user) +{ + DBusConnection *conn = dbus_gsm_connection(); + DBusMessage *msg = user; + DBusMessage *reply; + struct at_data *at = NULL; + const char *path; + const char *target, *driver; + char **modems; + + if (success == FALSE) + goto out; + + at = g_new0(struct at_data, 1); + + at->parser = g_at_chat_new(io, 0); + + if (!at->parser) + goto out; + + ofono_debug("Seting up AT channel"); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &target, + DBUS_TYPE_STRING, &driver, DBUS_TYPE_INVALID); + + send_init_commands(driver, at->parser); + + at->modem = ofono_modem_register(&ops); + + if (!at->modem) + goto out; + + ofono_modem_set_userdata(at->modem, at); + + at_ussd_init(at->modem); + at_call_forwarding_init(at->modem); + at_call_settings_init(at->modem); + at_call_waiting_init(at->modem); + at_network_registration_init(at->modem); + at_voicecall_init(at->modem); + at_call_meter_init(at->modem); + + at->io = io; + at->driver = g_strdup(driver); + + g_pending = g_slist_remove(g_pending, io); + g_sessions = g_slist_prepend(g_sessions, at); + + path = at->modem->path; + + reply = dbus_message_new_method_return(msg); + + dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + g_dbus_send_message(conn, reply); + + modem_list(&modems); + dbus_gsm_signal_array_property_changed(conn, "/", AT_MANAGER_INTERFACE, + "Modems", DBUS_TYPE_OBJECT_PATH, + &modems); + g_free(modems); + + return; + +out: + if (at) + at_destroy(at); + + reply = dbus_gsm_failed(msg); + g_dbus_send_message(conn, reply); +} + +static DBusMessage *manager_create(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *target; + const char *driver; + GIOChannel *io; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &target, + DBUS_TYPE_STRING, &driver, + DBUS_TYPE_INVALID)) + return dbus_gsm_invalid_args(msg); + + io = modem_session_create(target, create_cb, msg, msg_destroy); + + if (!io) + return dbus_gsm_invalid_format(msg); + + dbus_message_ref(msg); + + g_pending = g_slist_prepend(g_pending, io); + + return NULL; +} + +static DBusMessage *manager_destroy(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *path; + GSList *l; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return dbus_gsm_invalid_args(msg); + + for (l = g_sessions; l; l = l->next) { + struct at_data *at = l->data; + char **modems; + + if (strcmp(at->modem->path, path)) + continue; + + at_network_registration_exit(at->modem); + at_voicecall_exit(at->modem); + ofono_modem_unregister(at->modem); + + g_sessions = g_slist_remove(g_sessions, at); + at_destroy(at); + + modem_list(&modems); + dbus_gsm_signal_array_property_changed(conn, "/", + AT_MANAGER_INTERFACE, + "Modems", DBUS_TYPE_OBJECT_PATH, + &modems); + g_free(modems); + + return dbus_message_new_method_return(msg); + } + + return dbus_gsm_not_found(msg); +} + +static DBusMessage *manager_get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter; + DBusMessageIter dict; + DBusMessage *reply; + char **modems; + + reply = dbus_message_new_method_return(msg); + + if (!reply) + return NULL; + + modem_list(&modems); + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + PROPERTIES_ARRAY_SIGNATURE, &dict); + + dbus_gsm_dict_append_array(&dict, "Modems", DBUS_TYPE_OBJECT_PATH, + &modems); + + g_free(modems); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable manager_methods[] = { + { "Create", "ss", "o", manager_create, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Destroy", "o", "", manager_destroy }, + { "GetProperties", "", "a{sv}", manager_get_properties }, + { } +}; + +static GDBusSignalTable manager_signals[] = { + { "PropertyChanged", "sv" }, + { } +}; + +static int manager_init(DBusConnection *conn) +{ + if (g_dbus_register_interface(conn, "/", AT_MANAGER_INTERFACE, + manager_methods, manager_signals, + NULL, NULL, manager_free) == FALSE) + return -1; + + return 0; +} + +static void manager_exit(DBusConnection *conn) +{ + g_dbus_unregister_interface(conn, "/", AT_MANAGER_INTERFACE); +} static int atmodem_init(void) { - DBG(""); + DBusConnection *conn = dbus_gsm_connection(); + + manager_init(conn); return 0; } static void atmodem_exit(void) { - DBG(""); + DBusConnection *conn = dbus_gsm_connection(); + + manager_exit(conn); } OFONO_PLUGIN_DEFINE(atmodem, "AT modem driver", VERSION, diff --git a/drivers/atmodem/network-registration.c b/drivers/atmodem/network-registration.c new file mode 100644 index 00000000..3cee2c7d --- /dev/null +++ b/drivers/atmodem/network-registration.c @@ -0,0 +1,675 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 "driver.h" + +#include "gatchat.h" +#include "gatresult.h" + +#include "at.h" + +static const char *none_prefix[] = { NULL }; +static const char *creg_prefix[] = { "+CREG:", NULL }; +static const char *cops_prefix[] = { "+COPS:", NULL }; +static const char *csq_prefix[] = { "+CSQ:", NULL }; + +struct netreg_data { + gboolean supports_tech; + short mnc; + short mcc; +}; + +static void extract_mcc_mnc(const char *str, short *mcc, short *mnc) +{ + int num = 0; + unsigned int i; + + /* Three digit country code */ + for (i = 0; i < 3; i++) + num = num * 10 + (int)(str[i] - '0'); + + *mcc = num; + + num = 0; + + /* Usually a 2 but sometimes 3 digit network code */ + for (; i < strlen(str); i++) + num = num * 10 + (int)(str[i] - '0'); + + *mnc = num; +} + +static void at_creg_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct at_data *at = ofono_modem_userdata(cbd->modem); + GAtResultIter iter; + ofono_registration_status_cb_t cb = cbd->cb; + int status; + const char *str; + int lac = -1, ci = -1, tech = -1; + struct ofono_error error; + + dump_response("at_creg_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, -1, -1, -1, -1, cbd->data); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CREG:")) { + DECLARE_FAILURE(e); + + cb(&e, -1, -1, -1, -1, cbd->data); + return; + } + + /* Skip the unsolicited result code */ + g_at_result_iter_skip_next(&iter); + + g_at_result_iter_next_number(&iter, &status); + + if (g_at_result_iter_next_string(&iter, &str) == TRUE) + lac = strtol(str, NULL, 16); + + if (g_at_result_iter_next_string(&iter, &str) == TRUE) + ci = strtol(str, NULL, 16); + + if (g_at_result_iter_next_number(&iter, &tech) == TRUE) + at->netreg->supports_tech = TRUE; + + ofono_debug("creg_cb: %d, %d, %d, %d", status, lac, ci, tech); + + cb(&error, status, lac, ci, tech, cbd->data); +} + +static void at_registration_status(struct ofono_modem *modem, + ofono_registration_status_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+CREG?", creg_prefix, + at_creg_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, -1, -1, -1, -1, data); + } +} + +static void cops_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct at_data *at = ofono_modem_userdata(cbd->modem); + ofono_current_operator_cb_t cb = cbd->cb; + struct ofono_network_operator op; + GAtResultIter iter; + int format, tech; + const char *name; + struct ofono_error error; + + dump_response("cops_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok || at->netreg->mcc == -1 || at->netreg->mnc == -1) { + cb(&error, NULL, cbd->data); + goto out; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+COPS:")) + goto error; + + g_at_result_iter_skip_next(&iter); + + ok = g_at_result_iter_next_number(&iter, &format); + + if (ok == FALSE || format != 0) + goto error; + + if (g_at_result_iter_next_string(&iter, &name) == FALSE) + goto error; + + /* Default to GSM */ + if (g_at_result_iter_next_number(&iter, &tech) == FALSE) + tech = 0; + + strncpy(op.name, name, OFONO_MAX_OPERATOR_NAME_LENGTH); + op.name[OFONO_MAX_OPERATOR_NAME_LENGTH] = '\0'; + + op.mcc = at->netreg->mcc; + op.mnc = at->netreg->mnc; + op.status = -1; + op.tech = tech; + + ofono_debug("cops_cb: %s, %hd %hd %d", name, at->netreg->mcc, + at->netreg->mnc, tech); + + cb(&error, &op, cbd->data); + +out: + g_free(cbd); + + return; + +error: + { + DECLARE_FAILURE(e); + + cb(&e, NULL, cbd->data); + } + + g_free(cbd); +} + +static void cops_numeric_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct at_data *at = ofono_modem_userdata(cbd->modem); + GAtResultIter iter; + const char *str; + int format; + + dump_response("cops_numeric_cb", ok, result); + + if (!ok) + goto error; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+COPS:")) + goto error; + + g_at_result_iter_skip_next(&iter); + + ok = g_at_result_iter_next_number(&iter, &format); + + if (ok == FALSE || format != 2) + goto error; + + if (g_at_result_iter_next_string(&iter, &str) == FALSE || + strlen(str) == 0) + goto error; + + extract_mcc_mnc(str, &at->netreg->mcc, &at->netreg->mnc); + + ofono_debug("Cops numeric got mcc: %hd, mnc: %hd", + at->netreg->mcc, at->netreg->mnc); + + return; + +error: + at->netreg->mcc = at->netreg->mnc = -1; +} + +static void at_current_operator(struct ofono_modem *modem, + ofono_current_operator_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + gboolean ok; + + if (!cbd) + goto error; + + ok = g_at_chat_send(at->parser, "AT+COPS=3,2", none_prefix, + NULL, NULL, NULL); + + if (ok) + ok = g_at_chat_send(at->parser, "AT+COPS?", cops_prefix, + cops_numeric_cb, cbd, NULL); + + if (ok) + ok = g_at_chat_send(at->parser, "AT+COPS=3,0", none_prefix, + NULL, NULL, NULL); + + if (ok) + ok = g_at_chat_send(at->parser, "AT+COPS?", cops_prefix, + cops_cb, cbd, NULL); + + if (ok) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, NULL, data); + } +} + +static void cops_list_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + //struct at_data *at = ofono_modem_userdata(cbd->modem); + ofono_operator_list_cb_t cb = cbd->cb; + struct ofono_network_operator *list; + GAtResultIter iter; + int num = 0; + struct ofono_error error; + + dump_response("cops_list_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, 0, NULL, cbd->data); + return; + } + + g_at_result_iter_init(&iter, result); + + while (g_at_result_iter_next(&iter, "+COPS:")) { + while (g_at_result_iter_skip_next(&iter)) + num += 1; + } + + ofono_debug("Got %d elements", num); + + list = g_try_new0(struct ofono_network_operator, num); + + if (!list) { + DECLARE_FAILURE(e); + cb(&e, 0, NULL, cbd->data); + return; + } + + num = 0; + g_at_result_iter_init(&iter, result); + + while (g_at_result_iter_next(&iter, "+COPS:")) { + int status, tech; + const char *l, *s, *n; + gboolean have_long = FALSE; + + while (1) { + if (!g_at_result_iter_open_list(&iter)) + break; + + if (!g_at_result_iter_next_number(&iter, &status)) + break; + + list[num].status = status; + + if (!g_at_result_iter_next_string(&iter, &l)) + break; + + if (strlen(l) > 0) { + have_long = TRUE; + strncpy(list[num].name, l, + OFONO_MAX_OPERATOR_NAME_LENGTH); + } + + if (!g_at_result_iter_next_string(&iter, &s)) + break; + + if (strlen(s) > 0 && !have_long) + strncpy(list[num].name, s, + OFONO_MAX_OPERATOR_NAME_LENGTH); + + list[num].name[OFONO_MAX_OPERATOR_NAME_LENGTH] = '\0'; + + if (!g_at_result_iter_next_string(&iter, &n)) + break; + + extract_mcc_mnc(n, &list[num].mcc, &list[num].mnc); + + if (!g_at_result_iter_next_number(&iter, &tech)) + tech = 0; + + list[num].tech = tech; + + if (!g_at_result_iter_close_list(&iter)) + break; + + num += 1; + } + } + + ofono_debug("Got %d operators", num); + +{ + int i = 0; + + for (; i < num; i++) { + ofono_debug("Operator: %s, %hd, %hd, status: %d, %d", + list[i].name, list[i].mcc, list[i].mnc, + list[i].status, list[i].tech); + } +} + + cb(&error, num, list, cbd->data); + + g_free(list); +} + +static void at_list_operators(struct ofono_modem *modem, ofono_operator_list_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+COPS=?", cops_prefix, + cops_list_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, 0, NULL, data); + } +} + +static void register_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("register_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + cb(&error, cbd->data); +} + +static void at_register_auto(struct ofono_modem *modem, ofono_generic_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+COPS=0", none_prefix, + register_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void at_register_manual(struct ofono_modem *modem, + const struct ofono_network_operator *oper, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[128]; + + if (!cbd) + goto error; + + if (at->netreg->supports_tech && oper->tech != -1) + sprintf(buf, "AT+COPS=1,2,\"%03hd%02hd\",%1d", oper->mcc, + oper->mnc, + oper->tech); + else + sprintf(buf, "AT+COPS=1,2,\"%03hd%02hd\"", oper->mcc, + oper->mnc); + + if (g_at_chat_send(at->parser, buf, none_prefix, + register_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void at_deregister(struct ofono_modem *modem, ofono_generic_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+COPS=2", none_prefix, + register_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void csq_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + //struct at_data *at = ofono_modem_userdata(modem); + int strength; + GAtResultIter iter; + + dump_response("csq_notify", TRUE, result); + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CSQ:")) + return; + + if (!g_at_result_iter_next_number(&iter, &strength)) + return; + + ofono_debug("csq_notify: %d", strength); + + if (strength == 99) + strength = -1; + else + strength = strength * 100 / 31; + + ofono_signal_strength_notify(modem, strength); +} + +static void csq_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_signal_strength_cb_t cb = cbd->cb; + int strength; + GAtResultIter iter; + struct ofono_error error; + + dump_response("csq_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, -1, cbd->data); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CSQ:")) { + DECLARE_FAILURE(e); + + cb(&e, -1, cbd->data); + return; + } + + g_at_result_iter_next_number(&iter, &strength); + + ofono_debug("csq_cb: %d", strength); + + if (strength == 99) + strength = -1; + else + strength = strength * 100 / 31; + + cb(&error, strength, cbd->data); +} + +static void at_signal_strength(struct ofono_modem *modem, + ofono_signal_strength_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+CSQ", csq_prefix, + csq_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, -1, data); + } +} + +static void creg_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + GAtResultIter iter; + int status; + int lac = -1, ci = -1, tech = -1; + const char *str; + + dump_response("creg_notify", TRUE, result); + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CREG:")) + return; + + g_at_result_iter_next_number(&iter, &status); + + if (g_at_result_iter_next_string(&iter, &str) == TRUE) + lac = strtol(str, NULL, 16); + + if (g_at_result_iter_next_string(&iter, &str) == TRUE) + ci = strtol(str, NULL, 16); + + if (g_at_result_iter_next_number(&iter, &tech) == TRUE) + at->netreg->supports_tech = TRUE; + + ofono_debug("creg_notify: %d, %d, %d, %d", status, lac, ci, tech); + + ofono_network_registration_notify(modem, status, lac, ci, tech); +} + +static struct ofono_network_registration_ops ops = { + .registration_status = at_registration_status, + .current_operator = at_current_operator, + .list_operators = at_list_operators, + .register_auto = at_register_auto, + .register_manual = at_register_manual, + .deregister = at_deregister, + .signal_strength = at_signal_strength, +}; + +static void at_network_registration_initialized(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + + if (!ok) { + ofono_error("Unable to initialize Network Registration"); + return; + } + + g_at_chat_register(at->parser, "+CREG:", + creg_notify, FALSE, modem, NULL); + g_at_chat_register(at->parser, "+CSQ:", + csq_notify, FALSE, modem, NULL); + + ofono_network_registration_register(modem, &ops); +} + +void at_network_registration_init(struct ofono_modem *modem) +{ + struct at_data *at = ofono_modem_userdata(modem); + + at->netreg = g_try_new0(struct netreg_data, 1); + + if (!at->netreg) + return; + + g_at_chat_send(at->parser, "AT+CREG=2", NULL, + at_network_registration_initialized, + modem, NULL); +} + +void at_network_registration_exit(struct ofono_modem *modem) +{ + struct at_data *at = ofono_modem_userdata(modem); + + g_free(at->netreg); + at->netreg = NULL; + + ofono_network_registration_unregister(modem); +} diff --git a/drivers/atmodem/session.c b/drivers/atmodem/session.c new file mode 100644 index 00000000..2301756d --- /dev/null +++ b/drivers/atmodem/session.c @@ -0,0 +1,256 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "session.h" + +struct modem_session_callback { + modem_session_callback_t callback; + gpointer user_data; + GDestroyNotify notify; + guint timeout_watcher; + GIOChannel *io; +}; + +static void connect_destroy(gpointer user) +{ + struct modem_session_callback *callback = user; + + if (callback->notify) + callback->notify(callback->user_data); + + if (callback->timeout_watcher != 0) + g_source_remove(callback->timeout_watcher); + + g_free(callback); +} + +static gboolean connect_cb(GIOChannel *io, GIOCondition cond, gpointer user) +{ + struct modem_session_callback *callback = user; + int err = 0; + gboolean success; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & G_IO_OUT) { + int sock = g_io_channel_unix_get_fd(io); + socklen_t len = sizeof(err); + + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0) + err = errno; + } else if (cond & (G_IO_HUP | G_IO_ERR)) + err = ECONNRESET; + + success = !err; + + callback->callback(io, success, callback->user_data); + + return FALSE; +} + +static gboolean connect_timeout(gpointer user) +{ + struct modem_session_callback *callback = user; + + callback->callback(callback->io, FALSE, callback->user_data); + + callback->timeout_watcher = 0; + + g_io_channel_unref(callback->io); + + return FALSE; +} + +#if 0 +static int tty_open(const char *tty, struct termios *ti) +{ + int sk; + + sk = open(tty, O_RDWR | O_NOCTTY); + + if (sk < 0) { + ofono_error("Can't open TTY %s: %s(%d)", + tty, strerror(errno), errno); + return -1; + } + + if (ti && tcsetattr(sk, TCSANOW, ti) < 0) { + ofono_error("Can't change serial settings: %s(%d)", + strerror(errno), errno); + close(sk); + return -1; + } + + return sk; +} +#endif + +static GIOChannel *socket_common(int sk, struct sockaddr *addr, + socklen_t addrlen) +{ + GIOChannel *io = g_io_channel_unix_new(sk); + + if (io == NULL) { + close(sk); + return NULL; + } + + g_io_channel_set_close_on_unref(io, TRUE); + + if (g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, + NULL) != G_IO_STATUS_NORMAL) { + g_io_channel_unref(io); + return NULL; + } + + if (connect(sk, addr, addrlen) < 0) { + if (errno != EAGAIN && errno != EINPROGRESS) { + g_io_channel_unref(io); + return NULL; + } + } + + return io; +} + +static GIOChannel *unix_connect(const char *address) +{ + struct sockaddr_un addr; + int sk; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = PF_UNIX; + + if (strncmp("x00", address, 3) == 0) + strcpy(addr.sun_path + 1, address + 3); + else + strcpy(addr.sun_path, address); + + sk = socket(AF_UNIX, SOCK_STREAM, 0); + + if (sk < 0) + return NULL; + + return socket_common(sk, (struct sockaddr *)&addr, sizeof(addr)); +} + +static GIOChannel *tcp_connect(const char *address) +{ + struct sockaddr_in addr; + int sk; + unsigned short port; + in_addr_t inetaddr; + char *portstr; + char addrstr[16]; + + memset(&addr, 0, sizeof(addr)); + + portstr = strchr(address, ':'); + + if (!portstr || (unsigned int)(portstr-address) > (sizeof(addrstr) - 1)) + return NULL; + + strncpy(addrstr, address, portstr-address); + addrstr[portstr-address] = '\0'; + + portstr += 1; + + port = atoi(portstr); + + if (port == 0) + return NULL; + + inetaddr = inet_addr(addrstr); + + if (inetaddr == INADDR_NONE) + return NULL; + + sk = socket(PF_INET, SOCK_STREAM, 0); + + if (sk < 0) + return NULL; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inetaddr; + addr.sin_port = htons(port); + + return socket_common(sk, (struct sockaddr *) &addr, sizeof(addr)); +} + +GIOChannel *modem_session_create(const char *target, + modem_session_callback_t func, + gpointer user_data, + GDestroyNotify notify) +{ + struct modem_session_callback *callback; + GIOChannel *io = NULL; + GIOCondition cond; + + if (target == NULL || func == NULL) + return NULL; + + if (!strncasecmp(target, "tcp:", 4)) + io = tcp_connect(target+4); + else if (!strncasecmp(target, "unix:", 5)) + io = unix_connect(target+5); + + if (io == NULL) + return NULL; + + callback = g_new0(struct modem_session_callback, 1); + + callback->callback = func; + callback->user_data = user_data; + callback->notify = notify; + callback->io = io; + callback->timeout_watcher = g_timeout_add_seconds(20, connect_timeout, + callback); + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, + callback, connect_destroy); + + g_io_channel_unref(io); + + return io; +} diff --git a/drivers/atmodem/session.h b/drivers/atmodem/session.h new file mode 100644 index 00000000..2e3e305e --- /dev/null +++ b/drivers/atmodem/session.h @@ -0,0 +1,28 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 + * + */ + +typedef void (*modem_session_callback_t)(GIOChannel *io, gboolean success, + gpointer user_data); + +GIOChannel *modem_session_create(const char *target, + modem_session_callback_t func, + gpointer user_data, + GDestroyNotify notify); diff --git a/drivers/atmodem/ussd.c b/drivers/atmodem/ussd.c new file mode 100644 index 00000000..992fbe53 --- /dev/null +++ b/drivers/atmodem/ussd.c @@ -0,0 +1,151 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 "driver.h" +#include "util.h" + +#include "gatchat.h" +#include "gatresult.h" + +#include "at.h" + +static const char *none_prefix[] = { NULL }; + +static void cusd_request_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("cusd_request_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + cb(&error, cbd->data); +} + +static void at_ussd_request(struct ofono_modem *modem, const char *str, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + unsigned char *converted; + //struct ofono_error error; + int dcs; + int max_len; + long written; + char buf[256]; + + if (!cbd) + goto error; + + converted = convert_utf8_to_gsm(str, strlen(str), NULL, &written, 0); + + /* TODO: Be able to convert to UCS2, although the standard does not + * indicate that this is actually possible + */ + if (!converted) + goto error; + else { + dcs = 15; + max_len = 182; + } + + if (written > max_len) + goto error; + + sprintf(buf, "AT+CUSD=1,\"%s\",%d", converted, dcs); + + if (g_at_chat_send(at->parser, buf, none_prefix, + cusd_request_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void cusd_cancel_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("cusd_cancel_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + cb(&error, cbd->data); +} + +static void at_ussd_cancel(struct ofono_modem *modem, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+CUSD=2", none_prefix, + cusd_cancel_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static struct ofono_ussd_ops ops = { + .request = at_ussd_request, + .cancel = at_ussd_cancel +}; + +void at_ussd_init(struct ofono_modem *modem) +{ + /* TODO: Register for USSD Notifications */ + ofono_ussd_register(modem, &ops); +} + +void at_ussd_exit(struct ofono_modem *modem) +{ + ofono_ussd_unregister(modem); +} diff --git a/drivers/atmodem/voicecall.c b/drivers/atmodem/voicecall.c new file mode 100644 index 00000000..a6b19183 --- /dev/null +++ b/drivers/atmodem/voicecall.c @@ -0,0 +1,1048 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * 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 "driver.h" + +#include "gatchat.h" +#include "gatresult.h" + +#include "at.h" + +/* Amount of ms we wait between CLCC calls */ +#define POLL_CLCC_INTERVAL 500 + + /* Amount of time we give for CLIP to arrive before we commence CLCC poll */ +#define CLIP_INTERVAL 200 + +static const char *clcc_prefix[] = { "+CLCC:", NULL }; +static const char *none_prefix[] = { NULL }; + +/* According to 27.007 COLP is an intermediate status for ATD */ +static const char *aofono_prefix[] = { "+COLP:", NULL }; + +struct voicecall_data { + gboolean poll_clcc; + GSList *calls; + unsigned int id_list; + unsigned int local_release; + unsigned int clcc_source; +}; + +static gboolean poll_clcc(gpointer user_data); + +static int class_to_call_type(int cls) +{ + switch (cls) { + case 1: + return 0; + case 4: + return 2; + case 8: + return 9; + default: + return 1; + } +} + +static unsigned int alloc_next_id(struct voicecall_data *d) +{ + unsigned int i; + + for (i = 1; i < sizeof(d->id_list) * 8; i++) { + if (d->id_list & (0x1 << i)) + continue; + + d->id_list |= (0x1 << i); + return i; + } + + return 0; +} + +#if 0 +static gboolean alloc_specific_id(struct voicecall_data *d, unsigned int id) +{ + if (id < 1 || id > sizeof(d->id_list)) + return FALSE; + + if (d->id_list & (0x1 << id)) + return FALSE; + + d->id_list |= (0x1 << id); + + return TRUE; +} +#endif + +static void release_id(struct voicecall_data *d, unsigned int id) +{ + d->id_list &= ~(0x1 << id); +} + +#if 0 +static gint call_compare_by_id(gconstpointer a, gconstpointer b) +{ + const struct ofono_call *call = a; + unsigned int id = GPOINTER_TO_UINT(b); + + if (id < call->id) + return -1; + + if (id > call->id) + return 1; + + return 0; +} +#endif + +static gint call_compare_by_status(gconstpointer a, gconstpointer b) +{ + const struct ofono_call *call = a; + int status = GPOINTER_TO_INT(b); + + if (status != call->status) + return 1; + + return 0; +} + +static gint call_compare(gconstpointer a, gconstpointer b) +{ + 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; +} + +static struct ofono_call *create_call(struct voicecall_data *d, int type, + int direction, int status, + const char *num, int num_type, int clip) +{ + struct ofono_call *call; + + /* Generate a call structure for the waiting call */ + call = g_try_new0(struct ofono_call, 1); + + if (!call) + return NULL; + + call->id = alloc_next_id(d); + call->type = type; + call->direction = direction; + call->status = status; + + if (clip != 2) { + strncpy(call->phone_number, num, OFONO_MAX_PHONE_NUMBER_LENGTH); + call->number_type = num_type; + } + + call->clip_validity = clip; + + d->calls = g_slist_insert_sorted(d->calls, call, call_compare); + + return call; +} + +#if 0 +static void destroy_call(struct ofono_call *call, struct voicecall_data *d) +{ + + g_free(call); +} +#endif + +static GSList *parse_clcc(GAtResult *result) +{ + GAtResultIter iter; + GSList *l = NULL; + int id, dir, status, type, number_type; + //const char *str; + struct ofono_call *call; + + g_at_result_iter_init(&iter, result); + + while (g_at_result_iter_next(&iter, "+CLCC:")) { + const char *str; + + if (!g_at_result_iter_next_number(&iter, &id)) + continue; + + if (!g_at_result_iter_next_number(&iter, &dir)) + continue; + + if (!g_at_result_iter_next_number(&iter, &status)) + continue; + + if (!g_at_result_iter_next_number(&iter, &type)) + continue; + + if (!g_at_result_iter_skip_next(&iter)) + continue; + + if (!g_at_result_iter_next_string(&iter, &str)) + continue; + + if (!g_at_result_iter_next_number(&iter, &number_type)) + continue; + + call = g_try_new0(struct ofono_call, 1); + + if (!call) + break; + + call->id = id; + call->direction = dir; + call->status = status; + call->type = type; + strncpy(call->phone_number, str, OFONO_MAX_PHONE_NUMBER_LENGTH); + call->number_type = number_type; + + if (strlen(call->phone_number) > 0) + call->clip_validity = 0; + else + call->clip_validity = 2; + + l = g_slist_insert_sorted(l, call, call_compare); + } + + return l; +} + +static void clcc_poll_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + GSList *calls; + GSList *n, *o; + struct ofono_call *nc, *oc; + gboolean poll_again = FALSE; + + dump_response("clcc_poll_cb", ok, result); + + if (!ok) { + ofono_error("We are polling CLCC and CLCC resulted in an error"); + ofono_error("All bets are off for call management"); + return; + } + + calls = parse_clcc(result); + + n = calls; + o = at->voicecall->calls; + + while (n || o) { + nc = n ? n->data : NULL; + oc = o ? o->data : NULL; + + if (nc && nc->status >= 2 && nc->status <= 5) + poll_again = TRUE; + + if (oc && (!nc || (nc->id > oc->id))) { + enum ofono_disconnect_reason reason; + + if (at->voicecall->local_release & (0x1 << oc->id)) + reason = OFONO_DISCONNECT_REASON_LOCAL_HANGUP; + else + reason = OFONO_DISCONNECT_REASON_REMOTE_HANGUP; + + if (!oc->type) + ofono_voicecall_disconnected(modem, oc->id, + reason, NULL); + + release_id(at->voicecall, oc->id); + + o = o->next; + } else if (nc && (!oc || (nc->id < oc->id))) { + /* new call, signal it */ + if (nc->type == 0) + ofono_voicecall_notify(modem, nc); + + n = n->next; + } else { + if (memcmp(nc, oc, sizeof(struct ofono_call)) && !nc->type) + ofono_voicecall_notify(modem, nc); + + n = n->next; + o = o->next; + } + } + + g_slist_foreach(at->voicecall->calls, (GFunc) g_free, NULL); + g_slist_free(at->voicecall->calls); + + at->voicecall->calls = calls; + + at->voicecall->local_release = 0; + + if (poll_again && at->voicecall->poll_clcc && + !at->voicecall->clcc_source) + at->voicecall->clcc_source = g_timeout_add(POLL_CLCC_INTERVAL, + poll_clcc, + modem); +} + +static gboolean poll_clcc(gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + + g_at_chat_send(at->parser, "AT+CLCC", clcc_prefix, + clcc_poll_cb, modem, NULL); + + at->voicecall->clcc_source = 0; + + return FALSE; +} + +static void generic_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct at_data *at = ofono_modem_userdata(cbd->modem); + ofono_generic_cb_t cb = cbd->cb; + unsigned int released_status = GPOINTER_TO_UINT(cbd->user); + struct ofono_error error; + + dump_response("generic_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (ok && released_status) { + GSList *l; + struct ofono_call *call; + + for (l = at->voicecall->calls; l; l = l->next) { + call = l->data; + + if (released_status & (0x1 << call->status)) + at->voicecall->local_release |= + (0x1 << call->id); + } + } + + if (at->voicecall->poll_clcc) + g_at_chat_send(at->parser, "AT+CLCC", clcc_prefix, + clcc_poll_cb, cbd->modem, NULL); + + /* We have to callback after we schedule a poll if required */ + cb(&error, cbd->data); +} + +static void release_id_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct at_data *at = ofono_modem_userdata(cbd->modem); + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("release_id_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (ok) + at->voicecall->local_release = GPOINTER_TO_UINT(cbd->user); + + if (at->voicecall->poll_clcc) + g_at_chat_send(at->parser, "AT+CLCC", clcc_prefix, + clcc_poll_cb, cbd->modem, NULL); + + /* We have to callback after we schedule a poll if required */ + cb(&error, cbd->data); +} +static void aofono_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct at_data *at = ofono_modem_userdata(cbd->modem); + ofono_generic_cb_t cb = cbd->cb; + GAtResultIter iter; + const char *num; + int type = 128; + int validity = 2; + struct ofono_error error; + struct ofono_call *call; + + dump_response("aofono_cb", ok, result); + + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) + goto out; + + g_at_result_iter_init(&iter, result); + + if (g_at_result_iter_next(&iter, "+COLP:")) { + g_at_result_iter_next_string(&iter, &num); + g_at_result_iter_next_number(&iter, &type); + + if (strlen(num) > 0) + validity = 0; + else + validity = 2; + + ofono_debug("colp_notify: %s %d %d", num, type, validity); + } + + /* Generate a voice call that was just dialed, we guess the ID */ + call = create_call(at->voicecall, 0, 0, 2, num, type, validity); + + if (!call) { + ofono_error("Unable to allocate call, call tracking will fail!"); + return; + } + + /* Telephonyd will generate a call with the dialed number + * inside its dial callback. Unless we got COLP information + * we do not need to communicate that a call is being + * dialed + */ + if (validity != 2) + ofono_voicecall_notify(cbd->modem, call); + + if (at->voicecall->poll_clcc && !at->voicecall->clcc_source) + at->voicecall->clcc_source = g_timeout_add(POLL_CLCC_INTERVAL, + poll_clcc, + cbd->modem); + +out: + cb(&error, cbd->data); +} + +static void at_dial(struct ofono_modem *modem, const char *number, int number_type, + enum ofono_clir_option clir, enum ofono_cug_option cug, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[256]; + + if (!cbd) + goto error; + + sprintf(buf, "ATD%s", number); + + switch (clir) { + case OFONO_CLIR_OPTION_INVOCATION: + strcat(buf, "I"); + break; + case OFONO_CLIR_OPTION_SUPPRESSION: + strcat(buf, "i"); + break; + default: + break; + } + + switch (cug) { + case OFONO_CUG_OPTION_INVOCATION: + strcat(buf, "G"); + break; + default: + break; + } + + strcat(buf, ";"); + + if (g_at_chat_send(at->parser, buf, aofono_prefix, + aofono_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void at_template(const char *cmd, struct ofono_modem *modem, + GAtResultFunc result_cb, unsigned int released_status, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + cbd->user = GUINT_TO_POINTER(released_status); + + if (g_at_chat_send(at->parser, cmd, none_prefix, + result_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void at_answer(struct ofono_modem *modem, ofono_generic_cb_t cb, void *data) +{ + at_template("ATA", modem, generic_cb, 0, cb, data); +} + +static void at_hangup(struct ofono_modem *modem, ofono_generic_cb_t cb, void *data) +{ + /* Hangup all calls */ + at_template("AT+CHUP", modem, generic_cb, 0x3f, cb, data); +} + +static void clcc_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_list_cb_t cb = cbd->cb; + struct ofono_error error; + GSList *calls = NULL; + GSList *l; + struct ofono_call *list; + int num; + + dump_response("clcc_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + + if (!ok) { + cb(&error, 0, NULL, cbd->data); + goto out; + } + + calls = parse_clcc(result); + + if (calls == NULL) { + DECLARE_FAILURE(e); + cb(&e, 0, NULL, cbd->data); + goto out; + } + + list = g_try_new0(struct ofono_call, g_slist_length(calls)); + + if (!list) { + DECLARE_FAILURE(e); + cb(&e, 0, NULL, cbd->data); + goto out; + } + + for (num = 0, l = calls; l; l = l->next, num++) + memcpy(&list[num], l->data, sizeof(struct ofono_call)); + + cb(&error, num, list, cbd->data); + + g_free(list); + +out: + g_slist_foreach(calls, (GFunc) g_free, NULL); + g_slist_free(calls); +} + +static void at_list_calls(struct ofono_modem *modem, ofono_call_list_cb_t cb, + void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + + if (!cbd) + goto error; + + if (g_at_chat_send(at->parser, "AT+CLCC", clcc_prefix, + clcc_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, 0, NULL, data); + } + +} + +static void at_hold_all_active(struct ofono_modem *modem, ofono_generic_cb_t cb, + void *data) +{ + at_template("AT+CHLD=2", modem, generic_cb, 0, cb, data); +} + +static void at_release_all_held(struct ofono_modem *modem, ofono_generic_cb_t cb, + void *data) +{ + unsigned int held_status = 0x1 << 1; + at_template("AT+CHLD=0", modem, generic_cb, held_status, cb, data); +} + +static void at_set_udub(struct ofono_modem *modem, ofono_generic_cb_t cb, void *data) +{ + unsigned int incoming_or_waiting = (0x1 << 4) | (0x1 << 5); + at_template("AT+CHLD=0", modem, generic_cb, incoming_or_waiting, + cb, data); +} + +static void at_release_all_active(struct ofono_modem *modem, ofono_generic_cb_t cb, + void *data) +{ + at_template("AT+CHLD=1", modem, generic_cb, 0x1, cb, data); +} + +static void at_release_specific(struct ofono_modem *modem, int id, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + char buf[32]; + + if (!cbd) + goto error; + + sprintf(buf, "AT+CHLD=1%d", id); + cbd->user = GINT_TO_POINTER(id); + + if (g_at_chat_send(at->parser, buf, none_prefix, + release_id_cb, cbd, g_free) > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void at_private_chat(struct ofono_modem *modem, int id, + ofono_generic_cb_t cb, void *data) +{ + char buf[32]; + + sprintf(buf, "AT+CHLD=2%d", id); + at_template(buf, modem, generic_cb, 0, cb, data); +} + +static void at_create_multiparty(struct ofono_modem *modem, ofono_generic_cb_t cb, + void *data) +{ + at_template("AT+CHLD=3", modem, generic_cb, 0, cb, data); +} + +static void at_transfer(struct ofono_modem *modem, ofono_generic_cb_t cb, + void *data) +{ + /* Held & Active */ + unsigned int transfer = 0x1 | 0x2; + + /* Transfer can puts held & active calls together and disconnects + * from both. However, some networks support transfering of + * dialing/ringing calls as well. + */ + transfer |= 0x4 | 0x8; + + at_template("AT+CHLD=4", modem, generic_cb, transfer, cb, data); +} + +static void at_deflect(struct ofono_modem *modem, const char *number, + int number_type, ofono_generic_cb_t cb, void *data) +{ + char buf[128]; + unsigned int incoming_or_waiting = (0x1 << 4) | (0x1 << 5); + + sprintf(buf, "AT+CTFR=%s,%d", number, number_type); + at_template(buf, modem, generic_cb, incoming_or_waiting, cb, data); +} + +static void vts_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_generic_cb_t cb = cbd->cb; + struct ofono_error error; + + dump_response("vts_cb", ok, result); + decode_at_error(&error, g_at_result_final_response(result)); + cb(&error, cbd->data); +} + +static void at_send_dtmf(struct ofono_modem *modem, const char *dtmf, + ofono_generic_cb_t cb, void *data) +{ + struct at_data *at = ofono_modem_userdata(modem); + struct cb_data *cbd = cb_data_new(modem, cb, data); + int len = strlen(dtmf); + int s; + int i; + char *buf; + + if (!cbd) + goto error; + + /* strlen("+VTS=\"T\";") = 9 + initial AT + null */ + buf = g_try_new(char, len * 9 + 3); + + if (!buf) + goto error; + + s = sprintf(buf, "AT+VTS=\"%c\"", dtmf[0]); + + for (i = 1; i < len; i++) + s += sprintf(buf + s, ";+VTS=\"%c\"", dtmf[i]); + + s = g_at_chat_send(at->parser, buf, none_prefix, + vts_cb, cbd, g_free); + + g_free(buf); + + if (s > 0) + return; + +error: + if (cbd) + g_free(cbd); + + { + DECLARE_FAILURE(error); + cb(&error, data); + } +} + +static void ring_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + struct ofono_call *call; + + dump_response("ring_notify", TRUE, result); + + /* RING can repeat, ignore if we already have an incoming call */ + if (g_slist_find_custom(at->voicecall->calls, (gconstpointer)4, + call_compare_by_status)) + return; + + /* Generate an incoming call of unknown type */ + call = create_call(at->voicecall, 9, 1, 4, NULL, 128, 2); + + if (!call) { + ofono_error("Couldn't create call, call management is fubar!"); + return; + } + + /* We don't know the call type, we must run clcc */ + at->voicecall->clcc_source = g_timeout_add(CLIP_INTERVAL, + poll_clcc, modem); +} + +static void cring_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + GAtResultIter iter; + const char *line; + int type; + struct ofono_call *call; + //GSList *l; + + dump_response("cring_notify", TRUE, result); + + /* CRING can repeat, ignore if we already have an incoming call */ + if (g_slist_find_custom(at->voicecall->calls, (gconstpointer)4, + call_compare_by_status)) + return; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CRING:")) + return; + + line = g_at_result_iter_raw_line(&iter); + + if (line == NULL) + return; + + /* Ignore everything that is not voice for now */ + if (!strcasecmp(line, "VOICE")) + type = 0; + else + type = 9; + + /* Generate an incoming call */ + call = create_call(at->voicecall, type, 1, 4, NULL, 128, 2); + + /* We have a call, and call type but don't know the number and + * must wait for the CLIP to arrive before announcing the call. + * So we wait, and schedule the clcc call. If the CLIP arrives + * earlier, we announce the call there + */ + at->voicecall->clcc_source = + g_timeout_add(CLIP_INTERVAL, poll_clcc, modem); + + ofono_debug("cring_notify"); +} + +static void clip_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + GAtResultIter iter; + const char *num; + int type, validity; + GSList *l; + struct ofono_call *call; + + dump_response("clip_notify", TRUE, result); + + l = g_slist_find_custom(at->voicecall->calls, (gconstpointer)4, + call_compare_by_status); + + if (l == NULL) { + ofono_error("CLIP for unknown call"); + return; + } + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CLIP:")) + return; + + if (!g_at_result_iter_next_string(&iter, &num)) + return; + + if (!g_at_result_iter_next_number(&iter, &type)) + return; + + if (strlen(num) > 0) + validity = 0; + else + validity = 2; + + /* Skip subaddr, satype and alpha */ + g_at_result_iter_skip_next(&iter); + g_at_result_iter_skip_next(&iter); + g_at_result_iter_skip_next(&iter); + + /* If we have CLI validity field, override our guessed value */ + g_at_result_iter_next_number(&iter, &validity); + + ofono_debug("clip_notify: %s %d %d", num, type, validity); + + call = l->data; + + strncpy(call->phone_number, num, OFONO_MAX_PHONE_NUMBER_LENGTH); + call->phone_number[OFONO_MAX_PHONE_NUMBER_LENGTH] = '\0'; + call->number_type = type; + call->clip_validity = validity; + + if (call->type == 0) + ofono_voicecall_notify(modem, call); + + /* We started a CLCC, but the CLIP arrived and the call type + * is known. If we don't need to poll, cancel the GSource + */ + if (call->type != 9 && !at->voicecall->poll_clcc && + at->voicecall->clcc_source && + g_source_remove(at->voicecall->clcc_source)) + at->voicecall->clcc_source = 0; +} + +static void ccwa_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + GAtResultIter iter; + const char *num; + int num_type, validity, cls; + struct ofono_call *call; + + dump_response("ccwa_notify", TRUE, result); + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "+CCWA:")) + return; + + if (!g_at_result_iter_next_string(&iter, &num)) + return; + + if (!g_at_result_iter_next_number(&iter, &num_type)) + return; + + if (!g_at_result_iter_next_number(&iter, &cls)) + return; + + /* Skip alpha field */ + g_at_result_iter_skip_next(&iter); + + if (strlen(num) > 0) + validity = 0; + else + validity = 2; + + /* If we have CLI validity field, override our guessed value */ + g_at_result_iter_next_number(&iter, &validity); + + ofono_debug("ccwa_notify: %s %d %d %d", num, num_type, cls, validity); + + call = create_call(at->voicecall, class_to_call_type(cls), 1, 5, + num, num_type, validity); + + if (!call) { + ofono_error("malloc call structfailed. Call management is fubar"); + return; + } + + if (call->type == 0) /* Only notify voice calls */ + ofono_voicecall_notify(modem, call); + + if (at->voicecall->poll_clcc && !at->voicecall->clcc_source) + at->voicecall->clcc_source = g_timeout_add(POLL_CLCC_INTERVAL, + poll_clcc, + modem); +} + +static void no_carrier_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + + if (at->voicecall->poll_clcc) + g_at_chat_send(at->parser, "AT+CLCC", clcc_prefix, + clcc_poll_cb, modem, NULL); +} + +static void no_answer_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + + if (at->voicecall->poll_clcc) + g_at_chat_send(at->parser, "AT+CLCC", clcc_prefix, + clcc_poll_cb, modem, NULL); +} + +static void busy_notify(GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + + /* Call was rejected, most likely due to network congestion + * or UDUB on the other side + * TODO: Handle UDUB or other conditions somehow + */ + if (at->voicecall->poll_clcc) + g_at_chat_send(at->parser, "AT+CLCC", clcc_prefix, + clcc_poll_cb, modem, NULL); +} + +static struct ofono_voicecall_ops ops = { + .dial = at_dial, + .answer = at_answer, + .hangup = at_hangup, + .list_calls = at_list_calls, + .hold_all_active = at_hold_all_active, + .release_all_held = at_release_all_held, + .set_udub = at_set_udub, + .release_all_active = at_release_all_active, + .release_specific = at_release_specific, + .private_chat = at_private_chat, + .create_multiparty = at_create_multiparty, + .transfer = at_transfer, + .deflect = at_deflect, + .swap_without_accept = NULL, + .send_tones = at_send_dtmf +}; + +static void at_voicecall_initialized(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_modem *modem = user_data; + struct at_data *at = ofono_modem_userdata(modem); + + ofono_debug("voicecall_init: registering to notifications"); + + g_at_chat_register(at->parser, "RING", + ring_notify, FALSE, modem, NULL); + g_at_chat_register(at->parser, "+CRING:", + cring_notify, FALSE, modem, NULL); + g_at_chat_register(at->parser, "+CLIP:", + clip_notify, FALSE, modem, NULL); + g_at_chat_register(at->parser, "+CCWA:", + ccwa_notify, FALSE, modem, NULL); + + /* Modems with 'better' call progress indicators should + * probably not even bother registering to these + */ + g_at_chat_register(at->parser, "NO CARRIER", + no_carrier_notify, FALSE, modem, NULL); + g_at_chat_register(at->parser, "NO ANSWER", + no_answer_notify, FALSE, modem, NULL); + g_at_chat_register(at->parser, "BUSY", + busy_notify, FALSE, modem, NULL); + + ofono_voicecall_register(modem, &ops); +} + +void at_voicecall_init(struct ofono_modem *modem) +{ + struct at_data *at = ofono_modem_userdata(modem); + + at->voicecall = g_try_new0(struct voicecall_data, 1); + + if (!at->voicecall) + return; + + at->voicecall->poll_clcc = TRUE; + + ofono_debug("Sending voice initialization commands"); + + g_at_chat_send(at->parser, "AT+CRC=1", NULL, NULL, NULL, NULL); + g_at_chat_send(at->parser, "AT+CLIP=1", NULL, NULL, NULL, NULL); + g_at_chat_send(at->parser, "AT+COLP=1", NULL, NULL, NULL, NULL); + g_at_chat_send(at->parser, "AT+CCWA=1", NULL, + at_voicecall_initialized, modem, NULL); +} + +void at_voicecall_exit(struct ofono_modem *modem) +{ + struct at_data *at = ofono_modem_userdata(modem); + + g_free(at->voicecall); + at->voicecall = NULL; + + ofono_voicecall_unregister(modem); +}