1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright 2021 Google LLC 4 * Written by Simon Glass <sjg@chromium.org> 5 */ 6 7#define LOG_CATEGORY UCLASS_BOOTSTD 8 9#include <common.h> 10#include <blk.h> 11#include <bootflow.h> 12#include <bootmeth.h> 13#include <bootstd.h> 14#include <dm.h> 15#include <env_internal.h> 16#include <fs.h> 17#include <malloc.h> 18#include <mapmem.h> 19#include <dm/uclass-internal.h> 20 21DECLARE_GLOBAL_DATA_PTR; 22 23int bootmeth_get_state_desc(struct udevice *dev, char *buf, int maxsize) 24{ 25 const struct bootmeth_ops *ops = bootmeth_get_ops(dev); 26 27 if (!ops->get_state_desc) 28 return -ENOSYS; 29 30 return ops->get_state_desc(dev, buf, maxsize); 31} 32 33int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter) 34{ 35 const struct bootmeth_ops *ops = bootmeth_get_ops(dev); 36 37 if (!ops->check) 38 return 0; 39 40 return ops->check(dev, iter); 41} 42 43int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow) 44{ 45 const struct bootmeth_ops *ops = bootmeth_get_ops(dev); 46 47 if (!ops->read_bootflow) 48 return -ENOSYS; 49 50 return ops->read_bootflow(dev, bflow); 51} 52 53int bootmeth_set_bootflow(struct udevice *dev, struct bootflow *bflow, 54 char *buf, int size) 55{ 56 const struct bootmeth_ops *ops = bootmeth_get_ops(dev); 57 58 if (!ops->set_bootflow) 59 return -ENOSYS; 60 61 return ops->set_bootflow(dev, bflow, buf, size); 62} 63 64#if CONFIG_IS_ENABLED(BOOTSTD_FULL) 65int bootmeth_read_all(struct udevice *dev, struct bootflow *bflow) 66{ 67 const struct bootmeth_ops *ops = bootmeth_get_ops(dev); 68 69 if (!ops->read_all) 70 return -ENOSYS; 71 72 return ops->read_all(dev, bflow); 73} 74#endif /* BOOTSTD_FULL */ 75 76int bootmeth_boot(struct udevice *dev, struct bootflow *bflow) 77{ 78 const struct bootmeth_ops *ops = bootmeth_get_ops(dev); 79 80 if (!ops->boot) 81 return -ENOSYS; 82 83 return ops->boot(dev, bflow); 84} 85 86int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow, 87 const char *file_path, ulong addr, ulong *sizep) 88{ 89 const struct bootmeth_ops *ops = bootmeth_get_ops(dev); 90 91 if (!ops->read_file) 92 return -ENOSYS; 93 94 return ops->read_file(dev, bflow, file_path, addr, sizep); 95} 96 97int bootmeth_get_bootflow(struct udevice *dev, struct bootflow *bflow) 98{ 99 const struct bootmeth_ops *ops = bootmeth_get_ops(dev); 100 101 if (!ops->read_bootflow) 102 return -ENOSYS; 103 bootflow_init(bflow, NULL, dev); 104 105 return ops->read_bootflow(dev, bflow); 106} 107 108int bootmeth_setup_iter_order(struct bootflow_iter *iter, bool include_global) 109{ 110 struct bootstd_priv *std; 111 struct udevice **order; 112 int count; 113 int ret; 114 115 ret = bootstd_get_priv(&std); 116 if (ret) 117 return ret; 118 119 /* Create an array large enough */ 120 count = std->bootmeth_count ? std->bootmeth_count : 121 uclass_id_count(UCLASS_BOOTMETH); 122 if (!count) 123 return log_msg_ret("count", -ENOENT); 124 125 order = calloc(count, sizeof(struct udevice *)); 126 if (!order) 127 return log_msg_ret("order", -ENOMEM); 128 129 /* If we have an ordering, copy it */ 130 if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && std->bootmeth_count) { 131 int i; 132 133 /* 134 * We don't support skipping global bootmeths. Instead, the user 135 * should omit them from the ordering 136 */ 137 if (!include_global) 138 return log_msg_ret("glob", -EPERM); 139 memcpy(order, std->bootmeth_order, 140 count * sizeof(struct bootmeth *)); 141 142 if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) { 143 for (i = 0; i < count; i++) { 144 struct udevice *dev = order[i]; 145 struct bootmeth_uc_plat *ucp; 146 bool is_global; 147 148 ucp = dev_get_uclass_plat(dev); 149 is_global = ucp->flags & 150 BOOTMETHF_GLOBAL; 151 if (is_global) { 152 iter->first_glob_method = i; 153 break; 154 } 155 } 156 } 157 } else { 158 struct udevice *dev; 159 int i, upto, pass; 160 161 /* 162 * Do two passes, one to find the normal bootmeths and another 163 * to find the global ones, if required, The global ones go at 164 * the end. 165 */ 166 for (pass = 0, upto = 0; pass < 1 + include_global; pass++) { 167 if (pass) 168 iter->first_glob_method = upto; 169 /* 170 * Get a list of bootmethods, in seq order (i.e. using 171 * aliases). There may be gaps so try to count up high 172 * enough to find them all. 173 */ 174 for (i = 0; upto < count && i < 20 + count * 2; i++) { 175 struct bootmeth_uc_plat *ucp; 176 bool is_global; 177 178 ret = uclass_get_device_by_seq(UCLASS_BOOTMETH, 179 i, &dev); 180 if (ret) 181 continue; 182 ucp = dev_get_uclass_plat(dev); 183 is_global = 184 IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && 185 (ucp->flags & BOOTMETHF_GLOBAL); 186 if (pass ? is_global : !is_global) 187 order[upto++] = dev; 188 } 189 } 190 count = upto; 191 } 192 if (!count) 193 return log_msg_ret("count2", -ENOENT); 194 195 if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && include_global && 196 iter->first_glob_method != -1 && iter->first_glob_method != count) { 197 iter->cur_method = iter->first_glob_method; 198 iter->doing_global = true; 199 } 200 iter->method_order = order; 201 iter->num_methods = count; 202 203 return 0; 204} 205 206int bootmeth_set_order(const char *order_str) 207{ 208 struct bootstd_priv *std; 209 struct udevice **order; 210 int count, ret, i, len; 211 const char *s, *p; 212 213 ret = bootstd_get_priv(&std); 214 if (ret) 215 return ret; 216 217 if (!order_str) { 218 free(std->bootmeth_order); 219 std->bootmeth_order = NULL; 220 std->bootmeth_count = 0; 221 return 0; 222 } 223 224 /* Create an array large enough */ 225 count = uclass_id_count(UCLASS_BOOTMETH); 226 if (!count) 227 return log_msg_ret("count", -ENOENT); 228 229 order = calloc(count + 1, sizeof(struct udevice *)); 230 if (!order) 231 return log_msg_ret("order", -ENOMEM); 232 233 for (i = 0, s = order_str; *s && i < count; s = p + (*p == ' '), i++) { 234 struct udevice *dev; 235 236 p = strchrnul(s, ' '); 237 len = p - s; 238 ret = uclass_find_device_by_namelen(UCLASS_BOOTMETH, s, len, 239 &dev); 240 if (ret) { 241 printf("Unknown bootmeth '%.*s'\n", len, s); 242 free(order); 243 return ret; 244 } 245 order[i] = dev; 246 } 247 order[i] = NULL; 248 free(std->bootmeth_order); 249 std->bootmeth_order = order; 250 std->bootmeth_count = i; 251 252 return 0; 253} 254 255int bootmeth_setup_fs(struct bootflow *bflow, struct blk_desc *desc) 256{ 257 int ret; 258 259 if (desc) { 260 ret = fs_set_blk_dev_with_part(desc, bflow->part); 261 if (ret) 262 return log_msg_ret("set", ret); 263 } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) { 264 fs_set_type(bflow->fs_type); 265 } 266 267 return 0; 268} 269 270int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc, 271 const char *prefix, const char *fname) 272{ 273 char path[200]; 274 loff_t size; 275 int ret, ret2; 276 277 snprintf(path, sizeof(path), "%s%s", prefix ? prefix : "", fname); 278 log_debug("trying: %s\n", path); 279 280 free(bflow->fname); 281 bflow->fname = strdup(path); 282 if (!bflow->fname) 283 return log_msg_ret("name", -ENOMEM); 284 285 if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) 286 fs_set_type(bflow->fs_type); 287 288 ret = fs_size(path, &size); 289 log_debug(" %s - err=%d\n", path, ret); 290 291 /* Sadly FS closes the file after fs_size() so we must redo this */ 292 ret2 = bootmeth_setup_fs(bflow, desc); 293 if (ret2) 294 return log_msg_ret("fs", ret2); 295 296 if (ret) 297 return log_msg_ret("size", ret); 298 299 bflow->size = size; 300 bflow->state = BOOTFLOWST_FILE; 301 302 return 0; 303} 304 305int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align) 306{ 307 void *buf; 308 uint size; 309 int ret; 310 311 size = bflow->size; 312 log_debug(" - script file size %x\n", size); 313 if (size > size_limit) 314 return log_msg_ret("chk", -E2BIG); 315 316 ret = fs_read_alloc(bflow->fname, bflow->size, align, &buf); 317 if (ret) 318 return log_msg_ret("all", ret); 319 320 bflow->state = BOOTFLOWST_READY; 321 bflow->buf = buf; 322 323 return 0; 324} 325 326int bootmeth_alloc_other(struct bootflow *bflow, const char *fname, 327 void **bufp, uint *sizep) 328{ 329 struct blk_desc *desc = NULL; 330 char path[200]; 331 loff_t size; 332 void *buf; 333 int ret; 334 335 snprintf(path, sizeof(path), "%s%s", bflow->subdir, fname); 336 log_debug("trying: %s\n", path); 337 338 if (bflow->blk) 339 desc = dev_get_uclass_plat(bflow->blk); 340 341 ret = bootmeth_setup_fs(bflow, desc); 342 if (ret) 343 return log_msg_ret("fs", ret); 344 345 ret = fs_size(path, &size); 346 log_debug(" %s - err=%d\n", path, ret); 347 348 ret = bootmeth_setup_fs(bflow, desc); 349 if (ret) 350 return log_msg_ret("fs", ret); 351 352 ret = fs_read_alloc(path, size, 0, &buf); 353 if (ret) 354 return log_msg_ret("all", ret); 355 356 *bufp = buf; 357 *sizep = size; 358 359 return 0; 360} 361 362int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow, 363 const char *file_path, ulong addr, ulong *sizep) 364{ 365 struct blk_desc *desc = NULL; 366 loff_t len_read; 367 loff_t size; 368 int ret; 369 370 if (bflow->blk) 371 desc = dev_get_uclass_plat(bflow->blk); 372 373 ret = bootmeth_setup_fs(bflow, desc); 374 if (ret) 375 return log_msg_ret("fs", ret); 376 377 ret = fs_size(file_path, &size); 378 if (ret) 379 return log_msg_ret("size", ret); 380 if (size > *sizep) 381 return log_msg_ret("spc", -ENOSPC); 382 383 ret = bootmeth_setup_fs(bflow, desc); 384 if (ret) 385 return log_msg_ret("fs", ret); 386 387 ret = fs_read(file_path, addr, 0, 0, &len_read); 388 if (ret) 389 return ret; 390 *sizep = len_read; 391 392 return 0; 393} 394 395#ifdef CONFIG_BOOTSTD_FULL 396/** 397 * on_bootmeths() - Update the bootmeth order 398 * 399 * This will check for a valid list of bootmeths and only apply it if valid. 400 */ 401static int on_bootmeths(const char *name, const char *value, enum env_op op, 402 int flags) 403{ 404 int ret; 405 406 switch (op) { 407 case env_op_create: 408 case env_op_overwrite: 409 ret = bootmeth_set_order(value); 410 if (ret) 411 return 1; 412 return 0; 413 case env_op_delete: 414 bootmeth_set_order(NULL); 415 fallthrough; 416 default: 417 return 0; 418 } 419} 420U_BOOT_ENV_CALLBACK(bootmeths, on_bootmeths); 421#endif /* CONFIG_BOOTSTD_FULL */ 422 423UCLASS_DRIVER(bootmeth) = { 424 .id = UCLASS_BOOTMETH, 425 .name = "bootmeth", 426 .flags = DM_UC_FLAG_SEQ_ALIAS, 427 .per_device_plat_auto = sizeof(struct bootmeth_uc_plat), 428}; 429