/* -*- 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 #define _LIBMM_INSIDE_MM #include #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]); }