/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_ERRNO_H #include #endif #include #include #include #include "fu-realtek-mst-device.h" /* firmware debug address */ #define I2C_ADDR_DEBUG 0x35 /* programming address */ #define I2C_ADDR_ISP 0x4a /* some kind of operation attribute bits */ #define REG_CMD_ATTR 0x60 /* write set to begin executing, cleared when done */ #define CMD_ERASE_BUSY 0x01 /* 24-bit address for commands */ #define REG_CMD_ADDR_HI 0x64 #define REG_CMD_ADDR_MID 0x65 #define REG_CMD_ADDR_LO 0x66 /* register for erase commands */ #define REG_ERASE_OPCODE 0x61 #define CMD_OPCODE_ERASE_SECTOR 0x20 #define CMD_OPCODE_ERASE_BLOCK 0xD8 /* register for read commands */ #define REG_READ_OPCODE 0x6A #define CMD_OPCODE_READ 0x03 /* register for write commands */ #define REG_WRITE_OPCODE 0x6D #define CMD_OPCODE_WRITE 0x02 /* mode register address */ #define REG_MCU_MODE 0x6F /* when bit is set in mode register, ISP mode is active */ #define MCU_MODE_ISP (1 << 7) /* write set to begin write, reset by device when complete */ #define MCU_MODE_WRITE_BUSY (1 << 5) /* when bit is clear, write buffer contains data */ #define MCU_MODE_WRITE_BUF (1 << 4) /* write data into write buffer */ #define REG_WRITE_FIFO 0x70 /* number of bytes to write minus 1 (0xff means 256 bytes) */ #define REG_WRITE_LEN 0x71 /* Indirect registers allow access to registers with 16-bit addresses. Write * 0x9F to the LO register, then the top byte of the address to HI, the * bottom byte of the address to LO, then read or write HI to read or write * the value of the target register. */ #define REG_INDIRECT_LO 0xF4 #define REG_INDIRECT_HI 0xF5 /* GPIO configuration/access registers */ #define REG_GPIO88_CONFIG 0x104F #define REG_GPIO88_VALUE 0xFE3F /* flash chip properties */ #define FLASH_SIZE 0x100000 #define FLASH_SECTOR_SIZE 4096 #define FLASH_BLOCK_SIZE 65536 /* MST flash layout */ #define FLASH_USER1_ADDR 0x10000 #define FLASH_FLAG1_ADDR 0xfe304 #define FLASH_USER2_ADDR 0x80000 #define FLASH_FLAG2_ADDR 0xff304 #define FLASH_USER_SIZE 0x70000 enum dual_bank_mode { DUAL_BANK_USER_ONLY = 0, DUAL_BANK_DIFF = 1, DUAL_BANK_COPY = 2, DUAL_BANK_USER_ONLY_FLAG = 3, DUAL_BANK_MAX_VALUE = 3, }; enum flash_bank { FLASH_BANK_BOOT = 0, FLASH_BANK_USER1 = 1, FLASH_BANK_USER2 = 2, FLASH_BANK_MAX_VALUE = 2, FLASH_BANK_INVALID = 255, }; struct dual_bank_info { gboolean is_enabled; enum dual_bank_mode mode; enum flash_bank active_bank; guint8 user1_version[2]; guint8 user2_version[2]; }; struct _FuRealtekMstDevice { FuI2cDevice parent_instance; gchar *dp_aux_dev_name; enum flash_bank active_bank; }; G_DEFINE_TYPE (FuRealtekMstDevice, fu_realtek_mst_device, FU_TYPE_I2C_DEVICE) static gboolean fu_realtek_mst_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (device); if (g_strcmp0 (key, "RealtekMstDpAuxName") == 0) { self->dp_aux_dev_name = g_strdup (value); } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported quirk key: %s", key); return FALSE; } return TRUE; } static gboolean fu_realtek_mst_device_override_dev (FuRealtekMstDevice *self, GError **error) { gboolean found = FALSE; g_autoptr(GUdevClient) udev_client = g_udev_client_new (NULL); g_autoptr(GUdevEnumerator) udev_enumerator = g_udev_enumerator_new (udev_client); g_autoptr(GList) matches = NULL; g_udev_enumerator_add_match_subsystem (udev_enumerator, "drm_dp_aux_dev"); g_udev_enumerator_add_match_sysfs_attr (udev_enumerator, "name", self->dp_aux_dev_name); matches = g_udev_enumerator_execute (udev_enumerator); /* from a drm_dp_aux_dev with the given name, locate its sibling i2c * device and in turn the i2c-dev under that representing the actual * I2C bus that runs over DPDDC on the port represented by the * drm_dp_aux_dev */ for (GList *element = matches; element != NULL; element = element->next) { g_autoptr(FuUdevDevice) device = fu_udev_device_new (element->data); g_autoptr(GPtrArray) i2c_devices = NULL; if (found) { g_debug ("Ignoring additional aux device %s", fu_udev_device_get_sysfs_path (device)); continue; } i2c_devices = fu_udev_device_get_siblings_with_subsystem (device, "i2c"); for (guint i = 0; i < i2c_devices->len; i++) { FuUdevDevice *i2c_device = g_ptr_array_index (i2c_devices, i); FuUdevDevice *bus_device; g_autoptr(GPtrArray) i2c_buses = fu_udev_device_get_children_with_subsystem (i2c_device, "i2c-dev"); if (i2c_buses->len == 0) { g_debug ("no i2c-dev found under %s", fu_udev_device_get_sysfs_path (i2c_device)); continue; } if (i2c_buses->len > 1) { g_debug ("ignoring %u additional i2c-dev under %s", i2c_buses->len - 1, fu_udev_device_get_sysfs_path (i2c_device)); } bus_device = g_ptr_array_index (i2c_buses, 0); g_debug ("Found I2C bus at %s, using this device", fu_udev_device_get_sysfs_path (bus_device)); fu_udev_device_set_dev (FU_UDEV_DEVICE (self), fu_udev_device_get_dev (bus_device)); found = TRUE; } } if (!found) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "did not find an i2c-dev associated with DP aux \"%s\"", self->dp_aux_dev_name); return FALSE; } return TRUE; } static gboolean mst_ensure_device_address (FuRealtekMstDevice *self, guint8 address, GError **error) { return fu_udev_device_ioctl (FU_UDEV_DEVICE (self), I2C_SLAVE, (guint8 *) (guintptr) address, NULL, error); } /** Write a value to a device register */ static gboolean mst_write_register (FuRealtekMstDevice *self, guint8 address, guint8 value, GError **error) { const guint8 command[] = { address, value }; return fu_i2c_device_write_full (FU_I2C_DEVICE (self), command, sizeof(command), error); } static gboolean mst_write_register_multi (FuRealtekMstDevice *self, guint8 address, const guint8 *data, gsize count, GError **error) { g_autofree guint8 *command = g_malloc0 (count + 1); memcpy (command + 1, data, count); command[0] = address; return fu_i2c_device_write_full (FU_I2C_DEVICE (self), command, count + 1, error); } /** Read a register from the device */ static gboolean mst_read_register (FuRealtekMstDevice *self, guint8 address, guint8 *value, GError **error) { if (!fu_i2c_device_write (FU_I2C_DEVICE (self), address, error)) return FALSE; return fu_i2c_device_read (FU_I2C_DEVICE (self), value, error); } static gboolean mst_set_indirect_address (FuRealtekMstDevice *self, guint16 address, GError **error) { if (!mst_write_register (self, REG_INDIRECT_LO, 0x9F, error)) return FALSE; if (!mst_write_register (self, REG_INDIRECT_HI, address >> 8, error)) return FALSE; return mst_write_register (self, REG_INDIRECT_LO, address, error); } static gboolean mst_read_register_indirect (FuRealtekMstDevice *self, guint16 address, guint8 *value, GError **error) { if (!mst_set_indirect_address (self, address, error)) return FALSE; return mst_read_register (self, REG_INDIRECT_HI, value, error); } static gboolean mst_write_register_indirect (FuRealtekMstDevice *self, guint16 address, guint8 value, GError **error) { if (!mst_set_indirect_address (self, address, error)) return FALSE; return mst_write_register (self, REG_INDIRECT_HI, value, error); } /** * Wait until a device register reads an expected value. * * Waiting up to @timeout_seconds, poll the given @address for the read value * bitwise-ANDed with @mask to be equal to @expected. * * Returns an error if the timeout expires or in case of an I/O error. */ static gboolean mst_poll_register (FuRealtekMstDevice *self, guint8 address, guint8 mask, guint8 expected, guint timeout_seconds, GError **error) { guint8 value; g_autoptr(GTimer) timer = g_timer_new (); if (!mst_read_register (self, address, &value, error)) return FALSE; while ((value & mask) != expected && g_timer_elapsed (timer, NULL) <= timeout_seconds) { g_usleep(G_TIME_SPAN_MILLISECOND); if (!mst_read_register (self, address, &value, error)) return FALSE; } if ((value & mask) == expected) return TRUE; g_set_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "register %x still reads %x after %us, wanted %x (mask %x)", address, value, timeout_seconds, expected, mask); return FALSE; } static gboolean mst_set_gpio88 (FuRealtekMstDevice *self, gboolean level, GError **error) { guint8 value; /* ensure pin is configured as push-pull GPIO */ if (!mst_read_register_indirect (self, REG_GPIO88_CONFIG, &value, error)) return FALSE; if (!mst_write_register_indirect (self, REG_GPIO88_CONFIG, (value & 0xF0) | 1, error)) return FALSE; /* set output level */ g_debug ("set pin 88 = %d", level); if (!mst_read_register_indirect (self, REG_GPIO88_VALUE, &value, error)) return FALSE; return mst_write_register_indirect (self, REG_GPIO88_VALUE, (value & 0xFE) | (level != FALSE), error); } static gboolean fu_realtek_mst_device_probe (FuDevice *device, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (device); FuContext *context = fu_device_get_context (device); const gchar *hardware_family = NULL; const gchar *quirk_name = NULL; g_autofree gchar *family_instance_id = NULL; g_autofree gchar *instance_id = NULL; /* set custom instance ID and load matching quirks */ instance_id = g_strdup_printf ("REALTEK-MST\\NAME_%s", fu_udev_device_get_sysfs_attr ( FU_UDEV_DEVICE (device), "name", NULL)); fu_device_add_instance_id (device, instance_id); hardware_family = fu_context_get_hwid_value (context, FU_HWIDS_KEY_FAMILY); family_instance_id = g_strdup_printf ("%s&FAMILY_%s", instance_id, hardware_family); fu_device_add_instance_id_full (device, family_instance_id, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); /* having loaded quirks, check this device is supported */ quirk_name = fu_device_get_name (device); if (g_strcmp0 (quirk_name, "RTD2142") != 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only RTD2142 is supported"); return FALSE; } if (self->dp_aux_dev_name == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "RealtekMstDpAuxName must be specified"); return FALSE; } /* locate its sibling i2c device and use that instead */ if (!fu_realtek_mst_device_override_dev (self, error)) return FALSE; /* FuI2cDevice */ if (!FU_DEVICE_CLASS (fu_realtek_mst_device_parent_class)->probe (device, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_realtek_mst_device_get_dual_bank_info (FuRealtekMstDevice *self, struct dual_bank_info *info, GError **error) { guint8 response[11] = { 0x0 }; if (!mst_ensure_device_address (self, I2C_ADDR_DEBUG, error)) return FALSE; /* switch to DDCCI mode */ if (!mst_write_register (self, 0xca, 0x09, error)) return FALSE; /* wait for mode switch to complete */ g_usleep (200 * G_TIME_SPAN_MILLISECOND); /* request dual bank state and read back */ if (!fu_i2c_device_write (FU_I2C_DEVICE (self), 0x01, error)) return FALSE; if (!fu_i2c_device_read_full (FU_I2C_DEVICE (self), response, sizeof(response), error)) return FALSE; if (response[0] != 0xca || response[1] != 9) { /* unexpected response code or length usually means the current * firmware doesn't support dual-bank mode at all */ g_debug ("unexpected response code %#x, length %d", response[0], response[1]); info->is_enabled = FALSE; return TRUE; } /* enable flag, assume anything other than 1 is unsupported */ if (response[2] != 1) { info->is_enabled = FALSE; return TRUE; } info->is_enabled = TRUE; info->mode = response[3]; if (info->mode > DUAL_BANK_MAX_VALUE) { g_debug ("unexpected dual bank mode value %#x", info->mode); info->is_enabled = FALSE; return TRUE; } info->active_bank = response[4]; if (info->active_bank > FLASH_BANK_MAX_VALUE) { g_debug ("unexpected active flash bank value %#x", info->active_bank); info->is_enabled = FALSE; return TRUE; } info->user1_version[0] = response[5]; info->user1_version[1] = response[6]; info->user2_version[0] = response[7]; info->user2_version[1] = response[8]; /* last two bytes of response are reserved */ return TRUE; } static gboolean fu_realtek_mst_device_probe_version (FuDevice *device, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (device); struct dual_bank_info info = { 0x0 }; guint8 *active_version; g_autofree gchar *version_str = NULL; /* ensure probed state is cleared in case of error */ fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); self->active_bank = FLASH_BANK_INVALID; fu_device_set_version (device, NULL); if (!fu_realtek_mst_device_get_dual_bank_info (FU_REALTEK_MST_DEVICE (self), &info, error)) return FALSE; if (!info.is_enabled) { g_debug ("dual-bank mode is not enabled"); return TRUE; } if (info.mode != DUAL_BANK_DIFF) { g_debug ("can only update from dual-bank-diff mode"); return TRUE; } /* dual-bank mode seems to be fully supported, so we can update * regardless of the active bank- if it's FLASH_BANK_BOOT, updating is * possible even if the current version is unknown */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE); g_debug ("device is currently running from bank %u", info.active_bank); g_return_val_if_fail (info.active_bank <= FLASH_BANK_MAX_VALUE, FALSE); self->active_bank = info.active_bank; g_debug ("firmware version reports user1 %d.%d, user2 %d.%d", info.user1_version[0], info.user1_version[1], info.user2_version[0], info.user2_version[1]); if (info.active_bank == FLASH_BANK_USER1) active_version = info.user1_version; else if (info.active_bank == FLASH_BANK_USER2) active_version = info.user2_version; else /* only user bank versions are reported, can't tell otherwise */ return TRUE; version_str = g_strdup_printf ("%u.%u", active_version[0], active_version[1]); fu_device_set_version (FU_DEVICE (self), version_str); return TRUE; } static gboolean flash_iface_read (FuRealtekMstDevice *self, guint32 address, guint8 *buf, const gsize buf_size, GError **error) { gsize bytes_read = 0; guint8 byte; g_return_val_if_fail (address < FLASH_SIZE, FALSE); g_return_val_if_fail (buf_size <= FLASH_SIZE, FALSE); g_debug ("read %#" G_GSIZE_MODIFIER "x bytes from %#08x", buf_size, address); /* read must start one byte prior to the desired address and ignore the * first byte of data, since the first read value is unpredictable */ address = (address - 1) & 0xFFFFFF; if (!mst_write_register (self, REG_CMD_ADDR_HI, address >> 16, error)) return FALSE; if (!mst_write_register (self, REG_CMD_ADDR_MID, address >> 8, error)) return FALSE; if (!mst_write_register (self, REG_CMD_ADDR_LO, address, error)) return FALSE; if (!mst_write_register (self, REG_READ_OPCODE, CMD_OPCODE_READ, error)) return FALSE; /* ignore first byte of data */ if (!fu_i2c_device_write (FU_I2C_DEVICE (self), 0x70, error)) return FALSE; if (!fu_i2c_device_read (FU_I2C_DEVICE (self), &byte, error)) return FALSE; while (bytes_read < buf_size) { /* read up to 256 bytes in one transaction */ gsize read_len = buf_size - bytes_read; if (read_len > 256) read_len = 256; if (!fu_i2c_device_read_full (FU_I2C_DEVICE (self), buf + bytes_read, read_len, error)) return FALSE; bytes_read += read_len; fu_device_set_progress_full (FU_DEVICE (self), bytes_read, buf_size); } return TRUE; } static gboolean flash_iface_erase_sector (FuRealtekMstDevice *self, guint32 address, GError **error) { /* address must be 4k-aligned */ g_return_val_if_fail ((address & 0xFFF) == 0, FALSE); g_debug ("sector erase %#08x-%#08x", address, address + FLASH_SECTOR_SIZE); /* sector address */ if (!mst_write_register (self, REG_CMD_ADDR_HI, address >> 16, error)) return FALSE; if (!mst_write_register (self, REG_CMD_ADDR_MID, address >> 8, error)) return FALSE; if (!mst_write_register (self, REG_CMD_ADDR_LO, address, error)) return FALSE; /* command type + WREN */ if (!mst_write_register (self, REG_CMD_ATTR, 0xB8, error)) return FALSE; /* sector erase opcode */ if (!mst_write_register (self, REG_ERASE_OPCODE, CMD_OPCODE_ERASE_SECTOR, error)) return FALSE; /* begin operation and wait for completion */ if (!mst_write_register (self, REG_CMD_ATTR, 0xB8 | CMD_ERASE_BUSY, error)) return FALSE; return mst_poll_register (self, REG_CMD_ATTR, CMD_ERASE_BUSY, 0, 10, error); } static gboolean flash_iface_erase_block (FuRealtekMstDevice *self, guint32 address, GError **error) { /* address must be 64k-aligned */ g_return_val_if_fail ((address & 0xFFFF) == 0, FALSE); g_debug ("block erase %#08x-%#08x", address, address + FLASH_BLOCK_SIZE); /* block address */ if (!mst_write_register (self, REG_CMD_ADDR_HI, address >> 16, error)) return FALSE; if (!mst_write_register (self, REG_CMD_ADDR_MID, 0, error)) return FALSE; if (!mst_write_register (self, REG_CMD_ADDR_LO, 0, error)) return FALSE; /* command type + WREN */ if (!mst_write_register (self, REG_CMD_ATTR, 0xB8, error)) return FALSE; /* block erase opcode */ if (!mst_write_register (self, REG_ERASE_OPCODE, CMD_OPCODE_ERASE_BLOCK, error)) return FALSE; /* begin operation and wait for completion */ if (!mst_write_register (self, REG_CMD_ATTR, 0xB8 | CMD_ERASE_BUSY, error)) return FALSE; return mst_poll_register (self, REG_CMD_ATTR, CMD_ERASE_BUSY, 0, 10, error); } static gboolean flash_iface_write (FuRealtekMstDevice *self, guint32 address, GBytes *data, GError **error) { gsize bytes_written = 0; gsize total_size = g_bytes_get_size (data); g_autoptr(GPtrArray) chunks = fu_chunk_array_new_from_bytes (data, address, 0, 256); g_debug ("write %#" G_GSIZE_MODIFIER "x bytes at %#08x", total_size, address); for (guint i = 0; i < chunks->len; i++) { FuChunk *chunk = g_ptr_array_index (chunks, i); guint32 chunk_address = fu_chunk_get_address (chunk); guint32 chunk_size = fu_chunk_get_data_sz (chunk); /* write opcode */ if (!mst_write_register (self, REG_WRITE_OPCODE, CMD_OPCODE_WRITE, error)) return FALSE; /* write length */ if (!mst_write_register (self, REG_WRITE_LEN, chunk_size - 1, error)) return FALSE; /* target address */ if (!mst_write_register (self, REG_CMD_ADDR_HI, chunk_address >> 16, error)) return FALSE; if (!mst_write_register (self, REG_CMD_ADDR_MID, chunk_address >> 8, error)) return FALSE; if (!mst_write_register (self, REG_CMD_ADDR_LO, chunk_address, error)) return FALSE; /* ensure write buffer is empty */ if (!mst_poll_register (self, REG_MCU_MODE, MCU_MODE_WRITE_BUF, MCU_MODE_WRITE_BUF, 10, error)) { g_prefix_error (error, "failed waiting for write buffer to clear: "); return FALSE; } /* write data into FIFO */ if (!mst_write_register_multi (self, REG_WRITE_FIFO, fu_chunk_get_data (chunk), chunk_size, error)) return FALSE; /* begin operation and wait for completion */ if (!mst_write_register (self, REG_MCU_MODE, MCU_MODE_ISP | MCU_MODE_WRITE_BUSY, error)) return FALSE; if (!mst_poll_register (self, REG_MCU_MODE, MCU_MODE_WRITE_BUSY, 0, 10, error)) { g_prefix_error (error, "timed out waiting for write at %#x to complete: ", address); return FALSE; } bytes_written += chunk_size; fu_device_set_progress_full (FU_DEVICE (self), bytes_written, total_size); } return TRUE; } static gboolean fu_realtek_mst_device_detach (FuDevice *device, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (device); if (!mst_ensure_device_address (self, I2C_ADDR_ISP, error)) return FALSE; /* Switch to programming mode (stops regular operation) */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!mst_write_register (self, REG_MCU_MODE, MCU_MODE_ISP, error)) return FALSE; g_debug ("wait for ISP mode ready"); if (!mst_poll_register (self, REG_MCU_MODE, MCU_MODE_ISP, MCU_MODE_ISP, 60, error)) return FALSE; /* magic value makes the MCU clock run faster than normal; this both * helps programming performance and fixes flakiness where register * writes sometimes get nacked for no apparent reason */ if (!mst_write_register_indirect (self, 0x06A0, 0x74, error)) return FALSE; fu_device_add_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_set_status (device, FWUPD_STATUS_IDLE); /* Disable hardware write protect, assuming Flash ~WP is connected to * device pin 88, a GPIO. */ return mst_set_gpio88 (self, 1, error); } static gboolean fu_realtek_mst_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (device); /* write an inactive bank: USER2 if USER1 is active, otherwise USER1 * (including if the boot bank is active) */ guint32 base_addr = self->active_bank == FLASH_BANK_USER1 ? FLASH_USER2_ADDR : FLASH_USER1_ADDR; guint32 flag_addr = self->active_bank == FLASH_BANK_USER1 ? FLASH_FLAG2_ADDR : FLASH_FLAG1_ADDR; GBytes *firmware_bytes = fu_firmware_get_bytes (firmware, error); const guint8 flag_data[] = {0xaa, 0xaa, 0xaa, 0xff, 0xff}; g_autofree guint8 *readback_buf = g_malloc0 (FLASH_USER_SIZE); g_return_val_if_fail (g_bytes_get_size (firmware_bytes) == FLASH_USER_SIZE, FALSE); if (!mst_ensure_device_address (self, I2C_ADDR_ISP, error)) return FALSE; /* erase old image */ g_debug ("erase old image from %#x", base_addr); fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); for (guint32 offset = 0; offset < FLASH_USER_SIZE; offset += FLASH_BLOCK_SIZE) { fu_device_set_progress_full (device, offset, FLASH_USER_SIZE); if (!flash_iface_erase_block (self, base_addr + offset, error)) return FALSE; } /* write new image */ g_debug ("write new image to %#x", base_addr); fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); if (!flash_iface_write (self, base_addr, firmware_bytes, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY); if (!flash_iface_read (self, base_addr, readback_buf, FLASH_USER_SIZE, error)) return FALSE; if (memcmp (g_bytes_get_data (firmware_bytes, NULL), readback_buf, FLASH_USER_SIZE) != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "flash contents after write do not match firmware image"); return FALSE; } /* Erase old flag and write new one. The MST appears to modify the * flag value once booted, so we always write the same value here and * it picks up what we've updated. */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); if (!flash_iface_erase_sector (self, flag_addr & ~(FLASH_SECTOR_SIZE - 1), error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); return flash_iface_write (self, flag_addr, g_bytes_new_static (flag_data, sizeof(flag_data)), error); } static FuFirmware* fu_realtek_mst_device_read_firmware (FuDevice *device, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (device); guint32 bank_address; g_autofree guint8 *image_bytes = NULL; if (self->active_bank == FLASH_BANK_USER1) bank_address = FLASH_USER1_ADDR; else if (self->active_bank == FLASH_BANK_USER2) bank_address = FLASH_USER2_ADDR; else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot read firmware from bank %u", self->active_bank); return NULL; } image_bytes = g_malloc0 (FLASH_USER_SIZE); if (!mst_ensure_device_address (self, I2C_ADDR_ISP, error)) return NULL; if (!flash_iface_read (self, bank_address, image_bytes, FLASH_USER_SIZE, error)) return NULL; return fu_firmware_new_from_bytes(g_bytes_new_take (g_steal_pointer (&image_bytes), FLASH_USER_SIZE)); } static GBytes* fu_realtek_mst_device_dump_firmware (FuDevice *device, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (device); g_autofree guint8 *flash_contents = g_malloc0 (FLASH_SIZE); if (!mst_ensure_device_address (self, I2C_ADDR_ISP, error)) return NULL; fu_device_set_status (device, FWUPD_STATUS_DEVICE_READ); if (!flash_iface_read (self, 0, flash_contents, FLASH_SIZE, error)) return NULL; fu_device_set_status (device, FWUPD_STATUS_IDLE); return g_bytes_new_take (g_steal_pointer (&flash_contents), FLASH_SIZE); } static gboolean fu_realtek_mst_device_attach (FuDevice *device, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (device); guint8 value; if (!mst_ensure_device_address (self, I2C_ADDR_ISP, error)) return FALSE; /* re-enable hardware write protect via GPIO */ if (!mst_set_gpio88 (self, 0, error)) return FALSE; if (!mst_read_register (self, REG_MCU_MODE, &value, error)) return FALSE; if ((value & MCU_MODE_ISP) != 0) { g_autoptr(GError) error_local = NULL; g_debug ("resetting device to exit ISP mode"); fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); /* Set register EE bit 2 to request reset. This write can fail * spuriously, so we ignore the write result and verify the device is * no longer in programming mode after giving it time to reset. */ if (!mst_read_register (self, 0xEE, &value, error)) return FALSE; if (!mst_write_register (self, 0xEE, value | 2, &error_local)) { g_debug ("write spuriously failed, ignoring: %s", error_local->message); } /* allow device some time to reset */ g_usleep (G_USEC_PER_SEC); /* verify device has exited programming mode and actually reset */ if (!mst_read_register (self, REG_MCU_MODE, &value, error)) return FALSE; if ((value & MCU_MODE_ISP) == MCU_MODE_ISP) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, "device failed to reset when requested"); fu_device_add_flag (device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN); return FALSE; } } else { g_debug ("device is already in normal mode"); } fu_device_remove_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_set_status (device, FWUPD_STATUS_IDLE); return TRUE; } static void fu_realtek_mst_device_init (FuRealtekMstDevice *self) { self->active_bank = FLASH_BANK_INVALID; fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_PAIR); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_protocol (FU_DEVICE (self), "com.realtek.rtd2142"); fu_device_set_vendor (FU_DEVICE (self), "Realtek"); fu_device_add_vendor_id (FU_DEVICE (self), "PCI:0x10EC"); fu_device_set_summary (FU_DEVICE (self), "DisplayPort MST hub"); fu_device_add_icon (FU_DEVICE (self), "video-display"); fu_device_set_firmware_size (FU_DEVICE (self), FLASH_USER_SIZE); } static void fu_realtek_mst_device_finalize (GObject *object) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE (object); g_free (self->dp_aux_dev_name); G_OBJECT_CLASS (fu_realtek_mst_device_parent_class)->finalize (object); } static void fu_realtek_mst_device_class_init (FuRealtekMstDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); GObjectClass *klass_object = G_OBJECT_CLASS(klass); klass_object->finalize = fu_realtek_mst_device_finalize; klass_device->probe = fu_realtek_mst_device_probe; klass_device->set_quirk_kv = fu_realtek_mst_device_set_quirk_kv; klass_device->setup = fu_realtek_mst_device_probe_version; klass_device->detach = fu_realtek_mst_device_detach; klass_device->attach = fu_realtek_mst_device_attach; klass_device->write_firmware = fu_realtek_mst_device_write_firmware; klass_device->reload = fu_realtek_mst_device_probe_version; /* read active image */ klass_device->read_firmware = fu_realtek_mst_device_read_firmware; /* dump whole flash */ klass_device->dump_firmware = fu_realtek_mst_device_dump_firmware; }