1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * (C) Copyright 2017 Theobroma Systems Design und Consulting GmbH 4 */ 5 6#include <dm.h> 7#include <fdt_support.h> 8#include <log.h> 9#include <mmc.h> 10#include <spl.h> 11#include <asm/global_data.h> 12#include <dm/uclass-internal.h> 13 14#if CONFIG_IS_ENABLED(OF_LIBFDT) 15/** 16 * spl_node_to_boot_device() - maps from a DT-node to a SPL boot device 17 * @node: of_offset of the node 18 * 19 * The SPL framework uses BOOT_DEVICE_... constants to identify its boot 20 * sources. These may take on a device-specific meaning, depending on 21 * what nodes are enabled in a DTS (e.g. BOOT_DEVICE_MMC1 may refer to 22 * different controllers/block-devices, depending on which SD/MMC controllers 23 * are enabled in any given DTS). This function maps from a DT-node back 24 * onto a BOOT_DEVICE_... constant, considering the currently active devices. 25 * 26 * Returns 27 * -ENOENT, if no device matching the node could be found 28 * -ENOSYS, if the device matching the node can not be mapped onto a 29 * SPL boot device (e.g. the third MMC device) 30 * -1, for unspecified failures 31 * a positive integer (from the BOOT_DEVICE_... family) on success. 32 */ 33 34static int spl_node_to_boot_device(int node) 35{ 36 struct udevice *parent; 37 38 /* 39 * This should eventually move into the SPL code, once SPL becomes 40 * aware of the block-device layer. Until then (and to avoid unneeded 41 * delays in getting this feature out), it lives at the board-level. 42 */ 43 if (!uclass_get_device_by_of_offset(UCLASS_MMC, node, &parent)) { 44 struct udevice *dev; 45 struct blk_desc *desc = NULL; 46 47 for (device_find_first_child(parent, &dev); 48 dev; 49 device_find_next_child(&dev)) { 50 if (device_get_uclass_id(dev) == UCLASS_BLK) { 51 desc = dev_get_uclass_plat(dev); 52 break; 53 } 54 } 55 56 if (!desc) 57 return -ENOENT; 58 59 switch (desc->devnum) { 60 case 0: 61 return BOOT_DEVICE_MMC1; 62 case 1: 63 return BOOT_DEVICE_MMC2; 64 default: 65 return -ENOSYS; 66 } 67 } 68 69 /* 70 * SPL doesn't differentiate SPI flashes, so we keep the detection 71 * brief and inaccurate... hopefully, the common SPL layer can be 72 * extended with awareness of the BLK layer (and matching OF_CONTROL) 73 * soon. 74 */ 75 if (!uclass_get_device_by_of_offset(UCLASS_SPI_FLASH, node, &parent)) 76 return BOOT_DEVICE_SPI; 77 78 return -1; 79} 80 81/** 82 * board_spl_was_booted_from() - retrieves the of-path the SPL was loaded from 83 * 84 * To support a 'same-as-spl' specification in the search-order for the next 85 * stage, we need a SoC- or board-specific way to handshake with what 'came 86 * before us' (either a BROM or TPL stage) and map the info retrieved onto 87 * a OF path. 88 * 89 * Returns 90 * NULL, on failure or if the device could not be identified 91 * a of_path (a string), on success 92 */ 93__weak const char *board_spl_was_booted_from(void) 94{ 95 debug("%s: no support for 'same-as-spl' for this board\n", __func__); 96 return NULL; 97} 98 99void board_boot_order(u32 *spl_boot_list) 100{ 101 /* In case of no fdt (or only plat), use spl_boot_device() */ 102 if (!CONFIG_IS_ENABLED(OF_CONTROL) || CONFIG_IS_ENABLED(OF_PLATDATA)) { 103 spl_boot_list[0] = spl_boot_device(); 104 return; 105 } 106 107 const void *blob = gd->fdt_blob; 108 int chosen_node = fdt_path_offset(blob, "/chosen"); 109 int idx = 0; 110 int elem; 111 int boot_device; 112 int node; 113 const char *conf; 114 115 if (chosen_node < 0) { 116 debug("%s: /chosen not found, using spl_boot_device()\n", 117 __func__); 118 spl_boot_list[0] = spl_boot_device(); 119 return; 120 } 121 122 for (elem = 0; 123 (conf = fdt_stringlist_get(blob, chosen_node, 124 "u-boot,spl-boot-order", elem, NULL)); 125 elem++) { 126 const char *alias; 127 128 /* Handle the case of 'same device the SPL was loaded from' */ 129 if (strncmp(conf, "same-as-spl", 11) == 0) { 130 conf = board_spl_was_booted_from(); 131 if (!conf) 132 continue; 133 } 134 135 /* First check if the list element is an alias */ 136 alias = fdt_get_alias(blob, conf); 137 if (alias) 138 conf = alias; 139 140 /* Try to resolve the config item (or alias) as a path */ 141 node = fdt_path_offset(blob, conf); 142 if (node < 0) { 143 debug("%s: could not find %s in FDT\n", __func__, conf); 144 continue; 145 } 146 147 /* Try to map this back onto SPL boot devices */ 148 boot_device = spl_node_to_boot_device(node); 149 if (boot_device < 0) { 150 debug("%s: could not map node %s to a boot-device\n", 151 __func__, conf); 152 continue; 153 } 154 155 spl_boot_list[idx++] = boot_device; 156 } 157 158 /* If we had no matches, fall back to spl_boot_device */ 159 if (idx == 0) 160 spl_boot_list[0] = spl_boot_device(); 161} 162 163int spl_decode_boot_device(u32 boot_device, char *buf, size_t buflen) 164{ 165 struct udevice *dev; 166#if CONFIG_IS_ENABLED(BLK) 167 int dev_num; 168#endif 169 int ret; 170 171 if (boot_device == BOOT_DEVICE_SPI) { 172 /* Revert spl_node_to_boot_device() logic to find appropriate SPI flash device */ 173 174 /* 175 * Devices with multiple SPI flash devices will take the first SPI flash found in 176 * /chosen/u-boot,spl-boot-order. 177 */ 178 const void *blob = gd->fdt_blob; 179 int chosen_node = fdt_path_offset(blob, "/chosen"); 180 int elem; 181 int node; 182 const char *conf; 183 184 if (chosen_node < 0) { 185 debug("%s: /chosen not found\n", __func__); 186 return -ENODEV; 187 } 188 189 for (elem = 0; 190 (conf = fdt_stringlist_get(blob, chosen_node, 191 "u-boot,spl-boot-order", elem, NULL)); 192 elem++) { 193 const char *alias; 194 195 /* Handle the case of 'same device the SPL was loaded from' */ 196 if (strncmp(conf, "same-as-spl", 11) == 0) { 197 conf = board_spl_was_booted_from(); 198 if (!conf) 199 continue; 200 } 201 202 /* First check if the list element is an alias */ 203 alias = fdt_get_alias(blob, conf); 204 if (alias) 205 conf = alias; 206 207 /* Try to resolve the config item (or alias) as a path */ 208 node = fdt_path_offset(blob, conf); 209 if (node < 0) { 210 debug("%s: could not find %s in FDT\n", __func__, conf); 211 continue; 212 } 213 214 ret = uclass_find_device_by_of_offset(UCLASS_SPI_FLASH, node, &dev); 215 if (ret) { 216 debug("%s: could not find udevice for %s\n", __func__, conf); 217 continue; 218 } 219 220 return ofnode_get_path(dev_ofnode(dev), buf, buflen); 221 } 222 223 return -ENODEV; 224 } 225 226#if CONFIG_IS_ENABLED(BLK) 227 dev_num = (boot_device == BOOT_DEVICE_MMC1) ? 0 : 1; 228 229 ret = blk_find_device(UCLASS_MMC, dev_num, &dev); 230 if (ret) { 231 debug("%s: could not find blk device for MMC device %d: %d\n", 232 __func__, dev_num, ret); 233 return ret; 234 } 235 236 dev = dev_get_parent(dev); 237 return ofnode_get_path(dev_ofnode(dev), buf, buflen); 238#else 239 return -ENODEV; 240#endif 241} 242 243void spl_perform_fixups(struct spl_image_info *spl_image) 244{ 245 void *blob = spl_image_fdt_addr(spl_image); 246 char boot_ofpath[512]; 247 int chosen, ret; 248 249 /* 250 * Inject the ofpath of the device the full U-Boot (or Linux in 251 * Falcon-mode) was booted from into the FDT, if a FDT has been 252 * loaded at the same time. 253 */ 254 if (!blob) 255 return; 256 257 ret = spl_decode_boot_device(spl_image->boot_device, boot_ofpath, sizeof(boot_ofpath)); 258 if (ret) { 259 pr_err("%s: could not map boot_device to ofpath: %d\n", __func__, ret); 260 return; 261 } 262 263 chosen = fdt_find_or_add_subnode(blob, 0, "chosen"); 264 if (chosen < 0) { 265 pr_err("%s: could not find/create '/chosen'\n", __func__); 266 return; 267 } 268 fdt_setprop_string(blob, chosen, 269 "u-boot,spl-boot-device", boot_ofpath); 270} 271#endif 272