mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-06 06:27:58 +00:00
New plugin: parade-lspcon
This commit is contained in:
parent
db8a9927f4
commit
a12d715038
@ -23,6 +23,7 @@ meson .. \
|
||||
-Dplugin_altos=false \
|
||||
-Dplugin_dell=false \
|
||||
-Dplugin_nvme=false \
|
||||
-Dplugin_parade_lspcon=false \
|
||||
-Dplugin_realtek_mst=false \
|
||||
-Dplugin_platform_integrity=false \
|
||||
-Dplugin_tpm=false \
|
||||
|
@ -46,6 +46,7 @@ MESON_ARGS= -Dgudev=false \
|
||||
-Dplugin_dell=false \
|
||||
-Dplugin_emmc=false \
|
||||
-Dplugin_nvme=false \
|
||||
-Dplugin_parade_lspcon=false \
|
||||
-Dplugin_redfish=false \
|
||||
-Dplugin_synaptics_mst=false \
|
||||
-Dplugin_synaptics_rmi=false \
|
||||
|
@ -440,6 +440,7 @@ done
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_nitrokey.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_nvme.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_optionrom.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_parade_lspcon.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_pci_bcr.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_pci_mei.so
|
||||
%{_libdir}/fwupd-plugins-3/libfu_plugin_pixart_rf.so
|
||||
|
@ -18,6 +18,7 @@ option('plugin_amt', type : 'boolean', value : true, description : 'enable Intel
|
||||
option('plugin_dell', type : 'boolean', value : true, description : 'enable Dell-specific support')
|
||||
option('plugin_dummy', type : 'boolean', value : false, description : 'enable the dummy device')
|
||||
option('plugin_emmc', type : 'boolean', value : true, description : 'enable eMMC support')
|
||||
option('plugin_parade_lspcon', type : 'boolean', value : true, description : 'enable Parade LSPCON support')
|
||||
option('plugin_realtek_mst', type : 'boolean', value : true, description : 'enable Realtek MST hub support')
|
||||
option('plugin_synaptics_mst', type: 'boolean', value: true, description : 'enable Synaptics MST hub support')
|
||||
option('plugin_synaptics_rmi', type: 'boolean', value: true, description : 'enable Synaptics RMI support')
|
||||
|
@ -40,6 +40,7 @@ subdir('msr')
|
||||
subdir('nitrokey')
|
||||
subdir('nvme')
|
||||
subdir('optionrom')
|
||||
subdir('parade-lspcon')
|
||||
subdir('pci-bcr')
|
||||
subdir('pci-mei')
|
||||
subdir('pixart-rf')
|
||||
|
58
plugins/parade-lspcon/README.md
Normal file
58
plugins/parade-lspcon/README.md
Normal file
@ -0,0 +1,58 @@
|
||||
Parade LSPCON
|
||||
=============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This plugin updates the firmware of HDMI level shifter and protocol converter
|
||||
(LSPCON) devices made by Parade Technologies, such as the PS175.
|
||||
|
||||
These devices communicate over I²C, via either the DisplayPort aux channel or a
|
||||
dedicated bus- this plugin uses a dedicated bus declared by system firmware for,
|
||||
flashing, and reads the device firmware version from DPCD. Quirks specify the
|
||||
DisplayPort bus over which DPCD is read for a given system.
|
||||
|
||||
Firmware is stored on an external flash attached to an SPI bus on the device.
|
||||
The attached flash is assumed to be compatible with the W25Q20 series of
|
||||
devices, in particular supporting a 64k Block Erase command (0xD8) with 24-bit
|
||||
address and Write Enable for Volatile Status Register (0x05).
|
||||
|
||||
Firmware Format
|
||||
---------------
|
||||
|
||||
The device firmware is in an unspecified binary format that is written directly
|
||||
to an inactive partition of the Flash attached to the device.
|
||||
|
||||
This plugin supports the following protocol ID:
|
||||
|
||||
* com.paradetech.ps176
|
||||
|
||||
GUID Generation
|
||||
---------------
|
||||
|
||||
The plugin uses a custom DeviceInstanceId value derived from the device name
|
||||
provided by system firmware and read from sysfs, such as:
|
||||
|
||||
* `PARADE-LSPCON\NAME_1AF80175:00`
|
||||
* `PARADE-LSPCON\NAME_1AF80175:00&FAMILY_Google_Hatch`
|
||||
|
||||
Quirk use
|
||||
---------
|
||||
|
||||
This plugin uses the following plugin-specific quirks:
|
||||
|
||||
| Quirk | Description | Minimum fwupd version |
|
||||
| `ParadeLspconAuxDeviceName` | sysfs name of the `drm_dp_aux_dev` over which device version should be read | 1.6.2 |
|
||||
|
||||
Vendor ID security
|
||||
------------------
|
||||
|
||||
The vendor ID is specified by system firmware (such as ACPI tables) and is
|
||||
part of the device's name as read from sysfs.
|
||||
|
||||
External interface access
|
||||
-------------------------
|
||||
|
||||
This plugin requires access to the DisplayPort aux channel to read DPCD, such
|
||||
as `/dev/drm_dp_aux0` as well as the i2c bus attached to the device, such as
|
||||
`/dev/i2c-7`.
|
838
plugins/parade-lspcon/fu-parade-lspcon-device.c
Normal file
838
plugins/parade-lspcon/fu-parade-lspcon-device.c
Normal file
@ -0,0 +1,838 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Peter Marheine <pmarheine@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <linux/i2c-dev.h>
|
||||
|
||||
#include "fu-parade-lspcon-device.h"
|
||||
|
||||
/* device registers are split into pages, where
|
||||
* each page has its own I2C address */
|
||||
#define I2C_ADDR_PAGE2 0x4A
|
||||
#define REG_ADDR_CLT2SPI 0x82
|
||||
/* FLASH_ADDR_* are the upper 16 bits of the 24-bit flash address that gets
|
||||
* mapped into page 7. Writing 0x01, 0x42 will map the 256 bytes from 0x420100
|
||||
* into page 7. */
|
||||
#define REG_ADDR_FLASH_ADDR_LO 0x8E
|
||||
#define REG_ADDR_FLASH_ADDR_HI 0x8F
|
||||
/* 16-deep SPI write and read buffer FIFOs */
|
||||
#define REG_ADDR_WR_FIFO 0x90
|
||||
#define REG_ADDR_RD_FIFO 0x91
|
||||
/* Low nibble is write operation length, high nibble for read commands.
|
||||
* Reset to 0 after command completion. */
|
||||
#define REG_ADDR_SPI_LEN 0x92
|
||||
|
||||
#define REG_ADDR_SPI_CTL 0x93
|
||||
/* set to do a write-only transaction */
|
||||
#define SPI_CTL_NOREAD 0x04
|
||||
/* set to begin executing command */
|
||||
#define SPI_CTL_TRIGGER 0x01
|
||||
|
||||
/* operation status fields: set to 1 when operation begins, 2 when command has been
|
||||
* sent, reset to 0 when command completed */
|
||||
#define REG_ADDR_SPI_STATUS 0x9e
|
||||
/* byte programming */
|
||||
#define SPI_STATUS_BP_MASK 0x03
|
||||
/* sector erase */
|
||||
#define SPI_STATUS_SE_MASK 0x0C
|
||||
/* chip erase */
|
||||
#define SPI_STATUS_CE_MASK 0x30
|
||||
|
||||
/* write WR_PROTECT_DISABLE to permit flash write operations */
|
||||
#define REG_ADDR_WR_PROTECT 0xB3
|
||||
#define WR_PROTECT_DISABLE 0x10
|
||||
|
||||
/* MPU control register */
|
||||
#define REG_ADDR_MPU 0xBC
|
||||
|
||||
/* write a magic sequence to this register to enable writes to
|
||||
* mapped memory via page 7, or anything else to disable */
|
||||
#define REG_ADDR_MAP_WRITE 0xDA
|
||||
|
||||
#define I2C_ADDR_PAGE5 0x4D
|
||||
#define REG_ADDR_ACTIVE_PARTITION 0x0E
|
||||
|
||||
#define I2C_ADDR_PAGE7 0x4F
|
||||
|
||||
#define FLASH_BLOCK_SIZE 0x10000
|
||||
|
||||
/*
|
||||
* user1: 0x10000 - 0x20000
|
||||
* user2: 0x20000 - 0x30000
|
||||
* flag: 0x00002 - 0x00004
|
||||
*/
|
||||
struct _FuParadeLspconDevice {
|
||||
FuI2cDevice parent_instance;
|
||||
guint8 active_partition;
|
||||
gchar *aux_device_name;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (FuParadeLspconDevice, fu_parade_lspcon_device, FU_TYPE_I2C_DEVICE)
|
||||
|
||||
static void
|
||||
fu_parade_lspcon_device_init (FuParadeLspconDevice *self)
|
||||
{
|
||||
FuDevice *device = FU_DEVICE (self);
|
||||
fu_device_set_vendor (device, "Parade Technologies");
|
||||
fu_device_add_vendor_id (device, "PCI:0x1AF8");
|
||||
fu_device_add_protocol (device, "com.paradetech.ps176");
|
||||
fu_device_add_icon (device, "video-display");
|
||||
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_INTERNAL);
|
||||
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_DUAL_IMAGE);
|
||||
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_CAN_VERIFY);
|
||||
fu_device_set_firmware_size (device, 0x10000);
|
||||
fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_PAIR);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_parade_lspcon_device_finalize (GObject *object)
|
||||
{
|
||||
FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE (object);
|
||||
g_free (self->aux_device_name);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_device_set_quirk_kv (FuDevice *device,
|
||||
const gchar *key,
|
||||
const gchar *value,
|
||||
GError **error)
|
||||
{
|
||||
FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE (device);
|
||||
|
||||
if (g_strcmp0 (key, "ParadeLspconAuxDeviceName") == 0) {
|
||||
self->aux_device_name = g_strdup (value);
|
||||
return TRUE;
|
||||
}
|
||||
return FU_DEVICE_CLASS (fu_parade_lspcon_device_parent_class)
|
||||
->set_quirk_kv (device, key, value, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_device_probe (FuDevice *device, GError **error)
|
||||
{
|
||||
FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE (device);
|
||||
FuContext *context = fu_device_get_context (device);
|
||||
FuUdevDevice *udev_device = FU_UDEV_DEVICE (device);
|
||||
const gchar *device_name;
|
||||
g_autofree gchar *instance_id_hwid = NULL;
|
||||
g_autofree gchar *instance_id = NULL;
|
||||
|
||||
/* custom instance IDs to get device quirks */
|
||||
instance_id = g_strdup_printf ("PARADE-LSPCON\\NAME_%s",
|
||||
fu_udev_device_get_sysfs_attr (udev_device, "name", NULL));
|
||||
fu_device_add_instance_id (device, instance_id);
|
||||
instance_id_hwid = g_strdup_printf ("%s&FAMILY_%s", instance_id,
|
||||
fu_context_get_hwid_value (context,
|
||||
FU_HWIDS_KEY_FAMILY));
|
||||
fu_device_add_instance_id_full (device, instance_id_hwid,
|
||||
FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS);
|
||||
|
||||
device_name = fu_device_get_name (device);
|
||||
if (g_strcmp0 (device_name, "PS175") != 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"device name %s is not supported by this plugin",
|
||||
device_name);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* should know which aux device over which we read DPCD version */
|
||||
if (self->aux_device_name == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"ParadeLspconAuxDeviceName must be specified");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* FuI2cDevice->probe */
|
||||
return FU_DEVICE_CLASS (fu_parade_lspcon_device_parent_class)->probe (device, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_ensure_i2c_address (FuParadeLspconDevice *self,
|
||||
guint8 address,
|
||||
GError **error)
|
||||
{
|
||||
if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
|
||||
I2C_SLAVE, (guint8 *) (guintptr) address, NULL,
|
||||
error)) {
|
||||
g_prefix_error (error, "failed to set I2C slave address: ");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_device_open (FuDevice *device, GError **error)
|
||||
{
|
||||
if (!FU_DEVICE_CLASS (fu_parade_lspcon_device_parent_class)->open (device, error))
|
||||
return FALSE;
|
||||
|
||||
/* general assumption is that page 2 is selected: code that uses another address
|
||||
* should use an address guard to ensure it gets reset */
|
||||
return fu_parade_lspcon_ensure_i2c_address (FU_PARADE_LSPCON_DEVICE (device),
|
||||
I2C_ADDR_PAGE2,
|
||||
error);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a scope in which the device's target I2C address is something
|
||||
* other than page 2, and resets it to page 2 when the scope is left.
|
||||
*/
|
||||
typedef struct {
|
||||
FuParadeLspconDevice *device;
|
||||
} FuParadeLspconI2cAddressGuard;
|
||||
|
||||
static FuParadeLspconI2cAddressGuard *
|
||||
fu_parade_lspcon_i2c_address_guard_new (FuParadeLspconDevice *self, guint8 new_address, GError **error)
|
||||
{
|
||||
FuParadeLspconI2cAddressGuard *out;
|
||||
|
||||
if (!fu_parade_lspcon_ensure_i2c_address (self, new_address, error))
|
||||
return NULL;
|
||||
out = g_new0 (FuParadeLspconI2cAddressGuard, 1);
|
||||
out->device = self;
|
||||
return out;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_parade_lspcon_i2c_address_guard_free (FuParadeLspconI2cAddressGuard *guard)
|
||||
{
|
||||
g_autoptr(GError) error_local = NULL;
|
||||
if (!fu_parade_lspcon_ensure_i2c_address (guard->device,
|
||||
I2C_ADDR_PAGE2,
|
||||
&error_local)) {
|
||||
g_warning ("failed to set page2 back: %s", error_local->message);
|
||||
}
|
||||
g_free (guard);
|
||||
}
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuParadeLspconI2cAddressGuard,
|
||||
fu_parade_lspcon_i2c_address_guard_free);
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_write_register (FuParadeLspconDevice *self,
|
||||
guint8 register_addr,
|
||||
guint8 value,
|
||||
GError **error)
|
||||
{
|
||||
guint8 transaction[] = { register_addr, value };
|
||||
return fu_i2c_device_write_full (FU_I2C_DEVICE (self), transaction,
|
||||
sizeof(transaction), error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_read_register (FuParadeLspconDevice *self,
|
||||
guint8 register_addr,
|
||||
guint8 *value,
|
||||
GError **error)
|
||||
{
|
||||
FuI2cDevice *i2c_device = FU_I2C_DEVICE (self);
|
||||
if (!fu_i2c_device_write (i2c_device, register_addr, error))
|
||||
return FALSE;
|
||||
return fu_i2c_device_read (i2c_device, value, error);
|
||||
}
|
||||
|
||||
/* map the page containing the given address into page 7 */
|
||||
static gboolean
|
||||
fu_parade_lspcon_map_page (FuParadeLspconDevice *self, guint32 address, GError **error)
|
||||
{
|
||||
if (!fu_parade_lspcon_write_register (self,
|
||||
REG_ADDR_FLASH_ADDR_HI,
|
||||
address >> 16,
|
||||
error))
|
||||
return FALSE;
|
||||
return fu_parade_lspcon_write_register (self,
|
||||
REG_ADDR_FLASH_ADDR_LO,
|
||||
address >> 8,
|
||||
error);
|
||||
}
|
||||
|
||||
/* wait until the specified register masked with mask reads the expected
|
||||
* value, up to 10 seconds */
|
||||
static gboolean
|
||||
fu_parade_lspcon_poll_register (FuParadeLspconDevice *self,
|
||||
guint8 register_address,
|
||||
guint8 mask,
|
||||
guint8 expected,
|
||||
GError **error)
|
||||
{
|
||||
guint8 value;
|
||||
g_autoptr(GTimer) timer = g_timer_new();
|
||||
|
||||
do {
|
||||
if (!fu_parade_lspcon_read_register (self,
|
||||
register_address,
|
||||
&value,
|
||||
error))
|
||||
return FALSE;
|
||||
if ((value & mask) == expected)
|
||||
return TRUE;
|
||||
} while (g_timer_elapsed (timer, NULL) <= 10.0);
|
||||
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_TIMED_OUT,
|
||||
"register %x did not read %x (mask %x) within 10 seconds: read %x",
|
||||
register_address, expected, mask, value);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_flash_read (FuParadeLspconDevice *self,
|
||||
guint32 base_address,
|
||||
guint8 *data,
|
||||
const gsize len,
|
||||
GError **error)
|
||||
{
|
||||
FuDevice *device = FU_DEVICE (self);
|
||||
FuI2cDevice *i2c_device = FU_I2C_DEVICE (self);
|
||||
gsize offset = 0;
|
||||
|
||||
while (offset < len) {
|
||||
/* page 7 reads always start from the base of the mapped window- we'll
|
||||
* read the whole page then pull out the parts we care about, using the
|
||||
* full page everywhere except possibly in the first and last reads */
|
||||
guint8 page_data[256] = { 0x0 };
|
||||
guint8 page_data_start = base_address & 0xFF;
|
||||
gsize page_data_take = MIN ((gssize) len, 256 - page_data_start);
|
||||
g_autoptr(FuParadeLspconI2cAddressGuard) guard = NULL;
|
||||
|
||||
if (!fu_parade_lspcon_map_page (self, base_address, error))
|
||||
return FALSE;
|
||||
guard = fu_parade_lspcon_i2c_address_guard_new (self,
|
||||
I2C_ADDR_PAGE7,
|
||||
error);
|
||||
if (guard == NULL)
|
||||
return FALSE;
|
||||
if (!fu_i2c_device_read_full (i2c_device, page_data, 256, error))
|
||||
return FALSE;
|
||||
|
||||
if (!fu_memcpy_safe (data, len, offset,
|
||||
page_data, sizeof (page_data), page_data_start,
|
||||
page_data_take, error))
|
||||
return FALSE;
|
||||
base_address += page_data_take;
|
||||
offset += page_data_take;
|
||||
|
||||
fu_device_set_progress_full (device, offset, len);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_flash_transmit_command (FuParadeLspconDevice *self,
|
||||
const guint8 *command,
|
||||
gsize command_len,
|
||||
GError **error)
|
||||
{
|
||||
/* write length field is 4 bits wide */
|
||||
g_return_val_if_fail (command_len > 0 && command_len <= 16, FALSE);
|
||||
|
||||
/* fill transmit buffer */
|
||||
for (gsize i = 0; i < command_len; i++) {
|
||||
if (!fu_parade_lspcon_write_register (self,
|
||||
REG_ADDR_WR_FIFO,
|
||||
command[i],
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set command length */
|
||||
if (!fu_parade_lspcon_write_register (self,
|
||||
REG_ADDR_SPI_LEN,
|
||||
command_len - 1,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* execute operation */
|
||||
return fu_parade_lspcon_write_register (self,
|
||||
REG_ADDR_SPI_CTL,
|
||||
SPI_CTL_NOREAD | SPI_CTL_TRIGGER,
|
||||
error);
|
||||
}
|
||||
|
||||
/*
|
||||
* set the flash Write Enable Latch, permitting the next program, erase or
|
||||
* status register write operation.
|
||||
*/
|
||||
static gboolean
|
||||
fu_parade_lspcon_flash_enable_write (FuParadeLspconDevice *self, GError **error)
|
||||
{
|
||||
const guint8 write_enable[] = { 0x06 };
|
||||
return fu_parade_lspcon_flash_transmit_command (self,
|
||||
write_enable,
|
||||
sizeof(write_enable),
|
||||
error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_flash_read_status (FuParadeLspconDevice *self,
|
||||
guint8 *value,
|
||||
GError **error)
|
||||
{
|
||||
if (!fu_parade_lspcon_write_register (self, REG_ADDR_WR_FIFO, 0x05, error))
|
||||
return FALSE;
|
||||
if (!fu_parade_lspcon_write_register (self, REG_ADDR_SPI_LEN, 0, error))
|
||||
return FALSE;
|
||||
if (!fu_parade_lspcon_write_register (self, REG_ADDR_SPI_CTL, SPI_CTL_TRIGGER, error))
|
||||
return FALSE;
|
||||
|
||||
/* wait for command completion */
|
||||
if (!fu_parade_lspcon_poll_register (self, REG_ADDR_SPI_CTL, SPI_CTL_TRIGGER, 0, error))
|
||||
return FALSE;
|
||||
|
||||
/* read SR value */
|
||||
return fu_parade_lspcon_read_register (self, REG_ADDR_RD_FIFO, value, error);
|
||||
}
|
||||
|
||||
/* poll the flash status register for operation completion */
|
||||
static gboolean
|
||||
fu_parade_lspcon_flash_wait_ready (FuParadeLspconDevice *self, GError **error)
|
||||
{
|
||||
g_autoptr(GTimer) timer = g_timer_new ();
|
||||
|
||||
do {
|
||||
guint8 status_register;
|
||||
if (!fu_parade_lspcon_flash_read_status (self, &status_register, error))
|
||||
return FALSE;
|
||||
|
||||
/* BUSY bit clears on completion */
|
||||
if ((status_register & 1) == 0)
|
||||
return TRUE;
|
||||
|
||||
/* flash operations generally take between 1ms and 4s; polling
|
||||
* at 1000 Hz is still quite responsive and not overly slow */
|
||||
g_usleep (G_TIME_SPAN_MILLISECOND);
|
||||
} while (g_timer_elapsed (timer, NULL) <= 10.0);
|
||||
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_TIMED_OUT,
|
||||
"flash did not become ready within 10 seconds");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_flash_write (FuParadeLspconDevice *self,
|
||||
guint32 base_address,
|
||||
GBytes *data,
|
||||
GError **error)
|
||||
{
|
||||
FuDevice *device = FU_DEVICE (self);
|
||||
FuI2cDevice *i2c_device = FU_I2C_DEVICE (self);
|
||||
const guint8 unlock_writes[] = { 0xaa, 0x55, 0x50, 0x41, 0x52, 0x44 };
|
||||
gsize data_len = g_bytes_get_size (data);
|
||||
g_autoptr(GPtrArray) chunks = NULL;
|
||||
|
||||
/* address must be 256-byte aligned */
|
||||
g_return_val_if_fail ((base_address & 0xFF) == 0, FALSE);
|
||||
g_debug ("flash write %" G_GSIZE_FORMAT " bytes at %#x",
|
||||
g_bytes_get_size (data), base_address);
|
||||
|
||||
/* unlock map writes by writing the magic sequence */
|
||||
for (gsize i = 0; i < sizeof(unlock_writes); i++) {
|
||||
if (!fu_parade_lspcon_write_register (self,
|
||||
REG_ADDR_MAP_WRITE,
|
||||
unlock_writes[i],
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* reset clt2SPI, required before write */
|
||||
if (!fu_parade_lspcon_write_register (self, REG_ADDR_CLT2SPI, 0x20, error))
|
||||
return FALSE;
|
||||
g_usleep (100 * G_TIME_SPAN_MILLISECOND);
|
||||
if (!fu_parade_lspcon_write_register (self, REG_ADDR_CLT2SPI, 0, error))
|
||||
return FALSE;
|
||||
|
||||
chunks = fu_chunk_array_new_from_bytes (data, base_address, 0, 256);
|
||||
for (gsize i = 0; i < chunks->len; i++) {
|
||||
FuChunk *chunk = g_ptr_array_index (chunks, i);
|
||||
guint32 address = fu_chunk_get_address (chunk);
|
||||
guint32 chunk_size = fu_chunk_get_data_sz (chunk);
|
||||
guint8 write_data[257] = { 0x0 };
|
||||
g_autoptr(FuParadeLspconI2cAddressGuard) guard = NULL;
|
||||
|
||||
/* map target address range in page 7 */
|
||||
if (!fu_parade_lspcon_map_page (self, address, error))
|
||||
return FALSE;
|
||||
|
||||
/* write data to page 7 memory window */
|
||||
guard = fu_parade_lspcon_i2c_address_guard_new (self,
|
||||
I2C_ADDR_PAGE7,
|
||||
error);
|
||||
/* page write is prefixed with an offset:
|
||||
* we always start from offset 0 */
|
||||
write_data[0] = 0;
|
||||
if (!fu_memcpy_safe (write_data, sizeof (write_data), 1,
|
||||
fu_chunk_get_data (chunk), chunk_size, 0,
|
||||
chunk_size, error))
|
||||
return FALSE;
|
||||
|
||||
if (!fu_i2c_device_write_full (i2c_device,
|
||||
write_data,
|
||||
chunk_size + 1,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
fu_device_set_progress_full (device, address - base_address, data_len);
|
||||
}
|
||||
|
||||
/* re-lock map writes */
|
||||
return fu_parade_lspcon_write_register (self, REG_ADDR_MAP_WRITE, 0, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_flash_erase_block (FuParadeLspconDevice *self,
|
||||
guint32 base_address,
|
||||
guint32 size,
|
||||
GError **error)
|
||||
{
|
||||
const guint8 block_erase[] = { 0xd8,
|
||||
base_address >> 16,
|
||||
base_address >> 8,
|
||||
base_address };
|
||||
|
||||
/* address must be block-aligned */
|
||||
g_return_val_if_fail ((base_address & (FLASH_BLOCK_SIZE - 1)) == 0, FALSE);
|
||||
g_return_val_if_fail (size == FLASH_BLOCK_SIZE, FALSE);
|
||||
|
||||
g_debug ("flash erase block at %#x", base_address);
|
||||
|
||||
if (!fu_parade_lspcon_flash_enable_write (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (!fu_parade_lspcon_flash_transmit_command (self,
|
||||
block_erase,
|
||||
sizeof(block_erase),
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* wait for command completion */
|
||||
if (!fu_parade_lspcon_poll_register (self,
|
||||
REG_ADDR_SPI_STATUS,
|
||||
SPI_STATUS_SE_MASK,
|
||||
0,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* wait for flash to complete erase */
|
||||
return fu_parade_lspcon_flash_wait_ready (self, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_probe_active_flash_partition (FuParadeLspconDevice *self,
|
||||
guint8 *partition,
|
||||
GError **error)
|
||||
{
|
||||
guint8 data = 0x0;
|
||||
g_autoptr(FuParadeLspconI2cAddressGuard) guard = NULL;
|
||||
|
||||
/* read currently-running flash partition number */
|
||||
guard = fu_parade_lspcon_i2c_address_guard_new (self, I2C_ADDR_PAGE5, error);
|
||||
if (guard == NULL)
|
||||
return FALSE;
|
||||
if (!fu_parade_lspcon_read_register (self,
|
||||
REG_ADDR_ACTIVE_PARTITION,
|
||||
&data,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
*partition = data;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_device_reload (FuDevice *device, GError **error)
|
||||
{
|
||||
FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE (device);
|
||||
guint32 oui;
|
||||
guint8 version_buf[2] = { 0x0 };
|
||||
g_autofree gchar *version = NULL;
|
||||
g_autofree gchar *oui_string = NULL;
|
||||
g_autoptr(FuDeviceLocker) aux_device_locker = NULL;
|
||||
g_autoptr(FuUdevDevice) aux_device = NULL;
|
||||
g_autoptr(GList) aux_devices = NULL;
|
||||
g_autoptr(GUdevClient) udev_client = g_udev_client_new (NULL);
|
||||
g_autoptr(GUdevEnumerator) enumerator = g_udev_enumerator_new (udev_client);
|
||||
|
||||
/* determine active partition for flashing later */
|
||||
if (!fu_parade_lspcon_probe_active_flash_partition (self, &self->active_partition, error))
|
||||
return FALSE;
|
||||
g_debug ("device reports running from partition %d", self->active_partition);
|
||||
if (self->active_partition < 1 || self->active_partition > 3) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"unexpected active flash partition: %d",
|
||||
self->active_partition);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* find the drm_dp_aux_dev specified by quirks that is connected to the
|
||||
* LSPCON, in order to read DPCD from it */
|
||||
if (self->aux_device_name == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"no DP aux device specified, unable to query LSPCON");
|
||||
return FALSE;
|
||||
}
|
||||
g_udev_enumerator_add_match_subsystem (enumerator, "drm_dp_aux_dev");
|
||||
g_udev_enumerator_add_match_sysfs_attr (enumerator, "name", self->aux_device_name);
|
||||
aux_devices = g_udev_enumerator_execute (enumerator);
|
||||
if (aux_devices == NULL) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"failed to locate a DP aux device named \"%s\"",
|
||||
self->aux_device_name);
|
||||
return FALSE;
|
||||
}
|
||||
if (g_list_length (aux_devices) > 1) {
|
||||
g_list_free_full (aux_devices, g_object_unref);
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"found multiple DP aux devices with name \"%s\"",
|
||||
self->aux_device_name);
|
||||
return FALSE;
|
||||
}
|
||||
aux_device = fu_udev_device_new (g_steal_pointer (&aux_devices->data));
|
||||
g_debug ("using aux dev %s", fu_udev_device_get_sysfs_path (aux_device));
|
||||
|
||||
/* the following open() requires the device have IDs set */
|
||||
if (!fu_udev_device_set_physical_id (aux_device, "drm_dp_aux_dev", error))
|
||||
return FALSE;
|
||||
fu_device_set_context (FU_DEVICE (aux_device), fu_device_get_context (device));
|
||||
|
||||
/* open device to read version from DPCD */
|
||||
if ((aux_device_locker = fu_device_locker_new (aux_device, error)) == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* DPCD address 00500-00502: device OUI */
|
||||
if (!fu_udev_device_pread_full (aux_device, 0x500, (guint8 *) &oui, 3, error))
|
||||
return FALSE;
|
||||
oui = GUINT32_FROM_BE(oui) >> 8;
|
||||
oui_string = g_strdup_printf ("OUI:%06X", oui);
|
||||
fu_device_add_vendor_id (device, oui_string);
|
||||
|
||||
if (oui != 0x001CF8) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"device OUI %06X does not match expected value for Paradetech",
|
||||
oui);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* DPCD address 0x50A, 0x50B: branch device firmware
|
||||
* major and minor revision */
|
||||
if (!fu_udev_device_pread_full (aux_device, 0x50a,
|
||||
version_buf, sizeof(version_buf),
|
||||
error))
|
||||
return FALSE;
|
||||
version = g_strdup_printf ("%d.%d", version_buf[0], version_buf[1]);
|
||||
fu_device_set_version (device, version);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_device_write_firmware (FuDevice *device,
|
||||
FuFirmware *firmware,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE (device);
|
||||
const guint8 write_sr_volatile[] = { 0x50 };
|
||||
const guint8 write_sr_disable_bp[] = {
|
||||
0x01, /* write SR */
|
||||
0x80, /* write protect follows /WP signal, no block protection */
|
||||
0x00
|
||||
};
|
||||
const guint8 write_sr_enable_bp[] = { 0x01, 0x8c, 0x00 };
|
||||
/* if the boot partition is active we could flash either, but prefer
|
||||
* the first */
|
||||
const guint8 target_partition = self->active_partition == 1 ? 2 : 1;
|
||||
const guint32 target_address = target_partition << 16;
|
||||
const guint8 flag_data[] = { 0x55, 0xaa, target_partition, 1 - target_partition };
|
||||
const guint8 *buf;
|
||||
gsize bufsz;
|
||||
g_autofree guint8 *readback_buf = NULL;
|
||||
g_autoptr(GBytes) blob_fw = NULL;
|
||||
g_autoptr(GBytes) flag_data_bytes = NULL;
|
||||
|
||||
blob_fw = fu_firmware_get_bytes (firmware, error);
|
||||
if (blob_fw == NULL)
|
||||
return FALSE;
|
||||
|
||||
buf = g_bytes_get_data (blob_fw, &bufsz);
|
||||
if (bufsz != FLASH_BLOCK_SIZE) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"invalid image size %#" G_GSIZE_MODIFIER "x, expected %#x",
|
||||
bufsz, (unsigned) FLASH_BLOCK_SIZE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* deassert flash /WP */
|
||||
if (!fu_parade_lspcon_write_register (self,
|
||||
REG_ADDR_WR_PROTECT,
|
||||
WR_PROTECT_DISABLE,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* disable flash protection until next power-off */
|
||||
if (!fu_parade_lspcon_flash_transmit_command (self,
|
||||
write_sr_volatile,
|
||||
sizeof(write_sr_volatile),
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_parade_lspcon_flash_transmit_command (self,
|
||||
write_sr_disable_bp,
|
||||
sizeof(write_sr_disable_bp),
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* wait for SR write to complete */
|
||||
if (!fu_parade_lspcon_flash_wait_ready (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* erase entire target partition (one flash block) */
|
||||
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
|
||||
if (!fu_parade_lspcon_flash_erase_block (self, target_address, bufsz, error)) {
|
||||
g_prefix_error (error, "failed to erase flash partition %d: ",
|
||||
target_partition);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* write image */
|
||||
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
||||
if (!fu_parade_lspcon_flash_write (self, target_address, blob_fw, error)) {
|
||||
g_prefix_error (error, "failed to write firmware to partition %d: ",
|
||||
target_partition);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* read back written image to verify */
|
||||
fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY);
|
||||
readback_buf = g_malloc0 (bufsz);
|
||||
if (!fu_parade_lspcon_flash_read (self, target_address,
|
||||
readback_buf, bufsz,
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_common_bytes_compare_raw (buf, bufsz,
|
||||
readback_buf, bufsz,
|
||||
error)) {
|
||||
g_prefix_error (error, "flash contents do not match: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* erase flag partition */
|
||||
fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE);
|
||||
if (!fu_parade_lspcon_flash_erase_block (self, 0, FLASH_BLOCK_SIZE, error))
|
||||
return FALSE;
|
||||
|
||||
/* write flag indicating device should boot the target partition */
|
||||
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
||||
flag_data_bytes = g_bytes_new_static (flag_data, sizeof(flag_data));
|
||||
if (!fu_parade_lspcon_flash_write (self, 0, flag_data_bytes, error))
|
||||
return FALSE;
|
||||
|
||||
/* verify flag partition */
|
||||
fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY);
|
||||
if (!fu_parade_lspcon_flash_read (self, 0,
|
||||
readback_buf, sizeof(flag_data),
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_common_bytes_compare_raw (flag_data, sizeof(flag_data),
|
||||
readback_buf, MIN (sizeof(flag_data), bufsz),
|
||||
error)) {
|
||||
g_prefix_error (error, "flag partition contents do not match: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* re-enable flash protection */
|
||||
if (!fu_parade_lspcon_flash_transmit_command (self,
|
||||
write_sr_volatile,
|
||||
sizeof(write_sr_volatile),
|
||||
error))
|
||||
return FALSE;
|
||||
if (!fu_parade_lspcon_flash_transmit_command (self,
|
||||
write_sr_enable_bp,
|
||||
sizeof(write_sr_enable_bp),
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* reassert /WP to flash */
|
||||
return fu_parade_lspcon_write_register (self, REG_ADDR_WR_PROTECT, 0, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_set_mpu_running (FuParadeLspconDevice *self,
|
||||
gboolean running,
|
||||
GError **error)
|
||||
{
|
||||
/* reset */
|
||||
if (!fu_parade_lspcon_write_register (self, REG_ADDR_MPU, 0xc0, error))
|
||||
return FALSE;
|
||||
|
||||
/* release reset, set MPU active or not */
|
||||
return fu_parade_lspcon_write_register (self, REG_ADDR_MPU,
|
||||
running ? 0 : 0x40, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_device_detach (FuDevice *device, GError **error)
|
||||
{
|
||||
FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE (device);
|
||||
return fu_parade_lspcon_set_mpu_running (self, FALSE, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_parade_lspcon_device_attach (FuDevice *device, GError **error)
|
||||
{
|
||||
FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE (device);
|
||||
return fu_parade_lspcon_set_mpu_running (self, TRUE, error);
|
||||
}
|
||||
|
||||
static GBytes *
|
||||
fu_parade_lspcon_device_dump_firmware (FuDevice *device, GError **error)
|
||||
{
|
||||
FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE (device);
|
||||
g_autofree guint8 *data = g_malloc0 (FLASH_BLOCK_SIZE);
|
||||
|
||||
if (!fu_parade_lspcon_flash_read (self,
|
||||
self->active_partition * FLASH_BLOCK_SIZE,
|
||||
data, FLASH_BLOCK_SIZE, error))
|
||||
return NULL;
|
||||
return g_bytes_new_take (g_steal_pointer (&data), FLASH_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_parade_lspcon_device_class_init (FuParadeLspconDeviceClass *klass)
|
||||
{
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
||||
GObjectClass *klass_object = G_OBJECT_CLASS (klass);
|
||||
|
||||
klass_object->finalize = fu_parade_lspcon_device_finalize;
|
||||
klass_device->set_quirk_kv = fu_parade_lspcon_device_set_quirk_kv;
|
||||
klass_device->probe = fu_parade_lspcon_device_probe;
|
||||
klass_device->setup = fu_parade_lspcon_device_reload;
|
||||
klass_device->open = fu_parade_lspcon_device_open;
|
||||
klass_device->reload = fu_parade_lspcon_device_reload;
|
||||
klass_device->detach = fu_parade_lspcon_device_detach;
|
||||
klass_device->write_firmware = fu_parade_lspcon_device_write_firmware;
|
||||
klass_device->attach = fu_parade_lspcon_device_attach;
|
||||
klass_device->dump_firmware = fu_parade_lspcon_device_dump_firmware;
|
||||
}
|
14
plugins/parade-lspcon/fu-parade-lspcon-device.h
Normal file
14
plugins/parade-lspcon/fu-parade-lspcon-device.h
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Peter Marheine <pmarheine@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#define FU_TYPE_PARADE_LSPCON_DEVICE (fu_parade_lspcon_device_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (FuParadeLspconDevice,
|
||||
fu_parade_lspcon_device, FU,
|
||||
PARADE_LSPCON_DEVICE, FuI2cDevice)
|
17
plugins/parade-lspcon/fu-plugin-parade-lspcon.c
Normal file
17
plugins/parade-lspcon/fu-plugin-parade-lspcon.c
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Peter Marheine <pmarheine@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "fu-parade-lspcon-device.h"
|
||||
|
||||
void
|
||||
fu_plugin_init (FuPlugin *plugin)
|
||||
{
|
||||
fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
|
||||
fu_plugin_add_udev_subsystem (plugin, "i2c");
|
||||
fu_plugin_add_device_gtype (plugin, FU_TYPE_PARADE_LSPCON_DEVICE);
|
||||
}
|
38
plugins/parade-lspcon/meson.build
Normal file
38
plugins/parade-lspcon/meson.build
Normal file
@ -0,0 +1,38 @@
|
||||
if get_option('plugin_parade_lspcon')
|
||||
|
||||
if not get_option('gudev')
|
||||
error('gudev is required for plugin_parade_lspcon')
|
||||
endif
|
||||
|
||||
cargs = ['-DG_LOG_DOMAIN="FuPluginParadeLspcon"']
|
||||
|
||||
install_data(['parade-lspcon.quirk'],
|
||||
install_dir: join_paths(get_option('datadir'), 'fwupd', 'quirks.d')
|
||||
)
|
||||
|
||||
shared_module('fu_plugin_parade_lspcon',
|
||||
fu_hash,
|
||||
sources : [
|
||||
'fu-parade-lspcon-device.c',
|
||||
'fu-plugin-parade-lspcon.c',
|
||||
],
|
||||
include_directories : [
|
||||
root_incdir,
|
||||
fwupd_incdir,
|
||||
fwupdplugin_incdir,
|
||||
],
|
||||
install : true,
|
||||
install_dir: plugin_dir,
|
||||
link_with : [
|
||||
fwupd,
|
||||
fwupdplugin,
|
||||
],
|
||||
c_args : [
|
||||
cargs,
|
||||
'-DLOCALSTATEDIR="' + localstatedir + '"',
|
||||
],
|
||||
dependencies : [
|
||||
plugin_deps,
|
||||
],
|
||||
)
|
||||
endif
|
11
plugins/parade-lspcon/parade-lspcon.quirk
Normal file
11
plugins/parade-lspcon/parade-lspcon.quirk
Normal file
@ -0,0 +1,11 @@
|
||||
# match all devices with this udev subsystem
|
||||
[I2C]
|
||||
Plugin = parade_lspcon
|
||||
|
||||
# Parade PS175
|
||||
[PARADE-LSPCON\NAME_1AF80175:00]
|
||||
Name = PS175
|
||||
|
||||
# "Puff" Chromeboxes
|
||||
[PARADE-LSPCON\NAME_1AF80175:00&FAMILY_Google_Hatch]
|
||||
ParadeLspconAuxDeviceName=DPDDC-B
|
Loading…
Reference in New Issue
Block a user