diff --git a/plugins/vli/README.md b/plugins/vli/README.md index e14e31660..d1e4d57ba 100644 --- a/plugins/vli/README.md +++ b/plugins/vli/README.md @@ -44,6 +44,7 @@ Optional PD child devices sharing the SPI flash use just one extra GUID, e.g. Optional I²C child devices use just one extra GUID, e.g. * `USB\VID_17EF&PID_3083&I2C_MSP430` + * `USB\VID_17EF&PID_3083&I2C_PS186` Vendor ID Security ------------------ diff --git a/plugins/vli/fu-vli-common.c b/plugins/vli/fu-vli-common.c index 51595188d..67cd57ea8 100644 --- a/plugins/vli/fu-vli-common.c +++ b/plugins/vli/fu-vli-common.c @@ -92,6 +92,8 @@ fu_vli_common_device_kind_to_string (FuVliDeviceKind device_kind) return "VL212"; if (device_kind == FU_VLI_DEVICE_KIND_MSP430) return "MSP430"; + if (device_kind == FU_VLI_DEVICE_KIND_PS186) + return "PS186"; return NULL; } @@ -146,6 +148,8 @@ fu_vli_common_device_kind_from_string (const gchar *device_kind) return FU_VLI_DEVICE_KIND_VL212; if (g_strcmp0 (device_kind, "MSP430") == 0) return FU_VLI_DEVICE_KIND_MSP430; + if (g_strcmp0 (device_kind, "PS186") == 0) + return FU_VLI_DEVICE_KIND_PS186; return FU_VLI_DEVICE_KIND_UNKNOWN; } @@ -196,6 +200,8 @@ fu_vli_common_device_kind_get_size (FuVliDeviceKind device_kind) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q8) return 0x20000 * 2; + if (device_kind == FU_VLI_DEVICE_KIND_PS186) + return 0x40000; return 0x0; } diff --git a/plugins/vli/fu-vli-common.h b/plugins/vli/fu-vli-common.h index 967f3d9ce..8035f7fd8 100644 --- a/plugins/vli/fu-vli-common.h +++ b/plugins/vli/fu-vli-common.h @@ -35,6 +35,7 @@ typedef enum { FU_VLI_DEVICE_KIND_VL820Q7 = 0xa820, FU_VLI_DEVICE_KIND_VL820Q8 = 0xb820, FU_VLI_DEVICE_KIND_MSP430 = 0xf430, /* guessed */ + FU_VLI_DEVICE_KIND_PS186 = 0xf186, /* guessed */ } FuVliDeviceKind; const gchar *fu_vli_common_device_kind_to_string (FuVliDeviceKind device_kind); diff --git a/plugins/vli/fu-vli-pd-device.c b/plugins/vli/fu-vli-pd-device.c index c3d7f8dd3..2d738a174 100644 --- a/plugins/vli/fu-vli-pd-device.c +++ b/plugins/vli/fu-vli-pd-device.c @@ -11,6 +11,7 @@ #include "fu-vli-pd-device.h" #include "fu-vli-pd-firmware.h" +#include "fu-vli-pd-parade-device.h" struct _FuVliPdDevice { @@ -241,6 +242,33 @@ fu_vli_pd_device_reset_vl103 (FuVliDevice *device, GError **error) NULL, error); } +static gboolean +fu_vli_pd_device_parade_setup (FuVliPdDevice *self, GError **error) +{ + g_autoptr(FuDevice) dev = NULL; + g_autoptr(GError) error_local = NULL; + + /* add child */ + dev = fu_vli_pd_parade_device_new (FU_VLI_DEVICE (self)); + if (!fu_device_probe (dev, &error_local)) { + if (g_error_matches (error_local, + FWUPD_ERROR, + FWUPD_ERROR_NOT_FOUND)) { + g_debug ("%s", error_local->message); + } else { + g_warning ("cannot create I²C parade device: %s", + error_local->message); + } + return TRUE; + } + if (!fu_device_setup (dev, error)) { + g_prefix_error (error, "failed to set up parade device: "); + return FALSE; + } + fu_device_add_child (FU_DEVICE (self), dev); + return TRUE; +} + static gboolean fu_vli_pd_device_setup (FuVliDevice *device, GError **error) { @@ -307,7 +335,11 @@ fu_vli_pd_device_setup (FuVliDevice *device, GError **error) else fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); - /* TODO: detect any I²C child, e.g. parade device */ + /* detect any I²C child, e.g. parade device */ + if (fu_device_has_custom_flag (FU_DEVICE (self), "has-i2c-ps186")) { + if (!fu_vli_pd_device_parade_setup (self, error)) + return FALSE; + } /* success */ return TRUE; diff --git a/plugins/vli/fu-vli-pd-parade-device.c b/plugins/vli/fu-vli-pd-parade-device.c new file mode 100644 index 000000000..b96932937 --- /dev/null +++ b/plugins/vli/fu-vli-pd-parade-device.c @@ -0,0 +1,691 @@ +/* + * Copyright (C) 2015-2019 VIA Corporation + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-chunk.h" + +#include "fu-vli-pd-device.h" +#include "fu-vli-pd-parade-device.h" + +struct _FuVliPdParadeDevice +{ + FuDevice parent_instance; + FuVliDeviceKind device_kind; + guint8 page2; /* base address */ + guint8 page7; /* base address */ +}; + +G_DEFINE_TYPE (FuVliPdParadeDevice, fu_vli_pd_parade_device, FU_TYPE_DEVICE) + +#define FU_VLI_PD_PARADE_I2C_CMD_WRITE 0xa6 +#define FU_VLI_PD_PARADE_I2C_CMD_READ 0xa5 + +static void +fu_vli_pd_parade_device_to_string (FuDevice *device, guint idt, GString *str) +{ + FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE (device); + fu_common_string_append_kv (str, idt, "DeviceKind", + fu_vli_common_device_kind_to_string (self->device_kind)); + fu_common_string_append_kx (str, idt, "Page2", self->page2); + fu_common_string_append_kx (str, idt, "Page7", self->page7); +} + +static gboolean +fu_vli_pd_parade_device_i2c_read (FuVliPdParadeDevice *self, + guint8 page2, + guint8 reg_offset, /* customers addr offset */ + guint8 *buf, + gsize bufsz, + GError **error) +{ + guint16 value; + + /* sanity check */ + if (bufsz > 0x40) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "request too large"); + return FALSE; + } + + /* VL103 FW only Use bits[7:1], so divide by 2 */ + value = ((guint16) reg_offset << 8)| (page2 >> 1); + if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + FU_VLI_PD_PARADE_I2C_CMD_READ, value, 0x0, + buf, bufsz, NULL, + FU_VLI_DEVICE_TIMEOUT, + NULL, error)) { + g_prefix_error (error, "failed to read 0x%x:0x%x: ", page2, reg_offset); + return FALSE; + } + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_i2c_write (FuVliPdParadeDevice *self, + guint8 page2, + guint8 reg_offset, /* customers addr offset */ + guint8 val, /* only one byte supported */ + GError **error) +{ + guint16 value; + guint16 index; + guint8 buf[2] = { 0x0 }; /* apparently unused... */ + + /* VL103 FW only Use bits[7:1], so divide by 2 */ + value = ((guint16) reg_offset << 8) | (page2 >> 1); + index = (guint16) val << 8; + if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + FU_VLI_PD_PARADE_I2C_CMD_WRITE, + value, index, + buf, 0x0, NULL, + FU_VLI_DEVICE_TIMEOUT, + NULL, error)) { + g_prefix_error (error, "failed to write 0x%x:0x%x: ", page2, reg_offset); + return FALSE; + } + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_start_mcu (FuVliPdParadeDevice *self, GError **error) +{ + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xBC, 0x00, error)) { + g_prefix_error (error, "failed to start MCU: "); + return FALSE; + } + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_stop_mcu (FuVliPdParadeDevice *self, GError **error) +{ + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xBC, 0xC0, error)) { + g_prefix_error (error, "failed to stop MCU: "); + return FALSE; + } + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xBC, 0x40, error)) { + g_prefix_error (error, "failed to stop MCU 2nd: "); + return FALSE; + } + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_set_offset (FuVliPdParadeDevice *self, guint16 addr, GError **error) +{ + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x8E, addr >> 8, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x8F, addr & 0xff, error)) + return FALSE; + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_read_fw_ver (FuVliPdParadeDevice *self, GError **error) +{ + guint8 buf[0x20] = { 0x0 }; + g_autofree gchar *version_str = NULL; + + /* stop MCU */ + if (!fu_vli_pd_parade_device_stop_mcu (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_set_offset (self, 0x0, error)) + return FALSE; + g_usleep (1000 * 10); + if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, 0x02, buf, 0x1, error)) + return FALSE; + if (buf[0] != 0x01 && buf[0] != 0x02) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "not supported"); + return FALSE; + } + + g_debug ("getting FW%X version", buf[0]); + if (!fu_vli_pd_parade_device_set_offset (self, 0x5000 | buf[0], error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, 0x00, buf, sizeof(buf), error)) + return FALSE; + + /* start MCU */ + if (!fu_vli_pd_parade_device_start_mcu (self, error)) + return FALSE; + + /* format version triplet */ + version_str = g_strdup_printf ("%u.%u.%u", buf[0], buf[1], buf[2]); + fu_device_set_version (FU_DEVICE (self), version_str, FWUPD_VERSION_FORMAT_TRIPLET); + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_set_wp (FuVliPdParadeDevice *self, gboolean val, GError **error) +{ + return fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xB3, + val ? 0x10 : 0x00, error); +} + +static gboolean +fu_vli_pd_parade_device_write_enable (FuVliPdParadeDevice *self, GError **error) +{ + /* Set_WP_High, SPI_WEN_06, Len_00, Trigger_Write, Set_WP_Low */ + if (!fu_vli_pd_parade_device_set_wp (self, TRUE, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x06, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x92, 0x00, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x93, 0x05, error)) + return FALSE; + if (!fu_vli_pd_parade_device_set_wp (self, FALSE, error)) + return FALSE; + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_write_disable (FuVliPdParadeDevice *self, GError **error) +{ + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x00, error)) + return FALSE; + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_write_status (FuVliPdParadeDevice *self, guint8 target_status, GError **error) +{ + /* Set_WP_High, SPI_WSTS_01, Target_Status, Len_01, Trigger_Write, Set_WP_Low */ + if (!fu_vli_pd_parade_device_set_wp (self, TRUE, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x01, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, target_status, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x92, 0x01, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x93, 0x05, error)) + return FALSE; + if (!fu_vli_pd_parade_device_set_wp (self, FALSE, error)) + return FALSE; + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_wait_ready (FuVliPdParadeDevice *self, GError **error) +{ + gboolean ret = FALSE; + guint limit = 100; + guint8 buf = 0x0; + + /* wait for SPI ROM */ + for (guint wait_cnt1 = 0; wait_cnt1 < limit; wait_cnt1++) { + buf = 0xFF; + if (!fu_vli_pd_parade_device_i2c_read (self, self->page2, + 0x9E, &buf, sizeof(buf), + error)) + return FALSE; + /* busy status: + * bit[1,0]:Byte_Program + * bit[3,2]:Sector Erase + * bit[5,4]:Chip Erase */ + if ((buf & 0x0C) == 0) { + ret = TRUE; + break; + } + } + if (!ret) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to wait for SPI not BUSY"); + return FALSE; + } + + /* wait for SPI ROM status clear */ + ret = FALSE; + for (guint wait_cnt1 = 0; wait_cnt1 < limit; wait_cnt1++) { + gboolean ret2 = FALSE; + + /* SPI_RSTS_05, Len_01, Trigger_Read */ + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x05, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x92, 0x00, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x93, 0x01, error)) + return FALSE; + + /* wait for cmd done */ + for (guint wait_cnt2 = 0; wait_cnt2 < limit; wait_cnt2++) { + buf = 0xFF; + if (!fu_vli_pd_parade_device_i2c_read (self, self->page2, + 0x93, &buf, sizeof(buf), + error)) + return FALSE; + if ((buf & 0x01) == 0) { + ret2 = TRUE; + break; + } + } + if (!ret2) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to wait for SPI CMD done"); + return FALSE; + } + + /* Wait_SPI_STS_00 */ + buf = 0xFF; + if (!fu_vli_pd_parade_device_i2c_read (self, self->page2, + 0x91, &buf, sizeof(buf), + error)) + return FALSE; + if ((buf & 0x01) == 0) { + ret = TRUE; + break; + } + } + if (!ret) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "failed to wait for SPI status clear"); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_sector_erase (FuVliPdParadeDevice *self, guint16 addr, GError **error) +{ + /* SPI_SE_20, SPI_Adr_H, SPI_Adr_M, SPI_Adr_L, Len_03, Trigger_Write */ + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x20, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, addr >> 8, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, addr & 0xff, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x90, 0x00, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x92, 0x03, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x93, 0x05, error)) + return FALSE; + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_enable_mapping (FuVliPdParadeDevice *self, GError **error) +{ + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0xAA, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x55, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x50, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x41, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x52, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0xDA, 0x44, error)) + return FALSE; + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_block_erase (FuVliPdParadeDevice *self, guint8 block_idx, GError **error) +{ + /* erase */ + for (guint idx = 0x00; idx < 0x100; idx += 0x10){ + if (!fu_vli_pd_parade_device_write_enable (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_set_wp (self, TRUE, error)) + return FALSE; + if (!fu_vli_pd_parade_device_sector_erase (self, ((guint16) block_idx << 8) | idx, error)) + return FALSE; + if (!fu_vli_pd_parade_device_wait_ready (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_set_wp (self, FALSE, error)) + return FALSE; + } + + /* verify */ + for (guint idx = 0; idx < 0x100; idx += 0x10){ + guint8 buf[0x20] = { 0xff }; + if (!fu_vli_pd_parade_device_set_offset (self, (block_idx << 8) | idx, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, 0, buf, 0x20, error)) + return FALSE; + for (guint idx2 = 0; idx2 < 0x20; idx2++){ + if (buf[idx2] != 0xFF) { + guint32 addr = (block_idx << 16) + (idx << 8); + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "Erase failed @0x%x", addr); + return FALSE; + } + } + } + + /* success */ + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_block_write (FuVliPdParadeDevice *self, + guint8 block_idx, + const guint8 *txbuf, + GError **error) +{ + for (guint idx = 0; idx < 0x100; idx++) { + if (!fu_vli_pd_parade_device_set_offset (self, (block_idx << 8) | idx, error)) + return FALSE; + for (guint idx2 = 0; idx2 < 0x100; idx2++){ + guint32 buf_offset = (idx << 8) + idx2; + if (!fu_vli_pd_parade_device_i2c_write (self, + self->page7, + (guint8)idx2, + txbuf[buf_offset], + error)) + return FALSE; + } + } + + /* success */ + return TRUE; +} + +static GBytes * +_g_bytes_new_sized (gsize sz) +{ + guint8 *buf = g_malloc0 (sz); + return g_bytes_new_take (buf, sz); +} + +static gboolean +fu_vli_pd_parade_device_block_read (FuVliPdParadeDevice *self, + guint8 block_idx, + guint8 *buf, + gsize bufsz, + GError **error) +{ + for (guint idx = 0; idx < 0x100; idx++){ + if (!fu_vli_pd_parade_device_set_offset (self, (block_idx << 8) | idx, error)) + return FALSE; + for (guint idx2 = 0; idx2 < 0x100; idx2 += 0x20){ + guint buf_offset = (idx << 8) + idx2; + if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, idx2, buf + buf_offset, 0x20, error)) + return FALSE; + } + } + return TRUE; +} + +static gboolean +fu_vli_pd_parade_device_write_firmware (FuDevice *device, + FuFirmware *firmware, + FwupdInstallFlags flags, + GError **error) +{ + FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE (device); + FuVliPdDevice *parent = FU_VLI_PD_DEVICE (fu_device_get_parent (device)); + guint8 buf[0x20]; + guint block_idx_tmp; + g_autoptr(FuDeviceLocker) locker = NULL; + g_autoptr(GBytes) fw = NULL; + g_autoptr(GBytes) fw_verify = NULL; + g_autoptr(GPtrArray) blocks = NULL; + g_autoptr(GPtrArray) blocks_verify = NULL; + + /* simple image */ + fw = fu_firmware_get_image_default_bytes (firmware, error); + if (fw == NULL) + return FALSE; + + /* open device */ + locker = fu_device_locker_new (parent, error); + if (locker == NULL) + return FALSE; + + /* stop MPU and reset SPI */ + if (!fu_vli_pd_parade_device_stop_mcu (self, error)) + return FALSE; + + /* 64K block erase */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE); + if (!fu_vli_pd_parade_device_write_enable (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_write_status (self, 0x00, error)) + return FALSE; + if (!fu_vli_pd_parade_device_wait_ready (self, error)) + return FALSE; + blocks = fu_chunk_array_new_from_bytes (fw, 0x0, 0x0, 0x10000); + for (guint i = 1; i < blocks->len; i++) { + FuChunk *block = g_ptr_array_index (blocks, i); + if (!fu_vli_pd_parade_device_block_erase (self, block->idx, error)) + return FALSE; + fu_device_set_progress_full (FU_DEVICE (self), i, blocks->len); + } + + /* load F/W to SPI ROM */ + if (!fu_vli_pd_parade_device_enable_mapping (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x82, 0x20, error)) + return FALSE; /* Reset_CLT2SPI_Interface */ + g_usleep (1000 * 100); + if (!fu_vli_pd_parade_device_i2c_write (self, self->page2, 0x82, 0x00, error)) + return FALSE; + + /* write blocks */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE); + for (guint i = 1; i < blocks->len; i++) { + FuChunk *block = g_ptr_array_index (blocks, i); + if (!fu_vli_pd_parade_device_block_write (self, block->idx, block->data, error)) + return FALSE; + fu_device_set_progress_full (FU_DEVICE (self), i, blocks->len); + } + if (!fu_vli_pd_parade_device_write_disable (self, error)) + return FALSE; + + /* verify SPI ROM */ + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY); + fw_verify = _g_bytes_new_sized (g_bytes_get_size (fw)); + blocks_verify = fu_chunk_array_new_from_bytes (fw_verify, 0x0, 0x0, 0x10000); + for (guint i = 1; i < blocks_verify->len; i++) { + FuChunk *block = g_ptr_array_index (blocks_verify, i); + if (!fu_vli_pd_parade_device_block_read (self, + block->idx, + (guint8 *) block->data, + block->data_sz, + error)) + return FALSE; + fu_device_set_progress_full (FU_DEVICE (self), i, blocks->len); + } + if (!fu_common_bytes_compare (fw, fw_verify, error)) + return FALSE; + + /* save boot config into Block_0 */ + if (!fu_vli_pd_parade_device_write_enable (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_set_wp (self, TRUE, error)) + return FALSE; + if (!fu_vli_pd_parade_device_sector_erase (self, 0x0, error)) + return FALSE; + if (!fu_vli_pd_parade_device_wait_ready (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_set_wp (self, FALSE, error)) + return FALSE; + + /* Page_HW_Write_Disable */ + if (!fu_vli_pd_parade_device_enable_mapping (self, error)) + return FALSE; + + block_idx_tmp = 1; + if (!fu_vli_pd_parade_device_set_offset (self, 0x0, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, 0x00, 0x55, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, 0x01, 0xAA, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, 0x02, (guint8) block_idx_tmp, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_write (self, self->page7, 0x03, (guint8) (0x01 - block_idx_tmp), error)) + return FALSE; + if (!fu_vli_pd_parade_device_write_disable (self, error)) + return FALSE; + + /* check boot config data */ + if (!fu_vli_pd_parade_device_set_offset (self, 0x0, error)) + return FALSE; + if (!fu_vli_pd_parade_device_i2c_read (self, self->page7, 0, buf, sizeof(buf), error)) + return FALSE; + if (buf[0] != 0x55 || + buf[1] != 0xAA || + buf[2] != block_idx_tmp || + buf[3] != 0x01 - block_idx_tmp) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "boot config data error"); + return FALSE; + } + + /* enable write protection */ + if (!fu_vli_pd_parade_device_write_enable (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_write_status (self, 0x8C, error)) + return FALSE; + if (!fu_vli_pd_parade_device_wait_ready (self, error)) + return FALSE; + if (!fu_vli_pd_parade_device_write_disable (self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static FuFirmware * +fu_vli_pd_parade_device_read_firmware (FuDevice *device, GError **error) +{ + FuVliPdDevice *parent = FU_VLI_PD_DEVICE (fu_device_get_parent (device)); + FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE (device); + g_autoptr(FuDeviceLocker) locker = NULL; + g_autoptr(GBytes) fw = NULL; + g_autoptr(GPtrArray) blocks = NULL; + + /* open device */ + locker = fu_device_locker_new (parent, error); + if (locker == NULL) + return NULL; + + /* stop MPU and reset SPI */ + if (!fu_vli_pd_parade_device_stop_mcu (self, error)) + return NULL; + + /* read */ + fu_device_set_status (FU_DEVICE (device), FWUPD_STATUS_DEVICE_VERIFY); + fw = _g_bytes_new_sized (fu_device_get_firmware_size_max (device)); + blocks = fu_chunk_array_new_from_bytes (fw, 0x0, 0x0, 0x10000); + for (guint i = 0; i < blocks->len; i++) { + FuChunk *block = g_ptr_array_index (blocks, i); + if (!fu_vli_pd_parade_device_block_read (self, + block->idx, + (guint8 *) block->data, + block->data_sz, + error)) + return NULL; + } + return fu_firmware_new_from_bytes (fw); +} + + +static FuFirmware * +fu_vli_pd_parade_device_prepare_firmware (FuDevice *device, + GBytes *fw, + FwupdInstallFlags flags, + GError **error) +{ + /* check size */ + if (g_bytes_get_size (fw) < fu_device_get_firmware_size_min (device)) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "firmware too small, got 0x%x, expected >= 0x%x", + (guint) g_bytes_get_size (fw), + (guint) fu_device_get_firmware_size_min (device)); + return NULL; + } + return fu_firmware_new_from_bytes (fw); +} + +static gboolean +fu_vli_pd_parade_device_probe (FuDevice *device, GError **error) +{ + FuVliPdDevice *parent = FU_VLI_PD_DEVICE (fu_device_get_parent (device)); + FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE (device); + g_autofree gchar *instance_id1 = NULL; + + /* get version */ + if (!fu_vli_pd_parade_device_read_fw_ver (self, error)) + return FALSE; + + /* use header to populate device info */ + instance_id1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&I2C_%s", + fu_usb_device_get_vid (FU_USB_DEVICE (parent)), + fu_usb_device_get_pid (FU_USB_DEVICE (parent)), + fu_vli_common_device_kind_to_string (self->device_kind)); + fu_device_add_instance_id (device, instance_id1); + + /* success */ + return TRUE; +} + +static void +fu_vli_pd_parade_device_init (FuVliPdParadeDevice *self) +{ + self->device_kind = FU_VLI_DEVICE_KIND_PS186; + self->page2 = 0x14; + self->page7 = 0x1E; + fu_device_add_icon (FU_DEVICE (self), "video-display"); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_set_protocol (FU_DEVICE (self), "com.vli.i2c"); + fu_device_set_install_duration (FU_DEVICE (self), 15); /* seconds */ + fu_device_set_logical_id (FU_DEVICE (self), "PS186"); + fu_device_set_summary (FU_DEVICE (self), "DisplayPort 1.4a to HDMI 2.0b Protocol Converter"); + fu_device_set_firmware_size (FU_DEVICE (self), 0x40000); +} + +static void +fu_vli_pd_parade_device_class_init (FuVliPdParadeDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + klass_device->to_string = fu_vli_pd_parade_device_to_string; + klass_device->probe = fu_vli_pd_parade_device_probe; + klass_device->read_firmware = fu_vli_pd_parade_device_read_firmware; + klass_device->prepare_firmware = fu_vli_pd_parade_device_prepare_firmware; + klass_device->write_firmware = fu_vli_pd_parade_device_write_firmware; +} + +FuDevice * +fu_vli_pd_parade_device_new (FuVliDevice *parent) +{ + FuVliPdParadeDevice *self = g_object_new (FU_TYPE_VLI_PD_PARADE_DEVICE, + "parent", parent, + NULL); + return FU_DEVICE (self); +} diff --git a/plugins/vli/fu-vli-pd-parade-device.h b/plugins/vli/fu-vli-pd-parade-device.h new file mode 100644 index 000000000..f687a53e6 --- /dev/null +++ b/plugins/vli/fu-vli-pd-parade-device.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-plugin.h" + +#include "fu-vli-pd-common.h" + +#define FU_TYPE_VLI_PD_PARADE_DEVICE (fu_vli_pd_parade_device_get_type ()) +G_DECLARE_FINAL_TYPE (FuVliPdParadeDevice, fu_vli_pd_parade_device, FU, VLI_PD_PARADE_DEVICE, FuDevice) + +struct _FuVliPdParadeDeviceClass +{ + FuDeviceClass parent_class; +}; + +FuDevice *fu_vli_pd_parade_device_new (FuVliDevice *parent); diff --git a/plugins/vli/meson.build b/plugins/vli/meson.build index d07d2d24e..30e10a01f 100644 --- a/plugins/vli/meson.build +++ b/plugins/vli/meson.build @@ -17,6 +17,7 @@ shared_module('fu_plugin_vli', 'fu-vli-pd-common.c', 'fu-vli-pd-device.c', 'fu-vli-pd-firmware.c', + 'fu-vli-pd-parade-device.c', 'fu-vli-usbhub-common.c', 'fu-vli-usbhub-device.c', 'fu-vli-usbhub-firmware.c',