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