/* * * Connection Manager * * Copyright (C) 2007-2010 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 #ifndef IFF_LOWER_UP #define IFF_LOWER_UP 0x10000 #endif #include #define CONNMAN_API_SUBJECT_TO_CHANGE #include #include #include #include #include #include #include static const char *cfun_prefix[] = { "+CFUN:", NULL }; static const char *cind_prefix[] = { "+CIND:", NULL }; static const char *cops_prefix[] = { "+COPS:", NULL }; static const char *creg_prefix[] = { "+CREG:", NULL }; static const char *cgreg_prefix[] = { "+CGREG:", NULL }; struct mbm_data { GAtChat *chat; unsigned flags; unsigned int watch; struct connman_network *network; char *imsi; unsigned int cimi_counter; unsigned int creg_status; }; static void mbm_debug(const char *str, void *user_data) { connman_info("%s", str); } static void emrdy_notifier(GAtResult *result, gpointer user_data) { } static void erinfo_notifier(GAtResult *result, gpointer user_data) { GAtResultIter iter; int mode, gsm, umts; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "*ERINFO:") == FALSE) return; g_at_result_iter_next_number(&iter, &mode); g_at_result_iter_next_number(&iter, &gsm); g_at_result_iter_next_number(&iter, &umts); connman_info("network capability: GSM %d UMTS %d", gsm, umts); } static void erinfo_callback(gboolean ok, GAtResult *result, gpointer user_data) { if (ok == FALSE) return; erinfo_notifier(result, user_data); } static void cgdcont_callback(gboolean ok, GAtResult *result, gpointer user_data) { GAtResultIter iter; g_at_result_iter_init(&iter, result); } static void cgreg_query(gboolean ok, GAtResult *result, gpointer user_data) { struct mbm_data *data = user_data; GAtResultIter iter; int status, mode; if (data->network == NULL) return; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "+CGREG:") == FALSE) return; g_at_result_iter_skip_next(&iter); g_at_result_iter_next_number(&iter, &status); g_at_result_iter_skip_next(&iter); g_at_result_iter_skip_next(&iter); g_at_result_iter_next_number(&iter, &mode); connman_network_set_uint8(data->network, "Cellular.Mode", mode); connman_network_set_group(data->network, data->imsi); } static void enap_query(gboolean ok, GAtResult *result, gpointer user_data) { GAtResultIter iter; g_at_result_iter_init(&iter, result); } static void enap_enable(gboolean ok, GAtResult *result, gpointer user_data) { struct mbm_data *data = user_data; GAtResultIter iter; g_at_result_iter_init(&iter, result); g_at_chat_send(data->chat, "AT+CGREG?", cgreg_prefix, cgreg_query, data, NULL); } static void enap_disable(gboolean ok, GAtResult *result, gpointer user_data) { GAtResultIter iter; g_at_result_iter_init(&iter, result); } static void cind_callback(gboolean ok, GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); GAtResultIter iter; int dummy, strength; if (ok == FALSE) return; if (data->network == NULL) return; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "+CIND:") == FALSE) return; g_at_result_iter_next_number(&iter, &dummy); g_at_result_iter_next_number(&iter, &strength); connman_network_set_strength(data->network, strength * 20); connman_network_set_group(data->network, data->imsi); } static void network_callback(gboolean ok, GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); GAtResultIter iter; char *name, *mccmnc; const char *oper; int mode, format, tech; if (ok == FALSE) return; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "+COPS:") == FALSE) return; g_at_result_iter_next_number(&iter, &mode); g_at_result_iter_next_number(&iter, &format); g_at_result_iter_next_string(&iter, &oper); mccmnc = g_strdup(oper); g_at_result_iter_next_number(&iter, &tech); if (g_at_result_iter_next(&iter, "+COPS:") == FALSE) return; g_at_result_iter_next_number(&iter, &mode); g_at_result_iter_next_number(&iter, &format); g_at_result_iter_next_string(&iter, &oper); name = g_strdup(oper); g_at_result_iter_next_number(&iter, &tech); data->network = connman_network_create(mccmnc, CONNMAN_NETWORK_TYPE_MBM); if (data->network != NULL) { char *mcc, *mnc; int index; index = connman_device_get_index(device); connman_network_set_index(data->network, index); connman_network_set_protocol(data->network, CONNMAN_NETWORK_PROTOCOL_IP); mcc = g_strndup(mccmnc, 3); connman_network_set_string(data->network, "Cellular.MCC", mcc); g_free(mcc); mnc = g_strdup(mccmnc + 3); connman_network_set_string(data->network, "Cellular.MNC", mnc); g_free(mnc); connman_network_set_name(data->network, name); connman_network_set_group(data->network, data->imsi); connman_device_add_network(device, data->network); } g_free(name); g_free(mccmnc); g_at_chat_send(data->chat, "AT+CIND?", cind_prefix, cind_callback, device, NULL); } static void network_ready(struct connman_device *device) { struct mbm_data *data = connman_device_get_data(device); g_at_chat_send(data->chat, "AT*E2NAP=1", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT*ERINFO=1", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+COPS=3,2;+COPS?;+COPS=3,0;+COPS?", cops_prefix, network_callback, device, NULL); g_at_chat_send(data->chat, "AT*ERINFO?", NULL, erinfo_callback, device, NULL); } static gboolean lost_network(int old, int new) { if (old != 1 && old != 5) return FALSE; if (new == 1 || new == 5) return FALSE; return TRUE; } static gboolean get_network(int old, int new) { if (old == 1 || old == 5) return FALSE; if (new != 1 && new != 5) return FALSE; return TRUE; } static void cleanup_network(struct connman_device *device) { struct mbm_data *data = connman_device_get_data(device); const char *identifier; DBG(""); if (data->network == NULL) return; connman_network_set_connected(data->network, FALSE); identifier = connman_network_get_identifier(data->network); connman_device_remove_network(device, identifier); data->network = NULL; } static void update_roaming(struct connman_device *device, int status) { struct mbm_data *data = connman_device_get_data(device); if (data->network == NULL) return; if (status != 1 && status != 5) return; if (status == 1) connman_network_set_roaming(data->network, FALSE); else connman_network_set_roaming(data->network, TRUE); connman_network_set_group(data->network, data->imsi); } static void creg_update(struct connman_device *device, int status) { struct mbm_data *data = connman_device_get_data(device); int old_status = data->creg_status; DBG("old_status %d status %d", old_status, status); data->creg_status = status; if (lost_network(old_status, status) == TRUE) { cleanup_network(device); return; } if (get_network(old_status, status) == TRUE) network_ready(device); update_roaming(device, status); } static void creg_query(gboolean ok, GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; GAtResultIter iter; int status; if (ok == FALSE) return; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "+CREG:") == FALSE) return; g_at_result_iter_skip_next(&iter); g_at_result_iter_next_number(&iter, &status); creg_update(device, status); } static void cops_callback(gboolean ok, GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); if (ok == FALSE) return; g_at_chat_send(data->chat, "AT+CREG?", creg_prefix, creg_query, device, NULL); } static void register_network(struct connman_device *device) { struct mbm_data *data = connman_device_get_data(device); g_at_chat_send(data->chat, "AT+CREG=1", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+CGREG=2", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+CMER=3,0,0,1", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+COPS=0", cops_prefix, cops_callback, device, NULL); } static void e2nap_notifier(GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); GAtResultIter iter; int state; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "*E2NAP:") == FALSE) return; g_at_result_iter_next_number(&iter, &state); connman_info("network connection: state %d", state); g_at_chat_send(data->chat, "AT+CIND?", cind_prefix, cind_callback, device, NULL); } static void pacsp0_notifier(GAtResult *result, gpointer user_data) { } static void ciev_notifier(GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); GAtResultIter iter; int index, strength; if (data->network == NULL) return; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "+CIEV:") == FALSE) return; g_at_result_iter_next_number(&iter, &index); if (index != 2) return; g_at_result_iter_next_number(&iter, &strength); connman_network_set_strength(data->network, strength * 20); connman_network_set_group(data->network, data->imsi); } static void creg_notifier(GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; GAtResultIter iter; int status; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "+CREG:") == FALSE) return; g_at_result_iter_next_number(&iter, &status); creg_update(device, status); } static void cgreg_notifier(GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); GAtResultIter iter; int status, mode; if (data->network == NULL) return; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "+CGREG:") == FALSE) return; g_at_result_iter_next_number(&iter, &status); g_at_result_iter_skip_next(&iter); g_at_result_iter_skip_next(&iter); g_at_result_iter_next_number(&iter, &mode); connman_network_set_uint8(data->network, "Cellular.Mode", mode); connman_network_set_group(data->network, data->imsi); } static void cimi_callback(gboolean ok, GAtResult *result, gpointer user_data); static gboolean cimi_timeout(gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); data->cimi_counter++; if (data->cimi_counter > 5) { connman_device_set_powered(device, FALSE); return FALSE; } g_at_chat_send(data->chat, "AT+CIMI", NULL, cimi_callback, device, NULL); return FALSE; } static void cimi_callback(gboolean ok, GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); GAtResultIter iter; const char *imsi; int i; if (ok == FALSE) { g_timeout_add_seconds(1, cimi_timeout, device); return; } g_at_result_iter_init(&iter, result); for (i = 0; i < g_at_result_num_response_lines(result); i++) g_at_result_iter_next(&iter, NULL); imsi = g_at_result_iter_raw_line(&iter); data->imsi = g_strdup(imsi); register_network(device); } static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); if (ok == FALSE) { connman_device_set_powered(device, FALSE); return; } connman_device_set_powered(device, TRUE); g_at_chat_send(data->chat, "AT+CIMI", NULL, cimi_callback, device, NULL); } static void cfun_disable(gboolean ok, GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); connman_device_set_powered(device, FALSE); if (data->chat != NULL) { g_at_chat_unref(data->chat); data->chat = NULL; } } static void cfun_query(gboolean ok, GAtResult *result, gpointer user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); GAtResultIter iter; int status; if (ok == FALSE) return; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "+CFUN:") == FALSE) return; g_at_result_iter_next_number(&iter, &status); if (status == 1) { connman_device_set_powered(device, TRUE); g_at_chat_send(data->chat, "AT+CIMI", NULL, cimi_callback, device, NULL); } else { g_at_chat_send(data->chat, "AT+CFUN=1", cfun_prefix, cfun_enable, device, NULL); } } static int network_probe(struct connman_network *network) { struct connman_device *device = connman_network_get_device(network); struct mbm_data *data; DBG("network %p", network); data = connman_device_get_data(device); connman_network_set_data(network, data); g_at_chat_send(data->chat, "AT*ENAP?", NULL, enap_query, device, NULL); return 0; } static void network_remove(struct connman_network *network) { struct connman_device *device = connman_network_get_device(network); struct mbm_data *data; DBG("network %p", network); data = connman_device_get_data(device); data->network = NULL; connman_network_set_data(network, NULL); } static int network_connect(struct connman_network *network) { struct mbm_data *data = connman_network_get_data(network); const char *apn; char *cmd; DBG("network %p", network); apn = connman_network_get_string(network, "Cellular.APN"); if (apn == NULL) return -EINVAL; cmd = g_strdup_printf("AT+CGDCONT=1,\"IP\",\"%s\"", apn); g_at_chat_send(data->chat, cmd, NULL, cgdcont_callback, NULL, NULL); g_free(cmd); g_at_chat_send(data->chat, "AT*ENAP=1,1", NULL, enap_enable, data, NULL); return 0; } static int network_disconnect(struct connman_network *network) { struct mbm_data *data = connman_network_get_data(network); DBG("network %p", network); g_at_chat_send(data->chat, "AT*ENAP=0", NULL, enap_disable, data, NULL); return 0; } static struct connman_network_driver network_driver = { .name = "mbm", .type = CONNMAN_NETWORK_TYPE_MBM, .probe = network_probe, .remove = network_remove, .connect = network_connect, .disconnect = network_disconnect, }; static void mbm_newlink(unsigned flags, unsigned change, void *user_data) { struct connman_device *device = user_data; struct mbm_data *data = connman_device_get_data(device); if (data->network == NULL) goto done; DBG("device %p flags %d change %d", device, flags, change); if ((data->flags & IFF_LOWER_UP) != (flags & IFF_LOWER_UP)) { if (flags & IFF_LOWER_UP) { connman_network_set_method(data->network, CONNMAN_IPCONFIG_METHOD_DHCP); connman_network_set_connected(data->network, TRUE); } else { connman_network_set_connected(data->network, FALSE); } } done: data->flags = flags; } static int mbm_probe(struct connman_device *device) { struct mbm_data *data; int index; DBG("device %p", device); data = g_try_new0(struct mbm_data, 1); if (data == NULL) return -ENOMEM; connman_device_set_data(device, data); index = connman_device_get_index(device); data->watch = connman_rtnl_add_newlink_watch(index, mbm_newlink, device); return 0; } static void mbm_remove(struct connman_device *device) { struct mbm_data *data = connman_device_get_data(device); DBG("device %p", device); connman_device_set_data(device, NULL); connman_rtnl_remove_watch(data->watch); if (data->chat != NULL) { g_at_chat_unref(data->chat); data->chat = NULL; } g_free(data->imsi); g_free(data); } static int mbm_enable(struct connman_device *device) { struct mbm_data *data = connman_device_get_data(device); GAtSyntax *syntax; GIOChannel *channel; const char *devnode; int index; DBG("device %p", device); devnode = connman_device_get_control(device); if (devnode == NULL) return -EIO; channel = g_at_tty_open(devnode, NULL); if (channel == NULL) return -EIO; syntax = g_at_syntax_new_gsmv1(); data->chat = g_at_chat_new(channel, syntax); g_at_syntax_unref(syntax); g_io_channel_unref(channel); if (data->chat == NULL) return -EIO; if (getenv("MBM_DEBUG")) g_at_chat_set_debug(data->chat, mbm_debug, NULL); g_at_chat_register(data->chat, "*EMRDY:", emrdy_notifier, FALSE, device, NULL); g_at_chat_register(data->chat, "*ERINFO:", erinfo_notifier, FALSE, device, NULL); g_at_chat_register(data->chat, "*E2NAP:", e2nap_notifier, FALSE, device, NULL); g_at_chat_register(data->chat, "+PACSP0", pacsp0_notifier, FALSE, device, NULL); g_at_chat_register(data->chat, "+CIEV:", ciev_notifier, FALSE, device, NULL); g_at_chat_register(data->chat, "+CREG:", creg_notifier, FALSE, device, NULL); g_at_chat_register(data->chat, "+CGREG:", cgreg_notifier, FALSE, device, NULL); index = connman_device_get_index(device); connman_inet_ifup(index); g_at_chat_send(data->chat, "AT&F E0 V1 X4 &C1 +CMEE=1", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT*EMRDY?", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+CFUN?", cfun_prefix, cfun_query, device, NULL); return -EINPROGRESS; } static int mbm_disable(struct connman_device *device) { struct mbm_data *data = connman_device_get_data(device); int index; DBG("device %p", device); g_at_chat_send(data->chat, "AT+CMER=0", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+CREG=0", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+CGREG=0", NULL, NULL, NULL, NULL); g_at_chat_send(data->chat, "AT+CFUN=4", cfun_prefix, cfun_disable, device, NULL); index = connman_device_get_index(device); connman_inet_ifdown(index); return -EINPROGRESS; } static struct connman_device_driver mbm_driver = { .name = "mbm", .type = CONNMAN_DEVICE_TYPE_MBM, .probe = mbm_probe, .remove = mbm_remove, .enable = mbm_enable, .disable = mbm_disable, }; static int mbm_init(void) { int err; err = connman_network_driver_register(&network_driver); if (err < 0) return err; err = connman_device_driver_register(&mbm_driver); if (err < 0) { connman_network_driver_unregister(&network_driver); return err; } return 0; } static void mbm_exit(void) { connman_device_driver_unregister(&mbm_driver); connman_network_driver_register(&network_driver); } CONNMAN_PLUGIN_DEFINE(mbm, "Ericsson MBM device plugin", VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT, mbm_init, mbm_exit)