diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 9d1fd351..7ad05c85 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -315,6 +315,14 @@ usb:v05BAp000A* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver validity +usb:v138Ap0090* +usb:v138Ap0097* +usb:v06CBp009A* +usb:v138Ap009D* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver vcom5s usb:v061Ap0110* ID_AUTOSUSPEND=1 @@ -384,7 +392,6 @@ usb:v06CBp0051* usb:v06CBp0081* usb:v06CBp0088* usb:v06CBp008A* -usb:v06CBp009A* usb:v06CBp009B* usb:v06CBp00A1* usb:v06CBp00A2* @@ -432,11 +439,8 @@ usb:v138Ap003A* usb:v138Ap003C* usb:v138Ap003D* usb:v138Ap003F* -usb:v138Ap0090* usb:v138Ap0092* usb:v138Ap0094* -usb:v138Ap0097* -usb:v138Ap009D* usb:v138Ap00AB* usb:v138Ap00A6* usb:v147Ep1002* diff --git a/libfprint/drivers/validity/validity.c b/libfprint/drivers/validity/validity.c new file mode 100644 index 00000000..aaf8d870 --- /dev/null +++ b/libfprint/drivers/validity/validity.c @@ -0,0 +1,410 @@ +/* + * Main driver for Validity/Synaptics VCSFW fingerprint sensors + * + * Copyright (C) 2024 libfprint contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define FP_COMPONENT "validity" + +#include "drivers_api.h" +#include "fpi-byte-reader.h" +#include "validity.h" +#include "vcsfw_protocol.h" + +G_DEFINE_TYPE (FpiDeviceValidity, fpi_device_validity, FP_TYPE_DEVICE) + +static const FpIdEntry id_table[] = { + { .vid = 0x138A, .pid = 0x0090, .driver_data = VALIDITY_DEV_90 }, + { .vid = 0x138A, .pid = 0x0097, .driver_data = VALIDITY_DEV_97 }, + { .vid = 0x06CB, .pid = 0x009A, .driver_data = VALIDITY_DEV_9A }, + { .vid = 0x138A, .pid = 0x009D, .driver_data = VALIDITY_DEV_9D }, + { .vid = 0, .pid = 0, .driver_data = 0 }, +}; + +/* ================================================================ + * Probe + * ================================================================ + * + * Probe is done synchronously (like synaptics driver): + * 1) Open USB device + * 2) Send GET_VERSION (cmd 0x01) + * 3) Read response, parse firmware info + * 4) Close USB device + * 5) Report probe complete + */ + +static void +dev_probe (FpDevice *device) +{ + FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device); + GUsbDevice *usb_dev; + + g_autoptr(FpiUsbTransfer) transfer = NULL; + GError *error = NULL; + FpiByteReader reader; + guint16 status; + g_autofree gchar *serial = NULL; + + G_DEBUG_HERE (); + + self->dev_type = fpi_device_get_driver_data (device); + + usb_dev = fpi_device_get_usb_device (device); + + if (!g_usb_device_open (usb_dev, &error)) + { + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_reset (usb_dev, &error)) + { + fp_dbg ("USB reset failed: %s", error->message); + goto err_close; + } + + if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error)) + goto err_close; + + /* Send GET_VERSION (cmd 0x01) */ + transfer = fpi_usb_transfer_new (device); + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + VALIDITY_USB_SEND_HEADER_LEN); + transfer->short_is_error = TRUE; + transfer->buffer[0] = VCSFW_CMD_GET_VERSION; + if (!fpi_usb_transfer_submit_sync (transfer, VALIDITY_USB_TIMEOUT, &error)) + goto err_close; + + /* Read response */ + g_clear_pointer (&transfer, fpi_usb_transfer_unref); + transfer = fpi_usb_transfer_new (device); + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN, + VALIDITY_MAX_TRANSFER_LEN); + if (!fpi_usb_transfer_submit_sync (transfer, VALIDITY_USB_TIMEOUT, &error)) + goto err_close; + + /* Parse status */ + fpi_byte_reader_init (&reader, transfer->buffer, transfer->actual_length); + + if (!fpi_byte_reader_get_uint16_le (&reader, &status)) + { + g_warning ("GET_VERSION response too short"); + error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO); + goto err_close; + } + + if (status != VCSFW_STATUS_OK) + { + g_warning ("GET_VERSION returned error status: 0x%04x", status); + error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO); + goto err_close; + } + + /* Parse version info (data after the 2-byte status) */ + if (!vcsfw_parse_version (transfer->buffer + 2, + transfer->actual_length - 2, + &self->version_info)) + { + g_warning ("Failed to parse GET_VERSION response"); + error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO); + goto err_close; + } + + fp_dbg ("Validity sensor firmware:"); + fp_dbg (" Version: %d.%d", + self->version_info.version_major, + self->version_info.version_minor); + fp_dbg (" Product: %d", self->version_info.product); + fp_dbg (" Build Num: %d", self->version_info.build_num); + fp_dbg (" Build Time: %u", self->version_info.build_time); + + /* Build a serial string from the serial number bytes */ + serial = g_strdup_printf ("%02x%02x%02x%02x%02x%02x", + self->version_info.serial_number[0], + self->version_info.serial_number[1], + self->version_info.serial_number[2], + self->version_info.serial_number[3], + self->version_info.serial_number[4], + self->version_info.serial_number[5]); + + g_usb_device_release_interface (usb_dev, 0, 0, NULL); + g_usb_device_close (usb_dev, NULL); + + fpi_device_probe_complete (device, serial, NULL, NULL); + return; + +err_close: + g_usb_device_release_interface (usb_dev, 0, 0, NULL); + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); +} + +/* ================================================================ + * Open + * ================================================================ + * + * Open claims the USB interface and sends the init sequence: + * 1) GET_VERSION (0x01) + * 2) UNKNOWN_INIT (0x19) + * 3) GET_FW_INFO (0x43 0x02) — check if fwext loaded + * 4) Send init_hardcoded blob + * 5) If no fwext: send init_hardcoded_clean_slate blob + */ + +typedef enum { + OPEN_GET_VERSION = 0, + OPEN_RECV_VERSION, + OPEN_SEND_CMD19, + OPEN_RECV_CMD19, + OPEN_SEND_GET_FW_INFO, + OPEN_RECV_GET_FW_INFO, + OPEN_DONE, + OPEN_NUM_STATES, +} ValidityOpenSsmState; + +/* Track whether fw extension is loaded */ +typedef struct +{ + gboolean fwext_loaded; +} OpenSsmData; + +static void +open_run_state (FpiSsm *ssm, + FpDevice *dev) +{ + FpiUsbTransfer *transfer; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case OPEN_GET_VERSION: + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + VALIDITY_USB_SEND_HEADER_LEN); + transfer->buffer[0] = VCSFW_CMD_GET_VERSION; + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); + break; + + case OPEN_RECV_VERSION: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN, + VALIDITY_MAX_TRANSFER_LEN); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); + break; + + case OPEN_SEND_CMD19: + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + VALIDITY_USB_SEND_HEADER_LEN); + transfer->buffer[0] = VCSFW_CMD_UNKNOWN_INIT; + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); + break; + + case OPEN_RECV_CMD19: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN, + VALIDITY_MAX_TRANSFER_LEN); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); + break; + + case OPEN_SEND_GET_FW_INFO: + { + guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, 0x02 }; + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + sizeof (cmd)); + memcpy (transfer->buffer, cmd, sizeof (cmd)); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); + } + break; + + case OPEN_RECV_GET_FW_INFO: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN, + VALIDITY_MAX_TRANSFER_LEN); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); + + /* Parse result: check if fwext is loaded. + * We only need the 2-byte status: 0x0000 = loaded, anything else = not loaded. + * This is checked later when we get the actual response. For now, just read. */ + break; + + case OPEN_DONE: + /* All init commands sent. Mark open complete. */ + fpi_ssm_mark_completed (ssm); + break; + } +} + +static void +open_ssm_done (FpiSsm *ssm, + FpDevice *dev, + GError *error) +{ + FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); + + self->cmd_ssm = NULL; + + if (error) + { + g_usb_device_release_interface (fpi_device_get_usb_device (dev), 0, 0, NULL); + fpi_device_open_complete (dev, error); + return; + } + + fp_info ("Validity sensor opened successfully"); + fpi_device_open_complete (dev, NULL); +} + +static void +dev_open (FpDevice *device) +{ + FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device); + GError *error = NULL; + FpiSsm *ssm; + + G_DEBUG_HERE (); + + self->interrupt_cancellable = g_cancellable_new (); + + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error)) + { + fpi_device_open_complete (device, error); + return; + } + + ssm = fpi_ssm_new (device, open_run_state, OPEN_NUM_STATES); + self->cmd_ssm = ssm; + fpi_ssm_start (ssm, open_ssm_done); +} + +/* ================================================================ + * Close + * ================================================================ */ + +static void +dev_close (FpDevice *device) +{ + FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device); + + g_autoptr(GError) error = NULL; + + G_DEBUG_HERE (); + + g_clear_pointer (&self->cmd_response_data, g_free); + self->cmd_response_len = 0; + + g_clear_object (&self->interrupt_cancellable); + + g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error); + + fpi_device_close_complete (device, g_steal_pointer (&error)); +} + +/* ================================================================ + * Enroll / Verify / Identify / Delete stubs + * ================================================================ + * + * These are stubs for Iteration 1. Real implementations come in + * Iteration 5 (Flash/DB Management). + */ + +static void +enroll (FpDevice *device) +{ + fpi_device_enroll_complete (device, NULL, + fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); +} + +static void +verify (FpDevice *device) +{ + fpi_device_verify_complete (device, + fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); +} + +static void +identify (FpDevice *device) +{ + fpi_device_identify_complete (device, + fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); +} + +static void +delete_print (FpDevice *device) +{ + fpi_device_delete_complete (device, + fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); +} + +static void +cancel (FpDevice *device) +{ + FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device); + + g_cancellable_cancel (self->interrupt_cancellable); + g_clear_object (&self->interrupt_cancellable); + self->interrupt_cancellable = g_cancellable_new (); +} + +/* ================================================================ + * GObject boilerplate + * ================================================================ */ + +static void +fpi_device_validity_init (FpiDeviceValidity *self) +{ +} + +static void +fpi_device_validity_class_init (FpiDeviceValidityClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = "Validity VCSFW Fingerprint Sensor"; + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + dev_class->id_table = id_table; + dev_class->nr_enroll_stages = VALIDITY_ENROLL_STAGES; + dev_class->temp_hot_seconds = -1; + + dev_class->probe = dev_probe; + dev_class->open = dev_open; + dev_class->close = dev_close; + dev_class->enroll = enroll; + dev_class->verify = verify; + dev_class->identify = identify; + dev_class->delete = delete_print; + dev_class->cancel = cancel; + + fpi_device_class_auto_initialize_features (dev_class); +} diff --git a/libfprint/drivers/validity/validity.h b/libfprint/drivers/validity/validity.h new file mode 100644 index 00000000..4d358696 --- /dev/null +++ b/libfprint/drivers/validity/validity.h @@ -0,0 +1,103 @@ +/* + * Validity/Synaptics VCSFW fingerprint sensor driver + * + * Copyright (C) 2024 libfprint contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "fpi-device.h" +#include "fpi-ssm.h" + +/* USB Endpoint addresses */ +#define VALIDITY_EP_CMD_OUT 0x01 +#define VALIDITY_EP_CMD_IN 0x81 +#define VALIDITY_EP_DATA_IN 0x82 +#define VALIDITY_EP_INT_IN 0x83 + +/* USB transfer parameters */ +#define VALIDITY_USB_TIMEOUT 15000 +#define VALIDITY_USB_INT_TIMEOUT 100 +#define VALIDITY_MAX_TRANSFER_LEN (100 * 1024) +#define VALIDITY_USB_INT_DATA_SIZE 1024 +#define VALIDITY_USB_SEND_HEADER_LEN 1 + +/* Number of enroll stages */ +#define VALIDITY_ENROLL_STAGES 8 + +/* Interrupt response bits */ +#define VALIDITY_INT_FINGER_DOWN 0x02 +#define VALIDITY_INT_SCAN_COMPLETE 0x04 + +typedef enum { + VALIDITY_DEV_90 = 0, /* 138a:0090 */ + VALIDITY_DEV_97, /* 138a:0097 */ + VALIDITY_DEV_9A, /* 06cb:009a */ + VALIDITY_DEV_9D, /* 138a:009d */ +} ValidityDeviceType; + +/* Firmware version info from GET_VERSION (cmd 0x01) */ +typedef struct +{ + guint32 build_time; + guint32 build_num; + guint8 version_major; + guint8 version_minor; + guint8 target; + guint8 product; + guint8 silicon_rev; + guint8 formal_release; + guint8 platform; + guint8 patch; + guint8 serial_number[6]; + guint16 security; + guint8 iface; + guint8 device_type; +} ValidityVersionInfo; + +/* Open SSM states */ +typedef enum { + VALIDITY_OPEN_CLAIM_INTERFACE = 0, + VALIDITY_OPEN_SEND_INIT, + VALIDITY_OPEN_NUM_STATES, +} ValidityOpenState; + +/* Close SSM states */ +typedef enum { + VALIDITY_CLOSE_RELEASE_INTERFACE = 0, + VALIDITY_CLOSE_NUM_STATES, +} ValidityCloseState; + +#define FPI_TYPE_DEVICE_VALIDITY (fpi_device_validity_get_type ()) +G_DECLARE_FINAL_TYPE (FpiDeviceValidity, fpi_device_validity, + FPI, DEVICE_VALIDITY, FpDevice) + +struct _FpiDeviceValidity +{ + FpDevice parent; + + ValidityDeviceType dev_type; + ValidityVersionInfo version_info; + GCancellable *interrupt_cancellable; + + /* Command SSM: manages the send-cmd/recv-response cycle */ + FpiSsm *cmd_ssm; + + /* Pending response data stashed for higher-level SSM consumption */ + guint8 *cmd_response_data; + gsize cmd_response_len; +}; diff --git a/libfprint/drivers/validity/vcsfw_protocol.c b/libfprint/drivers/validity/vcsfw_protocol.c new file mode 100644 index 00000000..22b8ae20 --- /dev/null +++ b/libfprint/drivers/validity/vcsfw_protocol.c @@ -0,0 +1,248 @@ +/* + * VCSFW protocol implementation for Validity/Synaptics fingerprint sensors + * + * Copyright (C) 2024 libfprint contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define FP_COMPONENT "validity" + +#include "drivers_api.h" +#include "fpi-byte-reader.h" +#include "vcsfw_protocol.h" + +/* ---- VcsfwCmdData lifecycle ---- */ + +VcsfwCmdData * +vcsfw_cmd_data_new (const guint8 *cmd, + gsize cmd_len, + VcsfwCmdCallback callback) +{ + VcsfwCmdData *data = g_new0 (VcsfwCmdData, 1); + + data->cmd_data = g_memdup2 (cmd, cmd_len); + data->cmd_len = cmd_len; + data->callback = callback; + + return data; +} + +void +vcsfw_cmd_data_free (gpointer data) +{ + VcsfwCmdData *cmd_data = data; + + if (cmd_data == NULL) + return; + + g_free (cmd_data->cmd_data); + g_free (cmd_data); +} + +/* ---- Receive callback ---- */ + +static void +cmd_receive_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer user_data, + GError *error) +{ + FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device); + VcsfwCmdData *cmd_data = fpi_ssm_get_data (transfer->ssm); + guint16 status; + + if (error) + { + if (cmd_data->callback) + cmd_data->callback (self, NULL, 0, 0, error); + else + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + if (transfer->actual_length < 2) + { + g_warning ("VCSFW response too short: %" G_GSSIZE_FORMAT " bytes", + transfer->actual_length); + error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO); + if (cmd_data->callback) + cmd_data->callback (self, NULL, 0, 0, error); + else + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + status = FP_READ_UINT16_LE (transfer->buffer); + + fp_dbg ("VCSFW response: status=0x%04x, len=%" G_GSSIZE_FORMAT, + status, transfer->actual_length - 2); + + /* Stash raw response for direct access if needed */ + g_clear_pointer (&self->cmd_response_data, g_free); + if (transfer->actual_length > 2) + { + self->cmd_response_len = transfer->actual_length - 2; + self->cmd_response_data = g_memdup2 (transfer->buffer + 2, + self->cmd_response_len); + } + else + { + self->cmd_response_len = 0; + self->cmd_response_data = NULL; + } + + if (cmd_data->callback) + { + cmd_data->callback (self, + self->cmd_response_data, + self->cmd_response_len, + status, + NULL); + } + + /* If the callback didn't fail the SSM, advance it */ + fpi_ssm_mark_completed (transfer->ssm); +} + +/* ---- Command/Response SSM ---- */ + +void +vcsfw_cmd_run_state (FpiSsm *ssm, + FpDevice *dev) +{ + VcsfwCmdData *cmd_data = fpi_ssm_get_data (ssm); + FpiUsbTransfer *transfer; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case VCSFW_CMD_STATE_SEND: + fp_dbg ("VCSFW send cmd 0x%02x, len=%" G_GSIZE_FORMAT, + cmd_data->cmd_data[0], cmd_data->cmd_len); + + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + cmd_data->cmd_len); + memcpy (transfer->buffer, cmd_data->cmd_data, cmd_data->cmd_len); + transfer->ssm = ssm; + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); + break; + + case VCSFW_CMD_STATE_RECV: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN, + VALIDITY_MAX_TRANSFER_LEN); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + cmd_receive_cb, NULL); + break; + } +} + +/* ---- High-level command sender ---- */ + +static void +cmd_ssm_done (FpiSsm *ssm, + FpDevice *dev, + GError *error) +{ + FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); + + self->cmd_ssm = NULL; + + if (error) + fp_dbg ("VCSFW command SSM failed: %s", error->message); + + /* Error is handled by the callback, nothing else to do here */ + g_clear_error (&error); +} + +void +vcsfw_cmd_send (FpiDeviceValidity *self, + FpiSsm *parent_ssm, + const guint8 *cmd, + gsize cmd_len, + VcsfwCmdCallback callback) +{ + FpiSsm *ssm; + VcsfwCmdData *cmd_data; + + cmd_data = vcsfw_cmd_data_new (cmd, cmd_len, callback); + + ssm = fpi_ssm_new (FP_DEVICE (self), vcsfw_cmd_run_state, + VCSFW_CMD_STATE_NUM_STATES); + fpi_ssm_set_data (ssm, cmd_data, vcsfw_cmd_data_free); + + self->cmd_ssm = ssm; + + if (parent_ssm) + fpi_ssm_start_subsm (parent_ssm, ssm); + else + fpi_ssm_start (ssm, cmd_ssm_done); +} + +/* ---- GET_VERSION (cmd 0x01) response parser ---- */ + +gboolean +vcsfw_parse_version (const guint8 *data, + gsize data_len, + ValidityVersionInfo *info) +{ + FpiByteReader reader; + + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (info != NULL, FALSE); + + fpi_byte_reader_init (&reader, data, data_len); + + if (!fpi_byte_reader_get_uint32_le (&reader, &info->build_time)) + return FALSE; + if (!fpi_byte_reader_get_uint32_le (&reader, &info->build_num)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->version_major)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->version_minor)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->target)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->product)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->silicon_rev)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->formal_release)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->platform)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->patch)) + return FALSE; + + { + const guint8 *serial; + if (!fpi_byte_reader_get_data (&reader, sizeof (info->serial_number), &serial)) + return FALSE; + memcpy (info->serial_number, serial, sizeof (info->serial_number)); + } + + if (!fpi_byte_reader_get_uint16_le (&reader, &info->security)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->iface)) + return FALSE; + if (!fpi_byte_reader_get_uint8 (&reader, &info->device_type)) + return FALSE; + + return TRUE; +} diff --git a/libfprint/drivers/validity/vcsfw_protocol.h b/libfprint/drivers/validity/vcsfw_protocol.h new file mode 100644 index 00000000..f2c136df --- /dev/null +++ b/libfprint/drivers/validity/vcsfw_protocol.h @@ -0,0 +1,115 @@ +/* + * VCSFW protocol definitions for Validity/Synaptics fingerprint sensors + * + * Copyright (C) 2024 libfprint contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "validity.h" + +/* ---- VCSFW Command IDs (pre-TLS) ---- */ +#define VCSFW_CMD_GET_VERSION 0x01 +#define VCSFW_CMD_READ_HW_REG32 0x07 +#define VCSFW_CMD_WRITE_HW_REG32 0x08 +#define VCSFW_CMD_UNKNOWN_INIT 0x19 +#define VCSFW_CMD_GET_FLASH_INFO 0x3E +#define VCSFW_CMD_READ_FLASH 0x40 +#define VCSFW_CMD_WRITE_FLASH 0x41 +#define VCSFW_CMD_WRITE_FW_SIG 0x42 +#define VCSFW_CMD_GET_FW_INFO 0x43 +#define VCSFW_CMD_PARTITION_FLASH 0x4F + +/* ---- VCSFW Command IDs (post-TLS, via tls.app()) ---- */ +#define VCSFW_CMD_CAPTURE 0x02 +#define VCSFW_CMD_CAPTURE_STOP 0x04 +#define VCSFW_CMD_GLOW_START 0x39 +#define VCSFW_CMD_ERASE_FLASH 0x3F +#define VCSFW_CMD_DB_INFO 0x45 +#define VCSFW_CMD_GET_RECORD_CHILDREN 0x46 +#define VCSFW_CMD_NEW_RECORD 0x47 +#define VCSFW_CMD_DEL_RECORD 0x48 +#define VCSFW_CMD_GET_RECORD_VALUE 0x49 +#define VCSFW_CMD_GET_USER 0x4A +#define VCSFW_CMD_GET_USER_STORAGE 0x4B +#define VCSFW_CMD_GET_PRG_STATUS 0x51 +#define VCSFW_CMD_MATCH_FINGER 0x5E +#define VCSFW_CMD_GET_MATCH_RESULT 0x60 +#define VCSFW_CMD_MATCH_CLEANUP 0x62 +#define VCSFW_CMD_ENROLLMENT_UPDATE_START 0x68 +#define VCSFW_CMD_CREATE_ENROLLMENT 0x69 +#define VCSFW_CMD_ENROLLMENT_UPDATE 0x6B +#define VCSFW_CMD_GET_FACTORY_BITS 0x6F +#define VCSFW_CMD_IDENTIFY_SENSOR 0x75 + +/* ---- VCSFW Response Status Codes ---- */ +#define VCSFW_STATUS_OK 0x0000 +#define VCSFW_STATUS_NO_FW 0xB004 + +/* ---- Callback types ---- */ + +/** + * VcsfwCmdCallback: + * @self: the validity device + * @data: response data (after 2-byte status, NULL on error) + * @data_len: length of response data (excluding 2-byte status) + * @status: the 2-byte VCSFW status code + * @error: a GError if the transfer failed, or NULL + * + * Callback invoked after a VCSFW command/response exchange completes. + */ +typedef void (*VcsfwCmdCallback) (FpiDeviceValidity *self, + const guint8 *data, + gsize data_len, + guint16 status, + GError *error); + +/* ---- Command/response SSM states ---- */ +typedef enum { + VCSFW_CMD_STATE_SEND = 0, + VCSFW_CMD_STATE_RECV, + VCSFW_CMD_STATE_NUM_STATES, +} VcsfwCmdSsmState; + +/* ---- Context for a single command/response exchange ---- */ +typedef struct +{ + guint8 *cmd_data; + gsize cmd_len; + VcsfwCmdCallback callback; +} VcsfwCmdData; + +/* ---- Functions ---- */ + +void vcsfw_cmd_run_state (FpiSsm *ssm, + FpDevice *dev); + +VcsfwCmdData *vcsfw_cmd_data_new (const guint8 *cmd, + gsize cmd_len, + VcsfwCmdCallback callback); + +void vcsfw_cmd_data_free (gpointer data); + +void vcsfw_cmd_send (FpiDeviceValidity *self, + FpiSsm *parent_ssm, + const guint8 *cmd, + gsize cmd_len, + VcsfwCmdCallback callback); + +gboolean vcsfw_parse_version (const guint8 *data, + gsize data_len, + ValidityVersionInfo *info); diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index 6e2adb04..cb73f8c4 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -62,7 +62,6 @@ static const FpIdEntry allowlist_id_table[] = { { .vid = 0x06cb, .pid = 0x0081 }, { .vid = 0x06cb, .pid = 0x0088 }, { .vid = 0x06cb, .pid = 0x008a }, - { .vid = 0x06cb, .pid = 0x009a }, { .vid = 0x06cb, .pid = 0x009b }, { .vid = 0x06cb, .pid = 0x00a1 }, { .vid = 0x06cb, .pid = 0x00a2 }, @@ -110,11 +109,8 @@ static const FpIdEntry allowlist_id_table[] = { { .vid = 0x138a, .pid = 0x003c }, { .vid = 0x138a, .pid = 0x003d }, { .vid = 0x138a, .pid = 0x003f }, - { .vid = 0x138a, .pid = 0x0090 }, { .vid = 0x138a, .pid = 0x0092 }, { .vid = 0x138a, .pid = 0x0094 }, - { .vid = 0x138a, .pid = 0x0097 }, - { .vid = 0x138a, .pid = 0x009d }, { .vid = 0x138a, .pid = 0x00ab }, { .vid = 0x138a, .pid = 0x00a6 }, { .vid = 0x147e, .pid = 0x1002 }, diff --git a/libfprint/meson.build b/libfprint/meson.build index ae0f6e24..2a04b5e0 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -153,6 +153,9 @@ driver_sources = { [ 'drivers/realtek/realtek.c' ], 'focaltech_moc' : [ 'drivers/focaltech_moc/focaltech_moc.c' ], + 'validity' : + [ 'drivers/validity/validity.c', + 'drivers/validity/vcsfw_protocol.c' ], } helper_sources = { diff --git a/meson.build b/meson.build index 14fb11f2..1ba656f3 100644 --- a/meson.build +++ b/meson.build @@ -144,6 +144,7 @@ default_drivers = [ 'fpcmoc', 'realtek', 'focaltech_moc', + 'validity', ] spi_drivers = [ @@ -178,6 +179,7 @@ endian_independent_drivers = virtual_drivers + [ 'vcom5s', 'vfs101', 'vfs7552', + 'validity', ] all_drivers = default_drivers + virtual_drivers diff --git a/tests/meson.build b/tests/meson.build index 07c924be..121e2a39 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -59,6 +59,7 @@ drivers_tests = [ 'realtek', 'realtek-5816', 'focaltech_moc', + 'validity', ] if get_option('introspection') diff --git a/tests/validity/custom.pcapng b/tests/validity/custom.pcapng new file mode 100644 index 00000000..7cfeab77 Binary files /dev/null and b/tests/validity/custom.pcapng differ diff --git a/tests/validity/custom.py b/tests/validity/custom.py new file mode 100644 index 00000000..57f7d22c --- /dev/null +++ b/tests/validity/custom.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 + +import traceback +import sys +import gi + +gi.require_version('FPrint', '2.0') +from gi.repository import FPrint + +# Exit with error on any exception, including those happening in callbacks +sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1)) + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +assert len(devices) == 1, f"Expected 1 device, got {len(devices)}" + +d = devices[0] +del devices + +# Verify driver name +assert d.get_driver() == "validity", f"Expected 'validity', got '{d.get_driver()}'" + +# Verify features detected by auto_initialize_features +# Since iteration 1 stubs provide verify/identify/delete function pointers, +# those features are reported even though they return NOT_SUPPORTED. +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +assert d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert not d.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK) +assert not d.has_feature(FPrint.DeviceFeature.STORAGE) +assert not d.has_feature(FPrint.DeviceFeature.STORAGE_LIST) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) +assert not d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) +assert d.has_feature(FPrint.DeviceFeature.ALWAYS_ON) + +# Test open (sends GET_VERSION, cmd 0x19, GET_FW_INFO) and close +print("opening") +d.open_sync() +print("open done") + +print("closing") +d.close_sync() +print("close done") + +del d +del c diff --git a/tests/validity/device b/tests/validity/device new file mode 100644 index 00000000..656264f9 --- /dev/null +++ b/tests/validity/device @@ -0,0 +1,264 @@ +P: /devices/pci0000:00/0000:00:14.0/usb1/1-9 +N: bus/usb/001/006=12010002FF10FF08CB069A0064010000010109023500010100A0320904000005FF000000070501024000000705810240000007058202400000070583030800040705840310000A +E: BUSNUM=001 +E: DEVNAME=/dev/bus/usb/001/006 +E: DEVNUM=006 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_AUTOSUSPEND=1 +E: ID_BUS=usb +E: ID_INTEGRATION=internal +E: ID_MODEL=009a +E: ID_MODEL_ENC=009a +E: ID_MODEL_FROM_DATABASE=Metallica MIS Touch Fingerprint Reader +E: ID_MODEL_ID=009a +E: ID_PATH=pci-0000:00:14.0-usb-0:9 +E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_9 +E: ID_PATH_WITH_USB_REVISION=pci-0000:00:14.0-usbv2-0:9 +E: ID_PERSIST=0 +E: ID_REVISION=0164 +E: ID_SERIAL=06cb_009a_c7e2948627cb +E: ID_SERIAL_SHORT=c7e2948627cb +E: ID_USB_INTERFACES=:ff0000: +E: ID_USB_MODEL=009a +E: ID_USB_MODEL_ENC=009a +E: ID_USB_MODEL_ID=009a +E: ID_USB_REVISION=0164 +E: ID_USB_SERIAL=06cb_009a_c7e2948627cb +E: ID_USB_SERIAL_SHORT=c7e2948627cb +E: ID_USB_VENDOR=06cb +E: ID_USB_VENDOR_ENC=06cb +E: ID_USB_VENDOR_ID=06cb +E: ID_VENDOR=06cb +E: ID_VENDOR_ENC=06cb +E: ID_VENDOR_FROM_DATABASE=Synaptics, Inc. +E: ID_VENDOR_ID=06cb +E: MAJOR=189 +E: MINOR=5 +E: PRODUCT=6cb/9a/164 +E: SUBSYSTEM=usb +E: TYPE=255/16/255 +A: authorized=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=ff\n +A: bDeviceProtocol=ff\n +A: bDeviceSubClass=10\n +A: bMaxPacketSize0=8\n +A: bMaxPower=100mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0164\n +A: bmAttributes=a0\n +A: busnum=1\n +A: configuration= +H: descriptors=12010002FF10FF08CB069A0064010000010109023500010100A0320904000005FF000000070501024000000705810240000007058202400000070583030800040705840310000A +A: dev=189:5\n +A: devnum=6\n +A: devpath=9\n +L: driver=../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d/device:1e/device:29 +A: idProduct=009a\n +A: idVendor=06cb\n +A: ltm_capable=no\n +A: maxchild=0\n +A: physical_location/dock=no\n +A: physical_location/horizontal_position=left\n +A: physical_location/lid=no\n +A: physical_location/panel=unknown\n +A: physical_location/vertical_position=upper\n +L: port=../1-0:1.0/usb1-port9 +A: power/active_duration=39041\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=8463727\n +A: power/control=auto\n +A: power/level=auto\n +A: power/persist=1\n +A: power/runtime_active_time=39927\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=8423564\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: quirks=0x0\n +A: removable=fixed\n +A: rx_lanes=1\n +A: serial=c7e2948627cb\n +A: speed=12\n +A: tx_lanes=1\n +A: urbnum=124\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0/usb1 +N: bus/usb/001/001=12010002090001406B1D020019060302010109021900010100E0000904000001090000000705810304000C +E: BUSNUM=001 +E: CURRENT_TAGS=:seat: +E: DEVNAME=/dev/bus/usb/001/001 +E: DEVNUM=001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_AUTOSUSPEND=1 +E: ID_BUS=usb +E: ID_FOR_SEAT=usb-pci-0000_00_14_0 +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_MODEL_ID=0002 +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_REVISION=0619 +E: ID_SERIAL=Linux_6.19.10-zen1-1-zen_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +E: ID_USB_INTERFACES=:090000: +E: ID_USB_MODEL=xHCI_Host_Controller +E: ID_USB_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_USB_MODEL_ID=0002 +E: ID_USB_REVISION=0619 +E: ID_USB_SERIAL=Linux_6.19.10-zen1-1-zen_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_USB_SERIAL_SHORT=0000:00:14.0 +E: ID_USB_VENDOR=Linux_6.19.10-zen1-1-zen_xhci-hcd +E: ID_USB_VENDOR_ENC=Linux\x206.19.10-zen1-1-zen\x20xhci-hcd +E: ID_USB_VENDOR_ID=1d6b +E: ID_VENDOR=Linux_6.19.10-zen1-1-zen_xhci-hcd +E: ID_VENDOR_ENC=Linux\x206.19.10-zen1-1-zen\x20xhci-hcd +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_VENDOR_ID=1d6b +E: MAJOR=189 +E: MINOR=0 +E: PRODUCT=1d6b/2/619 +E: SUBSYSTEM=usb +E: TAGS=:seat: +E: TYPE=9/0/1 +A: authorized=1\n +A: authorized_default=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=09\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=0mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0619\n +A: bmAttributes=e0\n +A: busnum=1\n +A: configuration= +H: descriptors=12010002090001406B1D020019060302010109021900010100E0000904000001090000000705810304000C +A: dev=189:0\n +A: devnum=1\n +A: devpath=0\n +L: driver=../../../../bus/usb/drivers/usb +L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d/device:1e +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 6.19.10-zen1-1-zen xhci-hcd\n +A: maxchild=12\n +A: power/active_duration=8464967\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=8464967\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_time=8464965\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=xHCI Host Controller\n +A: quirks=0x0\n +A: removable=unknown\n +A: rx_lanes=1\n +A: serial=0000:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=1481\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: ID_AUTOSUSPEND=1 +E: ID_MODEL_FROM_DATABASE=Sunrise Point-LP USB 3.0 xHCI Controller +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: MODALIAS=pci:v00008086d00009D2Fsv000017AAsd00002258bc0Csc03i30 +E: PCI_CLASS=C0330 +E: PCI_ID=8086:9D2F +E: PCI_SLOT_NAME=0000:00:14.0 +E: PCI_SUBSYS_ID=17AA:2258 +E: SUBSYSTEM=pci +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=86802F9D060490022130030C00008000040032E8000000000000000000000000000000000000000000000000AA175822000000007000000000000000FF010000FD01348088C60F8000000000000000005B6ECE0F000000000000000000000000306000000000000000000000000000000180C2C10800000000000000000000000500B7001803E0FE0000000000000000090014F01000400100000000C10A080000080000001800008F40020000010400010000000200000000000000000000000000000000000000000000000000000001000000020000000000000000000000000000000000000000000000000000000000000000000000B30F410800000000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: dbc=disabled\n +A: dbc_bInterfaceProtocol=01\n +A: dbc_bcdDevice=0010\n +A: dbc_idProduct=0010\n +A: dbc_idVendor=1d6b\n +A: dbc_poll_interval_ms=64\n +A: device=0x9d2f\n +A: dma_mask_bits=64\n +L: driver=../../../bus/pci/drivers/xhci_hcd +A: driver_override=(null)\n +A: enable=1\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d +L: iommu=../../virtual/iommu/dmar1 +L: iommu_group=../../../kernel/iommu_groups/4 +A: irq=128\n +A: local_cpulist=0-7\n +A: local_cpus=ff\n +A: modalias=pci:v00008086d00009D2Fsv000017AAsd00002258bc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/128=msi\n +A: msi_irqs/129=msi\n +A: msi_irqs/130=msi\n +A: msi_irqs/131=msi\n +A: msi_irqs/132=msi\n +A: msi_irqs/133=msi\n +A: msi_irqs/134=msi\n +A: msi_irqs/135=msi\n +A: numa_node=-1\n +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 1 32 128 1\nbuffer-32 0 0 32 0\nxHCI 256 port bw ctx arrays 0 0 256 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 12 13 2112 13\nxHCI ring segments 45 45 4096 45\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 3 32 128 1\nbuffer-32 0 0 32 0\n +A: power/control=auto\n +A: power/runtime_active_time=8465365\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=0\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=0\n +A: power/wakeup_last_time_ms=0\n +A: power/wakeup_max_time_ms=0\n +A: power/wakeup_total_time_ms=0\n +A: power_state=D0\n +A: resource=0x00000000e8320000 0x00000000e832ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x21\n +A: subsystem_device=0x2258\n +A: subsystem_vendor=0x17aa\n +A: vendor=0x8086\n +