| /* -*- 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. |
| * Copyright (C) 2020 Aleksander Morgado <aleksander@aleksander.es> |
| */ |
| |
| #define _GNU_SOURCE |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #define _LIBMM_INSIDE_MM |
| #include <libmm-glib.h> |
| |
| #include <ModemManager-tags.h> |
| |
| #include "mm-kernel-device-generic.h" |
| #include "mm-kernel-device-generic-rules.h" |
| #include "mm-kernel-device-helpers.h" |
| #include "mm-log-object.h" |
| #include "mm-utils.h" |
| |
| #if !defined UDEVRULESDIR |
| # error UDEVRULESDIR is not defined |
| #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_RULES, |
| PROP_LAST |
| }; |
| |
| static GParamSpec *properties[PROP_LAST]; |
| |
| struct _MMKernelDeviceGenericPrivate { |
| /* Input properties */ |
| MMKernelEventProperties *properties; |
| /* Rules to apply */ |
| GArray *rules; |
| |
| /* Contents from sysfs */ |
| gchar **drivers; |
| gchar **subsystems; |
| gchar *sysfs_path; |
| gchar *wwandev_sysfs_path; |
| gchar *interface_sysfs_path; |
| guint8 interface_class; |
| guint8 interface_subclass; |
| guint8 interface_protocol; |
| guint8 interface_number; |
| gchar *interface_description; |
| gchar *physdev_sysfs_path; |
| guint16 physdev_vid; |
| guint16 physdev_pid; |
| guint16 physdev_subsystem_vid; |
| guint16 physdev_subsystem_pid; |
| guint16 physdev_revision; |
| gchar *physdev_manufacturer; |
| gchar *physdev_product; |
| }; |
| |
| static gboolean |
| has_sysfs_attribute (const gchar *path, |
| const gchar *attribute) |
| { |
| g_autofree gchar *aux_filepath = NULL; |
| |
| aux_filepath = g_strdup_printf ("%s/%s", path, attribute); |
| return g_file_test (aux_filepath, G_FILE_TEST_EXISTS); |
| } |
| |
| static gchar * |
| read_sysfs_attribute_as_string (const gchar *path, |
| const gchar *attribute) |
| { |
| g_autofree gchar *aux = NULL; |
| gchar *contents = NULL; |
| |
| aux = g_strdup_printf ("%s/%s", path, attribute); |
| if (g_file_get_contents (aux, &contents, NULL, NULL)) { |
| g_strdelimit (contents, "\r\n", ' '); |
| g_strstrip (contents); |
| } |
| return contents; |
| } |
| |
| static guint |
| read_sysfs_attribute_as_hex (const gchar *path, |
| const gchar *attribute) |
| { |
| g_autofree gchar *contents = NULL; |
| guint val = 0; |
| |
| contents = read_sysfs_attribute_as_string (path, attribute); |
| if (contents) |
| mm_get_uint_from_hex_str (contents, &val); |
| return val; |
| } |
| |
| static gchar * |
| read_sysfs_attribute_link_basename (const gchar *path, |
| const gchar *attribute) |
| { |
| g_autofree gchar *aux_filepath = NULL; |
| g_autofree gchar *canonicalized_path = NULL; |
| |
| aux_filepath = g_strdup_printf ("%s/%s", path, attribute); |
| if (!g_file_test (aux_filepath, G_FILE_TEST_EXISTS)) |
| return NULL; |
| |
| canonicalized_path = realpath (aux_filepath, NULL); |
| return g_path_get_basename (canonicalized_path); |
| } |
| |
| static gchar * |
| lookup_sysfs_attribute_as_string (MMKernelDeviceGeneric *self, |
| const gchar *attribute, |
| gboolean iterate) |
| { |
| g_autofree gchar *iter = NULL; |
| |
| /* if there is no parent sysfs path set, we look for the attribute |
| * only in the port sysfs path */ |
| if (!self->priv->physdev_sysfs_path) |
| return read_sysfs_attribute_as_string (self->priv->sysfs_path, attribute); |
| |
| iter = g_strdup (self->priv->sysfs_path); |
| while (iter) { |
| g_autofree gchar *parent = NULL; |
| gchar *value; |
| |
| /* return first one found */ |
| if ((value = read_sysfs_attribute_as_string (iter, attribute)) != NULL) |
| return value; |
| else if (!iterate) |
| break; |
| |
| if (g_strcmp0 (iter, self->priv->physdev_sysfs_path) == 0) |
| break; |
| parent = g_path_get_dirname (iter); |
| g_clear_pointer (&iter, g_free); |
| iter = g_steal_pointer (&parent); |
| } |
| |
| return NULL; |
| } |
| |
| /*****************************************************************************/ |
| /* Load contents */ |
| |
| static void |
| preload_sysfs_path (MMKernelDeviceGeneric *self) |
| { |
| g_autofree gchar *tmp = NULL; |
| |
| 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 = realpath (tmp, NULL); |
| if (!self->priv->sysfs_path || !g_file_test (self->priv->sysfs_path, G_FILE_TEST_EXISTS)) { |
| mm_obj_warn (self, "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) { |
| const gchar *devpath; |
| |
| mm_obj_dbg (self, "sysfs path: %s", self->priv->sysfs_path); |
| devpath = (g_str_has_prefix (self->priv->sysfs_path, "/sys") ? |
| &self->priv->sysfs_path[4] : |
| self->priv->sysfs_path); |
| g_object_set_data_full (G_OBJECT (self), "DEVPATH", g_strdup (devpath), g_free); |
| } |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| preload_common_properties (MMKernelDeviceGeneric *self) |
| { |
| if (self->priv->interface_sysfs_path) { |
| mm_obj_dbg (self, " ID_USB_INTERFACE_NUM: 0x%02x", self->priv->interface_number); |
| g_object_set_data_full (G_OBJECT (self), "ID_USB_INTERFACE_NUM", g_strdup_printf ("%02x", self->priv->interface_number), g_free); |
| } |
| |
| if (self->priv->physdev_product) { |
| mm_obj_dbg (self, " ID_MODEL: %s", self->priv->physdev_product); |
| g_object_set_data_full (G_OBJECT (self), "ID_MODEL", g_strdup (self->priv->physdev_product), g_free); |
| } |
| |
| if (self->priv->physdev_manufacturer) { |
| mm_obj_dbg (self, " ID_VENDOR: %s", self->priv->physdev_manufacturer); |
| g_object_set_data_full (G_OBJECT (self), "ID_VENDOR", g_strdup (self->priv->physdev_manufacturer), g_free); |
| } |
| |
| if (self->priv->physdev_sysfs_path) { |
| mm_obj_dbg (self, " ID_VENDOR_ID: 0x%04x", self->priv->physdev_vid); |
| g_object_set_data_full (G_OBJECT (self), "ID_VENDOR_ID", g_strdup_printf ("%04x", self->priv->physdev_vid), g_free); |
| mm_obj_dbg (self, " ID_MODEL_ID: 0x%04x", self->priv->physdev_pid); |
| g_object_set_data_full (G_OBJECT (self), "ID_MODEL_ID", g_strdup_printf ("%04x", self->priv->physdev_pid), g_free); |
| mm_obj_dbg (self, " ID_REVISION: 0x%04x", self->priv->physdev_revision); |
| g_object_set_data_full (G_OBJECT (self), "ID_REVISION", g_strdup_printf ("%04x", self->priv->physdev_revision), g_free); |
| } |
| } |
| |
| static void |
| ptr_array_add_sysfs_attribute_link_basename (GPtrArray *array, |
| const gchar *sysfs_path, |
| const gchar *attribute, |
| gchar **out_value) |
| { |
| g_autofree gchar *value = NULL; |
| |
| g_assert (array && sysfs_path && attribute); |
| value = read_sysfs_attribute_link_basename (sysfs_path, attribute); |
| |
| if (out_value) |
| *out_value = g_strdup (value); |
| if (value && !g_ptr_array_find_with_equal_func (array, value, g_str_equal, NULL)) |
| g_ptr_array_add (array, g_steal_pointer (&value)); |
| |
| } |
| |
| static void |
| preload_contents_other (MMKernelDeviceGeneric *self) |
| { |
| g_autofree gchar *lower_device_name = NULL; |
| GPtrArray *drivers; |
| GPtrArray *subsystems; |
| |
| /* For any other kind of bus (or the absence of one, as in virtual devices), |
| * assume this is a single port device and don't try to match multiple ports |
| * together. Also, obviously, no vendor, product, revision or interface. */ |
| |
| drivers = g_ptr_array_sized_new (2); |
| ptr_array_add_sysfs_attribute_link_basename (drivers, self->priv->sysfs_path, "driver", NULL); |
| g_ptr_array_add (drivers, NULL); |
| self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); |
| |
| subsystems = g_ptr_array_sized_new (2); |
| ptr_array_add_sysfs_attribute_link_basename (subsystems, self->priv->sysfs_path, "subsystem", NULL); |
| g_ptr_array_add (subsystems, NULL); |
| self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); |
| |
| /* But look for a lower real physical device, as we may have one */ |
| lower_device_name = mm_kernel_device_get_lower_device_name (self->priv->sysfs_path); |
| if (lower_device_name) { |
| g_autoptr(MMKernelDevice) lower_kernel_device = NULL; |
| g_autoptr(MMKernelEventProperties) props = NULL; |
| g_autoptr(GError) error = NULL; |
| const gchar *subsystem; |
| |
| subsystem = mm_kernel_device_get_subsystem (MM_KERNEL_DEVICE (self)); |
| |
| props = mm_kernel_event_properties_new (); |
| mm_kernel_event_properties_set_subsystem (props, subsystem); |
| mm_kernel_event_properties_set_name (props, lower_device_name); |
| |
| lower_kernel_device = mm_kernel_device_generic_new (props, &error); |
| if (!lower_kernel_device) { |
| mm_obj_dbg (self, "couldn't find lower device: %s/%s", subsystem, lower_device_name); |
| } else { |
| mm_obj_dbg (self, "setting up lower device: %s/%s", subsystem, lower_device_name); |
| g_object_set (self, |
| "lower-device", lower_kernel_device, |
| NULL); |
| } |
| } |
| } |
| |
| static void |
| preload_contents_platform (MMKernelDeviceGeneric *self, |
| const gchar *platform) |
| { |
| g_autofree gchar *iter = NULL; |
| GPtrArray *drivers; |
| GPtrArray *subsystems; |
| |
| drivers = g_ptr_array_sized_new (3); |
| subsystems = g_ptr_array_sized_new (3); |
| |
| iter = g_strdup (self->priv->sysfs_path); |
| while (iter && (g_strcmp0 (iter, "/") != 0)) { |
| gchar *parent; |
| g_autofree gchar *current_subsystem = NULL; |
| |
| ptr_array_add_sysfs_attribute_link_basename (drivers, iter, "driver", NULL); |
| ptr_array_add_sysfs_attribute_link_basename (subsystems, iter, "subsystem", ¤t_subsystem); |
| |
| /* Take first parent with the given platform subsystem as physical device */ |
| if (!self->priv->physdev_sysfs_path && (g_strcmp0 (current_subsystem, platform) == 0)) { |
| self->priv->physdev_sysfs_path = g_strdup (iter); |
| /* stop traversing as soon as the physical device is found */ |
| break; |
| } |
| |
| parent = g_path_get_dirname (iter); |
| g_clear_pointer (&iter, g_free); |
| iter = parent; |
| } |
| |
| g_ptr_array_add (drivers, NULL); |
| self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); |
| g_ptr_array_add (subsystems, NULL); |
| self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); |
| } |
| |
| static void |
| preload_contents_pcmcia (MMKernelDeviceGeneric *self) |
| { |
| g_autofree gchar *iter = NULL; |
| GPtrArray *drivers; |
| GPtrArray *subsystems; |
| gboolean pcmcia_subsystem_found = FALSE; |
| |
| drivers = g_ptr_array_sized_new (3); |
| subsystems = g_ptr_array_sized_new (3); |
| |
| iter = g_strdup (self->priv->sysfs_path); |
| while (iter && (g_strcmp0 (iter, "/") != 0)) { |
| g_autofree gchar *parent = NULL; |
| g_autofree gchar *parent_subsystem = NULL; |
| g_autofree gchar *current_subsystem = NULL; |
| |
| ptr_array_add_sysfs_attribute_link_basename (drivers, iter, "driver", NULL); |
| ptr_array_add_sysfs_attribute_link_basename (subsystems, iter, "subsystem", ¤t_subsystem); |
| |
| if (g_strcmp0 (current_subsystem, "pcmcia") == 0) |
| pcmcia_subsystem_found = TRUE; |
| |
| parent = g_path_get_dirname (iter); |
| if (parent) |
| parent_subsystem = read_sysfs_attribute_link_basename (parent, "subsystem"); |
| |
| if (pcmcia_subsystem_found && parent_subsystem && (g_strcmp0 (parent_subsystem, "pcmcia") != 0)) { |
| self->priv->physdev_sysfs_path = g_strdup (iter); |
| self->priv->physdev_vid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "manf_id"); |
| self->priv->physdev_pid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "card_id"); |
| /* stop traversing as soon as the physical device is found */ |
| break; |
| } |
| |
| g_clear_pointer (&iter, g_free); |
| iter = g_steal_pointer (&parent); |
| } |
| |
| g_ptr_array_add (drivers, NULL); |
| self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); |
| g_ptr_array_add (subsystems, NULL); |
| self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); |
| } |
| |
| static void |
| preload_contents_pci (MMKernelDeviceGeneric *self) |
| { |
| g_autofree gchar *iter = NULL; |
| GPtrArray *drivers; |
| GPtrArray *subsystems; |
| |
| drivers = g_ptr_array_sized_new (4); |
| subsystems = g_ptr_array_sized_new (4); |
| |
| iter = g_strdup (self->priv->sysfs_path); |
| while (iter && (g_strcmp0 (iter, "/") != 0)) { |
| g_autofree gchar *current_subsystem = NULL; |
| gchar *parent; |
| |
| ptr_array_add_sysfs_attribute_link_basename (drivers, iter, "driver", NULL); |
| ptr_array_add_sysfs_attribute_link_basename (subsystems, iter, "subsystem", ¤t_subsystem); |
| |
| /* the PCI channel specific devices have their own drivers and |
| * subsystems, we can rely on the physical device being the first |
| * one that reports the 'pci' subsystem */ |
| if (!self->priv->physdev_sysfs_path && (g_strcmp0 (current_subsystem, "pci") == 0)) { |
| self->priv->physdev_sysfs_path = g_strdup (iter); |
| self->priv->physdev_vid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "vendor"); |
| self->priv->physdev_pid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "device"); |
| self->priv->physdev_subsystem_vid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "subsystem_vendor"); |
| self->priv->physdev_subsystem_pid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "subsystem_device"); |
| self->priv->physdev_revision = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "revision"); |
| /* stop traversing as soon as the physical device is found */ |
| break; |
| } |
| |
| parent = g_path_get_dirname (iter); |
| g_clear_pointer (&iter, g_free); |
| iter = parent; |
| } |
| |
| g_ptr_array_add (drivers, NULL); |
| self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); |
| g_ptr_array_add (subsystems, NULL); |
| self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); |
| } |
| |
| static void |
| preload_contents_usb (MMKernelDeviceGeneric *self) |
| { |
| g_autofree gchar *iter = NULL; |
| GPtrArray *drivers; |
| GPtrArray *subsystems; |
| |
| drivers = g_ptr_array_sized_new (4); |
| subsystems = g_ptr_array_sized_new (4); |
| |
| iter = g_strdup (self->priv->sysfs_path); |
| while (iter && (g_strcmp0 (iter, "/") != 0)) { |
| gchar *parent; |
| |
| ptr_array_add_sysfs_attribute_link_basename (drivers, iter, "driver", NULL); |
| ptr_array_add_sysfs_attribute_link_basename (subsystems, iter, "subsystem", NULL); |
| |
| /* is this the USB interface? */ |
| if (!self->priv->interface_sysfs_path && has_sysfs_attribute (iter, "bInterfaceClass")) { |
| self->priv->interface_sysfs_path = g_strdup (iter); |
| self->priv->interface_class = read_sysfs_attribute_as_hex (self->priv->interface_sysfs_path, "bInterfaceClass"); |
| self->priv->interface_subclass = read_sysfs_attribute_as_hex (self->priv->interface_sysfs_path, "bInterfaceSubClass"); |
| self->priv->interface_protocol = read_sysfs_attribute_as_hex (self->priv->interface_sysfs_path, "bInterfaceProtocol"); |
| self->priv->interface_number = read_sysfs_attribute_as_hex (self->priv->interface_sysfs_path, "bInterfaceNumber"); |
| self->priv->interface_description = read_sysfs_attribute_as_string (self->priv->interface_sysfs_path, "interface"); |
| } |
| /* is this the USB physdev? */ |
| else if (!self->priv->physdev_sysfs_path && has_sysfs_attribute (iter, "idVendor")) { |
| self->priv->physdev_sysfs_path = g_strdup (iter); |
| self->priv->physdev_vid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "idVendor"); |
| self->priv->physdev_pid = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "idProduct"); |
| self->priv->physdev_revision = read_sysfs_attribute_as_hex (self->priv->physdev_sysfs_path, "bcdDevice"); |
| self->priv->physdev_manufacturer = read_sysfs_attribute_as_string (self->priv->physdev_sysfs_path, "manufacturer"); |
| self->priv->physdev_product = read_sysfs_attribute_as_string (self->priv->physdev_sysfs_path, "product"); |
| /* stop traversing as soon as the physical device is found */ |
| break; |
| } |
| |
| parent = g_path_get_dirname (iter); |
| g_clear_pointer (&iter, g_free); |
| iter = parent; |
| } |
| |
| g_ptr_array_add (drivers, NULL); |
| self->priv->drivers = (gchar **) g_ptr_array_free (drivers, FALSE); |
| g_ptr_array_add (subsystems, NULL); |
| self->priv->subsystems = (gchar **) g_ptr_array_free (subsystems, FALSE); |
| } |
| |
| static void |
| preload_contents_wwan (MMKernelDeviceGeneric *self) |
| { |
| g_autofree gchar *iter = NULL; |
| |
| /* Find the first parent device subsystem */ |
| iter = g_path_get_dirname(self->priv->sysfs_path); |
| while (iter && (g_strcmp0 (iter, "/") != 0)) { |
| g_autofree gchar *current_subsystem = NULL; |
| gchar *parent; |
| |
| current_subsystem = read_sysfs_attribute_link_basename (iter, "subsystem"); |
| if (current_subsystem) { |
| if (g_strcmp0 (current_subsystem, "wwan") == 0) |
| self->priv->wwandev_sysfs_path = g_strdup (iter); |
| break; |
| } |
| |
| parent = g_path_get_dirname (iter); |
| g_clear_pointer (&iter, g_free); |
| iter = parent; |
| } |
| } |
| |
| static gchar * |
| find_device_bus_subsystem (MMKernelDeviceGeneric *self) |
| { |
| g_autofree gchar *iter = NULL; |
| |
| iter = g_strdup (self->priv->sysfs_path); |
| while (iter && (g_strcmp0 (iter, "/") != 0)) { |
| g_autofree gchar *subsys = NULL; |
| gchar *parent; |
| |
| subsys = read_sysfs_attribute_link_basename (iter, "subsystem"); |
| |
| /* stop search as soon as we find a parent object |
| * of one of the supported bus subsystems */ |
| if (subsys && |
| ((g_strcmp0 (subsys, "usb") == 0) || |
| (g_strcmp0 (subsys, "pcmcia") == 0) || |
| (g_strcmp0 (subsys, "pci") == 0) || |
| (g_strcmp0 (subsys, "platform") == 0) || |
| (g_strcmp0 (subsys, "pnp") == 0) || |
| (g_strcmp0 (subsys, "sdio") == 0))) |
| return g_steal_pointer (&subsys); |
| |
| parent = g_path_get_dirname (iter); |
| g_clear_pointer (&iter, g_free); |
| iter = parent; |
| } |
| |
| /* no more parents to check */ |
| return NULL; |
| } |
| |
| static void |
| preload_contents (MMKernelDeviceGeneric *self) |
| { |
| g_autofree gchar *bus_subsys = NULL; |
| |
| if (self->priv->sysfs_path) |
| return; |
| |
| preload_sysfs_path (self); |
| if (!self->priv->sysfs_path) |
| return; |
| |
| bus_subsys = find_device_bus_subsystem (self); |
| if (g_strcmp0 (bus_subsys, "usb") == 0) |
| preload_contents_usb (self); |
| else if (g_strcmp0 (bus_subsys, "pcmcia") == 0) |
| preload_contents_pcmcia (self); |
| else if (g_strcmp0 (bus_subsys, "pci") == 0) |
| preload_contents_pci (self); |
| else if ((g_strcmp0 (bus_subsys, "platform") == 0) || |
| (g_strcmp0 (bus_subsys, "pnp") == 0) || |
| (g_strcmp0 (bus_subsys, "sdio") == 0)) |
| preload_contents_platform (self, bus_subsys); |
| else |
| preload_contents_other (self); |
| |
| preload_contents_wwan (self); /* wwan is bus agnostic class */ |
| |
| if (!bus_subsys) |
| return; |
| |
| mm_obj_dbg (self, "port contents loaded:"); |
| mm_obj_dbg (self, " bus: %s", bus_subsys ? bus_subsys : "n/a"); |
| if (self->priv->interface_sysfs_path) { |
| mm_obj_dbg (self, " interface: %s", self->priv->interface_sysfs_path); |
| mm_obj_dbg (self, " interface class: %02x", self->priv->interface_class); |
| mm_obj_dbg (self, " interface subclass: %02x", self->priv->interface_subclass); |
| mm_obj_dbg (self, " interface protocol: %02x", self->priv->interface_protocol); |
| mm_obj_dbg (self, " interface number: %02x", self->priv->interface_number); |
| } |
| if (self->priv->interface_description) |
| mm_obj_dbg (self, " interface description: %s", self->priv->interface_description); |
| if (self->priv->physdev_sysfs_path) |
| mm_obj_dbg (self, " device: %s", self->priv->physdev_sysfs_path); |
| if (self->priv->subsystems) { |
| g_autofree gchar *subsystems_str = NULL; |
| |
| subsystems_str = g_strjoinv (", ", self->priv->subsystems); |
| mm_obj_dbg (self, " subsystems: %s", subsystems_str); |
| } |
| if (self->priv->drivers) { |
| g_autofree gchar *drivers_str = NULL; |
| |
| drivers_str = g_strjoinv (", ", self->priv->drivers); |
| mm_obj_dbg (self, " drivers: %s", drivers_str); |
| } |
| if (self->priv->physdev_vid) |
| mm_obj_dbg (self, " vendor: %04x", self->priv->physdev_vid); |
| if (self->priv->physdev_pid) |
| mm_obj_dbg (self, " product: %04x", self->priv->physdev_pid); |
| if (self->priv->physdev_subsystem_vid) |
| mm_obj_dbg (self, " subsystem vendor: %04x", self->priv->physdev_subsystem_vid); |
| if (self->priv->physdev_subsystem_pid) |
| mm_obj_dbg (self, " subsystem device: %04x", self->priv->physdev_subsystem_pid); |
| if (self->priv->physdev_revision) |
| mm_obj_dbg (self, " revision: %04x", self->priv->physdev_revision); |
| if (self->priv->physdev_manufacturer) |
| mm_obj_dbg (self, " manufacturer: %s", self->priv->physdev_manufacturer); |
| if (self->priv->physdev_product) |
| mm_obj_dbg (self, " product: %s", self->priv->physdev_product); |
| |
| preload_common_properties (self); |
| } |
| |
| /*****************************************************************************/ |
| |
| static const gchar * |
| kernel_device_get_subsystem (MMKernelDevice *self) |
| { |
| return mm_kernel_event_properties_get_subsystem (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties); |
| } |
| |
| static const gchar * |
| kernel_device_get_name (MMKernelDevice *self) |
| { |
| return mm_kernel_event_properties_get_name (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties); |
| } |
| |
| static const gchar * |
| kernel_device_get_sysfs_path (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path; |
| } |
| |
| static const gchar * |
| kernel_device_get_wwandev_sysfs_path (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->wwandev_sysfs_path; |
| } |
| |
| static gint |
| kernel_device_get_interface_number (MMKernelDevice *self) |
| { |
| return (gint) MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_number; |
| } |
| |
| static gint |
| kernel_device_get_interface_class (MMKernelDevice *self) |
| { |
| return (gint) MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_class; |
| } |
| |
| static gint |
| kernel_device_get_interface_subclass (MMKernelDevice *self) |
| { |
| return (gint) MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_subclass; |
| } |
| |
| static gint |
| kernel_device_get_interface_protocol (MMKernelDevice *self) |
| { |
| return (gint) MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_protocol; |
| } |
| |
| static const gchar * |
| kernel_device_get_interface_sysfs_path (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_sysfs_path; |
| } |
| |
| static const gchar * |
| kernel_device_get_interface_description (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->interface_description; |
| } |
| |
| static const gchar * |
| kernel_device_get_physdev_uid (MMKernelDevice *self) |
| { |
| const gchar *uid; |
| |
| /* Prefer the one coming in the properties, if any */ |
| if ((uid = mm_kernel_event_properties_get_uid (MM_KERNEL_DEVICE_GENERIC (self)->priv->properties)) != NULL) |
| return uid; |
| |
| /* Try to load from properties set */ |
| if ((uid = mm_kernel_device_get_property (self, ID_MM_PHYSDEV_UID)) != NULL) |
| return uid; |
| |
| /* Use physical device path, if any */ |
| if (MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_sysfs_path) |
| return 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 */ |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path; |
| } |
| |
| static const gchar * |
| kernel_device_get_driver (MMKernelDevice *_self) |
| { |
| MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (_self); |
| |
| return (self->priv->drivers ? self->priv->drivers[0] : NULL); |
| } |
| |
| static guint16 |
| kernel_device_get_physdev_vid (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_vid; |
| } |
| |
| static guint16 |
| kernel_device_get_physdev_pid (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_pid; |
| } |
| |
| static guint16 |
| kernel_device_get_physdev_subsystem_vid (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_subsystem_vid; |
| } |
| |
| static guint16 |
| kernel_device_get_physdev_subsystem_pid (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_subsystem_pid; |
| } |
| |
| static guint16 |
| kernel_device_get_physdev_revision (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_revision; |
| } |
| |
| static const gchar * |
| kernel_device_get_physdev_sysfs_path (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_sysfs_path; |
| } |
| |
| static const gchar * |
| kernel_device_get_physdev_subsystem (MMKernelDevice *_self) |
| { |
| MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (_self); |
| guint len; |
| |
| len = (self->priv->subsystems ? g_strv_length (self->priv->subsystems) : 0); |
| return (len > 0 ? self->priv->subsystems[len - 1] : NULL); |
| } |
| |
| static const gchar * |
| kernel_device_get_physdev_manufacturer (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_manufacturer; |
| } |
| |
| static const gchar * |
| kernel_device_get_physdev_product (MMKernelDevice *self) |
| { |
| return MM_KERNEL_DEVICE_GENERIC (self)->priv->physdev_product; |
| } |
| |
| static gboolean |
| kernel_device_cmp (MMKernelDevice *a, |
| MMKernelDevice *b) |
| { |
| 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 gboolean |
| check_condition (MMKernelDeviceGeneric *self, |
| MMUdevRuleMatch *match) |
| { |
| gboolean condition_equal; |
| |
| condition_equal = (match->type == MM_UDEV_RULE_MATCH_TYPE_EQUAL); |
| |
| /* We only apply 'add' rules */ |
| if (g_str_equal (match->parameter, "ACTION")) |
| return ((!!strstr (match->value, "add")) == condition_equal); |
| |
| /* Exact SUBSYSTEM match */ |
| if (g_str_equal (match->parameter, "SUBSYSTEM")) |
| return ((self->priv->subsystems && !g_strcmp0 (self->priv->subsystems[0], match->value)) == condition_equal); |
| |
| /* Loose SUBSYSTEMS match */ |
| if (g_str_equal (match->parameter, "SUBSYSTEMS")) |
| return ((self->priv->subsystems && g_strv_contains ((const gchar * const *) self->priv->subsystems, match->value)) == condition_equal); |
| |
| /* Exact DRIVER match */ |
| if (g_str_equal (match->parameter, "DRIVER")) |
| return ((self->priv->drivers && !g_strcmp0 (self->priv->drivers[0], match->value)) == condition_equal); |
| |
| /* Loose DRIVERS match */ |
| if (g_str_equal (match->parameter, "DRIVERS")) |
| return ((self->priv->drivers && g_strv_contains ((const gchar * const *) self->priv->drivers, match->value)) == condition_equal); |
| |
| /* Device name checks */ |
| if (g_str_equal (match->parameter, "KERNEL")) |
| return (mm_kernel_device_generic_string_match (mm_kernel_device_get_name (MM_KERNEL_DEVICE (self)), match->value, self) == condition_equal); |
| |
| /* Device sysfs path checks; we allow both a direct match and a prefix patch */ |
| if (g_str_equal (match->parameter, "DEVPATH")) { |
| g_autofree gchar *prefix_match = NULL; |
| |
| /* If sysfs path invalid (e.g. path doesn't exist), no match */ |
| if (!self->priv->sysfs_path) |
| return FALSE; |
| |
| /* If not already doing a prefix match, do an implicit one. This is so that |
| * we can add properties to the usb_device owning all ports, and then apply |
| * the property to all ports individually processed here. */ |
| if (match->value[0] && match->value[strlen (match->value) - 1] != '*') |
| prefix_match = g_strdup_printf ("%s/*", match->value); |
| |
| if ((mm_kernel_device_generic_string_match (self->priv->sysfs_path, match->value, self) == condition_equal) || |
| (prefix_match && mm_kernel_device_generic_string_match (self->priv->sysfs_path, prefix_match, self) == condition_equal)) |
| return TRUE; |
| |
| if (g_str_has_prefix (self->priv->sysfs_path, "/sys")) { |
| if ((mm_kernel_device_generic_string_match (&self->priv->sysfs_path[4], match->value, self) == condition_equal) || |
| (prefix_match && mm_kernel_device_generic_string_match (&self->priv->sysfs_path[4], prefix_match, self) == condition_equal)) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| /* Attributes checks */ |
| if (g_str_has_prefix (match->parameter, "ATTR")) { |
| gchar *attribute; |
| gchar *contents = NULL; |
| gboolean result = FALSE; |
| guint val; |
| |
| attribute = g_strdup (&match->parameter[5]); |
| g_strdelimit (attribute, "{}", ' '); |
| g_strstrip (attribute); |
| |
| /* VID/PID/SUBSYSTEM VID directly from our API */ |
| if (g_str_equal (attribute, "idVendor") || g_str_equal (attribute, "vendor")) |
| result = ((mm_get_uint_from_hex_str (match->value, &val)) && |
| ((mm_kernel_device_get_physdev_vid (MM_KERNEL_DEVICE (self)) == val) == condition_equal)); |
| else if (g_str_equal (attribute, "idProduct") || g_str_equal (attribute, "device")) |
| result = ((mm_get_uint_from_hex_str (match->value, &val)) && |
| ((mm_kernel_device_get_physdev_pid (MM_KERNEL_DEVICE (self)) == val) == condition_equal)); |
| else if (g_str_equal (attribute, "subsystem_vendor")) |
| result = ((mm_get_uint_from_hex_str (match->value, &val)) && |
| ((mm_kernel_device_get_physdev_subsystem_vid (MM_KERNEL_DEVICE (self)) == val) == condition_equal)); |
| else if (g_str_equal (attribute, "subsystem_device")) |
| result = ((mm_get_uint_from_hex_str (match->value, &val)) && |
| ((mm_kernel_device_get_physdev_subsystem_pid (MM_KERNEL_DEVICE (self)) == val) == condition_equal)); |
| /* manufacturer in the physdev */ |
| else if (g_str_equal (attribute, "manufacturer")) |
| result = ((self->priv->physdev_manufacturer && g_str_equal (self->priv->physdev_manufacturer, match->value)) == condition_equal); |
| /* product in the physdev */ |
| else if (g_str_equal (attribute, "product")) |
| result = ((self->priv->physdev_product && g_str_equal (self->priv->physdev_product, match->value)) == condition_equal); |
| /* interface class/subclass/protocol/number in the interface */ |
| else if (g_str_equal (attribute, "bInterfaceClass")) |
| result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) && |
| ((self->priv->interface_class == val) == condition_equal))); |
| else if (g_str_equal (attribute, "bInterfaceSubClass")) |
| result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) && |
| ((self->priv->interface_subclass == val) == condition_equal))); |
| else if (g_str_equal (attribute, "bInterfaceProtocol")) |
| result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) && |
| ((self->priv->interface_protocol == val) == condition_equal))); |
| else if (g_str_equal (attribute, "bInterfaceNumber")) |
| result = (g_str_equal (match->value, "?*") || ((mm_get_uint_from_hex_str (match->value, &val)) && |
| ((self->priv->interface_number == val) == condition_equal))); |
| else { |
| g_autofree gchar *found_value = NULL; |
| |
| found_value = lookup_sysfs_attribute_as_string (self, attribute, g_str_has_prefix (match->parameter, "ATTRS")); |
| result = ((found_value && g_str_equal (found_value, match->value)) == condition_equal); |
| } |
| |
| g_free (contents); |
| g_free (attribute); |
| return result; |
| } |
| |
| /* Previously set property checks */ |
| if (g_str_has_prefix (match->parameter, "ENV")) { |
| gchar *property; |
| gboolean result = FALSE; |
| |
| property = g_strdup (&match->parameter[3]); |
| g_strdelimit (property, "{}", ' '); |
| g_strstrip (property); |
| |
| result = ((!g_strcmp0 ((const gchar *) g_object_get_data (G_OBJECT (self), property), match->value)) == condition_equal); |
| |
| g_free (property); |
| return result; |
| } |
| |
| mm_obj_warn (self, "unknown match condition parameter: %s", match->parameter); |
| return FALSE; |
| } |
| |
| static guint |
| check_rule (MMKernelDeviceGeneric *self, |
| guint rule_i) |
| { |
| MMUdevRule *rule; |
| gboolean apply = TRUE; |
| |
| g_assert (rule_i < self->priv->rules->len); |
| |
| rule = &g_array_index (self->priv->rules, MMUdevRule, rule_i); |
| if (rule->conditions) { |
| guint condition_i; |
| |
| for (condition_i = 0; condition_i < rule->conditions->len; condition_i++) { |
| MMUdevRuleMatch *match; |
| |
| match = &g_array_index (rule->conditions, MMUdevRuleMatch, condition_i); |
| if (!check_condition (self, match)) { |
| apply = FALSE; |
| break; |
| } |
| } |
| } |
| |
| if (apply) { |
| switch (rule->result.type) { |
| case MM_UDEV_RULE_RESULT_TYPE_PROPERTY: { |
| gchar *property_value_read = NULL; |
| |
| if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceClass}")) |
| property_value_read = g_strdup_printf ("%02x", self->priv->interface_class); |
| else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceSubClass}")) |
| property_value_read = g_strdup_printf ("%02x", self->priv->interface_subclass); |
| else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceProtocol}")) |
| property_value_read = g_strdup_printf ("%02x", self->priv->interface_protocol); |
| else if (g_str_equal (rule->result.content.property.value, "$attr{bInterfaceNumber}")) |
| property_value_read = g_strdup_printf ("%02x", self->priv->interface_number); |
| |
| /* add new property */ |
| mm_obj_dbg (self, "property added: %s=%s", |
| rule->result.content.property.name, |
| property_value_read ? property_value_read : rule->result.content.property.value); |
| |
| if (!property_value_read) |
| /* NOTE: we keep a reference to the list of rules ourselves, so it isn't |
| * an issue if we re-use the same string (i.e. without g_strdup-ing it) |
| * as a property value. */ |
| g_object_set_data (G_OBJECT (self), |
| rule->result.content.property.name, |
| rule->result.content.property.value); |
| else |
| g_object_set_data_full (G_OBJECT (self), |
| rule->result.content.property.name, |
| property_value_read, |
| g_free); |
| break; |
| } |
| |
| case MM_UDEV_RULE_RESULT_TYPE_LABEL: |
| /* noop */ |
| break; |
| |
| case MM_UDEV_RULE_RESULT_TYPE_GOTO_INDEX: |
| /* Jump to a new index */ |
| return rule->result.content.index; |
| |
| case MM_UDEV_RULE_RESULT_TYPE_GOTO_TAG: |
| case MM_UDEV_RULE_RESULT_TYPE_UNKNOWN: |
| default: |
| g_assert_not_reached (); |
| } |
| } |
| |
| /* Go to the next rule */ |
| return rule_i + 1; |
| } |
| |
| static void |
| preload_rule_properties (MMKernelDeviceGeneric *self) |
| { |
| guint i; |
| |
| g_assert (self->priv->rules); |
| g_assert (self->priv->rules->len > 0); |
| |
| /* Start to process rules */ |
| i = 0; |
| while (i < self->priv->rules->len) { |
| guint next_rule; |
| |
| next_rule = check_rule (self, i); |
| i = next_rule; |
| } |
| } |
| |
| static void |
| check_preload (MMKernelDeviceGeneric *self) |
| { |
| /* Only preload when properties and rules are set */ |
| if (!self->priv->properties || !self->priv->rules) |
| return; |
| |
| /* Don't preload on "remove" actions, where we don't have the device any more */ |
| if (g_strcmp0 (mm_kernel_event_properties_get_action (self->priv->properties), "remove") == 0) |
| return; |
| |
| /* Don't preload for devices in the 'virtual' subsystem */ |
| if (g_strcmp0 (mm_kernel_event_properties_get_subsystem (self->priv->properties), "virtual") == 0) |
| return; |
| |
| mm_obj_dbg (self, "preloading contents and properties..."); |
| preload_contents (self); |
| preload_rule_properties (self); |
| } |
| |
| static gboolean |
| kernel_device_has_property (MMKernelDevice *self, |
| const gchar *property) |
| { |
| return !!g_object_get_data (G_OBJECT (self), property); |
| } |
| |
| static const gchar * |
| kernel_device_get_property (MMKernelDevice *self, |
| const gchar *property) |
| { |
| return g_object_get_data (G_OBJECT (self), property); |
| } |
| |
| /*****************************************************************************/ |
| |
| static gchar * |
| build_attribute_data_key (const gchar *attribute) |
| { |
| return g_strdup_printf ("ATTR:%s", attribute); |
| } |
| |
| static gboolean |
| kernel_device_has_attribute (MMKernelDevice *self, |
| const gchar *attribute) |
| { |
| return has_sysfs_attribute (MM_KERNEL_DEVICE_GENERIC (self)->priv->sysfs_path, attribute); |
| } |
| |
| static const gchar * |
| kernel_device_get_attribute (MMKernelDevice *_self, |
| const gchar *attribute) |
| { |
| MMKernelDeviceGeneric *self; |
| g_autofree gchar *key = NULL; |
| gchar *value = NULL; |
| |
| self = MM_KERNEL_DEVICE_GENERIC (_self); |
| |
| key = build_attribute_data_key (attribute); |
| value = g_object_get_data (G_OBJECT (self), key); |
| if (!value) { |
| value = read_sysfs_attribute_as_string (self->priv->sysfs_path, attribute); |
| if (value) |
| g_object_set_data_full (G_OBJECT (self), key, value, g_free); |
| } |
| return (const gchar *) value; |
| } |
| |
| /*****************************************************************************/ |
| |
| MMKernelDevice * |
| mm_kernel_device_generic_new_with_rules (MMKernelEventProperties *props, |
| GArray *rules, |
| GError **error) |
| { |
| /* Note: we allow NULL rules, e.g. for virtual devices */ |
| |
| return MM_KERNEL_DEVICE (g_initable_new (MM_TYPE_KERNEL_DEVICE_GENERIC, |
| NULL, |
| error, |
| "properties", props, |
| "rules", rules, |
| NULL)); |
| } |
| |
| MMKernelDevice * |
| mm_kernel_device_generic_new (MMKernelEventProperties *props, |
| GError **error) |
| { |
| static GArray *rules = NULL; |
| |
| /* We only try to load the default list of rules once */ |
| if (G_UNLIKELY (!rules)) { |
| rules = mm_kernel_device_generic_rules_load (UDEVRULESDIR, error); |
| if (!rules) |
| return NULL; |
| } |
| |
| return mm_kernel_device_generic_new_with_rules (props, rules, error); |
| } |
| |
| /*****************************************************************************/ |
| |
| 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); |
| break; |
| case PROP_RULES: |
| g_assert (!self->priv->rules); |
| self->priv->rules = g_value_dup_boxed (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) |
| { |
| MMKernelDeviceGeneric *self = MM_KERNEL_DEVICE_GENERIC (object); |
| |
| switch (prop_id) { |
| case PROP_PROPERTIES: |
| g_value_set_object (value, self->priv->properties); |
| break; |
| case PROP_RULES: |
| g_value_set_boxed (value, self->priv->rules); |
| 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); |
| const gchar *subsystem; |
| |
| check_preload (self); |
| |
| subsystem = mm_kernel_device_get_subsystem (MM_KERNEL_DEVICE (self)); |
| if (!subsystem) { |
| 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_product, g_free); |
| g_clear_pointer (&self->priv->physdev_manufacturer, g_free); |
| g_clear_pointer (&self->priv->physdev_sysfs_path, g_free); |
| g_clear_pointer (&self->priv->interface_description, g_free); |
| g_clear_pointer (&self->priv->interface_sysfs_path, g_free); |
| g_clear_pointer (&self->priv->sysfs_path, g_free); |
| g_clear_pointer (&self->priv->drivers, g_strfreev); |
| g_clear_pointer (&self->priv->subsystems, g_strfreev); |
| g_clear_pointer (&self->priv->rules, g_array_unref); |
| 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_wwandev_sysfs_path = kernel_device_get_wwandev_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_physdev_subsystem_vid = kernel_device_get_physdev_subsystem_vid; |
| kernel_device_class->get_physdev_subsystem_pid = kernel_device_get_physdev_subsystem_pid; |
| kernel_device_class->get_physdev_revision = kernel_device_get_physdev_revision; |
| kernel_device_class->get_physdev_sysfs_path = kernel_device_get_physdev_sysfs_path; |
| kernel_device_class->get_physdev_subsystem = kernel_device_get_physdev_subsystem; |
| kernel_device_class->get_physdev_manufacturer = kernel_device_get_physdev_manufacturer; |
| kernel_device_class->get_physdev_product = kernel_device_get_physdev_product; |
| kernel_device_class->get_interface_number = kernel_device_get_interface_number; |
| kernel_device_class->get_interface_class = kernel_device_get_interface_class; |
| kernel_device_class->get_interface_subclass = kernel_device_get_interface_subclass; |
| kernel_device_class->get_interface_protocol = kernel_device_get_interface_protocol; |
| kernel_device_class->get_interface_sysfs_path = kernel_device_get_interface_sysfs_path; |
| kernel_device_class->get_interface_description = kernel_device_get_interface_description; |
| 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->has_attribute = kernel_device_has_attribute; |
| kernel_device_class->get_attribute = kernel_device_get_attribute; |
| |
| /* Device-wide properties are stored per-port in the generic backend */ |
| kernel_device_class->has_global_property = kernel_device_has_property; |
| kernel_device_class->get_global_property = kernel_device_get_property; |
| |
| 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]); |
| |
| properties[PROP_RULES] = |
| g_param_spec_boxed ("rules", |
| "Rules", |
| "List of rules to apply", |
| G_TYPE_ARRAY, |
| G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); |
| g_object_class_install_property (object_class, PROP_RULES, properties[PROP_RULES]); |
| } |