mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-28 16:03:16 +00:00

This allows us to 'nest' firmware formats, and removes a ton of duplication. The aim here is to deprecate FuFirmwareImage -- it's almost always acting as a 'child' FuFirmware instance, and even copies most of the vfuncs to allow custom types. If I'm struggling to work out what should be a FuFirmware and what should be a FuFirmwareImage then a plugin author has no hope. For simple payloads we were adding bytes into an image and then the image into a firmware. This gets really messy when most plugins are treating the FuFirmware *as* the binary firmware file. The GBytes saved in the FuFirmware would be considered the payload with the aim of not using FuFirmwareImage in the single-image case.
434 lines
12 KiB
C
434 lines
12 KiB
C
/*
|
|
* Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (C) 2020 boger wang <boger@goodix.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-chunk.h"
|
|
|
|
#include "fu-goodixmoc-common.h"
|
|
#include "fu-goodixmoc-device.h"
|
|
|
|
struct _FuGoodixMocDevice {
|
|
FuUsbDevice parent_instance;
|
|
guint8 dummy_seq;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuGoodixMocDevice, fu_goodixmoc_device, FU_TYPE_USB_DEVICE)
|
|
|
|
#define GX_USB_BULK_EP_IN (3 | 0x80)
|
|
#define GX_USB_BULK_EP_OUT (1 | 0x00)
|
|
#define GX_USB_INTERFACE 0
|
|
|
|
#define GX_USB_DATAIN_TIMEOUT 2000 /* ms */
|
|
#define GX_USB_DATAOUT_TIMEOUT 200 /* ms */
|
|
#define GX_FLASH_TRANSFER_BLOCK_SIZE 1000 /* 1000 */
|
|
|
|
static gboolean
|
|
goodixmoc_device_cmd_send (FuGoodixMocDevice *self,
|
|
guint8 cmd0,
|
|
guint8 cmd1,
|
|
GxPkgType type,
|
|
GByteArray *req,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
guint32 crc_all = 0;
|
|
guint32 crc_hdr = 0;
|
|
gsize actual_len = 0;
|
|
g_autoptr(GByteArray) buf = g_byte_array_new ();
|
|
|
|
/* build header */
|
|
fu_byte_array_append_uint8 (buf, cmd0);
|
|
fu_byte_array_append_uint8 (buf, cmd1);
|
|
fu_byte_array_append_uint8 (buf, type); /* pkg_flag */
|
|
fu_byte_array_append_uint8 (buf, self->dummy_seq++); /* reserved */
|
|
fu_byte_array_append_uint16 (buf, req->len + GX_SIZE_CRC32, G_LITTLE_ENDIAN);
|
|
crc_hdr = fu_common_crc8 (buf->data, buf->len);
|
|
fu_byte_array_append_uint8 (buf, crc_hdr);
|
|
fu_byte_array_append_uint8 (buf, ~crc_hdr);
|
|
g_byte_array_append (buf, req->data, req->len);
|
|
crc_all = fu_common_crc32 (buf->data, buf->len);
|
|
fu_byte_array_append_uint32 (buf, crc_all, G_LITTLE_ENDIAN);
|
|
|
|
/* send zero length package */
|
|
if (!g_usb_device_bulk_transfer (usb_device,
|
|
GX_USB_BULK_EP_OUT,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
GX_USB_DATAOUT_TIMEOUT, NULL, error)) {
|
|
g_prefix_error (error, "failed to req: ");
|
|
return FALSE;
|
|
}
|
|
if (g_getenv ("FWUPD_GOODIXFP_VERBOSE") != NULL) {
|
|
fu_common_dump_full (G_LOG_DOMAIN, "REQST",
|
|
buf->data, buf->len, 16,
|
|
FU_DUMP_FLAGS_SHOW_ADDRESSES);
|
|
}
|
|
|
|
/* send data */
|
|
if (!g_usb_device_bulk_transfer (usb_device,
|
|
GX_USB_BULK_EP_OUT,
|
|
buf->data,
|
|
buf->len,
|
|
&actual_len,
|
|
GX_USB_DATAOUT_TIMEOUT, NULL, error)) {
|
|
g_prefix_error (error, "failed to req: ");
|
|
return FALSE;
|
|
}
|
|
if (actual_len != buf->len) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"invalid length");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
goodixmoc_device_cmd_recv (FuGoodixMocDevice *self,
|
|
GxfpCmdResp *presponse,
|
|
gboolean data_reply,
|
|
GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
guint32 crc_actual = 0;
|
|
guint32 crc_calculated = 0;
|
|
gsize actual_len = 0;
|
|
gsize offset = 0;
|
|
|
|
g_return_val_if_fail (presponse != NULL, FALSE);
|
|
|
|
/*
|
|
* package format
|
|
* | zlp | ack | zlp | data |
|
|
*/
|
|
while (1) {
|
|
guint16 header_len = 0x0;
|
|
guint8 header_cmd0 = 0x0;
|
|
g_autoptr(GByteArray) reply = g_byte_array_new ();
|
|
fu_byte_array_set_size (reply, GX_FLASH_TRANSFER_BLOCK_SIZE);
|
|
if (!g_usb_device_bulk_transfer (usb_device,
|
|
GX_USB_BULK_EP_IN,
|
|
reply->data,
|
|
reply->len,
|
|
&actual_len, /* allowed to return short read */
|
|
GX_USB_DATAIN_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to reply: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* receive zero length package */
|
|
if (actual_len == 0)
|
|
continue;
|
|
if (g_getenv ("FWUPD_GOODIXFP_VERBOSE") != NULL) {
|
|
fu_common_dump_full (G_LOG_DOMAIN, "REPLY",
|
|
reply->data, actual_len, 16,
|
|
FU_DUMP_FLAGS_SHOW_ADDRESSES);
|
|
}
|
|
|
|
/* parse package header */
|
|
if (!fu_common_read_uint8_safe (reply->data, reply->len, 0x0,
|
|
&header_cmd0, error))
|
|
return FALSE;
|
|
if (!fu_common_read_uint16_safe (reply->data, reply->len, 0x4,
|
|
&header_len, G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
offset = sizeof(GxfpPkgHeader) + header_len - GX_SIZE_CRC32;
|
|
crc_actual = fu_common_crc32 (reply->data, offset);
|
|
if (!fu_common_read_uint32_safe (reply->data,
|
|
reply->len,
|
|
offset,
|
|
&crc_calculated,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
if (crc_actual != crc_calculated) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"invalid checksum, got 0x%x, expected 0x%x",
|
|
crc_calculated, crc_actual);
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse package data */
|
|
if (!fu_common_read_uint8_safe (reply->data, reply->len,
|
|
sizeof(GxfpPkgHeader) + 0x00,
|
|
&presponse->result, error))
|
|
return FALSE;
|
|
if (header_cmd0 == GX_CMD_ACK) {
|
|
if (header_len == 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"invalid bufsz");
|
|
return FALSE;
|
|
}
|
|
if (!fu_common_read_uint8_safe (reply->data, reply->len,
|
|
sizeof(GxfpPkgHeader) + 0x01,
|
|
&presponse->ack_msg.cmd, error))
|
|
return FALSE;
|
|
} else if (header_cmd0 == GX_CMD_VERSION) {
|
|
if (!fu_memcpy_safe ((guint8 *) &presponse->version_info,
|
|
sizeof(presponse->version_info), 0x0, /* dst */
|
|
reply->data, reply->len,
|
|
sizeof(GxfpPkgHeader) + 0x01, /* src */
|
|
sizeof(GxfpVersionInfo), error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* continue after ack received */
|
|
if (header_cmd0 == GX_CMD_ACK && data_reply)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_goodixmoc_device_cmd_xfer (FuGoodixMocDevice *device,
|
|
guint8 cmd0,
|
|
guint8 cmd1,
|
|
GxPkgType type,
|
|
GByteArray *req,
|
|
GxfpCmdResp *presponse,
|
|
gboolean data_reply,
|
|
GError **error)
|
|
{
|
|
FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device);
|
|
if (!goodixmoc_device_cmd_send (self, cmd0, cmd1, type, req, error))
|
|
return FALSE;
|
|
return goodixmoc_device_cmd_recv (self, presponse, data_reply, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_goodixmoc_device_setup_version (FuGoodixMocDevice *self, GError **error)
|
|
{
|
|
GxfpCmdResp rsp = { 0 };
|
|
g_autofree gchar *version = NULL;
|
|
g_autoptr(GByteArray) req = g_byte_array_new ();
|
|
|
|
fu_byte_array_append_uint8 (req, 0); /* dummy */
|
|
if (!fu_goodixmoc_device_cmd_xfer (self, GX_CMD_VERSION, GX_CMD1_DEFAULT,
|
|
GX_PKG_TYPE_EOP, req, &rsp, TRUE, error))
|
|
return FALSE;
|
|
version = g_strndup ((const gchar *) rsp.version_info.fwversion,
|
|
sizeof(rsp.version_info.fwversion));
|
|
fu_device_set_version (FU_DEVICE (self), version);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_goodixmoc_device_update_init (FuGoodixMocDevice *self, GError **error)
|
|
{
|
|
GxfpCmdResp rsp = { 0 };
|
|
g_autoptr(GByteArray) req = g_byte_array_new ();
|
|
|
|
/* update initial */
|
|
if (!fu_goodixmoc_device_cmd_xfer (self, GX_CMD_UPGRADE, GX_CMD_UPGRADE_INIT,
|
|
GX_PKG_TYPE_EOP,
|
|
req,
|
|
&rsp,
|
|
TRUE,
|
|
error)) {
|
|
g_prefix_error (error, "failed to send initial update: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check result */
|
|
if (rsp.result != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"initial update failed [0x%x]",
|
|
rsp.result);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_goodixmoc_device_attach (FuDevice *device, GError **error)
|
|
{
|
|
FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device);
|
|
GxfpCmdResp rsp = { 0 };
|
|
g_autoptr(GByteArray) req = g_byte_array_new ();
|
|
|
|
/* reset device */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
if (!fu_goodixmoc_device_cmd_xfer (self, GX_CMD_RESET, 0x03,
|
|
GX_PKG_TYPE_EOP,
|
|
req,
|
|
&rsp,
|
|
FALSE,
|
|
error)) {
|
|
g_prefix_error (error, "failed to send reset device: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check result */
|
|
if (rsp.result != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to reset device [0x%x]",
|
|
rsp.result);
|
|
return FALSE;
|
|
}
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_goodixmoc_device_open (FuDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
|
|
/* FuUsbDevice->open */
|
|
if (!FU_DEVICE_CLASS (fu_goodixmoc_device_parent_class)->open (device, error))
|
|
return FALSE;
|
|
|
|
return g_usb_device_claim_interface (usb_device, GX_USB_INTERFACE,
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_goodixmoc_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE (device);
|
|
|
|
/* ensure version */
|
|
if (!fu_goodixmoc_device_setup_version (self, error)) {
|
|
g_prefix_error (error, "failed to get firmware version: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_goodixmoc_device_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device);
|
|
GxPkgType pkg_eop = GX_PKG_TYPE_NORMAL;
|
|
GxfpCmdResp rsp = { 0 };
|
|
gboolean wait_data_reply = FALSE;
|
|
g_autoptr(GBytes) fw = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* get default image */
|
|
fw = fu_firmware_get_bytes (firmware, error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
|
|
/* build packets */
|
|
chunks = fu_chunk_array_new_from_bytes (fw,
|
|
0x00,
|
|
0x00, /* page_sz */
|
|
GX_FLASH_TRANSFER_BLOCK_SIZE);
|
|
|
|
/* don't auto-boot firmware */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
|
if (!fu_goodixmoc_device_update_init (self, &error_local)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to initial update: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* write each block */
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index (chunks, i);
|
|
g_autoptr(GByteArray) req = g_byte_array_new ();
|
|
g_autoptr(GError) error_block = NULL;
|
|
|
|
g_byte_array_append (req,
|
|
fu_chunk_get_data (chk),
|
|
fu_chunk_get_data_sz (chk));
|
|
|
|
/* the last chunk */
|
|
if (i == chunks->len - 1) {
|
|
wait_data_reply = TRUE;
|
|
pkg_eop = GX_PKG_TYPE_EOP;
|
|
}
|
|
if (!fu_goodixmoc_device_cmd_xfer (self,
|
|
GX_CMD_UPGRADE,
|
|
GX_CMD_UPGRADE_DATA,
|
|
pkg_eop,
|
|
req,
|
|
&rsp,
|
|
wait_data_reply,
|
|
&error_block)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to write: %s",
|
|
error_block->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check update status */
|
|
if (wait_data_reply && rsp.result != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to verify firmware [0x%x]",
|
|
rsp.result);
|
|
return FALSE;
|
|
}
|
|
|
|
/* update progress */
|
|
fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len);
|
|
}
|
|
|
|
/* success! */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_goodixmoc_device_init (FuGoodixMocDevice *self)
|
|
{
|
|
fu_device_add_flag (FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_flag (FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY);
|
|
fu_device_add_flag (FU_DEVICE(self), FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION);
|
|
fu_device_set_version_format (FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN);
|
|
fu_device_set_remove_delay (FU_DEVICE(self), 5000);
|
|
fu_device_add_protocol (FU_DEVICE (self), "com.goodix.goodixmoc");
|
|
fu_device_set_name (FU_DEVICE(self), "Fingerprint Sensor");
|
|
fu_device_set_summary (FU_DEVICE(self), "Match-On-Chip Fingerprint Sensor");
|
|
fu_device_set_vendor (FU_DEVICE(self), "Goodix");
|
|
fu_device_set_install_duration (FU_DEVICE(self), 10);
|
|
fu_device_set_firmware_size_min (FU_DEVICE(self), 0x20000);
|
|
fu_device_set_firmware_size_max (FU_DEVICE(self), 0x30000);
|
|
}
|
|
|
|
static void
|
|
fu_goodixmoc_device_class_init(FuGoodixMocDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
klass_device->write_firmware = fu_goodixmoc_device_write_firmware;
|
|
klass_device->setup = fu_goodixmoc_device_setup;
|
|
klass_device->attach = fu_goodixmoc_device_attach;
|
|
klass_device->open = fu_goodixmoc_device_open;
|
|
}
|