#!/usr/bin/env python3 # # Copyright (c) 2020 Intel Corporation # # SPDX-License-Identifier: Apache-2.0 """Create the kernel's page tables for x86 CPUs. For additional detail on paging and x86 memory management, please consult the IA Architecture SW Developer Manual, volume 3a, chapter 4. This script produces the initial page tables installed into the CPU at early boot. These pages will have an identity mapping of the kernel image. The script takes the 'zephyr_prebuilt.elf' as input to obtain region sizes, certain memory addresses, and configuration values. If CONFIG_SRAM_REGION_PERMISSIONS is not enabled, the kernel image will be mapped with the Present and Write bits set. The linker scripts shouldn't add page alignment padding between sections. If CONFIG_SRAM_REGION_PERMISSIONS is enabled, the access permissions vary: - By default, the Present, Write, and Execute Disable bits are set. - The __text_region region will have Present and User bits set - The __rodata_region region will have Present, User, and Execute Disable bits set - On x86_64, the _locore region will have Present set and the _lorodata region will have Present and Execute Disable set. This script will establish a dual mapping at the address defined by CONFIG_KERNEL_VM_BASE if it is not the same as CONFIG_SRAM_BASE_ADDRESS. - The double-mapping is used to transition the instruction pointer from a physical address at early boot to the virtual address where the kernel is actually linked. - The mapping is always double-mapped at the top-level paging structure and the physical/virtual base addresses must have the same alignment with respect to the scope of top-level paging structure entries. This allows the same second-level paging structure(s) to be used for both memory bases. - The double-mapping is needed so that we can still fetch instructions from identity-mapped physical addresses after we program this table into the MMU, then jump to the equivalent virtual address. The kernel then unlinks the identity mapping before continuing, the address space is purely virtual after that. Because the set of page tables are linked together by physical address, we must know a priori the physical address of each table. The linker script must define a z_x86_pagetables_start symbol where the page tables will be placed, and this memory address must not shift between prebuilt and final ELF builds. This script will not work on systems where the physical load address of the kernel is unknown at build time. 64-bit systems will always build IA-32e page tables. 32-bit systems build PAE page tables if CONFIG_X86_PAE is set, otherwise standard 32-bit page tables are built. The kernel will expect to find the top-level structure of the produced page tables at the physical address corresponding to the symbol z_x86_kernel_ptables. The linker script will need to set that symbol to the end of the binary produced by this script, minus the size of the top-level paging structure as it is written out last. """ import sys import array import argparse import ctypes import os import struct import re import textwrap from packaging import version import elftools from elftools.elf.elffile import ELFFile from elftools.elf.sections import SymbolTableSection if version.parse(elftools.__version__) < version.parse('0.24'): sys.exit("pyelftools is out of date, need version 0.24 or later") def bit(pos): """Get value by shifting 1 by pos""" return 1 << pos # Page table entry flags FLAG_P = bit(0) FLAG_RW = bit(1) FLAG_US = bit(2) FLAG_CD = bit(4) FLAG_SZ = bit(7) FLAG_G = bit(8) FLAG_XD = bit(63) FLAG_IGNORED0 = bit(9) FLAG_IGNORED1 = bit(10) FLAG_IGNORED2 = bit(11) ENTRY_RW = FLAG_RW | FLAG_IGNORED0 ENTRY_US = FLAG_US | FLAG_IGNORED1 ENTRY_XD = FLAG_XD | FLAG_IGNORED2 # PD_LEVEL and PT_LEVEL are used as list index to PtableSet.levels[] # to get table from back of list. PD_LEVEL = -2 PT_LEVEL = -1 def debug(text): """Display verbose debug message""" if not args.verbose: return sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") def verbose(text): """Display --verbose --verbose message""" if args.verbose and args.verbose > 1: sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") def error(text): """Display error message and exit program""" sys.exit(os.path.basename(sys.argv[0]) + ": " + text) def align_check(base, size, scope=4096): """Make sure base and size are page-aligned""" if (base % scope) != 0: error("unaligned base address %x" % base) if (size % scope) != 0: error("Unaligned region size 0x%x for base %x" % (size, base)) def dump_flags(flags): """Translate page table flags into string""" ret = "" if flags & FLAG_P: ret += "P " if flags & FLAG_RW: ret += "RW " if flags & FLAG_US: ret += "US " if flags & FLAG_G: ret += "G " if flags & FLAG_XD: ret += "XD " if flags & FLAG_SZ: ret += "SZ " if flags & FLAG_CD: ret += "CD " return ret.strip() def round_up(val, align): """Round up val to the next multiple of align""" return (val + (align - 1)) & (~(align - 1)) def round_down(val, align): """Round down val to the previous multiple of align""" return val & (~(align - 1)) # Hard-coded flags for intermediate paging levels. Permissive, we only control # access or set caching properties at leaf levels. INT_FLAGS = FLAG_P | FLAG_RW | FLAG_US class MMUTable(): """Represents a particular table in a set of page tables, at any level""" def __init__(self): self.entries = array.array(self.type_code, [0 for i in range(self.num_entries)]) def get_binary(self): """Return a bytearray representation of this table""" # Always little-endian ctype = "<" + self.type_code entry_size = struct.calcsize(ctype) ret = bytearray(entry_size * self.num_entries) for i in range(self.num_entries): struct.pack_into(ctype, ret, entry_size * i, self.entries[i]) return ret @property def supported_flags(self): """Class property indicating what flag bits are supported""" raise NotImplementedError() @property def addr_shift(self): """Class property for how much to shift virtual addresses to obtain the appropriate index in the table for it""" raise NotImplementedError() @property def addr_mask(self): """Mask to apply to an individual entry to get the physical address mapping""" raise NotImplementedError() @property def type_code(self): """Struct packing letter code for table entries. Either I for 32-bit entries, or Q for PAE/IA-32e""" raise NotImplementedError() @property def num_entries(self): """Number of entries in the table. Varies by table type and paging mode""" raise NotImplementedError() def entry_index(self, virt_addr): """Get the index of the entry in this table that corresponds to the provided virtual address""" return (virt_addr >> self.addr_shift) & (self.num_entries - 1) def has_entry(self, virt_addr): """Indicate whether an entry is present in this table for the provided virtual address""" index = self.entry_index(virt_addr) return (self.entries[index] & FLAG_P) != 0 def lookup(self, virt_addr): """Look up the physical mapping for a virtual address. If this is a leaf table, this is the physical address mapping. If not, this is the physical address of the next level table""" index = self.entry_index(virt_addr) return self.entries[index] & self.addr_mask def map(self, virt_addr, phys_addr, entry_flags): """For the table entry corresponding to the provided virtual address, set the corresponding physical entry in the table. Unsupported flags will be filtered out. If this is a leaf table, this is the physical address mapping. If not, this is the physical address of the next level table""" index = self.entry_index(virt_addr) verbose("%s: mapping 0x%x to 0x%x : %s" % (self.__class__.__name__, phys_addr, virt_addr, dump_flags(entry_flags))) self.entries[index] = ((phys_addr & self.addr_mask) | (entry_flags & self.supported_flags)) def set_perms(self, virt_addr, entry_flags): """"For the table entry corresponding to the provided virtual address, update just the flags, leaving the physical mapping alone. Unsupported flags will be filtered out.""" index = self.entry_index(virt_addr) verbose("%s: changing perm at 0x%x : %s" % (self.__class__.__name__, virt_addr, dump_flags(entry_flags))) self.entries[index] = ((self.entries[index] & self.addr_mask) | (entry_flags & self.supported_flags)) # Specific supported table types class Pml4(MMUTable): """Page mapping level 4 for IA-32e""" addr_shift = 39 addr_mask = 0x7FFFFFFFFFFFF000 type_code = 'Q' num_entries = 512 supported_flags = INT_FLAGS class Pdpt(MMUTable): """Page directory pointer table for IA-32e""" addr_shift = 30 addr_mask = 0x7FFFFFFFFFFFF000 type_code = 'Q' num_entries = 512 supported_flags = INT_FLAGS | FLAG_SZ | FLAG_CD class PdptPAE(Pdpt): """Page directory pointer table for PAE""" num_entries = 4 class Pd(MMUTable): """Page directory for 32-bit""" addr_shift = 22 addr_mask = 0xFFFFF000 type_code = 'I' num_entries = 1024 supported_flags = INT_FLAGS | FLAG_SZ | FLAG_CD class PdXd(Pd): """Page directory for either PAE or IA-32e""" addr_shift = 21 addr_mask = 0x7FFFFFFFFFFFF000 num_entries = 512 type_code = 'Q' class Pt(MMUTable): """Page table for 32-bit""" addr_shift = 12 addr_mask = 0xFFFFF000 type_code = 'I' num_entries = 1024 supported_flags = (FLAG_P | FLAG_RW | FLAG_US | FLAG_G | FLAG_CD | FLAG_IGNORED0 | FLAG_IGNORED1) class PtXd(Pt): """Page table for either PAE or IA-32e""" addr_mask = 0x07FFFFFFFFFFF000 type_code = 'Q' num_entries = 512 supported_flags = (FLAG_P | FLAG_RW | FLAG_US | FLAG_G | FLAG_XD | FLAG_CD | FLAG_IGNORED0 | FLAG_IGNORED1 | FLAG_IGNORED2) class PtableSet(): """Represents a complete set of page tables for any paging mode""" def __init__(self, pages_start): """Instantiate a set of page tables which will be located in the image starting at the provided physical memory location""" self.toplevel = self.levels[0]() self.page_pos = pages_start debug("%s starting at physical address 0x%x" % (self.__class__.__name__, self.page_pos)) # Database of page table pages. Maps physical memory address to # MMUTable objects, excluding the top-level table which is tracked # separately. Starts out empty as we haven't mapped anything and # the top-level table is tracked separately. self.tables = {} def get_new_mmutable_addr(self): """If we need to instantiate a new MMUTable, return a physical address location for it""" ret = self.page_pos self.page_pos += 4096 return ret @property def levels(self): """Class hierarchy of paging levels, with the first entry being the toplevel table class, and the last entry always being some kind of leaf page table class (Pt or PtXd)""" raise NotImplementedError() def is_mapped(self, virt_addr, level): """ Return True if virt_addr has already been mapped. level_from_last == 0 only searches leaf level page tables. level_from_last == 1 searches both page directories and page tables. """ table = self.toplevel num_levels = len(self.levels) + level + 1 has_mapping = False # Create and link up intermediate tables if necessary for depth in range(0, num_levels): # Create child table if needed if table.has_entry(virt_addr): if depth == num_levels: has_mapping = True else: table = self.tables[table.lookup(virt_addr)] if has_mapping: # pylint doesn't like break in the above if-block break return has_mapping def is_region_mapped(self, virt_base, size, level=PT_LEVEL): """Find out if a region has been mapped""" align_check(virt_base, size) for vaddr in range(virt_base, virt_base + size, 4096): if self.is_mapped(vaddr, level): return True return False def new_child_table(self, table, virt_addr, depth): """Create a new child table""" new_table_addr = self.get_new_mmutable_addr() new_table = self.levels[depth]() debug("new %s at physical addr 0x%x" % (self.levels[depth].__name__, new_table_addr)) self.tables[new_table_addr] = new_table table.map(virt_addr, new_table_addr, INT_FLAGS) return new_table def map_page(self, virt_addr, phys_addr, flags, reserve, level=PT_LEVEL): """Map a virtual address to a physical address in the page tables, with provided access flags""" table = self.toplevel num_levels = len(self.levels) + level + 1 # Create and link up intermediate tables if necessary for depth in range(1, num_levels): # Create child table if needed if not table.has_entry(virt_addr): table = self.new_child_table(table, virt_addr, depth) else: table = self.tables[table.lookup(virt_addr)] # Set up entry in leaf page table if not reserve: table.map(virt_addr, phys_addr, flags) def reserve(self, virt_base, size, to_level=PT_LEVEL): """Reserve page table space with already aligned virt_base and size""" debug("Reserving paging structures for 0x%x (0x%x)" % (virt_base, size)) align_check(virt_base, size) # How much memory is covered by leaf page table scope = 1 << self.levels[PD_LEVEL].addr_shift if virt_base % scope != 0: error("misaligned virtual address space, 0x%x not a multiple of 0x%x" % (virt_base, scope)) for addr in range(virt_base, virt_base + size, scope): self.map_page(addr, 0, 0, True, to_level) def reserve_unaligned(self, virt_base, size, to_level=PT_LEVEL): """Reserve page table space with virt_base and size alignment""" # How much memory is covered by leaf page table scope = 1 << self.levels[PD_LEVEL].addr_shift mem_start = round_down(virt_base, scope) mem_end = round_up(virt_base + size, scope) mem_size = mem_end - mem_start self.reserve(mem_start, mem_size, to_level) def map(self, phys_base, virt_base, size, flags, level=PT_LEVEL): """Map an address range in the page tables provided access flags. If virt_base is None, identity mapping using phys_base is done. """ is_identity_map = virt_base is None or virt_base == phys_base if virt_base is None: virt_base = phys_base scope = 1 << self.levels[level].addr_shift debug("Mapping 0x%x (0x%x) to 0x%x: %s" % (phys_base, size, virt_base, dump_flags(flags))) align_check(phys_base, size, scope) align_check(virt_base, size, scope) for paddr in range(phys_base, phys_base + size, scope): if is_identity_map and paddr == 0 and level == PT_LEVEL: # Never map the NULL page at page table level. continue vaddr = virt_base + (paddr - phys_base) self.map_page(vaddr, paddr, flags, False, level) def identity_map_unaligned(self, phys_base, size, flags, level=PT_LEVEL): """Identity map a region of memory""" scope = 1 << self.levels[level].addr_shift phys_aligned_base = round_down(phys_base, scope) phys_aligned_end = round_up(phys_base + size, scope) phys_aligned_size = phys_aligned_end - phys_aligned_base self.map(phys_aligned_base, None, phys_aligned_size, flags, level) def map_region(self, name, flags, virt_to_phys_offset, level=PT_LEVEL): """Map a named region""" if not isdef(name + "_start"): # Region may not exists return region_start = syms[name + "_start"] region_end = syms[name + "_end"] region_size = region_end - region_start region_start_phys = region_start if virt_to_phys_offset is not None: region_start_phys += virt_to_phys_offset self.map(region_start_phys, region_start, region_size, flags, level) def set_region_perms(self, name, flags, level=PT_LEVEL): """Set access permissions for a named region that is already mapped The bounds of the region will be looked up in the symbol table with _start and _size suffixes. The physical address mapping is unchanged and this will not disturb any double-mapping.""" if not isdef(name + "_start"): # Region may not exists return # Doesn't matter if this is a virtual address, we have a # either dual mapping or it's the same as physical base = syms[name + "_start"] if isdef(name + "_size"): size = syms[name + "_size"] else: region_end = syms[name + "_end"] size = region_end - base if size == 0: return debug("change flags for %s at 0x%x (0x%x): %s" % (name, base, size, dump_flags(flags))) num_levels = len(self.levels) + level + 1 scope = 1 << self.levels[level].addr_shift align_check(base, size, scope) try: for addr in range(base, base + size, scope): # Never map the NULL page if addr == 0: continue table = self.toplevel for _ in range(1, num_levels): table = self.tables[table.lookup(addr)] table.set_perms(addr, flags) except KeyError: error("no mapping for %s region 0x%x (size 0x%x)" % (name, base, size)) def write_output(self, filename): """Write the page tables to the output file in binary format""" written_size = 0 with open(filename, "wb") as output_fp: for addr in sorted(self.tables): mmu_table = self.tables[addr] mmu_table_bin = mmu_table.get_binary() output_fp.write(mmu_table_bin) written_size += len(mmu_table_bin) # We always have the top-level table be last. This is because # in PAE, the top-level PDPT has only 4 entries and is not a # full page in size. We do not put it in the tables dictionary # and treat it as a special case. debug("top-level %s at physical addr 0x%x" % (self.toplevel.__class__.__name__, self.get_new_mmutable_addr())) top_level_bin = self.toplevel.get_binary() output_fp.write(top_level_bin) written_size += len(top_level_bin) return written_size # Paging mode classes, we'll use one depending on configuration class Ptables32bit(PtableSet): """32-bit Page Tables""" levels = [Pd, Pt] class PtablesPAE(PtableSet): """PAE Page Tables""" levels = [PdptPAE, PdXd, PtXd] class PtablesIA32e(PtableSet): """Page Tables under IA32e mode""" levels = [Pml4, Pdpt, PdXd, PtXd] def parse_args(): """Parse command line arguments""" global args parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("-k", "--kernel", required=True, help="path to prebuilt kernel ELF binary") parser.add_argument("-o", "--output", required=True, help="output file") parser.add_argument("--map", action='append', help=textwrap.dedent('''\ Map extra memory: ,[,[,]] where flags can be empty or combination of: L - Large page (2MB or 4MB), U - Userspace accessible, W - Writable, X - Executable, D - Cache disabled. Default is small (4KB) page, supervisor only, read only, and execution disabled. ''')) parser.add_argument("-v", "--verbose", action="count", help="Print extra debugging information") args = parser.parse_args() if "VERBOSE" in os.environ: args.verbose = 1 def get_symbols(elf_obj): """Get all symbols from the ELF file""" for section in elf_obj.iter_sections(): if isinstance(section, SymbolTableSection): return {sym.name: sym.entry.st_value for sym in section.iter_symbols()} raise LookupError("Could not find symbol table") def isdef(sym_name): """True if symbol is defined in ELF file""" return sym_name in syms def find_symbol(obj, name): """Find symbol object from ELF file""" for section in obj.iter_sections(): if isinstance(section, SymbolTableSection): for sym in section.iter_symbols(): if sym.name == name: return sym return None def map_extra_regions(pt): """Map extra regions specified in command line""" # Extract command line arguments mappings = [] for entry in args.map: elements = entry.split(',') if len(elements) < 2: error("Not enough arguments for --map %s" % entry) one_map = {} one_map['cmdline'] = entry one_map['phys'] = int(elements[0], 0) one_map['size']= int(elements[1], 0) one_map['large_page'] = False flags = FLAG_P | ENTRY_XD if len(elements) > 2: map_flags = elements[2] # Check for allowed flags if not bool(re.match('^[LUWXD]*$', map_flags)): error("Unrecognized flags: %s" % map_flags) flags = FLAG_P | ENTRY_XD if 'W' in map_flags: flags |= ENTRY_RW if 'X' in map_flags: flags &= ~ENTRY_XD if 'U' in map_flags: flags |= ENTRY_US if 'L' in map_flags: flags |= FLAG_SZ one_map['large_page'] = True if 'D' in map_flags: flags |= FLAG_CD one_map['flags'] = flags if len(elements) > 3: one_map['virt'] = int(elements[3], 16) else: one_map['virt'] = one_map['phys'] mappings.append(one_map) # Map the regions for one_map in mappings: phys = one_map['phys'] size = one_map['size'] flags = one_map['flags'] virt = one_map['virt'] level = PD_LEVEL if one_map['large_page'] else PT_LEVEL # Check if addresses have already been mapped. # Error out if so as they could override kernel mappings. if pt.is_region_mapped(virt, size, level): error(("Region 0x%x (%d) already been mapped " "for --map %s" % (virt, size, one_map['cmdline']))) # Reserve space in page table, and map the region pt.reserve_unaligned(virt, size, level) pt.map(phys, virt, size, flags, level) def main(): """Main program""" global syms parse_args() with open(args.kernel, "rb") as elf_fp: kernel = ELFFile(elf_fp) syms = get_symbols(kernel) sym_dummy_pagetables = find_symbol(kernel, "dummy_pagetables") if sym_dummy_pagetables: reserved_pt_size = sym_dummy_pagetables['st_size'] else: reserved_pt_size = None if isdef("CONFIG_X86_64"): pclass = PtablesIA32e elif isdef("CONFIG_X86_PAE"): pclass = PtablesPAE else: pclass = Ptables32bit debug("building %s" % pclass.__name__) vm_base = syms["CONFIG_KERNEL_VM_BASE"] vm_size = syms["CONFIG_KERNEL_VM_SIZE"] vm_offset = syms["CONFIG_KERNEL_VM_OFFSET"] sram_base = syms["CONFIG_SRAM_BASE_ADDRESS"] sram_size = syms["CONFIG_SRAM_SIZE"] * 1024 mapped_kernel_base = syms["z_mapped_start"] mapped_kernel_size = syms["z_mapped_size"] if isdef("CONFIG_SRAM_OFFSET"): sram_offset = syms["CONFIG_SRAM_OFFSET"] else: sram_offset = 0 # Figure out if there is any need to do virtual-to-physical # address translation virt_to_phys_offset = (sram_base + sram_offset) - (vm_base + vm_offset) if isdef("CONFIG_ARCH_MAPS_ALL_RAM"): image_base = sram_base image_size = sram_size else: image_base = mapped_kernel_base image_size = mapped_kernel_size image_base_phys = image_base + virt_to_phys_offset ptables_phys = syms["z_x86_pagetables_start"] + virt_to_phys_offset debug("Address space: 0x%x - 0x%x size 0x%x" % (vm_base, vm_base + vm_size - 1, vm_size)) debug("Zephyr image: 0x%x - 0x%x size 0x%x" % (image_base, image_base + image_size - 1, image_size)) if virt_to_phys_offset != 0: debug("Physical address space: 0x%x - 0x%x size 0x%x" % (sram_base, sram_base + sram_size - 1, sram_size)) is_perm_regions = isdef("CONFIG_SRAM_REGION_PERMISSIONS") # Are pages in non-boot, non-pinned sections present at boot. is_generic_section_present = isdef("CONFIG_LINKER_GENERIC_SECTIONS_PRESENT_AT_BOOT") if image_size >= vm_size: error("VM size is too small (have 0x%x need more than 0x%x)" % (vm_size, image_size)) map_flags = 0 if is_perm_regions: # Don't allow execution by default for any pages. We'll adjust this # in later calls to pt.set_region_perms() map_flags = ENTRY_XD pt = pclass(ptables_phys) # Instantiate all the paging structures for the address space pt.reserve(vm_base, vm_size) # Map the zephyr image if is_generic_section_present: map_flags = map_flags | FLAG_P pt.map(image_base_phys, image_base, image_size, map_flags | ENTRY_RW) else: # When generic linker sections are not present in physical memory, # the corresponding virtual pages should not be mapped to non-existent # physical pages. So simply identity map them to create the page table # entries but without the present bit set. # Boot and pinned sections (if configured) will be mapped to # physical memory below. pt.map(image_base, image_base, image_size, map_flags | ENTRY_RW) if virt_to_phys_offset != 0: # Need to identity map the physical address space # as it is needed during early boot process. # This will be unmapped once z_x86_mmu_init() # is called. # Note that this only does the identity mapping # at the page directory level to minimize wasted space. pt.reserve_unaligned(image_base_phys, image_size, to_level=PD_LEVEL) pt.identity_map_unaligned(image_base_phys, image_size, FLAG_P | FLAG_RW | FLAG_SZ, level=PD_LEVEL) if isdef("CONFIG_X86_64"): # 64-bit has a special region in the first 64K to bootstrap other CPUs # from real mode locore_base = syms["_locore_start"] locore_size = syms["_lodata_end"] - locore_base debug("Base addresses: physical 0x%x size 0x%x" % (locore_base, locore_size)) pt.map(locore_base, None, locore_size, map_flags | FLAG_P | ENTRY_RW) if isdef("CONFIG_XIP"): # Additionally identity-map all ROM as read-only pt.map(syms["CONFIG_FLASH_BASE_ADDRESS"], None, syms["CONFIG_FLASH_SIZE"] * 1024, map_flags | FLAG_P) if isdef("CONFIG_LINKER_USE_BOOT_SECTION"): pt.map_region("lnkr_boot", map_flags | FLAG_P | ENTRY_RW, virt_to_phys_offset) if isdef("CONFIG_LINKER_USE_PINNED_SECTION"): pt.map_region("lnkr_pinned", map_flags | FLAG_P | ENTRY_RW, virt_to_phys_offset) # Process extra mapping requests if args.map: map_extra_regions(pt) # Adjust mapped region permissions if configured if is_perm_regions: # Need to accomplish the following things: # - Text regions need the XD flag cleared and RW flag removed # if not built with gdbstub support # - Rodata regions need the RW flag cleared # - User mode needs access as we currently do not separate application # text/rodata from kernel text/rodata if isdef("CONFIG_GDBSTUB"): flags = ENTRY_US | ENTRY_RW else: flags = ENTRY_US if is_generic_section_present: flags = flags | FLAG_P pt.set_region_perms("__text_region", flags) if isdef("CONFIG_LINKER_USE_BOOT_SECTION"): pt.set_region_perms("lnkr_boot_text", flags | FLAG_P) if isdef("CONFIG_LINKER_USE_PINNED_SECTION"): pt.set_region_perms("lnkr_pinned_text", flags | FLAG_P) flags = ENTRY_US | ENTRY_XD if is_generic_section_present: flags = flags | FLAG_P pt.set_region_perms("__rodata_region", flags) if isdef("CONFIG_LINKER_USE_BOOT_SECTION"): pt.set_region_perms("lnkr_boot_rodata", flags | FLAG_P) if isdef("CONFIG_LINKER_USE_PINNED_SECTION"): pt.set_region_perms("lnkr_pinned_rodata", flags | FLAG_P) if isdef("CONFIG_COVERAGE_GCOV") and isdef("CONFIG_USERSPACE"): # If GCOV is enabled, user mode must be able to write to its # common data area pt.set_region_perms("__gcov_bss", FLAG_P | ENTRY_RW | ENTRY_US | ENTRY_XD) if isdef("CONFIG_X86_64"): # Set appropriate permissions for locore areas much like we did # with the main text/rodata regions if isdef("CONFIG_X86_KPTI"): # Set the User bit for the read-only locore/lorodata areas. # This ensures they get mapped into the User page tables if # KPTI is turned on. There is no sensitive data in them, and # they contain text/data needed to take an exception or # interrupt. flag_user = ENTRY_US else: flag_user = 0 pt.set_region_perms("_locore", FLAG_P | flag_user) pt.set_region_perms("_lorodata", FLAG_P | ENTRY_XD | flag_user) written_size = pt.write_output(args.output) debug("Written %d bytes to %s" % (written_size, args.output)) # Warn if reserved page table is not of correct size if reserved_pt_size and written_size != reserved_pt_size: # Figure out how many extra pages needed size_diff = written_size - reserved_pt_size page_size = syms["CONFIG_MMU_PAGE_SIZE"] extra_pages_needed = int(round_up(size_diff, page_size) / page_size) if isdef("CONFIG_X86_EXTRA_PAGE_TABLE_PAGES"): extra_pages_kconfig = syms["CONFIG_X86_EXTRA_PAGE_TABLE_PAGES"] if isdef("CONFIG_X86_64"): extra_pages_needed += ctypes.c_int64(extra_pages_kconfig).value else: extra_pages_needed += ctypes.c_int32(extra_pages_kconfig).value reason = "big" if reserved_pt_size > written_size else "small" error(("Reserved space for page table is too %s." " Set CONFIG_X86_EXTRA_PAGE_TABLE_PAGES=%d") % (reason, extra_pages_needed)) if __name__ == "__main__": main()