<div dir="ltr">Back from the grave,<div><br></div><div>Here is finally the update... to the above code reviewed patch. A big thank you Dan and Aleksander for the detail in the review it was really a great learning experience for me. Please don't hold back with this one either :). It's a pretty big rewrite so I'm sure there will still be some minor things to tidy up still but I think it should be more inline now. There is one thing in here for sure that needs fixing still but I wanted your options so please see the note at line 1192 and let me know what you think. I promise I will be much faster on getting the next tidy up done (>_<) and after this I still have a few more patches.</div><div><br></div><div>Cheers,</div><div>Matt</div></div><div class="gmail_extra"><br><div class="gmail_quote">On Thu, Sep 15, 2016 at 5:30 AM, Aleksander Morgado <span dir="ltr"><<a href="mailto:aleksander@aleksander.es" target="_blank">aleksander@aleksander.es</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hey Matthew,<br>
<br>
I reviewed the first patch of the series only for now; let's try to get<br>
this right first and then we go on with the other ones. See comments<br>
inline below. Some of the comments are coding style only things, but<br>
others try to simplify or change the flow of the logic, let me know what<br>
you think.<br>
<br>
On 06/09/16 03:47, matthew stanger wrote:<br>
> Here is try two. First off if you are uncomfortable with the attachment let<br>
> me know and I'll send the patch in the body. It's just 4k lines and Chrome<br>
> almost crashes when I paste it in. I won't take offence if that is needed<br>
> though. I can also squash it down to a single commit if that makes viewing<br>
> it easier.<br>
><br>
<br>
You're not supposed to "paste" the patch in the email body or "merge"<br>
the patches in a single file and attach them to the email :) The target<br>
is that we can take the patches and quickly apply them with "git am"; if<br>
you paste the patch in the body it's likely that your email application<br>
may decide to modify the body before sending it; if you merge patches<br>
together in a single file, "git am" may end up choking when processing it.<br>
<br>
Sending patches can be done in 3 different ways:<br>
a) Use git-send-email command line tool, this will generate emails<br>
that can then be saved and applied with "git am".<br>
b) Use git format-patch and generate 1 file per patch; then attach the<br>
patches to the email. Again, we can use "git am" to apply them.<br>
c) Prepare a custom branch somewhere; e.g. bitbucket or github, and<br>
email the link to the branch so that we can pull from it.<br>
<br>
For reviewing, I usually prefer a), and if too complex to setup, just do<br>
b). For large developments c) is preferred, though.<br>
<br>
> The attached patch consist of 5 commits since I branched from '<br>
> 59f57befa4be81b562f9adfbac39fb<wbr>e19d88c111'. The first commit being the<br>
> original I sent and the next 4 address suggestions made by Dan. I tried to<br>
> get the GPS separated but there is some hardship in doing that with our<br>
> commit history.<br>
<br>
Reviewing logic is very very hard with commits including different things :/<br>
<br>
I was able to make all of the changes except, I don't think<br>
> I can move PDP_CID to modem initialization as it is tied to the net<br>
> interface being used by the bearer. Leading in to answer your last<br>
> question, it's not hard coded technically but our tec rep's at Cinterion<br>
> told me to map these contexts to these interface to avoid issues, though it<br>
> does seem to work with any context to any interface. I could not<br>
> get report_connection_status() to trigger off a manual connect cancellation<br>
> either so I left that logic in. As far as using the MM core for CGDCONT and<br>
> PDP ctx I thought that would only work if I was using<br>
> the MM_BROADBAND_MODEM class instead of the MM_BROADBAND_MODEM_CINTERION<br>
> class when creating a bearer. I may be super wrong on that though. I just<br>
> say that as I wasn't able to trace how it was called inside<br>
> of find_cid_ready in mm-bb-bearer.c. I though because of that class I had<br>
> to handle our own CGD & PDP ctx but if that's not the case then please let<br>
> me know how I can do that. I made all the other changes & to be honest it<br>
> was quite a mess so thanks for the feedback.<br>
><br>
> I also see you are using an old AT command ref for this modem. Cinterions<br>
> newest is 3.017. I can't give it to you due to NDA but if you want it I'd<br>
> be happy to lobby for you to Cinterion so they will give you a new copy. I<br>
> have already been lobbying MM to them just FYI, hoping they might take some<br>
> of the programming burden but they seemed really only focused on windows<br>
> atm. I'm not sure if you guys have PLS8 modems either but if you want ones<br>
> that's another thing I'm sure I could help you get. Having you test<br>
> anything I push even for 5 mins is well worth it so if you need anything<br>
> like that please let me know.<br>
<br>
Cinterion did send me a test-only PLS8 to play with the QMI integration<br>
they had, but not sure if they're still targeting to use QMI or not.<br>
<br>
See comments for the first patch below.<br>
<br>
> From 3b45e2f8afdd9f3aa284f3938e618b<wbr>4b5437c572 Mon Sep 17 00:00:00 2001<br>
> From: Matthew Stanger <<a href="mailto:matthew_stanger@trimble.com">matthew_stanger@trimble.com</a>><br>
> Date: Fri, 26 Aug 2016 16:35:49 -0600<br>
> Subject: [PATCH 1/5] Features: Added SWWAN support in Cinterion plugin.<br>
> Updated GPS support for Cinterion modems that use new SGPSC commands. Added<br>
> LTE status for Cinterion plugin. Made general Cinterion plugin improvements<br>
> to better support PLS8-X & PLS8-E modems.<br>
><br>
> Known issues:<br>
> SWWAN connection not tied into mmcli status updates.<br>
> Unknown logic flow for APN's which require User & Password.<br>
> No support for IP version.<br>
> Does not pull IP address from SWWAN into mmcli.<br>
> Does not auto preform DHCP for SWWAN connection.<br>
> Dual pdp context connections not yet supported.<br>
> Simple connect/disconnect does not clean up bearer.<br>
> ---<br>
> plugins/Makefile.am | 2 +<br>
> plugins/cinterion/77-mm-<wbr>cinterion-port-types.rules | 2 +-<br>
> plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.c | 1126 ++++++++++++++++++++<br>
> plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.h | 56 +<br>
> plugins/cinterion/mm-<wbr>broadband-modem-cinterion.c | 279 ++++-<br>
> plugins/cinterion/mm-<wbr>broadband-modem-cinterion.h | 3 +<br>
> plugins/cinterion/mm-common-<wbr>cinterion.c | 402 ++++++-<br>
> plugins/cinterion/mm-modem-<wbr>helpers-cinterion.c | 188 ++++<br>
> plugins/cinterion/mm-modem-<wbr>helpers-cinterion.h | 17 +<br>
> plugins/cinterion/mm-plugin-<wbr>cinterion.c | 141 ++-<br>
> src/mm-base-modem.c | 20 +<br>
> src/mm-base-modem.h | 1 +<br>
> 12 files changed, 2182 insertions(+), 55 deletions(-)<br>
> create mode 100644 plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.c<br>
> create mode 100644 plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.h<br>
><br>
> diff --git a/plugins/Makefile.am b/plugins/Makefile.am<br>
> index 018b696..744fd18 100644<br>
> --- a/plugins/Makefile.am<br>
> +++ b/plugins/Makefile.am<br>
> @@ -565,6 +565,8 @@ libmm_plugin_cinterion_la_<wbr>SOURCES = \<br>
> cinterion/mm-common-cinterion.<wbr>h \<br>
> cinterion/mm-broadband-modem-<wbr>cinterion.c \<br>
> cinterion/mm-broadband-modem-<wbr>cinterion.h \<br>
> + cinterion/mm-broadband-bearer-<wbr>cinterion.c \<br>
> + cinterion/mm-broadband-bearer-<wbr>cinterion.h \<br>
> $(NULL)<br>
> if WITH_QMI<br>
> libmm_plugin_cinterion_la_<wbr>SOURCES += \<br>
> diff --git a/plugins/cinterion/77-mm-<wbr>cinterion-port-types.rules b/plugins/cinterion/77-mm-<wbr>cinterion-port-types.rules<br>
> index 09de742..ee386f0 100644<br>
> --- a/plugins/cinterion/77-mm-<wbr>cinterion-port-types.rules<br>
> +++ b/plugins/cinterion/77-mm-<wbr>cinterion-port-types.rules<br>
> @@ -8,4 +8,4 @@ SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInte<br>
><br>
> ATTRS{idVendor}=="1e2d", ATTRS{idProduct}=="0053", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_CINTERION_PORT_TYPE_<wbr>GPS}="1"<br>
><br>
> -LABEL="mm_cinterion_port_<wbr>types_end"<br>
> +LABEL="mm_cinterion_port_<wbr>types_end"<br>
> \ No newline at end of file<br>
<br>
This previous change may be skipped.<br>
<br>
> diff --git a/plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.c b/plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.c<br>
> new file mode 100644<br>
> index 0000000..eb73cf5<br>
> --- /dev/null<br>
> +++ b/plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.c<br>
> @@ -0,0 +1,1126 @@<br>
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */<br>
> +/*<br>
> + * This program is free software; you can redistribute it and/or modify<br>
> + * it under the terms of the GNU General Public License as published by<br>
> + * the Free Software Foundation; either version 2 of the License, or<br>
> + * (at your option) any later version.<br>
> + *<br>
> + * This program is distributed in the hope that it will be useful,<br>
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>
> + * GNU General Public License for more details:<br>
> + *<br>
> + * Copyright (C) 2016 Trimble Navigation Limited<br>
> + * Author: Matthew Stanger <<a href="mailto:matthew_stanger@trimble.com">matthew_stanger@trimble.com</a>><br>
> + */<br>
> +<br>
> +#include <config.h><br>
> +#include <stdio.h><br>
> +#include <stdlib.h><br>
> +#include <unistd.h><br>
> +#include <string.h><br>
> +#include <ctype.h><br>
> +#include <arpa/inet.h><br>
> +#include <ModemManager.h><br>
> +#include "mm-base-modem-at.h"<br>
> +#include "mm-broadband-bearer-<wbr>cinterion.h"<br>
> +#include "mm-log.h"<br>
> +#include "mm-modem-helpers.h"<br>
> +#include "mm-modem-helpers-cinterion.h"<br>
> +#include "mm-daemon-enums-types.h"<br>
> +<br>
> +G_DEFINE_TYPE (MMBroadbandBearerCinterion, mm_broadband_bearer_cinterion, MM_TYPE_BROADBAND_BEARER);<br>
<br>
No need for the last ";"<br>
<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* Common enums and structs */<br>
> +<br>
> +typedef enum {<br>
> + MM_BEARER_CINTERION_AUTH_<wbr>UNKNOWN = -1,<br>
> + MM_BEARER_CINTERION_AUTH_NONE = 0,<br>
> + MM_BEARER_CINTERION_AUTH_PAP = 1,<br>
> + MM_BEARER_CINTERION_AUTH_CHAP = 2,<br>
> + MM_BEARER_CINTERION_AUTH_<wbr>MSCHAPV2 = 3,<br>
> +} MMBearerCinterionAuthPref;<br>
> +<br>
<br>
No need for the MM prefix in the enum if it's not public.<br>
<br>
> +typedef enum {<br>
> + CONNECTION_UNKNOWN = 0,<br>
> + CONNECTION_ACTIVE,<br>
> + CONNECTION_INACTIVE,<br>
> +} SWWAN_USB_CONNECTION_STATE;<br>
<br>
Enum types should be CamelCaseLikeThis.<br>
<br>
> +<br>
> +typedef enum {<br>
> + CONNECT_3GPP_CONTEXT_STEP_INIT = 0,<br>
> + CONNECT_3GPP_CONTEXT_STEP_<wbr>VERIFY_SWWAN,<br>
> + CONNECT_3GPP_CONTEXT_STEP_<wbr>BEARER_PROPERTIES,<br>
> + CONNECT_3GPP_CONTEXT_STEP_SET_<wbr>SWWAN,<br>
> + CONNECT_3GPP_CONTEXT_STEP_<wbr>CONNECTION_STATUS,<br>
> + CONNECT_3GPP_CONTEXT_STEP_<wbr>DONE,<br>
> +} Connect3gppContextStep;<br>
> +<br>
> +typedef enum {<br>
> + DISCONNECT_3GPP_CONTEXT_STEP_<wbr>INIT = 0,<br>
> + DISCONNECT_3GPP_CONTEXT_STEP_<wbr>SWWAN_DETACH,<br>
> + DISCONNECT_3GPP_CONTEXT_STEP_<wbr>CONNECTION_STATUS,<br>
> + DISCONNECT_3GPP_CONTEXT_STEP_<wbr>DONE<br>
> +} Disconnect3gppContextStep;<br>
> +<br>
> +typedef struct {<br>
> + MMBroadbandBearerCinterion *self;<br>
> + MMBaseModem *modem;<br>
> + MMPortSerialAt *primary;<br>
> + MMPort *data;<br>
> + Connect3gppContextStep connect;<br>
> + Disconnect3gppContextStep disconnect;<br>
> + SWWAN_USB_CONNECTION_STATE usb0_state;<br>
> + SWWAN_USB_CONNECTION_STATE usb1_state;<br>
> + MMBearerIpConfig *ipv4_config;<br>
> + GCancellable *cancellable;<br>
> + GSimpleAsyncResult *result;<br>
> +} Control3gppContext;<br>
<br>
I think it should be better to have separate Connect/Disconnect contexts.<br>
<br>
> +<br>
> +struct _<wbr>MMBroadbandBearerCinterionPriv<wbr>ate {<br>
> + gpointer connect_pending;<br>
> + gpointer disconnect_pending;<br>
> + guint network_disconnect_pending_id;<wbr>/* Tag for the post task for network-initiated disconnect */<br>
> + const gchar *bearer_interface;<br>
> + const gchar *pdp_cid;<br>
<br>
This should be a uint.<br>
<br>
> + guint retry_count;<br>
<br>
Retry count looks like part of the connection/disconnect logic;<br>
shouldn't it go in the connect/disconnect context?<br>
<br>
> + guint swwan_read_write;<br>
> +};<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* Common 3GPP Function Declarations */<br>
> +static void connect_3gpp_context_step (Control3gppContext *ctx);<br>
> +static void disconnect_3gpp_context_step (Control3gppContext *ctx);<br>
> +static void connect_3gpp_context_complete_<wbr>and_free (Control3gppContext *ctx);<br>
> +static void disconnect_3gpp_context_<wbr>complete_and_free (Control3gppContext *ctx);<br>
<br>
The connect/disconnect logic should have different contexts, and then<br>
the complete_and_free() names could match the context name.<br>
<br>
E.g. Connect3gppContext with connect_3gpp_context_step() and<br>
connect_3gpp_context_complete_<wbr>and_free ().<br>
<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* Common 3GPP */<br>
> +<br>
> +static MMPortSerialAt *<br>
> +get_dial_port (MMBroadbandModemCinterion *modem,<br>
> + MMPort *data,<br>
> + MMPortSerialAt *primary)<br>
> +{<br>
> + //Use only the primary port for sending AT commands.<br>
<br>
Coding style thing; we don't use "//" ever, not even for single-comment<br>
lines. Use /* this instead */. Applies to multiple places in the code.<br>
<br>
<br>
> + return g_object_ref (primary);<br>
<br>
What is the purpose of this method if it's just ref-ing the passed<br>
primary port? Why not just "g_object_ref (primary)" in the caller?<br>
<br>
> +}<br>
> +<br>
> +static void<br>
> +cinterion_3gpp_state_machine_<wbr>logic (MMBroadbandBearerCinterion *self)<br>
> +{<br>
> + Control3gppContext *ctx;<br>
> +<br>
> + //For connecting flows.<br>
> + if (self->priv->connect_pending != NULL)<br>
> + {<br>
<br>
Coding style thing only; the open-braces at the end of the if() line; e.g.<br>
<br>
if (1) {<br>
/* something */<br>
}<br>
<br>
> + ctx = self->priv->connect_pending;<br>
> +<br>
> + switch (ctx->connect) {<br>
> +<br>
> + //Always go from INIT to VERIFY_SWWAN.<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>INIT: {<br>
> + ctx->connect = CONNECT_3GPP_CONTEXT_STEP_<wbr>VERIFY_SWWAN;<br>
> + break;<br>
<br>
This step just wants to get to the next step. So just do ctx->connect++;<br>
and fall down to the next case without a break; E.g.<br>
<br>
case CONNECT_3GPP_CONTEXT_STEP_<wbr>INIT:<br>
ctx->connect++;<br>
/* fall down */<br>
case CONNECT_3GPP_CONTEXT_STEP_<wbr>VERIFY_SWWAN:<br>
....<br>
<br>
<br>
> + }<br>
> +<br>
> + //Always go from VERIFY_SWWAN to BEARER_PROPERTIES.<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>VERIFY_SWWAN: {<br>
<br>
The {} for the case is only really needed if you're going to define<br>
variables within the case clause; I would remove those otherwise.<br>
<br>
> + //Only try 5 times to check the connection then give up.<br>
> + if (self->priv->retry_count > 5)<br>
> + {<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_TOO_MANY,<br>
> + "Unknown bearer state during connection attempt.");<br>
> +<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> + return;<br>
> + }<br>
> + self->priv->retry_count++;<br>
> + ctx->connect = CONNECT_3GPP_CONTEXT_STEP_<wbr>BEARER_PROPERTIES;<br>
> + break;<br>
<br>
Same here for the step and fall-down.<br>
<br>
> + }<br>
> +<br>
> + //When in this state we can be setting up a brand new connection or,<br>
> + //have just torn down a connection to setup a new one.<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>BEARER_PROPERTIES: {<br>
> + //We were connected (flags still set) & just issued SWWAN disconnect.<br>
> + //Now refresh our usb connection state & retry.<br>
> + //TODO: Rework for dual sim connections.<br>
> + if (ctx->usb0_state == CONNECTION_ACTIVE || ctx->usb1_state == CONNECTION_ACTIVE)<br>
> + {<br>
> + ctx->connect = CONNECT_3GPP_CONTEXT_STEP_<wbr>VERIFY_SWWAN;<br>
<br>
Just set the new step to run and call connect_3gpp_context_step (ctx);<br>
directly. E.g.:<br>
<br>
if (...) {<br>
ctx->connect = NEXT_STEP;<br>
connect_3gpp_context_step (ctx);<br>
return;<br>
}<br>
<br>
if (some other thing) {<br>
ctx->connect = SOME_OTHER_NEXT_STEP;<br>
connect_3gpp_context_step (ctx);<br>
return;<br>
}<br>
<br>
...<br>
<br>
> + }<br>
> + //There is no active connection and we set the context correctly so continue to active SWWAN.<br>
> + else if (ctx->usb0_state == CONNECTION_INACTIVE || ctx->usb1_state == CONNECTION_INACTIVE)<br>
> + ctx->connect = CONNECT_3GPP_CONTEXT_STEP_SET_<wbr>SWWAN;<br>
> + else<br>
> + {<br>
> + //Error, should be impossible to get here. State machine broken.<br>
<br>
If this is impossible to get here, just g_assert_not_reached(); don't<br>
return an error to the caller if this is a programmer error.<br>
<br>
> + ctx->self->priv->connect_<wbr>pending = NULL;<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_WRONG_STATE,<br>
> + "Unknown bearer state during connection attempt.");<br>
> +<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> + break;<br>
> + }<br>
> +<br>
> + //Always verify that the SWWAN connection is active after trying to set it up.<br>
> + //Same step as CONNECT_3GPP_CONTEXT_STEP_<wbr>VERIFY_SWWAN but makes it easy to detect<br>
> + //if we just came from a new or old connection.<br>
> + case CONNECT_3GPP_CONTEXT_STEP_SET_<wbr>SWWAN: {<br>
> + ctx->connect = CONNECT_3GPP_CONTEXT_STEP_<wbr>CONNECTION_STATUS;<br>
> + break;<br>
<br>
Again, fall-down without break;.<br>
<br>
> + }<br>
> +<br>
> + //If SWWAN was just set, we expect it should be on.<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>CONNECTION_STATUS: {<br>
> + if ((g_ascii_strncasecmp(ctx-><wbr>self->priv->bearer_interface, "usb0", 4)==0 && ctx->usb0_state == CONNECTION_ACTIVE) ||<br>
> + (g_ascii_strncasecmp(ctx-><wbr>self->priv->bearer_interface, "usb1", 4)==0 && ctx->usb1_state == CONNECTION_ACTIVE))<br>
<br>
No, this is not right, usb0 and usb1 are network interface names that<br>
the kernel decided to use; what if you have 2 Cinterion modems at the<br>
same time?<br>
<br>
Instead of relying on the interface name, you may want to rely on the<br>
USB interface number; e.g.<br>
<br>
g_udev_device_get_property_as_<wbr>int (<br>
udev_device,<br>
"ID_USB_INTERFACE_NUM")<br>
<br>
NOTE: you'll need a GUdevDevice for the WWAN; which you could get using<br>
a new GUdevClient. This will be much easier to handle once the work I'm<br>
doing to allow skipping udev is integrated, as then you can just get a<br>
MMKernelDevice from the MMPort and query the property directly, like:<br>
<br>
mm_kernel_device_get_property_<wbr>as_int (<br>
mm_port_peek_kernel_device (port),<br>
"ID_USB_INTERFACE_NUM");<br>
<br>
See e.g. this similar case in the Huawei plugin:<br>
<a href="https://lists.freedesktop.org/archives/modemmanager-devel/2016-August/003279.html" rel="noreferrer" target="_blank">https://lists.freedesktop.org/<wbr>archives/modemmanager-devel/<wbr>2016-August/003279.html</a><br>
<br>
<br>
> + ctx->connect = CONNECT_3GPP_CONTEXT_STEP_<wbr>DONE;<br>
> + else<br>
> + {<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_WRONG_STATE,<br>
> + "Unknown bearer state during connection attempt.");<br>
> +<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> + return;<br>
> + }<br>
<br>
You could have a single if() to catch the error case, and then do the<br>
ctx->connect++; and /* fall-down */ thing to get to the next case.<br>
<br>
> +<br>
> + break;<br>
> + }<br>
> +<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>DONE: {<br>
> + //Place holder, nothing should call this. Fall into defualt error.<br>
> + }<br>
> +<br>
> + default: {<br>
<br>
I personally don't like a default case in a state machine where the step<br>
is a known enumeration.<br>
<br>
> + mm_err ("Unexpected SWWAN connect state. Unable to advance.");<br>
> + ctx->self->priv->connect_<wbr>pending = NULL;<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_WRONG_STATE,<br>
> + "Unexpected SWWAN connect state. Unable to advance.");<br>
> +<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> +<br>
> + }<br>
> +<br>
> + connect_3gpp_context_step (ctx);<br>
<br>
This connect_3gpp_context_step() wouldn't be needed as each case above<br>
should call it itself when needed and directly return.<br>
<br>
> + }<br>
> + //Assume disconnecting flow, assert protects assumption.<br>
<br>
Separate logic methods for connection and disconnection, please.<br>
<br>
> + else<br>
> + {<br>
> + g_assert (self->priv->disconnect_<wbr>pending != NULL);<br>
> + ctx = self->priv->disconnect_<wbr>pending;<br>
> +<br>
> + switch (ctx->disconnect) {<br>
> +<br>
> + case DISCONNECT_3GPP_CONTEXT_STEP_<wbr>INIT: {<br>
> + ctx->disconnect = DISCONNECT_3GPP_CONTEXT_STEP_<wbr>SWWAN_DETACH;<br>
> + break;<br>
<br>
Same as above, when just going to the next case, ctx->disconnect++; and<br>
/* fall down */<br>
<br>
> + }<br>
> +<br>
> + case DISCONNECT_3GPP_CONTEXT_STEP_<wbr>SWWAN_DETACH: {<br>
> + ctx->disconnect = DISCONNECT_3GPP_CONTEXT_STEP_<wbr>CONNECTION_STATUS;<br>
> + break;<br>
<br>
Another fall-down? Why do we need this step then?<br>
<br>
> + }<br>
> +<br>
> + case DISCONNECT_3GPP_CONTEXT_STEP_<wbr>CONNECTION_STATUS: {<br>
> + //We expect the bearer to be disconnected now. If it's not try again.<br>
> + //TODO: Rework for dual sim connections.<br>
> + if ((g_ascii_strncasecmp(ctx-><wbr>self->priv->bearer_interface, "usb0", 4)==0 && ctx->usb0_state == CONNECTION_INACTIVE) ||<br>
> + (g_ascii_strncasecmp(ctx-><wbr>self->priv->bearer_interface, "usb1", 4)==0 && ctx->usb1_state == CONNECTION_INACTIVE))<br>
<br>
As above, never rely on interface names.<br>
<br>
> + ctx->disconnect = DISCONNECT_3GPP_CONTEXT_STEP_<wbr>DONE;<br>
> + else<br>
> + {<br>
> + ctx->disconnect = DISCONNECT_3GPP_CONTEXT_STEP_<wbr>SWWAN_DETACH;<br>
> + sleep (1); //Wait a second on disconnect retry's.<br>
<br>
Never never ever just sleep() :) If you need to reschedule the same step<br>
in 1s, you should:<br>
<br>
g_timeout_add_seconds (<br>
1,<br>
(GSourceFunc) cinterion_3gpp_state_machine_<wbr>logic_reschedule_cb,<br>
ctx);<br>
return;<br>
<br>
And then implement the callback:<br>
static gboolean<br>
cinterion_3gpp_state_machine_<wbr>logic_cb (Control3gppContext *ctx)<br>
{<br>
cinterion_3gpp_state_machine_<wbr>logic (ctx);<br>
return G_SOURCE_REMOVE;<br>
}<br>
<br>
<br>
> + }<br>
> + break;<br>
> + }<br>
> +<br>
> + case DISCONNECT_3GPP_CONTEXT_STEP_<wbr>DONE: {<br>
> + //Place holder, nothing should call this. Fall into defualt error.<br>
<br>
Nothing should call this, yet there is an explicit step selection for<br>
this one some lines above... :)<br>
ctx->disconnect = DISCONNECT_3GPP_CONTEXT_STEP_<wbr>DONE;<br>
<br>
<br>
> + }<br>
> +<br>
> + default: {<br>
> + mm_err ("Unexpected SWWAN disconnect state. Unable to advance.");<br>
> + ctx->self->priv->connect_<wbr>pending = NULL;<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_WRONG_STATE,<br>
> + "Unexpected SWWAN disconnect state. Unable to advance.");<br>
> +<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> +<br>
> + return;<br>
> + }<br>
> +<br>
> + }<br>
> +<br>
> + disconnect_3gpp_context_step (ctx);<br>
<br>
As for the connect logic, I think it's better to just call the<br>
context_step() method directly after setting the next step, not doing a<br>
break; and a single context_step() here. This is because each step will<br>
be auto-contained within the case(), I think it helps readability of the<br>
state machine.<br>
<br>
> + }<br>
> +}<br>
> +<br>
> +static gboolean<br>
> +proccess_swwan_response(<wbr>MMBroadbandBearerCinterion *self, GList *result)<br>
> +{<br>
> + Control3gppContext *ctx;<br>
> +<br>
> + if (self->priv->connect_pending != NULL)<br>
> + ctx = self->priv->connect_pending;<br>
> + else<br>
> + {<br>
> + g_assert (self->priv->disconnect_<wbr>pending != NULL);<br>
> + ctx = self->priv->disconnect_<wbr>pending;<br>
> + }<br>
> +<br>
<br>
I see this logic mixing connect/disconnect again; I think we shouldn't<br>
do this. If the SWWAN response is parsed in the same way for both,<br>
better write a parser method and let the different connect/disconnect<br>
flows call it.<br>
<br>
> +<br>
> + if (g_list_length(result) != 0) {<br>
> + int first_result = GPOINTER_TO_INT(result->data);<br>
> +<br>
> + //mm_serial_parser_v1_parse will catch CME Error. Parent function will then send<br>
> + //that error to dbus out.<br>
> + if(first_result == -1)<br>
> + return FALSE;<br>
> + //Recived an 'OK' response from a write command, place holder so we don't error below.<br>
<br>
*Received<br>
<br>
> + if (first_result == 0 && ctx->self->priv->swwan_read_<wbr>write == 1)<br>
> + NULL;<br>
> + //Recived an 'OK'(0) response from an swwwan read command.<br>
<br>
*Received, *swwan<br>
<br>
> + else if (first_result == 0 && ctx->self->priv->swwan_read_<wbr>write == 0)<br>
> + {<br>
> + ctx->usb0_state = CONNECTION_INACTIVE;<br>
> + ctx->usb1_state = CONNECTION_INACTIVE;<br>
> + }<br>
> + //1 || 3 result is the CID, given when that context is activated.<br>
> + //TODO: Rework for dual sim connections.<br>
> + else if (first_result == 1 && ctx->self->priv->swwan_read_<wbr>write == 0)<br>
> + ctx->usb1_state = CONNECTION_ACTIVE;<br>
> + else if (first_result == 3 && ctx->self->priv->swwan_read_<wbr>write == 0)<br>
> + ctx->usb0_state = CONNECTION_ACTIVE;<br>
> + else<br>
> + {<br>
> + for (; result; result = g_list_next (result))<br>
> + mm_err ("Unknown SWWAN response data:%i", GPOINTER_TO_INT(result->data))<wbr>;<br>
> +<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_FAILED,<br>
> + "Internal error while processing SWWAN response.");<br>
> +<br>
> + return FALSE;<br>
> + }<br>
> + }<br>
> + else {<br>
> + mm_err ("Unable to parse zero length SWWAN response.");<br>
> + return FALSE;<br>
> + }<br>
> +<br>
> + return TRUE;<br>
> +}<br>
> +<br>
> +static void<br>
> +get_swwan_response (MMBaseModem *modem,<br>
> + GAsyncResult *res,<br>
> + MMBroadbandBearerCinterion *self)<br>
> +{<br>
> + Control3gppContext *ctx;<br>
> + const gchar *response;<br>
> + GError *error = NULL;<br>
> + GList *response_parsed = NULL;<br>
> +<br>
> + if (self->priv->connect_pending != NULL)<br>
> + ctx = self->priv->connect_pending;<br>
> + else<br>
> + {<br>
> + g_assert (self->priv->disconnect_<wbr>pending != NULL);<br>
> + ctx = self->priv->disconnect_<wbr>pending;<br>
> + }<br>
> +<br>
> + // Balance refcount<br>
> + g_object_unref (self);<br>
<br>
Too complex, too complex :). If you had one logic for connect and<br>
another logic for disconnect, you could pass around the actual Context<br>
struct in the async calls user_data, which holds already a reference to<br>
the bearer object. Instead, you're storing the contexts in private info<br>
and passing the bearer object.<br>
<br>
It is good to store the connect and disconnect contexts in the private<br>
info ONLY if there is no other place to manage them. If the logic relies<br>
on queries to the modem to check e.g. SWWAN status, you can pass around<br>
the contexts in the async calls and not ever store them in private info.<br>
One of the good reasons to store the context in private info is when you<br>
rely on unsolicited messages from the modem. For your use case, I think<br>
you could totally avoid storing the contexts in private info, and the<br>
logic would be much much simpler. What do you think?<br>
<br>
> +<br>
> + //Parse the swwan response.<br>
> + response = mm_base_modem_at_command_<wbr>finish (modem, res, &error);<br>
<br>
If !response, just return the error right away.<br>
<br>
> +<br>
> + //1st elem of response_parsed will be:<br>
> + //1 or 3 for an active swwan, -1 for a valid error, 0 for assumed 'OK' (no conection)<br>
> + if (error == NULL && !mm_cinterion_parse_swwan_<wbr>response (response, &response_parsed, &error))<br>
> + NULL;<br>
<br>
Hum?<br>
<br>
> +<br>
> +<br>
> + if (error != NULL || !proccess_swwan_response(ctx-><wbr>self, response_parsed)) {<br>
> + ctx->self->priv->connect_<wbr>pending = NULL;<br>
> + ctx->self->priv->disconnect_<wbr>pending = NULL;<br>
> +<br>
> + g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
<br>
But proccess_swwan_response() doesn't set a GError to return here if it<br>
fails.<br>
<br>
> +<br>
> + if (ctx->connect)<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> + else<br>
> + disconnect_3gpp_context_<wbr>complete_and_free (ctx);<br>
> +<br>
> + return;<br>
> +<br>
> + }<br>
> +<br>
> + mm_dbg ("usb0-state:%i usb1-state:%i", ctx->usb0_state, ctx->usb1_state);<br>
> +<br>
> + g_list_free(response_parsed);<br>
> + g_clear_error (&error);<br>
<br>
You would never need a g_clear_error() here if errors are propagated<br>
earlier whenever they happen.<br>
<br>
> +<br>
> + cinterion_3gpp_state_machine_<wbr>logic(ctx->self);<br>
> +<br>
<br>
Will this run the same state machine step again? I think it is usually<br>
more clear to update the step to run, and then call the state machine<br>
logic, something like:<br>
<br>
ctx->step++;<br>
context_step (ctx);<br>
<br>
<br>
> + return;<br>
<br>
Just remove this return; :)<br>
<br>
> +}<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* Cinterion AT Command Wrappers */<br>
> +<br>
> +static void<br>
> +send_swwan_read_command(<wbr>Control3gppContext *ctx)<br>
> +{<br>
> + ctx->self->priv->swwan_read_<wbr>write = 0;<br>
> +<br>
<br>
I'm sure this swwan_read_write flag could be avoided if different<br>
GAsyncReadyCallback are used for read/write operations :)<br>
<br>
> + //Check for swwan connection. The next state will be preformed by the callback.<br>
<br>
*performed<br>
<br>
> + mm_base_modem_at_command_full (ctx->modem,<br>
> + ctx->primary,<br>
> + "^SWWAN?",<br>
> + 5,<br>
> + FALSE,<br>
> + FALSE,<br>
> + NULL,<br>
> + (GAsyncReadyCallback)get_<wbr>swwan_response,<br>
> + g_object_ref (ctx->self));<br>
> +}<br>
> +<br>
> +static void<br>
> +send_swwan_connect_command(<wbr>Control3gppContext *ctx)<br>
> +{<br>
> + //USB0(1st wwan adapt) -> 3rd context, USB1(2nd wwan adapt) -> 1st context<br>
> + gchar *command;<br>
<br>
Remove wrong alignment whitespaces.<br>
<br>
> + command = g_strdup_printf ("^SWWAN=%s,%s,%s",<br>
> + "1",<br>
> + ctx->self->priv->pdp_cid, //Expect 1 or 3<br>
> + g_ascii_strncasecmp(ctx->self-<wbr>>priv->pdp_cid, "3", 1)==0 ? "1" : "2");<br>
<br>
Could you comment here on why "1" or "2" are given here based on CID number?<br>
<br>
> +<br>
> + ctx->self->priv->swwan_read_<wbr>write = 1;<br>
> +<br>
> + //Start the swwan connection. The next state will be preformed by the callback.<br>
<br>
*performed<br>
<br>
> + mm_base_modem_at_command_full (ctx->modem,<br>
> + ctx->primary,<br>
> + command,<br>
> + 10,/*Seen it take 5 seconds :0 */<br>
> + FALSE,<br>
> + FALSE,<br>
> + NULL,<br>
> + (GAsyncReadyCallback)get_<wbr>swwan_response,<br>
> + g_object_ref (ctx->self));<br>
<br>
As said earlier, passing around the context is much simpler.<br>
<br>
> +<br>
> + g_free (command);<br>
> +}<br>
> +<br>
> +static void<br>
> +send_swwan_disconnect_<wbr>command(Control3gppContext *ctx)<br>
> +{<br>
> + gchar *command;<br>
<br>
Remove wrong alignment whitespaces.<br>
<br>
> + command = g_strdup_printf ("^SWWAN=%s,%s,%s",<br>
> + "0",<br>
> + ctx->self->priv->pdp_cid,<br>
> + g_ascii_strncasecmp(ctx->self-<wbr>>priv->pdp_cid, "3", 1)==0 ? "1" : "2");<br>
> +<br>
> + ctx->self->priv->swwan_read_<wbr>write = 1;<br>
> +<br>
> + //Check for swwan connection. The next state will be preformed by the callback.<br>
> + mm_base_modem_at_command_full (ctx->modem,<br>
> + ctx->primary,<br>
> + command,<br>
> + 10,<br>
> + FALSE,<br>
> + FALSE,<br>
> + NULL,<br>
> + (GAsyncReadyCallback)get_<wbr>swwan_response,<br>
> + g_object_ref (ctx->self));<br>
<br>
As said earlier, passing around the context is much simpler.<br>
<br>
> +<br>
> + g_free (command);<br>
> +}<br>
> +<br>
<br>
Too many whitelines here :) Easy change!<br>
<br>
> +<br>
> +<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* Connect 3GPP */<br>
> +<br>
> +static gint<br>
> +cinterion_parse_auth_type (MMBearerAllowedAuth mm_auth)<br>
> +{<br>
> + switch (mm_auth) {<br>
> + case MM_BEARER_ALLOWED_AUTH_NONE:<br>
> + return MM_BEARER_CINTERION_AUTH_NONE;<br>
> + case MM_BEARER_ALLOWED_AUTH_PAP:<br>
> + return MM_BEARER_CINTERION_AUTH_PAP;<br>
> + case MM_BEARER_ALLOWED_AUTH_CHAP:<br>
> + return MM_BEARER_CINTERION_AUTH_CHAP;<br>
> + case MM_BEARER_ALLOWED_AUTH_<wbr>MSCHAPV2:<br>
> + return MM_BEARER_CINTERION_AUTH_<wbr>MSCHAPV2;<br>
> + default:<br>
> + return MM_BEARER_CINTERION_AUTH_<wbr>UNKNOWN;<br>
> + }<br>
> +}<br>
> +<br>
> +static void<br>
> +connect_3gpp_context_<wbr>complete_and_free (Control3gppContext *ctx)<br>
> +{<br>
> + g_simple_async_result_<wbr>complete_in_idle (ctx->result);<br>
> + g_object_unref (ctx->cancellable);<br>
> + g_object_unref (ctx->result);<br>
> + g_object_unref (ctx->modem);<br>
> + g_object_unref (ctx->self);<br>
> + g_clear_object (&ctx->ipv4_config);<br>
> + g_clear_object (&ctx->data);<br>
> + g_clear_object (&ctx->primary);<br>
> +<br>
> + g_slice_free (Control3gppContext, ctx);<br>
> +}<br>
> +<br>
> +static MMBearerConnectResult *<br>
> +connect_3gpp_finish (MMBroadbandBearer *self,<br>
> + GAsyncResult *res,<br>
> + GError **error)<br>
> +{<br>
> + if (g_simple_async_result_<wbr>propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))<br>
> + return NULL;<br>
> +<br>
> + return mm_bearer_connect_result_ref (g_simple_async_result_get_op_<wbr>res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));<br>
> +}<br>
> +<br>
> +static gchar *<br>
> +build_cinterion_pdp_context_<wbr>string(Control3gppContext *ctx)<br>
> +{<br>
> + const gchar *apn = NULL;<br>
> + const gchar *user = NULL;<br>
> + const gchar *passwd = NULL;<br>
> + MMBearerAllowedAuth auth;<br>
> + gint encoded_auth = MM_BEARER_CINTERION_AUTH_<wbr>UNKNOWN;<br>
> + gchar *command;<br>
> +<br>
> + apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)));<br>
> + user = mm_bearer_properties_get_user (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)));<br>
> + passwd = mm_bearer_properties_get_<wbr>password (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)));<br>
> + auth = mm_bearer_properties_get_<wbr>allowed_auth (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)));<br>
> + encoded_auth = cinterion_parse_auth_type (auth);<br>
> +<br>
> + /* Default to no authentication if not specified */<br>
> + if (encoded_auth == MM_BEARER_CINTERION_AUTH_<wbr>UNKNOWN) {<br>
> + encoded_auth = MM_BEARER_CINTERION_AUTH_NONE;<br>
> + mm_dbg ("Unable to detect authentication type. Defaulting to:%i", encoded_auth);<br>
> + }<br>
> +<br>
> + //TODO: Get IP type if specific protocol was specified. Hardcoded to IPV4 for now.<br>
> +<br>
> + if (!user && !passwd)<br>
> + command = g_strdup_printf ("+CGDCONT=%s,\"IP\",\"%s\",\"<wbr>0.0.0.0\",0,0",<br>
> + ctx->self->priv->pdp_cid,<br>
> + apn == NULL ? "" : apn);<br>
> +<br>
> + /*TODO: Can't test this as we can't get a hold of a SIM w/ this feature atm. This is a place<br>
> + * holder. When we can test this then it should be refactored & split into another state so we<br>
> + * can tell independently if the SGAUTH fails.<br>
> + * Write Command<br>
> + * AT^SGAUTH=<cid>[, <auth_type>[, <passwd>, <user>]]<br>
> + * Response(s) <br>
> + * OK<br>
> + * ERROR<br>
> + * +CME ERROR: <err><br>
> + */<br>
> + else<br>
> + command = g_strdup_printf ("^SGAUTH=%s,%i,%s,%s; +CGDCONT=%s,\"IP\",\"%s\",\"0.<wbr>0.0.0\",0,0 ",<br>
> + ctx->self->priv->pdp_cid,<br>
> + encoded_auth,<br>
> + passwd == NULL ? "" : passwd,<br>
> + user == NULL ? "" : user,<br>
> + ctx->self->priv->pdp_cid,<br>
> + apn == NULL ? "" : apn);<br>
<br>
I wouldn't chain up these 2 AT commands.<br>
<br>
> + return command;<br>
> +}<br>
> +<br>
> +static gboolean<br>
> +set_pdp_cid(<wbr>Control3gppContext *ctx)<br>
> +{<br>
> + GUdevClient *client;<br>
> + GUdevDevice *data_device;<br>
> +<br>
> + //Need the data port to figure out what context to activate.<br>
> + client = g_udev_client_new (NULL);<br>
> + data_device = (g_udev_client_query_by_<wbr>subsystem_and_name (<br>
> + client,<br>
> + "net",<br>
> + mm_port_get_device (ctx->data)));<br>
> + ctx->self->priv->bearer_<wbr>interface = g_udev_device_get_name(data_<wbr>device);<br>
> +<br>
> + //Map PDP context from the current Bearer. USB0 -> 3rd context, USB1 -> 1st context<br>
> + if (g_ascii_strncasecmp(ctx-><wbr>self->priv->bearer_interface, "usb0", 4) == 0)<br>
> + ctx->self->priv-> pdp_cid = "3";<br>
> + else if (g_ascii_strncasecmp(ctx-><wbr>self->priv->bearer_interface, "usb1", 4) == 0)<br>
> + ctx->self->priv->pdp_cid = "1";<br>
> + else<br>
> + {<br>
> + mm_err ("Unable to map usb interface:(%s) to context", ctx->self->priv->bearer_<wbr>interface);<br>
> +<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_FAILED,<br>
> + "Internal error while mapping USB network interface.");<br>
> +<br>
> + ctx->self->priv->connect_<wbr>pending = NULL;<br>
> + ctx->self->priv->disconnect_<wbr>pending = NULL;<br>
> +<br>
> + if (ctx->connect)<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> + else<br>
> + disconnect_3gpp_context_<wbr>complete_and_free (ctx);<br>
> +<br>
> + return FALSE;<br>
> + }<br>
> +<br>
> + return TRUE;<br>
> +}<br>
> +<br>
> +static void<br>
> +handle_cancel_connect(<wbr>Control3gppContext *ctx)<br>
> +{<br>
> + gchar *command;<br>
> +<br>
> + // Clear context<br>
> + ctx->self->priv->connect_<wbr>pending = NULL;<br>
> +<br>
> + command = g_strdup_printf ("^SWWAN=%s,%s,%s",<br>
> + "0",<br>
> + ctx->self->priv->pdp_cid, //Expect 1 or 3,<br>
> + g_ascii_strncasecmp(ctx->self-<wbr>>priv->pdp_cid, "3", 1)==0 ? "1" : "2");<br>
> +<br>
> + //Disconnect, may not succeed. Will not check response on cancel.<br>
> + mm_base_modem_at_command_full (ctx->modem,<br>
> + ctx->primary,<br>
> + command,<br>
> + 3,<br>
> + FALSE,<br>
> + FALSE,<br>
> + NULL,<br>
> + NULL, // Do not care the AT response<br>
> + NULL);<br>
> +<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_CANCELLED,<br>
> + "Cinterion connection operation has been cancelled");<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> +<br>
> +}<br>
> +<br>
> +static void<br>
> +context_ready (MMBaseModem *modem,<br>
> + GAsyncResult *res,<br>
> + MMBroadbandBearerCinterion *self)<br>
> +{<br>
> + Control3gppContext *ctx;<br>
> + GError *error = NULL;<br>
> + const gchar *response;<br>
> +<br>
> + ctx = self->priv->connect_pending;<br>
> + g_assert (ctx != NULL);<br>
> +<br>
> + /* Balance refcount */<br>
> + g_object_unref (self);<br>
> +<br>
> + //Get the cgdcont write response. We expect: 'OK'<br>
> + //Responses: OK, ERROR<br>
> + response = mm_base_modem_at_command_<wbr>finish (modem, res, &error);<br>
> +<br>
> + if (!response || error != NULL) {<br>
> + mm_err ("CGDCONT read- Error:%d response:%s", error->code, response);<br>
> +<br>
> + self->priv->connect_pending = NULL;<br>
> + g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> +<br>
> + return;<br>
> + }<br>
> +<br>
> + /* Go to next step */<br>
> + //ctx->connect = CONNECT_3GPP_CONTEXT_STEP_SET_<wbr>SWWAN;<br>
> + cinterion_3gpp_state_machine_<wbr>logic(ctx->self);<br>
> +}<br>
> +<br>
> +static void<br>
> +connect_3gpp_context_step (Control3gppContext *ctx)<br>
> +{<br>
> + /* Check for cancellation */<br>
> + if (g_cancellable_is_cancelled (ctx->cancellable)) {<br>
> + //Init the current context if not done already.<br>
> + if (g_ascii_strncasecmp(ctx-><wbr>self->priv->pdp_cid, "0", 1)==0)<br>
> + if(set_pdp_cid(ctx)) //Don't care about failure case for cancel.<br>
> +<br>
> + handle_cancel_connect(ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> + // Network-initiated disconnect should not be outstanding at this point,<br>
> + // because it interferes with the connect attempt.<br>
> + g_assert (ctx->self->priv->network_<wbr>disconnect_pending_id == 0);<br>
> + g_assert (ctx->self->priv->disconnect_<wbr>pending == NULL);<br>
> +<br>
> +<br>
> + switch (ctx->connect) {<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>INIT: {<br>
> + MMBearerIpFamily ip_family;<br>
> +<br>
> + //Initialize variables, reminder!<br>
> + ctx->usb0_state = CONNECTION_UNKNOWN;<br>
> + ctx->usb1_state = CONNECTION_UNKNOWN;<br>
<br>
You could do this just after allocating the context.<br>
<br>
> + ctx->self->priv->pdp_cid = "0"; //Cinterion doesn't have cid == 0<br>
<br>
I think I said it earlier, please make pdp_cid a uint.<br>
<br>
> + ctx->self->priv->swwan_read_<wbr>write = -1;<br>
> + ctx->self->priv->retry_count = 0;<br>
> +<br>
<br>
And you shouldn't really need those 2 things above in private info...<br>
<br>
> + if(!set_pdp_cid(ctx))<br>
> + return;<br>
> +<br>
> + ip_family = mm_bearer_properties_get_ip_<wbr>type (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)));<br>
> +<br>
> + //TODO: Fix so more than IPV4 can be used.<br>
> + if (ip_family == MM_BEARER_IP_FAMILY_NONE ||<br>
> + ip_family == MM_BEARER_IP_FAMILY_ANY) {<br>
> + gchar *ip_family_str;<br>
> +<br>
> + ip_family = mm_base_bearer_get_default_ip_<wbr>family (MM_BASE_BEARER (ctx->self));<br>
> + ip_family_str = mm_bearer_ip_family_build_<wbr>string_from_mask (ip_family);<br>
> + mm_dbg ("No specific IP family requested, defaulting to %s",<br>
> + ip_family_str);<br>
> + g_free (ip_family_str);<br>
> + }<br>
> +<br>
> + /* Default to automatic/DHCP addressing */<br>
<br>
Oh, weird, you're creating the MMBearerIpConfig before even knowing that<br>
the modem is connected... ok...<br>
<br>
> + ctx->ipv4_config = mm_bearer_ip_config_new ();<br>
> + mm_bearer_ip_config_set_method (ctx->ipv4_config, MM_BEARER_IP_METHOD_DHCP);<br>
> +<br>
> + /* Store the context */<br>
> + ctx->self->priv->connect_<wbr>pending = ctx;<br>
> +<br>
> + cinterion_3gpp_state_machine_<wbr>logic(ctx->self);<br>
> + return;<br>
> + }<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>VERIFY_SWWAN: {<br>
> + send_swwan_read_command(ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>BEARER_PROPERTIES: {<br>
> + gchar *command = NULL;<br>
> +<br>
> + //TODO: Rework for dual sim connections, don't disconnect the wrong one.<br>
> + //If there is already an active SWWAN connection disconnect before trying to set it's context.<br>
> + if ((ctx->usb0_state == CONNECTION_ACTIVE && g_ascii_strncasecmp(ctx->self-<wbr>>priv->pdp_cid, "3", 1)) || ctx->usb0_state == CONNECTION_UNKNOWN ||<br>
> + (ctx->usb1_state == CONNECTION_ACTIVE && g_ascii_strncasecmp(ctx->self-<wbr>>priv->pdp_cid, "1", 1)) || ctx->usb1_state == CONNECTION_UNKNOWN)<br>
> + {<br>
<br>
If "3" and "1" CIDs are special, we should have some enumeration or<br>
something flagging them with a name, now that I think of it.<br>
<br>
> + send_swwan_disconnect_command(<wbr>ctx);<br>
> + g_free (command);<br>
> + return;<br>
> + }<br>
> +<br>
> + command = build_cinterion_pdp_context_<wbr>string(ctx);<br>
> +<br>
> + //Set the PDP context with cgdcont.<br>
<br>
Isn't there already a generic way in MMBroadbandBearer doing this?<br>
<br>
> + mm_base_modem_at_command_full (ctx->modem,<br>
> + ctx->primary,<br>
> + command,<br>
> + 3,<br>
> + FALSE,<br>
> + FALSE,<br>
> + NULL,<br>
> + (GAsyncReadyCallback)context_<wbr>ready,<br>
> + g_object_ref (ctx->self));<br>
> +<br>
> + g_free (command);<br>
> + return;<br>
> + }<br>
> + case CONNECT_3GPP_CONTEXT_STEP_SET_<wbr>SWWAN: {<br>
> + send_swwan_connect_command(<wbr>ctx);<br>
> + return;<br>
> + }<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>CONNECTION_STATUS: {<br>
> + send_swwan_read_command(ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> + case CONNECT_3GPP_CONTEXT_STEP_<wbr>DONE: {<br>
> + /* Clear context */<br>
> + ctx->self->priv->connect_<wbr>pending = NULL;<br>
> + ctx->self->priv->bearer_<wbr>interface = NULL;<br>
> + ctx->self->priv->pdp_cid = NULL;<br>
> + ctx->self->priv->swwan_read_<wbr>write = -1;<br>
> +<br>
> + /* Setup result */<br>
> + {<br>
> + if (ctx->ipv4_config) {<br>
> + g_simple_async_result_set_op_<wbr>res_gpointer (<br>
> + ctx->result,<br>
> + mm_bearer_connect_result_new (ctx->data, ctx->ipv4_config, NULL),<br>
> + (GDestroyNotify)mm_bearer_<wbr>connect_result_unref);<br>
> + }<br>
> + else {<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_WRONG_STATE,<br>
> + "Cinterion connection failed to set IP protocol");<br>
> + }<br>
> + }<br>
> +<br>
> + connect_3gpp_context_complete_<wbr>and_free (ctx);<br>
> + return;<br>
> + }<br>
> + }<br>
> +}<br>
> +<br>
> +static void<br>
> +connect_3gpp (MMBroadbandBearer *self,<br>
> + MMBroadbandModem *modem,<br>
> + MMPortSerialAt *primary,<br>
> + MMPortSerialAt *secondary,<br>
> + GCancellable *cancellable,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data)<br>
> +{<br>
> + Control3gppContext *ctx;<br>
> + MMPort *port;<br>
> +<br>
> + g_assert (primary != NULL);<br>
> +<br>
> + // We need a net port<br>
> + port = mm_base_modem_peek_best_data_<wbr>port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);<br>
> + if (!port) {<br>
> + g_simple_async_report_error_<wbr>in_idle (G_OBJECT (self),<br>
> + callback,<br>
> + user_data,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_NOT_FOUND,<br>
> + "No valid data port found to launch connection");<br>
> + return;<br>
> + }<br>
> +<br>
> +<br>
> + /* Setup connection context */<br>
> + ctx = g_slice_new0 (Control3gppContext);<br>
> + ctx->self = g_object_ref (self);<br>
> + ctx->modem = g_object_ref (modem);<br>
> + ctx->data = g_object_ref (port);<br>
> + ctx->result = g_simple_async_result_new (G_OBJECT (self),<br>
> + callback,<br>
> + user_data,<br>
> + connect_3gpp);<br>
> + ctx->cancellable = g_object_ref (cancellable);<br>
> + ctx->connect = CONNECT_3GPP_CONTEXT_STEP_<wbr>INIT;<br>
> +<br>
> + g_assert (ctx->self->priv->connect_<wbr>pending == NULL);<br>
> + g_assert (ctx->self->priv->disconnect_<wbr>pending == NULL);<br>
> +<br>
> + ctx->primary = get_dial_port (MM_BROADBAND_MODEM_CINTERION (ctx->modem), ctx->data, primary);<br>
<br>
What if primary is connected? Shouldn't we be checking that?<br>
<br>
> +<br>
> + /* Run! */<br>
> + connect_3gpp_context_step (ctx);<br>
> +}<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* Disconnect 3GPP */<br>
> +<br>
> +static void<br>
> +disconnect_3gpp_context_<wbr>complete_and_free (Control3gppContext *ctx)<br>
> +{<br>
> + g_simple_async_result_<wbr>complete_in_idle (ctx->result);<br>
> + g_object_unref (ctx->result);<br>
> + g_object_unref (ctx->primary);<br>
> + g_object_unref (ctx->self);<br>
> + g_object_unref (ctx->modem);<br>
> + g_slice_free (Control3gppContext, ctx);<br>
> +}<br>
<br>
Yeah, nasty nasty to have 2 different complete_and_free() for the same<br>
Context struct based on different logics; just use 2 different contexts :)<br>
<br>
> +<br>
> +static gboolean<br>
> +disconnect_3gpp_finish (MMBroadbandBearer *self,<br>
> + GAsyncResult *res,<br>
> + GError **error)<br>
> +{<br>
> + return !g_simple_async_result_<wbr>propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);<br>
> +}<br>
> +<br>
> +static void<br>
> +disconnect_3gpp_context_step (Control3gppContext *ctx)<br>
> +{<br>
> + //There is no cancel handling b/c the only thing we could do would<br>
> + //be disconnect, which is already happening and bad things can happen<br>
> + //if we abandon the modem in an unknown state.<br>
> +<br>
> + //Don't allow disconnect while connect in progress.<br>
> + g_assert (ctx->self->priv->connect_<wbr>pending == NULL);<br>
> +<br>
> + switch (ctx->disconnect) {<br>
> + case DISCONNECT_3GPP_CONTEXT_STEP_<wbr>INIT:<br>
> + /* Store the context */<br>
> + ctx->self->priv->disconnect_<wbr>pending = ctx;<br>
> +<br>
> + //Initialize variables, reminder!<br>
> + ctx->self->priv->pdp_cid = "0";<br>
> + ctx->self->priv->retry_count = 0;<br>
> + ctx->self->priv->swwan_read_<wbr>write = -1;<br>
> +<br>
> + // We ignore any pending network-initiated disconnection in order to prevent it<br>
> + // from interfering with the client-initiated disconnection, as we would like to<br>
> + // proceed with the latter anyway.<br>
> + if (ctx->self->priv->network_<wbr>disconnect_pending_id != 0) {<br>
> + g_source_remove (ctx->self->priv->network_<wbr>disconnect_pending_id);<br>
> + ctx->self->priv->network_<wbr>disconnect_pending_id = 0;<br>
> + }<br>
> +<br>
> + if(!set_pdp_cid(ctx))<br>
> + return;<br>
> +<br>
> + cinterion_3gpp_state_machine_<wbr>logic(ctx->self);<br>
> + return;<br>
> +<br>
> + case DISCONNECT_3GPP_CONTEXT_STEP_<wbr>SWWAN_DETACH:<br>
> + // If too many retries (1s of wait between the retries), failed<br>
> + if ( ctx->self->priv->retry_count > 5) {<br>
> + // Clear context<br>
> + ctx->self->priv->disconnect_<wbr>pending = NULL;<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_TOO_MANY,<br>
> + "Disconnection attempt timed out");<br>
> +<br>
> + disconnect_3gpp_context_<wbr>complete_and_free (ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> + //Has call back to next state.<br>
> + send_swwan_disconnect_command(<wbr>ctx);<br>
> +<br>
> + //Only try to detach so many times.<br>
> + ctx->self->priv->retry_count++<wbr>;<br>
> +<br>
> + return;<br>
> + case DISCONNECT_3GPP_CONTEXT_STEP_<wbr>CONNECTION_STATUS:<br>
> + //Has call back to next state.<br>
> + send_swwan_read_command(ctx);<br>
> + return;<br>
> +<br>
> + case DISCONNECT_3GPP_CONTEXT_STEP_<wbr>DONE:<br>
> + // Clear context<br>
> + ctx->self->priv->disconnect_<wbr>pending = NULL;<br>
> + ctx->self->priv->pdp_cid = NULL;<br>
> + ctx->self->priv->swwan_read_<wbr>write = -1;<br>
> + // Set data port as result<br>
> + g_simple_async_result_set_op_<wbr>res_gboolean (ctx->result, TRUE);<br>
> + disconnect_3gpp_context_<wbr>complete_and_free (ctx);<br>
> +<br>
> + return;<br>
> + }<br>
> +}<br>
> +<br>
> +static void<br>
> +disconnect_3gpp (MMBroadbandBearer *self,<br>
> + MMBroadbandModem *modem,<br>
> + MMPortSerialAt *primary,<br>
> + MMPortSerialAt *secondary,<br>
> + MMPort *data,<br>
> + guint cid,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data)<br>
> +{<br>
> + Control3gppContext *ctx;<br>
> + MMPort *port;<br>
> +<br>
> + g_assert (primary != NULL);<br>
> +<br>
> + ctx = g_slice_new0 (Control3gppContext);<br>
> + ctx->self = g_object_ref (self);<br>
> + ctx->modem = MM_BASE_MODEM (g_object_ref (modem));<br>
> + ctx->result = g_simple_async_result_new (G_OBJECT (self),<br>
> + callback,<br>
> + user_data,<br>
> + disconnect_3gpp);<br>
> + ctx->disconnect = DISCONNECT_3GPP_CONTEXT_STEP_<wbr>INIT;<br>
> +<br>
> + g_assert (ctx->self->priv->connect_<wbr>pending == NULL);<br>
> + g_assert (ctx->self->priv->disconnect_<wbr>pending == NULL);<br>
> +<br>
> + //TODO: Not sure how else to get active data port? Can this be done without adding this<br>
> + //function to mm-base-modem.c?<br>
> + //TODO: Dual SIM how do we know which interface to grab/disconnect if two are active?<br>
> + port = mm_base_modem_peek_current_<wbr>data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);<br>
> + if (!port) {<br>
> + g_simple_async_report_error_<wbr>in_idle (G_OBJECT (self),<br>
> + callback,<br>
> + user_data,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_NOT_FOUND,<br>
> + "No valid data port found to tear down.");<br>
> + return;<br>
> + }<br>
> +<br>
> + ctx->data = g_object_ref (port);<br>
> +<br>
> + ctx->primary = get_dial_port (MM_BROADBAND_MODEM_CINTERION (ctx->modem), data, primary);<br>
> +<br>
> + /* Start! */<br>
> + disconnect_3gpp_context_step (ctx);<br>
> +}<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +<br>
> +static gboolean<br>
> +network_disconnect_3gpp_<wbr>delayed (MMBroadbandBearerCinterion *self)<br>
> +{<br>
> + mm_dbg ("Disconnect bearer '%s' on network request.",<br>
> + mm_base_bearer_get_path (MM_BASE_BEARER (self)));<br>
> +<br>
> + self->priv->network_<wbr>disconnect_pending_id = 0;<br>
> + mm_base_bearer_report_<wbr>connection_status (MM_BASE_BEARER (self),<br>
> + MM_BEARER_CONNECTION_STATUS_<wbr>DISCONNECTED);<br>
> + return G_SOURCE_REMOVE;<br>
> +}<br>
> +<br>
> +//TODO: Test this.<br>
> +static void<br>
> +report_connection_status (MMBaseBearer *bearer,<br>
> + MMBearerConnectionStatus status)<br>
> +{<br>
<br>
report_connection_status() will be called by the MODEM object when a<br>
specific unsolicited message is detected in the TTY port. If you don't<br>
setup an unsolicited message handler to catch disconnections, you don't<br>
need this.<br>
<br>
> + MMBroadbandBearerCinterion *self = MM_BROADBAND_BEARER_CINTERION (bearer);<br>
> +<br>
> + g_assert (status == MM_BEARER_CONNECTION_STATUS_<wbr>CONNECTED ||<br>
> + status == MM_BEARER_CONNECTION_STATUS_<wbr>DISCONNECTING ||<br>
> + status == MM_BEARER_CONNECTION_STATUS_<wbr>DISCONNECTED);<br>
> +<br>
> + /* When a pending connection / disconnection attempt is in progress, we use<br>
> + * ^SWWAN? to check the connection status and thus temporarily ignore<br>
> + * unsolicited messages */<br>
> + if (self->priv->connect_pending || self->priv->disconnect_<wbr>pending)<br>
> + return;<br>
> +<br>
> + /* Ignore 'CONNECTED' */<br>
> + if (status == MM_BEARER_CONNECTION_STATUS_<wbr>CONNECTED)<br>
> + return;<br>
> +<br>
> + /* We already use ^SWWAN? to poll the connection status, so only<br>
> + * handle network-initiated disconnection here. */<br>
> + if (status == MM_BEARER_CONNECTION_STATUS_<wbr>DISCONNECTING) {<br>
> + /* MM_BEARER_CONNECTION_STATUS_<wbr>DISCONNECTING is used to indicate that the<br>
> + * reporting of disconnection should be delayed. See MMBroadbandModemCinterion's<br>
> + * bearer_report_connection_<wbr>status for details. */<br>
> + if (mm_base_bearer_get_status (bearer) == MM_BEARER_STATUS_CONNECTED &&<br>
> + self->priv->network_<wbr>disconnect_pending_id == 0) {<br>
> + mm_dbg ("Delay network-initiated disconnection of bearer '%s'",<br>
> + mm_base_bearer_get_path (MM_BASE_BEARER (self)));<br>
> + self->priv->network_<wbr>disconnect_pending_id = (g_timeout_add_seconds (<br>
> + 4,<br>
> + (GSourceFunc) network_disconnect_3gpp_<wbr>delayed,<br>
> + self));<br>
> + }<br>
> + return;<br>
> + }<br>
> +<br>
> + /* Report disconnected right away */<br>
> + MM_BASE_BEARER_CLASS (mm_broadband_bearer_<wbr>cinterion_parent_class)-><wbr>report_connection_status (<br>
> + bearer,<br>
> + MM_BEARER_CONNECTION_STATUS_<wbr>DISCONNECTED);<br>
> +}<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +<br>
> +MMBaseBearer *<br>
> +mm_broadband_bearer_<wbr>cinterion_new_finish (GAsyncResult *res,<br>
> + GError **error)<br>
> +{<br>
> + GObject *bearer;<br>
> + GObject *source;<br>
> +<br>
> + source = g_async_result_get_source_<wbr>object (res);<br>
> + bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);<br>
> + g_object_unref (source);<br>
> +<br>
> + if (!bearer)<br>
> + return NULL;<br>
> +<br>
> + /* Only export valid bearers */<br>
> + mm_base_bearer_export (MM_BASE_BEARER (bearer));<br>
> +<br>
> + return MM_BASE_BEARER (bearer);<br>
> +}<br>
> +<br>
> +static void<br>
> +dispose (GObject *object)<br>
> +{<br>
> + MMBroadbandBearerCinterion *self = MM_BROADBAND_BEARER_CINTERION (object);<br>
> +<br>
> + if (self->priv->network_<wbr>disconnect_pending_id != 0) {<br>
> + g_source_remove (self->priv->network_<wbr>disconnect_pending_id);<br>
> + self->priv->network_<wbr>disconnect_pending_id = 0;<br>
> + }<br>
> +<br>
> + G_OBJECT_CLASS (mm_broadband_bearer_<wbr>cinterion_parent_class)-><wbr>dispose (object);<br>
> +}<br>
> +<br>
> +void<br>
> +mm_broadband_bearer_<wbr>cinterion_new (MMBroadbandModemCinterion *modem,<br>
> + MMBearerProperties *config,<br>
> + GCancellable *cancellable,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data)<br>
> +{<br>
> + g_async_initable_new_async (<br>
> + MM_TYPE_BROADBAND_BEARER_<wbr>CINTERION,<br>
> + G_PRIORITY_DEFAULT,<br>
> + cancellable,<br>
> + callback,<br>
> + user_data,<br>
> + MM_BASE_BEARER_MODEM, modem,<br>
> + MM_BASE_BEARER_CONFIG, config,<br>
> + NULL);<br>
> +}<br>
> +<br>
> +static void<br>
> +mm_broadband_bearer_<wbr>cinterion_init (MMBroadbandBearerCinterion *self)<br>
> +{<br>
> + // Initialize private data<br>
> + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,<br>
> + MM_TYPE_BROADBAND_BEARER_<wbr>CINTERION,<br>
> + MMBroadbandBearerCinterionPriv<wbr>ate);<br>
> +}<br>
> +<br>
> +static void<br>
> +mm_broadband_bearer_<wbr>cinterion_class_init (<wbr>MMBroadbandBearerCinterionClas<wbr>s *klass)<br>
> +{<br>
> + GObjectClass *object_class = G_OBJECT_CLASS (klass);<br>
> + MMBaseBearerClass *base_bearer_class = MM_BASE_BEARER_CLASS (klass);<br>
> + MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);<br>
> +<br>
> + g_type_class_add_private (object_class, sizeof (<wbr>MMBroadbandBearerCinterionPriv<wbr>ate));<br>
> +<br>
> + object_class->dispose = dispose;<br>
> + base_bearer_class->report_<wbr>connection_status = report_connection_status; //TODO:What triggers this?<br>
> +<br>
> + broadband_bearer_class-><wbr>connect_3gpp = connect_3gpp;<br>
> + broadband_bearer_class-><wbr>connect_3gpp_finish = connect_3gpp_finish;<br>
> + broadband_bearer_class-><wbr>disconnect_3gpp = disconnect_3gpp;<br>
> + broadband_bearer_class-><wbr>disconnect_3gpp_finish = disconnect_3gpp_finish;<br>
> +}<br>
> diff --git a/plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.h b/plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.h<br>
> new file mode 100644<br>
> index 0000000..8f315db<br>
> --- /dev/null<br>
> +++ b/plugins/cinterion/mm-<wbr>broadband-bearer-cinterion.h<br>
> @@ -0,0 +1,56 @@<br>
> +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */<br>
> +/*<br>
> + * This program is free software; you can redistribute it and/or modify<br>
> + * it under the terms of the GNU General Public License as published by<br>
> + * the Free Software Foundation; either version 2 of the License, or<br>
> + * (at your option) any later version.<br>
> + *<br>
> + * This program is distributed in the hope that it will be useful,<br>
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>
> + * GNU General Public License for more details:<br>
> + *<br>
> + * Copyright (C) 2016 Trimble Navigation Limited<br>
> + * Author: Matthew Stanger <<a href="mailto:Matthew_Stanger@trimble.com">Matthew_Stanger@trimble.com</a>><br>
> + */<br>
> +<br>
> +#ifndef MM_BROADBAND_BEARER_CINTERION_<wbr>H<br>
> +#define MM_BROADBAND_BEARER_CINTERION_<wbr>H<br>
> +<br>
> +#include <glib.h><br>
> +#include <glib-object.h><br>
> +<br>
> +#include "mm-broadband-bearer.h"<br>
> +#include "mm-broadband-modem-cinterion.<wbr>h"<br>
> +<br>
> +#define MM_TYPE_BROADBAND_BEARER_<wbr>CINTERION (mm_broadband_bearer_<wbr>cinterion_get_type ())<br>
> +#define MM_BROADBAND_BEARER_CINTERION(<wbr>obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_<wbr>CINTERION, MMBroadbandBearerCinterion))<br>
> +#define MM_BROADBAND_BEARER_CINTERION_<wbr>CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BROADBAND_BEARER_<wbr>CINTERION, MMBroadbandBearerCinterionClas<wbr>s))<br>
> +#define MM_IS_BROADBAND_BEARER_<wbr>CINTERION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_<wbr>CINTERION))<br>
> +#define MM_IS_BROADBAND_BEARER_<wbr>CINTERION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BROADBAND_BEARER_<wbr>CINTERION))<br>
> +#define MM_BROADBAND_BEARER_CINTERION_<wbr>GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BROADBAND_BEARER_<wbr>CINTERION, MMBroadbandBearerCinterionClas<wbr>s))<br>
> +<br>
> +typedef struct _MMBroadbandBearerCinterion MMBroadbandBearerCinterion;<br>
> +typedef struct _<wbr>MMBroadbandBearerCinterionClas<wbr>s MMBroadbandBearerCinterionClas<wbr>s;<br>
> +typedef struct _<wbr>MMBroadbandBearerCinterionPriv<wbr>ate MMBroadbandBearerCinterionPriv<wbr>ate;<br>
> +<br>
> +struct _MMBroadbandBearerCinterion {<br>
> + MMBroadbandBearer parent;<br>
> + MMBroadbandBearerCinterionPriv<wbr>ate *priv;<br>
> +};<br>
> +<br>
> +struct _<wbr>MMBroadbandBearerCinterionClas<wbr>s {<br>
> + MMBroadbandBearerClass parent;<br>
> +};<br>
> +<br>
> +GType mm_broadband_bearer_cinterion_<wbr>get_type (void);<br>
> +<br>
> +void mm_broadband_bearer_cinterion_<wbr>new (MMBroadbandModemCinterion *modem,<br>
> + MMBearerProperties *config,<br>
> + GCancellable *cancellable,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data);<br>
> +MMBaseBearer *mm_broadband_bearer_<wbr>cinterion_new_finish (GAsyncResult *res,<br>
> + GError **error);<br>
> +<br>
> +#endif /* MM_BROADBAND_BEARER_CINTERION_<wbr>H */<br>
> diff --git a/plugins/cinterion/mm-<wbr>broadband-modem-cinterion.c b/plugins/cinterion/mm-<wbr>broadband-modem-cinterion.c<br>
> index 4882a41..a7b8afc 100644<br>
> --- a/plugins/cinterion/mm-<wbr>broadband-modem-cinterion.c<br>
> +++ b/plugins/cinterion/mm-<wbr>broadband-modem-cinterion.c<br>
> @@ -12,7 +12,9 @@<br>
> *<br>
> * Copyright (C) 2011 Ammonit Measurement GmbH<br>
> * Copyright (C) 2011 Google Inc.<br>
> + * Copyright (C) 2016 Trimble Navigation Limited<br>
> * Author: Aleksander Morgado <<a href="mailto:aleksander@lanedo.com">aleksander@lanedo.com</a>><br>
> + * Contributor: Matthew Stanger <<a href="mailto:matthew_stanger@trimble.com">matthew_stanger@trimble.com</a>><br>
> */<br>
><br>
> #include <config.h><br>
> @@ -22,6 +24,8 @@<br>
> #include <string.h><br>
> #include <unistd.h><br>
> #include <ctype.h><br>
> +#include <gudev/gudev.h><br>
> +<br>
><br>
> #include "ModemManager.h"<br>
> #include "mm-modem-helpers.h"<br>
> @@ -36,6 +40,7 @@<br>
> #include "mm-broadband-modem-cinterion.<wbr>h"<br>
> #include "mm-modem-helpers-cinterion.h"<br>
> #include "mm-common-cinterion.h"<br>
> +#include "mm-broadband-bearer-<wbr>cinterion.h"<br>
><br>
> static void iface_modem_init (MMIfaceModem *iface);<br>
> static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);<br>
> @@ -50,6 +55,12 @@ G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion,<br>
> G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_<wbr>MESSAGING, iface_modem_messaging_init)<br>
> G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init))<br>
><br>
> +typedef enum {<br>
> + FEATURE_SUPPORT_UNKNOWN,<br>
> + FEATURE_NOT_SUPPORTED,<br>
> + FEATURE_SUPPORTED<br>
> +} FeatureSupport;<br>
> +<br>
> struct _<wbr>MMBroadbandModemCinterionPriva<wbr>te {<br>
> /* Flag to know if we should try AT^SIND or not to get psinfo */<br>
> gboolean sind_psinfo;<br>
> @@ -69,6 +80,9 @@ struct _<wbr>MMBroadbandModemCinterionPriva<wbr>te {<br>
> GArray *cnmi_supported_bm;<br>
> GArray *cnmi_supported_ds;<br>
> GArray *cnmi_supported_bfr;<br>
> +<br>
> + FeatureSupport swwan_support;<br>
> +<br>
> };<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> @@ -117,7 +131,7 @@ cnmi_test_ready (MMBaseModem *self,<br>
> {<br>
> GError *error = NULL;<br>
><br>
> - mm_base_modem_at_command_<wbr>finish (MM_BASE_MODEM (self), res, &error);<br>
> + mm_base_modem_at_command_<wbr>finish (MM_BASE_MODEM (self), res, &error);<br>
> if (error)<br>
> g_simple_async_result_take_<wbr>error (simple, error);<br>
> else<br>
> @@ -727,10 +741,16 @@ get_access_technology_from_<wbr>psinfo (const gchar *psinfo,<br>
> case 9:<br>
> case 10:<br>
> return (MM_MODEM_ACCESS_TECHNOLOGY_<wbr>HSDPA | MM_MODEM_ACCESS_TECHNOLOGY_<wbr>HSUPA);<br>
> + case 16:<br>
> + case 17:<br>
> + return MM_MODEM_ACCESS_TECHNOLOGY_<wbr>LTE;<br>
> default:<br>
> + mm_dbg ("Unable to identify access technology in case:%i", psinfoval);<br>
> break;<br>
> }<br>
> }<br>
> + else<br>
> + mm_err ("FAILED get_access_technology_from_<wbr>psinfo-int");<br>
><br>
> g_set_error (error,<br>
> MM_CORE_ERROR,<br>
> @@ -1648,6 +1668,259 @@ setup_ports (MMBroadbandModem *self)<br>
> }<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> +/* Create Bearer (Modem interface) */<br>
> +<br>
> +typedef struct {<br>
> + MMBroadbandModemCinterion *self;<br>
> + GSimpleAsyncResult *result;<br>
> + MMBearerProperties *properties;<br>
> +} CreateBearerContext;<br>
> +<br>
> +static void<br>
> +create_bearer_context_<wbr>complete_and_free (CreateBearerContext *ctx)<br>
> +{<br>
> + g_simple_async_result_complete (ctx->result);<br>
> + g_object_unref (ctx->result);<br>
> + g_object_unref (ctx->self);<br>
> + g_object_unref (ctx->properties);<br>
> + g_slice_free (CreateBearerContext, ctx);<br>
> +}<br>
> +<br>
> +static MMBaseBearer *<br>
> +cinterion_modem_create_<wbr>bearer_finish (MMIfaceModem *self,<br>
> + GAsyncResult *res,<br>
> + GError **error)<br>
> +{<br>
> + MMBaseBearer *bearer;<br>
> +<br>
> + if (g_simple_async_result_<wbr>propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))<br>
> + return NULL;<br>
> +<br>
> + bearer = g_simple_async_result_get_op_<wbr>res_gpointer (G_SIMPLE_ASYNC_RESULT (res));<br>
> + mm_dbg ("New cinterion bearer created at DBus path '%s'", mm_base_bearer_get_path (bearer));<br>
> + return g_object_ref (bearer);<br>
> +}<br>
> +<br>
> +static void<br>
> +broadband_bearer_cinterion_<wbr>new_ready (GObject *source,<br>
> + GAsyncResult *res,<br>
> + CreateBearerContext *ctx)<br>
> +{<br>
> + MMBaseBearer *bearer;<br>
> + GError *error = NULL;<br>
> +<br>
> + bearer = mm_broadband_bearer_cinterion_<wbr>new_finish (res, &error);<br>
> + if (!bearer)<br>
> + g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> + else<br>
> + g_simple_async_result_set_op_<wbr>res_gpointer (ctx->result, bearer, (GDestroyNotify)g_object_<wbr>unref);<br>
> + create_bearer_context_<wbr>complete_and_free (ctx);<br>
> +}<br>
> +<br>
> +static void<br>
> +broadband_bearer_new_ready (GObject *source,<br>
> + GAsyncResult *res,<br>
> + CreateBearerContext *ctx)<br>
> +{<br>
> + MMBaseBearer *bearer;<br>
> + GError *error = NULL;<br>
> +<br>
> + bearer = mm_broadband_bearer_new_finish (res, &error);<br>
> + if (!bearer)<br>
> + g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> + else<br>
> + g_simple_async_result_set_op_<wbr>res_gpointer (ctx->result, bearer, (GDestroyNotify)g_object_<wbr>unref);<br>
> + create_bearer_context_<wbr>complete_and_free (ctx);<br>
> +}<br>
> +<br>
> +static void<br>
> +create_bearer_for_net_port (CreateBearerContext *ctx)<br>
> +{<br>
> + switch (ctx->self->priv->swwan_<wbr>support) {<br>
> + case FEATURE_SUPPORT_UNKNOWN:<br>
> + g_assert_not_reached ();<br>
> + case FEATURE_NOT_SUPPORTED:<br>
> + mm_dbg ("^SWWAN not supported, creating default bearer...");<br>
> + mm_broadband_bearer_new (MM_BROADBAND_MODEM (ctx->self),<br>
> + ctx->properties,<br>
> + NULL, /* cancellable */<br>
> + (GAsyncReadyCallback)<wbr>broadband_bearer_new_ready,<br>
> + ctx);<br>
> + return;<br>
> + case FEATURE_SUPPORTED:<br>
> + mm_dbg ("^SWWAN supported, creating cinterion bearer...");<br>
> + mm_broadband_bearer_cinterion_<wbr>new (MM_BROADBAND_MODEM_CINTERION (ctx->self),<br>
> + ctx->properties,<br>
> + NULL, /* cancellable */<br>
> + (GAsyncReadyCallback)<wbr>broadband_bearer_cinterion_<wbr>new_ready,<br>
> + ctx);<br>
> + return;<br>
> + }<br>
> +}<br>
> +<br>
> +static MMPortSerialAt *<br>
> +peek_port_at_for_data (MMBroadbandModemCinterion *self,<br>
> + MMPort *port)<br>
> +{<br>
> + GList *cdc_wdm_at_ports, *l;<br>
<br>
cdc_wdm_at_ports doesn't look like a proper name, as you don't have any<br>
cdc-wdm port, just TTYs :)<br>
<br>
> + const gchar *net_port_parent_path;<br>
> +<br>
> + g_warn_if_fail (mm_port_get_subsys (port) == MM_PORT_SUBSYS_NET);<br>
> + net_port_parent_path = mm_port_get_parent_path (port);<br>
> +<br>
> + if (!net_port_parent_path) {<br>
> + g_warning ("(%s) no parent path for net port", mm_port_get_device (port));<br>
> + return NULL;<br>
> + }<br>
> +<br>
> + //Find the port to send AT commands on...<br>
> + cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self),<br>
> + MM_PORT_SUBSYS_USB,<br>
> + MM_PORT_TYPE_NET,<br>
> + NULL);<br>
> +<br>
> +<br>
> + for (l = cdc_wdm_at_ports; l; l = l->next) {<br>
> + const gchar *wdm_port_parent_path;<br>
> +<br>
> + g_assert (MM_IS_PORT_SERIAL_AT (l->data));<br>
> + wdm_port_parent_path = mm_port_get_parent_path (MM_PORT (l->data));<br>
> +<br>
> + if (wdm_port_parent_path && g_str_equal (wdm_port_parent_path, net_port_parent_path))<br>
> + return MM_PORT_SERIAL_AT (l->data);<br>
> + }<br>
> +<br>
> + return NULL;<br>
> +}<br>
> +<br>
> +MMPortSerialAt *<br>
> +mm_broadband_modem_cinterion_<wbr>peek_port_at_for_data (MMBroadbandModemCinterion *self,<br>
> + MMPort *port)<br>
> +{<br>
> + MMPortSerialAt *found;<br>
> +<br>
> + g_assert (self->priv->swwan_support == FEATURE_SUPPORTED);<br>
> +<br>
> + found = peek_port_at_for_data (self, port);<br>
> + if (!found)<br>
> + mm_warn ("Couldn't find associated cdc-wdm port for 'net/%s'",<br>
> + mm_port_get_device (port));<br>
> + return found;<br>
> +}<br>
> +<br>
> +static void<br>
> +ensure_swwan_support_checked (MMBroadbandModemCinterion *self,<br>
> + MMPort *port)<br>
> +{<br>
> + GUdevClient *client;<br>
> + GUdevDevice *data_device;<br>
> +<br>
> + /* Initialize the swwan feature */<br>
> + if (self->priv->swwan_support != FEATURE_SUPPORT_UNKNOWN)<br>
> + return;<br>
> +<br>
> + /* First, check for devices which could support SWWAN. They<br>
> + * will be tagged by udev as net devices. */<br>
> + client = g_udev_client_new (NULL);<br>
> + data_device = (g_udev_client_query_by_<wbr>subsystem_and_name (<br>
> + client,<br>
> + "net",<br>
> + mm_port_get_device (port)));<br>
> +<br>
> + /* udevadm info for Cinterion PLS8-X v3.017 Modem<br>
> + P: /devices/pci0000:00/0000:00:<wbr>14.0/usb3/3-2/3-2:1.10/net/<wbr>usb0<br>
> + E: DEVPATH=/devices/pci0000:00/<wbr>0000:00:14.0/usb3/3-2/3-2:1.<wbr>10/net/usb0<br>
> + E: ID_BUS=usb<br>
> + E: ID_MM_CANDIDATE=1<br>
> + E: ID_MODEL=LTE_Modem<br>
> + E: ID_MODEL_ENC=LTE\x20Modem<br>
> + E: ID_MODEL_FROM_DATABASE=2.0 root hub<br>
> + E: ID_MODEL_ID=0061<br>
> + E: ID_NET_NAME_MAC=<wbr>enxdeadbeef0000<br>
> + E: ID_NET_NAME_PATH=enp0s20u2i10<br>
> + E: ID_REVISION=0232<br>
> + E: ID_SERIAL=Cinterion_LTE_Modem<br>
> + E: ID_TYPE=generic<br>
> + E: ID_USB_DRIVER=cdc_ether<br>
> + E: ID_USB_INTERFACES=:020201:<wbr>0a0000:020600:<br>
> + E: ID_USB_INTERFACE_NUM=0a<br>
> + E: ID_VENDOR=Cinterion<br>
> + E: ID_VENDOR_ENC=Cinterion<br>
> + E: ID_VENDOR_FROM_DATABASE=Linux Foundation<br>
> + E: ID_VENDOR_ID=1e2d<br>
> + E: IFINDEX=11<br>
> + E: INTERFACE=usb0<br>
> + E: SUBSYSTEM=net<br>
> + E: USEC_INITIALIZED=4142603761<br>
> +<br>
> + P: /devices/pci0000:00/0000:00:<wbr>14.0/usb3/3-2/3-2:1.11<br>
> + E: DEVPATH=/devices/pci0000:00/<wbr>0000:00:14.0/usb3/3-2/3-2:1.11<br>
> + E: DEVTYPE=usb_interface<br>
> + E: DRIVER=cdc_ether<br>
> + E: ID_MODEL_FROM_DATABASE=2.0 root hub<br>
> + E: ID_VENDOR_FROM_DATABASE=Linux Foundation<br>
> + E: INTERFACE=10/0/0<br>
> + E: MODALIAS=usb:<wbr>v1E2Dp0061d0232dc00dsc00dp00ic<wbr>0Aisc00ip00in0B<br>
> + E: PRODUCT=1e2d/61/232<br>
> + E: SUBSYSTEM=usb<br>
> + E: TYPE=0/0/0<br>
> + E: USEC_INITIALIZED=604142604196<br>
> + */<br>
> +<br>
> + //Assumption - Cinterion modems that use cdc_ether will support the swwan via usb ethernet.<br>
> + //Cinterion alluded to this idea when I spoke with their engineering support but stopped short<br>
> + //of being able to confirm if all future/current models will be this way.<br>
> + if (data_device && g_str_equal(g_udev_device_get_<wbr>property(data_device, "ID_USB_DRIVER"),"cdc_ether")) {<br>
> + mm_dbg ("This device (%s) can support swwan feature", mm_port_get_device (port));<br>
> + self->priv->swwan_support = FEATURE_SUPPORTED;<br>
> + }<br>
> + else<br>
> + {<br>
> + self->priv->swwan_support = FEATURE_NOT_SUPPORTED;<br>
> + mm_dbg ("This device (%s) can not support swwan feature", mm_port_get_device (port));<br>
> + }<br>
> +<br>
> + /* Free the g_object*/<br>
> + if (data_device)<br>
> + g_object_unref (data_device);<br>
> + if (client)<br>
> + g_object_unref (client);<br>
> +}<br>
> +<br>
> +static void<br>
> +cinterion_modem_create_bearer (MMIfaceModem *self,<br>
> + MMBearerProperties *properties,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data)<br>
> +{<br>
> + CreateBearerContext *ctx;<br>
> + MMPort *port;<br>
> +<br>
> + ctx = g_slice_new0 (CreateBearerContext);<br>
> + ctx->self = g_object_ref (self);<br>
> + ctx->properties = g_object_ref (properties);<br>
> + ctx->result = g_simple_async_result_new (G_OBJECT (self),<br>
> + callback,<br>
> + user_data,<br>
> + cinterion_modem_create_bearer)<wbr>;<br>
> +<br>
> + port = mm_base_modem_peek_best_data_<wbr>port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET);<br>
> +<br>
> + if (port) {<br>
> + ensure_swwan_support_checked (ctx->self, port);<br>
> + create_bearer_for_net_port (ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> + mm_dbg ("Creating default bearer...");<br>
> + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),<br>
> + properties,<br>
> + NULL, /* cancellable */<br>
> + (GAsyncReadyCallback)<wbr>broadband_bearer_new_ready,<br>
> + ctx);<br>
> +}<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
><br>
> MMBroadbandModemCinterion *<br>
> mm_broadband_modem_cinterion_<wbr>new (const gchar *device,<br>
> @@ -1675,6 +1948,7 @@ mm_broadband_modem_cinterion_<wbr>init (MMBroadbandModemCinterion *self)<br>
><br>
> /* Set defaults */<br>
> self->priv->sind_psinfo = TRUE; /* Initially, always try to get psinfo */<br>
> + self->priv->swwan_support = FEATURE_SUPPORT_UNKNOWN;<br>
> }<br>
><br>
> static void<br>
> @@ -1704,6 +1978,8 @@ iface_modem_init (MMIfaceModem *iface)<br>
> {<br>
> iface_modem_parent = g_type_interface_peek_parent (iface);<br>
><br>
> + iface->create_bearer = cinterion_modem_create_bearer;<br>
> + iface->create_bearer_finish = cinterion_modem_create_bearer_<wbr>finish;<br>
> iface->load_supported_modes = load_supported_modes;<br>
> iface->load_supported_modes_<wbr>finish = load_supported_modes_finish;<br>
> iface->set_current_modes = set_current_modes;<br>
> @@ -1764,7 +2040,6 @@ mm_broadband_modem_cinterion_<wbr>class_init (<wbr>MMBroadbandModemCinterionClass *klass)<br>
> {<br>
> GObjectClass *object_class = G_OBJECT_CLASS (klass);<br>
> MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);<br>
> -<br>
> g_type_class_add_private (object_class, sizeof (<wbr>MMBroadbandModemCinterionPriva<wbr>te));<br>
><br>
> /* Virtual methods */<br>
> diff --git a/plugins/cinterion/mm-<wbr>broadband-modem-cinterion.h b/plugins/cinterion/mm-<wbr>broadband-modem-cinterion.h<br>
> index 47f0dcb..b675f3d 100644<br>
> --- a/plugins/cinterion/mm-<wbr>broadband-modem-cinterion.h<br>
> +++ b/plugins/cinterion/mm-<wbr>broadband-modem-cinterion.h<br>
> @@ -48,4 +48,7 @@ MMBroadbandModemCinterion *mm_broadband_modem_cinterion_<wbr>new (const gchar *device<br>
> guint16 vendor_id,<br>
> guint16 product_id);<br>
><br>
> +MMPortSerialAt *mm_broadband_modem_cinterion_<wbr>peek_port_at_for_data (MMBroadbandModemCinterion *self,<br>
> + MMPort *port);<br>
> +<br>
> #endif /* MM_BROADBAND_MODEM_CINTERION_H */<br>
> diff --git a/plugins/cinterion/mm-common-<wbr>cinterion.c b/plugins/cinterion/mm-common-<wbr>cinterion.c<br>
<br>
All the changes here look like GPS related, shouldn't go in this patch.<br>
<br>
> index 6e4da70..c9119ec 100644<br>
> --- a/plugins/cinterion/mm-common-<wbr>cinterion.c<br>
> +++ b/plugins/cinterion/mm-common-<wbr>cinterion.c<br>
> @@ -11,13 +11,15 @@<br>
> * GNU General Public License for more details:<br>
> *<br>
> * Copyright (C) 2014 Ammonit Measurement GmbH<br>
> + * Copyright (C) 2016 Trimble Navigation Limited<br>
> * Author: Aleksander Morgado <<a href="mailto:aleksander@aleksander.es">aleksander@aleksander.es</a>><br>
> + * Contributor: Matthew Stanger <<a href="mailto:matthew_stanger@trimble.com">matthew_stanger@trimble.com</a>><br>
> */<br>
><br>
> #include "mm-common-cinterion.h"<br>
> #include "mm-base-modem-at.h"<br>
> +#include "mm-log.h"<br>
><br>
> -static MMIfaceModemLocation *iface_modem_location_parent;<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
><br>
> @@ -25,11 +27,40 @@ static MMIfaceModemLocation *iface_modem_location_parent;<br>
> static GQuark cinterion_location_context_<wbr>quark;<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> +/* Def's and Enum's */<br>
> +<br>
> +typedef enum {<br>
> + GPS_CONTEXT_STEP_SGPSS = 0,<br>
> + GPS_CONTEXT_STEP_SGPSC_<wbr>ANTENNA,<br>
> + GPS_CONTEXT_STEP_SGPSC_ENGINE,<br>
> + GPS_CONTEXT_STEP_SGPSC_OUTPUT,<br>
> + GPS_CONTEXT_STEP_DONE,<br>
> +} ConnectGpsContextStep;<br>
> +<br>
> +typedef struct {<br>
> + MMBaseModem *self;<br>
> + GSimpleAsyncResult *result;<br>
> + MMModemLocationSource source;<br>
> + ConnectGpsContextStep enable_state;<br>
> + ConnectGpsContextStep disable_state;<br>
> + gpointer connect_pending;<br>
> + gpointer disconnect_pending;<br>
> + gint gps_retry;<br>
> +} LocationGatheringContext;<br>
><br>
> typedef struct {<br>
> MMModemLocationSource enabled_sources;<br>
> } LocationContext;<br>
><br>
> +static MMIfaceModemLocation *iface_modem_location_parent;<br>
> +static void try_gps_enable (MMIfaceModemLocation *self,<br>
> + LocationGatheringContext *ctx);<br>
> +static void try_gps_disable (MMIfaceModemLocation *self,<br>
> + LocationGatheringContext *ctx);<br>
> +static void send_gps_command (MMIfaceModemLocation *self,<br>
> + LocationGatheringContext *ctx,<br>
> + gchar **command);<br>
> +<br>
> static void<br>
> location_context_free (LocationContext *ctx)<br>
> {<br>
> @@ -127,13 +158,7 @@ mm_common_cinterion_location_<wbr>load_capabilities (MMIfaceModemLocation *self,<br>
> }<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> -/* Enable/Disable location gathering (Location interface) */<br>
> -<br>
> -typedef struct {<br>
> - MMBaseModem *self;<br>
> - GSimpleAsyncResult *result;<br>
> - MMModemLocationSource source;<br>
> -} LocationGatheringContext;<br>
> +/* Common location gathering (Location interface) */<br>
><br>
> static void<br>
> location_gathering_context_<wbr>complete_and_free (LocationGatheringContext *ctx)<br>
> @@ -144,6 +169,145 @@ location_gathering_context_<wbr>complete_and_free (LocationGatheringContext *ctx)<br>
> g_slice_free (LocationGatheringContext, ctx);<br>
> }<br>
><br>
> +static void<br>
> +location_gathering_context_<wbr>complete_and_free_full (LocationGatheringContext *ctx)<br>
> +{<br>
> + //It's importiant that this parent function remains protected by calls where<br>
> + //both context '*_pending' pointers could be active.<br>
> + if (ctx->connect_pending != NULL)<br>
> + ctx->connect_pending = NULL;<br>
> + else<br>
> + ctx->disconnect_pending = NULL;<br>
> +<br>
> + location_gathering_context_<wbr>complete_and_free(ctx);<br>
> +}<br>
> +<br>
> +static void<br>
> +promote_gps_state_from_<wbr>response (MMBaseModem *self,<br>
> + GAsyncResult *res,<br>
> + LocationGatheringContext *ctx)<br>
> +{<br>
> + GError *error = NULL;<br>
> + ConnectGpsContextStep state;<br>
> + gchar *result;<br>
> +<br>
> + //Adjust for whether we're dis/en-ableing<br>
> + if (ctx->connect_pending != NULL)<br>
> + state = ctx->enable_state;<br>
> + else<br>
> + {<br>
> + g_assert(ctx->disconnect_<wbr>pending != NULL);<br>
> + state = ctx->disable_state;<br>
> + }<br>
> +<br>
> + //We see that the 'Engine,1' command (PLS8) fails for no apparent reason<br>
> + //on ATMEL AT91SAM9263 ARM & not x86. See 'mm_common_cinterion_setup_<wbr>gps_port'<br>
> + //comments.<br>
> + result = g_strdup (mm_base_modem_at_command_<wbr>full_finish (self, res, &error));<br>
> +<br>
> + //If we get an error don't progress at first, try the same state again.<br>
> + if ((!result && state != GPS_CONTEXT_STEP_SGPSS && ctx-> gps_retry < 3))<br>
> + {<br>
> + state = GPS_CONTEXT_STEP_SGPSS;<br>
> + ctx->gps_retry++;<br>
> + }<br>
> + else {<br>
> +<br>
> +<br>
> + switch (state) {<br>
> +<br>
> + //First step try's the legacy SGPSS command. If it fails don't error out<br>
> + //continue on to send new Cinterion GPS commands and see if they work.<br>
> + case GPS_CONTEXT_STEP_SGPSS: {<br>
> + if (!result) {<br>
> + mm_info ("SGPSS command failed, will try another Cinterion GPS command.");<br>
> + state = GPS_CONTEXT_STEP_SGPSC_<wbr>ANTENNA;<br>
> + }<br>
> + else<br>
> + state = GPS_CONTEXT_STEP_DONE;<br>
> +<br>
> + break;<br>
> + }<br>
> + //If the new GPS enable comand fails then exit we are out of things to try.<br>
> + //Only connect flow cares about errors.<br>
> + case GPS_CONTEXT_STEP_SGPSC_<wbr>ANTENNA: {<br>
> + if (!result && ctx->disconnect_pending == NULL) {<br>
> + mm_info ("SGPSC command failed, we are out of things to try.");<br>
> + g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> + location_gathering_context_<wbr>complete_and_free_full (ctx);<br>
> + return;<br>
> + }<br>
> + else<br>
> + state = GPS_CONTEXT_STEP_SGPSC_ENGINE;<br>
> +<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_SGPSC_ENGINE: {<br>
> + if (!result && ctx->disconnect_pending == NULL) {<br>
> + g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> + location_gathering_context_<wbr>complete_and_free_full (ctx);<br>
> + return;<br>
> + }<br>
> + else<br>
> + state = GPS_CONTEXT_STEP_SGPSC_OUTPUT;<br>
> +<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_SGPSC_OUTPUT: {<br>
> + if (!result && ctx->disconnect_pending == NULL) {<br>
> + g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> + location_gathering_context_<wbr>complete_and_free_full (ctx);<br>
> + return;<br>
> + }<br>
> + else<br>
> + state = GPS_CONTEXT_STEP_DONE;<br>
> +<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_DONE: {<br>
> + //Shouldn't get here, fall into default error.<br>
> + }<br>
> + default: {<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_WRONG_STATE,<br>
> + "Unknown gps state during setup.");<br>
> +<br>
> + location_gathering_context_<wbr>complete_and_free_full (ctx);<br>
> + g_free(result);<br>
> + return;<br>
> + }<br>
> + }<br>
> + }<br>
> +<br>
> + //Save state and go back to dis/en-ableing<br>
> + if (ctx->connect_pending != NULL) {<br>
> + ctx->enable_state = state;<br>
> + try_gps_enable(ctx->connect_<wbr>pending, ctx);<br>
> + }<br>
> + else {<br>
> + ctx->disable_state = state;<br>
> + try_gps_disable(ctx-><wbr>disconnect_pending, ctx);<br>
> + }<br>
> + g_free(result);<br>
> +}<br>
> +<br>
> +static void<br>
> +send_gps_command (MMIfaceModemLocation *self,<br>
> + LocationGatheringContext *ctx,<br>
> + gchar **command)<br>
> +{<br>
> + mm_base_modem_at_command_full (MM_BASE_MODEM (self),<br>
> + mm_base_modem_peek_best_at_<wbr>port (MM_BASE_MODEM (self), NULL),<br>
> + *command,<br>
> + 5,<br>
> + FALSE,<br>
> + FALSE, /* raw */<br>
> + NULL, /* cancellable */<br>
> + (GAsyncReadyCallback)promote_<wbr>gps_state_from_response,<br>
> + ctx);<br>
> +}<br>
> +<br>
> /*****************************<wbr>*/<br>
> /* Disable location gathering */<br>
><br>
> @@ -157,15 +321,9 @@ mm_common_cinterion_disable_<wbr>location_gathering_finish (MMIfaceModemLocation *sel<br>
><br>
> static void<br>
> gps_disabled_ready (MMBaseModem *self,<br>
> - GAsyncResult *res,<br>
> LocationGatheringContext *ctx)<br>
> {<br>
> - GError *error = NULL;<br>
> -<br>
> - if (!mm_base_modem_at_command_<wbr>full_finish (self, res, &error))<br>
> - g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> - else<br>
> - g_simple_async_result_set_op_<wbr>res_gboolean (ctx->result, TRUE);<br>
> + g_simple_async_result_set_op_<wbr>res_gboolean (ctx->result, TRUE);<br>
><br>
> /* Only use the GPS port in NMEA/RAW setups */<br>
> if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_<wbr>NMEA |<br>
> @@ -178,11 +336,57 @@ gps_disabled_ready (MMBaseModem *self,<br>
> mm_port_serial_close (MM_PORT_SERIAL (gps_port));<br>
> }<br>
><br>
> - location_gathering_context_<wbr>complete_and_free (ctx);<br>
> + location_gathering_context_<wbr>complete_and_free_full (ctx);<br>
> +}<br>
> +<br>
> +static void<br>
> +try_gps_disable (MMIfaceModemLocation *self,<br>
> + LocationGatheringContext *ctx)<br>
> +{<br>
> + gchar *gps_command;<br>
> +<br>
> + switch (ctx->disable_state) {<br>
> +<br>
> + //The old command to enable GPS, works on at least PSX8<br>
> + case GPS_CONTEXT_STEP_SGPSS: {<br>
> + gps_command = "^SGPSS=0";<br>
> + break;<br>
> + }<br>
> + //Commands Power/Ant, Engine & Output w/ sgpsc are<br>
> + //used by PLS8-X & E. Since around versions 2.0+<br>
> + case GPS_CONTEXT_STEP_SGPSC_<wbr>ANTENNA: {<br>
> + gps_command = "^SGPSC=\"Power/Antenna\",\"<wbr>off\"";<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_SGPSC_ENGINE: {<br>
> + gps_command = "^SGPSC=\"Engine\",\"0\"";<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_SGPSC_OUTPUT: {<br>
> + gps_command = "^SGPSC=\"NMEA/Output\",\"off\<wbr>"";<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_DONE: {<br>
> + gps_disabled_ready(MM_BASE_<wbr>MODEM (self), ctx);<br>
> + return;<br>
> + }<br>
> + default: {<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_WRONG_STATE,<br>
> + "Unknown gps state during tear down.");<br>
> +<br>
> + location_gathering_context_<wbr>complete_and_free_full (ctx);<br>
> + return;<br>
> + }<br>
> + }<br>
> +<br>
> + send_gps_command(self, ctx, &gps_command);<br>
> + return;<br>
> }<br>
><br>
> static void<br>
> -internal_disable_location_<wbr>gathering (LocationGatheringContext *ctx)<br>
> +internal_disable_location_<wbr>gathering (MMIfaceModemLocation *self, LocationGatheringContext *ctx)<br>
> {<br>
> LocationContext *location_ctx;<br>
> gboolean stop_gps = FALSE;<br>
> @@ -202,21 +406,30 @@ internal_disable_location_<wbr>gathering (LocationGatheringContext *ctx)<br>
> }<br>
><br>
> if (stop_gps) {<br>
> - /* We disable continuous GPS fixes */<br>
> - mm_base_modem_at_command_full (MM_BASE_MODEM (ctx->self),<br>
> - mm_base_modem_peek_best_at_<wbr>port (MM_BASE_MODEM (ctx->self), NULL),<br>
> - "AT^SGPSS=0",<br>
> - 3,<br>
> - FALSE,<br>
> - FALSE, /* raw */<br>
> - NULL, /* cancellable */<br>
> - (GAsyncReadyCallback)gps_<wbr>disabled_ready,<br>
> - ctx);<br>
> + //Don't allow disconnects while trying to connect.<br>
> + if (ctx->connect_pending != NULL)<br>
> + {<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_IN_PROGRESS,<br>
> + "Can't disconnect GPS while another process is trying to connect it.");<br>
> +<br>
> + //*_complete_and_free_full is safe to use for the disconnect flow after this point.<br>
> + ctx->disconnect_pending = NULL;<br>
> + location_gathering_context_<wbr>complete_and_free (ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> + ctx->disable_state = GPS_CONTEXT_STEP_SGPSS;<br>
> + ctx->disconnect_pending = self;<br>
> + try_gps_disable(self, ctx);<br>
> +<br>
> return;<br>
> }<br>
><br>
> /* For any other location (e.g. 3GPP), or if still some GPS needed, just return */<br>
> g_simple_async_result_set_op_<wbr>res_gboolean (ctx->result, TRUE);<br>
> + ctx->disconnect_pending = NULL;<br>
> location_gathering_context_<wbr>complete_and_free (ctx);<br>
> }<br>
><br>
> @@ -236,12 +449,13 @@ parent_disable_location_<wbr>gathering_ready (MMIfaceModemLocation *self,<br>
> } else {<br>
> /* Fatal */<br>
> g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> + ctx->disconnect_pending = NULL;<br>
> location_gathering_context_<wbr>complete_and_free (ctx);<br>
> return;<br>
> }<br>
> }<br>
><br>
> - internal_disable_location_<wbr>gathering (ctx);<br>
> + internal_disable_location_<wbr>gathering (self, ctx);<br>
> }<br>
><br>
> void<br>
> @@ -259,6 +473,9 @@ mm_common_cinterion_disable_<wbr>location_gathering (MMIfaceModemLocation *self,<br>
> user_data,<br>
> mm_common_cinterion_disable_<wbr>location_gathering);<br>
> ctx->source = source;<br>
> + ctx->connect_pending = NULL;<br>
> + ctx->disconnect_pending = NULL;<br>
> + ctx->gps_retry = 0;<br>
><br>
> /* Chain up parent's gathering enable */<br>
> if (iface_modem_location_parent-><wbr>disable_location_gathering) {<br>
> @@ -270,7 +487,7 @@ mm_common_cinterion_disable_<wbr>location_gathering (MMIfaceModemLocation *self,<br>
> return;<br>
> }<br>
><br>
> - internal_disable_location_<wbr>gathering (ctx);<br>
> + internal_disable_location_<wbr>gathering (self, ctx);<br>
> }<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> @@ -286,16 +503,10 @@ mm_common_cinterion_enable_<wbr>location_gathering_finish (MMIfaceModemLocation *self<br>
><br>
> static void<br>
> gps_enabled_ready (MMBaseModem *self,<br>
> - GAsyncResult *res,<br>
> LocationGatheringContext *ctx)<br>
> {<br>
> GError *error = NULL;<br>
><br>
> - if (!mm_base_modem_at_command_<wbr>full_finish (self, res, &error)) {<br>
> - g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> - location_gathering_context_<wbr>complete_and_free (ctx);<br>
> - return;<br>
> - }<br>
><br>
> /* Only use the GPS port in NMEA/RAW setups */<br>
> if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_<wbr>NMEA |<br>
> @@ -312,12 +523,60 @@ gps_enabled_ready (MMBaseModem *self,<br>
> MM_CORE_ERROR,<br>
> MM_CORE_ERROR_FAILED,<br>
> "Couldn't open raw GPS serial port");<br>
> - } else<br>
> + }<br>
> + else<br>
> g_simple_async_result_set_op_<wbr>res_gboolean (ctx->result, TRUE);<br>
> - } else<br>
> +<br>
> + }<br>
> + else<br>
> g_simple_async_result_set_op_<wbr>res_gboolean (ctx->result, TRUE);<br>
><br>
> - location_gathering_context_<wbr>complete_and_free (ctx);<br>
> + location_gathering_context_<wbr>complete_and_free_full (ctx);<br>
> +}<br>
> +<br>
> +static void<br>
> +try_gps_enable (MMIfaceModemLocation *self,<br>
> + LocationGatheringContext *ctx)<br>
> +{<br>
> + gchar *gps_command;<br>
> +<br>
> + switch (ctx->enable_state) {<br>
> +<br>
> + //The old command to enable GPS, works on at least PSX8<br>
> + case GPS_CONTEXT_STEP_SGPSS: {<br>
> + gps_command = "^SGPSS=4";<br>
> + break;<br>
> + }<br>
> + //Commands Power/Ant, Engine & Output w/ sgpsc are<br>
> + //used by PLS8-X & E. Since versions 2.0+<br>
> + case GPS_CONTEXT_STEP_SGPSC_<wbr>ANTENNA: {<br>
> + gps_command = "^SGPSC=\"Power/Antenna\",\"<wbr>on\"";<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_SGPSC_ENGINE: {<br>
> + gps_command = "^SGPSC=\"Engine\",\"1\"";<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_SGPSC_OUTPUT: {<br>
> + gps_command = "^SGPSC=\"NMEA/Output\",\"on\"<wbr>";<br>
> + break;<br>
> + }<br>
> + case GPS_CONTEXT_STEP_DONE: {<br>
> + gps_enabled_ready(MM_BASE_<wbr>MODEM (self), ctx);<br>
> + return;<br>
> + }<br>
> + default: {<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_WRONG_STATE,<br>
> + "Unknown gps state during setup.");<br>
> + location_gathering_context_<wbr>complete_and_free_full (ctx);<br>
> + return;<br>
> + }<br>
> + }<br>
> +<br>
> + send_gps_command(self, ctx, &gps_command);<br>
> + return;<br>
> }<br>
><br>
> static void<br>
> @@ -338,13 +597,13 @@ parent_enable_location_<wbr>gathering_ready (MMIfaceModemLocation *self,<br>
> } else {<br>
> /* Fatal */<br>
> g_simple_async_result_take_<wbr>error (ctx->result, error);<br>
> + ctx->connect_pending = NULL;<br>
> location_gathering_context_<wbr>complete_and_free (ctx);<br>
> return;<br>
> }<br>
> }<br>
><br>
> /* Now our own enabling */<br>
> -<br>
> location_ctx = get_location_context (MM_BASE_MODEM (self));<br>
><br>
> /* NMEA and RAW are both enabled in the same way */<br>
> @@ -359,22 +618,33 @@ parent_enable_location_<wbr>gathering_ready (MMIfaceModemLocation *self,<br>
> location_ctx->enabled_sources |= ctx->source;<br>
> }<br>
><br>
> +<br>
> if (start_gps) {<br>
> - /* We enable continuous GPS fixes */<br>
> - mm_base_modem_at_command_full (MM_BASE_MODEM (self),<br>
> - mm_base_modem_peek_best_at_<wbr>port (MM_BASE_MODEM (self), NULL),<br>
> - "AT^SGPSS=4",<br>
> - 3,<br>
> - FALSE,<br>
> - FALSE, /* raw */<br>
> - NULL, /* cancellable */<br>
> - (GAsyncReadyCallback)gps_<wbr>enabled_ready,<br>
> - ctx);<br>
> + //Can only setup/tear down one thing at a time.<br>
> + if (ctx->disconnect_pending != NULL)<br>
> + {<br>
> + g_simple_async_result_set_<wbr>error (ctx->result,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_IN_PROGRESS,<br>
> + "Can't setup GPS while another process is trying to disconnect it.");<br>
> +<br>
> + //*_complete_and_free_full is safe to use for the connect flow after this point.<br>
> + ctx->connect_pending = NULL;<br>
> + location_gathering_context_<wbr>complete_and_free (ctx);<br>
> + return;<br>
> + }<br>
> +<br>
> + ctx->enable_state = GPS_CONTEXT_STEP_SGPSS;<br>
> + ctx->connect_pending = self;<br>
> + ctx->gps_retry = 0;<br>
> + try_gps_enable(self, ctx);<br>
> +<br>
> return;<br>
> }<br>
><br>
> /* For any other location (e.g. 3GPP), or if GPS already running just return */<br>
> g_simple_async_result_set_op_<wbr>res_gboolean (ctx->result, TRUE);<br>
> + ctx->connect_pending = NULL;<br>
> location_gathering_context_<wbr>complete_and_free (ctx);<br>
> }<br>
><br>
> @@ -393,6 +663,8 @@ mm_common_cinterion_enable_<wbr>location_gathering (MMIfaceModemLocation *self,<br>
> user_data,<br>
> mm_common_cinterion_enable_<wbr>location_gathering);<br>
> ctx->source = source;<br>
> + ctx->connect_pending = NULL;<br>
> + ctx->disconnect_pending = NULL;<br>
><br>
> /* Chain up parent's gathering enable */<br>
> iface_modem_location_parent-><wbr>enable_location_gathering (<br>
> @@ -443,11 +715,41 @@ mm_common_cinterion_setup_gps_<wbr>port (MMBroadbandModem *self)<br>
> if (gps_data_port) {<br>
> /* It may happen that the modem was started with GPS already enabled, or<br>
> * maybe ModemManager got rebooted and it was left enabled before. We'll make<br>
> - * sure that it is disabled when we initialize the modem */<br>
> + * sure that it is disabled when we initialize the modem.<br>
> + */<br>
> +<br>
> + //It may seem odd that we are turning Antenna & Engine on but it is critical for the PLS8<br>
> + //that this happen here. There is a bug that keeps 'Engine on' from succeding on<br>
> + //the first go around in the 'try_gps_enable' flow. Cinterion confirmed this a bug. Retry's in<br>
> + //the connect flow won't help and the only way found so far is to enable the engine here,<br>
> + //before the modem is enabled. This is not ideal and should be redone as soon as the root<br>
> + //cause of the bug is identified. This bug does not appear on my x86 platform. -Matt Stanger<br>
> + //TODO: Fix bug in 'try_gps_enable' where 'Engine on' command gets stuck returning 767 errors<br>
> + //on ARM AT91SAM9263 processor then change these to default off/0.<br>
> mm_base_modem_at_command_full (MM_BASE_MODEM (self),<br>
> mm_base_modem_peek_best_at_<wbr>port (MM_BASE_MODEM (self), NULL),<br>
> "AT^SGPSS=0",<br>
> 3, FALSE, FALSE, NULL, NULL, NULL);<br>
> + g_usleep (100);<br>
> +<br>
> + mm_base_modem_at_command_full (MM_BASE_MODEM (self),<br>
> + mm_base_modem_peek_best_at_<wbr>port (MM_BASE_MODEM (self), NULL),<br>
> + "^SGPSC=\"Power/Antenna\",\"<wbr>on\"",<br>
> + 3, FALSE, FALSE, NULL, NULL, NULL);<br>
> + g_usleep (100);<br>
> +<br>
> + mm_base_modem_at_command_full (MM_BASE_MODEM (self),<br>
> + mm_base_modem_peek_best_at_<wbr>port (MM_BASE_MODEM (self), NULL),<br>
> + "^SGPSC=\"Engine\",\"1\"",<br>
> + 3, FALSE, FALSE, NULL, NULL, NULL);<br>
> +<br>
> + g_usleep (100);<br>
> +<br>
> + mm_base_modem_at_command_full (MM_BASE_MODEM (self),<br>
> + mm_base_modem_peek_best_at_<wbr>port (MM_BASE_MODEM (self), NULL),<br>
> + "^SGPSC=\"NMEA/Output\",\"off\<wbr>"",<br>
> + 3, FALSE, FALSE, NULL, NULL, NULL);<br>
> +<br>
><br>
> /* Add handler for the NMEA traces */<br>
> mm_port_serial_gps_add_trace_<wbr>handler (gps_data_port,<br>
> diff --git a/plugins/cinterion/mm-modem-<wbr>helpers-cinterion.c b/plugins/cinterion/mm-modem-<wbr>helpers-cinterion.c<br>
> index bffe00a..14b18dd 100644<br>
> --- a/plugins/cinterion/mm-modem-<wbr>helpers-cinterion.c<br>
> +++ b/plugins/cinterion/mm-modem-<wbr>helpers-cinterion.c<br>
> @@ -10,12 +10,15 @@<br>
> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>
> * GNU General Public License for more details:<br>
> *<br>
> + * Copyright (C) 2016 Trimble Navigation Limited<br>
> * Copyright (C) 2014 Aleksander Morgado <<a href="mailto:aleksander@aleksander.es">aleksander@aleksander.es</a>><br>
> + * Contributor: Matthew Stanger <<a href="mailto:matthew_stanger@trimble.com">matthew_stanger@trimble.com</a>><br>
> */<br>
><br>
> #include <config.h><br>
> #include <string.h><br>
> #include <stdlib.h><br>
> +#include <stdio.h><br>
><br>
> #include "ModemManager.h"<br>
> #define _LIBMM_INSIDE_MM<br>
> @@ -24,6 +27,7 @@<br>
> #include "mm-charsets.h"<br>
> #include "mm-errors-types.h"<br>
> #include "mm-modem-helpers-cinterion.h"<br>
> +#include "mm-modem-helpers.h"<br>
><br>
> /* Setup relationship between the 3G band bitmask in the modem and the bitmask<br>
> * in ModemManager. */<br>
> @@ -482,3 +486,187 @@ mm_cinterion_parse_sind_<wbr>response (const gchar *response,<br>
><br>
> return TRUE;<br>
> }<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* ^DHCP response parser */<br>
> +<br>
> +static gboolean<br>
> +match_info_to_ip4_addr (GMatchInfo *match_info,<br>
> + guint match_index,<br>
> + guint *out_addr)<br>
> +{<br>
> + gchar *s, *bin = NULL;<br>
> + gchar buf[9];<br>
> + gsize len, bin_len;<br>
> + gboolean success = FALSE;<br>
> +<br>
> + s = g_match_info_fetch (match_info, match_index);<br>
> + g_return_val_if_fail (s != NULL, FALSE);<br>
> +<br>
> + len = strlen (s);<br>
> + if (len == 1 && s[0] == '0') {<br>
> + *out_addr = 0;<br>
> + success = TRUE;<br>
> + goto done;<br>
> + }<br>
> + if (len < 7 || len > 8)<br>
> + goto done;<br>
> +<br>
> + /* Handle possibly missing leading zero */<br>
> + memset (buf, 0, sizeof (buf));<br>
> + if (len == 7) {<br>
> + strcpy (&buf[1], s);<br>
> + buf[0] = '0';<br>
> + } else if (len == 8)<br>
> + strcpy (buf, s);<br>
> + else<br>
> + g_assert_not_reached ();<br>
> +<br>
> + bin = mm_utils_hexstr2bin (buf, &bin_len);<br>
> + if (!bin || bin_len != 4)<br>
> + goto done;<br>
> +<br>
> + *out_addr = GUINT32_SWAP_LE_BE (*((guint32 *) bin));<br>
> + success = TRUE;<br>
> +<br>
> +done:<br>
> + g_free (s);<br>
> + g_free (bin);<br>
> + return success;<br>
> +}<br>
> +<br>
> +gboolean<br>
> +mm_cinterion_parse_dhcp_<wbr>response (const char *reply,<br>
> + guint *out_address,<br>
> + guint *out_prefix,<br>
> + guint *out_gateway,<br>
> + guint *out_dns1,<br>
> + guint *out_dns2,<br>
> + GError **error)<br>
> +{<br>
> + gboolean matched;<br>
> + GRegex *r;<br>
> + GMatchInfo *match_info = NULL;<br>
> + GError *match_error = NULL;<br>
> +<br>
> + g_assert (reply != NULL);<br>
> + g_assert (out_address != NULL);<br>
> + g_assert (out_prefix != NULL);<br>
> + g_assert (out_gateway != NULL);<br>
> + g_assert (out_dns1 != NULL);<br>
> + g_assert (out_dns2 != NULL);<br>
> +<br>
> + /* Format:<br>
> + *<br>
> + * ^DHCP: <address>,<netmask>,<gateway>,<wbr><?>,<dns1>,<dns2>,<uplink>,<<wbr>downlink><br>
> + *<br>
> + * All numbers are hexadecimal representations of IPv4 addresses, with<br>
> + * least-significant byte first. eg, 192.168.50.32 is expressed as<br>
> + * "2032A8C0". Sometimes leading zeros are stripped, so "1010A0A" is<br>
> + * actually 10.10.1.1.<br>
> + */<br>
> +<br>
> + r = g_regex_new ("\\^DHCP:\\s*([0-9a-fA-F]+),(<wbr>[0-9a-fA-F]+),([0-9a-fA-F]+),(<wbr>[0-9a-fA-F]+),([0-9a-fA-F]+),(<wbr>[0-9a-fA-F]+),.*$", 0, 0, NULL);<br>
> + g_assert (r != NULL);<br>
> +<br>
> + matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);<br>
> + if (!matched) {<br>
> + if (match_error) {<br>
> + g_propagate_error (error, match_error);<br>
> + g_prefix_error (error, "Could not parse ^DHCP results: ");<br>
> + } else {<br>
> + g_set_error_literal (error,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_FAILED,<br>
> + "Couldn't match ^DHCP reply");<br>
> + }<br>
> + } else {<br>
> + guint netmask;<br>
> +<br>
> + if (match_info_to_ip4_addr (match_info, 1, out_address) &&<br>
> + match_info_to_ip4_addr (match_info, 2, &netmask) &&<br>
> + match_info_to_ip4_addr (match_info, 3, out_gateway) &&<br>
> + match_info_to_ip4_addr (match_info, 5, out_dns1) &&<br>
> + match_info_to_ip4_addr (match_info, 6, out_dns2)) {<br>
> + *out_prefix = mm_count_bits_set (netmask);<br>
> + matched = TRUE;<br>
> + }<br>
> + }<br>
> +<br>
> + if (match_info)<br>
> + g_match_info_free (match_info);<br>
> + g_regex_unref (r);<br>
> + return matched;<br>
> +}<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* ^SWWAN read parser<br>
> + * Description: Parses <cid>, <state>[, <WWAN adapter>] or CME ERROR from SWWAN.<br>
> + * Return: False if error occured while trying to parse SWWAN.<br>
> + * Requires: Check for AT Error response before calling.<br>
> + * Read Command<br>
> + * AT^SWWAN?<br>
> + * Response(s) <br>
> + * [^SWWAN: <cid>, <state>[, <WWAN adapter>]]<br>
> + * [^SWWAN: ...]<br>
> + * OK<br>
> + * ERROR<br>
> + * +CME ERROR: <err><br>
> + *<br>
> + * Examples:<br>
> + * OK - If no WWAN connection is active, then read command just returns OK<br>
> + * ^SWWAN: 3,1,1 - 3rd PDP Context, Activated, First WWAN Adaptor<br>
> + * +CME ERROR: ? -<br>
> +*/<br>
> +<br>
<br>
This is much better than returning a blind GList of integers where the<br>
caller needs to know which integer is which:<br>
<br>
gboolean<br>
mm_cinterion_parse_swwan_<wbr>response (<br>
const gchar *response,<br>
guint *cid,<br>
guint *state,<br>
guint *wwan_adapter,<br>
GError **error);<br>
<br>
Also, please write unit tests for the newly written parsers.<br>
<br>
> +gboolean<br>
> +mm_cinterion_parse_swwan_<wbr>response (const gchar *response,<br>
> + GList **result,<br>
> + GError **error)<br>
> +{<br>
> + if (!response){<br>
> + g_set_error (error,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_FAILED,<br>
> + "Recieved NULL from SWWAN response.");<br>
> + return FALSE;<br>
> + }<br>
> +<br>
> + //Parse for [^SWWAN: <cid>, <state>[, <WWAN adapter>]]<br>
> + if (strcasestr (response, "SWWAN"))<br>
> + {<br>
> + gint matched;<br>
> + guint cid, state, wwan_adapter;<br>
> + matched = sscanf (response, "^SWWAN: %d,%d,%d",<br>
> + &cid,<br>
> + &state,<br>
> + &wwan_adapter);<br>
> +<br>
> + if (matched != 3) {<br>
> + g_set_error (error,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_FAILED,<br>
> + "Could not parse SWWAN response: '%s'",<br>
> + response);<br>
> + return FALSE;<br>
> + }<br>
> +<br>
> + *result = g_list_append (*result, GINT_TO_POINTER(cid));<br>
> + *result = g_list_append (*result, GINT_TO_POINTER(state));<br>
> + *result = g_list_append (*result, GINT_TO_POINTER(wwan_adapter))<wbr>;<br>
> + }<br>
> + //TODO: It'd be nice to get 'OK' from response so we don't have to assume that<br>
> + //zero length response means 'OK' or am I doing it wrong...<br>
> + else if (!g_utf8_strlen (response, 100))<br>
> + *result = g_list_append (*result, GINT_TO_POINTER(0));<br>
> + else {<br>
> + g_set_error (error,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_FAILED,<br>
> + "Unknown SWWAN response, unable to parse.");<br>
> + return FALSE;<br>
> + }<br>
> +<br>
> +<br>
> + return TRUE;<br>
> +}<br>
> diff --git a/plugins/cinterion/mm-modem-<wbr>helpers-cinterion.h b/plugins/cinterion/mm-modem-<wbr>helpers-cinterion.h<br>
> index d37bcb0..11754ae 100644<br>
> --- a/plugins/cinterion/mm-modem-<wbr>helpers-cinterion.h<br>
> +++ b/plugins/cinterion/mm-modem-<wbr>helpers-cinterion.h<br>
> @@ -10,7 +10,9 @@<br>
> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>
> * GNU General Public License for more details:<br>
> *<br>
> + * Copyright (C) 2016 Trimble Navigation Limited<br>
> * Copyright (C) 2014 Aleksander Morgado <<a href="mailto:aleksander@aleksander.es">aleksander@aleksander.es</a>><br>
> + * Contributor: Matthew Stanger <<a href="mailto:matthew_stanger@trimble.com">matthew_stanger@trimble.com</a>><br>
> */<br>
><br>
> #ifndef MM_MODEM_HELPERS_CINTERION_H<br>
> @@ -63,4 +65,19 @@ gboolean mm_cinterion_parse_sind_<wbr>response (const gchar *response,<br>
> guint *value,<br>
> GError **error);<br>
><br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* ^DHCP response parser */<br>
> +gboolean mm_cinterion_parse_dhcp_<wbr>response (const char *reply,<br>
> + guint *out_address,<br>
> + guint *out_prefix,<br>
> + guint *out_gateway,<br>
> + guint *out_dns1,<br>
> + guint *out_dns2,<br>
> + GError **error);<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> +/* ^SWWAN response parser */<br>
> +gboolean mm_cinterion_parse_swwan_<wbr>response (const gchar *response,<br>
> + GList **result,<br>
> + GError **error);<br>
> #endif /* MM_MODEM_HELPERS_CINTERION_H */<br>
> diff --git a/plugins/cinterion/mm-plugin-<wbr>cinterion.c b/plugins/cinterion/mm-plugin-<wbr>cinterion.c<br>
> index 8af0a2a..d944c00 100644<br>
> --- a/plugins/cinterion/mm-plugin-<wbr>cinterion.c<br>
> +++ b/plugins/cinterion/mm-plugin-<wbr>cinterion.c<br>
> @@ -18,7 +18,9 @@<br>
> *<br>
> * Copyright (C) 2011 Ammonit Measurement GmbH<br>
> * Copyright (C) 2011 - 2012 Google Inc.<br>
> + * Copyright (C) 2016 Trimble Navigation Limited<br>
> * Author: Aleksander Morgado <<a href="mailto:aleksander@lanedo.com">aleksander@lanedo.com</a>><br>
> + * Contributor: Matthew Stanger <<a href="mailto:matthew_stanger@trimble.com">matthew_stanger@trimble.com</a>><br>
> */<br>
><br>
> #include <string.h><br>
> @@ -45,6 +47,7 @@ int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;<br>
><br>
> #define TAG_CINTERION_APP_PORT "cinterion-app-port"<br>
> #define TAG_CINTERION_MODEM_PORT "cinterion-modem-port"<br>
> +#define TAG_CINTERION_GPS_PORT "ID_MM_CINTERION_PORT_TYPE_<wbr>GPS"<br>
><br>
> typedef struct {<br>
> MMPortProbe *probe;<br>
> @@ -175,9 +178,14 @@ grab_port (MMPlugin *self,<br>
> mm_dbg ("(%s/%s)' Port flagged as PPP",<br>
> mm_port_probe_get_port_subsys (probe),<br>
> mm_port_probe_get_port_name (probe));<br>
> - pflags = MM_PORT_SERIAL_AT_FLAG_PPP;<br>
> +<br>
> + //Assuming PLS8 will stay idProduct 0061 and for that product we'll use SWWAN, not PPP.<br>
> + if ((g_strcmp0 (g_udev_device_get_property(<wbr>mm_port_probe_peek_port (probe),"ID_MODEL_ID"),"0061") == 0))<br>
> + pflags = MM_PORT_SERIAL_AT_FLAG_<wbr>SECONDARY;<br>
> + else<br>
> + pflags = MM_PORT_SERIAL_AT_FLAG_PPP;<br>
> } else if (g_udev_device_get_property_<wbr>as_boolean (mm_port_probe_peek_port (probe),<br>
> - "ID_MM_CINTERION_PORT_TYPE_<wbr>GPS")) {<br>
> + TAG_CINTERION_GPS_PORT)) {<br>
> mm_dbg ("(%s/%s)' Port flagged as GPS",<br>
<br>
Ignore the GPS flag for now in this patch.<br>
<br>
> mm_port_probe_get_port_subsys (probe),<br>
> mm_port_probe_get_port_name (probe));<br>
> @@ -185,6 +193,135 @@ grab_port (MMPlugin *self,<br>
> g_warn_if_fail (ptype == MM_PORT_TYPE_UNKNOWN);<br>
> ptype = MM_PORT_TYPE_GPS;<br>
> }<br>
> + //TODO: This should be handled by uDev w/ cinterion-port-types.rules but I can't get<br>
> + //it to work. Added the rule in there to set the ENV variable TAG_CINTERION_GPS_PORT.<br>
> + //Once that is fixed, it will set the ENV var above & you can delete this else if.<br>
> + else if ((g_strcmp0 (g_udev_device_get_property(<wbr>mm_port_probe_peek_port (probe),"ID_USB_INTERFACE_NUM"<wbr>),"04") == 0) &&<br>
> + (g_strcmp0 (g_udev_device_get_property(<wbr>mm_port_probe_peek_port (probe),"ID_MODEL_ID"),"0061") == 0)) {<br>
> + mm_dbg ("(%s/%s)' Port flagged as GPS",<br>
> + mm_port_probe_get_port_subsys (probe),<br>
> + mm_port_probe_get_port_name (probe));<br>
> + /* Not an AT port, but the port to grab GPS traces */<br>
> + g_warn_if_fail (ptype == MM_PORT_TYPE_UNKNOWN);<br>
> + ptype = MM_PORT_TYPE_GPS;<br>
<br>
Ignore the GPS flag for now in this patch.<br>
<br>
> + }<br>
> +<br>
> + /* udev info for PLS8-X of GPS Port, for troubleshooting above<br>
> + looking at device '/devices/pci0000:00/0000:00:<wbr>14.0/usb3/3-2/3-2:1.4/tty/<wbr>ttyACM2':<br>
> + KERNEL=="ttyACM2"<br>
> + SUBSYSTEM=="tty"<br>
> + DRIVER==""<br>
> +<br>
> + looking at parent device '/devices/pci0000:00/0000:00:<wbr>14.0/usb3/3-2/3-2:1.4':<br>
> + KERNELS=="3-2:1.4"<br>
> + SUBSYSTEMS=="usb"<br>
> + DRIVERS=="cdc_acm"<br>
> + ATTRS{bInterfaceClass}=="02"<br>
> + ATTRS{iad_bFunctionClass}=="<wbr>02"<br>
> + ATTRS{bmCapabilities}=="2"<br>
> + ATTRS{iad_bFirstInterface}=="<wbr>04"<br>
> + ATTRS{bInterfaceSubClass}=="<wbr>02"<br>
> + ATTRS{bInterfaceProtocol}=="<wbr>01"<br>
> + ATTRS{bNumEndpoints}=="01"<br>
> + ATTRS{iad_bFunctionSubClass}==<wbr>"02"<br>
> + ATTRS{iad_bFunctionProtocol}==<wbr>"01"<br>
> + ATTRS{supports_autosuspend}=="<wbr>1"<br>
> + ATTRS{iad_bInterfaceCount}=="<wbr>02"<br>
> + ATTRS{bAlternateSetting}==" 0"<br>
> + ATTRS{bInterfaceNumber}=="04"<br>
> + ATTRS{interface}=="CDC Abstract Control Model (ACM)"<br>
> +<br>
> + looking at parent device '/devices/pci0000:00/0000:00:<wbr>14.0/usb3/3-2':<br>
> + KERNELS=="3-2"<br>
> + SUBSYSTEMS=="usb"<br>
> + DRIVERS=="usb"<br>
> + ATTRS{bDeviceSubClass}=="00"<br>
> + ATTRS{bDeviceProtocol}=="00"<br>
> + ATTRS{devpath}=="2"<br>
> + ATTRS{idVendor}=="1e2d"<br>
> + ATTRS{speed}=="480"<br>
> + ATTRS{bNumInterfaces}=="14"<br>
> + ATTRS{bConfigurationValue}=="<wbr>1"<br>
> + ATTRS{bMaxPacketSize0}=="64"<br>
> + ATTRS{busnum}=="3"<br>
> + ATTRS{devnum}=="30"<br>
> + ATTRS{configuration}==""<br>
> + ATTRS{bMaxPower}=="20mA"<br>
> + ATTRS{authorized}=="1"<br>
> + ATTRS{bmAttributes}=="e0"<br>
> + ATTRS{bNumConfigurations}=="1"<br>
> + ATTRS{maxchild}=="0"<br>
> + ATTRS{bcdDevice}=="0232"<br>
> + ATTRS{avoid_reset_quirk}=="0"<br>
> + ATTRS{quirks}=="0x0"<br>
> + ATTRS{version}==" 2.00"<br>
> + ATTRS{urbnum}=="3395"<br>
> + ATTRS{ltm_capable}=="no"<br>
> + ATTRS{manufacturer}=="<wbr>Cinterion"<br>
> + ATTRS{removable}=="removable"<br>
> + ATTRS{idProduct}=="0061"<br>
> + ATTRS{bDeviceClass}=="00"<br>
> + ATTRS{product}=="LTE Modem"<br>
> +<br>
<br>
We don't need your usb host controller details... :)<br>
<br>
> + looking at parent device '/devices/pci0000:00/0000:00:<wbr>14.0/usb3':<br>
> + KERNELS=="usb3"<br>
> + SUBSYSTEMS=="usb"<br>
> + DRIVERS=="usb"<br>
> + ATTRS{bDeviceSubClass}=="00"<br>
> + ATTRS{bDeviceProtocol}=="01"<br>
> + ATTRS{devpath}=="0"<br>
> + ATTRS{idVendor}=="1d6b"<br>
> + ATTRS{speed}=="480"<br>
> + ATTRS{bNumInterfaces}==" 1"<br>
> + ATTRS{bConfigurationValue}=="<wbr>1"<br>
> + ATTRS{bMaxPacketSize0}=="64"<br>
> + ATTRS{authorized_default}=="1"<br>
> + ATTRS{busnum}=="3"<br>
> + ATTRS{devnum}=="1"<br>
> + ATTRS{configuration}==""<br>
> + ATTRS{bMaxPower}=="0mA"<br>
> + ATTRS{authorized}=="1"<br>
> + ATTRS{bmAttributes}=="e0"<br>
> + ATTRS{bNumConfigurations}=="1"<br>
> + ATTRS{maxchild}=="4"<br>
> + ATTRS{bcdDevice}=="0313"<br>
> + ATTRS{avoid_reset_quirk}=="0"<br>
> + ATTRS{quirks}=="0x0"<br>
> + ATTRS{serial}=="0000:00:14.0"<br>
> + ATTRS{version}==" 2.00"<br>
> + ATTRS{urbnum}=="516"<br>
> + ATTRS{ltm_capable}=="no"<br>
> + ATTRS{manufacturer}=="Linux 3.13.0-48-generic xhci_hcd"<br>
> + ATTRS{removable}=="unknown"<br>
> + ATTRS{idProduct}=="0002"<br>
> + ATTRS{bDeviceClass}=="09"<br>
> + ATTRS{product}=="xHCI Host Controller"<br>
> +<br>
> + looking at parent device '/devices/pci0000:00/0000:00:<wbr>14.0':<br>
> + KERNELS=="0000:00:14.0"<br>
> + SUBSYSTEMS=="pci"<br>
> + DRIVERS=="xhci_hcd"<br>
> + ATTRS{irq}=="42"<br>
> + ATTRS{subsystem_vendor}=="<wbr>0x1028"<br>
> + ATTRS{broken_parity_status}=="<wbr>0"<br>
> + ATTRS{class}=="0x0c0330"<br>
> + ATTRS{consistent_dma_mask_<wbr>bits}=="64"<br>
> + ATTRS{dma_mask_bits}=="64"<br>
> + ATTRS{local_cpus}=="00000000,<wbr>00000000,00000000,00000000,<wbr>00000000,00000000,00000000,<wbr>000000ff"<br>
> + ATTRS{device}=="0x1e31"<br>
> + ATTRS{enable}=="1"<br>
> + ATTRS{msi_bus}==""<br>
> + ATTRS{local_cpulist}=="0-7"<br>
> + ATTRS{vendor}=="0x8086"<br>
> + ATTRS{subsystem_device}=="<wbr>0x0577"<br>
> + ATTRS{numa_node}=="-1"<br>
> + ATTRS{d3cold_allowed}=="1"<br>
> +<br>
> + looking at parent device '/devices/pci0000:00':<br>
> + KERNELS=="pci0000:00"<br>
> + SUBSYSTEMS==""<br>
> + DRIVERS==""<br>
> + */<br>
><br>
> return mm_base_modem_grab_port (modem,<br>
> mm_port_probe_get_port_subsys (probe),<br>
> diff --git a/src/mm-base-modem.c b/src/mm-base-modem.c<br>
> index 9cd7c5f..449dddc 100644<br>
> --- a/src/mm-base-modem.c<br>
> +++ b/src/mm-base-modem.c<br>
> @@ -674,6 +674,26 @@ mm_base_modem_peek_best_data_<wbr>port (MMBaseModem *self,<br>
> return NULL;<br>
> }<br>
><br>
> +MMPort *<br>
> +mm_base_modem_peek_current_<wbr>data_port (MMBaseModem *self,<br>
> + MMPortType type)<br>
> +{<br>
> + GList *l;<br>
> +<br>
> + g_return_val_if_fail (MM_IS_BASE_MODEM (self), NULL);<br>
> +<br>
> + /* Return first connected data port */<br>
> + for (l = self->priv->data; l; l = g_list_next (l)) {<br>
> + if (mm_port_get_connected ((MMPort *)l->data) &&<br>
> + (mm_port_get_port_type ((MMPort *)l->data) == type ||<br>
> + type == MM_PORT_TYPE_UNKNOWN)) {<br>
> + return (MMPort *)l->data;<br>
> + }<br>
> + }<br>
> +<br>
> + return NULL;<br>
> +}<br>
> +<br>
> GList *<br>
> mm_base_modem_get_data_ports (MMBaseModem *self)<br>
> {<br>
> diff --git a/src/mm-base-modem.h b/src/mm-base-modem.h<br>
> index 3c0d16f..bbea52d 100644<br>
> --- a/src/mm-base-modem.h<br>
> +++ b/src/mm-base-modem.h<br>
> @@ -131,6 +131,7 @@ MMPortMbim *mm_base_modem_peek_port_mbim_<wbr>for_data (MMBaseModem *self, MMPo<br>
> #endif<br>
> MMPortSerialAt *mm_base_modem_peek_best_at_<wbr>port (MMBaseModem *self, GError **error);<br>
> MMPort *mm_base_modem_peek_best_data_<wbr>port (MMBaseModem *self, MMPortType type);<br>
> +MMPort *mm_base_modem_peek_current_<wbr>data_port (MMBaseModem *self, MMPortType type);<br>
> GList *mm_base_modem_peek_data_ports (MMBaseModem *self);<br>
><br>
> MMPortSerialAt *mm_base_modem_get_port_<wbr>primary (MMBaseModem *self);<br>
<span class="HOEnZb"><font color="#888888">> --<br>
> 1.9.1<br>
><br>
><br>
<br>
<br>
<br>
--<br>
Aleksander<br>
<a href="https://aleksander.es" rel="noreferrer" target="_blank">https://aleksander.es</a><br>
</font></span></blockquote></div><br></div>