Files
u-boot/boot/pxe_utils.c
Simon Glass aaf889390f boot: Support a fake go with pxe
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>
2025-09-22 11:23:05 -06:00

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;
}