// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }