xref: /kvm-unit-tests/lib/devicetree.c (revision e526bc786e9878c3880ae4b09b01a4572756e492)
1 /*
2  * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com>
3  *
4  * This work is licensed under the terms of the GNU LGPL, version 2.
5  */
6 #include "libcflat.h"
7 #include "libfdt/libfdt.h"
8 #include "devicetree.h"
9 
10 static const void *fdt;
11 
dt_fdt(void)12 const void *dt_fdt(void)
13 {
14 	return fdt;
15 }
16 
dt_available(void)17 bool dt_available(void)
18 {
19 	return fdt && fdt_check_header(fdt) == 0;
20 }
21 
dt_get_nr_cells(int fdtnode,u32 * nr_address_cells,u32 * nr_size_cells)22 int dt_get_nr_cells(int fdtnode, u32 *nr_address_cells, u32 *nr_size_cells)
23 {
24 	const struct fdt_property *prop;
25 	u32 *nr_cells;
26 	int len, nac, nsc;
27 
28 	prop = fdt_get_property(fdt, fdtnode, "#address-cells", &len);
29 	if (prop == NULL)
30 		return len;
31 
32 	nr_cells = (u32 *)prop->data;
33 	nac = fdt32_to_cpu(*nr_cells);
34 
35 	prop = fdt_get_property(fdt, fdtnode, "#size-cells", &len);
36 	if (prop == NULL)
37 		return len;
38 
39 	nr_cells = (u32 *)prop->data;
40 	nsc = fdt32_to_cpu(*nr_cells);
41 
42 	*nr_address_cells = nac;
43 	*nr_size_cells = nsc;
44 
45 	return 0;
46 }
47 
dt_reg_init(struct dt_reg * reg,u32 nr_address_cells,u32 nr_size_cells)48 void dt_reg_init(struct dt_reg *reg, u32 nr_address_cells, u32 nr_size_cells)
49 {
50 	memset(reg, 0, sizeof(struct dt_reg));
51 	reg->nr_address_cells = nr_address_cells;
52 	reg->nr_size_cells = nr_size_cells;
53 }
54 
dt_get_reg(int fdtnode,int regidx,struct dt_reg * reg)55 int dt_get_reg(int fdtnode, int regidx, struct dt_reg *reg)
56 {
57 	const struct fdt_property *prop;
58 	u32 *cells, i;
59 	unsigned nr_tuple_cells;
60 	int len;
61 
62 	prop = fdt_get_property(fdt, fdtnode, "reg", &len);
63 	if (prop == NULL)
64 		return len;
65 
66 	cells = (u32 *)prop->data;
67 	nr_tuple_cells = reg->nr_address_cells + reg->nr_size_cells;
68 	regidx *= nr_tuple_cells;
69 
70 	if (regidx + nr_tuple_cells > len/sizeof(u32))
71 		return -FDT_ERR_NOTFOUND;
72 
73 	for (i = 0; i < reg->nr_address_cells; ++i)
74 		reg->address_cells[i] = fdt32_to_cpu(cells[regidx + i]);
75 
76 	regidx += reg->nr_address_cells;
77 	for (i = 0; i < reg->nr_size_cells; ++i)
78 		reg->size_cells[i] = fdt32_to_cpu(cells[regidx + i]);
79 
80 	return 0;
81 }
82 
dt_pbus_translate_node(int fdtnode,int regidx,struct dt_pbus_reg * pbus_reg)83 int dt_pbus_translate_node(int fdtnode, int regidx,
84 			   struct dt_pbus_reg *pbus_reg)
85 {
86 	struct dt_reg raw_reg;
87 	u32 nac, nsc;
88 	int parent, ret;
89 
90 	parent = fdt_parent_offset(fdt, fdtnode);
91 	if (parent < 0)
92 		return parent;
93 
94 	ret = dt_get_nr_cells(parent, &nac, &nsc);
95 	if (ret != 0)
96 		return ret;
97 
98 	dt_reg_init(&raw_reg, nac, nsc);
99 
100 	ret = dt_get_reg(fdtnode, regidx, &raw_reg);
101 	if (ret < 0)
102 		return ret;
103 
104 	pbus_reg->addr = dt_pbus_read_cells(raw_reg.nr_address_cells,
105 					    raw_reg.address_cells);
106 	pbus_reg->size = dt_pbus_read_cells(raw_reg.nr_size_cells,
107 					    raw_reg.size_cells);
108 
109 	return 0;
110 }
111 
dt_pbus_translate(const struct dt_device * dev,int regidx,void * reg)112 int dt_pbus_translate(const struct dt_device *dev, int regidx,
113 		      void *reg)
114 {
115 	return dt_pbus_translate_node(dev->fdtnode, regidx, reg);
116 }
117 
dt_bus_match_any(const struct dt_device * dev __unused,int fdtnode)118 int dt_bus_match_any(const struct dt_device *dev __unused, int fdtnode)
119 {
120 	/* matches any device with a valid node */
121 	return fdtnode < 0 ? fdtnode : 1;
122 }
123 
124 static const struct dt_bus dt_default_bus = {
125 	.match = dt_bus_match_any,
126 	.translate = dt_pbus_translate,
127 };
128 
dt_bus_init_defaults(struct dt_bus * bus)129 void dt_bus_init_defaults(struct dt_bus *bus)
130 {
131 	memcpy(bus, &dt_default_bus, sizeof(struct dt_bus));
132 }
133 
dt_device_init(struct dt_device * dev,const struct dt_bus * bus,void * info)134 void dt_device_init(struct dt_device *dev, const struct dt_bus *bus,
135 		    void *info)
136 {
137 	memset(dev, 0, sizeof(struct dt_device));
138 	dev->bus = bus;
139 	dev->info = info;
140 }
141 
dt_device_find_compatible(const struct dt_device * dev,const char * compatible)142 int dt_device_find_compatible(const struct dt_device *dev,
143 			      const char *compatible)
144 {
145 	int node, ret;
146 
147 	node = fdt_node_offset_by_compatible(fdt, -1, compatible);
148 	while (node >= 0) {
149 		ret = dev->bus->match(dev, node);
150 		if (ret < 0)
151 			return ret;
152 		else if (ret)
153 			break;
154 		node = fdt_node_offset_by_compatible(fdt, node, compatible);
155 	}
156 	return node;
157 }
158 
dt_pbus_get_base_compatible(const char * compatible,struct dt_pbus_reg * base)159 int dt_pbus_get_base_compatible(const char *compatible,
160 				struct dt_pbus_reg *base)
161 {
162 	struct dt_device dev;
163 	int node;
164 
165 	dt_device_init(&dev, &dt_default_bus, NULL);
166 
167 	node = dt_device_find_compatible(&dev, compatible);
168 	if (node < 0)
169 		return node;
170 
171 	dt_device_bind_node(&dev, node);
172 
173 	return dt_pbus_get_base(&dev, base);
174 }
175 
dt_get_memory_params(struct dt_pbus_reg * regs,int nr_regs)176 int dt_get_memory_params(struct dt_pbus_reg *regs, int nr_regs)
177 {
178 	const char *pn = "device_type", *pv = "memory";
179 	int node, ret, reg_idx, pl = strlen(pv) + 1, nr = 0;
180 	struct dt_pbus_reg reg;
181 
182 	node = fdt_node_offset_by_prop_value(fdt, -1, pn, pv, pl);
183 
184 	while (node >= 0) {
185 
186 		reg_idx = 0;
187 
188 		while (nr < nr_regs) {
189 			ret = dt_pbus_translate_node(node, reg_idx, &reg);
190 			if (ret == -FDT_ERR_NOTFOUND)
191 				break;
192 			if (ret < 0)
193 				return ret;
194 			regs[nr].addr = reg.addr;
195 			regs[nr].size = reg.size;
196 			++nr, ++reg_idx;
197 		}
198 
199 		node = fdt_node_offset_by_prop_value(fdt, node, pn, pv, pl);
200 	}
201 
202 	return node != -FDT_ERR_NOTFOUND ? node : nr;
203 }
204 
dt_for_each_cpu_node(void (* func)(int fdtnode,u64 regval,void * info),void * info)205 int dt_for_each_cpu_node(void (*func)(int fdtnode, u64 regval, void *info),
206 			 void *info)
207 {
208 	const struct fdt_property *prop;
209 	int cpus, cpu, ret, len;
210 	struct dt_reg raw_reg;
211 	u32 nac, nsc;
212 	u64 regval;
213 
214 	cpus = fdt_path_offset(fdt, "/cpus");
215 	if (cpus < 0)
216 		return cpus;
217 
218 	ret = dt_get_nr_cells(cpus, &nac, &nsc);
219 	if (ret < 0)
220 		return ret;
221 
222 	dt_reg_init(&raw_reg, nac, nsc);
223 
224 	dt_for_each_subnode(cpus, cpu) {
225 
226 		prop = fdt_get_property(fdt, cpu, "device_type", &len);
227 		if (prop == NULL)
228 			continue;
229 
230 		if (len != 4 || strcmp((char *)prop->data, "cpu"))
231 			continue;
232 
233 		ret = dt_get_reg(cpu, 0, &raw_reg);
234 		if (ret < 0)
235 			return ret;
236 
237 		regval = raw_reg.address_cells[0];
238 		if (nac == 2)
239 			regval = (regval << 32) | raw_reg.address_cells[1];
240 
241 		func(cpu, regval, info);
242 	}
243 
244 	return 0;
245 }
246 
dt_get_bootargs(const char ** bootargs)247 int dt_get_bootargs(const char **bootargs)
248 {
249 	const struct fdt_property *prop;
250 	int node, len;
251 
252 	*bootargs = NULL;
253 
254 	node = fdt_path_offset(fdt, "/chosen");
255 	if (node < 0)
256 		return node;
257 
258 	prop = fdt_get_property(fdt, node, "bootargs", &len);
259 	if (!prop)
260 		return len;
261 
262 	*bootargs = prop->data;
263 	return 0;
264 }
265 
dt_get_default_console_node(void)266 int dt_get_default_console_node(void)
267 {
268 	const char *p, *q;
269 	int node, len;
270 
271 	node = fdt_path_offset(fdt, "/chosen");
272 	if (node < 0)
273 		return node;
274 
275 	p = fdt_getprop(fdt, node, "stdout-path", &len);
276 	if (!p) {
277 		p = fdt_getprop(fdt, node, "linux,stdout-path", &len);
278 		if (!p)
279 			return len;
280 	}
281 
282 	q = strchrnul(p, ':');
283 	len = q - p;
284 
285 	return fdt_path_offset_namelen(fdt, p, len);
286 }
287 
dt_get_initrd(const char ** initrd,u32 * size)288 int dt_get_initrd(const char **initrd, u32 *size)
289 {
290 	const struct fdt_property *prop;
291 	u64 start, end;
292 	int node, len;
293 	u32 *data;
294 
295 	*initrd = NULL;
296 	*size = 0;
297 
298 	node = fdt_path_offset(fdt, "/chosen");
299 	if (node < 0)
300 		return node;
301 
302 	prop = fdt_get_property(fdt, node, "linux,initrd-start", &len);
303 	if (!prop)
304 		return len;
305 	data = (u32 *)prop->data;
306 	start = fdt32_to_cpu(*data);
307 	if (len == 8) {
308 		data++;
309 		start = (start << 32) | fdt32_to_cpu(*data);
310 	}
311 
312 	prop = fdt_get_property(fdt, node, "linux,initrd-end", &len);
313 	if (!prop) {
314 		assert(len != -FDT_ERR_NOTFOUND);
315 		return len;
316 	}
317 	data = (u32 *)prop->data;
318 	end = fdt32_to_cpu(*data);
319 	if (len == 8) {
320 		data++;
321 		end = (end << 32) | fdt32_to_cpu(*data);
322 	}
323 
324 	assert(start < end);
325 	assert(sizeof(long) == 8 || !(end >> 32));
326 
327 	*initrd = (char *)(unsigned long)start;
328 	*size = end - start;
329 
330 	return 0;
331 }
332 
dt_init(const void * fdt_ptr)333 int dt_init(const void *fdt_ptr)
334 {
335 	int ret;
336 
337 	ret = fdt_check_header(fdt_ptr);
338 	if (ret < 0)
339 		return ret;
340 
341 	/* Sanity check the path.  */
342 	ret = fdt_path_offset(fdt_ptr, "/");
343 	if (ret < 0)
344 		return ret;
345 
346 	fdt = fdt_ptr;
347 	return 0;
348 }
349