1#
2# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3#
4# SPDX-License-Identifier: BSD-2-Clause
5#
6
7'''
8Wrapper around a dict of pages for some extra functionality. Only intended to
9be used internally.
10'''
11
12from __future__ import absolute_import, division, print_function, \
13    unicode_literals
14
15from .Cap import Cap
16from .Object import ASIDPool, Frame
17from .Spec import Spec
18from .util import round_down, PAGE_SIZE, lookup_architecture
19import collections
20
21
22def consume(iterator):
23    '''Take a generator and exhaust it. Useful for discarding the unused result
24    of something that would otherwise accumulate in memory. Clagged from
25    https://docs.python.org/2/library/itertools.html'''
26    # feed the entire iterator into a zero-length deque
27    collections.deque(iterator, maxlen=0)
28
29
30class PageCollection(object):
31    def __init__(self, name='', arch='arm11', infer_asid=True, vspace_root=None):
32        self.name = name
33        self.arch = arch
34        self._pages = {}
35        self._vspace_root = vspace_root
36        self._asid = None
37        self.infer_asid = infer_asid
38        self._spec = None
39
40    def add_page(self, vaddr, read=False, write=False, execute=False, size=PAGE_SIZE, elffill=[]):
41        if vaddr not in self._pages:
42            # Only create this page if we don't already have it.
43            self._pages[vaddr] = {
44                'read': False,
45                'write': False,
46                'execute': False,
47                'size': PAGE_SIZE,
48                'elffill': [],
49            }
50        # Now upgrade this page's permissions to meet our current requirements.
51        self._pages[vaddr]['read'] |= read
52        self._pages[vaddr]['write'] |= write
53        self._pages[vaddr]['execute'] |= execute
54        self._pages[vaddr]['size'] = size
55        self._pages[vaddr]['elffill'].extend(elffill)
56
57    def __getitem__(self, key):
58        return self._pages[key]
59
60    def __iter__(self):
61        return self._pages.__iter__()
62
63    def get_vspace_root(self):
64        if not self._vspace_root:
65            vspace = lookup_architecture(self.arch).vspace()
66            self._vspace_root = vspace.make_object('%s_%s' % (vspace.type_name, self.name))
67        return self._vspace_root, Cap(self._vspace_root)
68
69    def get_asid(self):
70        if not self._asid and self.infer_asid:
71            self._asid = ASIDPool('asid_%s' % self.name)
72            self._asid[0] = self.get_vspace_root()[1]
73        return self._asid
74
75    def _get_page_cap(self, existing_frames, page, page_vaddr, page_counter, spec):
76        '''
77        Get a mapping cap from somewhere. First check if the existing_frames we
78        were given contain a cap already.  Otherwise create a Frame and Cap from
79        the mapping information we have.
80        '''
81        if page_vaddr in existing_frames:
82            (size, cap) = existing_frames[page_vaddr]
83            assert size == page['size']
84            return cap
85        frame = Frame('frame_%s_%04d' % (self.name, page_counter),
86                      page['size'])
87        spec.add_object(frame)
88        return Cap(frame, read=page['read'], write=page['write'],
89                   grant=page['execute'])
90
91    def get_spec(self, existing_frames={}):
92        if self._spec is not None:
93            return self._spec
94
95        spec = Spec(self.arch)
96
97        # Page directory and ASID.
98        vspace_root, vspace_root_cap = self.get_vspace_root()
99        spec.add_object(vspace_root)
100        asid = self.get_asid()
101        if asid is not None:
102            spec.add_object(asid)
103
104        # Construct frames and infer page objects from the pages.
105        vspace = spec.arch.vspace()
106        object_counter = 0
107        objects = {}
108        for page_counter, (page_vaddr, page) in enumerate(self._pages.items()):
109            page_cap = self._get_page_cap(existing_frames, page, page_vaddr, page_counter, spec)
110            # Walk the hierarchy, creating missing objects until we can
111            # insert the frame
112            level = vspace
113            parent = vspace_root
114            while level.child is not None and page['size'] < level.child.coverage:
115                level = level.child
116                object_vaddr = level.base_vaddr(page_vaddr)
117                object_index = level.parent_index(object_vaddr)
118                if (level, object_vaddr) not in objects:
119                    object = level.make_object('%s_%s_%04d' % (
120                        level.type_name, self.name, object_counter))
121                    object_counter += 1
122                    spec.add_object(object)
123                    object_cap = Cap(object)
124                    parent[object_index] = object_cap
125                    objects[(level, object_vaddr)] = object
126                parent = parent[object_index].referent
127            object_counter += 1
128            if page_cap and page_cap.mapping_deferred:
129                # This cap requires set_mapping to be called on it to provide a reference
130                # to the mapping container and index. This is so the loader can use the same
131                # cap for mapping and then copy it into the target cspace. Otherwise the cap
132                # would be copied and therefore be an unmapped cap.
133                page_cap.set_mapping(parent, level.child_index(page_vaddr))
134                page_cap = Cap(page_cap.referent, read=page_cap.read,
135                               write=page_cap.write, grant=page_cap.grant, cached=page_cap.cached)
136            parent[level.child_index(page_vaddr)] = page_cap
137            if page_cap:
138                page["elffill"].extend(page_cap.referent.fill)
139                page_cap.referent.fill = page["elffill"]
140
141        # Cache the result for next time.
142        assert self._spec is None
143        self._spec = spec
144
145        return spec
146
147
148def create_address_space(regions, name='', arch='arm11'):
149    assert isinstance(regions, list)
150
151    pages = PageCollection(name, arch)
152    for r in regions:
153        assert 'start' in r
154        assert 'end' in r
155        v = round_down(r['start'])
156        while round_down(v) < r['end']:
157            pages.add_page(v, r.get('read', False), r.get('write', False),
158                           r.get('execute', False))
159            v += PAGE_SIZE
160
161    return pages
162