Provide a way to pass the 'fake go' flag from the bootflow flag through to the PXE implementation, so that a request for a fake go (via 'bootflow boot -f') is handled correctly in the bootmeth and when booting. Add a little more debugging of this in PXE. Signed-off-by: Simon Glass <sjg@chromium.org>
1154 lines
28 KiB
C
1154 lines
28 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright 2010-2011 Calxeda, Inc.
|
|
* Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
|
|
*/
|
|
|
|
#define LOG_CATEGORY LOGC_BOOT
|
|
|
|
#include <bootflow.h>
|
|
#include <bootm.h>
|
|
#include <command.h>
|
|
#include <dm.h>
|
|
#include <env.h>
|
|
#include <image.h>
|
|
#include <log.h>
|
|
#include <malloc.h>
|
|
#include <mapmem.h>
|
|
#include <net.h>
|
|
#include <fdt_support.h>
|
|
#include <video.h>
|
|
#include <linux/libfdt.h>
|
|
#include <linux/string.h>
|
|
#include <linux/ctype.h>
|
|
#include <errno.h>
|
|
#include <linux/list.h>
|
|
|
|
#include <rng.h>
|
|
|
|
#include <splash.h>
|
|
#include <asm/io.h>
|
|
|
|
#include "menu.h"
|
|
#include "cli.h"
|
|
|
|
#include "pxe_utils.h"
|
|
|
|
#define MAX_TFTP_PATH_LEN 512
|
|
|
|
int pxe_get_file_size(ulong *sizep)
|
|
{
|
|
const char *val;
|
|
|
|
val = from_env("filesize");
|
|
if (!val)
|
|
return -ENOENT;
|
|
|
|
if (strict_strtoul(val, 16, sizep) < 0)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* format_mac_pxe() - obtain a MAC address in the PXE format
|
|
*
|
|
* This produces a MAC-address string in the format for the current ethernet
|
|
* device:
|
|
*
|
|
* 01-aa-bb-cc-dd-ee-ff
|
|
*
|
|
* where aa-ff is the MAC address in hex
|
|
*
|
|
* @outbuf: Buffer to write string to
|
|
* @outbuf_len: length of buffer
|
|
* Return: 1 if OK, -ENOSPC if buffer is too small, -ENOENT is there is no
|
|
* current ethernet device
|
|
*/
|
|
int format_mac_pxe(char *outbuf, size_t outbuf_len)
|
|
{
|
|
uchar ethaddr[6];
|
|
|
|
if (outbuf_len < 21) {
|
|
printf("outbuf is too small (%zd < 21)\n", outbuf_len);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
if (!eth_env_get_enetaddr_by_index("eth", eth_get_dev_index(), ethaddr))
|
|
return -ENOENT;
|
|
|
|
sprintf(outbuf, "01-%02x-%02x-%02x-%02x-%02x-%02x",
|
|
ethaddr[0], ethaddr[1], ethaddr[2],
|
|
ethaddr[3], ethaddr[4], ethaddr[5]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* get_relfile() - read a file relative to the PXE file
|
|
*
|
|
* As in pxelinux, paths to files referenced from files we retrieve are
|
|
* relative to the location of bootfile. get_relfile takes such a path and
|
|
* joins it with the bootfile path to get the full path to the target file. If
|
|
* the bootfile path is NULL, we use file_path as is.
|
|
*
|
|
* @ctx: PXE context
|
|
* @file_path: File path to read (relative to the PXE file)
|
|
* @addrp: On entry, address to load file or 0 to reserve an address with lmb;
|
|
* on exit, address to which the file was loaded
|
|
* @align: Reservation alignment, if using lmb
|
|
* @filesizep: If not NULL, returns the file size in bytes
|
|
* Returns 1 for success, or < 0 on error
|
|
*/
|
|
static int get_relfile(struct pxe_context *ctx, const char *file_path,
|
|
ulong *addrp, ulong align, enum bootflow_img_t type,
|
|
ulong *filesizep)
|
|
{
|
|
size_t path_len;
|
|
char relfile[MAX_TFTP_PATH_LEN + 1];
|
|
ulong size;
|
|
int ret;
|
|
|
|
if (file_path[0] == '/' && ctx->allow_abs_path)
|
|
*relfile = '\0';
|
|
else
|
|
strncpy(relfile, ctx->bootdir, MAX_TFTP_PATH_LEN);
|
|
|
|
path_len = strlen(file_path) + strlen(relfile);
|
|
|
|
if (path_len > MAX_TFTP_PATH_LEN) {
|
|
printf("Base path too long (%s%s)\n", relfile, file_path);
|
|
|
|
return -ENAMETOOLONG;
|
|
}
|
|
|
|
strcat(relfile, file_path);
|
|
|
|
printf("Retrieving file: %s\n", relfile);
|
|
|
|
ret = ctx->getfile(ctx, relfile, addrp, align, type, &size);
|
|
if (ret < 0)
|
|
return log_msg_ret("get", ret);
|
|
if (filesizep)
|
|
*filesizep = size;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int get_pxe_file(struct pxe_context *ctx, const char *file_path,
|
|
ulong file_addr)
|
|
{
|
|
ulong size;
|
|
int err;
|
|
char *buf;
|
|
|
|
err = get_relfile(ctx, file_path, &file_addr, 0, BFI_EXTLINUX_CFG,
|
|
&size);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
buf = map_sysmem(file_addr + size, 1);
|
|
*buf = '\0';
|
|
unmap_sysmem(buf);
|
|
|
|
return 1;
|
|
}
|
|
|
|
#define PXELINUX_DIR "pxelinux.cfg/"
|
|
|
|
/**
|
|
* get_pxelinux_path() - Get a file in the pxelinux.cfg/ directory
|
|
*
|
|
* @ctx: PXE context
|
|
* @file: Filename to process (relative to pxelinux.cfg/)
|
|
* Returns 1 for success, -ENAMETOOLONG if the resulting path is too long.
|
|
* or other value < 0 on other error
|
|
*/
|
|
int get_pxelinux_path(struct pxe_context *ctx, const char *file,
|
|
unsigned long pxefile_addr_r)
|
|
{
|
|
size_t base_len = strlen(PXELINUX_DIR);
|
|
char path[MAX_TFTP_PATH_LEN + 1];
|
|
|
|
if (base_len + strlen(file) > MAX_TFTP_PATH_LEN) {
|
|
printf("path (%s%s) too long, skipping\n",
|
|
PXELINUX_DIR, file);
|
|
return -ENAMETOOLONG;
|
|
}
|
|
|
|
sprintf(path, PXELINUX_DIR "%s", file);
|
|
|
|
return get_pxe_file(ctx, path, pxefile_addr_r);
|
|
}
|
|
|
|
/**
|
|
* get_relfile_envaddr() - read a file to an address in an env var
|
|
*
|
|
* Wrapper to make it easier to store the file at file_path in the location
|
|
* specified by envaddr_name. file_path will be joined to the bootfile path,
|
|
* if any is specified.
|
|
*
|
|
* @ctx: PXE context
|
|
* @file_path: File path to read (relative to the PXE file)
|
|
* @envaddr_name: Name of environment variable which contains the address to
|
|
* load to. If this doesn't exist, an address is reserved using LMB
|
|
* @align: Reservation alignment, if using lmb
|
|
* @type: File type
|
|
* @addrp: Returns the address to which the file was loaded, on success
|
|
* @filesizep: Returns the file size in bytes
|
|
* Returns 1 on success, -ENOENT if @envaddr_name does not exist as an
|
|
* environment variable, -EINVAL if its format is not valid hex, or other
|
|
* value < 0 on other error
|
|
*/
|
|
static int get_relfile_envaddr(struct pxe_context *ctx, const char *file_path,
|
|
const char *envaddr_name, ulong align,
|
|
enum bootflow_img_t type, ulong *addrp,
|
|
ulong *filesizep)
|
|
{
|
|
ulong addr = 0;
|
|
char *envaddr;
|
|
int ret;
|
|
|
|
/*
|
|
* set the address if we have it, otherwise get_relfile() will reserve
|
|
* a space
|
|
*/
|
|
envaddr = env_get(envaddr_name);
|
|
if (envaddr && strict_strtoul(envaddr, 16, &addr) < 0)
|
|
return -EINVAL;
|
|
|
|
ret = get_relfile(ctx, file_path, &addr, align, type, filesizep);
|
|
if (ret != 1)
|
|
return ret;
|
|
*addrp = addr;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* label_print() - Print a label and its string members if they're defined
|
|
*
|
|
* This is passed as a callback to the menu code for displaying each
|
|
* menu entry.
|
|
*
|
|
* @data: Label to print (is cast to struct pxe_label *)
|
|
*/
|
|
static void label_print(void *data)
|
|
{
|
|
struct pxe_label *label = data;
|
|
const char *c = label->menu ? label->menu : label->name;
|
|
|
|
printf("%s:\t%s\n", label->num, c);
|
|
}
|
|
|
|
/**
|
|
* label_localboot() - Boot a label that specified 'localboot'
|
|
*
|
|
* This requires that the 'localcmd' environment variable is defined. Its
|
|
* contents will be executed as U-Boot commands. If the label specified an
|
|
* 'append' line, its contents will be used to overwrite the contents of the
|
|
* 'bootargs' environment variable prior to running 'localcmd'.
|
|
*
|
|
* @label: Label to process
|
|
* Returns 1 on success or < 0 on error
|
|
*/
|
|
static int label_localboot(struct pxe_label *label)
|
|
{
|
|
char *localcmd;
|
|
|
|
localcmd = from_env("localcmd");
|
|
if (!localcmd)
|
|
return -ENOENT;
|
|
|
|
if (label->append) {
|
|
char bootargs[CONFIG_SYS_CBSIZE];
|
|
|
|
cli_simple_process_macros(label->append, bootargs,
|
|
sizeof(bootargs));
|
|
env_set("bootargs", bootargs);
|
|
}
|
|
|
|
debug("running: %s\n", localcmd);
|
|
|
|
return run_command_list(localcmd, strlen(localcmd), 0);
|
|
}
|
|
|
|
/*
|
|
* label_boot_kaslrseed generate kaslrseed from hw rng
|
|
*/
|
|
|
|
static void label_boot_kaslrseed(void)
|
|
{
|
|
#if CONFIG_IS_ENABLED(DM_RNG)
|
|
ulong fdt_addr;
|
|
struct fdt_header *working_fdt;
|
|
int err;
|
|
|
|
/* Get the main fdt and map it */
|
|
fdt_addr = hextoul(env_get("fdt_addr_r"), NULL);
|
|
working_fdt = map_sysmem(fdt_addr, 0);
|
|
err = fdt_check_header(working_fdt);
|
|
if (err)
|
|
return;
|
|
|
|
/* add extra size for holding kaslr-seed */
|
|
/* err is new fdt size, 0 or negtive */
|
|
err = fdt_shrink_to_minimum(working_fdt, 512);
|
|
if (err <= 0)
|
|
return;
|
|
|
|
fdt_kaslrseed(working_fdt, true);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* label_boot_fdtoverlay() - Loads fdt overlays specified in 'fdtoverlays'
|
|
* or 'devicetree-overlay'
|
|
*
|
|
* @ctx: PXE context
|
|
* @label: Label to process
|
|
*/
|
|
#ifdef CONFIG_OF_LIBFDT_OVERLAY
|
|
static void label_boot_fdtoverlay(struct pxe_context *ctx,
|
|
struct pxe_label *label)
|
|
{
|
|
char *fdtoverlay = label->fdtoverlays;
|
|
struct fdt_header *working_fdt;
|
|
char *fdtoverlay_addr_env;
|
|
ulong fdtoverlay_addr;
|
|
ulong fdt_addr;
|
|
int err;
|
|
|
|
/* Get the main fdt and map it */
|
|
fdt_addr = hextoul(env_get("fdt_addr_r"), NULL);
|
|
working_fdt = map_sysmem(fdt_addr, 0);
|
|
err = fdt_check_header(working_fdt);
|
|
if (err)
|
|
return;
|
|
|
|
/* Get the specific overlay loading address */
|
|
fdtoverlay_addr_env = env_get("fdtoverlay_addr_r");
|
|
if (!fdtoverlay_addr_env) {
|
|
printf("Invalid fdtoverlay_addr_r for loading overlays\n");
|
|
return;
|
|
}
|
|
|
|
fdtoverlay_addr = hextoul(fdtoverlay_addr_env, NULL);
|
|
|
|
/* Cycle over the overlay files and apply them in order */
|
|
do {
|
|
struct fdt_header *blob;
|
|
char *overlayfile;
|
|
ulong addr;
|
|
char *end;
|
|
int len;
|
|
|
|
/* Drop leading spaces */
|
|
while (*fdtoverlay == ' ')
|
|
++fdtoverlay;
|
|
|
|
/* Copy a single filename if multiple provided */
|
|
end = strstr(fdtoverlay, " ");
|
|
if (end) {
|
|
len = (int)(end - fdtoverlay);
|
|
overlayfile = malloc(len + 1);
|
|
strncpy(overlayfile, fdtoverlay, len);
|
|
overlayfile[len] = '\0';
|
|
} else
|
|
overlayfile = fdtoverlay;
|
|
|
|
if (!strlen(overlayfile))
|
|
goto skip_overlay;
|
|
|
|
/* Load overlay file */
|
|
err = get_relfile_envaddr(ctx, overlayfile, "fdtoverlay_addr_r",
|
|
SZ_4K,
|
|
(enum bootflow_img_t)IH_TYPE_FLATDT,
|
|
&addr, NULL);
|
|
if (err < 0) {
|
|
printf("Failed loading overlay %s\n", overlayfile);
|
|
goto skip_overlay;
|
|
}
|
|
|
|
/* Resize main fdt */
|
|
fdt_shrink_to_minimum(working_fdt, 8192);
|
|
|
|
blob = map_sysmem(fdtoverlay_addr, 0);
|
|
err = fdt_check_header(blob);
|
|
if (err) {
|
|
printf("Invalid overlay %s, skipping\n",
|
|
overlayfile);
|
|
goto skip_overlay;
|
|
}
|
|
|
|
err = fdt_overlay_apply_verbose(working_fdt, blob);
|
|
if (err) {
|
|
printf("Failed to apply overlay %s, skipping\n",
|
|
overlayfile);
|
|
goto skip_overlay;
|
|
}
|
|
|
|
skip_overlay:
|
|
if (end)
|
|
free(overlayfile);
|
|
} while ((fdtoverlay = strstr(fdtoverlay, " ")));
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* label_process_fdt() - Process FDT for the label
|
|
*
|
|
* @ctx: PXE context
|
|
* @label: Label to process
|
|
* @kernel_addr: String containing kernel address
|
|
* @fdt_argp: bootm argument to fill in, for FDT
|
|
* Return: 0 if OK, -ENOMEM if out of memory, -ENOENT if FDT file could not be
|
|
* loaded
|
|
*
|
|
* fdt usage is optional:
|
|
* It handles the following scenarios.
|
|
*
|
|
* Scenario 1: If fdt_addr_r specified and "fdt" or "fdtdir" label is
|
|
* defined in pxe file, retrieve fdt blob from server. Pass fdt_addr_r to
|
|
* bootm, and adjust argc appropriately.
|
|
*
|
|
* If retrieve fails and no exact fdt blob is specified in pxe file with
|
|
* "fdt" label, try Scenario 2.
|
|
*
|
|
* Scenario 2: If there is an fdt_addr specified, pass it along to
|
|
* bootm, and adjust argc appropriately.
|
|
*
|
|
* Scenario 3: If there is an fdtcontroladdr specified, pass it along to
|
|
* bootm, and adjust argc appropriately, unless the image type is fitImage.
|
|
*
|
|
* Scenario 4: fdt blob is not available.
|
|
*/
|
|
static int label_process_fdt(struct pxe_context *ctx, struct pxe_label *label,
|
|
char *kernel_addr, const char **fdt_argp)
|
|
{
|
|
log_debug("label '%s' kernel_addr '%s' label->fdt '%s' fdtdir '%s' "
|
|
"kernel_label '%s' fdt_argp '%s'\n",
|
|
label->name, kernel_addr, label->fdt, label->fdtdir,
|
|
label->kernel_label, *fdt_argp);
|
|
|
|
/* For FIT, the label can be identical to kernel one */
|
|
if (label->fdt && !strcmp(label->kernel_label, label->fdt)) {
|
|
*fdt_argp = kernel_addr;
|
|
/* if fdt label is defined then get fdt from server */
|
|
} else if (*fdt_argp) {
|
|
char *fdtfile = NULL;
|
|
char *fdtfilefree = NULL;
|
|
|
|
if (label->fdt) {
|
|
if (IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS)) {
|
|
if (strcmp("-", label->fdt))
|
|
fdtfile = label->fdt;
|
|
} else {
|
|
fdtfile = label->fdt;
|
|
}
|
|
} else if (label->fdtdir) {
|
|
char *f1, *f2, *f3, *f4, *slash;
|
|
int len;
|
|
|
|
f1 = env_get("fdtfile");
|
|
if (f1) {
|
|
f2 = "";
|
|
f3 = "";
|
|
f4 = "";
|
|
} else {
|
|
/*
|
|
* For complex cases where this code doesn't
|
|
* generate the correct filename, the board
|
|
* code should set $fdtfile during early boot,
|
|
* or the boot scripts should set $fdtfile
|
|
* before invoking "pxe" or "sysboot".
|
|
*/
|
|
f1 = env_get("soc");
|
|
f2 = "-";
|
|
f3 = env_get("board");
|
|
f4 = ".dtb";
|
|
if (!f1) {
|
|
f1 = "";
|
|
f2 = "";
|
|
}
|
|
if (!f3) {
|
|
f2 = "";
|
|
f3 = "";
|
|
}
|
|
}
|
|
|
|
len = strlen(label->fdtdir);
|
|
if (!len)
|
|
slash = "./";
|
|
else if (label->fdtdir[len - 1] != '/')
|
|
slash = "/";
|
|
else
|
|
slash = "";
|
|
|
|
len = strlen(label->fdtdir) + strlen(slash) +
|
|
strlen(f1) + strlen(f2) + strlen(f3) +
|
|
strlen(f4) + 1;
|
|
fdtfilefree = malloc(len);
|
|
if (!fdtfilefree) {
|
|
printf("malloc fail (FDT filename)\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
snprintf(fdtfilefree, len, "%s%s%s%s%s%s",
|
|
label->fdtdir, slash, f1, f2, f3, f4);
|
|
fdtfile = fdtfilefree;
|
|
}
|
|
|
|
if (fdtfile) {
|
|
ulong addr;
|
|
int err;
|
|
|
|
err = get_relfile_envaddr(ctx, fdtfile, "fdt_addr_r",
|
|
SZ_4K,
|
|
(enum bootflow_img_t)IH_TYPE_FLATDT,
|
|
&addr, NULL);
|
|
|
|
free(fdtfilefree);
|
|
if (err < 0) {
|
|
*fdt_argp = NULL;
|
|
|
|
if (label->fdt) {
|
|
printf("Skipping %s for failure retrieving FDT\n",
|
|
label->name);
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (label->fdtdir) {
|
|
printf("Skipping fdtdir %s for failure retrieving dts\n",
|
|
label->fdtdir);
|
|
}
|
|
}
|
|
|
|
if (label->kaslrseed)
|
|
label_boot_kaslrseed();
|
|
|
|
#ifdef CONFIG_OF_LIBFDT_OVERLAY
|
|
if (label->fdtoverlays)
|
|
label_boot_fdtoverlay(ctx, label);
|
|
#endif
|
|
} else {
|
|
*fdt_argp = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* label_run_boot() - Set up the FDT and call the appropriate bootm/z/i command
|
|
*
|
|
* @ctx: PXE context
|
|
* @label: Label to process
|
|
* @kern_addr_str: String containing kernel address and possible FIT
|
|
* configuration (cannot be NULL)
|
|
* @kern_addr: Kernel address (cannot be 0)
|
|
* @kern_size: Kernel size in bytes
|
|
* @initrd_addr: String containing initrd address (0 if none)
|
|
* @initrd_size: initrd size (only used if @initrd_addr)
|
|
* @initrd_str: initrd string to process (only used if @initrd_addr)
|
|
* @conf_fdt_str: string containing the FDT address
|
|
* @conf_fdt: FDT address (0 if none)
|
|
* Return: does not return on success, or returns 0 if the boot command
|
|
* returned, or -ve error value on error
|
|
*/
|
|
static int label_run_boot(struct pxe_context *ctx, struct pxe_label *label,
|
|
char *kern_addr_str, ulong kern_addr, ulong kern_size,
|
|
ulong initrd_addr, ulong initrd_size,
|
|
char *initrd_str, const char *conf_fdt_str,
|
|
ulong conf_fdt)
|
|
{
|
|
struct bootm_info bmi;
|
|
int ret = 0;
|
|
void *buf;
|
|
enum image_fmt_t fmt;
|
|
|
|
log_debug("label '%s' kern_addr_str '%s' kern_addr %lx initrd_addr %lx "
|
|
"initrd_size %lx initrd_str '%s' conf_fdt_str '%s' "
|
|
"conf_fdt %lx\n", label->name, kern_addr_str, kern_addr,
|
|
initrd_addr, initrd_size, initrd_str, conf_fdt_str, conf_fdt);
|
|
|
|
bootm_init(&bmi);
|
|
|
|
bmi.addr_img = kern_addr_str;
|
|
bmi.conf_fdt = conf_fdt_str;
|
|
bootm_x86_set(&bmi, bzimage_addr, hextoul(kern_addr_str, NULL));
|
|
bmi.os_size = kern_size;
|
|
|
|
if (initrd_addr) {
|
|
bmi.conf_ramdisk = initrd_str;
|
|
bootm_x86_set(&bmi, initrd_addr, initrd_addr);
|
|
bootm_x86_set(&bmi, initrd_size, initrd_size);
|
|
}
|
|
|
|
buf = map_sysmem(kern_addr, 0);
|
|
|
|
/*
|
|
* Try bootm for legacy and FIT format image, assume booti if
|
|
* compressed
|
|
*/
|
|
fmt = genimg_get_format_comp(buf);
|
|
|
|
if (IS_ENABLED(CONFIG_CMD_BOOTM) && (fmt == IMAGE_FORMAT_FIT ||
|
|
fmt == IMAGE_FORMAT_LEGACY)) {
|
|
int states;
|
|
|
|
states = ctx->restart ? BOOTM_STATE_RESTART : BOOTM_STATE_START;
|
|
log_debug("using bootm fake_go=%d\n", ctx->fake_go);
|
|
if (ctx->fake_go)
|
|
states |= BOOTM_STATE_OS_FAKE_GO;
|
|
else
|
|
states |= BOOTM_STATE_OS_GO;
|
|
ret = boot_run(&bmi, "ext", states | BOOTM_STATE_FINDOS |
|
|
BOOTM_STATE_PRE_LOAD | BOOTM_STATE_FINDOTHER |
|
|
BOOTM_STATE_LOADOS);
|
|
/* Try booting an AArch64 Linux kernel image */
|
|
} else if (IS_ENABLED(CONFIG_CMD_BOOTI) && fmt == IMAGE_FORMAT_BOOTI) {
|
|
log_debug("using booti\n");
|
|
ret = booti_run(&bmi);
|
|
/* Try booting a Image */
|
|
} else if (IS_ENABLED(CONFIG_CMD_BOOTZ)) {
|
|
log_debug("using bootz\n");
|
|
ret = bootz_run(&bmi);
|
|
/* Try booting an x86_64 Linux kernel image */
|
|
} else if (IS_ENABLED(CONFIG_ZBOOT)) {
|
|
log_debug("using zboot\n");
|
|
ret = zboot_run(&bmi);
|
|
}
|
|
|
|
unmap_sysmem(buf);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* generate_localboot() - Try to come up with a localboot definition
|
|
*
|
|
* Adds a default kernel and initrd filename for use with localboot
|
|
*
|
|
* @label: Label to process
|
|
* Return 0 if OK, -ENOMEM if out of memory
|
|
*/
|
|
static int generate_localboot(struct pxe_label *label)
|
|
{
|
|
label->kernel = strdup("/vmlinuz");
|
|
label->kernel_label = strdup(label->kernel);
|
|
label->initrd = strdup("/initrd.img");
|
|
if (!label->kernel || !label->kernel_label || !label->initrd)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* label_boot() - Boot according to the contents of a pxe_label
|
|
*
|
|
* If we can't boot for any reason, we return. A successful boot never
|
|
* returns.
|
|
*
|
|
* The kernel will be stored in the location given by the 'kernel_addr_r'
|
|
* environment variable.
|
|
*
|
|
* If the label specifies an initrd file, it will be stored in the location
|
|
* given by the 'ramdisk_addr_r' environment variable.
|
|
*
|
|
* If the label specifies an 'append' line, its contents will overwrite that
|
|
* of the 'bootargs' environment variable.
|
|
*
|
|
* @ctx: PXE context
|
|
* @label: Label to process
|
|
* Returns does not return on success, otherwise returns 0 if a localboot
|
|
* label was processed, or 1 on error
|
|
*/
|
|
static int label_boot(struct pxe_context *ctx, struct pxe_label *label)
|
|
{
|
|
char *kern_addr_str;
|
|
ulong kern_addr = 0;
|
|
ulong initrd_addr = 0;
|
|
ulong initrd_size = 0;
|
|
char initrd_str[28] = "";
|
|
char mac_str[29] = "";
|
|
char ip_str[68] = "";
|
|
char fit_addr[200];
|
|
const char *conf_fdt_str;
|
|
ulong conf_fdt = 0;
|
|
ulong kern_size;
|
|
int ret;
|
|
|
|
label_print(label);
|
|
|
|
label->attempted = 1;
|
|
|
|
if (label->localboot) {
|
|
if (label->localboot_val >= 0) {
|
|
ret = label_localboot(label);
|
|
|
|
if (IS_ENABLED(CONFIG_BOOTMETH_EXTLINUX_LOCALBOOT) &&
|
|
ret == -ENOENT)
|
|
ret = generate_localboot(label);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!label->kernel) {
|
|
printf("No kernel given, skipping %s\n",
|
|
label->name);
|
|
return 1;
|
|
}
|
|
|
|
if (get_relfile_envaddr(ctx, label->kernel, "kernel_addr_r", SZ_2M,
|
|
(enum bootflow_img_t)IH_TYPE_KERNEL,
|
|
&kern_addr, &kern_size) < 0) {
|
|
printf("Skipping %s for failure retrieving kernel\n",
|
|
label->name);
|
|
return 1;
|
|
}
|
|
|
|
/* for FIT, append the configuration identifier */
|
|
snprintf(fit_addr, sizeof(fit_addr), "%lx%s", kern_addr,
|
|
label->config ? label->config : "");
|
|
kern_addr_str = fit_addr;
|
|
|
|
/* For FIT, the label can be identical to kernel one */
|
|
if (label->initrd && !strcmp(label->kernel_label, label->initrd)) {
|
|
initrd_addr = kern_addr;
|
|
} else if (label->initrd) {
|
|
ulong size;
|
|
int ret;
|
|
|
|
ret = get_relfile_envaddr(ctx, label->initrd, "ramdisk_addr_r",
|
|
SZ_2M,
|
|
(enum bootflow_img_t)IH_TYPE_RAMDISK,
|
|
&initrd_addr, &size);
|
|
if (ret < 0) {
|
|
printf("Skipping %s for failure retrieving initrd\n",
|
|
label->name);
|
|
return 1;
|
|
}
|
|
initrd_size = size;
|
|
size = snprintf(initrd_str, sizeof(initrd_str), "%lx:%lx",
|
|
initrd_addr, size);
|
|
if (size >= sizeof(initrd_str))
|
|
return 1;
|
|
}
|
|
|
|
if (label->ipappend & 0x1) {
|
|
sprintf(ip_str, " ip=%s:%s:%s:%s",
|
|
env_get("ipaddr"), env_get("serverip"),
|
|
env_get("gatewayip"), env_get("netmask"));
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_CMD_NET)) {
|
|
if (label->ipappend & 0x2) {
|
|
int err;
|
|
|
|
strcpy(mac_str, " BOOTIF=");
|
|
err = format_mac_pxe(mac_str + 8, sizeof(mac_str) - 8);
|
|
if (err < 0)
|
|
mac_str[0] = '\0';
|
|
}
|
|
}
|
|
|
|
if ((label->ipappend & 0x3) || label->append) {
|
|
char bootargs[CONFIG_SYS_CBSIZE] = "";
|
|
char finalbootargs[CONFIG_SYS_CBSIZE];
|
|
|
|
if (strlen(label->append ?: "") +
|
|
strlen(ip_str) + strlen(mac_str) + 1 > sizeof(bootargs)) {
|
|
printf("bootarg overflow %zd+%zd+%zd+1 > %zd\n",
|
|
strlen(label->append ?: ""),
|
|
strlen(ip_str), strlen(mac_str),
|
|
sizeof(bootargs));
|
|
return 1;
|
|
}
|
|
|
|
if (label->append)
|
|
strlcpy(bootargs, label->append, sizeof(bootargs));
|
|
|
|
strcat(bootargs, ip_str);
|
|
strcat(bootargs, mac_str);
|
|
|
|
cli_simple_process_macros(bootargs, finalbootargs,
|
|
sizeof(finalbootargs));
|
|
env_set("bootargs", finalbootargs);
|
|
printf("append: %s\n", finalbootargs);
|
|
}
|
|
|
|
conf_fdt_str = env_get("fdt_addr_r");
|
|
ret = label_process_fdt(ctx, label, kern_addr_str, &conf_fdt_str);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!conf_fdt_str) {
|
|
if (!IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS) ||
|
|
strcmp("-", label->fdt)) {
|
|
conf_fdt_str = env_get("fdt_addr");
|
|
log_debug("using fdt_addr '%s'\n", conf_fdt_str);
|
|
}
|
|
}
|
|
|
|
if (!conf_fdt_str) {
|
|
void *buf;
|
|
|
|
buf = map_sysmem(kern_addr, 0);
|
|
if (genimg_get_format(buf) != IMAGE_FORMAT_FIT) {
|
|
if (!IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS) ||
|
|
strcmp("-", label->fdt)) {
|
|
conf_fdt_str = env_get("fdtcontroladdr");
|
|
log_debug("using fdtcontroladdr '%s'\n",
|
|
conf_fdt_str);
|
|
}
|
|
}
|
|
unmap_sysmem(buf);
|
|
}
|
|
if (conf_fdt_str)
|
|
conf_fdt = hextoul(conf_fdt_str, NULL);
|
|
log_debug("conf_fdt %lx\n", conf_fdt);
|
|
|
|
if (ctx->bflow && conf_fdt_str)
|
|
ctx->bflow->fdt_addr = conf_fdt;
|
|
|
|
if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && ctx->no_boot) {
|
|
ctx->label = label;
|
|
ctx->kern_addr_str = strdup(kern_addr_str);
|
|
ctx->kern_addr = kern_addr;
|
|
ctx->kern_size = kern_size;
|
|
if (initrd_addr) {
|
|
ctx->initrd_addr = initrd_addr;
|
|
ctx->initrd_size = initrd_size;
|
|
ctx->initrd_str = strdup(initrd_str);
|
|
}
|
|
ctx->conf_fdt_str = strdup(conf_fdt_str);
|
|
ctx->conf_fdt = conf_fdt;
|
|
log_debug("Saving label '%s':\n", label->name);
|
|
log_debug("- kern_addr_str '%s' conf_fdt_str '%s' conf_fdt %lx\n",
|
|
ctx->kern_addr_str, ctx->conf_fdt_str, conf_fdt);
|
|
if (initrd_addr) {
|
|
log_debug("- initrd addr %lx filesize %lx str '%s'\n",
|
|
ctx->initrd_addr, ctx->initrd_size,
|
|
ctx->initrd_str);
|
|
}
|
|
if (!ctx->kern_addr_str || (conf_fdt_str && !ctx->conf_fdt_str) ||
|
|
(initrd_addr && !ctx->initrd_str)) {
|
|
printf("malloc fail (saving label)\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
label_run_boot(ctx, label, kern_addr_str, kern_addr, kern_size,
|
|
initrd_addr, initrd_size, initrd_str, conf_fdt_str,
|
|
conf_fdt);
|
|
/* ignore the error value since we are going to fail anyway */
|
|
|
|
return 1; /* returning is always failure */
|
|
}
|
|
|
|
/*
|
|
*/
|
|
void destroy_pxe_menu(struct pxe_menu *cfg)
|
|
{
|
|
struct list_head *pos, *n;
|
|
struct pxe_label *label;
|
|
|
|
free(cfg->title);
|
|
free(cfg->default_label);
|
|
free(cfg->fallback_label);
|
|
|
|
list_for_each_safe(pos, n, &cfg->labels) {
|
|
label = list_entry(pos, struct pxe_label, list);
|
|
|
|
label_destroy(label);
|
|
}
|
|
|
|
free(cfg);
|
|
}
|
|
|
|
struct pxe_menu *parse_pxefile(struct pxe_context *ctx, unsigned long menucfg)
|
|
{
|
|
struct pxe_menu *cfg;
|
|
char *buf;
|
|
int r;
|
|
|
|
cfg = malloc(sizeof(struct pxe_menu));
|
|
if (!cfg)
|
|
return NULL;
|
|
|
|
memset(cfg, 0, sizeof(struct pxe_menu));
|
|
|
|
INIT_LIST_HEAD(&cfg->labels);
|
|
|
|
buf = map_sysmem(menucfg, 0);
|
|
r = parse_pxefile_top(ctx, buf, menucfg, cfg, 1);
|
|
|
|
if (ctx->use_fallback) {
|
|
if (cfg->fallback_label) {
|
|
printf("Setting use of fallback\n");
|
|
cfg->default_label = cfg->fallback_label;
|
|
} else {
|
|
printf("Selected fallback option, but not set\n");
|
|
}
|
|
}
|
|
|
|
unmap_sysmem(buf);
|
|
if (r < 0) {
|
|
destroy_pxe_menu(cfg);
|
|
return NULL;
|
|
}
|
|
|
|
return cfg;
|
|
}
|
|
|
|
/*
|
|
* Converts a pxe_menu struct into a menu struct for use with U-Boot's generic
|
|
* menu code.
|
|
*/
|
|
static struct menu *pxe_menu_to_menu(struct pxe_menu *cfg)
|
|
{
|
|
struct pxe_label *label;
|
|
struct list_head *pos;
|
|
struct menu *m;
|
|
char *label_override;
|
|
int err;
|
|
int i = 1;
|
|
char *default_num = NULL;
|
|
char *override_num = NULL;
|
|
|
|
/*
|
|
* Create a menu and add items for all the labels.
|
|
*/
|
|
m = menu_create(cfg->title, DIV_ROUND_UP(cfg->timeout, 10),
|
|
cfg->prompt, NULL, label_print, NULL, NULL, NULL);
|
|
if (!m)
|
|
return NULL;
|
|
|
|
label_override = env_get("pxe_label_override");
|
|
|
|
list_for_each(pos, &cfg->labels) {
|
|
label = list_entry(pos, struct pxe_label, list);
|
|
|
|
sprintf(label->num, "%d", i++);
|
|
if (menu_item_add(m, label->num, label) != 1) {
|
|
menu_destroy(m);
|
|
return NULL;
|
|
}
|
|
if (cfg->default_label &&
|
|
(strcmp(label->name, cfg->default_label) == 0))
|
|
default_num = label->num;
|
|
if (label_override && !strcmp(label->name, label_override))
|
|
override_num = label->num;
|
|
}
|
|
|
|
if (label_override) {
|
|
if (override_num)
|
|
default_num = override_num;
|
|
else
|
|
printf("Missing override pxe label: %s\n",
|
|
label_override);
|
|
}
|
|
|
|
/*
|
|
* After we've created items for each label in the menu, set the
|
|
* menu's default label if one was specified.
|
|
*/
|
|
if (default_num) {
|
|
err = menu_default_set(m, default_num);
|
|
if (err != 1) {
|
|
if (err != -ENOENT) {
|
|
menu_destroy(m);
|
|
return NULL;
|
|
}
|
|
|
|
printf("Missing default: %s\n", cfg->default_label);
|
|
}
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
/*
|
|
* Try to boot any labels we have yet to attempt to boot.
|
|
*/
|
|
static void boot_unattempted_labels(struct pxe_context *ctx,
|
|
struct pxe_menu *cfg)
|
|
{
|
|
struct list_head *pos;
|
|
struct pxe_label *label;
|
|
|
|
log_debug("Booting unattempted labels\n");
|
|
list_for_each(pos, &cfg->labels) {
|
|
label = list_entry(pos, struct pxe_label, list);
|
|
|
|
if (!label->attempted) {
|
|
log_debug("attempt: %s\n", label->name);
|
|
if (!label_boot(ctx, label))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void handle_pxe_menu(struct pxe_context *ctx, struct pxe_menu *cfg)
|
|
{
|
|
void *choice;
|
|
struct menu *m;
|
|
int err;
|
|
|
|
if (IS_ENABLED(CONFIG_CMD_BMP)) {
|
|
/* display BMP if available */
|
|
if (cfg->bmp) {
|
|
if (get_relfile(ctx, cfg->bmp, &image_load_addr, 0,
|
|
BFI_LOGO, NULL)) {
|
|
#if defined(CONFIG_VIDEO)
|
|
struct udevice *dev;
|
|
|
|
err = uclass_first_device_err(UCLASS_VIDEO, &dev);
|
|
if (!err)
|
|
video_clear(dev);
|
|
#endif
|
|
bmp_display(image_load_addr,
|
|
BMP_ALIGN_CENTER, BMP_ALIGN_CENTER);
|
|
} else {
|
|
printf("Skipping background bmp %s for failure\n",
|
|
cfg->bmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
m = pxe_menu_to_menu(cfg);
|
|
if (!m)
|
|
return;
|
|
|
|
err = menu_get_choice(m, &choice);
|
|
menu_destroy(m);
|
|
|
|
/*
|
|
* err == 1 means we got a choice back from menu_get_choice.
|
|
*
|
|
* err == -ENOENT if the menu was setup to select the default but no
|
|
* default was set. in that case, we should continue trying to boot
|
|
* labels that haven't been attempted yet.
|
|
*
|
|
* otherwise, the user interrupted or there was some other error and
|
|
* we give up.
|
|
*/
|
|
|
|
if (err == 1) {
|
|
err = label_boot(ctx, choice);
|
|
log_debug("label_boot() returns %d\n", err);
|
|
if (!err)
|
|
return;
|
|
} else if (err != -ENOENT) {
|
|
return;
|
|
}
|
|
|
|
boot_unattempted_labels(ctx, cfg);
|
|
}
|
|
|
|
int pxe_setup_ctx(struct pxe_context *ctx, pxe_getfile_func getfile,
|
|
void *userdata, bool allow_abs_path, const char *bootfile,
|
|
bool use_ipv6, bool use_fallback, struct bootflow *bflow)
|
|
{
|
|
const char *last_slash;
|
|
size_t path_len = 0;
|
|
|
|
memset(ctx, '\0', sizeof(*ctx));
|
|
ctx->getfile = getfile;
|
|
ctx->userdata = userdata;
|
|
ctx->allow_abs_path = allow_abs_path;
|
|
ctx->use_ipv6 = use_ipv6;
|
|
ctx->use_fallback = use_fallback;
|
|
ctx->bflow = bflow;
|
|
|
|
/* figure out the boot directory, if there is one */
|
|
if (bootfile && strlen(bootfile) >= MAX_TFTP_PATH_LEN)
|
|
return -ENOSPC;
|
|
ctx->bootdir = strdup(bootfile ? bootfile : "");
|
|
if (!ctx->bootdir)
|
|
return -ENOMEM;
|
|
|
|
if (bootfile) {
|
|
last_slash = strrchr(bootfile, '/');
|
|
if (last_slash)
|
|
path_len = (last_slash - bootfile) + 1;
|
|
}
|
|
ctx->bootdir[path_len] = '\0';
|
|
|
|
return 0;
|
|
}
|
|
|
|
void pxe_destroy_ctx(struct pxe_context *ctx)
|
|
{
|
|
free(ctx->bootdir);
|
|
}
|
|
|
|
struct pxe_menu *pxe_prepare(struct pxe_context *ctx, ulong pxefile_addr_r,
|
|
bool prompt)
|
|
{
|
|
struct pxe_menu *cfg;
|
|
|
|
cfg = parse_pxefile(ctx, pxefile_addr_r);
|
|
if (!cfg) {
|
|
printf("Error parsing config file\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (prompt)
|
|
cfg->prompt = prompt;
|
|
|
|
return cfg;
|
|
}
|
|
|
|
int pxe_process(struct pxe_context *ctx, ulong pxefile_addr_r, bool prompt)
|
|
{
|
|
struct pxe_menu *cfg;
|
|
|
|
cfg = pxe_prepare(ctx, pxefile_addr_r, prompt);
|
|
if (!cfg)
|
|
return 1;
|
|
|
|
handle_pxe_menu(ctx, cfg);
|
|
|
|
destroy_pxe_menu(cfg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pxe_probe(struct pxe_context *ctx, ulong pxefile_addr_r, bool prompt)
|
|
{
|
|
ctx->cfg = pxe_prepare(ctx, pxefile_addr_r, prompt);
|
|
if (!ctx->cfg)
|
|
return -EINVAL;
|
|
ctx->no_boot = true;
|
|
|
|
handle_pxe_menu(ctx, ctx->cfg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pxe_do_boot(struct pxe_context *ctx)
|
|
{
|
|
int ret;
|
|
|
|
if (!ctx->label)
|
|
return log_msg_ret("pxb", -ENOENT);
|
|
|
|
ret = label_run_boot(ctx, ctx->label, ctx->kern_addr_str,
|
|
ctx->kern_addr, ctx->kern_size, ctx->initrd_addr,
|
|
ctx->initrd_size, ctx->initrd_str,
|
|
ctx->conf_fdt_str, ctx->conf_fdt);
|
|
if (ret)
|
|
return log_msg_ret("lrb", ret);
|
|
|
|
return 0;
|
|
}
|