diff --git a/Makefile.am b/Makefile.am index 2be469a5..eb2de7d9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,7 +10,8 @@ include_HEADERS = include/log.h include/plugin.h include/history.h \ include/phonebook.h include/ssn.h include/ussd.h \ include/sms.h include/sim.h include/message-waiting.h \ include/netreg.h include/voicecall.h include/devinfo.h \ - include/cbs.h include/call-volume.h + include/cbs.h include/call-volume.h \ + include/data-connection.h nodist_include_HEADERS = include/version.h @@ -111,7 +112,8 @@ builtin_sources += $(gatchat_sources) \ drivers/atmodem/call-volume.c \ drivers/atmodem/vendor.h \ drivers/atmodem/atutil.h \ - drivers/atmodem/atutil.c + drivers/atmodem/atutil.c \ + drivers/atmodem/data-connection.c builtin_modules += calypsomodem builtin_sources += drivers/atmodem/atutil.h \ @@ -176,7 +178,8 @@ src_ofonod_SOURCES = $(gdbus_sources) $(builtin_sources) \ src/ssn.c src/call-barring.c src/sim.c \ src/phonebook.c src/history.c src/message-waiting.c \ src/simutil.h src/simutil.c src/storage.h \ - src/storage.c src/cbs.c src/watch.c src/call-volume.c + src/storage.c src/cbs.c src/watch.c src/call-volume.c \ + src/data-connection.c src_ofonod_LDADD = $(builtin_libadd) \ @GLIB_LIBS@ @GTHREAD_LIBS@ @DBUS_LIBS@ -ldl @@ -208,7 +211,8 @@ doc_files = doc/overview.txt doc/ofono-paper.txt \ doc/manager-api.txt doc/modem-api.txt doc/network-api.txt \ doc/voicecallmanager-api.txt doc/voicecall-api.txt \ doc/call-forwarding-api.txt doc/call-settings-api.txt \ - doc/call-meter-api.txt + doc/call-meter-api.txt \ + doc/dataconnectionmanager-api.txt test_files = test/test-manager test/test-modem test/test-voicecall \ test/test-network-registration test/test-phonebook \ diff --git a/doc/dataconnectionmanager-api.txt b/doc/dataconnectionmanager-api.txt index e2bcf7cd..982005e3 100644 --- a/doc/dataconnectionmanager-api.txt +++ b/doc/dataconnectionmanager-api.txt @@ -42,7 +42,7 @@ Signals PropertyChanged(string property, variant value) Properties array{object} PrimaryContexts [readonly] - List of all primary contexts objects. + List of all primary context objects. boolean Attached [readonly] diff --git a/drivers/atmodem/atmodem.c b/drivers/atmodem/atmodem.c index 7cfcf6a5..026d8e03 100644 --- a/drivers/atmodem/atmodem.c +++ b/drivers/atmodem/atmodem.c @@ -48,6 +48,7 @@ static int atmodem_init(void) at_netreg_init(); at_cbs_init(); at_call_volume_init(); + at_data_connection_init(); return 0; } @@ -68,6 +69,7 @@ static void atmodem_exit(void) at_voicecall_exit(); at_cbs_exit(); at_call_volume_exit(); + at_data_connection_exit(); } OFONO_PLUGIN_DEFINE(atmodem, "AT modem driver", VERSION, diff --git a/drivers/atmodem/atmodem.h b/drivers/atmodem/atmodem.h index 8c610735..46c37806 100644 --- a/drivers/atmodem/atmodem.h +++ b/drivers/atmodem/atmodem.h @@ -62,3 +62,6 @@ extern void at_cbs_exit(); extern void at_call_volume_init(); extern void at_call_volume_exit(); + +extern void at_data_connection_init(); +extern void at_data_connection_exit(); 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include + +#include +#include +#include + +#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); +} diff --git a/include/data-connection.h b/include/data-connection.h new file mode 100644 index 00000000..24b68a84 --- /dev/null +++ b/include/data-connection.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 + * + */ + +#ifndef __OFONO_DATA_CONNECTION_H +#define __OFONO_DATA_CONNECTION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct ofono_data_connection; + +typedef void (*ofono_data_connection_cb_t)(const struct ofono_error *error, + void *data); + +typedef void (*ofono_data_connection_alloc_cb_t)( + const struct ofono_error *error, + struct ofono_data_context *ctx, + void *data); + +struct ofono_data_connection_driver { + const char *name; + int (*probe)(struct ofono_data_connection *dc, unsigned int vendor, + void *data); + void (*remove)(struct ofono_data_connection *dc); + void (*set_attached)(struct ofono_data_connection *dc, + int attached, ofono_data_connection_cb_t cb, + void *data); + void (*set_active)(struct ofono_data_connection *dc, unsigned id, + int active, ofono_data_connection_cb_t cb, + void *data); + void (*set_active_all)(struct ofono_data_connection *dc, + int active, ofono_data_connection_cb_t cb, + void *data); + void (*create_context)(struct ofono_data_connection *dc, + ofono_data_connection_alloc_cb_t cb, + void *data); + void (*remove_context)(struct ofono_data_connection *dc, unsigned id, + ofono_data_connection_cb_t cb, void *data); +}; + +void ofono_data_connection_notify(struct ofono_data_connection *dc, + struct ofono_data_context *ctx); +void ofono_data_connection_deactivated(struct ofono_data_connection *dc, + unsigned id); +void ofono_data_connection_detached(struct ofono_data_connection *dc); +void ofono_data_netreg_status_notify(struct ofono_data_connection *dc, + int status, int lac, int ci, int tech); + +int ofono_data_connection_driver_register( + const struct ofono_data_connection_driver *d); +void ofono_data_connection_driver_unregister( + const struct ofono_data_connection_driver *d); + +struct ofono_data_connection *ofono_data_connection_create( + struct ofono_modem *modem, unsigned int vendor, + const char *driver, void *data); +void ofono_data_connection_register(struct ofono_data_connection *dc); +void ofono_data_connection_remove(struct ofono_data_connection *dc); + +void ofono_data_connection_set_data(struct ofono_data_connection *dc, + void *data); +void *ofono_data_connection_get_data(struct ofono_data_connection *dc); + +#ifdef __cplusplus +} +#endif + +#endif /* __OFONO_DATA_CONNECTION_H */ diff --git a/include/types.h b/include/types.h index 6a9681d0..44428fb1 100644 --- a/include/types.h +++ b/include/types.h @@ -91,6 +91,16 @@ struct ofono_call { int clip_validity; }; +struct ofono_data_context { + unsigned id; + int type; + int direction; + int active; + char *apn; + char *username; + char *password; +}; + #ifdef __cplusplus } #endif diff --git a/plugins/phonesim.c b/plugins/phonesim.c index f1c44e5a..071cbc97 100644 --- a/plugins/phonesim.c +++ b/plugins/phonesim.c @@ -54,6 +54,7 @@ #include #include #include +#include #include @@ -312,6 +313,8 @@ static void phonesim_post_sim(struct ofono_modem *modem) ofono_cbs_create(modem, 0, "atmodem", data->chat); } + ofono_data_connection_create(modem, 0, "atmodem", data->chat); + mw = ofono_message_waiting_create(modem); if (mw) ofono_message_waiting_register(mw); diff --git a/src/data-connection.c b/src/data-connection.c new file mode 100644 index 00000000..eab36c79 --- /dev/null +++ b/src/data-connection.c @@ -0,0 +1,1074 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include + +#include "ofono.h" + +#include "common.h" + +#define DATA_CONNECTION_MANAGER_INTERFACE "org.ofono.DataConnectionManager" +#define DATA_CONTEXT_INTERFACE "org.ofono.PrimaryDataContext" + +#define DATA_CONNECTION_FLAG_ATTACHING 0x1 + +static GSList *g_drivers = NULL; + +struct ofono_data_connection { + GSList *contexts; + int attached; + int roaming_allowed; + int powered; + int status; + int location; + int cellid; + int technology; + + int flags; + struct context *current_context; + DBusMessage *pending; + const struct ofono_data_connection_driver *driver; + void *driver_data; + struct ofono_atom *atom; +}; + +struct context { + struct ofono_data_context *context; + struct ofono_data_connection *dc; +}; + +static void dc_netreg_update(struct ofono_data_connection *dc); + +static gint context_compare(gconstpointer a, gconstpointer b) +{ + const struct context *ctxa = a; + const struct context *ctxb = a; + + return ctxa->context->id - ctxb->context->id; +} + +enum { + DATA_CONTEXT_TYPE_INTERNET, + DATA_CONTEXT_TYPE_MMS, + DATA_CONTEXT_TYPE_WAP, +}; + +static inline const char *data_context_type_to_string(int type) +{ + switch (type) { + case DATA_CONTEXT_TYPE_INTERNET: + return "internet"; + case DATA_CONTEXT_TYPE_MMS: + return "mms"; + case DATA_CONTEXT_TYPE_WAP: + return "wap"; + } + + return NULL; +} + +static const char *dc_build_context_path(struct ofono_data_connection *dc, + const struct ofono_data_context *ctx) +{ + static char path[256]; + + snprintf(path, sizeof(path), "%s/primarycontext%02u", + __ofono_atom_get_path(dc->atom), ctx->id); + + return path; +} + +static struct context *dc_context_by_path( + struct ofono_data_connection *dc, const char *ctx_path) +{ + const char *path = __ofono_atom_get_path(dc->atom); + GSList *l; + unsigned id; + + if (!g_str_has_prefix(ctx_path, path)) + return NULL; + + if (sscanf(ctx_path + strlen(path), "/primarycontext%2u", &id) != 1) + return NULL; + + for (l = dc->contexts; l; l = l->next) { + struct context *ctx = l->data; + + if (ctx->context->id == id) + return ctx; + } + + return NULL; +} + +static DBusMessage *dc_get_context_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct context *ctx = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + dbus_bool_t value; + const char *type = data_context_type_to_string(ctx->context->type); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dict); + + value = ctx->context->active; + ofono_dbus_dict_append(&dict, "Active", DBUS_TYPE_BOOLEAN, &value); + + ofono_dbus_dict_append(&dict, "AccessPointName", + DBUS_TYPE_STRING, &ctx->context->apn); + + ofono_dbus_dict_append(&dict, "Type", + DBUS_TYPE_STRING, &type); + + ofono_dbus_dict_append(&dict, "Username", + DBUS_TYPE_STRING, &ctx->context->username); + + ofono_dbus_dict_append(&dict, "Passwod", + DBUS_TYPE_STRING, &ctx->context->password); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static void context_set_active_callback(const struct ofono_error *error, + void *data) +{ + struct context *ctx = data; + DBusConnection *conn = ofono_dbus_get_connection(); + DBusMessage *reply; + const char *path; + dbus_bool_t value; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + ofono_debug("Activating context failed with error: %s", + telephony_error_to_str(error)); + + reply = __ofono_error_failed(ctx->dc->pending); + goto reply; + } + + reply = dbus_message_new_method_return(ctx->dc->pending); + + if (!ctx->context->active) /* Signal emitted elsewhere */ + goto reply; + + path = dc_build_context_path(ctx->dc, ctx->context); + value = ctx->context->active; + ofono_dbus_signal_property_changed(conn, path, DATA_CONTEXT_INTERFACE, + "Active", DBUS_TYPE_BOOLEAN, + &value); + +reply: + __ofono_dbus_pending_reply(&ctx->dc->pending, reply); +} + +static DBusMessage *dc_set_context_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct context *ctx = data; + DBusMessageIter iter; + DBusMessageIter var; + const char *property; + dbus_bool_t value; + const char *str; + const char *path; + + if (ctx->dc->pending) + return __ofono_error_busy(msg); + + if (!dbus_message_iter_init(msg, &iter)) + return __ofono_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &property); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &var); + + if (!strcmp(property, "Active")) { + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_BOOLEAN) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &value); + + if ((dbus_bool_t) ctx->context->active == value) + return dbus_message_new_method_return(msg); + if (ctx->dc->flags & DATA_CONNECTION_FLAG_ATTACHING) + return __ofono_error_busy(msg); + if (value && !ctx->dc->attached) + return __ofono_error_failed(msg); + if (!ctx->dc->driver->set_active) + return __ofono_error_not_implemented(msg); + + ctx->dc->pending = dbus_message_ref(msg); + + ctx->dc->driver->set_active(ctx->dc, ctx->context->id, + value, + context_set_active_callback, + ctx); + + return NULL; + } + + /* All other properties are read-only when context is active */ + if (ctx->context->active) + return __ofono_error_invalid_args(msg); + + if (!strcmp(property, "AccessPointName")) { + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &str); + + if (ctx->context->apn) + g_free(ctx->context->apn); + ctx->context->apn = g_strdup(str); + } else if (!strcmp(property, "Type")) { + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &str); + + if (!strcmp(str, "internet")) + ctx->context->type = DATA_CONTEXT_TYPE_INTERNET; + else + return __ofono_error_invalid_args(msg); + } else if (!strcmp(property, "Username")) { + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &str); + + if (ctx->context->username) + g_free(ctx->context->username); + ctx->context->username = g_strdup(str); + } else if (!strcmp(property, "Password")) { + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &str); + + if (ctx->context->password) + g_free(ctx->context->password); + ctx->context->password = g_strdup(str); + } else + return __ofono_error_invalid_args(msg); + + path = dc_build_context_path(ctx->dc, ctx->context); + ofono_dbus_signal_property_changed(conn, path, DATA_CONTEXT_INTERFACE, + property, DBUS_TYPE_STRING, + &str); + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable context_methods[] = { + { "GetProperties", "", "a{sv}", dc_get_context_properties }, + { "SetProperty", "sv", "", dc_set_context_property, + G_DBUS_METHOD_FLAG_ASYNC }, + { } +}; + +static GDBusSignalTable context_signals[] = { + { "PropertyChanged", "sv" }, + { } +}; + +static struct context *context_create(struct ofono_data_connection *dc, + struct ofono_data_context *ctx) +{ + struct context *context = g_try_new0(struct context, 1); + + if (!context) + return NULL; + + context->context = ctx; + context->dc = dc; + + return context; +} + +static void context_destroy(gpointer userdata) +{ + struct context *ctx = userdata; + + g_free(ctx); +} + +static gboolean context_dbus_register(struct ofono_data_connection *dc, + struct context *ctx) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path = dc_build_context_path(dc, ctx->context); + + if (!g_dbus_register_interface(conn, path, DATA_CONTEXT_INTERFACE, + context_methods, context_signals, + NULL, ctx, context_destroy)) { + ofono_error("Could not register PrimaryContext %s", path); + context_destroy(ctx); + + return FALSE; + } + + return TRUE; +} + +static gboolean context_dbus_unregister(struct ofono_data_connection *dc, + struct ofono_data_context *ctx) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path = dc_build_context_path(dc, ctx); + + return g_dbus_unregister_interface(conn, path, DATA_CONTEXT_INTERFACE); +} + +static char **dc_contexts_path_list(struct ofono_data_connection *dc, + GSList *context_list) +{ + GSList *l; + char **i; + struct context *ctx; + char **objlist = g_new0(char *, g_slist_length(context_list) + 1); + + if (!objlist) + return NULL; + + for (i = objlist, l = context_list; l; l = l->next) { + ctx = l->data; + *i++ = g_strdup(dc_build_context_path(dc, ctx->context)); + } + + return objlist; +} + +static void dc_generic_callback(const struct ofono_error *error, void *data) +{ + struct ofono_data_connection *dc = data; + DBusMessage *reply; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) + ofono_debug("command failed with error: %s", + telephony_error_to_str(error)); + + if (!dc->pending) + return; + + if (error->type == OFONO_ERROR_TYPE_NO_ERROR) + reply = dbus_message_new_method_return(dc->pending); + else + reply = __ofono_error_failed(dc->pending); + + __ofono_dbus_pending_reply(&dc->pending, reply); +} + +static void dc_attach_callback(const struct ofono_error *error, + void *data) +{ + struct ofono_data_connection *dc = data; + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path; + dbus_bool_t value; + + if (error->type == OFONO_ERROR_TYPE_NO_ERROR && + (dc->flags & DATA_CONNECTION_FLAG_ATTACHING)) { + dc->attached = !dc->attached; + + path = __ofono_atom_get_path(dc->atom); + value = dc->attached; + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "Attached", DBUS_TYPE_BOOLEAN, &value); + } + + dc->flags &= ~DATA_CONNECTION_FLAG_ATTACHING; + + dc_netreg_update(dc); +} + +static void dc_netreg_update(struct ofono_data_connection *dc) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + int attach; + int operator_ok; + const char *path; + dbus_bool_t value = 0; + + operator_ok = dc->roaming_allowed || + (dc->status != NETWORK_REGISTRATION_STATUS_ROAMING); + + attach = dc->powered && operator_ok; + + if (dc->attached != attach && + !(dc->flags & DATA_CONNECTION_FLAG_ATTACHING)) { + dc->flags |= DATA_CONNECTION_FLAG_ATTACHING; + + dc->driver->set_attached(dc, attach, dc_attach_callback, dc); + + /* Prevent further attempts to attach */ + if (!attach && dc->powered) { + dc->powered = 0; + + path = __ofono_atom_get_path(dc->atom); + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "Powered", DBUS_TYPE_BOOLEAN, &value); + } + } +} + +static DBusMessage *dc_get_manager_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ofono_data_connection *dc = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + char **objpath_list; + dbus_bool_t value; + const char *status = registration_status_to_string(dc->status); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dict); + + objpath_list = dc_contexts_path_list(dc, dc->contexts); + if (!objpath_list) + return NULL; + + ofono_dbus_dict_append_array(&dict, "PrimaryContexts", + DBUS_TYPE_OBJECT_PATH, &objpath_list); + + g_strfreev(objpath_list); + + value = dc->attached; + ofono_dbus_dict_append(&dict, "Attached", DBUS_TYPE_BOOLEAN, &value); + + value = dc->roaming_allowed; + ofono_dbus_dict_append(&dict, "RoamingAllowed", + DBUS_TYPE_BOOLEAN, &value); + + value = dc->powered; + ofono_dbus_dict_append(&dict, "Powered", DBUS_TYPE_BOOLEAN, &value); + + ofono_dbus_dict_append(&dict, "Status", DBUS_TYPE_STRING, &status); + + if (dc->location != -1) { + dbus_uint16_t location = dc->location; + ofono_dbus_dict_append(&dict, "LocationAreaCode", + DBUS_TYPE_UINT16, &location); + } + + if (dc->cellid != -1) { + dbus_uint32_t cellid = dc->cellid; + ofono_dbus_dict_append(&dict, "CellId", + DBUS_TYPE_UINT32, &cellid); + } + + if (dc->technology != -1) { + const char *technology = + registration_tech_to_string(dc->technology); + + ofono_dbus_dict_append(&dict, "Technology", DBUS_TYPE_STRING, + &technology); + } + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *dc_set_manager_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ofono_data_connection *dc = data; + DBusMessageIter iter; + DBusMessageIter var; + const char *property; + dbus_bool_t value; + const char *path; + + if (dc->pending) + return __ofono_error_busy(msg); + + if (!dbus_message_iter_init(msg, &iter)) + return __ofono_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &property); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &var); + + if (!strcmp(property, "RoamingAllowed")) { + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_BOOLEAN) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &value); + + dc->roaming_allowed = value; + dc_netreg_update(dc); + } else if (!strcmp(property, "Powered")) { + if (!dc->driver->set_attached) + return __ofono_error_not_implemented(msg); + + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_BOOLEAN) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &value); + + dc->powered = value; + dc_netreg_update(dc); + } else + return __ofono_error_invalid_args(msg); + + path = __ofono_atom_get_path(dc->atom); + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + property, DBUS_TYPE_BOOLEAN, &value); + + return dbus_message_new_method_return(msg); +} + +static void dc_create_context_callback(const struct ofono_error *error, + struct ofono_data_context *ctx, + void *data) +{ + struct ofono_data_connection *dc = data; + DBusMessage *reply; + const char *path; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + ofono_debug("Creating new context failed with error: %s", + telephony_error_to_str(error)); + + reply = __ofono_error_failed(dc->pending); + goto error; + } + + reply = dbus_message_new_method_return(dc->pending); + + path = dc_build_context_path(dc, ctx); + dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + +error: + __ofono_dbus_pending_reply(&dc->pending, reply); +} + +static DBusMessage *dc_create_context(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ofono_data_connection *dc = data; + + if (dc->pending) + return __ofono_error_busy(msg); + + if (!dc->driver->create_context) + return __ofono_error_not_implemented(msg); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) + return __ofono_error_invalid_args(msg); + + dc->pending = dbus_message_ref(msg); + + dc->driver->create_context(dc, dc_create_context_callback, dc); + + return NULL; +} + +static void dc_remove_context_callback(const struct ofono_error *error, + void *data) +{ + struct ofono_data_connection *dc = data; + DBusMessage *reply; + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path; + char **objpath_list; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + ofono_error("Removing context failed with error: %s", + telephony_error_to_str(error)); + + reply = __ofono_error_failed(dc->pending); + goto error; + } + + context_dbus_unregister(dc, dc->current_context->context); + dc->contexts = g_slist_remove(dc->contexts, dc->current_context); + dc->current_context = NULL; + + objpath_list = dc_contexts_path_list(dc, dc->contexts); + if (!objpath_list) { + ofono_error("Could not allocate PrimaryContext objects list"); + return; + } + + path = __ofono_atom_get_path(dc->atom); + ofono_dbus_signal_array_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "PrimaryContexts", + DBUS_TYPE_OBJECT_PATH, &objpath_list); + + g_strfreev(objpath_list); + + reply = dbus_message_new_method_return(dc->pending); + +error: + __ofono_dbus_pending_reply(&dc->pending, reply); +} + +static void dc_deactivate_context_callback(const struct ofono_error *error, + void *data) +{ + struct ofono_data_connection *dc = data; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + ofono_debug("Removing context failed with error: %s", + telephony_error_to_str(error)); + + dc->current_context = NULL; + __ofono_dbus_pending_reply(&dc->pending, __ofono_error_failed( + dc->pending)); + return; + } + + dc->driver->remove_context(dc, dc->current_context->context->id, + dc_remove_context_callback, dc); +} + +static DBusMessage *dc_remove_context(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ofono_data_connection *dc = data; + struct context *ctx; + const char *path; + + if (dc->pending) + return __ofono_error_busy(msg); + + if (!dc->driver->remove_context) + return __ofono_error_not_implemented(msg); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return __ofono_error_invalid_args(msg); + + if (path[0] == '\0') + return __ofono_error_invalid_format(msg); + + ctx = dc_context_by_path(dc, path); + if (!ctx) + return __ofono_error_not_found(msg); + + dc->pending = dbus_message_ref(msg); + dc->current_context = ctx; + + if (ctx->context->active && dc->driver->set_active) { + dc->driver->set_active(dc, ctx->context->id, 0, + dc_deactivate_context_callback, dc); + + return NULL; + } + + dc->driver->remove_context(dc, ctx->context->id, + dc_remove_context_callback, dc); + + return NULL; +} + +static DBusMessage *dc_deactivate_all(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ofono_data_connection *dc = data; + + if (dc->pending) + return __ofono_error_busy(msg); + + if (!dc->driver->set_active_all) + return __ofono_error_not_implemented(msg); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) + return __ofono_error_invalid_args(msg); + + dc->pending = dbus_message_ref(msg); + + dc->driver->set_active_all(dc, 0, dc_generic_callback, dc); + + return NULL; +} + +static GDBusMethodTable manager_methods[] = { + { "GetProperties", "", "a{sv}", dc_get_manager_properties }, + { "SetProperty", "sv", "", dc_set_manager_property }, + { "CreateContext", "", "o", dc_create_context, + G_DBUS_METHOD_FLAG_ASYNC }, + { "RemoveContext", "o", "", dc_remove_context, + G_DBUS_METHOD_FLAG_ASYNC }, + { "DeactivateAll", "", "", dc_deactivate_all, + G_DBUS_METHOD_FLAG_ASYNC }, + { } +}; + +static GDBusSignalTable manager_signals[] = { + { "PropertyChanged", "sv" }, + { } +}; + +void ofono_data_connection_notify(struct ofono_data_connection *dc, + struct ofono_data_context *ctx) +{ + struct context *context = context_create(dc, ctx); + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path; + char **objpath_list; + + if (!context) { + ofono_error("Unable to allocate context struct"); + return; + } + + ofono_debug("Registering new context: %i", ctx->id); + if (!context_dbus_register(dc, context)) + return; + + dc->contexts = g_slist_insert_sorted(dc->contexts, + context, context_compare); + + objpath_list = dc_contexts_path_list(dc, dc->contexts); + if (!objpath_list) { + ofono_error("Unable to allocate PrimaryContext objects list"); + return; + } + + path = __ofono_atom_get_path(dc->atom); + ofono_dbus_signal_array_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "PrimaryContexts", + DBUS_TYPE_OBJECT_PATH, &objpath_list); + + g_strfreev(objpath_list); +} + +void ofono_data_connection_deactivated(struct ofono_data_connection *dc, + unsigned id) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path = NULL; /* Suppress warning */ + dbus_bool_t value = 0; + GSList *l; + struct context *ctx; + + for (l = dc->contexts; l; l = l->next) { + ctx = l->data; + + if (ctx->context->id == id) { + path = dc_build_context_path(dc, ctx->context); + break; + } + } + + ofono_dbus_signal_property_changed(conn, path, DATA_CONTEXT_INTERFACE, + "Active", DBUS_TYPE_BOOLEAN, + &value); + +} + +void ofono_data_connection_detached(struct ofono_data_connection *dc) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path; + dbus_bool_t value = 0; + + if (dc->attached && !(dc->flags & DATA_CONNECTION_FLAG_ATTACHING)) { + dc->attached = 0; + + path = __ofono_atom_get_path(dc->atom); + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "Attached", DBUS_TYPE_BOOLEAN, &value); + + dc_netreg_update(dc); + } +} + +static void set_registration_status(struct ofono_data_connection *dc, + int status) +{ + const char *str_status = registration_status_to_string(status); + const char *path = __ofono_atom_get_path(dc->atom); + DBusConnection *conn = ofono_dbus_get_connection(); + dbus_bool_t attached; + + dc->status = status; + + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "Status", DBUS_TYPE_STRING, + &str_status); + + attached = (status != NETWORK_REGISTRATION_STATUS_REGISTERED && + status != NETWORK_REGISTRATION_STATUS_ROAMING); + if (dc->attached != (int) attached && + !(dc->flags & DATA_CONNECTION_FLAG_ATTACHING)) { + dc->attached = (int) attached; + + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "Attached", DBUS_TYPE_BOOLEAN, + &attached); + + dc_netreg_update(dc); + } +} + +static void set_registration_location(struct ofono_data_connection *dc, + int lac) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path = __ofono_atom_get_path(dc->atom); + dbus_uint16_t dbus_lac = lac; + + if (lac > 0xffff) + return; + + dc->location = lac; + + if (dc->location == -1) + return; + + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "LocationAreaCode", + DBUS_TYPE_UINT16, &dbus_lac); +} + +static void set_registration_cellid(struct ofono_data_connection *dc, int ci) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path = __ofono_atom_get_path(dc->atom); + dbus_uint32_t dbus_ci = ci; + + dc->cellid = ci; + + if (dc->cellid == -1) + return; + + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "CellId", DBUS_TYPE_UINT32, + &dbus_ci); +} + +static void set_registration_technology(struct ofono_data_connection *dc, + int tech) +{ + const char *tech_str = registration_tech_to_string(tech); + DBusConnection *conn = ofono_dbus_get_connection(); + const char *path = __ofono_atom_get_path(dc->atom); + + dc->technology = tech; + + if (dc->technology == -1) + return; + + ofono_dbus_signal_property_changed(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + "Technology", DBUS_TYPE_STRING, + &tech_str); +} + +void ofono_data_netreg_status_notify(struct ofono_data_connection *dc, + int status, int lac, int ci, int tech) +{ + if (dc->status != status) + set_registration_status(dc, status); + + if (dc->location != lac) + set_registration_location(dc, lac); + + if (dc->cellid != ci) + set_registration_cellid(dc, ci); + + if (dc->technology != tech) + set_registration_technology(dc, tech); +} + +int ofono_data_connection_driver_register( + const struct ofono_data_connection_driver *d) +{ + DBG("driver: %p, name: %s", d, d->name); + + if (d->probe == NULL) + return -EINVAL; + + g_drivers = g_slist_prepend(g_drivers, (void *)d); + + return 0; +} + +void ofono_data_connection_driver_unregister( + const struct ofono_data_connection_driver *d) +{ + DBG("driver: %p, name: %s", d, d->name); + + g_drivers = g_slist_remove(g_drivers, (void *)d); +} + +static void data_connection_unregister(struct ofono_atom *atom) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + struct ofono_data_connection *dc = __ofono_atom_get_data(atom); + struct ofono_modem *modem = __ofono_atom_get_modem(atom); + const char *path = __ofono_atom_get_path(atom); + + g_slist_free(dc->contexts); + + ofono_modem_remove_interface(modem, DATA_CONNECTION_MANAGER_INTERFACE); + g_dbus_unregister_interface(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE); +} + +static void data_connection_remove(struct ofono_atom *atom) +{ + struct ofono_data_connection *dc = __ofono_atom_get_data(atom); + + DBG("atom: %p", atom); + + if (dc == NULL) + return; + + if (dc->driver && dc->driver->remove) + dc->driver->remove(dc); + + g_free(dc); +} + +struct ofono_data_connection *ofono_data_connection_create( + struct ofono_modem *modem, unsigned int vendor, + const char *driver, void *data) +{ + struct ofono_data_connection *dc; + GSList *l; + + if (driver == NULL) + return NULL; + + dc = g_try_new0(struct ofono_data_connection, 1); + + if (dc == NULL) + return NULL; + + dc->atom = __ofono_modem_add_atom(modem, + OFONO_ATOM_TYPE_DATA_CONNECTION, + data_connection_remove, dc); + + for (l = g_drivers; l; l = l->next) { + const struct ofono_data_connection_driver *drv = l->data; + + if (g_strcmp0(drv->name, driver)) + continue; + + if (drv->probe(dc, vendor, data) < 0) + continue; + + dc->driver = drv; + break; + } + + dc->technology = -1; + dc->cellid = -1; + dc->location = -1; + + return dc; +} + +void ofono_data_connection_register(struct ofono_data_connection *dc) +{ + DBusConnection *conn = ofono_dbus_get_connection(); + struct ofono_modem *modem = __ofono_atom_get_modem(dc->atom); + const char *path = __ofono_atom_get_path(dc->atom); + + if (!g_dbus_register_interface(conn, path, + DATA_CONNECTION_MANAGER_INTERFACE, + manager_methods, manager_signals, NULL, + dc, NULL)) { + ofono_error("Could not create %s interface", + DATA_CONNECTION_MANAGER_INTERFACE); + + return; + } + + ofono_modem_add_interface(modem, DATA_CONNECTION_MANAGER_INTERFACE); + + __ofono_atom_register(dc->atom, data_connection_unregister); +} + +void ofono_data_connection_remove(struct ofono_data_connection *dc) +{ + __ofono_atom_free(dc->atom); +} + +void ofono_data_connection_set_data(struct ofono_data_connection *dc, + void *data) +{ + dc->driver_data = data; +} + +void *ofono_data_connection_get_data(struct ofono_data_connection *dc) +{ + return dc->driver_data; +} diff --git a/src/ofono.h b/src/ofono.h index 409a9e2e..177e1fdd 100644 --- a/src/ofono.h +++ b/src/ofono.h @@ -106,6 +106,7 @@ enum ofono_atom_type { OFONO_ATOM_TYPE_MESSAGE_WAITING = 13, OFONO_ATOM_TYPE_CBS = 14, OFONO_ATOM_TYPES_CALL_VOLUME = 15, + OFONO_ATOM_TYPE_DATA_CONNECTION = 16, }; enum ofono_atom_watch_condition { @@ -160,6 +161,7 @@ void __ofono_atom_free(struct ofono_atom *atom); #include #include #include +#include #include