ofono/drivers/atmodem/network-registration.c

2179 lines
51 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
*
* 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
#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <glib.h>
#include <ofono/log.h>
#include <ofono/modem.h>
#include <ofono/netreg.h>
#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, &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 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;
* TATE link specific inband technique used to embed result codes and
* data when TA is in online 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 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);
}