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__ */