blob: dbc874c5f7ccdafea86dedcaa9140d9ccd7cebac [file] [log] [blame]
Greg Hartman76d05dc2016-11-23 15:51:27 -08001/* ----------------------------------------------------------------------- *
2 *
3 * Copyright 2007-2009 H. Peter Anvin - All Rights Reserved
4 * Copyright 2009 Intel Corporation; author: H. Peter Anvin
5 *
6 * Permission is hereby granted, free of charge, to any person
7 * obtaining a copy of this software and associated documentation
8 * files (the "Software"), to deal in the Software without
9 * restriction, including without limitation the rights to use,
10 * copy, modify, merge, publish, distribute, sublicense, and/or
11 * sell copies of the Software, and to permit persons to whom
12 * the Software is furnished to do so, subject to the following
13 * conditions:
14 *
15 * The above copyright notice and this permission notice shall
16 * be included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 * OTHER DEALINGS IN THE SOFTWARE.
26 *
27 * ----------------------------------------------------------------------- */
28
29/*
30 * zonelist.c
31 *
32 * Deal with syslinux_memmap's, which are data structures designed to
33 * hold memory maps. A zonelist is a sorted linked list of memory
34 * ranges, with the guarantee that no two adjacent blocks have the
35 * same range type. Additionally, all unspecified memory have a range
36 * type of zero.
37 */
38
39#include <stdlib.h>
40#include <syslinux/align.h>
41#include <syslinux/movebits.h>
42#include <dprintf.h>
43
44/*
45 * Create an empty syslinux_memmap list.
46 */
47struct syslinux_memmap *syslinux_init_memmap(void)
48{
49 struct syslinux_memmap *sp, *ep;
50
51 sp = malloc(sizeof(*sp));
52 if (!sp)
53 return NULL;
54
55 ep = malloc(sizeof(*ep));
56 if (!ep) {
57 free(sp);
58 return NULL;
59 }
60
61 sp->start = 0;
62 sp->type = SMT_UNDEFINED;
63 sp->next = ep;
64
65 ep->start = 0; /* Wrap around... */
66 ep->type = SMT_END; /* End of chain */
67 ep->next = NULL;
68
69 return sp;
70}
71
72/*
73 * Add an item to a syslinux_memmap list, potentially overwriting
74 * what is already there.
75 */
76int syslinux_add_memmap(struct syslinux_memmap **list,
77 addr_t start, addr_t len,
78 enum syslinux_memmap_types type)
79{
80 addr_t last;
81 struct syslinux_memmap *mp, **mpp;
82 struct syslinux_memmap *range;
83 enum syslinux_memmap_types oldtype;
84
85 dprintf("Input memmap:\n");
86 syslinux_dump_memmap(*list);
87
88 /* Remove this to make len == 0 mean all of memory */
89 if (len == 0)
90 return 0;
91
92 /* Last byte -- to avoid rollover */
93 last = start + len - 1;
94
95 mpp = list;
96 oldtype = SMT_END; /* Impossible value */
97 while (mp = *mpp, start > mp->start && mp->type != SMT_END) {
98 oldtype = mp->type;
99 mpp = &mp->next;
100 }
101
102 if (start < mp->start || mp->type == SMT_END) {
103 if (type != oldtype) {
104 /* Splice in a new start token */
105 range = malloc(sizeof(*range));
106 if (!range)
107 return -1;
108
109 range->start = start;
110 range->type = type;
111 *mpp = range;
112 range->next = mp;
113 mpp = &range->next;
114 }
115 } else {
116 /* mp is exactly aligned with the start of our region */
117 if (type != oldtype) {
118 /* Reclaim this entry as our own boundary marker */
119 oldtype = mp->type;
120 mp->type = type;
121 mpp = &mp->next;
122 }
123 }
124
125 while (mp = *mpp, last > mp->start - 1) {
126 oldtype = mp->type;
127 *mpp = mp->next;
128 free(mp);
129 }
130
131 if (last < mp->start - 1) {
132 if (oldtype != type) {
133 /* Need a new end token */
134 range = malloc(sizeof(*range));
135 if (!range)
136 return -1;
137
138 range->start = last + 1;
139 range->type = oldtype;
140 *mpp = range;
141 range->next = mp;
142 }
143 } else {
144 if (mp->type == type) {
145 /* Merge this region with the following one */
146 *mpp = mp->next;
147 free(mp);
148 }
149 }
150
151 dprintf("After adding (%#x,%#x,%d):\n", start, len, type);
152 syslinux_dump_memmap(*list);
153
154 return 0;
155}
156
157/*
158 * Verify what type a certain memory region is. This function returns
159 * SMT_ERROR if the memory region has multiple types, except that
160 * SMT_FREE can be demoted to SMT_TERMINAL.
161 */
162enum syslinux_memmap_types syslinux_memmap_type(struct syslinux_memmap *list,
163 addr_t start, addr_t len)
164{
165 addr_t last, llast;
166
167 last = start + len - 1;
168
169 while (list->type != SMT_END) {
170 llast = list->next->start - 1;
171 if (list->start <= start) {
172 if (llast >= last) {
173 return list->type; /* Region has a well-defined type */
174 } else if (llast >= start) {
175 /* Crosses region boundary */
176 while (valid_terminal_type(list->type)) {
177 list = list->next;
178 llast = list->next->start - 1;
179 if (llast >= last)
180 return SMT_TERMINAL;
181 }
182 return SMT_ERROR;
183 }
184 }
185 list = list->next;
186 }
187
188 return SMT_ERROR; /* Internal error? */
189}
190
191/*
192 * Find the largest zone of a specific type. Returns -1 on failure.
193 */
194int syslinux_memmap_largest(struct syslinux_memmap *list,
195 enum syslinux_memmap_types type,
196 addr_t * start, addr_t * len)
197{
198 addr_t size, best_size = 0;
199 struct syslinux_memmap *best = NULL;
200
201 while (list->type != SMT_END) {
202 size = list->next->start - list->start;
203
204 if (list->type == type && size > best_size) {
205 best = list;
206 best_size = size;
207 }
208
209 list = list->next;
210 }
211
212 if (!best)
213 return -1;
214
215 *start = best->start;
216 *len = best_size;
217
218 return 0;
219}
220
221/*
222 * Find the highest zone of a specific type that satisfies the
223 * constraints.
224 *
225 * 'start' is updated with the highest address on success. 'start' can
226 * be used to set a minimum address to begin searching from.
227 *
228 * Returns -1 on failure.
229 */
230int syslinux_memmap_highest(const struct syslinux_memmap *list,
231 enum syslinux_memmap_types type,
232 addr_t *start, addr_t len,
233 addr_t ceiling, addr_t align)
234{
235 addr_t size, best;
236
237 for (best = 0; list->type != SMT_END; list = list->next) {
238 size = list->next->start - list->start;
239
240 if (list->type != type)
241 continue;
242
243 if (list->start + size <= *start)
244 continue;
245
246 if (list->start + len >= ceiling)
247 continue;
248
249 if (list->start + size < ceiling)
250 best = ALIGN_DOWN(list->start + size - len, align);
251 else
252 best = ALIGN_DOWN(ceiling - len, align);
253
254 if (best < *start)
255 best = 0;
256 }
257
258 if (!best)
259 return -1;
260
261 *start = best;
262
263 return 0;
264}
265
266/*
267 * Find the first (lowest address) zone of a specific type and of
268 * a certain minimum size, with an optional starting address.
269 * The input values of start and len are used as minima.
270 */
271int syslinux_memmap_find_type(struct syslinux_memmap *list,
272 enum syslinux_memmap_types type,
273 addr_t * start, addr_t * len, addr_t align)
274{
275 addr_t min_start = *start;
276 addr_t min_len = *len;
277
278 while (list->type != SMT_END) {
279 if (list->type == type) {
280 addr_t xstart, xlen;
281 xstart = min_start > list->start ? min_start : list->start;
282 xstart = ALIGN_UP(xstart, align);
283
284 if (xstart < list->next->start) {
285 xlen = list->next->start - xstart;
286 if (xlen >= min_len) {
287 *start = xstart;
288 *len = xlen;
289 return 0;
290 }
291 }
292 }
293 list = list->next;
294 }
295
296 return -1; /* Not found */
297}
298
299/*
300 * Free a zonelist.
301 */
302void syslinux_free_memmap(struct syslinux_memmap *list)
303{
304 struct syslinux_memmap *ml;
305
306 while (list) {
307 ml = list;
308 list = list->next;
309 free(ml);
310 }
311}
312
313/*
314 * Duplicate a zonelist. Returns NULL on failure.
315 */
316struct syslinux_memmap *syslinux_dup_memmap(struct syslinux_memmap *list)
317{
318 struct syslinux_memmap *newlist = NULL, **nlp = &newlist;
319 struct syslinux_memmap *ml;
320
321 while (list) {
322 ml = malloc(sizeof(*ml));
323 if (!ml) {
324 syslinux_free_memmap(newlist);
325 return NULL;
326 }
327 ml->start = list->start;
328 ml->type = list->type;
329 ml->next = NULL;
330 *nlp = ml;
331 nlp = &ml->next;
332
333 list = list->next;
334 }
335
336 return newlist;
337}
338
339/*
340 * Find a memory region, given a set of heuristics and update 'base' if
341 * successful.
342 */
343int syslinux_memmap_find(struct syslinux_memmap *mmap,
344 addr_t *base, size_t size,
345 bool relocate, size_t align,
346 addr_t start_min, addr_t start_max,
347 addr_t end_min, addr_t end_max)
348{
349 const struct syslinux_memmap *mp;
350 enum syslinux_memmap_types type;
351 bool ok;
352
353 if (!size)
354 return 0;
355
356 type = syslinux_memmap_type(mmap, *base, size);
357
358 /* This assumes SMT_TERMINAL is OK if we can get the exact address */
359 if (valid_terminal_type(type))
360 return 0;
361
362 if (!relocate) {
363 dprintf("Cannot relocate\n");
364 return -1;
365 }
366
367 ok = false;
368 for (mp = mmap; mp && mp->type != SMT_END; mp = mp->next) {
369 addr_t start, end;
370 start = mp->start;
371 end = mp->next->start;
372
373 if (mp->type != SMT_FREE)
374 continue;
375
376 /* min */
377 if (end <= end_min)
378 continue; /* Only relocate upwards */
379
380 if (start < start_min)
381 start = start_min;
382
383 /* max */
384 if (end > end_max)
385 end = end_max;
386
387 start = ALIGN_UP(start, align);
388 if (start > start_max || start >= end)
389 continue;
390
391 if (end - start >= size) {
392 *base = start;
393 ok = true;
394 break;
395 }
396 }
397
398 if (!ok)
399 return -1;
400
401 return 0;
402}