blob: cc702c2c205b40355e841e431e24d1d4530a31cf [file] [log] [blame]
/*
*
* 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 <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <glib.h>
#include <ofono/log.h>
#include <ofono/modem.h>
#include <ofono/netreg.h>
#include "gatchat.h"
#include "gatresult.h"
#include "common.h"
#include "atmodem.h"
#include "vendor.h"
#include "network-registration.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 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 gemalto_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 at_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 gemalto_query_tech_cb(gboolean ok, GAtResult *result,
gpointer user_data)
{
struct tech_query *tq = user_data;
int tech;
tech = gemalto_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 at_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 at_netreg_data *nd = ofono_netreg_get_data(netreg);
if (ok)
nd->tech = option_parse_tech(result);
else
nd->tech = -1;
}
void at_registration_status(struct ofono_netreg *netreg,
ofono_netreg_status_cb_t cb,
void *data)
{
struct at_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 at_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 at_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);
}
void at_current_operator(struct ofono_netreg *netreg,
ofono_netreg_operator_cb_t cb, void *data)
{
struct at_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);
}
void at_list_operators(struct ofono_netreg *netreg,
ofono_netreg_operator_list_cb_t cb, void *data)
{
struct at_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);
}
void at_register_auto(struct ofono_netreg *netreg,
ofono_netreg_register_cb_t cb, void *data)
{
struct at_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);
}
void at_register_manual(struct ofono_netreg *netreg,
const char *mcc, const char *mnc,
ofono_netreg_register_cb_t cb, void *data)
{
struct at_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 at_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 at_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 at_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 gemalto_ciev_notify(GAtResult *result, gpointer user_data)
{
struct ofono_netreg *netreg = user_data;
struct at_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 at_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 at_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 at_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 at_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 at_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 at_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 at_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 at_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 at_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);
}
void at_signal_strength(struct ofono_netreg *netreg,
ofono_netreg_strength_cb_t cb, void *data)
{
struct at_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 at_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, &timestamp) == 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 at_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 at_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 at_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 at_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 at_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 at_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_GEMALTO:
if (g_at_chat_send(nd->chat, "AT^SMONI",
smoni_prefix,
gemalto_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 at_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 at_netreg_data *nd)
{
const char *ind;
int len = sprintf(buf, "AT+CMER=");
const char *mode;
DBG("");
switch (nd->vendor) {
case OFONO_VENDOR_UBLOX:
/* For all u-blox models, mode 3 is equivalent to mode 1;
* since some models do not support setting modes 2 nor 3
* (see UBX-13002752), we prefer mode 1 for all models.
*/
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: <ind>,<value>.
* <ind> indicates the indicator order number (as specified for +CIND)
* and <value> 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 at_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 at_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 at_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_GEMALTO:
/*
* We can't set rssi bounds from Gemalto responses
* so set them up to specified values here
*
* Gemalto 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:",
gemalto_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 at_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 at_netreg_data *nd;
nd = g_new0(struct at_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;
}
void at_netreg_remove(struct ofono_netreg *netreg)
{
struct at_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);
}