mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-11 10:05:30 +00:00
725 lines
21 KiB
C
725 lines
21 KiB
C
/*
|
|
* Copyright (C) 2017 VIA Corporation
|
|
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-chunk.h"
|
|
|
|
#include "fu-vli-device.h"
|
|
|
|
typedef struct {
|
|
FuUsbDevice parent_instance;
|
|
FuVliDeviceKind kind;
|
|
gboolean spi_auto_detect;
|
|
FuVliDeviceSpiReq spi_cmds[FU_VLI_DEVICE_SPI_REQ_LAST];
|
|
guint8 spi_cmd_read_id_sz;
|
|
guint32 flash_id;
|
|
} FuVliDevicePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (FuVliDevice, fu_vli_device, FU_TYPE_USB_DEVICE)
|
|
|
|
#define GET_PRIVATE(o) (fu_vli_device_get_instance_private (o))
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_KIND,
|
|
PROP_LAST
|
|
};
|
|
|
|
static const gchar *
|
|
fu_vli_device_spi_req_to_string (FuVliDeviceSpiReq req)
|
|
{
|
|
if (req == FU_VLI_DEVICE_SPI_REQ_READ_ID)
|
|
return "SpiCmdReadId";
|
|
if (req == FU_VLI_DEVICE_SPI_REQ_PAGE_PROG)
|
|
return "SpiCmdPageProg";
|
|
if (req == FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE)
|
|
return "SpiCmdChipErase";
|
|
if (req == FU_VLI_DEVICE_SPI_REQ_READ_DATA)
|
|
return "SpiCmdReadData";
|
|
if (req == FU_VLI_DEVICE_SPI_REQ_READ_STATUS)
|
|
return "SpiCmdReadStatus";
|
|
if (req == FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE)
|
|
return "SpiCmdSectorErase";
|
|
if (req == FU_VLI_DEVICE_SPI_REQ_WRITE_EN)
|
|
return "SpiCmdWriteEn";
|
|
if (req == FU_VLI_DEVICE_SPI_REQ_WRITE_STATUS)
|
|
return "SpiCmdWriteStatus";
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
fu_vli_device_get_spi_cmd (FuVliDevice *self,
|
|
FuVliDeviceSpiReq req,
|
|
guint8 *cmd,
|
|
GError **error)
|
|
{
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
if (req >= FU_VLI_DEVICE_SPI_REQ_LAST) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"SPI req invalid");
|
|
return FALSE;
|
|
}
|
|
if (priv->spi_cmds[req] == 0x0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"No defined SPI cmd for %s",
|
|
fu_vli_device_spi_req_to_string (req));
|
|
return FALSE;
|
|
}
|
|
if (cmd != NULL)
|
|
*cmd = priv->spi_cmds[req];
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_spi_write_enable (FuVliDevice *self, GError **error)
|
|
{
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self);
|
|
if (klass->spi_write_enable != NULL) {
|
|
if (!klass->spi_write_enable (self, error)) {
|
|
g_prefix_error (error, "failed to write enable SPI: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_spi_chip_erase (FuVliDevice *self, GError **error)
|
|
{
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self);
|
|
if (klass->spi_chip_erase != NULL) {
|
|
if (!klass->spi_chip_erase (self, error)) {
|
|
g_prefix_error (error, "failed to erase SPI data: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_spi_write_status (FuVliDevice *self, guint8 status, GError **error)
|
|
{
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self);
|
|
if (klass->spi_write_status != NULL) {
|
|
if (!klass->spi_write_status (self, status, error)) {
|
|
g_prefix_error (error, "failed to write SPI status 0x%x: ", status);
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_spi_read_status (FuVliDevice *self, guint8 *status, GError **error)
|
|
{
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self);
|
|
if (klass->spi_read_status != NULL) {
|
|
if (!klass->spi_read_status (self, status, error)) {
|
|
g_prefix_error (error, "failed to read status: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_spi_sector_erase (FuVliDevice *self, guint32 addr, GError **error)
|
|
{
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self);
|
|
if (klass->spi_sector_erase != NULL) {
|
|
if (!klass->spi_sector_erase (self, addr, error)) {
|
|
g_prefix_error (error, "failed to erase SPI data @0x%x: ", addr);
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_vli_device_spi_read_block (FuVliDevice *self, guint32 addr,
|
|
guint8 *buf, gsize bufsz, GError **error)
|
|
{
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self);
|
|
if (klass->spi_read_data != NULL) {
|
|
if (!klass->spi_read_data (self, addr, buf, bufsz, error)) {
|
|
g_prefix_error (error, "failed to read SPI data @0x%x: ", addr);
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_spi_write_data (FuVliDevice *self, guint32 addr,
|
|
const guint8 *buf, gsize bufsz, GError **error)
|
|
{
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (self);
|
|
if (klass->spi_write_data != NULL) {
|
|
if (!klass->spi_write_data (self, addr, buf, bufsz, error)) {
|
|
g_prefix_error (error, "failed to write SPI data @0x%x: ", addr);
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_spi_wait_finish (FuVliDevice *self, GError **error)
|
|
{
|
|
const guint32 rdy_cnt = 2;
|
|
guint32 cnt = 0;
|
|
|
|
for (guint32 idx = 0; idx < 1000; idx++) {
|
|
guint8 status = 0x7f;
|
|
|
|
/* must get bit[1:0] == 0 twice in a row for success */
|
|
if (!fu_vli_device_spi_read_status (self, &status, error))
|
|
return FALSE;
|
|
if ((status & 0x03) == 0x00) {
|
|
if (cnt++ >= rdy_cnt)
|
|
return TRUE;
|
|
} else {
|
|
cnt = 0;
|
|
}
|
|
g_usleep (500 * 1000);
|
|
}
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to wait for SPI");
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
fu_vli_device_spi_erase_sector (FuVliDevice *self, guint32 addr, GError **error)
|
|
{
|
|
const guint32 bufsz = 0x1000;
|
|
|
|
/* erase sector */
|
|
if (!fu_vli_device_spi_write_enable (self, error)) {
|
|
g_prefix_error (error, "->spi_write_enable failed: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_vli_device_spi_write_status (self, 0x00, error)) {
|
|
g_prefix_error (error, "->spi_write_status failed: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_vli_device_spi_write_enable (self, error)) {
|
|
g_prefix_error (error, "->spi_write_enable failed: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_vli_device_spi_sector_erase (self, addr, error)) {
|
|
g_prefix_error (error, "->spi_sector_erase failed: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_vli_device_spi_wait_finish (self, error)) {
|
|
g_prefix_error (error, "->spi_wait_finish failed: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* verify it really was blanked */
|
|
for (guint32 offset = 0; offset < bufsz; offset += FU_VLI_DEVICE_TXSIZE) {
|
|
guint8 buf[FU_VLI_DEVICE_TXSIZE] = { 0x0 };
|
|
if (!fu_vli_device_spi_read_block (self,
|
|
addr + offset,
|
|
buf, sizeof (buf),
|
|
error)) {
|
|
g_prefix_error (error, "failed to read back empty: ");
|
|
return FALSE;
|
|
}
|
|
for (guint i = 0; i < sizeof(buf); i++) {
|
|
if (buf[i] != 0xff) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to check blank @0x%x",
|
|
addr + offset + i);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
GBytes *
|
|
fu_vli_device_spi_read (FuVliDevice *self, guint32 address, gsize bufsz, GError **error)
|
|
{
|
|
g_autofree guint8 *buf = g_malloc0 (bufsz);
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* get data from hardware */
|
|
chunks = fu_chunk_array_mutable_new (buf, bufsz, address, 0x0, FU_VLI_DEVICE_TXSIZE);
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index (chunks, i);
|
|
if (!fu_vli_device_spi_read_block (self,
|
|
fu_chunk_get_address (chk),
|
|
fu_chunk_get_data_out (chk),
|
|
fu_chunk_get_data_sz (chk),
|
|
error)) {
|
|
g_prefix_error (error,
|
|
"SPI data read failed @0x%x: ",
|
|
fu_chunk_get_address (chk));
|
|
return NULL;
|
|
}
|
|
fu_device_set_progress_full (FU_DEVICE (self),
|
|
(gsize) i, (gsize) chunks->len);
|
|
}
|
|
return g_bytes_new_take (g_steal_pointer (&buf), bufsz);
|
|
}
|
|
|
|
gboolean
|
|
fu_vli_device_spi_write_block (FuVliDevice *self,
|
|
guint32 address,
|
|
const guint8 *buf,
|
|
gsize bufsz,
|
|
GError **error)
|
|
{
|
|
g_autofree guint8 *buf_tmp = g_malloc0 (bufsz);
|
|
|
|
/* sanity check */
|
|
if (bufsz > FU_VLI_DEVICE_TXSIZE) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"cannot write 0x%x in one block",
|
|
(guint) bufsz);
|
|
return FALSE;
|
|
}
|
|
|
|
/* write */
|
|
if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL)
|
|
g_debug ("writing 0x%x block @0x%x", (guint) bufsz, address);
|
|
if (!fu_vli_device_spi_write_enable (self, error)) {
|
|
g_prefix_error (error, "enabling SPI write failed: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_vli_device_spi_write_data (self, address, buf, bufsz, error)) {
|
|
g_prefix_error (error, "SPI data write failed: ");
|
|
return FALSE;
|
|
}
|
|
g_usleep (800);
|
|
|
|
/* verify */
|
|
if (!fu_vli_device_spi_read_block (self, address, buf_tmp, bufsz, error)) {
|
|
g_prefix_error (error, "SPI data read failed: ");
|
|
return FALSE;
|
|
}
|
|
return fu_common_bytes_compare_raw (buf, bufsz, buf_tmp, bufsz, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_vli_device_spi_write (FuVliDevice *self,
|
|
guint32 address,
|
|
const guint8 *buf,
|
|
gsize bufsz,
|
|
GError **error)
|
|
{
|
|
FuChunk *chk;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* write SPI data, then CRC bytes last */
|
|
g_debug ("writing 0x%x bytes @0x%x", (guint) bufsz, address);
|
|
chunks = fu_chunk_array_new (buf, bufsz, 0x0, 0x0, FU_VLI_DEVICE_TXSIZE);
|
|
if (chunks->len > 1) {
|
|
for (guint i = 1; i < chunks->len; i++) {
|
|
chk = g_ptr_array_index (chunks, i);
|
|
if (!fu_vli_device_spi_write_block (self,
|
|
fu_chunk_get_address (chk) + address,
|
|
fu_chunk_get_data (chk),
|
|
fu_chunk_get_data_sz (chk),
|
|
error)) {
|
|
g_prefix_error (error, "failed to write block 0x%x: ", fu_chunk_get_idx (chk));
|
|
return FALSE;
|
|
}
|
|
fu_device_set_progress_full (FU_DEVICE (self),
|
|
(gsize) i - 1,
|
|
(gsize) chunks->len);
|
|
}
|
|
}
|
|
chk = g_ptr_array_index (chunks, 0);
|
|
if (!fu_vli_device_spi_write_block (self,
|
|
fu_chunk_get_address (chk) + address,
|
|
fu_chunk_get_data (chk),
|
|
fu_chunk_get_data_sz (chk),
|
|
error)) {
|
|
g_prefix_error (error, "failed to write CRC block: ");
|
|
return FALSE;
|
|
}
|
|
fu_device_set_progress_full (FU_DEVICE (self), (gsize) chunks->len, (gsize) chunks->len);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_vli_device_spi_erase_all (FuVliDevice *self, GError **error)
|
|
{
|
|
fu_device_set_progress (FU_DEVICE (self), 0);
|
|
if (!fu_vli_device_spi_write_enable (self, error))
|
|
return FALSE;
|
|
if (!fu_vli_device_spi_write_status (self, 0x00, error))
|
|
return FALSE;
|
|
if (!fu_vli_device_spi_write_enable (self, error))
|
|
return FALSE;
|
|
if (!fu_vli_device_spi_chip_erase (self, error))
|
|
return FALSE;
|
|
fu_device_sleep_with_progress (FU_DEVICE (self), 4); /* seconds */
|
|
|
|
/* verify chip was erased */
|
|
for (guint addr = 0; addr < 0x10000; addr += 0x1000) {
|
|
guint8 buf[FU_VLI_DEVICE_TXSIZE] = { 0x0 };
|
|
if (!fu_vli_device_spi_read_block (self, addr, buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read @0x%x: ", addr);
|
|
return FALSE;
|
|
}
|
|
for (guint i = 0; i < sizeof(buf); i++) {
|
|
if (buf[i] != 0xff) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to verify erase @0x%x: ", addr);
|
|
return FALSE;
|
|
}
|
|
}
|
|
fu_device_set_progress_full (FU_DEVICE (self),
|
|
(gsize) addr, (gsize) 0x10000);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_vli_device_spi_erase (FuVliDevice *self, guint32 addr, gsize sz, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) chunks = fu_chunk_array_new (NULL, sz, addr, 0x0, 0x1000);
|
|
g_debug ("erasing 0x%x bytes @0x%x", (guint) sz, addr);
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index (chunks, i);
|
|
if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL)
|
|
g_debug ("erasing @0x%x", fu_chunk_get_address (chk));
|
|
if (!fu_vli_device_spi_erase_sector (FU_VLI_DEVICE (self),
|
|
fu_chunk_get_address (chk),
|
|
error)) {
|
|
g_prefix_error (error,
|
|
"failed to erase FW sector @0x%x: ",
|
|
fu_chunk_get_address (chk));
|
|
return FALSE;
|
|
}
|
|
fu_device_set_progress_full (FU_DEVICE (self),
|
|
(gsize) i, (gsize) chunks->len);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gchar *
|
|
fu_vli_device_get_flash_id_str (FuVliDevice *self)
|
|
{
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
if (priv->spi_cmd_read_id_sz == 4)
|
|
return g_strdup_printf ("%08X", priv->flash_id);
|
|
if (priv->spi_cmd_read_id_sz == 2)
|
|
return g_strdup_printf ("%04X", priv->flash_id);
|
|
if (priv->spi_cmd_read_id_sz == 1)
|
|
return g_strdup_printf ("%02X", priv->flash_id);
|
|
return g_strdup_printf ("%X", priv->flash_id);
|
|
}
|
|
|
|
void
|
|
fu_vli_device_set_kind (FuVliDevice *self, FuVliDeviceKind device_kind)
|
|
{
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
guint32 sz;
|
|
|
|
/* set and notify if different */
|
|
if (priv->kind != device_kind) {
|
|
priv->kind = device_kind;
|
|
g_object_notify (G_OBJECT (self), "kind");
|
|
}
|
|
|
|
/* set maximum firmware size */
|
|
sz = fu_vli_common_device_kind_get_size (device_kind);
|
|
if (sz > 0x0)
|
|
fu_device_set_firmware_size_max (FU_DEVICE (self), sz);
|
|
}
|
|
|
|
void
|
|
fu_vli_device_set_spi_auto_detect (FuVliDevice *self, gboolean spi_auto_detect)
|
|
{
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
priv->spi_auto_detect = spi_auto_detect;
|
|
}
|
|
|
|
FuVliDeviceKind
|
|
fu_vli_device_get_kind (FuVliDevice *self)
|
|
{
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
return priv->kind;
|
|
}
|
|
|
|
guint32
|
|
fu_vli_device_get_offset (FuVliDevice *self)
|
|
{
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
return fu_vli_common_device_kind_get_offset (priv->kind);
|
|
}
|
|
|
|
static void
|
|
fu_vli_device_to_string (FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (device);
|
|
FuVliDevice *self = FU_VLI_DEVICE (device);
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
fu_common_string_append_kv (str, idt, "DeviceKind",
|
|
fu_vli_common_device_kind_to_string (priv->kind));
|
|
fu_common_string_append_kb (str, idt, "SpiAutoDetect", priv->spi_auto_detect);
|
|
if (priv->flash_id != 0x0) {
|
|
g_autofree gchar *tmp = fu_vli_device_get_flash_id_str (self);
|
|
fu_common_string_append_kv (str, idt, "FlashId", tmp);
|
|
}
|
|
for (guint i = 0; i < FU_VLI_DEVICE_SPI_REQ_LAST; i++) {
|
|
fu_common_string_append_kx (str, idt,
|
|
fu_vli_device_spi_req_to_string (i),
|
|
priv->spi_cmds[i]);
|
|
}
|
|
|
|
/* subclassed further */
|
|
if (klass->to_string != NULL)
|
|
return klass->to_string (self, idt, str);
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_spi_read_flash_id (FuVliDevice *self, GError **error)
|
|
{
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
guint8 buf[4] = { 0x0 };
|
|
if (!g_usb_device_control_transfer (usb_device,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0xc0 | (priv->spi_cmd_read_id_sz * 2),
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_ID],
|
|
0x0000, buf, sizeof(buf), NULL,
|
|
FU_VLI_DEVICE_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "failed to read chip ID: ");
|
|
return FALSE;
|
|
}
|
|
if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL)
|
|
fu_common_dump_raw (G_LOG_DOMAIN, "SpiCmdReadId", buf, sizeof(buf));
|
|
if (priv->spi_cmd_read_id_sz == 4) {
|
|
if (!fu_common_read_uint32_safe (buf, sizeof(buf), 0x0,
|
|
&priv->flash_id,
|
|
G_BIG_ENDIAN, error))
|
|
return FALSE;
|
|
} else if (priv->spi_cmd_read_id_sz == 2) {
|
|
guint16 tmp = 0;
|
|
if (!fu_common_read_uint16_safe (buf, sizeof(buf), 0x0,
|
|
&tmp, G_BIG_ENDIAN, error))
|
|
return FALSE;
|
|
priv->flash_id = tmp;
|
|
} else if (priv->spi_cmd_read_id_sz == 1) {
|
|
guint8 tmp = 0;
|
|
if (!fu_common_read_uint8_safe (buf, sizeof(buf), 0x0,
|
|
&tmp, error))
|
|
return FALSE;
|
|
priv->flash_id = tmp;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuVliDevice *self = FU_VLI_DEVICE (device);
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS (device);
|
|
|
|
/* get the flash chip attached */
|
|
if (priv->spi_auto_detect) {
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
if (!fu_vli_device_spi_read_flash_id (self, error)) {
|
|
g_prefix_error (error, "failed to read SPI chip ID: ");
|
|
return FALSE;
|
|
}
|
|
if (priv->flash_id != 0x0) {
|
|
g_autofree gchar *spi_id = NULL;
|
|
g_autofree gchar *devid1 = NULL;
|
|
g_autofree gchar *devid2 = NULL;
|
|
g_autofree gchar *flash_id = fu_vli_device_get_flash_id_str (self);
|
|
|
|
/* load the SPI parameters from quirks */
|
|
spi_id = g_strdup_printf ("VLI_USBHUB\\SPI_%s", flash_id);
|
|
fu_device_add_instance_id_full (FU_DEVICE (self), spi_id,
|
|
FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS);
|
|
|
|
/* add extra instance IDs to include the SPI variant */
|
|
devid2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SPI_%s&REV_%04X",
|
|
g_usb_device_get_vid (usb_device),
|
|
g_usb_device_get_pid (usb_device),
|
|
flash_id,
|
|
g_usb_device_get_release (usb_device));
|
|
fu_device_add_instance_id (device, devid2);
|
|
devid1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SPI_%s",
|
|
g_usb_device_get_vid (usb_device),
|
|
g_usb_device_get_pid (usb_device),
|
|
flash_id);
|
|
fu_device_add_instance_id (device, devid1);
|
|
}
|
|
}
|
|
|
|
/* subclassed further */
|
|
if (klass->setup != NULL) {
|
|
if (!klass->setup (self, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* add extra DEV GUID too */
|
|
if (priv->kind != FU_VLI_DEVICE_KIND_UNKNOWN) {
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self));
|
|
g_autofree gchar *devid1 = NULL;
|
|
devid1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&DEV_%s",
|
|
g_usb_device_get_vid (usb_device),
|
|
g_usb_device_get_pid (usb_device),
|
|
fu_vli_common_device_kind_to_string (priv->kind));
|
|
fu_device_add_instance_id (device, devid1);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_vli_device_set_quirk_kv (FuDevice *device,
|
|
const gchar *key,
|
|
const gchar *value,
|
|
GError **error)
|
|
{
|
|
FuVliDevice *self = FU_VLI_DEVICE (device);
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
if (g_strcmp0 (key, "VliSpiCmdReadId") == 0) {
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_ID] = fu_common_strtoull (value);
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0 (key, "VliSpiCmdReadIdSz") == 0) {
|
|
priv->spi_cmd_read_id_sz = fu_common_strtoull (value);
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0 (key, "VliSpiCmdChipErase") == 0) {
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE] = fu_common_strtoull (value);
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0 (key, "VliSpiCmdSectorErase") == 0) {
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE] = fu_common_strtoull (value);
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0 (key, "VliSpiAutoDetect") == 0) {
|
|
priv->spi_auto_detect = fu_common_strtoull (value) > 0;
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0 (key, "VliDeviceKind") == 0) {
|
|
FuVliDeviceKind device_kind;
|
|
device_kind = fu_vli_common_device_kind_from_string (value);
|
|
if (device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"VliDeviceKind %s is not supported",
|
|
value);
|
|
return FALSE;
|
|
}
|
|
fu_vli_device_set_kind (self, device_kind);
|
|
return TRUE;
|
|
}
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"quirk key not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
fu_vli_device_report_metadata_pre (FuDevice *device, GHashTable *metadata)
|
|
{
|
|
FuVliDevice *self = FU_VLI_DEVICE (device);
|
|
g_hash_table_insert (metadata,
|
|
g_strdup ("GType"),
|
|
g_strdup (G_OBJECT_TYPE_NAME (self)));
|
|
}
|
|
|
|
static void
|
|
fu_vli_device_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
|
|
{
|
|
FuVliDevice *self = FU_VLI_DEVICE (object);
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
switch (prop_id) {
|
|
case PROP_KIND:
|
|
g_value_set_uint (value, priv->kind);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fu_vli_device_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
|
|
{
|
|
FuVliDevice *self = FU_VLI_DEVICE (object);
|
|
switch (prop_id) {
|
|
case PROP_KIND:
|
|
fu_vli_device_set_kind (self, g_value_get_uint (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fu_vli_device_init (FuVliDevice *self)
|
|
{
|
|
FuVliDevicePrivate *priv = GET_PRIVATE (self);
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_WRITE_STATUS] = 0x01;
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_PAGE_PROG] = 0x02;
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_DATA] = 0x03;
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_STATUS] = 0x05;
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_WRITE_EN] = 0x06;
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE] = 0x20;
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE] = 0x60;
|
|
priv->spi_cmds[FU_VLI_DEVICE_SPI_REQ_READ_ID] = 0x9f;
|
|
priv->spi_cmd_read_id_sz = 2;
|
|
priv->spi_auto_detect = TRUE;
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS);
|
|
}
|
|
|
|
static void
|
|
fu_vli_device_class_init (FuVliDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GParamSpec *pspec;
|
|
|
|
/* properties */
|
|
object_class->get_property = fu_vli_device_get_property;
|
|
object_class->set_property = fu_vli_device_set_property;
|
|
pspec = g_param_spec_uint ("kind", NULL, NULL,
|
|
0, G_MAXUINT, 0,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_NAME);
|
|
g_object_class_install_property (object_class, PROP_KIND, pspec);
|
|
|
|
klass_device->to_string = fu_vli_device_to_string;
|
|
klass_device->set_quirk_kv = fu_vli_device_set_quirk_kv;
|
|
klass_device->setup = fu_vli_device_setup;
|
|
klass_device->report_metadata_pre = fu_vli_device_report_metadata_pre;
|
|
}
|