mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-03 00:49:25 +00:00
572 lines
15 KiB
C
572 lines
15 KiB
C
/*
|
|
* Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
|
|
#include "fu-altos-device.h"
|
|
#include "fu-altos-firmware.h"
|
|
|
|
struct _FuAltosDevice {
|
|
FuUsbDevice parent_instance;
|
|
gchar *tty;
|
|
guint64 addr_base;
|
|
guint64 addr_bound;
|
|
struct termios tty_termios;
|
|
FuIOChannel *io_channel;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuAltosDevice, fu_altos_device, FU_TYPE_USB_DEVICE)
|
|
|
|
static void
|
|
fu_altos_device_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuAltosDevice *self = FU_ALTOS_DEVICE(device);
|
|
fu_common_string_append_kv(str, idt, "TTY", self->tty);
|
|
if (self->addr_base != 0x0)
|
|
fu_common_string_append_kx(str, idt, "AddrBase", self->addr_base);
|
|
if (self->addr_bound != 0x0)
|
|
fu_common_string_append_kx(str, idt, "AddrBound", self->addr_bound);
|
|
}
|
|
|
|
static void
|
|
fu_altos_device_finalize(GObject *object)
|
|
{
|
|
FuAltosDevice *self = FU_ALTOS_DEVICE(object);
|
|
|
|
g_free(self->tty);
|
|
|
|
G_OBJECT_CLASS(fu_altos_device_parent_class)->finalize(object);
|
|
}
|
|
|
|
static gboolean
|
|
fu_altos_device_find_tty(FuAltosDevice *self, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
g_autoptr(GList) devices = NULL;
|
|
g_autoptr(GUdevClient) gudev_client = g_udev_client_new(NULL);
|
|
|
|
/* find all tty devices */
|
|
devices = g_udev_client_query_by_subsystem(gudev_client, "tty");
|
|
for (GList *l = devices; l != NULL; l = l->next) {
|
|
GUdevDevice *dev = G_UDEV_DEVICE(l->data);
|
|
|
|
/* get the tty device */
|
|
const gchar *dev_file = g_udev_device_get_device_file(dev);
|
|
if (dev_file == NULL)
|
|
continue;
|
|
|
|
/* get grandparent */
|
|
dev = g_udev_device_get_parent(dev);
|
|
if (dev == NULL)
|
|
continue;
|
|
dev = g_udev_device_get_parent(dev);
|
|
if (dev == NULL)
|
|
continue;
|
|
|
|
/* check correct device */
|
|
if (g_udev_device_get_sysfs_attr_as_int(dev, "busnum") !=
|
|
g_usb_device_get_bus(usb_device))
|
|
continue;
|
|
if (g_udev_device_get_sysfs_attr_as_int(dev, "devnum") !=
|
|
g_usb_device_get_address(usb_device))
|
|
continue;
|
|
|
|
/* success */
|
|
self->tty = g_strdup(dev_file);
|
|
return TRUE;
|
|
}
|
|
|
|
/* failure */
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"failed to find tty for %u:%u",
|
|
g_usb_device_get_bus(usb_device),
|
|
g_usb_device_get_address(usb_device));
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_altos_device_tty_write(FuAltosDevice *self, const gchar *data, gssize data_len, GError **error)
|
|
{
|
|
/* lets assume this is text */
|
|
if (data_len < 0)
|
|
data_len = strlen(data);
|
|
if (g_getenv("FWUPD_ALTOS_VERBOSE") != NULL)
|
|
fu_common_dump_raw(G_LOG_DOMAIN, "write", (const guint8 *)data, (gsize)data_len);
|
|
return fu_io_channel_write_raw(self->io_channel,
|
|
(const guint8 *)data,
|
|
(gsize)data_len,
|
|
500, /* ms */
|
|
FU_IO_CHANNEL_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
static GString *
|
|
fu_altos_device_tty_read(FuAltosDevice *self, guint timeout_ms, gssize max_size, GError **error)
|
|
{
|
|
g_autoptr(GBytes) buf = NULL;
|
|
buf = fu_io_channel_read_bytes(self->io_channel,
|
|
max_size,
|
|
timeout_ms,
|
|
FU_IO_CHANNEL_FLAG_NONE,
|
|
error);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
if (g_getenv("FWUPD_ALTOS_VERBOSE") != NULL)
|
|
fu_common_dump_bytes(G_LOG_DOMAIN, "read", buf);
|
|
return g_string_new_len(g_bytes_get_data(buf, NULL), g_bytes_get_size(buf));
|
|
}
|
|
|
|
static gboolean
|
|
fu_altos_device_tty_open(FuAltosDevice *self, GError **error)
|
|
{
|
|
struct termios termios;
|
|
g_autoptr(GString) str = NULL;
|
|
|
|
/* open device */
|
|
self->io_channel = fu_io_channel_new_file(self->tty, error);
|
|
if (self->io_channel == NULL)
|
|
return FALSE;
|
|
|
|
/* get the old termios settings so we can restore later */
|
|
if (tcgetattr(fu_io_channel_unix_get_fd(self->io_channel), &termios) < 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to get attributes from fd");
|
|
return FALSE;
|
|
}
|
|
self->tty_termios = termios;
|
|
cfmakeraw(&termios);
|
|
|
|
/* set speed */
|
|
if (cfsetspeed(&termios, B9600) < 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to set terminal speed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* one input byte is enough to return
|
|
* inter-character timer off */
|
|
termios.c_cc[VMIN] = 1;
|
|
termios.c_cc[VTIME] = 0;
|
|
|
|
/* set all new data */
|
|
if (tcsetattr(fu_io_channel_unix_get_fd(self->io_channel), TCSAFLUSH, &termios) < 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to set attributes on fd");
|
|
return FALSE;
|
|
}
|
|
|
|
/* dump any pending input */
|
|
str = fu_altos_device_tty_read(self, 50, -1, NULL);
|
|
if (str != NULL)
|
|
g_debug("dumping pending buffer: %s", str->str);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_altos_device_tty_close(FuAltosDevice *self, GError **error)
|
|
{
|
|
if (self->io_channel != NULL) {
|
|
tcsetattr(fu_io_channel_unix_get_fd(self->io_channel),
|
|
TCSAFLUSH,
|
|
&self->tty_termios);
|
|
if (!fu_io_channel_shutdown(self->io_channel, error))
|
|
return FALSE;
|
|
g_clear_object(&self->io_channel);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static GString *
|
|
fu_altos_device_read_page(FuAltosDevice *self, guint address, GError **error)
|
|
{
|
|
g_autoptr(GString) str = NULL;
|
|
g_autofree gchar *cmd = g_strdup_printf("R %x\n", address);
|
|
if (!fu_altos_device_tty_write(self, cmd, -1, error))
|
|
return NULL;
|
|
str = fu_altos_device_tty_read(self, 1500, 256, error);
|
|
if (str == NULL)
|
|
return NULL;
|
|
return g_steal_pointer(&str);
|
|
}
|
|
|
|
static gboolean
|
|
fu_altos_device_write_page(FuAltosDevice *self,
|
|
guint address,
|
|
const guint8 *data,
|
|
guint data_len,
|
|
GError **error)
|
|
{
|
|
g_autofree gchar *cmd = g_strdup_printf("W %x\n", address);
|
|
if (!fu_altos_device_tty_write(self, cmd, -1, error))
|
|
return FALSE;
|
|
if (!fu_altos_device_tty_write(self, (const gchar *)data, data_len, error))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_altos_device_prepare_firmware(FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
g_autoptr(FuFirmware) firmware = fu_altos_firmware_new();
|
|
if (!fu_firmware_parse(firmware, fw, flags, error))
|
|
return NULL;
|
|
return g_steal_pointer(&firmware);
|
|
}
|
|
|
|
static gboolean
|
|
fu_altos_device_write_firmware(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuAltosDevice *self = FU_ALTOS_DEVICE(device);
|
|
const gchar *data;
|
|
const gsize data_len;
|
|
guint flash_len;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_autoptr(GBytes) fw = NULL;
|
|
g_autoptr(GString) buf = g_string_new(NULL);
|
|
|
|
/* check kind */
|
|
if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"verification only supported in bootloader");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check sizes */
|
|
if (self->addr_base == 0x0 || self->addr_bound == 0x0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"address base and bound are unset");
|
|
return FALSE;
|
|
}
|
|
|
|
/* read in blocks of 256 bytes */
|
|
flash_len = self->addr_bound - self->addr_base;
|
|
if (flash_len == 0x0 || flash_len > 0x100000) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"address range was icorrect");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check the start address */
|
|
if (fu_firmware_get_addr(firmware) != self->addr_base) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"start address not correct %" G_GUINT64_FORMAT ":"
|
|
"%" G_GUINT64_FORMAT,
|
|
fu_firmware_get_addr(firmware),
|
|
self->addr_base);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check firmware will fit */
|
|
fw = fu_firmware_get_bytes(firmware, error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
data = g_bytes_get_data(fw, (gsize *)&data_len);
|
|
if (data_len > flash_len) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"firmware too large for device %" G_GSIZE_FORMAT ":%u",
|
|
data_len,
|
|
flash_len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* open tty for download */
|
|
locker = fu_device_locker_new_full(device,
|
|
(FuDeviceLockerFunc)fu_altos_device_tty_open,
|
|
(FuDeviceLockerFunc)fu_altos_device_tty_close,
|
|
error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
fu_device_set_status(device, FWUPD_STATUS_DEVICE_WRITE);
|
|
for (guint i = 0; i < flash_len; i += 0x100) {
|
|
g_autoptr(GString) str = NULL;
|
|
guint8 buf_tmp[0x100];
|
|
|
|
/* copy remaining data into buf if required */
|
|
memset(buf_tmp, 0xff, sizeof(buf));
|
|
if (i < data_len) {
|
|
gsize chunk_len = 0x100;
|
|
if (i + 0x100 > data_len)
|
|
chunk_len = data_len - i;
|
|
if (!fu_memcpy_safe(buf_tmp,
|
|
sizeof(buf_tmp),
|
|
0, /* dst */
|
|
(const guint8 *)data,
|
|
data_len,
|
|
i, /* src */
|
|
chunk_len,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify data from device */
|
|
if (!fu_altos_device_write_page(self, self->addr_base + i, buf_tmp, 0x100, error))
|
|
return FALSE;
|
|
|
|
/* verify data written on device */
|
|
str = fu_altos_device_read_page(self, self->addr_base + i, error);
|
|
if (str == NULL)
|
|
return FALSE;
|
|
if (str->len != 0x100) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to verify @%x, "
|
|
"not enough data returned",
|
|
(guint)(self->addr_base + i));
|
|
return FALSE;
|
|
}
|
|
if (memcmp(str->str, buf_tmp, 0x100) != 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to verify @%x",
|
|
(guint)(self->addr_base + i));
|
|
return FALSE;
|
|
}
|
|
|
|
/* progress */
|
|
fu_device_set_progress_full(device, i, flash_len);
|
|
g_string_append_len(buf, str->str, str->len);
|
|
}
|
|
|
|
/* go to application mode */
|
|
if (!fu_altos_device_tty_write(self, "a\n", -1, error))
|
|
return FALSE;
|
|
|
|
/* progress complete */
|
|
fu_device_set_progress_full(device, flash_len, flash_len);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static GBytes *
|
|
fu_altos_device_dump_firmware(FuDevice *device, GError **error)
|
|
{
|
|
FuAltosDevice *self = FU_ALTOS_DEVICE(device);
|
|
guint flash_len;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_autoptr(GString) buf = g_string_new(NULL);
|
|
|
|
/* check kind */
|
|
if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"verification only supported in bootloader");
|
|
return NULL;
|
|
}
|
|
|
|
/* check sizes */
|
|
if (self->addr_base == 0x0 || self->addr_bound == 0x0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"address base and bound are unset");
|
|
return NULL;
|
|
}
|
|
|
|
/* read in blocks of 256 bytes */
|
|
flash_len = self->addr_bound - self->addr_base;
|
|
if (flash_len == 0x0 || flash_len > 0x100000) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"address range was icorrect");
|
|
return NULL;
|
|
}
|
|
|
|
/* open tty for download */
|
|
locker = fu_device_locker_new_full(device,
|
|
(FuDeviceLockerFunc)fu_altos_device_tty_open,
|
|
(FuDeviceLockerFunc)fu_altos_device_tty_close,
|
|
error);
|
|
if (locker == NULL)
|
|
return NULL;
|
|
for (guint i = self->addr_base; i < self->addr_bound; i += 0x100) {
|
|
g_autoptr(GString) str = NULL;
|
|
|
|
/* request data from device */
|
|
str = fu_altos_device_read_page(self, i, error);
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
/* progress */
|
|
fu_device_set_progress_full(device,
|
|
i - self->addr_base,
|
|
self->addr_bound - self->addr_base);
|
|
g_string_append_len(buf, str->str, str->len);
|
|
}
|
|
|
|
/* success */
|
|
return g_bytes_new(buf->str, buf->len);
|
|
}
|
|
|
|
static gboolean
|
|
fu_altos_device_probe_bootloader(FuAltosDevice *self, GError **error)
|
|
{
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_auto(GStrv) lines = NULL;
|
|
g_autoptr(GString) str = NULL;
|
|
|
|
/* get tty for upload */
|
|
if (!fu_altos_device_find_tty(self, error))
|
|
return FALSE;
|
|
locker = fu_device_locker_new_full(self,
|
|
(FuDeviceLockerFunc)fu_altos_device_tty_open,
|
|
(FuDeviceLockerFunc)fu_altos_device_tty_close,
|
|
error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
|
|
/* get the version information */
|
|
if (!fu_altos_device_tty_write(self, "v\n", -1, error))
|
|
return FALSE;
|
|
str = fu_altos_device_tty_read(self, 100, -1, error);
|
|
if (str == NULL) {
|
|
g_prefix_error(error, "failed to get version information: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse each line */
|
|
lines = g_strsplit_set(str->str, "\n\r", -1);
|
|
for (guint i = 0; lines[i] != NULL; i++) {
|
|
/* ignore */
|
|
if (lines[i][0] == '\0')
|
|
continue;
|
|
if (g_str_has_prefix(lines[i], "manufacturer "))
|
|
continue;
|
|
if (g_str_has_prefix(lines[i], "product "))
|
|
continue;
|
|
|
|
/* we can flash firmware */
|
|
if (g_strcmp0(lines[i], "altos-loader") == 0) {
|
|
fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER);
|
|
continue;
|
|
}
|
|
|
|
/* version number */
|
|
if (g_str_has_prefix(lines[i], "software-version ")) {
|
|
fu_device_set_version(FU_DEVICE(self), lines[i] + 17);
|
|
continue;
|
|
}
|
|
|
|
/* address base and bound */
|
|
if (g_str_has_prefix(lines[i], "flash-range ")) {
|
|
g_auto(GStrv) addrs = g_strsplit(lines[i] + 17, " ", -1);
|
|
self->addr_base = g_ascii_strtoull(addrs[0], NULL, 16);
|
|
self->addr_bound = g_ascii_strtoull(addrs[1], NULL, 16);
|
|
g_debug("base: %x, bound: %x",
|
|
(guint)self->addr_base,
|
|
(guint)self->addr_bound);
|
|
continue;
|
|
}
|
|
|
|
/* unknown line */
|
|
g_debug("unknown data: '%s'", lines[i]);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_altos_device_probe(FuDevice *device, GError **error)
|
|
{
|
|
FuAltosDevice *self = FU_ALTOS_DEVICE(device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
|
|
const gchar *version_prefix = "ChaosKey-hw-1.0-sw-";
|
|
guint8 version_idx;
|
|
g_autofree gchar *version = NULL;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
|
|
/* bootloader uses tty */
|
|
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER))
|
|
return fu_altos_device_probe_bootloader(self, error);
|
|
|
|
/* open */
|
|
locker = fu_device_locker_new(usb_device, error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
|
|
/* get string */
|
|
version_idx = g_usb_device_get_product_index(usb_device);
|
|
version = g_usb_device_get_string_descriptor(usb_device, version_idx, error);
|
|
if (version == NULL)
|
|
return FALSE;
|
|
if (!g_str_has_prefix(version, version_prefix)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"not a ChaosKey v1.0 device: %s",
|
|
version);
|
|
return FALSE;
|
|
}
|
|
fu_device_set_version(FU_DEVICE(self), version + 19);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_altos_device_init(FuAltosDevice *self)
|
|
{
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
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_TRIPLET);
|
|
fu_device_set_vendor(FU_DEVICE(self), "altusmetrum.org");
|
|
fu_device_set_summary(FU_DEVICE(self), "USB hardware random number generator");
|
|
fu_device_add_protocol(FU_DEVICE(self), "org.altusmetrum.altos");
|
|
|
|
/* requires manual step */
|
|
if (!fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER))
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER);
|
|
}
|
|
|
|
static void
|
|
fu_altos_device_class_init(FuAltosDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
klass_device->to_string = fu_altos_device_to_string;
|
|
klass_device->probe = fu_altos_device_probe;
|
|
klass_device->prepare_firmware = fu_altos_device_prepare_firmware;
|
|
klass_device->write_firmware = fu_altos_device_write_firmware;
|
|
klass_device->dump_firmware = fu_altos_device_dump_firmware;
|
|
object_class->finalize = fu_altos_device_finalize;
|
|
}
|