Skip to content

Instantly share code, notes, and snippets.

@winocm
Created April 13, 2014 21:34
Show Gist options
  • Save winocm/10603326 to your computer and use it in GitHub Desktop.
Save winocm/10603326 to your computer and use it in GitHub Desktop.
/*
* asdfghjkl;'
*/
#include <mach/machine/vm_types.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#pragma mark - MMU TLB lookup functionality
#define TB_BUCKET_NUM 32
#define TB_BUCKET_SIZE 8
#define tb_get_shadow_pa(tb_ent, va) ((tb_ent) ^ (va))
#define tb_get_shadow_va(tb_ent, pa) ((tb_ent) ^ (pa))
#define tb_set_shadow(tb_ent, va, pa) ((tb_ent) = (va) ^ (pa))
/*
* The TLB entries stores in each 'tb_entry_t' contain a shadowed
* mapping. Effectively, the VA and PA is xored together to form one
* unified member in the array. See tb_get_shadow_va and tb_get_shadow_pa
* for how the address entries are retrieved.
*
* Did you know? The Java version of *this exact same code* is 20 times
* slower than its C counterpart! That's your fun fact for the day.
*/
typedef struct __tb_entry {
uint32_t va_key;
uint32_t shadow;
uint32_t granularity;
uint32_t domain:4;
uint32_t ap:2;
} tb_entry_t;
typedef struct __tb_context {
uint8_t tlb_writepos[TB_BUCKET_NUM];
uint8_t tlb_readpos[TB_BUCKET_NUM];
tb_entry_t tlb_hashtbl[TB_BUCKET_NUM][TB_BUCKET_SIZE];
} tb_context_t;
tb_context_t tb_context = { };
void tb_insert(uint32_t va, uint32_t pa, uint32_t ap, uint32_t domain,
uint32_t granularity);
tb_entry_t *tb_probe(uint32_t va);
void tb_flush(void);
static inline uint32_t tb_get_pg_hash(uint32_t va, uint32_t pgshift,
uint32_t nmemb)
{
uint32_t addr_hash = (va >> pgshift);
return (addr_hash ^ (addr_hash >> (pgshift >> 1)) ^ (addr_hash >> pgshift))
% nmemb;
}
void tb_insert(uint32_t va, uint32_t pa, uint32_t ap, uint32_t domain,
uint32_t granularity)
{
uint32_t bucket = tb_get_pg_hash(va, PAGE_SHIFT, TB_BUCKET_NUM);
tb_set_shadow(tb_context.
tlb_hashtbl[bucket][tb_context.tlb_writepos[bucket]].shadow,
va, pa);
tb_context.tlb_hashtbl[bucket][tb_context.tlb_writepos[bucket]].va_key = va;
tb_context.tlb_hashtbl[bucket][tb_context.tlb_writepos[bucket]].ap = ap;
tb_context.tlb_hashtbl[bucket][tb_context.tlb_writepos[bucket]].domain =
domain;
tb_context.tlb_hashtbl[bucket][tb_context.tlb_writepos[bucket]].
granularity = granularity;
tb_context.tlb_readpos[bucket] = tb_context.tlb_writepos[bucket];
if (++tb_context.tlb_writepos[bucket] == TB_BUCKET_SIZE)
tb_context.tlb_writepos[bucket] = 0;
#if DEBUG
printf
("tb_insert: 0x%08x -> 0x%08x, hash bucket %d, populated %d (%x,%x)\n",
va, pa, bucket, tb_context.tlb_writepos[bucket], ap, domain);
#endif
}
tb_entry_t *tb_probe(uint32_t va)
{
uint32_t bucket = tb_get_pg_hash(va, PAGE_SHIFT, TB_BUCKET_NUM);
uint32_t i, j;
for (j = 0, i = tb_context.tlb_readpos[bucket]; j < TB_BUCKET_SIZE;
j++, i--) {
if (i == -1)
i = TB_BUCKET_SIZE - 1;
if ((tb_context.tlb_hashtbl[bucket][i].va_key <= va)
&&
((tb_context.tlb_hashtbl[bucket][i].va_key +
tb_context.tlb_hashtbl[bucket][i].granularity) > va)) {
#if DEBUG
uint32_t shadow = tb_context.tlb_hashtbl[bucket][i].shadow;
uint32_t pa = tb_get_shadow_pa(shadow, va);
tb_context.tlb_readpos[bucket] = i;
printf
("Victim found in TLB: Shadow 0x%08x, VA 0x%08x, PA 0x%08x, AP 0x%08x, Domain 0x%08x Size 0x%08x\n",
shadow, va, pa, tb_context.tlb_hashtbl[bucket][i].ap,
tb_context.tlb_hashtbl[bucket][i].domain,
tb_context.tlb_hashtbl[bucket][i].granularity);
#endif
return &tb_context.tlb_hashtbl[bucket][i];
}
}
return (tb_entry_t *) NULL;
}
void tb_flush(void)
{
uint32_t j;
bzero((void *) &tb_context.tlb_hashtbl,
sizeof(tb_entry_t) * TB_BUCKET_NUM * TB_BUCKET_SIZE);
for (j = 0; j < TB_BUCKET_NUM; j++) {
tb_context.tlb_readpos[j] = 0;
tb_context.tlb_writepos[j] = 0;
}
}
#pragma mark - Memory access routines
/*
* Each memory 'region' has its own Read and Write functions, much
* like any other emulator would. For regions that do not have a valid
* 'area', reads and writes can either be set to RAZ/WI, ... or we could
* raise a 'bus error' like condition and assert an external precise
* abort. Whatever.
*
* Note:
* The read function reads into an allocated buffer, write writes into
* an allocated buffer.
*/
#define MEMREGION_NUM 32
#define MEMORY_REGION_SUCCESS 0
#define MEMORY_REGION_NO_SUCH_DEVICE 1
#define MEMORY_REGION_WRITEFN_FAILED 2
#define MEMORY_REGION_READFN_FAILED 3
#define MEMORY_REGION_ABORTED 4
typedef uint32_t memory_error_t;
typedef memory_error_t(*memory_write_fn_t) (uint32_t offset, void *read,
uint32_t size);
typedef memory_error_t(*memory_read_fn_t) (uint32_t offset, void *write,
uint32_t size);
typedef struct __memory_region {
uint32_t phys;
uint32_t size;
memory_write_fn_t write;
memory_read_fn_t read;
} memory_region_t;
typedef struct __memory_context {
memory_region_t region[MEMREGION_NUM];
int reftrack;
} memory_context_t;
memory_context_t memory_context = { };
void memory_region_register(uint32_t phys, uint32_t size,
memory_read_fn_t readfn, memory_write_fn_t writefn);
memory_error_t memory_read(uint32_t phys, void *readbuf, uint32_t size);
memory_error_t memory_write(uint32_t phys, void *readbuf, uint32_t size);
#define memory_write_guts(off, val, type) \
do { \
type __v = val; \
memory_write(off, (void*)&__v, sizeof(type)); \
} while(0);
#define memory_write_uint64(off, val) memory_write_guts(off, val, uint64_t)
#define memory_write_uint32(off, val) memory_write_guts(off, val, uint32_t)
#define memory_write_uint16(off, val) memory_write_guts(off, val, uint16_t)
#define memory_write_uint8(off, val) memory_write_guts(off, val, uint8_t)
#define memory_read_inline_guts(name, type) \
type memory_read_ ##name (uint32_t off) { \
type __v; \
memory_read(off, (void*)&__v, sizeof(type)); \
return __v; \
}
uint64_t memory_read_uint64(uint32_t off);
uint32_t memory_read_uint32(uint32_t off);
uint16_t memory_read_uint16(uint32_t off);
uint8_t memory_read_uint8(uint32_t off);
memory_read_inline_guts(uint64, uint64_t);
memory_read_inline_guts(uint32, uint32_t);
memory_read_inline_guts(uint16, uint16_t);
memory_read_inline_guts(uint8, uint8_t);
void memory_region_register(uint32_t phys, uint32_t size,
memory_read_fn_t readfn, memory_write_fn_t writefn)
{
memory_context.region[memory_context.reftrack].phys = phys;
memory_context.region[memory_context.reftrack].size = size;
memory_context.region[memory_context.reftrack].read = readfn;
memory_context.region[memory_context.reftrack].write = writefn;
memory_context.reftrack++;
#if DEBUG
printf("%s: registered physical region 0x%08x->0x%08x, size %d\n",
__FUNCTION__, phys, phys + size, size);
#endif
}
memory_error_t memory_read(uint32_t phys, void *readbuf, uint32_t size)
{
int i;
for (i = 0; i < memory_context.reftrack; i++) {
if ((memory_context.region[i].phys <= phys)
&& (phys <
(memory_context.region[i].phys +
memory_context.region[i].size))) {
#if DEBUG
printf("%s: Memory READ of PA 0x%08x, region %d, size %d\n",
__FUNCTION__, phys, i, size);
#endif
return memory_context.region[i].read(phys, readbuf, size);
}
}
return MEMORY_REGION_NO_SUCH_DEVICE;
}
memory_error_t memory_write(uint32_t phys, void *readbuf, uint32_t size)
{
int i;
for (i = 0; i < memory_context.reftrack; i++) {
if ((memory_context.region[i].phys <= phys)
&& (phys <
(memory_context.region[i].phys +
memory_context.region[i].size))) {
#if DEBUG
printf("%s: Memory WRITE of PA 0x%08x, region %d, size %d\n",
__FUNCTION__, phys, i, size);
#endif
return memory_context.region[i].write(phys, readbuf, size);
}
}
return MEMORY_REGION_NO_SUCH_DEVICE;
}
#pragma mark - Address translation routines
/*
* Address Translation
*
* Address translation is relatively simple on ARM platforms. The first
* level descriptor is retrieved from the TTB base, then we check the last
* bits to see if we need to fetch a L2 descriptor. If we need to, we should
* and then we consider the translation successful if said descriptor
* is not a fault one.
*
* Successful translations are stored in our Translation Lookaside Buffer
* (TLB). See the tb_xxx routines above.
*/
static uint32_t ttbr_base = 0;
static uint32_t mmu_enabled = 1;
static uint32_t mmu_dacr = 0x01; /* temp */
uint32_t mmu_translate_address(uint32_t mva, uint32_t priv, uint32_t w,
uint32_t * pa, uint32_t * fault);
void mmu_set_ttb(uint32_t ttbr);
void mmu_set_enabled(void);
void mmu_set_disabled(void);
void mmu_toggle(void);
#define mmu_tlb_flush tb_flush
#define L1_TTE_SECT_SHIFT 20
#define L1_TTE_SECT_MASK 0xfff00000
#define L1_TTE_SECT_SIZE (1 << L1_TTE_SECT_SHIFT)
#define L1_TTE_OFFSET(addr) (((addr & L1_TTE_SECT_MASK) >> L1_TTE_SECT_SHIFT) << 2)
#define L1_TTE_COARSE_MASK 0xfffffc00;
#define L1_TTB_DESCRIPTOR_TYPE_FAULT 0
#define L1_TTB_DESCRIPTOR_TYPE_PAGE 1
#define L1_TTB_DESCRIPTOR_TYPE_SECTION 2
#define L1_TTB_DESCRIPTOR_MASK 3
#define L2_PTE_OFFSET(addr) ((addr & 0xff000) >> 10)
#define L2_PTE_DESCRIPTOR_FAULT 0
#define L2_PTE_DESCRIPTOR_64K 1
#define L2_PTE_DESCRIPTOR_4K 2
#define L2_PTE_DESCRIPTOR_1K 3
#define L2_PTE_DESCRIPTOR_MASK 3
#define L2_PTE_64K_MASK 0xffff0000
#define L2_PTE_4K_MASK 0xfffff000
#define L2_PTE_4K 4096
#define L2_PTE_64K 65536
#define FSR_ALIGNMENT 1
#define FSR_DEBUG 2
#define FSR_ACCESS_SECT 3
#define FSR_CACHE 4
#define FSR_TRANS_SECT 5
#define FSR_ACCESS_PAGE 6
#define FSR_TRANS_PAGE 7
#define FSR_EXTABT 8
#define FSR_DOMAIN 9
#define FSR_RESV 10
#define FSR_DOMAIN_PAGE 11
#define FSR_EXT_L1 12
#define FSR_PERM_SECT 13
#define FSR_EXT_L2 14
#define FSR_PERM_PAGE 15
#define DACR_NO_ACCESS 0
#define DACR_CLIENT 1
#define DACR_RESERVED 2
#define DACR_MANAGER 3
uint32_t mmu_translate_address(uint32_t mva, uint32_t priv, uint32_t w,
uint32_t * pa, uint32_t * fault)
{
uint32_t pa_offset, va_offset, l1_desc, l2_desc, l2_desc_addr, domain,
ap_bits;
int granularity, is_sect = 0;
memory_error_t err;
tb_entry_t *tlb_ent;
if (!mmu_enabled) {
pa_offset = va_offset = 0;
goto finalize;
}
/*
* Check the TLB for any address translations.
*/
if ((tlb_ent = tb_probe(mva)) != (tb_entry_t *) NULL) {
va_offset = mva;
pa_offset = tb_get_shadow_pa(tlb_ent->shadow, mva);
ap_bits = tlb_ent->ap;
domain = tlb_ent->domain;
goto ap_verify;
}
/*
* Verify the table is word aligned.
*/
if (ttbr_base & 3) {
*fault = FSR_ALIGNMENT | (w << 11);;
return 1;
}
err =
memory_read(L1_TTE_OFFSET(mva) + ttbr_base, &l1_desc, sizeof(uint32_t));
if (err) {
#if DEBUG
printf("%s: external abort on L1 descriptor fetch!\n", __FUNCTION__);
#endif
*fault = FSR_EXT_L1 | (w << 11);
return 1;
}
#if DEBUG
printf("mmu_translate_address: l1_desc: 0x%08x\n", l1_desc);
#endif
/*
* Check memory domain against DACR.
*/
domain = (l1_desc >> 5) & 0xF;
/*
* Cap the write bit to 1.
*/
w &= 1;
/*
* Check the descriptor type.
*/
switch (l1_desc & L1_TTB_DESCRIPTOR_MASK) {
case L1_TTB_DESCRIPTOR_TYPE_FAULT:
*fault = FSR_TRANS_SECT | (domain << 4) | (w << 11);
return 1;
case L1_TTB_DESCRIPTOR_TYPE_SECTION:
pa_offset = l1_desc & L1_TTE_SECT_MASK;
va_offset = mva & L1_TTE_SECT_MASK;
ap_bits = (l1_desc >> 10) & 3;
granularity = L1_TTE_SECT_SIZE;
is_sect = 1;
goto insert_tlb;
case L1_TTB_DESCRIPTOR_TYPE_PAGE:
is_sect = 0;
l2_desc_addr = l1_desc & L1_TTE_COARSE_MASK + L2_PTE_OFFSET(mva);
break;
default:
printf("???? type %d\n", l1_desc & L1_TTB_DESCRIPTOR_MASK);
*fault = FSR_EXT_L1 | (w << 11);
return 1;
}
/*
* Get second level table.
*/
printf("%s: l2_desc_addr: 0x%08x\n", __FUNCTION__, l2_desc_addr);
err = memory_read(l2_desc_addr, &l2_desc, sizeof(uint32_t));
if (err) {
#if DEBUG
printf("%s: external abort on L2 descriptor fetch!\n", __FUNCTION__);
#endif
*fault = FSR_EXT_L2 | (w << 11);
return 1;
}
/*
* Look at the descriptor bits
*/
switch (l2_desc & L2_PTE_DESCRIPTOR_MASK) {
case L2_PTE_DESCRIPTOR_FAULT:
*fault = FSR_TRANS_PAGE | (domain << 4) | (w << 11);
return 1;
case L2_PTE_DESCRIPTOR_64K:
pa_offset = l2_desc & L2_PTE_64K_MASK;
va_offset = mva & L2_PTE_64K_MASK;
ap_bits = (l1_desc >> 14) & 3;
granularity = L2_PTE_64K;
is_sect = 0;
goto insert_tlb;
case L2_PTE_DESCRIPTOR_4K:
pa_offset = l2_desc & L2_PTE_4K_MASK;
va_offset = mva & L2_PTE_4K_MASK;
ap_bits = (l1_desc >> 10) & 3;
granularity = L2_PTE_4K;
is_sect = 0;
goto insert_tlb;
default:
printf("???? %d\n", l2_desc & L2_PTE_DESCRIPTOR_FAULT);
*fault = FSR_EXT_L2 | (w << 11);
return 1;
}
insert_tlb:
tb_insert(va_offset, pa_offset, ap_bits, domain, granularity);
ap_verify:
/*
* Verify against DACR.
*/
#if DEBUG
printf("%s: verifying against DACR ... current DACR 0x%08x\n", __FUNCTION__,
mmu_dacr);
#endif
switch (mmu_dacr >> (domain * 2) & 3) {
case DACR_NO_ACCESS:
case DACR_RESERVED:
*fault =
(is_sect ? FSR_DOMAIN : FSR_DOMAIN_PAGE) | (domain << 4) | (w <<
11);
return 1;
case DACR_CLIENT:
break;
case DACR_MANAGER:
goto finalize;
}
/*
* Verify AP bits. (kinda broken.. no S/R-bit in SCTLR to respect!)
*/
#if DEBUG
printf("%s: verifying AP bits... bits %x\n", __FUNCTION__, ap_bits);
#endif
switch (ap_bits) {
case 0:
/*
* Read Only
*/
if (w) {
break;
}
goto finalize;
case 1:
/*
* Privileged Read + Write, User None
*/
if (!priv) {
break;
}
goto finalize;
case 2:
/*
* Privileged Read + Write, User Read Only
*/
if (!priv && w) {
break;
}
goto finalize;
case 3:
default:
/*
* Privileged Read + Write, User Read + Write
*/
goto finalize;
}
*fault =
(is_sect ? FSR_PERM_SECT : FSR_PERM_PAGE) | (domain << 4) | (w << 11);;
return 1;
finalize:
*pa = mva - va_offset + pa_offset;
*fault = 0;
return 0;
}
void mmu_set_ttb(uint32_t ttbr)
{
#if DEBUG
printf("%s: TTBR set to 0x%08x\n", __FUNCTION__, ttbr);
#endif
ttbr_base = ttbr;
}
void mmu_set_enabled(void)
{
#if DEBUG
printf("%s: enabling MMU status (%d)\n", __FUNCTION__, mmu_enabled);
#endif
mmu_enabled = 1;
tb_flush();
}
void mmu_set_disabled(void)
{
#if DEBUG
printf("%s: disabling MMU status (%d)\n", __FUNCTION__, mmu_enabled);
#endif
mmu_enabled = 0;
}
void mmu_toggle(void)
{
#if DEBUG
printf("%s: toggling MMU status (%d)\n", __FUNCTION__, mmu_enabled);
#endif
mmu_enabled ^= 1;
}
/* Implement privileged check, hook with CPSR state & 0x1F != 0x10. */
memory_error_t mmu_memory_read(uint32_t va, void *readbuf, uint32_t size,
uint32_t * fsr)
{
uint32_t translated_addr;
memory_error_t err;
if (!mmu_enabled)
return memory_read(va, readbuf, size);
err = mmu_translate_address(va, 1, 0, &translated_addr, fsr);
if (err)
return MEMORY_REGION_ABORTED;
#if DEBUG
printf("%s: VA 0x%08x => PA 0x%08x, read of size %d\n", __FUNCTION__, va,
translated_addr, size);
#endif
return memory_read(translated_addr, readbuf, size);
}
memory_error_t mmu_memory_write(uint32_t va, void *readbuf, uint32_t size,
uint32_t * fsr)
{
uint32_t translated_addr;
memory_error_t err;
if (!mmu_enabled)
return memory_write(va, readbuf, size);
err = mmu_translate_address(va, 1, 1, &translated_addr, fsr);
if (err)
return MEMORY_REGION_ABORTED;
#if DEBUG
printf("%s: VA 0x%08x => PA 0x%08x, write of size %d\n", __FUNCTION__, va,
translated_addr, size);
#endif
return memory_write(translated_addr, readbuf, size);
}
/* THESE DO NOT IMPLEMENT ABORT CHECKING NEVER USE */
#define mmu_memory_write_guts(off, val, type) \
do { \
type __v = val; \
uint32_t fsr; \
mmu_memory_write(off, (void*)&__v, sizeof(type), &fsr); \
} while(0);
#define mmu_memory_write_uint64(off, val) mmu_memory_write_guts(off, val, uint64_t)
#define mmu_memory_write_uint32(off, val) mmu_memory_write_guts(off, val, uint32_t)
#define mmu_memory_write_uint16(off, val) mmu_memory_write_guts(off, val, uint16_t)
#define mmu_memory_write_uint8(off, val) mmu_memory_write_guts(off, val, uint8_t)
#define mmu_memory_read_inline_guts(name, type) \
type mmu_memory_read_ ##name (uint32_t off) { \
type __v; \
uint32_t fsr; \
mmu_memory_read(off, (void*)&__v, sizeof(type), &fsr); \
return __v; \
}
uint64_t mmu_memory_read_uint64(uint32_t off);
uint32_t mmu_memory_read_uint32(uint32_t off);
uint16_t mmu_memory_read_uint16(uint32_t off);
uint8_t mmu_memory_read_uint8(uint32_t off);
mmu_memory_read_inline_guts(uint64, uint64_t);
mmu_memory_read_inline_guts(uint32, uint32_t);
mmu_memory_read_inline_guts(uint16, uint16_t);
mmu_memory_read_inline_guts(uint8, uint8_t);
#pragma mark - RAM emulation
/*
* RAM is dynamically allocated of size 32MB. We set all of the addresses
* to 0xAA55AA55 patterns.. much like real memory. The boot loader is responsible
* for zeroing out and initializing memory.
*
* The memory region routines are useful for reading and writing to physical
* addresses.
*/
static void *ram_buffer = NULL;
static int ram_size = 32 * 1024 * 1024;
memory_error_t ram_read(uint32_t offset, void *read, uint32_t size)
{
if (offset > ram_size)
return MEMORY_REGION_READFN_FAILED;
bcopy((void *) ((uintptr_t) ram_buffer + offset), read, size);
return MEMORY_REGION_SUCCESS;
}
memory_error_t ram_write(uint32_t offset, void *read, uint32_t size)
{
if (offset > ram_size)
return MEMORY_REGION_READFN_FAILED;
bcopy(read, (void *) ((uintptr_t) ram_buffer + offset), size);
return MEMORY_REGION_SUCCESS;
}
void ram_initialize(void)
{
int i;
ram_buffer = malloc(ram_size);
if (!ram_buffer) {
printf("malloc fail\n");
abort();
}
/*
* On Darwin, we would use memset_pattern4, that doesn't exist on Win32.
*/
for (i = 0; i < ram_size; i += 4) {
uint32_t *ram_buf = (uint32_t *) ((uintptr_t) ram_buffer + i);
*ram_buf = 0xAA55AA55;
}
memory_region_register(0, ram_size, ram_read, ram_write);
}
#if 1
int main(void)
{
#if 0
uint32_t buf;
ram_initialize();
memory_read(0x00000000, (void *) &buf, 4);
printf("buf = 0x%08x\n", buf);
buf = 0xdeadbeef;
memory_write(0x00000000, (void *) &buf, 4);
memory_read(0x00000000, (void *) &buf, 4);
printf("buf = 0x%08x\n", buf);
#endif
uint32_t addr = 0, fsr;
ram_initialize();
memory_write_uint32(0x00100020, 0xdeadbeef);
mmu_set_ttb(0);
mmu_set_enabled();
memory_write_uint32(0, 0x0100000 | (3 << 10) | 2);
printf("first section 0x%08x\n", memory_read_uint32(0));
memory_write_uint32(4, 0xfff00000 | 1);
printf("first pagetable 0x%08x\n", memory_read_uint32(4));
printf("0x%08x\n", mmu_memory_read_uint32(0x20));
printf("0x%08x\n", mmu_memory_read_uint32(0x00100020));
mmu_set_disabled();
printf("0x%08x\n", mmu_memory_read_uint32(0x20));
printf("0x%08x\n", mmu_memory_read_uint32(0x00100020));
#if 0
mmu_translate_address(0x12345, 0, 1, &addr, &fsr);
printf("0x%08x FSR 0x%08x\n", addr, fsr);
mmu_translate_address(0xfff, 0, 1, &addr, &fsr);
printf("0x%08x FSR 0x%08x\n", addr, fsr);
mmu_translate_address(0x00112345, 0, 1, &addr, &fsr);
printf("0x%08x FSR 0x%08x\n", addr, fsr);
mmu_translate_address(0x00100fff, 0, 1, &addr, &fsr);
printf("0x%08x FSR 0x%08x\n", addr, fsr);
#endif
#if 0
memory_region_register(0xfffe0000, 65535, NULL, NULL);
#endif
#if 0
uint32_t pa = 0, va = 0xffff0000;
for (pa = 0; pa <= 0xffff0000; pa += PAGE_SIZE, va -= PAGE_SIZE) {
tb_insert(va, pa, 0, 0, PAGE_SIZE);
// tb_flush();
tb_probe(0xfffe0000);
}
#endif
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment