mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-15 18:36:32 +00:00
258 lines
7.7 KiB
C
258 lines
7.7 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2016-2017 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 <string.h>
|
|
|
|
#include "lu-common.h"
|
|
#include "lu-device-runtime.h"
|
|
#include "lu-hidpp.h"
|
|
|
|
struct _LuDeviceRuntime
|
|
{
|
|
LuDevice parent_instance;
|
|
};
|
|
|
|
G_DEFINE_TYPE (LuDeviceRuntime, lu_device_runtime, LU_TYPE_DEVICE)
|
|
|
|
#ifndef HAVE_GUDEV_232
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref)
|
|
#endif
|
|
|
|
static gboolean
|
|
lu_device_runtime_enable_notifications (LuDevice *device, GError **error)
|
|
{
|
|
g_autoptr(LuHidppMsg) msg = lu_hidpp_msg_new ();
|
|
msg->report_id = HIDPP_REPORT_ID_SHORT;
|
|
msg->device_id = lu_device_get_hidpp_id (device);
|
|
msg->sub_id = HIDPP_SUBID_SET_REGISTER;
|
|
msg->function_id = HIDPP_REGISTER_HIDPP_NOTIFICATIONS;
|
|
msg->data[0] = 0x00;
|
|
msg->data[1] = 0x05; /* Wireless + SoftwarePresent */
|
|
msg->data[2] = 0x00;
|
|
return lu_device_hidpp_transfer (device, msg, error);
|
|
}
|
|
|
|
static gboolean
|
|
lu_device_runtime_open (LuDevice *device, GError **error)
|
|
{
|
|
GUdevDevice *udev_device = lu_device_get_udev_device (device);
|
|
GUsbDevice *usb_device = lu_device_get_usb_device (device);
|
|
guint16 release = 0xffff;
|
|
guint8 config[10];
|
|
guint8 version_bl_major = 0;
|
|
g_autofree gchar *devid1 = NULL;
|
|
g_autofree gchar *version_bl = NULL;
|
|
g_autofree gchar *version_fw = NULL;
|
|
|
|
/* add a generic GUID */
|
|
devid1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
|
(guint) LU_DEVICE_VID,
|
|
(guint) LU_DEVICE_PID_RUNTIME);
|
|
fu_device_add_guid (FU_DEVICE (device), devid1);
|
|
|
|
/* generate bootloadder-specific GUID */
|
|
if (usb_device != NULL) {
|
|
release = g_usb_device_get_release (usb_device);
|
|
} else if (udev_device != NULL) {
|
|
g_autoptr(GUdevDevice) udev_parent = NULL;
|
|
udev_parent = g_udev_device_get_parent_with_subsystem (udev_device,
|
|
"usb", "usb_device");
|
|
if (udev_parent != NULL) {
|
|
const gchar *release_str;
|
|
release_str = g_udev_device_get_property (udev_parent, "ID_REVISION");
|
|
if (release_str != NULL)
|
|
release = g_ascii_strtoull (release_str, NULL, 16);
|
|
}
|
|
}
|
|
if (release != 0xffff) {
|
|
g_autofree gchar *devid2 = NULL;
|
|
switch (release &= 0xff00) {
|
|
case 0x1200:
|
|
/* Nordic */
|
|
devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
|
(guint) LU_DEVICE_VID,
|
|
(guint) LU_DEVICE_PID_BOOTLOADER_NORDIC);
|
|
fu_device_add_guid (FU_DEVICE (device), devid2);
|
|
version_bl_major = 0x01;
|
|
break;
|
|
case 0x2400:
|
|
/* Texas */
|
|
devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
|
(guint) LU_DEVICE_VID,
|
|
(guint) LU_DEVICE_PID_BOOTLOADER_TEXAS);
|
|
fu_device_add_guid (FU_DEVICE (device), devid2);
|
|
version_bl_major = 0x03;
|
|
break;
|
|
default:
|
|
g_warning ("bootloader release %04x invalid", release);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* read all 10 bytes of the version register */
|
|
memset (config, 0x00, sizeof (config));
|
|
for (guint i = 0x01; i < 0x05; i++) {
|
|
g_autoptr(LuHidppMsg) msg = lu_hidpp_msg_new ();
|
|
|
|
/* workaround a bug in the 12.01 firmware, which fails with
|
|
* INVALID_VALUE when reading MCU1_HW_VERSION */
|
|
if (i == 0x03)
|
|
continue;
|
|
|
|
msg->report_id = HIDPP_REPORT_ID_SHORT;
|
|
msg->device_id = lu_device_get_hidpp_id (device);
|
|
msg->sub_id = HIDPP_SUBID_GET_REGISTER;
|
|
msg->function_id = HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION;
|
|
msg->data[0] = i;
|
|
if (!lu_device_hidpp_transfer (device, msg, error)) {
|
|
g_prefix_error (error, "failed to read device config: ");
|
|
return FALSE;
|
|
}
|
|
memcpy (config + (i * 2), msg->data + 1, 2);
|
|
}
|
|
|
|
/* get firmware version */
|
|
version_fw = lu_format_version ("RQR",
|
|
config[2],
|
|
config[3],
|
|
(guint16) config[4] << 8 |
|
|
config[5]);
|
|
fu_device_set_version (FU_DEVICE (device), version_fw);
|
|
|
|
/* get bootloader version */
|
|
if (version_bl_major > 0) {
|
|
version_bl = lu_format_version ("BOT",
|
|
version_bl_major,
|
|
config[8],
|
|
config[9]);
|
|
fu_device_set_version_bootloader (FU_DEVICE (device), version_bl);
|
|
|
|
/* is the dongle expecting signed firmware */
|
|
if ((version_bl_major == 0x01 && config[8] >= 0x04) ||
|
|
(version_bl_major == 0x03 && config[8] >= 0x02)) {
|
|
lu_device_add_flag (device, LU_DEVICE_FLAG_REQUIRES_SIGNED_FIRMWARE);
|
|
}
|
|
}
|
|
|
|
/* enable HID++ notifications */
|
|
if (!lu_device_runtime_enable_notifications (device, error)) {
|
|
g_prefix_error (error, "failed to enable notifications: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* this only exists with the original HID++1.0 version */
|
|
lu_device_set_hidpp_version (device, 1.f);
|
|
|
|
/* we can flash this */
|
|
fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
|
|
/* only the bootloader can do the update */
|
|
fu_device_set_name (FU_DEVICE (device), "Unifying Receiver");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
lu_device_runtime_detach (LuDevice *device, GError **error)
|
|
{
|
|
g_autoptr(LuHidppMsg) msg = lu_hidpp_msg_new ();
|
|
msg->report_id = HIDPP_REPORT_ID_SHORT;
|
|
msg->device_id = lu_device_get_hidpp_id (device);
|
|
msg->sub_id = HIDPP_SUBID_SET_REGISTER;
|
|
msg->function_id = HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE;
|
|
msg->data[0] = 'I';
|
|
msg->data[1] = 'C';
|
|
msg->data[2] = 'P';
|
|
msg->flags = LU_HIDPP_MSG_FLAG_LONGER_TIMEOUT;
|
|
if (!lu_device_hidpp_send (device, msg, LU_DEVICE_TIMEOUT_MS, error)) {
|
|
g_prefix_error (error, "failed to detach to bootloader: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
lu_device_runtime_poll (LuDevice *device, GError **error)
|
|
{
|
|
const guint timeout = 1; /* ms */
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(LuHidppMsg) msg = lu_hidpp_msg_new ();
|
|
|
|
/* is there any pending data to read */
|
|
if (!lu_device_hidpp_receive (device, msg, timeout, &error_local)) {
|
|
if (g_error_matches (error_local,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_TIMED_OUT)) {
|
|
return TRUE;
|
|
}
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to get pending read: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* HID++1.0 error */
|
|
if (!lu_hidpp_msg_is_error (msg, error))
|
|
return FALSE;
|
|
|
|
/* unifying receiver notification */
|
|
if (msg->report_id == HIDPP_REPORT_ID_SHORT) {
|
|
switch (msg->sub_id) {
|
|
case HIDPP_SUBID_DEVICE_CONNECTION:
|
|
case HIDPP_SUBID_DEVICE_DISCONNECTION:
|
|
case HIDPP_SUBID_DEVICE_LOCKING_CHANGED:
|
|
g_debug ("device connection event, do something");
|
|
break;
|
|
case HIDPP_SUBID_LINK_QUALITY:
|
|
g_debug ("ignoring link quality message");
|
|
break;
|
|
case HIDPP_SUBID_ERROR_MSG:
|
|
g_debug ("ignoring link quality message");
|
|
break;
|
|
default:
|
|
g_debug ("unknown SubID %02x", msg->sub_id);
|
|
break;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
lu_device_runtime_class_init (LuDeviceRuntimeClass *klass)
|
|
{
|
|
LuDeviceClass *klass_device = LU_DEVICE_CLASS (klass);
|
|
klass_device->open = lu_device_runtime_open;
|
|
klass_device->poll = lu_device_runtime_poll;
|
|
klass_device->detach = lu_device_runtime_detach;
|
|
}
|
|
|
|
static void
|
|
lu_device_runtime_init (LuDeviceRuntime *device)
|
|
{
|
|
/* FIXME: we need something better */
|
|
fu_device_add_icon (FU_DEVICE (device), "preferences-desktop-keyboard");
|
|
fu_device_set_summary (FU_DEVICE (device), "A miniaturised USB wireless receiver");
|
|
}
|