First version of allocation from firmware

This commit is contained in:
Vladimir 'phcoder' Serbinenko 2010-04-03 11:53:29 +02:00
parent accbdc88a5
commit 3a5768645c
5 changed files with 440 additions and 147 deletions

View File

@ -16,9 +16,16 @@ vga_text_mod_CFLAGS = $(COMMON_CFLAGS)
vga_text_mod_LDFLAGS = $(COMMON_LDFLAGS)
pkglib_MODULES += relocator.mod
ifeq ($(platform), ieee1275)
relocator_mod_SOURCES = lib/relocator.c lib/i386/relocator32.S \
lib/i386/relocator64.S lib/i386/relocator16.S \
lib/$(target_cpu)/relocator_asm.S lib/i386/relocator.c \
lib/ieee1275/relocator.c
else
relocator_mod_SOURCES = lib/relocator.c lib/i386/relocator32.S \
lib/i386/relocator64.S lib/i386/relocator16.S \
lib/$(target_cpu)/relocator_asm.S lib/i386/relocator.c
endif
relocator_mod_CFLAGS = $(COMMON_CFLAGS)
relocator_mod_ASFLAGS = $(COMMON_ASFLAGS)
relocator_mod_LDFLAGS = $(COMMON_LDFLAGS)

View File

@ -21,6 +21,7 @@
#include <grub/types.h>
#include <grub/err.h>
#include <grub/mm_private.h>
extern grub_size_t grub_relocator_align;
extern grub_size_t grub_relocator_forward_size;
@ -38,4 +39,49 @@ void grub_cpu_relocator_backward (void *rels, void *src, void *tgt,
grub_size_t size);
void grub_cpu_relocator_jumper (void *rels, grub_addr_t addr);
#ifdef GRUB_MACHINE_IEEE1275
#define GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS 1
#else
#define GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS 0
#endif
struct grub_relocator_mmap_event
{
enum {
IN_REG_START = 0,
IN_REG_END = 1,
REG_BEG_START = 2,
REG_BEG_END = REG_BEG_START | 1,
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
REG_FIRMWARE_START = 4,
REG_FIRMWARE_END = REG_FIRMWARE_START | 1,
/* To track the regions already in heap. */
FIRMWARE_BLOCK_START = 6,
FIRMWARE_BLOCK_END = FIRMWARE_BLOCK_START | 1,
#endif
COLLISION_START = 8,
COLLISION_END = COLLISION_START | 1
} type;
grub_addr_t pos;
union
{
struct
{
grub_mm_region_t reg;
grub_mm_header_t hancestor;
grub_mm_region_t *regancestor;
grub_mm_header_t head;
};
};
};
/* Return 0 on failure, 1 on success. The failure here
can be very time-expensive, so please make sure fill events is accurate. */
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
int grub_relocator_firmware_alloc_region (grub_addr_t start, grub_size_t size);
unsigned grub_relocator_firmware_fill_events (struct grub_relocator_mmap_event *events);
unsigned grub_relocator_firmware_get_max_events (void);
void grub_relocator_firmware_free_region (grub_addr_t start, grub_size_t size);
#endif
#endif

View File

@ -253,6 +253,7 @@ grub_relocator64_boot (struct grub_relocator *rel,
return err;
asm volatile ("cli");
grub_printf ("%x\n", relst);
((void (*) (void)) relst) ();
/* Not reached. */

83
lib/ieee1275/relocator.c Normal file
View File

@ -0,0 +1,83 @@
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/relocator.h>
#include <grub/relocator_private.h>
#include <grub/memory.h>
#include <grub/ieee1275/ieee1275.h>
unsigned
grub_relocator_firmware_get_max_events (void)
{
int counter = 0;
auto int NESTED_FUNC_ATTR count (grub_uint64_t addr __attribute__ ((unused)),
grub_uint64_t len __attribute__ ((unused)),
grub_uint32_t type __attribute__ ((unused)));
int NESTED_FUNC_ATTR count (grub_uint64_t addr __attribute__ ((unused)),
grub_uint64_t len __attribute__ ((unused)),
grub_uint32_t type __attribute__ ((unused)))
{
counter++;
return 0;
}
if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CANNOT_INTERPRET))
return 0;
grub_machine_mmap_iterate (count);
return 2 * counter;
}
unsigned
grub_relocator_firmware_fill_events (struct grub_relocator_mmap_event *events)
{
int counter = 0;
auto int NESTED_FUNC_ATTR fill (grub_uint64_t addr, grub_uint64_t len,
grub_uint32_t type);
int NESTED_FUNC_ATTR fill (grub_uint64_t addr, grub_uint64_t len,
grub_uint32_t type)
{
if (type != GRUB_MACHINE_MEMORY_AVAILABLE)
return 0;
events[counter].type = REG_FIRMWARE_START;
events[counter].pos = addr;
counter++;
events[counter].type = REG_FIRMWARE_END;
events[counter].pos = addr + len;
counter++;
return 0;
}
if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CANNOT_INTERPRET))
return 0;
grub_machine_mmap_iterate (fill);
return counter;
}
int
grub_relocator_firmware_alloc_region (grub_addr_t start, grub_size_t size)
{
return (grub_claimmap (start, size) >= 0);
}
void
grub_relocator_firmware_free_region (grub_addr_t start, grub_size_t size)
{
grub_ieee1275_release (start, size);
}

View File

@ -1,6 +1,6 @@
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2009 Free Software Foundation, Inc.
* Copyright (C) 2009, 2010 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -23,7 +23,6 @@
#include <grub/cache.h>
/* FIXME: check memory map. */
/* FIXME: try to request memory from firmware. */
struct grub_relocator
{
@ -36,7 +35,11 @@ struct grub_relocator
struct grub_relocator_subchunk
{
enum {CHUNK_TYPE_IN_REGION, CHUNK_TYPE_REGION_START} type;
enum {CHUNK_TYPE_IN_REGION, CHUNK_TYPE_REGION_START,
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
CHUNK_TYPE_FIRMWARE
#endif
} type;
grub_addr_t host_start;
grub_addr_t start;
grub_size_t size;
@ -52,6 +55,15 @@ struct grub_relocator_chunk
unsigned nsubchunks;
};
struct grub_relocator_extra_block
{
struct grub_relocator_extra_block *next;
grub_addr_t start;
grub_addr_t end;
};
struct grub_relocator_extra_block *extra_blocks;
struct grub_relocator *
grub_relocator_new (void)
{
@ -70,29 +82,6 @@ grub_relocator_new (void)
return ret;
}
struct event
{
enum {
IN_REG_START = 0,
IN_REG_END = 1,
REG_BEG_START = 2,
REG_BEG_END = REG_BEG_START | 1,
COLLISION_START = 4,
COLLISION_END = COLLISION_START | 1
} type;
grub_addr_t pos;
union
{
struct
{
grub_mm_region_t reg;
grub_mm_header_t hancestor;
grub_mm_region_t *regancestor;
grub_mm_header_t head;
};
};
};
#define DIGITSORT_BITS 8
#define DIGITSORT_MASK ((1 << DIGITSORT_BITS) - 1)
#define BITS_IN_BYTE 8
@ -221,7 +210,7 @@ malloc_in_range (struct grub_relocator *rel,
int from_low_priv, int collisioncheck)
{
grub_mm_region_t r, *ra, base_saved;
struct event *events = NULL, *eventt = NULL, *t;
struct grub_relocator_mmap_event *events = NULL, *eventt = NULL, *t;
unsigned maxevents = 2;
grub_mm_header_t p, pa;
unsigned *counter;
@ -254,7 +243,7 @@ malloc_in_range (struct grub_relocator *rel,
p = p->next;
}
while (p != r->first);
maxevents += 2;
maxevents += 4;
}
if (collisioncheck && rel)
{
@ -263,6 +252,16 @@ malloc_in_range (struct grub_relocator *rel,
maxevents += 2;
}
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
{
struct grub_relocator_extra_block *cur;
for (cur = extra_blocks; cur; cur = cur->next)
maxevents += 2;
}
#endif
maxevents += grub_relocator_firmware_get_max_events ();
events = grub_malloc (maxevents * sizeof (events[0]));
eventt = grub_malloc (maxevents * sizeof (events[0]));
counter = grub_malloc ((DIGITSORT_MASK + 2) * sizeof (counter[0]));
@ -290,6 +289,35 @@ malloc_in_range (struct grub_relocator *rel,
}
}
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
for (r = grub_mm_base; r; r = r->next)
{
grub_dprintf ("relocator", "Blocking at 0x%x-0x%x\n",
(grub_addr_t) r - r->pre_size,
(grub_addr_t) (r + 1) + r->size);
events[N].type = FIRMWARE_BLOCK_START;
events[N].pos = (grub_addr_t) r - r->pre_size;
N++;
events[N].type = FIRMWARE_BLOCK_END;
events[N].pos = (grub_addr_t) (r + 1) + r->size;
N++;
}
{
struct grub_relocator_extra_block *cur;
for (cur = extra_blocks; cur; cur = cur->next)
{
grub_dprintf ("relocator", "Blocking at 0x%x-0x%x\n",
cur->start, cur->end);
events[N].type = FIRMWARE_BLOCK_START;
events[N].pos = cur->start;
N++;
events[N].type = FIRMWARE_BLOCK_END;
events[N].pos = cur->end;
N++;
}
}
#endif
/* No malloc from this point. */
base_saved = grub_mm_base;
grub_mm_base = NULL;
@ -344,6 +372,8 @@ malloc_in_range (struct grub_relocator *rel,
}
}
N += grub_relocator_firmware_fill_events (events + N);
/* Put ending events after starting events. */
{
int st = 0, e = N / 2;
@ -373,104 +403,86 @@ malloc_in_range (struct grub_relocator *rel,
events = t;
}
grub_dprintf ("relocator", "scanline events:\n");
for (j = 0; j < N; j++)
grub_dprintf ("relocator", "event %x, type %d\n", events[j].pos,
events[j].type);
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
retry:
#endif
/* Now events are nicely sorted. */
if (from_low_priv)
{
int nstarted = 0, ncollisions = 0;
grub_addr_t starta = 0;
int numstarted;
for (j = 0; j < N; j++)
{
switch (events[j].type)
{
case COLLISION_END:
ncollisions--;
case IN_REG_START:
case REG_BEG_START:
if ((events[j].type == COLLISION_END ? nstarted != 0
: nstarted == 0)
&& ncollisions == 0)
{
starta = ALIGN_UP (events[j].pos, align);
numstarted = j;
}
if (events[j].type != COLLISION_END)
nstarted++;
break;
{
int nstarted = 0, ncollisions = 0, nstartedfw = 0, nblockfw = 0;
grub_addr_t starta = 0;
int numstarted;
for (j = from_low_priv ? 0 : N - 1; from_low_priv ? j < N : (j + 1);
from_low_priv ? j++ : j--)
{
int isinsidebefore, isinsideafter;
isinsidebefore = (!ncollisions
&& (nstarted || (nstartedfw && !nblockfw)));
switch (events[j].type)
{
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
case REG_FIRMWARE_START:
nstartedfw++;
break;
case IN_REG_END:
case REG_BEG_END:
nstarted--;
case COLLISION_START:
if (((events[j].type == COLLISION_START)
? nstarted != 0 : nstarted == 0)
&& ncollisions == 0)
{
target = starta;
if (target < start)
target = start;
grub_dprintf ("relocator", "%x, %x, %x\n", target, start,
events[j].pos);
if (target + size <= end && target + size <= events[j].pos)
/* Found an usable address. */
goto found;
}
if (events[j].type == COLLISION_START)
ncollisions++;
break;
}
}
}
else
{
int nstarted = 0, ncollisions = 0;
grub_addr_t enda = 0;
int numend;
for (j = N - 1; j != (unsigned) -1; j--)
{
switch (events[j].type)
{
case COLLISION_START:
ncollisions--;
case IN_REG_END:
case REG_BEG_END:
if ((events[j].type == COLLISION_END ? nstarted != 0
: nstarted == 0)
&& ncollisions == 0)
{
enda = ALIGN_DOWN (events[j].pos - size, align) + size;
numend = j;
}
nstarted++;
break;
case REG_FIRMWARE_END:
nstartedfw--;
break;
case IN_REG_START:
case REG_BEG_START:
nstarted--;
case COLLISION_END:
if ((events[j].type == COLLISION_START ? nstarted != 0
: nstarted == 0)
&& ncollisions == 0)
{
target = enda - size;
if (target > end - size)
target = end - size;
grub_dprintf ("relocator", "%x, %x, %x\n", target, start,
events[j].pos);
if (target >= start && target >= events[j].pos)
goto found;
}
if (events[j].type == COLLISION_START)
ncollisions++;
break;
}
}
}
case FIRMWARE_BLOCK_START:
nblockfw++;
break;
case FIRMWARE_BLOCK_END:
nblockfw--;
break;
#endif
case COLLISION_START:
ncollisions++;
break;
case COLLISION_END:
ncollisions--;
break;
case IN_REG_START:
case REG_BEG_START:
nstarted++;
break;
case IN_REG_END:
case REG_BEG_END:
nstarted--;
break;
}
isinsideafter = (!ncollisions
&& (nstarted || (nstartedfw && !nblockfw)));
if (!isinsidebefore && isinsideafter)
{
starta = from_low_priv ? ALIGN_UP (events[j].pos, align)
: ALIGN_DOWN (events[j].pos - size, align) + size;
numstarted = j;
}
if (isinsidebefore && !isinsideafter && from_low_priv)
{
target = starta;
if (target < start)
target = start;
if (target + size <= end && target + size <= events[j].pos)
/* Found an usable address. */
goto found;
}
if (isinsidebefore && !isinsideafter && !from_low_priv)
{
target = starta - size;
if (target > end - size)
target = end - size;
if (target >= start && target >= events[j].pos)
goto found;
}
}
}
grub_mm_base = base_saved;
grub_free (events);
@ -480,9 +492,24 @@ malloc_in_range (struct grub_relocator *rel,
found:
{
int inreg = 0, regbeg = 0, fwin = 0, fwb = 0, ncol = 0;
int last_start = 0;
for (j = 0; j < N; j++)
{
int typepre;
if (ncol)
typepre = -1;
else if (regbeg)
typepre = CHUNK_TYPE_REGION_START;
else if (inreg)
typepre = CHUNK_TYPE_IN_REGION;
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
else if (fwin && !fwb)
typepre = CHUNK_TYPE_FIRMWARE;
#endif
else
typepre = -1;
if (j != 0 && events[j - 1].pos != events[j].pos)
{
grub_addr_t alloc_start, alloc_end;
@ -490,28 +517,84 @@ malloc_in_range (struct grub_relocator *rel,
alloc_end = min (events[j].pos, target + size);
if (alloc_end > alloc_start)
{
grub_dprintf ("relocator", "%d\n", last_start);
if (events[last_start].type == REG_BEG_START
|| events[last_start].type == IN_REG_START)
switch (typepre)
{
if (events[last_start].type == REG_BEG_START &&
(grub_addr_t) (events[last_start].reg + 1) > target)
allocate_regstart (alloc_start, alloc_end - alloc_start,
events[last_start].reg,
events[last_start].regancestor,
events[last_start].hancestor);
else
allocate_inreg (alloc_start, alloc_end - alloc_start,
events[last_start].head,
events[last_start].hancestor,
events[last_start].reg);
case CHUNK_TYPE_REGION_START:
allocate_regstart (alloc_start, alloc_end - alloc_start,
events[last_start].reg,
events[last_start].regancestor,
events[last_start].hancestor);
break;
case CHUNK_TYPE_IN_REGION:
allocate_inreg (alloc_start, alloc_end - alloc_start,
events[last_start].head,
events[last_start].hancestor,
events[last_start].reg);
break;
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
case CHUNK_TYPE_FIRMWARE:
/* The failure here can be very expensive. */
if (!grub_relocator_firmware_alloc_region (alloc_start,
alloc_end - alloc_start))
{
grub_dprintf ("relocator",
"firmware allocation 0x%x-0x%x failed.\n",
alloc_start, alloc_end);
if (from_low_priv)
start = alloc_end;
else
end = alloc_start;
goto retry;
}
break;
#endif
}
nallocs++;
}
}
if (is_start (events[j].type))
last_start = j;
switch (events[j].type)
{
case REG_BEG_START:
case IN_REG_START:
if (events[j].type == REG_BEG_START &&
(grub_addr_t) (events[j].reg + 1) > target)
regbeg++;
else
inreg++;
last_start = j;
break;
case REG_BEG_END:
case IN_REG_END:
inreg = regbeg = 0;
break;
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
case REG_FIRMWARE_START:
fwin++;
break;
case REG_FIRMWARE_END:
fwin--;
break;
case FIRMWARE_BLOCK_START:
fwb++;
break;
case FIRMWARE_BLOCK_END:
fwb--;
break;
#endif
case COLLISION_START:
ncol++;
break;
case COLLISION_END:
ncol--;
break;
}
}
}
@ -538,9 +621,24 @@ malloc_in_range (struct grub_relocator *rel,
{
int last_start = 0;
int inreg = 0, regbeg = 0, fwin = 0, fwb = 0, ncol = 0;
int cural = 0;
for (j = 0; j < N; j++)
{
int typepre;
if (ncol)
typepre = -1;
else if (regbeg)
typepre = CHUNK_TYPE_REGION_START;
else if (inreg)
typepre = CHUNK_TYPE_IN_REGION;
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
else if (fwin && !fwb)
typepre = CHUNK_TYPE_FIRMWARE;
#endif
else
typepre = -1;
if (j != 0 && events[j - 1].pos != events[j].pos)
{
grub_addr_t alloc_start, alloc_end;
@ -548,23 +646,76 @@ malloc_in_range (struct grub_relocator *rel,
alloc_end = min (events[j].pos, target + size);
if (alloc_end > alloc_start)
{
res->subchunks[cural].start = alloc_start;
res->subchunks[cural].size = alloc_end - alloc_start;
if (res->subchunks[last_start].type == IN_REG_START)
res->subchunks[cural].type = CHUNK_TYPE_IN_REGION;
else if (res->subchunks[last_start].type == REG_BEG_START)
grub_dprintf ("relocator", "subchunk 0x%x-0x%x, %d\n",
alloc_start, alloc_end, typepre);
res->subchunks[cural].type = typepre;
if (typepre == CHUNK_TYPE_REGION_START)
{
res->subchunks[cural].type = CHUNK_TYPE_REGION_START;
res->subchunks[cural].host_start
= (grub_addr_t) events[last_start].reg;
}
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
if (typepre == CHUNK_TYPE_REGION_START
|| typepre == CHUNK_TYPE_FIRMWARE)
{
/* FIXME: react on out of memory. */
struct grub_relocator_extra_block *ne;
ne = grub_malloc (sizeof (*ne));
ne->start = alloc_start;
ne->end = alloc_end;
ne->next = extra_blocks;
extra_blocks = ne;
}
#endif
cural++;
}
}
if (is_start (events[j].type))
last_start = j;
switch (events[j].type)
{
case REG_BEG_START:
case IN_REG_START:
if (events[j].type == REG_BEG_START &&
(grub_addr_t) (events[j].reg + 1) > target)
regbeg++;
else
inreg++;
last_start = j;
break;
case REG_BEG_END:
case IN_REG_END:
inreg = regbeg = 0;
break;
#if GRUB_RELOCATOR_HAVE_FIRMWARE_REQUESTS
case REG_FIRMWARE_START:
fwin++;
break;
case REG_FIRMWARE_END:
fwin--;
break;
case FIRMWARE_BLOCK_START:
fwb++;
break;
case FIRMWARE_BLOCK_END:
fwb--;
break;
#endif
case COLLISION_START:
ncol++;
break;
case COLLISION_END:
ncol--;
break;
}
}
}
res->src = target;
res->size = size;
grub_dprintf ("relocator", "allocated: %x %x\n", target, size);
@ -801,6 +952,7 @@ grub_relocator_alloc_chunk_align (struct grub_relocator *rel, void **src,
return GRUB_ERR_NONE;
}
/* FIXME: remove extra blocks. */
void
grub_relocator_unload (struct grub_relocator *rel)
{
@ -856,6 +1008,10 @@ grub_relocator_unload (struct grub_relocator *rel)
grub_free (h + 1);
break;
}
case CHUNK_TYPE_FIRMWARE:
grub_relocator_firmware_free_region (chunk->subchunks[i].start,
chunk->subchunks[i].size);
break;
}
next = chunk->next;
grub_free (chunk->subchunks);