1757dc8abSWarner Losh /*-
2757dc8abSWarner Losh * Copyright (c) 2023, Netflix, Inc.
3757dc8abSWarner Losh *
4757dc8abSWarner Losh * SPDX-License-Identifier: BSD-2-Clause
5757dc8abSWarner Losh */
6757dc8abSWarner Losh
7757dc8abSWarner Losh #include "stand.h"
8757dc8abSWarner Losh #include "seg.h"
9757dc8abSWarner Losh
10757dc8abSWarner Losh #include <sys/param.h>
11757dc8abSWarner Losh
12757dc8abSWarner Losh static struct memory_segments *segs;
13757dc8abSWarner Losh static int nr_seg = 0;
14757dc8abSWarner Losh static int segalloc = 0;
15757dc8abSWarner Losh
16757dc8abSWarner Losh void
init_avail(void)17757dc8abSWarner Losh init_avail(void)
18757dc8abSWarner Losh {
19757dc8abSWarner Losh if (segs)
20757dc8abSWarner Losh free(segs);
21757dc8abSWarner Losh nr_seg = 0;
22757dc8abSWarner Losh segalloc = 16;
23ed1b3f13SWarner Losh free(segs);
24757dc8abSWarner Losh segs = malloc(sizeof(*segs) * segalloc);
25757dc8abSWarner Losh if (segs == NULL)
26757dc8abSWarner Losh panic("not enough memory to get memory map\n");
27757dc8abSWarner Losh }
28757dc8abSWarner Losh
29757dc8abSWarner Losh /*
30757dc8abSWarner Losh * Make sure at least n items can be accessed in the segs array. Note the
31757dc8abSWarner Losh * realloc here will invalidate cached pointers (potentially), so addresses
32757dc8abSWarner Losh * into the segs array must be recomputed after this call.
33757dc8abSWarner Losh */
34757dc8abSWarner Losh void
need_avail(int n)35757dc8abSWarner Losh need_avail(int n)
36757dc8abSWarner Losh {
37757dc8abSWarner Losh if (n <= segalloc)
38757dc8abSWarner Losh return;
39757dc8abSWarner Losh
40757dc8abSWarner Losh while (n > segalloc)
41757dc8abSWarner Losh segalloc *= 2;
42757dc8abSWarner Losh segs = realloc(segs, segalloc * sizeof(*segs));
43757dc8abSWarner Losh if (segs == NULL)
44757dc8abSWarner Losh panic("not enough memory to get memory map\n");
45757dc8abSWarner Losh }
46757dc8abSWarner Losh
47757dc8abSWarner Losh /*
48757dc8abSWarner Losh * Always called for a new range, so always just append a range,
49757dc8abSWarner Losh * unless it's continuous with the prior range.
50757dc8abSWarner Losh */
51757dc8abSWarner Losh void
add_avail(uint64_t start,uint64_t end,uint64_t type)52757dc8abSWarner Losh add_avail(uint64_t start, uint64_t end, uint64_t type)
53757dc8abSWarner Losh {
54757dc8abSWarner Losh /*
55757dc8abSWarner Losh * This range is contiguous with the previous range, and is
56757dc8abSWarner Losh * the same type: we can collapse the two.
57757dc8abSWarner Losh */
58757dc8abSWarner Losh if (nr_seg >= 1 &&
59757dc8abSWarner Losh segs[nr_seg - 1].end + 1 == start &&
60757dc8abSWarner Losh segs[nr_seg - 1].type == type) {
61757dc8abSWarner Losh segs[nr_seg - 1].end = end;
62757dc8abSWarner Losh return;
63757dc8abSWarner Losh }
64757dc8abSWarner Losh
65757dc8abSWarner Losh /*
66757dc8abSWarner Losh * Otherwise we need to add a new range at the end, but don't need to
67757dc8abSWarner Losh * adjust the current end.
68757dc8abSWarner Losh */
69757dc8abSWarner Losh need_avail(nr_seg + 1);
70757dc8abSWarner Losh segs[nr_seg].start = start;
71757dc8abSWarner Losh segs[nr_seg].end = end;
72757dc8abSWarner Losh segs[nr_seg].type = type;
73757dc8abSWarner Losh nr_seg++;
74757dc8abSWarner Losh }
75757dc8abSWarner Losh
76757dc8abSWarner Losh /*
77757dc8abSWarner Losh * All or part of a prior entry needs to be modified. Given the structure of the
78757dc8abSWarner Losh * code, we know that it will always be modifying the last time and/or extending
79757dc8abSWarner Losh * the one before it if its contiguous.
80757dc8abSWarner Losh */
81757dc8abSWarner Losh void
remove_avail(uint64_t start,uint64_t end,uint64_t type)82757dc8abSWarner Losh remove_avail(uint64_t start, uint64_t end, uint64_t type)
83757dc8abSWarner Losh {
84757dc8abSWarner Losh struct memory_segments *s;
85757dc8abSWarner Losh
86757dc8abSWarner Losh /*
87757dc8abSWarner Losh * simple case: we are extending a previously removed item.
88757dc8abSWarner Losh */
89757dc8abSWarner Losh if (nr_seg >= 2) {
90757dc8abSWarner Losh s = &segs[nr_seg - 2];
91757dc8abSWarner Losh if (s->end + 1 == start &&
92757dc8abSWarner Losh s->type == type) {
93757dc8abSWarner Losh s->end = end;
94757dc8abSWarner Losh /* Now adjust the ending element */
95757dc8abSWarner Losh s++;
96757dc8abSWarner Losh if (s->end == end) {
97757dc8abSWarner Losh /* we've used up the 'free' space */
98757dc8abSWarner Losh nr_seg--;
99757dc8abSWarner Losh return;
100757dc8abSWarner Losh }
101757dc8abSWarner Losh /* Otherwise adjust the 'free' space */
102757dc8abSWarner Losh s->start = end + 1;
103757dc8abSWarner Losh return;
104757dc8abSWarner Losh }
105757dc8abSWarner Losh }
106757dc8abSWarner Losh
107757dc8abSWarner Losh /*
108757dc8abSWarner Losh * OK, we have four cases:
109757dc8abSWarner Losh * (1) The new chunk is at the start of the free space, but didn't catch the above
110757dc8abSWarner Losh * folding for whatever reason (different type, start of space). In this case,
111757dc8abSWarner Losh * we allocate 1 additional item. The current end is copied to the new end. The
112757dc8abSWarner Losh * current end is set to <start, end, type> and the new end's start is set to end + 1.
113757dc8abSWarner Losh * (2) The new chunk is in the middle of the free space. In this case we allocate 2
114757dc8abSWarner Losh * additional items. We copy the current end to the new end, set the new end's start
115757dc8abSWarner Losh * to end + 1, the old end's end to start - 1 and the new item is <start, end, type>
116757dc8abSWarner Losh * (3) The new chunk is at the end of the current end. In this case we allocate 1 more
117757dc8abSWarner Losh * and adjust the current end's end to start - 1 and set the new end to <start, end, type>.
118757dc8abSWarner Losh * (4) The new chunk is exactly the current end, except for type. In this case, we just adjust
119757dc8abSWarner Losh * the type.
120757dc8abSWarner Losh * We can assume we always have at least one chunk since that's created with new_avail() above
121757dc8abSWarner Losh * necessarily before we are called to subset it.
122757dc8abSWarner Losh */
123757dc8abSWarner Losh s = &segs[nr_seg - 1];
124757dc8abSWarner Losh if (s->start == start) {
125757dc8abSWarner Losh if (s->end == end) { /* (4) */
126757dc8abSWarner Losh s->type = type;
127757dc8abSWarner Losh return;
128757dc8abSWarner Losh }
129757dc8abSWarner Losh /* chunk at start of old chunk -> (1) */
130757dc8abSWarner Losh need_avail(nr_seg + 1);
131757dc8abSWarner Losh s = &segs[nr_seg - 1]; /* Realloc may change pointers */
132757dc8abSWarner Losh s[1] = s[0];
133757dc8abSWarner Losh s->start = start;
134757dc8abSWarner Losh s->end = end;
135757dc8abSWarner Losh s->type = type;
136757dc8abSWarner Losh s[1].start = end + 1;
137757dc8abSWarner Losh nr_seg++;
138757dc8abSWarner Losh return;
139757dc8abSWarner Losh }
140757dc8abSWarner Losh if (s->end == end) { /* At end of old chunk (3) */
141757dc8abSWarner Losh need_avail(nr_seg + 1);
142757dc8abSWarner Losh s = &segs[nr_seg - 1]; /* Realloc may change pointers */
143757dc8abSWarner Losh s[1] = s[0];
144757dc8abSWarner Losh s->end = start - 1;
145757dc8abSWarner Losh s[1].start = start;
146757dc8abSWarner Losh s[1].type = type;
147757dc8abSWarner Losh nr_seg++;
148757dc8abSWarner Losh return;
149757dc8abSWarner Losh }
150757dc8abSWarner Losh /* In the middle, need to split things up (2) */
151757dc8abSWarner Losh need_avail(nr_seg + 2);
152757dc8abSWarner Losh s = &segs[nr_seg - 1]; /* Realloc may change pointers */
153757dc8abSWarner Losh s[2] = s[1] = s[0];
154757dc8abSWarner Losh s->end = start - 1;
155757dc8abSWarner Losh s[1].start = start;
156757dc8abSWarner Losh s[1].end = end;
157757dc8abSWarner Losh s[1].type = type;
158757dc8abSWarner Losh s[2].start = end + 1;
159757dc8abSWarner Losh nr_seg += 2;
160757dc8abSWarner Losh }
161757dc8abSWarner Losh
162757dc8abSWarner Losh void
print_avail(void)163757dc8abSWarner Losh print_avail(void)
164757dc8abSWarner Losh {
165757dc8abSWarner Losh printf("Found %d RAM segments:\n", nr_seg);
166757dc8abSWarner Losh
167757dc8abSWarner Losh for (int i = 0; i < nr_seg; i++) {
168757dc8abSWarner Losh printf("%#jx-%#jx type %lu\n",
169757dc8abSWarner Losh (uintmax_t)segs[i].start,
170757dc8abSWarner Losh (uintmax_t)segs[i].end,
171757dc8abSWarner Losh (u_long)segs[i].type);
172757dc8abSWarner Losh }
173757dc8abSWarner Losh }
174757dc8abSWarner Losh
175757dc8abSWarner Losh uint64_t
first_avail(uint64_t align,uint64_t min_size,uint64_t memtype)176757dc8abSWarner Losh first_avail(uint64_t align, uint64_t min_size, uint64_t memtype)
177757dc8abSWarner Losh {
178757dc8abSWarner Losh uint64_t s, len;
179757dc8abSWarner Losh
180757dc8abSWarner Losh for (int i = 0; i < nr_seg; i++) {
181757dc8abSWarner Losh if (segs[i].type != memtype) /* Not candidate */
182757dc8abSWarner Losh continue;
183757dc8abSWarner Losh s = roundup(segs[i].start, align);
184757dc8abSWarner Losh if (s >= segs[i].end) /* roundup past end */
185757dc8abSWarner Losh continue;
186757dc8abSWarner Losh len = segs[i].end - s + 1;
187757dc8abSWarner Losh if (len >= min_size) {
188757dc8abSWarner Losh printf("Found a big enough hole at in seg %d at %#jx (%#jx-%#jx)\n",
189757dc8abSWarner Losh i,
190757dc8abSWarner Losh (uintmax_t)s,
191757dc8abSWarner Losh (uintmax_t)segs[i].start,
192757dc8abSWarner Losh (uintmax_t)segs[i].end);
193757dc8abSWarner Losh return (s);
194757dc8abSWarner Losh }
195757dc8abSWarner Losh }
196757dc8abSWarner Losh
197757dc8abSWarner Losh return (0);
198757dc8abSWarner Losh }
199757dc8abSWarner Losh
200757dc8abSWarner Losh enum types {
201757dc8abSWarner Losh system_ram = SYSTEM_RAM,
202757dc8abSWarner Losh firmware_reserved,
203757dc8abSWarner Losh linux_code,
204757dc8abSWarner Losh linux_data,
205757dc8abSWarner Losh linux_bss,
206757dc8abSWarner Losh unknown,
207757dc8abSWarner Losh };
208757dc8abSWarner Losh
209757dc8abSWarner Losh static struct kv
210757dc8abSWarner Losh {
211757dc8abSWarner Losh uint64_t type;
212757dc8abSWarner Losh char * name;
213757dc8abSWarner Losh int flags;
214757dc8abSWarner Losh #define KV_KEEPER 1
215757dc8abSWarner Losh } str2type_kv[] = {
216757dc8abSWarner Losh { linux_code, "Kernel code", KV_KEEPER },
217757dc8abSWarner Losh { linux_data, "Kernel data", KV_KEEPER },
218757dc8abSWarner Losh { linux_bss, "Kernel bss", KV_KEEPER },
219*4caaab2eSWarner Losh { firmware_reserved, "Reserved" },
220757dc8abSWarner Losh };
221757dc8abSWarner Losh
222757dc8abSWarner Losh static const char *
parse_line(const char * line,uint64_t * startp,uint64_t * endp)223757dc8abSWarner Losh parse_line(const char *line, uint64_t *startp, uint64_t *endp)
224757dc8abSWarner Losh {
225757dc8abSWarner Losh const char *walker;
226757dc8abSWarner Losh char *next;
227757dc8abSWarner Losh uint64_t start, end;
228757dc8abSWarner Losh
229757dc8abSWarner Losh /*
230757dc8abSWarner Losh * Each line is a range followed by a description of the form:
231757dc8abSWarner Losh * <hex-number><dash><hex-number><space><colon><space><string>
232757dc8abSWarner Losh * Bail if we have any parsing errors.
233757dc8abSWarner Losh */
234757dc8abSWarner Losh walker = line;
235757dc8abSWarner Losh start = strtoull(walker, &next, 16);
236757dc8abSWarner Losh if (start == ULLONG_MAX || walker == next)
237757dc8abSWarner Losh return (NULL);
238757dc8abSWarner Losh walker = next;
239757dc8abSWarner Losh if (*walker != '-')
240757dc8abSWarner Losh return (NULL);
241757dc8abSWarner Losh walker++;
242757dc8abSWarner Losh end = strtoull(walker, &next, 16);
243757dc8abSWarner Losh if (end == ULLONG_MAX || walker == next)
244757dc8abSWarner Losh return (NULL);
245757dc8abSWarner Losh walker = next;
246757dc8abSWarner Losh /* Now eat the ' : ' in front of the string we want to return */
247757dc8abSWarner Losh if (strncmp(walker, " : ", 3) != 0)
248757dc8abSWarner Losh return (NULL);
249757dc8abSWarner Losh *startp = start;
250757dc8abSWarner Losh *endp = end;
251757dc8abSWarner Losh return (walker + 3);
252757dc8abSWarner Losh }
253757dc8abSWarner Losh
254757dc8abSWarner Losh static struct kv *
kvlookup(const char * str,struct kv * kvs,size_t nkv)255757dc8abSWarner Losh kvlookup(const char *str, struct kv *kvs, size_t nkv)
256757dc8abSWarner Losh {
257757dc8abSWarner Losh for (int i = 0; i < nkv; i++)
258757dc8abSWarner Losh if (strcmp(kvs[i].name, str) == 0)
259757dc8abSWarner Losh return (&kvs[i]);
260757dc8abSWarner Losh
261757dc8abSWarner Losh return (NULL);
262757dc8abSWarner Losh }
263757dc8abSWarner Losh
264757dc8abSWarner Losh /* Trim trailing whitespace */
265757dc8abSWarner Losh static void
chop(char * line)266757dc8abSWarner Losh chop(char *line)
267757dc8abSWarner Losh {
268757dc8abSWarner Losh char *ep = line + strlen(line) - 1;
269757dc8abSWarner Losh
270757dc8abSWarner Losh while (ep >= line && isspace(*ep))
271757dc8abSWarner Losh *ep-- = '\0';
272757dc8abSWarner Losh }
273757dc8abSWarner Losh
274757dc8abSWarner Losh #define SYSTEM_RAM_STR "System RAM"
275757dc8abSWarner Losh #define RESERVED "reserved"
276757dc8abSWarner Losh
277757dc8abSWarner Losh bool
populate_avail_from_iomem(void)278757dc8abSWarner Losh populate_avail_from_iomem(void)
279757dc8abSWarner Losh {
280757dc8abSWarner Losh int fd;
281757dc8abSWarner Losh char buf[128];
282757dc8abSWarner Losh const char *str;
283757dc8abSWarner Losh uint64_t start, end;
284757dc8abSWarner Losh struct kv *kv;
285757dc8abSWarner Losh
286757dc8abSWarner Losh fd = open("host:/proc/iomem", O_RDONLY);
287757dc8abSWarner Losh if (fd == -1) {
288757dc8abSWarner Losh printf("Can't get memory map\n");
289757dc8abSWarner Losh init_avail();
290757dc8abSWarner Losh // Hack: 32G of RAM starting at 4G
291757dc8abSWarner Losh add_avail(4ull << 30, 36ull << 30, system_ram);
292757dc8abSWarner Losh return false;
293757dc8abSWarner Losh }
294757dc8abSWarner Losh
295757dc8abSWarner Losh if (fgetstr(buf, sizeof(buf), fd) < 0)
296757dc8abSWarner Losh goto out; /* Nothing to do ???? */
297757dc8abSWarner Losh init_avail();
298757dc8abSWarner Losh chop(buf);
299757dc8abSWarner Losh while (true) {
300757dc8abSWarner Losh /*
301757dc8abSWarner Losh * Look for top level items we understand. Skip anything that's
302757dc8abSWarner Losh * a continuation, since we don't care here. If we care, we'll
303757dc8abSWarner Losh * consume them all when we recognize that top level item.
304757dc8abSWarner Losh */
305757dc8abSWarner Losh if (buf[0] == ' ') /* Continuation lines? Ignore */
306757dc8abSWarner Losh goto next_line;
307757dc8abSWarner Losh str = parse_line(buf, &start, &end);
308757dc8abSWarner Losh if (str == NULL) /* Malformed -> ignore */
309757dc8abSWarner Losh goto next_line;
310757dc8abSWarner Losh /*
311757dc8abSWarner Losh * All we care about is System RAM
312757dc8abSWarner Losh */
313757dc8abSWarner Losh if (strncmp(str, SYSTEM_RAM_STR, sizeof(SYSTEM_RAM_STR) - 1) == 0)
314757dc8abSWarner Losh add_avail(start, end, system_ram);
315757dc8abSWarner Losh else if (strncmp(str, RESERVED, sizeof(RESERVED) - 1) == 0)
316757dc8abSWarner Losh add_avail(start, end, firmware_reserved);
317757dc8abSWarner Losh else
318757dc8abSWarner Losh goto next_line; /* Ignore hardware */
319757dc8abSWarner Losh while (fgetstr(buf, sizeof(buf), fd) >= 0 && buf[0] == ' ') {
320757dc8abSWarner Losh chop(buf);
321757dc8abSWarner Losh str = parse_line(buf, &start, &end);
322757dc8abSWarner Losh if (str == NULL)
323757dc8abSWarner Losh break;
324757dc8abSWarner Losh kv = kvlookup(str, str2type_kv, nitems(str2type_kv));
325757dc8abSWarner Losh if (kv == NULL) /* failsafe for new types: igonre */
326757dc8abSWarner Losh remove_avail(start, end, unknown);
327757dc8abSWarner Losh else if ((kv->flags & KV_KEEPER) == 0)
328757dc8abSWarner Losh remove_avail(start, end, kv->type);
329757dc8abSWarner Losh /* Else no need to adjust since it's a keeper */
330757dc8abSWarner Losh }
331757dc8abSWarner Losh
332757dc8abSWarner Losh /*
333757dc8abSWarner Losh * if buf[0] == ' ' then we know that the fgetstr failed and we
334757dc8abSWarner Losh * should break. Otherwise fgetstr succeeded and we have a
335757dc8abSWarner Losh * buffer we need to examine for being a top level item.
336757dc8abSWarner Losh */
337757dc8abSWarner Losh if (buf[0] == ' ')
338757dc8abSWarner Losh break;
339757dc8abSWarner Losh chop(buf);
340757dc8abSWarner Losh continue; /* buf has next top level line to parse */
341757dc8abSWarner Losh next_line:
342757dc8abSWarner Losh if (fgetstr(buf, sizeof(buf), fd) < 0)
343757dc8abSWarner Losh break;
344757dc8abSWarner Losh }
345757dc8abSWarner Losh
346757dc8abSWarner Losh out:
347757dc8abSWarner Losh close(fd);
348757dc8abSWarner Losh return true;
349757dc8abSWarner Losh }
350757dc8abSWarner Losh
351757dc8abSWarner Losh /*
352757dc8abSWarner Losh * Return the amount of space available in the segment that @start@ lives in,
353757dc8abSWarner Losh * from @start@ to the end of the segment.
354757dc8abSWarner Losh */
355757dc8abSWarner Losh uint64_t
space_avail(uint64_t start)356757dc8abSWarner Losh space_avail(uint64_t start)
357757dc8abSWarner Losh {
358757dc8abSWarner Losh for (int i = 0; i < nr_seg; i++) {
359757dc8abSWarner Losh if (start >= segs[i].start && start <= segs[i].end)
360757dc8abSWarner Losh return segs[i].end - start;
361757dc8abSWarner Losh }
362757dc8abSWarner Losh
363757dc8abSWarner Losh /*
364757dc8abSWarner Losh * Properly used, we should never get here. Unsure if this should be a
365757dc8abSWarner Losh * panic or not.
366757dc8abSWarner Losh */
367757dc8abSWarner Losh return 0;
368757dc8abSWarner Losh }
369