Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| /* | |
| * | |
| * oFono - Open Source Telephony | |
| * | |
| * Copyright (C) 2011-2012 Intel Corporation. All rights reserved. | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #include <endian.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <ofono/log.h> | |
| #include <ofono/modem.h> | |
| #include <ofono/netreg.h> | |
| #include "qmi.h" | |
| #include "nas.h" | |
| #include "qmimodem.h" | |
| #include "src/common.h" | |
| struct netreg_data { | |
| struct qmi_service *nas; | |
| struct ofono_network_operator operator; | |
| uint8_t current_rat; | |
| bool is_roaming; | |
| }; | |
| enum roaming_status { | |
| ROAMING_STATUS_OFF, | |
| ROAMING_STATUS_ON, | |
| ROAMING_STATUS_NO_CHANGE, | |
| }; | |
| static bool extract_ss_info_time( | |
| struct qmi_result *result, | |
| struct ofono_network_time *time) | |
| { | |
| const struct qmi_nas_3gpp_time *time_3gpp = NULL; | |
| uint8_t dst_3gpp; | |
| bool dst_3gpp_valid; | |
| uint16_t len; | |
| /* parse 3gpp time & dst */ | |
| dst_3gpp_valid = qmi_result_get_uint8(result, QMI_NAS_RESULT_3GGP_DST, | |
| &dst_3gpp); | |
| time_3gpp = qmi_result_get(result, QMI_NAS_RESULT_3GPP_TIME, &len); | |
| if (time_3gpp && len == sizeof(struct qmi_nas_3gpp_time) && | |
| dst_3gpp_valid) { | |
| time->year = le16toh(time_3gpp->year); | |
| time->mon = time_3gpp->month; | |
| time->mday = time_3gpp->day; | |
| time->hour = time_3gpp->hour; | |
| time->min = time_3gpp->minute; | |
| time->sec = time_3gpp->second; | |
| time->utcoff = time_3gpp->timezone * 15 * 60; | |
| time->dst = dst_3gpp; | |
| return true; | |
| } | |
| /* TODO: 3gpp2 */ | |
| return false; | |
| } | |
| static bool extract_ss_info(struct qmi_result *result, int *status, | |
| int *lac, int *cellid, int *tech, | |
| enum roaming_status *roaming, | |
| struct ofono_network_operator *operator) | |
| { | |
| const struct qmi_nas_serving_system *ss; | |
| const struct qmi_nas_current_plmn *plmn; | |
| uint8_t i, roaming_status; | |
| uint16_t value16, len, opname_len; | |
| uint32_t value32; | |
| DBG(""); | |
| ss = qmi_result_get(result, QMI_NAS_RESULT_SERVING_SYSTEM, &len); | |
| if (!ss) | |
| return false; | |
| *status = ss->status; | |
| DBG("serving system status %d", ss->status); | |
| *tech = -1; | |
| for (i = 0; i < ss->radio_if_count; i++) { | |
| DBG("radio in use %d", ss->radio_if[i]); | |
| *tech = qmi_nas_rat_to_tech(ss->radio_if[i]); | |
| } | |
| *roaming = ROAMING_STATUS_NO_CHANGE; | |
| if (qmi_result_get_uint8(result, QMI_NAS_RESULT_ROAMING_STATUS, | |
| &roaming_status)) { | |
| if (roaming_status == 0) | |
| *roaming = ROAMING_STATUS_ON; | |
| else if (roaming_status == 1) | |
| *roaming = ROAMING_STATUS_OFF; | |
| } | |
| if (!operator) | |
| return true; | |
| plmn = qmi_result_get(result, QMI_NAS_RESULT_CURRENT_PLMN, &len); | |
| if (plmn) { | |
| snprintf(operator->mcc, OFONO_MAX_MCC_LENGTH + 1, "%03d", | |
| GUINT16_FROM_LE(plmn->mcc)); | |
| snprintf(operator->mnc, OFONO_MAX_MNC_LENGTH + 1, "%02d", | |
| GUINT16_FROM_LE(plmn->mnc)); | |
| opname_len = plmn->desc_len; | |
| if (opname_len > OFONO_MAX_OPERATOR_NAME_LENGTH) | |
| opname_len = OFONO_MAX_OPERATOR_NAME_LENGTH; | |
| /* | |
| * Telit QMI modems can return non-utf-8 characters in | |
| * plmn-desc. When that happens, libdbus will abort ofono. | |
| * If non-utf-8 characters are detected, use mccmnc string. | |
| */ | |
| if (g_utf8_validate(plmn->desc, opname_len, NULL)) { | |
| strncpy(operator->name, plmn->desc, opname_len); | |
| operator->name[opname_len] = '\0'; | |
| } else | |
| snprintf(operator->name, OFONO_MAX_OPERATOR_NAME_LENGTH, | |
| "%s%s", operator->mcc, operator->mnc); | |
| DBG("%s (%s:%s)", operator->name, operator->mcc, operator->mnc); | |
| } | |
| if (qmi_result_get_uint16(result, QMI_NAS_RESULT_LOCATION_AREA_CODE, | |
| &value16)) | |
| *lac = value16; | |
| else | |
| *lac = -1; | |
| if (qmi_result_get_uint32(result, QMI_NAS_RESULT_CELL_ID, &value32)) | |
| *cellid = value32; | |
| else | |
| *cellid = -1; | |
| DBG("roaming %u lac %d cellid %d tech %d", *roaming, *lac, *cellid, | |
| *tech); | |
| return true; | |
| } | |
| static void ss_info_notify(struct qmi_result *result, void *user_data) | |
| { | |
| struct ofono_netreg *netreg = user_data; | |
| struct ofono_network_time net_time; | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| int status, lac, cellid, tech; | |
| enum roaming_status roaming; | |
| DBG(""); | |
| if (extract_ss_info_time(result, &net_time)) | |
| ofono_netreg_time_notify(netreg, &net_time); | |
| if (!extract_ss_info(result, &status, &lac, &cellid, &tech, &roaming, | |
| &data->operator)) | |
| return; | |
| if (roaming == ROAMING_STATUS_ON) | |
| data->is_roaming = true; | |
| else if (roaming == ROAMING_STATUS_OFF) | |
| data->is_roaming = false; | |
| if (status == QMI_NAS_REGISTRATION_STATE_REGISTERED && | |
| data->is_roaming) | |
| status = NETWORK_REGISTRATION_STATUS_ROAMING; | |
| ofono_netreg_status_notify(netreg, status, lac, cellid, tech); | |
| } | |
| static void get_ss_info_cb(struct qmi_result *result, void *user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| ofono_netreg_status_cb_t cb = cbd->cb; | |
| struct netreg_data *data = cbd->user; | |
| int status, lac, cellid, tech; | |
| enum roaming_status roaming; | |
| DBG(""); | |
| if (qmi_result_set_error(result, NULL)) { | |
| CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, cbd->data); | |
| return; | |
| } | |
| if (!extract_ss_info(result, &status, &lac, &cellid, &tech, &roaming, | |
| &data->operator)) { | |
| CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, cbd->data); | |
| return; | |
| } | |
| if (roaming == ROAMING_STATUS_ON) | |
| data->is_roaming = true; | |
| else if (roaming == ROAMING_STATUS_OFF) | |
| data->is_roaming = false; | |
| if (status == QMI_NAS_REGISTRATION_STATE_REGISTERED && | |
| data->is_roaming) | |
| status = NETWORK_REGISTRATION_STATUS_ROAMING; | |
| CALLBACK_WITH_SUCCESS(cb, status, lac, cellid, tech, cbd->data); | |
| } | |
| static void qmi_registration_status(struct ofono_netreg *netreg, | |
| ofono_netreg_status_cb_t cb, void *user_data) | |
| { | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| struct cb_data *cbd = cb_data_new(cb, user_data); | |
| DBG(""); | |
| cbd->user = data; | |
| if (qmi_service_send(data->nas, QMI_NAS_GET_SS_INFO, NULL, | |
| get_ss_info_cb, cbd, g_free) > 0) | |
| return; | |
| CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, cbd->data); | |
| g_free(cbd); | |
| } | |
| static void qmi_current_operator(struct ofono_netreg *netreg, | |
| ofono_netreg_operator_cb_t cb, void *user_data) | |
| { | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| DBG(""); | |
| CALLBACK_WITH_SUCCESS(cb, &data->operator, user_data); | |
| } | |
| static void scan_nets_cb(struct qmi_result *result, void *user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| ofono_netreg_operator_list_cb_t cb = cbd->cb; | |
| struct ofono_network_operator *list; | |
| const struct qmi_nas_network_list *netlist; | |
| const struct qmi_nas_network_rat *netrat; | |
| const void *ptr; | |
| uint16_t len, num, offset, i; | |
| DBG(""); | |
| if (qmi_result_set_error(result, NULL)) { | |
| CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); | |
| return; | |
| } | |
| ptr = qmi_result_get(result, QMI_NAS_RESULT_NETWORK_LIST, &len); | |
| if (!ptr) { | |
| CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); | |
| return; | |
| } | |
| netlist = ptr; | |
| num = GUINT16_FROM_LE(netlist->count); | |
| DBG("found %d operators", num); | |
| list = g_try_new0(struct ofono_network_operator, num); | |
| if (!list) { | |
| CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); | |
| return; | |
| } | |
| offset = 2; | |
| for (i = 0; i < num; i++) { | |
| const struct qmi_nas_network_info *netinfo = ptr + offset; | |
| snprintf(list[i].mcc, OFONO_MAX_MCC_LENGTH + 1, "%03d", | |
| GUINT16_FROM_LE(netinfo->mcc)); | |
| snprintf(list[i].mnc, OFONO_MAX_MNC_LENGTH + 1, "%02d", | |
| GUINT16_FROM_LE(netinfo->mnc)); | |
| strncpy(list[i].name, netinfo->desc, netinfo->desc_len); | |
| list[i].name[netinfo->desc_len] = '\0'; | |
| if (netinfo->status & 0x10) | |
| list[i].status = 3; | |
| else if (netinfo->status & 0x01) | |
| list[i].status = 2; | |
| else if (netinfo->status & 0x02) | |
| list[i].status = 1; | |
| else | |
| list[i].status = 0; | |
| list[i].tech = -1; | |
| DBG("%s (%s:%s) status %d", list[i].name, | |
| list[i].mcc, list[i].mnc, list[i].status); | |
| offset += sizeof(struct qmi_nas_network_info) + | |
| netinfo->desc_len; | |
| } | |
| netrat = qmi_result_get(result, QMI_NAS_RESULT_NETWORK_RAT, &len); | |
| if (!netrat) | |
| goto done; | |
| if (GUINT16_FROM_LE(netrat->count) != num) | |
| goto done; | |
| for (i = 0; i < num; i++) { | |
| DBG("%03d:%02d %d", netrat->info[i].mcc, netrat->info[i].mnc, | |
| netrat->info[i].rat); | |
| list[i].tech = qmi_nas_rat_to_tech(netrat->info[i].rat); | |
| } | |
| done: | |
| CALLBACK_WITH_SUCCESS(cb, num, list, cbd->data); | |
| g_free(list); | |
| } | |
| static void qmi_list_operators(struct ofono_netreg *netreg, | |
| ofono_netreg_operator_list_cb_t cb, void *user_data) | |
| { | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| struct cb_data *cbd = cb_data_new(cb, user_data); | |
| DBG(""); | |
| if (qmi_service_send(data->nas, QMI_NAS_SCAN_NETS, NULL, | |
| scan_nets_cb, cbd, g_free) > 0) | |
| return; | |
| CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); | |
| g_free(cbd); | |
| } | |
| static void register_net_cb(struct qmi_result *result, void *user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| ofono_netreg_register_cb_t cb = cbd->cb; | |
| uint16_t error; | |
| int cme_error; | |
| DBG(""); | |
| if (qmi_result_set_error(result, &error)) { | |
| if (error == 26) { | |
| /* no effect */ | |
| goto done; | |
| } | |
| cme_error = qmi_error_to_ofono_cme(error); | |
| CALLBACK_WITH_CME_ERROR(cb, cme_error, cbd->data); | |
| return; | |
| } | |
| done: | |
| CALLBACK_WITH_SUCCESS(cb, cbd->data); | |
| } | |
| static void qmi_register_auto(struct ofono_netreg *netreg, | |
| ofono_netreg_register_cb_t cb, void *user_data) | |
| { | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| struct cb_data *cbd = cb_data_new(cb, user_data); | |
| struct qmi_param *param; | |
| DBG(""); | |
| param = qmi_param_new_uint8(QMI_NAS_PARAM_REGISTER_ACTION, | |
| QMI_NAS_REGISTER_ACTION_AUTO); | |
| if (!param) | |
| goto error; | |
| if (qmi_service_send(data->nas, QMI_NAS_REGISTER_NET, param, | |
| register_net_cb, cbd, g_free) > 0) | |
| return; | |
| qmi_param_free(param); | |
| error: | |
| CALLBACK_WITH_FAILURE(cb, cbd->data); | |
| g_free(cbd); | |
| } | |
| static void qmi_register_manual(struct ofono_netreg *netreg, | |
| const char *mcc, const char *mnc, | |
| ofono_netreg_register_cb_t cb, void *user_data) | |
| { | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| struct cb_data *cbd = cb_data_new(cb, user_data); | |
| struct qmi_nas_param_register_manual_info info; | |
| struct qmi_param *param; | |
| DBG(""); | |
| param = qmi_param_new_uint8(QMI_NAS_PARAM_REGISTER_ACTION, | |
| QMI_NAS_REGISTER_ACTION_MANUAL); | |
| if (!param) | |
| goto error; | |
| info.mcc = atoi(mcc); | |
| info.mnc = atoi(mnc); | |
| info.rat = QMI_NAS_NETWORK_RAT_NO_CHANGE; | |
| qmi_param_append(param, QMI_NAS_PARAM_REGISTER_MANUAL_INFO, | |
| sizeof(info), &info); | |
| if (qmi_service_send(data->nas, QMI_NAS_REGISTER_NET, param, | |
| register_net_cb, cbd, g_free) > 0) | |
| return; | |
| qmi_param_free(param); | |
| error: | |
| CALLBACK_WITH_FAILURE(cb, cbd->data); | |
| g_free(cbd); | |
| } | |
| static int dbm_to_strength(int8_t dbm) | |
| { | |
| if (dbm > -55) | |
| return 100; | |
| else if (dbm > -65) | |
| return 80; | |
| else if (dbm > -75) | |
| return 60; | |
| else if (dbm > -85) | |
| return 40; | |
| else if (dbm > -95) | |
| return 20; | |
| else if (dbm > -105) | |
| return 0; | |
| return -1; | |
| } | |
| static void get_rssi_cb(struct qmi_result *result, void *user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| ofono_netreg_strength_cb_t cb = cbd->cb; | |
| const struct qmi_nas_signal_strength *ss; | |
| uint16_t len; | |
| int strength; | |
| DBG(""); | |
| if (qmi_result_set_error(result, NULL)) { | |
| CALLBACK_WITH_FAILURE(cb, -1, cbd->data); | |
| return; | |
| } | |
| ss = qmi_result_get(result, QMI_NAS_RESULT_SIGNAL_STRENGTH, &len); | |
| if (!ss) { | |
| CALLBACK_WITH_FAILURE(cb, -1, cbd->data); | |
| return; | |
| } | |
| DBG("signal with %d dBm on %d", ss->dbm, ss->rat); | |
| strength = dbm_to_strength(ss->dbm); | |
| CALLBACK_WITH_SUCCESS(cb, strength, cbd->data); | |
| } | |
| static void qmi_signal_strength(struct ofono_netreg *netreg, | |
| ofono_netreg_strength_cb_t cb, void *user_data) | |
| { | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| struct cb_data *cbd = cb_data_new(cb, user_data); | |
| DBG(""); | |
| if (qmi_service_send(data->nas, QMI_NAS_GET_RSSI, NULL, | |
| get_rssi_cb, cbd, g_free) > 0) | |
| return; | |
| CALLBACK_WITH_FAILURE(cb, -1, cbd->data); | |
| g_free(cbd); | |
| } | |
| static void event_notify(struct qmi_result *result, void *user_data) | |
| { | |
| struct ofono_netreg *netreg = user_data; | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| const struct qmi_nas_signal_strength *ss; | |
| const struct qmi_nas_rf_info *rf; | |
| uint16_t len; | |
| DBG(""); | |
| ss = qmi_result_get(result, QMI_NAS_NOTIFY_SIGNAL_STRENGTH, &len); | |
| if (ss) { | |
| int strength; | |
| strength = dbm_to_strength(ss->dbm); | |
| DBG("signal with %d%%(%d dBm) on %d", | |
| strength, ss->dbm, ss->rat); | |
| ofono_netreg_strength_notify(netreg, strength); | |
| } | |
| rf = qmi_result_get(result, QMI_NAS_NOTIFY_RF_INFO, &len); | |
| if (rf) { | |
| uint8_t i; | |
| for (i = 0; i < rf->count; i++) { | |
| DBG("rat %d band %d channel %d", rf->info[i].rat, | |
| rf->info[i].band, rf->info[i].channel); | |
| } | |
| data->current_rat = rf->info[i].rat; | |
| } | |
| } | |
| static void set_event_cb(struct qmi_result *result, void *user_data) | |
| { | |
| struct ofono_netreg *netreg = user_data; | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| DBG(""); | |
| ofono_netreg_register(netreg); | |
| qmi_service_register(data->nas, QMI_NAS_EVENT, | |
| event_notify, netreg, NULL); | |
| qmi_service_register(data->nas, QMI_NAS_SS_INFO_IND, | |
| ss_info_notify, netreg, NULL); | |
| } | |
| static void create_nas_cb(struct qmi_service *service, void *user_data) | |
| { | |
| struct ofono_netreg *netreg = user_data; | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| struct qmi_param *param; | |
| struct qmi_nas_param_event_signal_strength ss = { .report = 0x01, | |
| .count = 5, .dbm[0] = -55, .dbm[1] = -65, | |
| .dbm[2] = -75, .dbm[3] = -85, .dbm[4] = -95 }; | |
| DBG(""); | |
| if (!service) { | |
| ofono_error("Failed to request NAS service"); | |
| ofono_netreg_remove(netreg); | |
| return; | |
| } | |
| data->nas = qmi_service_ref(service); | |
| param = qmi_param_new(); | |
| if (!param) | |
| goto done; | |
| qmi_param_append(param, QMI_NAS_PARAM_REPORT_SIGNAL_STRENGTH, | |
| sizeof(ss), &ss); | |
| qmi_param_append_uint8(param, QMI_NAS_PARAM_REPORT_RF_INFO, 0x01); | |
| if (qmi_service_send(data->nas, QMI_NAS_SET_EVENT, param, | |
| set_event_cb, netreg, NULL) > 0) | |
| return; | |
| qmi_param_free(param); | |
| done: | |
| ofono_netreg_register(netreg); | |
| } | |
| static int qmi_netreg_probe(struct ofono_netreg *netreg, | |
| unsigned int vendor, void *user_data) | |
| { | |
| struct qmi_device *device = user_data; | |
| struct netreg_data *data; | |
| DBG(""); | |
| data = g_new0(struct netreg_data, 1); | |
| data->operator.name[0] = '\0'; | |
| data->operator.mcc[0] = '\0'; | |
| data->operator.mnc[0] = '\0'; | |
| data->operator.status = -1; | |
| data->operator.tech = -1; | |
| data->current_rat = QMI_NAS_NETWORK_RAT_NO_CHANGE; | |
| data->is_roaming = false; | |
| ofono_netreg_set_data(netreg, data); | |
| qmi_service_create_shared(device, QMI_SERVICE_NAS, | |
| create_nas_cb, netreg, NULL); | |
| return 0; | |
| } | |
| static void qmi_netreg_remove(struct ofono_netreg *netreg) | |
| { | |
| struct netreg_data *data = ofono_netreg_get_data(netreg); | |
| DBG(""); | |
| ofono_netreg_set_data(netreg, NULL); | |
| qmi_service_unregister_all(data->nas); | |
| qmi_service_unref(data->nas); | |
| g_free(data); | |
| } | |
| static const struct ofono_netreg_driver driver = { | |
| .name = "qmimodem", | |
| .probe = qmi_netreg_probe, | |
| .remove = qmi_netreg_remove, | |
| .registration_status = qmi_registration_status, | |
| .current_operator = qmi_current_operator, | |
| .list_operators = qmi_list_operators, | |
| .register_auto = qmi_register_auto, | |
| .register_manual = qmi_register_manual, | |
| .strength = qmi_signal_strength, | |
| }; | |
| void qmi_netreg_init(void) | |
| { | |
| ofono_netreg_driver_register(&driver); | |
| } | |
| void qmi_netreg_exit(void) | |
| { | |
| ofono_netreg_driver_unregister(&driver); | |
| } |