[PATCH 04/10] api: new ReportKernelEvent() api method to report kernel device addition/removals

Dan Williams dcbw at redhat.com
Fri Aug 19 19:55:42 UTC 2016


On Sat, 2016-08-06 at 15:03 +0200, Aleksander Morgado wrote:
> The implementation allows different parameters being reported in the
> kernel
> event, but only 3 of them are mandatory: 'action', 'name' and
> 'subsystem'. Once
> these three parameters are received, ModemManager will load the
> remaining kernel
> device information directly from sysfs.
> 
> E.g.:
>     $ sudo mmcli --report-kernel-event=" \
>             action=add, \
>             name=wwan0, \
>             subsystem=net
> 
> The 'physdev-uid' optional parameter may be given to bind together
> multiple
> ports of the same device under a common known name.
> 
> The 'physdev-vid', 'physdev-pid' and 'driver' optional parameters may
> be given
> to force a given device to be managed as if it was some other device.
> This
> doesn't have any hard use case in real life, unless for testing
> devices (e.g. so
> that different filters get applied during runtime).
> 
> E.g.:
>     $ sudo mmcli --report-kernel-event=" \
>             action=add, \
>             name=wwan0, \
>             subsystem=net, \
>             physdev-uid=usb-modem-rear-usb-port, \
>             physdev-vid=1199, \
>             physdev-pid=68a2, \
>             driver=qmi_wwan"

In the API docs it talks about USB vid/pid, but MM does support at
least one PCI-native device; the Option Nozomi.  SDIO also uses 16-bit
vid/pid, though I'm not sure why anyone would use SDIO to hook up a
WWAN modem :)  Maybe just say it's a USB or PCI vid/pid for now, but
could be used for more in the future?

Which means I'm not sure it should be a 'q'.  Maybe just an 's' with
guint16 validation for now?

Also, would be nice to have some unit tests for the udev rules file
parsing code...

Dan

> The 'candidate' optional parameter may be given to force treating as
> candidate
> a given port that otherwise would have been ignored by the automatic
> rules
> applied by ModemManager.
> 
> E.g.:
>     $ sudo mmcli --report-kernel-event=" \
>             action=add, \
>             name=/dev/ttyS0, \
>             subsystem=tty,
> 	    candidate=yes
> ---
>  cli/mmcli-manager.c                               |   77 +-
>  docs/reference/libmm-glib/libmm-glib-docs.xml     |    1 +
>  docs/reference/libmm-glib/libmm-glib-sections.txt |   49 +-
>  introspection/org.freedesktop.ModemManager1.xml   |  121 +++
>  libmm-glib/Makefile.am                            |    3 +
>  libmm-glib/libmm-glib.h                           |    1 +
>  libmm-glib/mm-common-helpers.c                    |   43 +
>  libmm-glib/mm-common-helpers.h                    |    2 +
>  libmm-glib/mm-kernel-event-properties.c           |  689
> ++++++++++++++
>  libmm-glib/mm-kernel-event-properties.h           |  113 +++
>  libmm-glib/mm-manager.c                           |  124 +++
>  libmm-glib/mm-manager.h                           |   14 +
>  src/Makefile.am                                   |    6 +
>  src/kerneldevice/mm-kernel-device-generic.c       | 1010
> +++++++++++++++++++++
>  src/kerneldevice/mm-kernel-device-generic.h       |   51 ++
>  src/mm-base-manager.c                             |  147 ++-
>  16 files changed, 2444 insertions(+), 7 deletions(-)
>  create mode 100644 libmm-glib/mm-kernel-event-properties.c
>  create mode 100644 libmm-glib/mm-kernel-event-properties.h
>  create mode 100644 src/kerneldevice/mm-kernel-device-generic.c
>  create mode 100644 src/kerneldevice/mm-kernel-device-generic.h
> 
> diff --git a/cli/mmcli-manager.c b/cli/mmcli-manager.c
> index 9960412..d78734f 100644
> --- a/cli/mmcli-manager.c
> +++ b/cli/mmcli-manager.c
> @@ -47,6 +47,7 @@ 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 GOptionEntry entries[] = {
>      { "set-logging", 'G', 0, G_OPTION_ARG_STRING, &set_logging_str,
> @@ -65,6 +66,10 @@ 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,...\"]"
> +    },
>      { NULL }
>  };
>  
> @@ -96,7 +101,8 @@ 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);
>  
>      if (n_actions > 1) {
>          g_printerr ("error: too many manager actions requested\n");
> @@ -130,6 +136,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)
>  {
> @@ -276,6 +323,20 @@ 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;
> +    }
> +
>      /* Request to monitor modems? */
>      if (monitor_modems_flag) {
>          g_signal_connect (ctx->manager,
> @@ -362,6 +423,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/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..804a032 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,46 @@ mm_manager_get_type
>  </SECTION>
>  
>  <SECTION>
> +<FILE>mm-kernel-event-properties</FILE>
> +<TITLE>MMKernelEventProperties</TITLE>
> +MMKernelEventProperties
> +<SUBSECTION New>
> +mm_kernel_event_properties_new
> +mm_kernel_event_properties_dup
> +<SUBSECTION GettersSetters>
> +mm_kernel_event_properties_get_action
> +mm_kernel_event_properties_set_action
> +mm_kernel_event_properties_get_candidate
> +mm_kernel_event_properties_set_candidate
> +mm_kernel_event_properties_get_driver
> +mm_kernel_event_properties_set_driver
> +mm_kernel_event_properties_get_name
> +mm_kernel_event_properties_set_name
> +mm_kernel_event_properties_get_physdev_pid
> +mm_kernel_event_properties_set_physdev_pid
> +mm_kernel_event_properties_get_physdev_uid
> +mm_kernel_event_properties_set_physdev_uid
> +mm_kernel_event_properties_get_physdev_vid
> +mm_kernel_event_properties_set_physdev_vid
> +mm_kernel_event_properties_get_subsystem
> +mm_kernel_event_properties_set_subsystem
> +<SUBSECTION Private>
> +mm_kernel_event_properties_get_dictionary
> +mm_kernel_event_properties_new_from_dictionary
> +mm_kernel_event_properties_new_from_string
> +<SUBSECTION Standard>
> +MMKernelEventPropertiesClass
> +MMKernelEventPropertiesPrivate
> +MM_IS_KERNEL_EVENT_PROPERTIES
> +MM_IS_KERNEL_EVENT_PROPERTIES_CLASS
> +MM_KERNEL_EVENT_PROPERTIES
> +MM_KERNEL_EVENT_PROPERTIES_CLASS
> +MM_KERNEL_EVENT_PROPERTIES_GET_CLASS
> +MM_TYPE_KERNEL_EVENT_PROPERTIES
> +mm_kernel_event_properties_get_type
> +</SECTION>
> +
> +<SECTION>
>  <FILE>mm-object</FILE>
>  <TITLE>MMObject</TITLE>
>  MMObject
> @@ -295,7 +338,6 @@ mm_modem_3gpp_ussd_get_type
>  <FILE>mm-cdma-manual-activation-properties</FILE>
>  <TITLE>MMCdmaManualActivationProperties</TITLE>
>  MMCdmaManualActivationProperties
> -
>  <SUBSECTION Methods>
>  mm_cdma_manual_activation_properties_new
>  mm_cdma_manual_activation_properties_get_spc
> @@ -896,7 +938,6 @@ MMModemVoice
>  <SUBSECTION Getters>
>  mm_modem_voice_get_path
>  mm_modem_voice_dup_path
> -
>  <SUBSECTION Methods>
>  mm_modem_voice_create_call
>  mm_modem_voice_create_call_finish
> @@ -1555,10 +1596,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_fin
> ish
> +mm_gdbus_org_freedesktop_modem_manager1_call_report_kernel_event_syn
> c
>  <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..32c896b 100644
> --- a/introspection/org.freedesktop.ModemManager1.xml
> +++ b/introspection/org.freedesktop.ModemManager1.xml
> @@ -37,5 +37,126 @@
>        <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>driver</literal></term>
> +            <listitem>
> +              <para>
> +                The device driver, given as a string value
> (signature
> +                <literal>"s"</literal>).
> +                This parameter is OPTIONAL, if not given it will be
> retrieved
> +                from sysfs.
> +              </para>
> +            </listitem>
> +          </varlistentry>
> +
> +          <varlistentry><term><literal>physdev-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>
> +
> +          <varlistentry><term><literal>physdev-vid</literal></term>
> +            <listitem>
> +              <para>
> +                The USB vendor ID of the physical device, given as a
> 16 bit
> +                unsigned integer (signature <literal>"q"</literal>.
> +                This parameter is OPTIONAL, if not given it will be
> retrieved
> +                from sysfs. This parameter may be used to override
> the real one
> +                exposed by the device.
> +              </para>
> +            </listitem>
> +          </varlistentry>
> +
> +          <varlistentry><term><literal>physdev-pid</literal></term>
> +            <listitem>
> +              <para>
> +                The USB product ID of the physical device, given as
> a 16 bit
> +                unsigned integer (signature <literal>"q"</literal>.
> +                This parameter is OPTIONAL, if not given it will be
> retrieved
> +                from sysfs. This parameter may be used to override
> the real one
> +                exposed by the device.
> +              </para>
> +            </listitem>
> +          </varlistentry>
> +
> +          <varlistentry><term><literal>candidate</literal></term>
> +            <listitem>
> +              <para>
> +                Flag to force treating the device as a candidate,
> despite other
> +                rules that ModemManager may have, given as a boolean
> value
> +                (signature <literal>"b"</literal>).
> +                This parameter is OPTIONAL, and not specifying it
> has the same
> +                effect as setting it to false.
> +              </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-common-helpers.c b/libmm-glib/mm-common-
> helpers.c
> index c1ddd89..2af5850 100644
> --- a/libmm-glib/mm-common-helpers.c
> +++ b/libmm-glib/mm-common-helpers.c
> @@ -1385,6 +1385,49 @@ mm_get_uint_from_str (const gchar *str,
>      return FALSE;
>  }
>  
> +/**
> + * mm_get_uint_from_hex_str:
> + * @str: the hex string to convert to an unsigned int
> + * @out: on success, the number
> + *
> + * Converts a string to an unsigned number.  All characters in the
> string
> + * MUST be valid hexadecimal digits (0-9, A-F, a-f), otherwise FALSE
> is
> + * returned.
> + *
> + * An optional "0x" prefix may be given in @str.
> + *
> + * Returns: %TRUE if the string was converted, %FALSE if it was not
> or if it
> + * did not contain only digits.
> + */
> +gboolean
> +mm_get_uint_from_hex_str (const gchar *str,
> +                          guint       *out)
> +{
> +    gulong num;
> +
> +    if (!str)
> +        return FALSE;
> +
> +    if (g_str_has_prefix (str, "0x"))
> +        str = &str[2];
> +
> +    if (!str[0])
> +        return FALSE;
> +
> +    for (num = 0; str[num]; num++) {
> +        if (!g_ascii_isxdigit (str[num]))
> +            return FALSE;
> +    }
> +
> +    errno = 0;
> +    num = strtoul (str, NULL, 16);
> +    if (!errno && num <= G_MAXUINT) {
> +        *out = (guint)num;
> +        return TRUE;
> +    }
> +    return FALSE;
> +}
> +
>  gboolean
>  mm_get_uint_from_match_info (GMatchInfo *match_info,
>                               guint32 match_index,
> diff --git a/libmm-glib/mm-common-helpers.h b/libmm-glib/mm-common-
> helpers.h
> index c65d3b9..ab5777d 100644
> --- a/libmm-glib/mm-common-helpers.h
> +++ b/libmm-glib/mm-common-helpers.h
> @@ -143,6 +143,8 @@
> gboolean  mm_get_int_from_match_info             (GMatchInfo
> *match_info,
>                                                    gint *out);
>  gboolean  mm_get_uint_from_str                   (const gchar *str,
>                                                    guint *out);
> +gboolean  mm_get_uint_from_hex_str               (const gchar *str,
> +                                                  guint       *out);
>  gboolean  mm_get_uint_from_match_info            (GMatchInfo
> *match_info,
>                                                    guint32
> match_index,
>                                                    guint *out);
> diff --git a/libmm-glib/mm-kernel-event-properties.c b/libmm-glib/mm-
> kernel-event-properties.c
> new file mode 100644
> index 0000000..5ff52f9
> --- /dev/null
> +++ b/libmm-glib/mm-kernel-event-properties.c
> @@ -0,0 +1,689 @@
> +/* -*- 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_DRIVER      "driver"
> +#define PROPERTY_PHYSDEV_UID "physdev-uid"
> +#define PROPERTY_PHYSDEV_VID "physdev-vid"
> +#define PROPERTY_PHYSDEV_PID "physdev-pid"
> +#define PROPERTY_CANDIDATE   "candidate"
> +
> +struct _MMKernelEventPropertiesPrivate {
> +    gchar   *action;
> +    gchar   *subsystem;
> +    gchar   *name;
> +    gchar   *driver;
> +    gchar   *physdev_uid;
> +    guint16  physdev_vid;
> +    guint16  physdev_pid;
> +    gboolean candidate;
> +};
> +
> +/*******************************************************************
> **********/
> +
> +/**
> + * 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_driver:
> + * @self: A #MMKernelEventProperties.
> + * @driver: The driver to set.
> + *
> + * Sets the driver.
> + */
> +void
> +mm_kernel_event_properties_set_driver (MMKernelEventProperties
> *self,
> +                                       const
> gchar             *driver)
> +{
> +    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
> +
> +    g_free (self->priv->driver);
> +    self->priv->driver = g_strdup (driver);
> +}
> +
> +/**
> + * mm_kernel_event_properties_get_driver:
> + * @self: A #MMKernelEventProperties.
> + *
> + * Gets the driver.
> + *
> + * Returns: (transfer none): The driver. Do not free the returned
> value, it is owned by @self.
> + */
> +const gchar *
> +mm_kernel_event_properties_get_driver (MMKernelEventProperties
> *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self),
> NULL);
> +
> +    return self->priv->driver;
> +}
> +
> +/*******************************************************************
> **********/
> +
> +/**
> + * mm_kernel_event_properties_set_physdev_uid:
> + * @self: A #MMKernelEventProperties.
> + * @physdev_uid: The physdev uid to set.
> + *
> + * Sets the unique ID of the physical device.
> + */
> +void
> +mm_kernel_event_properties_set_physdev_uid (MMKernelEventProperties
> *self,
> +                                            const
> gchar             *physdev_uid)
> +{
> +    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
> +
> +    g_free (self->priv->physdev_uid);
> +    self->priv->physdev_uid = g_strdup (physdev_uid);
> +}
> +
> +/**
> + * mm_kernel_event_properties_get_physdev_uid:
> + * @self: A #MMKernelEventProperties.
> + *
> + * Gets the unique ID of the physical device.
> + *
> + * Returns: (transfer none): The physdev uid. Do not free the
> returned value, it is owned by @self.
> + */
> +const gchar *
> +mm_kernel_event_properties_get_physdev_uid (MMKernelEventProperties
> *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self),
> NULL);
> +
> +    return self->priv->physdev_uid;
> +}
> +
> +/*******************************************************************
> **********/
> +
> +/**
> + * mm_kernel_event_properties_set_physdev_vid:
> + * @self: A #MMKernelEventProperties.
> + * @physdev_vid: The vendor id to set.
> + *
> + * Sets the USB vendor id of the physical device.
> + */
> +void
> +mm_kernel_event_properties_set_physdev_vid (MMKernelEventProperties
> *self,
> +                                            guint16                 
>  physdev_vid)
> +{
> +    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
> +
> +    self->priv->physdev_vid = physdev_vid;
> +}
> +
> +/**
> + * mm_kernel_event_properties_get_physdev_vid:
> + * @self: A #MMKernelEventProperties.
> + *
> + * Gets the USB vendor id of the physical device.
> + *
> + * Returns: The vendor id.
> + */
> +guint16
> +mm_kernel_event_properties_get_physdev_vid (MMKernelEventProperties
> *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), 0);
> +
> +    return self->priv->physdev_vid;
> +}
> +
> +/*******************************************************************
> **********/
> +
> +/**
> + * mm_kernel_event_properties_set_physdev_pid:
> + * @self: A #MMKernelEventProperties.
> + * @physdev_pid: The product id to set.
> + *
> + * Sets the USB product id of the physical device.
> + */
> +void
> +mm_kernel_event_properties_set_physdev_pid (MMKernelEventProperties
> *self,
> +                                            guint16                 
>  physdev_pid)
> +{
> +    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
> +
> +    self->priv->physdev_pid = physdev_pid;
> +}
> +
> +/**
> + * mm_kernel_event_properties_get_physdev_pid:
> + * @self: A #MMKernelEventProperties.
> + *
> + * Gets the USB product id of the physical device.
> + *
> + * Returns: The product id.
> + */
> +guint16
> +mm_kernel_event_properties_get_physdev_pid (MMKernelEventProperties
> *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self), 0);
> +
> +    return self->priv->physdev_pid;
> +}
> +
> +/*******************************************************************
> **********/
> +
> +/**
> + * mm_kernel_event_properties_set_candidate:
> + * @self: A #MMKernelEventProperties.
> + * @candidate: %TRUE if candidate, %FALSE otherwise.
> + *
> + * Sets the flag specifying wheter the device should be treated as a
> candidate
> + * regardless of any internal rules to filter it out.
> + */
> +void
> +mm_kernel_event_properties_set_candidate (MMKernelEventProperties
> *self,
> +                                          gboolean                 c
> andidate)
> +{
> +    g_return_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self));
> +
> +    self->priv->candidate = candidate;
> +}
> +
> +/**
> + * mm_kernel_event_properties_get_candidate:
> + * @self: A #MMKernelEventProperties.
> + *
> + * Gets the flag specifying wheter the device should be treated as a
> candidate
> + * regardless of any internal rules to filter it out.
> + *
> + * Returns: %TRUE if candidate, %FALSE otherwise.
> + */
> +gboolean
> +mm_kernel_event_properties_get_candidate (MMKernelEventProperties
> *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES (self),
> FALSE);
> +
> +    return self->priv->candidate;
> +}
> +
> +/*******************************************************************
> **********/
> +
> +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->driver)
> +        g_variant_builder_add (&builder,
> +                               "{sv}",
> +                               PROPERTY_DRIVER,
> +                               g_variant_new_string (self->priv-
> >driver));
> +
> +    if (self->priv->physdev_uid)
> +        g_variant_builder_add (&builder,
> +                               "{sv}",
> +                               PROPERTY_PHYSDEV_UID,
> +                               g_variant_new_string (self->priv-
> >physdev_uid));
> +
> +    if (self->priv->physdev_vid)
> +        g_variant_builder_add (&builder,
> +                               "{sv}",
> +                               PROPERTY_PHYSDEV_VID,
> +                               g_variant_new_uint16 (self->priv-
> >physdev_vid));
> +
> +    if (self->priv->physdev_pid)
> +        g_variant_builder_add (&builder,
> +                               "{sv}",
> +                               PROPERTY_PHYSDEV_PID,
> +                               g_variant_new_uint16 (self->priv-
> >physdev_pid));
> +
> +    /* Just avoid adding it if FALSE */
> +    if (self->priv->candidate)
> +        g_variant_builder_add (&builder,
> +                               "{sv}",
> +                               PROPERTY_CANDIDATE,
> +                               g_variant_new_boolean (TRUE));
> +
> +    return g_variant_ref_sink (g_variant_builder_end (&builder));
> +}
> +
> +/*******************************************************************
> **********/
> +
> +static guint16
> +parse_uint16 (const gchar  *str,
> +              GError      **error)
> +{
> +    guint num;
> +
> +    if (!mm_get_uint_from_hex_str (str, &num) || num > G_MAXUINT16)
> {
> +        g_set_error (error,
> +                     MM_CORE_ERROR,
> +                     MM_CORE_ERROR_INVALID_ARGS,
> +                     "Invalid properties string, cannot parse '%s'
> as hexadecimal uint16",
> +                     str);
> +        return 0;
> +    }
> +
> +    return num;
> +}
> +
> +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_DRIVER))
> +        mm_kernel_event_properties_set_driver (self, value);
> +    else if (g_str_equal (key, PROPERTY_PHYSDEV_UID))
> +        mm_kernel_event_properties_set_physdev_uid (self, value);
> +    else if (g_str_equal (key, PROPERTY_PHYSDEV_VID)) {
> +        guint16  val;
> +        GError  *inner_error = NULL;
> +
> +        val = parse_uint16 (value, &inner_error);
> +        if (inner_error) {
> +            g_propagate_error (error, inner_error);
> +            return FALSE;
> +        }
> +        mm_kernel_event_properties_set_physdev_vid (self, val);
> +    } else if (g_str_equal (key, PROPERTY_PHYSDEV_PID)) {
> +        guint16  val;
> +        GError  *inner_error = NULL;
> +
> +        val = parse_uint16 (value, &inner_error);
> +        if (inner_error) {
> +            g_propagate_error (error, inner_error);
> +            return FALSE;
> +        }
> +        mm_kernel_event_properties_set_physdev_pid (self, val);
> +    } else if (g_str_equal (key, PROPERTY_CANDIDATE)) {
> +        gboolean val ;
> +        GError  *inner_error = NULL;
> +
> +        val = mm_common_get_boolean_from_string (value,
> &inner_error);
> +        if (inner_error) {
> +            g_propagate_error (error, inner_error);
> +            return FALSE;
> +        }
> +        mm_kernel_event_properties_set_candidate (self, val);
> +    } 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_DRIVER))
> +        mm_kernel_event_properties_set_driver (
> +            properties,
> +            g_variant_get_string (value, NULL));
> +    else if (g_str_equal (key, PROPERTY_PHYSDEV_UID))
> +        mm_kernel_event_properties_set_physdev_uid (
> +            properties,
> +            g_variant_get_string (value, NULL));
> +    else if (g_str_equal (key, PROPERTY_PHYSDEV_VID))
> +        mm_kernel_event_properties_set_physdev_vid (
> +            properties,
> +            g_variant_get_uint16 (value));
> +    else if (g_str_equal (key, PROPERTY_PHYSDEV_PID))
> +        mm_kernel_event_properties_set_physdev_pid (
> +            properties,
> +            g_variant_get_uint16 (value));
> +    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_P
> ROPERTIES,
> +                                              MMKernelEventPropertie
> sPrivate);
> +}
> +
> +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->driver);
> +    g_free (self->priv->physdev_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..eb52b21
> --- /dev/null
> +++ b/libmm-glib/mm-kernel-event-properties.h
> @@ -0,0 +1,113 @@
> +/* -*- 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_propertie
> s_get_type ())
> +#define
> MM_KERNEL_EVENT_PROPERTIES(obj)            (G_TYPE_CHECK_INSTANCE_CAS
> T ((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_TYP
> E ((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      (MMKernelEv
> entProperties *self,
> +                                                          const
> gchar             *action);
> +const
> gchar  *mm_kernel_event_properties_get_action      (MMKernelEventProp
> erties *self);
> +
> +void          mm_kernel_event_properties_set_subsystem   (MMKernelEv
> entProperties *self,
> +                                                          const
> gchar             *subsystem);
> +const
> gchar  *mm_kernel_event_properties_get_subsystem   (MMKernelEventProp
> erties *self);
> +
> +void          mm_kernel_event_properties_set_name        (MMKernelEv
> entProperties *self,
> +                                                          const
> gchar             *name);
> +const
> gchar  *mm_kernel_event_properties_get_name        (MMKernelEventProp
> erties *self);
> +
> +void          mm_kernel_event_properties_set_driver      (MMKernelEv
> entProperties *self,
> +                                                          const
> gchar             *driver);
> +const
> gchar  *mm_kernel_event_properties_get_driver      (MMKernelEventProp
> erties *self);
> +
> +void          mm_kernel_event_properties_set_physdev_uid
> (MMKernelEventProperties *self,
> +                                                          const
> gchar             *physdev_uid);
> +const gchar  *mm_kernel_event_properties_get_physdev_uid
> (MMKernelEventProperties *self);
> +
> +void          mm_kernel_event_properties_set_physdev_vid
> (MMKernelEventProperties *self,
> +                                                          guint16   
>                physdev_vid);
> +guint16       mm_kernel_event_properties_get_physdev_vid
> (MMKernelEventProperties *self);
> +
> +void          mm_kernel_event_properties_set_physdev_pid
> (MMKernelEventProperties *self,
> +                                                          guint16   
>                physdev_pid);
> +guint16       mm_kernel_event_properties_get_physdev_pid
> (MMKernelEventProperties *self);
> +
> +void          mm_kernel_event_properties_set_candidate   (MMKernelEv
> entProperties *self,
> +                                                          gboolean  
>                candidate);
> +gboolean      mm_kernel_event_properties_get_candidate   (MMKernelEv
> entProperties *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                 (MMKernelEventPropert
> ies *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_fi
> nish (
> +            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             *cancellabl
> e,
> +                                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  *prope
> rties,
> +                                     GCancellable             *cance
> llable,
> +                                     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_syn
> c (
> +                  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,
> +                                                MMKernelEventPropert
> ies  *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                *mana
> ger,
> +                                                MMKernelEventPropert
> ies  *properties,
> +                                                GCancellable        
>      *cancellable,
> +                                                GError              
>     **error);
> +
>  G_END_DECLS
>  
>  #endif /* _MM_MANAGER_H_ */
> diff --git a/src/Makefile.am b/src/Makefile.am
> index f64f1c1..e16d059 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -172,9 +172,15 @@ CLEANFILES    += $(PORT_ENUMS_GENERATED)
>  
>  noinst_LTLIBRARIES += libkerneldevice.la
>  
> +libkerneldevice_la_CPPFLAGS = \
> +	-DUDEVRULESDIR=\"$(udevrulesdir)\" \
> +	$(NULL)
> +
>  libkerneldevice_la_SOURCES = \
>  	kerneldevice/mm-kernel-device.h \
>  	kerneldevice/mm-kernel-device.c \
> +	kerneldevice/mm-kernel-device-generic.h \
> +	kerneldevice/mm-kernel-device-generic.c \
>  	$(NULL)
>  
>  if WITH_UDEV
> diff --git a/src/kerneldevice/mm-kernel-device-generic.c
> b/src/kerneldevice/mm-kernel-device-generic.c
> new file mode 100644
> index 0000000..04fce70
> --- /dev/null
> +++ b/src/kerneldevice/mm-kernel-device-generic.c
> @@ -0,0 +1,1010 @@
> +/* -*- 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.
> + */
> +
> +#define _GNU_SOURCE
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#define _LIBMM_INSIDE_MM
> +#include <libmm-glib.h>
> +
> +#include "mm-kernel-device-generic.h"
> +#include "mm-log.h"
> +
> +#if !defined UDEVRULESDIR
> +# error UDEVRULESDIR is not defined
> +#endif
> +
> +/* Define the following symbol to enable verbose rule parsing traces
> */
> +#undef TRACE_UDEV_RULES
> +
> +#if defined TRACE_UDEV_RULES
> +# define trace(...) mm_dbg (__VA_ARGS__)
> +#else
> +# define trace(...)
> +#endif
> +
> +static void initable_iface_init (GInitableIface *iface);
> +
> +G_DEFINE_TYPE_EXTENDED (MMKernelDeviceGeneric,
> mm_kernel_device_generic,  MM_TYPE_KERNEL_DEVICE, 0,
> +                        G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
> initable_iface_init))
> +
> +enum {
> +    PROP_0,
> +    PROP_PROPERTIES,
> +    PROP_LAST
> +};
> +
> +static GParamSpec *properties[PROP_LAST];
> +
> +struct _MMKernelDeviceGenericPrivate {
> +    /* Input properties */
> +    MMKernelEventProperties *properties;
> +
> +    /* Contents from sysfs */
> +    gchar   *driver;
> +    gchar   *sysfs_path;
> +    gchar   *parent_sysfs_path;
> +    gchar   *physdev_sysfs_path;
> +    guint16  physdev_vid;
> +    guint16  physdev_pid;
> +};
> +
> +static guint
> +read_sysfs_property_as_hex (const gchar *path,
> +                            const gchar *property)
> +{
> +    gchar *aux;
> +    gchar *contents = NULL;
> +    guint val = 0;
> +
> +    aux = g_strdup_printf ("%s/%s", path, property);
> +    if (g_file_get_contents (aux, &contents, NULL, NULL)) {
> +        g_strdelimit (contents, "\r\n", ' ');
> +        g_strstrip (contents);
> +        mm_get_uint_from_hex_str (contents, &val);
> +    }
> +    g_free (contents);
> +    g_free (aux);
> +    return val;
> +}
> +
> +static gchar *
> +read_sysfs_property_as_string (const gchar *path,
> +                               const gchar *property)
> +{
> +    gchar *aux;
> +    gchar *contents = NULL;
> +
> +    aux = g_strdup_printf ("%s/%s", path, property);
> +    if (g_file_get_contents (aux, &contents, NULL, NULL)) {
> +        g_strdelimit (contents, "\r\n", ' ');
> +        g_strstrip (contents);
> +    }
> +    g_free (aux);
> +    return contents;
> +}
> +
> +/*******************************************************************
> **********/
> +/* Load contents */
> +
> +static void
> +preload_sysfs_path (MMKernelDeviceGeneric *self)
> +{
> +    gchar *tmp;
> +
> +    if (self->priv->sysfs_path)
> +        return;
> +
> +    /* sysfs can be built directly using subsystem and name; e.g.
> for subsystem
> +     * usbmisc and name cdc-wdm0:
> +     *    $ realpath /sys/class/usbmisc/cdc-wdm0
> +     *    /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3/4-
> 1.3:1.8/usbmisc/cdc-wdm0
> +     */
> +    tmp = g_strdup_printf ("/sys/class/%s/%s",
> +                           mm_kernel_event_properties_get_subsystem
> (self->priv->properties),
> +                           mm_kernel_event_properties_get_name      
> (self->priv->properties));
> +
> +    self->priv->sysfs_path = canonicalize_file_name (tmp);
> +    if (!self->priv->sysfs_path || !g_file_test (self->priv-
> >sysfs_path, G_FILE_TEST_EXISTS)) {
> +        mm_warn ("Invalid sysfs path read for %s/%s",
> +                 mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                 mm_kernel_event_properties_get_name      (self-
> >priv->properties));
> +        g_clear_pointer (&self->priv->sysfs_path, g_free);
> +    }
> +
> +    if (self->priv->sysfs_path)
> +        mm_dbg ("(%s/%s) sysfs path: %s",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties),
> +                self->priv->sysfs_path);
> +    g_free (tmp);
> +}
> +
> +static void
> +preload_parent_sysfs_path (MMKernelDeviceGeneric *self)
> +{
> +    gchar *dirpath;
> +    gchar *aux;
> +
> +    if (self->priv->parent_sysfs_path || !self->priv->sysfs_path)
> +        return;
> +
> +    /* parent sysfs can be built directly using subsystem and name;
> e.g. for
> +     * subsystem usbmisc and name cdc-wdm0:
> +     *    $ realpath /sys/class/usbmisc/cdc-wdm0/device
> +     *    /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3/4-
> 1.3:1.8
> +     *
> +     * This sysfs path will be equal for all devices exported from
> within the
> +     * same interface (e.g. a pair of cdc-wdm/wwan devices).
> +     *
> +     * The correct parent dir we want to have is the first one with
> "usb" subsystem.
> +     */
> +    aux = g_strdup_printf ("%s/device", self->priv->sysfs_path);
> +    dirpath = canonicalize_file_name (aux);
> +    g_free (aux);
> +
> +    while (dirpath) {
> +        gchar *subsystem_filepath;
> +
> +        /* Directory must exist */
> +        if (!g_file_test (dirpath, G_FILE_TEST_EXISTS))
> +            break;
> +
> +        /* If subsystem file not found, keep looping */
> +        subsystem_filepath = g_strdup_printf ("%s/subsystem",
> dirpath);
> +        if (g_file_test (subsystem_filepath, G_FILE_TEST_EXISTS)) {
> +            gchar *canonicalized_subsystem;
> +            gchar *subsystem_name;
> +
> +            canonicalized_subsystem = canonicalize_file_name
> (subsystem_filepath);
> +            g_free (subsystem_filepath);
> +
> +            subsystem_name = g_path_get_basename
> (canonicalized_subsystem);
> +            g_free (canonicalized_subsystem);
> +
> +            if (subsystem_name && g_str_equal (subsystem_name,
> "usb")) {
> +                self->priv->parent_sysfs_path = dirpath;
> +                g_free (subsystem_name);
> +                break;
> +            }
> +        } else
> +            g_free (subsystem_filepath);
> +
> +        /* Just in case */
> +        if (g_str_equal (dirpath, "/")) {
> +            g_free (dirpath);
> +            break;
> +        }
> +
> +        aux = g_path_get_dirname (dirpath);
> +        g_free (dirpath);
> +        dirpath = aux;
> +    }
> +
> +    if (self->priv->parent_sysfs_path)
> +        mm_dbg ("(%s/%s) parent sysfs path: %s",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties),
> +                self->priv->parent_sysfs_path);
> +}
> +
> +static void
> +preload_physdev_sysfs_path (MMKernelDeviceGeneric *self)
> +{
> +    /* physdev sysfs is the dirname of the parent sysfs path, e.g.:
> +     *    /sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3
> +     *
> +     * This sysfs path will be equal for all devices exported from
> the same
> +     * physical device.
> +     */
> +    if (!self->priv->physdev_sysfs_path && self->priv-
> >parent_sysfs_path)
> +        self->priv->physdev_sysfs_path = g_path_get_dirname (self-
> >priv->parent_sysfs_path);
> +
> +    if (self->priv->physdev_sysfs_path)
> +        mm_dbg ("(%s/%s) physdev sysfs path: %s",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties),
> +                self->priv->physdev_sysfs_path);
> +}
> +
> +static void
> +preload_driver (MMKernelDeviceGeneric *self)
> +{
> +    if (!self->priv->driver && self->priv->parent_sysfs_path) {
> +        gchar *tmp;
> +        gchar *tmp2;
> +
> +        tmp = g_strdup_printf ("%s/driver", self->priv-
> >parent_sysfs_path);
> +        tmp2 = canonicalize_file_name (tmp);
> +        if (tmp2 && g_file_test (tmp2, G_FILE_TEST_EXISTS))
> +            self->priv->driver = g_path_get_basename (tmp2);
> +        g_free (tmp2);
> +        g_free (tmp);
> +    }
> +
> +    if (self->priv->driver)
> +        mm_dbg ("(%s/%s) driver: %s",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties),
> +                self->priv->driver);
> +}
> +
> +static void
> +preload_physdev_vid (MMKernelDeviceGeneric *self)
> +{
> +    if (!self->priv->physdev_vid && self->priv->physdev_sysfs_path)
> {
> +        guint val;
> +
> +        val = read_sysfs_property_as_hex (self->priv-
> >physdev_sysfs_path, "idVendor");
> +        if (val && val <= G_MAXUINT16)
> +            self->priv->physdev_vid = val;
> +    }
> +
> +    if (self->priv->physdev_vid)
> +        mm_dbg ("(%s/%s) vid: 0x%04x",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties),
> +                self->priv->physdev_vid);
> +}
> +
> +static void
> +preload_physdev_pid (MMKernelDeviceGeneric *self)
> +{
> +    if (!self->priv->physdev_pid && self->priv->physdev_sysfs_path)
> {
> +        guint val;
> +
> +        val = read_sysfs_property_as_hex (self->priv-
> >physdev_sysfs_path, "idProduct");
> +        if (val && val <= G_MAXUINT16)
> +            self->priv->physdev_pid = val;
> +    }
> +
> +    if (self->priv->physdev_pid)
> +        mm_dbg ("(%s/%s) pid: 0x%04x",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties),
> +                self->priv->physdev_pid);
> +}
> +
> +static void
> +preload_contents (MMKernelDeviceGeneric *self)
> +{
> +    preload_sysfs_path         (self);
> +    preload_parent_sysfs_path  (self);
> +    preload_physdev_sysfs_path (self);
> +
> +    if (!mm_kernel_event_properties_get_driver
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties))
> +        preload_driver (self);
> +
> +    if (!mm_kernel_event_properties_get_physdev_vid
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties))
> +        preload_physdev_vid (self);
> +
> +    if (!mm_kernel_event_properties_get_physdev_pid
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties))
> +        preload_physdev_pid (self);
> +}
> +
> +/*******************************************************************
> **********/
> +
> +static const gchar *
> +kernel_device_get_subsystem (MMKernelDevice *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
> +
> +    return mm_kernel_event_properties_get_subsystem
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
> +}
> +
> +static const gchar *
> +kernel_device_get_name (MMKernelDevice *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
> +
> +    return mm_kernel_event_properties_get_name
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
> +}
> +
> +static const gchar *
> +kernel_device_get_sysfs_path (MMKernelDevice *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
> +
> +    return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path;
> +}
> +
> +static const gchar *
> +kernel_device_get_parent_sysfs_path (MMKernelDevice *self)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
> +
> +    return MM_KERNEL_DEVICE_GENERIC (self)->priv->parent_sysfs_path;
> +}
> +
> +static const gchar *
> +kernel_device_get_physdev_uid (MMKernelDevice *self)
> +{
> +    const gchar *physdev_uid;
> +
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
> +
> +    /* Prefer the one coming in the properties, if any */
> +    physdev_uid = mm_kernel_event_properties_get_physdev_uid
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
> +    if (!physdev_uid)
> +        physdev_uid = MM_KERNEL_DEVICE_GENERIC (self)->priv-
> >physdev_sysfs_path;
> +    /* If there is no physdev sysfs path, e.g. for platform ports,
> use the device sysfs itself */
> +    if (!physdev_uid)
> +        physdev_uid = MM_KERNEL_DEVICE_GENERIC (self)->priv-
> >sysfs_path;
> +    return physdev_uid;
> +}
> +
> +static const gchar *
> +kernel_device_get_driver (MMKernelDevice *self)
> +{
> +    const gchar *driver;
> +
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
> +
> +    /* Prefer the one coming in the properties, if any */
> +    driver = mm_kernel_event_properties_get_driver
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
> +    if (!driver)
> +        driver = MM_KERNEL_DEVICE_GENERIC (self)->priv->driver;
> +    return driver;
> +}
> +
> +static guint16
> +kernel_device_get_physdev_vid (MMKernelDevice *self)
> +{
> +    guint16 physdev_vid;
> +
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), 0);
> +
> +    /* Prefer the one coming in the properties, if any */
> +    physdev_vid = mm_kernel_event_properties_get_physdev_vid
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
> +    if (!physdev_vid)
> +        physdev_vid = MM_KERNEL_DEVICE_GENERIC (self)->priv-
> >physdev_vid;
> +
> +    return physdev_vid;
> +}
> +
> +static guint16
> +kernel_device_get_physdev_pid (MMKernelDevice *self)
> +{
> +    guint16 physdev_pid;
> +
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), 0);
> +
> +    /* Prefer the one coming in the properties, if any */
> +    physdev_pid = mm_kernel_event_properties_get_physdev_pid
> (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties);
> +    if (!physdev_pid)
> +        physdev_pid = MM_KERNEL_DEVICE_GENERIC (self)->priv-
> >physdev_pid;
> +
> +    return physdev_pid;
> +}
> +
> +static gboolean
> +kernel_device_is_candidate (MMKernelDevice *_self,
> +                            gboolean        manual_scan)
> +{
> +    MMKernelDeviceGeneric *self;
> +    const gchar           *name;
> +
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (_self),
> FALSE);
> +
> +    self = MM_KERNEL_DEVICE_GENERIC (_self);
> +
> +    /* If being candidate is forced, do it */
> +    if (mm_kernel_event_properties_get_candidate (self->priv-
> >properties)) {
> +        mm_dbg ("(%s/%s) device forced to be candidate",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties));
> +        return TRUE;
> +    }
> +
> +    name = mm_kernel_event_properties_get_name (self->priv-
> >properties);
> +
> +    /* ignore VTs */
> +    if (strncmp (name, "tty", 3) == 0 && g_ascii_isdigit (name[3]))
> {
> +        mm_dbg ("(%s/%s) VT ignored",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties));
> +        return FALSE;
> +    }
> +
> +    /* only ports tagged as candidate */
> +    if (!mm_kernel_device_get_property_as_boolean (_self,
> "ID_MM_CANDIDATE")) {
> +        mm_dbg ("(%s/%s) device not flagged with ID_MM_CANDIDATE",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties));
> +        return FALSE;
> +    }
> +
> +    /* no devices without physical device */
> +    if (!self->priv->physdev_sysfs_path) {
> +        mm_dbg ("(%s/%s) device without physdev sysfs path",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties));
> +        return FALSE;
> +    }
> +
> +    /* ignore ports explicitly ignored; note that in this case the
> property
> +     * is set in this kernel device itself, unlike in the udev
> backend, that
> +     * goes in the parent udev device */
> +    if (mm_kernel_device_get_property_as_boolean (_self,
> "ID_MM_DEVICE_IGNORE")) {
> +        mm_dbg ("(%s/%s) device flagged with ID_MM_DEVICE_IGNORE",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties));
> +        return FALSE;
> +    }
> +
> +    mm_dbg ("(%s/%s) device is candidate",
> +            mm_kernel_event_properties_get_subsystem (self->priv-
> >properties),
> +            mm_kernel_event_properties_get_name      (self->priv-
> >properties));
> +    return TRUE;
> +}
> +
> +static gboolean
> +kernel_device_cmp (MMKernelDevice *a,
> +                   MMKernelDevice *b)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (a), FALSE);
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (b), FALSE);
> +
> +    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 const gchar *expected_rules_prefix[] = { "77-mm-", "80-mm-"
> };
> +
> +static GList *
> +list_rule_files (void)
> +{
> +    GFile           *udevrulesdir;
> +    GFileEnumerator *enumerator;
> +    GList           *children = NULL;
> +
> +    udevrulesdir = g_file_new_for_path (UDEVRULESDIR);
> +    enumerator   = g_file_enumerate_children (udevrulesdir,
> +                                              G_FILE_ATTRIBUTE_STAND
> ARD_NAME,
> +                                              G_FILE_QUERY_INFO_NONE
> ,
> +                                              NULL,
> +                                              NULL);
> +    if (enumerator) {
> +        GFileInfo *info;
> +
> +        /* If we get any kind of error, assume we need to stop
> enumerating */
> +        while ((info = g_file_enumerator_next_file (enumerator,
> NULL, NULL)) != NULL) {
> +            guint i;
> +
> +            for (i = 0; i < G_N_ELEMENTS (expected_rules_prefix);
> i++) {
> +                if (g_str_has_prefix (g_file_info_get_name (info),
> expected_rules_prefix[i])) {
> +                    children = g_list_prepend (children,
> g_build_path (G_DIR_SEPARATOR_S, UDEVRULESDIR, g_file_info_get_name
> (info), NULL));
> +                    break;
> +                }
> +            }
> +            g_object_unref (info);
> +        }
> +        g_object_unref (enumerator);
> +    }
> +    g_object_unref (udevrulesdir);
> +
> +    return g_list_sort (children, (GCompareFunc) g_strcmp0);
> +}
> +
> +static gboolean
> +apply_result (MMKernelDeviceGeneric *self,
> +              gchar                 *rule,
> +              gchar                **goto_label)
> +{
> +    gchar *aux;
> +    gchar *right;
> +
> +    g_strstrip (rule);
> +
> +    aux = strchr (rule, '=');
> +    if (!aux) {
> +        mm_warn ("Invalid rule to apply: missing '='");
> +        return FALSE;
> +    }
> +    right = aux + 1;
> +    *aux = '\0';
> +
> +    if (strlen (right) == 0) {
> +        mm_warn ("Invalid rule to apply: missing right operand");
> +        return FALSE;
> +    }
> +
> +    if (g_str_equal (rule, "GOTO")) {
> +        *goto_label = g_strdup (right);
> +        return TRUE;
> +    }
> +
> +    /* Just ignore */
> +    if (g_str_equal (rule, "LABEL"))
> +        return TRUE;
> +
> +    if (g_str_has_prefix (rule, "ENV")) {
> +        rule = &rule[3];
> +        g_strdelimit (rule, "{}", ' ');
> +        g_strstrip (rule);
> +        if (strlen (rule) == 0) {
> +            mm_warn ("Invalid rule to apply: empty property name");
> +            return FALSE;
> +        }
> +
> +        g_strdelimit (right, "\"'", ' ');
> +        g_strstrip (right);
> +        if (strlen (right) == 0) {
> +            mm_warn ("Invalid rule to apply: empty property value");
> +            return FALSE;
> +        }
> +
> +        mm_dbg ("(%s/%s) rule applied: %s=%s",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties),
> +                rule, right);
> +        g_object_set_data_full (G_OBJECT (self), rule, g_strdup
> (right), (GDestroyNotify) g_free);
> +        return TRUE;
> +    }
> +
> +    mm_warn ("Invalid rule to apply: unknown action '%s'", rule);
> +    return FALSE;
> +}
> +
> +static gboolean
> +string_match (const gchar *str,
> +              const gchar *original_pattern)
> +{
> +    gchar    *pattern;
> +    gchar    *start;
> +    gboolean  open_prefix = FALSE;
> +    gboolean  open_suffix = FALSE;
> +    gboolean  match;
> +
> +    pattern = g_strdup (original_pattern);
> +    start = pattern;
> +
> +    if (start[0] == '*') {
> +        open_prefix = TRUE;
> +        start++;
> +    }
> +
> +    if (start[strlen (start) - 1] == '*') {
> +        open_suffix = TRUE;
> +        start[strlen (start) - 1] = '\0';
> +    }
> +
> +    if (open_suffix && !open_prefix)
> +        match = g_str_has_prefix (str, start);
> +    else if (!open_suffix && open_prefix)
> +        match = g_str_has_suffix (str, start);
> +    else if (open_suffix && open_prefix)
> +        match = !!strstr (str, start);
> +    else
> +        match = g_str_equal (str, start);
> +
> +    g_free (pattern);
> +    return match;
> +}
> +
> +static gboolean
> +validate_condition (MMKernelDeviceGeneric *self,
> +                    gchar                 *condition)
> +{
> +    gboolean  condition_equal;
> +    gchar    *aux;
> +    gchar    *right;
> +    guint     val;
> +
> +    g_strstrip (condition);
> +
> +    if ((aux = strstr (condition, "==")) != NULL)
> +        condition_equal = TRUE;
> +    else if ((aux = strstr (condition, "!=")) != NULL)
> +        condition_equal = FALSE;
> +    else {
> +        mm_warn ("Invalid rule to apply: unknown condition: %s",
> condition);
> +        return FALSE;
> +    }
> +
> +    *aux = '\0';
> +    right = &aux[2];
> +    g_strdelimit (right, "\"'", ' ');
> +    g_strstrip (right);
> +
> +    trace ("condition: '%s', operation: '%s', right: '%s'",
> +           condition, condition_equal ? "==" : "!=", right);
> +
> +    /* We only apply 'add' rules */
> +    if (g_str_equal (condition, "ACTION"))
> +        return ((!!strstr (right, "add")) == condition_equal);
> +
> +    /* We look for the subsystem string in the whole sysfs path.
> +     *
> +     * Note that we're not really making a difference between
> "SUBSYSTEMS"
> +     * (where the whole device tree is checked) and "SUBSYSTEM"
> (where just one
> +     * single device is checked), because a lot of the MM udev rules
> are meant
> +     * to just tag the physical device (e.g. with
> ID_MM_DEVICE_IGNORE) instead
> +     * of the single ports. In our case with the custom parsing, we
> do tag all
> +     * independent ports.
> +     */
> +    if (g_str_equal (condition, "SUBSYSTEMS") || g_str_equal
> (condition, "SUBSYSTEM"))
> +        return ((self->priv->sysfs_path && !!strstr (self->priv-
> >sysfs_path, right)) == condition_equal);
> +
> +    /* Exact DRIVER match? We also include the check for DRIVERS,
> even if we
> +     * only apply it to this port driver. */
> +    if (g_str_equal (condition, "DRIVER") || g_str_equal (condition,
> "DRIVERS"))
> +        return ((!g_strcmp0 (right, mm_kernel_device_get_driver
> (MM_KERNEL_DEVICE (self)))) == condition_equal);
> +
> +    /* Device name checks */
> +    if (g_str_equal (condition, "KERNEL"))
> +        return (string_match (mm_kernel_device_get_name
> (MM_KERNEL_DEVICE (self)), right) == condition_equal);
> +
> +    /* Device sysfs path checks */
> +    if (g_str_equal (condition, "DEVPATH"))
> +        return (string_match (mm_kernel_device_get_sysfs_path
> (MM_KERNEL_DEVICE (self)), right) == condition_equal);
> +
> +    /* Attributes checks */
> +    if (g_str_has_prefix (condition, "ATTRS")) {
> +        gchar    *contents;
> +        gboolean  result;
> +
> +        condition = &condition[5];
> +        g_strdelimit (condition, "{}", ' ');
> +        g_strstrip (condition);
> +        g_strdelimit (right, "\"'", ' ');
> +        g_strstrip (right);
> +
> +        /* VID/PID directly from our API */
> +        if (g_str_equal (condition, "idVendor"))
> +            return ((mm_kernel_device_get_physdev_vid
> (MM_KERNEL_DEVICE (self)) == mm_get_uint_from_hex_str (right, &val))
> == condition_equal);
> +        if (g_str_equal (condition, "idProduct"))
> +            return ((mm_kernel_device_get_physdev_pid
> (MM_KERNEL_DEVICE (self)) == mm_get_uint_from_hex_str (right, &val))
> == condition_equal);
> +
> +        /* manufacturer/product in the physdev */
> +        if (g_str_equal (condition, "manufacturer") || g_str_equal
> (condition, "product")) {
> +            contents = (self->priv->physdev_sysfs_path ?
> read_sysfs_property_as_string (self->priv->sysfs_path, condition) :
> NULL);
> +            result = ((contents && g_str_equal (contents, right)) ==
> condition_equal);
> +            g_free (contents);
> +            return result;
> +        }
> +
> +        /* interface class/subclass/number in the exact device */
> +        if (g_str_equal (condition, "bInterfaceClass") ||
> +            g_str_equal (condition, "bInterfaceSubClass") ||
> +            g_str_equal (condition, "bInterfaceProtocol")) {
> +            contents = read_sysfs_property_as_string (self->priv-
> >sysfs_path, condition);
> +            result = ((contents && g_str_equal (contents, right)) ==
> condition_equal);
> +            g_free (contents);
> +            return result;
> +        }
> +
> +        mm_warn ("Invalid ATTRS: %s", condition);
> +        return FALSE;
> +    }
> +
> +    mm_warn ("Invalid rule to apply: unknown left operand: %s",
> condition);
> +    return FALSE;
> +}
> +
> +static gboolean
> +process_rule (MMKernelDeviceGeneric  *self,
> +              const gchar            *line,
> +              gchar                 **goto_label)
> +{
> +    gchar    **split;
> +    guint     n_items, i;
> +    gboolean  applied = FALSE;
> +    gboolean  validated = TRUE;
> +
> +    split = g_strsplit (line, ",", -1);
> +    n_items = g_strv_length (split);
> +    for (i = 0; validated && i < n_items; i++) {
> +        gchar *rule;
> +
> +        rule = g_strdup (split[i]);
> +        if (i < (n_items - 1)) {
> +            /* If one of the condition fails, break the rule */
> +            validated = validate_condition (self, rule);
> +            trace ("condition %s: %s", split[i], validated ? "valid"
> : "invalid");
> +        }
> +        else {
> +            /* Last item, the rule to apply */
> +            applied = apply_result (self, rule, goto_label);
> +            trace ("result %s: %s", split[i], applied ? "applied" :
> "not applied");
> +        }
> +
> +        g_free (rule);
> +    }
> +    g_strfreev (split);
> +    return applied;
> +}
> +
> +static void
> +preload_properties_from_file (MMKernelDeviceGeneric *self,
> +                              const gchar           *path)
> +{
> +    GFile            *file;
> +    GFileInputStream *fistream;
> +    GDataInputStream *distream = NULL;
> +    GError           *error = NULL;
> +    gchar            *line;
> +    gchar            *goto_label = NULL;
> +
> +    file = g_file_new_for_path (path);
> +    fistream = g_file_read (file, NULL, &error);
> +    if (!fistream)
> +        goto out;
> +
> +    distream = g_data_input_stream_new (G_INPUT_STREAM (fistream));
> +
> +    while ((line = g_data_input_stream_read_line_utf8 (distream,
> NULL, NULL, &error)) != NULL) {
> +        const gchar *aux;
> +
> +        if (goto_label) {
> +            if (strstr (line, goto_label))
> +                g_clear_pointer (&goto_label, g_free);
> +            goto next;
> +        }
> +
> +        aux = line;
> +        while (*aux == ' ')
> +            aux++;
> +        if (*aux == '#' || *aux == '\0')
> +            goto next;
> +
> +        process_rule (self, line, &goto_label);
> +
> +    next:
> +        g_free (line);
> +    }
> +
> +    g_clear_pointer (&goto_label, g_free);
> +
> +out:
> +    if (error) {
> +        mm_dbg ("(%s/%s) couldn't load properties from: %s: %s",
> +                mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                mm_kernel_event_properties_get_name      (self-
> >priv->properties),
> +                path,
> +                error->message);
> +        g_error_free (error);
> +    }
> +
> +    if (distream)
> +        g_object_unref (distream);
> +    if (fistream)
> +        g_object_unref (fistream);
> +    g_object_unref (file);
> +}
> +
> +static void
> +preload_properties (MMKernelDeviceGeneric *self)
> +{
> +    GList *rules, *l;
> +
> +    rules = list_rule_files ();
> +    for (l = rules; l; l = g_list_next (l)) {
> +        trace ("(%s/%s) loading properties from: %s",
> +               mm_kernel_event_properties_get_subsystem (self->priv-
> >properties),
> +               mm_kernel_event_properties_get_name      (self->priv-
> >properties),
> +               (const gchar *)(l->data));
> +        preload_properties_from_file (self, (const gchar *)(l-
> >data));
> +    }
> +    g_list_free_full (rules, (GDestroyNotify) g_free);
> +}
> +
> +static gboolean
> +kernel_device_has_property (MMKernelDevice *self,
> +                            const gchar    *property)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self),
> FALSE);
> +
> +    return !!g_object_get_data (G_OBJECT (self), property);
> +}
> +
> +static const gchar *
> +kernel_device_get_property (MMKernelDevice *self,
> +                            const gchar    *property)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), NULL);
> +
> +    return g_object_get_data (G_OBJECT (self), property);
> +}
> +
> +static gboolean
> +kernel_device_get_property_as_boolean (MMKernelDevice *self,
> +                                       const gchar    *property)
> +{
> +    const gchar *value;
> +
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self),
> FALSE);
> +
> +    value = g_object_get_data (G_OBJECT (self), property);
> +    return (value && mm_common_get_boolean_from_string (value,
> NULL));
> +}
> +
> +static gint
> +kernel_device_get_property_as_int (MMKernelDevice *self,
> +                                   const gchar    *property)
> +{
> +    const gchar *value;
> +    gint aux = 0;
> +
> +    g_return_val_if_fail (MM_IS_KERNEL_DEVICE_GENERIC (self), -1);
> +
> +    value = g_object_get_data (G_OBJECT (self), property);
> +    return ((value && mm_get_int_from_str (value, &aux)) ? aux : 0);
> +}
> +
> +/*******************************************************************
> **********/
> +
> +MMKernelDevice *
> +mm_kernel_device_generic_new (MMKernelEventProperties  *properties,
> +                              GError                  **error)
> +{
> +    g_return_val_if_fail (MM_IS_KERNEL_EVENT_PROPERTIES
> (properties), NULL);
> +
> +    return MM_KERNEL_DEVICE (g_initable_new
> (MM_TYPE_KERNEL_DEVICE_GENERIC,
> +                                             NULL,
> +                                             error,
> +                                             "properties",
> properties,
> +                                             NULL));
> +}
> +
> +/*******************************************************************
> **********/
> +
> +static void
> +mm_kernel_device_generic_init (MMKernelDeviceGeneric *self)
> +{
> +    /* Initialize private data */
> +    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
> MM_TYPE_KERNEL_DEVICE_GENERIC, MMKernelDeviceGenericPrivate);
> +}
> +
> +static void
> +set_property (GObject      *object,
> +              guint         prop_id,
> +              const GValue *value,
> +              GParamSpec   *pspec)
> +{
> +    MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object);
> +
> +    switch (prop_id) {
> +    case PROP_PROPERTIES:
> +        g_assert (!self->priv->properties);
> +        self->priv->properties = g_value_dup_object (value);
> +        /* Don't preload on "remove" actions, where we don't have
> the device any more,
> +         * or for devices in the 'virtual' subsystem */
> +        if (self->priv->properties &&
> +            g_strcmp0 (mm_kernel_event_properties_get_action (self-
> >priv->properties), "remove") &&
> +            g_strcmp0 (mm_kernel_event_properties_get_subsystem
> (self->priv->properties), "virtual")) {
> +            preload_contents (self);
> +            preload_properties (self);
> +        }
> +        break;
> +    default:
> +        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
> +        break;
> +    }
> +}
> +
> +static void
> +get_property (GObject    *object,
> +              guint       prop_id,
> +              GValue     *value,
> +              GParamSpec *pspec)
> +{
> +    MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object);
> +
> +    switch (prop_id) {
> +    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)
> +{
> +    MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC
> (initable);
> +
> +    if (!mm_kernel_device_get_subsystem (MM_KERNEL_DEVICE (self))) {
> +        g_set_error (error, MM_CORE_ERROR,
> MM_CORE_ERROR_INVALID_ARGS,
> +                     "subsystem is mandatory in kernel device");
> +        return FALSE;
> +    }
> +
> +    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;
> +    }
> +
> +    /* sysfs path is mandatory as output, and will only be given if
> the
> +     * specified device exists; but only if this wasn't a 'remove'
> event
> +     * and not a virtual device.
> +     */
> +    if (self->priv->properties &&
> +        g_strcmp0 (mm_kernel_event_properties_get_action (self-
> >priv->properties), "remove") &&
> +        g_strcmp0 (mm_kernel_event_properties_get_subsystem (self-
> >priv->properties), "virtual") &&
> +        !self->priv->sysfs_path) {
> +        g_set_error (error, MM_CORE_ERROR,
> MM_CORE_ERROR_INVALID_ARGS,
> +                     "device %s/%s not found",
> +                     mm_kernel_event_properties_get_subsystem (self-
> >priv->properties),
> +                     mm_kernel_event_properties_get_name      (self-
> >priv->properties));
> +        return FALSE;
> +    }
> +
> +    return TRUE;
> +}
> +
> +static void
> +dispose (GObject *object)
> +{
> +    MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object);
> +
> +    g_clear_pointer (&self->priv->physdev_sysfs_path, g_free);
> +    g_clear_pointer (&self->priv->parent_sysfs_path,  g_free);
> +    g_clear_pointer (&self->priv->sysfs_path,         g_free);
> +    g_clear_object  (&self->priv->properties);
> +
> +    G_OBJECT_CLASS (mm_kernel_device_generic_parent_class)->dispose
> (object);
> +}
> +
> +static void
> +initable_iface_init (GInitableIface *iface)
> +{
> +    iface->init = initable_init;
> +}
> +
> +static void
> +mm_kernel_device_generic_class_init (MMKernelDeviceGenericClass
> *klass)
> +{
> +    GObjectClass        *object_class        = G_OBJECT_CLASS
> (klass);
> +    MMKernelDeviceClass *kernel_device_class =
> MM_KERNEL_DEVICE_CLASS (klass);
> +
> +    g_type_class_add_private (object_class, sizeof
> (MMKernelDeviceGenericPrivate));
> +
> +    object_class->dispose      = dispose;
> +    object_class->get_property = get_property;
> +    object_class->set_property = set_property;
> +
> +    kernel_device_class->get_subsystem           =
> kernel_device_get_subsystem;
> +    kernel_device_class->get_name                =
> kernel_device_get_name;
> +    kernel_device_class->get_driver              =
> kernel_device_get_driver;
> +    kernel_device_class->get_sysfs_path          =
> kernel_device_get_sysfs_path;
> +    kernel_device_class->get_physdev_uid         =
> kernel_device_get_physdev_uid;
> +    kernel_device_class->get_physdev_vid         =
> kernel_device_get_physdev_vid;
> +    kernel_device_class->get_physdev_pid         =
> kernel_device_get_physdev_pid;
> +    kernel_device_class->get_parent_sysfs_path   =
> kernel_device_get_parent_sysfs_path;
> +    kernel_device_class->is_candidate            =
> kernel_device_is_candidate;
> +    kernel_device_class->cmp                     =
> kernel_device_cmp;
> +    kernel_device_class->has_property            =
> kernel_device_has_property;
> +    kernel_device_class->get_property            =
> kernel_device_get_property;
> +    kernel_device_class->get_property_as_boolean =
> kernel_device_get_property_as_boolean;
> +    kernel_device_class->get_property_as_int     =
> kernel_device_get_property_as_int;
> +
> +    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-generic.h
> b/src/kerneldevice/mm-kernel-device-generic.h
> new file mode 100644
> index 0000000..87f9994
> --- /dev/null
> +++ b/src/kerneldevice/mm-kernel-device-generic.h
> @@ -0,0 +1,51 @@
> +/* -*- 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_DEVICE_GENERIC_H
> +#define MM_KERNEL_DEVICE_GENERIC_H
> +
> +#include <glib.h>
> +#include <glib-object.h>
> +
> +#define _LIBMM_INSIDE_MM
> +#include <libmm-glib.h>
> +
> +#include "mm-kernel-device.h"
> +
> +#define
> MM_TYPE_KERNEL_DEVICE_GENERIC            (mm_kernel_device_generic_ge
> t_type ())
> +#define
> MM_KERNEL_DEVICE_GENERIC(obj)            (G_TYPE_CHECK_INSTANCE_CAST
> ((obj), MM_TYPE_KERNEL_DEVICE_GENERIC, MMKernelDeviceGeneric))
> +#define
> MM_KERNEL_DEVICE_GENERIC_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST
> ((klass),  MM_TYPE_KERNEL_DEVICE_GENERIC,
> MMKernelDeviceGenericClass))
> +#define
> MM_IS_KERNEL_DEVICE_GENERIC(obj)         (G_TYPE_CHECK_INSTANCE_TYPE
> ((obj), MM_TYPE_KERNEL_DEVICE_GENERIC))
> +#define MM_IS_KERNEL_DEVICE_GENERIC_CLASS(klass)
> (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_KERNEL_DEVICE_GENERIC))
> +#define
> MM_KERNEL_DEVICE_GENERIC_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS
> ((obj),  MM_TYPE_KERNEL_DEVICE_GENERIC, MMKernelDeviceGenericClass))
> +
> +typedef struct _MMKernelDeviceGeneric        MMKernelDeviceGeneric;
> +typedef struct
> _MMKernelDeviceGenericClass   MMKernelDeviceGenericClass;
> +typedef struct _MMKernelDeviceGenericPrivate
> MMKernelDeviceGenericPrivate;
> +
> +struct _MMKernelDeviceGeneric {
> +    MMKernelDevice parent;
> +    MMKernelDeviceGenericPrivate *priv;
> +};
> +
> +struct _MMKernelDeviceGenericClass {
> +    MMKernelDeviceClass parent;
> +};
> +
> +GType           mm_kernel_device_generic_get_type (void);
> +MMKernelDevice
> *mm_kernel_device_generic_new      (MMKernelEventProperties  *propert
> ies,
> +                                                   GError           
>        **error);
> +
> +#endif /* MM_KERNEL_DEVICE_GENERIC_H */
> diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c
> index 85f66a8..991b7e5 100644
> --- a/src/mm-base-manager.c
> +++ b/src/mm-base-manager.c
> @@ -26,6 +26,8 @@
>  
>  #if WITH_UDEV
>  # include "mm-kernel-device-udev.h"
> +#else
> +# include "mm-kernel-device-generic.h"
>  #endif
>  
>  #include <ModemManager.h>
> @@ -246,6 +248,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.
> @@ -275,6 +282,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,
> @@ -290,7 +302,11 @@ 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);
> @@ -425,9 +441,6 @@ mm_base_manager_start (MMBaseManager *manager,
>      }
>  #else
>      mm_dbg ("Unsupported %s device scan...", manual_scan ? "manual"
> : "automatic");
> -
> -    /* Make compiler happy */
> -    device_added (manager, NULL, FALSE, FALSE);
>  #endif
>  }
>  
> @@ -642,6 +655,128 @@ handle_scan_devices
> (MmGdbusOrgFreedesktopModemManager1 *manager,
>  }
>  
>  /*******************************************************************
> **********/
> +
> +#if !WITH_UDEV
> +
> +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;
> +    MMKernelDevice          *kernel_device;
> +    const gchar             *action;
> +
> +    if (!mm_auth_provider_authorize_finish (authp, res, &error))
> +        goto out;
> +
> +    properties = mm_kernel_event_properties_new_from_dictionary
> (ctx->dictionary, &error);
> +    if (!properties)
> +        goto out;
> +
> +    action = mm_kernel_event_properties_get_action (properties);
> +    if (g_strcmp0 (action, "add") != 0 && g_strcmp0 (action,
> "remove") != 0) {
> +        error = g_error_new_literal (MM_CORE_ERROR,
> MM_CORE_ERROR_INVALID_ARGS, "Invalid action");
> +        goto out;
> +    }
> +
> +    mm_dbg ("Kernel event reported:");
> +    mm_dbg ("  action:      %s",     action);
> +    if (mm_kernel_event_properties_get_subsystem (properties))
> +        mm_dbg ("  subsystem:   %s",
> mm_kernel_event_properties_get_subsystem (properties));
> +    if (mm_kernel_event_properties_get_name (properties))
> +        mm_dbg ("  name:        %s",
> mm_kernel_event_properties_get_name (properties));
> +    if (mm_kernel_event_properties_get_driver (properties))
> +        mm_dbg ("  driver:      %s",
> mm_kernel_event_properties_get_driver (properties));
> +    if (mm_kernel_event_properties_get_physdev_uid (properties))
> +        mm_dbg ("  physdev-uid: %s",
> mm_kernel_event_properties_get_physdev_uid (properties));
> +    if (mm_kernel_event_properties_get_physdev_vid (properties))
> +        mm_dbg ("  physdev-vid: 0x%04X",
> mm_kernel_event_properties_get_physdev_vid (properties));
> +    if (mm_kernel_event_properties_get_physdev_pid (properties))
> +        mm_dbg ("  physdev-pid: 0x%04X",
> mm_kernel_event_properties_get_physdev_pid (properties));
> +    if (mm_kernel_event_properties_get_candidate (properties))
> +        mm_dbg ("  candidate:   yes");
> +
> +    kernel_device = mm_kernel_device_generic_new (properties,
> &error);
> +    if (!kernel_device)
> +        goto out;
> +
> +    if (g_strcmp0 (action, "add") == 0)
> +        device_added (ctx->self, kernel_device, TRUE, TRUE);
> +    else if (g_strcmp0 (action, "remove") == 0)
> +        device_removed (ctx->self, kernel_device);
> +    else
> +        g_assert_not_reached ();
> +    g_object_unref (kernel_device);
> +
> +out:
> +    if (error)
> +        g_dbus_method_invocation_take_error (ctx->invocation,
> error);
> +    else
> +        mm_gdbus_org_freedesktop_modem_manager1_complete_report_kern
> el_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_e
> vent_auth_ready,
> +                                ctx);
> +    return TRUE;
> +}
> +
> +#else
> +
> +static gboolean
> +handle_report_kernel_event (MmGdbusOrgFreedesktopModemManager1
> *manager,
> +                            GDBusMethodInvocation *invocation,
> +                            GVariant *dictionary)
> +{
> +    g_dbus_method_invocation_return_error (invocation,
> +                                           MM_CORE_ERROR,
> +                                           MM_CORE_ERROR_UNSUPPORTED
> ,
> +                                           "Cannot report kernel
> event: "
> +                                           "udev monitoring already
> in place");
> +    return TRUE;
> +}
> +
> +#endif
> +
> +/*******************************************************************
> **********/
>  /* Test profile setup */
>  
>  static gboolean
> @@ -844,6 +979,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


More information about the ModemManager-devel mailing list