mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-22 07:14:49 +00:00
770 lines
22 KiB
C
770 lines
22 KiB
C
/*
|
|
* Copyright (C) 2018-2020 Evan Lojewski
|
|
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#ifdef HAVE_MMAN_H
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_VALGRIND
|
|
#include <valgrind.h>
|
|
#endif /* HAVE_VALGRIND */
|
|
|
|
#include "fu-common.h"
|
|
|
|
#include "fu-bcm57xx-common.h"
|
|
#include "fu-bcm57xx-recovery-device.h"
|
|
#include "fu-bcm57xx-firmware.h"
|
|
|
|
/* offsets into BAR[0] */
|
|
#define REG_DEVICE_PCI_VENDOR_DEVICE_ID 0x6434
|
|
#define REG_NVM_SOFTWARE_ARBITRATION 0x7020
|
|
#define REG_NVM_ACCESS 0x7024
|
|
#define REG_NVM_COMMAND 0x7000
|
|
#define REG_NVM_ADDR 0x700c
|
|
#define REG_NVM_READ 0x7010
|
|
#define REG_NVM_WRITE 0x7008
|
|
|
|
/* offsets into BAR[2] */
|
|
#define REG_APE_MODE 0x0
|
|
|
|
typedef struct {
|
|
guint8 *buf;
|
|
gsize bufsz;
|
|
} FuBcm57xxMmap;
|
|
|
|
#define FU_BCM57XX_BAR_DEVICE 0
|
|
#define FU_BCM57XX_BAR_APE 1
|
|
#define FU_BCM57XX_BAR_MAX 3
|
|
|
|
struct _FuBcm57xxRecoveryDevice {
|
|
FuUdevDevice parent_instance;
|
|
FuBcm57xxMmap bar[FU_BCM57XX_BAR_MAX];
|
|
};
|
|
|
|
typedef union {
|
|
guint32 r32;
|
|
struct {
|
|
guint32 reserved_0_0 : 1;
|
|
guint32 Reset : 1;
|
|
guint32 reserved_2_2 : 1;
|
|
guint32 Done : 1;
|
|
guint32 Doit : 1;
|
|
guint32 Wr : 1;
|
|
guint32 Erase : 1;
|
|
guint32 First : 1;
|
|
guint32 Last : 1;
|
|
guint32 reserved_15_9 : 7;
|
|
guint32 WriteEnableCommand : 1;
|
|
guint32 WriteDisableCommand : 1;
|
|
guint32 reserved_31_18 : 14;
|
|
} __attribute__((packed)) bits;
|
|
} BcmRegNVMCommand;
|
|
|
|
typedef union {
|
|
guint32 r32;
|
|
struct {
|
|
guint32 ReqSet0 : 1;
|
|
guint32 ReqSet1 : 1;
|
|
guint32 ReqSet2 : 1;
|
|
guint32 ReqSet3 : 1;
|
|
guint32 ReqClr0 : 1;
|
|
guint32 ReqClr1 : 1;
|
|
guint32 ReqClr2 : 1;
|
|
guint32 ReqClr3 : 1;
|
|
guint32 ArbWon0 : 1;
|
|
guint32 ArbWon1 : 1;
|
|
guint32 ArbWon2 : 1;
|
|
guint32 ArbWon3 : 1;
|
|
guint32 Req0 : 1;
|
|
guint32 Req1 : 1;
|
|
guint32 Req2 : 1;
|
|
guint32 Req3 : 1;
|
|
guint32 reserved_31_16 : 16;
|
|
} __attribute__((packed)) bits;
|
|
} BcmRegNVMSoftwareArbitration;
|
|
|
|
typedef union {
|
|
guint32 r32;
|
|
struct {
|
|
guint32 Enable : 1;
|
|
guint32 WriteEnable : 1;
|
|
guint32 reserved_31_2 : 30;
|
|
} __attribute__((packed)) bits;
|
|
} BcmRegNVMAccess;
|
|
|
|
typedef union {
|
|
guint32 r32;
|
|
struct {
|
|
guint32 Reset : 1;
|
|
guint32 Halt : 1;
|
|
guint32 FastBoot : 1;
|
|
guint32 HostDiag : 1;
|
|
guint32 reserved_4_4 : 1;
|
|
guint32 Event1 : 1;
|
|
guint32 Event2 : 1;
|
|
guint32 GRCint : 1;
|
|
guint32 reserved_8_8 : 1;
|
|
guint32 SwapATBdword : 1;
|
|
guint32 reserved_10_10 : 1;
|
|
guint32 SwapARBdword : 1;
|
|
guint32 reserved_13_12 : 2;
|
|
guint32 Channel0Enable : 1;
|
|
guint32 Channel2Enable : 1;
|
|
guint32 reserved_17_16 : 2;
|
|
guint32 MemoryECC : 1;
|
|
guint32 ICodePIPRdDisable : 1;
|
|
guint32 reserved_29_20 : 10;
|
|
guint32 Channel1Enable : 1;
|
|
guint32 Channel3Enable : 1;
|
|
} __attribute__((packed)) bits;
|
|
} BcmRegAPEMode;
|
|
|
|
G_DEFINE_TYPE (FuBcm57xxRecoveryDevice, fu_bcm57xx_recovery_device, FU_TYPE_UDEV_DEVICE)
|
|
|
|
#ifdef __ppc64__
|
|
#define BARRIER() __asm__ volatile ("sync 0\neieio\n" : : : "memory")
|
|
#else
|
|
#define BARRIER() __asm__ volatile ("" : : : "memory");
|
|
#endif
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_bar_read (FuBcm57xxRecoveryDevice *self,
|
|
guint bar, gsize offset, guint32 *val,
|
|
GError **error)
|
|
{
|
|
guint8 *base = self->bar[bar].buf + offset;
|
|
|
|
/* this should never happen */
|
|
if (self->bar[bar].buf == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"BAR[%u] is not mapped!", bar);
|
|
return FALSE;
|
|
}
|
|
|
|
BARRIER();
|
|
*val = *(guint32 *)base;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_bar_write (FuBcm57xxRecoveryDevice *self,
|
|
guint bar, gsize offset, guint32 val,
|
|
GError **error)
|
|
{
|
|
guint8 *base = self->bar[bar].buf + offset;
|
|
|
|
/* this should never happen */
|
|
if (self->bar[bar].buf == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"BAR[%u] is not mapped!", bar);
|
|
return FALSE;
|
|
}
|
|
|
|
BARRIER();
|
|
*(guint32 *)base = val;
|
|
BARRIER();
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_disable (FuBcm57xxRecoveryDevice *self,
|
|
GError **error)
|
|
{
|
|
BcmRegNVMAccess tmp;
|
|
if (!fu_bcm57xx_recovery_device_bar_read (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_ACCESS, &tmp.r32, error))
|
|
return FALSE;
|
|
tmp.bits.Enable = FALSE;
|
|
tmp.bits.WriteEnable = FALSE;
|
|
return fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_ACCESS, tmp.r32, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_enable (FuBcm57xxRecoveryDevice *self,
|
|
GError **error)
|
|
{
|
|
BcmRegNVMAccess tmp;
|
|
if (!fu_bcm57xx_recovery_device_bar_read (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_ACCESS, &tmp.r32, error))
|
|
return FALSE;
|
|
tmp.bits.Enable = TRUE;
|
|
tmp.bits.WriteEnable = FALSE;
|
|
return fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_ACCESS, tmp.r32, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_enable_write (FuBcm57xxRecoveryDevice *self,
|
|
GError **error)
|
|
{
|
|
BcmRegNVMAccess tmp;
|
|
if (!fu_bcm57xx_recovery_device_bar_read (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_ACCESS, &tmp.r32, error))
|
|
return FALSE;
|
|
tmp.bits.Enable = TRUE;
|
|
tmp.bits.WriteEnable = TRUE;
|
|
return fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_ACCESS, tmp.r32, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_acquire_lock (FuBcm57xxRecoveryDevice *self,
|
|
GError **error)
|
|
{
|
|
BcmRegNVMSoftwareArbitration tmp = { 0 };
|
|
g_autoptr(GTimer) timer = g_timer_new ();
|
|
tmp.bits.ReqSet1 = 1;
|
|
if (!fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_SOFTWARE_ARBITRATION,
|
|
tmp.r32, error))
|
|
return FALSE;
|
|
do {
|
|
if (!fu_bcm57xx_recovery_device_bar_read (self,
|
|
FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_SOFTWARE_ARBITRATION,
|
|
&tmp.r32, error))
|
|
return FALSE;
|
|
if (tmp.bits.ArbWon1)
|
|
return TRUE;
|
|
if (g_timer_elapsed (timer, NULL) > 0.2)
|
|
break;
|
|
} while (TRUE);
|
|
|
|
/* timed out */
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_TIMED_OUT,
|
|
"timed out trying to acquire lock #1");
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_release_lock (FuBcm57xxRecoveryDevice *self,
|
|
GError **error)
|
|
{
|
|
BcmRegNVMSoftwareArbitration tmp = { 0 };
|
|
tmp.r32 = 0;
|
|
tmp.bits.ReqClr1 = 1;
|
|
return fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_SOFTWARE_ARBITRATION, tmp.r32,
|
|
error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_wait_done (FuBcm57xxRecoveryDevice *self, GError **error)
|
|
{
|
|
BcmRegNVMCommand tmp = { 0 };
|
|
g_autoptr(GTimer) timer = g_timer_new ();
|
|
do {
|
|
if (!fu_bcm57xx_recovery_device_bar_read (self,
|
|
FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_COMMAND, &tmp.r32,
|
|
error))
|
|
return FALSE;
|
|
if (tmp.bits.Done)
|
|
return TRUE;
|
|
if (g_timer_elapsed (timer, NULL) > 0.2)
|
|
break;
|
|
} while (TRUE);
|
|
|
|
/* timed out */
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_TIMED_OUT,
|
|
"timed out");
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_clear_done (FuBcm57xxRecoveryDevice *self, GError **error)
|
|
{
|
|
BcmRegNVMCommand tmp = { 0 };
|
|
tmp.bits.Done = 1;
|
|
return fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_COMMAND, tmp.r32, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_read (FuBcm57xxRecoveryDevice *self,
|
|
guint32 address, guint32 *buf, gsize bufsz,
|
|
GError **error)
|
|
{
|
|
for (guint i = 0; i < bufsz; i++) {
|
|
BcmRegNVMCommand tmp = { 0 };
|
|
guint32 val32 = 0;
|
|
if (!fu_bcm57xx_recovery_device_nvram_clear_done (self, error))
|
|
return FALSE;
|
|
if (!fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_ADDR, address, error))
|
|
return FALSE;
|
|
tmp.bits.Doit = 1;
|
|
tmp.bits.First = i == 0;
|
|
tmp.bits.Last = i == bufsz - 1;
|
|
|
|
if (!fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_COMMAND, tmp.r32, error))
|
|
return FALSE;
|
|
if (!fu_bcm57xx_recovery_device_nvram_wait_done (self, error)) {
|
|
g_prefix_error (error, "failed to read @0x%x: ", address);
|
|
return FALSE;
|
|
}
|
|
if (!fu_bcm57xx_recovery_device_bar_read (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_READ, &val32, error))
|
|
return FALSE;
|
|
buf[i] = GUINT32_FROM_BE(val32);
|
|
address += sizeof(guint32);
|
|
fu_device_set_progress_full (FU_DEVICE (self), i, bufsz);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_nvram_write (FuBcm57xxRecoveryDevice *self,
|
|
guint32 address, const guint32 *buf, gsize bufsz_dwrds,
|
|
GError **error)
|
|
{
|
|
const guint32 page_size_dwrds = 64;
|
|
|
|
/* can only write in pages of 64 dwords */
|
|
if (bufsz_dwrds % page_size_dwrds != 0 ||
|
|
(address * sizeof(guint32)) % page_size_dwrds != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"can only write aligned with page size 0x%x",
|
|
page_size_dwrds);
|
|
return FALSE;
|
|
}
|
|
|
|
for (guint i = 0; i < bufsz_dwrds; i++) {
|
|
BcmRegNVMCommand tmp = { 0 };
|
|
if (!fu_bcm57xx_recovery_device_nvram_clear_done (self, error))
|
|
return FALSE;
|
|
if (!fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_WRITE, GUINT32_TO_BE(buf[i]), error))
|
|
return FALSE;
|
|
if (!fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_ADDR, address, error))
|
|
return FALSE;
|
|
tmp.bits.Wr = TRUE;
|
|
tmp.bits.Doit = TRUE;
|
|
tmp.bits.First = i % page_size_dwrds == 0;
|
|
tmp.bits.Last = (i + 1) % page_size_dwrds == 0;
|
|
if (!fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_DEVICE,
|
|
REG_NVM_COMMAND, tmp.r32, error))
|
|
return FALSE;
|
|
if (!fu_bcm57xx_recovery_device_nvram_wait_done (self, error)) {
|
|
g_prefix_error (error, "failed to write @0x%x: ", address);
|
|
return FALSE;
|
|
}
|
|
address += sizeof(guint32);
|
|
fu_device_set_progress_full (FU_DEVICE (self), i, bufsz_dwrds);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_detach (FuDevice *device, GError **error)
|
|
{
|
|
/* unbind tg3 */
|
|
return fu_device_unbind_driver (device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_attach (FuDevice *device, GError **error)
|
|
{
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
/* bind tg3, which might fail if the module is not compiled */
|
|
if (!fu_device_bind_driver (device, "pci", "tg3", &error_local)) {
|
|
if (g_error_matches (error_local,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED)) {
|
|
g_warning ("failed to bind tg3: %s", error_local->message);
|
|
} else {
|
|
g_propagate_prefixed_error (error,
|
|
g_steal_pointer (&error_local),
|
|
"failed to bind tg3: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_activate (FuDevice *device, GError **error)
|
|
{
|
|
BcmRegAPEMode mode = { 0 };
|
|
FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE (device);
|
|
|
|
/* halt */
|
|
mode.bits.Halt = 1;
|
|
mode.bits.FastBoot = 0;
|
|
if (!fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_APE,
|
|
REG_APE_MODE, mode.r32, error))
|
|
return FALSE;
|
|
|
|
/* boot */
|
|
mode.bits.Halt = 0;
|
|
mode.bits.FastBoot = 0;
|
|
mode.bits.Reset = 1;
|
|
return fu_bcm57xx_recovery_device_bar_write (self, FU_BCM57XX_BAR_APE,
|
|
REG_APE_MODE, mode.r32, error);
|
|
}
|
|
|
|
static GBytes *
|
|
fu_bcm57xx_recovery_device_dump_firmware (FuDevice *device, GError **error)
|
|
{
|
|
FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE (device);
|
|
gsize bufsz_dwrds = fu_device_get_firmware_size_max (FU_DEVICE (self)) / sizeof(guint32);
|
|
g_autofree guint32 *buf_dwrds = g_new0 (guint32, bufsz_dwrds);
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_autoptr(FuDeviceLocker) locker2 = NULL;
|
|
|
|
/* read from hardware */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_READ);
|
|
locker = fu_device_locker_new_full (self,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_acquire_lock,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_release_lock,
|
|
error);
|
|
if (locker == NULL)
|
|
return NULL;
|
|
locker2 = fu_device_locker_new_full (self,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_enable,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_disable,
|
|
error);
|
|
if (locker2 == NULL)
|
|
return NULL;
|
|
if (!fu_bcm57xx_recovery_device_nvram_read (self, 0x0, buf_dwrds, bufsz_dwrds, error))
|
|
return NULL;
|
|
if (!fu_device_locker_close (locker2, error))
|
|
return NULL;
|
|
return g_bytes_new (buf_dwrds, bufsz_dwrds * sizeof(guint32));
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_bcm57xx_recovery_device_prepare_firmware (FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
g_autoptr(FuFirmware) firmware_bin = fu_firmware_new ();
|
|
g_autoptr(FuFirmware) firmware_tmp = fu_bcm57xx_firmware_new ();
|
|
|
|
/* check is a NVRAM backup */
|
|
if (!fu_firmware_parse (firmware_tmp, fw, flags, error)) {
|
|
g_prefix_error (error, "failed to parse new firmware: ");
|
|
return NULL;
|
|
}
|
|
if (!fu_bcm57xx_firmware_is_backup (FU_BCM57XX_FIRMWARE (firmware_tmp))) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"can only recover with backup firmware");
|
|
return NULL;
|
|
}
|
|
if (!fu_firmware_parse (firmware_bin, fw, flags, error))
|
|
return NULL;
|
|
return g_steal_pointer (&firmware_bin);
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuBcm57xxRecoveryDevice *self= FU_BCM57XX_RECOVERY_DEVICE (device);
|
|
const guint8 *buf;
|
|
gsize bufsz = 0;
|
|
gsize bufsz_dwrds;
|
|
g_autofree guint32 *buf_dwrds = NULL;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_autoptr(FuDeviceLocker) locker2 = NULL;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
|
|
/* build the images into one linear blob of the correct size */
|
|
fu_device_set_status (device, FWUPD_STATUS_DECOMPRESSING);
|
|
blob = fu_firmware_write (firmware, error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
|
|
/* align into uint32_t buffer */
|
|
buf = g_bytes_get_data (blob, &bufsz);
|
|
bufsz_dwrds = bufsz / sizeof(guint32);
|
|
buf_dwrds = g_new0 (guint32, bufsz_dwrds);
|
|
if (!fu_memcpy_safe ((guint8 *) buf_dwrds, bufsz_dwrds * sizeof(guint32), 0x0, /* dst */
|
|
buf, bufsz, 0x0, /* src */
|
|
bufsz, error))
|
|
return FALSE;
|
|
|
|
/* hit hardware */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
|
locker = fu_device_locker_new_full (self,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_acquire_lock,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_release_lock,
|
|
error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
locker2 = fu_device_locker_new_full (self,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_enable_write,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_disable,
|
|
error);
|
|
if (locker2 == NULL)
|
|
return FALSE;
|
|
if (!fu_bcm57xx_recovery_device_nvram_write (self, 0x0, buf_dwrds, bufsz_dwrds, error))
|
|
return FALSE;
|
|
if (!fu_device_locker_close (locker2, error))
|
|
return FALSE;
|
|
if (!fu_device_locker_close (locker, error))
|
|
return FALSE;
|
|
|
|
/* reset APE */
|
|
return fu_device_activate (device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE (device);
|
|
guint32 fwversion = 0;
|
|
g_autoptr(FuDeviceLocker) locker = NULL;
|
|
g_autoptr(FuDeviceLocker) locker2 = NULL;
|
|
|
|
locker = fu_device_locker_new_full (self,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_acquire_lock,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_release_lock,
|
|
error);
|
|
if (locker == NULL)
|
|
return FALSE;
|
|
locker2 = fu_device_locker_new_full (self,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_enable,
|
|
(FuDeviceLockerFunc) fu_bcm57xx_recovery_device_nvram_disable,
|
|
error);
|
|
if (locker2 == NULL)
|
|
return FALSE;
|
|
|
|
/* get NVRAM version */
|
|
if (!fu_bcm57xx_recovery_device_nvram_read (self, BCM_NVRAM_STAGE1_BASE + BCM_NVRAM_STAGE1_VERSION,
|
|
&fwversion, 1, error))
|
|
return FALSE;
|
|
if (fwversion != 0x0) {
|
|
g_autofree gchar *fwversion_str = NULL;
|
|
|
|
/* this is only set on the OSS firmware */
|
|
fwversion_str = fu_common_version_from_uint32 (GUINT32_FROM_BE(fwversion),
|
|
FWUPD_VERSION_FORMAT_TRIPLET);
|
|
fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_TRIPLET);
|
|
fu_device_set_version (device, fwversion_str);
|
|
fu_device_set_version_raw (device, fwversion);
|
|
fu_device_set_branch (device, BCM_FW_BRANCH_OSS_FIRMWARE);
|
|
} else {
|
|
guint32 bufver[4] = { 0x0 };
|
|
guint32 veraddr = 0;
|
|
g_autoptr(Bcm57xxVeritem) veritem = NULL;
|
|
|
|
/* fall back to the string, e.g. '5719-v1.43' */
|
|
if (!fu_bcm57xx_recovery_device_nvram_read (self,
|
|
BCM_NVRAM_STAGE1_BASE + BCM_NVRAM_STAGE1_VERADDR,
|
|
&veraddr, 1, error))
|
|
return FALSE;
|
|
veraddr = GUINT32_FROM_BE(veraddr);
|
|
if (veraddr > BCM_PHYS_ADDR_DEFAULT)
|
|
veraddr -= BCM_PHYS_ADDR_DEFAULT;
|
|
if (!fu_bcm57xx_recovery_device_nvram_read (self,
|
|
BCM_NVRAM_STAGE1_BASE + veraddr,
|
|
bufver, 4, error))
|
|
return FALSE;
|
|
veritem = fu_bcm57xx_veritem_new ((guint8 *) bufver, sizeof(bufver));
|
|
if (veritem != NULL) {
|
|
fu_device_set_version (device, veritem->version);
|
|
fu_device_set_branch (device, veritem->branch);
|
|
fu_device_set_version_format (device, veritem->verfmt);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_open (FuDevice *device, GError **error)
|
|
{
|
|
#ifdef HAVE_MMAN_H
|
|
FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE (device);
|
|
FuUdevDevice *udev_device = FU_UDEV_DEVICE (device);
|
|
const gchar *sysfs_path = fu_udev_device_get_sysfs_path (udev_device);
|
|
#endif
|
|
|
|
#ifdef RUNNING_ON_VALGRIND
|
|
/* this can't work */
|
|
if (RUNNING_ON_VALGRIND) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"cannot mmap'ing BARs when using valgrind");
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_MMAN_H
|
|
/* map BARs */
|
|
for (guint i = 0; i < FU_BCM57XX_BAR_MAX; i++) {
|
|
int memfd;
|
|
struct stat st;
|
|
g_autofree gchar *fn = NULL;
|
|
g_autofree gchar *resfn = NULL;
|
|
|
|
/* open 64 bit resource */
|
|
resfn = g_strdup_printf ("resource%u", i * 2);
|
|
fn = g_build_filename (sysfs_path, resfn, NULL);
|
|
memfd = open (fn, O_RDWR | O_SYNC);
|
|
if (memfd < 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"error opening %s", fn);
|
|
return FALSE;
|
|
}
|
|
if (fstat (memfd, &st) < 0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"could not stat %s", fn);
|
|
close (memfd);
|
|
return FALSE;
|
|
}
|
|
|
|
/* mmap */
|
|
if (g_getenv ("FWUPD_BCM57XX_VERBOSE") != NULL)
|
|
g_debug ("mapping BAR[%u] %s for 0x%x bytes", i, fn, (guint) st.st_size);
|
|
self->bar[i].buf = (guint8 *) mmap (0, st.st_size,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_SHARED, memfd, 0);
|
|
self->bar[i].bufsz = st.st_size;
|
|
close (memfd);
|
|
if (self->bar[i].buf == MAP_FAILED) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"cound not mmap %s: %s",
|
|
fn, strerror(errno));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
#else
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"mmap() not supported as sys/mman.h not available");
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_close (FuDevice *device, GError **error)
|
|
{
|
|
#ifdef HAVE_MMAN_H
|
|
FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE (device);
|
|
|
|
/* unmap BARs */
|
|
for (guint i = 0; i < FU_BCM57XX_BAR_MAX; i++) {
|
|
if (self->bar[i].buf == NULL)
|
|
continue;
|
|
if (g_getenv ("FWUPD_BCM57XX_VERBOSE") != NULL)
|
|
g_debug ("unmapping BAR[%u]", i);
|
|
munmap (self->bar[i].buf, self->bar[i].bufsz);
|
|
self->bar[i].buf = NULL;
|
|
self->bar[i].bufsz = 0;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
#else
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"munmap() not supported as sys/mman.h not available");
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
fu_bcm57xx_recovery_device_init (FuBcm57xxRecoveryDevice *self)
|
|
{
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NO_GUID_MATCHING);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IGNORE_VALIDATION);
|
|
fu_device_set_protocol (FU_DEVICE (self), "com.broadcom.bcm57xx");
|
|
fu_device_add_icon (FU_DEVICE (self), "network-wired");
|
|
fu_device_set_logical_id (FU_DEVICE (self), "recovery");
|
|
|
|
/* other values are set from a quirk */
|
|
fu_device_set_firmware_size (FU_DEVICE (self), BCM_FIRMWARE_SIZE);
|
|
|
|
/* no BARs mapped */
|
|
for (guint i = 0; i < FU_BCM57XX_BAR_MAX; i++) {
|
|
self->bar[i].buf = NULL;
|
|
self->bar[i].bufsz = 0;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
fu_bcm57xx_recovery_device_probe (FuUdevDevice *device, GError **error)
|
|
{
|
|
/* success */
|
|
return fu_udev_device_set_physical_id (device, "pci", error);
|
|
}
|
|
|
|
static void
|
|
fu_bcm57xx_recovery_device_class_init (FuBcm57xxRecoveryDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass);
|
|
klass_device->activate = fu_bcm57xx_recovery_device_activate;
|
|
klass_device->prepare_firmware = fu_bcm57xx_recovery_device_prepare_firmware;
|
|
klass_device->setup = fu_bcm57xx_recovery_device_setup;
|
|
klass_device->reload = fu_bcm57xx_recovery_device_setup;
|
|
klass_device->open = fu_bcm57xx_recovery_device_open;
|
|
klass_device->close = fu_bcm57xx_recovery_device_close;
|
|
klass_device->write_firmware = fu_bcm57xx_recovery_device_write_firmware;
|
|
klass_device->dump_firmware = fu_bcm57xx_recovery_device_dump_firmware;
|
|
klass_device->attach = fu_bcm57xx_recovery_device_attach;
|
|
klass_device->detach = fu_bcm57xx_recovery_device_detach;
|
|
klass_udev_device->probe = fu_bcm57xx_recovery_device_probe;
|
|
}
|
|
|
|
FuBcm57xxRecoveryDevice *
|
|
fu_bcm57xx_recovery_device_new (void)
|
|
{
|
|
FuUdevDevice *self = g_object_new (FU_TYPE_BCM57XX_RECOVERY_DEVICE, NULL);
|
|
return FU_BCM57XX_RECOVERY_DEVICE (self);
|
|
}
|