<commit_before>/*
 * Copyright (C) 2012-2014 <NAME>
 * Copyright (C) 2012-2019 Synaptics Inc.
 * Copyright (C) 2019 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include "fu-common.h"
#include "fu-chunk.h"
#include "fu-synaptics-rmi-v7-device.h"

#include "fwupd-error.h"

#define RMI_F34_ERASE_WAIT_MS				10000		/* ms */

typedef enum {
	RMI_FLASH_CMD_IDLE				= 0x00,
	RMI_FLASH_CMD_ENTER_BL,
	RMI_FLASH_CMD_READ,
	RMI_FLASH_CMD_WRITE,
	RMI_FLASH_CMD_ERASE,
	RMI_FLASH_CMD_ERASE_AP,
	RMI_FLASH_CMD_SENSOR_ID,
} RmiFlashCommand;

typedef enum {
	RMI_PARTITION_ID_NONE				= 0x00,
	RMI_PARTITION_ID_BOOTLOADER			= 0x01,
	RMI_PARTITION_ID_DEVICE_CONFIG,
	RMI_PARTITION_ID_FLASH_CONFIG,
	RMI_PARTITION_ID_MANUFACTURING_BLOCK,
	RMI_PARTITION_ID_GUEST_SERIALIZATION,
	RMI_PARTITION_ID_GLOBAL_PARAMETERS,
	RMI_PARTITION_ID_CORE_CODE,
	RMI_PARTITION_ID_CORE_CONFIG,
	RMI_PARTITION_ID_GUEST_CODE,
	RMI_PARTITION_ID_DISPLAY_CONFIG,
	RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG,
	RMI_PARTITION_ID_UTILITY_PARAMETER,
} RmiPartitionId;

gboolean
fu_synaptics_rmi_v7_device_detach (FuDevice *device, GError **error)
{
	FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device);
	g_autoptr(GByteArray) enable_req = g_byte_array_new ();
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	FuSynapticsRmiFunction *f34;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* disable interrupts */
	if (!fu_synaptics_rmi_device_disable_irqs (self, error))
		return FALSE;

	/* enter BL */
	fu_byte_array_append_uint8 (enable_req, RMI_PARTITION_ID_BOOTLOADER);
	fu_byte_array_append_uint32 (enable_req, 0x0, G_LITTLE_ENDIAN);
	fu_byte_array_append_uint8 (enable_req, RMI_FLASH_CMD_ENTER_BL);
	fu_byte_array_append_uint8 (enable_req, flash->bootloader_id[0]);
	fu_byte_array_append_uint8 (enable_req, flash->bootloader_id[1]);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 1,
					    enable_req,
					    error)) {
		g_prefix_error (error, "failed to enable programming: ");
		return FALSE;
	}

	fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
	g_usleep (1000 * RMI_F34_ENABLE_WAIT_MS);
	return fu_synaptics_rmi_device_rebind_driver (self, error);
}

static gboolean
fu_synaptics_rmi_v7_device_erase_all (FuSynapticsRmiDevice *self, GError **error)
{
	FuSynapticsRmiFunction *f34;
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	g_autoptr(GByteArray) erase_cmd = g_byte_array_new ();

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	fu_byte_array_append_uint8 (erase_cmd, RMI_PARTITION_ID_CORE_CODE);
	fu_byte_array_append_uint32 (erase_cmd, 0x0, G_LITTLE_ENDIAN);
	if (flash->bootloader_id[1] == 8) {
		/* For bootloader v8 */
		fu_byte_array_append_uint8 (erase_cmd, RMI_FLASH_CMD_ERASE_AP);
	} else {
		/* For bootloader v7 */
		fu_byte_array_append_uint8 (erase_cmd, RMI_FLASH_CMD_ERASE);
	}
	fu_byte_array_append_uint8 (erase_cmd, flash->bootloader_id[0]);
	fu_byte_array_append_uint8 (erase_cmd, flash->bootloader_id[1]);
	/* for BL8 device, we need hold 1 seconds after querying F34 status to
	 * avoid not get attention by following giving erase command */
	if (flash->bootloader_id[1] == 8)
		g_usleep (1000 * 1000);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 1,
					    erase_cmd,
					    error)) {
		g_prefix_error (error, "failed to unlock erasing: ");
		return FALSE;
	}
	g_usleep (1000 * 100);
	if (flash->bootloader_id[1] == 8){
		/* wait for ATTN */
		if (!fu_synaptics_rmi_device_wait_for_idle (self,
							    RMI_F34_ERASE_WAIT_MS,
							    RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE,
							    error)) {
			g_prefix_error (error, "failed to wait for idle: ");
			return FALSE;
		}
	}
	if (!fu_synaptics_rmi_device_poll_wait (self, error)) {
		g_prefix_error (error, "failed to get flash success: ");
		return FALSE;
	}

	/* for BL7, we need erase config partition */
	if (flash->bootloader_id[1] == 7) {
		g_autoptr(GByteArray) erase_config_cmd = g_byte_array_new ();

		fu_byte_array_append_uint8 (erase_config_cmd, RMI_PARTITION_ID_CORE_CONFIG);
		fu_byte_array_append_uint32 (erase_config_cmd, 0x0, G_LITTLE_ENDIAN);
		fu_byte_array_append_uint8 (erase_config_cmd, RMI_FLASH_CMD_ERASE);

		g_usleep (1000 * 100);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 1,
						    erase_config_cmd,
						    error)) {
			g_prefix_error (error, "failed to erase core config: ");
			return FALSE;
		}

		/* wait for ATTN */
		g_usleep (1000 * 100);
		if (!fu_synaptics_rmi_device_wait_for_idle (self,
							    RMI_F34_ERASE_WAIT_MS,
							    RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34,
							    error)) {
			g_prefix_error (error, "failed to wait for idle: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_device_poll_wait (self, error)) {
			g_prefix_error (error, "failed to get flash success: ");
			return FALSE;
		}
	}
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_write_blocks (FuSynapticsRmiDevice *self,
					 guint32 address,
					 const guint8 *data,
					 guint32 datasz,
					 GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	g_autoptr(GPtrArray) chunks = NULL;

	/* write FW blocks */
	chunks = fu_chunk_array_new (data, datasz,
				     0x00,	/* start addr */
				     0x00,	/* page_sz */
				     flash->block_size);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chk = g_ptr_array_index (chunks, i);
		g_autoptr(GByteArray) req = g_byte_array_new ();
		g_byte_array_append (req, chk->data, chk->data_sz);
		if (!fu_synaptics_rmi_device_write (self, address, req, error)) {
			g_prefix_error (error, "failed to write block @0x%x:%x ", address, chk->address);
			return FALSE;
		}
	}

	/* wait for idle */
	if (!fu_synaptics_rmi_device_wait_for_idle (self,
						    RMI_F34_IDLE_WAIT_MS,
						    RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE,
						    error)) {
		g_prefix_error (error, "failed to wait for idle @0x%x: ", address);
		return FALSE;
	}

	/* success */
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_write_partition (FuSynapticsRmiDevice *self,
					    guint partition_id,
					    GBytes *bytes,
					    GError **error)
{
	FuSynapticsRmiFunction *f34;
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	g_autoptr(GByteArray) req_offset = g_byte_array_new ();
	g_autoptr(GByteArray) req_partition_id = g_byte_array_new ();
	g_autoptr(GPtrArray) chunks = NULL;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* write partition id */
	fu_byte_array_append_uint8 (req_partition_id, partition_id);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 0x1,
					    req_partition_id,
					    error)) {
		g_prefix_error (error, "failed to write flash partition: ");
		return FALSE;
	}
	fu_byte_array_append_uint16 (req_offset, 0x0, G_LITTLE_ENDIAN);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 0x2,
					    req_offset,
					    error)) {
		g_prefix_error (error, "failed to write offset: ");
		return FALSE;
	}

	/* write partition */
	chunks = fu_chunk_array_new_from_bytes (bytes,
						0x00,	/* start addr */
						0x00,	/* page_sz */
						flash->payload_length);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chk = g_ptr_array_index (chunks, i);
		g_autoptr(GByteArray) req_trans_sz = g_byte_array_new ();
		g_autoptr(GByteArray) req_cmd = g_byte_array_new ();
		fu_byte_array_append_uint16 (req_trans_sz, chk->data_sz, G_LITTLE_ENDIAN);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 0x3,
						    req_trans_sz,
						    error)) {
			g_prefix_error (error, "failed to write transfer length: ");
			return FALSE;
		}
		fu_byte_array_append_uint8 (req_cmd, RMI_FLASH_CMD_WRITE);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 0x4,
						    req_cmd,
						    error)) {
			g_prefix_error (error, "failed to flash command: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_v7_device_write_blocks (self,
							      f34->data_base + 0x5,
							      chk->data,
							      chk->data_sz,
							      error))
			return FALSE;
		fu_device_set_progress_full (FU_DEVICE (self), (gsize) i, (gsize) chunks->len);
	}
	return TRUE;
}

gboolean
fu_synaptics_rmi_v7_device_write_firmware (FuDevice *device,
					   FuFirmware *firmware,
					   FwupdInstallFlags flags,
					   GError **error)
{
	FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device);
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	FuSynapticsRmiFunction *f34;
	g_autoptr(GBytes) bytes_bin = NULL;
	g_autoptr(GBytes) bytes_cfg = NULL;
	g_autoptr(GBytes) bytes_flashcfg = NULL;

	/* we should be in bootloader mode now, but check anyway */
	if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "not bootloader, perhaps need detach?!");
		return FALSE;
	}

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* get both images */
	bytes_bin = fu_firmware_get_image_by_id_bytes (firmware, "ui", error);
	if (bytes_bin == NULL)
		return FALSE;
	bytes_cfg = fu_firmware_get_image_by_id_bytes (firmware, "config", error);
	if (bytes_cfg == NULL)
		return FALSE;
	if (flash->bootloader_id[1] == 8) {
		bytes_flashcfg = fu_firmware_get_image_by_id_bytes (firmware, "flash-config", error);
		if (bytes_flashcfg == NULL)
			return FALSE;
	}

	/* disable powersaving */
	if (!fu_synaptics_rmi_device_disable_sleep (self, error))
		return FALSE;

	/* erase all */
	if (!fu_synaptics_rmi_v7_device_erase_all (self, error)) {
		g_prefix_error (error, "failed to erase all: ");
		return FALSE;
	}

	/* write flash config for v8 */
	if (bytes_flashcfg != NULL) {
		if (!fu_synaptics_rmi_v7_device_write_partition (self,
								 RMI_PARTITION_ID_FLASH_CONFIG,
								 bytes_flashcfg,
								 error))
			return FALSE;
	}

	/* write core code */
	if (!fu_synaptics_rmi_v7_device_write_partition (self,
							 RMI_PARTITION_ID_CORE_CODE,
							 bytes_bin,
							 error))
		return FALSE;

	/* write core config */
	if (!fu_synaptics_rmi_v7_device_write_partition (self,
							 RMI_PARTITION_ID_CORE_CONFIG,
							 bytes_bin,
							 error))
		return FALSE;

	/* success */
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_read_blocks (FuSynapticsRmiDevice *self,
					guint32 address,
					const guint8 *data,
					guint32 datasz,
					GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	g_autoptr(GPtrArray) chunks = NULL;

	/* read blocks */
	chunks = fu_chunk_array_new (data, datasz,
				     0x00,	/* start addr */
				     0x00,	/* page_sz */
				     flash->block_size);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chk = g_ptr_array_index (chunks, i);
		g_autoptr(GByteArray) req_tmp = NULL;
		req_tmp = fu_synaptics_rmi_device_read (self, address, chk->data_sz, error);
		if (req_tmp == NULL) {
			g_prefix_error (error, "failed to read block @0x%x: ", chk->address);
			return FALSE;
		}
		if (!fu_memcpy_safe ((guint8 *) chk->data, chk->data_sz, 0x0,	/* dst */
				     req_tmp->data, req_tmp->len, 0x0,		/* src */
				     req_tmp->len, error))
			return FALSE;
	}

	/* success */
	return TRUE;
}

typedef struct {
	guint16			 partition_id;
	guint16			 partition_len;
	guint16			 partition_addr;
	guint16			 partition_prop;
	guint16			 data[8];
} RmiPartitionTbl;

static gboolean
fu_synaptics_rmi_device_read_flash_config_v7 (FuSynapticsRmiDevice *self, GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	FuSynapticsRmiFunction *f34;
	gsize buf_sz = (gsize) flash->block_size * (gsize) flash->config_length;
	g_autofree guint8 *buf = g_malloc0 (buf_sz);
	g_autoptr(GByteArray) req_addr_zero = g_byte_array_new ();
	g_autoptr(GByteArray) req_partition_id = g_byte_array_new ();
	g_autoptr(GPtrArray) chunks = NULL;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* set partition id for bootloader 7 */
	fu_byte_array_append_uint8 (req_partition_id, RMI_PARTITION_ID_FLASH_CONFIG);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 0x1,
					    req_partition_id,
					    error)) {
		g_prefix_error (error, "failed to write flash partition id: ");
		return FALSE;
	}
	fu_byte_array_append_uint16 (req_addr_zero, 0x0, G_LITTLE_ENDIAN);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 0x2,
					    req_addr_zero,
					    error)) {
		g_prefix_error (error, "failed to write flash config address: ");
		return FALSE;
	}

	/* read back entire buffer in payload length chunks */
	chunks = fu_chunk_array_new (buf, buf_sz, 0x0, 0x0, flash->payload_length);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chunk = g_ptr_array_index (chunks, i);
		g_autoptr(GByteArray) req_cmd = g_byte_array_new ();
		g_autoptr(GByteArray) req_read = g_byte_array_new ();

		/* set transfer length */
		fu_byte_array_append_uint16 (req_read, chunk->data_sz, G_LITTLE_ENDIAN);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 0x3,
						    req_read,
						    error)) {
			g_prefix_error (error, "failed to set transfer length: ");
			return FALSE;
		}

		/* set command to read */
		fu_byte_array_append_uint8 (req_cmd, RMI_FLASH_CMD_READ);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 0x4,
						    req_cmd,
						    error)) {
			g_prefix_error (error, "failed to write command to read: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_device_poll_wait (self, error)) {
			g_prefix_error (error, "failed to get flash success: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_v7_device_read_blocks (self,
							     f34->data_base + 0x5,
							     chunk->data,
							     chunk->data_sz,
							     error)) {
			g_prefix_error (error, "failed to read @0x%x: ", chunk->address);
			return FALSE;
		}
	}

	/* parse the config length */
	for (guint i = 0x2; i < buf_sz; i = i + 0x8) {
		RmiPartitionTbl tbl;
		if (!fu_memcpy_safe ((guint8 *) &tbl, sizeof(tbl), 0x0,			/* dst */
				     buf, buf_sz, i,					/* src */
				     sizeof(tbl), error))
			return FALSE;
		if (tbl.partition_id == RMI_PARTITION_ID_CORE_CONFIG) {<commit_msg>synaptics-rmi: Fix the packing of the partition table<commit_after>/*
 * Copyright (C) 2012-2014 <NAME>
 * Copyright (C) 2012-2019 Synaptics Inc.
 * Copyright (C) 2019 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include "fu-common.h"
#include "fu-chunk.h"
#include "fu-synaptics-rmi-v7-device.h"

#include "fwupd-error.h"

#define RMI_F34_ERASE_WAIT_MS				10000		/* ms */

typedef enum {
	RMI_FLASH_CMD_IDLE				= 0x00,
	RMI_FLASH_CMD_ENTER_BL,
	RMI_FLASH_CMD_READ,
	RMI_FLASH_CMD_WRITE,
	RMI_FLASH_CMD_ERASE,
	RMI_FLASH_CMD_ERASE_AP,
	RMI_FLASH_CMD_SENSOR_ID,
} RmiFlashCommand;

typedef enum {
	RMI_PARTITION_ID_NONE				= 0x00,
	RMI_PARTITION_ID_BOOTLOADER			= 0x01,
	RMI_PARTITION_ID_DEVICE_CONFIG,
	RMI_PARTITION_ID_FLASH_CONFIG,
	RMI_PARTITION_ID_MANUFACTURING_BLOCK,
	RMI_PARTITION_ID_GUEST_SERIALIZATION,
	RMI_PARTITION_ID_GLOBAL_PARAMETERS,
	RMI_PARTITION_ID_CORE_CODE,
	RMI_PARTITION_ID_CORE_CONFIG,
	RMI_PARTITION_ID_GUEST_CODE,
	RMI_PARTITION_ID_DISPLAY_CONFIG,
	RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG,
	RMI_PARTITION_ID_UTILITY_PARAMETER,
} RmiPartitionId;

gboolean
fu_synaptics_rmi_v7_device_detach (FuDevice *device, GError **error)
{
	FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device);
	g_autoptr(GByteArray) enable_req = g_byte_array_new ();
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	FuSynapticsRmiFunction *f34;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* disable interrupts */
	if (!fu_synaptics_rmi_device_disable_irqs (self, error))
		return FALSE;

	/* enter BL */
	fu_byte_array_append_uint8 (enable_req, RMI_PARTITION_ID_BOOTLOADER);
	fu_byte_array_append_uint32 (enable_req, 0x0, G_LITTLE_ENDIAN);
	fu_byte_array_append_uint8 (enable_req, RMI_FLASH_CMD_ENTER_BL);
	fu_byte_array_append_uint8 (enable_req, flash->bootloader_id[0]);
	fu_byte_array_append_uint8 (enable_req, flash->bootloader_id[1]);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 1,
					    enable_req,
					    error)) {
		g_prefix_error (error, "failed to enable programming: ");
		return FALSE;
	}

	fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
	g_usleep (1000 * RMI_F34_ENABLE_WAIT_MS);
	return fu_synaptics_rmi_device_rebind_driver (self, error);
}

static gboolean
fu_synaptics_rmi_v7_device_erase_all (FuSynapticsRmiDevice *self, GError **error)
{
	FuSynapticsRmiFunction *f34;
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	g_autoptr(GByteArray) erase_cmd = g_byte_array_new ();

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	fu_byte_array_append_uint8 (erase_cmd, RMI_PARTITION_ID_CORE_CODE);
	fu_byte_array_append_uint32 (erase_cmd, 0x0, G_LITTLE_ENDIAN);
	if (flash->bootloader_id[1] == 8) {
		/* For bootloader v8 */
		fu_byte_array_append_uint8 (erase_cmd, RMI_FLASH_CMD_ERASE_AP);
	} else {
		/* For bootloader v7 */
		fu_byte_array_append_uint8 (erase_cmd, RMI_FLASH_CMD_ERASE);
	}
	fu_byte_array_append_uint8 (erase_cmd, flash->bootloader_id[0]);
	fu_byte_array_append_uint8 (erase_cmd, flash->bootloader_id[1]);
	/* for BL8 device, we need hold 1 seconds after querying F34 status to
	 * avoid not get attention by following giving erase command */
	if (flash->bootloader_id[1] == 8)
		g_usleep (1000 * 1000);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 1,
					    erase_cmd,
					    error)) {
		g_prefix_error (error, "failed to unlock erasing: ");
		return FALSE;
	}
	g_usleep (1000 * 100);
	if (flash->bootloader_id[1] == 8){
		/* wait for ATTN */
		if (!fu_synaptics_rmi_device_wait_for_idle (self,
							    RMI_F34_ERASE_WAIT_MS,
							    RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE,
							    error)) {
			g_prefix_error (error, "failed to wait for idle: ");
			return FALSE;
		}
	}
	if (!fu_synaptics_rmi_device_poll_wait (self, error)) {
		g_prefix_error (error, "failed to get flash success: ");
		return FALSE;
	}

	/* for BL7, we need erase config partition */
	if (flash->bootloader_id[1] == 7) {
		g_autoptr(GByteArray) erase_config_cmd = g_byte_array_new ();

		fu_byte_array_append_uint8 (erase_config_cmd, RMI_PARTITION_ID_CORE_CONFIG);
		fu_byte_array_append_uint32 (erase_config_cmd, 0x0, G_LITTLE_ENDIAN);
		fu_byte_array_append_uint8 (erase_config_cmd, RMI_FLASH_CMD_ERASE);

		g_usleep (1000 * 100);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 1,
						    erase_config_cmd,
						    error)) {
			g_prefix_error (error, "failed to erase core config: ");
			return FALSE;
		}

		/* wait for ATTN */
		g_usleep (1000 * 100);
		if (!fu_synaptics_rmi_device_wait_for_idle (self,
							    RMI_F34_ERASE_WAIT_MS,
							    RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34,
							    error)) {
			g_prefix_error (error, "failed to wait for idle: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_device_poll_wait (self, error)) {
			g_prefix_error (error, "failed to get flash success: ");
			return FALSE;
		}
	}
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_write_blocks (FuSynapticsRmiDevice *self,
					 guint32 address,
					 const guint8 *data,
					 guint32 datasz,
					 GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	g_autoptr(GPtrArray) chunks = NULL;

	/* write FW blocks */
	chunks = fu_chunk_array_new (data, datasz,
				     0x00,	/* start addr */
				     0x00,	/* page_sz */
				     flash->block_size);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chk = g_ptr_array_index (chunks, i);
		g_autoptr(GByteArray) req = g_byte_array_new ();
		g_byte_array_append (req, chk->data, chk->data_sz);
		if (!fu_synaptics_rmi_device_write (self, address, req, error)) {
			g_prefix_error (error, "failed to write block @0x%x:%x ", address, chk->address);
			return FALSE;
		}
	}

	/* wait for idle */
	if (!fu_synaptics_rmi_device_wait_for_idle (self,
						    RMI_F34_IDLE_WAIT_MS,
						    RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE,
						    error)) {
		g_prefix_error (error, "failed to wait for idle @0x%x: ", address);
		return FALSE;
	}

	/* success */
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_write_partition (FuSynapticsRmiDevice *self,
					    guint partition_id,
					    GBytes *bytes,
					    GError **error)
{
	FuSynapticsRmiFunction *f34;
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	g_autoptr(GByteArray) req_offset = g_byte_array_new ();
	g_autoptr(GByteArray) req_partition_id = g_byte_array_new ();
	g_autoptr(GPtrArray) chunks = NULL;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* write partition id */
	fu_byte_array_append_uint8 (req_partition_id, partition_id);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 0x1,
					    req_partition_id,
					    error)) {
		g_prefix_error (error, "failed to write flash partition: ");
		return FALSE;
	}
	fu_byte_array_append_uint16 (req_offset, 0x0, G_LITTLE_ENDIAN);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 0x2,
					    req_offset,
					    error)) {
		g_prefix_error (error, "failed to write offset: ");
		return FALSE;
	}

	/* write partition */
	chunks = fu_chunk_array_new_from_bytes (bytes,
						0x00,	/* start addr */
						0x00,	/* page_sz */
						flash->payload_length);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chk = g_ptr_array_index (chunks, i);
		g_autoptr(GByteArray) req_trans_sz = g_byte_array_new ();
		g_autoptr(GByteArray) req_cmd = g_byte_array_new ();
		fu_byte_array_append_uint16 (req_trans_sz, chk->data_sz, G_LITTLE_ENDIAN);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 0x3,
						    req_trans_sz,
						    error)) {
			g_prefix_error (error, "failed to write transfer length: ");
			return FALSE;
		}
		fu_byte_array_append_uint8 (req_cmd, RMI_FLASH_CMD_WRITE);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 0x4,
						    req_cmd,
						    error)) {
			g_prefix_error (error, "failed to flash command: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_v7_device_write_blocks (self,
							      f34->data_base + 0x5,
							      chk->data,
							      chk->data_sz,
							      error))
			return FALSE;
		fu_device_set_progress_full (FU_DEVICE (self), (gsize) i, (gsize) chunks->len);
	}
	return TRUE;
}

gboolean
fu_synaptics_rmi_v7_device_write_firmware (FuDevice *device,
					   FuFirmware *firmware,
					   FwupdInstallFlags flags,
					   GError **error)
{
	FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE (device);
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	FuSynapticsRmiFunction *f34;
	g_autoptr(GBytes) bytes_bin = NULL;
	g_autoptr(GBytes) bytes_cfg = NULL;
	g_autoptr(GBytes) bytes_flashcfg = NULL;

	/* we should be in bootloader mode now, but check anyway */
	if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "not bootloader, perhaps need detach?!");
		return FALSE;
	}

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* get both images */
	bytes_bin = fu_firmware_get_image_by_id_bytes (firmware, "ui", error);
	if (bytes_bin == NULL)
		return FALSE;
	bytes_cfg = fu_firmware_get_image_by_id_bytes (firmware, "config", error);
	if (bytes_cfg == NULL)
		return FALSE;
	if (flash->bootloader_id[1] == 8) {
		bytes_flashcfg = fu_firmware_get_image_by_id_bytes (firmware, "flash-config", error);
		if (bytes_flashcfg == NULL)
			return FALSE;
	}

	/* disable powersaving */
	if (!fu_synaptics_rmi_device_disable_sleep (self, error))
		return FALSE;

	/* erase all */
	if (!fu_synaptics_rmi_v7_device_erase_all (self, error)) {
		g_prefix_error (error, "failed to erase all: ");
		return FALSE;
	}

	/* write flash config for v8 */
	if (bytes_flashcfg != NULL) {
		if (!fu_synaptics_rmi_v7_device_write_partition (self,
								 RMI_PARTITION_ID_FLASH_CONFIG,
								 bytes_flashcfg,
								 error))
			return FALSE;
	}

	/* write core code */
	if (!fu_synaptics_rmi_v7_device_write_partition (self,
							 RMI_PARTITION_ID_CORE_CODE,
							 bytes_bin,
							 error))
		return FALSE;

	/* write core config */
	if (!fu_synaptics_rmi_v7_device_write_partition (self,
							 RMI_PARTITION_ID_CORE_CONFIG,
							 bytes_bin,
							 error))
		return FALSE;

	/* success */
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_read_blocks (FuSynapticsRmiDevice *self,
					guint32 address,
					const guint8 *data,
					guint32 datasz,
					GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	g_autoptr(GPtrArray) chunks = NULL;

	/* read blocks */
	chunks = fu_chunk_array_new (data, datasz,
				     0x00,	/* start addr */
				     0x00,	/* page_sz */
				     flash->block_size);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chk = g_ptr_array_index (chunks, i);
		g_autoptr(GByteArray) req_tmp = NULL;
		req_tmp = fu_synaptics_rmi_device_read (self, address, chk->data_sz, error);
		if (req_tmp == NULL) {
			g_prefix_error (error, "failed to read block @0x%x: ", chk->address);
			return FALSE;
		}
		if (!fu_memcpy_safe ((guint8 *) chk->data, chk->data_sz, 0x0,	/* dst */
				     req_tmp->data, req_tmp->len, 0x0,		/* src */
				     req_tmp->len, error))
			return FALSE;
	}

	/* success */
	return TRUE;
}

typedef struct __attribute__((packed)) {
	guint16			 partition_id;
	guint16			 partition_len;
	guint16			 partition_addr;
	guint16			 partition_prop;
} RmiPartitionTbl;

G_STATIC_ASSERT(sizeof(RmiPartitionTbl) == 8);

static gboolean
fu_synaptics_rmi_device_read_flash_config_v7 (FuSynapticsRmiDevice *self, GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash (self);
	FuSynapticsRmiFunction *f34;
	gsize buf_sz = (gsize) flash->block_size * (gsize) flash->config_length;
	g_autofree guint8 *buf = g_malloc0 (buf_sz);
	g_autoptr(GByteArray) req_addr_zero = g_byte_array_new ();
	g_autoptr(GByteArray) req_partition_id = g_byte_array_new ();
	g_autoptr(GPtrArray) chunks = NULL;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function (self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* set partition id for bootloader 7 */
	fu_byte_array_append_uint8 (req_partition_id, RMI_PARTITION_ID_FLASH_CONFIG);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 0x1,
					    req_partition_id,
					    error)) {
		g_prefix_error (error, "failed to write flash partition id: ");
		return FALSE;
	}
	fu_byte_array_append_uint16 (req_addr_zero, 0x0, G_LITTLE_ENDIAN);
	if (!fu_synaptics_rmi_device_write (self,
					    f34->data_base + 0x2,
					    req_addr_zero,
					    error)) {
		g_prefix_error (error, "failed to write flash config address: ");
		return FALSE;
	}

	/* read back entire buffer in payload length chunks */
	chunks = fu_chunk_array_new (buf, buf_sz, 0x0, 0x0, flash->payload_length);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chunk = g_ptr_array_index (chunks, i);
		g_autoptr(GByteArray) req_cmd = g_byte_array_new ();
		g_autoptr(GByteArray) req_read = g_byte_array_new ();

		/* set transfer length */
		fu_byte_array_append_uint16 (req_read, chunk->data_sz, G_LITTLE_ENDIAN);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 0x3,
						    req_read,
						    error)) {
			g_prefix_error (error, "failed to set transfer length: ");
			return FALSE;
		}

		/* set command to read */
		fu_byte_array_append_uint8 (req_cmd, RMI_FLASH_CMD_READ);
		if (!fu_synaptics_rmi_device_write (self,
						    f34->data_base + 0x4,
						    req_cmd,
						    error)) {
			g_prefix_error (error, "failed to write command to read: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_device_poll_wait (self, error)) {
			g_prefix_error (error, "failed to get flash success: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_v7_device_read_blocks (self,
							     f34->data_base + 0x5,
							     chunk->data,
							     chunk->data_sz,
							     error)) {
			g_prefix_error (error, "failed to read @0x%x: ", chunk->address);
			return FALSE;
		}
	}

	/* parse the config length */
	for (guint i = 0x2; i < buf_sz; i += sizeof(RmiPartitionTbl)) {
		RmiPartitionTbl tbl;
		if (!fu_memcpy_safe ((guint8 *) &tbl, sizeof(tbl), 0x0,			/* dst */
				     buf, buf_sz, i,					/* src */
				     sizeof(tbl), error))
			return FALSE;
		if (tbl.partition_id == RMI_PARTITION_ID_CORE_CONFIG) {
{'fraction_non_alphanumeric': 0.10573624262385069, 'fraction_numerical': 0.022231370934540962, 'mean_word_length': 3.977629781420765, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 111, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3395357', 'n_tokens_mistral': 13301, 'n_tokens_neox': 11418, 'n_words': 2223}