[PATCH] ublox: wait for READY URCs during port probing
Dan Williams
dcbw at redhat.com
Wed Jan 3 15:49:10 UTC 2018
On Mon, 2017-12-25 at 20:02 +0100, Aleksander Morgado wrote:
> The AT control TTYs in the u-blox modems may take some time to be
> usable. In order to handle this issue, we configured some longer
> timeouts during AT probing, but that may not be always enough.
>
> The u-blox TTYs will report readiness via a "+AT: READY" URC, which
> we can use during custom initialization to decide right away that the
> port is AT. We use up to 20s as that is close to the worst case seen
> during experimentation, happening after the module undergoes a full
> NVM reset. If the timeout is reached without receiving the URC, we
> still run standard AT probing afterwards. This new logic just tries
> to make it sure we don't do any probing before the module is ready to
> accept it.
>
> If the module hasn't been hotplugged (i.e. it was already there when
> ModemManager started) we do a quick first AT probing and if that
> fails
> we run the "+AT: READY" URC wait as if it was hotplugged.
LGTM
Dan
> ---
> plugins/ublox/mm-plugin-ublox.c | 205
> +++++++++++++++++++++++++++++++++++++---
> 1 file changed, 191 insertions(+), 14 deletions(-)
>
> diff --git a/plugins/ublox/mm-plugin-ublox.c b/plugins/ublox/mm-
> plugin-ublox.c
> index 13d95816..5ee09a20 100644
> --- a/plugins/ublox/mm-plugin-ublox.c
> +++ b/plugins/ublox/mm-plugin-ublox.c
> @@ -20,6 +20,7 @@
> #include <libmm-glib.h>
>
> #include "mm-log.h"
> +#include "mm-serial-parsers.h"
> #include "mm-broadband-modem-ublox.h"
> #include "mm-plugin-ublox.h"
>
> @@ -28,19 +29,6 @@ G_DEFINE_TYPE (MMPluginUblox, mm_plugin_ublox,
> MM_TYPE_PLUGIN)
> MM_PLUGIN_DEFINE_MAJOR_VERSION
> MM_PLUGIN_DEFINE_MINOR_VERSION
>
> -/*******************************************************************
> **********/
> -/* Custom commands for AT probing */
> -
> -/* Increase the response timeout for probe commands since the TOBY-
> L2 modem
> - * takes longer to respond after a reset.
> - */
> -static const MMPortProbeAtCommand custom_at_probe[] = {
> - { "AT", 7, mm_port_probe_response_processor_is_at },
> - { "AT", 7, mm_port_probe_response_processor_is_at },
> - { "AT", 7, mm_port_probe_response_processor_is_at },
> - { NULL }
> -};
> -
> /*******************************************************************
> **********/
>
> static MMBaseModem *
> @@ -87,6 +75,191 @@ grab_port (MMPlugin *self,
> return mm_base_modem_grab_port (modem, port, port_type, pflags,
> error);
> }
>
> +/*******************************************************************
> **********/
> +/* Custom init context */
> +
> +/* Wait up to 20s for the +READY URC */
> +#define READY_WAIT_TIME_SECS 20
> +
> +typedef struct {
> + MMPortSerialAt *port;
> + GRegex *ready_regex;
> + guint timeout_id;
> +} CustomInitContext;
> +
> +static void
> +custom_init_context_free (CustomInitContext *ctx)
> +{
> + g_assert (!ctx->timeout_id);
> + g_regex_unref (ctx->ready_regex);
> + g_object_unref (ctx->port);
> + g_slice_free (CustomInitContext, ctx);
> +}
> +
> +static gboolean
> +ublox_custom_init_finish (MMPortProbe *probe,
> + GAsyncResult *result,
> + GError **error)
> +{
> + return g_task_propagate_boolean (G_TASK (result), error);
> +}
> +
> +static gboolean
> +ready_timeout (GTask *task)
> +{
> + CustomInitContext *ctx;
> + MMPortProbe *probe;
> +
> + ctx = g_task_get_task_data (task);
> + probe = g_task_get_source_object (task);
> +
> + ctx->timeout_id = 0;
> +
> + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx-
> >ready_regex,
> + NULL, NULL,
> NULL);
> +
> + mm_dbg ("(%s/%s) timed out waiting for READY unsolicited
> message",
> + mm_port_probe_get_port_subsys (probe),
> + mm_port_probe_get_port_name (probe));
> +
> + /* not an error really, we didn't probe anything yet, that's all
> */
> + g_task_return_boolean (task, TRUE);
> + g_object_unref (task);
> +
> + return G_SOURCE_REMOVE;
> +}
> +
> +static void
> +ready_received (MMPortSerialAt *port,
> + GMatchInfo *info,
> + GTask *task)
> +{
> + CustomInitContext *ctx;
> + MMPortProbe *probe;
> +
> + ctx = g_task_get_task_data (task);
> + probe = g_task_get_source_object (task);
> +
> + g_source_remove (ctx->timeout_id);
> + ctx->timeout_id = 0;
> +
> + mm_dbg ("(%s/%s) READY received: port is AT",
> + mm_port_probe_get_port_subsys (probe),
> + mm_port_probe_get_port_name (probe));
> +
> + /* Flag as an AT port right away */
> + mm_port_probe_set_result_at (probe, TRUE);
> +
> + g_task_return_boolean (task, TRUE);
> + g_object_unref (task);
> +}
> +
> +static void
> +wait_for_ready (GTask *task)
> +{
> + CustomInitContext *ctx;
> + MMPortProbe *probe;
> +
> + ctx = g_task_get_task_data (task);
> + probe = g_task_get_source_object (task);
> +
> + mm_dbg ("(%s/%s) waiting for READY unsolicited message...",
> + mm_port_probe_get_port_subsys (probe),
> + mm_port_probe_get_port_name (probe));
> +
> + /* Configure a regex on the TTY, so that we stop the custom init
> + * as soon as +READY URC is received */
> + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port,
> + ctx->ready_regex,
> + (MMPortSerialAtUn
> solicitedMsgFn) ready_received,
> + task,
> + NULL);
> +
> + /* Otherwise, let the custom init timeout in some seconds. */
> + ctx->timeout_id = g_timeout_add_seconds (READY_WAIT_TIME_SECS,
> (GSourceFunc) ready_timeout, task);
> +}
> +
> +static void
> +quick_at_ready (MMPortSerialAt *port,
> + GAsyncResult *res,
> + GTask *task)
> +{
> + CustomInitContext *ctx;
> + MMPortProbe *probe;
> + const gchar *response;
> + GError *error = NULL;
> +
> + ctx = g_task_get_task_data (task);
> + probe = g_task_get_source_object (task);
> +
> + response = mm_port_serial_at_command_finish (port, res, &error);
> + if (error) {
> + /* On a timeout error, wait for READY URC */
> + if (g_error_matches (error, MM_SERIAL_ERROR,
> MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
> + wait_for_ready (task);
> + goto out;
> + }
> + /* On an unknown error, make it fatal */
> + if (!mm_serial_parser_v1_is_known_error (error)) {
> + mm_warn ("(%s/%s) custom port initialization logic
> failed: %s",
> + mm_port_probe_get_port_subsys (probe),
> + mm_port_probe_get_port_name (probe),
> + error->message);
> + goto out_complete;
> + }
> + }
> +
> + mm_dbg ("(%s/%s) port is AT",
> + mm_port_probe_get_port_subsys (probe),
> + mm_port_probe_get_port_name (probe));
> + mm_port_probe_set_result_at (probe, TRUE);
> +
> +out_complete:
> + g_task_return_boolean (task, TRUE);
> + g_object_unref (task);
> +out:
> + g_clear_error (&error);
> +}
> +
> +static void
> +ublox_custom_init (MMPortProbe *probe,
> + MMPortSerialAt *port,
> + GCancellable *cancellable,
> + GAsyncReadyCallback callback,
> + gpointer user_data)
> +{
> + GTask *task;
> + CustomInitContext *ctx;
> +
> + task = g_task_new (probe, cancellable, callback, user_data);
> +
> + ctx = g_slice_new0 (CustomInitContext);
> + ctx->port = g_object_ref (port);
> + ctx->ready_regex = g_regex_new ("\\r\\n\\+AT:\\s*READY\\r\\n",
> + G_REGEX_RAW | G_REGEX_OPTIMIZE,
> 0, NULL);
> + g_task_set_task_data (task, ctx, (GDestroyNotify)
> custom_init_context_free);
> +
> + /* If the device hasn't been plugged in right away, we assume it
> was already
> + * running for some time. We validate the assumption with a
> quick AT probe,
> + * and if it times out, we run the explicit READY wait from
> scratch (e.g.
> + * to cope with the case where MM starts after the TTY has been
> exposed but
> + * where the device was also just reseted) */
> + if (!mm_device_get_hotplugged (mm_port_probe_peek_device
> (probe))) {
> + mm_port_serial_at_command (ctx->port,
> + "AT",
> + 1,
> + FALSE, /* raw */
> + FALSE, /* allow_cached */
> + g_task_get_cancellable (task),
> + (GAsyncReadyCallback)quick_at_rea
> dy,
> + task);
> + return;
> + }
> +
> + /* Device hotplugged, wait for READY URC */
> + wait_for_ready (task);
> +}
> +
> /*******************************************************************
> **********/
>
> G_MODULE_EXPORT MMPlugin *
> @@ -95,6 +268,10 @@ mm_plugin_create (void)
> static const gchar *subsystems[] = { "tty", "net", NULL };
> static const guint16 vendor_ids[] = { 0x1546, 0 };
> static const gchar *vendor_strings[] = { "u-blox", NULL };
> + static const MMAsyncMethod custom_init = {
> + .async = G_CALLBACK (ublox_custom_init),
> + .finish = G_CALLBACK (ublox_custom_init_finish),
> + };
>
> return MM_PLUGIN (g_object_new (MM_TYPE_PLUGIN_UBLOX,
> MM_PLUGIN_NAME,
> "u-blox",
> @@ -102,8 +279,8 @@ mm_plugin_create (void)
> MM_PLUGIN_ALLOWED_VENDOR_IDS,
> vendor_ids,
> MM_PLUGIN_ALLOWED_VENDOR_STRINGS
> , vendor_strings,
> MM_PLUGIN_ALLOWED_AT,
> TRUE,
> - MM_PLUGIN_CUSTOM_AT_PROBE,
> custom_at_probe,
> MM_PLUGIN_SEND_DELAY,
> (guint64) 0,
> + MM_PLUGIN_CUSTOM_INIT,
> &custom_init,
> NULL));
> }
>
More information about the ModemManager-devel
mailing list