mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-17 17:18:11 +02:00
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
693 lines
22 KiB
C
693 lines
22 KiB
C
/*
|
|
* 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;
|
|
}
|