mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-10 11:06:02 +00:00
284 lines
8.7 KiB
C
284 lines
8.7 KiB
C
/*
|
|
* Copyright (C) 2021 Daniel Campello <campello@chromium.org>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-flashrom-device.h"
|
|
#include "fu-flashrom-lspcon-i2c-spi-device.h"
|
|
|
|
#include <libflashrom.h>
|
|
|
|
#define I2C_PATH_REGEX "/i2c-([0-9]+)/"
|
|
#define HID_LENGTH 8
|
|
|
|
struct _FuFlashromLspconI2cSpiDevice {
|
|
FuFlashromDevice parent_instance;
|
|
gint bus_number;
|
|
guint8 active_partition;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuFlashromLspconI2cSpiDevice, fu_flashrom_lspcon_i2c_spi_device,
|
|
FU_TYPE_FLASHROM_DEVICE)
|
|
|
|
struct romentry {
|
|
guint32 start;
|
|
guint32 end;
|
|
gboolean included;
|
|
const gchar *name;
|
|
const gchar *file;
|
|
};
|
|
|
|
struct flashrom_layout {
|
|
struct romentry *entries;
|
|
gsize num_entries;
|
|
};
|
|
|
|
static struct romentry entries[5] = {
|
|
{ .start = 0x00002, .end = 0x00003, .name = "FLAG" },
|
|
{ .start = 0x10000, .end = 0x1ffff, .name = "PAR1" },
|
|
{ .start = 0x20000, .end = 0x2ffff, .name = "PAR2" },
|
|
{ .start = 0x15000, .end = 0x15002, .name = "VER1" },
|
|
{ .start = 0x25000, .end = 0x25002, .name = "VER2" },
|
|
};
|
|
|
|
static struct flashrom_layout layout = {
|
|
.entries = entries, .num_entries = sizeof(entries) / sizeof(entries[0])
|
|
};
|
|
|
|
|
|
static void
|
|
fu_flashrom_lspcon_i2c_spi_device_init (FuFlashromLspconI2cSpiDevice *self)
|
|
{
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PAIR);
|
|
}
|
|
|
|
static gboolean
|
|
fu_flashrom_lspcon_i2c_spi_device_probe (FuDevice *device, GError **error)
|
|
{
|
|
FuFlashromLspconI2cSpiDevice *self = FU_FLASHROM_LSPCON_I2C_SPI_DEVICE (device);
|
|
FuFlashromDevice *flashrom_device = FU_FLASHROM_DEVICE (device);
|
|
FuDeviceClass *klass =
|
|
FU_DEVICE_CLASS (fu_flashrom_lspcon_i2c_spi_device_parent_class);
|
|
g_autoptr(GRegex) regex = NULL;
|
|
g_autoptr(GMatchInfo) info = NULL;
|
|
const gchar *path = NULL;
|
|
|
|
/* FuFlashromDevice->probe */
|
|
if (!klass->probe (device, error))
|
|
return FALSE;
|
|
|
|
if (g_strcmp0 (fu_flashrom_device_get_programmer_name (flashrom_device),
|
|
"lspcon_i2c_spi") != 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid programmer");
|
|
return FALSE;
|
|
}
|
|
|
|
/* get bus number out of sysfs path */
|
|
path = fu_udev_device_get_sysfs_path (FU_UDEV_DEVICE (device));
|
|
regex = g_regex_new (I2C_PATH_REGEX, 0, 0, error);
|
|
if (regex && g_regex_match_full (regex, path, -1, 0, 0, &info, error)) {
|
|
self->bus_number = g_ascii_strtoll ( g_match_info_fetch (info, 1),
|
|
NULL, 10);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_flashrom_lspcon_i2c_spi_device_open (FuDevice *device,
|
|
GError **error)
|
|
{
|
|
FuFlashromLspconI2cSpiDevice *self = FU_FLASHROM_LSPCON_I2C_SPI_DEVICE (device);
|
|
FuFlashromDevice *flashrom_device = FU_FLASHROM_DEVICE (device);
|
|
FuDeviceClass *klass =
|
|
FU_DEVICE_CLASS (fu_flashrom_lspcon_i2c_spi_device_parent_class);
|
|
g_autofree gchar *temp = NULL;
|
|
|
|
/* flashrom_programmer_init() mutates the programmer_args string. */
|
|
temp = g_strdup_printf ("bus=%d", self->bus_number);
|
|
fu_flashrom_device_set_programmer_args (flashrom_device, temp);
|
|
|
|
return klass->open (device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_flashrom_lspcon_i2c_spi_device_set_version (FuDevice *device, GError **error)
|
|
{
|
|
FuFlashromLspconI2cSpiDevice *self = FU_FLASHROM_LSPCON_I2C_SPI_DEVICE (device);
|
|
FuFlashromDevice *flashrom_device = FU_FLASHROM_DEVICE (device);
|
|
g_autofree gchar *contents = NULL;
|
|
g_autofree gchar *version = NULL;
|
|
struct flashrom_flashctx *flashctx = NULL;
|
|
gsize flash_size;
|
|
guint32 addr;
|
|
|
|
/* set up flashrom layout */
|
|
flashctx = fu_flashrom_device_get_flashctx (flashrom_device);
|
|
flashrom_layout_set (flashctx, &layout);
|
|
|
|
/* only include flag and version regions on layout */
|
|
entries[0].included = TRUE;
|
|
entries[1].included = FALSE;
|
|
entries[2].included = FALSE;
|
|
entries[3].included = TRUE;
|
|
entries[4].included = TRUE;
|
|
|
|
/* read the current flash contents */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_READ);
|
|
flash_size = fu_flashrom_device_get_flash_size (flashrom_device);
|
|
contents = g_malloc0 (flash_size);
|
|
if (flashrom_image_read (flashctx, contents, flash_size)) {
|
|
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ,
|
|
"failed to read flash contents");
|
|
return FALSE;
|
|
}
|
|
|
|
/* grab the active partition */
|
|
self->active_partition = contents[entries[0].start];
|
|
|
|
if (self->active_partition != 1 && self->active_partition != 2) {
|
|
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_READ,
|
|
"failed to read active partition");
|
|
return FALSE;
|
|
}
|
|
|
|
/* find the current version for the active partition */
|
|
addr = entries[self->active_partition + 2].start;
|
|
version = g_strdup_printf ("%d.%d", contents[addr], contents[addr + 2]);
|
|
fu_device_set_version (device, version);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_flashrom_lspcon_i2c_spi_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
const gchar *hw_id = NULL;
|
|
g_autofree gchar *vid = NULL;
|
|
g_autofree gchar *pid = NULL;
|
|
g_autofree gchar *vendor_id = NULL;
|
|
g_autofree gchar *instance_id = NULL;
|
|
|
|
hw_id = fu_udev_device_get_sysfs_attr (FU_UDEV_DEVICE (device), "name", error);
|
|
if (hw_id == NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"HID not found");
|
|
return FALSE;
|
|
}
|
|
vid = g_strndup (hw_id, HID_LENGTH / 2);
|
|
pid = g_strndup (&hw_id[HID_LENGTH / 2], HID_LENGTH / 2);
|
|
vendor_id = g_strdup_printf ("I2C:%s", vid);
|
|
fu_device_add_vendor_id (device, vendor_id);
|
|
|
|
instance_id = g_strdup_printf ("FLASHROM-LSPCON-I2C-SPI\\VEN_%s&DEV_%s", vid, pid);
|
|
fu_device_add_instance_id (device, instance_id);
|
|
|
|
return fu_flashrom_lspcon_i2c_spi_device_set_version (device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_flashrom_lspcon_i2c_spi_device_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuFlashromDevice *parent = FU_FLASHROM_DEVICE (device);
|
|
FuFlashromLspconI2cSpiDevice *self = FU_FLASHROM_LSPCON_I2C_SPI_DEVICE (device);
|
|
struct flashrom_flashctx *flashctx = fu_flashrom_device_get_flashctx (parent);
|
|
gsize flash_size = fu_flashrom_device_get_flash_size (parent);
|
|
g_autofree guint8 *newcontents = g_malloc0 (flash_size);
|
|
const guint8 target_partition = self->active_partition == 1 ? 2 : 1;
|
|
const gsize region_size = entries[target_partition].end -
|
|
entries[target_partition].start + 1;
|
|
gsize sz = 0;
|
|
gint rc;
|
|
const guint8 *buf;
|
|
g_autoptr(GBytes) blob_fw = fu_firmware_get_bytes (firmware, error);
|
|
if (blob_fw == NULL)
|
|
return FALSE;
|
|
|
|
buf = g_bytes_get_data (blob_fw, &sz);
|
|
|
|
if (sz != region_size) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid image size 0x%x, expected 0x%x",
|
|
(guint) sz, (guint) flash_size);
|
|
return FALSE;
|
|
}
|
|
if (!fu_memcpy_safe (newcontents, flash_size,
|
|
entries[target_partition].start, buf, sz, 0,
|
|
region_size, error))
|
|
return FALSE;
|
|
|
|
flashrom_flag_set (flashctx, FLASHROM_FLAG_VERIFY_AFTER_WRITE, TRUE);
|
|
flashrom_layout_set (flashctx, &layout);
|
|
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
|
fu_device_set_progress (device, 0); /* urgh */
|
|
|
|
/* only include target_partition on layout */
|
|
entries[0].included = FALSE;
|
|
entries[3].included = FALSE;
|
|
entries[4].included = FALSE;
|
|
entries[self->active_partition].included = FALSE;
|
|
entries[target_partition].included = TRUE;
|
|
|
|
/* write target_partition */
|
|
rc = flashrom_image_write (flashctx, (void *) newcontents, flash_size,
|
|
NULL /* refbuffer */);
|
|
if (rc != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"image write failed, err=%i", rc);
|
|
return FALSE;
|
|
}
|
|
|
|
/* only include flag area on layout */
|
|
entries[0].included = TRUE;
|
|
entries[target_partition].included = FALSE;
|
|
|
|
/* Flag area is header bytes (0x55, 0xAA) followed by the bank ID to
|
|
* boot from (1 or 2) and the two's complement inverse of that bank ID
|
|
* (0 or 0xFF). We only write bytes 2 and 3, assuming the header is
|
|
* already valid. */
|
|
newcontents[2] = (gint8) target_partition;
|
|
newcontents[3] = (gint8) -target_partition + 1;
|
|
|
|
/* write flag area */
|
|
rc = flashrom_image_write (flashctx, (void *) newcontents, flash_size,
|
|
NULL /* refbuffer */);
|
|
if (rc != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"flag write failed, err=%i", rc);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_flashrom_lspcon_i2c_spi_device_class_init (FuFlashromLspconI2cSpiDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
klass_device->probe = fu_flashrom_lspcon_i2c_spi_device_probe;
|
|
klass_device->open = fu_flashrom_lspcon_i2c_spi_device_open;
|
|
klass_device->setup = fu_flashrom_lspcon_i2c_spi_device_setup;
|
|
klass_device->write_firmware = fu_flashrom_lspcon_i2c_spi_device_write_firmware;
|
|
klass_device->reload = fu_flashrom_lspcon_i2c_spi_device_set_version;
|
|
}
|