summaryrefslogtreecommitdiffstats
path: root/drivers/atmodem/data-connection.c
diff options
context:
space:
mode:
authorAndrzej Zaborowski <andrew.zaborowski@intel.com>2009-10-12 23:35:59 +0200
committerDenis Kenzior <denkenz@gmail.com>2009-10-23 17:05:53 -0500
commitf43efa8a5f24f2aab71f829082cc02a272268986 (patch)
tree3947897237b13880d172e4aaa116cc5792d4b115 /drivers/atmodem/data-connection.c
parent0faa8b3c42533578b5b6ed4d3de0cb25ba24cb1d (diff)
downloadofono-f43efa8a5f24f2aab71f829082cc02a272268986.tar.bz2
Add GPRS support
This commit implements the GPRS context setup and teardown according to doc/dataconnectionmanager-api.txt One issue with the AT implementation of the api is that "Powered" (a read-write property) can be set independently of "Attached" (read-only property) and remain set when "Attached" is clear. The semantics would be that the network doesn't have resources to let the modem attach, but the modem waits for the resources to become available and then attaches. On AT the modem is in this state only when executing +CGATT, so currently the code will rerun +CGATT as soon as the previous one returns with error, probably starving other commands. A possible workaround would be for "Powered" to flip back to False after the modem fails to attach once, or give up on having separate properties. Alternatively we could re-try to attach periodically but on one modem I've tried +CGATT fails after about 1 minute (that's the Calypso) and on another only about 0.5s (Nokia phones with AT emulation). When "Powered" is set and "RoamingAllowed" is clear and we manage to attach and find that we're roaming, ofono resets "Powered". We may want to catch the user trying to dial *99***1# which is the backwards compatibility quirk for old modems (same way ofono parses USSD strings).
Diffstat (limited to 'drivers/atmodem/data-connection.c')
-rw-r--r--drivers/atmodem/data-connection.c649
1 files changed, 649 insertions, 0 deletions
diff --git a/drivers/atmodem/data-connection.c b/drivers/atmodem/data-connection.c
new file mode 100644
index 00000000..47a90e5e
--- /dev/null
+++ b/drivers/atmodem/data-connection.c
@@ -0,0 +1,649 @@
+/*
+ *
+ * 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 <ofono/modem.h>
+#include <ofono/data-connection.h>
+
+#include "gatchat.h"
+#include "gatresult.h"
+
+#include "atmodem.h"
+
+static const char *cgdcont_prefix[] = { "+CGDCONT:", NULL };
+static const char *cgact_prefix[] = { "+CGACT:", NULL };
+static const char *none_prefix[] = { NULL };
+
+struct data_connection_data {
+ GSList *primary_id_range;
+ GSList *contexts;
+ GSList *new_contexts; /* Not yet defined contexts */
+ GAtChat *chat;
+};
+
+struct set_attached_req {
+ struct ofono_data_connection *dc;
+ int attached;
+ ofono_data_connection_cb_t cb;
+ void *data;
+};
+
+struct set_active_req {
+ struct ofono_data_connection *dc;
+ struct ofono_data_context *ctx;
+ int active;
+ ofono_data_connection_cb_t cb;
+ void *data;
+};
+
+static gint context_id_compare(gconstpointer a, gconstpointer b)
+{
+ const struct ofono_data_context *ctxa = a;
+ const gint *id = b;
+
+ return ctxa->id - *id;
+}
+
+static gint context_compare(gconstpointer a, gconstpointer b)
+{
+ const struct ofono_data_context *ctxa = a;
+ const struct ofono_data_context *ctxb = a;
+
+ return ctxa->id - ctxb->id;
+}
+
+static void context_free(struct ofono_data_context *ctx)
+{
+ if (ctx->apn)
+ g_free(ctx->apn);
+
+ if (ctx->username) {
+ memset(ctx->username, 0, strlen(ctx->username));
+ g_free(ctx->username);
+ }
+
+ if (ctx->password) {
+ memset(ctx->password, 0, strlen(ctx->password));
+ g_free(ctx->password);
+ }
+
+ g_free(ctx);
+}
+
+static unsigned int find_next_primary_id(struct data_connection_data *d)
+{
+ GSList *l;
+ gint i, *range;
+
+ for (l = d->primary_id_range; l; l = l->next)
+ for (range = l->data, i = range[0]; i <= range[1]; i++)
+ if (!g_slist_find_custom(d->contexts, &i,
+ context_id_compare))
+ return i;
+
+ return 0;
+}
+
+static void detached(struct ofono_data_connection *dc)
+{
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ GSList *l;
+ struct ofono_data_context *ctx;
+
+ for (l = dcd->contexts; l; l = l->next) {
+ ctx = l->data;
+ if (ctx->active) {
+ ctx->active = 0;
+
+ ofono_data_connection_deactivated(dc, ctx->id);
+ }
+ }
+}
+
+static void at_cgatt_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+ struct set_attached_req *req = user_data;
+ struct ofono_error error;
+
+ dump_response("cgatt_cb", ok, result);
+ decode_at_error(&error, g_at_result_final_response(result));
+
+ if (ok && !req->attached)
+ detached(req->dc);
+
+ req->cb(&error, req->data);
+}
+
+static void at_ps_set_attached(struct ofono_data_connection *dc,
+ int attached, ofono_data_connection_cb_t cb,
+ void *data)
+{
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ struct set_attached_req *req;
+ char buf[64];
+
+ req = g_new0(struct set_attached_req, 1);
+ if (!req)
+ goto error;
+
+ req->dc = dc;
+ req->attached = attached;
+ req->cb = cb;
+ req->data = data;
+
+ sprintf(buf, "AT+CGATT=%i", attached ? 1 : 0);
+
+ if (g_at_chat_send(dcd->chat, buf, none_prefix,
+ at_cgatt_cb, req, g_free) > 0)
+ return;
+
+error:
+ if (req)
+ g_free(req);
+
+ CALLBACK_WITH_FAILURE(cb, data);
+}
+
+static void at_cgact_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+ struct set_active_req *req = user_data;
+ struct data_connection_data *dcd =
+ ofono_data_connection_get_data(req->dc);
+ struct ofono_error error;
+ GSList *l;
+ struct ofono_data_context *ctx;
+
+ dump_response("cgact_cb", ok, result);
+ decode_at_error(&error, g_at_result_final_response(result));
+
+ if (ok) {
+ if (req->ctx) {
+ req->ctx->active = req->active;
+
+ if (!req->active)
+ ofono_data_connection_deactivated(req->dc,
+ req->ctx->id);
+ } else
+ for (l = dcd->contexts; l; l = l->next) {
+ ctx = l->data;
+
+ if (g_slist_find(dcd->new_contexts, ctx))
+ continue;
+
+ ctx->active = req->active;
+
+ if (!req->active)
+ ofono_data_connection_deactivated(
+ req->dc, ctx->id);
+ }
+ }
+
+ req->cb(&error, req->data);
+}
+
+static void at_cgdcont_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+ struct set_active_req *req = user_data;
+ struct data_connection_data *dcd =
+ ofono_data_connection_get_data(req->dc);
+ struct ofono_error error;
+ char buf[64];
+
+ dump_response("cgdcont_cb", ok, result);
+ decode_at_error(&error, g_at_result_final_response(result));
+
+ if (!ok) {
+ req->cb(&error, req->data);
+
+ g_free(req);
+ return;
+ }
+
+ /* Context is no longer undefined */
+ dcd->new_contexts = g_slist_remove(dcd->new_contexts, req->ctx);
+
+ sprintf(buf, "AT+CGACT=1,%u", req->ctx->id);
+
+ if (g_at_chat_send(dcd->chat, buf, none_prefix,
+ at_cgact_cb, req, g_free) > 0)
+ return;
+
+ CALLBACK_WITH_FAILURE(req->cb, req->data);
+
+ g_free(req);
+}
+
+static void at_pdp_set_active(struct ofono_data_connection *dc, unsigned id,
+ int active, ofono_data_connection_cb_t cb,
+ void *data)
+{
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ struct set_active_req *req = NULL;
+ char buf[64];
+ struct ofono_data_context *ctx;
+ gint cid = id;
+ int len;
+ GSList *l;
+
+ l = g_slist_find_custom(dcd->contexts, &cid, context_id_compare);
+ if (!l)
+ goto error;
+
+ ctx = l->data;
+
+ req = g_new0(struct set_active_req, 1);
+ if (!req)
+ goto error;
+
+ req->dc = dc;
+ req->ctx = ctx;
+ req->active = active;
+ req->cb = cb;
+ req->data = data;
+
+ if (active) {
+ len = sprintf(buf, "AT+CGDCONT=%u,\"IP\"", id);
+ if (ctx->apn)
+ snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"",
+ ctx->apn);
+
+ if (g_at_chat_send(dcd->chat, buf, none_prefix,
+ at_cgdcont_cb, req, NULL) > 0)
+ return;
+ } else {
+ sprintf(buf, "AT+CGACT=0,%u", id);
+
+ if (g_at_chat_send(dcd->chat, buf, none_prefix,
+ at_cgact_cb, req, g_free) > 0)
+ return;
+ }
+
+error:
+ if (req)
+ g_free(req);
+
+ CALLBACK_WITH_FAILURE(cb, data);
+}
+
+static void at_pdp_set_active_all(struct ofono_data_connection *dc,
+ int active, ofono_data_connection_cb_t cb,
+ void *data)
+{
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ struct set_active_req *req;
+ char buf[64];
+
+ req = g_new0(struct set_active_req, 1);
+ if (!req)
+ goto error;
+
+ req->dc = dc;
+ req->active = active;
+ req->cb = cb;
+ req->data = data;
+
+ sprintf(buf, "AT+CGACT=%i", active ? 1 : 0);
+
+ if (g_at_chat_send(dcd->chat, buf, none_prefix,
+ at_cgact_cb, req, g_free) > 0)
+ return;
+
+error:
+ if (req)
+ g_free(req);
+
+ CALLBACK_WITH_FAILURE(cb, data);
+}
+
+static void at_pdp_alloc(struct ofono_data_connection *dc,
+ ofono_data_connection_alloc_cb_t cb,
+ void *data)
+{
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ struct ofono_data_context *ctx;
+ struct ofono_error e;
+ unsigned id = find_next_primary_id(dcd);
+
+ if (!id) {
+ CALLBACK_WITH_FAILURE(cb, NULL, data);
+
+ return;
+ }
+
+ ctx = g_try_new0(struct ofono_data_context, 1);
+ if (!ctx) {
+ CALLBACK_WITH_FAILURE(cb, NULL, data);
+
+ return;
+ }
+
+ ctx->id = id;
+ ctx->apn = g_strdup("");
+ ctx->username = g_strdup("");
+ ctx->password = g_strdup("");
+
+ dcd->new_contexts = g_slist_insert_sorted(dcd->new_contexts,
+ ctx, context_compare);
+ dcd->contexts = g_slist_insert_sorted(dcd->contexts,
+ ctx, context_compare);
+
+ /* The context will be defined (+CGDCONT) lazily, once it's needed
+ * and the parameters are already set in ctx. Right now just call
+ * back */
+ e.type = OFONO_ERROR_TYPE_NO_ERROR;
+ e.error = 0;
+ cb(&e, ctx, data);
+
+ ofono_data_connection_notify(dc, ctx);
+}
+
+static void at_pdp_undefine_cb(gboolean ok, GAtResult *result,
+ gpointer user_data)
+{
+ dump_response("undefine_cb", ok, result);
+
+ if (!ok)
+ ofono_error("Undefining primary context failed");
+}
+
+static void at_pdp_free(struct ofono_data_connection *dc, unsigned id,
+ ofono_data_connection_cb_t cb, void *data)
+{
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ struct ofono_error e;
+ char buf[64];
+ struct ofono_data_context *ctx;
+ GSList *l;
+ gint cid = id;
+
+ l = g_slist_find_custom(dcd->contexts, &cid, context_id_compare);
+ if (!l) {
+ CALLBACK_WITH_FAILURE(cb, data);
+
+ return;
+ }
+
+ ctx = l->data;
+ if (ctx->active) {
+ CALLBACK_WITH_FAILURE(cb, data);
+
+ return;
+ }
+
+ /* We can call back already -- even if the request to undefine
+ * the context fails, the ID can be re-used. */
+ e.type = OFONO_ERROR_TYPE_NO_ERROR;
+ e.error = 0;
+ cb(&e, data);
+
+ context_free(ctx);
+ dcd->contexts = g_slist_remove(dcd->contexts, ctx);
+
+ if (g_slist_find(dcd->new_contexts, ctx)) {
+ dcd->new_contexts = g_slist_remove(dcd->new_contexts, ctx);
+ return;
+ }
+
+ sprintf(buf, "AT+CGDCONT=%u", id);
+
+ g_at_chat_send(dcd->chat, buf, none_prefix,
+ at_pdp_undefine_cb, NULL, NULL);
+}
+
+static void at_cgact_read_cb(gboolean ok, GAtResult *result,
+ gpointer user_data)
+{
+ struct ofono_data_connection *dc = user_data;
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ gint cid, state;
+ GAtResultIter iter;
+ struct ofono_data_context *ctx;
+ GSList *l;
+
+ dump_response("cgact_read_cb", ok, result);
+
+ if (!ok)
+ return;
+
+ while (g_at_result_iter_next(&iter, "+CGACT:")) {
+ if (!g_at_result_iter_next_number(&iter, &cid))
+ continue;
+
+ if (!g_at_result_iter_next_number(&iter, &state))
+ continue;
+
+ l = g_slist_find_custom(dcd->contexts, &cid,
+ context_id_compare);
+ if (!l)
+ continue;
+
+ ctx = l->data;
+ if (ctx->active != state) {
+ ctx->active = state;
+
+ if (state)
+ continue;
+
+ ofono_data_connection_deactivated(dc, ctx->id);
+ }
+ }
+}
+
+static void cgev_notify(GAtResult *result, gpointer user_data)
+{
+ struct ofono_data_connection *dc = user_data;
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ GAtResultIter iter;
+ const char *event;
+
+ if (!g_at_result_iter_next(&iter, "+CGEV:"))
+ return;
+
+ if (!g_at_result_iter_next_unquoted_string(&iter, &event))
+ return;
+
+ if (g_str_has_prefix(event, "REJECT "))
+ return;
+
+ if (g_str_has_prefix(event, "NW REACT ") ||
+ g_str_has_prefix(event, "NW DEACT ") ||
+ g_str_has_prefix(event, "ME DEACT ")) {
+ /* Ask what primary contexts are active now */
+ g_at_chat_send(dcd->chat, "AT+CGACT?", cgact_prefix,
+ at_cgact_read_cb, dc, NULL);
+
+ return;
+ }
+
+ if (g_str_has_prefix(event, "NW DETACH ") ||
+ g_str_has_prefix(event, "ME DETACH ")) {
+ detached(dc);
+
+ ofono_data_connection_detached(dc);
+
+ return;
+ }
+
+ if (g_str_has_prefix(event, "NW CLASS ") ||
+ g_str_has_prefix(event, "ME CLASS "))
+ return;
+}
+
+static void cgreg_notify(GAtResult *result, gpointer user_data)
+{
+ struct ofono_data_connection *dc = user_data;
+ GAtResultIter iter;
+ gint status, tech = -1;
+ int lac = -1, ci = -1;
+ const char *str;
+
+ dump_response("cgreg_notify", TRUE, result);
+
+ g_at_result_iter_init(&iter, result);
+
+ if (!g_at_result_iter_next(&iter, "+CGREG:"))
+ return;
+
+ g_at_result_iter_next_number(&iter, &status);
+
+ if (g_at_result_iter_next_string(&iter, &str))
+ lac = strtol(str, NULL, 16);
+ else
+ goto out;
+
+ if (g_at_result_iter_next_string(&iter, &str))
+ ci = strtol(str, NULL, 16);
+ else
+ goto out;
+
+ g_at_result_iter_next_number(&iter, &tech);
+
+out:
+ ofono_debug("cgreg_notify: %d, %d, %d, %d", status, lac, ci, tech);
+
+ if (status != 1 && status != 5)
+ detached(dc);
+
+ ofono_data_netreg_status_notify(dc, status, lac, ci, tech);
+}
+
+static void at_cgdcont_test_cb(gboolean ok, GAtResult *result,
+ gpointer user_data)
+{
+ struct ofono_data_connection *dc = user_data;
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+ GAtResultIter iter;
+ gint range[2];
+ GSList *ranges = NULL;
+ const char *pdp_type;
+
+ if (!ok)
+ goto error;
+
+ g_at_result_iter_init(&iter, result);
+
+ while (g_at_result_iter_next(&iter, "+CGDCONT:")) {
+ if (!g_at_result_iter_open_list(&iter))
+ goto next;
+
+ while (g_at_result_iter_next_range(&iter, &range[0],
+ &range[1]))
+ ranges = g_slist_prepend(ranges,
+ g_memdup(range, sizeof(range)));
+
+ if (!g_at_result_iter_close_list(&iter))
+ goto next;
+
+ if (!ranges || range[1] < range[0])
+ goto next;
+
+ if (!g_at_result_iter_next_string(&iter, &pdp_type))
+ goto next;
+
+ /* We look for IP PDPs */
+ if (!strcmp(pdp_type, "IP"))
+ break;
+
+next:
+ if (ranges) {
+ g_slist_foreach(ranges, (GFunc) g_free, NULL);
+ g_slist_free(ranges);
+ ranges = NULL;
+ }
+ }
+ if (!ranges)
+ goto error;
+
+ dcd->primary_id_range = g_slist_reverse(ranges);
+
+ ofono_debug("data_connection_init: registering to notifications");
+
+ g_at_chat_register(dcd->chat, "+CGEV:", cgev_notify, FALSE, dc, NULL);
+ g_at_chat_register(dcd->chat, "+CGREG:", cgreg_notify, FALSE, dc, NULL);
+
+ ofono_data_connection_register(dc);
+
+ return;
+
+error:
+ ofono_data_connection_remove(dc);
+}
+
+static int at_data_connection_probe(struct ofono_data_connection *dc,
+ unsigned int vendor, void *data)
+{
+ GAtChat *chat = data;
+ struct data_connection_data *dcd;
+
+ dcd = g_new0(struct data_connection_data, 1);
+ dcd->chat = chat;
+
+ ofono_data_connection_set_data(dc, dcd);
+
+ g_at_chat_send(chat, "AT+CGREG=2", NULL, NULL, NULL, NULL);
+ g_at_chat_send(chat, "AT+CGAUTO=0", NULL, NULL, NULL, NULL);
+ g_at_chat_send(chat, "AT+CGEREP=2,1", NULL, NULL, NULL, NULL);
+ g_at_chat_send(chat, "AT+CGDCONT=?", cgdcont_prefix,
+ at_cgdcont_test_cb, dc, NULL);
+ return 0;
+}
+
+static void at_data_connection_remove(struct ofono_data_connection *dc)
+{
+ struct data_connection_data *dcd = ofono_data_connection_get_data(dc);
+
+ g_slist_foreach(dcd->contexts, (GFunc) context_free, NULL);
+ g_slist_free(dcd->contexts);
+ g_slist_free(dcd->new_contexts);
+ g_free(dcd);
+}
+
+static struct ofono_data_connection_driver driver = {
+ .name = "atmodem",
+ .probe = at_data_connection_probe,
+ .remove = at_data_connection_remove,
+ .set_attached = at_ps_set_attached,
+ .set_active = at_pdp_set_active,
+ .set_active_all = at_pdp_set_active_all,
+ .create_context = at_pdp_alloc,
+ .remove_context = at_pdp_free,
+};
+
+void at_data_connection_init()
+{
+ ofono_data_connection_driver_register(&driver);
+}
+
+void at_data_connection_exit()
+{
+ ofono_data_connection_driver_unregister(&driver);
+}