| /* |
| * oFono - Open Source Telephony |
| * Copyright (C) 2016 EndoCode AG |
| * |
| * SPDX-License-Identifier: GPL-2.0-only |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #include <glib.h> |
| |
| #include <ofono/log.h> |
| #include <ofono/modem.h> |
| #include <ofono/netreg.h> |
| #include <ofono/netmon.h> |
| |
| #include "gatchat.h" |
| #include "gatresult.h" |
| |
| #include "common.h" |
| #include "ubloxmodem.h" |
| #include "drivers/atmodem/vendor.h" |
| |
| static const char *cops_prefix[] = { "+COPS:", NULL }; |
| static const char *cesq_prefix[] = { "+CESQ:", NULL }; |
| |
| struct netmon_driver_data { |
| GAtChat *chat; |
| }; |
| |
| struct req_cb_data { |
| gint ref_count; /* Ref count */ |
| |
| struct ofono_netmon *netmon; |
| |
| ofono_netmon_cb_t cb; |
| void *data; |
| |
| struct ofono_network_operator op; |
| |
| int rxlev; /* CESQ: Received Signal Strength Indication */ |
| int ber; /* CESQ: Bit Error Rate */ |
| int rscp; /* CESQ: Received Signal Code Powe */ |
| int rsrp; /* CESQ: Reference Signal Received Power */ |
| int ecn0; /* CESQ: Received Energy Ratio */ |
| int rsrq; /* CESQ: Reference Signal Received Quality */ |
| }; |
| |
| /* |
| * Returns the appropriate radio access technology. |
| * |
| * If we can not resolve to a specific radio access technolgy |
| * we return OFONO_NETMON_CELL_TYPE_GSM by default. |
| */ |
| static int ublox_map_radio_access_technology(int tech) |
| { |
| switch (tech) { |
| case ACCESS_TECHNOLOGY_GSM: |
| case ACCESS_TECHNOLOGY_GSM_COMPACT: |
| return OFONO_NETMON_CELL_TYPE_GSM; |
| case ACCESS_TECHNOLOGY_UTRAN: |
| case ACCESS_TECHNOLOGY_UTRAN_HSDPA: |
| case ACCESS_TECHNOLOGY_UTRAN_HSUPA: |
| case ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA: |
| return OFONO_NETMON_CELL_TYPE_UMTS; |
| case ACCESS_TECHNOLOGY_EUTRAN: |
| return OFONO_NETMON_CELL_TYPE_LTE; |
| } |
| |
| return OFONO_NETMON_CELL_TYPE_GSM; |
| } |
| |
| static inline struct req_cb_data *req_cb_data_new0(void *cb, void *data, |
| void *user) |
| { |
| struct req_cb_data *ret = g_new0(struct req_cb_data, 1); |
| |
| ret->ref_count = 1; |
| ret->cb = cb; |
| ret->data = data; |
| ret->netmon = user; |
| ret->rxlev = -1; |
| ret->ber = -1; |
| ret->rscp = -1; |
| ret->rsrp = -1; |
| ret->ecn0 = -1; |
| ret->rsrq = -1; |
| |
| return ret; |
| } |
| |
| static inline struct req_cb_data *req_cb_data_ref(struct req_cb_data *cbd) |
| { |
| if (cbd == NULL) |
| return NULL; |
| |
| g_atomic_int_inc(&cbd->ref_count); |
| |
| return cbd; |
| } |
| |
| static void req_cb_data_unref(gpointer user_data) |
| { |
| gboolean is_zero; |
| struct req_cb_data *cbd = user_data; |
| |
| if (cbd == NULL) |
| return; |
| |
| is_zero = g_atomic_int_dec_and_test(&cbd->ref_count); |
| |
| if (is_zero == TRUE) |
| g_free(cbd); |
| } |
| |
| static gboolean ublox_delayed_register(gpointer user_data) |
| { |
| struct ofono_netmon *netmon = user_data; |
| |
| ofono_netmon_register(netmon); |
| |
| return FALSE; |
| } |
| |
| static void ublox_netmon_finish_success(struct req_cb_data *cbd) |
| { |
| struct ofono_netmon *nm = cbd->netmon; |
| |
| ofono_netmon_serving_cell_notify(nm, |
| cbd->op.tech, |
| OFONO_NETMON_INFO_RXLEV, cbd->rxlev, |
| OFONO_NETMON_INFO_BER, cbd->ber, |
| OFONO_NETMON_INFO_RSCP, cbd->rscp, |
| OFONO_NETMON_INFO_ECN0, cbd->ecn0, |
| OFONO_NETMON_INFO_RSRQ, cbd->rsrq, |
| OFONO_NETMON_INFO_RSRP, cbd->rsrp, |
| OFONO_NETMON_INFO_INVALID); |
| |
| CALLBACK_WITH_SUCCESS(cbd->cb, cbd->data); |
| } |
| |
| static void cesq_cb(gboolean ok, GAtResult *result, gpointer user_data) |
| { |
| enum cesq_ofono_netmon_info { |
| CESQ_RXLEV, |
| CESQ_BER, |
| CESQ_RSCP, |
| CESQ_ECN0, |
| CESQ_RSRQ, |
| CESQ_RSRP, |
| _MAX, |
| }; |
| |
| struct req_cb_data *cbd = user_data; |
| struct ofono_error error; |
| GAtResultIter iter; |
| int idx, number; |
| |
| DBG("ok %d", ok); |
| |
| decode_at_error(&error, g_at_result_final_response(result)); |
| |
| if (!ok) { |
| CALLBACK_WITH_FAILURE(cbd->cb, cbd->data); |
| return; |
| } |
| |
| g_at_result_iter_init(&iter, result); |
| |
| if (!g_at_result_iter_next(&iter, "+CESQ:")) { |
| DBG(" CESQ: no result "); |
| goto out; |
| } |
| |
| for (idx = 0; idx < _MAX; idx++) { |
| ok = g_at_result_iter_next_number(&iter, &number); |
| |
| if (!ok) { |
| /* Ignore and do not fail */ |
| DBG(" CESQ: error parsing idx: %d ", idx); |
| goto out; |
| } |
| |
| switch (idx) { |
| case CESQ_RXLEV: |
| cbd->rxlev = number != 99 ? number:cbd->rxlev; |
| break; |
| case CESQ_BER: |
| cbd->ber = number != 99 ? number:cbd->ber; |
| break; |
| case CESQ_RSCP: |
| cbd->rscp = number != 255 ? number:cbd->rscp; |
| break; |
| case CESQ_ECN0: |
| cbd->ecn0 = number != 255 ? number:cbd->ecn0; |
| break; |
| case CESQ_RSRQ: |
| cbd->rsrq = number != 255 ? number:cbd->rsrq; |
| break; |
| case CESQ_RSRP: |
| cbd->rsrp = number != 255 ? number:cbd->rsrp; |
| break; |
| } |
| } |
| |
| DBG(" RXLEV %d ", cbd->rxlev); |
| DBG(" BER %d ", cbd->ber); |
| DBG(" RSCP %d ", cbd->rscp); |
| DBG(" ECN0 %d ", cbd->ecn0); |
| DBG(" RSRQ %d ", cbd->rsrq); |
| DBG(" RSRP %d ", cbd->rsrp); |
| |
| /* |
| * We never fail at this point we always send what we collected so |
| * far |
| */ |
| out: |
| ublox_netmon_finish_success(cbd); |
| } |
| |
| static void cops_cb(gboolean ok, GAtResult *result, gpointer user_data) |
| { |
| struct req_cb_data *cbd = user_data; |
| struct ofono_netmon *nm = cbd->netmon; |
| struct netmon_driver_data *nmd = ofono_netmon_get_data(nm); |
| struct ofono_error error; |
| GAtResultIter iter; |
| int tech; |
| |
| DBG("ok %d", ok); |
| |
| decode_at_error(&error, g_at_result_final_response(result)); |
| |
| if (!ok) { |
| CALLBACK_WITH_FAILURE(cbd->cb, cbd->data); |
| return; |
| } |
| |
| g_at_result_iter_init(&iter, result); |
| |
| /* Do not fail */ |
| if (!g_at_result_iter_next(&iter, "+COPS:")) { |
| CALLBACK_WITH_SUCCESS(cbd->cb, cbd->data); |
| return; |
| } |
| |
| g_at_result_iter_skip_next(&iter); |
| g_at_result_iter_skip_next(&iter); |
| g_at_result_iter_skip_next(&iter); |
| |
| /* Default to GSM */ |
| if (g_at_result_iter_next_number(&iter, &tech) == FALSE) |
| cbd->op.tech = OFONO_NETMON_CELL_TYPE_GSM; |
| else |
| cbd->op.tech = ublox_map_radio_access_technology(tech); |
| |
| cbd = req_cb_data_ref(cbd); |
| if (g_at_chat_send(nmd->chat, "AT+CESQ", cesq_prefix, |
| cesq_cb, cbd, req_cb_data_unref) == 0) { |
| CALLBACK_WITH_FAILURE(cbd->cb, cbd->data); |
| req_cb_data_unref(cbd); |
| } |
| } |
| |
| static void ublox_netmon_request_update(struct ofono_netmon *netmon, |
| ofono_netmon_cb_t cb, void *data) |
| { |
| struct netmon_driver_data *nmd = ofono_netmon_get_data(netmon); |
| struct req_cb_data *cbd; |
| |
| DBG("ublox netmon request update"); |
| |
| cbd = req_cb_data_new0(cb, data, netmon); |
| |
| if (g_at_chat_send(nmd->chat, "AT+COPS?", cops_prefix, |
| cops_cb, cbd, req_cb_data_unref) == 0) { |
| CALLBACK_WITH_FAILURE(cbd->cb, cbd->data); |
| req_cb_data_unref(cbd); |
| } |
| } |
| |
| static int ublox_netmon_probe(struct ofono_netmon *netmon, |
| unsigned int vendor, void *user) |
| { |
| GAtChat *chat = user; |
| struct netmon_driver_data *nmd; |
| |
| DBG("ublox netmon probe"); |
| |
| nmd = g_try_new0(struct netmon_driver_data, 1); |
| if (nmd == NULL) |
| return -ENOMEM; |
| |
| nmd->chat = g_at_chat_clone(chat); |
| |
| ofono_netmon_set_data(netmon, nmd); |
| |
| g_idle_add(ublox_delayed_register, netmon); |
| |
| return 0; |
| } |
| |
| static void ublox_netmon_remove(struct ofono_netmon *netmon) |
| { |
| struct netmon_driver_data *nmd = ofono_netmon_get_data(netmon); |
| |
| DBG("ublox netmon remove"); |
| |
| g_at_chat_unref(nmd->chat); |
| |
| ofono_netmon_set_data(netmon, NULL); |
| |
| g_free(nmd); |
| } |
| |
| static const struct ofono_netmon_driver driver = { |
| .probe = ublox_netmon_probe, |
| .remove = ublox_netmon_remove, |
| .request_update = ublox_netmon_request_update, |
| }; |
| |
| OFONO_ATOM_DRIVER_BUILTIN(netmon, ubloxmodem, &driver) |