coreboot-kgpe-d16/util/x86/x86_page_tables.go

936 lines
19 KiB
Go

/*
* This file is part of the coreboot project.
*
* Copyright 2018 Google LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package main
import "bufio"
import "encoding/binary"
import "encoding/csv"
import "flag"
import "fmt"
import "io"
import "log"
import "os"
import "sort"
import "strconv"
import "strings"
// This program generates 32-bit PAE page tables based on a CSV input file.
// By default each PDPTE entry is allocated a PD page such that it's easy
// fault in new entries that are 2MiB aligned and size.
var iomapFilePtr = flag.String("iomap_file", "", "CSV file detailing page table mapping")
var ptCFilePtr = flag.String("pt_output_c_file", "", "File to write page tables to in C code")
var ptBinFilePtr = flag.String("pt_output_bin_file", "", "File to write page tables to in binary")
var pdptCFilePtr = flag.String("pdpt_output_c_file", "", "File to write PDPT to in C code")
var pdptBinFilePtr = flag.String("pdpt_output_bin_file", "", "File to write PDPT to in binary")
var pagesBaseAddress = flag.Uint64("metadata_base_address", BASE_ADDR, "Physical base address where metadata pages allocated from")
const (
PAT_UC = 0
PAT_WC = 1
PAT_WT = 4
PAT_WP = 5
PAT_WB = 6
PAT_UCMINUS = 7
COMMENT_CHAR = '#'
NUM_PDPTE = 4
NUM_PDE = 512
NUM_PTE = 512
SIZE_4KiB = uint64(1 << 12)
MASK_4KiB = SIZE_4KiB - 1
SIZE_2MiB = uint64(1 << 21)
MASK_2MiB = SIZE_2MiB - 1
// This is a fake physical address for doing fixups when loading
// the page tables. There's room for 4096 4KiB physical PD or PTE
// tables. Anything with the present bit set will be pointing to an
// offset based on this address. At runtime the entries will be fixed up
BASE_ADDR = uint64(0xaa000000)
// Size of PD and PT structures
METADATA_TABLE_SIZE = 4096
PDPTE_PRES = uint64(1 << 0)
PDPTE_PWT = uint64(1 << 3)
PDPTE_PCD = uint64(1 << 4)
PDE_PRES = uint64(1 << 0)
PDE_RW = uint64(1 << 1)
PDE_US = uint64(1 << 2)
PDE_PWT = uint64(1 << 3)
PDE_PCD = uint64(1 << 4)
PDE_A = uint64(1 << 5)
PDE_D = uint64(1 << 6) // only valid with PS=1
PDE_PS = uint64(1 << 7)
PDE_G = uint64(1 << 8) // only valid with PS=1
PDE_PAT = uint64(1 << 12) // only valid with PS=1
PDE_XD = uint64(1 << 63)
PTE_PRES = uint64(1 << 0)
PTE_RW = uint64(1 << 1)
PTE_US = uint64(1 << 2)
PTE_PWT = uint64(1 << 3)
PTE_PCD = uint64(1 << 4)
PTE_A = uint64(1 << 5)
PTE_D = uint64(1 << 6)
PTE_PAT = uint64(1 << 7)
PTE_G = uint64(1 << 8)
PTE_XD = uint64(1 << 63)
PDPTE_IDX_SHIFT = 30
PDPTE_IDX_MASK = 0x3
PDE_IDX_SHIFT = 21
PDE_IDX_MASK = 0x1ff
PTE_IDX_SHIFT = 12
PTE_IDX_MASK = 0x1ff
)
// Different 'writers' implement this interface.
type pageTableEntryWriter interface {
WritePageEntry(data interface{}) error
}
// The full page objects, page directories and page tables, implement this
// interface to write their entire contents out
type pageTableWriter interface {
WritePage(wr pageTableEntryWriter) error
}
type binaryWriter struct {
wr io.Writer
}
func (bw *binaryWriter) WritePageEntry(data interface{}) error {
return binary.Write(bw.wr, binary.LittleEndian, data)
}
type cWriter struct {
name string
wr io.Writer
totalEntries uint
currentIndex uint
}
func newCWriter(wr io.Writer, name string, nr_entries uint) *cWriter {
cw := &cWriter{wr: wr, name: name, totalEntries: nr_entries}
return cw
}
func (cw *cWriter) WritePageEntry(data interface{}) error {
var entry uint64
doPrint := false
entry, ok := data.(uint64)
if !ok {
return fmt.Errorf("entry not uint64 %T", data)
}
if cw.currentIndex == 0 {
includes := []string{
"stdint.h",
}
for _, l := range includes {
if _, err := fmt.Fprintf(cw.wr, "#include <%s>\n", l); err != nil {
return err
}
}
if _, err := fmt.Fprintf(cw.wr, "uint64_t %s[] = {\n", cw.name); err != nil {
return err
}
}
if cw.currentIndex%NUM_PTE == 0 {
doPrint = true
page_num := cw.currentIndex / NUM_PTE
if _, err := fmt.Fprintf(cw.wr, "\t/* Page %d */\n", page_num); err != nil {
return err
}
}
// filter out 0 entries
if entry != 0 || doPrint {
_, err := fmt.Fprintf(cw.wr, "\t[%d] = %#016xULL,\n", cw.currentIndex, entry)
if err != nil {
return err
}
}
cw.currentIndex += 1
if cw.currentIndex == cw.totalEntries {
if _, err := fmt.Fprintln(cw.wr, "};"); err != nil {
return err
}
}
return nil
}
// This map represents what the IA32_PAT MSR
var patMsrIndexByType = map[uint]uint{
PAT_WB: 0,
PAT_WT: 1,
PAT_UCMINUS: 2,
PAT_UC: 3,
// In order to use WP and WC then the IA32_PAT MSR needs to be updated
// as these are not the power on reset values.
PAT_WP: 6,
PAT_WC: 7,
}
type addressRange struct {
begin uint64
end uint64
pat uint
nx bool
}
type addrRangeMerge func(a, b *addressRange) bool
func (ar *addressRange) Size() uint64 {
return ar.end - ar.begin
}
func (ar *addressRange) Base() uint64 {
return ar.begin
}
func (ar *addressRange) Pat() uint {
return ar.pat
}
func (ar *addressRange) Nx() bool {
return ar.nx
}
func (ar *addressRange) String() string {
var nx string
if ar.nx {
nx = "NX"
} else {
nx = " "
}
return fmt.Sprintf("%016x -- %016x %s %s", ar.begin, ar.end, patTypeToString(ar.pat), nx)
}
type pageTableEntry struct {
physAddr uint64
flags uint64
}
func (pte *pageTableEntry) Encode() uint64 {
return pte.physAddr | pte.flags
}
func ptePatFlags(base uint64, pat uint) uint64 {
idx, ok := patMsrIndexByType[pat]
patStr, _ := patTypesToString[pat]
if !ok {
log.Fatalf("Invalid pat entry for page %x: %s\n", base, patStr)
}
switch idx {
case 0:
return 0
case 1:
return PTE_PWT
case 2:
return PTE_PCD
case 3:
return PTE_PCD | PTE_PWT
case 4:
return PTE_PAT
case 5:
return PTE_PAT | PTE_PWT
case 6:
return PTE_PAT | PTE_PCD
case 7:
return PTE_PAT | PTE_PCD | PTE_PWT
}
log.Fatalf("Invalid PAT index %d for PTE %x %s\n", idx, base, patStr)
return 0
}
func (pte *pageTableEntry) SetMapping(base uint64, pat uint, nx bool) {
// Present and accessed
pte.flags |= PTE_PRES | PTE_A
// Non write protected entries mark as writable and dirty
if pat != PAT_WP {
pte.flags |= PTE_RW
pte.flags |= PTE_D
}
if nx {
pte.flags |= PTE_XD
}
pte.flags |= ptePatFlags(base, pat)
pte.physAddr = base
}
type pageTable struct {
ptes [NUM_PTE]pageTableEntry
}
func (pt *pageTable) WritePage(wr pageTableEntryWriter) error {
for i := range pt.ptes {
pte := &pt.ptes[i]
err := wr.WritePageEntry(pte.Encode())
if err != nil {
return err
}
}
return nil
}
type pageDirectoryEntry struct {
physAddr uint64
flags uint64
pt *pageTable
}
func (pde *pageDirectoryEntry) Encode() uint64 {
return pde.physAddr | pde.flags
}
func pdeTablePatFlags(pat uint) uint64 {
idx, ok := patMsrIndexByType[pat]
patStr, _ := patTypesToString[pat]
if !ok || idx >= 4 {
log.Fatalf("Invalid pat entry for PDE page table %s\n", patStr)
}
switch idx {
case 0:
return 0
case 1:
return PDE_PWT
case 2:
return PDE_PCD
case 3:
return PDE_PCD | PDE_PWT
}
log.Fatalf("Invalid PAT index %d for PDE page table %s\n", idx, patStr)
return 0
}
func pdeLargePatFlags(base uint64, pat uint) uint64 {
idx, ok := patMsrIndexByType[pat]
patStr, _ := patTypesToString[pat]
if !ok {
log.Fatalf("Invalid pat entry for large page %x: %s\n", base, patStr)
}
switch idx {
case 0:
return 0
case 1:
return PDE_PWT
case 2:
return PDE_PCD
case 3:
return PDE_PCD | PDE_PWT
case 4:
return PDE_PAT
case 5:
return PDE_PAT | PDE_PWT
case 6:
return PDE_PAT | PDE_PCD
case 7:
return PDE_PAT | PDE_PCD | PDE_PWT
}
log.Fatalf("Invalid PAT index %d for PDE %x %s\n", idx, base, patStr)
return 0
}
func (pde *pageDirectoryEntry) SetPageTable(pt_addr uint64, pat uint) {
// Set writable for whole region covered by page table. Individual
// ptes will have the correct writability flags
pde.flags |= PDE_PRES | PDE_A | PDE_RW
pde.flags |= pdeTablePatFlags(pat)
pde.physAddr = pt_addr
}
func (pde *pageDirectoryEntry) SetMapping(base uint64, pat uint, nx bool) {
// Present, accessed, and large
pde.flags |= PDE_PRES | PDE_A | PDE_PS
// Non write protected entries mark as writable and dirty
if pat != PAT_WP {
pde.flags |= PDE_RW
pde.flags |= PDE_D
}
if nx {
pde.flags |= PDE_XD
}
pde.flags |= pdeLargePatFlags(base, pat)
pde.physAddr = base
}
type pageDirectory struct {
pdes [NUM_PDE]pageDirectoryEntry
}
func (pd *pageDirectory) WritePage(wr pageTableEntryWriter) error {
for i := range pd.pdes {
pde := &pd.pdes[i]
err := wr.WritePageEntry(pde.Encode())
if err != nil {
return nil
}
}
return nil
}
type pageDirectoryPointerEntry struct {
physAddr uint64
flags uint64
pd *pageDirectory
}
func (pdpte *pageDirectoryPointerEntry) Encode() uint64 {
return pdpte.physAddr | pdpte.flags
}
func (pdpte *pageDirectoryPointerEntry) Init(addr uint64, pat uint) {
idx, ok := patMsrIndexByType[pat]
// Only 2 bits worth of PAT indexing in PDPTE
if !ok || idx >= 4 {
patStr, _ := patTypesToString[pat]
log.Fatalf("Can't use type '%s' as PDPTE type.\n", patStr)
}
pdpte.physAddr = addr
pdpte.flags = PDPTE_PRES
switch idx {
case 0:
pdpte.flags |= 0
case 1:
pdpte.flags |= PDPTE_PWT
case 2:
pdpte.flags |= PDPTE_PCD
case 3:
pdpte.flags |= PDPTE_PCD | PDPTE_PWT
default:
log.Fatalf("Invalid PAT index %d for PDPTE\n", idx)
}
}
type addressSpace struct {
ranges []*addressRange
mergeFunc addrRangeMerge
metatdataBaseAddr uint64
pdptes [NUM_PDPTE]pageDirectoryPointerEntry
numMetaPages uint
page_writers []pageTableWriter
}
func (as *addressSpace) newPage(pw pageTableWriter) uint64 {
v := as.metatdataBaseAddr + METADATA_TABLE_SIZE*uint64(as.numMetaPages)
as.numMetaPages += 1
as.page_writers = append(as.page_writers, pw)
return v
}
func newAddrSpace(mergeFunc addrRangeMerge, metatdataBaseAddr uint64) *addressSpace {
as := &addressSpace{mergeFunc: mergeFunc, metatdataBaseAddr: metatdataBaseAddr}
// Fill in all PDPTEs
for i := range as.pdptes {
pdpte := &as.pdptes[i]
pdpte.pd = &pageDirectory{}
// fetch paging structures as WB
pdpte.Init(as.newPage(pdpte.pd), PAT_WB)
}
return as
}
func (as *addressSpace) deleteEntries(indicies []int) {
// deletions need to be processed in reverse order so as not
// delete the wrong entries
sort.Sort(sort.Reverse(sort.IntSlice(indicies)))
for _, i := range indicies {
as.ranges = append(as.ranges[:i], as.ranges[i+1:]...)
}
}
func (as *addressSpace) mergeRanges() {
var toRemove []int
var prev *addressRange
for i, cur := range as.ranges {
if prev == nil {
prev = cur
continue
}
// merge previous with current
if as.mergeFunc(prev, cur) {
prev.end = cur.end
toRemove = append(toRemove, i)
cur = prev
}
prev = cur
}
as.deleteEntries(toRemove)
}
type addressRangeSlice []*addressRange
func (p addressRangeSlice) Len() int {
return len(p)
}
func (p addressRangeSlice) Less(i, j int) bool {
return !p[i].After(p[j])
}
func (p addressRangeSlice) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func (as *addressSpace) insertRange(r *addressRange) {
as.ranges = append(as.ranges, r)
sort.Sort(addressRangeSlice(as.ranges))
}
// Remove complete entries or trim existing ones
func (as *addressSpace) trimRanges(r *addressRange) {
var toRemove []int
// First remove all entries that are completely overlapped
for i, cur := range as.ranges {
if r.FullyOverlaps(cur) {
toRemove = append(toRemove, i)
continue
}
}
as.deleteEntries(toRemove)
var ar *addressRange
// Process partial overlaps
for _, cur := range as.ranges {
// Overlapping may be at beginning, middle, end. Only the
// middle overlap needs to create a new range since the
// beginning and end overlap can just adjust the current
// range.
if r.Overlaps(cur) {
// beginning overlap
if r.begin <= cur.begin {
cur.begin = r.end
continue
}
// end overlap
if r.end >= cur.end {
cur.end = r.begin
continue
}
// middle overlap. create new entry from the hole
// punched in the current entry. There's nothing
// further to do after this
begin := r.end
end := cur.end
pat := cur.pat
nx := cur.nx
// current needs new ending
cur.end = r.begin
ar = newAddrRange(begin, end, pat, nx)
break
}
}
if ar != nil {
as.insertRange(ar)
}
}
func (as *addressSpace) PrintEntries() {
for _, cur := range as.ranges {
log.Println(cur)
}
}
func (as *addressSpace) AddRange(r *addressRange) {
as.trimRanges(r)
as.insertRange(r)
as.mergeRanges()
}
func (as *addressSpace) insertMapping(base uint64, size uint64, pat uint, nx bool) {
pdpteIndex := (base >> PDPTE_IDX_SHIFT) & PDPTE_IDX_MASK
pdeIndex := (base >> PDE_IDX_SHIFT) & PDE_IDX_MASK
pteIndex := (base >> PTE_IDX_SHIFT) & PTE_IDX_MASK
pd := as.pdptes[pdpteIndex].pd
pde := &pd.pdes[pdeIndex]
if size == SIZE_2MiB {
pde.SetMapping(base, pat, nx)
return
}
if pde.pt == nil {
pde.pt = &pageTable{}
// Fetch paging structures as WB
pde.SetPageTable(as.newPage(pde.pt), PAT_WB)
}
pte := &pde.pt.ptes[pteIndex]
pte.SetMapping(base, pat, nx)
}
func (as *addressSpace) CreatePageTables() {
var size uint64
var base uint64
for _, r := range as.ranges {
size = r.Size()
base = r.Base()
pat := r.Pat()
nx := r.Nx()
numSmallEntries := 0
numBigEntries := 0
for size != 0 {
mappingSize := SIZE_4KiB
if (base&MASK_2MiB) == 0 && size >= SIZE_2MiB {
mappingSize = SIZE_2MiB
numBigEntries += 1
} else {
numSmallEntries += 1
}
as.insertMapping(base, mappingSize, pat, nx)
base += mappingSize
size -= mappingSize
}
log.Printf("%s : %d big %d small\n", r, numBigEntries, numSmallEntries)
}
}
func (as *addressSpace) PageTableSize() uint {
return as.numMetaPages * METADATA_TABLE_SIZE
}
func (as *addressSpace) NumPages() uint {
return as.numMetaPages
}
func (as *addressSpace) WritePageTable(ptew pageTableEntryWriter) error {
for _, pw := range as.page_writers {
err := pw.WritePage(ptew)
if err != nil {
return err
}
}
return nil
}
func (as *addressSpace) WritePageDirectoryPointerTable(ptew pageTableEntryWriter) error {
for i := range as.pdptes {
err := ptew.WritePageEntry(as.pdptes[i].Encode())
if err != nil {
return err
}
}
return nil
}
var pat_types_from_str = map[string]uint{
"UC": PAT_UC,
"WC": PAT_WC,
"WT": PAT_WT,
"WP": PAT_WP,
"WB": PAT_WB,
"UC-": PAT_UCMINUS,
}
var patTypesToString = map[uint]string{
PAT_UC: "UC",
PAT_WC: "WC",
PAT_WT: "WT",
PAT_WP: "WP",
PAT_WB: "WB",
PAT_UCMINUS: "UC-",
}
func openCsvFile(file string) (*csv.Reader, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
csvr := csv.NewReader(f)
csvr.Comment = COMMENT_CHAR
csvr.TrimLeadingSpace = true
return csvr, nil
}
// After returns true if ar beings at or after other.end.
func (ar addressRange) After(other *addressRange) bool {
return ar.begin >= other.end
}
func (ar addressRange) FullyOverlaps(other *addressRange) bool {
return ar.begin <= other.begin && ar.end >= other.end
}
func (ar addressRange) Overlaps(other *addressRange) bool {
if other.end <= ar.begin || other.begin >= ar.end {
return false
}
return true
}
func MergeByPat(a, b *addressRange) bool {
// 'b' is assumed to be following 'a'
if a.end != b.begin {
return false
}
if a.pat != b.pat {
return false
}
return true
}
func MergeByNx(a, b *addressRange) bool {
// 'b' is assumed to be following 'a'
if a.end != b.begin {
return false
}
if a.nx != b.nx {
return false
}
return true
}
func MergeByPatNx(a, b *addressRange) bool {
return MergeByPat(a, b) && MergeByNx(a, b)
}
func hexNumber(s string) (uint64, error) {
return strconv.ParseUint(strings.TrimSpace(s), 0, 0)
}
func patTypeToString(pat uint) string {
return patTypesToString[pat]
}
func patTypeFromString(s string) (uint, error) {
s1 := strings.TrimSpace(s)
v, ok := pat_types_from_str[s1]
if !ok {
return 0, fmt.Errorf("No PAT type '%s'", s1)
}
return v, nil
}
func removeComment(field, comment string) string {
str_slice := strings.Split(field, comment)
return strings.TrimSpace(str_slice[0])
}
func newAddrRange(begin, end uint64, pat uint, nx bool) *addressRange {
return &addressRange{begin: begin, end: end, pat: pat, nx: nx}
}
func readRecords(csvr *csv.Reader, as *addressSpace) {
i := 0
for true {
fields, err := csvr.Read()
i++
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
if len(fields) < 3 {
log.Fatal("Need at least 3 fields: begin, end, PAT\n")
}
begin, err := hexNumber(fields[0])
if err != nil {
log.Fatal(err)
}
end, err := hexNumber(fields[1])
if err != nil {
log.Fatal(err)
}
if begin&MASK_4KiB != 0 {
log.Fatalf("begin %x must be at least 4KiB aligned\n", begin)
}
if end&MASK_4KiB != 0 {
log.Fatalf("end %x must be at least 4KiB aligned\n", end)
}
if begin >= end {
log.Fatalf("%x must be < %x at record %d\n", begin, end, i)
}
pat, err := patTypeFromString(fields[2])
if err != nil {
log.Fatal(err)
}
var nx bool = false
if len(fields) > 3 && len(removeComment(fields[3], string(COMMENT_CHAR))) > 0 {
nx = true
}
as.AddRange(newAddrRange(begin, end, pat, nx))
}
}
func main() {
log.SetFlags(0)
flag.Parse()
var ptWriters []pageTableEntryWriter
var pdptWriters []pageTableEntryWriter
if *iomapFilePtr == "" {
log.Fatal("No iomap_file provided.\n")
}
csvr, err := openCsvFile(*iomapFilePtr)
if err != nil {
log.Fatal(err)
}
as := newAddrSpace(MergeByPatNx, *pagesBaseAddress)
readRecords(csvr, as)
log.Println("Merged address space:")
as.CreatePageTables()
log.Println()
log.Printf("Total Pages of page tables: %d\n", as.NumPages())
log.Println()
log.Printf("Pages linked using base address of %#x.\n", *pagesBaseAddress)
if *ptCFilePtr != "" {
f, err := os.Create(*ptCFilePtr)
if err != nil {
log.Fatal(err)
}
defer f.Close()
bwr := bufio.NewWriter(f)
defer bwr.Flush()
cw := newCWriter(bwr, "page_tables", as.NumPages()*NUM_PTE)
ptWriters = append(ptWriters, cw)
}
if *ptBinFilePtr != "" {
f, err := os.Create(*ptBinFilePtr)
if err != nil {
log.Fatal(err)
}
defer f.Close()
bwr := bufio.NewWriter(f)
defer bwr.Flush()
bw := &binaryWriter{wr: bwr}
ptWriters = append(ptWriters, bw)
}
if *pdptCFilePtr != "" {
f, err := os.Create(*pdptCFilePtr)
if err != nil {
log.Fatal(err)
}
defer f.Close()
bwr := bufio.NewWriter(f)
defer bwr.Flush()
cw := newCWriter(bwr, "pdptes", NUM_PDPTE)
pdptWriters = append(pdptWriters, cw)
}
if *pdptBinFilePtr != "" {
f, err := os.Create(*pdptBinFilePtr)
if err != nil {
log.Fatal(err)
}
defer f.Close()
bwr := bufio.NewWriter(f)
defer bwr.Flush()
bw := &binaryWriter{wr: bwr}
pdptWriters = append(pdptWriters, bw)
}
// Write out page tables
for _, w := range ptWriters {
err = as.WritePageTable(w)
if err != nil {
log.Fatal(err)
}
}
// Write out pdptes
for _, w := range pdptWriters {
err = as.WritePageDirectoryPointerTable(w)
if err != nil {
log.Fatal(err)
}
}
}