| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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: |
| * |
| * Copyright (C) 2008 - 2009 Novell, Inc. |
| * Copyright (C) 2009 - 2012 Red Hat, Inc. |
| * Copyright (C) 2011 - 2012 Google Inc. |
| * Copyright (C) 2012 Huawei Technologies Co., Ltd |
| * Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it> |
| * Copyright (C) 2012 - 2019 Aleksander Morgado <aleksander@aleksander.es> |
| */ |
| |
| #include <config.h> |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <time.h> |
| |
| #include <ModemManager.h> |
| #define _LIBMM_INSIDE_MM |
| #include <libmm-glib.h> |
| |
| #include "mm-log-object.h" |
| #include "mm-errors-types.h" |
| #include "mm-modem-helpers.h" |
| #include "mm-modem-helpers-huawei.h" |
| #include "mm-base-modem-at.h" |
| #include "mm-iface-modem.h" |
| #include "mm-iface-modem-3gpp.h" |
| #include "mm-iface-modem-3gpp-ussd.h" |
| #include "mm-iface-modem-location.h" |
| #include "mm-iface-modem-time.h" |
| #include "mm-iface-modem-cdma.h" |
| #include "mm-iface-modem-signal.h" |
| #include "mm-iface-modem-voice.h" |
| #include "mm-broadband-modem-huawei.h" |
| #include "mm-broadband-bearer-huawei.h" |
| #include "mm-broadband-bearer.h" |
| #include "mm-bearer-list.h" |
| #include "mm-sim-huawei.h" |
| |
| static void iface_modem_init (MMIfaceModem *iface); |
| static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); |
| static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface); |
| static void iface_modem_location_init (MMIfaceModemLocation *iface); |
| static void iface_modem_cdma_init (MMIfaceModemCdma *iface); |
| static void iface_modem_time_init (MMIfaceModemTime *iface); |
| static void iface_modem_voice_init (MMIfaceModemVoice *iface); |
| static void iface_modem_signal_init (MMIfaceModemSignal *iface); |
| |
| static MMIfaceModem *iface_modem_parent; |
| static MMIfaceModem3gpp *iface_modem_3gpp_parent; |
| static MMIfaceModemLocation *iface_modem_location_parent; |
| static MMIfaceModemCdma *iface_modem_cdma_parent; |
| static MMIfaceModemVoice *iface_modem_voice_parent; |
| |
| G_DEFINE_TYPE_EXTENDED (MMBroadbandModemHuawei, mm_broadband_modem_huawei, MM_TYPE_BROADBAND_MODEM, 0, |
| G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)) |
| |
| typedef enum { |
| FEATURE_SUPPORT_UNKNOWN, |
| FEATURE_NOT_SUPPORTED, |
| FEATURE_SUPPORTED |
| } FeatureSupport; |
| |
| typedef struct { |
| MMSignal *cdma; |
| MMSignal *evdo; |
| MMSignal *gsm; |
| MMSignal *umts; |
| MMSignal *lte; |
| MMSignal *nr5g; |
| } DetailedSignal; |
| |
| struct _MMBroadbandModemHuaweiPrivate { |
| /* Regex for signal quality related notifications */ |
| GRegex *rssi_regex; |
| GRegex *rssilvl_regex; |
| GRegex *hrssilvl_regex; |
| |
| /* Regex for access-technology related notifications */ |
| GRegex *mode_regex; |
| |
| /* Regex for connection status related notifications */ |
| GRegex *dsflowrpt_regex; |
| GRegex *ndisstat_regex; |
| |
| /* Regex for voice management notifications */ |
| GRegex *orig_regex; |
| GRegex *conf_regex; |
| GRegex *conn_regex; |
| GRegex *cend_regex; |
| GRegex *ddtmf_regex; |
| |
| /* Regex to ignore */ |
| GRegex *boot_regex; |
| GRegex *connect_regex; |
| GRegex *csnr_regex; |
| GRegex *cusatp_regex; |
| GRegex *cusatend_regex; |
| GRegex *dsdormant_regex; |
| GRegex *simst_regex; |
| GRegex *srvst_regex; |
| GRegex *stin_regex; |
| GRegex *hcsq_regex; |
| GRegex *pdpdeact_regex; |
| GRegex *ndisend_regex; |
| GRegex *rfswitch_regex; |
| GRegex *position_regex; |
| GRegex *posend_regex; |
| GRegex *ecclist_regex; |
| GRegex *ltersrp_regex; |
| GRegex *cschannelinfo_regex; |
| GRegex *ccallstate_regex; |
| GRegex *eons_regex; |
| |
| FeatureSupport ndisdup_support; |
| FeatureSupport rfswitch_support; |
| FeatureSupport sysinfoex_support; |
| FeatureSupport syscfg_support; |
| FeatureSupport syscfgex_support; |
| FeatureSupport prefmode_support; |
| FeatureSupport time_support; |
| FeatureSupport nwtime_support; |
| FeatureSupport cvoice_support; |
| |
| MMModemLocationSource enabled_sources; |
| |
| GArray *syscfg_supported_modes; |
| GArray *syscfgex_supported_modes; |
| GArray *prefmode_supported_modes; |
| |
| DetailedSignal detailed_signal; |
| |
| /* Voice call audio related properties */ |
| guint audio_hz; |
| guint audio_bits; |
| }; |
| |
| /*****************************************************************************/ |
| |
| GList * |
| mm_broadband_modem_huawei_get_at_port_list (MMBroadbandModemHuawei *self) |
| { |
| GList *out = NULL; |
| MMPortSerialAt *port; |
| GList *cdc_wdm_at_ports; |
| |
| /* Primary */ |
| port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); |
| if (port) |
| out = g_list_append (out, port); |
| |
| /* Secondary */ |
| port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); |
| if (port) |
| out = g_list_append (out, port); |
| |
| /* Additional cdc-wdm ports used for dialing */ |
| cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self), |
| MM_PORT_SUBSYS_USBMISC, |
| MM_PORT_TYPE_AT); |
| |
| return g_list_concat (out, cdc_wdm_at_ports); |
| } |
| |
| /*****************************************************************************/ |
| |
| typedef struct { |
| gboolean extended; |
| guint srv_status; |
| guint srv_domain; |
| guint roam_status; |
| guint sim_state; |
| guint sys_mode; |
| gboolean sys_submode_valid; |
| guint sys_submode; |
| } SysinfoResult; |
| |
| static gboolean |
| sysinfo_finish (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| gboolean *extended, |
| guint *srv_status, |
| guint *srv_domain, |
| guint *roam_status, |
| guint *sim_state, |
| guint *sys_mode, |
| gboolean *sys_submode_valid, |
| guint *sys_submode, |
| GError **error) |
| { |
| SysinfoResult *result; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| if (!result) |
| return FALSE; |
| |
| if (extended) |
| *extended = result->extended; |
| if (srv_status) |
| *srv_status = result->srv_status; |
| if (srv_domain) |
| *srv_domain = result->srv_domain; |
| if (roam_status) |
| *roam_status = result->roam_status; |
| if (sim_state) |
| *sim_state = result->sim_state; |
| if (sys_mode) |
| *sys_mode = result->sys_mode; |
| if (sys_submode_valid) |
| *sys_submode_valid = result->sys_submode_valid; |
| if (sys_submode) |
| *sys_submode = result->sys_submode; |
| |
| g_free (result); |
| return TRUE; |
| } |
| |
| static void |
| run_sysinfo_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| const gchar *response; |
| SysinfoResult *result; |
| |
| response = mm_base_modem_at_command_finish (self, res, &error); |
| if (!response) { |
| mm_obj_dbg (self, "^SYSINFO failed: %s", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| result = g_new0 (SysinfoResult, 1); |
| result->extended = FALSE; |
| if (!mm_huawei_parse_sysinfo_response (response, |
| &result->srv_status, |
| &result->srv_domain, |
| &result->roam_status, |
| &result->sys_mode, |
| &result->sim_state, |
| &result->sys_submode_valid, |
| &result->sys_submode, |
| &error)) { |
| mm_obj_dbg (self, "^SYSINFO parsing failed: %s", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| g_free (result); |
| return; |
| } |
| |
| g_task_return_pointer (task, result, g_free); |
| g_object_unref (task); |
| } |
| |
| static void |
| run_sysinfo (MMBroadbandModemHuawei *self, |
| GTask *task) |
| { |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^SYSINFO", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)run_sysinfo_ready, |
| task); |
| } |
| |
| static void |
| run_sysinfoex_ready (MMBaseModem *_self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GError *error = NULL; |
| const gchar *response; |
| SysinfoResult *result; |
| |
| response = mm_base_modem_at_command_finish (_self, res, &error); |
| if (!response) { |
| /* First time we try, we fallback to ^SYSINFO */ |
| if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN) { |
| self->priv->sysinfoex_support = FEATURE_NOT_SUPPORTED; |
| mm_obj_dbg (self, "^SYSINFOEX failed: %s, assuming unsupported", error->message); |
| g_error_free (error); |
| run_sysinfo (self, task); |
| return; |
| } |
| |
| /* Otherwise, propagate error */ |
| mm_obj_dbg (self, "^SYSINFOEX failed: %s", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN) |
| self->priv->sysinfoex_support = FEATURE_SUPPORTED; |
| |
| result = g_new0 (SysinfoResult, 1); |
| result->extended = TRUE; |
| if (!mm_huawei_parse_sysinfoex_response (response, |
| &result->srv_status, |
| &result->srv_domain, |
| &result->roam_status, |
| &result->sim_state, |
| &result->sys_mode, |
| &result->sys_submode, |
| &error)) { |
| mm_obj_dbg (self, "^SYSINFOEX parsing failed: %s", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| g_free (result); |
| return; |
| } |
| |
| /* Submode from SYSINFOEX always valid */ |
| result->sys_submode_valid = TRUE; |
| g_task_return_pointer (task, result, g_free); |
| g_object_unref (task); |
| } |
| |
| static void |
| run_sysinfoex (MMBroadbandModemHuawei *self, |
| GTask *task) |
| { |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^SYSINFOEX", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)run_sysinfoex_ready, |
| task); |
| } |
| |
| static void |
| sysinfo (MMBroadbandModemHuawei *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN || |
| self->priv->sysinfoex_support == FEATURE_SUPPORTED) |
| run_sysinfoex (self, task); |
| else |
| run_sysinfo (self, task); |
| } |
| |
| /*****************************************************************************/ |
| /* Reset (Modem interface) */ |
| |
| static gboolean |
| reset_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); |
| } |
| |
| static void |
| reset (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| const gchar *command; |
| |
| /* Unlike other Huawei modems that support AT^RESET for resetting the modem, |
| * Huawei MU736 supports AT^RESET but does not reset the modem upon receiving |
| * AT^RESET. It does, however, support resetting itself via AT+CFUN=16. |
| */ |
| if (g_strcmp0 (mm_iface_modem_get_model (self), "MU736") == 0) |
| command = "+CFUN=16"; |
| else |
| command = "^RESET"; |
| |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| command, |
| 3, |
| FALSE, |
| callback, |
| user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* Load access technologies (Modem interface) */ |
| |
| static MMModemAccessTechnology |
| huawei_sysinfo_submode_to_act (guint submode) |
| { |
| /* new more detailed system mode/access technology */ |
| switch (submode) { |
| case 1: |
| return MM_MODEM_ACCESS_TECHNOLOGY_GSM; |
| case 2: |
| return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; |
| case 3: |
| return MM_MODEM_ACCESS_TECHNOLOGY_EDGE; |
| case 4: |
| return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| case 5: |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; |
| case 6: |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; |
| case 7: |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSPA; |
| case 8: /* TD-SCDMA */ |
| break; |
| case 9: /* HSPA+ */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; |
| case 10: |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; |
| case 11: |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; |
| case 12: |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB; |
| case 13: /* 1xRTT */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| case 16: /* 3xRTT */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| case 17: /* HSPA+ (64QAM) */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; |
| case 18: /* HSPA+ (MIMO) */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; |
| default: |
| break; |
| } |
| |
| return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| } |
| |
| static MMModemAccessTechnology |
| huawei_sysinfo_mode_to_act (guint mode) |
| { |
| /* Older, less detailed system mode/access technology */ |
| switch (mode) { |
| case 1: /* AMPS */ |
| break; |
| case 2: /* CDMA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| case 3: /* GSM/GPRS */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; |
| case 4: /* HDR */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; |
| case 5: /* WCDMA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| case 6: /* GPS */ |
| break; |
| case 7: /* GSM/WCDMA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| case 8: /* CDMA/HDR hybrid */ |
| return (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 | MM_MODEM_ACCESS_TECHNOLOGY_1XRTT); |
| case 15: /* TD-SCDMA */ |
| break; |
| default: |
| break; |
| } |
| |
| return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| } |
| |
| static MMModemAccessTechnology |
| huawei_sysinfoex_submode_to_act (guint submode) |
| { |
| switch (submode) { |
| case 1: /* GSM */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_GSM; |
| case 2: /* GPRS */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; |
| case 3: /* EDGE */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_EDGE; |
| |
| case 21: /* IS95A */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| case 22: /* IS95B */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| case 23: /* CDMA2000 1x */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| case 24: /* EVDO rel0 */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; |
| case 25: /* EVDO relA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; |
| case 26: /* EVDO relB */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB; |
| case 27: /* Hybrid CDMA2000 1x */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| case 28: /* Hybrid EVDO rel0 */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; |
| case 29: /* Hybrid EVDO relA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; |
| case 30: /* Hybrid EVDO relB */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB; |
| |
| case 41: /* WCDMA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| case 42: /* HSDPA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; |
| case 43: /* HSUPA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; |
| case 44: /* HSPA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSPA; |
| case 45: /* HSPA+ */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; |
| case 46: /* DC-HSPA+ */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; |
| |
| case 61: /* TD-SCDMA */ |
| break; |
| |
| case 81: /* 802.16e (WiMAX) */ |
| break; |
| |
| case 101: /* LTE */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_LTE; |
| |
| default: |
| break; |
| } |
| |
| return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| } |
| |
| static MMModemAccessTechnology |
| huawei_sysinfoex_mode_to_act (guint mode) |
| { |
| /* Older, less detailed system mode/access technology */ |
| switch (mode) { |
| case 1: /* GSM */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_GSM; |
| case 2: /* CDMA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| case 3: /* WCDMA */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| case 4: /* TD-SCDMA */ |
| break; |
| case 5: /* WIMAX */ |
| break; |
| case 6: /* LTE */ |
| return MM_MODEM_ACCESS_TECHNOLOGY_LTE; |
| default: |
| break; |
| } |
| |
| return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| } |
| |
| static gboolean |
| load_access_technologies_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| MMModemAccessTechnology *access_technologies, |
| guint *mask, |
| GError **error) |
| { |
| MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| gboolean extended = FALSE; |
| guint srv_status = 0; |
| gboolean sys_submode_valid = FALSE; |
| guint sys_submode = 0; |
| guint sys_mode = 0; |
| |
| if (!sysinfo_finish (MM_BROADBAND_MODEM_HUAWEI (self), |
| res, |
| &extended, |
| &srv_status, |
| NULL, /* srv_domain */ |
| NULL, /* roam_status */ |
| NULL, /* sim_state */ |
| &sys_mode, |
| &sys_submode_valid, |
| &sys_submode, |
| error)) |
| return FALSE; |
| |
| if (srv_status != 0) { |
| /* Valid service */ |
| if (sys_submode_valid) |
| act = (extended ? |
| huawei_sysinfoex_submode_to_act (sys_submode) : |
| huawei_sysinfo_submode_to_act (sys_submode)); |
| |
| if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) |
| act = (extended ? |
| huawei_sysinfoex_mode_to_act (sys_mode) : |
| huawei_sysinfo_mode_to_act (sys_mode)); |
| } |
| |
| *access_technologies = act; |
| *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; |
| return TRUE; |
| } |
| |
| static void |
| load_access_technologies (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| sysinfo (MM_BROADBAND_MODEM_HUAWEI (self), callback, user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* Load unlock retries (Modem interface) */ |
| |
| static MMUnlockRetries * |
| load_unlock_retries_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| MMUnlockRetries *unlock_retries; |
| const gchar *result; |
| GRegex *r; |
| GMatchInfo *match_info = NULL; |
| GError *match_error = NULL; |
| guint i; |
| MMModemLock locks[4] = { |
| MM_MODEM_LOCK_SIM_PUK, |
| MM_MODEM_LOCK_SIM_PIN, |
| MM_MODEM_LOCK_SIM_PUK2, |
| MM_MODEM_LOCK_SIM_PIN2 |
| }; |
| |
| result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); |
| if (!result) |
| return NULL; |
| |
| r = g_regex_new ("\\^CPIN:\\s*([^,]+),[^,]*,(\\d+),(\\d+),(\\d+),(\\d+)", |
| G_REGEX_UNGREEDY, 0, NULL); |
| g_assert (r != NULL); |
| |
| if (!g_regex_match_full (r, result, strlen (result), 0, 0, &match_info, &match_error)) { |
| if (match_error) |
| g_propagate_error (error, match_error); |
| else |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Could not parse ^CPIN results: Response didn't match (%s)", |
| result); |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| return NULL; |
| } |
| |
| unlock_retries = mm_unlock_retries_new (); |
| for (i = 0; i <= 3; i++) { |
| guint num; |
| |
| if (!mm_get_uint_from_match_info (match_info, i + 2, &num) || |
| num > 10) { |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Could not parse ^CPIN results: " |
| "Missing or invalid match info for lock '%s'", |
| mm_modem_lock_get_string (locks[i])); |
| g_object_unref (unlock_retries); |
| unlock_retries = NULL; |
| break; |
| } |
| |
| mm_unlock_retries_set (unlock_retries, locks[i], num); |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| |
| return unlock_retries; |
| } |
| |
| static void |
| load_unlock_retries (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^CPIN?", |
| 3, |
| FALSE, |
| callback, |
| user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* After SIM unlock (Modem interface) */ |
| |
| static gboolean |
| modem_after_sim_unlock_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static gboolean |
| after_sim_unlock_wait_cb (GTask *task) |
| { |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| modem_after_sim_unlock (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* A 3-second wait is necessary for SIM to become ready, or the firmware may |
| * fail miserably and reboot itself */ |
| g_timeout_add_seconds (3, (GSourceFunc)after_sim_unlock_wait_cb, task); |
| } |
| |
| /*****************************************************************************/ |
| /* Common band/mode handling code */ |
| |
| typedef struct { |
| MMModemBand mm; |
| guint32 huawei; |
| } BandTable; |
| |
| static BandTable bands[] = { |
| /* Sort 3G first since it's preferred */ |
| { MM_MODEM_BAND_UTRAN_1, 0x00400000 }, |
| { MM_MODEM_BAND_UTRAN_2, 0x00800000 }, |
| { MM_MODEM_BAND_UTRAN_5, 0x04000000 }, |
| { MM_MODEM_BAND_UTRAN_8, 0x00020000 }, |
| /* 2G second */ |
| { MM_MODEM_BAND_G850, 0x00080000 }, |
| { MM_MODEM_BAND_DCS, 0x00000080 }, |
| { MM_MODEM_BAND_EGSM, 0x00000100 }, |
| { MM_MODEM_BAND_PCS, 0x00200000 } |
| }; |
| |
| static gboolean |
| bands_array_to_huawei (GArray *bands_array, |
| guint32 *out_huawei) |
| { |
| guint i; |
| |
| /* Treat ANY as a special case: All huawei flags enabled */ |
| if (bands_array->len == 1 && |
| g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { |
| *out_huawei = 0x3FFFFFFF; |
| return TRUE; |
| } |
| |
| *out_huawei = 0; |
| for (i = 0; i < bands_array->len; i++) { |
| guint j; |
| |
| for (j = 0; j < G_N_ELEMENTS (bands); j++) { |
| if (g_array_index (bands_array, MMModemBand, i) == bands[j].mm) |
| *out_huawei |= bands[j].huawei; |
| } |
| } |
| |
| return (*out_huawei > 0 ? TRUE : FALSE); |
| } |
| |
| static gboolean |
| huawei_to_bands_array (guint32 huawei, |
| GArray **bands_array, |
| GError **error) |
| { |
| guint i; |
| |
| *bands_array = NULL; |
| for (i = 0; i < G_N_ELEMENTS (bands); i++) { |
| if (huawei & bands[i].huawei) { |
| if (G_UNLIKELY (!*bands_array)) |
| *bands_array = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); |
| g_array_append_val (*bands_array, bands[i].mm); |
| } |
| } |
| |
| if (!*bands_array) { |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't build bands array from '%u'", |
| huawei); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| parse_syscfg (const gchar *response, |
| GArray **bands_array, |
| GError **error) |
| { |
| gint mode; |
| gint acquisition_order; |
| guint32 band; |
| gint roaming; |
| gint srv_domain; |
| |
| if (!response || |
| strncmp (response, "^SYSCFG:", 8) != 0 || |
| !sscanf (response + 8, "%d,%d,%x,%d,%d", &mode, &acquisition_order, &band, &roaming, &srv_domain)) { |
| /* Dump error to upper layer */ |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Unexpected SYSCFG response: '%s'", |
| response); |
| return FALSE; |
| } |
| |
| /* Band */ |
| if (bands_array && |
| !huawei_to_bands_array (band, bands_array, error)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* Load current bands (Modem interface) */ |
| |
| static GArray * |
| load_current_bands_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| const gchar *response; |
| GArray *bands_array = NULL; |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); |
| if (!response) |
| return NULL; |
| |
| if (!parse_syscfg (response, &bands_array, error)) |
| return NULL; |
| |
| return bands_array; |
| } |
| |
| static void |
| load_current_bands (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^SYSCFG?", |
| 3, |
| FALSE, |
| callback, |
| user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* Set current bands (Modem interface) */ |
| |
| static gboolean |
| set_current_bands_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| syscfg_set_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) |
| /* Let the error be critical */ |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| set_current_bands (MMIfaceModem *self, |
| GArray *bands_array, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| gchar *cmd; |
| guint32 huawei_band = 0x3FFFFFFF; |
| gchar *bands_string; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands_array->data, |
| bands_array->len); |
| |
| if (!bands_array_to_huawei (bands_array, &huawei_band)) { |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Invalid bands requested: '%s'", |
| bands_string); |
| g_object_unref (task); |
| g_free (bands_string); |
| return; |
| } |
| |
| cmd = g_strdup_printf ("AT^SYSCFG=16,3,%X,2,4", huawei_band); |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| cmd, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)syscfg_set_ready, |
| task); |
| g_free (cmd); |
| g_free (bands_string); |
| } |
| |
| /*****************************************************************************/ |
| /* Load supported modes (Modem interface) */ |
| |
| static GArray * |
| load_supported_modes_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| syscfg_test_ready (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); |
| if (response) { |
| /* There are 2G+3G Huawei modems out there which support mode switching with |
| * AT^SYSCFG, but fail to provide a valid response for AT^SYSCFG=? (they just |
| * return an empty string). So handle that case by providing a default response |
| * string to get parsed. Ugly, ugly, blame Huawei. |
| */ |
| if (response[0]) |
| self->priv->syscfg_supported_modes = mm_huawei_parse_syscfg_test (response, self, &error); |
| else { |
| self->priv->syscfg_supported_modes = mm_huawei_parse_syscfg_test (MM_HUAWEI_DEFAULT_SYSCFG_FMT, self, NULL); |
| g_assert (self->priv->syscfg_supported_modes != NULL); |
| } |
| } |
| |
| if (self->priv->syscfg_supported_modes) { |
| MMModemModeCombination mode; |
| guint i; |
| GArray *combinations; |
| |
| /* Build list of combinations */ |
| combinations = g_array_sized_new (FALSE, |
| FALSE, |
| sizeof (MMModemModeCombination), |
| self->priv->syscfg_supported_modes->len); |
| for (i = 0; i < self->priv->syscfg_supported_modes->len; i++) { |
| MMHuaweiSyscfgCombination *huawei_mode; |
| |
| huawei_mode = &g_array_index (self->priv->syscfg_supported_modes, |
| MMHuaweiSyscfgCombination, |
| i); |
| mode.allowed = huawei_mode->allowed; |
| mode.preferred = huawei_mode->preferred; |
| g_array_append_val (combinations, mode); |
| } |
| |
| self->priv->syscfg_support = FEATURE_SUPPORTED; |
| g_task_return_pointer (task, |
| combinations, |
| (GDestroyNotify)g_array_unref); |
| } else { |
| mm_obj_dbg (self, "error while checking ^SYSCFG format: %s", error->message); |
| /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */ |
| if (!g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) |
| self->priv->syscfg_support = FEATURE_NOT_SUPPORTED; |
| g_task_return_error (task, error); |
| } |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| syscfgex_test_ready (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); |
| if (response) |
| self->priv->syscfgex_supported_modes = mm_huawei_parse_syscfgex_test (response, &error); |
| |
| if (self->priv->syscfgex_supported_modes) { |
| MMModemModeCombination mode; |
| guint i; |
| GArray *combinations; |
| |
| /* Build list of combinations */ |
| combinations = g_array_sized_new (FALSE, |
| FALSE, |
| sizeof (MMModemModeCombination), |
| self->priv->syscfgex_supported_modes->len); |
| for (i = 0; i < self->priv->syscfgex_supported_modes->len; i++) { |
| MMHuaweiSyscfgexCombination *huawei_mode; |
| |
| huawei_mode = &g_array_index (self->priv->syscfgex_supported_modes, |
| MMHuaweiSyscfgexCombination, |
| i); |
| mode.allowed = huawei_mode->allowed; |
| mode.preferred = huawei_mode->preferred; |
| g_array_append_val (combinations, mode); |
| } |
| |
| self->priv->syscfgex_support = FEATURE_SUPPORTED; |
| |
| g_task_return_pointer (task, |
| combinations, |
| (GDestroyNotify)g_array_unref); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */ |
| if (error) { |
| mm_obj_dbg (self, "error while checking ^SYSCFGEX format: %s", error->message); |
| if (g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| g_error_free (error); |
| } |
| |
| self->priv->syscfgex_support = FEATURE_NOT_SUPPORTED; |
| |
| /* Try with SYSCFG */ |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^SYSCFG=?", |
| 3, |
| TRUE, |
| (GAsyncReadyCallback)syscfg_test_ready, |
| task); |
| } |
| |
| static void |
| prefmode_test_ready (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); |
| if (response) |
| self->priv->prefmode_supported_modes = mm_huawei_parse_prefmode_test (response, self, &error); |
| |
| if (self->priv->prefmode_supported_modes) { |
| MMModemModeCombination mode; |
| guint i; |
| GArray *combinations; |
| |
| /* Build list of combinations */ |
| combinations = g_array_sized_new (FALSE, |
| FALSE, |
| sizeof (MMModemModeCombination), |
| self->priv->prefmode_supported_modes->len); |
| for (i = 0; i < self->priv->prefmode_supported_modes->len; i++) { |
| MMHuaweiPrefmodeCombination *huawei_mode; |
| |
| huawei_mode = &g_array_index (self->priv->prefmode_supported_modes, |
| MMHuaweiPrefmodeCombination, |
| i); |
| mode.allowed = huawei_mode->allowed; |
| mode.preferred = huawei_mode->preferred; |
| g_array_append_val (combinations, mode); |
| } |
| |
| self->priv->prefmode_support = FEATURE_SUPPORTED; |
| g_task_return_pointer (task, |
| combinations, |
| (GDestroyNotify)g_array_unref); |
| } else { |
| mm_obj_dbg (self, "error while checking ^PREFMODE format: %s", error->message); |
| /* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */ |
| if (!g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) |
| self->priv->prefmode_support = FEATURE_NOT_SUPPORTED; |
| g_task_return_error (task, error); |
| } |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| load_supported_modes (MMIfaceModem *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (mm_iface_modem_is_cdma_only (_self)) { |
| /* ^PREFMODE only in CDMA-only modems */ |
| self->priv->syscfg_support = FEATURE_NOT_SUPPORTED; |
| self->priv->syscfgex_support = FEATURE_NOT_SUPPORTED; |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^PREFMODE=?", |
| 3, |
| TRUE, |
| (GAsyncReadyCallback)prefmode_test_ready, |
| task); |
| return; |
| } |
| |
| /* Check SYSCFGEX */ |
| self->priv->prefmode_support = FEATURE_NOT_SUPPORTED; |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^SYSCFGEX=?", |
| 3, |
| TRUE, |
| (GAsyncReadyCallback)syscfgex_test_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load initial allowed/preferred modes (Modem interface) */ |
| |
| static gboolean |
| load_current_modes_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| MMModemMode *allowed, |
| MMModemMode *preferred, |
| GError **error) |
| { |
| MMModemModeCombination *out; |
| |
| out = g_task_propagate_pointer (G_TASK (res), error); |
| if (!out) |
| return FALSE; |
| |
| *allowed = out->allowed; |
| *preferred = out->preferred; |
| |
| g_free (out); |
| return TRUE; |
| } |
| |
| static void |
| prefmode_load_current_modes_ready (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| const MMHuaweiPrefmodeCombination *current = NULL; |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); |
| if (response) |
| current = mm_huawei_parse_prefmode_response (response, |
| self->priv->prefmode_supported_modes, |
| &error); |
| |
| if (error) |
| g_task_return_error (task, error); |
| else { |
| MMModemModeCombination *out; |
| |
| out = g_new (MMModemModeCombination, 1); |
| out->allowed = current->allowed; |
| out->preferred = current->preferred; |
| g_task_return_pointer (task, out, g_free); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| syscfg_load_current_modes_ready (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| const MMHuaweiSyscfgCombination *current = NULL; |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); |
| if (response) |
| current = mm_huawei_parse_syscfg_response (response, |
| self->priv->syscfg_supported_modes, |
| &error); |
| |
| if (error) |
| g_task_return_error (task, error); |
| else { |
| MMModemModeCombination *out; |
| |
| out = g_new (MMModemModeCombination, 1); |
| out->allowed = current->allowed; |
| out->preferred = current->preferred; |
| g_task_return_pointer (task, out, g_free); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| syscfgex_load_current_modes_ready (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response; |
| GError *error = NULL; |
| const MMHuaweiSyscfgexCombination *current = NULL; |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); |
| if (response) |
| current = mm_huawei_parse_syscfgex_response (response, |
| self->priv->syscfgex_supported_modes, |
| &error); |
| if (error) |
| g_task_return_error (task, error); |
| else { |
| MMModemModeCombination *out; |
| |
| out = g_new (MMModemModeCombination, 1); |
| out->allowed = current->allowed; |
| out->preferred = current->preferred; |
| g_task_return_pointer (task, out, g_free); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| load_current_modes (MMIfaceModem *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (self->priv->syscfgex_support == FEATURE_SUPPORTED) { |
| g_assert (self->priv->syscfgex_supported_modes != NULL); |
| mm_base_modem_at_command ( |
| MM_BASE_MODEM (self), |
| "^SYSCFGEX?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)syscfgex_load_current_modes_ready, |
| task); |
| return; |
| } |
| |
| if (self->priv->syscfg_support == FEATURE_SUPPORTED) { |
| g_assert (self->priv->syscfg_supported_modes != NULL); |
| mm_base_modem_at_command ( |
| MM_BASE_MODEM (self), |
| "^SYSCFG?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)syscfg_load_current_modes_ready, |
| task); |
| return; |
| } |
| |
| if (self->priv->prefmode_support == FEATURE_SUPPORTED) { |
| g_assert (self->priv->prefmode_supported_modes != NULL); |
| mm_base_modem_at_command ( |
| MM_BASE_MODEM (self), |
| "^PREFMODE?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)prefmode_load_current_modes_ready, |
| task); |
| return; |
| } |
| |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Unable to load current modes"); |
| g_object_unref (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Set current modes (Modem interface) */ |
| |
| static gboolean |
| set_current_modes_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| set_current_modes_ready (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); |
| if (error) |
| /* Let the error be critical. */ |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static gboolean |
| prefmode_set_current_modes (MMBroadbandModemHuawei *self, |
| MMModemMode allowed, |
| MMModemMode preferred, |
| GTask *task, |
| GError **error) |
| { |
| guint i; |
| MMHuaweiPrefmodeCombination *found = NULL; |
| gchar *command; |
| |
| for (i = 0; i < self->priv->prefmode_supported_modes->len; i++) { |
| MMHuaweiPrefmodeCombination *single; |
| |
| single = &g_array_index (self->priv->prefmode_supported_modes, |
| MMHuaweiPrefmodeCombination, |
| i); |
| if (single->allowed == allowed && single->preferred == preferred) { |
| found = single; |
| break; |
| } |
| } |
| |
| if (!found) { |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_NOT_FOUND, |
| "Requested mode ^PREFMODE combination not found"); |
| return FALSE; |
| } |
| |
| command = g_strdup_printf ("^PREFMODE=%u", found->prefmode); |
| mm_base_modem_at_command ( |
| MM_BASE_MODEM (self), |
| command, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)set_current_modes_ready, |
| task); |
| g_free (command); |
| return TRUE; |
| } |
| |
| static gboolean |
| syscfg_set_current_modes (MMBroadbandModemHuawei *self, |
| MMModemMode allowed, |
| MMModemMode preferred, |
| GTask *task, |
| GError **error) |
| { |
| guint i; |
| MMHuaweiSyscfgCombination *found = NULL; |
| gchar *command; |
| |
| for (i = 0; i < self->priv->syscfg_supported_modes->len; i++) { |
| MMHuaweiSyscfgCombination *single; |
| |
| single = &g_array_index (self->priv->syscfg_supported_modes, |
| MMHuaweiSyscfgCombination, |
| i); |
| if (single->allowed == allowed && single->preferred == preferred) { |
| found = single; |
| break; |
| } |
| } |
| |
| if (!found) { |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_NOT_FOUND, |
| "Requested mode ^SYSCFG combination not found"); |
| return FALSE; |
| } |
| |
| command = g_strdup_printf ("^SYSCFG=%u,%u,40000000,2,4", |
| found->mode, |
| found->acqorder); |
| mm_base_modem_at_command ( |
| MM_BASE_MODEM (self), |
| command, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)set_current_modes_ready, |
| task); |
| g_free (command); |
| return TRUE; |
| } |
| |
| static gboolean |
| syscfgex_set_current_modes (MMBroadbandModemHuawei *self, |
| MMModemMode allowed, |
| MMModemMode preferred, |
| GTask *task, |
| GError **error) |
| { |
| guint i; |
| MMHuaweiSyscfgexCombination *found = NULL; |
| gchar *command; |
| |
| for (i = 0; i < self->priv->syscfgex_supported_modes->len; i++) { |
| MMHuaweiSyscfgexCombination *single; |
| |
| single = &g_array_index (self->priv->syscfgex_supported_modes, |
| MMHuaweiSyscfgexCombination, |
| i); |
| if (single->allowed == allowed && single->preferred == preferred) { |
| found = single; |
| break; |
| } |
| } |
| |
| if (!found) { |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_NOT_FOUND, |
| "Requested mode ^SYSCFGEX combination not found"); |
| return FALSE; |
| } |
| |
| command = g_strdup_printf ("^SYSCFGEX=\"%s\",3fffffff,2,4,7fffffffffffffff,,", |
| found->mode_str); |
| mm_base_modem_at_command ( |
| MM_BASE_MODEM (self), |
| command, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)set_current_modes_ready, |
| task); |
| g_free (command); |
| return TRUE; |
| } |
| |
| static void |
| set_current_modes (MMIfaceModem *_self, |
| MMModemMode allowed, |
| MMModemMode preferred, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GTask *task; |
| GError *error = NULL; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (self->priv->syscfgex_support == FEATURE_SUPPORTED) |
| syscfgex_set_current_modes (self, allowed, preferred, task, &error); |
| else if (self->priv->syscfg_support == FEATURE_SUPPORTED) |
| syscfg_set_current_modes (self, allowed, preferred, task, &error); |
| else if (self->priv->prefmode_support == FEATURE_SUPPORTED) |
| prefmode_set_current_modes (self, allowed, preferred, task, &error); |
| else |
| error = g_error_new (MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Setting current modes is not supported"); |
| |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| } |
| } |
| |
| /*****************************************************************************/ |
| /* Setup/Cleanup unsolicited events (3GPP interface) */ |
| |
| static void |
| huawei_signal_changed (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| guint quality = 0; |
| |
| if (!mm_get_uint_from_match_info (match_info, 1, &quality)) |
| return; |
| |
| if (quality == 99) { |
| /* 99 means unknown */ |
| quality = 0; |
| } else { |
| /* Normalize the quality */ |
| quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31; |
| } |
| |
| mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); |
| } |
| |
| static void |
| huawei_mode_changed (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| gchar *str; |
| gint a; |
| guint32 mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| |
| str = g_match_info_fetch (match_info, 1); |
| a = atoi (str); |
| g_free (str); |
| |
| /* CDMA/EVDO devices may not send this */ |
| str = g_match_info_fetch (match_info, 2); |
| if (str[0]) |
| act = huawei_sysinfo_submode_to_act (atoi (str)); |
| g_free (str); |
| |
| switch (a) { |
| case 3: |
| /* GSM/GPRS mode */ |
| if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN && |
| (act < MM_MODEM_ACCESS_TECHNOLOGY_GSM || |
| act > MM_MODEM_ACCESS_TECHNOLOGY_EDGE)) { |
| str = mm_modem_access_technology_build_string_from_mask (act); |
| mm_obj_warn (self, "unexpected access technology (%s) in GSM/GPRS mode", str); |
| g_free (str); |
| act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| } |
| mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; |
| break; |
| |
| case 5: |
| /* WCDMA mode */ |
| if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN && |
| (act < MM_MODEM_ACCESS_TECHNOLOGY_UMTS || |
| act > MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)) { |
| str = mm_modem_access_technology_build_string_from_mask (act); |
| mm_obj_warn (self, "unexpected access technology (%s) in WCDMA mode", str); |
| g_free (str); |
| act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| } |
| mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; |
| break; |
| |
| case 2: |
| /* CDMA mode */ |
| if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN && |
| act != MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) { |
| str = mm_modem_access_technology_build_string_from_mask (act); |
| mm_obj_warn (self, "unexpected access technology (%s) in CDMA mode", str); |
| g_free (str); |
| act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| } |
| if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) |
| act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; |
| mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK; |
| break; |
| |
| case 4: /* HDR mode */ |
| case 8: /* CDMA/HDR hybrid mode */ |
| if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN && |
| (act < MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 || |
| act > MM_MODEM_ACCESS_TECHNOLOGY_EVDOB)) { |
| str = mm_modem_access_technology_build_string_from_mask (act); |
| mm_obj_warn (self, "unexpected access technology (%s) in EVDO mode", str); |
| g_free (str); |
| act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| } |
| if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) |
| act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; |
| mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK; |
| break; |
| |
| case 0: |
| act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| break; |
| |
| default: |
| mm_obj_warn (self, "unexpected mode change value reported: '%d'", a); |
| return; |
| } |
| |
| mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), act, mask); |
| } |
| |
| static void |
| huawei_status_changed (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| gchar *str; |
| gint n1, n2, n3, n4, n5, n6, n7; |
| |
| str = g_match_info_fetch (match_info, 1); |
| if (sscanf (str, "%x,%x,%x,%x,%x,%x,%x", &n1, &n2, &n3, &n4, &n5, &n6, &n7)) |
| mm_obj_dbg (self, "duration: %d up: %d Kbps down: %d Kbps total: %d total: %d\n", |
| n1, n2 * 8 / 1000, n3 * 8 / 1000, n4 / 1024, n5 / 1024); |
| g_free (str); |
| } |
| |
| typedef struct { |
| gboolean ipv4_available; |
| gboolean ipv4_connected; |
| gboolean ipv6_available; |
| gboolean ipv6_connected; |
| } NdisstatResult; |
| |
| static void |
| bearer_report_connection_status (MMBaseBearer *bearer, |
| NdisstatResult *ndisstat_result) |
| { |
| if (ndisstat_result->ipv4_available) { |
| /* TODO: MMBroadbandBearerHuawei does not currently support IPv6. |
| * When it does, we should check the IP family associated with each bearer. */ |
| mm_base_bearer_report_connection_status (bearer, |
| ndisstat_result->ipv4_connected ? |
| MM_BEARER_CONNECTION_STATUS_CONNECTED : |
| MM_BEARER_CONNECTION_STATUS_DISCONNECTED); |
| } |
| } |
| |
| static void |
| huawei_ndisstat_changed (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| gchar *str; |
| NdisstatResult ndisstat_result; |
| GError *error = NULL; |
| MMBearerList *list = NULL; |
| |
| str = g_match_info_fetch (match_info, 1); |
| if (!mm_huawei_parse_ndisstatqry_response (str, |
| &ndisstat_result.ipv4_available, |
| &ndisstat_result.ipv4_connected, |
| &ndisstat_result.ipv6_available, |
| &ndisstat_result.ipv6_connected, |
| &error)) { |
| mm_obj_dbg (self, "ignored invalid ^NDISSTAT unsolicited message '%s': %s", |
| str, error->message); |
| g_error_free (error); |
| g_free (str); |
| return; |
| } |
| g_free (str); |
| |
| mm_obj_dbg (self, "NDIS status: IPv4 %s, IPv6 %s", |
| ndisstat_result.ipv4_available ? |
| (ndisstat_result.ipv4_connected ? "connected" : "disconnected") : "not available", |
| ndisstat_result.ipv6_available ? |
| (ndisstat_result.ipv6_connected ? "connected" : "disconnected") : "not available"); |
| |
| /* If empty bearer list, nothing else to do */ |
| g_object_get (self, |
| MM_IFACE_MODEM_BEARER_LIST, &list, |
| NULL); |
| if (!list) |
| return; |
| |
| mm_bearer_list_foreach (list, |
| (MMBearerListForeachFunc)bearer_report_connection_status, |
| &ndisstat_result); |
| |
| g_object_unref (list); |
| } |
| |
| static void |
| detailed_signal_clear (DetailedSignal *signal) |
| { |
| g_clear_object (&signal->cdma); |
| g_clear_object (&signal->evdo); |
| g_clear_object (&signal->gsm); |
| g_clear_object (&signal->umts); |
| g_clear_object (&signal->lte); |
| } |
| |
| static gboolean |
| get_rssi_dbm (guint rssi, gdouble *out_val) |
| { |
| if (rssi <= 96) { |
| *out_val = (double) (-121.0 + rssi); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| get_ecio_db (guint ecio, gdouble *out_val) |
| { |
| if (ecio <= 65) { |
| *out_val = -32.5 + ((double) ecio / 2.0); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| get_rsrp_dbm (guint rsrp, gdouble *out_val) |
| { |
| if (rsrp <= 97) { |
| *out_val = (double) (-141.0 + rsrp); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| get_sinr_db (guint sinr, gdouble *out_val) |
| { |
| if (sinr <= 251) { |
| *out_val = -20.2 + (double) (sinr / 5.0); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| get_rsrq_db (guint rsrq, gdouble *out_val) |
| { |
| if (rsrq <= 34) { |
| *out_val = -20 + (double) (rsrq / 2.0); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static void |
| huawei_hcsq_changed (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| gchar *str; |
| MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| guint value1 = 0; |
| guint value2 = 0; |
| guint value3 = 0; |
| guint value4 = 0; |
| guint value5 = 0; |
| gdouble v; |
| GError *error = NULL; |
| |
| str = g_match_info_fetch (match_info, 1); |
| if (!mm_huawei_parse_hcsq_response (str, |
| &act, |
| &value1, |
| &value2, |
| &value3, |
| &value4, |
| &value5, |
| &error)) { |
| mm_obj_dbg (self, "ignored invalid ^HCSQ message '%s': %s", str, error->message); |
| g_error_free (error); |
| g_free (str); |
| return; |
| } |
| g_free (str); |
| |
| detailed_signal_clear (&self->priv->detailed_signal); |
| |
| /* 2G */ |
| if (act == MM_MODEM_ACCESS_TECHNOLOGY_GSM) { |
| self->priv->detailed_signal.gsm = mm_signal_new (); |
| /* value1: gsm_rssi */ |
| if (get_rssi_dbm (value1, &v)) |
| mm_signal_set_rssi (self->priv->detailed_signal.gsm, v); |
| return; |
| } |
| |
| /* 3G */ |
| if (act == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) { |
| self->priv->detailed_signal.umts = mm_signal_new (); |
| /* value1: wcdma_rssi */ |
| if (get_rssi_dbm (value1, &v)) |
| mm_signal_set_rssi (self->priv->detailed_signal.umts, v); |
| /* value2: wcdma_rscp; unused */ |
| /* value3: wcdma_ecio */ |
| if (get_ecio_db (value3, &v)) |
| mm_signal_set_ecio (self->priv->detailed_signal.umts, v); |
| return; |
| } |
| |
| /* 4G */ |
| if (act == MM_MODEM_ACCESS_TECHNOLOGY_LTE) { |
| self->priv->detailed_signal.lte = mm_signal_new (); |
| /* value1: lte_rssi */ |
| if (get_rssi_dbm (value1, &v)) |
| mm_signal_set_rssi (self->priv->detailed_signal.lte, v); |
| /* value2: lte_rsrp */ |
| if (get_rsrp_dbm (value2, &v)) |
| mm_signal_set_rsrp (self->priv->detailed_signal.lte, v); |
| /* value3: lte_sinr -> SNR? */ |
| if (get_sinr_db (value3, &v)) |
| mm_signal_set_snr (self->priv->detailed_signal.lte, v); |
| /* value4: lte_rsrq */ |
| if (get_rsrq_db (value4, &v)) |
| mm_signal_set_rsrq (self->priv->detailed_signal.lte, v); |
| return; |
| } |
| |
| /* CDMA and EVDO not yet supported */ |
| } |
| |
| static void |
| set_3gpp_unsolicited_events_handlers (MMBroadbandModemHuawei *self, |
| gboolean enable) |
| { |
| GList *ports, *l; |
| |
| ports = mm_broadband_modem_huawei_get_at_port_list (self); |
| |
| /* Enable/disable unsolicited events in given port */ |
| for (l = ports; l; l = g_list_next (l)) { |
| MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data); |
| |
| /* Signal quality related */ |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->rssi_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_signal_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| |
| /* Access technology related */ |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->mode_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_mode_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| |
| /* Connection status related */ |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->dsflowrpt_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_status_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->ndisstat_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_ndisstat_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->hcsq_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_hcsq_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| } |
| |
| g_list_free_full (ports, g_object_unref); |
| } |
| |
| static gboolean |
| modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| parent_3gpp_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) |
| g_task_return_error (task, error); |
| else { |
| /* Our own setup now */ |
| set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE); |
| g_task_return_boolean (task, TRUE); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Chain up parent's setup */ |
| iface_modem_3gpp_parent->setup_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_3gpp_setup_unsolicited_events_ready, |
| task); |
| } |
| |
| static void |
| parent_3gpp_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Our own cleanup first */ |
| set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); |
| |
| /* And now chain up parent's cleanup */ |
| iface_modem_3gpp_parent->cleanup_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_3gpp_cleanup_unsolicited_events_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Enabling unsolicited events (3GPP interface) */ |
| |
| static gboolean |
| modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| own_enable_unsolicited_events_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); |
| if (error) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static const MMBaseModemAtCommand unsolicited_enable_sequence[] = { |
| /* With ^PORTSEL we specify whether we want the PCUI port (0) or the |
| * modem port (1) to receive the unsolicited messages */ |
| { "^PORTSEL=0", 5, FALSE, NULL }, |
| { "^CURC=1", 3, FALSE, NULL }, |
| { NULL } |
| }; |
| |
| static void |
| parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| } |
| |
| /* Our own enable now */ |
| mm_base_modem_at_sequence_full ( |
| MM_BASE_MODEM (self), |
| mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), |
| unsolicited_enable_sequence, |
| NULL, /* response_processor_context */ |
| NULL, /* response_processor_context_free */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)own_enable_unsolicited_events_ready, |
| task); |
| } |
| |
| static void |
| modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Chain up parent's enable */ |
| iface_modem_3gpp_parent->enable_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Disabling unsolicited events (3GPP interface) */ |
| |
| static gboolean |
| modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| own_disable_unsolicited_events_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_full_finish (self, res, &error); |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Next, chain up parent's disable */ |
| iface_modem_3gpp_parent->disable_unsolicited_events ( |
| MM_IFACE_MODEM_3GPP (self), |
| (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, |
| task); |
| } |
| |
| static void |
| modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Our own disable first */ |
| mm_base_modem_at_command_full ( |
| MM_BASE_MODEM (self), |
| mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), |
| "^CURC=0", |
| 5, |
| FALSE, /* allow_cached */ |
| FALSE, /* raw */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)own_disable_unsolicited_events_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Create Bearer (Modem interface) */ |
| |
| static MMBaseBearer * |
| huawei_modem_create_bearer_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| broadband_bearer_huawei_new_ready (GObject *source, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseBearer *bearer; |
| GError *error = NULL; |
| |
| bearer = mm_broadband_bearer_huawei_new_finish (res, &error); |
| if (!bearer) |
| g_task_return_error (task, error); |
| else |
| g_task_return_pointer (task, bearer, g_object_unref); |
| g_object_unref (task); |
| } |
| |
| static void |
| broadband_bearer_new_ready (GObject *source, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseBearer *bearer; |
| GError *error = NULL; |
| |
| bearer = mm_broadband_bearer_new_finish (res, &error); |
| if (!bearer) |
| g_task_return_error (task, error); |
| else |
| g_task_return_pointer (task, bearer, g_object_unref); |
| g_object_unref (task); |
| } |
| |
| static void |
| create_bearer_for_net_port (GTask *task) |
| { |
| MMBroadbandModemHuawei *self; |
| MMBearerProperties *properties; |
| |
| self = g_task_get_source_object (task); |
| properties = g_task_get_task_data (task); |
| |
| switch (self->priv->ndisdup_support) { |
| case FEATURE_NOT_SUPPORTED: |
| mm_obj_dbg (self, "^NDISDUP not supported, creating default bearer..."); |
| mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), |
| properties, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)broadband_bearer_new_ready, |
| task); |
| return; |
| case FEATURE_SUPPORTED: |
| mm_obj_dbg (self, "^NDISDUP supported, creating huawei bearer..."); |
| mm_broadband_bearer_huawei_new (MM_BROADBAND_MODEM_HUAWEI (self), |
| properties, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)broadband_bearer_huawei_new_ready, |
| task); |
| return; |
| case FEATURE_SUPPORT_UNKNOWN: |
| default: |
| g_assert_not_reached (); |
| } |
| } |
| |
| static MMPortSerialAt * |
| peek_port_at_for_data (MMBroadbandModemHuawei *self, |
| MMPort *port) |
| { |
| GList *cdc_wdm_at_ports, *l; |
| const gchar *net_port_parent_path; |
| MMPortSerialAt *found = NULL; |
| |
| g_warn_if_fail (mm_port_get_subsys (port) == MM_PORT_SUBSYS_NET); |
| net_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (port)); |
| if (!net_port_parent_path) { |
| mm_obj_warn (self, "no parent path for net port %s", mm_port_get_device (port)); |
| return NULL; |
| } |
| |
| /* Find the CDC-WDM port on the same USB interface as the given net port */ |
| cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self), |
| MM_PORT_SUBSYS_USBMISC, |
| MM_PORT_TYPE_AT); |
| for (l = cdc_wdm_at_ports; l && !found; l = g_list_next (l)) { |
| const gchar *wdm_port_parent_path; |
| |
| g_assert (MM_IS_PORT_SERIAL_AT (l->data)); |
| wdm_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (MM_PORT (l->data))); |
| if (wdm_port_parent_path && g_str_equal (wdm_port_parent_path, net_port_parent_path)) |
| found = MM_PORT_SERIAL_AT (l->data); |
| } |
| |
| g_list_free_full (cdc_wdm_at_ports, g_object_unref); |
| return found; |
| } |
| |
| |
| MMPortSerialAt * |
| mm_broadband_modem_huawei_peek_port_at_for_data (MMBroadbandModemHuawei *self, |
| MMPort *port) |
| { |
| MMPortSerialAt *found; |
| |
| g_assert (self->priv->ndisdup_support == FEATURE_SUPPORTED); |
| |
| found = peek_port_at_for_data (self, port); |
| if (!found) |
| mm_obj_dbg (self, "couldn't find associated cdc-wdm port for %s", mm_port_get_device (port)); |
| return found; |
| } |
| |
| static void |
| ensure_ndisdup_support_checked (MMBroadbandModemHuawei *self, |
| MMPort *port) |
| { |
| /* Check NDISDUP support the first time we need it */ |
| if (self->priv->ndisdup_support != FEATURE_SUPPORT_UNKNOWN) |
| return; |
| |
| /* First, check for devices which support NDISDUP on any AT port. These |
| * devices are tagged by udev */ |
| if (mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_HUAWEI_NDISDUP_SUPPORTED")) { |
| mm_obj_dbg (self, "^NDISDUP is supported"); |
| self->priv->ndisdup_support = FEATURE_SUPPORTED; |
| } |
| /* Then, look for devices which have both a net port and a cdc-wdm |
| * AT-capable port. We assume that these devices allow NDISDUP only |
| * when issued in the cdc-wdm port. */ |
| else if (peek_port_at_for_data (self, port)) { |
| mm_obj_dbg (self, "^NDISDUP is supported on non-serial AT port"); |
| self->priv->ndisdup_support = FEATURE_SUPPORTED; |
| } |
| |
| if (self->priv->ndisdup_support != FEATURE_SUPPORT_UNKNOWN) |
| return; |
| |
| mm_obj_dbg (self, "^NDISDUP is not supported"); |
| self->priv->ndisdup_support = FEATURE_NOT_SUPPORTED; |
| } |
| |
| static void |
| huawei_modem_create_bearer (MMIfaceModem *self, |
| MMBearerProperties *properties, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| MMPort *port; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, g_object_ref (properties), g_object_unref); |
| |
| port = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET); |
| if (port) { |
| ensure_ndisdup_support_checked (MM_BROADBAND_MODEM_HUAWEI (self), port); |
| create_bearer_for_net_port (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "creating default bearer..."); |
| mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), |
| properties, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)broadband_bearer_new_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* USSD encode/decode (3GPP-USSD interface) |
| * |
| * Huawei devices don't use the current charset (as per AT+CSCS) in the CUSD |
| * command, they instead expect data encoded in GSM-7 already, given as a |
| * hex string. |
| */ |
| |
| static gchar * |
| encode (MMIfaceModem3gppUssd *self, |
| const gchar *command, |
| guint *scheme, |
| GError **error) |
| { |
| g_autoptr(GByteArray) gsm = NULL; |
| g_autofree guint8 *packed = NULL; |
| guint32 packed_len = 0; |
| |
| gsm = mm_modem_charset_bytearray_from_utf8 (command, MM_MODEM_CHARSET_GSM, FALSE, error); |
| if (!gsm) |
| return NULL; |
| |
| *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT; |
| |
| /* If command is a multiple of 7 characters long, Huawei firmwares |
| * apparently want that padded. Maybe all modems? |
| */ |
| if (gsm->len % 7 == 0) { |
| static const guint8 padding = 0x0d; |
| |
| g_byte_array_append (gsm, &padding, 1); |
| } |
| |
| packed = mm_charset_gsm_pack (gsm->data, gsm->len, 0, &packed_len); |
| return mm_utils_bin2hexstr (packed, packed_len); |
| } |
| |
| static gchar * |
| decode (MMIfaceModem3gppUssd *self, |
| const gchar *reply, |
| GError **error) |
| { |
| g_autofree guint8 *bin = NULL; |
| gsize bin_len = 0; |
| g_autofree guint8 *unpacked = NULL; |
| guint32 unpacked_len; |
| g_autoptr(GByteArray) unpacked_array = NULL; |
| |
| bin = mm_utils_hexstr2bin (reply, -1, &bin_len, error); |
| if (!bin) |
| return NULL; |
| |
| unpacked = mm_charset_gsm_unpack (bin, (bin_len * 8) / 7, 0, &unpacked_len); |
| /* if the last character in a 7-byte block is padding, then drop it */ |
| if ((bin_len % 7 == 0) && (unpacked[unpacked_len - 1] == 0x0d)) |
| unpacked_len--; |
| |
| unpacked_array = g_byte_array_sized_new (unpacked_len); |
| g_byte_array_append (unpacked_array, unpacked, unpacked_len); |
| |
| return mm_modem_charset_bytearray_to_utf8 (unpacked_array, MM_MODEM_CHARSET_GSM, FALSE, error); |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| huawei_1x_signal_changed (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| guint quality = 0; |
| |
| if (!mm_get_uint_from_match_info (match_info, 1, &quality)) |
| return; |
| |
| quality = MM_CLAMP_HIGH (quality, 100); |
| mm_obj_dbg (self, "1X signal quality: %u", quality); |
| mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); |
| } |
| |
| static void |
| huawei_evdo_signal_changed (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| guint quality = 0; |
| |
| if (!mm_get_uint_from_match_info (match_info, 1, &quality)) |
| return; |
| |
| quality = MM_CLAMP_HIGH (quality, 100); |
| mm_obj_dbg (self, "EVDO signal quality: %u", quality); |
| mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); |
| } |
| |
| /* Signal quality loading (Modem interface) */ |
| |
| static guint |
| modem_load_signal_quality_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| GError *inner_error = NULL; |
| gssize value; |
| |
| value = g_task_propagate_int (G_TASK (res), &inner_error); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return 0; |
| } |
| return (guint)value; |
| } |
| |
| static void |
| parent_load_signal_quality_ready (MMIfaceModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| guint signal_quality; |
| |
| signal_quality = iface_modem_parent->load_signal_quality_finish (self, res, &error); |
| if (error) |
| g_task_return_error (task, error); |
| else |
| g_task_return_int (task, signal_quality); |
| g_object_unref (task); |
| } |
| |
| static void |
| signal_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| const gchar *response, *command; |
| gchar buf[5]; |
| guint quality = 0, i = 0; |
| |
| response = mm_base_modem_at_command_finish (self, res, NULL); |
| if (!response) { |
| /* Fallback to parent's method */ |
| iface_modem_parent->load_signal_quality ( |
| MM_IFACE_MODEM (self), |
| (GAsyncReadyCallback)parent_load_signal_quality_ready, |
| task); |
| return; |
| } |
| |
| command = g_task_get_task_data (task); |
| g_assert (command); |
| response = mm_strip_tag (response, command); |
| /* 'command' won't include the trailing ':' in the response, so strip that */ |
| while ((*response == ':') || isspace (*response)) |
| response++; |
| |
| /* Sanitize response for mm_get_uint_from_str() which wants only digits */ |
| memset (buf, 0, sizeof (buf)); |
| while (i < (sizeof (buf) - 1) && isdigit (*response)) |
| buf[i++] = *response++; |
| |
| if (mm_get_uint_from_str (buf, &quality)) { |
| quality = MM_CLAMP_HIGH (quality, 100); |
| g_task_return_int (task, quality); |
| } else { |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't parse %s response: '%s'", |
| command, response); |
| } |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_load_signal_quality (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; |
| const char *command = "^CSQLVL"; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* 3GPP modems can just run parent's signal quality loading */ |
| if (mm_iface_modem_is_3gpp (self)) { |
| iface_modem_parent->load_signal_quality ( |
| self, |
| (GAsyncReadyCallback)parent_load_signal_quality_ready, |
| task); |
| return; |
| } |
| |
| /* CDMA modems need custom signal quality loading */ |
| |
| g_object_get (G_OBJECT (self), |
| MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE, &evdo_state, |
| NULL); |
| if (evdo_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) |
| command = "^HDRCSQLVL"; |
| |
| g_task_set_task_data (task, g_strdup (command), g_free); |
| |
| mm_base_modem_at_command ( |
| MM_BASE_MODEM (self), |
| command, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)signal_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Setup/Cleanup unsolicited events (CDMA interface) */ |
| |
| static void |
| set_cdma_unsolicited_events_handlers (MMBroadbandModemHuawei *self, |
| gboolean enable) |
| { |
| GList *ports, *l; |
| |
| ports = mm_broadband_modem_huawei_get_at_port_list (self); |
| |
| /* Enable/disable unsolicited events in given port */ |
| for (l = ports; l; l = g_list_next (l)) { |
| MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data); |
| |
| /* Signal quality related */ |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->rssilvl_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_1x_signal_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->hrssilvl_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_evdo_signal_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| /* Access technology related */ |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->mode_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_mode_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| } |
| |
| g_list_free_full (ports, g_object_unref); |
| } |
| |
| static gboolean |
| modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| parent_cdma_setup_unsolicited_events_ready (MMIfaceModemCdma *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_cdma_parent->setup_unsolicited_events_finish (self, res, &error)) |
| g_task_return_error (task, error); |
| else { |
| /* Our own setup now */ |
| set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE); |
| g_task_return_boolean (task, TRUE); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Chain up parent's setup if needed */ |
| if (iface_modem_cdma_parent->setup_unsolicited_events && |
| iface_modem_cdma_parent->setup_unsolicited_events_finish) { |
| iface_modem_cdma_parent->setup_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_cdma_setup_unsolicited_events_ready, |
| task); |
| return; |
| } |
| |
| /* Otherwise just run our setup and complete */ |
| set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE); |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| parent_cdma_cleanup_unsolicited_events_ready (MMIfaceModemCdma *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_cdma_parent->cleanup_unsolicited_events_finish (self, res, &error)) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Our own cleanup first */ |
| set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); |
| |
| /* Chain up parent's setup if needed */ |
| if (iface_modem_cdma_parent->cleanup_unsolicited_events && |
| iface_modem_cdma_parent->cleanup_unsolicited_events_finish) { |
| iface_modem_cdma_parent->cleanup_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_cdma_cleanup_unsolicited_events_ready, |
| task); |
| return; |
| } |
| |
| /* Otherwise we're done */ |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Setup registration checks (CDMA interface) */ |
| |
| typedef struct { |
| gboolean skip_qcdm_call_manager_step; |
| gboolean skip_qcdm_hdr_step; |
| gboolean skip_at_cdma_service_status_step; |
| gboolean skip_at_cdma1x_serving_system_step; |
| gboolean skip_detailed_registration_state; |
| } SetupRegistrationChecksResults; |
| |
| static gboolean |
| setup_registration_checks_finish (MMIfaceModemCdma *self, |
| GAsyncResult *res, |
| gboolean *skip_qcdm_call_manager_step, |
| gboolean *skip_qcdm_hdr_step, |
| gboolean *skip_at_cdma_service_status_step, |
| gboolean *skip_at_cdma1x_serving_system_step, |
| gboolean *skip_detailed_registration_state, |
| GError **error) |
| { |
| SetupRegistrationChecksResults *results; |
| |
| results = g_task_propagate_pointer (G_TASK (res), error); |
| if (!results) |
| return FALSE; |
| |
| *skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step; |
| *skip_qcdm_hdr_step = results->skip_qcdm_hdr_step; |
| *skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step; |
| *skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step; |
| *skip_detailed_registration_state = results->skip_detailed_registration_state; |
| g_free (results); |
| return TRUE; |
| } |
| |
| static void |
| parent_setup_registration_checks_ready (MMIfaceModemCdma *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| SetupRegistrationChecksResults *results; |
| GError *error = NULL; |
| |
| results = g_new0 (SetupRegistrationChecksResults, 1); |
| |
| if (!iface_modem_cdma_parent->setup_registration_checks_finish (self, |
| res, |
| &results->skip_qcdm_call_manager_step, |
| &results->skip_qcdm_hdr_step, |
| &results->skip_at_cdma_service_status_step, |
| &results->skip_at_cdma1x_serving_system_step, |
| &results->skip_detailed_registration_state, |
| &error)) { |
| g_free (results); |
| g_task_return_error (task, error); |
| } else { |
| gboolean evdo_supported = FALSE; |
| |
| g_object_get (self, |
| MM_IFACE_MODEM_CDMA_EVDO_NETWORK_SUPPORTED, &evdo_supported, |
| NULL); |
| |
| /* Don't use AT+CSS on EVDO-capable hardware for determining registration |
| * status, because often the device will have only an EVDO connection and |
| * AT+CSS won't necessarily report EVDO registration status, only 1X. |
| */ |
| if (evdo_supported) |
| results->skip_at_cdma1x_serving_system_step = TRUE; |
| |
| /* Force to always use the detailed registration checks, as we have |
| * ^SYSINFO for that */ |
| results->skip_detailed_registration_state = FALSE; |
| |
| g_task_return_pointer (task, results, g_free); |
| } |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| setup_registration_checks (MMIfaceModemCdma *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Run parent's checks first */ |
| iface_modem_cdma_parent->setup_registration_checks (self, |
| (GAsyncReadyCallback)parent_setup_registration_checks_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Detailed registration state (CDMA interface) */ |
| |
| typedef struct { |
| MMModemCdmaRegistrationState detailed_cdma1x_state; |
| MMModemCdmaRegistrationState detailed_evdo_state; |
| } DetailedRegistrationStateResults; |
| |
| typedef struct { |
| DetailedRegistrationStateResults state; |
| } DetailedRegistrationStateContext; |
| |
| static gboolean |
| get_detailed_registration_state_finish (MMIfaceModemCdma *self, |
| GAsyncResult *res, |
| MMModemCdmaRegistrationState *detailed_cdma1x_state, |
| MMModemCdmaRegistrationState *detailed_evdo_state, |
| GError **error) |
| { |
| DetailedRegistrationStateResults *results; |
| |
| results = g_task_propagate_pointer (G_TASK (res), error); |
| if (!results) |
| return FALSE; |
| |
| *detailed_cdma1x_state = results->detailed_cdma1x_state; |
| *detailed_evdo_state = results->detailed_evdo_state; |
| g_free (results); |
| return TRUE; |
| } |
| |
| static void |
| registration_state_sysinfo_ready (MMBroadbandModemHuawei *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| DetailedRegistrationStateContext *ctx; |
| gboolean extended = FALSE; |
| guint srv_status = 0; |
| guint sys_mode = 0; |
| guint roam_status = 0; |
| |
| ctx = g_task_get_task_data (task); |
| |
| if (!sysinfo_finish (self, |
| res, |
| &extended, |
| &srv_status, |
| NULL, /* srv_domain */ |
| &roam_status, |
| NULL, /* sim_state */ |
| &sys_mode, |
| NULL, /* sys_submode_valid */ |
| NULL, /* sys_submode */ |
| NULL)) { |
| /* If error, leave superclass' reg state alone if ^SYSINFO isn't supported. */ |
| g_task_return_pointer (task, |
| g_memdup (&ctx->state, sizeof (ctx->state)), |
| g_free); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (srv_status == 2) { |
| MMModemCdmaRegistrationState reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; |
| MMModemAccessTechnology act; |
| gboolean cdma1x = FALSE; |
| gboolean evdo = FALSE; |
| |
| /* Service available, check roaming state */ |
| if (roam_status == 0) |
| reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME; |
| else if (roam_status == 1) |
| reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING; |
| |
| /* Check service type */ |
| act = (extended ? |
| huawei_sysinfoex_mode_to_act (sys_mode): |
| huawei_sysinfo_mode_to_act (sys_mode)); |
| |
| if (act & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) { |
| cdma1x = TRUE; |
| ctx->state.detailed_cdma1x_state = reg_state; |
| } |
| |
| if (act & MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 || |
| act & MM_MODEM_ACCESS_TECHNOLOGY_EVDOA || |
| act & MM_MODEM_ACCESS_TECHNOLOGY_EVDOB) { |
| evdo = TRUE; |
| ctx->state.detailed_evdo_state = reg_state; |
| } |
| |
| if (!cdma1x && !evdo) { |
| /* Say we're registered to something even though sysmode parsing failed */ |
| mm_obj_dbg (self, "assuming registered at least in CDMA1x"); |
| ctx->state.detailed_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; |
| } |
| } |
| |
| g_task_return_pointer (task, |
| g_memdup (&ctx->state, sizeof (ctx->state)), |
| g_free); |
| g_object_unref (task); |
| } |
| |
| static void |
| get_detailed_registration_state (MMIfaceModemCdma *self, |
| MMModemCdmaRegistrationState cdma1x_state, |
| MMModemCdmaRegistrationState evdo_state, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| DetailedRegistrationStateContext *ctx; |
| GTask *task; |
| |
| /* Setup context */ |
| ctx = g_new (DetailedRegistrationStateContext, 1); |
| ctx->state.detailed_cdma1x_state = cdma1x_state; |
| ctx->state.detailed_evdo_state = evdo_state; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, g_free); |
| |
| sysinfo (MM_BROADBAND_MODEM_HUAWEI (self), |
| (GAsyncReadyCallback)registration_state_sysinfo_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Check if Voice supported (Voice interface) */ |
| |
| static gboolean |
| modem_voice_check_support_finish (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| voice_parent_check_support_ready (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| gboolean parent_support; |
| |
| parent_support = iface_modem_voice_parent->check_support_finish (self, res, NULL); |
| g_task_return_boolean (task, parent_support); |
| g_object_unref (task); |
| } |
| |
| static void |
| cvoice_check_ready (MMBaseModem *_self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GError *error = NULL; |
| const gchar *response; |
| |
| response = mm_base_modem_at_command_finish (_self, res, &error); |
| if (!response || |
| !mm_huawei_parse_cvoice_response (response, |
| &self->priv->audio_hz, |
| &self->priv->audio_bits, |
| &error)) { |
| self->priv->cvoice_support = FEATURE_NOT_SUPPORTED; |
| mm_obj_dbg (self, "CVOICE is unsupported: %s", error->message); |
| g_clear_error (&error); |
| |
| /* Now check generic support */ |
| iface_modem_voice_parent->check_support (MM_IFACE_MODEM_VOICE (self), |
| (GAsyncReadyCallback)voice_parent_check_support_ready, |
| task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "CVOICE is supported"); |
| self->priv->cvoice_support = FEATURE_SUPPORTED; |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_voice_check_support (MMIfaceModemVoice *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| /* Check for Huawei-specific ^CVOICE support */ |
| task = g_task_new (self, NULL, callback, user_data); |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^CVOICE?", |
| 3, |
| TRUE, |
| (GAsyncReadyCallback)cvoice_check_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* In-call audio channel setup/cleanup */ |
| |
| static gboolean |
| modem_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| modem_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* If there is no CVOICE support, no custom audio setup required |
| * (i.e. audio path is externally managed) */ |
| if (self->priv->cvoice_support == FEATURE_SUPPORTED) { |
| MMPort *port; |
| |
| /* The QCDM port, if present, switches back from voice to QCDM after |
| * the voice call is dropped. */ |
| port = MM_PORT (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self))); |
| if (port) { |
| /* During a voice call, we'll set the QCDM port as connected, and that |
| * will make us ignore all incoming data and avoid sending any outgoing |
| * data. */ |
| mm_port_set_connected (port, FALSE); |
| } |
| } |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static gboolean |
| modem_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *_self, |
| GAsyncResult *res, |
| MMPort **audio_port, |
| MMCallAudioFormat **audio_format, |
| GError **error) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| |
| if (!g_task_propagate_boolean (G_TASK (res), error)) |
| return FALSE; |
| |
| if (self->priv->cvoice_support == FEATURE_SUPPORTED) { |
| MMPort *port; |
| |
| /* Setup audio format */ |
| if (audio_format) { |
| gchar *resolution_str; |
| |
| resolution_str = g_strdup_printf ("s%ule", self->priv->audio_bits); |
| *audio_format = mm_call_audio_format_new (); |
| mm_call_audio_format_set_encoding (*audio_format, "pcm"); |
| mm_call_audio_format_set_resolution (*audio_format, resolution_str); |
| mm_call_audio_format_set_rate (*audio_format, self->priv->audio_hz); |
| g_free (resolution_str); |
| } |
| |
| /* The QCDM port, if present, switches from QCDM to voice while |
| * a voice call is active. */ |
| port = MM_PORT (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self))); |
| if (port) { |
| /* During a voice call, we'll set the QCDM port as connected, and that |
| * will make us ignore all incoming data and avoid sending any outgoing |
| * data. */ |
| mm_port_set_connected (port, TRUE); |
| } |
| |
| if (audio_port) |
| *audio_port = (port ? g_object_ref (port) : NULL);; |
| } else { |
| if (audio_format) |
| *audio_format = NULL; |
| if (audio_port) |
| *audio_port = NULL; |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| ddsetex_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!mm_base_modem_at_command_finish (self, res, &error)) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_voice_setup_in_call_audio_channel (MMIfaceModemVoice *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* If there is no CVOICE support, no custom audio setup required |
| * (i.e. audio path is externally managed) */ |
| if (self->priv->cvoice_support != FEATURE_SUPPORTED) { |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Enable audio streaming on the audio port */ |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^DDSETEX=2", |
| 5, |
| FALSE, |
| (GAsyncReadyCallback)ddsetex_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Common setup/cleanup voice unsolicited events */ |
| |
| typedef enum { |
| HUAWEI_CALL_TYPE_VOICE = 0, |
| HUAWEI_CALL_TYPE_CS_DATA = 1, |
| HUAWEI_CALL_TYPE_PS_DATA = 2, |
| HUAWEI_CALL_TYPE_CDMA_SMS = 3, |
| HUAWEI_CALL_TYPE_OTA_STANDARD_OTASP = 7, |
| HUAWEI_CALL_TYPE_OTA_NON_STANDARD_OTASP = 8, |
| HUAWEI_CALL_TYPE_EMERGENCY = 9, |
| } HuaweiCallType; |
| |
| static void |
| orig_received (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| MMCallInfo call_info = { 0 }; |
| guint aux = 0; |
| |
| if (!mm_get_uint_from_match_info (match_info, 2, &aux)) { |
| mm_obj_warn (self, "couldn't parse call type from ^ORIG"); |
| return; |
| } |
| if (aux != HUAWEI_CALL_TYPE_VOICE && aux != HUAWEI_CALL_TYPE_EMERGENCY) { |
| mm_obj_dbg (self, "ignored ^ORIG for non-voice call"); |
| return; |
| } |
| |
| if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { |
| mm_obj_warn (self, "couldn't parse call index from ^ORIG"); |
| return; |
| } |
| call_info.index = aux; |
| call_info.state = MM_CALL_STATE_DIALING; |
| call_info.direction = MM_CALL_DIRECTION_OUTGOING; |
| |
| mm_obj_dbg (self, "call %u state updated: dialing", call_info.index); |
| |
| mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); |
| } |
| |
| static void |
| conf_received (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| MMCallInfo call_info = { 0 }; |
| guint aux = 0; |
| |
| if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { |
| mm_obj_warn (self, "couldn't parse call index from ^CONF"); |
| return; |
| } |
| call_info.index = aux; |
| call_info.state = MM_CALL_STATE_RINGING_OUT; |
| call_info.direction = MM_CALL_DIRECTION_OUTGOING; |
| |
| mm_obj_dbg (self, "call %u state updated: ringing-out", call_info.index); |
| |
| mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); |
| } |
| |
| static void |
| conn_received (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| MMCallInfo call_info = { 0 }; |
| guint aux = 0; |
| |
| if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { |
| mm_obj_warn (self, "couldn't parse call index from ^CONN"); |
| return; |
| } |
| call_info.index = aux; |
| call_info.state = MM_CALL_STATE_ACTIVE; |
| call_info.direction = MM_CALL_DIRECTION_UNKNOWN; |
| |
| mm_obj_dbg (self, "call %u state updated: active", aux); |
| |
| mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); |
| } |
| |
| static void |
| cend_received (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| MMCallInfo call_info = { 0 }; |
| guint aux = 0; |
| |
| /* only index is mandatory */ |
| if (!mm_get_uint_from_match_info (match_info, 1, &aux)) { |
| mm_obj_warn (self, "couldn't parse call index from ^CEND"); |
| return; |
| } |
| call_info.index = aux; |
| call_info.state = MM_CALL_STATE_TERMINATED; |
| call_info.direction = MM_CALL_DIRECTION_UNKNOWN; |
| |
| mm_obj_dbg (self, "call %u state updated: terminated", call_info.index); |
| if (mm_get_uint_from_match_info (match_info, 2, &aux)) |
| mm_obj_dbg (self, " call duration: %u seconds", aux); |
| if (mm_get_uint_from_match_info (match_info, 3, &aux)) |
| mm_obj_dbg (self, " end status code: %u", aux); |
| if (mm_get_uint_from_match_info (match_info, 4, &aux)) |
| mm_obj_dbg (self, " call control cause: %u", aux); |
| |
| mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); |
| } |
| |
| static void |
| ddtmf_received (MMPortSerialAt *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemHuawei *self) |
| { |
| gchar *dtmf; |
| |
| dtmf = g_match_info_fetch (match_info, 1); |
| mm_obj_dbg (self, "received DTMF: %s", dtmf); |
| /* call index unknown */ |
| mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf); |
| g_free (dtmf); |
| } |
| |
| static void |
| common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemHuawei *self, |
| gboolean enable) |
| { |
| MMPortSerialAt *ports[2]; |
| guint i; |
| |
| ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); |
| ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); |
| |
| for (i = 0; i < G_N_ELEMENTS (ports); i++) { |
| if (!ports[i]) |
| continue; |
| |
| mm_port_serial_at_add_unsolicited_msg_handler (ports[i], |
| self->priv->orig_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)orig_received : NULL, |
| enable ? self : NULL, |
| NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler (ports[i], |
| self->priv->conf_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)conf_received : NULL, |
| enable ? self : NULL, |
| NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler (ports[i], |
| self->priv->conn_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)conn_received : NULL, |
| enable ? self : NULL, |
| NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler (ports[i], |
| self->priv->cend_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)cend_received : NULL, |
| enable ? self : NULL, |
| NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler (ports[i], |
| self->priv->ddtmf_regex, |
| enable ? (MMPortSerialAtUnsolicitedMsgFn)ddtmf_received : NULL, |
| enable ? self : NULL, |
| NULL); |
| } |
| } |
| |
| /*****************************************************************************/ |
| /* Setup unsolicited events (Voice interface) */ |
| |
| static gboolean |
| modem_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Our own setup now */ |
| common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), TRUE); |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Chain up parent's setup */ |
| iface_modem_voice_parent->setup_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Cleanup unsolicited events (Voice interface) */ |
| |
| static gboolean |
| modem_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* cleanup our own */ |
| common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); |
| |
| /* Chain up parent's cleanup */ |
| iface_modem_voice_parent->cleanup_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Enabling unsolicited events (Voice interface) */ |
| |
| static gboolean |
| modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| own_voice_enable_unsolicited_events_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); |
| if (error) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static const MMBaseModemAtCommand unsolicited_voice_enable_sequence[] = { |
| /* With ^DDTMFCFG we active the DTMF Decoder */ |
| { "^DDTMFCFG=0,1", 3, FALSE, NULL }, |
| { NULL } |
| }; |
| |
| static void |
| parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Our own enable now */ |
| mm_base_modem_at_sequence_full ( |
| MM_BASE_MODEM (self), |
| mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), |
| unsolicited_voice_enable_sequence, |
| NULL, /* response_processor_context */ |
| NULL, /* response_processor_context_free */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)own_voice_enable_unsolicited_events_ready, |
| task); |
| } |
| |
| static void |
| modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Chain up parent's enable */ |
| iface_modem_voice_parent->enable_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Disabling unsolicited events (Voice interface) */ |
| |
| static gboolean |
| modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| own_voice_disable_unsolicited_events_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); |
| if (error) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static const MMBaseModemAtCommand unsolicited_voice_disable_sequence[] = { |
| /* With ^DDTMFCFG we deactivate the DTMF Decoder */ |
| { "^DDTMFCFG=1,0", 3, FALSE, NULL }, |
| { NULL } |
| }; |
| |
| static void |
| parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* our own disable now */ |
| |
| mm_base_modem_at_sequence_full ( |
| MM_BASE_MODEM (self), |
| mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), |
| unsolicited_voice_disable_sequence, |
| NULL, /* response_processor_context */ |
| NULL, /* response_processor_context_free */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)own_voice_disable_unsolicited_events_ready, |
| task); |
| } |
| |
| static void |
| modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Chain up parent's disable */ |
| iface_modem_voice_parent->disable_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Create call (Voice interface) */ |
| |
| static MMBaseCall * |
| create_call (MMIfaceModemVoice *self, |
| MMCallDirection direction, |
| const gchar *number) |
| { |
| return mm_base_call_new (MM_BASE_MODEM (self), |
| direction, |
| number, |
| TRUE, /* skip_incoming_timeout */ |
| TRUE, /* supports_dialing_to_ringing */ |
| TRUE); /* supports_ringing_to_active) */ |
| } |
| |
| /*****************************************************************************/ |
| /* Load network time (Time interface) */ |
| |
| static MMNetworkTimezone * |
| modem_time_load_network_timezone_finish (MMIfaceModemTime *_self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| MMNetworkTimezone *tz = NULL; |
| const gchar *response; |
| |
| g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED || |
| self->priv->time_support == FEATURE_SUPPORTED); |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error); |
| if (!response) |
| return NULL; |
| |
| if (self->priv->nwtime_support == FEATURE_SUPPORTED) |
| mm_huawei_parse_nwtime_response (response, NULL, &tz, error); |
| else if (self->priv->time_support == FEATURE_SUPPORTED) |
| mm_huawei_parse_time_response (response, NULL, &tz, error); |
| return tz; |
| } |
| |
| |
| static gchar * |
| modem_time_load_network_time_finish (MMIfaceModemTime *_self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| const gchar *response; |
| gchar *iso8601 = NULL; |
| |
| g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED || |
| self->priv->time_support == FEATURE_SUPPORTED); |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error); |
| if (!response) |
| return NULL; |
| |
| if (self->priv->nwtime_support == FEATURE_SUPPORTED) |
| mm_huawei_parse_nwtime_response (response, &iso8601, NULL, error); |
| else if (self->priv->time_support == FEATURE_SUPPORTED) |
| mm_huawei_parse_time_response (response, &iso8601, NULL, error); |
| return iso8601; |
| } |
| |
| static void |
| modem_time_load_network_time_or_zone (MMIfaceModemTime *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| const char *command = NULL; |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| |
| if (self->priv->nwtime_support == FEATURE_SUPPORTED) |
| command = "^NWTIME?"; |
| else if (self->priv->time_support == FEATURE_SUPPORTED) |
| command = "^TIME"; |
| |
| g_assert (command != NULL); |
| |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| command, |
| 3, |
| FALSE, |
| callback, |
| user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* Power state loading (Modem interface) */ |
| |
| static void |
| enable_disable_unsolicited_rfswitch_event_handler (MMBroadbandModemHuawei *self, |
| gboolean enable) |
| { |
| GList *ports, *l; |
| |
| ports = mm_broadband_modem_huawei_get_at_port_list (self); |
| |
| mm_obj_dbg (self, "%s ^RFSWITCH unsolicited event handler", |
| enable ? "enable" : "disable"); |
| |
| for (l = ports; l; l = g_list_next (l)) { |
| MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data); |
| |
| mm_port_serial_at_enable_unsolicited_msg_handler ( |
| port, |
| self->priv->rfswitch_regex, |
| enable); |
| } |
| |
| g_list_free_full (ports, g_object_unref); |
| } |
| |
| static void |
| parent_load_power_state_ready (MMIfaceModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| MMModemPowerState power_state; |
| |
| power_state = iface_modem_parent->load_power_state_finish (self, res, &error); |
| if (error) |
| g_task_return_error (task, error); |
| else { |
| /* As modem_power_down uses +CFUN=0 to put the modem in low state, we treat |
| * CFUN 0 as 'LOW' power state instead of 'OFF'. Otherwise, MMIfaceModem |
| * would prevent the modem from transitioning back to the 'ON' power state. */ |
| if (power_state == MM_MODEM_POWER_STATE_OFF) |
| power_state = MM_MODEM_POWER_STATE_LOW; |
| |
| g_task_return_int (task, power_state); |
| } |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| huawei_rfswitch_check_ready (MMBaseModem *_self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GError *error = NULL; |
| const gchar *response; |
| gint sw_state; |
| |
| enable_disable_unsolicited_rfswitch_event_handler (MM_BROADBAND_MODEM_HUAWEI (self), |
| TRUE /* enable */); |
| |
| response = mm_base_modem_at_command_finish (_self, res, &error); |
| if (response) { |
| response = mm_strip_tag (response, "^RFSWITCH:"); |
| if (sscanf (response, "%d", &sw_state) != 1 || |
| (sw_state != 0 && sw_state != 1)) { |
| mm_obj_warn (self, "couldn't parse ^RFSWITCH response '%s'", response); |
| error = g_error_new (MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't parse ^RFSWITCH response '%s'", |
| response); |
| } |
| } |
| |
| if (self->priv->rfswitch_support == FEATURE_SUPPORT_UNKNOWN) { |
| if (error) { |
| mm_obj_dbg (self, "^RFSWITCH is not supported"); |
| self->priv->rfswitch_support = FEATURE_NOT_SUPPORTED; |
| g_error_free (error); |
| /* Fall back to parent's load_power_state */ |
| iface_modem_parent->load_power_state (MM_IFACE_MODEM (self), |
| (GAsyncReadyCallback)parent_load_power_state_ready, |
| task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "^RFSWITCH is supported"); |
| self->priv->rfswitch_support = FEATURE_SUPPORTED; |
| } |
| |
| if (error) |
| g_task_return_error (task, error); |
| else |
| g_task_return_int (task, |
| sw_state ? MM_MODEM_POWER_STATE_ON : MM_MODEM_POWER_STATE_LOW); |
| |
| g_object_unref (task); |
| } |
| |
| static MMModemPowerState |
| load_power_state_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| GError *inner_error = NULL; |
| gssize value; |
| |
| value = g_task_propagate_int (G_TASK (res), &inner_error); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return MM_MODEM_POWER_STATE_UNKNOWN; |
| } |
| return (MMModemPowerState)value; |
| } |
| |
| static void |
| load_power_state (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) { |
| case FEATURE_SUPPORT_UNKNOWN: |
| case FEATURE_SUPPORTED: { |
| /* Temporarily disable the unsolicited ^RFSWITCH event handler in order to |
| * prevent it from discarding the response to the ^RFSWITCH? command. |
| * It will be re-enabled in huawei_rfswitch_check_ready. |
| */ |
| enable_disable_unsolicited_rfswitch_event_handler (MM_BROADBAND_MODEM_HUAWEI (self), |
| FALSE /* enable */); |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^RFSWITCH?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)huawei_rfswitch_check_ready, |
| task); |
| break; |
| } |
| case FEATURE_NOT_SUPPORTED: |
| /* Run parent's load_power_state */ |
| iface_modem_parent->load_power_state (self, |
| (GAsyncReadyCallback)parent_load_power_state_ready, |
| task); |
| break; |
| default: |
| g_assert_not_reached (); |
| break; |
| } |
| } |
| |
| /*****************************************************************************/ |
| /* Modem power up (Modem interface) */ |
| |
| static gboolean |
| huawei_modem_power_up_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); |
| } |
| |
| static void |
| huawei_modem_power_up (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) { |
| case FEATURE_NOT_SUPPORTED: |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "+CFUN=1", |
| 30, |
| FALSE, |
| callback, |
| user_data); |
| break; |
| case FEATURE_SUPPORTED: |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^RFSWITCH=1", |
| 30, |
| FALSE, |
| callback, |
| user_data); |
| break; |
| case FEATURE_SUPPORT_UNKNOWN: |
| default: |
| g_assert_not_reached (); |
| break; |
| } |
| } |
| |
| /*****************************************************************************/ |
| /* Modem power down (Modem interface) */ |
| |
| static gboolean |
| huawei_modem_power_down_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); |
| } |
| |
| static void |
| huawei_modem_power_down (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) { |
| case FEATURE_NOT_SUPPORTED: |
| /* +CFUN=0 is supported on all Huawei modems but +CFUN=4 isn't, |
| * thus we use +CFUN=0 to put the modem in low power state. */ |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "+CFUN=0", |
| 30, |
| FALSE, |
| callback, |
| user_data); |
| break; |
| case FEATURE_SUPPORTED: |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^RFSWITCH=0", |
| 30, |
| FALSE, |
| callback, |
| user_data); |
| break; |
| case FEATURE_SUPPORT_UNKNOWN: |
| default: |
| g_assert_not_reached (); |
| break; |
| } |
| } |
| |
| /*****************************************************************************/ |
| /* Create SIM (Modem interface) */ |
| |
| static MMBaseSim * |
| huawei_modem_create_sim_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return mm_sim_huawei_new_finish (res, error); |
| } |
| |
| static void |
| huawei_modem_create_sim (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| /* New Sierra SIM */ |
| mm_sim_huawei_new (MM_BASE_MODEM (self), |
| NULL, /* cancellable */ |
| callback, |
| user_data); |
| } |
| |
| |
| /*****************************************************************************/ |
| /* Location capabilities loading (Location interface) */ |
| |
| static MMModemLocationSource |
| location_load_capabilities_finish (MMIfaceModemLocation *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| GError *inner_error = NULL; |
| gssize value; |
| |
| value = g_task_propagate_int (G_TASK (res), &inner_error); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return MM_MODEM_LOCATION_SOURCE_NONE; |
| } |
| return (MMModemLocationSource)value; |
| } |
| |
| static void |
| parent_load_capabilities_ready (MMIfaceModemLocation *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMModemLocationSource sources; |
| GError *error = NULL; |
| |
| sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* not sure how to check if GPS is supported, just allow it */ |
| if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) |
| sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | |
| MM_MODEM_LOCATION_SOURCE_GPS_RAW | |
| MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED); |
| |
| /* So we're done, complete */ |
| g_task_return_int (task, sources); |
| g_object_unref (task); |
| } |
| |
| static void |
| location_load_capabilities (MMIfaceModemLocation *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Chain up parent's setup */ |
| iface_modem_location_parent->load_capabilities (self, |
| (GAsyncReadyCallback)parent_load_capabilities_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Disable location gathering (Location interface) */ |
| |
| static gboolean |
| disable_location_gathering_finish (MMIfaceModemLocation *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| gps_disabled_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!mm_base_modem_at_command_finish (self, res, &error)) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| disable_location_gathering (MMIfaceModemLocation *_self, |
| MMModemLocationSource source, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GTask *task; |
| |
| /* NOTE: no parent disable_location_gathering() implementation */ |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| self->priv->enabled_sources &= ~source; |
| |
| /* Only stop GPS engine if no GPS-related sources enabled */ |
| if ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | |
| MM_MODEM_LOCATION_SOURCE_GPS_RAW | |
| MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) && |
| !(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | |
| MM_MODEM_LOCATION_SOURCE_GPS_RAW | |
| MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) { |
| MMPortSerialGps *gps_port; |
| |
| /* Close the data port if we don't need it anymore */ |
| if (source & (MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) { |
| gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); |
| if (gps_port) |
| mm_port_serial_close (MM_PORT_SERIAL (gps_port)); |
| } |
| |
| mm_base_modem_at_command (MM_BASE_MODEM (_self), |
| "^WPEND", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)gps_disabled_ready, |
| task); |
| return; |
| } |
| |
| /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */ |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Enable location gathering (Location interface) */ |
| |
| static const MMBaseModemAtCommand gps_startup[] = { |
| { "^WPDOM=0", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, |
| { "^WPDST=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, |
| { "^WPDFR=65535,30", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, |
| { "^WPDGP", 3, FALSE, mm_base_modem_response_processor_no_result_continue }, |
| { NULL } |
| }; |
| |
| static gboolean |
| enable_location_gathering_finish (MMIfaceModemLocation *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| gps_startup_ready (MMBaseModem *_self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| MMModemLocationSource source; |
| GError *error = NULL; |
| |
| mm_base_modem_at_sequence_finish (_self, res, NULL, &error); |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| source = GPOINTER_TO_UINT (g_task_get_task_data (task)); |
| |
| /* Only open the GPS port in NMEA/RAW setups */ |
| if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { |
| MMPortSerialGps *gps_port; |
| |
| gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); |
| if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { |
| if (error) |
| g_task_return_error (task, error); |
| else |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't open raw GPS serial port"); |
| } else { |
| /* GPS port was successfully opened */ |
| self->priv->enabled_sources |= source; |
| g_task_return_boolean (task, TRUE); |
| } |
| } else { |
| /* No need to open GPS port */ |
| self->priv->enabled_sources |= source; |
| g_task_return_boolean (task, TRUE); |
| } |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GError *error = NULL; |
| MMModemLocationSource source; |
| gboolean start_gps = FALSE; |
| |
| if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Now our own enabling */ |
| |
| source = GPOINTER_TO_UINT (g_task_get_task_data (task)); |
| |
| /* Only start GPS engine if not done already */ |
| start_gps = ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | |
| MM_MODEM_LOCATION_SOURCE_GPS_RAW | |
| MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) && |
| !(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | |
| MM_MODEM_LOCATION_SOURCE_GPS_RAW | |
| MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))); |
| |
| if (start_gps) { |
| mm_base_modem_at_sequence ( |
| MM_BASE_MODEM (self), |
| gps_startup, |
| NULL, /* response_processor_context */ |
| NULL, /* response_processor_context_free */ |
| (GAsyncReadyCallback)gps_startup_ready, |
| task); |
| return; |
| } |
| |
| /* For any other location (e.g. 3GPP), or if GPS already running just return */ |
| self->priv->enabled_sources |= source; |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| enable_location_gathering (MMIfaceModemLocation *self, |
| MMModemLocationSource source, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL); |
| |
| /* Chain up parent's gathering enable */ |
| iface_modem_location_parent->enable_location_gathering (self, |
| source, |
| (GAsyncReadyCallback)parent_enable_location_gathering_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Check support (Time interface) */ |
| |
| static gboolean |
| modem_time_check_support_finish (MMIfaceModemTime *_self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| modem_time_check_ready (MMBaseModem *_self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| |
| /* Responses are checked in the sequence parser, ignore overall result */ |
| mm_base_modem_at_sequence_finish (_self, res, NULL, NULL); |
| |
| g_task_return_boolean (task, |
| (self->priv->nwtime_support == FEATURE_SUPPORTED || |
| self->priv->time_support == FEATURE_SUPPORTED)); |
| g_object_unref (task); |
| } |
| |
| static MMBaseModemAtResponseProcessorResult |
| modem_check_time_reply (MMBaseModem *_self, |
| gpointer none, |
| const gchar *command, |
| const gchar *response, |
| gboolean last_command, |
| const GError *error, |
| GVariant **result, |
| GError **result_error) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| |
| if (!error) { |
| if (strstr (response, "^NTCT")) |
| self->priv->nwtime_support = FEATURE_SUPPORTED; |
| else if (strstr (response, "^TIME")) |
| self->priv->time_support = FEATURE_SUPPORTED; |
| } else { |
| if (strstr (command, "^NTCT")) |
| self->priv->nwtime_support = FEATURE_NOT_SUPPORTED; |
| else if (strstr (command, "^TIME")) |
| self->priv->time_support = FEATURE_NOT_SUPPORTED; |
| } |
| |
| *result = NULL; |
| *result_error = NULL; |
| return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE; |
| } |
| |
| static const MMBaseModemAtCommand time_cmd_sequence[] = { |
| { "^NTCT?", 3, FALSE, modem_check_time_reply }, /* 3GPP/LTE */ |
| { "^TIME", 3, FALSE, modem_check_time_reply }, /* CDMA */ |
| { NULL } |
| }; |
| |
| static void |
| modem_time_check_support (MMIfaceModemTime *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| mm_base_modem_at_sequence (MM_BASE_MODEM (self), |
| time_cmd_sequence, |
| NULL, /* response_processor_context */ |
| NULL, /* response_processor_context_free */ |
| (GAsyncReadyCallback)modem_time_check_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Check support (Signal interface) */ |
| |
| static void |
| hcsq_check_ready (MMBaseModem *_self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| const gchar *response; |
| |
| response = mm_base_modem_at_command_finish (_self, res, &error); |
| if (response) |
| g_task_return_boolean (task, TRUE); |
| else |
| g_task_return_error (task, error); |
| |
| g_object_unref (task); |
| } |
| |
| static gboolean |
| signal_check_support_finish (MMIfaceModemSignal *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| signal_check_support (MMIfaceModemSignal *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^HCSQ?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)hcsq_check_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load extended signal information */ |
| |
| static void |
| detailed_signal_free (DetailedSignal *signal) |
| { |
| detailed_signal_clear (signal); |
| g_slice_free (DetailedSignal, signal); |
| } |
| |
| static gboolean |
| signal_load_values_finish (MMIfaceModemSignal *self, |
| GAsyncResult *res, |
| MMSignal **cdma, |
| MMSignal **evdo, |
| MMSignal **gsm, |
| MMSignal **umts, |
| MMSignal **lte, |
| MMSignal **nr5g, |
| GError **error) |
| { |
| DetailedSignal *signals; |
| |
| signals = g_task_propagate_pointer (G_TASK (res), error); |
| if (!signals) |
| return FALSE; |
| |
| *cdma = signals->cdma ? g_object_ref (signals->cdma) : NULL; |
| *evdo = signals->evdo ? g_object_ref (signals->evdo) : NULL; |
| *gsm = signals->gsm ? g_object_ref (signals->gsm) : NULL; |
| *umts = signals->umts ? g_object_ref (signals->umts) : NULL; |
| *lte = signals->lte ? g_object_ref (signals->lte) : NULL; |
| *nr5g = signals->nr5g ? g_object_ref (signals->nr5g) : NULL; |
| |
| detailed_signal_free (signals); |
| return TRUE; |
| } |
| |
| static void |
| hcsq_get_ready (MMBaseModem *_self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| DetailedSignal *signals; |
| GError *error = NULL; |
| |
| /* Don't care about the response; it will have been parsed by the HCSQ |
| * unsolicited event handler and self->priv->detailed_signal will already |
| * be updated. |
| */ |
| if (!mm_base_modem_at_command_finish (_self, res, &error)) { |
| mm_obj_dbg (self, "^HCSQ failed: %s", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| signals = g_slice_new0 (DetailedSignal); |
| signals->cdma = self->priv->detailed_signal.cdma ? g_object_ref (self->priv->detailed_signal.cdma) : NULL; |
| signals->evdo = self->priv->detailed_signal.evdo ? g_object_ref (self->priv->detailed_signal.evdo) : NULL; |
| signals->gsm = self->priv->detailed_signal.gsm ? g_object_ref (self->priv->detailed_signal.gsm) : NULL; |
| signals->umts = self->priv->detailed_signal.umts ? g_object_ref (self->priv->detailed_signal.umts) : NULL; |
| signals->lte = self->priv->detailed_signal.lte ? g_object_ref (self->priv->detailed_signal.lte) : NULL; |
| |
| g_task_return_pointer (task, signals, (GDestroyNotify)detailed_signal_free); |
| g_object_unref (task); |
| } |
| |
| static void |
| signal_load_values (MMIfaceModemSignal *_self, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); |
| GTask *task; |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| |
| /* Clear any previous detailed signal values to get new ones */ |
| detailed_signal_clear (&self->priv->detailed_signal); |
| |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "^HCSQ?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)hcsq_get_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Setup ports (Broadband modem class) */ |
| |
| static void |
| set_ignored_unsolicited_events_handlers (MMBroadbandModemHuawei *self) |
| { |
| GList *ports, *l; |
| |
| ports = mm_broadband_modem_huawei_get_at_port_list (self); |
| |
| /* Enable/disable unsolicited events in given port */ |
| for (l = ports; l; l = g_list_next (l)) { |
| MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data); |
| |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->boot_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->connect_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->csnr_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->cusatp_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->cusatend_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->dsdormant_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->simst_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->srvst_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->stin_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->pdpdeact_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->ndisend_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->rfswitch_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->position_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->posend_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->ecclist_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->ltersrp_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->cschannelinfo_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->ccallstate_regex, |
| NULL, NULL, NULL); |
| mm_port_serial_at_add_unsolicited_msg_handler ( |
| port, |
| self->priv->eons_regex, |
| NULL, NULL, NULL); |
| } |
| |
| g_list_free_full (ports, g_object_unref); |
| } |
| |
| static void |
| gps_trace_received (MMPortSerialGps *port, |
| const gchar *trace, |
| MMIfaceModemLocation *self) |
| { |
| mm_iface_modem_location_gps_update (self, trace); |
| } |
| |
| static void |
| setup_ports (MMBroadbandModem *self) |
| { |
| MMPortSerialGps *gps_data_port; |
| |
| /* Call parent's setup ports first always */ |
| MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_huawei_parent_class)->setup_ports (self); |
| |
| /* Unsolicited messages to always ignore */ |
| set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self)); |
| |
| /* Now reset the unsolicited messages we'll handle when enabled */ |
| set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); |
| set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE); |
| |
| /* NMEA GPS monitoring */ |
| gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); |
| if (gps_data_port) { |
| /* make sure GPS is stopped incase it was left enabled */ |
| mm_base_modem_at_command_full (MM_BASE_MODEM (self), |
| mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), |
| "^WPEND", |
| 3, FALSE, FALSE, NULL, NULL, NULL); |
| /* Add handler for the NMEA traces */ |
| mm_port_serial_gps_add_trace_handler (gps_data_port, |
| (MMPortSerialGpsTraceFn)gps_trace_received, |
| self, NULL); |
| } |
| } |
| |
| /*****************************************************************************/ |
| |
| MMBroadbandModemHuawei * |
| mm_broadband_modem_huawei_new (const gchar *device, |
| const gchar **drivers, |
| const gchar *plugin, |
| guint16 vendor_id, |
| guint16 product_id) |
| { |
| return g_object_new (MM_TYPE_BROADBAND_MODEM_HUAWEI, |
| MM_BASE_MODEM_DEVICE, device, |
| MM_BASE_MODEM_DRIVERS, drivers, |
| MM_BASE_MODEM_PLUGIN, plugin, |
| MM_BASE_MODEM_VENDOR_ID, vendor_id, |
| MM_BASE_MODEM_PRODUCT_ID, product_id, |
| /* Generic bearer (TTY) or Huawei bearer (NET) supported */ |
| MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, |
| MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, |
| NULL); |
| } |
| |
| static void |
| mm_broadband_modem_huawei_init (MMBroadbandModemHuawei *self) |
| { |
| /* Initialize private data */ |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, |
| MM_TYPE_BROADBAND_MODEM_HUAWEI, |
| MMBroadbandModemHuaweiPrivate); |
| /* Prepare regular expressions to setup */ |
| self->priv->rssi_regex = g_regex_new ("\\r\\n\\^RSSI:\\s*(\\d+)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->rssilvl_regex = g_regex_new ("\\r\\n\\^RSSILVL:\\s*(\\d+)\\r+\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->hrssilvl_regex = g_regex_new ("\\r\\n\\^HRSSILVL:\\s*(\\d+)\\r+\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| |
| /* 3GPP: <cr><lf>^MODE:5<cr><lf> |
| * CDMA: <cr><lf>^MODE: 2<cr><cr><lf> |
| */ |
| self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:\\s*(\\d*),?(\\d*)\\r+\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->dsflowrpt_regex = g_regex_new ("\\r\\n\\^DSFLOWRPT:(.+)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->ndisstat_regex = g_regex_new ("\\r\\n(\\^NDISSTAT:.+)\\r+\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| |
| self->priv->orig_regex = g_regex_new ("\\r\\n\\^ORIG:\\s*(\\d+),\\s*(\\d+)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->conf_regex = g_regex_new ("\\r\\n\\^CONF:\\s*(\\d+)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->conn_regex = g_regex_new ("\\r\\n\\^CONN:\\s*(\\d+),\\s*(\\d+)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->cend_regex = g_regex_new ("\\r\\n\\^CEND:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d*))?\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->ddtmf_regex = g_regex_new ("\\r\\n\\^DDTMF:\\s*([0-9A-D\\*\\#])\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| |
| self->priv->boot_regex = g_regex_new ("\\r\\n\\^BOOT:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->connect_regex = g_regex_new ("\\r\\n\\^CONNECT .+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->csnr_regex = g_regex_new ("\\r\\n\\^CSNR:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->cusatp_regex = g_regex_new ("\\r\\n\\+CUSATP:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->cusatend_regex = g_regex_new ("\\r\\n\\+CUSATEND\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->dsdormant_regex = g_regex_new ("\\r\\n\\^DSDORMANT:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->simst_regex = g_regex_new ("\\r\\n\\^SIMST:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->srvst_regex = g_regex_new ("\\r\\n\\^SRVST:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->stin_regex = g_regex_new ("\\r\\n\\^STIN:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->hcsq_regex = g_regex_new ("\\r\\n(\\^HCSQ:.+)\\r+\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->pdpdeact_regex = g_regex_new ("\\r\\n\\^PDPDEACT:.+\\r+\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->ndisend_regex = g_regex_new ("\\r\\n\\^NDISEND:.+\\r+\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->rfswitch_regex = g_regex_new ("\\r\\n\\^RFSWITCH:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->position_regex = g_regex_new ("\\r\\n\\^POSITION:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->posend_regex = g_regex_new ("\\r\\n\\^POSEND:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->ecclist_regex = g_regex_new ("\\r\\n\\^ECCLIST:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->ltersrp_regex = g_regex_new ("\\r\\n\\^LTERSRP:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->cschannelinfo_regex = g_regex_new ("\\r\\n\\^CSCHANNELINFO:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->ccallstate_regex = g_regex_new ("\\r\\n\\^CCALLSTATE:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->eons_regex = g_regex_new ("\\r\\n\\^EONS:.+\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| |
| self->priv->ndisdup_support = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->rfswitch_support = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->sysinfoex_support = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->syscfg_support = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->syscfgex_support = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->prefmode_support = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->nwtime_support = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->time_support = FEATURE_SUPPORT_UNKNOWN; |
| self->priv->cvoice_support = FEATURE_SUPPORT_UNKNOWN; |
| } |
| |
| static void |
| dispose (GObject *object) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (object); |
| |
| detailed_signal_clear (&self->priv->detailed_signal); |
| |
| G_OBJECT_CLASS (mm_broadband_modem_huawei_parent_class)->dispose (object); |
| } |
| |
| static void |
| finalize (GObject *object) |
| { |
| MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (object); |
| |
| g_regex_unref (self->priv->rssi_regex); |
| g_regex_unref (self->priv->rssilvl_regex); |
| g_regex_unref (self->priv->hrssilvl_regex); |
| g_regex_unref (self->priv->mode_regex); |
| g_regex_unref (self->priv->dsflowrpt_regex); |
| g_regex_unref (self->priv->ndisstat_regex); |
| g_regex_unref (self->priv->orig_regex); |
| g_regex_unref (self->priv->conf_regex); |
| g_regex_unref (self->priv->conn_regex); |
| g_regex_unref (self->priv->cend_regex); |
| g_regex_unref (self->priv->ddtmf_regex); |
| |
| g_regex_unref (self->priv->boot_regex); |
| g_regex_unref (self->priv->connect_regex); |
| g_regex_unref (self->priv->csnr_regex); |
| g_regex_unref (self->priv->cusatp_regex); |
| g_regex_unref (self->priv->cusatend_regex); |
| g_regex_unref (self->priv->dsdormant_regex); |
| g_regex_unref (self->priv->simst_regex); |
| g_regex_unref (self->priv->srvst_regex); |
| g_regex_unref (self->priv->stin_regex); |
| g_regex_unref (self->priv->hcsq_regex); |
| g_regex_unref (self->priv->pdpdeact_regex); |
| g_regex_unref (self->priv->ndisend_regex); |
| g_regex_unref (self->priv->rfswitch_regex); |
| g_regex_unref (self->priv->position_regex); |
| g_regex_unref (self->priv->posend_regex); |
| g_regex_unref (self->priv->ecclist_regex); |
| g_regex_unref (self->priv->ltersrp_regex); |
| g_regex_unref (self->priv->cschannelinfo_regex); |
| g_regex_unref (self->priv->ccallstate_regex); |
| g_regex_unref (self->priv->eons_regex); |
| |
| if (self->priv->syscfg_supported_modes) |
| g_array_unref (self->priv->syscfg_supported_modes); |
| if (self->priv->syscfgex_supported_modes) |
| g_array_unref (self->priv->syscfgex_supported_modes); |
| if (self->priv->prefmode_supported_modes) |
| g_array_unref (self->priv->prefmode_supported_modes); |
| |
| G_OBJECT_CLASS (mm_broadband_modem_huawei_parent_class)->finalize (object); |
| } |
| |
| static void |
| iface_modem_init (MMIfaceModem *iface) |
| { |
| iface_modem_parent = g_type_interface_peek_parent (iface); |
| |
| iface->reset = reset; |
| iface->reset_finish = reset_finish; |
| iface->load_access_technologies = load_access_technologies; |
| iface->load_access_technologies_finish = load_access_technologies_finish; |
| iface->load_unlock_retries = load_unlock_retries; |
| iface->load_unlock_retries_finish = load_unlock_retries_finish; |
| iface->modem_after_sim_unlock = modem_after_sim_unlock; |
| iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_finish; |
| iface->load_current_bands = load_current_bands; |
| iface->load_current_bands_finish = load_current_bands_finish; |
| iface->set_current_bands = set_current_bands; |
| iface->set_current_bands_finish = set_current_bands_finish; |
| iface->load_supported_modes = load_supported_modes; |
| iface->load_supported_modes_finish = load_supported_modes_finish; |
| iface->load_current_modes = load_current_modes; |
| iface->load_current_modes_finish = load_current_modes_finish; |
| iface->set_current_modes = set_current_modes; |
| iface->set_current_modes_finish = set_current_modes_finish; |
| iface->load_signal_quality = modem_load_signal_quality; |
| iface->load_signal_quality_finish = modem_load_signal_quality_finish; |
| iface->create_bearer = huawei_modem_create_bearer; |
| iface->create_bearer_finish = huawei_modem_create_bearer_finish; |
| iface->load_power_state = load_power_state; |
| iface->load_power_state_finish = load_power_state_finish; |
| iface->modem_power_up = huawei_modem_power_up; |
| iface->modem_power_up_finish = huawei_modem_power_up_finish; |
| iface->modem_power_down = huawei_modem_power_down; |
| iface->modem_power_down_finish = huawei_modem_power_down_finish; |
| iface->create_sim = huawei_modem_create_sim; |
| iface->create_sim_finish = huawei_modem_create_sim_finish; |
| } |
| |
| static void |
| iface_modem_3gpp_init (MMIfaceModem3gpp *iface) |
| { |
| iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); |
| |
| iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; |
| iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; |
| iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; |
| iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; |
| iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; |
| iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; |
| iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; |
| iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; |
| } |
| |
| static void |
| iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface) |
| { |
| iface->encode = encode; |
| iface->decode = decode; |
| } |
| |
| static void |
| iface_modem_cdma_init (MMIfaceModemCdma *iface) |
| { |
| iface_modem_cdma_parent = g_type_interface_peek_parent (iface); |
| |
| 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 = setup_registration_checks; |
| iface->setup_registration_checks_finish = setup_registration_checks_finish; |
| iface->get_detailed_registration_state = get_detailed_registration_state; |
| iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish; |
| } |
| |
| static void |
| iface_modem_location_init (MMIfaceModemLocation *iface) |
| { |
| iface_modem_location_parent = g_type_interface_peek_parent (iface); |
| |
| iface->load_capabilities = location_load_capabilities; |
| iface->load_capabilities_finish = location_load_capabilities_finish; |
| iface->enable_location_gathering = enable_location_gathering; |
| iface->enable_location_gathering_finish = enable_location_gathering_finish; |
| iface->disable_location_gathering = disable_location_gathering; |
| iface->disable_location_gathering_finish = disable_location_gathering_finish; |
| } |
| |
| static void |
| iface_modem_time_init (MMIfaceModemTime *iface) |
| { |
| iface->check_support = modem_time_check_support; |
| iface->check_support_finish = modem_time_check_support_finish; |
| iface->load_network_time = modem_time_load_network_time_or_zone; |
| iface->load_network_time_finish = modem_time_load_network_time_finish; |
| iface->load_network_timezone = modem_time_load_network_time_or_zone; |
| iface->load_network_timezone_finish = modem_time_load_network_timezone_finish; |
| } |
| |
| static void |
| iface_modem_voice_init (MMIfaceModemVoice *iface) |
| { |
| iface_modem_voice_parent = g_type_interface_peek_parent (iface); |
| |
| iface->check_support = modem_voice_check_support; |
| iface->check_support_finish = modem_voice_check_support_finish; |
| iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events; |
| iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish; |
| iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events; |
| iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish; |
| iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events; |
| iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish; |
| iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events; |
| iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish; |
| iface->setup_in_call_audio_channel = modem_voice_setup_in_call_audio_channel; |
| iface->setup_in_call_audio_channel_finish = modem_voice_setup_in_call_audio_channel_finish; |
| iface->cleanup_in_call_audio_channel = modem_voice_cleanup_in_call_audio_channel; |
| iface->cleanup_in_call_audio_channel_finish = modem_voice_cleanup_in_call_audio_channel_finish; |
| |
| iface->create_call = create_call; |
| } |
| |
| static void |
| iface_modem_signal_init (MMIfaceModemSignal *iface) |
| { |
| iface->check_support = signal_check_support; |
| iface->check_support_finish = signal_check_support_finish; |
| iface->load_values = signal_load_values; |
| iface->load_values_finish = signal_load_values_finish; |
| } |
| |
| static void |
| mm_broadband_modem_huawei_class_init (MMBroadbandModemHuaweiClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); |
| |
| g_type_class_add_private (object_class, sizeof (MMBroadbandModemHuaweiPrivate)); |
| |
| object_class->dispose = dispose; |
| object_class->finalize = finalize; |
| |
| broadband_modem_class->setup_ports = setup_ports; |
| } |