fwupd/plugins/genesys/fu-genesys-scaler-device.c
2022-06-14 14:36:52 -05:00

1980 lines
55 KiB
C

/*
* Copyright (C) 2021 Gaël PORTAY <gael.portay@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include <gusb.h>
#include <string.h>
#include "fu-genesys-common.h"
#include "fu-genesys-scaler-device.h"
#include "fu-genesys-scaler-firmware.h"
#define GENESYS_SCALER_BANK_SIZE 0x200000U
#define GENESYS_SCALER_MSTAR_READ 0x7a
#define GENESYS_SCALER_MSTAR_WRITE 0x7b
#define GENESYS_SCALER_MSTAR_DATA_OUT 0x7c
#define GENESYS_SCALER_MSTAR_DATA_IN 0x7f
#define GENESYS_SCALER_CMD_DDCCI_FIRMWARE_PACKET_VERSION 0x06
#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 /* 5s */
/**
* FU_SCALER_FLAG_PAUSE_R2_CPU:
*
* Pause R2 CPU.
*
* Since 1.7.6
*/
#define FU_SCALER_FLAG_PAUSE_R2_CPU (1 << 1)
/**
* FU_SCALER_FLAG_USE_I2C_CH0:
*
* Use I2C ch0.
*
* Since 1.7.6
*/
#define FU_SCALER_FLAG_USE_I2C_CH0 (1 << 0)
typedef struct {
guint8 req_read;
guint8 req_write;
} FuGenesysVendorCommand;
typedef struct {
guint8 stage;
guint8 model;
guint8 major;
guint8 minor;
} FuGenesysScalerFirmwarePacketVersion;
struct _FuGenesysScalerDevice {
FuDevice parent_instance;
guint8 level;
FuGenesysPublicKey public_key;
guint32 cfi_flash_id;
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;
};
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); /* 1ms */
/* 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_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"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_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"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_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"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_genesys_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_genesys_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 */
}
/* success */
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 */
if (!fu_device_retry_full(FU_DEVICE(self),
fu_genesys_scaler_device_wait_flash_control_register_cb,
100,
50, /* 50ms */
&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 */
if (!fu_device_retry_full(FU_DEVICE(self),
fu_genesys_scaler_device_wait_flash_control_register_cb,
100,
50, /* 50ms */
&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 */
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 guint8
fu_genesys_scaler_device_calculate_checksum(guint8 *buf, gsize bufsz)
{
guint8 checksum = 0x00;
for (gsize i = 0; i < bufsz; i++)
checksum ^= buf[i];
return checksum;
}
static gboolean
fu_genesys_scaler_device_get_ddcci_data(FuGenesysScalerDevice *self,
guint8 cmd,
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));
guint8 data[] = {0x6e, 0x51, 0x83, 0xcd, 0x01, 0x00 /* command */, 0x00 /* checksum */};
data[5] = cmd;
data[6] = fu_genesys_scaler_device_calculate_checksum(data, 6);
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,
GENESYS_SCALER_MSTAR_DATA_OUT,
0x0000, /* value */
0x0000, /* idx */
data, /* data */
sizeof(data), /* data length */
NULL, /* actual length */
GENESYS_SCALER_USB_TIMEOUT,
NULL,
error)) {
g_prefix_error(error, "error setting dddci data: ");
return FALSE;
}
g_usleep(100000); /* 100ms */
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_MSTAR_DATA_IN,
0x0001, /* value */
0x0000, /* idx */
buf, /* data */
bufsz, /* data length */
NULL, /* actual length */
GENESYS_SCALER_USB_TIMEOUT,
NULL,
error)) {
g_prefix_error(error, "error getting dddci data: ");
return FALSE;
}
g_usleep(100000); /* 100ms */
/* success */
return TRUE;
}
static gboolean
fu_genesys_scaler_device_get_firmware_packet_version(FuGenesysScalerDevice *self,
FuGenesysScalerFirmwarePacketVersion *ver,
GError **error)
{
guint8 buf[0x40];
guint8 offset = 4;
if (!fu_genesys_scaler_device_get_ddcci_data(
self,
GENESYS_SCALER_CMD_DDCCI_FIRMWARE_PACKET_VERSION,
buf,
sizeof(buf),
error))
return FALSE;
if (buf[0] == 0x6f && buf[1] == 0x6e) {
gsize len = buf[2] ^ 0x80;
guint8 checksum;
guint8 checksum_tmp = 0x0;
if (len > sizeof(buf) - 3) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"error dddci length too large, got 0x%x, expected <= 0x%zx: ",
(guint)len,
sizeof(buf));
return FALSE;
}
buf[0] = 0x50; /* drifted value */
checksum = fu_genesys_scaler_device_calculate_checksum(buf, len + 3);
if (!fu_memread_uint8_safe(buf, sizeof(buf), len + 3, &checksum_tmp, error))
return FALSE;
if (checksum_tmp != checksum) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"error dddci checksum mismatch, got 0x%02x, expected 0x%02x",
checksum_tmp,
checksum);
return FALSE;
}
offset = 7;
}
ver->stage = buf[offset];
ver->model = buf[offset + 1];
ver->major = buf[offset + 2];
ver->minor = buf[offset + 3];
/* success */
return TRUE;
}
static gboolean
fu_genesys_scaler_device_probe(FuDevice *device, GError **error)
{
FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device);
FuGenesysScalerFirmwarePacketVersion ver;
guint8 buf[7 + 1] = {0};
g_autofree gchar *guid = NULL;
g_autofree gchar *version = NULL;
g_autofree gchar *panelrev = NULL;
if (!fu_genesys_scaler_device_get_level(self, &self->level, error))
return FALSE;
if (!fu_genesys_scaler_device_get_public_key(self,
(guint8 *)&self->public_key,
sizeof(self->public_key),
error))
return FALSE;
if (memcmp(self->public_key.N, "N = ", 4) != 0 ||
memcmp(self->public_key.E, "E = ", 4) != 0) {
if (g_getenv("FWUPD_GENESYS_SCALER_VERBOSE") != NULL) {
fu_dump_raw(G_LOG_DOMAIN,
"PublicKey",
(const guint8 *)&self->public_key,
sizeof(self->public_key));
}
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_SIGNATURE_INVALID,
"invalid public-key");
return FALSE;
}
guid = fwupd_guid_hash_data((const guint8 *)&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;
/* ?xIM123; where ? is 0x06 (length?) */
panelrev = fu_strsafe((const gchar *)&buf[1], 6);
if (!fu_genesys_scaler_device_get_firmware_packet_version(self, &ver, error))
return FALSE;
version = g_strdup_printf("%d.%d.%d.%d", ver.stage, ver.model, ver.major, ver.minor);
fu_device_set_version(device, version);
fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN);
fu_device_set_logical_id(device, "scaler");
/* add instance ID */
fu_device_add_instance_str(device, "MSTAR", "TSUM_G");
fu_device_add_instance_strup(device, "PUBKEY", guid);
fu_device_add_instance_strup(device, "PANELREV", panelrev);
fu_device_build_instance_id(device, NULL, "GENESYS_SCALER", "MSTAR", "PUBKEY", NULL);
fu_device_build_instance_id(device,
NULL,
"GENESYS_SCALER",
"MSTAR",
"PUBKEY",
"PANELREV",
NULL);
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_setup(FuDevice *device, GError **error)
{
FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device);
guint64 size_min = fu_device_get_firmware_size_max(device);
guint64 size;
guint32 sector_size;
guint32 page_size;
g_autofree gchar *flash_id = NULL;
flash_id = g_strdup_printf("%06X", self->cfi_flash_id);
self->cfi_device = fu_cfi_device_new(fu_device_get_context(FU_DEVICE(self)), flash_id);
if (!fu_device_setup(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_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE))
size_min *= 2;
size = fu_device_get_firmware_size_max(FU_DEVICE(self->cfi_device));
if (size != 0 && size < size_min) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"CFI device too small, got 0x%x, expected >= 0x%x",
(guint)size,
(guint)size_min);
return FALSE;
}
/* 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_cfi_device_get_size(self->cfi_device);
g_autofree guint8 *buf = NULL;
g_autoptr(FuDeviceLocker) locker = NULL;
/* 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, NULL);
/* 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,
0,
buf,
size,
fu_progress_get_child(progress),
error))
return NULL;
fu_progress_step_done(progress);
/* success */
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(GBytes) blob_payload = NULL;
g_autoptr(GBytes) blob_public_key = NULL;
/* parse firmware */
if (!fu_firmware_parse(firmware, fw, flags, error))
return NULL;
/* check public-key */
blob_public_key =
fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_SIGNATURE, error);
if (blob_public_key == NULL)
return NULL;
if (g_getenv("FWUPD_GENESYS_SCALER_VERBOSE") != NULL) {
fu_dump_raw(G_LOG_DOMAIN,
"PublicKey",
g_bytes_get_data(blob_public_key, NULL),
g_bytes_get_size(blob_public_key));
}
if (memcmp(g_bytes_get_data(blob_public_key, NULL),
&self->public_key,
sizeof(self->public_key)) != 0 &&
(flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_SIGNATURE_INVALID,
"mismatch public-key");
return NULL;
}
/* check size */
blob_payload = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error);
if (blob_payload == NULL)
return NULL;
if (g_bytes_get_size(blob_payload) > 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(blob_payload),
(guint)fu_device_get_firmware_size_max(device));
return NULL;
}
/* 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 = 0;
gsize size;
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, NULL);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 54, NULL);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 42, NULL);
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE))
addr = GENESYS_SCALER_BANK_SIZE;
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_memcmp_safe(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_string_append_kx(str, idt, "Level", self->level);
if (fu_memcpy_safe((guint8 *)public_key_e,
sizeof(public_key_e),
0, /* dst */
(const guint8 *)&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_string_append(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 */
(const guint8 *)&self->public_key,
sizeof(self->public_key),
4, /* src */
sizeof(public_key_n) - 1,
&error_local_n)) {
fu_string_append(str, idt, "PublicKeyN", public_key_n);
} else {
g_debug("ignoring public-key parameter N: %s", error_local_n->message);
}
fu_string_append_kx(str, idt, "ReadRequestRead", self->vc.req_read);
fu_string_append_kx(str, idt, "WriteRequest", self->vc.req_write);
fu_string_append_kx(str, idt, "SectorSize", self->sector_size);
fu_string_append_kx(str, idt, "PageSize", self->page_size);
fu_string_append_kx(str, idt, "TransferSize", self->transfer_size);
fu_string_append_kx(str, idt, "GpioOutputRegister", self->gpio_out_reg);
fu_string_append_kx(str, idt, "GpioEnableRegister", self->gpio_en_reg);
fu_string_append_kx(str, idt, "GpioValue", self->gpio_val);
fu_string_append_kx(str, idt, "CfiFlashId", self->cfi_flash_id);
}
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_strtoull(value, &tmp, 0, G_MAXUINT32, error))
return FALSE;
self->transfer_size = tmp;
/* success */
return TRUE;
}
if (g_strcmp0(key, "GenesysScalerGpioOutputRegister") == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error))
return FALSE;
self->gpio_out_reg = tmp;
/* success */
return TRUE;
}
if (g_strcmp0(key, "GenesysScalerGpioEnableRegister") == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error))
return FALSE;
self->gpio_en_reg = tmp;
/* success */
return TRUE;
}
if (g_strcmp0(key, "GenesysScalerGpioValue") == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error))
return FALSE;
self->gpio_val = tmp;
/* success */
return TRUE;
}
if (g_strcmp0(key, "GenesysScalerCfiFlashId") == 0) {
if (!fu_strtoull(value, &tmp, 0, 0x00ffffffU, error))
return FALSE;
self->cfi_flash_id = tmp;
/* success */
return TRUE;
}
/* failure */
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); /* 10ms */
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN);
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");
fu_device_set_install_duration(FU_DEVICE(self), 730); /* 12min 10s */
self->sector_size = 0x1000; /* 4KB */
self->page_size = 0x100; /* 256B */
self->transfer_size = 0x40; /* 64B */
fu_device_set_firmware_size(FU_DEVICE(self), GENESYS_SCALER_BANK_SIZE); /* 2MB */
}
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->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;
}