fwupd/plugins/pixart-rf/fu-pxi-device.c
2021-02-17 08:48:45 +00:00

664 lines
19 KiB
C

/*
* Copyright (C) 2020 Jimmy Yu <Jimmy_yu@pixart.com>
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#ifdef HAVE_HIDRAW_H
#include <linux/hidraw.h>
#include <linux/input.h>
#endif
#include "fu-common.h"
#include "fu-chunk.h"
#include "fu-pxi-device.h"
#include "fu-pxi-firmware.h"
#define PXI_HID_DEV_OTA_INPUT_REPORT_ID 0x05
#define PXI_HID_DEV_OTA_FEATURE_REPORT_ID 0x07
#define FU_PXI_DEVICE_CMD_FW_OTA_INIT 0x10u
#define FU_PXI_DEVICE_CMD_FW_WRITE 0x17u
#define FU_PXI_DEVICE_CMD_FW_UPGRADE 0x18u
#define FU_PXI_DEVICE_CMD_FW_MCU_RESET 0x22u
#define FU_PXI_DEVICE_CMD_FW_GET_INFO 0x23u
#define FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE 0x25u
#define FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW 0x27u
#define FU_PXI_DEVICE_CMD_FW_OTA_RETRANSMIT 0x28u
#define FU_PXI_DEVICE_CMD_FW_OTA_DISCONNECT 0x29u
#define FU_PXI_DEVICE_OBJECT_SIZE_MAX 4096 /* bytes */
#define FU_PXI_DEVICE_OTA_BUF_SZ 512 /* bytes */
#define FU_PXI_DEVICE_NOTTFY_RET_LEN 4 /* bytes */
#define FU_PXI_DEVICE_FW_INFO_RET_LEN 8 /* bytes */
/* OTA target selection */
enum ota_process_setting {
OTA_MAIN_FW, /* Main firmware */
OTA_HELPER_FW, /* Helper firmware */
OTA_EXTERNAL_RESOURCE, /* External resource */
};
/* OTA spec check result */
enum ota_spec_check_result {
OTA_SPEC_CHECK_OK = 1, /* Spec check ok */
OTA_FW_OUT_OF_BOUNDS = 2, /* OTA firmware size out of bound */
OTA_PROCESS_ILLEGAL = 3, /* Illegal OTA process */
OTA_RECONNECT = 4, /* Inform OTA app do reconnect */
OTA_FW_IMG_VERSION_ERROR = 5, /* FW image file version check error */
OTA_SPEC_CHECK_MAX_NUM, /* Max number of OTA driver defined error code */
};
/* OTA disconnect reason */
enum ota_disconnect_reason {
OTA_CODE_JUMP = 1, /* OTA code jump */
OTA_UPDATE_DONE = 2, /* OTA update done */
OTA_RESET, /* OTA reset */
};
struct _FuPxiDevice {
FuUdevDevice parent_instance;
guint8 status;
guint8 new_flow;
guint16 offset;
guint16 checksum;
guint32 max_object_size;
guint16 mtu_size;
guint16 prn_threshold;
guint8 spec_check_result;
};
G_DEFINE_TYPE (FuPxiDevice, fu_pxi_device, FU_TYPE_UDEV_DEVICE)
#ifdef HAVE_HIDRAW_H
static gboolean
fu_pxi_device_get_raw_info (FuPxiDevice *self, struct hidraw_devinfo *info, GError **error)
{
if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
HIDIOCGRAWINFO, (guint8 *) info,
NULL, error)) {
return FALSE;
}
return TRUE;
}
#endif
static const gchar *
fu_pxi_device_spec_check_result_to_string (guint8 spec_check_result)
{
if (spec_check_result == OTA_SPEC_CHECK_OK)
return "ok";
if (spec_check_result == OTA_FW_OUT_OF_BOUNDS)
return "fw-out-of-bounds";
if (spec_check_result == OTA_PROCESS_ILLEGAL)
return "process-illegal";
if (spec_check_result == OTA_RECONNECT)
return "reconnect";
if (spec_check_result == OTA_FW_IMG_VERSION_ERROR)
return "fw-img-version-error";
return NULL;
}
static void
fu_pxi_device_to_string (FuDevice *device, guint idt, GString *str)
{
FuPxiDevice *self = FU_PXI_DEVICE (device);
fu_common_string_append_kx (str, idt, "Status", self->status);
fu_common_string_append_kx (str, idt, "NewFlow", self->new_flow);
fu_common_string_append_kx (str, idt, "CurrentObjectOffset", self->offset);
fu_common_string_append_kx (str, idt, "CurrentChecksum", self->checksum);
fu_common_string_append_kx (str, idt, "MaxObjectSize", self->max_object_size);
fu_common_string_append_kx (str, idt, "MtuSize", self->mtu_size);
fu_common_string_append_kx (str, idt, "PacketReceiptNotificationThreshold", self->prn_threshold);
fu_common_string_append_kv (str, idt, "SpecCheckResult",
fu_pxi_device_spec_check_result_to_string (self->spec_check_result));
}
static FuFirmware *
fu_pxi_device_prepare_firmware (FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
g_autoptr(FuFirmware) firmware = fu_pxi_firmware_new ();
if (!fu_firmware_parse (firmware, fw, flags, error))
return NULL;
return g_steal_pointer (&firmware);
}
static gboolean
fu_pxi_device_set_feature (FuPxiDevice *self, GByteArray *req, GError **error)
{
#ifdef HAVE_HIDRAW_H
if (g_getenv ("FWUPD_PIXART_RF_VERBOSE") != NULL) {
fu_common_dump_raw (G_LOG_DOMAIN, "SetFeature",
req->data, req->len);
}
return fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
HIDIOCSFEATURE(req->len), (guint8 *) req->data,
NULL, error);
#else
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"<linux/hidraw.h> not available");
return FALSE;
#endif
}
static gboolean
fu_pxi_device_get_feature (FuPxiDevice *self, guint8 *buf, guint bufsz, GError **error)
{
#ifdef HAVE_HIDRAW_H
if (!fu_udev_device_ioctl (FU_UDEV_DEVICE (self),
HIDIOCGFEATURE(bufsz), buf,
NULL, error)) {
return FALSE;
}
if (g_getenv ("FWUPD_PIXART_RF_VERBOSE") != NULL)
fu_common_dump_raw (G_LOG_DOMAIN, "GetFeature", buf, bufsz);
return TRUE;
#else
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"<linux/hidraw.h> not available");
return FALSE;
#endif
}
static guint16
fu_pxi_device_calculate_checksum (const guint8 *buf, gsize bufsz)
{
guint16 checksum = 0;
for (gsize idx = 0; idx < bufsz; idx++)
checksum += (guint16) buf[idx];
return checksum;
}
static gboolean
fu_pxi_device_check_support_resume (FuPxiDevice *self,
FuFirmware *firmware,
GError **error)
{
g_autoptr(GBytes) fw = NULL;
g_autoptr(GPtrArray) chunks = NULL;
guint16 checksum_tmp = 0x0;
/* get the default image */
fw = fu_firmware_get_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* check offset is invalid or not */
chunks = fu_chunk_array_new_from_bytes (fw, 0x0, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX);
if (self->offset > chunks->len) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"offset from device is invalid: "
"got 0x%x, current maximum 0x%x",
self->offset,
chunks->len);
return FALSE;
}
/* calculate device current checksum */
for (guint i = 0; i < self->offset; i++) {
FuChunk *chk = g_ptr_array_index (chunks, i);
checksum_tmp += fu_pxi_device_calculate_checksum (fu_chunk_get_data (chk),
fu_chunk_get_data_sz (chk));
}
/* check current file is different with previous fw bin or not */
if (self->checksum != checksum_tmp) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"checksum is different from previous fw: "
"got 0x%04x, expected 0x%04x",
self->checksum,
checksum_tmp);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_pxi_device_wait_notify (FuPxiDevice *self,
goffset port,
guint8 *status,
guint16 *checksum,
GError **error)
{
guint8 res[FU_PXI_DEVICE_OTA_BUF_SZ] = {
PXI_HID_DEV_OTA_INPUT_REPORT_ID,
0x0,
};
if (!fu_udev_device_pread_full (FU_UDEV_DEVICE (self),
port, res, (FU_PXI_DEVICE_NOTTFY_RET_LEN + 1) - port,
error))
return FALSE;
if (status != NULL) {
if (!fu_common_read_uint8_safe (res, sizeof(res), 0x1,
status, error))
return FALSE;
}
if (checksum != NULL) {
if (!fu_common_read_uint16_safe (res, sizeof(res), 0x3,
checksum, G_LITTLE_ENDIAN, error))
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_pxi_device_fw_object_create (FuPxiDevice *self, FuChunk *chk, GError **error)
{
guint8 opcode = 0;
g_autoptr(GByteArray) req = g_byte_array_new ();
/* request */
fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID);
fu_byte_array_append_uint8 (req, FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE);
fu_byte_array_append_uint32 (req, fu_chunk_get_address (chk), G_LITTLE_ENDIAN);
fu_byte_array_append_uint32 (req, fu_chunk_get_data_sz (chk), G_LITTLE_ENDIAN);
if (!fu_pxi_device_set_feature (self, req, error))
return FALSE;
/* check object create success or not */
if (!fu_pxi_device_wait_notify (self, 0x0, &opcode, NULL, error))
return FALSE;
if (opcode != FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"FwObjectCreate opcode got 0x%02x, expected 0x%02x",
opcode,
FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_pxi_device_write_payload (FuPxiDevice *self, FuChunk *chk, GError **error)
{
g_autoptr(GByteArray) req = g_byte_array_new ();
fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID);
g_byte_array_append (req, fu_chunk_get_data (chk), fu_chunk_get_data_sz (chk));
return fu_pxi_device_set_feature (self, req, error);
}
static gboolean
fu_pxi_device_write_chunk (FuPxiDevice *self, FuChunk *chk, GError **error)
{
guint32 prn = 0;
guint16 checksum;
guint16 checksum_device = 0;
g_autoptr(GPtrArray) chunks = NULL;
/* send create fw object command */
if (!fu_pxi_device_fw_object_create (self, chk, error))
return FALSE;
/* write payload */
chunks = fu_chunk_array_new (fu_chunk_get_data (chk),
fu_chunk_get_data_sz (chk),
fu_chunk_get_address (chk),
0x0, self->mtu_size);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk2 = g_ptr_array_index (chunks, i);
if (!fu_pxi_device_write_payload (self, chk2, error))
return FALSE;
prn++;
/* wait notify from device when PRN over threshold write or
* offset reach max object sz or write offset reach fw length */
if (prn >= self->prn_threshold || i == chunks->len - 1) {
guint8 opcode = 0;
if (!fu_pxi_device_wait_notify (self, 0x0,
&opcode,
&checksum_device,
error))
return FALSE;
if (opcode != FU_PXI_DEVICE_CMD_FW_WRITE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"FwWrite opcode invalid 0x%02x",
opcode);
return FALSE;
}
prn = 0;
}
}
/* the last chunk */
checksum = fu_pxi_device_calculate_checksum (fu_chunk_get_data (chk),
fu_chunk_get_data_sz (chk));
self->checksum += checksum;
if (checksum_device != self->checksum ) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"checksum fail, got 0x%04x, expected 0x%04x",
checksum_device,
checksum);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_pxi_device_reset (FuPxiDevice *self, GError **error)
{
g_autoptr(GByteArray) req = g_byte_array_new ();
fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID);
fu_byte_array_append_uint8 (req, FU_PXI_DEVICE_CMD_FW_MCU_RESET);
fu_byte_array_append_uint8 (req, OTA_RESET);
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_RESTART);
if (!fu_pxi_device_set_feature (self, req, error)) {
g_prefix_error (error, "failed to reset: ");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_pxi_device_fw_ota_init (FuPxiDevice *self, GError **error)
{
g_autoptr(GByteArray) req = g_byte_array_new ();
/* write fw ota init command */
fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID);
fu_byte_array_append_uint8 (req, FU_PXI_DEVICE_CMD_FW_OTA_INIT);
return fu_pxi_device_set_feature (self, req, error);
}
static gboolean
fu_pxi_device_fw_ota_init_new (FuPxiDevice *self, gsize bufsz, GError **error)
{
guint8 res[FU_PXI_DEVICE_OTA_BUF_SZ] = { 0x0 };
guint8 fw_version[10] = { 0x0 };
g_autoptr(GByteArray) req = g_byte_array_new ();
/* write fw ota init new command */
fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID);
fu_byte_array_append_uint8 (req, FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW);
fu_byte_array_append_uint32 (req, bufsz, G_LITTLE_ENDIAN);
fu_byte_array_append_uint8 (req, 0x0); /* OTA setting */
g_byte_array_append (req, fw_version, sizeof(fw_version));
if (!fu_pxi_device_set_feature (self, req, error))
return FALSE;
/* delay for BLE device read command */
g_usleep (10 * 1000);
/* read fw ota init new command */
res[0] = PXI_HID_DEV_OTA_FEATURE_REPORT_ID;
res[1] = FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW;
if (!fu_pxi_device_get_feature (self, res, sizeof(res), error))
return FALSE;
/* shared state */
if (!fu_common_read_uint8_safe (res, sizeof(res), 0x3,
&self->status, error))
return FALSE;
if (!fu_common_read_uint8_safe (res, sizeof(res), 0x4,
&self->new_flow, error))
return FALSE;
if (!fu_common_read_uint16_safe (res, sizeof(res), 0x5,
&self->offset, G_LITTLE_ENDIAN, error))
return FALSE;
if (!fu_common_read_uint16_safe (res, sizeof(res), 0x7,
&self->checksum, G_LITTLE_ENDIAN, error))
return FALSE;
if (!fu_common_read_uint32_safe (res, sizeof(res), 0x9,
&self->max_object_size, G_LITTLE_ENDIAN, error))
return FALSE;
if (!fu_common_read_uint16_safe (res, sizeof(res), 0xd,
&self->mtu_size, G_LITTLE_ENDIAN, error))
return FALSE;
if (!fu_common_read_uint16_safe (res, sizeof(res), 0xf,
&self->prn_threshold, G_LITTLE_ENDIAN, error))
return FALSE;
if (!fu_common_read_uint8_safe (res, sizeof(res), 0x11,
&self->spec_check_result, error))
return FALSE;
/* sanity check */
if (self->spec_check_result != OTA_SPEC_CHECK_OK) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"FwInitNew spec check fail with %s [0x%02x]",
fu_pxi_device_spec_check_result_to_string (self->spec_check_result),
self->spec_check_result);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_pxi_device_fw_upgrade (FuPxiDevice *self, FuFirmware *firmware, GError **error)
{
const gchar *version;
const guint8 *buf;
gsize bufsz = 0;
guint8 fw_version[10] = { 0x0 };
guint8 opcode = 0;
guint16 checksum;
g_autoptr(GBytes) fw = NULL;
g_autoptr(GByteArray) req = g_byte_array_new ();
fw = fu_firmware_get_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
buf = g_bytes_get_data (fw, &bufsz);
checksum = fu_pxi_device_calculate_checksum (buf, bufsz);
fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID);
fu_byte_array_append_uint8 (req, FU_PXI_DEVICE_CMD_FW_UPGRADE);
fu_byte_array_append_uint32 (req, bufsz, G_LITTLE_ENDIAN);
fu_byte_array_append_uint16 (req, checksum, G_LITTLE_ENDIAN);
version = fu_firmware_get_version (firmware);
if (!fu_memcpy_safe (fw_version, sizeof(fw_version), 0x0, /* dst */
(guint8 *) version, strlen (version), 0x0, /* src */
strlen (version), error))
return FALSE;
g_byte_array_append (req, fw_version, sizeof(fw_version));
/* send fw upgrade command */
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY);
if (!fu_pxi_device_set_feature (self, req, error))
return FALSE;
if (g_getenv ("FWUPD_PIXART_RF_VERBOSE") != NULL)
fu_common_dump_raw (G_LOG_DOMAIN, "fw upgrade", req->data, req->len);
/* read fw upgrade command result */
if (!fu_pxi_device_wait_notify (self, 0x1, &opcode, NULL, error))
return FALSE;
if (opcode != FU_PXI_DEVICE_CMD_FW_UPGRADE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"FwUpgrade opcode invalid 0x%02x",
opcode);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_pxi_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuPxiDevice *self = FU_PXI_DEVICE (device);
g_autoptr(GBytes) fw = NULL;
g_autoptr(GPtrArray) chunks = NULL;
g_autoptr(GError) error_local = NULL;
/* get the default image */
fw = fu_firmware_get_image_default_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* send fw ota init command */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY);
if (!fu_pxi_device_fw_ota_init (self, error))
return FALSE;
if (!fu_pxi_device_fw_ota_init_new (self, g_bytes_get_size (fw), error))
return FALSE;
/* prepare write fw into device */
chunks = fu_chunk_array_new_from_bytes (fw, 0x0, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX);
if (!fu_pxi_device_check_support_resume (self, firmware, &error_local)) {
g_debug ("do not resume: %s", error_local->message);
self->offset = 0;
self->checksum = 0;
}
/* write fw into device */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
for (guint i = self->offset; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index (chunks, i);
if (!fu_pxi_device_write_chunk (self, chk, error))
return FALSE;
fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len);
}
/* fw upgrade command */
if (!fu_pxi_device_fw_upgrade (self, firmware, error))
return FALSE;
/* send device reset command */
return fu_pxi_device_reset (self, error);
}
static gboolean
fu_pxi_device_fw_get_info (FuPxiDevice *self, GError **error)
{
guint8 res[FU_PXI_DEVICE_OTA_BUF_SZ] = { 0x0 };
guint8 opcode = 0x0;
guint16 checksum = 0;
g_autofree gchar *version_str = NULL;
g_autoptr(GByteArray) req = g_byte_array_new ();
fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID);
fu_byte_array_append_uint8 (req, FU_PXI_DEVICE_CMD_FW_GET_INFO);
if (!fu_pxi_device_set_feature (self, req, error))
return FALSE;
res[0] = PXI_HID_DEV_OTA_FEATURE_REPORT_ID;
res[1] = FU_PXI_DEVICE_CMD_FW_GET_INFO;
if (!fu_pxi_device_get_feature (self, res, FU_PXI_DEVICE_FW_INFO_RET_LEN + 1, error))
return FALSE;
if (!fu_common_read_uint8_safe (res, sizeof(res), 0x2, &opcode, error))
return FALSE;
if (opcode != FU_PXI_DEVICE_CMD_FW_GET_INFO) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"FwGetInfo opcode invalid 0x%02x",
opcode);
return FALSE;
}
/* set current version */
version_str = g_strndup ((gchar *) res + 0x4, 5);
fu_device_set_version (FU_DEVICE (self), version_str);
/* add current checksum */
if (!fu_common_read_uint16_safe (res, sizeof(res), 0x9,
&checksum, G_LITTLE_ENDIAN, error))
return FALSE;
/* success */
return TRUE;
}
static gboolean
fu_pxi_device_probe (FuDevice *device, GError **error)
{
/* set the physical ID */
return fu_udev_device_set_physical_id (FU_UDEV_DEVICE (device), "hid", error);
}
static gboolean
fu_pxi_device_setup_guid (FuPxiDevice *self, GError **error)
{
#ifdef HAVE_HIDRAW_H
struct hidraw_devinfo hid_raw_info = { 0x0 };
g_autofree gchar *devid = NULL;
g_autoptr(GString) ble_name = NULL;
/* extra GUID */
if (!fu_pxi_device_get_raw_info (self, &hid_raw_info ,error))
return FALSE;
ble_name = g_string_new (fu_device_get_name (FU_DEVICE (self)));
g_string_ascii_up (ble_name);
fu_common_string_replace (ble_name, " ", "_");
devid = g_strdup_printf ("HIDRAW\\VID_%04X&PID_%04X&BLE_%s",
(guint) hid_raw_info.vendor,
(guint) hid_raw_info.product,
ble_name->str);
fu_device_add_instance_id (FU_DEVICE (self), devid);
#endif
return TRUE;
}
static gboolean
fu_pxi_device_setup (FuDevice *device, GError **error)
{
FuPxiDevice *self = FU_PXI_DEVICE (device);
if (!fu_pxi_device_setup_guid (self ,error))
return FALSE;
if (!fu_pxi_device_fw_ota_init (self, error))
return FALSE;
if (!fu_pxi_device_fw_get_info (self, error))
return FALSE;
return TRUE;
}
static void
fu_pxi_device_init (FuPxiDevice *self)
{
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_TRIPLET);
fu_device_add_vendor_id (FU_DEVICE (self), "USB:0x093A");
fu_device_set_protocol (FU_DEVICE (self), "com.pixart.rf");
}
static void
fu_pxi_device_class_init (FuPxiDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
klass_device->probe = fu_pxi_device_probe;
klass_device->setup = fu_pxi_device_setup;
klass_device->to_string = fu_pxi_device_to_string;
klass_device->write_firmware = fu_pxi_device_write_firmware;
klass_device->prepare_firmware = fu_pxi_device_prepare_firmware;
}