/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fresco-pd-common.h" #include "fu-fresco-pd-device.h" #include "fu-fresco-pd-firmware.h" struct _FuFrescoPdDevice { FuUsbDevice parent_instance; guint8 customer_id; }; G_DEFINE_TYPE(FuFrescoPdDevice, fu_fresco_pd_device, FU_TYPE_USB_DEVICE) static void fu_fresco_pd_device_to_string(FuDevice *device, guint idt, GString *str) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE(device); fu_common_string_append_ku(str, idt, "CustomerID", self->customer_id); } static gboolean fu_fresco_pd_device_transfer_read(FuFrescoPdDevice *self, guint16 offset, guint8 *buf, guint16 bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(bufsz != 0, FALSE); /* to device */ if (g_getenv("FWUPD_FRESCO_PD_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "read", buf, bufsz); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x40, 0x0, offset, buf, bufsz, &actual_length, 5000, NULL, error)) { g_prefix_error(error, "failed to read from offset 0x%x: ", offset); return FALSE; } if (bufsz != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "read 0x%x bytes of 0x%x", (guint)actual_length, bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_fresco_pd_device_transfer_write(FuFrescoPdDevice *self, guint16 offset, guint8 *buf, guint16 bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(bufsz != 0, FALSE); /* to device */ if (g_getenv("FWUPD_FRESCO_PD_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "write", buf, bufsz); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x41, 0x0, offset, buf, bufsz, &actual_length, 5000, NULL, error)) { g_prefix_error(error, "failed to write offset 0x%x: ", offset); return FALSE; } if (bufsz != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "wrote 0x%x bytes of 0x%x", (guint)actual_length, bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_fresco_pd_device_read_byte(FuFrescoPdDevice *self, guint16 offset, guint8 *buf, GError **error) { return fu_fresco_pd_device_transfer_read(self, offset, buf, 1, error); } static gboolean fu_fresco_pd_device_write_byte(FuFrescoPdDevice *self, guint16 offset, guint8 buf, GError **error) { return fu_fresco_pd_device_transfer_write(self, offset, &buf, 1, error); } static gboolean fu_fresco_pd_device_set_byte(FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf = 0x0; if (!fu_fresco_pd_device_read_byte(self, offset, &buf, error)) return FALSE; if (buf == val) return TRUE; return fu_fresco_pd_device_write_byte(self, offset, val, error); } static gboolean fu_fresco_pd_device_and_byte(FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf = 0xff; if (!fu_fresco_pd_device_read_byte(self, offset, &buf, error)) return FALSE; buf &= val; return fu_fresco_pd_device_write_byte(self, offset, buf, error); } static gboolean fu_fresco_pd_device_or_byte(FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf; if (!fu_fresco_pd_device_read_byte(self, offset, &buf, error)) return FALSE; buf |= val; return fu_fresco_pd_device_write_byte(self, offset, buf, error); } static gboolean fu_fresco_pd_device_setup(FuDevice *device, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE(device); FuUsbDevice *usb_device = FU_USB_DEVICE(device); guint8 ver[4] = {0x0}; g_autofree gchar *instance_id = NULL; g_autofree gchar *version = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_fresco_pd_device_parent_class)->setup(device, error)) return FALSE; /* read existing device version */ for (guint i = 0; i < 4; i++) { if (!fu_fresco_pd_device_transfer_read(self, 0x3000 + i, &ver[i], 1, error)) { g_prefix_error(error, "failed to read device version [%u]: ", i); return FALSE; } } version = fu_fresco_pd_version_from_buf(ver); fu_device_set_version(FU_DEVICE(self), version); /* get customer ID */ self->customer_id = ver[1]; instance_id = g_strdup_printf("USB\\VID_%04X&PID_%04X&CID_%02X", fu_usb_device_get_vid(usb_device), fu_usb_device_get_pid(usb_device), self->customer_id); fu_device_add_instance_id(device, instance_id); /* success */ return TRUE; } static FuFirmware * fu_fresco_pd_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE(device); guint8 customer_id; g_autoptr(FuFirmware) firmware = fu_fresco_pd_firmware_new(); /* check firmware is suitable */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; customer_id = fu_fresco_pd_firmware_get_customer_id(FU_FRESCO_PD_FIRMWARE(firmware)); if (customer_id != self->customer_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "device is incompatible with firmware x.%u.x.x", customer_id); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_fresco_pd_device_panther_reset_device(FuFrescoPdDevice *self, GError **error) { g_autoptr(GError) error_local = NULL; g_debug("resetting target device"); fu_device_set_status(FU_DEVICE(self), FWUPD_STATUS_DEVICE_RESTART); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* ignore when the device reset before completing the transaction */ if (!fu_fresco_pd_device_or_byte(self, 0xA003, 1 << 3, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug("ignoring %s", error_local->message); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to reset device: "); return FALSE; } return TRUE; } static gboolean fu_fresco_pd_device_write_firmware(FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE(device); const guint8 *buf; gsize bufsz = 0x0; guint16 begin_addr = 0x6420; guint8 config[3] = {0x0}; guint8 start_symbols[2] = {0x0}; g_autoptr(GBytes) fw = NULL; /* get default blob, which we know is already bigger than FirmwareMin */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; buf = g_bytes_get_data(fw, &bufsz); /* get start symbols, and be slightly paranoid */ if (!fu_memcpy_safe(start_symbols, sizeof(start_symbols), 0x0, /* dst */ buf, bufsz, 0x4000, /* src */ sizeof(start_symbols), error)) return FALSE; /* 0xA001 = b'0 * 0x6C00 = b'0 * 0x6C04 = 0x08 */ fu_device_set_status(device, FWUPD_STATUS_DEVICE_BUSY); g_debug("disable MCU, and enable mtp write"); if (!fu_fresco_pd_device_and_byte(self, 0xa001, ~(1 << 2), error)) { g_prefix_error(error, "failed to disable MCU bit 2: "); return FALSE; } if (!fu_fresco_pd_device_and_byte(self, 0x6c00, ~(1 << 1), error)) { g_prefix_error(error, "failed to disable MCU bit 1: "); return FALSE; } if (!fu_fresco_pd_device_write_byte(self, 0x6c04, 0x08, error)) { g_prefix_error(error, "failed to disable MCU: "); return FALSE; } /* fill safe code in the boot code */ fu_device_set_status(device, FWUPD_STATUS_DEVICE_WRITE); for (guint16 i = 0; i < 0x400; i += 3) { for (guint j = 0; j < 3; j++) { if (!fu_fresco_pd_device_read_byte(self, begin_addr + i + j, &config[j], error)) { g_prefix_error(error, "failed to read config byte %u: ", j); return FALSE; } } if (config[0] == start_symbols[0] && config[1] == start_symbols[1]) { begin_addr = 0x6420 + i; break; } if (config[0] == 0 && config[1] == 0 && config[2] == 0) break; } g_debug("begin_addr: 0x%04x", begin_addr); for (guint16 i = begin_addr + 3; i < begin_addr + 0x400; i += 3) { for (guint j = 0; j < 3; j++) { if (!fu_fresco_pd_device_read_byte(self, i + j, &config[j], error)) { g_prefix_error(error, "failed to read config byte %u: ", j); return FALSE; } } if (config[0] == 0x74 && config[1] == 0x06 && config[2] != 0x22) { if (!fu_fresco_pd_device_write_byte(self, i + 2, 0x22, error)) return FALSE; } else if (config[0] == 0x6c && config[1] == 0x00 && config[2] != 0x01) { if (!fu_fresco_pd_device_write_byte(self, i + 2, 0x01, error)) return FALSE; } else if (config[0] == 0x00 && config[1] == 0x00 && config[2] != 0x00) break; } /* copy buf offset [0 - 0x3FFFF] to mmio address [0x2000 - 0x5FFF] */ g_debug("fill firmware body"); for (guint16 byte_index = 0; byte_index < 0x4000; byte_index++) { if (!fu_fresco_pd_device_set_byte(self, byte_index + 0x2000, buf[byte_index], error)) return FALSE; fu_device_set_progress_full(device, (gsize)byte_index, 0x4000); } /* write file buf 0x4200 ~ 0x4205, 6 bytes to internal address 0x6600 ~ 0x6605 * write file buf 0x4210 ~ 0x4215, 6 bytes to internal address 0x6610 ~ 0x6615 * write file buf 0x4220 ~ 0x4225, 6 bytes to internal address 0x6620 ~ 0x6625 * write file buf 0x4230, 1 byte, to internal address 0x6630 */ g_debug("update customize data"); for (guint16 byte_index = 0; byte_index < 6; byte_index++) { if (!fu_fresco_pd_device_set_byte(self, 0x6600 + byte_index, buf[0x4200 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte(self, 0x6610 + byte_index, buf[0x4210 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte(self, 0x6620 + byte_index, buf[0x4220 + byte_index], error)) return FALSE; } if (!fu_fresco_pd_device_set_byte(self, 0x6630, buf[0x4230], error)) return FALSE; /* overwrite firmware file's boot code area (0x4020 ~ 0x41ff) to the area on the device * marked by begin_addr example: if the begin_addr = 0x6420, then copy file buf [0x4020 ~ * 0x41ff] to device offset[0x6420 ~ 0x65ff] */ g_debug("write boot configuration area"); for (guint16 byte_index = 0; byte_index < 0x1e0; byte_index += 3) { if (!fu_fresco_pd_device_set_byte(self, begin_addr + byte_index + 0, buf[0x4020 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte(self, begin_addr + byte_index + 1, buf[0x4021 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte(self, begin_addr + byte_index + 2, buf[0x4022 + byte_index], error)) return FALSE; } /* reset the device */ return fu_fresco_pd_device_panther_reset_device(self, error); } static void fu_fresco_pd_device_init(FuFrescoPdDevice *self) { fu_device_add_icon(FU_DEVICE(self), "audio-card"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_protocol(FU_DEVICE(self), "com.frescologic.pd"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_set_install_duration(FU_DEVICE(self), 15); fu_device_set_remove_delay(FU_DEVICE(self), 20000); fu_device_set_firmware_size(FU_DEVICE(self), 0x4400); } static void fu_fresco_pd_device_class_init(FuFrescoPdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_fresco_pd_device_to_string; klass_device->setup = fu_fresco_pd_device_setup; klass_device->write_firmware = fu_fresco_pd_device_write_firmware; klass_device->prepare_firmware = fu_fresco_pd_device_prepare_firmware; }