fdtbus.c revision 1.25 1 /* $NetBSD: fdtbus.c,v 1.25 2019/01/02 14:54:54 jmcneill Exp $ */
2
3 /*-
4 * Copyright (c) 2015 Jared D. McNeill <jmcneill (at) 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.25 2019/01/02 14:54:54 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_rescan(device_t, const char *, const int *);
74 static void fdt_childdet(device_t, device_t);
75
76 static int fdt_scan_submatch(device_t, cfdata_t, const int *, void *);
77 static void fdt_scan(struct fdt_softc *, int);
78 static void fdt_add_node(struct fdt_node *);
79 static u_int fdt_get_order(int);
80
81 static const char * const fdtbus_compatible[] =
82 { "simple-bus", "simple-mfd", NULL };
83
84 CFATTACH_DECL2_NEW(simplebus, sizeof(struct fdt_softc),
85 fdt_match, fdt_attach, NULL, NULL, fdt_rescan, fdt_childdet);
86
87 static int
88 fdt_match(device_t parent, cfdata_t cf, void *aux)
89 {
90 const struct fdt_attach_args *faa = aux;
91 const int phandle = faa->faa_phandle;
92 int match;
93
94 /* Check compatible string */
95 match = of_match_compatible(phandle, fdtbus_compatible);
96 if (match)
97 return match;
98
99 /* Some nodes have no compatible string */
100 if (!of_hasprop(phandle, "compatible")) {
101 if (OF_finddevice("/clocks") == phandle)
102 return 1;
103 if (OF_finddevice("/chosen") == phandle)
104 return 1;
105 }
106
107 /* Always match the root node */
108 return OF_finddevice("/") == phandle;
109 }
110
111 static void
112 fdt_attach(device_t parent, device_t self, void *aux)
113 {
114 struct fdt_softc *sc = device_private(self);
115 const struct fdt_attach_args *faa = aux;
116 const int phandle = faa->faa_phandle;
117 const char *descr;
118
119 sc->sc_dev = self;
120 sc->sc_phandle = phandle;
121 sc->sc_faa = *faa;
122
123 aprint_naive("\n");
124
125 descr = fdtbus_get_string(phandle, "model");
126 if (descr)
127 aprint_normal(": %s\n", descr);
128 else
129 aprint_normal("\n");
130
131 /* Find all child nodes */
132 fdt_add_bus(self, phandle, &sc->sc_faa);
133
134 /* Only the root bus should scan for devices */
135 if (OF_finddevice("/") != faa->faa_phandle)
136 return;
137
138 /* Scan devices */
139 fdt_rescan(self, NULL, NULL);
140 }
141
142 static int
143 fdt_rescan(device_t self, const char *ifattr, const int *locs)
144 {
145 struct fdt_softc *sc = device_private(self);
146 int pass;
147
148 pass = 0;
149 fdt_need_rescan = false;
150 do {
151 fdt_scan(sc, pass);
152 if (fdt_need_rescan == true) {
153 pass = 0;
154 fdt_need_rescan = false;
155 } else {
156 pass++;
157 }
158 } while (pass <= FDTCF_PASS_DEFAULT);
159
160 return 0;
161 }
162
163 static void
164 fdt_childdet(device_t parent, device_t child)
165 {
166 struct fdt_node *node;
167
168 TAILQ_FOREACH(node, &fdt_nodes, n_nodes)
169 if (node->n_dev == child) {
170 node->n_dev = NULL;
171 break;
172 }
173 }
174
175 static void
176 fdt_init_attach_args(const struct fdt_attach_args *faa_tmpl, struct fdt_node *node,
177 bool quiet, struct fdt_attach_args *faa)
178 {
179 *faa = *faa_tmpl;
180 faa->faa_phandle = node->n_phandle;
181 faa->faa_name = node->n_name;
182 faa->faa_quiet = quiet;
183 }
184
185 static bool
186 fdt_add_bus_stdmatch(void *arg, int child)
187 {
188 return fdtbus_status_okay(child);
189 }
190
191 void
192 fdt_add_bus(device_t bus, const int phandle, struct fdt_attach_args *faa)
193 {
194 fdt_add_bus_match(bus, phandle, faa, fdt_add_bus_stdmatch, NULL);
195 }
196
197 void
198 fdt_add_bus_match(device_t bus, const int phandle, struct fdt_attach_args *faa,
199 bool (*fn)(void *, int), void *fnarg)
200 {
201 int child;
202
203 for (child = OF_child(phandle); child; child = OF_peer(child)) {
204 if (fn && !fn(fnarg, child))
205 continue;
206
207 fdt_add_child(bus, child, faa, fdt_get_order(child));
208 }
209 }
210
211 void
212 fdt_add_child(device_t bus, const int child, struct fdt_attach_args *faa,
213 u_int order)
214 {
215 struct fdt_node *node;
216
217 /* Add the node to our device list */
218 node = kmem_alloc(sizeof(*node), KM_SLEEP);
219 node->n_bus = bus;
220 node->n_dev = NULL;
221 node->n_phandle = child;
222 node->n_name = fdtbus_get_string(child, "name");
223 node->n_order = order;
224 node->n_faa = *faa;
225 node->n_faa.faa_phandle = child;
226 node->n_faa.faa_name = node->n_name;
227
228 fdt_add_node(node);
229 fdt_need_rescan = true;
230 }
231
232 static int
233 fdt_scan_submatch(device_t parent, cfdata_t cf, const int *locs, void *aux)
234 {
235 if (locs[FDTCF_PASS] != FDTCF_PASS_DEFAULT &&
236 locs[FDTCF_PASS] != cf->cf_loc[FDTCF_PASS])
237 return 0;
238
239 return config_stdsubmatch(parent, cf, locs, aux);
240 }
241
242 static cfdata_t
243 fdt_scan_best(struct fdt_softc *sc, struct fdt_node *node)
244 {
245 struct fdt_attach_args faa;
246 cfdata_t cf, best_cf;
247 int match, best_match;
248
249 best_cf = NULL;
250 best_match = 0;
251
252 for (int pass = 0; pass <= FDTCF_PASS_DEFAULT; pass++) {
253 const int locs[FDTCF_NLOCS] = {
254 [FDTCF_PASS] = pass
255 };
256 fdt_init_attach_args(&sc->sc_faa, node, true, &faa);
257 cf = config_search_loc(fdt_scan_submatch, node->n_bus, "fdt", locs, &faa);
258 if (cf == NULL)
259 continue;
260 match = config_match(node->n_bus, cf, &faa);
261 if (match > best_match) {
262 best_match = match;
263 best_cf = cf;
264 }
265 }
266
267 return best_cf;
268 }
269
270 static void
271 fdt_scan(struct fdt_softc *sc, int pass)
272 {
273 struct fdt_node *node;
274 struct fdt_attach_args faa;
275 const int locs[FDTCF_NLOCS] = {
276 [FDTCF_PASS] = pass
277 };
278 bool quiet = pass != FDTCF_PASS_DEFAULT;
279 prop_dictionary_t dict;
280 char buf[FDT_MAX_PATH];
281
282 TAILQ_FOREACH(node, &fdt_nodes, n_nodes) {
283 if (node->n_dev != NULL)
284 continue;
285
286 fdt_init_attach_args(&sc->sc_faa, node, quiet, &faa);
287
288 /*
289 * Make sure we don't attach before a better match in a later pass.
290 */
291 cfdata_t cf_best = fdt_scan_best(sc, node);
292 cfdata_t cf_pass =
293 config_search_loc(fdt_scan_submatch, node->n_bus, "fdt", locs, &faa);
294 if (cf_best != cf_pass)
295 continue;
296
297 /*
298 * Attach the device.
299 */
300 node->n_dev = config_found_sm_loc(node->n_bus, "fdt", locs,
301 &faa, fdtbus_print, fdt_scan_submatch);
302 if (node->n_dev) {
303 dict = device_properties(node->n_dev);
304 if (fdtbus_get_path(node->n_phandle, buf, sizeof(buf)))
305 prop_dictionary_set_cstring(dict, "fdt-path", buf);
306 }
307 }
308 }
309
310 static void
311 fdt_add_node(struct fdt_node *new_node)
312 {
313 struct fdt_node *node;
314
315 TAILQ_FOREACH(node, &fdt_nodes, n_nodes)
316 if (node->n_order > new_node->n_order) {
317 TAILQ_INSERT_BEFORE(node, new_node, n_nodes);
318 return;
319 }
320 TAILQ_INSERT_TAIL(&fdt_nodes, new_node, n_nodes);
321 }
322
323 void
324 fdt_remove_byhandle(int phandle)
325 {
326 struct fdt_node *node;
327
328 TAILQ_FOREACH(node, &fdt_nodes, n_nodes) {
329 if (node->n_phandle == phandle) {
330 TAILQ_REMOVE(&fdt_nodes, node, n_nodes);
331 return;
332 }
333 }
334 }
335
336 void
337 fdt_remove_bycompat(const char *compatible[])
338 {
339 struct fdt_node *node, *next;
340
341 TAILQ_FOREACH_SAFE(node, &fdt_nodes, n_nodes, next) {
342 if (of_match_compatible(node->n_phandle, compatible)) {
343 TAILQ_REMOVE(&fdt_nodes, node, n_nodes);
344 }
345 }
346 }
347
348 static u_int
349 fdt_get_order(int phandle)
350 {
351 u_int val = UINT_MAX;
352 int child;
353
354 of_getprop_uint32(phandle, "phandle", &val);
355
356 for (child = OF_child(phandle); child; child = OF_peer(child)) {
357 u_int child_val = fdt_get_order(child);
358 if (child_val < val)
359 val = child_val;
360 }
361
362 return val;
363 }
364
365 int
366 fdtbus_print(void *aux, const char *pnp)
367 {
368 const struct fdt_attach_args * const faa = aux;
369 char buf[FDT_MAX_PATH];
370 const char *name = buf;
371 int len;
372
373 if (pnp && faa->faa_quiet)
374 return QUIET;
375
376 /* Skip "not configured" for nodes w/o compatible property */
377 if (pnp && OF_getproplen(faa->faa_phandle, "compatible") <= 0)
378 return QUIET;
379
380 if (!fdtbus_get_path(faa->faa_phandle, buf, sizeof(buf)))
381 name = faa->faa_name;
382
383 if (pnp) {
384 aprint_normal("%s at %s", name, pnp);
385 const char *compat = fdt_getprop(fdtbus_get_data(),
386 fdtbus_phandle2offset(faa->faa_phandle), "compatible",
387 &len);
388 while (len > 0) {
389 aprint_debug(" <%s>", compat);
390 len -= (strlen(compat) + 1);
391 compat += (strlen(compat) + 1);
392 }
393 } else
394 aprint_debug(" (%s)", name);
395
396 return UNCONF;
397 }
398