aboutsummaryrefslogtreecommitdiff
path: root/src/mm-plugin-manager.c
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2016-01-11 19:46:25 +0100
committerAleksander Morgado <aleksander@aleksander.es>2016-03-09 23:59:46 +0100
commitfd4fdbf21b20983cdd46fc925389dd96ca3a99d3 (patch)
tree64e61dcfdbf201f86658ad26ead0ec133401147b /src/mm-plugin-manager.c
parentbe9b5e0141bbebdff0694a6a664e1074448c3618 (diff)
plugin-manager: refactor device/port support checks and allow cancellations
Diffstat (limited to 'src/mm-plugin-manager.c')
-rw-r--r--src/mm-plugin-manager.c1732
1 files changed, 1179 insertions, 553 deletions
diff --git a/src/mm-plugin-manager.c b/src/mm-plugin-manager.c
index 187c0159..636af09b 100644
--- a/src/mm-plugin-manager.c
+++ b/src/mm-plugin-manager.c
@@ -29,16 +29,6 @@
#include "mm-plugin.h"
#include "mm-log.h"
-/* Default time to defer probing checks */
-#define DEFER_TIMEOUT_SECS 3
-
-/* Time to wait for ports to appear before starting to probe the first one */
-#define MIN_WAIT_TIME_MSECS 1500
-
-/* Time to wait for other ports to appear once the first port is exposed
- * (needs to be > MIN_WAIT_TIME_MSECS!!) */
-#define MIN_PROBING_TIME_MSECS 2500
-
static void initable_iface_init (GInitableIface *iface);
G_DEFINE_TYPE_EXTENDED (MMPluginManager, mm_plugin_manager, G_TYPE_OBJECT, 0,
@@ -61,286 +51,253 @@ struct _MMPluginManagerPrivate {
GList *plugins;
/* Last, the generic plugin. */
MMPlugin *generic;
+
+ /* List of ongoing device support checks */
+ GList *device_contexts;
};
/*****************************************************************************/
-/* Look for plugin */
+/* Build plugin list for a single port */
-MMPlugin *
-mm_plugin_manager_peek_plugin (MMPluginManager *self,
- const gchar *plugin_name)
+static GList *
+plugin_manager_build_plugins_list (MMPluginManager *self,
+ MMDevice *device,
+ GUdevDevice *port)
{
+ GList *list = NULL;
GList *l;
+ gboolean supported_found = FALSE;
- if (self->priv->generic && g_str_equal (plugin_name, mm_plugin_get_name (self->priv->generic)))
- return self->priv->generic;
-
- for (l = self->priv->plugins; l; l = g_list_next (l)) {
- MMPlugin *plugin = MM_PLUGIN (l->data);
+ for (l = self->priv->plugins; l && !supported_found; l = g_list_next (l)) {
+ MMPluginSupportsHint hint;
- if (g_str_equal (plugin_name, mm_plugin_get_name (plugin)))
- return plugin;
+ hint = mm_plugin_discard_port_early (MM_PLUGIN (l->data), device, port);
+ switch (hint) {
+ case MM_PLUGIN_SUPPORTS_HINT_UNSUPPORTED:
+ /* Fully discard */
+ break;
+ case MM_PLUGIN_SUPPORTS_HINT_MAYBE:
+ /* Maybe supported, add to tail of list */
+ list = g_list_append (list, g_object_ref (l->data));
+ break;
+ case MM_PLUGIN_SUPPORTS_HINT_LIKELY:
+ /* Likely supported, add to head of list */
+ list = g_list_prepend (list, g_object_ref (l->data));
+ break;
+ case MM_PLUGIN_SUPPORTS_HINT_SUPPORTED:
+ /* Really supported, clean existing list and add it alone */
+ if (list) {
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+ list = NULL;
+ }
+ list = g_list_prepend (list, g_object_ref (l->data));
+ /* This will end the loop as well */
+ supported_found = TRUE;
+ break;
+ default:
+ g_assert_not_reached();
+ }
}
- return NULL;
+ /* Add the generic plugin at the end of the list */
+ if (self->priv->generic)
+ list = g_list_append (list, g_object_ref (self->priv->generic));
+
+ return list;
}
/*****************************************************************************/
-/* Find device support */
+/* Common context for async operations
+ *
+ * The DeviceContext and PortContext structs are not proper objects, and that
+ * means that they cannot be given as core parameter of GIO async results.
+ * Instead, we'll use the MMPluginManager as that core parameter always, and
+ * we'll pass along a common context with all the remaining details as user
+ * data.
+ */
+
+typedef struct _DeviceContext DeviceContext;
+typedef struct _PortContext PortContext;
+
+static DeviceContext *device_context_ref (DeviceContext *device_context);
+static void device_context_unref (DeviceContext *device_context);
+static PortContext *port_context_ref (PortContext *port_context);
+static void port_context_unref (PortContext *port_context);
typedef struct {
MMPluginManager *self;
- MMDevice *device;
- GSimpleAsyncResult *result;
- GTimer *timer;
- guint timeout_id;
- gulong grabbed_id;
- gulong released_id;
+ DeviceContext *device_context;
+ PortContext *port_context;
+ GTask *task;
+} CommonAsyncContext;
- gboolean min_wait_timeout_expired;
- GList *running_probes;
-} FindDeviceSupportContext;
+static void
+common_async_context_free (CommonAsyncContext *common)
+{
+ if (common->port_context)
+ port_context_unref (common->port_context);
+ if (common->device_context)
+ device_context_unref (common->device_context);
+ if (common->self)
+ g_object_unref (common->self);
+ if (common->task)
+ g_object_unref (common->task);
+ g_slice_free (CommonAsyncContext, common);
+}
-typedef struct {
- FindDeviceSupportContext *parent_ctx;
+static CommonAsyncContext *
+common_async_context_new (MMPluginManager *self,
+ DeviceContext *device_context,
+ PortContext *port_context,
+ GTask *task)
+{
+ CommonAsyncContext *common;
+
+ common = g_slice_new0 (CommonAsyncContext);
+ common->self = (self ? g_object_ref (self) : NULL);
+ common->device_context = (device_context ? device_context_ref (device_context) : NULL);
+ common->port_context = (port_context ? port_context_ref (port_context) : NULL);
+ common->task = (task ? g_object_ref (task) : NULL);
+ return common;
+}
+
+/*****************************************************************************/
+/* Port context */
+
+/* Default time to defer probing checks */
+#define DEFER_TIMEOUT_SECS 3
+
+/*
+ * Port context
+ *
+ * This structure hold all the probing information related to a single port.
+ */
+struct _PortContext {
+ /* Reference counting */
+ volatile gint ref_count;
+ /* The name of the context */
+ gchar *name;
+ /* The device where the port is*/
+ MMDevice *device;
+ /* The GUDev reported port object */
GUdevDevice *port;
- guint initial_timeout_id;
+ /* The operation task */
+ GTask *task;
+ /* Internal ancellable */
+ GCancellable *cancellable;
+ /* Timer tracking how much time is required for the port support check */
+ GTimer *timer;
+
+ /* This list contains all the plugins that have to be tested with a given
+ * port. The list is created once when the task is started, and is never
+ * modified afterwards. */
GList *plugins;
+ /* This is the current plugin being tested. If NULL, there are no more
+ * plugins to try. */
GList *current;
+ /* A best plugin has been found for this port. */
MMPlugin *best_plugin;
+ /* A plugin was suggested for this port. */
MMPlugin *suggested_plugin;
+
+ /* The probe has been deferred */
guint defer_id;
+ /* The probe must be deferred until a result is suggested by other
+ * port probe results (e.g. for WWAN ports). */
gboolean defer_until_suggested;
-} PortProbeContext;
-
-static void port_probe_context_step (PortProbeContext *port_probe_ctx);
-static void suggest_port_probe_result (FindDeviceSupportContext *ctx,
- PortProbeContext *origin,
- MMPlugin *suggested_plugin);
+};
static void
-port_probe_context_free (PortProbeContext *ctx)
+port_context_unref (PortContext *port_context)
{
- g_assert (ctx->defer_id == 0);
-
- if (ctx->best_plugin)
- g_object_unref (ctx->best_plugin);
- if (ctx->suggested_plugin)
- g_object_unref (ctx->suggested_plugin);
- if (ctx->plugins)
- g_list_free_full (ctx->plugins, (GDestroyNotify)g_object_unref);
- g_object_unref (ctx->port);
- g_slice_free (PortProbeContext, ctx);
+ if (g_atomic_int_dec_and_test (&port_context->ref_count)) {
+ /* There must never be a deferred task scheduled for this port */
+ g_assert (port_context->defer_id == 0);
+
+ /* The port support check task must have been completed previously */
+ g_assert (!port_context->task);
+
+ if (port_context->best_plugin)
+ g_object_unref (port_context->best_plugin);
+ if (port_context->suggested_plugin)
+ g_object_unref (port_context->suggested_plugin);
+ if (port_context->plugins)
+ g_list_free_full (port_context->plugins, (GDestroyNotify) g_object_unref);
+ if (port_context->cancellable)
+ g_object_unref (port_context->cancellable);
+ g_free (port_context->name);
+ g_timer_destroy (port_context->timer);
+ g_object_unref (port_context->port);
+ g_object_unref (port_context->device);
+ g_slice_free (PortContext, port_context);
+ }
}
-static void
-find_device_support_context_complete_and_free (FindDeviceSupportContext *ctx)
+static PortContext *
+port_context_ref (PortContext *port_context)
{
- g_assert (ctx->timeout_id == 0);
-
- mm_dbg ("(Plugin Manager) [%s] device support check finished in '%lf' seconds",
- mm_device_get_path (ctx->device),
- g_timer_elapsed (ctx->timer, NULL));
- g_timer_destroy (ctx->timer);
-
- /* Set async operation result */
- if (!mm_device_peek_plugin (ctx->device)) {
- g_simple_async_result_set_error (ctx->result,
- MM_CORE_ERROR,
- MM_CORE_ERROR_UNSUPPORTED,
- "not supported by any plugin");
- } else {
- g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
- }
-
- g_simple_async_result_complete (ctx->result);
-
- g_signal_handler_disconnect (ctx->device, ctx->grabbed_id);
- g_signal_handler_disconnect (ctx->device, ctx->released_id);
-
- g_warn_if_fail (ctx->running_probes == NULL);
-
- g_object_unref (ctx->result);
- g_object_unref (ctx->device);
- g_object_unref (ctx->self);
- g_slice_free (FindDeviceSupportContext, ctx);
+ g_atomic_int_inc (&port_context->ref_count);
+ return port_context;
}
-gboolean
-mm_plugin_manager_find_device_support_finish (MMPluginManager *self,
- GAsyncResult *result,
- GError **error)
+static MMPlugin *
+port_context_run_finish (MMPluginManager *self,
+ GAsyncResult *res,
+ GError **error)
{
- return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+ return g_task_propagate_pointer (G_TASK (res), error);
}
static void
-port_probe_context_finished (PortProbeContext *port_probe_ctx)
+port_context_complete (PortContext *port_context)
{
- FindDeviceSupportContext *ctx = port_probe_ctx->parent_ctx;
- MMPlugin *device_plugin;
-
- /* Get info about the currently scheduled plugin in the device */
- device_plugin = (MMPlugin *)mm_device_peek_plugin (ctx->device);
-
- if (!port_probe_ctx->best_plugin) {
- /* If the port appeared after an already probed port, which decided that
- * the Generic plugin was the best one (which is by default not initially
- * suggested), we'll end up arriving here. Don't ignore it, it may well
- * be a wwan port that we do need to grab. */
- if (device_plugin) {
- mm_dbg ("(Plugin Manager) [%s] assuming port can be handled by the '%s' plugin",
- g_udev_device_get_name (port_probe_ctx->port),
- mm_plugin_get_name (device_plugin));
- } else {
- gboolean cancel_remaining;
- GList *l;
-
- mm_dbg ("(Plugin Manager) [%s] not supported by any plugin",
- g_udev_device_get_name (port_probe_ctx->port));
-
- /* Tell the device to ignore this port */
- mm_device_ignore_port (ctx->device, port_probe_ctx->port);
-
- /* If this is the last valid probe which was running (i.e. the last one
- * not being deferred-until-suggested), cancel all remaining ones. */
- cancel_remaining = TRUE;
- for (l = ctx->running_probes; l; l = g_list_next (l)) {
- PortProbeContext *other = l->data;
-
- /* Do not cancel anything if we find at least one probe which is not
- * waiting for the suggested plugin */
- if (other != port_probe_ctx && !other->defer_until_suggested) {
- cancel_remaining = FALSE;
- break;
- }
- }
+ GTask *task;
- if (cancel_remaining)
- /* Set a NULL suggested plugin, will cancel the probes */
- suggest_port_probe_result (ctx, port_probe_ctx, NULL);
- }
- } else {
- /* Notify the plugin to the device, if this is the first port probing
- * result we got.
- * Also, if the previously suggested plugin was the GENERIC one and now
- * we're reporting a more specific one, use the new one.
- */
- if (!device_plugin ||
- (g_str_equal (mm_plugin_get_name (device_plugin), MM_PLUGIN_GENERIC_NAME) &&
- device_plugin != port_probe_ctx->best_plugin)) {
- /* Only log best plugin if it's not the generic one */
- if (!g_str_equal (mm_plugin_get_name (port_probe_ctx->best_plugin), MM_PLUGIN_GENERIC_NAME))
- mm_dbg ("(Plugin Manager) (%s) [%s]: found best plugin for device (%s)",
- mm_plugin_get_name (port_probe_ctx->best_plugin),
- g_udev_device_get_name (port_probe_ctx->port),
- mm_device_get_path (ctx->device));
-
- mm_device_set_plugin (ctx->device, G_OBJECT (port_probe_ctx->best_plugin));
-
- /* Suggest this plugin also to other port probes */
- suggest_port_probe_result (ctx, port_probe_ctx, port_probe_ctx->best_plugin);
- }
- /* Warn if the best plugin found for this port differs from the
- * best plugin found for the the first probed port */
- else if (!g_str_equal (mm_plugin_get_name (device_plugin),
- mm_plugin_get_name (port_probe_ctx->best_plugin))) {
- /* Icera modems may not reply to the icera probing in all ports. We handle this by
- * checking the forbidden/allowed icera flags in both the current and the expected
- * plugins. If either of these plugins requires icera and the other doesn't, we
- * pick the Icera one as best plugin. */
- gboolean previous_forbidden_icera;
- gboolean previous_allowed_icera;
- gboolean new_forbidden_icera;
- gboolean new_allowed_icera;
-
- g_object_get (device_plugin,
- MM_PLUGIN_ALLOWED_ICERA, &previous_allowed_icera,
- MM_PLUGIN_FORBIDDEN_ICERA, &previous_forbidden_icera,
- NULL);
- g_assert (previous_allowed_icera == FALSE || previous_forbidden_icera == FALSE);
-
- g_object_get (port_probe_ctx->best_plugin,
- MM_PLUGIN_ALLOWED_ICERA, &new_allowed_icera,
- MM_PLUGIN_FORBIDDEN_ICERA, &new_forbidden_icera,
- NULL);
- g_assert (new_allowed_icera == FALSE || new_forbidden_icera == FALSE);
-
- if (previous_allowed_icera && new_forbidden_icera) {
- mm_warn ("(Plugin Manager) (%s): will use plugin '%s' instead of '%s', modem is Icera-capable",
- g_udev_device_get_name (port_probe_ctx->port),
- mm_plugin_get_name (MM_PLUGIN (mm_device_peek_plugin (ctx->device))),
- mm_plugin_get_name (port_probe_ctx->best_plugin));
- } else if (new_allowed_icera && previous_forbidden_icera) {
- mm_warn ("(Plugin Manager) (%s): overriding previously selected device plugin '%s' with '%s', modem is Icera-capable",
- g_udev_device_get_name (port_probe_ctx->port),
- mm_plugin_get_name (MM_PLUGIN (mm_device_peek_plugin (ctx->device))),
- mm_plugin_get_name (port_probe_ctx->best_plugin));
- mm_device_set_plugin (ctx->device, G_OBJECT (port_probe_ctx->best_plugin));
- } else {
- mm_warn ("(Plugin Manager) (%s): plugin mismatch error (expected: '%s', got: '%s')",
- g_udev_device_get_name (port_probe_ctx->port),
- mm_plugin_get_name (MM_PLUGIN (mm_device_peek_plugin (ctx->device))),
- mm_plugin_get_name (port_probe_ctx->best_plugin));
- }
- }
- }
+ /* Steal the task from the task */
+ g_assert (port_context->task);
+ task = port_context->task;
+ port_context->task = NULL;
- /* Remove us from the list of running probes */
- g_assert (g_list_find (ctx->running_probes, port_probe_ctx) != NULL);
- ctx->running_probes = g_list_remove (ctx->running_probes, port_probe_ctx);
+ /* Log about the time required to complete the checks */
+ mm_dbg ("[plugin manager] task %s: finished in '%lf' seconds",
+ port_context->name, g_timer_elapsed (port_context->timer, NULL));
- /* If there are running probes around, wait for them to finish */
- if (ctx->running_probes != NULL) {
- GList *l;
- GString *s = NULL;
- guint i = 0;
+ if (!port_context->best_plugin)
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Unsupported");
+ else
+ g_task_return_pointer (task, g_object_ref (port_context->best_plugin), g_object_unref);
+ g_object_unref (task);
+}
- for (l = ctx->running_probes; l; l = g_list_next (l)) {
- const gchar *portname = g_udev_device_get_name (((PortProbeContext *)l->data)->port);
+static void port_context_next (PortContext *port_context);
- if (!s)
- s = g_string_new (portname);
- else
- g_string_append_printf (s, ", %s", portname);
- i++;
- }
+static void
+port_context_supported (PortContext *port_context,
+ MMPlugin *plugin)
+{
+ g_assert (plugin);
- mm_dbg ("(Plugin Manager) '%s' port probe finished, still %u running probes in this device (%s)",
- g_udev_device_get_name (port_probe_ctx->port), i, s->str);
- g_string_free (s, TRUE);
- }
- /* If we didn't use the minimum probing time, wait for it to finish */
- else if (ctx->timeout_id > 0) {
- mm_dbg ("(Plugin Manager) '%s' port probe finished, last one in device, "
- "but minimum probing time not consumed yet ('%lf' seconds elapsed)",
- g_udev_device_get_name (port_probe_ctx->port),
- g_timer_elapsed (ctx->timer, NULL));
- } else {
- mm_dbg ("(Plugin Manager) '%s' port probe finished, last one in device",
- g_udev_device_get_name (port_probe_ctx->port));
- /* If we just finished the last running probe, we can now finish the device
- * support check */
- find_device_support_context_complete_and_free (ctx);
- }
+ mm_dbg ("[plugin manager] task %s: found best plugin for port (%s)",
+ port_context->name, mm_plugin_get_name (plugin));
- port_probe_context_free (port_probe_ctx);
+ /* Found a best plugin, store it to return it */
+ port_context->best_plugin = g_object_ref (plugin);
+ port_context_complete (port_context);
}
static gboolean
-deferred_support_check_idle (PortProbeContext *port_probe_ctx)
+port_context_defer_ready (PortContext *port_context)
{
- port_probe_ctx->defer_id = 0;
- port_probe_context_step (port_probe_ctx);
+ port_context->defer_id = 0;
+ port_context_next (port_context);
return G_SOURCE_REMOVE;
}
static void
-suggest_single_port_probe_result (PortProbeContext *target_port_probe_ctx,
- MMPlugin *suggested_plugin,
- gboolean reschedule_deferred)
+port_context_set_suggestion (PortContext *port_context,
+ MMPlugin *suggested_plugin)
{
gboolean forbidden_icera;
@@ -353,36 +310,32 @@ suggest_single_port_probe_result (PortProbeContext *target_port_probe_ctx,
* the deferred until suggested probes get finished.
*/
- if (target_port_probe_ctx->best_plugin || target_port_probe_ctx->suggested_plugin)
+ /* Do nothing if already best plugin found, or if a plugin has already been
+ * suggested before */
+ if (port_context->best_plugin || port_context->suggested_plugin)
return;
/* Complete tasks which were deferred until suggested */
- if (target_port_probe_ctx->defer_until_suggested) {
+ if (port_context->defer_until_suggested) {
/* Reset the defer until suggested flag; we consider this
* cancelled probe completed now. */
- target_port_probe_ctx->defer_until_suggested = FALSE;
+ port_context->defer_until_suggested = FALSE;
if (suggested_plugin) {
- mm_dbg ("(Plugin Manager) (%s) [%s] deferred task completed, got suggested plugin",
- mm_plugin_get_name (suggested_plugin),
- g_udev_device_get_name (target_port_probe_ctx->port));
+ mm_dbg ("[plugin manager] task %s: deferred task completed, got suggested plugin (%s)",
+ port_context->name, mm_plugin_get_name (suggested_plugin));
/* Advance to the suggested plugin and re-check support there */
- target_port_probe_ctx->suggested_plugin = g_object_ref (suggested_plugin);
- target_port_probe_ctx->current = g_list_find (target_port_probe_ctx->current,
- target_port_probe_ctx->suggested_plugin);
- } else {
- mm_dbg ("(Plugin Manager) [%s] deferred task cancelled, no suggested plugin",
- g_udev_device_get_name (target_port_probe_ctx->port));
- target_port_probe_ctx->best_plugin = NULL;
- target_port_probe_ctx->current = NULL;
+ port_context->suggested_plugin = g_object_ref (suggested_plugin);
+ port_context->current = g_list_find (port_context->current, port_context->suggested_plugin);
+ /* Schedule checking support */
+ g_assert (port_context->defer_id == 0);
+ port_context->defer_id = g_idle_add ((GSourceFunc) port_context_defer_ready, port_context);
+ return;
}
- /* Schedule checking support, which will end the operation */
- if (reschedule_deferred) {
- g_assert (target_port_probe_ctx->defer_id == 0);
- target_port_probe_ctx->defer_id = g_idle_add ((GSourceFunc)deferred_support_check_idle,
- target_port_probe_ctx);
- }
+ mm_dbg ("[plugin manager] task %s: deferred task completed, no suggested plugin",
+ port_context->name);
+ port_context_complete (port_context);
return;
}
@@ -409,403 +362,1076 @@ suggest_single_port_probe_result (PortProbeContext *target_port_probe_ctx,
* should run its probing independently, and we'll later decide
* which result applies to the whole device.
*/
- mm_dbg ("(Plugin Manager) (%s) [%s] suggested plugin for port",
- mm_plugin_get_name (suggested_plugin),
- g_udev_device_get_name (target_port_probe_ctx->port));
- target_port_probe_ctx->suggested_plugin = g_object_ref (suggested_plugin);
+ mm_dbg ("[plugin manager] task %s: got suggested plugin (%s)",
+ port_context->name, mm_plugin_get_name (suggested_plugin));
+ port_context->suggested_plugin = g_object_ref (suggested_plugin);
}
static void
-suggest_port_probe_result (FindDeviceSupportContext *ctx,
- PortProbeContext *origin,
- MMPlugin *suggested_plugin)
+port_context_unsupported (PortContext *port_context,
+ MMPlugin *plugin)
{
- GList *l;
+ g_assert (plugin);
- for (l = ctx->running_probes; l; l = g_list_next (l)) {
- PortProbeContext *port_probe_ctx = l->data;
+ /* If there is no suggested plugin, go on to the next one */
+ if (!port_context->suggested_plugin) {
+ port_context->current = g_list_next (port_context->current);
+ port_context_next (port_context);
+ return;
+ }
- if (port_probe_ctx != origin)
- suggest_single_port_probe_result (port_probe_ctx, suggested_plugin, TRUE);
+ /* If the plugin that just completed the support check claims
+ * not to support this port, but this plugin is clearly the
+ * right plugin since it claimed this port's physical modem,
+ * just cancel the port probing and avoid more tests.
+ */
+ if (port_context->suggested_plugin == plugin) {
+ mm_dbg ("[plugin manager] task %s: ignoring port unsupported by physical modem's plugin",
+ port_context->name);
+ port_context_complete (port_context);
+ return;
}
+
+ /* The last plugin we tried is NOT the one we got suggested, so
+ * directly check support with the suggested plugin. If we
+ * already checked its support, it won't be checked again. */
+ port_context->current = g_list_find (port_context->current, port_context->suggested_plugin);
+ port_context_next (port_context);
+}
+
+static void
+port_context_defer (PortContext *port_context)
+{
+ /* Try with the suggested one after being deferred */
+ if (port_context->suggested_plugin) {
+ mm_dbg ("[plugin manager] task %s: deferring support check (%s suggested)",
+ port_context->name, mm_plugin_get_name (MM_PLUGIN (port_context->suggested_plugin)));
+ port_context->current = g_list_find (port_context->current, port_context->suggested_plugin);
+ } else
+ mm_dbg ("[plugin manager] task %s: deferring support check",
+ port_context->name);
+
+ /* Schedule checking support.
+ *
+ * In this case we don't pass a port context reference because we're able
+ * to fully cancel the timeout ourselves. */
+ port_context->defer_id = g_timeout_add_seconds (DEFER_TIMEOUT_SECS,
+ (GSourceFunc) port_context_defer_ready,
+ port_context);
}
static void
-plugin_supports_port_ready (MMPlugin *plugin,
- GAsyncResult *result,
- PortProbeContext *port_probe_ctx)
+port_context_defer_until_suggested (PortContext *port_context,
+ MMPlugin *plugin)
{
- MMPluginSupportsResult support_result;
- GError *error = NULL;
+ g_assert (plugin);
+
+ /* If we arrived here and we already have a plugin suggested, use it */
+ if (port_context->suggested_plugin) {
+ /* We can finish this context */
+ if (port_context->suggested_plugin == plugin) {
+ mm_dbg ("[plugin manager] task %s: completed, got suggested plugin (%s)",
+ port_context->name, mm_plugin_get_name (port_context->suggested_plugin));
+ /* Store best plugin and end operation */
+ port_context->best_plugin = g_object_ref (port_context->suggested_plugin);
+ port_context_complete (port_context);
+ return;
+ }
- /* Get supports check results */
- support_result = mm_plugin_supports_port_finish (plugin, result, &error);
+ /* Recheck support in deferred task */
+ mm_dbg ("[plugin manager] task %s: re-checking support on deferred task, got suggested plugin (%s)",
+ port_context->name, mm_plugin_get_name (port_context->suggested_plugin));
+ port_context->current = g_list_find (port_context->current, port_context->suggested_plugin);
+ port_context_next (port_context);
+ return;
+ }
+
+ /* We are deferred until a suggested plugin is given. If last supports task
+ * of a given device is finished without finding a best plugin, this task
+ * will get finished reporting unsupported. */
+ mm_dbg ("[plugin manager] task %s: deferring support check until result suggested",
+ port_context->name);
+ port_context->defer_until_suggested = TRUE;
+}
+
+static void
+plugin_supports_port_ready (MMPlugin *plugin,
+ GAsyncResult *res,
+ PortContext *port_context)
+{
+ MMPluginSupportsResult support_result;
+ GError *error = NULL;
+ /* Get supports check results */
+ support_result = mm_plugin_supports_port_finish (plugin, res, &error);
if (error) {
- mm_warn ("(Plugin Manager) (%s) [%s] error when checking support: '%s'",
- mm_plugin_get_name (plugin),
- g_udev_device_get_name (port_probe_ctx->port),
- error->message);
+ mm_warn ("[plugin manager] task %s: error when checking support with plugin '%s': '%s'",
+ port_context->name, mm_plugin_get_name (plugin), error->message);
g_error_free (error);
}
switch (support_result) {
case MM_PLUGIN_SUPPORTS_PORT_SUPPORTED:
- /* Found a best plugin */
- port_probe_ctx->best_plugin = g_object_ref (plugin);
-
- if (port_probe_ctx->suggested_plugin &&
- port_probe_ctx->suggested_plugin != plugin) {
- /* The last plugin we tried said it supported this port, but it
- * doesn't correspond with the one we're being suggested. */
- mm_dbg ("(Plugin Manager) (%s) [%s] found best plugin for port, "
- "but not the same as the suggested one (%s)",
- mm_plugin_get_name (port_probe_ctx->best_plugin),
- g_udev_device_get_name (port_probe_ctx->port),
- mm_plugin_get_name (port_probe_ctx->suggested_plugin));
- } else {
- mm_dbg ("(Plugin Manager) (%s) [%s] found best plugin for port",
- mm_plugin_get_name (port_probe_ctx->best_plugin),
- g_udev_device_get_name (port_probe_ctx->port));
- }
- port_probe_ctx->current = NULL;
+ port_context_supported (port_context, plugin);
+ break;
+ case MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED:
+ port_context_unsupported (port_context, plugin);
+ break;
+ case MM_PLUGIN_SUPPORTS_PORT_DEFER:
+ port_context_defer (port_context);
+ break;
+ case MM_PLUGIN_SUPPORTS_PORT_DEFER_UNTIL_SUGGESTED:
+ port_context_defer_until_suggested (port_context, plugin);
+ break;
+ }
- /* Step, which will end the port probe operation */
- port_probe_context_step (port_probe_ctx);
- return;
+ /* We received a full reference, to make sure the context was always
+ * valid during the async call */
+ port_context_unref (port_context);
+}
+static void
+port_context_next (PortContext *port_context)
+{
+ MMPlugin *plugin;
- case MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED:
- if (port_probe_ctx->suggested_plugin) {
- if (port_probe_ctx->suggested_plugin == plugin) {
- /* If the plugin that just completed the support check claims
- * not to support this port, but this plugin is clearly the
- * right plugin since it claimed this port's physical modem,
- * just drop the port.
- */
- mm_dbg ("(Plugin Manager) [%s] ignoring port unsupported by physical modem's plugin",
- g_udev_device_get_name (port_probe_ctx->port));
- port_probe_ctx->best_plugin = NULL;
- port_probe_ctx->current = NULL;
- } else {
- /* The last plugin we tried is NOT the one we got suggested, so
- * directly check support with the suggested plugin. If we
- * already checked its support, it won't be checked again. */
- port_probe_ctx->current = g_list_find (port_probe_ctx->current,
- port_probe_ctx->suggested_plugin);
- }
- } else {
- /* If the plugin knows it doesn't support the modem, just keep on
- * checking the next plugin.
- */
- port_probe_ctx->current = g_list_next (port_probe_ctx->current);
- }
+ /* If we're cancelled, done */
+ if (g_cancellable_is_cancelled (port_context->cancellable)) {
+ port_context_complete (port_context);
+ return;
+ }
- /* Step */
- port_probe_context_step (port_probe_ctx);
+ /* Already checked all plugins? */
+ if (!port_context->current) {
+ port_context_complete (port_context);
return;
+ }
+ /* Ask the current plugin to check support of this port.
+ *
+ * A full new reference to the port context is given as user data to the
+ * async method because we want to make sure the context is still valid
+ * once the method finishes. */
+ plugin = MM_PLUGIN (port_context->current->data);
+ mm_dbg ("[plugin manager] task %s: checking with plugin '%s'",
+ port_context->name, mm_plugin_get_name (plugin));
+ mm_plugin_supports_port (plugin,
+ port_context->device,
+ port_context->port,
+ (GAsyncReadyCallback) plugin_supports_port_ready,
+ port_context_ref (port_context));
+}
- case MM_PLUGIN_SUPPORTS_PORT_DEFER:
- /* Try with the suggested one after being deferred */
- if (port_probe_ctx->suggested_plugin) {
- mm_dbg ("(Plugin Manager) (%s) [%s] deferring support check, suggested: %s",
- mm_plugin_get_name (MM_PLUGIN (port_probe_ctx->current->data)),
- g_udev_device_get_name (port_probe_ctx->port),
- mm_plugin_get_name (MM_PLUGIN (port_probe_ctx->suggested_plugin)));
- port_probe_ctx->current = g_list_find (port_probe_ctx->current,
- port_probe_ctx->suggested_plugin);
- } else {
- mm_dbg ("(Plugin Manager) (%s) [%s] deferring support check",
- mm_plugin_get_name (MM_PLUGIN (port_probe_ctx->current->data)),
- g_udev_device_get_name (port_probe_ctx->port));
- }
+static gboolean
+port_context_cancel (PortContext *port_context)
+{
+ /* Port context cancellation, which only makes sense if the context is
+ * actually being run, so just exit if it isn't. */
+ if (!port_context->task)
+ return FALSE;
+
+ /* If cancelled already, do nothing */
+ if (g_cancellable_is_cancelled (port_context->cancellable))
+ return FALSE;
+
+ mm_dbg ("[plugin manager) task %s: cancellation requested",
+ port_context->name);
+
+ /* The port context is cancelled now */
+ g_cancellable_cancel (port_context->cancellable);
+
+ /* If the task was deferred, we can cancel and complete it right away */
+ if (port_context->defer_id) {
+ g_source_remove (port_context->defer_id);
+ port_context->defer_id = 0;
+ port_context_complete (port_context);
+ return TRUE;
+ }
- /* Schedule checking support */
- port_probe_ctx->defer_id = g_timeout_add_seconds (DEFER_TIMEOUT_SECS,
- (GSourceFunc)deferred_support_check_idle,
- port_probe_ctx);
- return;
+ /* If the task was deferred until a result is suggested, we can also
+ * complete it right away */
+ if (port_context->defer_until_suggested) {
+ port_context_complete (port_context);
+ return TRUE;
+ }
+ /* The task may be currently checking support with a given plugin */
+ return TRUE;
+}
- case MM_PLUGIN_SUPPORTS_PORT_DEFER_UNTIL_SUGGESTED:
- /* If we're deferred until suggested, but there is already a plugin
- * suggested in the parent device context, grab it. This may happen if
- * e.g. a wwan interface arrives *after* a port has already been probed.
- */
- if (!port_probe_ctx->suggested_plugin) {
- MMPlugin *device_plugin;
-
- /* Get info about the currently scheduled plugin in the device */
- device_plugin = (MMPlugin *)mm_device_peek_plugin (port_probe_ctx->parent_ctx->device);
- if (device_plugin) {
- mm_dbg ("(Plugin Manager) (%s) [%s] task deferred until result suggested and got suggested plugin",
- mm_plugin_get_name (device_plugin),
- g_udev_device_get_name (port_probe_ctx->port));
- /* Flag it as deferred before suggesting probe result */
- port_probe_ctx->defer_until_suggested = TRUE;
- suggest_single_port_probe_result (port_probe_ctx, device_plugin, FALSE);
+static void
+port_context_run (MMPluginManager *self,
+ PortContext *port_context,
+ GList *plugins,
+ MMPlugin *suggested,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_assert (!port_context->task);
+ g_assert (!port_context->defer_id);
+ g_assert (!port_context->plugins);
+ g_assert (!port_context->current);
+ g_assert (!port_context->suggested_plugin);
+
+ /* Setup plugins to probe and first one to check. */
+ port_context->plugins = g_list_copy_deep (plugins, (GCopyFunc) g_object_ref, NULL);
+ port_context->current = port_context->plugins;
+
+ /* If we got one suggested, it will be the first one */
+ if (suggested) {
+ port_context->suggested_plugin = g_object_ref (suggested);
+ port_context->current = g_list_find (port_context->current, port_context->suggested_plugin);
+ if (!port_context->current)
+ mm_warn ("[plugin manager] task %s: suggested plugin (%s) not among the ones to test",
+ port_context->name, mm_plugin_get_name (suggested));
+ }
+
+ /* Log the list of plugins found and specify which are the ones that are going
+ * to be run */
+ {
+ gboolean suggested_found = FALSE;
+ GList *l;
+
+ mm_dbg ("[plugin manager] task %s: found '%u' plugins to try",
+ port_context->name, g_list_length (port_context->plugins));
+
+ for (l = port_context->plugins; l; l = g_list_next (l)) {
+ MMPlugin *plugin;
+
+ plugin = MM_PLUGIN (l->data);
+ if (suggested_found) {
+ mm_dbg ("[plugin manager] task %s: may try with plugin '%s'",
+ port_context->name, mm_plugin_get_name (plugin));
+ continue;
}
+ if (suggested && l == port_context->current) {
+ suggested_found = TRUE;
+ mm_dbg ("[plugin manager] task %s: will try with plugin '%s' (suggested)",
+ port_context->name, mm_plugin_get_name (plugin));
+ continue;
+ }
+ if (suggested && !suggested_found) {
+ mm_dbg ("[plugin manager] task %s: won't try with plugin '%s' (skipped)",
+ port_context->name, mm_plugin_get_name (plugin));
+ continue;
+ }
+ mm_dbg ("[plugin manager] task %s: will try with plugin '%s'",
+ port_context->name, mm_plugin_get_name (plugin));
}
+ }
- /* If we arrived here and we already have a plugin suggested, use it */
- if (port_probe_ctx->suggested_plugin) {
- if (port_probe_ctx->suggested_plugin == plugin) {
- mm_dbg ("(Plugin Manager) (%s) [%s] task completed, got suggested plugin",
- mm_plugin_get_name (port_probe_ctx->suggested_plugin),
- g_udev_device_get_name (port_probe_ctx->port));
- port_probe_ctx->best_plugin = g_object_ref (port_probe_ctx->suggested_plugin);
- port_probe_ctx->current = NULL;
- } else {
- mm_dbg ("(Plugin Manager) (%s) [%s] re-checking support on deferred task, got suggested plugin",
- mm_plugin_get_name (port_probe_ctx->suggested_plugin),
- g_udev_device_get_name (port_probe_ctx->port));
- port_probe_ctx->current = g_list_find (port_probe_ctx->current,
- port_probe_ctx->suggested_plugin);
- }
+ /* The full port context is now cancellable. We pass this cancellable also
+ * to the inner GTask, so that if we're cancelled we always return a
+ * cancellation error, regardless of what the standard logic does. */
+ port_context->cancellable = g_cancellable_new ();
- /* Schedule checking support, which will end the operation */
- port_probe_context_step (port_probe_ctx);
- return;
- }
+ /* Create a inner task for the port context. The result we expect is the
+ * best plugin found for the port. */
+ port_context->task = g_task_new (self, port_context->cancellable, callback, user_data);
- /* We are deferred until a suggested plugin is given. If last supports task
- * of a given device is finished without finding a best plugin, this task
- * will get finished reporting unsupported. */
- mm_dbg ("(Plugin Manager) [%s] deferring support check until result suggested",
- g_udev_device_get_name (port_probe_ctx->port));
- port_probe_ctx->defer_until_suggested = TRUE;
- return;
+ mm_dbg ("[plugin manager) task %s: started", port_context->name);
+
+ /* Go probe with the first plugin */
+ port_context_next (port_context);
+}
+
+static PortContext *
+port_context_new (MMPluginManager *self,
+ const gchar *parent_name,
+ MMDevice *device,
+ GUdevDevice *port)
+{
+ PortContext *port_context;
+
+ port_context = g_slice_new0 (PortContext);
+ port_context->ref_count = 1;
+ port_context->device = g_object_ref (device);
+ port_context->port = g_object_ref (port);
+ port_context->timer = g_timer_new ();
+
+ /* Set context name */
+ port_context->name = g_strdup_printf ("%s,%s", parent_name, g_udev_device_get_name (port));
+
+ return port_context;
+}
+
+/*****************************************************************************/
+/* Device context */
+
+/* Time to wait for ports to appear before starting to probe the first one */
+#define MIN_WAIT_TIME_MSECS 1500
+
+/* Time to wait for other ports to appear once the first port is exposed
+ * (needs to be > MIN_WAIT_TIME_MSECS!!) */
+#define MIN_PROBING_TIME_MSECS 2500
+
+/* The wait time we define must always be less than the probing time */
+G_STATIC_ASSERT (MIN_WAIT_TIME_MSECS < MIN_PROBING_TIME_MSECS);
+
+/*
+ * Device context
+ *
+ * This structure holds all the information related to a single device. This
+ * information includes references to all port contexts generated in the device,
+ * as well as a reference to the parent plugin manager object and the async
+ * task to complete when finished.
+ */
+struct _DeviceContext {
+ /* Reference counting */
+ volatile gint ref_count;
+ /* The name of the context */
+ gchar *name;
+ /* The plugin manager */
+ MMPluginManager *self;
+ /* The device for which we're looking support */
+ MMDevice *device;
+
+ /* The operation task */
+ GTask *task;
+ /* Internal ancellable */
+ GCancellable *cancellable;
+
+ /* Timer tracking how much time is required for the device support check */
+ GTimer *timer;
+
+ /* The best plugin at a given moment. Once the last port task finishes, this
+ * will be the one being returned in the async result */
+ MMPlugin *best_plugin;
+
+ /* Minimum wait time. No port probing can start before this timeout expires.
+ * Once the timeout is expired, the id is reset to 0. */
+ guint min_wait_time_id;
+ /* Port support check contexts waiting to be run after min wait time */
+ GList *wait_port_contexts;
+
+ /* Minimum probing_time. The device support check task cannot be finished
+ * before this timeout expires. Once the timeout is expired, the id is reset
+ * to 0. */
+ guint min_probing_time_id;
+
+ /* Signal connection ids for the grabbed/released signals from the device.
+ * These are the signals that will give us notifications of what ports are
+ * available (or suddenly unavailable) in the device. */
+ gulong grabbed_id;
+ gulong released_id;
+
+ /* Port support check contexts being run */
+ GList *port_contexts;
+};
+
+static void
+device_context_unref (DeviceContext *device_context)
+{
+ if (g_atomic_int_dec_and_test (&device_context->ref_count)) {
+ /* When the last reference is gone there must be no source scheduled and no
+ * pending port tasks. */
+ g_assert (!device_context->grabbed_id);
+ g_assert (!device_context->released_id);
+ g_assert (!device_context->min_wait_time_id);
+ g_assert (!device_context->min_probing_time_id);
+ g_assert (!device_context->port_contexts);
+
+ /* The device support check task must have been completed previously */
+ g_assert (!device_context->task);
+
+ g_free (device_context->name);
+ g_timer_destroy (device_context->timer);
+ if (device_context->cancellable)
+ g_object_unref (device_context->cancellable);
+ if (device_context->best_plugin)
+ g_object_unref (device_context->best_plugin);
+ g_object_unref (device_context->device);
+ g_object_unref (device_context->self);
+ g_slice_free (DeviceContext, device_context);
+ }
+}
+
+static DeviceContext *
+device_context_ref (DeviceContext *device_context)
+{
+ g_atomic_int_inc (&device_context->ref_count);
+ return device_context;
+}
+
+static PortContext *
+device_context_peek_port_context (DeviceContext *device_context,
+ GUdevDevice *port)
+{
+ GList *l;
+
+ for (l = device_context->port_contexts; l; l = g_list_next (l)) {
+ PortContext *port_context;
+
+ port_context = (PortContext *)(l->data);
+ if ((port_context->port == port) ||
+ (!g_strcmp0 (g_udev_device_get_name (port_context->port), g_udev_device_get_name (port))))
+ return port_context;
}
+ return NULL;
+}
+
+static MMPlugin *
+device_context_run_finish (MMPluginManager *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_PLUGIN (g_task_propagate_pointer (G_TASK (res), error));
}
static void
-port_probe_context_step (PortProbeContext *port_probe_ctx)
+device_context_complete (DeviceContext *device_context)
{
- FindDeviceSupportContext *ctx = port_probe_ctx->parent_ctx;
+ GTask *task;
- /* Already checked all plugins? */
- if (!port_probe_ctx->current) {
- port_probe_context_finished (port_probe_ctx);
- return;
+ /* Steal the task from the context */
+ g_assert (device_context->task);
+ task = device_context->task;
+ device_context->task = NULL;
+
+ /* Log about the time required to complete the checks */
+ mm_dbg ("[plugin manager] task %s: finished in '%lf' seconds",
+ device_context->name, g_timer_elapsed (device_context->timer, NULL));
+
+ /* Remove signal handlers */
+ if (device_context->grabbed_id) {
+ g_signal_handler_disconnect (device_context->device, device_context->grabbed_id);
+ device_context->grabbed_id = 0;
+ }
+ if (device_context->released_id) {
+ g_signal_handler_disconnect (device_context->device, device_context->released_id);
+ device_context->released_id = 0;
}
- /* Ask the current plugin to check support of this port */
- mm_plugin_supports_port (MM_PLUGIN (port_probe_ctx->current->data),
- ctx->device,
- port_probe_ctx->port,
- (GAsyncReadyCallback)plugin_supports_port_ready,
- port_probe_ctx);
+ /* Remove timeouts, if still around */
+ if (device_context->min_wait_time_id) {
+ g_source_remove (device_context->min_wait_time_id);
+ device_context->min_wait_time_id = 0;
+ }
+ if (device_context->min_probing_time_id) {
+ g_source_remove (device_context->min_probing_time_id);
+ device_context->min_probing_time_id = 0;
+ }
+
+ /* Task completion */
+ if (!device_context->best_plugin)
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "not supported by any plugin");
+ else
+ g_task_return_pointer (task, g_object_ref (device_context->best_plugin), (GDestroyNotify) g_object_unref);
+ g_object_unref (task);
}
-static GList *
-build_plugins_list (MMPluginManager *self,
- MMDevice *device,
- GUdevDevice *port)
+static void
+device_context_suggest_plugin (DeviceContext *device_context,
+ PortContext *port_context,
+ MMPlugin *suggested_plugin)
{
- GList *list = NULL;
GList *l;
- gboolean supported_found = FALSE;
+ GList *listdup;
+
+ /* If the suggested plugin is NULL, we'll propagate the suggestion only if all
+ * the port contexts are deferred until suggested. */
+ if (!suggested_plugin) {
+ for (l = device_context->port_contexts; l; l = g_list_next (l)) {
+ PortContext *other_port_context = (PortContext *)(l->data);
+
+ /* Do not propagate NULL if we find at least one probe which is not
+ * waiting for the suggested plugin */
+ if (other_port_context != port_context && !other_port_context->defer_until_suggested)
+ return;
+ }
+ }
- for (l = self->priv->plugins; l && !supported_found; l = g_list_next (l)) {
- MMPluginSupportsHint hint;
+ /* Do the suggestion propagation.
+ * Shallow copy, just so that we can iterate safely without worrying about the
+ * original list being modified while hte completions happen */
+ listdup = g_list_copy (device_context->port_contexts);
+ for (l = listdup; l; l = g_list_next (l))
+ port_context_set_suggestion ((PortContext *)(l->data), suggested_plugin);
+ g_list_free (listdup);
+}
- hint = mm_plugin_discard_port_early (MM_PLUGIN (l->data), device, port);
- switch (hint) {
- case MM_PLUGIN_SUPPORTS_HINT_UNSUPPORTED:
- /* Fully discard */
- break;
- case MM_PLUGIN_SUPPORTS_HINT_MAYBE:
- /* Maybe supported, add to tail of list */
- list = g_list_append (list, g_object_ref (l->data));
- break;
- case MM_PLUGIN_SUPPORTS_HINT_LIKELY:
- /* Likely supported, add to head of list */
- list = g_list_prepend (list, g_object_ref (l->data));
- break;
- case MM_PLUGIN_SUPPORTS_HINT_SUPPORTED:
- /* Really supported, clean existing list and add it alone */
- if (list) {
- g_list_free_full (list, (GDestroyNotify)g_object_unref);
- list = NULL;
- }
- list = g_list_prepend (list, g_object_ref (l->data));
- /* This will end the loop as well */
- supported_found = TRUE;
- break;
- default:
- g_assert_not_reached();
+static void
+device_context_set_best_plugin (DeviceContext *device_context,
+ PortContext *port_context,
+ MMPlugin *best_plugin)
+{
+ if (!best_plugin) {
+ /* If the port appeared after an already probed port, which decided that
+ * the Generic plugin was the best one (which is by default not initially
+ * suggested), we'll end up arriving here. Don't ignore it, it may well
+ * be a wwan port that we do need to grab. */
+ if (device_context->best_plugin) {
+ mm_dbg ("[plugin manager] task %s: assuming port can be handled by the '%s' plugin",
+ port_context->name, mm_plugin_get_name (device_context->best_plugin));
+ return;
}
+
+ /* Unsupported error, this is generic when we cannot find a plugin */
+ mm_dbg ("[plugin manager] task %s: not supported by any plugin" ,
+ port_context->name);
+
+ /* Tell the device to ignore this port */
+ mm_device_ignore_port (device_context->device, port_context->port);
+
+ /* If this is the last valid probe which was running (i.e. the last one
+ * not being deferred-until-suggested), cancel all remaining ones. */
+ device_context_suggest_plugin (device_context, port_context, NULL);
+ return;
}
- /* Add the generic plugin at the end of the list */
- if (self->priv->generic)
- list = g_list_append (list, g_object_ref (self->priv->generic));
+ /* Store the plugin as the best one in the device if this is the first
+ * result we got. Also, if the previously suggested plugin was the GENERIC
+ * one and now we're reporting a more specific one, use the new one.
+ */
+ if (!device_context->best_plugin ||
+ (g_str_equal (mm_plugin_get_name (device_context->best_plugin), MM_PLUGIN_GENERIC_NAME) &&
+ device_context->best_plugin != best_plugin)) {
+ /* Only log best plugin if it's not the generic one */
+ if (!g_str_equal (mm_plugin_get_name (best_plugin), MM_PLUGIN_GENERIC_NAME))
+ mm_dbg ("[plugin manager] task %s: found best plugin: %s",
+ port_context->name, mm_plugin_get_name (best_plugin));
+ /* Store and suggest this plugin also to other port probes */
+ device_context->best_plugin = g_object_ref (best_plugin);
+ device_context_suggest_plugin (device_context, port_context, best_plugin);
+ return;
+ }
- mm_dbg ("(Plugin Manager) [%s] Found '%u' plugins to try...",
- g_udev_device_get_name (port),
- g_list_length (list));
- for (l = list; l; l = g_list_next (l)) {
- mm_dbg ("(Plugin Manager) [%s] Will try with plugin '%s'",
- g_udev_device_get_name (port),
- mm_plugin_get_name (MM_PLUGIN (l->data)));
+ /* Warn if the best plugin found for this port differs from the
+ * best plugin found for the the first probed port */
+ if (!g_str_equal (mm_plugin_get_name (device_context->best_plugin), mm_plugin_get_name (best_plugin))) {
+ /* Icera modems may not reply to the icera probing in all ports. We handle this by
+ * checking the forbidden/allowed icera flags in both the current and the expected
+ * plugins. If either of these plugins requires icera and the other doesn't, we
+ * pick the Icera one as best plugin. */
+ gboolean previous_forbidden_icera;
+ gboolean previous_allowed_icera;
+ gboolean new_forbidden_icera;
+ gboolean new_allowed_icera;
+
+ g_object_get (device_context->best_plugin,
+ MM_PLUGIN_ALLOWED_ICERA, &previous_allowed_icera,
+ MM_PLUGIN_FORBIDDEN_ICERA, &previous_forbidden_icera,
+ NULL);
+ g_assert (previous_allowed_icera == FALSE || previous_forbidden_icera == FALSE);
+
+ g_object_get (best_plugin,
+ MM_PLUGIN_ALLOWED_ICERA, &new_allowed_icera,
+ MM_PLUGIN_FORBIDDEN_ICERA, &new_forbidden_icera,
+ NULL);
+ g_assert (new_allowed_icera == FALSE || new_forbidden_icera == FALSE);
+
+ if (previous_allowed_icera && new_forbidden_icera)
+ mm_warn ("[plugin manager] task %s: will use plugin '%s' instead of '%s', modem is icera-capable",
+ port_context->name,
+ mm_plugin_get_name (device_context->best_plugin),
+ mm_plugin_get_name (best_plugin));
+ else if (new_allowed_icera && previous_forbidden_icera) {
+ mm_warn ("[plugin manager] task %s: overriding previously selected device plugin '%s' with '%s', modem is icera-capable",
+ port_context->name,
+ mm_plugin_get_name (device_context->best_plugin),
+ mm_plugin_get_name (best_plugin));
+ g_object_unref (device_context->best_plugin);
+ device_context->best_plugin = g_object_ref (best_plugin);
+ } else
+ mm_warn ("[plugin manager] task %s: plugin mismatch error (device reports '%s', port reports '%s')",
+ port_context->name,
+ mm_plugin_get_name (device_context->best_plugin),
+ mm_plugin_get_name (best_plugin));
+ return;
}
- return list;
+ /* Device plugin equal to best plugin */
+ mm_dbg ("[plugin manager] task %s: best plugin matches device reported one: %s",
+ port_context->name, mm_plugin_get_name (best_plugin));
}
static void
-port_probe_context_start (FindDeviceSupportContext *ctx,
- PortProbeContext *port_probe_ctx)
+device_context_continue (DeviceContext *device_context)
{
- /* Setup plugins to probe and first one to check.
- * Make sure this plugins list is built after the MIN WAIT TIME has been expired
- * (so that per-driver filters work correctly) */
- port_probe_ctx->plugins = build_plugins_list (ctx->self, ctx->device, port_probe_ctx->port);
- port_probe_ctx->current = port_probe_ctx->plugins;
-
- /* If we got one suggested, it will be the first one, unless it is the generic plugin */
- port_probe_ctx->suggested_plugin = (!!mm_device_peek_plugin (ctx->device) ?
- MM_PLUGIN (mm_device_get_plugin (ctx->device)) :
- NULL);
- if (port_probe_ctx->suggested_plugin) {
- if (g_str_equal (mm_plugin_get_name (port_probe_ctx->suggested_plugin),
- MM_PLUGIN_GENERIC_NAME))
- /* Initially ignore generic plugin suggested */
- g_clear_object (&port_probe_ctx->suggested_plugin);
+ GList *l;
+ GString *s = NULL;
+ guint n = 0;
+ guint n_active = 0;
+
+ /* If there are no running port contexts around, we're free to finish */
+ if (!device_context->port_contexts) {
+ mm_dbg ("[plugin manager] task %s: no more ports to probe", device_context->name);
+ device_context_complete (device_context);
+ return;
+ }
+
+ /* We'll count how many port contexts are 'active' (i.e. not deferred
+ * until a result is suggested). Also, prepare to log about the pending
+ * ports */
+ for (l = device_context->port_contexts; l; l = g_list_next (l)) {
+ PortContext *port_context = (PortContext *) (l->data);
+ const gchar *portname;
+
+ portname = g_udev_device_get_name (port_context->port);
+ if (!s)
+ s = g_string_new (portname);
else
- port_probe_ctx->current = g_list_find (port_probe_ctx->current,
- port_probe_ctx->suggested_plugin);
+ g_string_append_printf (s, ", %s", portname);
+
+ /* Active? */
+ if (!port_context->defer_until_suggested)
+ n_active++;
+ n++;
}
- port_probe_context_step (port_probe_ctx);
+ g_assert (n > 0 && s);
+ mm_dbg ("[plugin Manager] task %s: still %u running probes (%u active): %s",
+ device_context->name, n, n_active, s->str);
+ g_string_free (s, TRUE);
+
+ if (n_active == 0) {
+ mm_dbg ("[plugin manager] task %s: no active tasks to probe", device_context->name);
+ device_context_suggest_plugin (device_context, NULL, NULL);
+ }
}
static void
-device_port_grabbed_cb (MMDevice *device,
- GUdevDevice *port,
- FindDeviceSupportContext *ctx)
+port_context_run_ready (MMPluginManager *self,
+ GAsyncResult *res,
+ CommonAsyncContext *common)
{
- PortProbeContext *port_probe_ctx;
+ GError *error = NULL;
+ MMPlugin *best_plugin;
+
+ /* Returns a full reference to the best plugin */
+ best_plugin = port_context_run_finish (self, res, &error);
+ if (!best_plugin) {
+ /* The only error we can ignore is UNSUPPORTED */
+ if (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {
+ /* This error is not critical */
+ device_context_set_best_plugin (common->device_context, common->port_context, NULL);
+ } else
+ mm_warn ("[plugin manager] task %s: failed: %s", common->port_context->name, error->message);
+ g_error_free (error);
+ } else {
+ /* Set the plugin as the best one in the device context */
+ device_context_set_best_plugin (common->device_context, common->port_context, best_plugin);
+ g_object_unref (best_plugin);
+ }
+
+ /* We MUST have the port context in the list at this point, because we're
+ * going to remove the reference, so assert if this is not true. The caller
+ * must always make sure that the port_context is available in the list */
+ g_assert (g_list_find (common->device_context->port_contexts, common->port_context));
+ common->device_context->port_contexts = g_list_remove (common->device_context->port_contexts,
+ common->port_context);
+ port_context_unref (common->port_context);
- /* Launch probing task on this port with the first plugin of the list */
- port_probe_ctx = g_slice_new0 (PortProbeContext);
- port_probe_ctx->parent_ctx = ctx;
- port_probe_ctx->port = g_object_ref (port);
+ /* Continue the device context logic */
+ device_context_continue (common->device_context);
+
+ /* Cleanup the context of the async operation */
+ common_async_context_free (common);
+}
+
+static gboolean
+device_context_min_probing_time_elapsed (DeviceContext *device_context)
+{
+ device_context->min_probing_time_id = 0;
- /* Schedule in the list of probes to run */
- ctx->running_probes = g_list_prepend (ctx->running_probes, port_probe_ctx);
+ mm_dbg ("[plugin manager] task %s: min probing time elapsed", device_context->name);
- /* If port grabbed after the min wait timeout expired, launch probing directly */
- if (ctx->min_wait_timeout_expired)
- port_probe_context_start (ctx, port_probe_ctx);
+ /* Wakeup the device context logic */
+ device_context_continue (device_context);
+ return G_SOURCE_REMOVE;
}
static void
-device_port_released_cb (MMDevice *device,
- GUdevDevice *port,
- FindDeviceSupportContext *ctx)
+device_context_run_port_context (DeviceContext *device_context,
+ PortContext *port_context)
{
- /* TODO: abort probing on that port */
+ GList *plugins;
+ MMPlugin *suggested = NULL;
+ MMPluginManager *self;
+
+ /* Recover plugin manager */
+ self = MM_PLUGIN_MANAGER (device_context->self);
+
+ /* Setup plugins to probe and first one to check.
+ * Make sure this plugins list is built after the MIN WAIT TIME has been expired
+ * (so that per-driver filters work correctly) */
+ plugins = plugin_manager_build_plugins_list (self, device_context->device, port_context->port);
+
+ /* If we got one already set in the device context, it will be the first one,
+ * unless it is the generic plugin */
+ if (device_context->best_plugin &&
+ !g_str_equal (mm_plugin_get_name (device_context->best_plugin), MM_PLUGIN_GENERIC_NAME)) {
+ suggested = device_context->best_plugin;
+ }
+
+ port_context_run (self,
+ port_context,
+ plugins,
+ suggested,
+ (GAsyncReadyCallback) port_context_run_ready,
+ common_async_context_new (self,
+ device_context,
+ port_context,
+ NULL));
+ g_list_free_full (plugins, g_object_unref);
}
static gboolean
-min_probing_timeout_cb (FindDeviceSupportContext *ctx)
+device_context_min_wait_time_elapsed (DeviceContext *device_context)
{
- ctx->timeout_id = 0;
+ MMPluginManager *self;
+ GList *l;
- /* If there are no running probes around, we're free to finish */
- if (ctx->running_probes == NULL) {
- mm_dbg ("(Plugin Manager) [%s] Minimum probing time consumed and no more ports to probe",
- mm_device_get_path (ctx->device));
- find_device_support_context_complete_and_free (ctx);
- } else {
- GList *l;
- gboolean not_deferred = FALSE;
+ /* Recover plugin manager */
+ self = MM_PLUGIN_MANAGER (device_context->self);
- mm_dbg ("(Plugin Manager) [%s] Minimum probing time consumed",
- mm_device_get_path (ctx->device));
+ device_context->min_wait_time_id = 0;
+ mm_dbg ("[plugin manager] task %s: min wait time elapsed", device_context->name);
- /* If all we got were probes with 'deferred_until_suggested', just cancel
- * the probing. May happen e.g. with just 'net' ports */
- for (l = ctx->running_probes; l; l = g_list_next (l)) {
- PortProbeContext *port_probe_ctx = l->data;
+ /* Move list of port contexts out of the wait list */
+ g_assert (!device_context->port_contexts);
+ device_context->port_contexts = device_context->wait_port_contexts;
+ device_context->wait_port_contexts = NULL;
- if (!port_probe_ctx->defer_until_suggested) {
- not_deferred = TRUE;
- break;
- }
- }
+ /* Launch supports check for each port in the Plugin Manager */
+ for (l = device_context->port_contexts; l; l = g_list_next (l))
+ device_context_run_port_context (device_context, (PortContext *)(l->data));
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+device_context_port_released (DeviceContext *device_context,
+ GUdevDevice *port)
+{
+ PortContext *port_context;
+
+ mm_dbg ("[plugin manager] task %s: port released: %s",
+ device_context->name, g_udev_device_get_name (port));
+
+ port_context = device_context_peek_port_context (device_context, port);
+
+ /* This is not something worth warning. If the probing task has already
+ * been finished, it will already be removed from the list */
+ if (!port_context)
+ return;
+
+ /* Request cancellation of this single port, will be completed asynchronously */
+ port_context_cancel (port_context);
+}
+
+static void
+device_context_port_grabbed (DeviceContext *device_context,
+ GUdevDevice *port)
+{
+ MMPluginManager *self;
+ PortContext *port_context;
+
+ /* Recover plugin manager */
+ self = MM_PLUGIN_MANAGER (device_context->self);
+
+ mm_dbg ("[plugin manager] task %s: port grabbed: %s",
+ device_context->name, g_udev_device_get_name (port));
+
+ /* Ignore if for any reason we still have it around */
+ port_context = device_context_peek_port_context (device_context, port);
+ if (port_context) {
+ mm_warn ("[plugin manager] task %s: port already being processed",
+ device_context->name);
+ return;
+ }
- if (!not_deferred)
- suggest_port_probe_result (ctx, NULL, NULL);
+ /* Setup a new port context for the newly grabbed port */
+ port_context = port_context_new (self,
+ device_context->name,
+ device_context->device,
+ port);
+
+ mm_dbg ("[plugin manager] task %s: new support task for port",
+ port_context->name);
+
+ /* Îf still waiting the min wait time, store it in the waiting list */
+ if (device_context->min_wait_time_id) {
+ mm_dbg ("[plugin manager) task %s: deferred until min wait time elapsed",
+ port_context->name);
+ /* Store the port reference in the list within the device */
+ device_context->wait_port_contexts = g_list_prepend (device_context->wait_port_contexts, port_context);
+ return;
}
- return FALSE;
+ /* Store the port reference in the list within the device */
+ device_context->port_contexts = g_list_prepend (device_context->port_contexts, port_context) ;
+
+ /* If the port has been grabbed after the min wait timeout expired, launch
+ * probing directly */
+ device_context_run_port_context (device_context, port_context);
}
static gboolean
-min_wait_timeout_cb (FindDeviceSupportContext *ctx)
+device_context_cancel (DeviceContext *device_context)
{
- GList *l;
+ /* If cancelled already, do nothing */
+ if (g_cancellable_is_cancelled (device_context->cancellable))
+ return FALSE;
+
+ mm_dbg ("[plugin manager) task %s: cancellation requested",
+ device_context->name);
+
+ /* The device context is cancelled now */
+ g_cancellable_cancel (device_context->cancellable);
+
+ /* Remove all port contexts in the waiting list. This will allow early cancellation
+ * if it arrives before the min wait time has elapsed */
+ if (device_context->wait_port_contexts) {
+ g_assert (!device_context->port_contexts);
+ g_list_free_full (device_context->wait_port_contexts, (GDestroyNotify) port_context_unref);
+ device_context->wait_port_contexts = NULL;
+ }
- ctx->min_wait_timeout_expired = TRUE;
+ /* Cancel all ongoing port contexts, if they're not already cancelled */
+ if (device_context->port_contexts) {
+ g_assert (!device_context->wait_port_contexts);
+ /* Request cancellation, will be completed asynchronously */
+ g_list_foreach (device_context->port_contexts, (GFunc) port_context_cancel, NULL);
+ }
- /* Launch supports check for each port in the Plugin Manager */
- for (l = ctx->running_probes; l; l = g_list_next (l))
- port_probe_context_start (ctx, (PortProbeContext *)(l->data));
+ /* Wakeup the device context logic. If we were still waiting for the
+ * min probing time, this will complete the device context. */
+ device_context_continue (device_context);
+ return TRUE;
+}
+
+static void
+device_context_run (MMPluginManager *self,
+ DeviceContext *device_context,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_assert (!device_context->task);
+ g_assert (!device_context->grabbed_id);
+ g_assert (!device_context->released_id);
+ g_assert (!device_context->min_wait_time_id);
+ g_assert (!device_context->min_probing_time_id);
+
+ /* Connect to device port grabbed/released notifications from the device */
+ device_context->grabbed_id = g_signal_connect_swapped (device_context->device,
+ MM_DEVICE_PORT_GRABBED,
+ G_CALLBACK (device_context_port_grabbed),
+ device_context);
+ device_context->released_id = g_signal_connect_swapped (device_context->device,
+ MM_DEVICE_PORT_RELEASED,
+ G_CALLBACK (device_context_port_released),
+ device_context);
- /* Schedule additional min probing timeout */
+ /* Set the initial waiting timeout. We don't want to probe any port before
+ * this timeout expires, so that we get as many ports added in the device
+ * as possible. If we don't do this, some plugin filters won't work properly,
+ * like the 'forbidden-drivers' one.
+ */
+ device_context->min_wait_time_id = g_timeout_add (MIN_WAIT_TIME_MSECS,
+ (GSourceFunc) device_context_min_wait_time_elapsed,
+ device_context);
- /* Set the initial timeout of 2s. We force the probing time of the device to
+ /* Set the initial probing timeout. We force the probing time of the device to
* be at least this amount of time, so that the kernel has enough time to
* bring up ports. Given that we launch this only when the first port of the
* device has been exposed in udev, this timeout effectively means that we
- * leave up to 2s to the remaining ports to appear. */
- ctx->timeout_id = g_timeout_add (MIN_PROBING_TIME_MSECS - MIN_WAIT_TIME_MSECS,
- (GSourceFunc)min_probing_timeout_cb,
- ctx);
- return G_SOURCE_REMOVE;
+ * leave up to 2s to the remaining ports to appear.
+ */
+ device_context->min_probing_time_id = g_timeout_add (MIN_PROBING_TIME_MSECS,
+ (GSourceFunc) device_context_min_probing_time_elapsed,
+ device_context);
+
+ /* The full device context is now cancellable. We pass this cancellable also
+ * to the inner GTask, so that if we're cancelled we always return a
+ * cancellation error, regardless of what the standard logic does. */
+ device_context->cancellable = g_cancellable_new ();
+
+ /* Create a inner task for the device context. We'll complete this task when
+ * the last port has been probed. */
+ device_context->task = g_task_new (self, device_context->cancellable, callback, user_data);
+}
+
+static DeviceContext *
+device_context_new (MMPluginManager *self,
+ MMDevice *device)
+{
+ static gulong unique_task_id = 0;
+ DeviceContext *device_context;
+
+ /* Create new device context and store the task */
+ device_context = g_slice_new0 (DeviceContext);
+ device_context->ref_count = 1;
+ device_context->self = g_object_ref (self);
+ device_context->device = g_object_ref (device);
+ device_context->timer = g_timer_new ();
+
+ /* Set context name (just for logging) */
+ device_context->name = g_strdup_printf ("%lu", unique_task_id++);
+
+ return device_context;
+}
+
+/*****************************************************************************/
+/* Look for plugin to support the given device
+ *
+ * This operation is initiated when the new MMDevice is detected. Once that
+ * happens, a new support check for the whole device will arrive at the plugin
+ * manager.
+ *
+ * Ports in the device, though, are added dynamically and automatically
+ * afterwards once the device support check has been created. It is the device
+ * support check context itself adding the newly added ports.
+ *
+ * The device support check task is finished once all port support check tasks
+ * have also been finished.
+ *
+ * Given that the ports are added dynamically, there is some minimum duration
+ * for the device support check task, otherwise we may end up not detecting
+ * any port.
+ *
+ * The device support check tasks are stored also in the plugin manager, so
+ * that the cancellation API doesn't require anything more specific than the
+ * device for which the support check task should be cancelled.
+ */
+
+MMPlugin *
+mm_plugin_manager_device_support_check_finish (MMPluginManager *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return MM_PLUGIN (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+static DeviceContext *
+plugin_manager_peek_device_context (MMPluginManager *self,
+ MMDevice *device)
+{
+ GList *l;
+
+ for (l = self->priv->device_contexts; l; l = g_list_next (l)) {
+ DeviceContext *device_context;
+
+ device_context = (DeviceContext *)(l->data);
+ if ((device == device_context->device) ||
+ (! g_strcmp0 (mm_device_get_path (device_context->device), mm_device_get_path (device))))
+ return device_context;
+ }
+ return NULL;
+}
+
+gboolean
+mm_plugin_manager_device_support_check_cancel (MMPluginManager *self,
+ MMDevice *device)
+{
+ DeviceContext *device_context;
+
+ /* If the device context isn't found, ignore the cancellation request. */
+ device_context = plugin_manager_peek_device_context (self, device);
+ if (!device_context)
+ return FALSE;
+
+ /* Request cancellation, will be completed asynchronously */
+ return device_context_cancel (device_context);
+}
+
+static void
+device_context_run_ready (MMPluginManager *self,
+ GAsyncResult *res,
+ CommonAsyncContext *common)
+{
+ GError *error = NULL;
+ MMPlugin *best_plugin;
+
+ /* We get a full reference back */
+ best_plugin = device_context_run_finish (self, res, &error);
+
+ /*
+ * Once the task is finished, we can also remove it from the plugin manager
+ * list. We MUST have the port context in the list at this point, because
+ * we're going to dispose the reference, so assert if this is not true.
+ */
+ g_assert (g_list_find (common->self->priv->device_contexts, common->device_context));
+ common->self->priv->device_contexts = g_list_remove (common->self->priv->device_contexts,
+ common->device_context);
+ device_context_unref (common->device_context);
+
+ /* Report result or error once removed from our internal list */
+ if (!best_plugin)
+ g_task_return_error (common->task, error);
+ else
+ g_task_return_pointer (common->task, best_plugin, (GDestroyNotify) g_object_unref);
+ common_async_context_free (common);
}
void
-mm_plugin_manager_find_device_support (MMPluginManager *self,
- MMDevice *device,
- GAsyncReadyCallback callback,
- gpointer user_data)
+mm_plugin_manager_device_support_check (MMPluginManager *self,
+ MMDevice *device,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
- FindDeviceSupportContext *ctx;
-
- mm_dbg ("(Plugin Manager) [%s] Checking device support...",
- mm_device_get_path (device));
-
- ctx = g_slice_new0 (FindDeviceSupportContext);
- ctx->self = g_object_ref (self);
- ctx->device = g_object_ref (device);
- ctx->result = g_simple_async_result_new (G_OBJECT (self),
- callback,
- user_data,
- mm_plugin_manager_find_device_support);
-
- /* Connect to device port grabbed/released notifications */
- ctx->grabbed_id = g_signal_connect (device,
- MM_DEVICE_PORT_GRABBED,
- G_CALLBACK (device_port_grabbed_cb),
- ctx);
- ctx->released_id = g_signal_connect (device,
- MM_DEVICE_PORT_RELEASED,
- G_CALLBACK (device_port_released_cb),
- ctx);
-
- ctx->timer = g_timer_new ();
+ DeviceContext *device_context;
+ GTask *task;
- /* Set the initial waiting timeout. We don't want to probe any port before
- * this timeout expires, so that we get as many ports added in the device
- * as possible. If we don't do this, some plugin filters won't work properly,
- * like the 'forbidden-drivers' one */
- ctx->timeout_id = g_timeout_add (MIN_WAIT_TIME_MSECS,
- (GSourceFunc)min_wait_timeout_cb,
- ctx);
+ /*
+ * Create a new task for the device support check request.
+ *
+ * Note that we handle cancellations ourselves, as we don't want the caller
+ * to be required to keep track of a GCancellable for each of these tasks.
+ */
+ task = g_task_new (G_OBJECT (self), NULL, callback, user_data);
+
+ /* Fail if there is already a task for the same device */
+ device_context = plugin_manager_peek_device_context (self, device);
+ if (device_context) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS,
+ "Device support check task already available for device '%s'",
+ mm_device_get_path (device));
+ g_object_unref (task);
+ return;
+ }
+
+ /* Create new device context */
+ device_context = device_context_new (self, device);
+
+ /* Track the device context in the list within the plugin manager. */
+ self->priv->device_contexts = g_list_prepend (self->priv->device_contexts, device_context);
+
+ mm_dbg ("[plugin manager] task %s: new support task for device: %s",
+ device_context->name, mm_device_get_path (device_context->device));
+
+ /* Run device context */
+ device_context_run (self,
+ device_context,
+ (GAsyncReadyCallback) device_context_run_ready,
+ common_async_context_new (self,
+ device_context,
+ NULL,
+ task));
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Look for plugin */
+
+MMPlugin *
+mm_plugin_manager_peek_plugin (MMPluginManager *self,
+ const gchar *plugin_name)
+{
+ GList *l;
+
+ if (self->priv->generic && g_str_equal (plugin_name, mm_plugin_get_name (self->priv->generic)))
+ return self->priv->generic;
+
+ for (l = self->priv->plugins; l; l = g_list_next (l)) {
+ MMPlugin *plugin = MM_PLUGIN (l->data);
+
+ if (g_str_equal (plugin_name, mm_plugin_get_name (plugin)))
+ return plugin;
+ }
+
+ return NULL;
}
/*****************************************************************************/