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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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