blob: 2dfdbd4ad44dcd3db8267c466c97ce62aa097c32 [file] [log] [blame]
// Copyright 2020 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.
#include <abr.h>
#include <bootbyte.h>
#include <bootimg.h>
#include <diskio.h>
#include <fastboot.h>
#include <inet6.h>
#include <inttypes.h>
#include <lib/abr/data.h>
#include <netifc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xefi.h>
#include <zircon/hw/gpt.h>
// Constants.
#define DEBUG 0
#define FB_CMD_MAX_LEN 64
#define FB_HDR_SIZE 4
#define FB_MAX_PAYLOAD_SIZE (UDP6_MAX_PAYLOAD - FB_HDR_SIZE)
#define FB_PROTOCOL_VERSION 4
#define INIT_PKT_SIZE (FB_HDR_SIZE + 4)
#define INITIAL_SEQ_NUM 0x55aa
#define NUM_COMMANDS 7
#define NUM_VARIABLES 14
#define PAGE_SIZE 4096
#define PARTITION_OFFSET 0
#define QUERY_PKT_SIZE (FB_HDR_SIZE + 2)
// Enumeration of the types of packets allowed in the fastboot protocol.
typedef enum {
ERROR_TYPE = 0x00,
QUERY_TYPE = 0x01,
INIT_TYPE = 0x02,
FASTBOOT_TYPE = 0x03
} pkt_type;
// Enumeration of the phase a fastboot command is in.
typedef enum { IDLE = 0, CMD = 1, DATA = 2, ALLVAR = 3 } fb_cmd_phase;
// Types.
// Fastboot packet.
typedef struct {
uint8_t pkt_id;
uint8_t pkt_flags;
uint16_t seq_num;
uint8_t data[FB_MAX_PAYLOAD_SIZE];
} fb_pkt_t;
// A UDP destination address.
typedef struct {
void *daddr;
uint16_t dport;
uint16_t sport;
} udp_addr_t;
// fb_cmd_t represents a fastboot command, and contains a function to both
// execute the command and send a response to the host.
typedef struct {
const char *name;
void (*func)(char *cmd);
} fb_cmd_t;
// fb_var contains the name of a fastboot variable, along with either a
// constant value or a function that can get it. This function places the result
// in the second argument, and returns 0 on success, -1 on failure.
typedef struct {
const char *name;
const char *value;
int (*func)(const char *arg, char *result);
const char **default_args;
} fb_var_t;
// fb_img_t represents an in memory download image.
typedef struct {
uint32_t size;
uint32_t bytes_received;
void *data;
} fb_img_t;
// Function prototypes.
// Helpers.
void pp_fb_pkt(const char *direction, fb_pkt_t *pkt, size_t len);
void fb_send_data(const char *msg);
void fb_send_okay(const char *msg);
void fb_send_info(const char *msg);
void fb_send_fail(const char *msg);
void fb_send_ack(void);
void fb_resend(void);
// Functions that respond to each packet type.
void respond_to_init_packet(fb_pkt_t *pkt);
void respond_to_query_packet(fb_pkt_t *pkt);
void respond_to_fastboot_pkt(fb_pkt_t *pkt, size_t len);
// Fastboot command functions. These functions execute a command and send
// results/responses to the host.
void fb_reboot(char *cmd);
void fb_flash(char *cmd);
void fb_erase(char *cmd);
void fb_download(char *cmd);
void fb_getvar(char *cmd);
void fb_set_active(char *cmd);
void fb_boot(char *cmd);
// Fastboot variable functions. These functions retreive a variable and return
// the value as a null terminated string. They are responsible for sending
// failures to the host when they encountered.
int get_max_download_size(const char *arg, char *result);
int get_current_slot(const char *arg, char *result);
int get_slot_unbootable(const char *slot, char *result);
int get_slot_successful(const char *slot, char *result);
int get_slot_retry_count(const char *slot, char *result);
// Global state.
static uint16_t max_pkt_size;
static udp_addr_t dest_addr;
static fb_pkt_t pkt_to_send;
static size_t pkt_to_send_len;
static char curr_cmd[FB_CMD_MAX_LEN + 1]; // Add space for null terminator.
static uint16_t expected_seq_num = INITIAL_SEQ_NUM;
static fb_img_t curr_img;
static fb_cmd_phase cmd_phase;
static uint8_t curr_var_idx;
static uint8_t curr_var_arg_idx;
static const char *slot_suffix_list[] = {"a", "b", NULL};
static fb_bootimg_t boot_img;
static int fb_boot_now = 0;
// cmdlist maps a command name to the function that handles that command.
static fb_cmd_t cmdlist[NUM_COMMANDS] = {
{
// This command handles (-recovery|-bootloader) as well.
.name = "reboot",
.func = fb_reboot,
},
{
.name = "flash",
.func = fb_flash,
},
{
.name = "erase",
.func = fb_erase,
},
{
.name = "download",
.func = fb_download,
},
{
.name = "getvar",
.func = fb_getvar,
},
{
.name = "set_active",
.func = fb_set_active,
},
{
.name = "boot",
.func = fb_boot,
}};
// varlist contains all variables this bootloader supports.
static fb_var_t varlist[NUM_VARIABLES] = {
{
.name = "has-slot",
.value = "",
},
{
.name = "partition-type",
.value = "",
},
{
.name = "max-download-size",
.func = get_max_download_size,
},
{
.name = "is-logical",
.value = "no",
},
{
.name = "slot-count",
.value = "2",
},
{
.name = "bootloader-min-versions",
.value = "0",
},
{
.name = "current-slot",
.func = get_current_slot,
},
{
.name = "hw-revision",
.value = "unimplemented",
},
{
.name = "product",
.value = "gigaboot",
},
{
.name = "serialno",
.value = "unimplemented",
},
{
.name = "slot-retry-count",
.func = get_slot_retry_count,
.default_args = slot_suffix_list,
},
{
.name = "slot-successful",
.func = get_slot_successful,
.default_args = slot_suffix_list,
},
{
.name = "slot-unbootable",
.func = get_slot_unbootable,
.default_args = slot_suffix_list,
},
{
.name = "version",
.value = "0.4",
},
};
int fb_poll(fb_bootimg_t *img) {
// If a previous fastboot boot failed, prevent retries by clearing fb_boot_now.
fb_boot_now = 0;
// Continue processing fastboot packets.
netifc_poll();
if (fb_boot_now) {
memcpy((void *) img, (void *) &boot_img, sizeof(fb_bootimg_t));
}
return fb_boot_now;
}
// fb_recv runs every time a UDP packet destined for the fastboot port is
// received.
void fb_recv(void *data, size_t len, void *saddr, uint16_t sport, uint16_t dport) {
if (len > sizeof(fb_pkt_t)) {
fb_send_fail("received fastboot packet larger than max packet size");
return;
}
fb_pkt_t *pkt = (fb_pkt_t *)data;
if (DEBUG) {
pp_fb_pkt("host", pkt, len);
}
uint16_t cur_seq_num = ntohs(pkt->seq_num);
// Prepare the destination address.
dest_addr.daddr = saddr;
dest_addr.dport = sport;
dest_addr.sport = dport;
if (pkt->pkt_id == QUERY_TYPE) {
// Clear the last response.
memset(&pkt_to_send, 0, sizeof(pkt_to_send));
pkt_to_send.pkt_id = pkt->pkt_id;
pkt_to_send.seq_num = pkt->seq_num;
pkt_to_send.pkt_flags = 0;
respond_to_query_packet(pkt);
} else if (cur_seq_num == expected_seq_num) {
// Clear the last response.
memset(&pkt_to_send, 0, sizeof(pkt_to_send));
pkt_to_send.pkt_id = pkt->pkt_id;
pkt_to_send.seq_num = pkt->seq_num;
pkt_to_send.pkt_flags = 0;
if (pkt->pkt_id == INIT_TYPE) {
respond_to_init_packet(pkt);
// Reset the command phase.
cmd_phase = IDLE;
} else if (pkt->pkt_id == FASTBOOT_TYPE) {
respond_to_fastboot_pkt(pkt, len);
} else if (pkt->pkt_id == ERROR_TYPE) {
printf("got error from host: %s", (char *)(pkt->data));
} else {
// Send an error to the host.
pkt_to_send.pkt_id = ERROR_TYPE;
snprintf((char *)pkt_to_send.data, FB_MAX_PAYLOAD_SIZE,
"fastboot packet had malformed type %#02x", pkt->pkt_id);
udp6_send((void *)&pkt_to_send,
FB_HDR_SIZE + strnlen((char *)pkt_to_send.data, FB_MAX_PAYLOAD_SIZE),
dest_addr.daddr, dest_addr.dport, dest_addr.sport);
printf("error: malformed type: %#02x", pkt->pkt_id);
return;
}
expected_seq_num += 1;
} else if (cur_seq_num == expected_seq_num - 1) {
fb_resend();
}
}
void respond_to_fastboot_pkt(fb_pkt_t *pkt, size_t len) {
switch (cmd_phase) {
case IDLE: {
memcpy((void *)curr_cmd, (void *)(pkt->data), (len - FB_HDR_SIZE));
// Ensure that the current command is null terminated, as we will depend
// on this to tokenize later.
curr_cmd[len - FB_HDR_SIZE] = '\0';
cmd_phase = CMD;
// Handle the "getvar:all" special case, as it requires multi packet
// interaction.
if (!strncmp(curr_cmd, "getvar:all", strlen("getvar:all"))) {
cmd_phase = ALLVAR;
curr_var_idx = 0;
curr_var_arg_idx = 0;
}
fb_send_ack();
break;
}
case CMD: {
// Generally, we transition to the IDLE phase after handling a CMD.
cmd_phase = IDLE;
bool found = false;
for (int i = 0; i < NUM_COMMANDS; i++) {
fb_cmd_t cmd = cmdlist[i];
// strlen is safe here because the cmd name is specified as a constant
// above.
if (!strncmp(curr_cmd, cmd.name, strlen(cmd.name))) {
found = true;
cmd.func(curr_cmd);
break;
}
}
if (!found) {
fb_send_fail("command not found");
}
// Clear the current command.
memset(curr_cmd, '\0', FB_CMD_MAX_LEN);
break;
}
case DATA: {
if (curr_img.bytes_received == curr_img.size) {
fb_send_okay("");
cmd_phase = IDLE;
} else {
// Keep copying data from the host until we've received all of it.
uint32_t payload_size = len - FB_HDR_SIZE;
memcpy(curr_img.data + curr_img.bytes_received, pkt->data, payload_size);
curr_img.bytes_received += payload_size;
// Send an ACK to tell the host we received the data.
fb_send_ack();
}
break;
}
case ALLVAR: {
if (curr_var_idx == NUM_VARIABLES) {
// If we've gone through all of our variables, send an OKAY and return
// to IDLE.
cmd_phase = IDLE;
fb_send_okay("");
return;
}
fb_var_t var = varlist[curr_var_idx];
char allvar_result[FB_CMD_MAX_LEN];
if (var.value) {
snprintf(allvar_result, FB_CMD_MAX_LEN, "%s:%s", var.name, var.value);
fb_send_info(allvar_result);
curr_var_idx += 1;
} else {
const char *arg = NULL;
if (var.default_args) {
arg = var.default_args[curr_var_arg_idx];
}
char result[FB_CMD_MAX_LEN];
memset(result, 0, FB_CMD_MAX_LEN); // Zero out to null terminate.
if (var.func(arg, result) == 0) {
// Since the variable was successfully retrieved, generate the
// formatted key:value pair response and send.
if (arg) {
snprintf(allvar_result, sizeof(allvar_result), "%s:%s:%s", var.name, arg, result);
} else {
snprintf(allvar_result, sizeof(allvar_result), "%s:%s", var.name, result);
}
fb_send_info(allvar_result);
} else {
fb_send_fail(result);
}
// If we've exhausted all default args, or there are no default args,
// move to the next var.
curr_var_arg_idx += 1;
if (!var.default_args || !var.default_args[curr_var_arg_idx]) {
curr_var_idx += 1;
curr_var_arg_idx = 0;
}
}
break;
}
}
}
void respond_to_query_packet(fb_pkt_t *pkt) {
uint16_t be_seq_num = htons(expected_seq_num);
memcpy((void *)pkt_to_send.data, (void *)&be_seq_num, sizeof(uint16_t));
if (DEBUG) {
pp_fb_pkt("device", &pkt_to_send, FB_HDR_SIZE + sizeof(uint16_t));
}
udp6_send((void *)&pkt_to_send, FB_HDR_SIZE + sizeof(uint16_t), dest_addr.daddr, dest_addr.dport,
dest_addr.sport);
}
void respond_to_init_packet(fb_pkt_t *pkt) {
// In this case, the response data is 2 big endian 2-byte values containing
// the protocol version and max UDP packet size.
max_pkt_size = sizeof(fb_pkt_t);
uint16_t data[2] = {htons(1), htons(max_pkt_size)};
memcpy((void *)pkt_to_send.data, (void *)&data, 2 * sizeof(uint16_t));
// Set the max packet size.
uint16_t host_max_pkt_size = 0;
memcpy((void *)&host_max_pkt_size, (void *)(pkt->data + 2), sizeof(uint16_t));
if (ntohs(host_max_pkt_size) < max_pkt_size) {
max_pkt_size = ntohs(host_max_pkt_size);
}
if (DEBUG) {
pp_fb_pkt("device", &pkt_to_send, FB_HDR_SIZE + 4);
}
udp6_send((void *)&pkt_to_send, FB_HDR_SIZE + 4, dest_addr.daddr, dest_addr.dport,
dest_addr.sport);
}
void fb_reboot(char *cmd) {
// Throw away the reboot command.
strtok(cmd, "-");
char *partition = strtok(NULL, "-");
if (!partition) {
bootbyte_set_normal();
} else if (!strncmp(partition, "bootloader", 10)) {
bootbyte_set_bootloader();
} else if (!strncmp(partition, "recovery", 8)) {
bootbyte_set_recovery();
}
fb_send_okay("");
gSys->RuntimeServices->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
}
void fb_flash(char *cmd) {
// Throw away the flash command string.
strtok(cmd, ":");
// Get the partition to flash by getting the next token.
char *partition = strtok(NULL, ":");
if (!partition) {
fb_send_fail("no partition provided to flash");
return;
}
const uint8_t *type_guid = partition_type_guid(partition);
if (!type_guid) {
fb_send_fail("could not find partition type GUID");
return;
}
efi_status status = write_partition(gImg, gSys, type_guid, partition, PARTITION_OFFSET,
(unsigned char *)curr_img.data, curr_img.size);
if (status != EFI_SUCCESS) {
char err_msg[FB_CMD_MAX_LEN];
snprintf(err_msg, FB_CMD_MAX_LEN, "failed to write partition; efi_status: %016llx", status);
fb_send_fail(err_msg);
return;
}
fb_send_okay("");
}
void fb_erase(char *cmd) {
// Throw away the erase command string.
strtok(cmd, ":");
// Get the partition to flash by getting the next token.
char *partition = strtok(NULL, ":");
if (!partition) {
fb_send_fail("no partition provided to erase");
return;
}
const uint8_t *type_guid = partition_type_guid(partition);
if (!type_guid) {
fb_send_fail("could not find partition type GUID");
return;
}
disk_t disk;
if (disk_find_boot(gImg, gSys, DEBUG, &disk) < 0) {
fb_send_fail("could not find boot disk");
return;
}
gpt_entry_t entry;
if (disk_find_partition(&disk, DEBUG, type_guid, NULL, NULL, &entry)) {
fb_send_fail("could not find partition");
return;
}
uint64_t offset = entry.first * disk.blksz;
uint64_t size = (entry.last - entry.first + 1) * disk.blksz;
// Allocate some memory to clear.
size_t num_pages = PAGE_SIZE * 16;
efi_physical_addr pg_addr;
efi_status status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, num_pages, &pg_addr);
if (status != EFI_SUCCESS) {
char err_msg[FB_CMD_MAX_LEN];
snprintf(err_msg, FB_CMD_MAX_LEN, "failed to allocate memory; efi_status: %016llx", status);
fb_send_fail(err_msg);
return;
}
size_t increment = num_pages * PAGE_SIZE;
memset((void *)pg_addr, 0xff, increment);
// Clear the partition in 256MiB increments. This value is just large enough
// to erase an entire zircon partition in less than 500ms. Admittedly, this
// is a bit fragile to future partition size increases, so we should
// probably intermittently poll the network interface so the host doesn't
// think the port is closed.
while (size > 0) {
size_t len = (size < increment) ? size : increment;
efi_status status = disk_write(&disk, offset, (void *)pg_addr, len);
if (status != EFI_SUCCESS) {
char err_msg[FB_CMD_MAX_LEN];
snprintf(err_msg, FB_CMD_MAX_LEN, "failed to write to disk; efi_status: %016llx", status);
fb_send_fail(err_msg);
return;
}
size -= len;
offset += len;
}
// Send the OKAY.
fb_send_okay("");
// Free the memory.
gBS->FreePages(pg_addr, num_pages);
}
// Turns a hex string of exactly length 8 into a uint32_t.
uint32_t hex_to_int(const char *hexstring) {
uint32_t value = 0;
uint8_t hexstring_length = 8;
uint8_t bits_per_char = 4;
for (uint8_t i = 0; i < hexstring_length; i++) {
char hex_digit = *(hexstring + i);
uint32_t ascii = (uint32_t)hex_digit;
if (ascii >= '0' && ascii <= '9') {
// character is 0-9
value += (ascii - '0') << ((7 - i) * bits_per_char);
} else if (ascii >= 'a' && ascii <= 'f') {
// character is a-f
uint32_t intermediate = (ascii - 'a') + 10;
value += intermediate << ((7 - i) * bits_per_char);
} else {
// This will lead to unexpected failures if the provided hexstring is
// 0xffffffff, but this seems like a rare edge case.
return -1;
}
}
return value;
}
void fb_download(char *cmd) {
// Throw away download command string.
strtok(cmd, ":");
// Free any pages used during a previous download.
if (curr_img.data != NULL) {
uint32_t pages_used = (curr_img.size + PAGE_SIZE - 1) / PAGE_SIZE;
efi_status status = gBS->FreePages((efi_physical_addr)(curr_img.data), pages_used);
if (status != EFI_SUCCESS) {
char err_msg[FB_CMD_MAX_LEN];
snprintf(err_msg, FB_CMD_MAX_LEN, "failed to free memory; efi_status: %016llx", status);
fb_send_fail(err_msg);
return;
}
curr_img.data = NULL;
curr_img.bytes_received = 0;
}
// Get the size of the current download.
char *hexstring = strtok(NULL, ":");
if (!hexstring) {
fb_send_fail("download size not provided");
return;
}
curr_img.size = hex_to_int(hexstring);
if (curr_img.size == (uint32_t)(-1)) {
fb_send_fail("failed to convert download size to integer");
return;
}
// Allocate space for the download.
uint32_t pages_needed = (curr_img.size + PAGE_SIZE - 1) / PAGE_SIZE;
efi_physical_addr mem_addr;
efi_status status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, pages_needed, &mem_addr);
if (status != EFI_SUCCESS) {
char err_msg[FB_CMD_MAX_LEN];
snprintf(err_msg, FB_CMD_MAX_LEN, "failed to allocate memory; efi_status: %016llx", status);
fb_send_fail(err_msg);
return;
}
curr_img.data = (void *)mem_addr;
// Respond with the appropriate DATA packet.
fb_send_data(hexstring);
cmd_phase = DATA;
}
void fb_set_active(char *cmd) {
// Throw away set-active command string.
strtok(cmd, ":");
char *slot = strtok(NULL, ":");
if (!slot) {
fb_send_fail("no slot provided to set-active");
return;
}
AbrResult res;
if (*slot == 'a') {
res = zircon_abr_set_slot_active(kAbrSlotIndexA);
} else if (*slot == 'b') {
res = zircon_abr_set_slot_active(kAbrSlotIndexB);
} else {
fb_send_fail("invalid slot in set-active");
return;
}
if (res != kAbrResultOk) {
fb_send_fail("failed to set slot active");
return;
}
fb_send_okay("");
}
// getvar retrieves the value of the requested fastboot variable (if it exists).
void fb_getvar(char *cmd) {
// Throw away the "getvar" portion of the string.
strtok(cmd, ":");
char *varname = strtok(NULL, ":");
if (!varname) {
fb_send_fail("no variable provided");
return;
}
char *arg = strtok(NULL, ":");
bool found = false;
for (int i = 0; i < NUM_VARIABLES; i++) {
fb_var_t var = varlist[i];
// strlen is safe here because all of the variable names and values
// are constant strings specified above.
if (!strncmp(varname, var.name, strlen(var.name))) {
found = true;
if (var.value != NULL) {
fb_send_okay(var.value);
} else {
char result[FB_CMD_MAX_LEN];
memset(result, 0, FB_CMD_MAX_LEN); // Zero out to null terminate.
if (var.func(arg, result) == 0) {
fb_send_okay(result);
} else {
fb_send_fail(result);
}
}
break;
}
}
if (!found) {
fb_send_fail("no such variable");
}
}
// boots the previously downloaded image in memory.
void fb_boot(char *cmd) {
uint32_t bootimg_hdr_version = validate_bootimg(curr_img.data);
if (bootimg_hdr_version == (uint32_t)(-1)) {
fb_send_fail("invalid boot image magic");
return;
}
uint32_t kernel_size = get_kernel_size(curr_img.data, bootimg_hdr_version);
if (kernel_size == (uint32_t)(-1)) {
fb_send_fail("failed to get kernel size from bootimg");
return;
}
uint32_t page_size = get_page_size(curr_img.data, bootimg_hdr_version);
if (page_size == (uint32_t)(-1)) {
fb_send_fail("failed to get page size from bootimg");
return;
}
fb_boot_now = 1;
boot_img.kernel_size = kernel_size;
boot_img.kernel_start = curr_img.data + page_size;
fb_send_okay("");
}
// get_max_download_size puts the size of the largest contiguous section of
// memory in the result buffer. Returns 0 on success, -1 on failure.
int get_max_download_size(const char *arg, char *result) {
efi_memory_type mem_type = EfiLoaderData | EfiConventionalMemory;
uint64_t max_download_size = 0;
// Get memory map.
static char buf[32786];
size_t buf_size = sizeof(buf);
size_t mkey = 0;
size_t dsize = 0;
uint32_t dversion = 0;
efi_status status =
gBS->GetMemoryMap(&buf_size, (efi_memory_descriptor *)buf, &mkey, &dsize, &dversion);
if (status != EFI_SUCCESS) {
snprintf(result, FB_MAX_PAYLOAD_SIZE, "failed to get memory map; efi_status: %016llx", status);
return -1;
}
// Look through the memory map for the largest contiguous region of memory.
for (void *p = (void *)buf; p < (void *)(buf) + buf_size; p += dsize) {
efi_memory_descriptor *des = (efi_memory_descriptor *)p;
if ((des->Type & mem_type) && (des->NumberOfPages * PAGE_SIZE) > max_download_size) {
max_download_size = (des->NumberOfPages * PAGE_SIZE);
}
}
snprintf(result, FB_MAX_PAYLOAD_SIZE, "0x%016llx", max_download_size);
return 0;
}
// get_current_slot returns the current boot slot.
int get_current_slot(const char *arg, char *result) {
AbrSlotIndex idx = zircon_abr_get_boot_slot();
switch (idx) {
case kAbrSlotIndexA:
strncpy(result, "a", FB_MAX_PAYLOAD_SIZE);
break;
case kAbrSlotIndexB:
strncpy(result, "b", FB_MAX_PAYLOAD_SIZE);
break;
case kAbrSlotIndexR:
strncpy(result, "r", FB_MAX_PAYLOAD_SIZE);
break;
default:
strncpy(result, "failed to get boot slot", FB_MAX_PAYLOAD_SIZE);
return -1;
}
return 0;
}
// get_slot_info is a helper function that populates an AbrSlotInfo object given
// a slot.
// Returns 0 on success, -1 on failure.
int get_slot_info(char slot, AbrSlotInfo *info) {
AbrSlotIndex slotIdx;
if (slot == 'a') {
slotIdx = kAbrSlotIndexA;
} else if (slot == 'b') {
slotIdx = kAbrSlotIndexB;
} else {
// Fastboot does not support getting boot bit for any other partition.
return -1;
}
if (zircon_abr_get_slot_info(slotIdx, info) != kAbrResultOk) {
return -1;
}
return 0;
}
int get_slot_unbootable(const char *slot, char *result) {
if (!slot) {
strncpy(result, "no slot provided", FB_MAX_PAYLOAD_SIZE);
return -1;
}
AbrSlotInfo info;
if (get_slot_info(*slot, &info) != 0) {
strncpy(result, "could not get slot info", FB_MAX_PAYLOAD_SIZE);
return -1;
}
if (!info.is_bootable) {
strncpy(result, "yes", FB_MAX_PAYLOAD_SIZE);
} else {
strncpy(result, "no", FB_MAX_PAYLOAD_SIZE);
}
return 0;
}
int get_slot_successful(const char *slot, char *result) {
if (!slot) {
strncpy(result, "no slot provided", FB_MAX_PAYLOAD_SIZE);
return -1;
}
AbrSlotInfo info;
if (get_slot_info(*slot, &info) != 0) {
strncpy(result, "could not get slot info", FB_MAX_PAYLOAD_SIZE);
return -1;
}
if (info.is_marked_successful) {
strncpy(result, "yes", FB_MAX_PAYLOAD_SIZE);
} else {
strncpy(result, "no", FB_MAX_PAYLOAD_SIZE);
}
return 0;
}
int get_slot_retry_count(const char *slot, char *result) {
if (!slot) {
strncpy(result, "no slot provided", FB_MAX_PAYLOAD_SIZE);
return -1;
}
AbrSlotInfo info;
if (get_slot_info(*slot, &info) != 0) {
strncpy(result, "could not get slot info", FB_MAX_PAYLOAD_SIZE);
return -1;
}
snprintf(result, FB_MAX_PAYLOAD_SIZE, "%d", (kAbrMaxTriesRemaining - info.num_tries_remaining));
return 0;
}
void pp_fb_pkt(const char *direction, fb_pkt_t *pkt, size_t len) {
// Pretty printing is too slow when transferring data, so skip in the data
// phase. TCP dump is generally sufficient when debugging data transfer
// issues.
if (cmd_phase == DATA) {
return;
}
printf("Size: %zu, %s: ", len, direction);
switch (pkt->pkt_id) {
case ERROR_TYPE:
printf("ERROR");
break;
case QUERY_TYPE:
printf("QUERY");
break;
case INIT_TYPE:
printf("INIT");
printf(" Protocol version: 0x%04x ", *((uint16_t *)(pkt->data)));
printf(" Max packet size: 0x%04x", *((uint16_t *)(pkt->data) + 1));
break;
case FASTBOOT_TYPE:
printf("FASTBOOT");
break;
default:
printf("error: malformed type: %#02x", pkt->pkt_id);
return;
}
printf(" Flags: %02x", pkt->pkt_flags);
printf(" Seq_Num: %04x", pkt->seq_num);
pkt->data[len - FB_HDR_SIZE] = '\0';
printf(" Data: \"%s\" \n", pkt->data);
}
void fb_send_ack(void) {
pkt_to_send_len = FB_HDR_SIZE;
if (DEBUG) {
pp_fb_pkt("device", &pkt_to_send, pkt_to_send_len);
}
udp6_send((void *)&pkt_to_send, pkt_to_send_len, dest_addr.daddr, dest_addr.dport,
dest_addr.sport);
}
void fb_resend(void) {
udp6_send((void *)&pkt_to_send, pkt_to_send_len, dest_addr.daddr, dest_addr.dport,
dest_addr.sport);
}
void fb_send(const char *msg) {
// Copy the payload into the packet.
size_t msg_len = strnlen(msg, FB_MAX_PAYLOAD_SIZE - 4);
strncpy((char *)(pkt_to_send.data + 4), msg, msg_len);
pkt_to_send_len = FB_HDR_SIZE + msg_len + 4;
// Send the packet.
if (DEBUG) {
pp_fb_pkt("device", &pkt_to_send, pkt_to_send_len);
}
udp6_send((void *)&pkt_to_send, pkt_to_send_len, dest_addr.daddr, dest_addr.dport,
dest_addr.sport);
}
void fb_send_okay(const char *msg) {
memcpy(pkt_to_send.data, "OKAY", 4);
fb_send(msg);
}
void fb_send_fail(const char *msg) {
memcpy(pkt_to_send.data, "FAIL", 4);
fb_send(msg);
}
void fb_send_data(const char *msg) {
memcpy(pkt_to_send.data, "DATA", 4);
fb_send(msg);
}
void fb_send_info(const char *msg) {
memcpy(pkt_to_send.data, "INFO", 4);
fb_send(msg);
}