blob: 720b16316c5252fee37313f1fa3491ed794bcd51 [file] [log] [blame]
/*
* 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,
GigabootVersion_1_1 = 0x0001010,
GigabootVersion_Current = GigabootVersion_1_1,
};
enum {
// Used to acknowledge receipt of a command.
GigabootAck = 0,
// Used to acknowledge the receipt of all a file's data.
GigabootFileReceived = 0x70000001,
// 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,
// Receive the last chunk of data for the file started with SendFile.
GigabootCommandLastData = 11,
};
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 {
void *data;
size_t size; // Max size of the buffer.
size_t offset; // Write pointer.
} GigabootBuffer;
static GigabootBuffer gigaboot_kernel = {
.data = (void *)(uintptr_t)CONFIG_KERNEL_START,
.size = CONFIG_KERNEL_SIZE,
};
static char gigaboot_cmdline_buf[4096];
static GigabootBuffer gigaboot_cmdline = {
.data = gigaboot_cmdline_buf,
.size = sizeof(gigaboot_cmdline_buf),
};
static GigabootBuffer gigaboot_ramdisk;
typedef struct {
NetConOps *con;
GigabootHeader last;
GigabootHeader ack;
GigabootBuffer *buffer;
GigabootMessage *message;
size_t message_len;
} GigabootState;
/*
* Receive messages and act on commands.
*/
static GigabootBuffer *gigaboot_prepare_buffer(const char *name, size_t size)
{
if (!strcmp(name, "kernel.bin"))
return &gigaboot_kernel;
if (!strcmp(name, "cmdline"))
return &gigaboot_cmdline;
if (!strcmp(name, "ramdisk.bin")) {
if (gigaboot_ramdisk.size > size)
return &gigaboot_ramdisk;
if (gigaboot_free_buffer(gigaboot_ramdisk.data,
gigaboot_ramdisk.size)) {
return NULL;
}
gigaboot_ramdisk.data = NULL;
gigaboot_ramdisk.size = 0;
if (!size)
size = 512 * 1024 * 1024;
void *data = gigaboot_allocate_buffer(&size);
if (!data)
return NULL;
gigaboot_ramdisk.data = data;
gigaboot_ramdisk.size = size;
return &gigaboot_ramdisk;
}
return NULL;
}
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;
int send_ack = 1;
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_prepare_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:
case GigabootCommandLastData:
if (!state->buffer)
return;
if (msg->hdr.arg != state->buffer->offset) {
printf("gigaboot: received chunk at offset %d but "
"current offset is %zu\n",
msg->hdr.arg, state->buffer->offset);
state->ack.arg = state->buffer->offset;
state->ack.command = GigabootAck;
} else if (state->buffer->offset + data_size >
state->buffer->size) {
state->ack.command = GigabootErrorTooLarge;
state->ack.arg = msg->hdr.arg;
} else {
memcpy((uint8_t *)state->buffer->data +
state->buffer->offset,
msg->data, data_size);
state->buffer->offset += data_size;
if (msg->hdr.command == GigabootCommandLastData) {
state->ack.command = GigabootFileReceived;
} else {
state->ack.command = GigabootAck;
send_ack = 0;
}
}
break;
case GigabootCommandBoot:
{
printf("netboot: Boot Kernel...\n");
if (!gigaboot_kernel.offset) {
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(gigaboot_kernel.data, gigaboot_cmdline.data, NULL,
NULL, gigaboot_ramdisk.data, gigaboot_ramdisk.offset);
return;
}
default:
state->ack.command = GigabootErrorBadCmd;
state->ack.arg = 0;
}
if (send_ack)
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.1\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_Current,
},
};
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_kernel.offset = 0;
gigaboot_cmdline.offset = 0;
gigaboot_ramdisk.offset = 0;
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, &Ipv6LlAllNodes,
33331);
netcon_bind(&con->ops, 33330);
gigaboot_attempt(&con->ops);
netcon_close(&con->ops);
free(con);
} else {
net_find_devices();
}
}
}