[PATCH 3/5] core: allow disabling auto-scan and notifying ports one by one via API

Aleksander Morgado aleksander at aleksander.es
Thu Sep 29 13:39:01 UTC 2016


This commit enables a new core ModemManager daemon option, so that automatic
detection of available modems is totally disabled: '--no-auto-scan'. Note that
this option also replaces the previously used '--test-no-auto-scan' option,
which was only used during tests.

Along with the new ModemManager option, a new ReportKernelEvent() method in
the API is defined, which allows notifying the daemon of which interfaces it
should be accessing, as well as the main details of each interface. The only
mandatory parameters in the new method are 'action' (add/remove), 'name' (the
name of the interface) and 'subsystem' (the subsystem of the interface).

The mmcli tool has support for using the new api method via several new options:

 * The '--report-kernel-event' option allows specifying device ports one by
   one, and is a direct mapping of the ReportKernelEvent() method:
     $ sudo mmcli --report-kernel-event="action=add,name=wwan0,subsystem=net"
     $ sudo mmcli --report-kernel-event="action=add,name=cdc-wdm0,subsystem=usbmisc"

 * The '--report-kernel-event-auto-scan' option uses udev monitoring to notify
   events automatically to the daemon. This allows to operate in a way
   equivalent to the default daemon operation (with implicit auto-scan).

Worth noting that the ReportKernelEvent() method is only usable when
'--no-auto-scan' is explicitly used in the daemon. An error will be reported if
the method is tried while standard udev monitoring is enabled (implicit if
auto scan isn't explicitly disabled in the daemon).

If mmcli is going to be used only to report 'real time' events, an optional
'--initial-kernel-events=[PATH]' may be given in the ModemManager call to
automatically process a set of port kernel events one by one on boot. The file
may e.g. contain:
  action=add,name=wwan0,subsystem=net
  action=add,name=cdc-wdm0,subsystem=usbmisc
---
 cli/Makefile.am                                    |   2 +
 cli/mmcli-manager.c                                | 148 ++++++-
 .../tests/org.freedesktop.ModemManager1.service.in |   2 +-
 docs/reference/libmm-glib/libmm-glib-docs.xml      |   1 +
 docs/reference/libmm-glib/libmm-glib-sections.txt  |  38 ++
 introspection/org.freedesktop.ModemManager1.xml    |  74 ++++
 libmm-glib/Makefile.am                             |   3 +
 libmm-glib/libmm-glib.h                            |   1 +
 libmm-glib/mm-kernel-event-properties.c            | 454 +++++++++++++++++++++
 libmm-glib/mm-kernel-event-properties.h            |  97 +++++
 libmm-glib/mm-manager.c                            | 124 ++++++
 libmm-glib/mm-manager.h                            |  14 +
 src/kerneldevice/mm-kernel-device-udev.c           | 221 ++++++++--
 src/kerneldevice/mm-kernel-device-udev.h           |   9 +-
 src/main.c                                         |   3 +-
 src/mm-base-manager.c                              | 266 +++++++++++-
 src/mm-base-manager.h                              |   2 +
 src/mm-context.c                                   |  32 +-
 src/mm-context.h                                   |  19 +-
 19 files changed, 1437 insertions(+), 73 deletions(-)
 create mode 100644 libmm-glib/mm-kernel-event-properties.c
 create mode 100644 libmm-glib/mm-kernel-event-properties.h

diff --git a/cli/Makefile.am b/cli/Makefile.am
index d2573db..7915f28 100644
--- a/cli/Makefile.am
+++ b/cli/Makefile.am
@@ -2,6 +2,7 @@ bin_PROGRAMS = mmcli
 
 mmcli_CPPFLAGS = \
 	$(MMCLI_CFLAGS) \
+        $(GUDEV_CFLAGS) \
 	-I$(top_srcdir) \
 	-I$(top_srcdir)/include \
 	-I$(top_builddir)/include \
@@ -34,6 +35,7 @@ mmcli_SOURCES = \
 	$(NULL)
 
 mmcli_LDADD = \
+        $(GUDEV_LIBS) \
 	$(MMCLI_LIBS) \
 	$(top_builddir)/libmm-glib/libmm-glib.la \
 	$(NULL)
diff --git a/cli/mmcli-manager.c b/cli/mmcli-manager.c
index 9960412..4e078f1 100644
--- a/cli/mmcli-manager.c
+++ b/cli/mmcli-manager.c
@@ -15,8 +15,8 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
- * Copyright (C) 2011 Aleksander Morgado <aleksander at gnu.org>
  * Copyright (C) 2011 Google, Inc.
+ * Copyright (C) 2011-2016 Aleksander Morgado <aleksander at aleksander.es>
  */
 
 #include "config.h"
@@ -29,6 +29,8 @@
 #include <glib.h>
 #include <gio/gio.h>
 
+#include <gudev/gudev.h>
+
 #define _LIBMM_INSIDE_MMCLI
 #include "libmm-glib.h"
 
@@ -39,6 +41,7 @@
 typedef struct {
     MMManager *manager;
     GCancellable *cancellable;
+    GUdevClient *udev;
 } Context;
 static Context *ctx;
 
@@ -47,6 +50,8 @@ static gboolean list_modems_flag;
 static gboolean monitor_modems_flag;
 static gboolean scan_modems_flag;
 static gchar *set_logging_str;
+static gchar *report_kernel_event_str;
+static gboolean report_kernel_event_auto_scan;
 
 static GOptionEntry entries[] = {
     { "set-logging", 'G', 0, G_OPTION_ARG_STRING, &set_logging_str,
@@ -65,6 +70,14 @@ static GOptionEntry entries[] = {
       "Request to re-scan looking for modems",
       NULL
     },
+    { "report-kernel-event", 'K', 0, G_OPTION_ARG_STRING, &report_kernel_event_str,
+      "Report kernel event",
+      "[\"key=value,...\"]"
+    },
+    { "report-kernel-event-auto-scan", 0, 0, G_OPTION_ARG_NONE, &report_kernel_event_auto_scan,
+      "Automatically report kernel events based on udev notifications",
+      NULL
+    },
     { NULL }
 };
 
@@ -96,7 +109,9 @@ mmcli_manager_options_enabled (void)
     n_actions = (list_modems_flag +
                  monitor_modems_flag +
                  scan_modems_flag +
-                 !!set_logging_str);
+                 !!set_logging_str +
+                 !!report_kernel_event_str +
+                 report_kernel_event_auto_scan);
 
     if (n_actions > 1) {
         g_printerr ("error: too many manager actions requested\n");
@@ -106,6 +121,9 @@ mmcli_manager_options_enabled (void)
     if (monitor_modems_flag)
         mmcli_force_async_operation ();
 
+    if (report_kernel_event_auto_scan)
+        mmcli_force_async_operation ();
+
     checked = TRUE;
     return !!n_actions;
 }
@@ -116,6 +134,8 @@ context_free (Context *ctx)
     if (!ctx)
         return;
 
+    if (ctx->udev)
+        g_object_unref (ctx->udev);
     if (ctx->manager)
         g_object_unref (ctx->manager);
     if (ctx->cancellable)
@@ -130,6 +150,47 @@ mmcli_manager_shutdown (void)
 }
 
 static void
+report_kernel_event_process_reply (gboolean      result,
+                                   const GError *error)
+{
+    if (!result) {
+        g_printerr ("error: couldn't report kernel event: '%s'\n",
+                    error ? error->message : "unknown error");
+        exit (EXIT_FAILURE);
+    }
+
+    g_print ("successfully reported kernel event\n");
+}
+
+static void
+report_kernel_event_ready (MMManager    *manager,
+                           GAsyncResult *result)
+{
+    gboolean operation_result;
+    GError *error = NULL;
+
+    operation_result = mm_manager_report_kernel_event_finish (manager, result, &error);
+    report_kernel_event_process_reply (operation_result, error);
+
+    mmcli_async_operation_done ();
+}
+
+static MMKernelEventProperties *
+build_kernel_event_properties_from_input (const gchar *properties_string)
+{
+    GError *error = NULL;
+    MMKernelEventProperties *properties;
+
+    properties = mm_kernel_event_properties_new_from_string (properties_string, &error);
+    if (!properties) {
+        g_printerr ("error: cannot parse properties string: '%s'\n", error->message);
+        exit (EXIT_FAILURE);
+    }
+
+    return properties;
+}
+
+static void
 set_logging_process_reply (gboolean      result,
                            const GError *error)
 {
@@ -248,6 +309,22 @@ cancelled (GCancellable *cancellable)
 }
 
 static void
+handle_uevent (GUdevClient *client,
+               const char  *action,
+               GUdevDevice *device,
+               gpointer     none)
+{
+    MMKernelEventProperties *properties;
+
+    properties = mm_kernel_event_properties_new ();
+    mm_kernel_event_properties_set_action (properties, action);
+    mm_kernel_event_properties_set_subsystem (properties, g_udev_device_get_subsystem (device));
+    mm_kernel_event_properties_set_name (properties, g_udev_device_get_name (device));
+    mm_manager_report_kernel_event (ctx->manager, properties, NULL, NULL, NULL);
+    g_object_unref (properties);
+}
+
+static void
 get_manager_ready (GObject      *source,
                    GAsyncResult *result,
                    gpointer      none)
@@ -276,6 +353,54 @@ get_manager_ready (GObject      *source,
         return;
     }
 
+    /* Request to report kernel event? */
+    if (report_kernel_event_str) {
+        MMKernelEventProperties *properties;
+
+        properties = build_kernel_event_properties_from_input (report_kernel_event_str);
+        mm_manager_report_kernel_event (ctx->manager,
+                                        properties,
+                                        ctx->cancellable,
+                                        (GAsyncReadyCallback)report_kernel_event_ready,
+                                        NULL);
+        g_object_unref (properties);
+        return;
+    }
+
+    if (report_kernel_event_auto_scan) {
+        const gchar *subsys[] = { "tty", "usbmisc", "net", NULL };
+        guint i;
+
+        ctx->udev = g_udev_client_new (subsys);
+        g_signal_connect (ctx->udev, "uevent", G_CALLBACK (handle_uevent), NULL);
+
+        for (i = 0; subsys[i]; i++) {
+            GList *list, *iter;
+
+            list = g_udev_client_query_by_subsystem (ctx->udev, subsys[i]);
+            for (iter = list; iter; iter = g_list_next (iter)) {
+                MMKernelEventProperties *properties;
+                GUdevDevice *device;
+
+                device = G_UDEV_DEVICE (iter->data);
+                properties = mm_kernel_event_properties_new ();
+                mm_kernel_event_properties_set_action (properties, "add");
+                mm_kernel_event_properties_set_subsystem (properties, subsys[i]);
+                mm_kernel_event_properties_set_name (properties, g_udev_device_get_name (device));
+                mm_manager_report_kernel_event (ctx->manager, properties, NULL, NULL, NULL);
+                g_object_unref (properties);
+            }
+            g_list_free_full (list, (GDestroyNotify) g_object_unref);
+        }
+
+        /* If we get cancelled, operation done */
+        g_cancellable_connect (ctx->cancellable,
+                               G_CALLBACK (cancelled),
+                               NULL,
+                               NULL);
+        return;
+    }
+
     /* Request to monitor modems? */
     if (monitor_modems_flag) {
         g_signal_connect (ctx->manager,
@@ -332,6 +457,11 @@ mmcli_manager_run_synchronous (GDBusConnection *connection)
         exit (EXIT_FAILURE);
     }
 
+    if (report_kernel_event_auto_scan) {
+        g_printerr ("error: monitoring udev events cannot be done synchronously\n");
+        exit (EXIT_FAILURE);
+    }
+
     /* Initialize context */
     ctx = g_new0 (Context, 1);
     ctx->manager = mmcli_get_manager_sync (connection);
@@ -362,6 +492,20 @@ mmcli_manager_run_synchronous (GDBusConnection *connection)
         return;
     }
 
+    /* Request to report kernel event? */
+    if (report_kernel_event_str) {
+        MMKernelEventProperties *properties;
+        gboolean result;
+
+        properties = build_kernel_event_properties_from_input (report_kernel_event_str);
+        result = mm_manager_report_kernel_event_sync (ctx->manager,
+                                                      properties,
+                                                      NULL,
+                                                      &error);
+        report_kernel_event_process_reply (result, error);
+        return;
+    }
+
     /* Request to list modems? */
     if (list_modems_flag) {
         list_current_modems (ctx->manager);
diff --git a/data/tests/org.freedesktop.ModemManager1.service.in b/data/tests/org.freedesktop.ModemManager1.service.in
index ee8bdea..d7c1a00 100644
--- a/data/tests/org.freedesktop.ModemManager1.service.in
+++ b/data/tests/org.freedesktop.ModemManager1.service.in
@@ -2,4 +2,4 @@
 
 [D-BUS Service]
 Name=org.freedesktop.ModemManager1
-Exec=@abs_top_builddir@/src/ModemManager --test-session --test-no-auto-scan --test-enable --test-plugin-dir="@abs_top_builddir@/plugins/.libs" --debug
+Exec=@abs_top_builddir@/src/ModemManager --test-session --no-auto-scan --test-enable --test-plugin-dir="@abs_top_builddir@/plugins/.libs" --debug
diff --git a/docs/reference/libmm-glib/libmm-glib-docs.xml b/docs/reference/libmm-glib/libmm-glib-docs.xml
index 4e1d7fe..f611b72 100644
--- a/docs/reference/libmm-glib/libmm-glib-docs.xml
+++ b/docs/reference/libmm-glib/libmm-glib-docs.xml
@@ -72,6 +72,7 @@
     <chapter>
       <title>The Manager object</title>
       <xi:include href="xml/mm-manager.xml"/>
+      <xi:include href="xml/mm-kernel-event-properties.xml"/>
     </chapter>
 
     <chapter>
diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt
index 27a98cd..250ae71 100644
--- a/docs/reference/libmm-glib/libmm-glib-sections.txt
+++ b/docs/reference/libmm-glib/libmm-glib-sections.txt
@@ -20,6 +20,9 @@ mm_manager_scan_devices_sync
 mm_manager_set_logging
 mm_manager_set_logging_finish
 mm_manager_set_logging_sync
+mm_manager_report_kernel_event
+mm_manager_report_kernel_event_finish
+mm_manager_report_kernel_event_sync
 <SUBSECTION Standard>
 MMManagerClass
 MMManagerPrivate
@@ -33,6 +36,37 @@ mm_manager_get_type
 </SECTION>
 
 <SECTION>
+<FILE>mm-kernel-event-properties</FILE>
+<TITLE>MMKernelEventProperties</TITLE>
+MMKernelEventProperties
+<SUBSECTION Methods>
+mm_kernel_event_properties_new
+mm_kernel_event_properties_get_action
+mm_kernel_event_properties_set_action
+mm_kernel_event_properties_get_name
+mm_kernel_event_properties_set_name
+mm_kernel_event_properties_get_subsystem
+mm_kernel_event_properties_set_subsystem
+mm_kernel_event_properties_get_uid
+mm_kernel_event_properties_set_uid
+<SUBSECTION Private>
+mm_kernel_event_properties_new_from_string
+mm_kernel_event_properties_new_from_dictionary
+mm_kernel_event_properties_dup
+mm_kernel_event_properties_get_dictionary
+<SUBSECTION Standard>
+MMKernelEventPropertiesClass
+MMKernelEventPropertiesPrivate
+MM_KERNEL_EVENT_PROPERTIES
+MM_KERNEL_EVENT_PROPERTIES_CLASS
+MM_KERNEL_EVENT_PROPERTIES_GET_CLASS
+MM_IS_KERNEL_EVENT_PROPERTIES
+MM_IS_KERNEL_EVENT_PROPERTIES_CLASS
+MM_TYPE_KERNEL_EVENT_PROPERTIES
+mm_kernel_event_properties_get_type
+</SECTION>
+
+<SECTION>
 <FILE>mm-object</FILE>
 <TITLE>MMObject</TITLE>
 MMObject
@@ -1555,10 +1589,14 @@ mm_gdbus_org_freedesktop_modem_manager1_call_scan_devices_sync
 mm_gdbus_org_freedesktop_modem_manager1_call_set_logging
 mm_gdbus_org_freedesktop_modem_manager1_call_set_logging_finish
 mm_gdbus_org_freedesktop_modem_manager1_call_set_logging_sync
+mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event
+mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_finish
+mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_sync
 <SUBSECTION Private>
 mm_gdbus_org_freedesktop_modem_manager1_override_properties
 mm_gdbus_org_freedesktop_modem_manager1_complete_scan_devices
 mm_gdbus_org_freedesktop_modem_manager1_complete_set_logging
+mm_gdbus_org_freedesktop_modem_manager1_complete_report_kernel_event
 mm_gdbus_org_freedesktop_modem_manager1_interface_info
 <SUBSECTION Standard>
 MM_GDBUS_IS_ORG_FREEDESKTOP_MODEM_MANAGER1
diff --git a/introspection/org.freedesktop.ModemManager1.xml b/introspection/org.freedesktop.ModemManager1.xml
index 2ecd026..4eccbc9 100644
--- a/introspection/org.freedesktop.ModemManager1.xml
+++ b/introspection/org.freedesktop.ModemManager1.xml
@@ -37,5 +37,79 @@
       <arg name="level" type="s" direction="in" />
     </method>
 
+    <!--
+        ReportKernelEvent:
+        @properties: event properties.
+
+        Reports a kernel event to ModemManager.
+
+        This method is only available if udev is not being used to report kernel
+        events.
+
+        The @properties dictionary is composed of key/value string pairs. The
+        possible keys are:
+
+        <variablelist>
+
+          <varlistentry><term><literal>action</literal></term>
+            <listitem>
+              <para>
+                The type of action, given as a string value (signature
+                <literal>"s"</literal>).
+                This parameter is MANDATORY.
+              </para>
+              <variablelist>
+                <varlistentry><term><literal>add</literal></term>
+                  <listitem>
+                    A new kernel device has been added.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>remove</literal></term>
+                  <listitem>
+                    An existing kernel device has been removed.
+                  </listitem>
+                </varlistentry>
+              </variablelist>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry><term><literal>name</literal></term>
+            <listitem>
+              <para>
+                The device name, given as a string value (signature
+                <literal>"s"</literal>).
+                This parameter is MANDATORY.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry><term><literal>subsystem</literal></term>
+            <listitem>
+              <para>
+                The device subsystem, given as a string value (signature
+                <literal>"s"</literal>).
+                This parameter is MANDATORY.
+              </para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry><term><literal>uid</literal></term>
+            <listitem>
+              <para>
+                The unique ID of the physical device, given as a string value
+                (signature <literal>"s"</literal>).
+                This parameter is OPTIONAL, if not given the sysfs path of the
+                physical device will be used. This parameter must be the same
+                for all devices exposed by the same physical device.
+              </para>
+            </listitem>
+          </varlistentry>
+
+        </variablelist>
+    -->
+    <method name="ReportKernelEvent">
+      <arg name="properties" type="a{sv}" direction="in" />
+    </method>
+
   </interface>
 </node>
diff --git a/libmm-glib/Makefile.am b/libmm-glib/Makefile.am
index e257d6f..64bca40 100644
--- a/libmm-glib/Makefile.am
+++ b/libmm-glib/Makefile.am
@@ -81,6 +81,8 @@ libmm_glib_la_SOURCES = \
 	mm-cdma-manual-activation-properties.c \
 	mm-signal.h \
 	mm-signal.c \
+	mm-kernel-event-properties.h \
+	mm-kernel-event-properties.c \
 	$(NULL)
 
 libmm_glib_la_CPPFLAGS = \
@@ -149,6 +151,7 @@ include_HEADERS = \
 	mm-firmware-properties.h \
 	mm-cdma-manual-activation-properties.h \
 	mm-signal.h \
+	mm-kernel-event-properties.h \
 	$(NULL)
 
 CLEANFILES =
diff --git a/libmm-glib/libmm-glib.h b/libmm-glib/libmm-glib.h
index fa4c7e2..53f0a19 100644
--- a/libmm-glib/libmm-glib.h
+++ b/libmm-glib/libmm-glib.h
@@ -72,6 +72,7 @@
 #include <mm-firmware-properties.h>
 #include <mm-cdma-manual-activation-properties.h>
 #include <mm-signal.h>
+#include <mm-kernel-event-properties.h>
 
 /* generated */
 #include <mm-errors-types.h>
diff --git a/libmm-glib/mm-kernel-event-properties.c b/libmm-glib/mm-kernel-event-properties.c
new file mode 100644
index 0000000..6782fbf
--- /dev/null
+++ b/libmm-glib/mm-kernel-event-properties.c
@@ -0,0 +1,454 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2016 Velocloud, Inc.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "mm-errors-types.h"
+#include "mm-enums-types.h"
+#include "mm-common-helpers.h"
+#include "mm-kernel-event-properties.h"
+
+/**
+ * SECTION: mm-kernel-event-properties
+ * @title: MMKernelEventProperties
+ * @short_description: Helper object to handle kernel event properties.
+ *
+ * The #MMKernelEventProperties is an object handling the properties to be set
+ * in reported kernel events.
+ *
+ * This object is created by the user and passed to ModemManager with either
+ * mm_manager_report_kernel_event() or mm_manager_report_kernel_event_sync().
+ */
+
+G_DEFINE_TYPE (MMKernelEventProperties, mm_kernel_event_properties, G_TYPE_OBJECT)
+
+#define PROPERTY_ACTION    "action"
+#define PROPERTY_SUBSYSTEM "subsystem"
+#define PROPERTY_NAME      "name"
+#define PROPERTY_UID       "uid"
+
+struct _MMKernelEventPropertiesPrivate {
+    gchar   *action;
+    gchar   *subsystem;
+    gchar   *name;
+    gchar   *uid;
+};
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_action:
+ * @self: A #MMKernelEventProperties.
+ * @action: The action to set.
+ *
+ * Sets the action.
+ */
+void
+mm_kernel_event_properties_set_action (MMKernelEventProperties *self,
+                                       const gchar             *action)
+{
+    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+    g_free (self->priv->action);
+    self->priv->action = g_strdup (action);
+}
+
+/**
+ * mm_kernel_event_properties_get_action:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the action.
+ *
+ * Returns: (transfer none): The action. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_action (MMKernelEventProperties *self)
+{
+    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+    return self->priv->action;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_subsystem:
+ * @self: A #MMKernelEventProperties.
+ * @subsystem: The subsystem to set.
+ *
+ * Sets the subsystem.
+ */
+void
+mm_kernel_event_properties_set_subsystem (MMKernelEventProperties *self,
+                                          const gchar             *subsystem)
+{
+    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+    g_free (self->priv->subsystem);
+    self->priv->subsystem = g_strdup (subsystem);
+}
+
+/**
+ * mm_kernel_event_properties_get_subsystem:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the subsystem.
+ *
+ * Returns: (transfer none): The subsystem. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_subsystem (MMKernelEventProperties *self)
+{
+    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+    return self->priv->subsystem;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_name:
+ * @self: A #MMKernelEventProperties.
+ * @name: The name to set.
+ *
+ * Sets the name.
+ */
+void
+mm_kernel_event_properties_set_name (MMKernelEventProperties *self,
+                                     const gchar             *name)
+{
+    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+    g_free (self->priv->name);
+    self->priv->name = g_strdup (name);
+}
+
+/**
+ * mm_kernel_event_properties_get_name:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the name.
+ *
+ * Returns: (transfer none): The name. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_name (MMKernelEventProperties *self)
+{
+    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+    return self->priv->name;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_set_uid:
+ * @self: A #MMKernelEventProperties.
+ * @uid: The uid to set.
+ *
+ * Sets the unique ID of the physical device.
+ */
+void
+mm_kernel_event_properties_set_uid (MMKernelEventProperties *self,
+                                    const gchar             *uid)
+{
+    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
+
+    g_free (self->priv->uid);
+    self->priv->uid = g_strdup (uid);
+}
+
+/**
+ * mm_kernel_event_properties_get_uid:
+ * @self: A #MMKernelEventProperties.
+ *
+ * Gets the unique ID of the physical device.
+ *
+ * Returns: (transfer none): The uid. Do not free the returned value, it is owned by @self.
+ */
+const gchar *
+mm_kernel_event_properties_get_uid (MMKernelEventProperties *self)
+{
+    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+    return self->priv->uid;
+}
+
+/*****************************************************************************/
+
+GVariant *
+mm_kernel_event_properties_get_dictionary (MMKernelEventProperties *self)
+{
+    GVariantBuilder builder;
+
+    /* We do allow NULL */
+    if (!self)
+        return NULL;
+
+    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), NULL);
+
+    g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+    if (self->priv->action)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_ACTION,
+                               g_variant_new_string (self->priv->action));
+
+    if (self->priv->subsystem)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_SUBSYSTEM,
+                               g_variant_new_string (self->priv->subsystem));
+
+    if (self->priv->name)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_NAME,
+                               g_variant_new_string (self->priv->name));
+
+    if (self->priv->uid)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_UID,
+                               g_variant_new_string (self->priv->uid));
+
+    return g_variant_ref_sink (g_variant_builder_end (&builder));
+}
+
+/*****************************************************************************/
+
+static gboolean
+consume_string (MMKernelEventProperties  *self,
+                const gchar              *key,
+                const gchar              *value,
+                GError                  **error)
+{
+    if (g_str_equal (key, PROPERTY_ACTION))
+        mm_kernel_event_properties_set_action (self, value);
+    else if (g_str_equal (key, PROPERTY_SUBSYSTEM))
+        mm_kernel_event_properties_set_subsystem (self, value);
+    else if (g_str_equal (key, PROPERTY_NAME))
+        mm_kernel_event_properties_set_name (self, value);
+    else if (g_str_equal (key, PROPERTY_UID))
+        mm_kernel_event_properties_set_uid (self, value);
+    else {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_INVALID_ARGS,
+                     "Invalid properties string, unexpected key '%s'",
+                     key);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+typedef struct {
+    MMKernelEventProperties *properties;
+    GError                  *error;
+} ParseKeyValueContext;
+
+static gboolean
+key_value_foreach (const gchar          *key,
+                   const gchar          *value,
+                   ParseKeyValueContext *ctx)
+{
+    return consume_string (ctx->properties,
+                           key,
+                           value,
+                           &ctx->error);
+}
+
+MMKernelEventProperties *
+mm_kernel_event_properties_new_from_string (const gchar  *str,
+                                            GError      **error)
+{
+    ParseKeyValueContext ctx;
+
+    ctx.properties = mm_kernel_event_properties_new ();
+    ctx.error = NULL;
+
+    mm_common_parse_key_value_string (str,
+                                      &ctx.error,
+                                      (MMParseKeyValueForeachFn) key_value_foreach,
+                                      &ctx);
+
+    /* If error, destroy the object */
+    if (ctx.error) {
+        g_propagate_error (error, ctx.error);
+        g_object_unref (ctx.properties);
+        ctx.properties = NULL;
+    }
+
+    return ctx.properties;
+}
+
+/*****************************************************************************/
+
+static gboolean
+consume_variant (MMKernelEventProperties  *properties,
+                 const gchar              *key,
+                 GVariant                 *value,
+                 GError                  **error)
+{
+    if (g_str_equal (key, PROPERTY_ACTION))
+        mm_kernel_event_properties_set_action (
+            properties,
+            g_variant_get_string (value, NULL));
+    else if (g_str_equal (key, PROPERTY_SUBSYSTEM))
+        mm_kernel_event_properties_set_subsystem (
+            properties,
+            g_variant_get_string (value, NULL));
+    else if (g_str_equal (key, PROPERTY_NAME))
+        mm_kernel_event_properties_set_name (
+            properties,
+            g_variant_get_string (value, NULL));
+    else if (g_str_equal (key, PROPERTY_UID))
+        mm_kernel_event_properties_set_uid (
+            properties,
+            g_variant_get_string (value, NULL));
+    else {
+        /* Set error */
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_INVALID_ARGS,
+                     "Invalid properties dictionary, unexpected key '%s'",
+                     key);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+MMKernelEventProperties *
+mm_kernel_event_properties_new_from_dictionary (GVariant  *dictionary,
+                                                GError   **error)
+{
+    GError *inner_error = NULL;
+    GVariantIter iter;
+    gchar *key;
+    GVariant *value;
+    MMKernelEventProperties *properties;
+
+    properties = mm_kernel_event_properties_new ();
+    if (!dictionary)
+        return properties;
+
+    if (!g_variant_is_of_type (dictionary, G_VARIANT_TYPE ("a{sv}"))) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_INVALID_ARGS,
+                     "Cannot create kernel event properties from dictionary: "
+                     "invalid variant type received");
+        g_object_unref (properties);
+        return NULL;
+    }
+
+    g_variant_iter_init (&iter, dictionary);
+    while (!inner_error &&
+           g_variant_iter_next (&iter, "{sv}", &key, &value)) {
+        consume_variant (properties,
+                         key,
+                         value,
+                         &inner_error);
+        g_free (key);
+        g_variant_unref (value);
+    }
+
+    /* If error, destroy the object */
+    if (inner_error) {
+        g_propagate_error (error, inner_error);
+        g_object_unref (properties);
+        properties = NULL;
+    }
+
+    return properties;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_dup:
+ * @orig: a #MMKernelEventProperties
+ *
+ * Returns a copy of @orig.
+ *
+ * Returns: (transfer full): a #MMKernelEventProperties
+ */
+MMKernelEventProperties *
+mm_kernel_event_properties_dup (MMKernelEventProperties *orig)
+{
+    GVariant *dict;
+    MMKernelEventProperties *copy;
+    GError *error = NULL;
+
+    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (orig), NULL);
+
+    dict = mm_kernel_event_properties_get_dictionary (orig);
+    copy = mm_kernel_event_properties_new_from_dictionary (dict, &error);
+    g_assert_no_error (error);
+    g_variant_unref (dict);
+
+    return copy;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_kernel_event_properties_new:
+ *
+ * Creates a new empty #MMKernelEventProperties.
+ *
+ * Returns: (transfer full): a #MMKernelEventProperties. The returned value should be freed with g_object_unref().
+ */
+MMKernelEventProperties *
+mm_kernel_event_properties_new (void)
+{
+    return (MM_KERNEL_EVENT_PROPERTIES (g_object_new (MM_TYPE_KERNEL_EVENT_PROPERTIES, NULL)));
+}
+
+static void
+mm_kernel_event_properties_init (MMKernelEventProperties *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                              MM_TYPE_KERNEL_EVENT_PROPERTIES,
+                                              MMKernelEventPropertiesPrivate);
+}
+
+static void
+finalize (GObject *object)
+{
+    MMKernelEventProperties *self = MM_KERNEL_EVENT_PROPERTIES (object);
+
+    g_free (self->priv->action);
+    g_free (self->priv->subsystem);
+    g_free (self->priv->name);
+    g_free (self->priv->uid);
+
+    G_OBJECT_CLASS (mm_kernel_event_properties_parent_class)->finalize (object);
+}
+
+static void
+mm_kernel_event_properties_class_init (MMKernelEventPropertiesClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMKernelEventPropertiesPrivate));
+
+    object_class->finalize = finalize;
+}
diff --git a/libmm-glib/mm-kernel-event-properties.h b/libmm-glib/mm-kernel-event-properties.h
new file mode 100644
index 0000000..576e356
--- /dev/null
+++ b/libmm-glib/mm-kernel-event-properties.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2016 Velocloud, Inc.
+ */
+
+#ifndef MM_KERNEL_EVENT_PROPERTIES_H
+#define MM_KERNEL_EVENT_PROPERTIES_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_KERNEL_EVENT_PROPERTIES            (mm_kernel_event_properties_get_type ())
+#define MM_KERNEL_EVENT_PROPERTIES(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_KERNEL_EVENT_PROPERTIES, MMKernelEventProperties))
+#define MM_KERNEL_EVENT_PROPERTIES_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_KERNEL_EVENT_PROPERTIES, MMKernelEventPropertiesClass))
+#define MM_IS_KERNEL_EVENT_PROPERTIES(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_KERNEL_EVENT_PROPERTIES))
+#define MM_IS_KERNEL_EVENT_PROPERTIES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_KERNEL_EVENT_PROPERTIES))
+#define MM_KERNEL_EVENT_PROPERTIES_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_KERNEL_EVENT_PROPERTIES, MMKernelEventPropertiesClass))
+
+typedef struct _MMKernelEventProperties MMKernelEventProperties;
+typedef struct _MMKernelEventPropertiesClass MMKernelEventPropertiesClass;
+typedef struct _MMKernelEventPropertiesPrivate MMKernelEventPropertiesPrivate;
+
+/**
+ * MMKernelEventProperties:
+ *
+ * The #MMKernelEventProperties structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMKernelEventProperties {
+    /*< private >*/
+    GObject parent;
+    MMKernelEventPropertiesPrivate *priv;
+};
+
+struct _MMKernelEventPropertiesClass {
+    /*< private >*/
+    GObjectClass parent;
+};
+
+GType mm_kernel_event_properties_get_type (void);
+
+MMKernelEventProperties *mm_kernel_event_properties_new (void);
+
+void          mm_kernel_event_properties_set_action    (MMKernelEventProperties *self,
+                                                        const gchar             *action);
+const gchar  *mm_kernel_event_properties_get_action    (MMKernelEventProperties *self);
+
+void          mm_kernel_event_properties_set_subsystem (MMKernelEventProperties *self,
+                                                        const gchar             *subsystem);
+const gchar  *mm_kernel_event_properties_get_subsystem (MMKernelEventProperties *self);
+
+void          mm_kernel_event_properties_set_name      (MMKernelEventProperties *self,
+                                                        const gchar             *name);
+const gchar  *mm_kernel_event_properties_get_name      (MMKernelEventProperties *self);
+
+void          mm_kernel_event_properties_set_uid       (MMKernelEventProperties *self,
+                                                        const gchar             *uid);
+const gchar  *mm_kernel_event_properties_get_uid       (MMKernelEventProperties *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) ||    \
+    defined (_LIBMM_INSIDE_MMCLI) || \
+    defined (LIBMM_GLIB_COMPILATION)
+
+MMKernelEventProperties *mm_kernel_event_properties_new_from_string     (const gchar  *str,
+                                                                         GError      **error);
+
+MMKernelEventProperties *mm_kernel_event_properties_new_from_dictionary (GVariant  *dictionary,
+                                                                         GError   **error);
+
+MMKernelEventProperties *mm_kernel_event_properties_dup                 (MMKernelEventProperties *orig);
+
+GVariant                *mm_kernel_event_properties_get_dictionary      (MMKernelEventProperties *self);
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_KERNEL_EVENT_PROPERTIES_H */
diff --git a/libmm-glib/mm-manager.c b/libmm-glib/mm-manager.c
index cb196b3..6a9ce5d 100644
--- a/libmm-glib/mm-manager.c
+++ b/libmm-glib/mm-manager.c
@@ -497,6 +497,130 @@ mm_manager_scan_devices_sync (MMManager     *manager,
 
 /*****************************************************************************/
 
+/**
+ * mm_manager_report_kernel_event_finish:
+ * @manager: A #MMManager.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_manager_report_kernel_event().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_manager_report_kernel_event().
+ *
+ * Returns: %TRUE if the operation succeded, %FALSE if @error is set.
+ */
+gboolean
+mm_manager_report_kernel_event_finish (MMManager     *manager,
+                                       GAsyncResult  *res,
+                                       GError       **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+report_kernel_event_ready (MmGdbusOrgFreedesktopModemManager1 *manager_iface_proxy,
+                           GAsyncResult                       *res,
+                           GTask                              *task)
+{
+    GError *error = NULL;
+
+    if (!mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_finish (
+            manager_iface_proxy,
+            res,
+            &error))
+        g_task_return_error (task, error);
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+/**
+ * mm_manager_report_kernel_event:
+ * @manager: A #MMManager.
+ * @properties: the properties of the kernel event.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously report kernel event.
+ *
+ * When the operation is finished, @callback will be invoked in the
+ * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * mm_manager_report_kernel_event_finish() to get the result of the operation.
+ *
+ * See mm_manager_report_kernel_event_sync() for the synchronous, blocking version of this method.
+ */
+void
+mm_manager_report_kernel_event (MMManager                *manager,
+                                MMKernelEventProperties  *properties,
+                                GCancellable             *cancellable,
+                                GAsyncReadyCallback       callback,
+                                gpointer                  user_data)
+{
+    GTask    *task;
+    GError   *inner_error = NULL;
+    GVariant *dictionary;
+
+    g_return_if_fail (MM_IS_MANAGER (manager));
+
+    task = g_task_new (manager, cancellable, callback, user_data);
+
+    if (!ensure_modem_manager1_proxy (manager, &inner_error)) {
+        g_task_return_error (task, inner_error);
+        g_object_unref (task);
+        return;
+    }
+
+    dictionary = mm_kernel_event_properties_get_dictionary (properties);
+    mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event (
+        manager->priv->manager_iface_proxy,
+        dictionary,
+        cancellable,
+        (GAsyncReadyCallback)report_kernel_event_ready,
+        task);
+    g_variant_unref (dictionary);
+}
+
+/**
+ * mm_manager_report_kernel_event_sync:
+ * @manager: A #MMManager.
+ * @properties: the properties of the kernel event.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously report kernel event.
+ *
+ * The calling thread is blocked until a reply is received.
+ *
+ * See mm_manager_report_kernel_event() for the asynchronous version of this method.
+ *
+ * Returns: %TRUE if the operation succeded, %FALSE if @error is set.
+ */
+gboolean
+mm_manager_report_kernel_event_sync (MMManager                *manager,
+                                     MMKernelEventProperties  *properties,
+                                     GCancellable             *cancellable,
+                                     GError                  **error)
+{
+    GVariant *dictionary;
+    gboolean  result;
+
+    g_return_val_if_fail (MM_IS_MANAGER (manager), FALSE);
+
+    if (!ensure_modem_manager1_proxy (manager, error))
+        return FALSE;
+
+    dictionary = mm_kernel_event_properties_get_dictionary (properties);
+    result = (mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_sync (
+                  manager->priv->manager_iface_proxy,
+                  dictionary,
+                  cancellable,
+                  error));
+    g_variant_unref (dictionary);
+    return result;
+}
+
+/*****************************************************************************/
+
 static void
 register_dbus_errors (void)
 {
diff --git a/libmm-glib/mm-manager.h b/libmm-glib/mm-manager.h
index 96c3066..57a129f 100644
--- a/libmm-glib/mm-manager.h
+++ b/libmm-glib/mm-manager.h
@@ -33,6 +33,7 @@
 #include <ModemManager.h>
 
 #include "mm-gdbus-modem.h"
+#include "mm-kernel-event-properties.h"
 
 G_BEGIN_DECLS
 
@@ -108,6 +109,19 @@ gboolean mm_manager_scan_devices_sync (MMManager     *manager,
                                        GCancellable  *cancellable,
                                        GError       **error);
 
+void     mm_manager_report_kernel_event        (MMManager                *manager,
+                                                MMKernelEventProperties  *properties,
+                                                GCancellable             *cancellable,
+                                                GAsyncReadyCallback       callback,
+                                                gpointer                  user_data);
+gboolean mm_manager_report_kernel_event_finish (MMManager                *manager,
+                                                GAsyncResult             *res,
+                                                GError                  **error);
+gboolean mm_manager_report_kernel_event_sync   (MMManager                *manager,
+                                                MMKernelEventProperties  *properties,
+                                                GCancellable             *cancellable,
+                                                GError                  **error);
+
 G_END_DECLS
 
 #endif /* _MM_MANAGER_H_ */
diff --git a/src/kerneldevice/mm-kernel-device-udev.c b/src/kerneldevice/mm-kernel-device-udev.c
index 4953f48..f4b47d8 100644
--- a/src/kerneldevice/mm-kernel-device-udev.c
+++ b/src/kerneldevice/mm-kernel-device-udev.c
@@ -21,11 +21,15 @@
 #include "mm-kernel-device-udev.h"
 #include "mm-log.h"
 
-G_DEFINE_TYPE (MMKernelDeviceUdev, mm_kernel_device_udev,  MM_TYPE_KERNEL_DEVICE)
+static void initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMKernelDeviceUdev, mm_kernel_device_udev,  MM_TYPE_KERNEL_DEVICE, 0,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
 
 enum {
     PROP_0,
     PROP_UDEV_DEVICE,
+    PROP_PROPERTIES,
     PROP_LAST
 };
 
@@ -37,6 +41,8 @@ struct _MMKernelDeviceUdevPrivate {
     GUdevDevice *physdev;
     guint16      vendor;
     guint16      product;
+
+    MMKernelEventProperties *properties;
 };
 
 /*****************************************************************************/
@@ -153,8 +159,11 @@ ensure_device_ids (MMKernelDeviceUdev *self)
     if (self->priv->vendor || self->priv->product)
         return;
 
+    if (!self->priv->device)
+        return;
+
     if (!get_device_ids (self->priv->device, &self->priv->vendor, &self->priv->product))
-        mm_dbg ("(%s/%s) could not get vendor/product ID",
+        mm_dbg ("(%s/%s) could not get vendor/product id",
                 g_udev_device_get_subsystem (self->priv->device),
                 g_udev_device_get_name      (self->priv->device));
 }
@@ -240,25 +249,42 @@ ensure_physdev (MMKernelDeviceUdev *self)
 {
     if (self->priv->physdev)
         return;
-    self->priv->physdev = find_physical_gudevdevice (self->priv->device);
+    if (self->priv->device)
+        self->priv->physdev = find_physical_gudevdevice (self->priv->device);
 }
 
 /*****************************************************************************/
 
 static const gchar *
-kernel_device_get_subsystem (MMKernelDevice *self)
+kernel_device_get_subsystem (MMKernelDevice *_self)
 {
-    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (self), NULL);
+    MMKernelDeviceUdev *self;
+
+    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), NULL);
+
+    self = MM_KERNEL_DEVICE_UDEV (_self);
+
+    if (self->priv->device)
+        return g_udev_device_get_subsystem (self->priv->device);
 
-    return g_udev_device_get_subsystem (MM_KERNEL_DEVICE_UDEV (self)->priv->device);
+    g_assert (self->priv->properties);
+    return mm_kernel_event_properties_get_subsystem (self->priv->properties);
 }
 
 static const gchar *
-kernel_device_get_name (MMKernelDevice *self)
+kernel_device_get_name (MMKernelDevice *_self)
 {
-    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (self), NULL);
+    MMKernelDeviceUdev *self;
+
+    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), NULL);
+
+    self = MM_KERNEL_DEVICE_UDEV (_self);
 
-    return g_udev_device_get_name (MM_KERNEL_DEVICE_UDEV (self)->priv->device);
+    if (self->priv->device)
+        return g_udev_device_get_name (self->priv->device);
+
+    g_assert (self->priv->properties);
+    return mm_kernel_event_properties_get_name (self->priv->properties);
 }
 
 static const gchar *
@@ -271,6 +297,9 @@ kernel_device_get_driver (MMKernelDevice *_self)
 
     self = MM_KERNEL_DEVICE_UDEV (_self);
 
+    if (!self->priv->device)
+        return NULL;
+
     driver = g_udev_device_get_driver (self->priv->device);
     if (!driver) {
         GUdevDevice *parent;
@@ -308,6 +337,9 @@ kernel_device_get_sysfs_path (MMKernelDevice *self)
 {
     g_return_val_if_fail (MM_IS_KERNEL_DEVICE (self), NULL);
 
+    if (!MM_KERNEL_DEVICE_UDEV (self)->priv->device)
+        return NULL;
+
     return g_udev_device_get_sysfs_path (MM_KERNEL_DEVICE_UDEV (self)->priv->device);
 }
 
@@ -320,14 +352,20 @@ kernel_device_get_physdev_uid (MMKernelDevice *_self)
     g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), NULL);
 
     self = MM_KERNEL_DEVICE_UDEV (_self);
-    ensure_physdev (self);
 
-    if (!self->priv->physdev)
-        return NULL;
+    /* Prefer the one coming in the properties, if any */
+    if (self->priv->properties)
+        uid = mm_kernel_event_properties_get_uid (MM_KERNEL_DEVICE_UDEV (self)->priv->properties);
 
-    uid = g_udev_device_get_property (self->priv->physdev, "ID_MM_PHYSDEV_UID");
-    if (!uid)
-        uid = g_udev_device_get_sysfs_path (self->priv->physdev);
+    if (!uid) {
+        ensure_physdev (self);
+        if (!self->priv->physdev)
+            return NULL;
+
+        uid = g_udev_device_get_property (self->priv->physdev, "ID_MM_PHYSDEV_UID");
+        if (!uid)
+            uid = g_udev_device_get_sysfs_path (self->priv->physdev);
+    }
 
     return uid;
 }
@@ -364,7 +402,8 @@ kernel_device_get_parent_sysfs_path (MMKernelDevice *_self)
     g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), 0);
 
     self = MM_KERNEL_DEVICE_UDEV (_self);
-    if (!self->priv->parent)
+
+    if (!self->priv->parent && self->priv->device)
         self->priv->parent = g_udev_device_get_parent (self->priv->device);
     return (self->priv->parent ? g_udev_device_get_sysfs_path (self->priv->parent) : NULL);
 }
@@ -382,6 +421,9 @@ kernel_device_is_candidate (MMKernelDevice *_self,
 
     self = MM_KERNEL_DEVICE_UDEV (_self);
 
+    if (!self->priv->device)
+        return FALSE;
+
     name   = g_udev_device_get_name      (self->priv->device);
     subsys = g_udev_device_get_subsystem (self->priv->device);
 
@@ -455,17 +497,22 @@ kernel_device_cmp (MMKernelDevice *_a,
     a = MM_KERNEL_DEVICE_UDEV (_a);
     b = MM_KERNEL_DEVICE_UDEV (_b);
 
-    if (g_udev_device_has_property (a->priv->device, "DEVPATH_OLD") &&
-        g_str_has_suffix (g_udev_device_get_sysfs_path (b->priv->device),
-                          g_udev_device_get_property   (a->priv->device, "DEVPATH_OLD")))
-        return TRUE;
+    if (a->priv->device && b->priv->device) {
+        if (g_udev_device_has_property (a->priv->device, "DEVPATH_OLD") &&
+            g_str_has_suffix (g_udev_device_get_sysfs_path (b->priv->device),
+                              g_udev_device_get_property   (a->priv->device, "DEVPATH_OLD")))
+            return TRUE;
 
-    if (g_udev_device_has_property (b->priv->device, "DEVPATH_OLD") &&
-        g_str_has_suffix (g_udev_device_get_sysfs_path (a->priv->device),
-                          g_udev_device_get_property   (b->priv->device, "DEVPATH_OLD")))
-        return TRUE;
+        if (g_udev_device_has_property (b->priv->device, "DEVPATH_OLD") &&
+            g_str_has_suffix (g_udev_device_get_sysfs_path (a->priv->device),
+                              g_udev_device_get_property   (b->priv->device, "DEVPATH_OLD")))
+            return TRUE;
+
+        return !g_strcmp0 (g_udev_device_get_sysfs_path (a->priv->device), g_udev_device_get_sysfs_path (b->priv->device));
+    }
 
-    return !g_strcmp0 (g_udev_device_get_sysfs_path (a->priv->device), g_udev_device_get_sysfs_path (b->priv->device));
+    return (!g_strcmp0 (mm_kernel_device_get_subsystem (_a), mm_kernel_device_get_subsystem (_b)) &&
+            !g_strcmp0 (mm_kernel_device_get_name      (_a), mm_kernel_device_get_name      (_b)));
 }
 
 static gboolean
@@ -477,6 +524,10 @@ kernel_device_has_property (MMKernelDevice *_self,
     g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), FALSE);
 
     self = MM_KERNEL_DEVICE_UDEV (_self);
+
+    if (!self->priv->device)
+        return FALSE;
+
     return g_udev_device_has_property (self->priv->device, property);
 }
 
@@ -489,6 +540,10 @@ kernel_device_get_property (MMKernelDevice *_self,
     g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), NULL);
 
     self = MM_KERNEL_DEVICE_UDEV (_self);
+
+    if (!self->priv->device)
+        return NULL;
+
     return g_udev_device_get_property (self->priv->device, property);
 }
 
@@ -501,6 +556,10 @@ kernel_device_get_property_as_boolean (MMKernelDevice *_self,
     g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), FALSE);
 
     self = MM_KERNEL_DEVICE_UDEV (_self);
+
+    if (!self->priv->device)
+        return FALSE;
+
     return g_udev_device_get_property_as_boolean (self->priv->device, property);
 }
 
@@ -513,6 +572,10 @@ kernel_device_get_property_as_int (MMKernelDevice *_self,
     g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), -1);
 
     self = MM_KERNEL_DEVICE_UDEV (_self);
+
+    if (!self->priv->device)
+        return -1;
+
     return g_udev_device_get_property_as_int (self->priv->device, property);
 }
 
@@ -521,11 +584,31 @@ kernel_device_get_property_as_int (MMKernelDevice *_self,
 MMKernelDevice *
 mm_kernel_device_udev_new (GUdevDevice *udev_device)
 {
+    GError *error = NULL;
+    MMKernelDevice *self;
+
     g_return_val_if_fail (G_UDEV_IS_DEVICE (udev_device), NULL);
 
-    return MM_KERNEL_DEVICE (g_object_new (MM_TYPE_KERNEL_DEVICE_UDEV,
-                                           "udev-device", udev_device,
-                                           NULL));
+    self = MM_KERNEL_DEVICE (g_initable_new (MM_TYPE_KERNEL_DEVICE_UDEV,
+                                             NULL,
+                                             &error,
+                                             "udev-device", udev_device,
+                                             NULL));
+    g_assert_no_error (error);
+    return self;
+}
+
+/*****************************************************************************/
+
+MMKernelDevice *
+mm_kernel_device_udev_new_from_properties (MMKernelEventProperties  *properties,
+                                           GError                  **error)
+{
+    return MM_KERNEL_DEVICE (g_initable_new (MM_TYPE_KERNEL_DEVICE_UDEV,
+                                             NULL,
+                                             error,
+                                             "properties", properties,
+                                             NULL));
 }
 
 /*****************************************************************************/
@@ -550,6 +633,10 @@ set_property (GObject      *object,
         g_assert (!self->priv->device);
         self->priv->device = g_value_dup_object (value);
         break;
+    case PROP_PROPERTIES:
+        g_assert (!self->priv->properties);
+        self->priv->properties = g_value_dup_object (value);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -568,12 +655,73 @@ get_property (GObject    *object,
     case PROP_UDEV_DEVICE:
         g_value_set_object (value, self->priv->device);
         break;
+    case PROP_PROPERTIES:
+        g_value_set_object (value, self->priv->properties);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
     }
 }
 
+static gboolean
+initable_init (GInitable     *initable,
+               GCancellable  *cancellable,
+               GError       **error)
+{
+    MMKernelDeviceUdev *self = MM_KERNEL_DEVICE_UDEV (initable);
+    const gchar *subsystem;
+    const gchar *name;
+
+    /* When created from a GUdevDevice, we're done */
+    if (self->priv->device)
+        return TRUE;
+
+    /* Otherwise, we do need properties with subsystem and name */
+    if (!self->priv->properties) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                     "missing properties in kernel device");
+        return FALSE;
+    }
+
+    subsystem = mm_kernel_event_properties_get_subsystem (self->priv->properties);
+    if (!subsystem) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                     "subsystem is mandatory in kernel device");
+        return FALSE;
+    }
+
+    name = mm_kernel_event_properties_get_name (self->priv->properties);
+    if (!mm_kernel_device_get_name (MM_KERNEL_DEVICE (self))) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                     "name is mandatory in kernel device");
+        return FALSE;
+    }
+
+    /* On remove events, we don't look for the GUdevDevice */
+    if (g_strcmp0 (mm_kernel_event_properties_get_action (self->priv->properties), "remove")) {
+        GUdevClient *client;
+        GUdevDevice *device;
+
+        client = g_udev_client_new (NULL);
+        device = g_udev_client_query_by_subsystem_and_name (client, subsystem, name);
+        if (!device) {
+            g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                         "device %s/%s not found",
+                         subsystem,
+                         name);
+            g_object_unref (client);
+            return FALSE;
+        }
+
+        /* Store device */
+        self->priv->device = device;
+        g_object_unref (client);
+    }
+
+    return TRUE;
+}
+
 static void
 dispose (GObject *object)
 {
@@ -582,11 +730,18 @@ dispose (GObject *object)
     g_clear_object (&self->priv->physdev);
     g_clear_object (&self->priv->parent);
     g_clear_object (&self->priv->device);
+    g_clear_object (&self->priv->properties);
 
     G_OBJECT_CLASS (mm_kernel_device_udev_parent_class)->dispose (object);
 }
 
 static void
+initable_iface_init (GInitableIface *iface)
+{
+    iface->init = initable_init;
+}
+
+static void
 mm_kernel_device_udev_class_init (MMKernelDeviceUdevClass *klass)
 {
     GObjectClass        *object_class        = G_OBJECT_CLASS (klass);
@@ -618,6 +773,14 @@ mm_kernel_device_udev_class_init (MMKernelDeviceUdevClass *klass)
                              "udev device",
                              "Device object as reported by GUdev",
                              G_UDEV_TYPE_DEVICE,
-                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+                             G_PARAM_READWRITE);
     g_object_class_install_property (object_class, PROP_UDEV_DEVICE, properties[PROP_UDEV_DEVICE]);
+
+    properties[PROP_PROPERTIES] =
+        g_param_spec_object ("properties",
+                             "Properties",
+                             "Generic kernel event properties",
+                             MM_TYPE_KERNEL_EVENT_PROPERTIES,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+    g_object_class_install_property (object_class, PROP_PROPERTIES, properties[PROP_PROPERTIES]);
 }
diff --git a/src/kerneldevice/mm-kernel-device-udev.h b/src/kerneldevice/mm-kernel-device-udev.h
index ed83159..9096ca7 100644
--- a/src/kerneldevice/mm-kernel-device-udev.h
+++ b/src/kerneldevice/mm-kernel-device-udev.h
@@ -20,6 +20,9 @@
 #include <glib-object.h>
 #include <gudev/gudev.h>
 
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
 #include "mm-kernel-device.h"
 
 #define MM_TYPE_KERNEL_DEVICE_UDEV            (mm_kernel_device_udev_get_type ())
@@ -42,7 +45,9 @@ struct _MMKernelDeviceUdevClass {
     MMKernelDeviceClass parent;
 };
 
-GType           mm_kernel_device_udev_get_type (void);
-MMKernelDevice *mm_kernel_device_udev_new      (GUdevDevice *udev_device);
+GType           mm_kernel_device_udev_get_type            (void);
+MMKernelDevice *mm_kernel_device_udev_new                 (GUdevDevice              *udev_device);
+MMKernelDevice *mm_kernel_device_udev_new_from_properties (MMKernelEventProperties  *properties,
+                                                           GError                  **error);
 
 #endif /* MM_KERNEL_DEVICE_UDEV_H */
diff --git a/src/main.c b/src/main.c
index 8a3ebab..d83baaf 100644
--- a/src/main.c
+++ b/src/main.c
@@ -86,7 +86,8 @@ bus_acquired_cb (GDBusConnection *connection,
     g_assert (!manager);
     manager = mm_base_manager_new (connection,
                                    mm_context_get_test_plugin_dir (),
-                                   !mm_context_get_test_no_auto_scan (),
+                                   !mm_context_get_no_auto_scan (),
+                                   mm_context_get_initial_kernel_events (),
                                    mm_context_get_test_enable (),
                                    &error);
     if (!manager) {
diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c
index 9da3896..3e03d55 100644
--- a/src/mm-base-manager.c
+++ b/src/mm-base-manager.c
@@ -47,6 +47,7 @@ enum {
     PROP_AUTO_SCAN,
     PROP_ENABLE_TEST,
     PROP_PLUGIN_DIR,
+    PROP_INITIAL_KERNEL_EVENTS,
     LAST_PROP
 };
 
@@ -59,6 +60,8 @@ struct _MMBaseManagerPrivate {
     gboolean enable_test;
     /* Path to look for plugins */
     gchar *plugin_dir;
+    /* Path to the list of initial kernel events */
+    gchar *initial_kernel_events;
     /* The UDev client */
     GUdevClient *udev;
     /* The authorization provider */
@@ -238,6 +241,11 @@ device_added (MMBaseManager  *manager,
 
     g_return_if_fail (port != NULL);
 
+    mm_dbg ("(%s/%s): adding device at sysfs path: %s",
+            mm_kernel_device_get_subsystem (port),
+            mm_kernel_device_get_name (port),
+            mm_kernel_device_get_sysfs_path (port));
+
     if (!mm_kernel_device_is_candidate (port, manual_scan)) {
         /* This could mean that device changed, losing its ID_MM_CANDIDATE
          * flags (such as Bluetooth RFCOMM devices upon disconnect.
@@ -267,6 +275,11 @@ device_added (MMBaseManager  *manager,
     if (!device) {
         FindDeviceSupportContext *ctx;
 
+        mm_dbg ("(%s/%s): first port in device %s",
+                mm_kernel_device_get_subsystem (port),
+                mm_kernel_device_get_name (port),
+                physdev_uid);
+
         /* Keep the device listed in the Manager */
         device = mm_device_new (physdev_uid, hotplugged, FALSE);
         g_hash_table_insert (manager->priv->devices,
@@ -282,12 +295,73 @@ device_added (MMBaseManager  *manager,
             device,
             (GAsyncReadyCallback) device_support_check_ready,
             ctx);
-    }
+    } else
+        mm_dbg ("(%s/%s): additional port in device %s",
+                mm_kernel_device_get_subsystem (port),
+                mm_kernel_device_get_name (port),
+                physdev_uid);
 
     /* Grab the port in the existing device. */
     mm_device_grab_port (device, port);
 }
 
+static gboolean
+handle_kernel_event (MMBaseManager            *self,
+                     MMKernelEventProperties  *properties,
+                     GError                  **error)
+{
+    MMKernelDevice *kernel_device;
+    const gchar    *action;
+    const gchar    *subsystem;
+    const gchar    *name;
+    const gchar    *uid;
+
+    action = mm_kernel_event_properties_get_action (properties);
+    if (!action) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Missing mandatory parameter 'action'");
+        return FALSE;
+    }
+    if (g_strcmp0 (action, "add") != 0 && g_strcmp0 (action, "remove") != 0) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Invalid 'action' parameter given: '%s' (expected 'add' or 'remove')", action);
+        return FALSE;
+    }
+
+    subsystem = mm_kernel_event_properties_get_subsystem (properties);
+    if (!subsystem) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Missing mandatory parameter 'subsystem'");
+        return FALSE;
+    }
+
+    name = mm_kernel_event_properties_get_name (properties);
+    if (!name) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Missing mandatory parameter 'name'");
+        return FALSE;
+    }
+
+    uid = mm_kernel_event_properties_get_uid (properties);
+
+    mm_dbg ("Kernel event reported:");
+    mm_dbg ("  action:    %s", action);
+    mm_dbg ("  subsystem: %s", subsystem);
+    mm_dbg ("  name:      %s", name);
+    mm_dbg ("  uid:       %s", uid ? uid : "n/a");
+
+    kernel_device = mm_kernel_device_udev_new_from_properties (properties, error);
+
+    if (!kernel_device)
+        return FALSE;
+
+    if (g_strcmp0 (action, "add") == 0)
+        device_added (self, kernel_device, TRUE, TRUE);
+    else if (g_strcmp0 (action, "remove") == 0)
+        device_removed (self, kernel_device);
+    else
+        g_assert_not_reached ();
+    g_object_unref (kernel_device);
+
+    return TRUE;
+}
+
 static void
 handle_uevent (GUdevClient *client,
                const char *action,
@@ -356,57 +430,112 @@ start_device_added (MMBaseManager *self,
     g_idle_add ((GSourceFunc)start_device_added_idle, ctx);
 }
 
-void
-mm_base_manager_start (MMBaseManager *manager,
-                       gboolean manual_scan)
+static void
+process_scan (MMBaseManager *self,
+              gboolean       manual_scan)
 {
     GList *devices, *iter;
 
-    g_return_if_fail (manager != NULL);
-    g_return_if_fail (MM_IS_BASE_MANAGER (manager));
-
-    if (!manager->priv->auto_scan && !manual_scan)
-        return;
-
-    mm_dbg ("Starting %s device scan...", manual_scan ? "manual" : "automatic");
-
-    devices = g_udev_client_query_by_subsystem (manager->priv->udev, "tty");
+    devices = g_udev_client_query_by_subsystem (self->priv->udev, "tty");
     for (iter = devices; iter; iter = g_list_next (iter)) {
-        start_device_added (manager, G_UDEV_DEVICE (iter->data), manual_scan);
+        start_device_added (self, G_UDEV_DEVICE (iter->data), manual_scan);
         g_object_unref (G_OBJECT (iter->data));
     }
     g_list_free (devices);
 
-    devices = g_udev_client_query_by_subsystem (manager->priv->udev, "net");
+    devices = g_udev_client_query_by_subsystem (self->priv->udev, "net");
     for (iter = devices; iter; iter = g_list_next (iter)) {
-        start_device_added (manager, G_UDEV_DEVICE (iter->data), manual_scan);
+        start_device_added (self, G_UDEV_DEVICE (iter->data), manual_scan);
         g_object_unref (G_OBJECT (iter->data));
     }
     g_list_free (devices);
 
-    devices = g_udev_client_query_by_subsystem (manager->priv->udev, "usb");
+    devices = g_udev_client_query_by_subsystem (self->priv->udev, "usb");
     for (iter = devices; iter; iter = g_list_next (iter)) {
         const gchar *name;
 
         name = g_udev_device_get_name (G_UDEV_DEVICE (iter->data));
         if (name && g_str_has_prefix (name, "cdc-wdm"))
-            start_device_added (manager, G_UDEV_DEVICE (iter->data), manual_scan);
+            start_device_added (self, G_UDEV_DEVICE (iter->data), manual_scan);
         g_object_unref (G_OBJECT (iter->data));
     }
     g_list_free (devices);
 
     /* Newer kernels report 'usbmisc' subsystem */
-    devices = g_udev_client_query_by_subsystem (manager->priv->udev, "usbmisc");
+    devices = g_udev_client_query_by_subsystem (self->priv->udev, "usbmisc");
     for (iter = devices; iter; iter = g_list_next (iter)) {
         const gchar *name;
 
         name = g_udev_device_get_name (G_UDEV_DEVICE (iter->data));
         if (name && g_str_has_prefix (name, "cdc-wdm"))
-            start_device_added (manager, G_UDEV_DEVICE (iter->data), manual_scan);
+            start_device_added (self, G_UDEV_DEVICE (iter->data), manual_scan);
         g_object_unref (G_OBJECT (iter->data));
     }
     g_list_free (devices);
+}
+
+static void
+process_initial_kernel_events (MMBaseManager *self)
+{
+    gchar *contents = NULL;
+    gchar *line;
+    GError *error = NULL;
+
+    if (!self->priv->initial_kernel_events)
+        return;
+
+    if (!g_file_get_contents (self->priv->initial_kernel_events, &contents, NULL, &error)) {
+        g_warning ("Couldn't load initial kernel events: %s", error->message);
+        g_error_free (error);
+        return;
+    }
+
+    line = contents;
+    while (line) {
+        gchar *next;
+
+        next = strchr (line, '\n');
+        if (next) {
+            *next = '\0';
+            next++;
+        }
+
+        /* ignore empty lines */
+        if (line[0] != '\0') {
+            MMKernelEventProperties *properties;
+
+            properties = mm_kernel_event_properties_new_from_string (line, &error);
+            if (!properties) {
+                g_warning ("Couldn't parse line '%s' as initial kernel event %s", line, error->message);
+                g_clear_error (&error);
+            } else if (!handle_kernel_event (self, properties, &error)) {
+                g_warning ("Couldn't process line '%s' as initial kernel event %s", line, error->message);
+                g_clear_error (&error);
+            } else
+                g_debug ("Processed initial kernel event:' %s'", line);
+        }
+
+        line = next;
+    }
+
+    g_free (contents);
+}
+
+void
+mm_base_manager_start (MMBaseManager *self,
+                       gboolean       manual_scan)
+{
+    g_return_if_fail (self != NULL);
+    g_return_if_fail (MM_IS_BASE_MANAGER (self));
+
+    if (!self->priv->auto_scan && !manual_scan) {
+        /* If we have a list of initial kernel events, process it now */
+        process_initial_kernel_events (self);
+        return;
+    }
 
+    mm_dbg ("Starting %s device scan...", manual_scan ? "manual" : "automatic");
+    process_scan (self, manual_scan);
     mm_dbg ("Finished device scan...");
 }
 
@@ -615,6 +744,81 @@ handle_scan_devices (MmGdbusOrgFreedesktopModemManager1 *manager,
 }
 
 /*****************************************************************************/
+
+typedef struct {
+    MMBaseManager *self;
+    GDBusMethodInvocation *invocation;
+    GVariant *dictionary;
+} ReportKernelEventContext;
+
+static void
+report_kernel_event_context_free (ReportKernelEventContext *ctx)
+{
+    g_object_unref (ctx->invocation);
+    g_object_unref (ctx->self);
+    g_variant_unref (ctx->dictionary);
+    g_slice_free (ReportKernelEventContext, ctx);
+}
+
+static void
+report_kernel_event_auth_ready (MMAuthProvider           *authp,
+                                GAsyncResult             *res,
+                                ReportKernelEventContext *ctx)
+{
+    GError                  *error = NULL;
+    MMKernelEventProperties *properties = NULL;
+
+    if (!mm_auth_provider_authorize_finish (authp, res, &error))
+        goto out;
+
+    if (ctx->self->priv->auto_scan) {
+        error = g_error_new_literal (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+                                     "Cannot report kernel event: "
+                                     "udev monitoring already in place");
+        goto out;
+    }
+
+    properties = mm_kernel_event_properties_new_from_dictionary (ctx->dictionary, &error);
+    if (!properties)
+        goto out;
+
+    handle_kernel_event (ctx->self, properties, &error);
+
+out:
+    if (error)
+        g_dbus_method_invocation_take_error (ctx->invocation, error);
+    else
+        mm_gdbus_org_freedesktop_modem_manager1_complete_report_kernel_event (
+            MM_GDBUS_ORG_FREEDESKTOP_MODEM_MANAGER1 (ctx->self),
+            ctx->invocation);
+
+    if (properties)
+        g_object_unref (properties);
+    report_kernel_event_context_free (ctx);
+}
+
+static gboolean
+handle_report_kernel_event (MmGdbusOrgFreedesktopModemManager1 *manager,
+                            GDBusMethodInvocation *invocation,
+                            GVariant *dictionary)
+{
+    ReportKernelEventContext *ctx;
+
+    ctx = g_slice_new0 (ReportKernelEventContext);
+    ctx->self = g_object_ref (manager);
+    ctx->invocation = g_object_ref (invocation);
+    ctx->dictionary = g_variant_ref (dictionary);
+
+    mm_auth_provider_authorize (ctx->self->priv->authp,
+                                invocation,
+                                MM_AUTHORIZATION_MANAGER_CONTROL,
+                                ctx->self->priv->authp_cancellable,
+                                (GAsyncReadyCallback)report_kernel_event_auth_ready,
+                                ctx);
+    return TRUE;
+}
+
+/*****************************************************************************/
 /* Test profile setup */
 
 static gboolean
@@ -684,6 +888,7 @@ MMBaseManager *
 mm_base_manager_new (GDBusConnection *connection,
                      const gchar *plugin_dir,
                      gboolean auto_scan,
+                     const gchar *initial_kernel_events,
                      gboolean enable_test,
                      GError **error)
 {
@@ -695,6 +900,7 @@ mm_base_manager_new (GDBusConnection *connection,
                            MM_BASE_MANAGER_CONNECTION, connection,
                            MM_BASE_MANAGER_PLUGIN_DIR, plugin_dir,
                            MM_BASE_MANAGER_AUTO_SCAN, auto_scan,
+                           MM_BASE_MANAGER_INITIAL_KERNEL_EVENTS, initial_kernel_events,
                            MM_BASE_MANAGER_ENABLE_TEST, enable_test,
                            NULL);
 }
@@ -740,6 +946,10 @@ set_property (GObject *object,
         g_free (priv->plugin_dir);
         priv->plugin_dir = g_value_dup_string (value);
         break;
+    case PROP_INITIAL_KERNEL_EVENTS:
+        g_free (priv->initial_kernel_events);
+        priv->initial_kernel_events = g_value_dup_string (value);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -767,6 +977,9 @@ get_property (GObject *object,
     case PROP_PLUGIN_DIR:
         g_value_set_string (value, priv->plugin_dir);
         break;
+    case PROP_INITIAL_KERNEL_EVENTS:
+        g_value_set_string (value, priv->initial_kernel_events);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -812,6 +1025,10 @@ mm_base_manager_init (MMBaseManager *manager)
                       "handle-scan-devices",
                       G_CALLBACK (handle_scan_devices),
                       NULL);
+    g_signal_connect (manager,
+                      "handle-report-kernel-event",
+                      G_CALLBACK (handle_report_kernel_event),
+                      NULL);
 }
 
 static gboolean
@@ -864,6 +1081,7 @@ finalize (GObject *object)
 {
     MMBaseManagerPrivate *priv = MM_BASE_MANAGER (object)->priv;
 
+    g_free (priv->initial_kernel_events);
     g_free (priv->plugin_dir);
 
     g_hash_table_destroy (priv->devices);
@@ -943,4 +1161,12 @@ mm_base_manager_class_init (MMBaseManagerClass *manager_class)
                               "Where to look for plugins",
                               NULL,
                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+    g_object_class_install_property
+        (object_class, PROP_INITIAL_KERNEL_EVENTS,
+         g_param_spec_string (MM_BASE_MANAGER_INITIAL_KERNEL_EVENTS,
+                              "Initial kernel events",
+                              "Path to a file with the list of initial kernel events",
+                              NULL,
+                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 }
diff --git a/src/mm-base-manager.h b/src/mm-base-manager.h
index 43e7cae..56b3016 100644
--- a/src/mm-base-manager.h
+++ b/src/mm-base-manager.h
@@ -34,6 +34,7 @@
 #define MM_BASE_MANAGER_AUTO_SCAN   "auto-scan"   /* Construct-only */
 #define MM_BASE_MANAGER_ENABLE_TEST "enable-test" /* Construct-only */
 #define MM_BASE_MANAGER_PLUGIN_DIR  "plugin-dir"  /* Construct-only */
+#define MM_BASE_MANAGER_INITIAL_KERNEL_EVENTS "initial-kernel-events" /* Construct-only */
 
 typedef struct _MMBaseManagerPrivate MMBaseManagerPrivate;
 
@@ -51,6 +52,7 @@ GType mm_base_manager_get_type (void);
 MMBaseManager   *mm_base_manager_new         (GDBusConnection *bus,
                                               const gchar *plugin_dir,
                                               gboolean auto_scan,
+                                              const gchar *initial_kernel_events,
                                               gboolean enable_test,
                                               GError **error);
 
diff --git a/src/mm-context.c b/src/mm-context.c
index df8d07c..cf8025b 100644
--- a/src/mm-context.c
+++ b/src/mm-context.c
@@ -26,6 +26,8 @@ static const gchar *log_level;
 static const gchar *log_file;
 static gboolean show_ts;
 static gboolean rel_ts;
+static const gchar *initial_kernel_events;
+static gboolean no_auto_scan;
 
 static const GOptionEntry entries[] = {
     { "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag, "Print version", NULL },
@@ -34,6 +36,8 @@ static const GOptionEntry entries[] = {
     { "log-file", 0, 0, G_OPTION_ARG_FILENAME, &log_file, "Path to log file", "[PATH]" },
     { "timestamps", 0, 0, G_OPTION_ARG_NONE, &show_ts, "Show timestamps in log output", NULL },
     { "relative-timestamps", 0, 0, G_OPTION_ARG_NONE, &rel_ts, "Use relative timestamps (from MM start)", NULL },
+    { "no-auto-scan", 0, 0, G_OPTION_ARG_NONE, &no_auto_scan, "Don't auto-scan looking for devices", NULL },
+    { "initial-kernel-events", 0, 0, G_OPTION_ARG_FILENAME, &initial_kernel_events, "Path to initial kernel events file (requires --no-auto-scan)", "[PATH]" },
     { NULL }
 };
 
@@ -67,17 +71,27 @@ mm_context_get_relative_timestamps (void)
     return rel_ts;
 }
 
+const gchar *
+mm_context_get_initial_kernel_events (void)
+{
+    return initial_kernel_events;
+}
+
+gboolean
+mm_context_get_no_auto_scan (void)
+{
+    return no_auto_scan;
+}
+
 /*****************************************************************************/
 /* Test context */
 
 static gboolean test_session;
-static gboolean test_no_auto_scan;
 static gboolean test_enable;
 static gchar *test_plugin_dir;
 
 static const GOptionEntry test_entries[] = {
     { "test-session", 0, 0, G_OPTION_ARG_NONE, &test_session, "Run in session DBus", NULL },
-    { "test-no-auto-scan", 0, 0, G_OPTION_ARG_NONE, &test_no_auto_scan, "Don't auto-scan looking for devices", NULL },
     { "test-enable", 0, 0, G_OPTION_ARG_NONE, &test_enable, "Enable the Test interface in the daemon", NULL },
     { "test-plugin-dir", 0, 0, G_OPTION_ARG_FILENAME, &test_plugin_dir, "Path to look for plugins", "[PATH]" },
     { NULL }
@@ -104,12 +118,6 @@ mm_context_get_test_session (void)
 }
 
 gboolean
-mm_context_get_test_no_auto_scan (void)
-{
-    return test_no_auto_scan;
-}
-
-gboolean
 mm_context_get_test_enable (void)
 {
     return test_enable;
@@ -149,7 +157,7 @@ mm_context_init (gint argc,
     g_option_context_add_group (ctx, test_get_option_group ());
 
     if (!g_option_context_parse (ctx, &argc, &argv, &error)) {
-        g_warning ("%s\n", error->message);
+        g_warning ("error: %s", error->message);
         g_error_free (error);
         exit (1);
     }
@@ -166,4 +174,10 @@ mm_context_init (gint argc,
     /* If just version requested, print and exit */
     if (version_flag)
         print_version ();
+
+    /* Initial kernel events processing may only be used if autoscan is disabled */
+    if (!no_auto_scan && initial_kernel_events) {
+        g_warning ("error: --initial-kernel-events must be used only if --no-auto-scan is also used");
+        exit (1);
+    }
 }
diff --git a/src/mm-context.h b/src/mm-context.h
index 6627a60..63a8ec4 100644
--- a/src/mm-context.h
+++ b/src/mm-context.h
@@ -26,16 +26,17 @@
 void mm_context_init (gint argc,
                       gchar **argv);
 
-gboolean     mm_context_get_debug               (void);
-const gchar *mm_context_get_log_level           (void);
-const gchar *mm_context_get_log_file            (void);
-gboolean     mm_context_get_timestamps          (void);
-gboolean     mm_context_get_relative_timestamps (void);
+gboolean     mm_context_get_debug                 (void);
+const gchar *mm_context_get_log_level             (void);
+const gchar *mm_context_get_log_file              (void);
+gboolean     mm_context_get_timestamps            (void);
+gboolean     mm_context_get_relative_timestamps   (void);
+const gchar *mm_context_get_initial_kernel_events (void);
+gboolean     mm_context_get_no_auto_scan          (void);
 
 /* Testing support */
-gboolean     mm_context_get_test_session        (void);
-gboolean     mm_context_get_test_no_auto_scan   (void);
-gboolean     mm_context_get_test_enable         (void);
-const gchar *mm_context_get_test_plugin_dir     (void);
+gboolean     mm_context_get_test_session    (void);
+gboolean     mm_context_get_test_enable     (void);
+const gchar *mm_context_get_test_plugin_dir (void);
 
 #endif /* MM_CONTEXT_H */
-- 
2.9.3



More information about the ModemManager-devel mailing list