ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
emu_seam_mve.c File Reference

Minimal MVE (Helium) emulation seam (see emu_seams.h). More...

#include <capstone/capstone.h>
#include <stdio.h>
#include <string.h>
#include "emu_engine.h"
#include "emu_exc.h"
#include "emu_seams.h"
Include dependency graph for emu_seam_mve.c:

Go to the source code of this file.

Data Structures

struct  mve_mem_op_t
 One decoded MVE contiguous load/store. More...

Enumerations

enum  : uint32_t {
  k_mve_insn_len = 4U ,
  k_mve_q_bytes = 16U ,
  k_mve_lane_shift = 32U ,
  k_mve_max_run = 4096U
}
enum  mve_mem_field_t : uint32_t {
  k_mve_mem_h1_mask = 0xFF40U ,
  k_mve_mem_h1_val = 0xED00U ,
  k_mve_mem_h1_post = 0xEC00U ,
  k_mve_mem_h2_mask = 0x1E00U ,
  k_mve_mem_h2_val = 0x1E00U ,
  k_mve_mem_bit_u = 0x0080U ,
  k_mve_mem_bit_w = 0x0020U ,
  k_mve_mem_bit_l = 0x0010U ,
  k_mve_mem_size_sh = 7U ,
  k_mve_mem_size_msk = 0x3U ,
  k_mve_mem_sz_byte = 0U ,
  k_mve_mem_sz_half = 1U ,
  k_mve_mem_sz_word = 2U ,
  k_mve_mem_scl_byte = 1U ,
  k_mve_mem_scl_half = 2U ,
  k_mve_mem_scl_word = 4U ,
  k_mve_qd_shift = 13U ,
  k_mve_qd_mask = 0x7U ,
  k_mve_rn_mask = 0xFU ,
  k_mve_imm7_mask = 0x7FU
}

Functions

static RA8_INTERNAL bool internal_mve_mem_decode (uint16_t hw1, uint16_t hw2, mve_mem_op_t *op)
 Decode an MVE contiguous load/store from its two halfwords.
static RA8_INTERNAL void internal_mve_mem_exec (uc_engine *uc, const mve_mem_op_t *op)
 Perform one decoded MVE contiguous load/store against emulated state.
static RA8_INTERNAL bool internal_mve_mem_try (uc_engine *uc, const uint8_t code[4])
 Perform the MVE contiguous load/store at code, if that is what it is.
static RA8_INTERNAL int internal_mve_q_d (unsigned int qreg, bool high)
 Map capstone Q-reg qreg to its Unicorn D-register half (low/high).
static RA8_INTERNAL bool internal_mve_exec_one (uc_engine *uc, const cs_insn *insn)
 Execute one decoded MVE instruction (no PC change); true iff handled.
static RA8_INTERNAL csh * internal_mve_capstone (void)
 Lazily open the shared Thumb/M-class Capstone handle; nullptr on failure.
bool emulate_mve (uc_engine *uc, uint32_t pc0, const uint8_t code0[4])
 Emulate a run of consecutive auto-vectoriser MVE instructions.
bool emu_mve_nocp_emulate (uc_engine *uc, uint32_t pc)
 Emulate an MVE contiguous load/store from the NoCP UsageFault.
bool emu_mve_nocp_spurious (uint32_t pc)
 Report whether an invalid-instruction trap at pc is the bogus one Unicorn raises just after the MVE seam serviced a NoCP fault.
bool emu_mve_nocp_take (void)
 Implementation of emu_mve_nocp_take() – test-and-clear the latch.
uint64_t emu_mve_emulated_count (void)
 Implementation of emu_mve_emulated_count() – plain counter read.

Variables

static uint64_t s_mve_emulated = 0U
 Count of MVE instructions emulated this run (run-end telemetry).
static bool s_mve_nocp_handled = false
 Latch: the chunk just ended in a NoCP fault this seam serviced.
static uint32_t s_mve_resume_pc = 0U
 Address the seam advanced PC to after servicing the last NoCP fault.
static bool s_mve_resume_armed = false
 True while one bogus post-NoCP invalid report is still expected.

Detailed Description

Minimal MVE (Helium) emulation seam (see emu_seams.h).

The RA8D2 is Cortex-M85 (Armv8.1-M, has MVE) but the closest core Unicorn offers is M33 (Armv8-M, NO MVE), so the Helium instructions GCC's auto-vectoriser emits either trap as invalid (VMOV.I32) or silently decode as legacy coprocessor stores (VSTRW.32). This seam emulates the handled subset – the invalid-instruction path consumes runs of trapped MVE ops, and a one-time image scan hooks every VSTRW.32 site so the store happens before the core reaches the mis-decoding instruction. Moved verbatim out of the ra8_emulator main translation unit.

Since
0.1.0

Definition in file emu_seam_mve.c.

Enumeration Type Documentation

◆ anonymous enum

anonymous enum : uint32_t
Enumerator
k_mve_insn_len 

MVE instructions are 32-bit Thumb-2.

k_mve_q_bytes 

Bytes in a Q (128-bit) register.

k_mve_lane_shift 

32-bit lane width (two lanes per D register).

k_mve_max_run 

Loop bound: max consecutive MVE ops per trap.

Definition at line 43 of file emu_seam_mve.c.

◆ mve_mem_field_t

enum mve_mem_field_t : uint32_t
Enumerator
k_mve_mem_h1_mask 

hw1 fixed bits with P/U/W/L/Rn excluded.

k_mve_mem_h1_val 

hw1 match for immediate-offset forms.

k_mve_mem_h1_post 

hw1 match for post-index write-back forms.

k_mve_mem_h2_mask 

Isolates hw2[12:9], the coprocessor space.

k_mve_mem_h2_val 

hw2[12:9] == 0b1111 selects MVE, not FP.

k_mve_mem_bit_u 

hw1[7]: add (1) or subtract (0) the offset.

k_mve_mem_bit_w 

hw1[5]: write the computed address to Rn.

k_mve_mem_bit_l 

hw1[4]: load (1) or store (0).

k_mve_mem_size_sh 

Position of the size field in hw2.

k_mve_mem_size_msk 

Width of the size field (two bits).

k_mve_mem_sz_byte 

size 0b00: byte lanes.

k_mve_mem_sz_half 

size 0b01: halfword lanes.

k_mve_mem_sz_word 

size 0b10: word lanes.

k_mve_mem_scl_byte 

imm7 scale for byte lanes.

k_mve_mem_scl_half 

imm7 scale for halfword lanes.

k_mve_mem_scl_word 

imm7 scale for word lanes.

k_mve_qd_shift 

Qd field position in hw2.

k_mve_qd_mask 

Qd field width (three bits) after shift.

k_mve_rn_mask 

Rn field (four bits) in hw1[3:0].

k_mve_imm7_mask 

imm7 field (unscaled offset) in hw2[6:0].

Definition at line 141 of file emu_seam_mve.c.

Function Documentation

◆ emu_mve_emulated_count()

uint64_t emu_mve_emulated_count ( void )

Implementation of emu_mve_emulated_count() – plain counter read.

Count of MVE instructions emulated this run (run-end telemetry).

Definition at line 490 of file emu_seam_mve.c.

References s_mve_emulated.

Referenced by internal_run_print_stop_summary().

◆ emu_mve_nocp_emulate()

bool emu_mve_nocp_emulate ( uc_engine * uc,
uint32_t pc )

Emulate an MVE contiguous load/store from the NoCP UsageFault.

Armv8.1-M reallocates coprocessor space 0b1110 / 0b1111 to MVE, so VLDRB/VLDRH/VLDRW and VSTRB/VSTRH/VSTRW (immediate offset) reuse the legacy STC/LDC encodings byte for byte – stc p15, c7, [r0, #196] and vstrw.32 q3, [r0, #196] are both ED80 7F31. Unicorn's M33 implements neither MVE nor coprocessor 14/15, so it does not trap them as invalid instructions: it raises a NoCP UsageFault with PC still at the faulting word. This decodes that word straight from its two halfwords (capstone renders the family as a legacy stc, so it cannot be used), moves the 16-byte vector, applies write-back, and advances PC past the instruction. Anything outside the family – including the neighbouring FP stores, which differ only in hw2[12:9] and which Unicorn executes correctly – is rejected so it still faults honestly.

Parameters
[in,out]ucUnicorn engine.
[in]pcAddress of the faulting instruction.
Returns
true iff an MVE contiguous load/store was decoded and performed.
Return values
trueThe access happened and PC advanced by four bytes.
falseNot this family; no engine state changed.
Precondition
uc is stopped inside the UC_HOOK_INTR NoCP fault.
pc is the faulting instruction address, not an EXC_RETURN magic.
Postcondition
On true, PC points at the next instruction.
On false, no engine or memory state changed.
Note
Not thread-safe; the emulator is single-threaded host-side.
See also
emulate_mve() Handles the MVE forms that do trap as invalid.
Since
0.1.0

Definition at line 452 of file emu_seam_mve.c.

References emu_mem_read(), internal_mve_mem_try(), k_mve_insn_len, s_mve_emulated, s_mve_nocp_handled, s_mve_resume_armed, and s_mve_resume_pc.

Referenced by internal_on_intr().

◆ emu_mve_nocp_spurious()

bool emu_mve_nocp_spurious ( uint32_t pc)

Report whether an invalid-instruction trap at pc is the bogus one Unicorn raises just after the MVE seam serviced a NoCP fault.

Servicing the NoCP fault means writing PC and calling uc_emu_stop from inside the interrupt hook. Unicorn then re-decodes at the advanced PC and reports UC_ERR_INSN_INVALID there even though the instruction is perfectly valid – in ereader_cover the address is a plain strd r3, r3, [r7, #8] four bytes past a vstrw.32. A relaunch from that same PC executes it correctly, so the report is noise, but it is indistinguishable from a real one by decode alone: this predicate distinguishes it by address instead.

Exactly one report is absorbed per serviced fault, and only at the address the seam advanced to. A genuinely invalid instruction sitting at that address is therefore delayed by one relaunch, never suppressed: the arming is cleared on the first call, so the second trap reports normally.

Parameters
[in]pcAddress the invalid-instruction hook trapped at.
Returns
true iff this trap is the expected post-NoCP artefact.
Return values
trueAbsorb it silently and relaunch; nothing is wrong.
falseReport it; the arming (if any) is now cleared.
Precondition
Called only after every real seam in the dispatch chain declined.
Called at most once per invalid-instruction trap.
Postcondition
The one-shot arming is clear on return.
Note
Not thread-safe; the emulator is single-threaded host-side.
See also
emu_mve_nocp_emulate() Arms this.
Since
0.1.0
Postcondition
Ownership of caller-supplied storage is unchanged.

Definition at line 470 of file emu_seam_mve.c.

References s_mve_nocp_handled, s_mve_resume_armed, and s_mve_resume_pc.

Referenced by on_invalid_insn().

◆ emu_mve_nocp_take()

bool emu_mve_nocp_take ( void )

Implementation of emu_mve_nocp_take() – test-and-clear the latch.

Test and clear the "NoCP fault serviced by the MVE seam" latch.

Definition at line 482 of file emu_seam_mve.c.

References s_mve_nocp_handled.

Referenced by internal_run_inner_take_exception().

◆ emulate_mve()

bool emulate_mve ( uc_engine * uc,
uint32_t pc0,
const uint8_t code0[4] )

Emulate a run of consecutive auto-vectoriser MVE instructions.

Helium ops come in tight back-to-back runs (e.g. one VMOV.I32 then several VSTRW.32 to zero a struct). Emulating only the first and relaunching would land the next launch on another invalid instruction, which Unicorn faults on – so this consumes every consecutive handled MVE op in one trap and sets PC to the first instruction it does NOT handle, exactly mirroring the cond-select seam's "stop, then relaunch on valid code" contract. Bounded by k_mve_max_run.

Returns
true iff at least one MVE instruction was emulated.

Definition at line 407 of file emu_seam_mve.c.

References emu_mem_read(), internal_mve_capstone(), internal_mve_exec_one(), internal_mve_mem_try(), k_mve_insn_len, k_mve_max_run, memcpy(), and s_mve_emulated.

Referenced by internal_dispatch_armv81_seam().

◆ internal_mve_capstone()

RA8_INTERNAL csh * internal_mve_capstone ( void )
static

Lazily open the shared Thumb/M-class Capstone handle; nullptr on failure.

Definition at line 380 of file emu_seam_mve.c.

References RA8_INTERNAL.

Referenced by emulate_mve().

◆ internal_mve_exec_one()

RA8_INTERNAL bool internal_mve_exec_one ( uc_engine * uc,
const cs_insn * insn )
static

Execute one decoded MVE instruction (no PC change); true iff handled.

Execute one decoded mve instruction (no pc change); true iff handled; this step is contained within the emu seam mve model and uses bounded caller or module-owned storage.

Parameters
[in,out]ucUnicorn engine whose emulated state is read or updated.
[in]insnInsn input used by the operation.
Returns
The mve exec one result produced by the emu seam mve model.
Return values
trueThe mve exec one condition holds or completed successfully; false otherwise.
Precondition
Arguments satisfy the ranges documented for mve exec one.
The call executes on the emulator's single owning thread.
Postcondition
State changes remain confined to the emu seam mve model and documented output objects.
Ownership of caller-supplied storage is unchanged.
Note
The operation is synchronous and does not transfer heap ownership.
Since
0.1.0

Definition at line 359 of file emu_seam_mve.c.

References internal_mve_q_d(), k_mve_lane_shift, RA8_INTERNAL, and strstr().

Referenced by emulate_mve().

◆ internal_mve_mem_decode()

RA8_INTERNAL bool internal_mve_mem_decode ( uint16_t hw1,
uint16_t hw2,
mve_mem_op_t * op )
static

Decode an MVE contiguous load/store from its two halfwords.

Rejects anything outside the family, including the reserved size == 0b11 encoding, so an unallocated word is left to fault rather than emulated as some neighbouring instruction.

Parameters
[in]hw1First instruction halfword.
[in]hw2Second instruction halfword.
[out]opDecoded operation; untouched unless true is returned.
Returns
true iff hw1 / hw2 encode a member of this family.
Return values
trueop holds the decoded fields.
falseNot this family; op is unmodified.
Precondition
op is non-NULL.
hw1 and hw2 are the little-endian halfwords in program order.
Postcondition
On true every field of op is initialised.
No engine or memory state is touched (pure decode).
Note
Not thread-safe by inheritance only; the decode itself is pure.
Since
0.1.0

Definition at line 209 of file emu_seam_mve.c.

References mve_mem_op_t::add, k_mve_imm7_mask, k_mve_mem_bit_l, k_mve_mem_bit_u, k_mve_mem_bit_w, k_mve_mem_h1_mask, k_mve_mem_h1_post, k_mve_mem_h1_val, k_mve_mem_h2_mask, k_mve_mem_h2_val, k_mve_mem_scl_byte, k_mve_mem_scl_half, k_mve_mem_scl_word, k_mve_mem_size_msk, k_mve_mem_size_sh, k_mve_mem_sz_byte, k_mve_mem_sz_half, k_mve_mem_sz_word, k_mve_qd_mask, k_mve_qd_shift, k_mve_rn_mask, mve_mem_op_t::load, mve_mem_op_t::off, mve_mem_op_t::post, mve_mem_op_t::qd, RA8_INTERNAL, mve_mem_op_t::rn, and mve_mem_op_t::wback.

Referenced by internal_mve_mem_try().

◆ internal_mve_mem_exec()

RA8_INTERNAL void internal_mve_mem_exec ( uc_engine * uc,
const mve_mem_op_t * op )
static

Perform one decoded MVE contiguous load/store against emulated state.

The Q registers alias the FP D registers (Qn == D[2n]:D[2n+1]) which Unicorn's M33 does have, so the vector operand is read and written through them. UC_ARM_REG_D0..D15 are contiguous in Unicorn's enum (checked), but the core-register ids are NOT – SP, LR and PC sit far from R0..R12 – so the base register is resolved through k_arm_reg_id. Indexing UC_ARM_REG_R0 + rn instead would read an unrelated register whenever Rn is SP, which the toolchain does emit (vstrw.32 q1, [sp, #4] assembles to ED8D 3F01).

Parameters
[in,out]ucUnicorn engine.
[in]opDecoded operation from internal_mve_mem_decode.
Returns
Nothing.
Precondition
op was produced by a successful internal_mve_mem_decode.
uc is stopped inside the NoCP fault for this instruction.
Postcondition
The 16-byte vector has moved in the requested direction.
Rn holds the computed address iff op->wback.
Note
Not thread-safe; the emulator is single-threaded host-side.
Since
0.1.0

Definition at line 265 of file emu_seam_mve.c.

References mve_mem_op_t::add, emu_mem_read(), emu_mem_write(), k_arm_reg_id, k_mve_q_bytes, mve_mem_op_t::load, memcpy(), mve_mem_op_t::off, mve_mem_op_t::post, mve_mem_op_t::qd, RA8_INTERNAL, mve_mem_op_t::rn, and mve_mem_op_t::wback.

Referenced by internal_mve_mem_try().

◆ internal_mve_mem_try()

RA8_INTERNAL bool internal_mve_mem_try ( uc_engine * uc,
const uint8_t code[4] )
static

Perform the MVE contiguous load/store at code, if that is what it is.

Shared by both arrival paths. Unicorn delivers the FIRST instruction of a run as a NoCP UsageFault through UC_HOOK_INTR, but once that handler has written PC and stopped the engine, the NEXT instruction of the same run arrives at the invalid-instruction hook instead – verified standalone against libunicorn with two consecutive vstrw.32. Both callers therefore need the identical decode-and-access step; only PC bookkeeping differs.

Parameters
[in,out]ucUnicorn engine.
[in]codeThe four instruction bytes to decode.
Returns
true iff code was an MVE contiguous load/store and was performed.
Return values
trueThe 16-byte access happened; PC is NOT changed.
falseNot this family; no state changed.
Precondition
code holds four valid instruction bytes.
uc is stopped in a hook callback.
Postcondition
On true the access (and any write-back) has been applied.
PC is never modified here; the caller owns it.
Note
Not thread-safe; the emulator is single-threaded host-side.
Since
0.1.0

Definition at line 317 of file emu_seam_mve.c.

References internal_mve_mem_decode(), internal_mve_mem_exec(), k_byte_bits, and RA8_INTERNAL.

Referenced by emu_mve_nocp_emulate(), and emulate_mve().

◆ internal_mve_q_d()

RA8_INTERNAL int internal_mve_q_d ( unsigned int qreg,
bool high )
static

Map capstone Q-reg qreg to its Unicorn D-register half (low/high).

Map capstone q-reg qreg to its unicorn d-register half (low/high); this step is contained within the emu seam mve model and uses bounded caller or module-owned storage.

Parameters
[in]qregQreg input used by the operation.
[in]highHigh input used by the operation.
Returns
The mve q d result produced by the emu seam mve model.
Return values
valueThe operation-specific mve q d value.
Precondition
Arguments satisfy the ranges documented for mve q d.
The call executes on the emulator's single owning thread.
Postcondition
State changes remain confined to the emu seam mve model and documented output objects.
Ownership of caller-supplied storage is unchanged.
Note
The operation is synchronous and does not transfer heap ownership.
Since
0.1.0

Definition at line 341 of file emu_seam_mve.c.

References RA8_INTERNAL.

Referenced by internal_mve_exec_one().

Variable Documentation

◆ s_mve_emulated

uint64_t s_mve_emulated = 0U
static

Count of MVE instructions emulated this run (run-end telemetry).

Definition at line 50 of file emu_seam_mve.c.

Referenced by emu_mve_emulated_count(), emu_mve_nocp_emulate(), and emulate_mve().

◆ s_mve_nocp_handled

bool s_mve_nocp_handled = false
static

Latch: the chunk just ended in a NoCP fault this seam serviced.

Unicorn returns UC_ERR_EXCEPTION from uc_emu_start for the NoCP UsageFault even when the UC_HOOK_INTR callback fully handled it and stopped the engine, so the status alone cannot distinguish a serviced MVE access from a real unhandled exception. This latch carries that distinction to the run loop, which clears it via emu_mve_nocp_take and relaunches instead of ending the run – the same shape as the MPU and divide-by-zero latches.

Note
Read and cleared only by emu_mve_nocp_take.
Warning
Do not set this anywhere the access did not actually happen; the run loop would then swallow a genuine unhandled exception.
Since
0.1.0

Definition at line 67 of file emu_seam_mve.c.

Referenced by emu_mve_nocp_emulate(), emu_mve_nocp_spurious(), and emu_mve_nocp_take().

◆ s_mve_resume_armed

bool s_mve_resume_armed = false
static

True while one bogus post-NoCP invalid report is still expected.

Set when a NoCP fault is serviced, cleared by the first emu_mve_nocp_spurious call whether or not it matched, so at most one report is ever absorbed per serviced fault.

Note
Read and cleared only by emu_mve_nocp_spurious.
Warning
Leaving this set across unrelated code would mask a real fault.
Since
0.1.0

Definition at line 93 of file emu_seam_mve.c.

Referenced by emu_mve_nocp_emulate(), and emu_mve_nocp_spurious().

◆ s_mve_resume_pc

uint32_t s_mve_resume_pc = 0U
static

Address the seam advanced PC to after servicing the last NoCP fault.

Paired with s_mve_resume_armed to absorb the one bogus invalid-instruction report Unicorn emits at this address; see emu_mve_nocp_spurious for why that report happens.

Note
Meaningful only while s_mve_resume_armed is set.
Warning
Never treat this as the current PC; it is a one-shot expectation.
Since
0.1.0

Definition at line 80 of file emu_seam_mve.c.

Referenced by emu_mve_nocp_emulate(), and emu_mve_nocp_spurious().