Index: commands/i386/pc/drivemap.c =================================================================== RCS file: commands/i386/pc/drivemap.c diff -N commands/i386/pc/drivemap.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ commands/i386/pc/drivemap.c 10 Jun 2008 20:02:19 -0000 @@ -0,0 +1,348 @@ +/* drivemap.c - command to manage the BIOS drive mappings. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Uncomment the following line to enable debugging output */ +/* #define DRIVEMAP_DEBUG */ + +#ifdef DRIVEMAP_DEBUG +# define DBG_PRINTF(...) grub_printf(__VA_ARGS__) +#else +# define DBG_PRINTF(...) +#endif + +static const struct grub_arg_option options[] = + { + {"list", 'l', 0, "show the current mappings", 0, 0}, + {"reset", 'r', 0, "reset all mappings to the default values", 0, 0}, + {0, 0, 0, 0, 0, 0} + }; + +/* ASSEMBLY SYMBOLS/VARS/FUNCS - start */ + +/* realmode far ptr = 2 * 16b */ +extern grub_uint32_t EXPORT_VAR(grub_drivemap_int13_oldhandler); +/* Size of the section to be copied */ +extern grub_uint16_t EXPORT_VAR(grub_drivemap_int13_size); + +/* NOT a typo - just need the symbol's address with &symbol */ +typedef void grub_symbol_t; +extern grub_symbol_t EXPORT_VAR(grub_drivemap_int13_handler_base); +extern grub_symbol_t EXPORT_VAR(grub_drivemap_int13_mapstart); + +void EXPORT_FUNC(grub_drivemap_int13_handler)(void); + +/* ASSEMBLY SYMBOLS/VARS/FUNCS - end */ + +static grub_preboot_hookid insthandler_hook = 0; + +typedef struct drivemap_node +{ + grub_uint8_t newdrive; + grub_uint8_t redirto; + struct drivemap_node *next; +} drivemap_node_t; + +static drivemap_node_t *drivemap = 0; +static grub_err_t drivemap_install_int13_handler(void); + +static grub_err_t +drivemap_set (grub_uint8_t newdrive, grub_uint8_t redirto) + /* Puts the specified mapping into the table, replacing an existing mapping + * for newdrive or adding a new one if required. */ +{ + drivemap_node_t *mapping = 0, *search = drivemap; + while (search) + { + if (search->newdrive == newdrive) + { + mapping = search; + break; + } + search = search->next; + } + + if (mapping) /* There was a mapping already in place, modify it */ + mapping->redirto = redirto; + else /* Create a new mapping and add it to the head of the list */ + { + mapping = grub_malloc (sizeof (drivemap_node_t)); + if (!mapping) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate map entry"); + mapping->newdrive = newdrive; + mapping->redirto = redirto; + mapping->next = drivemap; + drivemap = mapping; + } + return GRUB_ERR_NONE; +} + +static void +drivemap_remove (grub_uint8_t newdrive) + /* Removes the mapping for newdrive from the table. If there is no mapping, + * then this function behaves like a no-op on the map. */ +{ + drivemap_node_t *mapping = 0, *search = drivemap, *previous = 0; + while (search) + { + if (search->newdrive == newdrive) + { + mapping = search; + break; + } + previous = search; + search = search->next; + } + if (mapping) /* Found */ + { + if (previous) + previous->next = mapping->next; + else drivemap = mapping->next; /* Entry was head of list */ + grub_free (mapping); + } +} + +static grub_err_t parse_biosdisk (const char *name, grub_uint8_t *disknum) +{ + if (!name) return GRUB_ERR_BAD_ARGUMENT; + if (name[0] == '(') + name++; /* Skip the first ( in (hd0) - disk_open wants just the name! */ + grub_disk_t disk = grub_disk_open (name); + if (!disk) + return GRUB_ERR_UNKNOWN_DEVICE; + else + { + enum grub_disk_dev_id id = disk->dev->id; + if (disknum) + *disknum = disk->id; /* Only valid, of course if it's a biosdisk */ + grub_disk_close (disk); + return (GRUB_DISK_DEVICE_BIOSDISK_ID != id) ? + GRUB_ERR_BAD_DEVICE : GRUB_ERR_NONE; + } +} + +static grub_err_t revparse_biosdisk(const grub_uint8_t dnum, const char **output) +{ + grub_err_t retval = GRUB_ERR_UNKNOWN_DEVICE; + auto int find (const char *name); + int find (const char *name) + { + grub_disk_t disk = grub_disk_open (name); + if (!disk) + return 0; + else + { + int found = 0; + if (dnum == disk->id && GRUB_DISK_DEVICE_BIOSDISK_ID == disk->dev->id) + { + found = 1; + *output = name; + retval = GRUB_ERR_NONE; + } + grub_disk_close (disk); + return found; + } + } + + grub_disk_dev_iterate (&find); + return retval; +} + +static grub_err_t +grub_cmd_drivemap (struct grub_arg_list *state, int argc, char **args) +{ + if (state[0].set) /* Show: list mappings */ + { + if (!drivemap) + grub_printf ("No drives have been remapped"); + else + { + grub_printf ("Showing only remapped drives. Drives that have had " + "their slot assigned to another one and have not been " + "themselves remapped will become inaccessible through " + "the BIOS routines to the booted OS.\n\n"); + grub_printf ("Mapped\tGRUB\n"); + drivemap_node_t *curnode = drivemap; + while (curnode) + { + const char *dname = 0; + grub_err_t err = revparse_biosdisk (curnode->redirto, &dname); + if (err != GRUB_ERR_NONE) + return grub_error (err, "invalid mapping: non-existent disk or" + "not managed by the BIOS"); + grub_printf("0x%02x\t%4s\n", curnode->newdrive, dname); + curnode = curnode->next; + } + } + } + else if (state[1].set) /* Reset: just delete all mappings */ + { + if (drivemap) + { + drivemap_node_t *curnode = drivemap, *prevnode = 0; + while (curnode) + { + prevnode = curnode; + curnode = curnode->next; + grub_free (prevnode); + } + drivemap = 0; + } + } + else + { + if (argc != 2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "two arguments required"); + grub_uint8_t mapfrom = 0; + grub_err_t err = parse_biosdisk (args[0], &mapfrom); + if (err != GRUB_ERR_NONE) + return grub_error (err, "invalid disk or not managed by the BIOS"); + + grub_errno = GRUB_ERR_NONE; + unsigned long mapto = grub_strtoul (args[1], 0, 0); + if (grub_errno != GRUB_ERR_NONE) + return grub_error (grub_errno, + "BIOS disk number must be between 0 and 255"); + else if (mapto == mapfrom) /* Reset to default */ + { + DBG_PRINTF ("Removing the mapping for %s (%02x)", args[0], mapfrom); + drivemap_remove (mapfrom); + } + else /* Map */ + { + DBG_PRINTF ("Mapping %s (%02x) to %02x\n", args[0], mapfrom, mapto); + return drivemap_set ((grub_uint8_t)mapto, mapfrom); + } + } + + return GRUB_ERR_NONE; +} + +typedef struct __attribute__ ((packed)) int13map_node +{ + grub_uint8_t disknum; + grub_uint8_t mapto; +} int13map_node_t; + +/* The min amount of mem that must remain free after installing the handler. + * 32 KiB is just above 0x7C00-0x7E00, where the bootsector is loaded. */ +#define MIN_FREE_MEM_KB 32 +#define INT13H_OFFSET(x) ( ((grub_uint8_t*)(x)) - ((grub_uint8_t*)&grub_drivemap_int13_handler_base) ) +#define INT13H_REBASE(x) ( (void*) (((grub_uint8_t*)handler_base) + (x)) ) +#define INT13H_TONEWADDR(x) INT13H_REBASE( INT13H_OFFSET( x ) ) +/* Int13h handler installer - reserves conventional memory for the handler, + * copies it over and sets the IVT entry for int13h. + * This code rests on the assumption that GRUB does not activate any kind of + * memory mapping, since it accesses realmode structures by their absolute + * addresses, like the IVT at 0 or the BDA at 0x400 */ +static grub_err_t drivemap_install_int13_handler(void) +{ + grub_size_t entries = 0; + drivemap_node_t *curentry = drivemap; + while (curentry) /* Count entries to prepare a contiguous map block */ + { + entries++; + curentry = curentry->next; + } + if (0 == entries) + { + DBG_PRINTF ("drivemap: No drives remapped, int13h handler not installed"); + return GRUB_ERR_NONE; /* No need to install the int13h handler */ + } + else + { + DBG_PRINTF ("drivemap: Installing int13h handler...\n"); + grub_uint32_t *ivtslot = (grub_uint32_t*)0x0000004c; + + /* Save the pointer to the old int13h handler */ + grub_drivemap_int13_oldhandler = *ivtslot; + DBG_PRINTF ("Old int13 handler at %04x:%04x\n", + (grub_drivemap_int13_oldhandler >> 16) & 0x0ffff, + grub_drivemap_int13_oldhandler & 0x0ffff); + + /* Reserve a section of conventional memory as "BIOS memory" for handler: + * BDA offset 0x13 contains the top of such memory */ + grub_uint16_t *bpaMemInKb = (grub_uint16_t*)0x00000413; + DBG_PRINTF ("Top of conventional memory: %u KiB\n", *bpaMemInKb); + grub_size_t totalSize = grub_drivemap_int13_size + + (entries + 1) * sizeof(int13map_node_t); + grub_uint16_t payloadSizeKb = (totalSize >> 10) + + (((totalSize % 1024) == 0) ? 0 : 1); + if ((*bpaMemInKb - payloadSizeKb) < MIN_FREE_MEM_KB) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "refusing to install int13 handler, not enough free memory after"); + DBG_PRINTF ("Payload is %u b long, reserving %u Kb\n", totalSize, payloadSizeKb); + *bpaMemInKb -= payloadSizeKb; + + /* Copy int13h handler chunk to reserved area */ + grub_uint8_t *handler_base = (grub_uint8_t*)(*bpaMemInKb << 10); + DBG_PRINTF ("Copying int13 handler to: %p\n", handler_base); + grub_memcpy (handler_base, &grub_drivemap_int13_handler_base, grub_drivemap_int13_size); + + /* Copy the mappings to the reserved area */ + curentry = drivemap; + grub_size_t i; + int13map_node_t *handler_map = (int13map_node_t*) INT13H_TONEWADDR(&grub_drivemap_int13_mapstart); + DBG_PRINTF ("Target map at %p, copying mappings...\n", handler_map); + for (i = 0; i < entries && curentry; i++, curentry = curentry->next) + { + handler_map[i].disknum = curentry->newdrive; + handler_map[i].mapto = curentry->redirto; + DBG_PRINTF ("\t#%d: 0x%02x <- 0x%02x\n", i, handler_map[i].disknum, handler_map[i].mapto); + } + /* Signal end-of-map */ + handler_map[i].disknum = 0; + handler_map[i].mapto = 0; + DBG_PRINTF ("\t#%d: 0x%02x <- 0x%02x (end)\n", i, handler_map[i].disknum, handler_map[i].mapto); + + /* Install our function as the int13h handler in the IVT */ + grub_uint32_t ivtentry = ((grub_uint32_t)handler_base) << 12; /* Segment address */ + ivtentry |= (grub_uint16_t) INT13H_OFFSET(grub_drivemap_int13_handler); + DBG_PRINTF ("New int13 handler IVT pointer: %04x:%04x\n", + (ivtentry >> 16) & 0x0ffff, ivtentry & 0x0ffff); + *ivtslot = ivtentry; + + return GRUB_ERR_NONE; + } +} +#undef INT13H_TONEWADDR +#undef INT13H_REBASE +#undef INT13H_OFFSET + +GRUB_MOD_INIT(drivemap) +{ + (void)mod; /* To stop warning. */ + grub_register_command ("drivemap", grub_cmd_drivemap, GRUB_COMMAND_FLAG_BOTH, + "drivemap -s | -r | (hdX) newdrivenum", "Manage the BIOS drive mappings", options); + insthandler_hook = grub_loader_register_preboot (&drivemap_install_int13_handler, 1); +} + +GRUB_MOD_FINI(drivemap) +{ + grub_loader_unregister_preboot (insthandler_hook); + insthandler_hook = 0; + grub_unregister_command ("drivemap"); +} Index: commands/i386/pc/drivemap_int13h.S =================================================================== RCS file: commands/i386/pc/drivemap_int13h.S diff -N commands/i386/pc/drivemap_int13h.S --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ commands/i386/pc/drivemap_int13h.S 10 Jun 2008 20:02:19 -0000 @@ -0,0 +1,128 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2005,2006,2007,2008 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 . + */ + + +/* + * Note: These functions defined in this file may be called from C. + * Be careful of that you must not modify some registers. Quote + * from gcc-2.95.2/gcc/config/i386/i386.h: + + 1 for registers not available across function calls. + These must include the FIXED_REGISTERS and also any + registers that can be used without being saved. + The latter must include the registers where values are returned + and the register where structure-value addresses are passed. + Aside from that, you can include as many other registers as you like. + + ax,dx,cx,bx,si,di,bp,sp,st,st1,st2,st3,st4,st5,st6,st7,arg +{ 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } + */ + +/* + * Note: GRUB is compiled with the options -mrtd and -mregparm=3. + * So the first three arguments are passed in %eax, %edx, and %ecx, + * respectively, and if a function has a fixed number of arguments + * and the number if greater than three, the function must return + * with "ret $N" where N is ((the number of arguments) - 3) * 4. + */ + +/* + * This is the area for all of the special variables. + */ + +#include + +#define GRUB_DRIVEMAP_INT13H_OFFSET(x) ((x) - grub_drivemap_int13_handler_base) + +/* Copy starts here. When deployed, this label must be segment-aligned */ +VARIABLE(grub_drivemap_int13_handler_base) + +VARIABLE(grub_drivemap_int13_oldhandler) + .word 0xdead, 0xbeef +/* Drivemap module - INT 13h handler - BIOS HD map */ +/* We need to use relative addressing, and with CS to top it all, since we + * must make as few changes to the registers as possible. Pity we're not on + * amd64: rIP-relative addressing would make life easier here. + */ +.code16 +FUNCTION(grub_drivemap_int13_handler) + push %bp + mov %sp, %bp + push %ax /* We'll need it later to determine the used BIOS function */ + + /* Map the drive number (always in DL?) */ + push %ax + push %bx + push %si + mov $GRUB_DRIVEMAP_INT13H_OFFSET(grub_drivemap_int13_mapstart), %bx + xor %si, %si +1:movw %cs:(%bx,%si), %ax + cmp %ah, %al + jz 3f /* DRV=DST => map end - drive not remapped, leave DL as-is */ + cmp %dl, %al + jz 2f /* Found - drive remapped, modify DL */ + add $2, %si + jmp 1b /* Not found, but more remaining, loop */ +2:mov %ah, %dl +3:pop %si + pop %bx + xchgw %ax, -4(%bp) /* Recover the old AX and save the map entry for later */ + + push %bp + /* Simulate interrupt call: push flags and do a far call in order to set + * the stack the way the old handler expects it so that its iret works */ + push 6(%bp) + movw (%bp), %bp /* Restore the caller BP (is this needed and/or sensible?) */ + lcall *%cs:GRUB_DRIVEMAP_INT13H_OFFSET(grub_drivemap_int13_oldhandler) + pop %bp /* The pushed flags were removed by iret */ + /* Set the saved flags to what the int13h handler returned */ + push %ax + pushf + pop %ax + movw %ax, 6(%bp) + pop %ax + + /* Reverse map any returned drive number if the data returned includes it. + * The only func that does this seems to be origAH = 0x08, but many BIOS + * refs say retDL = # of drives connected. However, the GRUB Legacy code + * treats this as the _drive number_ and "undoes" the remapping. Thus, + * this section has been disabled for testing if it's required */ +# cmpb $0x08, -1(%bp) /* Caller's AH */ +# jne 4f +# push %es +# pushal +# mov $0, %di +# mov $0xb800, %bx +# mov %bx, %es +# xchgw %ax, -4(%bp) /* Map entry used */ +# cmp %ah, %al /* DRV=DST => drive not remapped */ +# je 4f +# mov %ah, %dl /* Undo remap */ + +4:mov %bp, %sp + pop %bp + iret +/* This label MUST be at the end of the copied block, since the installer code + * reserves additional space for mappings at runtime and copies them over it */ +.align 2 +VARIABLE(grub_drivemap_int13_mapstart) + .space 0 +.code32 /* Copy stops here */ +VARIABLE(grub_drivemap_int13_size) + .word GRUB_DRIVEMAP_INT13H_OFFSET(grub_drivemap_int13_size) +#undef GRUB_DRIVEMAP_INT13H_OFFSET Index: conf/i386-pc.rmk =================================================================== RCS file: /sources/grub/grub2/conf/i386-pc.rmk,v retrieving revision 1.119 diff -u -p -r1.119 i386-pc.rmk --- conf/i386-pc.rmk 30 May 2008 04:20:47 -0000 1.119 +++ conf/i386-pc.rmk 10 Jun 2008 20:02:26 -0000 @@ -152,7 +152,7 @@ pkglib_MODULES = biosdisk.mod _chain.mod vbe.mod vbetest.mod vbeinfo.mod video.mod gfxterm.mod \ videotest.mod play.mod bitmap.mod tga.mod cpuid.mod serial.mod \ ata.mod vga.mod memdisk.mod jpeg.mod png.mod pci.mod lspci.mod \ - aout.mod _bsd.mod bsd.mod + aout.mod _bsd.mod bsd.mod drivemap.mod # For biosdisk.mod. biosdisk_mod_SOURCES = disk/i386/pc/biosdisk.c @@ -319,4 +319,11 @@ bsd_mod_SOURCES = loader/i386/bsd_normal bsd_mod_CFLAGS = $(COMMON_CFLAGS) bsd_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For drivemap.mod. +drivemap_mod_SOURCES = commands/i386/pc/drivemap.c \ + commands/i386/pc/drivemap_int13h.S +drivemap_mod_ASFLAGS = $(COMMON_ASFLAGS) +drivemap_mod_CFLAGS = $(COMMON_CFLAGS) +drivemap_mod_LDFLAGS = $(COMMON_LDFLAGS) + include $(srcdir)/conf/common.mk Index: include/grub/loader.h =================================================================== RCS file: /sources/grub/grub2/include/grub/loader.h,v retrieving revision 1.9 diff -u -p -r1.9 loader.h --- include/grub/loader.h 21 Jul 2007 23:32:22 -0000 1.9 +++ include/grub/loader.h 10 Jun 2008 20:02:26 -0000 @@ -37,6 +37,19 @@ void EXPORT_FUNC(grub_loader_set) (grub_ /* Unset current loader, if any. */ void EXPORT_FUNC(grub_loader_unset) (void); +typedef struct hooklist_node *grub_preboot_hookid; + +/* Register a function to be called before the boot hook. Returns an id that + can be later used to unregister the preboot (i.e. if module unloaded). If + abort_on_error is set, the boot sequence will abort if any of the registered + functions return anything else than GRUB_ERR_NONE */ +grub_preboot_hookid EXPORT_FUNC(grub_loader_register_preboot) + (grub_err_t (*hook) (void), int abort_on_error); + +/* Unregister a preboot hook by the id returned by loader_register_preboot. + This functions becomes a no-op if no such function is registered */ +void EXPORT_FUNC(grub_loader_unregister_preboot) (grub_preboot_hookid id); + /* Call the boot hook in current loader. This may or may not return, depending on the setting by grub_loader_set. */ grub_err_t EXPORT_FUNC(grub_loader_boot) (void); Index: kern/loader.c =================================================================== RCS file: /sources/grub/grub2/kern/loader.c,v retrieving revision 1.9 diff -u -p -r1.9 loader.c --- kern/loader.c 21 Jul 2007 23:32:26 -0000 1.9 +++ kern/loader.c 10 Jun 2008 20:02:27 -0000 @@ -61,11 +61,78 @@ grub_loader_unset(void) grub_loader_loaded = 0; } +struct hooklist_node +{ + grub_err_t (*hook) (void); + int abort_on_error; + struct hooklist_node *next; +}; + +static struct hooklist_node *preboot_hooks = 0; + +grub_preboot_hookid +grub_loader_register_preboot(grub_err_t (*hook) (void), int abort_on_error) +{ + if (0 == hook) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "preboot hoook must not be null"); + return 0; + } + grub_preboot_hookid newentry = grub_malloc (sizeof (struct hooklist_node)); + if (!newentry) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot alloc a hookinfo structure"); + return 0; + } + else + { + newentry->hook = hook; + newentry->abort_on_error = abort_on_error; + newentry->next = preboot_hooks; + preboot_hooks = newentry; + return newentry; + } +} + +void +grub_loader_unregister_preboot(grub_preboot_hookid id) +{ + if (0 == id) + return; + grub_preboot_hookid entry = 0, search = preboot_hooks, previous = 0; + while (search) + { + if (search == id) + { + entry = search; + break; + } + previous = search; + search = search->next; + } + if (entry) /* Found */ + { + if (previous) + previous->next = entry->next; + else preboot_hooks = entry->next; /* Entry was head of list */ + grub_free (entry); + } +} + grub_err_t grub_loader_boot (void) { if (! grub_loader_loaded) return grub_error (GRUB_ERR_NO_KERNEL, "no loaded kernel"); + + grub_preboot_hookid entry = preboot_hooks; + while (entry) + { + grub_err_t retVal = entry->hook(); + if (retVal != GRUB_ERR_NONE && entry->abort_on_error) + return retVal; + entry = entry->next; + } if (grub_loader_noreturn) grub_machine_fini ();