blob: edbb7eeaade84fea3bb8ed93bfd9da543a39627f [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flash-programmer.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <fbl/algorithm.h>
#include <fbl/unique_ptr.h>
#include <fuchsia/mem/c/fidl.h>
#include <lib/zx/vmo.h>
#include <limits>
#include <zircon/usb/test/fwloader/c/fidl.h>
#include "flash-programmer-hw.h"
namespace {
constexpr char kBootFirmwarePath[] = "Fx3BootAppGcc.img";
// The expected image format is detailed in EZ-USB/FX3 Boot Options, Table 19.
constexpr uint32_t kImageHeaderSize = 4;
constexpr uint32_t kKB = 1024;
// The I2C EEPROM size is stored in the firmware image header as a number from 0 to 7,
// which is the index for this lookup table.
constexpr uint32_t KNumEepromSizes = 8;
constexpr uint32_t kEepromSizeLUT[KNumEepromSizes] = {
0, // Reserved
0, // Reserved
4 * kKB,
8 * kKB,
16 * kKB,
32 * kKB,
64 * kKB,
128 * kKB,
};
// The maximum number of addressable EEPROMs.
constexpr uint32_t kMaxNumEeproms = 8;
// Vendor request write sizes must be a multiple of this.
constexpr uint16_t kVendorReqSizeAlignment = 64;
constexpr uint16_t kVendorReqMaxSize = 4096;
constexpr uint32_t kReqTimeoutSecs = 1;
struct ImageHeader {
uint8_t signature[2];
uint8_t image_ctl;
uint8_t image_type;
} __PACKED;
static_assert(sizeof(ImageHeader) == kImageHeaderSize, "");
zx_status_t ParseImageHeader(const zx::vmo& fw_vmo, uint32_t* out_i2c_size) {
// The header layout can be found in EZ-USB/FX3 Boot Options, Table 19.
ImageHeader image_header;
zx_status_t status = fw_vmo.read(&image_header, 0, sizeof(image_header));
if (status != ZX_OK) {
return status;
}
if (image_header.signature[0] != 'C' || image_header.signature[1] != 'Y') {
return ZX_ERR_BAD_STATE;
}
// I2C size is stored in bits 1 to 3 of image_ctl.
uint32_t idx = (image_header.image_ctl >> 1) & 0x7;
ZX_DEBUG_ASSERT(idx < KNumEepromSizes);
*out_i2c_size = kEepromSizeLUT[idx];
zxlogf(TRACE, "image header: ctl 0x%02x type 0x%02x i2c eeprom size %u\n",
image_header.image_ctl, image_header.image_type, *out_i2c_size);
return ZX_OK;
}
zx_status_t fidl_LoadPrebuiltFirmware(void* ctx, zircon_usb_test_fwloader_PrebuiltType type,
fidl_txn_t* txn) {
auto fp = static_cast<usb::FlashProgrammer*>(ctx);
zx_status_t status = fp->LoadPrebuiltFirmware(type);
return zircon_usb_test_fwloader_DeviceLoadPrebuiltFirmware_reply(txn, status);
}
zx_status_t fidl_LoadFirmware(void* ctx, const fuchsia_mem_Buffer* firmware, fidl_txn_t* txn) {
auto fp = static_cast<usb::FlashProgrammer*>(ctx);
zx_status_t status = fp->LoadFirmware(zx::vmo(firmware->vmo), firmware->size);
return zircon_usb_test_fwloader_DeviceLoadFirmware_reply(txn, status);
}
zircon_usb_test_fwloader_Device_ops_t fidl_ops = {
.LoadPrebuiltFirmware = fidl_LoadPrebuiltFirmware,
.LoadFirmware = fidl_LoadFirmware,
};
} // namespace
namespace usb {
zx_status_t FlashProgrammer::DeviceWrite(uint8_t eeprom_slave_addr, uint16_t eeprom_byte_addr,
uint8_t* buf, size_t len_to_write) {
if (len_to_write > kVendorReqMaxSize) {
return ZX_ERR_INVALID_ARGS;
}
size_t out_len;
zx_status_t status = usb_control(&usb_, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
FLASH_PROGRAMMER_WRITE, eeprom_slave_addr, eeprom_byte_addr,
buf, len_to_write, ZX_SEC(kReqTimeoutSecs), &out_len);
if (status != ZX_OK) {
zxlogf(ERROR, "usb control returned err %d\n", status);
return status;
} else if (out_len != len_to_write) {
zxlogf(ERROR, "DeviceWrite returned bad len, want: %lu, got: %lu\n",
len_to_write, out_len);
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t FlashProgrammer::EEPROMSlaveWrite(uint8_t eeprom_slave_addr,
const zx::vmo& fw_vmo, size_t vmo_offset,
uint16_t len_to_write) {
// We need to do the writes in up to 4K chunks.
uint8_t write_buf[kVendorReqMaxSize];
uint16_t eeprom_byte_addr = 0;
size_t total_written = 0;
while (total_written < len_to_write) {
// The request size needs to be a multiple of kVendorReqSizeAlignment,
// so make sure the buffer is padded with zeros.
memset(write_buf, 0, kVendorReqMaxSize);
size_t req_write_len = fbl::min(len_to_write - total_written,
static_cast<size_t>(kVendorReqMaxSize));
zx_status_t status = fw_vmo.read(write_buf, vmo_offset, req_write_len);
if (status != ZX_OK) {
return status;
}
req_write_len = fbl::round_up(req_write_len, kVendorReqSizeAlignment);
status = DeviceWrite(eeprom_slave_addr, eeprom_byte_addr, write_buf, req_write_len);
zxlogf(TRACE, "EEPROM [%u] write addr %u vmo offset %lu len to write %lu status %d\n",
eeprom_slave_addr, eeprom_byte_addr, vmo_offset, req_write_len, status);
if (status != ZX_OK) {
return status;
}
total_written += req_write_len;
eeprom_byte_addr = static_cast<uint16_t>(eeprom_byte_addr + req_write_len);
vmo_offset += req_write_len;
}
return ZX_OK;
}
zx_status_t FlashProgrammer::LoadPrebuiltFirmware(zircon_usb_test_fwloader_PrebuiltType type) {
const char* fw_path = nullptr;
switch (type) {
case zircon_usb_test_fwloader_PrebuiltType_BOOT:
fw_path = kBootFirmwarePath;
break;
default:
zxlogf(ERROR, "unsupported firmware type: %u\n", type);
return ZX_ERR_NOT_SUPPORTED;
}
zx::vmo fw_vmo;
size_t fw_size;
zx_status_t status = load_firmware(zxdev(), fw_path, fw_vmo.reset_and_get_address(), &fw_size);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to load firmware at path ""%s"", err: %d\n",
fw_path, status);
return status;
}
return LoadFirmware(std::move(fw_vmo), fw_size);
}
zx_status_t FlashProgrammer::LoadFirmware(zx::vmo fw_vmo, size_t fw_size) {
size_t vmo_size;
zx_status_t status = fw_vmo.get_size(&vmo_size);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to get firmware vmo size, err: %d\n", status);
return ZX_ERR_INVALID_ARGS;
}
if (vmo_size < fw_size) {
zxlogf(ERROR, "invalid vmo, vmo size was %lu, fw size was %lu\n", vmo_size, fw_size);
return ZX_ERR_INVALID_ARGS;
}
uint32_t eeprom_size;
status = ParseImageHeader(fw_vmo, &eeprom_size);
if (status != ZX_OK) {
zxlogf(ERROR, "invalid firmware image header, err: %d\n", status);
return status;
}
if (eeprom_size == 0) {
return ZX_ERR_NOT_SUPPORTED;
}
if (fw_size > eeprom_size * kMaxNumEeproms) {
return ZX_ERR_OUT_OF_RANGE;
}
size_t vmo_offset = 0;
uint8_t eeprom_slave_addr = 0;
while (vmo_offset < fw_size) {
// Write up to the EEPROM size.
size_t len_to_write = fbl::min(fw_size - vmo_offset, static_cast<size_t>(eeprom_size));
// TODO(jocelyndang): different handling needs to be done for 128K EEPROMs.
if (len_to_write > std::numeric_limits<uint16_t>::max()) {
return ZX_ERR_NOT_SUPPORTED;
}
status = EEPROMSlaveWrite(eeprom_slave_addr, fw_vmo, vmo_offset,
static_cast<uint16_t>(len_to_write));
if (status != ZX_OK) {
return status;
}
vmo_offset += len_to_write;
eeprom_slave_addr++;
}
return ZX_OK;
}
zx_status_t FlashProgrammer::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
return zircon_usb_test_fwloader_Device_dispatch(this, txn, msg, &fidl_ops);
}
zx_status_t FlashProgrammer::Bind() {
return DdkAdd("flash-programmer", DEVICE_ADD_NON_BINDABLE);
}
// static
zx_status_t FlashProgrammer::Create(zx_device_t* parent) {
usb_protocol_t usb;
zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_USB_OLD, &usb);
if (status != ZX_OK) {
return status;
}
fbl::AllocChecker ac;
fbl::unique_ptr<FlashProgrammer> dev(new (&ac) FlashProgrammer(parent, usb));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = dev->Bind();
if (status == ZX_OK) {
// Intentionally leak as it is now held by DevMgr.
__UNUSED auto ptr = dev.release();
}
return status;
}
extern "C" zx_status_t flash_programmer_bind(void* ctx, zx_device_t* parent) {
zxlogf(TRACE, "flash_programmer_bind\n");
return usb::FlashProgrammer::Create(parent);
}
static zx_driver_ops_t flash_programmer_driver_ops = []() {
zx_driver_ops_t ops;
ops.version = DRIVER_OPS_VERSION;
ops.bind = flash_programmer_bind;
return ops;
}();
} // namespace usb
// clang-format off
ZIRCON_DRIVER_BEGIN(flash_programmer, usb::flash_programmer_driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_DEVICE_OLD),
BI_ABORT_IF(NE, BIND_USB_VID, CYPRESS_VID),
BI_MATCH_IF(EQ, BIND_USB_PID, FLASH_PROGRAMMER_PID),
ZIRCON_DRIVER_END(flash_programmer)
// clang-format on