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