Files
Tao Lin 28cf7f346d Add 64K page size support for ARM64
This adds support for running gVisor on ARM64 systems with 64K page
size (e.g., NVIDIA Grace CPU).

Key changes:

1. hostarch: Split page size constants into separate files selected by
   the `pagesize_64k` build tag. 64K pages use PageShift=16 and
   HugePageShift=29 (512MB huge pages). Remove uint16 from the
   bytecount interface since 64K (2^16) cannot be represented by uint16.

2. ring0: Move page table layout constants to page-size-specific files.
   64K pages use larger page table entries (64K per table) and skip the
   PUD level.

3. ring0/pagetables: Add 3-level page table walker for 64K pages
   (PGD→PMD→PTE, no PUD). Each level indexes 8192 entries (13 bits)
   with 512MB sect pages at the PMD level.

4. sentry/arch: Fix mmap layout constants that overflowed the 48-bit
   address space with 64K pages. Use fixed values for maxMmapRand64
   (1<<45 = 32TB) and minMmapRand64 (1<<30 = 1GB), and compute
   maxStackRand64 using hostarch.PageShift.

5. Bazel build system: Add `--define=pagesize=64k` support that
   propagates the `pagesize_64k` Go build tag to all binaries and tests.

Build with `bazel build --define=pagesize=64k //runsc:runsc` to enable.

Note: Currently only the ptrace platform is supported for 64K pages.
KVM and systrap platforms require additional work.

Updates #8196
2026-03-04 14:00:13 -08:00

233 lines
6.5 KiB
Go

// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build arm64 && !pagesize_64k
// +build arm64,!pagesize_64k
package pagetables
// iterateRangeCanonical walks a canonical range.
//
//go:nosplit
func (w *Walker) iterateRangeCanonical(start, end uintptr) bool {
pgdEntryIndex := w.pageTables.root
if start >= upperBottom {
pgdEntryIndex = w.pageTables.archPageTables.root
}
for pgdIndex := (uint16((start & pgdMask) >> pgdShift)); start < end && pgdIndex < entriesPerPage; pgdIndex++ {
var (
pgdEntry = &pgdEntryIndex[pgdIndex]
pudEntries *PTEs
)
if !pgdEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
start = next(start, pgdSize)
continue
}
// Allocate a new pgd.
pudEntries = w.pageTables.Allocator.NewPTEs()
pgdEntry.setPageTable(w.pageTables, pudEntries)
} else {
pudEntries = w.pageTables.Allocator.LookupPTEs(pgdEntry.Address())
}
// Map the next level.
clearPUDEntries := uint16(0)
for pudIndex := uint16((start & pudMask) >> pudShift); start < end && pudIndex < entriesPerPage; pudIndex++ {
var (
pudEntry = &pudEntries[pudIndex]
pmdEntries *PTEs
)
if !pudEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
clearPUDEntries++
start = next(start, pudSize)
continue
}
// This level has 1-GB sect pages. Is this
// entire region at least as large as a single
// PUD entry? If so, we can skip allocating a
// new page for the pmd.
if start&(pudSize-1) == 0 && end-start >= pudSize {
pudEntry.SetSect()
if !w.visitor.visit(uintptr(start), pudEntry, pudSize-1) {
return false
}
if pudEntry.Valid() {
start = next(start, pudSize)
continue
}
}
// Allocate a new pud.
pmdEntries = w.pageTables.Allocator.NewPTEs()
pudEntry.setPageTable(w.pageTables, pmdEntries)
} else if pudEntry.IsSect() {
// Does this page need to be split?
if w.visitor.requiresSplit() && (start&(pudSize-1) != 0 || end < next(start, pudSize)) {
// Install the relevant entries.
pmdEntries = w.pageTables.Allocator.NewPTEs()
for index := uint16(0); index < entriesPerPage; index++ {
pmdEntries[index].SetSect()
pmdEntries[index].Set(
pudEntry.Address()+(pmdSize*uintptr(index)),
pudEntry.Opts())
}
pudEntry.setPageTable(w.pageTables, pmdEntries)
} else {
// A sect page to be checked directly.
if !w.visitor.visit(uintptr(start), pudEntry, pudSize-1) {
return false
}
// Might have been cleared.
if !pudEntry.Valid() {
clearPUDEntries++
}
// Note that the sect page was changed.
start = next(start, pudSize)
continue
}
} else {
pmdEntries = w.pageTables.Allocator.LookupPTEs(pudEntry.Address())
}
// Map the next level, since this is valid.
clearPMDEntries := uint16(0)
for pmdIndex := uint16((start & pmdMask) >> pmdShift); start < end && pmdIndex < entriesPerPage; pmdIndex++ {
var (
pmdEntry = &pmdEntries[pmdIndex]
pteEntries *PTEs
)
if !pmdEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
clearPMDEntries++
start = next(start, pmdSize)
continue
}
// This level has 2-MB huge pages. If this
// region is continued in a single PMD entry?
// As above, we can skip allocating a new page.
if start&(pmdSize-1) == 0 && end-start >= pmdSize {
pmdEntry.SetSect()
if !w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1) {
return false
}
if pmdEntry.Valid() {
start = next(start, pmdSize)
continue
}
}
// Allocate a new pmd.
pteEntries = w.pageTables.Allocator.NewPTEs()
pmdEntry.setPageTable(w.pageTables, pteEntries)
} else if pmdEntry.IsSect() {
// Does this page need to be split?
if w.visitor.requiresSplit() && (start&(pmdSize-1) != 0 || end < next(start, pmdSize)) {
// Install the relevant entries.
pteEntries = w.pageTables.Allocator.NewPTEs()
for index := uint16(0); index < entriesPerPage; index++ {
pteEntries[index].Set(
pmdEntry.Address()+(pteSize*uintptr(index)),
pmdEntry.Opts())
}
pmdEntry.setPageTable(w.pageTables, pteEntries)
} else {
// A huge page to be checked directly.
if !w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1) {
return false
}
// Might have been cleared.
if !pmdEntry.Valid() {
clearPMDEntries++
}
// Note that the huge page was changed.
start = next(start, pmdSize)
continue
}
} else {
pteEntries = w.pageTables.Allocator.LookupPTEs(pmdEntry.Address())
}
// Map the next level, since this is valid.
clearPTEEntries := uint16(0)
for pteIndex := uint16((start & pteMask) >> pteShift); start < end && pteIndex < entriesPerPage; pteIndex++ {
var (
pteEntry = &pteEntries[pteIndex]
)
if !pteEntry.Valid() && !w.visitor.requiresAlloc() {
clearPTEEntries++
start += pteSize
continue
}
// At this point, we are guaranteed that start%pteSize == 0.
if !w.visitor.visit(uintptr(start), pteEntry, pteSize-1) {
return false
}
if !pteEntry.Valid() {
if w.visitor.requiresAlloc() {
panic("PTE not set after iteration with requiresAlloc!")
}
clearPTEEntries++
}
// Note that the pte was changed.
start += pteSize
continue
}
// Check if we no longer need this page.
if clearPTEEntries == entriesPerPage {
pmdEntry.Clear()
w.pageTables.Allocator.FreePTEs(pteEntries)
clearPMDEntries++
}
}
// Check if we no longer need this page.
if clearPMDEntries == entriesPerPage {
pudEntry.Clear()
w.pageTables.Allocator.FreePTEs(pmdEntries)
clearPUDEntries++
}
}
// Check if we no longer need this page.
if clearPUDEntries == entriesPerPage {
pgdEntry.Clear()
w.pageTables.Allocator.FreePTEs(pudEntries)
}
}
return true
}