fwupd/plugins/system76-launch/fu-system76-launch-device.c
Richard Hughes 19abf996c7 Allow the daemon to request interactive action from the end user
The "return error and hope the client resubmits the firmware again"
pattern is clunky. There are two plugins doing this now, and about to
be one more.

This adds FwupdRequest which provides a structured way of asking the
user to perform an action, e.g. to replug the device or to press a
special key or button.

This replaces much of the UpdateMessage and UpdateImage API although
it is still used internally. Clients capable of processing the new
DeviceRequest signal should add REQUESTS to their feature flags.

Also, this allows us go back to the old meaning of _NEEDS_BOOTLOADER,
which was "needs rebooting into a bootloader mode" rather than the
slightly weird "user needs to do something and resubmit request".
2021-07-14 17:03:50 +01:00

219 lines
6.2 KiB
C

/*
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2021 Jeremy Soller <jeremy@system76.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-system76-launch-device.h"
#define SYSTEM76_LAUNCH_CMD_VERSION 3
#define SYSTEM76_LAUNCH_CMD_RESET 6
#define SYSTEM76_LAUNCH_TIMEOUT 1000
struct _FuSystem76LaunchDevice {
FuUsbDevice parent_instance;
};
G_DEFINE_TYPE (FuSystem76LaunchDevice, fu_system76_launch_device, FU_TYPE_USB_DEVICE)
static gboolean
fu_system76_launch_device_command (FuDevice *device, guint8 *data, gsize len, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
const guint8 ep_in = 0x82;
const guint8 ep_out = 0x03;
gsize actual_len = 0;
/* send command */
if (!g_usb_device_interrupt_transfer (usb_device,
ep_out,
data,
len,
&actual_len,
SYSTEM76_LAUNCH_TIMEOUT,
NULL,
error)) {
g_prefix_error (error, "failed to send command: ");
return FALSE;
}
if (actual_len < len) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"command truncated: sent %" G_GSIZE_FORMAT " bytes",
actual_len);
return FALSE;
}
/* receive response */
if (!g_usb_device_interrupt_transfer (usb_device,
ep_in,
data,
len,
&actual_len,
SYSTEM76_LAUNCH_TIMEOUT,
NULL,
error)) {
g_prefix_error (error, "failed to read response: ");
return FALSE;
}
if (actual_len < len) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"response truncated: received %" G_GSIZE_FORMAT " bytes",
actual_len);
return FALSE;
}
return TRUE;
}
static gboolean
fu_system76_launch_device_setup (FuDevice *device, GError **error)
{
guint8 data[32] = { 0 };
g_autofree gchar *version = NULL;
/* FuUsbDevice->setup */
if (!FU_DEVICE_CLASS (fu_system76_launch_device_parent_class)->setup (device, error))
return FALSE;
/* execute version command */
data[0] = SYSTEM76_LAUNCH_CMD_VERSION;
if (!fu_system76_launch_device_command (device, data, sizeof(data), error)) {
g_prefix_error (error, "failed to execute version command: ");
return FALSE;
}
version = g_strdup_printf ("%s", &data[2]);
fu_device_set_version (device, version);
return TRUE;
}
static gboolean
fu_system76_launch_device_reset (FuDevice *device, guint8 *rc, GError **error)
{
guint8 data[32] = { SYSTEM76_LAUNCH_CMD_RESET, 0 };
/* execute reset command */
if (!fu_system76_launch_device_command (device, data, sizeof(data), error)) {
g_prefix_error (error, "failed to execute reset command: ");
return FALSE;
}
*rc = data[1];
return TRUE;
}
static gboolean
fu_system76_launch_device_detach (FuDevice *device, GError **error)
{
guint8 rc = 0x0;
g_autoptr(FwupdRequest) request = fwupd_request_new ();
g_autoptr(GTimer) timer = g_timer_new ();
/* prompt for unlock if reset was blocked */
if (!fu_system76_launch_device_reset (device, &rc, error))
return FALSE;
/* unlikely, but already unlocked */
if (rc == 0)
return TRUE;
/* generate a message if not already set */
if (fu_device_get_update_message (device) == NULL) {
g_autofree gchar *msg = NULL;
msg = g_strdup_printf ("To ensure you have physical access, %s needs to be manually unlocked. "
"Please press Fn+Esc to unlock and re-run the update.",
fu_device_get_name (device));
fu_device_set_update_message (device, msg);
}
/* the user has to do something */
fwupd_request_set_kind (request, FWUPD_REQUEST_KIND_IMMEDIATE);
fwupd_request_set_id (request, FWPUD_REQUEST_ID_PRESS_UNLOCK);
fwupd_request_set_message (request, fu_device_get_update_message (device));
fu_device_emit_request (device, request);
/* poll for the user-unlock */
fu_device_set_progress (device, 0);
do {
g_usleep (G_USEC_PER_SEC);
if (!fu_system76_launch_device_reset (device, &rc, error))
return FALSE;
} while (rc != 0 &&
g_timer_elapsed (timer, NULL) * 1000.f < FU_DEVICE_REMOVE_DELAY_USER_REPLUG);
if (rc != 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NEEDS_USER_ACTION,
fu_device_get_update_message (device));
return FALSE;
}
return TRUE;
}
static gboolean
fu_system76_launch_device_open (FuDevice *device, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
const guint8 iface_idx = 0x01;
/* FuUsbDevice->open */
if (!FU_DEVICE_CLASS (fu_system76_launch_device_parent_class)->open (device, error))
return FALSE;
if (!g_usb_device_claim_interface (usb_device, iface_idx,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error (error, "failed to claim interface: ");
return FALSE;
}
return TRUE;
}
static gboolean
fu_system76_launch_device_close (FuDevice *device, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
const guint8 iface_idx = 0x01;
if (!g_usb_device_release_interface (usb_device, iface_idx,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error (error, "failed to release interface: ");
return FALSE;
}
/* FuUsbDevice->close */
return FU_DEVICE_CLASS (fu_system76_launch_device_parent_class)->close (device, error);
}
static void
fu_system76_launch_device_init (FuSystem76LaunchDevice *self)
{
fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS);
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID);
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PLAIN);
fu_device_add_protocol (FU_DEVICE (self), "org.usb.dfu");
fu_device_retry_set_delay (FU_DEVICE (self), 100);
}
static void
fu_system76_launch_device_class_init (FuSystem76LaunchDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
klass_device->setup = fu_system76_launch_device_setup;
klass_device->detach = fu_system76_launch_device_detach;
klass_device->open = fu_system76_launch_device_open;
klass_device->close = fu_system76_launch_device_close;
}