[PATCH] broadband-modem/libqcdm: use signal strength from QCDM EVDO Pilot Sets log messages
Aleksander Morgado
aleksander at aleksander.es
Tue Jul 26 15:44:57 UTC 2016
On Tue, Jul 26, 2016 at 5:24 PM, Dan Williams <dcbw at redhat.com> wrote:
> When a CDMA-only modem is registered with the EVDO network, its not possible to
> read signal strength in the following cases:
>
> 1) while a data connection is active on single-AT-port modems, because the AT
> port is used for PPP and not available for AT+CSQ, AT+CIND or vendor-specific
> signal strength commands
>
> 2) when the modem reports only CDMA 1x signal strength with AT+CSQ
>
> Now that we have a reasonable interpretation of RSSI from the QCDM
> EVDO Pilot Sets V2 log messgae, use that when other means of getting
> signal strength aren't available.
Nice thing! Do you have logs of a successful run for reference? +1
from me anyway.
> ---
> libqcdm/src/Makefile.am | 2 +
> libqcdm/src/log-items.h | 34 +++----
> libqcdm/src/logs.c | 184 +++++++++++++++++++++++++++++++++++
> libqcdm/src/logs.h | 50 ++++++++++
> src/mm-broadband-modem.c | 240 +++++++++++++++++++++++++++++++++++++++++++++-
> src/mm-port-serial-qcdm.c | 160 ++++++++++++++++++++++++++++++-
> src/mm-port-serial-qcdm.h | 16 ++++
> 7 files changed, 663 insertions(+), 23 deletions(-)
> create mode 100644 libqcdm/src/logs.c
> create mode 100644 libqcdm/src/logs.h
>
> diff --git a/libqcdm/src/Makefile.am b/libqcdm/src/Makefile.am
> index 2caaf00..f13098f 100644
> --- a/libqcdm/src/Makefile.am
> +++ b/libqcdm/src/Makefile.am
> @@ -16,6 +16,8 @@ libqcdm_la_SOURCES = \
> commands.h \
> errors.c \
> errors.h \
> + logs.c \
> + logs.h \
> result.c \
> result.h \
> result-private.h \
> diff --git a/libqcdm/src/log-items.h b/libqcdm/src/log-items.h
> index 71f0f1e..8802dfd 100644
> --- a/libqcdm/src/log-items.h
> +++ b/libqcdm/src/log-items.h
> @@ -37,7 +37,7 @@ enum {
> DM_LOG_ITEM_EVDO_REV_POWER_CONTROL = 0x1063,
> DM_LOG_ITEM_EVDO_ARQ_EFFECTIVE_RECEIVE_RATE = 0x1066,
> DM_LOG_ITEM_EVDO_AIR_LINK_SUMMARY = 0x1068,
> - DM_LOG_ITEM_EVDO_POWER = 0x1069
> + DM_LOG_ITEM_EVDO_POWER = 0x1069,
> DM_LOG_ITEM_EVDO_FWD_LINK_PACKET_SNAPSHOT = 0x106A,
> DM_LOG_ITEM_EVDO_ACCESS_ATTEMPT = 0x106C,
> DM_LOG_ITEM_EVDO_REV_ACTIVITY_BITS_BUFFER = 0x106D,
> @@ -91,14 +91,14 @@ struct DMLogItemCdmaReversePowerControl {
> typedef struct DMLogItemCdmaReversePowerControl DMLogItemCdmaReversePowerControl;
>
> /* DM_LOG_ITEM_EVDO_PILOT_SETS_V2 */
> -struct EvdoPilotSetsV2PilotRecord {
> +struct DMLogItemEvdoPilotSetsV2Pilot {
> u_int16_t pilot_pn;
> /* HDR pilot energy doesn't appear to be in the same units as 1x pilot
> - * energy (eg, -0.5 dBm increments). Instead, you can approximate EC/IO
> - * by using this formula empirically derived from simultaneous AT!ECIO
> - * and HDR Pilot Sets V2 results from a Sierra modem:
> + * energy (eg, -0.5 dBm increments). Instead it appears roughly correlated
> + * to RSSI dBm by using this formula empirically derived from simultaneous
> + * AT!RSSI and HDR Pilot Sets V2 results from a Sierra modem:
> *
> - * EC/IO = (pilot_energy / -50) + 1
> + * RSSI dBm = -110 + (MAX(pilot_energy - 50, 0) / 14)
> */
> u_int16_t pilot_energy;
> union {
> @@ -122,22 +122,22 @@ struct EvdoPilotSetsV2PilotRecord {
> } Remaining;
> };
> } __attribute__ ((packed));
> -typedef struct EvdoPilotSetsV2PilotRecord EvdoPilotSetsV2PilotRecord;
> +typedef struct DMLogItemEvdoPilotSetsV2Pilot DMLogItemEvdoPilotSetsV2Pilot;
>
> /* DM_LOG_ITEM_EVDO_PILOT_SETS_V2 */
> struct DMLogItemEvdoPilotSetsV2 {
> u_int8_t pn_offset;
> - u_int8_t active_set_count;
> - u_int8_t active_set_window;
> - u_int16_t active_set_channel;
> + u_int8_t active_count;
> + u_int8_t active_window;
> + u_int16_t active_channel;
> u_int8_t unknown1;
> - u_int8_t candidate_set_count;
> - u_int8_t candidate_set_window;
> - u_int8_t remaining_set_count;
> - u_int8_t remaining_set_window;
> + u_int8_t candidate_count;
> + u_int8_t candidate_window;
> + u_int8_t remaining_count;
> + u_int8_t remaining_window;
> u_int8_t unknown2;
>
> - EvdoPilotSetsV2PilotRecord records[];
> + DMLogItemEvdoPilotSetsV2Pilot sets[];
> } __attribute__ ((packed));
> typedef struct DMLogItemEvdoPilotSetsV2 DMLogItemEvdoPilotSetsV2;
>
> @@ -148,7 +148,7 @@ struct DMLogItemWcdmaTaFingerInfo {
> u_int8_t non_coherent_interval_len;
> u_int8_t num_paths;
> u_int32_t path_enr;
> - int32_t pn_pos_path
> + int32_t pn_pos_path;
> int16_t pri_cpich_psc;
> u_int8_t unknown1;
> u_int8_t sec_cpich_ssc;
> @@ -214,7 +214,7 @@ typedef struct DMLogItemGsmBurstMetric DMLogItemGsmBurstMetric;
>
> struct DMLogItemGsmBurstMetrics {
> u_int8_t channel;
> - DMLogItemBurstMetric metrics[4];
> + DMLogItemGsmBurstMetric metrics[4];
> } __attribute__ ((packed));
> typedef struct DMLogItemGsmBurstMetrics DMLogItemGsmBurstMetrics;
>
> diff --git a/libqcdm/src/logs.c b/libqcdm/src/logs.c
> new file mode 100644
> index 0000000..ef604f6
> --- /dev/null
> +++ b/libqcdm/src/logs.c
> @@ -0,0 +1,184 @@
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
> +/*
> + * Copyright (C) 2010 Red Hat, Inc.
> + *
> + * This program is free software: you can redistribute it and/or
> + * modify it under the terms of version 2 of the GNU General Public
> + * License 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, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <string.h>
> +#include <stdlib.h>
> +#include <endian.h>
> +
> +#include "log-items.h"
> +#include "logs.h"
> +#include "errors.h"
> +#include "dm-commands.h"
> +#include "result-private.h"
> +#include "utils.h"
> +
> +
> +/**********************************************************************/
> +
> +static qcdmbool
> +check_log_item (const char *buf, size_t len, u_int16_t log_code, size_t min_len, int *out_error)
> +{
> + DMCmdLog *log_cmd = (DMCmdLog *) buf;
> +
> + if (len < sizeof (DMCmdLog)) {
> + qcdm_err (0, "DM log item malformed (must be at least %zu bytes in length)", sizeof (DMCmdLog));
> + if (out_error)
> + *out_error = -QCDM_ERROR_RESPONSE_MALFORMED;
> + return FALSE;
> + }
> +
> + if (buf[0] != DIAG_CMD_LOG) {
> + if (out_error)
> + *out_error = -QCDM_ERROR_RESPONSE_UNEXPECTED;
> + return FALSE;
> + }
> +
> + if (le16toh (log_cmd->log_code) != log_code) {
> + if (out_error)
> + *out_error = -QCDM_ERROR_RESPONSE_UNEXPECTED;
> + return FALSE;
> + }
> +
> + if (len < sizeof (DMCmdLog) + min_len) {
> + qcdm_err (0, "DM log item response not long enough (got %zu, expected "
> + "at least %zu).", len, sizeof (DMCmdLog) + min_len);
> + if (out_error)
> + *out_error = -QCDM_ERROR_RESPONSE_BAD_LENGTH;
> + return FALSE;
> + }
> +
> + return TRUE;
> +}
> +
> +/**********************************************************************/
> +
> +#define PILOT_SETS_LOG_ACTIVE_SET "active-set"
> +#define PILOT_SETS_LOG_CANDIDATE_SET "candidate-set"
> +#define PILOT_SETS_LOG_REMAINING_SET "remaining-set"
> +
> +static const char *
> +set_num_to_str (u_int32_t num)
> +{
> + if (num == QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_ACTIVE)
> + return PILOT_SETS_LOG_ACTIVE_SET;
> + if (num == QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_CANDIDATE)
> + return PILOT_SETS_LOG_CANDIDATE_SET;
> + if (num == QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_REMAINING)
> + return PILOT_SETS_LOG_REMAINING_SET;
> + return NULL;
> +}
> +
> +QcdmResult *
> +qcdm_log_item_evdo_pilot_sets_v2_new (const char *buf, size_t len, int *out_error)
> +{
> + QcdmResult *result = NULL;
> + DMLogItemEvdoPilotSetsV2 *pilot_sets;
> + DMCmdLog *log_cmd = (DMCmdLog *) buf;
> + size_t sets_len;
> +
> + qcdm_return_val_if_fail (buf != NULL, NULL);
> +
> + if (!check_log_item (buf, len, DM_LOG_ITEM_EVDO_PILOT_SETS_V2, sizeof (DMLogItemEvdoPilotSetsV2), out_error))
> + return NULL;
> +
> + pilot_sets = (DMLogItemEvdoPilotSetsV2 *) log_cmd->data;
> +
> + result = qcdm_result_new ();
> +
> + sets_len = pilot_sets->active_count * sizeof (DMLogItemEvdoPilotSetsV2Pilot);
> + if (sets_len > 0) {
> + qcdm_result_add_u8_array (result,
> + PILOT_SETS_LOG_ACTIVE_SET,
> + (const u_int8_t *) &pilot_sets->sets[0],
> + sets_len);
> + }
> +
> + sets_len = pilot_sets->candidate_count * sizeof (DMLogItemEvdoPilotSetsV2Pilot);
> + if (sets_len > 0) {
> + qcdm_result_add_u8_array (result,
> + PILOT_SETS_LOG_CANDIDATE_SET,
> + (const u_int8_t *) &pilot_sets->sets[pilot_sets->active_count],
> + sets_len);
> + }
> +
> + sets_len = pilot_sets->remaining_count * sizeof (DMLogItemEvdoPilotSetsV2Pilot);
> + if (sets_len > 0) {
> + qcdm_result_add_u8_array (result,
> + PILOT_SETS_LOG_REMAINING_SET,
> + (const u_int8_t *) &pilot_sets->sets[pilot_sets->active_count + pilot_sets->candidate_count],
> + sets_len);
> + }
> +
> + return result;
> +
> +}
> +
> +qcdmbool
> +qcdm_log_item_evdo_pilot_sets_v2_get_num (QcdmResult *result,
> + u_int32_t set_type,
> + u_int32_t *out_num)
> +{
> + const char *set_name;
> + const u_int8_t *array = NULL;
> + size_t array_len = 0;
> +
> + qcdm_return_val_if_fail (result != NULL, FALSE);
> +
> + set_name = set_num_to_str (set_type);
> + qcdm_return_val_if_fail (set_name != NULL, FALSE);
> +
> + if (qcdm_result_get_u8_array (result, set_name, &array, &array_len))
> + return FALSE;
> +
> + *out_num = array_len / sizeof (DMLogItemEvdoPilotSetsV2Pilot);
> + return TRUE;
> +}
> +
> +#define MAX(a, b) (((a) > (b)) ? (a) : (b))
> +
> +qcdmbool
> +qcdm_log_item_evdo_pilot_sets_v2_get_pilot (QcdmResult *result,
> + u_int32_t set_type,
> + u_int32_t num,
> + u_int32_t *out_pilot_pn,
> + u_int32_t *out_pilot_energy,
> + int32_t *out_rssi_dbm)
> +{
> + const char *set_name;
> + DMLogItemEvdoPilotSetsV2Pilot *pilot;
> + const u_int8_t *array = NULL;
> + size_t array_len = 0;
> +
> + qcdm_return_val_if_fail (result != NULL, FALSE);
> +
> + set_name = set_num_to_str (set_type);
> + qcdm_return_val_if_fail (set_name != NULL, FALSE);
> +
> + if (qcdm_result_get_u8_array (result, set_name, &array, &array_len))
> + return FALSE;
> +
> + qcdm_return_val_if_fail (num < array_len / sizeof (DMLogItemEvdoPilotSetsV2Pilot), FALSE);
> +
> + pilot = (DMLogItemEvdoPilotSetsV2Pilot *) &array[num * sizeof (DMLogItemEvdoPilotSetsV2Pilot)];
> + *out_pilot_pn = le16toh (pilot->pilot_pn);
> + *out_pilot_energy = le16toh (pilot->pilot_energy);
> + *out_rssi_dbm = (int32_t) (-110.0 + ((float) MAX (le16toh (pilot->pilot_energy) - 50, 0) / 14.0));
> + return TRUE;
> +}
> +
> +/**********************************************************************/
> +
> diff --git a/libqcdm/src/logs.h b/libqcdm/src/logs.h
> new file mode 100644
> index 0000000..bb17d1f
> --- /dev/null
> +++ b/libqcdm/src/logs.h
> @@ -0,0 +1,50 @@
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
> +/*
> + * Copyright (C) 2010 Red Hat, Inc.
> + *
> + * This program is free software: you can redistribute it and/or
> + * modify it under the terms of version 2 of the GNU General Public
> + * License 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, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef LIBQCDM_LOGS_H
> +#define LIBQCDM_LOGS_H
> +
> +#include "utils.h"
> +#include "result.h"
> +
> +/**********************************************************************/
> +
> +enum {
> + QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_UNKNOWN = 0,
> + QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_ACTIVE = 1,
> + QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_CANDIDATE = 2,
> + QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_REMAINING = 3,
> +};
> +
> +QcdmResult *qcdm_log_item_evdo_pilot_sets_v2_new (const char *buf,
> + size_t len,
> + int *out_error);
> +
> +qcdmbool qcdm_log_item_evdo_pilot_sets_v2_get_num (QcdmResult *result,
> + u_int32_t set_type,
> + u_int32_t *out_num);
> +
> +qcdmbool qcdm_log_item_evdo_pilot_sets_v2_get_pilot (QcdmResult *result,
> + u_int32_t set_type,
> + u_int32_t num,
> + u_int32_t *out_pilot_pn,
> + u_int32_t *out_pilot_energy,
> + int32_t *out_rssi_dbm);
> +
> +/**********************************************************************/
> +
> +#endif /* LIBQCDM_LOGS_H */
> diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
> index e1fd7ca..04c716d 100644
> --- a/src/mm-broadband-modem.c
> +++ b/src/mm-broadband-modem.c
> @@ -53,6 +53,8 @@
> #include "mm-port-serial-qcdm.h"
> #include "libqcdm/src/errors.h"
> #include "libqcdm/src/commands.h"
> +#include "libqcdm/src/logs.h"
> +#include "libqcdm/src/log-items.h"
>
> static void iface_modem_init (MMIfaceModem *iface);
> static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
> @@ -172,6 +174,7 @@ struct _MMBroadbandModemPrivate {
> gboolean checked_sprint_support;
> gboolean has_spservice;
> gboolean has_speri;
> + gint evdo_pilot_rssi;
>
> /*<--- Modem Simple interface --->*/
> /* Properties */
> @@ -1713,6 +1716,45 @@ modem_load_supported_ip_families (MMIfaceModem *self,
> /*****************************************************************************/
> /* Signal quality loading (Modem interface) */
>
> +static void
> +qcdm_evdo_pilot_sets_log_handle (MMPortSerialQcdm *port,
> + GByteArray *log_buffer,
> + gpointer user_data)
> +{
> + MMBroadbandModem *self = MM_BROADBAND_MODEM (user_data);
> + QcdmResult *result;
> + u_int32_t num_active = 0;
> + u_int32_t pilot_pn = 0;
> + u_int32_t pilot_energy = 0;
> + int32_t rssi_dbm = 0;
> +
> + result = qcdm_log_item_evdo_pilot_sets_v2_new ((const char *) log_buffer->data,
> + log_buffer->len,
> + NULL);
> + if (!result)
> + return;
> +
> + if (!qcdm_log_item_evdo_pilot_sets_v2_get_num (result,
> + QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_ACTIVE,
> + &num_active)) {
> + qcdm_result_unref (result);
> + return;
> + }
> +
> + if (num_active > 0 &&
> + qcdm_log_item_evdo_pilot_sets_v2_get_pilot (result,
> + QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_ACTIVE,
> + 0,
> + &pilot_pn,
> + &pilot_energy,
> + &rssi_dbm)) {
> + mm_dbg ("EVDO active pilot RSSI: %ddBm", rssi_dbm);
> + self->priv->evdo_pilot_rssi = rssi_dbm;
> + }
> +
> + qcdm_result_unref (result);
> +}
> +
> typedef struct {
> MMBroadbandModem *self;
> GSimpleAsyncResult *result;
> @@ -1742,6 +1784,21 @@ modem_load_signal_quality_finish (MMIfaceModem *self,
> G_SIMPLE_ASYNC_RESULT (res)));
> }
>
> +static guint
> +signal_quality_evdo_pilot_sets (MMBroadbandModem *self)
> +{
> + gint dbm;
> +
> + if (self->priv->modem_cdma_evdo_registration_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
> + return 0;
> +
> + if (self->priv->evdo_pilot_rssi >= 0)
> + return 0;
> +
> + dbm = CLAMP (self->priv->evdo_pilot_rssi, -113, -51);
> + return 100 - ((dbm + 51) * 100 / (-113 + 51));
> +}
> +
> static void
> signal_quality_csq_ready (MMBroadbandModem *self,
> GAsyncResult *res,
> @@ -1767,8 +1824,11 @@ signal_quality_csq_ready (MMBroadbandModem *self,
> result_str = mm_strip_tag (result_str, "+CSQ:");
> if (sscanf (result_str, "%d, %d", &quality, &ber)) {
> if (quality == 99) {
> - /* 99 means unknown, no service, etc */
> - quality = 0;
> + /* 99 can mean unknown, no service, etc. But the modem may
> + * also only report CDMA 1x quality in CSQ, so try EVDO via
> + * QCDM log messages too.
> + */
> + quality = signal_quality_evdo_pilot_sets (self);
> } else {
> /* Normalize the quality */
> quality = CLAMP (quality, 0, 31) * 100 / 31;
> @@ -1977,6 +2037,17 @@ static void
> signal_quality_qcdm (SignalQualityContext *ctx)
> {
> GByteArray *pilot_sets;
> + guint quality;
> +
> + /* If EVDO is active try that signal strength first */
> + quality = signal_quality_evdo_pilot_sets (ctx->self);
> + if (quality > 0) {
> + g_simple_async_result_set_op_res_gpointer (ctx->result,
> + GUINT_TO_POINTER (quality),
> + NULL);
> + signal_quality_context_complete_and_free (ctx);
> + return;
> + }
>
> /* Use CDMA1x pilot EC/IO if we can */
> pilot_sets = g_byte_array_sized_new (25);
> @@ -6713,6 +6784,167 @@ modem_cdma_load_meid (MMIfaceModemCdma *self,
> }
>
> /*****************************************************************************/
> +/* Setup/Cleanup unsolicited events (CDMA interface) */
> +
> +typedef struct {
> + MMBroadbandModem *self;
> + gboolean setup;
> + GSimpleAsyncResult *result;
> + MMPortSerialQcdm *qcdm;
> +} CdmaUnsolicitedEventsContext;
> +
> +static void
> +cdma_unsolicited_events_context_complete_and_free (CdmaUnsolicitedEventsContext *ctx,
> + gboolean close_port,
> + GError *error)
> +{
> + if (error)
> + g_simple_async_result_take_error (ctx->result, error);
> + else
> + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
> + g_simple_async_result_complete_in_idle (ctx->result);
> +
> + g_clear_object (&ctx->result);
> + g_clear_object (&ctx->self);
> +
> + if (ctx->qcdm && close_port)
> + mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm));
> + g_clear_object (&ctx->qcdm);
> +
> + g_free (ctx);
> +}
> +
> +static void
> +logcmd_qcdm_ready (MMPortSerialQcdm *port,
> + GAsyncResult *res,
> + CdmaUnsolicitedEventsContext *ctx)
> +{
> + QcdmResult *result;
> + gint err = QCDM_SUCCESS;
> + GByteArray *response;
> + GError *error = NULL;
> +
> + response = mm_port_serial_qcdm_command_finish (port, res, &error);
> + if (error) {
> + cdma_unsolicited_events_context_complete_and_free (ctx, TRUE, error);
> + return;
> + }
> +
> + /* Parse the response */
> + result = qcdm_cmd_log_config_set_mask_result ((const gchar *) response->data,
> + response->len,
> + &err);
> + g_byte_array_unref (response);
> + if (!result) {
> + error = g_error_new (MM_CORE_ERROR,
> + MM_CORE_ERROR_FAILED,
> + "Failed to parse Log Config Set Mask command result: %d",
> + err);
> + cdma_unsolicited_events_context_complete_and_free (ctx, TRUE, error);
> + return;
> + }
> +
> + mm_port_serial_qcdm_add_unsolicited_msg_handler (port,
> + DM_LOG_ITEM_EVDO_PILOT_SETS_V2,
> + ctx->setup ? qcdm_evdo_pilot_sets_log_handle : NULL,
> + ctx->self,
> + NULL);
> +
> + qcdm_result_unref (result);
> +
> + /* Balance the mm_port_seral_open() from modem_cdma_setup_cleanup_unsolicited_events().
> + * We want to close it in either case:
> + * (a) we're cleaning up and setup opened the port
> + * (b) if it was unexpectedly closed before cleanup and thus cleanup opened it
> + *
> + * Setup should leave the port open to allow log messages to be received
> + * and sent to handlers.
> + */
> + cdma_unsolicited_events_context_complete_and_free (ctx, ctx->setup ? FALSE : TRUE, NULL);
> +}
> +
> +static void
> +modem_cdma_setup_cleanup_unsolicited_events (MMBroadbandModem *self,
> + gboolean setup,
> + GAsyncReadyCallback callback,
> + gpointer user_data)
> +{
> + CdmaUnsolicitedEventsContext *ctx;
> + GByteArray *logcmd;
> + u_int16_t log_items[] = { DM_LOG_ITEM_EVDO_PILOT_SETS_V2, 0 };
> + GError *error = NULL;
> +
> + ctx = g_new0 (CdmaUnsolicitedEventsContext, 1);
> + ctx->self = g_object_ref (self);
> + ctx->setup = TRUE;
> + ctx->result = g_simple_async_result_new (G_OBJECT (self),
> + callback,
> + user_data,
> + modem_cdma_setup_cleanup_unsolicited_events);
> + ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
> + if (!ctx->qcdm) {
> + cdma_unsolicited_events_context_complete_and_free (ctx, FALSE, NULL);
> + return;
> + }
> +
> + /* Setup must open the QCDM port and keep it open to receive unsolicited
> + * events. Cleanup expects the port to already be opened from setup, but
> + * if not we still want to open it and try to disable log messages.
> + */
> + if (setup || !mm_port_serial_is_open (MM_PORT_SERIAL (ctx->qcdm))) {
> + if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->qcdm), &error)) {
> + cdma_unsolicited_events_context_complete_and_free (ctx, FALSE, error);
> + return;
> + }
> + }
> +
> + logcmd = g_byte_array_sized_new (512);
> + logcmd->len = qcdm_cmd_log_config_set_mask_new ((char *) logcmd->data,
> + 512,
> + 0x01, /* Equipment ID */
> + setup ? log_items : NULL);
> + assert (logcmd->len);
> +
> + mm_port_serial_qcdm_command (ctx->qcdm,
> + logcmd,
> + 5,
> + NULL,
> + (GAsyncReadyCallback)logcmd_qcdm_ready,
> + ctx);
> + g_byte_array_unref (logcmd);
> +}
> +
> +static gboolean
> +modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self,
> + GAsyncResult *res,
> + GError **error)
> +{
> + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
> +}
> +
> +static void
> +modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self,
> + GAsyncReadyCallback callback,
> + gpointer user_data)
> +{
> + modem_cdma_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM (self),
> + TRUE,
> + callback,
> + user_data);
> +}
> +
> +static void
> +modem_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self,
> + GAsyncReadyCallback callback,
> + gpointer user_data)
> +{
> + modem_cdma_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM (self),
> + FALSE,
> + callback,
> + user_data);
> +}
> +
> +/*****************************************************************************/
> /* HDR state check (CDMA interface) */
>
> typedef struct {
> @@ -10429,6 +10661,10 @@ iface_modem_cdma_init (MMIfaceModemCdma *iface)
> iface->load_meid_finish = modem_cdma_load_meid_finish;
>
> /* Registration check steps */
> + iface->setup_unsolicited_events = modem_cdma_setup_unsolicited_events;
> + iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
> + iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events;
> + iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
> iface->setup_registration_checks = modem_cdma_setup_registration_checks;
> iface->setup_registration_checks_finish = modem_cdma_setup_registration_checks_finish;
> iface->get_call_manager_state = modem_cdma_get_call_manager_state;
> diff --git a/src/mm-port-serial-qcdm.c b/src/mm-port-serial-qcdm.c
> index 7732851..e997bb0 100644
> --- a/src/mm-port-serial-qcdm.c
> +++ b/src/mm-port-serial-qcdm.c
> @@ -26,10 +26,15 @@
> #include "libqcdm/src/com.h"
> #include "libqcdm/src/utils.h"
> #include "libqcdm/src/errors.h"
> +#include "libqcdm/src/dm-commands.h"
> #include "mm-log.h"
>
> G_DEFINE_TYPE (MMPortSerialQcdm, mm_port_serial_qcdm, MM_TYPE_PORT_SERIAL)
>
> +struct _MMPortSerialQcdmPrivate {
> + GSList *unsolicited_msg_handlers;
> +};
> +
> /*****************************************************************************/
>
> static gboolean
> @@ -60,10 +65,10 @@ find_qcdm_start (GByteArray *response, gsize *start)
> }
>
> static MMPortSerialResponseType
> -parse_response (MMPortSerial *port,
> - GByteArray *response,
> - GByteArray **parsed_response,
> - GError **error)
> +parse_qcdm (GByteArray *response,
> + gboolean want_log,
> + GByteArray **parsed_response,
> + GError **error)
> {
> gsize start = 0;
> gsize used = 0;
> @@ -111,6 +116,14 @@ parse_response (MMPortSerial *port,
> return MM_PORT_SERIAL_RESPONSE_NONE;
> }
>
> + if (want_log && unescaped_buffer[0] != DIAG_CMD_LOG) {
> + /* If we only want log items and this isn't one, don't remove this
> + * DM packet from the buffer.
> + */
> + g_free (unescaped_buffer);
> + return MM_PORT_SERIAL_RESPONSE_NONE;
> + }
> +
> /* Successfully decapsulated the DM command. We'll build a new byte array
> * with the response, and leave the input buffer cleaned up. */
> g_assert (unescaped_len <= 1024);
> @@ -124,6 +137,15 @@ parse_response (MMPortSerial *port,
> return MM_PORT_SERIAL_RESPONSE_BUFFER;
> }
>
> +static MMPortSerialResponseType
> +parse_response (MMPortSerial *port,
> + GByteArray *response,
> + GByteArray **parsed_response,
> + GError **error)
> +{
> + return parse_qcdm (response, FALSE, parsed_response, error);
> +}
> +
> /*****************************************************************************/
>
> GByteArray *
> @@ -202,6 +224,111 @@ debug_log (MMPortSerial *port, const char *prefix, const char *buf, gsize len)
>
> /*****************************************************************************/
>
> +typedef struct {
> + guint log_code;
> + MMPortSerialQcdmUnsolicitedMsgFn callback;
> + gboolean enable;
> + gpointer user_data;
> + GDestroyNotify notify;
> +} MMQcdmUnsolicitedMsgHandler;
> +
> +static gint
> +unsolicited_msg_handler_cmp (MMQcdmUnsolicitedMsgHandler *handler,
> + gpointer log_code)
> +{
> + return handler->log_code - GPOINTER_TO_UINT (log_code);
> +}
> +
> +void
> +mm_port_serial_qcdm_add_unsolicited_msg_handler (MMPortSerialQcdm *self,
> + guint log_code,
> + MMPortSerialQcdmUnsolicitedMsgFn callback,
> + gpointer user_data,
> + GDestroyNotify notify)
> +{
> + GSList *existing;
> + MMQcdmUnsolicitedMsgHandler *handler;
> +
> + g_return_if_fail (MM_IS_PORT_SERIAL_QCDM (self));
> + g_return_if_fail (log_code > 0 && log_code <= G_MAXUINT16);
> +
> + existing = g_slist_find_custom (self->priv->unsolicited_msg_handlers,
> + GUINT_TO_POINTER (log_code),
> + (GCompareFunc)unsolicited_msg_handler_cmp);
> + if (existing) {
> + handler = existing->data;
> + /* We OVERWRITE any existing one, so if any context data existing, free it */
> + if (handler->notify)
> + handler->notify (handler->user_data);
> + } else {
> + handler = g_slice_new (MMQcdmUnsolicitedMsgHandler);
> + self->priv->unsolicited_msg_handlers = g_slist_append (self->priv->unsolicited_msg_handlers, handler);
> + handler->log_code = log_code;
> + }
> +
> + handler->callback = callback;
> + handler->enable = TRUE;
> + handler->user_data = user_data;
> + handler->notify = notify;
> +}
> +
> +void
> +mm_port_serial_qcdm_enable_unsolicited_msg_handler (MMPortSerialQcdm *self,
> + guint log_code,
> + gboolean enable)
> +{
> + GSList *existing;
> + MMQcdmUnsolicitedMsgHandler *handler;
> +
> + g_return_if_fail (MM_IS_PORT_SERIAL_QCDM (self));
> + g_return_if_fail (log_code > 0 && log_code <= G_MAXUINT16);
> +
> + existing = g_slist_find_custom (self->priv->unsolicited_msg_handlers,
> + GUINT_TO_POINTER (log_code),
> + (GCompareFunc)unsolicited_msg_handler_cmp);
> + if (existing) {
> + handler = existing->data;
> + handler->enable = enable;
> + }
> +}
> +
> +static void
> +parse_unsolicited (MMPortSerial *port, GByteArray *response)
> +{
> + MMPortSerialQcdm *self = MM_PORT_SERIAL_QCDM (port);
> + GByteArray *log_buffer = NULL;
> + GSList *iter;
> +
> + if (parse_qcdm (response,
> + TRUE,
> + &log_buffer,
> + NULL) != MM_PORT_SERIAL_RESPONSE_BUFFER) {
> + return;
> + }
> +
> + /* These should be guaranteed by parse_qcdm() */
> + g_return_if_fail (log_buffer);
> + g_return_if_fail (log_buffer->len > 0);
> + g_return_if_fail (log_buffer->data[0] == DIAG_CMD_LOG);
> +
> + if (log_buffer->len < sizeof (DMCmdLog))
> + return;
> +
> + for (iter = self->priv->unsolicited_msg_handlers; iter; iter = iter->next) {
> + MMQcdmUnsolicitedMsgHandler *handler = (MMQcdmUnsolicitedMsgHandler *) iter->data;
> + DMCmdLog *log_cmd = (DMCmdLog *) log_buffer->data;
> +
> + if (!handler->enable)
> + continue;
> + if (handler->log_code != le16toh (log_cmd->log_code))
> + continue;
> + if (handler->callback)
> + handler->callback (self, log_buffer, handler->user_data);
> + }
> +}
> +
> +/*****************************************************************************/
> +
> static gboolean
> config_fd (MMPortSerial *port, int fd, GError **error)
> {
> @@ -250,14 +377,39 @@ mm_port_serial_qcdm_new_fd (int fd)
> static void
> mm_port_serial_qcdm_init (MMPortSerialQcdm *self)
> {
> + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_PORT_SERIAL_QCDM, MMPortSerialQcdmPrivate);
> +}
> +
> +static void
> +finalize (GObject *object)
> +{
> + MMPortSerialQcdm *self = MM_PORT_SERIAL_QCDM (object);
> +
> + while (self->priv->unsolicited_msg_handlers) {
> + MMQcdmUnsolicitedMsgHandler *handler = (MMQcdmUnsolicitedMsgHandler *) self->priv->unsolicited_msg_handlers->data;
> +
> + if (handler->notify)
> + handler->notify (handler->user_data);
> +
> + g_slice_free (MMQcdmUnsolicitedMsgHandler, handler);
> + self->priv->unsolicited_msg_handlers = g_slist_delete_link (self->priv->unsolicited_msg_handlers,
> + self->priv->unsolicited_msg_handlers);
> + }
> +
> + G_OBJECT_CLASS (mm_port_serial_qcdm_parent_class)->finalize (object);
> }
>
> static void
> mm_port_serial_qcdm_class_init (MMPortSerialQcdmClass *klass)
> {
> + GObjectClass *object_class = G_OBJECT_CLASS (klass);
> MMPortSerialClass *port_class = MM_PORT_SERIAL_CLASS (klass);
>
> + g_type_class_add_private (object_class, sizeof (MMPortSerialQcdmPrivate));
> +
> /* Virtual methods */
> + object_class->finalize = finalize;
> + port_class->parse_unsolicited = parse_unsolicited;
> port_class->parse_response = parse_response;
> port_class->config_fd = config_fd;
> port_class->debug_log = debug_log;
> diff --git a/src/mm-port-serial-qcdm.h b/src/mm-port-serial-qcdm.h
> index 5e3e38f..e7ba01f 100644
> --- a/src/mm-port-serial-qcdm.h
> +++ b/src/mm-port-serial-qcdm.h
> @@ -31,9 +31,11 @@
>
> typedef struct _MMPortSerialQcdm MMPortSerialQcdm;
> typedef struct _MMPortSerialQcdmClass MMPortSerialQcdmClass;
> +typedef struct _MMPortSerialQcdmPrivate MMPortSerialQcdmPrivate;
>
> struct _MMPortSerialQcdm {
> MMPortSerial parent;
> + MMPortSerialQcdmPrivate *priv;
> };
>
> struct _MMPortSerialQcdmClass {
> @@ -55,4 +57,18 @@ GByteArray *mm_port_serial_qcdm_command_finish (MMPortSerialQcdm *self,
> GAsyncResult *res,
> GError **error);
>
> +typedef void (*MMPortSerialQcdmUnsolicitedMsgFn) (MMPortSerialQcdm *port,
> + GByteArray *log_buffer,
> + gpointer user_data);
> +
> +void mm_port_serial_qcdm_add_unsolicited_msg_handler (MMPortSerialQcdm *self,
> + guint log_code,
> + MMPortSerialQcdmUnsolicitedMsgFn callback,
> + gpointer user_data,
> + GDestroyNotify notify);
> +
> +void mm_port_serial_qcdm_enable_unsolicited_msg_handler (MMPortSerialQcdm *self,
> + guint log_code,
> + gboolean enable);
> +
> #endif /* MM_PORT_SERIAL_QCDM_H */
> --
> 2.5.5
> _______________________________________________
> ModemManager-devel mailing list
> ModemManager-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/modemmanager-devel
--
Aleksander
https://aleksander.es
More information about the ModemManager-devel
mailing list