/* 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)
		}
	}
}