mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-29 11:35:32 +00:00
863 lines
23 KiB
C
863 lines
23 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 "unifying-dongle.h"
|
|
|
|
#define UNIFYING_REQUEST_SET_REPORT 0x09
|
|
#define UNIFYING_DONGLE_TIMEOUT_MS 2500
|
|
#define UNIFYING_DONGLE_EP1 0x81
|
|
#define UNIFYING_DONGLE_EP3 0x83
|
|
|
|
/* HID++ constants */
|
|
#define UNIFYING_HIDPP_DEVICE_INDEX_RECEIVER 0xff
|
|
#define UNIFYING_HIDPP_REPORT_ID_SHORT 0x10
|
|
#define UNIFYING_HIDPP_REPORT_ID_LONG 0x11
|
|
#define UNIFYING_HIDPP_REPORT_ID_MEDIUM 0x20
|
|
#define UNIFYING_HIDPP_SET_REGISTER_REQ 0x80
|
|
#define UNIFYING_HIDPP_GET_REGISTER_REQ 0x81
|
|
|
|
#define UNIFYING_HIDPP_REGISTER_ADDR_UNKNOWN_F0 0xf0
|
|
#define UNIFYING_HIDPP_REGISTER_ADDR_VERSION 0xf1
|
|
|
|
#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
|
|
{
|
|
UnifyingDongleKind kind;
|
|
GUsbDevice *usb_device;
|
|
gchar *guid;
|
|
gchar *version_firmware;
|
|
gchar *version_bootloader;
|
|
} UnifyingDonglePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (UnifyingDongle, unifying_dongle, G_TYPE_OBJECT)
|
|
|
|
#define GET_PRIVATE(o) (unifying_dongle_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);
|
|
}
|
|
|
|
UnifyingDongleKind
|
|
unifying_dongle_kind_from_string (const gchar *kind)
|
|
{
|
|
if (g_strcmp0 (kind, "runtime") == 0)
|
|
return UNIFYING_DONGLE_KIND_RUNTIME;
|
|
if (g_strcmp0 (kind, "bootloader-nordic") == 0)
|
|
return UNIFYING_DONGLE_KIND_BOOTLOADER_NORDIC;
|
|
if (g_strcmp0 (kind, "bootloader-texas") == 0)
|
|
return UNIFYING_DONGLE_KIND_BOOTLOADER_TEXAS;
|
|
return UNIFYING_DONGLE_KIND_UNKNOWN;
|
|
}
|
|
|
|
const gchar *
|
|
unifying_dongle_kind_to_string (UnifyingDongleKind kind)
|
|
{
|
|
if (kind == UNIFYING_DONGLE_KIND_RUNTIME)
|
|
return "runtime";
|
|
if (kind == UNIFYING_DONGLE_KIND_BOOTLOADER_NORDIC)
|
|
return "bootloader-nordic";
|
|
if (kind == UNIFYING_DONGLE_KIND_BOOTLOADER_TEXAS)
|
|
return "bootloader-texas";
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
unifying_dongle_finalize (GObject *object)
|
|
{
|
|
UnifyingDongle *dongle = UNIFYING_DONGLE (object);
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
|
|
g_free (priv->guid);
|
|
g_free (priv->version_firmware);
|
|
g_free (priv->version_bootloader);
|
|
if (priv->usb_device != NULL)
|
|
g_object_unref (priv->usb_device);
|
|
|
|
G_OBJECT_CLASS (unifying_dongle_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
unifying_dongle_init (UnifyingDongle *dongle)
|
|
{
|
|
}
|
|
|
|
static void
|
|
unifying_dongle_class_init (UnifyingDongleClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = unifying_dongle_finalize;
|
|
}
|
|
|
|
UnifyingDongleKind
|
|
unifying_dongle_get_kind (UnifyingDongle *dongle)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
return priv->kind;
|
|
}
|
|
|
|
GUsbDevice *
|
|
unifying_dongle_get_usb_device (UnifyingDongle *dongle)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
return priv->usb_device;
|
|
}
|
|
|
|
static gboolean
|
|
unifying_dongle_send_command (UnifyingDongle *dongle,
|
|
guint16 value,
|
|
guint16 idx,
|
|
const guint8 *data_in,
|
|
gsize data_in_length,
|
|
guint8 *data_out,
|
|
gsize data_out_length,
|
|
guint8 endpoint,
|
|
GError **error)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
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_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, data_in_length,
|
|
&actual_length,
|
|
UNIFYING_DONGLE_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,
|
|
UNIFYING_DONGLE_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 ("dongle->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,
|
|
"dongle 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
|
|
unifying_dongle_detach (UnifyingDongle *dongle, GError **error)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
const guint8 cmd[] = { UNIFYING_HIDPP_REPORT_ID_SHORT,
|
|
UNIFYING_HIDPP_DEVICE_INDEX_RECEIVER,
|
|
UNIFYING_HIDPP_SET_REGISTER_REQ,
|
|
UNIFYING_HIDPP_REGISTER_ADDR_UNKNOWN_F0,
|
|
0x49, 0x43, 0x50 /* value */};
|
|
|
|
g_return_val_if_fail (UNIFYING_IS_DONGLE (dongle), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* check kind */
|
|
if (priv->kind != UNIFYING_DONGLE_KIND_RUNTIME) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"dongle 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,
|
|
UNIFYING_DONGLE_TIMEOUT_MS,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error (error, "failed to detach to bootloader: ");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
unifying_dongle_attach (UnifyingDongle *dongle, GError **error)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
guint8 cmd[32];
|
|
|
|
g_return_val_if_fail (UNIFYING_IS_DONGLE (dongle), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* check kind */
|
|
if (priv->kind == UNIFYING_DONGLE_KIND_RUNTIME) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"dongle 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 (!unifying_dongle_send_command (dongle,
|
|
0x0200, 0x0000,
|
|
cmd, sizeof (cmd),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_EP1,
|
|
error)) {
|
|
g_prefix_error (error, "failed to attach back to runtime: ");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
unifying_dongle_reset (UnifyingDongle *dongle, GError **error)
|
|
{
|
|
const guint8 cmd[] = { UNIFYING_HIDPP_REPORT_ID_SHORT,
|
|
UNIFYING_HIDPP_DEVICE_INDEX_RECEIVER,
|
|
UNIFYING_HIDPP_GET_REGISTER_REQ,
|
|
UNIFYING_HIDPP_REGISTER_ADDR_VERSION,
|
|
0x00, 0x00, 0x00 };
|
|
if (!unifying_dongle_send_command (dongle, 0x0210, 0x0002,
|
|
cmd, sizeof (cmd),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_EP3,
|
|
error)) {
|
|
g_prefix_error (error, "failed to reset");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
unifying_dongle_open (UnifyingDongle *dongle, GError **error)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
guint i;
|
|
guint num_interfaces = 0x1;
|
|
g_autofree gchar *devid = NULL;
|
|
|
|
g_return_val_if_fail (UNIFYING_IS_DONGLE (dongle), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* emulated */
|
|
if (priv->usb_device == NULL) {
|
|
priv->version_firmware = g_strdup ("001.002.00003");
|
|
priv->version_bootloader = g_strdup ("BL.004.005");
|
|
return TRUE;
|
|
}
|
|
|
|
/* generate GUID -- in runtime mode we have to use the release */
|
|
if (priv->kind == UNIFYING_DONGLE_KIND_RUNTIME) {
|
|
guint16 release = g_usb_device_get_release (priv->usb_device);
|
|
release &= 0xff00;
|
|
devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X&REV_%04X",
|
|
g_usb_device_get_vid (priv->usb_device),
|
|
g_usb_device_get_pid (priv->usb_device),
|
|
release);
|
|
} else {
|
|
devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
|
g_usb_device_get_vid (priv->usb_device),
|
|
g_usb_device_get_pid (priv->usb_device));
|
|
}
|
|
g_debug ("Using %s for GUID", devid);
|
|
priv->guid = as_utils_guid_from_string (devid);
|
|
|
|
/* open device */
|
|
g_debug ("opening unifying device");
|
|
if (!g_usb_device_open (priv->usb_device, error))
|
|
return FALSE;
|
|
if (priv->kind == UNIFYING_DONGLE_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 == UNIFYING_DONGLE_KIND_RUNTIME) {
|
|
guint8 config[10];
|
|
guint8 buf[15];
|
|
guint8 cmd[] = { UNIFYING_HIDPP_REPORT_ID_SHORT,
|
|
UNIFYING_HIDPP_DEVICE_INDEX_RECEIVER,
|
|
UNIFYING_HIDPP_GET_REGISTER_REQ,
|
|
UNIFYING_HIDPP_REGISTER_ADDR_VERSION,
|
|
0x00, 0x00, 0x00 };
|
|
|
|
g_debug ("clearing existing data");
|
|
if (!unifying_dongle_reset (dongle, 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 (!unifying_dongle_send_command (dongle, 0x0210, 0x0002,
|
|
cmd, sizeof (cmd),
|
|
buf, sizeof (buf),
|
|
UNIFYING_DONGLE_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... */
|
|
priv->version_firmware = g_strdup_printf ("%03x.%03x.%02x%03x",
|
|
config[2],
|
|
config[3],
|
|
config[4],
|
|
config[5]);
|
|
priv->version_bootloader = g_strdup_printf ("BL.%03x.%03x",
|
|
config[8],
|
|
config[9]);
|
|
} else {
|
|
priv->version_firmware = g_strdup ("000.000.00000");
|
|
priv->version_bootloader = g_strdup ("BL.000.000");
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
unifying_dongle_close (UnifyingDongle *dongle, GError **error)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
guint i;
|
|
guint num_interfaces = 0x1;
|
|
|
|
g_return_val_if_fail (UNIFYING_IS_DONGLE (dongle), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* emulated */
|
|
if (priv->usb_device == NULL)
|
|
return TRUE;
|
|
|
|
if (priv->kind == UNIFYING_DONGLE_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;
|
|
}
|
|
|
|
const gchar *
|
|
unifying_dongle_get_guid (UnifyingDongle *dongle)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
g_return_val_if_fail (UNIFYING_IS_DONGLE (dongle), FALSE);
|
|
return priv->guid;
|
|
}
|
|
|
|
const gchar *
|
|
unifying_dongle_get_version_fw (UnifyingDongle *dongle)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
g_return_val_if_fail (UNIFYING_IS_DONGLE (dongle), FALSE);
|
|
return priv->version_firmware;
|
|
}
|
|
|
|
const gchar *
|
|
unifying_dongle_get_version_bl (UnifyingDongle *dongle)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
g_return_val_if_fail (UNIFYING_IS_DONGLE (dongle), FALSE);
|
|
return priv->version_bootloader;
|
|
}
|
|
|
|
static guint8
|
|
read_uint8 (const gchar *str)
|
|
{
|
|
guint64 tmp;
|
|
guint8 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;
|
|
} UnifyingDonglePayload;
|
|
|
|
static GPtrArray *
|
|
unifying_dongle_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++) {
|
|
UnifyingDonglePayload *payload;
|
|
guint idx = 0x00;
|
|
|
|
/* skip empty lines */
|
|
tmp = lines[i];
|
|
if (strlen (tmp) < 5)
|
|
continue;
|
|
|
|
payload = g_new0 (UnifyingDonglePayload, 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
|
|
unifying_dongle_nordic_write_firmware (UnifyingDongle *dongle,
|
|
GBytes *fw,
|
|
GFileProgressCallback progress_cb,
|
|
gpointer progress_data,
|
|
GError **error)
|
|
{
|
|
const UnifyingDonglePayload *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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf,
|
|
sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf, sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_EP1,
|
|
error)) {
|
|
g_prefix_error (error, "failed to erase fw @0x%02x: ", i);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* transfer payload */
|
|
payloads = unifying_dongle_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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf, sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf, sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf, sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_EP1,
|
|
error)) {
|
|
g_prefix_error (error, "failed to set completed: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success! */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
unifying_dongle_texas_write_address (UnifyingDongle *dongle,
|
|
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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf, sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf, sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_EP1,
|
|
error)) {
|
|
g_prefix_error (error, "failed to clear address @0x%04x: ", addr);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
unifying_dongle_texas_write_firmware (UnifyingDongle *dongle,
|
|
GBytes *fw,
|
|
GFileProgressCallback progress_cb,
|
|
gpointer progress_data,
|
|
GError **error)
|
|
{
|
|
const UnifyingDonglePayload *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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf,
|
|
sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_EP1,
|
|
error)) {
|
|
g_prefix_error (error, "failed to init fw transfer: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* transfer payload */
|
|
payloads = unifying_dongle_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 (!unifying_dongle_texas_write_address (dongle,
|
|
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 (!unifying_dongle_send_command (dongle, 0x0200, 0x0000,
|
|
buf, sizeof (buf),
|
|
NULL, 0,
|
|
UNIFYING_DONGLE_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 (!unifying_dongle_texas_write_address (dongle,
|
|
last_set_addr + 0x80,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* success! */
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
unifying_dongle_write_firmware (UnifyingDongle *dongle,
|
|
GBytes *fw,
|
|
GFileProgressCallback progress_cb,
|
|
gpointer progress_data,
|
|
GError **error)
|
|
{
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
|
|
g_return_val_if_fail (UNIFYING_IS_DONGLE (dongle), 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 == UNIFYING_DONGLE_KIND_BOOTLOADER_NORDIC) {
|
|
return unifying_dongle_nordic_write_firmware (dongle,
|
|
fw,
|
|
progress_cb,
|
|
progress_data,
|
|
error);
|
|
}
|
|
|
|
/* texas style */
|
|
if (priv->kind == UNIFYING_DONGLE_KIND_BOOTLOADER_TEXAS) {
|
|
return unifying_dongle_texas_write_firmware (dongle,
|
|
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;
|
|
}
|
|
|
|
typedef struct {
|
|
guint16 vid;
|
|
guint16 pid;
|
|
} UnifyingVidPid;
|
|
|
|
UnifyingDongle *
|
|
unifying_dongle_new (GUsbDevice *usb_device)
|
|
{
|
|
struct {
|
|
guint16 vid;
|
|
guint16 pid;
|
|
UnifyingDongleKind kind;
|
|
} vid_pids[] = {
|
|
{ 0x046d, 0xc52b, UNIFYING_DONGLE_KIND_RUNTIME},
|
|
{ 0x046d, 0xaaaa, UNIFYING_DONGLE_KIND_BOOTLOADER_NORDIC},
|
|
{ 0x046d, 0xaaac, UNIFYING_DONGLE_KIND_BOOTLOADER_TEXAS},
|
|
{ 0x0000, 0x0000, 0 }
|
|
};
|
|
for (guint i = 0; vid_pids[i].vid != 0x0000; i++) {
|
|
if (g_usb_device_get_vid (usb_device) == vid_pids[i].vid &&
|
|
g_usb_device_get_pid (usb_device) == vid_pids[i].pid) {
|
|
UnifyingDongle *dongle = g_object_new (UNIFYING_TYPE_DONGLE, NULL);
|
|
UnifyingDonglePrivate *priv = GET_PRIVATE (dongle);
|
|
priv->usb_device = g_object_ref (usb_device);
|
|
priv->kind = vid_pids[i].kind;
|
|
return dongle;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
UnifyingDongle *
|
|
unifying_dongle_emulated_new (UnifyingDongleKind kind)
|
|
{
|
|
UnifyingDongle *dongle;
|
|
UnifyingDonglePrivate *priv;
|
|
dongle = g_object_new (UNIFYING_TYPE_DONGLE, NULL);
|
|
priv = GET_PRIVATE (dongle);
|
|
priv->kind = kind;
|
|
return dongle;
|
|
}
|