An EL2 monitor/hypervisor and toolchain that boots macOS 27 on the ASUS A16 (Snapdragon X2 Elite), with an Adreno Metal plug-in and IOGPU kext, and air2ir3, an AIR to Mesa ir3 shader compiler. MIT, with GPL-2.0 components listed in LICENSE and THIRD_PARTY_NOTICES.md. No Apple, Qualcomm, ASUS or Microsoft binaries are included.
109 lines
3.8 KiB
C
109 lines
3.8 KiB
C
/* SPDX-License-Identifier: MIT */
|
|
/* See mmu.h. */
|
|
#include "mmu.h"
|
|
|
|
/* Index bits per level. A table is one granule of 8-byte entries, so a 16K
|
|
* granule indexes 11 bits per level and a 4K granule 9 -- which is why the 16K
|
|
* and 4K level boundaries do not line up and a shadow is a re-translation
|
|
* rather than a structural rewrite of the guest's tables. */
|
|
static uint32_t index_bits(uint32_t granule_shift)
|
|
{
|
|
return granule_shift - 3;
|
|
}
|
|
|
|
/* Lowest VA bit resolved by `level`. Level 3 resolves the granule offset. */
|
|
static uint32_t level_shift(uint32_t granule_shift, uint32_t level)
|
|
{
|
|
return granule_shift + (3 - level) * index_bits(granule_shift);
|
|
}
|
|
|
|
uint64_t mmu_level_size(uint32_t granule_shift, uint32_t level)
|
|
{
|
|
return 1UL << level_shift(granule_shift, level);
|
|
}
|
|
|
|
uint32_t mmu_index(uint32_t granule_shift, uint32_t level, uint64_t va)
|
|
{
|
|
uint32_t bits = index_bits(granule_shift);
|
|
return (uint32_t)((va >> level_shift(granule_shift, level)) & ((1UL << bits) - 1));
|
|
}
|
|
|
|
int mmu_block_allowed(uint32_t granule_shift, uint32_t level)
|
|
{
|
|
/* 4K permits 1GB blocks at L1 and 2MB at L2. 16K and 64K permit blocks only
|
|
* at L2 -- an L1 block would span 64GB and 4TB respectively, which the
|
|
* architecture does not encode. Getting this wrong turns a table descriptor
|
|
* into a huge bogus mapping, so it is checked rather than assumed. */
|
|
if (granule_shift == 12) return level == 1 || level == 2;
|
|
return level == 2;
|
|
}
|
|
|
|
int mmu_start_level(uint32_t granule_shift, uint32_t txsz)
|
|
{
|
|
if (txsz > 39 || txsz < 16) return -1;
|
|
uint32_t va_bits = 64 - txsz;
|
|
if (va_bits <= granule_shift) return -1;
|
|
uint32_t bits = index_bits(granule_shift);
|
|
/* The first level whose index reaches the top VA bit. */
|
|
uint32_t levels = (va_bits - granule_shift + bits - 1) / bits;
|
|
if (levels < 1 || levels > 4) return -1;
|
|
return (int)(4 - levels);
|
|
}
|
|
|
|
int mmu_walk(const struct mmu_config *cfg, uint64_t va, mmu_read_fn read, void *ctx,
|
|
struct mmu_leaf *out)
|
|
{
|
|
for (unsigned n = 0; n < sizeof(*out); n++) ((uint8_t *)out)[n] = 0;
|
|
|
|
if (cfg->granule_shift != 12 && cfg->granule_shift != 14 && cfg->granule_shift != 16)
|
|
return -1;
|
|
int level = mmu_start_level(cfg->granule_shift, cfg->txsz);
|
|
if (level < 0) return -1;
|
|
|
|
uint64_t table = cfg->root & PT_OA_MASK;
|
|
uint32_t va_bits = 64 - cfg->txsz;
|
|
/* Only the bits this regime translates; the caller is responsible for
|
|
* having picked the right root for the top bits. */
|
|
uint64_t address = va & (va_bits >= 64 ? ~0UL : ((1UL << va_bits) - 1));
|
|
|
|
for (; level <= 3; level++) {
|
|
uint64_t descriptor;
|
|
uint64_t slot = table + 8UL * mmu_index(cfg->granule_shift, (uint32_t)level, address);
|
|
if (read(ctx, slot, &descriptor)) return -1;
|
|
|
|
if (!(descriptor & PT_VALID)) {
|
|
out->level = (uint32_t)level;
|
|
out->valid = 0;
|
|
return 0;
|
|
}
|
|
|
|
int is_table = (descriptor & PT_TABLE) && level < 3;
|
|
if (is_table) {
|
|
table = descriptor & PT_OA_MASK;
|
|
continue;
|
|
}
|
|
|
|
/* A level-3 leaf sets bit 1; a block at levels 0-2 clears it. Anything
|
|
* else is a malformed descriptor, not a mapping. */
|
|
if (level == 3) {
|
|
if (!(descriptor & PT_TABLE)) {
|
|
out->level = 3;
|
|
out->valid = 0;
|
|
return 0;
|
|
}
|
|
} else if (!mmu_block_allowed(cfg->granule_shift, (uint32_t)level)) {
|
|
return -1;
|
|
}
|
|
|
|
uint64_t size = mmu_level_size(cfg->granule_shift, (uint32_t)level);
|
|
out->descriptor = descriptor;
|
|
out->base = address & ~(size - 1);
|
|
out->size = size;
|
|
out->pa = (descriptor & PT_OA_MASK & ~(size - 1)) | (address & (size - 1));
|
|
out->level = (uint32_t)level;
|
|
out->valid = 1;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|