6c75105a3aae903a7ff3197f242bf04a8f3d01a4
[profile/ivi/syslinux.git] / com32 / lib / syslinux / movebits.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2007-2008 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  * movebits.c
31  *
32  * Utility function to take a list of memory areas to shuffle and
33  * convert it to a set of shuffle operations.
34  *
35  * Note: a lot of the functions in this file deal with "parent pointers",
36  * which are pointers to a pointer to a list, or part of the list.
37  * They can be pointers to a variable holding the list root pointer,
38  * or pointers to a next field of a previous entry.
39  */
40
41 #include <assert.h>
42 #include <stdio.h>
43 #include <errno.h>
44 #include <stdlib.h>
45 #include <inttypes.h>
46 #include <setjmp.h>
47 #include <minmax.h>
48 #include <stdbool.h>
49
50 #include <syslinux/movebits.h>
51 #include <dprintf.h>
52
53 static jmp_buf new_movelist_bail;
54
55 static struct syslinux_movelist *new_movelist(addr_t dst, addr_t src,
56                                               addr_t len)
57 {
58     struct syslinux_movelist *ml = malloc(sizeof(struct syslinux_movelist));
59
60     if (!ml)
61         longjmp(new_movelist_bail, 1);
62
63     ml->dst = dst;
64     ml->src = src;
65     ml->len = len;
66     ml->next = NULL;
67
68     return ml;
69 }
70
71 static struct syslinux_movelist *dup_movelist(struct syslinux_movelist *src)
72 {
73     struct syslinux_movelist *dst = NULL, **dstp = &dst, *ml;
74
75     while (src) {
76         ml = new_movelist(src->dst, src->src, src->len);
77         *dstp = ml;
78         dstp = &ml->next;
79         src = src->next;
80     }
81
82     return dst;
83 }
84
85 static void
86 add_freelist(struct syslinux_memmap **mmap, addr_t start,
87              addr_t len, enum syslinux_memmap_types type)
88 {
89     if (syslinux_add_memmap(mmap, start, len, type))
90         longjmp(new_movelist_bail, 1);
91 }
92
93 /*
94  * Take a chunk, entirely confined in **parentptr, and split it off so that
95  * it has its own structure.
96  */
97 static struct syslinux_movelist **split_movelist(addr_t start, addr_t len,
98                                                  struct syslinux_movelist
99                                                  **parentptr)
100 {
101     struct syslinux_movelist *m, *ml = *parentptr;
102
103     assert(start >= ml->src);
104     assert(start < ml->src + ml->len);
105
106     /* Split off the beginning */
107     if (start > ml->src) {
108         addr_t l = start - ml->src;
109
110         m = new_movelist(ml->dst + l, start, ml->len - l);
111         m->next = ml->next;
112         ml->len = l;
113         ml->next = m;
114
115         parentptr = &ml->next;
116         ml = m;                 /* Continue processing the new node */
117     }
118
119     /* Split off the end */
120     if (ml->len > len) {
121         addr_t l = ml->len - len;
122
123         m = new_movelist(ml->dst + len, ml->src + len, l);
124         m->next = ml->next;
125         ml->len = len;
126         ml->next = m;
127     }
128
129     return parentptr;
130 }
131
132 static void delete_movelist(struct syslinux_movelist **parentptr)
133 {
134     struct syslinux_movelist *o = *parentptr;
135     *parentptr = o->next;
136     free(o);
137 }
138
139 static void free_movelist(struct syslinux_movelist **parentptr)
140 {
141     while (*parentptr)
142         delete_movelist(parentptr);
143 }
144
145 /*
146  * Scan the freelist looking for a particular chunk of memory
147  */
148 static const struct syslinux_memmap *is_free_zone(const struct syslinux_memmap
149                                                   *list, addr_t start,
150                                                   addr_t len)
151 {
152     dprintf("f: 0x%08x bytes at 0x%08x\n", len, start);
153
154     addr_t last, llast;
155
156     last = start + len - 1;
157
158     while (list->type != SMT_END) {
159         llast = list->next->start - 1;
160         if (list->start <= start) {
161             if (llast >= last) {
162                 /* Chunk has a single, well-defined type */
163                 if (list->type == SMT_FREE) {
164                     dprintf("F: 0x%08x bytes at 0x%08x\n",
165                             list->next->start, list->start);
166                     return list;        /* It's free */
167                 }
168                 return NULL;    /* Not free */
169             } else if (llast >= start) {
170                 return NULL;    /* Crosses region boundary */
171             }
172         }
173         list = list->next;
174     }
175
176     return NULL;                /* Internal error? */
177 }
178
179 /*
180  * Scan the freelist looking for the smallest chunk of memory which
181  * can fit X bytes; returns the length of the block on success.
182  */
183 static addr_t free_area(const struct syslinux_memmap *mmap,
184                         addr_t len, addr_t * start)
185 {
186     const struct syslinux_memmap *best = NULL;
187     const struct syslinux_memmap *s;
188     addr_t slen, best_len = -1;
189
190     for (s = mmap; s->type != SMT_END; s = s->next) {
191         if (s->type != SMT_FREE)
192             continue;
193         slen = s->next->start - s->start;
194         if (slen >= len) {
195             if (!best || best_len > slen) {
196                 best = s;
197                 best_len = slen;
198             }
199         }
200     }
201
202     if (best) {
203         *start = best->start;
204         return best_len;
205     } else {
206         return 0;
207     }
208 }
209
210 /*
211  * Remove a chunk from the freelist
212  */
213 static void
214 allocate_from(struct syslinux_memmap **mmap, addr_t start, addr_t len)
215 {
216     syslinux_add_memmap(mmap, start, len, SMT_ALLOC);
217 }
218
219 /*
220  * Find chunks of a movelist which are one-to-many (one source, multiple
221  * destinations.)  Those chunks can get turned into post-shuffle copies,
222  * to avoid confusing the shuffler.
223  */
224 static void shuffle_dealias(struct syslinux_movelist **fraglist,
225                             struct syslinux_movelist **postcopy)
226 {
227     struct syslinux_movelist *mp, **mpp, *mx, *np;
228     addr_t ps, pe, xs, xe, delta;
229     bool advance;
230
231 #if DEBUG
232     dprintf("Before alias resolution:\n");
233     syslinux_dump_movelist(stdout, *fraglist);
234 #endif
235
236     *postcopy = NULL;
237
238     /*
239      * Note: as written, this is an O(n^2) algorithm; by producing a list
240      * sorted by destination address we could reduce it to O(n log n).
241      */
242     mpp = fraglist;
243     while ((mp = *mpp)) {
244         dprintf("mp -> (%#x,%#x,%#x)\n", mp->dst, mp->src, mp->len);
245         ps = mp->src;
246         pe = mp->src + mp->len - 1;
247         for (mx = *fraglist; mx != mp; mx = mx->next) {
248             dprintf("mx -> (%#x,%#x,%#x)\n", mx->dst, mx->src, mx->len);
249             /*
250              * If there is any overlap between mx and mp, mp should be
251              * modified and possibly split.
252              */
253             xs = mx->src;
254             xe = mx->src + mx->len - 1;
255
256             dprintf("?: %#x..%#x (inside %#x..%#x)\n", ps, pe, xs, xe);
257
258             if (pe <= xs || ps >= xe)
259                 continue;       /* No overlap */
260
261             advance = false;
262             *mpp = mp->next;    /* Remove from list */
263
264             if (pe > xe) {
265                 delta = pe - xe;
266                 np = new_movelist(mp->dst + mp->len - delta,
267                                   mp->src + mp->len - delta, delta);
268                 mp->len -= delta;
269                 pe = xe;
270                 np->next = *mpp;
271                 *mpp = np;
272                 advance = true;
273             }
274             if (ps < xs) {
275                 delta = xs - ps;
276                 np = new_movelist(mp->dst, ps, delta);
277                 mp->src += delta;
278                 ps = mp->src;
279                 mp->dst += delta;
280                 mp->len -= delta;
281                 np->next = *mpp;
282                 *mpp = np;
283                 advance = true;
284             }
285
286             assert(ps >= xs && pe <= xe);
287
288             dprintf("Overlap: %#x..%#x (inside %#x..%#x)\n", ps, pe, xs, xe);
289
290             mp->src = mx->dst + (ps - xs);
291             mp->next = *postcopy;
292             *postcopy = mp;
293
294             mp = *mpp;
295
296             if (!advance)
297                 goto restart;
298         }
299
300         mpp = &mp->next;
301 restart:
302         ;
303     }
304
305 #if DEBUG
306     dprintf("After alias resolution:\n");
307     syslinux_dump_movelist(stdout, *fraglist);
308     dprintf("Post-shuffle copies:\n");
309     syslinux_dump_movelist(stdout, *postcopy);
310 #endif
311 }
312
313 /*
314  * The code to actually emit moving of a chunk into its final place.
315  */
316 static void
317 move_chunk(struct syslinux_movelist ***moves,
318            struct syslinux_memmap **mmap,
319            struct syslinux_movelist **fp, addr_t copylen)
320 {
321     addr_t copydst, copysrc;
322     addr_t freebase, freelen;
323     addr_t needlen;
324     int reverse;
325     struct syslinux_movelist *f = *fp, *mv;
326
327     if (f->src < f->dst && (f->dst - f->src) < f->len) {
328         /* "Shift up" type overlap */
329         needlen = f->dst - f->src;
330         reverse = 1;
331     } else if (f->src > f->dst && (f->src - f->dst) < f->len) {
332         /* "Shift down" type overlap */
333         needlen = f->src - f->dst;
334         reverse = 0;
335     } else {
336         needlen = f->len;
337         reverse = 0;
338     }
339
340     copydst = f->dst;
341     copysrc = f->src;
342
343     dprintf("Q: copylen = 0x%08x, needlen = 0x%08x\n", copylen, needlen);
344
345     if (copylen < needlen) {
346         if (reverse) {
347             copydst += (f->len - copylen);
348             copysrc += (f->len - copylen);
349         }
350
351         dprintf("X: 0x%08x bytes at 0x%08x -> 0x%08x\n",
352                 copylen, copysrc, copydst);
353
354         /* Didn't get all we wanted, so we have to split the chunk */
355         fp = split_movelist(copysrc, copylen, fp);      /* Is this right? */
356         f = *fp;
357     }
358
359     mv = new_movelist(f->dst, f->src, f->len);
360     dprintf("A: 0x%08x bytes at 0x%08x -> 0x%08x\n", mv->len, mv->src, mv->dst);
361     **moves = mv;
362     *moves = &mv->next;
363
364     /* Figure out what memory we just freed up */
365     if (f->dst > f->src) {
366         freebase = f->src;
367         freelen = min(f->len, f->dst - f->src);
368     } else if (f->src >= f->dst + f->len) {
369         freebase = f->src;
370         freelen = f->len;
371     } else {
372         freelen = f->src - f->dst;
373         freebase = f->dst + f->len;
374     }
375
376     dprintf("F: 0x%08x bytes at 0x%08x\n", freelen, freebase);
377
378     add_freelist(mmap, freebase, freelen, SMT_FREE);
379
380     delete_movelist(fp);
381 }
382
383 /*
384  * moves is computed from "frags" and "freemem".  "space" lists
385  * free memory areas at our disposal, and is (src, cnt) only.
386  */
387 int
388 syslinux_compute_movelist(struct syslinux_movelist **moves,
389                           struct syslinux_movelist *ifrags,
390                           struct syslinux_memmap *memmap)
391 {
392     struct syslinux_memmap *mmap = NULL;
393     const struct syslinux_memmap *mm, *ep;
394     struct syslinux_movelist *frags = NULL;
395     struct syslinux_movelist *postcopy = NULL;
396     struct syslinux_movelist *mv;
397     struct syslinux_movelist *f, **fp;
398     struct syslinux_movelist *o, **op;
399     addr_t needbase, needlen, copysrc, copydst, copylen;
400     addr_t avail;
401     addr_t fstart, flen;
402     addr_t cbyte;
403     addr_t ep_len;
404     int rv = -1;
405     int reverse;
406
407     dprintf("entering syslinux_compute_movelist()...\n");
408
409     if (setjmp(new_movelist_bail)) {
410 nomem:
411         dprintf("Out of working memory!\n");
412         goto bail;
413     }
414
415     *moves = NULL;
416
417     /* Create our memory map.  Anything that is SMT_FREE or SMT_ZERO is
418        fair game, but mark anything used by source material as SMT_ALLOC. */
419     mmap = syslinux_init_memmap();
420     if (!mmap)
421         goto nomem;
422
423     frags = dup_movelist(ifrags);
424
425     /* Process one-to-many conditions */
426     shuffle_dealias(&frags, &postcopy);
427
428     for (mm = memmap; mm->type != SMT_END; mm = mm->next)
429         add_freelist(&mmap, mm->start, mm->next->start - mm->start,
430                      mm->type == SMT_ZERO ? SMT_FREE : mm->type);
431
432     for (f = frags; f; f = f->next)
433         add_freelist(&mmap, f->src, f->len, SMT_ALLOC);
434
435     /* As long as there are unprocessed fragments in the chain... */
436     while ((fp = &frags, f = *fp)) {
437
438 #if DEBUG
439         dprintf("Current free list:\n");
440         syslinux_dump_memmap(stdout, mmap);
441         dprintf("Current frag list:\n");
442         syslinux_dump_movelist(stdout, frags);
443 #endif
444
445         /* Scan for fragments which can be discarded without action. */
446         if (f->src == f->dst) {
447             delete_movelist(fp);
448             continue;
449         }
450         op = &f->next;
451         while ((o = *op)) {
452             if (o->src == o->dst)
453                 delete_movelist(op);
454             else
455                 op = &o->next;
456         }
457
458         /* Scan for fragments which can be immediately moved
459            to their final destination, if so handle them now */
460         for (op = fp; (o = *op); op = &o->next) {
461             if (o->src < o->dst && (o->dst - o->src) < o->len) {
462                 /* "Shift up" type overlap */
463                 needlen = o->dst - o->src;
464                 needbase = o->dst + (o->len - needlen);
465                 reverse = 1;
466                 cbyte = o->dst + o->len - 1;
467             } else if (o->src > o->dst && (o->src - o->dst) < o->len) {
468                 /* "Shift down" type overlap */
469                 needlen = o->src - o->dst;
470                 needbase = o->dst;
471                 reverse = 0;
472                 cbyte = o->dst; /* "Critical byte" */
473             } else {
474                 needlen = o->len;
475                 needbase = o->dst;
476                 reverse = 0;
477                 cbyte = o->dst; /* "Critical byte" */
478             }
479
480             if (is_free_zone(mmap, needbase, needlen)) {
481                 fp = op, f = o;
482                 dprintf("!: 0x%08x bytes at 0x%08x -> 0x%08x\n",
483                         f->len, f->src, f->dst);
484                 copysrc = f->src;
485                 copylen = needlen;
486                 allocate_from(&mmap, needbase, copylen);
487                 goto move_chunk;
488             }
489         }
490
491         /* Ok, bother.  Need to do real work at least with one chunk. */
492
493         dprintf("@: 0x%08x bytes at 0x%08x -> 0x%08x\n",
494                 f->len, f->src, f->dst);
495
496         /* See if we can move this chunk into place by claiming
497            the destination, or in the case of partial overlap, the
498            missing portion. */
499
500         if (f->src < f->dst && (f->dst - f->src) < f->len) {
501             /* "Shift up" type overlap */
502             needlen = f->dst - f->src;
503             needbase = f->dst + (f->len - needlen);
504             reverse = 1;
505             cbyte = f->dst + f->len - 1;
506         } else if (f->src > f->dst && (f->src - f->dst) < f->len) {
507             /* "Shift down" type overlap */
508             needlen = f->src - f->dst;
509             needbase = f->dst;
510             reverse = 0;
511             cbyte = f->dst;     /* "Critical byte" */
512         } else {
513             needlen = f->len;
514             needbase = f->dst;
515             reverse = 0;
516             cbyte = f->dst;
517         }
518
519         dprintf("need: base = 0x%08x, len = 0x%08x, "
520                 "reverse = %d, cbyte = 0x%08x\n",
521                 needbase, needlen, reverse, cbyte);
522
523         ep = is_free_zone(mmap, cbyte, 1);
524         if (ep) {
525             ep_len = ep->next->start - ep->start;
526             if (reverse)
527                 avail = needbase + needlen - ep->start;
528             else
529                 avail = ep_len - (needbase - ep->start);
530         } else {
531             avail = 0;
532         }
533
534         if (avail) {
535             /* We can move at least part of this chunk into place without
536                further ado */
537             dprintf("space: start 0x%08x, len 0x%08x, free 0x%08x\n",
538                     ep->start, ep_len, avail);
539             copylen = min(needlen, avail);
540
541             if (reverse)
542                 allocate_from(&mmap, needbase + needlen - copylen, copylen);
543             else
544                 allocate_from(&mmap, needbase, copylen);
545
546             goto move_chunk;
547         }
548
549         /* At this point, we need to evict something out of our space.
550            Find the object occupying the critical byte of our target space,
551            and move it out (the whole object if we can, otherwise a subset.)
552            Then move a chunk of ourselves into place. */
553         for (op = &f->next, o = *op; o; op = &o->next, o = *op) {
554
555             dprintf("O: 0x%08x bytes at 0x%08x -> 0x%08x\n",
556                     o->len, o->src, o->dst);
557
558             if (!(o->src <= cbyte && o->src + o->len > cbyte))
559                 continue;       /* Not what we're looking for... */
560
561             /* Find somewhere to put it... */
562
563             if (is_free_zone(mmap, o->dst, o->len)) {
564                 /* Score!  We can move it into place directly... */
565                 copydst = o->dst;
566                 copysrc = o->src;
567                 copylen = o->len;
568             } else if (free_area(mmap, o->len, &fstart)) {
569                 /* We can move the whole chunk */
570                 copydst = fstart;
571                 copysrc = o->src;
572                 copylen = o->len;
573             } else {
574                 /* Well, copy as much as we can... */
575                 if (syslinux_memmap_largest(mmap, SMT_FREE, &fstart, &flen)) {
576                     dprintf("No free memory at all!\n");
577                     goto bail;  /* Stuck! */
578                 }
579
580                 /* Make sure we include the critical byte */
581                 copydst = fstart;
582                 if (reverse) {
583                     copysrc = max(o->src, cbyte + 1 - flen);
584                     copylen = cbyte + 1 - copysrc;
585                 } else {
586                     copysrc = cbyte;
587                     copylen = min(flen, o->len - (cbyte - o->src));
588                 }
589             }
590             allocate_from(&mmap, copydst, copylen);
591
592             if (copylen < o->len) {
593                 op = split_movelist(copysrc, copylen, op);
594                 o = *op;
595             }
596
597             mv = new_movelist(copydst, copysrc, copylen);
598             dprintf("C: 0x%08x bytes at 0x%08x -> 0x%08x\n",
599                     mv->len, mv->src, mv->dst);
600             *moves = mv;
601             moves = &mv->next;
602
603             o->src = copydst;
604
605             if (copylen > needlen) {
606                 /* We don't need all the memory we freed up.  Mark it free. */
607                 if (copysrc < needbase) {
608                     add_freelist(&mmap, copysrc, needbase - copysrc, SMT_FREE);
609                     copylen -= (needbase - copysrc);
610                 }
611                 if (copylen > needlen) {
612                     add_freelist(&mmap, copysrc + needlen, copylen - needlen,
613                                  SMT_FREE);
614                     copylen = needlen;
615                 }
616             }
617             reverse = 0;
618             goto move_chunk;
619         }
620         dprintf("Cannot find the chunk containing the critical byte\n");
621         goto bail;              /* Stuck! */
622
623 move_chunk:
624         move_chunk(&moves, &mmap, fp, copylen);
625     }
626
627     /* Finally, append the postcopy chain to the end of the moves list */
628     for (op = moves; (o = *op); op = &o->next) ;        /* Locate the end of the list */
629     *op = postcopy;
630     postcopy = NULL;
631
632     rv = 0;
633 bail:
634     if (mmap)
635         syslinux_free_memmap(mmap);
636     if (frags)
637         free_movelist(&frags);
638     if (postcopy)
639         free_movelist(&postcopy);
640     return rv;
641 }
642
643 #ifdef TEST
644
645 #include <stdio.h>
646
647 int main(int argc, char *argv[])
648 {
649     FILE *f;
650     unsigned long d, s, l;
651     struct syslinux_movelist *frags;
652     struct syslinux_movelist **fep = &frags;
653     struct syslinux_movelist *mv, *moves;
654     struct syslinux_memmap *memmap;
655     char line[BUFSIZ];
656
657     (void)argc;
658
659     memmap = syslinux_init_memmap();
660
661     f = fopen(argv[1], "r");
662     while (fgets(line, sizeof line, f) != NULL) {
663         if (sscanf(line, "%lx %lx %lx", &s, &d, &l) == 3) {
664             if (d) {
665                 if (s == -1UL) {
666                     syslinux_add_memmap(&memmap, d, l, SMT_ZERO);
667                 } else {
668                     mv = new_movelist(d, s, l);
669                     *fep = mv;
670                     fep = &mv->next;
671                 }
672             } else {
673                 syslinux_add_memmap(&memmap, s, l, SMT_FREE);
674             }
675         }
676     }
677     fclose(f);
678
679     *fep = NULL;
680
681     printf("Input move list:\n");
682     syslinux_dump_movelist(stdout, frags);
683     printf("Input free list:\n");
684     syslinux_dump_memmap(stdout, memmap);
685
686     if (syslinux_compute_movelist(&moves, frags, memmap)) {
687         printf("Failed to compute a move sequence\n");
688         return 1;
689     } else {
690         printf("Final move list:\n");
691         syslinux_dump_movelist(stdout, moves);
692         return 0;
693     }
694 }
695
696 #endif /* TEST */