mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-29 20:32:50 +00:00
Add an experimental plugin to update some new Wacom tablets
This commit is contained in:
parent
445afbf62c
commit
872ec1b68f
@ -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
|
||||
|
@ -7,6 +7,7 @@ subdir('test')
|
||||
subdir('udev')
|
||||
subdir('unifying')
|
||||
subdir('upower')
|
||||
subdir('wacomhid')
|
||||
|
||||
# depends on dfu
|
||||
subdir('csr')
|
||||
|
17
plugins/wacomhid/README.md
Normal file
17
plugins/wacomhid/README.md
Normal 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.
|
39
plugins/wacomhid/fu-plugin-wacomhid.c
Normal file
39
plugins/wacomhid/fu-plugin-wacomhid.c
Normal 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);
|
||||
}
|
78
plugins/wacomhid/fu-self-test.c
Normal file
78
plugins/wacomhid/fu-self-test.c
Normal 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 ();
|
||||
}
|
||||
|
90
plugins/wacomhid/fu-wac-common.c
Normal file
90
plugins/wacomhid/fu-wac-common.c
Normal 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");
|
||||
}
|
55
plugins/wacomhid/fu-wac-common.h
Normal file
55
plugins/wacomhid/fu-wac-common.h
Normal 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 */
|
909
plugins/wacomhid/fu-wac-device.c
Normal file
909
plugins/wacomhid/fu-wac-device.c
Normal 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;
|
||||
}
|
43
plugins/wacomhid/fu-wac-device.h
Normal file
43
plugins/wacomhid/fu-wac-device.h
Normal 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 */
|
224
plugins/wacomhid/fu-wac-firmware.c
Normal file
224
plugins/wacomhid/fu-wac-firmware.c
Normal 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;
|
||||
}
|
25
plugins/wacomhid/fu-wac-firmware.h
Normal file
25
plugins/wacomhid/fu-wac-firmware.h
Normal 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 */
|
190
plugins/wacomhid/fu-wac-module-bluetooth.c
Normal file
190
plugins/wacomhid/fu-wac-module-bluetooth.c
Normal 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;
|
||||
}
|
25
plugins/wacomhid/fu-wac-module-bluetooth.h
Normal file
25
plugins/wacomhid/fu-wac-module-bluetooth.h
Normal 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 */
|
112
plugins/wacomhid/fu-wac-module-touch.c
Normal file
112
plugins/wacomhid/fu-wac-module-touch.c
Normal 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;
|
||||
}
|
25
plugins/wacomhid/fu-wac-module-touch.h
Normal file
25
plugins/wacomhid/fu-wac-module-touch.h
Normal 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 */
|
358
plugins/wacomhid/fu-wac-module.c
Normal file
358
plugins/wacomhid/fu-wac-module.c
Normal 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;
|
||||
}
|
42
plugins/wacomhid/fu-wac-module.h
Normal file
42
plugins/wacomhid/fu-wac-module.h
Normal 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 */
|
65
plugins/wacomhid/meson.build
Normal file
65
plugins/wacomhid/meson.build
Normal 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
|
4
plugins/wacomhid/wac-intuos.quirk
Normal file
4
plugins/wacomhid/wac-intuos.quirk
Normal file
@ -0,0 +1,4 @@
|
||||
[FuWacDevice]
|
||||
|
||||
# Intuos Pro
|
||||
USB\VID_056A&PID_0378=use-runtime-version
|
Loading…
Reference in New Issue
Block a user