/* * * oFono - Open Source Telephony * * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "isimodem.h" #include "isiutil.h" #include "network.h" #include "debug.h" struct reg_info { uint8_t status; uint8_t mode; }; struct gsm_info { uint16_t lac; uint32_t ci; uint8_t egprs; uint8_t hsdpa; uint8_t hsupa; }; struct rat_info { uint8_t rat; uint8_t compact; }; struct network_time { uint8_t year; uint8_t mon; uint8_t mday; uint8_t hour; uint8_t min; uint8_t sec; uint8_t utc; uint8_t dst; }; struct netreg_data { GIsiClient *client; GIsiClient *pn_network; GIsiClient *pn_modem_network; struct reg_info reg; struct gsm_info gsm; struct rat_info rat; GIsiVersion version; char nitz_name[OFONO_MAX_OPERATOR_NAME_LENGTH + 1]; }; static inline guint8 *mccmnc_to_bcd(const char *mcc, const char *mnc, guint8 *bcd) { bcd[0] = (mcc[0] - '0') | (mcc[1] - '0') << 4; bcd[1] = (mcc[2] - '0'); bcd[1] |= (mnc[2] == '\0' ? 0x0f : (mnc[2] - '0')) << 4; bcd[2] = (mnc[0] - '0') | (mnc[1] - '0') << 4; return bcd; } static inline int isi_status_to_at_status(struct reg_info *reg) { switch (reg->status) { case NET_REG_STATUS_NOSERV: case NET_REG_STATUS_NOSERV_NOTSEARCHING: case NET_REG_STATUS_NOSERV_NOSIM: case NET_REG_STATUS_POWER_OFF: case NET_REG_STATUS_NSPS: case NET_REG_STATUS_NSPS_NO_COVERAGE: return 0; case NET_REG_STATUS_HOME: return 1; case NET_REG_STATUS_NOSERV_SEARCHING: return 2; case NET_REG_STATUS_NOSERV_SIM_REJECTED_BY_NW: return 3; case NET_REG_STATUS_ROAM_BLINK: case NET_REG_STATUS_ROAM: return 5; } return 4; } static inline int isi_to_at_tech(struct rat_info *rat, struct gsm_info *gsm) { int tech = -1; if (rat == NULL || gsm == NULL) return -1; if (rat->rat == NET_GSM_RAT) tech = 0; if (rat->compact) tech = 1; if (rat->rat == NET_UMTS_RAT) tech = 2; if (gsm->egprs) tech = 3; if (gsm->hsdpa) tech = 4; if (gsm->hsupa) tech = 5; if (gsm->hsdpa && gsm->hsupa) tech = 6; return tech; } static gboolean check_response_status(const GIsiMessage *msg, uint8_t msgid) { uint8_t cause; if (g_isi_msg_error(msg) < 0) { DBG("Error: %s", g_isi_msg_strerror(msg)); return FALSE; } if (g_isi_msg_id(msg) != msgid) return FALSE; if (!g_isi_msg_data_get_byte(msg, 0, &cause)) return FALSE; if (cause != NET_CAUSE_OK) { DBG("Request failed: %s", net_isi_cause_name(cause)); return FALSE; } return TRUE; } static gboolean parse_common_info(GIsiSubBlockIter *iter, struct reg_info *reg) { return reg && g_isi_sb_iter_get_byte(iter, ®->status, 2) && g_isi_sb_iter_get_byte(iter, ®->mode, 3); } static gboolean parse_gsm_info(GIsiSubBlockIter *iter, struct gsm_info *gsm) { return gsm && g_isi_sb_iter_get_word(iter, &gsm->lac, 2) && g_isi_sb_iter_get_dword(iter, &gsm->ci, 4) && g_isi_sb_iter_get_byte(iter, &gsm->egprs, 17) && g_isi_sb_iter_get_byte(iter, &gsm->hsdpa, 20) && g_isi_sb_iter_get_byte(iter, &gsm->hsupa, 21); } static void reg_status_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_netreg *netreg = data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GIsiSubBlockIter iter; if (netreg == NULL || nd == NULL) return; if (g_isi_msg_id(msg) != NET_REG_STATUS_IND && g_isi_msg_id(msg) != NET_MODEM_REG_STATUS_IND) return; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { switch (g_isi_sb_iter_get_id(&iter)) { case NET_REG_INFO_COMMON: if (!parse_common_info(&iter, &nd->reg)) return; break; case NET_GSM_REG_INFO: if (!parse_gsm_info(&iter, &nd->gsm)) return; break; } } ofono_netreg_status_notify(netreg, isi_status_to_at_status(&nd->reg), nd->gsm.lac, nd->gsm.ci, isi_to_at_tech(&nd->rat, &nd->gsm)); } static gboolean parse_rat_info(GIsiSubBlockIter *iter, struct rat_info *rat) { uint8_t len; if (!g_isi_sb_iter_get_byte(iter, &rat->rat, 2)) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &len, 3)) return FALSE; if (len != 0 && !g_isi_sb_iter_get_byte(iter, &rat->compact, 4)) return FALSE; return TRUE; } static void rat_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_netreg *netreg = data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GIsiSubBlockIter iter; if (nd == NULL || g_isi_msg_id(msg) != NET_RAT_IND) return; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { if (g_isi_sb_iter_get_id(&iter) != NET_RAT_INFO) continue; if (!parse_rat_info(&iter, &nd->rat)) return; } ofono_netreg_status_notify(netreg, isi_status_to_at_status(&nd->reg), nd->gsm.lac, nd->gsm.ci, isi_to_at_tech(&nd->rat, &nd->gsm)); } static void reg_status_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; struct ofono_netreg *netreg = cbd->user; struct netreg_data *nd = ofono_netreg_get_data(netreg); ofono_netreg_status_cb_t cb = cbd->cb; GIsiSubBlockIter iter; if (!check_response_status(msg, NET_MODEM_REG_STATUS_GET_RESP) && !check_response_status(msg, NET_REG_STATUS_GET_RESP)) goto error; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { switch (g_isi_sb_iter_get_id(&iter)) { case NET_REG_INFO_COMMON: if (!parse_common_info(&iter, &nd->reg)) goto error; break; case NET_GSM_REG_INFO: if (!parse_gsm_info(&iter, &nd->gsm)) goto error; break; } } CALLBACK_WITH_SUCCESS(cb, isi_status_to_at_status(&nd->reg), nd->gsm.lac, nd->gsm.ci, isi_to_at_tech(&nd->rat, &nd->gsm), cbd->data); g_free(cbd); return; error: CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, cbd->data); g_free(cbd); } static void rat_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; struct ofono_netreg *netreg = cbd->user; struct netreg_data *nd = ofono_netreg_get_data(netreg); ofono_netreg_status_cb_t cb = cbd->cb; uint8_t req[] = { NET_REG_STATUS_GET_REQ, }; GIsiSubBlockIter iter; if (cbd == NULL || nd == NULL) goto error; if (!check_response_status(msg, NET_RAT_RESP)) goto error; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { if (g_isi_sb_iter_get_id(&iter) != NET_RAT_INFO) continue; if (!parse_rat_info(&iter, &nd->rat)) goto error; } if (g_isi_client_resource(nd->client) == PN_MODEM_NETWORK || nd->version.major < 14) req[0] = NET_MODEM_REG_STATUS_GET_REQ; if (g_isi_client_send(nd->client, req, sizeof(req), reg_status_resp_cb, cbd, NULL)) return; error: CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, data); g_free(cbd); } static void isi_registration_status(struct ofono_netreg *netreg, ofono_netreg_status_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct isi_cb_data *cbd = isi_cb_data_new(netreg, cb, data); /* * Current technology depends on the current RAT as well as * the services reported by the current cell. Therefore we * need a pair of queries to deduce the full registration * status: first query for the RAT then the actual * registration status. */ const uint8_t rat[] = { NET_RAT_REQ, NET_CURRENT_RAT }; if (nd == NULL || cbd == NULL) goto error; if (g_isi_client_send(nd->client, rat, sizeof(rat), rat_resp_cb, cbd, NULL)) return; error: CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, data); g_free(cbd); } static void cell_info_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; ofono_netreg_operator_cb_t cb = cbd->cb; struct ofono_netreg *netreg = cbd->user; struct netreg_data *nd = ofono_netreg_get_data(netreg); struct ofono_network_operator op; GIsiSubBlockIter iter; memset(&op, 0, sizeof(struct ofono_network_operator)); if (!check_response_status(msg, NET_CELL_INFO_GET_RESP)) goto error; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { switch (g_isi_sb_iter_get_id(&iter)) { case NET_GSM_CELL_INFO: if (!g_isi_sb_iter_get_oper_code(&iter, op.mcc, op.mnc, 12)) goto error; op.tech = 0; break; case NET_WCDMA_CELL_INFO: if (!g_isi_sb_iter_get_oper_code(&iter, op.mcc, op.mnc, 12)) goto error; op.tech = 2; break; } } if (nd->nitz_name[0] != '\0') strcpy(op.name, nd->nitz_name); CALLBACK_WITH_SUCCESS(cb, &op, cbd->data); return; error: CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); } static void create_cell_info_get_req(struct ofono_netreg *netreg, ofono_netreg_operator_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct isi_cb_data *cbd = isi_cb_data_new(netreg, cb, data); const uint8_t msg[] = { NET_CELL_INFO_GET_REQ, }; if (cbd == NULL || nd == NULL) goto error; if (g_isi_client_send(nd->client, msg, sizeof(msg), cell_info_resp_cb, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, NULL, data); g_free(cbd); } static void name_get_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; ofono_netreg_operator_cb_t cb = cbd->cb; struct ofono_network_operator op; GIsiSubBlockIter iter; uint8_t len = 0; char *tag = NULL; memset(&op, 0, sizeof(struct ofono_network_operator)); if (!check_response_status(msg, NET_OLD_OPER_NAME_READ_RESP) && !check_response_status(msg, NET_OPER_NAME_READ_RESP)) goto error; for (g_isi_sb_iter_init(&iter, msg, 6); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { switch (g_isi_sb_iter_get_id(&iter)) { case NET_GSM_OPERATOR_INFO: if (!g_isi_sb_iter_get_oper_code(&iter, op.mcc, op.mnc, 2)) goto error; break; case NET_OPER_NAME_INFO: if (!g_isi_sb_iter_get_byte(&iter, &len, 3)) goto error; /* Name is UCS-2 encoded */ len *= 2; if (!g_isi_sb_iter_get_alpha_tag(&iter, &tag, len, 4)) goto error; strncpy(op.name, tag, OFONO_MAX_OPERATOR_NAME_LENGTH); op.name[OFONO_MAX_OPERATOR_NAME_LENGTH] = '\0'; g_free(tag); break; } } CALLBACK_WITH_SUCCESS(cb, &op, cbd->data); return; error: CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); } static void create_name_get_req(struct ofono_netreg *netreg, ofono_netreg_operator_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct isi_cb_data *cbd = isi_cb_data_new(netreg, cb, data); uint8_t msg[] = { NET_OPER_NAME_READ_REQ, NET_HARDCODED_LATIN_OPER_NAME, OFONO_MAX_OPERATOR_NAME_LENGTH, 0x00, 0x00, /* Index not used */ 0x00, /* Filler */ 0x00, /* No sub-blocks */ }; if (cbd == NULL || nd == NULL) goto error; if (nd->version.major < 14) msg[0] = NET_OLD_OPER_NAME_READ_REQ; if (g_isi_client_send(nd->client, msg, sizeof(msg), name_get_resp_cb, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, NULL, data); g_free(cbd); } static void isi_current_operator(struct ofono_netreg *netreg, ofono_netreg_operator_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); if (g_isi_client_resource(nd->client) == PN_MODEM_NETWORK) create_cell_info_get_req(netreg, cb, data); else create_name_get_req(netreg, cb, data); } static void available_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; ofono_netreg_operator_list_cb_t cb = cbd->cb; struct ofono_network_operator *list = NULL; GIsiSubBlockIter iter; uint8_t sb_count; int total = 0; int common = 0; int detail = 0; if (!check_response_status(msg, NET_MODEM_AVAILABLE_GET_RESP) && !check_response_status(msg, NET_AVAILABLE_GET_RESP)) goto error; if (!g_isi_msg_data_get_byte(msg, 1, &sb_count)) goto error; /* Each description of an operator has a pair of sub-blocks */ total = sb_count / 2; list = alloca(total * sizeof(struct ofono_network_operator)); for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { struct ofono_network_operator *op; char *tag = NULL; uint8_t taglen = 0; uint8_t status = 0; uint8_t umts = 0; switch (g_isi_sb_iter_get_id(&iter)) { case NET_MODEM_AVAIL_NETWORK_INFO_COMMON: if (!g_isi_sb_iter_get_byte(&iter, &status, 2)) goto error; op = list + common++; op->status = status; /* * FIXME: PN_MODEM_NETWORK provides no name * tags. We need access to the GSMA name list * here, or alternatively, core has to fill in * the blanks. */ op->name[0] = '\0'; break; case NET_AVAIL_NETWORK_INFO_COMMON: if (!g_isi_sb_iter_get_byte(&iter, &status, 2)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &taglen, 5)) goto error; if (!g_isi_sb_iter_get_alpha_tag(&iter, &tag, taglen * 2, 6)) goto error; op = list + common++; op->status = status; strncpy(op->name, tag, OFONO_MAX_OPERATOR_NAME_LENGTH); op->name[OFONO_MAX_OPERATOR_NAME_LENGTH] = '\0'; g_free(tag); break; /* case NET_MODEM_DETAILED_NETWORK_INFO: */ case NET_DETAILED_NETWORK_INFO: op = list + detail++; if (!g_isi_sb_iter_get_oper_code(&iter, op->mcc, op->mnc, 2)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &umts, 7)) goto error; op->tech = umts ? 2 : 3; break; } } if (common == detail && detail == total) { CALLBACK_WITH_SUCCESS(cb, total, list, cbd->data); return; } error: CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); } static void isi_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 isi_cb_data *cbd = isi_cb_data_new(netreg, cb, data); uint8_t msg[] = { NET_AVAILABLE_GET_REQ, NET_MANUAL_SEARCH, 0x01, /* Sub-block count */ NET_GSM_BAND_INFO, 0x04, /* Sub-block length */ NET_GSM_BAND_ALL_SUPPORTED_BANDS, 0x00 }; if (cbd == NULL || nd == NULL) goto error; if (g_isi_client_resource(nd->client) == PN_MODEM_NETWORK || nd->version.major < 14) msg[0] = NET_MODEM_AVAILABLE_GET_REQ; if (g_isi_client_send_with_timeout(nd->client, msg, sizeof(msg), NETWORK_SCAN_TIMEOUT, available_resp_cb, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, 0, NULL, data); g_free(cbd); } static void set_auto_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; struct netreg_data *nd = cbd->user; ofono_netreg_register_cb_t cb = cbd->cb; if (!check_response_status(msg, NET_SET_RESP)) { CALLBACK_WITH_FAILURE(cb, cbd->data); return; } nd->reg.mode = NET_SELECT_MODE_AUTOMATIC; CALLBACK_WITH_SUCCESS(cb, cbd->data); } static void isi_register_auto(struct ofono_netreg *netreg, ofono_netreg_register_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct isi_cb_data *cbd = isi_cb_data_new(netreg, cb, data); const unsigned char msg[] = { NET_SET_REQ, 0x00, /* Registered in another protocol? */ 0x01, /* Sub-block count */ NET_OPERATOR_INFO_COMMON, 0x04, /* Sub-block length */ nd->reg.mode == NET_SELECT_MODE_AUTOMATIC ? NET_SELECT_MODE_USER_RESELECTION : NET_SELECT_MODE_AUTOMATIC, 0x00 /* Index not used */ }; if (nd == NULL || cbd == NULL) goto error; if (g_isi_client_send_with_timeout(nd->client, msg, sizeof(msg), NETWORK_SET_TIMEOUT, set_auto_resp_cb, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, data); g_free(cbd); } static void set_manual_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; struct ofono_netreg *netreg = cbd->user; struct netreg_data *nd = ofono_netreg_get_data(netreg); ofono_netreg_register_cb_t cb = cbd->cb; if (!check_response_status(msg, NET_SET_RESP)) { CALLBACK_WITH_FAILURE(cb, cbd->data); return; } nd->reg.mode = NET_SELECT_MODE_MANUAL; CALLBACK_WITH_SUCCESS(cb, cbd->data); } static void isi_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 isi_cb_data *cbd = isi_cb_data_new(netreg, cb, data); guint8 buffer[3] = { 0 }; guint8 *bcd = mccmnc_to_bcd(mcc, mnc, buffer); const unsigned char msg[] = { NET_SET_REQ, 0x00, /* Registered in another protocol? */ 0x02, /* Sub-block count */ NET_OPERATOR_INFO_COMMON, 0x04, /* Sub-block length */ NET_SELECT_MODE_MANUAL, 0x00, /* Index not used */ NET_GSM_OPERATOR_INFO, 0x08, /* Sub-block length */ bcd[0], bcd[1], bcd[2], NET_GSM_BAND_INFO_NOT_AVAIL, /* Pick any supported band */ 0x00, 0x00 /* Filler */ }; if (cbd == NULL || nd == NULL) goto error; if (g_isi_client_send_with_timeout(nd->client, msg, sizeof(msg), NETWORK_SET_TIMEOUT, set_manual_resp_cb, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, data); g_free(cbd); } static void rssi_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_netreg *netreg = data; uint8_t rssi; if (g_isi_msg_id(msg) != NET_RSSI_IND) return; if (!g_isi_msg_data_get_byte(msg, 0, &rssi)) return; ofono_netreg_strength_notify(netreg, rssi ? rssi : -1); } static gboolean parse_nettime(GIsiSubBlockIter *iter, struct ofono_network_time *info) { struct network_time *time; size_t len = sizeof(struct network_time); if (!g_isi_sb_iter_get_struct(iter, (void **) &time, len, 2)) return FALSE; /* Value is years since last turn of century */ info->year = time->year != NET_INVALID_TIME ? time->year : -1; info->year += 2000; info->mon = time->mon != NET_INVALID_TIME ? time->mon : -1; info->mday = time->mday != NET_INVALID_TIME ? time->mday : -1; info->hour = time->hour != NET_INVALID_TIME ? time->hour : -1; info->min = time->min != NET_INVALID_TIME ? time->min : -1; info->sec = time->sec != NET_INVALID_TIME ? time->sec : -1; /* * Most significant bit set indicates negative offset. The * second most significant bit is 'reserved'. The value is the * offset from UTCin a count of 15min intervals, possibly * including the current DST adjustment. */ info->utcoff = (time->utc & 0x3F) * 15 * 60; if (time->utc & 0x80) info->utcoff *= -1; info->dst = time->dst != NET_INVALID_TIME ? time->dst : -1; return TRUE; } static void time_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_netreg *netreg = data; struct ofono_network_time info; GIsiSubBlockIter iter; if (g_isi_msg_id(msg) != NET_TIME_IND) return; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { if (g_isi_sb_iter_get_id(&iter) != NET_TIME_INFO) continue; if (!parse_nettime(&iter, &info)) return; ofono_netreg_time_notify(netreg, &info); return; } } static void name_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_netreg *netreg = data; struct netreg_data *nd = ofono_netreg_get_data(netreg); GIsiSubBlockIter iter; char *tag; uint8_t taglen; if (g_isi_msg_id(msg) != NET_NITZ_NAME_IND) return; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { uint8_t id; id = g_isi_sb_iter_get_id(&iter); if (id != NET_FULL_NITZ_NAME && id != NET_SHORT_NITZ_NAME) continue; if (!g_isi_sb_iter_get_byte(&iter, &taglen, 5)) return; if (!g_isi_sb_iter_get_alpha_tag(&iter, &tag, taglen * 2, 7)) return; strncpy(nd->nitz_name, tag, OFONO_MAX_OPERATOR_NAME_LENGTH); nd->nitz_name[OFONO_MAX_OPERATOR_NAME_LENGTH] = '\0'; g_free(tag); } } static void rssi_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; ofono_netreg_strength_cb_t cb = cbd->cb; uint8_t rssi; GIsiSubBlockIter iter; if (!check_response_status(msg, NET_RSSI_GET_RESP)) goto error; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { if (g_isi_sb_iter_get_id(&iter) != NET_RSSI_CURRENT) continue; if (!g_isi_sb_iter_get_byte(&iter, &rssi, 2)) break; CALLBACK_WITH_SUCCESS(cb, rssi ? rssi : -1, cbd->data); return; } error: CALLBACK_WITH_FAILURE(cb, -1, cbd->data); } static void isi_strength(struct ofono_netreg *netreg, ofono_netreg_strength_cb_t cb, void *data) { struct netreg_data *nd = ofono_netreg_get_data(netreg); struct isi_cb_data *cbd = isi_cb_data_new(netreg, cb, data); const uint8_t msg[] = { NET_RSSI_GET_REQ, NET_CS_GSM, NET_CURRENT_CELL_RSSI, 0, 0, 0, 0, }; size_t len = sizeof(msg); if (nd == NULL || cbd == NULL) goto error; /* Filler is only required by PN_MODEM_NETWORK */ if (g_isi_client_resource(nd->client) != PN_MODEM_NETWORK) len -= 4; if (g_isi_client_send(nd->client, msg, len, rssi_resp_cb, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, -1, data); g_free(cbd); } static void cs_access_config_resp_cb(const GIsiMessage *msg, void *data) { GIsiSubBlockIter iter; DBG(""); if (g_isi_msg_id(msg) != NET_NW_ACCESS_CONF_RESP) return; /* * TODO: Check that roaming and registration * are now enabled. */ for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { uint8_t id = g_isi_sb_iter_get_id(&iter); uint8_t mode; DBG("SB=%02X", id); switch (id) { case NET_REGISTRATION_CONF_INFO: case NET_REGISTRATION_CONF1_INFO: g_isi_sb_iter_get_byte(&iter, &mode, 2); DBG("Reg %X", mode); break; case NET_ROAMING_CONF_INFO: case NET_ROAMING_CONF1_INFO: g_isi_sb_iter_get_byte(&iter, &mode, 2); DBG("Roam %X", mode); break; default: DBG("Unknown subblock"); } } } static void enable_registration(struct ofono_netreg *netreg) { struct netreg_data *nd = ofono_netreg_get_data(netreg); const uint8_t req[] = { NET_NW_ACCESS_CONF_REQ, 0, 2, /* Subblock 1 */ 0x59, 4, 1, 0, /* Subblock 2 */ 0x5A, 4, 1, 0, }; DBG(""); g_isi_client_send(nd->client, req, sizeof(req), cs_access_config_resp_cb, netreg, NULL); } static void activate_cs_and_enable_registration(struct ofono_netreg *netreg) { DBG("not implemented"); } static void cs_state_resp_cb(const GIsiMessage *msg, void *data) { struct ofono_netreg *netreg = data; uint8_t code; DBG(""); if (g_isi_msg_id(msg) != NET_CS_STATE_RESP) return; if (!g_isi_msg_data_get_byte(msg, 0, &code)) return; if (code != NET_CAUSE_OK) { DBG("Failed with cause=%X", code); return; } if (!g_isi_msg_data_get_byte(msg, 1, &code)) return; DBG("CS STATE=%X", code); if (code == NET_CS_INACTIVE) activate_cs_and_enable_registration(netreg); else enable_registration(netreg); } static void subscribe_indications(GIsiClient *cl, void *data) { g_isi_client_ind_subscribe(cl, NET_RSSI_IND, rssi_ind_cb, data); g_isi_client_ind_subscribe(cl, NET_NITZ_NAME_IND, name_ind_cb, data); g_isi_client_ind_subscribe(cl, NET_RAT_IND, rat_ind_cb, data); g_isi_client_ind_subscribe(cl, NET_TIME_IND, time_ind_cb, data); g_isi_client_ind_subscribe(cl, NET_REG_STATUS_IND, reg_status_ind_cb, data); g_isi_client_ind_subscribe(cl, NET_MODEM_REG_STATUS_IND,reg_status_ind_cb, data); } static void pn_network_reachable_cb(const GIsiMessage *msg, void *data) { struct ofono_netreg *netreg = data; struct netreg_data *nd = ofono_netreg_get_data(netreg); if (g_isi_msg_error(msg) < 0) { DBG("PN_NETWORK not reachable, removing client"); g_isi_client_destroy(nd->pn_network); nd->pn_network = NULL; if (nd->pn_modem_network == NULL) ofono_netreg_remove(netreg); return; } ISI_RESOURCE_DBG(msg); if (nd == NULL || nd->client != NULL) return; nd->client = nd->pn_network; nd->version.major = g_isi_msg_version_major(msg); nd->version.minor = g_isi_msg_version_minor(msg); subscribe_indications(nd->client, netreg); ofono_netreg_register(netreg); } static void pn_modem_network_reachable_cb(const GIsiMessage *msg, void *data) { struct ofono_netreg *netreg = data; struct netreg_data *nd = ofono_netreg_get_data(netreg); const uint8_t req[] = { NET_CS_STATE_REQ, }; if (g_isi_msg_error(msg) < 0) { DBG("PN_MODEM_NETWORK not reachable, removing client"); g_isi_client_destroy(nd->pn_modem_network); nd->pn_modem_network = NULL; if (nd->pn_network == NULL) ofono_netreg_remove(netreg); return; } ISI_RESOURCE_DBG(msg); if (nd == NULL || nd->client != NULL) return; nd->client = nd->pn_modem_network; nd->version.major = g_isi_msg_version_major(msg); nd->version.minor = g_isi_msg_version_minor(msg); subscribe_indications(nd->client, netreg); ofono_netreg_register(netreg); g_isi_client_send(nd->client, req, sizeof(req), cs_state_resp_cb, netreg, NULL); } static int isi_netreg_probe(struct ofono_netreg *netreg, unsigned int vendor, void *user) { GIsiModem *modem = user; struct netreg_data *nd; nd = g_try_new0(struct netreg_data, 1); if (nd == NULL) return -ENOMEM; nd->pn_network = g_isi_client_create(modem, PN_NETWORK); if (nd->pn_network == NULL) { g_free(nd); return -ENOMEM; } nd->pn_modem_network = g_isi_client_create(modem, PN_MODEM_NETWORK); if (nd->pn_modem_network == NULL) { g_isi_client_destroy(nd->pn_network); g_free(nd); return -ENOMEM; } ofono_netreg_set_data(netreg, nd); g_isi_client_verify(nd->pn_network, pn_network_reachable_cb, netreg, NULL); g_isi_client_verify(nd->pn_modem_network, pn_modem_network_reachable_cb, netreg, NULL); return 0; } static void isi_netreg_remove(struct ofono_netreg *netreg) { struct netreg_data *data = ofono_netreg_get_data(netreg); ofono_netreg_set_data(netreg, NULL); if (data == NULL) return; g_isi_client_destroy(data->pn_modem_network); g_isi_client_destroy(data->pn_network); g_free(data); } static const struct ofono_netreg_driver isimodem = { .name = "isimodem", .probe = isi_netreg_probe, .remove = isi_netreg_remove, .registration_status = isi_registration_status, .current_operator = isi_current_operator, .list_operators = isi_list_operators, .register_auto = isi_register_auto, .register_manual = isi_register_manual, .strength = isi_strength, }; void isi_netreg_init(void) { ofono_netreg_driver_register(&isimodem); } void isi_netreg_exit(void) { ofono_netreg_driver_unregister(&isimodem); }