/* This file is part of the coreboot project. */ /* SPDX-License-Identifier: GPL-2.0-only */ package main import "bufio" import "encoding/binary" import "encoding/csv" import "flag" import "fmt" import "io" import "log" import "os" import "path/filepath" 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") var generatedCodeLicense string = `/* * Copyright 2018 Generated Code * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ` + "``" + `AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ ` 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 { if _, err := fmt.Fprint(cw.wr, generatedCodeLicense); err != nil { return err } if _, err := fmt.Fprintf(cw.wr, "/* Generated by:\n util/x86/%s %s\n */\n", filepath.Base(os.Args[0]), strings.Join(os.Args[1:], " ")); err != nil { return err } 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 should be at runtime. The indices // are what the linux kernel uses. Reserved entries are not used. // 0 WB : _PAGE_CACHE_MODE_WB // 1 WC : _PAGE_CACHE_MODE_WC // 2 UC-: _PAGE_CACHE_MODE_UC_MINUS // 3 UC : _PAGE_CACHE_MODE_UC // 4 WB : Reserved // 5 WP : _PAGE_CACHE_MODE_WP // 6 UC-: Reserved // 7 WT : _PAGE_CACHE_MODE_WT // 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. var patMsrIndexByType = map[uint]uint{ PAT_WB: 0, PAT_WC: 1, PAT_UCMINUS: 2, PAT_UC: 3, PAT_WP: 5, PAT_WT: 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(indices []int) { // deletions need to be processed in reverse order so as not // delete the wrong entries sort.Sort(sort.Reverse(sort.IntSlice(indices))) for _, i := range indices { 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) } } }