fwupd/plugins/goodix-moc/fu-goodixmoc-device.c
Richard Hughes b3f9841924 Support more than one protocol for a given device
Devices may want to support more than one protocol, and for some devices
(e.g. Unifying peripherals stuck in bootloader mode) you might not even be able
to query for the correct protocol anyway.
2021-03-01 16:14:36 +00:00

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_image_default_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;
}