blob: a488ae76a25a90b26ce2fd63c9179b2eda52885b [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 <fbl/auto_call.h>
#include <fbl/unique_fd.h>
#include <fbl/unique_ptr.h>
#include <lib/fdio/watcher.h>
#include <lib/zx/vmo.h>
#include <zircon/device/usb-device.h>
#include <zircon/device/usb-test-fwloader.h>
#include <zircon/device/usb-tester.h>
#include <zircon/hw/usb.h>
#include <zircon/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
static const char* const DEV_FX3_DIR = "/dev/class/usb-test-fwloader";
static const char* const DEV_USB_TESTER_DIR = "/dev/class/usb-tester";
static const int ENUMERATION_WAIT_SECS = 5;
static constexpr uint32_t BUFFER_SIZE = 8 * 1024;
static inline int MSB(int n) { return n >> 8; }
static inline int LSB(int n) { return n & 0xFF; }
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) {
return ZX_OK;
}
auto out_fd = reinterpret_cast<int*>(cookie);
*out_fd = fd;
return ZX_ERR_STOP;
}
// Waits for a device to enumerate and be added to the given directory.
zx_status_t wait_dev_enumerate(const char* dir, fbl::unique_fd& out_fd) {
DIR* d = opendir(dir);
if (d == nullptr) {
fprintf(stderr, "Could not open dir: \"%s\"\n", dir);
return ZX_ERR_BAD_STATE;
}
auto close_dir = fbl::MakeAutoCall([&] { closedir(d); });
int fd = 0;
zx_status_t status = fdio_watch_directory(dirfd(d), watch_dir_cb,
zx_deadline_after(ZX_SEC(ENUMERATION_WAIT_SECS)),
reinterpret_cast<void*>(&fd));
if (status == ZX_ERR_STOP) {
out_fd.reset(fd);
return ZX_OK;
} else {
return status;
}
}
zx_status_t open_dev(const char* dir, fbl::unique_fd& out_fd) {
DIR* d = opendir(dir);
if (d == nullptr) {
fprintf(stderr, "Could not open dir: \"%s\"\n", dir);
return ZX_ERR_BAD_STATE;
}
struct dirent* de;
while ((de = readdir(d)) != nullptr) {
int fd = openat(dirfd(d), de->d_name, O_RDWR);
if (fd < 0) {
continue;
}
out_fd.reset(fd);
closedir(d);
return ZX_OK;
}
closedir(d);
return ZX_ERR_NOT_FOUND;
}
zx_status_t open_fwloader_dev(fbl::unique_fd& out_fd) {
return open_dev(DEV_FX3_DIR, out_fd);
}
zx_status_t open_usb_tester_dev(fbl::unique_fd& out_fd) {
return open_dev(DEV_USB_TESTER_DIR, out_fd);
}
// Reads the firmware file and populates the provided vmo with the contents.
static zx_status_t read_firmware(fbl::unique_fd& file_fd, zx::vmo& vmo) {
struct stat s;
if (fstat(file_fd.get(), &s) < 0) {
fprintf(stderr, "could not get size of file, err: %s\n", strerror(errno));
return ZX_ERR_IO;
}
zx_status_t status = zx::vmo::create(s.st_size, 0, &vmo);
if (status != ZX_OK) {
return status;
}
fbl::unique_ptr<unsigned char[]> buf(new unsigned char[BUFFER_SIZE]);
ssize_t res;
off_t total_read = 0;
while ((total_read < s.st_size) &&
((res = read(file_fd.get(), buf.get(), BUFFER_SIZE)) != 0)) {
if (res < 0) {
fprintf(stderr, "Fatal read error: %s\n", strerror(errno));
return ZX_ERR_IO;
}
zx_status_t status = vmo.write(buf.get(), total_read, res);
if (status != ZX_OK) {
return status;
}
total_read += res;
}
if (total_read != s.st_size) {
fprintf(stderr, "Read %jd bytes, want %jd\n", (intmax_t)total_read, (intmax_t)s.st_size);
return ZX_ERR_IO;
}
return ZX_OK;
}
int main(int argc, char** argv) {
if (argc != 2) {
printf("Usage: %s <firmware_image_path>\n", argv[0]);
return -1;
}
const char* filename = argv[1];
fbl::unique_fd file_fd(open(filename, O_RDONLY));
if (!file_fd) {
fprintf(stderr, "Failed to open \"%s\", err: %s\n", filename, strerror(errno));
return -1;
}
zx::vmo fw_vmo;
zx_status_t status = read_firmware(file_fd, fw_vmo);
if (status != ZX_OK) {
fprintf(stderr, "Failed to read firmware file, err: %d\n", status);
return -1;
}
fbl::unique_fd fd;
status = open_fwloader_dev(fd);
if (status != ZX_OK) {
// Check if there is a usb tester device we can switch to firmware loading mode.
status = open_usb_tester_dev(fd);
if (status != ZX_OK) {
fprintf(stderr, "No usb test fwloader or tester device found, err: %d\n", status);
return -1;
}
printf("Switching usb tester device to fwloader mode\n");
ssize_t res = ioctl_usb_tester_set_mode_fwloader(fd.get());
if (res != 0) {
fprintf(stderr, "Failed to switch usb test device to fwloader mode, err: %zd\n", res);
return -1;
}
status = wait_dev_enumerate(DEV_FX3_DIR, fd);
if (status != ZX_OK) {
fprintf(stderr, "Failed to wait for fwloader to re-enumerate, err: %d\n", status);
return -1;
}
}
zx_handle_t handle = fw_vmo.release();
ssize_t res = ioctl_usb_test_fwloader_load_firmware(fd.get(), &handle);
if (res < ZX_OK) {
fprintf(stderr, "Failed to load firmware, err: %zd\n", res);
return -1;
}
status = wait_dev_enumerate(DEV_USB_TESTER_DIR, fd);
if (status != ZX_OK) {
fprintf(stderr, "Failed to wait for updated usb tester to enumerate, err: %d\n", status);
return -1;
}
usb_device_descriptor_t device_desc;
res = ioctl_usb_get_device_desc(fd.get(), &device_desc);
if (res != sizeof(device_desc)) {
printf("Failed to get updated usb tester device descriptor, err: %zd\n", res);
return -1;
}
printf("Updated usb tester firmware to v%x.%x\n",
MSB(device_desc.bcdDevice), LSB(device_desc.bcdDevice));
return 0;
}