#! /usr/bin/env python

import sys
from optparse import OptionParser
import random
import math

def convert(size):
    length = len(size)
    lastchar = size[length-1]
    if (lastchar == 'k') or (lastchar == 'K'):
        m = 1024
        nsize = int(size[0:length-1]) * m
    elif (lastchar == 'm') or (lastchar == 'M'):
        m = 1024*1024
        nsize = int(size[0:length-1]) * m
    elif (lastchar == 'g') or (lastchar == 'G'):
        m = 1024*1024*1024
        nsize = int(size[0:length-1]) * m
    else:
        nsize = int(size)
    return nsize

def roundup(size):
    value = 1.0
    while value < size:
        value = value * 2.0
    return value

    
class OS:
    def __init__(self):
        # 4k phys memory (128 pages)
        self.pageSize  = 32
        self.physPages = 128
        self.physMem   = self.pageSize * self.physPages
        self.vaPages   = 1024
        self.vaSize    = self.pageSize * self.vaPages
        self.pteSize   = 1
        self.pageBits  = 5 # log of page size

        # os tracks
        self.usedPages      = []
        self.usedPagesCount = 0
        self.maxPageCount   = self.physMem / self.pageSize

        # no pages used (yet)
        for i in range(0, self.maxPageCount):
            self.usedPages.append(0)

        # set contents of memory to 0, too
        self.memory = []
        for i in range(0, self.physMem):
            self.memory.append(0)

        # associative array of pdbr's (indexed by PID)
        self.pdbr = {}

        # mask is 11111 00000 00000 --> 0111 1100 0000 0000 
        self.PDE_MASK    = 0x7c00
        self.PDE_SHIFT   = 10

        # 00000 11111 00000 -> 000 0011 1110 0000
        self.PTE_MASK    = 0x03e0
        self.PTE_SHIFT   = 5

        self.VPN_MASK    = self.PDE_MASK | self.PTE_MASK
        self.VPN_SHIFT   = self.PTE_SHIFT

        # grabs the last five bits of a virtual address
        self.OFFSET_MASK = 0x1f

    def findFree(self):
        assert(self.usedPagesCount < self.maxPageCount)
        look = int(random.random() * self.maxPageCount)
        while self.usedPages[look] == 1:
            look = int(random.random() * self.maxPageCount)
        self.usedPagesCount = self.usedPagesCount + 1
        self.usedPages[look] = 1
        return look

    def initPageDir(self, whichPage):
        whichByte = whichPage << self.pageBits
        for i in range(whichByte, whichByte + self.pageSize):
            self.memory[i] = 0x7f

    def initPageTablePage(self, whichPage):
        self.initPageDir(whichPage)

    def getPageTableEntry(self, virtualAddr, ptePage, printStuff):
        pteBits = (virtualAddr & self.PTE_MASK) >> self.PTE_SHIFT
        pteAddr = (ptePage << self.pageBits) | pteBits
        pte     = self.memory[pteAddr]
        valid   = (pte & 0x80) >> 7
        pfn     = (pte & 0x7f)
        if printStuff == True:
            print '    --> pte index:0x%x  pte contents:(valid %d, pfn 0x%02x)' % (pteBits, valid, pfn)
        return (valid, pfn, pteAddr)

    def getPageDirEntry(self, pid, virtualAddr, printStuff):
        pageDir = self.pdbr[pid]
        pdeBits = (virtualAddr & self.PDE_MASK) >> self.PDE_SHIFT
        pdeAddr = (pageDir << self.pageBits) | pdeBits
        pde     = self.memory[pdeAddr]
        valid   = (pde & 0x80) >> 7
        ptPtr   = (pde & 0x7f)
        if printStuff == True:
            print '  --> pde index:0x%x  pde contents:(valid %d, pfn 0x%02x)' % (pdeBits, valid, ptPtr)
        return (valid, ptPtr, pdeAddr)

    def setPageTableEntry(self, pteAddr, physicalPage):
        self.memory[pteAddr] = 0x80 | physicalPage

    def setPageDirEntry(self, pdeAddr, physicalPage):
        self.memory[pdeAddr] = 0x80 | physicalPage
        
    def allocVirtualPage(self, pid, virtualPage, physicalPage):
        # make it into a virtual address, as everything uses this (and not VPN)
        virtualAddr = virtualPage << self.pageBits
        (valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, False)
        if valid == 0:
            # must allocate a page of the page table now, and have the PD point to it
            assert(ptPtr == 127)
            ptePage = self.findFree()
            self.setPageDirEntry(pdeAddr, ptePage)
            self.initPageTablePage(ptePage)
        else:
            # otherwise, just extract page number of page table page
            ptePage = ptPtr
        # now, look up page table entry too, and mark it valid and fill in translation
        (valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, False)
        assert(valid == 0)
        assert(pfn == 127)
        self.setPageTableEntry(pteAddr, physicalPage)

    # -2 -> PTE fault, -1 means PDE fault
    def translate(self, pid, virtualAddr):
        (valid, ptPtr, pdeAddr) = self.getPageDirEntry(pid, virtualAddr, True)
        if valid == 1:
            ptePage = ptPtr
            (valid, pfn, pteAddr) = self.getPageTableEntry(virtualAddr, ptePage, True)
            if valid == 1:
                offset = (virtualAddr & self.OFFSET_MASK)
                paddr  = (pfn << self.pageBits) | offset
		# print '     --> pfn: %02x  offset: %x' % (pfn, offset)
                return paddr
            else:
                return -2
        return -1

    def fillPage(self, whichPage):
        for j in range(0, self.pageSize):
            self.memory[(whichPage * self.pageSize) + j] = int(random.random() * 31)

    def procAlloc(self, pid, numPages):
        # need a PDBR: find one somewhere in memory
        pageDir = self.findFree()
        # print '**ALLOCATE** page dir', pageDir
        self.pdbr[pid] = pageDir
        self.initPageDir(pageDir)

        used = {}
        for vp in range(0, self.vaPages):
            used[vp] = 0
        allocatedVPs = []
        
        for vp in range(0, numPages):
            vp = int(random.random() * self.vaPages)
            while used[vp] == 1:
                vp = int(random.random() * self.vaPages)
            assert(used[vp] == 0)
            used[vp] = 1
            allocatedVPs.append(vp)
            pp = self.findFree()
            # print '**ALLOCATE** page', pp
            # print '  trying to map vp:%08x to pp:%08x' % (vp, pp)
            self.allocVirtualPage(pid, vp, pp)
            self.fillPage(pp)
        return allocatedVPs

    def dumpPage(self, whichPage):
        i = whichPage
        for j in range(0, self.pageSize):
            print self.memory[(i * self.pageSize) + j],
        print ''

    def memoryDump(self):
        for i in range(0, self.physMem / self.pageSize):
            print 'page %3d:' %  i, 
            for j in range(0, self.pageSize):
                print '%02x' % self.memory[(i * self.pageSize) + j],
            print ''

    def getPDBR(self, pid):
        return self.pdbr[pid]

    def getValue(self, addr):
        return self.memory[addr]

# allocate some processes in memory
# allocate some multi-level page tables in memory
# make a bit of a mystery:
# can examine PDBR (PFN of current proc's page directory)
# can examine contents of any page
# fill pages with values too
# ask: when given
#   LOAD VA, R1
# what will final value will be loaded into R1?

#
# main program
#
parser = OptionParser()
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
parser.add_option('-a', '--allocated', default=64, help='number of virtual pages allocated',
                  action='store', type='int', dest='allocated')
parser.add_option('-n', '--addresses', default=10, help='number of virtual addresses to generate',
                  action='store', type='int', dest='num')
parser.add_option('-c', '--solve', help='compute answers for me', action='store_true', default=False, dest='solve')


(options, args) = parser.parse_args()

print 'ARG seed', options.seed
print 'ARG allocated',  options.allocated
print 'ARG num',  options.num
print ""

random.seed(options.seed)

# do the work now
os = OS()
used = os.procAlloc(1, options.allocated)

os.memoryDump()

print '\nPDBR:', os.getPDBR(1), ' (decimal) [This means the page directory is held in this page]\n'

for i in range(0, options.num):
    if (random.random() * 100) > 50.0 or i >= len(used):
        vaddr = int(random.random() * 1024 * 32)
    else:
        vaddr = (used[i] << 5) | int(random.random() * 32)
    if options.solve == True:
        print 'Virtual Address %04x:' % vaddr
        r = os.translate(1, vaddr)
        if r > -1:
            print '      --> Translates to Physical Address 0x%03x --> Value: %02x' % (r, os.getValue(r))
        elif r == -1:
            print '      --> Fault (page directory entry not valid)'
        else:
            print '      --> Fault (page table entry not valid)'
    else:
        print 'Virtual Address %04x: Translates To What Physical Address (And Fetches what Value)? Or Fault?' % vaddr

print ''

exit(0)