xref: /kvm-unit-tests/lib/devicetree.c (revision 184074189f14c36cb4bccbe67b30178480a4f21a)
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 
12 const void *dt_fdt(void)
13 {
14 	return fdt;
15 }
16 
17 bool dt_available(void)
18 {
19 	return fdt_check_header(fdt) == 0;
20 }
21 
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;
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 	*nr_address_cells = 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 	*nr_size_cells = fdt32_to_cpu(*nr_cells);
41 
42 	return 0;
43 }
44 
45 void dt_reg_init(struct dt_reg *reg, u32 nr_address_cells, u32 nr_size_cells)
46 {
47 	memset(reg, 0, sizeof(struct dt_reg));
48 	reg->nr_address_cells = nr_address_cells;
49 	reg->nr_size_cells = nr_size_cells;
50 }
51 
52 int dt_get_reg(int fdtnode, int regidx, struct dt_reg *reg)
53 {
54 	const struct fdt_property *prop;
55 	u32 *cells, i;
56 	unsigned nr_tuple_cells;
57 	int len;
58 
59 	prop = fdt_get_property(fdt, fdtnode, "reg", &len);
60 	if (prop == NULL)
61 		return len;
62 
63 	cells = (u32 *)prop->data;
64 	nr_tuple_cells = reg->nr_address_cells + reg->nr_size_cells;
65 	regidx *= nr_tuple_cells;
66 
67 	if (regidx + nr_tuple_cells > len/sizeof(u32))
68 		return -FDT_ERR_NOTFOUND;
69 
70 	for (i = 0; i < reg->nr_address_cells; ++i)
71 		reg->address_cells[i] = fdt32_to_cpu(cells[regidx + i]);
72 
73 	regidx += reg->nr_address_cells;
74 	for (i = 0; i < reg->nr_size_cells; ++i)
75 		reg->size_cells[i] = fdt32_to_cpu(cells[regidx + i]);
76 
77 	return 0;
78 }
79 
80 int dt_pbus_translate_node(int fdtnode, int regidx,
81 			   struct dt_pbus_reg *pbus_reg)
82 {
83 	struct dt_reg raw_reg;
84 	u32 nac, nsc;
85 	int parent, ret;
86 
87 	parent = fdt_parent_offset(fdt, fdtnode);
88 	if (parent < 0)
89 		return parent;
90 
91 	ret = dt_get_nr_cells(parent, &nac, &nsc);
92 	if (ret != 0)
93 		return ret;
94 
95 	dt_reg_init(&raw_reg, nac, nsc);
96 
97 	ret = dt_get_reg(fdtnode, regidx, &raw_reg);
98 	if (ret < 0)
99 		return ret;
100 
101 	pbus_reg->addr = dt_pbus_read_cells(raw_reg.nr_address_cells,
102 					    raw_reg.address_cells);
103 	pbus_reg->size = dt_pbus_read_cells(raw_reg.nr_size_cells,
104 					    raw_reg.size_cells);
105 
106 	return 0;
107 }
108 
109 int dt_pbus_translate(const struct dt_device *dev, int regidx,
110 		      void *reg)
111 {
112 	return dt_pbus_translate_node(dev->fdtnode, regidx, reg);
113 }
114 
115 int dt_bus_match_any(const struct dt_device *dev __unused, int fdtnode)
116 {
117 	/* matches any device with a valid node */
118 	return fdtnode < 0 ? fdtnode : 1;
119 }
120 
121 static const struct dt_bus dt_default_bus = {
122 	.match = dt_bus_match_any,
123 	.translate = dt_pbus_translate,
124 };
125 
126 void dt_bus_init_defaults(struct dt_bus *bus)
127 {
128 	memcpy(bus, &dt_default_bus, sizeof(struct dt_bus));
129 }
130 
131 void dt_device_init(struct dt_device *dev, const struct dt_bus *bus,
132 		    void *info)
133 {
134 	memset(dev, 0, sizeof(struct dt_device));
135 	dev->bus = bus;
136 	dev->info = info;
137 }
138 
139 int dt_device_find_compatible(const struct dt_device *dev,
140 			      const char *compatible)
141 {
142 	int node, ret;
143 
144 	node = fdt_node_offset_by_compatible(fdt, -1, compatible);
145 	while (node >= 0) {
146 		ret = dev->bus->match(dev, node);
147 		if (ret < 0)
148 			return ret;
149 		else if (ret)
150 			break;
151 		node = fdt_node_offset_by_compatible(fdt, node, compatible);
152 	}
153 	return node;
154 }
155 
156 int dt_pbus_get_base_compatible(const char *compatible,
157 				struct dt_pbus_reg *base)
158 {
159 	struct dt_device dev;
160 	int node;
161 
162 	dt_device_init(&dev, &dt_default_bus, NULL);
163 
164 	node = dt_device_find_compatible(&dev, compatible);
165 	if (node < 0)
166 		return node;
167 
168 	dt_device_bind_node(&dev, node);
169 
170 	return dt_pbus_get_base(&dev, base);
171 }
172 
173 int dt_get_memory_params(struct dt_pbus_reg *regs, int nr_regs)
174 {
175 	const char *pn = "device_type", *pv = "memory";
176 	int node, ret, reg_idx, pl = strlen(pv) + 1, nr = 0;
177 	struct dt_pbus_reg reg;
178 
179 	node = fdt_node_offset_by_prop_value(fdt, -1, pn, pv, pl);
180 
181 	while (node >= 0) {
182 
183 		reg_idx = 0;
184 
185 		while (nr < nr_regs) {
186 			ret = dt_pbus_translate_node(node, reg_idx, &reg);
187 			if (ret == -FDT_ERR_NOTFOUND)
188 				break;
189 			if (ret < 0)
190 				return ret;
191 			regs[nr].addr = reg.addr;
192 			regs[nr].size = reg.size;
193 			++nr, ++reg_idx;
194 		}
195 
196 		node = fdt_node_offset_by_prop_value(fdt, node, pn, pv, pl);
197 	}
198 
199 	return node != -FDT_ERR_NOTFOUND ? node : nr;
200 }
201 
202 int dt_for_each_cpu_node(void (*func)(int fdtnode, u32 regval, void *info),
203 			 void *info)
204 {
205 	const struct fdt_property *prop;
206 	int cpus, cpu, ret, len;
207 	struct dt_reg raw_reg;
208 	u32 nac, nsc;
209 
210 	cpus = fdt_path_offset(fdt, "/cpus");
211 	if (cpus < 0)
212 		return cpus;
213 
214 	ret = dt_get_nr_cells(cpus, &nac, &nsc);
215 	if (ret < 0)
216 		return ret;
217 
218 	dt_reg_init(&raw_reg, nac, nsc);
219 
220 	dt_for_each_subnode(cpus, cpu) {
221 
222 		prop = fdt_get_property(fdt, cpu, "device_type", &len);
223 		if (prop == NULL)
224 			return len;
225 
226 		if (len != 4 || strcmp((char *)prop->data, "cpu"))
227 			continue;
228 
229 		ret = dt_get_reg(cpu, 0, &raw_reg);
230 		if (ret < 0)
231 			return ret;
232 
233 		func(cpu, raw_reg.address_cells[0], info);
234 	}
235 
236 	return 0;
237 }
238 
239 int dt_get_bootargs(const char **bootargs)
240 {
241 	const struct fdt_property *prop;
242 	int node, len;
243 
244 	*bootargs = NULL;
245 
246 	node = fdt_path_offset(fdt, "/chosen");
247 	if (node < 0)
248 		return node;
249 
250 	prop = fdt_get_property(fdt, node, "bootargs", &len);
251 	if (prop)
252 		*bootargs = prop->data;
253 	else if (len < 0 && len != -FDT_ERR_NOTFOUND)
254 		return len;
255 
256 	return 0;
257 }
258 
259 int dt_get_default_console_node(void)
260 {
261 	const struct fdt_property *prop;
262 	int node, len;
263 
264 	node = fdt_path_offset(fdt, "/chosen");
265 	if (node < 0)
266 		return node;
267 
268 	prop = fdt_get_property(fdt, node, "stdout-path", &len);
269 	if (!prop) {
270 		prop = fdt_get_property(fdt, node, "linux,stdout-path", &len);
271 		if (!prop)
272 			return len;
273 	}
274 
275 	return fdt_path_offset(fdt, prop->data);
276 }
277 
278 int dt_init(const void *fdt_ptr)
279 {
280 	int ret;
281 
282 	ret = fdt_check_header(fdt_ptr);
283 	if (ret < 0)
284 		return ret;
285 
286 	/* Sanity check the path.  */
287 	ret = fdt_path_offset(fdt_ptr, "/");
288 	if (ret < 0)
289 		return ret;
290 
291 	fdt = fdt_ptr;
292 	return 0;
293 }
294