| /* -*- 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) 2012 Aleksander Morgado <aleksander@gnu.org> |
| */ |
| |
| #include <config.h> |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| |
| #include "ModemManager.h" |
| #include "mm-modem-helpers.h" |
| #include "mm-log.h" |
| #include "mm-errors-types.h" |
| #include "mm-iface-modem.h" |
| #include "mm-iface-modem-3gpp.h" |
| #include "mm-base-modem-at.h" |
| #include "mm-broadband-modem-option.h" |
| |
| static void iface_modem_init (MMIfaceModem *iface); |
| static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); |
| |
| static MMIfaceModem3gpp *iface_modem_3gpp_parent; |
| |
| G_DEFINE_TYPE_EXTENDED (MMBroadbandModemOption, mm_broadband_modem_option, 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)); |
| |
| struct _MMBroadbandModemOptionPrivate { |
| /* Regex for access-technology related notifications */ |
| GRegex *_ossysi_regex; |
| GRegex *_octi_regex; |
| GRegex *_ouwcti_regex; |
| |
| /* Regex for signal quality related notifications */ |
| GRegex *_osigq_regex; |
| |
| /* Regex for other notifications to ignore */ |
| GRegex *ignore_regex; |
| |
| guint after_power_up_wait_id; |
| }; |
| |
| /*****************************************************************************/ |
| /* Load initial allowed/preferred modes (Modem interface) */ |
| |
| static gboolean |
| load_allowed_modes_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| MMModemMode *allowed, |
| MMModemMode *preferred, |
| GError **error) |
| { |
| const gchar *response; |
| const gchar *str; |
| gint a, b; |
| |
| response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); |
| if (!response) |
| return FALSE; |
| |
| str = mm_strip_tag (response, "_OPSYS:"); |
| |
| if (!sscanf (str, "%d,%d", &a, &b)) { |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't parse OPSYS response: '%s'", |
| response); |
| return FALSE; |
| } |
| |
| switch (a) { |
| case 0: |
| *allowed = MM_MODEM_MODE_2G; |
| *preferred = MM_MODEM_MODE_NONE; |
| return TRUE; |
| case 1: |
| *allowed = MM_MODEM_MODE_3G; |
| *preferred = MM_MODEM_MODE_NONE; |
| return TRUE; |
| case 2: |
| *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); |
| *preferred = MM_MODEM_MODE_2G; |
| return TRUE; |
| case 3: |
| *allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); |
| *preferred = MM_MODEM_MODE_3G; |
| return TRUE; |
| case 5: /* any */ |
| *allowed = (MM_MODEM_MODE_CS | MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); |
| *preferred = MM_MODEM_MODE_NONE; |
| return TRUE; |
| default: |
| break; |
| } |
| |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't parse unexpected OPSYS response: '%s'", |
| response); |
| return FALSE; |
| } |
| |
| static void |
| load_allowed_modes (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_base_modem_at_command (MM_BASE_MODEM (self), |
| "_OPSYS?", |
| 3, |
| FALSE, |
| callback, |
| user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* Set allowed modes (Modem interface) */ |
| |
| static gboolean |
| set_allowed_modes_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static void |
| allowed_mode_update_ready (MMBroadbandModemOption *self, |
| GAsyncResult *res, |
| GSimpleAsyncResult *operation_result) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error); |
| if (error) |
| /* Let the error be critical. */ |
| g_simple_async_result_take_error (operation_result, error); |
| else |
| g_simple_async_result_set_op_res_gboolean (operation_result, TRUE); |
| g_simple_async_result_complete (operation_result); |
| g_object_unref (operation_result); |
| } |
| |
| static void |
| set_allowed_modes (MMIfaceModem *self, |
| MMModemMode allowed, |
| MMModemMode preferred, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GSimpleAsyncResult *result; |
| gchar *command; |
| gint option_mode = -1; |
| |
| result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| set_allowed_modes); |
| |
| /* There is no explicit config for CS connections, we just assume we may |
| * have them as part of 2G when no GPRS is available */ |
| if (allowed & MM_MODEM_MODE_CS) { |
| allowed |= MM_MODEM_MODE_2G; |
| allowed &= ~MM_MODEM_MODE_CS; |
| } |
| |
| if (allowed == MM_MODEM_MODE_2G) |
| option_mode = 0; |
| else if (allowed == MM_MODEM_MODE_3G) |
| option_mode = 1; |
| else if (allowed == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) { |
| if (preferred == MM_MODEM_MODE_2G) |
| option_mode = 2; |
| else if (preferred == MM_MODEM_MODE_3G) |
| option_mode = 3; |
| else /* none preferred, so AUTO */ |
| option_mode = 5; |
| } |
| |
| if (option_mode < 0) { |
| gchar *allowed_str; |
| gchar *preferred_str; |
| |
| allowed_str = mm_modem_mode_build_string_from_mask (allowed); |
| preferred_str = mm_modem_mode_build_string_from_mask (preferred); |
| g_simple_async_result_set_error (result, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Requested mode (allowed: '%s', preferred: '%s') not " |
| "supported by the modem.", |
| allowed_str, |
| preferred_str); |
| g_free (allowed_str); |
| g_free (preferred_str); |
| |
| g_simple_async_result_complete_in_idle (result); |
| g_object_unref (result); |
| return; |
| } |
| |
| command = g_strdup_printf ("AT_OPSYS=%d,2", option_mode); |
| mm_base_modem_at_command ( |
| MM_BASE_MODEM (self), |
| command, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)allowed_mode_update_ready, |
| result); |
| g_free (command); |
| } |
| |
| /*****************************************************************************/ |
| /* Load access technologies (Modem interface) */ |
| |
| typedef enum { |
| ACCESS_TECHNOLOGIES_STEP_FIRST, |
| ACCESS_TECHNOLOGIES_STEP_OSSYS, |
| ACCESS_TECHNOLOGIES_STEP_OCTI, |
| ACCESS_TECHNOLOGIES_STEP_OWCTI, |
| ACCESS_TECHNOLOGIES_STEP_LAST |
| } AccessTechnologiesStep; |
| |
| typedef struct { |
| MMBroadbandModemOption *self; |
| GSimpleAsyncResult *result; |
| MMModemAccessTechnology access_technology; |
| gboolean check_2g; |
| gboolean check_3g; |
| AccessTechnologiesStep step; |
| } AccessTechnologiesContext; |
| |
| static void load_access_technologies_step (AccessTechnologiesContext *ctx); |
| |
| static void |
| access_technologies_context_complete_and_free (AccessTechnologiesContext *ctx) |
| { |
| g_simple_async_result_complete (ctx->result); |
| g_object_unref (ctx->result); |
| g_object_unref (ctx->self); |
| g_free (ctx); |
| } |
| |
| static gboolean |
| load_access_technologies_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| MMModemAccessTechnology *access_technologies, |
| guint *mask, |
| GError **error) |
| { |
| if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) |
| return FALSE; |
| |
| /* We are reporting ALL 3GPP access technologies here */ |
| *access_technologies = (MMModemAccessTechnology) GPOINTER_TO_UINT ( |
| g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); |
| *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK; |
| return TRUE; |
| } |
| |
| static gboolean |
| ossys_to_mm (gchar ossys, |
| MMModemAccessTechnology *access_technology) |
| { |
| if (ossys == '0') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; |
| return TRUE; |
| } |
| |
| if (ossys == '2') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| return TRUE; |
| } |
| |
| if (ossys == '3') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| parse_ossys_response (const gchar *response, |
| MMModemAccessTechnology *access_technology) |
| { |
| MMModemAccessTechnology current = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| const gchar *p; |
| GRegex *r; |
| GMatchInfo *match_info; |
| gchar *str; |
| gboolean success = FALSE; |
| |
| p = mm_strip_tag (response, "_OSSYS:"); |
| r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); |
| g_assert (r != NULL); |
| |
| g_regex_match (r, p, 0, &match_info); |
| if (g_match_info_matches (match_info)) { |
| str = g_match_info_fetch (match_info, 2); |
| if (str && ossys_to_mm (str[0], ¤t)) { |
| *access_technology = current; |
| success = TRUE; |
| } |
| g_free (str); |
| } |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| |
| return success; |
| } |
| |
| static void |
| ossys_query_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| AccessTechnologiesContext *ctx) |
| { |
| const gchar *response; |
| |
| /* If for some reason the OSSYS request failed, still try to check |
| * explicit 2G/3G mode with OCTI and OWCTI; maybe we'll get something. |
| */ |
| response = mm_base_modem_at_command_finish (self, res, NULL); |
| /* Response is _OSSYS: <n>,<act> so we must skip the <n> */ |
| if (response && |
| parse_ossys_response (response, &ctx->access_technology)) { |
| /* If the OSSYS response indicated a generic access tech type |
| * then only check for more specific access tech of that type. |
| */ |
| if (ctx->access_technology == MM_MODEM_ACCESS_TECHNOLOGY_GPRS) |
| ctx->check_3g = FALSE; |
| else if (ctx->access_technology == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) |
| ctx->check_2g = FALSE; |
| } |
| |
| /* Go on to next step */ |
| ctx->step++; |
| load_access_technologies_step (ctx); |
| } |
| |
| static gboolean |
| octi_to_mm (gchar octi, |
| MMModemAccessTechnology *access_technology) |
| { |
| if (octi == '1') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GSM; |
| return TRUE; |
| } |
| |
| if (octi == '2') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; |
| return TRUE; |
| } |
| |
| if (octi == '3') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_EDGE; |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| parse_octi_response (const gchar *response, |
| MMModemAccessTechnology *access_technology) |
| { |
| MMModemAccessTechnology current = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| const gchar *p; |
| GRegex *r; |
| GMatchInfo *match_info; |
| gchar *str; |
| gboolean success = FALSE; |
| |
| p = mm_strip_tag (response, "_OCTI:"); |
| r = g_regex_new ("(\\d),(\\d)", G_REGEX_UNGREEDY, 0, NULL); |
| g_assert (r != NULL); |
| |
| g_regex_match (r, p, 0, &match_info); |
| if (g_match_info_matches (match_info)) { |
| str = g_match_info_fetch (match_info, 2); |
| if (str && octi_to_mm (str[0], ¤t)) { |
| *access_technology = current; |
| success = TRUE; |
| } |
| g_free (str); |
| } |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| |
| return success; |
| } |
| |
| static void |
| octi_query_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| AccessTechnologiesContext *ctx) |
| { |
| MMModemAccessTechnology octi = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| const gchar *response; |
| |
| response = mm_base_modem_at_command_finish (self, res, NULL); |
| if (response && |
| parse_octi_response (response, &octi)) { |
| /* If current tech is 2G or unknown then use the more specific |
| * OCTI response. |
| */ |
| if (ctx->access_technology < MM_MODEM_ACCESS_TECHNOLOGY_UMTS) |
| ctx->access_technology = octi; |
| } |
| |
| /* Go on to next step */ |
| ctx->step++; |
| load_access_technologies_step (ctx); |
| } |
| |
| static gboolean |
| owcti_to_mm (gchar owcti, MMModemAccessTechnology *access_technology) |
| { |
| if (owcti == '1') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| return TRUE; |
| } |
| |
| if (owcti == '2') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; |
| return TRUE; |
| } |
| |
| if (owcti == '3') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; |
| return TRUE; |
| } |
| |
| if (owcti == '4') { |
| *access_technology = MM_MODEM_ACCESS_TECHNOLOGY_HSPA; |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| parse_owcti_response (const gchar *response, |
| MMModemAccessTechnology *access_technology) |
| { |
| response = mm_strip_tag (response, "_OWCTI:"); |
| return owcti_to_mm (*response, access_technology); |
| } |
| |
| static void |
| owcti_query_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| AccessTechnologiesContext *ctx) |
| { |
| MMModemAccessTechnology owcti = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| const gchar *response; |
| |
| response = mm_base_modem_at_command_finish (self, res, NULL); |
| if (response && |
| parse_owcti_response (response, &owcti)) { |
| ctx->access_technology = owcti; |
| } |
| |
| /* Go on to next step */ |
| ctx->step++; |
| load_access_technologies_step (ctx); |
| } |
| |
| static void |
| load_access_technologies_step (AccessTechnologiesContext *ctx) |
| { |
| switch (ctx->step) { |
| case ACCESS_TECHNOLOGIES_STEP_FIRST: |
| /* Go on to next step */ |
| ctx->step++; |
| |
| case ACCESS_TECHNOLOGIES_STEP_OSSYS: |
| mm_base_modem_at_command (MM_BASE_MODEM (ctx->self), |
| "_OSSYS?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)ossys_query_ready, |
| ctx); |
| break; |
| |
| case ACCESS_TECHNOLOGIES_STEP_OCTI: |
| if (ctx->check_2g) { |
| mm_base_modem_at_command (MM_BASE_MODEM (ctx->self), |
| "_OCTI?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)octi_query_ready, |
| ctx); |
| return; |
| } |
| /* Go on to next step */ |
| ctx->step++; |
| |
| case ACCESS_TECHNOLOGIES_STEP_OWCTI: |
| if (ctx->check_3g) { |
| mm_base_modem_at_command (MM_BASE_MODEM (ctx->self), |
| "_OWCTI?", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)owcti_query_ready, |
| ctx); |
| return; |
| } |
| /* Go on to next step */ |
| ctx->step++; |
| |
| case ACCESS_TECHNOLOGIES_STEP_LAST: |
| /* All done, set result and complete */ |
| g_simple_async_result_set_op_res_gpointer (ctx->result, |
| GUINT_TO_POINTER (ctx->access_technology), |
| NULL); |
| access_technologies_context_complete_and_free (ctx); |
| break; |
| } |
| } |
| |
| static void |
| run_access_technology_loading_sequence (MMIfaceModem *self, |
| AccessTechnologiesStep first, |
| gboolean check_2g, |
| gboolean check_3g, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| AccessTechnologiesContext *ctx; |
| |
| ctx = g_new (AccessTechnologiesContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| run_access_technology_loading_sequence); |
| ctx->step = first; |
| ctx->check_2g = check_2g; |
| ctx->check_3g = check_3g; |
| ctx->access_technology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| |
| load_access_technologies_step (ctx); |
| } |
| |
| static void |
| load_access_technologies (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| run_access_technology_loading_sequence (self, |
| ACCESS_TECHNOLOGIES_STEP_FIRST, |
| TRUE, /* check 2g */ |
| TRUE, /* check 3g */ |
| callback, |
| user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* After power up (Modem interface) */ |
| |
| static gboolean |
| modem_after_power_up_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static gboolean |
| after_power_up_wait_cb (GSimpleAsyncResult *result) |
| { |
| MMBroadbandModemOption *option; |
| |
| option = MM_BROADBAND_MODEM_OPTION (g_async_result_get_source_object (G_ASYNC_RESULT (result))); |
| |
| g_simple_async_result_set_op_res_gboolean (result, TRUE); |
| g_simple_async_result_complete (result); |
| g_object_unref (result); |
| |
| option->priv->after_power_up_wait_id = 0; |
| g_object_unref (option); |
| |
| return FALSE; |
| } |
| |
| static void |
| modem_after_power_up (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMBroadbandModemOption *option = MM_BROADBAND_MODEM_OPTION (self); |
| GSimpleAsyncResult *result; |
| |
| /* Some Option devices return OK on +CFUN=1 right away but need some time |
| * to finish initialization. |
| */ |
| result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| modem_after_power_up); |
| g_warn_if_fail (option->priv->after_power_up_wait_id == 0); |
| option->priv->after_power_up_wait_id = |
| g_timeout_add_seconds (10, |
| (GSourceFunc)after_power_up_wait_cb, |
| result); |
| } |
| |
| /*****************************************************************************/ |
| /* Setup/Cleanup unsolicited events (3GPP interface) */ |
| |
| static void |
| option_ossys_tech_changed (MMAtSerialPort *port, |
| GMatchInfo *info, |
| MMBroadbandModemOption *self) |
| { |
| MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| gchar *str; |
| |
| str = g_match_info_fetch (info, 1); |
| if (str) { |
| ossys_to_mm (str[0], &act); |
| g_free (str); |
| } |
| |
| mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), |
| act, |
| MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); |
| |
| /* _OSSYSI only indicates general 2G/3G mode, so queue up some explicit |
| * access technology requests. |
| */ |
| if (act == MM_MODEM_ACCESS_TECHNOLOGY_GPRS) |
| run_access_technology_loading_sequence (MM_IFACE_MODEM (self), |
| ACCESS_TECHNOLOGIES_STEP_OCTI, |
| TRUE, /* check 2g */ |
| FALSE, /* check 3g */ |
| NULL, |
| NULL); |
| else if (act == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) |
| run_access_technology_loading_sequence (MM_IFACE_MODEM (self), |
| ACCESS_TECHNOLOGIES_STEP_OWCTI, |
| FALSE, /* check 2g */ |
| TRUE, /* check 3g */ |
| NULL, |
| NULL); |
| } |
| |
| static void |
| option_2g_tech_changed (MMAtSerialPort *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemOption *self) |
| { |
| MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| gchar *str; |
| |
| str = g_match_info_fetch (match_info, 1); |
| if (str && octi_to_mm (str[0], &act)) |
| mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), |
| act, |
| MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); |
| g_free (str); |
| } |
| |
| static void |
| option_3g_tech_changed (MMAtSerialPort *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemOption *self) |
| { |
| MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| gchar *str; |
| |
| str = g_match_info_fetch (match_info, 1); |
| if (str && owcti_to_mm (str[0], &act)) |
| mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), |
| act, |
| MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); |
| g_free (str); |
| } |
| |
| static void |
| option_signal_changed (MMAtSerialPort *port, |
| GMatchInfo *match_info, |
| MMBroadbandModemOption *self) |
| { |
| gchar *str; |
| gint quality = 0; |
| |
| str = g_match_info_fetch (match_info, 1); |
| if (str) { |
| quality = atoi (str); |
| g_free (str); |
| } |
| |
| if (quality == 99) { |
| /* 99 means unknown */ |
| quality = 0; |
| } else { |
| /* Normalize the quality */ |
| quality = CLAMP (quality, 0, 31) * 100 / 31; |
| } |
| |
| mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), (guint)quality); |
| } |
| |
| static void |
| set_unsolicited_events_handlers (MMBroadbandModemOption *self, |
| gboolean enable) |
| { |
| MMAtSerialPort *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)); |
| |
| /* Enable unsolicited events in given port */ |
| for (i = 0; i < 2; i++) { |
| if (!ports[i]) |
| continue; |
| |
| /* Access technology related */ |
| mm_at_serial_port_add_unsolicited_msg_handler ( |
| ports[i], |
| self->priv->_ossysi_regex, |
| enable ? (MMAtSerialUnsolicitedMsgFn)option_ossys_tech_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| mm_at_serial_port_add_unsolicited_msg_handler ( |
| ports[i], |
| self->priv->_octi_regex, |
| enable ? (MMAtSerialUnsolicitedMsgFn)option_2g_tech_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| mm_at_serial_port_add_unsolicited_msg_handler ( |
| ports[i], |
| self->priv->_ouwcti_regex, |
| enable ? (MMAtSerialUnsolicitedMsgFn)option_3g_tech_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| |
| /* Signal quality related */ |
| mm_at_serial_port_add_unsolicited_msg_handler ( |
| ports[i], |
| self->priv->_osigq_regex, |
| enable ? (MMAtSerialUnsolicitedMsgFn)option_signal_changed : NULL, |
| enable ? self : NULL, |
| NULL); |
| |
| /* Other unsolicited events to always ignore */ |
| if (!enable) |
| mm_at_serial_port_add_unsolicited_msg_handler ( |
| ports[i], |
| self->priv->ignore_regex, |
| NULL, NULL, NULL); |
| } |
| } |
| |
| static gboolean |
| modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static void |
| parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GSimpleAsyncResult *simple) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) |
| g_simple_async_result_take_error (simple, error); |
| else { |
| /* Our own setup now */ |
| set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), TRUE); |
| g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res), TRUE); |
| } |
| |
| g_simple_async_result_complete (simple); |
| g_object_unref (simple); |
| } |
| |
| static void |
| modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GSimpleAsyncResult *result; |
| |
| result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| modem_3gpp_setup_unsolicited_events); |
| |
| /* Chain up parent's setup */ |
| iface_modem_3gpp_parent->setup_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, |
| result); |
| } |
| |
| static void |
| parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GSimpleAsyncResult *simple) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) |
| g_simple_async_result_take_error (simple, error); |
| else |
| g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res), TRUE); |
| g_simple_async_result_complete (simple); |
| g_object_unref (simple); |
| } |
| |
| static void |
| modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GSimpleAsyncResult *result; |
| |
| result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| modem_3gpp_cleanup_unsolicited_events); |
| |
| /* Our own cleanup first */ |
| set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), FALSE); |
| |
| /* And now chain up parent's cleanup */ |
| iface_modem_3gpp_parent->cleanup_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, |
| result); |
| } |
| |
| /*****************************************************************************/ |
| /* Enabling unsolicited events (3GPP interface) */ |
| |
| static gboolean |
| modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static void |
| own_enable_unsolicited_events_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GSimpleAsyncResult *simple) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); |
| if (error) |
| g_simple_async_result_take_error (simple, error); |
| else |
| g_simple_async_result_set_op_res_gboolean (simple, TRUE); |
| g_simple_async_result_complete (simple); |
| g_object_unref (simple); |
| } |
| |
| static const MMBaseModemAtCommand unsolicited_enable_sequence[] = { |
| { "_OSSYS=1", 3, FALSE, NULL }, |
| { "_OCTI=1", 3, FALSE, NULL }, |
| { "_OUWCTI=1", 3, FALSE, NULL }, |
| { "_OSQI=1", 3, FALSE, NULL }, |
| { NULL } |
| }; |
| |
| static void |
| parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GSimpleAsyncResult *simple) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) { |
| g_simple_async_result_take_error (simple, error); |
| g_simple_async_result_complete (simple); |
| g_object_unref (simple); |
| } |
| |
| /* 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, |
| simple); |
| } |
| |
| static void |
| modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GSimpleAsyncResult *result; |
| |
| result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| modem_3gpp_enable_unsolicited_events); |
| |
| /* Chain up parent's enable */ |
| iface_modem_3gpp_parent->enable_unsolicited_events ( |
| self, |
| (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, |
| result); |
| } |
| |
| /*****************************************************************************/ |
| /* Disabling unsolicited events (3GPP interface) */ |
| |
| static gboolean |
| modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); |
| } |
| |
| static const MMBaseModemAtCommand unsolicited_disable_sequence[] = { |
| { "_OSSYS=0", 3, FALSE, NULL }, |
| { "_OCTI=0", 3, FALSE, NULL }, |
| { "_OUWCTI=0", 3, FALSE, NULL }, |
| { "_OSQI=0", 3, FALSE, NULL }, |
| { NULL } |
| }; |
| |
| static void |
| parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GSimpleAsyncResult *simple) |
| { |
| GError *error = NULL; |
| |
| if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) |
| g_simple_async_result_take_error (simple, error); |
| else |
| g_simple_async_result_set_op_res_gboolean (simple, TRUE); |
| g_simple_async_result_complete (simple); |
| g_object_unref (simple); |
| } |
| |
| static void |
| own_disable_unsolicited_events_ready (MMBaseModem *self, |
| GAsyncResult *res, |
| GSimpleAsyncResult *simple) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_sequence_full_finish (self, res, NULL, &error); |
| if (error) { |
| g_simple_async_result_take_error (simple, error); |
| g_simple_async_result_complete (simple); |
| g_object_unref (simple); |
| 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, |
| simple); |
| } |
| |
| static void |
| modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GSimpleAsyncResult *result; |
| |
| result = g_simple_async_result_new (G_OBJECT (self), |
| callback, |
| user_data, |
| modem_3gpp_disable_unsolicited_events); |
| |
| /* Our own disable first */ |
| mm_base_modem_at_sequence_full ( |
| MM_BASE_MODEM (self), |
| mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)), |
| unsolicited_disable_sequence, |
| NULL, /* response_processor_context */ |
| NULL, /* response_processor_context_free */ |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)own_disable_unsolicited_events_ready, |
| result); |
| } |
| |
| /*****************************************************************************/ |
| /* Setup ports (Broadband modem class) */ |
| |
| static void |
| setup_ports (MMBroadbandModem *self) |
| { |
| /* Call parent's setup ports first always */ |
| MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_option_parent_class)->setup_ports (self); |
| |
| /* Now reset the unsolicited messages we'll handle when enabled */ |
| set_unsolicited_events_handlers (MM_BROADBAND_MODEM_OPTION (self), FALSE); |
| } |
| |
| /*****************************************************************************/ |
| |
| MMBroadbandModemOption * |
| mm_broadband_modem_option_new (const gchar *device, |
| const gchar *driver, |
| const gchar *plugin, |
| guint16 vendor_id, |
| guint16 product_id) |
| { |
| return g_object_new (MM_TYPE_BROADBAND_MODEM_OPTION, |
| MM_BASE_MODEM_DEVICE, device, |
| MM_BASE_MODEM_DRIVER, driver, |
| MM_BASE_MODEM_PLUGIN, plugin, |
| MM_BASE_MODEM_VENDOR_ID, vendor_id, |
| MM_BASE_MODEM_PRODUCT_ID, product_id, |
| NULL); |
| } |
| |
| static void |
| finalize (GObject *object) |
| { |
| MMBroadbandModemOption *self = MM_BROADBAND_MODEM_OPTION (object); |
| |
| g_regex_unref (self->priv->_ossysi_regex); |
| g_regex_unref (self->priv->_octi_regex); |
| g_regex_unref (self->priv->_ouwcti_regex); |
| g_regex_unref (self->priv->_osigq_regex); |
| g_regex_unref (self->priv->ignore_regex); |
| |
| G_OBJECT_CLASS (mm_broadband_modem_option_parent_class)->finalize (object); |
| } |
| |
| static void |
| mm_broadband_modem_option_init (MMBroadbandModemOption *self) |
| { |
| /* Initialize private data */ |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), |
| MM_TYPE_BROADBAND_MODEM_OPTION, |
| MMBroadbandModemOptionPrivate); |
| self->priv->after_power_up_wait_id = 0; |
| |
| /* Prepare regular expressions to setup */ |
| self->priv->_ossysi_regex = g_regex_new ("\\r\\n_OSSYSI:\\s*(\\d+)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->_octi_regex = g_regex_new ("\\r\\n_OCTI:\\s*(\\d+)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->_ouwcti_regex = g_regex_new ("\\r\\n_OUWCTI:\\s*(\\d+)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->_osigq_regex = g_regex_new ("\\r\\n_OSIGQ:\\s*(\\d+),(\\d)\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| self->priv->ignore_regex = g_regex_new ("\\r\\n\\+PACSP0\\r\\n", |
| G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); |
| } |
| |
| static void |
| iface_modem_init (MMIfaceModem *iface) |
| { |
| iface->modem_after_power_up = modem_after_power_up; |
| iface->modem_after_power_up_finish = modem_after_power_up_finish; |
| iface->load_access_technologies = load_access_technologies; |
| iface->load_access_technologies_finish = load_access_technologies_finish; |
| iface->load_allowed_modes = load_allowed_modes; |
| iface->load_allowed_modes_finish = load_allowed_modes_finish; |
| iface->set_allowed_modes = set_allowed_modes; |
| iface->set_allowed_modes_finish = set_allowed_modes_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 |
| mm_broadband_modem_option_class_init (MMBroadbandModemOptionClass *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 (MMBroadbandModemOptionPrivate)); |
| |
| object_class->finalize = finalize; |
| broadband_modem_class->setup_ports = setup_ports; |
| } |