• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* ----------------------------------------------------------------------- *
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  */
syslinux_init_memmap(void)47 struct 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  */
syslinux_add_memmap(struct syslinux_memmap ** list,addr_t start,addr_t len,enum syslinux_memmap_types type)76 int 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  */
syslinux_memmap_type(struct syslinux_memmap * list,addr_t start,addr_t len)162 enum 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  */
syslinux_memmap_largest(struct syslinux_memmap * list,enum syslinux_memmap_types type,addr_t * start,addr_t * len)194 int 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  */
syslinux_memmap_highest(const struct syslinux_memmap * list,enum syslinux_memmap_types type,addr_t * start,addr_t len,addr_t ceiling,addr_t align)230 int 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  */
syslinux_memmap_find_type(struct syslinux_memmap * list,enum syslinux_memmap_types type,addr_t * start,addr_t * len,addr_t align)271 int 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  */
syslinux_free_memmap(struct syslinux_memmap * list)302 void 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  */
syslinux_dup_memmap(struct syslinux_memmap * list)316 struct 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  */
syslinux_memmap_find(struct syslinux_memmap * mmap,addr_t * base,size_t size,bool relocate,size_t align,addr_t start_min,addr_t start_max,addr_t end_min,addr_t end_max)343 int 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 }
403