1#
2# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7import functools
8import hardware
9
10
11@functools.total_ordering
12class Region:
13    ''' Represents a region of memory. '''
14
15    def __init__(self, base: int, size: int, owner: 'WrappedNode' = None):
16        self.base = base
17        self.size = size
18        self.owner = owner
19
20    @staticmethod
21    def clone(other):
22        return Region(other.base, other.size)
23
24    def __repr__(self):
25        ''' Returns a string representation that is a valid Python expression
26        that eval() can parse. '''
27        return 'Region(base=0x{:x},size=0x{:x})'.format(self.base, self.size)
28
29    def __str__(self):
30        ''' Returns a string representation. '''
31        return 'Region [0x{:x}..0x{:x}] ({:d} bytes)'.format(
32            self.base,
33            self.base + self.size - (1 if self.size > 0 else 0),
34            self.size)
35
36    def __eq__(self, other):
37        return self.base == other.base and self.size == other.size
38
39    def __ne__(self, other):
40        # Needed only for py2.
41        return not self.__eq__(other)
42
43    def __gt__(self, other):
44        return self.base > other.base
45
46    def __hash__(self):
47        return hash((self.base, self.size))
48
49    @staticmethod
50    def from_range(start, end, owner=None):
51        ''' create a region from a start/end rather than start/size '''
52        if start > end:
53            raise ValueError(
54                'invalid rage start (0x{:x}) > end (0x{:x})'.format(start > end))
55        return Region(start, end - start, owner)
56
57    def overlaps(self, other):
58        ''' returns True if this region overlaps the given region '''
59        # Either our base is first, and to overlap our end must be > other.base
60        # or other.base is first, and to overlap other's end must be > self.base
61        return (self.base <= other.base and (self.base + self.size) > other.base) \
62            or (other.base <= self.base and (other.base + other.size) > self.base)
63
64    def reserve(self, excluded):
65        ''' returns an array of regions that represent this region
66        minus the excluded range '''
67        if not self.overlaps(excluded):
68            return [Region(self.base, self.size, self.owner)]
69
70        ret = []
71        if self.base < excluded.base:
72            # the first region is from our base to excluded.base
73            ret.append(Region.from_range(self.base, excluded.base, self.owner))
74            # skip the region from excluded.base - excluded.base + excluded.size
75            # if there's anything left, add it.
76            if (excluded.base + excluded.size) < (self.base + self.size):
77                ret.append(Region.from_range(excluded.base + excluded.size,
78                                             self.base + self.size, self.owner))
79        else:  # self.base >= excluded.base
80            # we skip the first chunk
81            # we add what's left after the current chunk.
82            if (self.base + self.size) > (excluded.base + excluded.size):
83                ret.append(Region.from_range(excluded.base + excluded.size,
84                                             self.base + self.size, self.owner))
85        return ret
86
87    def align_base(self, align_bits):
88        ''' align this region up to a given number of bits '''
89        new_base = hardware.utils.align_up(self.base, align_bits)
90        diff = new_base - self.base
91        if self.size < diff:
92            raise ValueError(
93                'can''t align region base to {} bits, {} too small'.format(
94                    align_bits, self))
95        # This could become an empty region now. We don't care, the caller has
96        # to check if this region still fits its needs.
97        return Region(new_base, self.size - diff, self.owner)
98
99    def align_size(self, align_bits):
100        ''' align this region's size to a given number of bits.
101         will move the base address down and the region's size
102         up '''
103        new_base = hardware.utils.align_down(self.base, align_bits)
104        new_size = hardware.utils.align_up(self.size, align_bits)
105        return Region(new_base, new_size, self.owner)
106
107    def make_chunks(self, chunksz):
108        base = self.base
109        size = self.size
110        ret = []
111        while size > 0:
112            ret.append(Region(base, min(size, chunksz), self.owner))
113            base += chunksz
114            size -= chunksz
115        return ret
116