From b0d9d0076209fa3de1458cafb5eda494dfc226d1 Mon Sep 17 00:00:00 2001 From: Matthew Mirvish Date: Thu, 8 Apr 2021 16:46:01 +0200 Subject: [PATCH] Add support for udev based device discovery This is primarily useful for SPI devices. These devices sometimes needs a combination of an SPI and HID device, so discovery is a bit more complicated. --- doc/libfprint-2-sections.txt | 2 + libfprint/fp-context.c | 109 ++++++++++++++++++++++++++++++++++ libfprint/fp-device-private.h | 21 ++++--- libfprint/fp-device.c | 45 ++++++++++++++ libfprint/fp-device.h | 2 + libfprint/fpi-device.c | 33 ++++++++++ libfprint/fpi-device.h | 24 ++++++++ libfprint/meson.build | 1 + meson.build | 14 ++++- 9 files changed, 242 insertions(+), 9 deletions(-) diff --git a/doc/libfprint-2-sections.txt b/doc/libfprint-2-sections.txt index 61dd985..37fddf3 100644 --- a/doc/libfprint-2-sections.txt +++ b/doc/libfprint-2-sections.txt @@ -132,7 +132,9 @@ FpDeviceClass FpTimeoutFunc FpiDeviceAction FpIdEntry +FpiDeviceUdevSubtypeFlags fpi_device_get_usb_device +fpi_device_get_udev_data fpi_device_get_virtual_env fpi_device_get_current_action fpi_device_retry_new diff --git a/libfprint/fp-context.c b/libfprint/fp-context.c index 584b0b7..ea99e06 100644 --- a/libfprint/fp-context.c +++ b/libfprint/fp-context.c @@ -24,6 +24,18 @@ #include "fpi-device.h" #include +#include + +#ifdef HAVE_UDEV +#include +#include +#include +#include +#include +#include +#include +#endif + /** * SECTION: fp-context * @title: FpContext @@ -434,6 +446,103 @@ fp_context_enumerate (FpContext *context) } } + +#ifdef HAVE_UDEV + { + g_autoptr(GUdevClient) udev_client = g_udev_client_new (NULL); + + /* This uses a very simple algorithm to allocate devices to drivers and assumes that no two drivers will want the same device. Future improvements + * could add a usb_discover style udev_discover that returns a score, however for internal devices the potential overlap should be very low between + * separate drivers. + */ + + g_autoptr(GList) spidev_devices = g_udev_client_query_by_subsystem (udev_client, "spidev"); + g_autoptr(GList) hidraw_devices = g_udev_client_query_by_subsystem (udev_client, "hidraw"); + + /* for each potential driver, try to match all requested resources. */ + for (i = 0; i < priv->drivers->len; i++) + { + GType driver = g_array_index (priv->drivers, GType, i); + g_autoptr(FpDeviceClass) cls = g_type_class_ref (driver); + const FpIdEntry *entry; + + if (cls->type != FP_DEVICE_TYPE_UDEV) + continue; + + for (entry = cls->id_table; entry->udev_types; entry++) + { + GList *matched_spidev = NULL, *matched_hidraw = NULL; + + if (entry->udev_types & FPI_DEVICE_UDEV_SUBTYPE_SPIDEV) + { + for (matched_spidev = spidev_devices; matched_spidev; matched_spidev = matched_spidev->next) + { + const gchar * sysfs = g_udev_device_get_sysfs_path (matched_spidev->data); + if (!sysfs) + continue; + if (strstr (sysfs, entry->spi_acpi_id)) + break; + } + /* If match was not found exit */ + if (matched_spidev == NULL) + continue; + } + if (entry->udev_types & FPI_DEVICE_UDEV_SUBTYPE_HIDRAW) + { + for (matched_hidraw = hidraw_devices; matched_hidraw; matched_hidraw = matched_hidraw->next) + { + const gchar * devnode = g_udev_device_get_device_file (matched_hidraw->data); + int temp_hid = -1, res; + struct hidraw_devinfo info; + + if (!devnode) + continue; + + temp_hid = open (devnode, O_RDWR); + if (temp_hid < 0) + continue; + + res = ioctl (temp_hid, HIDIOCGRAWINFO, &info); + close (temp_hid); + if (res < 0) + continue; + if (info.vendor == entry->hid_id.vid && info.product == entry->hid_id.pid) + break; + } + /* If match was not found exit */ + if (matched_hidraw == NULL) + continue; + } + priv->pending_devices++; + g_async_initable_new_async (driver, + G_PRIORITY_LOW, + priv->cancellable, + async_device_init_done_cb, + context, + "fpi-driver-data", entry->driver_data, + "fpi-udev-data-spidev", (matched_spidev ? g_udev_device_get_device_file (matched_spidev->data) : NULL), + "fpi-udev-data-hidraw", (matched_hidraw ? g_udev_device_get_device_file (matched_hidraw->data) : NULL), + NULL); + /* remove entries from list to avoid conflicts */ + if (matched_spidev) + { + g_object_unref (matched_spidev->data); + spidev_devices = g_list_delete_link (spidev_devices, matched_spidev); + } + if (matched_hidraw) + { + g_object_unref (matched_hidraw->data); + hidraw_devices = g_list_delete_link (hidraw_devices, matched_hidraw); + } + } + } + + /* free all unused elemnts in both lists */ + g_list_foreach (spidev_devices, (GFunc) g_object_unref, NULL); + g_list_foreach (hidraw_devices, (GFunc) g_object_unref, NULL); + } +#endif + while (priv->pending_devices) g_main_context_iteration (NULL, TRUE); } diff --git a/libfprint/fp-device-private.h b/libfprint/fp-device-private.h index b8d5291..47230cc 100644 --- a/libfprint/fp-device-private.h +++ b/libfprint/fp-device-private.h @@ -28,18 +28,23 @@ typedef struct GUsbDevice *usb_device; const gchar *virtual_env; + struct + { + gchar *spidev_path; + gchar *hidraw_path; + } udev_data; - gboolean is_removed; - gboolean is_open; + gboolean is_removed; + gboolean is_open; - gchar *device_id; - gchar *device_name; - FpScanType scan_type; + gchar *device_id; + gchar *device_name; + FpScanType scan_type; - guint64 driver_data; + guint64 driver_data; - gint nr_enroll_stages; - GSList *sources; + gint nr_enroll_stages; + GSList *sources; /* We always make sure that only one task is run at a time. */ FpiDeviceAction current_action; diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c index 42b1322..251ed83 100644 --- a/libfprint/fp-device.c +++ b/libfprint/fp-device.c @@ -50,6 +50,8 @@ enum { PROP_FINGER_STATUS, PROP_FPI_ENVIRON, PROP_FPI_USB_DEVICE, + PROP_FPI_UDEV_DATA_SPIDEV, + PROP_FPI_UDEV_DATA_HIDRAW, PROP_FPI_DRIVER_DATA, N_PROPS }; @@ -169,6 +171,8 @@ fp_device_finalize (GObject *object) g_clear_object (&priv->usb_device); g_clear_pointer (&priv->virtual_env, g_free); + g_clear_pointer (&priv->udev_data.spidev_path, g_free); + g_clear_pointer (&priv->udev_data.hidraw_path, g_free); G_OBJECT_CLASS (fp_device_parent_class)->finalize (object); } @@ -248,6 +252,20 @@ fp_device_set_property (GObject *object, g_assert (g_value_get_object (value) == NULL); break; + case PROP_FPI_UDEV_DATA_SPIDEV: + if (cls->type == FP_DEVICE_TYPE_UDEV) + priv->udev_data.spidev_path = g_value_dup_string (value); + else + g_assert (g_value_get_string (value) == NULL); + break; + + case PROP_FPI_UDEV_DATA_HIDRAW: + if (cls->type == FP_DEVICE_TYPE_UDEV) + priv->udev_data.hidraw_path = g_value_dup_string (value); + else + g_assert (g_value_get_string (value) == NULL); + break; + case PROP_FPI_DRIVER_DATA: priv->driver_data = g_value_get_uint64 (value); break; @@ -425,6 +443,32 @@ fp_device_class_init (FpDeviceClass *klass) "Private: The USB device for the device", G_USB_TYPE_DEVICE, G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + /** + * FpDevice::fpi-udev-data-spidev: (skip) + * + * This property is only for internal purposes. + * + * Stability: private + */ + properties[PROP_FPI_UDEV_DATA_SPIDEV] = + g_param_spec_string ("fpi-udev-data-spidev", + "Udev data: spidev path", + "Private: The path to /dev/spidevN.M", + NULL, + G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + /** + * FpDevice::fpi-udev-data-hidraw: (skip) + * + * This property is only for internal purposes. + * + * Stability: private + */ + properties[PROP_FPI_UDEV_DATA_HIDRAW] = + g_param_spec_string ("fpi-udev-data-hidraw", + "Udev data: hidraw path", + "Private: The path to /dev/hidrawN", + NULL, + G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); /** * FpDevice::fpi-driver-data: (skip) @@ -673,6 +717,7 @@ fp_device_open (FpDevice *device, break; case FP_DEVICE_TYPE_VIRTUAL: + case FP_DEVICE_TYPE_UDEV: break; default: diff --git a/libfprint/fp-device.h b/libfprint/fp-device.h index c2ecd5e..806ad19 100644 --- a/libfprint/fp-device.h +++ b/libfprint/fp-device.h @@ -38,10 +38,12 @@ G_DECLARE_DERIVABLE_TYPE (FpDevice, fp_device, FP, DEVICE, GObject) /** * FpDeviceType: * @FP_DEVICE_TYPE_VIRTUAL: The device is a virtual device + * @FP_DEVICE_TYPE_UDEV: The device is a udev device * @FP_DEVICE_TYPE_USB: The device is a USB device */ typedef enum { FP_DEVICE_TYPE_VIRTUAL, + FP_DEVICE_TYPE_UDEV, FP_DEVICE_TYPE_USB, } FpDeviceType; diff --git a/libfprint/fpi-device.c b/libfprint/fpi-device.c index 231dde9..9f334e2 100644 --- a/libfprint/fpi-device.c +++ b/libfprint/fpi-device.c @@ -335,6 +335,38 @@ fpi_device_get_usb_device (FpDevice *device) return priv->usb_device; } +/** + * fpi_device_get_udev_data: + * @device: The #FpDevice + * @subtype: Which subtype to get information about + * + * Get a subtype-specific hardware resource for this #FpDevice. Only permissible to call if the + * #FpDevice is of type %FP_DEVICE_TYPE_UDEV. + * + * Returns: Depends on @subtype; for SPIDEV/HIDRAW returns a path to the relevant device. + */ +gpointer +fpi_device_get_udev_data (FpDevice *device, FpiDeviceUdevSubtypeFlags subtype) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + g_return_val_if_fail (FP_IS_DEVICE (device), NULL); + g_return_val_if_fail (priv->type == FP_DEVICE_TYPE_UDEV, NULL); + + switch (subtype) + { + case FPI_DEVICE_UDEV_SUBTYPE_HIDRAW: + return priv->udev_data.hidraw_path; + + case FPI_DEVICE_UDEV_SUBTYPE_SPIDEV: + return priv->udev_data.spidev_path; + + default: + g_return_val_if_reached (NULL); + return NULL; + } +} + /** * fpi_device_get_virtual_env: * @device: The #FpDevice @@ -977,6 +1009,7 @@ fpi_device_close_complete (FpDevice *device, GError *error) break; case FP_DEVICE_TYPE_VIRTUAL: + case FP_DEVICE_TYPE_UDEV: break; default: diff --git a/libfprint/fpi-device.h b/libfprint/fpi-device.h index e0938ae..669ce15 100644 --- a/libfprint/fpi-device.h +++ b/libfprint/fpi-device.h @@ -24,6 +24,18 @@ #include "fp-image.h" #include "fpi-print.h" +#include + +/** + * FpiDeviceUdevSubtype: + * @FPI_DEVICE_UDEV_SUBTYPE_SPIDEV: The device requires an spidev node + * @FPI_DEVICE_UDEV_SUBTYPE_HIDRAW: The device requires a hidraw node + */ +typedef enum { + FPI_DEVICE_UDEV_SUBTYPE_SPIDEV = 1 << 0, + FPI_DEVICE_UDEV_SUBTYPE_HIDRAW = 1 << 1, +} FpiDeviceUdevSubtypeFlags; + /** * FpIdEntry: * @@ -43,6 +55,16 @@ struct _FpIdEntry guint vid; }; const gchar *virtual_envvar; + struct + { + FpiDeviceUdevSubtypeFlags udev_types; + const gchar *spi_acpi_id; + struct + { + guint pid; + guint vid; + } hid_id; + }; }; guint64 driver_data; }; @@ -171,6 +193,8 @@ typedef enum { GUsbDevice *fpi_device_get_usb_device (FpDevice *device); const gchar *fpi_device_get_virtual_env (FpDevice *device); +gpointer fpi_device_get_udev_data (FpDevice *device, + FpiDeviceUdevSubtypeFlags subtype); //const gchar *fpi_device_get_spi_dev (FpDevice *device); diff --git a/libfprint/meson.build b/libfprint/meson.build index ee86033..0a24ffb 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -224,6 +224,7 @@ deps = [ glib_dep, gobject_dep, gusb_dep, + gudev_dep, imaging_dep, mathlib_dep, nss_dep, diff --git a/meson.build b/meson.build index 1d63530..e110b33 100644 --- a/meson.build +++ b/meson.build @@ -94,6 +94,9 @@ virtual_drivers = [ 'virtual_device_storage', ] +udev_drivers = [ +] + default_drivers = [ 'upektc_img', 'vfs5011', @@ -126,7 +129,7 @@ endian_independent_drivers = virtual_drivers + [ 'synaptics', ] -all_drivers = default_drivers + virtual_drivers +all_drivers = default_drivers + virtual_drivers + udev_drivers if drivers == [ 'all' ] drivers = all_drivers @@ -154,6 +157,7 @@ endif nss_dep = dependency('', required: false) imaging_dep = dependency('', required: false) +gudev_dep = dependency('', required: false) libfprint_conf.set10('HAVE_PIXMAN', false) foreach driver: drivers if driver == 'uru4000' @@ -170,6 +174,14 @@ foreach driver: drivers libfprint_conf.set10('HAVE_PIXMAN', true) endif + if udev_drivers.contains(driver) + gudev_dep = dependency('gudev-1.0', required: false) + if not gudev_dep.found() + error('udev is required for SPI support') + endif + + libfprint_conf.set10('HAVE_UDEV', true) + endif if not all_drivers.contains(driver) error('Invalid driver \'' + driver + '\'') endif