target/arm: Implement MTE tag-checking functions for FEAT_MOPS

The FEAT_MOPS instructions need a couple of helper routines that
check for MTE tag failures:
 * mte_mops_probe() checks whether there is going to be a tag
   error in the next up-to-a-page worth of data
 * mte_check_fail() is an existing function to record the fact
   of a tag failure, which we need to make global so we can
   call it from helper-a64.c

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20230912140434.1333369-7-peter.maydell@linaro.org
This commit is contained in:
Peter Maydell 2023-09-12 15:04:28 +01:00
parent aa03378bcc
commit 8163998920
2 changed files with 80 additions and 2 deletions

View File

@ -1272,6 +1272,34 @@ FIELD(MTEDESC, SIZEM1, 12, SIMD_DATA_BITS - 12) /* size - 1 */
bool mte_probe(CPUARMState *env, uint32_t desc, uint64_t ptr);
uint64_t mte_check(CPUARMState *env, uint32_t desc, uint64_t ptr, uintptr_t ra);
/**
* mte_mops_probe: Check where the next MTE failure is for a FEAT_MOPS operation
* @env: CPU env
* @ptr: start address of memory region (dirty pointer)
* @size: length of region (guaranteed not to cross a page boundary)
* @desc: MTEDESC descriptor word (0 means no MTE checks)
* Returns: the size of the region that can be copied without hitting
* an MTE tag failure
*
* Note that we assume that the caller has already checked the TBI
* and TCMA bits with mte_checks_needed() and an MTE check is definitely
* required.
*/
uint64_t mte_mops_probe(CPUARMState *env, uint64_t ptr, uint64_t size,
uint32_t desc);
/**
* mte_check_fail: Record an MTE tag check failure
* @env: CPU env
* @desc: MTEDESC descriptor word
* @dirty_ptr: Failing dirty address
* @ra: TCG retaddr
*
* This may never return (if the MTE tag checks are configured to fault).
*/
void mte_check_fail(CPUARMState *env, uint32_t desc,
uint64_t dirty_ptr, uintptr_t ra);
static inline int allocation_tag_from_addr(uint64_t ptr)
{
return extract64(ptr, 56, 4);

View File

@ -617,8 +617,8 @@ static void mte_async_check_fail(CPUARMState *env, uint64_t dirty_ptr,
}
/* Record a tag check failure. */
static void mte_check_fail(CPUARMState *env, uint32_t desc,
uint64_t dirty_ptr, uintptr_t ra)
void mte_check_fail(CPUARMState *env, uint32_t desc,
uint64_t dirty_ptr, uintptr_t ra)
{
int mmu_idx = FIELD_EX32(desc, MTEDESC, MIDX);
ARMMMUIdx arm_mmu_idx = core_to_aa64_mmu_idx(mmu_idx);
@ -991,3 +991,53 @@ uint64_t HELPER(mte_check_zva)(CPUARMState *env, uint32_t desc, uint64_t ptr)
done:
return useronly_clean_ptr(ptr);
}
uint64_t mte_mops_probe(CPUARMState *env, uint64_t ptr, uint64_t size,
uint32_t desc)
{
int mmu_idx, tag_count;
uint64_t ptr_tag, tag_first, tag_last;
void *mem;
bool w = FIELD_EX32(desc, MTEDESC, WRITE);
uint32_t n;
mmu_idx = FIELD_EX32(desc, MTEDESC, MIDX);
/* True probe; this will never fault */
mem = allocation_tag_mem_probe(env, mmu_idx, ptr,
w ? MMU_DATA_STORE : MMU_DATA_LOAD,
size, MMU_DATA_LOAD, true, 0);
if (!mem) {
return size;
}
/*
* TODO: checkN() is not designed for checks of the size we expect
* for FEAT_MOPS operations, so we should implement this differently.
* Maybe we should do something like
* if (region start and size are aligned nicely) {
* do direct loads of 64 tag bits at a time;
* } else {
* call checkN()
* }
*/
/* Round the bounds to the tag granule, and compute the number of tags. */
ptr_tag = allocation_tag_from_addr(ptr);
tag_first = QEMU_ALIGN_DOWN(ptr, TAG_GRANULE);
tag_last = QEMU_ALIGN_DOWN(ptr + size - 1, TAG_GRANULE);
tag_count = ((tag_last - tag_first) / TAG_GRANULE) + 1;
n = checkN(mem, ptr & TAG_GRANULE, ptr_tag, tag_count);
if (likely(n == tag_count)) {
return size;
}
/*
* Failure; for the first granule, it's at @ptr. Otherwise
* it's at the first byte of the nth granule. Calculate how
* many bytes we can access without hitting that failure.
*/
if (n == 0) {
return 0;
} else {
return n * TAG_GRANULE - (ptr - tag_first);
}
}