Files
ModemManager/src/kerneldevice/mm-kernel-device-udev.c
Aleksander Morgado aa4577dfb9 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.
2016-09-29 15:43:05 +02:00

624 lines
21 KiB
C

/* -*- 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]);
}