| 1 | /* $OpenBSD: intc.c,v 1.4 2016/01/31 00:14:50 jsg Exp $ */ |
| 2 | /* |
| 3 | * Copyright (c) 2007,2009 Dale Rahn <drahn@openbsd.org> |
| 4 | * |
| 5 | * Permission to use, copy, modify, and distribute this software for any |
| 6 | * purpose with or without fee is hereby granted, provided that the above |
| 7 | * copyright notice and this permission notice appear in all copies. |
| 8 | * |
| 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 16 | */ |
| 17 | |
| 18 | #include <sys/param.h> |
| 19 | #include <sys/systm.h> |
| 20 | #include <sys/queue.h> |
| 21 | #include <sys/malloc.h> |
| 22 | #include <sys/device.h> |
| 23 | #include <sys/evcount.h> |
| 24 | #include <machine/bus.h> |
| 25 | #include <armv7/armv7/armv7var.h> |
| 26 | #include "intc.h" |
| 27 | |
| 28 | #define INTC_NUM_IRQ intc_nirq |
| 29 | #define INTC_NUM_BANKS (intc_nirq/32) |
| 30 | #define INTC_MAX_IRQ 128 |
| 31 | #define INTC_MAX_BANKS (INTC_MAX_IRQ/32) |
| 32 | |
| 33 | /* registers */ |
| 34 | #define INTC_REVISION 0x00 /* R */ |
| 35 | #define INTC_SYSCONFIG 0x10 /* RW */ |
| 36 | #define INTC_SYSCONFIG_AUTOIDLE 0x1 |
| 37 | #define INTC_SYSCONFIG_SOFTRESET 0x2 |
| 38 | #define INTC_SYSSTATUS 0x14 /* R */ |
| 39 | #define INTC_SYSSYSTATUS_RESETDONE 0x1 |
| 40 | #define INTC_SIR_IRQ 0x40 /* R */ |
| 41 | #define INTC_SIR_FIQ 0x44 /* R */ |
| 42 | #define INTC_CONTROL 0x48 /* RW */ |
| 43 | #define INTC_CONTROL_NEWIRQ 0x1 |
| 44 | #define INTC_CONTROL_NEWFIQ 0x2 |
| 45 | #define INTC_CONTROL_GLOBALMASK 0x1 |
| 46 | #define INTC_PROTECTION 0x4c /* RW */ |
| 47 | #define INTC_PROTECTION_PROT 1 /* only privileged mode */ |
| 48 | #define INTC_IDLE 0x50 /* RW */ |
| 49 | |
| 50 | #define INTC_IRQ_TO_REG(i) (((i) >> 5) & 0x3) |
| 51 | #define INTC_IRQ_TO_REGi(i) ((i) & 0x1f) |
| 52 | #define INTC_ITRn(i) 0x80+(0x20*i)+0x00 /* R */ |
| 53 | #define INTC_MIRn(i) 0x80+(0x20*i)+0x04 /* RW */ |
| 54 | #define INTC_CLEARn(i) 0x80+(0x20*i)+0x08 /* RW */ |
| 55 | #define INTC_SETn(i) 0x80+(0x20*i)+0x0c /* RW */ |
| 56 | #define INTC_ISR_SETn(i) 0x80+(0x20*i)+0x10 /* RW */ |
| 57 | #define INTC_ISR_CLEARn(i) 0x80+(0x20*i)+0x14 /* RW */ |
| 58 | #define INTC_PENDING_IRQn(i) 0x80+(0x20*i)+0x18 /* R */ |
| 59 | #define INTC_PENDING_FIQn(i) 0x80+(0x20*i)+0x1c /* R */ |
| 60 | |
| 61 | #define INTC_ILRn(i) 0x100+(4*i) |
| 62 | #define INTC_ILR_IRQ 0x0 /* not of FIQ */ |
| 63 | #define INTC_ILR_FIQ 0x1 |
| 64 | #define INTC_ILR_PRIs(pri) ((pri) << 2) |
| 65 | #define INTC_ILR_PRI(reg) (((reg) >> 2) & 0x2f) |
| 66 | #define INTC_MIN_PRI 63 |
| 67 | #define INTC_STD_PRI 32 |
| 68 | #define INTC_MAX_PRI 0 |
| 69 | |
| 70 | struct intrhand { |
| 71 | TAILQ_ENTRY(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_irq; /* IRQ number */ |
| 76 | struct evcount ih_count; |
| 77 | char *ih_name; |
| 78 | }; |
| 79 | |
| 80 | struct intrq { |
| 81 | TAILQ_HEAD(, intrhand) iq_list; /* handler list */ |
| 82 | int iq_irq; /* IRQ to mask while handling */ |
| 83 | int iq_levels; /* IPL_*'s this IRQ has */ |
| 84 | int iq_ist; /* share type */ |
| 85 | }; |
| 86 | |
| 87 | volatile int softint_pending; |
| 88 | |
| 89 | struct intrq intc_handler[INTC_MAX_IRQ]; |
| 90 | u_int32_t intc_smask[NIPL]; |
| 91 | u_int32_t intc_imask[INTC_MAX_BANKS][NIPL]; |
| 92 | |
| 93 | bus_space_tag_t intc_iot; |
| 94 | bus_space_handle_t intc_ioh; |
| 95 | int intc_nirq; |
| 96 | |
| 97 | void intc_attach(struct device *, struct device *, void *); |
| 98 | int intc_spllower(int new); |
| 99 | int intc_splraise(int new); |
| 100 | void intc_setipl(int new); |
| 101 | void intc_calc_mask(void); |
| 102 | |
| 103 | struct cfattach intc_ca = { |
| 104 | sizeof (struct device), NULL, intc_attach |
| 105 | }; |
| 106 | |
| 107 | struct cfdriver intc_cd = { |
| 108 | NULL, "intc", DV_DULL |
| 109 | }; |
| 110 | |
| 111 | int intc_attached = 0; |
| 112 | |
| 113 | void |
| 114 | intc_attach(struct device *parent, struct device *self, void *args) |
| 115 | { |
| 116 | struct armv7_attach_args *aa = args; |
| 117 | int i; |
| 118 | u_int32_t rev; |
| 119 | |
| 120 | intc_iot = aa->aa_iot; |
| 121 | if (bus_space_map(intc_iot, aa->aa_dev->mem[0].addr, |
| 122 | aa->aa_dev->mem[0].size, 0, &intc_ioh)) |
| 123 | panic("intc_attach: bus_space_map failed!"); |
| 124 | |
| 125 | rev = bus_space_read_4(intc_iot, intc_ioh, INTC_REVISION); |
| 126 | |
| 127 | printf(" rev %d.%d\n", rev >> 4 & 0xf, rev & 0xf); |
| 128 | |
| 129 | /* software reset of the part? */ |
| 130 | /* set protection bit (kernel only)? */ |
| 131 | #if 0 |
| 132 | bus_space_write_4(intc_iot, intc_ioh, INTC_PROTECTION, |
| 133 | INTC_PROTECTION_PROT); |
| 134 | #endif |
| 135 | |
| 136 | /* enable interface clock power saving mode */ |
| 137 | bus_space_write_4(intc_iot, intc_ioh, INTC_SYSCONFIG, |
| 138 | INTC_SYSCONFIG_AUTOIDLE); |
| 139 | |
| 140 | switch (board_id) { |
| 141 | case BOARD_ID_AM335X_BEAGLEBONE: |
| 142 | intc_nirq = 128; |
| 143 | break; |
| 144 | default: |
| 145 | intc_nirq = 96; |
| 146 | break; |
| 147 | } |
| 148 | |
| 149 | /* mask all interrupts */ |
| 150 | for (i = 0; i < INTC_NUM_BANKS; i++) |
| 151 | bus_space_write_4(intc_iot, intc_ioh, INTC_MIRn(i), 0xffffffff); |
| 152 | |
| 153 | for (i = 0; i < INTC_NUM_IRQ; i++) { |
| 154 | bus_space_write_4(intc_iot, intc_ioh, INTC_ILRn(i), |
| 155 | INTC_ILR_PRIs(INTC_MIN_PRI)|INTC_ILR_IRQ); |
| 156 | |
| 157 | TAILQ_INIT(&intc_handler[i].iq_list); |
| 158 | } |
| 159 | |
| 160 | intc_calc_mask(); |
| 161 | bus_space_write_4(intc_iot, intc_ioh, INTC_CONTROL, |
| 162 | INTC_CONTROL_NEWIRQ); |
| 163 | |
| 164 | intc_attached = 1; |
| 165 | |
| 166 | /* insert self as interrupt handler */ |
| 167 | arm_set_intr_handler(intc_splraise, intc_spllower, intc_splx, |
| 168 | intc_setipl, |
| 169 | intc_intr_establish, intc_intr_disestablish, intc_intr_string, |
| 170 | intc_irq_handler); |
| 171 | |
| 172 | intc_setipl(IPL_HIGH); /* XXX ??? */ |
| 173 | enable_interrupts(PSR_I); |
| 174 | } |
| 175 | |
| 176 | void |
| 177 | intc_calc_mask(void) |
| 178 | { |
| 179 | struct cpu_info *ci = curcpu(); |
| 180 | int irq; |
| 181 | struct intrhand *ih; |
| 182 | int i; |
| 183 | |
| 184 | for (irq = 0; irq < INTC_NUM_IRQ; irq++) { |
| 185 | int max = IPL_NONE; |
| 186 | int min = IPL_HIGH; |
| 187 | TAILQ_FOREACH(ih, &intc_handler[irq].iq_list, ih_list) { |
| 188 | if (ih->ih_ipl > max) |
| 189 | max = ih->ih_ipl; |
| 190 | |
| 191 | if (ih->ih_ipl < min) |
| 192 | min = ih->ih_ipl; |
| 193 | } |
| 194 | |
| 195 | intc_handler[irq].iq_irq = max; |
| 196 | |
| 197 | if (max == IPL_NONE) |
| 198 | min = IPL_NONE; |
| 199 | |
| 200 | #ifdef DEBUG_INTC |
| 201 | if (min != IPL_NONE) { |
| 202 | printf("irq %d to block at %d %d reg %d bit %d\n", |
| 203 | irq, max, min, INTC_IRQ_TO_REG(irq), |
| 204 | INTC_IRQ_TO_REGi(irq)); |
| 205 | } |
| 206 | #endif |
| 207 | /* Enable interrupts at lower levels, clear -> enable */ |
| 208 | for (i = 0; i < min; i++) |
| 209 | intc_imask[INTC_IRQ_TO_REG(irq)][i] &= |
| 210 | ~(1 << INTC_IRQ_TO_REGi(irq)); |
| 211 | for (; i <= IPL_HIGH; i++) |
| 212 | intc_imask[INTC_IRQ_TO_REG(irq)][i] |= |
| 213 | 1 << INTC_IRQ_TO_REGi(irq); |
| 214 | /* XXX - set enable/disable, priority */ |
| 215 | bus_space_write_4(intc_iot, intc_ioh, INTC_ILRn(irq), |
| 216 | INTC_ILR_PRIs(NIPL-max)|INTC_ILR_IRQ); |
| 217 | } |
| 218 | arm_init_smask(); |
| 219 | intc_setipl(ci->ci_cpl); |
| 220 | } |
| 221 | |
| 222 | void |
| 223 | intc_splx(int new) |
| 224 | { |
| 225 | struct cpu_info *ci = curcpu(); |
| 226 | intc_setipl(new); |
| 227 | |
| 228 | if (ci->ci_ipending & arm_smask[ci->ci_cpl]) |
| 229 | arm_do_pending_intr(ci->ci_cpl); |
| 230 | } |
| 231 | |
| 232 | int |
| 233 | intc_spllower(int new) |
| 234 | { |
| 235 | struct cpu_info *ci = curcpu(); |
| 236 | int old = ci->ci_cpl; |
| 237 | intc_splx(new); |
| 238 | return (old); |
| 239 | } |
| 240 | |
| 241 | int |
| 242 | intc_splraise(int new) |
| 243 | { |
| 244 | struct cpu_info *ci = curcpu(); |
| 245 | int old; |
| 246 | old = ci->ci_cpl; |
| 247 | |
| 248 | /* |
| 249 | * setipl must always be called because there is a race window |
| 250 | * where the variable is updated before the mask is set |
| 251 | * an interrupt occurs in that window without the mask always |
| 252 | * being set, the hardware might not get updated on the next |
| 253 | * splraise completely messing up spl protection. |
| 254 | */ |
| 255 | if (old > new) |
| 256 | new = old; |
| 257 | |
| 258 | intc_setipl(new); |
| 259 | |
| 260 | return (old); |
| 261 | } |
| 262 | |
| 263 | void |
| 264 | intc_setipl(int new) |
| 265 | { |
| 266 | struct cpu_info *ci = curcpu(); |
| 267 | int i; |
| 268 | int psw; |
| 269 | if (intc_attached == 0) |
| 270 | return; |
| 271 | |
| 272 | psw = disable_interrupts(PSR_I); |
| 273 | #if 0 |
| 274 | { |
| 275 | volatile static int recursed = 0; |
| 276 | if (recursed == 0) { |
| 277 | recursed = 1; |
| 278 | if (new != 12) |
| 279 | printf("setipl %d\n", new); |
| 280 | recursed = 0; |
| 281 | } |
| 282 | } |
| 283 | #endif |
| 284 | ci->ci_cpl = new; |
| 285 | for (i = 0; i < INTC_NUM_BANKS; i++) |
| 286 | bus_space_write_4(intc_iot, intc_ioh, |
| 287 | INTC_MIRn(i), intc_imask[i][new]); |
| 288 | bus_space_write_4(intc_iot, intc_ioh, INTC_CONTROL, |
| 289 | INTC_CONTROL_NEWIRQ); |
| 290 | restore_interrupts(psw); |
| 291 | } |
| 292 | |
| 293 | void |
| 294 | intc_intr_bootstrap(vaddr_t addr) |
| 295 | { |
| 296 | int i, j; |
| 297 | extern struct bus_space armv7_bs_tag; |
| 298 | intc_iot = &armv7_bs_tag; |
| 299 | intc_ioh = addr; |
| 300 | for (i = 0; i < INTC_NUM_BANKS; i++) |
| 301 | for (j = 0; j < NIPL; j++) |
| 302 | intc_imask[i][j] = 0xffffffff; |
| 303 | } |
| 304 | |
| 305 | void |
| 306 | intc_irq_handler(void *frame) |
| 307 | { |
| 308 | int irq, pri, s; |
| 309 | struct intrhand *ih; |
| 310 | void *arg; |
| 311 | |
| 312 | irq = bus_space_read_4(intc_iot, intc_ioh, INTC_SIR_IRQ); |
| 313 | #ifdef DEBUG_INTC |
| 314 | printf("irq %d fired\n", irq); |
| 315 | #endif |
| 316 | |
| 317 | pri = intc_handler[irq].iq_irq; |
| 318 | s = intc_splraise(pri); |
| 319 | TAILQ_FOREACH(ih, &intc_handler[irq].iq_list, ih_list) { |
| 320 | if (ih->ih_arg != 0) |
| 321 | arg = ih->ih_arg; |
| 322 | else |
| 323 | arg = frame; |
| 324 | |
| 325 | if (ih->ih_func(arg)) |
| 326 | ih->ih_count.ec_count++; |
| 327 | |
| 328 | } |
| 329 | bus_space_write_4(intc_iot, intc_ioh, INTC_CONTROL, |
| 330 | INTC_CONTROL_NEWIRQ); |
| 331 | |
| 332 | intc_splx(s); |
| 333 | } |
| 334 | |
| 335 | void * |
| 336 | intc_intr_establish(int irqno, int level, int (*func)(void *), |
| 337 | void *arg, char *name) |
| 338 | { |
| 339 | int psw; |
| 340 | struct intrhand *ih; |
| 341 | |
| 342 | if (irqno < 0 || irqno >= INTC_NUM_IRQ) |
| 343 | panic("intc_intr_establish: bogus irqnumber %d: %s", |
| 344 | irqno, name); |
| 345 | psw = disable_interrupts(PSR_I); |
| 346 | |
| 347 | /* no point in sleeping unless someone can free memory. */ |
| 348 | ih = (struct intrhand *)malloc (sizeof *ih, M_DEVBUF, |
| 349 | cold ? M_NOWAIT : M_WAITOK); |
| 350 | if (ih == NULL) |
| 351 | panic("intr_establish: can't malloc handler info"); |
| 352 | ih->ih_func = func; |
| 353 | ih->ih_arg = arg; |
| 354 | ih->ih_ipl = level; |
| 355 | ih->ih_irq = irqno; |
| 356 | ih->ih_name = name; |
| 357 | |
| 358 | TAILQ_INSERT_TAIL(&intc_handler[irqno].iq_list, ih, ih_list); |
| 359 | |
| 360 | if (name != NULL) |
| 361 | evcount_attach(&ih->ih_count, name, &ih->ih_irq); |
| 362 | |
| 363 | #ifdef DEBUG_INTC |
| 364 | printf("intc_intr_establish irq %d level %d [%s]\n", irqno, level, |
| 365 | name); |
| 366 | #endif |
| 367 | intc_calc_mask(); |
| 368 | |
| 369 | restore_interrupts(psw); |
| 370 | return (ih); |
| 371 | } |
| 372 | |
| 373 | void |
| 374 | intc_intr_disestablish(void *cookie) |
| 375 | { |
| 376 | int psw; |
| 377 | struct intrhand *ih = cookie; |
| 378 | int irqno = ih->ih_irq; |
| 379 | psw = disable_interrupts(PSR_I); |
| 380 | TAILQ_REMOVE(&intc_handler[irqno].iq_list, ih, ih_list); |
| 381 | if (ih->ih_name != NULL) |
| 382 | evcount_detach(&ih->ih_count); |
| 383 | free(ih, M_DEVBUF, 0); |
| 384 | restore_interrupts(psw); |
| 385 | } |
| 386 | |
| 387 | const char * |
| 388 | intc_intr_string(void *cookie) |
| 389 | { |
| 390 | return "huh?"; |
| 391 | } |
| 392 | |
| 393 | |
| 394 | #if 0 |
| 395 | int intc_tst(void *a); |
| 396 | |
| 397 | int |
| 398 | intc_tst(void *a) |
| 399 | { |
| 400 | printf("inct_tst called\n"); |
| 401 | bus_space_write_4(intc_iot, intc_ioh, INTC_ISR_CLEARn(0), 2); |
| 402 | return 1; |
| 403 | } |
| 404 | |
| 405 | void intc_test(void); |
| 406 | void intc_test(void) |
| 407 | { |
| 408 | void * ih; |
| 409 | printf("about to register handler\n"); |
| 410 | ih = intc_intr_establish(1, IPL_BIO, intc_tst, NULL, "intctst"); |
| 411 | |
| 412 | printf("about to set bit\n"); |
| 413 | bus_space_write_4(intc_iot, intc_ioh, INTC_ISR_SETn(0), 2); |
| 414 | |
| 415 | printf("about to clear bit\n"); |
| 416 | bus_space_write_4(intc_iot, intc_ioh, INTC_ISR_CLEARn(0), 2); |
| 417 | |
| 418 | printf("about to remove handler\n"); |
| 419 | intc_intr_disestablish(ih); |
| 420 | |
| 421 | printf("done\n"); |
| 422 | } |
| 423 | #endif |