aboutsummaryrefslogtreecommitdiff
path: root/src/kerneldevice/mm-kernel-device-udev.c
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2016-03-27 19:40:03 +0200
committerAleksander Morgado <aleksander@aleksander.es>2016-09-29 15:43:05 +0200
commitaa4577dfb9b5a7863a4939ec2409eae392e2fc0c (patch)
treefe696d759164829dcad1a05e02e850b5ba4369e9 /src/kerneldevice/mm-kernel-device-udev.c
parent1f813c4e9691f22017802278ab6f5b1475185113 (diff)
core: new kernel device object instead of an explicit GUdevDevice
Instead of relying constantly on GUdevDevice objects reported by GUdev, we now use a new generic object (MMKernelDevice) for which we provide an initial GUdev based backend.
Diffstat (limited to 'src/kerneldevice/mm-kernel-device-udev.c')
-rw-r--r--src/kerneldevice/mm-kernel-device-udev.c623
1 files changed, 623 insertions, 0 deletions
diff --git a/src/kerneldevice/mm-kernel-device-udev.c b/src/kerneldevice/mm-kernel-device-udev.c
new file mode 100644
index 00000000..4953f48f
--- /dev/null
+++ b/src/kerneldevice/mm-kernel-device-udev.c
@@ -0,0 +1,623 @@
+/* -*- 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>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-kernel-device-udev.h"
+#include "mm-log.h"
+
+G_DEFINE_TYPE (MMKernelDeviceUdev, mm_kernel_device_udev, MM_TYPE_KERNEL_DEVICE)
+
+enum {
+ PROP_0,
+ PROP_UDEV_DEVICE,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MMKernelDeviceUdevPrivate {
+ GUdevDevice *device;
+ GUdevDevice *parent;
+ GUdevDevice *physdev;
+ guint16 vendor;
+ guint16 product;
+};
+
+/*****************************************************************************/
+
+static gboolean
+get_device_ids (GUdevDevice *device,
+ guint16 *vendor,
+ guint16 *product)
+{
+ GUdevDevice *parent = NULL;
+ const gchar *vid = NULL, *pid = NULL, *parent_subsys;
+ gboolean success = FALSE;
+ char *pci_vid = NULL, *pci_pid = NULL;
+
+ parent = g_udev_device_get_parent (device);
+ if (parent) {
+ parent_subsys = g_udev_device_get_subsystem (parent);
+ if (parent_subsys) {
+ if (g_str_equal (parent_subsys, "bluetooth")) {
+ /* Bluetooth devices report the VID/PID of the BT adapter here,
+ * which isn't really what we want. Just return null IDs instead.
+ */
+ success = TRUE;
+ goto out;
+ } else if (g_str_equal (parent_subsys, "pcmcia")) {
+ /* For PCMCIA devices we need to grab the PCMCIA subsystem's
+ * manfid and cardid, since any IDs on the tty device itself
+ * may be from PCMCIA controller or something else.
+ */
+ vid = g_udev_device_get_sysfs_attr (parent, "manf_id");
+ pid = g_udev_device_get_sysfs_attr (parent, "card_id");
+ if (!vid || !pid)
+ goto out;
+ } else if (g_str_equal (parent_subsys, "platform")) {
+ /* Platform devices don't usually have a VID/PID */
+ success = TRUE;
+ goto out;
+ } else if (g_str_has_prefix (parent_subsys, "usb") &&
+ (!g_strcmp0 (g_udev_device_get_driver (parent), "qmi_wwan") ||
+ !g_strcmp0 (g_udev_device_get_driver (parent), "cdc_mbim"))) {
+ /* Need to look for vendor/product in the parent of the QMI/MBIM device */
+ GUdevDevice *qmi_parent;
+
+ qmi_parent = g_udev_device_get_parent (parent);
+ if (qmi_parent) {
+ vid = g_udev_device_get_property (qmi_parent, "ID_VENDOR_ID");
+ pid = g_udev_device_get_property (qmi_parent, "ID_MODEL_ID");
+ g_object_unref (qmi_parent);
+ }
+ } else if (g_str_equal (parent_subsys, "pci")) {
+ const char *pci_id;
+
+ /* We can't always rely on the model + vendor showing up on
+ * the PCI device's child, so look at the PCI parent. PCI_ID
+ * has the format "1931:000C".
+ */
+ pci_id = g_udev_device_get_property (parent, "PCI_ID");
+ if (pci_id && strlen (pci_id) == 9 && pci_id[4] == ':') {
+ vid = pci_vid = g_strdup (pci_id);
+ pci_vid[4] = '\0';
+ pid = pci_pid = g_strdup (pci_id + 5);
+ }
+ }
+ }
+ }
+
+ if (!vid)
+ vid = g_udev_device_get_property (device, "ID_VENDOR_ID");
+ if (!vid)
+ goto out;
+
+ if (strncmp (vid, "0x", 2) == 0)
+ vid += 2;
+ if (strlen (vid) != 4)
+ goto out;
+
+ if (vendor) {
+ *vendor = (guint16) (mm_utils_hex2byte (vid + 2) & 0xFF);
+ *vendor |= (guint16) ((mm_utils_hex2byte (vid) & 0xFF) << 8);
+ }
+
+ if (!pid)
+ pid = g_udev_device_get_property (device, "ID_MODEL_ID");
+ if (!pid) {
+ *vendor = 0;
+ goto out;
+ }
+
+ if (strncmp (pid, "0x", 2) == 0)
+ pid += 2;
+ if (strlen (pid) != 4) {
+ *vendor = 0;
+ goto out;
+ }
+
+ if (product) {
+ *product = (guint16) (mm_utils_hex2byte (pid + 2) & 0xFF);
+ *product |= (guint16) ((mm_utils_hex2byte (pid) & 0xFF) << 8);
+ }
+
+ success = TRUE;
+
+out:
+ if (parent)
+ g_object_unref (parent);
+ g_free (pci_vid);
+ g_free (pci_pid);
+ return success;
+}
+
+static void
+ensure_device_ids (MMKernelDeviceUdev *self)
+{
+ if (self->priv->vendor || self->priv->product)
+ return;
+
+ if (!get_device_ids (self->priv->device, &self->priv->vendor, &self->priv->product))
+ mm_dbg ("(%s/%s) could not get vendor/product ID",
+ g_udev_device_get_subsystem (self->priv->device),
+ g_udev_device_get_name (self->priv->device));
+}
+
+/*****************************************************************************/
+
+static GUdevDevice *
+find_physical_gudevdevice (GUdevDevice *child)
+{
+ GUdevDevice *iter, *old = NULL;
+ GUdevDevice *physdev = NULL;
+ const char *subsys, *type, *name;
+ guint32 i = 0;
+ gboolean is_usb = FALSE, is_pci = FALSE, is_pcmcia = FALSE, is_platform = FALSE;
+ gboolean is_pnp = FALSE;
+
+ g_return_val_if_fail (child != NULL, NULL);
+
+ /* Bluetooth rfcomm devices are "virtual" and don't necessarily have
+ * parents at all.
+ */
+ name = g_udev_device_get_name (child);
+ if (name && strncmp (name, "rfcomm", 6) == 0)
+ return g_object_ref (child);
+
+ iter = g_object_ref (child);
+ while (iter && i++ < 8) {
+ subsys = g_udev_device_get_subsystem (iter);
+ if (subsys) {
+ if (is_usb || g_str_has_prefix (subsys, "usb")) {
+ is_usb = TRUE;
+ type = g_udev_device_get_devtype (iter);
+ if (type && !strcmp (type, "usb_device")) {
+ physdev = iter;
+ break;
+ }
+ } else if (is_pcmcia || !strcmp (subsys, "pcmcia")) {
+ GUdevDevice *pcmcia_parent;
+ const char *tmp_subsys;
+
+ is_pcmcia = TRUE;
+
+ /* If the parent of this PCMCIA device is no longer part of
+ * the PCMCIA subsystem, we want to stop since we're looking
+ * for the base PCMCIA device, not the PCMCIA controller which
+ * is usually PCI or some other bus type.
+ */
+ pcmcia_parent = g_udev_device_get_parent (iter);
+ if (pcmcia_parent) {
+ tmp_subsys = g_udev_device_get_subsystem (pcmcia_parent);
+ if (tmp_subsys && strcmp (tmp_subsys, "pcmcia"))
+ physdev = iter;
+ g_object_unref (pcmcia_parent);
+ if (physdev)
+ break;
+ }
+ } else if (is_platform || !strcmp (subsys, "platform")) {
+ /* Take the first platform parent as the physical device */
+ is_platform = TRUE;
+ physdev = iter;
+ break;
+ } else if (is_pci || !strcmp (subsys, "pci")) {
+ is_pci = TRUE;
+ physdev = iter;
+ break;
+ } else if (is_pnp || !strcmp (subsys, "pnp")) {
+ is_pnp = TRUE;
+ physdev = iter;
+ break;
+ }
+ }
+
+ old = iter;
+ iter = g_udev_device_get_parent (old);
+ g_object_unref (old);
+ }
+
+ return physdev;
+}
+
+static void
+ensure_physdev (MMKernelDeviceUdev *self)
+{
+ if (self->priv->physdev)
+ return;
+ self->priv->physdev = find_physical_gudevdevice (self->priv->device);
+}
+
+/*****************************************************************************/
+
+static const gchar *
+kernel_device_get_subsystem (MMKernelDevice *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (self), NULL);
+
+ return g_udev_device_get_subsystem (MM_KERNEL_DEVICE_UDEV (self)->priv->device);
+}
+
+static const gchar *
+kernel_device_get_name (MMKernelDevice *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (self), NULL);
+
+ return g_udev_device_get_name (MM_KERNEL_DEVICE_UDEV (self)->priv->device);
+}
+
+static const gchar *
+kernel_device_get_driver (MMKernelDevice *_self)
+{
+ MMKernelDeviceUdev *self;
+ const gchar *driver, *subsys, *name;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), NULL);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+
+ driver = g_udev_device_get_driver (self->priv->device);
+ if (!driver) {
+ GUdevDevice *parent;
+
+ parent = g_udev_device_get_parent (self->priv->device);
+ if (parent)
+ driver = g_udev_device_get_driver (parent);
+
+ /* Check for bluetooth; it's driver is a bunch of levels up so we
+ * just check for the subsystem of the parent being bluetooth.
+ */
+ if (!driver && parent) {
+ subsys = g_udev_device_get_subsystem (parent);
+ if (subsys && !strcmp (subsys, "bluetooth"))
+ driver = "bluetooth";
+ }
+
+ if (parent)
+ g_object_unref (parent);
+ }
+
+ /* Newer kernels don't set up the rfcomm port parent in sysfs,
+ * so we must infer it from the device name.
+ */
+ name = g_udev_device_get_name (self->priv->device);
+ if (!driver && strncmp (name, "rfcomm", 6) == 0)
+ driver = "bluetooth";
+
+ /* Note: may return NULL! */
+ return driver;
+}
+
+static const gchar *
+kernel_device_get_sysfs_path (MMKernelDevice *self)
+{
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE (self), NULL);
+
+ return g_udev_device_get_sysfs_path (MM_KERNEL_DEVICE_UDEV (self)->priv->device);
+}
+
+static const gchar *
+kernel_device_get_physdev_uid (MMKernelDevice *_self)
+{
+ MMKernelDeviceUdev *self;
+ const gchar *uid = NULL;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), NULL);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+ ensure_physdev (self);
+
+ if (!self->priv->physdev)
+ return NULL;
+
+ uid = g_udev_device_get_property (self->priv->physdev, "ID_MM_PHYSDEV_UID");
+ if (!uid)
+ uid = g_udev_device_get_sysfs_path (self->priv->physdev);
+
+ return uid;
+}
+
+static guint16
+kernel_device_get_physdev_vid (MMKernelDevice *_self)
+{
+ MMKernelDeviceUdev *self;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), 0);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+ ensure_device_ids (self);
+ return self->priv->vendor;
+}
+
+static guint16
+kernel_device_get_physdev_pid (MMKernelDevice *_self)
+{
+ MMKernelDeviceUdev *self;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), 0);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+ ensure_device_ids (self);
+ return self->priv->product;
+}
+
+static const gchar *
+kernel_device_get_parent_sysfs_path (MMKernelDevice *_self)
+{
+ MMKernelDeviceUdev *self;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), 0);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+ if (!self->priv->parent)
+ self->priv->parent = g_udev_device_get_parent (self->priv->device);
+ return (self->priv->parent ? g_udev_device_get_sysfs_path (self->priv->parent) : NULL);
+}
+
+static gboolean
+kernel_device_is_candidate (MMKernelDevice *_self,
+ gboolean manual_scan)
+{
+ MMKernelDeviceUdev *self;
+ const gchar *physdev_subsys;
+ const gchar *name;
+ const gchar *subsys;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), FALSE);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+
+ name = g_udev_device_get_name (self->priv->device);
+ subsys = g_udev_device_get_subsystem (self->priv->device);
+
+ /* ignore VTs */
+ if (strncmp (name, "tty", 3) == 0 && g_ascii_isdigit (name[3]))
+ return FALSE;
+
+ /* Ignore devices that aren't completely configured by udev yet. If
+ * ModemManager is started in parallel with udev, explicitly requesting
+ * devices may return devices for which not all udev rules have yet been
+ * applied (a bug in udev/gudev). Since we often need those rules to match
+ * the device to a specific ModemManager driver, we need to ensure that all
+ * rules have been processed before handling a device.
+ *
+ * The udev tag applies to each port in a device. In other words, the flag
+ * may be set in some ports, but not in others */
+ if (!g_udev_device_get_property_as_boolean (self->priv->device, "ID_MM_CANDIDATE"))
+ return FALSE;
+
+ /* Load physical device. If there is no physical device, we don't process
+ * the device. */
+ ensure_physdev (self);
+ if (!self->priv->physdev) {
+ /* Log about it, but filter out some common ports that we know don't have
+ * anything to do with mobile broadband.
+ */
+ if ( strcmp (name, "console")
+ && strcmp (name, "ptmx")
+ && strcmp (name, "lo")
+ && strcmp (name, "tty")
+ && !strstr (name, "virbr"))
+ mm_dbg ("(%s/%s): could not get port's parent device", subsys, name);
+ return FALSE;
+ }
+
+ /* The blacklist applies to the device as a whole, and therefore the flag
+ * will be applied always in the physical device, not in each port. */
+ if (g_udev_device_get_property_as_boolean (self->priv->physdev, "ID_MM_DEVICE_IGNORE")) {
+ mm_dbg ("(%s/%s): device is blacklisted", subsys, name);
+ return FALSE;
+ }
+
+ /* Is the device in the manual-only greylist? If so, return if this is an
+ * automatic scan. */
+ if (!manual_scan && g_udev_device_get_property_as_boolean (self->priv->physdev, "ID_MM_DEVICE_MANUAL_SCAN_ONLY")) {
+ mm_dbg ("(%s/%s): device probed only in manual scan", subsys, name);
+ return FALSE;
+ }
+
+ /* If the physdev is a 'platform' or 'pnp' device that's not whitelisted, ignore it */
+ physdev_subsys = g_udev_device_get_subsystem (self->priv->physdev);
+ if ((!g_strcmp0 (physdev_subsys, "platform") || !g_strcmp0 (physdev_subsys, "pnp")) &&
+ (!g_udev_device_get_property_as_boolean (self->priv->physdev, "ID_MM_PLATFORM_DRIVER_PROBE"))) {
+ mm_dbg ("(%s/%s): port's parent platform driver is not whitelisted", subsys, name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+kernel_device_cmp (MMKernelDevice *_a,
+ MMKernelDevice *_b)
+{
+ MMKernelDeviceUdev *a;
+ MMKernelDeviceUdev *b;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_a), FALSE);
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_b), FALSE);
+
+ a = MM_KERNEL_DEVICE_UDEV (_a);
+ b = MM_KERNEL_DEVICE_UDEV (_b);
+
+ if (g_udev_device_has_property (a->priv->device, "DEVPATH_OLD") &&
+ g_str_has_suffix (g_udev_device_get_sysfs_path (b->priv->device),
+ g_udev_device_get_property (a->priv->device, "DEVPATH_OLD")))
+ return TRUE;
+
+ if (g_udev_device_has_property (b->priv->device, "DEVPATH_OLD") &&
+ g_str_has_suffix (g_udev_device_get_sysfs_path (a->priv->device),
+ g_udev_device_get_property (b->priv->device, "DEVPATH_OLD")))
+ return TRUE;
+
+ return !g_strcmp0 (g_udev_device_get_sysfs_path (a->priv->device), g_udev_device_get_sysfs_path (b->priv->device));
+}
+
+static gboolean
+kernel_device_has_property (MMKernelDevice *_self,
+ const gchar *property)
+{
+ MMKernelDeviceUdev *self;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), FALSE);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+ return g_udev_device_has_property (self->priv->device, property);
+}
+
+static const gchar *
+kernel_device_get_property (MMKernelDevice *_self,
+ const gchar *property)
+{
+ MMKernelDeviceUdev *self;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), NULL);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+ return g_udev_device_get_property (self->priv->device, property);
+}
+
+static gboolean
+kernel_device_get_property_as_boolean (MMKernelDevice *_self,
+ const gchar *property)
+{
+ MMKernelDeviceUdev *self;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), FALSE);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+ return g_udev_device_get_property_as_boolean (self->priv->device, property);
+}
+
+static gint
+kernel_device_get_property_as_int (MMKernelDevice *_self,
+ const gchar *property)
+{
+ MMKernelDeviceUdev *self;
+
+ g_return_val_if_fail (MM_IS_KERNEL_DEVICE_UDEV (_self), -1);
+
+ self = MM_KERNEL_DEVICE_UDEV (_self);
+ return g_udev_device_get_property_as_int (self->priv->device, property);
+}
+
+/*****************************************************************************/
+
+MMKernelDevice *
+mm_kernel_device_udev_new (GUdevDevice *udev_device)
+{
+ g_return_val_if_fail (G_UDEV_IS_DEVICE (udev_device), NULL);
+
+ return MM_KERNEL_DEVICE (g_object_new (MM_TYPE_KERNEL_DEVICE_UDEV,
+ "udev-device", udev_device,
+ NULL));
+}
+
+/*****************************************************************************/
+
+static void
+mm_kernel_device_udev_init (MMKernelDeviceUdev *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_KERNEL_DEVICE_UDEV, MMKernelDeviceUdevPrivate);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMKernelDeviceUdev *self = MM_KERNEL_DEVICE_UDEV (object);
+
+ switch (prop_id) {
+ case PROP_UDEV_DEVICE:
+ g_assert (!self->priv->device);
+ self->priv->device = g_value_dup_object (value);
+ 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)
+{
+ MMKernelDeviceUdev *self = MM_KERNEL_DEVICE_UDEV (object);
+
+ switch (prop_id) {
+ case PROP_UDEV_DEVICE:
+ g_value_set_object (value, self->priv->device);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ MMKernelDeviceUdev *self = MM_KERNEL_DEVICE_UDEV (object);
+
+ g_clear_object (&self->priv->physdev);
+ g_clear_object (&self->priv->parent);
+ g_clear_object (&self->priv->device);
+
+ G_OBJECT_CLASS (mm_kernel_device_udev_parent_class)->dispose (object);
+}
+
+static void
+mm_kernel_device_udev_class_init (MMKernelDeviceUdevClass *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 (MMKernelDeviceUdevPrivate));
+
+ 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_UDEV_DEVICE] =
+ g_param_spec_object ("udev-device",
+ "udev device",
+ "Device object as reported by GUdev",
+ G_UDEV_TYPE_DEVICE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_UDEV_DEVICE, properties[PROP_UDEV_DEVICE]);
+}