| 1 | /* Blackfin Core Event Controller (CEC) model. |
| 2 | |
| 3 | Copyright (C) 2010-2014 Free Software Foundation, Inc. |
| 4 | Contributed by Analog Devices, Inc. |
| 5 | |
| 6 | This file is part of simulators. |
| 7 | |
| 8 | This program is free software; you can redistribute it and/or modify |
| 9 | it under the terms of the GNU General Public License as published by |
| 10 | the Free Software Foundation; either version 3 of the License, or |
| 11 | (at your option) any later version. |
| 12 | |
| 13 | This program is distributed in the hope that it will be useful, |
| 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | GNU General Public License for more details. |
| 17 | |
| 18 | You should have received a copy of the GNU General Public License |
| 19 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| 20 | |
| 21 | #include "config.h" |
| 22 | |
| 23 | #include "sim-main.h" |
| 24 | #include "devices.h" |
| 25 | #include "dv-bfin_cec.h" |
| 26 | #include "dv-bfin_evt.h" |
| 27 | #include "dv-bfin_mmu.h" |
| 28 | |
| 29 | struct bfin_cec |
| 30 | { |
| 31 | bu32 base; |
| 32 | SIM_CPU *cpu; |
| 33 | struct hw *me; |
| 34 | struct hw_event *pending; |
| 35 | |
| 36 | /* Order after here is important -- matches hardware MMR layout. */ |
| 37 | bu32 evt_override, imask, ipend, ilat, iprio; |
| 38 | }; |
| 39 | #define mmr_base() offsetof(struct bfin_cec, evt_override) |
| 40 | #define mmr_offset(mmr) (offsetof(struct bfin_cec, mmr) - mmr_base()) |
| 41 | |
| 42 | static const char * const mmr_names[] = |
| 43 | { |
| 44 | "EVT_OVERRIDE", "IMASK", "IPEND", "ILAT", "IPRIO", |
| 45 | }; |
| 46 | #define mmr_name(off) mmr_names[(off) / 4] |
| 47 | |
| 48 | static void _cec_raise (SIM_CPU *, struct bfin_cec *, int); |
| 49 | |
| 50 | static void |
| 51 | bfin_cec_hw_event_callback (struct hw *me, void *data) |
| 52 | { |
| 53 | struct bfin_cec *cec = data; |
| 54 | hw_event_queue_deschedule (me, cec->pending); |
| 55 | _cec_raise (cec->cpu, cec, -1); |
| 56 | cec->pending = NULL; |
| 57 | } |
| 58 | static void |
| 59 | bfin_cec_check_pending (struct hw *me, struct bfin_cec *cec) |
| 60 | { |
| 61 | if (cec->pending) |
| 62 | return; |
| 63 | cec->pending = hw_event_queue_schedule (me, 0, bfin_cec_hw_event_callback, cec); |
| 64 | } |
| 65 | static void |
| 66 | _cec_check_pending (SIM_CPU *cpu, struct bfin_cec *cec) |
| 67 | { |
| 68 | bfin_cec_check_pending (cec->me, cec); |
| 69 | } |
| 70 | |
| 71 | static void |
| 72 | _cec_imask_write (struct bfin_cec *cec, bu32 value) |
| 73 | { |
| 74 | cec->imask = (value & IVG_MASKABLE_B) | (cec->imask & IVG_UNMASKABLE_B); |
| 75 | } |
| 76 | |
| 77 | static unsigned |
| 78 | bfin_cec_io_write_buffer (struct hw *me, const void *source, |
| 79 | int space, address_word addr, unsigned nr_bytes) |
| 80 | { |
| 81 | struct bfin_cec *cec = hw_data (me); |
| 82 | bu32 mmr_off; |
| 83 | bu32 value; |
| 84 | |
| 85 | value = dv_load_4 (source); |
| 86 | mmr_off = addr - cec->base; |
| 87 | |
| 88 | HW_TRACE_WRITE (); |
| 89 | |
| 90 | switch (mmr_off) |
| 91 | { |
| 92 | case mmr_offset(evt_override): |
| 93 | cec->evt_override = value; |
| 94 | break; |
| 95 | case mmr_offset(imask): |
| 96 | _cec_imask_write (cec, value); |
| 97 | bfin_cec_check_pending (me, cec); |
| 98 | break; |
| 99 | case mmr_offset(ipend): |
| 100 | /* Read-only register. */ |
| 101 | break; |
| 102 | case mmr_offset(ilat): |
| 103 | dv_w1c_4 (&cec->ilat, value, 0xffee); |
| 104 | break; |
| 105 | case mmr_offset(iprio): |
| 106 | cec->iprio = (value & IVG_UNMASKABLE_B); |
| 107 | break; |
| 108 | } |
| 109 | |
| 110 | return nr_bytes; |
| 111 | } |
| 112 | |
| 113 | static unsigned |
| 114 | bfin_cec_io_read_buffer (struct hw *me, void *dest, |
| 115 | int space, address_word addr, unsigned nr_bytes) |
| 116 | { |
| 117 | struct bfin_cec *cec = hw_data (me); |
| 118 | bu32 mmr_off; |
| 119 | bu32 *valuep; |
| 120 | |
| 121 | mmr_off = addr - cec->base; |
| 122 | valuep = (void *)((unsigned long)cec + mmr_base() + mmr_off); |
| 123 | |
| 124 | HW_TRACE_READ (); |
| 125 | |
| 126 | dv_store_4 (dest, *valuep); |
| 127 | |
| 128 | return nr_bytes; |
| 129 | } |
| 130 | |
| 131 | static const struct hw_port_descriptor bfin_cec_ports[] = |
| 132 | { |
| 133 | { "emu", IVG_EMU, 0, input_port, }, |
| 134 | { "rst", IVG_RST, 0, input_port, }, |
| 135 | { "nmi", IVG_NMI, 0, input_port, }, |
| 136 | { "evx", IVG_EVX, 0, input_port, }, |
| 137 | { "ivhw", IVG_IVHW, 0, input_port, }, |
| 138 | { "ivtmr", IVG_IVTMR, 0, input_port, }, |
| 139 | { "ivg7", IVG7, 0, input_port, }, |
| 140 | { "ivg8", IVG8, 0, input_port, }, |
| 141 | { "ivg9", IVG9, 0, input_port, }, |
| 142 | { "ivg10", IVG10, 0, input_port, }, |
| 143 | { "ivg11", IVG11, 0, input_port, }, |
| 144 | { "ivg12", IVG12, 0, input_port, }, |
| 145 | { "ivg13", IVG13, 0, input_port, }, |
| 146 | { "ivg14", IVG14, 0, input_port, }, |
| 147 | { "ivg15", IVG15, 0, input_port, }, |
| 148 | { NULL, 0, 0, 0, }, |
| 149 | }; |
| 150 | |
| 151 | static void |
| 152 | bfin_cec_port_event (struct hw *me, int my_port, struct hw *source, |
| 153 | int source_port, int level) |
| 154 | { |
| 155 | struct bfin_cec *cec = hw_data (me); |
| 156 | _cec_raise (cec->cpu, cec, my_port); |
| 157 | } |
| 158 | |
| 159 | static void |
| 160 | attach_bfin_cec_regs (struct hw *me, struct bfin_cec *cec) |
| 161 | { |
| 162 | address_word attach_address; |
| 163 | int attach_space; |
| 164 | unsigned attach_size; |
| 165 | reg_property_spec reg; |
| 166 | |
| 167 | if (hw_find_property (me, "reg") == NULL) |
| 168 | hw_abort (me, "Missing \"reg\" property"); |
| 169 | |
| 170 | if (!hw_find_reg_array_property (me, "reg", 0, ®)) |
| 171 | hw_abort (me, "\"reg\" property must contain three addr/size entries"); |
| 172 | |
| 173 | hw_unit_address_to_attach_address (hw_parent (me), |
| 174 | ®.address, |
| 175 | &attach_space, &attach_address, me); |
| 176 | hw_unit_size_to_attach_size (hw_parent (me), ®.size, &attach_size, me); |
| 177 | |
| 178 | if (attach_size != BFIN_COREMMR_CEC_SIZE) |
| 179 | hw_abort (me, "\"reg\" size must be %#x", BFIN_COREMMR_CEC_SIZE); |
| 180 | |
| 181 | hw_attach_address (hw_parent (me), |
| 182 | 0, attach_space, attach_address, attach_size, me); |
| 183 | |
| 184 | cec->base = attach_address; |
| 185 | /* XXX: should take from the device tree. */ |
| 186 | cec->cpu = STATE_CPU (hw_system (me), 0); |
| 187 | cec->me = me; |
| 188 | } |
| 189 | |
| 190 | static void |
| 191 | bfin_cec_finish (struct hw *me) |
| 192 | { |
| 193 | struct bfin_cec *cec; |
| 194 | |
| 195 | cec = HW_ZALLOC (me, struct bfin_cec); |
| 196 | |
| 197 | set_hw_data (me, cec); |
| 198 | set_hw_io_read_buffer (me, bfin_cec_io_read_buffer); |
| 199 | set_hw_io_write_buffer (me, bfin_cec_io_write_buffer); |
| 200 | set_hw_ports (me, bfin_cec_ports); |
| 201 | set_hw_port_event (me, bfin_cec_port_event); |
| 202 | |
| 203 | attach_bfin_cec_regs (me, cec); |
| 204 | |
| 205 | /* Initialize the CEC. */ |
| 206 | cec->imask = IVG_UNMASKABLE_B; |
| 207 | cec->ipend = IVG_RST_B | IVG_IRPTEN_B; |
| 208 | } |
| 209 | |
| 210 | const struct hw_descriptor dv_bfin_cec_descriptor[] = |
| 211 | { |
| 212 | {"bfin_cec", bfin_cec_finish,}, |
| 213 | {NULL, NULL}, |
| 214 | }; |
| 215 | |
| 216 | static const char * const excp_decoded[] = |
| 217 | { |
| 218 | [VEC_SYS ] = "Custom exception 0 (system call)", |
| 219 | [VEC_EXCPT01 ] = "Custom exception 1 (software breakpoint)", |
| 220 | [VEC_EXCPT02 ] = "Custom exception 2 (KGDB hook)", |
| 221 | [VEC_EXCPT03 ] = "Custom exception 3 (userspace stack overflow)", |
| 222 | [VEC_EXCPT04 ] = "Custom exception 4 (dump trace buffer)", |
| 223 | [VEC_EXCPT05 ] = "Custom exception 5", |
| 224 | [VEC_EXCPT06 ] = "Custom exception 6", |
| 225 | [VEC_EXCPT07 ] = "Custom exception 7", |
| 226 | [VEC_EXCPT08 ] = "Custom exception 8", |
| 227 | [VEC_EXCPT09 ] = "Custom exception 9", |
| 228 | [VEC_EXCPT10 ] = "Custom exception 10", |
| 229 | [VEC_EXCPT11 ] = "Custom exception 11", |
| 230 | [VEC_EXCPT12 ] = "Custom exception 12", |
| 231 | [VEC_EXCPT13 ] = "Custom exception 13", |
| 232 | [VEC_EXCPT14 ] = "Custom exception 14", |
| 233 | [VEC_EXCPT15 ] = "Custom exception 15", |
| 234 | [VEC_STEP ] = "Hardware single step", |
| 235 | [VEC_OVFLOW ] = "Trace buffer overflow", |
| 236 | [VEC_UNDEF_I ] = "Undefined instruction", |
| 237 | [VEC_ILGAL_I ] = "Illegal instruction combo (multi-issue)", |
| 238 | [VEC_CPLB_VL ] = "DCPLB protection violation", |
| 239 | [VEC_MISALI_D ] = "Unaligned data access", |
| 240 | [VEC_UNCOV ] = "Unrecoverable event (double fault)", |
| 241 | [VEC_CPLB_M ] = "DCPLB miss", |
| 242 | [VEC_CPLB_MHIT ] = "Multiple DCPLB hit", |
| 243 | [VEC_WATCH ] = "Watchpoint match", |
| 244 | [VEC_ISTRU_VL ] = "ADSP-BF535 only", |
| 245 | [VEC_MISALI_I ] = "Unaligned instruction access", |
| 246 | [VEC_CPLB_I_VL ] = "ICPLB protection violation", |
| 247 | [VEC_CPLB_I_M ] = "ICPLB miss", |
| 248 | [VEC_CPLB_I_MHIT] = "Multiple ICPLB hit", |
| 249 | [VEC_ILL_RES ] = "Illegal supervisor resource", |
| 250 | }; |
| 251 | |
| 252 | #define CEC_STATE(cpu) DV_STATE_CACHED (cpu, cec) |
| 253 | |
| 254 | #define __cec_get_ivg(val) (ffs ((val) & ~IVG_IRPTEN_B) - 1) |
| 255 | #define _cec_get_ivg(cec) __cec_get_ivg ((cec)->ipend & ~IVG_EMU_B) |
| 256 | |
| 257 | int |
| 258 | cec_get_ivg (SIM_CPU *cpu) |
| 259 | { |
| 260 | switch (STATE_ENVIRONMENT (CPU_STATE (cpu))) |
| 261 | { |
| 262 | case OPERATING_ENVIRONMENT: |
| 263 | return _cec_get_ivg (CEC_STATE (cpu)); |
| 264 | default: |
| 265 | return IVG_USER; |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | static bool |
| 270 | _cec_is_supervisor_mode (struct bfin_cec *cec) |
| 271 | { |
| 272 | return (cec->ipend & ~(IVG_EMU_B | IVG_IRPTEN_B)); |
| 273 | } |
| 274 | bool |
| 275 | cec_is_supervisor_mode (SIM_CPU *cpu) |
| 276 | { |
| 277 | switch (STATE_ENVIRONMENT (CPU_STATE (cpu))) |
| 278 | { |
| 279 | case OPERATING_ENVIRONMENT: |
| 280 | return _cec_is_supervisor_mode (CEC_STATE (cpu)); |
| 281 | case USER_ENVIRONMENT: |
| 282 | return false; |
| 283 | default: |
| 284 | return true; |
| 285 | } |
| 286 | } |
| 287 | static bool |
| 288 | _cec_is_user_mode (struct bfin_cec *cec) |
| 289 | { |
| 290 | return !_cec_is_supervisor_mode (cec); |
| 291 | } |
| 292 | bool |
| 293 | cec_is_user_mode (SIM_CPU *cpu) |
| 294 | { |
| 295 | return !cec_is_supervisor_mode (cpu); |
| 296 | } |
| 297 | static void |
| 298 | _cec_require_supervisor (SIM_CPU *cpu, struct bfin_cec *cec) |
| 299 | { |
| 300 | if (_cec_is_user_mode (cec)) |
| 301 | cec_exception (cpu, VEC_ILL_RES); |
| 302 | } |
| 303 | void |
| 304 | cec_require_supervisor (SIM_CPU *cpu) |
| 305 | { |
| 306 | /* Do not call _cec_require_supervisor() to avoid CEC_STATE() |
| 307 | as that macro requires OS operating mode. */ |
| 308 | if (cec_is_user_mode (cpu)) |
| 309 | cec_exception (cpu, VEC_ILL_RES); |
| 310 | } |
| 311 | |
| 312 | #define excp_to_sim_halt(reason, sigrc) \ |
| 313 | sim_engine_halt (CPU_STATE (cpu), cpu, NULL, PCREG, reason, sigrc) |
| 314 | void |
| 315 | cec_exception (SIM_CPU *cpu, int excp) |
| 316 | { |
| 317 | SIM_DESC sd = CPU_STATE (cpu); |
| 318 | int sigrc = -1; |
| 319 | |
| 320 | TRACE_EVENTS (cpu, "processing exception %#x in EVT%i", excp, |
| 321 | cec_get_ivg (cpu)); |
| 322 | |
| 323 | /* Ideally what would happen here for real hardware exceptions (not |
| 324 | fake sim ones) is that: |
| 325 | - For service exceptions (excp <= 0x11): |
| 326 | RETX is the _next_ PC which can be tricky with jumps/hardware loops/... |
| 327 | - For error exceptions (excp > 0x11): |
| 328 | RETX is the _current_ PC (i.e. the one causing the exception) |
| 329 | - PC is loaded with EVT3 MMR |
| 330 | - ILAT/IPEND in CEC is updated depending on current IVG level |
| 331 | - the fault address MMRs get updated with data/instruction info |
| 332 | - Execution continues on in the EVT3 handler */ |
| 333 | |
| 334 | /* Handle simulator exceptions first. */ |
| 335 | switch (excp) |
| 336 | { |
| 337 | case VEC_SIM_HLT: |
| 338 | excp_to_sim_halt (sim_exited, 0); |
| 339 | return; |
| 340 | case VEC_SIM_ABORT: |
| 341 | excp_to_sim_halt (sim_exited, 1); |
| 342 | return; |
| 343 | case VEC_SIM_TRAP: |
| 344 | /* GDB expects us to step over EMUEXCPT. */ |
| 345 | /* XXX: What about hwloops and EMUEXCPT at the end? |
| 346 | Pretty sure gdb doesn't handle this already... */ |
| 347 | SET_PCREG (PCREG + 2); |
| 348 | /* Only trap when we are running in gdb. */ |
| 349 | if (STATE_OPEN_KIND (sd) == SIM_OPEN_DEBUG) |
| 350 | excp_to_sim_halt (sim_stopped, SIM_SIGTRAP); |
| 351 | return; |
| 352 | case VEC_SIM_DBGA: |
| 353 | /* If running in gdb, simply trap. */ |
| 354 | if (STATE_OPEN_KIND (sd) == SIM_OPEN_DEBUG) |
| 355 | excp_to_sim_halt (sim_stopped, SIM_SIGTRAP); |
| 356 | else |
| 357 | excp_to_sim_halt (sim_exited, 2); |
| 358 | } |
| 359 | |
| 360 | if (excp <= 0x3f) |
| 361 | { |
| 362 | SET_EXCAUSE (excp); |
| 363 | if (STATE_ENVIRONMENT (sd) == OPERATING_ENVIRONMENT) |
| 364 | { |
| 365 | /* ICPLB regs always get updated. */ |
| 366 | /* XXX: Should optimize this call path ... */ |
| 367 | if (excp != VEC_MISALI_I && excp != VEC_MISALI_D |
| 368 | && excp != VEC_CPLB_I_M && excp != VEC_CPLB_M |
| 369 | && excp != VEC_CPLB_I_VL && excp != VEC_CPLB_VL |
| 370 | && excp != VEC_CPLB_I_MHIT && excp != VEC_CPLB_MHIT) |
| 371 | mmu_log_ifault (cpu); |
| 372 | _cec_raise (cpu, CEC_STATE (cpu), IVG_EVX); |
| 373 | /* We need to restart the engine so that we don't return |
| 374 | and continue processing this bad insn. */ |
| 375 | if (EXCAUSE >= 0x20) |
| 376 | sim_engine_restart (sd, cpu, NULL, PCREG); |
| 377 | return; |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | TRACE_EVENTS (cpu, "running virtual exception handler"); |
| 382 | |
| 383 | switch (excp) |
| 384 | { |
| 385 | case VEC_SYS: |
| 386 | bfin_syscall (cpu); |
| 387 | break; |
| 388 | |
| 389 | case VEC_EXCPT01: /* Userspace gdb breakpoint. */ |
| 390 | sigrc = SIM_SIGTRAP; |
| 391 | break; |
| 392 | |
| 393 | case VEC_UNDEF_I: /* Undefined instruction. */ |
| 394 | sigrc = SIM_SIGILL; |
| 395 | break; |
| 396 | |
| 397 | case VEC_ILL_RES: /* Illegal supervisor resource. */ |
| 398 | case VEC_MISALI_I: /* Misaligned instruction. */ |
| 399 | sigrc = SIM_SIGBUS; |
| 400 | break; |
| 401 | |
| 402 | case VEC_CPLB_M: |
| 403 | case VEC_CPLB_I_M: |
| 404 | sigrc = SIM_SIGSEGV; |
| 405 | break; |
| 406 | |
| 407 | default: |
| 408 | sim_io_eprintf (sd, "Unhandled exception %#x at 0x%08x (%s)\n", |
| 409 | excp, PCREG, excp_decoded[excp]); |
| 410 | sigrc = SIM_SIGILL; |
| 411 | break; |
| 412 | } |
| 413 | |
| 414 | if (sigrc != -1) |
| 415 | excp_to_sim_halt (sim_stopped, sigrc); |
| 416 | } |
| 417 | |
| 418 | bu32 cec_cli (SIM_CPU *cpu) |
| 419 | { |
| 420 | struct bfin_cec *cec; |
| 421 | bu32 old_mask; |
| 422 | |
| 423 | if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT) |
| 424 | return 0; |
| 425 | |
| 426 | cec = CEC_STATE (cpu); |
| 427 | _cec_require_supervisor (cpu, cec); |
| 428 | |
| 429 | /* XXX: what about IPEND[4] ? */ |
| 430 | old_mask = cec->imask; |
| 431 | _cec_imask_write (cec, 0); |
| 432 | |
| 433 | TRACE_EVENTS (cpu, "CLI changed IMASK from %#x to %#x", old_mask, cec->imask); |
| 434 | |
| 435 | return old_mask; |
| 436 | } |
| 437 | |
| 438 | void cec_sti (SIM_CPU *cpu, bu32 ints) |
| 439 | { |
| 440 | struct bfin_cec *cec; |
| 441 | bu32 old_mask; |
| 442 | |
| 443 | if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT) |
| 444 | return; |
| 445 | |
| 446 | cec = CEC_STATE (cpu); |
| 447 | _cec_require_supervisor (cpu, cec); |
| 448 | |
| 449 | /* XXX: what about IPEND[4] ? */ |
| 450 | old_mask = cec->imask; |
| 451 | _cec_imask_write (cec, ints); |
| 452 | |
| 453 | TRACE_EVENTS (cpu, "STI changed IMASK from %#x to %#x", old_mask, cec->imask); |
| 454 | |
| 455 | /* Check for pending interrupts that are now enabled. */ |
| 456 | _cec_check_pending (cpu, cec); |
| 457 | } |
| 458 | |
| 459 | static void |
| 460 | cec_irpten_enable (SIM_CPU *cpu, struct bfin_cec *cec) |
| 461 | { |
| 462 | /* Globally mask interrupts. */ |
| 463 | TRACE_EVENTS (cpu, "setting IPEND[4] to globally mask interrupts"); |
| 464 | cec->ipend |= IVG_IRPTEN_B; |
| 465 | } |
| 466 | |
| 467 | static void |
| 468 | cec_irpten_disable (SIM_CPU *cpu, struct bfin_cec *cec) |
| 469 | { |
| 470 | /* Clear global interrupt mask. */ |
| 471 | TRACE_EVENTS (cpu, "clearing IPEND[4] to not globally mask interrupts"); |
| 472 | cec->ipend &= ~IVG_IRPTEN_B; |
| 473 | } |
| 474 | |
| 475 | static void |
| 476 | _cec_raise (SIM_CPU *cpu, struct bfin_cec *cec, int ivg) |
| 477 | { |
| 478 | SIM_DESC sd = CPU_STATE (cpu); |
| 479 | int curr_ivg = _cec_get_ivg (cec); |
| 480 | bool snen; |
| 481 | bool irpten; |
| 482 | |
| 483 | TRACE_EVENTS (cpu, "processing request for EVT%i while at EVT%i", |
| 484 | ivg, curr_ivg); |
| 485 | |
| 486 | irpten = (cec->ipend & IVG_IRPTEN_B); |
| 487 | snen = (SYSCFGREG & SYSCFG_SNEN); |
| 488 | |
| 489 | if (curr_ivg == -1) |
| 490 | curr_ivg = IVG_USER; |
| 491 | |
| 492 | /* Just check for higher latched interrupts. */ |
| 493 | if (ivg == -1) |
| 494 | { |
| 495 | if (irpten) |
| 496 | goto done; /* All interrupts are masked anyways. */ |
| 497 | |
| 498 | ivg = __cec_get_ivg (cec->ilat & cec->imask); |
| 499 | if (ivg < 0) |
| 500 | goto done; /* Nothing latched. */ |
| 501 | |
| 502 | if (ivg > curr_ivg) |
| 503 | goto done; /* Nothing higher latched. */ |
| 504 | |
| 505 | if (!snen && ivg == curr_ivg) |
| 506 | goto done; /* Self nesting disabled. */ |
| 507 | |
| 508 | /* Still here, so fall through to raise to higher pending. */ |
| 509 | } |
| 510 | |
| 511 | cec->ilat |= (1 << ivg); |
| 512 | |
| 513 | if (ivg <= IVG_EVX) |
| 514 | { |
| 515 | /* These two are always processed. */ |
| 516 | if (ivg == IVG_EMU || ivg == IVG_RST) |
| 517 | goto process_int; |
| 518 | |
| 519 | /* Anything lower might trigger a double fault. */ |
| 520 | if (curr_ivg <= ivg) |
| 521 | { |
| 522 | /* Double fault ! :( */ |
| 523 | SET_EXCAUSE (VEC_UNCOV); |
| 524 | /* XXX: SET_RETXREG (...); */ |
| 525 | sim_io_error (sd, "%s: double fault at 0x%08x ! :(", __func__, PCREG); |
| 526 | excp_to_sim_halt (sim_stopped, SIM_SIGABRT); |
| 527 | } |
| 528 | |
| 529 | /* No double fault -> always process. */ |
| 530 | goto process_int; |
| 531 | } |
| 532 | else if (irpten && curr_ivg != IVG_USER) |
| 533 | { |
| 534 | /* Interrupts are globally masked. */ |
| 535 | } |
| 536 | else if (!(cec->imask & (1 << ivg))) |
| 537 | { |
| 538 | /* This interrupt is masked. */ |
| 539 | } |
| 540 | else if (ivg < curr_ivg || (snen && ivg == curr_ivg)) |
| 541 | { |
| 542 | /* Do transition! */ |
| 543 | bu32 oldpc; |
| 544 | |
| 545 | process_int: |
| 546 | cec->ipend |= (1 << ivg); |
| 547 | cec->ilat &= ~(1 << ivg); |
| 548 | |
| 549 | /* Interrupts are processed in between insns which means the return |
| 550 | point is the insn-to-be-executed (which is the current PC). But |
| 551 | exceptions are handled while executing an insn, so we may have to |
| 552 | advance the PC ourselves when setting RETX. |
| 553 | XXX: Advancing the PC should only be for "service" exceptions, and |
| 554 | handling them after executing the insn should be OK, which |
| 555 | means we might be able to use the event interface for it. */ |
| 556 | |
| 557 | oldpc = PCREG; |
| 558 | switch (ivg) |
| 559 | { |
| 560 | case IVG_EMU: |
| 561 | /* Signal the JTAG ICE. */ |
| 562 | /* XXX: what happens with 'raise 0' ? */ |
| 563 | SET_RETEREG (oldpc); |
| 564 | excp_to_sim_halt (sim_stopped, SIM_SIGTRAP); |
| 565 | /* XXX: Need an easy way for gdb to signal it isnt here. */ |
| 566 | cec->ipend &= ~IVG_EMU_B; |
| 567 | break; |
| 568 | case IVG_RST: |
| 569 | /* Have the core reset simply exit (i.e. "shutdown"). */ |
| 570 | excp_to_sim_halt (sim_exited, 0); |
| 571 | break; |
| 572 | case IVG_NMI: |
| 573 | /* XXX: Should check this. */ |
| 574 | SET_RETNREG (oldpc); |
| 575 | break; |
| 576 | case IVG_EVX: |
| 577 | /* Non-service exceptions point to the excepting instruction. */ |
| 578 | if (EXCAUSE >= 0x20) |
| 579 | SET_RETXREG (oldpc); |
| 580 | else |
| 581 | { |
| 582 | bu32 nextpc = hwloop_get_next_pc (cpu, oldpc, INSN_LEN); |
| 583 | SET_RETXREG (nextpc); |
| 584 | } |
| 585 | |
| 586 | break; |
| 587 | case IVG_IRPTEN: |
| 588 | /* XXX: what happens with 'raise 4' ? */ |
| 589 | sim_io_error (sd, "%s: what to do with 'raise 4' ?", __func__); |
| 590 | break; |
| 591 | default: |
| 592 | SET_RETIREG (oldpc | (ivg == curr_ivg ? 1 : 0)); |
| 593 | break; |
| 594 | } |
| 595 | |
| 596 | /* If EVT_OVERRIDE is in effect (IVG7+), use the reset address. */ |
| 597 | if ((cec->evt_override & 0xff80) & (1 << ivg)) |
| 598 | SET_PCREG (cec_get_reset_evt (cpu)); |
| 599 | else |
| 600 | SET_PCREG (cec_get_evt (cpu, ivg)); |
| 601 | |
| 602 | TRACE_BRANCH (cpu, oldpc, PCREG, -1, "CEC changed PC (to EVT%i):", ivg); |
| 603 | BFIN_CPU_STATE.did_jump = true; |
| 604 | |
| 605 | /* Enable the global interrupt mask upon interrupt entry. */ |
| 606 | if (ivg >= IVG_IVHW) |
| 607 | cec_irpten_enable (cpu, cec); |
| 608 | } |
| 609 | |
| 610 | /* When moving between states, don't let internal states bleed through. */ |
| 611 | DIS_ALGN_EXPT &= ~1; |
| 612 | |
| 613 | /* When going from user to super, we set LSB in LB regs to avoid |
| 614 | misbehavior and/or malicious code. |
| 615 | Also need to load SP alias with KSP. */ |
| 616 | if (curr_ivg == IVG_USER) |
| 617 | { |
| 618 | int i; |
| 619 | for (i = 0; i < 2; ++i) |
| 620 | if (!(LBREG (i) & 1)) |
| 621 | SET_LBREG (i, LBREG (i) | 1); |
| 622 | SET_USPREG (SPREG); |
| 623 | SET_SPREG (KSPREG); |
| 624 | } |
| 625 | |
| 626 | done: |
| 627 | TRACE_EVENTS (cpu, "now at EVT%i", _cec_get_ivg (cec)); |
| 628 | } |
| 629 | |
| 630 | static bu32 |
| 631 | cec_read_ret_reg (SIM_CPU *cpu, int ivg) |
| 632 | { |
| 633 | switch (ivg) |
| 634 | { |
| 635 | case IVG_EMU: return RETEREG; |
| 636 | case IVG_NMI: return RETNREG; |
| 637 | case IVG_EVX: return RETXREG; |
| 638 | default: return RETIREG; |
| 639 | } |
| 640 | } |
| 641 | |
| 642 | void |
| 643 | cec_latch (SIM_CPU *cpu, int ivg) |
| 644 | { |
| 645 | struct bfin_cec *cec; |
| 646 | |
| 647 | if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT) |
| 648 | { |
| 649 | bu32 oldpc = PCREG; |
| 650 | SET_PCREG (cec_read_ret_reg (cpu, ivg)); |
| 651 | TRACE_BRANCH (cpu, oldpc, PCREG, -1, "CEC changed PC"); |
| 652 | return; |
| 653 | } |
| 654 | |
| 655 | cec = CEC_STATE (cpu); |
| 656 | cec->ilat |= (1 << ivg); |
| 657 | _cec_check_pending (cpu, cec); |
| 658 | } |
| 659 | |
| 660 | void |
| 661 | cec_hwerr (SIM_CPU *cpu, int hwerr) |
| 662 | { |
| 663 | SET_HWERRCAUSE (hwerr); |
| 664 | cec_latch (cpu, IVG_IVHW); |
| 665 | } |
| 666 | |
| 667 | void |
| 668 | cec_return (SIM_CPU *cpu, int ivg) |
| 669 | { |
| 670 | SIM_DESC sd = CPU_STATE (cpu); |
| 671 | struct bfin_cec *cec; |
| 672 | bool snen; |
| 673 | int curr_ivg; |
| 674 | bu32 oldpc, newpc; |
| 675 | |
| 676 | oldpc = PCREG; |
| 677 | |
| 678 | BFIN_CPU_STATE.did_jump = true; |
| 679 | if (STATE_ENVIRONMENT (sd) != OPERATING_ENVIRONMENT) |
| 680 | { |
| 681 | SET_PCREG (cec_read_ret_reg (cpu, ivg)); |
| 682 | TRACE_BRANCH (cpu, oldpc, PCREG, -1, "CEC changed PC"); |
| 683 | return; |
| 684 | } |
| 685 | |
| 686 | cec = CEC_STATE (cpu); |
| 687 | |
| 688 | /* XXX: This isn't entirely correct ... */ |
| 689 | cec->ipend &= ~IVG_EMU_B; |
| 690 | |
| 691 | curr_ivg = _cec_get_ivg (cec); |
| 692 | if (curr_ivg == -1) |
| 693 | curr_ivg = IVG_USER; |
| 694 | if (ivg == -1) |
| 695 | ivg = curr_ivg; |
| 696 | |
| 697 | TRACE_EVENTS (cpu, "returning from EVT%i (should be EVT%i)", curr_ivg, ivg); |
| 698 | |
| 699 | /* Not allowed to return from usermode. */ |
| 700 | if (curr_ivg == IVG_USER) |
| 701 | cec_exception (cpu, VEC_ILL_RES); |
| 702 | |
| 703 | if (ivg > IVG15 || ivg < 0) |
| 704 | sim_io_error (sd, "%s: ivg %i out of range !", __func__, ivg); |
| 705 | |
| 706 | _cec_require_supervisor (cpu, cec); |
| 707 | |
| 708 | switch (ivg) |
| 709 | { |
| 710 | case IVG_EMU: |
| 711 | /* RTE -- only valid in emulation mode. */ |
| 712 | /* XXX: What does the hardware do ? */ |
| 713 | if (curr_ivg != IVG_EMU) |
| 714 | cec_exception (cpu, VEC_ILL_RES); |
| 715 | break; |
| 716 | case IVG_NMI: |
| 717 | /* RTN -- only valid in NMI. */ |
| 718 | /* XXX: What does the hardware do ? */ |
| 719 | if (curr_ivg != IVG_NMI) |
| 720 | cec_exception (cpu, VEC_ILL_RES); |
| 721 | break; |
| 722 | case IVG_EVX: |
| 723 | /* RTX -- only valid in exception. */ |
| 724 | /* XXX: What does the hardware do ? */ |
| 725 | if (curr_ivg != IVG_EVX) |
| 726 | cec_exception (cpu, VEC_ILL_RES); |
| 727 | break; |
| 728 | default: |
| 729 | /* RTI -- not valid in emulation, nmi, exception, or user. */ |
| 730 | /* XXX: What does the hardware do ? */ |
| 731 | if (curr_ivg == IVG_EMU || curr_ivg == IVG_NMI |
| 732 | || curr_ivg == IVG_EVX || curr_ivg == IVG_USER) |
| 733 | cec_exception (cpu, VEC_ILL_RES); |
| 734 | break; |
| 735 | case IVG_IRPTEN: |
| 736 | /* XXX: Is this even possible ? */ |
| 737 | excp_to_sim_halt (sim_stopped, SIM_SIGABRT); |
| 738 | break; |
| 739 | } |
| 740 | newpc = cec_read_ret_reg (cpu, ivg); |
| 741 | |
| 742 | /* XXX: Does this nested trick work on EMU/NMI/EVX ? */ |
| 743 | snen = (newpc & 1); |
| 744 | /* XXX: Delayed clear shows bad PCREG register trace above ? */ |
| 745 | SET_PCREG (newpc & ~1); |
| 746 | |
| 747 | TRACE_BRANCH (cpu, oldpc, PCREG, -1, "CEC changed PC (from EVT%i)", ivg); |
| 748 | |
| 749 | /* Update ipend after the TRACE_BRANCH so dv-bfin_trace |
| 750 | knows current CEC state wrt overflow. */ |
| 751 | if (!snen) |
| 752 | cec->ipend &= ~(1 << ivg); |
| 753 | |
| 754 | /* Disable global interrupt mask to let any interrupt take over, but |
| 755 | only when we were already in a RTI level. Only way we could have |
| 756 | raised at that point is if it was cleared in the first place. */ |
| 757 | if (ivg >= IVG_IVHW || ivg == IVG_RST) |
| 758 | cec_irpten_disable (cpu, cec); |
| 759 | |
| 760 | /* When going from super to user, we clear LSB in LB regs in case |
| 761 | it was set on the transition up. |
| 762 | Also need to load SP alias with USP. */ |
| 763 | if (_cec_get_ivg (cec) == -1) |
| 764 | { |
| 765 | int i; |
| 766 | for (i = 0; i < 2; ++i) |
| 767 | if (LBREG (i) & 1) |
| 768 | SET_LBREG (i, LBREG (i) & ~1); |
| 769 | SET_KSPREG (SPREG); |
| 770 | SET_SPREG (USPREG); |
| 771 | } |
| 772 | |
| 773 | /* Check for pending interrupts before we return to usermode. */ |
| 774 | _cec_check_pending (cpu, cec); |
| 775 | } |
| 776 | |
| 777 | void |
| 778 | cec_push_reti (SIM_CPU *cpu) |
| 779 | { |
| 780 | /* XXX: Need to check hardware with popped RETI value |
| 781 | and bit 1 is set (when handling nested interrupts). |
| 782 | Also need to check behavior wrt SNEN in SYSCFG. */ |
| 783 | struct bfin_cec *cec; |
| 784 | |
| 785 | if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT) |
| 786 | return; |
| 787 | |
| 788 | TRACE_EVENTS (cpu, "pushing RETI"); |
| 789 | |
| 790 | cec = CEC_STATE (cpu); |
| 791 | cec_irpten_disable (cpu, cec); |
| 792 | /* Check for pending interrupts. */ |
| 793 | _cec_check_pending (cpu, cec); |
| 794 | } |
| 795 | |
| 796 | void |
| 797 | cec_pop_reti (SIM_CPU *cpu) |
| 798 | { |
| 799 | /* XXX: Need to check hardware with popped RETI value |
| 800 | and bit 1 is set (when handling nested interrupts). |
| 801 | Also need to check behavior wrt SNEN in SYSCFG. */ |
| 802 | struct bfin_cec *cec; |
| 803 | |
| 804 | if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT) |
| 805 | return; |
| 806 | |
| 807 | TRACE_EVENTS (cpu, "popping RETI"); |
| 808 | |
| 809 | cec = CEC_STATE (cpu); |
| 810 | cec_irpten_enable (cpu, cec); |
| 811 | } |