WIP on replay-nand
Change-Id: I90ae11fedc7bc6d3fa39df43554fbdc093fc17c0
diff --git a/sdk/fidl/fuchsia.paver/paver.fidl b/sdk/fidl/fuchsia.paver/paver.fidl
index 2de0909..c5c8e9f 100644
--- a/sdk/fidl/fuchsia.paver/paver.fidl
+++ b/sdk/fidl/fuchsia.paver/paver.fidl
@@ -192,6 +192,13 @@
status zx.status;
});
+ /// Writes FVM with raw nand data streamed via `payload`.
+ WriteRawVolumes(resource struct {
+ payload client_end:PayloadStream;
+ }) -> (struct {
+ status zx.status;
+ });
+
// TODO(fxbug.dev/45606): transition users to `WriteFirmware()` and delete this.
/// Writes bootloader partition with data from `payload`.
///
diff --git a/src/bringup/bin/netsvc/paver.h b/src/bringup/bin/netsvc/paver.h
index fd1de67..a86d1f5 100644
--- a/src/bringup/bin/netsvc/paver.h
+++ b/src/bringup/bin/netsvc/paver.h
@@ -70,6 +70,7 @@
kFirmware,
kDataFile,
kFvm,
+ kFvmRaw,
kInitPartitionTables,
kWipePartitionTables,
};
diff --git a/src/storage/bin/disk-pave/disk-pave.cc b/src/storage/bin/disk-pave/disk-pave.cc
index c4a482b..32dc16e 100644
--- a/src/storage/bin/disk-pave/disk-pave.cc
+++ b/src/storage/bin/disk-pave/disk-pave.cc
@@ -46,6 +46,7 @@
ERROR(" install-vbmetab : Install a VBMETA-B partition to the device\n");
ERROR(" install-vbmetar : Install a VBMETA-R partition to the device\n");
ERROR(" install-fvm : Install a sparse FVM to the device\n");
+ ERROR(" install-raw-fvm : Install a raw FVM (from NAND dump) to the device\n");
ERROR(" install-data-file : Install a file to DATA (--path required)\n");
ERROR(" wipe : Remove the FVM partition\n");
ERROR(" init-partition-tables : Initialize block device with valid GPT and FVM\n");
@@ -69,6 +70,7 @@
kBootloader,
kDataFile,
kFvm,
+ kFvmRaw,
};
struct Flags {
@@ -131,6 +133,8 @@
flags->cmd = Command::kDataFile;
} else if (!strcmp(argv[0], "install-fvm")) {
flags->cmd = Command::kFvm;
+ } else if (!strcmp(argv[0], "install-raw-fvm")) {
+ flags->cmd = Command::kFvmRaw;
} else if (!strcmp(argv[0], "wipe")) {
flags->cmd = Command::kWipe;
} else if (!strcmp(argv[0], "init-partition-tables")) {
@@ -297,6 +301,36 @@
return ZX_OK;
}
+ case Command::kFvmRaw: {
+ auto data_sink = fidl::CreateEndpoints<fuchsia_paver::DataSink>();
+ if (data_sink.is_error()) {
+ ERROR("Unable to create channels.\n");
+ return data_sink.status_value();
+ }
+ auto [data_sink_local, data_sink_remote] = std::move(*data_sink);
+ paver_client->FindDataSink(std::move(data_sink_remote));
+
+ auto streamer_endpoints = fidl::CreateEndpoints<fuchsia_paver::PayloadStream>();
+ if (streamer_endpoints.is_error()) {
+ return streamer_endpoints.status_value();
+ }
+ auto [client, server] = std::move(*streamer_endpoints);
+
+ // Launch thread which implements interface.
+ async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
+ disk_pave::PayloadStreamer streamer(std::move(server), std::move(flags.payload_fd));
+ loop.StartThread("payload-stream");
+
+ auto result =
+ fidl::BindSyncClient(std::move(data_sink_local))->WriteRawVolumes(std::move(client));
+ zx_status_t status = result.ok() ? result.value().status : result.status();
+ if (status != ZX_OK) {
+ ERROR("Failed to write volumes: %s\n", zx_status_get_string(status));
+ return status;
+ }
+
+ return ZX_OK;
+ }
case Command::kWipe: {
auto data_sink = fidl::CreateEndpoints<fuchsia_paver::DataSink>();
if (data_sink.is_error()) {
diff --git a/src/storage/lib/paver/paver.cc b/src/storage/lib/paver/paver.cc
index f828c7d..308d4c7 100644
--- a/src/storage/lib/paver/paver.cc
+++ b/src/storage/lib/paver/paver.cc
@@ -179,6 +179,26 @@
return zx::ok();
}
+zx::status<> FvmReplay(const fbl::unique_fd& devfs_root, const DevicePartitioner& partitioner,
+ std::unique_ptr<fvm::ReaderInterface> payload) {
+ LOG("Replaying FVM partition.\n");
+ if (!partitioner.IsFvmWithinFtl()) {
+ ERROR("Failed to replay FVM: this action can only be performed on devices where the FVM "
+ "resides within the FTL.\n");
+ return zx::error(ZX_ERR_NOT_SUPPORTED);
+ }
+
+ ERROR("Replaying FVM partition not implemented yet\n");
+ return zx::error(ZX_ERR_NOT_SUPPORTED);
+ // TODO: open the block device
+ // sys/platform/05:00:f/aml-raw_nand/nand/fvm
+ // nandpart root = "<devfs_root>/sys/platform/05:00:f/aml-raw_nand/nand/"
+ // nandpart child is "fvm"
+ // TODO: openat(devfs_root.get(), "sys/platform/05:00:f/aml-raw_nand/nand/fvm");
+ // TODO: issue the nandpart client commands here
+ // return zx::ok();
+}
+
// Formats the FVM partition and returns a channel to the new volume.
zx::status<zx::channel> FormatFvm(const fbl::unique_fd& devfs_root,
const DevicePartitioner& partitioner) {
@@ -626,6 +646,15 @@
return FvmPave(devfs_root_, *partitioner_, std::move(status.value()));
}
+zx::status<> DataSinkImpl::WriteRawVolumes(zx::channel payload_stream) {
+ auto status = StreamReader::Create(std::move(payload_stream));
+ if (status.is_error()) {
+ ERROR("Unable to create stream.\n");
+ return status.take_error();
+ }
+ return FvmReplay(devfs_root_, *partitioner_, std::move(status.value()));
+}
+
// Deprecated in favor of WriteFirmware().
// TODO(fxbug.dev/45606): move clients off this function and delete it.
zx::status<> DataSinkImpl::WriteBootloader(fuchsia_mem::wire::Buffer payload) {
diff --git a/src/storage/lib/paver/paver.h b/src/storage/lib/paver/paver.h
index e9de732..8832d72 100644
--- a/src/storage/lib/paver/paver.h
+++ b/src/storage/lib/paver/paver.h
@@ -84,6 +84,8 @@
zx::status<> WriteVolumes(zx::channel payload_stream);
+ zx::status<> WriteRawVolumes(zx::channel payload_stream);
+
zx::status<> WriteBootloader(fuchsia_mem::wire::Buffer payload);
zx::status<> WriteDataFile(fidl::StringView filename, fuchsia_mem::wire::Buffer payload);
@@ -125,6 +127,11 @@
completer.Reply(sink_.WriteVolumes(request->payload.TakeChannel()).status_value());
}
+ void WriteRawVolumes(WriteRawVolumesRequestView request,
+ WriteRawVolumesCompleter::Sync& completer) override {
+ completer.Reply(sink_.WriteRawVolumes(request->payload.TakeChannel()).status_value());
+ }
+
void WriteBootloader(WriteBootloaderRequestView request,
WriteBootloaderCompleter::Sync& completer) override {
completer.Reply(sink_.WriteBootloader(std::move(request->payload)).status_value());
@@ -177,6 +184,11 @@
completer.Reply(sink_.WriteVolumes(request->payload.TakeChannel()).status_value());
}
+ void WriteRawVolumes(WriteRawVolumesRequestView request,
+ WriteRawVolumesCompleter::Sync& completer) override {
+ completer.Reply(sink_.WriteRawVolumes(request->payload.TakeChannel()).status_value());
+ }
+
void WriteBootloader(WriteBootloaderRequestView request,
WriteBootloaderCompleter::Sync& completer) override {
completer.Reply(sink_.WriteBootloader(std::move(request->payload)).status_value());
diff --git a/tools/bootserver_old/BUILD.gn b/tools/bootserver_old/BUILD.gn
index c0d4e6c..04bd39d 100644
--- a/tools/bootserver_old/BUILD.gn
+++ b/tools/bootserver_old/BUILD.gn
@@ -18,6 +18,19 @@
]
}
+executable("replay-nand") {
+ sources = [
+ "replay-nand.c",
+ "netboot.c",
+ "tftp.c",
+ ]
+ deps = [ "//zircon/system/ulib/tftp" ]
+ configs += [
+ # TODO(fxbug.dev/58162): delete the below and fix compiler warnings
+ "//build/config:Wno-conversion",
+ ]
+}
+
sdk_host_tool("bootserver_sdk") {
category = "partner"
output_name = "bootserver"
@@ -25,6 +38,12 @@
}
install_host_tools("host") {
- deps = [ ":bootserver" ]
- outputs = [ "bootserver" ]
+ deps = [
+ ":bootserver",
+ ":replay-nand",
+ ]
+ outputs = [
+ "bootserver",
+ "replay-nand",
+ ]
}
diff --git a/tools/bootserver_old/replay-nand.c b/tools/bootserver_old/replay-nand.c
new file mode 100644
index 0000000..c0b5627
--- /dev/null
+++ b/tools/bootserver_old/replay-nand.c
@@ -0,0 +1,699 @@
+// Copyright 2016 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.
+
+#define _POSIX_C_SOURCE 200809L
+#define _GNU_SOURCE
+#define _DARWIN_C_SOURCE
+
+#include "bootserver.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <limits.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+#include <zircon/boot/netboot.h>
+
+#define ANSI_RED "\x1b[31m"
+#define ANSI_GREEN "\x1b[32m"
+#define ANSI_YELLOW "\x1b[33m"
+#define ANSI_BLUE "\x1b[34m"
+#define ANSI_MAGENTA "\x1b[35m"
+#define ANSI_CYAN "\x1b[36m"
+#define ANSI_RESET "\x1b[0m"
+#define ANSI_CLEARLINE "\33[2K\r"
+
+#define ANSI(name) (use_color == false || is_redirected) ? "" : ANSI_##name
+
+#define log(args...) \
+ do { \
+ char logline[1024]; \
+ snprintf(logline, sizeof(logline), args); \
+ fprintf(stderr, "%s [%s] %s\n", date_string(), appname, logline); \
+ } while (false)
+
+#define RETRY_DELAY_SEC 1
+
+char* appname;
+int64_t us_between_packets = DEFAULT_US_BETWEEN_PACKETS;
+
+static bool use_color = true;
+static size_t total_file_size;
+static bool file_info_printed;
+static int progress_reported;
+static int packets_sent;
+static char filename_in_flight[PATH_MAX];
+static struct timeval start_time, end_time;
+static bool is_redirected;
+static const char spinner[] = {'|', '/', '-', '\\'};
+static bool no_bind = false;
+static bool reuseport = false;
+
+char* date_string(void) {
+ static char date_buf[80];
+ time_t t = time(NULL);
+ struct tm tm = *localtime(&t);
+
+ snprintf(date_buf, sizeof(date_buf), "%4d-%02d-%02d %02d:%02d:%02d", tm.tm_year + 1900,
+ tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+ return date_buf;
+}
+
+char* sockaddr_str(struct sockaddr_in6* addr) {
+ static char buf[128];
+ char tmp[INET6_ADDRSTRLEN];
+ snprintf(buf, sizeof(buf), "[%s]:%d",
+ inet_ntop(AF_INET6, &addr->sin6_addr, tmp, INET6_ADDRSTRLEN), ntohs(addr->sin6_port));
+ return buf;
+}
+
+void initialize_status(const char* name, size_t size) {
+ total_file_size = size;
+ progress_reported = 0;
+ packets_sent = 0;
+ snprintf(filename_in_flight, sizeof(filename_in_flight), "%s", name);
+}
+
+void update_status(size_t bytes_so_far) {
+ char progress_str[PATH_MAX];
+ size_t offset = 0;
+
+#define UPDATE_LOG(args...) \
+ do { \
+ if (offset < PATH_MAX) { \
+ offset += snprintf(progress_str + offset, PATH_MAX - offset, args); \
+ } \
+ } while (false)
+
+ packets_sent++;
+
+ bool is_last_piece = (bytes_so_far == total_file_size);
+ if (total_file_size == 0) {
+ return;
+ }
+
+ if (is_redirected) {
+ int percent_sent = (bytes_so_far * 100 / (total_file_size));
+ if (percent_sent - progress_reported >= 5) {
+ fprintf(stderr, "\t%d%%...", percent_sent);
+ progress_reported = percent_sent;
+ }
+ } else {
+ if (packets_sent > 1024 || is_last_piece) {
+ packets_sent = 0;
+ static int spin = 0;
+
+ size_t divider = (total_file_size > 0) ? total_file_size : 1;
+ UPDATE_LOG("[%c] %5.01f%% of ", spinner[(spin++) % 4],
+ 100.0 * (float)bytes_so_far / (float)divider);
+ if (total_file_size < 1024) {
+ UPDATE_LOG(" %3zu.0 B", total_file_size);
+ } else if (total_file_size < 1024 * 1024) {
+ UPDATE_LOG(" %5.1f KB", (float)total_file_size / 1024.0);
+ } else if (total_file_size < 1024 * 1024 * 1024) {
+ UPDATE_LOG(" %5.1f MB", (float)total_file_size / 1024.0 / 1024.0);
+ } else {
+ UPDATE_LOG(" %5.1f GB", (float)total_file_size / 1024.0 / 1024.0 / 1024.0);
+ }
+
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ int64_t sec = (int64_t)(now.tv_sec - start_time.tv_sec);
+ int64_t usec = (int64_t)(now.tv_usec - start_time.tv_usec);
+ int64_t elapsed_usec = sec * 1000000 + usec;
+ float bytes_in_sec;
+ bytes_in_sec = (float)bytes_so_far * 1000000 / ((float)elapsed_usec);
+ if (bytes_in_sec < 1024) {
+ UPDATE_LOG(" %5.1f B/s", bytes_in_sec);
+ } else if (bytes_in_sec < 1024 * 1024) {
+ UPDATE_LOG(" %5.1f KB/s", bytes_in_sec / 1024.0);
+ } else if (bytes_in_sec < 1024 * 1024 * 1024) {
+ UPDATE_LOG(" %5.1f MB/s", bytes_in_sec / 1024.0 / 1024.0);
+ } else {
+ UPDATE_LOG(" %5.1f GB/s", bytes_in_sec / 1024.0 / 1024.0 / 1024.0);
+ }
+
+ if (is_last_piece) {
+ UPDATE_LOG(".");
+ } else {
+ UPDATE_LOG(" ");
+ }
+
+ // Simplify the file path if from "//out/".
+ char* relative_path = strstr(filename_in_flight, "/out/");
+ if (!relative_path) {
+ UPDATE_LOG(" %s%s%s", ANSI(GREEN), filename_in_flight, ANSI(RESET));
+ } else {
+ // Path starting with "//" indicates the relative path wrt
+ // the base directory of Fuchsia source code.
+ UPDATE_LOG(" %s/%s%s", ANSI(GREEN), relative_path, ANSI(RESET));
+ }
+ fprintf(stderr, "%s%s", ANSI_CLEARLINE, progress_str);
+ }
+ }
+}
+
+static int xfer(struct sockaddr_in6* addr, const char* local_name, const char* remote_name) {
+ int result;
+ is_redirected = !isatty(fileno(stdout));
+ gettimeofday(&start_time, NULL);
+ file_info_printed = false;
+ bool first = true;
+ while ((result = tftp_xfer(addr, local_name, remote_name, true)) == -EAGAIN) {
+ if (first) {
+ fprintf(stderr, "Target busy, waiting.");
+ first = false;
+ } else {
+ fprintf(stderr, ".");
+ }
+ sleep(1);
+ gettimeofday(&start_time, NULL);
+ }
+ gettimeofday(&end_time, NULL);
+ if (end_time.tv_usec < start_time.tv_usec) {
+ end_time.tv_sec -= 1;
+ end_time.tv_usec += 1000000;
+ }
+ fprintf(stderr, "\n");
+ return result;
+}
+
+// Similar to xfer, but reads from remote to local.
+static int xfer2(struct sockaddr_in6* addr, const char* local_name, const char* remote_name) {
+ int result;
+ is_redirected = !isatty(fileno(stdout));
+ gettimeofday(&start_time, NULL);
+ file_info_printed = false;
+ bool first = true;
+ while ((result = tftp_xfer(addr, local_name, remote_name, false)) == -EAGAIN) {
+ if (first) {
+ fprintf(stderr, "Target busy, waiting.");
+ first = false;
+ } else {
+ fprintf(stderr, ".");
+ }
+ sleep(1);
+ gettimeofday(&start_time, NULL);
+ }
+ gettimeofday(&end_time, NULL);
+ if (end_time.tv_usec < start_time.tv_usec) {
+ end_time.tv_sec -= 1;
+ end_time.tv_usec += 1000000;
+ }
+ fprintf(stderr, "\n");
+ return result;
+}
+
+void usage(void) {
+ fprintf(
+ stderr,
+ "usage: %s [ <option> ]* [<zbi>] -- [ <kerneloption> ]* ]\n"
+ "\n"
+ "options:\n"
+ " -a only boot device with this IPv6 address\n"
+ " -b <sz> tftp block size (default=%d, ignored with --netboot)\n"
+ " -i <NN> number of microseconds between packets\n"
+ " set between 50-500 to deal with poor bootloader network stacks (default=%d)\n"
+ " (ignored with --tftp)\n"
+ " -n only boot device with this nodename\n"
+ " -w <sz> tftp window size (default=%d, ignored with --netboot)\n"
+ //" --board_name <name> name of the board files are meant for\n"
+ " --fvm <file> use the supplied file as a raw NAND image\n"
+ " --fail-fast exit on first error\n"
+ " --nocolor disable ANSI color (false)\n"
+ " --allow-zedboot-version-mismatch warn on zedboot version mismatch rather than fail\n"
+ " --fail-fast-if-version-mismatch error if zedboot version does not match\n"
+ " --no-bind do not bind to bootserver port. Should be used with -a <IPV6>\n"
+ " --reuseport allow other programs to bind the listen port\n",
+ appname, DEFAULT_TFTP_BLOCK_SZ, DEFAULT_US_BETWEEN_PACKETS, DEFAULT_TFTP_WIN_SZ);
+ exit(1);
+}
+
+void drain(int fd) {
+ char buf[4096];
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == 0) {
+ while (read(fd, buf, sizeof(buf)) > 0)
+ ;
+ fcntl(fd, F_SETFL, 0);
+ }
+}
+
+int send_boot_command(struct sockaddr_in6* ra) {
+ // Construct message
+ nbmsg msg;
+ static int cookie = 0;
+ msg.magic = NB_MAGIC;
+ msg.cookie = cookie++;
+ msg.cmd = NB_BOOT;
+ msg.arg = 0;
+
+ // Send to NB_SERVER_PORT
+ struct sockaddr_in6 target_addr;
+ memcpy(&target_addr, ra, sizeof(struct sockaddr_in6));
+ target_addr.sin6_port = htons(NB_SERVER_PORT);
+ int s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (s < 0) {
+ log("cannot create socket %d", s);
+ return -1;
+ }
+ ssize_t send_result =
+ sendto(s, &msg, sizeof(msg), 0, (struct sockaddr*)&target_addr, sizeof(target_addr));
+ if (send_result == sizeof(msg)) {
+ close(s);
+ log("Issued boot command to %s\n\n", sockaddr_str(ra));
+ return 0;
+ }
+ close(s);
+ log("failure sending boot command to %s", sockaddr_str(ra));
+ return -1;
+}
+
+int send_reboot_command(struct sockaddr_in6* ra) {
+ // Construct message
+ nbmsg msg;
+ static int cookie = 0;
+ msg.magic = NB_MAGIC;
+ msg.cookie = cookie++;
+ msg.cmd = NB_REBOOT;
+ msg.arg = 0;
+
+ // Send to NB_SERVER_PORT
+ struct sockaddr_in6 target_addr;
+ memcpy(&target_addr, ra, sizeof(struct sockaddr_in6));
+ target_addr.sin6_port = htons(NB_SERVER_PORT);
+ int s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (s < 0) {
+ log("cannot create socket %d", s);
+ return -1;
+ }
+ ssize_t send_result =
+ sendto(s, &msg, sizeof(msg), 0, (struct sockaddr*)&target_addr, sizeof(target_addr));
+ if (send_result == sizeof(msg)) {
+ close(s);
+ log("Issued reboot command to %s\n\n", sockaddr_str(ra));
+ return 0;
+ }
+ close(s);
+ log("failure sending reboot command to %s", sockaddr_str(ra));
+ return -1;
+}
+
+static int validate_board_name(const char* board_name, const char* board_info_file) {
+ chmod(board_info_file, S_IRWXU);
+ int fd = open(board_info_file, O_RDONLY);
+ if (fd < 0) {
+ log("Unable to read the board info file [%s]", board_info_file);
+ return -1;
+ }
+
+ board_info_t board_info = {};
+ if (read(fd, &board_info, sizeof(board_info)) < (ssize_t)sizeof(board_info)) {
+ log("Unable to read the board info file [%s]", board_info_file);
+ goto err;
+ }
+ if (strncmp(board_info.board_name, board_name, sizeof(board_info.board_name)) != 0) {
+ log("Expected target to be [%s], but found target is [%s]\n", board_name,
+ board_info.board_name);
+ log("Confirm that your `fx set` matches the target's board.");
+ goto err;
+ }
+
+ return 0;
+err:
+ close(fd);
+ return -1;
+}
+
+int main(int argc, char** argv) {
+ bool fail_fast = false;
+ bool fail_fast_if_version_mismatch = false;
+ struct in6_addr allowed_addr;
+ int32_t allowed_scope_id = -1;
+ struct sockaddr_in6 addr;
+ char tmp[INET6_ADDRSTRLEN];
+ char* nodename = NULL;
+ int sock = 1;
+ const char* tmpdir = getenv("TMPDIR");
+ char board_info_template[] = "%s/board_info.XXXXXX";
+ char board_info_file[PATH_MAX];
+ const char* board_name = NULL;
+ const char* fvm_image = NULL;
+ bool allow_zedboot_version_mismatch = false;
+ int status;
+
+ if (tmpdir == NULL) {
+ tmpdir = "/tmp";
+ }
+
+ memset(&allowed_addr, 0, sizeof(allowed_addr));
+ if ((appname = strrchr(argv[0], '/')) != NULL) {
+ appname++;
+ } else {
+ appname = argv[0];
+ }
+
+ while (argc > 1) {
+ if (argv[1][0] != '-') {
+ usage();
+ } else if (!strcmp(argv[1], "--fvm")) {
+ argc--;
+ argv++;
+ if (argc <= 1) {
+ fprintf(stderr, "'--fvm' option requires an argument (raw NAND image)\n");
+ return -1;
+ }
+ if (fvm_image != NULL) {
+ fprintf(stderr, "'--fvm' supplied too many times\n");
+ return -1;
+ }
+ fvm_image = argv[1];
+ } else if (!strcmp(argv[1], "--fail-fast")) {
+ fail_fast = true;
+ } else if (!strcmp(argv[1], "--fail-fast-if-version-mismatch")) {
+ fail_fast_if_version_mismatch = true;
+ } else if (!strcmp(argv[1], "-b")) {
+ argc--;
+ argv++;
+ if (argc <= 1) {
+ fprintf(stderr, "'-b' option requires an argument (tftp block size)\n");
+ return -1;
+ }
+ errno = 0;
+ static uint16_t block_size;
+ block_size = strtoll(argv[1], NULL, 10);
+ if (errno != 0 || block_size <= 0) {
+ fprintf(stderr, "invalid arg for -b: %s\n", argv[1]);
+ return -1;
+ }
+ tftp_block_size = &block_size;
+ } else if (!strcmp(argv[1], "-w")) {
+ argc--;
+ argv++;
+ if (argc <= 1) {
+ fprintf(stderr, "'-w' option requires an argument (tftp window size)\n");
+ return -1;
+ }
+ errno = 0;
+ static uint16_t window_size;
+ window_size = strtoll(argv[1], NULL, 10);
+ if (errno != 0 || window_size <= 0) {
+ fprintf(stderr, "invalid arg for -w: %s\n", argv[1]);
+ return -1;
+ }
+ tftp_window_size = &window_size;
+ } else if (!strcmp(argv[1], "-i")) {
+ argc--;
+ argv++;
+ if (argc <= 1) {
+ fprintf(stderr, "'-i' option requires an argument (micros between packets)\n");
+ return -1;
+ }
+ errno = 0;
+ us_between_packets = strtoll(argv[1], NULL, 10);
+ if (errno != 0 || us_between_packets <= 0) {
+ fprintf(stderr, "invalid arg for -i: %s\n", argv[1]);
+ return -1;
+ }
+ fprintf(stderr, "packet spacing set to %" PRId64 " microseconds\n", us_between_packets);
+ } else if (!strcmp(argv[1], "-a")) {
+ argc--;
+ argv++;
+ if (argc <= 1) {
+ fprintf(stderr, "'-a' option requires a valid ipv6 address\n");
+ return -1;
+ }
+
+ char* token = strchr(argv[1], '/');
+ if (token) {
+ allowed_scope_id = atoi(token + 1);
+ char temp_ifname[IF_NAMESIZE] = "";
+ if (!token[1] || if_indextoname(allowed_scope_id, temp_ifname) == NULL) {
+ fprintf(stderr, "%s: invalid interface specified\n", argv[1]);
+ return -1;
+ }
+ argv[1][token - argv[1]] = '\0';
+ }
+
+ if (inet_pton(AF_INET6, argv[1], &allowed_addr) != 1) {
+ fprintf(stderr, "%s: invalid ipv6 address specified\n", argv[1]);
+ return -1;
+ }
+ } else if (!strcmp(argv[1], "-n")) {
+ argc--;
+ argv++;
+ if (argc <= 1) {
+ fprintf(stderr, "'-n' option requires a valid nodename\n");
+ return -1;
+ }
+ nodename = argv[1];
+ } else if (!strcmp(argv[1], "--nocolor")) {
+ use_color = false;
+ } else if (!strcmp(argv[1], "--board_name")) {
+ argc--;
+ argv++;
+ if (argc <= 1) {
+ fprintf(stderr, "'--board_name' option requires a valid board name\n");
+ return -1;
+ }
+ board_name = argv[1];
+ } else if (!strcmp(argv[1], "--allow-zedboot-version-mismatch")) {
+ allow_zedboot_version_mismatch = true;
+ } else if (!strcmp(argv[1], "--no-bind")) {
+ no_bind = true;
+ } else if (!strcmp(argv[1], "--reuseport")) {
+ reuseport = true;
+ } else {
+ usage();
+ }
+ argc--;
+ argv++;
+ }
+ if (!fvm_image) {
+ usage();
+ }
+ if (!nodename) {
+ nodename = getenv("ZIRCON_NODENAME");
+ }
+ if (nodename) {
+ fprintf(stderr, "[%s] Will only boot nodename '%s'\n", appname, nodename);
+ }
+
+ if (board_name) {
+ log("Board name set to [%s]", board_name);
+ }
+
+ sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0) {
+ log("cannot create socket %d", sock);
+ return -1;
+ }
+
+ if (!IN6_IS_ADDR_UNSPECIFIED(&allowed_addr) || nodename || reuseport) {
+ setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof 1);
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof 1);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ if (no_bind) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&allowed_addr)) {
+ log("need to specify ipv6 address using -a for --no-bind");
+ close(sock);
+ return -1;
+ }
+ if (allowed_scope_id == -1) {
+ log("need to specify interface number in -a for --no-bind.");
+ log("Ex: -a fe80::5054:ff:fe12:3456/4 \nHint: use netls to get the address");
+ close(sock);
+ return -1;
+ }
+ memcpy(&addr.sin6_addr, &allowed_addr, sizeof(struct in6_addr));
+ addr.sin6_port = htons(NB_SERVER_PORT);
+ addr.sin6_scope_id = allowed_scope_id;
+ log("Sending request to %s", sockaddr_str(&addr));
+ } else {
+ addr.sin6_port = htons(NB_ADVERT_PORT);
+ if (bind(sock, (void*)&addr, sizeof(addr)) < 0) {
+ log("cannot bind to %s %d: %s\nthere may be another bootserver running\n",
+ sockaddr_str(&addr), errno, strerror(errno));
+ close(sock);
+ return -1;
+ }
+ log("listening on %s", sockaddr_str(&addr));
+ }
+
+ for (;;) {
+ struct sockaddr_in6 ra;
+ socklen_t rlen;
+ char buf[4096];
+ nbmsg* msg = (void*)buf;
+ rlen = sizeof(ra);
+
+ if (no_bind) {
+ // Send request to device to get the advertisement instead of waiting for the
+ // broadcasted advertisement.
+ msg->magic = NB_MAGIC;
+ msg->cmd = NB_GET_ADVERT;
+
+ ssize_t send_result =
+ sendto(sock, buf, sizeof(nbmsg), 0, (struct sockaddr*)&addr, sizeof(addr));
+ if (send_result != sizeof(nbmsg)) {
+ if (fail_fast) {
+ close(sock);
+ return -1;
+ }
+ sleep(RETRY_DELAY_SEC);
+ continue;
+ }
+
+ // Ensure that response is received.
+ struct pollfd read_fd[1];
+ read_fd[0].fd = sock;
+ read_fd[0].events = POLLIN;
+ int ret = poll(read_fd, 1, 1000);
+ if (ret < 0 || !(read_fd[0].revents & POLLIN)) {
+ // No response received. Resend request after delay.
+ if (fail_fast) {
+ close(sock);
+ return -1;
+ }
+ sleep(RETRY_DELAY_SEC);
+ continue;
+ }
+ }
+
+ ssize_t r = recvfrom(sock, buf, sizeof(buf) - 1, 0, (void*)&ra, &rlen);
+ if (r < 0) {
+ log("socket read error %s", strerror(errno));
+ close(sock);
+ return -1;
+ }
+ if ((size_t)r < sizeof(nbmsg)) {
+ continue;
+ }
+ if (!IN6_IS_ADDR_LINKLOCAL(&ra.sin6_addr)) {
+ log("ignoring non-link-local message");
+ continue;
+ }
+ if (!IN6_IS_ADDR_UNSPECIFIED(&allowed_addr) &&
+ !IN6_ARE_ADDR_EQUAL(&allowed_addr, &ra.sin6_addr)) {
+ log("ignoring message not from allowed address '%s'",
+ inet_ntop(AF_INET6, &allowed_addr, tmp, sizeof(tmp)));
+ continue;
+ }
+ if (msg->magic != NB_MAGIC)
+ continue;
+ if (msg->cmd != NB_ADVERTISE)
+ continue;
+ if (msg->arg < NB_VERSION_1_3) {
+ log("%sIncompatible version 0x%08X of bootloader "
+ "detected from %s, please upgrade your bootloader%s",
+ ANSI(RED), msg->arg, sockaddr_str(&ra), ANSI(RESET));
+ if (fail_fast) {
+ close(sock);
+ return -1;
+ }
+ continue;
+ }
+
+ log("Received request from %s", sockaddr_str(&ra));
+
+ // ensure any payload is null-terminated
+ buf[r] = 0;
+
+ char* save = NULL;
+ char* adv_nodename = NULL;
+ const char* adv_version = "unknown";
+ for (char* var = strtok_r((char*)msg->data, ";", &save); var;
+ var = strtok_r(NULL, ";", &save)) {
+ if (!strncmp(var, "nodename=", 9)) {
+ adv_nodename = var + 9;
+ } else if (!strncmp(var, "version=", 8)) {
+ adv_version = var + 8;
+ }
+ }
+
+ if (nodename) {
+ if (adv_nodename == NULL) {
+ log("ignoring unknown nodename (expecting %s)", nodename);
+ } else if (strcmp(adv_nodename, nodename)) {
+ log("ignoring nodename %s (expecting %s)", adv_nodename, nodename);
+ continue;
+ }
+ }
+
+ if (strcmp(BOOTLOADER_VERSION, adv_version)) {
+ if (allow_zedboot_version_mismatch) {
+ log("%sWARNING: Bootserver version '%s' != remote Zedboot version '%s'."
+ " Paving may fail.%s",
+ ANSI(RED), BOOTLOADER_VERSION, adv_version, ANSI(RESET));
+ } else {
+ log("%sWARNING: Bootserver version '%s' != remote Zedboot version '%s'."
+ " Device will not be serviced. Please upgrade Zedboot.%s",
+ ANSI(RED), BOOTLOADER_VERSION, adv_version, ANSI(RESET));
+ if (fail_fast || fail_fast_if_version_mismatch) {
+ close(sock);
+ return -1;
+ }
+ continue;
+ }
+ }
+
+ if (adv_nodename) {
+ log("Proceeding with nodename %s", adv_nodename);
+ }
+
+ log("Transfer starts");
+ status = 0;
+ // This needs to be first as it validates that the other images are
+ // correct.
+ if (status == 0 && board_name) {
+ snprintf(board_info_file, sizeof(board_info_file), board_info_template, tmpdir);
+ const char* tmpfile = mktemp(board_info_file);
+ status = xfer2(&ra, tmpfile, NB_BOARD_INFO_FILENAME);
+ if (status == 0) {
+ status = validate_board_name(board_name, tmpfile);
+ }
+ unlink(tmpfile);
+ }
+
+ if (status == 0 && fvm_image) {
+ status = xfer(&ra, fvm_image, NB_NAND_FVM_FILENAME);
+ }
+
+ if (status == 0) {
+ log("Transfer ends successfully.");
+ // Only reboot if we actually paved an image.
+ if (fvm_image) {
+ send_reboot_command(&ra);
+ }
+ close(sock);
+ return 0;
+ } else if (fail_fast) {
+ close(sock);
+ return -1;
+ } else {
+ log("Transfer ends incompletely.");
+ log("Wait for %u secs before retrying...\n\n", RETRY_DELAY_SEC);
+ sleep(RETRY_DELAY_SEC);
+ }
+ drain(sock);
+ }
+
+ close(sock);
+ return 0;
+}
diff --git a/zircon/public/sysroot/sdk/sysroot.api b/zircon/public/sysroot/sdk/sysroot.api
index 1ad22c6..7f9e0a1 100644
--- a/zircon/public/sysroot/sdk/sysroot.api
+++ b/zircon/public/sysroot/sdk/sysroot.api
@@ -187,7 +187,7 @@
"include/zircon/boot/driver-config.h": "c3dde151b634076ca9b946585e4bef7b",
"include/zircon/boot/image.h": "ea7d6b275e9f07eb7e068e7ef8ef29f9",
"include/zircon/boot/multiboot.h": "75c4ffa342d5ca0da1d261796e91a990",
- "include/zircon/boot/netboot.h": "e226f8e10be309db8273546862108f11",
+ "include/zircon/boot/netboot.h": "d4ac26513d84a9a9a272329ef7f685b2",
"include/zircon/boot/sysconfig.h": "b81d51a7e75a7117566005f41766f192",
"include/zircon/compiler.h": "1dd6601600b7c036d5247f5f5dc73b41",
"include/zircon/device/audio.h": "7e2afee52e24ad839a479e4cba9247a5",
diff --git a/zircon/system/public/zircon/boot/netboot.h b/zircon/system/public/zircon/boot/netboot.h
index 75b7792..2a475c4 100644
--- a/zircon/system/public/zircon/boot/netboot.h
+++ b/zircon/system/public/zircon/boot/netboot.h
@@ -63,6 +63,8 @@
#define NB_IMAGE_PREFIX "<<image>>"
#define NB_FVM_HOST_FILENAME "sparse.fvm"
#define NB_FVM_FILENAME NB_IMAGE_PREFIX NB_FVM_HOST_FILENAME
+#define NB_NAND_FVM_HOST_FILENAME "nanddump.fvm"
+#define NB_NAND_FVM_FILENAME NB_IMAGE_PREFIX NB_NAND_FVM_HOST_FILENAME
#define NB_BOOTLOADER_HOST_FILENAME "bootloader.img"
#define NB_BOOTLOADER_FILENAME NB_IMAGE_PREFIX NB_BOOTLOADER_HOST_FILENAME
// Firmware images are slightly different, as they have an optional type suffix: