ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
emu_exc.c
Go to the documentation of this file.
1
20
21#include "emu_exc.h"
22
23#include <stdio.h>
24
25#include "board_periph.h"
26#include "emu_console.h"
27#include "emu_engine.h"
29#include "emu_memmap.h"
30#include "emu_seams.h"
31
33typedef enum : uint32_t {
34 k_op_branch_self = 0xE7FEU,
35 k_op_wfi = 0xBF30U,
36 k_op_cpsie_i = 0xB662U,
37 k_op_bn_mask = 0xF800U,
38 k_op_bn_base = 0xE000U,
39 k_op_bn_imm = 0x07FFU,
48
49static uint32_t s_systick_fires;
50
51/* Hand-modelled Cortex-M exception state. Unicorn's M33 core has no NVIC /
52 * exception unit, so ra8_emulator takes SysTick / PendSV / SVCall by hand: it
53 * tracks the active-exception priority stack here (everything else -- MSP/PSP/
54 * CONTROL/xPSR/PRIMASK -- is read straight from Unicorn). s_exc_stack holds the
55 * priority of each handler currently active so a higher-priority exception
56 * (e.g. SysTick, prio 0x40) can pre-empt a lower one (PendSV, prio 0xFF) but
57 * not vice-versa, exactly as the real priority logic would nest them. */
58static uint32_t s_exc_stack[k_exc_nest_max];
59static uint32_t s_exc_depth;
60static uint32_t s_pendsv_takes;
61static uint32_t s_svc_takes;
62static uint64_t s_exc_return_pc;
63static bool s_exc_return_hit;
64static bool s_systick_pending;
65static bool s_pendsv_stop;
66static bool s_bkpt_hit;
67static uint32_t s_bkpt_pc;
68
77{
78 return (s_exc_depth == 0U) ? (uint32_t)k_exc_prio_none : s_exc_stack[s_exc_depth - 1U];
79}
80
104RA8_INTERNAL static uint32_t internal_exc_priority(uc_engine* uc, uint32_t exc_num)
105{
106 if (exc_num == (uint32_t)k_exc_svcall) {
107 return (rd32(uc, (uint64_t)k_scb_shpr2) >> (3U * (uint32_t)k_byte_bits)) &
108 (uint32_t)k_byte_mask;
109 }
110 if (exc_num == (uint32_t)k_exc_pendsv) {
111 return (rd32(uc, (uint64_t)k_scb_shpr3) >> (2U * (uint32_t)k_byte_bits)) &
112 (uint32_t)k_byte_mask;
113 }
114 if (exc_num == (uint32_t)k_exc_systick) {
115 return (rd32(uc, (uint64_t)k_scb_shpr3) >> (3U * (uint32_t)k_byte_bits)) &
116 (uint32_t)k_byte_mask;
117 }
118 return (uint32_t)k_exc_prio_max;
119}
120
131RA8_INTERNAL static void
132internal_exc_stack_frame(uc_engine* uc, uint32_t sp, bool fp_active, uint32_t frame_xpsr)
133{
134 wr32(uc, (uint64_t)sp + 0U, reg_get(uc, UC_ARM_REG_R0));
135 wr32(uc, (uint64_t)sp + 4U, reg_get(uc, UC_ARM_REG_R1));
136 wr32(uc, (uint64_t)sp + 8U, reg_get(uc, UC_ARM_REG_R2));
137 wr32(uc, (uint64_t)sp + (uint64_t)k_frame_off_r3, reg_get(uc, UC_ARM_REG_R3));
138 wr32(uc, (uint64_t)sp + 16U, reg_get(uc, UC_ARM_REG_R12));
139 wr32(uc, (uint64_t)sp + (uint64_t)k_frame_off_lr, reg_get(uc, UC_ARM_REG_LR));
140 wr32(uc, (uint64_t)sp + (uint64_t)k_frame_off_pc, reg_get(uc, UC_ARM_REG_PC));
141 wr32(uc, (uint64_t)sp + (uint64_t)k_frame_off_xpsr, frame_xpsr);
142
143 /* Armv8-M FP extended frame: S0-S15 + FPSCR sit directly above the 8-word
144 * basic frame (ThreadX's PendSV adds S16-S31 on top of this). */
145 if (fp_active) {
146 for (uint32_t i = 0U; i < (uint32_t)k_fp_s_words; i++) {
147 wr32(uc,
148 (uint64_t)sp + (uint64_t)k_frame_off_s0 + (uint64_t)(i * (uint32_t)k_word_bytes),
149 reg_get(uc, UC_ARM_REG_S0 + (int)i));
150 }
151 wr32(uc, (uint64_t)sp + (uint64_t)k_frame_off_fpscr, reg_get(uc, UC_ARM_REG_FPSCR));
152 }
153}
154
165RA8_INTERNAL static uint32_t internal_exc_return_value(bool in_thread, bool use_psp, bool fp_active)
166{
167 /* EXC_RETURN encodes where to unstack: Thread/PSP, Thread/MSP, or (when an
168 * exception pre-empts another) Handler/MSP. */
169 uint32_t exc_ret;
170 if (!in_thread) {
171 exc_ret = (uint32_t)k_exc_ret_handler;
172 } else if (use_psp) {
173 exc_ret = (uint32_t)k_exc_ret_psp;
174 } else {
175 exc_ret = (uint32_t)k_exc_ret_msp;
176 }
177 if (fp_active) {
178 exc_ret &= ~(uint32_t)k_exc_ret_ftype; /* FType=0: an FP frame was stacked. */
179 }
180 return exc_ret;
181}
182
210void exc_enter(uc_engine* uc, uint32_t exc_num, uint32_t handler)
211{
212 const uint32_t xpsr_in = reg_get(uc, UC_ARM_REG_XPSR);
213 const uint32_t control = reg_get(uc, UC_ARM_REG_CONTROL);
214 const bool in_thread = (xpsr_in & (uint32_t)k_xpsr_ipsr_mask) == 0U;
215 const bool use_psp = in_thread && ((control & (uint32_t)k_control_spsel) != 0U);
216 const bool fp_active = (control & (uint32_t)k_control_fpca) != 0U;
217
218 const int sp_reg = use_psp ? UC_ARM_REG_PSP : UC_ARM_REG_MSP;
219 uint32_t sp = reg_get(uc, sp_reg);
220
221 /* Hardware aligns the stack pointer to 8 bytes on entry and flags the pad in
222 * the stacked xPSR (bit 9) so the matching exception return can remove it. */
223 uint32_t frame_xpsr = xpsr_in;
224 if ((sp & 0x4U) != 0U) {
225 sp -= 4U;
226 frame_xpsr |= (uint32_t)k_xpsr_align9;
227 } else {
228 frame_xpsr &= ~(uint32_t)k_xpsr_align9;
229 }
230 sp -= (uint32_t)k_exc_frame_bytes;
231 if (fp_active) {
232 sp -= (uint32_t)k_fp_frame_extra;
233 }
234
235 internal_exc_stack_frame(uc, sp, fp_active, frame_xpsr);
236
237 /* Commit the new value of whichever stack the frame went onto. */
238 reg_set(uc, sp_reg, sp);
239
240 const uint32_t exc_ret = internal_exc_return_value(in_thread, use_psp, fp_active);
241
242 /* Handler mode always runs on MSP with CONTROL.SPSEL clear. */
243 reg_set(uc, UC_ARM_REG_CONTROL, control & ~(uint32_t)k_control_spsel);
244 reg_set(uc, UC_ARM_REG_SP, reg_get(uc, UC_ARM_REG_MSP));
245
246 uint32_t handler_xpsr =
247 (xpsr_in & ~(uint32_t)k_xpsr_ipsr_mask) | (exc_num & (uint32_t)k_xpsr_ipsr_mask);
248 handler_xpsr |= (uint32_t)k_xpsr_t_bit; /* M-profile is always Thumb. */
249 reg_set(uc, UC_ARM_REG_XPSR, handler_xpsr);
250 reg_set(uc, UC_ARM_REG_LR, exc_ret);
251 reg_set(uc, UC_ARM_REG_PC, handler & ~1U);
252
253 if (s_exc_depth < (uint32_t)k_exc_nest_max) {
255 s_exc_depth++;
256 }
257}
258
267RA8_INTERNAL static void internal_exc_restore_fp_frame(uc_engine* uc, uint32_t sp)
268{
269 /* Armv8-M FP extended frame (EXC_RETURN bit4 clear): S0-S15 + FPSCR sit above
270 * the basic frame. Restore them so the thread's scalar FP state survives.
271 * PC/xPSR keep their basic-frame offsets (accounted for by the caller). */
272 for (uint32_t i = 0U; i < (uint32_t)k_fp_s_words; i++) {
273 reg_set(
274 uc,
275 UC_ARM_REG_S0 + (int)i,
276 rd32(uc, (uint64_t)sp + (uint64_t)k_frame_off_s0 + (uint64_t)(i * (uint32_t)k_word_bytes)));
277 }
278 reg_set(uc, UC_ARM_REG_FPSCR, rd32(uc, (uint64_t)sp + (uint64_t)k_frame_off_fpscr));
279}
280
291RA8_INTERNAL static void
292internal_exc_restore_mode(uc_engine* uc, uint32_t xpsr, bool to_thread, bool to_psp)
293{
294 /* Restore mode: on return to Thread, CONTROL.SPSEL follows EXC_RETURN bit2;
295 * on return to a pre-empted handler the core stays on MSP. */
296 uint32_t control = reg_get(uc, UC_ARM_REG_CONTROL);
297 if (to_thread && to_psp) {
298 control |= (uint32_t)k_control_spsel;
299 } else {
300 control &= ~(uint32_t)k_control_spsel;
301 }
302 reg_set(uc, UC_ARM_REG_CONTROL, control);
303
304 uint32_t new_xpsr = xpsr | (uint32_t)k_xpsr_t_bit;
305 if (to_thread) {
306 new_xpsr &= ~(uint32_t)k_xpsr_ipsr_mask; /* Thread mode: IPSR == 0 */
307 }
308 reg_set(uc, UC_ARM_REG_XPSR, new_xpsr);
309}
310
335void exc_return(uc_engine* uc, uint32_t exc_ret)
336{
337 const bool to_psp = (exc_ret & (uint32_t)k_exc_ret_spsel) != 0U;
338 const bool to_thread = (exc_ret & (uint32_t)k_exc_ret_mode) != 0U;
339 const bool fp_frame = (exc_ret & (uint32_t)k_exc_ret_ftype) == 0U;
340 const int sp_reg = to_psp ? UC_ARM_REG_PSP : UC_ARM_REG_MSP;
341 uint32_t sp = reg_get(uc, sp_reg);
342
343 const uint32_t r0 = rd32(uc, (uint64_t)sp + 0U);
344 const uint32_t r1 = rd32(uc, (uint64_t)sp + 4U);
345 const uint32_t r2 = rd32(uc, (uint64_t)sp + 8U);
346 const uint32_t r3 = rd32(uc, (uint64_t)sp + (uint64_t)k_frame_off_r3);
347 const uint32_t r12 = rd32(uc, (uint64_t)sp + 16U);
348 const uint32_t lr = rd32(uc, (uint64_t)sp + (uint64_t)k_frame_off_lr);
349 const uint32_t pc = rd32(uc, (uint64_t)sp + (uint64_t)k_frame_off_pc);
350 const uint32_t xpsr = rd32(uc, (uint64_t)sp + (uint64_t)k_frame_off_xpsr);
351
352 if (fp_frame) {
354 }
355
356 sp += (uint32_t)k_exc_frame_bytes;
357 if (fp_frame) {
358 sp += (uint32_t)k_fp_frame_extra;
359 }
360 if ((xpsr & (uint32_t)k_xpsr_align9) != 0U) {
361 sp += 4U; /* undo the entry-time 8-byte realignment pad */
362 }
363 reg_set(uc, sp_reg, sp);
364
365 reg_set(uc, UC_ARM_REG_R0, r0);
366 reg_set(uc, UC_ARM_REG_R1, r1);
367 reg_set(uc, UC_ARM_REG_R2, r2);
368 reg_set(uc, UC_ARM_REG_R3, r3);
369 reg_set(uc, UC_ARM_REG_R12, r12);
370 reg_set(uc, UC_ARM_REG_LR, lr);
371
372 internal_exc_restore_mode(uc, xpsr, to_thread, to_psp);
373
374 /* Active SP becomes whichever stack the returned-to context uses. */
375 reg_set(uc, UC_ARM_REG_SP, reg_get(uc, to_psp && to_thread ? UC_ARM_REG_PSP : UC_ARM_REG_MSP));
376 reg_set(uc, UC_ARM_REG_PC, pc & ~1U);
377
378 if (s_exc_depth > 0U) {
379 s_exc_depth--;
380 }
381}
382
398RA8_INTERNAL static bool internal_is_exc_return(uint64_t pc)
399{
400 return (pc & (uint64_t)k_exc_ret_v8_mask) == (uint64_t)k_exc_ret_v8_mask;
401}
402
417uint32_t exc_vector(uc_engine* uc, uint32_t vtor_base, uint32_t exc_num)
418{
419 uint32_t vtor = rd32(uc, (uint64_t)k_scb_vtor);
420 if (vtor == 0U) {
421 vtor = vtor_base;
422 }
423 const uint32_t handler = rd32(uc, (uint64_t)vtor + ((uint64_t)exc_num * 4U)) & ~1U;
424 if ((handler == 0U) || (handler == (uint32_t)k_vector_erased)) {
425 return 0U;
426 }
427 return handler;
428}
429
456RA8_INTERNAL static bool
457internal_exc_take_periph_irq(uc_engine* uc, uint32_t vtor_base, uint32_t active)
458{
459 uint32_t irq = 0U;
460 if (!board_periph_next_irq(&irq)) {
461 return false;
462 }
463 const uint8_t prio_byte = (uint8_t)rd32(uc, (uint64_t)k_nvic_ipr_base + irq);
464 const uint32_t prio =
465 (uint32_t)(prio_byte >> (uint32_t)k_nvic_prio_shift) & (uint32_t)k_lo4_mask; /* 4 MSBs used */
466 if (prio >= active) {
467 return false; /* an equal/higher-priority handler is active -- defer */
468 }
469 const uint32_t handler = exc_vector(uc, vtor_base, (uint32_t)k_exc_irq_vec0 + irq);
470 if (handler == 0U) {
471 return false; /* no ISR installed: drop (default handler would just return) */
472 }
473 /* Clear the NVIC ISPR pending bit on activation, as hardware does. */
474 const uint64_t ispr_word = (uint64_t)k_nvic_ispr_base + ((uint64_t)(irq / 32U) * 4U);
475 uint32_t ispr = rd32(uc, ispr_word);
476 ispr &= ~(1U << (irq % 32U));
477 wr32(uc, ispr_word, ispr);
478
479 exc_enter(uc, (uint32_t)k_exc_irq_vec0 + irq, handler);
481 return true;
482}
483
530bool exc_take_pending(uc_engine* uc, uint32_t vtor_base, bool allow_systick)
531{
532 const uint32_t primask = reg_get(uc, UC_ARM_REG_PRIMASK);
533 if ((primask & 1U) != 0U) {
534 return false; /* interrupts masked -- no exception may be taken now */
535 }
536 const uint32_t active = internal_exc_active_prio();
537
538 /* SysTick first: highest-priority of the modelled exceptions, so it can
539 * pre-empt a lower-priority PendSV that is spinning for a runnable thread.
540 * Skipped on a context-switch stop (allow_systick == false) so the tick does
541 * not advance while ready threads still have work to run. */
542 if (allow_systick && s_systick_pending) {
543 const bool armed =
544 (rd32(uc, (uint64_t)k_syst_csr) & (uint32_t)k_syst_csr_run) == (uint32_t)k_syst_csr_run;
545 if (!armed) {
546 s_systick_pending = false; /* disabled SysTick: drop the pended tick */
547 } else if (internal_exc_priority(uc, (uint32_t)k_exc_systick) < active) {
548 const uint32_t handler = exc_vector(uc, vtor_base, (uint32_t)k_exc_systick);
549 if (handler != 0U) {
550 s_systick_pending = false;
551 exc_enter(uc, (uint32_t)k_exc_systick, handler);
553 return true;
554 }
555 }
556 }
557
558 /* PendSV: taken when the firmware has requested a context switch. */
559 uint32_t icsr = rd32(uc, (uint64_t)k_scb_icsr);
560 if ((icsr & (1U << (uint32_t)k_icsr_pendsvset)) != 0U) {
561 if (internal_exc_priority(uc, (uint32_t)k_exc_pendsv) < active) {
562 const uint32_t handler = exc_vector(uc, vtor_base, (uint32_t)k_exc_pendsv);
563 if (handler != 0U) {
564 /* Hardware clears PENDSVSET when PendSV is activated. */
565 icsr &= ~(1U << (uint32_t)k_icsr_pendsvset);
566 wr32(uc, (uint64_t)k_scb_icsr, icsr);
567 exc_enter(uc, (uint32_t)k_exc_pendsv, handler);
569 return true;
570 }
571 }
572 }
573
574 /* Peripheral NVIC IRQs queued by the ICU model (timer overflow / underflow
575 * routed through IELSR). Taken last among the modelled exceptions but via the
576 * identical real entry/return path, with NVIC IPR priority honoured. */
577 return internal_exc_take_periph_irq(uc, vtor_base, active);
578}
579
589RA8_INTERNAL static const char* internal_unmapped_access_kind(uc_mem_type type)
590{
591 if (type == UC_MEM_READ_UNMAPPED) {
592 return "read";
593 }
594 if (type == UC_MEM_WRITE_UNMAPPED) {
595 return "write";
596 }
597 return "fetch";
598}
599
613RA8_INTERNAL static bool internal_on_unmapped(uc_engine* uc,
614 uc_mem_type type,
615 uint64_t addr,
616 int size,
617 int64_t value,
618 void* user)
619{
620 (void)size;
621 (void)value;
622 (void)user;
623 /* A FETCH into the EXC_RETURN range is not a fault -- it is the core taking
624 * an exception return (the handler ran "BX lr" with an EXC_RETURN magic in
625 * LR). Capture it and stop cleanly so the run loop can unstack the frame and
626 * resume; the Unicorn fetch-fault error this produces is expected/handled. */
627 if ((type == UC_MEM_FETCH_UNMAPPED) && internal_is_exc_return(addr)) {
628 s_exc_return_pc = addr;
629 s_exc_return_hit = true;
630 (void)uc_emu_stop(uc);
631 return false;
632 }
633 (void)priv_emu_io_errf(" UNMAPPED %s @ 0x%08llX (extend the memory/peripheral map)\n",
635 (unsigned long long)addr);
636 return false; /* stop emulation and report */
637}
638
664RA8_INTERNAL static bool internal_on_intr_sec_insn(uc_engine* uc, uint32_t pc, uint32_t insn)
665{
666 if (insn == (uint32_t)k_armv8m_sg_opcode) {
667 const uint32_t next = pc + (uint32_t)k_thumb2_insn_bytes;
668 (void)uc_reg_write(uc, UC_ARM_REG_PC, &next);
669 (void)uc_emu_stop(uc);
670 return true;
671 }
672 if ((insn == (uint32_t)k_fpcxtns_push) || (insn == (uint32_t)k_fpcxtns_pop)) {
673 uint32_t sp = 0U;
674 (void)uc_reg_read(uc, UC_ARM_REG_SP, &sp);
675 if (insn == (uint32_t)k_fpcxtns_push) {
676 sp -= (uint32_t)k_word_bytes;
677 const uint32_t zero = 0U;
678 (void)emu_mem_write(uc, (uint64_t)sp, &zero, sizeof(zero));
679 } else {
680 sp += (uint32_t)k_word_bytes;
681 }
682 const uint32_t next = pc + (uint32_t)k_thumb2_insn_bytes;
683 (void)uc_reg_write(uc, UC_ARM_REG_SP, &sp);
684 (void)uc_reg_write(uc, UC_ARM_REG_PC, &next);
685 (void)uc_emu_stop(uc);
686 return true;
687 }
688 return false;
689}
690
711RA8_INTERNAL static bool internal_on_intr_bkpt(uc_engine* uc, uint32_t pc, uint32_t insn)
712{
713 if (((uint16_t)(insn & (uint32_t)k_lo16_mask) & (uint16_t)k_bkpt_hw_mask) ==
714 (uint16_t)k_bkpt_hw_base) {
715 s_bkpt_hit = true;
716 s_bkpt_pc = pc;
717 (void)uc_emu_stop(uc);
718 return true;
719 }
720 return false;
721}
722
752RA8_INTERNAL static void internal_on_intr(uc_engine* uc, uint32_t int_no, void* user_data)
753{
754 (void)int_no;
755 uint32_t pc = 0U;
756 (void)uc_reg_read(uc, UC_ARM_REG_PC, &pc);
757
758 /* Cortex-M exception RETURN. Unicorn's M-profile core does not pop the
759 * exception frame itself, but it DOES trap a branch to an EXC_RETURN magic
760 * (0xFFFFFFFx) by raising an interrupt with the magic left in PC (Thumb bit
761 * masked off, but the stack/mode selector bits 2/3 intact). Unstack the basic
762 * frame and resume the interrupted context. This is how every handler that
763 * ra8_emulator vectors in (SysTick / PendSV / SVCall) returns. */
764 if (internal_is_exc_return((uint64_t)pc)) {
765 s_exc_return_pc = (uint64_t)pc;
766 s_exc_return_hit = true;
767 (void)uc_emu_stop(uc);
768 return;
769 }
770
771 /* MVE (Helium) contiguous load/store raises a NoCP UsageFault on Unicorn's
772 * M33; ::emu_mve_nocp_emulate documents the decode and why it precedes SVCall. */
773 if (emu_mve_nocp_emulate(uc, pc)) {
774 (void)uc_emu_stop(uc);
775 return;
776 }
777
778 /* Armv8-M secure gateway: every Non-Secure-Callable veneer starts with `SG`
779 * (0xE97FE97F) then `B.W __acle_se_<fn>`. Unicorn's M33 has no Security
780 * Extension, so it raises INTR on the unrecognised SG instead of switching to
781 * Secure state. ra8_emulator has a single flat address space (no S/NS split), so
782 * the faithful model is to treat SG as a NOP and let the following branch reach
783 * the secure entry directly. Without this the SG is mis-taken as an `svc` and
784 * re-taken forever (the firmware has no SVC), looping until the stack
785 * underflows -- the tz_nsc_cgc_usb fault. The matching `BXNS`/`BLXNS` returns
786 * are handled below. */
787 uint32_t insn = 0U;
788 (void)emu_mem_read(uc, (uint64_t)pc, &insn, sizeof(insn));
789
790 /* Armv8-M Security Extension instructions (SG / FPCXTNS) reduce to their plain
791 * effects in this single-domain model; a BKPT is a deliberate firmware halt. */
792 if (internal_on_intr_sec_insn(uc, pc, insn)) {
793 return;
794 }
795 if (internal_on_intr_bkpt(uc, pc, insn)) {
796 return;
797 }
798
799 /* Otherwise it is a synchronous `svc` -- take SVCall (#11). ThreadX in single
800 * mode never issues one, but bare-metal / future RTOS first-thread-start
801 * paths do. The VTOR fallback is the MRAM vector-table base; the live VTOR
802 * (set by SystemInit) is read inside exc_vector. */
803 (void)user_data;
804 const uint32_t vtor_base = (uint32_t)emu_memmap_mram_base();
805 const uint32_t handler = exc_vector(uc, vtor_base, (uint32_t)k_exc_svcall);
806 if (handler != 0U) {
807 exc_enter(uc, (uint32_t)k_exc_svcall, handler);
808 s_svc_takes++;
809 }
810 (void)uc_emu_stop(uc);
811}
812
843RA8_INTERNAL static void internal_on_icsr_write(uc_engine* uc,
844 uc_mem_type type,
845 uint64_t addr,
846 int size,
847 int64_t value,
848 void* user)
849{
850 (void)type;
851 (void)addr;
852 (void)size;
853 (void)user;
854 if (((uint32_t)value & (1U << (uint32_t)k_icsr_pendsvset)) == 0U) {
855 return; /* not a PendSV request (e.g. PENDSVCLR / status write) */
856 }
857 /* Only end the chunk when PendSV could actually activate now: interrupts
858 * unmasked and no equal/higher-priority handler already running. If masked
859 * (the store sits inside a ThreadX critical section), do NOT stop -- the run
860 * loop would otherwise relaunch from this same store and re-pend forever,
861 * since uc_emu_stop here leaves PC on the store. With those guards, the run
862 * loop's exc_take_pending takes PendSV and moves PC to the handler, so the
863 * store is never re-executed. The masked case is picked up at the next
864 * boundary once TX_RESTORE re-enables interrupts. */
865 uint32_t primask = 0U;
866 (void)uc_reg_read(uc, UC_ARM_REG_PRIMASK, &primask);
867 if ((primask & 1U) != 0U) {
868 return;
869 }
871 return; /* a higher/equal-priority handler is active -- defer */
872 }
873 /* Advance PC past the storing instruction before stopping so the PendSV we
874 * are about to take stacks the return address of the NEXT instruction -- as
875 * real hardware does (PendSV activates after the store retires), not the store
876 * itself. Re-stacking the store would re-pend PendSV on every return and spin.
877 * Thumb length: a halfword whose top 5 bits are 0b111xx with xx != 00 starts a
878 * 32-bit instruction; otherwise it is 16-bit. */
879 uint32_t pc = 0U;
880 (void)uc_reg_read(uc, UC_ARM_REG_PC, &pc);
881 uint16_t hw0 = 0U;
882 (void)emu_mem_read(uc, pc, &hw0, sizeof(hw0));
883 const uint32_t op5 = (uint32_t)(hw0 >> (uint32_t)k_thumb_op5_shift) & (uint32_t)k_thumb_op5_mask;
884 const uint32_t step = (op5 >= (uint32_t)k_thumb32_op5_min) ? 4U : 2U;
885 uint32_t next = pc + step;
886 (void)uc_reg_write(uc, UC_ARM_REG_PC, &next);
887 /* Mark this as a context-switch stop so the run loop takes PendSV WITHOUT
888 * advancing the SysTick: a context switch consumes no modelled time, so a
889 * thread that suspends (e.g. tx_thread_sleep) must not have its tick-based
890 * wait expired the instant it yields -- that would starve lower-priority
891 * ready threads. Time (SysTick) advances only on a full-budget run (genuine
892 * execution / idle spin). See the run loop's inner dispatch. */
893 s_pendsv_stop = true;
894 (void)uc_emu_stop(uc);
895}
896
898void emu_exc_install_core(uc_engine* uc)
899{
900 static uc_hook s_h_unmapped;
901 static uc_hook s_h_intr;
902 static uc_hook s_h_icsr;
903 (void)uc_hook_add(uc,
904 &s_h_unmapped,
905 UC_HOOK_MEM_UNMAPPED,
907 nullptr,
908 1,
909 0);
910 /* SVCall / exception-return: Unicorn raises UC_HOOK_INTR on a Thumb `svc` and
911 * on a branch to an EXC_RETURN magic; internal_on_intr vectors / unstacks accordingly. */
912 (void)uc_hook_add(uc, &s_h_intr, UC_HOOK_INTR, (void*)internal_on_intr, nullptr, 1, 0);
913 /* Watch the ICSR word so a PENDSVSET store ends the chunk at once, giving
914 * PendSV next-instruction activation (see internal_on_icsr_write). ICSR lives in PPB
915 * RAM, so this memory-write hook is the only way to observe the request. */
916 (void)uc_hook_add(uc,
917 &s_h_icsr,
918 UC_HOOK_MEM_WRITE,
920 nullptr,
921 (uint64_t)k_scb_icsr,
922 (uint64_t)k_scb_icsr + 3U);
923}
924
927{
928 s_systick_pending = true;
929}
930
932bool emu_exc_take_exc_return(uint64_t* out_pc)
933{
934 if (!s_exc_return_hit) {
935 return false;
936 }
937 s_exc_return_hit = false;
938 *out_pc = s_exc_return_pc;
939 return true;
940}
941
944{
945 s_pendsv_stop = false;
946}
947
950{
951 return s_pendsv_stop;
952}
953
956{
957 return s_bkpt_hit;
958}
959
961uint32_t emu_exc_bkpt_pc(void)
962{
963 return s_bkpt_pc;
964}
965
968{
969 return s_systick_fires;
970}
971
974{
975 return s_pendsv_takes;
976}
977
979uint32_t emu_exc_svc_takes(void)
980{
981 return s_svc_takes;
982}
983
986{
987 s_exc_depth = 0U;
988 s_systick_pending = true;
989 s_bkpt_hit = false;
990 s_exc_return_hit = false;
991 s_pendsv_stop = false;
992 s_systick_fires = 0U;
993 s_pendsv_takes = 0U;
994 s_svc_takes = 0U;
995}
Register-accurate peripheral-model framework for the board emulator.
void board_periph_note_irq_taken(uint32_t irq)
Record that NVIC IRQ irq was actually taken (for the summary).
bool board_periph_next_irq(uint32_t *out_irq)
Pop the next pending, enabled NVIC IRQ number the ICU has queued.
Emulator text-console surfaces: UART echo, ITM/SWO echo, escapes.
Shared Unicorn engine access utilities for the board emulator.
static uint32_t reg_get(uc_engine *uc, int reg)
Read a Unicorn 32-bit register by its UC_ARM_REG_* id.
Definition emu_engine.h:113
static uint32_t rd32(uc_engine *uc, uint64_t addr)
Read a 32-bit little-endian word from emulated memory.
Definition emu_engine.h:67
static void wr32(uc_engine *uc, uint64_t addr, uint32_t v)
Write a 32-bit little-endian word to emulated memory.
Definition emu_engine.h:91
static void reg_set(uc_engine *uc, int reg, uint32_t v)
Write a Unicorn 32-bit register by its UC_ARM_REG_* id.
Definition emu_engine.h:137
emu_exc_decode_t
Thumb decode + idle-spin scan constants for this exception engine.
Definition emu_exc.c:33
bool emu_exc_take_exc_return(uint64_t *out_pc)
Implementation of emu_exc_take_exc_return() – read + clear latch.
Definition emu_exc.c:932
void emu_exc_arm_systick(void)
Implementation of emu_exc_arm_systick() – pend the periodic tick.
Definition emu_exc.c:926
void emu_exc_clear_pendsv_stop(void)
Implementation of emu_exc_clear_pendsv_stop() – per-launch clear.
Definition emu_exc.c:943
static RA8_INTERNAL void internal_exc_restore_fp_frame(uc_engine *uc, uint32_t sp)
Restore the Armv8-M FP extended frame (S0-S15 + FPSCR) from sp.
Definition emu_exc.c:267
static RA8_INTERNAL bool internal_on_intr_bkpt(uc_engine *uc, uint32_t pc, uint32_t insn)
Model a firmware BKPT as a halt (record the site and stop).
Definition emu_exc.c:711
static RA8_INTERNAL bool internal_on_intr_sec_insn(uc_engine *uc, uint32_t pc, uint32_t insn)
Model the Armv8-M Security-Extension opcodes Unicorn's M33 lacks.
Definition emu_exc.c:664
static uint32_t s_exc_depth
Number of active handlers.
Definition emu_exc.c:59
static RA8_INTERNAL uint32_t internal_exc_active_prio(void)
Priority value (lower = higher) of the active handler, or sentinel.
Definition emu_exc.c:76
static uint32_t s_svc_takes
SVCall exceptions taken.
Definition emu_exc.c:61
static uint64_t s_exc_return_pc
Pending EXC_RETURN to unstack.
Definition emu_exc.c:62
static RA8_INTERNAL bool internal_on_unmapped(uc_engine *uc, uc_mem_type type, uint64_t addr, int size, int64_t value, void *user)
Hook fired on access to unmapped memory (peripheral surface gap).
Definition emu_exc.c:613
static RA8_INTERNAL bool internal_is_exc_return(uint64_t pc)
True if pc is an EXC_RETURN magic value.
Definition emu_exc.c:398
bool emu_exc_bkpt_hit(void)
Implementation of emu_exc_bkpt_hit() – plain flag read.
Definition emu_exc.c:955
static uint32_t s_exc_stack[k_exc_nest_max]
Active-handler priorities.
Definition emu_exc.c:58
static RA8_INTERNAL void internal_exc_stack_frame(uc_engine *uc, uint32_t sp, bool fp_active, uint32_t frame_xpsr)
Stack the basic {R0-R3,R12,LR,PC,xPSR} frame + optional FP frame at sp.
Definition emu_exc.c:132
uint32_t exc_vector(uc_engine *uc, uint32_t vtor_base, uint32_t exc_num)
Read the handler address for an exception from the vector table.
Definition emu_exc.c:417
static RA8_INTERNAL bool internal_exc_take_periph_irq(uc_engine *uc, uint32_t vtor_base, uint32_t active)
Take one pending peripheral NVIC IRQ the ICU has queued, if allowed.
Definition emu_exc.c:457
static RA8_INTERNAL const char * internal_unmapped_access_kind(uc_mem_type type)
Name the access kind that hit an unmapped address.
Definition emu_exc.c:589
bool emu_exc_pendsv_stop(void)
Implementation of emu_exc_pendsv_stop() – plain flag read.
Definition emu_exc.c:949
static bool s_pendsv_stop
Chunk ended on a PENDSVSET.
Definition emu_exc.c:65
static RA8_INTERNAL uint32_t internal_exc_return_value(bool in_thread, bool use_psp, bool fp_active)
Select the EXC_RETURN magic for the outgoing mode/stack/FP state.
Definition emu_exc.c:165
static bool s_systick_pending
SysTick exception is pended.
Definition emu_exc.c:64
uint32_t emu_exc_pendsv_takes(void)
Implementation of emu_exc_pendsv_takes() – plain counter read.
Definition emu_exc.c:973
void exc_enter(uc_engine *uc, uint32_t exc_num, uint32_t handler)
Enter a Cortex-M exception: stack the basic frame and vector in.
Definition emu_exc.c:210
static uint32_t s_pendsv_takes
PendSV exceptions taken.
Definition emu_exc.c:60
uint32_t emu_exc_bkpt_pc(void)
Implementation of emu_exc_bkpt_pc() – plain state read.
Definition emu_exc.c:961
static RA8_INTERNAL void internal_on_intr(uc_engine *uc, uint32_t int_no, void *user_data)
UC_HOOK_INTR handler: take the SVCall exception on an svc opcode.
Definition emu_exc.c:752
void exc_return(uc_engine *uc, uint32_t exc_ret)
Perform a Cortex-M exception return for an observed EXC_RETURN branch.
Definition emu_exc.c:335
static RA8_INTERNAL void internal_exc_restore_mode(uc_engine *uc, uint32_t xpsr, bool to_thread, bool to_psp)
Restore CONTROL.SPSEL + xPSR/IPSR for the returned-to context.
Definition emu_exc.c:292
void emu_exc_install_core(uc_engine *uc)
Implementation of emu_exc_install_core() – unmapped/INTR/ICSR hooks.
Definition emu_exc.c:898
static RA8_INTERNAL uint32_t internal_exc_priority(uc_engine *uc, uint32_t exc_num)
Read a system-handler priority byte from an SHPR register.
Definition emu_exc.c:104
uint32_t emu_exc_svc_takes(void)
Implementation of emu_exc_svc_takes() – plain counter read.
Definition emu_exc.c:979
bool exc_take_pending(uc_engine *uc, uint32_t vtor_base, bool allow_systick)
Take the highest-priority pending exception, if one may activate now.
Definition emu_exc.c:530
void emu_exc_reset(void)
Implementation of emu_exc_reset() – warm-reboot exception state.
Definition emu_exc.c:985
static RA8_INTERNAL void internal_on_icsr_write(uc_engine *uc, uc_mem_type type, uint64_t addr, int size, int64_t value, void *user)
UC_HOOK_MEM_WRITE handler for SCB ICSR – take PendSV promptly.
Definition emu_exc.c:843
uint32_t emu_exc_systick_fires(void)
Implementation of emu_exc_systick_fires() – plain counter read.
Definition emu_exc.c:967
static uint32_t s_systick_fires
Definition emu_exc.c:49
static bool s_exc_return_hit
An EXC_RETURN branch was seen.
Definition emu_exc.c:63
static uint32_t s_bkpt_pc
PC of the BKPT that halted.
Definition emu_exc.c:67
static bool s_bkpt_hit
Firmware executed a BKPT.
Definition emu_exc.c:66
Cortex-M exception model constants and interfaces for ra8_emulator.
@ k_fp_s_words
S0-S15 saved in the FP extended frame.
Definition emu_exc.h:105
@ k_xpsr_t_bit
xPSR.T (Thumb) – must stay set.
Definition emu_exc.h:93
@ k_exc_ret_spsel
EXC_RETURN bit2: return stack = PSP.
Definition emu_exc.h:89
@ k_thumb2_insn_bytes
32-bit Thumb-2 instruction width.
Definition emu_exc.h:112
@ k_frame_off_xpsr
Basic exception-frame offset of xPSR.
Definition emu_exc.h:103
@ k_control_spsel
CONTROL.SPSEL: thread SP = PSP.
Definition emu_exc.h:91
@ k_exc_ret_mode
EXC_RETURN bit3: return to Thread.
Definition emu_exc.h:90
@ k_xpsr_ipsr_mask
xPSR[8:0] = IPSR (active exception).
Definition emu_exc.h:95
@ k_fpcxtns_pop
VLDR FPCXTNS,[sp],#4 (LE word).
Definition emu_exc.h:114
@ k_lo4_mask
Low nibble (register / cond field).
Definition emu_exc.h:110
@ k_frame_off_lr
Basic exception-frame offset of LR.
Definition emu_exc.h:101
@ k_exc_ret_ftype
EXC_RETURN bit4: 1 = basic, 0 = FP.
Definition emu_exc.h:85
@ k_exc_prio_max
Lowest configurable priority value.
Definition emu_exc.h:97
@ k_frame_off_pc
Basic exception-frame offset of PC.
Definition emu_exc.h:102
@ k_frame_off_fpscr
FP-frame offset of FPSCR (32 + 16*4).
Definition emu_exc.h:107
@ k_exc_frame_bytes
8 words * 4 bytes.
Definition emu_exc.h:83
@ k_lo16_mask
Low halfword of a 32-bit fetch.
Definition emu_exc.h:119
@ k_exc_ret_handler
Return to Handler mode, MSP.
Definition emu_exc.h:86
@ k_exc_ret_psp
Return to Thread mode, PSP.
Definition emu_exc.h:88
@ k_fpcxtns_push
VSTR FPCXTNS,[sp,#-4]! (LE word).
Definition emu_exc.h:113
@ k_exc_ret_v8_mask
Armv8-M EXC_RETURN prefix: bits[31:7].
Definition emu_exc.h:84
@ k_nvic_prio_shift
Implemented priority is the 4 MSBs.
Definition emu_exc.h:109
@ k_vector_erased
Erased-flash / invalid vector word.
Definition emu_exc.h:108
@ k_exc_ret_msp
Return to Thread mode, MSP.
Definition emu_exc.h:87
@ k_frame_off_r3
Basic exception-frame offset of R3.
Definition emu_exc.h:100
@ k_fp_frame_extra
FP ext frame above basic: S0-15+FPSCR.
Definition emu_exc.h:104
@ k_bkpt_hw_base
BKPT #imm8 halfword (imm free).
Definition emu_exc.h:120
@ k_exc_nest_max
Tracked active-exception nesting cap.
Definition emu_exc.h:98
@ k_byte_bits
Bits per byte (SHPR field width).
Definition emu_exc.h:99
@ k_word_bytes
One stacked word.
Definition emu_exc.h:115
@ k_frame_off_s0
FP-frame offset of S0 (above basic).
Definition emu_exc.h:106
@ k_control_fpca
CONTROL.FPCA: FP context is active.
Definition emu_exc.h:92
@ k_armv8m_sg_opcode
Armv8-M SG secure-gateway opcode.
Definition emu_exc.h:111
@ k_bkpt_hw_mask
Mask isolating the BKPT opcode.
Definition emu_exc.h:121
@ k_xpsr_align9
xPSR bit9: stack-frame realignment.
Definition emu_exc.h:94
@ k_exc_prio_none
Sentinel "no handler active" prio.
Definition emu_exc.h:96
@ k_syst_csr
SysTick control/status (SYST_CSR).
Definition emu_exc.h:36
@ k_nvic_ipr_base
NVIC IPR priority bytes (one per IRQ).
Definition emu_exc.h:61
@ k_scb_shpr2
System handler priority 2 (SVC=b3).
Definition emu_exc.h:42
@ k_scb_vtor
Vector table offset register.
Definition emu_exc.h:39
@ k_icsr_pendsvset
ICSR.PENDSVSET bit (request PendSV).
Definition emu_exc.h:54
@ k_syst_csr_run
ENABLE | TICKINT both set.
Definition emu_exc.h:37
@ k_scb_shpr3
System handler priority 3 (PSV/SYT).
Definition emu_exc.h:43
@ k_exc_svcall
SVCall exception / vector index.
Definition emu_exc.h:58
@ k_exc_pendsv
PendSV exception / vector index.
Definition emu_exc.h:59
@ k_exc_systick
SysTick exception / vector index.
Definition emu_exc.h:60
@ k_scb_icsr
Interrupt control/state (ICSR).
Definition emu_exc.h:38
@ k_exc_irq_vec0
Vector index of IRQ0 (16 + IRQn).
Definition emu_exc.h:67
Bounded raw-descriptor I/O seam for the RA8 emulator.
emu_io_result_t priv_emu_io_errf(const char *format,...)
Format bounded text and write it to the injected error descriptor.
Shared aliased-memory backing and Unicorn memory-map bindings.
uint64_t emu_memmap_mram_base(void)
Return the MRAM boot-vector base.
Definition emu_memmap.c:523
uc_err emu_mem_read(uc_engine *uc, uint64_t address, void *bytes, size_t count)
Read guest memory through the central access seam.
uc_err emu_mem_write(uc_engine *uc, uint64_t address, const void *bytes, size_t count)
Write guest memory through the central access seam.
@ k_op_wfi
Thumb wfi (wait-for-interrupt).
Definition emu_run.h:53
@ k_idle_loop_max
Largest idle loop (bytes) that may hold PC.
Definition emu_run.h:61
@ k_idle_scan_fwd
Halfwords scanned ahead for a loop edge.
Definition emu_run.h:60
@ k_bn_imm_sext_shl
Shift imm11 bit10 up to bit31 (sign bit).
Definition emu_run.h:58
@ k_bn_imm_sext_shr
Arith >> sign-extends and scales imm by 2.
Definition emu_run.h:59
@ k_op_bn_mask
Mask selecting a Thumb T2 b.n opcode.
Definition emu_run.h:55
@ k_op_branch_self
Thumb "b ." (branch-to-self idle loop).
Definition emu_run.h:52
@ k_op_bn_base
Thumb T2 unconditional b.n base value.
Definition emu_run.h:56
@ k_op_cpsie_i
Thumb cpsie i (re-enable IRQ in a poll).
Definition emu_run.h:54
@ k_op_bn_imm
Thumb T2 b.n imm11 field mask.
Definition emu_run.h:57
@ k_thumb32_op5_min
op5 >= this -> 32-bit instruction.
Definition emu_run.h:91
@ k_thumb_op5_mask
5-bit op5 field.
Definition emu_run.h:90
@ k_thumb_op5_shift
op5 = hw0[15:11].
Definition emu_run.h:89
Armv8.1-M instruction-emulation seams (M85 ops on Unicorn's M33).
bool emu_mve_nocp_emulate(uc_engine *uc, uint32_t pc)
Emulate an MVE contiguous load/store from the NoCP UsageFault.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_byte_mask
Byte mask.
@ k_nvic_ispr_base
NVIC Interrupt Set-Pending Register array.