1 // Copyright 2017 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6 
7 #include <lib/memory_limit.h>
8 
9 #include <assert.h>
10 #include <err.h>
11 #include <fbl/algorithm.h>
12 #include <inttypes.h>
13 #include <kernel/cmdline.h>
14 #include <kernel/range_check.h>
15 #include <platform.h>
16 #include <pretty/sizes.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <trace.h>
20 #include <vm/bootreserve.h>
21 #include <vm/pmm.h>
22 #include <vm/vm.h>
23 
24 // The max bytes of memory allowed by the system. Since it's specified in MB via the command
25 // line argument it will always be page aligned.
26 static size_t SystemMemoryLimit = 0;
27 // On init this is set to the memory limit and then decremented as we add memory to the system.
28 static size_t SystemMemoryRemaining = 0;
29 static bool MemoryLimitDbg = false;
30 
31 typedef struct reserve_entry {
32     uintptr_t start;     // Start of the reserved range
33     size_t len;          // Length of the reserved range, does not change as start/end are adjusted
34     uintptr_t end;       // End of the reserved range
35     size_t unused_front; // Space before the region that is available
36     size_t unused_back;  // Space after the region that is available
37 } reserve_entry_t;
38 
39 // Boot reserve entries are processed and added here for memory limit calculations.
40 const size_t kReservedRegionMax = 64;
41 static reserve_entry_t ReservedRegions[kReservedRegionMax];
42 static size_t ReservedRegionCount;
43 
add_arena(uintptr_t base,size_t size,pmm_arena_info_t arena_template)44 static zx_status_t add_arena(uintptr_t base, size_t size, pmm_arena_info_t arena_template) {
45     auto arena = arena_template;
46     arena.base = base;
47     arena.size = size;
48     return pmm_add_arena(&arena);
49 }
50 
print_reserve_state(void)51 static void print_reserve_state(void) {
52     for (size_t i = 0; i < ReservedRegionCount; i++) {
53         const auto& entry = ReservedRegions[i];
54         printf("%zu: [f: %-#10" PRIxPTR " |%#10" PRIxPTR " - %-#10" PRIxPTR "| (len: %#10" PRIxPTR
55                 ") b: %-#10" PRIxPTR "]\n", i, entry.unused_front, entry.start, entry.end,
56                 entry.len, entry.unused_back);
57     }
58 }
59 
memory_limit_init()60 zx_status_t memory_limit_init() {
61     if (!SystemMemoryLimit) {
62         ReservedRegionCount = 0;
63         SystemMemoryLimit = cmdline_get_uint64("kernel.memory-limit-mb", 0u) * MB;
64         if (!SystemMemoryLimit) {
65             return ZX_ERR_NOT_SUPPORTED;
66         }
67         MemoryLimitDbg = cmdline_get_bool("kernel.memory-limit-dbg", false);
68         SystemMemoryRemaining = SystemMemoryLimit;
69         return ZX_OK;
70     }
71 
72     return ZX_ERR_BAD_STATE;
73 }
74 
memory_limit_add_range(uintptr_t range_base,size_t range_size,pmm_arena_info_t arena_template)75 zx_status_t memory_limit_add_range(uintptr_t range_base,
76                                    size_t range_size,
77                                    pmm_arena_info_t arena_template) {
78     // Arenas passed to us should never overlap. For that reason we can get a good idea of whether
79     // a given memory limit can fit all the reserved regions by getting the total.
80     auto cb = [range_base, range_size](reserve_range_t reserve) {
81         // Is this reserved region in the arena?
82         uintptr_t in_offset;
83         size_t in_len;
84         // If there's no intersection then move on to the next reserved region.
85         if (!GetIntersect(range_base, range_size, reserve.pa, reserve.len, &in_offset, &in_len)) {
86             return true;
87         }
88 
89         auto& entry = ReservedRegions[ReservedRegionCount];
90         entry.start = reserve.pa;
91         entry.len = reserve.len;
92         entry.end = reserve.pa + reserve.len;
93 
94         // For the first pass the goal is to ensure we can include all reserved ranges along
95         // with enough space for their bookkeeping if we have to trim the arenas down due to
96         // memory restrictions.
97         if (ReservedRegionCount == 0) {
98             entry.unused_front = entry.start - range_base;
99             entry.unused_back = 0;
100         } else {
101             auto& prev = ReservedRegions[ReservedRegionCount - 1];
102             // There's no limit to how many memory ranges may be added by the platform so we
103             // need to figure out if we're in a new contiguous range, or contiguously next to
104             // another reservation so we know where to set our starting point for this section.
105             uintptr_t start = fbl::max(range_base, prev.end);
106             if (start == prev.end) {
107                 // We're next to someone! Figure out the gap space and share some of it with them.
108                 // This prevents corner case situations such as reserved regions on the edge of the
109                 // start or end, or where an expanded region may almost line up with the region
110                 // following it, but takes up enough space that the remaining region has no space to
111                 // allocate pages for its own bookkeeping. It also simplifies the logic for growing
112                 // space later, and results in less 'cheating' if we've allocated all of our
113                 // specified space and have to add room for a region's bookkeeping regardless. These
114                 // problems can be resolved in other ways, but they require extra passes, or more
115                 // complicated solutions.
116                 size_t spare_pages = (reserve.pa - start) / PAGE_SIZE;
117                 entry.unused_front = (spare_pages / 2) * PAGE_SIZE;
118                 prev.unused_back = (spare_pages / 2) * PAGE_SIZE;
119                 // If the page count was odd, account for the remaining page.
120                 if (spare_pages & 0x1) {
121                     entry.unused_front += PAGE_SIZE;
122                 }
123             } else {
124                 entry.unused_front = reserve.pa - start;
125             }
126         }
127 
128         // Increment our number of regions and move to the next, unless we've hit the limit.
129         ReservedRegionCount++;
130         return (ReservedRegionCount < kReservedRegionMax);
131     };
132 
133     // Something bad happened if we return false from a callback, so just add the arena outright
134     // now to prevent the system from falling over when it tries to wire out the heap.
135     if (!boot_reserve_foreach(cb)) {
136         add_arena(range_base, range_size, arena_template);
137         return ZX_ERR_OUT_OF_RANGE;
138     }
139 
140     // If there's still space between the last reserved region in an arena and the end of the
141     // arena then it should be accounted for in that last reserved region.
142     if (ReservedRegionCount) {
143         auto& last_entry = ReservedRegions[ReservedRegionCount - 1];
144         if (Intersects(range_base, range_size, last_entry.start, last_entry.end)) {
145             last_entry.unused_back = (range_base + range_size) - last_entry.end;
146         }
147     }
148 
149     if (MemoryLimitDbg) {
150         printf("MemoryLimit: Processed arena [%#" PRIxPTR " - %#" PRIxPTR "]\n", range_base,
151                range_base + range_size);
152     }
153 
154     return ZX_OK;
155 }
156 
memory_limit_add_arenas(pmm_arena_info_t arena_template)157 zx_status_t memory_limit_add_arenas(pmm_arena_info_t arena_template) {
158     // First pass, add up the memory needed for reserved ranges
159     size_t required_for_reserved = 0;
160     for (size_t i = 0; i < ReservedRegionCount; i++) {
161         const auto& entry = ReservedRegions[i];
162         required_for_reserved += (entry.end - entry.start);
163     }
164 
165     char lim[16];
166     printf("MemoryLimit: Limit of %s provided by kernel.memory-limit-mb\n",
167             format_size(lim, sizeof(lim), SystemMemoryRemaining));
168     if (required_for_reserved > SystemMemoryRemaining) {
169         char req[16];
170         printf("MemoryLimit: reserved regions need %s at a minimum!\n",
171                 format_size(req, sizeof(req), required_for_reserved));
172         return ZX_ERR_NO_MEMORY;
173     }
174 
175     SystemMemoryRemaining -= required_for_reserved;
176     if (MemoryLimitDbg) {
177         printf("MemoryLimit: First Pass, %#" PRIxPTR " remaining\n", SystemMemoryRemaining);
178         print_reserve_state();
179     }
180 
181     // Second pass, expand to take memory from the front / back of each region
182     for (size_t i = 0; i < ReservedRegionCount; i++) {
183         auto& entry = ReservedRegions[i];
184         // Now expand based on any remaining memory we have to spare from the front
185         // and back of the reserved region.
186         size_t available = fbl::min(SystemMemoryRemaining, entry.unused_front);
187         if (available) {
188             SystemMemoryRemaining -= available;
189             entry.unused_front -= available;
190             entry.start = PAGE_ALIGN(entry.start - available);
191         }
192 
193         available = fbl::min(SystemMemoryRemaining, entry.unused_back);
194         if (available) {
195             SystemMemoryRemaining -= available;
196             entry.unused_back -= available;
197             entry.end = PAGE_ALIGN(entry.end + available);
198         }
199 
200         // Calculate how many pages are needed to hold the vm_page_t entries for this range
201         size_t pages_needed = ROUNDUP_PAGE_SIZE((entry.len / PAGE_SIZE) * sizeof(vm_page_t));
202         // Now add extra pages to account for pages added in the previous step
203         const size_t vm_pages_per_page = PAGE_SIZE / sizeof(vm_page_t);
204         pages_needed += (pages_needed + vm_pages_per_page - 1) / vm_pages_per_page;
205         // Check if there is enough space in the range to hold the reserve region's bookkeeping
206         // in a contiguous block on either side of it. If necessary, add some space if possible.
207         size_t needed = ROUNDUP_PAGE_SIZE(((entry.len * 101) / 100));
208         if (needed > (entry.end - entry.start)) {
209             size_t pages_needed = (entry.len / PAGE_SIZE);
210             size_t diff = needed - entry.len;
211             printf("MemoryLimit: %zu needs %#zx for bookkeeping still (%zu pages)\n", i, diff, pages_needed);
212             if (entry.unused_front > diff) {
213                 entry.unused_front -= diff;
214                 entry.start -= diff;
215             } else if (entry.unused_back > diff) {
216                 entry.unused_back -= diff;
217                 entry.end += diff;
218             } else {
219                 printf("KMemoryLimit: Unable to grow %zu to fit bookkeeping. Need %#" PRIxPTR "\n",
220                        i, diff);
221                 return ZX_ERR_NO_MEMORY;
222             }
223         }
224     }
225 
226     if (MemoryLimitDbg) {
227         printf("MemoryLimit: Second Pass, %#" PRIxPTR " remaining\n", SystemMemoryRemaining);
228         print_reserve_state();
229     }
230 
231     // Third pass, coalesce the regions into the smallest number of arenas possible
232     for (size_t i = 0; i < ReservedRegionCount - 1; i++) {
233         auto& cur = ReservedRegions[i];
234         auto& next = ReservedRegions[i + 1];
235 
236         if (cur.end == next.start) {
237             printf("SystemMemoryLimit: merging |%#" PRIxPTR " - %#" PRIxPTR "| and |%#" PRIxPTR " - %#"
238                    PRIxPTR "|\n", cur.start, cur.end, next.start, next.end);
239             cur.end = next.end;
240             memmove(&ReservedRegions[i + 1], &ReservedRegions[i + 2],
241                     sizeof(reserve_entry_t) * (ReservedRegionCount - i - 2));
242             // We've removed on entry and we also need to compare this new current entry to the new
243             // next entry. To do so, we hold our position in the loop and come around again.
244             ReservedRegionCount--;
245             i--;
246         }
247     }
248 
249     if (MemoryLimitDbg) {
250         printf("MemoryLimit: Third Pass\n");
251         print_reserve_state();
252         printf("MemoryLimit: Fourth Pass\n");
253     }
254 
255     // Last pass, add arenas to the system
256     for (uint i = 0; i < ReservedRegionCount; i++) {
257         auto& entry = ReservedRegions[i];
258         size_t size = entry.end - entry.start;
259         if (MemoryLimitDbg) {
260             printf("MemoryLimit: adding [%#" PRIxPTR " - %#" PRIxPTR "]\n", entry.start, entry.end);
261         }
262         zx_status_t status = add_arena(entry.start, size, arena_template);
263         if (status != ZX_OK) {
264             printf("MemoryLimit: Failed to add arena [%#" PRIxPTR " - %#" PRIxPTR
265                    "]: %d, system problems may result!\n",
266                    entry.start, entry.end, status);
267         }
268     }
269 
270     return ZX_OK;
271 }
272