summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenis Kenzior <denis.kenzior@intel.com>2009-05-10 23:39:27 -0700
committerMarcel Holtmann <marcel.holtmann@intel.com>2009-05-10 23:40:24 -0700
commit6d486b7fe19945f4c01ccf98b40e83345cd78a2d (patch)
tree6b651083185bd7be2b29339f890b7b25cfee1cc4
parent4f545152099a4e2092dae91270278ff4aa51d9c5 (diff)
downloadofono-6d486b7fe19945f4c01ccf98b40e83345cd78a2d.tar.bz2
Add initial implementation of AT modem driver
-rw-r--r--drivers/Makefile.am12
-rw-r--r--drivers/atmodem/at.h89
-rw-r--r--drivers/atmodem/call-forwarding.c247
-rw-r--r--drivers/atmodem/call-meter.c387
-rw-r--r--drivers/atmodem/call-settings.c271
-rw-r--r--drivers/atmodem/call-waiting.c180
-rw-r--r--drivers/atmodem/main.c477
-rw-r--r--drivers/atmodem/network-registration.c675
-rw-r--r--drivers/atmodem/session.c256
-rw-r--r--drivers/atmodem/session.h28
-rw-r--r--drivers/atmodem/ussd.c151
-rw-r--r--drivers/atmodem/voicecall.c1048
12 files changed, 3815 insertions, 6 deletions
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 <config.h>
+#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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#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, &currency);
+ 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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#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 <config.h>
#endif
-#define OFONO_API_SUBJECT_TO_CHANGE
+#include <string.h>
+#include <glib.h>
+#include <gdbus.h>
+#include <gatchat.h>
+
#include <ofono/plugin.h>
#include <ofono/log.h>
+#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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#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 <n> 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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+
+#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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#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);
+}