mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-14 23:58:08 +02:00
validity: Add firmware extension upload (Iteration 3)
Implement the firmware extension (fwext) upload module for
Validity/Synaptics VCSFW sensors. When the sensor reports no
firmware loaded (GET_FW_INFO returns status 0xB004), the driver
uploads the .xpfwext firmware file using the following sequence:
1. WRITE_HW_REG32 (0x08) to prepare hardware register
2. READ_HW_REG32 (0x07) to verify register state
3. Load .xpfwext file from filesystem search paths
4. For each 4KB chunk:
a. Send db_write_enable blob (encrypted auth token)
b. WRITE_FLASH (0x41) with chunk payload
c. CLEANUP (0x1A) to commit chunk
5. WRITE_FW_SIG (0x42) to upload RSA signature
6. GET_FW_INFO (0x43) to verify successful upload
7. REBOOT (0x05 0x02 0x00) to activate new firmware
Architecture: Uses the NULL-callback subsm pattern where SEND
states call vcsfw_cmd_send(self, ssm, cmd, len, NULL) and RECV
states read self->cmd_response_status/data directly. This avoids
the double-advance bug with fpi_ssm_start_subsm auto-advancing
the parent.
New files:
- validity_fwext.h: Structures, SSM state enum, API declarations
- validity_fwext.c: Upload SSM, file parser, command builders
- validity_blob_dbe_009a.inc: db_write_enable blob for 06cb:009a
- test-validity-fwext.c: 19 unit tests covering all pure functions
Modified files:
- validity.h: Add cmd_response_status field to FpiDeviceValidity
- validity.c: Add OPEN_UPLOAD_FWEXT state to open sequence
- vcsfw_protocol.c: Save status in cmd_receive_cb for RECV states
- meson.build: Add validity_fwext.c to driver sources
Test results: 34 OK, 0 Fail, 2 Skipped
This commit is contained in:
parent
3e86894c81
commit
b028a3ebf5
9 changed files with 1788 additions and 4 deletions
|
|
@ -23,6 +23,7 @@
|
|||
#include "drivers_api.h"
|
||||
#include "fpi-byte-reader.h"
|
||||
#include "validity.h"
|
||||
#include "validity_fwext.h"
|
||||
#include "validity_tls.h"
|
||||
#include "vcsfw_protocol.h"
|
||||
|
||||
|
|
@ -162,8 +163,9 @@ err_close:
|
|||
* 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
|
||||
* 4) Upload firmware extension (if not loaded)
|
||||
* 5) Send init_hardcoded blob
|
||||
* 6) If no fwext: send init_hardcoded_clean_slate blob
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
|
|
@ -173,6 +175,7 @@ typedef enum {
|
|||
OPEN_RECV_CMD19,
|
||||
OPEN_SEND_GET_FW_INFO,
|
||||
OPEN_RECV_GET_FW_INFO,
|
||||
OPEN_UPLOAD_FWEXT,
|
||||
OPEN_TLS_READ_FLASH,
|
||||
OPEN_TLS_DERIVE_PSK,
|
||||
OPEN_TLS_HANDSHAKE,
|
||||
|
|
@ -245,6 +248,33 @@ flash_read_ssm_done (FpiSsm *ssm,
|
|||
fpi_ssm_next_state (self->open_ssm);
|
||||
}
|
||||
|
||||
/* Callback for fwext upload child SSM */
|
||||
static void
|
||||
fwext_upload_ssm_done (FpiSsm *ssm,
|
||||
FpDevice *dev,
|
||||
GError *error)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (error)
|
||||
{
|
||||
fp_warn ("Firmware extension upload failed: %s", error->message);
|
||||
/* Non-fatal: the device will work without fwext (TLS will be skipped)
|
||||
* but user should be informed. Continue to OPEN_DONE. */
|
||||
g_clear_error (&error);
|
||||
fpi_ssm_jump_to_state (self->open_ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* After successful upload and reboot, the device will re-enumerate
|
||||
* on USB. We cannot continue the open sequence — report an error
|
||||
* that tells fprintd to retry. */
|
||||
fp_info ("Firmware extension uploaded successfully — device rebooting");
|
||||
fpi_ssm_mark_failed (self->open_ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_REMOVED,
|
||||
"Device rebooting after firmware upload"));
|
||||
}
|
||||
|
||||
/* Callback for optional TLS handshake child SSM */
|
||||
static void
|
||||
tls_handshake_ssm_done (FpiSsm *ssm,
|
||||
|
|
@ -334,6 +364,33 @@ open_run_state (FpiSsm *ssm,
|
|||
fw_info_recv_cb, NULL);
|
||||
break;
|
||||
|
||||
case OPEN_UPLOAD_FWEXT:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
/* If fwext is already loaded, skip upload */
|
||||
if (self->fwext_loaded)
|
||||
{
|
||||
fpi_ssm_next_state (ssm);
|
||||
return;
|
||||
}
|
||||
|
||||
/* In emulation mode, skip upload — no real device */
|
||||
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
|
||||
{
|
||||
fp_dbg ("Emulation mode — skipping fwext upload");
|
||||
fpi_ssm_next_state (ssm);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("Firmware extension not loaded — starting upload");
|
||||
|
||||
self->open_ssm = ssm;
|
||||
FpiSsm *fwext_ssm = validity_fwext_upload_ssm_new (dev);
|
||||
fpi_ssm_start (fwext_ssm, fwext_upload_ssm_done);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_TLS_READ_FLASH:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ struct _FpiDeviceValidity
|
|||
FpiSsm *open_ssm;
|
||||
|
||||
/* Pending response data stashed for higher-level SSM consumption */
|
||||
guint16 cmd_response_status;
|
||||
guint8 *cmd_response_data;
|
||||
gsize cmd_response_len;
|
||||
};
|
||||
|
|
|
|||
230
libfprint/drivers/validity/validity_blob_dbe_009a.inc
Normal file
230
libfprint/drivers/validity/validity_blob_dbe_009a.inc
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
/* db_write_enable blob for 06cb:009a (3621 bytes) */
|
||||
static const guint8 db_write_enable_009a[] = {
|
||||
0x06, 0x02, 0x00, 0x00, 0x01, 0xf4, 0x80, 0x01, 0x07, 0x48, 0x92, 0xb6, 0xc5, 0x7d, 0xeb, 0x78,
|
||||
0x89, 0xb5, 0xeb, 0xf8, 0x6b, 0xc3, 0x04, 0x0f, 0x6d, 0x91, 0xff, 0x1f, 0x68, 0x76, 0x5f, 0x04,
|
||||
0x65, 0x91, 0x18, 0x4b, 0xe0, 0x8c, 0xf3, 0x6c, 0x15, 0x4b, 0x7e, 0xc5, 0x36, 0x81, 0x39, 0xd0,
|
||||
0xf9, 0x53, 0x23, 0x82, 0x21, 0x43, 0x79, 0xaf, 0xf3, 0xff, 0xbf, 0xe4, 0x65, 0x9e, 0x2f, 0x27,
|
||||
0x4e, 0x86, 0x4b, 0xd0, 0xad, 0x66, 0x0f, 0x99, 0xe2, 0x1d, 0xa2, 0xba, 0xb6, 0x77, 0xdb, 0xfa,
|
||||
0x90, 0x7a, 0x66, 0xce, 0x11, 0x0c, 0x18, 0x0d, 0x2d, 0xdc, 0x5d, 0xfe, 0x40, 0xb8, 0xed, 0x97,
|
||||
0x5c, 0xbe, 0xdf, 0xfc, 0x11, 0x63, 0x1f, 0x12, 0xf8, 0xbd, 0x64, 0x6a, 0x0e, 0xe8, 0x2d, 0x44,
|
||||
0xd2, 0xa6, 0xc1, 0xec, 0x9c, 0xfb, 0xd4, 0x0f, 0x48, 0x5c, 0xb3, 0xd9, 0x12, 0x43, 0x76, 0xb9,
|
||||
0x7b, 0x4a, 0x33, 0x49, 0xb0, 0xa7, 0x30, 0xad, 0xda, 0x62, 0x6d, 0x8a, 0xc2, 0x8e, 0xc2, 0x0e,
|
||||
0x88, 0x6a, 0xab, 0x1b, 0x88, 0x51, 0xde, 0xee, 0x34, 0x31, 0xc4, 0xd8, 0x9c, 0x8b, 0xb3, 0xe7,
|
||||
0x87, 0xea, 0xa9, 0xc0, 0x32, 0x3d, 0xfe, 0x58, 0x3d, 0x54, 0x24, 0xd3, 0x64, 0x36, 0xe4, 0x43,
|
||||
0x50, 0x43, 0xe0, 0x4f, 0xd4, 0xea, 0x46, 0xb1, 0xfb, 0x25, 0x07, 0xca, 0x6f, 0x0e, 0xb0, 0x3b,
|
||||
0xaf, 0x27, 0xc8, 0x4b, 0x81, 0x9c, 0xbc, 0x96, 0xce, 0xc3, 0x1a, 0x78, 0x04, 0x5e, 0xb6, 0x48,
|
||||
0x33, 0x9e, 0x2a, 0xa4, 0x78, 0x9e, 0x76, 0x72, 0xd9, 0x33, 0x93, 0x60, 0x05, 0xf4, 0x72, 0x0c,
|
||||
0x8f, 0xfd, 0xc1, 0xea, 0x23, 0xa4, 0xf3, 0x0a, 0x1c, 0xdc, 0x8f, 0x6e, 0x87, 0x77, 0x5c, 0x24,
|
||||
0x1b, 0x9a, 0xb1, 0x56, 0x6f, 0x77, 0x71, 0x85, 0x7c, 0xc4, 0x70, 0x3d, 0x57, 0x1f, 0x11, 0x06,
|
||||
0xc5, 0x26, 0xf9, 0x52, 0x32, 0x92, 0x5a, 0x6a, 0x93, 0xec, 0x8e, 0x91, 0x90, 0x22, 0xfb, 0xe3,
|
||||
0x03, 0xa5, 0x15, 0xf9, 0xaa, 0xa8, 0xca, 0x21, 0x50, 0x72, 0x06, 0x93, 0x11, 0xdd, 0x3f, 0x97,
|
||||
0xd9, 0xa4, 0xf5, 0x62, 0x59, 0xba, 0xb3, 0xa1, 0xb7, 0xa8, 0x58, 0x2d, 0x6d, 0xc2, 0xf9, 0x2d,
|
||||
0x49, 0xf0, 0x23, 0xd6, 0xf2, 0x5a, 0x05, 0x83, 0x7e, 0x15, 0x36, 0xa6, 0x33, 0xe2, 0x52, 0xef,
|
||||
0x64, 0x52, 0x25, 0xf4, 0x29, 0x39, 0x55, 0x04, 0x1a, 0x0d, 0x54, 0xdc, 0xb1, 0xd1, 0xdd, 0x7e,
|
||||
0x09, 0x7b, 0x78, 0x39, 0xde, 0x5f, 0xde, 0x2a, 0x6c, 0xe9, 0x99, 0x96, 0x6d, 0x71, 0x2b, 0x4c,
|
||||
0xb2, 0xfd, 0x9d, 0x78, 0x30, 0x03, 0x1d, 0xa5, 0x5d, 0x9f, 0xaa, 0x99, 0xf8, 0x66, 0xfb, 0xb7,
|
||||
0xe5, 0x20, 0x56, 0x6e, 0xfb, 0xa4, 0x3c, 0x25, 0x09, 0x28, 0x6b, 0xf2, 0x8e, 0x1a, 0x20, 0xc6,
|
||||
0xa8, 0x36, 0xdb, 0x8a, 0x1f, 0xa4, 0xcb, 0x9b, 0x8d, 0x19, 0x37, 0x80, 0xaa, 0xb5, 0x92, 0xd4,
|
||||
0x16, 0x53, 0x83, 0x96, 0x70, 0x12, 0x90, 0x66, 0xac, 0x56, 0xf1, 0x26, 0x8e, 0x6f, 0x76, 0x13,
|
||||
0x37, 0xf7, 0x68, 0x55, 0x5e, 0x13, 0xc5, 0xd6, 0x81, 0x37, 0xc6, 0x0f, 0x83, 0xdb, 0xa8, 0xdc,
|
||||
0x38, 0x63, 0xe0, 0x0e, 0x73, 0xfd, 0x3a, 0xf2, 0x1e, 0x23, 0xa5, 0x66, 0xda, 0xa6, 0x7f, 0x3f,
|
||||
0x14, 0xdd, 0x93, 0x4e, 0x32, 0x36, 0x51, 0x16, 0x70, 0x21, 0xca, 0x6b, 0x82, 0xa6, 0x10, 0x3c,
|
||||
0xb3, 0x0b, 0xe8, 0x49, 0x44, 0x6e, 0x2f, 0x54, 0xdd, 0xe6, 0x4a, 0x05, 0x37, 0x70, 0x52, 0xb5,
|
||||
0x73, 0x32, 0xe9, 0xbf, 0x08, 0xa1, 0x8c, 0xf5, 0x2d, 0xa2, 0xa1, 0x3e, 0xbb, 0xd5, 0x5e, 0x60,
|
||||
0x33, 0x3f, 0x8b, 0xc3, 0x19, 0xe1, 0x45, 0x7f, 0x38, 0xec, 0x5d, 0x48, 0x39, 0xec, 0x0e, 0xcd,
|
||||
0x03, 0x48, 0x25, 0xbd, 0xea, 0xf6, 0x49, 0x26, 0x85, 0x8c, 0x6e, 0x8c, 0x2d, 0xf4, 0x18, 0x71,
|
||||
0x7b, 0x5f, 0x67, 0x13, 0x5a, 0xbc, 0x03, 0x88, 0x35, 0xd3, 0xe4, 0xe1, 0xaa, 0x80, 0x95, 0x46,
|
||||
0xfd, 0x0d, 0x7f, 0x01, 0x06, 0x6a, 0x71, 0x53, 0x7f, 0x96, 0xbd, 0x1e, 0xce, 0xc3, 0x68, 0x75,
|
||||
0x83, 0xe1, 0xb5, 0x11, 0xbf, 0x48, 0xc2, 0x77, 0x6f, 0x46, 0x70, 0x15, 0x8e, 0x56, 0x16, 0x4c,
|
||||
0x62, 0xda, 0x20, 0xf6, 0x71, 0x76, 0x4c, 0x78, 0x5c, 0x35, 0x2f, 0xc3, 0xcc, 0xe2, 0x2c, 0xef,
|
||||
0xa2, 0x07, 0x60, 0xac, 0xff, 0x8f, 0x45, 0xef, 0xb5, 0x4a, 0x93, 0x4f, 0x98, 0x34, 0xd5, 0x4f,
|
||||
0x97, 0x01, 0xde, 0xda, 0xcd, 0x4d, 0x38, 0x3a, 0xc0, 0x1f, 0x8c, 0xca, 0x92, 0x56, 0x2e, 0xec,
|
||||
0x77, 0x4a, 0x58, 0xda, 0x6f, 0x55, 0xda, 0x25, 0x2c, 0x49, 0x1e, 0xe2, 0xab, 0x58, 0xff, 0x76,
|
||||
0x9f, 0x89, 0xa9, 0x64, 0x9d, 0x39, 0x56, 0x68, 0x2c, 0xa7, 0xd0, 0x6b, 0xbf, 0x33, 0xf9, 0xa9,
|
||||
0x35, 0xb7, 0x81, 0xdf, 0xc2, 0x1b, 0x12, 0x3b, 0x16, 0x69, 0x44, 0x24, 0xe7, 0x2d, 0x6a, 0x3e,
|
||||
0x67, 0x81, 0xdc, 0xf1, 0x95, 0xef, 0xfd, 0x36, 0x47, 0x0a, 0x4e, 0xab, 0x0f, 0xdc, 0x74, 0xe8,
|
||||
0x71, 0x02, 0x87, 0x9e, 0xc8, 0x1f, 0xea, 0x65, 0x49, 0x92, 0x0c, 0xce, 0x45, 0x4a, 0xc7, 0x81,
|
||||
0x39, 0x97, 0xb8, 0x2d, 0x51, 0xe7, 0xb8, 0xc1, 0xee, 0x24, 0xfa, 0xd3, 0x89, 0x90, 0x44, 0x78,
|
||||
0xf8, 0x47, 0x65, 0x4e, 0xc3, 0xa6, 0x3b, 0xc5, 0x95, 0xb9, 0xa7, 0xdd, 0xe7, 0x98, 0xdb, 0x5c,
|
||||
0x0b, 0x6f, 0x24, 0x49, 0x01, 0xf2, 0x39, 0xe7, 0x67, 0x4c, 0x98, 0xee, 0xbb, 0x42, 0xb6, 0x6e,
|
||||
0x89, 0x56, 0xa7, 0x33, 0xc3, 0x79, 0x65, 0x86, 0x28, 0x0a, 0x19, 0xa1, 0xdf, 0x8a, 0x69, 0x22,
|
||||
0x4a, 0xcd, 0x25, 0x56, 0xf7, 0xec, 0x2e, 0x27, 0xca, 0xe3, 0x7c, 0x69, 0xb3, 0x32, 0xb2, 0xc0,
|
||||
0xec, 0x85, 0x99, 0x1a, 0xe4, 0x87, 0x22, 0xf9, 0x88, 0x93, 0x5f, 0x65, 0x8b, 0x9c, 0xf3, 0x2f,
|
||||
0x46, 0xdf, 0xc6, 0xd9, 0x6a, 0x5a, 0x36, 0xf1, 0x8b, 0x6b, 0xf9, 0xf6, 0x57, 0xb5, 0x9b, 0x3d,
|
||||
0xa4, 0x24, 0x14, 0xe4, 0xd5, 0x6c, 0x0a, 0x24, 0x48, 0x5a, 0xa2, 0x98, 0xd2, 0xd0, 0xd1, 0xb1,
|
||||
0x77, 0xe7, 0xd0, 0xda, 0xfe, 0x60, 0x2a, 0x4f, 0xb4, 0xf4, 0x23, 0xde, 0xf4, 0xbd, 0xb0, 0x10,
|
||||
0xfd, 0xc6, 0x26, 0xc9, 0x47, 0x58, 0x7e, 0x19, 0xe7, 0xe4, 0xb0, 0xe6, 0xf9, 0xf2, 0xda, 0x41,
|
||||
0xc2, 0x9a, 0x8f, 0x19, 0x03, 0xd0, 0xd2, 0x80, 0x33, 0x65, 0xfe, 0x0a, 0x11, 0x3a, 0xbb, 0xa1,
|
||||
0x92, 0x20, 0x14, 0x1d, 0x1a, 0xc7, 0xce, 0xc6, 0x83, 0x96, 0x20, 0x30, 0xd3, 0xf6, 0x59, 0x1f,
|
||||
0x98, 0xea, 0x3d, 0xd0, 0x91, 0x62, 0x71, 0x5e, 0x5c, 0x12, 0xf4, 0x03, 0x32, 0xb4, 0x7c, 0x53,
|
||||
0x16, 0x45, 0x32, 0x82, 0x7e, 0x55, 0x96, 0xfb, 0x2c, 0xc0, 0xaa, 0x8f, 0x31, 0x68, 0x3c, 0xc6,
|
||||
0x3e, 0xc1, 0x4c, 0x03, 0x4c, 0x6f, 0x3d, 0x2c, 0x70, 0xb8, 0xc4, 0x76, 0x11, 0xb4, 0xc5, 0xcb,
|
||||
0x53, 0x48, 0xa2, 0x55, 0x9f, 0xb1, 0x62, 0xa7, 0x80, 0xa2, 0xb4, 0x03, 0xb0, 0x12, 0x0a, 0x68,
|
||||
0x46, 0xe2, 0x7d, 0x60, 0x57, 0xa3, 0xab, 0x9e, 0x1b, 0x18, 0x91, 0x5a, 0xe2, 0x03, 0x9e, 0x81,
|
||||
0xcc, 0x6c, 0x50, 0xd2, 0xa1, 0x4d, 0x59, 0x13, 0x61, 0x7b, 0xac, 0xae, 0x78, 0xfe, 0x9b, 0x91,
|
||||
0xe9, 0xe4, 0x9d, 0x2e, 0x82, 0xde, 0xf4, 0x75, 0x65, 0xc1, 0x2f, 0xf9, 0x38, 0xb1, 0x82, 0xf8,
|
||||
0xce, 0x94, 0x1d, 0x27, 0x81, 0xb7, 0x73, 0x47, 0x95, 0x38, 0xc7, 0x6e, 0xd9, 0xf7, 0xd4, 0x46,
|
||||
0x9f, 0x6f, 0xe5, 0xba, 0x7f, 0x6e, 0x3a, 0xd9, 0x88, 0x71, 0xb2, 0x86, 0x6f, 0x0e, 0xf4, 0xf3,
|
||||
0x62, 0x77, 0xda, 0xa7, 0x6c, 0x10, 0x42, 0xc8, 0x3f, 0x77, 0xdf, 0x0f, 0xf2, 0xe2, 0x63, 0x95,
|
||||
0x40, 0xbb, 0x35, 0x5e, 0xa8, 0x42, 0x73, 0x41, 0x1c, 0x45, 0x30, 0x81, 0xbd, 0x1e, 0x10, 0x35,
|
||||
0xc4, 0x02, 0xc5, 0x31, 0x90, 0xd0, 0xbd, 0x90, 0x5e, 0x8d, 0x01, 0xfc, 0x37, 0x87, 0xc6, 0x5b,
|
||||
0x69, 0x17, 0x2c, 0xca, 0x5b, 0x23, 0x4e, 0x92, 0xe3, 0x58, 0x46, 0x3b, 0xbb, 0x8d, 0x23, 0xe3,
|
||||
0x8c, 0x74, 0xa3, 0xa8, 0xe2, 0x73, 0x55, 0x42, 0xb9, 0x96, 0xba, 0x5e, 0xc2, 0x2c, 0x50, 0x95,
|
||||
0xa7, 0x77, 0xb6, 0x77, 0x5a, 0x72, 0x8d, 0xf5, 0x9c, 0x35, 0x60, 0xc7, 0xf3, 0x6b, 0x83, 0xd5,
|
||||
0x5f, 0x81, 0x9f, 0x19, 0x65, 0x73, 0xf8, 0xfd, 0x35, 0x63, 0x79, 0xfe, 0x9a, 0x5e, 0x7c, 0xec,
|
||||
0xb3, 0x76, 0x39, 0x5e, 0x01, 0x30, 0x9e, 0x20, 0x05, 0xb2, 0x9e, 0x3b, 0x16, 0x0c, 0xb7, 0x4c,
|
||||
0x6a, 0x58, 0x56, 0x09, 0x34, 0x80, 0xdd, 0x06, 0xae, 0xa5, 0xfb, 0x3f, 0xbe, 0x23, 0xe0, 0x04,
|
||||
0xf8, 0xd7, 0xa3, 0x8f, 0xd0, 0x78, 0x66, 0xcd, 0xf2, 0x41, 0x61, 0x39, 0x1c, 0xc7, 0x56, 0xf6,
|
||||
0xff, 0x71, 0xff, 0x07, 0x2e, 0x30, 0x8b, 0x35, 0xe2, 0x59, 0x43, 0x51, 0x11, 0xbe, 0xe0, 0x9d,
|
||||
0xdf, 0x2b, 0x8d, 0xf9, 0x9d, 0x0f, 0x2c, 0x2e, 0x8e, 0xda, 0xa4, 0xec, 0xaa, 0xbc, 0x69, 0x75,
|
||||
0xa5, 0x8f, 0x23, 0xbb, 0x6b, 0xfc, 0x94, 0xeb, 0xcb, 0xbb, 0xa0, 0xd5, 0x81, 0xf1, 0x6b, 0xe9,
|
||||
0xd0, 0x43, 0xc4, 0xe4, 0x10, 0xb3, 0x21, 0xc6, 0xdf, 0x42, 0x4e, 0xca, 0xee, 0xa9, 0x4e, 0xdb,
|
||||
0xe5, 0x80, 0x1e, 0xb7, 0x86, 0x19, 0x91, 0x24, 0x22, 0x2b, 0x09, 0x1e, 0x5b, 0x33, 0xba, 0xd6,
|
||||
0x76, 0x14, 0x45, 0xa8, 0xa6, 0x60, 0x6d, 0x0e, 0x78, 0x1c, 0x07, 0xa6, 0xf9, 0x1c, 0xd5, 0xfe,
|
||||
0x18, 0x8d, 0xdb, 0x9f, 0x9e, 0x17, 0xf5, 0xe0, 0x7b, 0x0c, 0xba, 0x31, 0x9c, 0x52, 0xe5, 0xfb,
|
||||
0x03, 0xf5, 0x3d, 0xf5, 0x70, 0xf8, 0x2d, 0xdb, 0x60, 0x3d, 0x30, 0x5b, 0x72, 0xa2, 0x40, 0x6b,
|
||||
0xc7, 0xc1, 0xa3, 0x7f, 0x92, 0x04, 0x05, 0xf8, 0xf1, 0x4d, 0x3d, 0xdf, 0x5d, 0x83, 0x6b, 0xa6,
|
||||
0x8d, 0x83, 0xc1, 0xa8, 0xd7, 0xf1, 0xa4, 0x1d, 0x14, 0x8c, 0xc3, 0x4b, 0x1e, 0xf9, 0x96, 0xdd,
|
||||
0xfb, 0x43, 0xef, 0x19, 0xd2, 0xfb, 0xf0, 0xad, 0xca, 0xd3, 0x01, 0xa4, 0x73, 0x49, 0x77, 0x39,
|
||||
0xea, 0xa1, 0x0b, 0xbc, 0xe8, 0x5e, 0x15, 0xc3, 0x2f, 0x1d, 0x90, 0xc8, 0xab, 0x86, 0x05, 0xd0,
|
||||
0xae, 0x94, 0x1e, 0xb9, 0x14, 0x08, 0x65, 0x92, 0xd0, 0x87, 0xa5, 0x21, 0xfd, 0xe3, 0x3a, 0x67,
|
||||
0x6c, 0xdf, 0xb9, 0x4a, 0x42, 0x47, 0xf6, 0x0f, 0x51, 0xed, 0xd3, 0x72, 0x94, 0x51, 0x1e, 0x92,
|
||||
0xec, 0x71, 0xa9, 0xa5, 0x4b, 0xab, 0x68, 0xa0, 0xed, 0xaa, 0xbd, 0xcb, 0x2c, 0x1a, 0x3a, 0xde,
|
||||
0xa7, 0x78, 0xf4, 0x16, 0xe3, 0x92, 0x00, 0xaf, 0x4c, 0x51, 0x7d, 0xd7, 0x15, 0x2b, 0xb7, 0x24,
|
||||
0x76, 0xc5, 0xd1, 0x41, 0x3f, 0x04, 0x70, 0x46, 0x15, 0xd7, 0x95, 0x30, 0x0f, 0x3a, 0x09, 0x12,
|
||||
0x14, 0xf4, 0xe4, 0xac, 0x2e, 0xf4, 0x19, 0x69, 0xc8, 0x1f, 0x8f, 0xc0, 0x86, 0x10, 0x86, 0x49,
|
||||
0x07, 0xb2, 0xe6, 0xed, 0xfa, 0x5f, 0xdb, 0x09, 0x26, 0xb6, 0xf0, 0x64, 0xb2, 0xa1, 0xc3, 0xb8,
|
||||
0xc7, 0xb6, 0x31, 0xcc, 0x75, 0x66, 0x3c, 0xed, 0xad, 0x5e, 0x71, 0x86, 0x8a, 0xbc, 0x9b, 0xac,
|
||||
0x67, 0x8e, 0x43, 0x01, 0x44, 0x61, 0x3c, 0xb0, 0xe5, 0x19, 0x82, 0xb9, 0xe0, 0x19, 0x09, 0x90,
|
||||
0x26, 0xb0, 0x69, 0xbb, 0x7a, 0x4d, 0xc3, 0x76, 0xcd, 0xd6, 0xa3, 0xc5, 0x95, 0x66, 0x31, 0x79,
|
||||
0x76, 0x21, 0x36, 0x72, 0x75, 0x4f, 0xac, 0x87, 0xdf, 0x85, 0x95, 0x3c, 0xdc, 0x0d, 0xe2, 0x76,
|
||||
0xfb, 0x87, 0x42, 0xf4, 0x8b, 0xa2, 0x18, 0xd4, 0x20, 0x2f, 0xe6, 0xf8, 0x65, 0x83, 0x41, 0x52,
|
||||
0x97, 0x9d, 0x6d, 0xa9, 0xb4, 0x73, 0xe5, 0xd4, 0x76, 0xc0, 0xaa, 0xa6, 0x84, 0x91, 0xf5, 0x45,
|
||||
0x09, 0x1b, 0x87, 0x9c, 0x01, 0x98, 0x60, 0x78, 0xd6, 0x4f, 0xa5, 0xf4, 0x9f, 0x60, 0xe6, 0x15,
|
||||
0xcb, 0x86, 0x5f, 0x15, 0x4f, 0x48, 0xb4, 0x51, 0x73, 0xa1, 0xdc, 0x85, 0xf2, 0xeb, 0x11, 0x28,
|
||||
0x65, 0x22, 0x90, 0xbd, 0x38, 0x3c, 0xde, 0xdc, 0xd8, 0xf2, 0x80, 0x11, 0x7e, 0x60, 0xbe, 0x03,
|
||||
0x4c, 0xe2, 0x24, 0xf9, 0x26, 0x73, 0x93, 0x4e, 0xd9, 0xe0, 0x07, 0x7d, 0x5f, 0x78, 0x99, 0xf4,
|
||||
0xe0, 0xee, 0xe0, 0x97, 0x93, 0x3a, 0x35, 0xe4, 0x0f, 0x20, 0x5d, 0x84, 0xa1, 0x07, 0x33, 0xf4,
|
||||
0x92, 0xda, 0x61, 0x98, 0x02, 0xff, 0x70, 0xd9, 0xb9, 0x49, 0xca, 0x0c, 0x2b, 0xcb, 0x9b, 0xa6,
|
||||
0x8c, 0x29, 0x0f, 0x2e, 0xf9, 0xa2, 0x0a, 0x3b, 0xf4, 0x96, 0x83, 0x4c, 0x66, 0x95, 0x6a, 0x8e,
|
||||
0xc4, 0x17, 0x92, 0x66, 0x99, 0x9d, 0x9f, 0x87, 0xbd, 0xfc, 0x14, 0xae, 0xa8, 0x65, 0xf0, 0x48,
|
||||
0x7e, 0x2b, 0xe1, 0x0a, 0x64, 0xbe, 0xcb, 0xa6, 0x95, 0x47, 0xd0, 0x16, 0x58, 0x93, 0x5e, 0x63,
|
||||
0x70, 0x39, 0x86, 0xa5, 0x6d, 0x6c, 0xe3, 0x8f, 0xe6, 0x6d, 0xbf, 0x61, 0xd7, 0x54, 0xba, 0x9a,
|
||||
0x1a, 0x27, 0x83, 0x53, 0x91, 0x34, 0x22, 0xe4, 0xf2, 0xe4, 0x10, 0x0c, 0x59, 0x62, 0x99, 0x9a,
|
||||
0x3e, 0xaa, 0x3e, 0x16, 0x72, 0xbc, 0x73, 0xed, 0xcf, 0xcc, 0x75, 0x25, 0xa2, 0xd3, 0xdb, 0xe9,
|
||||
0x56, 0x83, 0xb4, 0xbf, 0x38, 0xf7, 0x44, 0x4a, 0xc0, 0xf4, 0x70, 0xf0, 0xe9, 0x80, 0x79, 0x91,
|
||||
0x6e, 0x4e, 0x1f, 0xba, 0x3f, 0xcd, 0x5b, 0x08, 0x2f, 0xc2, 0x77, 0x2e, 0x63, 0xb5, 0xe0, 0x66,
|
||||
0x3f, 0x87, 0x63, 0x8a, 0x16, 0x38, 0x58, 0xf5, 0x90, 0x84, 0x52, 0x40, 0xa8, 0xc2, 0x2d, 0xac,
|
||||
0xf6, 0xf7, 0x99, 0x9c, 0x43, 0x1a, 0x2a, 0xb5, 0x20, 0x4a, 0x7d, 0xa7, 0x83, 0x9c, 0x9a, 0x93,
|
||||
0x26, 0x08, 0xc7, 0xf8, 0x3a, 0x87, 0xd1, 0xd7, 0x3d, 0x7d, 0x8b, 0x2f, 0xec, 0x65, 0xab, 0xb9,
|
||||
0x52, 0x21, 0xfa, 0xda, 0x44, 0x36, 0x5f, 0xe2, 0x10, 0x61, 0xdb, 0xcd, 0xe5, 0x2c, 0xb8, 0x4c,
|
||||
0xbf, 0xe9, 0xf0, 0x61, 0xc4, 0xda, 0xb3, 0xbe, 0x86, 0x00, 0x2e, 0x76, 0x83, 0xee, 0xd1, 0x6c,
|
||||
0x23, 0xc6, 0x87, 0xce, 0x61, 0xc5, 0xd9, 0x23, 0xff, 0xba, 0xb4, 0x0b, 0xee, 0x6a, 0xe9, 0x3e,
|
||||
0xd7, 0xf8, 0x57, 0xf3, 0x04, 0xe5, 0xeb, 0x16, 0xec, 0x6d, 0x08, 0x85, 0x63, 0x52, 0x4e, 0x90,
|
||||
0xd9, 0x16, 0xe4, 0x1a, 0x3a, 0x8c, 0x77, 0x77, 0xe2, 0x97, 0x31, 0xf0, 0xf4, 0x5c, 0x12, 0x50,
|
||||
0x82, 0xc4, 0x23, 0xa5, 0xc0, 0x27, 0x04, 0xc0, 0x7c, 0x6f, 0xc1, 0x9b, 0x1c, 0x48, 0x38, 0xee,
|
||||
0x3e, 0xab, 0xe1, 0x25, 0x62, 0x82, 0x9e, 0x67, 0x58, 0x1d, 0x31, 0x2c, 0x72, 0x0b, 0x79, 0x2a,
|
||||
0x41, 0x74, 0x4d, 0xec, 0x1e, 0x15, 0x74, 0x26, 0xab, 0x75, 0x13, 0x6d, 0x31, 0xee, 0x2f, 0x20,
|
||||
0x81, 0x47, 0x03, 0x90, 0x91, 0x45, 0x3c, 0x0b, 0x0e, 0x39, 0x70, 0xc5, 0x62, 0x4d, 0x7a, 0x53,
|
||||
0xdf, 0x80, 0x76, 0xe9, 0xd1, 0x62, 0x5d, 0x2c, 0x8e, 0x69, 0x3e, 0x0e, 0x9a, 0x81, 0xe2, 0x38,
|
||||
0x62, 0xdc, 0xa7, 0x89, 0x21, 0xb6, 0x6c, 0xa4, 0xc3, 0xc5, 0xed, 0x35, 0xb0, 0xb5, 0xed, 0x2e,
|
||||
0x24, 0x62, 0x2e, 0xb2, 0x16, 0xba, 0x0b, 0xa6, 0xe0, 0xc0, 0xea, 0xf9, 0x7c, 0x75, 0x4e, 0xeb,
|
||||
0x3d, 0xb4, 0xa5, 0x06, 0xd5, 0x85, 0x4a, 0x3e, 0xdc, 0x92, 0xd0, 0x11, 0x1a, 0xf3, 0xd2, 0x13,
|
||||
0x5a, 0x99, 0x87, 0x29, 0x12, 0x3f, 0x03, 0xd0, 0xf9, 0x36, 0x6b, 0xb0, 0xd2, 0xc6, 0x81, 0xcf,
|
||||
0xc6, 0x2c, 0x59, 0xbc, 0xd7, 0x5c, 0x6b, 0x41, 0x0d, 0x8e, 0x69, 0x97, 0xcc, 0xa5, 0x5c, 0x98,
|
||||
0x9f, 0x01, 0x03, 0x93, 0xd6, 0xc2, 0x42, 0xf7, 0xce, 0x1e, 0xa7, 0x1c, 0x6f, 0x26, 0x2e, 0x49,
|
||||
0x88, 0x55, 0x58, 0x43, 0x47, 0xb0, 0x4c, 0xe2, 0x6c, 0xce, 0x2e, 0x82, 0x2b, 0x8c, 0x6b, 0x7b,
|
||||
0x49, 0x37, 0x14, 0x8a, 0x45, 0xc9, 0x47, 0x07, 0x3b, 0x30, 0x0f, 0x7c, 0x72, 0xb6, 0xe7, 0x8c,
|
||||
0x42, 0x31, 0x07, 0x8d, 0x80, 0x53, 0x1b, 0x7f, 0x93, 0x17, 0xc1, 0xbb, 0x4d, 0x60, 0x70, 0xf2,
|
||||
0x99, 0xe9, 0xa9, 0x77, 0x31, 0xb1, 0xbe, 0xfe, 0xee, 0xc2, 0xda, 0xe0, 0xa1, 0xa0, 0x36, 0x45,
|
||||
0x68, 0xac, 0xbe, 0xba, 0xb0, 0x69, 0xa4, 0xb9, 0x01, 0x47, 0x77, 0x6f, 0xf7, 0xe7, 0xf7, 0x9c,
|
||||
0x1c, 0xc9, 0x8b, 0x2f, 0xe6, 0x21, 0x47, 0x92, 0x50, 0x15, 0x54, 0xf4, 0x19, 0x57, 0x83, 0xb0,
|
||||
0xf9, 0x18, 0x8c, 0xcf, 0xe9, 0x6a, 0xd8, 0xcd, 0x29, 0xf5, 0x46, 0x34, 0x09, 0xc2, 0x05, 0x4e,
|
||||
0x4a, 0x24, 0x96, 0xee, 0x65, 0xea, 0xa1, 0xfc, 0xda, 0x3d, 0x77, 0x64, 0xcd, 0x3e, 0x84, 0x31,
|
||||
0xe4, 0x4a, 0x2b, 0x05, 0xe6, 0x4a, 0xa2, 0xf9, 0xfb, 0x0d, 0x13, 0x45, 0x6b, 0xfe, 0xa9, 0xc9,
|
||||
0x1e, 0xc2, 0xd9, 0x0d, 0x00, 0x99, 0xe7, 0xe3, 0x95, 0xdc, 0xe8, 0x18, 0x65, 0x0d, 0xca, 0xf8,
|
||||
0xbd, 0xfe, 0x23, 0xb4, 0xc6, 0x44, 0x3f, 0x5c, 0x69, 0x0b, 0x18, 0xea, 0xd2, 0x21, 0xa6, 0xc2,
|
||||
0xbc, 0xd3, 0x45, 0x72, 0xff, 0xb8, 0x3b, 0x33, 0x32, 0xea, 0xfd, 0xe6, 0xe2, 0x5b, 0x37, 0xff,
|
||||
0x3a, 0xc6, 0xda, 0x0c, 0x3c, 0xc6, 0x97, 0xb9, 0x96, 0x26, 0x5c, 0xaa, 0x5a, 0x53, 0xce, 0x44,
|
||||
0x57, 0x03, 0x03, 0xd7, 0xd1, 0x11, 0xf4, 0x4c, 0x63, 0x51, 0x19, 0x59, 0x5c, 0x24, 0x7e, 0x86,
|
||||
0xa3, 0x20, 0x83, 0xf2, 0x86, 0x55, 0x01, 0x75, 0x2f, 0x93, 0xe3, 0x02, 0x4b, 0x2e, 0x2b, 0x6d,
|
||||
0x82, 0xd0, 0xc0, 0x3b, 0x74, 0x5b, 0xfd, 0x80, 0x9a, 0xf7, 0xe8, 0xe1, 0x34, 0x9d, 0x1a, 0x79,
|
||||
0xbe, 0xd5, 0x1b, 0xba, 0x41, 0x50, 0x64, 0x70, 0x1a, 0x2a, 0x78, 0x90, 0xe8, 0xf3, 0x99, 0x37,
|
||||
0xc6, 0xd2, 0xf5, 0x63, 0xb0, 0x74, 0x7b, 0xd9, 0x4f, 0x1b, 0x69, 0x86, 0x24, 0xb4, 0xfd, 0x17,
|
||||
0xdf, 0xdf, 0x68, 0xff, 0xdc, 0x04, 0x50, 0xc2, 0x6d, 0x77, 0x1f, 0x8f, 0xf4, 0xfb, 0x01, 0xa2,
|
||||
0x6f, 0xf8, 0xf6, 0x4e, 0xb5, 0xb6, 0xd9, 0x15, 0x3f, 0x5c, 0xe2, 0x9d, 0x9d, 0xfc, 0xf8, 0x4c,
|
||||
0xa2, 0x30, 0xa4, 0xc2, 0x12, 0x40, 0x1b, 0x43, 0x7d, 0x11, 0x37, 0xf8, 0x3a, 0x44, 0xf7, 0xa9,
|
||||
0x8a, 0x9f, 0xd1, 0xbc, 0x3d, 0x88, 0x3e, 0x62, 0x27, 0xce, 0x36, 0x9e, 0xd3, 0x2a, 0x96, 0x05,
|
||||
0x50, 0xaa, 0x86, 0x3f, 0x3d, 0x01, 0x4d, 0xe7, 0x49, 0x4d, 0xea, 0xd3, 0x4f, 0xce, 0xd1, 0xd7,
|
||||
0xb4, 0xea, 0xb6, 0x51, 0xd4, 0x99, 0x03, 0x35, 0x89, 0x44, 0x6f, 0xb5, 0xa1, 0x56, 0x45, 0x57,
|
||||
0xd6, 0x3e, 0x72, 0x49, 0x41, 0xe7, 0x7a, 0xe3, 0xf4, 0x6b, 0x79, 0x70, 0x3d, 0x06, 0x27, 0x7d,
|
||||
0x87, 0x35, 0x69, 0x99, 0xb5, 0x1f, 0x61, 0x89, 0x3d, 0x31, 0xc7, 0x23, 0x1b, 0x0c, 0x63, 0x5f,
|
||||
0x1d, 0x83, 0xab, 0x38, 0xa0, 0xdc, 0xe5, 0x44, 0xf5, 0xf6, 0x80, 0x38, 0x61, 0xd6, 0xe3, 0xd7,
|
||||
0xe7, 0x0d, 0x61, 0x7e, 0xcc, 0x59, 0x39, 0x20, 0xb1, 0xab, 0x90, 0x06, 0xbd, 0xc7, 0xbf, 0xf3,
|
||||
0x4a, 0x8b, 0x36, 0xa7, 0x60, 0x1e, 0xb1, 0x70, 0xa0, 0x40, 0x15, 0x6b, 0x45, 0x67, 0xab, 0x37,
|
||||
0xf5, 0x5f, 0xdf, 0x2d, 0x46, 0x6f, 0xca, 0x93, 0x74, 0x27, 0x73, 0x22, 0xf2, 0x18, 0x11, 0xd0,
|
||||
0x2c, 0x7b, 0xc5, 0x99, 0xc9, 0xed, 0x5c, 0x2b, 0x1f, 0xe7, 0xb6, 0xba, 0xa1, 0x9b, 0x1b, 0x0a,
|
||||
0x30, 0xf7, 0x9f, 0x86, 0x41, 0xb9, 0x7b, 0xf6, 0x64, 0x91, 0xdc, 0xa0, 0xb4, 0xc0, 0x34, 0x13,
|
||||
0x67, 0xaa, 0x5a, 0xce, 0xc1, 0x39, 0x8b, 0xb3, 0x7c, 0x03, 0x7d, 0x81, 0xac, 0x23, 0x68, 0xdb,
|
||||
0x49, 0xc5, 0xd5, 0x72, 0x0b, 0xbf, 0xb7, 0x46, 0x6b, 0xa6, 0x16, 0xc7, 0x0c, 0x7d, 0x83, 0x42,
|
||||
0x86, 0x30, 0x30, 0x47, 0x35, 0x7d, 0xa0, 0xe9, 0xa3, 0x4f, 0xc1, 0x4b, 0x00, 0xc1, 0x7a, 0x0a,
|
||||
0x02, 0xf6, 0xa6, 0x2a, 0x5b, 0x52, 0x97, 0x6b, 0x00, 0xed, 0x67, 0xbb, 0x2d, 0x0a, 0xa1, 0xb4,
|
||||
0xa8, 0xa9, 0x31, 0x00, 0xb7, 0x99, 0xe1, 0x83, 0x96, 0x95, 0xbd, 0xae, 0x9b, 0x98, 0xe7, 0x5c,
|
||||
0x8d, 0xf5, 0xd8, 0x34, 0x0d, 0x15, 0x8b, 0xe6, 0x03, 0x79, 0xa6, 0xf6, 0x26, 0xaf, 0x05, 0x2a,
|
||||
0xd5, 0x5c, 0x5c, 0xea, 0x01, 0xf8, 0x06, 0x04, 0x8e, 0x93, 0x7f, 0x87, 0xe0, 0x1e, 0x72, 0x5e,
|
||||
0x67, 0x62, 0x03, 0x64, 0xe5, 0x11, 0xaf, 0xd2, 0x88, 0xb2, 0x59, 0x53, 0xe9, 0xad, 0xe3, 0x43,
|
||||
0xb5, 0x96, 0x06, 0x86, 0x08, 0x19, 0x0f, 0xa5, 0xc4, 0xdf, 0x11, 0x4c, 0x93, 0xd3, 0xc8, 0xde,
|
||||
0xca, 0x92, 0x9c, 0x06, 0x6d, 0x8b, 0xae, 0x5a, 0xc2, 0xd6, 0x07, 0xe3, 0xf9, 0x4d, 0x68, 0xa5,
|
||||
0xd3, 0x55, 0x48, 0x27, 0xa6, 0x47, 0x35, 0xa4, 0x3c, 0x46, 0x2b, 0xc3, 0x68, 0x2c, 0xc1, 0x66,
|
||||
0x44, 0x11, 0xf5, 0x92, 0xc9, 0x45, 0x6f, 0x53, 0xda, 0x10, 0x26, 0xf5, 0x14, 0x59, 0xa0, 0xcf,
|
||||
0x20, 0xcc, 0x17, 0x1b, 0x9b, 0x6b, 0xed, 0xe4, 0x7c, 0xe5, 0x7d, 0x84, 0x5d, 0xff, 0xe1, 0x02,
|
||||
0x5c, 0x6e, 0xb2, 0x40, 0x61, 0x5d, 0xa1, 0x51, 0x10, 0x6a, 0x56, 0x01, 0xb7, 0x5c, 0x24, 0xc6,
|
||||
0x73, 0xd6, 0xea, 0x81, 0x8d, 0x60, 0xc3, 0x1f, 0x41, 0x4a, 0xea, 0xa5, 0x55, 0x97, 0xb4, 0x0c,
|
||||
0xc4, 0xf2, 0xed, 0x2b, 0x38, 0x50, 0xd3, 0x66, 0x08, 0x4a, 0x52, 0x51, 0x34, 0x20, 0xb0, 0x13,
|
||||
0x69, 0x5e, 0x2b, 0xfc, 0xb0, 0xdb, 0xfa, 0xd0, 0x01, 0x49, 0x75, 0xc6, 0x74, 0x71, 0xa3, 0x80,
|
||||
0x75, 0x28, 0xd1, 0x57, 0x30, 0x80, 0x2a, 0x44, 0x28, 0x84, 0x2c, 0x63, 0x68, 0xc7, 0x26, 0x50,
|
||||
0xb3, 0x16, 0x12, 0x65, 0xd6, 0xb8, 0x60, 0x07, 0x26, 0x4c, 0xf0, 0x93, 0xa3, 0x17, 0xfe, 0xe4,
|
||||
0xee, 0x38, 0x8e, 0x77, 0x21, 0xa0, 0x24, 0x34, 0xc5, 0x14, 0x32, 0x4c, 0xbf, 0x85, 0xcb, 0x57,
|
||||
0xf7, 0x09, 0xb5, 0x3f, 0xdf, 0x69, 0x62, 0x4a, 0xdc, 0x29, 0xb8, 0x55, 0x18, 0xf1, 0xa0, 0x51,
|
||||
0xf2, 0x47, 0x3a, 0xd9, 0x38, 0x4d, 0x7a, 0xc5, 0x7c, 0x2a, 0x78, 0x0a, 0xb7, 0x25, 0x06, 0xba,
|
||||
0x92, 0x5f, 0xa3, 0x99, 0x92, 0xdd, 0x2d, 0x0b, 0x00, 0xff, 0xd8, 0xc3, 0x86, 0x45, 0xd5, 0x5c,
|
||||
0x2c, 0xa2, 0xae, 0x94, 0xcf, 0x4f, 0xfa, 0x37, 0x22, 0x84, 0xa2, 0x8a, 0x13, 0x79, 0x7e, 0x25,
|
||||
0xeb, 0x0d, 0x95, 0x0c, 0x08, 0x37, 0x16, 0x56, 0xa8, 0x89, 0xe6, 0x18, 0x9f, 0x83, 0xb9, 0xc0,
|
||||
0xc8, 0xe0, 0x69, 0x52, 0xb3, 0x4f, 0xe1, 0x3c, 0xcb, 0x5c, 0x3b, 0x2c, 0x82, 0xf2, 0xd9, 0x88,
|
||||
0xf6, 0xd9, 0xa2, 0x33, 0xf1, 0xa9, 0xe6, 0x4d, 0xe9, 0x72, 0x18, 0xbe, 0x12, 0xee, 0x7a, 0x8e,
|
||||
0x84, 0x63, 0xd8, 0x21, 0x31, 0x62, 0x4c, 0xe1, 0x67, 0xe7, 0x44, 0xa3, 0xca, 0x39, 0x15, 0xc7,
|
||||
0x8e, 0x6e, 0x76, 0x36, 0x2d, 0x06, 0x09, 0x0e, 0x2a, 0x7e, 0xd6, 0x0e, 0xa7, 0x43, 0x7c, 0x84,
|
||||
0x83, 0x8d, 0x8a, 0xa7, 0x5f, 0x09, 0x6c, 0x9a, 0x92, 0x8e, 0x40, 0x3e, 0x24, 0x28, 0x55, 0x0c,
|
||||
0x98, 0xde, 0x8c, 0x43, 0xbd, 0x25, 0xe2, 0x45, 0x20, 0xae, 0xf6, 0xee, 0x53, 0x4e, 0xe1, 0xd4,
|
||||
0x70, 0x21, 0x75, 0xe6, 0xb2, 0x5d, 0x03, 0x0b, 0x87, 0x94, 0x18, 0x45, 0xce, 0xfc, 0x1d, 0xc2,
|
||||
0x89, 0xce, 0xe3, 0x3c, 0x72, 0x13, 0x9f, 0x29, 0x83, 0x9a, 0xf8, 0x1c, 0xb6, 0xa0, 0x97, 0xd1,
|
||||
0x14, 0x31, 0x1a, 0x01, 0x73, 0x6f, 0x47, 0x9b, 0xda, 0xe3, 0x2a, 0x59, 0x39, 0x8f, 0xc4, 0xa7,
|
||||
0x49, 0x4d, 0x03, 0x4f, 0xc8, 0xdc, 0x5f, 0x2b, 0xa8, 0xaf, 0x93, 0xfc, 0x4c, 0x57, 0x6b, 0x70,
|
||||
0x39, 0x67, 0xae, 0x59, 0x37, 0x80, 0x41, 0x3b, 0x44, 0xb9, 0x8f, 0x4b, 0xab, 0xa9, 0xd3, 0xfd,
|
||||
0x7b, 0x55, 0x71, 0x5a, 0xd5, 0xe5, 0xc4, 0x1f, 0x93, 0x61, 0xa4, 0x2a, 0x75, 0x7d, 0x9a, 0x6d,
|
||||
0x72, 0x20, 0xa9, 0x46, 0x7e, 0x19, 0xf7, 0x39, 0x87, 0x70, 0x76, 0x16, 0x4c, 0x14, 0x2d, 0x40,
|
||||
0xbb, 0xae, 0x95, 0x01, 0x31, 0x2c, 0x39, 0x4d, 0xc0, 0x23, 0x3d, 0xc5, 0x86, 0x88, 0x14, 0x16,
|
||||
0x2b, 0xfc, 0x1f, 0x10, 0xbd, 0x46, 0x63, 0xb2, 0x85, 0xdd, 0x2d, 0x00, 0x5f, 0x3b, 0xc3, 0xda,
|
||||
0xd2, 0xff, 0x02, 0x3f, 0x7e, 0x81, 0xb7, 0x99, 0xb1, 0xb3, 0x23, 0xb3, 0x7e, 0x82, 0xfc, 0x99,
|
||||
0xdc, 0x81, 0x29, 0x1c, 0xf9, 0x3c, 0xc0, 0x4a, 0x0e, 0x05, 0xaa, 0x67, 0x4b, 0xcf, 0xd3, 0xbc,
|
||||
0x0d, 0x93, 0x0a, 0x10, 0xd0, 0x95, 0x7e, 0xc7, 0x71, 0x2b, 0x8c, 0xc7, 0x83, 0x75, 0xdd, 0x90,
|
||||
0x4e, 0xb5, 0xa4, 0x68, 0x29, 0x60, 0x15, 0xda, 0xb1, 0xba, 0xbb, 0x07, 0x67, 0x86, 0xf3, 0x05,
|
||||
0xc8, 0xad, 0x90, 0xca, 0x39, 0x47, 0xb1, 0x50, 0xda, 0x79, 0xcb, 0x94, 0x03, 0x7e, 0x97, 0x0e,
|
||||
0x91, 0x80, 0x43, 0x7e, 0xa3, 0x4c, 0x72, 0x77, 0x1d, 0x67, 0x30, 0x00, 0x82, 0x67, 0x41, 0xfe,
|
||||
0x75, 0x9f, 0xcd, 0xc2, 0xb0, 0x35, 0x58, 0x33, 0x1f, 0xdf, 0x5b, 0x89, 0xd6, 0xe3, 0xf2, 0x5a,
|
||||
0x05, 0x24, 0x1f, 0x32, 0xf1, 0x39, 0xe8, 0x98, 0x12, 0x6a, 0xec, 0x8b, 0x17, 0x15, 0xca, 0xc0,
|
||||
0x20, 0x88, 0x31, 0xfb, 0x12, 0x05, 0xf9, 0xef, 0xb7, 0x55, 0x38, 0x75, 0x5b, 0x2d, 0x83, 0x93,
|
||||
0x1c, 0x7a, 0xd9, 0xe2, 0x52, 0xc8, 0x8c, 0x8a, 0xf3, 0xc5, 0xdf, 0x62, 0xfb, 0x99, 0x65, 0x3a,
|
||||
0xff, 0x99, 0xe6, 0xc6, 0xc0, 0x51, 0xa9, 0xa1, 0x24, 0x13, 0x81, 0xcd, 0x5c, 0xe1, 0x30, 0x72,
|
||||
0x61, 0xf8, 0x66, 0x57, 0x5c, 0xae, 0xa0, 0xa3, 0xe8, 0x47, 0x28, 0x6e, 0xcc, 0x67, 0xd7, 0xd9,
|
||||
0xaa, 0x18, 0xf4, 0x8e, 0xf2, 0xa5, 0xe5, 0xf1, 0x83, 0x28, 0x61, 0x27, 0xf8, 0xb9, 0xaa, 0x2c,
|
||||
0xaa, 0x08, 0x69, 0xec, 0x5e, 0x47, 0x4a, 0x70, 0xe5, 0x42, 0x7d, 0xc2, 0xf0, 0x48, 0x8b, 0x13,
|
||||
0x4d, 0x20, 0x12, 0x41, 0xda, 0xe6, 0x8e, 0xd3, 0x99, 0x68, 0x69, 0x45, 0x32, 0x47, 0xbb, 0x50,
|
||||
0xd8, 0xbc, 0x3d, 0x3c, 0x90, 0x99, 0x51, 0xe5, 0xa4, 0x7b, 0x1e, 0x89, 0x96, 0x10, 0x34, 0x7e,
|
||||
0xa8, 0xd7, 0x19, 0x33, 0x46, 0xbf, 0xe7, 0x54, 0xc2, 0x89, 0xad, 0x1c, 0xa4, 0x54, 0xb9, 0xc9,
|
||||
0x2a, 0x07, 0x52, 0x7b, 0x95, 0xa1, 0xfe, 0x50, 0x8d, 0x0b, 0x7c, 0x8d, 0xa5, 0xb9, 0x04, 0x7d,
|
||||
0x27, 0x75, 0x08, 0xff, 0x61, 0x5c, 0x9d, 0xc9, 0xab, 0x11, 0x59, 0x6a, 0xa8, 0x8d, 0x0c, 0x97,
|
||||
0x34, 0xa4, 0x5d, 0x81, 0xf0, 0x39, 0x32, 0x19, 0xbe, 0xad, 0x58, 0x7d, 0x3a, 0x6f, 0x9d, 0x07,
|
||||
0xc4, 0x70, 0xf2, 0xab, 0xf8, 0xd7, 0xc6, 0x99, 0x22, 0x28, 0xbf, 0x0a, 0xb6, 0xef, 0x79, 0xe4,
|
||||
0x65, 0x99, 0xbb, 0x0a, 0x60,
|
||||
};
|
||||
693
libfprint/drivers/validity/validity_fwext.c
Normal file
693
libfprint/drivers/validity/validity_fwext.c
Normal file
|
|
@ -0,0 +1,693 @@
|
|||
/*
|
||||
* Validity/Synaptics VCSFW firmware extension upload
|
||||
*
|
||||
* 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-utils.h"
|
||||
#include "validity.h"
|
||||
#include "validity_fwext.h"
|
||||
#include "vcsfw_protocol.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---- Constants ---- */
|
||||
|
||||
#define FWEXT_CHUNK_SIZE 0x1000 /* 4 KB per write_flash chunk */
|
||||
#define FWEXT_SIGNATURE_SIZE 256 /* RSA signature length */
|
||||
#define FWEXT_HEADER_DELIMITER 0x1A /* .xpfwext header end marker */
|
||||
|
||||
#define FWEXT_HW_REG_WRITE_ADDR 0x8000205C
|
||||
#define FWEXT_HW_REG_WRITE_VALUE 7
|
||||
#define FWEXT_HW_REG_READ_ADDR 0x80002080
|
||||
|
||||
/* Firmware partition */
|
||||
#define FWEXT_PARTITION 2
|
||||
|
||||
/* Reboot command: 0x05 0x02 0x00 */
|
||||
#define VCSFW_CMD_REBOOT 0x05
|
||||
#define VCSFW_REBOOT_SUBCMD 0x02
|
||||
|
||||
/* Cleanup command (call_cleanups): 0x1a */
|
||||
#define VCSFW_CMD_CLEANUP 0x1A
|
||||
|
||||
/* ---- Firmware file search paths ---- */
|
||||
|
||||
static const gchar *firmware_search_paths[] = {
|
||||
"/usr/share/libfprint/validity",
|
||||
"/var/lib/python-validity",
|
||||
"/var/run/python-validity",
|
||||
"/usr/share/python-validity",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* ---- db_write_enable blob for 06cb:009a (3621 bytes) ----
|
||||
* Opaque encrypted blob sent before each flash write.
|
||||
* The sensor firmware decrypts this internally.
|
||||
* Extracted from python-validity blobs_9a.py.
|
||||
* TODO: Iteration 6 (HAL) will consolidate blobs for all PIDs. */
|
||||
#include "validity_blob_dbe_009a.inc"
|
||||
|
||||
/* ================================================================
|
||||
* Firmware info parsing
|
||||
* ================================================================ */
|
||||
|
||||
gboolean
|
||||
validity_fwext_parse_fw_info (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint16 status,
|
||||
ValidityFwInfo *info)
|
||||
{
|
||||
memset (info, 0, sizeof (*info));
|
||||
|
||||
/* Status 0xB004 (bytes: 0xb0 0x04) means no firmware loaded.
|
||||
* python-validity checks: rsp[0]==0xb0 and rsp[1]==4
|
||||
* Our vcsfw_cmd_send reads status as uint16 LE from the first 2 bytes,
|
||||
* so wire bytes {0xb0, 0x04} -> status = 0x04B0 in LE. */
|
||||
if (status == VCSFW_STATUS_NO_FW)
|
||||
{
|
||||
info->loaded = FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("GET_FW_INFO unexpected status: 0x%04x", status);
|
||||
info->loaded = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Response data (after 2-byte status stripped by vcsfw_cmd_send):
|
||||
* major(2) + minor(2) + modcnt(2) + buildtime(4) = 10 bytes header
|
||||
* + 12 bytes per module */
|
||||
if (data_len < 10)
|
||||
{
|
||||
fp_warn ("GET_FW_INFO response too short: %zu", data_len);
|
||||
info->loaded = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
info->loaded = TRUE;
|
||||
info->major = FP_READ_UINT16_LE (data);
|
||||
info->minor = FP_READ_UINT16_LE (data + 2);
|
||||
info->module_count = FP_READ_UINT16_LE (data + 4);
|
||||
info->buildtime = FP_READ_UINT32_LE (data + 6);
|
||||
|
||||
if (info->module_count > 32)
|
||||
info->module_count = 32;
|
||||
|
||||
for (guint16 i = 0; i < info->module_count && (10 + (i + 1) * 12) <= data_len; i++)
|
||||
{
|
||||
const guint8 *m = data + 10 + i * 12;
|
||||
info->modules[i].type = FP_READ_UINT16_LE (m);
|
||||
info->modules[i].subtype = FP_READ_UINT16_LE (m + 2);
|
||||
info->modules[i].major = FP_READ_UINT16_LE (m + 4);
|
||||
info->modules[i].minor = FP_READ_UINT16_LE (m + 6);
|
||||
info->modules[i].size = FP_READ_UINT32_LE (m + 8);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Firmware filename mapping
|
||||
* ================================================================ */
|
||||
|
||||
const gchar *
|
||||
validity_fwext_get_firmware_name (guint16 vid,
|
||||
guint16 pid)
|
||||
{
|
||||
if (vid == 0x138a && pid == 0x0090)
|
||||
return "6_07f_Lenovo.xpfwext";
|
||||
|
||||
/* All other supported PIDs use the same firmware */
|
||||
if ((vid == 0x138a && (pid == 0x0097 || pid == 0x009d)) ||
|
||||
(vid == 0x06cb && pid == 0x009a))
|
||||
return "6_07f_lenovo_mis_qm.xpfwext";
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gchar *
|
||||
validity_fwext_find_firmware (guint16 vid,
|
||||
guint16 pid,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *filename = validity_fwext_get_firmware_name (vid, pid);
|
||||
|
||||
if (filename == NULL)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED,
|
||||
"No firmware filename known for %04x:%04x", vid, pid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (const gchar **path = firmware_search_paths; *path != NULL; path++)
|
||||
{
|
||||
g_autofree gchar *full_path = g_build_filename (*path, filename, NULL);
|
||||
|
||||
if (g_file_test (full_path, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
fp_info ("Found firmware file: %s", full_path);
|
||||
return g_steal_pointer (&full_path);
|
||||
}
|
||||
}
|
||||
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND,
|
||||
"Firmware file '%s' not found. "
|
||||
"Search paths: /usr/share/libfprint/validity/, "
|
||||
"/var/lib/python-validity/, /var/run/python-validity/. "
|
||||
"Extract from Lenovo driver installer (nz3gf09w.exe).",
|
||||
filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* .xpfwext file parser
|
||||
* ================================================================ */
|
||||
|
||||
gboolean
|
||||
validity_fwext_load_file (const gchar *path,
|
||||
ValidityFwextFile *fwext,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree guint8 *contents = NULL;
|
||||
gsize length = 0;
|
||||
|
||||
memset (fwext, 0, sizeof (*fwext));
|
||||
|
||||
if (!g_file_get_contents (path, (gchar **) &contents, &length, error))
|
||||
return FALSE;
|
||||
|
||||
/* Find 0x1A delimiter that ends the header */
|
||||
const guint8 *delim = memchr (contents, FWEXT_HEADER_DELIMITER, length);
|
||||
|
||||
if (delim == NULL)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID,
|
||||
"Firmware file has no 0x1A header delimiter");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gsize header_len = (delim - contents) + 1; /* includes 0x1A itself */
|
||||
gsize data_len = length - header_len;
|
||||
|
||||
if (data_len < FWEXT_SIGNATURE_SIZE + 1)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID,
|
||||
"Firmware file too short after header (%zu bytes)", data_len);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Split: payload = data[:-256], signature = data[-256:] */
|
||||
fwext->payload_len = data_len - FWEXT_SIGNATURE_SIZE;
|
||||
fwext->payload = g_memdup2 (contents + header_len, fwext->payload_len);
|
||||
memcpy (fwext->signature,
|
||||
contents + header_len + fwext->payload_len,
|
||||
FWEXT_SIGNATURE_SIZE);
|
||||
|
||||
fp_info ("Firmware file loaded: %zu bytes payload, %d bytes signature",
|
||||
fwext->payload_len, FWEXT_SIGNATURE_SIZE);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_file_clear (ValidityFwextFile *fwext)
|
||||
{
|
||||
g_clear_pointer (&fwext->payload, g_free);
|
||||
fwext->payload_len = 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Command builders
|
||||
* ================================================================ */
|
||||
|
||||
void
|
||||
validity_fwext_build_write_hw_reg32 (guint32 addr,
|
||||
guint32 value,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
/* pack('<BLLB', 0x08, addr, val, 4) = 10 bytes */
|
||||
cmd[0] = VCSFW_CMD_WRITE_HW_REG32;
|
||||
FP_WRITE_UINT32_LE (cmd + 1, addr);
|
||||
FP_WRITE_UINT32_LE (cmd + 5, value);
|
||||
cmd[9] = 4;
|
||||
*cmd_len = 10;
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_build_read_hw_reg32 (guint32 addr,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
/* pack('<BLB', 0x07, addr, 4) = 6 bytes */
|
||||
cmd[0] = VCSFW_CMD_READ_HW_REG32;
|
||||
FP_WRITE_UINT32_LE (cmd + 1, addr);
|
||||
cmd[5] = 4;
|
||||
*cmd_len = 6;
|
||||
}
|
||||
|
||||
gboolean
|
||||
validity_fwext_parse_read_hw_reg32 (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint32 *value)
|
||||
{
|
||||
/* Response data (after 2-byte status): uint32 LE value */
|
||||
if (data_len < 4)
|
||||
return FALSE;
|
||||
|
||||
*value = FP_READ_UINT32_LE (data);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_build_write_flash (guint8 partition,
|
||||
guint32 offset,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
/* pack('<BBBHLL', 0x41, partition, 1, 0, addr, len) + data = 13 + data bytes */
|
||||
cmd[0] = VCSFW_CMD_WRITE_FLASH;
|
||||
cmd[1] = partition;
|
||||
cmd[2] = 1; /* flag */
|
||||
cmd[3] = 0; /* reserved LE low */
|
||||
cmd[4] = 0; /* reserved LE high */
|
||||
FP_WRITE_UINT32_LE (cmd + 5, offset);
|
||||
FP_WRITE_UINT32_LE (cmd + 9, (guint32) data_len);
|
||||
memcpy (cmd + 13, data, data_len);
|
||||
*cmd_len = 13 + data_len;
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_build_write_fw_sig (guint8 partition,
|
||||
const guint8 *signature,
|
||||
gsize sig_len,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
/* pack('<BBxH', 0x42, partition, len) + signature = 5 + sig bytes */
|
||||
cmd[0] = VCSFW_CMD_WRITE_FW_SIG;
|
||||
cmd[1] = partition;
|
||||
cmd[2] = 0; /* reserved byte (the 'x' in pack format) */
|
||||
FP_WRITE_UINT16_LE (cmd + 3, (guint16) sig_len);
|
||||
memcpy (cmd + 5, signature, sig_len);
|
||||
*cmd_len = 5 + sig_len;
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_build_reboot (guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
/* b'\x05\x02\x00' */
|
||||
cmd[0] = VCSFW_CMD_REBOOT;
|
||||
cmd[1] = VCSFW_REBOOT_SUBCMD;
|
||||
cmd[2] = 0x00;
|
||||
*cmd_len = 3;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* db_write_enable blob lookup
|
||||
* ================================================================ */
|
||||
|
||||
const guint8 *
|
||||
validity_fwext_get_db_write_enable (guint16 vid,
|
||||
guint16 pid,
|
||||
gsize *len)
|
||||
{
|
||||
/* Currently only 06cb:009a is supported.
|
||||
* Iteration 6 (HAL) will add blobs for all PIDs. */
|
||||
if (vid == 0x06cb && pid == 0x009a)
|
||||
{
|
||||
*len = sizeof (db_write_enable_009a);
|
||||
return db_write_enable_009a;
|
||||
}
|
||||
|
||||
*len = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Upload SSM -- firmware extension upload state machine
|
||||
*
|
||||
* This SSM is started as a standalone child from the open sequence
|
||||
* when fwext_loaded == FALSE. It uses the subsm pattern:
|
||||
* SEND states call vcsfw_cmd_send(self, ssm, ..., NULL) with a
|
||||
* NULL callback. The child SSM auto-advances the parent to the
|
||||
* RECV state on completion. RECV states read
|
||||
* self->cmd_response_status and self->cmd_response_data.
|
||||
* ================================================================ */
|
||||
|
||||
/* SSM data for the upload state machine */
|
||||
typedef struct
|
||||
{
|
||||
ValidityFwextFile fwext;
|
||||
gsize write_offset; /* current offset into payload */
|
||||
guint16 vid;
|
||||
guint16 pid;
|
||||
} FwextUploadData;
|
||||
|
||||
static void
|
||||
fwext_upload_data_free (gpointer data)
|
||||
{
|
||||
FwextUploadData *ud = data;
|
||||
|
||||
validity_fwext_file_clear (&ud->fwext);
|
||||
g_free (ud);
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_upload_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
FwextUploadData *ud = fpi_ssm_get_data (ssm);
|
||||
|
||||
switch (fpi_ssm_get_cur_state (ssm))
|
||||
{
|
||||
case FWEXT_SEND_WRITE_HW_REG:
|
||||
{
|
||||
guint8 cmd[10];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_hw_reg32 (FWEXT_HW_REG_WRITE_ADDR,
|
||||
FWEXT_HW_REG_WRITE_VALUE,
|
||||
cmd, &cmd_len);
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_WRITE_HW_REG:
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"WRITE_HW_REG failed: status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
return;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_READ_HW_REG:
|
||||
{
|
||||
guint8 cmd[6];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_read_hw_reg32 (FWEXT_HW_REG_READ_ADDR,
|
||||
cmd, &cmd_len);
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_READ_HW_REG:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"READ_HW_REG failed: status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
return;
|
||||
}
|
||||
|
||||
guint32 value;
|
||||
|
||||
if (!validity_fwext_parse_read_hw_reg32 (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
&value))
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"READ_HW_REG response too short"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (value != 2 && value != 3)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"Unexpected HW register value: %u "
|
||||
"(expected 2 or 3)", value));
|
||||
return;
|
||||
}
|
||||
|
||||
fp_dbg ("FWEXT: HW register 0x%08x = %u (OK)", FWEXT_HW_REG_READ_ADDR, value);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_LOAD_FILE:
|
||||
{
|
||||
GError *error = NULL;
|
||||
GUsbDevice *usb_dev = fpi_device_get_usb_device (dev);
|
||||
guint16 vid = g_usb_device_get_vid (usb_dev);
|
||||
guint16 pid = g_usb_device_get_pid (usb_dev);
|
||||
|
||||
ud->vid = vid;
|
||||
ud->pid = pid;
|
||||
|
||||
g_autofree gchar *fw_path = validity_fwext_find_firmware (vid, pid, &error);
|
||||
|
||||
if (fw_path == NULL)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validity_fwext_load_file (fw_path, &ud->fwext, &error))
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ud->write_offset = 0;
|
||||
fp_info ("FWEXT: Loaded firmware file, %zu bytes to write",
|
||||
ud->fwext.payload_len);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_DB_WRITE_ENABLE:
|
||||
{
|
||||
gsize dbe_len;
|
||||
const guint8 *dbe = validity_fwext_get_db_write_enable (ud->vid,
|
||||
ud->pid,
|
||||
&dbe_len);
|
||||
|
||||
if (dbe == NULL || dbe_len == 0)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED,
|
||||
"No db_write_enable blob for "
|
||||
"%04x:%04x", ud->vid, ud->pid));
|
||||
return;
|
||||
}
|
||||
|
||||
vcsfw_cmd_send (self, ssm, dbe, dbe_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_DB_WRITE_ENABLE:
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"db_write_enable failed: "
|
||||
"status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
return;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_WRITE_CHUNK:
|
||||
{
|
||||
gsize remaining = ud->fwext.payload_len - ud->write_offset;
|
||||
gsize chunk_size = MIN (remaining, FWEXT_CHUNK_SIZE);
|
||||
|
||||
/* cmd buffer: 13-byte header + payload */
|
||||
g_autofree guint8 *cmd = g_malloc (13 + chunk_size);
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_flash (FWEXT_PARTITION,
|
||||
(guint32) ud->write_offset,
|
||||
ud->fwext.payload + ud->write_offset,
|
||||
chunk_size,
|
||||
cmd, &cmd_len);
|
||||
|
||||
fp_dbg ("FWEXT: Writing chunk at offset 0x%zx (%zu/%zu bytes)",
|
||||
ud->write_offset, ud->write_offset + chunk_size,
|
||||
ud->fwext.payload_len);
|
||||
|
||||
ud->write_offset += chunk_size;
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_WRITE_CHUNK:
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"WRITE_FLASH chunk failed: "
|
||||
"status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
return;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_CLEANUP:
|
||||
{
|
||||
guint8 cmd[] = { VCSFW_CMD_CLEANUP };
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_CLEANUP:
|
||||
/* Status 0x0491 means "nothing to commit" -- not an error */
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK &&
|
||||
self->cmd_response_status != 0x0491)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"Cleanup cmd failed: "
|
||||
"status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ud->write_offset < ud->fwext.payload_len)
|
||||
{
|
||||
/* More chunks to write -- loop back to db_write_enable */
|
||||
fpi_ssm_jump_to_state (ssm, FWEXT_SEND_DB_WRITE_ENABLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* All chunks written -- proceed to signature */
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_WRITE_SIGNATURE:
|
||||
{
|
||||
guint8 cmd[5 + FWEXT_SIGNATURE_SIZE];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_fw_sig (FWEXT_PARTITION,
|
||||
ud->fwext.signature,
|
||||
FWEXT_SIGNATURE_SIZE,
|
||||
cmd, &cmd_len);
|
||||
|
||||
fp_info ("FWEXT: Writing firmware signature (%d bytes)",
|
||||
FWEXT_SIGNATURE_SIZE);
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_WRITE_SIGNATURE:
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"WRITE_FW_SIG failed: "
|
||||
"status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
return;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_VERIFY:
|
||||
{
|
||||
guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, FWEXT_PARTITION };
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_VERIFY:
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
|
||||
if (!validity_fwext_parse_fw_info (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
self->cmd_response_status,
|
||||
&info) ||
|
||||
!info.loaded)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"Firmware not detected after upload"));
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("FWEXT: Upload verified -- firmware v%d.%d, %d modules",
|
||||
info.major, info.minor, info.module_count);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_REBOOT:
|
||||
{
|
||||
guint8 cmd[3];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_reboot (cmd, &cmd_len);
|
||||
|
||||
fp_info ("FWEXT: Rebooting sensor to activate new firmware");
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_REBOOT:
|
||||
/* Sensor will disconnect and re-enumerate on USB.
|
||||
* We mark SSM completed -- the caller (open sequence)
|
||||
* handles the post-reboot re-init. */
|
||||
fp_info ("FWEXT: Reboot sent. Device will re-enumerate.");
|
||||
fpi_ssm_mark_completed (ssm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- SSM factory ---- */
|
||||
|
||||
FpiSsm *
|
||||
validity_fwext_upload_ssm_new (FpDevice *dev)
|
||||
{
|
||||
FpiSsm *ssm;
|
||||
FwextUploadData *ud;
|
||||
|
||||
ssm = fpi_ssm_new (dev, validity_fwext_upload_run_state,
|
||||
FWEXT_NUM_STATES);
|
||||
ud = g_new0 (FwextUploadData, 1);
|
||||
fpi_ssm_set_data (ssm, ud, fwext_upload_data_free);
|
||||
|
||||
return ssm;
|
||||
}
|
||||
144
libfprint/drivers/validity/validity_fwext.h
Normal file
144
libfprint/drivers/validity/validity_fwext.h
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Validity/Synaptics VCSFW firmware extension upload
|
||||
*
|
||||
* 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 <glib.h>
|
||||
|
||||
/* ---- Firmware info from GET_FW_INFO response ---- */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint16 type;
|
||||
guint16 subtype;
|
||||
guint16 major;
|
||||
guint16 minor;
|
||||
guint32 size;
|
||||
} ValidityFwModule;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gboolean loaded;
|
||||
guint16 major;
|
||||
guint16 minor;
|
||||
guint32 buildtime;
|
||||
guint16 module_count;
|
||||
ValidityFwModule modules[32];
|
||||
} ValidityFwInfo;
|
||||
|
||||
/* ---- Parsed firmware extension file ---- */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint8 *payload; /* firmware data (without header, without signature) */
|
||||
gsize payload_len;
|
||||
guint8 signature[256]; /* 256-byte RSA signature */
|
||||
} ValidityFwextFile;
|
||||
|
||||
/* ---- Firmware upload SSM states ----
|
||||
*
|
||||
* Each command has a SEND and RECV pair:
|
||||
* - SEND: calls vcsfw_cmd_send(self, ssm, cmd, len, NULL)
|
||||
* which starts a child SSM (subsm). The parent is paused.
|
||||
* - RECV: entered automatically when child completes. Checks
|
||||
* self->cmd_response_status and self->cmd_response_data.
|
||||
* Advances or loops as needed.
|
||||
*/
|
||||
typedef enum {
|
||||
FWEXT_SEND_WRITE_HW_REG = 0,
|
||||
FWEXT_RECV_WRITE_HW_REG,
|
||||
FWEXT_SEND_READ_HW_REG,
|
||||
FWEXT_RECV_READ_HW_REG,
|
||||
FWEXT_LOAD_FILE,
|
||||
FWEXT_SEND_DB_WRITE_ENABLE,
|
||||
FWEXT_RECV_DB_WRITE_ENABLE,
|
||||
FWEXT_SEND_WRITE_CHUNK,
|
||||
FWEXT_RECV_WRITE_CHUNK,
|
||||
FWEXT_SEND_CLEANUP,
|
||||
FWEXT_RECV_CLEANUP,
|
||||
FWEXT_SEND_WRITE_SIGNATURE,
|
||||
FWEXT_RECV_WRITE_SIGNATURE,
|
||||
FWEXT_SEND_VERIFY,
|
||||
FWEXT_RECV_VERIFY,
|
||||
FWEXT_SEND_REBOOT,
|
||||
FWEXT_RECV_REBOOT,
|
||||
FWEXT_NUM_STATES,
|
||||
} ValidityFwextSsmState;
|
||||
|
||||
/* ---- API ---- */
|
||||
|
||||
gboolean validity_fwext_parse_fw_info (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint16 status,
|
||||
ValidityFwInfo *info);
|
||||
|
||||
gboolean validity_fwext_load_file (const gchar *filename,
|
||||
ValidityFwextFile *fwext,
|
||||
GError **error);
|
||||
|
||||
void validity_fwext_file_clear (ValidityFwextFile *fwext);
|
||||
|
||||
const gchar *validity_fwext_get_firmware_name (guint16 vid,
|
||||
guint16 pid);
|
||||
|
||||
gchar *validity_fwext_find_firmware (guint16 vid,
|
||||
guint16 pid,
|
||||
GError **error);
|
||||
|
||||
void validity_fwext_build_write_hw_reg32 (guint32 addr,
|
||||
guint32 value,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
void validity_fwext_build_read_hw_reg32 (guint32 addr,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
gboolean validity_fwext_parse_read_hw_reg32 (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint32 *value);
|
||||
|
||||
void validity_fwext_build_write_flash (guint8 partition,
|
||||
guint32 offset,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
void validity_fwext_build_write_fw_sig (guint8 partition,
|
||||
const guint8 *signature,
|
||||
gsize sig_len,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
void validity_fwext_build_reboot (guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
const guint8 *validity_fwext_get_db_write_enable (guint16 vid,
|
||||
guint16 pid,
|
||||
gsize *len);
|
||||
|
||||
/* SSM entry point for upload state machine */
|
||||
void validity_fwext_upload_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
|
||||
/* Create the fwext upload SSM with data attached.
|
||||
* Caller starts it via fpi_ssm_start(). */
|
||||
FpiSsm *validity_fwext_upload_ssm_new (FpDevice *dev);
|
||||
|
|
@ -90,7 +90,8 @@ cmd_receive_cb (FpiUsbTransfer *transfer,
|
|||
fp_dbg ("VCSFW response: status=0x%04x, len=%" G_GSSIZE_FORMAT,
|
||||
status, transfer->actual_length - 2);
|
||||
|
||||
/* Stash raw response for direct access if needed */
|
||||
/* Stash status and data for direct access in RECV states */
|
||||
self->cmd_response_status = status;
|
||||
g_clear_pointer (&self->cmd_response_data, g_free);
|
||||
if (transfer->actual_length > 2)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -156,7 +156,8 @@ driver_sources = {
|
|||
'validity' :
|
||||
[ 'drivers/validity/validity.c',
|
||||
'drivers/validity/vcsfw_protocol.c',
|
||||
'drivers/validity/validity_tls.c' ],
|
||||
'drivers/validity/validity_tls.c',
|
||||
'drivers/validity/validity_fwext.c' ],
|
||||
}
|
||||
|
||||
helper_sources = {
|
||||
|
|
|
|||
|
|
@ -339,6 +339,20 @@ if 'validity' in supported_drivers
|
|||
env: envs,
|
||||
)
|
||||
endif
|
||||
|
||||
# Validity fwext unit tests (no OpenSSL needed)
|
||||
validity_fwext_test = executable('test-validity-fwext',
|
||||
sources: 'test-validity-fwext.c',
|
||||
dependencies: [ libfprint_private_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-fwext',
|
||||
validity_fwext_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
endif
|
||||
|
||||
# Run udev rule generator with fatal warnings
|
||||
|
|
|
|||
643
tests/test-validity-fwext.c
Normal file
643
tests/test-validity-fwext.c
Normal file
|
|
@ -0,0 +1,643 @@
|
|||
/*
|
||||
* Unit tests for validity firmware extension upload functions
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-ssm.h"
|
||||
#include "fpi-byte-utils.h"
|
||||
|
||||
#include "drivers/validity/validity_fwext.h"
|
||||
#include "drivers/validity/vcsfw_protocol.h"
|
||||
|
||||
/* ================================================================
|
||||
* T3.1: test_fw_info_parse_present
|
||||
*
|
||||
* Verify that a valid GET_FW_INFO response (status=OK) with 1 module
|
||||
* is parsed correctly into ValidityFwInfo.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_info_parse_present (void)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
|
||||
/* Build a synthetic GET_FW_INFO response:
|
||||
* major(2) + minor(2) + modcnt(2) + buildtime(4) = 10 header bytes
|
||||
* + 1 module * 12 bytes = 12
|
||||
* Total = 22 bytes (data after 2-byte status, which is stripped) */
|
||||
guint8 data[22];
|
||||
|
||||
memset (data, 0, sizeof (data));
|
||||
|
||||
/* major = 6 */
|
||||
data[0] = 6;
|
||||
data[1] = 0;
|
||||
/* minor = 7 */
|
||||
data[2] = 7;
|
||||
data[3] = 0;
|
||||
/* module_count = 1 */
|
||||
data[4] = 1;
|
||||
data[5] = 0;
|
||||
/* buildtime = 0x12345678 LE */
|
||||
data[6] = 0x78;
|
||||
data[7] = 0x56;
|
||||
data[8] = 0x34;
|
||||
data[9] = 0x12;
|
||||
/* Module 0: type=3, subtype=4, major=1, minor=2, size=0x1000 */
|
||||
data[10] = 3;
|
||||
data[11] = 0;
|
||||
data[12] = 4;
|
||||
data[13] = 0;
|
||||
data[14] = 1;
|
||||
data[15] = 0;
|
||||
data[16] = 2;
|
||||
data[17] = 0;
|
||||
data[18] = 0x00;
|
||||
data[19] = 0x10;
|
||||
data[20] = 0x00;
|
||||
data[21] = 0x00;
|
||||
|
||||
gboolean ok = validity_fwext_parse_fw_info (data, sizeof (data),
|
||||
VCSFW_STATUS_OK, &info);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_true (info.loaded);
|
||||
g_assert_cmpuint (info.major, ==, 6);
|
||||
g_assert_cmpuint (info.minor, ==, 7);
|
||||
g_assert_cmpuint (info.module_count, ==, 1);
|
||||
g_assert_cmpuint (info.buildtime, ==, 0x12345678);
|
||||
g_assert_cmpuint (info.modules[0].type, ==, 3);
|
||||
g_assert_cmpuint (info.modules[0].subtype, ==, 4);
|
||||
g_assert_cmpuint (info.modules[0].major, ==, 1);
|
||||
g_assert_cmpuint (info.modules[0].minor, ==, 2);
|
||||
g_assert_cmpuint (info.modules[0].size, ==, 0x1000);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.2: test_fw_info_parse_absent
|
||||
*
|
||||
* Verify that status VCSFW_STATUS_NO_FW sets loaded=FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_info_parse_absent (void)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
|
||||
gboolean ok = validity_fwext_parse_fw_info (NULL, 0,
|
||||
VCSFW_STATUS_NO_FW, &info);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_false (info.loaded);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.2b: test_fw_info_parse_unknown_status
|
||||
*
|
||||
* Verify that an unexpected status returns FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_info_parse_unknown_status (void)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
|
||||
g_test_expect_message ("libfprint-validity", G_LOG_LEVEL_WARNING,
|
||||
"*unexpected status*");
|
||||
|
||||
gboolean ok = validity_fwext_parse_fw_info (NULL, 0, 0x9999, &info);
|
||||
|
||||
g_test_assert_expected_messages ();
|
||||
|
||||
g_assert_false (ok);
|
||||
g_assert_false (info.loaded);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.2c: test_fw_info_parse_truncated
|
||||
*
|
||||
* Verify that status=OK but data too short returns FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_info_parse_truncated (void)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
guint8 data[5] = { 0 };
|
||||
|
||||
g_test_expect_message ("libfprint-validity", G_LOG_LEVEL_WARNING,
|
||||
"*too short*");
|
||||
|
||||
gboolean ok = validity_fwext_parse_fw_info (data, sizeof (data),
|
||||
VCSFW_STATUS_OK, &info);
|
||||
|
||||
g_test_assert_expected_messages ();
|
||||
|
||||
g_assert_false (ok);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.3: test_xpfwext_file_parse
|
||||
*
|
||||
* Create a synthetic .xpfwext file in a temp dir and verify parsing.
|
||||
* Format: header + 0x1A + payload + 256-byte signature
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_xpfwext_file_parse (void)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
ValidityFwextFile fwext;
|
||||
gchar *tmpdir;
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
tmpdir = g_dir_make_tmp ("fwext-test-XXXXXX", &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
path = g_build_filename (tmpdir, "test.xpfwext", NULL);
|
||||
|
||||
/* Build file: "HDR" + 0x1A + 8 bytes payload + 256 bytes sig */
|
||||
gsize header_len = 4; /* "HDR" + 0x1A */
|
||||
gsize payload_len = 8;
|
||||
gsize sig_len = 256;
|
||||
gsize total = header_len + payload_len + sig_len;
|
||||
|
||||
guint8 *content = g_malloc0 (total);
|
||||
|
||||
content[0] = 'H';
|
||||
content[1] = 'D';
|
||||
content[2] = 'R';
|
||||
content[3] = 0x1A;
|
||||
|
||||
/* Payload: 0x01..0x08 */
|
||||
for (gsize i = 0; i < payload_len; i++)
|
||||
content[header_len + i] = (guint8) (i + 1);
|
||||
|
||||
/* Signature: 0xAA repeated */
|
||||
memset (content + header_len + payload_len, 0xAA, sig_len);
|
||||
|
||||
g_file_set_contents (path, (gchar *) content, total, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
gboolean ok = validity_fwext_load_file (path, &fwext, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
|
||||
g_assert_cmpuint (fwext.payload_len, ==, payload_len);
|
||||
|
||||
/* Check payload content */
|
||||
for (gsize i = 0; i < payload_len; i++)
|
||||
g_assert_cmpuint (fwext.payload[i], ==, i + 1);
|
||||
|
||||
/* Check signature */
|
||||
for (gsize i = 0; i < sig_len; i++)
|
||||
g_assert_cmpuint (fwext.signature[i], ==, 0xAA);
|
||||
|
||||
validity_fwext_file_clear (&fwext);
|
||||
g_assert_null (fwext.payload);
|
||||
g_assert_cmpuint (fwext.payload_len, ==, 0);
|
||||
|
||||
/* Cleanup */
|
||||
g_unlink (path);
|
||||
g_rmdir (tmpdir);
|
||||
g_free (content);
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.3b: test_xpfwext_file_no_delimiter
|
||||
*
|
||||
* Verify that a file without 0x1A delimiter fails to parse.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_xpfwext_file_no_delimiter (void)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
ValidityFwextFile fwext;
|
||||
gchar *tmpdir;
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
tmpdir = g_dir_make_tmp ("fwext-test-XXXXXX", &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
path = g_build_filename (tmpdir, "bad.xpfwext", NULL);
|
||||
|
||||
/* All 0xFF — no 0x1A delimiter */
|
||||
guint8 content[300];
|
||||
|
||||
memset (content, 0xFF, sizeof (content));
|
||||
|
||||
g_file_set_contents (path, (gchar *) content, sizeof (content), &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
gboolean ok = validity_fwext_load_file (path, &fwext, &error);
|
||||
g_assert_false (ok);
|
||||
g_assert_nonnull (error);
|
||||
|
||||
g_unlink (path);
|
||||
g_rmdir (tmpdir);
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.3c: test_xpfwext_file_too_short
|
||||
*
|
||||
* Verify that a file with valid header but data shorter than
|
||||
* signature size fails.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_xpfwext_file_too_short (void)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
ValidityFwextFile fwext;
|
||||
gchar *tmpdir;
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
tmpdir = g_dir_make_tmp ("fwext-test-XXXXXX", &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
path = g_build_filename (tmpdir, "short.xpfwext", NULL);
|
||||
|
||||
/* "X" + 0x1A + 10 bytes (< 257 needed for sig + 1 byte payload) */
|
||||
guint8 content[12];
|
||||
|
||||
content[0] = 'X';
|
||||
content[1] = 0x1A;
|
||||
memset (content + 2, 0, 10);
|
||||
|
||||
g_file_set_contents (path, (gchar *) content, sizeof (content), &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
gboolean ok = validity_fwext_load_file (path, &fwext, &error);
|
||||
g_assert_false (ok);
|
||||
g_assert_nonnull (error);
|
||||
|
||||
g_unlink (path);
|
||||
g_rmdir (tmpdir);
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.4: test_flash_write_cmd_format
|
||||
*
|
||||
* Verify that build_write_flash produces the correct wire format:
|
||||
* [0x41, partition, 1, 0, 0, offset_LE32, len_LE32, data...]
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_write_cmd_format (void)
|
||||
{
|
||||
guint8 payload[] = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
guint8 cmd[13 + sizeof (payload)];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_flash (2, 0x1000, payload, sizeof (payload),
|
||||
cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 13 + sizeof (payload));
|
||||
g_assert_cmpuint (cmd[0], ==, 0x41); /* WRITE_FLASH command */
|
||||
g_assert_cmpuint (cmd[1], ==, 2); /* partition */
|
||||
g_assert_cmpuint (cmd[2], ==, 1); /* flag */
|
||||
g_assert_cmpuint (cmd[3], ==, 0); /* reserved low */
|
||||
g_assert_cmpuint (cmd[4], ==, 0); /* reserved high */
|
||||
|
||||
/* offset = 0x1000 LE */
|
||||
g_assert_cmpuint (cmd[5], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[6], ==, 0x10);
|
||||
g_assert_cmpuint (cmd[7], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[8], ==, 0x00);
|
||||
|
||||
/* length = 4 LE */
|
||||
g_assert_cmpuint (cmd[9], ==, 0x04);
|
||||
g_assert_cmpuint (cmd[10], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[11], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[12], ==, 0x00);
|
||||
|
||||
/* data */
|
||||
g_assert_cmpmem (cmd + 13, sizeof (payload), payload, sizeof (payload));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.5: test_fw_sig_cmd_format
|
||||
*
|
||||
* Verify that build_write_fw_sig produces the correct wire format:
|
||||
* [0x42, partition, 0, len_LE16, signature...]
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_sig_cmd_format (void)
|
||||
{
|
||||
guint8 sig[256];
|
||||
guint8 cmd[5 + 256];
|
||||
gsize cmd_len;
|
||||
|
||||
memset (sig, 0xBB, sizeof (sig));
|
||||
|
||||
validity_fwext_build_write_fw_sig (2, sig, sizeof (sig), cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 5 + 256);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x42); /* WRITE_FW_SIG command */
|
||||
g_assert_cmpuint (cmd[1], ==, 2); /* partition */
|
||||
g_assert_cmpuint (cmd[2], ==, 0); /* reserved */
|
||||
|
||||
/* sig length = 256 LE */
|
||||
g_assert_cmpuint (cmd[3], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[4], ==, 0x01);
|
||||
|
||||
g_assert_cmpmem (cmd + 5, 256, sig, 256);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.6: test_chunk_iteration
|
||||
*
|
||||
* Verify that building write_flash with increasing offsets covers
|
||||
* the entire payload. Simulate the chunk loop used by the SSM.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_chunk_iteration (void)
|
||||
{
|
||||
gsize payload_len = 0x2800; /* 10 KB = 2.5 chunks of 4 KB */
|
||||
gsize write_offset = 0;
|
||||
guint chunk_count = 0;
|
||||
const gsize CHUNK_SIZE = 0x1000;
|
||||
|
||||
while (write_offset < payload_len)
|
||||
{
|
||||
gsize remaining = payload_len - write_offset;
|
||||
gsize chunk_size = MIN (remaining, CHUNK_SIZE);
|
||||
|
||||
g_assert_cmpuint (chunk_size, >, 0);
|
||||
g_assert_cmpuint (chunk_size, <=, CHUNK_SIZE);
|
||||
|
||||
write_offset += chunk_size;
|
||||
chunk_count++;
|
||||
}
|
||||
|
||||
g_assert_cmpuint (write_offset, ==, payload_len);
|
||||
g_assert_cmpuint (chunk_count, ==, 3); /* 4096 + 4096 + 2048 */
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.7: test_hw_reg_cmd_format
|
||||
*
|
||||
* Verify WRITE_HW_REG32 and READ_HW_REG32 command formats.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hw_reg_write_cmd_format (void)
|
||||
{
|
||||
guint8 cmd[10];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_hw_reg32 (0x8000205C, 7, cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 10);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x08); /* WRITE_HW_REG32 command */
|
||||
|
||||
/* address = 0x8000205C LE */
|
||||
g_assert_cmpuint (cmd[1], ==, 0x5C);
|
||||
g_assert_cmpuint (cmd[2], ==, 0x20);
|
||||
g_assert_cmpuint (cmd[3], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[4], ==, 0x80);
|
||||
|
||||
/* value = 7 LE */
|
||||
g_assert_cmpuint (cmd[5], ==, 0x07);
|
||||
g_assert_cmpuint (cmd[6], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[7], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[8], ==, 0x00);
|
||||
|
||||
/* size = 4 */
|
||||
g_assert_cmpuint (cmd[9], ==, 4);
|
||||
}
|
||||
|
||||
static void
|
||||
test_hw_reg_read_cmd_format (void)
|
||||
{
|
||||
guint8 cmd[6];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_read_hw_reg32 (0x80002080, cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 6);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x07); /* READ_HW_REG32 command */
|
||||
|
||||
/* address = 0x80002080 LE */
|
||||
g_assert_cmpuint (cmd[1], ==, 0x80);
|
||||
g_assert_cmpuint (cmd[2], ==, 0x20);
|
||||
g_assert_cmpuint (cmd[3], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[4], ==, 0x80);
|
||||
|
||||
/* size = 4 */
|
||||
g_assert_cmpuint (cmd[5], ==, 4);
|
||||
}
|
||||
|
||||
static void
|
||||
test_hw_reg_read_parse (void)
|
||||
{
|
||||
guint32 value;
|
||||
guint8 data[] = { 0x02, 0x00, 0x00, 0x00 };
|
||||
|
||||
gboolean ok = validity_fwext_parse_read_hw_reg32 (data, sizeof (data),
|
||||
&value);
|
||||
g_assert_true (ok);
|
||||
g_assert_cmpuint (value, ==, 2);
|
||||
|
||||
/* Too short should fail */
|
||||
ok = validity_fwext_parse_read_hw_reg32 (data, 3, &value);
|
||||
g_assert_false (ok);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.8: test_firmware_filename
|
||||
*
|
||||
* Verify firmware filename mapping for known PIDs.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_firmware_filename (void)
|
||||
{
|
||||
const gchar *name;
|
||||
|
||||
name = validity_fwext_get_firmware_name (0x06cb, 0x009a);
|
||||
g_assert_cmpstr (name, ==, "6_07f_lenovo_mis_qm.xpfwext");
|
||||
|
||||
name = validity_fwext_get_firmware_name (0x138a, 0x0090);
|
||||
g_assert_cmpstr (name, ==, "6_07f_Lenovo.xpfwext");
|
||||
|
||||
name = validity_fwext_get_firmware_name (0x138a, 0x0097);
|
||||
g_assert_cmpstr (name, ==, "6_07f_lenovo_mis_qm.xpfwext");
|
||||
|
||||
name = validity_fwext_get_firmware_name (0x138a, 0x009d);
|
||||
g_assert_cmpstr (name, ==, "6_07f_lenovo_mis_qm.xpfwext");
|
||||
|
||||
/* Unknown PID should return NULL */
|
||||
name = validity_fwext_get_firmware_name (0x1234, 0x5678);
|
||||
g_assert_null (name);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.9: test_missing_firmware_file
|
||||
*
|
||||
* Verify that find_firmware returns an error when the file is not
|
||||
* found in any search path.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_missing_firmware_file (void)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
/* This should fail since firmware files aren't installed in CI */
|
||||
g_autofree gchar *path = validity_fwext_find_firmware (0x06cb, 0x009a,
|
||||
&error);
|
||||
|
||||
/* It either found a file or returned an error — both are valid.
|
||||
* In CI, it should be an error. On a real system, it might succeed. */
|
||||
if (path == NULL)
|
||||
{
|
||||
g_assert_nonnull (error);
|
||||
g_assert_true (g_error_matches (error, FP_DEVICE_ERROR,
|
||||
FP_DEVICE_ERROR_DATA_NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (g_file_test (path, G_FILE_TEST_IS_REGULAR));
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.9b: test_unsupported_pid_firmware
|
||||
*
|
||||
* Verify that find_firmware for an unknown PID returns NOT_SUPPORTED.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_unsupported_pid_firmware (void)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autofree gchar *path = validity_fwext_find_firmware (0x1234, 0x5678,
|
||||
&error);
|
||||
|
||||
g_assert_null (path);
|
||||
g_assert_nonnull (error);
|
||||
g_assert_true (g_error_matches (error, FP_DEVICE_ERROR,
|
||||
FP_DEVICE_ERROR_NOT_SUPPORTED));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.10: test_db_write_enable_blob
|
||||
*
|
||||
* Verify that db_write_enable blob is returned for supported PID
|
||||
* and NULL for unsupported PID.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_db_write_enable_blob (void)
|
||||
{
|
||||
gsize len;
|
||||
const guint8 *blob;
|
||||
|
||||
blob = validity_fwext_get_db_write_enable (0x06cb, 0x009a, &len);
|
||||
g_assert_nonnull (blob);
|
||||
g_assert_cmpuint (len, ==, 3621);
|
||||
|
||||
blob = validity_fwext_get_db_write_enable (0x1234, 0x5678, &len);
|
||||
g_assert_null (blob);
|
||||
g_assert_cmpuint (len, ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.11: test_reboot_cmd_format
|
||||
*
|
||||
* Verify reboot command: 0x05, 0x02, 0x00
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_reboot_cmd_format (void)
|
||||
{
|
||||
guint8 cmd[3];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_reboot (cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 3);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x05);
|
||||
g_assert_cmpuint (cmd[1], ==, 0x02);
|
||||
g_assert_cmpuint (cmd[2], ==, 0x00);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: fwext_file_clear on already-cleared struct
|
||||
*
|
||||
* Double-free guard.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_file_clear_idempotent (void)
|
||||
{
|
||||
ValidityFwextFile fwext = { 0 };
|
||||
|
||||
/* Clear an empty struct — should not crash */
|
||||
validity_fwext_file_clear (&fwext);
|
||||
validity_fwext_file_clear (&fwext);
|
||||
|
||||
g_assert_null (fwext.payload);
|
||||
g_assert_cmpuint (fwext.payload_len, ==, 0);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* Firmware info parsing */
|
||||
g_test_add_func ("/validity/fwext/fw-info/parse-present",
|
||||
test_fw_info_parse_present);
|
||||
g_test_add_func ("/validity/fwext/fw-info/parse-absent",
|
||||
test_fw_info_parse_absent);
|
||||
g_test_add_func ("/validity/fwext/fw-info/parse-unknown-status",
|
||||
test_fw_info_parse_unknown_status);
|
||||
g_test_add_func ("/validity/fwext/fw-info/parse-truncated",
|
||||
test_fw_info_parse_truncated);
|
||||
|
||||
/* File parsing */
|
||||
g_test_add_func ("/validity/fwext/file/parse",
|
||||
test_xpfwext_file_parse);
|
||||
g_test_add_func ("/validity/fwext/file/no-delimiter",
|
||||
test_xpfwext_file_no_delimiter);
|
||||
g_test_add_func ("/validity/fwext/file/too-short",
|
||||
test_xpfwext_file_too_short);
|
||||
g_test_add_func ("/validity/fwext/file/clear-idempotent",
|
||||
test_file_clear_idempotent);
|
||||
|
||||
/* Command format */
|
||||
g_test_add_func ("/validity/fwext/cmd/write-flash",
|
||||
test_flash_write_cmd_format);
|
||||
g_test_add_func ("/validity/fwext/cmd/write-fw-sig",
|
||||
test_fw_sig_cmd_format);
|
||||
g_test_add_func ("/validity/fwext/cmd/write-hw-reg",
|
||||
test_hw_reg_write_cmd_format);
|
||||
g_test_add_func ("/validity/fwext/cmd/read-hw-reg",
|
||||
test_hw_reg_read_cmd_format);
|
||||
g_test_add_func ("/validity/fwext/cmd/read-hw-reg-parse",
|
||||
test_hw_reg_read_parse);
|
||||
g_test_add_func ("/validity/fwext/cmd/reboot",
|
||||
test_reboot_cmd_format);
|
||||
|
||||
/* Chunk iteration */
|
||||
g_test_add_func ("/validity/fwext/chunk-iteration",
|
||||
test_chunk_iteration);
|
||||
|
||||
/* Firmware filename mapping */
|
||||
g_test_add_func ("/validity/fwext/firmware-name",
|
||||
test_firmware_filename);
|
||||
g_test_add_func ("/validity/fwext/find-firmware/missing",
|
||||
test_missing_firmware_file);
|
||||
g_test_add_func ("/validity/fwext/find-firmware/unsupported-pid",
|
||||
test_unsupported_pid_firmware);
|
||||
|
||||
/* Blob lookup */
|
||||
g_test_add_func ("/validity/fwext/db-write-enable",
|
||||
test_db_write_enable_blob);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue