xref: /freebsd/stand/kboot/libkboot/seg.c (revision 4caaab2e6d492c30b21de6aa9c251d5071724eb9)
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