/* * * oFono - Open Source Telephony * * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. * Copyright (C) 2010 ST-Ericsson AB. * * 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 "common.h" #include "atmodem.h" #include "vendor.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 }; static const char *cind_prefix[] = { "+CIND:", NULL }; static const char *cmer_prefix[] = { "+CMER:", NULL }; static const char *smoni_prefix[] = { "^SMONI:", NULL }; static const char *zpas_prefix[] = { "+ZPAS:", NULL }; static const char *option_tech_prefix[] = { "_OCTI:", "_OUWCTI:", NULL }; struct netreg_data { GAtChat *chat; char mcc[OFONO_MAX_MCC_LENGTH + 1]; char mnc[OFONO_MAX_MNC_LENGTH + 1]; int signal_index; /* If strength is reported via CIND */ int signal_min; /* min strength reported via CIND */ int signal_max; /* max strength reported via CIND */ int signal_invalid; /* invalid strength reported via CIND */ int tech; struct ofono_network_time time; guint nitz_timeout; unsigned int vendor; }; struct tech_query { int status; int lac; int ci; struct ofono_netreg *netreg; }; static void extract_mcc_mnc(const char *str, char *mcc, char *mnc) { /* Three digit country code */ strncpy(mcc, str, OFONO_MAX_MCC_LENGTH); mcc[OFONO_MAX_MCC_LENGTH] = '\0'; /* Usually a 2 but sometimes 3 digit network code */ strncpy(mnc, str + OFONO_MAX_MCC_LENGTH, OFONO_MAX_MNC_LENGTH); mnc[OFONO_MAX_MNC_LENGTH] = '\0'; } static int zte_parse_tech(GAtResult *result) { GAtResultIter iter; const char *network, *domain; int tech; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+ZPAS:")) return -1; if (!g_at_result_iter_next_string(&iter, &network)) return -1; if (!g_at_result_iter_next_string(&iter, &domain)) return -1; if (g_str_equal(network, "GSM") == TRUE || g_str_equal(network, "GPRS") == TRUE) tech = ACCESS_TECHNOLOGY_GSM; else if (g_str_equal(network, "EDGE") == TRUE) tech = ACCESS_TECHNOLOGY_GSM_EGPRS; else if (g_str_equal(network, "UMTS") == TRUE) tech = ACCESS_TECHNOLOGY_UTRAN; else if (g_str_equal(network, "HSDPA") == TRUE) tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA; else tech = -1; DBG("network %s domain %s tech %d", network, domain, tech); return tech; } static int option_parse_tech(GAtResult *result) { GAtResultIter iter; int s, octi, ouwcti; int tech; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "_OCTI:")) return -1; if (!g_at_result_iter_next_number(&iter, &s)) return -1; if (!g_at_result_iter_next_number(&iter, &octi)) return -1; if (!g_at_result_iter_next(&iter, "_OUWCTI:")) return -1; if (!g_at_result_iter_next_number(&iter, &s)) return -1; if (!g_at_result_iter_next_number(&iter, &ouwcti)) return -1; switch (octi) { case 1: /* GSM */ tech = ACCESS_TECHNOLOGY_GSM; break; case 2: /* GPRS */ tech = ACCESS_TECHNOLOGY_GSM; break; case 3: /* EDGE */ tech = ACCESS_TECHNOLOGY_GSM_EGPRS; break; default: tech = -1; break; } switch (ouwcti) { case 1: /* UMTS */ tech = ACCESS_TECHNOLOGY_UTRAN; break; case 2: /* HSDPA */ tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA; break; case 3: /* HSUPA */ tech = ACCESS_TECHNOLOGY_UTRAN_HSUPA; break; case 4: /* HSPA */ tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA; break; } DBG("octi %d ouwcti %d tech %d", octi, ouwcti, tech); return tech; } static int cinterion_parse_tech(GAtResult *result) { int tech = -1; GAtResultIter iter; const char *technology; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "^SMONI: ")) return tech; if (!g_at_result_iter_next_unquoted_string(&iter, &technology)) return tech; if (strcmp(technology, "2G") == 0) { tech = ACCESS_TECHNOLOGY_GSM_EGPRS; } else if (strcmp(technology, "3G") == 0) { tech = ACCESS_TECHNOLOGY_UTRAN; } else if (strcmp(technology, "4G") == 0) { tech = ACCESS_TECHNOLOGY_EUTRAN; } return tech; } static void at_creg_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; ofono_netreg_status_cb_t cb = cbd->cb; int status, lac, ci, tech; struct ofono_error error; struct netreg_data *nd = cbd->user; decode_at_error(&error, g_at_result_final_response(result)); if (!ok) { cb(&error, -1, -1, -1, -1, cbd->data); return; } if (at_util_parse_reg(result, "+CREG:", NULL, &status, &lac, &ci, &tech, nd->vendor) == FALSE) { CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, cbd->data); return; } if ((status == 1 || status == 5) && (tech == -1)) tech = nd->tech; /* 6-10 is EUTRAN, with 8 being emergency bearer case */ if (status > 5 && tech == -1) tech = ACCESS_TECHNOLOGY_EUTRAN; cb(&error, status, lac, ci, tech, cbd->data); } static void cinterion_query_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct tech_query *tq = user_data; int tech; tech = cinterion_parse_tech(result); ofono_netreg_status_notify(tq->netreg, tq->status, tq->lac, tq->ci, tech); } static void zte_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; struct ofono_netreg *netreg = cbd->data; struct netreg_data *nd = ofono_netreg_get_data(netreg); if (ok) nd->tech = zte_parse_tech(result); else nd->tech = -1; } static void option_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; struct ofono_netreg *netreg = cbd->data; struct netreg_data *nd = ofono_netreg_get_data(netreg); if (ok) nd->tech = option_parse_tech(result); else nd->tech = -1; } static void at_registration_status(struct ofono_netreg *netreg, ofono_netreg_status_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); cbd->user = nd; switch (nd->vendor) { case OFONO_VENDOR_MBM: /* * Send *ERINFO to find out the current tech, it will be * intercepted in mbm_erinfo_notify */ g_at_chat_send(nd->chat, "AT*ERINFO?", none_prefix, NULL, NULL, NULL); break; case OFONO_VENDOR_GOBI: /* * Send *CNTI=0 to find out the current tech, it will be * intercepted in gobi_cnti_notify */ g_at_chat_send(nd->chat, "AT*CNTI=0", none_prefix, NULL, NULL, NULL); break; case OFONO_VENDOR_NOVATEL: /* * Send $CNTI=0 to find out the current tech, it will be * intercepted in nw_cnti_notify */ g_at_chat_send(nd->chat, "AT$CNTI=0", none_prefix, NULL, NULL, NULL); break; case OFONO_VENDOR_ZTE: /* * Send +ZPAS? to find out the current tech, zte_tech_cb * will call, fire CREG? to do the rest. */ if (g_at_chat_send(nd->chat, "AT+ZPAS?", zpas_prefix, zte_tech_cb, cbd, NULL) == 0) nd->tech = -1; break; case OFONO_VENDOR_OPTION_HSO: /* * Send AT_OCTI?;_OUWCTI? to find out the current tech, * option_tech_cb will call, fire CREG? to do the rest. */ if (g_at_chat_send(nd->chat, "AT_OCTI?;_OUWCTI?", option_tech_prefix, option_tech_cb, cbd, NULL) == 0) nd->tech = -1; break; } if (g_at_chat_send(nd->chat, "AT+CREG?", creg_prefix, at_creg_cb, cbd, g_free) > 0) return; g_free(cbd); CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, data); } static void cops_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; struct netreg_data *nd = ofono_netreg_get_data(cbd->user); ofono_netreg_operator_cb_t cb = cbd->cb; struct ofono_network_operator op; GAtResultIter iter; int format, tech; const char *name; struct ofono_error error; decode_at_error(&error, g_at_result_final_response(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 != 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 = ACCESS_TECHNOLOGY_GSM; strncpy(op.name, name, OFONO_MAX_OPERATOR_NAME_LENGTH); op.name[OFONO_MAX_OPERATOR_NAME_LENGTH] = '\0'; strncpy(op.mcc, nd->mcc, OFONO_MAX_MCC_LENGTH); op.mcc[OFONO_MAX_MCC_LENGTH] = '\0'; strncpy(op.mnc, nd->mnc, OFONO_MAX_MNC_LENGTH); op.mnc[OFONO_MAX_MNC_LENGTH] = '\0'; /* Set to current */ op.status = 2; op.tech = tech; DBG("cops_cb: %s, %s %s %d", name, nd->mcc, nd->mnc, tech); cb(&error, &op, cbd->data); g_free(cbd); return; error: cb(&error, 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 netreg_data *nd = ofono_netreg_get_data(cbd->user); ofono_netreg_operator_cb_t cb = cbd->cb; GAtResultIter iter; const char *str; int format; int len; struct ofono_error error; decode_at_error(&error, g_at_result_final_response(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) goto error; len = strspn(str, "0123456789"); if (len != 5 && len != 6) goto error; extract_mcc_mnc(str, nd->mcc, nd->mnc); DBG("Cops numeric got mcc: %s, mnc: %s", nd->mcc, nd->mnc); ok = g_at_chat_send(nd->chat, "AT+COPS=3,0", none_prefix, NULL, NULL, NULL); if (ok) ok = g_at_chat_send(nd->chat, "AT+COPS?", cops_prefix, cops_cb, cbd, NULL); if (ok) return; error: cb(&error, NULL, cbd->data); g_free(cbd); } static void at_current_operator(struct ofono_netreg *netreg, ofono_netreg_operator_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); gboolean ok; cbd->user = netreg; /* Nokia modems have a broken return value for the string * returned for the numeric value. It misses a " at the end. * Trying to read this will stall the parser. So skip it. */ if (nd->vendor == OFONO_VENDOR_NOKIA) { ok = g_at_chat_send(nd->chat, "AT+COPS=3,0", none_prefix, NULL, NULL, NULL); if (ok) ok = g_at_chat_send(nd->chat, "AT+COPS?", cops_prefix, cops_cb, cbd, NULL); } else { ok = g_at_chat_send(nd->chat, "AT+COPS=3,2", none_prefix, NULL, NULL, NULL); if (ok) ok = g_at_chat_send(nd->chat, "AT+COPS?", cops_prefix, cops_numeric_cb, cbd, NULL); } if (ok) return; g_free(cbd); CALLBACK_WITH_FAILURE(cb, NULL, data); } static void cops_list_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; ofono_netreg_operator_list_cb_t cb = cbd->cb; struct ofono_network_operator *list; GAtResultIter iter; int num = 0; struct ofono_error error; 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; } DBG("Got %d elements", num); list = g_try_new0(struct ofono_network_operator, num); if (list == NULL) { CALLBACK_WITH_FAILURE(cb, 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, plmn; 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 = ACCESS_TECHNOLOGY_GSM; list[num].tech = tech; if (!g_at_result_iter_next_number(&iter, &plmn)) plmn = 0; if (!g_at_result_iter_close_list(&iter)) break; num += 1; } } DBG("Got %d operators", num); { int i = 0; for (; i < num; i++) { DBG("Operator: %s, %s, %s, 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_netreg *netreg, ofono_netreg_operator_list_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); if (g_at_chat_send(nd->chat, "AT+COPS=?", cops_prefix, cops_list_cb, cbd, g_free) > 0) return; g_free(cbd); CALLBACK_WITH_FAILURE(cb, 0, NULL, data); } static void register_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; ofono_netreg_register_cb_t cb = cbd->cb; struct ofono_error error; decode_at_error(&error, g_at_result_final_response(result)); cb(&error, cbd->data); } static void at_register_auto(struct ofono_netreg *netreg, ofono_netreg_register_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); if (g_at_chat_send(nd->chat, "AT+COPS=0", none_prefix, register_cb, cbd, g_free) > 0) return; g_free(cbd); CALLBACK_WITH_FAILURE(cb, data); } static void at_register_manual(struct ofono_netreg *netreg, const char *mcc, const char *mnc, ofono_netreg_register_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); char buf[128]; snprintf(buf, sizeof(buf), "AT+COPS=1,2,\"%s%s\"", mcc, mnc); if (g_at_chat_send(nd->chat, buf, none_prefix, register_cb, cbd, g_free) > 0) return; g_free(cbd); CALLBACK_WITH_FAILURE(cb, data); } static void csq_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; int strength; GAtResultIter iter; 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_netreg_strength_notify(netreg, at_util_convert_signal_strength(strength)); } static void calypso_csq_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; int strength; GAtResultIter iter; 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_netreg_strength_notify(netreg, at_util_convert_signal_strength(strength)); } static void option_osigq_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; int strength; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "_OSIGQ:")) return; if (!g_at_result_iter_next_number(&iter, &strength)) return; ofono_netreg_strength_notify(netreg, at_util_convert_signal_strength(strength)); } static void ifx_xhomezr_notify(GAtResult *result, gpointer user_data) { //struct ofono_netreg *netreg = user_data; const char *label; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+XHOMEZR:")) return; if (!g_at_result_iter_next_string(&iter, &label)) return; ofono_info("Home zone: %s", label); } static void ifx_xreg_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); int state; const char *band; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+XREG:")) return; if (!g_at_result_iter_next_number(&iter, &state)) return; if (!g_at_result_iter_next_unquoted_string(&iter, &band)) DBG("state %d band %s", state, band); switch (state) { case 0: /* not registered */ nd->tech = -1; break; case 1: /* registered, GPRS attached */ nd->tech = ACCESS_TECHNOLOGY_GSM; break; case 2: /* registered, EDGE attached */ nd->tech = ACCESS_TECHNOLOGY_GSM_EGPRS; break; case 3: /* registered, WCDMA attached */ nd->tech = ACCESS_TECHNOLOGY_UTRAN; break; case 4: /* registered, HSDPA attached */ nd->tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA; break; case 5: /* registered, HSUPA attached */ nd->tech = ACCESS_TECHNOLOGY_UTRAN_HSUPA; break; case 6: /* registered, HSUPA and HSDPA attached */ nd->tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA; break; case 7: /* registered, GSM */ nd->tech = ACCESS_TECHNOLOGY_GSM; break; } } static void ifx_xciev_notify(GAtResult *result, gpointer user_data) { //struct ofono_netreg *netreg = user_data; int ind; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+XCIEV:")) return; if (!g_at_result_iter_next_number(&iter, &ind)) return; DBG("ind %d", ind); /* * Radio signal strength indicators are defined for 0-7, * but this notification seems to return CSQ 0-31,99 values. * * Ignore this indication for now since it can not be trusted. */ } static void ifx_xcsq_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; int rssi, ber, strength; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+XCSQ:")) return; if (!g_at_result_iter_next_number(&iter, &rssi)) return; if (!g_at_result_iter_next_number(&iter, &ber)) return; DBG("rssi %d ber %d", rssi, ber); if (rssi == 99) strength = -1; else strength = (rssi * 100) / 31; ofono_netreg_strength_notify(netreg, strength); } static void ciev_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); int strength, ind; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CIEV:")) return; if (!g_at_result_iter_next_number(&iter, &ind)) return; if (ind != nd->signal_index) return; if (!g_at_result_iter_next_number(&iter, &strength)) return; if (strength == nd->signal_invalid) strength = -1; else strength = (strength * 100) / (nd->signal_max - nd->signal_min); ofono_netreg_strength_notify(netreg, strength); } static void telit_ciev_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); const char *signal_identifier = "rssi"; const char *ind_str; int strength; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CIEV:")) return; if (!g_at_result_iter_next_unquoted_string(&iter, &ind_str)) return; if (!g_str_equal(signal_identifier, ind_str)) return; if (!g_at_result_iter_next_number(&iter, &strength)) return; if (strength == nd->signal_invalid) strength = -1; else strength = (strength * 100) / (nd->signal_max - nd->signal_min); ofono_netreg_strength_notify(netreg, strength); } static void cinterion_ciev_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); const char *signal_identifier = "rssi"; const char *ind_str; int strength; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CIEV:")) return; if (!g_at_result_iter_next_unquoted_string(&iter, &ind_str)) return; if (!g_str_equal(signal_identifier, ind_str)) return; if (!g_at_result_iter_next_number(&iter, &strength)) return; DBG("rssi %d", strength); if (strength == nd->signal_invalid) strength = -1; else strength = (strength * 100) / (nd->signal_max - nd->signal_min); ofono_netreg_strength_notify(netreg, strength); } static void ctzv_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); const char *tz; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CTZV:")) return; if (!g_at_result_iter_next_unquoted_string(&iter, &tz)) return; DBG("tz %s", tz); nd->time.utcoff = atoi(tz) * 15 * 60; ofono_netreg_time_notify(netreg, &nd->time); } static void tlts_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); int year, mon, mday, hour, min, sec; char tz[4]; const char *time; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "*TLTS:")) return; if (!g_at_result_iter_next_string(&iter, &time)) return; DBG("time %s", time); if (sscanf(time, "%02u/%02u/%02u,%02u:%02u:%02u%s", &year, &mon, &mday, &hour, &min, &sec, tz) != 7) return; nd->time.sec = sec; nd->time.min = min; nd->time.hour = hour; nd->time.mday = mday; nd->time.mon = mon; nd->time.year = 2000 + year; nd->time.utcoff = atoi(tz) * 15 * 60; ofono_netreg_time_notify(netreg, &nd->time); } static gboolean notify_time(gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); nd->nitz_timeout = 0; ofono_netreg_time_notify(netreg, &nd->time); return FALSE; } static void ifx_ctzv_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); int year, mon, mday, hour, min, sec; const char *tz, *time; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CTZV:")) return; if (!g_at_result_iter_next_unquoted_string(&iter, &tz)) return; if (!g_at_result_iter_next_string(&iter, &time)) return; DBG("tz %s time %s", tz, time); if (sscanf(time, "%u/%u/%u,%u:%u:%u", &year, &mon, &mday, &hour, &min, &sec) != 6) return; nd->time.sec = sec; nd->time.min = min; nd->time.hour = hour; nd->time.mday = mday; nd->time.mon = mon; nd->time.year = 2000 + year; if (nd->nitz_timeout > 0) g_source_remove(nd->nitz_timeout); nd->nitz_timeout = g_timeout_add_seconds(1, notify_time, user_data); } static void ifx_ctzdst_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); int dst; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CTZDST:")) return; if (!g_at_result_iter_next_number(&iter, &dst)) return; DBG("dst %d", dst); nd->time.dst = dst; if (nd->nitz_timeout > 0) { g_source_remove(nd->nitz_timeout); nd->nitz_timeout = 0; } ofono_netreg_time_notify(netreg, &nd->time); } static void cind_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; ofono_netreg_strength_cb_t cb = cbd->cb; struct netreg_data *nd = cbd->user; int index; int strength; GAtResultIter iter; struct ofono_error error; 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, "+CIND:")) { CALLBACK_WITH_FAILURE(cb, -1, cbd->data); return; } for (index = 1; index < nd->signal_index; index++) g_at_result_iter_skip_next(&iter); g_at_result_iter_next_number(&iter, &strength); if (strength == nd->signal_invalid) strength = -1; else strength = (strength * 100) / (nd->signal_max - nd->signal_min); cb(&error, strength, cbd->data); } static void huawei_rssi_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; GAtResultIter iter; int strength; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "^RSSI:")) return; if (!g_at_result_iter_next_number(&iter, &strength)) return; ofono_netreg_strength_notify(netreg, at_util_convert_signal_strength(strength)); } static void huawei_mode_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; int mode, submode; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "^MODE:")) return; if (!g_at_result_iter_next_number(&iter, &mode)) return; if (!g_at_result_iter_next_number(&iter, &submode)) return; switch (mode) { case 3: nd->tech = ACCESS_TECHNOLOGY_GSM; break; case 5: nd->tech = ACCESS_TECHNOLOGY_UTRAN; break; } } static void huawei_hcsq_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *mode; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "^HCSQ:")) return; if (!g_at_result_iter_next_string(&iter, &mode)) return; if (!strcmp("LTE", mode)) nd->tech = ACCESS_TECHNOLOGY_EUTRAN; /* for other technologies, notification ^MODE is used */ } static void huawei_nwtime_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); int year, mon, mday, hour, min, sec; char tz[4]; const char *date, *time, *dst; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "^NWTIME:")) return; if (!g_at_result_iter_next_unquoted_string(&iter, &date)) return; if (!g_at_result_iter_next_unquoted_string(&iter, &time)) return; if (!g_at_result_iter_next_unquoted_string(&iter, &dst)) return; DBG("date %s time %s dst %s", date, time, dst); if (sscanf(date, "%u/%u/%u", &year, &mon, &mday) != 3) return; if (sscanf(time, "%u:%u:%u%s", &hour, &min, &sec, tz) != 4) return; nd->time.utcoff = atoi(tz) * 15 * 60; nd->time.dst = atoi(dst); nd->time.sec = sec; nd->time.min = min; nd->time.hour = hour; nd->time.mday = mday; nd->time.mon = mon; nd->time.year = 2000 + year; ofono_netreg_time_notify(netreg, &nd->time); } static void csq_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct cb_data *cbd = user_data; ofono_netreg_strength_cb_t cb = cbd->cb; int strength; GAtResultIter iter; struct ofono_error error; 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:")) { CALLBACK_WITH_FAILURE(cb, -1, cbd->data); return; } g_at_result_iter_next_number(&iter, &strength); DBG("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_netreg *netreg, ofono_netreg_strength_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct cb_data *cbd = cb_data_new(cb, data); cbd->user = nd; /* * If we defaulted to using CIND, then keep using it, * otherwise fall back to CSQ */ if (nd->signal_index > 0) { if (g_at_chat_send(nd->chat, "AT+CIND?", cind_prefix, cind_cb, cbd, g_free) > 0) return; } else { if (g_at_chat_send(nd->chat, "AT+CSQ", csq_prefix, csq_cb, cbd, g_free) > 0) return; } g_free(cbd); CALLBACK_WITH_FAILURE(cb, -1, data); } static void mbm_etzv_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); int year, mon, mday, hour, min, sec; const char *tz, *time, *timestamp; GAtResultIter iter; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "*ETZV:") == FALSE) return; if (g_at_result_iter_next_string(&iter, &tz) == FALSE) return; if (g_at_result_iter_next_string(&iter, &time) == FALSE) time = NULL; if (g_at_result_iter_next_string(&iter, ×tamp) == FALSE) timestamp = NULL; DBG("tz %s time %s timestamp %s", tz, time, timestamp); if (time == NULL) { year = -1; mon = -1; mday = -1; hour = -1; min = -1; sec = -1; } else { if (sscanf(time, "%u/%u/%u,%u:%u:%u", &year, &mon, &mday, &hour, &min, &sec) != 6) return; } nd->time.utcoff = atoi(tz) * 15 * 60; nd->time.sec = sec; nd->time.min = min; nd->time.hour = hour; nd->time.mday = mday; nd->time.mon = mon; nd->time.year = year; ofono_netreg_time_notify(netreg, &nd->time); } static void mbm_erinfo_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; int mode, gsm, umts; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "*ERINFO:") == FALSE) return; if (g_at_result_iter_next_number(&iter, &mode) == FALSE) return; if (g_at_result_iter_next_number(&iter, &gsm) == FALSE) return; /* * According to MBM the ERINFO unsolicited response does not contain * the mode parameter, however at least the MD300 does report it. So * we handle both 2 and 3 argument versions */ if (g_at_result_iter_next_number(&iter, &umts) == FALSE) { gsm = mode; umts = gsm; } ofono_info("network capability: GSM %d UMTS %d", gsm, umts); /* Convert to tech values from 27.007 */ switch (gsm) { case 1: /* GSM */ nd->tech = ACCESS_TECHNOLOGY_GSM; break; case 2: /* EDGE */ nd->tech = ACCESS_TECHNOLOGY_GSM_EGPRS; break; default: nd->tech = -1; } switch (umts) { case 1: /* UMTS */ nd->tech = ACCESS_TECHNOLOGY_UTRAN; break; case 2: /* UMTS + HSDPA */ nd->tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA; break; } } static void icera_nwstate_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *mccmnc, *tech, *state; int rssi; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "%NWSTATE:") == FALSE) return; if (g_at_result_iter_next_number(&iter, &rssi) == FALSE) return; if (g_at_result_iter_next_unquoted_string(&iter, &mccmnc) == FALSE) return; if (g_at_result_iter_next_unquoted_string(&iter, &tech) == FALSE) return; if (g_at_result_iter_next_unquoted_string(&iter, &state) == FALSE) return; DBG("rssi %d tech %s state %s", rssi, tech, state); /* small 'g' means CS, big 'G' means PS */ if (g_str_equal(tech, "2g") == TRUE || g_str_equal(tech, "2G") == TRUE || g_str_equal(tech, "2G-GPRS") == TRUE) { nd->tech = ACCESS_TECHNOLOGY_GSM; } else if (g_str_equal(tech, "2G-EDGE") == TRUE) { nd->tech = ACCESS_TECHNOLOGY_GSM_EGPRS; } else if (g_str_equal(tech, "3g") == TRUE || g_str_equal(tech, "3G") == TRUE || g_str_equal(tech, "R99") == TRUE) { if (g_str_equal(state, "HSDPA") == TRUE) nd->tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA; else if (g_str_equal(state, "HSUPA") == TRUE) nd->tech = ACCESS_TECHNOLOGY_UTRAN_HSUPA; else if (g_str_equal(state, "HSDPA-HSUPA") == TRUE) nd->tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA; else if (g_str_equal(state, "HSDPA-HSUPA-HSPA+") == TRUE) nd->tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA; else nd->tech = ACCESS_TECHNOLOGY_UTRAN; } else nd->tech = -1; } static int cnti_to_tech(const char *cnti) { if (g_str_equal(cnti, "GSM") == TRUE || g_str_equal(cnti, "GPRS") == TRUE) return ACCESS_TECHNOLOGY_GSM; else if (g_str_equal(cnti, "EDGE") == TRUE) return ACCESS_TECHNOLOGY_GSM_EGPRS; else if (g_str_equal(cnti, "UMTS") == TRUE) return ACCESS_TECHNOLOGY_UTRAN; else if (g_str_equal(cnti, "HSDPA") == TRUE) return ACCESS_TECHNOLOGY_UTRAN_HSDPA; else if (g_str_equal(cnti, "HSUPA") == TRUE) return ACCESS_TECHNOLOGY_UTRAN_HSUPA; return -1; } static void gobi_cnti_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *tech; int option; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "*CNTI:") == FALSE) return; if (g_at_result_iter_next_number(&iter, &option) == FALSE) return; if (option != 0) return; if (g_at_result_iter_next_unquoted_string(&iter, &tech) == FALSE) return; nd->tech = cnti_to_tech(tech); } static void nw_cnti_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *tech; int option; g_at_result_iter_init(&iter, result); if (g_at_result_iter_next(&iter, "$CNTI:") == FALSE) return; if (g_at_result_iter_next_number(&iter, &option) == FALSE) return; if (option != 0) return; if (g_at_result_iter_next_unquoted_string(&iter, &tech) == FALSE) return; nd->tech = cnti_to_tech(tech); } static void cnti_query_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct tech_query *tq = user_data; struct netreg_data *nd = ofono_netreg_get_data(tq->netreg); ofono_netreg_status_notify(tq->netreg, tq->status, tq->lac, tq->ci, nd->tech); } static void zte_query_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct tech_query *tq = user_data; int tech; if (ok) tech = zte_parse_tech(result); else tech = -1; ofono_netreg_status_notify(tq->netreg, tq->status, tq->lac, tq->ci, tech); } static void option_query_tech_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct tech_query *tq = user_data; int tech; if (ok) tech = option_parse_tech(result); else tech = -1; ofono_netreg_status_notify(tq->netreg, tq->status, tq->lac, tq->ci, tech); } static void creg_notify(GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; int status, lac, ci, tech; struct netreg_data *nd = ofono_netreg_get_data(netreg); struct tech_query *tq; if (at_util_parse_reg_unsolicited(result, "+CREG:", &status, &lac, &ci, &tech, nd->vendor) == FALSE) return; if (status != 1 && status != 5) goto notify; tq = g_try_new0(struct tech_query, 1); if (tq == NULL) goto notify; tq->status = status; tq->lac = lac; tq->ci = ci; tq->netreg = netreg; switch (nd->vendor) { case OFONO_VENDOR_GOBI: if (g_at_chat_send(nd->chat, "AT*CNTI=0", none_prefix, cnti_query_tech_cb, tq, g_free) > 0) return; break; case OFONO_VENDOR_NOVATEL: if (g_at_chat_send(nd->chat, "AT$CNTI=0", none_prefix, cnti_query_tech_cb, tq, g_free) > 0) return; break; case OFONO_VENDOR_ZTE: if (g_at_chat_send(nd->chat, "AT+ZPAS?", zpas_prefix, zte_query_tech_cb, tq, g_free) > 0) return; break; case OFONO_VENDOR_OPTION_HSO: if (g_at_chat_send(nd->chat, "AT_OCTI?;_OUWCTI?", option_tech_prefix, option_query_tech_cb, tq, g_free) > 0) return; break; case OFONO_VENDOR_CINTERION: if (g_at_chat_send(nd->chat, "AT^SMONI", smoni_prefix, cinterion_query_tech_cb, tq, g_free) > 0) return; break; } g_free(tq); if ((status == 1 || status == 5) && tech == -1) tech = nd->tech; notify: ofono_netreg_status_notify(netreg, status, lac, ci, tech); } static void at_cmer_not_supported(struct ofono_netreg *netreg) { ofono_error("+CMER not supported by this modem. If this is an error" " please submit patches to support this hardware"); ofono_netreg_remove(netreg); } static void at_cmer_set_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); if (!ok) { at_cmer_not_supported(netreg); return; } /* * Telit uses strings instead of numbers to identify indicators * in a +CIEV URC. * Handle them in a separate function to keep the code clean. */ if (nd->vendor == OFONO_VENDOR_TELIT) g_at_chat_register(nd->chat, "+CIEV:", telit_ciev_notify, FALSE, netreg, NULL); else g_at_chat_register(nd->chat, "+CIEV:", ciev_notify, FALSE, netreg, NULL); g_at_chat_register(nd->chat, "+CREG:", creg_notify, FALSE, netreg, NULL); ofono_netreg_register(netreg); } static inline char wanted_cmer(int supported, const char *pref) { while (*pref) { if (supported & (1 << (*pref - '0'))) return *pref; pref++; } return '\0'; } static inline ofono_bool_t append_cmer_element(char *buf, int *len, int cap, const char *wanted, ofono_bool_t last) { char setting = wanted_cmer(cap, wanted); if (!setting) return FALSE; buf[*len] = setting; if (last) buf[*len + 1] = '\0'; else buf[*len + 1] = ','; *len += 2; return TRUE; } static ofono_bool_t build_cmer_string(char *buf, int *cmer_opts, struct netreg_data *nd) { const char *ind; int len = sprintf(buf, "AT+CMER="); const char *mode; DBG(""); switch (nd->vendor) { case OFONO_VENDOR_UBLOX_TOBY_L2: /* UBX-13002752 R33: TOBY L2 doesn't support mode 2 and 3 */ mode = "1"; break; default: mode = "3"; break; } /* * Forward unsolicited result codes directly to the TE; * TA‑TE link specific inband technique used to embed result codes and * data when TA is in on‑line data mode */ if (!append_cmer_element(buf, &len, cmer_opts[0], mode, FALSE)) return FALSE; /* No keypad event reporting */ if (!append_cmer_element(buf, &len, cmer_opts[1], "0", FALSE)) return FALSE; /* No display event reporting */ if (!append_cmer_element(buf, &len, cmer_opts[2], "0", FALSE)) return FALSE; switch (nd->vendor) { case OFONO_VENDOR_TELIT: /* * Telit does not support mode 1. * All indicator events shall be directed from TA to TE. */ ind = "2"; break; default: /* * Only those indicator events, which are not caused by +CIND * shall be indicated by the TA to the TE. */ ind = "1"; break; } /* * Indicator event reporting using URC +CIEV: ,. * indicates the indicator order number (as specified for +CIND) * and is the new value of indicator. */ if (!append_cmer_element(buf, &len, cmer_opts[3], ind, TRUE)) return FALSE; return TRUE; } static void at_cmer_query_cb(ofono_bool_t ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; int cmer_opts_cnt = 5; /* See 27.007 Section 8.10 */ int cmer_opts[cmer_opts_cnt]; int opt; int mode; char buf[128]; if (!ok) goto error; memset(cmer_opts, 0, sizeof(cmer_opts)); g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CMER:")) goto error; for (opt = 0; opt < cmer_opts_cnt; opt++) { int min, max; if (!g_at_result_iter_open_list(&iter)) goto error; while (g_at_result_iter_next_range(&iter, &min, &max)) { for (mode = min; mode <= max; mode++) cmer_opts[opt] |= 1 << mode; } if (!g_at_result_iter_close_list(&iter)) goto error; } if (build_cmer_string(buf, cmer_opts, nd) == FALSE) goto error; g_at_chat_send(nd->chat, buf, cmer_prefix, at_cmer_set_cb, netreg, NULL); return; error: at_cmer_not_supported(netreg); } static void cind_support_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GAtResultIter iter; const char *str; char *signal_identifier = "signal"; int index; int min = 0; int max = 0; int tmp_min, tmp_max, invalid; int i, len; char buf[256]; if (!ok) goto error; g_at_result_iter_init(&iter, result); if (!g_at_result_iter_next(&iter, "+CIND:")) goto error; index = 1; /* * Telit encapsulates the CIND=? tokens with braces * so we need to skip them */ if (nd->vendor == OFONO_VENDOR_TELIT) { g_at_result_iter_open_list(&iter); signal_identifier = "rssi"; } while (g_at_result_iter_open_list(&iter)) { /* Reset invalid default value for every token */ invalid = 99; if (!g_at_result_iter_next_string(&iter, &str)) goto error; if (!g_at_result_iter_open_list(&iter)) goto error; while (g_at_result_iter_next_range(&iter, &tmp_min, &tmp_max)) { if (tmp_min != tmp_max) { min = tmp_min; max = tmp_max; } else invalid = tmp_min; } if (!g_at_result_iter_close_list(&iter)) goto error; if (!g_at_result_iter_close_list(&iter)) goto error; if (g_str_equal(signal_identifier, str) == TRUE) { nd->signal_index = index; nd->signal_min = min; nd->signal_max = max; nd->signal_invalid = invalid; } index += 1; } if (nd->vendor == OFONO_VENDOR_TELIT) g_at_result_iter_close_list(&iter); if (nd->signal_index == 0) goto error; /* Turn off all CIEV indicators except the signal indicator */ len = sprintf(buf, "AT+CIND="); for (i = 1; i < index - 1; i++) len += sprintf(buf + len, i == nd->signal_index ? "1," : "0,"); len += sprintf(buf + len, i == nd->signal_index ? "1" : "0"); g_at_chat_send(nd->chat, buf, NULL, NULL, NULL, NULL); switch (nd->vendor) { case OFONO_VENDOR_MBM: /* * MBM devices report 'CMER: (0,3),(0,2),0,(0-1),0' when * +CMER=? is executed, which cannot be parsed. Simply * send the desired settings in this case. */ g_at_chat_send(nd->chat, "AT+CMER=3,0,0,1", none_prefix, at_cmer_set_cb, netreg, NULL); break; default: g_at_chat_send(nd->chat, "AT+CMER=?", cmer_prefix, at_cmer_query_cb, netreg, NULL); break; } return; error: ofono_error("This driver is not setup with Signal Strength reporting" " via CIND indications, please write proper netreg" " handling for this device"); ofono_netreg_remove(netreg); } static void at_creg_set_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); if (!ok) { ofono_error("Unable to initialize Network Registration"); ofono_netreg_remove(netreg); return; } switch (nd->vendor) { case OFONO_VENDOR_SIMCOM: /* Register for CSQ changes */ g_at_chat_send(nd->chat, "AT+AUTOCSQ=1,1", none_prefix, NULL, NULL, NULL); g_at_chat_register(nd->chat, "+CSQ:", csq_notify, FALSE, netreg, NULL); break; case OFONO_VENDOR_PHONESIM: g_at_chat_register(nd->chat, "+CSQ:", csq_notify, FALSE, netreg, NULL); break; case OFONO_VENDOR_CALYPSO: g_at_chat_send(nd->chat, "AT%CSQ=1", none_prefix, NULL, NULL, NULL); g_at_chat_register(nd->chat, "%CSQ:", calypso_csq_notify, FALSE, netreg, NULL); break; case OFONO_VENDOR_OPTION_HSO: g_at_chat_send(nd->chat, "AT_OSSYS=1", none_prefix, NULL, NULL, NULL); g_at_chat_send(nd->chat, "AT_OSQI=1", none_prefix, NULL, NULL, NULL); g_at_chat_register(nd->chat, "_OSIGQ:", option_osigq_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT_OSSYS?", none_prefix, NULL, NULL, NULL); g_at_chat_send(nd->chat, "AT_OSQI?", none_prefix, NULL, NULL, NULL); /* Register for network time update reports */ g_at_chat_register(nd->chat, "+CTZV:", ctzv_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT+CTZR=1", none_prefix, NULL, NULL, NULL); break; case OFONO_VENDOR_MBM: /* Enable network registration updates */ g_at_chat_send(nd->chat, "AT*E2REG=1", none_prefix, NULL, NULL, NULL); g_at_chat_send(nd->chat, "AT*EREG=2", none_prefix, NULL, NULL, NULL); g_at_chat_send(nd->chat, "AT*EPSB=1", none_prefix, NULL, NULL, NULL); /* Register for network technology updates */ g_at_chat_send(nd->chat, "AT*ERINFO=1", none_prefix, NULL, NULL, NULL); g_at_chat_register(nd->chat, "*ERINFO:", mbm_erinfo_notify, FALSE, netreg, NULL); /* Register for network time update reports */ g_at_chat_register(nd->chat, "*ETZV:", mbm_etzv_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT*ETZR=2", none_prefix, NULL, NULL, NULL); g_at_chat_send(nd->chat, "AT+CIND=?", cind_prefix, cind_support_cb, netreg, NULL); return; case OFONO_VENDOR_GOBI: /* * Gobi devices don't support unsolicited notifications * of technology changes, but register a handle for * CNTI so we get notified by any query. */ g_at_chat_register(nd->chat, "*CNTI:", gobi_cnti_notify, FALSE, netreg, NULL); break; case OFONO_VENDOR_NOVATEL: /* * Novatel doesn't support unsolicited notifications * of technology changes, but register a handle for * CNTI so we get notified by any query. */ g_at_chat_register(nd->chat, "$CNTI:", nw_cnti_notify, FALSE, netreg, NULL); break; case OFONO_VENDOR_HUAWEI: /* Register for RSSI reports */ g_at_chat_register(nd->chat, "^RSSI:", huawei_rssi_notify, FALSE, netreg, NULL); /* Register for system mode reports */ g_at_chat_register(nd->chat, "^MODE:", huawei_mode_notify, FALSE, netreg, NULL); /* Register for 4G system mode reports */ g_at_chat_register(nd->chat, "^HCSQ:", huawei_hcsq_notify, FALSE, netreg, NULL); /* Register for network time reports */ g_at_chat_register(nd->chat, "^NWTIME:", huawei_nwtime_notify, FALSE, netreg, NULL); break; case OFONO_VENDOR_IFX: /* Register for specific signal strength reports */ g_at_chat_register(nd->chat, "+XCIEV:", ifx_xciev_notify, FALSE, netreg, NULL); g_at_chat_register(nd->chat, "+XCSQ:", ifx_xcsq_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT+XCSQ=1", none_prefix, NULL, NULL, NULL); g_at_chat_send(nd->chat, "AT+XMER=1", none_prefix, NULL, NULL, NULL); /* Register for network technology updates */ g_at_chat_register(nd->chat, "+XREG:", ifx_xreg_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT+XREG=1", none_prefix, NULL, NULL, NULL); g_at_chat_send(nd->chat, "AT+XBANDSEL?", none_prefix, NULL, NULL, NULL); g_at_chat_send(nd->chat, "AT+XUBANDSEL?", none_prefix, NULL, NULL, NULL); /* Register for home zone reports */ g_at_chat_register(nd->chat, "+XHOMEZR:", ifx_xhomezr_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT+XHOMEZR=1", none_prefix, NULL, NULL, NULL); /* Register for network time update reports */ g_at_chat_register(nd->chat, "+CTZV:", ifx_ctzv_notify, FALSE, netreg, NULL); g_at_chat_register(nd->chat, "+CTZDST:", ifx_ctzdst_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT+CTZR=1", none_prefix, NULL, NULL, NULL); break; case OFONO_VENDOR_ZTE: /* Register for network time update reports */ g_at_chat_register(nd->chat, "+CTZV:", ctzv_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT+CTZR=1", none_prefix, NULL, NULL, NULL); break; case OFONO_VENDOR_ICERA: /* Register for network technology updates */ g_at_chat_register(nd->chat, "%NWSTATE:", icera_nwstate_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT%NWSTATE=1", none_prefix, NULL, NULL, NULL); /* Register for radio access technology updates */ g_at_chat_send(nd->chat, "AT*TRATD=1", none_prefix, NULL, NULL, NULL); /* Register for network time update reports */ g_at_chat_register(nd->chat, "*TLTS:", tlts_notify, FALSE, netreg, NULL); g_at_chat_send(nd->chat, "AT*TLTS=1", none_prefix, NULL, NULL, NULL); break; case OFONO_VENDOR_CINTERION: /* * We can't set rssi bounds from Cinterion responses * so set them up to specified values here * * Cinterion rssi signal strength specified as: * 0 <= -112dBm * 1 - 4 signal strengh in 15 dB steps * 5 >= -51 dBm * 99 not known or undetectable */ nd->signal_min = 0; nd->signal_max = 5; nd->signal_invalid = 99; /* Register for specific signal strength reports */ g_at_chat_send(nd->chat, "AT^SIND=\"rssi\",1", none_prefix, NULL, NULL, NULL); g_at_chat_register(nd->chat, "+CIEV:", cinterion_ciev_notify, FALSE, netreg, NULL); break; case OFONO_VENDOR_NOKIA: case OFONO_VENDOR_SAMSUNG: /* Signal strength reporting via CIND is not supported */ break; default: g_at_chat_send(nd->chat, "AT+CIND=?", cind_prefix, cind_support_cb, netreg, NULL); return; } g_at_chat_register(nd->chat, "+CREG:", creg_notify, FALSE, netreg, NULL); ofono_netreg_register(netreg); } static void at_creg_test_cb(gboolean ok, GAtResult *result, gpointer user_data) { struct ofono_netreg *netreg = user_data; struct netreg_data *nd = ofono_netreg_get_data(netreg); gint range[2]; GAtResultIter iter; int creg1 = 0; int creg2 = 0; if (!ok) goto error; g_at_result_iter_init(&iter, result); retry: if (!g_at_result_iter_next(&iter, "+CREG:")) goto error; if (!g_at_result_iter_open_list(&iter)) goto retry; while (g_at_result_iter_next_range(&iter, &range[0], &range[1])) { if (1 >= range[0] && 1 <= range[1]) creg1 = 1; if (2 >= range[0] && 2 <= range[1]) creg2 = 1; } g_at_result_iter_close_list(&iter); if (creg2) { g_at_chat_send(nd->chat, "AT+CREG=2", none_prefix, at_creg_set_cb, netreg, NULL); return; } if (creg1) { g_at_chat_send(nd->chat, "AT+CREG=1", none_prefix, at_creg_set_cb, netreg, NULL); return; } error: ofono_error("Unable to initialize Network Registration"); ofono_netreg_remove(netreg); } static int at_netreg_probe(struct ofono_netreg *netreg, unsigned int vendor, void *data) { GAtChat *chat = data; struct netreg_data *nd; nd = g_new0(struct netreg_data, 1); nd->chat = g_at_chat_clone(chat); nd->vendor = vendor; nd->tech = -1; nd->time.sec = -1; nd->time.min = -1; nd->time.hour = -1; nd->time.mday = -1; nd->time.mon = -1; nd->time.year = -1; nd->time.dst = 0; nd->time.utcoff = 0; ofono_netreg_set_data(netreg, nd); g_at_chat_send(nd->chat, "AT+CREG=?", creg_prefix, at_creg_test_cb, netreg, NULL); return 0; } static void at_netreg_remove(struct ofono_netreg *netreg) { struct netreg_data *nd = ofono_netreg_get_data(netreg); if (nd->nitz_timeout) g_source_remove(nd->nitz_timeout); ofono_netreg_set_data(netreg, NULL); g_at_chat_unref(nd->chat); g_free(nd); } static const struct ofono_netreg_driver driver = { .name = "atmodem", .probe = at_netreg_probe, .remove = at_netreg_remove, .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, .strength = at_signal_strength, }; void at_netreg_init(void) { ofono_netreg_driver_register(&driver); } void at_netreg_exit(void) { ofono_netreg_driver_unregister(&driver); }