1 /* $OpenBSD: plic.c,v 1.13 2024/09/04 07:54:51 mglocker Exp $ */ 2 3 /* 4 * Copyright (c) 2020, Mars Li <mengshi.li.mars@gmail.com> 5 * Copyright (c) 2020, Brian Bamsch <bbamsch@google.com> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/param.h> 21 #include <sys/systm.h> 22 #include <sys/queue.h> 23 #include <sys/malloc.h> 24 #include <sys/device.h> 25 #include <sys/evcount.h> 26 27 #include <machine/bus.h> 28 #include <machine/fdt.h> 29 #include <machine/cpu.h> 30 #include <machine/sbi.h> 31 #include "riscv64/dev/riscv_cpu_intc.h" 32 33 #include <dev/ofw/openfirm.h> 34 #include <dev/ofw/fdt.h> 35 36 /* 37 * This driver implements a version of the RISC-V PLIC with the actual layout 38 * specified in chapter 8 of the SiFive U5 Coreplex Series Manual: 39 * 40 * https://static.dev.sifive.com/U54-MC-RVCoreIP.pdf 41 * 42 * The largest number supported by devices marked as 'sifive,plic-1.0.0', is 43 * 1024, of which device 0 is defined as non-existent by the RISC-V Privileged 44 * Spec. 45 */ 46 47 #define PLIC_MAX_IRQS 1024 48 49 #define PLIC_PRIORITY_BASE 0x000000U 50 51 #define PLIC_ENABLE_BASE 0x002000U 52 #define PLIC_ENABLE_STRIDE 0x80U 53 #define IRQ_ENABLE 1 54 #define IRQ_DISABLE 0 55 56 #define PLIC_CONTEXT_BASE 0x200000U 57 #define PLIC_CONTEXT_STRIDE 0x1000U 58 #define PLIC_CONTEXT_THRESHOLD 0x0U 59 #define PLIC_CONTEXT_CLAIM 0x4U 60 61 #define PLIC_PRIORITY(n) (PLIC_PRIORITY_BASE + (n) * sizeof(uint32_t)) 62 #define PLIC_ENABLE(sc, n, h) \ 63 (sc->sc_contexts[h].enable_offset + ((n) / 32) * sizeof(uint32_t)) 64 #define PLIC_THRESHOLD(sc, h) \ 65 (sc->sc_contexts[h].context_offset + PLIC_CONTEXT_THRESHOLD) 66 #define PLIC_CLAIM(sc, h) \ 67 (sc->sc_contexts[h].context_offset + PLIC_CONTEXT_CLAIM) 68 69 70 struct plic_intrhand { 71 TAILQ_ENTRY(plic_intrhand) ih_list; /* link on intrq list */ 72 int (*ih_func)(void *); /* handler */ 73 void *ih_arg; /* arg for handler */ 74 int ih_ipl; /* IPL_* */ 75 int ih_flags; 76 int ih_irq; /* IRQ number */ 77 struct evcount ih_count; 78 char *ih_name; 79 struct cpu_info *ih_ci; 80 }; 81 82 /* 83 * One interrupt source could have multiple handler attached, 84 * each handler could have different priority level, 85 * we track the max and min priority level. 86 */ 87 struct plic_irqsrc { 88 TAILQ_HEAD(, plic_intrhand) is_list; /* handler list */ 89 int is_irq_max; /* IRQ to mask while handling */ 90 int is_irq_min; /* lowest IRQ when shared */ 91 }; 92 93 struct plic_context { 94 bus_size_t enable_offset; 95 bus_size_t context_offset; 96 }; 97 98 struct plic_softc { 99 struct device sc_dev; 100 int sc_node; 101 bus_space_tag_t sc_iot; 102 bus_space_handle_t sc_ioh; 103 struct plic_irqsrc *sc_isrcs; 104 struct plic_context sc_contexts[MAXCPUS]; 105 int sc_ndev; 106 struct interrupt_controller sc_intc; 107 }; 108 struct plic_softc *plic = NULL; 109 110 int plic_match(struct device *, void *, void *); 111 void plic_attach(struct device *, struct device *, void *); 112 int plic_irq_handler(void *); 113 int plic_irq_dispatch(uint32_t, void *); 114 void *plic_intr_establish(int, int, struct cpu_info *, 115 int (*)(void *), void *, char *); 116 void *plic_intr_establish_fdt(void *, int *, int, struct cpu_info *, 117 int (*)(void *), void *, char *); 118 void plic_intr_disestablish(void *); 119 void plic_intr_route(void *, int, struct cpu_info *); 120 void plic_intr_barrier(void *); 121 122 void plic_splx(int); 123 int plic_spllower(int); 124 int plic_splraise(int); 125 void plic_setipl(int); 126 void plic_calc_mask(void); 127 128 /* helper function */ 129 int plic_get_cpuid(int); 130 void plic_set_priority(int, uint32_t); 131 void plic_set_threshold(int, uint32_t); 132 void plic_intr_route_grid(int, int, int); 133 void plic_intr_enable_with_pri(int, uint32_t, int); 134 void plic_intr_disable(int, int); 135 136 137 const struct cfattach plic_ca = { 138 sizeof(struct plic_softc), plic_match, plic_attach, 139 }; 140 141 struct cfdriver plic_cd = { 142 NULL, "plic", DV_DULL 143 }; 144 145 int plic_attached = 0; 146 147 int 148 plic_match(struct device *parent, void *cfdata, void *aux) 149 { 150 struct fdt_attach_args *faa = aux; 151 152 if (plic_attached) 153 return 0; // Only expect one instance of PLIC 154 155 return (OF_is_compatible(faa->fa_node, "riscv,plic0") || 156 OF_is_compatible(faa->fa_node, "sifive,plic-1.0.0") || 157 OF_is_compatible(faa->fa_node, "thead,c900-plic")); 158 } 159 160 void 161 plic_attach(struct device *parent, struct device *dev, void *aux) 162 { 163 struct plic_softc *sc; 164 struct fdt_attach_args *faa; 165 uint32_t *cells; 166 uint32_t irq; 167 int cpu; 168 int node; 169 int len; 170 int ncell; 171 int context; 172 int i; 173 struct cpu_info *ci; 174 CPU_INFO_ITERATOR cii; 175 176 if (plic_attached) 177 return; 178 179 plic = sc = (struct plic_softc *)dev; 180 faa = (struct fdt_attach_args *)aux; 181 182 if (faa->fa_nreg < 1) 183 return; 184 185 sc->sc_node = node = faa->fa_node; 186 sc->sc_iot = faa->fa_iot; 187 188 /* determine number of devices sending intr to this ic */ 189 sc->sc_ndev = OF_getpropint(faa->fa_node, "riscv,ndev", -1); 190 if (sc->sc_ndev < 0) { 191 printf(": unable to resolve number of devices\n"); 192 return; 193 } 194 195 if (sc->sc_ndev >= PLIC_MAX_IRQS) { 196 printf(": invalid ndev (%d)\n", sc->sc_ndev); 197 return; 198 } 199 200 /* map interrupt controller to va space */ 201 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, 202 faa->fa_reg[0].size, 0, &sc->sc_ioh)) 203 panic("%s: bus_space_map failed!", __func__); 204 205 sc->sc_isrcs = mallocarray(PLIC_MAX_IRQS, sizeof(struct plic_irqsrc), 206 M_DEVBUF, M_ZERO | M_NOWAIT); 207 208 for (irq = 1; irq <= sc->sc_ndev; irq++) { 209 TAILQ_INIT(&sc->sc_isrcs[irq].is_list); 210 plic_set_priority(irq, 0);// Mask interrupt 211 } 212 213 /* 214 * Calculate the per-cpu enable and context register offsets. 215 * 216 * This is tricky for a few reasons. The PLIC divides the interrupt 217 * enable, threshold, and claim bits by "context" 218 * 219 * The tricky part is that the PLIC spec imposes no restrictions on how 220 * these contexts are laid out. So for example, there is no guarantee 221 * that each CPU will have both a machine mode and supervisor context, 222 * or that different PLIC implementations will organize the context 223 * registers in the same way. On top of this, we must handle the fact 224 * that cpuid != hartid, as they may have been renumbered during boot. 225 * We perform the following steps: 226 * 227 * 1. Examine the PLIC's "interrupts-extended" property and skip any 228 * entries that are not for supervisor external interrupts. 229 * 230 * 2. Walk up the device tree to find the corresponding CPU, using node 231 * property to identify the cpuid. 232 * 233 * 3. Calculate the register offsets based on the context number. 234 */ 235 len = OF_getproplen(node, "interrupts-extended"); 236 if (len <= 0) { 237 printf(": could not find interrupts-extended\n"); 238 return; 239 } 240 241 cells = malloc(len, M_TEMP, M_WAITOK); 242 ncell = len / sizeof(*cells); 243 if (OF_getpropintarray(node, "interrupts-extended", cells, len) < 0) { 244 printf(": failed to read interrupts-extended\n"); 245 free(cells, M_TEMP, len); 246 return; 247 } 248 249 for (i = 0, context = 0; i < ncell; i += 2, context++) { 250 /* Skip M-mode external interrupts */ 251 if (cells[i + 1] != IRQ_EXTERNAL_SUPERVISOR) 252 continue; 253 254 /* Get the corresponding cpuid. */ 255 cpu = plic_get_cpuid(OF_getnodebyphandle(cells[i])); 256 if (cpu < 0) 257 continue; 258 259 /* 260 * Set the enable and context register offsets for the CPU. 261 * 262 * We assume S-mode handler always comes later than M-mode 263 * handler, but this might be a little fragile. 264 * 265 * XXX 266 * sifive spec doesn't list hart0 S-mode enable/contexts 267 * in its memory map, but QEMU emulates hart0 S-mode 268 * enable/contexts? Otherwise the following offset calculation 269 * would point to hart1 M-mode enable/contexts. 270 */ 271 sc->sc_contexts[cpu].enable_offset = PLIC_ENABLE_BASE + 272 context * PLIC_ENABLE_STRIDE; 273 sc->sc_contexts[cpu].context_offset = PLIC_CONTEXT_BASE + 274 context * PLIC_CONTEXT_STRIDE; 275 } 276 277 free(cells, M_TEMP, len); 278 279 /* Set CPU interrupt priority thresholds to minimum */ 280 CPU_INFO_FOREACH(cii, ci) { 281 plic_set_threshold(ci->ci_cpuid, 0); 282 } 283 284 plic_setipl(IPL_HIGH); /* XXX ??? */ 285 plic_calc_mask(); 286 287 /* 288 * insert self into the external interrupt handler entry in 289 * global interrupt handler vector 290 */ 291 riscv_intc_intr_establish(IRQ_EXTERNAL_SUPERVISOR, 0, 292 plic_irq_handler, NULL, "plic0"); 293 294 /* 295 * From now on, spl update must be enforced to plic, so 296 * spl* routine should be updated. 297 */ 298 riscv_set_intr_func(plic_splraise, plic_spllower, 299 plic_splx, plic_setipl); 300 301 plic_attached = 1; 302 303 /* enable external interrupt */ 304 csr_set(sie, SIE_SEIE); 305 306 sc->sc_intc.ic_node = faa->fa_node; 307 sc->sc_intc.ic_cookie = sc; 308 sc->sc_intc.ic_establish = plic_intr_establish_fdt; 309 sc->sc_intc.ic_disestablish = plic_intr_disestablish; 310 sc->sc_intc.ic_route = plic_intr_route; 311 // sc->sc_intc.ic_cpu_enable = XXX Per-CPU Initialization? 312 sc->sc_intc.ic_barrier = plic_intr_barrier; 313 314 riscv_intr_register_fdt(&sc->sc_intc); 315 316 printf("\n"); 317 } 318 319 int 320 plic_irq_handler(void *frame) 321 { 322 struct plic_softc* sc; 323 uint32_t pending; 324 uint32_t cpu; 325 int handled = 0; 326 327 sc = plic; 328 cpu = cpu_number(); 329 330 pending = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 331 PLIC_CLAIM(sc, cpu)); 332 333 if (pending >= sc->sc_ndev) { 334 printf("plic0: pending %x\n", pending); 335 return 0; 336 } 337 338 if (pending) { 339 handled = plic_irq_dispatch(pending, frame); 340 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 341 PLIC_CLAIM(sc, cpu), pending); 342 343 //#define DEBUG_INTC 344 #ifdef DEBUG_INTC 345 if (handled == 0) { 346 printf("plic handled == 0 on pending %d\n", pending); 347 } 348 #endif /* DEBUG_INTC */ 349 } 350 351 return handled; 352 } 353 354 int 355 plic_irq_dispatch(uint32_t irq, void *frame) 356 { 357 int pri, s; 358 int handled = 0; 359 struct plic_softc* sc; 360 struct plic_intrhand *ih; 361 void *arg; 362 363 #ifdef DEBUG_INTC 364 printf("plic irq %d fired\n", irq); 365 #endif 366 367 sc = plic; 368 pri = sc->sc_isrcs[irq].is_irq_max; 369 s = plic_splraise(pri); 370 TAILQ_FOREACH(ih, &sc->sc_isrcs[irq].is_list, ih_list) { 371 #ifdef MULTIPROCESSOR 372 int need_lock; 373 374 if (ih->ih_flags & IPL_MPSAFE) 375 need_lock = 0; 376 else 377 need_lock = s < IPL_SCHED; 378 379 if (need_lock) 380 KERNEL_LOCK(); 381 #endif 382 383 if (ih->ih_arg) 384 arg = ih->ih_arg; 385 else 386 arg = frame; 387 388 intr_enable(); 389 handled = ih->ih_func(arg); 390 intr_disable(); 391 if (handled) 392 ih->ih_count.ec_count++; 393 394 #ifdef MULTIPROCESSOR 395 if (need_lock) 396 KERNEL_UNLOCK(); 397 #endif 398 } 399 400 plic_splx(s); 401 return handled; 402 } 403 404 void * 405 plic_intr_establish(int irqno, int level, struct cpu_info *ci, 406 int (*func)(void *), void *arg, char *name) 407 { 408 struct plic_softc *sc = plic; 409 struct plic_intrhand *ih; 410 u_long sie; 411 412 if (irqno < 0 || irqno >= PLIC_MAX_IRQS) 413 panic("plic_intr_establish: bogus irqnumber %d: %s", 414 irqno, name); 415 416 if (ci == NULL) 417 ci = &cpu_info_primary; 418 419 ih = malloc(sizeof *ih, M_DEVBUF, M_WAITOK); 420 ih->ih_func = func; 421 ih->ih_arg = arg; 422 ih->ih_ipl = level & IPL_IRQMASK; 423 ih->ih_flags = level & IPL_FLAGMASK; 424 ih->ih_irq = irqno; 425 ih->ih_name = name; 426 ih->ih_ci = ci; 427 428 sie = intr_disable(); 429 430 TAILQ_INSERT_TAIL(&sc->sc_isrcs[irqno].is_list, ih, ih_list); 431 432 if (name != NULL) 433 evcount_attach(&ih->ih_count, name, &ih->ih_irq); 434 435 #ifdef DEBUG_INTC 436 printf("%s irq %d level %d [%s]\n", __func__, irqno, level, 437 name); 438 #endif 439 440 plic_calc_mask(); 441 442 intr_restore(sie); 443 return (ih); 444 } 445 446 void * 447 plic_intr_establish_fdt(void *cookie, int *cell, int level, 448 struct cpu_info *ci, int (*func)(void *), void *arg, char *name) 449 { 450 return plic_intr_establish(cell[0], level, ci, func, arg, name); 451 } 452 453 void 454 plic_intr_disestablish(void *cookie) 455 { 456 struct plic_softc *sc = plic; 457 struct plic_intrhand *ih = cookie; 458 int irqno = ih->ih_irq; 459 u_long sie; 460 461 sie = intr_disable(); 462 463 TAILQ_REMOVE(&sc->sc_isrcs[irqno].is_list, ih, ih_list); 464 if (ih->ih_name != NULL) 465 evcount_detach(&ih->ih_count); 466 467 intr_restore(sie); 468 469 free(ih, M_DEVBUF, 0); 470 } 471 472 void 473 plic_intr_route(void *cookie, int enable, struct cpu_info *ci) 474 { 475 struct plic_softc *sc = plic; 476 struct plic_intrhand *ih = cookie; 477 478 int irq = ih->ih_irq; 479 int cpu = ci->ci_cpuid; 480 uint32_t min_pri = sc->sc_isrcs[irq].is_irq_min; 481 482 if (enable == IRQ_ENABLE) { 483 plic_intr_enable_with_pri(irq, min_pri, cpu); 484 } else { 485 plic_intr_route_grid(irq, IRQ_DISABLE, cpu); 486 } 487 } 488 489 void 490 plic_intr_barrier(void *cookie) 491 { 492 struct plic_intrhand *ih = cookie; 493 494 sched_barrier(ih->ih_ci); 495 } 496 497 void 498 plic_splx(int new) 499 { 500 /* XXX 501 * how to do pending external interrupt ? 502 * After set the new threshold, if there is any pending 503 * external interrupts whose priority is now greater than the 504 * threshold, they will get passed through plic to cpu, 505 * trigger a new claim/complete cycle. 506 * So there is no need to handle pending external intr here. 507 * 508 */ 509 struct cpu_info *ci = curcpu(); 510 511 /* Pending software intr is handled here */ 512 if (ci->ci_ipending & riscv_smask[new]) 513 riscv_do_pending_intr(new); 514 515 plic_setipl(new); 516 } 517 518 int 519 plic_spllower(int new) 520 { 521 struct cpu_info *ci = curcpu(); 522 int old = ci->ci_cpl; 523 plic_splx(new); 524 return (old); 525 } 526 527 int 528 plic_splraise(int new) 529 { 530 struct cpu_info *ci = curcpu(); 531 int old; 532 old = ci->ci_cpl; 533 534 /* 535 * setipl must always be called because there is a race window 536 * where the variable is updated before the mask is set 537 * an interrupt occurs in that window without the mask always 538 * being set, the hardware might not get updated on the next 539 * splraise completely messing up spl protection. 540 */ 541 if (old > new) 542 new = old; 543 544 plic_setipl(new); 545 546 return (old); 547 } 548 549 void 550 plic_setipl(int new) 551 { 552 struct cpu_info *ci = curcpu(); 553 u_long sie; 554 555 /* disable here is only to keep hardware in sync with ci->ci_cpl */ 556 sie = intr_disable(); 557 ci->ci_cpl = new; 558 559 /* higher values are higher priority */ 560 plic_set_threshold(ci->ci_cpuid, new); 561 562 /* trigger deferred timer interrupt if cpl is now low enough */ 563 if (ci->ci_timer_deferred && new < IPL_CLOCK) 564 sbi_set_timer(0); 565 566 intr_restore(sie); 567 } 568 569 /* 570 * update the max/min priority for an interrupt src, 571 * and enforce the updated priority to plic. 572 * this should be called whenever a new handler is attached. 573 */ 574 void 575 plic_calc_mask(void) 576 { 577 struct cpu_info *ci = curcpu(); 578 struct plic_softc *sc = plic; 579 struct plic_intrhand *ih; 580 int irq; 581 582 /* PLIC irq 0 is reserved, thus we start from 1 */ 583 for (irq = 1; irq <= sc->sc_ndev; irq++) { 584 int max = IPL_NONE; 585 int min = IPL_HIGH; 586 TAILQ_FOREACH(ih, &sc->sc_isrcs[irq].is_list, ih_list) { 587 if (ih->ih_ipl > max) 588 max = ih->ih_ipl; 589 590 if (ih->ih_ipl < min) 591 min = ih->ih_ipl; 592 } 593 594 if (max == IPL_NONE) 595 min = IPL_NONE; 596 597 if (sc->sc_isrcs[irq].is_irq_max == max && 598 sc->sc_isrcs[irq].is_irq_min == min) 599 continue; 600 601 sc->sc_isrcs[irq].is_irq_max = max; 602 sc->sc_isrcs[irq].is_irq_min = min; 603 604 /* Enable interrupts at lower levels, clear -> enable */ 605 /* Set interrupt priority/enable */ 606 if (min != IPL_NONE) { 607 plic_intr_enable_with_pri(irq, min, ci->ci_cpuid); 608 } else { 609 plic_intr_disable(irq, ci->ci_cpuid); 610 } 611 } 612 613 plic_setipl(ci->ci_cpl); 614 } 615 616 /***************** helper functions *****************/ 617 618 /* 619 * OpenBSD saves cpu node info in ci struct, so we can search 620 * cpuid by node matching 621 */ 622 int 623 plic_get_cpuid(int intc) 624 { 625 uint32_t hart; 626 int parent_node; 627 struct cpu_info *ci; 628 CPU_INFO_ITERATOR cii; 629 630 /* Check the interrupt controller layout. */ 631 if (OF_getpropintarray(intc, "#interrupt-cells", &hart, 632 sizeof(hart)) < 0) { 633 printf(": could not find #interrupt-cells for phandle %u\n", intc); 634 return (-1); 635 } 636 637 /* 638 * The parent of the interrupt-controller is the CPU we are 639 * interested in, so search for its OF node index. 640 */ 641 parent_node = OF_parent(intc); 642 CPU_INFO_FOREACH(cii, ci) { 643 if (ci->ci_node == parent_node) 644 return ci->ci_cpuid; 645 } 646 return -1; 647 } 648 649 /* update priority for intr src 'irq' */ 650 void 651 plic_set_priority(int irq, uint32_t pri) 652 { 653 struct plic_softc *sc = plic; 654 uint32_t prival; 655 656 /* 657 * sifive plic only has 0 - 7 priority levels, yet OpenBSD defines 658 * 0 - 12 priority levels(level 1 - 4 are for SOFT*, level 12 659 * is for IPI. They should NEVER be passed to plic. 660 * So we calculate plic priority in the following way: 661 */ 662 if (pri <= 4 || pri >= 12)//invalid input 663 prival = 0;//effectively disable this intr source 664 else 665 prival = pri - 4; 666 667 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 668 PLIC_PRIORITY(irq), prival); 669 } 670 671 /* update threshold for 'cpu' */ 672 void 673 plic_set_threshold(int cpu, uint32_t threshold) 674 { 675 struct plic_softc *sc = plic; 676 uint32_t prival; 677 678 if (threshold < 4) // enable everything (as far as plic is concerned) 679 prival = 0; 680 else if (threshold >= 12) // invalid priority level ? 681 prival = IPL_HIGH - 4; // XXX Device-specific high threshold 682 else // everything else 683 prival = threshold - 4; // XXX Device-specific threshold offset 684 685 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 686 PLIC_THRESHOLD(sc, cpu), prival); 687 } 688 689 /* 690 * turns on/off the route from intr source 'irq' 691 * to context 'ci' based on 'enable' 692 */ 693 void 694 plic_intr_route_grid(int irq, int enable, int cpu) 695 { 696 struct plic_softc *sc = plic; 697 uint32_t val, mask; 698 699 if (irq == 0) 700 return; 701 702 KASSERT(cpu < MAXCPUS); 703 704 mask = (1 << (irq % 32)); 705 val = bus_space_read_4(sc->sc_iot, sc->sc_ioh, 706 PLIC_ENABLE(sc, irq, cpu)); 707 if (enable == IRQ_ENABLE) 708 val |= mask; 709 else 710 val &= ~mask; 711 712 bus_space_write_4(sc->sc_iot, sc->sc_ioh, 713 PLIC_ENABLE(sc, irq, cpu), val); 714 } 715 716 /* 717 * Enable intr src 'irq' to cpu 'cpu' by setting: 718 * - priority 719 * - threshold 720 * - enable bit 721 */ 722 void 723 plic_intr_enable_with_pri(int irq, uint32_t min_pri, int cpu) 724 { 725 plic_set_priority(irq, min_pri); 726 plic_set_threshold(cpu, min_pri-1); 727 plic_intr_route_grid(irq, IRQ_ENABLE, cpu); 728 } 729 730 void 731 plic_intr_disable(int irq, int cpu) 732 { 733 plic_set_priority(irq, 0); 734 plic_set_threshold(cpu, IPL_HIGH); 735 plic_intr_route_grid(irq, IRQ_DISABLE, cpu); 736 } 737 /***************** end of helper functions *****************/ 738