fwupd/plugins/wacomhid/fu-wac-device.c
Richard Hughes 87fb9ff447 Change the quirk file structure to be more efficient
This pivots the data storage so that the group is used as the preconditon
and the key name is used as the parameter to change. This allows a more natural
data flow, where a new device needs one new group and a few few keys, rather
than multiple groups, each with one key.

This also allows us to remove the key globbing when matching the version format
which is often a source of confusion.

Whilst changing all the quirk files, change the key prefixes to be more familiar
to Windows users (e.g. Hwid -> Smbios, and FuUsbDevice -> DeviceInstanceId)
who have to use the same IDs in Windows Update.

This also allows us to pre-match the desired plugin, rather than calling the
probe() function on each plugin.
2018-06-28 13:32:30 +01:00

896 lines
26 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include "fu-wac-device.h"
#include "fu-wac-common.h"
#include "fu-wac-firmware.h"
#include "fu-wac-module-bluetooth.h"
#include "fu-wac-module-touch.h"
#include "dfu-chunked.h"
#include "dfu-common.h"
#include "dfu-firmware.h"
typedef struct __attribute__((packed)) {
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
{
FuUsbDevice 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_USB_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_to_string (FuDevice *device, GString *str)
{
GPtrArray *children;
FuWacDevice *self = FU_WAC_DEVICE (device);
g_autoptr(GString) status_str = NULL;
g_string_append (str, " FuWacDevice:\n");
if (self->firmware_index != 0xffff) {
g_string_append_printf (str, " fw-index: 0x%04x\n",
self->firmware_index);
}
if (self->loader_ver > 0) {
g_string_append_printf (str, " loader-ver: 0x%04x\n",
(guint) self->loader_ver);
}
if (self->read_data_sz > 0) {
g_string_append_printf (str, " read-data-sz: 0x%04x\n",
(guint) self->read_data_sz);
}
if (self->write_word_sz > 0) {
g_string_append_printf (str, " write-word-sz: 0x%04x\n",
(guint) self->write_word_sz);
}
if (self->write_block_sz > 0) {
g_string_append_printf (str, " write-block-sz: 0x%04x\n",
(guint) self->write_block_sz);
}
if (self->nr_flash_blocks > 0) {
g_string_append_printf (str, " nr-flash-blocks: 0x%04x\n",
(guint) self->nr_flash_blocks);
}
if (self->configuration != 0xffff) {
g_string_append_printf (str, " configuration: 0x%04x\n",
(guint) self->configuration);
}
for (guint i = 0; i < self->flash_descriptors->len; i++) {
FuWacFlashDescriptor *fd = g_ptr_array_index (self->flash_descriptors, i);
g_string_append_printf (str, " flash-descriptor-%02u:\n", i);
g_string_append_printf (str, " start-addr:\t0x%08x\n",
(guint) fd->start_addr);
g_string_append_printf (str, " block-sz:\t0x%08x\n",
(guint) fd->block_sz);
g_string_append_printf (str, " write-sz:\t0x%04x\n",
(guint) fd->write_sz & ~0x8000);
g_string_append_printf (str, " protected:\t%s\n",
fu_wav_device_flash_descriptor_is_wp (fd) ? "yes" : "no");
}
status_str = fu_wac_device_status_to_string (self->status_word);
g_string_append_printf (str, " status:\t\t%s\n", status_str->str);
/* print children also */
children = fu_device_get_children (device);
for (guint i = 0; i < children->len; i++) {
FuDevice *child = g_ptr_array_index (children, i);
g_autofree gchar *tmp = fu_device_to_string (FU_DEVICE (child));
g_string_append (str, " FuWacDeviceChild:\n");
g_string_append (str, tmp);
}
}
gboolean
fu_wac_device_get_feature_report (FuWacDevice *self,
guint8 *buf, gsize bufsz,
FuWacDeviceFeatureFlags flags,
GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
gsize sz = 0;
guint8 cmd = buf[0];
/* hit hardware */
fu_wac_buffer_dump ("GET", cmd, buf, bufsz);
if (!g_usb_device_control_transfer (usb_device,
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
G_USB_DEVICE_REQUEST_TYPE_CLASS,
G_USB_DEVICE_RECIPIENT_INTERFACE,
HID_REPORT_GET, /* bRequest */
HID_FEATURE | cmd, /* wValue */
0x0000, /* wIndex */
buf, bufsz, &sz,
FU_WAC_DEVICE_TIMEOUT,
NULL, error)) {
g_prefix_error (error, "Failed to get feature report: ");
return FALSE;
}
fu_wac_buffer_dump ("GE2", cmd, buf, sz);
/* check packet */
if (flags && FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC == 0 && sz != bufsz) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"packet get bytes %" G_GSIZE_FORMAT
" expected %" G_GSIZE_FORMAT,
sz, bufsz);
return FALSE;
}
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,
FuWacDeviceFeatureFlags flags,
GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
gsize sz = 0;
guint8 cmd = buf[0];
/* hit hardware */
fu_wac_buffer_dump ("SET", cmd, buf, bufsz);
if (g_getenv ("FWUPD_WAC_EMULATE") != NULL)
return TRUE;
if (!g_usb_device_control_transfer (usb_device,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_CLASS,
G_USB_DEVICE_RECIPIENT_INTERFACE,
HID_REPORT_SET, /* bRequest */
HID_FEATURE | cmd, /* wValue */
0x0000, /* wIndex */
buf, bufsz, &sz,
FU_WAC_DEVICE_TIMEOUT,
NULL, error)) {
g_prefix_error (error, "Failed to set feature report: ");
return FALSE;
}
/* check packet */
if (flags && FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC == 0 && sz != bufsz) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"packet sent bytes %" G_GSIZE_FORMAT
" expected %" G_GSIZE_FORMAT,
sz, bufsz);
return FALSE;
}
return TRUE;
}
static gboolean
fu_wac_device_ensure_flash_descriptors (FuWacDevice *self, GError **error)
{
gsize sz = (self->nr_flash_blocks * 10) + 1;
g_autofree guint8 *buf = g_malloc (sz);
/* already done */
if (self->flash_descriptors->len > 0)
return TRUE;
/* hit hardware */
memset (buf, 0xff, sz);
buf[0] = FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR;
if (!fu_wac_device_get_feature_report (self, buf, sz,
FU_WAC_DEVICE_FEATURE_FLAG_NONE,
error))
return FALSE;
/* parse */
for (guint i = 0; i < self->nr_flash_blocks; i++) {
FuWacFlashDescriptor *fd = g_new0 (FuWacFlashDescriptor, 1);
const guint blksz = sizeof(FuWacFlashDescriptor);
fd->start_addr = fu_common_read_uint32 (buf + (i * blksz) + 1, G_LITTLE_ENDIAN);
fd->block_sz = fu_common_read_uint32 (buf + (i * blksz) + 5, G_LITTLE_ENDIAN);
fd->write_sz = fu_common_read_uint16 (buf + (i * blksz) + 9, G_LITTLE_ENDIAN);
g_ptr_array_add (self->flash_descriptors, fd);
}
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_WAC_DEVICE_FEATURE_FLAG_NONE,
error))
return FALSE;
/* parse */
self->status_word = fu_common_read_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_WAC_DEVICE_FEATURE_FLAG_NONE,
error))
return FALSE;
/* parse */
updater_version = fu_common_read_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_common_read_uint32 (buf + 5 + (i * 4), G_LITTLE_ENDIAN);
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_WAC_DEVICE_FEATURE_FLAG_NONE,
error))
return FALSE;
/* parse */
self->firmware_index = fu_common_read_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_WAC_DEVICE_FEATURE_FLAG_NONE,
error))
return FALSE;
/* parse */
self->loader_ver = fu_common_read_uint16 (buf + 1, G_LITTLE_ENDIAN);
self->read_data_sz = fu_common_read_uint16 (buf + 3, G_LITTLE_ENDIAN);
self->write_word_sz = fu_common_read_uint16 (buf + 5, G_LITTLE_ENDIAN);
self->write_block_sz = fu_common_read_uint16 (buf + 7, G_LITTLE_ENDIAN);
self->nr_flash_blocks = fu_common_read_uint16 (buf + 9, G_LITTLE_ENDIAN);
self->configuration = fu_common_read_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 = g_malloc (bufsz);
/* 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 */
memset (buf, 0xff, bufsz);
buf[0] = FU_WAC_REPORT_ID_WRITE_BLOCK;
fu_common_write_uint32 (buf + 1, addr, G_LITTLE_ENDIAN);
if (sz > 0)
memcpy (buf + 5, tmp, sz);
/* hit hardware */
return fu_wac_device_set_feature_report (self, buf, bufsz,
FU_WAC_DEVICE_FEATURE_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_common_write_uint32 (buf + 1, addr, G_LITTLE_ENDIAN);
/* hit hardware */
return fu_wac_device_set_feature_report (self, buf, sizeof(buf),
FU_WAC_DEVICE_FEATURE_FLAG_NONE,
error);
}
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_WAC_DEVICE_FEATURE_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_common_write_uint16 (buf + 1, block_nr, G_LITTLE_ENDIAN);
fu_common_write_uint32 (buf + 3, checksum, G_LITTLE_ENDIAN);
/* hit hardware */
return fu_wac_device_set_feature_report (self, buf, sizeof(buf),
FU_WAC_DEVICE_FEATURE_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_common_write_uint16 (buf + 1, block_nr, G_LITTLE_ENDIAN);
/* hit hardware */
return fu_wac_device_set_feature_report (self, buf, sizeof(buf),
FU_WAC_DEVICE_FEATURE_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_WAC_DEVICE_FEATURE_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_WAC_DEVICE_FEATURE_FLAG_NONE,
error);
}
static gboolean
fu_wac_device_write_firmware (FuDevice *device, GBytes *blob, GError **error)
{
DfuElement *element;
DfuImage *image;
FuWacDevice *self = FU_WAC_DEVICE (device);
gsize blocks_done = 0;
gsize blocks_total = 0;
g_autoptr(DfuFirmware) firmware = dfu_firmware_new ();
g_autoptr(GHashTable) fd_blobs = NULL;
g_autofree guint32 *csum_local = NULL;
/* load .wac file, including metadata */
if (!fu_wac_firmware_parse_data (firmware, blob,
DFU_FIRMWARE_PARSE_FLAG_NONE,
error))
return FALSE;
if (dfu_firmware_get_format (firmware) != DFU_FIRMWARE_FORMAT_SREC) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"expected firmware format is 'srec', got '%s'",
dfu_firmware_format_to_string (dfu_firmware_get_format (firmware)));
return FALSE;
}
/* enter flash mode */
if (!fu_wac_device_switch_to_flash_loader (self, error))
return FALSE;
/* get current selected device */
if (!fu_wac_device_ensure_firmware_index (self, error))
return FALSE;
/* use the correct image from the firmware */
image = dfu_firmware_get_image (firmware, self->firmware_index == 1 ? 1 : 0);
if (image == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"no firmware image for index %" G_GUINT16_FORMAT,
self->firmware_index);
return FALSE;
}
element = dfu_image_get_element_default (image);
if (element == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"no element in image %" G_GUINT16_FORMAT,
self->firmware_index);
return FALSE;
}
g_debug ("using element at addr 0x%0x",
(guint) dfu_element_get_address (element));
/* 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;
/* clear all checksums of pages */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
for (guint16 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;
}
/* 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 (guint16 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 = dfu_element_get_contents_chunk (element,
fd->start_addr,
fd->block_sz,
NULL);
if (blob_tmp == NULL)
break;
blob_block = dfu_utils_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) + 2;
/* write the data into the flash page */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
csum_local = g_new0 (guint32, self->flash_descriptors->len);
for (guint16 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;
/* erase entire block */
if (!fu_wac_device_erase_block (self, i, error))
return FALSE;
/* write block in chunks */
chunks = dfu_chunked_new_from_bytes (blob_block,
fd->start_addr,
0, /* page_sz */
self->write_block_sz);
for (guint j = 0; j < chunks->len; j++) {
DfuChunkedPacket *pkt = g_ptr_array_index (chunks, j);
g_autoptr(GBytes) blob_chunk = g_bytes_new (pkt->data, pkt->data_sz);
if (!fu_wac_device_write_block (self, pkt->address, blob_chunk, error))
return FALSE;
}
/* calculate expected checksum and save to device RAM */
csum_local[i] = fu_wac_calculate_checksum32le_bytes (blob_block);
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_device_set_progress_full (device, blocks_done++, blocks_total);
}
/* calculate CRC inside device */
for (guint16 i = 0; i < self->flash_descriptors->len; i++) {
if (!fu_wac_device_calculate_checksum_of_block (self, i, error))
return FALSE;
}
/* update device progress */
fu_device_set_progress_full (device, blocks_done++, blocks_total);
/* read all CRC of all pages and verify with local CRC */
if (!fu_wac_device_ensure_checksums (self, error))
return FALSE;
for (guint16 i = 0; i < self->flash_descriptors->len; i++) {
FuWacFlashDescriptor *fd = g_ptr_array_index (self->flash_descriptors, i);
guint32 csum_rom;
/* if page is protected */
if (fu_wav_device_flash_descriptor_is_wp (fd))
continue;
/* no more written pages */
if (g_hash_table_lookup (fd_blobs, fd) == NULL)
break;
/* 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;
}
g_debug ("matched checksum at block %u of 0x%08x", i, csum_rom);
}
/* update device progress */
fu_device_set_progress_full (device, blocks_done++, blocks_total);
/* store host CRC into flash */
if (!fu_wac_device_write_checksum_table (self, error))
return FALSE;
/* update progress */
fu_device_set_progress_full (device, blocks_total, blocks_total);
/* reboot, which switches the boot index of the firmware */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
return fu_wac_device_update_reset (self, error);
}
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 *version = NULL;
g_autoptr(FuWacModule) module = NULL;
guint8 buf[] = { [0] = FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH,
[1 ... 14] = 0xff };
buf[0] = FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH;
if (!fu_wac_device_get_feature_report (self, buf, sizeof(buf),
FU_WAC_DEVICE_FEATURE_FLAG_NONE,
error)) {
g_prefix_error (error, "Failed to get GetFirmwareVersionBluetooth: ");
return FALSE;
}
/* success */
name = g_strdup_printf ("%s [Legacy Bluetooth Module]",
fu_device_get_name (FU_DEVICE (self)));
version = g_strdup_printf ("%x.%x", (guint) buf[2], (guint) buf[1]);
module = fu_wac_module_bluetooth_new (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);
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 };
if (!fu_wac_device_get_feature_report (self, buf, sizeof(buf),
FU_WAC_DEVICE_FEATURE_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 */
version_bootloader = g_strdup_printf ("%u.%u", buf[1], buf[2]);
fu_device_set_version_bootloader (FU_DEVICE (self), version_bootloader);
/* 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;
/* version number is decimal */
version = g_strdup_printf ("%u.%u", buf[(i * 4) + 5], buf[(i * 4) + 6]);
switch (fw_type) {
case FU_WAC_MODULE_FW_TYPE_TOUCH:
module = fu_wac_module_touch_new (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);
break;
case FU_WAC_MODULE_FW_TYPE_BLUETOOTH:
module = fu_wac_module_bluetooth_new (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);
break;
case FU_WAC_MODULE_FW_TYPE_MAIN:
fu_device_set_version (FU_DEVICE (self), version);
break;
default:
g_warning ("unknown submodule type 0x%0x", fw_type);
break;
}
}
return TRUE;
}
static gboolean
fu_wac_device_open (FuUsbDevice *device, GError **error)
{
FuWacDevice *self = FU_WAC_DEVICE (device);
GUsbDevice *usb_device = fu_usb_device_get_dev (device);
g_autoptr(GString) str = g_string_new (NULL);
/* open device */
if (!g_usb_device_claim_interface (usb_device, 0x00, /* HID */
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error (error, "failed to claim HID interface: ");
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 */
fu_wac_device_to_string (FU_DEVICE (self), str);
g_debug ("opened: %s", str->str);
return TRUE;
}
static gboolean
fu_wac_device_close (FuUsbDevice *device, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (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);
/* success */
return TRUE;
}
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_icon (FU_DEVICE (self), "input-tablet");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
}
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);
FuUsbDeviceClass *klass_usb_device = FU_USB_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_usb_device->open = fu_wac_device_open;
klass_usb_device->close = fu_wac_device_close;
}
FuWacDevice *
fu_wac_device_new (GUsbDevice *usb_device)
{
FuWacDevice *device = NULL;
device = g_object_new (FU_TYPE_WAC_DEVICE,
"usb-device", usb_device,
NULL);
return device;
}