From 118f4fb31e857fce875636bebf7cdded2b01601e Mon Sep 17 00:00:00 2001 From: okuji Date: Sun, 28 May 2006 03:20:18 +0000 Subject: [PATCH] 2006-05-28 Yoshinori K. Okuji * fs/hfsplus.c (grub_hfsplus_btree_recoffset): Moved to near the top. (grub_hfsplus_btree_recptr): Likewise. (grub_hfsplus_find_block): Do not take RETRY any longer. Use FILEBLOCK both to pass a block number and store next block number. (grub_hfsplus_read_block): Rewritten heavily to support an extent overflow file correctly. Specify errors appropriately, because fshelp expects that GRUB_ERRNO is set when fails. Reuse grub_hfsplus_btree_recptr to get the pointer to a found key. (grub_hfsplus_btree_search): Return 1 instead of 0 when no match is found. * conf/i386-efi.rmk (pkgdata_MODULES): Added _linux.mod and linux.mod. (_linux_mod_SOURCES): New variable. (_linux_mod_CFLAGS): Likewise. (_linux_mod_LDFLAGS): Likewise. (linux_mod_SOURCES): Likewise. (linux_mod_CFLAGS): Likewise. (linux_mod_LDFLAGS): Likewise. * DISTLIST: Added loader/i386/efi/linux.c, loader/i386/efi/linux_normal.c and include/grub/i386/efi/loader.h. * loader/i386/efi/linux.c: New file. * loader/i386/efi/linux_normal.c: Likewise. * include/grub/i386/efi/loader.h: Likewise. --- ChangeLog | 32 ++ DISTLIST | 3 + NEWS | 27 ++ conf/i386-efi.mk | 115 +++++- conf/i386-efi.rmk | 13 +- fs/hfsplus.c | 113 +++--- include/grub/i386/efi/loader.h | 28 ++ loader/i386/efi/linux.c | 688 +++++++++++++++++++++++++++++++++ loader/i386/efi/linux_normal.c | 61 +++ 9 files changed, 1022 insertions(+), 58 deletions(-) create mode 100644 include/grub/i386/efi/loader.h create mode 100644 loader/i386/efi/linux.c create mode 100644 loader/i386/efi/linux_normal.c diff --git a/ChangeLog b/ChangeLog index 8fc234de0..d8e56e540 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,35 @@ +2006-05-28 Yoshinori K. Okuji + + * fs/hfsplus.c (grub_hfsplus_btree_recoffset): Moved to near the + top. + (grub_hfsplus_btree_recptr): Likewise. + (grub_hfsplus_find_block): Do not take RETRY any longer. Use + FILEBLOCK both to pass a block number and store next block + number. + (grub_hfsplus_read_block): Rewritten heavily to support an extent + overflow file correctly. Specify errors appropriately, because + fshelp expects that GRUB_ERRNO is set when fails. Reuse + grub_hfsplus_btree_recptr to get the pointer to a found key. + (grub_hfsplus_btree_search): Return 1 instead of 0 when no match + is found. + + * conf/i386-efi.rmk (pkgdata_MODULES): Added _linux.mod and + linux.mod. + (_linux_mod_SOURCES): New variable. + (_linux_mod_CFLAGS): Likewise. + (_linux_mod_LDFLAGS): Likewise. + (linux_mod_SOURCES): Likewise. + (linux_mod_CFLAGS): Likewise. + (linux_mod_LDFLAGS): Likewise. + + * DISTLIST: Added loader/i386/efi/linux.c, + loader/i386/efi/linux_normal.c and + include/grub/i386/efi/loader.h. + + * loader/i386/efi/linux.c: New file. + * loader/i386/efi/linux_normal.c: Likewise. + * include/grub/i386/efi/loader.h: Likewise. + 2006-05-27 Yoshinori K. Okuji * commands/blocklist.c: New file. diff --git a/DISTLIST b/DISTLIST index 431da8f5d..f17038c9f 100644 --- a/DISTLIST +++ b/DISTLIST @@ -122,6 +122,7 @@ include/grub/efi/time.h include/grub/i386/setjmp.h include/grub/i386/types.h include/grub/i386/efi/kernel.h +include/grub/i386/efi/loader.h include/grub/i386/efi/time.h include/grub/i386/pc/biosdisk.h include/grub/i386/pc/boot.h @@ -200,6 +201,8 @@ kern/sparc64/ieee1275/init.c kern/sparc64/ieee1275/openfw.c loader/efi/chainloader.c loader/efi/chainloader_normal.c +loader/i386/efi/linux.c +loader/i386/efi/linux_normal.c loader/i386/pc/chainloader.c loader/i386/pc/chainloader_normal.c loader/i386/pc/linux.c diff --git a/NEWS b/NEWS index 917cd153c..0a6f5c0bd 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,30 @@ +New in 1.94: + +* Fix several serious bugs in HFS+. + +* Add experimental EFI support. Chainloading and Linux loading are + supported at the moment. + +* Add a new command "blocklist" to show a block list. + +* Use --with-platform to specify a boot environment. For now, efi, + ieee1275 and pc are supported. + +* Use the filename "kernel.elf" instead of "grubof" on ieee1275. + +* Install GRUB into pkglibdir instead of pkgdatadir. + +* Support environmental variables. You can export variables by the + command "export". + +* Remove the commands "default" and "timeout". They are now variables. + +* Add the commands "source" and "." to include a file. + +* Implement experimental Video API and a new terminal "gfxterm" based + on the Video API. + + New in 1.93 - 2006-03-10: * Add support for the HFS+ wrapper. diff --git a/conf/i386-efi.mk b/conf/i386-efi.mk index 4c9219f24..b673d42dd 100644 --- a/conf/i386-efi.mk +++ b/conf/i386-efi.mk @@ -111,7 +111,8 @@ genmoddep-util_genmoddep.d: util/genmoddep.c #grub_install_SOURCES = util/efi/pc/grub-install.in # Modules. -pkgdata_MODULES = kernel.mod normal.mod _chain.mod chain.mod +pkgdata_MODULES = kernel.mod normal.mod _chain.mod chain.mod \ + _linux.mod linux.mod # For kernel.mod. kernel_mod_EXPORTS = no @@ -1047,4 +1048,116 @@ fs-chain_mod-loader_efi_chainloader_normal.lst: loader/efi/chainloader_normal.c chain_mod_CFLAGS = $(COMMON_CFLAGS) chain_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For _linux.mod. +_linux_mod_SOURCES = loader/i386/efi/linux.c +CLEANFILES += _linux.mod mod-_linux.o mod-_linux.c pre-_linux.o _linux_mod-loader_i386_efi_linux.o und-_linux.lst +ifneq ($(_linux_mod_EXPORTS),no) +CLEANFILES += def-_linux.lst +DEFSYMFILES += def-_linux.lst +endif +MOSTLYCLEANFILES += _linux_mod-loader_i386_efi_linux.d +UNDSYMFILES += und-_linux.lst + +_linux.mod: pre-_linux.o mod-_linux.o + -rm -f $@ + $(CC) $(_linux_mod_LDFLAGS) $(LDFLAGS) -Wl,-r,-d -o $@ $^ + $(STRIP) --strip-unneeded -K grub_mod_init -K grub_mod_fini -R .note -R .comment $@ + +pre-_linux.o: _linux_mod-loader_i386_efi_linux.o + -rm -f $@ + $(CC) $(_linux_mod_LDFLAGS) $(LDFLAGS) -Wl,-r,-d -o $@ $^ + +mod-_linux.o: mod-_linux.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(_linux_mod_CFLAGS) -c -o $@ $< + +mod-_linux.c: moddep.lst genmodsrc.sh + sh $(srcdir)/genmodsrc.sh '_linux' $< > $@ || (rm -f $@; exit 1) + +ifneq ($(_linux_mod_EXPORTS),no) +def-_linux.lst: pre-_linux.o + $(NM) -g --defined-only -P -p $< | sed 's/^\([^ ]*\).*/\1 _linux/' > $@ +endif + +und-_linux.lst: pre-_linux.o + echo '_linux' > $@ + $(NM) -u -P -p $< | cut -f1 -d' ' >> $@ + +_linux_mod-loader_i386_efi_linux.o: loader/i386/efi/linux.c + $(CC) -Iloader/i386/efi -I$(srcdir)/loader/i386/efi $(CPPFLAGS) $(CFLAGS) $(_linux_mod_CFLAGS) -c -o $@ $< + +_linux_mod-loader_i386_efi_linux.d: loader/i386/efi/linux.c + set -e; $(CC) -Iloader/i386/efi -I$(srcdir)/loader/i386/efi $(CPPFLAGS) $(CFLAGS) $(_linux_mod_CFLAGS) -M $< | sed 's,linux\.o[ :]*,_linux_mod-loader_i386_efi_linux.o $@ : ,g' > $@; [ -s $@ ] || rm -f $@ + +-include _linux_mod-loader_i386_efi_linux.d + +CLEANFILES += cmd-_linux_mod-loader_i386_efi_linux.lst fs-_linux_mod-loader_i386_efi_linux.lst +COMMANDFILES += cmd-_linux_mod-loader_i386_efi_linux.lst +FSFILES += fs-_linux_mod-loader_i386_efi_linux.lst + +cmd-_linux_mod-loader_i386_efi_linux.lst: loader/i386/efi/linux.c gencmdlist.sh + set -e; $(CC) -Iloader/i386/efi -I$(srcdir)/loader/i386/efi $(CPPFLAGS) $(CFLAGS) $(_linux_mod_CFLAGS) -E $< | sh $(srcdir)/gencmdlist.sh _linux > $@ || (rm -f $@; exit 1) + +fs-_linux_mod-loader_i386_efi_linux.lst: loader/i386/efi/linux.c genfslist.sh + set -e; $(CC) -Iloader/i386/efi -I$(srcdir)/loader/i386/efi $(CPPFLAGS) $(CFLAGS) $(_linux_mod_CFLAGS) -E $< | sh $(srcdir)/genfslist.sh _linux > $@ || (rm -f $@; exit 1) + + +_linux_mod_CFLAGS = $(COMMON_CFLAGS) +_linux_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For linux.mod. +linux_mod_SOURCES = loader/i386/efi/linux_normal.c +CLEANFILES += linux.mod mod-linux.o mod-linux.c pre-linux.o linux_mod-loader_i386_efi_linux_normal.o und-linux.lst +ifneq ($(linux_mod_EXPORTS),no) +CLEANFILES += def-linux.lst +DEFSYMFILES += def-linux.lst +endif +MOSTLYCLEANFILES += linux_mod-loader_i386_efi_linux_normal.d +UNDSYMFILES += und-linux.lst + +linux.mod: pre-linux.o mod-linux.o + -rm -f $@ + $(CC) $(linux_mod_LDFLAGS) $(LDFLAGS) -Wl,-r,-d -o $@ $^ + $(STRIP) --strip-unneeded -K grub_mod_init -K grub_mod_fini -R .note -R .comment $@ + +pre-linux.o: linux_mod-loader_i386_efi_linux_normal.o + -rm -f $@ + $(CC) $(linux_mod_LDFLAGS) $(LDFLAGS) -Wl,-r,-d -o $@ $^ + +mod-linux.o: mod-linux.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(linux_mod_CFLAGS) -c -o $@ $< + +mod-linux.c: moddep.lst genmodsrc.sh + sh $(srcdir)/genmodsrc.sh 'linux' $< > $@ || (rm -f $@; exit 1) + +ifneq ($(linux_mod_EXPORTS),no) +def-linux.lst: pre-linux.o + $(NM) -g --defined-only -P -p $< | sed 's/^\([^ ]*\).*/\1 linux/' > $@ +endif + +und-linux.lst: pre-linux.o + echo 'linux' > $@ + $(NM) -u -P -p $< | cut -f1 -d' ' >> $@ + +linux_mod-loader_i386_efi_linux_normal.o: loader/i386/efi/linux_normal.c + $(CC) -Iloader/i386/efi -I$(srcdir)/loader/i386/efi $(CPPFLAGS) $(CFLAGS) $(linux_mod_CFLAGS) -c -o $@ $< + +linux_mod-loader_i386_efi_linux_normal.d: loader/i386/efi/linux_normal.c + set -e; $(CC) -Iloader/i386/efi -I$(srcdir)/loader/i386/efi $(CPPFLAGS) $(CFLAGS) $(linux_mod_CFLAGS) -M $< | sed 's,linux_normal\.o[ :]*,linux_mod-loader_i386_efi_linux_normal.o $@ : ,g' > $@; [ -s $@ ] || rm -f $@ + +-include linux_mod-loader_i386_efi_linux_normal.d + +CLEANFILES += cmd-linux_mod-loader_i386_efi_linux_normal.lst fs-linux_mod-loader_i386_efi_linux_normal.lst +COMMANDFILES += cmd-linux_mod-loader_i386_efi_linux_normal.lst +FSFILES += fs-linux_mod-loader_i386_efi_linux_normal.lst + +cmd-linux_mod-loader_i386_efi_linux_normal.lst: loader/i386/efi/linux_normal.c gencmdlist.sh + set -e; $(CC) -Iloader/i386/efi -I$(srcdir)/loader/i386/efi $(CPPFLAGS) $(CFLAGS) $(linux_mod_CFLAGS) -E $< | sh $(srcdir)/gencmdlist.sh linux > $@ || (rm -f $@; exit 1) + +fs-linux_mod-loader_i386_efi_linux_normal.lst: loader/i386/efi/linux_normal.c genfslist.sh + set -e; $(CC) -Iloader/i386/efi -I$(srcdir)/loader/i386/efi $(CPPFLAGS) $(CFLAGS) $(linux_mod_CFLAGS) -E $< | sh $(srcdir)/genfslist.sh linux > $@ || (rm -f $@; exit 1) + + +linux_mod_CFLAGS = $(COMMON_CFLAGS) +linux_mod_LDFLAGS = $(COMMON_LDFLAGS) + include $(srcdir)/conf/common.mk diff --git a/conf/i386-efi.rmk b/conf/i386-efi.rmk index e8d62b999..6e8e36199 100644 --- a/conf/i386-efi.rmk +++ b/conf/i386-efi.rmk @@ -67,7 +67,8 @@ genmoddep_SOURCES = util/genmoddep.c #grub_install_SOURCES = util/efi/pc/grub-install.in # Modules. -pkgdata_MODULES = kernel.mod normal.mod _chain.mod chain.mod +pkgdata_MODULES = kernel.mod normal.mod _chain.mod chain.mod \ + _linux.mod linux.mod # For kernel.mod. kernel_mod_EXPORTS = no @@ -115,4 +116,14 @@ chain_mod_SOURCES = loader/efi/chainloader_normal.c chain_mod_CFLAGS = $(COMMON_CFLAGS) chain_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For _linux.mod. +_linux_mod_SOURCES = loader/i386/efi/linux.c +_linux_mod_CFLAGS = $(COMMON_CFLAGS) +_linux_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For linux.mod. +linux_mod_SOURCES = loader/i386/efi/linux_normal.c +linux_mod_CFLAGS = $(COMMON_CFLAGS) +linux_mod_LDFLAGS = $(COMMON_LDFLAGS) + include $(srcdir)/conf/common.mk diff --git a/fs/hfsplus.c b/fs/hfsplus.c index 08b507824..cf9dbe5b8 100644 --- a/fs/hfsplus.c +++ b/fs/hfsplus.c @@ -226,15 +226,41 @@ static grub_dl_t my_mod; #endif +/* Return the offset of the record with the index INDEX, in the node + NODE which is part of the B+ tree BTREE. */ +static inline unsigned int +grub_hfsplus_btree_recoffset (struct grub_hfsplus_btree *btree, + struct grub_hfsplus_btnode *node, int index) +{ + char *cnode = (char *) node; + grub_uint16_t *recptr; + recptr = (grub_uint16_t *) (&cnode[btree->nodesize + - index * sizeof (grub_uint16_t) - 2]); + return grub_be_to_cpu16 (*recptr); +} + +/* Return a pointer to the record with the index INDEX, in the node + NODE which is part of the B+ tree BTREE. */ +static inline struct grub_hfsplus_key * +grub_hfsplus_btree_recptr (struct grub_hfsplus_btree *btree, + struct grub_hfsplus_btnode *node, int index) +{ + char *cnode = (char *) node; + unsigned int offset; + offset = grub_hfsplus_btree_recoffset (btree, node, index); + return (struct grub_hfsplus_key *) &cnode[offset]; +} + + /* Find the extent that points to FILEBLOCK. If it is not in one of the 8 extents described by EXTENT, return -1. In that case set - RETRY to the last block that was found in the last extent. */ + FILEBLOCK to the next block. */ static int grub_hfsplus_find_block (struct grub_hfsplus_extent *extent, - int fileblock, int *retry) + int *fileblock) { int i; - grub_size_t blksleft = fileblock; + grub_size_t blksleft = *fileblock; /* First lookup the file in the given extents. */ for (i = 0; i < 8; i++) @@ -244,7 +270,7 @@ grub_hfsplus_find_block (struct grub_hfsplus_extent *extent, blksleft -= grub_be_to_cpu32 (extent[i].count); } - *retry = fileblock - blksleft; + *fileblock = blksleft; return -1; } @@ -263,21 +289,19 @@ static int grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya, static int grub_hfsplus_read_block (grub_fshelp_node_t node, int fileblock) { - struct grub_hfsplus_extkey_internal extoverflow; - struct grub_hfsplus_extkey *keyfound; - int blk; struct grub_hfsplus_btnode *nnode = 0; - int ptr; - int retry = 0; - + int blksleft = fileblock; struct grub_hfsplus_extent *extents = &node->extents[0]; while (1) { - char *cnode; + struct grub_hfsplus_extkey *key; + struct grub_hfsplus_extkey_internal extoverflow; + int blk; + int ptr; /* Try to find this block in the current set of extents. */ - blk = grub_hfsplus_find_block (extents, fileblock, &retry); + blk = grub_hfsplus_find_block (extents, &blksleft); /* The previous iteration of this loop allocated memory. The code above used this memory, it can be free'ed now. */ @@ -293,30 +317,33 @@ grub_hfsplus_read_block (grub_fshelp_node_t node, int fileblock) the extent overflow file. If this happens, you found a bug... */ if (node->fileid == GRUB_HFSPLUS_FILEID_OVERFLOW) - break; + { + grub_error (GRUB_ERR_READ_ERROR, + "extra extents found in an extend overflow file"); + break; + } /* Set up the key to look for in the extent overflow file. */ extoverflow.fileid = node->fileid; - extoverflow.start = retry; + extoverflow.start = fileblock - blksleft; if (grub_hfsplus_btree_search (&node->data->extoverflow_tree, (struct grub_hfsplus_key_internal *) &extoverflow, grub_hfsplus_cmp_extkey, &nnode, &ptr)) - break; + { + grub_error (GRUB_ERR_READ_ERROR, + "no block found for the file id 0x%x and the block offset 0x%x", + node->fileid, fileblock); + break; + } - cnode = (char *) nnode; - - /* The extent overflow file has a 8 extents right after the key. */ - keyfound = (struct grub_hfsplus_extkey *) - &cnode[(int) cnode[node->data->extoverflow_tree.nodesize - ptr - 1]]; - extents = (struct grub_hfsplus_extent *) - ((char *) keyfound + sizeof (struct grub_hfsplus_extkey)); + /* The extent overflow file has 8 extents right after the key. */ + key = (struct grub_hfsplus_extkey *) + grub_hfsplus_btree_recptr (&node->data->extoverflow_tree, nnode, ptr); + extents = (struct grub_hfsplus_extent *) (key + 1); /* The block wasn't found. Perhaps the next iteration will find - it. The last block we found is stored in FILEBLOCK now. */ - /* XXX: Multiple iterations for locating the right extent was - not tested enough... */ - fileblock = retry; + it. The last block we found is stored in BLKSLEFT now. */ } grub_free (nnode); @@ -517,32 +544,6 @@ grub_hfsplus_cmp_extkey (struct grub_hfsplus_key *keya, return diff; } -/* Return the offset of the record with the index INDEX, in the node - NODE which is part of the B+ tree BTREE. */ -static inline unsigned int -grub_hfsplus_btree_recoffset (struct grub_hfsplus_btree *btree, - struct grub_hfsplus_btnode *node, int index) -{ - char *cnode = (char *) node; - grub_uint16_t *recptr; - recptr = (grub_uint16_t *) (&cnode[btree->nodesize - - index * sizeof (grub_uint16_t) - 2]); - return grub_be_to_cpu16 (*recptr); -} - -/* Return a pointer to the record with the index INDEX, in the node - NODE which is part of the B+ tree BTREE. */ -static inline struct grub_hfsplus_key * -grub_hfsplus_btree_recptr (struct grub_hfsplus_btree *btree, - struct grub_hfsplus_btnode *node, int index) -{ - char *cnode = (char *) node; - unsigned int offset; - offset = grub_hfsplus_btree_recoffset (btree, node, index); - return (struct grub_hfsplus_key *) &cnode[offset]; -} - - static char * grub_hfsplus_read_symlink (grub_fshelp_node_t node) { @@ -639,8 +640,8 @@ grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree, for (rec = 0; rec < grub_be_to_cpu16 (nodedesc->count); rec++) { struct grub_hfsplus_key *currkey; - currkey = grub_hfsplus_btree_recptr (btree, nodedesc, rec); - + currkey = grub_hfsplus_btree_recptr (btree, nodedesc, rec); + /* The action that has to be taken depend on the type of record. */ if (nodedesc->type == GRUB_HFSPLUS_BTNODE_TYPE_LEAF @@ -675,13 +676,13 @@ grub_hfsplus_btree_search (struct grub_hfsplus_btree *btree, } } - /* No match was found, no record with this key exists in the + /* No match is found, no record with this key exists in the tree. */ if (! match) { *matchnode = 0; grub_free (node); - return 0; + return 1; } } } diff --git a/include/grub/i386/efi/loader.h b/include/grub/i386/efi/loader.h new file mode 100644 index 000000000..7b1fb3033 --- /dev/null +++ b/include/grub/i386/efi/loader.h @@ -0,0 +1,28 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2004,2006 Free Software Foundation, Inc. + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef GRUB_LOADER_MACHINE_HEADER +#define GRUB_LOADER_MACHINE_HEADER 1 + +/* It is necessary to export these functions, because normal mode commands + reuse rescue mode commands. */ +void grub_rescue_cmd_linux (int argc, char *argv[]); +void grub_rescue_cmd_initrd (int argc, char *argv[]); + +#endif /* ! GRUB_LOADER_MACHINE_HEADER */ diff --git a/loader/i386/efi/linux.c b/loader/i386/efi/linux.c new file mode 100644 index 000000000..3c95868fc --- /dev/null +++ b/loader/i386/efi/linux.c @@ -0,0 +1,688 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006 Free Software Foundation, Inc. + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NEXT_MEMORY_DESCRIPTOR(desc, size) \ + ((grub_efi_memory_descriptor_t *) ((char *) (desc) + (size))) + +static grub_dl_t my_mod; + +static grub_size_t linux_mem_size; +static int loaded; +static void *real_mode_mem; +static void *prot_mode_mem; +static void *initrd_mem; +static grub_efi_uintn_t real_mode_pages; +static grub_efi_uintn_t prot_mode_pages; +static grub_efi_uintn_t initrd_pages; +static void *mmap_buf; + +static grub_uint8_t gdt[] __attribute__ ((aligned(16))) = + { + /* NULL. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Reserved. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Code segment. */ + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x9A, 0xCF, 0x00, + /* Data segment. */ + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x92, 0xCF, 0x00 + }; + +struct gdt_descriptor +{ + grub_uint16_t dummy; + grub_uint16_t limit; + grub_uint32_t base; +} __attribute__ ((aligned(4), packed)); + +static struct gdt_descriptor gdt_desc = + { + 0, + sizeof (gdt) - 1, + (grub_addr_t) gdt + }; + +struct idt_descriptor +{ + grub_uint16_t dummy; + grub_uint16_t limit; + grub_uint32_t base; +} __attribute__ ((aligned(4))); + +static struct idt_descriptor idt_desc = + { + 0, + 0, + 0 + }; + +static inline grub_size_t +page_align (grub_size_t size) +{ + return (size + (1 << 12) - 1) & (~((1 << 12) - 1)); +} + +/* Find the optimal number of pages for the memory map. Is it better to + move this code to efi/mm.c? */ +static grub_efi_uintn_t +find_mmap_size (void) +{ + static grub_efi_uintn_t mmap_size = 0; + + if (mmap_size != 0) + return mmap_size; + + mmap_size = (1 << 12); + while (1) + { + int ret; + grub_efi_memory_descriptor_t *mmap; + grub_efi_uintn_t desc_size; + + mmap = grub_malloc (mmap_size); + if (! mmap) + return 0; + + ret = grub_efi_get_memory_map (&mmap_size, mmap, 0, &desc_size, 0); + grub_free (mmap); + + if (ret < 0) + grub_fatal ("cannot get memory map"); + else if (ret > 0) + break; + + mmap_size += (1 << 12); + } + + /* Increase the size a bit for safety, because GRUB allocates more on + later, and EFI itself may allocate more. */ + mmap_size += (1 << 12); + + return page_align (mmap_size); +} + +static void +free_pages (void) +{ + if (real_mode_mem) + { + grub_efi_free_pages ((grub_addr_t) real_mode_mem, real_mode_pages); + real_mode_mem = 0; + } + + if (prot_mode_mem) + { + grub_efi_free_pages ((grub_addr_t) prot_mode_mem, prot_mode_pages); + prot_mode_mem = 0; + } + + if (initrd_mem) + { + grub_efi_free_pages ((grub_addr_t) initrd_mem, initrd_pages); + initrd_mem = 0; + } +} + +/* Allocate pages for the real mode code and the protected mode code + for linux as well as a memory map buffer. */ +static int +allocate_pages (grub_size_t real_size, grub_size_t prot_size) +{ + grub_efi_uintn_t desc_size; + grub_efi_memory_descriptor_t *mmap, *mmap_end; + grub_efi_uintn_t mmap_size, tmp_mmap_size; + grub_efi_memory_descriptor_t *desc; + + /* Make sure that each size is aligned to a page boundary. */ + real_size = page_align (real_size + GRUB_DISK_SECTOR_SIZE); + prot_size = page_align (prot_size); + mmap_size = find_mmap_size (); + + grub_dprintf ("linux", "real_size = %x, prot_size = %x, mmap_size = %x\n", + real_size, prot_size, mmap_size); + + /* Calculate the number of pages; Combine the real mode code with + the memory map buffer for simplicity. */ + real_mode_pages = ((real_size + mmap_size) >> 12); + prot_mode_pages = (prot_size >> 12); + + /* Initialize the memory pointers with NULL for convenience. */ + real_mode_mem = 0; + prot_mode_mem = 0; + + /* Read the memory map temporarily, to find free space. */ + mmap = grub_malloc (mmap_size); + if (! mmap) + return 0; + + tmp_mmap_size = mmap_size; + if (grub_efi_get_memory_map (&tmp_mmap_size, mmap, 0, &desc_size, 0) <= 0) + grub_fatal ("cannot get memory map"); + + mmap_end = NEXT_MEMORY_DESCRIPTOR (mmap, tmp_mmap_size); + + /* First, find free pages for the real mode code + and the memory map buffer. */ + for (desc = mmap; + desc < mmap_end; + desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size)) + { + /* Probably it is better to put the real mode code in the traditional + space for safety. */ + if (desc->type == GRUB_EFI_CONVENTIONAL_MEMORY + && desc->physical_start <= 0x90000 + && desc->num_pages >= real_mode_pages) + { + grub_efi_physical_address_t physical_end; + grub_efi_physical_address_t addr; + + physical_end = desc->physical_start + (desc->num_pages << 12); + if (physical_end > 0x90000) + physical_end = 0x90000; + + grub_dprintf ("linux", "physical_start = %x, physical_end = %x\n", + (unsigned) desc->physical_start, + (unsigned) physical_end); + addr = physical_end - real_size - mmap_size; + if (addr < 0x10000) + continue; + + grub_dprintf ("linux", "trying to allocate %u pages at %x\n", + real_mode_pages, (unsigned) addr); + real_mode_mem = grub_efi_allocate_pages (addr, real_mode_pages); + if (! real_mode_mem) + grub_fatal ("cannot allocate pages"); + + desc->num_pages -= real_mode_pages; + break; + } + } + + if (! real_mode_mem) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate real mode pages"); + goto fail; + } + + mmap_buf = (void *) ((char *) real_mode_mem + real_size); + + /* Next, find free pages for the protected mode code. */ + /* XXX what happens if anything is using this address? */ + prot_mode_mem = grub_efi_allocate_pages (0x100000, prot_mode_pages); + if (! prot_mode_mem) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, + "cannot allocate protected mode pages"); + goto fail; + } + + grub_free (mmap); + return 1; + + fail: + grub_free (mmap); + free_pages (); + return 0; +} + +static grub_err_t +grub_linux_boot (void) +{ + struct linux_kernel_header *lh; + struct linux_kernel_params *params; + grub_efi_uintn_t mmap_size; + grub_efi_uintn_t map_key; + grub_efi_uintn_t desc_size; + grub_efi_uint32_t desc_version; + + lh = real_mode_mem; + params = real_mode_mem; + + grub_dprintf ("linux", "code32_start = %x, idt_desc = %x, gdt_desc = %x\n", + (unsigned) lh->code32_start, (grub_addr_t) &(idt_desc.limit), + (grub_addr_t) &(gdt_desc.limit)); + grub_dprintf ("linux", "idt = %x:%x, gdt = %x:%x\n", + (unsigned) idt_desc.limit, (unsigned) idt_desc.base, + (unsigned) gdt_desc.limit, (unsigned) gdt_desc.base); + mmap_size = find_mmap_size (); + if (grub_efi_get_memory_map (&mmap_size, mmap_buf, &map_key, + &desc_size, &desc_version) <= 0) + grub_fatal ("cannot get memory map"); + + if (! grub_efi_exit_boot_services (map_key)) + grub_fatal ("cannot exit boot services"); + + /* Note that no boot services are available from here. */ + + /* Hardware interrupts are not safe any longer. */ + asm volatile ("cli" : : ); + + /* Pass EFI parameters. */ + params->efi_mem_desc_size = desc_size; + params->efi_mem_desc_version = desc_version; + params->efi_mmap = (grub_addr_t) mmap_buf; + params->efi_mmap_size = mmap_size; + + /* Pass parameters. */ + asm volatile ("movl %0, %%esi" : : "m" (real_mode_mem)); + asm volatile ("movl %0, %%ecx" : : "m" (lh->code32_start)); + asm volatile ("xorl %%ebx, %%ebx" : : ); + + /* Load the IDT and the GDT for the bootstrap. */ + asm volatile ("lidt %0" : : "m" (idt_desc.limit)); + asm volatile ("lgdt %0" : : "m" (gdt_desc.limit)); + + /* Enter Linux. */ + asm volatile ("jmp *%%ecx" : : ); + + /* Never reach here. */ + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_unload (void) +{ + free_pages (); + grub_dl_unref (my_mod); + loaded = 0; + return GRUB_ERR_NONE; +} + +void +grub_rescue_cmd_linux (int argc, char *argv[]) +{ + grub_file_t file = 0; + struct linux_kernel_header lh; + struct linux_kernel_params *params; + grub_uint8_t setup_sects; + grub_size_t real_size, prot_size; + grub_ssize_t len; + int i; + char *dest; + + grub_dl_ref (my_mod); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified"); + goto fail; + } + + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + if (grub_file_read (file, (char *) &lh, sizeof (lh)) != sizeof (lh)) + { + grub_error (GRUB_ERR_READ_ERROR, "cannot read the linux header"); + goto fail; + } + + if (lh.boot_flag != grub_cpu_to_le16 (0xaa55)) + { + grub_error (GRUB_ERR_BAD_OS, "invalid magic number"); + goto fail; + } + + if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS) + { + grub_error (GRUB_ERR_BAD_OS, "too many setup sectors"); + goto fail; + } + + /* EFI support is quite new, so reject old versions. */ + if (lh.header != grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE) + || grub_le_to_cpu16 (lh.version) < 0x0203) + { + grub_error (GRUB_ERR_BAD_OS, "too old version"); + goto fail; + } + + /* I'm not sure how to support zImage on EFI. */ + if (! (lh.loadflags & GRUB_LINUX_FLAG_BIG_KERNEL)) + { + grub_error (GRUB_ERR_BAD_OS, "zImage is not supported"); + goto fail; + } + + setup_sects = lh.setup_sects; + + /* If SETUP_SECTS is not set, set it to the default (4). */ + if (! setup_sects) + setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS; + + real_size = setup_sects << GRUB_DISK_SECTOR_BITS; + prot_size = grub_file_size (file) - real_size - GRUB_DISK_SECTOR_SIZE; + + if (! allocate_pages (real_size, prot_size)) + goto fail; + + /* XXX Linux assumes that only elilo can boot Linux on EFI!!! */ + lh.type_of_loader = 0x50; + + lh.cl_magic = GRUB_LINUX_CL_MAGIC; + lh.cl_offset = GRUB_LINUX_CL_END_OFFSET; + lh.cmd_line_ptr = (char *) real_mode_mem + GRUB_LINUX_CL_OFFSET; + lh.ramdisk_image = 0; + lh.ramdisk_size = 0; + + params = (struct linux_kernel_params *) &lh; + + /* These are not needed to be precise, because Linux uses these values + only to raise an error when the decompression code cannot find good + space. */ + params->ext_mem = ((32 * 0x100000) >> 10); + params->alt_mem = ((32 * 0x100000) >> 10); + + params->video_cursor_x = grub_efi_system_table->con_out->mode->cursor_column; + params->video_cursor_y = grub_efi_system_table->con_out->mode->cursor_row; + params->video_page = 0; /* ??? */ + params->video_mode = grub_efi_system_table->con_out->mode->mode; + params->video_width = (grub_getwh () >> 8); + params->video_ega_bx = 0; + params->video_height = (grub_getwh () & 0xff); + params->have_vga = 0; + params->font_size = 16; /* XXX */ + + /* No VBE on EFI. */ + params->lfb_width = 0; + params->lfb_height = 0; + params->lfb_depth = 0; + params->lfb_base = 0; + params->lfb_size = 0; + params->lfb_line_len = 0; + params->red_mask_size = 0; + params->red_field_pos = 0; + params->green_mask_size = 0; + params->green_field_pos = 0; + params->blue_mask_size = 0; + params->blue_field_pos = 0; + params->reserved_mask_size = 0; + params->reserved_field_pos = 0; + params->vesapm_segment = 0; + params->vesapm_offset = 0; + params->lfb_pages = 0; + params->vesa_attrib = 0; + + /* No APM on EFI. */ + params->apm_version = 0; + params->apm_code_segment = 0; + params->apm_entry = 0; + params->apm_16bit_code_segment = 0; + params->apm_data_segment = 0; + params->apm_flags = 0; + params->apm_code_len = 0; + params->apm_data_len = 0; + + /* XXX is there any way to use SpeedStep on EFI? */ + params->ist_signature = 0; + params->ist_command = 0; + params->ist_event = 0; + params->ist_perf_level = 0; + + /* Let the kernel probe the information. */ + grub_memset (params->hd0_drive_info, 0, sizeof (params->hd0_drive_info)); + grub_memset (params->hd1_drive_info, 0, sizeof (params->hd1_drive_info)); + + /* No MCA on EFI. */ + params->rom_config_len = 0; + + params->efi_signature = GRUB_LINUX_EFI_SIGNATURE; /* XXX not used */ + params->efi_system_table = (grub_addr_t) grub_efi_system_table; + /* The other EFI parameters are filled when booting. */ + + /* No need to fake the BIOS's memory map. */ + params->mmap_size = 0; + + /* Let the kernel probe the information. */ + params->ps_mouse = 0; + + /* Clear padding for future compatibility. */ + grub_memset (params->padding1, 0, sizeof (params->padding1)); + grub_memset (params->padding2, 0, sizeof (params->padding2)); + grub_memset (params->padding3, 0, sizeof (params->padding3)); + grub_memset (params->padding4, 0, sizeof (params->padding4)); + grub_memset (params->padding5, 0, sizeof (params->padding5)); + grub_memset (params->padding6, 0, sizeof (params->padding6)); + grub_memset (params->padding7, 0, sizeof (params->padding7)); + grub_memset (params->padding8, 0, sizeof (params->padding8)); + grub_memset (params->padding9, 0, sizeof (params->padding9)); + + /* Put the real mode code at the real location. */ + grub_memmove (real_mode_mem, &lh, sizeof (lh)); + + len = real_size + GRUB_DISK_SECTOR_SIZE - sizeof (lh); + if (grub_file_read (file, (char *) real_mode_mem + sizeof (lh), len) != len) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, "Couldn't read file"); + goto fail; + } + + /* XXX there is no way to know if the kernel really supports EFI. */ + grub_printf (" [Linux-EFI, setup=0x%x, size=0x%x]\n", + real_size, prot_size); + + /* Detect explicitly specified memory size, if any. */ + linux_mem_size = 0; + for (i = 1; i < argc; i++) + if (grub_memcmp (argv[i], "mem=", 4) == 0) + { + char *val = argv[i] + 4; + + linux_mem_size = grub_strtoul (val, &val, 0); + + if (grub_errno) + { + grub_errno = GRUB_ERR_NONE; + linux_mem_size = 0; + } + else + { + int shift = 0; + + switch (grub_tolower (val[0])) + { + case 'g': + shift += 10; + case 'm': + shift += 10; + case 'k': + shift += 10; + default: + break; + } + + /* Check an overflow. */ + if (linux_mem_size > (~0UL >> shift)) + linux_mem_size = 0; + else + linux_mem_size <<= shift; + } + } + + /* Specify the boot file. */ + dest = grub_stpcpy ((char *) real_mode_mem + GRUB_LINUX_CL_OFFSET, + "BOOT_IMAGE="); + dest = grub_stpcpy (dest, argv[0]); + + /* Copy kernel parameters. */ + for (i = 1; + i < argc + && dest + grub_strlen (argv[i]) + 1 < ((char *) real_mode_mem + + GRUB_LINUX_CL_END_OFFSET); + i++) + { + *dest++ = ' '; + dest = grub_stpcpy (dest, argv[i]); + } + + len = prot_size; + if (grub_file_read (file, (char *) GRUB_LINUX_BZIMAGE_ADDR, len) != len) + grub_error (GRUB_ERR_FILE_READ_ERROR, "Couldn't read file"); + + if (grub_errno == GRUB_ERR_NONE) + { + grub_loader_set (grub_linux_boot, grub_linux_unload, 1); + loaded = 1; + } + + fail: + + if (file) + grub_file_close (file); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_dl_unref (my_mod); + loaded = 0; + } +} + +void +grub_rescue_cmd_initrd (int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_ssize_t size; + grub_addr_t addr_min, addr_max; + grub_addr_t addr; + grub_efi_uintn_t mmap_size; + grub_efi_memory_descriptor_t *desc; + grub_efi_uintn_t desc_size; + struct linux_kernel_header *lh; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "No module specified"); + goto fail; + } + + if (! loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "You need to load the kernel first."); + goto fail; + } + + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + size = grub_file_size (file); + initrd_pages = (page_align (size) >> 12); + + lh = (struct linux_kernel_header *) real_mode_mem; + + addr_max = grub_cpu_to_le32 (lh->initrd_addr_max); + if (linux_mem_size != 0 && linux_mem_size < addr_max) + addr_max = linux_mem_size; + + /* Linux 2.3.xx has a bug in the memory range check, so avoid + the last page. + Linux 2.2.xx has a bug in the memory range check, which is + worse than that of Linux 2.3.xx, so avoid the last 64kb. */ + addr_max -= 0x10000; + + /* Usually, the compression ratio is about 50%. */ + addr_min = (grub_addr_t) prot_mode_mem + ((prot_mode_pages * 3) << 12); + + /* Find the highest address to put the initrd. */ + mmap_size = find_mmap_size (); + if (grub_efi_get_memory_map (&mmap_size, mmap_buf, 0, &desc_size, 0) <= 0) + grub_fatal ("cannot get memory map"); + + addr = 0; + for (desc = mmap_buf; + desc < NEXT_MEMORY_DESCRIPTOR (mmap_buf, mmap_size); + desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size)) + { + if (desc->type == GRUB_EFI_CONVENTIONAL_MEMORY + && desc->physical_start >= addr_min + && desc->physical_start + size < addr_max + && desc->num_pages >= initrd_pages) + { + grub_efi_physical_address_t physical_end; + + physical_end = desc->physical_start + (desc->num_pages << 12); + if (physical_end > addr_max) + physical_end = addr_max; + + if (physical_end > addr) + addr = physical_end - page_align (size); + } + } + + if (addr == 0) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "no free pages available"); + goto fail; + } + + initrd_mem = grub_efi_allocate_pages (addr, initrd_pages); + if (! initrd_mem) + grub_fatal ("cannot allocate pages"); + + if (grub_file_read (file, initrd_mem, size) != size) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, "Couldn't read file"); + goto fail; + } + + grub_printf (" [Initrd, addr=0x%x, size=0x%x]\n", + addr, size); + + lh->ramdisk_image = addr; + lh->ramdisk_size = size; + lh->root_dev = 0x0100; /* XXX */ + + fail: + if (file) + grub_file_close (file); +} + + +GRUB_MOD_INIT(linux) +{ + grub_rescue_register_command ("linux", + grub_rescue_cmd_linux, + "load linux"); + grub_rescue_register_command ("initrd", + grub_rescue_cmd_initrd, + "load initrd"); + my_mod = mod; +} + +GRUB_MOD_FINI(linux) +{ + grub_rescue_unregister_command ("linux"); + grub_rescue_unregister_command ("initrd"); +} diff --git a/loader/i386/efi/linux_normal.c b/loader/i386/efi/linux_normal.c new file mode 100644 index 000000000..bc0fd84e6 --- /dev/null +++ b/loader/i386/efi/linux_normal.c @@ -0,0 +1,61 @@ +/* linux_normal.c - boot linux */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2004,2005,2006 Free Software Foundation, Inc. + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +static grub_err_t +grub_normal_linux_command (struct grub_arg_list *state __attribute__ ((unused)), + int argc, char **args) +{ + grub_rescue_cmd_linux (argc, args); + return grub_errno; +} + + +static grub_err_t +grub_normal_initrd_command (struct grub_arg_list *state __attribute__ ((unused)), + int argc, char **args) +{ + grub_rescue_cmd_initrd (argc, args); + return grub_errno; +} + +GRUB_MOD_INIT(linux_normal) +{ + (void) mod; /* To stop warning. */ + grub_register_command ("linux", grub_normal_linux_command, + GRUB_COMMAND_FLAG_BOTH, + "linux FILE [ARGS...]", + "Load a linux kernel.", 0); + + grub_register_command ("initrd", grub_normal_initrd_command, + GRUB_COMMAND_FLAG_BOTH, + "initrd FILE", + "Load an initrd.", 0); +} + +GRUB_MOD_FINI(linux_normal) +{ + grub_unregister_command ("linux"); + grub_unregister_command ("initrd"); +}