Add an experimental plugin to update some new Wacom tablets

This commit is contained in:
Richard Hughes 2018-05-03 13:51:28 +01:00
parent 445afbf62c
commit 872ec1b68f
19 changed files with 2303 additions and 0 deletions

View File

@ -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

View File

@ -7,6 +7,7 @@ subdir('test')
subdir('udev')
subdir('unifying')
subdir('upower')
subdir('wacomhid')
# depends on dfu
subdir('csr')

View File

@ -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.

View File

@ -0,0 +1,39 @@
/* -*- 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 "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);
}

View File

@ -0,0 +1,78 @@
/* -*- 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 <glib-object.h>
#include <stdlib.h>
#include <string.h>
#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 ();
}

View File

@ -0,0 +1,90 @@
/* -*- 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-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");
}

View File

@ -0,0 +1,55 @@
/* -*- 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+
*/
#ifndef __FU_WAC_HID_H
#define __FU_WAC_HID_H
#include <glib-object.h>
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 */

View File

@ -0,0 +1,909 @@
/* -*- 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 */
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;
}

View File

@ -0,0 +1,43 @@
/* -*- 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+
*/
#ifndef __FU_WAC_DEVICE_H
#define __FU_WAC_DEVICE_H
#include <glib-object.h>
#include <gusb.h>
#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 */

View File

@ -0,0 +1,224 @@
/* -*- 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 "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;
}

View File

@ -0,0 +1,25 @@
/* -*- 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+
*/
#ifndef __FU_WAC_FIRMWARE_H
#define __FU_WAC_FIRMWARE_H
#include <glib-object.h>
#include <gio/gio.h>
#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 */

View File

@ -0,0 +1,190 @@
/* -*- 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-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;
}

View File

@ -0,0 +1,25 @@
/* -*- 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+
*/
#ifndef __FU_WAC_MODULE_BLUETOOTH_H
#define __FU_WAC_MODULE_BLUETOOTH_H
#include <glib-object.h>
#include <gusb.h>
#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 */

View File

@ -0,0 +1,112 @@
/* -*- 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-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;
}

View File

@ -0,0 +1,25 @@
/* -*- 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+
*/
#ifndef __FU_WAC_MODULE_TOUCH_H
#define __FU_WAC_MODULE_TOUCH_H
#include <glib-object.h>
#include <gusb.h>
#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 */

View File

@ -0,0 +1,358 @@
/* -*- 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-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;
}

View File

@ -0,0 +1,42 @@
/* -*- 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+
*/
#ifndef __FU_WAC_MODULE_H
#define __FU_WAC_MODULE_H
#include <glib-object.h>
#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 */

View File

@ -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

View File

@ -0,0 +1,4 @@
[FuWacDevice]
# Intuos Pro
USB\VID_056A&PID_0378=use-runtime-version