forked from zephyrproject-rtos/zephyr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgen_mmu.py
executable file
·937 lines (724 loc) · 32.1 KB
/
gen_mmu.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
#!/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, allow_abbrev=False)
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:
<physical address>,<size>[,<flags:LUWXD>[,<virtual address>]]
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()