mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-28 21:18:24 +00:00

Devices may want to support more than one protocol, and for some devices (e.g. Unifying peripherals stuck in bootloader mode) you might not even be able to query for the correct protocol anyway.
379 lines
11 KiB
C
379 lines
11 KiB
C
/*
|
|
* 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"
|
|
|
|
#define FU_WAC_MODULE_STATUS_OK 0
|
|
#define FU_WAC_MODULE_STATUS_BUSY 1
|
|
#define FU_WAC_MODULE_STATUS_ERR_CRC 2
|
|
#define FU_WAC_MODULE_STATUS_ERR_CMD 3
|
|
#define FU_WAC_MODULE_STATUS_ERR_HW_ACCESS_FAIL 4
|
|
#define FU_WAC_MODULE_STATUS_ERR_FLASH_NO_SUPPORT 5
|
|
#define FU_WAC_MODULE_STATUS_ERR_MODE_WRONG 6
|
|
#define FU_WAC_MODULE_STATUS_ERR_MPU_NO_SUPPORT 7
|
|
#define FU_WAC_MODULE_STATUS_ERR_VERSION_NO_SUPPORT 8
|
|
#define FU_WAC_MODULE_STATUS_ERR_ERASE 9
|
|
#define FU_WAC_MODULE_STATUS_ERR_WRITE 10
|
|
#define FU_WAC_MODULE_STATUS_ERR_EXIT 11
|
|
#define FU_WAC_MODULE_STATUS_ERR 12
|
|
#define FU_WAC_MODULE_STATUS_ERR_INVALID_OP 13
|
|
#define FU_WAC_MODULE_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_MODULE_STATUS_OK)
|
|
return "ok";
|
|
if (status == FU_WAC_MODULE_STATUS_BUSY)
|
|
return "busy";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_CRC)
|
|
return "err-crc";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_CMD)
|
|
return "err-cmd";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_HW_ACCESS_FAIL)
|
|
return "err-hw-access-fail";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_FLASH_NO_SUPPORT)
|
|
return "err-flash-no-support";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_MODE_WRONG)
|
|
return "err-mode-wrong";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_MPU_NO_SUPPORT)
|
|
return "err-mpu-no-support";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_VERSION_NO_SUPPORT)
|
|
return "erro-version-no-support";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_ERASE)
|
|
return "err-erase";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_WRITE)
|
|
return "err-write";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_EXIT)
|
|
return "err-exit";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR)
|
|
return "err-err";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_INVALID_OP)
|
|
return "err-invalid-op";
|
|
if (status == FU_WAC_MODULE_STATUS_ERR_WRONG_IMAGE)
|
|
return "err-wrong-image";
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
fu_wac_module_to_string (FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuWacModule *self = FU_WAC_MODULE (device);
|
|
FuWacModulePrivate *priv = GET_PRIVATE (self);
|
|
fu_common_string_append_kv (str, idt, "FwType",
|
|
fu_wac_module_fw_type_to_string (priv->fw_type));
|
|
fu_common_string_append_kv (str, idt, "Status",
|
|
fu_wac_module_status_to_string (priv->status));
|
|
fu_common_string_append_kv (str, idt, "Command",
|
|
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_HID_DEVICE_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 and status */
|
|
if (priv->command != buf[2] || priv->status != buf[3]) {
|
|
priv->command = buf[2];
|
|
priv->status = buf[3];
|
|
if (g_getenv ("FWUPD_WACOM_VERBOSE") != NULL) {
|
|
g_debug ("command: %s, status: %s",
|
|
fu_wac_module_command_to_string (priv->command),
|
|
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 };
|
|
|
|
/* sanity check */
|
|
g_return_val_if_fail (FU_IS_WAC_MODULE (self), FALSE);
|
|
g_return_val_if_fail (FU_IS_WAC_DEVICE (parent_device), FALSE);
|
|
|
|
/* verify the size of the blob */
|
|
if (blob != NULL) {
|
|
data = g_bytes_get_data (blob, &len);
|
|
if (!fu_memcpy_safe (buf, sizeof(buf), 0x03, /* dst */
|
|
data, len, 0x0, /* src */
|
|
len, error)) {
|
|
g_prefix_error (error, "Submodule blob larger than buffer: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* 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, len + 3,
|
|
FU_HID_DEVICE_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_MODULE_STATUS_BUSY) {
|
|
g_usleep (10000); /* 10ms */
|
|
continue;
|
|
}
|
|
if (priv->status == FU_WAC_MODULE_STATUS_OK)
|
|
break;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"Failed to SetFeature: %s",
|
|
fu_wac_module_status_to_string (priv->status));
|
|
return FALSE;
|
|
}
|
|
|
|
/* too many retries */
|
|
if (priv->status != FU_WAC_MODULE_STATUS_OK) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"Timed out after %u loops with status %s",
|
|
busy_poll_loops,
|
|
fu_wac_module_status_to_string (priv->status));
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_wac_module_cleanup (FuDevice *device, FwupdInstallFlags flags, GError **error)
|
|
{
|
|
FuDevice *parent = fu_device_get_parent (device);
|
|
g_autoptr(FuDeviceLocker) locker = fu_device_locker_new (parent, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
return fu_device_cleanup (parent, flags, error);
|
|
}
|
|
|
|
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)
|
|
{
|
|
fu_device_add_protocol (FU_DEVICE (self), "com.wacom.usb");
|
|
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PAIR);
|
|
fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
|
}
|
|
|
|
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 *vendor_id = NULL;
|
|
|
|
/* set vendor ID */
|
|
vendor_id = g_strdup_printf ("USB:0x%04X", g_usb_device_get_vid (priv->usb_device));
|
|
fu_device_add_vendor_id (FU_DEVICE (self), vendor_id);
|
|
|
|
/* set USB physical and logical IDs */
|
|
fu_device_set_physical_id (FU_DEVICE (self),
|
|
g_usb_device_get_platform_id (priv->usb_device));
|
|
fu_device_set_logical_id (FU_DEVICE (self),
|
|
fu_wac_module_fw_type_to_string (priv->fw_type));
|
|
|
|
/* 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_instance_id (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;
|
|
klass_device->cleanup = fu_wac_module_cleanup;
|
|
}
|