1 /* $NetBSD: fdt_intr.c,v 1.29 2021/01/15 22:59:49 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.29 2021/01/15 22:59:49 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 void * 181 fdtbus_intr_establish(int phandle, u_int index, int ipl, int flags, 182 int (*func)(void *), void *arg) 183 { 184 return fdtbus_intr_establish_xname(phandle, index, ipl, flags, 185 func, arg, NULL); 186 } 187 188 void * 189 fdtbus_intr_establish_xname(int phandle, u_int index, int ipl, int flags, 190 int (*func)(void *), void *arg, const char *xname) 191 { 192 const u_int *specifier; 193 int ihandle; 194 195 specifier = get_specifier_by_index(phandle, index, &ihandle); 196 if (specifier == NULL) 197 return NULL; 198 199 return fdtbus_intr_establish_raw(ihandle, specifier, ipl, 200 flags, func, arg, xname); 201 } 202 203 void * 204 fdtbus_intr_establish_byname(int phandle, const char *name, int ipl, 205 int flags, int (*func)(void *), void *arg, const char *xname) 206 { 207 u_int index; 208 int err; 209 210 err = fdtbus_get_index(phandle, "interrupt-names", name, &index); 211 if (err != 0) 212 return NULL; 213 214 return fdtbus_intr_establish_xname(phandle, index, ipl, flags, func, 215 arg, xname); 216 } 217 218 void * 219 fdtbus_intr_establish_raw(int ihandle, const u_int *specifier, int ipl, 220 int flags, int (*func)(void *), void *arg, const char *xname) 221 { 222 struct fdtbus_interrupt_controller *ic; 223 struct fdtbus_interrupt_cookie *c; 224 void *ih; 225 226 ic = fdtbus_get_interrupt_controller(ihandle); 227 if (ic == NULL) { 228 printf("%s: ihandle %d is not a controller\n",__func__,ihandle); 229 return NULL; 230 } 231 232 c = kmem_zalloc(sizeof(*c), KM_SLEEP); 233 c->c_ic = ic; 234 mutex_enter(&fdtbus_interrupt_cookie_mutex); 235 LIST_INSERT_HEAD(&fdtbus_interrupt_cookies, c, c_next); 236 mutex_exit(&fdtbus_interrupt_cookie_mutex); 237 238 /* 239 * XXX This leaves a small window where the handler is registered 240 * (and thus could be called) before the cookie on the list has a 241 * valid lookup key (and thus can be found). This will cause a 242 * panic in fdt_intr_mask() if that is called from the handler before 243 * this situation is resolved. For now we just cross our fingers 244 * and hope that the device won't actually interrupt until we return. 245 */ 246 ih = ic->ic_funcs->establish(ic->ic_dev, __UNCONST(specifier), 247 ipl, flags, func, arg, xname); 248 if (ih != NULL) { 249 atomic_store_release(&c->c_ih, ih); 250 } else { 251 mutex_enter(&fdtbus_interrupt_cookie_mutex); 252 LIST_REMOVE(c, c_next); 253 mutex_exit(&fdtbus_interrupt_cookie_mutex); 254 kmem_free(c, sizeof(*c)); 255 } 256 257 return ih; 258 } 259 260 void 261 fdtbus_intr_disestablish(int phandle, void *cookie) 262 { 263 struct fdtbus_interrupt_cookie *c; 264 265 if ((c = fdtbus_get_interrupt_cookie(cookie)) == NULL) { 266 panic("%s: interrupt handle not valid", __func__); 267 } 268 269 const struct fdtbus_interrupt_controller *ic = c->c_ic; 270 ic->ic_funcs->disestablish(ic->ic_dev, cookie); 271 272 /* 273 * Wait for any dangling references other than ours to 274 * drain away. 275 */ 276 mutex_enter(&fdtbus_interrupt_cookie_mutex); 277 while (c->c_refcnt != 1) { 278 KASSERT(c->c_refcnt > 0); 279 fdtbus_interrupt_cookies_wanted = true; 280 cv_wait(&fdtbus_interrupt_cookie_wait, 281 &fdtbus_interrupt_cookie_mutex); 282 } 283 LIST_REMOVE(c, c_next); 284 mutex_exit(&fdtbus_interrupt_cookie_mutex); 285 286 kmem_free(c, sizeof(*c)); 287 } 288 289 void 290 fdtbus_intr_mask(int phandle, void *cookie) 291 { 292 struct fdtbus_interrupt_cookie *c; 293 294 if ((c = fdtbus_get_interrupt_cookie(cookie)) == NULL) { 295 panic("%s: interrupt handle not valid", __func__); 296 } 297 298 struct fdtbus_interrupt_controller * const ic = c->c_ic; 299 300 if (ic->ic_funcs->mask == NULL) { 301 panic("%s: no 'mask' method for %s", __func__, 302 device_xname(ic->ic_dev)); 303 } 304 305 ic->ic_funcs->mask(ic->ic_dev, cookie); 306 fdtbus_put_interrupt_cookie(c); 307 } 308 309 void 310 fdtbus_intr_unmask(int phandle, void *cookie) 311 { 312 struct fdtbus_interrupt_cookie *c; 313 314 if ((c = fdtbus_get_interrupt_cookie(cookie)) == NULL) { 315 panic("%s: interrupt handle not valid", __func__); 316 } 317 318 struct fdtbus_interrupt_controller * const ic = c->c_ic; 319 320 if (ic->ic_funcs->unmask == NULL) { 321 panic("%s: no 'unmask' method for %s", __func__, 322 device_xname(ic->ic_dev)); 323 } 324 325 ic->ic_funcs->unmask(ic->ic_dev, cookie); 326 fdtbus_put_interrupt_cookie(c); 327 } 328 329 bool 330 fdtbus_intr_str(int phandle, u_int index, char *buf, size_t buflen) 331 { 332 const u_int *specifier; 333 int ihandle; 334 335 specifier = get_specifier_by_index(phandle, index, &ihandle); 336 if (specifier == NULL) 337 return false; 338 339 return fdtbus_intr_str_raw(ihandle, specifier, buf, buflen); 340 } 341 342 bool 343 fdtbus_intr_str_raw(int ihandle, const u_int *specifier, char *buf, size_t buflen) 344 { 345 struct fdtbus_interrupt_controller *ic; 346 347 ic = fdtbus_get_interrupt_controller(ihandle); 348 if (ic == NULL) 349 return false; 350 351 return ic->ic_funcs->intrstr(ic->ic_dev, __UNCONST(specifier), buf, buflen); 352 } 353 354 static int 355 find_address_cells(int phandle) 356 { 357 uint32_t cells; 358 359 if (of_getprop_uint32(phandle, "#address-cells", &cells) != 0) 360 cells = 2; 361 362 return cells; 363 } 364 365 static int 366 find_interrupt_cells(int phandle) 367 { 368 uint32_t cells; 369 370 while (phandle > 0) { 371 if (of_getprop_uint32(phandle, "#interrupt-cells", &cells) == 0) 372 return cells; 373 phandle = OF_parent(phandle); 374 } 375 return 0; 376 } 377 378 static const u_int * 379 get_specifier_from_map(int phandle, const u_int *interrupt_spec, int *piphandle) 380 { 381 const u_int *result = NULL; 382 int len, resid; 383 384 const u_int *data = fdtbus_get_prop(phandle, "interrupt-map", &len); 385 if (data == NULL || len <= 0) 386 return NULL; 387 resid = len; 388 389 /* child unit address: #address-cells prop of child bus node */ 390 const int cua_cells = find_address_cells(phandle); 391 /* child interrupt specifier: #interrupt-cells of the nexus node */ 392 const int cis_cells = find_interrupt_cells(phandle); 393 394 /* Offset (in cells) from map entry to child unit address specifier */ 395 const u_int cua_off = 0; 396 /* Offset (in cells) from map entry to child interrupt specifier */ 397 const u_int cis_off = cua_off + cua_cells; 398 /* Offset (in cells) from map entry to interrupt parent phandle */ 399 const u_int ip_off = cis_off + cis_cells; 400 /* Offset (in cells) from map entry to parent unit specifier */ 401 const u_int pus_off = ip_off + 1; 402 403 const u_int *p = (const u_int *)data; 404 while (resid > 0) { 405 /* Interrupt parent phandle */ 406 const u_int iparent = fdtbus_get_phandle_from_native(be32toh(p[ip_off])); 407 408 /* parent unit specifier: #address-cells of the interrupt parent */ 409 const u_int pus_cells = find_address_cells(iparent); 410 /* parent interrupt specifier: #interrupt-cells of the interrupt parent */ 411 const u_int pis_cells = find_interrupt_cells(iparent); 412 413 /* Offset (in cells) from map entry to parent interrupt specifier */ 414 const u_int pis_off = pus_off + pus_cells; 415 416 #ifdef FDT_INTR_DEBUG 417 printf(" intr map (len %d):", pis_off + pis_cells); 418 for (int i = 0; i < pis_off + pis_cells; i++) 419 printf(" %08x", p[i]); 420 printf("\n"); 421 #endif 422 423 if (memcmp(&p[cis_off], interrupt_spec, cis_cells * 4) == 0) { 424 #ifdef FDT_INTR_DEBUG 425 const int slen = pus_cells + pis_cells; 426 printf(" intr map match iparent %08x slen %d:", iparent, slen); 427 for (int i = 0; i < slen; i++) 428 printf(" %08x", p[pus_off + i]); 429 printf("\n"); 430 #endif 431 result = &p[pus_off]; 432 *piphandle = iparent; 433 goto done; 434 } 435 /* Determine the length of the entry and skip that many 436 * 32 bit words 437 */ 438 const u_int reclen = pis_off + pis_cells; 439 resid -= reclen * sizeof(u_int); 440 p += reclen; 441 } 442 443 done: 444 return result; 445 } 446 447 448 static const u_int * 449 get_specifier_from_extended(int phandle, int pindex, int *piphandle) 450 { 451 const u_int *result = NULL; 452 struct fdt_phandle_data data; 453 454 if (fdtbus_get_phandle_with_data(phandle, "interrupts-extended", 455 "#interrupt-cells", pindex, &data) == 0) { 456 *piphandle = data.phandle; 457 result = data.values; 458 } 459 460 return result; 461 } 462 463 static const u_int * 464 get_specifier_by_index(int phandle, int pindex, int *piphandle) 465 { 466 const u_int *node_specifier; 467 int interrupt_parent, interrupt_cells, len; 468 469 if (of_hasprop(phandle, "interrupts-extended")) 470 return get_specifier_from_extended(phandle, pindex, piphandle); 471 472 interrupt_parent = fdtbus_get_interrupt_parent(phandle); 473 if (interrupt_parent <= 0) 474 return NULL; 475 476 node_specifier = fdtbus_get_prop(phandle, "interrupts", &len); 477 if (node_specifier == NULL) 478 return NULL; 479 480 interrupt_cells = find_interrupt_cells(interrupt_parent); 481 if (interrupt_cells <= 0) 482 return NULL; 483 484 const u_int spec_length = len / 4; 485 const u_int nintr = spec_length / interrupt_cells; 486 if (pindex >= nintr) 487 return NULL; 488 489 node_specifier += (interrupt_cells * pindex); 490 491 if (of_hasprop(interrupt_parent, "interrupt-map")) 492 return get_specifier_from_map(interrupt_parent, node_specifier, piphandle); 493 494 *piphandle = interrupt_parent; 495 496 return node_specifier; 497 } 498