[usb-dfu] LoadFirmware impl and updated test app.
Currently doesn't enter application mode as we are missing USB reset.
Renamed and modified usb-test-fwloader to also work for DFU devices.
ZX-3155 #comment
TEST= usb-fwloader -d -f firmware_image
Firmware downloaded to DFU device with no errors.
Verified usb-fwloader still works for fx3 test device.
Change-Id: I05ee050198b90fad582a47ffa51e3d9395eb69f2
diff --git a/system/dev/usb/usb-dfu/usb-dfu.cpp b/system/dev/usb/usb-dfu/usb-dfu.cpp
index 137a879..f602ea6 100644
--- a/system/dev/usb/usb-dfu/usb-dfu.cpp
+++ b/system/dev/usb/usb-dfu/usb-dfu.cpp
@@ -17,6 +17,8 @@
namespace {
+constexpr uint32_t kReqTimeoutSecs = 1;
+
inline uint8_t MSB(int n) { return static_cast<uint8_t>(n >> 8); }
inline uint8_t LSB(int n) { return static_cast<uint8_t>(n & 0xFF); }
@@ -27,8 +29,9 @@
}
zx_status_t fidl_LoadFirmware(void* ctx, const fuchsia_mem_Buffer* firmware, fidl_txn_t* txn) {
- // TODO(jocelyndang): implement this.
- return zircon_usb_test_fwloader_DeviceLoadFirmware_reply(txn, ZX_ERR_NOT_SUPPORTED);
+ auto fp = static_cast<usb::Dfu*>(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 = {
@@ -40,6 +43,157 @@
namespace usb {
+zx_status_t Dfu::ControlReq(uint8_t dir, uint8_t request, uint16_t value,
+ void* data, size_t length, size_t* out_length) {
+ if (dir != USB_DIR_OUT && dir != USB_DIR_IN) {
+ return ZX_ERR_INVALID_ARGS;
+ }
+ zx_status_t status = usb_control(&usb_, dir | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ request, value, intf_num_, data, length,
+ ZX_SEC(kReqTimeoutSecs), out_length);
+ if (status == ZX_ERR_IO_REFUSED || status == ZX_ERR_IO_INVALID) {
+ usb_reset_endpoint(&usb_, 0);
+ }
+ return status;
+}
+
+zx_status_t Dfu::Download(uint16_t block_num, uint8_t* buf, size_t len_to_write) {
+ if (len_to_write > func_desc_.wTransferSize) {
+ return ZX_ERR_INVALID_ARGS;
+ }
+ size_t out_len;
+ zx_status_t status = ControlReq(USB_DIR_OUT, USB_DFU_DNLOAD,
+ block_num, buf, len_to_write, &out_len);
+
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "DNLOAD returned err %d\n", status);
+ return status;
+ } else if (out_len != len_to_write) {
+ zxlogf(ERROR, "DNLOAD returned bad len, want: %lu, got: %lu\n", len_to_write, out_len);
+ return ZX_ERR_IO;
+ }
+ return ZX_OK;
+}
+
+zx_status_t Dfu::GetStatus(usb_dfu_get_status_data_t* out_status) {
+ size_t want_len = sizeof(*out_status);
+ size_t out_len;
+ zx_status_t status = ControlReq(USB_DIR_IN, USB_DFU_GET_STATUS,
+ 0, out_status, want_len, &out_len);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "GET_STATUS returned err %d\n", status);
+ return status;
+ } else if (out_len != want_len) {
+ zxlogf(ERROR, "GET_STATUS returned bad len, want: %lu, got: %lu\n", want_len, out_len);
+ return ZX_ERR_IO;
+ }
+ return ZX_OK;
+}
+
+zx_status_t Dfu::ClearStatus() {
+ size_t out_len;
+ zx_status_t status = ControlReq(USB_DIR_OUT, USB_DFU_CLR_STATUS, 0, nullptr, 0, &out_len);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "CLR_STATUS returned err %d\n", status);
+ return status;
+ }
+ return ZX_OK;
+}
+
+zx_status_t Dfu::GetState(uint8_t* out_state) {
+ size_t want_len = sizeof(*out_state);
+ size_t out_len;
+ zx_status_t status = ControlReq(USB_DIR_IN, USB_DFU_GET_STATE,
+ 0, out_state, want_len, &out_len);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "GET_STATE returned err %d\n", status);
+ return status;
+ } else if (out_len != want_len) {
+ zxlogf(ERROR, "GET_STATE returned bad len, want: %lu, got: %lu\n", want_len, out_len);
+ return ZX_ERR_IO;
+ }
+ return ZX_OK;
+}
+
+zx_status_t Dfu::LoadFirmware(zx::vmo fw_vmo, size_t fw_size) {
+ if (fw_size == 0) {
+ return ZX_ERR_INVALID_ARGS;
+ }
+ 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;
+ }
+
+ // We need to be in the DFU Idle state.
+ uint8_t state;
+ status = GetState(&state);
+ if (status != ZX_OK) {
+ return status;
+ }
+ switch (state) {
+ case USB_DFU_STATE_DFU_IDLE:
+ break;
+ case USB_DFU_STATE_DFU_ERROR:
+ // We can get back to the DFU Idle state by clearing the error status.
+ // USB DFU Spec Rev, 1.1, Table A.2.11.
+ zxlogf(ERROR, "device is in dfuERROR state, trying to clear error status...\n");
+ status = ClearStatus();
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "could not clear error status, got err: %d\n", status);
+ return status;
+ }
+ break;
+ default:
+ // TODO(jocelyndang): handle more states.
+ zxlogf(ERROR, "device is in an unexpected state: %u\n", state);
+ return ZX_ERR_BAD_STATE;
+ }
+
+ // Write the firmware to the device.
+ // We just need to slice the firmware image into N pieces and call the USB_DFU_DNLOAD command.
+ size_t vmo_offset = 0;
+ // The block number is incremented per transfer.
+ uint16_t block_num = 0;
+ uint8_t write_buf[func_desc_.wTransferSize];
+
+ size_t len_to_write;
+ do {
+ len_to_write = fbl::min(fw_size - vmo_offset,
+ static_cast<size_t>(func_desc_.wTransferSize));
+ zxlogf(TRACE, "fetching block %u, offset %lu len %lu\n",
+ block_num, vmo_offset, len_to_write);
+ zx_status_t status = fw_vmo.read(write_buf, vmo_offset, len_to_write);
+ if (status != ZX_OK) {
+ return status;
+ }
+ status = Download(block_num, write_buf, len_to_write);
+ if (status != ZX_OK) {
+ return status;
+ }
+ usb_dfu_get_status_data_t dfu_status;
+ status = GetStatus(&dfu_status);
+ if (status != ZX_OK) {
+ return status;
+ }
+ if (dfu_status.bStatus != USB_DFU_STATUS_OK) {
+ zxlogf(ERROR, "bad status %u\n", dfu_status.bStatus);
+ return ZX_ERR_IO;
+ }
+ // The device expects the block number to wrap around to zero, so no need to bounds check.
+ block_num++;
+ vmo_offset += len_to_write;
+ } while (len_to_write != 0); // The device expects a zero length transfer to signify the end.
+ // TODO(jocelyndang): issue a USB Reset to enter Application Mode.
+
+ return ZX_OK;
+}
+
zx_status_t Dfu::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
return zircon_usb_test_fwloader_Device_dispatch(this, txn, msg, &fidl_ops);
}
@@ -98,7 +252,7 @@
}
fbl::AllocChecker ac;
- auto dev = fbl::make_unique_checked<Dfu>(&ac, parent, intf_num, func_desc);
+ auto dev = fbl::make_unique_checked<Dfu>(&ac, parent, usb, intf_num, func_desc);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
diff --git a/system/dev/usb/usb-dfu/usb-dfu.h b/system/dev/usb/usb-dfu/usb-dfu.h
index f5de19c..b241084 100644
--- a/system/dev/usb/usb-dfu/usb-dfu.h
+++ b/system/dev/usb/usb-dfu/usb-dfu.h
@@ -19,8 +19,10 @@
public ddk::EmptyProtocol<ZX_PROTOCOL_USB_TEST_FWLOADER> {
public:
- Dfu(zx_device_t* parent, uint8_t intf_num, const usb_dfu_func_desc_t& func_desc)
+ Dfu(zx_device_t* parent, const usb_protocol_t& usb,
+ uint8_t intf_num, const usb_dfu_func_desc_t& func_desc)
: DfuBase(parent),
+ usb_(usb),
intf_num_(intf_num),
func_desc_(func_desc) {}
@@ -32,9 +34,31 @@
void DdkUnbind() { DdkRemove(); }
void DdkRelease() { delete this; }
+ // FIDL message implementation.
+ zx_status_t LoadFirmware(zx::vmo fw_vmo, size_t fw_size);
+
private:
zx_status_t Bind();
+ // Sends a USB control request to the device, and resets the control endpoint if stalled.
+ // Returns ZX_OK if the request succeeded.
+ zx_status_t ControlReq(uint8_t dir, uint8_t request, uint16_t value,
+ void* data, size_t length, size_t* out_length);
+
+ // Downloads the data block to the device.
+ // |block_num| should be incremented each time a block is transferred and range
+ // from 0 to 65,535, wrapping around if necessary.
+ // |len_to_write| is limited by the device max transfer size stored in |func_desc_|.
+ zx_status_t Download(uint16_t block_num, uint8_t* buf, size_t len_to_write);
+ // Stores the status data of the last download transfer into |out_status|.
+ zx_status_t GetStatus(usb_dfu_get_status_data_t* out_status);
+ // Sets the device status to OK and transitions the device to the DFU Idle state.
+ zx_status_t ClearStatus();
+ // Stores the current state of the device to |out_dfu_state|.
+ zx_status_t GetState(uint8_t* out_dfu_state);
+
+ const usb_protocol_t usb_;
+
const uint8_t intf_num_;
const usb_dfu_func_desc_t func_desc_;
};
diff --git a/system/uapp/usb-test-fwloader/rules.mk b/system/uapp/usb-fwloader/rules.mk
similarity index 93%
rename from system/uapp/usb-test-fwloader/rules.mk
rename to system/uapp/usb-fwloader/rules.mk
index 27d5247..b9818e0 100644
--- a/system/uapp/usb-test-fwloader/rules.mk
+++ b/system/uapp/usb-fwloader/rules.mk
@@ -8,7 +8,7 @@
MODULE_GROUP := misc
MODULE_SRCS += \
- $(LOCAL_DIR)/usb-test-fwloader.cpp \
+ $(LOCAL_DIR)/usb-fwloader.cpp \
MODULE_STATIC_LIBS := \
system/ulib/zx \
diff --git a/system/uapp/usb-test-fwloader/usb-test-fwloader.cpp b/system/uapp/usb-fwloader/usb-fwloader.cpp
similarity index 78%
rename from system/uapp/usb-test-fwloader/usb-test-fwloader.cpp
rename to system/uapp/usb-fwloader/usb-fwloader.cpp
index 7812450..1c4d6ab 100644
--- a/system/uapp/usb-test-fwloader/usb-test-fwloader.cpp
+++ b/system/uapp/usb-fwloader/usb-fwloader.cpp
@@ -30,7 +30,13 @@
struct WatchDirData {
const char* dev_name;
- int fd;
+ fbl::unique_fd fd;
+};
+
+enum class Mode {
+ kUpdateTest = 0, // Update the test firmware.
+ kUpdateTestBoot = 1, // Update the test device bootloader.
+ kDeviceFirmwareUpgrade = 2, // Perform a DFU. The device must implement the USB DFU Spec.
};
constexpr char kFwLoaderDir[] = "/dev/class/usb-test-fwloader";
@@ -38,6 +44,8 @@
constexpr char kFirmwareLoader[] = "fx3";
constexpr char kFlashProgrammer[] = "flash-programmer";
+constexpr char kUSBDFU[] = "usb-dfu";
+constexpr char kUSBTester[] = "usb-tester";
constexpr int kEnumerationWaitSecs = 5;
@@ -50,30 +58,37 @@
printf(" -t : Load test firmware mode.\n"
" This is the default if no mode is specified.\n"
" -b : Flash bootloader mode.\n"
+ " -d : USB Device Firmware Upgrade.\n"
" -f <firmware_path> : Firmware to load.\n"
" -p <flash_prog_path> : Firmware image for the flash programmer.\n"
- " This is required when flashing a new bootloader.\n");
+ " This is only required when flashing a new bootloader.\n");
+}
+
+zx_status_t fd_matches_name(const fbl::unique_fd& fd, const char* dev_name, bool* out_match) {
+ char path[PATH_MAX];
+ const ssize_t r = ioctl_device_get_topo_path(fd.get(), path, sizeof(path));
+ if (r < 0) {
+ return ZX_ERR_IO;
+ }
+ *out_match = dev_name == nullptr || strstr(path, dev_name) != nullptr;
+ return ZX_OK;
}
zx_status_t watch_dir_cb(int dirfd, int event, const char* filename, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
- int fd = openat(dirfd, filename, O_RDWR);
- if (fd < 0) {
+ fbl::unique_fd fd(openat(dirfd, filename, O_RDWR));
+ if (!fd) {
return ZX_OK;
}
auto data = reinterpret_cast<WatchDirData*>(cookie);
- char path[PATH_MAX];
- const ssize_t r = ioctl_device_get_topo_path(fd, path, sizeof(path));
- if (r < 0) {
- return ZX_ERR_IO;
+ bool match = false;
+ zx_status_t status = fd_matches_name(fd, data->dev_name, &match);
+ if (status != ZX_OK || !match) {
+ return status;
}
- if (data->dev_name && strstr(path, data->dev_name) == nullptr) {
- close(fd);
- return ZX_OK;
- }
- data->fd = fd;
+ data->fd = std::move(fd);
return ZX_ERR_STOP;
}
@@ -85,19 +100,19 @@
return ZX_ERR_BAD_STATE;
}
auto close_dir = fbl::MakeAutoCall([&] { closedir(d); });
- WatchDirData data = { .dev_name = dev_name, .fd = 0 };
+ WatchDirData data = { .dev_name = dev_name, .fd = fbl::unique_fd() };
zx_status_t status = fdio_watch_directory(dirfd(d), watch_dir_cb,
zx_deadline_after(ZX_SEC(kEnumerationWaitSecs)),
reinterpret_cast<void*>(&data));
if (status == ZX_ERR_STOP) {
- out_fd->reset(data.fd);
+ *out_fd = std::move(data.fd);
return ZX_OK;
} else {
return status;
}
}
-zx_status_t open_dev(const char* dir, fbl::unique_fd* out_fd) {
+zx_status_t open_dev(const char* dir, const char* dev_name, fbl::unique_fd* out_fd) {
DIR* d = opendir(dir);
if (d == nullptr) {
fprintf(stderr, "Could not open dir: \"%s\"\n", dir);
@@ -106,11 +121,16 @@
struct dirent* de;
while ((de = readdir(d)) != nullptr) {
- int fd = openat(dirfd(d), de->d_name, O_RDWR);
- if (fd < 0) {
+ fbl::unique_fd fd(openat(dirfd(d), de->d_name, O_RDWR));
+ if (!fd) {
continue;
}
- out_fd->reset(fd);
+ bool match = false;
+ zx_status_t status = fd_matches_name(fd, dev_name, &match);
+ if (status != ZX_OK || !match) {
+ continue;
+ }
+ *out_fd = std::move(fd);
closedir(d);
return ZX_OK;
}
@@ -119,13 +139,18 @@
return ZX_ERR_NOT_FOUND;
}
-zx_status_t open_fwloader_dev(fbl::unique_fd* out_fd) {
- return open_dev(kFwLoaderDir, out_fd);
+zx_status_t open_test_fwloader_dev(fbl::unique_fd* out_fd) {
+ return open_dev(kFwLoaderDir, kFirmwareLoader, out_fd);
}
zx_status_t open_usb_tester_dev(fbl::unique_fd* out_fd) {
- return open_dev(kUsbTesterDevDir, out_fd);
+ return open_dev(kUsbTesterDevDir, kUSBTester, out_fd);
}
+
+zx_status_t open_dfu_dev(fbl::unique_fd* out_fd) {
+ return open_dev(kFwLoaderDir, kUSBDFU, out_fd);
+}
+
// Reads the firmware file and populates the provided vmo with the contents.
zx_status_t read_firmware(fbl::unique_fd& file_fd, zx::vmo& vmo, size_t* out_fw_size) {
struct stat s;
@@ -222,7 +247,7 @@
std::variant<const char*, zircon_usb_test_fwloader_PrebuiltType> firmware) {
fbl::unique_fd fd;
- zx_status_t status = open_fwloader_dev(&fd);
+ zx_status_t status = open_test_fwloader_dev(&fd);
if (status != ZX_OK) {
fbl::unique_fd usb_tester_fd;
// Check if there is a usb tester device we can switch to firmware loading mode.
@@ -324,23 +349,44 @@
return ZX_OK;
}
+zx_status_t device_firmware_upgrade(const char* firmware_path) {
+ fbl::unique_fd fd;
+ zx_status_t status = open_dfu_dev(&fd);
+ if (status != ZX_OK) {
+ fprintf(stderr, "Could not find any connected USB DFU device.\n");
+ return status;
+ }
+ // TODO(jocelyndang): support prebuilts.
+ std::variant<const char*, zircon_usb_test_fwloader_PrebuiltType> firmware = firmware_path;
+ status = device_load_firmware(std::move(fd), firmware);
+ if (status != ZX_OK) {
+ fprintf(stderr, "Device firmware upgrade failed, err: %d\n", status);
+ return status;
+ }
+ printf("Finished device firmware upgrade.\n");
+ return ZX_OK;
+}
+
} // namespace
int main(int argc, char** argv) {
auto print_usage = fbl::MakeAutoCall([prog_name = argv[0]]() { usage(prog_name); });
- bool load_test_firmware_mode = true;
+ Mode mode = Mode::kUpdateTest;
const char* firmware_path = nullptr;
const char* flash_prog_path = nullptr;
int opt;
- while ((opt = getopt(argc, argv, "tbf:p:")) != -1) {
+ while ((opt = getopt(argc, argv, "tbdf:p:")) != -1) {
switch (opt) {
case 't':
- load_test_firmware_mode = true;
+ mode = Mode::kUpdateTest;
break;
case 'b':
- load_test_firmware_mode = false;
+ mode = Mode::kUpdateTestBoot;
+ break;
+ case 'd':
+ mode = Mode::kDeviceFirmwareUpgrade;
break;
case 'f':
firmware_path = optarg;
@@ -356,10 +402,16 @@
print_usage.cancel();
zx_status_t status;
- if (load_test_firmware_mode) {
+ switch (mode) {
+ case Mode::kUpdateTest:
status = load_test_firmware(firmware_path);
- } else {
+ break;
+ case Mode::kUpdateTestBoot:
status = load_bootloader(flash_prog_path, firmware_path);
+ break;
+ case Mode::kDeviceFirmwareUpgrade:
+ status = device_firmware_upgrade(firmware_path);
+ break;
}
return status == ZX_OK ? 0 : -1;
}