xref: /netbsd-src/sys/dev/fdt/fdtbus.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /* $NetBSD: fdtbus.c,v 1.22 2018/06/30 17:28:09 jmcneill Exp $ */
2 
3 /*-
4  * Copyright (c) 2015 Jared D. McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: fdtbus.c,v 1.22 2018/06/30 17:28:09 jmcneill Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/device.h>
35 #include <sys/kmem.h>
36 
37 #include <sys/bus.h>
38 
39 #include <dev/ofw/openfirm.h>
40 
41 #include <dev/fdt/fdtvar.h>
42 
43 #include <libfdt.h>
44 
45 #include "locators.h"
46 
47 #define	FDT_MAX_PATH	256
48 
49 struct fdt_node {
50 	device_t	n_bus;
51 	device_t	n_dev;
52 	int		n_phandle;
53 	const char	*n_name;
54 	struct fdt_attach_args n_faa;
55 
56 	u_int		n_order;
57 
58 	TAILQ_ENTRY(fdt_node) n_nodes;
59 };
60 
61 static TAILQ_HEAD(, fdt_node) fdt_nodes =
62     TAILQ_HEAD_INITIALIZER(fdt_nodes);
63 static bool fdt_need_rescan = false;
64 
65 struct fdt_softc {
66 	device_t	sc_dev;
67 	int		sc_phandle;
68 	struct fdt_attach_args sc_faa;
69 };
70 
71 static int	fdt_match(device_t, cfdata_t, void *);
72 static void	fdt_attach(device_t, device_t, void *);
73 static int	fdt_scan_submatch(device_t, cfdata_t, const int *, void *);
74 static void	fdt_scan(struct fdt_softc *, int);
75 static void	fdt_add_node(struct fdt_node *);
76 static u_int	fdt_get_order(int);
77 
78 static const char * const fdtbus_compatible[] =
79     { "simple-bus", "simple-mfd", NULL };
80 
81 CFATTACH_DECL_NEW(simplebus, sizeof(struct fdt_softc),
82     fdt_match, fdt_attach, NULL, NULL);
83 
84 static int
85 fdt_match(device_t parent, cfdata_t cf, void *aux)
86 {
87 	const struct fdt_attach_args *faa = aux;
88 	const int phandle = faa->faa_phandle;
89 	int match;
90 
91 	/* Check compatible string */
92 	match = of_match_compatible(phandle, fdtbus_compatible);
93 	if (match)
94 		return match;
95 
96 	/* Some nodes have no compatible string */
97 	if (!of_hasprop(phandle, "compatible")) {
98 		if (OF_finddevice("/clocks") == phandle)
99 			return 1;
100 		if (OF_finddevice("/chosen") == phandle)
101 			return 1;
102 	}
103 
104 	/* Always match the root node */
105 	return OF_finddevice("/") == phandle;
106 }
107 
108 static void
109 fdt_attach(device_t parent, device_t self, void *aux)
110 {
111 	struct fdt_softc *sc = device_private(self);
112 	const struct fdt_attach_args *faa = aux;
113 	const int phandle = faa->faa_phandle;
114 	const char *descr;
115 	int pass;
116 
117 	sc->sc_dev = self;
118 	sc->sc_phandle = phandle;
119 	sc->sc_faa = *faa;
120 
121 	aprint_naive("\n");
122 
123 	descr = fdtbus_get_string(phandle, "model");
124 	if (descr)
125 		aprint_normal(": %s\n", descr);
126 	else
127 		aprint_normal("\n");
128 
129 	/* Find all child nodes */
130 	fdt_add_bus(self, phandle, &sc->sc_faa);
131 
132 	/* Only the root bus should scan for devices */
133 	if (OF_finddevice("/") != faa->faa_phandle)
134 		return;
135 
136 	/* Scan devices */
137 	pass = 0;
138 	fdt_need_rescan = false;
139 	do {
140 		fdt_scan(sc, pass);
141 		if (fdt_need_rescan == true) {
142 			pass = 0;
143 			fdt_need_rescan = false;
144 		} else {
145 			pass++;
146 		}
147 	} while (pass <= FDTCF_PASS_DEFAULT);
148 }
149 
150 static void
151 fdt_init_attach_args(const struct fdt_attach_args *faa_tmpl, struct fdt_node *node,
152     bool quiet, struct fdt_attach_args *faa)
153 {
154 	*faa = *faa_tmpl;
155 	faa->faa_phandle = node->n_phandle;
156 	faa->faa_name = node->n_name;
157 	faa->faa_quiet = quiet;
158 }
159 
160 void
161 fdt_add_bus(device_t bus, const int phandle, struct fdt_attach_args *faa)
162 {
163 	struct fdt_node *node;
164 	int child;
165 
166 	for (child = OF_child(phandle); child; child = OF_peer(child)) {
167 		if (!fdtbus_status_okay(child))
168 			continue;
169 
170 		/* Add the node to our device list */
171 		node = kmem_alloc(sizeof(*node), KM_SLEEP);
172 		node->n_bus = bus;
173 		node->n_dev = NULL;
174 		node->n_phandle = child;
175 		node->n_name = fdtbus_get_string(child, "name");
176 		node->n_order = fdt_get_order(child);
177 		node->n_faa = *faa;
178 		node->n_faa.faa_phandle = child;
179 		node->n_faa.faa_name = node->n_name;
180 
181 		fdt_add_node(node);
182 		fdt_need_rescan = true;
183 	}
184 }
185 
186 static int
187 fdt_scan_submatch(device_t parent, cfdata_t cf, const int *locs, void *aux)
188 {
189 	if (locs[FDTCF_PASS] != FDTCF_PASS_DEFAULT &&
190 	    locs[FDTCF_PASS] != cf->cf_loc[FDTCF_PASS])
191 		return 0;
192 
193 	return config_stdsubmatch(parent, cf, locs, aux);
194 }
195 
196 static cfdata_t
197 fdt_scan_best(struct fdt_softc *sc, struct fdt_node *node)
198 {
199 	struct fdt_attach_args faa;
200 	cfdata_t cf, best_cf;
201 	int match, best_match;
202 
203 	best_cf = NULL;
204 	best_match = 0;
205 
206 	for (int pass = 0; pass <= FDTCF_PASS_DEFAULT; pass++) {
207 		const int locs[FDTCF_NLOCS] = {
208 			[FDTCF_PASS] = pass
209 		};
210 		fdt_init_attach_args(&sc->sc_faa, node, true, &faa);
211 		cf = config_search_loc(fdt_scan_submatch, node->n_bus, "fdt", locs, &faa);
212 		if (cf == NULL)
213 			continue;
214 		match = config_match(node->n_bus, cf, &faa);
215 		if (match > best_match) {
216 			best_match = match;
217 			best_cf = cf;
218 		}
219 	}
220 
221 	return best_cf;
222 }
223 
224 static void
225 fdt_scan(struct fdt_softc *sc, int pass)
226 {
227 	struct fdt_node *node;
228 	struct fdt_attach_args faa;
229 	const int locs[FDTCF_NLOCS] = {
230 		[FDTCF_PASS] = pass
231 	};
232 	bool quiet = pass != FDTCF_PASS_DEFAULT;
233 	prop_dictionary_t dict;
234 	char buf[FDT_MAX_PATH];
235 
236 	TAILQ_FOREACH(node, &fdt_nodes, n_nodes) {
237 		if (node->n_dev != NULL)
238 			continue;
239 
240 		fdt_init_attach_args(&sc->sc_faa, node, quiet, &faa);
241 
242 		/*
243 		 * Make sure we don't attach before a better match in a later pass.
244 		 */
245 		cfdata_t cf_best = fdt_scan_best(sc, node);
246 		cfdata_t cf_pass =
247 		    config_search_loc(fdt_scan_submatch, node->n_bus, "fdt", locs, &faa);
248 		if (cf_best != cf_pass)
249 			continue;
250 
251 		/*
252 		 * Attach the device.
253 		 */
254 		node->n_dev = config_found_sm_loc(node->n_bus, "fdt", locs,
255 		    &faa, fdtbus_print, fdt_scan_submatch);
256 		if (node->n_dev) {
257 			dict = device_properties(node->n_dev);
258 			if (fdtbus_get_path(node->n_phandle, buf, sizeof(buf)))
259 				prop_dictionary_set_cstring(dict, "fdt-path", buf);
260 		}
261 	}
262 }
263 
264 static void
265 fdt_add_node(struct fdt_node *new_node)
266 {
267 	struct fdt_node *node;
268 
269 	TAILQ_FOREACH(node, &fdt_nodes, n_nodes)
270 		if (node->n_order > new_node->n_order) {
271 			TAILQ_INSERT_BEFORE(node, new_node, n_nodes);
272 			return;
273 		}
274 	TAILQ_INSERT_TAIL(&fdt_nodes, new_node, n_nodes);
275 }
276 
277 void
278 fdt_remove_byhandle(int phandle)
279 {
280 	struct fdt_node *node;
281 
282 	TAILQ_FOREACH(node, &fdt_nodes, n_nodes) {
283 		if (node->n_phandle == phandle) {
284 			TAILQ_REMOVE(&fdt_nodes, node, n_nodes);
285 			return;
286 		}
287 	}
288 }
289 
290 void
291 fdt_remove_bycompat(const char *compatible[])
292 {
293 	struct fdt_node *node, *next;
294 
295 	TAILQ_FOREACH_SAFE(node, &fdt_nodes, n_nodes, next) {
296 		if (of_match_compatible(node->n_phandle, compatible)) {
297 			TAILQ_REMOVE(&fdt_nodes, node, n_nodes);
298 		}
299 	}
300 }
301 
302 static u_int
303 fdt_get_order(int phandle)
304 {
305 	u_int val = UINT_MAX;
306 	int child;
307 
308 	of_getprop_uint32(phandle, "phandle", &val);
309 
310 	for (child = OF_child(phandle); child; child = OF_peer(child)) {
311 		u_int child_val = fdt_get_order(child);
312 		if (child_val < val)
313 			val = child_val;
314 	}
315 
316 	return val;
317 }
318 
319 int
320 fdtbus_print(void *aux, const char *pnp)
321 {
322 	const struct fdt_attach_args * const faa = aux;
323 	char buf[FDT_MAX_PATH];
324 	const char *name = buf;
325 	int len;
326 
327 	if (pnp && faa->faa_quiet)
328 		return QUIET;
329 
330 	/* Skip "not configured" for nodes w/o compatible property */
331 	if (pnp && OF_getproplen(faa->faa_phandle, "compatible") <= 0)
332 		return QUIET;
333 
334 	if (!fdtbus_get_path(faa->faa_phandle, buf, sizeof(buf)))
335 		name = faa->faa_name;
336 
337 	if (pnp) {
338 		aprint_normal("%s at %s", name, pnp);
339 		const char *compat = fdt_getprop(fdtbus_get_data(),
340 		    fdtbus_phandle2offset(faa->faa_phandle), "compatible",
341 		    &len);
342 		while (len > 0) {
343 			aprint_debug(" <%s>", compat);
344 			len -= (strlen(compat) + 1);
345 			compat += (strlen(compat) + 1);
346 		}
347 	} else
348 		aprint_debug(" (%s)", name);
349 
350 	return UNCONF;
351 }
352