xref: /freebsd/stand/kboot/kboot/seg.c (revision f126890ac5386406dadf7c4cfa9566cbb56537c5)
1 /*-
2  * Copyright (c) 2023, Netflix, Inc.
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include "stand.h"
8 #include "kboot.h"
9 
10 #include <sys/param.h>
11 
12 static struct memory_segments *segs;
13 static int nr_seg = 0;
14 static int segalloc = 0;
15 
16 void
17 init_avail(void)
18 {
19 	if (segs)
20 		free(segs);
21 	nr_seg = 0;
22 	segalloc = 16;
23 	segs = malloc(sizeof(*segs) * segalloc);
24 	if (segs == NULL)
25 		panic("not enough memory to get memory map\n");
26 }
27 
28 /*
29  * Make sure at least n items can be accessed in the segs array.  Note the
30  * realloc here will invalidate cached pointers (potentially), so addresses
31  * into the segs array must be recomputed after this call.
32  */
33 void
34 need_avail(int n)
35 {
36 	if (n <= segalloc)
37 		return;
38 
39 	while (n > segalloc)
40 		segalloc *= 2;
41 	segs = realloc(segs, segalloc * sizeof(*segs));
42 	if (segs == NULL)
43 		panic("not enough memory to get memory map\n");
44 }
45 
46 /*
47  * Always called for a new range, so always just append a range,
48  * unless it's continuous with the prior range.
49  */
50 void
51 add_avail(uint64_t start, uint64_t end, uint64_t type)
52 {
53 	/*
54 	 * This range is contiguous with the previous range, and is
55 	 * the same type: we can collapse the two.
56 	 */
57 	if (nr_seg >= 1 &&
58 	    segs[nr_seg - 1].end + 1 == start &&
59 	    segs[nr_seg - 1].type == type) {
60 		segs[nr_seg - 1].end = end;
61 		return;
62 	}
63 
64 	/*
65 	 * Otherwise we need to add a new range at the end, but don't need to
66 	 * adjust the current end.
67 	 */
68 	need_avail(nr_seg + 1);
69 	segs[nr_seg].start = start;
70 	segs[nr_seg].end = end;
71 	segs[nr_seg].type = type;
72 	nr_seg++;
73 }
74 
75 /*
76  * All or part of a prior entry needs to be modified. Given the structure of the
77  * code, we know that it will always be modifying the last time and/or extending
78  * the one before it if its contiguous.
79  */
80 void
81 remove_avail(uint64_t start, uint64_t end, uint64_t type)
82 {
83 	struct memory_segments *s;
84 
85 	/*
86 	 * simple case: we are extending a previously removed item.
87 	 */
88 	if (nr_seg >= 2) {
89 		s = &segs[nr_seg - 2];
90 		if (s->end + 1 == start &&
91 		    s->type == type) {
92 			s->end = end;
93 			/* Now adjust the ending element */
94 			s++;
95 			if (s->end == end) {
96 				/* we've used up the 'free' space */
97 				nr_seg--;
98 				return;
99 			}
100 			/* Otherwise adjust the 'free' space */
101 			s->start = end + 1;
102 			return;
103 		}
104 	}
105 
106 	/*
107 	 * OK, we have four cases:
108 	 * (1) The new chunk is at the start of the free space, but didn't catch the above
109 	 *     folding for whatever reason (different type, start of space). In this case,
110 	 *     we allocate 1 additional item. The current end is copied to the new end. The
111 	 *     current end is set to <start, end, type> and the new end's start is set to end + 1.
112 	 * (2) The new chunk is in the middle of the free space. In this case we allocate 2
113 	 *     additional items. We copy the current end to the new end, set the new end's start
114 	 *     to end + 1, the old end's end to start - 1 and the new item is <start, end, type>
115 	 * (3) The new chunk is at the end of the current end. In this case we allocate 1 more
116 	 *     and adjust the current end's end to start - 1 and set the new end to <start, end, type>.
117 	 * (4) The new chunk is exactly the current end, except for type. In this case, we just adjust
118 	 *     the type.
119 	 * We can assume we always have at least one chunk since that's created with new_avail() above
120 	 * necessarily before we are called to subset it.
121 	 */
122 	s = &segs[nr_seg - 1];
123 	if (s->start == start) {
124 		if (s->end == end) { /* (4) */
125 			s->type = type;
126 			return;
127 		}
128 		/* chunk at start of old chunk -> (1) */
129 		need_avail(nr_seg + 1);
130 		s = &segs[nr_seg - 1];	/* Realloc may change pointers */
131 		s[1] = s[0];
132 		s->start = start;
133 		s->end = end;
134 		s->type = type;
135 		s[1].start = end + 1;
136 		nr_seg++;
137 		return;
138 	}
139 	if (s->end == end) {	/* At end of old chunk (3) */
140 		need_avail(nr_seg + 1);
141 		s = &segs[nr_seg - 1];	/* Realloc may change pointers */
142 		s[1] = s[0];
143 		s->end = start - 1;
144 		s[1].start = start;
145 		s[1].type = type;
146 		nr_seg++;
147 		return;
148 	}
149 	/* In the middle, need to split things up (2) */
150 	need_avail(nr_seg + 2);
151 	s = &segs[nr_seg - 1];	/* Realloc may change pointers */
152 	s[2] = s[1] = s[0];
153 	s->end = start - 1;
154 	s[1].start = start;
155 	s[1].end = end;
156 	s[1].type = type;
157 	s[2].start = end + 1;
158 	nr_seg += 2;
159 }
160 
161 void
162 print_avail(void)
163 {
164 	printf("Found %d RAM segments:\n", nr_seg);
165 
166 	for (int i = 0; i < nr_seg; i++) {
167 		printf("%#jx-%#jx type %lu\n",
168 		    (uintmax_t)segs[i].start,
169 		    (uintmax_t)segs[i].end,
170 		    (u_long)segs[i].type);
171 	}
172 }
173 
174 uint64_t
175 first_avail(uint64_t align, uint64_t min_size, uint64_t memtype)
176 {
177 	uint64_t s, len;
178 
179 	for (int i = 0; i < nr_seg; i++) {
180 		if (segs[i].type != memtype)	/* Not candidate */
181 			continue;
182 		s = roundup(segs[i].start, align);
183 		if (s >= segs[i].end)		/* roundup past end */
184 			continue;
185 		len = segs[i].end - s + 1;
186 		if (len >= min_size) {
187 			printf("Found a big enough hole at in seg %d at %#jx (%#jx-%#jx)\n",
188 			    i,
189 			    (uintmax_t)s,
190 			    (uintmax_t)segs[i].start,
191 			    (uintmax_t)segs[i].end);
192 			return (s);
193 		}
194 	}
195 
196 	return (0);
197 }
198 
199 enum types {
200 	system_ram = SYSTEM_RAM,
201 	firmware_reserved,
202 	linux_code,
203 	linux_data,
204 	linux_bss,
205 	unknown,
206 };
207 
208 static struct kv
209 {
210 	uint64_t	type;
211 	char *		name;
212 	int		flags;
213 #define KV_KEEPER 1
214 } str2type_kv[] = {
215 	{ linux_code,		"Kernel code", KV_KEEPER },
216 	{ linux_data,		"Kernel data", KV_KEEPER },
217 	{ linux_bss,		"Kernel bss", KV_KEEPER },
218 	{ firmware_reserved,	"reserved" },
219 	{ 0, NULL },
220 };
221 
222 static const char *
223 parse_line(const char *line, uint64_t *startp, uint64_t *endp)
224 {
225 	const char *walker;
226 	char *next;
227 	uint64_t start, end;
228 
229 	/*
230 	 * Each line is a range followed by a description of the form:
231 	 * <hex-number><dash><hex-number><space><colon><space><string>
232 	 * Bail if we have any parsing errors.
233 	 */
234 	walker = line;
235 	start = strtoull(walker, &next, 16);
236 	if (start == ULLONG_MAX || walker == next)
237 		return (NULL);
238 	walker = next;
239 	if (*walker != '-')
240 		return (NULL);
241 	walker++;
242 	end = strtoull(walker, &next, 16);
243 	if (end == ULLONG_MAX || walker == next)
244 		return (NULL);
245 	walker = next;
246 	/* Now eat the ' : ' in front of the string we want to return */
247 	if (strncmp(walker, " : ", 3) != 0)
248 		return (NULL);
249 	*startp = start;
250 	*endp = end;
251 	return (walker + 3);
252 }
253 
254 static struct kv *
255 kvlookup(const char *str, struct kv *kvs, size_t nkv)
256 {
257 	for (int i = 0; i < nkv; i++)
258 		if (strcmp(kvs[i].name, str) == 0)
259 			return (&kvs[i]);
260 
261 	return (NULL);
262 }
263 
264 /* Trim trailing whitespace */
265 static void
266 chop(char *line)
267 {
268 	char *ep = line + strlen(line) - 1;
269 
270 	while (ep >= line && isspace(*ep))
271 		*ep-- = '\0';
272 }
273 
274 #define SYSTEM_RAM_STR "System RAM"
275 #define RESERVED "reserved"
276 
277 bool
278 populate_avail_from_iomem(void)
279 {
280 	int fd;
281 	char buf[128];
282 	const char *str;
283 	uint64_t start, end;
284 	struct kv *kv;
285 
286 	fd = open("host:/proc/iomem", O_RDONLY);
287 	if (fd == -1) {
288 		printf("Can't get memory map\n");
289 		init_avail();
290 		// Hack: 32G of RAM starting at 4G
291 		add_avail(4ull << 30, 36ull << 30, system_ram);
292 		return false;
293 	}
294 
295 	if (fgetstr(buf, sizeof(buf), fd) < 0)
296 		goto out;	/* Nothing to do ???? */
297 	init_avail();
298 	chop(buf);
299 	while (true) {
300 		/*
301 		 * Look for top level items we understand.  Skip anything that's
302 		 * a continuation, since we don't care here. If we care, we'll
303 		 * consume them all when we recognize that top level item.
304 		 */
305 		if (buf[0] == ' ')	/* Continuation lines? Ignore */
306 			goto next_line;
307 		str = parse_line(buf, &start, &end);
308 		if (str == NULL)	/* Malformed -> ignore */
309 			goto next_line;
310 		/*
311 		 * All we care about is System RAM
312 		 */
313 		if (strncmp(str, SYSTEM_RAM_STR, sizeof(SYSTEM_RAM_STR) - 1) == 0)
314 			add_avail(start, end, system_ram);
315 		else if (strncmp(str, RESERVED, sizeof(RESERVED) - 1) == 0)
316 			add_avail(start, end, firmware_reserved);
317 		else
318 			goto next_line;	/* Ignore hardware */
319 		while (fgetstr(buf, sizeof(buf), fd) >= 0 && buf[0] == ' ') {
320 			chop(buf);
321 			str = parse_line(buf, &start, &end);
322 			if (str == NULL)
323 				break;
324 			kv = kvlookup(str, str2type_kv, nitems(str2type_kv));
325 			if (kv == NULL) /* failsafe for new types: igonre */
326 				remove_avail(start, end, unknown);
327 			else if ((kv->flags & KV_KEEPER) == 0)
328 				remove_avail(start, end, kv->type);
329 			/* Else no need to adjust since it's a keeper */
330 		}
331 
332 		/*
333 		 * if buf[0] == ' ' then we know that the fgetstr failed and we
334 		 * should break. Otherwise fgetstr succeeded and we have a
335 		 * buffer we need to examine for being a top level item.
336 		 */
337 		if (buf[0] == ' ')
338 			break;
339 		chop(buf);
340 		continue; /* buf has next top level line to parse */
341 next_line:
342 		if (fgetstr(buf, sizeof(buf), fd) < 0)
343 			break;
344 	}
345 
346 out:
347 	close(fd);
348 	return true;
349 }
350 
351 /*
352  * Return the amount of space available in the segment that @start@ lives in,
353  * from @start@ to the end of the segment.
354  */
355 uint64_t
356 space_avail(uint64_t start)
357 {
358 	for (int i = 0; i < nr_seg; i++) {
359 		if (start >= segs[i].start && start <= segs[i].end)
360 			return segs[i].end - start;
361 	}
362 
363 	/*
364 	 * Properly used, we should never get here. Unsure if this should be a
365 	 * panic or not.
366 	 */
367 	return 0;
368 }
369