gigaboot: Add a gigaboot protocol implementation.
This is functionally based on the protocol implementation in the gigaboot
bootloader, but redone to match depthcharge's style and to fit with its
infrastructure.
Change-Id: I78d1690e6046664da96a51f8b3b5e61f315adb0e
diff --git a/src/net/Makefile.inc b/src/net/Makefile.inc
index cbb4861..95143a9 100644
--- a/src/net/Makefile.inc
+++ b/src/net/Makefile.inc
@@ -15,6 +15,6 @@
## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
##
-subdirs-y += netboot ipv4 ipv6
+subdirs-y += gigaboot netboot ipv4 ipv6
net-y += net.c
diff --git a/src/net/gigaboot/Makefile.inc b/src/net/gigaboot/Makefile.inc
new file mode 100644
index 0000000..6de008d
--- /dev/null
+++ b/src/net/gigaboot/Makefile.inc
@@ -0,0 +1,18 @@
+##
+## Copyright 2013 Google Inc.
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; version 2 of the License.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+##
+
+netboot-y += gigaboot.c
diff --git a/src/net/gigaboot/gigaboot.c b/src/net/gigaboot/gigaboot.c
new file mode 100644
index 0000000..af04246
--- /dev/null
+++ b/src/net/gigaboot/gigaboot.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/xalloc.h"
+#include "base/time.h"
+#include "boot/boot.h"
+#include "drivers/net/net.h"
+#include "net/connection.h"
+#include "net/gigaboot/gigaboot.h"
+#include "net/ipv6/ipv6.h"
+
+enum {
+ GigabootMagic = 0xAA774217,
+ GigabootAdvertise = 0x77777777,
+};
+
+enum {
+ GigabootError = 0x80000000,
+ GigabootErrorBadCmd = 0x80000001,
+ GigabootErrorBadParam = 0x80000002,
+ GigabootErrorTooLarge = 0x80000003,
+ GigabootErrorBadFile = 0x80000004,
+};
+
+enum {
+ GigabootVersion_1_0 = 0x0001000,
+};
+
+enum {
+ // Used to acknowledge receipt of a command.
+ GigabootAck = 0,
+ // Doesn't do anything.
+ GigabootCommandCommand = 1,
+ // Start receiving a file.
+ GigabootCommandSendFile = 2,
+ // Receive some data for the file started with SendFile.
+ GigabootCommandData = 3,
+ // Boot using the data received so far.
+ GigabootCommandBoot = 4,
+};
+
+const char gigaboot_buffer_kernel[] = "kernel.bin";
+const char gigaboot_buffer_cmdline[] = "cmdline";
+const char gigaboot_buffer_ramdisk[] = "ramdisk.bin";
+
+typedef struct {
+ uint32_t magic;
+ uint32_t cookie;
+ uint32_t command;
+ uint32_t arg;
+} GigabootHeader;
+
+typedef struct {
+ GigabootHeader hdr;
+ uint8_t data[0];
+} GigabootMessage;
+
+typedef struct {
+ NetConOps *con;
+
+ GigabootHeader last;
+ GigabootHeader ack;
+
+ GigabootBuffer *buffer;
+
+ GigabootMessage *message;
+ size_t message_len;
+} GigabootState;
+
+
+
+/*
+ * Receive messages and act on commands.
+ */
+
+static void gigaboot_recv_message(GigabootState *state, GigabootMessage *msg,
+ size_t size)
+{
+ if (state->last.cookie == msg->hdr.cookie &&
+ state->last.command == msg->hdr.command &&
+ state->last.arg == msg->hdr.arg) {
+ // Host must have missed the ack. Resend.
+ netcon_send(state->con, &state->ack, sizeof(state->ack));
+ return;
+ }
+
+ memcpy(&state->last, &msg->hdr, sizeof(GigabootHeader));
+
+ size_t data_size = size - sizeof(GigabootHeader);
+
+ state->ack.cookie = msg->hdr.cookie;
+ state->ack.arg = msg->hdr.arg;
+
+ switch (msg->hdr.command) {
+ case GigabootCommandCommand:
+ if (data_size == 0)
+ return;
+ break;
+
+ case GigabootCommandSendFile:
+ if (data_size == 0)
+ return;
+
+ msg->data[data_size - 1] = 0;
+ for (int i = 0; i < data_size - 1; i++) {
+ if (msg->data[i] < ' ' || msg->data[i] > 127)
+ msg->data[i] = '.';
+ }
+ state->buffer = gigaboot_get_buffer((const char *)msg->data,
+ msg->hdr.arg);
+ if (state->buffer) {
+ state->buffer->offset = 0;
+ state->ack.arg = msg->hdr.arg;
+ printf("gigaboot: Receive file '%s'...\n",
+ (char *)msg->data);
+ } else {
+ state->ack.command = GigabootErrorBadFile;
+ printf("gigaboot: Rejected file '%s'...\n",
+ (char *)msg->data);
+ }
+ break;
+
+ case GigabootCommandData:
+ if (!state->buffer)
+ return;
+ if (msg->hdr.arg != state->buffer->offset)
+ return;
+ if (state->buffer->offset + data_size > state->buffer->size) {
+ state->ack.command = GigabootErrorTooLarge;
+ } else {
+ memcpy((uint8_t *)state->buffer->data +
+ state->buffer->offset,
+ msg->data, data_size);
+ state->buffer->offset += data_size;
+ state->ack.command = GigabootAck;
+ }
+ break;
+
+ case GigabootCommandBoot:
+ {
+ printf("netboot: Boot Kernel...\n");
+
+ GigabootBuffer *kernel =
+ gigaboot_get_buffer(gigaboot_buffer_kernel, 0);
+ GigabootBuffer *cmdline =
+ gigaboot_get_buffer(gigaboot_buffer_cmdline, 0);
+ if (!kernel || !cmdline) {
+ state->ack.command = GigabootErrorBadParam;
+ break;
+ }
+
+ // Send an ack here since we're (probably) not coming back.
+ netcon_send(state->con, &state->ack, sizeof(state->ack));
+ boot(kernel->data, cmdline->data, NULL, NULL);
+ return;
+ }
+
+ default:
+ state->ack.command = GigabootErrorBadCmd;
+ state->ack.arg = 0;
+ }
+
+ netcon_send(state->con, &state->ack, sizeof(state->ack));
+}
+
+
+
+/*
+ * Poke the host to get it to start sending commands.
+ */
+
+static void gigaboot_advertise(GigabootState *state)
+{
+ static const char ad_data[] =
+ "version\01.0\0"
+ "serialno\0unknown\0"
+ "board\0unknown\0";
+
+ static struct {
+ GigabootHeader header;
+ uint8_t data[sizeof(ad_data)];
+ } ad_message = {
+ .header = {
+ .magic = GigabootMagic,
+ .cookie = 0,
+ .command = GigabootAdvertise,
+ .arg = GigabootVersion_1_0,
+ },
+ };
+ if (!ad_message.data[0])
+ memcpy(ad_message.data, ad_data, sizeof(ad_data));
+
+ netcon_send(state->con, &ad_message, sizeof(ad_message));
+}
+
+
+
+/*
+ * Try running the gigaboot protocol on a given connection.
+ */
+
+static void gigaboot_attempt(NetConOps *con)
+{
+ GigabootState state;
+ memset(&state, 0, sizeof(state));
+ state.con = con;
+ state.ack.magic = GigabootMagic;
+
+ gigaboot_advertise(&state);
+
+ uint64_t start = time_us(0);
+ while (time_us(start) < 1000000) {
+ size_t incoming;
+ if (netcon_incoming(con, &incoming)) {
+ printf("Incoming failed.\n");
+ return;
+ }
+ if (!incoming)
+ continue;
+ if (incoming < sizeof(GigabootHeader))
+ return;
+
+ if (incoming > state.message_len) {
+ free(state.message);
+ state.message = xmalloc(incoming);
+ state.message_len = incoming;
+ }
+
+ netcon_receive(con, state.message, &incoming, incoming);
+ gigaboot_recv_message(&state, (GigabootMessage *)state.message,
+ incoming);
+ start = time_us(0);
+ }
+
+ free(state.message);
+}
+
+
+
+/*
+ * Keep trying to gigaboot on each available network device in a loop.
+ */
+
+void gigaboot(void)
+{
+ NetDevice *dev = NULL;
+ while (1) {
+ dev = net_scan_for_link(dev);
+ if (dev) {
+ Ipv6UdpCon *con =
+ new_ipv6_udp_con(dev, &ip6_ll_all_nodes,
+ 33331);
+ netcon_bind(&con->ops, 33330);
+ gigaboot_attempt(&con->ops);
+ netcon_close(&con->ops);
+ free(con);
+ } else {
+ net_find_devices();
+ }
+ }
+}
diff --git a/src/net/gigaboot/gigaboot.h b/src/net/gigaboot/gigaboot.h
new file mode 100644
index 0000000..3f35f03
--- /dev/null
+++ b/src/net/gigaboot/gigaboot.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#ifndef __NET_GIGABOOT_GIGABOOT_H__
+#define __NET_GIGABOOT_GIGABOOT_H__
+
+#include <stddef.h>
+
+#include "base/list.h"
+#include "net/connection.h"
+
+// Call to trigger the gigaboot protocol.
+void gigaboot(void);
+
+typedef struct {
+ void *data;
+ size_t size; // Max size of the buffer.
+ size_t offset; // Write pointer.
+} GigabootBuffer;
+
+extern const char gigaboot_buffer_kernel[];
+extern const char gigaboot_buffer_cmdline[];
+extern const char gigaboot_buffer_ramdisk[];
+
+// Implement to give gigaboot a place to put the data it downloads.
+GigabootBuffer *gigaboot_get_buffer(const char *name, size_t size);
+
+#endif /* __NET_GIGABOOT_GIGABOOT_H__ */