blob: 7140030734b6bccfbe4d1ff5a68a5bd7b6e97a33 [file] [log] [blame]
/*
* Copyright (C) 2021 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.
*/
#include "boot/avb.h"
#include "boot/bootbyte.h"
#include "boot/zbi.h"
#include "config.h"
#include "fastboot/cmd.h"
#include "fastboot/disk.h"
#include "fastboot/fastboot.h"
#include "fastboot/tcp.h"
#include "fastboot/vars.h"
#include "fuchsia-sdk/pkg/avb/libavb/libavb.h"
#include "fuchsia-sdk/pkg/avb/libavb_atx/libavb_atx.h"
#include "fuchsia-sdk/pkg/zircon_boot/zircon_vboot.h"
#include "gpt_misc.h"
#include <stdlib.h>
#include <libpayload.h>
static int parse_hex(const char *str, size_t len, uint32_t *ret)
{
int valid = 0;
uint32_t result = 0;
for (size_t i = 0; i < len; i++) {
char c = str[i];
int units = 0;
if (c >= '0' && c <= '9') {
units = c - '0';
} else if (c >= 'A' && c <= 'F') {
units = 10 + (c - 'A');
} else if (c >= 'a' && c <= 'f') {
units = 10 + (c - 'a');
} else {
break;
}
result *= 0x10;
result += units;
valid++;
}
*ret = result;
return valid;
}
static void fastboot_cmd_continue(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
fastboot_okay(fb, "Continuing boot");
fb->state = FINISHED;
}
static void fastboot_cmd_download(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
uint32_t size = 0;
int digits = parse_hex(arg, arg_len, &size);
if (digits != arg_len) {
fastboot_fail(fb, "Invalid argument");
return;
}
if (size > FASTBOOT_MAX_DOWNLOAD_SIZE) {
fastboot_fail(fb, "File too big");
return;
}
fastboot_data(fb, size);
}
static void fastboot_cmd_flash(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
if (!fb->has_download) {
fastboot_fail(fb, "No data staged to flash");
return;
}
uint64_t data_len;
void *data = fastboot_get_download_buffer(fb, &data_len);
if (!strncmp(arg, "bootloader", arg_len)) {
fastboot_write_bootloader(fb, data, data_len);
return;
} else if (!strncmp(arg, "gpt", arg_len)) {
fastboot_write_gpt(fb, data, data_len);
return;
}
struct fastboot_disk disk;
if (!fastboot_disk_init(&disk)) {
fastboot_fail(fb, "Failed to init disk");
return;
}
fastboot_write(fb, &disk, arg, arg_len, data, (uint32_t)data_len);
fastboot_disk_destroy(&disk);
}
static size_t upload_size = 0;
void fastboot_set_upload_size(size_t size) { upload_size = size; }
static void fastboot_cmd_upload(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
if (upload_size == 0) {
fastboot_fail(fb, "No data has been staged");
return;
}
char response[FASTBOOT_MSG_MAX + 1];
snprintf(response, FASTBOOT_MSG_MAX, "DATA%08zx", upload_size);
fastboot_send(response, strlen(response));
// Send the data
printf("Uploading %zu bytes of data...\n", upload_size);
fastboot_send(fastboot_get_upload_buffer(), upload_size);
printf("Done\n");
// Reset upload size.
fastboot_set_upload_size(0);
fastboot_succeed(fb);
}
static void fastboot_cmd_oem_stage_partition(fastboot_session_t *fb,
const char *arg, uint64_t arg_len)
{
struct fastboot_disk disk;
if (!fastboot_disk_init(&disk)) {
fastboot_fail(fb, "Failed to init disk");
return;
}
if (!strncmp(arg, "gpt", arg_len))
fastboot_stage_gpt(fb, &disk);
else
fastboot_fail(fb, "Partition is unsupported for staging");
fastboot_disk_destroy(&disk);
}
static void fastboot_cmd_erase(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
struct fastboot_disk disk;
if (!fastboot_disk_init(&disk)) {
fastboot_fail(fb, "Failed to init disk");
return;
}
fastboot_erase(fb, &disk, arg, arg_len);
fastboot_disk_destroy(&disk);
}
static void fastboot_cmd_oem_add_staged_bootloader_file(fastboot_session_t *fb,
const char *arg,
uint64_t arg_len)
{
if (!fb->has_download) {
fastboot_fail(fb, "Nothing staged");
return;
}
uint64_t data_len;
const void *data = fastboot_get_download_buffer(fb, &data_len);
if (zbi_stage_bootloader_file(arg, arg_len, data, data_len)) {
fastboot_fail(fb, "Failed to stage bootloader file");
return;
}
fastboot_succeed(fb);
}
// `fastboot oem get-kernels` returns a list of slot letter:kernel mapping.
// This is useful for us because the partition tables are more flexible than on
// more traditional devices, so there could be many kernels.
// For instance, installing Fuchsia on a Chromebook will result in the device
// having five kernel partitions:
// KERN-A
// KERN-B
// zircon-a
// zircon-b
// zircon-r
//
// We map them to fastboot slots by having the first slot be the first partition
// we found.
struct get_kernels_ctx {
fastboot_session_t *fb;
int cur_kernel;
};
static bool get_kernels_cb(void *ctx, int index, GptEntry *e,
char *partition_name)
{
if (get_slot_for_partition_name(e, partition_name) == 0)
return false;
struct get_kernels_ctx *gk = (struct get_kernels_ctx *)ctx;
char fastboot_slot = gk->cur_kernel + 'a';
fastboot_info(gk->fb, "%c:%s:prio=%d", fastboot_slot, partition_name,
GetEntryPriority(e));
gk->cur_kernel++;
return false;
}
static void fastboot_cmd_oem_get_kernels(fastboot_session_t *fb,
const char *arg, uint64_t arg_len)
{
struct fastboot_disk disk;
if (!fastboot_disk_init(&disk)) {
fastboot_fail(fb, "Failed to init disk");
return;
}
struct get_kernels_ctx ctx = {
.fb = fb,
.cur_kernel = 0,
};
fastboot_disk_foreach_partition(&disk, get_kernels_cb, &ctx);
fastboot_disk_destroy(&disk);
fastboot_succeed(fb);
}
static void fastboot_cmd_reboot(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
fastboot_succeed(fb);
fb->state = REBOOT;
}
static void fastboot_cmd_reboot_recovery(fastboot_session_t *fb,
const char *arg, uint64_t arg_len)
{
bootbyte_set_mode(BOOT_MODE_RECOVERY);
fastboot_succeed(fb);
fb->state = REBOOT;
}
static void fastboot_cmd_reboot_bootloader(fastboot_session_t *fb,
const char *arg, uint64_t arg_len)
{
bootbyte_set_mode(BOOT_MODE_BOOTLOADER);
fastboot_succeed(fb);
fb->state = REBOOT;
}
static void fastboot_cmd_set_active(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
struct fastboot_disk disk;
if (!fastboot_disk_init(&disk)) {
fastboot_fail(fb, "Failed to init disk");
return;
}
GptEntry *slot = fastboot_get_kernel_for_slot(&disk, arg[0]);
if (slot == NULL) {
fastboot_fail(fb, "Could not find slot");
goto out;
}
fastboot_slots_disable_all(&disk);
GptUpdateKernelWithEntry(disk.gpt, slot, GPT_UPDATE_ENTRY_ACTIVE);
// Keep zircon_r successful. Otherwise it's left invalid and
// `dm reboot-recovery` and `fastboot recovery` won't work.
GptEntry *zircon_r_slot =
fastboot_find_partition(&disk, "zircon_r", strlen("zircon_r"));
if (zircon_r_slot == NULL) {
fastboot_fail(fb, "Failed to find zircon_r slot");
goto out;
}
SetEntrySuccessful(zircon_r_slot, 1);
GptModified(disk.gpt);
fastboot_succeed(fb);
out:
fastboot_disk_destroy(&disk);
}
static void fastboot_vx_get_unlock_challenge(fastboot_session_t *fb,
const char *arg, uint64_t arg_len)
{
if (sizeof(AvbAtxUnlockChallenge) > fastboot_get_upload_buffer_size()) {
fastboot_fail(fb, "Upload buffer is too small");
return;
}
struct fastboot_disk disk;
if (!fastboot_disk_init(&disk)) {
fastboot_fail(fb, "Failed to init disk");
return;
}
struct zircon_boot_context priv;
ZirconBootOps zircon_boot_ops;
setup_zircon_boot_context_and_ops(disk.disk, disk.gpt, &priv,
&zircon_boot_ops);
AvbAtxUnlockChallenge unlock_challenge;
if (!ZirconVbootGenerateUnlockChallenge(&zircon_boot_ops,
&unlock_challenge)) {
fastboot_fail(fb, "Failed to generate unlock challenge");
fastboot_disk_destroy(&disk);
return;
}
memcpy(fastboot_get_upload_buffer(), &unlock_challenge,
sizeof(unlock_challenge));
fastboot_set_upload_size(sizeof(unlock_challenge));
fastboot_disk_destroy(&disk);
fastboot_succeed(fb);
}
static void fastboot_vx_unlock(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
uint64_t data_len;
void *download_buffer = fastboot_get_download_buffer(fb, &data_len);
if (data_len != sizeof(AvbAtxUnlockCredential)) {
fastboot_fail(fb, "Invalid unlock credential size");
return;
}
static AvbAtxUnlockCredential unlock_credential;
memcpy(&unlock_credential, download_buffer, data_len);
struct fastboot_disk disk;
if (!fastboot_disk_init(&disk)) {
fastboot_fail(fb, "Failed to init disk");
return;
}
struct zircon_boot_context priv;
ZirconBootOps zircon_boot_ops;
setup_zircon_boot_context_and_ops(disk.disk, disk.gpt, &priv,
&zircon_boot_ops);
if (!ZirconVbootValidateUnlockCredential(&zircon_boot_ops,
&unlock_credential)) {
fastboot_fail(fb, "Invalid unlock credential");
return;
}
unlock_device();
fastboot_succeed(fb);
}
static void fastboot_vx_lock(fastboot_session_t *fb, const char *arg,
uint64_t arg_len)
{
lock_device();
fastboot_succeed(fb);
}
#define CMD_ARGS(_name, _sep, _fn) \
{ \
.name = _name, .has_args = true, .sep = _sep, .fn = _fn \
}
#define CMD_NO_ARGS(_name, _fn) \
{ \
.name = _name, .has_args = false, .fn = _fn \
}
struct fastboot_cmd fastboot_cmds[] = {
CMD_NO_ARGS("continue", fastboot_cmd_continue),
CMD_ARGS("download", ':', fastboot_cmd_download),
CMD_NO_ARGS("upload", fastboot_cmd_upload),
CMD_ARGS("erase", ':', fastboot_cmd_erase),
CMD_ARGS("flash", ':', fastboot_cmd_flash),
CMD_ARGS("getvar", ':', fastboot_cmd_getvar),
CMD_ARGS("oem add-staged-bootloader-file", ' ',
fastboot_cmd_oem_add_staged_bootloader_file),
CMD_NO_ARGS("oem get-kernels", fastboot_cmd_oem_get_kernels),
CMD_NO_ARGS("oem vx-lock", fastboot_vx_lock),
CMD_NO_ARGS("oem vx-unlock", fastboot_vx_unlock),
CMD_ARGS("oem stage-partition", ' ', fastboot_cmd_oem_stage_partition),
CMD_NO_ARGS("oem vx-get-unlock-challenge",
fastboot_vx_get_unlock_challenge),
CMD_NO_ARGS("reboot-recovery", fastboot_cmd_reboot_recovery),
CMD_NO_ARGS("reboot-bootloader", fastboot_cmd_reboot_bootloader),
CMD_NO_ARGS("reboot", fastboot_cmd_reboot),
CMD_ARGS("set_active", ':', fastboot_cmd_set_active),
{
.name = NULL,
},
};