Skip to content

fix(mem): fix hypervisor self-coloring boot hang - #391

Open
rabara wants to merge 2 commits into
bao-project:mainfrom
rabara:rabara-cache-coloring-fix
Open

fix(mem): fix hypervisor self-coloring boot hang#391
rabara wants to merge 2 commits into
bao-project:mainfrom
rabara:rabara-cache-coloring-fix

Conversation

@rabara

@rabara rabara commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix root page-pool bitmap relocation under hyp coloring (bitmap lives in the image via pp_bitmap_alloc(), not a standalone SEC_HYP_GLOBAL region).
  • Maintain I/D cache coherency when relocating the colored image (clean to PoC before switch; ic iallu in switch_space()).

Test

  • bao-demos PLATFORM=qemu-aarch64-virt DEMO=baremetal with config.hyp.colors set
  • Unpatched main hangs after U-Boot go 0x50000000; fixed build prints Bao banner and boots guest

rabara added 2 commits August 7, 2026 13:18
Enabling hypervisor coloring aborts in mem_color_hypervisor(), before the
console is up, so the failure shows as a silent hang.

The bitmap is copied as a standalone region and mapped in the new address
space at its original virtual address. That address, however, belongs to
the image, as pp_bitmap_alloc() carves the bitmap out of a static pool, so
it is already taken by the SEC_HYP_IMAGE mapping. mem_alloc_vpage() returns
INVALID_VA and the "Can't allocate address" error is reached. The matching
cleanup at the end of the function is stale for the same reason: it frees a
range past the CPU regions where no bitmap was ever placed, which belongs
to the colored page pool by then.

Since the bitmap lives inside the image, it is relocated by the image copy
already. What that copy misses is the page tables allocated afterwards to
map the image in the new address space, which the bitmap must account for.
Refresh it by re-copying the image over the mapping copy_space() left
behind, once every CPU is past the barrier and no allocation is left to be
done. This keeps the property that the bitmap is the last thing copied and
needs no allocation of its own, which would not be tracked by the bitmap
being copied.

Also check that the bitmap is part of the image, so that a future change to
how it is allocated does not silently reintroduce the problem.

Signed-off-by: Niravkumar L Rabara <niravkumarlaxmidas.rabara@altera.com>
With hypervisor coloring enabled, the boot hangs before any console output
on armv8-a aarch64. mem_color_hypervisor() relocates the image, .text
included, with a plain data-side copy, and then calls switch_space() to
repoint TTBR0_EL2 at the colored pages and return into that copy. Nothing
makes the relocated instructions visible to instruction fetch: the copy may
still sit in the data cache above the PoC, and the PE may hold instruction
cache lines for the new physical pages, so the fetches after the switch
execute stale memory.

The existing cache_flush_range() after switch_space() cannot serve this
purpose, as execution already resumed from those pages, and it only cleans
the data side.

Clean the relocated image to the PoC through the temporary mapping, since
the image virtual addresses still translate to the original pages at that
point. Invalidate the instruction cache in switch_space(), so that every PE
does it, just before writing TTBR0_EL2, while the code being executed still
comes from the original pages.

Builds without hypervisor coloring are unaffected, as
mem_color_hypervisor() only runs when config.hyp.colors is set.

Signed-off-by: Niravkumar L Rabara <niravkumarlaxmidas.rabara@altera.com>
@rabara
rabara force-pushed the rabara-cache-coloring-fix branch from 91dc4a6 to 1f2a8a0 Compare August 7, 2026 05:24
@josecm josecm self-assigned this Aug 7, 2026
@josecm

josecm commented Aug 7, 2026

Copy link
Copy Markdown
Member

Thanks @rabara! This feature has not been used that much lately, so the issue slipped through when we modified the memory layout and allocations to accommodate MCU architectures. Just about to add a test infrastructure finally #363, so we should probably add tests which include setting up hypervisor colors.

Both fixes look good to me. I went through the allocation flow and the invariant the recopy leans on holds: every allocation happens before the barrier, so the refreshed image picks up the final bitmap along with the rest of the pool bookkeeping. The removed cleanup was also zeroing pages that by now belong to the free pool, so that was a corruption waiting to happen. The check that the bitmap lives inside the image is a nice tripwire for future changes to how it is allocated. On the cache side, the sequence is the right one: clean through the temporary mapping while the image virtual addresses still translate to the old pages, and the per PE icache invalidate in switch_space before the TTBR write.

Comment thread src/core/mmu/mem.c
Comment on lines +840 to +851
/**
* Refresh the root page pool bitmap in the colored image.
*
* The bitmap lives inside the image, so the copy above already relocated it, but that
* snapshot is stale: it predates the page tables allocated to map the image in the new
* address space. Every CPU is past the barrier and no allocation is left to be done, so
* re-copy the image over the mapping copy_space() left behind to pick up the final bitmap,
* along with the rest of the page pool bookkeeping. Copying the image again takes no
* allocation, which a separate bitmap copy would, and that allocation would no longer be
* tracked by the bitmap being copied.
*/
memcpy(image_cpy, &_image_start, image_size);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this, we memcpy the whole image twice. Once in copy_space(), then again after the barrier to pick up the final bitmap. I think we can skip the first copy altogether (without skipping the allocation and mapping).

So I'm proposing we split the operations of copy_space() (alloc+map and copy) to save an additional image memcpy on boot.

Comment on lines +93 to +104
/**
* Invalidate the instruction cache.
*
* This routine returns into a copy of the image the caller relocated with a data-side copy and
* cleaned to the PoC. The image virtual addresses are about to be repointed at that copy, so
* drop any instruction cache lines this PE holds for the new physical pages, otherwise the
* fetches after the switch may hit stale instructions.
*/
ic iallu
dsb ish
isb

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the ic iallu just go after the tlbi alle2 in the existing block? They don't depend on each other and both do a dsb+isb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants