1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <fbl/auto_call.h>
6#include <fbl/unique_fd.h>
7#include <fbl/unique_ptr.h>
8#include <lib/fdio/watcher.h>
9#include <lib/zx/vmo.h>
10#include <zircon/device/usb-device.h>
11#include <zircon/device/usb-test-fwloader.h>
12#include <zircon/device/usb-tester.h>
13#include <zircon/hw/usb.h>
14#include <zircon/types.h>
15
16#include <dirent.h>
17#include <errno.h>
18#include <fcntl.h>
19#include <stdio.h>
20#include <string.h>
21#include <sys/stat.h>
22
23static const char* const DEV_FX3_DIR = "/dev/class/usb-test-fwloader";
24static const char* const DEV_USB_TESTER_DIR = "/dev/class/usb-tester";
25
26static const int ENUMERATION_WAIT_SECS = 5;
27
28static constexpr uint32_t BUFFER_SIZE = 8 * 1024;
29
30static inline int MSB(int n) { return n >> 8; }
31static inline int LSB(int n) { return n & 0xFF; }
32
33zx_status_t watch_dir_cb(int dirfd, int event, const char* filename, void* cookie) {
34    if (event != WATCH_EVENT_ADD_FILE) {
35        return ZX_OK;
36    }
37    int fd = openat(dirfd, filename, O_RDWR);
38    if (fd < 0) {
39        return ZX_OK;
40    }
41    auto out_fd = reinterpret_cast<int*>(cookie);
42    *out_fd = fd;
43    return ZX_ERR_STOP;
44}
45
46// Waits for a device to enumerate and be added to the given directory.
47zx_status_t wait_dev_enumerate(const char* dir, fbl::unique_fd& out_fd) {
48    DIR* d = opendir(dir);
49    if (d == nullptr) {
50        fprintf(stderr, "Could not open dir: \"%s\"\n", dir);
51        return ZX_ERR_BAD_STATE;
52    }
53    auto close_dir = fbl::MakeAutoCall([&] { closedir(d); });
54    int fd = 0;
55    zx_status_t status = fdio_watch_directory(dirfd(d), watch_dir_cb,
56                                              zx_deadline_after(ZX_SEC(ENUMERATION_WAIT_SECS)),
57                                              reinterpret_cast<void*>(&fd));
58    if (status == ZX_ERR_STOP) {
59        out_fd.reset(fd);
60        return ZX_OK;
61    } else {
62        return status;
63    }
64}
65
66zx_status_t open_dev(const char* dir, fbl::unique_fd& out_fd) {
67    DIR* d = opendir(dir);
68    if (d == nullptr) {
69        fprintf(stderr, "Could not open dir: \"%s\"\n", dir);
70        return ZX_ERR_BAD_STATE;
71    }
72
73    struct dirent* de;
74    while ((de = readdir(d)) != nullptr) {
75        int fd = openat(dirfd(d), de->d_name, O_RDWR);
76        if (fd < 0) {
77            continue;
78        }
79        out_fd.reset(fd);
80        closedir(d);
81        return ZX_OK;
82    }
83    closedir(d);
84
85    return ZX_ERR_NOT_FOUND;
86}
87
88zx_status_t open_fwloader_dev(fbl::unique_fd& out_fd) {
89    return open_dev(DEV_FX3_DIR, out_fd);
90}
91
92zx_status_t open_usb_tester_dev(fbl::unique_fd& out_fd) {
93    return open_dev(DEV_USB_TESTER_DIR, out_fd);
94}
95// Reads the firmware file and populates the provided vmo with the contents.
96static zx_status_t read_firmware(fbl::unique_fd& file_fd, zx::vmo& vmo) {
97    struct stat s;
98    if (fstat(file_fd.get(), &s) < 0) {
99        fprintf(stderr, "could not get size of file, err: %s\n", strerror(errno));
100        return ZX_ERR_IO;
101    }
102    zx_status_t status = zx::vmo::create(s.st_size, 0, &vmo);
103    if (status != ZX_OK) {
104        return status;
105    }
106
107    fbl::unique_ptr<unsigned char[]> buf(new unsigned char[BUFFER_SIZE]);
108    ssize_t res;
109    off_t total_read = 0;
110    while ((total_read < s.st_size) &&
111           ((res = read(file_fd.get(), buf.get(), BUFFER_SIZE)) != 0)) {
112        if (res < 0) {
113            fprintf(stderr, "Fatal read error: %s\n", strerror(errno));
114            return ZX_ERR_IO;
115        }
116        zx_status_t status = vmo.write(buf.get(), total_read, res);
117        if (status != ZX_OK) {
118            return status;
119        }
120        total_read += res;
121    }
122    if (total_read != s.st_size) {
123        fprintf(stderr, "Read %jd bytes, want %jd\n", (intmax_t)total_read, (intmax_t)s.st_size);
124        return ZX_ERR_IO;
125    }
126    return ZX_OK;
127}
128
129int main(int argc, char** argv) {
130    if (argc != 2) {
131        printf("Usage: %s <firmware_image_path>\n", argv[0]);
132        return -1;
133    }
134    const char* filename = argv[1];
135    fbl::unique_fd file_fd(open(filename, O_RDONLY));
136    if (!file_fd) {
137        fprintf(stderr, "Failed to open \"%s\", err: %s\n", filename, strerror(errno));
138        return -1;
139    }
140    zx::vmo fw_vmo;
141    zx_status_t status = read_firmware(file_fd, fw_vmo);
142    if (status != ZX_OK) {
143        fprintf(stderr, "Failed to read firmware file, err: %d\n", status);
144        return -1;
145    }
146    fbl::unique_fd fd;
147    status = open_fwloader_dev(fd);
148    if (status != ZX_OK) {
149        // Check if there is a usb tester device we can switch to firmware loading mode.
150        status = open_usb_tester_dev(fd);
151        if (status != ZX_OK) {
152            fprintf(stderr, "No usb test fwloader or tester device found, err: %d\n", status);
153            return -1;
154        }
155        printf("Switching usb tester device to fwloader mode\n");
156        ssize_t res = ioctl_usb_tester_set_mode_fwloader(fd.get());
157        if (res != 0) {
158            fprintf(stderr, "Failed to switch usb test device to fwloader mode, err: %zd\n", res);
159            return -1;
160        }
161        status = wait_dev_enumerate(DEV_FX3_DIR, fd);
162        if (status != ZX_OK) {
163            fprintf(stderr, "Failed to wait for fwloader to re-enumerate, err: %d\n", status);
164            return -1;
165        }
166    }
167    zx_handle_t handle = fw_vmo.release();
168    ssize_t res = ioctl_usb_test_fwloader_load_firmware(fd.get(), &handle);
169    if (res < ZX_OK) {
170        fprintf(stderr, "Failed to load firmware, err: %zd\n", res);
171        return -1;
172    }
173    status = wait_dev_enumerate(DEV_USB_TESTER_DIR, fd);
174    if (status != ZX_OK) {
175        fprintf(stderr, "Failed to wait for updated usb tester to enumerate, err: %d\n", status);
176        return -1;
177    }
178    usb_device_descriptor_t device_desc;
179    res = ioctl_usb_get_device_desc(fd.get(), &device_desc);
180    if (res != sizeof(device_desc)) {
181        printf("Failed to get updated usb tester device descriptor, err: %zd\n", res);
182        return -1;
183    }
184    printf("Updated usb tester firmware to v%x.%x\n",
185           MSB(device_desc.bcdDevice), LSB(device_desc.bcdDevice));
186    return 0;
187}
188