Linux driver for Goodix GF3658 fingerprint sensors (USB VID 27C6),
targeting libfprint 2.x.
The protocol was reverse-engineered from the Windows driver stack shipped on Huawei Kepler laptops:
gfspi.dll— UMDF 2.15 driver (v1.1.124.12): SPI-over-USB transport, command set, status codesAlgoMilan.dll— fingerprint algorithm (calibration, template pack/unpack)AlgoChicago.dll— coating variant (IS_COATING=1), template-compatibleEngineAdapter.dll— Windows Biometric engine adapter
The protocol layer, calibration architecture, template serialisation, image
processing, firmware extraction, and OTP/config download are complete and
unit-tested. The driver has not yet been validated on real hardware —
SPI commands do not get a response on the 27c6:5117 CDC-ACM composite
device and a Windows USB capture is required to confirm the real transport.
A USB hardware emulator is included via LD_PRELOAD — see Emulator below.
| USB VID | USB PID | Model |
|---|---|---|
| 27C6 | 5117 | GF3658 var. A |
| 27C6 | 5110 | GF3658 var. B |
| 27C6 | 5130 | GF3658 var. C |
Sensor geometry: 108 x 800 pixels, 16-bit raw, 8-bit processed.
┌───────────────────────────────────────────────────────────┐
│ tools/goodix-cli.c standalone diagnostic CLI (libusb)│
├───────────────────────────────────────────────────────────┤
│ drivers/goodix.c libfprint FpImageDevice driver │
├───────────────────────────────────────────────────────────┤
│ libfprint/goodix-*.c sensor interface + protocol │
│ goodix-dev.c device lifecycle (new/free/reset) │
│ goodix-proto.c USB transport (bulk/ctrl/intr, SPI frame)│
│ goodix-cmd.c sensor command wrappers │
│ goodix-calib.c calibration (config table, OTP, DAC) │
│ goodix-img.c image pipeline (normalize, quality, ROI) │
│ goodix-tpl.c template pack/unpack (TLV + CRC32) │
│ goodix-fw.h firmware upload protocol (4-phase UPFW) │
├───────────────────────────────────────────────────────────┤
│ libfprint/goodix-*.h protocol definitions (headers) │
│ goodix-spi.h SPI frame format, command set, status │
│ goodix-proto.h USB endpoints, vendor requests, errors │
│ goodix-cmd.h chip info, capture, reg, nav, fw update │
│ goodix-calib.h calibration structures and constants │
│ goodix-img.h image processing prototypes │
│ goodix-tpl.h template serialisation format │
├───────────────────────────────────────────────────────────┤
│ emu/goodix-emu.c LD_PRELOAD USB emulator (no HW) │
└───────────────────────────────────────────────────────────┘
Reverse engineering of gfspi.dll revealed that GF3658 on Huawei Kepler does
not use a monolithic NVRAM calibration blob. Calibration is built from
three sources at init time:
- Embedded config tables — 256 bytes per sensor type, stored in the
DLL
.rdatasection, indexed bysensor_typefrom the CMD_CHIP response. - Factory OTP — 32 bytes (MilanL) / 64 bytes (ChicagoHS), read via
GOODIX_ACTION_READ_OTP. - Live DAC registers —
TCODE+DAC1-4, read viaCMD_REG_READ.
The runtime context is struct goodix_calib (see goodix-calib.h).
The sensor MCU firmware was extracted from gfspi.dll's .rdata section:
| Property | Value |
|---|---|
| Size | 13,641 bytes |
| Arch | ARM Cortex-M Thumb |
| Version | 0x0101 |
| Checksum | 0x00137B44 |
| Embedded | drivers/goodix-firmware.c |
Upload uses the 4-phase UPFW protocol (START -> DATA chunks -> END -> reset). Never tested on hardware — use with caution.
meson setup builddir
meson compile -C builddir
sudo meson install -C builddir # installs udev rules
sudo udevadm control --reload-rules && sudo udevadm triggercd tools && make
./goodix-cli --helpgcc -fsyntax-only -std=c11 -I. libfprint/goodix.hcp drivers/goodix.c libfprint/drivers/
# then follow libfprint's CONTRIBUTING.md for the buildSelf-contained unit tests (no hardware required):
cd tests && make && ./run-tests| Suite | Coverage |
|---|---|
| test-tpl | template pack/unpack, CRC32, round-trip, tamper |
| test-calib | calibration constants, chip IDs, DAC regs, geometry |
| test-img | normalize, quality, ROI, flip, bad pixels, bg subtract |
| test-spi | commands, flags, frames, status codes, USB endpoints |
The CI pipeline runs on every push and pull request:
- Build & test matrix — gcc + clang, meson + make
- Static analysis — cppcheck + strict compiler warnings (
-Werror) - Format check — clang-format (non-blocking)
- Alpine/musl — portability build in a minimal container
Run the same checks locally:
./scripts/ci-local.shgoodix-cli [options] <command>
Options:
-v, --verbose Verbose output
-V, --vid HEX USB Vendor ID (default 27c6)
-P, --pid HEX USB Product ID (default 5117)
-o, --output DIR Output directory for images (default .)
-h, --help This help
Commands:
list List Goodix USB devices
init Open and initialise sensor
info Print chip information
capture Capture fingerprint and save as PGM
raw Capture raw 16-bit data and save as .raw
calib Show calibration status
reset Send USB vendor reset request
close Close device and release resources
all Full sequence: init -> capture -> close
regdump <addr> <cnt> Dump sensor registers
rawcapture [file] Capture raw 16-bit data to file
navtest <on|off> Enable/disable gesture mode (polls 5s)
stream <count> <ms> Continuous capture of N frames with delay
tracedump <hex> Send SPI command, dump all USB traffic
preamble [try|custom] Show/set compact frame preamble pattern
gpio read|cs Read GPIO or control SPI chip select
Examples:
goodix-cli list
goodix-cli -v init
goodix-cli -o /tmp/captures all
goodix-cli init && goodix-cli regdump 1000 16
goodix-cli init && goodix-cli stream 10 100
goodix-cli init && goodix-cli navtest onA USB hardware emulator is provided at emu/goodix-emu.so. It intercepts
libusb calls via LD_PRELOAD and simulates a GF3658 sensor — no real
hardware needed. All 15 SPI commands, vendor control requests, CDC ACM
setup, and fingerprint image capture are emulated.
make -C emu/# Run goodix-cli with the emulator
./emu/emu-run.sh list
./emu/emu-run.sh init
./emu/emu-run.sh all
# Specify a synthetic fingerprint image (108x800, 16-bit raw)
python3 emu/gen_image.py /tmp/fingerprint.raw
EMU_RAW_IMAGE=/tmp/fingerprint.raw ./emu/emu-run.sh all
# Simulate no finger
EMU_FINGER=0 ./emu/emu-run.sh capture
# MilanL variant instead of ChicagoHS
EMU_CHIP_ID=0x2205 ./emu/emu-run.sh info
# Debug logs
EMU_LOG_LEVEL=3 ./emu/emu-run.sh init| Variable | Default | Description |
|---|---|---|
EMU_FINGER |
1 | Simulate finger present (0/1) |
EMU_CHIP_ID |
0x2504 | Sensor chip ID (0x2504=ChicagoHS, 0x2205=MilanL) |
EMU_RAW_IMAGE |
built-in | Path to 16-bit raw 108x800 image |
EMU_LOG_LEVEL |
1 | Verbosity: 0=quiet, 1=normal, 2=debug, 3=trace |
- No hardware validation yet. SPI commands time out on the
27c6:5117CDC-ACM composite device; a Windows USB capture is needed to confirm the real transport protocol. Use the emulator instead. - Image calibration apply is currently a simple high-byte 16->8 bit
conversion; the per-pixel gain/offset pipeline needs wiring to the new
struct goodix_calibcontext. - Firmware upload never tested. Risk of bricking the sensor MCU.
- Dual SPI frame format. The driver supports both the standard 8-byte
header and the compact 7-byte header with 4-byte preamble; the correct
variant for
27c6:5117is still unknown.
LGPL-2.1+ — same as libfprint.