diff --git a/contrib/ci/oss-fuzz.py b/contrib/ci/oss-fuzz.py index e7c4c219e..7526d7a6a 100755 --- a/contrib/ci/oss-fuzz.py +++ b/contrib/ci/oss-fuzz.py @@ -352,6 +352,8 @@ def _build(bld: Builder) -> None: Fuzzer("ebitdo"), Fuzzer("elanfp"), Fuzzer("elantp"), + Fuzzer("genesys-scaler", srcdir="genesys", pattern="genesys-scaler-firmware"), + Fuzzer("genesys-usbhub", srcdir="genesys", pattern="genesys-usbhub-firmware"), Fuzzer("pixart", srcdir="pixart-rf", pattern="pxi-firmware"), Fuzzer("redfish-smbios", srcdir="redfish", pattern="redfish-smbios"), Fuzzer("synaptics-prometheus", pattern="synaprom-firmware"), diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index 5612d8fef..f3cd1c65f 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -435,6 +435,7 @@ done %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ep963x.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_fastboot.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_fresco_pd.so +%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_genesys.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_gpio.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_hailuck.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_iommu.so diff --git a/plugins/genesys/README.md b/plugins/genesys/README.md new file mode 100644 index 000000000..e903f268b --- /dev/null +++ b/plugins/genesys/README.md @@ -0,0 +1,134 @@ +# Genesys Logic + +## Introduction + +This plugin allows updating the Genesys Logic USB Hub devices. + +* GL3523 +* GL3590 + +Additionally, this plugin allows updating the MStar Semiconductor Scaler connected via an I²C bus. + +* TSUM G + +## Firmware Format + +The daemon will decompress the cabinet archives and extract the firmware blob in an unspecified binary file format. + +This plugin supports the following protocol IDs: + +* com.genesys.usbhub +* com.mstarsemi.scaler + +## GUID Generation + +These devices use the standard USB DeviceInstanceId values for the USB Hub, e.g. + +* HP Mxfd FHD Monitor: `USB\VID_03F0&PID_0610` + +These devices also use custom GUID values for the Scaler, e.g. + +* HP M24fd USB-C Monitor: `GENESYS_SCALER\MSTAR_TSUM_G&PUBKEY_B335BDCE-7073-5D0E-9BD3-9B69C1A6899F` +* HP M27fd USB-C Monitor: `GENESYS_SCALER\MSTAR_TSUM_G&PUBKEY_847A3650-8648-586B-83C8-8B53714F37E3` + +The Public Key is product-specific and is required to identify the product. + +## Quirk Use + +This plugin uses the following plugin-specific quirks: + +### has-mstar-scaler + +USB Hub has a MStar Semiconductor Scaler attached via I²C. + +Since 1.7.6. + +### GenesysUsbhubSwitchRequest + +USB Hub Switch Request value. + +* HP Mxfd FHD Monitors: `0xA1` + +Since 1.7.6. + +### GenesysUsbhubReadRequest + +USB Hub Read Request value. + +* HP Mxfd FHD Monitors: `0xA2` + +Since 1.7.6. + +### GenesysUsbhubWriteRequest + +USB Hub Write Request value. + +* HP Mxfd FHD Monitors: `0xA3` + +Since 1.7.6. + +### use-i2c-ch0 + +Scalar uses I²C channel 0. + +Since 1.7.6. + +### pause-r2-cpu + +Scalar pause R2 CPU. + +Since 1.7.6. + +### GenesysScalerDeviceTransferSize + +Scaler Block size to use for transfers. + +* MStar Semiconductor TSUM G: `0x40` + +Since 1.7.6. + +### GenesysScalerGpioOutputRegister + +Scaler GPIO Output Register value. + +* MStar Semiconductor TSUM G: `0x0426` + +Since 1.7.6. + +### GenesysScalerGpioEnableRegister + +Scaler GPIO Enable Register value. + +* MStar Semiconductor TSUM G: `0x0428` + +Since 1.7.6. + +### GenesysScalerGpioValue + +Scaler GPIO value. + +* MStar Semiconductor TSUM G: `0x01` + +Since 1.7.6. + +## Runtime Requirement + +The USB Hub devices and its attached Scaler require libgusb version [0.3.8][1] or later to be detected. + +## Update Behavior + +The devices are independently updated at runtime using USB control transfers. + +The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. + +The HP Mxfd FHD Monitors must be connected to host via the USB-C cable to apply an update. The devices remain functional during the update; the Scaler update is ~10 minute long. + +## Vendor ID Security + +The vendor ID is set from the USB vendor, for example set to `USB:0x03F0` for HP. + +## External Interface Access + +This plugin requires read/write access to `/dev/bus/usb`. + +[1]: https://github.com/hughsie/libgusb/commit/4e118c154dde70e196c4381bd97790a9413c3552 diff --git a/plugins/genesys/fu-genesys-common.h b/plugins/genesys/fu-genesys-common.h new file mode 100644 index 000000000..ada76cece --- /dev/null +++ b/plugins/genesys/fu-genesys-common.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Gaël PORTAY + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +typedef struct { + guint8 reg; + guint8 expected_val; +} FuGenesysWaitFlashRegisterHelper; + +typedef struct __attribute__((packed)) { + guint8 tool_string_version; /* 0xff = not supported */ + + /* byte arrays are ASCII encoded and not NUL terminated */ + guint8 mask_project_code[4]; + guint8 mask_project_hardware[1]; /* 0=a, 1=b... */ + guint8 mask_project_firmware[2]; /* 01,02,03... */ + guint8 mask_project_ic_type[6]; /* 352310=GL3523-10 (ASCII string) */ + + guint8 running_project_code[4]; + guint8 running_project_hardware[1]; + guint8 running_project_firmware[2]; + guint8 running_project_ic_type[6]; + + guint8 firmware_version[4]; /* MMmm=MM.mm (ASCII string) */ +} FuGenesysStaticToolString; diff --git a/plugins/genesys/fu-genesys-scaler-device.c b/plugins/genesys/fu-genesys-scaler-device.c new file mode 100644 index 000000000..98fa96702 --- /dev/null +++ b/plugins/genesys/fu-genesys-scaler-device.c @@ -0,0 +1,1820 @@ +/* + * Copyright (C) 2021 Gaël PORTAY + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include +#include + +#include "fu-genesys-common.h" +#include "fu-genesys-scaler-device.h" +#include "fu-genesys-scaler-firmware.h" + +#define GENESYS_SCALER_MSTAR_READ 0x7a +#define GENESYS_SCALER_MSTAR_WRITE 0x7b + +#define GENESYS_SCALER_CMD_DATA_WRITE 0x10 +#define GENESYS_SCALER_CMD_DATA_READ 0x11 +#define GENESYS_SCALER_CMD_DATA_END 0x12 + +#define GENESYS_SCALER_INFO 0xA4 + +#define GENESYS_SCALER_USB_TIMEOUT 5000 /* ms */ + +/** + * FU_SCALER_FLAG_PAUSE_R2_CPU: + * + * Pause R2 CPU. + */ +#define FU_SCALER_FLAG_PAUSE_R2_CPU (1 << 1) +/** + * FU_SCALER_FLAG_USE_I2C_CH0: + * + * Use I2C ch0. + */ +#define FU_SCALER_FLAG_USE_I2C_CH0 (1 << 0) + +typedef struct { + guint8 req_read; + guint8 req_write; +} FuGenesysVendorCommand; + +struct _FuGenesysScalerDevice { + FuDevice parent_instance; + guint8 level; + guint8 public_key[0x212]; + FuCfiDevice *cfi_device; + FuGenesysVendorCommand vc; + guint32 sector_size; + guint32 page_size; + guint32 transfer_size; + guint16 gpio_out_reg; + guint16 gpio_en_reg; + guint8 gpio_val; + FuGenesysMtkFooter footer; +}; + +G_DEFINE_TYPE(FuGenesysScalerDevice, fu_genesys_scaler_device, FU_TYPE_DEVICE) + +static gboolean +fu_genesys_scaler_device_enter_serial_debug_mode(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x53, 0x45, 0x52, 0x44, 0x42}; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + data, /* data */ + sizeof(data), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error entering Serial Debug Mode: "); + return FALSE; + } + + g_usleep(1000); /* 1 ms */ + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_exit_serial_debug_mode(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x45}; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + data, /* data */ + sizeof(data), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error exiting Serial Debug Mode: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_enter_single_step_mode(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data1[] = {0x10, 0xc0, 0xc1, 0x53}; + guint8 data2[] = {0x10, 0x1f, 0xc1, 0x53}; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + data1, /* data */ + sizeof(data1), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error entering Single Step Mode: "); + return FALSE; + } + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + data2, /* data */ + sizeof(data2), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error entering Single Step Mode: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_exit_single_step_mode(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x10, 0xc0, 0xc1, 0xff}; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + data, /* data */ + sizeof(data), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error exiting Single Step Mode: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_enter_debug_mode(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x10, 0x00, 0x00, 0x00}; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + data, /* data */ + sizeof(data), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error entering Debug Mode: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_mst_i2c_bus_ctrl(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x35, 0x71}; + + for (guint i = 0; i < sizeof(data); i++) { + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + &data[i], /* data */ + sizeof(data[i]), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error sending i2c bus ctrl 0x%02x: ", data[i]); + return FALSE; + } + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_mst_i2c_bus_switch_to_ch0(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x80, 0x82, 0x84, 0x51, 0x7f, 0x37, 0x61}; + + for (guint i = 0; i < sizeof(data); i++) { + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + &data[i], /* data */ + sizeof(data[i]), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error sending i2c bus ch0 0x%02x: ", data[i]); + return FALSE; + } + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_mst_i2c_bus_switch_to_ch4(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x80, 0x82, 0x85, 0x53, 0x7f}; + + for (guint i = 0; i < sizeof(data); i++) { + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + &data[i], /* data */ + sizeof(data[i]), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error sending i2c bus ch4 0x%02x: ", data[i]); + return FALSE; + } + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_disable_wp(FuGenesysScalerDevice *self, gboolean disable, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data_out[] = {0x10, + 0x00 /* gpio_out_reg_h */, + 0x00 /* gpio_out_reg_l */, + 0x00 /* gpio_out_val */}; + guint8 data_en[] = {0x10, + 0x00 /* gpio_en_reg_h */, + 0x00 /* gpio_en_reg_l */, + 0x00 /* gpio_en_val */}; + + /* disable write protect [output] */ + + data_out[1] = (self->gpio_out_reg & 0xff00) >> 8; + data_out[2] = self->gpio_out_reg & 0x00ff; + + /* read gpio-out register */ + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0003, /* value */ + 0x0000, /* idx */ + data_out, /* data */ + sizeof(data_out) - 1, /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error reading GPIO-Out Register 0x%02x%02x: ", + data_out[1], + data_out[2]); + return FALSE; + } + + 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, + self->vc.req_read, + 0x0003, /* value */ + 0x0000, /* idx */ + &data_out[3], /* data */ + sizeof(data_out[3]), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error reading GPIO-Out Register 0x%02x%02x: ", + data_out[1], + data_out[2]); + return FALSE; + } + + if (data_out[3] == 0xff) { + g_prefix_error(error, + "error reading GPIO-Out Register 0x%02x%02x: ", + data_out[1], + data_out[2]); + return FALSE; + } + + if (disable) + data_out[3] |= self->gpio_val; /* pull high */ + else + data_out[3] &= ~self->gpio_val; /* pull low */ + + /* write gpio-out register */ + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + data_out, /* data */ + sizeof(data_out), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error writing GPIO-Out Register 0x%02x%02x=0x%02x: ", + data_out[1], + data_out[2], + data_out[3]); + return FALSE; + } + + /* disable write protect [enable] */ + + data_en[1] = (self->gpio_en_reg & 0xff00) >> 8; + data_en[2] = self->gpio_en_reg & 0x00ff; + + /* read gpio-enable register */ + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0003, /* value */ + 0x0000, /* idx */ + data_en, /* data */ + sizeof(data_en) - 1, /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error writing GPIO-Enable Register 0x%02x%02x: ", + data_en[1], + data_en[2]); + return FALSE; + } + + 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, + self->vc.req_read, + 0x0003, /* value */ + 0x0000, /* idx */ + &data_en[3], /* data */ + sizeof(data_en[3]), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error reading GPIO-Out Register 0x%02x%02x: ", + data_en[1], + data_en[2]); + return FALSE; + } + + if (data_en[3] == 0xff) { + g_prefix_error(error, + "error reading GPIO-Enable Register 0x%02x%02x: ", + data_en[1], + data_en[2]); + return FALSE; + } + + data_en[3] &= ~self->gpio_val; + + /* write gpio-enable register */ + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0001, /* value */ + 0x0000, /* idx */ + data_en, /* data */ + sizeof(data_en), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error writing GPIO-Enable Register 0x%02x%02x=0x%02x: ", + data_en[1], + data_en[2], + data_en[3]); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_pause_r2_cpu(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x10, 0x00, 0x10, 0x0F, 0xD7, 0x00}; + + /* + * MST9U only! + * + * Pause R2 CPU for preventing Scaler entering Power Saving Mode also + * need for Disable SPI Flash Write Protect Mode. + */ + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0003, /* value */ + 0x0000, /* idx */ + data, /* data */ + sizeof(data) - 1, /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error reading register 0x%02x%02x%02x%02x%02x: ", + data[0], + data[1], + data[2], + data[3], + data[4]); + return FALSE; + } + + 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, + self->vc.req_read, + 0x0003, /* value */ + 0x0000, /* idx */ + &data[5], /* data */ + sizeof(data[5]), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error reading register 0x%02x%02x%02x%02x%02x: ", + data[0], + data[1], + data[2], + data[3], + data[4]); + return FALSE; + } + + if (data[5] == 0xff) { + g_prefix_error(error, + "error reading register 0x%02x%02x%02x%02x%02x: ", + data[0], + data[1], + data[2], + data[3], + data[4]); + return FALSE; + } + + data[5] |= 0x80; + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0003, /* value */ + 0x0000, /* idx */ + data, /* data */ + sizeof(data), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error writing register 0x%02x%02x%02x%02x%02x: ", + data[0], + data[1], + data[2], + data[3], + data[4]); + return FALSE; + } + + g_usleep(200000); /* 200ms */ + + /* success */ + return TRUE; +} + +static gboolean +fu_geensys_scaler_device_set_isp_mode(FuDevice *device, gpointer user_data, GError **error) +{ + FuGenesysScalerDevice *self = user_data; + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x4d, 0x53, 0x54, 0x41, 0x52}; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data, /* data */ + sizeof(data), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + return FALSE; + } + + g_usleep(1000); /* 1ms */ + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_enter_isp_mode(FuGenesysScalerDevice *self, GError **error) +{ + /* + * Enter ISP mode: + * + * Note: MStar application note say execute twice to avoid race + * condition + */ + + if (!fu_device_retry_full(FU_DEVICE(self), + fu_geensys_scaler_device_set_isp_mode, + 2, + 1000 /* 1ms */, + self, + error)) { + g_prefix_error(error, "error entering ISP mode: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_exit_isp_mode(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data[] = {0x24}; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data, /* data */ + sizeof(data), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error exiting ISP mode: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_detach(FuDevice *device, FuProgress *progress, GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + + /* + * Important: do not change the order below; otherwise, unexpected + * condition occurs. + */ + + if (!fu_genesys_scaler_device_enter_serial_debug_mode(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_enter_single_step_mode(self, error)) + return FALSE; + + if (fu_device_has_private_flag(device, FU_SCALER_FLAG_USE_I2C_CH0)) + if (!fu_genesys_scaler_device_mst_i2c_bus_switch_to_ch0(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_enter_debug_mode(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_mst_i2c_bus_ctrl(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_disable_wp(self, TRUE, error)) + return FALSE; + + if (fu_device_has_private_flag(device, FU_SCALER_FLAG_PAUSE_R2_CPU)) { + if (!fu_genesys_scaler_device_mst_i2c_bus_switch_to_ch4(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_mst_i2c_bus_ctrl(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_pause_r2_cpu(self, error)) + return FALSE; + } + + if (!fu_genesys_scaler_device_enter_isp_mode(self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_attach(FuDevice *device, FuProgress *progress, GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + + if (!fu_genesys_scaler_device_exit_single_step_mode(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_exit_serial_debug_mode(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_exit_isp_mode(self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_get_level(FuGenesysScalerDevice *self, guint8 *level, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + + 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, + GENESYS_SCALER_INFO, + 0x0004, /* value */ + 0x0000, /* idx */ + level, /* data */ + 1, /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error getting level: "); + return FALSE; + } + + g_usleep(100000); /* 100ms */ + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_get_version(FuGenesysScalerDevice *self, + guint8 *buf, + guint bufsz, + GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + + 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, + GENESYS_SCALER_INFO, + 0x0005, /* value */ + 0x0000, /* idx */ + buf, /* data */ + bufsz, /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error getting version: "); + return FALSE; + } + + g_usleep(100000); /* 100ms */ + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_get_public_key(FuGenesysScalerDevice *self, + guint8 *buf, + guint bufsz, + GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + const gsize data_size = 0x20; + g_autoptr(GPtrArray) chunks = NULL; + + chunks = fu_chunk_array_mutable_new(buf, bufsz, 0, 0, data_size); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + + 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, + GENESYS_SCALER_INFO, + 0x0006, /* value */ + fu_chunk_get_address(chk), /* idx */ + fu_chunk_get_data_out(chk), /* data */ + fu_chunk_get_data_sz(chk), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error getting public key: "); + return FALSE; + } + + g_usleep(100000); /* 100ms */ + } + + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_read_flash(FuGenesysScalerDevice *self, + guint addr, + guint8 *buf, + guint bufsz, + FuProgress *progress, + GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data1[] = { + GENESYS_SCALER_CMD_DATA_WRITE, + 0x00, /* Read Data command */ + (addr & 0x00ff0000) >> 16, + (addr & 0x0000ff00) >> 8, + (addr & 0x000000ff), + }; + guint8 data2[] = { + GENESYS_SCALER_CMD_DATA_READ, + }; + guint8 data3[] = { + GENESYS_SCALER_CMD_DATA_END, + }; + g_autoptr(GPtrArray) chunks = NULL; + + if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_READ_DATA, &data1[1], error)) + return FALSE; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data1, /* data */ + sizeof(data1), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error reading flash at 0x%06x: ", addr); + return FALSE; + } + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data2, /* data */ + sizeof(data2), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error reading flash at 0x%06x: ", addr); + return FALSE; + } + + chunks = fu_chunk_array_mutable_new(buf, bufsz, addr, 0, self->transfer_size); + fu_progress_set_id(progress, G_STRLOC); + fu_progress_set_steps(progress, chunks->len); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + + 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, + self->vc.req_read, + 0x0000, /* value */ + 0x0000, /* idx */ + fu_chunk_get_data_out(chk), /* data */ + fu_chunk_get_data_sz(chk), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error reading flash at 0x%06x: ", + fu_chunk_get_address(chk)); + return FALSE; + } + fu_progress_step_done(progress); + } + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data3, /* data */ + sizeof(data3), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error reading flash at 0x%06x: ", addr); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_wait_flash_control_register_cb(FuDevice *dev, + gpointer user_data, + GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(dev); + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + FuGenesysWaitFlashRegisterHelper *helper = user_data; + guint8 status = 0; + + 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, + self->vc.req_read, + helper->reg << 8 | 0x04, /* value */ + 0x0000, /* idx */ + &status, /* data */ + sizeof(status), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error reading flash control register: "); + return FALSE; + } + + if ((status & 0x81) != helper->expected_val) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "wrong value in flash control register"); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_flash_control_write_enable(FuGenesysScalerDevice *self, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data1[] = { + GENESYS_SCALER_CMD_DATA_WRITE, + 0x00, /* Write Enable command */ + }; + guint8 data2[] = { + GENESYS_SCALER_CMD_DATA_END, + }; + + if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_WRITE_EN, &data1[1], error)) + return FALSE; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data1, /* data */ + sizeof(data1), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error sending flash control write enable: "); + return FALSE; + } + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data2, /* data */ + sizeof(data2), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error sending flash control write enable: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_flash_control_write_status(FuGenesysScalerDevice *self, + guint8 status, + GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + guint8 data1[] = { + GENESYS_SCALER_CMD_DATA_WRITE, + 0x00, /* Write Status command */ + status, + }; + guint8 data2[] = { + GENESYS_SCALER_CMD_DATA_END, + }; + + if (!fu_cfi_device_get_cmd(self->cfi_device, + FU_CFI_DEVICE_CMD_WRITE_STATUS, + &data1[1], + error)) + return FALSE; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data1, /* data */ + sizeof(data1), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error sending flash control write status 0x%02x: ", status); + return FALSE; + } + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data2, /* data */ + sizeof(data2), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error sending flash control write status 0x%02x: ", status); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_flash_control_sector_erase(FuGenesysScalerDevice *self, + guint addr, + GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + FuGenesysWaitFlashRegisterHelper helper = { + .reg = 0x00, /* Read Status command */ + .expected_val = 0, + }; + guint8 data1[] = { + GENESYS_SCALER_CMD_DATA_WRITE, + 0x00, /* Sector Erase command */ + (addr & 0x00ff0000) >> 16, + (addr & 0x0000ff00) >> 8, + (addr & 0x000000ff), + }; + guint8 data2[] = { + GENESYS_SCALER_CMD_DATA_END, + }; + + if (!fu_cfi_device_get_cmd(self->cfi_device, + FU_CFI_DEVICE_CMD_READ_STATUS, + &helper.reg, + error)) + return FALSE; + + if (!fu_cfi_device_get_cmd(self->cfi_device, + FU_CFI_DEVICE_CMD_SECTOR_ERASE, + &data1[1], + error)) + return FALSE; + + if (!fu_genesys_scaler_device_flash_control_write_enable(self, error)) + return FALSE; + + if (!fu_genesys_scaler_device_flash_control_write_status(self, 0x00, error)) + return FALSE; + + /* 5s: 500x10ms retries */ + if (!fu_device_retry(FU_DEVICE(self), + fu_genesys_scaler_device_wait_flash_control_register_cb, + 500, + &helper, + error)) { + g_prefix_error(error, "error waiting for flash control read status register: "); + return FALSE; + } + + if (!fu_genesys_scaler_device_flash_control_write_enable(self, error)) + return FALSE; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data1, /* data */ + sizeof(data1), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error sending flash control erase at address 0x%06x: ", + addr); + return FALSE; + } + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + 0x0000, /* value */ + 0x0000, /* idx */ + data2, /* data */ + sizeof(data2), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error sending flash control erase at address 0x%06x: ", + addr); + return FALSE; + } + + /* 5s: 500x10ms retries */ + if (!fu_device_retry(FU_DEVICE(self), + fu_genesys_scaler_device_wait_flash_control_register_cb, + 500, + &helper, + error)) { + g_prefix_error(error, "error waiting for flash control read status register: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_erase_flash(FuGenesysScalerDevice *self, + guint addr, + guint bufsz, + FuProgress *progress, + GError **error) +{ + g_autoptr(GPtrArray) chunks = NULL; + + chunks = fu_chunk_array_new(NULL, bufsz, addr, 0, self->sector_size); + fu_progress_set_id(progress, G_STRLOC); + fu_progress_set_steps(progress, chunks->len); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + + if (!fu_genesys_scaler_device_flash_control_sector_erase(self, + fu_chunk_get_address(chk), + error)) { + g_prefix_error(error, + "error erasing flash at address 0x%06x: ", + fu_chunk_get_address(chk)); + return FALSE; + } + fu_progress_step_done(progress); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_flash_control_page_program(FuGenesysScalerDevice *self, + guint addr, + const guint8 *buf, + guint bufsz, + FuProgress *progress, + GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); + FuGenesysWaitFlashRegisterHelper helper = { + .reg = 0x00, /* Read Status command */ + .expected_val = 0, + }; + guint8 data1[] = { + GENESYS_SCALER_CMD_DATA_WRITE, + 0x00, /* Page Program command */ + (addr & 0x00ff0000) >> 16, + (addr & 0x0000ff00) >> 8, + (addr & 0x000000ff), + }; + gsize datasz = 0; + g_autoptr(GPtrArray) chunks = NULL; + g_autofree guint8 *data = NULL; + + if (!fu_cfi_device_get_cmd(self->cfi_device, + FU_CFI_DEVICE_CMD_READ_STATUS, + &helper.reg, + error)) + return FALSE; + + if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_PAGE_PROG, &data1[1], error)) + return FALSE; + + datasz = sizeof(data1) + bufsz; + data = g_malloc0(datasz); + if (!fu_memcpy_safe(data, + datasz, + 0, /* dst */ + data1, + sizeof(data1), + 0, /* src */ + sizeof(data1), + error)) + return FALSE; + if (!fu_memcpy_safe(data, + datasz, + sizeof(data1), /* dst */ + buf, + bufsz, + 0, /* src */ + bufsz, + error)) + return FALSE; + + chunks = + fu_chunk_array_mutable_new(data, datasz, addr + sizeof(data1), 0, self->transfer_size); + fu_progress_set_id(progress, G_STRLOC); + fu_progress_set_steps(progress, chunks->len); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + guint16 index = 0x0010 * (i + 1); + /* last chunk */ + if ((i + 1) == chunks->len) + index |= 0x0080; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vc.req_write, + index, /* value */ + 0x0000, /* idx */ + fu_chunk_get_data_out(chk), /* data */ + fu_chunk_get_data_sz(chk), /* data length */ + NULL, /* actual length */ + GENESYS_SCALER_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error( + error, + "error sending flash control page program at address 0x%06x: ", + fu_chunk_get_address(chk)); + return FALSE; + } + fu_progress_step_done(progress); + } + + /* 200ms: 20x10ms retries */ + if (!fu_device_retry(FU_DEVICE(self), + fu_genesys_scaler_device_wait_flash_control_register_cb, + 20, + &helper, + error)) { + g_prefix_error(error, "error waiting for flash control read status register: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_write_sector(FuGenesysScalerDevice *self, + guint addr, + const guint8 *buf, + guint bufsz, + FuProgress *progress, + GError **error) +{ + g_autoptr(GPtrArray) chunks = NULL; + + chunks = fu_chunk_array_new(buf, bufsz, addr, 0, self->page_size); + fu_progress_set_id(progress, G_STRLOC); + fu_progress_set_steps(progress, chunks->len); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + + if (!fu_genesys_scaler_device_flash_control_page_program( + self, + fu_chunk_get_address(chk), + fu_chunk_get_data(chk), + fu_chunk_get_data_sz(chk), + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_write_flash(FuGenesysScalerDevice *self, + guint addr, + const guint8 *buf, + guint bufsz, + FuProgress *progress, + GError **error) +{ + g_autoptr(GPtrArray) chunks = NULL; + + chunks = fu_chunk_array_new(buf, bufsz, addr, 0, self->sector_size); + fu_progress_set_id(progress, G_STRLOC); + fu_progress_set_steps(progress, chunks->len); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + + if (!fu_genesys_scaler_device_write_sector(self, + fu_chunk_get_address(chk), + fu_chunk_get_data(chk), + fu_chunk_get_data_sz(chk), + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_probe(FuDevice *device, GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + guint8 buf[7 + 1] = {0}; + g_autofree gchar *guid = NULL; + g_autofree gchar *guid_upper = NULL; + g_autofree gchar *instance_id = NULL; + g_autofree gchar *version = NULL; + + if (!fu_genesys_scaler_device_get_level(self, &self->level, error)) + return FALSE; + + if (!fu_genesys_scaler_device_get_public_key(self, + self->public_key, + sizeof(self->public_key), + error)) + return FALSE; + guid = + fwupd_guid_hash_data(self->public_key, sizeof(self->public_key), FWUPD_GUID_FLAG_NONE); + + if (!fu_genesys_scaler_device_get_version(self, buf, sizeof(buf), error)) + return FALSE; + version = + fu_common_strsafe((const gchar *)&buf[1], 6); /* ?RIM123; where ? is 0x06 (length?) */ + + fu_device_set_version(device, version); + fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN); + fu_device_set_logical_id(device, "scaler"); + guid_upper = g_ascii_strup(guid, -1); + instance_id = g_strdup_printf("GENESYS_SCALER\\MSTAR_TSUM_G&PUBKEY_%s", guid_upper); + fu_device_add_instance_id(device, instance_id); + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); + + self->vc.req_read = GENESYS_SCALER_MSTAR_READ; + self->vc.req_write = GENESYS_SCALER_MSTAR_WRITE; + if (self->level != 1) { + self->vc.req_read += 3; + self->vc.req_write += 3; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_scaler_device_open(FuDevice *device, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(device); + + return fu_device_open(parent_device, error); +} + +static gboolean +fu_genesys_scaler_device_close(FuDevice *device, GError **error) +{ + FuDevice *parent_device = fu_device_get_parent(device); + + return fu_device_open(parent_device, error); +} + +static gboolean +fu_genesys_scaler_device_setup(FuDevice *device, GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + guint32 sector_size; + guint32 page_size; + + self->cfi_device = fu_cfi_device_new(fu_device_get_context(FU_DEVICE(self)), "C84016"); + if (!fu_device_probe(FU_DEVICE(self->cfi_device), error)) + return FALSE; + + sector_size = fu_cfi_device_get_sector_size(self->cfi_device); + if (sector_size != 0) + self->sector_size = sector_size; + page_size = fu_cfi_device_get_page_size(self->cfi_device); + if (page_size != 0) + self->page_size = page_size; + + if (fu_device_get_firmware_size_max(device) == 0) { + guint64 size_max = fu_device_get_firmware_size_max(FU_DEVICE(self->cfi_device)); + + if (size_max == 0) + size_max = 0x400000; + + if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) + size_max /= 2; + + fu_device_set_firmware_size_max(FU_DEVICE(self), size_max); + } + + /* success */ + return TRUE; +} + +static GBytes * +fu_genesys_scaler_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + gsize size = fu_device_get_firmware_size_max(device); + guint addr = 0x000000; + g_autofree guint8 *buf = NULL; + g_autoptr(FuDeviceLocker) locker = NULL; + + if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) + addr = 0x200000; + + /* progress */ + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); /* detach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 99); + + /* require detach -> attach */ + locker = fu_device_locker_new_full(device, + (FuDeviceLockerFunc)fu_device_detach, + (FuDeviceLockerFunc)fu_device_attach, + error); + if (locker == NULL) + return NULL; + fu_progress_step_done(progress); + + buf = g_malloc0(size); + if (!fu_genesys_scaler_device_read_flash(self, + addr, + buf, + size, + fu_progress_get_child(progress), + error)) + return NULL; + fu_progress_step_done(progress); + + return g_bytes_new_take(g_steal_pointer(&buf), size); +} + +static FuFirmware * +fu_genesys_scaler_device_prepare_firmware(FuDevice *device, + GBytes *fw, + FwupdInstallFlags flags, + GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + g_autoptr(FuFirmware) firmware = fu_genesys_scaler_firmware_new(); + g_autoptr(FuFirmware) footer = fu_firmware_new(); + g_autoptr(FuFirmware) payload = fu_firmware_new(); + g_autoptr(GBytes) fw_payload = NULL; + g_autoptr(GBytes) fw_footer = NULL; + + /* parse firmware */ + if (!fu_firmware_parse(firmware, fw, flags, error)) + return NULL; + + /* payload */ + fw_payload = fu_common_bytes_new_offset(fw, + 0, + g_bytes_get_size(fw) - sizeof(FuGenesysMtkFooter), + error); + if (fw_payload == NULL) + return NULL; + if (!fu_firmware_parse(payload, fw_payload, flags, error)) + return NULL; + fu_firmware_set_id(payload, FU_FIRMWARE_ID_PAYLOAD); + fu_firmware_add_image(firmware, payload); + + /* footer */ + fw_footer = fu_common_bytes_new_offset(fw, + g_bytes_get_size(fw) - sizeof(FuGenesysMtkFooter), + sizeof(FuGenesysMtkFooter), + error); + if (!fu_firmware_parse(footer, fw_footer, flags, error)) + return NULL; + if (!fu_memcpy_safe((guint8 *)&self->footer, + sizeof(self->footer), + 0, /* dst */ + g_bytes_get_data(fw_footer, NULL), + g_bytes_get_size(fw_footer), + 0, /* src */ + sizeof(self->footer), + error)) + return NULL; + fu_genesys_scaler_firmware_decrypt((guint8 *)&self->footer, sizeof(self->footer)); + if (g_getenv("FWUPD_GENESYS_SCALER_VERBOSE") != NULL) { + fu_common_dump_raw(G_LOG_DOMAIN, + "Footer", + (const guint8 *)&self->footer, + sizeof(self->footer)); + } + if (memcmp(self->footer.data.header.default_head, + MTK_RSA_HEADER, + sizeof(self->footer.data.header.default_head)) != 0) { + g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid footer"); + return NULL; + } + if (memcmp(&self->footer.data.public_key, + self->public_key, + sizeof(self->footer.data.public_key)) != 0) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "mismatch public-key"); + return NULL; + } + fu_firmware_set_id(footer, FU_FIRMWARE_ID_HEADER); + fu_firmware_add_image(firmware, footer); + + /* success */ + return g_steal_pointer(&firmware); +} + +static gboolean +fu_genesys_scaler_device_write_firmware(FuDevice *device, + FuFirmware *firmware, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + guint addr = 0x000000; + gsize size = 0x200000; + const guint8 *data; + g_autofree guint8 *buf = NULL; + g_autoptr(FuFirmware) payload = NULL; + g_autoptr(GBytes) fw_payload = NULL; + + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 4); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 54); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 42); + + if (self->footer.data.header.configuration_setting.bits.second_image) { + addr = fu_common_read_uint32( + (const guint8 *)self->footer.data.header.second_image_program_addr, + G_LITTLE_ENDIAN); + } + + payload = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_PAYLOAD, error); + if (payload == NULL) + return FALSE; + fw_payload = fu_firmware_get_bytes(payload, error); + if (fw_payload == NULL) + return FALSE; + data = g_bytes_get_data(fw_payload, &size); + if (data == NULL) + return FALSE; + + if (!fu_genesys_scaler_device_erase_flash(self, + addr, + size, + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + + if (!fu_genesys_scaler_device_write_flash(self, + addr, + data, + size, + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + + buf = g_malloc0(size); + if (!fu_genesys_scaler_device_read_flash(self, + addr, + buf, + size, + fu_progress_get_child(progress), + error)) + return FALSE; + if (!fu_common_bytes_compare_raw(buf, size, data, size, error)) + return FALSE; + fu_progress_step_done(progress); + + /* success */ + return TRUE; +} + +static void +fu_genesys_scaler_device_set_progress(FuDevice *self, FuProgress *progress) +{ + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* detach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100); /* write */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* attach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0); /* reload */ +} + +static void +fu_genesys_scaler_device_to_string(FuDevice *device, guint idt, GString *str) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + gchar public_key_e[6 + 1] = {0}; + gchar public_key_n[0x200 + 1] = {0}; + g_autoptr(GError) error_local_e = NULL; + g_autoptr(GError) error_local_n = NULL; + + fu_common_string_append_kx(str, idt, "Level", self->level); + if (fu_memcpy_safe((guint8 *)public_key_e, + sizeof(public_key_e), + 0, /* dst */ + self->public_key, + sizeof(self->public_key), + sizeof(self->public_key) - 2 - (sizeof(public_key_e) - 1), /* src */ + sizeof(public_key_e) - 1, + &error_local_e)) { + fu_common_string_append_kv(str, idt, "PublicKeyE", public_key_e); + } else { + g_debug("ignoring public-key parameter E: %s", error_local_e->message); + } + if (fu_memcpy_safe((guint8 *)public_key_n, + sizeof(public_key_n), + 0, /* dst */ + self->public_key, + sizeof(self->public_key), + 4, /* src */ + sizeof(public_key_n) - 1, + &error_local_n)) { + fu_common_string_append_kv(str, idt, "PublicKeyN", public_key_n); + } else { + g_debug("ignoring public-key parameter N: %s", error_local_n->message); + } + fu_common_string_append_kx(str, idt, "ReadRequestRead", self->vc.req_read); + fu_common_string_append_kx(str, idt, "WriteRequest", self->vc.req_write); + fu_common_string_append_kx(str, idt, "SectorSize", self->sector_size); + fu_common_string_append_kx(str, idt, "PageSize", self->page_size); + fu_common_string_append_kx(str, idt, "TransferSize", self->transfer_size); + fu_common_string_append_kx(str, idt, "GpioOutputRegister", self->gpio_out_reg); + fu_common_string_append_kx(str, idt, "GpioEnableRegister", self->gpio_en_reg); + fu_common_string_append_kx(str, idt, "GpioValue", self->gpio_val); +} + +static gboolean +fu_genesys_scaler_device_set_quirk_kv(FuDevice *device, + const gchar *key, + const gchar *value, + GError **error) +{ + FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); + guint64 tmp; + + if (g_strcmp0(key, "GenesysScalerDeviceTransferSize") == 0) { + if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) + return FALSE; + self->transfer_size = tmp; + + /* success */ + return TRUE; + } + if (g_strcmp0(key, "GenesysScalerGpioOutputRegister") == 0) { + if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) + return FALSE; + self->gpio_out_reg = tmp; + + /* success */ + return TRUE; + } + if (g_strcmp0(key, "GenesysScalerGpioEnableRegister") == 0) { + if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) + return FALSE; + self->gpio_en_reg = tmp; + + /* success */ + return TRUE; + } + if (g_strcmp0(key, "GenesysScalerGpioValue") == 0) { + if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) + return FALSE; + self->gpio_val = tmp; + + /* success */ + return TRUE; + } + + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "quirk key not supported"); + return FALSE; +} + +static void +fu_genesys_scaler_device_init(FuGenesysScalerDevice *self) +{ + fu_device_set_vendor(FU_DEVICE(self), "MStar Semiconductor"); + fu_device_set_name(FU_DEVICE(self), "TSUMG"); + fu_device_add_protocol(FU_DEVICE(self), "com.mstarsemi.scaler"); + fu_device_retry_set_delay(FU_DEVICE(self), 10); /* ms */ + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); + fu_device_register_private_flag(FU_DEVICE(self), + FU_SCALER_FLAG_PAUSE_R2_CPU, + "pause-r2-cpu"); + fu_device_register_private_flag(FU_DEVICE(self), FU_SCALER_FLAG_USE_I2C_CH0, "use-i2c-ch0"); + self->sector_size = 0x1000; /* 4KB */ + self->page_size = 0x100; /* 256B */ + self->transfer_size = 0x40; /* 64B */ +} + +static void +fu_genesys_scaler_device_class_init(FuGenesysScalerDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->probe = fu_genesys_scaler_device_probe; + klass_device->open = fu_genesys_scaler_device_open; + klass_device->close = fu_genesys_scaler_device_close; + klass_device->setup = fu_genesys_scaler_device_setup; + klass_device->dump_firmware = fu_genesys_scaler_device_dump_firmware; + klass_device->prepare_firmware = fu_genesys_scaler_device_prepare_firmware; + klass_device->write_firmware = fu_genesys_scaler_device_write_firmware; + klass_device->set_progress = fu_genesys_scaler_device_set_progress; + klass_device->detach = fu_genesys_scaler_device_detach; + klass_device->attach = fu_genesys_scaler_device_attach; + klass_device->to_string = fu_genesys_scaler_device_to_string; + klass_device->set_quirk_kv = fu_genesys_scaler_device_set_quirk_kv; +} + +FuGenesysScalerDevice * +fu_genesys_scaler_device_new(FuContext *ctx) +{ + FuGenesysScalerDevice *device = NULL; + device = g_object_new(FU_TYPE_GENESYS_SCALER_DEVICE, "context", ctx, NULL); + return device; +} diff --git a/plugins/genesys/fu-genesys-scaler-device.h b/plugins/genesys/fu-genesys-scaler-device.h new file mode 100644 index 000000000..ca59191de --- /dev/null +++ b/plugins/genesys/fu-genesys-scaler-device.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 Gaël PORTAY + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_GENESYS_SCALER_DEVICE (fu_genesys_scaler_device_get_type()) +G_DECLARE_FINAL_TYPE(FuGenesysScalerDevice, + fu_genesys_scaler_device, + FU, + GENESYS_SCALER_DEVICE, + FuDevice) + +FuGenesysScalerDevice * +fu_genesys_scaler_device_new(FuContext *ctx); diff --git a/plugins/genesys/fu-genesys-scaler-firmware.c b/plugins/genesys/fu-genesys-scaler-firmware.c new file mode 100644 index 000000000..c2ac35dd0 --- /dev/null +++ b/plugins/genesys/fu-genesys-scaler-firmware.c @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2021 Gaël PORTAY + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-genesys-scaler-firmware.h" + +struct _FuGenesysScalerFirmware { + FuFirmwareClass parent_instance; + FuGenesysMtkFooter footer; + guint protect_sector_addr[2]; + gsize protect_sector_size[2]; + guint public_key_addr; + gsize public_key_size; + guint addr; +}; + +G_DEFINE_TYPE(FuGenesysScalerFirmware, fu_genesys_scaler_firmware, FU_TYPE_FIRMWARE) + +void +fu_genesys_scaler_firmware_decrypt(guint8 *buf, gsize bufsz) +{ + const gchar *key = "mstar"; + const gsize keylen = strlen(key); + + for (guint i = 0; i < bufsz; i++) + buf[i] ^= key[i % keylen]; +} + +static gboolean +fu_genesys_scaler_firmware_parse(FuFirmware *firmware, + GBytes *fw, + guint64 addr_start, + guint64 addr_end, + FwupdInstallFlags flags, + GError **error) +{ + FuGenesysScalerFirmware *self = FU_GENESYS_SCALER_FIRMWARE(firmware); + gsize bufsz = 0; + const guint8 *buf = g_bytes_get_data(fw, &bufsz); + + buf = g_bytes_get_data(fw, &bufsz); + if (!fu_memcpy_safe((guint8 *)&self->footer, + sizeof(self->footer), + 0, /* dst */ + buf, + bufsz, + bufsz - sizeof(self->footer), /* src */ + sizeof(self->footer), + error)) + return FALSE; + fu_genesys_scaler_firmware_decrypt((guint8 *)&self->footer, sizeof(self->footer)); + if (memcmp(self->footer.data.header.default_head, + MTK_RSA_HEADER, + sizeof(self->footer.data.header.default_head)) != 0) { + g_autofree gchar *str = NULL; + str = fu_common_strsafe((const gchar *)self->footer.data.header.default_head, + sizeof(self->footer.data.header.default_head)); + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "invalid footer, expected %s, and got %s", + MTK_RSA_HEADER, + str); + return FALSE; + } + + if (self->footer.data.header.configuration_setting.bits.second_image) { + if (!fu_common_read_uint32_safe( + self->footer.data.header.second_image_program_addr, + sizeof(self->footer.data.header.second_image_program_addr), + 0, + &self->addr, + G_LITTLE_ENDIAN, + error)) + return FALSE; + } + if (self->footer.data.header.configuration_setting.bits.decrypt_mode) { + if (!fu_common_read_uint32_safe( + self->footer.data.header.scaler_public_key_addr, + sizeof(self->footer.data.header.scaler_public_key_addr), + 0, + &self->public_key_addr, + G_LITTLE_ENDIAN, + error)) + return FALSE; + self->public_key_size = 0x1000; + } + + if (self->footer.data.header.configuration_setting.bits.special_protect_sector) { + if (self->footer.data.header.protect_sector[0].area.size) { + self->protect_sector_addr[0] = + (self->footer.data.header.protect_sector[0].area.addr_high << 16) | + (self->footer.data.header.protect_sector[0].area.addr_low[1] << 8) | + (self->footer.data.header.protect_sector[0].area.addr_low[0]); + self->protect_sector_addr[0] *= 0x1000; + self->protect_sector_size[0] = + self->footer.data.header.protect_sector[0].area.size * 0x1000; + } + + if (self->footer.data.header.protect_sector[1].area.size) { + self->protect_sector_addr[1] = + (self->footer.data.header.protect_sector[1].area.addr_high << 16) | + (self->footer.data.header.protect_sector[1].area.addr_low[1] << 8) | + (self->footer.data.header.protect_sector[1].area.addr_low[0]); + self->protect_sector_addr[1] *= 0x1000; + self->protect_sector_size[1] = + self->footer.data.header.protect_sector[1].area.size * 0x1000; + } + } + + /* success */ + return TRUE; +} + +static void +fu_genesys_scaler_firmware_export(FuFirmware *firmware, + FuFirmwareExportFlags flags, + XbBuilderNode *bn) +{ + FuGenesysScalerFirmware *self = FU_GENESYS_SCALER_FIRMWARE(firmware); + + if (self->footer.data.header.model_name[0] != '\0') { + fu_xmlb_builder_insert_kv(bn, + "model_name", + (const gchar *)self->footer.data.header.model_name); + } + if (self->footer.data.header.scaler_group[0] != '\0') { + fu_xmlb_builder_insert_kv(bn, + "scaler_group", + (const gchar *)self->footer.data.header.scaler_group); + } + if (self->footer.data.header.panel_type[0] != '\0') { + fu_xmlb_builder_insert_kv(bn, + "panel_type", + (const gchar *)self->footer.data.header.panel_type); + } + if (self->footer.data.header.scaler_packet_date[0] != '\0') { + fu_xmlb_builder_insert_kv( + bn, + "scaler_packet_date", + (const gchar *)self->footer.data.header.scaler_packet_date); + } + if (self->footer.data.header.scaler_packet_version[0] != '\0') { + fu_xmlb_builder_insert_kv( + bn, + "scaler_packet_version", + (const gchar *)self->footer.data.header.scaler_packet_version); + } + fu_xmlb_builder_insert_kx(bn, + "configuration_setting", + self->footer.data.header.configuration_setting.r8); + + if (self->footer.data.header.configuration_setting.bits.second_image) + fu_xmlb_builder_insert_kx(bn, "second_image_program_addr", self->addr); + + if (self->footer.data.header.configuration_setting.bits.decrypt_mode) { + gchar N[0x200 + 1] = {'\0'}; + gchar E[0x006 + 1] = {'\0'}; + + fu_xmlb_builder_insert_kx(bn, "public_key_addr", self->public_key_addr); + fu_xmlb_builder_insert_kx(bn, "public_key_size", self->public_key_size); + + memcpy(N, self->footer.data.public_key.N + 4, sizeof(N) - 1); + fu_xmlb_builder_insert_kv(bn, "N", N); + + memcpy(E, self->footer.data.public_key.E + 4, sizeof(E) - 1); + fu_xmlb_builder_insert_kv(bn, "E", E); + } + + if (self->footer.data.header.configuration_setting.bits.special_protect_sector) { + if (self->protect_sector_size[0]) { + fu_xmlb_builder_insert_kx(bn, + "protect_sector_addr0", + self->protect_sector_addr[0]); + fu_xmlb_builder_insert_kx(bn, + "protect_sector_size0", + self->protect_sector_size[0]); + } + + if (self->protect_sector_size[1]) { + fu_xmlb_builder_insert_kx(bn, + "protect_sector_addr1", + self->protect_sector_addr[1]); + fu_xmlb_builder_insert_kx(bn, + "protect_sector_size1", + self->protect_sector_size[1]); + } + } + + if (self->footer.data.header.configuration_setting.bits.boot_code_size_in_header) { + fu_xmlb_builder_insert_kx(bn, + "boot_code_size", + self->footer.data.header.boot_code_size); + } + fu_xmlb_builder_insert_kx(bn, "addr", self->addr); +} + +static gboolean +fu_genesys_scaler_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) +{ + FuGenesysScalerFirmware *self = FU_GENESYS_SCALER_FIRMWARE(firmware); + const gchar *tmp; + + /* optional properties */ + tmp = xb_node_query_text(n, "model_name", NULL); + if (tmp != NULL) { + if (!fu_memcpy_safe((guint8 *)&self->footer.data.header.model_name, + sizeof(self->footer.data.header.model_name), + 0x0, /* dst */ + (const guint8 *)tmp, + strlen(tmp), + 0x0, /* src */ + strlen(tmp), + error)) + return FALSE; + } + + /* success */ + return TRUE; +} + +static GBytes * +fu_genesys_scaler_firmware_write(FuFirmware *firmware, GError **error) +{ + FuGenesysScalerFirmware *self = FU_GENESYS_SCALER_FIRMWARE(firmware); + FuGenesysMtkFooter footer = {0x0}; + g_autoptr(GByteArray) buf = g_byte_array_new(); + g_autoptr(GBytes) blob = NULL; + + /* payload */ + blob = fu_firmware_get_bytes(firmware, error); + if (blob == NULL) + return NULL; + fu_byte_array_append_bytes(buf, blob); + + /* "encrypted" footer */ + if (!fu_memcpy_safe((guint8 *)&footer, + sizeof(footer), + 0, /* dst */ + (guint8 *)&self->footer, + sizeof(self->footer), + 0, /* src */ + sizeof(footer), + error)) + return NULL; + if (!fu_memcpy_safe((guint8 *)&footer.data.header.default_head, + sizeof(footer.data.header.default_head), + 0, /* dst */ + (guint8 *)&MTK_RSA_HEADER, + strlen(MTK_RSA_HEADER), + 0, /* src */ + strlen(MTK_RSA_HEADER), + error)) + return NULL; + fu_genesys_scaler_firmware_decrypt((guint8 *)&footer, sizeof(footer)); + g_byte_array_append(buf, (const guint8 *)&footer, sizeof(footer)); + + /* success */ + return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); +} + +static void +fu_genesys_scaler_firmware_init(FuGenesysScalerFirmware *self) +{ +} + +static void +fu_genesys_scaler_firmware_class_init(FuGenesysScalerFirmwareClass *klass) +{ + FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); + klass_firmware->parse = fu_genesys_scaler_firmware_parse; + klass_firmware->export = fu_genesys_scaler_firmware_export; + klass_firmware->build = fu_genesys_scaler_firmware_build; + klass_firmware->write = fu_genesys_scaler_firmware_write; +} + +FuFirmware * +fu_genesys_scaler_firmware_new(void) +{ + return FU_FIRMWARE(g_object_new(FU_TYPE_GENESYS_SCALER_FIRMWARE, NULL)); +} diff --git a/plugins/genesys/fu-genesys-scaler-firmware.h b/plugins/genesys/fu-genesys-scaler-firmware.h new file mode 100644 index 000000000..e8f64c3a1 --- /dev/null +++ b/plugins/genesys/fu-genesys-scaler-firmware.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2021 Gaël PORTAY + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_GENESYS_SCALER_FIRMWARE (fu_genesys_scaler_firmware_get_type()) +G_DECLARE_FINAL_TYPE(FuGenesysScalerFirmware, + fu_genesys_scaler_firmware, + FU, + GENESYS_SCALER_FIRMWARE, + FuFirmware) + +#define MTK_RSA_HEADER "MTK_RSA_HEADER" + +typedef struct __attribute__((packed)) { + guint8 default_head[14]; + guint8 reserved_0e_0f[2]; + guint8 model_name[16]; + guint8 reserved_20; + guint8 size[2]; + guint8 reserved_23_27[5]; + guint8 scaler_group[10]; + guint8 reserved_32_53[34]; + guint8 panel_type[10]; + guint8 scaler_packet_date[8]; + guint8 reserved_66_67[2]; + guint8 scaler_packet_version[4]; + guint8 reserved_6c_7f[20]; + union { + guint8 r8; + struct { + /* + * Decrypt Mode: + * + * 0: Scaler decrypt + * 1: ISP Tool decrypt + */ + guint8 decrypt_mode : 1; + + /* + * Second Image: + * + * 0: 1st image or dual image; programming address at 0x000000 + * 1: 2nd image; programming address set by .second_image_program_addr + */ + guint8 second_image : 1; + + /* + * Dual image turn: + * + * 0: fix second programing address set by .second_image_program_addr + * 1: support “Dual image turn” rule + * - TSUM: Not supported + * - MST9U: ISP Tool need update to DUT least version image address + * - HAWK: Not supported + */ + guint8 dual_image_turn : 1; + + /* + * Special Protect Sector: + * + * 0: No Special Protect sector + * 1: Support Special Protect sector + */ + guint8 special_protect_sector : 1; + + /* + * HAWK bypass mode + * + * 0: No support HAWK bypass mode + * 1: Support HAWK bypass mode + */ + guint8 hawk_bypass_mode : 1; + + /* + * Boot Code Size in header + * + * 0: Follow original search bin address rule + * 1: Get Boot code size from header set by .boot_code_size + */ + guint8 boot_code_size_in_header : 1; + + /* Reserved */ + guint8 reserved_6_7 : 2; + } __attribute__((packed)) bits; + } configuration_setting; + + guint8 reserved_81_85[5]; + + /* If configuration_setting.bits.second_image set */ + guint8 second_image_program_addr[4]; + + /* + * If configuration_setting.bits.decrypt is set + * + * TSUM/HAWK: ISP Tool need protect flash public address can’t erase and write + * MST9U: Not supported + */ + guint8 scaler_public_key_addr[4]; + + /* + * If configuration_setting.bits.special_protect_sector is set + * + * ISP Tool can't erase "Special Protect Sector" area. + * + * [19:00]: Protect continuous sector start. + * [23:20]: Protect sector continuous number. + * + * Examples: If need to protect FA000 ~FFFFF, Special Protect sector = 0x6000FA; + * If need to protect FA000 only, Special Protect sector = 0x1000FA; + * If no need to protect, Special Protect sector = 0x000000; + */ + union { + guint32 r32; + struct { + guint8 addr_low[2]; + guint8 addr_high : 4; + guint8 size : 4; + } __attribute__((packed)) area; + } protect_sector[2]; + + /* + * If configuration.bits .second_image and .dual_image_turn are set + * and .boot_code_size. + */ + guint32 boot_code_size; +} FuGenesysMtkRsaHeader; + +typedef union __attribute__((packed)) { + guint8 raw[0x312]; + struct { + struct { + guint8 N[0x206]; + guint8 E[0x00c]; + } __attribute__((packed)) public_key; + FuGenesysMtkRsaHeader header; + } data; +} FuGenesysMtkFooter; + +void +fu_genesys_scaler_firmware_decrypt(guint8 *buf, gsize bufsz); + +FuFirmware * +fu_genesys_scaler_firmware_new(void); diff --git a/plugins/genesys/fu-genesys-usbhub-device.c b/plugins/genesys/fu-genesys-usbhub-device.c new file mode 100644 index 000000000..3a75cb24d --- /dev/null +++ b/plugins/genesys/fu-genesys-usbhub-device.c @@ -0,0 +1,1359 @@ +/* + * Copyright (C) 2021 Ricardo Cañuelo + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include +#include + +#include "fu-genesys-common.h" +#include "fu-genesys-scaler-device.h" +#include "fu-genesys-usbhub-device.h" +#include "fu-genesys-usbhub-firmware.h" + +/** + * FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER: + * + * Device has a MStar scaler attached via I2C. + */ +#define FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER (1 << 0) + +#define GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_3_0 0x84 +#define GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_3_0 0x85 +#define GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_2_0 0x81 +#define GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_2_0 0x82 +#define GENESYS_USBHUB_FW_INFO_DESC_IDX 0x83 +#define GENESYS_USBHUB_VENDOR_SUPPORT_DESC_IDX 0x86 + +#define GENESYS_USBHUB_FW_SIG_OFFSET 0xFC +#define GENESYS_USBHUB_FW_SIG_LEN 4 +#define GENESYS_USBHUB_FW_SIG_TEXT_HUB "XROM" + +#define GENESYS_USBHUB_CODE_SIZE_OFFSET 0xFB + +#define GENESYS_USBHUB_GL_HUB_VERIFY 0x71 +#define GENESYS_USBHUB_GL_HUB_SWITCH 0x81 +#define GENESYS_USBHUB_GL_HUB_READ 0x82 +#define GENESYS_USBHUB_GL_HUB_WRITE 0x83 + +#define GENESYS_USBHUB_ENCRYPT_REGION_START 0x01 +#define GENESYS_USBHUB_ENCRYPT_REGION_END 0x15 + +#define GL3523_PUBLIC_KEY_LEN 0x212 +#define GL3523_SIG_LEN 0x100 + +#define GENESYS_USBHUB_USB_TIMEOUT 5000 /* ms */ +#define GENESYS_USBHUB_FLASH_WRITE_TIMEOUT 500 /* ms */ + +typedef enum { + TOOL_STRING_VERSION_9BYTE_DYNAMIC, + TOOL_STRING_VERSION_BONDING, + TOOL_STRING_VERSION_BONDING_QC, + TOOL_STRING_VERSION_VENDOR_SUPPORT, + TOOL_STRING_VERSION_MULTI_TOKEN, + TOOL_STRING_VERSION_2ND_DYNAMIC, + TOOL_STRING_VERSION_RESERVED, + TOOL_STRING_VERSION_13BYTE_DYNAMIC, +} FuGenesysToolStringVersion; + +typedef enum { + ISP_EXIT, + ISP_ENTER, +} FuGenesysIspMode; + +typedef enum { + ISP_MODEL_UNKNOWN, + + /* hub */ + ISP_MODEL_HUB_GL3510, + ISP_MODEL_HUB_GL3521, + ISP_MODEL_HUB_GL3523, + ISP_MODEL_HUB_GL3590, + ISP_MODEL_HUB_GL7000, + ISP_MODEL_HUB_GL3525, + + /* pd */ + ISP_MODEL_PD_GL9510, +} FuGenesysModel; + +typedef struct __attribute__((packed)) { + guint8 tool_version[6]; /* ISP tool defined by itself */ + guint8 address_mode; + guint8 build_fw_time[12]; /* YYYYMMDDhhmm */ + guint8 update_fw_time[12]; /* YYYYMMDDhhmm */ +} FuGenesysFirmwareInfoToolString; + +typedef struct { + guint8 req_switch; + guint8 req_read; + guint8 req_write; +} FuGenesysVendorCommandSetting; + +struct _FuGenesysUsbhubDevice { + FuUsbDevice parent_instance; + FuGenesysStaticToolString static_ts; + FuGenesysFirmwareInfoToolString fwinfo_ts; + FuGenesysVendorCommandSetting vcs; + guint32 isp_model; + guint8 isp_revision; + guint32 flash_erase_delay; + guint32 flash_write_delay; + guint32 flash_block_size; + guint32 flash_sector_size; + guint32 flash_rw_size; + + guint32 fw_bank_addr[2]; + guint16 fw_bank_vers[2]; + guint32 code_size; /* 0: get from device */ + guint32 fw_data_total_count; + guint32 extend_size; + gboolean read_first_bank; + gboolean write_recovery_bank; + + FuCfiDevice *cfi_device; +}; + +G_DEFINE_TYPE(FuGenesysUsbhubDevice, fu_genesys_usbhub_device, FU_TYPE_USB_DEVICE) + +static gboolean +fu_genesys_usbhub_device_mstar_scaler_setup(FuGenesysUsbhubDevice *self, GError **error) +{ + FuContext *ctx = fu_device_get_context(FU_DEVICE(self)); + g_autoptr(FuGenesysScalerDevice) scaler_device = fu_genesys_scaler_device_new(ctx); + + fu_device_add_child(FU_DEVICE(self), FU_DEVICE(scaler_device)); + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_read_flash(FuGenesysUsbhubDevice *self, + guint start_addr, + guint8 *buf, + guint bufsz, + FuProgress *progress, + GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); + g_autoptr(GPtrArray) chunks = NULL; + + chunks = fu_chunk_array_mutable_new(buf, bufsz, start_addr, 0x0, self->flash_rw_size); + if (progress != NULL) { + fu_progress_set_id(progress, G_STRLOC); + fu_progress_set_steps(progress, chunks->len); + } + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + + 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, + self->vcs.req_read, + (fu_chunk_get_address(chk) & 0x0f0000) >> + 4, /* value */ + fu_chunk_get_address(chk) & 0xffff, /* idx */ + fu_chunk_get_data_out(chk), /* data */ + fu_chunk_get_data_sz(chk), /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error reading flash at 0x%04x: ", + fu_chunk_get_address(chk)); + return FALSE; + } + if (progress != NULL) + fu_progress_step_done(progress); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_reset(FuGenesysUsbhubDevice *self, GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); + + /* send data to device */ + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vcs.req_switch, + 0x0003, /* value */ + 0, /* idx */ + NULL, /* data */ + 0, /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error resetting device: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static FuCfiDevice * +fu_genesys_usbhub_device_cfi_setup(FuGenesysUsbhubDevice *self, GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); + const guint8 rdid_dummy_addr[] = {0x01, 0x02}; + const guint8 rdid_cmd[] = {0x9f, 0x90, 0xAB, 0x1D, 0x15, 0x4D, 0x4B}; + + for (guint8 i = 0; i < G_N_ELEMENTS(rdid_cmd); i++) { + for (guint8 j = 0; j < G_N_ELEMENTS(rdid_dummy_addr); j++) { + guint16 val = ((guint16)rdid_cmd[i] << 8) | rdid_dummy_addr[j]; + guint8 buf[2 * 3] = {0}; /* 2 x 3-bytes JEDEC-ID-bytes */ + g_autoptr(GError) error_local = NULL; + g_autoptr(FuCfiDevice) cfi_device = NULL; + g_autofree gchar *flash_id = NULL; + + 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, + self->vcs.req_read, + val, /* value */ + 0, /* idx */ + buf, /* data */ + sizeof(buf), /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, "error reading flash chip: "); + return NULL; + } + + flash_id = g_strdup_printf("%02X%02X%02X", buf[0], buf[1], buf[2]); + cfi_device = + fu_cfi_device_new(fu_device_get_context(FU_DEVICE(self)), flash_id); + if (cfi_device == NULL) + continue; + + if (!fu_device_probe(FU_DEVICE(cfi_device), &error_local)) { + g_debug("ignoring %s: %s", flash_id, error_local->message); + continue; + } + + if (fu_device_get_name(FU_DEVICE(cfi_device)) == NULL) + continue; + + if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL) { + guint len; + + /* + * The USB vendor command loops over the JEDEC-ID-bytes. + * + * Therefore, the CFI is 3-bytes long if the first 3-bytes are + * identical to the last 3-bytes. + */ + if (buf[0] == buf[3] && buf[1] == buf[4] && buf[2] == buf[5]) + len = 3; + else + len = 2; + + fu_common_dump_raw(G_LOG_DOMAIN, "Flash ID", buf, len); + g_debug("CFI: %s", fu_device_get_name(FU_DEVICE(cfi_device))); + } + + return g_steal_pointer(&cfi_device); + } + } + + /* failure */ + g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no CFI device found"); + return NULL; +} + +static gboolean +fu_genesys_usbhub_device_wait_flash_status_register_cb(FuDevice *device, + gpointer user_data, + GError **error) +{ + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); + guint8 status = 0; + FuGenesysWaitFlashRegisterHelper *helper = user_data; + + 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, + self->vcs.req_read, + helper->reg << 8 | 0x02, /* value */ + 0, /* idx */ + &status, /* data */ + 1, /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error getting flash status register (0x%02x): ", + helper->reg); + return FALSE; + } + if (status != helper->expected_val) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "wrong value in flash status register"); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_set_isp_mode(FuGenesysUsbhubDevice *self, + FuGenesysIspMode mode, + GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vcs.req_switch, + mode, /* value */ + 0, /* idx */ + NULL, /* data */ + 0, /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error setting isp mode - " + "control transfer error (reg 0x%02x) ", + self->vcs.req_switch); + return FALSE; + } + + if (mode == ISP_ENTER) { + FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0}; + if (!fu_device_retry(FU_DEVICE(self), + fu_genesys_usbhub_device_wait_flash_status_register_cb, + 5, + &helper, + error)) { + g_prefix_error(error, "error setting isp mode: "); + return FALSE; + } + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_authentication_request(FuGenesysUsbhubDevice *self, + guint8 offset_start, + guint8 offset_end, + guint8 data_check, + GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); + guint8 buf = 0; + + 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, + GENESYS_USBHUB_GL_HUB_VERIFY, + (offset_end << 8) | offset_start, /* value */ + 0, /* idx */ + &buf, /* data */ + 1, /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "control transfer error (req: 0x%0x): ", + (guint)GENESYS_USBHUB_GL_HUB_VERIFY); + return FALSE; + } + 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, + GENESYS_USBHUB_GL_HUB_VERIFY, + (offset_end << 8) | offset_start, /* value */ + 1 | (data_check << 8), /* idx */ + &buf, /* data */ + 1, /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "control transfer error (req: 0x%0x): ", + (guint)GENESYS_USBHUB_GL_HUB_VERIFY); + return FALSE; + } + if (buf != 1) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "device authentication failed"); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_authenticate(FuGenesysUsbhubDevice *self, GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); + guint8 low_byte; + guint8 high_byte; + guint8 temp_byte; + guint8 offset_start; + guint8 offset_end; + guint8 *fwinfo = (guint8 *)&self->fwinfo_ts; + + if (self->vcs.req_switch == GENESYS_USBHUB_GL_HUB_SWITCH) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "device authentication not supported"); + return FALSE; + } + + low_byte = g_usb_device_get_release(usb_device) & 0xff; + high_byte = (g_usb_device_get_release(usb_device) & 0xff00) >> 8; + temp_byte = low_byte ^ high_byte; + + offset_start = g_random_int_range(GENESYS_USBHUB_ENCRYPT_REGION_START, + GENESYS_USBHUB_ENCRYPT_REGION_END - 1); + offset_end = g_random_int_range(offset_start + 1, GENESYS_USBHUB_ENCRYPT_REGION_END); + for (guint8 i = offset_start; i <= offset_end; i++) { + temp_byte ^= fwinfo[i]; + } + if (!fu_genesys_usbhub_device_authentication_request(self, + offset_start, + offset_end, + temp_byte, + error)) { + g_prefix_error(error, "error authenticating device: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_probe(FuDevice *device, GError **error) +{ + /* FuUsbDevice->probe */ + if (!FU_DEVICE_CLASS(fu_genesys_usbhub_device_parent_class)->probe(device, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_open(FuDevice *device, GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); + + /* FuUsbDevice->open */ + if (!FU_DEVICE_CLASS(fu_genesys_usbhub_device_parent_class)->open(device, error)) { + g_prefix_error(error, "error opening device: "); + return FALSE; + } + + if (!g_usb_device_claim_interface(usb_device, + 0, + G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, + error)) { + g_prefix_error(error, "error claiming interface: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_close(FuDevice *device, GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); + + if (!g_usb_device_release_interface(usb_device, + 0, + G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, + error)) { + return FALSE; + } + + /* FuUsbDevice->close */ + if (!FU_DEVICE_CLASS(fu_genesys_usbhub_device_parent_class)->close(device, error)) { + g_prefix_error(error, "error closing device: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_get_descriptor_data(GBytes *desc_bytes, + guint8 *dst, + guint dst_size, + GError **error) +{ + gsize bufsz = 0; + const guint8 *buf = g_bytes_get_data(desc_bytes, &bufsz); + + if (bufsz <= 2) { + g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "data is too small"); + return FALSE; + } + + /* discard first 2 bytes (desc. length and type) */ + buf += 2; + bufsz -= 2; + for (gsize i = 0, j = 0; i < bufsz && j < dst_size; i += 2, j++) + dst[j] = buf[i]; + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_check_fw_signature(FuGenesysUsbhubDevice *self, + int bank_num, + GError **error) +{ + guint8 sig[GENESYS_USBHUB_FW_SIG_LEN] = {0}; + g_return_val_if_fail(bank_num < 2, FALSE); + + if (!fu_genesys_usbhub_device_read_flash(self, + self->fw_bank_addr[bank_num] + + GENESYS_USBHUB_FW_SIG_OFFSET, + sig, + GENESYS_USBHUB_FW_SIG_LEN, + NULL, + error)) { + g_prefix_error(error, + "error getting fw signature (bank %d) from device: ", + bank_num); + return FALSE; + } + if (memcmp(sig, GENESYS_USBHUB_FW_SIG_TEXT_HUB, GENESYS_USBHUB_FW_SIG_LEN) != 0) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "wrong firmware signature"); + return FALSE; + } + + /* success */ + return TRUE; +} + +/* read the firmware size from the firmware stored in the device */ +static gboolean +fu_genesys_usbhub_device_get_fw_size(FuGenesysUsbhubDevice *self, int bank_num, GError **error) +{ + guint8 kbs = 0; + + g_return_val_if_fail(bank_num < 2, FALSE); + g_return_val_if_fail(self->code_size == 0, FALSE); + + if (!fu_genesys_usbhub_device_check_fw_signature(self, bank_num, error)) + return FALSE; + + /* get firmware size from device */ + if (!fu_genesys_usbhub_device_read_flash(self, + self->fw_bank_addr[bank_num] + + GENESYS_USBHUB_CODE_SIZE_OFFSET, + &kbs, + 1, + NULL, + error)) { + g_prefix_error(error, "error getting fw size from device: "); + return FALSE; + } + self->code_size = 1024 * kbs; + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_detach(FuDevice *device, FuProgress *progress, GError **error) +{ + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + if (!fu_genesys_usbhub_device_authenticate(self, error)) + return FALSE; + if (!fu_genesys_usbhub_device_set_isp_mode(self, ISP_ENTER, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_attach(FuDevice *device, FuProgress *progress, GError **error) +{ + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + if (!fu_genesys_usbhub_device_reset(self, error)) + return FALSE; + + /* success */ + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); + return TRUE; +} + +static GBytes * +fu_genesys_usbhub_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) +{ + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + gsize address = self->fw_bank_addr[0]; + gsize size = self->code_size + self->extend_size; + g_autoptr(FuDeviceLocker) locker = NULL; + g_autofree guint8 *buf = NULL; + + if (self->fw_bank_vers[0] == 0 && fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) + address = self->fw_bank_addr[1]; + + /* progress */ + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); /* detach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 99); + + /* require detach -> attach */ + locker = fu_device_locker_new_full(device, + (FuDeviceLockerFunc)fu_device_detach, + (FuDeviceLockerFunc)fu_device_attach, + error); + if (locker == NULL) + return NULL; + fu_progress_step_done(progress); + + buf = g_malloc0(size); + if (!fu_genesys_usbhub_device_read_flash(self, + address, + buf, + size, + fu_progress_get_child(progress), + error)) + return NULL; + fu_progress_step_done(progress); + + return g_bytes_new_take(g_steal_pointer(&buf), size); +} + +static gboolean +fu_genesys_usbhub_device_setup(FuDevice *device, GError **error) +{ +#if G_USB_CHECK_VERSION(0, 3, 8) + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); + gsize bufsz; + guint32 block_size; + guint32 sector_size; + guint16 version_raw; + guint8 static_idx = 0; + guint8 dynamic_idx = 0; + g_autoptr(FuFirmware) firmware = NULL; + g_autoptr(GBytes) static_buf = NULL; + g_autoptr(GBytes) dynamic_buf = NULL; + g_autoptr(GBytes) fw_buf = NULL; + g_autoptr(GError) error_local = NULL; + g_autofree guint8 *buf = NULL; + + /* FuUsbDevice->setup */ + if (!FU_DEVICE_CLASS(fu_genesys_usbhub_device_parent_class)->setup(device, error)) { + g_prefix_error(error, "error setuping device: "); + return FALSE; + } + + /* [DEBUG] - additional info from device: + * release version: g_usb_device_get_release(usb_device) + */ + + /* read standard string descriptors */ + if (g_usb_device_get_spec(usb_device) >= 0x300) { + static_idx = GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_3_0; + dynamic_idx = GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_3_0; + } else { + static_idx = GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_2_0; + dynamic_idx = GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_2_0; + } + + /* + * Read/parse vendor-specific string descriptors and use that + * data to setup device attributes. + */ + static_buf = + g_usb_device_get_string_descriptor_bytes_full(usb_device, + static_idx, + G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, + 64, + error); + if (static_buf == NULL) { + g_prefix_error(error, "failed to get static tool info from device: "); + return FALSE; + } + if (!fu_genesys_usbhub_device_get_descriptor_data(static_buf, + (guint8 *)&self->static_ts, + sizeof(FuGenesysStaticToolString), + error)) { + g_prefix_error(error, "failed to get static tool info from device: "); + return FALSE; + } + if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL) { + fu_common_dump_raw(G_LOG_DOMAIN, + "Static info", + (guint8 *)&self->static_ts, + sizeof(FuGenesysStaticToolString)); + } + if (self->static_ts.tool_string_version != 0xff) { + gchar rev[3] = {0}; + guint64 tmp; + + if (memcmp(self->static_ts.mask_project_ic_type, "3523", 4) == 0) { + self->isp_model = ISP_MODEL_HUB_GL3523; + } else if (memcmp(self->static_ts.mask_project_ic_type, "3590", 4) == 0) { + self->isp_model = ISP_MODEL_HUB_GL3590; + } else { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "Unknown ISP model"); + return FALSE; + } + memcpy(rev, &self->static_ts.mask_project_ic_type[4], 2); + tmp = fu_common_strtoull(rev); + self->isp_revision = tmp; + } + + dynamic_buf = + g_usb_device_get_string_descriptor_bytes_full(usb_device, + dynamic_idx, + G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, + 64, + error); + if (dynamic_buf == NULL) { + g_prefix_error(error, "failed to get dynamic tool info from device: "); + return FALSE; + } + + fw_buf = + g_usb_device_get_string_descriptor_bytes_full(usb_device, + GENESYS_USBHUB_FW_INFO_DESC_IDX, + G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, + 64, + error); + if (fw_buf == NULL) { + g_prefix_error(error, "failed to get firmware info from device: "); + return FALSE; + } + if (!fu_genesys_usbhub_device_get_descriptor_data(fw_buf, + (guint8 *)&self->fwinfo_ts, + sizeof(FuGenesysFirmwareInfoToolString), + error)) { + g_prefix_error(error, "failed to get firmware info from device: "); + return FALSE; + } + if (g_getenv("FWUPD_GENESYS_USBHUB_VERBOSE") != NULL) { + fu_common_dump_raw(G_LOG_DOMAIN, + "Fw info", + (guint8 *)&self->fwinfo_ts, + sizeof(FuGenesysFirmwareInfoToolString)); + } + + if (self->static_ts.tool_string_version >= TOOL_STRING_VERSION_VENDOR_SUPPORT) { + g_autoptr(GBytes) vendor_buf = g_usb_device_get_string_descriptor_bytes_full( + usb_device, + GENESYS_USBHUB_VENDOR_SUPPORT_DESC_IDX, + G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, + 64, + error); + if (vendor_buf == NULL) { + g_prefix_error(error, "failed to get vendor support info from device: "); + return FALSE; + } + } + + if (!fu_genesys_usbhub_device_authenticate(self, error)) + return FALSE; + if (!fu_genesys_usbhub_device_set_isp_mode(self, ISP_ENTER, error)) + return FALSE; + self->cfi_device = fu_genesys_usbhub_device_cfi_setup(self, error); + if (self->cfi_device == NULL) + return FALSE; + block_size = fu_cfi_device_get_block_size(self->cfi_device); + if (block_size != 0) + self->flash_block_size = block_size; + sector_size = fu_cfi_device_get_sector_size(self->cfi_device); + if (sector_size != 0) + self->flash_sector_size = sector_size; + + /* setup firmware parameters */ + switch (self->isp_model) { + case ISP_MODEL_HUB_GL3523: + self->fw_bank_addr[0] = 0x0000; + self->fw_bank_addr[1] = 0x8000; + self->fw_data_total_count = 0x6000; + self->extend_size = GL3523_PUBLIC_KEY_LEN + GL3523_SIG_LEN; + if (self->isp_revision == 50) { + self->fw_data_total_count = 0x8000; + if (!fu_genesys_usbhub_device_get_fw_size(self, 0, error)) + return FALSE; + } else { + self->code_size = self->fw_data_total_count; + } + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); + fu_device_set_firmware_size_max(device, 0x8000); + break; + case ISP_MODEL_HUB_GL3590: + if (!fu_genesys_usbhub_device_get_fw_size(self, 0, error)) + return FALSE; + self->fw_bank_addr[0] = 0x0000; + self->fw_bank_addr[1] = 0x10000; + self->fw_data_total_count = 0x8000; + fu_device_add_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); + fu_device_set_firmware_size_max(device, 0x10000); + break; + default: + break; + } + + /* verify firmware integrity */ + bufsz = self->fw_data_total_count + self->extend_size; + buf = g_malloc0(bufsz); + if (!fu_genesys_usbhub_device_read_flash(self, + self->fw_bank_addr[0], + buf, + bufsz, + NULL, + error)) + return FALSE; + firmware = fu_genesys_usbhub_firmware_new(); + if (!fu_firmware_parse(firmware, + g_bytes_new_take(buf, bufsz), + FWUPD_INSTALL_FLAG_NONE, + &error_local)) { + g_debug("ignoring firmware: %s", error_local->message); + self->fw_bank_vers[0] = 0; + } else { + version_raw = fu_firmware_get_version_raw(firmware); + if (version_raw != 0xffff) + self->fw_bank_vers[0] = version_raw; + } + + if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) { + gsize address = self->fw_bank_addr[0]; + gsize bufsz_dual; + g_autoptr(FuFirmware) firmware_dual = NULL; + g_autoptr(GError) error_local_dual = NULL; + g_autofree guint8 *buf_dual = NULL; + + /* verify dual firmware integrity */ + bufsz_dual = self->fw_data_total_count + self->extend_size; + buf_dual = g_malloc0(bufsz_dual); + if (!fu_genesys_usbhub_device_read_flash(self, + self->fw_bank_addr[1], + buf_dual, + bufsz_dual, + NULL, + error)) + return FALSE; + firmware_dual = fu_genesys_usbhub_firmware_new(); + if (!fu_firmware_parse(firmware_dual, + g_bytes_new_take(buf_dual, bufsz_dual), + FWUPD_INSTALL_FLAG_NONE, + &error_local_dual)) { + g_debug("ignoring recovery firmware: %s", error_local_dual->message); + self->fw_bank_vers[1] = 0; + } else { + version_raw = fu_firmware_get_version_raw(firmware_dual); + if (version_raw != 0xffff) + self->fw_bank_vers[1] = version_raw; + } + + /* write recovery needed? */ + if (self->fw_bank_vers[0] == 0 && self->fw_bank_vers[1] == 0) { + /* first bank and recovery are both blanks: write fw on both */ + address = self->fw_bank_addr[1]; + } else if (self->fw_bank_vers[0] > self->fw_bank_vers[1]) { + /* first bank is more recent than recovery: write fw on recovery first */ + address = self->fw_bank_addr[1]; + } else { + /* recovery is more recent than first bank: write fw on first bank only */ + address = self->fw_bank_addr[0]; + } + + self->read_first_bank = + (self->isp_model == ISP_MODEL_HUB_GL3523) && self->fw_bank_vers[0] != 0; + self->write_recovery_bank = address == self->fw_bank_addr[1]; + } + + /* have MStar scaler */ + if (fu_device_has_private_flag(device, FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER)) + if (!fu_genesys_usbhub_device_mstar_scaler_setup(self, error)) + return FALSE; + + /* success */ + return TRUE; +#else + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "GUsb version is too old, " + "fwupd needs to be rebuilt against 0.3.8 or later"); + return FALSE; +#endif +} + +static void +fu_genesys_usbhub_device_to_string(FuDevice *device, guint idt, GString *str) +{ + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + fu_common_string_append_kx(str, idt, "FlashEraseDelay", self->flash_erase_delay); + fu_common_string_append_kx(str, idt, "FlashWriteDelay", self->flash_write_delay); + fu_common_string_append_kx(str, idt, "FlashBlockSize", self->flash_block_size); + fu_common_string_append_kx(str, idt, "FlashSectorSize", self->flash_sector_size); + fu_common_string_append_kx(str, idt, "FlashRwSize", self->flash_rw_size); + fu_common_string_append_kx(str, idt, "FwBank0Addr", self->fw_bank_addr[0]); + fu_common_string_append_kx(str, idt, "FwBank0Vers", self->fw_bank_vers[0]); + if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) { + fu_common_string_append_kx(str, idt, "FwBank1Addr", self->fw_bank_addr[1]); + fu_common_string_append_kx(str, idt, "FwBank1Vers", self->fw_bank_vers[1]); + } + fu_common_string_append_kx(str, idt, "CodeSize", self->code_size); + fu_common_string_append_kx(str, idt, "FwDataTotalCount", self->fw_data_total_count); + fu_common_string_append_kx(str, idt, "ExtendSize", self->extend_size); +} + +static FuFirmware * +fu_genesys_usbhub_device_prepare_firmware(FuDevice *device, + GBytes *fw, + FwupdInstallFlags flags, + GError **error) +{ + g_autoptr(FuFirmware) firmware = fu_genesys_usbhub_firmware_new(); + + /* parse firmware */ + if (!fu_firmware_parse(firmware, fw, flags, error)) + return NULL; + + /* check size */ + if (g_bytes_get_size(fw) > fu_device_get_firmware_size_max(device)) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "firmware too large, got 0x%x, expected <= 0x%x", + (guint)g_bytes_get_size(fw), + (guint)fu_device_get_firmware_size_max(device)); + return NULL; + } + + return fu_firmware_new_from_bytes(fw); +} + +static gboolean +fu_genesys_usbhub_device_erase_flash(FuGenesysUsbhubDevice *self, + guint start_addr, + guint len, + FuProgress *progress, + GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); + FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0}; + g_autoptr(GPtrArray) chunks = NULL; + + chunks = fu_chunk_array_new(NULL, + len, + start_addr, + self->flash_block_size, + self->flash_sector_size); + fu_progress_set_id(progress, G_STRLOC); + fu_progress_set_steps(progress, chunks->len); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + guint16 sectornum = fu_chunk_get_address(chk) / self->flash_sector_size; + guint16 blocknum = fu_chunk_get_page(chk); + guint16 index = (0x01 << 8) | (sectornum << 4) | blocknum; + + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vcs.req_write, + 0x2001, /* value */ + index, /* idx */ + NULL, /* data */ + 0, /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error erasing flash at sector 0x%02x in block 0x%02x", + sectornum, + blocknum); + return FALSE; + } + + if (!fu_device_retry(FU_DEVICE(self), + fu_genesys_usbhub_device_wait_flash_status_register_cb, + self->flash_erase_delay / 30, + &helper, + error)) { + g_prefix_error(error, "error erasing flash: "); + return FALSE; + } + fu_progress_step_done(progress); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_write_flash(FuGenesysUsbhubDevice *self, + guint start_addr, + const guint8 *buf, + guint bufsz, + FuProgress *progress, + GError **error) +{ + GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); + FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0}; + g_autoptr(GPtrArray) chunks = NULL; + + chunks = + fu_chunk_array_new(buf, bufsz, start_addr, self->flash_block_size, self->flash_rw_size); + fu_progress_set_id(progress, G_STRLOC); + fu_progress_set_steps(progress, chunks->len); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index(chunks, i); + g_autofree guint8 *chkbuf_mut = NULL; + + chkbuf_mut = + fu_memdup_safe(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error); + if (chkbuf_mut == NULL) + return FALSE; + if (!g_usb_device_control_transfer(usb_device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + self->vcs.req_write, + (fu_chunk_get_page(chk) & 0x000f) + << 12, /* value */ + fu_chunk_get_address(chk) & 0x00ffff, /* idx */ + chkbuf_mut, /* data */ + fu_chunk_get_data_sz(chk), /* data length */ + NULL, /* actual length */ + GENESYS_USBHUB_USB_TIMEOUT, + NULL, + error)) { + g_prefix_error(error, + "error writing flash at 0x%02x%04x: ", + fu_chunk_get_page(chk), + fu_chunk_get_address(chk)); + return FALSE; + } + + if (!fu_device_retry(FU_DEVICE(self), + fu_genesys_usbhub_device_wait_flash_status_register_cb, + self->flash_write_delay / 30, + &helper, + error)) { + g_prefix_error(error, "error writing flash: "); + return FALSE; + } + fu_progress_step_done(progress); + } + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_write_recovery(FuGenesysUsbhubDevice *self, + GBytes *blob, + FuProgress *progress, + GError **error) +{ + gsize bufsz = 0; + g_autofree guint8 *buf = NULL; + g_autofree guint8 *buf_verify = NULL; + + /* progress */ + fu_progress_set_id(progress, G_STRLOC); + if (self->read_first_bank) + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 20); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 30); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 20); + + /* reuse fw on first bank for GL3523 */ + if (self->read_first_bank) { + bufsz = self->code_size; + if (bufsz == 0) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "code size is zero"); + return FALSE; + } + + buf = g_malloc0(bufsz); + if (!fu_genesys_usbhub_device_read_flash(self, + self->fw_bank_addr[0], + buf, + bufsz, + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + } else { + bufsz = g_bytes_get_size(blob); + buf = fu_memdup_safe(g_bytes_get_data(blob, NULL), bufsz, error); + if (buf == NULL) + return FALSE; + } + + /* erase */ + if (!fu_genesys_usbhub_device_erase_flash(self, + self->fw_bank_addr[1], + bufsz, + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + + /* write */ + if (!fu_genesys_usbhub_device_write_flash(self, + self->fw_bank_addr[1], + buf, + bufsz, + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + + /* verify */ + buf_verify = g_malloc0(bufsz); + if (!fu_genesys_usbhub_device_read_flash(self, + self->fw_bank_addr[1], + buf_verify, + bufsz, + fu_progress_get_child(progress), + error)) + return FALSE; + if (!fu_common_bytes_compare_raw(buf_verify, bufsz, buf, bufsz, error)) + return FALSE; + fu_progress_step_done(progress); + + /* success */ + return TRUE; +} + +static gboolean +fu_genesys_usbhub_device_write_firmware(FuDevice *device, + FuFirmware *firmware, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + g_autoptr(GBytes) blob = NULL; + g_autofree guint8 *buf_verify = NULL; + + blob = fu_firmware_get_bytes(firmware, error); + if (blob == NULL) + return FALSE; + + /* progress */ + fu_progress_set_id(progress, G_STRLOC); + if (self->write_recovery_bank) { + if (self->read_first_bank) + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 120); + else + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100); + } + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 30); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 20); + + /* write fw to recovery bank first? */ + if (self->write_recovery_bank) { + if (!fu_genesys_usbhub_device_write_recovery(self, + blob, + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + } + + /* write fw to first bank then */ + if (!fu_genesys_usbhub_device_erase_flash(self, + self->fw_bank_addr[0], + g_bytes_get_size(blob), + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + + if (!fu_genesys_usbhub_device_write_flash(self, + self->fw_bank_addr[0], + g_bytes_get_data(blob, NULL), + g_bytes_get_size(blob), + fu_progress_get_child(progress), + error)) + return FALSE; + fu_progress_step_done(progress); + + /* verify */ + buf_verify = g_malloc0(g_bytes_get_size(blob)); + if (!fu_genesys_usbhub_device_read_flash(self, + self->fw_bank_addr[0], + buf_verify, + g_bytes_get_size(blob), + fu_progress_get_child(progress), + error)) + return FALSE; + if (!fu_common_bytes_compare_raw(buf_verify, + g_bytes_get_size(blob), + g_bytes_get_data(blob, NULL), + g_bytes_get_size(blob), + error)) + return FALSE; + fu_progress_step_done(progress); + + /* success */ + return TRUE; +} + +static void +fu_genesys_usbhub_device_set_progress(FuDevice *device, FuProgress *progress) +{ + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + + fu_progress_set_id(progress, G_STRLOC); + if (self->write_recovery_bank) { + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* detach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 30); /* write */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* attach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 70); /* reload */ + } else { + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* detach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 15); /* write */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* attach */ + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 85); /* reload */ + } +} + +static gboolean +fu_genesys_usbhub_device_set_quirk_kv(FuDevice *device, + const gchar *key, + const gchar *value, + GError **error) +{ + FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); + guint64 tmp; + + if (g_strcmp0(key, "GenesysUsbhubDeviceTransferSize") == 0) { + if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) + return FALSE; + self->flash_rw_size = tmp; + + /* success */ + return TRUE; + } + if (g_strcmp0(key, "GenesysUsbhubSwitchRequest") == 0) { + if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) + return FALSE; + self->vcs.req_switch = tmp; + + /* success */ + return TRUE; + } + if (g_strcmp0(key, "GenesysUsbhubReadRequest") == 0) { + if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) + return FALSE; + self->vcs.req_read = tmp; + + /* success */ + return TRUE; + } + if (g_strcmp0(key, "GenesysUsbhubWriteRequest") == 0) { + if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) + return FALSE; + self->vcs.req_write = tmp; + + /* success */ + return TRUE; + } + + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "quirk key not supported"); + return FALSE; +} + +static void +fu_genesys_usbhub_device_init(FuGenesysUsbhubDevice *self) +{ + fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_add_protocol(FU_DEVICE(self), "com.genesys.usbhub"); + fu_device_retry_set_delay(FU_DEVICE(self), 30); /* ms */ + fu_device_set_remove_delay(FU_DEVICE(self), 5000); /* ms */ + fu_device_register_private_flag(FU_DEVICE(self), + FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER, + "has-mstar-scaler"); + self->vcs.req_switch = GENESYS_USBHUB_GL_HUB_SWITCH; + self->vcs.req_read = GENESYS_USBHUB_GL_HUB_READ; + self->vcs.req_write = GENESYS_USBHUB_GL_HUB_WRITE; + self->flash_erase_delay = 8000; + self->flash_write_delay = 500; + self->flash_block_size = 0x10000; /* 64KB */ + self->flash_sector_size = 0x1000; /* 4KB */ + self->flash_rw_size = 0x40; /* 64B */ +} + +static void +fu_genesys_usbhub_device_class_init(FuGenesysUsbhubDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->probe = fu_genesys_usbhub_device_probe; + klass_device->open = fu_genesys_usbhub_device_open; + klass_device->close = fu_genesys_usbhub_device_close; + klass_device->setup = fu_genesys_usbhub_device_setup; + klass_device->dump_firmware = fu_genesys_usbhub_device_dump_firmware; + klass_device->prepare_firmware = fu_genesys_usbhub_device_prepare_firmware; + klass_device->write_firmware = fu_genesys_usbhub_device_write_firmware; + klass_device->set_progress = fu_genesys_usbhub_device_set_progress; + klass_device->detach = fu_genesys_usbhub_device_detach; + klass_device->attach = fu_genesys_usbhub_device_attach; + klass_device->to_string = fu_genesys_usbhub_device_to_string; + klass_device->set_quirk_kv = fu_genesys_usbhub_device_set_quirk_kv; +} diff --git a/plugins/genesys/fu-genesys-usbhub-device.h b/plugins/genesys/fu-genesys-usbhub-device.h new file mode 100644 index 000000000..b16b51bc1 --- /dev/null +++ b/plugins/genesys/fu-genesys-usbhub-device.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2021 Ricardo Cañuelo + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_GENESYS_USBHUB_DEVICE (fu_genesys_usbhub_device_get_type()) +G_DECLARE_FINAL_TYPE(FuGenesysUsbhubDevice, + fu_genesys_usbhub_device, + FU, + GENESYS_USBHUB_DEVICE, + FuUsbDevice) diff --git a/plugins/genesys/fu-genesys-usbhub-firmware.c b/plugins/genesys/fu-genesys-usbhub-firmware.c new file mode 100644 index 000000000..10072ccd2 --- /dev/null +++ b/plugins/genesys/fu-genesys-usbhub-firmware.c @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2021 Gaël PORTAY + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-genesys-common.h" +#include "fu-genesys-usbhub-firmware.h" + +struct _FuGenesysUsbhubFirmware { + FuFirmwareClass parent_instance; + FuGenesysStaticToolString static_ts; +}; + +G_DEFINE_TYPE(FuGenesysUsbhubFirmware, fu_genesys_usbhub_firmware, FU_TYPE_FIRMWARE) + +static gboolean +fu_genesys_usbhub_firmware_verify(const guint8 *buf, gsize bufsz, guint16 code_size, GError **error) +{ + guint16 fw_checksum, checksum; + + /* check code-size */ + if (code_size < sizeof(checksum)) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "code-size is too small: %u bytes", + code_size); + return FALSE; + } + + /* get checksum */ + if (!fu_common_read_uint16_safe(buf, + bufsz, + code_size - sizeof(checksum), + &fw_checksum, + G_BIG_ENDIAN, + error)) + return FALSE; + + /* calculate checksum */ + checksum = fu_common_sum16(buf, code_size - sizeof(checksum)); + if (checksum != fw_checksum) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INTERNAL, + "checksum mismatch, got 0x%04x, expected 0x%04x", + checksum, + fw_checksum); + return FALSE; + } + + return TRUE; +} + +static gboolean +fu_genesys_usbhub_firmware_parse(FuFirmware *firmware, + GBytes *fw, + guint64 addr_start, + guint64 addr_end, + FwupdInstallFlags flags, + GError **error) +{ + FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware); + gsize bufsz = 0; + const guint8 *buf = g_bytes_get_data(fw, &bufsz); + guint8 sign[4]; + guint16 code_size = 0x6000; + guint16 version_raw = 0; + gboolean is3590 = FALSE; + g_autofree gchar *version = NULL; + + /* check signature */ + if (!fu_memcpy_safe(sign, + sizeof(sign), + 0, /* dst */ + buf, + bufsz, + 0xfc, + sizeof(sign), + error)) + return FALSE; + + if (memcmp(sign, "XROM", sizeof(sign)) != 0 && memcmp(sign, "SRON", sizeof(sign)) != 0) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "signature not supported"); + return FALSE; + } + + /* get static tool string */ + if (!fu_memcpy_safe((guint8 *)&self->static_ts, + sizeof(self->static_ts), + 0, /* dst */ + buf, + bufsz, + 0x221, /* src */ + sizeof(self->static_ts), + error)) + return FALSE; + + /* not a GL3523, is GL3590? */ + if (memcmp(self->static_ts.mask_project_ic_type, "3523", 4) != 0) { + if (!fu_memcpy_safe((guint8 *)&self->static_ts, + sizeof(self->static_ts), + 0, /* dst */ + buf, + bufsz, + 0x241, /* src */ + sizeof(self->static_ts), + error)) + return FALSE; + + /* not a GL3590 either */ + if (memcmp(self->static_ts.mask_project_ic_type, "3590", 4) != 0) { + g_autofree gchar *tmp = + fu_common_strsafe((const gchar *)&self->static_ts.mask_project_ic_type, + sizeof(self->static_ts.mask_project_ic_type)); + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "IC type %s not supported", + tmp); + return FALSE; + } + + is3590 = TRUE; + } + + /* unsupported static tool string */ + if (self->static_ts.tool_string_version == 0xff) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "Static Tool String not supported"); + return FALSE; + } + + /* deduce code size */ + if (!is3590) { + guint8 ic_type_revision; + + code_size = 0x6000; + ic_type_revision = 10 * (self->static_ts.mask_project_ic_type[4] - '0') + + (self->static_ts.mask_project_ic_type[5] - '0'); + if (ic_type_revision == 50) + code_size = 0x8000; + } else { + code_size = 0x6000; + } + + /* calculate checksum */ + if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) + if (!fu_genesys_usbhub_firmware_verify(buf, bufsz, code_size, error)) + return FALSE; + + /* get firmware version */ + if (!fu_common_read_uint16_safe(buf, bufsz, 0x10E, &version_raw, G_LITTLE_ENDIAN, error)) + return FALSE; + fu_firmware_set_version_raw(firmware, version_raw); + version = + g_strdup_printf("%02x.%02x", (version_raw & 0xFF00U) >> 8, (version_raw & 0x00FFU)); + fu_firmware_set_version(firmware, version); + + /* success */ + return TRUE; +} + +static GBytes * +fu_genesys_usbhub_firmware_write(FuFirmware *firmware, GError **error) +{ + FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware); + g_autoptr(GByteArray) buf = g_byte_array_new(); + guint16 code_size = 0x6000; + guint16 checksum; + const guint8 sign[] = {'X', 'R', 'O', 'M'}; + + /* fixed size */ + fu_byte_array_set_size(buf, code_size); + + /* signature */ + if (!fu_memcpy_safe(buf->data, + buf->len, + 0xfc, /* dst */ + sign, + sizeof(sign), + 0x0, /* src */ + sizeof(sign), + error)) + return NULL; + + /* static tool string */ + if (!fu_memcpy_safe(buf->data, + buf->len, + 0x221, /* dst */ + (const guint8 *)&self->static_ts, + sizeof(self->static_ts), + 0x0, /* src */ + sizeof(self->static_ts), + error)) + return NULL; + + /* checksum */ + checksum = fu_common_sum16(buf->data, code_size - sizeof(checksum)); + if (!fu_common_write_uint16_safe(buf->data, + buf->len, + code_size - sizeof(guint16), + checksum, + G_BIG_ENDIAN, + error)) + return NULL; + + /* version */ + if (!fu_common_write_uint16_safe(buf->data, + buf->len, + 0x10E, + 0x1234, // TODO: parse from firmware version string + G_BIG_ENDIAN, + error)) + return NULL; + + /* success */ + return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); +} + +static gchar * +fu_genesys_usbhub_firmware_get_project_ic_type_string(const gchar *tmp) +{ + return g_strdup_printf("GL%c%c%c%c-%c%c", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5]); +} + +static void +fu_genesys_usbhub_firmware_export(FuFirmware *firmware, + FuFirmwareExportFlags flags, + XbBuilderNode *bn) +{ + FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware); + g_autofree gchar *tool_string_version = NULL; + g_autofree gchar *mask_project_code = NULL; + g_autofree gchar *mask_project_hardware = NULL; + g_autofree gchar *mask_project_firmware = NULL; + g_autofree gchar *mask_project_ic_type = NULL; + g_autofree gchar *running_project_code = NULL; + g_autofree gchar *running_project_hardware = NULL; + g_autofree gchar *running_project_firmware = NULL; + g_autofree gchar *running_project_ic_type = NULL; + + tool_string_version = fu_common_strsafe((const gchar *)&self->static_ts.tool_string_version, + sizeof(self->static_ts.tool_string_version)); + fu_xmlb_builder_insert_kv(bn, "tool_string_version", tool_string_version); + + mask_project_code = fu_common_strsafe((const gchar *)&self->static_ts.mask_project_code, + sizeof(self->static_ts.mask_project_code)); + fu_xmlb_builder_insert_kv(bn, "mask_project_code", mask_project_code); + + mask_project_hardware = + fu_common_strsafe((const gchar *)&self->static_ts.mask_project_hardware, + sizeof(self->static_ts.mask_project_hardware)); + if (mask_project_hardware != NULL) + mask_project_hardware[0] += 0x10; /* '1' -> 'A'... */ + fu_xmlb_builder_insert_kv(bn, "mask_project_hardware", mask_project_hardware); + + mask_project_firmware = + fu_common_strsafe((const gchar *)&self->static_ts.mask_project_firmware, + sizeof(self->static_ts.mask_project_firmware)); + fu_xmlb_builder_insert_kv(bn, "mask_project_firmware", mask_project_firmware); + + mask_project_ic_type = fu_genesys_usbhub_firmware_get_project_ic_type_string( + (const gchar *)self->static_ts.mask_project_ic_type); + fu_xmlb_builder_insert_kv(bn, "mask_project_ic_type", mask_project_ic_type); + + running_project_code = + fu_common_strsafe((const gchar *)&self->static_ts.running_project_code, + sizeof(self->static_ts.running_project_code)); + fu_xmlb_builder_insert_kv(bn, "running_project_code", running_project_code); + + running_project_hardware = + fu_common_strsafe((const gchar *)&self->static_ts.running_project_hardware, + sizeof(self->static_ts.running_project_hardware)); + if (running_project_hardware != NULL) + running_project_hardware[0] += 0x10; /* '1' -> 'A'... */ + fu_xmlb_builder_insert_kv(bn, "running_project_hardware", running_project_hardware); + + running_project_firmware = + fu_common_strsafe((const gchar *)&self->static_ts.running_project_firmware, + sizeof(self->static_ts.running_project_firmware)); + fu_xmlb_builder_insert_kv(bn, "running_project_firmware", running_project_firmware); + + running_project_ic_type = fu_genesys_usbhub_firmware_get_project_ic_type_string( + (const gchar *)self->static_ts.running_project_ic_type); + fu_xmlb_builder_insert_kv(bn, "running_project_ic_type", running_project_ic_type); +} + +static gboolean +fu_genesys_usbhub_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) +{ + FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware); + const gchar *tmp; + guint64 tmp64; + + /* optional properties */ + tmp64 = xb_node_query_text_as_uint(n, "tool_string_version", NULL); + if (tmp64 != G_MAXUINT64) { + if (tmp64 > G_MAXUINT8) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid tool_string_version"); + return FALSE; + } + self->static_ts.tool_string_version = tmp64; + } + + /* mask_project_code */ + tmp = xb_node_query_text(n, "mask_project_code", NULL); + if (tmp != NULL) { + gsize len = strlen(tmp); + if (len != 4) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid mask_project_code %s, got 0x%x length", + tmp, + (guint)len); + return FALSE; + } + if (!fu_memcpy_safe((guint8 *)&self->static_ts.mask_project_code, + sizeof(self->static_ts.mask_project_code), + 0x0, /* dst */ + (const guint8 *)tmp, + len, + 0x0, /* src */ + len, + error)) + return FALSE; + } + + /* mask_project_ic_type */ + tmp = xb_node_query_text(n, "mask_project_ic_type", NULL); + if (tmp != NULL) { + gsize len = strlen(tmp); + if (len != 6) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "invalid mask_project_ic_type %s, got 0x%x length", + tmp, + (guint)len); + return FALSE; + } + if (!fu_memcpy_safe((guint8 *)&self->static_ts.mask_project_ic_type, + sizeof(self->static_ts.mask_project_ic_type), + 0x0, /* dst */ + (const guint8 *)tmp, + len, + 0x0, /* src */ + len, + error)) + return FALSE; + } + + /* success */ + return TRUE; +} + +static void +fu_genesys_usbhub_firmware_init(FuGenesysUsbhubFirmware *self) +{ + fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); +} + +static void +fu_genesys_usbhub_firmware_class_init(FuGenesysUsbhubFirmwareClass *klass) +{ + FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); + klass_firmware->parse = fu_genesys_usbhub_firmware_parse; + klass_firmware->export = fu_genesys_usbhub_firmware_export; + klass_firmware->build = fu_genesys_usbhub_firmware_build; + klass_firmware->write = fu_genesys_usbhub_firmware_write; +} + +FuFirmware * +fu_genesys_usbhub_firmware_new(void) +{ + return FU_FIRMWARE(g_object_new(FU_TYPE_GENESYS_USBHUB_FIRMWARE, NULL)); +} diff --git a/plugins/genesys/fu-genesys-usbhub-firmware.h b/plugins/genesys/fu-genesys-usbhub-firmware.h new file mode 100644 index 000000000..98438163a --- /dev/null +++ b/plugins/genesys/fu-genesys-usbhub-firmware.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 Gaël PORTAY + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_GENESYS_USBHUB_FIRMWARE (fu_genesys_usbhub_firmware_get_type()) +G_DECLARE_FINAL_TYPE(FuGenesysUsbhubFirmware, + fu_genesys_usbhub_firmware, + FU, + GENESYS_USBHUB_FIRMWARE, + FuFirmware) + +FuFirmware * +fu_genesys_usbhub_firmware_new(void); diff --git a/plugins/genesys/fu-plugin-genesys.c b/plugins/genesys/fu-plugin-genesys.c new file mode 100644 index 000000000..257df77d8 --- /dev/null +++ b/plugins/genesys/fu-plugin-genesys.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Ricardo Cañuelo + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-genesys-scaler-firmware.h" +#include "fu-genesys-usbhub-device.h" +#include "fu-genesys-usbhub-firmware.h" + +static void +fu_plugin_genesys_init(FuPlugin *plugin) +{ + FuContext *ctx = fu_plugin_get_context(plugin); + fu_context_add_quirk_key(ctx, "GenesysScalerGpioOutputRegister"); + fu_context_add_quirk_key(ctx, "GenesysScalerGpioEnableRegister"); + fu_context_add_quirk_key(ctx, "GenesysScalerGpioValue"); + fu_plugin_add_device_gtype(plugin, FU_TYPE_GENESYS_USBHUB_DEVICE); + fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_GENESYS_USBHUB_FIRMWARE); + fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_GENESYS_SCALER_FIRMWARE); +} + +void +fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) +{ + vfuncs->build_hash = FU_BUILD_HASH; + vfuncs->init = fu_plugin_genesys_init; +} diff --git a/plugins/genesys/genesys.quirk b/plugins/genesys/genesys.quirk new file mode 100644 index 000000000..e8ac02c8c --- /dev/null +++ b/plugins/genesys/genesys.quirk @@ -0,0 +1,25 @@ +# M2xfd + +# usbhub +[USB\VID_03F0&PID_0610] +Plugin = genesys +Name = HP USB-C Controller +Flags = has-mstar-scaler +GenesysUsbhubSwitchRequest = 0xA1 +GenesysUsbhubReadRequest = 0xA2 +GenesysUsbhubWriteRequest = 0xA3 + +# scaler +[GENESYS_SCALER\MSTAR_TSUM_G&PUBKEY_B335BDCE-7073-5D0E-9BD3-9B69C1A6899F] +Name = HP M24fd USB-C Monitor +Flags = use-i2c-ch0 +GenesysScalerGpioOutputRegister = 0x0426 +GenesysScalerGpioEnableRegister = 0x0428 +GenesysScalerGpioValue = 0x01 + +[GENESYS_SCALER\MSTAR_TSUM_G&PUBKEY_847A3650-8648-586B-83C8-8B53714F37E3] +Name = HP M27fd USB-C Monitor +Flags = use-i2c-ch0 +GenesysScalerGpioOutputRegister = 0x0426 +GenesysScalerGpioEnableRegister = 0x0428 +GenesysScalerGpioValue = 0x01 diff --git a/plugins/genesys/meson.build b/plugins/genesys/meson.build new file mode 100644 index 000000000..3a77057e9 --- /dev/null +++ b/plugins/genesys/meson.build @@ -0,0 +1,35 @@ +if get_option('gusb') +cargs = ['-DG_LOG_DOMAIN="FuPluginGenesys"'] + +install_data([ + 'genesys.quirk', + ], + install_dir: join_paths(datadir, 'fwupd', 'quirks.d') +) + +shared_module('fu_plugin_genesys', + fu_hash, + sources : [ + 'fu-genesys-scaler-firmware.c', # fuzzing + 'fu-genesys-usbhub-firmware.c', # fuzzing + 'fu-genesys-scaler-device.c', + 'fu-genesys-usbhub-device.c', + 'fu-plugin-genesys.c', + ], + include_directories : [ + root_incdir, + fwupd_incdir, + fwupdplugin_incdir, + ], + install : true, + install_dir: plugin_dir, + link_with : [ + fwupd, + fwupdplugin, + ], + c_args : cargs, + dependencies : [ + plugin_deps, + ], +) +endif diff --git a/plugins/genesys/tests/genesys-scaler.bin b/plugins/genesys/tests/genesys-scaler.bin new file mode 100644 index 000000000..4ce1ccd05 --- /dev/null +++ b/plugins/genesys/tests/genesys-scaler.bin @@ -0,0 +1 @@ +hello worldmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstar '?> >2+)7,713rm2tarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarmstarm \ No newline at end of file diff --git a/plugins/genesys/tests/genesys-scaler.builder.xml b/plugins/genesys/tests/genesys-scaler.builder.xml new file mode 100644 index 000000000..251e4a49a --- /dev/null +++ b/plugins/genesys/tests/genesys-scaler.builder.xml @@ -0,0 +1,4 @@ + + aGVsbG8gd29ybGQ= + A + diff --git a/plugins/genesys/tests/genesys-usbhub.bin b/plugins/genesys/tests/genesys-usbhub.bin new file mode 100644 index 000000000..8b500ac1e Binary files /dev/null and b/plugins/genesys/tests/genesys-usbhub.bin differ diff --git a/plugins/genesys/tests/genesys-usbhub.builder.xml b/plugins/genesys/tests/genesys-usbhub.builder.xml new file mode 100644 index 000000000..94d6c9d49 --- /dev/null +++ b/plugins/genesys/tests/genesys-usbhub.builder.xml @@ -0,0 +1,5 @@ + + aGVsbG8gd29ybGQ= + 1 + 352310 + diff --git a/plugins/meson.build b/plugins/meson.build index 6da3d1e7b..df1c7fbef 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -24,6 +24,7 @@ subdir('ep963x') subdir('fastboot') subdir('flashrom') subdir('fresco-pd') +subdir('genesys') subdir('goodix-moc') subdir('gpio') subdir('hailuck')