1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Bootmethod for extlinux boot using PXE (network boot) 4 * 5 * Copyright 2021 Google LLC 6 * Written by Simon Glass <sjg@chromium.org> 7 */ 8 9#define LOG_CATEGORY UCLASS_BOOTSTD 10 11#include <common.h> 12#include <bootdev.h> 13#include <bootflow.h> 14#include <bootmeth.h> 15#include <command.h> 16#include <dm.h> 17#include <extlinux.h> 18#include <fs.h> 19#include <log.h> 20#include <malloc.h> 21#include <mapmem.h> 22#include <mmc.h> 23#include <net.h> 24#include <pxe_utils.h> 25 26static int extlinux_pxe_getfile(struct pxe_context *ctx, const char *file_path, 27 char *file_addr, ulong *sizep) 28{ 29 struct extlinux_info *info = ctx->userdata; 30 ulong addr; 31 int ret; 32 33 addr = simple_strtoul(file_addr, NULL, 16); 34 35 /* Allow up to 1GB */ 36 *sizep = 1 << 30; 37 ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, 38 sizep); 39 if (ret) 40 return log_msg_ret("read", ret); 41 42 return 0; 43} 44 45static int extlinux_pxe_check(struct udevice *dev, struct bootflow_iter *iter) 46{ 47 int ret; 48 49 /* This only works on network devices */ 50 ret = bootflow_iter_check_net(iter); 51 if (ret) 52 return log_msg_ret("net", ret); 53 54 if (iter->method_flags & BOOTFLOW_METHF_DHCP_ONLY) 55 return log_msg_ret("dhcp", -ENOTSUPP); 56 57 return 0; 58} 59 60static int extlinux_pxe_read_bootflow(struct udevice *dev, 61 struct bootflow *bflow) 62{ 63 const char *addr_str; 64 char fname[200]; 65 char *bootdir; 66 ulong addr; 67 ulong size; 68 char *buf; 69 int ret; 70 71 addr_str = env_get("pxefile_addr_r"); 72 if (!addr_str) 73 return log_msg_ret("pxeb", -EPERM); 74 addr = simple_strtoul(addr_str, NULL, 16); 75 76 log_debug("calling pxe_get()\n"); 77 ret = pxe_get(addr, &bootdir, &size, false); 78 log_debug("pxe_get() returned %d\n", ret); 79 if (ret) 80 return log_msg_ret("pxeb", ret); 81 bflow->size = size; 82 83 /* Use the directory of the dhcp bootdir as our subdir, if provided */ 84 if (bootdir) { 85 const char *last_slash; 86 int path_len; 87 88 last_slash = strrchr(bootdir, '/'); 89 if (last_slash) { 90 path_len = (last_slash - bootdir) + 1; 91 bflow->subdir = malloc(path_len + 1); 92 memcpy(bflow->subdir, bootdir, path_len); 93 bflow->subdir[path_len] = '\0'; 94 } 95 } 96 snprintf(fname, sizeof(fname), "%s%s", 97 bflow->subdir ? bflow->subdir : "", EXTLINUX_FNAME); 98 99 bflow->fname = strdup(fname); 100 if (!bflow->fname) 101 return log_msg_ret("name", -ENOMEM); 102 103 bflow->state = BOOTFLOWST_READY; 104 105 /* Allocate the buffer, including the \0 byte added by get_pxe_file() */ 106 buf = malloc(size + 1); 107 if (!buf) 108 return log_msg_ret("buf", -ENOMEM); 109 memcpy(buf, map_sysmem(addr, 0), size + 1); 110 bflow->buf = buf; 111 112 return 0; 113} 114 115static int extlinux_pxe_read_file(struct udevice *dev, struct bootflow *bflow, 116 const char *file_path, ulong addr, 117 ulong *sizep) 118{ 119 char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; 120 struct pxe_context *ctx = dev_get_priv(dev); 121 char file_addr[17]; 122 ulong size; 123 int ret; 124 125 sprintf(file_addr, "%lx", addr); 126 tftp_argv[1] = file_addr; 127 tftp_argv[2] = (void *)file_path; 128 129 if (do_tftpb(ctx->cmdtp, 0, 3, tftp_argv)) 130 return -ENOENT; 131 ret = pxe_get_file_size(&size); 132 if (ret) 133 return log_msg_ret("tftp", ret); 134 if (size > *sizep) 135 return log_msg_ret("spc", -ENOSPC); 136 *sizep = size; 137 138 return 0; 139} 140 141static int extlinux_pxe_boot(struct udevice *dev, struct bootflow *bflow) 142{ 143 struct pxe_context *ctx = dev_get_priv(dev); 144 struct cmd_tbl cmdtp = {}; /* dummy */ 145 struct extlinux_info info; 146 ulong addr; 147 int ret; 148 149 addr = map_to_sysmem(bflow->buf); 150 info.dev = dev; 151 info.bflow = bflow; 152 info.cmdtp = &cmdtp; 153 ret = pxe_setup_ctx(ctx, &cmdtp, extlinux_pxe_getfile, &info, false, 154 bflow->subdir, false); 155 if (ret) 156 return log_msg_ret("ctx", -EINVAL); 157 158 ret = pxe_process(ctx, addr, false); 159 if (ret) 160 return log_msg_ret("bread", -EINVAL); 161 162 return 0; 163} 164 165static int extlinux_bootmeth_pxe_bind(struct udevice *dev) 166{ 167 struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); 168 169 plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? 170 "PXE boot from a network device" : "PXE"; 171 172 return 0; 173} 174 175static struct bootmeth_ops extlinux_bootmeth_pxe_ops = { 176 .check = extlinux_pxe_check, 177 .read_bootflow = extlinux_pxe_read_bootflow, 178 .read_file = extlinux_pxe_read_file, 179 .boot = extlinux_pxe_boot, 180}; 181 182static const struct udevice_id extlinux_bootmeth_pxe_ids[] = { 183 { .compatible = "u-boot,extlinux-pxe" }, 184 { } 185}; 186 187U_BOOT_DRIVER(bootmeth_zpxe) = { 188 .name = "bootmeth_pxe", 189 .id = UCLASS_BOOTMETH, 190 .of_match = extlinux_bootmeth_pxe_ids, 191 .ops = &extlinux_bootmeth_pxe_ops, 192 .bind = extlinux_bootmeth_pxe_bind, 193 .priv_auto = sizeof(struct pxe_context), 194}; 195