1#!/usr/bin/env python
2#
3# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
4#
5# SPDX-License-Identifier: BSD-2-Clause
6#
7"""
8Generate an initial list of untyped objects based on the available physical
9memory regions, the kernel image size, user image size, and devices on a platform
10"""
11
12import argparse
13import logging
14from collections import namedtuple, deque, defaultdict
15
16from elftools.elf.elffile import ELFFile
17from pyaml import yaml
18from sortedcontainers import SortedList
19
20from capdl import register_object_sizes
21from capdl.ELF import ELF
22from capdl.Object import get_object_size_bits, ObjectType
23from capdl.util import ctz, valid_architectures, lookup_architecture, round_down, round_up, PAGE_SIZE
24
25Region = namedtuple('Region', ['start', 'end'])
26
27
28def create_untypeds_for_region(object_sizes, region, arch, device):
29    untyped = []
30    start = region.start
31    end = region.end
32    while start != end:
33        assert (start <= end)
34        size_bits = (end - start).bit_length() - 1 if start < end else arch.word_size_bits()
35        align_bits = ctz(start) if start > 0 else size_bits
36        size_bits = min(size_bits,
37                        align_bits,
38                        object_sizes['seL4_Value_MaxUntypedBits'])
39        if size_bits > object_sizes['seL4_Value_MinUntypedBits']:
40            untyped.append({'device': device, 'size_bits': size_bits, 'paddr': start})
41        start += 1 << size_bits
42    return untyped
43
44
45def init_freemem(available, reserved):
46    """
47    Remove any reserved regions from available and return a new list of
48    available regions that does not contain any reserved regions
49
50    This method mirrors init_freemem in the kernel.
51    """
52
53    freemem = []
54    available = deque(available)
55    reserved = deque(reserved)
56    while len(available) and len(reserved):
57        if reserved[0].start == reserved[0].end:
58            # reserved region is empty - skip it
59            reserved.popleft()
60        if available[0].start >= available[0].end:
61            # skip the entire region - it's empty now after trimming
62            available.popleft()
63        elif reserved[0].end <= available[0].start:
64            # the reserved region is below the available region - skip it
65            reserved.popleft()
66        elif reserved[0].start >= available[0].end:
67            # the reserved region is above the available region - take the whole thing
68            freemem.append(available[0])
69            available.popleft()
70        else:
71            # the reserved region overlaps with the available region
72            if reserved[0].start <= available[0].start:
73                # the region overlaps with the start of the available region.
74                # trim start of the available region
75                available[0] = Region(min(available[0].end, reserved[0].end),
76                                      available[0].end)
77                reserved.popleft()
78            else:
79                assert reserved[0].start < available[0].end
80                # take the first chunk of the available region and move
81                # the start to the end of the reserved region
82                freemem.append(Region(available[0].start, reserved[0].start))
83                if available[0].end > reserved[0].end:
84                    available[0] = Region(reserved[0].end, available[0].end)
85                    reserved.popleft()
86                else:
87                    available.popleft()
88
89    # no more reserved regions - add the rest
90    freemem += list(available)
91    return freemem
92
93
94def get_load_bounds(elf):
95    end = 0
96    start = 0xFFFFFFFFFFFFFFFF
97    for s in elf.iter_segments():
98        if s['p_type'] == 'PT_LOAD':
99            paddr = s['p_paddr']
100            memsz = s['p_memsz']
101            start = min(paddr, start)
102            end = max(paddr + memsz, end)
103    print("ELF image: {0}<-->{1}".format(hex(start), hex(end)))
104    return Region(start, end)
105
106
107def get_symbol_size(elf, name):
108    symbol_table = elf.get_section_by_name('.symtab')
109    symbol = symbol_table.get_symbol_by_name(name)
110    if not symbol:
111        logging.fatal("No symbol {0}".format(name))
112    return symbol['st_size']
113
114
115def main(args):
116    arch = lookup_architecture(args.architecture)
117    addresses = yaml.load(args.input)
118    object_sizes = yaml.load(args.object_sizes)
119    register_object_sizes(object_sizes)
120
121    # create the list of reserved regions. This duplicates the load_images part of the elf loader. Ultimately
122    # we should feed this info to the elf loader rather than doing it dynamically
123    reserved = SortedList()
124    # first the kernel image
125    kernel_elf = ELFFile(args.kernel_elf)
126    kernel_region = get_load_bounds(kernel_elf)
127    # elfloader currently rounds end to page boundary
128    kernel_region = Region(kernel_region.start, round_up(kernel_region.end, PAGE_SIZE))
129    reserved.add(kernel_region)
130
131    # now the DTB
132    next_paddr = kernel_region.end
133    if args.dtb_size:
134        dtb_region = Region(next_paddr, round_up(next_paddr + args.dtb_size, PAGE_SIZE))
135        reserved.add(dtb_region)
136        print("DTB: {0}<-->{1}".format(hex(dtb_region.start), hex(dtb_region.end)))
137
138    available = SortedList()
139    for a in addresses['memory']:
140        # trim to paddr-top
141        start, end = a['start'], a['end']
142        if args.paddr_top is not None:
143            start = min(start, args.paddr_top)
144            end = min(end, args.paddr_top)
145        if start != end:
146            available.add(Region(start, end))
147
148    # calculate free regions based on available + reserved regions
149    freemem = init_freemem(available, reserved)
150
151    # create untyped for each region
152    untypeds = []
153    for f in freemem:
154        untypeds += create_untypeds_for_region(object_sizes, f, arch, False)
155
156    # create untyped for each device untyped
157    for d in addresses['devices']:
158        untypeds += create_untypeds_for_region(object_sizes,
159                                               Region(d['start'], d['end']), arch, True)
160
161    # finally output the file
162    yaml.dump(untypeds, args.output)
163
164
165if __name__ == '__main__':
166    def int_or_hex(s):
167        if s.strip().startswith('0x'):
168            return int(s, 0)
169        else:
170            return int(s)
171
172    parser = argparse.ArgumentParser()
173    parser.add_argument('--input', required=True, type=argparse.FileType('r'),
174                        help='input yaml describing physical and device memory')
175    parser.add_argument('--output', required=True, type=argparse.FileType('w'),
176                        help='output file for generated untyped yaml')
177    parser.add_argument('--linker', type=argparse.FileType('w'), help="Output file for linker")
178    parser.add_argument('--object-sizes', required=True, type=argparse.FileType('r'),
179                        help='Yaml file with kernel object sizes')
180    parser.add_argument('--extra-bi-size-bits', default=0, type=int,
181                        help='Size_bits of extra bootinfo frame (0 if none)')
182    parser.add_argument('--kernel-elf', type=argparse.FileType('rb'),
183                        help='Kernel elf file', required=False)
184    parser.add_argument('--paddr-top', type=int_or_hex,
185                        help='Kernel\'s PADDR_TOP (highest usable physical memory addr)')
186    parser.add_argument('--architecture', choices=valid_architectures())
187    parser.add_argument('--dtb-size', type=int_or_hex, default=0,
188                        help='DTB (device tree binary) blob size')
189    parser.add_argument('--elffile', nargs='+', action='append')
190
191    main(parser.parse_args())
192