diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index fe682714..6dd37c14 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -191,6 +191,11 @@ usb:v2808p079A* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver focaltech_moh +usb:v2808p9338* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver fpcmoc usb:v10A5pFFE0* usb:v10A5pA305* @@ -484,7 +489,6 @@ usb:v27C6p589A* usb:v27C6p5F10* usb:v27C6p5F91* usb:v27C6p6382* -usb:v2808p9338* usb:v2808p9348* usb:v2808p93A9* usb:v2808pA658* diff --git a/libfprint/drivers/focaltech_moh/focaltech_moh.c b/libfprint/drivers/focaltech_moh/focaltech_moh.c new file mode 100644 index 00000000..3088b5ea --- /dev/null +++ b/libfprint/drivers/focaltech_moh/focaltech_moh.c @@ -0,0 +1,700 @@ +/* + * FocalTech FT9201 Match-on-Host driver for libfprint + * + * Copyright (C) 2025-2026 0xCoDSnet + * + * 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 + */ + +/* + * FocalTech FT9201 (chip FT9338, VID:2808 PID:9338) + * + * Area fingerprint sensor with USB SIU (Serial Interface Unit) bridge. + * 64x80 pixels, 8-bit grayscale, match-on-host. + * + * The sensor resolution (~250 DPI) is too low for NBIS/bozorth3 minutiae + * matching, so this driver implements custom pixel-correlation matching + * using Normalized Cross-Correlation (NCC) with translation search. + * + * The Windows driver uses a similar approach: proprietary "mayflower" + * matching engine with Gabor filter preprocessing. + */ + +#define FP_COMPONENT "focaltech_moh" + +#include "drivers_api.h" +#include "focaltech_moh.h" + +#include + +G_DEFINE_TYPE (FpiDeviceFocaltechMoh, fpi_device_focaltech_moh, + FP_TYPE_DEVICE) + +static const FpIdEntry id_table[] = { + { .vid = FT9201_VID, .pid = FT9201_PID }, + { .vid = 0, .pid = 0 }, +}; + +/* ------------------------------------------------------------------ */ +/* Image preprocessing */ +/* ------------------------------------------------------------------ */ + +static void +ft9201_preprocess (const guint8 *src, guint8 *dst) +{ + int w = FT9201_RAW_WIDTH; + int h = FT9201_RAW_HEIGHT; + int half = FT9201_LOCAL_MEAN_WINDOW / 2; + int x, y, kx, ky; + + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + /* Bitwise NOT — matches Windows driver ~pixel inversion */ + int val = ~src[y * w + x] & 0xFF; + + /* Local mean subtraction (high-pass filter) */ + int sum = 0; + int count = 0; + + for (ky = MAX (0, y - half); ky <= MIN (h - 1, y + half); ky++) + for (kx = MAX (0, x - half); kx <= MIN (w - 1, x + half); kx++) + { + sum += ~src[ky * w + kx] & 0xFF; + count++; + } + + int diff = val - sum / count + 128; + + dst[y * w + x] = (guint8) CLAMP (diff, 0, 255); + } + } +} + +static int +count_unique_values (const guint8 *data, int size) +{ + gboolean seen[256] = { FALSE, }; + int unique = 0; + int i; + + for (i = 0; i < size; i++) + { + if (!seen[data[i]]) + { + seen[data[i]] = TRUE; + unique++; + } + } + + return unique; +} + +/* ------------------------------------------------------------------ */ +/* NCC matching */ +/* ------------------------------------------------------------------ */ + +static double +ft9201_ncc (const guint8 *a, const guint8 *b, int dx, int dy) +{ + int w = FT9201_RAW_WIDTH; + int h = FT9201_RAW_HEIGHT; + int x0 = MAX (0, -dx), x1 = MIN (w, w - dx); + int y0 = MAX (0, -dy), y1 = MIN (h, h - dy); + int n = (x1 - x0) * (y1 - y0); + double sum_a = 0, sum_b = 0; + double mean_a, mean_b; + double num = 0, denom_a = 0, denom_b = 0, denom; + int x, y; + + if (n < w * h / 2) + return -1.0; + + for (y = y0; y < y1; y++) + for (x = x0; x < x1; x++) + { + sum_a += a[y * w + x]; + sum_b += b[(y + dy) * w + (x + dx)]; + } + + mean_a = sum_a / n; + mean_b = sum_b / n; + + for (y = y0; y < y1; y++) + for (x = x0; x < x1; x++) + { + double da = a[y * w + x] - mean_a; + double db = b[(y + dy) * w + (x + dx)] - mean_b; + + num += da * db; + denom_a += da * da; + denom_b += db * db; + } + + denom = sqrt (denom_a * denom_b); + if (denom < 1e-6) + return 0.0; + + return num / denom; +} + +static double +ft9201_match_score (const guint8 *tmpl, const guint8 *probe) +{ + int r = FT9201_SEARCH_RADIUS; + double best = -1.0; + int dx, dy; + + for (dy = -r; dy <= r; dy++) + for (dx = -r; dx <= r; dx++) + { + double score = ft9201_ncc (tmpl, probe, dx, dy); + + if (score > best) + best = score; + } + + return best; +} + +/* ------------------------------------------------------------------ */ +/* USB helper: send vendor control OUT */ +/* ------------------------------------------------------------------ */ + +static void +ft9201_ctrl_out (FpDevice *dev, + FpiSsm *ssm, + guint8 request, + guint16 value, + guint16 index) +{ + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + + transfer->ssm = ssm; + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + request, value, index, 0); + fpi_usb_transfer_submit (transfer, FT9201_CMD_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); +} + +/* ------------------------------------------------------------------ */ +/* Capture state machine (used as sub-SSM) */ +/* ------------------------------------------------------------------ */ + +static void +capture_read_cb (FpiUsbTransfer *transfer, + FpDevice *dev, + gpointer user_data, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + fpi_ssm_next_state (transfer->ssm); +} + +static void +finger_poll_cb (FpiUsbTransfer *transfer, + FpDevice *dev, + gpointer user_data, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + fp_dbg ("INT_STATUS: 0x%02x 0x%02x 0x%02x 0x%02x (len=%zu)", + transfer->buffer[0], transfer->buffer[1], + transfer->buffer[2], transfer->buffer[3], + transfer->actual_length); + + if (transfer->buffer[0] == 0x01) + { + fp_dbg ("Finger detected!"); + fpi_device_report_finger_status_changes (dev, + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NONE); + fpi_ssm_next_state (transfer->ssm); + } + else + { + fpi_ssm_jump_to_state_delayed (transfer->ssm, CAPTURE_POLL_FINGER, + FT9201_POLL_INTERVAL); + } +} + +static void +capture_ssm_handler (FpiSsm *ssm, FpDevice *dev) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + int state = fpi_ssm_get_cur_state (ssm); + + switch (state) + { + case CAPTURE_WARMUP_PREP1: + if (self->warmup_done) + { + fpi_ssm_jump_to_state (ssm, CAPTURE_POLL_FINGER); + return; + } + ft9201_ctrl_out (dev, ssm, FT9201_REQ_PREPARE, FT9201_PREPARE_INIT, 0); + break; + + case CAPTURE_WARMUP_PREP2: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_PREPARE, FT9201_PREPARE_READ, 0); + break; + + case CAPTURE_WARMUP_CMD: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_NEW_SIU_RW, 0x0020, FT9201_REG_STATUS); + break; + + case CAPTURE_WARMUP_READ: + { + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_bulk (transfer, FT9201_EP_IN, 32); + transfer->short_is_error = FALSE; + transfer->ssm = ssm; + fpi_usb_transfer_submit (transfer, FT9201_CMD_TIMEOUT, NULL, + capture_read_cb, NULL); + self->warmup_done = TRUE; + fp_dbg ("Warmup bulk read submitted"); + } + break; + + case CAPTURE_POLL_FINGER: + { + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + FT9201_REQ_INT_STATUS, 0, 0, 4); + transfer->ssm = ssm; + fpi_usb_transfer_submit (transfer, FT9201_CMD_TIMEOUT, NULL, + finger_poll_cb, NULL); + } + break; + + case CAPTURE_SYNC_PREP1: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_PREPARE, FT9201_PREPARE_INIT, 0); + break; + + case CAPTURE_SYNC_PREP2: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_PREPARE, FT9201_PREPARE_READ, 0); + break; + + case CAPTURE_SYNC_CMD: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_NEW_SIU_RW, 0, FT9201_REG_SYNC); + break; + + case CAPTURE_STATUS_PREP1: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_PREPARE, FT9201_PREPARE_INIT, 0); + break; + + case CAPTURE_STATUS_PREP2: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_PREPARE, FT9201_PREPARE_READ, 0); + break; + + case CAPTURE_STATUS_CMD: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_NEW_SIU_RW, 4, FT9201_REG_STATUS); + break; + + case CAPTURE_STATUS_READ: + { + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_bulk (transfer, FT9201_EP_IN, 32); + transfer->short_is_error = FALSE; + transfer->ssm = ssm; + fpi_usb_transfer_submit (transfer, FT9201_CMD_TIMEOUT, NULL, + capture_read_cb, NULL); + } + break; + + case CAPTURE_IMG_PREP1: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_PREPARE, FT9201_PREPARE_INIT, 0); + break; + + case CAPTURE_IMG_PREP2: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_PREPARE, FT9201_PREPARE_READ, 0); + break; + + case CAPTURE_IMG_CMD: + ft9201_ctrl_out (dev, ssm, FT9201_REQ_NEW_SIU_RW, + FT9201_RAW_SIZE, FT9201_REG_CAPTURE); + break; + + case CAPTURE_IMG_READ: + { + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_bulk_full (transfer, FT9201_EP_IN, + self->image_buf, FT9201_RAW_SIZE, + NULL); + transfer->short_is_error = FALSE; + transfer->ssm = ssm; + fpi_usb_transfer_submit (transfer, FT9201_CMD_TIMEOUT, NULL, + capture_read_cb, NULL); + } + break; + + default: + g_assert_not_reached (); + } +} + +/* ------------------------------------------------------------------ */ +/* Enroll state machine */ +/* ------------------------------------------------------------------ */ + +static void +enroll_ssm_handler (FpiSsm *ssm, FpDevice *dev) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + int state = fpi_ssm_get_cur_state (ssm); + + switch (state) + { + case ENROLL_CAPTURE: + { + FpiSsm *capture = fpi_ssm_new (dev, capture_ssm_handler, + CAPTURE_NUM_STATES); + + fpi_device_report_finger_status_changes (dev, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + fpi_ssm_start_subsm (ssm, capture); + } + break; + + case ENROLL_STORE_IMAGE: + { + int unique; + guint8 preprocessed[FT9201_RAW_SIZE]; + + fpi_device_report_finger_status_changes (dev, + FP_FINGER_STATUS_NONE, + FP_FINGER_STATUS_PRESENT); + + unique = count_unique_values (self->image_buf, FT9201_RAW_SIZE); + fp_dbg ("Enroll stage %d: %d unique values", self->enroll_stage, unique); + + if (unique < FT9201_MIN_UNIQUE_VALUES) + { + fp_dbg ("Low quality image, retrying"); + fpi_device_enroll_progress (dev, self->enroll_stage, NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER)); + fpi_ssm_jump_to_state (ssm, ENROLL_CAPTURE); + return; + } + + ft9201_preprocess (self->image_buf, preprocessed); + memcpy (self->enroll_images[self->enroll_stage], preprocessed, + FT9201_RAW_SIZE); + + self->enroll_stage++; + fp_dbg ("Enroll stage %d/%d completed", + self->enroll_stage, FT9201_NUM_ENROLL_STAGES); + + fpi_device_enroll_progress (dev, self->enroll_stage, NULL, NULL); + + if (self->enroll_stage < FT9201_NUM_ENROLL_STAGES) + fpi_ssm_jump_to_state (ssm, ENROLL_CAPTURE); + else + fpi_ssm_next_state (ssm); + } + break; + + case ENROLL_COMMIT: + { + FpPrint *print = NULL; + GVariantBuilder builder; + GVariant *data; + int i; + + fpi_device_get_enroll_data (dev, &print); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ay)")); + for (i = 0; i < FT9201_NUM_ENROLL_STAGES; i++) + { + GVariant *img = g_variant_new_fixed_array ( + G_VARIANT_TYPE_BYTE, + self->enroll_images[i], FT9201_RAW_SIZE, 1); + + g_variant_builder_add (&builder, "(@ay)", img); + } + data = g_variant_new ("(ya(ay))", (guint8) 1, &builder); + + fpi_print_set_type (print, FPI_PRINT_RAW); + g_object_set (print, "fpi-data", data, NULL); + + fp_info ("Enrollment complete, %d templates stored", + FT9201_NUM_ENROLL_STAGES); + + fpi_device_enroll_complete (dev, g_object_ref (print), NULL); + fpi_ssm_mark_completed (ssm); + } + break; + + default: + g_assert_not_reached (); + } +} + +static void +enroll_ssm_complete (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + + self->task_ssm = NULL; + + if (error) + fpi_device_enroll_complete (dev, NULL, error); +} + +/* ------------------------------------------------------------------ */ +/* Verify state machine */ +/* ------------------------------------------------------------------ */ + +static void +verify_ssm_handler (FpiSsm *ssm, FpDevice *dev) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + int state = fpi_ssm_get_cur_state (ssm); + + switch (state) + { + case VERIFY_CAPTURE: + { + FpiSsm *capture = fpi_ssm_new (dev, capture_ssm_handler, + CAPTURE_NUM_STATES); + + fpi_device_report_finger_status_changes (dev, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + fpi_ssm_start_subsm (ssm, capture); + } + break; + + case VERIFY_MATCH: + { + FpPrint *print = NULL; + g_autoptr(GVariant) var_data = NULL; + g_autoptr(GVariant) var_images = NULL; + guint8 preprocessed[FT9201_RAW_SIZE]; + guint8 version; + double best_score = -1.0; + GVariantIter iter; + GVariant *img_var; + int unique; + int tmpl_idx = 0; + + fpi_device_report_finger_status_changes (dev, + FP_FINGER_STATUS_NONE, + FP_FINGER_STATUS_PRESENT); + + unique = count_unique_values (self->image_buf, FT9201_RAW_SIZE); + fp_dbg ("Verify: %d unique values", unique); + + if (unique < FT9201_MIN_UNIQUE_VALUES) + { + fp_dbg ("Low quality verify image, retrying"); + fpi_device_verify_report (dev, FPI_MATCH_ERROR, NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER)); + fpi_device_verify_complete (dev, NULL); + fpi_ssm_mark_completed (ssm); + return; + } + + ft9201_preprocess (self->image_buf, preprocessed); + + fpi_device_get_verify_data (dev, &print); + g_object_get (print, "fpi-data", &var_data, NULL); + + if (!g_variant_check_format_string (var_data, "(ya(ay))", FALSE)) + { + fpi_device_verify_report (dev, FPI_MATCH_ERROR, NULL, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + fpi_device_verify_complete (dev, NULL); + fpi_ssm_mark_completed (ssm); + return; + } + + g_variant_get (var_data, "(y@a(ay))", &version, &var_images); + fp_dbg ("Template version: %d", version); + + g_variant_iter_init (&iter, var_images); + while ((img_var = g_variant_iter_next_value (&iter)) != NULL) + { + g_autoptr(GVariant) inner = NULL; + const guint8 *tmpl_data; + gsize tmpl_len; + + g_variant_get (img_var, "(@ay)", &inner); + tmpl_data = g_variant_get_fixed_array (inner, &tmpl_len, 1); + + if (tmpl_len == FT9201_RAW_SIZE) + { + double score = ft9201_match_score (tmpl_data, preprocessed); + + fp_dbg ("NCC template %d: %.4f", tmpl_idx, score); + if (score > best_score) + best_score = score; + } + + g_variant_unref (img_var); + tmpl_idx++; + } + + fp_info ("Best NCC score: %.4f (threshold: %.2f)", + best_score, FT9201_NCC_THRESHOLD); + + if (best_score >= FT9201_NCC_THRESHOLD) + fpi_device_verify_report (dev, FPI_MATCH_SUCCESS, print, NULL); + else + fpi_device_verify_report (dev, FPI_MATCH_FAIL, NULL, NULL); + + fpi_device_verify_complete (dev, NULL); + fpi_ssm_mark_completed (ssm); + } + break; + + default: + g_assert_not_reached (); + } +} + +static void +verify_ssm_complete (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + + self->task_ssm = NULL; + + if (error) + { + fpi_device_verify_report (dev, FPI_MATCH_ERROR, NULL, error); + fpi_device_verify_complete (dev, NULL); + } +} + +/* ------------------------------------------------------------------ */ +/* Device lifecycle */ +/* ------------------------------------------------------------------ */ + +static void +dev_open (FpDevice *dev) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + GError *error = NULL; + + G_DEBUG_HERE (); + + if (!g_usb_device_reset (fpi_device_get_usb_device (dev), &error)) + { + fp_dbg ("USB reset failed (non-fatal): %s", error->message); + g_clear_error (&error); + } + + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (dev), + 0, 0, &error)) + { + fpi_device_open_complete (dev, error); + return; + } + + self->image_buf = g_malloc0 (FT9201_RAW_SIZE); + self->warmup_done = FALSE; + + fpi_device_open_complete (dev, NULL); +} + +static void +dev_close (FpDevice *dev) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + GError *error = NULL; + + G_DEBUG_HERE (); + + g_clear_pointer (&self->image_buf, g_free); + + g_usb_device_release_interface (fpi_device_get_usb_device (dev), + 0, 0, &error); + fpi_device_close_complete (dev, error); +} + +/* ------------------------------------------------------------------ */ +/* Enroll / Verify entry points */ +/* ------------------------------------------------------------------ */ + +static void +focaltech_moh_enroll (FpDevice *dev) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + + self->enroll_stage = 0; + self->task_ssm = fpi_ssm_new (dev, enroll_ssm_handler, ENROLL_NUM_STATES); + fpi_ssm_start (self->task_ssm, enroll_ssm_complete); +} + +static void +focaltech_moh_verify (FpDevice *dev) +{ + FpiDeviceFocaltechMoh *self = FPI_DEVICE_FOCALTECH_MOH (dev); + + self->task_ssm = fpi_ssm_new (dev, verify_ssm_handler, VERIFY_NUM_STATES); + fpi_ssm_start (self->task_ssm, verify_ssm_complete); +} + +/* ------------------------------------------------------------------ */ +/* GType boilerplate */ +/* ------------------------------------------------------------------ */ + +static void +fpi_device_focaltech_moh_init (FpiDeviceFocaltechMoh *self) +{ +} + +static void +fpi_device_focaltech_moh_class_init (FpiDeviceFocaltechMohClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = "focaltech_moh"; + dev_class->full_name = "FocalTech FT9201 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 = FT9201_NUM_ENROLL_STAGES; + + dev_class->open = dev_open; + dev_class->close = dev_close; + dev_class->enroll = focaltech_moh_enroll; + dev_class->verify = focaltech_moh_verify; + + fpi_device_class_auto_initialize_features (dev_class); +} diff --git a/libfprint/drivers/focaltech_moh/focaltech_moh.h b/libfprint/drivers/focaltech_moh/focaltech_moh.h new file mode 100644 index 00000000..31161544 --- /dev/null +++ b/libfprint/drivers/focaltech_moh/focaltech_moh.h @@ -0,0 +1,129 @@ +/* + * FocalTech FT9201 Match-on-Host driver for libfprint + * + * Copyright (C) 2025-2026 0xCoDSnet + * + * 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" + +G_DECLARE_FINAL_TYPE (FpiDeviceFocaltechMoh, fpi_device_focaltech_moh, FPI, + DEVICE_FOCALTECH_MOH, FpDevice) + +#define FT9201_VID 0x2808 +#define FT9201_PID 0x9338 + +#define FT9201_EP_IN 0x83 /* Bulk IN (EP3, 32B max packet) */ + +/* Raw sensor image: 64 wide x 80 high, 8-bit grayscale */ +#define FT9201_RAW_WIDTH 64 +#define FT9201_RAW_HEIGHT 80 +#define FT9201_RAW_SIZE (FT9201_RAW_WIDTH * FT9201_RAW_HEIGHT) /* 5120 */ + +#define FT9201_CMD_TIMEOUT 5000 +#define FT9201_POLL_INTERVAL 30 /* ms between finger detection polls */ + +/* Enrollment and matching */ +#define FT9201_NUM_ENROLL_STAGES 5 +#define FT9201_NCC_THRESHOLD 0.30 +#define FT9201_SEARCH_RADIUS 3 /* pixels, each direction */ +#define FT9201_LOCAL_MEAN_WINDOW 7 /* 7x7 window for high-pass */ +#define FT9201_MIN_UNIQUE_VALUES 50 /* minimum unique pixel values for quality */ + +/* USB vendor request codes */ +#define FT9201_REQ_PREPARE 0x34 +#define FT9201_REQ_INT_STATUS 0x43 +#define FT9201_REQ_NEW_SIU_RW 0x6F + +/* Prepare command wValue values */ +#define FT9201_PREPARE_INIT 0x00FF +#define FT9201_PREPARE_READ 0x0003 + +/* New SIU compound register addresses (wIndex for req 0x6F) */ +#define FT9201_REG_STATUS 0x9180 /* Chip status / OTP info */ +#define FT9201_REG_CAPTURE 0x9080 /* Image capture */ +#define FT9201_REG_SYNC 0xFF00 /* Sync / reset (size=0, no bulk) */ + +/* + * Capture state machine — one state per async USB transfer. + * + * The read sequence is: PREPARE_INIT -> PREPARE_READ -> NEW_SIU_RW -> BULK_IN. + * Each is a separate async transfer, so each gets its own SSM state. + * + * This SSM is used as a sub-SSM within enroll and verify SSMs. + */ +enum capture_states { + /* Warmup: discard first bulk read after USB reset */ + CAPTURE_WARMUP_PREP1, /* OUT 0x34(0xFF) */ + CAPTURE_WARMUP_PREP2, /* OUT 0x34(3) */ + CAPTURE_WARMUP_CMD, /* OUT 0x6F(32, 0x9180) */ + CAPTURE_WARMUP_READ, /* BULK IN 32B (discard) */ + + /* Finger detection: poll INT_STATUS until finger present */ + CAPTURE_POLL_FINGER, /* IN 0x43 -- byte0: 0=no finger, 1=finger */ + + /* Sync: poke 0xFF00 */ + CAPTURE_SYNC_PREP1, /* OUT 0x34(0xFF) */ + CAPTURE_SYNC_PREP2, /* OUT 0x34(3) */ + CAPTURE_SYNC_CMD, /* OUT 0x6F(0, 0xFF00) -- no bulk */ + + /* Status: read 4 bytes from 0x9180 */ + CAPTURE_STATUS_PREP1, /* OUT 0x34(0xFF) */ + CAPTURE_STATUS_PREP2, /* OUT 0x34(3) */ + CAPTURE_STATUS_CMD, /* OUT 0x6F(4, 0x9180) */ + CAPTURE_STATUS_READ, /* BULK IN 32B (check status) */ + + /* Image: read 5120 bytes from 0x9080 */ + CAPTURE_IMG_PREP1, /* OUT 0x34(0xFF) */ + CAPTURE_IMG_PREP2, /* OUT 0x34(3) */ + CAPTURE_IMG_CMD, /* OUT 0x6F(0x1400, 0x9080) */ + CAPTURE_IMG_READ, /* BULK IN 5120B */ + + CAPTURE_NUM_STATES, +}; + +/* Enroll SSM: captures 5 images, stores as template */ +enum enroll_states { + ENROLL_CAPTURE, /* Sub-SSM: full capture cycle */ + ENROLL_STORE_IMAGE, /* Preprocess + store in template array */ + ENROLL_COMMIT, /* Serialize to GVariant, complete enrollment */ + ENROLL_NUM_STATES, +}; + +/* Verify SSM: captures 1 image, matches against stored template */ +enum verify_states { + VERIFY_CAPTURE, /* Sub-SSM: full capture cycle */ + VERIFY_MATCH, /* NCC matching against stored templates */ + VERIFY_NUM_STATES, +}; + +struct _FpiDeviceFocaltechMoh +{ + FpDevice parent; + + gboolean warmup_done; + guint8 *image_buf; + + /* Enroll state */ + int enroll_stage; + guint8 enroll_images[FT9201_NUM_ENROLL_STAGES][FT9201_RAW_SIZE]; + + /* Top-level SSM */ + FpiSsm *task_ssm; +}; diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index 6e2adb04..eb0f9d87 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -158,7 +158,6 @@ static const FpIdEntry allowlist_id_table[] = { { .vid = 0x27c6, .pid = 0x5f10 }, { .vid = 0x27c6, .pid = 0x5f91 }, { .vid = 0x27c6, .pid = 0x6382 }, - { .vid = 0x2808, .pid = 0x9338 }, { .vid = 0x2808, .pid = 0x9348 }, { .vid = 0x2808, .pid = 0x93a9 }, { .vid = 0x2808, .pid = 0xa658 }, diff --git a/libfprint/meson.build b/libfprint/meson.build index ae0f6e24..9acf14b4 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -153,6 +153,8 @@ driver_sources = { [ 'drivers/realtek/realtek.c' ], 'focaltech_moc' : [ 'drivers/focaltech_moc/focaltech_moc.c' ], + 'focaltech_moh' : + [ 'drivers/focaltech_moh/focaltech_moh.c' ], } helper_sources = { diff --git a/meson.build b/meson.build index 14fb11f2..1d588b55 100644 --- a/meson.build +++ b/meson.build @@ -144,6 +144,7 @@ default_drivers = [ 'fpcmoc', 'realtek', 'focaltech_moc', + 'focaltech_moh', ] spi_drivers = [ @@ -168,6 +169,7 @@ endian_independent_drivers = virtual_drivers + [ 'elanmoc', 'etes603', 'focaltech_moc', + 'focaltech_moh', 'nb1010', 'realtek', 'synaptics', diff --git a/tests/focaltech_moh/custom.pcapng b/tests/focaltech_moh/custom.pcapng new file mode 100644 index 00000000..bbb4eb0b Binary files /dev/null and b/tests/focaltech_moh/custom.pcapng differ diff --git a/tests/focaltech_moh/custom.py b/tests/focaltech_moh/custom.py new file mode 100755 index 00000000..b9a6d97f --- /dev/null +++ b/tests/focaltech_moh/custom.py @@ -0,0 +1,54 @@ +#!/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, included those happening in async callbacks +sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1)) + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +d = devices[0] +del devices + +assert d.get_driver() == "focaltech_moh" +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert not d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +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 not d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) +assert not d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) + +d.open_sync() + +template = FPrint.Print.new(d) + + +def enroll_progress(*args): + print('enroll progress: ' + str(args)) + + +# Enroll +print("enrolling") +p = d.enroll_sync(template, None, enroll_progress, None) +print("enroll done") + +# Verify +print("verifying") +verify_res, verify_print = d.verify_sync(p) +print("verify done") +assert verify_res == True + +d.close_sync() + +del d +del c diff --git a/tests/focaltech_moh/device b/tests/focaltech_moh/device new file mode 100644 index 00000000..88b5611b --- /dev/null +++ b/tests/focaltech_moh/device @@ -0,0 +1,336 @@ +P: /devices/pci0000:00/0000:00:08.1/0000:03:00.4/usb3/3-4 +N: bus/usb/003/004=12010002000000080828389300010102000109022000010100A0640904000002FFFFFF000705020210000007058302200000 +E: BUSNUM=003 +E: CURRENT_TAGS=:uaccess:seat: +E: DEVNAME=/dev/bus/usb/003/004 +E: DEVNUM=004 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_AUTOSUSPEND=1 +E: ID_BUS=usb +E: ID_FOR_SEAT=usb-pci-0000_03_00_4-usb-0_4 +E: ID_MODEL=FT9201Fingerprint.̚ +E: ID_MODEL_ENC=FT9201Fingerprint.̚ +E: ID_MODEL_ID=9338 +E: ID_PATH=pci-0000:03:00.4-usb-0:4 +E: ID_PATH_TAG=pci-0000_03_00_4-usb-0_4 +E: ID_PATH_WITH_USB_REVISION=pci-0000:03:00.4-usbv2-0:4 +E: ID_PERSIST=0 +E: ID_REVISION=0100 +E: ID_SERIAL=Focal-systems.Corp_FT9201Fingerprint.̚ +E: ID_USB_INTERFACES=:ffffff: +E: ID_USB_MODEL=FT9201Fingerprint.̚ +E: ID_USB_MODEL_ENC=FT9201Fingerprint.̚ +E: ID_USB_MODEL_ID=9338 +E: ID_USB_REVISION=0100 +E: ID_USB_SERIAL=Focal-systems.Corp_FT9201Fingerprint.̚ +E: ID_USB_VENDOR=Focal-systems.Corp +E: ID_USB_VENDOR_ENC=Focal-systems.Corp +E: ID_USB_VENDOR_ID=2808 +E: ID_VENDOR=Focal-systems.Corp +E: ID_VENDOR_ENC=Focal-systems.Corp +E: ID_VENDOR_ID=2808 +E: MAJOR=189 +E: MINOR=259 +E: NVME_HOST_IFACE=none +E: PRODUCT=2808/9338/100 +E: SUBSYSTEM=usb +E: TAGS=:uaccess:seat: +E: TYPE=0/0/0 +E: __DEVCONTEXT=system_u:object_r:usb_device_t:s0 +A: authorized=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=00\n +A: bDeviceProtocol=00\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=8\n +A: bMaxPower=200mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0100\n +A: bmAttributes=a0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002000000080828389300010102000109022000010100A0640904000002FFFFFF000705020210000007058302200000 +A: dev=189:259\n +A: devnum=4\n +A: devpath=4\n +L: driver=../../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12/device:22/device:23/device:27 +A: idProduct=9338\n +A: idVendor=2808\n +A: ltm_capable=no\n +A: manufacturer=Focal-systems.Corp\n +A: maxchild=0\n +A: physical_location/dock=no\n +A: physical_location/horizontal_position=center\n +A: physical_location/lid=no\n +A: physical_location/panel=left\n +A: physical_location/vertical_position=lower\n +L: port=../3-0:1.0/usb3-port4 +A: power/active_duration=889312\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=25080493\n +A: power/control=auto\n +A: power/level=auto\n +A: power/persist=0\n +A: power/runtime_active_time=895447\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=24184353\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=FT9201Fingerprint.\314\232\n +A: quirks=0x0\n +A: removable=fixed\n +A: rx_lanes=1\n +A: speed=12\n +A: tx_lanes=1\n +A: urbnum=18951\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:08.1/0000:03:00.4/usb3 +N: bus/usb/003/001=12010002090001406B1D020018060302010109021900010100E0000904000001090000000705810304000C +E: BUSNUM=003 +E: CURRENT_TAGS=:seat: +E: DEVNAME=/dev/bus/usb/003/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_03_00_4 +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:03:00.4 +E: ID_PATH_TAG=pci-0000_03_00_4 +E: ID_REVISION=0618 +E: ID_SERIAL=Linux_6.18.16-200.fc43.x86_64_xhci-hcd_xHCI_Host_Controller_0000:03:00.4 +E: ID_SERIAL_SHORT=0000:03:00.4 +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=0618 +E: ID_USB_SERIAL=Linux_6.18.16-200.fc43.x86_64_xhci-hcd_xHCI_Host_Controller_0000:03:00.4 +E: ID_USB_SERIAL_SHORT=0000:03:00.4 +E: ID_USB_VENDOR=Linux_6.18.16-200.fc43.x86_64_xhci-hcd +E: ID_USB_VENDOR_ENC=Linux\x206.18.16-200.fc43.x86_64\x20xhci-hcd +E: ID_USB_VENDOR_ID=1d6b +E: ID_VENDOR=Linux_6.18.16-200.fc43.x86_64_xhci-hcd +E: ID_VENDOR_ENC=Linux\x206.18.16-200.fc43.x86_64\x20xhci-hcd +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_VENDOR_ID=1d6b +E: MAJOR=189 +E: MINOR=256 +E: PRODUCT=1d6b/2/618 +E: SUBSYSTEM=usb +E: TAGS=:seat: +E: TYPE=9/0/1 +E: __DEVCONTEXT=system_u:object_r:usb_device_t:s0 +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=0618\n +A: bmAttributes=e0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002090001406B1D020018060302010109021900010100E0000904000001090000000705810304000C +A: dev=189:256\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:12/device:22/device:23 +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 6.18.16-200.fc43.x86_64 xhci-hcd\n +A: maxchild=4\n +A: power/active_duration=898224\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=25081190\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_time=898320\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=24182422\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:03:00.4\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=1423\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:08.1/0000:03:00.4 +E: DRIVER=xhci_hcd +E: ID_MODEL_FROM_DATABASE=Renoir/Cezanne USB 3.1 +E: ID_PATH=pci-0000:03:00.4 +E: ID_PATH_TAG=pci-0000_03_00_4 +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=Advanced Micro Devices, Inc. [AMD] +E: MODALIAS=pci:v00001022d00001639sv00001022sd00001639bc0Csc03i30 +E: PCI_CLASS=C0330 +E: PCI_ID=1022:1639 +E: PCI_SLOT_NAME=0000:03:00.4 +E: PCI_SUBSYS_ID=1022:1639 +E: SUBSYSTEM=pci +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=22103916070410000030030C100080000400A0FC00000000000000000000000000000000000000000000000022103916000000004800000000000000FF01000000000000000000000950080022103916016403C80000000000000000000000003120000010A00200A18F000030290000030D400040000311000000000000000000000000000000001F007100000000001E00800100000100000000000000000005C08600000000000000000000000000000000000000000000000000000000001100078000E00F0000F00F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B00012A010001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000D000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +A: consistent_dma_mask_bits=64\n +A: current_link_speed=8.0 GT/s PCIe\n +A: current_link_width=16\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=0x1639\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:12/device:22 +L: iommu=../../0000:00:00.2/iommu/ivhd0 +L: iommu_group=../../../../kernel/iommu_groups/14 +A: irq=40\n +A: link/l0s_aspm=0\n +A: link/l1_aspm=0\n +A: local_cpulist=0-11\n +A: local_cpus=fff\n +A: max_link_speed=8.0 GT/s PCIe\n +A: max_link_width=16\n +A: modalias=pci:v00001022d00001639sv00001022sd00001639bc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/41=msix\n +A: msi_irqs/42=msix\n +A: msi_irqs/43=msix\n +A: msi_irqs/44=msix\n +A: msi_irqs/45=msix\n +A: msi_irqs/46=msix\n +A: msi_irqs/47=msix\n +A: msi_irqs/48=msix\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 0 0 128 0\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 6 7 2112 7\nxHCI ring segments 23 23 4096 23\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\n +A: power/control=auto\n +A: power/runtime_active_time=901715\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=24179263\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: reset_method=pm\n +A: resource=0x00000000fca00000 0x00000000fcafffff 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=0x00\n +A: subsystem_device=0x1639\n +A: subsystem_vendor=0x1022\n +A: vendor=0x1022\n + +P: /devices/pci0000:00/0000:00:08.1 +E: DRIVER=pcieport +E: ID_MODEL_FROM_DATABASE=Renoir Internal PCIe GPP Bridge to Bus +E: ID_PATH=pci-0000:00:08.1 +E: ID_PATH_TAG=pci-0000_00_08_1 +E: ID_PCI_CLASS_FROM_DATABASE=Bridge +E: ID_PCI_INTERFACE_FROM_DATABASE=Normal decode +E: ID_PCI_SUBCLASS_FROM_DATABASE=PCI bridge +E: ID_VENDOR_FROM_DATABASE=Advanced Micro Devices, Inc. [AMD] +E: MODALIAS=pci:v00001022d00001635sv00001022sd00001635bc06sc04i00 +E: PCI_CLASS=60400 +E: PCI_ID=1022:1635 +E: PCI_SLOT_NAME=0000:00:08.1 +E: PCI_SUBSYS_ID=1022:1635 +E: SUBSYSTEM=pci +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x060400\n +H: config=22103516070410000000040610008100000000000000000000030300F1F1000090FCD0FC01D011E00000000000000000000000005000000000000000FF01120000000000000000000000000000000000015803C80000000010A042002280000030290000030D7000400C03310000000000004000180001000000000000003100000000001E00800143001F00000000000000000000000000000000000000000005C081000000E0FE0000000000000000000000000000000000000000000000000D00000022103516000000000000000000000000000000000000000000000000000000007CA006000000000000000000000000000000000000000000000000000B000127010001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001900012A00000000000000007F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F000000000D0001405F001D000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000250001410100008001000080000000002600014400000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2700010000000000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +A: consistent_dma_mask_bits=32\n +A: current_link_speed=8.0 GT/s PCIe\n +A: current_link_width=16\n +A: d3cold_allowed=1\n +A: device=0x1635\n +A: dma_mask_bits=32\n +L: driver=../../../bus/pci/drivers/pcieport +A: driver_override=(null)\n +A: enable=2\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:12 +L: iommu=../0000:00:00.2/iommu/ivhd0 +L: iommu_group=../../../kernel/iommu_groups/5 +A: irq=30\n +A: local_cpulist=0-11\n +A: local_cpus=fff\n +A: max_link_speed=8.0 GT/s PCIe\n +A: max_link_width=16\n +A: modalias=pci:v00001022d00001635sv00001022sd00001635bc06sc04i00\n +A: msi_bus=1\n +A: msi_irqs/30=msi\n +A: numa_node=-1\n +A: power/autosuspend_delay_ms=100\n +A: power/control=on\n +A: power/runtime_active_time=25080986\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: reset_method=pm\n +A: resource=0x0000000000000000 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\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x000000000000f000 0x000000000000ffff 0x0000000000000101\n0x00000000fc900000 0x00000000fcdfffff 0x0000000000000200\n0x00000000d0000000 0x00000000e01fffff 0x0000000000102201\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x00\n +A: secondary_bus_number=3\n +A: subordinate_bus_number=3\n +A: subsystem_device=0x1635\n +A: subsystem_vendor=0x1022\n +A: vendor=0x1022\n + diff --git a/tests/meson.build b/tests/meson.build index 07c924be..38ecf770 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -59,6 +59,7 @@ drivers_tests = [ 'realtek', 'realtek-5816', 'focaltech_moc', + 'focaltech_moh', ] if get_option('introspection')