scripts: x86: MMU: Script to generate page tables
Generates the MMU page tables. This creates a binary output for the page tables. It takes an binary input which represents the configuration information needed to generate the page tables. JIRA:ZEP-2096 Signed-off-by: Adithya Baglody <adithya.nagaraj.baglody@intel.com>
This commit is contained in:
parent
576c1ebba2
commit
a407739ef4
1 changed files with 525 additions and 0 deletions
525
scripts/gen_mmu.py
Normal file
525
scripts/gen_mmu.py
Normal file
|
@ -0,0 +1,525 @@
|
|||
#!/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 = {}
|
||||
|
||||
#############
|
||||
|
||||
#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
|
||||
|
||||
|
||||
|
||||
# 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
|
||||
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()
|
||||
raw_info=[]
|
||||
|
||||
# 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);
|
||||
|
||||
#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])
|
||||
|
||||
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)
|
||||
# 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) ) > (1024*4096):
|
||||
pte_valid_addr_end = 1024
|
||||
mem_size = ( (pte_valid_addr_end - 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] - \
|
||||
((pte_valid_addr_end -
|
||||
pte_valid_addr_start) * 4096)
|
||||
|
||||
# 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
|
||||
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*(4096*1024)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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 > (1024*4096):
|
||||
extra_region_size = 1024*4096
|
||||
extra_pte_valid_addr_end = 1024
|
||||
|
||||
# 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_pte_valid_addr_end) * 4096
|
||||
|
||||
if extra_pde not in page_tables_list:
|
||||
page_tables_list.append(extra_pde)
|
||||
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 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]
|
||||
|
||||
present = 1 << 0;
|
||||
read_write = ( ( value.page_entries_info[0].permissions >> 1) & 0x1) << 1;
|
||||
user_mode = ( ( value.page_entries_info[0].permissions >> 2) & 0x1) << 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)
|
||||
|
||||
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 = format_string( str(hex((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("\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("Memory Region "+str(count) +" start address = "+
|
||||
str(hex(i.start_addr)))
|
||||
|
||||
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()
|
Loading…
Add table
Add a link
Reference in a new issue