blob: a97a2de4a01d75a5d5024a0c51a3f12f9f73a096 [file] [log] [blame]
#!/usr/bin/env python3
import os
import sys
import struct
import parser
from collections import namedtuple
import ctypes
import argparse
############# global variables
pd_complete = ''
inputfile = ''
outputfile = ''
list_of_pde = {}
num_of_regions = 0
read_buff=''
struct_mmu_regions_tuple = {"start_addr","size","permissions"}
mmu_region_details = namedtuple("mmu_region_details", "pde_index page_entries_info")
valid_pages_inside_pde = namedtuple("valid_pages_inside_pde","start_addr size \
pte_valid_addr_start \
pte_valid_addr_end \
permissions")
page_tables_list = []
pd_start_addr = 0
validation_issue_memory_overlap = [False, 0, -1]
output_offset = 0
print_string_pde_list = ''
pde_pte_string = {}
FourMB = (1024*4096) #In Bytes
#############
#return the page directory number for the give address
def get_pde_number(value):
return( (value >> 22 ) & 0x3FF)
#return the page table number for the given address
def get_pte_number(value):
return( (value >> 12 ) & 0x3FF)
# update the tuple values for the memory regions needed
def set_pde_pte_values(pde_index, address, mem_size,
pte_valid_addr_start, pte_valid_addr_end, perm):
pages_tuple = valid_pages_inside_pde(
start_addr = address,
size = mem_size,
pte_valid_addr_start = pte_valid_addr_start,
pte_valid_addr_end = pte_valid_addr_end,
permissions = perm)
mem_region_values = mmu_region_details(pde_index = pde_index,
page_entries_info = [])
mem_region_values.page_entries_info.append(pages_tuple)
if pde_index in list_of_pde.keys():
# this step adds the new page info to the exsisting pages info
list_of_pde[pde_index].page_entries_info.append(pages_tuple)
else:
list_of_pde[pde_index] = mem_region_values
def print_list_of_pde(list_of_pde):
for key, value in list_of_pde.items():
print(key,value)
print('\n')
# read the binary from the input file and populate a dict for
# start address of mem region
# size of the region - so page tables entries will be created with this
# read write permissions
raw_info=[]
def read_mmu_list_marshal_param():
global read_buff
global page_tables_list
global pd_start_addr
global validation_issue_memory_overlap
read_buff = input_file.read()
input_file.close()
# read contents of the binary file first 2 values read are
# num_of_regions and page directory start address both calculated and
# populated by the linker
num_of_regions, pd_start_addr = struct.unpack_from(header_values_format,read_buff,0);
# a offset used to remember next location to read in the binary
size_read_from_binary = struct.calcsize(header_values_format);
# for each of the regions mentioned in the binary loop and populate all the
# required parameters
for region in range(num_of_regions):
basic_mem_region_values = struct.unpack_from(struct_mmu_regions_format,
read_buff,
size_read_from_binary);
size_read_from_binary += struct.calcsize(struct_mmu_regions_format);
if basic_mem_region_values[1] == 0:
continue
#validate for memory overlap here
for i in raw_info:
start_location = basic_mem_region_values[0]
end_location = basic_mem_region_values[0] + basic_mem_region_values[1]
overlap_occurred = ( (start_location >= i[0]) and \
(start_location <= (i[0]+i[1]))) and \
((end_location >= i[0]) and \
(end_location <= i[0]+i[1]))
if overlap_occurred:
validation_issue_memory_overlap = [True,
start_location,
get_pde_number(start_location)]
return
# add the retrived info another list
raw_info.append(basic_mem_region_values)
for region in raw_info:
pde_index = get_pde_number(region[0])
pte_valid_addr_start = get_pte_number(region[0])
# Get the end of the page table entries
# Since a memory region can take up only a few entries in the Page
# table, this helps us get the last valid PTE.
pte_valid_addr_end = get_pte_number(region[0] +
region[1] - 1)
mem_size = region[1]
# In-case the start address aligns with a page table entry other than zero
# and the mem_size is greater than (1024*4096) i.e 4MB
# in case where it overflows the currenty PDE's range then limit the
# PTE to 1024 and so make the mem_size reflect the actual size taken up
# in the current PDE
if (region[1] + (pte_valid_addr_start * 4096) ) >= (FourMB):
pte_valid_addr_end = 1023
mem_size = ( (1024 - pte_valid_addr_start)*4096)
set_pde_pte_values(pde_index, region[0], mem_size,
pte_valid_addr_start, pte_valid_addr_end, region[2])
if pde_index not in page_tables_list:
page_tables_list.append(pde_index)
# IF the current pde couldn't fit the entire requested region size then
# there is a need to create new PDEs to match the size.
# Here the overflow_size represents the size that couldn't be fit inside
# the current PDE, this is will now to used to create a new PDE/PDEs
# so the size remaining will be
# requested size - allocated size(in the current PDE)
overflow_size = region[1] - mem_size
# create all the extra PDEs needed to fit the requested size
# this loop starts from the current pde till the last pde that is needed
# the last pde is calcualted as the (start_addr + size) >> 22
if overflow_size != 0:
for extra_pde in range(pde_index+1, get_pde_number(
region[0] + region[1])+1):
# new pde's start address
# each page directory entry has a addr range of (1024 *4096)
# thus the new PDE start address is a multiple of that number
extra_pde_start_address = extra_pde*(FourMB)
# the start address of and extra pde will always be 0
# and the end address is calculated with the new pde's start address
# and the overflow_size
extra_pte_valid_addr_end = get_pte_number(extra_pde_start_address
+ overflow_size - 1)
# if the overflow_size couldn't be fit inside this new pde then
# need another pde and so we now need to limit the end of the PTE
# to 1024 and set the size of this new region to the max possible
extra_region_size = overflow_size
if overflow_size >= (FourMB):
extra_region_size = FourMB
extra_pte_valid_addr_end = 1023
# load the new PDE's details
set_pde_pte_values(extra_pde, extra_pde_start_address,
extra_region_size,
0, extra_pte_valid_addr_end, region[2] )
# for the next iteration of the loop the size needs to decreased
overflow_size -= extra_region_size
# print(hex_32(overflow_size),extra_pde)
if extra_pde not in page_tables_list:
page_tables_list.append(extra_pde)
if overflow_size == 0:
break
page_tables_list.sort()
def validate_pde_regions():
#validation for correct page alignment of the regions
for key, value in list_of_pde.items():
for pages_inside_pde in value.page_entries_info:
if pages_inside_pde.start_addr & (0xFFF) != 0:
print("Memory Regions are not page aligned",
hex(pages_inside_pde.start_addr))
sys.exit(2)
#validation for correct page alignment of the regions
if pages_inside_pde.size & (0xFFF) != 0:
print("Memory Regions size is not page aligned",
hex(pages_inside_pde.size))
sys.exit(2)
#validation for spiling of the regions across various
if validation_issue_memory_overlap[0] == True:
print("Memory Regions are overlapping at memory address " +
str(hex(validation_issue_memory_overlap[1]))+
" with Page directory Entry number " +
str(validation_issue_memory_overlap[2]))
sys.exit(2)
# the return value will have the page address and it is assumed to be a 4096 boundary
# hence the output of this API will be a 20bit address of the page table
def address_of_page_table(page_table_number):
global pd_start_addr
# location from where the Page tables will be written
PT_start_addr = pd_start_addr + 4096
return ( (PT_start_addr + (page_tables_list.index(page_table_number)*4096) >>12))
# union x86_mmu_pde_pt {
# u32_t value;
# struct {
# u32_t p:1;
# u32_t rw:1;
# u32_t us:1;
# u32_t pwt:1;
# u32_t pcd:1;
# u32_t a:1;
# u32_t ignored1:1;
# u32_t ps:1;
# u32_t ignored2:4;
# u32_t page_table:20;
# };
# };
def check_bits(val, bits):
for b in bits:
if val & (1 << b):
return 1
return 0
def page_directory_create_binary_file():
global output_buffer
global output_offset
for pde in range(1024):
binary_value = 0 # the page directory entry is not valid
# if i have a valid entry to populate
if pde in sorted(list_of_pde.keys()):
value = list_of_pde[pde]
perms = value.page_entries_info[0].permissions
present = 1 << 0;
read_write = check_bits(perms, [1, 29]) << 1;
user_mode = check_bits(perms, [2, 28]) << 2;
pwt = 0 << 3;
pcd = 0 << 4;
a = 0 << 5; # this is a read only field
ps = 0 << 7; # this is a read only field
page_table = address_of_page_table(value.pde_index) << 12;
binary_value = (present | read_write | user_mode | pwt | pcd | a | ps | page_table)
pde_verbose_output(pde, binary_value)
struct.pack_into(write_4byte_bin,output_buffer, output_offset, binary_value)
output_offset += struct.calcsize(write_4byte_bin)
# union x86_mmu_pte {
# u32_t value;
# struct {
# u32_t p:1;
# u32_t rw:1;
# u32_t us:1;
# u32_t pwt:1;
# u32_t pcd:1;
# u32_t a:1;
# u32_t d:1;
# u32_t pat:1;
# u32_t g:1;
# u32_t alloc:1;
# u32_t custom:2;
# u32_t page:20;
# };
# };
def page_table_create_binary_file():
global output_buffer
global output_offset
for key, value in sorted(list_of_pde.items()):
for pte in range(1024):
binary_value = 0 # the page directory entry is not valid
valid_pte = 0
for i in value.page_entries_info:
temp_value = ((pte >= i.pte_valid_addr_start) and (pte <= i.pte_valid_addr_end))
if temp_value:
perm_for_pte = i.permissions
valid_pte |= temp_value
# if i have a valid entry to populate
if valid_pte:
present = 1 << 0;
read_write = ( ( perm_for_pte >> 1) & 0x1) << 1;
user_mode = ( ( perm_for_pte >> 2) & 0x1) << 2;
pwt = 0 << 3;
pcd = 0 << 4;
a = 0 << 5; # this is a read only field
d = 0 << 6; # this is a read only field
pat = 0 << 7
g = 0<< 8
alloc = 1 << 9
custom = 0 <<10
# This points to the actual memory in the HW
# totally 20 bits to rep the phy address
# first 10 is the number got from pde and next 10 is pte
page_table = ((value.pde_index <<10) |pte) << 12;
binary_value = (present | read_write | user_mode |
pwt | pcd | a | d | pat | g | alloc | custom |
page_table)
pte_verbose_output(key,pte,binary_value)
struct.pack_into(write_4byte_bin, output_buffer, output_offset, binary_value)
output_offset += struct.calcsize(write_4byte_bin)
# Read the parameters passed to the file
def parse_args():
global args
parser = argparse.ArgumentParser(description = __doc__,
formatter_class = argparse.RawDescriptionHelpFormatter)
parser.add_argument("-e", "--big-endian", action="store_true",
help="Target encodes data in big-endian format"
"(little endian is the default)")
parser.add_argument("-i", "--input",
help="Input file from which MMU regions are read.")
parser.add_argument("-o", "--output",
help="Output file into which the page tables are written.")
parser.add_argument("-v", "--verbose", action="store_true",
help="Lists all the relavent data generated.")
args = parser.parse_args()
# the format for writing in the binary file would be decided by the
# endian selected
def set_struct_endian_format():
endian_string = "<"
if args.big_endian == True:
endian_string = ">"
global struct_mmu_regions_format
global header_values_format
global write_4byte_bin
struct_mmu_regions_format = endian_string + "III"
header_values_format = endian_string + "II"
write_4byte_bin = endian_string + "I"
def format_string(input_str):
output_str = '{0: <5}'.format(str(input_str))
return(output_str)
#format for 32bit hex value
def hex_32(input_value):
output_value ="{0:#0{1}x}".format(input_value,10)
return(output_value)
#format for 20bit hex value
def hex_20(input_value):
output_value ="{0:#0{1}x}".format(input_value,7)
return(output_value)
def pde_verbose_output(pde, binary_value):
if args.verbose == False:
return
global print_string_pde_list
present = format_string(binary_value & 0x1 )
read_write = format_string((binary_value >> 1 ) & 0x1 )
user_mode = format_string((binary_value >> 2 ) & 0x1 )
pwt = format_string((binary_value >> 3 ) & 0x1 )
pcd = format_string((binary_value >> 4 ) & 0x1 )
a = format_string((binary_value >> 5 ) & 0x1 )
ignored1 = format_string(0)
ps = format_string((binary_value >> 7 ) & 0x1 )
ignored2 = format_string(0000)
page_table_addr = format_string( hex((binary_value >> 12 ) & 0xFFFFF))
print_string_pde_list += ( format_string(str(pde))+" | "+(present)+ " | "+\
(read_write)+ " | "+\
(user_mode)+ " | "+\
(pwt)+ " | "+\
(pcd)+ " | "+\
(a)+ " | "+\
(ps)+ " | "+
page_table_addr +"\n"
)
def pde_print_elements():
global print_string_pde_list
print("PAGE DIRECTORY ")
print(format_string("PDE")+" | "+ \
format_string('P') +" | "+ \
format_string('rw') +" | "+ \
format_string('us') +" | "+ \
format_string('pwt') +" | "+ \
format_string('pcd') +" | "+ \
format_string('a') +" | "+ \
format_string('ps') +" | "+ \
format_string('Addr page table'))
print(print_string_pde_list)
print("END OF PAGE DIRECTORY")
def pte_verbose_output(pde, pte, binary_value):
global pde_pte_string
present = format_string( str((binary_value >> 0) & 0x1))
read_write = format_string( str((binary_value >> 1) & 0x1))
user_mode = format_string( str((binary_value >> 2) & 0x1))
pwt = format_string( str((binary_value >> 3) & 0x1))
pcd = format_string( str((binary_value >> 4) & 0x1))
a = format_string( str((binary_value >> 5) & 0x1))
d = format_string( str((binary_value >> 6) & 0x1))
pat = format_string( str((binary_value >> 7) & 0x1))
g = format_string( str((binary_value >> 8) & 0x1))
alloc = format_string( str((binary_value >> 9) & 0x1))
custom = format_string( str((binary_value >> 10) & 0x3))
page_table_addr = hex_20((binary_value >> 12) & 0xFFFFF)
print_string_list = ( format_string(str(pte))+" | "+(present)+ " | "+\
(read_write)+ " | "+\
(user_mode)+ " | "+\
(pwt)+ " | "+\
(pcd)+ " | "+\
(a)+ " | "+\
(d)+ " | "+\
(pat)+ " | "+\
(g)+ " | "+\
(alloc)+ " | "+\
(custom)+ " | "+\
page_table_addr +"\n"
)
if pde in pde_pte_string.keys():
pde_pte_string[pde] += (print_string_list)
else:
pde_pte_string[pde] = print_string_list
def pte_print_elements():
global pde_pte_string
for pde,print_string in sorted(pde_pte_string.items()):
print("\nPAGE TABLE "+str(pde))
print(format_string("PTE")+" | "+ \
format_string('P') +" | "+ \
format_string('rw') +" | "+ \
format_string('us') +" | "+ \
format_string('pwt') +" | "+ \
format_string('pcd') +" | "+ \
format_string('a') +" | "+ \
format_string('d') +" | "+ \
format_string('pat') +" | "+ \
format_string('g') +" | "+ \
format_string('alloc') +" | "+ \
format_string('custom') +" | "+ \
format_string('page addr'))
print(print_string)
print("END OF PAGE TABLE "+ str(pde))
def verbose_output():
if args.verbose == False:
return
print("\nMemory Regions as defined:")
for info in raw_info:
print("Memory region start address = " + hex_32(info[0]) +\
", Memory size = " + hex_32(info[1]) +\
", Permission = "+ hex(info[2]))
print("\nTotal Page directory entries " + str(len(list_of_pde.keys())))
count =0
for key, value in list_of_pde.items():
for i in value.page_entries_info:
count+=1
print("In Page directory entry "+format_string(value.pde_index) +\
": valid start address = "+ \
hex_32(i.start_addr) + ", end address = " + \
hex_32((i.pte_valid_addr_end +1 )*4096 -1 +\
(value.pde_index * (FourMB))))
pde_print_elements()
pte_print_elements()
def main():
global output_buffer
parse_args()
set_struct_endian_format()
global input_file
input_file = open(args.input, 'rb')
global binary_output_file
binary_output_file = open(args.output, 'wb')
# inputfile= file_name
read_mmu_list_marshal_param()
#validate the inputs
validate_pde_regions()
# The size of the output buffer has to match the number of bytes we write
# this corresponds to the number of page tables gets created.
output_buffer = ctypes.create_string_buffer((4096)+
(len(list_of_pde.keys()) *
4096))
page_directory_create_binary_file()
page_table_create_binary_file()
#write the binary data into the file
binary_output_file.write(output_buffer);
binary_output_file.close()
# verbose output needed by the build system
verbose_output()
if __name__ == "__main__":
main()