diff options
Diffstat (limited to 'cmd/pxe.c')
-rw-r--r-- | cmd/pxe.c | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/cmd/pxe.c b/cmd/pxe.c new file mode 100644 index 00000000000..982e2b1e7ea --- /dev/null +++ b/cmd/pxe.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2010-2011 Calxeda, Inc. + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ + +#include <command.h> +#include <fs.h> +#include <net.h> +#include <net6.h> +#include <malloc.h> +#include <vsprintf.h> + +#include "pxe_utils.h" + +#ifdef CONFIG_CMD_NET +const char *pxe_default_paths[] = { +#ifdef CONFIG_SYS_SOC +#ifdef CONFIG_SYS_BOARD + "default-" CONFIG_SYS_ARCH "-" CONFIG_SYS_SOC "-" CONFIG_SYS_BOARD, +#endif + "default-" CONFIG_SYS_ARCH "-" CONFIG_SYS_SOC, +#endif + "default-" CONFIG_SYS_ARCH, + "default", + NULL +}; + +static int do_get_tftp(struct pxe_context *ctx, const char *file_path, + char *file_addr, ulong *sizep) +{ + char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; + int ret; + int num_args; + + tftp_argv[1] = file_addr; + tftp_argv[2] = (void *)file_path; + if (ctx->use_ipv6) { + tftp_argv[3] = USE_IP6_CMD_PARAM; + num_args = 4; + } else { + num_args = 3; + } + + if (do_tftpb(ctx->cmdtp, 0, num_args, tftp_argv)) + return -ENOENT; + + ret = pxe_get_file_size(sizep); + if (ret) + return log_msg_ret("tftp", ret); + ctx->pxe_file_size = *sizep; + + return 1; +} + +/* + * Looks for a pxe file with specified config file name, + * which is received from DHCPv4 option 209 or + * DHCPv6 option 60. + * + * Returns 1 on success or < 0 on error. + */ +static int pxe_dhcp_option_path(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + int ret = get_pxe_file(ctx, pxelinux_configfile, pxefile_addr_r); + + free(pxelinux_configfile); + + return ret; +} + +/* + * Looks for a pxe file with a name based on the pxeuuid environment variable. + * + * Returns 1 on success or < 0 on error. + */ +static int pxe_uuid_path(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + char *uuid_str; + + uuid_str = from_env("pxeuuid"); + + if (!uuid_str) + return -ENOENT; + + return get_pxelinux_path(ctx, uuid_str, pxefile_addr_r); +} + +/* + * Looks for a pxe file with a name based on the 'ethaddr' environment + * variable. + * + * Returns 1 on success or < 0 on error. + */ +static int pxe_mac_path(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + char mac_str[21]; + int err; + + err = format_mac_pxe(mac_str, sizeof(mac_str)); + + if (err < 0) + return err; + + return get_pxelinux_path(ctx, mac_str, pxefile_addr_r); +} + +/* + * Looks for pxe files with names based on our IP address. See pxelinux + * documentation for details on what these file names look like. We match + * that exactly. + * + * Returns 1 on success or < 0 on error. + */ +static int pxe_ipaddr_paths(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + char ip_addr[9]; + int mask_pos, err; + + sprintf(ip_addr, "%08X", ntohl(net_ip.s_addr)); + + for (mask_pos = 7; mask_pos >= 0; mask_pos--) { + err = get_pxelinux_path(ctx, ip_addr, pxefile_addr_r); + + if (err > 0) + return err; + + ip_addr[mask_pos] = '\0'; + } + + return -ENOENT; +} + +int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep, bool use_ipv6) +{ + struct cmd_tbl cmdtp[] = {}; /* dummy */ + struct pxe_context ctx; + int i; + + if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false, + env_get("bootfile"), use_ipv6, false)) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_BOOTP_PXE_DHCP_OPTION) && + pxelinux_configfile && !use_ipv6) { + if (pxe_dhcp_option_path(&ctx, pxefile_addr_r) > 0) + goto done; + + goto error_exit; + } + + if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION) && + pxelinux_configfile && use_ipv6) { + if (pxe_dhcp_option_path(&ctx, pxefile_addr_r) > 0) + goto done; + + goto error_exit; + } + + /* + * Keep trying paths until we successfully get a file we're looking + * for. + */ + if (pxe_uuid_path(&ctx, pxefile_addr_r) > 0 || + pxe_mac_path(&ctx, pxefile_addr_r) > 0 || + pxe_ipaddr_paths(&ctx, pxefile_addr_r) > 0) + goto done; + + i = 0; + while (pxe_default_paths[i]) { + if (get_pxelinux_path(&ctx, pxe_default_paths[i], + pxefile_addr_r) > 0) + goto done; + i++; + } + +error_exit: + pxe_destroy_ctx(&ctx); + + return -ENOENT; +done: + *bootdirp = env_get("bootfile"); + + /* + * The PXE file size is returned but not the name. It is probably not + * that useful. + */ + *sizep = ctx.pxe_file_size; + pxe_destroy_ctx(&ctx); + + return 0; +} + +/* + * Entry point for the 'pxe get' command. + * This Follows pxelinux's rules to download a config file from a tftp server. + * The file is stored at the location given by the pxefile_addr_r environment + * variable, which must be set. + * + * UUID comes from pxeuuid env variable, if defined + * MAC addr comes from ethaddr env variable, if defined + * IP + * + * see http://syslinux.zytor.com/wiki/index.php/PXELINUX + * + * Returns 0 on success or 1 on error. + */ +static int +do_pxe_get(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *pxefile_addr_str; + ulong pxefile_addr_r; + char *fname; + ulong size; + int ret; + bool use_ipv6 = false; + + if (IS_ENABLED(CONFIG_IPV6)) { + if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM)) + use_ipv6 = true; + + if (!(argc == 1 || (argc == 2 && use_ipv6))) + return CMD_RET_USAGE; + } else { + if (argc != 1) + return CMD_RET_USAGE; + } + + pxefile_addr_str = from_env("pxefile_addr_r"); + + if (!pxefile_addr_str) + return 1; + + ret = strict_strtoul(pxefile_addr_str, 16, + (unsigned long *)&pxefile_addr_r); + if (ret < 0) + return 1; + + ret = pxe_get(pxefile_addr_r, &fname, &size, use_ipv6); + switch (ret) { + case 0: + printf("Config file '%s' found\n", fname); + break; + case -ENOMEM: + printf("Out of memory\n"); + return CMD_RET_FAILURE; + default: + printf("Config file not found\n"); + return CMD_RET_FAILURE; + } + + return 0; +} + +/* + * Boots a system using a pxe file + * + * Returns 0 on success, 1 on error. + */ +static int +do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned long pxefile_addr_r; + char *pxefile_addr_str; + struct pxe_context ctx; + int ret; + bool use_ipv6 = false; + + if (IS_ENABLED(CONFIG_IPV6)) { + if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM)) + use_ipv6 = true; + } + + if (argc == 1 || (argc == 2 && use_ipv6)) { + pxefile_addr_str = from_env("pxefile_addr_r"); + if (!pxefile_addr_str) + return 1; + + } else if (argc == 2 || (argc == 3 && use_ipv6)) { + pxefile_addr_str = argv[1]; + } else { + return CMD_RET_USAGE; + } + + if (strict_strtoul(pxefile_addr_str, 16, &pxefile_addr_r) < 0) { + printf("Invalid pxefile address: %s\n", pxefile_addr_str); + return 1; + } + + if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false, + env_get("bootfile"), use_ipv6, false)) { + printf("Out of memory\n"); + return CMD_RET_FAILURE; + } + ret = pxe_process(&ctx, pxefile_addr_r, false); + pxe_destroy_ctx(&ctx); + if (ret) + return CMD_RET_FAILURE; + + copy_filename(net_boot_file_name, "", sizeof(net_boot_file_name)); + + return 0; +} + +static struct cmd_tbl cmd_pxe_sub[] = { + U_BOOT_CMD_MKENT(get, 2, 1, do_pxe_get, "", ""), + U_BOOT_CMD_MKENT(boot, 3, 1, do_pxe_boot, "", "") +}; + +static int do_pxe(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + /* drop initial "pxe" arg */ + argc--; + argv++; + + cp = find_cmd_tbl(argv[0], cmd_pxe_sub, ARRAY_SIZE(cmd_pxe_sub)); + + if (cp) + return cp->cmd(cmdtp, flag, argc, argv); + + return CMD_RET_USAGE; +} + +U_BOOT_CMD(pxe, 4, 1, do_pxe, + "get and boot from pxe files", + "get [" USE_IP6_CMD_PARAM "] - try to retrieve a pxe file using tftp\n" + "pxe boot [pxefile_addr_r] [-ipv6] - boot from the pxe file at pxefile_addr_r\n" +); + +#endif /* CONFIG_CMD_NET */ |