diff options
author | Dan Williams <dcbw@redhat.com> | 2012-08-31 12:30:13 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2012-08-31 12:30:13 -0500 |
commit | 6f2c440b7be4e843c1eb193eb04f111924a89dfd (patch) | |
tree | 606772f6942301157f12b643139fa8c408cc6ba0 /src | |
parent | 04ce8b567cca9f3ab125f59af3bd8bfed554adce (diff) |
broadband-modem-qmi: fix handling of current and overall modem capabilities
Current capabilities is the set of *active* radios that can be used
right now. Modem capabilities are the set of all radios the modem
could use, if some action were performed to enable them if they are
not enabled already (firmware reload, changing allowed mode, etc).
For QMI devices, the DMS Get Capabilities command represents all
radios, and thus "modem capabilities".
But to read *current* capabilities, ie active radios, we need to
query the NAS System Selection Preference and grab the "mode
preference" TLV. Unfortunately that is only available with NAS
>= 1.1, which means older Gobi devices (1K and 2K) don't support
it. So for older devices, we try to get the Technology Preference
(which takes into account user-requested limitations) and then
mask that with the DMS Get Capabilities result for a best-effort
current capabilities.
For example, the Pantech UML290VW reports DMS Get Capabilities
of "cdma, evdo, gsm, umts, lte", but a more limited SSP mode
preference according to what modes are actually enabled. Gobi
1K devices don't support SSP, and the DMS Get Capabilities
reports cdma/evdo or gsm/umts depending on the currently loaded
firmware. Previous to this patch, ModemManager reported all
modes as available on the UML290, ignoring what modes were
actually enabled.
Diffstat (limited to 'src')
-rw-r--r-- | src/mm-broadband-modem-qmi.c | 310 | ||||
-rw-r--r-- | src/mm-modem-helpers-qmi.c | 57 | ||||
-rw-r--r-- | src/mm-modem-helpers-qmi.h | 4 |
3 files changed, 364 insertions, 7 deletions
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c index 3a718572..8e4c1168 100644 --- a/src/mm-broadband-modem-qmi.c +++ b/src/mm-broadband-modem-qmi.c @@ -146,7 +146,18 @@ modem_create_bearer (MMIfaceModem *self, } /*****************************************************************************/ -/* Capabilities loading (Modem interface) */ +/* Current Capabilities loading (Modem interface) */ + + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientNas *nas_client; + QmiClientDms *dms_client; + GSimpleAsyncResult *result; + gboolean run_get_system_selection_preference; + gboolean run_get_technology_preference; + gboolean run_get_capabilities; +} LoadCurrentCapabilitiesContext; static MMModemCapability modem_load_current_capabilities_finish (MMIfaceModem *self, @@ -169,6 +180,289 @@ modem_load_current_capabilities_finish (MMIfaceModem *self, } static void +load_current_capabilities_context_complete_and_free (LoadCurrentCapabilitiesContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->nas_client); + g_object_unref (ctx->dms_client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static void load_current_capabilities_context_step (LoadCurrentCapabilitiesContext *ctx); + +static void +load_current_capabilities_get_capabilities_ready (QmiClientDms *client, + GAsyncResult *res, + LoadCurrentCapabilitiesContext *ctx) +{ + MMModemCapability caps = MM_MODEM_CAPABILITY_NONE; + QmiMessageDmsGetCapabilitiesOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_dms_get_capabilities_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + } else if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get Capabilities: "); + g_simple_async_result_take_error (ctx->result, error); + } else { + guint i; + guint mask = MM_MODEM_CAPABILITY_NONE; + GArray *radio_interface_list; + + qmi_message_dms_get_capabilities_output_get_info ( + output, + NULL, /* info_max_tx_channel_rate */ + NULL, /* info_max_rx_channel_rate */ + NULL, /* info_data_service_capability */ + NULL, /* info_sim_capability */ + &radio_interface_list, + NULL); + + for (i = 0; i < radio_interface_list->len; i++) { + mask |= mm_modem_capability_from_qmi_radio_interface (g_array_index (radio_interface_list, + QmiDmsRadioInterface, + i)); + } + + /* Final capabilities are the intersection between the Technology + * Preference (ie, allowed modes) and the device's capabilities. If + * the Technology Preference was "auto" or unknown we just fall back to + * the Get Capabilities response. + */ + caps = ((MMModemCapability) GPOINTER_TO_UINT ( + g_simple_async_result_get_op_res_gpointer (ctx->result))); + if (caps == MM_MODEM_CAPABILITY_NONE) + caps = mask; + else + caps &= mask; + } + + if (output) + qmi_message_dms_get_capabilities_output_unref (output); + + g_simple_async_result_set_op_res_gpointer (ctx->result, GUINT_TO_POINTER (caps), NULL); + load_current_capabilities_context_complete_and_free (ctx); +} + +static void +load_current_capabilities_get_technology_preference_ready (QmiClientNas *client, + GAsyncResult *res, + LoadCurrentCapabilitiesContext *ctx) +{ + MMModemCapability caps = MM_MODEM_CAPABILITY_NONE; + QmiMessageNasGetTechnologyPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_get_technology_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_get_technology_preference_output_get_result (output, &error)) { + mm_dbg ("Couldn't get technology preference: %s", error->message); + g_error_free (error); + } else { + QmiNasRadioTechnologyPreference preference_mask; + + qmi_message_nas_get_technology_preference_output_get_active ( + output, + &preference_mask, + NULL, /* duration */ + NULL); + if (preference_mask != QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO) { + caps = mm_modem_capability_from_qmi_radio_technology_preference (preference_mask); + if (caps == MM_MODEM_CAPABILITY_NONE) { + gchar *str; + + str = qmi_nas_radio_technology_preference_build_string_from_mask (preference_mask); + mm_dbg ("Unsupported modes reported: '%s'", str); + g_free (str); + } + } + } + + if (output) + qmi_message_nas_get_technology_preference_output_unref (output); + + ctx->run_get_technology_preference = FALSE; + + /* Get DMS Capabilities too */ + g_simple_async_result_set_op_res_gpointer (ctx->result, GUINT_TO_POINTER (caps), NULL); + load_current_capabilities_context_step (ctx); +} + +static void +load_current_capabilities_get_system_selection_preference_ready (QmiClientNas *client, + GAsyncResult *res, + LoadCurrentCapabilitiesContext *ctx) +{ + MMModemCapability caps = MM_MODEM_CAPABILITY_NONE; + QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; + GError *error = NULL; + QmiNasRatModePreference mode_preference_mask = 0; + + output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) { + mm_dbg ("Couldn't get system selection preference: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_get_system_selection_preference_output_get_mode_preference ( + output, + &mode_preference_mask, + NULL)) { + mm_dbg ("Mode preference not reported in system selection preference"); + } else { + caps = mm_modem_capability_from_qmi_rat_mode_preference (mode_preference_mask); + if (caps == MM_MODEM_CAPABILITY_NONE) { + gchar *str; + + str = qmi_nas_rat_mode_preference_build_string_from_mask (mode_preference_mask); + mm_dbg ("Unsupported capabilities reported: '%s'", str); + g_free (str); + } + } + + if (output) + qmi_message_nas_get_system_selection_preference_output_unref (output); + + if (caps == MM_MODEM_CAPABILITY_NONE) { + /* Fall back to Technology Preference */ + ctx->run_get_system_selection_preference = FALSE; + load_current_capabilities_context_step (ctx); + return; + } + + /* Success */ + g_simple_async_result_set_op_res_gpointer (ctx->result, GUINT_TO_POINTER (caps), NULL); + load_current_capabilities_context_complete_and_free (ctx); +} + +static void +load_current_capabilities_context_step (LoadCurrentCapabilitiesContext *ctx) +{ + if (ctx->run_get_system_selection_preference) { + qmi_client_nas_get_system_selection_preference ( + ctx->nas_client, + NULL, /* no input */ + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)load_current_capabilities_get_system_selection_preference_ready, + ctx); + return; + } + + if (ctx->run_get_technology_preference) { + qmi_client_nas_get_technology_preference ( + ctx->nas_client, + NULL, /* no input */ + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)load_current_capabilities_get_technology_preference_ready, + ctx); + return; + } + + if (ctx->run_get_capabilities) { + qmi_client_dms_get_capabilities ( + ctx->dms_client, + NULL, /* no input */ + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)load_current_capabilities_get_capabilities_ready, + ctx); + return; + } + + g_simple_async_result_set_error ( + ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Loading current capabilities is not supported by this device"); + load_current_capabilities_context_complete_and_free (ctx); +} + +static void +modem_load_current_capabilities (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadCurrentCapabilitiesContext *ctx; + QmiClient *nas_client = NULL; + QmiClient *dms_client = NULL; + + /* Best way to get current capabilities (ie, enabled radios) is + * Get System Selection Preference's "mode preference" TLV, but that's + * only supported by NAS >= 1.1, meaning older Gobi devices don't + * implement it. + * + * On these devices, the DMS Get Capabilities call appears to report + * currently enabled radios, but this does not take the user's + * technology preference into account. + * + * So in the absence of System Selection Preference, we check the + * Technology Preference first, and if that is "AUTO" we fall back to + * Get Capabilities. + */ + + mm_dbg ("loading current capabilities..."); + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &nas_client, + callback, user_data)) + return; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &dms_client, + callback, user_data)) + return; + + ctx = g_new0 (LoadCurrentCapabilitiesContext, 1); + ctx->self = g_object_ref (self); + ctx->nas_client = g_object_ref (nas_client); + ctx->dms_client = g_object_ref (dms_client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_current_capabilities); + + /* System selection preference introduced in NAS 1.1 */ + ctx->run_get_system_selection_preference = qmi_client_check_version (nas_client, 1, 1); + + ctx->run_get_technology_preference = TRUE; + ctx->run_get_capabilities = TRUE; + + load_current_capabilities_context_step (ctx); +} + +/*****************************************************************************/ +/* Modem Capabilities loading (Modem interface) */ + +static MMModemCapability +modem_load_modem_capabilities_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + MMModemCapability caps; + gchar *caps_str; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_MODEM_CAPABILITY_NONE; + + caps = ((MMModemCapability) GPOINTER_TO_UINT ( + g_simple_async_result_get_op_res_gpointer ( + G_SIMPLE_ASYNC_RESULT (res)))); + caps_str = mm_modem_capability_build_string_from_mask (caps); + mm_dbg ("loaded modem capabilities: %s", caps_str); + g_free (caps_str); + return caps; +} + +static void dms_get_capabilities_ready (QmiClientDms *client, GAsyncResult *res, GSimpleAsyncResult *simple) @@ -181,7 +475,7 @@ dms_get_capabilities_ready (QmiClientDms *client, g_prefix_error (&error, "QMI operation failed: "); g_simple_async_result_take_error (simple, error); } else if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) { - g_prefix_error (&error, "Couldn't get Capabilities: "); + g_prefix_error (&error, "Couldn't get modem capabilities: "); g_simple_async_result_take_error (simple, error); } else { guint i; @@ -216,9 +510,9 @@ dms_get_capabilities_ready (QmiClientDms *client, } static void -modem_load_current_capabilities (MMIfaceModem *self, - GAsyncReadyCallback callback, - gpointer user_data) +modem_load_modem_capabilities (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) { GSimpleAsyncResult *result; QmiClient *client = NULL; @@ -231,9 +525,9 @@ modem_load_current_capabilities (MMIfaceModem *self, result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, - modem_load_current_capabilities); + modem_load_modem_capabilities); - mm_dbg ("loading current capabilities..."); + mm_dbg ("loading modem capabilities..."); qmi_client_dms_get_capabilities (QMI_CLIENT_DMS (client), NULL, 5, @@ -4730,6 +5024,8 @@ iface_modem_init (MMIfaceModem *iface) /* Initialization steps */ iface->load_current_capabilities = modem_load_current_capabilities; iface->load_current_capabilities_finish = modem_load_current_capabilities_finish; + iface->load_modem_capabilities = modem_load_modem_capabilities; + iface->load_modem_capabilities_finish = modem_load_modem_capabilities_finish; iface->load_manufacturer = modem_load_manufacturer; iface->load_manufacturer_finish = modem_load_manufacturer_finish; iface->load_model = modem_load_model; diff --git a/src/mm-modem-helpers-qmi.c b/src/mm-modem-helpers-qmi.c index c170dba6..0b5c4647 100644 --- a/src/mm-modem-helpers-qmi.c +++ b/src/mm-modem-helpers-qmi.c @@ -630,6 +630,63 @@ mm_modem_mode_to_qmi_rat_mode_preference (MMModemMode mode, /*****************************************************************************/ +MMModemCapability +mm_modem_capability_from_qmi_rat_mode_preference (QmiNasRatModePreference qmi) +{ + MMModemCapability caps = MM_MODEM_CAPABILITY_NONE; + + if (qmi & QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1X) + caps |= MM_MODEM_CAPABILITY_CDMA_EVDO; + + if (qmi & QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1XEVDO) + caps |= MM_MODEM_CAPABILITY_CDMA_EVDO; + + if (qmi & QMI_NAS_RAT_MODE_PREFERENCE_GSM) + caps |= MM_MODEM_CAPABILITY_GSM_UMTS; + + if (qmi & QMI_NAS_RAT_MODE_PREFERENCE_UMTS) + caps |= MM_MODEM_CAPABILITY_GSM_UMTS; + + if (qmi & QMI_NAS_RAT_MODE_PREFERENCE_LTE) + caps |= MM_MODEM_CAPABILITY_LTE; + + /* FIXME: LTE Advanced? */ + + return caps; +} + +/*****************************************************************************/ + +MMModemCapability +mm_modem_capability_from_qmi_radio_technology_preference (QmiNasRadioTechnologyPreference qmi) +{ + MMModemCapability caps = MM_MODEM_CAPABILITY_NONE; + + if (qmi & QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP2) { + /* Skip AMPS */ + if (qmi & QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA) + caps |= MM_MODEM_CAPABILITY_CDMA_EVDO; /* CDMA */ + if (qmi & QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_HDR) + caps |= MM_MODEM_CAPABILITY_CDMA_EVDO; /* EV-DO */ + } + + if (qmi & QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP) { + if (qmi & QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AMPS_OR_GSM) + caps |= MM_MODEM_CAPABILITY_GSM_UMTS; /* GSM */ + if (qmi & QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA) + caps |= MM_MODEM_CAPABILITY_GSM_UMTS; /* WCDMA */ + } + + if (qmi & QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_LTE) + caps |= MM_MODEM_CAPABILITY_LTE; + + /* FIXME: LTE Advanced? */ + + return caps; +} + +/*****************************************************************************/ + MMModemMode mm_modem_mode_from_qmi_gsm_wcdma_acquisition_order_preference (QmiNasGsmWcdmaAcquisitionOrderPreference qmi) { diff --git a/src/mm-modem-helpers-qmi.h b/src/mm-modem-helpers-qmi.h index 4a69653b..5c5df9dd 100644 --- a/src/mm-modem-helpers-qmi.h +++ b/src/mm-modem-helpers-qmi.h @@ -52,6 +52,10 @@ QmiNasRatModePreference mm_modem_mode_to_qmi_rat_mode_preference (MMModemMode mo gboolean is_cdma, gboolean is_3gpp); +MMModemCapability mm_modem_capability_from_qmi_rat_mode_preference (QmiNasRatModePreference qmi); + +MMModemCapability mm_modem_capability_from_qmi_radio_technology_preference (QmiNasRadioTechnologyPreference qmi); + MMModemMode mm_modem_mode_from_qmi_gsm_wcdma_acquisition_order_preference (QmiNasGsmWcdmaAcquisitionOrderPreference qmi); QmiNasGsmWcdmaAcquisitionOrderPreference mm_modem_mode_to_qmi_gsm_wcdma_acquisition_order_preference (MMModemMode mode); |