mirror of
				https://git.proxmox.com/git/fwupd
				synced 2025-10-31 20:24:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			941 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			941 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 | |
|  *
 | |
|  * Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
 | |
|  *
 | |
|  * Licensed under the GNU Lesser General Public License Version 2.1
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Lesser General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2.1 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public
 | |
|  * License along with this library; if not, write to the Free Software
 | |
|  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #include <appstream-glib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include "fu-device-unifying.h"
 | |
| 
 | |
| #define UNIFYING_REQUEST_SET_REPORT		0x09
 | |
| #define FU_DEVICE_UNIFYING_TIMEOUT_MS		2500
 | |
| #define FU_DEVICE_UNIFYING_EP1			0x81
 | |
| #define FU_DEVICE_UNIFYING_EP3			0x83
 | |
| 
 | |
| /*
 | |
|  * Based on the HID++ documentation provided by Nestor Lopez Casado at:
 | |
|  *   https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing
 | |
|  */
 | |
| #define HIDPP_RECEIVER_IDX			0xFF
 | |
| #define HIDPP_WIRED_DEVICE_IDX			0x00
 | |
| 
 | |
| #define HIDPP_REPORT_ID_SHORT			0x10
 | |
| #define HIDPP_REPORT_ID_LONG			0x11
 | |
| #define HIDPP_REPORT_ID_MEDIUM			0x20
 | |
| 
 | |
| #define HIDPP_SHORT_MESSAGE_LENGTH		7
 | |
| #define HIDPP_LONG_MESSAGE_LENGTH		20
 | |
| 
 | |
| #define HIDPP_SET_REGISTER_REQ			0x80
 | |
| #define HIDPP_SET_REGISTER_RSP			0x80
 | |
| #define HIDPP_GET_REGISTER_REQ			0x81
 | |
| #define HIDPP_GET_REGISTER_RSP			0x81
 | |
| #define HIDPP_SET_LONG_REGISTER_REQ		0x82
 | |
| #define HIDPP_SET_LONG_REGISTER_RSP		0x82
 | |
| #define HIDPP_GET_LONG_REGISTER_REQ		0x83
 | |
| #define HIDPP_GET_LONG_REGISTER_RSP		0x83
 | |
| #define HIDPP_ERROR_MSG				0x8F
 | |
| 
 | |
| #define HIDPP_ERR_SUCCESS			0x00
 | |
| #define HIDPP_ERR_INVALID_SUBID			0x01
 | |
| #define HIDPP_ERR_INVALID_ADDRESS		0x02
 | |
| #define HIDPP_ERR_INVALID_VALUE			0x03
 | |
| #define HIDPP_ERR_CONNECT_FAIL			0x04
 | |
| #define HIDPP_ERR_TOO_MANY_DEVICES		0x05
 | |
| #define HIDPP_ERR_ALREADY_EXISTS		0x06
 | |
| #define HIDPP_ERR_BUSY				0x07
 | |
| #define HIDPP_ERR_UNKNOWN_DEVICE		0x08
 | |
| #define HIDPP_ERR_RESOURCE_ERROR		0x09
 | |
| #define HIDPP_ERR_REQUEST_UNAVAILABLE		0x0A
 | |
| #define HIDPP_ERR_INVALID_PARAM_VALUE		0x0B
 | |
| #define HIDPP_ERR_WRONG_PIN_CODE		0x0C
 | |
| 
 | |
| /*
 | |
|  * HID++ 1.0 registers
 | |
|  */
 | |
| 
 | |
| #define HIDPP_REGISTER_HIDPP_NOTIFICATIONS			0x00
 | |
| #define HIDPP_REGISTER_ENABLE_INDIVIDUAL_FEATURES		0x01
 | |
| #define HIDPP_REGISTER_BATTERY_STATUS				0x07
 | |
| #define HIDPP_REGISTER_BATTERY_MILEAGE				0x0D
 | |
| #define HIDPP_REGISTER_PROFILE					0x0F
 | |
| #define HIDPP_REGISTER_LED_STATUS				0x51
 | |
| #define HIDPP_REGISTER_LED_INTENSITY				0x54
 | |
| #define HIDPP_REGISTER_LED_COLOR				0x57
 | |
| #define HIDPP_REGISTER_OPTICAL_SENSOR_SETTINGS			0x61
 | |
| #define HIDPP_REGISTER_CURRENT_RESOLUTION			0x63
 | |
| #define HIDPP_REGISTER_USB_REFRESH_RATE				0x64
 | |
| #define HIDPP_REGISTER_GENERIC_MEMORY_MANAGEMENT		0xA0
 | |
| #define HIDPP_REGISTER_HOT_CONTROL				0xA1
 | |
| #define HIDPP_REGISTER_READ_MEMORY				0xA2
 | |
| #define HIDPP_REGISTER_DEVICE_CONNECTION_DISCONNECTION		0xB2
 | |
| #define HIDPP_REGISTER_PAIRING_INFORMATION			0xB5
 | |
| #define HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE		0xF0
 | |
| #define HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION		0xF1
 | |
| 
 | |
| /*
 | |
|  * HID++ 2.0 pages
 | |
|  */
 | |
| 
 | |
| #define HIDPP_PAGE_ROOT						0x0000
 | |
| #define HIDPP_PAGE_FEATURE_SET					0x0001
 | |
| #define HIDPP_PAGE_DEVICE_INFO					0x0003
 | |
| #define HIDPP_PAGE_BATTERY_LEVEL_STATUS				0x1000
 | |
| #define HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS			0x1b00
 | |
| #define HIDPP_PAGE_SPECIAL_KEYS_BUTTONS				0x1b04
 | |
| #define HIDPP_PAGE_MOUSE_POINTER_BASIC				0x2200
 | |
| #define HIDPP_PAGE_ADJUSTABLE_DPI				0x2201
 | |
| #define HIDPP_PAGE_ADJUSTABLE_REPORT_RATE			0x8060
 | |
| #define HIDPP_PAGE_COLOR_LED_EFFECTS				0x8070
 | |
| #define HIDPP_PAGE_ONBOARD_PROFILES				0x8100
 | |
| #define HIDPP_PAGE_MOUSE_BUTTON_SPY				0x8110
 | |
| 
 | |
| #define UNIFYING_FIRMWARE_SIZE				0x7000
 | |
| 
 | |
| typedef enum {
 | |
| 	UNIFYING_BOOTLOADER_CMD_PAYLOAD			= 0x20,
 | |
| 	UNIFYING_BOOTLOADER_CMD_ERASE_PAGE		= 0x30,
 | |
| 	UNIFYING_BOOTLOADER_CMD_REBOOT			= 0x70,
 | |
| 	UNIFYING_BOOTLOADER_CMD_INIT_TRANSFER		= 0x80,
 | |
| 	UNIFYING_BOOTLOADER_CMD_WRITE_PAGE		= 0xc0,
 | |
| 	UNIFYING_BOOTLOADER_CMD_SET_ADDRESS		= 0xd0,
 | |
| 	UNIFYING_BOOTLOADER_CMD_LAST
 | |
| } UnifyingBootloaderCmd;
 | |
| 
 | |
| typedef struct
 | |
| {
 | |
| 	FuDeviceUnifyingKind	 kind;
 | |
| 	GUsbDevice		*usb_device;
 | |
| } FuDeviceUnifyingPrivate;
 | |
| 
 | |
| G_DEFINE_TYPE_WITH_PRIVATE (FuDeviceUnifying, fu_device_unifying, FU_TYPE_DEVICE)
 | |
| 
 | |
| #define GET_PRIVATE(o) (fu_device_unifying_get_instance_private (o))
 | |
| 
 | |
| static void
 | |
| fu_unifying_dump_raw (const gchar *title, const guint8 *data, gsize len)
 | |
| {
 | |
| 	g_autoptr(GString) str = g_string_new (NULL);
 | |
| 	if (len == 0)
 | |
| 		return;
 | |
| 	g_string_append_printf (str, "%s:", title);
 | |
| 	for (gsize i = strlen (title); i < 16; i++)
 | |
| 		g_string_append (str, " ");
 | |
| 	for (gsize i = 0; i < len; i++) {
 | |
| 		g_string_append_printf (str, "%02x ", data[i]);
 | |
| 		if (i > 0 && i % 32 == 0)
 | |
| 			g_string_append (str, "\n");
 | |
| 	}
 | |
| 	g_debug ("%s", str->str);
 | |
| }
 | |
| 
 | |
| FuDeviceUnifyingKind
 | |
| fu_device_unifying_kind_from_string (const gchar *kind)
 | |
| {
 | |
| 	if (g_strcmp0 (kind, "runtime") == 0)
 | |
| 		return FU_DEVICE_UNIFYING_KIND_RUNTIME;
 | |
| 	if (g_strcmp0 (kind, "bootloader-nordic") == 0)
 | |
| 		return FU_DEVICE_UNIFYING_KIND_BOOTLOADER_NORDIC;
 | |
| 	if (g_strcmp0 (kind, "bootloader-texas") == 0)
 | |
| 		return FU_DEVICE_UNIFYING_KIND_BOOTLOADER_TEXAS;
 | |
| 	return FU_DEVICE_UNIFYING_KIND_UNKNOWN;
 | |
| }
 | |
| 
 | |
| const gchar *
 | |
| fu_device_unifying_kind_to_string (FuDeviceUnifyingKind kind)
 | |
| {
 | |
| 	if (kind == FU_DEVICE_UNIFYING_KIND_RUNTIME)
 | |
| 		return "runtime";
 | |
| 	if (kind == FU_DEVICE_UNIFYING_KIND_BOOTLOADER_NORDIC)
 | |
| 		return "bootloader-nordic";
 | |
| 	if (kind == FU_DEVICE_UNIFYING_KIND_BOOTLOADER_TEXAS)
 | |
| 		return "bootloader-texas";
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static void
 | |
| fu_device_unifying_finalize (GObject *object)
 | |
| {
 | |
| 	FuDeviceUnifying *device = FU_DEVICE_UNIFYING (object);
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 
 | |
| 	if (priv->usb_device != NULL)
 | |
| 		g_object_unref (priv->usb_device);
 | |
| 
 | |
| 	G_OBJECT_CLASS (fu_device_unifying_parent_class)->finalize (object);
 | |
| }
 | |
| 
 | |
| static void
 | |
| fu_device_unifying_init (FuDeviceUnifying *device)
 | |
| {
 | |
| }
 | |
| 
 | |
| static void
 | |
| fu_device_unifying_class_init (FuDeviceUnifyingClass *klass)
 | |
| {
 | |
| 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 | |
| 	object_class->finalize = fu_device_unifying_finalize;
 | |
| }
 | |
| 
 | |
| FuDeviceUnifyingKind
 | |
| fu_device_unifying_get_kind (FuDeviceUnifying *device)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 	return priv->kind;
 | |
| }
 | |
| 
 | |
| GUsbDevice *
 | |
| fu_device_unifying_get_usb_device (FuDeviceUnifying *device)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 	return priv->usb_device;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| fu_device_unifying_send_command (FuDeviceUnifying *device,
 | |
| 				 guint16 value,
 | |
| 				 guint16 idx,
 | |
| 				 const guint8 *data_in,
 | |
| 				 gsize data_in_length,
 | |
| 				 guint8 *data_out,
 | |
| 				 gsize data_out_length,
 | |
| 				 guint8 endpoint,
 | |
| 				 GError **error)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 	gsize actual_length = 0;
 | |
| 	guint8 buf[32];
 | |
| 
 | |
| 	/* send request */
 | |
| 	fu_unifying_dump_raw ("host->device", data_in, data_in_length);
 | |
| 	if (priv->usb_device != NULL) {
 | |
| 		g_autofree guint8 *data_in_buf = g_memdup (data_in, data_in_length);
 | |
| 		if (!g_usb_device_control_transfer (priv->usb_device,
 | |
| 						    G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
 | |
| 						    G_USB_DEVICE_REQUEST_TYPE_CLASS,
 | |
| 						    G_USB_DEVICE_RECIPIENT_INTERFACE,
 | |
| 						    UNIFYING_REQUEST_SET_REPORT,
 | |
| 						    value, idx,
 | |
| 						    data_in_buf, data_in_length,
 | |
| 						    &actual_length,
 | |
| 						    FU_DEVICE_UNIFYING_TIMEOUT_MS,
 | |
| 						    NULL,
 | |
| 						    error)) {
 | |
| 			g_prefix_error (error, "failed to send data: ");
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* get response */
 | |
| 	memset (buf, 0x00, sizeof (buf));
 | |
| 	if (priv->usb_device != NULL) {
 | |
| 		if (!g_usb_device_interrupt_transfer (priv->usb_device,
 | |
| 						      endpoint,
 | |
| 						      buf,
 | |
| 						      sizeof (buf),
 | |
| 						      &actual_length,
 | |
| 						      FU_DEVICE_UNIFYING_TIMEOUT_MS,
 | |
| 						      NULL,
 | |
| 						      error)) {
 | |
| 			g_prefix_error (error, "failed to get data: ");
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 	} else {
 | |
| 		/* emulated */
 | |
| 		actual_length = data_out_length;
 | |
| 	}
 | |
| 	fu_unifying_dump_raw ("device->host", buf, actual_length);
 | |
| 
 | |
| 	/* check sizes */
 | |
| 	if (data_out != NULL) {
 | |
| 		if (actual_length > data_out_length) {
 | |
| 			g_set_error (error,
 | |
| 				     G_IO_ERROR,
 | |
| 				     G_IO_ERROR_FAILED,
 | |
| 				     "device output %" G_GSIZE_FORMAT " bytes, "
 | |
| 				     "buffer size only %" G_GSIZE_FORMAT,
 | |
| 				     actual_length, data_out_length);
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 		memcpy (data_out, buf, actual_length);
 | |
| 	}
 | |
| 
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| fu_device_unifying_detach (FuDeviceUnifying *device, GError **error)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 	guint8 cmd[] = { HIDPP_REPORT_ID_SHORT,
 | |
| 			 HIDPP_RECEIVER_IDX,
 | |
| 			 HIDPP_SET_REGISTER_REQ,
 | |
| 			 HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE,
 | |
| 			 0x49, 0x43, 0x50 /* value */};
 | |
| 
 | |
| 	g_return_val_if_fail (FU_IS_DEVICE_UNIFYING (device), FALSE);
 | |
| 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 | |
| 
 | |
| 	/* check kind */
 | |
| 	if (priv->kind != FU_DEVICE_UNIFYING_KIND_RUNTIME) {
 | |
| 		g_set_error_literal (error,
 | |
| 				     G_IO_ERROR,
 | |
| 				     G_IO_ERROR_FAILED,
 | |
| 				     "device is not in runtime state");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* detach */
 | |
| 	fu_unifying_dump_raw ("host->device", cmd, sizeof (cmd));
 | |
| 	if (!g_usb_device_control_transfer (priv->usb_device,
 | |
| 					    G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
 | |
| 					    G_USB_DEVICE_REQUEST_TYPE_CLASS,
 | |
| 					    G_USB_DEVICE_RECIPIENT_INTERFACE,
 | |
| 					    UNIFYING_REQUEST_SET_REPORT,
 | |
| 					    0x0210, 0x0002,
 | |
| 					    cmd, sizeof (cmd),
 | |
| 					    NULL,
 | |
| 					    FU_DEVICE_UNIFYING_TIMEOUT_MS,
 | |
| 					    NULL,
 | |
| 					    error)) {
 | |
| 		g_prefix_error (error, "failed to detach to bootloader: ");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| fu_device_unifying_attach (FuDeviceUnifying *device, GError **error)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 	guint8 cmd[32];
 | |
| 
 | |
| 	g_return_val_if_fail (FU_IS_DEVICE_UNIFYING (device), FALSE);
 | |
| 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 | |
| 
 | |
| 	/* check kind */
 | |
| 	if (priv->kind == FU_DEVICE_UNIFYING_KIND_RUNTIME) {
 | |
| 		g_set_error_literal (error,
 | |
| 				     G_IO_ERROR,
 | |
| 				     G_IO_ERROR_FAILED,
 | |
| 				     "device is not in bootloader state");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* attach */
 | |
| 	memset (cmd, 0x0, sizeof(cmd));
 | |
| 	cmd[0x0] = UNIFYING_BOOTLOADER_CMD_REBOOT;
 | |
| 	fu_unifying_dump_raw ("host->device", cmd, sizeof (cmd));
 | |
| 	if (!fu_device_unifying_send_command (device,
 | |
| 					      0x0200, 0x0000,
 | |
| 					      cmd, sizeof (cmd),
 | |
| 					      NULL, 0,
 | |
| 					      FU_DEVICE_UNIFYING_EP1,
 | |
| 					      error)) {
 | |
| 		g_prefix_error (error, "failed to attach back to runtime: ");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| fu_device_unifying_reset (FuDeviceUnifying *device, GError **error)
 | |
| {
 | |
| 	const guint8 cmd[] =  { HIDPP_REPORT_ID_SHORT,
 | |
| 				HIDPP_RECEIVER_IDX,
 | |
| 				HIDPP_GET_REGISTER_REQ,
 | |
| 				HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION,
 | |
| 				0x00, 0x00, 0x00 };
 | |
| 	if (!fu_device_unifying_send_command (device, 0x0210, 0x0002,
 | |
| 					      cmd, sizeof (cmd),
 | |
| 					      NULL, 0,
 | |
| 					      FU_DEVICE_UNIFYING_EP3,
 | |
| 					      error)) {
 | |
| 		g_prefix_error (error, "failed to reset");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| fu_device_unifying_open (FuDeviceUnifying *device, GError **error)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 	guint i;
 | |
| 	guint num_interfaces = 0x1;
 | |
| 	g_autofree gchar *devid = NULL;
 | |
| 
 | |
| 	g_return_val_if_fail (FU_IS_DEVICE_UNIFYING (device), FALSE);
 | |
| 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 | |
| 
 | |
| 	/* emulated */
 | |
| 	if (priv->usb_device == NULL) {
 | |
| 		fu_device_set_version (FU_DEVICE (device), "001.002.00003");
 | |
| 		fu_device_set_version_bootloader (FU_DEVICE (device), "BL.004.005");
 | |
| 		return TRUE;
 | |
| 	}
 | |
| 
 | |
| 	/* open device */
 | |
| 	g_debug ("opening unifying device");
 | |
| 	if (!g_usb_device_open (priv->usb_device, error))
 | |
| 		return FALSE;
 | |
| 	if (priv->kind == FU_DEVICE_UNIFYING_KIND_RUNTIME)
 | |
| 		num_interfaces = 0x03;
 | |
| 	for (i = 0; i < num_interfaces; i++) {
 | |
| 		g_debug ("claiming interface 0x%02x", i);
 | |
| 		if (!g_usb_device_claim_interface (priv->usb_device, i,
 | |
| 						   G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
 | |
| 						   error)) {
 | |
| 			g_prefix_error (error, "Failed to claim 0x%02x: ", i);
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* get config */
 | |
| 	if (priv->kind == FU_DEVICE_UNIFYING_KIND_RUNTIME) {
 | |
| 		guint8 config[10];
 | |
| 		guint8 buf[15];
 | |
| 		guint8 cmd[] =  { HIDPP_REPORT_ID_SHORT,
 | |
| 				  HIDPP_RECEIVER_IDX,
 | |
| 				  HIDPP_GET_REGISTER_REQ,
 | |
| 				  HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION,
 | |
| 				  0x00, 0x00, 0x00 };
 | |
| 		g_autofree gchar *version_fw = NULL;
 | |
| 		g_autofree gchar *version_bl = NULL;
 | |
| 
 | |
| 		g_debug ("clearing existing data");
 | |
| 		if (!fu_device_unifying_reset (device, error))
 | |
| 			return FALSE;
 | |
| 
 | |
| 		/* read all 10 bytes of the version register */
 | |
| 		memset (config, 0x00, sizeof (config));
 | |
| 		for (i = 0; i < 0x05; i++) {
 | |
| 			cmd[4] = i;
 | |
| 			memset (buf, 0x00, sizeof (buf));
 | |
| 			if (!fu_device_unifying_send_command (device, 0x0210, 0x0002,
 | |
| 							      cmd, sizeof (cmd),
 | |
| 							      buf, sizeof (buf),
 | |
| 							      FU_DEVICE_UNIFYING_EP3,
 | |
| 							      error)) {
 | |
| 				g_prefix_error (error, "failed to read config 0x%02x: ", i);
 | |
| 				return FALSE;
 | |
| 			}
 | |
| 			memcpy (config + (i * 2), buf + 5, 2);
 | |
| 		}
 | |
| 
 | |
| 		/* logitech sends base 16 and then pads as if base 10... */
 | |
| 		version_fw = g_strdup_printf ("%03x.%03x.%02x%03x",
 | |
| 					      config[2],
 | |
| 					      config[3],
 | |
| 					      config[4],
 | |
| 					      config[5]);
 | |
| 		version_bl = g_strdup_printf ("BL.%03x.%03x",
 | |
| 					      config[8],
 | |
| 					      config[9]);
 | |
| 		fu_device_set_version (FU_DEVICE (device), version_fw);
 | |
| 		fu_device_set_version_bootloader (FU_DEVICE (device), version_bl);
 | |
| 	} else {
 | |
| 		fu_device_set_version (FU_DEVICE (device), "000.000.00000");
 | |
| 		fu_device_set_version_bootloader (FU_DEVICE (device), "BL.000.000");
 | |
| 	}
 | |
| 
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| fu_device_unifying_close (FuDeviceUnifying *device, GError **error)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 	guint i;
 | |
| 	guint num_interfaces = 0x1;
 | |
| 
 | |
| 	g_return_val_if_fail (FU_IS_DEVICE_UNIFYING (device), FALSE);
 | |
| 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 | |
| 
 | |
| 	/* emulated */
 | |
| 	if (priv->usb_device == NULL)
 | |
| 		return TRUE;
 | |
| 
 | |
| 	if (priv->kind == FU_DEVICE_UNIFYING_KIND_RUNTIME)
 | |
| 		num_interfaces = 0x03;
 | |
| 	for (i = 0; i < num_interfaces; i++) {
 | |
| 		g_debug ("releasing interface 0x%02x", i);
 | |
| 		if (!g_usb_device_release_interface (priv->usb_device, i,
 | |
| 						     G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
 | |
| 						     error)) {
 | |
| 			g_prefix_error (error, "Failed to release 0x%02x: ", i);
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	g_debug ("closing device");
 | |
| 	if (!g_usb_device_close (priv->usb_device, error))
 | |
| 		return FALSE;
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| static guint8
 | |
| read_uint8 (const gchar *str)
 | |
| {
 | |
| 	guint64 tmp;
 | |
| 	gchar buf[3] = { 0x0, 0x0, 0x0 };
 | |
| 	memcpy (buf, str, 2);
 | |
| 	tmp = g_ascii_strtoull (buf, NULL, 16);
 | |
| 	return tmp;
 | |
| }
 | |
| 
 | |
| typedef struct {
 | |
| 	guint8		 op;
 | |
| 	guint16		 addr;
 | |
| 	guint8		 data[32];
 | |
| 	gsize		 data_len;
 | |
| } FuDeviceUnifyingPayload;
 | |
| 
 | |
| static GPtrArray *
 | |
| fu_device_unifying_generate_payloads (GBytes *fw)
 | |
| {
 | |
| 	GPtrArray *payloads;
 | |
| 	const gchar *tmp;
 | |
| 	g_auto(GStrv) lines = NULL;
 | |
| 
 | |
| 	payloads = g_ptr_array_new_with_free_func (g_free);
 | |
| 	tmp = g_bytes_get_data (fw, NULL);
 | |
| 	lines = g_strsplit_set (tmp, "\n\r", -1);
 | |
| 	for (guint i = 0; lines[i] != NULL; i++) {
 | |
| 		FuDeviceUnifyingPayload *payload;
 | |
| 		guint idx = 0x00;
 | |
| 
 | |
| 		/* skip empty lines */
 | |
| 		tmp = lines[i];
 | |
| 		if (strlen (tmp) < 5)
 | |
| 			continue;
 | |
| 
 | |
| 		payload = g_new0 (FuDeviceUnifyingPayload, 1);
 | |
| 		payload->op = read_uint8 (tmp + 0x01);
 | |
| 		payload->addr = ((guint16) read_uint8 (tmp + 0x03)) << 8;
 | |
| 		payload->addr |= read_uint8 (tmp + 0x05);
 | |
| 
 | |
| 		/* read the data, but skip the checksum byte */
 | |
| 		for (guint j = 0x09; tmp[j + 2] != '\0'; j += 2)
 | |
| 			payload->data[idx++] = read_uint8 (tmp + j);
 | |
| 		payload->data_len = idx;
 | |
| 		g_ptr_array_add (payloads, payload);
 | |
| 	}
 | |
| 	return payloads;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| fu_device_unifying_nordic_write_firmware (FuDeviceUnifying *device,
 | |
| 					GBytes *fw,
 | |
| 					GFileProgressCallback progress_cb,
 | |
| 					gpointer progress_data,
 | |
| 					GError **error)
 | |
| {
 | |
| 	const FuDeviceUnifyingPayload *payload;
 | |
| 	g_autoptr(GPtrArray) payloads = NULL;
 | |
| 	guint8 buf[32];
 | |
| 
 | |
| 	/* init firmware transfer */
 | |
| 	memset (buf, 0x0, sizeof(buf));
 | |
| 	buf[0x00] = UNIFYING_BOOTLOADER_CMD_INIT_TRANSFER;
 | |
| 	if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 					      buf,
 | |
| 					      sizeof (buf),
 | |
| 					      NULL, 0,
 | |
| 					      FU_DEVICE_UNIFYING_EP1,
 | |
| 					      error)) {
 | |
| 		g_prefix_error (error, "failed to init fw transfer: ");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* erase firmware pages up to the bootloader */
 | |
| 	memset (buf, 0x0, sizeof(buf));
 | |
| 	for (guint i = 0; i < UNIFYING_FIRMWARE_SIZE; i += 0x200) {
 | |
| 		buf[0x00] = UNIFYING_BOOTLOADER_CMD_ERASE_PAGE;
 | |
| 		buf[0x01] = i << 8;
 | |
| 		buf[0x02] = 0x00;
 | |
| 		buf[0x03] = 0x01;
 | |
| 		if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 						      buf, sizeof (buf),
 | |
| 						      NULL, 0,
 | |
| 						      FU_DEVICE_UNIFYING_EP1,
 | |
| 						      error)) {
 | |
| 			g_prefix_error (error, "failed to erase fw @0x%02x: ", i);
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* transfer payload */
 | |
| 	payloads = fu_device_unifying_generate_payloads (fw);
 | |
| 	for (guint i = 1; i < payloads->len; i++) {
 | |
| 		payload = g_ptr_array_index (payloads, i);
 | |
| 
 | |
| 		/* skip the bootloader */
 | |
| 		if (payload->addr > UNIFYING_FIRMWARE_SIZE)
 | |
| 			break;
 | |
| 
 | |
| 		/* build packet */
 | |
| 		memset (buf, 0x00, sizeof (buf));
 | |
| 		buf[0x00] = UNIFYING_BOOTLOADER_CMD_PAYLOAD;
 | |
| 		buf[0x01] = payload->addr >> 8;
 | |
| 		buf[0x02] = payload->addr & 0xff;
 | |
| 		buf[0x03] = payload->op;
 | |
| 		memcpy (buf + 0x04, payload->data, payload->data_len);
 | |
| 		if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 						      buf, sizeof (buf),
 | |
| 						      NULL, 0,
 | |
| 						      FU_DEVICE_UNIFYING_EP1,
 | |
| 						      error)) {
 | |
| 			g_prefix_error (error, "failed to transfer fw @0x%02x: ", i);
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 		if (progress_cb != NULL) {
 | |
| 			progress_cb ((goffset) i * 32,
 | |
| 				     (goffset) payloads->len * 32,
 | |
| 				     progress_data);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* send the first managed packet last */
 | |
| 	payload = g_ptr_array_index (payloads, 0);
 | |
| 	memset (buf, 0x00, sizeof (buf));
 | |
| 	buf[0x00] = UNIFYING_BOOTLOADER_CMD_PAYLOAD;
 | |
| 	buf[0x01] = (payload->addr + 1) >> 8;
 | |
| 	buf[0x02] = (payload->addr + 1) & 0xff;
 | |
| 	buf[0x03] = payload->op - 1;
 | |
| 	memcpy (buf + 0x04, payload->data + 1, payload->data_len - 1);
 | |
| 	if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 					      buf, sizeof (buf),
 | |
| 					      NULL, 0,
 | |
| 					      FU_DEVICE_UNIFYING_EP1,
 | |
| 					      error)) {
 | |
| 		g_prefix_error (error, "failed to transfer fw start: ");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* mark as complete */
 | |
| 	if (progress_cb != NULL) {
 | |
| 		progress_cb ((goffset) payloads->len * 32,
 | |
| 			     (goffset) payloads->len * 32,
 | |
| 			     progress_data);
 | |
| 	}
 | |
| 
 | |
| 	/* completed upload */
 | |
| 	memset (buf, 0x0, sizeof(buf));
 | |
| 	buf[0x00] = UNIFYING_BOOTLOADER_CMD_PAYLOAD;
 | |
| 	buf[0x01] = 0x00;
 | |
| 	buf[0x02] = 0x00;
 | |
| 	buf[0x03] = 0x01;
 | |
| 	buf[0x04] = 0x02;
 | |
| 	if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 					      buf, sizeof (buf),
 | |
| 					      NULL, 0,
 | |
| 					      FU_DEVICE_UNIFYING_EP1,
 | |
| 					      error)) {
 | |
| 		g_prefix_error (error, "failed to set completed: ");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* success! */
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| fu_device_unifying_texas_write_address (FuDeviceUnifying *device,
 | |
| 					guint16 addr,
 | |
| 					GError **error)
 | |
| {
 | |
| 	guint8 buf[32];
 | |
| 	memset (buf, 0x00, sizeof (buf));
 | |
| 	if (addr == 0x0400) {
 | |
| 		buf[0x00] = UNIFYING_BOOTLOADER_CMD_SET_ADDRESS;
 | |
| 		buf[0x01] = 0x00;
 | |
| 		buf[0x02] = 0x00;
 | |
| 		buf[0x03] = 0x01;
 | |
| 		buf[0x04] = 0x00;
 | |
| 	} else {
 | |
| 		guint16 addr_tmp = addr - 0x80;
 | |
| 		buf[0x00] = UNIFYING_BOOTLOADER_CMD_SET_ADDRESS;
 | |
| 		buf[0x01] = addr_tmp >> 8;
 | |
| 		buf[0x02] = addr_tmp & 0xff;
 | |
| 		buf[0x03] = 0x01;
 | |
| 		buf[0x04] = 0x01;
 | |
| 	}
 | |
| 	if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 					      buf, sizeof (buf),
 | |
| 					      NULL, 0,
 | |
| 					      FU_DEVICE_UNIFYING_EP1,
 | |
| 					      error)) {
 | |
| 		g_prefix_error (error, "failed to set address @0x%04x: ", addr);
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 	memset (buf, 0x00, sizeof (buf));
 | |
| 	if (addr == 0x6c00) {
 | |
| 		buf[0x00] = UNIFYING_BOOTLOADER_CMD_SET_ADDRESS;
 | |
| 		buf[0x01] = 0x00;
 | |
| 		buf[0x02] = 0x00;
 | |
| 		buf[0x03] = 0x01;
 | |
| 		buf[0x04] = 0x03;
 | |
| 	} else {
 | |
| 		buf[0x00] = UNIFYING_BOOTLOADER_CMD_SET_ADDRESS;
 | |
| 		buf[0x01] = 0x00;
 | |
| 		buf[0x02] = 0x00;
 | |
| 		buf[0x03] = 0x01;
 | |
| 		buf[0x04] = 0x02;
 | |
| 	}
 | |
| 	if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 					      buf, sizeof (buf),
 | |
| 					      NULL, 0,
 | |
| 					      FU_DEVICE_UNIFYING_EP1,
 | |
| 					      error)) {
 | |
| 		g_prefix_error (error, "failed to clear address @0x%04x: ", addr);
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| fu_device_unifying_texas_write_firmware (FuDeviceUnifying *device,
 | |
| 					 GBytes *fw,
 | |
| 					 GFileProgressCallback progress_cb,
 | |
| 					 gpointer progress_data,
 | |
| 					 GError **error)
 | |
| {
 | |
| 	const FuDeviceUnifyingPayload *payload;
 | |
| 	guint16 last_set_addr = 0xffff;
 | |
| 	guint8 buf[32];
 | |
| 	g_autoptr(GPtrArray) payloads = NULL;
 | |
| 
 | |
| 	/* init firmware transfer */
 | |
| 	memset (buf, 0x0, sizeof(buf));
 | |
| 	buf[0x00] = UNIFYING_BOOTLOADER_CMD_INIT_TRANSFER;
 | |
| 	if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 					      buf,
 | |
| 					      sizeof (buf),
 | |
| 					      NULL, 0,
 | |
| 					      FU_DEVICE_UNIFYING_EP1,
 | |
| 					      error)) {
 | |
| 		g_prefix_error (error, "failed to init fw transfer: ");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* transfer payload */
 | |
| 	payloads = fu_device_unifying_generate_payloads (fw);
 | |
| 	for (guint i = 0; i < payloads->len; i++) {
 | |
| 		payload = g_ptr_array_index (payloads, i);
 | |
| 
 | |
| 		/* skip the bootloader */
 | |
| 		if (payload->addr >= UNIFYING_FIRMWARE_SIZE)
 | |
| 			break;
 | |
| 
 | |
| 		/* skip the header */
 | |
| 		if (payload->addr < 0x0400)
 | |
| 			continue;
 | |
| 
 | |
| 		/* skip record ??? */
 | |
| 		if (payload->op == 0x02)
 | |
| 			continue;
 | |
| 
 | |
| 		/* set address */
 | |
| 		if (last_set_addr == 0xffff || payload->addr - last_set_addr >= 0x80) {
 | |
| 			if (!fu_device_unifying_texas_write_address (device,
 | |
| 								     payload->addr,
 | |
| 								     error))
 | |
| 				return FALSE;
 | |
| 			last_set_addr = payload->addr;
 | |
| 		}
 | |
| 
 | |
| 		/* build packet */
 | |
| 		memset (buf, 0x00, sizeof (buf));
 | |
| 		buf[0x00] = UNIFYING_BOOTLOADER_CMD_WRITE_PAGE;
 | |
| 		buf[0x01] = 0x00;
 | |
| 		buf[0x02] = payload->addr & 0x7f;
 | |
| 		buf[0x03] = payload->op;
 | |
| 		memcpy (buf + 0x04, payload->data, payload->data_len);
 | |
| 		if (!fu_device_unifying_send_command (device, 0x0200, 0x0000,
 | |
| 						      buf, sizeof (buf),
 | |
| 						      NULL, 0,
 | |
| 						      FU_DEVICE_UNIFYING_EP1,
 | |
| 						      error)) {
 | |
| 			g_prefix_error (error, "failed to transfer fw @0x%02x: ", i);
 | |
| 			return FALSE;
 | |
| 		}
 | |
| 		if (progress_cb != NULL) {
 | |
| 			progress_cb ((goffset) i * 32,
 | |
| 				     (goffset) payloads->len * 32,
 | |
| 				     progress_data);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* finish page */
 | |
| 	if (!fu_device_unifying_texas_write_address (device,
 | |
| 						     last_set_addr + 0x80,
 | |
| 						     error))
 | |
| 		return FALSE;
 | |
| 
 | |
| 	/* success! */
 | |
| 	return TRUE;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| fu_device_unifying_write_firmware (FuDeviceUnifying *device,
 | |
| 				   GBytes *fw,
 | |
| 				   GFileProgressCallback progress_cb,
 | |
| 				   gpointer progress_data,
 | |
| 				   GError **error)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 
 | |
| 	g_return_val_if_fail (FU_IS_DEVICE_UNIFYING (device), FALSE);
 | |
| 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 | |
| 
 | |
| 	/* corrupt */
 | |
| 	if (g_bytes_get_size (fw) < 0x4000) {
 | |
| 		g_set_error_literal (error,
 | |
| 				     G_IO_ERROR,
 | |
| 				     G_IO_ERROR_INVALID_DATA,
 | |
| 				     "firmware is too small");
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| 	/* nordic style */
 | |
| 	if (priv->kind == FU_DEVICE_UNIFYING_KIND_BOOTLOADER_NORDIC) {
 | |
| 		return fu_device_unifying_nordic_write_firmware (device,
 | |
| 								 fw,
 | |
| 								 progress_cb,
 | |
| 								 progress_data,
 | |
| 								 error);
 | |
| 	}
 | |
| 
 | |
| 	/* texas style */
 | |
| 	if (priv->kind == FU_DEVICE_UNIFYING_KIND_BOOTLOADER_TEXAS) {
 | |
| 		return fu_device_unifying_texas_write_firmware (device,
 | |
| 								fw,
 | |
| 								progress_cb,
 | |
| 								progress_data,
 | |
| 								error);
 | |
| 	}
 | |
| 
 | |
| 	/* eeek */
 | |
| 	g_set_error_literal (error,
 | |
| 			     G_IO_ERROR,
 | |
| 			     G_IO_ERROR_INVALID_DATA,
 | |
| 			     "bootloader is not supported");
 | |
| 	return FALSE;
 | |
| }
 | |
| 
 | |
| /* now with kind and usb_device set */
 | |
| static void
 | |
| fu_device_unifying_init_real (FuDeviceUnifying *device)
 | |
| {
 | |
| 	FuDeviceUnifyingPrivate *priv = GET_PRIVATE (device);
 | |
| 	guint16 pid_for_guid = 0xffff;
 | |
| 	g_autofree gchar *devid = NULL;
 | |
| 	g_autofree gchar *name = NULL;
 | |
| 
 | |
| 	/* allowed, but requires manual bootloader step */
 | |
| 	fu_device_add_flag (FU_DEVICE (device),
 | |
| 			    FWUPD_DEVICE_FLAG_ALLOW_ONLINE);
 | |
| 
 | |
| 	/* set default vendor */
 | |
| 	fu_device_set_vendor (FU_DEVICE (device), "Logitech");
 | |
| 
 | |
| 	/* generate name */
 | |
| 	name = g_strdup_printf ("Unifying [%s]",
 | |
| 				fu_device_unifying_kind_to_string (priv->kind));
 | |
| 	fu_device_set_name (FU_DEVICE (device), name);
 | |
| 
 | |
| 	/* generate GUID -- in runtime mode we have to use the release */
 | |
| 	if (priv->kind == FU_DEVICE_UNIFYING_KIND_RUNTIME) {
 | |
| 		guint16 release = g_usb_device_get_release (priv->usb_device);
 | |
| 		switch (release &= 0xff00) {
 | |
| 		case 0x1200:
 | |
| 			/* Nordic */
 | |
| 			pid_for_guid = 0xaaaa;
 | |
| 			break;
 | |
| 		case 0x2400:
 | |
| 			/* Texas */
 | |
| 			pid_for_guid = 0xaaac;
 | |
| 			break;
 | |
| 		default:
 | |
| 			g_warning ("bootloader release %04x invalid", release);
 | |
| 			break;
 | |
| 		}
 | |
| 	} else {
 | |
| 		pid_for_guid = g_usb_device_get_pid (priv->usb_device);
 | |
| 	}
 | |
| 	devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
 | |
| 				 g_usb_device_get_vid (priv->usb_device),
 | |
| 				 pid_for_guid);
 | |
| 
 | |
| 	fu_device_add_guid (FU_DEVICE (device), devid);
 | |
| 
 | |
| 	/* only the bootloader can do the update */
 | |
| 	if (priv->kind == FU_DEVICE_UNIFYING_KIND_RUNTIME) {
 | |
| 		fu_device_add_flag (FU_DEVICE (device),
 | |
| 				    FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| FuDeviceUnifying *
 | |
| fu_device_unifying_new (GUsbDevice *usb_device)
 | |
| {
 | |
| 	FuDeviceUnifying *device;
 | |
| 	FuDeviceUnifyingPrivate *priv;
 | |
| 	struct {
 | |
| 		guint16			 vid;
 | |
| 		guint16			 pid;
 | |
| 		FuDeviceUnifyingKind	 kind;
 | |
| 	} vidpids[] = {
 | |
| 		{ 0x046d, 0xc52b, FU_DEVICE_UNIFYING_KIND_RUNTIME},
 | |
| 		{ 0x046d, 0xaaaa, FU_DEVICE_UNIFYING_KIND_BOOTLOADER_NORDIC},
 | |
| 		{ 0x046d, 0xaaac, FU_DEVICE_UNIFYING_KIND_BOOTLOADER_TEXAS},
 | |
| 		{ 0x0000, 0x0000, 0 }
 | |
| 	};
 | |
| 	for (guint i = 0; vidpids[i].vid != 0x0000; i++) {
 | |
| 		if (g_usb_device_get_vid (usb_device) != vidpids[i].vid)
 | |
| 			continue;
 | |
| 		if (g_usb_device_get_pid (usb_device) != vidpids[i].pid)
 | |
| 			continue;
 | |
| 		device = g_object_new (FU_TYPE_DEVICE_UNIFYING, NULL);
 | |
| 		priv = GET_PRIVATE (device);
 | |
| 		priv->kind = vidpids[i].kind;
 | |
| 		priv->usb_device = g_object_ref (usb_device);
 | |
| 		fu_device_unifying_init_real (device);
 | |
| 		return device;
 | |
| 	}
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| FuDeviceUnifying *
 | |
| fu_device_unifying_emulated_new (FuDeviceUnifyingKind kind)
 | |
| {
 | |
| 	FuDeviceUnifying *device;
 | |
| 	FuDeviceUnifyingPrivate *priv;
 | |
| 	device = g_object_new (FU_TYPE_DEVICE_UNIFYING, NULL);
 | |
| 	priv = GET_PRIVATE (device);
 | |
| 	priv->kind = kind;
 | |
| 	return device;
 | |
| }
 | 
