diff --git a/util/x86/x86_page_tables.go b/util/x86/x86_page_tables.go new file mode 100644 index 0000000000..25ad6a2543 --- /dev/null +++ b/util/x86/x86_page_tables.go @@ -0,0 +1,935 @@ +/* + * 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) + } + } +}