broadband-modem/libqcdm: add signal strength from QCDM EVDO Pilot Sets log messages

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.
This commit is contained in:
Dan Williams
2016-07-27 10:44:43 -05:00
parent 30f60de7d3
commit 5350b9f568
7 changed files with 663 additions and 23 deletions
+2
View File
@@ -16,6 +16,8 @@ libqcdm_la_SOURCES = \
commands.h \
errors.c \
errors.h \
logs.c \
logs.h \
result.c \
result.h \
result-private.h \
+17 -17
View File
@@ -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;
+184
View File
@@ -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;
}
/**********************************************************************/
+50
View File
@@ -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 */
+238 -2
View File
@@ -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);
@@ -6712,6 +6783,167 @@ modem_cdma_load_meid (MMIfaceModemCdma *self,
user_data);
}
/*****************************************************************************/
/* 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) */
@@ -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;
+156 -4
View File
@@ -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;
+16
View File
@@ -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 */