diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index 7a2b57423..322ab5450 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -250,6 +250,7 @@ mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg %endif %{_libdir}/fwupd-plugins-3/libfu_plugin_unifying.so %{_libdir}/fwupd-plugins-3/libfu_plugin_upower.so +%{_libdir}/fwupd-plugins-3/libfu_plugin_wacomhid.so %ghost %{_localstatedir}/lib/fwupd/gnupg %files devel diff --git a/plugins/meson.build b/plugins/meson.build index 9cfa3eeb1..907e31804 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -7,6 +7,7 @@ subdir('test') subdir('udev') subdir('unifying') subdir('upower') +subdir('wacomhid') # depends on dfu subdir('csr') diff --git a/plugins/wacomhid/README.md b/plugins/wacomhid/README.md new file mode 100644 index 000000000..69758aff2 --- /dev/null +++ b/plugins/wacomhid/README.md @@ -0,0 +1,17 @@ +Wacom HID Support +================= + +Introduction +------------ + +Wacom provides interactive pen displays, pen tablets, and styluses to equip and +inspire everyone make the world a more creative place. + +From 2016 Wacom has been using a HID-based proprietary flashing algorithm which +has been documented by support team at Wacom and provided under NDA under the +understanding it would be used to build a plugin under a LGPLv2+ licence. + +Wacom devices are actually composite devices, with the main ARM CPU being +programmed using a more complicated erase, write, verify algorithm based +on a historical update protocol. The "sub-module" devices use a newer protocol, +again based on HID, but are handled differently depending on thier type. diff --git a/plugins/wacomhid/fu-plugin-wacomhid.c b/plugins/wacomhid/fu-plugin-wacomhid.c new file mode 100644 index 000000000..42bcecfe7 --- /dev/null +++ b/plugins/wacomhid/fu-plugin-wacomhid.c @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-plugin.h" +#include "fu-plugin-vfuncs.h" + +#include "fu-wac-device.h" + +gboolean +fu_plugin_usb_device_added (FuPlugin *plugin, GUsbDevice *usb_device, GError **error) +{ + g_autoptr(FuWacDevice) device = NULL; + g_autoptr(FuDeviceLocker) locker = NULL; + device = fu_wac_device_new (usb_device); + fu_device_set_quirks (FU_DEVICE (device), fu_plugin_get_quirks (plugin)); + locker = fu_device_locker_new (device, error); + if (locker == NULL) + return FALSE; + fu_plugin_device_add (plugin, FU_DEVICE (device)); + return TRUE; +} + +gboolean +fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, + FwupdInstallFlags flags, GError **error) +{ + FuDevice *parent = fu_device_get_parent (device); + g_autoptr(FuDeviceLocker) locker = NULL; + locker = fu_device_locker_new (parent != NULL ? parent : device, error); + if (locker == NULL) + return FALSE; + return fu_device_write_firmware (device, blob_fw, error); +} diff --git a/plugins/wacomhid/fu-self-test.c b/plugins/wacomhid/fu-self-test.c new file mode 100644 index 000000000..0b9dbf97c --- /dev/null +++ b/plugins/wacomhid/fu-self-test.c @@ -0,0 +1,78 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include +#include +#include + +#include "fu-common.h" +#include "fu-test.h" +#include "fu-wac-common.h" +#include "fu-wac-firmware.h" + +#include "fwupd-error.h" + +static void +fu_wac_firmware_parse_func (void) +{ + DfuElement *element; + DfuImage *image; + gboolean ret; + g_autofree gchar *fn = NULL; + g_autoptr(DfuFirmware) firmware = dfu_firmware_new (); + g_autoptr(GBytes) blob_block = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GError) error = NULL; + + /* parse the test file */ + fn = fu_test_get_filename (TESTDATADIR, "test.wac"); + if (fn == NULL) { + g_test_skip ("no data file found"); + return; + } + bytes = fu_common_get_contents_bytes (fn, &error); + g_assert_no_error (error); + g_assert_nonnull (bytes); + ret = fu_wac_firmware_parse_data (firmware, bytes, + DFU_FIRMWARE_PARSE_FLAG_NONE, &error); + g_assert_no_error (error); + g_assert_true (ret); + + /* get image data */ + image = dfu_firmware_get_image (firmware, 0); + g_assert_nonnull (image); + element = dfu_image_get_element_default (image); + g_assert_nonnull (element); + + /* get block */ + blob_block = dfu_element_get_contents_chunk (element, 0x8008000, + 1024, &error); + g_assert_no_error (error); + g_assert_nonnull (blob_block); + fu_wac_buffer_dump ("IMG", FU_WAC_REPORT_ID_MODULE, + g_bytes_get_data (blob_block, NULL), + g_bytes_get_size (blob_block)); +} + +int +main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + + /* only critical and error are fatal */ + g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); + + /* log everything */ + g_setenv ("G_MESSAGES_DEBUG", "all", FALSE); + + /* tests go here */ + g_test_add_func ("/wac/firmware{parse}", fu_wac_firmware_parse_func); + return g_test_run (); +} + diff --git a/plugins/wacomhid/fu-wac-common.c b/plugins/wacomhid/fu-wac-common.c new file mode 100644 index 000000000..cf3e5f316 --- /dev/null +++ b/plugins/wacomhid/fu-wac-common.c @@ -0,0 +1,90 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-wac-common.h" + +guint32 +fu_wac_calculate_checksum32le (const guint8 *data, gsize len) +{ + guint32 csum = 0x0; + g_return_val_if_fail (len % 4 == 0, 0xff); + for (guint i = 0; i < len; i += 4) { + guint32 tmp; + memcpy (&tmp, &data[i], sizeof(guint32)); + csum += GUINT32_FROM_LE (tmp); + } + return GUINT32_TO_LE (csum); +} + +guint32 +fu_wac_calculate_checksum32le_bytes (GBytes *blob) +{ + gsize len = 0; + const guint8 *data = g_bytes_get_data (blob, &len); + return fu_wac_calculate_checksum32le (data, len); +} + +const gchar * +fu_wac_report_id_to_string (guint8 report_id) +{ + if (report_id == FU_WAC_REPORT_ID_FW_DESCRIPTOR) + return "FwDescriptor"; + if (report_id == FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER) + return "SwitchToFlashLoader"; + if (report_id == FU_WAC_REPORT_ID_QUIT_AND_RESET) + return "QuitAndReset"; + if (report_id == FU_WAC_REPORT_ID_READ_BLOCK_DATA) + return "ReadBlockData"; + if (report_id == FU_WAC_REPORT_ID_WRITE_BLOCK) + return "WriteBlock"; + if (report_id == FU_WAC_REPORT_ID_ERASE_BLOCK) + return "EraseBlock"; + if (report_id == FU_WAC_REPORT_ID_SET_READ_ADDRESS) + return "SetReadAddress"; + if (report_id == FU_WAC_REPORT_ID_GET_STATUS) + return "GetStatus"; + if (report_id == FU_WAC_REPORT_ID_UPDATE_RESET) + return "UpdateReset"; + if (report_id == FU_WAC_REPORT_ID_WRITE_WORD) + return "WriteWord"; + if (report_id == FU_WAC_REPORT_ID_GET_PARAMETERS) + return "GetParameters"; + if (report_id == FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR) + return "GetFlashDescriptor"; + if (report_id == FU_WAC_REPORT_ID_GET_CHECKSUMS) + return "GetChecksums"; + if (report_id == FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK) + return "SetChecksumForBlock"; + if (report_id == FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK) + return "CalculateChecksumForBlock"; + if (report_id == FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE) + return "WriteChecksumTable"; + if (report_id == FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX) + return "GetCurrentFirmwareIdx"; + if (report_id == FU_WAC_REPORT_ID_MODULE) + return "Module"; + return NULL; +} + +void +fu_wac_buffer_dump (const gchar *title, guint8 cmd, const guint8 *buf, gsize sz) +{ + if (g_getenv ("FWUPD_WAC_VERBOSE") == NULL) + return; + g_print ("%s %s (%" G_GSIZE_FORMAT "):\n", + title, fu_wac_report_id_to_string (cmd), sz); + for (gsize i = 0; i < sz; i++) { + g_print ("%02x ", buf[i]); + if (i > 0 && (i + 1) % 256 == 0) + g_print ("\n"); + } + g_print ("\n"); +} diff --git a/plugins/wacomhid/fu-wac-common.h b/plugins/wacomhid/fu-wac-common.h new file mode 100644 index 000000000..17c753711 --- /dev/null +++ b/plugins/wacomhid/fu-wac-common.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef __FU_WAC_HID_H +#define __FU_WAC_HID_H + +#include + +G_BEGIN_DECLS + +#define FU_WAC_PACKET_LEN 512 + +#define FU_WAC_REPORT_ID_COMMAND 0x01 +#define FU_WAC_REPORT_ID_STATUS 0x02 +#define FU_WAC_REPORT_ID_CONTROL 0x03 + +#define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_MAIN 0x07 +#define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_TOUCH 0x07 +#define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH 0x16 + +#define FU_WAC_REPORT_ID_FW_DESCRIPTOR 0xcb /* GET_FEATURE */ +#define FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER 0xcc /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_QUIT_AND_RESET 0xcd /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_READ_BLOCK_DATA 0xd1 /* GET_FEATURE */ +#define FU_WAC_REPORT_ID_WRITE_BLOCK 0xd2 /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_ERASE_BLOCK 0xd3 /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_SET_READ_ADDRESS 0xd4 /* GET_FEATURE */ +#define FU_WAC_REPORT_ID_GET_STATUS 0xd5 /* GET_FEATURE */ +#define FU_WAC_REPORT_ID_UPDATE_RESET 0xd6 /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_WRITE_WORD 0xd7 /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_GET_PARAMETERS 0xd8 /* GET_FEATURE */ +#define FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR 0xd9 /* GET_FEATURE */ +#define FU_WAC_REPORT_ID_GET_CHECKSUMS 0xda /* GET_FEATURE */ +#define FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK 0xdb /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK 0xdc /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE 0xde /* SET_FEATURE */ +#define FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX 0xe2 /* GET_FEATURE */ +#define FU_WAC_REPORT_ID_MODULE 0xe4 + +guint32 fu_wac_calculate_checksum32le (const guint8 *data, + gsize len); +guint32 fu_wac_calculate_checksum32le_bytes (GBytes *blob); +const gchar *fu_wac_report_id_to_string (guint8 report_id); +void fu_wac_buffer_dump (const gchar *title, + guint8 cmd, + const guint8 *buf, + gsize sz); + +G_END_DECLS + +#endif /* __FU_WAC_HID_H */ diff --git a/plugins/wacomhid/fu-wac-device.c b/plugins/wacomhid/fu-wac-device.c new file mode 100644 index 000000000..3a4a53fc0 --- /dev/null +++ b/plugins/wacomhid/fu-wac-device.c @@ -0,0 +1,909 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#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 */ + 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 */ + 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 (FU_DEVICE (self), + 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 (FU_DEVICE (self), 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 (FU_DEVICE (self), 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 (FU_DEVICE (self), blocks_total, blocks_total); + + /* reboot, which switches the boot index of the firmware */ + return fu_wac_device_update_reset (self, error); +} + +static gboolean +fu_wac_device_probe (FuUsbDevice *device, GError **error) +{ + const gchar *plugin_hints; + + /* devices have to be whitelisted */ + plugin_hints = fu_device_get_plugin_hints (FU_DEVICE (device)); + if (plugin_hints == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "not supported with this device"); + return FALSE; + } + + /* hardware cannot respond to GetReport(DeviceFirmwareDescriptor) */ + if (g_strcmp0 (plugin_hints, "use-runtime-version") == 0) { + fu_device_add_flag (FU_DEVICE (device), + FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION); + } + + /* hardcoded */ + fu_device_add_icon (FU_DEVICE (device), "input-tablet"); + fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE); + 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 *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 */ + 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_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 *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); + fu_device_add_child (FU_DEVICE (self), FU_DEVICE (module)); + fu_device_set_version (FU_DEVICE (module), version); + break; + case FU_WAC_MODULE_FW_TYPE_BLUETOOTH: + module = fu_wac_module_bluetooth_new (usb_device); + fu_device_add_child (FU_DEVICE (self), FU_DEVICE (module)); + 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; +} + +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; + klass_usb_device->probe = fu_wac_device_probe; +} + +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; +} diff --git a/plugins/wacomhid/fu-wac-device.h b/plugins/wacomhid/fu-wac-device.h new file mode 100644 index 000000000..3cf6bd791 --- /dev/null +++ b/plugins/wacomhid/fu-wac-device.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef __FU_WAC_DEVICE_H +#define __FU_WAC_DEVICE_H + +#include +#include + +#include "fu-plugin.h" + +G_BEGIN_DECLS + +#define FU_TYPE_WAC_DEVICE (fu_wac_device_get_type ()) +G_DECLARE_FINAL_TYPE (FuWacDevice, fu_wac_device, FU, WAC_DEVICE, FuUsbDevice) + +typedef enum { + FU_WAC_DEVICE_FEATURE_FLAG_NONE = 0, + FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC = 1 << 0, + FU_WAC_DEVICE_FEATURE_FLAG_LAST +} FuWacDeviceFeatureFlags; + +FuWacDevice *fu_wac_device_new (GUsbDevice *usb_device); +gboolean fu_wac_device_update_reset (FuWacDevice *self, + GError **error); +gboolean fu_wac_device_get_feature_report (FuWacDevice *self, + guint8 *buf, + gsize bufsz, + FuWacDeviceFeatureFlags flags, + GError **error); +gboolean fu_wac_device_set_feature_report (FuWacDevice *self, + guint8 *buf, + gsize bufsz, + FuWacDeviceFeatureFlags flags, + GError **error); + +G_END_DECLS + +#endif /* __FU_WAC_DEVICE_H */ diff --git a/plugins/wacomhid/fu-wac-firmware.c b/plugins/wacomhid/fu-wac-firmware.c new file mode 100644 index 000000000..74ea06f47 --- /dev/null +++ b/plugins/wacomhid/fu-wac-firmware.c @@ -0,0 +1,224 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "dfu-element.h" +#include "dfu-format-srec.h" +#include "dfu-image.h" + +#include "fu-wac-firmware.h" + +#include "fwupd-error.h" + +typedef struct { + guint32 addr; + guint32 sz; + guint32 prog_start_addr; +} DfuFirmwareWacHeaderRecord; + +/** + * fu_wac_firmware_parse_data: + * @firmware: a #DfuFirmware + * @bytes: data to parse + * @flags: some #DfuFirmwareParseFlags + * @error: a #GError, or %NULL + * + * Unpacks into a firmware object from DfuSe data. + * + * Returns: %TRUE for success + **/ +gboolean +fu_wac_firmware_parse_data (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error) +{ + gsize len; + guint8 *data; + g_auto(GStrv) lines = NULL; + g_autoptr(GString) image_buffer = NULL; + g_autofree gchar *data_str = NULL; + guint8 images_cnt = 0; + g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func (g_free); + + /* check the prefix (BE) */ + data = (guint8 *) g_bytes_get_data (bytes, &len); + if (memcmp (data, "WACOM", 5) != 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "invalid .wac prefix"); + return FALSE; + } + + /* parse each line */ + data_str = g_strndup ((const gchar *) data, len); + lines = g_strsplit (data_str, "\n", -1); + for (guint i = 0; lines[i] != NULL; i++) { + g_autofree gchar *cmd = g_strndup (lines[i], 2); + + /* remove windows line endings */ + g_strdelimit (lines[i], "\r", '\0'); + + /* Wacom-specific metadata */ + if (g_strcmp0 (cmd, "WA") == 0) { + guint cmdlen = strlen (lines[i]); + + /* header info record */ + if (memcmp (lines[i] + 2, "COM", 3) == 0) { + guint8 header_image_cnt = 0; + if (cmdlen != 40) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "invalid header, got %u bytes", + cmdlen); + return FALSE; + } + header_image_cnt = dfu_utils_buffer_parse_uint4 (lines[i] + 5); + for (guint j = 0; j < header_image_cnt; j++) { + DfuFirmwareWacHeaderRecord *hdr = g_new0 (DfuFirmwareWacHeaderRecord, 1); + hdr->addr = dfu_utils_buffer_parse_uint32 (lines[i] + (j * 16) + 6); + hdr->sz = dfu_utils_buffer_parse_uint32 (lines[i] + (j * 16) + 14); + g_ptr_array_add (header_infos, hdr); + g_debug ("header_fw%u_addr: 0x%x", j, hdr->addr); + g_debug ("header_fw%u_sz: 0x%x", j, hdr->sz); + } + continue; + } + + /* firmware headline record */ + if (cmdlen == 13) { + DfuFirmwareWacHeaderRecord *hdr; + guint8 idx = dfu_utils_buffer_parse_uint4 (lines[i] + 2); + if (idx == 0) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "headline %u invalid", + idx); + return FALSE; + } + if (idx > header_infos->len) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "headline %u exceeds header count %u", + idx, header_infos->len); + return FALSE; + } + hdr = g_ptr_array_index (header_infos, idx - 1); + hdr->prog_start_addr = dfu_utils_buffer_parse_uint32 (lines[i] + 3); + if (hdr->prog_start_addr != hdr->addr) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "programming address 0x%x != " + "base address 0x%0x for idx %u", + hdr->prog_start_addr, + hdr->addr, + idx); + return FALSE; + } + g_debug ("programing-start-address: 0x%x", hdr->prog_start_addr); + continue; + } + + g_debug ("unknown Wacom-specific metadata"); + continue; + } + + /* start */ + if (g_strcmp0 (cmd, "S0") == 0) { + if (image_buffer != NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "duplicate S0 without S7"); + return FALSE; + } + image_buffer = g_string_new (NULL); + } + + /* these are things we want to include in the image */ + if (g_strcmp0 (cmd, "S0") == 0 || + g_strcmp0 (cmd, "S1") == 0 || + g_strcmp0 (cmd, "S2") == 0 || + g_strcmp0 (cmd, "S3") == 0 || + g_strcmp0 (cmd, "S5") == 0 || + g_strcmp0 (cmd, "S7") == 0 || + g_strcmp0 (cmd, "S8") == 0 || + g_strcmp0 (cmd, "S9") == 0) { + if (image_buffer == NULL) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "%s without S0", cmd); + return FALSE; + } + g_string_append_printf (image_buffer, "%s\n", lines[i]); + } + + /* end */ + if (g_strcmp0 (cmd, "S7") == 0) { + g_autoptr(GBytes) blob = NULL; + g_autoptr(DfuImage) image = dfu_image_new (); + DfuFirmwareWacHeaderRecord *hdr; + + /* get the correct relocated start address */ + if (images_cnt >= header_infos->len) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "%s without header", cmd); + return FALSE; + } + hdr = g_ptr_array_index (header_infos, images_cnt); + + /* parse SREC file and add as image */ + blob = g_bytes_new (image_buffer->str, image_buffer->len); + if (!dfu_image_from_srec (image, blob, hdr->addr, flags, error)) + return FALSE; + + /* the alt-setting is used for the firmware index */ + dfu_image_set_alt_setting (image, images_cnt); + dfu_firmware_add_image (firmware, image); + images_cnt++; + + /* clear the image buffer */ + g_string_free (image_buffer, TRUE); + image_buffer = NULL; + } + } + + /* verify data is complete */ + if (image_buffer != NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "truncated data: no S7"); + return FALSE; + } + + /* ensure this matched the header */ + if (header_infos->len != images_cnt) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "not enough images %u for header count %u", + images_cnt, + header_infos->len); + return FALSE; + } + + /* success */ + dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_SREC); + return TRUE; +} diff --git a/plugins/wacomhid/fu-wac-firmware.h b/plugins/wacomhid/fu-wac-firmware.h new file mode 100644 index 000000000..81e8ac1c4 --- /dev/null +++ b/plugins/wacomhid/fu-wac-firmware.h @@ -0,0 +1,25 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef __FU_WAC_FIRMWARE_H +#define __FU_WAC_FIRMWARE_H + +#include +#include + +#include "dfu-firmware.h" + +G_BEGIN_DECLS + +gboolean fu_wac_firmware_parse_data (DfuFirmware *firmware, + GBytes *bytes, + DfuFirmwareParseFlags flags, + GError **error); + +G_END_DECLS + +#endif /* __FU_WAC_FIRMWARE_H */ diff --git a/plugins/wacomhid/fu-wac-module-bluetooth.c b/plugins/wacomhid/fu-wac-module-bluetooth.c new file mode 100644 index 000000000..6decbf916 --- /dev/null +++ b/plugins/wacomhid/fu-wac-module-bluetooth.c @@ -0,0 +1,190 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-wac-common.h" +#include "fu-wac-device.h" +#include "fu-wac-module-bluetooth.h" + +#include "dfu-chunked.h" + +struct _FuWacModuleBluetooth +{ + FuWacModule parent_instance; +}; + +G_DEFINE_TYPE (FuWacModuleBluetooth, fu_wac_module_bluetooth, FU_TYPE_WAC_MODULE) + +#define FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ 256 +#define FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_START 0x3000 +#define FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_STOP 0x8000 + +typedef struct { + guint8 preamble[7]; + guint8 addr[3]; + guint8 crc; + guint8 cdata[FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ]; +} FuWacModuleBluetoothBlockData; + +static void +fu_wac_module_bluetooth_calculate_crc_byte (guint8 *crc, guint8 data) +{ + guint8 c[8]; + guint8 m[8]; + guint8 r[8]; + + /* find out what bits are set */ + for (guint i = 0; i < 8; i++) { + c[i] = (*crc & 1 << i) > 0; + m[i] = (data & 1 << i) > 0; + } + + /* do CRC on byte */ + r[7] = (c[7] ^ m[4] ^ c[3] ^ m[3] ^ c[4] ^ m[6] ^ c[1] ^ m[0]); + r[6] = (c[6] ^ m[5] ^ c[2] ^ m[4] ^ c[3] ^ m[7] ^ c[0] ^ m[1]); + r[5] = (c[5] ^ m[6] ^ c[1] ^ m[5] ^ c[2] ^ m[2]); + r[4] = (c[4] ^ m[7] ^ c[0] ^ m[6] ^ c[1] ^ m[3]); + r[3] = (m[7] ^ m[0] ^ c[7] ^ c[0] ^ m[3] ^ c[4] ^ m[6] ^ c[1]); + r[2] = (m[1] ^ c[6] ^ m[0] ^ c[7] ^ m[3] ^ c[4] ^ m[7] ^ c[0] ^ m[6] ^ c[1]); + r[1] = (m[2] ^ c[5] ^ m[1] ^ c[6] ^ m[4] ^ c[3] ^ m[7] ^ c[0]); + r[0] = (m[3] ^ c[4] ^ m[2] ^ c[5] ^ m[5] ^ c[2]); + + /* copy back into CRC */ + *crc = 0; + for (guint i = 0; i < 8; i++) { + if (r[i] == 0) + continue; + *crc |= (1 << i); + } +} + +static guint8 +fu_wac_module_bluetooth_calculate_crc (const guint8 *data, gsize sz) +{ + guint8 crc = 0; + for (gsize i = 0; i < sz; i++) + fu_wac_module_bluetooth_calculate_crc_byte (&crc, data[i]); + return crc; +} + +static GPtrArray * +fu_wac_module_bluetooth_parse_blocks (const guint8 *data, gsize sz, gboolean skip_user_data) +{ + const guint8 preamble[] = {0x02, 0x00, 0x0f, 0x06, 0x01, 0x08, 0x01}; + GPtrArray *blocks = g_ptr_array_new_with_free_func (g_free); + for (guint addr = 0x0; addr < sz; addr += FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ) { + FuWacModuleBluetoothBlockData *bd; + gsize cdata_sz = FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ; + + /* user data area */ + if (skip_user_data && + addr >= FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_START && + addr < FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_STOP) + continue; + + bd = g_new0 (FuWacModuleBluetoothBlockData, 1); + memcpy (bd->preamble, preamble, sizeof (preamble)); + bd->addr[0] = (addr >> 16) & 0xff; + bd->addr[1] = (addr >> 8) & 0xff; + bd->addr[2] = addr & 0xff; + memset (bd->cdata, 0xff, FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ); + + /* if file is not in multiples of payload size */ + if (addr + FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ >= sz) + cdata_sz = sz - addr; + memcpy (bd->cdata, data + addr, cdata_sz); + bd->crc = fu_wac_module_bluetooth_calculate_crc (bd->cdata, + FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ); + g_ptr_array_add (blocks, bd); + } + return blocks; +} + +static gboolean +fu_wac_module_bluetooth_write_firmware (FuDevice *device, GBytes *blob, GError **error) +{ + FuWacDevice *parent = FU_WAC_DEVICE (fu_device_get_parent (device)); + FuWacModule *self = FU_WAC_MODULE (device); + const guint8 *data; + gsize len = 0; + gsize blocks_total = 0; + const guint8 buf_start[] = { 0x00 }; + g_autoptr(GPtrArray) blocks = NULL; + g_autoptr(GBytes) blob_start = g_bytes_new_static (buf_start, 1); + + /* build each data packet */ + data = g_bytes_get_data (blob, &len); + blocks = fu_wac_module_bluetooth_parse_blocks (data, len, TRUE); + blocks_total = blocks->len + 2; + + /* start, which will erase the module */ + if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_START, blob_start, error)) + return FALSE; + + /* update progress */ + fu_device_set_progress_full (device, 1, blocks_total); + + /* data */ + for (guint i = 0; i < blocks->len; i++) { + FuWacModuleBluetoothBlockData *bd = g_ptr_array_index (blocks, i); + guint8 buf[256+11]; + g_autoptr(GBytes) blob_chunk = NULL; + + /* build data packet */ + memset (buf, 0xff, sizeof(buf)); + memcpy(&buf[0], bd->preamble, 7); + memcpy(&buf[7], bd->addr, 3); + buf[10] = bd->crc; + memcpy (&buf[11], bd->cdata, sizeof(bd->cdata)); + blob_chunk = g_bytes_new (buf, sizeof(buf)); + if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_DATA, + blob_chunk, error)) + return FALSE; + + /* update progress */ + fu_device_set_progress_full (device, i + 1, blocks_total); + } + + /* end */ + if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_END, NULL, error)) + return FALSE; + + /* update progress */ + fu_device_set_progress_full (device, blocks_total, blocks_total); + + /* reboot */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_RESTART); + return fu_wac_device_update_reset (parent, error); +} + +static void +fu_wac_module_bluetooth_init (FuWacModuleBluetooth *self) +{ + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_set_name (FU_DEVICE (self), "Bluetooth Module"); +} + +static void +fu_wac_module_bluetooth_class_init (FuWacModuleBluetoothClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + klass_device->write_firmware = fu_wac_module_bluetooth_write_firmware; +} + +FuWacModule * +fu_wac_module_bluetooth_new (GUsbDevice *usb_device) +{ + FuWacModule *module = NULL; + module = g_object_new (FU_TYPE_WAC_MODULE_BLUETOOTH, + "usb-device", usb_device, + "fw-type", FU_WAC_MODULE_FW_TYPE_BLUETOOTH, + NULL); + return module; +} diff --git a/plugins/wacomhid/fu-wac-module-bluetooth.h b/plugins/wacomhid/fu-wac-module-bluetooth.h new file mode 100644 index 000000000..b696b25c2 --- /dev/null +++ b/plugins/wacomhid/fu-wac-module-bluetooth.h @@ -0,0 +1,25 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef __FU_WAC_MODULE_BLUETOOTH_H +#define __FU_WAC_MODULE_BLUETOOTH_H + +#include +#include + +#include "fu-wac-module.h" + +G_BEGIN_DECLS + +#define FU_TYPE_WAC_MODULE_BLUETOOTH (fu_wac_module_bluetooth_get_type ()) +G_DECLARE_FINAL_TYPE (FuWacModuleBluetooth, fu_wac_module_bluetooth, FU, WAC_MODULE_BLUETOOTH, FuWacModule) + +FuWacModule *fu_wac_module_bluetooth_new (GUsbDevice *usb_device); + +G_END_DECLS + +#endif /* __FU_WAC_MODULE_BLUETOOTH_H */ diff --git a/plugins/wacomhid/fu-wac-module-touch.c b/plugins/wacomhid/fu-wac-module-touch.c new file mode 100644 index 000000000..416505c53 --- /dev/null +++ b/plugins/wacomhid/fu-wac-module-touch.c @@ -0,0 +1,112 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-wac-device.h" +#include "fu-wac-module-touch.h" + +#include "dfu-chunked.h" + +struct _FuWacModuleTouch +{ + FuWacModule parent_instance; +}; + +G_DEFINE_TYPE (FuWacModuleTouch, fu_wac_module_touch, FU_TYPE_WAC_MODULE) + +static gboolean +fu_wac_module_touch_write_firmware (FuDevice *device, GBytes *blob, GError **error) +{ + FuWacDevice *parent = FU_WAC_DEVICE (fu_device_get_parent (device)); + FuWacModule *self = FU_WAC_MODULE (device); + const guint8 *data; + gsize blocks_total = 0; + gsize len = 0; + g_autoptr(GPtrArray) chunks = NULL; + + /* build each data packet */ + data = g_bytes_get_data (blob, &len); + if (len % 128 != 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "firmware has to be padded to 128b"); + return FALSE; + } + chunks = dfu_chunked_new (data, (guint32) len, + 0x0, /* addr_start */ + 0x0, /* page_sz */ + 128); /* packet_sz */ + blocks_total = chunks->len + 2; + + /* start, which will erase the module */ + if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_START, NULL, error)) + return FALSE; + + /* update progress */ + fu_device_set_progress_full (device, 1, blocks_total); + + /* data */ + for (guint i = 0; i < chunks->len; i++) { + DfuChunkedPacket *pkt = g_ptr_array_index (chunks, i); + guint8 buf[128+7]; + g_autoptr(GBytes) blob_chunk = NULL; + + /* build G11T data packet */ + memset (buf, 0xff, sizeof(buf)); + buf[0] = 0x01; /* writing */ + fu_common_write_uint32 (&buf[1], pkt->address, G_LITTLE_ENDIAN); + buf[5] = pkt->idx; + memcpy (&buf[6], pkt->data, pkt->data_sz); + blob_chunk = g_bytes_new (buf, sizeof(buf)); + if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_DATA, + blob_chunk, error)) + return FALSE; + + /* update progress */ + fu_device_set_progress_full (device, i + 1, blocks_total); + } + + /* end */ + if (!fu_wac_module_set_feature (self, FU_WAC_MODULE_COMMAND_END, NULL, error)) + return FALSE; + + /* update progress */ + fu_device_set_progress_full (device, blocks_total, blocks_total); + + /* reboot */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_RESTART); + return fu_wac_device_update_reset (parent, error); +} + +static void +fu_wac_module_touch_init (FuWacModuleTouch *self) +{ + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_set_name (FU_DEVICE (self), "Touch Module"); +} + +static void +fu_wac_module_touch_class_init (FuWacModuleTouchClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + klass_device->write_firmware = fu_wac_module_touch_write_firmware; +} + +FuWacModule * +fu_wac_module_touch_new (GUsbDevice *usb_device) +{ + FuWacModule *module = NULL; + module = g_object_new (FU_TYPE_WAC_MODULE_TOUCH, + "usb-device", usb_device, + "fw-type", FU_WAC_MODULE_FW_TYPE_TOUCH, + NULL); + return module; +} diff --git a/plugins/wacomhid/fu-wac-module-touch.h b/plugins/wacomhid/fu-wac-module-touch.h new file mode 100644 index 000000000..77f3cf7f2 --- /dev/null +++ b/plugins/wacomhid/fu-wac-module-touch.h @@ -0,0 +1,25 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef __FU_WAC_MODULE_TOUCH_H +#define __FU_WAC_MODULE_TOUCH_H + +#include +#include + +#include "fu-wac-module.h" + +G_BEGIN_DECLS + +#define FU_TYPE_WAC_MODULE_TOUCH (fu_wac_module_touch_get_type ()) +G_DECLARE_FINAL_TYPE (FuWacModuleTouch, fu_wac_module_touch, FU, WAC_MODULE_TOUCH, FuWacModule) + +FuWacModule *fu_wac_module_touch_new (GUsbDevice *usb_device); + +G_END_DECLS + +#endif /* __FU_WAC_MODULE_TOUCH_H */ diff --git a/plugins/wacomhid/fu-wac-module.c b/plugins/wacomhid/fu-wac-module.c new file mode 100644 index 000000000..c3288e0de --- /dev/null +++ b/plugins/wacomhid/fu-wac-module.c @@ -0,0 +1,358 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-wac-module.h" +#include "fu-wac-common.h" +#include "fu-wac-device.h" + +#include "dfu-common.h" +#include "dfu-chunked.h" +#include "dfu-firmware.h" + +#define FU_WAC_MODLE_STATUS_OK 0 +#define FU_WAC_MODLE_STATUS_BUSY 1 +#define FU_WAC_MODLE_STATUS_ERR_CRC 2 +#define FU_WAC_MODLE_STATUS_ERR_CMD 3 +#define FU_WAC_MODLE_STATUS_ERR_HW_ACCESS_FAIL 4 +#define FU_WAC_MODLE_STATUS_ERR_FLASH_NO_SUPPORT 5 +#define FU_WAC_MODLE_STATUS_ERR_MODE_WRONG 6 +#define FU_WAC_MODLE_STATUS_ERR_MPU_NO_SUPPORT 7 +#define FU_WAC_MODLE_STATUS_ERR_VERSION_NO_SUPPORT 8 +#define FU_WAC_MODLE_STATUS_ERR_ERASE 9 +#define FU_WAC_MODLE_STATUS_ERR_WRITE 10 +#define FU_WAC_MODLE_STATUS_ERR_EXIT 11 +#define FU_WAC_MODLE_STATUS_ERR 12 +#define FU_WAC_MODLE_STATUS_ERR_INVALID_OP 13 +#define FU_WAC_MODLE_STATUS_ERR_WRONG_IMAGE 14 + +typedef struct { + GUsbDevice *usb_device; + guint8 fw_type; + guint8 command; + guint8 status; +} FuWacModulePrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (FuWacModule, fu_wac_module, FU_TYPE_DEVICE) +#define GET_PRIVATE(o) (fu_wac_module_get_instance_private (o)) + +enum { + PROP_0, + PROP_FW_TYPE, + PROP_USB_DEVICE, + PROP_LAST +}; + +static const gchar * +fu_wac_module_fw_type_to_string (guint8 fw_type) +{ + if (fw_type == FU_WAC_MODULE_FW_TYPE_TOUCH) + return "touch"; + if (fw_type == FU_WAC_MODULE_FW_TYPE_BLUETOOTH) + return "bluetooth"; + if (fw_type == FU_WAC_MODULE_FW_TYPE_EMR_CORRECTION) + return "emr-correction"; + if (fw_type == FU_WAC_MODULE_FW_TYPE_BLUETOOTH_HID) + return "bluetooth-hid"; + return NULL; +} + +static const gchar * +fu_wac_module_command_to_string (guint8 command) +{ + if (command == FU_WAC_MODULE_COMMAND_START) + return "start"; + if (command == FU_WAC_MODULE_COMMAND_DATA) + return "data"; + if (command == FU_WAC_MODULE_COMMAND_END) + return "end"; + return NULL; +} + +static const gchar * +fu_wac_module_status_to_string (guint8 status) +{ + if (status == FU_WAC_MODLE_STATUS_OK) + return "ok"; + if (status == FU_WAC_MODLE_STATUS_BUSY) + return "busy"; + if (status == FU_WAC_MODLE_STATUS_ERR_CRC) + return "err-crc"; + if (status == FU_WAC_MODLE_STATUS_ERR_CMD) + return "err-cmd"; + if (status == FU_WAC_MODLE_STATUS_ERR_HW_ACCESS_FAIL) + return "err-hw-access-fail"; + if (status == FU_WAC_MODLE_STATUS_ERR_FLASH_NO_SUPPORT) + return "err-flash-no-support"; + if (status == FU_WAC_MODLE_STATUS_ERR_MODE_WRONG) + return "err-mode-wrong"; + if (status == FU_WAC_MODLE_STATUS_ERR_MPU_NO_SUPPORT) + return "err-mpu-no-support"; + if (status == FU_WAC_MODLE_STATUS_ERR_VERSION_NO_SUPPORT) + return "erro-version-no-support"; + if (status == FU_WAC_MODLE_STATUS_ERR_ERASE) + return "err-erase"; + if (status == FU_WAC_MODLE_STATUS_ERR_WRITE) + return "err-write"; + if (status == FU_WAC_MODLE_STATUS_ERR_EXIT) + return "err-exit"; + if (status == FU_WAC_MODLE_STATUS_ERR) + return "err-err"; + if (status == FU_WAC_MODLE_STATUS_ERR_INVALID_OP) + return "err-invalid-op"; + if (status == FU_WAC_MODLE_STATUS_ERR_WRONG_IMAGE) + return "err-wrong-image"; + return NULL; +} + +static void +fu_wac_module_to_string (FuDevice *device, GString *str) +{ + FuWacModule *self = FU_WAC_MODULE (device); + FuWacModulePrivate *priv = GET_PRIVATE (self); + g_string_append (str, " FuWacSubModule:\n"); + g_string_append_printf (str, " fw-type:\t\t%s\n", + fu_wac_module_fw_type_to_string (priv->fw_type)); + g_string_append_printf (str, " status:\t\t%s\n", + fu_wac_module_status_to_string (priv->status)); + g_string_append_printf (str, " command:\t\t%s\n", + fu_wac_module_command_to_string (priv->command)); +} + +static gboolean +fu_wac_module_refresh (FuWacModule *self, GError **error) +{ + FuWacDevice *parent_device = FU_WAC_DEVICE (fu_device_get_parent (FU_DEVICE (self))); + FuWacModulePrivate *priv = GET_PRIVATE (self); + guint8 buf[] = { [0] = FU_WAC_REPORT_ID_MODULE, + [1 ... FU_WAC_PACKET_LEN - 1] = 0xff }; + + /* get from hardware */ + if (!fu_wac_device_get_feature_report (parent_device, buf, sizeof(buf), + FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC, + error)) { + g_prefix_error (error, "failed to refresh status: "); + return FALSE; + } + + /* check fw type */ + if (priv->fw_type != buf[1]) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "Submodule GetFeature fw_Type invalid " + "got 0x%02x expected 0x%02x", + (guint) buf[1], (guint) priv->fw_type); + return FALSE; + } + + /* current phase */ + priv->command = buf[2]; + g_debug ("command: %s", fu_wac_module_command_to_string (priv->command)); + + /* current status */ + priv->status = buf[3]; + g_debug ("status: %s", fu_wac_module_status_to_string (priv->status)); + + /* success */ + return TRUE; +} + +gboolean +fu_wac_module_set_feature (FuWacModule *self, + guint8 command, + GBytes *blob, /* optional */ + GError **error) +{ + FuWacDevice *parent_device = FU_WAC_DEVICE (fu_device_get_parent (FU_DEVICE (self))); + FuWacModulePrivate *priv = GET_PRIVATE (self); + const guint8 *data; + gsize len = 0; + guint busy_poll_loops = 100; /* 1s */ + guint8 buf[] = { [0] = FU_WAC_REPORT_ID_MODULE, + [1] = priv->fw_type, + [2] = command, + [3 ... FU_WAC_PACKET_LEN - 1] = 0xff }; + + /* verify the size of the blob */ + if (blob != NULL) { + data = g_bytes_get_data (blob, &len); + if (len > 509) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "Submodule SetFeature blob larger than " + "buffer %" G_GSIZE_FORMAT, len); + return FALSE; + } + } + + /* build packet */ + if (len > 0) + memcpy (&buf[3], data, len); + + /* tell the daemon the current status */ + switch (command) { + case FU_WAC_MODULE_COMMAND_START: + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); + break; + case FU_WAC_MODULE_COMMAND_DATA: + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); + break; + case FU_WAC_MODULE_COMMAND_END: + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY); + break; + default: + break; + } + + /* send to hardware */ + if (!fu_wac_device_set_feature_report (parent_device, buf, sizeof(buf), + FU_WAC_DEVICE_FEATURE_FLAG_ALLOW_TRUNC, + error)) { + g_prefix_error (error, "failed to set module feature: "); + return FALSE; + } + + /* special case StartProgram, as it can take much longer as it is + * erasing the blocks (15s) */ + if (command == FU_WAC_MODULE_COMMAND_START) + busy_poll_loops *= 15; + + /* wait for hardware */ + for (guint i = 0; i < busy_poll_loops; i++) { + if (!fu_wac_module_refresh (self, error)) + return FALSE; + if (priv->status == FU_WAC_MODLE_STATUS_BUSY) { + g_usleep (10000); /* 10ms */ + continue; + } + if (priv->status == FU_WAC_MODLE_STATUS_OK) + return TRUE; + } + + /* the hardware never responded */ + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "Failed to SetFeature: %s", + fu_wac_module_status_to_string (priv->status)); + return FALSE; +} + +static void +fu_wac_module_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + FuWacModule *self = FU_WAC_MODULE (object); + FuWacModulePrivate *priv = GET_PRIVATE (self); + switch (prop_id) { + case PROP_FW_TYPE: + g_value_set_uint (value, priv->fw_type); + break; + case PROP_USB_DEVICE: + g_value_set_object (value, priv->usb_device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +fu_wac_module_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + FuWacModule *self = FU_WAC_MODULE (object); + FuWacModulePrivate *priv = GET_PRIVATE (self); + switch (prop_id) { + case PROP_FW_TYPE: + priv->fw_type = g_value_get_uint (value); + break; + case PROP_USB_DEVICE: + g_set_object (&priv->usb_device, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +fu_wac_module_init (FuWacModule *self) +{ +} + +static void +fu_wac_module_constructed (GObject *object) +{ + FuWacModule *self = FU_WAC_MODULE (object); + FuWacModulePrivate *priv = GET_PRIVATE (self); + g_autofree gchar *devid = NULL; + g_autofree gchar *platform_id = NULL; + g_autofree gchar *vendor_id = NULL; + + /* set vendor ID */ + vendor_id = g_strdup_printf ("USB:0x%04X", g_usb_device_get_vid (priv->usb_device)); + fu_device_set_vendor_id (FU_DEVICE (self), vendor_id); + + /* set USB platform ID automatically */ + platform_id = g_strdup_printf ("%s-%s", + g_usb_device_get_platform_id (priv->usb_device), + fu_wac_module_fw_type_to_string (priv->fw_type)); + fu_device_set_platform_id (FU_DEVICE (self), platform_id); + + /* append the firmware kind to the generated GUID */ + devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X-%s", + g_usb_device_get_vid (priv->usb_device), + g_usb_device_get_pid (priv->usb_device), + fu_wac_module_fw_type_to_string (priv->fw_type)); + fu_device_add_guid (FU_DEVICE (self), devid); + + G_OBJECT_CLASS (fu_wac_module_parent_class)->constructed (object); +} + +static void +fu_wac_module_finalize (GObject *object) +{ + FuWacModule *self = FU_WAC_MODULE (object); + FuWacModulePrivate *priv = GET_PRIVATE (self); + if (priv->usb_device != NULL) + g_object_unref (priv->usb_device); + G_OBJECT_CLASS (fu_wac_module_parent_class)->finalize (object); +} + +static void +fu_wac_module_class_init (FuWacModuleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + + /* properties */ + object_class->get_property = fu_wac_module_get_property; + object_class->set_property = fu_wac_module_set_property; + pspec = g_param_spec_object ("usb-device", NULL, NULL, + G_USB_TYPE_DEVICE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_NAME); + g_object_class_install_property (object_class, PROP_USB_DEVICE, pspec); + pspec = g_param_spec_uint ("fw-type", NULL, NULL, + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME); + g_object_class_install_property (object_class, PROP_FW_TYPE, pspec); + + object_class->constructed = fu_wac_module_constructed; + object_class->finalize = fu_wac_module_finalize; + klass_device->to_string = fu_wac_module_to_string; +} diff --git a/plugins/wacomhid/fu-wac-module.h b/plugins/wacomhid/fu-wac-module.h new file mode 100644 index 000000000..5c0604b5b --- /dev/null +++ b/plugins/wacomhid/fu-wac-module.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef __FU_WAC_MODULE_H +#define __FU_WAC_MODULE_H + +#include + +#include "fu-plugin.h" + +G_BEGIN_DECLS + +#define FU_TYPE_WAC_MODULE (fu_wac_module_get_type ()) +G_DECLARE_DERIVABLE_TYPE (FuWacModule, fu_wac_module, FU, WAC_MODULE, FuDevice) + +struct _FuWacModuleClass +{ + FuDeviceClass parent_class; +}; + +#define FU_WAC_MODULE_FW_TYPE_TOUCH 0x00 +#define FU_WAC_MODULE_FW_TYPE_BLUETOOTH 0x01 +#define FU_WAC_MODULE_FW_TYPE_EMR_CORRECTION 0x02 +#define FU_WAC_MODULE_FW_TYPE_BLUETOOTH_HID 0x03 +#define FU_WAC_MODULE_FW_TYPE_MAIN 0x3f + +#define FU_WAC_MODULE_COMMAND_START 0x01 +#define FU_WAC_MODULE_COMMAND_DATA 0x02 +#define FU_WAC_MODULE_COMMAND_END 0x03 + +gboolean fu_wac_module_set_feature (FuWacModule *self, + guint8 command, + GBytes *blob, + GError **error); + +G_END_DECLS + +#endif /* __FU_WAC_MODULE_H */ diff --git a/plugins/wacomhid/meson.build b/plugins/wacomhid/meson.build new file mode 100644 index 000000000..2eaa25a29 --- /dev/null +++ b/plugins/wacomhid/meson.build @@ -0,0 +1,65 @@ +cargs = ['-DG_LOG_DOMAIN="FuPluginWac"'] + +install_data(['wac-intuos.quirk'], + install_dir: join_paths(datadir, 'fwupd', 'quirks.d') +) + +shared_module('fu_plugin_wacomhid', + sources : [ + 'fu-wac-common.c', + 'fu-wac-device.c', + 'fu-wac-firmware.c', + 'fu-wac-module.c', + 'fu-wac-module-bluetooth.c', + 'fu-wac-module-touch.c', + 'fu-plugin-wacomhid.c', + ], + include_directories : [ + include_directories('../..'), + include_directories('../dfu'), + include_directories('../../src'), + include_directories('../../libfwupd'), + ], + install : true, + install_dir: plugin_dir, + c_args : cargs, + dependencies : [ + plugin_deps, + ], + link_with : [ + dfu, + ], +) + +if get_option('tests') + testdatadir = join_paths(meson.current_source_dir(), 'tests') + cargs += '-DTESTDATADIR="' + testdatadir + '"' + e = executable( + 'fu-self-test-wac', + sources : [ + 'fu-self-test.c', + 'fu-wac-common.c', + 'fu-wac-firmware.c', + ], + include_directories : [ + include_directories('..'), + include_directories('../dfu'), + include_directories('../..'), + include_directories('../../libfwupd'), + include_directories('../../src'), + ], + dependencies : [ + appstream_glib, + gio, + gusb, + libm, + ], + link_with : [ + dfu, + fwupd, + libfwupdprivate, + ], + c_args : cargs + ) + test('dfu-self-test', e) +endif diff --git a/plugins/wacomhid/wac-intuos.quirk b/plugins/wacomhid/wac-intuos.quirk new file mode 100644 index 000000000..64cfc284c --- /dev/null +++ b/plugins/wacomhid/wac-intuos.quirk @@ -0,0 +1,4 @@ +[FuWacDevice] + +# Intuos Pro +USB\VID_056A&PID_0378=use-runtime-version