]>
Commit | Line | Data |
---|---|---|
a8f5be17 SG |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright 2021 Google LLC | |
4 | * Written by Simon Glass <[email protected]> | |
5 | */ | |
6 | ||
7 | #define LOG_CATEGORY UCLASS_BOOTSTD | |
8 | ||
a8f5be17 SG |
9 | #include <bootdev.h> |
10 | #include <bootflow.h> | |
11 | #include <bootmeth.h> | |
12 | #include <bootstd.h> | |
13 | #include <dm.h> | |
d42243fe | 14 | #include <env_internal.h> |
a8f5be17 | 15 | #include <malloc.h> |
33ebcb46 | 16 | #include <serial.h> |
a8f5be17 SG |
17 | #include <dm/device-internal.h> |
18 | #include <dm/uclass-internal.h> | |
19 | ||
20 | /* error codes used to signal running out of things */ | |
21 | enum { | |
22 | BF_NO_MORE_PARTS = -ESHUTDOWN, | |
23 | BF_NO_MORE_DEVICES = -ENODEV, | |
24 | }; | |
25 | ||
26 | /** | |
27 | * bootflow_state - name for each state | |
28 | * | |
29 | * See enum bootflow_state_t for what each of these means | |
30 | */ | |
31 | static const char *const bootflow_state[BOOTFLOWST_COUNT] = { | |
32 | "base", | |
33 | "media", | |
34 | "part", | |
35 | "fs", | |
36 | "file", | |
37 | "ready", | |
38 | }; | |
39 | ||
40 | const char *bootflow_state_get_name(enum bootflow_state_t state) | |
41 | { | |
42 | /* This doesn't need to be a useful name, since it will never occur */ | |
43 | if (state < 0 || state >= BOOTFLOWST_COUNT) | |
44 | return "?"; | |
45 | ||
46 | return bootflow_state[state]; | |
47 | } | |
48 | ||
49 | int bootflow_first_glob(struct bootflow **bflowp) | |
50 | { | |
51 | struct bootstd_priv *std; | |
52 | int ret; | |
53 | ||
54 | ret = bootstd_get_priv(&std); | |
55 | if (ret) | |
56 | return ret; | |
57 | ||
58 | if (list_empty(&std->glob_head)) | |
59 | return -ENOENT; | |
60 | ||
61 | *bflowp = list_first_entry(&std->glob_head, struct bootflow, | |
62 | glob_node); | |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
67 | int bootflow_next_glob(struct bootflow **bflowp) | |
68 | { | |
69 | struct bootstd_priv *std; | |
70 | struct bootflow *bflow = *bflowp; | |
71 | int ret; | |
72 | ||
73 | ret = bootstd_get_priv(&std); | |
74 | if (ret) | |
75 | return ret; | |
76 | ||
77 | *bflowp = NULL; | |
78 | ||
79 | if (list_is_last(&bflow->glob_node, &std->glob_head)) | |
80 | return -ENOENT; | |
81 | ||
82 | *bflowp = list_entry(bflow->glob_node.next, struct bootflow, glob_node); | |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
87 | void bootflow_iter_init(struct bootflow_iter *iter, int flags) | |
88 | { | |
89 | memset(iter, '\0', sizeof(*iter)); | |
2b80bc1e | 90 | iter->first_glob_method = -1; |
a8f5be17 | 91 | iter->flags = flags; |
a950f285 SG |
92 | |
93 | /* remember the first bootdevs we see */ | |
94 | iter->max_devs = BOOTFLOW_MAX_USED_DEVS; | |
a8f5be17 SG |
95 | } |
96 | ||
97 | void bootflow_iter_uninit(struct bootflow_iter *iter) | |
98 | { | |
a8f5be17 SG |
99 | free(iter->method_order); |
100 | } | |
101 | ||
102 | int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, | |
103 | const struct udevice *bmeth) | |
104 | { | |
105 | /* We only support disabling the current bootmeth */ | |
106 | if (bmeth != iter->method || iter->cur_method >= iter->num_methods || | |
107 | iter->method_order[iter->cur_method] != bmeth) | |
108 | return -EINVAL; | |
109 | ||
110 | memmove(&iter->method_order[iter->cur_method], | |
111 | &iter->method_order[iter->cur_method + 1], | |
112 | (iter->num_methods - iter->cur_method - 1) * sizeof(void *)); | |
113 | ||
114 | iter->num_methods--; | |
115 | ||
116 | return 0; | |
117 | } | |
118 | ||
47aedc29 SG |
119 | /** |
120 | * bootflow_iter_set_dev() - switch to the next bootdev when iterating | |
121 | * | |
122 | * This sets iter->dev, records the device in the dev_used[] list and shows a | |
123 | * message if required | |
124 | * | |
125 | * @iter: Iterator to update | |
126 | * @dev: Bootdev to use, or NULL if there are no more | |
127 | */ | |
a8f5be17 | 128 | static void bootflow_iter_set_dev(struct bootflow_iter *iter, |
47aedc29 | 129 | struct udevice *dev, int method_flags) |
a8f5be17 | 130 | { |
2b80bc1e SG |
131 | struct bootmeth_uc_plat *ucp = dev_get_uclass_plat(iter->method); |
132 | ||
47aedc29 SG |
133 | log_debug("iter: Setting dev to %s, flags %x\n", |
134 | dev ? dev->name : "(none)", method_flags); | |
a8f5be17 | 135 | iter->dev = dev; |
47aedc29 SG |
136 | iter->method_flags = method_flags; |
137 | ||
a950f285 SG |
138 | if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) { |
139 | /* record the device for later */ | |
140 | if (dev && iter->num_devs < iter->max_devs) | |
141 | iter->dev_used[iter->num_devs++] = dev; | |
142 | ||
4f806f31 SG |
143 | if ((iter->flags & (BOOTFLOWIF_SHOW | BOOTFLOWIF_SINGLE_DEV)) == |
144 | BOOTFLOWIF_SHOW) { | |
a950f285 SG |
145 | if (dev) |
146 | printf("Scanning bootdev '%s':\n", dev->name); | |
147 | else if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && | |
148 | ucp->flags & BOOTMETHF_GLOBAL) | |
149 | printf("Scanning global bootmeth '%s':\n", | |
150 | iter->method->name); | |
151 | else | |
152 | printf("No more bootdevs\n"); | |
153 | } | |
a8f5be17 SG |
154 | } |
155 | } | |
156 | ||
16e19350 SG |
157 | /** |
158 | * scan_next_in_uclass() - Scan for the next bootdev in the same media uclass | |
159 | * | |
160 | * Move through the following bootdevs until we find another in this media | |
161 | * uclass, or run out | |
162 | * | |
163 | * @devp: On entry, the device to check, on exit the new device, or NULL if | |
164 | * there is none | |
165 | */ | |
166 | static void scan_next_in_uclass(struct udevice **devp) | |
167 | { | |
168 | struct udevice *dev = *devp; | |
169 | enum uclass_id cur_id = device_get_uclass_id(dev->parent); | |
170 | ||
171 | do { | |
172 | uclass_find_next_device(&dev); | |
173 | } while (dev && cur_id != device_get_uclass_id(dev->parent)); | |
174 | ||
175 | *devp = dev; | |
176 | } | |
177 | ||
a8f5be17 SG |
178 | /** |
179 | * iter_incr() - Move to the next item (method, part, bootdev) | |
180 | * | |
181 | * Return: 0 if OK, BF_NO_MORE_DEVICES if there are no more bootdevs | |
182 | */ | |
183 | static int iter_incr(struct bootflow_iter *iter) | |
184 | { | |
185 | struct udevice *dev; | |
2b80bc1e SG |
186 | bool inc_dev = true; |
187 | bool global; | |
a8f5be17 SG |
188 | int ret; |
189 | ||
47aedc29 | 190 | log_debug("entry: err=%d\n", iter->err); |
2b80bc1e SG |
191 | global = iter->doing_global; |
192 | ||
a8f5be17 SG |
193 | if (iter->err == BF_NO_MORE_DEVICES) |
194 | return BF_NO_MORE_DEVICES; | |
195 | ||
196 | if (iter->err != BF_NO_MORE_PARTS) { | |
197 | /* Get the next boothmethod */ | |
198 | if (++iter->cur_method < iter->num_methods) { | |
199 | iter->method = iter->method_order[iter->cur_method]; | |
200 | return 0; | |
201 | } | |
2b80bc1e SG |
202 | |
203 | /* | |
204 | * If we have finished scanning the global bootmeths, start the | |
205 | * normal bootdev scan | |
206 | */ | |
207 | if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && global) { | |
208 | iter->num_methods = iter->first_glob_method; | |
209 | iter->doing_global = false; | |
210 | ||
211 | /* | |
212 | * Don't move to the next dev as we haven't tried this | |
213 | * one yet! | |
214 | */ | |
215 | inc_dev = false; | |
216 | } | |
a8f5be17 SG |
217 | } |
218 | ||
11324714 NC |
219 | if (iter->flags & BOOTFLOWIF_SINGLE_PARTITION) |
220 | return BF_NO_MORE_DEVICES; | |
221 | ||
a8f5be17 SG |
222 | /* No more bootmeths; start at the first one, and... */ |
223 | iter->cur_method = 0; | |
224 | iter->method = iter->method_order[iter->cur_method]; | |
225 | ||
226 | if (iter->err != BF_NO_MORE_PARTS) { | |
227 | /* ...select next partition */ | |
228 | if (++iter->part <= iter->max_part) | |
229 | return 0; | |
230 | } | |
231 | ||
47aedc29 | 232 | /* No more partitions; start at the first one and... */ |
a8f5be17 SG |
233 | iter->part = 0; |
234 | ||
235 | /* | |
236 | * Note: as far as we know, there is no partition table on the next | |
237 | * bootdev, so set max_part to 0 until we discover otherwise. See | |
238 | * bootdev_find_in_blk() for where this is set. | |
239 | */ | |
240 | iter->max_part = 0; | |
241 | ||
242 | /* ...select next bootdev */ | |
4f806f31 | 243 | if (iter->flags & BOOTFLOWIF_SINGLE_DEV) { |
a8f5be17 | 244 | ret = -ENOENT; |
a8f5be17 | 245 | } else { |
47aedc29 SG |
246 | int method_flags; |
247 | ||
248 | ret = 0; | |
249 | dev = iter->dev; | |
250 | log_debug("inc_dev=%d\n", inc_dev); | |
251 | if (!inc_dev) { | |
91943ff7 SG |
252 | ret = bootdev_setup_iter(iter, NULL, &dev, |
253 | &method_flags); | |
254 | } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && | |
4f806f31 | 255 | (iter->flags & BOOTFLOWIF_SINGLE_UCLASS)) { |
16e19350 | 256 | scan_next_in_uclass(&dev); |
91943ff7 SG |
257 | if (!dev) { |
258 | log_debug("finished uclass %s\n", | |
259 | dev_get_uclass_name(dev)); | |
260 | ret = -ENODEV; | |
261 | } | |
262 | } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && | |
4f806f31 | 263 | iter->flags & BOOTFLOWIF_SINGLE_MEDIA) { |
91943ff7 SG |
264 | log_debug("next in single\n"); |
265 | method_flags = 0; | |
266 | do { | |
267 | /* | |
268 | * Move to the next bootdev child of this media | |
269 | * device. This ensures that we cover all the | |
270 | * available SCSI IDs and LUNs. | |
271 | */ | |
272 | device_find_next_child(&dev); | |
273 | log_debug("- next %s\n", | |
274 | dev ? dev->name : "(none)"); | |
275 | } while (dev && device_get_uclass_id(dev) != | |
276 | UCLASS_BOOTDEV); | |
277 | if (!dev) { | |
278 | log_debug("finished uclass %s\n", | |
279 | dev_get_uclass_name(dev)); | |
280 | ret = -ENODEV; | |
281 | } | |
47aedc29 SG |
282 | } else { |
283 | log_debug("labels %p\n", iter->labels); | |
284 | if (iter->labels) { | |
7a790f01 SG |
285 | /* |
286 | * when the label is "mmc" we want to scan all | |
287 | * mmc bootdevs, not just the first. See | |
288 | * bootdev_find_by_label() where this flag is | |
289 | * set up | |
290 | */ | |
291 | if (iter->method_flags & | |
292 | BOOTFLOW_METHF_SINGLE_UCLASS) { | |
293 | scan_next_in_uclass(&dev); | |
294 | log_debug("looking for next device %s: %s\n", | |
295 | iter->dev->name, | |
296 | dev ? dev->name : "<none>"); | |
297 | } else { | |
298 | dev = NULL; | |
299 | } | |
300 | if (!dev) { | |
301 | log_debug("looking at next label\n"); | |
302 | ret = bootdev_next_label(iter, &dev, | |
303 | &method_flags); | |
304 | } | |
47aedc29 SG |
305 | } else { |
306 | ret = bootdev_next_prio(iter, &dev); | |
307 | method_flags = 0; | |
308 | } | |
309 | } | |
310 | log_debug("ret=%d, dev=%p %s\n", ret, dev, | |
311 | dev ? dev->name : "none"); | |
312 | if (ret) { | |
313 | bootflow_iter_set_dev(iter, NULL, 0); | |
2b80bc1e | 314 | } else { |
965020c3 SG |
315 | /* |
316 | * Probe the bootdev. This does not probe any attached | |
317 | * block device, since they are siblings | |
318 | */ | |
2b80bc1e | 319 | ret = device_probe(dev); |
47aedc29 | 320 | log_debug("probe %s %d\n", dev->name, ret); |
2b80bc1e | 321 | if (!log_msg_ret("probe", ret)) |
47aedc29 | 322 | bootflow_iter_set_dev(iter, dev, method_flags); |
2b80bc1e | 323 | } |
a8f5be17 SG |
324 | } |
325 | ||
326 | /* if there are no more bootdevs, give up */ | |
327 | if (ret) | |
328 | return log_msg_ret("incr", BF_NO_MORE_DEVICES); | |
329 | ||
330 | return 0; | |
331 | } | |
332 | ||
333 | /** | |
334 | * bootflow_check() - Check if a bootflow can be obtained | |
335 | * | |
336 | * @iter: Provides part, bootmeth to use | |
337 | * @bflow: Bootflow to update on success | |
338 | * Return: 0 if OK, -ENOSYS if there is no bootflow support on this device, | |
339 | * BF_NO_MORE_PARTS if there are no more partitions on bootdev | |
340 | */ | |
341 | static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow) | |
342 | { | |
343 | struct udevice *dev; | |
344 | int ret; | |
345 | ||
2b80bc1e | 346 | if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && iter->doing_global) { |
47aedc29 | 347 | bootflow_iter_set_dev(iter, NULL, 0); |
2b80bc1e SG |
348 | ret = bootmeth_get_bootflow(iter->method, bflow); |
349 | if (ret) | |
350 | return log_msg_ret("glob", ret); | |
351 | ||
352 | return 0; | |
353 | } | |
354 | ||
a8f5be17 SG |
355 | dev = iter->dev; |
356 | ret = bootdev_get_bootflow(dev, iter, bflow); | |
357 | ||
358 | /* If we got a valid bootflow, return it */ | |
359 | if (!ret) { | |
4de979f6 | 360 | log_debug("Bootdev '%s' part %d method '%s': Found bootflow\n", |
a8f5be17 SG |
361 | dev->name, iter->part, iter->method->name); |
362 | return 0; | |
363 | } | |
364 | ||
365 | /* Unless there is nothing more to try, move to the next device */ | |
3f931223 | 366 | if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { |
4de979f6 | 367 | log_debug("Bootdev '%s' part %d method '%s': Error %d\n", |
a8f5be17 SG |
368 | dev->name, iter->part, iter->method->name, ret); |
369 | /* | |
370 | * For 'all' we return all bootflows, even | |
371 | * those with errors | |
372 | */ | |
4f806f31 | 373 | if (iter->flags & BOOTFLOWIF_ALL) |
a8f5be17 SG |
374 | return log_msg_ret("all", ret); |
375 | } | |
a8f5be17 | 376 | |
3f931223 | 377 | return log_msg_ret("check", ret); |
a8f5be17 SG |
378 | } |
379 | ||
4b7cb058 SG |
380 | int bootflow_scan_first(struct udevice *dev, const char *label, |
381 | struct bootflow_iter *iter, int flags, | |
382 | struct bootflow *bflow) | |
a8f5be17 SG |
383 | { |
384 | int ret; | |
385 | ||
91943ff7 | 386 | if (dev || label) |
4f806f31 | 387 | flags |= BOOTFLOWIF_SKIP_GLOBAL; |
a8f5be17 SG |
388 | bootflow_iter_init(iter, flags); |
389 | ||
47aedc29 SG |
390 | /* |
391 | * Set up the ordering of bootmeths. This sets iter->doing_global and | |
392 | * iter->first_glob_method if we are starting with the global bootmeths | |
393 | */ | |
4f806f31 | 394 | ret = bootmeth_setup_iter_order(iter, !(flags & BOOTFLOWIF_SKIP_GLOBAL)); |
a8f5be17 SG |
395 | if (ret) |
396 | return log_msg_ret("obmeth", -ENODEV); | |
397 | ||
398 | /* Find the first bootmeth (there must be at least one!) */ | |
399 | iter->method = iter->method_order[iter->cur_method]; | |
47aedc29 SG |
400 | |
401 | if (!IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) || !iter->doing_global) { | |
402 | struct udevice *dev = NULL; | |
403 | int method_flags; | |
404 | ||
91943ff7 | 405 | ret = bootdev_setup_iter(iter, label, &dev, &method_flags); |
47aedc29 SG |
406 | if (ret) |
407 | return log_msg_ret("obdev", -ENODEV); | |
408 | ||
409 | bootflow_iter_set_dev(iter, dev, method_flags); | |
410 | } | |
a8f5be17 SG |
411 | |
412 | ret = bootflow_check(iter, bflow); | |
413 | if (ret) { | |
f738c73a | 414 | log_debug("check - ret=%d\n", ret); |
a8f5be17 | 415 | if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { |
4f806f31 | 416 | if (iter->flags & BOOTFLOWIF_ALL) |
a8f5be17 SG |
417 | return log_msg_ret("all", ret); |
418 | } | |
419 | iter->err = ret; | |
420 | ret = bootflow_scan_next(iter, bflow); | |
421 | if (ret) | |
422 | return log_msg_ret("get", ret); | |
423 | } | |
424 | ||
425 | return 0; | |
426 | } | |
427 | ||
a8f5be17 SG |
428 | int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow) |
429 | { | |
430 | int ret; | |
431 | ||
432 | do { | |
433 | ret = iter_incr(iter); | |
f738c73a | 434 | log_debug("iter_incr: ret=%d\n", ret); |
a8f5be17 SG |
435 | if (ret == BF_NO_MORE_DEVICES) |
436 | return log_msg_ret("done", ret); | |
437 | ||
438 | if (!ret) { | |
439 | ret = bootflow_check(iter, bflow); | |
f738c73a | 440 | log_debug("check - ret=%d\n", ret); |
a8f5be17 SG |
441 | if (!ret) |
442 | return 0; | |
443 | iter->err = ret; | |
444 | if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { | |
4f806f31 | 445 | if (iter->flags & BOOTFLOWIF_ALL) |
a8f5be17 SG |
446 | return log_msg_ret("all", ret); |
447 | } | |
448 | } else { | |
f738c73a | 449 | log_debug("incr failed, err=%d\n", ret); |
a8f5be17 SG |
450 | iter->err = ret; |
451 | } | |
452 | ||
453 | } while (1); | |
454 | } | |
455 | ||
b190deb8 SG |
456 | void bootflow_init(struct bootflow *bflow, struct udevice *bootdev, |
457 | struct udevice *meth) | |
458 | { | |
459 | memset(bflow, '\0', sizeof(*bflow)); | |
460 | bflow->dev = bootdev; | |
461 | bflow->method = meth; | |
462 | bflow->state = BOOTFLOWST_BASE; | |
463 | } | |
464 | ||
a8f5be17 SG |
465 | void bootflow_free(struct bootflow *bflow) |
466 | { | |
467 | free(bflow->name); | |
468 | free(bflow->subdir); | |
469 | free(bflow->fname); | |
741d1e9d SG |
470 | if (!(bflow->flags & BOOTFLOWF_STATIC_BUF)) |
471 | free(bflow->buf); | |
2175e76a | 472 | free(bflow->os_name); |
7638c851 | 473 | free(bflow->fdt_fname); |
76bd6844 | 474 | free(bflow->bootmeth_priv); |
a8f5be17 SG |
475 | } |
476 | ||
477 | void bootflow_remove(struct bootflow *bflow) | |
478 | { | |
eccb25cd SG |
479 | if (bflow->dev) |
480 | list_del(&bflow->bm_node); | |
a8f5be17 SG |
481 | list_del(&bflow->glob_node); |
482 | ||
483 | bootflow_free(bflow); | |
484 | free(bflow); | |
485 | } | |
486 | ||
c279224e SG |
487 | #if CONFIG_IS_ENABLED(BOOTSTD_FULL) |
488 | int bootflow_read_all(struct bootflow *bflow) | |
489 | { | |
490 | int ret; | |
491 | ||
492 | if (bflow->state != BOOTFLOWST_READY) | |
493 | return log_msg_ret("rd", -EPROTO); | |
494 | ||
495 | ret = bootmeth_read_all(bflow->method, bflow); | |
496 | if (ret) | |
497 | return log_msg_ret("rd2", ret); | |
498 | ||
499 | return 0; | |
500 | } | |
501 | #endif /* BOOTSTD_FULL */ | |
502 | ||
a8f5be17 SG |
503 | int bootflow_boot(struct bootflow *bflow) |
504 | { | |
505 | int ret; | |
506 | ||
507 | if (bflow->state != BOOTFLOWST_READY) | |
508 | return log_msg_ret("load", -EPROTO); | |
509 | ||
510 | ret = bootmeth_boot(bflow->method, bflow); | |
511 | if (ret) | |
512 | return log_msg_ret("boot", ret); | |
513 | ||
514 | /* | |
515 | * internal error, should not get here since we should have booted | |
516 | * something or returned an error | |
517 | */ | |
518 | ||
519 | return log_msg_ret("end", -EFAULT); | |
520 | } | |
521 | ||
522 | int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow) | |
523 | { | |
524 | int ret; | |
525 | ||
526 | printf("** Booting bootflow '%s' with %s\n", bflow->name, | |
527 | bflow->method->name); | |
47dd6b4d SG |
528 | if (IS_ENABLED(CONFIG_OF_HAS_PRIOR_STAGE) && |
529 | (bflow->flags & BOOTFLOWF_USE_PRIOR_FDT)) | |
530 | printf("Using prior-stage device tree\n"); | |
a8f5be17 SG |
531 | ret = bootflow_boot(bflow); |
532 | if (!IS_ENABLED(CONFIG_BOOTSTD_FULL)) { | |
533 | printf("Boot failed (err=%d)\n", ret); | |
534 | return ret; | |
535 | } | |
536 | ||
537 | switch (ret) { | |
538 | case -EPROTO: | |
539 | printf("Bootflow not loaded (state '%s')\n", | |
540 | bootflow_state_get_name(bflow->state)); | |
541 | break; | |
542 | case -ENOSYS: | |
543 | printf("Boot method '%s' not supported\n", bflow->method->name); | |
544 | break; | |
545 | case -ENOTSUPP: | |
546 | /* Disable this bootflow for this iteration */ | |
547 | if (iter) { | |
548 | int ret2; | |
549 | ||
550 | ret2 = bootflow_iter_drop_bootmeth(iter, bflow->method); | |
551 | if (!ret2) { | |
552 | printf("Boot method '%s' failed and will not be retried\n", | |
553 | bflow->method->name); | |
554 | } | |
555 | } | |
556 | ||
557 | break; | |
558 | default: | |
559 | printf("Boot failed (err=%d)\n", ret); | |
560 | break; | |
561 | } | |
562 | ||
563 | return ret; | |
564 | } | |
565 | ||
865328c3 | 566 | int bootflow_iter_check_blk(const struct bootflow_iter *iter) |
a8f5be17 SG |
567 | { |
568 | const struct udevice *media = dev_get_parent(iter->dev); | |
569 | enum uclass_id id = device_get_uclass_id(media); | |
570 | ||
571 | log_debug("uclass %d: %s\n", id, uclass_get_name(id)); | |
9c6d57dc | 572 | if (id != UCLASS_ETH && id != UCLASS_BOOTSTD && id != UCLASS_QFW) |
a8f5be17 SG |
573 | return 0; |
574 | ||
575 | return -ENOTSUPP; | |
576 | } | |
577 | ||
be0b076c MK |
578 | int bootflow_iter_check_mmc(const struct bootflow_iter *iter) |
579 | { | |
580 | const struct udevice *media = dev_get_parent(iter->dev); | |
581 | enum uclass_id id = device_get_uclass_id(media); | |
582 | ||
583 | log_debug("uclass %d: %s\n", id, uclass_get_name(id)); | |
584 | if (id == UCLASS_MMC) | |
585 | return 0; | |
586 | ||
587 | return -ENOTSUPP; | |
588 | } | |
589 | ||
0c1f4a9f SG |
590 | int bootflow_iter_check_sf(const struct bootflow_iter *iter) |
591 | { | |
592 | const struct udevice *media = dev_get_parent(iter->dev); | |
593 | enum uclass_id id = device_get_uclass_id(media); | |
594 | ||
595 | log_debug("uclass %d: %s\n", id, uclass_get_name(id)); | |
596 | if (id == UCLASS_SPI_FLASH) | |
597 | return 0; | |
598 | ||
599 | return -ENOTSUPP; | |
600 | } | |
601 | ||
865328c3 | 602 | int bootflow_iter_check_net(const struct bootflow_iter *iter) |
a8f5be17 SG |
603 | { |
604 | const struct udevice *media = dev_get_parent(iter->dev); | |
605 | enum uclass_id id = device_get_uclass_id(media); | |
606 | ||
607 | log_debug("uclass %d: %s\n", id, uclass_get_name(id)); | |
608 | if (id == UCLASS_ETH) | |
609 | return 0; | |
610 | ||
611 | return -ENOTSUPP; | |
612 | } | |
613 | ||
865328c3 | 614 | int bootflow_iter_check_system(const struct bootflow_iter *iter) |
a8f5be17 SG |
615 | { |
616 | const struct udevice *media = dev_get_parent(iter->dev); | |
617 | enum uclass_id id = device_get_uclass_id(media); | |
618 | ||
619 | log_debug("uclass %d: %s\n", id, uclass_get_name(id)); | |
620 | if (id == UCLASS_BOOTSTD) | |
621 | return 0; | |
622 | ||
623 | return -ENOTSUPP; | |
624 | } | |
d42243fe SG |
625 | |
626 | /** | |
627 | * bootflow_cmdline_set() - Set the command line for a bootflow | |
628 | * | |
629 | * @value: New command-line string | |
630 | * Returns 0 if OK, -ENOENT if no current bootflow, -ENOMEM if out of memory | |
631 | */ | |
632 | int bootflow_cmdline_set(struct bootflow *bflow, const char *value) | |
633 | { | |
634 | char *cmdline = NULL; | |
635 | ||
636 | if (value) { | |
637 | cmdline = strdup(value); | |
638 | if (!cmdline) | |
639 | return -ENOMEM; | |
640 | } | |
641 | ||
642 | free(bflow->cmdline); | |
643 | bflow->cmdline = cmdline; | |
644 | ||
645 | return 0; | |
646 | } | |
647 | ||
648 | #ifdef CONFIG_BOOTSTD_FULL | |
649 | /** | |
650 | * on_bootargs() - Update the cmdline of a bootflow | |
651 | */ | |
652 | static int on_bootargs(const char *name, const char *value, enum env_op op, | |
653 | int flags) | |
654 | { | |
655 | struct bootstd_priv *std; | |
656 | struct bootflow *bflow; | |
657 | int ret; | |
658 | ||
659 | ret = bootstd_get_priv(&std); | |
660 | if (ret) | |
661 | return 0; | |
662 | bflow = std->cur_bootflow; | |
663 | if (!bflow) | |
664 | return 0; | |
665 | ||
666 | switch (op) { | |
667 | case env_op_create: | |
668 | case env_op_overwrite: | |
669 | ret = bootflow_cmdline_set(bflow, value); | |
670 | if (ret && ret != ENOENT) | |
671 | return 1; | |
672 | return 0; | |
673 | case env_op_delete: | |
674 | bootflow_cmdline_set(bflow, NULL); | |
675 | fallthrough; | |
676 | default: | |
677 | return 0; | |
678 | } | |
679 | } | |
680 | U_BOOT_ENV_CALLBACK(bootargs, on_bootargs); | |
681 | #endif | |
d07861cc SG |
682 | |
683 | /** | |
684 | * copy_in() - Copy a string into a cmdline buffer | |
685 | * | |
686 | * @buf: Buffer to copy into | |
687 | * @end: End of buffer (pointer to char after the end) | |
688 | * @arg: String to copy from | |
689 | * @len: Number of chars to copy from @arg (note that this is not usually the | |
690 | * sane as strlen(arg) since the string may contain following arguments) | |
691 | * @new_val: Value to put after arg, or BOOTFLOWCL_EMPTY to use an empty value | |
692 | * with no '=' sign | |
693 | * Returns: Number of chars written to @buf | |
694 | */ | |
695 | static int copy_in(char *buf, char *end, const char *arg, int len, | |
696 | const char *new_val) | |
697 | { | |
698 | char *to = buf; | |
699 | ||
700 | /* copy the arg name */ | |
701 | if (to + len >= end) | |
702 | return -E2BIG; | |
703 | memcpy(to, arg, len); | |
704 | to += len; | |
705 | ||
706 | if (new_val == BOOTFLOWCL_EMPTY) { | |
707 | /* no value */ | |
708 | } else { | |
709 | bool need_quote = strchr(new_val, ' '); | |
710 | len = strlen(new_val); | |
711 | ||
712 | /* need space for value, equals sign and maybe two quotes */ | |
713 | if (to + 1 + (need_quote ? 2 : 0) + len >= end) | |
714 | return -E2BIG; | |
715 | *to++ = '='; | |
716 | if (need_quote) | |
717 | *to++ = '"'; | |
718 | memcpy(to, new_val, len); | |
719 | to += len; | |
720 | if (need_quote) | |
721 | *to++ = '"'; | |
722 | } | |
723 | ||
724 | return to - buf; | |
725 | } | |
726 | ||
727 | int cmdline_set_arg(char *buf, int maxlen, const char *cmdline, | |
728 | const char *set_arg, const char *new_val, int *posp) | |
729 | { | |
730 | bool found_arg = false; | |
731 | const char *from; | |
732 | char *to, *end; | |
733 | int set_arg_len; | |
734 | char empty = '\0'; | |
735 | int ret; | |
736 | ||
737 | from = cmdline ?: ∅ | |
738 | ||
739 | /* check if the value has quotes inside */ | |
740 | if (new_val && new_val != BOOTFLOWCL_EMPTY && strchr(new_val, '"')) | |
741 | return -EBADF; | |
742 | ||
743 | set_arg_len = strlen(set_arg); | |
744 | for (to = buf, end = buf + maxlen; *from;) { | |
745 | const char *val, *arg_end, *val_end, *p; | |
746 | bool in_quote; | |
747 | ||
748 | if (to >= end) | |
749 | return -E2BIG; | |
750 | while (*from == ' ') | |
751 | from++; | |
752 | if (!*from) | |
753 | break; | |
754 | ||
755 | /* find the end of this arg */ | |
756 | val = NULL; | |
757 | arg_end = NULL; | |
758 | val_end = NULL; | |
759 | in_quote = false; | |
760 | for (p = from;; p++) { | |
761 | if (in_quote) { | |
762 | if (!*p) | |
763 | return -EINVAL; | |
764 | if (*p == '"') | |
765 | in_quote = false; | |
766 | continue; | |
767 | } | |
19248dce | 768 | if (*p == '=' && !arg_end) { |
d07861cc SG |
769 | arg_end = p; |
770 | val = p + 1; | |
771 | } else if (*p == '"') { | |
772 | in_quote = true; | |
773 | } else if (!*p || *p == ' ') { | |
774 | val_end = p; | |
775 | if (!arg_end) | |
776 | arg_end = p; | |
777 | break; | |
778 | } | |
779 | } | |
780 | /* | |
781 | * At this point val_end points to the end of the value, or the | |
782 | * last char after the arg name, if there is no label. | |
783 | * arg_end is the char after the arg name | |
784 | * val points to the value, or NULL if there is none | |
785 | * char after the value. | |
786 | * | |
787 | * fred=1234 | |
788 | * ^ ^^ ^ | |
789 | * from || | | |
790 | * / \ \ | |
791 | * arg_end val val_end | |
792 | */ | |
793 | log_debug("from %s arg_end %ld val %ld val_end %ld\n", from, | |
794 | (long)(arg_end - from), (long)(val - from), | |
795 | (long)(val_end - from)); | |
796 | ||
797 | if (to != buf) { | |
798 | if (to >= end) | |
799 | return -E2BIG; | |
800 | *to++ = ' '; | |
801 | } | |
802 | ||
803 | /* if this is the target arg, update it */ | |
19248dce SG |
804 | if (arg_end - from == set_arg_len && |
805 | !strncmp(from, set_arg, set_arg_len)) { | |
d07861cc SG |
806 | if (!buf) { |
807 | bool has_quote = val_end[-1] == '"'; | |
808 | ||
809 | /* | |
810 | * exclude any start/end quotes from | |
811 | * calculations | |
812 | */ | |
813 | if (!val) | |
814 | val = val_end; | |
815 | *posp = val - cmdline + has_quote; | |
816 | return val_end - val - 2 * has_quote; | |
817 | } | |
818 | found_arg = true; | |
819 | if (!new_val) { | |
820 | /* delete this arg */ | |
821 | from = val_end + (*val_end == ' '); | |
822 | log_debug("delete from: %s\n", from); | |
823 | if (to != buf) | |
824 | to--; /* drop the space we added */ | |
825 | continue; | |
826 | } | |
827 | ||
828 | ret = copy_in(to, end, from, arg_end - from, new_val); | |
829 | if (ret < 0) | |
830 | return ret; | |
831 | to += ret; | |
832 | ||
833 | /* if not the target arg, copy it unchanged */ | |
834 | } else if (to) { | |
835 | int len; | |
836 | ||
837 | len = val_end - from; | |
838 | if (to + len >= end) | |
839 | return -E2BIG; | |
840 | memcpy(to, from, len); | |
841 | to += len; | |
842 | } | |
843 | from = val_end; | |
844 | } | |
845 | ||
846 | /* If we didn't find the arg, add it */ | |
847 | if (!found_arg) { | |
848 | /* trying to delete something that is not there */ | |
849 | if (!new_val || !buf) | |
850 | return -ENOENT; | |
851 | if (to >= end) | |
852 | return -E2BIG; | |
853 | ||
854 | /* add a space to separate it from the previous arg */ | |
855 | if (to != buf && to[-1] != ' ') | |
856 | *to++ = ' '; | |
857 | ret = copy_in(to, end, set_arg, set_arg_len, new_val); | |
858 | log_debug("ret=%d, to: %s buf: %s\n", ret, to, buf); | |
859 | if (ret < 0) | |
860 | return ret; | |
861 | to += ret; | |
862 | } | |
863 | ||
864 | /* delete any trailing space */ | |
865 | if (to > buf && to[-1] == ' ') | |
866 | to--; | |
867 | ||
868 | if (to >= end) | |
869 | return -E2BIG; | |
870 | *to++ = '\0'; | |
871 | ||
872 | return to - buf; | |
873 | } | |
82c0938f SG |
874 | |
875 | int bootflow_cmdline_set_arg(struct bootflow *bflow, const char *set_arg, | |
876 | const char *new_val, bool set_env) | |
877 | { | |
878 | char buf[2048]; | |
879 | char *cmd = NULL; | |
880 | int ret; | |
881 | ||
882 | ret = cmdline_set_arg(buf, sizeof(buf), bflow->cmdline, set_arg, | |
883 | new_val, NULL); | |
884 | if (ret < 0) | |
885 | return ret; | |
886 | ||
887 | ret = bootflow_cmdline_set(bflow, buf); | |
888 | if (*buf) { | |
889 | cmd = strdup(buf); | |
890 | if (!cmd) | |
891 | return -ENOMEM; | |
892 | } | |
893 | free(bflow->cmdline); | |
894 | bflow->cmdline = cmd; | |
895 | ||
896 | if (set_env) { | |
897 | ret = env_set("bootargs", bflow->cmdline); | |
898 | if (ret) | |
899 | return ret; | |
900 | } | |
901 | ||
902 | return 0; | |
903 | } | |
904 | ||
905 | int cmdline_get_arg(const char *cmdline, const char *arg, int *posp) | |
906 | { | |
907 | int ret; | |
908 | ||
909 | ret = cmdline_set_arg(NULL, 1, cmdline, arg, NULL, posp); | |
910 | ||
911 | return ret; | |
912 | } | |
913 | ||
914 | int bootflow_cmdline_get_arg(struct bootflow *bflow, const char *arg, | |
915 | const char **val) | |
916 | { | |
917 | int ret; | |
918 | int pos; | |
919 | ||
920 | ret = cmdline_get_arg(bflow->cmdline, arg, &pos); | |
921 | if (ret < 0) | |
922 | return ret; | |
923 | *val = bflow->cmdline + pos; | |
924 | ||
925 | return ret; | |
926 | } | |
33ebcb46 SG |
927 | |
928 | int bootflow_cmdline_auto(struct bootflow *bflow, const char *arg) | |
929 | { | |
930 | struct serial_device_info info; | |
931 | char buf[50]; | |
932 | int ret; | |
933 | ||
934 | ret = serial_getinfo(gd->cur_serial_dev, &info); | |
935 | if (ret) | |
936 | return ret; | |
937 | ||
938 | *buf = '\0'; | |
ea37e506 | 939 | if (!strcmp("earlycon", arg) && info.type == SERIAL_CHIP_16550_COMPATIBLE) { |
33ebcb46 SG |
940 | snprintf(buf, sizeof(buf), |
941 | "uart8250,mmio32,%#lx,%dn8", info.addr, | |
942 | info.baudrate); | |
ea37e506 MB |
943 | } else if (!strcmp("earlycon", arg) && info.type == SERIAL_CHIP_PL01X) { |
944 | snprintf(buf, sizeof(buf), | |
945 | "pl011,mmio32,%#lx,%dn8", info.addr, | |
946 | info.baudrate); | |
947 | } else if (!strcmp("console", arg) && info.type == SERIAL_CHIP_16550_COMPATIBLE) { | |
33ebcb46 SG |
948 | snprintf(buf, sizeof(buf), |
949 | "ttyS0,%dn8", info.baudrate); | |
950 | } | |
951 | ||
952 | if (!*buf) { | |
953 | printf("Unknown param '%s\n", arg); | |
954 | return -ENOENT; | |
955 | } | |
956 | ||
957 | ret = bootflow_cmdline_set_arg(bflow, arg, buf, true); | |
958 | if (ret) | |
959 | return ret; | |
960 | ||
961 | return 0; | |
962 | } |