fwupd/plugins/wacom-usb/fu-wac-module.c
Richard Hughes b3f9841924 Support more than one protocol for a given device
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.
2021-03-01 16:14:36 +00:00

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;
}