1 /* $NetBSD: fdt_intr.c,v 1.30 2021/11/07 17:13:53 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2015-2018 Jared 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: fdt_intr.c,v 1.30 2021/11/07 17:13:53 jmcneill Exp $"); 31 32 #include <sys/param.h> 33 #include <sys/atomic.h> 34 #include <sys/bus.h> 35 #include <sys/kmem.h> 36 #include <sys/queue.h> 37 #include <sys/mutex.h> 38 #include <sys/condvar.h> 39 40 #include <libfdt.h> 41 #include <dev/fdt/fdtvar.h> 42 #include <dev/fdt/fdt_private.h> 43 44 struct fdtbus_interrupt_controller { 45 device_t ic_dev; 46 int ic_phandle; 47 const struct fdtbus_interrupt_controller_func *ic_funcs; 48 49 LIST_ENTRY(fdtbus_interrupt_controller) ic_next; 50 }; 51 52 static LIST_HEAD(, fdtbus_interrupt_controller) fdtbus_interrupt_controllers = 53 LIST_HEAD_INITIALIZER(fdtbus_interrupt_controllers); 54 55 struct fdtbus_interrupt_cookie { 56 struct fdtbus_interrupt_controller *c_ic; 57 void *c_ih; 58 59 LIST_ENTRY(fdtbus_interrupt_cookie) c_next; 60 uint32_t c_refcnt; 61 }; 62 63 static LIST_HEAD(, fdtbus_interrupt_cookie) fdtbus_interrupt_cookies = 64 LIST_HEAD_INITIALIZER(fdtbus_interrupt_cookies); 65 static kmutex_t fdtbus_interrupt_cookie_mutex; 66 static kcondvar_t fdtbus_interrupt_cookie_wait; 67 static bool fdtbus_interrupt_cookies_wanted; 68 69 static const u_int * get_specifier_by_index(int, int, int *); 70 static const u_int * get_specifier_from_map(int, const u_int *, int *); 71 72 void 73 fdtbus_intr_init(void) 74 { 75 76 mutex_init(&fdtbus_interrupt_cookie_mutex, MUTEX_DEFAULT, IPL_HIGH); 77 cv_init(&fdtbus_interrupt_cookie_wait, "fdtintr"); 78 } 79 80 /* 81 * Find the interrupt controller for a given node. This function will either 82 * return the phandle of the interrupt controller for this node, or the phandle 83 * of a node containing an interrupt-map table that can be used to find the 84 * real interrupt controller. 85 */ 86 static int 87 fdtbus_get_interrupt_parent(int phandle) 88 { 89 int iparent = phandle; 90 91 do { 92 /* 93 * If the node is an interrupt-controller, we are done. Note that 94 * a node cannot be an interrupt-controller for itself, so we skip 95 * the leaf node here. 96 */ 97 if (phandle != iparent && of_hasprop(iparent, "interrupt-controller")) 98 return iparent; 99 100 /* 101 * If the node has an explicit interrupt-parent, follow the reference. 102 */ 103 if (of_hasprop(iparent, "interrupt-parent")) 104 return fdtbus_get_phandle(iparent, "interrupt-parent"); 105 106 /* 107 * If the node has an interrupt-map, use it. The caller is responsible 108 * for parsing the interrupt-map and finding the real interrupt parent. 109 */ 110 if (phandle != iparent && of_hasprop(iparent, "interrupt-map")) 111 return iparent; 112 113 /* 114 * Continue searching up the tree. 115 */ 116 iparent = OF_parent(iparent); 117 } while (iparent > 0); 118 119 return 0; 120 } 121 122 static struct fdtbus_interrupt_controller * 123 fdtbus_get_interrupt_controller(int phandle) 124 { 125 struct fdtbus_interrupt_controller * ic; 126 LIST_FOREACH(ic, &fdtbus_interrupt_controllers, ic_next) { 127 if (ic->ic_phandle == phandle) 128 return ic; 129 } 130 return NULL; 131 } 132 133 int 134 fdtbus_register_interrupt_controller(device_t dev, int phandle, 135 const struct fdtbus_interrupt_controller_func *funcs) 136 { 137 struct fdtbus_interrupt_controller *ic; 138 139 ic = kmem_alloc(sizeof(*ic), KM_SLEEP); 140 ic->ic_dev = dev; 141 ic->ic_phandle = phandle; 142 ic->ic_funcs = funcs; 143 144 LIST_INSERT_HEAD(&fdtbus_interrupt_controllers, ic, ic_next); 145 146 return 0; 147 } 148 149 static struct fdtbus_interrupt_cookie * 150 fdtbus_get_interrupt_cookie(void *cookie) 151 { 152 struct fdtbus_interrupt_cookie *c; 153 154 mutex_enter(&fdtbus_interrupt_cookie_mutex); 155 LIST_FOREACH(c, &fdtbus_interrupt_cookies, c_next) { 156 if (c->c_ih == cookie) { 157 c->c_refcnt++; 158 KASSERT(c->c_refcnt > 0); 159 break; 160 } 161 } 162 mutex_exit(&fdtbus_interrupt_cookie_mutex); 163 return c; 164 } 165 166 static void 167 fdtbus_put_interrupt_cookie(struct fdtbus_interrupt_cookie *c) 168 { 169 170 mutex_enter(&fdtbus_interrupt_cookie_mutex); 171 KASSERT(c->c_refcnt > 0); 172 c->c_refcnt--; 173 if (fdtbus_interrupt_cookies_wanted) { 174 fdtbus_interrupt_cookies_wanted = false; 175 cv_signal(&fdtbus_interrupt_cookie_wait); 176 } 177 mutex_exit(&fdtbus_interrupt_cookie_mutex); 178 } 179 180 int 181 fdtbus_intr_parent(int phandle) 182 { 183 return fdtbus_get_interrupt_parent(phandle); 184 } 185 186 void * 187 fdtbus_intr_establish(int phandle, u_int index, int ipl, int flags, 188 int (*func)(void *), void *arg) 189 { 190 return fdtbus_intr_establish_xname(phandle, index, ipl, flags, 191 func, arg, NULL); 192 } 193 194 void * 195 fdtbus_intr_establish_xname(int phandle, u_int index, int ipl, int flags, 196 int (*func)(void *), void *arg, const char *xname) 197 { 198 const u_int *specifier; 199 int ihandle; 200 201 specifier = get_specifier_by_index(phandle, index, &ihandle); 202 if (specifier == NULL) 203 return NULL; 204 205 return fdtbus_intr_establish_raw(ihandle, specifier, ipl, 206 flags, func, arg, xname); 207 } 208 209 void * 210 fdtbus_intr_establish_byname(int phandle, const char *name, int ipl, 211 int flags, int (*func)(void *), void *arg, const char *xname) 212 { 213 u_int index; 214 int err; 215 216 err = fdtbus_get_index(phandle, "interrupt-names", name, &index); 217 if (err != 0) 218 return NULL; 219 220 return fdtbus_intr_establish_xname(phandle, index, ipl, flags, func, 221 arg, xname); 222 } 223 224 void * 225 fdtbus_intr_establish_raw(int ihandle, const u_int *specifier, int ipl, 226 int flags, int (*func)(void *), void *arg, const char *xname) 227 { 228 struct fdtbus_interrupt_controller *ic; 229 struct fdtbus_interrupt_cookie *c; 230 void *ih; 231 232 ic = fdtbus_get_interrupt_controller(ihandle); 233 if (ic == NULL) { 234 printf("%s: ihandle %d is not a controller\n",__func__,ihandle); 235 return NULL; 236 } 237 238 c = kmem_zalloc(sizeof(*c), KM_SLEEP); 239 c->c_ic = ic; 240 mutex_enter(&fdtbus_interrupt_cookie_mutex); 241 LIST_INSERT_HEAD(&fdtbus_interrupt_cookies, c, c_next); 242 mutex_exit(&fdtbus_interrupt_cookie_mutex); 243 244 /* 245 * XXX This leaves a small window where the handler is registered 246 * (and thus could be called) before the cookie on the list has a 247 * valid lookup key (and thus can be found). This will cause a 248 * panic in fdt_intr_mask() if that is called from the handler before 249 * this situation is resolved. For now we just cross our fingers 250 * and hope that the device won't actually interrupt until we return. 251 */ 252 ih = ic->ic_funcs->establish(ic->ic_dev, __UNCONST(specifier), 253 ipl, flags, func, arg, xname); 254 if (ih != NULL) { 255 atomic_store_release(&c->c_ih, ih); 256 } else { 257 mutex_enter(&fdtbus_interrupt_cookie_mutex); 258 LIST_REMOVE(c, c_next); 259 mutex_exit(&fdtbus_interrupt_cookie_mutex); 260 kmem_free(c, sizeof(*c)); 261 } 262 263 return ih; 264 } 265 266 void 267 fdtbus_intr_disestablish(int phandle, void *cookie) 268 { 269 struct fdtbus_interrupt_cookie *c; 270 271 if ((c = fdtbus_get_interrupt_cookie(cookie)) == NULL) { 272 panic("%s: interrupt handle not valid", __func__); 273 } 274 275 const struct fdtbus_interrupt_controller *ic = c->c_ic; 276 ic->ic_funcs->disestablish(ic->ic_dev, cookie); 277 278 /* 279 * Wait for any dangling references other than ours to 280 * drain away. 281 */ 282 mutex_enter(&fdtbus_interrupt_cookie_mutex); 283 while (c->c_refcnt != 1) { 284 KASSERT(c->c_refcnt > 0); 285 fdtbus_interrupt_cookies_wanted = true; 286 cv_wait(&fdtbus_interrupt_cookie_wait, 287 &fdtbus_interrupt_cookie_mutex); 288 } 289 LIST_REMOVE(c, c_next); 290 mutex_exit(&fdtbus_interrupt_cookie_mutex); 291 292 kmem_free(c, sizeof(*c)); 293 } 294 295 void 296 fdtbus_intr_mask(int phandle, void *cookie) 297 { 298 struct fdtbus_interrupt_cookie *c; 299 300 if ((c = fdtbus_get_interrupt_cookie(cookie)) == NULL) { 301 panic("%s: interrupt handle not valid", __func__); 302 } 303 304 struct fdtbus_interrupt_controller * const ic = c->c_ic; 305 306 if (ic->ic_funcs->mask == NULL) { 307 panic("%s: no 'mask' method for %s", __func__, 308 device_xname(ic->ic_dev)); 309 } 310 311 ic->ic_funcs->mask(ic->ic_dev, cookie); 312 fdtbus_put_interrupt_cookie(c); 313 } 314 315 void 316 fdtbus_intr_unmask(int phandle, void *cookie) 317 { 318 struct fdtbus_interrupt_cookie *c; 319 320 if ((c = fdtbus_get_interrupt_cookie(cookie)) == NULL) { 321 panic("%s: interrupt handle not valid", __func__); 322 } 323 324 struct fdtbus_interrupt_controller * const ic = c->c_ic; 325 326 if (ic->ic_funcs->unmask == NULL) { 327 panic("%s: no 'unmask' method for %s", __func__, 328 device_xname(ic->ic_dev)); 329 } 330 331 ic->ic_funcs->unmask(ic->ic_dev, cookie); 332 fdtbus_put_interrupt_cookie(c); 333 } 334 335 bool 336 fdtbus_intr_str(int phandle, u_int index, char *buf, size_t buflen) 337 { 338 const u_int *specifier; 339 int ihandle; 340 341 specifier = get_specifier_by_index(phandle, index, &ihandle); 342 if (specifier == NULL) 343 return false; 344 345 return fdtbus_intr_str_raw(ihandle, specifier, buf, buflen); 346 } 347 348 bool 349 fdtbus_intr_str_raw(int ihandle, const u_int *specifier, char *buf, size_t buflen) 350 { 351 struct fdtbus_interrupt_controller *ic; 352 353 ic = fdtbus_get_interrupt_controller(ihandle); 354 if (ic == NULL) 355 return false; 356 357 return ic->ic_funcs->intrstr(ic->ic_dev, __UNCONST(specifier), buf, buflen); 358 } 359 360 static int 361 find_address_cells(int phandle) 362 { 363 uint32_t cells; 364 365 if (of_getprop_uint32(phandle, "#address-cells", &cells) != 0) 366 cells = 2; 367 368 return cells; 369 } 370 371 static int 372 find_interrupt_cells(int phandle) 373 { 374 uint32_t cells; 375 376 while (phandle > 0) { 377 if (of_getprop_uint32(phandle, "#interrupt-cells", &cells) == 0) 378 return cells; 379 phandle = OF_parent(phandle); 380 } 381 return 0; 382 } 383 384 static const u_int * 385 get_specifier_from_map(int phandle, const u_int *interrupt_spec, int *piphandle) 386 { 387 const u_int *result = NULL; 388 int len, resid; 389 390 const u_int *data = fdtbus_get_prop(phandle, "interrupt-map", &len); 391 if (data == NULL || len <= 0) 392 return NULL; 393 resid = len; 394 395 /* child unit address: #address-cells prop of child bus node */ 396 const int cua_cells = find_address_cells(phandle); 397 /* child interrupt specifier: #interrupt-cells of the nexus node */ 398 const int cis_cells = find_interrupt_cells(phandle); 399 400 /* Offset (in cells) from map entry to child unit address specifier */ 401 const u_int cua_off = 0; 402 /* Offset (in cells) from map entry to child interrupt specifier */ 403 const u_int cis_off = cua_off + cua_cells; 404 /* Offset (in cells) from map entry to interrupt parent phandle */ 405 const u_int ip_off = cis_off + cis_cells; 406 /* Offset (in cells) from map entry to parent unit specifier */ 407 const u_int pus_off = ip_off + 1; 408 409 const u_int *p = (const u_int *)data; 410 while (resid > 0) { 411 /* Interrupt parent phandle */ 412 const u_int iparent = fdtbus_get_phandle_from_native(be32toh(p[ip_off])); 413 414 /* parent unit specifier: #address-cells of the interrupt parent */ 415 const u_int pus_cells = find_address_cells(iparent); 416 /* parent interrupt specifier: #interrupt-cells of the interrupt parent */ 417 const u_int pis_cells = find_interrupt_cells(iparent); 418 419 /* Offset (in cells) from map entry to parent interrupt specifier */ 420 const u_int pis_off = pus_off + pus_cells; 421 422 #ifdef FDT_INTR_DEBUG 423 printf(" intr map (len %d):", pis_off + pis_cells); 424 for (int i = 0; i < pis_off + pis_cells; i++) 425 printf(" %08x", p[i]); 426 printf("\n"); 427 #endif 428 429 if (memcmp(&p[cis_off], interrupt_spec, cis_cells * 4) == 0) { 430 #ifdef FDT_INTR_DEBUG 431 const int slen = pus_cells + pis_cells; 432 printf(" intr map match iparent %08x slen %d:", iparent, slen); 433 for (int i = 0; i < slen; i++) 434 printf(" %08x", p[pus_off + i]); 435 printf("\n"); 436 #endif 437 result = &p[pus_off]; 438 *piphandle = iparent; 439 goto done; 440 } 441 /* Determine the length of the entry and skip that many 442 * 32 bit words 443 */ 444 const u_int reclen = pis_off + pis_cells; 445 resid -= reclen * sizeof(u_int); 446 p += reclen; 447 } 448 449 done: 450 return result; 451 } 452 453 454 static const u_int * 455 get_specifier_from_extended(int phandle, int pindex, int *piphandle) 456 { 457 const u_int *result = NULL; 458 struct fdt_phandle_data data; 459 460 if (fdtbus_get_phandle_with_data(phandle, "interrupts-extended", 461 "#interrupt-cells", pindex, &data) == 0) { 462 *piphandle = data.phandle; 463 result = data.values; 464 } 465 466 return result; 467 } 468 469 static const u_int * 470 get_specifier_by_index(int phandle, int pindex, int *piphandle) 471 { 472 const u_int *node_specifier; 473 int interrupt_parent, interrupt_cells, len; 474 475 if (of_hasprop(phandle, "interrupts-extended")) 476 return get_specifier_from_extended(phandle, pindex, piphandle); 477 478 interrupt_parent = fdtbus_get_interrupt_parent(phandle); 479 if (interrupt_parent <= 0) 480 return NULL; 481 482 node_specifier = fdtbus_get_prop(phandle, "interrupts", &len); 483 if (node_specifier == NULL) 484 return NULL; 485 486 interrupt_cells = find_interrupt_cells(interrupt_parent); 487 if (interrupt_cells <= 0) 488 return NULL; 489 490 const u_int spec_length = len / 4; 491 const u_int nintr = spec_length / interrupt_cells; 492 if (pindex >= nintr) 493 return NULL; 494 495 node_specifier += (interrupt_cells * pindex); 496 497 if (of_hasprop(interrupt_parent, "interrupt-map")) 498 return get_specifier_from_map(interrupt_parent, node_specifier, piphandle); 499 500 *piphandle = interrupt_parent; 501 502 return node_specifier; 503 } 504