[PATCH] broadband-modem/libqcdm: use signal strength from QCDM EVDO Pilot Sets log messages

Dan Williams dcbw at redhat.com
Tue Jul 26 15:24:03 UTC 2016


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.
---
 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


More information about the ModemManager-devel mailing list