diff options
author | Aleksander Morgado <aleksander@aleksander.es> | 2016-03-27 19:40:03 +0200 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2016-09-29 15:43:05 +0200 |
commit | aa4577dfb9b5a7863a4939ec2409eae392e2fc0c (patch) | |
tree | fe696d759164829dcad1a05e02e850b5ba4369e9 /src/kerneldevice/mm-kernel-device-udev.c | |
parent | 1f813c4e9691f22017802278ab6f5b1475185113 (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.c | 623 |
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]); +} |