fwupd/plugins/goodix-moc/fu-goodixmoc-device.c
2021-08-24 11:18:40 -05:00

465 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 <fwupdplugin.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);
/* FuUsbDevice->setup */
if (!FU_DEVICE_CLASS(fu_goodixmoc_device_parent_class)->setup(device, error))
return FALSE;
/* 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;
}