]>
Commit | Line | Data |
---|---|---|
201417d7 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 | ||
9 | #include <common.h> | |
10 | #include <dm.h> | |
11 | #include <bootdev.h> | |
12 | #include <bootflow.h> | |
a8f5be17 | 13 | #include <bootmeth.h> |
201417d7 | 14 | #include <bootstd.h> |
201417d7 SG |
15 | #include <fs.h> |
16 | #include <log.h> | |
17 | #include <malloc.h> | |
18 | #include <part.h> | |
19 | #include <sort.h> | |
20 | #include <dm/device-internal.h> | |
21 | #include <dm/lists.h> | |
22 | #include <dm/uclass-internal.h> | |
23 | ||
24 | enum { | |
25 | /* | |
26 | * Set some sort of limit on the number of partitions a bootdev can | |
27 | * have. Note that for disks this limits the partitions numbers that | |
28 | * are scanned to 1..MAX_BOOTFLOWS_PER_BOOTDEV | |
29 | */ | |
30 | MAX_PART_PER_BOOTDEV = 30, | |
31 | ||
32 | /* Maximum supported length of the "boot_targets" env string */ | |
33 | BOOT_TARGETS_MAX_LEN = 100, | |
34 | }; | |
35 | ||
36 | int bootdev_add_bootflow(struct bootflow *bflow) | |
37 | { | |
201417d7 SG |
38 | struct bootstd_priv *std; |
39 | struct bootflow *new; | |
40 | int ret; | |
41 | ||
42 | assert(bflow->dev); | |
43 | ret = bootstd_get_priv(&std); | |
44 | if (ret) | |
45 | return ret; | |
46 | ||
47 | new = malloc(sizeof(*bflow)); | |
48 | if (!new) | |
49 | return log_msg_ret("bflow", -ENOMEM); | |
50 | memcpy(new, bflow, sizeof(*bflow)); | |
51 | ||
52 | list_add_tail(&new->glob_node, &std->glob_head); | |
eccb25cd SG |
53 | if (bflow->dev) { |
54 | struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); | |
55 | ||
56 | list_add_tail(&new->bm_node, &ucp->bootflow_head); | |
57 | } | |
201417d7 SG |
58 | |
59 | return 0; | |
60 | } | |
61 | ||
62 | int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp) | |
63 | { | |
64 | struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); | |
65 | ||
66 | if (list_empty(&ucp->bootflow_head)) | |
67 | return -ENOENT; | |
68 | ||
69 | *bflowp = list_first_entry(&ucp->bootflow_head, struct bootflow, | |
70 | bm_node); | |
71 | ||
72 | return 0; | |
73 | } | |
74 | ||
75 | int bootdev_next_bootflow(struct bootflow **bflowp) | |
76 | { | |
77 | struct bootflow *bflow = *bflowp; | |
78 | struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); | |
79 | ||
80 | *bflowp = NULL; | |
81 | ||
82 | if (list_is_last(&bflow->bm_node, &ucp->bootflow_head)) | |
83 | return -ENOENT; | |
84 | ||
85 | *bflowp = list_entry(bflow->bm_node.next, struct bootflow, bm_node); | |
86 | ||
87 | return 0; | |
88 | } | |
89 | ||
90 | int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name, | |
91 | struct udevice **devp) | |
92 | { | |
93 | struct udevice *dev; | |
94 | char dev_name[30]; | |
95 | char *str; | |
96 | int ret; | |
97 | ||
98 | snprintf(dev_name, sizeof(dev_name), "%s.%s", parent->name, name); | |
99 | str = strdup(dev_name); | |
100 | if (!str) | |
101 | return -ENOMEM; | |
102 | ret = device_bind_driver(parent, drv_name, str, &dev); | |
103 | if (ret) | |
104 | return ret; | |
105 | device_set_name_alloced(dev); | |
106 | *devp = dev; | |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
111 | int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, | |
112 | struct bootflow_iter *iter, struct bootflow *bflow) | |
113 | { | |
114 | struct blk_desc *desc = dev_get_uclass_plat(blk); | |
115 | struct disk_partition info; | |
116 | char partstr[20]; | |
117 | char name[60]; | |
118 | int ret; | |
119 | ||
120 | /* Sanity check */ | |
121 | if (iter->part >= MAX_PART_PER_BOOTDEV) | |
122 | return log_msg_ret("max", -ESHUTDOWN); | |
123 | ||
124 | bflow->blk = blk; | |
125 | if (iter->part) | |
126 | snprintf(partstr, sizeof(partstr), "part_%x", iter->part); | |
127 | else | |
128 | strcpy(partstr, "whole"); | |
129 | snprintf(name, sizeof(name), "%s.%s", dev->name, partstr); | |
130 | bflow->name = strdup(name); | |
131 | if (!bflow->name) | |
132 | return log_msg_ret("name", -ENOMEM); | |
133 | ||
134 | bflow->part = iter->part; | |
135 | ||
a8f5be17 SG |
136 | ret = bootmeth_check(bflow->method, iter); |
137 | if (ret) | |
138 | return log_msg_ret("check", ret); | |
139 | ||
201417d7 SG |
140 | /* |
141 | * partition numbers start at 0 so this cannot succeed, but it can tell | |
142 | * us whether there is valid media there | |
143 | */ | |
144 | ret = part_get_info(desc, iter->part, &info); | |
145 | if (!iter->part && ret == -ENOENT) | |
146 | ret = 0; | |
147 | ||
148 | /* | |
149 | * This error indicates the media is not present. Otherwise we just | |
150 | * blindly scan the next partition. We could be more intelligent here | |
151 | * and check which partition numbers actually exist. | |
152 | */ | |
153 | if (ret == -EOPNOTSUPP) | |
154 | ret = -ESHUTDOWN; | |
155 | else | |
156 | bflow->state = BOOTFLOWST_MEDIA; | |
76afc845 SG |
157 | if (ret) { |
158 | /* allow partition 1 to be missing */ | |
159 | if (iter->part == 1) { | |
160 | iter->max_part = 3; | |
161 | ret = -ENOENT; | |
162 | } | |
163 | ||
201417d7 | 164 | return log_msg_ret("part", ret); |
76afc845 | 165 | } |
201417d7 SG |
166 | |
167 | /* | |
168 | * Currently we don't get the number of partitions, so just | |
169 | * assume a large number | |
170 | */ | |
171 | iter->max_part = MAX_PART_PER_BOOTDEV; | |
172 | ||
f0e358f0 SG |
173 | /* If this is the whole disk, check if we have bootable partitions */ |
174 | if (!iter->part) { | |
175 | iter->first_bootable = part_get_bootable(desc); | |
176 | log_debug("checking bootable=%d\n", iter->first_bootable); | |
177 | ||
178 | /* if there are bootable partitions, scan only those */ | |
179 | } else if (iter->first_bootable ? !info.bootable : iter->part != 1) { | |
180 | return log_msg_ret("boot", -EINVAL); | |
181 | } else { | |
201417d7 SG |
182 | ret = fs_set_blk_dev_with_part(desc, bflow->part); |
183 | bflow->state = BOOTFLOWST_PART; | |
1aabe4ef SG |
184 | if (ret) |
185 | return log_msg_ret("fs", ret); | |
201417d7 SG |
186 | |
187 | /* Use an #ifdef due to info.sys_ind */ | |
188 | #ifdef CONFIG_DOS_PARTITION | |
189 | log_debug("%s: Found partition %x type %x fstype %d\n", | |
190 | blk->name, bflow->part, info.sys_ind, | |
191 | ret ? -1 : fs_get_type()); | |
192 | #endif | |
1aabe4ef | 193 | bflow->blk = blk; |
201417d7 SG |
194 | bflow->state = BOOTFLOWST_FS; |
195 | } | |
196 | ||
a8f5be17 SG |
197 | ret = bootmeth_read_bootflow(bflow->method, bflow); |
198 | if (ret) | |
199 | return log_msg_ret("method", ret); | |
200 | ||
201417d7 SG |
201 | return 0; |
202 | } | |
203 | ||
204 | void bootdev_list(bool probe) | |
205 | { | |
206 | struct udevice *dev; | |
207 | int ret; | |
208 | int i; | |
209 | ||
210 | printf("Seq Probed Status Uclass Name\n"); | |
211 | printf("--- ------ ------ -------- ------------------\n"); | |
212 | if (probe) | |
28a22cd9 | 213 | ret = uclass_first_device_check(UCLASS_BOOTDEV, &dev); |
201417d7 SG |
214 | else |
215 | ret = uclass_find_first_device(UCLASS_BOOTDEV, &dev); | |
216 | for (i = 0; dev; i++) { | |
217 | printf("%3x [ %c ] %6s %-9.9s %s\n", dev_seq(dev), | |
218 | device_active(dev) ? '+' : ' ', | |
ca9d9263 | 219 | ret ? simple_itoa(-ret) : "OK", |
201417d7 SG |
220 | dev_get_uclass_name(dev_get_parent(dev)), dev->name); |
221 | if (probe) | |
28a22cd9 | 222 | ret = uclass_next_device_check(&dev); |
201417d7 SG |
223 | else |
224 | ret = uclass_find_next_device(&dev); | |
225 | } | |
226 | printf("--- ------ ------ -------- ------------------\n"); | |
227 | printf("(%d bootdev%s)\n", i, i != 1 ? "s" : ""); | |
228 | } | |
229 | ||
230 | int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name) | |
231 | { | |
232 | struct udevice *bdev; | |
233 | int ret; | |
234 | ||
235 | ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, | |
236 | &bdev); | |
237 | if (ret) { | |
238 | if (ret != -ENODEV) { | |
239 | log_debug("Cannot access bootdev device\n"); | |
240 | return ret; | |
241 | } | |
242 | ||
243 | ret = bootdev_bind(parent, drv_name, "bootdev", &bdev); | |
244 | if (ret) { | |
245 | log_debug("Cannot create bootdev device\n"); | |
246 | return ret; | |
247 | } | |
248 | } | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
3a2cb96e SG |
253 | static int bootdev_get_suffix_start(struct udevice *dev, const char *suffix) |
254 | { | |
255 | int len, slen; | |
256 | ||
257 | len = strlen(dev->name); | |
258 | slen = strlen(suffix); | |
259 | if (len > slen && !strcmp(suffix, dev->name + len - slen)) | |
260 | return len - slen; | |
261 | ||
262 | return len; | |
263 | } | |
264 | ||
d7d78576 | 265 | int bootdev_setup_for_sibling_blk(struct udevice *blk, const char *drv_name) |
201417d7 SG |
266 | { |
267 | struct udevice *parent, *dev; | |
268 | char dev_name[50]; | |
3a2cb96e | 269 | int ret, len; |
201417d7 | 270 | |
3a2cb96e SG |
271 | len = bootdev_get_suffix_start(blk, ".blk"); |
272 | snprintf(dev_name, sizeof(dev_name), "%.*s.%s", len, blk->name, | |
273 | "bootdev"); | |
201417d7 SG |
274 | |
275 | parent = dev_get_parent(blk); | |
276 | ret = device_find_child_by_name(parent, dev_name, &dev); | |
277 | if (ret) { | |
278 | char *str; | |
279 | ||
280 | if (ret != -ENODEV) { | |
281 | log_debug("Cannot access bootdev device\n"); | |
282 | return ret; | |
283 | } | |
284 | str = strdup(dev_name); | |
285 | if (!str) | |
286 | return -ENOMEM; | |
287 | ||
288 | ret = device_bind_driver(parent, drv_name, str, &dev); | |
289 | if (ret) { | |
290 | log_debug("Cannot create bootdev device\n"); | |
291 | return ret; | |
292 | } | |
293 | device_set_name_alloced(dev); | |
294 | } | |
295 | ||
296 | return 0; | |
297 | } | |
298 | ||
299 | int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp) | |
300 | { | |
301 | struct udevice *parent = dev_get_parent(dev); | |
302 | struct udevice *blk; | |
303 | int ret, len; | |
201417d7 SG |
304 | |
305 | if (device_get_uclass_id(dev) != UCLASS_BOOTDEV) | |
306 | return -EINVAL; | |
307 | ||
d7d78576 SG |
308 | /* |
309 | * This should always work if bootdev_setup_for_sibling_blk() was used | |
310 | */ | |
3a2cb96e | 311 | len = bootdev_get_suffix_start(dev, ".bootdev"); |
201417d7 | 312 | ret = device_find_child_by_namelen(parent, dev->name, len, &blk); |
3a2cb96e SG |
313 | if (ret) { |
314 | char dev_name[50]; | |
315 | ||
316 | snprintf(dev_name, sizeof(dev_name), "%.*s.blk", len, | |
317 | dev->name); | |
318 | ret = device_find_child_by_name(parent, dev_name, &blk); | |
319 | if (ret) | |
320 | return log_msg_ret("find", ret); | |
321 | } | |
965020c3 SG |
322 | ret = device_probe(blk); |
323 | if (ret) | |
324 | return log_msg_ret("act", ret); | |
201417d7 SG |
325 | *blkp = blk; |
326 | ||
327 | return 0; | |
328 | } | |
329 | ||
330 | static int bootdev_get_from_blk(struct udevice *blk, struct udevice **bootdevp) | |
331 | { | |
332 | struct udevice *parent = dev_get_parent(blk); | |
333 | struct udevice *bootdev; | |
334 | char dev_name[50]; | |
3a2cb96e | 335 | int ret, len; |
201417d7 SG |
336 | |
337 | if (device_get_uclass_id(blk) != UCLASS_BLK) | |
338 | return -EINVAL; | |
339 | ||
d7d78576 | 340 | /* This should always work if bootdev_setup_for_sibling_blk() was used */ |
3a2cb96e SG |
341 | len = bootdev_get_suffix_start(blk, ".blk"); |
342 | snprintf(dev_name, sizeof(dev_name), "%.*s.%s", len, blk->name, | |
343 | "bootdev"); | |
201417d7 SG |
344 | ret = device_find_child_by_name(parent, dev_name, &bootdev); |
345 | if (ret) | |
346 | return log_msg_ret("find", ret); | |
347 | *bootdevp = bootdev; | |
348 | ||
349 | return 0; | |
350 | } | |
351 | ||
352 | int bootdev_unbind_dev(struct udevice *parent) | |
353 | { | |
354 | struct udevice *dev; | |
355 | int ret; | |
356 | ||
357 | ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, &dev); | |
358 | if (!ret) { | |
359 | ret = device_remove(dev, DM_REMOVE_NORMAL); | |
360 | if (ret) | |
361 | return log_msg_ret("rem", ret); | |
362 | ret = device_unbind(dev); | |
363 | if (ret) | |
364 | return log_msg_ret("unb", ret); | |
365 | } | |
366 | ||
367 | return 0; | |
368 | } | |
369 | ||
74ebfb60 SG |
370 | /** |
371 | * label_to_uclass() - Convert a label to a uclass and sequence number | |
372 | * | |
373 | * @label: Label to look up (e.g. "mmc1" or "mmc0") | |
374 | * @seqp: Returns the sequence number, or -1 if none | |
d9f48579 SG |
375 | * @method_flagsp: If non-NULL, returns any flags implied by the label |
376 | * (enum bootflow_meth_flags_t), 0 if none | |
1736b4af SG |
377 | * Returns: sequence number on success, -EPFNOSUPPORT is the uclass is not |
378 | * known, other -ve error code on other error | |
74ebfb60 | 379 | */ |
d9f48579 | 380 | static int label_to_uclass(const char *label, int *seqp, int *method_flagsp) |
74ebfb60 | 381 | { |
d9f48579 | 382 | int seq, len, method_flags; |
74ebfb60 SG |
383 | enum uclass_id id; |
384 | const char *end; | |
74ebfb60 | 385 | |
d9f48579 | 386 | method_flags = 0; |
74ebfb60 SG |
387 | seq = trailing_strtoln_end(label, NULL, &end); |
388 | len = end - label; | |
389 | if (!len) | |
390 | return -EINVAL; | |
391 | id = uclass_get_by_namelen(label, len); | |
392 | log_debug("find %s: seq=%d, id=%d/%s\n", label, seq, id, | |
393 | uclass_get_name(id)); | |
394 | if (id == UCLASS_INVALID) { | |
0c1f4a9f SG |
395 | /* try some special cases */ |
396 | if (IS_ENABLED(CONFIG_BOOTDEV_SPI_FLASH) && | |
397 | !strncmp("spi", label, len)) { | |
398 | id = UCLASS_SPI_FLASH; | |
d9f48579 SG |
399 | } else if (IS_ENABLED(CONFIG_BOOTDEV_ETH) && |
400 | !strncmp("pxe", label, len)) { | |
401 | id = UCLASS_ETH; | |
402 | method_flags |= BOOTFLOW_METHF_PXE_ONLY; | |
403 | } else if (IS_ENABLED(CONFIG_BOOTDEV_ETH) && | |
404 | !strncmp("dhcp", label, len)) { | |
405 | id = UCLASS_ETH; | |
406 | method_flags |= BOOTFLOW_METHF_DHCP_ONLY; | |
0c1f4a9f | 407 | } else { |
1736b4af | 408 | return -EPFNOSUPPORT; |
0c1f4a9f | 409 | } |
74ebfb60 SG |
410 | } |
411 | if (id == UCLASS_USB) | |
412 | id = UCLASS_MASS_STORAGE; | |
413 | *seqp = seq; | |
d9f48579 SG |
414 | if (method_flagsp) |
415 | *method_flagsp = method_flags; | |
74ebfb60 SG |
416 | |
417 | return id; | |
418 | } | |
419 | ||
d9f48579 SG |
420 | int bootdev_find_by_label(const char *label, struct udevice **devp, |
421 | int *method_flagsp) | |
201417d7 | 422 | { |
d9f48579 | 423 | int seq, ret, method_flags = 0; |
201417d7 SG |
424 | struct udevice *media; |
425 | struct uclass *uc; | |
426 | enum uclass_id id; | |
201417d7 | 427 | |
d9f48579 | 428 | ret = label_to_uclass(label, &seq, &method_flags); |
74ebfb60 SG |
429 | if (ret < 0) |
430 | return log_msg_ret("uc", ret); | |
431 | id = ret; | |
201417d7 SG |
432 | |
433 | /* Iterate through devices in the media uclass (e.g. UCLASS_MMC) */ | |
434 | uclass_id_foreach_dev(id, media, uc) { | |
435 | struct udevice *bdev, *blk; | |
436 | int ret; | |
437 | ||
438 | /* if there is no seq, match anything */ | |
439 | if (seq != -1 && dev_seq(media) != seq) { | |
440 | log_debug("- skip, media seq=%d\n", dev_seq(media)); | |
441 | continue; | |
442 | } | |
443 | ||
444 | ret = device_find_first_child_by_uclass(media, UCLASS_BOOTDEV, | |
445 | &bdev); | |
446 | if (ret) { | |
447 | log_debug("- looking via blk, seq=%d, id=%d\n", seq, | |
448 | id); | |
449 | ret = blk_find_device(id, seq, &blk); | |
450 | if (!ret) { | |
451 | log_debug("- get from blk %s\n", blk->name); | |
452 | ret = bootdev_get_from_blk(blk, &bdev); | |
453 | } | |
454 | } | |
455 | if (!ret) { | |
456 | log_debug("- found %s\n", bdev->name); | |
457 | *devp = bdev; | |
66e3dce7 SG |
458 | |
459 | /* | |
460 | * if no sequence number was provided, we must scan all | |
461 | * bootdevs for this media uclass | |
462 | */ | |
463 | if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && seq == -1) | |
464 | method_flags |= BOOTFLOW_METHF_SINGLE_UCLASS; | |
d9f48579 SG |
465 | if (method_flagsp) |
466 | *method_flagsp = method_flags; | |
201417d7 SG |
467 | return 0; |
468 | } | |
469 | log_debug("- no device in %s\n", media->name); | |
470 | } | |
201417d7 SG |
471 | |
472 | return -ENOENT; | |
473 | } | |
474 | ||
d9f48579 SG |
475 | int bootdev_find_by_any(const char *name, struct udevice **devp, |
476 | int *method_flagsp) | |
201417d7 SG |
477 | { |
478 | struct udevice *dev; | |
d9f48579 | 479 | int method_flags = 0; |
66e3dce7 | 480 | int ret = -ENODEV, seq; |
201417d7 SG |
481 | char *endp; |
482 | ||
483 | seq = simple_strtol(name, &endp, 16); | |
484 | ||
485 | /* Select by name, label or number */ | |
486 | if (*endp) { | |
487 | ret = uclass_get_device_by_name(UCLASS_BOOTDEV, name, &dev); | |
488 | if (ret == -ENODEV) { | |
d9f48579 | 489 | ret = bootdev_find_by_label(name, &dev, &method_flags); |
201417d7 SG |
490 | if (ret) { |
491 | printf("Cannot find bootdev '%s' (err=%d)\n", | |
492 | name, ret); | |
d9f48579 | 493 | return log_msg_ret("lab", ret); |
201417d7 SG |
494 | } |
495 | ret = device_probe(dev); | |
496 | } | |
497 | if (ret) { | |
498 | printf("Cannot probe bootdev '%s' (err=%d)\n", name, | |
499 | ret); | |
d9f48579 | 500 | return log_msg_ret("pro", ret); |
201417d7 | 501 | } |
66e3dce7 | 502 | } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) { |
201417d7 | 503 | ret = uclass_get_device_by_seq(UCLASS_BOOTDEV, seq, &dev); |
66e3dce7 | 504 | method_flags |= BOOTFLOW_METHF_SINGLE_DEV; |
201417d7 SG |
505 | } |
506 | if (ret) { | |
507 | printf("Cannot find '%s' (err=%d)\n", name, ret); | |
508 | return ret; | |
509 | } | |
510 | ||
511 | *devp = dev; | |
d9f48579 SG |
512 | if (method_flagsp) |
513 | *method_flagsp = method_flags; | |
201417d7 SG |
514 | |
515 | return 0; | |
516 | } | |
517 | ||
66e3dce7 SG |
518 | int bootdev_hunt_and_find_by_label(const char *label, struct udevice **devp, |
519 | int *method_flagsp) | |
520 | { | |
521 | int ret; | |
522 | ||
523 | ret = bootdev_hunt(label, false); | |
524 | if (ret) | |
525 | return log_msg_ret("scn", ret); | |
526 | ret = bootdev_find_by_label(label, devp, method_flagsp); | |
527 | if (ret) | |
528 | return log_msg_ret("fnd", ret); | |
529 | ||
530 | return 0; | |
531 | } | |
532 | ||
b85fc8db SG |
533 | static int default_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, |
534 | struct bootflow *bflow) | |
535 | { | |
536 | struct udevice *blk; | |
537 | int ret; | |
538 | ||
539 | ret = bootdev_get_sibling_blk(dev, &blk); | |
3500ae13 SG |
540 | log_debug("sibling_blk ret=%d, blk=%s\n", ret, |
541 | ret ? "(none)" : blk->name); | |
b85fc8db SG |
542 | /* |
543 | * If there is no media, indicate that no more partitions should be | |
544 | * checked | |
545 | */ | |
546 | if (ret == -EOPNOTSUPP) | |
547 | ret = -ESHUTDOWN; | |
548 | if (ret) | |
549 | return log_msg_ret("blk", ret); | |
550 | assert(blk); | |
551 | ret = bootdev_find_in_blk(dev, blk, iter, bflow); | |
552 | if (ret) | |
553 | return log_msg_ret("find", ret); | |
554 | ||
555 | return 0; | |
556 | } | |
557 | ||
201417d7 SG |
558 | int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, |
559 | struct bootflow *bflow) | |
560 | { | |
561 | const struct bootdev_ops *ops = bootdev_get_ops(dev); | |
562 | ||
f738c73a | 563 | log_debug("->get_bootflow %s=%p\n", dev->name, ops->get_bootflow); |
b190deb8 | 564 | bootflow_init(bflow, dev, iter->method); |
b85fc8db SG |
565 | if (!ops->get_bootflow) |
566 | return default_get_bootflow(dev, iter, bflow); | |
201417d7 SG |
567 | |
568 | return ops->get_bootflow(dev, iter, bflow); | |
569 | } | |
570 | ||
571 | void bootdev_clear_bootflows(struct udevice *dev) | |
572 | { | |
573 | struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); | |
574 | ||
575 | while (!list_empty(&ucp->bootflow_head)) { | |
576 | struct bootflow *bflow; | |
577 | ||
578 | bflow = list_first_entry(&ucp->bootflow_head, struct bootflow, | |
579 | bm_node); | |
a8f5be17 | 580 | bootflow_remove(bflow); |
201417d7 SG |
581 | } |
582 | } | |
583 | ||
e4b69489 SG |
584 | int bootdev_next_label(struct bootflow_iter *iter, struct udevice **devp, |
585 | int *method_flagsp) | |
586 | { | |
587 | struct udevice *dev; | |
588 | ||
589 | log_debug("next\n"); | |
590 | for (dev = NULL; !dev && iter->labels[++iter->cur_label];) { | |
1736b4af SG |
591 | const char *label = iter->labels[iter->cur_label]; |
592 | int ret; | |
593 | ||
594 | log_debug("Scanning: %s\n", label); | |
595 | ret = bootdev_hunt_and_find_by_label(label, &dev, | |
596 | method_flagsp); | |
597 | if (iter->flags & BOOTFLOWIF_SHOW) { | |
598 | if (ret == -EPFNOSUPPORT) { | |
599 | log_warning("Unknown uclass '%s' in label\n", | |
600 | label); | |
601 | } else if (ret == -ENOENT) { | |
602 | /* | |
603 | * looking for, e.g. 'scsi0' should find | |
604 | * something if SCSI is present | |
605 | */ | |
606 | if (!trailing_strtol(label)) { | |
607 | log_warning("No bootdevs for '%s'\n", | |
608 | label); | |
609 | } | |
610 | } | |
611 | } | |
612 | ||
e4b69489 SG |
613 | } |
614 | ||
615 | if (!dev) | |
616 | return log_msg_ret("fin", -ENODEV); | |
617 | *devp = dev; | |
618 | ||
619 | return 0; | |
620 | } | |
621 | ||
43e89a30 SG |
622 | int bootdev_next_prio(struct bootflow_iter *iter, struct udevice **devp) |
623 | { | |
624 | struct udevice *dev = *devp; | |
625 | bool found; | |
626 | int ret; | |
627 | ||
628 | /* find the next device with this priority */ | |
629 | *devp = NULL; | |
630 | log_debug("next prio %d: dev=%p/%s\n", iter->cur_prio, dev, | |
631 | dev ? dev->name : "none"); | |
632 | do { | |
633 | /* | |
634 | * Don't probe devices here since they may not be of the | |
635 | * required priority | |
636 | */ | |
637 | if (!dev) | |
638 | uclass_find_first_device(UCLASS_BOOTDEV, &dev); | |
639 | else | |
640 | uclass_find_next_device(&dev); | |
641 | found = false; | |
642 | ||
643 | /* scan for the next device with the correct priority */ | |
644 | while (dev) { | |
645 | struct bootdev_uc_plat *plat; | |
646 | ||
647 | plat = dev_get_uclass_plat(dev); | |
648 | log_debug("- %s: %d, want %d\n", dev->name, plat->prio, | |
649 | iter->cur_prio); | |
650 | if (plat->prio == iter->cur_prio) | |
651 | break; | |
652 | uclass_find_next_device(&dev); | |
653 | } | |
654 | ||
655 | /* none found for this priority, so move to the next */ | |
656 | if (!dev) { | |
657 | log_debug("None found at prio %d, moving to %d\n", | |
658 | iter->cur_prio, iter->cur_prio + 1); | |
659 | if (++iter->cur_prio == BOOTDEVP_COUNT) | |
660 | return log_msg_ret("fin", -ENODEV); | |
661 | ||
4f806f31 | 662 | if (iter->flags & BOOTFLOWIF_HUNT) { |
43e89a30 SG |
663 | /* hunt to find new bootdevs */ |
664 | ret = bootdev_hunt_prio(iter->cur_prio, | |
665 | iter->flags & | |
4f806f31 | 666 | BOOTFLOWIF_SHOW); |
3500ae13 SG |
667 | log_debug("- bootdev_hunt_prio() ret %d\n", |
668 | ret); | |
43e89a30 SG |
669 | if (ret) |
670 | return log_msg_ret("hun", ret); | |
671 | } | |
672 | } else { | |
673 | ret = device_probe(dev); | |
674 | if (ret) { | |
675 | log_debug("Device '%s' failed to probe\n", | |
676 | dev->name); | |
677 | dev = NULL; | |
678 | } | |
679 | } | |
680 | } while (!dev); | |
681 | ||
682 | *devp = dev; | |
683 | ||
684 | return 0; | |
685 | } | |
686 | ||
91943ff7 SG |
687 | int bootdev_setup_iter(struct bootflow_iter *iter, const char *label, |
688 | struct udevice **devp, int *method_flagsp) | |
201417d7 | 689 | { |
91943ff7 | 690 | struct udevice *bootstd, *dev = NULL; |
4f806f31 | 691 | bool show = iter->flags & BOOTFLOWIF_SHOW; |
47aedc29 | 692 | int method_flags; |
201417d7 SG |
693 | int ret; |
694 | ||
695 | ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); | |
696 | if (ret) { | |
697 | log_err("Missing bootstd device\n"); | |
698 | return log_msg_ret("std", ret); | |
699 | } | |
700 | ||
eacc2611 | 701 | /* hunt for any pre-scan devices */ |
4f806f31 | 702 | if (iter->flags & BOOTFLOWIF_HUNT) { |
eacc2611 | 703 | ret = bootdev_hunt_prio(BOOTDEVP_1_PRE_SCAN, show); |
3500ae13 | 704 | log_debug("- bootdev_hunt_prio() ret %d\n", ret); |
eacc2611 SG |
705 | if (ret) |
706 | return log_msg_ret("pre", ret); | |
707 | } | |
708 | ||
201417d7 | 709 | /* Handle scanning a single device */ |
91943ff7 | 710 | if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && label) { |
4f806f31 | 711 | if (iter->flags & BOOTFLOWIF_HUNT) { |
91943ff7 SG |
712 | ret = bootdev_hunt(label, show); |
713 | if (ret) | |
714 | return log_msg_ret("hun", ret); | |
715 | } | |
716 | ret = bootdev_find_by_any(label, &dev, &method_flags); | |
717 | if (ret) | |
718 | return log_msg_ret("lab", ret); | |
719 | ||
720 | log_debug("method_flags: %x\n", method_flags); | |
721 | if (method_flags & BOOTFLOW_METHF_SINGLE_UCLASS) | |
4f806f31 | 722 | iter->flags |= BOOTFLOWIF_SINGLE_UCLASS; |
91943ff7 | 723 | else if (method_flags & BOOTFLOW_METHF_SINGLE_DEV) |
4f806f31 | 724 | iter->flags |= BOOTFLOWIF_SINGLE_DEV; |
91943ff7 | 725 | else |
4f806f31 | 726 | iter->flags |= BOOTFLOWIF_SINGLE_MEDIA; |
91943ff7 | 727 | log_debug("Selected label: %s, flags %x\n", label, iter->flags); |
47aedc29 SG |
728 | } else { |
729 | bool ok; | |
730 | ||
731 | /* This either returns a non-empty list or NULL */ | |
732 | iter->labels = bootstd_get_bootdev_order(bootstd, &ok); | |
733 | if (!ok) | |
734 | return log_msg_ret("ord", -ENOMEM); | |
735 | log_debug("setup labels %p\n", iter->labels); | |
736 | if (iter->labels) { | |
737 | iter->cur_label = -1; | |
738 | ret = bootdev_next_label(iter, &dev, &method_flags); | |
739 | } else { | |
740 | ret = bootdev_next_prio(iter, &dev); | |
741 | method_flags = 0; | |
742 | } | |
743 | if (!dev) | |
744 | return log_msg_ret("fin", -ENOENT); | |
745 | log_debug("Selected bootdev: %s\n", dev->name); | |
201417d7 SG |
746 | } |
747 | ||
201417d7 SG |
748 | ret = device_probe(dev); |
749 | if (ret) | |
750 | return log_msg_ret("probe", ret); | |
47aedc29 SG |
751 | if (method_flagsp) |
752 | *method_flagsp = method_flags; | |
201417d7 SG |
753 | *devp = dev; |
754 | ||
755 | return 0; | |
756 | } | |
757 | ||
c7b63d50 SG |
758 | static int bootdev_hunt_drv(struct bootdev_hunter *info, uint seq, bool show) |
759 | { | |
760 | const char *name = uclass_get_name(info->uclass); | |
761 | struct bootstd_priv *std; | |
762 | int ret; | |
763 | ||
764 | ret = bootstd_get_priv(&std); | |
765 | if (ret) | |
766 | return log_msg_ret("std", ret); | |
767 | ||
768 | if (!(std->hunters_used & BIT(seq))) { | |
769 | if (show) | |
770 | printf("Hunting with: %s\n", | |
771 | uclass_get_name(info->uclass)); | |
772 | log_debug("Hunting with: %s\n", name); | |
773 | if (info->hunt) { | |
774 | ret = info->hunt(info, show); | |
3500ae13 | 775 | log_debug(" - hunt result %d\n", ret); |
c7b63d50 SG |
776 | if (ret) |
777 | return ret; | |
778 | } | |
779 | std->hunters_used |= BIT(seq); | |
780 | } | |
781 | ||
782 | return 0; | |
783 | } | |
784 | ||
785 | int bootdev_hunt(const char *spec, bool show) | |
786 | { | |
787 | struct bootdev_hunter *start; | |
788 | const char *end; | |
789 | int n_ent, i; | |
790 | int result; | |
791 | size_t len; | |
792 | ||
793 | start = ll_entry_start(struct bootdev_hunter, bootdev_hunter); | |
794 | n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter); | |
795 | result = 0; | |
796 | ||
797 | len = SIZE_MAX; | |
798 | if (spec) { | |
799 | trailing_strtoln_end(spec, NULL, &end); | |
800 | len = end - spec; | |
801 | } | |
802 | ||
803 | for (i = 0; i < n_ent; i++) { | |
804 | struct bootdev_hunter *info = start + i; | |
805 | const char *name = uclass_get_name(info->uclass); | |
806 | int ret; | |
807 | ||
808 | log_debug("looking at %.*s for %s\n", | |
809 | (int)max(strlen(name), len), spec, name); | |
e4b69489 SG |
810 | if (spec && strncmp(spec, name, max(strlen(name), len))) { |
811 | if (info->uclass != UCLASS_ETH || | |
812 | (strcmp("dhcp", spec) && strcmp("pxe", spec))) | |
813 | continue; | |
814 | } | |
c7b63d50 SG |
815 | ret = bootdev_hunt_drv(info, i, show); |
816 | if (ret) | |
817 | result = ret; | |
818 | } | |
819 | ||
820 | return result; | |
821 | } | |
822 | ||
79a7d4a6 SG |
823 | int bootdev_hunt_prio(enum bootdev_prio_t prio, bool show) |
824 | { | |
825 | struct bootdev_hunter *start; | |
826 | int n_ent, i; | |
827 | int result; | |
828 | ||
829 | start = ll_entry_start(struct bootdev_hunter, bootdev_hunter); | |
830 | n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter); | |
831 | result = 0; | |
832 | ||
833 | log_debug("Hunting for priority %d\n", prio); | |
834 | for (i = 0; i < n_ent; i++) { | |
835 | struct bootdev_hunter *info = start + i; | |
836 | int ret; | |
837 | ||
838 | if (prio != info->prio) | |
839 | continue; | |
840 | ret = bootdev_hunt_drv(info, i, show); | |
3500ae13 | 841 | log_debug("bootdev_hunt_drv() return %d\n", ret); |
79a7d4a6 SG |
842 | if (ret && ret != -ENOENT) |
843 | result = ret; | |
844 | } | |
3500ae13 | 845 | log_debug("exit %d\n", result); |
79a7d4a6 SG |
846 | |
847 | return result; | |
848 | } | |
849 | ||
bd90b092 SG |
850 | void bootdev_list_hunters(struct bootstd_priv *std) |
851 | { | |
852 | struct bootdev_hunter *orig, *start; | |
853 | int n_ent, i; | |
854 | ||
855 | orig = ll_entry_start(struct bootdev_hunter, bootdev_hunter); | |
856 | n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter); | |
857 | ||
858 | /* | |
859 | * workaround for strange bug in clang-12 which sees all the below data | |
860 | * as zeroes. Any access of start seems to fix it, such as | |
861 | * | |
862 | * printf("%p", start); | |
863 | * | |
864 | * Use memcpy() to force the correct behaviour. | |
865 | */ | |
866 | memcpy(&start, &orig, sizeof(orig)); | |
867 | printf("%4s %4s %-15s %s\n", "Prio", "Used", "Uclass", "Hunter"); | |
868 | printf("%4s %4s %-15s %s\n", "----", "----", "---------------", "---------------"); | |
869 | for (i = 0; i < n_ent; i++) { | |
870 | struct bootdev_hunter *info = start + i; | |
871 | ||
872 | printf("%4d %4s %-15s %s\n", info->prio, | |
873 | std->hunters_used & BIT(i) ? "*" : "", | |
874 | uclass_get_name(info->uclass), | |
875 | info->drv ? info->drv->name : "(none)"); | |
876 | } | |
877 | ||
878 | printf("(total hunters: %d)\n", n_ent); | |
879 | } | |
880 | ||
201417d7 SG |
881 | static int bootdev_post_bind(struct udevice *dev) |
882 | { | |
883 | struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); | |
884 | ||
885 | INIT_LIST_HEAD(&ucp->bootflow_head); | |
886 | ||
887 | return 0; | |
888 | } | |
889 | ||
890 | static int bootdev_pre_unbind(struct udevice *dev) | |
891 | { | |
892 | bootdev_clear_bootflows(dev); | |
893 | ||
894 | return 0; | |
895 | } | |
896 | ||
897 | UCLASS_DRIVER(bootdev) = { | |
898 | .id = UCLASS_BOOTDEV, | |
899 | .name = "bootdev", | |
900 | .flags = DM_UC_FLAG_SEQ_ALIAS, | |
901 | .per_device_plat_auto = sizeof(struct bootdev_uc_plat), | |
902 | .post_bind = bootdev_post_bind, | |
903 | .pre_unbind = bootdev_pre_unbind, | |
904 | }; |