mirror of
https://git.proxmox.com/git/fwupd
synced 2025-07-10 06:29:15 +00:00
949 lines
28 KiB
C
949 lines
28 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-wac-common.h"
|
|
#include "fu-wac-device.h"
|
|
#include "fu-wac-firmware.h"
|
|
#include "fu-wac-module-bluetooth-id6.h"
|
|
#include "fu-wac-module-bluetooth.h"
|
|
#include "fu-wac-module-touch.h"
|
|
|
|
typedef struct {
|
|
guint32 start_addr;
|
|
guint32 block_sz;
|
|
guint16 write_sz; /* bit 15 is write protection flag */
|
|
} FuWacFlashDescriptor;
|
|
|
|
typedef enum {
|
|
FU_WAC_STATUS_UNKNOWN = 0,
|
|
FU_WAC_STATUS_WRITING = 1 << 0,
|
|
FU_WAC_STATUS_ERASING = 1 << 1,
|
|
FU_WAC_STATUS_ERROR_WRITE = 1 << 2,
|
|
FU_WAC_STATUS_ERROR_ERASE = 1 << 3,
|
|
FU_WAC_STATUS_WRITE_PROTECTED = 1 << 4,
|
|
FU_WAC_STATUS_LAST
|
|
} FuWacStatus;
|
|
|
|
#define FU_WAC_DEVICE_TIMEOUT 5000 /* ms */
|
|
|
|
struct _FuWacDevice {
|
|
FuHidDevice parent_instance;
|
|
GPtrArray *flash_descriptors;
|
|
GArray *checksums;
|
|
guint32 status_word;
|
|
guint16 firmware_index;
|
|
guint16 loader_ver;
|
|
guint16 read_data_sz;
|
|
guint16 write_word_sz;
|
|
guint16 write_block_sz; /* usb transfer size */
|
|
guint16 nr_flash_blocks;
|
|
guint16 configuration;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuWacDevice, fu_wac_device, FU_TYPE_HID_DEVICE)
|
|
|
|
static GString *
|
|
fu_wac_device_status_to_string(guint32 status_word)
|
|
{
|
|
GString *str = g_string_new(NULL);
|
|
if (status_word & FU_WAC_STATUS_WRITING)
|
|
g_string_append(str, "writing,");
|
|
if (status_word & FU_WAC_STATUS_ERASING)
|
|
g_string_append(str, "erasing,");
|
|
if (status_word & FU_WAC_STATUS_ERROR_WRITE)
|
|
g_string_append(str, "error-write,");
|
|
if (status_word & FU_WAC_STATUS_ERROR_ERASE)
|
|
g_string_append(str, "error-erase,");
|
|
if (status_word & FU_WAC_STATUS_WRITE_PROTECTED)
|
|
g_string_append(str, "write-protected,");
|
|
if (str->len == 0) {
|
|
g_string_append(str, "none");
|
|
return str;
|
|
}
|
|
g_string_truncate(str, str->len - 1);
|
|
return str;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wav_device_flash_descriptor_is_wp(const FuWacFlashDescriptor *fd)
|
|
{
|
|
return fd->write_sz & 0x8000;
|
|
}
|
|
|
|
static void
|
|
fu_wac_device_flash_descriptor_to_string(FuWacFlashDescriptor *fd, guint idt, GString *str)
|
|
{
|
|
fu_string_append_kx(str, idt, "StartAddr", fd->start_addr);
|
|
fu_string_append_kx(str, idt, "BlockSize", fd->block_sz);
|
|
fu_string_append_kx(str, idt, "WriteSize", fd->write_sz & ~0x8000);
|
|
fu_string_append_kb(str, idt, "Protected", fu_wav_device_flash_descriptor_is_wp(fd));
|
|
}
|
|
|
|
static void
|
|
fu_wac_device_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuWacDevice *self = FU_WAC_DEVICE(device);
|
|
g_autoptr(GString) status_str = NULL;
|
|
|
|
if (self->firmware_index != 0xffff) {
|
|
g_autofree gchar *tmp = g_strdup_printf("0x%04x", self->firmware_index);
|
|
fu_string_append(str, idt, "FwIndex", tmp);
|
|
}
|
|
if (self->loader_ver > 0) {
|
|
g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->loader_ver);
|
|
fu_string_append(str, idt, "LoaderVer", tmp);
|
|
}
|
|
if (self->read_data_sz > 0) {
|
|
g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->read_data_sz);
|
|
fu_string_append(str, idt, "ReadDataSize", tmp);
|
|
}
|
|
if (self->write_word_sz > 0) {
|
|
g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->write_word_sz);
|
|
fu_string_append(str, idt, "WriteWordSize", tmp);
|
|
}
|
|
if (self->write_block_sz > 0) {
|
|
g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->write_block_sz);
|
|
fu_string_append(str, idt, "WriteBlockSize", tmp);
|
|
}
|
|
if (self->nr_flash_blocks > 0) {
|
|
g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->nr_flash_blocks);
|
|
fu_string_append(str, idt, "NrFlashBlocks", tmp);
|
|
}
|
|
if (self->configuration != 0xffff) {
|
|
g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->configuration);
|
|
fu_string_append(str, idt, "Configuration", tmp);
|
|
}
|
|
if (g_getenv("FWUPD_WACOM_USB_VERBOSE") != NULL) {
|
|
for (guint i = 0; i < self->flash_descriptors->len; i++) {
|
|
FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i);
|
|
g_autofree gchar *title = g_strdup_printf("FlashDescriptor%02u", i);
|
|
fu_string_append(str, idt, title, NULL);
|
|
fu_wac_device_flash_descriptor_to_string(fd, idt + 1, str);
|
|
}
|
|
}
|
|
status_str = fu_wac_device_status_to_string(self->status_word);
|
|
fu_string_append(str, idt, "Status", status_str->str);
|
|
}
|
|
|
|
gboolean
|
|
fu_wac_device_get_feature_report(FuWacDevice *self,
|
|
guint8 *buf,
|
|
gsize bufsz,
|
|
FuHidDeviceFlags flags,
|
|
GError **error)
|
|
{
|
|
guint8 cmd = buf[0];
|
|
|
|
/* hit hardware */
|
|
if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
|
|
cmd,
|
|
buf,
|
|
bufsz,
|
|
FU_WAC_DEVICE_TIMEOUT,
|
|
flags | FU_HID_DEVICE_FLAG_IS_FEATURE,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* check packet */
|
|
if (buf[0] != cmd) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"command response was %i expected %i",
|
|
buf[0],
|
|
cmd);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_wac_device_set_feature_report(FuWacDevice *self,
|
|
guint8 *buf,
|
|
gsize bufsz,
|
|
FuHidDeviceFlags flags,
|
|
GError **error)
|
|
{
|
|
/* hit hardware */
|
|
if (g_getenv("FWUPD_WAC_EMULATE") != NULL)
|
|
return TRUE;
|
|
return fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
buf[0],
|
|
buf,
|
|
bufsz,
|
|
FU_WAC_DEVICE_TIMEOUT,
|
|
flags | FU_HID_DEVICE_FLAG_IS_FEATURE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_ensure_flash_descriptors(FuWacDevice *self, GError **error)
|
|
{
|
|
gsize sz = (self->nr_flash_blocks * 10) + 1;
|
|
g_autofree guint8 *buf = NULL;
|
|
|
|
/* already done */
|
|
if (self->flash_descriptors->len > 0)
|
|
return TRUE;
|
|
|
|
/* hit hardware */
|
|
buf = g_malloc(sz);
|
|
memset(buf, 0xff, sz);
|
|
buf[0] = FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR;
|
|
if (!fu_wac_device_get_feature_report(self, buf, sz, FU_HID_DEVICE_FLAG_NONE, error))
|
|
return FALSE;
|
|
|
|
/* parse */
|
|
for (guint i = 0; i < self->nr_flash_blocks; i++) {
|
|
g_autofree FuWacFlashDescriptor *fd = g_new0(FuWacFlashDescriptor, 1);
|
|
const guint blksz = 0x0A;
|
|
if (!fu_memread_uint32_safe(buf,
|
|
sz,
|
|
(i * blksz) + 1,
|
|
&fd->start_addr,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_memread_uint32_safe(buf,
|
|
sz,
|
|
(i * blksz) + 5,
|
|
&fd->block_sz,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_memread_uint16_safe(buf,
|
|
sz,
|
|
(i * blksz) + 9,
|
|
&fd->write_sz,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
g_ptr_array_add(self->flash_descriptors, g_steal_pointer(&fd));
|
|
}
|
|
if (self->flash_descriptors->len > G_MAXUINT16) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"too many flash descriptors for hardware: 0x%x",
|
|
self->flash_descriptors->len);
|
|
return FALSE;
|
|
}
|
|
|
|
g_debug("added %u flash descriptors", self->flash_descriptors->len);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_ensure_status(FuWacDevice *self, GError **error)
|
|
{
|
|
g_autoptr(GString) str = NULL;
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_GET_STATUS, [1 ... 4] = 0xff};
|
|
|
|
/* hit hardware */
|
|
buf[0] = FU_WAC_REPORT_ID_GET_STATUS;
|
|
if (!fu_wac_device_get_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* parse */
|
|
self->status_word = fu_memread_uint32(buf + 1, G_LITTLE_ENDIAN);
|
|
str = fu_wac_device_status_to_string(self->status_word);
|
|
g_debug("status now: %s", str->str);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_ensure_checksums(FuWacDevice *self, GError **error)
|
|
{
|
|
gsize sz = (self->nr_flash_blocks * 4) + 5;
|
|
guint32 updater_version;
|
|
g_autofree guint8 *buf = g_malloc(sz);
|
|
|
|
/* hit hardware */
|
|
memset(buf, 0xff, sz);
|
|
buf[0] = FU_WAC_REPORT_ID_GET_CHECKSUMS;
|
|
if (!fu_wac_device_get_feature_report(self, buf, sz, FU_HID_DEVICE_FLAG_NONE, error))
|
|
return FALSE;
|
|
|
|
/* parse */
|
|
updater_version = fu_memread_uint32(buf + 1, G_LITTLE_ENDIAN);
|
|
g_debug("updater-version: %" G_GUINT32_FORMAT, updater_version);
|
|
|
|
/* get block checksums */
|
|
g_array_set_size(self->checksums, 0);
|
|
for (guint i = 0; i < self->nr_flash_blocks; i++) {
|
|
guint32 csum = fu_memread_uint32(buf + 5 + (i * 4), G_LITTLE_ENDIAN);
|
|
if (g_getenv("FWUPD_WACOM_USB_VERBOSE") != NULL)
|
|
g_debug("checksum block %02u: 0x%08x", i, (guint)csum);
|
|
g_array_append_val(self->checksums, csum);
|
|
}
|
|
g_debug("added %u checksums", self->flash_descriptors->len);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_ensure_firmware_index(FuWacDevice *self, GError **error)
|
|
{
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX, [1 ... 2] = 0xff};
|
|
|
|
/* hit hardware */
|
|
if (!fu_wac_device_get_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* parse */
|
|
self->firmware_index = fu_memread_uint16(buf + 1, G_LITTLE_ENDIAN);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_ensure_parameters(FuWacDevice *self, GError **error)
|
|
{
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_GET_PARAMETERS, [1 ... 12] = 0xff};
|
|
|
|
/* hit hardware */
|
|
if (!fu_wac_device_get_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* parse */
|
|
self->loader_ver = fu_memread_uint16(buf + 1, G_LITTLE_ENDIAN);
|
|
self->read_data_sz = fu_memread_uint16(buf + 3, G_LITTLE_ENDIAN);
|
|
self->write_word_sz = fu_memread_uint16(buf + 5, G_LITTLE_ENDIAN);
|
|
self->write_block_sz = fu_memread_uint16(buf + 7, G_LITTLE_ENDIAN);
|
|
self->nr_flash_blocks = fu_memread_uint16(buf + 9, G_LITTLE_ENDIAN);
|
|
self->configuration = fu_memread_uint16(buf + 11, G_LITTLE_ENDIAN);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_write_block(FuWacDevice *self, guint32 addr, GBytes *blob, GError **error)
|
|
{
|
|
const guint8 *tmp;
|
|
gsize bufsz = self->write_block_sz + 5;
|
|
gsize sz = 0;
|
|
g_autofree guint8 *buf = NULL;
|
|
|
|
/* check size */
|
|
tmp = g_bytes_get_data(blob, &sz);
|
|
if (sz > self->write_block_sz) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"packet was too large at %" G_GSIZE_FORMAT " bytes",
|
|
sz);
|
|
return FALSE;
|
|
}
|
|
|
|
/* build packet */
|
|
buf = g_malloc(bufsz);
|
|
memset(buf, 0xff, bufsz);
|
|
buf[0] = FU_WAC_REPORT_ID_WRITE_BLOCK;
|
|
fu_memwrite_uint32(buf + 1, addr, G_LITTLE_ENDIAN);
|
|
if (sz > 0) {
|
|
if (!fu_memcpy_safe(buf,
|
|
bufsz,
|
|
0x5, /* dst */
|
|
tmp,
|
|
sz,
|
|
0x0, /* src */
|
|
sz,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* hit hardware */
|
|
return fu_wac_device_set_feature_report(self, buf, bufsz, FU_HID_DEVICE_FLAG_NONE, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_erase_block(FuWacDevice *self, guint32 addr, GError **error)
|
|
{
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_ERASE_BLOCK, [1 ... 4] = 0xff};
|
|
|
|
/* build packet */
|
|
fu_memwrite_uint32(buf + 1, addr, G_LITTLE_ENDIAN);
|
|
|
|
/* hit hardware */
|
|
return fu_wac_device_set_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_update_reset(FuWacDevice *self, GError **error)
|
|
{
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_UPDATE_RESET, [1 ... 4] = 0xff};
|
|
|
|
/* hit hardware */
|
|
return fu_wac_device_set_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_set_checksum_of_block(FuWacDevice *self,
|
|
guint16 block_nr,
|
|
guint32 checksum,
|
|
GError **error)
|
|
{
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK, [1 ... 6] = 0xff};
|
|
|
|
/* build packet */
|
|
fu_memwrite_uint16(buf + 1, block_nr, G_LITTLE_ENDIAN);
|
|
fu_memwrite_uint32(buf + 3, checksum, G_LITTLE_ENDIAN);
|
|
|
|
/* hit hardware */
|
|
return fu_wac_device_set_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_calculate_checksum_of_block(FuWacDevice *self, guint16 block_nr, GError **error)
|
|
{
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK, [1 ... 2] = 0xff};
|
|
|
|
/* build packet */
|
|
fu_memwrite_uint16(buf + 1, block_nr, G_LITTLE_ENDIAN);
|
|
|
|
/* hit hardware */
|
|
return fu_wac_device_set_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_write_checksum_table(FuWacDevice *self, GError **error)
|
|
{
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE, [1 ... 4] = 0xff};
|
|
|
|
/* hit hardware */
|
|
return fu_wac_device_set_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_switch_to_flash_loader(FuWacDevice *self, GError **error)
|
|
{
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER, [1] = 0x05, [2] = 0x6a};
|
|
|
|
/* hit hardware */
|
|
return fu_wac_device_set_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuWacDevice *self = FU_WAC_DEVICE(device);
|
|
gsize blocks_done = 0;
|
|
gsize blocks_total = 0;
|
|
g_autofree guint32 *csum_local = NULL;
|
|
g_autoptr(FuFirmware) img = NULL;
|
|
g_autoptr(GHashTable) fd_blobs = NULL;
|
|
|
|
/* progress */
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2, NULL);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL);
|
|
|
|
/* get current selected device */
|
|
if (!fu_wac_device_ensure_firmware_index(self, error))
|
|
return FALSE;
|
|
|
|
/* use the correct image from the firmware */
|
|
img = fu_firmware_get_image_by_idx(firmware, self->firmware_index == 1 ? 1 : 0, error);
|
|
if (img == NULL)
|
|
return FALSE;
|
|
g_debug("using image at addr 0x%0x", (guint)fu_firmware_get_addr(img));
|
|
|
|
/* enter flash mode */
|
|
if (!fu_wac_device_switch_to_flash_loader(self, error))
|
|
return FALSE;
|
|
|
|
/* get firmware parameters (page sz and transfer sz) */
|
|
if (!fu_wac_device_ensure_parameters(self, error))
|
|
return FALSE;
|
|
|
|
/* get the current flash descriptors */
|
|
if (!fu_wac_device_ensure_flash_descriptors(self, error))
|
|
return FALSE;
|
|
|
|
/* get the updater protocol version */
|
|
if (!fu_wac_device_ensure_checksums(self, error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* clear all checksums of pages */
|
|
for (guint i = 0; i < self->flash_descriptors->len; i++) {
|
|
FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i);
|
|
if (fu_wav_device_flash_descriptor_is_wp(fd))
|
|
continue;
|
|
if (!fu_wac_device_set_checksum_of_block(self, i, 0x0, error))
|
|
return FALSE;
|
|
}
|
|
fu_progress_step_done(progress);
|
|
|
|
/* get the blobs for each chunk */
|
|
fd_blobs = g_hash_table_new_full(g_direct_hash,
|
|
g_direct_equal,
|
|
NULL,
|
|
(GDestroyNotify)g_bytes_unref);
|
|
for (guint i = 0; i < self->flash_descriptors->len; i++) {
|
|
FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i);
|
|
GBytes *blob_block;
|
|
g_autoptr(GBytes) blob_tmp = NULL;
|
|
|
|
if (fu_wav_device_flash_descriptor_is_wp(fd))
|
|
continue;
|
|
blob_tmp = fu_firmware_write_chunk(img, fd->start_addr, fd->block_sz, NULL);
|
|
if (blob_tmp == NULL)
|
|
break;
|
|
blob_block = fu_bytes_pad(blob_tmp, fd->block_sz);
|
|
g_hash_table_insert(fd_blobs, fd, blob_block);
|
|
}
|
|
|
|
/* checksum actions post-write */
|
|
blocks_total = g_hash_table_size(fd_blobs);
|
|
|
|
/* write the data into the flash page */
|
|
csum_local = g_new0(guint32, self->flash_descriptors->len);
|
|
for (guint i = 0; i < self->flash_descriptors->len; i++) {
|
|
FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i);
|
|
GBytes *blob_block;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* if page is protected */
|
|
if (fu_wav_device_flash_descriptor_is_wp(fd))
|
|
continue;
|
|
|
|
/* get data for page */
|
|
blob_block = g_hash_table_lookup(fd_blobs, fd);
|
|
if (blob_block == NULL)
|
|
break;
|
|
|
|
/* ignore empty blocks */
|
|
if (fu_bytes_is_empty(blob_block)) {
|
|
g_debug("empty block, ignoring");
|
|
fu_progress_set_percentage_full(fu_progress_get_child(progress),
|
|
blocks_done++,
|
|
blocks_total);
|
|
continue;
|
|
}
|
|
|
|
/* erase entire block */
|
|
if (!fu_wac_device_erase_block(self, i, error))
|
|
return FALSE;
|
|
|
|
/* write block in chunks */
|
|
chunks = fu_chunk_array_new_from_bytes(blob_block,
|
|
fd->start_addr,
|
|
0, /* page_sz */
|
|
self->write_block_sz);
|
|
for (guint j = 0; j < chunks->len; j++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, j);
|
|
g_autoptr(GBytes) blob_chunk = fu_chunk_get_bytes(chk);
|
|
if (!fu_wac_device_write_block(self,
|
|
fu_chunk_get_address(chk),
|
|
blob_chunk,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* calculate expected checksum and save to device RAM */
|
|
csum_local[i] = GUINT32_TO_LE(fu_sum32w_bytes(blob_block, G_LITTLE_ENDIAN));
|
|
if (g_getenv("FWUPD_WACOM_USB_VERBOSE") != NULL)
|
|
g_debug("block checksum %02u: 0x%08x", i, csum_local[i]);
|
|
if (!fu_wac_device_set_checksum_of_block(self, i, csum_local[i], error))
|
|
return FALSE;
|
|
|
|
/* update device progress */
|
|
fu_progress_set_percentage_full(fu_progress_get_child(progress),
|
|
blocks_done++,
|
|
blocks_total);
|
|
}
|
|
fu_progress_step_done(progress);
|
|
|
|
/* check at least one block was written */
|
|
if (blocks_done == 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"empty firmware image or all blocks write-protected");
|
|
return FALSE;
|
|
}
|
|
|
|
/* calculate CRC inside device */
|
|
for (guint i = 0; i < self->flash_descriptors->len; i++) {
|
|
if (!fu_wac_device_calculate_checksum_of_block(self, i, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* read all CRC of all pages and verify with local CRC */
|
|
if (!fu_wac_device_ensure_checksums(self, error))
|
|
return FALSE;
|
|
for (guint i = 0; i < self->flash_descriptors->len; i++) {
|
|
FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i);
|
|
GBytes *blob_block;
|
|
guint32 csum_rom;
|
|
|
|
/* if page is protected */
|
|
if (fu_wav_device_flash_descriptor_is_wp(fd))
|
|
continue;
|
|
|
|
/* no more written pages */
|
|
blob_block = g_hash_table_lookup(fd_blobs, fd);
|
|
if (blob_block == NULL)
|
|
continue;
|
|
if (fu_bytes_is_empty(blob_block))
|
|
continue;
|
|
|
|
/* check checksum matches */
|
|
csum_rom = g_array_index(self->checksums, guint32, i);
|
|
if (csum_rom != csum_local[i]) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed local checksum at block %u, "
|
|
"got 0x%08x expected 0x%08x",
|
|
i,
|
|
(guint)csum_rom,
|
|
(guint)csum_local[i]);
|
|
return FALSE;
|
|
}
|
|
if (g_getenv("FWUPD_WACOM_USB_VERBOSE") != NULL)
|
|
g_debug("matched checksum at block %u of 0x%08x", i, csum_rom);
|
|
}
|
|
fu_progress_step_done(progress);
|
|
|
|
/* store host CRC into flash */
|
|
if (!fu_wac_device_write_checksum_table(self, error))
|
|
return FALSE;
|
|
fu_progress_step_done(progress);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_add_modules_bluetooth(FuWacDevice *self, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
g_autofree gchar *name = NULL;
|
|
g_autofree gchar *name_id6 = NULL;
|
|
g_autofree gchar *version = NULL;
|
|
g_autoptr(FuWacModule) module = NULL;
|
|
g_autoptr(FuWacModule) module_id6 = NULL;
|
|
guint16 fw_ver;
|
|
|
|
/* it can take up to 5s to get the new version after a fw update */
|
|
for (guint i = 0; i < 5; i++) {
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH,
|
|
[1 ... 14] = 0xff};
|
|
if (!fu_wac_device_get_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to get GetFirmwareVersionBluetooth: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_memread_uint16_safe(buf, sizeof(buf), 1, &fw_ver, G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
if (fw_ver != 0)
|
|
break;
|
|
g_usleep(G_USEC_PER_SEC);
|
|
}
|
|
version = fu_version_from_uint16(fw_ver, FWUPD_VERSION_FORMAT_BCD);
|
|
|
|
/* Success! But legacy bluetooth can't tell us which module the device needs.
|
|
* Initialize both and rely on the firmware update containing the appropriate
|
|
* package.
|
|
*/
|
|
name = g_strdup_printf("%s [Legacy Bluetooth Module]", fu_device_get_name(FU_DEVICE(self)));
|
|
module = fu_wac_module_bluetooth_new(fu_device_get_context(FU_DEVICE(self)), usb_device);
|
|
fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module));
|
|
fu_device_set_name(FU_DEVICE(module), name);
|
|
fu_device_set_version(FU_DEVICE(module), version);
|
|
fu_device_set_version_raw(FU_DEVICE(module), fw_ver);
|
|
|
|
name_id6 = g_strdup_printf("%s [Legacy Bluetooth Module (ID6)]",
|
|
fu_device_get_name(FU_DEVICE(self)));
|
|
module_id6 =
|
|
fu_wac_module_bluetooth_id6_new(fu_device_get_context(FU_DEVICE(self)), usb_device);
|
|
fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module_id6));
|
|
fu_device_set_name(FU_DEVICE(module_id6), name_id6);
|
|
fu_device_set_version(FU_DEVICE(module_id6), version);
|
|
fu_device_set_version_raw(FU_DEVICE(module_id6), fw_ver);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_add_modules_legacy(FuWacDevice *self, GError **error)
|
|
{
|
|
g_autoptr(GError) error_bt = NULL;
|
|
|
|
/* optional bluetooth */
|
|
if (!fu_wac_device_add_modules_bluetooth(self, &error_bt))
|
|
g_debug("no bluetooth hardware: %s", error_bt->message);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_add_modules(FuWacDevice *self, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
g_autofree gchar *version_bootloader = NULL;
|
|
guint8 buf[] = {[0] = FU_WAC_REPORT_ID_FW_DESCRIPTOR, [1 ... 31] = 0xff};
|
|
guint16 boot_ver;
|
|
|
|
if (!fu_wac_device_get_feature_report(self,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_HID_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to get DeviceFirmwareDescriptor: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify bootloader is compatible */
|
|
if (buf[1] != 0x01) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"bootloader major version not compatible");
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify the number of submodules is possible */
|
|
if (buf[3] > (512 - 4) / 4) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"number of submodules is impossible");
|
|
return FALSE;
|
|
}
|
|
|
|
/* bootloader version */
|
|
if (!fu_memread_uint16_safe(buf, sizeof(buf), 1, &boot_ver, G_BIG_ENDIAN, error))
|
|
return FALSE;
|
|
version_bootloader = fu_version_from_uint16(boot_ver, FWUPD_VERSION_FORMAT_BCD);
|
|
fu_device_set_version_bootloader(FU_DEVICE(self), version_bootloader);
|
|
fu_device_set_version_bootloader_raw(FU_DEVICE(self), boot_ver);
|
|
|
|
/* get versions of each submodule */
|
|
for (guint8 i = 0; i < buf[3]; i++) {
|
|
guint8 fw_type = buf[(i * 4) + 4] & ~0x80;
|
|
g_autofree gchar *name = NULL;
|
|
g_autofree gchar *version = NULL;
|
|
g_autoptr(FuWacModule) module = NULL;
|
|
guint16 ver;
|
|
|
|
if (!fu_memread_uint16_safe(buf,
|
|
sizeof(buf),
|
|
(i * 4) + 5,
|
|
&ver,
|
|
G_BIG_ENDIAN,
|
|
error))
|
|
return FALSE;
|
|
version = fu_version_from_uint16(ver, FWUPD_VERSION_FORMAT_BCD);
|
|
|
|
switch (fw_type) {
|
|
case FU_WAC_MODULE_FW_TYPE_TOUCH:
|
|
module = fu_wac_module_touch_new(fu_device_get_context(FU_DEVICE(self)),
|
|
usb_device);
|
|
name = g_strdup_printf("%s [Touch Module]",
|
|
fu_device_get_name(FU_DEVICE(self)));
|
|
fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module));
|
|
fu_device_set_name(FU_DEVICE(module), name);
|
|
fu_device_set_version(FU_DEVICE(module), version);
|
|
fu_device_set_version_raw(FU_DEVICE(module), ver);
|
|
break;
|
|
case FU_WAC_MODULE_FW_TYPE_BLUETOOTH:
|
|
module = fu_wac_module_bluetooth_new(fu_device_get_context(FU_DEVICE(self)),
|
|
usb_device);
|
|
name = g_strdup_printf("%s [Bluetooth Module]",
|
|
fu_device_get_name(FU_DEVICE(self)));
|
|
fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module));
|
|
fu_device_set_name(FU_DEVICE(module), name);
|
|
fu_device_set_version(FU_DEVICE(module), version);
|
|
fu_device_set_version_raw(FU_DEVICE(module), ver);
|
|
break;
|
|
case FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID6:
|
|
module =
|
|
fu_wac_module_bluetooth_id6_new(fu_device_get_context(FU_DEVICE(self)),
|
|
usb_device);
|
|
name = g_strdup_printf("%s [Bluetooth Module (ID6)]",
|
|
fu_device_get_name(FU_DEVICE(self)));
|
|
fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module));
|
|
fu_device_set_name(FU_DEVICE(module), name);
|
|
fu_device_set_version(FU_DEVICE(module), version);
|
|
fu_device_set_version_raw(FU_DEVICE(module), ver);
|
|
break;
|
|
case FU_WAC_MODULE_FW_TYPE_MAIN:
|
|
fu_device_set_version(FU_DEVICE(self), version);
|
|
fu_device_set_version_raw(FU_DEVICE(self), ver);
|
|
break;
|
|
default:
|
|
g_warning("unknown submodule type 0x%0x", fw_type);
|
|
break;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_setup(FuDevice *device, GError **error)
|
|
{
|
|
FuWacDevice *self = FU_WAC_DEVICE(device);
|
|
|
|
/* FuUsbDevice->setup */
|
|
if (!FU_DEVICE_CLASS(fu_wac_device_parent_class)->setup(device, error))
|
|
return FALSE;
|
|
|
|
/* get current status */
|
|
if (!fu_wac_device_ensure_status(self, error))
|
|
return FALSE;
|
|
|
|
/* get version of each sub-module */
|
|
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION)) {
|
|
if (!fu_wac_device_add_modules_legacy(self, error))
|
|
return FALSE;
|
|
} else {
|
|
if (!fu_wac_device_add_modules(self, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_close(FuDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
|
|
|
|
/* reattach wacom.ko */
|
|
if (!g_usb_device_release_interface(usb_device,
|
|
0x00, /* HID */
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
error)) {
|
|
g_prefix_error(error, "failed to re-attach interface: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* The hidcore subsystem uses a generic power_supply that has a deferred
|
|
* work item that will lock the device. When removing the power_supply,
|
|
* we take the lock, then cancel the work item which needs to take the
|
|
* lock too. This needs to be fixed in the kernel, but for the moment
|
|
* this should let the kernel unstick itself. */
|
|
g_usleep(20 * 1000);
|
|
|
|
/* FuUsbDevice->close */
|
|
return FU_DEVICE_CLASS(fu_wac_device_parent_class)->close(device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_device_cleanup(FuDevice *device,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return fu_wac_device_update_reset(FU_WAC_DEVICE(device), error);
|
|
}
|
|
|
|
static void
|
|
fu_wac_device_set_progress(FuDevice *self, FuProgress *progress)
|
|
{
|
|
fu_progress_set_id(progress, G_STRLOC);
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload");
|
|
}
|
|
|
|
static void
|
|
fu_wac_device_init(FuWacDevice *self)
|
|
{
|
|
self->flash_descriptors = g_ptr_array_new_with_free_func(g_free);
|
|
self->checksums = g_array_new(FALSE, FALSE, sizeof(guint32));
|
|
self->configuration = 0xffff;
|
|
self->firmware_index = 0xffff;
|
|
fu_device_add_protocol(FU_DEVICE(self), "com.wacom.usb");
|
|
fu_device_add_icon(FU_DEVICE(self), "input-tablet");
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
|
|
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_BCD);
|
|
fu_device_set_install_duration(FU_DEVICE(self), 10);
|
|
fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
|
fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_WAC_FIRMWARE);
|
|
}
|
|
|
|
static void
|
|
fu_wac_device_finalize(GObject *object)
|
|
{
|
|
FuWacDevice *self = FU_WAC_DEVICE(object);
|
|
|
|
g_ptr_array_unref(self->flash_descriptors);
|
|
g_array_unref(self->checksums);
|
|
|
|
G_OBJECT_CLASS(fu_wac_device_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
fu_wac_device_class_init(FuWacDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
object_class->finalize = fu_wac_device_finalize;
|
|
klass_device->write_firmware = fu_wac_device_write_firmware;
|
|
klass_device->to_string = fu_wac_device_to_string;
|
|
klass_device->setup = fu_wac_device_setup;
|
|
klass_device->cleanup = fu_wac_device_cleanup;
|
|
klass_device->close = fu_wac_device_close;
|
|
klass_device->set_progress = fu_wac_device_set_progress;
|
|
}
|