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