diff options
-rw-r--r-- | libqcdm/src/Makefile.am | 2 | ||||
-rw-r--r-- | libqcdm/src/log-items.h | 34 | ||||
-rw-r--r-- | libqcdm/src/logs.c | 184 | ||||
-rw-r--r-- | libqcdm/src/logs.h | 50 | ||||
-rw-r--r-- | src/mm-broadband-modem.c | 240 | ||||
-rw-r--r-- | src/mm-port-serial-qcdm.c | 160 | ||||
-rw-r--r-- | src/mm-port-serial-qcdm.h | 16 |
7 files changed, 663 insertions, 23 deletions
diff --git a/libqcdm/src/Makefile.am b/libqcdm/src/Makefile.am index 2caaf008..f13098f3 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 71f0f1e0..8802dfdb 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 00000000..ef604f6e --- /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 00000000..bb17d1f8 --- /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 e1fd7ca3..04c716d1 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 7732851f..e997bb06 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 5e3e38f5..e7ba01fe 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 */ |