1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Uclass implementation for standard boot
4  *
5  * Copyright 2021 Google LLC
6  * Written by Simon Glass <sjg@chromium.org>
7  */
8 
9 #include <alist.h>
10 #include <bootflow.h>
11 #include <bootstd.h>
12 #include <dm.h>
13 #include <env.h>
14 #include <log.h>
15 #include <malloc.h>
16 #include <dm/device-internal.h>
17 #include <dm/lists.h>
18 #include <dm/read.h>
19 #include <dm/uclass-internal.h>
20 
21 DECLARE_GLOBAL_DATA_PTR;
22 
23 /* These are used if filename-prefixes is not present */
24 const char *const default_prefixes[] = {"/", "/boot/", NULL};
25 
bootstd_of_to_plat(struct udevice * dev)26 static int bootstd_of_to_plat(struct udevice *dev)
27 {
28 	struct bootstd_priv *priv = dev_get_priv(dev);
29 	int ret;
30 
31 	if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) {
32 		/* Don't check errors since livetree and flattree are different */
33 		ret = dev_read_string_list(dev, "filename-prefixes",
34 					   &priv->prefixes);
35 		dev_read_string_list(dev, "bootdev-order",
36 				     &priv->bootdev_order);
37 
38 		priv->theme = ofnode_find_subnode(dev_ofnode(dev), "theme");
39 	}
40 
41 	return 0;
42 }
43 
bootstd_clear_glob_(struct bootstd_priv * priv)44 static void bootstd_clear_glob_(struct bootstd_priv *priv)
45 {
46 	struct bootflow *bflow;
47 
48 	alist_for_each(bflow, &priv->bootflows)
49 		bootflow_remove(bflow);
50 	alist_empty(&priv->bootflows);
51 }
52 
bootstd_clear_glob(void)53 void bootstd_clear_glob(void)
54 {
55 	struct bootstd_priv *std;
56 
57 	if (bootstd_get_priv(&std))
58 		return;
59 
60 	bootstd_clear_glob_(std);
61 }
62 
bootstd_add_bootflow(struct bootflow * bflow)63 int bootstd_add_bootflow(struct bootflow *bflow)
64 {
65 	struct bootstd_priv *std;
66 	int ret;
67 
68 	ret = bootstd_get_priv(&std);
69 	if (ret)
70 		return ret;
71 
72 	ret = std->bootflows.count;
73 	bflow = alist_add(&std->bootflows, *bflow);
74 	if (!bflow)
75 		return log_msg_ret("bf2", -ENOMEM);
76 
77 	return ret;
78 }
79 
bootstd_clear_bootflows_for_bootdev(struct udevice * dev)80 int bootstd_clear_bootflows_for_bootdev(struct udevice *dev)
81 {
82 	struct bootstd_priv *std = bootstd_try_priv();
83 	struct bootflow *from, *to;
84 
85 	/* if bootstd does not exist we cannot have any bootflows */
86 	if (!std)
87 		return 0;
88 
89 	/* Drop any bootflows that mention this dev */
90 	alist_for_each_filter(from, to, &std->bootflows) {
91 		if (from->dev == dev)
92 			bootflow_remove(from);
93 		else
94 			*to++ = *from;
95 	}
96 	alist_update_end(&std->bootflows, to);
97 
98 	return 0;
99 }
100 
bootstd_remove(struct udevice * dev)101 static int bootstd_remove(struct udevice *dev)
102 {
103 	struct bootstd_priv *priv = dev_get_priv(dev);
104 
105 	free(priv->prefixes);
106 	free(priv->bootdev_order);
107 	bootstd_clear_glob_(priv);
108 
109 	return 0;
110 }
111 
bootstd_get_bootdev_order(struct udevice * dev,bool * okp)112 const char *const *const bootstd_get_bootdev_order(struct udevice *dev,
113 						   bool *okp)
114 {
115 	struct bootstd_priv *std = dev_get_priv(dev);
116 	const char *targets = env_get("boot_targets");
117 
118 	*okp = true;
119 	log_debug("- targets %s %p\n", targets, std->bootdev_order);
120 	if (targets && *targets) {
121 		str_free_list(std->env_order);
122 		std->env_order = str_to_list(targets);
123 		if (!std->env_order) {
124 			*okp = false;
125 			return NULL;
126 		}
127 		return std->env_order;
128 	}
129 
130 	return std->bootdev_order;
131 }
132 
bootstd_get_prefixes(struct udevice * dev)133 const char *const *const bootstd_get_prefixes(struct udevice *dev)
134 {
135 	struct bootstd_priv *std = dev_get_priv(dev);
136 
137 	return std->prefixes ? std->prefixes : default_prefixes;
138 }
139 
bootstd_try_priv(void)140 struct bootstd_priv *bootstd_try_priv(void)
141 {
142 	struct udevice *dev;
143 
144 	dev = uclass_try_first_device(UCLASS_BOOTSTD);
145 	if (!dev || !device_active(dev))
146 		return NULL;
147 
148 	return dev_get_priv(dev);
149 }
150 
bootstd_get_priv(struct bootstd_priv ** stdp)151 int bootstd_get_priv(struct bootstd_priv **stdp)
152 {
153 	struct udevice *dev;
154 	int ret;
155 
156 	ret = uclass_first_device_err(UCLASS_BOOTSTD, &dev);
157 	if (ret)
158 		return ret;
159 	*stdp = dev_get_priv(dev);
160 
161 	return 0;
162 }
163 
bootstd_probe(struct udevice * dev)164 static int bootstd_probe(struct udevice *dev)
165 {
166 	struct bootstd_priv *std = dev_get_priv(dev);
167 
168 	alist_init_struct(&std->bootflows, struct bootflow);
169 
170 	return 0;
171 }
172 
173 /* For now, bind the bootmethod device if none are found in the devicetree */
dm_scan_other(bool pre_reloc_only)174 int dm_scan_other(bool pre_reloc_only)
175 {
176 	struct driver *drv = ll_entry_start(struct driver, driver);
177 	const int n_ents = ll_entry_count(struct driver, driver);
178 	struct udevice *dev, *bootstd;
179 	int i, ret;
180 
181 	/* These are not needed before relocation */
182 	if (!(gd->flags & GD_FLG_RELOC))
183 		return 0;
184 
185 	/* Create a bootstd device if needed */
186 	uclass_find_first_device(UCLASS_BOOTSTD, &bootstd);
187 	if (!bootstd) {
188 		ret = device_bind_driver(gd->dm_root, "bootstd_drv", "bootstd",
189 					 &bootstd);
190 		if (ret)
191 			return log_msg_ret("bootstd", ret);
192 	}
193 
194 	/* If there are no bootmeth devices, create them */
195 	uclass_find_first_device(UCLASS_BOOTMETH, &dev);
196 	if (dev)
197 		return 0;
198 
199 	for (i = 0; i < n_ents; i++, drv++) {
200 		if (drv->id == UCLASS_BOOTMETH) {
201 			const char *name = drv->name;
202 
203 			if (!strncmp("bootmeth_", name, 9))
204 				name += 9;
205 			ret = device_bind(bootstd, drv, name, 0, ofnode_null(),
206 					  &dev);
207 			if (ret)
208 				return log_msg_ret("meth", ret);
209 		}
210 	}
211 
212 	return 0;
213 }
214 
215 static const struct udevice_id bootstd_ids[] = {
216 	{ .compatible = "u-boot,boot-std" },
217 	{ }
218 };
219 
220 U_BOOT_DRIVER(bootstd_drv) = {
221 	.id		= UCLASS_BOOTSTD,
222 	.name		= "bootstd_drv",
223 	.of_to_plat	= bootstd_of_to_plat,
224 	.probe		= bootstd_probe,
225 	.remove		= bootstd_remove,
226 	.of_match	= bootstd_ids,
227 	.priv_auto	= sizeof(struct bootstd_priv),
228 };
229 
230 UCLASS_DRIVER(bootstd) = {
231 	.id		= UCLASS_BOOTSTD,
232 	.name		= "bootstd",
233 #if CONFIG_IS_ENABLED(OF_REAL)
234 	.post_bind	= dm_scan_fdt_dev,
235 #endif
236 };
237