mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-28 07:57:08 +00:00

The setup() is the counterpart to probe(), the difference being the former needs the device open and the latter does not. This allows objects that derive from FuDevice, and use FuDeviceLocker to use open() and close() without worrying about the performance implications of probing the hardware, i.e. open() now simply opens a file or device.
293 lines
8.1 KiB
C
293 lines
8.1 KiB
C
/*
|
|
* Copyright (C) 2016-2017 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <appstream-glib.h>
|
|
|
|
#include "fu-nitrokey-common.h"
|
|
#include "fu-nitrokey-device.h"
|
|
|
|
G_DEFINE_TYPE (FuNitrokeyDevice, fu_nitrokey_device, FU_TYPE_USB_DEVICE)
|
|
|
|
#define NITROKEY_TRANSACTION_TIMEOUT 100 /* ms */
|
|
#define NITROKEY_NR_RETRIES 5
|
|
|
|
#define NITROKEY_REQUEST_DATA_LENGTH 59
|
|
#define NITROKEY_REPLY_DATA_LENGTH 53
|
|
|
|
#define NITROKEY_CMD_GET_DEVICE_STATUS (0x20 + 14)
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 command;
|
|
guint8 payload[NITROKEY_REQUEST_DATA_LENGTH];
|
|
guint32 crc;
|
|
} NitrokeyHidRequest;
|
|
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 _padding; /* always zero */
|
|
guint8 device_status;
|
|
guint32 last_command_crc;
|
|
guint8 last_command_status;
|
|
guint8 payload[NITROKEY_REPLY_DATA_LENGTH];
|
|
guint32 crc;
|
|
} NitrokeyHidResponse;
|
|
|
|
/* based from libnitrokey/stick20_commands.h */
|
|
typedef struct __attribute__((packed)) {
|
|
guint8 _padding[24];
|
|
guint8 SendCounter;
|
|
guint8 SendDataType;
|
|
guint8 FollowBytesFlag;
|
|
guint8 SendSize;
|
|
guint16 MagicNumber_StickConfig;
|
|
guint8 ReadWriteFlagUncryptedVolume;
|
|
guint8 ReadWriteFlagCryptedVolume;
|
|
guint8 VersionReserved1;
|
|
guint8 VersionMinor;
|
|
guint8 VersionReserved2;
|
|
guint8 VersionMajor;
|
|
guint8 ReadWriteFlagHiddenVolume;
|
|
guint8 FirmwareLocked;
|
|
guint8 NewSDCardFound;
|
|
guint8 SDFillWithRandomChars;
|
|
guint32 ActiveSD_CardID;
|
|
guint8 VolumeActiceFlag;
|
|
guint8 NewSmartCardFound;
|
|
guint8 UserPwRetryCount;
|
|
guint8 AdminPwRetryCount;
|
|
guint32 ActiveSmartCardID;
|
|
guint8 StickKeysNotInitiated;
|
|
} NitrokeyGetDeviceStatusPayload;
|
|
|
|
static void
|
|
_dump_to_console (const gchar *title, const guint8 *buf, gsize buf_sz)
|
|
{
|
|
if (g_getenv ("FWUPD_NITROKEY_VERBOSE") == NULL)
|
|
return;
|
|
g_debug ("%s", title);
|
|
for (gsize i = 0; i < buf_sz; i++)
|
|
g_debug ("%" G_GSIZE_FORMAT "=0x%02x", i, buf[i]);
|
|
}
|
|
|
|
static gboolean
|
|
nitrokey_execute_cmd (GUsbDevice *usb_device, guint8 command,
|
|
const guint8 *buf_in, gsize buf_in_sz,
|
|
guint8 *buf_out, gsize buf_out_sz,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
NitrokeyHidResponse res;
|
|
gboolean ret;
|
|
gsize actual_len = 0;
|
|
guint32 crc_tmp;
|
|
guint8 buf[64];
|
|
|
|
g_return_val_if_fail (buf_in_sz <= NITROKEY_REQUEST_DATA_LENGTH, FALSE);
|
|
g_return_val_if_fail (buf_out_sz <= NITROKEY_REPLY_DATA_LENGTH, FALSE);
|
|
|
|
/* create the request */
|
|
memset (buf, 0x00, sizeof(buf));
|
|
buf[0] = command;
|
|
if (buf_in != NULL)
|
|
memcpy (&buf[1], buf_in, buf_in_sz);
|
|
crc_tmp = fu_nitrokey_perform_crc32 (buf, sizeof(buf) - 4);
|
|
fu_common_write_uint32 (&buf[NITROKEY_REQUEST_DATA_LENGTH + 1], crc_tmp, G_LITTLE_ENDIAN);
|
|
|
|
/* send request */
|
|
_dump_to_console ("request", buf, sizeof(buf));
|
|
ret = g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
0x09, 0x0300, 0x0002,
|
|
buf, sizeof(buf),
|
|
&actual_len,
|
|
NITROKEY_TRANSACTION_TIMEOUT,
|
|
NULL, error);
|
|
if (!ret) {
|
|
g_prefix_error (error, "failed to do HOST_TO_DEVICE: ");
|
|
return FALSE;
|
|
}
|
|
if (actual_len != sizeof(buf)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"only wrote %" G_GSIZE_FORMAT "bytes", actual_len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* get response */
|
|
memset (buf, 0x00, sizeof(buf));
|
|
ret = g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
0x01, 0x0300, 0x0002,
|
|
buf, sizeof(buf),
|
|
&actual_len,
|
|
NITROKEY_TRANSACTION_TIMEOUT,
|
|
NULL,
|
|
error);
|
|
if (!ret) {
|
|
g_prefix_error (error, "failed to do DEVICE_TO_HOST: ");
|
|
return FALSE;
|
|
}
|
|
if (actual_len != sizeof(res)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"only wrote %" G_GSIZE_FORMAT "bytes", actual_len);
|
|
return FALSE;
|
|
}
|
|
_dump_to_console ("response", buf, sizeof(buf));
|
|
|
|
/* verify this is the answer to the question we asked */
|
|
memcpy (&res, buf, sizeof(buf));
|
|
if (GUINT32_FROM_LE (res.last_command_crc) != crc_tmp) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"got response CRC %x, expected %x",
|
|
GUINT32_FROM_LE (res.last_command_crc), crc_tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify the response checksum */
|
|
crc_tmp = fu_nitrokey_perform_crc32 (buf, sizeof(res) - 4);
|
|
if (GUINT32_FROM_LE (res.crc) != crc_tmp) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"got packet CRC %x, expected %x",
|
|
GUINT32_FROM_LE (res.crc), crc_tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
/* copy out the payload */
|
|
if (buf_out != NULL)
|
|
memcpy (buf_out, &res.payload, buf_out_sz);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
nitrokey_execute_cmd_full (GUsbDevice *usb_device, guint8 command,
|
|
const guint8 *buf_in, gsize buf_in_sz,
|
|
guint8 *buf_out, gsize buf_out_sz,
|
|
GCancellable *cancellable, GError **error)
|
|
{
|
|
for (guint i = 0; i < NITROKEY_NR_RETRIES; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
gboolean ret;
|
|
ret = nitrokey_execute_cmd (usb_device, command,
|
|
buf_in, buf_in_sz,
|
|
buf_out, buf_out_sz,
|
|
cancellable, &error_local);
|
|
if (ret)
|
|
return TRUE;
|
|
if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_FAILED)) {
|
|
if (error != NULL)
|
|
*error = g_steal_pointer (&error_local);
|
|
return FALSE;
|
|
}
|
|
g_warning ("retrying command: %s", error_local->message);
|
|
g_usleep (100 * 1000);
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"failed to issue command after %i retries",
|
|
NITROKEY_NR_RETRIES);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_nitrokey_device_open (FuUsbDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (device);
|
|
|
|
/* claim interface */
|
|
if (!g_usb_device_claim_interface (usb_device, 0x02, /* idx */
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
error)) {
|
|
g_prefix_error (error, "failed to do claim nitrokey: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_nitrokey_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
NitrokeyGetDeviceStatusPayload payload;
|
|
guint8 buf_reply[NITROKEY_REPLY_DATA_LENGTH];
|
|
g_autofree gchar *version = NULL;
|
|
|
|
/* get firmware version */
|
|
if (!nitrokey_execute_cmd_full (usb_device,
|
|
NITROKEY_CMD_GET_DEVICE_STATUS,
|
|
NULL, 0,
|
|
buf_reply, sizeof(buf_reply),
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to do get firmware version: ");
|
|
return FALSE;
|
|
}
|
|
_dump_to_console ("payload", buf_reply, sizeof(buf_reply));
|
|
memcpy (&payload, buf_reply, sizeof(buf_reply));
|
|
version = g_strdup_printf ("%u.%u", payload.VersionMinor, payload.VersionMajor);
|
|
fu_device_set_version (FU_DEVICE (device), version);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_nitrokey_device_close (FuUsbDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (device);
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* reconnect kernel driver */
|
|
if (!g_usb_device_release_interface (usb_device, 0x02,
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
&error_local)) {
|
|
g_warning ("failed to release interface: %s", error_local->message);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_nitrokey_device_init (FuNitrokeyDevice *device)
|
|
{
|
|
fu_device_set_remove_delay (FU_DEVICE (device), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
|
|
fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
}
|
|
|
|
static void
|
|
fu_nitrokey_device_class_init (FuNitrokeyDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass);
|
|
klass_device->setup = fu_nitrokey_device_setup;
|
|
klass_usb_device->open = fu_nitrokey_device_open;
|
|
klass_usb_device->close = fu_nitrokey_device_close;
|
|
}
|
|
|
|
FuNitrokeyDevice *
|
|
fu_nitrokey_device_new (GUsbDevice *usb_device)
|
|
{
|
|
FuNitrokeyDevice *device;
|
|
device = g_object_new (FU_TYPE_NITROKEY_DEVICE,
|
|
"usb-device", usb_device,
|
|
NULL);
|
|
return FU_NITROKEY_DEVICE (device);
|
|
}
|